diff --git a/openlp.pyw b/openlp.pyw index fb96ef17c..c4f062b87 100755 --- a/openlp.pyw +++ b/openlp.pyw @@ -34,7 +34,8 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import Receiver from openlp.core.resources import qInitResources -from openlp.core.ui import MainWindow, SplashScreen, ScreenList +from openlp.core.ui.mainwindow import MainWindow +from openlp.core.ui import SplashScreen, ScreenList from openlp.core.utils import AppLocation, LanguageManager, VersionThread log = logging.getLogger() @@ -47,7 +48,6 @@ QMainWindow::separator QDockWidget::title { - /*background: palette(dark);*/ border: 1px solid palette(dark); padding-left: 5px; padding-top: 2px; diff --git a/openlp/.version b/openlp/.version index 8fdcf3869..ae8249b9a 100644 --- a/openlp/.version +++ b/openlp/.version @@ -1 +1 @@ -1.9.2 +1.9.2-bzr987 \ No newline at end of file diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index ae5d0602c..b76179c2c 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -35,6 +35,67 @@ from PyQt4 import QtCore, QtGui log = logging.getLogger(__name__) +# TODO make external and configurable in alpha 4 via a settings dialog +html_expands = [] + +html_expands.append({u'desc':u'Red', u'start tag':u'{r}', \ + u'start html':u'', \ + u'end tag':u'{/r}', u'end html':u'', \ + u'protected':False}) +html_expands.append({u'desc':u'Black', u'start tag':u'{b}', \ + u'start html':u'', \ + u'end tag':u'{/b}', u'end html':u'', \ + u'protected':False}) +html_expands.append({u'desc':u'Blue', u'start tag':u'{bl}', \ + u'start html':u'', \ + u'end tag':u'{/bl}', u'end html':u'', \ + u'protected':False}) +html_expands.append({u'desc':u'Yellow', u'start tag':u'{y}', \ + u'start html':u'', \ + u'end tag':u'{/y}', u'end html':u'', \ + u'protected':False}) +html_expands.append({u'desc':u'Green', u'start tag':u'{g}', \ + u'start html':u'', \ + u'end tag':u'{/g}', u'end html':u'', \ + u'protected':False}) +html_expands.append({u'desc':u'Pink', u'start tag':u'{pk}', \ + u'start html':u'', \ + u'end tag':u'{/pk}', u'end html':u'', \ + u'protected':False}) +html_expands.append({u'desc':u'Orange', u'start tag':u'{o}', \ + u'start html':u'', \ + u'end tag':u'{/o}', u'end html':u'', \ + u'protected':False}) +html_expands.append({u'desc':u'Purple', u'start tag':u'{pp}', \ + u'start html':u'', \ + u'end tag':u'{/pp}', u'end html':u'', \ + u'protected':False}) +html_expands.append({u'desc':u'White', u'start tag':u'{w}', \ + u'start html':u'', \ + u'end tag':u'{/w}', u'end html':u'', \ + u'protected':False}) +html_expands.append({u'desc':u'Superscript', u'start tag':u'{su}', \ + u'start html':u'', \ + u'end tag':u'{/su}', u'end html':u'', \ + u'protected':True}) +html_expands.append({u'desc':u'Subscript', u'start tag':u'{sb}', \ + u'start html':u'', \ + u'end tag':u'{/sb}', u'end html':u'', \ + u'protected':True}) +html_expands.append({u'desc':u'Paragraph', u'start tag':u'{p}', \ + u'start html':u'

', \ + u'end tag':u'{/p}', u'end html':u'

', \ + u'protected':True}) +html_expands.append({u'desc':u'Bold', u'start tag':u'{st}', \ + u'start html':u'', \ + u'end tag':u'{/st}', \ + u'end html':u'', \ + u'protected':True}) +html_expands.append({u'desc':u'Italics', u'start tag':u'{it}', \ + u'start html':u'', \ + u'end tag':u'{/it}', u'end html':u'', \ + u'protected':True}) + def translate(context, text, comment=None): """ A special shortcut method to wrap around the Qt4 translation functions. @@ -166,15 +227,46 @@ def context_menu_separator(base): action.setSeparator(True) return action -def resize_image(image, width, height): +def image_to_byte(image): + """ + Resize an image to fit on the current screen for the web and returns + it as a byte stream. + + ``image`` + The image to converted. + """ + byte_array = QtCore.QByteArray() + # use buffer to store pixmap into byteArray + buffie = QtCore.QBuffer(byte_array) + buffie.open(QtCore.QIODevice.WriteOnly) + if isinstance(image, QtGui.QImage): + pixmap = QtGui.QPixmap.fromImage(image) + else: + pixmap = QtGui.QPixmap(image) + pixmap.save(buffie, "PNG") + # convert to base64 encoding so does not get missed! + return byte_array.toBase64() + +def resize_image(image, width, height, background=QtCore.Qt.black): """ Resize an image to fit on the current screen. ``image`` The image to resize. + + ``width`` + The new image width. + + ``height`` + The new image height. + + ``background`` + The background colour defaults to black. + """ preview = QtGui.QImage(image) if not preview.isNull(): + # Only resize if different size if preview.width() == width and preview.height == height: return preview preview = preview.scaled(width, height, QtCore.Qt.KeepAspectRatio, @@ -184,7 +276,7 @@ def resize_image(image, width, height): # and move it to the centre of the preview space new_image = QtGui.QImage(width, height, QtGui.QImage.Format_ARGB32_Premultiplied) - new_image.fill(QtCore.Qt.black) + new_image.fill(background) painter = QtGui.QPainter(new_image) painter.drawImage((width - realw) / 2, (height - realh) / 2, preview) return new_image @@ -205,6 +297,25 @@ def check_item_selected(list_widget, message): return False return True +def clean_tags(text): + """ + Remove Tags from text for display + """ + text = text.replace(u'
', u'\n') + for tag in html_expands: + text = text.replace(tag[u'start tag'], u'') + text = text.replace(tag[u'end tag'], u'') + return text + +def expand_tags(text): + """ + Expand tags HTML for display + """ + for tag in html_expands: + text = text.replace(tag[u'start tag'], tag[u'start html']) + text = text.replace(tag[u'end tag'], tag[u'end html']) + return text + from eventreceiver import Receiver from settingsmanager import SettingsManager from plugin import PluginStatus, Plugin @@ -213,6 +324,7 @@ from settingstab import SettingsTab from serviceitem import ServiceItem from serviceitem import ServiceItemType from serviceitem import ItemCapabilities +from htmlbuilder import build_html from toolbar import OpenLPToolbar from dockwidget import OpenLPDockWidget from theme import ThemeLevel, ThemeXML diff --git a/openlp/core/lib/htmlbuilder.py b/openlp/core/lib/htmlbuilder.py new file mode 100644 index 000000000..2fa7b0df0 --- /dev/null +++ b/openlp/core/lib/htmlbuilder.py @@ -0,0 +1,445 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2010 Raoul Snyman # +# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Meinert Jordan, Andreas Preikschat, Christian # +# Richter, Philip Ridout, Maikel Stuivenberg, Martin Thompson, Jon Tibble, # +# Carsten Tinggaard, Frode Woldsund # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### + +from openlp.core.lib import image_to_byte + +HTMLSRC = u""" + + +OpenLP Display + + + + + + + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + + +
+ + + + """ + +def build_html(item, screen, alert): + """ + Build the full web paged structure for display + + `item` + Service Item to be displayed + `screen` + Current display information + `alert` + Alert display display information + """ + width = screen[u'size'].width() + height = screen[u'size'].height() + theme = item.themedata + if item.bg_frame: + image = u'data:image/png;base64,%s' % image_to_byte(item.bg_frame) + else: + image = u'' + html = HTMLSRC % (width, height, + build_alert(alert, width), + build_footer(item), + build_lyrics(item), + u'true' if theme and theme.display_slideTransition \ + else u'false', + image) + return html + +def build_lyrics(item): + """ + Build the video display div + + `item` + Service Item containing theme and location information + """ + style = """ + .lyricscommon { position: absolute; %s } + .lyricstable { z-index:4; %s } + .lyricsoutlinetable { z-index:3; %s } + .lyricsshadowtable { z-index:2; %s } + .lyrics { %s } + .lyricsoutline { %s } + .lyricsshadow { %s } + """ + theme = item.themedata + lyricscommon = u'' + lyricstable = u'' + outlinetable = u'' + shadowtable = u'' + lyrics = u'' + outline = u'display: none;' + shadow = u'display: none;' + if theme: + lyricscommon = u'width: %spx; height: %spx; word-wrap: break-word; ' \ + u'font-family: %s; font-size: %spx; color: %s; line-height: %d%%;' % \ + (item.main.width(), item.main.height(), + theme.font_main_name, theme.font_main_proportion, + theme.font_main_color, 100 + int(theme.font_main_line_adjustment)) + lyricstable = u'left: %spx; top: %spx;' % \ + (item.main.x(), item.main.y()) + outlinetable = u'left: %spx; top: %spx;' % \ + (item.main.x(), item.main.y()) + shadowtable = u'left: %spx; top: %spx;' % \ + (item.main.x() + float(theme.display_shadow_size), + item.main.y() + float(theme.display_shadow_size)) + align = u'' + if theme.display_horizontalAlign == 2: + align = u'text-align:center;' + elif theme.display_horizontalAlign == 1: + align = u'text-align:right;' + else: + align = u'text-align:left;' + if theme.display_verticalAlign == 2: + valign = u'vertical-align:bottom;' + elif theme.display_verticalAlign == 1: + valign = u'vertical-align:middle;' + else: + valign = u'vertical-align:top;' + lyrics = u'%s %s' % (align, valign) + if theme.display_outline: + lyricscommon += u' letter-spacing: 1px;' + outline = u'-webkit-text-stroke: %sem %s; ' % \ + (float(theme.display_outline_size) / 16, + theme.display_outline_color) + if theme.display_shadow: + shadow = u'-webkit-text-stroke: %sem %s; ' \ + u'-webkit-text-fill-color: %s; '% \ + (float(theme.display_outline_size) / 16, + theme.display_shadow_color, theme.display_shadow_color) + else: + if theme.display_shadow: + shadow = u'color: %s;' % (theme.display_shadow_color) + lyrics_html = style % (lyricscommon, lyricstable, outlinetable, + shadowtable, lyrics, outline, shadow) + return lyrics_html + +def build_footer(item): + """ + Build the display of the item footer + + `item` + Service Item to be processed. + """ + style = """ + left: %spx; + top: %spx; + width: %spx; + height: %spx; + font-family: %s; + font-size: %spx; + color: %s; + text-align: %s; + """ + theme = item.themedata + if not theme: + return u'' + if theme.display_horizontalAlign == 2: + align = u'center' + elif theme.display_horizontalAlign == 1: + align = u'right' + else: + align = u'left' + lyrics_html = style % (item.footer.x(), item.footer.y(), + item.footer.width(), item.footer.height(), theme.font_footer_name, + theme.font_footer_proportion, theme.font_footer_color, align) + return lyrics_html + +def build_alert(alertTab, width): + """ + Build the display of the footer + + `alertTab` + Details from the Alert tab for fonts etc + """ + style = """ + width: %s; + vertical-align: %s; + font-family: %s; + font-size: %spx; + color: %s; + background-color: %s; + """ + if not alertTab: + return u'' + align = u'' + if alertTab.location == 2: + align = u'bottom' + elif alertTab.location == 1: + align = u'middle' + else: + align = u'top' + alert = style % (width, align, alertTab.font_face, alertTab.font_size, + alertTab.font_color, alertTab.bg_color) + return alert diff --git a/openlp/core/lib/plugin.py b/openlp/core/lib/plugin.py index 45fb12ec4..45fbcb6b0 100644 --- a/openlp/core/lib/plugin.py +++ b/openlp/core/lib/plugin.py @@ -131,7 +131,6 @@ class Plugin(QtCore.QObject): self.serviceManager = plugin_helpers[u'service'] self.settingsForm = plugin_helpers[u'settings form'] self.mediadock = plugin_helpers[u'toolbox'] - self.displayManager = plugin_helpers[u'displaymanager'] self.pluginManager = plugin_helpers[u'pluginmanager'] self.formparent = plugin_helpers[u'formparent'] QtCore.QObject.connect(Receiver.get_receiver(), diff --git a/openlp/core/lib/renderer.py b/openlp/core/lib/renderer.py index f764f069f..433018c23 100644 --- a/openlp/core/lib/renderer.py +++ b/openlp/core/lib/renderer.py @@ -31,7 +31,7 @@ import logging from PyQt4 import QtGui, QtCore -from openlp.core.lib import resize_image +from openlp.core.lib import resize_image, expand_tags log = logging.getLogger(__name__) @@ -80,7 +80,6 @@ class Renderer(object): self.bg_image = None self._bg_image_filename = None self.theme_name = theme.theme_name - self._set_theme_font() if theme.background_type == u'image': if theme.background_filename: self.set_bg_image(theme.background_filename) @@ -99,6 +98,20 @@ class Renderer(object): self.frame.width(), self.frame.height()) + def set_text_rectangle(self, rect_main, rect_footer): + """ + Sets the rectangle within which text should be rendered. + + ``rect_main`` + The main text block. + + ``rect_footer`` + The footer text block. + """ + log.debug(u'set_text_rectangle %s , %s' % (rect_main, rect_footer)) + self._rect = rect_main + self._rect_footer = rect_footer + def set_frame_dest(self, frame_width, frame_height, preview=False): """ Set the size of the slide. @@ -118,26 +131,24 @@ class Renderer(object): frame_height) self.frame = QtGui.QImage(frame_width, frame_height, QtGui.QImage.Format_ARGB32_Premultiplied) - self.frame_opaque = QtGui.QImage(frame_width, frame_height, - QtGui.QImage.Format_ARGB32_Premultiplied) if self._bg_image_filename and not self.bg_image: self.bg_image = resize_image(self._bg_image_filename, self.frame.width(), self.frame.height()) if self.bg_frame is None: self._generate_background_frame() - def format_slide(self, words, footer): + def format_slide(self, words, line_break): """ Figure out how much text can appear on a slide, using the current theme settings. ``words`` The words to be fitted on the slide. - - ``footer`` - The footer of the slide. """ log.debug(u'format_slide - Start') + line_end = u'' + if line_break: + line_end = u'
' words = words.replace(u'\r\n', u'\n') verses_text = words.split(u'\n') text = [] @@ -145,124 +156,43 @@ class Renderer(object): lines = verse.split(u'\n') for line in lines: text.append(line) - split_text = self.pre_render_text(text) - log.debug(u'format_slide - End') - return split_text - - def pre_render_text(self, text): - metrics = QtGui.QFontMetrics(self.main_font) - #work out line width - line_width = self._rect.width() - #number of lines on a page - adjust for rounding up. - line_height = metrics.height() - if self._theme.display_shadow: - line_height += int(self._theme.display_shadow_size) - if self._theme.display_outline: - # pixels top/bottom - line_height += 2 * int(self._theme.display_outline_size) - page_length = int(self._rect.height() / line_height ) - #Average number of characters in line - ave_line_width = line_width / metrics.averageCharWidth() - #Maximum size of a character - max_char_width = metrics.maxWidth() - #Max characters pre line based on min size of a character - char_per_line = line_width / metrics.width(u'i') - log.debug(u'Page Length area height %s , metrics %s , lines %s' % - (int(self._rect.height()), metrics.height(), page_length )) - split_pages = [] - page = [] - split_lines = [] - count = 0 - for line in text: - #Must be a blank line so keep it. - if len(line) == 0: - line = u' ' - while line: - pos = char_per_line - split_text = line[:pos] - #line needs splitting - if metrics.width(split_text, -1) > line_width: - #We have no spaces - if split_text.find(u' ') == -1: - #Move back 1 char at a time till it fits - while metrics.width(split_text, -1) > line_width: - split_text = split_text[:-1] - pos = len(split_text) - else: - #We have spaces so split at previous one - while metrics.width(split_text, -1) > line_width: - pos = split_text.rfind(u' ') - #no more spaces and we are still too long - if pos == -1: - while \ - metrics.width(split_text, -1) > line_width: - split_text = split_text[:-1] - pos = len(split_text) - else: - split_text = line[:pos] - split_lines.append(split_text) - line = line[pos:].lstrip() - #if we have more text add up to 10 spaces on the front. - if line and self._theme.font_main_indentation > 0: - line = u'%s%s' % \ - (u' '[:int(self._theme.font_main_indentation)], - line) - #Text fits in a line now - for count, line in enumerate(split_lines): - page.append(line) - #last but one line and only 2 lines to go or end of page - if (len(page) == page_length - 1 and - len(split_lines) - 3 == count) or \ - len(page) == page_length: - split_pages.append(page) - page = [] - if page and page != u' ': - split_pages.append(page) - return split_pages - - def set_text_rectangle(self, rect_main, rect_footer): - """ - Sets the rectangle within which text should be rendered. - - ``rect_main`` - The main text block. - - ``rect_footer`` - The footer text block. - """ - log.debug(u'set_text_rectangle %s , %s' % (rect_main, rect_footer)) - self._rect = rect_main - self._rect_footer = rect_footer - - def generate_frame_from_lines(self, lines, footer_lines=None): - """ - Render a set of lines according to the theme, and return the block - dimensions. - - ``lines`` - The lines to be rendered. - - ``footer_lines`` - Defaults to *None*. The footer to render. - """ - log.debug(u'generate_frame_from_lines - Start') - bbox = self._render_lines_unaligned(lines, False) - if footer_lines: - bbox1 = self._render_lines_unaligned(footer_lines, True) - # reset the frame. first time do not worry about what you paint on. - self.frame = QtGui.QImage(self.bg_frame) - if self._theme.display_slideTransition: - self.frame_opaque = QtGui.QImage(self.bg_frame) - x, y = self._correct_alignment(self._rect, bbox) - bbox = self._render_lines_unaligned(lines, False, (x, y), True) - if footer_lines: - bbox = self._render_lines_unaligned(footer_lines, True, - (self._rect_footer.left(), self._rect_footer.top()), True) - log.debug(u'generate_frame_from_lines - Finish') - if self._theme.display_slideTransition: - return {u'main':self.frame, u'trans':self.frame_opaque} + doc = QtGui.QTextDocument() + doc.setPageSize(QtCore.QSizeF(self._rect.width(), self._rect.height())) + df = doc.defaultFont() + df.setPixelSize(self._theme.font_main_proportion) + df.setFamily(self._theme.font_main_name) + main_weight = 50 + if self._theme.font_main_weight == u'Bold': + main_weight = 75 + df.setWeight(main_weight) + doc.setDefaultFont(df) + layout = doc.documentLayout() + formatted = [] + if self._theme.font_main_weight == u'Bold' and \ + self._theme.font_main_italics: + shell = u'{p}{st}{it}%s{/it}{/st}{/p}' + elif self._theme.font_main_weight == u'Bold' and \ + not self._theme.font_main_italics: + shell = u'{p}{st}%s{/st}{/p}' + elif self._theme.font_main_italics: + shell = u'{p}{it}%s{/it}{/p}' else: - return {u'main':self.frame, u'trans':None} + shell = u'{p}%s{/p}' + temp_text = u'' + old_html_text = u'' + for line in text: + # mark line ends + temp_text = temp_text + line + line_end + html_text = shell % expand_tags(temp_text) + doc.setHtml(html_text) + # Text too long so gone to next mage + if layout.pageCount() != 1: + formatted.append(shell % old_html_text) + temp_text = line + old_html_text = temp_text + formatted.append(shell % old_html_text) + log.debug(u'format_slide - End') + return formatted def _generate_background_frame(self): """ @@ -270,327 +200,47 @@ class Renderer(object): Results are cached for performance reasons. """ assert(self._theme) - if self._theme.background_mode == u'transparent': - self.bg_frame = \ - QtGui.QPixmap(self.frame.width(), self.frame.height()) - self.bg_frame.fill(QtCore.Qt.transparent) - else: - self.bg_frame = QtGui.QImage(self.frame.width(), - self.frame.height(), QtGui.QImage.Format_ARGB32_Premultiplied) + self.bg_frame = QtGui.QImage(self.frame.width(), + self.frame.height(), QtGui.QImage.Format_ARGB32_Premultiplied) log.debug(u'render background %s start', self._theme.background_type) painter = QtGui.QPainter() painter.begin(self.bg_frame) - if self._theme.background_mode == u'transparent': - painter.fillRect(self.frame.rect(), QtCore.Qt.transparent) - else: - if self._theme.background_type == u'solid': - painter.fillRect(self.frame.rect(), - QtGui.QColor(self._theme.background_color)) - elif self._theme.background_type == u'gradient': - # gradient - gradient = None - if self._theme.background_direction == u'horizontal': - w = int(self.frame.width()) / 2 - # vertical - gradient = QtGui.QLinearGradient(w, 0, w, - self.frame.height()) - elif self._theme.background_direction == u'vertical': - h = int(self.frame.height()) / 2 - # Horizontal - gradient = QtGui.QLinearGradient(0, h, self.frame.width(), - h) - else: - w = int(self.frame.width()) / 2 - h = int(self.frame.height()) / 2 - # Circular - gradient = QtGui.QRadialGradient(w, h, w) - gradient.setColorAt(0, - QtGui.QColor(self._theme.background_startColor)) - gradient.setColorAt(1, - QtGui.QColor(self._theme.background_endColor)) - painter.setBrush(QtGui.QBrush(gradient)) - rect_path = QtGui.QPainterPath() - max_x = self.frame.width() - max_y = self.frame.height() - rect_path.moveTo(0, 0) - rect_path.lineTo(0, max_y) - rect_path.lineTo(max_x, max_y) - rect_path.lineTo(max_x, 0) - rect_path.closeSubpath() - painter.drawPath(rect_path) - elif self._theme.background_type == u'image': - # image - painter.fillRect(self.frame.rect(), QtCore.Qt.black) - if self.bg_image: - painter.drawImage(0, 0, self.bg_image) - painter.end() - log.debug(u'render background End') - - def _correct_alignment(self, rect, bbox): - """ - Corrects the vertical alignment of text. - - ``rect`` - The block dimentions. - - ``bbox`` - Footer dimensions? - """ - x = rect.left() - if self._theme.display_verticalAlign == 0: - # top align - y = rect.top() - elif self._theme.display_verticalAlign == 2: - # bottom align - y = rect.bottom() - bbox.height() - elif self._theme.display_verticalAlign == 1: - # centre align - y = rect.top() + (rect.height() - bbox.height()) / 2 - else: - log.error(u'Invalid value for theme.VerticalAlign:%s', - self._theme.display_verticalAlign) - return x, y - - def _render_lines_unaligned(self, lines, footer, tlcorner=(0, 0), - live=False): - """ - Given a list of lines to render, render each one in turn (using the - ``_render_single_line`` fn - which may result in going off the bottom). - They are expected to be pre-arranged to less than a screenful (eg. by - using split_set_of_lines). - - Returns the bounding box of the text as QRect. - - ``lines`` - The lines of text to render. - - ``footer`` - The slide footer. - - ``tlcorner`` - Defaults to *``(0, 0)``*. Co-ordinates of the top left corner. - - ``live`` - Defaults to *False*. Whether or not this is a live screen. - """ - x, y = tlcorner - brx = x - bry = y - for line in lines: - # render after current bottom, but at original left edge - # keep track of right edge to see which is biggest - (thisx, bry) = self._render_and_wrap_single_line(line, footer, - (x, bry), live) - if (thisx > brx): - brx = thisx - retval = QtCore.QRect(x, y, brx - x, bry - y) - if self._debug: - painter = QtGui.QPainter() - painter.begin(self.frame) - painter.setPen(QtGui.QPen(QtGui.QColor(0, 0, 255))) - painter.drawRect(retval) - painter.end() - return retval - - def _render_and_wrap_single_line(self, line, footer, tlcorner=(0, 0), - live=False): - """ - Render a single line of words onto the DC, top left corner specified. - If the line is too wide for the context, it wraps, but right-aligns - the surplus words in the manner of song lyrics. - - Returns the bottom-right corner (of what was rendered) as a tuple(x, y). - - ``line`` - Line of text to be rendered. - - ``footer`` - The footer of the slide. - - ``tlcorner`` - Defaults to *``(0, 0)``*. The top left corner. - - ``live`` - Defaults to *False*. Whether or not this is a live screen. - """ - x, y = tlcorner - maxx = self._rect.width() - maxy = self._rect.height() - lines = [] - lines.append(line) - startx = x - starty = y - rightextent = None - self.painter = QtGui.QPainter() - self.painter.begin(self.frame) - self.painter.setRenderHint(QtGui.QPainter.Antialiasing) - if self._theme.display_slideTransition: - self.painter2 = QtGui.QPainter() - self.painter2.begin(self.frame_opaque) - self.painter2.setRenderHint(QtGui.QPainter.Antialiasing) - self.painter2.setOpacity(0.7) - # dont allow alignment messing with footers - if footer: - align = 0 - display_shadow_size = self._display_shadow_size_footer - display_outline_size = self._display_outline_size_footer - else: - align = self._theme.display_horizontalAlign - display_shadow_size = int(self._theme.display_shadow_size) - display_outline_size = int(self._theme.display_outline_size) - for linenum in range(len(lines)): - line = lines[linenum] - #find out how wide line is - w, h = self._get_extent_and_render(line, footer, tlcorner=(x, y), - draw=False) - if self._theme.display_shadow: - w += display_shadow_size - h += display_shadow_size - if self._theme.display_outline: - # pixels either side - w += 2 * display_outline_size - # pixels top/bottom - h += 2 * display_outline_size - if align == 0: # left align - rightextent = x + w - # shift right from last line's rh edge - if self._theme.display_wrapStyle == 1 and linenum != 0: - rightextent = self._first_line_right_extent - if rightextent > maxx: - rightextent = maxx - x = rightextent - w - # right align - elif align == 1: - rightextent = maxx - x = maxx - w - # centre - elif align == 2: - x = (maxx - w) / 2 - rightextent = x + w - if live: - # now draw the text, and any outlines/shadows - if self._theme.display_shadow: - self._get_extent_and_render(line, footer, - tlcorner=(x + display_shadow_size, - y + display_shadow_size), - draw=True, color=self._theme.display_shadow_color) - self._get_extent_and_render(line, footer, tlcorner=(x, y), - draw=True, outline_size=display_outline_size) - y += h - if linenum == 0: - self._first_line_right_extent = rightextent - # draw a box around the text - debug only - - if self._debug: - self.painter.setPen(QtGui.QPen(QtGui.QColor(0, 255, 0))) - self.painter.drawRect(startx, starty, rightextent-startx, y-starty) - brcorner = (rightextent, y) - self.painter.end() - if self._theme.display_slideTransition: - self.painter2.end() - return brcorner - - def _set_theme_font(self): - """ - Set the fonts from the current theme settings. - """ - footer_weight = 50 - if self._theme.font_footer_weight == u'Bold': - footer_weight = 75 - #TODO Add myfont.setPixelSize((screen_height / 100) * font_size) - self.footer_font = QtGui.QFont(self._theme.font_footer_name, - self._theme.font_footer_proportion, # size - footer_weight, # weight - self._theme.font_footer_italics) # italic - self.footer_font.setPixelSize(self._theme.font_footer_proportion) - main_weight = 50 - if self._theme.font_main_weight == u'Bold': - main_weight = 75 - self.main_font = QtGui.QFont(self._theme.font_main_name, - self._theme.font_main_proportion, # size - main_weight, # weight - self._theme.font_main_italics)# italic - self.main_font.setPixelSize(self._theme.font_main_proportion) - - def _get_extent_and_render(self, line, footer, tlcorner=(0, 0), draw=False, - color=None, outline_size=0): - """ - Find bounding box of text - as render_single_line. If draw is set, - actually draw the text to the current DC as well return width and - height of text as a tuple (w, h). - - ``line`` - The line of text to render. - - ``footer`` - The footer text. - - ``tlcorner`` - Defaults to *``(0, 0)``*. The top left corner co-ordinates. - - ``draw`` - Defaults to *False*. Draw the text to the current surface. - - ``color`` - Defaults to *None*. The colour to draw with. - """ - # setup defaults - if footer: - font = self.footer_font - else: - font = self.main_font - metrics = QtGui.QFontMetrics(font) - w = metrics.width(line) - if footer: - h = metrics.height() - else: - h = metrics.height() + int(self._theme.font_main_line_adjustment) - if draw: - self.painter.setFont(font) - if color is None: - if footer: - pen = QtGui.QColor(self._theme.font_footer_color) - else: - pen = QtGui.QColor(self._theme.font_main_color) + if self._theme.background_type == u'solid': + painter.fillRect(self.frame.rect(), + QtGui.QColor(self._theme.background_color)) + elif self._theme.background_type == u'gradient': + # gradient + gradient = None + if self._theme.background_direction == u'horizontal': + w = int(self.frame.width()) / 2 + # vertical + gradient = QtGui.QLinearGradient(w, 0, w, self.frame.height()) + elif self._theme.background_direction == u'vertical': + h = int(self.frame.height()) / 2 + # Horizontal + gradient = QtGui.QLinearGradient(0, h, self.frame.width(), h) else: - pen = QtGui.QColor(color) - x, y = tlcorner - rowpos = y + metrics.ascent() - if self._theme.display_outline and outline_size != 0 and not footer: - path = QtGui.QPainterPath() - path.addText(QtCore.QPointF(x, rowpos), font, line) - self.painter.setBrush(self.painter.pen().brush()) - self.painter.setPen(QtGui.QPen(QtGui.QColor( - self._theme.display_outline_color), outline_size)) - self.painter.drawPath(path) - self.painter.setPen(pen) - self.painter.drawText(x, rowpos, line) - if self._theme.display_slideTransition: - # Print 2nd image with 70% weight - if self._theme.display_outline and outline_size != 0 and \ - not footer: - path = QtGui.QPainterPath() - path.addText(QtCore.QPointF(x, rowpos), font, line) - self.painter2.setBrush(self.painter2.pen().brush()) - self.painter2.setPen(QtGui.QPen( - QtGui.QColor(self._theme.display_outline_color), - outline_size)) - self.painter2.drawPath(path) - self.painter2.setFont(font) - self.painter2.setPen(pen) - self.painter2.drawText(x, rowpos, line) - return (w, h) - - def snoop_image(self, image, image2=None): - """ - Debugging method to allow images to be viewed. - - ``image`` - An image to save to disk. - - ``image2`` - Defaults to *None*. Another image to save to disk. - """ - image.save(u'renderer.png', u'png') - if image2: - image2.save(u'renderer2.png', u'png') + w = int(self.frame.width()) / 2 + h = int(self.frame.height()) / 2 + # Circular + gradient = QtGui.QRadialGradient(w, h, w) + gradient.setColorAt(0, + QtGui.QColor(self._theme.background_startColor)) + gradient.setColorAt(1, + QtGui.QColor(self._theme.background_endColor)) + painter.setBrush(QtGui.QBrush(gradient)) + rect_path = QtGui.QPainterPath() + max_x = self.frame.width() + max_y = self.frame.height() + rect_path.moveTo(0, 0) + rect_path.lineTo(0, max_y) + rect_path.lineTo(max_x, max_y) + rect_path.lineTo(max_x, 0) + rect_path.closeSubpath() + painter.drawPath(rect_path) + elif self._theme.background_type == u'image': + # image + painter.fillRect(self.frame.rect(), QtCore.Qt.black) + if self.bg_image: + painter.drawImage(0, 0, self.bg_image) + painter.end() diff --git a/openlp/core/lib/rendermanager.py b/openlp/core/lib/rendermanager.py index 7b6124fa8..13a8cfc6b 100644 --- a/openlp/core/lib/rendermanager.py +++ b/openlp/core/lib/rendermanager.py @@ -28,7 +28,8 @@ import logging from PyQt4 import QtCore -from openlp.core.lib import Renderer, ThemeLevel +from openlp.core.lib import Renderer, ThemeLevel, ServiceItem +from openlp.core.ui import MainDisplay log = logging.getLogger(__name__) @@ -55,6 +56,8 @@ class RenderManager(object): """ log.debug(u'Initilisation started') self.screens = screens + self.display = MainDisplay(self, screens, False) + self.display.setup() self.theme_manager = theme_manager self.renderer = Renderer() self.calculate_default(self.screens.current[u'size']) @@ -63,6 +66,7 @@ class RenderManager(object): self.theme_level = u'' self.override_background = None self.themedata = None + self.alertTab = None def update_display(self): """ @@ -70,7 +74,10 @@ class RenderManager(object): """ log.debug(u'Update Display') self.calculate_default(self.screens.current[u'size']) + self.display = MainDisplay(self, self.screens, False) + self.display.setup() self.renderer.bg_frame = None + self.themedata = None def set_global_theme(self, global_theme, theme_level=ThemeLevel.Global): """ @@ -96,17 +103,22 @@ class RenderManager(object): """ self.service_theme = service_theme - def set_override_theme(self, theme): + def set_override_theme(self, theme, overrideLevels=False): """ Set the appropriate theme depending on the theme level. + Called by the service item when building a display frame ``theme`` - The name of the song-level theme. + The name of the song-level theme. None means the service + item wants to use the given value. """ log.debug(u'set override theme to %s', theme) - if self.theme_level == ThemeLevel.Global: + theme_level = self.theme_level + if overrideLevels: + theme_level = ThemeLevel.Song + if theme_level == ThemeLevel.Global: self.theme = self.global_theme - elif self.theme_level == ThemeLevel.Service: + elif theme_level == ThemeLevel.Service: if self.service_theme == u'': self.theme = self.global_theme else: @@ -114,20 +126,26 @@ class RenderManager(object): else: if theme: self.theme = theme - elif self.theme_level == ThemeLevel.Song or \ - self.theme_level == ThemeLevel.Service: + elif theme_level == ThemeLevel.Song or \ + theme_level == ThemeLevel.Service: if self.service_theme == u'': self.theme = self.global_theme else: self.theme = self.service_theme else: self.theme = self.global_theme - if self.theme != self.renderer.theme_name or self.themedata is None: + if self.theme != self.renderer.theme_name or self.themedata is None \ + or overrideLevels: log.debug(u'theme is now %s', self.theme) - self.themedata = self.theme_manager.getThemeData(self.theme) + if overrideLevels: + self.themedata = theme + else: + self.themedata = self.theme_manager.getThemeData(self.theme) self.calculate_default(self.screens.current[u'size']) self.renderer.set_theme(self.themedata) self.build_text_rectangle(self.themedata) + self.renderer.set_frame_dest(self.width, self.height) + return self.renderer._rect, self.renderer._rect_footer def build_text_rectangle(self, theme): """ @@ -163,13 +181,8 @@ class RenderManager(object): The theme to generated a preview for. """ log.debug(u'generate preview') - #set the default image size for previews + # set the default image size for previews self.calculate_default(self.screens.preview[u'size']) - self.renderer.set_theme(themedata) - self.build_text_rectangle(themedata) - self.renderer.set_frame_dest(self.width, self.height, True) - #Reset the real screen size for subsequent render requests - self.calculate_default(self.screens.current[u'size']) verse = u'Amazing Grace!\n'\ 'How sweet the sound\n'\ 'To save a wretch like me;\n'\ @@ -179,12 +192,21 @@ class RenderManager(object): footer.append(u'Amazing Grace (John Newton)' ) footer.append(u'Public Domain') footer.append(u'CCLI 123456') - formatted = self.renderer.format_slide(verse, False) - #Only Render the first slide page returned - return self.renderer.generate_frame_from_lines(formatted[0], - footer)[u'main'] + # build a service item to generate preview + serviceItem = ServiceItem() + serviceItem.theme = themedata + serviceItem.add_from_text(u'', verse, footer) + serviceItem.render_manager = self + serviceItem.raw_footer = footer + serviceItem.render(True) + self.display.buildHtml(serviceItem) + frame, raw_html = serviceItem.get_rendered_frame(0) + preview = self.display.text(raw_html) + # Reset the real screen size for subsequent render requests + self.calculate_default(self.screens.current[u'size']) + return preview - def format_slide(self, words): + def format_slide(self, words, line_break): """ Calculate how much text can fit on a slide. @@ -193,22 +215,7 @@ class RenderManager(object): """ log.debug(u'format slide') self.build_text_rectangle(self.themedata) - return self.renderer.format_slide(words, False) - - def generate_slide(self, main_text, footer_text): - """ - Generate the actual slide image. - - ``main_text`` - The text for the main area of the slide. - - ``footer_text`` - The text for the slide footer. - """ - log.debug(u'generate slide') - self.build_text_rectangle(self.themedata) - self.renderer.set_frame_dest(self.width, self.height) - return self.renderer.generate_frame_from_lines(main_text, footer_text) + return self.renderer.format_slide(words, line_break) def calculate_default(self, screen): """ diff --git a/openlp/core/lib/serviceitem.py b/openlp/core/lib/serviceitem.py index 2bb28ada5..134df0c42 100644 --- a/openlp/core/lib/serviceitem.py +++ b/openlp/core/lib/serviceitem.py @@ -35,7 +35,7 @@ import uuid from PyQt4 import QtGui -from openlp.core.lib import build_icon, resize_image +from openlp.core.lib import build_icon, resize_image, clean_tags, expand_tags log = logging.getLogger(__name__) @@ -57,6 +57,7 @@ class ItemCapabilities(object): RequiresMedia = 4 AllowsLoop = 5 AllowsAdditions = 6 + NoLineBreaks = 7 class ServiceItem(object): """ @@ -82,6 +83,7 @@ class ServiceItem(object): self.items = [] self.iconic_representation = None self.raw_footer = None + self.foot_text = None self.theme = None self.service_item_type = None self._raw_frames = [] @@ -91,10 +93,18 @@ class ServiceItem(object): self.from_plugin = False self.capabilities = [] self.is_valid = True - self.cache = {} self.icon = None + self.themedata = None + self.main = None + self.footer = None + self.bg_frame = None def _new_item(self): + """ + Method to set the internal id of the item + This is used to compare service items to see if they are + the same + """ self._uuid = unicode(uuid.uuid1()) def add_capability(self, capability): @@ -126,34 +136,38 @@ class ServiceItem(object): self.icon = icon self.iconic_representation = build_icon(icon) - def render(self): + def render(self, useOverride=False): """ - The render method is what generates the frames for the screen. + The render method is what generates the frames for the screen and + obtains the display information from the renderemanager. + At this point all the slides are build for the given + display size. """ log.debug(u'Render called') self._display_frames = [] - self.clear_cache() + self.bg_frame = None + line_break = True + if self.is_capable(ItemCapabilities.NoLineBreaks): + line_break = False if self.service_item_type == ServiceItemType.Text: log.debug(u'Formatting slides') - if self.theme is None: - self.render_manager.set_override_theme(None) - else: - self.render_manager.set_override_theme(self.theme) + theme = None + if self.theme: + theme = self.theme + self.main, self.footer = \ + self.render_manager.set_override_theme(theme, useOverride) + self.bg_frame = self.render_manager.renderer.bg_frame + self.themedata = self.render_manager.renderer._theme for slide in self._raw_frames: before = time.time() - formated = self.render_manager.format_slide(slide[u'raw_slide']) - for format in formated: - lines = u'' - title = u'' - for line in format: - if title == u'': - title = line - lines += line + u'\n' - self._display_frames.append({u'title': title, - u'text': lines.rstrip(), + formated = self.render_manager \ + .format_slide(slide[u'raw_slide'], line_break) + for page in formated: + self._display_frames.append( + {u'title': clean_tags(page), + u'text': clean_tags(page.rstrip()), + u'html': expand_tags(page.rstrip()), u'verseTag': slide[u'verseTag'] }) - if len(self._display_frames) in self.cache.keys(): - del self.cache[len(self._display_frames)] log.log(15, u'Formatting took %4s' % (time.time() - before)) elif self.service_item_type == ServiceItemType.Image: for slide in self._raw_frames: @@ -163,29 +177,14 @@ class ServiceItem(object): pass else: log.error(u'Invalid value renderer :%s' % self.service_item_type) - - def render_individual(self, row): - """ - Takes an array of text and generates an Image from the - theme. It assumes the text will fit on the screen as it - has generated by the render method above. - """ - log.debug(u'render individual') - if self.theme is None: - self.render_manager.set_override_theme(None) - else: - self.render_manager.set_override_theme(self.theme) - format = self._display_frames[row][u'text'].split(u'\n') - if self.cache.get(row): - frame = self.cache[row] - else: - if format[0]: - frame = self.render_manager.generate_slide(format, - self.raw_footer) - else: - frame = self.render_manager.generate_slide(format, u'') - self.cache[row] = frame - return frame + self.title = clean_tags(self.title) + self.foot_text = None + if self.raw_footer: + for foot in self.raw_footer: + if not self.foot_text: + self.foot_text = foot + else: + self.foot_text = u'%s
%s' % (self.foot_text, foot) def add_from_image(self, path, title, image): """ @@ -375,9 +374,9 @@ class ServiceItem(object): renders it if required. """ if self.service_item_type == ServiceItemType.Text: - return self.render_individual(row) + return None, self._display_frames[row][u'html'].split(u'\n')[0] else: - return {u'main':self._raw_frames[row][u'image'], u'trans':None} + return self._raw_frames[row][u'image'], u'' def get_frame_title(self, row=0): """ @@ -390,9 +389,3 @@ class ServiceItem(object): Returns the title of the raw frame """ return self._raw_frames[row][u'path'] - - def clear_cache(self): - """ - Clear's the service item's cache. - """ - self.cache = {} diff --git a/openlp/core/lib/theme.py b/openlp/core/lib/theme.py index 9f084e40c..50894cc47 100644 --- a/openlp/core/lib/theme.py +++ b/openlp/core/lib/theme.py @@ -55,7 +55,6 @@ BLANK_THEME_XML = \ 30 Normal False - 0 0 @@ -65,7 +64,6 @@ BLANK_THEME_XML = \ 12 Normal False - 0 0 @@ -184,7 +182,7 @@ class ThemeXML(object): self.child_element(background, u'filename', filename) def add_font(self, name, color, proportion, override, fonttype=u'main', - weight=u'Normal', italics=u'False', indentation=0, line_adjustment=0, + weight=u'Normal', italics=u'False', line_adjustment=0, xpos=0, ypos=0, width=0, height=0): """ Add a Font. @@ -210,9 +208,6 @@ class ThemeXML(object): ``italics`` Does the font render to italics Defaults to 0 Normal - ``indentation`` - Number of characters the wrap line is indented - ``xpos`` The X position of the text block. @@ -239,8 +234,6 @@ class ThemeXML(object): #Create italics name element self.child_element(background, u'italics', italics) #Create indentation name element - self.child_element(background, u'indentation', unicode(indentation)) - #Create indentation name element self.child_element( background, u'line_adjustment', unicode(line_adjustment)) diff --git a/openlp/core/theme/theme.py b/openlp/core/theme/theme.py index e5f3b4c4c..52dda1631 100644 --- a/openlp/core/theme/theme.py +++ b/openlp/core/theme/theme.py @@ -204,7 +204,7 @@ class Theme(object): val = element_text if (element.tag.find(u'Color') > 0 or (element.tag.find(u'BackgroundParameter') == 0 and - isinstance(int, val))): + isinstance(val, int))): # convert to a wx.Colour if not delphi_color_change: val = QtGui.QColor( diff --git a/openlp/core/ui/__init__.py b/openlp/core/ui/__init__.py index 60d6ea941..e6da7f975 100644 --- a/openlp/core/ui/__init__.py +++ b/openlp/core/ui/__init__.py @@ -27,6 +27,143 @@ The :mod:`ui` module provides the core user interface for OpenLP """ +# http://john.nachtimwald.com/2009/08/22/qplaintextedit-with-in-line-spell-check/ + +import re +import sys +try: + import enchant + enchant_available = True +except ImportError: + enchant_available = False + +from PyQt4 import QtCore, QtGui +from openlp.core.lib import html_expands, translate, context_menu_action + +class SpellTextEdit(QtGui.QPlainTextEdit): + + def __init__(self, *args): + QtGui.QPlainTextEdit.__init__(self, *args) + # Default dictionary based on the current locale. + if enchant_available: + self.dict = enchant.Dict() + self.highlighter = Highlighter(self.document()) + self.highlighter.setDict(self.dict) + + def mousePressEvent(self, event): + if event.button() == QtCore.Qt.RightButton: + # Rewrite the mouse event to a left button event so the cursor is + # moved to the location of the pointer. + event = QtGui.QMouseEvent(QtCore.QEvent.MouseButtonPress, event.pos(), + QtCore.Qt.LeftButton, QtCore.Qt.LeftButton, QtCore.Qt.NoModifier) + QtGui.QPlainTextEdit.mousePressEvent(self, event) + + def contextMenuEvent(self, event): + popup_menu = self.createStandardContextMenu() + + # Select the word under the cursor. + cursor = self.textCursor() + cursor.select(QtGui.QTextCursor.WordUnderCursor) + self.setTextCursor(cursor) + + # Check if the selected word is misspelled and offer spelling + # suggestions if it is. + if enchant_available and self.textCursor().hasSelection(): + text = unicode(self.textCursor().selectedText()) + if not self.dict.check(text): + spell_menu = QtGui.QMenu(translate('OpenLP.SpellTextEdit', + 'Spelling Suggestions')) + for word in self.dict.suggest(text): + action = SpellAction(word, spell_menu) + action.correct.connect(self.correctWord) + spell_menu.addAction(action) + # Only add the spelling suggests to the menu if there are + # suggestions. + if len(spell_menu.actions()) != 0: + popup_menu.insertSeparator(popup_menu.actions()[0]) + popup_menu.insertMenu(popup_menu.actions()[0], spell_menu) + tag_menu = QtGui.QMenu(translate('OpenLP.SpellTextEdit', + 'Formatting Tags')) + for html in html_expands: + action = SpellAction( html[u'desc'], tag_menu) + action.correct.connect(self.htmlTag) + tag_menu.addAction(action) + popup_menu.insertSeparator(popup_menu.actions()[0]) + popup_menu.insertMenu(popup_menu.actions()[0], tag_menu) + + popup_menu.exec_(event.globalPos()) + + def correctWord(self, word): + ''' + Replaces the selected text with word. + ''' + cursor = self.textCursor() + cursor.beginEditBlock() + + cursor.removeSelectedText() + cursor.insertText(word) + + cursor.endEditBlock() + + def htmlTag(self, tag): + ''' + Replaces the selected text with word. + ''' + for html in html_expands: + if tag == html[u'desc']: + cursor = self.textCursor() + if self.textCursor().hasSelection(): + text = cursor.selectedText() + cursor.beginEditBlock() + cursor.removeSelectedText() + cursor.insertText(html[u'start tag']) + cursor.insertText(text) + cursor.insertText(html[u'end tag']) + cursor.endEditBlock() + else: + cursor = self.textCursor() + cursor.insertText(html[u'start tag']) + cursor.insertText(html[u'end tag']) + +class Highlighter(QtGui.QSyntaxHighlighter): + + WORDS = u'(?iu)[\w\']+' + + def __init__(self, *args): + QtGui.QSyntaxHighlighter.__init__(self, *args) + + self.dict = None + + def setDict(self, dict): + self.dict = dict + + def highlightBlock(self, text): + if not self.dict: + return + + text = unicode(text) + + format = QtGui.QTextCharFormat() + format.setUnderlineColor(QtCore.Qt.red) + format.setUnderlineStyle(QtGui.QTextCharFormat.SpellCheckUnderline) + + for word_object in re.finditer(self.WORDS, text): + if not self.dict.check(word_object.group()): + self.setFormat(word_object.start(), + word_object.end() - word_object.start(), format) + +class SpellAction(QtGui.QAction): + ''' + A special QAction that returns the text in a signal. + ''' + correct = QtCore.pyqtSignal(unicode) + + def __init__(self, *args): + QtGui.QAction.__init__(self, *args) + + self.triggered.connect(lambda x: self.correct.emit( + unicode(self.text()))) + class HideMode(object): """ This is basically an enumeration class which specifies the mode of a Bible. @@ -37,13 +174,11 @@ class HideMode(object): Theme = 2 Screen = 3 +from maindisplay import MainDisplay from slidecontroller import HideMode from servicenoteform import ServiceNoteForm from serviceitemeditform import ServiceItemEditForm from screen import ScreenList -from maindisplay import MainDisplay -from maindisplay import VideoDisplay -from maindisplay import DisplayManager from amendthemeform import AmendThemeForm from slidecontroller import SlideController from splashscreen import SplashScreen @@ -56,8 +191,7 @@ from settingsform import SettingsForm from mediadockmanager import MediaDockManager from servicemanager import ServiceManager from thememanager import ThemeManager -from mainwindow import MainWindow -__all__ = ['SplashScreen', 'AboutForm', 'SettingsForm', 'MainWindow', +__all__ = ['SplashScreen', 'AboutForm', 'SettingsForm', 'MainDisplay', 'SlideController', 'ServiceManager', 'ThemeManager', 'AmendThemeForm', 'MediaDockManager', 'ServiceItemEditForm'] diff --git a/openlp/core/ui/amendthemedialog.py b/openlp/core/ui/amendthemedialog.py index 893b5e32d..b77c9cfff 100644 --- a/openlp/core/ui/amendthemedialog.py +++ b/openlp/core/ui/amendthemedialog.py @@ -68,17 +68,6 @@ class Ui_AmendThemeDialog(object): self.backgroundLayout.setMargin(8) self.backgroundLayout.setSpacing(8) self.backgroundLayout.setObjectName(u'backgroundLayout') - self.backgroundLabel = QtGui.QLabel(self.backgroundTab) - self.backgroundLabel.setObjectName(u'backgroundLabel') - self.backgroundLayout.setWidget(0, QtGui.QFormLayout.LabelRole, - self.backgroundLabel) - self.backgroundComboBox = QtGui.QComboBox(self.backgroundTab) - self.backgroundComboBox.setObjectName(u'backgroundComboBox') - self.backgroundLabel.setBuddy(self.backgroundComboBox) - self.backgroundComboBox.addItem(QtCore.QString()) - self.backgroundComboBox.addItem(QtCore.QString()) - self.backgroundLayout.setWidget(0, QtGui.QFormLayout.FieldRole, - self.backgroundComboBox) self.backgroundTypeLabel = QtGui.QLabel(self.backgroundTab) self.backgroundTypeLabel.setObjectName(u'backgroundTypeLabel') self.backgroundLayout.setWidget(1, QtGui.QFormLayout.LabelRole, @@ -216,17 +205,6 @@ class Ui_AmendThemeDialog(object): self.fontMainLineAdjustmentSpinBox.setMinimum(-99) self.mainFontLayout.setWidget(4, QtGui.QFormLayout.FieldRole, self.fontMainLineAdjustmentSpinBox) - self.fontMainWrapIndentationLabel = QtGui.QLabel(self.fontMainGroupBox) - self.fontMainWrapIndentationLabel.setObjectName( - u'fontMainWrapIndentationLabel') - self.mainFontLayout.setWidget(5, QtGui.QFormLayout.LabelRole, - self.fontMainWrapIndentationLabel) - self.fontMainLineSpacingSpinBox = QtGui.QSpinBox(self.fontMainGroupBox) - self.fontMainLineSpacingSpinBox.setObjectName( - u'fontMainLineSpacingSpinBox') - self.fontMainLineSpacingSpinBox.setMaximum(10) - self.mainFontLayout.setWidget(5, QtGui.QFormLayout.FieldRole, - self.fontMainLineSpacingSpinBox) self.fontMainLinesPageLabel = QtGui.QLabel(self.fontMainGroupBox) self.fontMainLinesPageLabel.setObjectName(u'fontMainLinesPageLabel') self.mainFontLayout.addRow(self.fontMainLinesPageLabel) @@ -661,12 +639,6 @@ class Ui_AmendThemeDialog(object): translate('OpenLP.AmendThemeForm', 'Theme Maintenance')) self.themeNameLabel.setText( translate('OpenLP.AmendThemeForm', 'Theme &name:')) - self.backgroundLabel.setText( - translate('OpenLP.AmendThemeForm', '&Visibility:')) - self.backgroundComboBox.setItemText(0, - translate('OpenLP.AmendThemeForm', 'Opaque')) - self.backgroundComboBox.setItemText(1, - translate('OpenLP.AmendThemeForm', 'Transparent')) self.backgroundTypeLabel.setText( translate('OpenLP.AmendThemeForm', 'Type:')) self.backgroundTypeComboBox.setItemText(0, @@ -700,8 +672,6 @@ class Ui_AmendThemeDialog(object): translate('OpenLP.AmendThemeForm', 'Size:')) self.fontMainSizeSpinBox.setSuffix( translate('OpenLP.AmendThemeForm', 'pt')) - self.fontMainWrapIndentationLabel.setText( - translate('OpenLP.AmendThemeForm', 'Wrap indentation:')) self.fontMainWrapLineAdjustmentLabel.setText( translate('OpenLP.AmendThemeForm', 'Adjust line spacing:')) self.fontMainWeightComboBox.setItemText(0, diff --git a/openlp/core/ui/amendthemeform.py b/openlp/core/ui/amendthemeform.py index cefd79df2..e23044a2b 100644 --- a/openlp/core/ui/amendthemeform.py +++ b/openlp/core/ui/amendthemeform.py @@ -50,7 +50,6 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog): self.path = None self.theme = ThemeXML() self.setupUi(self) - # define signals # Buttons QtCore.QObject.connect(self.color1PushButton, QtCore.SIGNAL(u'pressed()'), self.onColor1PushButtonClicked) @@ -68,8 +67,6 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog): QtCore.QObject.connect(self.imageToolButton, QtCore.SIGNAL(u'clicked()'), self.onImageToolButtonClicked) # Combo boxes - QtCore.QObject.connect(self.backgroundComboBox, - QtCore.SIGNAL(u'activated(int)'), self.onBackgroundComboBoxSelected) QtCore.QObject.connect(self.backgroundTypeComboBox, QtCore.SIGNAL(u'activated(int)'), self.onBackgroundTypeComboBoxSelected) @@ -109,9 +106,6 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog): QtCore.QObject.connect(self.fontMainLineAdjustmentSpinBox, QtCore.SIGNAL(u'editingFinished()'), self.onFontMainLineAdjustmentSpinBoxChanged) - QtCore.QObject.connect(self.fontMainLineSpacingSpinBox, - QtCore.SIGNAL(u'editingFinished()'), - self.onFontMainLineSpacingSpinBoxChanged) QtCore.QObject.connect(self.fontFooterXSpinBox, QtCore.SIGNAL(u'editingFinished()'), self.onFontFooterXSpinBoxChanged) @@ -151,30 +145,26 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog): new_theme.new_document(theme_name) save_from = None save_to = None - if self.theme.background_mode == u'transparent': - new_theme.add_background_transparent() + if self.theme.background_type == u'solid': + new_theme.add_background_solid( + unicode(self.theme.background_color)) + elif self.theme.background_type == u'gradient': + new_theme.add_background_gradient( + unicode(self.theme.background_startColor), + unicode(self.theme.background_endColor), + self.theme.background_direction) else: - if self.theme.background_type == u'solid': - new_theme.add_background_solid( - unicode(self.theme.background_color)) - elif self.theme.background_type == u'gradient': - new_theme.add_background_gradient( - unicode(self.theme.background_startColor), - unicode(self.theme.background_endColor), - self.theme.background_direction) - else: - filename = \ - os.path.split(unicode(self.theme.background_filename))[1] - new_theme.add_background_image(filename) - save_to = os.path.join(self.path, theme_name, filename) - save_from = self.theme.background_filename + filename = \ + os.path.split(unicode(self.theme.background_filename))[1] + new_theme.add_background_image(filename) + save_to = os.path.join(self.path, theme_name, filename) + save_from = self.theme.background_filename new_theme.add_font(unicode(self.theme.font_main_name), unicode(self.theme.font_main_color), unicode(self.theme.font_main_proportion), unicode(self.theme.font_main_override), u'main', unicode(self.theme.font_main_weight), unicode(self.theme.font_main_italics), - unicode(self.theme.font_main_indentation), unicode(self.theme.font_main_line_adjustment), unicode(self.theme.font_main_x), unicode(self.theme.font_main_y), @@ -186,7 +176,6 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog): unicode(self.theme.font_footer_override), u'footer', unicode(self.theme.font_footer_weight), unicode(self.theme.font_footer_italics), - 0, # indentation 0, # line adjustment unicode(self.theme.font_footer_x), unicode(self.theme.font_footer_y), @@ -230,7 +219,7 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog): self.previewTheme() # - #Main Font Tab + # Main Font Tab # def onFontMainComboBoxSelected(self): self.theme.font_main_name = self.fontMainComboBox.currentFont().family() @@ -283,8 +272,6 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog): self.fontMainHeightSpinBox.setValue(self.theme.font_main_height) self.fontMainLineAdjustmentSpinBox.setValue( self.theme.font_main_line_adjustment) - self.fontMainLineSpacingSpinBox.setValue( - self.theme.font_main_indentation) self.stateChanging(self.theme) self.previewTheme() @@ -310,20 +297,13 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog): self.fontMainLineAdjustmentSpinBox.value() self.previewTheme() - def onFontMainLineSpacingSpinBoxChanged(self): - if self.theme.font_main_indentation != \ - self.fontMainLineSpacingSpinBox.value(): - self.theme.font_main_indentation = \ - self.fontMainLineSpacingSpinBox.value() - self.previewTheme() - def onFontMainHeightSpinBoxChanged(self): if self.theme.font_main_height != self.fontMainHeightSpinBox.value(): self.theme.font_main_height = self.fontMainHeightSpinBox.value() self.previewTheme() # - #Footer Font Tab + # Footer Font Tab # def onFontFooterComboBoxSelected(self): self.theme.font_footer_name = \ @@ -404,20 +384,12 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog): self.previewTheme() # - #Background Tab + # Background Tab # def onGradientComboBoxSelected(self, currentIndex): self.setBackground(self.backgroundTypeComboBox.currentIndex(), currentIndex) - def onBackgroundComboBoxSelected(self, currentIndex): - if currentIndex == 0: # Opaque - self.theme.background_mode = u'opaque' - else: - self.theme.background_mode = u'transparent' - self.stateChanging(self.theme) - self.previewTheme() - def onBackgroundTypeComboBoxSelected(self, currentIndex): self.setBackground(currentIndex, self.gradientComboBox.currentIndex()) @@ -472,7 +444,7 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog): self.previewTheme() # - #Other Tab + # Other Tab # def onOutlineCheckBoxChanged(self, value): if value == 2: # checked @@ -537,16 +509,12 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog): self.previewTheme() # - #Local Methods + # Local Methods # def paintUi(self, theme): self.stateChanging(theme) self.themeNameEdit.setText(self.theme.theme_name) # Background Tab - if self.theme.background_mode == u'opaque': - self.backgroundComboBox.setCurrentIndex(0) - else: - self.backgroundComboBox.setCurrentIndex(1) self.imageLineEdit.setText(u'') if theme.background_type == u'solid': self.backgroundTypeComboBox.setCurrentIndex(0) @@ -576,8 +544,6 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog): self.fontMainWeightComboBox.setCurrentIndex(2) else: self.fontMainWeightComboBox.setCurrentIndex(3) - self.fontMainLineSpacingSpinBox.setValue( - self.theme.font_main_indentation) self.fontMainXSpinBox.setValue(self.theme.font_main_x) self.fontMainYSpinBox.setValue(self.theme.font_main_y) self.fontMainWidthSpinBox.setValue(self.theme.font_main_width) @@ -641,9 +607,15 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog): self.verticalComboBox.setCurrentIndex(self.theme.display_verticalAlign) def stateChanging(self, theme): - if theme.background_mode == u'transparent': - self.color1Label.setVisible(False) - self.color1PushButton.setVisible(False) + self.backgroundTypeComboBox.setVisible(True) + self.backgroundTypeLabel.setVisible(True) + if theme.background_type == u'solid': + self.color1PushButton.setStyleSheet( + u'background-color: %s' % unicode(theme.background_color)) + self.color1Label.setText( + translate('OpenLP.AmendThemeForm', 'Color:')) + self.color1Label.setVisible(True) + self.color1PushButton.setVisible(True) self.color2Label.setVisible(False) self.color2PushButton.setVisible(False) self.imageLabel.setVisible(False) @@ -651,53 +623,34 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog): self.imageFilenameWidget.setVisible(False) self.gradientLabel.setVisible(False) self.gradientComboBox.setVisible(False) - self.backgroundTypeComboBox.setVisible(False) - self.backgroundTypeLabel.setVisible(False) - else: - self.backgroundTypeComboBox.setVisible(True) - self.backgroundTypeLabel.setVisible(True) - if theme.background_type == u'solid': - self.color1PushButton.setStyleSheet( - u'background-color: %s' % unicode(theme.background_color)) - self.color1Label.setText( - translate('OpenLP.AmendThemeForm', 'Color:')) - self.color1Label.setVisible(True) - self.color1PushButton.setVisible(True) - self.color2Label.setVisible(False) - self.color2PushButton.setVisible(False) - self.imageLabel.setVisible(False) - self.imageLineEdit.setVisible(False) - self.imageFilenameWidget.setVisible(False) - self.gradientLabel.setVisible(False) - self.gradientComboBox.setVisible(False) - elif theme.background_type == u'gradient': - self.color1PushButton.setStyleSheet(u'background-color: %s' \ - % unicode(theme.background_startColor)) - self.color2PushButton.setStyleSheet(u'background-color: %s' \ - % unicode(theme.background_endColor)) - self.color1Label.setText( - translate('OpenLP.AmendThemeForm', 'First color:')) - self.color2Label.setText( - translate('OpenLP.AmendThemeForm', 'Second color:')) - self.color1Label.setVisible(True) - self.color1PushButton.setVisible(True) - self.color2Label.setVisible(True) - self.color2PushButton.setVisible(True) - self.imageLabel.setVisible(False) - self.imageLineEdit.setVisible(False) - self.imageFilenameWidget.setVisible(False) - self.gradientLabel.setVisible(True) - self.gradientComboBox.setVisible(True) - else: # must be image - self.color1Label.setVisible(False) - self.color1PushButton.setVisible(False) - self.color2Label.setVisible(False) - self.color2PushButton.setVisible(False) - self.imageLabel.setVisible(True) - self.imageLineEdit.setVisible(True) - self.imageFilenameWidget.setVisible(True) - self.gradientLabel.setVisible(False) - self.gradientComboBox.setVisible(False) + elif theme.background_type == u'gradient': + self.color1PushButton.setStyleSheet(u'background-color: %s' \ + % unicode(theme.background_startColor)) + self.color2PushButton.setStyleSheet(u'background-color: %s' \ + % unicode(theme.background_endColor)) + self.color1Label.setText( + translate('OpenLP.AmendThemeForm', 'First color:')) + self.color2Label.setText( + translate('OpenLP.AmendThemeForm', 'Second color:')) + self.color1Label.setVisible(True) + self.color1PushButton.setVisible(True) + self.color2Label.setVisible(True) + self.color2PushButton.setVisible(True) + self.imageLabel.setVisible(False) + self.imageLineEdit.setVisible(False) + self.imageFilenameWidget.setVisible(False) + self.gradientLabel.setVisible(True) + self.gradientComboBox.setVisible(True) + else: # must be image + self.color1Label.setVisible(False) + self.color1PushButton.setVisible(False) + self.color2Label.setVisible(False) + self.color2PushButton.setVisible(False) + self.imageLabel.setVisible(True) + self.imageLineEdit.setVisible(True) + self.imageFilenameWidget.setVisible(True) + self.gradientLabel.setVisible(False) + self.gradientComboBox.setVisible(False) if not theme.font_main_override: self.fontMainXSpinBox.setEnabled(False) self.fontMainYSpinBox.setEnabled(False) diff --git a/openlp/core/ui/generaltab.py b/openlp/core/ui/generaltab.py index faafb5223..bc050b6f9 100644 --- a/openlp/core/ui/generaltab.py +++ b/openlp/core/ui/generaltab.py @@ -390,26 +390,16 @@ class GeneralTab(SettingsTab): unicode(self.screens.current[u'size'].width())) self.overrideCheckBox.setChecked(settings.value(u'override position', QtCore.QVariant(False)).toBool()) - if self.overrideCheckBox.isChecked(): - self.customXValueEdit.setText(settings.value(u'x position', - QtCore.QVariant(self.screens.current[u'size'].x())).toString()) - self.customYValueEdit.setText(settings.value(u'y position', - QtCore.QVariant(self.screens.current[u'size'].y())).toString()) - self.customHeightValueEdit.setText( - settings.value(u'height', QtCore.QVariant( - self.screens.current[u'size'].height())).toString()) - self.customWidthValueEdit.setText( - settings.value(u'width', QtCore.QVariant( - self.screens.current[u'size'].width())).toString()) - else: - self.customXValueEdit.setText( - unicode(self.screens.current[u'size'].x())) - self.customYValueEdit.setText( - unicode(self.screens.current[u'size'].y())) - self.customHeightValueEdit.setText( - unicode(self.screens.current[u'size'].height())) - self.customWidthValueEdit.setText( - unicode(self.screens.current[u'size'].width())) + self.customXValueEdit.setText(settings.value(u'x position', + QtCore.QVariant(self.screens.current[u'size'].x())).toString()) + self.customYValueEdit.setText(settings.value(u'y position', + QtCore.QVariant(self.screens.current[u'size'].y())).toString()) + self.customHeightValueEdit.setText( + settings.value(u'height', QtCore.QVariant( + self.screens.current[u'size'].height())).toString()) + self.customWidthValueEdit.setText( + settings.value(u'width', QtCore.QVariant( + self.screens.current[u'size'].width())).toString()) settings.endGroup() self.customXValueEdit.setEnabled(self.overrideCheckBox.isChecked()) self.customYValueEdit.setEnabled(self.overrideCheckBox.isChecked()) @@ -436,10 +426,8 @@ class GeneralTab(SettingsTab): QtCore.QVariant(self.saveCheckServiceCheckBox.isChecked())) settings.setValue(u'auto preview', QtCore.QVariant(self.autoPreviewCheckBox.isChecked())) - settings.setValue(u'loop delay', + settings.setValue(u'loop delay', QtCore.QVariant(self.timeoutSpinBox.value())) - Receiver.send_message(u'slidecontroller_live_spin_delay', - self.timeoutSpinBox.value()) settings.setValue(u'ccli number', QtCore.QVariant(self.numberEdit.displayText())) settings.setValue(u'songselect username', @@ -459,17 +447,18 @@ class GeneralTab(SettingsTab): settings.endGroup() self.screens.display = self.displayOnMonitorCheck.isChecked() # Monitor Number has changed. + postUpdate = False if self.screens.monitor_number != self.monitorNumber: self.screens.monitor_number = self.monitorNumber self.screens.set_current_display(self.monitorNumber) - Receiver.send_message(u'config_screen_changed') - Receiver.send_message(u'config_updated') + postUpdate = True # On save update the screens as well - self.postSetUp() + self.postSetUp(postUpdate) - def postSetUp(self): + def postSetUp(self, postUpdate=False): """ - Apply settings after settings tab has loaded + Apply settings after settings tab has loaded and most of the + system so must be delayed """ Receiver.send_message(u'slidecontroller_live_spin_delay', self.timeoutSpinBox.value()) @@ -480,12 +469,15 @@ class GeneralTab(SettingsTab): int(self.customYValueEdit.text()), int(self.customWidthValueEdit.text()), int(self.customHeightValueEdit.text())) - if self.overrideCheckBox.isChecked(): - self.screens.set_override_display() - Receiver.send_message(u'config_screen_changed') - else: - self.screens.reset_current_display() - Receiver.send_message(u'config_screen_changed') + if self.overrideCheckBox.isChecked(): + self.screens.set_override_display() + else: + self.screens.reset_current_display() + # Order is important so be careful if you change + if self.overrideChanged or postUpdate: + Receiver.send_message(u'config_screen_changed') + Receiver.send_message(u'config_updated') + self.overrideChanged = False def onOverrideCheckBoxToggled(self, checked): """ diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index 169faa5a0..98b7a84ec 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -26,149 +26,46 @@ import logging import os -import time from PyQt4 import QtCore, QtGui, QtWebKit from PyQt4.phonon import Phonon -from openlp.core.lib import Receiver, resize_image +from openlp.core.lib import Receiver, resize_image, build_html, ServiceItem, \ + image_to_byte from openlp.core.ui import HideMode log = logging.getLogger(__name__) #http://www.steveheffernan.com/html5-video-player/demo-video-player.html -HTMLVIDEO = u""" - - - - - -