forked from openlp/openlp
trunk
This commit is contained in:
commit
4653e3eacb
@ -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()
|
||||||
|
@ -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')
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
try:
|
||||||
page = get_web_page(reference_url, header, True)
|
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
|
||||||
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
@ -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:
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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:'))
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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')
|
||||||
|
Loading…
Reference in New Issue
Block a user