Status: | Superseded |
---|---|
Proposed branch: | lp:~tomasgroth/openlp/mupdf |
Merge into: | lp:openlp |
Diff against target: |
868 lines (+632/-38) 10 files modified
openlp/core/lib/serviceitem.py (+5/-1) openlp/core/ui/slidecontroller.py (+2/-2) openlp/plugins/presentations/lib/mediaitem.py (+74/-30) openlp/plugins/presentations/lib/messagelistener.py (+28/-1) openlp/plugins/presentations/lib/pdfcontroller.py (+301/-0) openlp/plugins/presentations/lib/presentationtab.py (+99/-2) 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 (+108/-0) |
To merge this branch: | bzr merge lp:~tomasgroth/openlp/mupdf |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Raoul Snyman | Needs Fixing | ||
Tim Bentley | Pending | ||
Review via email: mp+195291@code.launchpad.net |
This proposal supersedes a proposal from 2013-08-25.
This proposal has been superseded by a proposal from 2013-12-29.
Commit message
Description of the change
Support for presenting PDF using mupdf or ghostscript.
Dmitriy Marmyshev (marmyshev) wrote : Posted in a previous version of this proposal | # |
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.
>u'mainslide001
>
>instead of this:
>
>os.path.
Good catch Dmitriy!
--
Sent from my Android phone with K-9 Mail. Please excuse my brevity.
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.
> instead of this:
> os.path.
I'll change this in the next push.
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.
> > instead of this:
> > os.path.
> I'll change this in the next push.
> --
> https:/
> Your team OpenLP Core is requested to review the proposed merge of
> lp:~tomasgroth/openlp/mupdf into lp:openlp.
>
> _______
> Mailing list: https:/
> Post to : <email address hidden>
> Unsubscribe : https:/
> More help : https:/
>
--
Tim and Alison Bentley
<email address hidden>
Andreas Preikschat (googol-deactivatedaccount) wrote : Posted in a previous version of this proposal | # |
And please add docstrings to *all* methods/functions
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.
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.
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:/
> You are subscribed to branch lp:openlp.
>
--
Dave Warnock: http://
Cycling Blog: http://
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal | # |
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:/
> > You are subscribed to branch lp:openlp.
> >
>
>
>
> --
> Dave Warnock: http://
> Cycling Blog: http://
>
> https:/
> Your team OpenL...
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.
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.
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?
Jonathan Corwin (j-corwin) wrote : Posted in a previous version of this proposal | # |
Push it and get it reviewed :)
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.
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal | # |
That is because you have set the ServiceItem.
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:/
> Your team OpenLP Core is requested to review the proposed merge of
> lp:~tomasgroth/openlp/mupdf into lp:openlp.
>
> _______
> Mailing list: https:/
> Post to : <email address hidden>
> Unsubscribe : https:/
> More help : https:/
>
--
Tim and Alison Bentley
<email address hidden>
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.
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?
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_
* 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?
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.
Tomas Groth (tomasgroth) wrote : | # |
I need some feedback/
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-
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-
Raoul Snyman (raoul-snyman) wrote : | # |
Hi Tomas, in it's current state your merge proposal has some conflicts. Can you merge from trunk and fix those please?
- 2285. By Tomas Groth
-
Merged with trunk to resolve conflicts.
Tomas Groth (tomasgroth) wrote : | # |
Merged with trunk to resolve conflicts. Last set of questions still applies.
- 2286. By Tomas Groth
-
Made presenting PDF from the servicemanager work
- 2287. By Tomas Groth
-
Merged with trunk
- 2288. By Tomas Groth
-
Fixes for the presentation setting tab
- 2289. By Tomas Groth
-
Added tests for the PdfController.
- 2290. By Tomas Groth
-
Merged with trunk.
- 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
1 | === modified file 'openlp/core/lib/serviceitem.py' |
2 | --- openlp/core/lib/serviceitem.py 2013-12-26 08:56:53 +0000 |
3 | +++ openlp/core/lib/serviceitem.py 2013-12-29 19:49:15 +0000 |
4 | @@ -420,7 +420,11 @@ |
5 | self._raw_frames.append(slide) |
6 | elif self.service_item_type == ServiceItemType.Image: |
7 | settings_section = serviceitem['serviceitem']['header']['name'] |
8 | - background = QtGui.QColor(Settings().value(settings_section + '/background color')) |
9 | + background = None |
10 | + try: |
11 | + background = QtGui.QColor(Settings().value(settings_section + '/background color')) |
12 | + except Exception: |
13 | + pass |
14 | if path: |
15 | self.has_original_files = False |
16 | for text_image in serviceitem['serviceitem']['data']: |
17 | |
18 | === modified file 'openlp/core/ui/slidecontroller.py' |
19 | --- openlp/core/ui/slidecontroller.py 2013-12-24 08:56:50 +0000 |
20 | +++ openlp/core/ui/slidecontroller.py 2013-12-29 19:49:15 +0000 |
21 | @@ -438,7 +438,7 @@ |
22 | # "V1" was the slide we wanted to go. |
23 | self.preview_widget.change_slide(self.slideList[self.current_shortcut]) |
24 | self.slide_selected() |
25 | - # Reset the shortcut. |
26 | + # Reset the shortcut. |
27 | self.current_shortcut = '' |
28 | |
29 | def set_live_hotkeys(self, parent=None): |
30 | @@ -733,7 +733,7 @@ |
31 | if old_item and self.is_live and old_item.is_capable(ItemCapabilities.ProvidesOwnDisplay): |
32 | self._reset_blank() |
33 | Registry().execute( |
34 | - '%s_start' % service_item.name.lower(), [service_item, self.is_live, self.hide_mode(), slideno]) |
35 | + '%s_start' % service_item.name.lower(), [self.service_item, self.is_live, self.hide_mode(), slideno]) |
36 | self.slideList = {} |
37 | if self.is_live: |
38 | self.song_menu.menu().clear() |
39 | |
40 | === modified file 'openlp/plugins/presentations/lib/mediaitem.py' |
41 | --- openlp/plugins/presentations/lib/mediaitem.py 2013-12-24 08:56:50 +0000 |
42 | +++ openlp/plugins/presentations/lib/mediaitem.py 2013-12-29 19:49:15 +0000 |
43 | @@ -238,7 +238,7 @@ |
44 | Settings().setValue(self.settings_section + '/presentations files', self.get_file_list()) |
45 | |
46 | def generate_slide_data(self, service_item, item=None, xml_version=False, |
47 | - remote=False, context=ServiceItemContext.Service): |
48 | + remote=False, context=ServiceItemContext.Service, presentation_file=None): |
49 | """ |
50 | Load the relevant information for displaying the presentation in the slidecontroller. In the case of |
51 | powerpoints, an image for each slide. |
52 | @@ -249,45 +249,89 @@ |
53 | items = self.list_view.selectedItems() |
54 | if len(items) > 1: |
55 | return False |
56 | - service_item.processor = self.display_type_combo_box.currentText() |
57 | - service_item.add_capability(ItemCapabilities.ProvidesOwnDisplay) |
58 | + filename = presentation_file |
59 | + if filename is None: |
60 | + filename = items[0].data(QtCore.Qt.UserRole) |
61 | + file_type = os.path.splitext(filename)[1][1:] |
62 | if not self.display_type_combo_box.currentText(): |
63 | return False |
64 | - for bitem in items: |
65 | - filename = bitem.data(QtCore.Qt.UserRole) |
66 | - (path, name) = os.path.split(filename) |
67 | - service_item.title = name |
68 | - if os.path.exists(filename): |
69 | - if service_item.processor == self.automatic: |
70 | - service_item.processor = self.findControllerByType(filename) |
71 | - if not service_item.processor: |
72 | + if (file_type == 'pdf' or file_type == 'xps') and context != ServiceItemContext.Service: |
73 | + service_item.add_capability(ItemCapabilities.CanMaintain) |
74 | + service_item.add_capability(ItemCapabilities.CanPreview) |
75 | + service_item.add_capability(ItemCapabilities.CanLoop) |
76 | + service_item.add_capability(ItemCapabilities.CanAppend) |
77 | + # force a nonexistent theme |
78 | + service_item.theme = -1 |
79 | + for bitem in items: |
80 | + filename = presentation_file |
81 | + if filename is None: |
82 | + filename = bitem.data(QtCore.Qt.UserRole) |
83 | + (path, name) = os.path.split(filename) |
84 | + service_item.title = name |
85 | + if os.path.exists(filename): |
86 | + processor = self.findControllerByType(filename) |
87 | + if not processor: |
88 | return False |
89 | - controller = self.controllers[service_item.processor] |
90 | - doc = controller.add_document(filename) |
91 | - if doc.get_thumbnail_path(1, True) is None: |
92 | - doc.load_presentation() |
93 | - i = 1 |
94 | - img = doc.get_thumbnail_path(i, True) |
95 | - if img: |
96 | - while img: |
97 | - service_item.add_from_command(path, name, img) |
98 | + controller = self.controllers[processor] |
99 | + service_item.processor = None |
100 | + doc = controller.add_document(filename) |
101 | + if doc.get_thumbnail_path(1, True) is None or not os.path.isfile( |
102 | + os.path.join(doc.get_temp_folder(), 'mainslide001.png')): |
103 | + doc.load_presentation() |
104 | + i = 1 |
105 | + imagefile = 'mainslide%03d.png' % i |
106 | + image = os.path.join(doc.get_temp_folder(), imagefile) |
107 | + while os.path.isfile(image): |
108 | + service_item.add_from_image(image, name) |
109 | i += 1 |
110 | - img = doc.get_thumbnail_path(i, True) |
111 | + imagefile = 'mainslide%03d.png' % i |
112 | + image = os.path.join(doc.get_temp_folder(), imagefile) |
113 | doc.close_presentation() |
114 | return True |
115 | else: |
116 | # File is no longer present |
117 | if not remote: |
118 | critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'), |
119 | - translate('PresentationPlugin.MediaItem', |
120 | - 'The presentation %s is incomplete, please reload.') % filename) |
121 | - return False |
122 | - else: |
123 | - # File is no longer present |
124 | - if not remote: |
125 | - critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'), |
126 | - translate('PresentationPlugin.MediaItem', 'The presentation %s no longer exists.') % filename) |
127 | - return False |
128 | + translate('PresentationPlugin.MediaItem', 'The presentation %s no longer exists.') % filename) |
129 | + return False |
130 | + else: |
131 | + service_item.processor = self.display_type_combo_box.currentText() |
132 | + service_item.add_capability(ItemCapabilities.ProvidesOwnDisplay) |
133 | + for bitem in items: |
134 | + filename = bitem.data(QtCore.Qt.UserRole) |
135 | + (path, name) = os.path.split(filename) |
136 | + service_item.title = name |
137 | + if os.path.exists(filename): |
138 | + if service_item.processor == self.automatic: |
139 | + service_item.processor = self.findControllerByType(filename) |
140 | + if not service_item.processor: |
141 | + return False |
142 | + controller = self.controllers[service_item.processor] |
143 | + doc = controller.add_document(filename) |
144 | + if doc.get_thumbnail_path(1, True) is None: |
145 | + doc.load_presentation() |
146 | + i = 1 |
147 | + img = doc.get_thumbnail_path(i, True) |
148 | + if img: |
149 | + while img: |
150 | + service_item.add_from_command(path, name, img) |
151 | + i += 1 |
152 | + img = doc.get_thumbnail_path(i, True) |
153 | + doc.close_presentation() |
154 | + return True |
155 | + else: |
156 | + # File is no longer present |
157 | + if not remote: |
158 | + critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'), |
159 | + translate('PresentationPlugin.MediaItem', |
160 | + 'The presentation %s is incomplete, please reload.') % filename) |
161 | + return False |
162 | + else: |
163 | + # File is no longer present |
164 | + if not remote: |
165 | + critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'), |
166 | + translate('PresentationPlugin.MediaItem', 'The presentation %s no longer exists.') % filename) |
167 | + return False |
168 | |
169 | def findControllerByType(self, filename): |
170 | """ |
171 | |
172 | === modified file 'openlp/plugins/presentations/lib/messagelistener.py' |
173 | --- openlp/plugins/presentations/lib/messagelistener.py 2013-12-24 08:56:50 +0000 |
174 | +++ openlp/plugins/presentations/lib/messagelistener.py 2013-12-29 19:49:15 +0000 |
175 | @@ -33,6 +33,7 @@ |
176 | |
177 | from openlp.core.lib import Registry |
178 | from openlp.core.ui import HideMode |
179 | +from openlp.core.lib import ServiceItemContext, ServiceItem |
180 | |
181 | log = logging.getLogger(__name__) |
182 | |
183 | @@ -69,6 +70,7 @@ |
184 | return |
185 | self.doc.slidenumber = slide_no |
186 | self.hide_mode = hide_mode |
187 | + log.debug('add_handler, slidenumber: %d' % slide_no) |
188 | if self.is_live: |
189 | if hide_mode == HideMode.Screen: |
190 | Registry().execute('live_display_hide', HideMode.Screen) |
191 | @@ -316,6 +318,26 @@ |
192 | hide_mode = message[2] |
193 | file = item.get_frame_path() |
194 | self.handler = item.processor |
195 | + # When starting presentation from the servicemanager/slidecontroller we convert |
196 | + # PDF/XPS-serviceitems into image-serviceitems. When started from the mediamanager |
197 | + # the conversion has already been done. |
198 | + if file.endswith('.pdf') or file.endswith('.xps'): |
199 | + log.debug('Converting from pdf/xps to images for serviceitem with file %s', file) |
200 | + # Create a new image-serviceitem which will overwrite the old one |
201 | + new_item = ServiceItem() |
202 | + new_item.name = 'images' |
203 | + if is_live: |
204 | + self.media_item.generate_slide_data(new_item, item, False, False, ServiceItemContext.Live, file) |
205 | + else: |
206 | + self.media_item.generate_slide_data(new_item, item, False, False, ServiceItemContext.Preview, file) |
207 | + # We need to overwrite the current serviceitem to make the slidecontroller |
208 | + # present the images, so we do some copying |
209 | + service_repr = {'serviceitem': new_item.get_service_repr(True) } |
210 | + item.set_from_service(service_repr) |
211 | + item._raw_frames = new_item._raw_frames |
212 | + # When presenting PDF or XPS, we are using the image presentation code, |
213 | + # so handler & processor is set to None, and we skip adding the handler. |
214 | + self.handler = None |
215 | if self.handler == self.media_item.automatic: |
216 | self.handler = self.media_item.findControllerByType(file) |
217 | if not self.handler: |
218 | @@ -324,7 +346,12 @@ |
219 | controller = self.live_handler |
220 | else: |
221 | controller = self.preview_handler |
222 | - controller.add_handler(self.controllers[self.handler], file, hide_mode, message[3]) |
223 | + # When presenting PDF or XPS, we are using the image presentation code, |
224 | + # so handler & processor is set to None, and we skip adding the handler. |
225 | + if self.handler == None: |
226 | + self.controller = controller |
227 | + else: |
228 | + controller.add_handler(self.controllers[self.handler], file, hide_mode, message[3]) |
229 | |
230 | def slide(self, message): |
231 | """ |
232 | |
233 | === added file 'openlp/plugins/presentations/lib/pdfcontroller.py' |
234 | --- openlp/plugins/presentations/lib/pdfcontroller.py 1970-01-01 00:00:00 +0000 |
235 | +++ openlp/plugins/presentations/lib/pdfcontroller.py 2013-12-29 19:49:15 +0000 |
236 | @@ -0,0 +1,301 @@ |
237 | +# -*- coding: utf-8 -*- |
238 | +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 |
239 | + |
240 | +############################################################################### |
241 | +# OpenLP - Open Source Lyrics Projection # |
242 | +# --------------------------------------------------------------------------- # |
243 | +# Copyright (c) 2008-2014 Raoul Snyman # |
244 | +# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan # |
245 | +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # |
246 | +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # |
247 | +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # |
248 | +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # |
249 | +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # |
250 | +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # |
251 | +# --------------------------------------------------------------------------- # |
252 | +# This program is free software; you can redistribute it and/or modify it # |
253 | +# under the terms of the GNU General Public License as published by the Free # |
254 | +# Software Foundation; version 2 of the License. # |
255 | +# # |
256 | +# This program is distributed in the hope that it will be useful, but WITHOUT # |
257 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # |
258 | +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # |
259 | +# more details. # |
260 | +# # |
261 | +# You should have received a copy of the GNU General Public License along # |
262 | +# with this program; if not, write to the Free Software Foundation, Inc., 59 # |
263 | +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # |
264 | +############################################################################### |
265 | + |
266 | +import os |
267 | +import logging |
268 | +from tempfile import NamedTemporaryFile |
269 | +import re |
270 | +from subprocess import check_output, CalledProcessError, STDOUT |
271 | + |
272 | +from openlp.core.utils import AppLocation |
273 | +from openlp.core.common import Settings |
274 | +from openlp.core.lib import ScreenList |
275 | +from .presentationcontroller import PresentationController, PresentationDocument |
276 | + |
277 | + |
278 | +log = logging.getLogger(__name__) |
279 | + |
280 | +class PdfController(PresentationController): |
281 | + """ |
282 | + Class to control PDF presentations |
283 | + """ |
284 | + log.info('PdfController loaded') |
285 | + |
286 | + def __init__(self, plugin): |
287 | + """ |
288 | + Initialise the class |
289 | + """ |
290 | + log.debug('Initialising') |
291 | + self.process = None |
292 | + PresentationController.__init__(self, plugin, 'Pdf', PdfDocument) |
293 | + self.supports = ['pdf'] |
294 | + self.mudrawbin = '' |
295 | + self.gsbin = '' |
296 | + if self.check_installed() and self.mudrawbin: |
297 | + self.also_supports = ['xps'] |
298 | + |
299 | + @staticmethod |
300 | + def check_binary(program_path): |
301 | + """ |
302 | + Function that checks whether a binary is either ghostscript or mudraw or neither. |
303 | + Is also used from presentationtab.py |
304 | + """ |
305 | + program_type = None |
306 | + runlog = '' |
307 | + log.debug('testing program_path: %s', program_path) |
308 | + try: |
309 | + runlog = check_output([program_path, '--help'], stderr=STDOUT) |
310 | + except CalledProcessError as e: |
311 | + runlog = e.output |
312 | + except Exception: |
313 | + runlog = '' |
314 | + # Analyse the output to see it the program is mudraw, ghostscript or neither |
315 | + for line in runlog.splitlines(): |
316 | + decoded_line = line.decode() |
317 | + found_mudraw = re.search('usage: mudraw.*', decoded_line) |
318 | + if found_mudraw: |
319 | + program_type = 'mudraw' |
320 | + break |
321 | + found_gs = re.search('GPL Ghostscript.*', decoded_line) |
322 | + if found_gs: |
323 | + program_type = 'gs' |
324 | + break |
325 | + log.debug('in check_binary, found: %s', program_type) |
326 | + return program_type |
327 | + |
328 | + def check_available(self): |
329 | + """ |
330 | + PdfController is able to run on this machine. |
331 | + """ |
332 | + log.debug('check_available Pdf') |
333 | + return self.check_installed() |
334 | + |
335 | + def check_installed(self): |
336 | + """ |
337 | + Check the viewer is installed. |
338 | + """ |
339 | + log.debug('check_installed Pdf') |
340 | + # Use the user defined program if given |
341 | + if (Settings().value('presentations/enable_pdf_program')): |
342 | + pdf_program = Settings().value('presentations/pdf_program') |
343 | + program_type = self.check_binary(pdf_program) |
344 | + if program_type == 'gs': |
345 | + self.gsbin = pdf_program |
346 | + return True |
347 | + elif program_type == 'mudraw': |
348 | + self.mudrawbin = pdf_program |
349 | + return True |
350 | + # Fallback to autodetection |
351 | + application_path = AppLocation.get_directory(AppLocation.AppDir) |
352 | + if os.name == 'nt': |
353 | + # for windows we only accept mudraw.exe in the base folder |
354 | + application_path = AppLocation.get_directory(AppLocation.AppDir) |
355 | + if os.path.isfile(application_path + '/../mudraw.exe'): |
356 | + self.mudrawbin = application_path + '/../mudraw.exe' |
357 | + else: |
358 | + # First try to find mupdf |
359 | + try: |
360 | + self.mudrawbin = check_output(['which', 'mudraw']).decode(encoding='UTF-8').rstrip('\n') |
361 | + except CalledProcessError: |
362 | + self.mudrawbin = '' |
363 | + # if mupdf isn't installed, fallback to ghostscript |
364 | + if not self.mudrawbin: |
365 | + try: |
366 | + self.gsbin = check_output(['which', 'gs']).rstrip('\n') |
367 | + except CalledProcessError: |
368 | + self.gsbin = '' |
369 | + # Last option: check if mudraw is placed in OpenLP base folder |
370 | + if not self.mudrawbin and not self.gsbin: |
371 | + application_path = AppLocation.get_directory(AppLocation.AppDir) |
372 | + if os.path.isfile(application_path + '/../mudraw'): |
373 | + self.mudrawbin = application_path + '/../mudraw' |
374 | + if not self.mudrawbin and not self.gsbin: |
375 | + return False |
376 | + else: |
377 | + return True |
378 | + |
379 | + def kill(self): |
380 | + """ |
381 | + Called at system exit to clean up any running presentations |
382 | + """ |
383 | + log.debug('Kill pdfviewer') |
384 | + while self.docs: |
385 | + self.docs[0].close_presentation() |
386 | + |
387 | + |
388 | +class PdfDocument(PresentationDocument): |
389 | + """ |
390 | + Class which holds information of a single presentation. |
391 | + This class is not actually used to present the PDF, instead we convert to |
392 | + image-serviceitem on the fly and present as such. Therefore some of the 'playback' |
393 | + functions is not implemented. |
394 | + """ |
395 | + def __init__(self, controller, presentation): |
396 | + """ |
397 | + Constructor, store information about the file and initialise. |
398 | + """ |
399 | + log.debug('Init Presentation Pdf') |
400 | + PresentationDocument.__init__(self, controller, presentation) |
401 | + self.presentation = None |
402 | + self.blanked = False |
403 | + self.hidden = False |
404 | + self.image_files = [] |
405 | + self.num_pages = -1 |
406 | + |
407 | + def gs_get_resolution(self, size): |
408 | + """ |
409 | + Only used when using ghostscript |
410 | + Ghostscript can't scale automaticly while keeping aspect like mupdf, so we need |
411 | + to get the ratio bewteen the screen size and the PDF to scale |
412 | + """ |
413 | + # Use a postscript script to get size of the pdf. It is assumed that all pages have same size |
414 | + postscript = '%!PS \n\ |
415 | +() = \n\ |
416 | +File dup (r) file runpdfbegin \n\ |
417 | +1 pdfgetpage dup \n\ |
418 | +/MediaBox pget { \n\ |
419 | +aload pop exch 4 1 roll exch sub 3 1 roll sub \n\ |
420 | +( Size: x: ) print =print (, y: ) print =print (\n) print \n\ |
421 | +} if \n\ |
422 | +flush \n\ |
423 | +quit \n\ |
424 | +' |
425 | + # Put postscript into tempfile |
426 | + tmpfile = NamedTemporaryFile(delete=False) |
427 | + tmpfile.write(postscript) |
428 | + tmpfile.close() |
429 | + # Run the script on the pdf to get the size |
430 | + runlog = [] |
431 | + try: |
432 | + runlog = check_output([self.controller.gsbin, '-dNOPAUSE', '-dNODISPLAY', '-dBATCH', '-sFile=' + self.filepath, tmpfile.name]) |
433 | + except CalledProcessError as e: |
434 | + log.debug(' '.join(e.cmd)) |
435 | + log.debug(e.output) |
436 | + os.unlink(tmpfile.name) |
437 | + # Extract the pdf resolution from output, the format is " Size: x: <width>, y: <height>" |
438 | + width = 0 |
439 | + height = 0 |
440 | + for line in runlog.splitlines(): |
441 | + try: |
442 | + width = re.search('.*Size: x: (\d+\.?\d*), y: \d+.*', line).group(1) |
443 | + height = re.search('.*Size: x: \d+\.?\d*, y: (\d+\.?\d*).*', line).group(1) |
444 | + break; |
445 | + except AttributeError: |
446 | + pass |
447 | + # Calculate the ratio from pdf to screen |
448 | + if width > 0 and height > 0: |
449 | + width_ratio = size.right() / float(width) |
450 | + height_ratio = size.bottom() / float(height) |
451 | + # return the resolution that should be used. 72 is default. |
452 | + if width_ratio > height_ratio: |
453 | + return int(height_ratio * 72) |
454 | + else: |
455 | + return int(width_ratio * 72) |
456 | + else: |
457 | + return 72 |
458 | + |
459 | + def load_presentation(self): |
460 | + """ |
461 | + Called when a presentation is added to the SlideController. It generates images from the PDF. |
462 | + """ |
463 | + log.debug('load_presentation pdf') |
464 | + # Check if the images has already been created, and if yes load them |
465 | + if os.path.isfile(os.path.join(self.get_temp_folder(), 'mainslide001.png')): |
466 | + created_files = sorted(os.listdir(self.get_temp_folder())) |
467 | + for fn in created_files: |
468 | + if os.path.isfile(os.path.join(self.get_temp_folder(), fn)): |
469 | + self.image_files.append(os.path.join(self.get_temp_folder(), fn)) |
470 | + self.num_pages = len(self.image_files) |
471 | + return True |
472 | + size = ScreenList().current['size'] |
473 | + # Generate images from PDF that will fit the frame. |
474 | + runlog = '' |
475 | + try: |
476 | + if not os.path.isdir(self.get_temp_folder()): |
477 | + os.makedirs(self.get_temp_folder()) |
478 | + if self.controller.mudrawbin: |
479 | + runlog = check_output([self.controller.mudrawbin, '-w', str(size.right()), '-h', str(size.bottom()), '-o', os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.filepath]) |
480 | + elif self.controller.gsbin: |
481 | + resolution = self.gs_get_resolution(size) |
482 | + runlog = check_output([self.controller.gsbin, '-dSAFER', '-dNOPAUSE', '-dBATCH', '-sDEVICE=png16m', '-r' + str(resolution), '-dTextAlphaBits=4', '-dGraphicsAlphaBits=4', '-sOutputFile=' + os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.filepath]) |
483 | + created_files = sorted(os.listdir(self.get_temp_folder())) |
484 | + for fn in created_files: |
485 | + if os.path.isfile(os.path.join(self.get_temp_folder(), fn)): |
486 | + self.image_files.append(os.path.join(self.get_temp_folder(), fn)) |
487 | + except Exception as e: |
488 | + log.debug(e) |
489 | + log.debug(runlog) |
490 | + return False |
491 | + self.num_pages = len(self.image_files) |
492 | + # Create thumbnails |
493 | + self.create_thumbnails() |
494 | + return True |
495 | + |
496 | + def create_thumbnails(self): |
497 | + """ |
498 | + Generates thumbnails |
499 | + """ |
500 | + log.debug('create_thumbnails pdf') |
501 | + if self.check_thumbnails(): |
502 | + return |
503 | + # use builtin function to create thumbnails from generated images |
504 | + index = 1 |
505 | + for image in self.image_files: |
506 | + self.convert_thumbnail(image, index) |
507 | + index += 1 |
508 | + |
509 | + def close_presentation(self): |
510 | + """ |
511 | + Close presentation and clean up objects. Triggered by new object being added to SlideController or OpenLP being |
512 | + shut down. |
513 | + """ |
514 | + log.debug('close_presentation pdf') |
515 | + self.controller.remove_doc(self) |
516 | + |
517 | + def is_loaded(self): |
518 | + """ |
519 | + Returns true if a presentation is loaded. |
520 | + """ |
521 | + log.debug('is_loaded pdf') |
522 | + if self.num_pages < 0: |
523 | + return False |
524 | + return True |
525 | + |
526 | + def is_active(self): |
527 | + """ |
528 | + Returns true if a presentation is currently active. |
529 | + """ |
530 | + log.debug('is_active pdf') |
531 | + return self.is_loaded() and not self.hidden |
532 | + |
533 | + def get_slide_count(self): |
534 | + """ |
535 | + Returns total number of slides |
536 | + """ |
537 | + return self.num_pages |
538 | \ No newline at end of file |
539 | |
540 | === modified file 'openlp/plugins/presentations/lib/presentationtab.py' |
541 | --- openlp/plugins/presentations/lib/presentationtab.py 2013-12-24 08:56:50 +0000 |
542 | +++ openlp/plugins/presentations/lib/presentationtab.py 2013-12-29 19:49:15 +0000 |
543 | @@ -30,8 +30,9 @@ |
544 | from PyQt4 import QtGui |
545 | |
546 | from openlp.core.common import Settings, UiStrings, translate |
547 | -from openlp.core.lib import SettingsTab |
548 | - |
549 | +from openlp.core.lib import SettingsTab, build_icon |
550 | +from openlp.core.lib.ui import critical_error_message_box |
551 | +from .pdfcontroller import PdfController |
552 | |
553 | class PresentationTab(SettingsTab): |
554 | """ |
555 | @@ -64,6 +65,7 @@ |
556 | self.presenter_check_boxes[controller.name] = checkbox |
557 | self.controllers_layout.addWidget(checkbox) |
558 | self.left_layout.addWidget(self.controllers_group_box) |
559 | + # Advanced |
560 | self.advanced_group_box = QtGui.QGroupBox(self.left_column) |
561 | self.advanced_group_box.setObjectName('advanced_group_box') |
562 | self.advanced_layout = QtGui.QVBoxLayout(self.advanced_group_box) |
563 | @@ -72,8 +74,35 @@ |
564 | self.override_app_check_box.setObjectName('override_app_check_box') |
565 | self.advanced_layout.addWidget(self.override_app_check_box) |
566 | self.left_layout.addWidget(self.advanced_group_box) |
567 | + # Pdf options |
568 | + self.pdf_group_box = QtGui.QGroupBox(self.left_column) |
569 | + self.pdf_group_box.setObjectName('pdf_group_box') |
570 | + self.pdf_layout = QtGui.QFormLayout(self.pdf_group_box) |
571 | + self.pdf_layout.setObjectName('pdf_layout') |
572 | + self.pdf_program_check_box = QtGui.QCheckBox(self.pdf_group_box) |
573 | + self.pdf_program_check_box.setObjectName('pdf_program_check_box') |
574 | + self.pdf_layout.addRow(self.pdf_program_check_box) |
575 | + self.pdf_program_path_layout = QtGui.QHBoxLayout() |
576 | + self.pdf_program_path_layout.setObjectName('pdf_program_path_layout') |
577 | + self.pdf_program_path = QtGui.QLineEdit(self.pdf_group_box) |
578 | + self.pdf_program_path.setObjectName('pdf_program_path') |
579 | + self.pdf_program_path.setReadOnly(True) |
580 | + self.pdf_program_path.setPalette(self.get_grey_text_palette(True)) |
581 | + self.pdf_program_path_layout.addWidget(self.pdf_program_path) |
582 | + self.pdf_program_browse_button = QtGui.QToolButton(self.pdf_group_box) |
583 | + self.pdf_program_browse_button.setObjectName('pdf_program_browse_button') |
584 | + self.pdf_program_browse_button.setIcon(build_icon(':/general/general_open.png')) |
585 | + self.pdf_program_browse_button.setEnabled(False) |
586 | + self.pdf_program_path_layout.addWidget(self.pdf_program_browse_button) |
587 | + self.pdf_layout.addRow(self.pdf_program_path_layout) |
588 | + self.left_layout.addWidget(self.pdf_group_box) |
589 | self.left_layout.addStretch() |
590 | + self.right_column.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred) |
591 | self.right_layout.addStretch() |
592 | + # Signals and slots |
593 | + self.pdf_program_path.editingFinished.connect(self.on_pdf_program_path_edit_finished) |
594 | + self.pdf_program_browse_button.clicked.connect(self.on_pdf_program_browse_button_clicked) |
595 | + self.pdf_program_check_box.clicked.connect(self.on_pdf_program_check_box_clicked) |
596 | |
597 | def retranslateUi(self): |
598 | """ |
599 | @@ -85,8 +114,12 @@ |
600 | checkbox = self.presenter_check_boxes[controller.name] |
601 | self.set_controller_text(checkbox, controller) |
602 | self.advanced_group_box.setTitle(UiStrings().Advanced) |
603 | + self.pdf_group_box.setTitle(translate('PresentationPlugin.PresentationTab', 'PDF options')) |
604 | self.override_app_check_box.setText( |
605 | translate('PresentationPlugin.PresentationTab', 'Allow presentation application to be overridden')) |
606 | + self.pdf_program_check_box.setText( |
607 | + translate('PresentationPlugin.PresentationTab', 'Use given full path for mudraw or ghostscript binary:')) |
608 | + |
609 | |
610 | def set_controller_text(self, checkbox, controller): |
611 | if checkbox.isEnabled(): |
612 | @@ -103,6 +136,15 @@ |
613 | checkbox = self.presenter_check_boxes[controller.name] |
614 | checkbox.setChecked(Settings().value(self.settings_section + '/' + controller.name)) |
615 | self.override_app_check_box.setChecked(Settings().value(self.settings_section + '/override app')) |
616 | + # load pdf-program settings |
617 | + enable_pdf_program = Settings().value(self.settings_section + '/enable_pdf_program') |
618 | + self.pdf_program_check_box.setChecked(enable_pdf_program) |
619 | + self.pdf_program_path.setReadOnly(not enable_pdf_program) |
620 | + self.pdf_program_path.setPalette(self.get_grey_text_palette(not enable_pdf_program)) |
621 | + self.pdf_program_browse_button.setEnabled(enable_pdf_program) |
622 | + pdf_program = Settings().value(self.settings_section + '/pdf_program') |
623 | + if pdf_program: |
624 | + self.pdf_program_path.setText(pdf_program) |
625 | |
626 | def save(self): |
627 | """ |
628 | @@ -128,6 +170,20 @@ |
629 | if Settings().value(setting_key) != self.override_app_check_box.checkState(): |
630 | Settings().setValue(setting_key, self.override_app_check_box.checkState()) |
631 | changed = True |
632 | + |
633 | + # Save pdf-settings |
634 | + pdf_program = self.pdf_program_path.text() |
635 | + enable_pdf_program = self.pdf_program_check_box.checkState() |
636 | + # If the given program is blank disable using the program |
637 | + if pdf_program == '': |
638 | + enable_pdf_program = 0 |
639 | + if pdf_program != Settings().value(self.settings_section + '/pdf_program'): |
640 | + Settings().setValue(self.settings_section + '/pdf_program', pdf_program) |
641 | + changed = True |
642 | + if enable_pdf_program != Settings().value(self.settings_section + '/enable_pdf_program'): |
643 | + Settings().setValue(self.settings_section + '/enable_pdf_program', enable_pdf_program) |
644 | + changed = True |
645 | + |
646 | if changed: |
647 | self.settings_form.register_post_process('mediaitem_suffix_reset') |
648 | self.settings_form.register_post_process('mediaitem_presentation_rebuild') |
649 | @@ -143,3 +199,44 @@ |
650 | checkbox = self.presenter_check_boxes[controller.name] |
651 | checkbox.setEnabled(controller.is_available()) |
652 | self.set_controller_text(checkbox, controller) |
653 | + |
654 | + def on_pdf_program_path_edit_finished(self): |
655 | + """ |
656 | + After selecting/typing in a program it is validated that it is a actually ghostscript or mudraw |
657 | + """ |
658 | + program_type = None |
659 | + if self.pdf_program_path.text() != '': |
660 | + program_type = PdfController.check_binary(self.pdf_program_path.text()) |
661 | + if not program_type: |
662 | + critical_error_message_box(UiStrings().Error, |
663 | + translate('PresentationPlugin.PresentationTab', 'The program is not ghostscript or mudraw which is required.')) |
664 | + self.pdf_program_path.setFocus() |
665 | + |
666 | + def on_pdf_program_browse_button_clicked(self): |
667 | + """ |
668 | + Select the mudraw or ghostscript binary that should be used. |
669 | + """ |
670 | + filename = QtGui.QFileDialog.getOpenFileName(self, translate('PresentationPlugin.PresentationTab', 'Select mudraw or ghostscript binary.')) |
671 | + if filename: |
672 | + self.pdf_program_path.setText(filename) |
673 | + self.pdf_program_path.setFocus() |
674 | + |
675 | + def on_pdf_program_check_box_clicked(self, checked): |
676 | + """ |
677 | + When checkbox for manual entering pdf-program is clicked, |
678 | + enable or disable the textbox for the programpath and the browse-button. |
679 | + """ |
680 | + self.pdf_program_path.setReadOnly(not checked) |
681 | + self.pdf_program_path.setPalette(self.get_grey_text_palette(not checked)) |
682 | + self.pdf_program_browse_button.setEnabled(checked) |
683 | + |
684 | + def get_grey_text_palette(self, greyed): |
685 | + """ |
686 | + Returns a QPalette with greyed out text as used for placeholderText. |
687 | + """ |
688 | + palette = QtGui.QPalette() |
689 | + color = self.palette().color(QtGui.QPalette.Active, QtGui.QPalette.Text) |
690 | + if greyed: |
691 | + color.setAlpha(128) |
692 | + palette.setColor(QtGui.QPalette.Active, QtGui.QPalette.Text, color) |
693 | + return palette |
694 | |
695 | === modified file 'openlp/plugins/presentations/presentationplugin.py' |
696 | --- openlp/plugins/presentations/presentationplugin.py 2013-12-24 08:56:50 +0000 |
697 | +++ openlp/plugins/presentations/presentationplugin.py 2013-12-29 19:49:15 +0000 |
698 | @@ -45,9 +45,12 @@ |
699 | |
700 | __default_settings__ = { |
701 | 'presentations/override app': QtCore.Qt.Unchecked, |
702 | + 'presentations/enable_pdf_program': QtCore.Qt.Unchecked, |
703 | + 'presentations/pdf_program': '', |
704 | 'presentations/Impress': QtCore.Qt.Checked, |
705 | 'presentations/Powerpoint': QtCore.Qt.Checked, |
706 | 'presentations/Powerpoint Viewer': QtCore.Qt.Checked, |
707 | + 'presentations/Pdf': QtCore.Qt.Checked, |
708 | 'presentations/presentations files': [] |
709 | } |
710 | |
711 | |
712 | === modified file 'resources/pyinstaller/hook-openlp.plugins.presentations.presentationplugin.py' |
713 | --- resources/pyinstaller/hook-openlp.plugins.presentations.presentationplugin.py 2013-12-24 08:56:50 +0000 |
714 | +++ resources/pyinstaller/hook-openlp.plugins.presentations.presentationplugin.py 2013-12-29 19:49:15 +0000 |
715 | @@ -29,4 +29,5 @@ |
716 | |
717 | hiddenimports = ['openlp.plugins.presentations.lib.impresscontroller', |
718 | 'openlp.plugins.presentations.lib.powerpointcontroller', |
719 | - 'openlp.plugins.presentations.lib.pptviewcontroller'] |
720 | + 'openlp.plugins.presentations.lib.pptviewcontroller', |
721 | + 'openlp.plugins.presentations.lib.pdfcontroller'] |
722 | |
723 | === modified file 'tests/functional/openlp_plugins/presentations/test_mediaitem.py' |
724 | --- tests/functional/openlp_plugins/presentations/test_mediaitem.py 2013-12-24 08:56:50 +0000 |
725 | +++ tests/functional/openlp_plugins/presentations/test_mediaitem.py 2013-12-29 19:49:15 +0000 |
726 | @@ -75,11 +75,16 @@ |
727 | presentation_controller.also_supports = [] |
728 | presentation_viewer_controller = MagicMock() |
729 | presentation_viewer_controller.enabled.return_value = False |
730 | + pdf_controller = MagicMock() |
731 | + pdf_controller.enabled.return_value = True |
732 | + pdf_controller.supports = ['pdf'] |
733 | + pdf_controller.also_supports = ['xps'] |
734 | # Mock the controllers. |
735 | self.media_item.controllers = { |
736 | 'Impress': impress_controller, |
737 | 'Powerpoint': presentation_controller, |
738 | - 'Powerpoint Viewer': presentation_viewer_controller |
739 | + 'Powerpoint Viewer': presentation_viewer_controller, |
740 | + 'Pdf': pdf_controller |
741 | } |
742 | |
743 | # WHEN: Build the file mask. |
744 | @@ -92,3 +97,7 @@ |
745 | 'The file mask should contain the odp extension') |
746 | self.assertIn('*.ppt', self.media_item.on_new_file_masks, |
747 | 'The file mask should contain the ppt extension') |
748 | + self.assertIn('*.pdf', self.media_item.on_new_file_masks, |
749 | + 'The file mask should contain the pdf extension') |
750 | + self.assertIn('*.xps', self.media_item.on_new_file_masks, |
751 | + 'The file mask should contain the xps extension') |
752 | |
753 | === added file 'tests/functional/openlp_plugins/presentations/test_pdfcontroller.py' |
754 | --- tests/functional/openlp_plugins/presentations/test_pdfcontroller.py 1970-01-01 00:00:00 +0000 |
755 | +++ tests/functional/openlp_plugins/presentations/test_pdfcontroller.py 2013-12-29 19:49:15 +0000 |
756 | @@ -0,0 +1,108 @@ |
757 | +# -*- coding: utf-8 -*- |
758 | +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 |
759 | + |
760 | +############################################################################### |
761 | +# OpenLP - Open Source Lyrics Projection # |
762 | +# --------------------------------------------------------------------------- # |
763 | +# Copyright (c) 2008-2014 Raoul Snyman # |
764 | +# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan # |
765 | +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # |
766 | +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # |
767 | +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # |
768 | +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # |
769 | +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # |
770 | +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # |
771 | +# --------------------------------------------------------------------------- # |
772 | +# This program is free software; you can redistribute it and/or modify it # |
773 | +# under the terms of the GNU General Public License as published by the Free # |
774 | +# Software Foundation; version 2 of the License. # |
775 | +# # |
776 | +# This program is distributed in the hope that it will be useful, but WITHOUT # |
777 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # |
778 | +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # |
779 | +# more details. # |
780 | +# # |
781 | +# You should have received a copy of the GNU General Public License along # |
782 | +# with this program; if not, write to the Free Software Foundation, Inc., 59 # |
783 | +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # |
784 | +############################################################################### |
785 | +""" |
786 | +This module contains tests for the PdfController |
787 | +""" |
788 | +import os |
789 | +import shutil |
790 | +from unittest import TestCase, SkipTest |
791 | +from tempfile import mkstemp, mkdtemp |
792 | + |
793 | +from PyQt4 import QtGui |
794 | + |
795 | +from openlp.plugins.presentations.lib.pdfcontroller import PdfController, PdfDocument |
796 | +from tests.functional import MagicMock |
797 | +from openlp.core.common import Settings |
798 | +from openlp.core.lib import ScreenList |
799 | +from tests.utils.constants import TEST_RESOURCES_PATH |
800 | + |
801 | +__default_settings__ = { |
802 | + 'presentations/enable_pdf_program': False |
803 | +} |
804 | + |
805 | +class TestPdfController(TestCase): |
806 | + """ |
807 | + Test the PdfController. |
808 | + """ |
809 | + def setUp(self): |
810 | + """ |
811 | + Set up the components need for all tests. |
812 | + """ |
813 | + self.fd, self.ini_file = mkstemp('.ini') |
814 | + Settings().set_filename(self.ini_file) |
815 | + self.application = QtGui.QApplication.instance() |
816 | + ScreenList.create(self.application.desktop()) |
817 | + Settings().extend_default_settings(__default_settings__) |
818 | + self.temp_folder = mkdtemp() |
819 | + self.thumbnail_folder = mkdtemp() |
820 | + |
821 | + def tearDown(self): |
822 | + """ |
823 | + Delete all the C++ objects at the end so that we don't have a segfault |
824 | + """ |
825 | + del self.application |
826 | + try: |
827 | + os.unlink(self.ini_file) |
828 | + shutil.rmtree(self.thumbnail_folder) |
829 | + shutil.rmtree(self.temp_folder) |
830 | + except OSError: |
831 | + pass |
832 | + |
833 | + def constructor_test(self): |
834 | + """ |
835 | + Test the Constructor |
836 | + """ |
837 | + # GIVEN: No presentation controller |
838 | + controller = None |
839 | + |
840 | + # WHEN: The presentation controller object is created |
841 | + controller = PdfController(plugin = MagicMock()) |
842 | + |
843 | + # THEN: The name of the presentation controller should be correct |
844 | + self.assertEqual('Pdf', controller.name, 'The name of the presentation controller should be correct') |
845 | + |
846 | + def load_pdf_test(self): |
847 | + """ |
848 | + Test loading of a Pdf |
849 | + """ |
850 | + # GIVEN: A Pdf-file |
851 | + test_file = os.path.join(TEST_RESOURCES_PATH, 'presentations', 'pdf_test1.pdf') |
852 | + |
853 | + # WHEN: The Pdf is loaded |
854 | + controller = PdfController(plugin = MagicMock()) |
855 | + if not controller.check_available(): |
856 | + raise SkipTest('Could not detect mudraw or ghostscript, so skipping PDF test') |
857 | + controller.temp_folder = self.temp_folder |
858 | + controller.thumbnail_folder = self.thumbnail_folder |
859 | + document = PdfDocument(controller, test_file) |
860 | + loaded = document.load_presentation() |
861 | + |
862 | + # THEN: The load should succeed and we should be able to get a pagecount |
863 | + self.assertTrue(loaded, 'The loading of the PDF should succeed.') |
864 | + self.assertEqual(3, document.get_slide_count(), 'The pagecount of the PDF should be 3.') |
865 | |
866 | === added directory 'tests/resources/presentations' |
867 | === added file 'tests/resources/presentations/pdf_test1.pdf' |
868 | Binary files tests/resources/presentations/pdf_test1.pdf 1970-01-01 00:00:00 +0000 and tests/resources/presentations/pdf_test1.pdf 2013-12-29 19:49:15 +0000 differ |
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')