diff --git a/openlp.pyw b/openlp.pyw index 85ba81fba..ad06cfe0f 100755 --- a/openlp.pyw +++ b/openlp.pyw @@ -273,6 +273,9 @@ def main(): qInitResources() # Now create and actually run the application. app = OpenLP(qt_args) + if sys.platform == 'darwin': + OpenLP.addLibraryPath(QtGui.QApplication.applicationDirPath() + + "/qt4_plugins") #i18n Set Language language = LanguageManager.get_language() appTranslator = LanguageManager.get_translator(language) diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index 89ecaf3be..dc60a5a65 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -106,8 +106,8 @@ def translate(context, text, comment=None, def get_text_file_string(text_file): """ - Open a file and return its content as unicode string. If the supplied file - name is not a file then the function returns False. If there is an error + Open a file and return its content as unicode string. If the supplied file + name is not a file then the function returns False. If there is an error loading the file or the content can't be decoded then the function will return None. diff --git a/openlp/core/lib/db.py b/openlp/core/lib/db.py index 62150cce3..c92f992c8 100644 --- a/openlp/core/lib/db.py +++ b/openlp/core/lib/db.py @@ -65,7 +65,7 @@ def delete_database(plugin_name, db_file_name=None): The name of the plugin to remove the database for ``db_file_name`` - The database file name. Defaults to None resulting in the + The database file name. Defaults to None resulting in the plugin_name being used. """ db_file_path = None @@ -91,6 +91,7 @@ class BaseModel(object): instance.__setattr__(key, kwargs[key]) return instance + class Manager(object): """ Provide generic object persistence management @@ -107,7 +108,7 @@ class Manager(object): The init_schema function for this database ``db_file_name`` - The file name to use for this database. Defaults to None resulting + The file name to use for this database. Defaults to None resulting in the plugin_name being used. """ settings = QtCore.QSettings() @@ -211,11 +212,11 @@ class Manager(object): The type of objects to return ``filter_clause`` - The filter governing selection of objects to return. Defaults to + The filter governing selection of objects to return. Defaults to None. ``order_by_ref`` - Any parameters to order the returned objects by. Defaults to None. + Any parameters to order the returned objects by. Defaults to None. """ query = self.session.query(object_class) if filter_clause is not None: @@ -232,7 +233,7 @@ class Manager(object): The type of objects to return. ``filter_clause`` - The filter governing selection of objects to return. Defaults to + The filter governing selection of objects to return. Defaults to None. """ query = self.session.query(object_class) diff --git a/openlp/core/lib/rendermanager.py b/openlp/core/lib/rendermanager.py index b68237b4a..eaf628a34 100644 --- a/openlp/core/lib/rendermanager.py +++ b/openlp/core/lib/rendermanager.py @@ -34,6 +34,15 @@ from openlp.core.ui import MainDisplay log = logging.getLogger(__name__) +VERSE = u'The Lord said to {r}Noah{/r}: \n' \ + 'There\'s gonna be a {su}floody{/su}, {sb}floody{/sb}\n' \ + 'The Lord said to {g}Noah{/g}:\n' \ + 'There\'s gonna be a {st}floody{/st}, {it}floody{/it}\n' \ + 'Get those children out of the muddy, muddy \n' \ + '{r}C{/r}{b}h{/b}{bl}i{/bl}{y}l{/y}{g}d{/g}{pk}' \ + 'r{/pk}{o}e{/o}{pp}n{/pp} of the Lord\n' +FOOTER = [u'Arky Arky (Unknown)', u'Public Domain', u'CCLI 123456'] + class RenderManager(object): """ Class to pull all Renderer interactions into one place. The plugins will @@ -202,28 +211,17 @@ class RenderManager(object): self.force_page = force_page # set the default image size for previews self.calculate_default(self.screens.preview[u'size']) - verse = u'The Lord said to {r}Noah{/r}: \n' \ - 'There\'s gonna be a {su}floody{/su}, {sb}floody{/sb}\n' \ - 'The Lord said to {g}Noah{/g}:\n' \ - 'There\'s gonna be a {st}floody{/st}, {it}floody{/it}\n' \ - 'Get those children out of the muddy, muddy \n' \ - '{r}C{/r}{b}h{/b}{bl}i{/bl}{y}l{/y}{g}d{/g}{pk}' \ - 'r{/pk}{o}e{/o}{pp}n{/pp} of the Lord\n' - # make big page for theme edit dialog to get line count - if self.force_page: - verse = verse + verse + verse - else: - self.image_manager.del_image(theme_data.theme_name) - footer = [] - footer.append(u'Arky Arky (Unknown)') - footer.append(u'Public Domain') - footer.append(u'CCLI 123456') # build a service item to generate preview serviceItem = ServiceItem() serviceItem.theme = theme_data - serviceItem.add_from_text(u'', verse, footer) + if self.force_page: + # make big page for theme edit dialog to get line count + serviceItem.add_from_text(u'', VERSE + VERSE + VERSE, FOOTER) + else: + self.image_manager.del_image(theme_data.theme_name) + serviceItem.add_from_text(u'', VERSE, FOOTER) serviceItem.render_manager = self - serviceItem.raw_footer = footer + serviceItem.raw_footer = FOOTER serviceItem.render(True) if not self.force_page: self.display.buildHtml(serviceItem) diff --git a/openlp/core/lib/settingsmanager.py b/openlp/core/lib/settingsmanager.py index 3d8d8059e..d09497540 100644 --- a/openlp/core/lib/settingsmanager.py +++ b/openlp/core/lib/settingsmanager.py @@ -64,7 +64,7 @@ class SettingsManager(object): Read the last directory used for plugin. ``section`` - The section of code calling the method. This is used in the + The section of code calling the method. This is used in the settings key. ``num`` @@ -84,7 +84,7 @@ class SettingsManager(object): Save the last directory used for plugin. ``section`` - The section of code calling the method. This is used in the + The section of code calling the method. This is used in the settings key. ``directory`` @@ -160,11 +160,11 @@ class SettingsManager(object): Get a list of files from the data files path. ``section`` - Defaults to *None*. The section of code getting the files - used + Defaults to *None*. The section of code getting the files - used to load from a section's data subdirectory. ``extension`` - Defaults to *None*. The extension to search for. + Defaults to *None*. The extension to search for. """ path = AppLocation.get_data_path() if section: diff --git a/openlp/core/lib/theme.py b/openlp/core/lib/theme.py index 6f97c0608..ef26ca842 100644 --- a/openlp/core/lib/theme.py +++ b/openlp/core/lib/theme.py @@ -91,6 +91,7 @@ class ThemeLevel(object): Service = 2 Song = 3 + class BackgroundType(object): """ Type enumeration for backgrounds. @@ -123,6 +124,7 @@ class BackgroundType(object): elif type_string == u'image': return BackgroundType.Image + class BackgroundGradientType(object): """ Type enumeration for background gradients. @@ -200,6 +202,7 @@ INTEGER_LIST = [u'size', u'line_adjustment', u'x', u'height', u'y', u'width', u'shadow_size', u'outline_size', u'horizontal_align', u'vertical_align', u'wrap_style'] + class ThemeXML(object): """ A class to encapsulate the Theme XML. diff --git a/openlp/core/lib/ui.py b/openlp/core/lib/ui.py index c127d810d..eae4f60ca 100644 --- a/openlp/core/lib/ui.py +++ b/openlp/core/lib/ui.py @@ -130,7 +130,7 @@ def create_accept_reject_button_box(parent, okay=False): methods to handle the default ``accepted()`` and ``rejected()`` signals. ``parent`` - The parent object. This should be a ``QWidget`` descendant. + The parent object. This should be a ``QWidget`` descendant. ``okay`` If true creates an okay/cancel combination instead of save/cancel. @@ -185,15 +185,15 @@ def media_item_combo_box(parent, name): def create_delete_push_button(parent, icon=None): """ - Creates a standard push button with a delete label and optional icon. The + Creates a standard push button with a delete label and optional icon. The button is connected to the parent's ``onDeleteButtonClicked()`` method to handle the ``clicked()`` signal. ``parent`` - The parent object. This should be a ``QWidget`` descendant. + The parent object. This should be a ``QWidget`` descendant. ``icon`` - An icon to display on the button. This can be either a ``QIcon``, a + An icon to display on the button. This can be either a ``QIcon``, a resource path or a file name. """ delete_button = QtGui.QPushButton(parent) @@ -210,12 +210,12 @@ def create_delete_push_button(parent, icon=None): def create_up_down_push_button_set(parent): """ Creates a standard set of two push buttons, one for up and the other for - down, for use with lists. The buttons use arrow icons and no text and are + down, for use with lists. The buttons use arrow icons and no text and are connected to the parent's ``onUpButtonClicked()`` and ``onDownButtonClicked()`` to handle their respective ``clicked()`` signals. ``parent`` - The parent object. This should be a ``QWidget`` descendant. + The parent object. This should be a ``QWidget`` descendant. """ up_button = QtGui.QPushButton(parent) up_button.setIcon(build_icon(u':/services/service_up.png')) @@ -295,7 +295,7 @@ def create_valign_combo(form, parent, layout): The UI screen that the label and combo will appear on. ``parent`` - The parent object. This should be a ``QWidget`` descendant. + The parent object. This should be a ``QWidget`` descendant. ``layout`` A layout object to add the label and combo widgets to. diff --git a/openlp/core/theme/theme.py b/openlp/core/theme/theme.py index 17217113a..bb6b25a9e 100644 --- a/openlp/core/theme/theme.py +++ b/openlp/core/theme/theme.py @@ -92,7 +92,7 @@ class Theme(object): * ``solid`` - color ``BackgroundParameter2`` - Extra information about the background. The contents of this attribute + Extra information about the background. The contents of this attribute depend on the BackgroundType: * ``image`` - border color @@ -100,7 +100,7 @@ class Theme(object): * ``solid`` - N/A ``BackgroundParameter3`` - Extra information about the background. The contents of this attribute + Extra information about the background. The contents of this attribute depend on the BackgroundType: * ``image`` - N/A @@ -142,7 +142,7 @@ class Theme(object): Color for the outline (or None if Outline is 0) ``HorizontalAlign`` - The horizontal alignment to apply to text. Valid alignments are: + The horizontal alignment to apply to text. Valid alignments are: * ``0`` - left align * ``1`` - right align @@ -156,7 +156,7 @@ class Theme(object): * ``2`` - centre align ``WrapStyle`` - The wrap style to apply to the text. Valid styles are: + The wrap style to apply to the text. Valid styles are: * ``0`` - normal * ``1`` - lyrics diff --git a/openlp/core/ui/displaytagform.py b/openlp/core/ui/displaytagform.py index 114a52bf6..e29fe3384 100644 --- a/openlp/core/ui/displaytagform.py +++ b/openlp/core/ui/displaytagform.py @@ -72,7 +72,7 @@ class DisplayTagForm(QtGui.QDialog, Ui_DisplayTagDialog): def preLoad(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 + update the display. If Cancel was selected this is needed to reset the dsiplay to the correct version. """ # Initial Load of the Tags diff --git a/openlp/core/ui/generaltab.py b/openlp/core/ui/generaltab.py index f2bcbbe7e..4eacc5959 100644 --- a/openlp/core/ui/generaltab.py +++ b/openlp/core/ui/generaltab.py @@ -45,7 +45,7 @@ class ValidEdit(QtGui.QLineEdit): def validText(self): """ - Only return Integers. Space is 0 + Only return Integers. Space is 0 """ if self.text().isEmpty(): return QtCore.QString(u'0') diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 4ec1f91cc..b9de8a12e 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -279,7 +279,7 @@ class Ui_MainWindow(object): add_actions(self.SettingsLanguageMenu, self.LanguageGroup.actions()) add_actions(self.SettingsMenu, (self.settingsPluginListItem, self.SettingsLanguageMenu.menuAction(), None, - self.SettingsShortcutsItem, self.DisplayTagItem, + self.DisplayTagItem, self.SettingsShortcutsItem, self.SettingsConfigureItem)) add_actions(self.ToolsMenu, (self.ToolsAddToolItem, None)) add_actions(self.ToolsMenu, (self.ToolsOpenDataFolder, None)) diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index 974f98f61..0e04d7b46 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -266,7 +266,7 @@ class ThemeManager(QtGui.QWidget): oldThemeName = unicode(item.data(QtCore.Qt.UserRole).toString()) self.fileRenameForm.fileNameEdit.setText(oldThemeName) if self.fileRenameForm.exec_(): - newThemeName = unicode(self.fileRenameForm.fileNameEdit.text()) + newThemeName = unicode(self.fileRenameForm.fileNameEdit.text()) if self.checkIfThemeExists(newThemeName): oldThemeData = self.getThemeData(oldThemeName) self.cloneThemeData(oldThemeData, newThemeName) @@ -284,7 +284,7 @@ class ThemeManager(QtGui.QWidget): oldThemeName = unicode(item.data(QtCore.Qt.UserRole).toString()) self.fileRenameForm.fileNameEdit.setText(oldThemeName) if self.fileRenameForm.exec_(True): - newThemeName = unicode(self.fileRenameForm.fileNameEdit.text()) + newThemeName = unicode(self.fileRenameForm.fileNameEdit.text()) if self.checkIfThemeExists(newThemeName): themeData = self.getThemeData(oldThemeName) self.cloneThemeData(themeData, newThemeName) @@ -399,7 +399,7 @@ class ThemeManager(QtGui.QWidget): def onImportTheme(self): """ Opens a file dialog to select the theme file(s) to import before - attempting to extract OpenLP themes from those files. This process + attempting to extract OpenLP themes from those files. This process will load both OpenLP version 1 and version 2 themes. """ files = QtGui.QFileDialog.getOpenFileNames(self, diff --git a/openlp/core/utils/__init__.py b/openlp/core/utils/__init__.py index 86372e080..70b994653 100644 --- a/openlp/core/utils/__init__.py +++ b/openlp/core/utils/__init__.py @@ -242,7 +242,7 @@ def add_actions(target, actions): The menu or toolbar to add actions to. ``actions`` - The actions to be added. An action consisting of the keyword 'None' + The actions to be added. An action consisting of the keyword 'None' will result in a separator being inserted into the target. """ for action in actions: @@ -318,7 +318,7 @@ def get_web_page(url, header=None, update_openlp=False): Tells OpenLP to update itself if the page is successfully downloaded. Defaults to False. """ - # TODO: Add proxy usage. Get proxy info from OpenLP settings, add to a + # TODO: Add proxy usage. Get proxy info from OpenLP settings, add to a # proxy_handler, build into an opener and install the opener into urllib2. # http://docs.python.org/library/urllib2.html if not url: diff --git a/openlp/plugins/bibles/lib/db.py b/openlp/plugins/bibles/lib/db.py index 63dd15466..5cf000ee1 100644 --- a/openlp/plugins/bibles/lib/db.py +++ b/openlp/plugins/bibles/lib/db.py @@ -322,7 +322,7 @@ class BibleDB(QtCore.QObject, Manager): def get_books(self): """ A wrapper so both local and web bibles have a get_books() method that - manager can call. Used in the media manager advanced search tab. + manager can call. Used in the media manager advanced search tab. """ return self.get_all_objects(Book, order_by_ref=Book.id) diff --git a/openlp/plugins/bibles/lib/http.py b/openlp/plugins/bibles/lib/http.py index 60b515f88..e2dde59fd 100644 --- a/openlp/plugins/bibles/lib/http.py +++ b/openlp/plugins/bibles/lib/http.py @@ -35,7 +35,7 @@ import socket import urllib from HTMLParser import HTMLParseError -from BeautifulSoup import BeautifulSoup, NavigableString +from BeautifulSoup import BeautifulSoup, NavigableString, Tag from openlp.core.lib import Receiver, translate from openlp.core.lib.ui import critical_error_message_box @@ -221,25 +221,18 @@ class BGExtract(object): crossrefs = soup.findAll(u'sup', u'xref') if crossrefs: [crossref.extract() for crossref in crossrefs] + headings = soup.findAll(u'h5') + if headings: + [heading.extract() for heading in headings] cleanup = [(re.compile('\s+'), lambda match: ' ')] verses = BeautifulSoup(str(soup), markupMassage=cleanup) - content = verses.find(u'div', u'result-text-style-normal') - if not content: - content = verses.find(u'div', u'result-text-style-rtl-serif') - if not content: - log.debug(u'No content found in the BibleGateway response.') - send_error_message(u'parse') - return None - verse_count = len(verses.findAll(u'sup', u'versenum')) - found_count = 0 verse_list = {} - while found_count < verse_count: - content = content.findNext(u'sup', u'versenum') - raw_verse_num = content.next + for verse in verses(u'sup', u'versenum'): + raw_verse_num = verse.next clean_verse_num = 0 # Not all verses exist in all translations and may or may not be - # represented by a verse number. If they are not fine, if they are - # it will probably be in a format that breaks int(). We will then + # represented by a verse number. If they are not fine, if they are + # it will probably be in a format that breaks int(). We will then # have no idea what garbage may be sucked in to the verse text so # if we do not get a clean int() then ignore the verse completely. try: @@ -248,9 +241,22 @@ class BGExtract(object): log.exception(u'Illegal verse number in %s %s %s:%s', version, bookname, chapter, unicode(raw_verse_num)) if clean_verse_num: - raw_verse_text = raw_verse_num.next - verse_list[clean_verse_num] = unicode(raw_verse_text) - found_count += 1 + verse_text = raw_verse_num.next + part = raw_verse_num.next.next + while not (isinstance(part, Tag) and part.attrMap and + part.attrMap[u'class'] == u'versenum'): + # While we are still in the same verse grab all the text. + if isinstance(part, NavigableString): + verse_text = verse_text + part + if isinstance(part.next, Tag) and part.next.name == u'div': + # Run out of verses so stop. + break + part = part.next + verse_list[clean_verse_num] = unicode(verse_text) + if not verse_list: + log.debug(u'No content found in the BibleGateway response.') + send_error_message(u'parse') + return None return SearchResults(bookname, chapter, verse_list) @@ -384,7 +390,7 @@ class HTTPBible(BibleDB): BibleDB.__init__(self, parent, **kwargs) self.download_source = kwargs[u'download_source'] self.download_name = kwargs[u'download_name'] - # TODO: Clean up proxy stuff. We probably want one global proxy per + # TODO: Clean up proxy stuff. We probably want one global proxy per # connection type (HTTP and HTTPS) at most. self.proxy_server = None self.proxy_username = None @@ -458,8 +464,8 @@ class HTTPBible(BibleDB): search_results = self.get_chapter(book, reference[1]) if search_results and search_results.has_verselist(): ## We have found a book of the bible lets check to see - ## if it was there. By reusing the returned book name - ## we get a correct book. For example it is possible + ## if it was there. By reusing the returned book name + ## we get a correct book. For example it is possible ## to request ac and get Acts back. bookname = search_results.book Receiver.send_message(u'openlp_process_events') diff --git a/openlp/plugins/bibles/lib/manager.py b/openlp/plugins/bibles/lib/manager.py index 251ce350b..344d11584 100644 --- a/openlp/plugins/bibles/lib/manager.py +++ b/openlp/plugins/bibles/lib/manager.py @@ -46,16 +46,6 @@ except ImportError: log = logging.getLogger(__name__) -class BibleMode(object): - """ - This is basically an enumeration class which specifies the mode of a Bible. - Mode refers to whether or not a Bible in OpenLP is a full Bible or needs to - be downloaded from the Internet on an as-needed basis. - """ - Full = 1 - Partial = 2 - - class BibleFormat(object): """ This is a special enumeration class that holds the various types of Bibles, @@ -275,7 +265,7 @@ class BibleManager(object): 'Scripture Reference Error'), u'message': translate('BiblesPlugin.BibleManager', 'Your scripture reference is either not supported by OpenLP ' - 'or is invalid. Please make sure your reference conforms to ' + 'or is invalid. Please make sure your reference conforms to ' 'one of the following patterns:\n\n' 'Book Chapter\n' 'Book Chapter-Chapter\n' diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index e7cf273c1..ab7897828 100644 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -482,7 +482,7 @@ class BibleMediaItem(MediaManagerItem): self.listView.clear() if self.listView.count() != 0: self.__checkSecondBible(bible, second_bible) - else: + elif self.search_results: self.displayResults(bible, second_bible) Receiver.send_message(u'cursor_normal') self.advancedSearchButton.setEnabled(True) @@ -698,11 +698,7 @@ class BibleMediaItem(MediaManagerItem): service_item.add_capability(ItemCapabilities.AllowsPreview) service_item.add_capability(ItemCapabilities.AllowsLoop) # Service Item: Title - for title in raw_title: - if not service_item.title: - service_item.title = title - else: - service_item.title += u', ' + title + service_item.title = u', '.join(raw_title) # Service Item: Theme if len(self.settings.bible_theme) == 0: service_item.theme = None diff --git a/openlp/plugins/media/mediaplugin.py b/openlp/plugins/media/mediaplugin.py index ac3a2636b..3438f0279 100644 --- a/openlp/plugins/media/mediaplugin.py +++ b/openlp/plugins/media/mediaplugin.py @@ -63,7 +63,7 @@ class MediaPlugin(Plugin): if ext not in list: list.append(ext) self.serviceManager.supportedSuffixes(extension[1:]) - log.info(u'MediaPlugin: %s extensions: %s' % (mimetype, + log.info(u'MediaPlugin: %s extensions: %s' % (mimetype, u' '.join(extensions))) def about(self): diff --git a/openlp/plugins/presentations/lib/impresscontroller.py b/openlp/plugins/presentations/lib/impresscontroller.py index 2880bd27a..48fb85ed3 100644 --- a/openlp/plugins/presentations/lib/impresscontroller.py +++ b/openlp/plugins/presentations/lib/impresscontroller.py @@ -211,8 +211,8 @@ class ImpressDocument(PresentationDocument): """ Called when a presentation is added to the SlideController. It builds the environment, starts communcations with the background - OpenOffice task started earlier. If OpenOffice is not present is is - started. Once the environment is available the presentation is loaded + OpenOffice task started earlier. If OpenOffice is not present is is + started. Once the environment is available the presentation is loaded and started. ``presentation`` diff --git a/openlp/plugins/presentations/lib/messagelistener.py b/openlp/plugins/presentations/lib/messagelistener.py index cf43566ae..4db78f7a5 100644 --- a/openlp/plugins/presentations/lib/messagelistener.py +++ b/openlp/plugins/presentations/lib/messagelistener.py @@ -213,6 +213,7 @@ class Controller(object): def poll(self): self.doc.poll_slidenumber(self.is_live) + class MessageListener(object): """ This is the Presentation listener who acts on events from the slide diff --git a/openlp/plugins/presentations/lib/presentationcontroller.py b/openlp/plugins/presentations/lib/presentationcontroller.py index d995baa8c..bd37746c1 100644 --- a/openlp/plugins/presentations/lib/presentationcontroller.py +++ b/openlp/plugins/presentations/lib/presentationcontroller.py @@ -357,7 +357,7 @@ class PresentationController(object): def __init__(self, plugin=None, name=u'PresentationController', document_class=PresentationDocument): """ - This is the constructor for the presentationcontroller object. This + This is the constructor for the presentationcontroller object. This provides an easy way for descendent plugins to populate common data. This method *must* be overridden, like so:: diff --git a/openlp/plugins/remotes/lib/httpserver.py b/openlp/plugins/remotes/lib/httpserver.py index 173c89e13..c92569a4a 100644 --- a/openlp/plugins/remotes/lib/httpserver.py +++ b/openlp/plugins/remotes/lib/httpserver.py @@ -43,7 +43,7 @@ log = logging.getLogger(__name__) class HttpServer(object): """ Ability to control OpenLP via a webbrowser - e.g. http://localhost:4316/send/slidecontroller_live_next + e.g. http://localhost:4316/send/slidecontroller_live_next http://localhost:4316/send/alerts_text?q=your%20alert%20text """ def __init__(self, parent): diff --git a/openlp/plugins/songs/forms/editsongform.py b/openlp/plugins/songs/forms/editsongform.py index ef47e5694..0e874c3d5 100644 --- a/openlp/plugins/songs/forms/editsongform.py +++ b/openlp/plugins/songs/forms/editsongform.py @@ -557,7 +557,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): """ Check the validity of the song. """ - # This checks data in the form *not* self.song. self.song is still + # This checks data in the form *not* self.song. self.song is still # None at this point. log.debug(u'Validate Song') # Lets be nice and assume the data is correct. @@ -714,14 +714,14 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): def saveSong(self, preview=False): """ Get all the data from the widgets on the form, and then save it to the - database. The form has been validated and all reference items + database. The form has been validated and all reference items (Authors, Books and Topics) have been saved before this function is called. ``preview`` Should be ``True`` if the song is also previewed (boolean). """ - # The Song() assignment. No database calls should be made while a + # The Song() assignment. No database calls should be made while a # Song() is in a partially complete state. if not self.song: self.song = Song() diff --git a/openlp/plugins/songs/lib/easislidesimport.py b/openlp/plugins/songs/lib/easislidesimport.py index 67f889898..c4683765b 100644 --- a/openlp/plugins/songs/lib/easislidesimport.py +++ b/openlp/plugins/songs/lib/easislidesimport.py @@ -96,7 +96,7 @@ class EasiSlidesImport(SongImport): mandatory=False): """ Add imported values to the song model converting them to unicode at the - same time. If the unicode decode fails or a mandatory attribute is not + same time. If the unicode decode fails or a mandatory attribute is not present _success is set to False so the importer can react appropriately. diff --git a/openlp/plugins/songs/lib/ewimport.py b/openlp/plugins/songs/lib/ewimport.py index 2119cc245..880c7f547 100644 --- a/openlp/plugins/songs/lib/ewimport.py +++ b/openlp/plugins/songs/lib/ewimport.py @@ -72,7 +72,7 @@ def strip_rtf(blob, encoding): elif control_str == 'tab': clear_text.append(u'\t') # Prefer the encoding specified by the RTF data to that - # specified by the Paradox table header + # specified by the Paradox table header # West European encoding elif control_str == 'fcharset0': encoding = u'cp1252' @@ -129,6 +129,7 @@ class FieldDescEntry: self.type = type self.size = size + class EasyWorshipSongImport(SongImport): """ The :class:`EasyWorshipSongImport` class provides OpenLP with the @@ -163,7 +164,7 @@ class EasyWorshipSongImport(SongImport): if code_page == 852: self.encoding = u'cp1250' # The following codepage to actual encoding mappings have not been - # observed, but merely guessed. Actual example files are needed. + # observed, but merely guessed. Actual example files are needed. elif code_page == 737: self.encoding = u'cp1253' elif code_page == 775: diff --git a/openlp/plugins/songs/lib/foilpresenterimport.py b/openlp/plugins/songs/lib/foilpresenterimport.py index 1a2aaa18d..0d81e6f41 100644 --- a/openlp/plugins/songs/lib/foilpresenterimport.py +++ b/openlp/plugins/songs/lib/foilpresenterimport.py @@ -133,6 +133,7 @@ class FoilPresenterImport(SongImport): log.exception(u'XML syntax error in file %s' % file_path) return True + class FoilPresenter(object): """ This class represents the converter for Foilpresenter XML from a song. @@ -259,7 +260,6 @@ class FoilPresenter(object): copyright = None if copyright: strings = [] - author_temp = [] if copyright.find(u'Copyright') != -1: temp = copyright.partition(u'Copyright') copyright = temp[0] @@ -296,7 +296,7 @@ class FoilPresenter(object): u'Text +u\.?n?d? +Musik', u'T & M', u'Melodie und Satz', u'Text[\w\,\. ]*:', u'Melodie', u'Musik', u'Satz', u'Weise', u'[dD]eutsch', u'[dD]t[\.\:]', u'Englisch', - u'[oO]riginal', u'Bearbeitung', u'[R|r]efrain'] + u'[oO]riginal', u'Bearbeitung', u'[R|r]efrain'] for marker in markers: copyright = re.compile(marker).sub(u'', copyright, re.U) copyright = re.compile(u'(?<=) *:').sub(u'', copyright) @@ -305,7 +305,7 @@ class FoilPresenter(object): while i != 1: if copyright.find(u'') != -1: temp = copyright.partition(u'') - if (temp[0].strip() != u'') & (x > 0): + if temp[0].strip() and x > 0: strings.append(temp[0]) copyright = temp[2] x += 1 @@ -314,14 +314,15 @@ class FoilPresenter(object): i = 1 else: i = 1 + author_temp = [] for author in strings: temp = re.split(u',(?=\D{2})|(?<=\D),|\/(?=\D{3,})|(?<=\D);', author) for tempx in temp: author_temp.append(tempx) for author in author_temp: - regex = u'^[\/,;\-\s]+|[\/,;\-\s]+$|'\ - '\s*[0-9]{4}\s*[\-\/]?\s*([0-9]{4})?[\/,;\-\s]*$' + regex = u'^[\/,;\-\s\.]+|[\/,;\-\s\.]+$|'\ + '\s*[0-9]{4}\s*[\-\/]?\s*([0-9]{4})?[\/,;\-\s\.]*$' author = re.compile(regex).sub(u'', author) author = re.compile( u'[0-9]{1,2}\.\s?J(ahr)?h\.|um\s*$|vor\s*$').sub(u'', @@ -330,12 +331,12 @@ class FoilPresenter(object): author = author.strip() if re.search( u'\w+\.?\s+\w{3,}\s+[a|u]nd\s|\w+\.?\s+\w{3,}\s+&\s', - author, re.U) != None: + author, re.U): temp = re.split(u'\s[a|u]nd\s|\s&\s', author) for tempx in temp: tempx = tempx.strip() authors.append(tempx) - elif (len(author) > 2): + elif len(author) > 2: authors.append(author) for display_name in authors: author = self.manager.get_object_filtered(Author, @@ -411,7 +412,7 @@ class FoilPresenter(object): temp_verse_order_backup = [] temp_sortnr_backup = 1 temp_sortnr_liste = [] - versenumber = {u'V': 1, u'C': 1, u'B': 1, u'E': 1, u'O': 1, u'I': 1, + versenumber = {u'V': 1, u'C': 1, u'B': 1, u'E': 1, u'O': 1, u'I': 1, u'P': 1} for strophe in foilpresenterfolie.strophen.strophe: text = self._child(strophe.text_) diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py index 1bf00b403..d29e18c1d 100644 --- a/openlp/plugins/songs/lib/mediaitem.py +++ b/openlp/plugins/songs/lib/mediaitem.py @@ -53,6 +53,7 @@ class SongSearch(object): Authors = 4 Themes = 5 + class SongMediaItem(MediaManagerItem): """ This is the custom media manager item for Songs. @@ -199,7 +200,7 @@ class SongMediaItem(MediaManagerItem): """ log.debug(u'onSongListLoad') # Called to redisplay the song list screen edit from a search - # or from the exit of the Song edit dialog. If remote editing is active + # or from the exit of the Song edit dialog. If remote editing is active # Trigger it and clean up so it will not update again. if self.remoteTriggered == u'L': self.onAddClick() @@ -218,13 +219,9 @@ class SongMediaItem(MediaManagerItem): self.listView.clear() searchresults.sort(cmp=self.collateSongTitles) for song in searchresults: - author_list = u'' - for author in song.authors: - if author_list != u'': - author_list = author_list + u', ' - author_list = author_list + author.display_name + author_list = [author.display_name for author in song.authors] song_title = unicode(song.title) - song_detail = u'%s (%s)' % (song_title, author_list) + song_detail = u'%s (%s)' % (song_title, u', '.join(author_list)) song_name = QtGui.QListWidgetItem(song_detail) song_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(song.id)) self.listView.addItem(song_name) @@ -334,9 +331,6 @@ class SongMediaItem(MediaManagerItem): def generateSlideData(self, service_item, item=None, xmlVersion=False): log.debug(u'generateSlideData (%s:%s)' % (service_item, item)) raw_footer = [] - author_list = u'' - author_audit = [] - ccli = u'' item_id = self._getIdOfItemToGenerate(item, self.remoteSong) service_item.add_capability(ItemCapabilities.AllowsEdit) service_item.add_capability(ItemCapabilities.AllowsPreview) @@ -397,13 +391,9 @@ class SongMediaItem(MediaManagerItem): for slide in verses: service_item.add_from_text(slide[:30], unicode(slide)) service_item.title = song.title - for author in song.authors: - if len(author_list) > 1: - author_list = author_list + u', ' - author_list = author_list + unicode(author.display_name) - author_audit.append(unicode(author.display_name)) + author_list = [unicode(author.display_name) for author in song.authors] raw_footer.append(song.title) - raw_footer.append(author_list) + raw_footer.append(u', '.join(author_list)) raw_footer.append(song.copyright) if QtCore.QSettings().value(u'general/ccli number', QtCore.QVariant(u'')).toString(): @@ -413,10 +403,10 @@ class SongMediaItem(MediaManagerItem): QtCore.QVariant(u'')).toString())) service_item.raw_footer = raw_footer service_item.audit = [ - song.title, author_audit, song.copyright, unicode(song.ccli_number) + song.title, author_list, song.copyright, unicode(song.ccli_number) ] service_item.data_string = {u'title': song.search_title, - u'authors': author_list} + u'authors': u', '.join(author_list)} service_item.xml_version = self.openLyrics.song_to_xml(song) return True diff --git a/openlp/plugins/songs/lib/opensongimport.py b/openlp/plugins/songs/lib/opensongimport.py index 8fddf5e4f..4da1234a5 100644 --- a/openlp/plugins/songs/lib/opensongimport.py +++ b/openlp/plugins/songs/lib/opensongimport.py @@ -36,7 +36,6 @@ from openlp.plugins.songs.lib.songimport import SongImport log = logging.getLogger(__name__) -#TODO: Use lxml for parsing and make sure we use methods of "SongImport" . class OpenSongImport(SongImport): """ Import songs exported from OpenSong diff --git a/openlp/plugins/songs/lib/songimport.py b/openlp/plugins/songs/lib/songimport.py index 0212184f8..5bfba9b77 100644 --- a/openlp/plugins/songs/lib/songimport.py +++ b/openlp/plugins/songs/lib/songimport.py @@ -347,7 +347,7 @@ class SongImport(QtCore.QObject): """ For debugging """ - print u'========================================' \ + print u'========================================' \ + u'========================================' print u'TITLE: ' + self.title print u'ALT TITLE: ' + self.alternate_title diff --git a/resources/osx/Info.plist.master b/resources/osx/Info.plist.master new file mode 100755 index 000000000..e97e1faf5 --- /dev/null +++ b/resources/osx/Info.plist.master @@ -0,0 +1,32 @@ + + + + +CFBundleIdentifier +org.openlp +CFBundleShortVersionString +%(openlp_version)s +CFBundleVersion +%(openlp_version)s +CFBundleInfoDictionaryVersion +6.0 +CFBundleDisplayName +%(openlp_appname)s +CFBundleIconFile +openlp-logo-with-text.icns +CFBundleExecutable +MacOS/openlp +CFBundleName +%(openlp_appname)s +CFBundleGetInfoString +%(openlp_appname)s %(openlp_version)s +LSHasLocalizedDisplayName + +NSAppleScriptEnabled + +CFBundlePackageType +APPL +LSBackgroundOnly + + + diff --git a/resources/osx/Makefile b/resources/osx/Makefile new file mode 100644 index 000000000..422749da5 --- /dev/null +++ b/resources/osx/Makefile @@ -0,0 +1,28 @@ +all: + python build.py -c openlp.cfg + +view: + python build.py -c openlp.cfg --package-view --compress-view + +package: + + python build.py -c openlp.cfg --package --package-view + +bundle: + + python build.py -c openlp.cfg --compress --compress-view + +clean: + # remove old configuration files + rm -f openlp.spec + rm -f Info.plist + rm -f .version + + # remove old build artifacts + rm -rf build + rm -rf dist + rm -rf Macopenlp.app + rm -rf OpenLP.app + rm -f warnopenlp.txt + rm -f *dmg + diff --git a/resources/osx/applescript-adjustview-10-5.master b/resources/osx/applescript-adjustview-10-5.master new file mode 100755 index 000000000..abc0e5a8d --- /dev/null +++ b/resources/osx/applescript-adjustview-10-5.master @@ -0,0 +1,74 @@ +on saveImageWithItselfAsIcon(icon_image_file) + -- save icon_image_file with itself as icon + set icon_image_file_string to icon_image_file as string + tell application "Image Events" + launch + set icon_image to open file icon_image_file_string + save icon_image with icon + close icon_image + end tell +end saveImageWithItselfAsIcon + +on copyIconOfTo(aFileOrFolderWithIcon, aFileOrFolder) + tell application "Finder" to set f to aFileOrFolderWithIcon as alias + -- grab the file's icon + my CopyOrPaste(f, "c") + -- now the icon is in the clipboard + tell application "Finder" to set c to aFileOrFolder as alias + my CopyOrPaste(result, "v") +end copyIconOfTo + +on CopyOrPaste(i, cv) + tell application "Finder" + activate + open information window of i + end tell + tell application "System Events" to tell process "Finder" to tell window 1 + keystroke tab -- select icon button + keystroke (cv & "w") using command down (* (copy or paste) + close window *) + end tell -- window 1 then process Finder then System Events +end CopyOrPaste + +on run + set icon_image_file to POSIX file "%s" as alias + set dmg_file to POSIX file "/Volumes/%s" as alias + + my saveImageWithItselfAsIcon(icon_image_file) + -- wait for virus scanner + delay 2 + my copyIconOfTo(icon_image_file, dmg_file) + + tell application "Finder" + tell disk "%s" + open + set current view of container window to icon view + set toolbar visible of container window to false + set statusbar visible of container window to false + set the bounds of container window to {400, 100, 1100, 500} + 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" + if not exists file "Applications" then + make new alias file at container window to POSIX file "/Applications" with properties {name:"Applications"} + end if + delay 5 + 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 ".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} + 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} + end if + open + close + update without registering applications + -- wait until the virus scan completes + delay 5 + -- eject + end tell + end tell +end run diff --git a/resources/osx/applescript-adjustview-10-6.master b/resources/osx/applescript-adjustview-10-6.master new file mode 100755 index 000000000..2b5a0c000 --- /dev/null +++ b/resources/osx/applescript-adjustview-10-6.master @@ -0,0 +1,73 @@ +on saveImageWithItselfAsIcon(icon_image_file) + -- save icon_image_file with itself as icon + set icon_image_file_string to icon_image_file as string + tell application "Image Events" + launch + set icon_image to open file icon_image_file_string + save icon_image with icon + close icon_image + end tell +end saveImageWithItselfAsIcon + +on copyIconOfTo(aFileOrFolderWithIcon, aFileOrFolder) + tell application "Finder" to set f to aFileOrFolderWithIcon as alias + -- grab the file's icon + my CopyOrPaste(f, "c") + -- now the icon is in the clipboard + tell application "Finder" to set c to aFileOrFolder as alias + my CopyOrPaste(result, "v") +end copyIconOfTo + +on CopyOrPaste(i, cv) + tell application "Finder" + activate + set infoWindow to open information window of i + set infoWindowName to name of infoWindow + end tell + tell application "System Events" to tell process "Finder" to tell window infoWindowName + keystroke tab -- select icon button + keystroke (cv & "w") using command down (* (copy or paste) + close window *) + end tell -- window 1 then process Finder then System Events +end CopyOrPaste + +on run + set icon_image_file to POSIX file "%s" as alias + set dmg_file to POSIX file "/Volumes/%s" as alias + + my saveImageWithItselfAsIcon(icon_image_file) + -- wait for virus scanner + delay 2 + my copyIconOfTo(icon_image_file, dmg_file) + + tell application "Finder" + tell disk "%s" + open + set current view of container window to icon view + set toolbar visible of container window to false + set statusbar visible of container window to false + set the bounds of container window to {400, 100, 1100, 500} + 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 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 ".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} + 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} + end if + open + close + update without registering applications + -- wait until the virus scan completes + delay 5 + -- eject + end tell + end tell +end run diff --git a/resources/osx/applescript-seticon-10-5.master b/resources/osx/applescript-seticon-10-5.master new file mode 100755 index 000000000..738c4b883 --- /dev/null +++ b/resources/osx/applescript-seticon-10-5.master @@ -0,0 +1,40 @@ +on saveImageWithItselfAsIcon(icon_image_file) + -- save icon_image_file with itself as icon + set icon_image_file_string to icon_image_file as string + tell application "Image Events" + launch + set icon_image to open file icon_image_file_string + save icon_image with icon + close icon_image + end tell +end saveImageWithItselfAsIcon + +on copyIconOfTo(aFileOrFolderWithIcon, aFileOrFolder) + tell application "Finder" to set f to aFileOrFolderWithIcon as alias + -- grab the file's icon + my CopyOrPaste(f, "c") + -- now the icon is in the clipboard + tell application "Finder" to set c to aFileOrFolder as alias + my CopyOrPaste(result, "v") +end copyIconOfTo + +on CopyOrPaste(i, cv) + tell application "Finder" + activate + open information window of i + end tell + tell application "System Events" to tell process "Finder" to tell window 1 + keystroke tab -- select icon button + keystroke (cv & "w") using command down (* (copy or paste) + close window *) + end tell -- window 1 then process Finder then System Events +end CopyOrPaste + +on run + set icon_image_file to POSIX file "%s" as alias + set dmg_file to POSIX file "%s" as alias + + my saveImageWithItselfAsIcon(icon_image_file) + -- wait for virus scanner + delay 2 + my copyIconOfTo(icon_image_file, dmg_file) +end run diff --git a/resources/osx/applescript-seticon-10-6.master b/resources/osx/applescript-seticon-10-6.master new file mode 100755 index 000000000..849f02344 --- /dev/null +++ b/resources/osx/applescript-seticon-10-6.master @@ -0,0 +1,41 @@ +on saveImageWithItselfAsIcon(icon_image_file) + -- save icon_image_file with itself as icon + set icon_image_file_string to icon_image_file as string + tell application "Image Events" + launch + set icon_image to open file icon_image_file_string + save icon_image with icon + close icon_image + end tell +end saveImageWithItselfAsIcon + +on copyIconOfTo(aFileOrFolderWithIcon, aFileOrFolder) + tell application "Finder" to set f to aFileOrFolderWithIcon as alias + -- grab the file's icon + my CopyOrPaste(f, "c") + -- now the icon is in the clipboard + tell application "Finder" to set c to aFileOrFolder as alias + my CopyOrPaste(result, "v") +end copyIconOfTo + +on CopyOrPaste(i, cv) + tell application "Finder" + activate + set infoWindow to open information window of i + set infoWindowName to name of infoWindow + end tell + tell application "System Events" to tell process "Finder" to tell window infoWindowName + keystroke tab -- select icon button + keystroke (cv & "w") using command down (* (copy or paste) + close window *) + end tell -- window 1 then process Finder then System Events +end CopyOrPaste + +on run + set icon_image_file to POSIX file "%s" as alias + set dmg_file to POSIX file "%s" as alias + + my saveImageWithItselfAsIcon(icon_image_file) + -- wait for virus scanner + delay 2 + my copyIconOfTo(icon_image_file, dmg_file) +end run diff --git a/resources/osx/build.py b/resources/osx/build.py new file mode 100644 index 000000000..dfe0b897a --- /dev/null +++ b/resources/osx/build.py @@ -0,0 +1,412 @@ +#!/usr/bin/python +# -*- encoding: utf-8 -*- + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2011 Raoul Snyman # +# Portions copyright (c) 2008-2011 Tim Bentley, Jonathan Corwin, Michael # +# Gorven, Scott Guerrieri, Meinert Jordan, Armin Köhler, Andreas Preikschat, # +# Christian Richter, Philip Ridout, Maikel Stuivenberg, Martin Thompson, Jon # +# Tibble, Carsten Tinggaard, Frode Woldsund # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### + +""" +Mac OS X Build Script +--------------------- + +This script is used to build the OS X 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. + +PyQt4 + You should already have this installed, OpenLP doesn't work without it. + The version the script expects is the packaged one available from River + Bank Computing. + +PyInstaller + PyInstaller should be a checkout of revision 1355 of trunk, and in a + directory which is configured in the openlp.cfg. The revision is very + important as there is just included a fix for builds on OS X. + + To install PyInstaller, first checkout trunk from Subversion. The + easiest way is to do a + + svn co 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. + +openlp.cfg + The configuration file contains settings of the version string to include + in the bundle as well as directory and file settings for different + purposes (e.g. PyInstaller location or installer background image) + +To start the build process do a + + make + +inside the resources/osx directory. The result should be a {openlp_dmgname}.dmg +file in the same directory. If something went wrong - this sometimes happen +with the graphical commands in the Apple script - do a + + make clean + +and start the build process again. If you want to execute only parts of the +build process you can specify different make targets + + make view -- runs the Apple scripts to set the icons + make package -- creates the dmg file and copies the application files + make bundle -- compresses the dmg file and sets the dmg file icon +""" + +import time +import os +import ConfigParser +import logging +import optparse +import sys +import platform +import re +import subprocess as subp + +# set the script name +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']) + if (result != 0): + logging.error('[%s] The pyinstaller build reported an error, cannot \ + continue!', script_name) + sys.exit(1) + + logging.info('[%s] copying the qt_menu files...', script_name) + # see http://www.pyinstaller.org/ticket/157 + result = os.system('cp -R %(qt_menu_directory)s \ + %(application_directory)s/Contents/Resources' \ + % { 'qt_menu_directory' : settings['qt_menu_basedir'], + 'application_directory' : app_dir }) + if (result != 0): + logging.error('[%s] could not copy the qt_menu files, cannot \ + continue!', script_name) + sys.exit(1) + + dist_folder = os.getcwd() + '/dist/' + app_name_lower + + logging.info('[%s] copying the new plugins...', script_name) + result = os.system('cp -R %(openlp_directory)s/openlp/plugins \ + %(application_directory)s/Contents/MacOS' \ + % { 'openlp_directory' : settings['openlp_basedir'], + 'application_directory' : app_dir }) + if (result != 0): + logging.error('[%s] could not copy plugins, dmg creation failed!', + script_name) + sys.exit(1) + + logging.info('[%s] copying the icons to the resource directory...', + script_name) + result = os.system('cp %(icon_file)s \ + %(application_directory)s/Contents/Resources' \ + % { 'icon_file' : settings['openlp_icon_file'], + 'application_directory' : app_dir }) + if (result != 0): + logging.error('[%s] could not copy the icon, dmg creation failed!', + script_name) + sys.exit(1) + + logging.info('[%s] copying the version file...', script_name) + result = os.system('CpMac %s/.version %s/Contents/MacOS' % (os.getcwd(), + app_dir)) + if (result != 0): + logging.error('[%s] could not copy the version file, dmg creation \ + failed!', script_name) + sys.exit(1) + + logging.info('[%s] copying the new Info.plist...', script_name) + result = os.system('cp %(target_directory)s/Info.plist \ + %(application_directory)s/Contents' \ + % { 'target_directory' : os.getcwd(), + 'application_directory' : app_dir }) + if (result != 0): + logging.error('[%s] could not copy the info file, dmg creation \ + failed!', script_name) + sys.exit(1) + +def deploy_qt(settings): + logging.info('[%s] running mac deploy qt on %s.app...', script_name, + settings['openlp_appname']); + + result = os.system('macdeployqt %s.app' % settings['openlp_appname']); + if (result != 0): + logging.error('[%s] could not create dmg file!', script_name) + sys.exit(1) + +def create_dmg(settings): + logging.info('[%s] creating the dmg...', script_name) + dmg_file = os.getcwd() + '/' + settings['openlp_dmgname'] + '.dmg' + result = os.system('hdiutil create %(dmg_file)s~ -ov -megabytes \ + %(vol_size)s -fs HFS+ -volname %(vol_name)s' \ + % { 'dmg_file' : dmg_file, + 'vol_size' : '250', + 'vol_name' : settings['openlp_appname'] }) + if (result != 0): + logging.error('[%s] could not create dmg file!', script_name) + sys.exit(1) + + logging.info('[%s] mounting the dmg file...', script_name) + output = subp.Popen(["hdiutil", "attach", dmg_file + "~.dmg"], + stdout=subp.PIPE).communicate()[0] + logging.debug(output) + + p = re.compile('Apple_HFS\s+(.+?)\s*$') + result = p.search(output, re.M) + volume_basedir = '' + if result: + volume_basedir = result.group(1) + else: + logging.error('could not mount dmg file, cannot continue!') + sys.exit(1) + + logging.info('[%s] copying the app (from %s) to the dmg (at %s)...', + script_name, app_dir, volume_basedir) + result = os.system('CpMac -r %s %s' \ + % ( app_dir, volume_basedir )) + if (result != 0): + logging.error('[%s] could not copy application, dmg creation failed!', + script_name) + sys.exit(1) + + logging.info('[%s] copying the background image...', script_name) + # os.mkdir(volume_basedir + '/.background') + result = os.system('CpMac %s %s' + % (settings['installer_backgroundimage_file'], + volume_basedir + '/.installer-background.png')) + if (result != 0): + logging.error('[%s] could not copy the background image, dmg creation\ + failed!', script_name) + sys.exit(1) + + return (volume_basedir, dmg_file) + +def unmount_dmg(settings, volume_basedir): + logging.info('[%s] unmounting the dmg...', script_name) + result = os.system('hdiutil detach %s' % volume_basedir) + if (result != 0): + logging.error('[%s] could not unmount the dmg file, dmg creation \ + failed!', script_name) + sys.exit(1) + +def compress_view(settings, seticon_scriptname, dmg_file): + logging.info('[%s] setting icon of the dmg file...', script_name) + try: + f = open(seticon_scriptname) + p = subp.Popen(["osascript"], stdin=subp.PIPE) + p.communicate(f.read() % ((os.getcwd() + '/' + + settings['openlp_dmg_icon_file']), dmg_file)) + f.close() + result = p.returncode + if (result != 0): + logging.error('[%s] could not set the icon to the dmg file, \ + dmg creation failed!', script_name) + sys.exit(1) + except IOError, e: + logging.error('[%s] could not adjust the view (%s), dmg creation \ + failed!', script_name, e) + sys.exit(1) + except OSError, e: + logging.error('[%s] could not set the icon to the dmg file(%s), \ + dmg creation failed!', script_name, e) + sys.exit(1) + +def adjust_package_view(settings, adjustview_scriptname): + logging.info('[%s] making adjustments to the view...', script_name) + try: + f = open(adjustview_scriptname) + p = subp.Popen(["osascript"], stdin=subp.PIPE) + p.communicate(f.read() % ((os.getcwd() + '/' + \ + settings['openlp_dmg_icon_file']), + settings['openlp_appname'], + settings['openlp_appname'], + settings['openlp_appname'])) + f.close() + result = p.returncode + if (result != 0): + logging.error('[%s] could not adjust the view, dmg creation \ + failed!', script_name) + sys.exit(1) + except IOError, e: + logging.error('[%s] could not adjust the view (%s), dmg creation \ + failed!', script_name, e) + sys.exit(1) + except OSError, e: + logging.error('[%s] could not adjust the view (%s), dmg creation \ + failed!', script_name, e) + sys.exit(1) + +def compress_dmg(settings): + logging.info('[%s] compress the dmg file...', script_name) + result = os.system('hdiutil convert %s~.dmg -format UDZO \ + -imagekey zlib-level=9 -o %s' \ + % (dmg_file, dmg_file)) + if (result != 0): + logging.error('[%s] could not compress the dmg file, dmg creation \ + failed!', script_name) + sys.exit(1) + + +if __name__ == '__main__': + + # set default actions + do_build = True + do_compress_view = True + do_package_view = True + do_create_dmg = True + do_compress_dmg = True + do_deploy_qt = True + + parser = optparse.OptionParser() + parser.add_option('-c', '--config', dest='config', help='config file', + metavar='CONFIG') + parser.add_option('-v', '--package-view', dest='package_view', + help='triggers view adjustment scripts for package', + metavar='PACKAGEVIEWONLY', action='store_true', default=False) + parser.add_option('-y', '--compress-view', dest='compress_view', + help='triggers view adjustment scripts for dmg', + metavar='COMPRESSVIEWONLY', action='store_true', default=False) + parser.add_option('-p', '--package', dest='package', + help='package application folder to dmg', metavar='PACKAGE', + action='store_true', default=False) + parser.add_option('-z', '--compress', dest='compress', + help='compresses the existing dmg', metavar='COMPRESS', + action='store_true', default=False) + parser.add_option('-b', '--basedir', dest='basedir', + help='volume basedir like /Volumes/OpenLP', metavar='BASEDIR', + default='/Volumes/OpenLP') + + (options, args) = parser.parse_args() + + # if an option is set, false all + if (options.package_view is True or options.compress_view is True + or options.package is True or options.compress is True): + do_build = False + do_deploy_qt = False + do_package_view = options.package_view + do_compress_view = options.compress_view + do_create_dmg = options.package + do_compress_dmg = options.compress + + if not options.config: + parser.error('option --config|-c is required') + + logHandler = logging.StreamHandler() + logHandler.setFormatter(logging.Formatter( + '%(asctime)s %(levelname)-8s %(message)s', + '%a, %d %b %Y %H:%M:%S')) + logging.getLogger().addHandler(logHandler) + logging.getLogger().setLevel(logging.DEBUG) + + config = ConfigParser.RawConfigParser() + config.readfp(open(options.config, 'r')) + + if not config.has_section('openlp'): + logging.error('[%s] config file "%s" lacks an [openlp] section', + script_name, options.config) + sys.exit(1) + + if not sys.platform == "darwin": + logging.error('[%s] this script only works on Macintosh OS X systems,' + + 'not on %s', script_name, sys.platform) + sys.exit(1) + + version = platform.mac_ver()[0] + # we only need the differenciation between leopard and snow leopard + if version.startswith("10.6"): + SNOWLEOPARD = True + logging.info('[%s] using snow leopard scripts (version = %s)', + script_name, version) + adjustview_scriptname = "applescript-adjustview-10-6.master" + seticon_scriptname = "applescript-seticon-10-6.master" + else: + SNOWLEOPARD = False + logging.info('[%s] using leopard scripts (version = %s)', script_name, + version) + adjustview_scriptname = "applescript-adjustview-10-5.master" + seticon_scriptname = "applescript-seticon-10-5.master" + + if not os.path.isfile(adjustview_scriptname) \ + or not os.path.isfile(seticon_scriptname): + logging.error('[%s] could not find apple scripts for given OS X ' + + 'version %s', script_name, version) + sys.exit(1) + + settings = dict() + for k in config.options('openlp'): + settings[k] = config.get('openlp', k) + + # prepare the configuration files + os.system('python expander.py --config %(config_file)s \ + --template openlp.spec.master \ + --expandto %(target_directory)s/openlp.spec' \ + % { 'config_file' : options.config, 'target_directory' : os.getcwd() }) + os.system('python expander.py --config %(config_file)s \ + --template Info.plist.master \ + --expandto %(target_directory)s/Info.plist' \ + % { 'config_file' : options.config, 'target_directory' : os.getcwd() }) + os.system('python expander.py --config %(config_file)s \ + --template version.master \ + --expandto %(target_directory)s/.version' \ + % { 'config_file' : options.config, 'target_directory' : os.getcwd() }) + + # prepare variables + app_name_lower = settings['openlp_appname'].lower() + app_dir = os.getcwd() + '/' + settings['openlp_appname'] + '.app' + + # if the view option is set, skip the building steps + if (do_build is True): + build_application(settings, app_name_lower, app_dir) + + if (do_deploy_qt is True): + deploy_qt(settings) + + if (do_create_dmg is True): + (volume_basedir, dmg_file) = create_dmg(settings) + else: + # setting base dir + volume_basedir = options.basedir + dmg_file = os.getcwd() + '/' + settings['openlp_dmgname'] + '.dmg' + + if (do_package_view is True): + adjust_package_view(settings, adjustview_scriptname) + + if (do_create_dmg is True): + unmount_dmg(settings, volume_basedir) + + if (do_compress_dmg is True): + compress_dmg(settings) + + if (do_compress_view is True): + compress_view(settings, seticon_scriptname, dmg_file) + + if (do_compress_dmg is True): + logging.info('[%s] finished creating dmg file, resulting file is "%s"', + script_name, dmg_file) + diff --git a/resources/osx/expander.py b/resources/osx/expander.py new file mode 100755 index 000000000..77b0d1441 --- /dev/null +++ b/resources/osx/expander.py @@ -0,0 +1,202 @@ +#!/usr/bin/python +# -*- encoding: utf-8 -*- + +# TODOs: +# - defaults for non-supplied expansions: +# template contains + +import ConfigParser +import logging +import optparse +import os +import re +import sys + +# variable expansion: +# - %(dog)s --- normal python expansion +# - %(dog%)s --- no python expansion, leave as is (stripping the trailing %) +# - %(dog:cat) --- if there is an expansion for dog, dog will be used; +# otherwise if cat exists cat will be used +# - %(dog=cat) --- if there is an expansion for dog, dog will be used; +# otherwise "cat" will be used +# re_conf = re.compile(r'(?[^\(]+?)\)s') +re_conf = re.compile(r'(?P%?)%\((?P[^+=:&\)]+?)' + + '(?:(?P[+=:&])(?P[^\)]+))?\)(?Ps|d)') + +def expand_variable(match, expansions, errors): + key = match.group('key') + kind = match.group('kind') + default = match.group('default') + typ = match.group('type') + verbatim = match.group('verbatim') + + if verbatim: + return match.group(0)[1:] + + # literal default + if kind == '=': + if key in expansions: + return expansions[key] + return default + + # variable default + if kind == ':' and default in expansions: + return expansions[default] + + if kind == '+' and default in expansions: + if key in expansions: + key = expansions[key] + if typ == 's': + return '%s%s' % (key, expansions[default]) + if typ == 'd': + try: + return str(int(key) + int(expansions[default])) + except: + pass + + if kind == '&' and default in expansions: + if typ == 's': + return '%s%s' % (key, expansions[default]) + if typ == 'd': + try: + return str(int(key) + int(expansions[default])) + except: + pass + + if key in expansions: + return expansions[key] + + if not match.group(0) in errors: + errors.append(match.group(0)) + + return None + +options = None + +if __name__ == '__main__': + + # get config file + parser = optparse.OptionParser() + parser.add_option('-c', '--config', dest='config', + help='config file', metavar='CONFIG') + parser.add_option('-t', '--template', dest='template', + help='template file', metavar='TEMPLATE') + parser.add_option('-x', '--expandto', dest='expanded', + help='expanded file', metavar='EXPANDED') + parser.add_option('-e', '--echo', dest='echo', + help='echo variable', metavar='ECHOVAR') + + (options, args) = parser.parse_args() + + if not options.config: + parser.error('option --config|-c is required') + if not os.path.exists(options.config): + parser.error('config file "%s" does not exist' % options.config) + if not options.echo: + if not options.template: + parser.error('option --template|-t is required') + if not os.path.exists(options.template): + parser.error('template file "%s" does not exist' \ + % options.template) + if not options.expanded: + parser.error('option --expandto|-e is required') + + logHandler = logging.StreamHandler() + logHandler.setFormatter(logging.Formatter('%(asctime)s %(levelname)-8s ' + + ' %(message)s', '%a, %d %b %Y %H:%M:%S')) + logging.getLogger().addHandler(logHandler) + logging.getLogger().setLevel(logging.DEBUG) + + config = ConfigParser.RawConfigParser() + config.readfp(open(options.config, 'r')) + + if not config.has_section('openlp'): + logging.error('[expander] %s: config file "%s" lacks an [openlp] ' + + 'section', options.template, options.config) + + expansions = dict() + for k in config.options('openlp'): + expansions[k] = config.get('openlp', k) + + # commandline overrides? + for override in args: + if not '=' in override: + continue + + (k, v) = override.split('=', 2) + expansions[k] = v + + if options.echo: + if options.echo in expansions: + print expansions[options.echo] + sys.exit(0) + else: + sys.exit(1) + + # closure to capture expansions and errors variable + errors = [] + expanded = [] + + try: + # try to expand the template + line = 0 + faulty = False + + template = open(options.template, 'r') + raw = template.readlines() + template.close() + + def _expand(m): + return expand_variable(m, expansions = expansions, errors = errors) + + for l in raw: + line += 1 + exp = re_conf.sub(_expand, l) + if errors: + for key in errors: + logging.error('[expander] %s: line %d: could not expand ' + + 'key "%s"', options.template, line, key) + faulty = True + errors = [] + else: + expanded.append(exp) + + if faulty: + sys.exit(1) + + # successfully expanded template, now backup potentially existing + # target file + targetFile = options.expanded % expansions + if os.path.exists(targetFile): + if os.path.exists('%s~' % targetFile): + os.unlink('%s~' % targetFile) + os.rename(options.expanded, '%s~' % targetFile) + logging.info('[expander] %s: backed up existing target file "%s" ' + + 'to "%s"', options.template, targetFile, + '%s~' % options.expanded) + + # make sure that target directory exists + targetDir = os.path.dirname(targetFile) + if not os.path.exists(targetDir): + os.makedirs(targetDir) + + # write target file + try: + target = open(targetFile, 'w') + for exp in expanded: + target.write(exp) + target.close() + except Exception, e: + logging.error('[expander] %s: could not expand to "%s"', + options.template, options.expaned, e) + + # copy over file access mode from template + mode = os.stat(options.template) + os.chmod(options.expanded, mode.st_mode) + + logging.info('[expander] expanded "%s" to "%s"', + options.template, options.expanded) + + except: + pass + diff --git a/resources/osx/installation-background.png b/resources/osx/installation-background.png new file mode 100755 index 000000000..8a41d3b68 Binary files /dev/null and b/resources/osx/installation-background.png differ diff --git a/resources/osx/openlp-logo-420x420-background.png b/resources/osx/openlp-logo-420x420-background.png new file mode 100755 index 000000000..1008621d6 Binary files /dev/null and b/resources/osx/openlp-logo-420x420-background.png differ diff --git a/resources/osx/openlp-logo-420x420.png b/resources/osx/openlp-logo-420x420.png new file mode 100644 index 000000000..85457d8fb Binary files /dev/null and b/resources/osx/openlp-logo-420x420.png differ diff --git a/resources/osx/openlp-logo-with-text.icns b/resources/osx/openlp-logo-with-text.icns new file mode 100755 index 000000000..6066b111e Binary files /dev/null and b/resources/osx/openlp-logo-with-text.icns differ diff --git a/resources/osx/openlp-splash-screen.png b/resources/osx/openlp-splash-screen.png new file mode 100755 index 000000000..618e47c6e Binary files /dev/null and b/resources/osx/openlp-splash-screen.png differ diff --git a/resources/osx/openlp.cfg b/resources/osx/openlp.cfg new file mode 100755 index 000000000..1f04861b5 --- /dev/null +++ b/resources/osx/openlp.cfg @@ -0,0 +1,11 @@ +[openlp] +openlp_appname = OpenLP +openlp_dmgname = OpenLP-1.9.4-bzrXXXX +openlp_version = XXXX +openlp_full_version = 1.9.4-latest +openlp_basedir = /Users/openlp/trunk +openlp_icon_file = openlp-logo-with-text.icns +openlp_dmg_icon_file = openlp-logo-420x420.png +installer_backgroundimage_file = installation-background.png +pyinstaller_basedir = /Users/openlp/pyinstaller/trunk +qt_menu_basedir = /Library/Frameworks/QtGui.framework/Versions/4/Resources/qt_menu.nib diff --git a/resources/osx/openlp.spec.master b/resources/osx/openlp.spec.master new file mode 100755 index 000000000..94aec2a60 --- /dev/null +++ b/resources/osx/openlp.spec.master @@ -0,0 +1,24 @@ +# -*- mode: python -*- +a = Analysis([os.path.join(HOMEPATH,'support/_mountzlib.py'), os.path.join(HOMEPATH,'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, + a.scripts, + exclude_binaries=1, + name=os.path.join('build/pyi.darwin/openlp', 'openlp'), + debug=False, + strip=False, + upx=True, + console=1 ) +coll = COLLECT( exe, + a.binaries, + a.zipfiles, + a.datas, + strip=False, + upx=True, + name=os.path.join('dist', 'openlp')) +import sys +if sys.platform.startswith("darwin"): + app = BUNDLE(coll, + name='%(openlp_appname)s.app', + version='%(openlp_version)s') diff --git a/resources/osx/version.master b/resources/osx/version.master new file mode 100755 index 000000000..d517a51bc --- /dev/null +++ b/resources/osx/version.master @@ -0,0 +1 @@ +%(openlp_full_version)s