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

Proposed by Tomas Groth
Status: Superseded
Proposed branch: lp:~tomasgroth/openlp/mupdf
Merge into: lp:openlp
Diff against target: 897 lines (+644/-41)
9 files modified
openlp/core/ui/slidecontroller.py (+2/-2)
openlp/plugins/presentations/lib/mediaitem.py (+83/-35)
openlp/plugins/presentations/lib/messagelistener.py (+31/-1)
openlp/plugins/presentations/lib/pdfcontroller.py (+306/-0)
openlp/plugins/presentations/lib/presentationtab.py (+98/-1)
openlp/plugins/presentations/presentationplugin.py (+3/-0)
resources/pyinstaller/hook-openlp.plugins.presentations.presentationplugin.py (+2/-1)
tests/functional/openlp_plugins/presentations/test_mediaitem.py (+10/-1)
tests/functional/openlp_plugins/presentations/test_pdfcontroller.py (+109/-0)
To merge this branch: bzr merge lp:~tomasgroth/openlp/mupdf
Reviewer Review Type Date Requested Status
Tim Bentley Needs Fixing
Raoul Snyman Pending
Review via email: mp+200161@code.launchpad.net

This proposal supersedes a proposal from 2013-11-14.

This proposal has been superseded by a proposal from 2013-12-31.

Description of the change

Support for presenting PDF using mupdf or ghostscript.

To post a comment you must log in.
Revision history for this message
Dmitriy Marmyshev (marmyshev) wrote : Posted in a previous version of this proposal

Looks good!
Really expecting function for us! Keep develop it!

1Q: Does it works on OS X?
2Q: I didnt get the point with PdfViewer class. Dont you use slide's system in OpenLP? Do I correctly understand that you just convert PDF to images and then show them like images? May be it will be easier to use image plugin for showing images after conversion.

And 1 more suggestion: may be it is better to use this:

os.path.isfile(os.path.join(self.get_temp_folder(), u'mainslide001.png'))

instead of this:

os.path.isfile(self.get_temp_folder() + u'/mainslide001.png')

Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

>And 1 more suggestion: may be it is better to use this:
>
>os.path.isfile(os.path.join(self.get_temp_folder(),
>u'mainslide001.png'))
>
>instead of this:
>
>os.path.isfile(self.get_temp_folder() + u'/mainslide001.png')

Good catch Dmitriy!

--
Sent from my Android phone with K-9 Mail. Please excuse my brevity.

Revision history for this message
Tomas Groth (tomasgroth) wrote : Posted in a previous version of this proposal

> 1Q: Does it works on OS X?
Yea, provided that either ghostscript or mupdf is available.

> 2Q: I didnt get the point with PdfViewer class. Dont you use slide's system in
> OpenLP? Do I correctly understand that you just convert PDF to images and then
> show them like images? May be it will be easier to use image plugin for
> showing images after conversion.
I've thought of the same thing, but I have to admit that I'm yet to find a way to bend the system to my need. I'd like the service-item to be a pdf-presentation, but presenting that using the same aproach as the image plugin isn't easy. But maybe I just don't understand the structure of OpenLP yet.

> And 1 more suggestion: may be it is better to use this:
> os.path.isfile(os.path.join(self.get_temp_folder(), u'mainslide001.png'))
> instead of this:
> os.path.isfile(self.get_temp_folder() + u'/mainslide001.png')
I'll change this in the next push.

Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

Hum
The conversion to images is done on importing the PDF so all the images are
in the file system.
You could then add the images as image items to the service instead of
presentation file.

That would be a clean cross over Services would work as images but added as
a PDF.

On 21 July 2013 15:16, Tomas Groth <email address hidden> wrote:

> > 1Q: Does it works on OS X?
> Yea, provided that either ghostscript or mupdf is available.
>
> > 2Q: I didnt get the point with PdfViewer class. Dont you use slide's
> system in
> > OpenLP? Do I correctly understand that you just convert PDF to images
> and then
> > show them like images? May be it will be easier to use image plugin for
> > showing images after conversion.
> I've thought of the same thing, but I have to admit that I'm yet to find a
> way to bend the system to my need. I'd like the service-item to be a
> pdf-presentation, but presenting that using the same aproach as the image
> plugin isn't easy. But maybe I just don't understand the structure of
> OpenLP yet.
>
> > And 1 more suggestion: may be it is better to use this:
> > os.path.isfile(os.path.join(self.get_temp_folder(), u'mainslide001.png'))
> > instead of this:
> > os.path.isfile(self.get_temp_folder() + u'/mainslide001.png')
> I'll change this in the next push.
> --
> https://code.launchpad.net/~tomasgroth/openlp/mupdf/+merge/174849
> Your team OpenLP Core is requested to review the proposed merge of
> lp:~tomasgroth/openlp/mupdf into lp:openlp.
>
> _______________________________________________
> Mailing list: https://launchpad.net/~openlp-core
> Post to : <email address hidden>
> Unsubscribe : https://launchpad.net/~openlp-core
> More help : https://help.launchpad.net/ListHelp
>

--
Tim and Alison Bentley
<email address hidden>

Revision history for this message
Andreas Preikschat (googol-deactivatedaccount) wrote : Posted in a previous version of this proposal

And please add docstrings to *all* methods/functions

Revision history for this message
Dmitriy Marmyshev (marmyshev) wrote : Posted in a previous version of this proposal

> Hum
> The conversion to images is done on importing the PDF so all the images are
> in the file system.
> You could then add the images as image items to the service instead of
> presentation file.
>
> That would be a clean cross over Services would work as images but added as
> a PDF.
>

Let me not to agree with this. We should not to make life of OpenLP operator (user) harder by changing visually a "PDF file" to a "group of imageses". If I (as user) add PDF presentation to OpenLP - i'd like to see PDF in the list.
Upper, I mean that create new class (PdfViewer) just to show images may be is not the easiest way, may be it is better simply to use classes from ImagePlugin to show images from PDF controller.

Revision history for this message
Jonathan Corwin (j-corwin) wrote : Posted in a previous version of this proposal

(Note comment only, I've not looked at the code)

I too think it is nice to keep it in the Presentation plugin. The user is dealing with a PDF file as far as they are concerned and transporting the PDF in the service file rather than lots of images is in my view the correct way of doing this. Importing a PDF in Presentations and having to look for it in the image plugin would confuse many. Also by moving the PDF around it protects us from adding it on one computer with a 640x480 resolution moving to a 1900x1080 system and ending up having to deal with images in an resolution that don't scale nicely.

In the future some other app (e.g. Impress) might support the opening of PDF files and in this case the user would then be able to choose between mupdf or something else. If we're only storing the images then that wouldn't be so easy.

Yes it would be nice if we could just hook into the already existing image handling functionality of OpenLP for the display, but I wouldn't want us to tear the code base to pieces to get this to work. (I don't know the difficulties of this). I don't see any harm in making it a two phase task anyway and to do this later if we find a nice way to do it.

Finally an idea has just come to mind that in the future we could also support an "Export to Images" function on the presentation whereby we could copy the thumbnail images (created at output resolution) to the an Image Plugin group, to at least cater for those situations where the user knows the presentation application isn't installed at the destination system. However this would be outside the scope of this particular branch and would be a whole new feature.

Revision history for this message
Dave Warnock (dave-warnock) wrote : Posted in a previous version of this proposal

Just wanted to add my support to what Jonathon suggests.

On 22 July 2013 10:18, Jonathan Corwin <email address hidden> wrote:

> (Note comment only, I've not looked at the code)
>
> I too think it is nice to keep it in the Presentation plugin. The user is
> dealing with a PDF file as far as they are concerned and transporting the
> PDF in the service file rather than lots of images is in my view the
> correct way of doing this. Importing a PDF in Presentations and having to
> look for it in the image plugin would confuse many. Also by moving the PDF
> around it protects us from adding it on one computer with a 640x480
> resolution moving to a 1900x1080 system and ending up having to deal with
> images in an resolution that don't scale nicely.
>
> In the future some other app (e.g. Impress) might support the opening of
> PDF files and in this case the user would then be able to choose between
> mupdf or something else. If we're only storing the images then that
> wouldn't be so easy.
>
> Yes it would be nice if we could just hook into the already existing image
> handling functionality of OpenLP for the display, but I wouldn't want us to
> tear the code base to pieces to get this to work. (I don't know the
> difficulties of this). I don't see any harm in making it a two phase task
> anyway and to do this later if we find a nice way to do it.
>
> Finally an idea has just come to mind that in the future we could also
> support an "Export to Images" function on the presentation whereby we could
> copy the thumbnail images (created at output resolution) to the an Image
> Plugin group, to at least cater for those situations where the user knows
> the presentation application isn't installed at the destination system.
> However this would be outside the scope of this particular branch and would
> be a whole new feature.
> --
> https://code.launchpad.net/~tomasgroth/openlp/mupdf/+merge/174849
> You are subscribed to branch lp:openlp.
>

--
Dave Warnock: http://42.blogs.warnock.me.uk
Cycling Blog: http://42bikes.warnock.me.uk

Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal
Download full text (3.5 KiB)

Ok so I worded it in a clumsy manner.

PDF gets added to the presentation plugin no problems. It generates images
of the pages like the presentation plugins do.

The difference suggested and echoed by Jonathan is we use the image
processing part of the serviceitem to do the displays instead of writing
and managing an 8th player within the architecture.

It will look like a PDF, quack like a PDF display like a set of images.

The changes suggested will be constrained to the Presentations plugin so
easier to implement and test with less disruption.

Adding a new player is not a small job and with the issues we have had in
the past (4 months to sort out media after 5 months getting it in) and the
migrations going on it will be a challenge.

BTW are the PDF libraries fully python3 compliant and have versions
available for all the distros we support. That would also be a merge
requirement.

Tim

On 22 July 2013 11:35, Dave Warnock <email address hidden> wrote:

> Just wanted to add my support to what Jonathon suggests.
>
> On 22 July 2013 10:18, Jonathan Corwin <email address hidden> wrote:
>
> > (Note comment only, I've not looked at the code)
> >
> > I too think it is nice to keep it in the Presentation plugin. The user is
> > dealing with a PDF file as far as they are concerned and transporting the
> > PDF in the service file rather than lots of images is in my view the
> > correct way of doing this. Importing a PDF in Presentations and having to
> > look for it in the image plugin would confuse many. Also by moving the
> PDF
> > around it protects us from adding it on one computer with a 640x480
> > resolution moving to a 1900x1080 system and ending up having to deal with
> > images in an resolution that don't scale nicely.
> >
> > In the future some other app (e.g. Impress) might support the opening of
> > PDF files and in this case the user would then be able to choose between
> > mupdf or something else. If we're only storing the images then that
> > wouldn't be so easy.
> >
> > Yes it would be nice if we could just hook into the already existing
> image
> > handling functionality of OpenLP for the display, but I wouldn't want us
> to
> > tear the code base to pieces to get this to work. (I don't know the
> > difficulties of this). I don't see any harm in making it a two phase task
> > anyway and to do this later if we find a nice way to do it.
> >
> > Finally an idea has just come to mind that in the future we could also
> > support an "Export to Images" function on the presentation whereby we
> could
> > copy the thumbnail images (created at output resolution) to the an Image
> > Plugin group, to at least cater for those situations where the user knows
> > the presentation application isn't installed at the destination system.
> > However this would be outside the scope of this particular branch and
> would
> > be a whole new feature.
> > --
> > https://code.launchpad.net/~tomasgroth/openlp/mupdf/+merge/174849
> > You are subscribed to branch lp:openlp.
> >
>
>
>
> --
> Dave Warnock: http://42.blogs.warnock.me.uk
> Cycling Blog: http://42bikes.warnock.me.uk
>
> https://code.launchpad.net/~tomasgroth/openlp/mupdf/+merge/174849
> Your team OpenL...

Read more...

Revision history for this message
Tomas Groth (tomasgroth) wrote : Posted in a previous version of this proposal

Ok, here is some more thoughts...
The challenge as I see it is that the presentation plugin has been build around the fact the PowerPoint, PowerPoint Viewer and Impress all have their own "players", which means that since this pdf-presentation extensions is inside the presentation plugin, it is expected to provide a player, which is what I implemented in this first revision by adding the simple PdfViewer class. As some of you noted it might not be a great idea to add another "player", but the problem then is how to bend OpenLP into presenting images, even though it is a presentation...
I've looked around a bit and for now my best idea is to remove my PdfViewer class, and instead use my own instance of the MainDisplay class, which can present images. This way I reuse a "player", and avoid having to hack various parts too much. I haven't tried it yet, but I think it could work.
About python3, that shouldn't be an issue since I call the ghostscript or mupdf programs directly, not via python libs.

Revision history for this message
Jonathan Corwin (j-corwin) wrote : Posted in a previous version of this proposal

Tim, I don't understand why adding a new player /here/ will be a big problem. Yes it was a problem for media because that was all integrated with songs, the toolbar, etc and there was a lot of overlap across plugins.

However the Presentations plugin is completely designed to deal with third party apps. All they need to be able to do is blank, and the plugin will deal with the rest. Having a simple Window that can go full screen is not going to have the problems that we had with media. In fact I personally think trying to tie this in to use the existing display would make the code more complicated and error prone that keeping it standalone.

Revision history for this message
Tomas Groth (tomasgroth) wrote : Posted in a previous version of this proposal

Hi guys,
I've now also made a version which uses the approach Tim mentioned. It works quite well, but I haven't pushed it yet.
So how do we figure out which version should be used?

Revision history for this message
Jonathan Corwin (j-corwin) wrote : Posted in a previous version of this proposal

Push it and get it reviewed :)

Revision history for this message
Tomas Groth (tomasgroth) wrote : Posted in a previous version of this proposal

Ok, so I've just pushed the player-less version, where the image-presentation functionality is used instead. There is still a few issues. For example it tries to load the first image added to the serviceitem as a pdf-presentation when going live/preview. Haven't figured out why.

Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

That is because you have set the ServiceItem.processor.
You have code in the pdf route which is not needed.

On 27 July 2013 10:52, Tomas Groth <email address hidden> wrote:

> Ok, so I've just pushed the player-less version, where the
> image-presentation functionality is used instead. There is still a few
> issues. For example it tries to load the first image added to the
> serviceitem as a pdf-presentation when going live/preview. Haven't figured
> out why.
> --
> https://code.launchpad.net/~tomasgroth/openlp/mupdf/+merge/174849
> Your team OpenLP Core is requested to review the proposed merge of
> lp:~tomasgroth/openlp/mupdf into lp:openlp.
>
> _______________________________________________
> Mailing list: https://launchpad.net/~openlp-core
> Post to : <email address hidden>
> Unsubscribe : https://launchpad.net/~openlp-core
> More help : https://help.launchpad.net/ListHelp
>

--
Tim and Alison Bentley
<email address hidden>

Revision history for this message
Tomas Groth (tomasgroth) wrote : Posted in a previous version of this proposal

I've now added the possibility for pointing to ghostscript or mudraw if autodetection isn't working. Also minor fixes for the pdf-presentation code.

Revision history for this message
Tomas Groth (tomasgroth) wrote : Posted in a previous version of this proposal

Hi guys, I was wondering if you think something more should be added before this is ready to be merged into trunk? Does it make sense to make tests for presentation plugins?

Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

Looks good but these need to be fixed.

* settings screen the grouping has moved from the left side to the middle.
* settings screen "binary" has the "y" half chopped off.
* showing an image live / preview does not work when loaded from the service manager. Fine from the media manager. Have not tried to save / reload but guess that is broken.
* blank lines in functions please remove (general)
* questions in comments please removed.
* check_binary why is this outside the class as a standalone function?
* if os.name != u'nt': swap logic round so is positive and less chance of a miss read.
* 320 would this be better to have in the code a file instead of writing to a file each run?
* if self.pdf_program_path.text() != u'': should be if not self.pdf_program_path.text(): - General point.
* setting do not need "given" in the string.
* img should be image (old bad code copied!
* no tests.
* line 53 far too long.
* lines 52-64 do we need all this. if the images are generated then it would be a case of just loading them not processing them again?

review: Needs Fixing
Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

Please resubmit due to the age of this request and additionally it will need converting to Python 3 as on 1/9/2013 truck will be converted.

review: Needs Resubmitting
Revision history for this message
Tomas Groth (tomasgroth) wrote : Posted in a previous version of this proposal

I need some feedback/guidance/help to finish this implementation...
The basic idea is to use mupdf or ghostscript to create a picture for each page in the PDF, and when going to live/preview mode, then replace the service_item with an image-based service_item. I've made it work when going to live/preview from the mediamanager, but I can't figure out how/where to do it from the servicemanager - any hints would be appreciated! As an alternative it can be implemented as the other presentation-backends, with a "player".

I'd also like some directions as to what tests to include? I couldn't find any tests for the other presentation backends...

And finally, I've added some extra fields to the presentation-settings-tab, and for some reason now all fields has moved a bit to the right instead for being to the far left - any hints as to why?

Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

Hi Tomas, in it's current state your merge proposal has some conflicts. Can you merge from trunk and fix those please?

review: Needs Fixing
Revision history for this message
Tomas Groth (tomasgroth) wrote : Posted in a previous version of this proposal

Merged with trunk to resolve conflicts. Last set of questions still applies.

Revision history for this message
Tomas Groth (tomasgroth) wrote :

I think I've resolved most problems now and even added some simple tests.
The basic idea is to (still) use mupdf or ghostscript to create a picture for each page in the PDF, and when going to live/preview mode the service_item is replaced with an image-based service_item on the fly.

Revision history for this message
Tim Bentley (trb143) wrote :

Conflicts with head.

lines 8-13 why. This needs a good explanation as it looks like a botch.
lines 52-53 - fix for PEP8 - have a look at other places as well.
line 77 - why not service?

Linn 200+ Confused. If generated from plugin we have a image service item. Good. If started from Service Manager we need to convert. Why? How did it get to the service manager cos the plugin would have created an image service item not a presentation one. Also not sure about exposing the innards of the serviceitem in this could. could it be a new method on the service item?

tmpFile = tmp_file

484 - 498 Line lengths.
611, 668, 675 etc tabbing
650 space

review: Needs Fixing
lp:~tomasgroth/openlp/mupdf updated
2291. By Tomas Groth

Changed the way a serviceitem from the servicemanager is copied and converted. Also fixed for PEP8.

2292. By Tomas Groth

merged with trunk

2293. By Tomas Groth

Added some docstring documentation.

2294. By Tomas Groth

Moved PS script into separate file.

2295. By Tomas Groth

Make the PdfController reload backend if setting changes.

2296. By Tomas Groth

Only allow pdf-program selection using filedialog.

2297. By Tomas Groth

merged with trunk

2298. By Tomas Groth

fixed an exception

2299. By Tomas Groth

merged with head

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'openlp/core/ui/slidecontroller.py'
--- openlp/core/ui/slidecontroller.py 2013-12-28 21:33:38 +0000
+++ openlp/core/ui/slidecontroller.py 2013-12-31 12:36:15 +0000
@@ -436,7 +436,7 @@
436 # "V1" was the slide we wanted to go.436 # "V1" was the slide we wanted to go.
437 self.preview_widget.change_slide(self.slide_list[self.current_shortcut])437 self.preview_widget.change_slide(self.slide_list[self.current_shortcut])
438 self.slide_selected()438 self.slide_selected()
439 # Reset the shortcut.439 # Reset the shortcut.
440 self.current_shortcut = ''440 self.current_shortcut = ''
441441
442 def set_live_hot_keys(self, parent=None):442 def set_live_hot_keys(self, parent=None):
@@ -730,7 +730,7 @@
730 if old_item and self.is_live and old_item.is_capable(ItemCapabilities.ProvidesOwnDisplay):730 if old_item and self.is_live and old_item.is_capable(ItemCapabilities.ProvidesOwnDisplay):
731 self._reset_blank()731 self._reset_blank()
732 Registry().execute(732 Registry().execute(
733 '%s_start' % service_item.name.lower(), [service_item, self.is_live, self.hide_mode(), slide_no])733 '%s_start' % service_item.name.lower(), [self.service_item, self.is_live, self.hide_mode(), slide_no])
734 self.slide_list = {}734 self.slide_list = {}
735 if self.is_live:735 if self.is_live:
736 self.song_menu.menu().clear()736 self.song_menu.menu().clear()
737737
=== modified file 'openlp/plugins/presentations/lib/mediaitem.py'
--- openlp/plugins/presentations/lib/mediaitem.py 2013-12-28 21:33:38 +0000
+++ openlp/plugins/presentations/lib/mediaitem.py 2013-12-31 12:36:15 +0000
@@ -116,7 +116,7 @@
116 self.display_type_label = QtGui.QLabel(self.presentation_widget)116 self.display_type_label = QtGui.QLabel(self.presentation_widget)
117 self.display_type_label.setObjectName('display_type_label')117 self.display_type_label.setObjectName('display_type_label')
118 self.display_type_combo_box = create_horizontal_adjusting_combo_box(self.presentation_widget,118 self.display_type_combo_box = create_horizontal_adjusting_combo_box(self.presentation_widget,
119 'display_type_combo_box')119 'display_type_combo_box')
120 self.display_type_label.setBuddy(self.display_type_combo_box)120 self.display_type_label.setBuddy(self.display_type_combo_box)
121 self.display_layout.addRow(self.display_type_label, self.display_type_combo_box)121 self.display_layout.addRow(self.display_type_label, self.display_type_combo_box)
122 # Add the Presentation widget to the page layout.122 # Add the Presentation widget to the page layout.
@@ -177,9 +177,8 @@
177 if titles.count(filename) > 0:177 if titles.count(filename) > 0:
178 if not initial_load:178 if not initial_load:
179 critical_error_message_box(translate('PresentationPlugin.MediaItem', 'File Exists'),179 critical_error_message_box(translate('PresentationPlugin.MediaItem', 'File Exists'),
180 translate('PresentationPlugin.MediaItem',180 translate('PresentationPlugin.MediaItem',
181 'A presentation with that filename already exists.')181 'A presentation with that filename already exists.'))
182 )
183 continue182 continue
184 controller_name = self.findControllerByType(filename)183 controller_name = self.findControllerByType(filename)
185 if controller_name:184 if controller_name:
@@ -203,7 +202,8 @@
203 icon = build_icon(':/general/general_delete.png')202 icon = build_icon(':/general/general_delete.png')
204 else:203 else:
205 critical_error_message_box(UiStrings().UnsupportedFile,204 critical_error_message_box(UiStrings().UnsupportedFile,
206 translate('PresentationPlugin.MediaItem', 'This type of presentation is not supported.'))205 translate('PresentationPlugin.MediaItem',
206 'This type of presentation is not supported.'))
207 continue207 continue
208 item_name = QtGui.QListWidgetItem(filename)208 item_name = QtGui.QListWidgetItem(filename)
209 item_name.setData(QtCore.Qt.UserRole, file)209 item_name.setData(QtCore.Qt.UserRole, file)
@@ -238,7 +238,7 @@
238 Settings().setValue(self.settings_section + '/presentations files', self.get_file_list())238 Settings().setValue(self.settings_section + '/presentations files', self.get_file_list())
239239
240 def generate_slide_data(self, service_item, item=None, xml_version=False,240 def generate_slide_data(self, service_item, item=None, xml_version=False,
241 remote=False, context=ServiceItemContext.Service):241 remote=False, context=ServiceItemContext.Service, presentation_file=None):
242 """242 """
243 Load the relevant information for displaying the presentation in the slidecontroller. In the case of243 Load the relevant information for displaying the presentation in the slidecontroller. In the case of
244 powerpoints, an image for each slide.244 powerpoints, an image for each slide.
@@ -249,45 +249,93 @@
249 items = self.list_view.selectedItems()249 items = self.list_view.selectedItems()
250 if len(items) > 1:250 if len(items) > 1:
251 return False251 return False
252 service_item.processor = self.display_type_combo_box.currentText()252 filename = presentation_file
253 service_item.add_capability(ItemCapabilities.ProvidesOwnDisplay)253 if filename is None:
254 filename = items[0].data(QtCore.Qt.UserRole)
255 file_type = os.path.splitext(filename)[1][1:]
254 if not self.display_type_combo_box.currentText():256 if not self.display_type_combo_box.currentText():
255 return False257 return False
256 for bitem in items:258 if (file_type == 'pdf' or file_type == 'xps') and context != ServiceItemContext.Service:
257 filename = bitem.data(QtCore.Qt.UserRole)259 service_item.add_capability(ItemCapabilities.CanMaintain)
258 (path, name) = os.path.split(filename)260 service_item.add_capability(ItemCapabilities.CanPreview)
259 service_item.title = name261 service_item.add_capability(ItemCapabilities.CanLoop)
260 if os.path.exists(filename):262 service_item.add_capability(ItemCapabilities.CanAppend)
261 if service_item.processor == self.automatic:263 # force a nonexistent theme
262 service_item.processor = self.findControllerByType(filename)264 service_item.theme = -1
263 if not service_item.processor:265 for bitem in items:
266 filename = presentation_file
267 if filename is None:
268 filename = bitem.data(QtCore.Qt.UserRole)
269 (path, name) = os.path.split(filename)
270 service_item.title = name
271 if os.path.exists(filename):
272 processor = self.findControllerByType(filename)
273 if not processor:
264 return False274 return False
265 controller = self.controllers[service_item.processor]275 controller = self.controllers[processor]
266 doc = controller.add_document(filename)276 service_item.processor = None
267 if doc.get_thumbnail_path(1, True) is None:277 doc = controller.add_document(filename)
268 doc.load_presentation()278 if doc.get_thumbnail_path(1, True) is None or not os.path.isfile(
269 i = 1279 os.path.join(doc.get_temp_folder(), 'mainslide001.png')):
270 img = doc.get_thumbnail_path(i, True)280 doc.load_presentation()
271 if img:281 i = 1
272 while img:282 imagefile = 'mainslide%03d.png' % i
273 service_item.add_from_command(path, name, img)283 image = os.path.join(doc.get_temp_folder(), imagefile)
284 while os.path.isfile(image):
285 service_item.add_from_image(image, name)
274 i += 1286 i += 1
275 img = doc.get_thumbnail_path(i, True)287 imagefile = 'mainslide%03d.png' % i
288 image = os.path.join(doc.get_temp_folder(), imagefile)
276 doc.close_presentation()289 doc.close_presentation()
277 return True290 return True
278 else:291 else:
279 # File is no longer present292 # File is no longer present
280 if not remote:293 if not remote:
281 critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'),294 critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'),
282 translate('PresentationPlugin.MediaItem',295 translate('PresentationPlugin.MediaItem',
283 'The presentation %s is incomplete, please reload.') % filename)296 'The presentation %s no longer exists.') % filename)
284 return False297 return False
285 else:298 else:
286 # File is no longer present299 service_item.processor = self.display_type_combo_box.currentText()
287 if not remote:300 service_item.add_capability(ItemCapabilities.ProvidesOwnDisplay)
288 critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'),301 for bitem in items:
289 translate('PresentationPlugin.MediaItem', 'The presentation %s no longer exists.') % filename)302 filename = bitem.data(QtCore.Qt.UserRole)
290 return False303 (path, name) = os.path.split(filename)
304 service_item.title = name
305 if os.path.exists(filename):
306 if service_item.processor == self.automatic:
307 service_item.processor = self.findControllerByType(filename)
308 if not service_item.processor:
309 return False
310 controller = self.controllers[service_item.processor]
311 doc = controller.add_document(filename)
312 if doc.get_thumbnail_path(1, True) is None:
313 doc.load_presentation()
314 i = 1
315 img = doc.get_thumbnail_path(i, True)
316 if img:
317 while img:
318 service_item.add_from_command(path, name, img)
319 i += 1
320 img = doc.get_thumbnail_path(i, True)
321 doc.close_presentation()
322 return True
323 else:
324 # File is no longer present
325 if not remote:
326 critical_error_message_box(translate('PresentationPlugin.MediaItem',
327 'Missing Presentation'),
328 translate('PresentationPlugin.MediaItem',
329 'The presentation %s is incomplete, please reload.')
330 % filename)
331 return False
332 else:
333 # File is no longer present
334 if not remote:
335 critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'),
336 translate('PresentationPlugin.MediaItem',
337 'The presentation %s no longer exists.') % filename)
338 return False
291339
292 def findControllerByType(self, filename):340 def findControllerByType(self, filename):
293 """341 """
294342
=== modified file 'openlp/plugins/presentations/lib/messagelistener.py'
--- openlp/plugins/presentations/lib/messagelistener.py 2013-12-28 21:33:38 +0000
+++ openlp/plugins/presentations/lib/messagelistener.py 2013-12-31 12:36:15 +0000
@@ -28,11 +28,13 @@
28###############################################################################28###############################################################################
2929
30import logging30import logging
31import copy
3132
32from PyQt4 import QtCore33from PyQt4 import QtCore
3334
34from openlp.core.common import Registry35from openlp.core.common import Registry
35from openlp.core.ui import HideMode36from openlp.core.ui import HideMode
37from openlp.core.lib import ServiceItemContext, ServiceItem
3638
37log = logging.getLogger(__name__)39log = logging.getLogger(__name__)
3840
@@ -69,6 +71,7 @@
69 return71 return
70 self.doc.slidenumber = slide_no72 self.doc.slidenumber = slide_no
71 self.hide_mode = hide_mode73 self.hide_mode = hide_mode
74 log.debug('add_handler, slidenumber: %d' % slide_no)
72 if self.is_live:75 if self.is_live:
73 if hide_mode == HideMode.Screen:76 if hide_mode == HideMode.Screen:
74 Registry().execute('live_display_hide', HideMode.Screen)77 Registry().execute('live_display_hide', HideMode.Screen)
@@ -316,6 +319,28 @@
316 hide_mode = message[2]319 hide_mode = message[2]
317 file = item.get_frame_path()320 file = item.get_frame_path()
318 self.handler = item.processor321 self.handler = item.processor
322 # When starting presentation from the servicemanager we convert
323 # PDF/XPS-serviceitems into image-serviceitems. When started from the mediamanager
324 # the conversion has already been done at this point.
325 if file.endswith('.pdf') or file.endswith('.xps'):
326 log.debug('Converting from pdf/xps to images for serviceitem with file %s', file)
327 # Create a copy of the original item, and then clear the original item so it can be filled with images
328 item_cpy = copy.copy(item)
329 item.__init__(None)
330 if is_live:
331 self.media_item.generate_slide_data(item, item_cpy, False, False, ServiceItemContext.Live, file)
332 else:
333 self.media_item.generate_slide_data(item, item_cpy, False, False, ServiceItemContext.Preview, file)
334 # Some of the original serviceitem attributes is needed in the new serviceitem
335 item.footer = item_cpy.footer
336 item.from_service = item_cpy.from_service
337 item.iconic_representation = item_cpy.iconic_representation
338 item.image_border = item_cpy.image_border
339 item.main = item_cpy.main
340 item.theme_data = item_cpy.theme_data
341 # When presenting PDF or XPS, we are using the image presentation code,
342 # so handler & processor is set to None, and we skip adding the handler.
343 self.handler = None
319 if self.handler == self.media_item.automatic:344 if self.handler == self.media_item.automatic:
320 self.handler = self.media_item.findControllerByType(file)345 self.handler = self.media_item.findControllerByType(file)
321 if not self.handler:346 if not self.handler:
@@ -324,7 +349,12 @@
324 controller = self.live_handler349 controller = self.live_handler
325 else:350 else:
326 controller = self.preview_handler351 controller = self.preview_handler
327 controller.add_handler(self.controllers[self.handler], file, hide_mode, message[3])352 # When presenting PDF or XPS, we are using the image presentation code,
353 # so handler & processor is set to None, and we skip adding the handler.
354 if self.handler is None:
355 self.controller = controller
356 else:
357 controller.add_handler(self.controllers[self.handler], file, hide_mode, message[3])
328358
329 def slide(self, message):359 def slide(self, message):
330 """360 """
331361
=== 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-12-31 12:36:15 +0000
@@ -0,0 +1,306 @@
1# -*- coding: utf-8 -*-
2# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
3
4###############################################################################
5# OpenLP - Open Source Lyrics Projection #
6# --------------------------------------------------------------------------- #
7# Copyright (c) 2008-2014 Raoul Snyman #
8# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
9# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
10# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
11# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
12# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
13# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
14# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
15# --------------------------------------------------------------------------- #
16# This program is free software; you can redistribute it and/or modify it #
17# under the terms of the GNU General Public License as published by the Free #
18# Software Foundation; version 2 of the License. #
19# #
20# This program is distributed in the hope that it will be useful, but WITHOUT #
21# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
22# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
23# more details. #
24# #
25# You should have received a copy of the GNU General Public License along #
26# with this program; if not, write to the Free Software Foundation, Inc., 59 #
27# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
28###############################################################################
29
30import os
31import logging
32from tempfile import NamedTemporaryFile
33import re
34from subprocess import check_output, CalledProcessError, STDOUT
35
36from openlp.core.utils import AppLocation
37from openlp.core.common import Settings
38from openlp.core.lib import ScreenList
39from .presentationcontroller import PresentationController, PresentationDocument
40
41log = logging.getLogger(__name__)
42
43
44class PdfController(PresentationController):
45 """
46 Class to control PDF presentations
47 """
48 log.info('PdfController loaded')
49
50 def __init__(self, plugin):
51 """
52 Initialise the class
53 """
54 log.debug('Initialising')
55 self.process = None
56 PresentationController.__init__(self, plugin, 'Pdf', PdfDocument)
57 self.supports = ['pdf']
58 self.mudrawbin = ''
59 self.gsbin = ''
60 if self.check_installed() and self.mudrawbin:
61 self.also_supports = ['xps']
62
63 @staticmethod
64 def check_binary(program_path):
65 """
66 Function that checks whether a binary is either ghostscript or mudraw or neither.
67 Is also used from presentationtab.py
68 """
69 program_type = None
70 runlog = ''
71 log.debug('testing program_path: %s', program_path)
72 try:
73 runlog = check_output([program_path, '--help'], stderr=STDOUT)
74 except CalledProcessError as e:
75 runlog = e.output
76 except Exception:
77 runlog = ''
78 # Analyse the output to see it the program is mudraw, ghostscript or neither
79 for line in runlog.splitlines():
80 decoded_line = line.decode()
81 found_mudraw = re.search('usage: mudraw.*', decoded_line)
82 if found_mudraw:
83 program_type = 'mudraw'
84 break
85 found_gs = re.search('GPL Ghostscript.*', decoded_line)
86 if found_gs:
87 program_type = 'gs'
88 break
89 log.debug('in check_binary, found: %s', program_type)
90 return program_type
91
92 def check_available(self):
93 """
94 PdfController is able to run on this machine.
95 """
96 log.debug('check_available Pdf')
97 return self.check_installed()
98
99 def check_installed(self):
100 """
101 Check the viewer is installed.
102 """
103 log.debug('check_installed Pdf')
104 # Use the user defined program if given
105 if (Settings().value('presentations/enable_pdf_program')):
106 pdf_program = Settings().value('presentations/pdf_program')
107 program_type = self.check_binary(pdf_program)
108 if program_type == 'gs':
109 self.gsbin = pdf_program
110 return True
111 elif program_type == 'mudraw':
112 self.mudrawbin = pdf_program
113 return True
114 # Fallback to autodetection
115 application_path = AppLocation.get_directory(AppLocation.AppDir)
116 if os.name == 'nt':
117 # for windows we only accept mudraw.exe in the base folder
118 application_path = AppLocation.get_directory(AppLocation.AppDir)
119 if os.path.isfile(application_path + '/../mudraw.exe'):
120 self.mudrawbin = application_path + '/../mudraw.exe'
121 else:
122 # First try to find mupdf
123 try:
124 self.mudrawbin = check_output(['which', 'mudraw']).decode(encoding='UTF-8').rstrip('\n')
125 except CalledProcessError:
126 self.mudrawbin = ''
127 # if mupdf isn't installed, fallback to ghostscript
128 if not self.mudrawbin:
129 try:
130 self.gsbin = check_output(['which', 'gs']).rstrip('\n')
131 except CalledProcessError:
132 self.gsbin = ''
133 # Last option: check if mudraw is placed in OpenLP base folder
134 if not self.mudrawbin and not self.gsbin:
135 application_path = AppLocation.get_directory(AppLocation.AppDir)
136 if os.path.isfile(application_path + '/../mudraw'):
137 self.mudrawbin = application_path + '/../mudraw'
138 if not self.mudrawbin and not self.gsbin:
139 return False
140 else:
141 return True
142
143 def kill(self):
144 """
145 Called at system exit to clean up any running presentations
146 """
147 log.debug('Kill pdfviewer')
148 while self.docs:
149 self.docs[0].close_presentation()
150
151
152class PdfDocument(PresentationDocument):
153 """
154 Class which holds information of a single presentation.
155 This class is not actually used to present the PDF, instead we convert to
156 image-serviceitem on the fly and present as such. Therefore some of the 'playback'
157 functions is not implemented.
158 """
159 def __init__(self, controller, presentation):
160 """
161 Constructor, store information about the file and initialise.
162 """
163 log.debug('Init Presentation Pdf')
164 PresentationDocument.__init__(self, controller, presentation)
165 self.presentation = None
166 self.blanked = False
167 self.hidden = False
168 self.image_files = []
169 self.num_pages = -1
170
171 def gs_get_resolution(self, size):
172 """
173 Only used when using ghostscript
174 Ghostscript can't scale automaticly while keeping aspect like mupdf, so we need
175 to get the ratio bewteen the screen size and the PDF to scale
176 """
177 # Use a postscript script to get size of the pdf. It is assumed that all pages have same size
178 postscript = '%!PS \n\
179() = \n\
180File dup (r) file runpdfbegin \n\
1811 pdfgetpage dup \n\
182/MediaBox pget { \n\
183aload pop exch 4 1 roll exch sub 3 1 roll sub \n\
184( Size: x: ) print =print (, y: ) print =print (\n) print \n\
185} if \n\
186flush \n\
187quit \n\
188'
189 # Put postscript into tempfile
190 tmp_file = NamedTemporaryFile(delete=False)
191 tmp_file.write(postscript)
192 tmp_file.close()
193 # Run the script on the pdf to get the size
194 runlog = []
195 try:
196 runlog = check_output([self.controller.gsbin, '-dNOPAUSE', '-dNODISPLAY', '-dBATCH',
197 '-sFile=' + self.filepath, tmp_file.name])
198 except CalledProcessError as e:
199 log.debug(' '.join(e.cmd))
200 log.debug(e.output)
201 os.unlink(tmp_file.name)
202 # Extract the pdf resolution from output, the format is " Size: x: <width>, y: <height>"
203 width = 0
204 height = 0
205 for line in runlog.splitlines():
206 try:
207 width = re.search('.*Size: x: (\d+\.?\d*), y: \d+.*', line).group(1)
208 height = re.search('.*Size: x: \d+\.?\d*, y: (\d+\.?\d*).*', line).group(1)
209 break
210 except AttributeError:
211 pass
212 # Calculate the ratio from pdf to screen
213 if width > 0 and height > 0:
214 width_ratio = size.right() / float(width)
215 height_ratio = size.bottom() / float(height)
216 # return the resolution that should be used. 72 is default.
217 if width_ratio > height_ratio:
218 return int(height_ratio * 72)
219 else:
220 return int(width_ratio * 72)
221 else:
222 return 72
223
224 def load_presentation(self):
225 """
226 Called when a presentation is added to the SlideController. It generates images from the PDF.
227 """
228 log.debug('load_presentation pdf')
229 # Check if the images has already been created, and if yes load them
230 if os.path.isfile(os.path.join(self.get_temp_folder(), 'mainslide001.png')):
231 created_files = sorted(os.listdir(self.get_temp_folder()))
232 for fn in created_files:
233 if os.path.isfile(os.path.join(self.get_temp_folder(), fn)):
234 self.image_files.append(os.path.join(self.get_temp_folder(), fn))
235 self.num_pages = len(self.image_files)
236 return True
237 size = ScreenList().current['size']
238 # Generate images from PDF that will fit the frame.
239 runlog = ''
240 try:
241 if not os.path.isdir(self.get_temp_folder()):
242 os.makedirs(self.get_temp_folder())
243 if self.controller.mudrawbin:
244 runlog = check_output([self.controller.mudrawbin, '-w', str(size.right()), '-h', str(size.bottom()),
245 '-o', os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.filepath])
246 elif self.controller.gsbin:
247 resolution = self.gs_get_resolution(size)
248 runlog = check_output([self.controller.gsbin, '-dSAFER', '-dNOPAUSE', '-dBATCH', '-sDEVICE=png16m',
249 '-r' + str(resolution), '-dTextAlphaBits=4', '-dGraphicsAlphaBits=4',
250 '-sOutputFile=' + os.path.join(self.get_temp_folder(), 'mainslide%03d.png'),
251 self.filepath])
252 created_files = sorted(os.listdir(self.get_temp_folder()))
253 for fn in created_files:
254 if os.path.isfile(os.path.join(self.get_temp_folder(), fn)):
255 self.image_files.append(os.path.join(self.get_temp_folder(), fn))
256 except Exception as e:
257 log.debug(e)
258 log.debug(runlog)
259 return False
260 self.num_pages = len(self.image_files)
261 # Create thumbnails
262 self.create_thumbnails()
263 return True
264
265 def create_thumbnails(self):
266 """
267 Generates thumbnails
268 """
269 log.debug('create_thumbnails pdf')
270 if self.check_thumbnails():
271 return
272 # use builtin function to create thumbnails from generated images
273 index = 1
274 for image in self.image_files:
275 self.convert_thumbnail(image, index)
276 index += 1
277
278 def close_presentation(self):
279 """
280 Close presentation and clean up objects. Triggered by new object being added to SlideController or OpenLP being
281 shut down.
282 """
283 log.debug('close_presentation pdf')
284 self.controller.remove_doc(self)
285
286 def is_loaded(self):
287 """
288 Returns true if a presentation is loaded.
289 """
290 log.debug('is_loaded pdf')
291 if self.num_pages < 0:
292 return False
293 return True
294
295 def is_active(self):
296 """
297 Returns true if a presentation is currently active.
298 """
299 log.debug('is_active pdf')
300 return self.is_loaded() and not self.hidden
301
302 def get_slide_count(self):
303 """
304 Returns total number of slides
305 """
306 return self.num_pages
0307
=== modified file 'openlp/plugins/presentations/lib/presentationtab.py'
--- openlp/plugins/presentations/lib/presentationtab.py 2013-12-24 08:56:50 +0000
+++ openlp/plugins/presentations/lib/presentationtab.py 2013-12-31 12:36:15 +0000
@@ -30,7 +30,9 @@
30from PyQt4 import QtGui30from PyQt4 import QtGui
3131
32from openlp.core.common import Settings, UiStrings, translate32from openlp.core.common import Settings, UiStrings, translate
33from openlp.core.lib import SettingsTab33from openlp.core.lib import SettingsTab, build_icon
34from openlp.core.lib.ui import critical_error_message_box
35from .pdfcontroller import PdfController
3436
3537
36class PresentationTab(SettingsTab):38class PresentationTab(SettingsTab):
@@ -64,6 +66,7 @@
64 self.presenter_check_boxes[controller.name] = checkbox66 self.presenter_check_boxes[controller.name] = checkbox
65 self.controllers_layout.addWidget(checkbox)67 self.controllers_layout.addWidget(checkbox)
66 self.left_layout.addWidget(self.controllers_group_box)68 self.left_layout.addWidget(self.controllers_group_box)
69 # Advanced
67 self.advanced_group_box = QtGui.QGroupBox(self.left_column)70 self.advanced_group_box = QtGui.QGroupBox(self.left_column)
68 self.advanced_group_box.setObjectName('advanced_group_box')71 self.advanced_group_box.setObjectName('advanced_group_box')
69 self.advanced_layout = QtGui.QVBoxLayout(self.advanced_group_box)72 self.advanced_layout = QtGui.QVBoxLayout(self.advanced_group_box)
@@ -72,8 +75,35 @@
72 self.override_app_check_box.setObjectName('override_app_check_box')75 self.override_app_check_box.setObjectName('override_app_check_box')
73 self.advanced_layout.addWidget(self.override_app_check_box)76 self.advanced_layout.addWidget(self.override_app_check_box)
74 self.left_layout.addWidget(self.advanced_group_box)77 self.left_layout.addWidget(self.advanced_group_box)
78 # Pdf options
79 self.pdf_group_box = QtGui.QGroupBox(self.left_column)
80 self.pdf_group_box.setObjectName('pdf_group_box')
81 self.pdf_layout = QtGui.QFormLayout(self.pdf_group_box)
82 self.pdf_layout.setObjectName('pdf_layout')
83 self.pdf_program_check_box = QtGui.QCheckBox(self.pdf_group_box)
84 self.pdf_program_check_box.setObjectName('pdf_program_check_box')
85 self.pdf_layout.addRow(self.pdf_program_check_box)
86 self.pdf_program_path_layout = QtGui.QHBoxLayout()
87 self.pdf_program_path_layout.setObjectName('pdf_program_path_layout')
88 self.pdf_program_path = QtGui.QLineEdit(self.pdf_group_box)
89 self.pdf_program_path.setObjectName('pdf_program_path')
90 self.pdf_program_path.setReadOnly(True)
91 self.pdf_program_path.setPalette(self.get_grey_text_palette(True))
92 self.pdf_program_path_layout.addWidget(self.pdf_program_path)
93 self.pdf_program_browse_button = QtGui.QToolButton(self.pdf_group_box)
94 self.pdf_program_browse_button.setObjectName('pdf_program_browse_button')
95 self.pdf_program_browse_button.setIcon(build_icon(':/general/general_open.png'))
96 self.pdf_program_browse_button.setEnabled(False)
97 self.pdf_program_path_layout.addWidget(self.pdf_program_browse_button)
98 self.pdf_layout.addRow(self.pdf_program_path_layout)
99 self.left_layout.addWidget(self.pdf_group_box)
75 self.left_layout.addStretch()100 self.left_layout.addStretch()
101 self.right_column.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred)
76 self.right_layout.addStretch()102 self.right_layout.addStretch()
103 # Signals and slots
104 self.pdf_program_path.editingFinished.connect(self.on_pdf_program_path_edit_finished)
105 self.pdf_program_browse_button.clicked.connect(self.on_pdf_program_browse_button_clicked)
106 self.pdf_program_check_box.clicked.connect(self.on_pdf_program_check_box_clicked)
77107
78 def retranslateUi(self):108 def retranslateUi(self):
79 """109 """
@@ -85,8 +115,11 @@
85 checkbox = self.presenter_check_boxes[controller.name]115 checkbox = self.presenter_check_boxes[controller.name]
86 self.set_controller_text(checkbox, controller)116 self.set_controller_text(checkbox, controller)
87 self.advanced_group_box.setTitle(UiStrings().Advanced)117 self.advanced_group_box.setTitle(UiStrings().Advanced)
118 self.pdf_group_box.setTitle(translate('PresentationPlugin.PresentationTab', 'PDF options'))
88 self.override_app_check_box.setText(119 self.override_app_check_box.setText(
89 translate('PresentationPlugin.PresentationTab', 'Allow presentation application to be overridden'))120 translate('PresentationPlugin.PresentationTab', 'Allow presentation application to be overridden'))
121 self.pdf_program_check_box.setText(
122 translate('PresentationPlugin.PresentationTab', 'Use given full path for mudraw or ghostscript binary:'))
90123
91 def set_controller_text(self, checkbox, controller):124 def set_controller_text(self, checkbox, controller):
92 if checkbox.isEnabled():125 if checkbox.isEnabled():
@@ -103,6 +136,15 @@
103 checkbox = self.presenter_check_boxes[controller.name]136 checkbox = self.presenter_check_boxes[controller.name]
104 checkbox.setChecked(Settings().value(self.settings_section + '/' + controller.name))137 checkbox.setChecked(Settings().value(self.settings_section + '/' + controller.name))
105 self.override_app_check_box.setChecked(Settings().value(self.settings_section + '/override app'))138 self.override_app_check_box.setChecked(Settings().value(self.settings_section + '/override app'))
139 # load pdf-program settings
140 enable_pdf_program = Settings().value(self.settings_section + '/enable_pdf_program')
141 self.pdf_program_check_box.setChecked(enable_pdf_program)
142 self.pdf_program_path.setReadOnly(not enable_pdf_program)
143 self.pdf_program_path.setPalette(self.get_grey_text_palette(not enable_pdf_program))
144 self.pdf_program_browse_button.setEnabled(enable_pdf_program)
145 pdf_program = Settings().value(self.settings_section + '/pdf_program')
146 if pdf_program:
147 self.pdf_program_path.setText(pdf_program)
106148
107 def save(self):149 def save(self):
108 """150 """
@@ -128,6 +170,18 @@
128 if Settings().value(setting_key) != self.override_app_check_box.checkState():170 if Settings().value(setting_key) != self.override_app_check_box.checkState():
129 Settings().setValue(setting_key, self.override_app_check_box.checkState())171 Settings().setValue(setting_key, self.override_app_check_box.checkState())
130 changed = True172 changed = True
173 # Save pdf-settings
174 pdf_program = self.pdf_program_path.text()
175 enable_pdf_program = self.pdf_program_check_box.checkState()
176 # If the given program is blank disable using the program
177 if pdf_program == '':
178 enable_pdf_program = 0
179 if pdf_program != Settings().value(self.settings_section + '/pdf_program'):
180 Settings().setValue(self.settings_section + '/pdf_program', pdf_program)
181 changed = True
182 if enable_pdf_program != Settings().value(self.settings_section + '/enable_pdf_program'):
183 Settings().setValue(self.settings_section + '/enable_pdf_program', enable_pdf_program)
184 changed = True
131 if changed:185 if changed:
132 self.settings_form.register_post_process('mediaitem_suffix_reset')186 self.settings_form.register_post_process('mediaitem_suffix_reset')
133 self.settings_form.register_post_process('mediaitem_presentation_rebuild')187 self.settings_form.register_post_process('mediaitem_presentation_rebuild')
@@ -143,3 +197,46 @@
143 checkbox = self.presenter_check_boxes[controller.name]197 checkbox = self.presenter_check_boxes[controller.name]
144 checkbox.setEnabled(controller.is_available())198 checkbox.setEnabled(controller.is_available())
145 self.set_controller_text(checkbox, controller)199 self.set_controller_text(checkbox, controller)
200
201 def on_pdf_program_path_edit_finished(self):
202 """
203 After selecting/typing in a program it is validated that it is a actually ghostscript or mudraw
204 """
205 program_type = None
206 if self.pdf_program_path.text() != '':
207 program_type = PdfController.check_binary(self.pdf_program_path.text())
208 if not program_type:
209 critical_error_message_box(UiStrings().Error,
210 translate('PresentationPlugin.PresentationTab',
211 'The program is not ghostscript or mudraw which is required.'))
212 self.pdf_program_path.setFocus()
213
214 def on_pdf_program_browse_button_clicked(self):
215 """
216 Select the mudraw or ghostscript binary that should be used.
217 """
218 filename = QtGui.QFileDialog.getOpenFileName(self, translate('PresentationPlugin.PresentationTab',
219 'Select mudraw or ghostscript binary.'))
220 if filename:
221 self.pdf_program_path.setText(filename)
222 self.pdf_program_path.setFocus()
223
224 def on_pdf_program_check_box_clicked(self, checked):
225 """
226 When checkbox for manual entering pdf-program is clicked,
227 enable or disable the textbox for the programpath and the browse-button.
228 """
229 self.pdf_program_path.setReadOnly(not checked)
230 self.pdf_program_path.setPalette(self.get_grey_text_palette(not checked))
231 self.pdf_program_browse_button.setEnabled(checked)
232
233 def get_grey_text_palette(self, greyed):
234 """
235 Returns a QPalette with greyed out text as used for placeholderText.
236 """
237 palette = QtGui.QPalette()
238 color = self.palette().color(QtGui.QPalette.Active, QtGui.QPalette.Text)
239 if greyed:
240 color.setAlpha(128)
241 palette.setColor(QtGui.QPalette.Active, QtGui.QPalette.Text, color)
242 return palette
146243
=== modified file 'openlp/plugins/presentations/presentationplugin.py'
--- openlp/plugins/presentations/presentationplugin.py 2013-12-24 08:56:50 +0000
+++ openlp/plugins/presentations/presentationplugin.py 2013-12-31 12:36:15 +0000
@@ -45,9 +45,12 @@
4545
46__default_settings__ = {46__default_settings__ = {
47 'presentations/override app': QtCore.Qt.Unchecked,47 'presentations/override app': QtCore.Qt.Unchecked,
48 'presentations/enable_pdf_program': QtCore.Qt.Unchecked,
49 'presentations/pdf_program': '',
48 'presentations/Impress': QtCore.Qt.Checked,50 'presentations/Impress': QtCore.Qt.Checked,
49 'presentations/Powerpoint': QtCore.Qt.Checked,51 'presentations/Powerpoint': QtCore.Qt.Checked,
50 'presentations/Powerpoint Viewer': QtCore.Qt.Checked,52 'presentations/Powerpoint Viewer': QtCore.Qt.Checked,
53 'presentations/Pdf': QtCore.Qt.Checked,
51 'presentations/presentations files': []54 'presentations/presentations files': []
52}55}
5356
5457
=== modified file 'resources/pyinstaller/hook-openlp.plugins.presentations.presentationplugin.py'
--- resources/pyinstaller/hook-openlp.plugins.presentations.presentationplugin.py 2013-12-24 08:56:50 +0000
+++ resources/pyinstaller/hook-openlp.plugins.presentations.presentationplugin.py 2013-12-31 12:36:15 +0000
@@ -29,4 +29,5 @@
2929
30hiddenimports = ['openlp.plugins.presentations.lib.impresscontroller',30hiddenimports = ['openlp.plugins.presentations.lib.impresscontroller',
31 'openlp.plugins.presentations.lib.powerpointcontroller',31 'openlp.plugins.presentations.lib.powerpointcontroller',
32 'openlp.plugins.presentations.lib.pptviewcontroller']32 'openlp.plugins.presentations.lib.pptviewcontroller',
33 'openlp.plugins.presentations.lib.pdfcontroller']
3334
=== modified file 'tests/functional/openlp_plugins/presentations/test_mediaitem.py'
--- tests/functional/openlp_plugins/presentations/test_mediaitem.py 2013-12-28 21:33:38 +0000
+++ tests/functional/openlp_plugins/presentations/test_mediaitem.py 2013-12-31 12:36:15 +0000
@@ -75,11 +75,16 @@
75 presentation_controller.also_supports = []75 presentation_controller.also_supports = []
76 presentation_viewer_controller = MagicMock()76 presentation_viewer_controller = MagicMock()
77 presentation_viewer_controller.enabled.return_value = False77 presentation_viewer_controller.enabled.return_value = False
78 pdf_controller = MagicMock()
79 pdf_controller.enabled.return_value = True
80 pdf_controller.supports = ['pdf']
81 pdf_controller.also_supports = ['xps']
78 # Mock the controllers.82 # Mock the controllers.
79 self.media_item.controllers = {83 self.media_item.controllers = {
80 'Impress': impress_controller,84 'Impress': impress_controller,
81 'Powerpoint': presentation_controller,85 'Powerpoint': presentation_controller,
82 'Powerpoint Viewer': presentation_viewer_controller86 'Powerpoint Viewer': presentation_viewer_controller,
87 'Pdf': pdf_controller
83 }88 }
8489
85 # WHEN: Build the file mask.90 # WHEN: Build the file mask.
@@ -92,3 +97,7 @@
92 'The file mask should contain the odp extension')97 'The file mask should contain the odp extension')
93 self.assertIn('*.ppt', self.media_item.on_new_file_masks,98 self.assertIn('*.ppt', self.media_item.on_new_file_masks,
94 'The file mask should contain the ppt extension')99 'The file mask should contain the ppt extension')
100 self.assertIn('*.pdf', self.media_item.on_new_file_masks,
101 'The file mask should contain the pdf extension')
102 self.assertIn('*.xps', self.media_item.on_new_file_masks,
103 'The file mask should contain the xps extension')
95104
=== added file 'tests/functional/openlp_plugins/presentations/test_pdfcontroller.py'
--- tests/functional/openlp_plugins/presentations/test_pdfcontroller.py 1970-01-01 00:00:00 +0000
+++ tests/functional/openlp_plugins/presentations/test_pdfcontroller.py 2013-12-31 12:36:15 +0000
@@ -0,0 +1,109 @@
1# -*- coding: utf-8 -*-
2# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
3
4###############################################################################
5# OpenLP - Open Source Lyrics Projection #
6# --------------------------------------------------------------------------- #
7# Copyright (c) 2008-2014 Raoul Snyman #
8# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
9# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
10# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
11# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
12# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
13# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
14# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
15# --------------------------------------------------------------------------- #
16# This program is free software; you can redistribute it and/or modify it #
17# under the terms of the GNU General Public License as published by the Free #
18# Software Foundation; version 2 of the License. #
19# #
20# This program is distributed in the hope that it will be useful, but WITHOUT #
21# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
22# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
23# more details. #
24# #
25# You should have received a copy of the GNU General Public License along #
26# with this program; if not, write to the Free Software Foundation, Inc., 59 #
27# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
28###############################################################################
29"""
30This module contains tests for the PdfController
31"""
32import os
33import shutil
34from unittest import TestCase, SkipTest
35from tempfile import mkstemp, mkdtemp
36
37from PyQt4 import QtGui
38
39from openlp.plugins.presentations.lib.pdfcontroller import PdfController, PdfDocument
40from tests.functional import MagicMock
41from openlp.core.common import Settings
42from openlp.core.lib import ScreenList
43from tests.utils.constants import TEST_RESOURCES_PATH
44
45__default_settings__ = {
46 'presentations/enable_pdf_program': False
47}
48
49
50class TestPdfController(TestCase):
51 """
52 Test the PdfController.
53 """
54 def setUp(self):
55 """
56 Set up the components need for all tests.
57 """
58 self.fd, self.ini_file = mkstemp('.ini')
59 Settings().set_filename(self.ini_file)
60 self.application = QtGui.QApplication.instance()
61 ScreenList.create(self.application.desktop())
62 Settings().extend_default_settings(__default_settings__)
63 self.temp_folder = mkdtemp()
64 self.thumbnail_folder = mkdtemp()
65
66 def tearDown(self):
67 """
68 Delete all the C++ objects at the end so that we don't have a segfault
69 """
70 del self.application
71 try:
72 os.unlink(self.ini_file)
73 shutil.rmtree(self.thumbnail_folder)
74 shutil.rmtree(self.temp_folder)
75 except OSError:
76 pass
77
78 def constructor_test(self):
79 """
80 Test the Constructor
81 """
82 # GIVEN: No presentation controller
83 controller = None
84
85 # WHEN: The presentation controller object is created
86 controller = PdfController(plugin=MagicMock())
87
88 # THEN: The name of the presentation controller should be correct
89 self.assertEqual('Pdf', controller.name, 'The name of the presentation controller should be correct')
90
91 def load_pdf_test(self):
92 """
93 Test loading of a Pdf
94 """
95 # GIVEN: A Pdf-file
96 test_file = os.path.join(TEST_RESOURCES_PATH, 'presentations', 'pdf_test1.pdf')
97
98 # WHEN: The Pdf is loaded
99 controller = PdfController(plugin=MagicMock())
100 if not controller.check_available():
101 raise SkipTest('Could not detect mudraw or ghostscript, so skipping PDF test')
102 controller.temp_folder = self.temp_folder
103 controller.thumbnail_folder = self.thumbnail_folder
104 document = PdfDocument(controller, test_file)
105 loaded = document.load_presentation()
106
107 # THEN: The load should succeed and we should be able to get a pagecount
108 self.assertTrue(loaded, 'The loading of the PDF should succeed.')
109 self.assertEqual(3, document.get_slide_count(), 'The pagecount of the PDF should be 3.')
0110
=== added directory 'tests/resources/presentations'
=== added file 'tests/resources/presentations/pdf_test1.pdf'
1Binary files tests/resources/presentations/pdf_test1.pdf 1970-01-01 00:00:00 +0000 and tests/resources/presentations/pdf_test1.pdf 2013-12-31 12:36:15 +0000 differ111Binary files tests/resources/presentations/pdf_test1.pdf 1970-01-01 00:00:00 +0000 and tests/resources/presentations/pdf_test1.pdf 2013-12-31 12:36:15 +0000 differ