36
36
from openlp.core.common import md5_hash
37
37
from openlp.core.common.applocation import AppLocation
38
38
from openlp.core.common.i18n import translate
39
from openlp.core.ui.icons import UiIcons
40
39
from openlp.core.common.mixins import RegistryProperties
41
40
from openlp.core.common.path import Path
42
41
from openlp.core.common.settings import Settings
43
from openlp.core.lib import ImageSource, clean_tags, expand_tags, expand_chords
42
from openlp.core.display.render import remove_tags, render_tags
43
from openlp.core.lib import ItemCapabilities
44
from openlp.core.ui.icons import UiIcons
45
47
log = logging.getLogger(__name__)
57
class ItemCapabilities(object):
59
Provides an enumeration of a service item's capabilities
62
The capability to allow the ServiceManager to add to the preview tab when making the previous item live.
65
The capability to allow the ServiceManager to allow the item to be edited
68
The capability to allow the ServiceManager to allow the item to be reordered.
71
Determines is the service_item needs a Media Player
74
The capability to allow the SlideController to allow the loop processing.
77
The capability to allow the ServiceManager to add leaves to the
81
The capability to remove lines breaks in the renderer
84
The capability to update MediaManager when a service Item is loaded.
89
``ProvidesOwnDisplay``
90
The capability to tell the SlideController the service Item has a different display.
92
``HasDetailedTitleDisplay``
93
Being Removed and decommissioned.
95
``HasVariableStartTime``
96
The capability to tell the ServiceManager that a change to start time is possible.
99
The capability to tell the renderer that Soft Break is allowed
102
The capability to tell the renderer that it can split words is
105
``HasBackgroundAudio``
106
That a audio file is present with the text.
108
``CanAutoStartForLive``
109
The capability to ignore the do not play if display blank flag.
112
The capability to edit the title of the item
115
Determines is the service_item is based on an optical device
118
The item contains 'displaytitle' on every frame which should be
119
preferred over 'title' when displaying the item
122
The item contains 'notes'
125
The item has related thumbnails available
128
The item has Meta Data about item
139
ProvidesOwnDisplay = 10
140
# HasDetailedTitleDisplay = 11
141
HasVariableStartTime = 12
144
HasBackgroundAudio = 15
145
CanAutoStartForLive = 16
154
59
class ServiceItem(RegistryProperties):
156
61
The service item is a base class for the plugins to use to interact with
249
155
self.icon = UiIcons().clone
251
def render(self, provides_own_theme_data=False):
253
The render method is what generates the frames for the screen and obtains the display information from the
254
renderer. At this point all slides are built for the given display size.
157
def _create_slides(self):
159
Create frames for rendering and display
161
self._rendered_slides = []
162
self._display_slides = []
256
:param provides_own_theme_data: This switch disables the usage of the item's theme. However, this is
257
disabled by default. If this is used, it has to be taken care, that
258
the renderer knows the correct theme data. However, this is needed
259
for the theme manager.
261
log.debug('Render called')
262
self._display_frames = []
263
self.bg_image_bytes = None
264
if not provides_own_theme_data:
265
self.renderer.set_item_theme(self.theme)
266
self.theme_data, self.main, self.footer = self.renderer.pre_render()
267
if self.service_item_type == ServiceItemType.Text:
268
expand_chord_tags = hasattr(self, 'name') and self.name == 'songs' and Settings().value(
269
'songs/enable chords')
270
log.debug('Formatting slides: {title}'.format(title=self.title))
271
# Save rendered pages to this dict. In the case that a slide is used twice we can use the pages saved to
272
# the dict instead of rendering them again.
274
for slide in self._raw_frames:
275
verse_tag = slide['verseTag']
276
if verse_tag in previous_pages and previous_pages[verse_tag][0] == slide['raw_slide']:
277
pages = previous_pages[verse_tag][1]
279
pages = self.renderer.format_slide(slide['raw_slide'], self)
280
previous_pages[verse_tag] = (slide['raw_slide'], pages)
282
page = page.replace('<br>', '{br}')
283
html_data = expand_tags(page.rstrip(), expand_chord_tags)
285
'title': clean_tags(page),
286
'text': clean_tags(page.rstrip(), expand_chord_tags),
287
'chords_text': expand_chords(clean_tags(page.rstrip(), False)),
288
'html': html_data.replace('&nbsp;', ' '),
289
'printing_html': expand_tags(html.escape(page.rstrip()), expand_chord_tags, True),
290
'verseTag': verse_tag,
292
self._display_frames.append(new_frame)
293
elif self.service_item_type == ServiceItemType.Image or self.service_item_type == ServiceItemType.Command:
296
log.error('Invalid value renderer: {item}'.format(item=self.service_item_type))
297
self.title = clean_tags(self.title)
298
# The footer should never be None, but to be compatible with a few
299
# nightly builds between 1.9.4 and 1.9.5, we have to correct this to
301
if self.raw_footer is None:
164
# Save rendered pages to this dict. In the case that a slide is used twice we can use the pages saved to
165
# the dict instead of rendering them again.
303
168
self.foot_text = '<br>'.join([_f for _f in self.raw_footer if _f])
169
for raw_slide in self.slides:
170
verse_tag = raw_slide['verse']
171
if verse_tag in previous_pages and previous_pages[verse_tag][0] == raw_slide:
172
pages = previous_pages[verse_tag][1]
174
pages = self.renderer.format_slide(raw_slide['text'], self)
175
previous_pages[verse_tag] = (raw_slide, pages)
178
'title': raw_slide['title'],
179
'text': render_tags(page),
181
'footer': self.foot_text,
183
self._rendered_slides.append(rendered_slide)
185
'title': raw_slide['title'],
186
'text': remove_tags(page),
189
self._display_slides.append(display_slide)
193
def rendered_slides(self):
195
Render the frames and return them
197
if not self._rendered_slides:
198
self._create_slides()
199
return self._rendered_slides
202
def display_slides(self):
204
Render the frames and return them
206
if not self._display_slides:
207
self._create_slides()
208
return self._display_slides
305
210
def add_from_image(self, path, title, background=None, thumbnail=None):
309
214
:param path: The directory in which the image file is located.
310
215
:param title: A title for the slide in the service item.
216
:param background: The background colour
312
217
:param thumbnail: Optional alternative thumbnail, used for remote thumbnails.
315
220
self.image_border = background
316
221
self.service_item_type = ServiceItemType.Image
318
self._raw_frames.append({'title': title, 'path': path})
320
self._raw_frames.append({'title': title, 'path': path, 'image': thumbnail})
321
self.image_manager.add_image(path, ImageSource.ImagePlugin, self.image_border)
222
slide = {'title': title, 'path': path}
224
slide['thumbnail'] = thumbnail
225
self.slides.append(slide)
226
# self.image_manager.add_image(path, ImageSource.ImagePlugin, self.image_border)
324
def add_from_text(self, raw_slide, verse_tag=None):
229
def add_from_text(self, text, verse_tag=None):
326
231
Add a text slide to the service item.
328
:param raw_slide: The raw text of the slide.
233
:param text: The raw text of the slide.
329
234
:param verse_tag:
332
237
verse_tag = verse_tag.upper()
239
# For items that don't have a verse tag, autoincrement the slide numbers
240
verse_tag = str(len(self.slides))
333
241
self.service_item_type = ServiceItemType.Text
334
title = raw_slide[:30].split('\n')[0]
335
self._raw_frames.append({'title': title, 'raw_slide': raw_slide, 'verseTag': verse_tag})
242
title = text[:30].split('\n')[0]
243
self.slides.append({'title': title, 'text': text, 'verse': verse_tag})
338
246
def add_from_command(self, path, file_name, image, display_title=None, notes=None):
349
257
# If the item should have a display title but this frame doesn't have one, we make one up
350
258
if self.is_capable(ItemCapabilities.HasDisplayTitle) and not display_title:
351
259
display_title = translate('OpenLP.ServiceItem',
352
'[slide {frame:d}]').format(frame=len(self._raw_frames) + 1)
260
'[slide {frame:d}]').format(frame=len(self.slides) + 1)
353
261
# Update image path to match servicemanager location if file was loaded from service
354
262
if image and not self.has_original_files and self.name == 'presentations':
355
263
file_location = os.path.join(path, file_name)
356
264
file_location_hash = md5_hash(file_location.encode('utf-8'))
357
265
image = os.path.join(str(AppLocation.get_section_data_path(self.name)), 'thumbnails',
358
266
file_location_hash, ntpath.basename(image))
359
self._raw_frames.append({'title': file_name, 'image': image, 'path': path,
360
'display_title': display_title, 'notes': notes})
361
if self.is_capable(ItemCapabilities.HasThumbnails):
362
self.image_manager.add_image(image, ImageSource.CommandPlugins, '#000000')
267
self.slides.append({'title': file_name, 'image': image, 'path': path, 'display_title': display_title,
268
'notes': notes, 'thumbnail': image})
269
# if self.is_capable(ItemCapabilities.HasThumbnails):
270
# self.image_manager.add_image(image, ImageSource.CommandPlugins, '#000000')
365
273
def get_service_repr(self, lite_save):
395
303
service_data = []
396
304
if self.service_item_type == ServiceItemType.Text:
397
service_data = [slide for slide in self._raw_frames]
305
for slide in self.slides:
306
data_slide = deepcopy(slide)
307
data_slide['raw_slide'] = data_slide.pop('text')
308
data_slide['verseTag'] = data_slide.pop('verse')
309
service_data.append(data_slide)
398
310
elif self.service_item_type == ServiceItemType.Image:
400
for slide in self._raw_frames:
312
for slide in self.slides:
401
313
service_data.append({'title': slide['title'], 'path': slide['path']})
403
service_data = [slide['title'] for slide in self._raw_frames]
315
service_data = [slide['title'] for slide in self.slides]
404
316
elif self.service_item_type == ServiceItemType.Command:
405
for slide in self._raw_frames:
317
for slide in self.slides:
406
318
service_data.append({'title': slide['title'], 'image': slide['image'], 'path': slide['path'],
407
319
'display_title': slide['display_title'], 'notes': slide['notes']})
408
320
return {'header': service_header, 'data': service_data}
589
501
:param row: The service item slide to be returned
591
503
if self.service_item_type == ServiceItemType.Text:
592
return self._display_frames[row]['html'].split('\n')[0]
504
# return self.display_frames[row]['html'].split('\n')[0]
505
return self.rendered_slides[row]['text']
593
506
elif self.service_item_type == ServiceItemType.Image:
594
return self._raw_frames[row]['path']
507
return self.slides[row]['path']
596
return self._raw_frames[row]['image']
509
return self.slides[row]['image']
598
511
def get_frame_title(self, row=0):
600
513
Returns the title of the raw frame
603
return self._raw_frames[row]['title']
516
return self.get_frames()[row]['title']
604
517
except IndexError:
678
590
Returns if there are any frames in the service item
680
return not bool(self._raw_frames)
592
return not bool(self.slides)
682
594
def validate_item(self, suffix_list=None):
684
596
Validates a service item to make sure it is valid
686
598
self.is_valid = True
687
for frame in self._raw_frames:
688
if self.is_image() and not os.path.exists(frame['path']):
599
for slide in self.slides:
600
if self.is_image() and not os.path.exists(slide['path']):
689
601
self.is_valid = False
691
603
elif self.is_command():
692
604
if self.is_capable(ItemCapabilities.IsOptical) and State().check_preconditions('media'):
693
if not os.path.exists(frame['title']):
605
if not os.path.exists(slide['title']):
694
606
self.is_valid = False
697
file_name = os.path.join(frame['path'], frame['title'])
609
file_name = os.path.join(slide['path'], slide['title'])
698
610
if not os.path.exists(file_name):
699
611
self.is_valid = False
701
613
if suffix_list and not self.is_text():
702
file_suffix = frame['title'].split('.')[-1]
614
file_suffix = slide['title'].split('.')[-1]
703
615
if file_suffix.lower() not in suffix_list:
704
616
self.is_valid = False