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 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
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 self.is_capable(ItemCapabilities.HasDisplayTitle) and not display_title:
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,
'display_title': display_title, 'notes': notes})
self._new_item()

View File

@ -1106,8 +1106,6 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow, RegistryProperties):
self.image_manager.stop_manager = True
while self.image_manager.image_thread.isRunning():
time.sleep(0.1)
# Clean temporary files used by services
self.service_manager_contents.clean_up()
if save_settings:
if Settings().value('advanced/save current plugin'):
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:
self.live_controller.display.close()
self.live_controller.display = None
# Clean temporary files used by services
self.service_manager_contents.clean_up()
if is_win():
# Needed for Windows to stop crashes on exit
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 distutils.version import LooseVersion
from http.client import HTTPException
import logging
import locale
import os
@ -414,6 +415,11 @@ def get_web_page(url, header=None, update_openlp=False):
page = None
if retries > CONNECTION_RETRIES:
raise
except socket.gaierror:
log.exception('Socket gaierror: {}'.format(url))
page = None
if retries > CONNECTION_RETRIES:
raise
except ConnectionRefusedError:
log.exception('ConnectionRefused: {}'.format(url))
page = None
@ -425,6 +431,11 @@ def get_web_page(url, header=None, update_openlp=False):
page = None
if retries > CONNECTION_RETRIES:
raise
except HTTPException:
log.exception('HTTPException error: {}'.format(url))
page = None
if retries > CONNECTION_RETRIES:
raise
except:
# Don't know what's happening, so reraise the original
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:
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:
send_error_message('download')
return None

View File

@ -35,7 +35,7 @@ import logging
import os
import time
from openlp.core.common import is_win
from openlp.core.common import is_win, Registry
if is_win():
from win32com.client import Dispatch
@ -231,21 +231,13 @@ class ImpressDocument(PresentationDocument):
return False
self.desktop = desktop
properties = []
if not is_win():
# 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.append(self.create_property('Hidden', True))
properties = tuple(properties)
try:
self.document = desktop.loadComponentFromURL(url, '_blank', 0, properties)
except:
log.warning('Failed to load presentation %s' % url)
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.Display = ScreenList().current['number'] + 1
self.control = None
@ -382,6 +374,8 @@ class ImpressDocument(PresentationDocument):
"""
log.debug('start presentation OpenOffice')
if self.control is None or not self.control.isRunning():
window = self.document.getCurrentController().getFrame().getContainerWindow()
window.setVisible(True)
self.presentation.start()
self.control = self.presentation.getController()
# start() returns before the Component is ready. Try for 15 seconds.
@ -390,9 +384,13 @@ class ImpressDocument(PresentationDocument):
time.sleep(0.1)
sleep_count += 1
self.control = self.presentation.getController()
window.setVisible(False)
else:
self.control.activate()
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):
"""

View File

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

View File

@ -243,6 +243,10 @@ class Controller(object):
Instruct the controller to stop and hide the presentation.
"""
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.
if self.doc.is_active():
self.doc.slidenumber = self.doc.get_slide_number()
@ -352,6 +356,7 @@ class MessageListener(object):
self.controller = controller
else:
controller.add_handler(self.controllers[self.handler], file, hide_mode, message[3])
self.timer.start()
def slide(self, message):
"""
@ -423,6 +428,7 @@ class MessageListener(object):
is_live = message[1]
if is_live:
self.live_handler.shutdown()
self.timer.stop()
else:
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
for line in runlog.splitlines():
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:
program_type = 'mudraw'
break
found_gs = re.search('GPL Ghostscript.*', decoded_line)
found_gs = re.search('GPL Ghostscript.*', decoded_line, re.IGNORECASE)
if found_gs:
program_type = 'gs'
break
@ -222,8 +222,8 @@ class PdfDocument(PresentationDocument):
continue
# Calculate the ratio from pdf to screen
if width > 0 and height > 0:
width_ratio = size.right() / width
height_ratio = size.bottom() / height
width_ratio = size.width() / width
height_ratio = size.height() / height
# return the resolution that should be used. 72 is default.
if width_ratio > height_ratio:
return int(height_ratio * 72)
@ -254,7 +254,7 @@ class PdfDocument(PresentationDocument):
if not os.path.isdir(self.get_temp_folder()):
os.makedirs(self.get_temp_folder())
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],
startupinfo=self.startupinfo)
elif self.controller.gsbin:

View File

@ -32,7 +32,7 @@ import time
from openlp.core.common import is_win, Settings
if is_win():
from win32com.client import DispatchWithEvents
from win32com.client import Dispatch
import win32com
import win32con
import winreg
@ -93,22 +93,9 @@ class PowerpointController(PresentationController):
"""
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')
if not self.process:
self.process = DispatchWithEvents('PowerPoint.Application', PowerPointEvents)
self.process = Dispatch('PowerPoint.Application')
def kill(self):
"""
@ -330,6 +317,7 @@ class PowerpointDocument(PresentationDocument):
"""
log.debug('start_presentation')
# SlideShowWindow measures its size/position by points, not pixels
# https://technet.microsoft.com/en-us/library/dn528846.aspx
try:
dpi = win32ui.GetActiveWindow().GetDC().GetDeviceCaps(88)
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'
% (size.y(), top, size.height(), (bottom - top), size.x(), left, size.width(), (right - left)))
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 \
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')
self.presentation_hwnd = hwnd
# 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.'))
self.ppt_window_check_box.setText(
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(
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.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
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.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):
"""
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(537, image.width(), 'The width should be 537')
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')