Fix path of presentation thumbnail when loading from service files. Fixes bug 1463703.

Fix PDF reader using wrong maindisplay size.
Fix log-traceback when trying to remove servicemanager files before eg. powerpoint has closed the files.
Improve network exception handling.
Fix timer that checks for presentation slide change, remove powerpoint event handler that did the same.
Hide the main impress window when loading a presentation, but make it visible for a moment when starting presentation. Fixes bug 1420356.
Improve the detection of powerpoints presentation window by only looking at the filename root.
Fix traceback when going live with presentation while blanked to desktop.
Set service_item.name to "images" for pdfs.

bzr-revno: 2544
Fixes: https://launchpad.net/bugs/1463703, https://launchpad.net/bugs/1420356
This commit is contained in:
Tomas Groth 2015-06-14 20:38:44 +02:00 committed by Raoul Snyman
commit f071284b0f
12 changed files with 80 additions and 38 deletions

View File

@ -33,7 +33,7 @@ import ntpath
from PyQt4 import QtGui from PyQt4 import QtGui
from openlp.core.common import RegistryProperties, Settings, translate, AppLocation from openlp.core.common import RegistryProperties, Settings, translate, AppLocation, md5_hash
from openlp.core.lib import ImageSource, build_icon, clean_tags, expand_tags, create_thumb from openlp.core.lib import ImageSource, build_icon, clean_tags, expand_tags, create_thumb
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -326,6 +326,12 @@ class ServiceItem(RegistryProperties):
# If the item should have a display title but this frame doesn't have one, we make one up # If the item should have a display title but this frame doesn't have one, we make one up
if self.is_capable(ItemCapabilities.HasDisplayTitle) and not display_title: if self.is_capable(ItemCapabilities.HasDisplayTitle) and not display_title:
display_title = translate('OpenLP.ServiceItem', '[slide %d]') % (len(self._raw_frames) + 1) display_title = translate('OpenLP.ServiceItem', '[slide %d]') % (len(self._raw_frames) + 1)
# Update image path to match servicemanager location if file was loaded from service
if image and not self.has_original_files and self.name == 'presentations':
file_location = os.path.join(path, file_name)
file_location_hash = md5_hash(file_location.encode('utf-8'))
image = os.path.join(AppLocation.get_section_data_path(self.name), 'thumbnails',
file_location_hash, ntpath.basename(image))
self._raw_frames.append({'title': file_name, 'image': image, 'path': path, self._raw_frames.append({'title': file_name, 'image': image, 'path': path,
'display_title': display_title, 'notes': notes}) 'display_title': display_title, 'notes': notes})
self._new_item() self._new_item()

View File

@ -1106,8 +1106,6 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow, RegistryProperties):
self.image_manager.stop_manager = True self.image_manager.stop_manager = True
while self.image_manager.image_thread.isRunning(): while self.image_manager.image_thread.isRunning():
time.sleep(0.1) time.sleep(0.1)
# Clean temporary files used by services
self.service_manager_contents.clean_up()
if save_settings: if save_settings:
if Settings().value('advanced/save current plugin'): if Settings().value('advanced/save current plugin'):
Settings().setValue('advanced/current media plugin', self.media_tool_box.currentIndex()) Settings().setValue('advanced/current media plugin', self.media_tool_box.currentIndex())
@ -1124,6 +1122,8 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow, RegistryProperties):
if self.live_controller.display: if self.live_controller.display:
self.live_controller.display.close() self.live_controller.display.close()
self.live_controller.display = None self.live_controller.display = None
# Clean temporary files used by services
self.service_manager_contents.clean_up()
if is_win(): if is_win():
# Needed for Windows to stop crashes on exit # Needed for Windows to stop crashes on exit
Registry().remove('application') Registry().remove('application')

View File

@ -24,6 +24,7 @@ The :mod:`openlp.core.utils` module provides the utility libraries for OpenLP.
""" """
from datetime import datetime from datetime import datetime
from distutils.version import LooseVersion from distutils.version import LooseVersion
from http.client import HTTPException
import logging import logging
import locale import locale
import os import os
@ -414,6 +415,11 @@ def get_web_page(url, header=None, update_openlp=False):
page = None page = None
if retries > CONNECTION_RETRIES: if retries > CONNECTION_RETRIES:
raise raise
except socket.gaierror:
log.exception('Socket gaierror: {}'.format(url))
page = None
if retries > CONNECTION_RETRIES:
raise
except ConnectionRefusedError: except ConnectionRefusedError:
log.exception('ConnectionRefused: {}'.format(url)) log.exception('ConnectionRefused: {}'.format(url))
page = None page = None
@ -425,6 +431,11 @@ def get_web_page(url, header=None, update_openlp=False):
page = None page = None
if retries > CONNECTION_RETRIES: if retries > CONNECTION_RETRIES:
raise raise
except HTTPException:
log.exception('HTTPException error: {}'.format(url))
page = None
if retries > CONNECTION_RETRIES:
raise
except: except:
# Don't know what's happening, so reraise the original # Don't know what's happening, so reraise the original
raise raise

View File

@ -748,7 +748,10 @@ def get_soup_for_bible_ref(reference_url, header=None, pre_parse_regex=None, pre
""" """
if not reference_url: if not reference_url:
return None return None
page = get_web_page(reference_url, header, True) try:
page = get_web_page(reference_url, header, True)
except:
page = None
if not page: if not page:
send_error_message('download') send_error_message('download')
return None return None

View File

@ -35,7 +35,7 @@ import logging
import os import os
import time import time
from openlp.core.common import is_win from openlp.core.common import is_win, Registry
if is_win(): if is_win():
from win32com.client import Dispatch from win32com.client import Dispatch
@ -231,21 +231,13 @@ class ImpressDocument(PresentationDocument):
return False return False
self.desktop = desktop self.desktop = desktop
properties = [] properties = []
if not is_win(): properties.append(self.create_property('Hidden', True))
# Recent versions of Impress on Windows won't start the presentation if it starts as minimized. It seems OK
# on Linux though.
properties.append(self.create_property('Minimized', True))
properties = tuple(properties) properties = tuple(properties)
try: try:
self.document = desktop.loadComponentFromURL(url, '_blank', 0, properties) self.document = desktop.loadComponentFromURL(url, '_blank', 0, properties)
except: except:
log.warning('Failed to load presentation %s' % url) log.warning('Failed to load presentation %s' % url)
return False return False
if is_win():
# As we can't start minimized the Impress window gets in the way.
# Either window.setPosSize(0, 0, 200, 400, 12) or .setVisible(False)
window = self.document.getCurrentController().getFrame().getContainerWindow()
window.setVisible(False)
self.presentation = self.document.getPresentation() self.presentation = self.document.getPresentation()
self.presentation.Display = ScreenList().current['number'] + 1 self.presentation.Display = ScreenList().current['number'] + 1
self.control = None self.control = None
@ -382,6 +374,8 @@ class ImpressDocument(PresentationDocument):
""" """
log.debug('start presentation OpenOffice') log.debug('start presentation OpenOffice')
if self.control is None or not self.control.isRunning(): if self.control is None or not self.control.isRunning():
window = self.document.getCurrentController().getFrame().getContainerWindow()
window.setVisible(True)
self.presentation.start() self.presentation.start()
self.control = self.presentation.getController() self.control = self.presentation.getController()
# start() returns before the Component is ready. Try for 15 seconds. # start() returns before the Component is ready. Try for 15 seconds.
@ -390,9 +384,13 @@ class ImpressDocument(PresentationDocument):
time.sleep(0.1) time.sleep(0.1)
sleep_count += 1 sleep_count += 1
self.control = self.presentation.getController() self.control = self.presentation.getController()
window.setVisible(False)
else: else:
self.control.activate() self.control.activate()
self.goto_slide(1) self.goto_slide(1)
# Make sure impress doesn't steal focus, unless we're on a single screen setup
if len(ScreenList().screen_list) > 1:
Registry().get('main_window').activateWindow()
def get_slide_number(self): def get_slide_number(self):
""" """

View File

@ -281,6 +281,7 @@ class PresentationMediaItem(MediaManagerItem):
service_item.add_capability(ItemCapabilities.CanPreview) service_item.add_capability(ItemCapabilities.CanPreview)
service_item.add_capability(ItemCapabilities.CanLoop) service_item.add_capability(ItemCapabilities.CanLoop)
service_item.add_capability(ItemCapabilities.CanAppend) service_item.add_capability(ItemCapabilities.CanAppend)
service_item.name = 'images'
# force a nonexistent theme # force a nonexistent theme
service_item.theme = -1 service_item.theme = -1
for bitem in items: for bitem in items:

View File

@ -243,6 +243,10 @@ 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)
# The document has not been loaded yet, so don't do anything. This can happen when going live with a
# presentation while blanked to desktop.
if not self.doc:
return
# Save the current slide number to be able to return to this slide if the presentation is activated again. # Save the current slide number to be able to return to this slide if the presentation is activated again.
if self.doc.is_active(): if self.doc.is_active():
self.doc.slidenumber = self.doc.get_slide_number() self.doc.slidenumber = self.doc.get_slide_number()
@ -352,6 +356,7 @@ class MessageListener(object):
self.controller = controller self.controller = controller
else: else:
controller.add_handler(self.controllers[self.handler], file, hide_mode, message[3]) controller.add_handler(self.controllers[self.handler], file, hide_mode, message[3])
self.timer.start()
def slide(self, message): def slide(self, message):
""" """
@ -423,6 +428,7 @@ class MessageListener(object):
is_live = message[1] is_live = message[1]
if is_live: if is_live:
self.live_handler.shutdown() self.live_handler.shutdown()
self.timer.stop()
else: else:
self.preview_handler.shutdown() self.preview_handler.shutdown()

View File

@ -89,11 +89,11 @@ class PdfController(PresentationController):
# Analyse the output to see it the program is mudraw, ghostscript or neither # Analyse the output to see it the program is mudraw, ghostscript or neither
for line in runlog.splitlines(): for line in runlog.splitlines():
decoded_line = line.decode() decoded_line = line.decode()
found_mudraw = re.search('usage: mudraw.*', decoded_line) found_mudraw = re.search('usage: mudraw.*', decoded_line, re.IGNORECASE)
if found_mudraw: if found_mudraw:
program_type = 'mudraw' program_type = 'mudraw'
break break
found_gs = re.search('GPL Ghostscript.*', decoded_line) found_gs = re.search('GPL Ghostscript.*', decoded_line, re.IGNORECASE)
if found_gs: if found_gs:
program_type = 'gs' program_type = 'gs'
break break
@ -222,8 +222,8 @@ class PdfDocument(PresentationDocument):
continue continue
# Calculate the ratio from pdf to screen # Calculate the ratio from pdf to screen
if width > 0 and height > 0: if width > 0 and height > 0:
width_ratio = size.right() / width width_ratio = size.width() / width
height_ratio = size.bottom() / height height_ratio = size.height() / height
# return the resolution that should be used. 72 is default. # return the resolution that should be used. 72 is default.
if width_ratio > height_ratio: if width_ratio > height_ratio:
return int(height_ratio * 72) return int(height_ratio * 72)
@ -254,7 +254,7 @@ class PdfDocument(PresentationDocument):
if not os.path.isdir(self.get_temp_folder()): if not os.path.isdir(self.get_temp_folder()):
os.makedirs(self.get_temp_folder()) os.makedirs(self.get_temp_folder())
if self.controller.mudrawbin: if self.controller.mudrawbin:
runlog = check_output([self.controller.mudrawbin, '-w', str(size.right()), '-h', str(size.bottom()), runlog = check_output([self.controller.mudrawbin, '-w', str(size.width()), '-h', str(size.height()),
'-o', os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.file_path], '-o', os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.file_path],
startupinfo=self.startupinfo) startupinfo=self.startupinfo)
elif self.controller.gsbin: elif self.controller.gsbin:

View File

@ -32,7 +32,7 @@ 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 DispatchWithEvents from win32com.client import Dispatch
import win32com import win32com
import win32con import win32con
import winreg import winreg
@ -93,22 +93,9 @@ 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 = DispatchWithEvents('PowerPoint.Application', PowerPointEvents) self.process = Dispatch('PowerPoint.Application')
def kill(self): def kill(self):
""" """
@ -330,6 +317,7 @@ class PowerpointDocument(PresentationDocument):
""" """
log.debug('start_presentation') log.debug('start_presentation')
# SlideShowWindow measures its size/position by points, not pixels # SlideShowWindow measures its size/position by points, not pixels
# https://technet.microsoft.com/en-us/library/dn528846.aspx
try: try:
dpi = win32ui.GetActiveWindow().GetDC().GetDeviceCaps(88) dpi = win32ui.GetActiveWindow().GetDC().GetDeviceCaps(88)
except win32ui.error: except win32ui.error:
@ -378,8 +366,9 @@ class PowerpointDocument(PresentationDocument):
log.debug('compare size: %d and %d, %d and %d, %d and %d, %d and %d' 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))) % (size.y(), top, size.height(), (bottom - top), size.x(), left, size.width(), (right - left)))
log.debug('window title: %s' % window_title) log.debug('window title: %s' % window_title)
filename_root, filename_ext = os.path.splitext(os.path.basename(self.file_path))
if size.y() == top and size.height() == (bottom - top) and size.x() == left and \ 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: size.width() == (right - left) and filename_root in window_title:
log.debug('Found a match and will save the handle') log.debug('Found a match and will save the handle')
self.presentation_hwnd = hwnd self.presentation_hwnd = hwnd
# Stop powerpoint from flashing in the taskbar # Stop powerpoint from flashing in the taskbar

View File

@ -128,7 +128,8 @@ class PresentationTab(SettingsTab):
'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( self.ppt_window_check_box.setText(
translate('PresentationPlugin.PresentationTab', translate('PresentationPlugin.PresentationTab',
'Let PowerPoint control the size and position of the presentation window.')) 'Let PowerPoint control the size and position of the presentation window '
'(workaround for Windows 8 scaling issue).'))
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:'))

View File

@ -28,7 +28,7 @@ from unittest import TestCase
from tests.functional import MagicMock, patch from tests.functional import MagicMock, patch
from tests.utils import assert_length, convert_file_service_item from tests.utils import assert_length, convert_file_service_item
from openlp.core.common import Registry from openlp.core.common import Registry, md5_hash
from openlp.core.lib import ItemCapabilities, ServiceItem, ServiceItemType from openlp.core.lib import ItemCapabilities, ServiceItem, ServiceItemType
VERSE = 'The Lord said to {r}Noah{/r}: \n'\ VERSE = 'The Lord said to {r}Noah{/r}: \n'\
@ -244,6 +244,33 @@ class TestServiceItem(TestCase):
self.assertEqual(service_item.service_item_type, ServiceItemType.Command, 'It should be a Command') self.assertEqual(service_item.service_item_type, ServiceItemType.Command, 'It should be a Command')
self.assertEqual(service_item.get_frames()[0], frame, 'Frames should match') self.assertEqual(service_item.get_frames()[0], frame, 'Frames should match')
@patch('openlp.core.lib.serviceitem.AppLocation.get_section_data_path')
def add_from_command_for_a_presentation_thumb_test(self, mocked_get_section_data_path):
"""
Test the Service Item - adding a presentation, and updating the thumb path
"""
# GIVEN: A service item, a mocked AppLocation and presentation data
mocked_get_section_data_path.return_value = os.path.join('mocked', 'section', 'path')
service_item = ServiceItem(None)
service_item.has_original_files = False
service_item.name = 'presentations'
presentation_name = 'test.pptx'
thumb = os.path.join('tmp', 'test', 'thumb.png')
display_title = 'DisplayTitle'
notes = 'Note1\nNote2\n'
expected_thumb_path = os.path.join('mocked', 'section', 'path', 'thumbnails',
md5_hash(os.path.join(TEST_PATH, presentation_name).encode('utf-8')),
'thumb.png')
frame = {'title': presentation_name, 'image': expected_thumb_path, 'path': TEST_PATH,
'display_title': display_title, 'notes': notes}
# WHEN: adding presentation to service_item
service_item.add_from_command(TEST_PATH, presentation_name, thumb, display_title, notes)
# THEN: verify that it is setup as a Command and that the frame data matches
self.assertEqual(service_item.service_item_type, ServiceItemType.Command, 'It should be a Command')
self.assertEqual(service_item.get_frames()[0], frame, 'Frames should match')
def service_item_load_optical_media_from_service_test(self): def service_item_load_optical_media_from_service_test(self):
""" """
Test the Service Item - load an optical media item Test the Service Item - load an optical media item

View File

@ -135,5 +135,5 @@ class TestPdfController(TestCase, TestMixin):
self.assertEqual(760, image.height(), 'The height should be 760') self.assertEqual(760, image.height(), 'The height should be 760')
self.assertEqual(537, image.width(), 'The width should be 537') self.assertEqual(537, image.width(), 'The width should be 537')
else: else:
self.assertEqual(767, image.height(), 'The height should be 767') self.assertEqual(768, image.height(), 'The height should be 768')
self.assertEqual(543, image.width(), 'The width should be 543') self.assertEqual(543, image.width(), 'The width should be 543')