diff --git a/.bzrignore b/.bzrignore index 2620fea42..1d2bb8267 100644 --- a/.bzrignore +++ b/.bzrignore @@ -20,3 +20,4 @@ _eric4project openlp/core/resources.py.old *.qm resources/windows/warnOpenLP.txt +openlp.cfg diff --git a/openlp.pyw b/openlp.pyw index a39f6cf65..962109592 100755 --- a/openlp.pyw +++ b/openlp.pyw @@ -79,6 +79,8 @@ class OpenLP(QtGui.QApplication): class in order to provide the core of the application. """ + args = [] + def exec_(self): """ Override exec method to allow the shared memory to be released on exit @@ -92,7 +94,7 @@ class OpenLP(QtGui.QApplication): """ # On Windows, the args passed into the constructor are # ignored. Not very handy, so set the ones we want to use. - self.args = args + self.args.extend(args) # provide a listener for widgets to reqest a screen update. QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'openlp_process_events'), self.processEvents) @@ -125,6 +127,8 @@ class OpenLP(QtGui.QApplication): # now kill the splashscreen self.splash.finish(self.mainWindow) log.debug(u'Splashscreen closed') + # make sure Qt really display the splash screen + self.processEvents() self.mainWindow.repaint() self.processEvents() if not has_run_wizard: @@ -180,6 +184,18 @@ class OpenLP(QtGui.QApplication): """ self.restoreOverrideCursor() + def event(self, event): + """ + Enables direct file opening on OS X + """ + if event.type() == QtCore.QEvent.FileOpen: + file_name = event.file() + log.debug(u'Got open file event for %s!', file_name) + self.args.insert(0, unicode(file_name)) + return True + else: + return QtGui.QApplication.event(self, event) + def main(): """ The main function which parses command line options and then runs diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index 7fbd5243c..f83e92de7 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -205,7 +205,7 @@ def clean_tags(text): text = text.replace(u'
', u'\n') text = text.replace(u'{br}', u'\n') text = text.replace(u' ', u' ') - for tag in DisplayTags.get_html_tags(): + for tag in FormattingTags.get_html_tags(): text = text.replace(tag[u'start tag'], u'') text = text.replace(tag[u'end tag'], u'') return text @@ -214,7 +214,7 @@ def expand_tags(text): """ Expand tags HTML for display """ - for tag in DisplayTags.get_html_tags(): + for tag in FormattingTags.get_html_tags(): text = text.replace(tag[u'start tag'], tag[u'start html']) text = text.replace(tag[u'end tag'], tag[u'end html']) return text @@ -233,9 +233,9 @@ def check_directory_exists(dir): except IOError: pass -from listwidgetwithdnd import ListWidgetWithDnD -from displaytags import DisplayTags from eventreceiver import Receiver +from listwidgetwithdnd import ListWidgetWithDnD +from formattingtags import FormattingTags from spelltextedit import SpellTextEdit from settingsmanager import SettingsManager from plugin import PluginStatus, StringContent, Plugin diff --git a/openlp/core/lib/displaytags.py b/openlp/core/lib/formattingtags.py similarity index 76% rename from openlp/core/lib/displaytags.py rename to openlp/core/lib/formattingtags.py index 95ce13bda..ae9d5c1cf 100644 --- a/openlp/core/lib/displaytags.py +++ b/openlp/core/lib/formattingtags.py @@ -25,12 +25,12 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ -Provide Html Tag management and Display Tag access class +Provide HTML Tag management and Formatting Tag access class """ from openlp.core.lib import translate -class DisplayTags(object): +class FormattingTags(object): """ Static Class to HTML Tags to be access around the code the list is managed by the Options Tab. @@ -42,89 +42,93 @@ class DisplayTags(object): """ Provide access to the html_expands list. """ - return DisplayTags.html_expands + return FormattingTags.html_expands @staticmethod def reset_html_tags(): """ Resets the html_expands list. """ - DisplayTags.html_expands = [] + FormattingTags.html_expands = [] base_tags = [] # Append the base tags. # Hex Color tags from http://www.w3schools.com/html/html_colornames.asp - base_tags.append({u'desc': translate('OpenLP.DisplayTags', 'Red'), + base_tags.append({u'desc': translate('OpenLP.FormattingTags', 'Red'), u'start tag': u'{r}', u'start html': u'', u'end tag': u'{/r}', u'end html': u'', u'protected': True}) - base_tags.append({u'desc': translate('OpenLP.DisplayTags', 'Black'), + base_tags.append({u'desc': translate('OpenLP.FormattingTags', 'Black'), u'start tag': u'{b}', u'start html': u'', u'end tag': u'{/b}', u'end html': u'', u'protected': True}) - base_tags.append({u'desc': translate('OpenLP.DisplayTags', 'Blue'), + base_tags.append({u'desc': translate('OpenLP.FormattingTags', 'Blue'), u'start tag': u'{bl}', u'start html': u'', u'end tag': u'{/bl}', u'end html': u'', u'protected': True}) - base_tags.append({u'desc': translate('OpenLP.DisplayTags', 'Yellow'), + base_tags.append({u'desc': translate('OpenLP.FormattingTags', 'Yellow'), u'start tag': u'{y}', u'start html': u'', u'end tag': u'{/y}', u'end html': u'', u'protected': True}) - base_tags.append({u'desc': translate('OpenLP.DisplayTags', 'Green'), + base_tags.append({u'desc': translate('OpenLP.FormattingTags', 'Green'), u'start tag': u'{g}', u'start html': u'', u'end tag': u'{/g}', u'end html': u'', u'protected': True}) - base_tags.append({u'desc': translate('OpenLP.DisplayTags', 'Pink'), + base_tags.append({u'desc': translate('OpenLP.FormattingTags', 'Pink'), u'start tag': u'{pk}', u'start html': u'', u'end tag': u'{/pk}', u'end html': u'', u'protected': True}) - base_tags.append({u'desc': translate('OpenLP.DisplayTags', 'Orange'), + base_tags.append({u'desc': translate('OpenLP.FormattingTags', 'Orange'), u'start tag': u'{o}', u'start html': u'', u'end tag': u'{/o}', u'end html': u'', u'protected': True}) - base_tags.append({u'desc': translate('OpenLP.DisplayTags', 'Purple'), + base_tags.append({u'desc': translate('OpenLP.FormattingTags', 'Purple'), u'start tag': u'{pp}', u'start html': u'', u'end tag': u'{/pp}', u'end html': u'', u'protected': True}) - base_tags.append({u'desc': translate('OpenLP.DisplayTags', 'White'), + base_tags.append({u'desc': translate('OpenLP.FormattingTags', 'White'), u'start tag': u'{w}', u'start html': u'', u'end tag': u'{/w}', u'end html': u'', u'protected': True}) base_tags.append({ - u'desc': translate('OpenLP.DisplayTags', 'Superscript'), + u'desc': translate('OpenLP.FormattingTags', 'Superscript'), u'start tag': u'{su}', u'start html': u'', u'end tag': u'{/su}', u'end html': u'', u'protected': True}) - base_tags.append({u'desc': translate('OpenLP.DisplayTags', 'Subscript'), + base_tags.append({ + u'desc': translate('OpenLP.FormattingTags', 'Subscript'), u'start tag': u'{sb}', u'start html': u'', u'end tag': u'{/sb}', u'end html': u'', u'protected': True}) - base_tags.append({u'desc': translate('OpenLP.DisplayTags', 'Paragraph'), + base_tags.append({ + u'desc': translate('OpenLP.FormattingTags', 'Paragraph'), u'start tag': u'{p}', u'start html': u'

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

', u'protected': True}) - base_tags.append({u'desc': translate('OpenLP.DisplayTags', 'Bold'), + base_tags.append({u'desc': translate('OpenLP.FormattingTags', 'Bold'), u'start tag': u'{st}', u'start html': u'', u'end tag': u'{/st}', u'end html': u'', u'protected': True}) - base_tags.append({u'desc': translate('OpenLP.DisplayTags', 'Italics'), + base_tags.append({ + u'desc': translate('OpenLP.FormattingTags', 'Italics'), u'start tag': u'{it}', u'start html': u'', u'end tag': u'{/it}', u'end html': u'', u'protected': True}) - base_tags.append({u'desc': translate('OpenLP.DisplayTags', 'Underline'), + base_tags.append({ + u'desc': translate('OpenLP.FormattingTags', 'Underline'), u'start tag': u'{u}', u'start html': u'', u'end tag': u'{/u}', u'end html': u'', u'protected': True}) - base_tags.append({u'desc': translate('OpenLP.DisplayTags', 'Break'), + base_tags.append({u'desc': translate('OpenLP.FormattingTags', 'Break'), u'start tag': u'{br}', u'start html': u'
', u'end tag': u'', u'end html': u'', u'protected': True}) - DisplayTags.add_html_tags(base_tags) + FormattingTags.add_html_tags(base_tags) @staticmethod def add_html_tags(tags): """ Add a list of tags to the list """ - DisplayTags.html_expands.extend(tags) + FormattingTags.html_expands.extend(tags) @staticmethod def remove_html_tag(tag_id): """ Removes an individual html_expands tag. """ - DisplayTags.html_expands.pop(tag_id) + FormattingTags.html_expands.pop(tag_id) diff --git a/openlp/core/lib/htmlbuilder.py b/openlp/core/lib/htmlbuilder.py index 1f50e0d65..6f56cf8b2 100644 --- a/openlp/core/lib/htmlbuilder.py +++ b/openlp/core/lib/htmlbuilder.py @@ -34,6 +34,7 @@ from openlp.core.lib.theme import BackgroundType, BackgroundGradientType, \ log = logging.getLogger(__name__) +# FIXME: Add html5 doctype. However, do not break theme gradients. HTMLSRC = u""" @@ -56,44 +57,44 @@ body { height: %spx; } #black { - z-index:8; + z-index: 8; background-color: black; display: none; } #bgimage { - z-index:1; + z-index: 1; } #image { - z-index:2; + z-index: 2; } #video1 { - z-index:3; + z-index: 3; } #video2 { - z-index:3; + z-index: 3; } #alert { position: absolute; left: 0px; top: 0px; - z-index:10; + z-index: 10; %s } #footer { position: absolute; - z-index:6; + z-index: 6; %s } /* lyric css */ %s sup { - font-size:0.6em; - vertical-align:top; - position:relative; - top:-0.3em; + font-size: 0.6em; + vertical-align: top; + position: relative; + top: -0.3em; } - +
""" % \ (build_lyrics_format_css(self.theme_data, self.page_width, self.page_height), build_lyrics_outline_css(self.theme_data)) + self.web.setHtml(html) - def _paginate_slide(self, lines, line_break, force_page=False): + def _paginate_slide(self, lines, line_end): """ Figure out how much text can appear on a slide, using the current theme settings. + **Note:** The smallest possible "unit" of text for a slide is one line. + If the line is too long it will be cut off when displayed. ``lines`` - The words to be fitted on the slide split into lines. - - ``line_break`` - Add line endings after each line of text (used for bibles). - - ``force_page`` - Flag to tell message lines in page. + The text to be fitted on the slide split into lines. + ``line_end`` + The text added after each line. Either ``u' '`` or ``u'
``. """ log.debug(u'_paginate_slide - Start') - line_end = u'' - if line_break: - line_end = u'
' - formatted = [] - html_text = u'' - styled_text = u'' - line_count = 0 - for line in lines: - if line_count != -1: - line_count += 1 - styled_line = expand_tags(line) + line_end - styled_text += styled_line - html = self.page_shell + styled_text + HTML_END - self.web.setHtml(html) - # Text too long so go to next page. - if self.web_frame.contentsSize().height() > self.page_height: - if force_page and line_count > 0: - Receiver.send_message(u'theme_line_count', line_count - 1) - line_count = -1 - while html_text.endswith(u'
'): - html_text = html_text[:-4] - formatted.append(html_text) - html_text = u'' - styled_text = styled_line - html_text += line + line_end - while html_text.endswith(u'
'): - html_text = html_text[:-4] - formatted.append(html_text) - log.debug(u'_paginate_slide - End') - return formatted - - def _paginate_slide_words(self, text, line_break): - """ - Figure out how much text can appear on a slide, using the current - theme settings. This version is to handle text which needs to be split - into words to get it to fit. - - ``text`` - The words to be fitted on the slide split into lines. - - ``line_break`` - Add line endings after each line of text used for bibles. - - """ - log.debug(u'_paginate_slide_words - Start') - line_end = u' ' - if line_break: - line_end = u'
' formatted = [] previous_html = u'' previous_raw = u'' - lines = text.split(u'\n') + separator = u'
' + html_lines = map(expand_tags, lines) + # Text too long so go to next page. + if self._text_fits_on_slide(separator.join(html_lines)): + html_text, previous_raw = self._binary_chop(formatted, + previous_html, previous_raw, html_lines, lines, separator, u'') + else: + previous_raw = separator.join(lines) + if previous_raw: + formatted.append(previous_raw) + log.debug(u'_paginate_slide - End') + return formatted + + def _paginate_slide_words(self, lines, line_end): + """ + Figure out how much text can appear on a slide, using the current + theme settings. + **Note:** The smallest possible "unit" of text for a slide is one word. + If one line is too long it will be processed word by word. This is + sometimes need for **bible** verses. + + ``lines`` + The text to be fitted on the slide split into lines. + + ``line_end`` + The text added after each line. Either ``u' '`` or ``u'
``. + This is needed for **bibles**. + """ + log.debug(u'_paginate_slide_words - Start') + formatted = [] + previous_html = u'' + previous_raw = u'' for line in lines: line = line.strip() - styled_line = expand_tags(line) - html = self.page_shell + previous_html + styled_line + HTML_END - self.web.setHtml(html) + html_line = expand_tags(line) # Text too long so go to next page. - if self.web_frame.contentsSize().height() > self.page_height: + if self._text_fits_on_slide(previous_html + html_line): # Check if there was a verse before the current one and append # it, when it fits on the page. if previous_html: - html = self.page_shell + previous_html + HTML_END - self.web.setHtml(html) - if self.web_frame.contentsSize().height() <= \ - self.page_height: - while previous_raw.endswith(u'
'): - previous_raw = previous_raw[:-4] + if not self._text_fits_on_slide(previous_html): formatted.append(previous_raw) previous_html = u'' previous_raw = u'' - html = self.page_shell + styled_line + HTML_END - self.web.setHtml(html) # Now check if the current verse will fit, if it does # not we have to start to process the verse word by # word. - if self.web_frame.contentsSize().height() <= \ - self.page_height: - previous_html = styled_line + line_end + if not self._text_fits_on_slide(html_line): + previous_html = html_line + line_end previous_raw = line + line_end continue - # Figure out how many words of the line will fit on screen by - # using the algorithm known as "binary chop". + # Figure out how many words of the line will fit on screen as + # the line will not fit as a whole. raw_words = self._words_split(line) - html_words = [expand_tags(word) for word in raw_words] - smallest_index = 0 - highest_index = len(html_words) - 1 - index = int(highest_index / 2) - while True: - html = self.page_shell + previous_html + \ - u''.join(html_words[:index + 1]).strip() + HTML_END - self.web.setHtml(html) - if self.web_frame.contentsSize().height() > \ - self.page_height: - # We know that it does not fit, so change/calculate the - # new index and highest_index accordingly. - highest_index = index - index = int(index - (index - smallest_index) / 2) - else: - smallest_index = index - index = int(index + (highest_index - index) / 2) - # We found the number of words which will fit. - if smallest_index == index or highest_index == index: - index = smallest_index - formatted.append(previous_raw.rstrip(u'
') + - u''.join(raw_words[:index + 1])) - previous_html = u'' - previous_raw = u'' - else: - continue - # Check if the rest of the line fits on the slide. If it - # does we do not have to do the much more intensive "word by - # word" checking. - html = self.page_shell + \ - u''.join(html_words[index + 1:]).strip() + HTML_END - self.web.setHtml(html) - if self.web_frame.contentsSize().height() <= \ - self.page_height: - previous_html = \ - u''.join(html_words[index + 1:]).strip() + line_end - previous_raw = \ - u''.join(raw_words[index + 1:]).strip() + line_end - break - else: - # The other words do not fit, thus reset the indexes, - # create a new list and continue with "word by word". - raw_words = raw_words[index + 1:] - html_words = html_words[index + 1:] - smallest_index = 0 - highest_index = len(html_words) - 1 - index = int(highest_index / 2) + html_words = map(expand_tags, raw_words) + previous_html, previous_raw = self._binary_chop( + formatted, previous_html, previous_raw, html_words, + raw_words, u' ', line_end) else: - previous_html += styled_line + line_end + previous_html += html_line + line_end previous_raw += line + line_end - while previous_raw.endswith(u'
'): - previous_raw = previous_raw[:-4] formatted.append(previous_raw) log.debug(u'_paginate_slide_words - End') return formatted + def _binary_chop(self, formatted, previous_html, previous_raw, html_list, + raw_list, separator, line_end): + """ + This implements the binary chop algorithm for faster rendering. This + algorithm works line based (line by line) and word based (word by word). + It is assumed that this method is **only** called, when the lines/words + to be rendered do **not** fit as a whole. + + ``formatted`` + The list to append any slides. + + ``previous_html`` + The html text which is know to fit on a slide, but is not yet added + to the list of slides. (unicode string) + + ``previous_raw`` + The raw text (with formatting tags) which is know to fit on a slide, + but is not yet added to the list of slides. (unicode string) + + ``html_list`` + The elements which do not fit on a slide and needs to be processed + using the binary chop. The text contains html. + + ``raw_list`` + The elements which do not fit on a slide and needs to be processed + using the binary chop. The elements can contain formatting tags. + + ``separator`` + The separator for the elements. For lines this is ``u'
'`` and + for words this is ``u' '``. + + ``line_end`` + The text added after each "element line". Either ``u' '`` or + ``u'
``. This is needed for bibles. + """ + smallest_index = 0 + highest_index = len(html_list) - 1 + index = int(highest_index / 2) + while True: + if self._text_fits_on_slide( + previous_html + separator.join(html_list[:index + 1]).strip()): + # We know that it does not fit, so change/calculate the + # new index and highest_index accordingly. + highest_index = index + index = int(index - (index - smallest_index) / 2) + else: + smallest_index = index + index = int(index + (highest_index - index) / 2) + # We found the number of words which will fit. + if smallest_index == index or highest_index == index: + index = smallest_index + formatted.append(previous_raw.rstrip(u'
') + + separator.join(raw_list[:index + 1])) + previous_html = u'' + previous_raw = u'' + # Stop here as the theme line count was requested. + if self.force_page: + Receiver.send_message(u'theme_line_count', index + 1) + break + else: + continue + # Check if the remaining elements fit on the slide. + if not self._text_fits_on_slide( + separator.join(html_list[index + 1:]).strip()): + previous_html = separator.join( + html_list[index + 1:]).strip() + line_end + previous_raw = separator.join( + raw_list[index + 1:]).strip() + line_end + break + else: + # The remaining elements do not fit, thus reset the indexes, + # create a new list and continue. + raw_list = raw_list[index + 1:] + html_list = html_list[index + 1:] + smallest_index = 0 + highest_index = len(html_list) - 1 + index = int(highest_index / 2) + return previous_html, previous_raw + + def _text_fits_on_slide(self, text): + """ + Checks if the given ``text`` fits on a slide. If it does ``True`` is + returned, otherwise ``False``. + + ``text`` + The text to check. It can contain HTML tags. + """ + self.web_frame.evaluateJavaScript(u'show_text("%s")' % + text.replace(u'\\', u'\\\\').replace(u'\"', u'\\\"')) + return self.web_frame.contentsSize().height() > self.page_height + def _words_split(self, line): """ Split the slide up by word so can wrap better """ # this parse we are to be wordy line = line.replace(u'\n', u' ') - words = line.split(u' ') - return [word + u' ' for word in words] + return line.split(u' ') def _lines_split(self, text): """ @@ -479,5 +508,5 @@ class Renderer(object): """ # this parse we do not want to use this so remove it text = text.replace(u'\n[---]', u'') - lines = text.split(u'\n') - return [line.replace(u'[---]', u'') for line in lines] + text = text.replace(u'[---]', u'') + return text.split(u'\n') diff --git a/openlp/core/lib/serviceitem.py b/openlp/core/lib/serviceitem.py index ad762e326..15c16c551 100644 --- a/openlp/core/lib/serviceitem.py +++ b/openlp/core/lib/serviceitem.py @@ -35,8 +35,7 @@ import logging import os import uuid -from openlp.core.lib import build_icon, clean_tags, expand_tags -from openlp.core.lib.ui import UiStrings +from openlp.core.lib import build_icon, clean_tags, expand_tags, translate log = logging.getLogger(__name__) @@ -165,7 +164,6 @@ class ServiceItem(object): log.debug(u'Render called') self._display_frames = [] self.bg_image_bytes = None - line_break = not self.is_capable(ItemCapabilities.NoLineBreaks) theme = self.theme if self.theme else None self.main, self.footer = \ self.renderer.set_override_theme(theme, use_override) @@ -173,9 +171,8 @@ class ServiceItem(object): if self.service_item_type == ServiceItemType.Text: log.debug(u'Formatting slides') for slide in self._raw_frames: - formatted = self.renderer \ - .format_slide(slide[u'raw_slide'], line_break, self) - for page in formatted: + pages = self.renderer.format_slide(slide[u'raw_slide'], self) + for page in pages: page = page.replace(u'
', u'{br}') html = expand_tags(cgi.escape(page.rstrip())) self._display_frames.append({ @@ -210,7 +207,7 @@ class ServiceItem(object): """ self.service_item_type = ServiceItemType.Image self._raw_frames.append({u'title': title, u'path': path}) - self.renderer.image_manager.add_image(title, path) + self.renderer.imageManager.add_image(title, path) self._new_item() def add_from_text(self, title, raw_slide, verse_tag=None): @@ -352,6 +349,9 @@ class ServiceItem(object): Updates the _uuid with the value from the original one The _uuid is unique for a given service item but this allows one to replace an original version. + + ``other`` + The service item to be merged with """ self._uuid = other._uuid self.notes = other.notes @@ -447,10 +447,12 @@ class ServiceItem(object): start = None end = None if self.start_time != 0: - start = UiStrings().StartTimeCode % \ + start = unicode(translate('OpenLP.ServiceItem', + 'Start: %s')) % \ unicode(datetime.timedelta(seconds=self.start_time)) if self.media_length != 0: - end = UiStrings().LengthTime % \ + end = unicode(translate('OpenLP.ServiceItem', + 'Length: %s')) % \ unicode(datetime.timedelta(seconds=self.media_length)) if not start and not end: return None @@ -459,5 +461,16 @@ class ServiceItem(object): elif not start and end: return end else: - return u'%s : %s' % (start, end) + return u'%s
%s' % (start, end) + + def update_theme(self, theme): + """ + updates the theme in the service item + + ``theme`` + The new theme to be replaced in the service item + """ + self.theme = theme + self._new_item() + self.render() diff --git a/openlp/core/lib/spelltextedit.py b/openlp/core/lib/spelltextedit.py index 57a176a69..25e4e24ae 100644 --- a/openlp/core/lib/spelltextedit.py +++ b/openlp/core/lib/spelltextedit.py @@ -39,7 +39,7 @@ except ImportError: from PyQt4 import QtCore, QtGui -from openlp.core.lib import translate, DisplayTags +from openlp.core.lib import translate, FormattingTags from openlp.core.lib.ui import checkable_action log = logging.getLogger(__name__) @@ -48,16 +48,17 @@ class SpellTextEdit(QtGui.QPlainTextEdit): """ Spell checking widget based on QPlanTextEdit. """ - def __init__(self, *args): + def __init__(self, parent=None, formattingTagsAllowed=True): global ENCHANT_AVAILABLE - QtGui.QPlainTextEdit.__init__(self, *args) + QtGui.QPlainTextEdit.__init__(self, parent) + self.formattingTagsAllowed = formattingTagsAllowed # Default dictionary based on the current locale. if ENCHANT_AVAILABLE: try: self.dictionary = enchant.Dict() self.highlighter = Highlighter(self.document()) self.highlighter.spellingDictionary = self.dictionary - except Error, DictNotFoundError: + except (Error, DictNotFoundError): ENCHANT_AVAILABLE = False log.debug(u'Could not load default dictionary') @@ -110,16 +111,17 @@ class SpellTextEdit(QtGui.QPlainTextEdit): spell_menu.addAction(action) # Only add the spelling suggests to the menu if there are # suggestions. - if len(spell_menu.actions()): + if spell_menu.actions(): popupMenu.insertMenu(popupMenu.actions()[0], spell_menu) tagMenu = QtGui.QMenu(translate('OpenLP.SpellTextEdit', 'Formatting Tags')) - for html in DisplayTags.get_html_tags(): - action = SpellAction(html[u'desc'], tagMenu) - action.correct.connect(self.htmlTag) - tagMenu.addAction(action) - popupMenu.insertSeparator(popupMenu.actions()[0]) - popupMenu.insertMenu(popupMenu.actions()[0], tagMenu) + if self.formattingTagsAllowed: + for html in FormattingTags.get_html_tags(): + action = SpellAction(html[u'desc'], tagMenu) + action.correct.connect(self.htmlTag) + tagMenu.addAction(action) + popupMenu.insertSeparator(popupMenu.actions()[0]) + popupMenu.insertMenu(popupMenu.actions()[0], tagMenu) popupMenu.exec_(event.globalPos()) def setLanguage(self, action): @@ -148,7 +150,7 @@ class SpellTextEdit(QtGui.QPlainTextEdit): """ Replaces the selected text with word. """ - for html in DisplayTags.get_html_tags(): + for html in FormattingTags.get_html_tags(): if tag == html[u'desc']: cursor = self.textCursor() if self.textCursor().hasSelection(): diff --git a/openlp/core/lib/theme.py b/openlp/core/lib/theme.py index 7ad50490d..c87f9aa2e 100644 --- a/openlp/core/lib/theme.py +++ b/openlp/core/lib/theme.py @@ -34,8 +34,7 @@ import logging from xml.dom.minidom import Document from lxml import etree, objectify -from openlp.core.lib import str_to_bool, translate -from openlp.core.lib.ui import UiStrings +from openlp.core.lib import str_to_bool log = logging.getLogger(__name__) diff --git a/openlp/core/lib/ui.py b/openlp/core/lib/ui.py index 5d20b212d..756df36c3 100644 --- a/openlp/core/lib/ui.py +++ b/openlp/core/lib/ui.py @@ -64,6 +64,7 @@ class UiStrings(object): self.Cancel = translate('OpenLP.Ui', 'Cancel') self.CCLINumberLabel = translate('OpenLP.Ui', 'CCLI number:') self.CreateService = translate('OpenLP.Ui', 'Create a new service.') + self.ConfirmDelete = translate('OpenLP.Ui', 'Confirm Delete') self.Continuous = translate('OpenLP.Ui', 'Continuous') self.Default = unicode(translate('OpenLP.Ui', 'Default')) self.Delete = translate('OpenLP.Ui', '&Delete') @@ -82,7 +83,6 @@ class UiStrings(object): self.Image = translate('OpenLP.Ui', 'Image') self.Import = translate('OpenLP.Ui', 'Import') self.LayoutStyle = translate('OpenLP.Ui', 'Layout style:') - self.LengthTime = unicode(translate('OpenLP.Ui', 'Length %s')) self.Live = translate('OpenLP.Ui', 'Live') self.LiveBGError = translate('OpenLP.Ui', 'Live Background Error') self.LiveToolbar = translate('OpenLP.Ui', 'Live Toolbar') @@ -102,6 +102,8 @@ class UiStrings(object): self.OpenLPStart = translate('OpenLP.Ui', 'OpenLP is already running. ' 'Do you wish to continue?') self.OpenService = translate('OpenLP.Ui', 'Open service.') + self.PlaySlidesInLoop = translate('OpenLP.Ui','Play Slides in Loop') + self.PlaySlidesToEnd = translate('OpenLP.Ui','Play Slides to End') self.Preview = translate('OpenLP.Ui', 'Preview') self.PrintService = translate('OpenLP.Ui', 'Print Service') self.ReplaceBG = translate('OpenLP.Ui', 'Replace Background') @@ -123,6 +125,10 @@ class UiStrings(object): self.SplitToolTip = translate('OpenLP.Ui', 'Split a slide into two ' 'only if it does not fit on the screen as one slide.') self.StartTimeCode = unicode(translate('OpenLP.Ui', 'Start %s')) + self.StopPlaySlidesInLoop = translate('OpenLP.Ui', + 'Stop Play Slides in Loop') + self.StopPlaySlidesToEnd = translate('OpenLP.Ui', + 'Stop Play Slides to End') self.Theme = translate('OpenLP.Ui', 'Theme', 'Singular') self.Themes = translate('OpenLP.Ui', 'Themes', 'Plural') self.Tools = translate('OpenLP.Ui', 'Tools') @@ -323,8 +329,9 @@ def shortcut_action(parent, name, shortcuts, function, icon=None, checked=None, if checked is not None: action.setCheckable(True) action.setChecked(checked) - action.setShortcuts(shortcuts) - action.setShortcutContext(context) + if shortcuts: + action.setShortcuts(shortcuts) + action.setShortcutContext(context) action_list = ActionList.get_instance() action_list.add_action(action, category) QtCore.QObject.connect(action, QtCore.SIGNAL(u'triggered(bool)'), function) diff --git a/openlp/core/ui/__init__.py b/openlp/core/ui/__init__.py index 7aebcb1df..e754480e0 100644 --- a/openlp/core/ui/__init__.py +++ b/openlp/core/ui/__init__.py @@ -69,7 +69,7 @@ from advancedtab import AdvancedTab from aboutform import AboutForm from pluginform import PluginForm from settingsform import SettingsForm -from displaytagform import DisplayTagForm +from formattingtagform import FormattingTagForm from shortcutlistform import ShortcutListForm from mediadockmanager import MediaDockManager from servicemanager import ServiceManager diff --git a/openlp/core/ui/aboutform.py b/openlp/core/ui/aboutform.py index 2909503f6..4e031656c 100644 --- a/openlp/core/ui/aboutform.py +++ b/openlp/core/ui/aboutform.py @@ -61,6 +61,5 @@ class AboutForm(QtGui.QDialog, Ui_AboutDialog): Launch a web browser and go to the contribute page on the site. """ import webbrowser - url = u'http://www.openlp.org/en/documentation/introduction/' \ - + u'contributing.html' + url = u'http://openlp.org/en/documentation/introduction/contributing' webbrowser.open_new(url) diff --git a/openlp/core/ui/firsttimeform.py b/openlp/core/ui/firsttimeform.py index 8ffd87bcf..4beebfde6 100644 --- a/openlp/core/ui/firsttimeform.py +++ b/openlp/core/ui/firsttimeform.py @@ -129,6 +129,7 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard): os.path.join(gettempdir(), u'openlp', screenshot))) item.setCheckState(QtCore.Qt.Unchecked) item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable) + Receiver.send_message(u'cursor_normal') def nextId(self): """ @@ -156,10 +157,27 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard): item = self.themesListWidget.item(iter) if item.checkState() == QtCore.Qt.Checked: self.themeComboBox.addItem(item.text()) + # Check if this is a re-run of the wizard. + self.has_run_wizard = QtCore.QSettings().value( + u'general/has run wizard', QtCore.QVariant(False)).toBool() + if self.has_run_wizard: + # Add any existing themes to list. + for theme in self.parent().themeManagerContents.getThemes(): + index = self.themeComboBox.findText(theme) + if index == -1: + self.themeComboBox.addItem(theme) + default_theme = unicode(QtCore.QSettings().value( + u'themes/global theme', + QtCore.QVariant(u'')).toString()) + # Pre-select the current default theme. + index = self.themeComboBox.findText(default_theme) + self.themeComboBox.setCurrentIndex(index) elif pageId == FirstTimePage.Progress: + Receiver.send_message(u'cursor_busy') self._preWizard() self._performWizard() self._postWizard() + Receiver.send_message(u'cursor_normal') def updateScreenListCombo(self): """ @@ -248,11 +266,21 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard): """ if self.max_progress: self.progressBar.setValue(self.progressBar.maximum()) - self.progressLabel.setText(translate('OpenLP.FirstTimeWizard', - 'Download complete. Click the finish button to start OpenLP.')) + if self.has_run_wizard: + self.progressLabel.setText(translate('OpenLP.FirstTimeWizard', + 'Download complete.' + ' Click the finish button to return to OpenLP.')) + else: + self.progressLabel.setText(translate('OpenLP.FirstTimeWizard', + 'Download complete.' + ' Click the finish button to start OpenLP.')) else: - self.progressLabel.setText(translate('OpenLP.FirstTimeWizard', - 'Click the finish button to start OpenLP.')) + if self.has_run_wizard: + self.progressLabel.setText(translate('OpenLP.FirstTimeWizard', + 'Click the finish button to return to OpenLP.')) + else: + self.progressLabel.setText(translate('OpenLP.FirstTimeWizard', + 'Click the finish button to start OpenLP.')) self.finishButton.setVisible(True) self.finishButton.setEnabled(True) self.cancelButton.setVisible(False) diff --git a/openlp/core/ui/firsttimewizard.py b/openlp/core/ui/firsttimewizard.py index 3644ed07b..7e94b4595 100644 --- a/openlp/core/ui/firsttimewizard.py +++ b/openlp/core/ui/firsttimewizard.py @@ -49,7 +49,7 @@ class Ui_FirstTimeWizard(object): FirstTimeWizard.resize(550, 386) FirstTimeWizard.setModal(True) FirstTimeWizard.setWizardStyle(QtGui.QWizard.ModernStyle) - FirstTimeWizard.setOptions(QtGui.QWizard.IndependentPages| + FirstTimeWizard.setOptions(QtGui.QWizard.IndependentPages | QtGui.QWizard.NoBackButtonOnStartPage | QtGui.QWizard.NoBackButtonOnLastPage) self.finishButton = self.button(QtGui.QWizard.FinishButton) @@ -81,9 +81,9 @@ class Ui_FirstTimeWizard(object): self.pluginLayout.addWidget(self.imageCheckBox) self.presentationCheckBox = QtGui.QCheckBox(self.pluginPage) if sys.platform == "darwin": - self.presentationCheckBox.setChecked(False) + self.presentationCheckBox.setChecked(False) else: - self.presentationCheckBox.setChecked(True) + self.presentationCheckBox.setChecked(True) self.presentationCheckBox.setObjectName(u'presentationCheckBox') self.pluginLayout.addWidget(self.presentationCheckBox) self.mediaCheckBox = QtGui.QCheckBox(self.pluginPage) @@ -209,7 +209,7 @@ class Ui_FirstTimeWizard(object): 'Select the Plugins you wish to use. ')) self.songsCheckBox.setText(translate('OpenLP.FirstTimeWizard', 'Songs')) self.customCheckBox.setText(translate('OpenLP.FirstTimeWizard', - 'Custom Text')) + 'Custom Slides')) self.bibleCheckBox.setText(translate('OpenLP.FirstTimeWizard', 'Bible')) self.imageCheckBox.setText(translate('OpenLP.FirstTimeWizard', 'Images')) diff --git a/openlp/core/ui/displaytagdialog.py b/openlp/core/ui/formattingtagdialog.py similarity index 81% rename from openlp/core/ui/displaytagdialog.py rename to openlp/core/ui/formattingtagdialog.py index 65e900bbb..186f4739b 100644 --- a/openlp/core/ui/displaytagdialog.py +++ b/openlp/core/ui/formattingtagdialog.py @@ -28,17 +28,17 @@ from PyQt4 import QtCore, QtGui from openlp.core.lib import translate -from openlp.core.lib.ui import UiStrings, create_accept_reject_button_box +from openlp.core.lib.ui import UiStrings -class Ui_DisplayTagDialog(object): +class Ui_FormattingTagDialog(object): - def setupUi(self, displayTagDialog): - displayTagDialog.setObjectName(u'displayTagDialog') - displayTagDialog.resize(725, 548) - self.listdataGridLayout = QtGui.QGridLayout(displayTagDialog) + def setupUi(self, formattingTagDialog): + formattingTagDialog.setObjectName(u'formattingTagDialog') + formattingTagDialog.resize(725, 548) + self.listdataGridLayout = QtGui.QGridLayout(formattingTagDialog) self.listdataGridLayout.setMargin(8) self.listdataGridLayout.setObjectName(u'listdataGridLayout') - self.tagTableWidget = QtGui.QTableWidget(displayTagDialog) + self.tagTableWidget = QtGui.QTableWidget(formattingTagDialog) self.tagTableWidget.setHorizontalScrollBarPolicy( QtCore.Qt.ScrollBarAlwaysOff) self.tagTableWidget.setEditTriggers( @@ -67,11 +67,11 @@ class Ui_DisplayTagDialog(object): spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem) - self.deletePushButton = QtGui.QPushButton(displayTagDialog) + self.deletePushButton = QtGui.QPushButton(formattingTagDialog) self.deletePushButton.setObjectName(u'deletePushButton') self.horizontalLayout.addWidget(self.deletePushButton) self.listdataGridLayout.addLayout(self.horizontalLayout, 1, 0, 1, 1) - self.editGroupBox = QtGui.QGroupBox(displayTagDialog) + self.editGroupBox = QtGui.QGroupBox(formattingTagDialog) self.editGroupBox.setObjectName(u'editGroupBox') self.dataGridLayout = QtGui.QGridLayout(self.editGroupBox) self.dataGridLayout.setObjectName(u'dataGridLayout') @@ -112,38 +112,38 @@ class Ui_DisplayTagDialog(object): self.savePushButton.setObjectName(u'savePushButton') self.dataGridLayout.addWidget(self.savePushButton, 4, 2, 1, 1) self.listdataGridLayout.addWidget(self.editGroupBox, 2, 0, 1, 1) - self.buttonBox = QtGui.QDialogButtonBox(displayTagDialog) - self.buttonBox.setObjectName('displayTagDialogButtonBox') + self.buttonBox = QtGui.QDialogButtonBox(formattingTagDialog) + self.buttonBox.setObjectName('formattingTagDialogButtonBox') self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Close) self.listdataGridLayout.addWidget(self.buttonBox, 3, 0, 1, 1) - self.retranslateUi(displayTagDialog) - QtCore.QMetaObject.connectSlotsByName(displayTagDialog) + self.retranslateUi(formattingTagDialog) + QtCore.QMetaObject.connectSlotsByName(formattingTagDialog) - def retranslateUi(self, displayTagDialog): - displayTagDialog.setWindowTitle(translate('OpenLP.displayTagDialog', - 'Configure Display Tags')) + def retranslateUi(self, formattingTagDialog): + formattingTagDialog.setWindowTitle(translate( + 'OpenLP.FormattingTagDialog', 'Configure Formatting Tags')) self.editGroupBox.setTitle( - translate('OpenLP.DisplayTagDialog', 'Edit Selection')) + translate('OpenLP.FormattingTagDialog', 'Edit Selection')) self.savePushButton.setText( - translate('OpenLP.DisplayTagDialog', 'Save')) + translate('OpenLP.FormattingTagDialog', 'Save')) self.descriptionLabel.setText( - translate('OpenLP.DisplayTagDialog', 'Description')) - self.tagLabel.setText(translate('OpenLP.DisplayTagDialog', 'Tag')) + translate('OpenLP.FormattingTagDialog', 'Description')) + self.tagLabel.setText(translate('OpenLP.FormattingTagDialog', 'Tag')) self.startTagLabel.setText( - translate('OpenLP.DisplayTagDialog', 'Start tag')) + translate('OpenLP.FormattingTagDialog', 'Start tag')) self.endTagLabel.setText( - translate('OpenLP.DisplayTagDialog', 'End tag')) + translate('OpenLP.FormattingTagDialog', 'End tag')) self.deletePushButton.setText(UiStrings().Delete) self.newPushButton.setText(UiStrings().New) self.tagTableWidget.horizontalHeaderItem(0).setText( - translate('OpenLP.DisplayTagDialog', 'Description')) + translate('OpenLP.FormattingTagDialog', 'Description')) self.tagTableWidget.horizontalHeaderItem(1).setText( - translate('OpenLP.DisplayTagDialog', 'Tag Id')) + translate('OpenLP.FormattingTagDialog', 'Tag Id')) self.tagTableWidget.horizontalHeaderItem(2).setText( - translate('OpenLP.DisplayTagDialog', 'Start HTML')) + translate('OpenLP.FormattingTagDialog', 'Start HTML')) self.tagTableWidget.horizontalHeaderItem(3).setText( - translate('OpenLP.DisplayTagDialog', 'End HTML')) + translate('OpenLP.FormattingTagDialog', 'End HTML')) self.tagTableWidget.setColumnWidth(0, 120) self.tagTableWidget.setColumnWidth(1, 80) self.tagTableWidget.setColumnWidth(2, 330) diff --git a/openlp/core/ui/displaytagform.py b/openlp/core/ui/formattingtagform.py similarity index 82% rename from openlp/core/ui/displaytagform.py rename to openlp/core/ui/formattingtagform.py index 22ac38f06..2a8625b1a 100644 --- a/openlp/core/ui/displaytagform.py +++ b/openlp/core/ui/formattingtagform.py @@ -25,22 +25,22 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ -The :mod:`DisplayTagTab` provides an Tag Edit facility. The Base set are -protected and included each time loaded. Custom tags can be defined and saved. -The Custom Tag arrays are saved in a pickle so QSettings works on them. Base +The :mod:`formattingtagform` provides an Tag Edit facility. The Base set are +protected and included each time loaded. Custom tags can be defined and saved. +The Custom Tag arrays are saved in a pickle so QSettings works on them. Base Tags cannot be changed. """ import cPickle from PyQt4 import QtCore, QtGui -from openlp.core.lib import translate, DisplayTags +from openlp.core.lib import translate, FormattingTags from openlp.core.lib.ui import critical_error_message_box -from openlp.core.ui.displaytagdialog import Ui_DisplayTagDialog +from openlp.core.ui.formattingtagdialog import Ui_FormattingTagDialog -class DisplayTagForm(QtGui.QDialog, Ui_DisplayTagDialog): +class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog): """ - The :class:`DisplayTagTab` manages the settings tab . + The :class:`FormattingTagForm` manages the settings tab . """ def __init__(self, parent): """ @@ -48,7 +48,7 @@ class DisplayTagForm(QtGui.QDialog, Ui_DisplayTagDialog): """ QtGui.QDialog.__init__(self, parent) self.setupUi(self) - self._loadDisplayTags() + self._loadFormattingTags() QtCore.QObject.connect(self.tagTableWidget, QtCore.SIGNAL(u'clicked(QModelIndex)'), self.onRowSelected) QtCore.QObject.connect(self.newPushButton, @@ -65,19 +65,20 @@ class DisplayTagForm(QtGui.QDialog, Ui_DisplayTagDialog): Load Display and set field state. """ # Create initial copy from master - self._loadDisplayTags() + self._loadFormattingTags() self._resetTable() self.selected = -1 return QtGui.QDialog.exec_(self) - def _loadDisplayTags(self): + def _loadFormattingTags(self): """ Load the Tags from store so can be used in the system or used to update the display. If Cancel was selected this is needed to reset the dsiplay to the correct version. """ # Initial Load of the Tags - DisplayTags.reset_html_tags() + FormattingTags.reset_html_tags() + # Formatting Tags were also known as display tags. user_expands = QtCore.QSettings().value(u'displayTags/html_tags', QtCore.QVariant(u'')).toString() # cPickle only accepts str not unicode strings @@ -85,14 +86,14 @@ class DisplayTagForm(QtGui.QDialog, Ui_DisplayTagDialog): if user_expands_string: user_tags = cPickle.loads(user_expands_string) # If we have some user ones added them as well - DisplayTags.add_html_tags(user_tags) + FormattingTags.add_html_tags(user_tags) def onRowSelected(self): """ Table Row selected so display items and set field state. """ row = self.tagTableWidget.currentRow() - html = DisplayTags.get_html_tags()[row] + html = FormattingTags.get_html_tags()[row] self.selected = row self.descriptionLineEdit.setText(html[u'desc']) self.tagLineEdit.setText(self._strip(html[u'start tag'])) @@ -117,23 +118,23 @@ class DisplayTagForm(QtGui.QDialog, Ui_DisplayTagDialog): """ Add a new tag to list only if it is not a duplicate. """ - for html in DisplayTags.get_html_tags(): + for html in FormattingTags.get_html_tags(): if self._strip(html[u'start tag']) == u'n': critical_error_message_box( - translate('OpenLP.DisplayTagTab', 'Update Error'), - translate('OpenLP.DisplayTagTab', + translate('OpenLP.FormattingTagForm', 'Update Error'), + translate('OpenLP.FormattingTagForm', 'Tag "n" already defined.')) return # Add new tag to list tag = { - u'desc': translate('OpenLP.DisplayTagTab', 'New Tag'), + u'desc': translate('OpenLP.FormattingTagForm', 'New Tag'), u'start tag': u'{n}', - u'start html': translate('OpenLP.DisplayTagTab', ''), + u'start html': translate('OpenLP.FormattingTagForm', ''), u'end tag': u'{/n}', - u'end html': translate('OpenLP.DisplayTagTab', ''), + u'end html': translate('OpenLP.FormattingTagForm', ''), u'protected': False } - DisplayTags.add_html_tags([tag]) + FormattingTags.add_html_tags([tag]) self._resetTable() # Highlight new row self.tagTableWidget.selectRow(self.tagTableWidget.rowCount() - 1) @@ -145,7 +146,7 @@ class DisplayTagForm(QtGui.QDialog, Ui_DisplayTagDialog): Delete selected custom tag. """ if self.selected != -1: - DisplayTags.remove_html_tag(self.selected) + FormattingTags.remove_html_tag(self.selected) self.selected = -1 self._resetTable() self._saveTable() @@ -154,7 +155,7 @@ class DisplayTagForm(QtGui.QDialog, Ui_DisplayTagDialog): """ Update Custom Tag details if not duplicate and save the data. """ - html_expands = DisplayTags.get_html_tags() + html_expands = FormattingTags.get_html_tags() if self.selected != -1: html = html_expands[self.selected] tag = unicode(self.tagLineEdit.text()) @@ -162,8 +163,8 @@ class DisplayTagForm(QtGui.QDialog, Ui_DisplayTagDialog): if self._strip(html1[u'start tag']) == tag and \ linenumber != self.selected: critical_error_message_box( - translate('OpenLP.DisplayTagTab', 'Update Error'), - unicode(translate('OpenLP.DisplayTagTab', + translate('OpenLP.FormattingTagForm', 'Update Error'), + unicode(translate('OpenLP.FormattingTagForm', 'Tag %s already defined.')) % tag) return html[u'desc'] = unicode(self.descriptionLineEdit.text()) @@ -177,18 +178,15 @@ class DisplayTagForm(QtGui.QDialog, Ui_DisplayTagDialog): def _saveTable(self): """ - Saves all display tags except protected ones. + Saves all formatting tags except protected ones. """ tags = [] - for tag in DisplayTags.get_html_tags(): + for tag in FormattingTags.get_html_tags(): if not tag[u'protected']: tags.append(tag) - if tags: - QtCore.QSettings().setValue(u'displayTags/html_tags', - QtCore.QVariant(cPickle.dumps(tags))) - else: - QtCore.QSettings().setValue(u'displayTags/html_tags', - QtCore.QVariant(u'')) + # Formatting Tags were also known as display tags. + QtCore.QSettings().setValue(u'displayTags/html_tags', + QtCore.QVariant(cPickle.dumps(tags) if tags else u'')) def _resetTable(self): """ @@ -199,7 +197,7 @@ class DisplayTagForm(QtGui.QDialog, Ui_DisplayTagDialog): self.newPushButton.setEnabled(True) self.savePushButton.setEnabled(False) self.deletePushButton.setEnabled(False) - for linenumber, html in enumerate(DisplayTags.get_html_tags()): + for linenumber, html in enumerate(FormattingTags.get_html_tags()): self.tagTableWidget.setRowCount( self.tagTableWidget.rowCount() + 1) self.tagTableWidget.setItem(linenumber, 0, diff --git a/openlp/core/ui/generaltab.py b/openlp/core/ui/generaltab.py index 925e3d31f..9a241473a 100644 --- a/openlp/core/ui/generaltab.py +++ b/openlp/core/ui/generaltab.py @@ -44,7 +44,7 @@ class GeneralTab(SettingsTab): """ self.screens = ScreenList.get_instance() self.icon_path = u':/icon/openlp-logo-16x16.png' - generalTranslated = translate('GeneralTab', 'General') + generalTranslated = translate('OpenLP.GeneralTab', 'General') SettingsTab.__init__(self, parent, u'General', generalTranslated) def setupUi(self): diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index b661b1e49..9904868ce 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -48,13 +48,13 @@ class MainDisplay(QtGui.QGraphicsView): """ This is the display screen. """ - def __init__(self, parent, image_manager, live): + def __init__(self, parent, imageManager, live): if live: QtGui.QGraphicsView.__init__(self) else: QtGui.QGraphicsView.__init__(self, parent) self.isLive = live - self.image_manager = image_manager + self.imageManager = imageManager self.screens = ScreenList.get_instance() self.alertTab = None self.hideMode = None @@ -188,7 +188,7 @@ class MainDisplay(QtGui.QGraphicsView): while not self.webLoaded: Receiver.send_message(u'openlp_process_events') self.setGeometry(self.screen[u'size']) - self.frame.evaluateJavaScript(u'show_text("%s")' % \ + self.frame.evaluateJavaScript(u'show_text("%s")' % slide.replace(u'\\', u'\\\\').replace(u'\"', u'\\\"')) return self.preview() @@ -232,11 +232,13 @@ class MainDisplay(QtGui.QGraphicsView): """ API for replacement backgrounds so Images are added directly to cache """ - self.image_manager.add_image(name, path) - self.image(name) + self.imageManager.add_image(name, path) if hasattr(self, u'serviceItem'): self.override[u'image'] = name self.override[u'theme'] = self.serviceItem.themedata.theme_name + self.image(name) + return True + return False def image(self, name): """ @@ -247,7 +249,7 @@ class MainDisplay(QtGui.QGraphicsView): The name of the image to be displayed """ log.debug(u'image to display') - image = self.image_manager.get_image_bytes(name) + image = self.imageManager.get_image_bytes(name) self.resetVideo() self.displayImage(image) return self.preview() @@ -349,6 +351,9 @@ class MainDisplay(QtGui.QGraphicsView): """ Loads and starts a video to run with the option of sound """ + # We request a background video but have no service Item + if isBackground and not hasattr(self, u'serviceItem'): + return None if not self.mediaObject: self.createMediaObject() log.debug(u'video') @@ -477,13 +482,13 @@ class MainDisplay(QtGui.QGraphicsView): self.override = {} else: # replace the background - background = self.image_manager. \ + background = self.imageManager. \ get_image_bytes(self.override[u'image']) if self.serviceItem.themedata.background_filename: - self.serviceItem.bg_image_bytes = self.image_manager. \ + self.serviceItem.bg_image_bytes = self.imageManager. \ get_image_bytes(self.serviceItem.themedata.theme_name) if image: - image_bytes = self.image_manager.get_image_bytes(image) + image_bytes = self.imageManager.get_image_bytes(image) else: image_bytes = None html = build_html(self.serviceItem, self.screen, self.alertTab, diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 2f002e3a8..a6e909b97 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -28,20 +28,23 @@ import logging import os import sys +import shutil from tempfile import gettempdir from PyQt4 import QtCore, QtGui from openlp.core.lib import Renderer, build_icon, OpenLPDockWidget, \ - PluginManager, Receiver, translate, ImageManager + PluginManager, Receiver, translate, ImageManager, PluginStatus from openlp.core.lib.ui import UiStrings, base_action, checkable_action, \ icon_action, shortcut_action from openlp.core.ui import AboutForm, SettingsForm, ServiceManager, \ ThemeManager, SlideController, PluginForm, MediaDockManager, \ - ShortcutListForm, DisplayTagForm + ShortcutListForm, FormattingTagForm from openlp.core.utils import AppLocation, add_actions, LanguageManager, \ get_application_version, delete_file from openlp.core.utils.actions import ActionList, CategoryOrder +from openlp.core.ui.firsttimeform import FirstTimeForm +from openlp.core.ui import ScreenList log = logging.getLogger(__name__) @@ -65,6 +68,12 @@ MEDIA_MANAGER_STYLE = """ } """ +PROGRESSBAR_STYLE = """ + QProgressBar{ + height: 10px; + } +""" + class Ui_MainWindow(object): def setupUi(self, mainWindow): """ @@ -93,12 +102,16 @@ class Ui_MainWindow(object): self.previewController.panel.setVisible(previewVisible) liveVisible = QtCore.QSettings().value(u'user interface/live panel', QtCore.QVariant(True)).toBool() + panelLocked = QtCore.QSettings().value(u'user interface/lock panel', + QtCore.QVariant(False)).toBool() self.liveController.panel.setVisible(liveVisible) # Create menu self.menuBar = QtGui.QMenuBar(mainWindow) self.menuBar.setObjectName(u'menuBar') self.fileMenu = QtGui.QMenu(self.menuBar) self.fileMenu.setObjectName(u'fileMenu') + self.recentFilesMenu = QtGui.QMenu(self.fileMenu) + self.recentFilesMenu.setObjectName(u'recentFilesMenu') self.fileImportMenu = QtGui.QMenu(self.fileMenu) self.fileImportMenu.setObjectName(u'fileImportMenu') self.fileExportMenu = QtGui.QMenu(self.fileMenu) @@ -128,6 +141,7 @@ class Ui_MainWindow(object): self.statusBar.addPermanentWidget(self.loadProgressBar) self.loadProgressBar.hide() self.loadProgressBar.setValue(0) + self.loadProgressBar.setStyleSheet(PROGRESSBAR_STYLE) self.defaultThemeLabel = QtGui.QLabel(self.statusBar) self.defaultThemeLabel.setObjectName(u'defaultThemeLabel') self.statusBar.addPermanentWidget(self.defaultThemeLabel) @@ -213,7 +227,11 @@ class Ui_MainWindow(object): self.viewLivePanel = shortcut_action(mainWindow, u'viewLivePanel', [QtGui.QKeySequence(u'F12')], self.setLivePanelVisibility, checked=liveVisible, category=UiStrings().View) - action_list.add_category(UiStrings().ViewMode, CategoryOrder.standardMenu) + self.lockPanel = shortcut_action(mainWindow, u'lockPanel', + None, self.setLockPanel, + checked=panelLocked, category=None) + action_list.add_category(UiStrings().ViewMode, + CategoryOrder.standardMenu) self.modeDefaultItem = checkable_action( mainWindow, u'modeDefaultItem', category=UiStrings().ViewMode) self.modeSetupItem = checkable_action( @@ -231,9 +249,13 @@ class Ui_MainWindow(object): self.toolsOpenDataFolder = icon_action(mainWindow, u'toolsOpenDataFolder', u':/general/general_open.png', category=UiStrings().Tools) + self.toolsFirstTimeWizard = icon_action(mainWindow, + u'toolsFirstTimeWizard', u':/general/general_revert.png', + category=UiStrings().Tools) self.updateThemeImages = base_action(mainWindow, u'updateThemeImages', category=UiStrings().Tools) - action_list.add_category(UiStrings().Settings, CategoryOrder.standardMenu) + action_list.add_category(UiStrings().Settings, + CategoryOrder.standardMenu) self.settingsPluginListItem = shortcut_action(mainWindow, u'settingsPluginListItem', [QtGui.QKeySequence(u'Alt+F7')], self.onPluginItemClicked, u':/system/settings_plugin_list.png', @@ -255,40 +277,45 @@ class Ui_MainWindow(object): u'settingsShortcutsItem', u':/system/system_configure_shortcuts.png', category=UiStrings().Settings) - self.displayTagItem = icon_action(mainWindow, + # Formatting Tags were also known as display tags. + self.formattingTagItem = icon_action(mainWindow, u'displayTagItem', u':/system/tag_editor.png', category=UiStrings().Settings) self.settingsConfigureItem = icon_action(mainWindow, u'settingsConfigureItem', u':/system/system_settings.png', category=UiStrings().Settings) action_list.add_category(UiStrings().Help, CategoryOrder.standardMenu) - self.helpDocumentationItem = icon_action(mainWindow, - u'helpDocumentationItem', u':/system/system_help_contents.png', - category=None)#UiStrings().Help) - self.helpDocumentationItem.setEnabled(False) - self.helpAboutItem = shortcut_action(mainWindow, u'helpAboutItem', - [QtGui.QKeySequence(u'Ctrl+F1')], self.onHelpAboutItemClicked, + self.aboutItem = shortcut_action(mainWindow, u'aboutItem', + [QtGui.QKeySequence(u'Ctrl+F1')], self.onAboutItemClicked, u':/system/system_about.png', category=UiStrings().Help) - self.helpOnlineHelpItem = shortcut_action( - mainWindow, u'helpOnlineHelpItem', [QtGui.QKeySequence(u'F1')], - self.onHelpOnlineHelpClicked, u':/system/system_online_help.png', - category=UiStrings().Help) - self.helpWebSiteItem = base_action( - mainWindow, u'helpWebSiteItem', category=UiStrings().Help) + if os.name == u'nt': + self.localHelpFile = os.path.join( + AppLocation.get_directory(AppLocation.AppDir), 'OpenLP.chm') + self.offlineHelpItem = shortcut_action( + mainWindow, u'offlineHelpItem', [QtGui.QKeySequence(u'F1')], + self.onOfflineHelpClicked, + u':/system/system_help_contents.png', category=UiStrings().Help) + self.onlineHelpItem = shortcut_action( + mainWindow, u'onlineHelpItem', + [QtGui.QKeySequence(u'Alt+F1')], self.onOnlineHelpClicked, + u':/system/system_online_help.png', category=UiStrings().Help) + self.webSiteItem = base_action( + mainWindow, u'webSiteItem', category=UiStrings().Help) add_actions(self.fileImportMenu, (self.importThemeItem, self.importLanguageItem)) add_actions(self.fileExportMenu, (self.exportThemeItem, self.exportLanguageItem)) - self.fileMenuActions = (self.fileNewItem, self.fileOpenItem, - self.fileSaveItem, self.fileSaveAsItem, None, - self.printServiceOrderItem, None, self.fileImportMenu.menuAction(), - self.fileExportMenu.menuAction(), self.fileExitItem) + add_actions(self.fileMenu, (self.fileNewItem, self.fileOpenItem, + self.fileSaveItem, self.fileSaveAsItem, + self.recentFilesMenu.menuAction(), None, + self.fileImportMenu.menuAction(), self.fileExportMenu.menuAction(), + None, self.printServiceOrderItem, self.fileExitItem)) add_actions(self.viewModeMenu, (self.modeDefaultItem, self.modeSetupItem, self.modeLiveItem)) add_actions(self.viewMenu, (self.viewModeMenu.menuAction(), None, self.viewMediaManagerItem, self.viewServiceManagerItem, self.viewThemeManagerItem, None, self.viewPreviewPanel, - self.viewLivePanel)) + self.viewLivePanel, None, self.lockPanel)) # i18n add Language Actions add_actions(self.settingsLanguageMenu, (self.autoLanguageItem, None)) add_actions(self.settingsLanguageMenu, self.languageGroup.actions()) @@ -298,18 +325,23 @@ class Ui_MainWindow(object): add_actions(self.settingsMenu, (self.settingsPluginListItem, self.settingsLanguageMenu.menuAction(), None, self.settingsConfigureItem, self.settingsShortcutsItem, - self.displayTagItem)) + self.formattingTagItem)) else: add_actions(self.settingsMenu, (self.settingsPluginListItem, self.settingsLanguageMenu.menuAction(), None, - self.displayTagItem, self.settingsShortcutsItem, + self.formattingTagItem, self.settingsShortcutsItem, self.settingsConfigureItem)) add_actions(self.toolsMenu, (self.toolsAddToolItem, None)) add_actions(self.toolsMenu, (self.toolsOpenDataFolder, None)) + add_actions(self.toolsMenu, (self.toolsFirstTimeWizard, None)) add_actions(self.toolsMenu, [self.updateThemeImages]) - add_actions(self.helpMenu, (self.helpDocumentationItem, - self.helpOnlineHelpItem, None, self.helpWebSiteItem, - self.helpAboutItem)) + if os.name == u'nt': + add_actions(self.helpMenu, (self.offlineHelpItem, + self.onlineHelpItem, None, self.webSiteItem, + self.aboutItem)) + else: + add_actions(self.helpMenu, (self.onlineHelpItem, None, + self.webSiteItem, self.aboutItem)) add_actions(self.menuBar, (self.fileMenu.menuAction(), self.viewMenu.menuAction(), self.toolsMenu.menuAction(), self.settingsMenu.menuAction(), self.helpMenu.menuAction())) @@ -318,13 +350,13 @@ class Ui_MainWindow(object): self.mediaToolBox.setCurrentIndex(0) # Connect up some signals and slots QtCore.QObject.connect(self.fileMenu, - QtCore.SIGNAL(u'aboutToShow()'), self.updateFileMenu) + QtCore.SIGNAL(u'aboutToShow()'), self.updateRecentFilesMenu) QtCore.QMetaObject.connectSlotsByName(mainWindow) # Hide the entry, as it does not have any functionality yet. self.toolsAddToolItem.setVisible(False) self.importLanguageItem.setVisible(False) self.exportLanguageItem.setVisible(False) - self.helpDocumentationItem.setVisible(False) + self.setLockPanel(panelLocked) def retranslateUi(self, mainWindow): """ @@ -335,6 +367,8 @@ class Ui_MainWindow(object): self.fileMenu.setTitle(translate('OpenLP.MainWindow', '&File')) self.fileImportMenu.setTitle(translate('OpenLP.MainWindow', '&Import')) self.fileExportMenu.setTitle(translate('OpenLP.MainWindow', '&Export')) + self.recentFilesMenu.setTitle( + translate('OpenLP.MainWindow', '&Recent Files')) self.viewMenu.setTitle(translate('OpenLP.MainWindow', '&View')) self.viewModeMenu.setTitle(translate('OpenLP.MainWindow', 'M&ode')) self.toolsMenu.setTitle(translate('OpenLP.MainWindow', '&Tools')) @@ -382,8 +416,8 @@ class Ui_MainWindow(object): translate('OpenLP.MainWindow', '&Language')) self.settingsShortcutsItem.setText( translate('OpenLP.MainWindow', 'Configure &Shortcuts...')) - self.displayTagItem.setText( - translate('OpenLP.MainWindow', '&Configure Display Tags')) + self.formattingTagItem.setText( + translate('OpenLP.MainWindow', '&Configure Formatting Tags...')) self.settingsConfigureItem.setText( translate('OpenLP.MainWindow', '&Configure OpenLP...')) self.viewMediaManagerItem.setText( @@ -414,20 +448,25 @@ class Ui_MainWindow(object): translate('OpenLP.MainWindow', '&Live Panel')) self.viewLivePanel.setToolTip( translate('OpenLP.MainWindow', 'Toggle Live Panel')) + self.lockPanel.setText( + translate('OpenLP.MainWindow', 'L&ock Panels')) + self.lockPanel.setStatusTip( + translate('OpenLP.MainWindow', 'Prevent the panels being moved.')) self.viewLivePanel.setStatusTip(translate('OpenLP.MainWindow', 'Toggle the visibility of the live panel.')) self.settingsPluginListItem.setText(translate('OpenLP.MainWindow', '&Plugin List')) self.settingsPluginListItem.setStatusTip( translate('OpenLP.MainWindow', 'List the Plugins')) - self.helpDocumentationItem.setText( - translate('OpenLP.MainWindow', '&User Guide')) - self.helpAboutItem.setText(translate('OpenLP.MainWindow', '&About')) - self.helpAboutItem.setStatusTip( + self.aboutItem.setText(translate('OpenLP.MainWindow', '&About')) + self.aboutItem.setStatusTip( translate('OpenLP.MainWindow', 'More information about OpenLP')) - self.helpOnlineHelpItem.setText( + if os.name == u'nt': + self.offlineHelpItem.setText( + translate('OpenLP.MainWindow', '&User Guide')) + self.onlineHelpItem.setText( translate('OpenLP.MainWindow', '&Online Help')) - self.helpWebSiteItem.setText( + self.webSiteItem.setText( translate('OpenLP.MainWindow', '&Web Site')) for item in self.languageGroup.actions(): item.setText(item.objectName()) @@ -445,6 +484,10 @@ class Ui_MainWindow(object): translate('OpenLP.MainWindow', 'Open &Data Folder...')) self.toolsOpenDataFolder.setStatusTip(translate('OpenLP.MainWindow', 'Open the folder where songs, bibles and other data resides.')) + self.toolsFirstTimeWizard.setText( + translate('OpenLP.MainWindow', 'Re-run First Time Wizard')) + self.toolsFirstTimeWizard.setStatusTip(translate('OpenLP.MainWindow', + 'Re-run the First Time Wizard, importing songs, Bibles and themes.')) self.updateThemeImages.setText( translate('OpenLP.MainWindow', 'Update Theme Images')) self.updateThemeImages.setStatusTip( @@ -485,7 +528,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): self.serviceNotSaved = False self.aboutForm = AboutForm(self) self.settingsForm = SettingsForm(self, self) - self.displayTagForm = DisplayTagForm(self) + self.formattingTagForm = FormattingTagForm(self) self.shortcutForm = ShortcutListForm(self) self.recentFiles = QtCore.QStringList() # Set up the path with plugins @@ -497,8 +540,8 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): self.setupUi(self) # Load settings after setupUi so default UI sizes are overwritten self.loadSettings() - # Once settings are loaded update FileMenu with recentFiles - self.updateFileMenu() + # Once settings are loaded update the menu with the recent files. + self.updateRecentFilesMenu() self.pluginForm = PluginForm(self) # Set up signals and slots QtCore.QObject.connect(self.importThemeItem, @@ -516,14 +559,16 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): QtCore.QObject.connect(self.themeManagerDock, QtCore.SIGNAL(u'visibilityChanged(bool)'), self.viewThemeManagerItem.setChecked) - QtCore.QObject.connect(self.helpWebSiteItem, + QtCore.QObject.connect(self.webSiteItem, QtCore.SIGNAL(u'triggered()'), self.onHelpWebSiteClicked) QtCore.QObject.connect(self.toolsOpenDataFolder, QtCore.SIGNAL(u'triggered()'), self.onToolsOpenDataFolderClicked) + QtCore.QObject.connect(self.toolsFirstTimeWizard, + QtCore.SIGNAL(u'triggered()'), self.onFirstTimeWizardClicked) QtCore.QObject.connect(self.updateThemeImages, QtCore.SIGNAL(u'triggered()'), self.onUpdateThemeImages) - QtCore.QObject.connect(self.displayTagItem, - QtCore.SIGNAL(u'triggered()'), self.onDisplayTagItemClicked) + QtCore.QObject.connect(self.formattingTagItem, + QtCore.SIGNAL(u'triggered()'), self.onFormattingTagItemClicked) QtCore.QObject.connect(self.settingsConfigureItem, QtCore.SIGNAL(u'triggered()'), self.onSettingsConfigureItemClicked) QtCore.QObject.connect(self.settingsShortcutsItem, @@ -653,7 +698,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): QtCore.QVariant(False)).toBool(): self.serviceManagerContents.loadLastFile() view_mode = QtCore.QSettings().value(u'%s/view mode' % \ - self.generalSettingsSection, u'default') + self.generalSettingsSection, u'default').toString() if view_mode == u'default': self.modeDefaultItem.setChecked(True) elif view_mode == u'setup': @@ -682,11 +727,46 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): plugin.firstTime() Receiver.send_message(u'openlp_process_events') temp_dir = os.path.join(unicode(gettempdir()), u'openlp') - if not os.path.exists(temp_dir): + shutil.rmtree(temp_dir, True) + + def onFirstTimeWizardClicked(self): + """ + Re-run the first time wizard. Prompts the user for run confirmation + If wizard is run, songs, bibles and themes are imported. The default + theme is changed (if necessary). The plugins in pluginmanager are + set active/in-active to match the selection in the wizard. + """ + answer = QtGui.QMessageBox.warning(self, + translate('OpenLP.MainWindow', 'Re-run First Time Wizard?'), + translate('OpenLP.MainWindow', + 'Are you sure you want to re-run the First Time Wizard?\n\n' + 'Re-running this wizard may make changes to your current ' + 'OpenLP configuration and possibly add songs to your ' + 'existing songs list and change your default theme.'), + QtGui.QMessageBox.StandardButtons( + QtGui.QMessageBox.Yes | + QtGui.QMessageBox.No), + QtGui.QMessageBox.No) + if answer == QtGui.QMessageBox.No: return - for filename in os.listdir(temp_dir): - delete_file(os.path.join(temp_dir, filename)) - os.removedirs(temp_dir) + Receiver.send_message(u'cursor_busy') + screens = ScreenList.get_instance() + if FirstTimeForm(screens, self).exec_() == QtGui.QDialog.Accepted: + self.firstTime() + for plugin in self.pluginManager.plugins: + self.activePlugin = plugin + oldStatus = self.activePlugin.status + self.activePlugin.setStatus() + if oldStatus != self.activePlugin.status: + if self.activePlugin.status == PluginStatus.Active: + self.activePlugin.toggleStatus(PluginStatus.Active) + self.activePlugin.appStartup() + else: + self.activePlugin.toggleStatus(PluginStatus.Inactive) + self.themeManagerContents.configUpdated() + self.themeManagerContents.loadThemes(True) + Receiver.send_message(u'theme_update_global', + self.themeManagerContents.global_theme) def blankCheck(self): """ @@ -723,14 +803,20 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): import webbrowser webbrowser.open_new(u'http://openlp.org/') - def onHelpOnlineHelpClicked(self): + def onOfflineHelpClicked(self): + """ + Load the local OpenLP help file + """ + os.startfile(self.localHelpFile) + + def onOnlineHelpClicked(self): """ Load the online OpenLP manual """ import webbrowser webbrowser.open_new(u'http://manual.openlp.org/') - def onHelpAboutItemClicked(self): + def onAboutItemClicked(self): """ Show the About form """ @@ -756,11 +842,11 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): """ self.themeManagerContents.updatePreviewImages() - def onDisplayTagItemClicked(self): + def onFormattingTagItemClicked(self): """ Show the Settings dialog """ - self.displayTagForm.exec_() + self.formattingTagForm.exec_() def onSettingsConfigureItemClicked(self): """ @@ -936,7 +1022,8 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): self.mediaManagerDock.setVisible(not self.mediaManagerDock.isVisible()) def toggleServiceManager(self): - self.serviceManagerDock.setVisible(not self.serviceManagerDock.isVisible()) + self.serviceManagerDock.setVisible( + not self.serviceManagerDock.isVisible()) def toggleThemeManager(self): self.themeManagerDock.setVisible(not self.themeManagerDock.isVisible()) @@ -956,6 +1043,37 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): QtCore.QVariant(visible)) self.viewPreviewPanel.setChecked(visible) + def setLockPanel(self, lock): + """ + Sets the ability to stop the toolbars being changed. + """ + if lock: + self.themeManagerDock.setFeatures( + QtGui.QDockWidget.NoDockWidgetFeatures) + self.serviceManagerDock.setFeatures( + QtGui.QDockWidget.NoDockWidgetFeatures) + self.mediaManagerDock.setFeatures( + QtGui.QDockWidget.NoDockWidgetFeatures) + self.viewMediaManagerItem.setEnabled(False) + self.viewServiceManagerItem.setEnabled(False) + self.viewThemeManagerItem.setEnabled(False) + self.viewPreviewPanel.setEnabled(False) + self.viewLivePanel.setEnabled(False) + else: + self.themeManagerDock.setFeatures( + QtGui.QDockWidget.AllDockWidgetFeatures) + self.serviceManagerDock.setFeatures( + QtGui.QDockWidget.AllDockWidgetFeatures) + self.mediaManagerDock.setFeatures( + QtGui.QDockWidget.AllDockWidgetFeatures) + self.viewMediaManagerItem.setEnabled(True) + self.viewServiceManagerItem.setEnabled(True) + self.viewThemeManagerItem.setEnabled(True) + self.viewPreviewPanel.setEnabled(True) + self.viewLivePanel.setEnabled(True) + QtCore.QSettings().setValue(u'user interface/lock panel', + QtCore.QVariant(lock)) + def setLivePanelVisibility(self, visible): """ Sets the visibility of the live panel including saving the setting and @@ -986,6 +1104,13 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): self.restoreGeometry( settings.value(u'main window geometry').toByteArray()) self.restoreState(settings.value(u'main window state').toByteArray()) + self.liveController.splitter.restoreState( + settings.value(u'live splitter geometry').toByteArray()) + self.previewController.splitter.restoreState( + settings.value(u'preview splitter geometry').toByteArray()) + self.controlSplitter.restoreState( + settings.value(u'mainwindow splitter geometry').toByteArray()) + settings.endGroup() def saveSettings(self): @@ -1006,32 +1131,44 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): QtCore.QVariant(self.saveState())) settings.setValue(u'main window geometry', QtCore.QVariant(self.saveGeometry())) + settings.setValue(u'live splitter geometry', + QtCore.QVariant(self.liveController.splitter.saveState())) + settings.setValue(u'preview splitter geometry', + QtCore.QVariant(self.previewController.splitter.saveState())) + settings.setValue(u'mainwindow splitter geometry', + QtCore.QVariant(self.controlSplitter.saveState())) settings.endGroup() - def updateFileMenu(self): + def updateRecentFilesMenu(self): """ - Updates the file menu with the latest list of service files accessed. + Updates the recent file menu with the latest list of service files + accessed. """ recentFileCount = QtCore.QSettings().value( u'advanced/recent file count', QtCore.QVariant(4)).toInt()[0] - self.fileMenu.clear() - add_actions(self.fileMenu, self.fileMenuActions[:-1]) existingRecentFiles = [recentFile for recentFile in self.recentFiles if QtCore.QFile.exists(recentFile)] recentFilesToDisplay = existingRecentFiles[0:recentFileCount] - if recentFilesToDisplay: - self.fileMenu.addSeparator() - for fileId, filename in enumerate(recentFilesToDisplay): - log.debug('Recent file name: %s', filename) - action = base_action(self, u'') - action.setText(u'&%d %s' % - (fileId + 1, QtCore.QFileInfo(filename).fileName())) - action.setData(QtCore.QVariant(filename)) - self.connect(action, QtCore.SIGNAL(u'triggered()'), - self.serviceManagerContents.onRecentServiceClicked) - self.fileMenu.addAction(action) - self.fileMenu.addSeparator() - self.fileMenu.addAction(self.fileMenuActions[-1]) + self.recentFilesMenu.clear() + for fileId, filename in enumerate(recentFilesToDisplay): + log.debug('Recent file name: %s', filename) + action = base_action(self, u'') + action.setText(u'&%d %s' % + (fileId + 1, QtCore.QFileInfo(filename).fileName())) + action.setData(QtCore.QVariant(filename)) + self.connect(action, QtCore.SIGNAL(u'triggered()'), + self.serviceManagerContents.onRecentServiceClicked) + self.recentFilesMenu.addAction(action) + clearRecentFilesAction = base_action(self, u'') + clearRecentFilesAction.setText( + translate('OpenLP.MainWindow', 'Clear List', + 'Clear List of recent files')) + clearRecentFilesAction.setStatusTip( + translate('OpenLP.MainWindow', 'Clear the list of recent files.')) + add_actions(self.recentFilesMenu, (None, clearRecentFilesAction)) + self.connect(clearRecentFilesAction, QtCore.SIGNAL(u'triggered()'), + self.recentFiles.clear) + clearRecentFilesAction.setEnabled(not self.recentFiles.isEmpty()) def addRecentFile(self, filename): """ diff --git a/openlp/core/ui/mediadockmanager.py b/openlp/core/ui/mediadockmanager.py index 3eb5a899c..cc6cc92bc 100644 --- a/openlp/core/ui/mediadockmanager.py +++ b/openlp/core/ui/mediadockmanager.py @@ -66,7 +66,7 @@ class MediaDockManager(object): match = False for dock_index in range(0, self.media_dock.count()): if self.media_dock.widget(dock_index).settingsSection == \ - media_item.plugin.name.lower(): + media_item.plugin.name: match = True break if not match: @@ -84,6 +84,6 @@ class MediaDockManager(object): for dock_index in range(0, self.media_dock.count()): if self.media_dock.widget(dock_index): if self.media_dock.widget(dock_index).settingsSection == \ - media_item.plugin.name.lower(): + media_item.plugin.name: self.media_dock.widget(dock_index).setVisible(False) self.media_dock.removeItem(dock_index) diff --git a/openlp/core/ui/printservicedialog.py b/openlp/core/ui/printservicedialog.py index b0065df99..8287ef02a 100644 --- a/openlp/core/ui/printservicedialog.py +++ b/openlp/core/ui/printservicedialog.py @@ -108,7 +108,7 @@ class Ui_PrintServiceDialog(object): self.footerLabel = QtGui.QLabel(self.optionsWidget) self.footerLabel.setObjectName(u'footerLabel') self.optionsLayout.addWidget(self.footerLabel) - self.footerTextEdit = SpellTextEdit(self.optionsWidget) + self.footerTextEdit = SpellTextEdit(self.optionsWidget, False) self.footerTextEdit.setObjectName(u'footerTextEdit') self.optionsLayout.addWidget(self.footerTextEdit) self.optionsGroupBox = QtGui.QGroupBox() diff --git a/openlp/core/ui/printserviceform.py b/openlp/core/ui/printserviceform.py index 2bbf2ab56..55fc6eb3c 100644 --- a/openlp/core/ui/printserviceform.py +++ b/openlp/core/ui/printserviceform.py @@ -24,6 +24,7 @@ # with this program; if not, write to the Free Software Foundation, Inc., 59 # # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### +import cgi import datetime import os @@ -183,7 +184,7 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog): self._addElement(u'style', custom_css, html_data.head, attribute=(u'type', u'text/css')) self._addElement(u'body', parent=html_data) - self._addElement(u'h1', unicode(self.titleLineEdit.text()), + self._addElement(u'h1', cgi.escape(unicode(self.titleLineEdit.text())), html_data.body, classId=u'serviceTitle') for index, item in enumerate(self.serviceManager.serviceItems): self._addPreviewItem(html_data.body, item[u'service_item'], index) @@ -193,8 +194,9 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog): classId=u'customNotes') self._addElement(u'span', translate('OpenLP.ServiceManager', 'Custom Service Notes: '), div, classId=u'customNotesTitle') - self._addElement(u'span', self.footerTextEdit.toPlainText(), div, - classId=u'customNotesText') + self._addElement(u'span', + cgi.escape(self.footerTextEdit.toPlainText()), + div, classId=u'customNotesText') self.document.setHtml(html.tostring(html_data)) self.previewWidget.updatePreview() @@ -204,8 +206,8 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog): item_title = self._addElement(u'h2', parent=div, classId=u'itemTitle') self._addElement(u'img', parent=item_title, attribute=(u'src', item.icon)) - self._addElement(u'span', u' ' + item.get_display_title(), - item_title) + self._addElement(u'span', + u' ' + cgi.escape(item.get_display_title()), item_title) if self.slideTextCheckBox.isChecked(): # Add the text of the service item. if item.is_text(): @@ -230,8 +232,9 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog): foot_text = item.foot_text foot_text = foot_text.partition(u'
')[2] if foot_text: - foot = self._addElement(u'div', foot_text, parent=div, - classId=u'itemFooter') + foot_text = cgi.escape(foot_text.replace(u'
', u'\n')) + self._addElement(u'div', foot_text.replace(u'\n', u'
'), + parent=div, classId=u'itemFooter') # Add service items' notes. if self.notesCheckBox.isChecked(): if item.notes: @@ -239,8 +242,8 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog): self._addElement(u'span', translate('OpenLP.ServiceManager', 'Notes: '), p, classId=u'itemNotesTitle') - notes = self._addElement(u'span', - item.notes.replace(u'\n', u'
'), p, + self._addElement(u'span', + cgi.escape(unicode(item.notes)).replace(u'\n', u'
'), p, classId=u'itemNotesText') # Add play length of media files. if item.is_media() and self.metaDataCheckBox.isChecked(): diff --git a/openlp/core/ui/serviceitemeditdialog.py b/openlp/core/ui/serviceitemeditdialog.py index a00feafc7..d821430b2 100644 --- a/openlp/core/ui/serviceitemeditdialog.py +++ b/openlp/core/ui/serviceitemeditdialog.py @@ -35,6 +35,8 @@ class Ui_ServiceItemEditDialog(object): def setupUi(self, serviceItemEditDialog): serviceItemEditDialog.setObjectName(u'serviceItemEditDialog') self.dialogLayout = QtGui.QGridLayout(serviceItemEditDialog) + self.dialogLayout.setContentsMargins(8, 8, 8, 8) + self.dialogLayout.setSpacing(8) self.dialogLayout.setObjectName(u'dialogLayout') self.listWidget = QtGui.QListWidget(serviceItemEditDialog) self.listWidget.setAlternatingRowColors(True) diff --git a/openlp/core/ui/serviceitemeditform.py b/openlp/core/ui/serviceitemeditform.py index f19638e43..974133c3d 100644 --- a/openlp/core/ui/serviceitemeditform.py +++ b/openlp/core/ui/serviceitemeditform.py @@ -79,7 +79,7 @@ class ServiceItemEditForm(QtGui.QDialog, Ui_ServiceItemEditDialog): if not item: return row = self.listWidget.row(item) - self.itemList.remove(self.itemList[row]) + self.itemList.pop(row) self.loadData() if row == self.listWidget.count(): self.listWidget.setCurrentRow(row - 1) @@ -109,7 +109,7 @@ class ServiceItemEditForm(QtGui.QDialog, Ui_ServiceItemEditDialog): return row = self.listWidget.row(item) temp = self.itemList[row] - self.itemList.remove(self.itemList[row]) + self.itemList.pop(row) if direction == u'up': row -= 1 else: diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 8fc796ea4..3ab2e9239 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -24,6 +24,7 @@ # with this program; if not, write to the Free Software Foundation, Inc., 59 # # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### +import cgi import cPickle import logging import os @@ -48,18 +49,18 @@ class ServiceManagerList(QtGui.QTreeWidget): """ Set up key bindings and mouse behaviour for the service list """ - def __init__(self, mainwindow, parent=None, name=None): + def __init__(self, serviceManager, parent=None, name=None): QtGui.QTreeWidget.__init__(self, parent) - self.mainwindow = mainwindow + self.serviceManager = serviceManager def keyPressEvent(self, event): if isinstance(event, QtGui.QKeyEvent): # here accept the event and do something if event.key() == QtCore.Qt.Key_Up: - self.mainwindow.onMoveSelectionUp() + self.serviceManager.onMoveSelectionUp() event.accept() elif event.key() == QtCore.Qt.Key_Down: - self.mainwindow.onMoveSelectionDown() + self.serviceManager.onMoveSelectionDown() event.accept() event.ignore() else: @@ -408,20 +409,33 @@ class ServiceManager(QtGui.QWidget): return False self.newFile() - def onLoadServiceClicked(self): + def onLoadServiceClicked(self, loadFile=None): + """ + Loads the service file and saves the existing one it there is one + unchanged + + ``loadFile`` + The service file to the loaded. Will be None is from menu so + selection will be required. + """ if self.isModified(): result = self.saveModifiedService() if result == QtGui.QMessageBox.Cancel: return False elif result == QtGui.QMessageBox.Save: self.saveFile() - fileName = unicode(QtGui.QFileDialog.getOpenFileName(self.mainwindow, - translate('OpenLP.ServiceManager', 'Open File'), - SettingsManager.get_last_dir( - self.mainwindow.serviceSettingsSection), - translate('OpenLP.ServiceManager', 'OpenLP Service Files (*.osz)'))) - if not fileName: - return False + if not loadFile: + fileName = unicode(QtGui.QFileDialog.getOpenFileName( + self.mainwindow, + translate('OpenLP.ServiceManager', 'Open File'), + SettingsManager.get_last_dir( + self.mainwindow.serviceSettingsSection), + translate('OpenLP.ServiceManager', + 'OpenLP Service Files (*.osz)'))) + if not fileName: + return False + else: + fileName = loadFile SettingsManager.set_last_dir(self.mainwindow.serviceSettingsSection, split_filename(fileName)[0]) self.loadFile(fileName) @@ -474,6 +488,7 @@ class ServiceManager(QtGui.QWidget): item[u'service_item'].get_service_repr()}) if not item[u'service_item'].uses_file(): continue + skipMissing = False for frame in item[u'service_item'].get_frames(): if item[u'service_item'].is_image(): path_from = frame[u'path'] @@ -482,25 +497,29 @@ class ServiceManager(QtGui.QWidget): # Only write a file once if path_from in write_list: continue - file_size = os.path.getsize(path_from) - size_limit = 52428800 # 50MiB - #if file_size > size_limit: - # # File exeeds size_limit bytes, ask user - # message = unicode(translate('OpenLP.ServiceManager', - # 'Do you want to include \n%.1f MB file "%s"\n' - # 'into the service file?\nThis may take some time.\n\n' - # 'Please note that you need to\ntake care of that file' - # ' yourself,\nif you leave it out.')) % \ - # (file_size/1048576, os.path.split(path_from)[1]) - # ans = QtGui.QMessageBox.question(self.mainwindow, - # translate('OpenLP.ServiceManager', 'Including Large ' - # 'File'), message, QtGui.QMessageBox.StandardButtons( - # QtGui.QMessageBox.Ok|QtGui.QMessageBox.Cancel), - # QtGui.QMessageBox.Ok) - # if ans == QtGui.QMessageBox.Cancel: - # continue - write_list.append(path_from) - total_size += file_size + if not os.path.exists(path_from): + if not skipMissing: + Receiver.send_message(u'cursor_normal') + title = unicode(translate('OpenLP.ServiceManager', + 'Service File Missing')) + message = unicode(translate('OpenLP.ServiceManager', + 'File missing from service\n\n %s \n\n' + 'Continue saving?' % path_from )) + answer = QtGui.QMessageBox.critical(self, title, + message, + QtGui.QMessageBox.StandardButtons( + QtGui.QMessageBox.Yes | QtGui.QMessageBox.No | + QtGui.QMessageBox.YesToAll)) + if answer == QtGui.QMessageBox.No: + self.mainwindow.finishedProgressBar() + return False + if answer == QtGui.QMessageBox.YesToAll: + skipMissing = True + Receiver.send_message(u'cursor_busy') + else: + file_size = os.path.getsize(path_from) + write_list.append(path_from) + total_size += file_size log.debug(u'ServiceManager.saveFile - ZIP contents size is %i bytes' % total_size) service_content = cPickle.dumps(service) @@ -688,7 +707,7 @@ class ServiceManager(QtGui.QWidget): QtGui.QAction, serviceItem[u'service_item'].theme) if themeAction is not None: themeAction.setChecked(True) - action = self.menu.exec_(self.serviceManagerList.mapToGlobal(point)) + self.menu.exec_(self.serviceManagerList.mapToGlobal(point)) def onServiceItemNoteForm(self): item = self.findServiceItem()[0] @@ -701,6 +720,9 @@ class ServiceManager(QtGui.QWidget): self.setModified() def onStartTimeForm(self): + """ + Opens a dialog to type in service item notes. + """ item = self.findServiceItem()[0] self.startTimeForm.item = self.serviceItems[item] if self.startTimeForm.exec_(): @@ -781,50 +803,25 @@ class ServiceManager(QtGui.QWidget): def onMoveSelectionUp(self): """ - Moves the selection up the window. Called by the up arrow. + Moves the cursor selection up the window. + Called by the up arrow. """ - serviceIterator = QtGui.QTreeWidgetItemIterator(self.serviceManagerList) - tempItem = None - setLastItem = False - while serviceIterator.value(): - if serviceIterator.value().isSelected() and tempItem is None: - setLastItem = True - serviceIterator.value().setSelected(False) - if serviceIterator.value().isSelected(): - # We are on the first record - if tempItem: - tempItem.setSelected(True) - serviceIterator.value().setSelected(False) - else: - tempItem = serviceIterator.value() - lastItem = serviceIterator.value() - serviceIterator += 1 - # Top Item was selected so set the last one - if setLastItem: - lastItem.setSelected(True) - self.setModified() + item = self.serviceManagerList.currentItem() + itemBefore = self.serviceManagerList.itemAbove(item) + if itemBefore is None: + return + self.serviceManagerList.setCurrentItem(itemBefore) def onMoveSelectionDown(self): """ - Moves the selection down the window. Called by the down arrow. + Moves the cursor selection down the window. + Called by the down arrow. """ - serviceIterator = QtGui.QTreeWidgetItemIterator(self.serviceManagerList) - firstItem = None - setSelected = False - while serviceIterator.value(): - if not firstItem: - firstItem = serviceIterator.value() - if setSelected: - setSelected = False - serviceIterator.value().setSelected(True) - elif serviceIterator.value() and \ - serviceIterator.value().isSelected(): - serviceIterator.value().setSelected(False) - setSelected = True - serviceIterator += 1 - if setSelected: - firstItem.setSelected(True) - self.setModified() + item = self.serviceManagerList.currentItem() + itemAfter = self.serviceManagerList.itemBelow(item) + if itemAfter is None: + return + self.serviceManagerList.setCurrentItem(itemAfter) def onCollapseAll(self): """ @@ -832,7 +829,7 @@ class ServiceManager(QtGui.QWidget): """ for item in self.serviceItems: item[u'expanded'] = False - self.regenerateServiceItems() + self.serviceManagerList.collapseAll() def collapsed(self, item): """ @@ -848,7 +845,7 @@ class ServiceManager(QtGui.QWidget): """ for item in self.serviceItems: item[u'expanded'] = True - self.regenerateServiceItems() + self.serviceManagerList.expandAll() def expanded(self, item): """ @@ -856,19 +853,19 @@ class ServiceManager(QtGui.QWidget): correct state. """ pos = item.data(0, QtCore.Qt.UserRole).toInt()[0] - self.serviceItems[pos -1 ][u'expanded'] = True + self.serviceItems[pos - 1][u'expanded'] = True def onServiceTop(self): """ Move the current ServiceItem to the top of the list. """ item, child = self.findServiceItem() - if item < len(self.serviceItems) and item is not -1: + if item < len(self.serviceItems) and item != -1: temp = self.serviceItems[item] self.serviceItems.remove(self.serviceItems[item]) self.serviceItems.insert(0, temp) self.repaintServiceList(0, child) - self.setModified() + self.setModified() def onServiceUp(self): """ @@ -880,31 +877,31 @@ class ServiceManager(QtGui.QWidget): self.serviceItems.remove(self.serviceItems[item]) self.serviceItems.insert(item - 1, temp) self.repaintServiceList(item - 1, child) - self.setModified() + self.setModified() def onServiceDown(self): """ Move the current ServiceItem one position down in the list. """ item, child = self.findServiceItem() - if item < len(self.serviceItems) and item is not -1: + if item < len(self.serviceItems) and item != -1: temp = self.serviceItems[item] self.serviceItems.remove(self.serviceItems[item]) self.serviceItems.insert(item + 1, temp) self.repaintServiceList(item + 1, child) - self.setModified() + self.setModified() def onServiceEnd(self): """ Move the current ServiceItem to the bottom of the list. """ item, child = self.findServiceItem() - if item < len(self.serviceItems) and item is not -1: + if item < len(self.serviceItems) and item != -1: temp = self.serviceItems[item] self.serviceItems.remove(self.serviceItems[item]) self.serviceItems.insert(len(self.serviceItems), temp) self.repaintServiceList(len(self.serviceItems) - 1, child) - self.setModified() + self.setModified() def onDeleteFromService(self): """ @@ -914,7 +911,7 @@ class ServiceManager(QtGui.QWidget): if item != -1: self.serviceItems.remove(self.serviceItems[item]) self.repaintServiceList(item - 1, -1) - self.setModified() + self.setModified() def repaintServiceList(self, serviceItem, serviceItemChild): """ @@ -956,7 +953,19 @@ class ServiceManager(QtGui.QWidget): treewidgetitem.setIcon(0, build_icon(u':/general/general_delete.png')) treewidgetitem.setText(0, serviceitem.get_display_title()) - treewidgetitem.setToolTip(0, serviceitem.notes) + tips = [] + if serviceitem.theme and serviceitem.theme != -1: + tips.append(u'%s: %s' % + (unicode(translate('OpenLP.ServiceManager', 'Slide theme')), + serviceitem.theme)) + if serviceitem.notes: + tips.append(u'%s: %s' % + (unicode(translate('OpenLP.ServiceManager', 'Notes')), + cgi.escape(unicode(serviceitem.notes)))) + if item[u'service_item'] \ + .is_capable(ItemCapabilities.AllowsVariableStartTime): + tips.append(item[u'service_item'].get_media_time()) + treewidgetitem.setToolTip(0, u'
'.join(tips)) treewidgetitem.setData(0, QtCore.Qt.UserRole, QtCore.QVariant(item[u'order'])) treewidgetitem.setSelected(item[u'selected']) @@ -966,11 +975,6 @@ class ServiceManager(QtGui.QWidget): text = frame[u'title'].replace(u'\n', u' ') child.setText(0, text[:40]) child.setData(0, QtCore.Qt.UserRole, QtCore.QVariant(count)) - if item[u'service_item'] \ - .is_capable(ItemCapabilities.AllowsVariableStartTime): - tip = item[u'service_item'].get_media_time() - if tip: - child.setToolTip(0, tip) if serviceItem == itemcount: if item[u'expanded'] and serviceItemChild == count: self.serviceManagerList.setCurrentItem(child) @@ -1257,7 +1261,14 @@ class ServiceManager(QtGui.QWidget): Handle of the event pint passed """ link = event.mimeData() - if link.hasText(): + if event.mimeData().hasUrls(): + event.setDropAction(QtCore.Qt.CopyAction) + event.accept() + for url in event.mimeData().urls(): + filename = unicode(url.toLocalFile()) + if filename.endswith(u'.osz'): + self.onLoadServiceClicked(filename) + elif event.mimeData().hasText(): plugin = unicode(event.mimeData().text()) item = self.serviceManagerList.itemAt(event.pos()) # ServiceManager started the drag and drop @@ -1338,7 +1349,7 @@ class ServiceManager(QtGui.QWidget): if not theme: theme = None item = self.findServiceItem()[0] - self.serviceItems[item][u'service_item'].theme = theme + self.serviceItems[item][u'service_item'].update_theme(theme) self.regenerateServiceItems() def _getParentItemData(self, item): diff --git a/openlp/core/ui/servicenoteform.py b/openlp/core/ui/servicenoteform.py index d361c567e..ddfcb3381 100644 --- a/openlp/core/ui/servicenoteform.py +++ b/openlp/core/ui/servicenoteform.py @@ -27,7 +27,7 @@ from PyQt4 import QtCore, QtGui -from openlp.core.lib import translate +from openlp.core.lib import translate, SpellTextEdit from openlp.core.lib.ui import create_accept_reject_button_box class ServiceNoteForm(QtGui.QDialog): @@ -49,8 +49,10 @@ class ServiceNoteForm(QtGui.QDialog): def setupUi(self): self.setObjectName(u'serviceNoteEdit') self.dialogLayout = QtGui.QVBoxLayout(self) + self.dialogLayout.setContentsMargins(8, 8, 8, 8) + self.dialogLayout.setSpacing(8) self.dialogLayout.setObjectName(u'verticalLayout') - self.textEdit = QtGui.QTextEdit(self) + self.textEdit = SpellTextEdit(self, False) self.textEdit.setObjectName(u'textEdit') self.dialogLayout.addWidget(self.textEdit) self.dialogLayout.addWidget(create_accept_reject_button_box(self)) diff --git a/openlp/core/ui/settingsform.py b/openlp/core/ui/settingsform.py index 711cac7c2..49d27a466 100644 --- a/openlp/core/ui/settingsform.py +++ b/openlp/core/ui/settingsform.py @@ -29,7 +29,7 @@ The :mod:`settingsform` provides a user interface for the OpenLP settings """ import logging -from PyQt4 import QtGui, QtCore +from PyQt4 import QtGui from openlp.core.lib import Receiver, build_icon, PluginStatus from openlp.core.ui import AdvancedTab, GeneralTab, ThemesTab diff --git a/openlp/core/ui/shortcutlistdialog.py b/openlp/core/ui/shortcutlistdialog.py index 6d877358f..a9b9b22cf 100644 --- a/openlp/core/ui/shortcutlistdialog.py +++ b/openlp/core/ui/shortcutlistdialog.py @@ -123,7 +123,7 @@ class Ui_ShortcutListDialog(object): def retranslateUi(self, shortcutListDialog): shortcutListDialog.setWindowTitle( - translate('OpenLP.ShortcutListDialog', 'Customize Shortcuts')) + translate('OpenLP.ShortcutListDialog', 'Configure Shortcuts')) self.descriptionLabel.setText(translate('OpenLP.ShortcutListDialog', 'Select an action and click one of the buttons below to start ' 'capturing a new primary or alternate shortcut, respectively.')) diff --git a/openlp/core/ui/shortcutlistform.py b/openlp/core/ui/shortcutlistform.py index 6bdcc46bc..1eccddc95 100644 --- a/openlp/core/ui/shortcutlistform.py +++ b/openlp/core/ui/shortcutlistform.py @@ -247,7 +247,8 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog): alternate_label_text = action.defaultShortcuts[1].toString() shortcuts = self._actionShortcuts(action) # We do not want to loose pending changes, that is why we have to - # keep the text when, this function has not been triggered by a signal. + # keep the text when, this function has not been triggered by a + # signal. if item is None: primary_text = self.primaryPushButton.text() alternate_text = self.alternatePushButton.text() @@ -280,7 +281,8 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog): """ Restores all default shortcuts. """ - if self.buttonBox.buttonRole(button) != QtGui.QDialogButtonBox.ResetRole: + if self.buttonBox.buttonRole(button) != \ + QtGui.QDialogButtonBox.ResetRole: return if QtGui.QMessageBox.question(self, translate('OpenLP.ShortcutListDialog', 'Restore Default Shortcuts'), diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 8e2c199a1..0f83cbc30 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -27,12 +27,14 @@ import logging import os +import time +import copy from PyQt4 import QtCore, QtGui from PyQt4.phonon import Phonon -from openlp.core.lib import OpenLPToolbar, Receiver, resize_image, \ - ItemCapabilities, translate +from openlp.core.lib import OpenLPToolbar, Receiver, ItemCapabilities, \ + translate, build_icon from openlp.core.lib.ui import UiStrings, shortcut_action from openlp.core.ui import HideMode, MainDisplay, ScreenList from openlp.core.utils.actions import ActionList, CategoryOrder @@ -193,13 +195,11 @@ class SlideController(QtGui.QWidget): self.playSlidesLoop = shortcut_action(self.playSlidesMenu, u'playSlidesLoop', [], self.onPlaySlidesLoop, u':/media/media_time.png', False, UiStrings().LiveToolbar) - self.playSlidesLoop.setText( - translate('OpenLP.SlideController', 'Play Slides in Loop')) + self.playSlidesLoop.setText(UiStrings().PlaySlidesInLoop) self.playSlidesOnce = shortcut_action(self.playSlidesMenu, u'playSlidesOnce', [], self.onPlaySlidesOnce, u':/media/media_time.png', False, UiStrings().LiveToolbar) - self.playSlidesOnce.setText( - translate('OpenLP.SlideController', 'Play Slides to End')) + self.playSlidesOnce.setText(UiStrings().PlaySlidesToEnd) if QtCore.QSettings().value(self.parent().generalSettingsSection + u'/enable slide loop', QtCore.QVariant(True)).toBool(): self.playSlidesMenu.setDefaultAction(self.playSlidesLoop) @@ -412,9 +412,11 @@ class SlideController(QtGui.QWidget): self.display.videoStop() def servicePrevious(self): + time.sleep(0.1) Receiver.send_message('servicemanager_previous_item') def serviceNext(self): + time.sleep(0.1) Receiver.send_message('servicemanager_next_item') def screenSizeChanged(self): @@ -506,6 +508,11 @@ class SlideController(QtGui.QWidget): self.mediabar.setVisible(False) self.toolbar.makeWidgetsInvisible([u'Song Menu']) self.toolbar.makeWidgetsInvisible(self.loopList) + # Reset the button + self.playSlidesOnce.setChecked(False) + self.playSlidesOnce.setIcon(build_icon(u':/media/media_time.png')) + self.playSlidesLoop.setChecked(False) + self.playSlidesLoop.setIcon(build_icon(u':/media/media_time.png')) if item.is_text(): if QtCore.QSettings().value( self.parent().songsSettingsSection + u'/display songbar', @@ -597,7 +604,8 @@ class SlideController(QtGui.QWidget): log.debug(u'processManagerItem live = %s' % self.isLive) self.onStopLoop() old_item = self.serviceItem - self.serviceItem = serviceItem + # take a copy not a link to the servicemeanager copy. + self.serviceItem = copy.copy(serviceItem) if old_item and self.isLive and old_item.is_capable( ItemCapabilities.ProvidesOwnDisplay): self._resetBlank() @@ -1056,6 +1064,14 @@ class SlideController(QtGui.QWidget): else: self.playSlidesLoop.setChecked(checked) log.debug(u'onPlaySlidesLoop %s' % checked) + if checked: + self.playSlidesLoop.setIcon(build_icon(u':/media/media_stop.png')) + self.playSlidesLoop.setText(UiStrings().StopPlaySlidesInLoop) + self.playSlidesOnce.setIcon(build_icon(u':/media/media_time.png')) + self.playSlidesOnce.setText(UiStrings().PlaySlidesToEnd) + else: + self.playSlidesLoop.setIcon(build_icon(u':/media/media_time.png')) + self.playSlidesLoop.setText(UiStrings().PlaySlidesInLoop) self.playSlidesMenu.setDefaultAction(self.playSlidesLoop) self.playSlidesOnce.setChecked(False) self.onToggleLoop() @@ -1069,6 +1085,14 @@ class SlideController(QtGui.QWidget): else: self.playSlidesOnce.setChecked(checked) log.debug(u'onPlaySlidesOnce %s' % checked) + if checked: + self.playSlidesOnce.setIcon(build_icon(u':/media/media_stop.png')) + self.playSlidesOnce.setText(UiStrings().StopPlaySlidesToEnd) + self.playSlidesLoop.setIcon(build_icon(u':/media/media_time.png')) + self.playSlidesLoop.setText(UiStrings().PlaySlidesInLoop) + else: + self.playSlidesOnce.setIcon(build_icon(u':/media/media_time')) + self.playSlidesOnce.setText(UiStrings().PlaySlidesToEnd) self.playSlidesMenu.setDefaultAction(self.playSlidesOnce) self.playSlidesLoop.setChecked(False) self.onToggleLoop() diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index d84fe7e1f..69c229532 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -39,7 +39,8 @@ from openlp.core.lib import OpenLPToolbar, get_text_file_string, build_icon, \ check_directory_exists from openlp.core.lib.theme import ThemeXML, BackgroundType, VerticalType, \ BackgroundGradientType -from openlp.core.lib.ui import UiStrings, critical_error_message_box +from openlp.core.lib.ui import UiStrings, critical_error_message_box, \ + context_menu_action, context_menu_separator from openlp.core.theme import Theme from openlp.core.ui import FileRenameForm, ThemeForm from openlp.core.utils import AppLocation, delete_file, file_is_unicode, \ @@ -104,25 +105,29 @@ class ThemeManager(QtGui.QWidget): self.contextMenu) # build the context menu self.menu = QtGui.QMenu() - self.editAction = self.menu.addAction( - translate('OpenLP.ThemeManager', '&Edit Theme')) - self.editAction.setIcon(build_icon(u':/themes/theme_edit.png')) - self.copyAction = self.menu.addAction( - translate('OpenLP.ThemeManager', '&Copy Theme')) - self.copyAction.setIcon(build_icon(u':/themes/theme_edit.png')) - self.renameAction = self.menu.addAction( - translate('OpenLP.ThemeManager', '&Rename Theme')) - self.renameAction.setIcon(build_icon(u':/themes/theme_edit.png')) - self.deleteAction = self.menu.addAction( - translate('OpenLP.ThemeManager', '&Delete Theme')) - self.deleteAction.setIcon(build_icon(u':/general/general_delete.png')) - self.separator = self.menu.addSeparator() - self.globalAction = self.menu.addAction( - translate('OpenLP.ThemeManager', 'Set As &Global Default')) - self.globalAction.setIcon(build_icon(u':/general/general_export.png')) - self.exportAction = self.menu.addAction( - translate('OpenLP.ThemeManager', '&Export Theme')) - self.exportAction.setIcon(build_icon(u':/general/general_export.png')) + self.editAction = context_menu_action( + self.menu, u':/themes/theme_edit.png', + translate('OpenLP.ThemeManager', '&Edit Theme'), self.onEditTheme) + self.copyAction = context_menu_action( + self.menu, u':/themes/theme_edit.png', + translate('OpenLP.ThemeManager', '&Copy Theme'), self.onCopyTheme) + self.renameAction = context_menu_action( + self.menu, u':/themes/theme_edit.png', + translate('OpenLP.ThemeManager', '&Rename Theme'), + self.onRenameTheme) + self.deleteAction = context_menu_action( + self.menu, u':/general/general_delete.png', + translate('OpenLP.ThemeManager', '&Delete Theme'), + self.onDeleteTheme) + context_menu_separator(self.menu) + self.globalAction = context_menu_action( + self.menu, u':/general/general_export.png', + translate('OpenLP.ThemeManager', 'Set As &Global Default'), + self.changeGlobalFromScreen) + self.exportAction = context_menu_action( + self.menu, u':/general/general_export.png', + translate('OpenLP.ThemeManager', '&Export Theme'), + self.onExportTheme) # Signals QtCore.QObject.connect(self.themeListWidget, QtCore.SIGNAL(u'doubleClicked(QModelIndex)'), @@ -198,19 +203,7 @@ class ThemeManager(QtGui.QWidget): self.deleteAction.setVisible(True) self.renameAction.setVisible(True) self.globalAction.setVisible(True) - action = self.menu.exec_(self.themeListWidget.mapToGlobal(point)) - if action == self.editAction: - self.onEditTheme() - if action == self.copyAction: - self.onCopyTheme() - if action == self.renameAction: - self.onRenameTheme() - if action == self.deleteAction: - self.onDeleteTheme() - if action == self.globalAction: - self.changeGlobalFromScreen() - if action == self.exportAction: - self.onExportTheme() + self.menu.exec_(self.themeListWidget.mapToGlobal(point)) def changeGlobalFromTab(self, themeName): """ @@ -298,11 +291,10 @@ class ThemeManager(QtGui.QWidget): Copies an existing theme to a new name """ item = self.themeListWidget.currentItem() - oldThemeName = unicode( - translate('OpenLP.ThemeManager', 'Copy of %s', - 'Copy of ')) % unicode( - item.data(QtCore.Qt.UserRole).toString()) - self.fileRenameForm.fileNameEdit.setText(oldThemeName) + oldThemeName = unicode(item.data(QtCore.Qt.UserRole).toString()) + self.fileRenameForm.fileNameEdit.setText( + unicode(translate('OpenLP.ThemeManager', + 'Copy of %s','Copy of ')) % oldThemeName) if self.fileRenameForm.exec_(True): newThemeName = unicode(self.fileRenameForm.fileNameEdit.text()) if self.checkIfThemeExists(newThemeName): diff --git a/openlp/core/ui/themestab.py b/openlp/core/ui/themestab.py index 86087e82f..572efdf4b 100644 --- a/openlp/core/ui/themestab.py +++ b/openlp/core/ui/themestab.py @@ -37,7 +37,7 @@ class ThemesTab(SettingsTab): """ def __init__(self, parent, mainwindow): self.mainwindow = mainwindow - generalTranslated = translate('ThemeTab', 'Themes') + generalTranslated = translate('OpenLP.ThemesTab', 'Themes') SettingsTab.__init__(self, parent, u'Themes', generalTranslated) self.icon_path = u':/themes/theme_new.png' diff --git a/openlp/core/utils/__init__.py b/openlp/core/utils/__init__.py index 317083799..3612bb002 100644 --- a/openlp/core/utils/__init__.py +++ b/openlp/core/utils/__init__.py @@ -53,6 +53,7 @@ APPLICATION_VERSION = {} IMAGES_FILTER = None UNO_CONNECTION_TYPE = u'pipe' #UNO_CONNECTION_TYPE = u'socket' +VERSION_SPLITTER = re.compile(r'([0-9]+).([0-9]+).([0-9]+)(?:-bzr([0-9]+))?') class VersionThread(QtCore.QThread): """ @@ -61,8 +62,6 @@ class VersionThread(QtCore.QThread): """ def __init__(self, parent): QtCore.QThread.__init__(self, parent) - self.version_splitter = re.compile( - r'([0-9]+).([0-9]+).([0-9]+)(?:-bzr([0-9]+))?') def run(self): """ @@ -73,7 +72,7 @@ class VersionThread(QtCore.QThread): version = check_latest_version(app_version) remote_version = {} local_version = {} - match = self.version_splitter.match(version) + match = VERSION_SPLITTER.match(version) if match: remote_version[u'major'] = int(match.group(1)) remote_version[u'minor'] = int(match.group(2)) @@ -82,7 +81,7 @@ class VersionThread(QtCore.QThread): remote_version[u'revision'] = int(match.group(4)) else: return - match = self.version_splitter.match(app_version[u'full']) + match = VERSION_SPLITTER.match(app_version[u'full']) if match: local_version[u'major'] = int(match.group(1)) local_version[u'minor'] = int(match.group(2)) @@ -387,6 +386,17 @@ def split_filename(path): else: return os.path.split(path) +def clean_filename(filename): + """ + Removes invalid characters from the given ``filename``. + + ``filename`` + The "dirty" file name to clean. + """ + if not isinstance(filename, unicode): + filename = unicode(filename, u'utf-8') + return re.sub(r'[/\\?*|<>\[\]":<>+%]+', u'_', filename).strip(u'_') + def delete_file(file_path_name): """ Deletes a file from the system. @@ -460,25 +470,6 @@ def file_is_unicode(filename): return None return ucsfile -def string_is_unicode(test_string): - """ - Makes sure a string is unicode. - - ``test_string`` - The string to confirm is unicode. - """ - return_string = u'' - if not test_string: - return return_string - if isinstance(test_string, unicode): - return_string = test_string - if not isinstance(test_string, unicode): - try: - return_string = unicode(test_string, u'utf-8') - except UnicodeError: - log.exception("Error encoding string to unicode") - return return_string - def get_uno_command(): """ Returns the UNO command to launch an openoffice.org instance. @@ -511,5 +502,5 @@ from actions import ActionList __all__ = [u'AppLocation', u'get_application_version', u'check_latest_version', u'add_actions', u'get_filesystem_encoding', u'LanguageManager', - u'ActionList', u'get_web_page', u'file_is_unicode', u'string_is_unicode', - u'get_uno_command', u'get_uno_instance', u'delete_file'] + u'ActionList', u'get_web_page', u'file_is_unicode', u'get_uno_command', + u'get_uno_instance', u'delete_file', u'clean_filename'] diff --git a/openlp/plugins/alerts/alertsplugin.py b/openlp/plugins/alerts/alertsplugin.py index 41c2e2211..21db1972a 100644 --- a/openlp/plugins/alerts/alertsplugin.py +++ b/openlp/plugins/alerts/alertsplugin.py @@ -27,7 +27,7 @@ import logging -from PyQt4 import QtCore, QtGui +from PyQt4 import QtCore from openlp.core.lib import Plugin, StringContent, build_icon, translate from openlp.core.lib.db import Manager @@ -43,7 +43,7 @@ class AlertsPlugin(Plugin): log.info(u'Alerts Plugin loaded') def __init__(self, plugin_helpers): - Plugin.__init__(self, u'Alerts', plugin_helpers, + Plugin.__init__(self, u'alerts', plugin_helpers, settings_tab_class=AlertsTab) self.weight = -3 self.icon_path = u':/plugins/plugin_alerts.png' @@ -104,7 +104,7 @@ class AlertsPlugin(Plugin): def about(self): about_text = translate('AlertsPlugin', 'Alerts Plugin' '
The alert plugin controls the displaying of nursery alerts ' - 'on the display screen') + 'on the display screen.') return about_text def setPluginTextStrings(self): diff --git a/openlp/plugins/alerts/forms/alertform.py b/openlp/plugins/alerts/forms/alertform.py index 8de7744aa..45d283f28 100644 --- a/openlp/plugins/alerts/forms/alertform.py +++ b/openlp/plugins/alerts/forms/alertform.py @@ -176,8 +176,8 @@ class AlertForm(QtGui.QDialog, Ui_AlertDialog): # We found '<>' in the alert text, but the ParameterEdit field is empty. if text.find(u'<>') != -1 and not self.parameterEdit.text() and \ QtGui.QMessageBox.question(self, - translate('AlertPlugin.AlertForm', 'No Parameter Found'), - translate('AlertPlugin.AlertForm', 'You have not entered a ' + translate('AlertsPlugin.AlertForm', 'No Parameter Found'), + translate('AlertsPlugin.AlertForm', 'You have not entered a ' 'parameter to be replaced.\nDo you want to continue anyway?'), QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.No | QtGui.QMessageBox.Yes)) == QtGui.QMessageBox.No: @@ -187,8 +187,8 @@ class AlertForm(QtGui.QDialog, Ui_AlertDialog): # in the alert text. elif text.find(u'<>') == -1 and self.parameterEdit.text() and \ QtGui.QMessageBox.question(self, - translate('AlertPlugin.AlertForm', 'No Placeholder Found'), - translate('AlertPlugin.AlertForm', 'The alert text does not' + translate('AlertsPlugin.AlertForm', 'No Placeholder Found'), + translate('AlertsPlugin.AlertForm', 'The alert text does not' ' contain \'<>\'.\nDo you want to continue anyway?'), QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.No | QtGui.QMessageBox.Yes)) == QtGui.QMessageBox.No: diff --git a/openlp/plugins/alerts/lib/alertstab.py b/openlp/plugins/alerts/lib/alertstab.py index 0a1eb3e75..15577fd0e 100644 --- a/openlp/plugins/alerts/lib/alertstab.py +++ b/openlp/plugins/alerts/lib/alertstab.py @@ -44,85 +44,85 @@ class AlertsTab(SettingsTab): self.fontGroupBox.setObjectName(u'fontGroupBox') self.fontLayout = QtGui.QFormLayout(self.fontGroupBox) self.fontLayout.setObjectName(u'fontLayout') - self.FontLabel = QtGui.QLabel(self.fontGroupBox) - self.FontLabel.setObjectName(u'FontLabel') - self.FontComboBox = QtGui.QFontComboBox(self.fontGroupBox) - self.FontComboBox.setObjectName(u'FontComboBox') - self.fontLayout.addRow(self.FontLabel, self.FontComboBox) - self.FontColorLabel = QtGui.QLabel(self.fontGroupBox) - self.FontColorLabel.setObjectName(u'FontColorLabel') - self.ColorLayout = QtGui.QHBoxLayout() - self.ColorLayout.setObjectName(u'ColorLayout') - self.FontColorButton = QtGui.QPushButton(self.fontGroupBox) - self.FontColorButton.setObjectName(u'FontColorButton') - self.ColorLayout.addWidget(self.FontColorButton) - self.ColorLayout.addSpacing(20) - self.BackgroundColorLabel = QtGui.QLabel(self.fontGroupBox) - self.BackgroundColorLabel.setObjectName(u'BackgroundColorLabel') - self.ColorLayout.addWidget(self.BackgroundColorLabel) - self.BackgroundColorButton = QtGui.QPushButton(self.fontGroupBox) - self.BackgroundColorButton.setObjectName(u'BackgroundColorButton') - self.ColorLayout.addWidget(self.BackgroundColorButton) - self.fontLayout.addRow(self.FontColorLabel, self.ColorLayout) - self.FontSizeLabel = QtGui.QLabel(self.fontGroupBox) - self.FontSizeLabel.setObjectName(u'FontSizeLabel') - self.FontSizeSpinBox = QtGui.QSpinBox(self.fontGroupBox) - self.FontSizeSpinBox.setObjectName(u'FontSizeSpinBox') - self.fontLayout.addRow(self.FontSizeLabel, self.FontSizeSpinBox) - self.TimeoutLabel = QtGui.QLabel(self.fontGroupBox) - self.TimeoutLabel.setObjectName(u'TimeoutLabel') - self.TimeoutSpinBox = QtGui.QSpinBox(self.fontGroupBox) - self.TimeoutSpinBox.setMaximum(180) - self.TimeoutSpinBox.setObjectName(u'TimeoutSpinBox') - self.fontLayout.addRow(self.TimeoutLabel, self.TimeoutSpinBox) + self.fontLabel = QtGui.QLabel(self.fontGroupBox) + self.fontLabel.setObjectName(u'fontLabel') + self.fontComboBox = QtGui.QFontComboBox(self.fontGroupBox) + self.fontComboBox.setObjectName(u'fontComboBox') + self.fontLayout.addRow(self.fontLabel, self.fontComboBox) + self.fontColorLabel = QtGui.QLabel(self.fontGroupBox) + self.fontColorLabel.setObjectName(u'fontColorLabel') + self.colorLayout = QtGui.QHBoxLayout() + self.colorLayout.setObjectName(u'colorLayout') + self.fontColorButton = QtGui.QPushButton(self.fontGroupBox) + self.fontColorButton.setObjectName(u'fontColorButton') + self.colorLayout.addWidget(self.fontColorButton) + self.colorLayout.addSpacing(20) + self.backgroundColorLabel = QtGui.QLabel(self.fontGroupBox) + self.backgroundColorLabel.setObjectName(u'backgroundColorLabel') + self.colorLayout.addWidget(self.backgroundColorLabel) + self.backgroundColorButton = QtGui.QPushButton(self.fontGroupBox) + self.backgroundColorButton.setObjectName(u'backgroundColorButton') + self.colorLayout.addWidget(self.backgroundColorButton) + self.fontLayout.addRow(self.fontColorLabel, self.colorLayout) + self.fontSizeLabel = QtGui.QLabel(self.fontGroupBox) + self.fontSizeLabel.setObjectName(u'fontSizeLabel') + self.fontSizeSpinBox = QtGui.QSpinBox(self.fontGroupBox) + self.fontSizeSpinBox.setObjectName(u'fontSizeSpinBox') + self.fontLayout.addRow(self.fontSizeLabel, self.fontSizeSpinBox) + self.timeoutLabel = QtGui.QLabel(self.fontGroupBox) + self.timeoutLabel.setObjectName(u'timeoutLabel') + self.timeoutSpinBox = QtGui.QSpinBox(self.fontGroupBox) + self.timeoutSpinBox.setMaximum(180) + self.timeoutSpinBox.setObjectName(u'timeoutSpinBox') + self.fontLayout.addRow(self.timeoutLabel, self.timeoutSpinBox) create_valign_combo(self, self.fontGroupBox, self.fontLayout) self.leftLayout.addWidget(self.fontGroupBox) self.leftLayout.addStretch() - self.PreviewGroupBox = QtGui.QGroupBox(self.rightColumn) - self.PreviewGroupBox.setObjectName(u'PreviewGroupBox') - self.PreviewLayout = QtGui.QVBoxLayout(self.PreviewGroupBox) - self.PreviewLayout.setObjectName(u'PreviewLayout') - self.FontPreview = QtGui.QLineEdit(self.PreviewGroupBox) - self.FontPreview.setObjectName(u'FontPreview') - self.PreviewLayout.addWidget(self.FontPreview) - self.rightLayout.addWidget(self.PreviewGroupBox) + self.previewGroupBox = QtGui.QGroupBox(self.rightColumn) + self.previewGroupBox.setObjectName(u'previewGroupBox') + self.previewLayout = QtGui.QVBoxLayout(self.previewGroupBox) + self.previewLayout.setObjectName(u'previewLayout') + self.fontPreview = QtGui.QLineEdit(self.previewGroupBox) + self.fontPreview.setObjectName(u'fontPreview') + self.previewLayout.addWidget(self.fontPreview) + self.rightLayout.addWidget(self.previewGroupBox) self.rightLayout.addStretch() # Signals and slots - QtCore.QObject.connect(self.BackgroundColorButton, + QtCore.QObject.connect(self.backgroundColorButton, QtCore.SIGNAL(u'pressed()'), self.onBackgroundColorButtonClicked) - QtCore.QObject.connect(self.FontColorButton, + QtCore.QObject.connect(self.fontColorButton, QtCore.SIGNAL(u'pressed()'), self.onFontColorButtonClicked) - QtCore.QObject.connect(self.FontComboBox, + QtCore.QObject.connect(self.fontComboBox, QtCore.SIGNAL(u'activated(int)'), self.onFontComboBoxClicked) - QtCore.QObject.connect(self.TimeoutSpinBox, + QtCore.QObject.connect(self.timeoutSpinBox, QtCore.SIGNAL(u'valueChanged(int)'), self.onTimeoutSpinBoxChanged) - QtCore.QObject.connect(self.FontSizeSpinBox, + QtCore.QObject.connect(self.fontSizeSpinBox, QtCore.SIGNAL(u'valueChanged(int)'), self.onFontSizeSpinBoxChanged) def retranslateUi(self): self.fontGroupBox.setTitle( translate('AlertsPlugin.AlertsTab', 'Font')) - self.FontLabel.setText( + self.fontLabel.setText( translate('AlertsPlugin.AlertsTab', 'Font name:')) - self.FontColorLabel.setText( + self.fontColorLabel.setText( translate('AlertsPlugin.AlertsTab', 'Font color:')) - self.BackgroundColorLabel.setText( + self.backgroundColorLabel.setText( translate('AlertsPlugin.AlertsTab', 'Background color:')) - self.FontSizeLabel.setText( + self.fontSizeLabel.setText( translate('AlertsPlugin.AlertsTab', 'Font size:')) - self.FontSizeSpinBox.setSuffix(UiStrings().FontSizePtUnit) - self.TimeoutLabel.setText( + self.fontSizeSpinBox.setSuffix(UiStrings().FontSizePtUnit) + self.timeoutLabel.setText( translate('AlertsPlugin.AlertsTab', 'Alert timeout:')) - self.TimeoutSpinBox.setSuffix(UiStrings().Seconds) - self.PreviewGroupBox.setTitle(UiStrings().Preview) - self.FontPreview.setText(UiStrings().OLPV2) + self.timeoutSpinBox.setSuffix(UiStrings().Seconds) + self.previewGroupBox.setTitle(UiStrings().Preview) + self.fontPreview.setText(UiStrings().OLPV2) def onBackgroundColorButtonClicked(self): new_color = QtGui.QColorDialog.getColor( QtGui.QColor(self.bg_color), self) if new_color.isValid(): self.bg_color = new_color.name() - self.BackgroundColorButton.setStyleSheet( + self.backgroundColorButton.setStyleSheet( u'background-color: %s' % self.bg_color) self.updateDisplay() @@ -134,15 +134,15 @@ class AlertsTab(SettingsTab): QtGui.QColor(self.font_color), self) if new_color.isValid(): self.font_color = new_color.name() - self.FontColorButton.setStyleSheet( + self.fontColorButton.setStyleSheet( u'background-color: %s' % self.font_color) self.updateDisplay() def onTimeoutSpinBoxChanged(self): - self.timeout = self.TimeoutSpinBox.value() + self.timeout = self.timeoutSpinBox.value() def onFontSizeSpinBoxChanged(self): - self.font_size = self.FontSizeSpinBox.value() + self.font_size = self.fontSizeSpinBox.value() self.updateDisplay() def load(self): @@ -160,16 +160,16 @@ class AlertsTab(SettingsTab): self.location = settings.value( u'location', QtCore.QVariant(1)).toInt()[0] settings.endGroup() - self.FontSizeSpinBox.setValue(self.font_size) - self.TimeoutSpinBox.setValue(self.timeout) - self.FontColorButton.setStyleSheet( + self.fontSizeSpinBox.setValue(self.font_size) + self.timeoutSpinBox.setValue(self.timeout) + self.fontColorButton.setStyleSheet( u'background-color: %s' % self.font_color) - self.BackgroundColorButton.setStyleSheet( + self.backgroundColorButton.setStyleSheet( u'background-color: %s' % self.bg_color) self.verticalComboBox.setCurrentIndex(self.location) font = QtGui.QFont() font.setFamily(self.font_face) - self.FontComboBox.setCurrentFont(font) + self.fontComboBox.setCurrentFont(font) self.updateDisplay() def save(self): @@ -178,7 +178,7 @@ class AlertsTab(SettingsTab): settings.setValue(u'background color', QtCore.QVariant(self.bg_color)) settings.setValue(u'font color', QtCore.QVariant(self.font_color)) settings.setValue(u'font size', QtCore.QVariant(self.font_size)) - self.font_face = self.FontComboBox.currentFont().family() + self.font_face = self.fontComboBox.currentFont().family() settings.setValue(u'font face', QtCore.QVariant(self.font_face)) settings.setValue(u'timeout', QtCore.QVariant(self.timeout)) self.location = self.verticalComboBox.currentIndex() @@ -187,10 +187,10 @@ class AlertsTab(SettingsTab): def updateDisplay(self): font = QtGui.QFont() - font.setFamily(self.FontComboBox.currentFont().family()) + font.setFamily(self.fontComboBox.currentFont().family()) font.setBold(True) font.setPointSize(self.font_size) - self.FontPreview.setFont(font) - self.FontPreview.setStyleSheet(u'background-color: %s; color: %s' % + self.fontPreview.setFont(font) + self.fontPreview.setStyleSheet(u'background-color: %s; color: %s' % (self.bg_color, self.font_color)) diff --git a/openlp/plugins/bibles/bibleplugin.py b/openlp/plugins/bibles/bibleplugin.py index a5abc32d2..619581b17 100644 --- a/openlp/plugins/bibles/bibleplugin.py +++ b/openlp/plugins/bibles/bibleplugin.py @@ -41,7 +41,7 @@ class BiblePlugin(Plugin): log.info(u'Bible Plugin loaded') def __init__(self, plugin_helpers): - Plugin.__init__(self, u'Bibles', plugin_helpers, + Plugin.__init__(self, u'bibles', plugin_helpers, BibleMediaItem, BiblesTab) self.weight = -9 self.icon_path = u':/plugins/plugin_bibles.png' @@ -117,9 +117,9 @@ class BiblePlugin(Plugin): self.toolsUpgradeItem = QtGui.QAction(tools_menu) self.toolsUpgradeItem.setObjectName(u'toolsUpgradeItem') self.toolsUpgradeItem.setText( - translate('BiblePlugin', '&Upgrade older Bibles')) + translate('BiblesPlugin', '&Upgrade older Bibles')) self.toolsUpgradeItem.setStatusTip( - translate('BiblePlugin', 'Upgrade the Bible databases to the ' + translate('BiblesPlugin', 'Upgrade the Bible databases to the ' 'latest format.')) tools_menu.addAction(self.toolsUpgradeItem) QtCore.QObject.connect(self.toolsUpgradeItem, diff --git a/openlp/plugins/bibles/forms/bibleimportform.py b/openlp/plugins/bibles/forms/bibleimportform.py index de1574eaa..b5478e514 100644 --- a/openlp/plugins/bibles/forms/bibleimportform.py +++ b/openlp/plugins/bibles/forms/bibleimportform.py @@ -27,7 +27,6 @@ """ The bible import functions for OpenLP """ -import csv import logging import os import os.path @@ -39,7 +38,7 @@ from openlp.core.lib import Receiver, translate from openlp.core.lib.db import delete_database from openlp.core.lib.ui import UiStrings, critical_error_message_box from openlp.core.ui.wizard import OpenLPWizard, WizardStrings -from openlp.core.utils import AppLocation, string_is_unicode +from openlp.core.utils import AppLocation from openlp.plugins.bibles.lib.manager import BibleFormat from openlp.plugins.bibles.lib.db import BiblesResourcesDB, clean_filename diff --git a/openlp/plugins/bibles/forms/bibleupgradeform.py b/openlp/plugins/bibles/forms/bibleupgradeform.py index 14936a340..322e3219a 100644 --- a/openlp/plugins/bibles/forms/bibleupgradeform.py +++ b/openlp/plugins/bibles/forms/bibleupgradeform.py @@ -27,20 +27,19 @@ The bible import functions for OpenLP """ import logging -import os.path -import re +import os import shutil +from tempfile import gettempdir from PyQt4 import QtCore, QtGui from openlp.core.lib import Receiver, SettingsManager, translate, \ check_directory_exists -from openlp.core.lib.db import delete_database from openlp.core.lib.ui import UiStrings, critical_error_message_box from openlp.core.ui.wizard import OpenLPWizard, WizardStrings from openlp.core.utils import AppLocation, delete_file -from openlp.plugins.bibles.lib.db import BibleDB, BibleMeta, OldBibleDB,\ - BiblesResourcesDB, clean_filename +from openlp.plugins.bibles.lib.db import BibleDB, BibleMeta, OldBibleDB, \ + BiblesResourcesDB from openlp.plugins.bibles.lib.http import BSExtract, BGExtract, CWExtract log = logging.getLogger(__name__) @@ -70,8 +69,8 @@ class BibleUpgradeForm(OpenLPWizard): self.mediaItem = bibleplugin.mediaItem self.suffix = u'.sqlite' self.settingsSection = u'bibles' - self.path = AppLocation.get_section_data_path( - self.settingsSection) + self.path = AppLocation.get_section_data_path(self.settingsSection) + self.temp_dir = os.path.join(gettempdir(), u'openlp') self.files = self.manager.old_bible_databases self.success = {} self.newbibles = {} @@ -93,20 +92,6 @@ class BibleUpgradeForm(OpenLPWizard): log.debug(u'Stopping import') self.stop_import_flag = True - def onCheckBoxIndexChanged(self, index): - """ - Show/Hide warnings if CheckBox state has changed - """ - for number, filename in enumerate(self.files): - if not self.checkBox[number].checkState() == QtCore.Qt.Checked: - self.verticalWidget[number].hide() - self.formWidget[number].hide() - else: - version_name = unicode(self.versionNameEdit[number].text()) - if self.manager.exists(version_name): - self.verticalWidget[number].show() - self.formWidget[number].show() - def reject(self): """ Stop the wizard on cancel button, close button or ESC key. @@ -115,8 +100,6 @@ class BibleUpgradeForm(OpenLPWizard): self.stop_import_flag = True if not self.currentPage() == self.progressPage: self.done(QtGui.QDialog.Rejected) - else: - self.postWizard() def onCurrentIdChanged(self, pageId): """ @@ -126,7 +109,7 @@ class BibleUpgradeForm(OpenLPWizard): self.preWizard() self.performWizard() self.postWizard() - elif self.page(pageId) == self.selectPage and self.maxBibles == 0: + elif self.page(pageId) == self.selectPage and not self.files: self.next() def onBackupBrowseButtonClicked(self): @@ -245,78 +228,13 @@ class BibleUpgradeForm(OpenLPWizard): Add the content to the scrollArea. """ self.checkBox = {} - self.versionNameEdit = {} - self.versionNameLabel = {} - self.versionInfoLabel = {} - self.versionInfoPixmap = {} - self.verticalWidget = {} - self.horizontalLayout = {} - self.formWidget = {} - self.formLayoutAttention = {} for number, filename in enumerate(self.files): bible = OldBibleDB(self.mediaItem, path=self.path, file=filename[0]) self.checkBox[number] = QtGui.QCheckBox(self.scrollAreaContents) - checkBoxName = u'checkBox[%d]' % number - self.checkBox[number].setObjectName(checkBoxName) + self.checkBox[number].setObjectName(u'checkBox[%d]' % number) self.checkBox[number].setText(bible.get_name()) self.checkBox[number].setCheckState(QtCore.Qt.Checked) self.formLayout.addWidget(self.checkBox[number]) - self.verticalWidget[number] = QtGui.QWidget(self.scrollAreaContents) - verticalWidgetName = u'verticalWidget[%d]' % number - self.verticalWidget[number].setObjectName(verticalWidgetName) - self.horizontalLayout[number] = QtGui.QHBoxLayout( - self.verticalWidget[number]) - self.horizontalLayout[number].setContentsMargins(25, 0, 0, 0) - horizontalLayoutName = u'horizontalLayout[%d]' % number - self.horizontalLayout[number].setObjectName(horizontalLayoutName) - self.versionInfoPixmap[number] = QtGui.QLabel( - self.verticalWidget[number]) - versionInfoPixmapName = u'versionInfoPixmap[%d]' % number - self.versionInfoPixmap[number].setObjectName(versionInfoPixmapName) - self.versionInfoPixmap[number].setPixmap(QtGui.QPixmap( - u':/bibles/bibles_upgrade_alert.png')) - self.versionInfoPixmap[number].setAlignment(QtCore.Qt.AlignRight) - self.horizontalLayout[number].addWidget( - self.versionInfoPixmap[number]) - self.versionInfoLabel[number] = QtGui.QLabel( - self.verticalWidget[number]) - versionInfoLabelName = u'versionInfoLabel[%d]' % number - self.versionInfoLabel[number].setObjectName(versionInfoLabelName) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, - QtGui.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth( - self.versionInfoLabel[number].sizePolicy().hasHeightForWidth()) - self.versionInfoLabel[number].setSizePolicy(sizePolicy) - self.horizontalLayout[number].addWidget( - self.versionInfoLabel[number]) - self.formLayout.addWidget(self.verticalWidget[number]) - self.formWidget[number] = QtGui.QWidget(self.scrollAreaContents) - formWidgetName = u'formWidget[%d]' % number - self.formWidget[number].setObjectName(formWidgetName) - self.formLayoutAttention[number] = QtGui.QFormLayout( - self.formWidget[number]) - self.formLayoutAttention[number].setContentsMargins(25, 0, 0, 5) - formLayoutAttentionName = u'formLayoutAttention[%d]' % number - self.formLayoutAttention[number].setObjectName( - formLayoutAttentionName) - self.versionNameLabel[number] = QtGui.QLabel( - self.formWidget[number]) - self.versionNameLabel[number].setObjectName(u'VersionNameLabel') - self.formLayoutAttention[number].setWidget(0, - QtGui.QFormLayout.LabelRole, self.versionNameLabel[number]) - self.versionNameEdit[number] = QtGui.QLineEdit( - self.formWidget[number]) - self.versionNameEdit[number].setObjectName(u'VersionNameEdit') - self.formLayoutAttention[number].setWidget(0, - QtGui.QFormLayout.FieldRole, self.versionNameEdit[number]) - self.versionNameEdit[number].setText(bible.get_name()) - self.formLayout.addWidget(self.formWidget[number]) - # Set up the Signal for the checkbox. - QtCore.QObject.connect(self.checkBox[number], - QtCore.SIGNAL(u'stateChanged(int)'), - self.onCheckBoxIndexChanged) self.spacerItem = QtGui.QSpacerItem(20, 5, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.formLayout.addItem(self.spacerItem) @@ -329,23 +247,6 @@ class BibleUpgradeForm(OpenLPWizard): for number, filename in enumerate(self.files): self.formLayout.removeWidget(self.checkBox[number]) self.checkBox[number].setParent(None) - self.horizontalLayout[number].removeWidget( - self.versionInfoPixmap[number]) - self.versionInfoPixmap[number].setParent(None) - self.horizontalLayout[number].removeWidget( - self.versionInfoLabel[number]) - self.versionInfoLabel[number].setParent(None) - self.formLayout.removeWidget(self.verticalWidget[number]) - self.verticalWidget[number].setParent(None) - self.formLayoutAttention[number].removeWidget( - self.versionNameLabel[number]) - self.versionNameLabel[number].setParent(None) - self.formLayoutAttention[number].removeWidget( - self.versionNameEdit[number]) - self.formLayoutAttention[number].deleteLater() - self.versionNameEdit[number].setParent(None) - self.formLayout.removeWidget(self.formWidget[number]) - self.formWidget[number].setParent(None) self.formLayout.removeItem(self.spacerItem) def retranslateUi(self): @@ -387,12 +288,6 @@ class BibleUpgradeForm(OpenLPWizard): self.selectPage.setSubTitle( translate('BiblesPlugin.UpgradeWizardForm', 'Please select the Bibles to upgrade')) - for number, bible in enumerate(self.files): - self.versionNameLabel[number].setText( - translate('BiblesPlugin.UpgradeWizardForm', 'Version name:')) - self.versionInfoLabel[number].setText( - translate('BiblesPlugin.UpgradeWizardForm', 'This ' - 'Bible still exists. Please change the name or uncheck it.')) self.progressPage.setTitle(translate('BiblesPlugin.UpgradeWizardForm', 'Upgrading')) self.progressPage.setSubTitle( @@ -413,7 +308,7 @@ class BibleUpgradeForm(OpenLPWizard): if not backup_path: critical_error_message_box(UiStrings().EmptyField, translate('BiblesPlugin.UpgradeWizardForm', - 'You need to specify a Backup Directory for your ' + 'You need to specify a backup directory for your ' 'Bibles.')) self.backupDirectoryEdit.setFocus() return False @@ -427,58 +322,16 @@ class BibleUpgradeForm(OpenLPWizard): return False return True elif self.currentPage() == self.selectPage: + check_directory_exists(self.temp_dir) for number, filename in enumerate(self.files): if not self.checkBox[number].checkState() == QtCore.Qt.Checked: continue - version_name = unicode(self.versionNameEdit[number].text()) - if not version_name: - critical_error_message_box(UiStrings().EmptyField, - translate('BiblesPlugin.UpgradeWizardForm', - 'You need to specify a version name for your Bible.')) - self.versionNameEdit[number].setFocus() - return False - elif self.manager.exists(version_name): - critical_error_message_box( - translate('BiblesPlugin.UpgradeWizardForm', - 'Bible Exists'), - translate('BiblesPlugin.UpgradeWizardForm', - 'This Bible already exists. Please upgrade ' - 'a different Bible, delete the existing one or ' - 'uncheck.')) - self.versionNameEdit[number].setFocus() - return False - elif os.path.exists(os.path.join(self.path, clean_filename( - version_name))) and version_name == filename[1]: - newfilename = u'old_database_%s' % filename[0] - if not os.path.exists(os.path.join(self.path, - newfilename)): - os.rename(os.path.join(self.path, filename[0]), - os.path.join(self.path, newfilename)) - self.files[number] = [newfilename, filename[1]] - continue - else: - critical_error_message_box( - translate('BiblesPlugin.UpgradeWizardForm', - 'Bible Exists'), - translate('BiblesPlugin.UpgradeWizardForm', - 'This Bible already exists. Please upgrade ' - 'a different Bible, delete the existing one or ' - 'uncheck.')) - self.verticalWidget[number].show() - self.formWidget[number].show() - self.versionNameEdit[number].setFocus() - return False - elif os.path.exists(os.path.join(self.path, - clean_filename(version_name))): - critical_error_message_box( - translate('BiblesPlugin.UpgradeWizardForm', - 'Bible Exists'), - translate('BiblesPlugin.UpgradeWizardForm', - 'This Bible already exists. Please upgrade ' - 'a different Bible, delete the existing one or ' - 'uncheck.')) - self.versionNameEdit[number].setFocus() - return False + # Move bibles to temp dir. + if not os.path.exists(os.path.join(self.temp_dir, filename[0])): + shutil.move( + os.path.join(self.path, filename[0]), self.temp_dir) + else: + delete_file(os.path.join(self.path, filename[0])) return True if self.currentPage() == self.progressPage: return True @@ -497,16 +350,8 @@ class BibleUpgradeForm(OpenLPWizard): self.files = self.manager.old_bible_databases self.addScrollArea() self.retranslateUi() - self.maxBibles = len(self.files) for number, filename in enumerate(self.files): self.checkBox[number].setCheckState(QtCore.Qt.Checked) - oldname = filename[1] - if self.manager.exists(oldname): - self.verticalWidget[number].show() - self.formWidget[number].show() - else: - self.verticalWidget[number].hide() - self.formWidget[number].hide() self.progressBar.show() self.restart() self.finishButton.setVisible(False) @@ -518,9 +363,8 @@ class BibleUpgradeForm(OpenLPWizard): Prepare the UI for the upgrade. """ OpenLPWizard.preWizard(self) - self.progressLabel.setText(translate( - 'BiblesPlugin.UpgradeWizardForm', - 'Starting Bible upgrade...')) + self.progressLabel.setText( + translate('BiblesPlugin.UpgradeWizardForm', 'Starting upgrade...')) Receiver.send_message(u'openlp_process_events') def performWizard(self): @@ -529,48 +373,42 @@ class BibleUpgradeForm(OpenLPWizard): """ self.include_webbible = False proxy_server = None - if self.maxBibles == 0: + if not self.files: self.progressLabel.setText( translate('BiblesPlugin.UpgradeWizardForm', 'There are no ' - 'Bibles available to upgrade.')) + 'Bibles that need to be upgraded.')) self.progressBar.hide() return - self.maxBibles = 0 + max_bibles = 0 for number, file in enumerate(self.files): if self.checkBox[number].checkState() == QtCore.Qt.Checked: - self.maxBibles += 1 - number = 0 - for biblenumber, filename in enumerate(self.files): + max_bibles += 1 + oldBible = None + for number, filename in enumerate(self.files): + # Close the previous bible's connection. + if oldBible is not None: + oldBible.close_connection() + # Set to None to make obvious that we have already closed the + # database. + oldBible = None if self.stop_import_flag: - bible_failed = True + self.success[number] = False break - bible_failed = False - self.success[biblenumber] = False - if not self.checkBox[biblenumber].checkState() == QtCore.Qt.Checked: + if not self.checkBox[number].checkState() == QtCore.Qt.Checked: + self.success[number] = False continue self.progressBar.reset() - oldbible = OldBibleDB(self.mediaItem, path=self.path, + oldBible = OldBibleDB(self.mediaItem, path=self.temp_dir, file=filename[0]) name = filename[1] - if name is None: - delete_file(os.path.join(self.path, filename[0])) - self.incrementProgressBar(unicode(translate( - 'BiblesPlugin.UpgradeWizardForm', - 'Upgrading Bible %s of %s: "%s"\nFailed')) % - (number + 1, self.maxBibles, name), - self.progressBar.maximum() - self.progressBar.value()) - number += 1 - continue self.progressLabel.setText(unicode(translate( 'BiblesPlugin.UpgradeWizardForm', 'Upgrading Bible %s of %s: "%s"\nUpgrading ...')) % - (number + 1, self.maxBibles, name)) - if os.path.exists(os.path.join(self.path, filename[0])): - name = unicode(self.versionNameEdit[biblenumber].text()) + (number + 1, max_bibles, name)) self.newbibles[number] = BibleDB(self.mediaItem, path=self.path, - name=name) + name=name, file=filename[0]) self.newbibles[number].register(self.plugin.upgrade_wizard) - metadata = oldbible.get_metadata() + metadata = oldBible.get_metadata() webbible = False meta_data = {} for meta in metadata: @@ -597,7 +435,7 @@ class BibleUpgradeForm(OpenLPWizard): u'name: "%s" failed' % ( meta_data[u'download source'], meta_data[u'download name'])) - delete_database(self.path, clean_filename(name)) + self.newbibles[number].session.close() del self.newbibles[number] critical_error_message_box( translate('BiblesPlugin.UpgradeWizardForm', @@ -608,9 +446,9 @@ class BibleUpgradeForm(OpenLPWizard): self.incrementProgressBar(unicode(translate( 'BiblesPlugin.UpgradeWizardForm', 'Upgrading Bible %s of %s: "%s"\nFailed')) % - (number + 1, self.maxBibles, name), + (number + 1, max_bibles, name), self.progressBar.maximum() - self.progressBar.value()) - number += 1 + self.success[number] = False continue bible = BiblesResourcesDB.get_webbible( meta_data[u'download name'], @@ -623,25 +461,25 @@ class BibleUpgradeForm(OpenLPWizard): language_id = self.newbibles[number].get_language(name) if not language_id: log.warn(u'Upgrading from "%s" failed' % filename[0]) - delete_database(self.path, clean_filename(name)) + self.newbibles[number].session.close() del self.newbibles[number] self.incrementProgressBar(unicode(translate( 'BiblesPlugin.UpgradeWizardForm', 'Upgrading Bible %s of %s: "%s"\nFailed')) % - (number + 1, self.maxBibles, name), + (number + 1, max_bibles, name), self.progressBar.maximum() - self.progressBar.value()) - number += 1 + self.success[number] = False continue self.progressBar.setMaximum(len(books)) for book in books: if self.stop_import_flag: - bible_failed = True + self.success[number] = False break self.incrementProgressBar(unicode(translate( 'BiblesPlugin.UpgradeWizardForm', 'Upgrading Bible %s of %s: "%s"\n' 'Upgrading %s ...')) % - (number + 1, self.maxBibles, name, book)) + (number + 1, max_bibles, name, book)) book_ref_id = self.newbibles[number].\ get_book_ref_id_by_name(book, len(books), language_id) if not book_ref_id: @@ -649,24 +487,24 @@ class BibleUpgradeForm(OpenLPWizard): u'name: "%s" aborted by user' % ( meta_data[u'download source'], meta_data[u'download name'])) - delete_database(self.path, clean_filename(name)) + self.newbibles[number].session.close() del self.newbibles[number] - bible_failed = True + self.success[number] = False break book_details = BiblesResourcesDB.get_book_by_id(book_ref_id) db_book = self.newbibles[number].create_book(book, book_ref_id, book_details[u'testament_id']) - # Try to import still downloaded verses - oldbook = oldbible.get_book(book) + # Try to import already downloaded verses. + oldbook = oldBible.get_book(book) if oldbook: - verses = oldbible.get_verses(oldbook[u'id']) + verses = oldBible.get_verses(oldbook[u'id']) if not verses: log.warn(u'No verses found to import for book ' u'"%s"', book) continue for verse in verses: if self.stop_import_flag: - bible_failed = True + self.success[number] = False break self.newbibles[number].create_verse(db_book.id, int(verse[u'chapter']), @@ -680,40 +518,40 @@ class BibleUpgradeForm(OpenLPWizard): language_id = self.newbibles[number].get_language(name) if not language_id: log.warn(u'Upgrading books from "%s" failed' % name) - delete_database(self.path, clean_filename(name)) + self.newbibles[number].session.close() del self.newbibles[number] self.incrementProgressBar(unicode(translate( 'BiblesPlugin.UpgradeWizardForm', 'Upgrading Bible %s of %s: "%s"\nFailed')) % - (number + 1, self.maxBibles, name), + (number + 1, max_bibles, name), self.progressBar.maximum() - self.progressBar.value()) - number += 1 + self.success[number] = False continue - books = oldbible.get_books() + books = oldBible.get_books() self.progressBar.setMaximum(len(books)) for book in books: if self.stop_import_flag: - bible_failed = True + self.success[number] = False break self.incrementProgressBar(unicode(translate( 'BiblesPlugin.UpgradeWizardForm', 'Upgrading Bible %s of %s: "%s"\n' 'Upgrading %s ...')) % - (number + 1, self.maxBibles, name, book[u'name'])) + (number + 1, max_bibles, name, book[u'name'])) book_ref_id = self.newbibles[number].\ get_book_ref_id_by_name(book[u'name'], len(books), language_id) if not book_ref_id: log.warn(u'Upgrading books from %s " '\ 'failed - aborted by user' % name) - delete_database(self.path, clean_filename(name)) + self.newbibles[number].session.close() del self.newbibles[number] - bible_failed = True + self.success[number] = False break book_details = BiblesResourcesDB.get_book_by_id(book_ref_id) db_book = self.newbibles[number].create_book(book[u'name'], book_ref_id, book_details[u'testament_id']) - verses = oldbible.get_verses(book[u'id']) + verses = oldBible.get_verses(book[u'id']) if not verses: log.warn(u'No verses found to import for book ' u'"%s"', book[u'name']) @@ -721,31 +559,32 @@ class BibleUpgradeForm(OpenLPWizard): continue for verse in verses: if self.stop_import_flag: - bible_failed = True + self.success[number] = False break self.newbibles[number].create_verse(db_book.id, int(verse[u'chapter']), int(verse[u'verse']), unicode(verse[u'text'])) Receiver.send_message(u'openlp_process_events') self.newbibles[number].session.commit() - if not bible_failed: + if self.success.has_key(number) and not self.success[number]: + self.incrementProgressBar(unicode(translate( + 'BiblesPlugin.UpgradeWizardForm', + 'Upgrading Bible %s of %s: "%s"\nFailed')) % + (number + 1, max_bibles, name), + self.progressBar.maximum() - self.progressBar.value()) + else: + self.success[number] = True self.newbibles[number].create_meta(u'Version', name) - oldbible.close_connection() - delete_file(os.path.join(self.path, filename[0])) self.incrementProgressBar(unicode(translate( 'BiblesPlugin.UpgradeWizardForm', 'Upgrading Bible %s of %s: "%s"\n' 'Complete')) % - (number + 1, self.maxBibles, name)) - self.success[biblenumber] = True - else: - self.incrementProgressBar(unicode(translate( - 'BiblesPlugin.UpgradeWizardForm', - 'Upgrading Bible %s of %s: "%s"\nFailed')) % - (number + 1, self.maxBibles, name), - self.progressBar.maximum() - self.progressBar.value()) - delete_database(self.path, clean_filename(name)) - number += 1 + (number + 1, max_bibles, name)) + if self.newbibles.has_key(number): + self.newbibles[number].session.close() + # Close the last bible's connection if possible. + if oldBible is not None: + oldBible.close_connection() def postWizard(self): """ @@ -754,10 +593,14 @@ class BibleUpgradeForm(OpenLPWizard): successful_import = 0 failed_import = 0 for number, filename in enumerate(self.files): - if number in self.success and self.success[number] == True: + if self.success.has_key(number) and self.success[number]: successful_import += 1 elif self.checkBox[number].checkState() == QtCore.Qt.Checked: failed_import += 1 + # Delete upgraded (but not complete, corrupted, ...) bible. + delete_file(os.path.join(self.path, filename[0])) + # Copy not upgraded bible back. + shutil.move(os.path.join(self.temp_dir, filename[0]), self.path) if failed_import > 0: failed_import_text = unicode(translate( 'BiblesPlugin.UpgradeWizardForm', @@ -778,7 +621,8 @@ class BibleUpgradeForm(OpenLPWizard): 'Bible(s): %s successful%s')) % (successful_import, failed_import_text)) else: - self.progressLabel.setText( - translate('BiblesPlugin.UpgradeWizardForm', 'Upgrade ' - 'failed.')) + self.progressLabel.setText(translate( + 'BiblesPlugin.UpgradeWizardForm', 'Upgrade failed.')) + # Remove temp directory. + shutil.rmtree(self.temp_dir, True) OpenLPWizard.postWizard(self) diff --git a/openlp/plugins/bibles/forms/languageform.py b/openlp/plugins/bibles/forms/languageform.py index 477c7ee1e..c5069815b 100644 --- a/openlp/plugins/bibles/forms/languageform.py +++ b/openlp/plugins/bibles/forms/languageform.py @@ -44,8 +44,8 @@ class LanguageForm(QDialog, Ui_LanguageDialog): Class to manage a dialog which ask the user for a language. """ log.info(u'LanguageForm loaded') - - def __init__(self, parent = None): + + def __init__(self, parent=None): """ Constructor """ @@ -57,12 +57,11 @@ class LanguageForm(QDialog, Ui_LanguageDialog): if bible_name: self.bibleLabel.setText(unicode(bible_name)) items = BiblesResourcesDB.get_languages() - for item in items: - self.languageComboBox.addItem(item[u'name']) + self.languageComboBox.addItems([item[u'name'] for item in items]) return QDialog.exec_(self) - + def accept(self): - if self.languageComboBox.currentText() == u'': + if not self.languageComboBox.currentText(): critical_error_message_box( message=translate('BiblesPlugin.LanguageForm', 'You need to choose a language.')) diff --git a/openlp/plugins/bibles/lib/csvbible.py b/openlp/plugins/bibles/lib/csvbible.py index 83b52971c..6735a7344 100644 --- a/openlp/plugins/bibles/lib/csvbible.py +++ b/openlp/plugins/bibles/lib/csvbible.py @@ -115,7 +115,8 @@ class CSVBible(BibleDB): if self.stop_import_flag: break self.wizard.incrementProgressBar(unicode( - translate('BibleDB.Wizard', 'Importing books... %s')) % + translate('BiblesPlugin.CSVBible', + 'Importing books... %s')) % unicode(line[2], details['encoding'])) book_ref_id = self.get_book_ref_id_by_name( unicode(line[2], details['encoding']), 67, language_id) @@ -155,7 +156,7 @@ class CSVBible(BibleDB): book = self.get_book(line_book) book_ptr = book.name self.wizard.incrementProgressBar(unicode(translate( - 'BibleDB.Wizard', 'Importing verses from %s...', + 'BiblesPlugin.CSVBible', 'Importing verses from %s...', 'Importing verses from ...')) % book.name) self.session.commit() try: @@ -163,7 +164,7 @@ class CSVBible(BibleDB): except UnicodeError: verse_text = unicode(line[3], u'cp1252') self.create_verse(book.id, line[1], line[2], verse_text) - self.wizard.incrementProgressBar(translate('BibleDB.Wizard', + self.wizard.incrementProgressBar(translate('BiblesPlugin.CSVBible', 'Importing verses... done.')) Receiver.send_message(u'openlp_process_events') self.session.commit() diff --git a/openlp/plugins/bibles/lib/db.py b/openlp/plugins/bibles/lib/db.py index 68f105dff..e5962664b 100644 --- a/openlp/plugins/bibles/lib/db.py +++ b/openlp/plugins/bibles/lib/db.py @@ -36,10 +36,10 @@ from sqlalchemy import Column, ForeignKey, or_, Table, types from sqlalchemy.orm import class_mapper, mapper, relation from sqlalchemy.orm.exc import UnmappedClassError -from openlp.core.lib import Receiver, translate, check_directory_exists +from openlp.core.lib import Receiver, translate from openlp.core.lib.db import BaseModel, init_db, Manager from openlp.core.lib.ui import critical_error_message_box -from openlp.core.utils import AppLocation +from openlp.core.utils import AppLocation, clean_filename log = logging.getLogger(__name__) @@ -63,19 +63,6 @@ class Verse(BaseModel): """ pass -def clean_filename(filename): - """ - Clean up the version name of the Bible and convert it into a valid - file name. - - ``filename`` - The "dirty" file name or version name. - """ - if not isinstance(filename, unicode): - filename = unicode(filename, u'utf-8') - filename = re.sub(r'[^\w]+', u'_', filename).strip(u'_') - return filename + u'.sqlite' - def init_schema(url): """ Setup a bible database connection and initialise the database schema. @@ -158,7 +145,7 @@ class BibleDB(QtCore.QObject, Manager): self.name = kwargs[u'name'] if not isinstance(self.name, unicode): self.name = unicode(self.name, u'utf-8') - self.file = clean_filename(self.name) + self.file = clean_filename(self.name) + u'.sqlite' if u'file' in kwargs: self.file = kwargs[u'file'] Manager.__init__(self, u'bibles', init_schema, self.file) @@ -210,7 +197,7 @@ class BibleDB(QtCore.QObject, Manager): The book_reference_id from bibles_resources.sqlite of the book. ``testament`` - *Defaults to 1.* The testament_reference_id from + *Defaults to 1.* The testament_reference_id from bibles_resources.sqlite of the testament this book belongs to. """ log.debug(u'BibleDB.create_book("%s", "%s")', name, bk_ref_id) @@ -329,7 +316,7 @@ class BibleDB(QtCore.QObject, Manager): return self.get_object_filtered(Book, Book.book_reference_id.like(id)) def get_book_ref_id_by_name(self, book, maxbooks, language_id=None): - log.debug(u'BibleDB.get_book_ref_id_by_name:("%s", "%s")', book, + log.debug(u'BibleDB.get_book_ref_id_by_name:("%s", "%s")', book, language_id) if BiblesResourcesDB.get_book(book, True): book_temp = BiblesResourcesDB.get_book(book, True) @@ -471,7 +458,7 @@ class BibleDB(QtCore.QObject, Manager): def get_language(self, bible_name=None): """ - If no language is given it calls a dialog window where the user could + If no language is given it calls a dialog window where the user could select the bible language. Return the language id of a bible. @@ -521,9 +508,9 @@ class BiblesResourcesDB(QtCore.QObject, Manager): some resources which are used in the Bibles plugin. A wrapper class around a small SQLite database which contains the download resources, a biblelist from the different download resources, the books, - chapter counts and verse counts for the web download Bibles, a language - reference, the testament reference and some alternative book names. This - class contains a singleton "cursor" so that only one connection to the + chapter counts and verse counts for the web download Bibles, a language + reference, the testament reference and some alternative book names. This + class contains a singleton "cursor" so that only one connection to the SQLite database is ever used. """ cursor = None @@ -582,7 +569,7 @@ class BiblesResourcesDB(QtCore.QObject, Manager): ``name`` The name or abbreviation of the book. - + ``lower`` True if the comparsion should be only lowercase """ @@ -592,7 +579,7 @@ class BiblesResourcesDB(QtCore.QObject, Manager): if lower: books = BiblesResourcesDB.run_sql(u'SELECT id, testament_id, name, ' u'abbreviation, chapters FROM book_reference WHERE ' - u'LOWER(name) = ? OR LOWER(abbreviation) = ?', + u'LOWER(name) = ? OR LOWER(abbreviation) = ?', (name.lower(), name.lower())) else: books = BiblesResourcesDB.run_sql(u'SELECT id, testament_id, name, ' @@ -621,7 +608,7 @@ class BiblesResourcesDB(QtCore.QObject, Manager): if not isinstance(id, int): id = int(id) books = BiblesResourcesDB.run_sql(u'SELECT id, testament_id, name, ' - u'abbreviation, chapters FROM book_reference WHERE id = ?', + u'abbreviation, chapters FROM book_reference WHERE id = ?', (id, )) if books: return { @@ -645,12 +632,12 @@ class BiblesResourcesDB(QtCore.QObject, Manager): ``chapter`` The chapter number. """ - log.debug(u'BiblesResourcesDB.get_chapter("%s", "%s")', book_id, + log.debug(u'BiblesResourcesDB.get_chapter("%s", "%s")', book_id, chapter) if not isinstance(chapter, int): chapter = int(chapter) chapters = BiblesResourcesDB.run_sql(u'SELECT id, book_reference_id, ' - u'chapter, verse_count FROM chapters WHERE book_reference_id = ?', + u'chapter, verse_count FROM chapters WHERE book_reference_id = ?', (book_id,)) if chapters: return { @@ -687,7 +674,7 @@ class BiblesResourcesDB(QtCore.QObject, Manager): ``chapter`` The number of the chapter. """ - log.debug(u'BiblesResourcesDB.get_verse_count("%s", "%s")', book_id, + log.debug(u'BiblesResourcesDB.get_verse_count("%s", "%s")', book_id, chapter) details = BiblesResourcesDB.get_chapter(book_id, chapter) if details: @@ -715,7 +702,7 @@ class BiblesResourcesDB(QtCore.QObject, Manager): } else: return None - + @staticmethod def get_webbibles(source): """ @@ -737,7 +724,7 @@ class BiblesResourcesDB(QtCore.QObject, Manager): u'id': bible[0], u'name': bible[1], u'abbreviation': bible[2], - u'language_id': bible[3], + u'language_id': bible[3], u'download_source_id': bible[4] } for bible in bibles @@ -752,11 +739,11 @@ class BiblesResourcesDB(QtCore.QObject, Manager): ``abbreviation`` The abbreviation of the webbible. - + ``source`` The source of the webbible. """ - log.debug(u'BiblesResourcesDB.get_webbibles("%s", "%s")', abbreviation, + log.debug(u'BiblesResourcesDB.get_webbibles("%s", "%s")', abbreviation, source) if not isinstance(abbreviation, unicode): abbreviation = unicode(abbreviation) @@ -765,14 +752,14 @@ class BiblesResourcesDB(QtCore.QObject, Manager): source = BiblesResourcesDB.get_download_source(source) bible = BiblesResourcesDB.run_sql(u'SELECT id, name, abbreviation, ' u'language_id, download_source_id FROM webbibles WHERE ' - u'download_source_id = ? AND abbreviation = ?', (source[u'id'], + u'download_source_id = ? AND abbreviation = ?', (source[u'id'], abbreviation)) if bible: return { u'id': bible[0][0], u'name': bible[0][1], u'abbreviation': bible[0][2], - u'language_id': bible[0][3], + u'language_id': bible[0][3], u'download_source_id': bible[0][4] } else: @@ -785,11 +772,11 @@ class BiblesResourcesDB(QtCore.QObject, Manager): ``name`` The name to search the id. - + ``language_id`` The language_id for which language should be searched """ - log.debug(u'BiblesResourcesDB.get_alternative_book_name("%s", "%s")', + log.debug(u'BiblesResourcesDB.get_alternative_book_name("%s", "%s")', name, language_id) if language_id: books = BiblesResourcesDB.run_sql(u'SELECT book_reference_id, name ' @@ -806,7 +793,7 @@ class BiblesResourcesDB(QtCore.QObject, Manager): @staticmethod def get_language(name): """ - Return a dict containing the language id, name and code by name or + Return a dict containing the language id, name and code by name or abbreviation. ``name`` @@ -865,7 +852,7 @@ class BiblesResourcesDB(QtCore.QObject, Manager): class AlternativeBookNamesDB(QtCore.QObject, Manager): """ - This class represents a database-bound alternative book names system. + This class represents a database-bound alternative book names system. """ cursor = None conn = None @@ -874,7 +861,7 @@ class AlternativeBookNamesDB(QtCore.QObject, Manager): def get_cursor(): """ Return the cursor object. Instantiate one if it doesn't exist yet. - If necessary loads up the database and creates the tables if the + If necessary loads up the database and creates the tables if the database doesn't exist. """ if AlternativeBookNamesDB.cursor is None: @@ -904,7 +891,7 @@ class AlternativeBookNamesDB(QtCore.QObject, Manager): ``parameters`` Any variable parameters to add to the query - + ``commit`` If a commit statement is necessary this should be True. """ @@ -921,11 +908,11 @@ class AlternativeBookNamesDB(QtCore.QObject, Manager): ``name`` The name to search the id. - + ``language_id`` The language_id for which language should be searched """ - log.debug(u'AlternativeBookNamesDB.get_book_reference_id("%s", "%s")', + log.debug(u'AlternativeBookNamesDB.get_book_reference_id("%s", "%s")', name, language_id) if language_id: books = AlternativeBookNamesDB.run_sql(u'SELECT book_reference_id, ' @@ -962,11 +949,11 @@ class AlternativeBookNamesDB(QtCore.QObject, Manager): class OldBibleDB(QtCore.QObject, Manager): """ - This class conects to the old bible databases to reimport them to the new + This class conects to the old bible databases to reimport them to the new database scheme. """ cursor = None - + def __init__(self, parent, **kwargs): """ The constructor loads up the database and creates and initialises the diff --git a/openlp/plugins/bibles/lib/http.py b/openlp/plugins/bibles/lib/http.py index 03b094e82..290da104c 100644 --- a/openlp/plugins/bibles/lib/http.py +++ b/openlp/plugins/bibles/lib/http.py @@ -29,9 +29,7 @@ The :mod:`http` module enables OpenLP to retrieve scripture from bible websites. """ import logging -import os import re -import sqlite3 import socket import urllib from HTMLParser import HTMLParseError @@ -40,7 +38,7 @@ from BeautifulSoup import BeautifulSoup, NavigableString, Tag from openlp.core.lib import Receiver, translate from openlp.core.lib.ui import critical_error_message_box -from openlp.core.utils import AppLocation, get_web_page +from openlp.core.utils import get_web_page from openlp.plugins.bibles.lib import SearchResults from openlp.plugins.bibles.lib.db import BibleDB, BiblesResourcesDB, \ Book @@ -69,10 +67,10 @@ class BGExtract(object): ``chapter`` Chapter number. """ - log.debug(u'BGExtract.get_bible_chapter("%s", "%s", "%s")', version, + log.debug(u'BGExtract.get_bible_chapter("%s", "%s", "%s")', version, bookname, chapter) urlbookname = urllib.quote(bookname.encode("utf-8")) - url_params = u'search=%s+%s&version=%s' % (urlbookname, chapter, + url_params = u'search=%s+%s&version=%s' % (urlbookname, chapter, version) cleaner = [(re.compile(' |
|\'\+\''), lambda match: '')] soup = get_soup_for_bible_ref( @@ -147,7 +145,10 @@ class BGExtract(object): send_error_message(u'download') return None page_source = page.read() - page_source = unicode(page_source, 'utf8') + try: + page_source = unicode(page_source, u'utf8') + except UnicodeDecodeError: + page_source = unicode(page_source, u'cp1251') page_source_temp = re.search(u'.*?'\ u'
', page_source, re.DOTALL) if page_source_temp: @@ -200,7 +201,7 @@ class BSExtract(object): ``chapter`` Chapter number """ - log.debug(u'BSExtract.get_bible_chapter("%s", "%s", "%s")', version, + log.debug(u'BSExtract.get_bible_chapter("%s", "%s", "%s")', version, bookname, chapter) urlversion = urllib.quote(version.encode("utf-8")) urlbookname = urllib.quote(bookname.encode("utf-8")) @@ -227,7 +228,7 @@ class BSExtract(object): def get_books_from_http(self, version): """ - Load a list of all books a Bible contains from Bibleserver mobile + Load a list of all books a Bible contains from Bibleserver mobile website. ``version`` @@ -273,7 +274,7 @@ class CWExtract(object): ``chapter`` Chapter number """ - log.debug(u'CWExtract.get_bible_chapter("%s", "%s", "%s")', version, + log.debug(u'CWExtract.get_bible_chapter("%s", "%s", "%s")', version, bookname, chapter) urlbookname = bookname.replace(u' ', u'-') urlbookname = urlbookname.lower() @@ -386,7 +387,7 @@ class HTTPBible(BibleDB): """ self.wizard.progressBar.setMaximum(68) self.wizard.incrementProgressBar(unicode(translate( - 'BiblesPlugin.HTTPBible', + 'BiblesPlugin.HTTPBible', 'Registering Bible and loading books...'))) self.create_meta(u'download source', self.download_source) self.create_meta(u'download name', self.download_name) @@ -412,7 +413,7 @@ class HTTPBible(BibleDB): self.wizard.progressBar.setMaximum(len(books)+2) self.wizard.incrementProgressBar(unicode(translate( 'BiblesPlugin.HTTPBible', 'Registering Language...'))) - bible = BiblesResourcesDB.get_webbible(self.download_name, + bible = BiblesResourcesDB.get_webbible(self.download_name, self.download_source.lower()) if bible[u'language_id']: language_id = bible[u'language_id'] @@ -429,14 +430,14 @@ class HTTPBible(BibleDB): self.wizard.incrementProgressBar(unicode(translate( 'BiblesPlugin.HTTPBible', 'Importing %s...', 'Importing ...')) % book) - book_ref_id = self.get_book_ref_id_by_name(book, len(books), + book_ref_id = self.get_book_ref_id_by_name(book, len(books), language_id) if not book_ref_id: log.exception(u'Importing books from %s - download name: "%s" '\ 'failed' % (self.download_source, self.download_name)) return False book_details = BiblesResourcesDB.get_book_by_id(book_ref_id) - log.debug(u'Book details: Name:%s; id:%s; testament_id:%s', + log.debug(u'Book details: Name:%s; id:%s; testament_id:%s', book, book_ref_id, book_details[u'testament_id']) self.create_book(book, book_ref_id, book_details[u'testament_id']) if self.stop_import_flag: @@ -521,7 +522,7 @@ class HTTPBible(BibleDB): def get_chapter_count(self, book): """ Return the number of chapters in a particular book. - + ``book`` The book object to get the chapter count for. """ @@ -594,14 +595,14 @@ def send_error_message(error_type): """ if error_type == u'download': critical_error_message_box( - translate('BiblePlugin.HTTPBible', 'Download Error'), - translate('BiblePlugin.HTTPBible', 'There was a ' + translate('BiblesPlugin.HTTPBible', 'Download Error'), + translate('BiblesPlugin.HTTPBible', 'There was a ' 'problem downloading your verse selection. Please check your ' 'Internet connection, and if this error continues to occur ' 'please consider reporting a bug.')) elif error_type == u'parse': critical_error_message_box( - translate('BiblePlugin.HTTPBible', 'Parse Error'), - translate('BiblePlugin.HTTPBible', 'There was a ' + translate('BiblesPlugin.HTTPBible', 'Parse Error'), + translate('BiblesPlugin.HTTPBible', 'There was a ' 'problem extracting your verse selection. If this error continues ' 'to occur please consider reporting a bug.')) diff --git a/openlp/plugins/bibles/lib/manager.py b/openlp/plugins/bibles/lib/manager.py index 9a0b20820..934aa2d90 100644 --- a/openlp/plugins/bibles/lib/manager.py +++ b/openlp/plugins/bibles/lib/manager.py @@ -34,7 +34,7 @@ from openlp.core.lib import Receiver, SettingsManager, translate from openlp.core.lib.ui import critical_error_message_box from openlp.core.utils import AppLocation, delete_file from openlp.plugins.bibles.lib import parse_reference -from openlp.plugins.bibles.lib.db import BibleDB, BibleMeta, OldBibleDB +from openlp.plugins.bibles.lib.db import BibleDB, BibleMeta from csvbible import CSVBible from http import HTTPBible from opensong import OpenSongBible @@ -151,9 +151,10 @@ class BibleManager(object): name = bible.get_name() # Remove corrupted files. if name is None: + bible.session.close() delete_file(os.path.join(self.path, filename)) continue - # Find old database versions + # Find old database versions. if bible.is_old_database(): self.old_bible_databases.append([filename, name]) bible.session.close() @@ -220,7 +221,7 @@ class BibleManager(object): return [ { u'name': book.name, - u'book_reference_id': book.book_reference_id, + u'book_reference_id': book.book_reference_id, u'chapters': self.db_cache[bible].get_chapter_count(book) } for book in self.db_cache[bible].get_books() @@ -229,10 +230,10 @@ class BibleManager(object): def get_chapter_count(self, bible, book): """ Returns the number of Chapters for a given book. - + ``bible`` Unicode. The Bible to get the list of books from. - + ``book`` The book object to get the chapter count for. """ @@ -295,7 +296,7 @@ class BibleManager(object): if db_book: book_id = db_book.book_reference_id log.debug(u'Book name corrected to "%s"', db_book.name) - new_reflist.append((book_id, item[1], item[2], + new_reflist.append((book_id, item[1], item[2], item[3])) else: log.debug(u'OpenLP failed to find book %s', item[0]) diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index cb8273327..91009424c 100644 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -34,7 +34,8 @@ from openlp.core.lib import MediaManagerItem, Receiver, ItemCapabilities, \ translate from openlp.core.lib.searchedit import SearchEdit from openlp.core.lib.ui import UiStrings, add_widget_completer, \ - media_item_combo_box, critical_error_message_box, find_and_set_in_combo_box + media_item_combo_box, critical_error_message_box, \ + find_and_set_in_combo_box, build_icon from openlp.plugins.bibles.forms import BibleImportForm from openlp.plugins.bibles.lib import LayoutStyle, DisplayStyle, \ VerseReferenceList, get_reference_match @@ -57,8 +58,8 @@ class BibleMediaItem(MediaManagerItem): def __init__(self, parent, plugin, icon): self.IconPath = u'songs/song' - self.lockIcon = QtGui.QIcon(u':/bibles/bibles_search_lock.png') - self.unlockIcon = QtGui.QIcon(u':/bibles/bibles_search_unlock.png') + self.lockIcon = build_icon(u':/bibles/bibles_search_lock.png') + self.unlockIcon = build_icon(u':/bibles/bibles_search_unlock.png') MediaManagerItem.__init__(self, parent, plugin, icon) # Place to store the search results for both bibles. self.settings = self.plugin.settings_tab @@ -86,7 +87,7 @@ class BibleMediaItem(MediaManagerItem): not second_bible: self.displayResults(bible, second_bible) elif critical_error_message_box( - message=translate('BiblePlugin.MediaItem', + message=translate('BiblesPlugin.MediaItem', 'You cannot combine single and dual Bible verse search results. ' 'Do you want to delete your search results and start a new ' 'search?'), @@ -394,6 +395,7 @@ class BibleMediaItem(MediaManagerItem): log.debug(u'Reloading Bibles') self.plugin.manager.reload_bibles() self.loadBibles() + self.updateAutoCompleter() def initialiseAdvancedBible(self, bible): """ @@ -436,7 +438,7 @@ class BibleMediaItem(MediaManagerItem): if verse_count == 0: self.advancedSearchButton.setEnabled(False) critical_error_message_box( - message=translate('BiblePlugin.MediaItem', + message=translate('BiblesPlugin.MediaItem', 'Bible not fully loaded.')) else: self.advancedSearchButton.setEnabled(True) @@ -611,7 +613,7 @@ class BibleMediaItem(MediaManagerItem): if restore: old_text = unicode(combo.currentText()) combo.clear() - combo.addItems([unicode(i) for i in range(range_from, range_to + 1)]) + combo.addItems(map(unicode, range(range_from, range_to + 1))) if restore and combo.findText(old_text) != -1: combo.setCurrentIndex(combo.findText(old_text)) @@ -693,8 +695,8 @@ class BibleMediaItem(MediaManagerItem): verse.verse, verse.verse)) if passage_not_found: QtGui.QMessageBox.information(self, - translate('BiblePlugin.MediaItem', 'Information'), - unicode(translate('BiblePlugin.MediaItem', + translate('BiblesPlugin.MediaItem', 'Information'), + unicode(translate('BiblesPlugin.MediaItem', 'The second Bible does not contain all the verses ' 'that are in the main Bible. Only verses found in both ' 'Bibles will be shown. %d verses have not been ' @@ -983,7 +985,8 @@ class BibleMediaItem(MediaManagerItem): Search for some Bible verses (by reference). """ bible = unicode(self.quickVersionComboBox.currentText()) - search_results = self.plugin.manager.get_verses(bible, string, False, False) + search_results = self.plugin.manager.get_verses(bible, string, False, + False) if search_results: versetext = u' '.join([verse.text for verse in search_results]) return [[string, versetext]] diff --git a/openlp/plugins/bibles/resources/bibles_resources.sqlite b/openlp/plugins/bibles/resources/bibles_resources.sqlite index 3235c9562..c0fa931d1 100644 Binary files a/openlp/plugins/bibles/resources/bibles_resources.sqlite and b/openlp/plugins/bibles/resources/bibles_resources.sqlite differ diff --git a/openlp/plugins/custom/customplugin.py b/openlp/plugins/custom/customplugin.py index 7b7c939b1..e9260f926 100644 --- a/openlp/plugins/custom/customplugin.py +++ b/openlp/plugins/custom/customplugin.py @@ -46,7 +46,7 @@ class CustomPlugin(Plugin): log.info(u'Custom Plugin loaded') def __init__(self, plugin_helpers): - Plugin.__init__(self, u'Custom', plugin_helpers, + Plugin.__init__(self, u'custom', plugin_helpers, CustomMediaItem, CustomTab) self.weight = -5 self.manager = Manager(u'custom', init_schema) diff --git a/openlp/plugins/custom/forms/editcustomform.py b/openlp/plugins/custom/forms/editcustomform.py index 904fa598c..a3a80caf9 100644 --- a/openlp/plugins/custom/forms/editcustomform.py +++ b/openlp/plugins/custom/forms/editcustomform.py @@ -93,7 +93,6 @@ class EditCustomForm(QtGui.QDialog, Ui_CustomEditDialog): self.titleEdit.setText(u'') self.creditEdit.setText(u'') self.themeComboBox.setCurrentIndex(0) - self.titleEdit.setFocus(QtCore.Qt.OtherFocusReason) else: self.customSlide = self.manager.get_object(CustomSlide, id) self.titleEdit.setText(self.customSlide.title) @@ -104,10 +103,9 @@ class EditCustomForm(QtGui.QDialog, Ui_CustomEditDialog): self.slideListView.addItem(slide[1]) theme = self.customSlide.theme_name find_and_set_in_combo_box(self.themeComboBox, theme) + self.titleEdit.setFocus(QtCore.Qt.OtherFocusReason) # If not preview hide the preview button. - self.previewButton.setVisible(False) - if preview: - self.previewButton.setVisible(True) + self.previewButton.setVisible(preview) def reject(self): Receiver.send_message(u'custom_edit_clear') diff --git a/openlp/plugins/custom/lib/mediaitem.py b/openlp/plugins/custom/lib/mediaitem.py index 2f7c7f9b3..667434a8b 100644 --- a/openlp/plugins/custom/lib/mediaitem.py +++ b/openlp/plugins/custom/lib/mediaitem.py @@ -200,6 +200,17 @@ class CustomMediaItem(MediaManagerItem): Remove a custom item from the list and database """ if check_item_selected(self.listView, UiStrings().SelectDelete): + items = self.listView.selectedIndexes() + if QtGui.QMessageBox.question(self, + UiStrings().ConfirmDelete, + translate('CustomPlugin.MediaItem', + 'Are you sure you want to delete the %n selected custom' + ' slides(s)?', '', + QtCore.QCoreApplication.CodecForTr, len(items)), + QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Yes | + QtGui.QMessageBox.No), + QtGui.QMessageBox.Yes) == QtGui.QMessageBox.No: + return row_list = [item.row() for item in self.listView.selectedIndexes()] row_list.sort(reverse=True) id_list = [(item.data(QtCore.Qt.UserRole)).toInt()[0] diff --git a/openlp/plugins/images/imageplugin.py b/openlp/plugins/images/imageplugin.py index b92a804fd..1ddbe8357 100644 --- a/openlp/plugins/images/imageplugin.py +++ b/openlp/plugins/images/imageplugin.py @@ -36,7 +36,7 @@ class ImagePlugin(Plugin): log.info(u'Image Plugin loaded') def __init__(self, plugin_helpers): - Plugin.__init__(self, u'Images', plugin_helpers, ImageMediaItem) + Plugin.__init__(self, u'images', plugin_helpers, ImageMediaItem) self.weight = -7 self.icon_path = u':/plugins/plugin_images.png' self.icon = build_icon(self.icon_path) diff --git a/openlp/plugins/images/lib/mediaitem.py b/openlp/plugins/images/lib/mediaitem.py index 481b55c09..acd420880 100644 --- a/openlp/plugins/images/lib/mediaitem.py +++ b/openlp/plugins/images/lib/mediaitem.py @@ -52,6 +52,8 @@ class ImageMediaItem(MediaManagerItem): self.hasSearch = True QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'live_theme_changed'), self.liveThemeChanged) + # Allow DnD from the desktop + self.listView.activateDnD() def retranslateUi(self): self.onNewPrompt = translate('ImagePlugin.MediaItem', @@ -131,6 +133,7 @@ class ImageMediaItem(MediaManagerItem): icon = self.iconFromFile(imageFile, thumb) item_name = QtGui.QListWidgetItem(filename) item_name.setIcon(icon) + item_name.setToolTip(imageFile) item_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(imageFile)) self.listView.addItem(item_name) if not initialLoad: @@ -208,8 +211,13 @@ class ImageMediaItem(MediaManagerItem): filename = unicode(bitem.data(QtCore.Qt.UserRole).toString()) if os.path.exists(filename): (path, name) = os.path.split(filename) - self.plugin.liveController.display.directImage(name, filename) - self.resetAction.setVisible(True) + if self.plugin.liveController.display.directImage(name, + filename): + self.resetAction.setVisible(True) + else: + critical_error_message_box(UiStrings().LiveBGError, + translate('ImagePlugin.MediaItem', + 'There was no display item to amend.')) else: critical_error_message_box(UiStrings().LiveBGError, unicode(translate('ImagePlugin.MediaItem', diff --git a/openlp/plugins/media/lib/mediaitem.py b/openlp/plugins/media/lib/mediaitem.py index 1e39a0426..e3c36bd77 100644 --- a/openlp/plugins/media/lib/mediaitem.py +++ b/openlp/plugins/media/lib/mediaitem.py @@ -39,6 +39,8 @@ from PyQt4.phonon import Phonon log = logging.getLogger(__name__) +CLAPPERBOARD = QtGui.QPixmap(u':/media/media_video.png').toImage() + class MediaMediaItem(MediaManagerItem): """ This is the custom media manager item for Media Slides. @@ -48,8 +50,7 @@ class MediaMediaItem(MediaManagerItem): def __init__(self, parent, plugin, icon): self.IconPath = u'images/image' self.background = False - self.PreviewFunction = QtGui.QPixmap( - u':/media/media_video.png').toImage() + self.PreviewFunction = CLAPPERBOARD MediaManagerItem.__init__(self, parent, plugin, icon) self.singleServiceItem = False self.hasSearch = True @@ -60,6 +61,8 @@ class MediaMediaItem(MediaManagerItem): QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'openlp_phonon_creation'), self.createPhonon) + # Allow DnD from the desktop + self.listView.activateDnD() def retranslateUi(self): self.onNewPrompt = translate('MediaPlugin.MediaItem', 'Select Media') @@ -114,8 +117,12 @@ class MediaMediaItem(MediaManagerItem): filename = unicode(item.data(QtCore.Qt.UserRole).toString()) if os.path.exists(filename): (path, name) = os.path.split(filename) - self.plugin.liveController.display.video(filename, 0, True) - self.resetAction.setVisible(True) + if self.plugin.liveController.display.video(filename, 0, True): + self.resetAction.setVisible(True) + else: + critical_error_message_box(UiStrings().LiveBGError, + translate('MediaPlugin.MediaItem', + 'There was no display item to amend.')) else: critical_error_message_box(UiStrings().LiveBGError, unicode(translate('MediaPlugin.MediaItem', @@ -197,17 +204,17 @@ class MediaMediaItem(MediaManagerItem): SettingsManager.set_list(self.settingsSection, u'media', self.getFileList()) - def loadList(self, files): + def loadList(self, media): # Sort the themes by its filename considering language specific # characters. lower() is needed for windows! - files.sort(cmp=locale.strcoll, + media.sort(cmp=locale.strcoll, key=lambda filename: os.path.split(unicode(filename))[1].lower()) - for file in files: - filename = os.path.split(unicode(file))[1] + for track in media: + filename = os.path.split(unicode(track))[1] item_name = QtGui.QListWidgetItem(filename) - img = QtGui.QPixmap(u':/media/media_video.png').toImage() - item_name.setIcon(build_icon(img)) - item_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(file)) + item_name.setIcon(build_icon(CLAPPERBOARD)) + item_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(track)) + item_name.setToolTip(track) self.listView.addItem(item_name) def createPhonon(self): diff --git a/openlp/plugins/media/mediaplugin.py b/openlp/plugins/media/mediaplugin.py index 308ebd959..c215b1be0 100644 --- a/openlp/plugins/media/mediaplugin.py +++ b/openlp/plugins/media/mediaplugin.py @@ -39,7 +39,7 @@ class MediaPlugin(Plugin): log.info(u'%s MediaPlugin loaded', __name__) def __init__(self, plugin_helpers): - Plugin.__init__(self, u'Media', plugin_helpers, + Plugin.__init__(self, u'media', plugin_helpers, MediaMediaItem, MediaTab) self.weight = -6 self.icon_path = u':/plugins/plugin_media.png' diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index 07b490d83..85721c65d 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -58,6 +58,8 @@ class PresentationMediaItem(MediaManagerItem): self.hasSearch = True QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'mediaitem_presentation_rebuild'), self.rebuild) + # Allow DnD from the desktop + self.listView.activateDnD() def retranslateUi(self): """ @@ -205,6 +207,7 @@ class PresentationMediaItem(MediaManagerItem): item_name = QtGui.QListWidgetItem(filename) item_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(file)) item_name.setIcon(icon) + item_name.setToolTip(file) self.listView.addItem(item_name) Receiver.send_message(u'cursor_normal') if not initialLoad: diff --git a/openlp/plugins/presentations/presentationplugin.py b/openlp/plugins/presentations/presentationplugin.py index 2990862e1..a97f82159 100644 --- a/openlp/plugins/presentations/presentationplugin.py +++ b/openlp/plugins/presentations/presentationplugin.py @@ -52,7 +52,7 @@ class PresentationPlugin(Plugin): """ log.debug(u'Initialised') self.controllers = {} - Plugin.__init__(self, u'Presentations', plugin_helpers) + Plugin.__init__(self, u'presentations', plugin_helpers) self.weight = -8 self.icon_path = u':/plugins/plugin_presentations.png' self.icon = build_icon(self.icon_path) diff --git a/openlp/plugins/remotes/lib/httpserver.py b/openlp/plugins/remotes/lib/httpserver.py index 34271019d..1545986f0 100644 --- a/openlp/plugins/remotes/lib/httpserver.py +++ b/openlp/plugins/remotes/lib/httpserver.py @@ -150,13 +150,11 @@ class HttpResponse(object): class HttpServer(object): """ - Ability to control OpenLP via a webbrowser - e.g. http://localhost:4316/send/slidecontroller_live_next - http://localhost:4316/send/alerts_text?q=your%20alert%20text + Ability to control OpenLP via a web browser. """ def __init__(self, plugin): """ - Initialise the httpserver, and start the server + Initialise the httpserver, and start the server. """ log.debug(u'Initialise httpserver') self.plugin = plugin @@ -170,9 +168,9 @@ class HttpServer(object): def start_tcp(self): """ - Start the http server, use the port in the settings default to 4316 + Start the http server, use the port in the settings default to 4316. Listen out for slide and song changes so they can be broadcast to - clients. Listen out for socket connections + clients. Listen out for socket connections. """ log.debug(u'Start TCP server') port = QtCore.QSettings().value( @@ -195,20 +193,20 @@ class HttpServer(object): def slide_change(self, row): """ - Slide change listener. Store the item and tell the clients + Slide change listener. Store the item and tell the clients. """ self.current_slide = row def item_change(self, items): """ - Item (song) change listener. Store the slide and tell the clients + Item (song) change listener. Store the slide and tell the clients. """ self.current_item = items[0] def new_connection(self): """ A new http connection has been made. Create a client object to handle - communication + communication. """ log.debug(u'new http connection') socket = self.server.nextPendingConnection() @@ -225,15 +223,16 @@ class HttpServer(object): def close(self): """ - Close down the http server + Close down the http server. """ log.debug(u'close http server') self.server.close() + class HttpConnection(object): """ A single connection, this handles communication between the server - and the client + and the client. """ def __init__(self, parent, socket): """ @@ -287,9 +286,12 @@ class HttpConnection(object): """ self.template_vars = { 'app_title': translate('RemotePlugin.Mobile', 'OpenLP 2.0 Remote'), - 'stage_title': translate('RemotePlugin.Mobile', 'OpenLP 2.0 Stage View'), - 'service_manager': translate('RemotePlugin.Mobile', 'Service Manager'), - 'slide_controller': translate('RemotePlugin.Mobile', 'Slide Controller'), + 'stage_title': translate('RemotePlugin.Mobile', + 'OpenLP 2.0 Stage View'), + 'service_manager': translate('RemotePlugin.Mobile', + 'Service Manager'), + 'slide_controller': translate('RemotePlugin.Mobile', + 'Slide Controller'), 'alerts': translate('RemotePlugin.Mobile', 'Alerts'), 'search': translate('RemotePlugin.Mobile', 'Search'), 'back': translate('RemotePlugin.Mobile', 'Back'), @@ -301,7 +303,8 @@ class HttpConnection(object): 'text': translate('RemotePlugin.Mobile', 'Text'), 'show_alert': translate('RemotePlugin.Mobile', 'Show Alert'), 'go_live': translate('RemotePlugin.Mobile', 'Go Live'), - 'add_to_service': translate('RemotePlugin.Mobile', 'Add To Service'), + 'add_to_service': translate('RemotePlugin.Mobile', + 'Add to Service'), 'no_results': translate('RemotePlugin.Mobile', 'No Results'), 'options': translate('RemotePlugin.Mobile', 'Options') } @@ -357,7 +360,8 @@ class HttpConnection(object): if ext == u'.html': mimetype = u'text/html' variables = self.template_vars - html = Template(filename=path, input_encoding=u'utf-8', output_encoding=u'utf-8').render(**variables) + html = Template(filename=path, input_encoding=u'utf-8', + output_encoding=u'utf-8').render(**variables) elif ext == u'.css': mimetype = u'text/css' elif ext == u'.js': @@ -396,7 +400,7 @@ class HttpConnection(object): if self.parent.current_item else u'' } return HttpResponse(json.dumps({u'results': result}), - {u'Content-Type': u'application/json'}) + {u'Content-Type': u'application/json'}) def display(self, action): """ @@ -482,10 +486,11 @@ class HttpConnection(object): def pluginInfo(self, action): """ - Return plugin related information, based on the action + Return plugin related information, based on the action. - ``action`` - The action to perform - if 'search' return a list of plugin names which support search + ``action`` + The action to perform. If *search* return a list of plugin names + which support search. """ if action == u'search': searches = [] @@ -500,10 +505,10 @@ class HttpConnection(object): def search(self, type): """ - Return a list of items that match the search text + Return a list of items that match the search text. ``type`` - The plugin name to search in. + The plugin name to search in. """ text = json.loads(self.url_params[u'data'][0])[u'request'][u'text'] plugin = self.parent.plugin.pluginManager.get_plugin_by_name(type) @@ -527,7 +532,7 @@ class HttpConnection(object): def add_to_service(self, type): """ - Add item of type ``type`` to the end of the service + Add item of type ``type`` to the end of the service. """ id = json.loads(self.url_params[u'data'][0])[u'request'][u'id'] plugin = self.parent.plugin.pluginManager.get_plugin_by_name(type) diff --git a/openlp/plugins/remotes/remoteplugin.py b/openlp/plugins/remotes/remoteplugin.py index a120becba..dfaff9675 100644 --- a/openlp/plugins/remotes/remoteplugin.py +++ b/openlp/plugins/remotes/remoteplugin.py @@ -39,7 +39,7 @@ class RemotesPlugin(Plugin): """ remotes constructor """ - Plugin.__init__(self, u'Remotes', plugin_helpers, + Plugin.__init__(self, u'remotes', plugin_helpers, settings_tab_class=RemoteTab) self.icon_path = u':/plugins/plugin_remote.png' self.icon = build_icon(self.icon_path) diff --git a/openlp/plugins/songs/forms/editsongform.py b/openlp/plugins/songs/forms/editsongform.py index 6d261ffc2..c7dbf85cf 100644 --- a/openlp/plugins/songs/forms/editsongform.py +++ b/openlp/plugins/songs/forms/editsongform.py @@ -90,7 +90,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): self.onVerseListViewPressed) QtCore.QObject.connect(self.themeAddButton, QtCore.SIGNAL(u'clicked()'), - self.mediaitem.plugin.renderer.theme_manager.onAddTheme) + self.mediaitem.plugin.renderer.themeManager.onAddTheme) QtCore.QObject.connect(self.maintenanceButton, QtCore.SIGNAL(u'clicked()'), self.onMaintenanceButtonClicked) QtCore.QObject.connect(Receiver.get_receiver(), @@ -209,9 +209,11 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): self.alternativeEdit.setText(u'') if self.song.song_book_id != 0: book_name = self.manager.get_object(Book, self.song.song_book_id) - find_and_set_in_combo_box(self.songBookComboBox, unicode(book_name.name)) + find_and_set_in_combo_box( + self.songBookComboBox, unicode(book_name.name)) if self.song.theme_name: - find_and_set_in_combo_box(self.themeComboBox, unicode(self.song.theme_name)) + find_and_set_in_combo_box( + self.themeComboBox, unicode(self.song.theme_name)) if self.song.copyright: self.copyrightEdit.setText(self.song.copyright) else: diff --git a/openlp/plugins/songs/forms/editverseform.py b/openlp/plugins/songs/forms/editverseform.py index 79b2b295a..1845bc0e3 100644 --- a/openlp/plugins/songs/forms/editverseform.py +++ b/openlp/plugins/songs/forms/editverseform.py @@ -37,6 +37,8 @@ from editversedialog import Ui_EditVerseDialog log = logging.getLogger(__name__) +VERSE_REGEX = re.compile(r'---\[(.+):\D*(\d*)\D*.*\]---') + class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog): """ This is the form that is used to edit the verses of the song. @@ -60,7 +62,6 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog): QtCore.QObject.connect(self.verseTypeComboBox, QtCore.SIGNAL(u'currentIndexChanged(int)'), self.onVerseTypeComboBoxChanged) - self.verse_regex = re.compile(r'---\[(.+):\D*(\d*)\D*.*\]---') def contextMenu(self, point): item = self.serviceManagerList.itemAt(point) @@ -105,7 +106,7 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog): if position == -1: return text = text[:position + 4] - match = self.verse_regex.match(text) + match = VERSE_REGEX.match(text) if match: verse_tag = match.group(1) try: @@ -136,7 +137,7 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog): if position == -1: return text = text[:position + 4] - match = self.verse_regex.match(text) + match = VERSE_REGEX.match(text) if match: verse_type = match.group(1) verse_type_index = VerseType.from_loose_input(verse_type) diff --git a/openlp/plugins/songs/forms/songexportform.py b/openlp/plugins/songs/forms/songexportform.py index 3432c8846..90c3b0275 100644 --- a/openlp/plugins/songs/forms/songexportform.py +++ b/openlp/plugins/songs/forms/songexportform.py @@ -28,6 +28,7 @@ The :mod:`songexportform` module provides the wizard for exporting songs to the OpenLyrics format. """ +import locale import logging from PyQt4 import QtCore, QtGui @@ -249,6 +250,7 @@ class SongExportForm(OpenLPWizard): # Load the list of songs. Receiver.send_message(u'cursor_busy') songs = self.plugin.manager.get_all_objects(Song) + songs.sort(cmp=locale.strcoll, key=lambda song: song.title.lower()) for song in songs: authors = u', '.join([author.display_name for author in song.authors]) diff --git a/openlp/plugins/songs/forms/songimportform.py b/openlp/plugins/songs/forms/songimportform.py index 4ab60d360..52524b838 100644 --- a/openlp/plugins/songs/forms/songimportform.py +++ b/openlp/plugins/songs/forms/songimportform.py @@ -699,7 +699,8 @@ class SongImportForm(OpenLPWizard): elif source_format == SongFormat.OpenLP1: # Import an openlp.org database importer = self.plugin.importSongs(SongFormat.OpenLP1, - filename=unicode(self.openLP1FilenameEdit.text()) + filename=unicode(self.openLP1FilenameEdit.text()), + plugin=self.plugin ) elif source_format == SongFormat.OpenLyrics: # Import OpenLyrics songs diff --git a/openlp/plugins/songs/lib/__init__.py b/openlp/plugins/songs/lib/__init__.py index 308ff6aa1..e2996ff8f 100644 --- a/openlp/plugins/songs/lib/__init__.py +++ b/openlp/plugins/songs/lib/__init__.py @@ -267,6 +267,12 @@ def clean_song(manager, song): ``song`` The song object. """ + if isinstance(song.title, buffer): + song.title = unicode(song.title) + if isinstance(song.alternate_title, buffer): + song.alternate_title = unicode(song.alternate_title) + if isinstance(song.lyrics, buffer): + song.lyrics = unicode(song.lyrics) song.title = song.title.rstrip() if song.title else u'' if song.alternate_title is None: song.alternate_title = u'' diff --git a/openlp/plugins/songs/lib/db.py b/openlp/plugins/songs/lib/db.py index 93d56a05a..c5c019c3c 100644 --- a/openlp/plugins/songs/lib/db.py +++ b/openlp/plugins/songs/lib/db.py @@ -29,7 +29,7 @@ The :mod:`db` module provides the database and schema that is the backend for the Songs plugin """ -from sqlalchemy import Column, ForeignKey, Index, Table, types +from sqlalchemy import Column, ForeignKey, Table, types from sqlalchemy.orm import mapper, relation from openlp.core.lib.db import BaseModel, init_db diff --git a/openlp/plugins/songs/lib/easislidesimport.py b/openlp/plugins/songs/lib/easislidesimport.py index b1eaceeaf..b24287130 100644 --- a/openlp/plugins/songs/lib/easislidesimport.py +++ b/openlp/plugins/songs/lib/easislidesimport.py @@ -26,13 +26,10 @@ ############################################################################### import logging -import os import re from lxml import etree, objectify -from openlp.core.lib import translate -from openlp.core.ui.wizard import WizardStrings from openlp.plugins.songs.lib import VerseType from openlp.plugins.songs.lib.songimport import SongImport diff --git a/openlp/plugins/songs/lib/ewimport.py b/openlp/plugins/songs/lib/ewimport.py index 09f84fbe2..820cf595a 100644 --- a/openlp/plugins/songs/lib/ewimport.py +++ b/openlp/plugins/songs/lib/ewimport.py @@ -31,18 +31,33 @@ EasyWorship song databases into the current installation database. import os import struct +import re from openlp.core.lib import translate -from openlp.core.ui.wizard import WizardStrings from openlp.plugins.songs.lib import VerseType from openlp.plugins.songs.lib import retrieve_windows_encoding from songimport import SongImport +RTF_STRIPPING_REGEX = re.compile(r'\{\\tx[^}]*\}') +# regex: at least two newlines, can have spaces between them +SLIDE_BREAK_REGEX = re.compile(r'\n *?\n[\n ]*') +NUMBER_REGEX = re.compile(r'[0-9]+') +NOTE_REGEX = re.compile(r'\(.*?\)') + def strip_rtf(blob, encoding): depth = 0 control = False clear_text = [] control_word = [] + + # workaround for \tx bug: remove one pair of curly braces + # if \tx is encountered + match = RTF_STRIPPING_REGEX.search(blob) + if match: + # start and end indices of match are curly braces - filter them out + blob = ''.join([blob[i] for i in xrange(len(blob)) + if i != match.start() and i !=match.end()]) + for c in blob: if control: # for delimiters, set control to False @@ -259,9 +274,45 @@ class EasyWorshipSongImport(SongImport): if words: # Format the lyrics words = strip_rtf(words, self.encoding) - for verse in words.split(u'\n\n'): + verse_type = VerseType.Tags[VerseType.Verse] + for verse in SLIDE_BREAK_REGEX.split(words): + verse = verse.strip() + if not verse: + continue + verse_split = verse.split(u'\n', 1) + first_line_is_tag = False + # EW tags: verse, chorus, pre-chorus, bridge, tag, + # intro, ending, slide + for type in VerseType.Names+[u'tag', u'slide']: + type = type.lower() + ew_tag = verse_split[0].strip().lower() + if ew_tag.startswith(type): + verse_type = type[0] + if type == u'tag' or type == u'slide': + verse_type = VerseType.Tags[VerseType.Other] + first_line_is_tag = True + number_found = False + # check if tag is followed by number and/or note + if len(ew_tag) > len(type): + match = NUMBER_REGEX.search(ew_tag) + if match: + number = match.group() + verse_type += number + number_found = True + match = NOTE_REGEX.search(ew_tag) + if match: + self.comments += ew_tag + u'\n' + if not number_found: + verse_type += u'1' + break self.add_verse( - verse.strip(), VerseType.Tags[VerseType.Verse]) + verse_split[-1].strip() if first_line_is_tag else verse, + verse_type) + if len(self.comments) > 5: + self.comments += unicode( + translate('SongsPlugin.EasyWorshipSongImport', + '\n[above are Song Tags with notes imported from \ + EasyWorship]')) if self.stop_import_flag: break if not self.finish(): diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py index cd7484df9..a2814a1df 100644 --- a/openlp/plugins/songs/lib/mediaitem.py +++ b/openlp/plugins/songs/lib/mediaitem.py @@ -35,7 +35,8 @@ from sqlalchemy.sql import or_ from openlp.core.lib import MediaManagerItem, Receiver, ItemCapabilities, \ translate, check_item_selected, PluginStatus from openlp.core.lib.searchedit import SearchEdit -from openlp.core.lib.ui import UiStrings +from openlp.core.lib.ui import UiStrings, context_menu_action, \ + context_menu_separator from openlp.plugins.songs.forms import EditSongForm, SongMaintenanceForm, \ SongImportForm, SongExportForm from openlp.plugins.songs.lib import OpenLyrics, SongXML, VerseType, \ @@ -128,6 +129,13 @@ class SongMediaItem(MediaManagerItem): QtCore.SIGNAL(u'searchTypeChanged(int)'), self.onSearchTextButtonClick) + def addCustomContextActions(self): + context_menu_separator(self.listView) + context_menu_action( + self.listView, u':/general/general_clone.png', + translate('OpenLP.MediaManagerItem', + '&Clone'), self.onCloneClick) + def onFocus(self): self.searchTextEdit.setFocus() @@ -353,19 +361,37 @@ class SongMediaItem(MediaManagerItem): if check_item_selected(self.listView, UiStrings().SelectDelete): items = self.listView.selectedIndexes() if QtGui.QMessageBox.question(self, - translate('SongsPlugin.MediaItem', 'Delete Song(s)?'), + UiStrings().ConfirmDelete, translate('SongsPlugin.MediaItem', 'Are you sure you want to delete the %n selected song(s)?', '', QtCore.QCoreApplication.CodecForTr, len(items)), - QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok | - QtGui.QMessageBox.Cancel), - QtGui.QMessageBox.Ok) == QtGui.QMessageBox.Cancel: + QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Yes | + QtGui.QMessageBox.No), + QtGui.QMessageBox.Yes) == QtGui.QMessageBox.No: return for item in items: item_id = (item.data(QtCore.Qt.UserRole)).toInt()[0] self.plugin.manager.delete_object(Song, item_id) self.onSearchTextButtonClick() + def onCloneClick(self): + """ + Clone a Song + """ + log.debug(u'onCloneClick') + if check_item_selected(self.listView, UiStrings().SelectEdit): + self.editItem = self.listView.currentItem() + item_id = (self.editItem.data(QtCore.Qt.UserRole)).toInt()[0] + old_song = self.plugin.manager.get_object(Song, item_id) + song_xml = self.openLyrics.song_to_xml(old_song) + new_song_id = self.openLyrics.xml_to_song(song_xml) + new_song = self.plugin.manager.get_object(Song, new_song_id) + new_song.title = u'%s <%s>' % (new_song.title, + translate('SongsPlugin.MediaItem', 'copy', + 'For song cloning')) + self.plugin.manager.save_object(new_song) + self.onSongListLoad() + def generateSlideData(self, service_item, item=None, xmlVersion=False): log.debug(u'generateSlideData (%s:%s)' % (service_item, item)) item_id = self._getIdOfItemToGenerate(item, self.remoteSong) diff --git a/openlp/plugins/songs/lib/olp1import.py b/openlp/plugins/songs/lib/olp1import.py index 423f5ece0..de7264a5b 100644 --- a/openlp/plugins/songs/lib/olp1import.py +++ b/openlp/plugins/songs/lib/olp1import.py @@ -57,6 +57,8 @@ class OpenLP1SongImport(SongImport): The database providing the data to import. """ SongImport.__init__(self, manager, **kwargs) + self.available_themes = \ + kwargs[u'plugin'].formparent.themeManagerContents.getThemes() def do_import(self): """ @@ -70,27 +72,34 @@ class OpenLP1SongImport(SongImport): encoding = self.get_encoding() if not encoding: return - # Connect to the database + # Connect to the database. connection = sqlite.connect(self.import_source, mode=0444, encoding=(encoding, 'replace')) cursor = connection.cursor() - # Determine if we're using a new or an old DB + # Determine if we're using a new or an old DB. cursor.execute(u'SELECT name FROM sqlite_master ' u'WHERE type = \'table\' AND name = \'tracks\'') new_db = len(cursor.fetchall()) > 0 - # "cache" our list of authors + # "cache" our list of authors. cursor.execute(u'-- types int, unicode') cursor.execute(u'SELECT authorid, authorname FROM authors') authors = cursor.fetchall() if new_db: - # "cache" our list of tracks + # "cache" our list of tracks. cursor.execute(u'-- types int, unicode') cursor.execute(u'SELECT trackid, fulltrackname FROM tracks') tracks = cursor.fetchall() - # Import the songs - cursor.execute(u'-- types int, unicode, unicode, unicode') + # "cache" our list of themes. + cursor.execute(u'-- types int, unicode') + cursor.execute(u'SELECT settingsid, settingsname FROM settings') + themes = {} + for theme_id, theme_name in cursor.fetchall(): + if theme_name in self.available_themes: + themes[theme_id] = theme_name + # Import the songs. + cursor.execute(u'-- types int, unicode, unicode, unicode, int') cursor.execute(u'SELECT songid, songtitle, lyrics || \'\' AS lyrics, ' - u'copyrightinfo FROM songs') + u'copyrightinfo, settingsid FROM songs') songs = cursor.fetchall() self.import_wizard.progressBar.setMaximum(len(songs)) for song in songs: @@ -101,8 +110,12 @@ class OpenLP1SongImport(SongImport): self.title = song[1] lyrics = song[2].replace(u'\r\n', u'\n') self.add_copyright(song[3]) + if themes.has_key(song[4]): + self.theme_name = themes[song[4]] verses = lyrics.split(u'\n\n') - [self.add_verse(verse.strip()) for verse in verses if verse.strip()] + for verse in verses: + if verse.strip(): + self.add_verse(verse.strip()) cursor.execute(u'-- types int') cursor.execute(u'SELECT authorid FROM songauthors ' u'WHERE songid = %s' % song_id) @@ -137,12 +150,12 @@ class OpenLP1SongImport(SongImport): """ Detect character encoding of an openlp.org 1.x song database. """ - # Connect to the database + # Connect to the database. connection = sqlite.connect(self.import_source, mode=0444) cursor = connection.cursor() detector = UniversalDetector() - # detect charset by authors + # Detect charset by authors. cursor.execute(u'SELECT authorname FROM authors') authors = cursor.fetchall() for author in authors: @@ -150,7 +163,7 @@ class OpenLP1SongImport(SongImport): if detector.done: detector.close() return detector.result[u'encoding'] - # detect charset by songs + # Detect charset by songs. cursor.execute(u'SELECT songtitle, copyrightinfo, ' u'lyrics || \'\' AS lyrics FROM songs') songs = cursor.fetchall() @@ -160,7 +173,7 @@ class OpenLP1SongImport(SongImport): if detector.done: detector.close() return detector.result[u'encoding'] - # detect charset by songs + # Detect charset by songs. cursor.execute(u'SELECT name FROM sqlite_master ' u'WHERE type = \'table\' AND name = \'tracks\'') if len(cursor.fetchall()) > 0: diff --git a/openlp/plugins/songs/lib/openlyricsexport.py b/openlp/plugins/songs/lib/openlyricsexport.py index 6ba1fabe7..ec5677ea4 100644 --- a/openlp/plugins/songs/lib/openlyricsexport.py +++ b/openlp/plugins/songs/lib/openlyricsexport.py @@ -35,6 +35,7 @@ import re from lxml import etree from openlp.core.lib import check_directory_exists, Receiver, translate +from openlp.core.utils import clean_filename from openlp.plugins.songs.lib import OpenLyrics log = logging.getLogger(__name__) @@ -72,8 +73,7 @@ class OpenLyricsExport(object): tree = etree.ElementTree(etree.fromstring(xml)) filename = u'%s (%s)' % (song.title, u', '.join([author.display_name for author in song.authors])) - filename = re.sub( - r'[/\\?*|<>\[\]":<>+%]+', u'_', filename).strip(u'_') + filename = clean_filename(filename) # Ensure the filename isn't too long for some filesystems filename = u'%s.xml' % filename[0:250 - len(self.save_path)] # Pass a file object, because lxml does not cope with some special diff --git a/openlp/plugins/songs/lib/opensongimport.py b/openlp/plugins/songs/lib/opensongimport.py index 3c8b46d4e..7fca88262 100644 --- a/openlp/plugins/songs/lib/opensongimport.py +++ b/openlp/plugins/songs/lib/opensongimport.py @@ -26,9 +26,7 @@ ############################################################################### import logging -import os import re -from zipfile import ZipFile from lxml import objectify from lxml.etree import Error, LxmlError @@ -110,48 +108,13 @@ class OpenSongImport(SongImport): SongImport.__init__(self, manager, **kwargs) def do_import(self): - """ - Import either each of the files in self.import_source - each element of - which can be either a single opensong file, or a zipfile containing - multiple opensong files. - """ - numfiles = 0 - for filename in self.import_source: - ext = os.path.splitext(filename)[1] - if ext.lower() == u'.zip': - z = ZipFile(filename, u'r') - numfiles += len(z.infolist()) - z.close() - else: - numfiles += 1 - log.debug(u'Total number of files: %d', numfiles) - self.import_wizard.progressBar.setMaximum(numfiles) + self.import_wizard.progressBar.setMaximum(len(self.import_source)) for filename in self.import_source: if self.stop_import_flag: return - ext = os.path.splitext(filename)[1] - if ext.lower() == u'.zip': - log.debug(u'Zipfile found %s', filename) - z = ZipFile(filename, u'r') - for song in z.infolist(): - if self.stop_import_flag: - z.close() - return - parts = os.path.split(song.filename) - if parts[-1] == u'': - # No final part => directory - continue - log.info(u'Zip importing %s', parts[-1]) - song_file = z.open(song) - self.do_import_file(song_file) - song_file.close() - z.close() - else: - # not a zipfile - log.info(u'Direct import %s', filename) - song_file = open(filename) - self.do_import_file(song_file) - song_file.close() + song_file = open(filename) + self.do_import_file(song_file) + song_file.close() def do_import_file(self, file): """ diff --git a/openlp/plugins/songs/lib/songbeamerimport.py b/openlp/plugins/songs/lib/songbeamerimport.py index 3a8d63783..400db8f9a 100644 --- a/openlp/plugins/songs/lib/songbeamerimport.py +++ b/openlp/plugins/songs/lib/songbeamerimport.py @@ -36,7 +36,6 @@ import re from openlp.plugins.songs.lib import VerseType from openlp.plugins.songs.lib.songimport import SongImport -from openlp.plugins.songs.lib.ui import SongStrings log = logging.getLogger(__name__) diff --git a/openlp/plugins/songs/lib/songimport.py b/openlp/plugins/songs/lib/songimport.py index 781321fe4..dc45764e4 100644 --- a/openlp/plugins/songs/lib/songimport.py +++ b/openlp/plugins/songs/lib/songimport.py @@ -29,9 +29,8 @@ import re from PyQt4 import QtCore -from openlp.core.lib import Receiver, translate, check_directory_exists +from openlp.core.lib import Receiver, translate from openlp.core.ui.wizard import WizardStrings -from openlp.core.utils import AppLocation from openlp.plugins.songs.lib import clean_song, VerseType from openlp.plugins.songs.lib.db import Song, Author, Topic, Book, MediaFile from openlp.plugins.songs.lib.ui import SongStrings diff --git a/openlp/plugins/songs/lib/songshowplusimport.py b/openlp/plugins/songs/lib/songshowplusimport.py index 7f7527c6d..c50df8752 100644 --- a/openlp/plugins/songs/lib/songshowplusimport.py +++ b/openlp/plugins/songs/lib/songshowplusimport.py @@ -102,7 +102,6 @@ class SongShowPlusImport(SongImport): if not isinstance(self.import_source, list): return self.import_wizard.progressBar.setMaximum(len(self.import_source)) - for file in self.import_source: self.sspVerseOrderList = [] otherCount = 0 @@ -111,7 +110,6 @@ class SongShowPlusImport(SongImport): self.import_wizard.incrementProgressBar( WizardStrings.ImportingType % file_name, 0) songData = open(file, 'rb') - while True: blockKey, = struct.unpack("I", songData.read(4)) # The file ends with 4 NUL's @@ -126,8 +124,9 @@ class SongShowPlusImport(SongImport): songData.read(2)) verseName = songData.read(verseNameLength) lengthDescriptorSize, = struct.unpack("B", songData.read(1)) + log.debug(lengthDescriptorSize) # Detect if/how long the length descriptor is - if lengthDescriptorSize == 12: + if lengthDescriptorSize == 12 or lengthDescriptorSize == 20: lengthDescriptor, = struct.unpack("I", songData.read(4)) elif lengthDescriptorSize == 2: lengthDescriptor = 1 @@ -135,6 +134,7 @@ class SongShowPlusImport(SongImport): lengthDescriptor = 0 else: lengthDescriptor, = struct.unpack("B", songData.read(1)) + log.debug(lengthDescriptorSize) data = songData.read(lengthDescriptor) if blockKey == TITLE: self.title = unicode(data, u'cp1252') diff --git a/openlp/plugins/songs/lib/wowimport.py b/openlp/plugins/songs/lib/wowimport.py index 0648abe66..e2a5820a5 100644 --- a/openlp/plugins/songs/lib/wowimport.py +++ b/openlp/plugins/songs/lib/wowimport.py @@ -31,7 +31,6 @@ Worship songs into the OpenLP database. import os import logging -from openlp.core.ui.wizard import WizardStrings from openlp.plugins.songs.lib.songimport import SongImport BLOCK_TYPES = (u'V', u'C', u'B') diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py index c5bf85ad8..193a823d5 100644 --- a/openlp/plugins/songs/lib/xml.py +++ b/openlp/plugins/songs/lib/xml.py @@ -73,6 +73,8 @@ from openlp.core.utils import get_application_version log = logging.getLogger(__name__) +CHORD_REGEX = re.compile(u'') + class SongXML(object): """ This class builds and parses the XML used to describe songs. @@ -234,7 +236,6 @@ class OpenLyrics(object): IMPLEMENTED_VERSION = u'0.7' def __init__(self, manager): self.manager = manager - self.chord_regex = re.compile(u'') def song_to_xml(self, song): """ @@ -245,8 +246,9 @@ class OpenLyrics(object): # Append the necessary meta data to the song. song_xml.set(u'xmlns', u'http://openlyrics.info/namespace/2009/song') song_xml.set(u'version', OpenLyrics.IMPLEMENTED_VERSION) - song_xml.set(u'createdIn', get_application_version()[u'version']) - song_xml.set(u'modifiedIn', get_application_version()[u'version']) + application_name = u'OpenLP ' + get_application_version()[u'version'] + song_xml.set(u'createdIn', application_name) + song_xml.set(u'modifiedIn', application_name) song_xml.set(u'modifiedDate', datetime.datetime.now().strftime(u'%Y-%m-%dT%H:%M:%S')) properties = etree.SubElement(song_xml, u'properties') @@ -319,7 +321,7 @@ class OpenLyrics(object): if xml[:5] == u'general_save.png general_email.png general_revert.png + general_clone.png slide_close.png @@ -137,6 +138,10 @@ messagebox_info.png messagebox_warning.png + + song_usage_active.png + song_usage_inactive.png + tools_add.png tools_alert.png diff --git a/resources/images/song_usage_active.png b/resources/images/song_usage_active.png new file mode 100644 index 000000000..1221e1310 Binary files /dev/null and b/resources/images/song_usage_active.png differ diff --git a/resources/images/song_usage_inactive.png b/resources/images/song_usage_inactive.png new file mode 100644 index 000000000..cdcf944ee Binary files /dev/null and b/resources/images/song_usage_inactive.png differ diff --git a/resources/osx/Info.plist.master b/resources/osx/Info.plist.master index 45aab92a5..5fc3aa48f 100755 --- a/resources/osx/Info.plist.master +++ b/resources/osx/Info.plist.master @@ -2,6 +2,99 @@ + + CFBundleDocumentTypes + + + CFBundleTypeExtension + + osz + + CFBundleTypeIconFiles + + openlp-logo-with-text.icns + + CFBundleTypeName + OpenLP Service + CFBundleTypeRole + Viewer + LSHandlerRank + Owner + LSItemContentTypes + + org.openlp.osz + + + + CFBundleTypeExtension + + otz + + CFBundleTypeIconFiles + + openlp-logo-with-text.icns + + CFBundleTypeName + OpenLP Theme + CFBundleTypeRole + Viewer + LSHandlerRank + Owner + LSItemContentTypes + + org.openlp.otz + + + + + UTExportedTypeDeclarations + + + UTTypeIdentifier + org.openlp.osz + UTTypeDescription + OpenLP Service + UTTypeConformsTo + + public.data + public.content + + UTTypeTagSpecification + + public.filename-extension + + osz + + public.mime-type + + application/x-openlp-service + + + + + UTTypeIdentifier + org.openlp.otz + UTTypeDescription + OpenLP Theme + UTTypeConformsTo + + public.data + public.content + + UTTypeTagSpecification + + public.filename-extension + + otz + + public.mime-type + + application/x-openlp-theme + + + + + CFBundleIdentifier org.openlp CFBundleShortVersionString diff --git a/resources/osx/applescript-adjustview-10-5.master b/resources/osx/applescript-adjustview-10-5.master index abc0e5a8d..19be239d0 100755 --- a/resources/osx/applescript-adjustview-10-5.master +++ b/resources/osx/applescript-adjustview-10-5.master @@ -48,7 +48,7 @@ on run set theViewOptions to the icon view options of container window set arrangement of theViewOptions to not arranged set icon size of theViewOptions to 128 - set background picture of theViewOptions to file ".installer-background.png" + set background picture of theViewOptions to file ".background:installer-background.png" if not exists file "Applications" then make new alias file at container window to POSIX file "/Applications" with properties {name:"Applications"} end if diff --git a/resources/osx/applescript-adjustview-10-6.master b/resources/osx/applescript-adjustview-10-6.master index 2b5a0c000..170842564 100755 --- a/resources/osx/applescript-adjustview-10-6.master +++ b/resources/osx/applescript-adjustview-10-6.master @@ -49,15 +49,19 @@ on run set theViewOptions to the icon view options of container window set arrangement of theViewOptions to not arranged set icon size of theViewOptions to 128 - set background picture of theViewOptions to file ".installer-background.png" - make new alias file at container window to POSIX file "/Applications" with properties {name:"Applications"} - delay 5 + set background picture of theViewOptions to file ".background:installer-background.png" + if not exists file "Applications" then + make new alias file at container window to POSIX file "/Applications" with properties {name:"Applications"} + end if + delay 1 set position of item "%s" of container window to {160, 200} set position of item ".Trashes" of container window to {100, 500} - set position of item ".installer-background.png" of container window to {200, 500} + set position of item ".background" of container window to {200, 500} set position of item ".DS_Store" of container window to {400, 500} set position of item "Applications" of container window to {550, 200} - set position of item ".VolumeIcon.icns" of container window to {500, 500} + if exists file ".VolumeIcon.icns" then + set position of item ".VolumeIcon.icns" of container window to {500, 500} + end if set position of item ".fseventsd" of container window to {300, 500} if exists POSIX file ".SymAVx86QSFile" then set position of item ".SymAVx86QSFile" of container window to {600, 500} diff --git a/resources/osx/build.py b/resources/osx/build.py index ff7f6bdfd..38e56e42a 100644 --- a/resources/osx/build.py +++ b/resources/osx/build.py @@ -93,8 +93,12 @@ script_name = "build" def build_application(settings, app_name_lower, app_dir): logging.info('[%s] now building the app with pyinstaller at "%s"...', script_name, settings['pyinstaller_basedir']) - result = os.system('python %s/pyinstaller.py openlp.spec' \ - % settings['pyinstaller_basedir']) + full_python_dir = os.path.join('/opt/local/Library/Frameworks', + 'Python.framework/Versions/2.6/Resources/', + 'Python.app/Contents/MacOS/Python') + result = os.system('arch -i386 %s %s/pyinstaller.py openlp.spec' \ + % ( full_python_dir, + settings['pyinstaller_basedir']) ) if (result != 0): logging.error('[%s] The pyinstaller build reported an error, cannot \ continue!', script_name) @@ -219,10 +223,10 @@ def create_dmg(settings): sys.exit(1) logging.info('[%s] copying the background image...', script_name) - # os.mkdir(volume_basedir + '/.background') + os.mkdir(volume_basedir + '/.background') result = os.system('CpMac %s %s' % (settings['installer_backgroundimage_file'], - volume_basedir + '/.installer-background.png')) + volume_basedir + '/.background/installer-background.png')) if (result != 0): logging.error('[%s] could not copy the background image, dmg creation\ failed!', script_name) diff --git a/resources/osx/openlp-logo-with-text.icns b/resources/osx/openlp-logo-with-text.icns old mode 100755 new mode 100644 diff --git a/resources/osx/openlp.cfg b/resources/osx/openlp.cfg.sample similarity index 81% rename from resources/osx/openlp.cfg rename to resources/osx/openlp.cfg.sample index 3be085dc0..f72a8fca1 100755 --- a/resources/osx/openlp.cfg +++ b/resources/osx/openlp.cfg.sample @@ -1,8 +1,8 @@ [openlp] openlp_appname = OpenLP -openlp_dmgname = OpenLP-1.9.4-bzrXXXX +openlp_dmgname = OpenLP-1.9.6-bzrXXXX openlp_version = XXXX -openlp_basedir = /Users/openlp/trunk +openlp_basedir = /Users/openlp/repo/trunk openlp_icon_file = openlp-logo-with-text.icns openlp_dmg_icon_file = openlp-logo-420x420.png installer_backgroundimage_file = installation-background.png diff --git a/resources/osx/openlp.spec.master b/resources/osx/openlp.spec.master index 94aec2a60..eb743c40b 100755 --- a/resources/osx/openlp.spec.master +++ b/resources/osx/openlp.spec.master @@ -1,5 +1,5 @@ # -*- mode: python -*- -a = Analysis([os.path.join(HOMEPATH,'support/_mountzlib.py'), os.path.join(HOMEPATH,'support/useUnicode.py'), '%(openlp_basedir)s/openlp.pyw'], +a = Analysis([os.path.join(HOMEPATH,'support/_mountzlib.py'), os.path.join(CONFIGDIR,'support/useUnicode.py'), '%(openlp_basedir)s/openlp.pyw'], pathex=['%(pyinstaller_basedir)s'], hookspath=['%(openlp_basedir)s/resources/pyinstaller']) pyz = PYZ(a.pure) exe = EXE(pyz, diff --git a/resources/windows/OpenLP.spec b/resources/windows/OpenLP.spec deleted file mode 100644 index 47a1952f3..000000000 --- a/resources/windows/OpenLP.spec +++ /dev/null @@ -1,14 +0,0 @@ -# -*- mode: python -*- -a = Analysis([ - os.path.join(HOMEPATH, 'support\\_mountzlib.py'), - os.path.join(HOMEPATH, 'support\\useUnicode.py'), - os.path.abspath('openlp.pyw')], - pathex=[os.path.abspath('.')]) -pyz = PYZ(a.pure) -exe = EXE(pyz, a.scripts, exclude_binaries=1, - name=os.path.abspath(os.path.join('build', 'pyi.win32', 'OpenLP', - 'OpenLP.exe')), - debug=False, strip=False, upx=True, console=False, - icon=os.path.abspath(os.path.join('resources', 'images', 'OpenLP.ico'))) -coll = COLLECT(exe, a.binaries, a.zipfiles, a.datas, strip=False, upx=True, - name=os.path.abspath(os.path.join('dist', 'OpenLP'))) diff --git a/scripts/check_dependencies.py b/scripts/check_dependencies.py new file mode 100755 index 000000000..7048ceeab --- /dev/null +++ b/scripts/check_dependencies.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2011 Raoul Snyman # +# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # +# Armin Köhler, Joshua Millar, Stevan Pettit, Andreas Preikschat, Mattias # +# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, 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 # +############################################################################### + +""" +This script is used to check dependencies of OpenLP. It checks availability +of required python modules and their version. To verify availability of Python +modules, simply run this script:: + + @:~$ ./check_dependencies.py + +""" +import os +import sys + +is_win = sys.platform.startswith('win') + +VERS = { + 'Python': '2.6', + 'PyQt4': '4.6', + 'Qt4': '4.6', + 'sqlalchemy': '0.5', + # pyenchant 1.6 required on Windows + 'enchant': '1.6' if is_win else '1.3' + } + +# pywin32 +WIN32_MODULES = [ + 'win32com', + 'win32ui', + 'pywintypes', + ] + +MODULES = [ + 'PyQt4', + 'PyQt4.QtCore', + 'PyQt4.QtGui', + 'PyQt4.QtNetwork', + 'PyQt4.QtOpenGL', + 'PyQt4.QtSvg', + 'PyQt4.QtTest', + 'PyQt4.QtWebKit', + 'PyQt4.phonon', + 'sqlalchemy', + 'sqlite3', + 'lxml', + 'chardet', + 'enchant', + 'BeautifulSoup', + 'mako', + ] + + +OPTIONAL_MODULES = [ + ('sqlite', ' (SQLite 2 support)'), + ('MySQLdb', ' (MySQL support)'), + ('psycopg2', ' (PostgreSQL support)'), +] + +w = sys.stdout.write + +def check_vers(version, required, text): + if type(version) is str: + version = version.split('.') + version = map(int, version) + if type(required) is str: + required = required.split('.') + required = map(int, required) + w(' %s >= %s ... ' % (text, '.'.join(map(str, required)))) + if version >= required: + w('.'.join(map(str, version)) + os.linesep) + return True + else: + w('FAIL' + os.linesep) + return False + +def print_vers_fail(required, text): + print(' %s >= %s ... FAIL' % (text, required)) + +def verify_python(): + if not check_vers(list(sys.version_info), VERS['Python'], text='Python'): + exit(1) + +def verify_versions(): + print('Verifying version of modules...') + try: + from PyQt4 import QtCore + check_vers(QtCore.PYQT_VERSION_STR, VERS['PyQt4'], 'PyQt4') + check_vers(QtCore.qVersion(), VERS['Qt4'], 'Qt4') + except ImportError: + print_vers_fail(VERS['PyQt4'], 'PyQt4') + print_vers_fail(VERS['Qt4'], 'Qt4') + try: + import sqlalchemy + check_vers(sqlalchemy.__version__, VERS['sqlalchemy'], 'sqlalchemy') + except ImportError: + print_vers_fail(VERS['sqlalchemy'], 'sqlalchemy') + try: + import enchant + check_vers(enchant.__version__, VERS['enchant'], 'enchant') + except ImportError: + print_vers_fail(VERS['enchant'], 'enchant') + +def check_module(mod, text='', indent=' '): + space = (30 - len(mod) - len(text)) * ' ' + w(indent + '%s%s... ' % (mod, text) + space) + try: + __import__(mod) + w('OK') + except ImportError: + w('FAIL') + w(os.linesep) + +def verify_pyenchant(): + w('Enchant (spell checker)... ') + try: + import enchant + w(os.linesep) + backends = ', '.join([x.name for x in enchant.Broker().describe()]) + print(' available backends: %s' % backends) + langs = ', '.join(enchant.list_languages()) + print(' available languages: %s' % langs) + except ImportError: + w('FAIL' + os.linesep) + +def verify_pyqt(): + w('Qt4 image formats... ') + try: + from PyQt4 import QtGui + read_f = ', '.join([unicode(format).lower() + for format in QtGui.QImageReader.supportedImageFormats()]) + write_f = ', '.join([unicode(format).lower() + for format in QtGui.QImageWriter.supportedImageFormats()]) + w(os.linesep) + print(' read: %s' % read_f) + print(' write: %s' % write_f) + except ImportError: + w('FAIL' + os.linesep) + +def main(): + verify_python() + + print('Checking for modules...') + for m in MODULES: + check_module(m) + + print('Checking for optional modules...') + for m in OPTIONAL_MODULES: + check_module(m[0], text=m[1]) + + if is_win: + print('Checking for Windows specific modules...') + for m in WIN32_MODULES: + check_module(m) + + verify_versions() + verify_pyqt() + verify_pyenchant() + +if __name__ == u'__main__': + main() diff --git a/scripts/windows-builder.py b/scripts/windows-builder.py index 854c927c9..b7bf70472 100644 --- a/scripts/windows-builder.py +++ b/scripts/windows-builder.py @@ -32,8 +32,7 @@ Windows Build Script This script is used to build the Windows binary and the accompanying installer. For this script to work out of the box, it depends on a number of things: -Python 2.6 - This build script only works with Python 2.6. +Python 2.6/2.7 PyQt4 You should already have this installed, OpenLP doesn't work without it. The @@ -47,12 +46,6 @@ PyEnchant Inno Setup 5 Inno Setup should be installed into "C:\%PROGRAMFILES%\Inno Setup 5" -UPX - This is used to compress DLLs and EXEs so that they take up less space, but - still function exactly the same. To install UPS, download it from - http://upx.sourceforge.net/, extract it into C:\%PROGRAMFILES%\UPX, and then - add that directory to your PATH environment variable. - Sphinx This is used to build the documentation. The documentation trunk must be at the same directory level as Openlp trunk and named "documentation" @@ -61,7 +54,7 @@ HTML Help Workshop This is used to create the help file PyInstaller - PyInstaller should be a checkout of revision 844 of trunk, and in a + PyInstaller should be a checkout of revision 1470 of trunk, and in a directory called, "pyinstaller" on the same level as OpenLP's Bazaar shared repository directory. The revision is very important as there is currently a major regression in HEAD. @@ -73,13 +66,8 @@ PyInstaller http://svn.pyinstaller.org/trunk Then you need to copy the two hook-*.py files from the "pyinstaller" - subdirectory in OpenLP's "resources" directory into PyInstaller's "hooks" - directory. - - Once you've done that, open a command prompt (DOS shell), navigate to the - PyInstaller directory and run:: - - C:\Projects\pyinstaller>python Configure.py + subdirectory in OpenLP's "resources" directory into PyInstaller's + "PyInstaller/hooks" directory. Bazaar You need the command line "bzr" client installed. @@ -102,7 +90,7 @@ psvince.dll the install will fail. The dll can be obtained from here: http://www.vincenzo.net/isxkb/index.php?title=PSVince) -Mako +Mako Mako Templates for Python. This package is required for building the remote plugin. It can be installed by going to your python_directory\scripts\.. and running "easy_install Mako". If you do not @@ -137,9 +125,18 @@ site_packages = os.path.join(os.path.split(python_exe)[0], u'Lib', # Files and executables pyi_build = os.path.abspath(os.path.join(branch_path, u'..', u'..', - u'pyinstaller', u'Build.py')) -lrelease_exe = os.path.join(site_packages, u'PyQt4', u'bin', u'lrelease.exe') + u'pyinstaller', u'pyinstaller.py')) +openlp_main_script = os.path.abspath(os.path.join(branch_path, 'openlp.pyw')) +if os.path.exists(os.path.join(site_packages, u'PyQt4', u'bin')): + # Older versions of the PyQt4 Windows installer put their binaries in the + # "bin" directory + lrelease_exe = os.path.join(site_packages, u'PyQt4', u'bin', u'lrelease.exe') +else: + # Newer versions of the PyQt4 Windows installer put their binaries in the + # base directory of the installation + lrelease_exe = os.path.join(site_packages, u'PyQt4', u'lrelease.exe') i18n_utils = os.path.join(script_path, u'translation_utils.py') +win32_icon = os.path.join(branch_path, u'resources', u'images', 'OpenLP.ico') # Paths source_path = os.path.join(branch_path, u'openlp') @@ -148,9 +145,8 @@ manual_build_path = os.path.join(manual_path, u'build') helpfile_path = os.path.join(manual_build_path, u'htmlhelp') i18n_path = os.path.join(branch_path, u'resources', u'i18n') winres_path = os.path.join(branch_path, u'resources', u'windows') -build_path = os.path.join(branch_path, u'build', u'pyi.win32', u'OpenLP') +build_path = os.path.join(branch_path, u'build') dist_path = os.path.join(branch_path, u'dist', u'OpenLP') -enchant_path = os.path.join(site_packages, u'enchant') pptviewlib_path = os.path.join(source_path, u'plugins', u'presentations', u'lib', u'pptviewlib') @@ -174,8 +170,16 @@ def update_code(): def run_pyinstaller(): print u'Running PyInstaller...' os.chdir(branch_path) - pyinstaller = Popen((python_exe, pyi_build, u'-y', u'-o', build_path, - os.path.join(winres_path, u'OpenLP.spec')), stdout=PIPE) + pyinstaller = Popen((python_exe, pyi_build, + u'--noconfirm', + u'--windowed', + u'--noupx', + u'-o', branch_path, + u'-i', win32_icon, + u'-p', branch_path, + u'-n', 'OpenLP', + openlp_main_script), + stdout=PIPE) output, error = pyinstaller.communicate() code = pyinstaller.wait() if code != 0: @@ -208,19 +212,6 @@ def write_version_file(): f.write(versionstring) f.close() -def copy_enchant(): - print u'Copying enchant/pyenchant...' - source = enchant_path - dest = os.path.join(dist_path, u'enchant') - for root, dirs, files in os.walk(source): - for filename in files: - if not filename.endswith(u'.pyc') and not filename.endswith(u'.pyo'): - dest_path = os.path.join(dest, root[len(source) + 1:]) - if not os.path.exists(dest_path): - os.makedirs(dest_path) - copy(os.path.join(root, filename), - os.path.join(dest_path, filename)) - def copy_plugins(): print u'Copying plugins...' source = os.path.join(source_path, u'plugins') @@ -242,10 +233,10 @@ def copy_windows_files(): os.path.join(dist_path, u'LICENSE.txt')) copy(os.path.join(winres_path, u'psvince.dll'), os.path.join(dist_path, u'psvince.dll')) - if os.path.isfile(os.path.join(helpfile_path, u'Openlp.chm')): + if os.path.isfile(os.path.join(helpfile_path, u'OpenLP.chm')): print u' Windows help file found' - copy(os.path.join(helpfile_path, u'Openlp.chm'), - os.path.join(dist_path, u'Openlp.chm')) + copy(os.path.join(helpfile_path, u'OpenLP.chm'), + os.path.join(dist_path, u'OpenLP.chm')) else: print u' WARNING ---- Windows help file not found ---- WARNING' @@ -330,17 +321,19 @@ def main(): import sys for arg in sys.argv: if arg == u'-v' or arg == u'--verbose': - print "Script path:", script_path - print "Branch path:", branch_path - print "Source path:", source_path - print "\"dist\" path:", dist_path - print "PyInstaller:", pyi_build + print "OpenLP main script: ......", openlp_main_script + print "Script path: .............", script_path + print "Branch path: .............", branch_path + print "Source path: .............", source_path + print "\"dist\" path: .............", dist_path + print "PyInstaller: .............", pyi_build print "Documentation branch path:", doc_branch_path - print "Help file build path;", helpfile_path - print "Inno Setup path:", innosetup_exe - print "Windows resources:", winres_path - print "VCBuild path:", vcbuild_exe - print "PPTVIEWLIB path:", pptviewlib_path + print "Help file build path: ....", helpfile_path + print "Inno Setup path: .........", innosetup_exe + print "Windows resources: .......", winres_path + print "VCBuild path: ............", vcbuild_exe + print "PPTVIEWLIB path: .........", pptviewlib_path + print "" elif arg == u'--skip-update': skip_update = True elif arg == u'/?' or arg == u'-h' or arg == u'--help': @@ -353,7 +346,6 @@ def main(): build_pptviewlib() run_pyinstaller() write_version_file() - copy_enchant() copy_plugins() if os.path.exists(manual_path): run_sphinx()