← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~tomasgroth/openlp/mupdf into lp:openlp

 

Tomas Groth has proposed merging lp:~tomasgroth/openlp/mupdf into lp:openlp.

Requested reviews:
  OpenLP Core (openlp-core)

For more details, see:
https://code.launchpad.net/~tomasgroth/openlp/mupdf/+merge/182009

Support for presenting PDF using mupdf or ghostscript.
-- 
https://code.launchpad.net/~tomasgroth/openlp/mupdf/+merge/182009
Your team OpenLP Core is requested to review the proposed merge of lp:~tomasgroth/openlp/mupdf into lp:openlp.
=== modified file 'openlp/plugins/presentations/lib/mediaitem.py'
--- openlp/plugins/presentations/lib/mediaitem.py	2013-07-17 21:07:52 +0000
+++ openlp/plugins/presentations/lib/mediaitem.py	2013-08-25 20:30:40 +0000
@@ -244,45 +244,90 @@
             items = self.list_view.selectedItems()
             if len(items) > 1:
                 return False
-        service_item.processor = self.display_type_combo_box.currentText()
-        service_item.add_capability(ItemCapabilities.ProvidesOwnDisplay)
+        
+        filename = items[0].data(QtCore.Qt.UserRole)
+        file_type = os.path.splitext(filename)[1][1:]
         if not self.display_type_combo_box.currentText():
             return False
-        for bitem in items:
-            filename = bitem.data(QtCore.Qt.UserRole)
-            (path, name) = os.path.split(filename)
-            service_item.title = name
-            if os.path.exists(filename):
-                if service_item.processor == self.Automatic:
-                    service_item.processor = self.findControllerByType(filename)
-                    if not service_item.processor:
+        
+        if context == ServiceItemContext.Live and (file_type == u'pdf' or file_type == u'xps'):
+            service_item.add_capability(ItemCapabilities.CanMaintain)
+            service_item.add_capability(ItemCapabilities.CanPreview)
+            service_item.add_capability(ItemCapabilities.CanLoop)
+            service_item.add_capability(ItemCapabilities.CanAppend)
+            # force a nonexistent theme
+            service_item.theme = -1
+            
+            # Why the loop when we above return False if len(items) is > 1?
+            for bitem in items:
+                filename = bitem.data(QtCore.Qt.UserRole)
+                (path, name) = os.path.split(filename)
+                service_item.title = name
+                if os.path.exists(filename):
+                    processor = self.findControllerByType(filename)
+                    if not processor:
                         return False
-                controller = self.controllers[service_item.processor]
-                doc = controller.add_document(filename)
-                if doc.get_thumbnail_path(1, True) is None:
-                    doc.load_presentation()
-                i = 1
-                img = doc.get_thumbnail_path(i, True)
-                if img:
-                    while img:
-                        service_item.add_from_command(path, name, img)
+                    controller = self.controllers[processor]
+                    service_item.processor = None
+                    doc = controller.add_document(filename)
+                    if doc.get_thumbnail_path(1, True) is None or not os.path.isfile(os.path.join(doc.get_temp_folder(), u'mainslide001.png')):
+                        doc.load_presentation()
+                    i = 1
+                    imagefile = u'mainslide%03d.png' % i
+                    img = os.path.join(doc.get_temp_folder(), imagefile)
+                    while os.path.isfile(img):
+                        service_item.add_from_image(img, name)
                         i += 1
-                        img = doc.get_thumbnail_path(i, True)
+                        imagefile = u'mainslide%03d.png' % i
+                        img = os.path.join(doc.get_temp_folder(), imagefile)
                     doc.close_presentation()
                     return True
                 else:
                     # File is no longer present
                     if not remote:
                         critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'),
-                            translate('PresentationPlugin.MediaItem',
-                                'The presentation %s is incomplete, please reload.') % filename)
-                    return False
-            else:
-                # File is no longer present
-                if not remote:
-                    critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'),
-                        translate('PresentationPlugin.MediaItem', 'The presentation %s no longer exists.') % filename)
-                return False
+                            translate('PresentationPlugin.MediaItem', 'The presentation %s no longer exists.') % filename)
+                    return False
+        else:
+            service_item.processor = self.display_type_combo_box.currentText()
+            service_item.add_capability(ItemCapabilities.ProvidesOwnDisplay)
+
+            # Why the loop when we above return False if len(items) is > 1?
+            for bitem in items:
+                filename = bitem.data(QtCore.Qt.UserRole)
+                (path, name) = os.path.split(filename)
+                service_item.title = name
+                if os.path.exists(filename):
+                    if service_item.processor == self.Automatic:
+                        service_item.processor = self.findControllerByType(filename)
+                        if not service_item.processor:
+                            return False
+                    controller = self.controllers[service_item.processor]
+                    doc = controller.add_document(filename)
+                    if doc.get_thumbnail_path(1, True) is None:
+                        doc.load_presentation()
+                    i = 1
+                    img = doc.get_thumbnail_path(i, True)
+                    if img:
+                        while img:
+                            service_item.add_from_command(path, name, img)
+                            i += 1
+                            img = doc.get_thumbnail_path(i, True)
+                        doc.close_presentation()
+                        return True
+                    else:
+                        # File is no longer present
+                        if not remote:
+                            critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'),
+                                translate('PresentationPlugin.MediaItem',
+                                    'The presentation %s is incomplete, please reload.') % filename)
+                        return False
+                else:
+                    # File is no longer present
+                    if not remote:
+                        critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'),
+                            translate('PresentationPlugin.MediaItem', 'The presentation %s no longer exists.') % filename)
+                    return False
 
     def findControllerByType(self, filename):
         """

=== modified file 'openlp/plugins/presentations/lib/messagelistener.py'
--- openlp/plugins/presentations/lib/messagelistener.py	2013-05-26 17:03:14 +0000
+++ openlp/plugins/presentations/lib/messagelistener.py	2013-08-25 20:30:40 +0000
@@ -324,7 +324,12 @@
             controller = self.live_handler
         else:
             controller = self.preview_handler
-        controller.add_handler(self.controllers[self.handler], file, hide_mode, message[3])
+        # when presenting PDF, we're using the image presentation code, 
+        # so handler & processor is set to None, and we skip adding the handler.
+        if self.handler == None:
+            self.controller = controller
+        else: 
+            controller.add_handler(self.controllers[self.handler], file, hide_mode, message[3])
 
     def slide(self, message):
         """

=== added file 'openlp/plugins/presentations/lib/pdfcontroller.py'
--- openlp/plugins/presentations/lib/pdfcontroller.py	1970-01-01 00:00:00 +0000
+++ openlp/plugins/presentations/lib/pdfcontroller.py	2013-08-25 20:30:40 +0000
@@ -0,0 +1,302 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2013 Raoul Snyman                                        #
+# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan      #
+# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub,      #
+# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer.   #
+# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru,          #
+# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith,             #
+# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock,              #
+# Frode Woldsund, Martin Zibricky, Patrick Zimmermann                         #
+# --------------------------------------------------------------------------- #
+# This program is free software; you can redistribute it and/or modify it     #
+# under the terms of the GNU General Public License as published by the Free  #
+# Software Foundation; version 2 of the License.                              #
+#                                                                             #
+# This program is distributed in the hope that it will be useful, but WITHOUT #
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
+# more details.                                                               #
+#                                                                             #
+# You should have received a copy of the GNU General Public License along     #
+# with this program; if not, write to the Free Software Foundation, Inc., 59  #
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
+###############################################################################
+
+import os
+import logging
+from tempfile import NamedTemporaryFile
+import re
+from subprocess import check_output, CalledProcessError, STDOUT
+from PyQt4 import QtCore, QtGui
+
+from openlp.core.utils import AppLocation
+from openlp.core.lib import ScreenList, Settings
+from presentationcontroller import PresentationController, PresentationDocument
+
+
+log = logging.getLogger(__name__)
+
+def check_binary(program_path):
+    """
+    Function that checks whether a binary is either ghostscript or mudraw or neither.
+    """
+    program_type = None
+    runlog = u''
+    try:
+        runlog = check_output([program_path,  u'--help'], stderr=STDOUT)
+    except CalledProcessError as e:
+        runlog = e.output
+    except Exception:
+        runlog = u''
+        
+    # Analyse the output to see it the program is mudraw, ghostscript or neither
+    for line in runlog.splitlines():
+        found_mudraw = re.search(u'usage: mudraw.*', line)
+        if found_mudraw:
+            program_type = u'mudraw'
+            break
+        found_gs = re.search(u'GPL Ghostscript.*', line)
+        if found_gs:
+            program_type = u'gs'
+            break
+    return program_type
+
+class PdfController(PresentationController):
+    """
+    Class to control PDF presentations
+    """
+    log.info(u'PdfController loaded')
+
+    def __init__(self, plugin):
+        """
+        Initialise the class
+        """
+        log.debug(u'Initialising')
+        self.process = None
+        PresentationController.__init__(self, plugin, u'Pdf', PdfDocument)
+        self.supports = [u'pdf']
+        self.mudrawbin = u''
+        self.gsbin = u''
+        if self.check_installed() and self.mudrawbin != u'':
+            self.also_supports = [u'xps']
+
+    def check_available(self):
+        """
+        PdfController is able to run on this machine.
+        """
+        log.debug(u'check_available Pdf')
+        return self.check_installed()
+
+    def check_installed(self):
+        """
+        Check the viewer is installed.
+        """
+        log.debug(u'check_installed Pdf')
+        # Use the user defined program if given
+        if (Settings().value(u'presentations/enable_given_pdf_program')):
+            given_pdf_program = Settings().value(u'presentations/given_pdf_program')
+            type = check_binary(given_pdf_program)
+            if type == u'gs':
+                self.gsbin = given_pdf_program
+                return True
+            elif type == u'mudraw':
+                self.mudrawbin = given_pdf_program
+                return True
+
+        # Fallback to autodetection
+        application_path = AppLocation.get_directory(AppLocation.AppDir)
+        if os.name != u'nt':
+            # First try to find mupdf
+            try:
+                self.mudrawbin = check_output([u'which', u'mudraw']).rstrip('\n')
+            except CalledProcessError:
+                self.mudrawbin = u''
+
+            # if mupdf isn't installed, fallback to ghostscript
+            if self.mudrawbin == u'':
+                try:
+                    self.gsbin = check_output([u'which', u'gs']).rstrip('\n')
+                except CalledProcessError:
+                    self.gsbin = u''
+                
+            # Last option: check if mudraw is placed in OpenLP base folder
+            if self.mudrawbin == u'' and self.gsbin == u'':
+                application_path = AppLocation.get_directory(AppLocation.AppDir)
+                if os.path.isfile(application_path + u'/../mudraw'):
+                    self.mudrawbin = application_path + u'/../mudraw'
+        else:
+            # for windows we only accept mudraw.exe in the base folder
+            application_path = AppLocation.get_directory(AppLocation.AppDir)
+            if os.path.isfile(application_path + u'/../mudraw.exe'):
+                self.mudrawbin = application_path + u'/../mudraw.exe'
+            
+        if self.mudrawbin == u'' and self.gsbin == u'':
+            return False
+        else:
+            return True
+        
+    def kill(self):
+        """
+        Called at system exit to clean up any running presentations
+        """
+        log.debug(u'Kill pdfviewer')
+        while self.docs:
+            self.docs[0].close_presentation()
+
+
+class PdfDocument(PresentationDocument):
+    """
+    Class which holds information of a single presentation.
+    """
+    def __init__(self, controller, presentation):
+        """
+        Constructor, store information about the file and initialise.
+        """
+        log.debug(u'Init Presentation Pdf')
+        PresentationDocument.__init__(self, controller, presentation)
+        self.presentation = None
+        self.blanked = False
+        self.hidden = False
+        self.image_files = []
+        self.num_pages = -1
+
+    def gs_get_resolution(self,  size):
+        """ 
+        Only used when using ghostscript
+        Ghostscript can't scale automaticly while keeping aspect like mupdf, so we need
+        to get the ratio bewteen the screen size and the PDF to scale
+        """
+        # Use a postscript script to get size of the pdf. It is assumed that all pages have same size
+        postscript = u'%!PS \n\
+() = \n\
+File dup (r) file runpdfbegin \n\
+1 pdfgetpage dup \n\
+/MediaBox pget { \n\
+aload pop exch 4 1 roll exch sub 3 1 roll sub \n\
+( Size: x: ) print =print (, y: ) print =print (\n) print \n\
+} if \n\
+flush \n\
+quit \n\
+'
+        # Put postscript into tempfile
+        tmpfile = NamedTemporaryFile(delete=False)
+        tmpfile.write(postscript)
+        tmpfile.close()
+        
+        # Run the script on the pdf to get the size
+        runlog = []
+        try:
+            runlog = check_output([self.controller.gsbin, u'-dNOPAUSE', u'-dNODISPLAY', u'-dBATCH', u'-sFile=' + self.filepath, tmpfile.name])
+        except CalledProcessError as e:
+            log.debug(u' '.join(e.cmd))
+            log.debug(e.output)
+        os.unlink(tmpfile.name)
+        
+        # Extract the pdf resolution from output, the format is " Size: x: <width>, y: <height>"
+        width = 0
+        height = 0
+        for line in runlog.splitlines():
+            try:
+                width = re.search(u'.*Size: x: (\d+\.?\d*), y: \d+.*', line).group(1)
+                height = re.search(u'.*Size: x: \d+\.?\d*, y: (\d+\.?\d*).*', line).group(1)
+                break;
+            except AttributeError:
+                pass
+
+        # Calculate the ratio from pdf to screen
+        if width > 0 and height > 0:
+            width_ratio = size.right() / float(width)
+            height_ratio = size.bottom() / float(height)
+            
+            # return the resolution that should be used. 72 is default.
+            if width_ratio > height_ratio:
+                return int(height_ratio * 72)
+            else:
+                return int(width_ratio * 72)
+        else:
+            return 72
+
+    def load_presentation(self):
+        """
+        Called when a presentation is added to the SlideController. It generates images from the PDF.
+        """
+        log.debug(u'load_presentation pdf')
+        
+        # Check if the images has already been created, and if yes load them
+        if os.path.isfile(os.path.join(self.get_temp_folder(), u'mainslide001.png')):
+            created_files = sorted(os.listdir(self.get_temp_folder()))
+            for fn in created_files:
+                if os.path.isfile(os.path.join(self.get_temp_folder(),  fn)):
+                    self.image_files.append(os.path.join(self.get_temp_folder(), fn))
+            self.num_pages = len(self.image_files)
+            return True
+        
+        size = ScreenList().current[u'size']
+        # Generate images from PDF that will fit the frame.
+        runlog = u''
+        try:
+            if not os.path.isdir(self.get_temp_folder()):
+                os.makedirs(self.get_temp_folder())
+            if self.controller.mudrawbin != u'':
+                runlog = check_output([self.controller.mudrawbin, u'-w', str(size.right()), u'-h', str(size.bottom()), u'-o', os.path.join(self.get_temp_folder(), u'mainslide%03d.png'), self.filepath])
+            elif self.controller.gsbin != u'':
+                resolution = self.gs_get_resolution(size)
+                runlog = check_output([self.controller.gsbin, u'-dSAFER', u'-dNOPAUSE', u'-dBATCH', u'-sDEVICE=png16m', u'-r' + str(resolution), u'-dTextAlphaBits=4', u'-dGraphicsAlphaBits=4', u'-sOutputFile=' + os.path.join(self.get_temp_folder(), u'mainslide%03d.png'), self.filepath])
+            created_files = sorted(os.listdir(self.get_temp_folder()))
+            for fn in created_files:
+                if os.path.isfile(os.path.join(self.get_temp_folder(), fn)):
+                    self.image_files.append(os.path.join(self.get_temp_folder(), fn))
+        except Exception as e: 
+            log.debug(e)
+            log.debug(runlog)
+            return False 
+        self.num_pages = len(self.image_files)
+        
+        # Create thumbnails
+        self.create_thumbnails()
+        return True
+
+    def create_thumbnails(self):
+        """
+        Generates thumbnails
+        """
+        log.debug(u'create_thumbnails pdf')
+        if self.check_thumbnails():
+            return
+        log.debug(u'create_thumbnails proceeding')
+        
+        # use builtin function to create thumbnails from generated images
+        index = 1
+        for image in self.image_files:
+            self.convert_thumbnail(image, index)
+            index += 1
+
+    def close_presentation(self):
+        """
+        Close presentation and clean up objects. Triggered by new object being added to SlideController or OpenLP being
+        shut down.
+        """
+        log.debug(u'close_presentation pdf')
+        self.controller.remove_doc(self)
+        
+    def is_loaded(self):
+        """
+        Returns true if a presentation is loaded.
+        """
+        log.debug(u'is_loaded pdf')
+        if self.num_pages < 0:
+            return False
+        return True
+
+    def is_active(self):
+        """
+        Returns true if a presentation is currently active.
+        """
+        log.debug(u'is_active pdf')
+        return self.is_loaded() and not self.hidden
+

=== modified file 'openlp/plugins/presentations/lib/presentationtab.py'
--- openlp/plugins/presentations/lib/presentationtab.py	2013-04-19 18:52:39 +0000
+++ openlp/plugins/presentations/lib/presentationtab.py	2013-08-25 20:30:40 +0000
@@ -29,8 +29,9 @@
 
 from PyQt4 import QtGui
 
-from openlp.core.lib import Settings, SettingsTab, UiStrings, translate
-
+from openlp.core.lib import Settings, SettingsTab, UiStrings, translate, build_icon
+from openlp.core.lib.ui import critical_error_message_box
+from pdfcontroller import check_binary
 
 class PresentationTab(SettingsTab):
     """
@@ -63,6 +64,7 @@
             self.presenter_check_boxes[controller.name] = checkbox
             self.controllers_layout.addWidget(checkbox)
         self.left_layout.addWidget(self.controllers_group_box)
+        # Advanced
         self.advanced_group_box = QtGui.QGroupBox(self.left_column)
         self.advanced_group_box.setObjectName(u'advanced_group_box')
         self.advanced_layout = QtGui.QVBoxLayout(self.advanced_group_box)
@@ -70,7 +72,33 @@
         self.override_app_check_box = QtGui.QCheckBox(self.advanced_group_box)
         self.override_app_check_box.setObjectName(u'override_app_check_box')
         self.advanced_layout.addWidget(self.override_app_check_box)
+
+        # Pdf options
+        self.pdf_group_box = QtGui.QGroupBox(self.left_column)
+        self.pdf_group_box.setObjectName(u'pdf_group_box')
+        self.pdf_layout = QtGui.QFormLayout(self.pdf_group_box)
+        self.pdf_layout.setObjectName(u'pdf_layout')
+        self.pdf_program_check_box = QtGui.QCheckBox(self.pdf_group_box)
+        self.pdf_program_check_box.setObjectName(u'pdf_program_check_box')
+        self.pdf_layout.addWidget(self.pdf_program_check_box)
+        self.pdf_program_path_layout = QtGui.QHBoxLayout()
+        self.pdf_program_path_layout.setObjectName(u'pdf_program_path_layout')
+        self.pdf_program_path = QtGui.QLineEdit(self.pdf_group_box)
+        self.pdf_program_path.setObjectName(u'pdf_program_path')
+        self.pdf_program_path.setReadOnly(True)
+        self.pdf_program_path.setPalette(self.get_grey_text_palette(True))
+        self.pdf_program_path_layout.addWidget(self.pdf_program_path)
+        self.pdf_program_browse_button = QtGui.QToolButton(self.pdf_group_box)
+        self.pdf_program_browse_button.setObjectName(u'pdf_program_browse_button')
+        self.pdf_program_browse_button.setIcon(build_icon(u':/general/general_open.png'))
+        self.pdf_program_browse_button.setEnabled(False)
+        self.pdf_program_path_layout.addWidget(self.pdf_program_browse_button)
+        self.pdf_layout.addRow(self.pdf_program_path_layout)
+        self.pdf_program_path.editingFinished.connect(self.on_pdf_program_path_edit_finished)
+        self.pdf_program_browse_button.clicked.connect(self.on_pdf_program_browse_button_clicked)
+        self.pdf_program_check_box.clicked.connect(self.on_pdf_program_check_box_clicked)
         self.left_layout.addWidget(self.advanced_group_box)
+        self.left_layout.addWidget(self.pdf_group_box)
         self.left_layout.addStretch()
         self.right_layout.addStretch()
 
@@ -84,8 +112,12 @@
             checkbox = self.presenter_check_boxes[controller.name]
             self.set_controller_text(checkbox, controller)
         self.advanced_group_box.setTitle(UiStrings().Advanced)
+        self.pdf_group_box.setTitle(translate('PresentationPlugin.PresentationTab', 'PDF options'))
         self.override_app_check_box.setText(
             translate('PresentationPlugin.PresentationTab', 'Allow presentation application to be overridden'))
+        self.pdf_program_check_box.setText(
+            translate('PresentationPlugin.PresentationTab', 'Use given full path for mudraw or ghostscript binary:'))
+
 
     def set_controller_text(self, checkbox, controller):
         if checkbox.isEnabled():
@@ -102,6 +134,15 @@
             checkbox = self.presenter_check_boxes[controller.name]
             checkbox.setChecked(Settings().value(self.settings_section + u'/' + controller.name))
         self.override_app_check_box.setChecked(Settings().value(self.settings_section + u'/override app'))
+        # load pdf-program settings
+        enable_given_pdf_program = Settings().value(self.settings_section + u'/enable_given_pdf_program')
+        self.pdf_program_check_box.setChecked(enable_given_pdf_program)
+        self.pdf_program_path.setReadOnly(not enable_given_pdf_program)
+        self.pdf_program_path.setPalette(self.get_grey_text_palette(not enable_given_pdf_program))
+        self.pdf_program_browse_button.setEnabled(enable_given_pdf_program)
+        pdf_program = Settings().value(self.settings_section + u'/given_pdf_program')
+        if pdf_program:
+            self.pdf_program_path.setText(pdf_program)
 
     def save(self):
         """
@@ -127,10 +168,25 @@
         if Settings().value(setting_key) != self.override_app_check_box.checkState():
             Settings().setValue(setting_key, self.override_app_check_box.checkState())
             changed = True
+        
+        # Save pdf-settings
+        pdf_program = self.pdf_program_path.text()
+        enable_given_pdf_program = self.pdf_program_check_box.checkState()
+        # If the given program is blank disable using the program
+        if pdf_program == u'':
+            enable_given_pdf_program = 0
+        if pdf_program != Settings().value(self.settings_section + u'/given_pdf_program'):
+            Settings().setValue(self.settings_section + u'/given_pdf_program',  pdf_program)
+            changed = True
+        if enable_given_pdf_program != Settings().value(self.settings_section + u'/enable_given_pdf_program'):
+            Settings().setValue(self.settings_section + u'/enable_given_pdf_program',  enable_given_pdf_program)
+            changed = True
+        
         if changed:
             self.settings_form.register_post_process(u'mediaitem_suffix_reset')
             self.settings_form.register_post_process(u'mediaitem_presentation_rebuild')
             self.settings_form.register_post_process(u'mediaitem_suffixes')
+                
 
     def tab_visible(self):
         """
@@ -142,3 +198,44 @@
             checkbox = self.presenter_check_boxes[controller.name]
             checkbox.setEnabled(controller.is_available())
             self.set_controller_text(checkbox, controller)
+        
+    def on_pdf_program_path_edit_finished(self):
+        """
+        After selecting/typing in a program it is validated that it is a actually ghostscript or mudraw
+        """
+        type = None 
+        if self.pdf_program_path.text() != u'':
+            type = check_binary(self.pdf_program_path.text())
+            if not type:
+                critical_error_message_box(UiStrings().Error, 
+                        translate('PresentationPlugin.PresentationTab', 'The program is not ghostscript or mudraw which is required.'))
+                self.pdf_program_path.setFocus()
+
+    def on_pdf_program_browse_button_clicked(self):
+        """
+        Select the mudraw or ghostscript binary that should be used.
+        """
+        filename = QtGui.QFileDialog.getOpenFileName(self, translate('PresentationPlugin.PresentationTab', 'Select mudraw or ghostscript binary.'))
+        if filename:
+            self.pdf_program_path.setText(filename)
+        self.pdf_program_path.setFocus()
+
+    def on_pdf_program_check_box_clicked(self, checked):
+        """
+        When checkbox for manual entering pdf-program is clicked,
+        enable or disable the textbox for the programpath and the browse-button.
+        """
+        self.pdf_program_path.setReadOnly(not checked)
+        self.pdf_program_path.setPalette(self.get_grey_text_palette(not checked))
+        self.pdf_program_browse_button.setEnabled(checked)
+
+    def get_grey_text_palette(self, greyed):
+        """
+        Returns a QPalette with greyed out text as used for placeholderText.
+        """
+        palette = QtGui.QPalette()
+        color = self.palette().color(QtGui.QPalette.Active, QtGui.QPalette.Text)
+        if greyed:
+            color.setAlpha(128)
+        palette.setColor(QtGui.QPalette.Active, QtGui.QPalette.Text, color)
+        return palette

=== modified file 'openlp/plugins/presentations/presentationplugin.py'
--- openlp/plugins/presentations/presentationplugin.py	2013-05-24 20:17:47 +0000
+++ openlp/plugins/presentations/presentationplugin.py	2013-08-25 20:30:40 +0000
@@ -45,9 +45,12 @@
 
 __default_settings__ = {
         u'presentations/override app': QtCore.Qt.Unchecked,
+        u'presentations/enable_given_pdf_program': QtCore.Qt.Unchecked,
+        u'presentations/given_pdf_program': u'',
         u'presentations/Impress': QtCore.Qt.Checked,
         u'presentations/Powerpoint': QtCore.Qt.Checked,
         u'presentations/Powerpoint Viewer': QtCore.Qt.Checked,
+        u'presentations/Pdf': QtCore.Qt.Checked,
         u'presentations/presentations files': []
 }
 

=== modified file 'resources/pyinstaller/hook-openlp.plugins.presentations.presentationplugin.py'
--- resources/pyinstaller/hook-openlp.plugins.presentations.presentationplugin.py	2013-07-18 19:28:35 +0000
+++ resources/pyinstaller/hook-openlp.plugins.presentations.presentationplugin.py	2013-08-25 20:30:40 +0000
@@ -29,4 +29,5 @@
 
 hiddenimports = ['openlp.plugins.presentations.lib.impresscontroller',
                  'openlp.plugins.presentations.lib.powerpointcontroller',
-                 'openlp.plugins.presentations.lib.pptviewcontroller']
+                 'openlp.plugins.presentations.lib.pptviewcontroller',
+                 'openlp.plugins.presentations.lib.pdfcontroller']


Follow ups