Merge lp:~suutari-olli/openlp/fix-openlp-importer into lp:openlp/2.4

Proposed by Azaziah
Status: Superseded
Proposed branch: lp:~suutari-olli/openlp/fix-openlp-importer
Merge into: lp:openlp/2.4
Diff against target: 114285 lines (+91012/-6647) (has conflicts)
394 files modified
.bzrignore (+6/-1)
.coveragerc (+0/-5)
nose2.cfg (+22/-0)
openlp/.version (+4/-0)
openlp/core/__init__.py (+61/-23)
openlp/core/common/__init__.py (+218/-17)
openlp/core/common/actions.py (+390/-0)
openlp/core/common/db.py (+72/-0)
openlp/core/common/httputils.py (+255/-0)
openlp/core/common/languagemanager.py (+207/-0)
openlp/core/common/languages.py (+201/-0)
openlp/core/common/openlpmixin.py (+9/-2)
openlp/core/common/registry.py (+39/-8)
openlp/core/common/settings.py (+25/-13)
openlp/core/common/uistrings.py (+40/-5)
openlp/core/common/versionchecker.py (+173/-0)
openlp/core/lib/__init__.py (+54/-43)
openlp/core/lib/db.py (+40/-37)
openlp/core/lib/exceptions.py (+7/-1)
openlp/core/lib/filedialog.py (+2/-2)
openlp/core/lib/htmlbuilder.py (+249/-235)
openlp/core/lib/imagemanager.py (+4/-4)
openlp/core/lib/mediamanageritem.py (+24/-31)
openlp/core/lib/plugin.py (+6/-7)
openlp/core/lib/pluginmanager.py (+14/-12)
openlp/core/lib/projector/constants.py (+233/-153)
openlp/core/lib/projector/db.py (+63/-45)
openlp/core/lib/projector/pjlink1.py (+101/-33)
openlp/core/lib/renderer.py (+19/-13)
openlp/core/lib/screen.py (+9/-7)
openlp/core/lib/searchedit.py (+11/-15)
openlp/core/lib/serviceitem.py (+15/-10)
openlp/core/lib/settingstab.py (+1/-1)
openlp/core/lib/theme.py (+32/-7)
openlp/core/lib/ui.py (+4/-4)
openlp/core/resources.py (+50027/-2)
openlp/core/ui/__init__.py (+1/-3)
openlp/core/ui/aboutdialog.py (+116/-97)
openlp/core/ui/aboutform.py (+2/-2)
openlp/core/ui/advancedtab.py (+84/-103)
openlp/core/ui/exceptiondialog.py (+19/-9)
openlp/core/ui/exceptionform.py (+49/-34)
openlp/core/ui/filerenamedialog.py (+1/-1)
openlp/core/ui/firsttimeform.py (+59/-115)
openlp/core/ui/firsttimelanguagedialog.py (+1/-1)
openlp/core/ui/firsttimelanguageform.py (+1/-1)
openlp/core/ui/firsttimewizard.py (+52/-22)
openlp/core/ui/formattingtagcontroller.py (+13/-10)
openlp/core/ui/formattingtagdialog.py (+1/-1)
openlp/core/ui/formattingtagform.py (+4/-3)
openlp/core/ui/generaltab.py (+88/-6)
openlp/core/ui/lib/__init__.py (+34/-0)
openlp/core/ui/lib/colorbutton.py (+82/-0)
openlp/core/ui/lib/dockwidget.py (+56/-0)
openlp/core/ui/lib/historycombobox.py (+84/-0)
openlp/core/ui/lib/listpreviewwidget.py (+239/-0)
openlp/core/ui/lib/listwidgetwithdnd.py (+139/-0)
openlp/core/ui/lib/mediadockmanager.py (+71/-0)
openlp/core/ui/lib/spelltextedit.py (+204/-0)
openlp/core/ui/lib/toolbar.py (+90/-0)
openlp/core/ui/lib/treewidgetwithdnd.py (+144/-0)
openlp/core/ui/lib/wizard.py (+311/-0)
openlp/core/ui/maindisplay.py (+44/-27)
openlp/core/ui/mainwindow.py (+152/-124)
openlp/core/ui/media/__init__.py (+16/-10)
openlp/core/ui/media/mediacontroller.py (+198/-108)
openlp/core/ui/media/mediaplayer.py (+69/-2)
openlp/core/ui/media/playertab.py (+10/-3)
openlp/core/ui/media/systemplayer.py (+76/-24)
openlp/core/ui/media/vendor/mediainfoWrapper.py (+140/-0)
openlp/core/ui/media/vlcplayer.py (+57/-21)
openlp/core/ui/media/webkitplayer.py (+62/-145)
openlp/core/ui/plugindialog.py (+6/-14)
openlp/core/ui/pluginform.py (+28/-28)
openlp/core/ui/printservicedialog.py (+3/-2)
openlp/core/ui/projector/editform.py (+35/-36)
openlp/core/ui/projector/manager.py (+128/-90)
openlp/core/ui/projector/sourceselectform.py (+65/-2)
openlp/core/ui/projector/tab.py (+1/-1)
openlp/core/ui/serviceitemeditdialog.py (+1/-1)
openlp/core/ui/servicemanager.py (+69/-37)
openlp/core/ui/servicenoteform.py (+1/-1)
openlp/core/ui/settingsdialog.py (+1/-1)
openlp/core/ui/settingsform.py (+5/-2)
openlp/core/ui/shortcutlistdialog.py (+1/-1)
openlp/core/ui/shortcutlistform.py (+6/-5)
openlp/core/ui/slidecontroller.py (+143/-80)
openlp/core/ui/starttimedialog.py (+1/-1)
openlp/core/ui/starttimeform.py (+3/-3)
openlp/core/ui/themeform.py (+52/-10)
openlp/core/ui/themelayoutdialog.py (+1/-1)
openlp/core/ui/thememanager.py (+38/-30)
openlp/core/ui/themewizard.py (+41/-15)
openlp/plugins/alerts/alertsplugin.py (+10/-7)
openlp/plugins/alerts/forms/alertdialog.py (+1/-1)
openlp/plugins/alerts/lib/alertsmanager.py (+1/-1)
openlp/plugins/alerts/lib/alertstab.py (+7/-5)
openlp/plugins/bibles/bibleplugin.py (+10/-49)
openlp/plugins/bibles/forms/__init__.py (+3/-21)
openlp/plugins/bibles/forms/bibleimportform.py (+261/-23)
openlp/plugins/bibles/forms/booknamedialog.py (+1/-1)
openlp/plugins/bibles/forms/editbibledialog.py (+7/-4)
openlp/plugins/bibles/forms/editbibleform.py (+8/-7)
openlp/plugins/bibles/forms/languagedialog.py (+1/-1)
openlp/plugins/bibles/forms/languageform.py (+5/-5)
openlp/plugins/bibles/lib/__init__.py (+10/-8)
openlp/plugins/bibles/lib/bibleimport.py (+234/-0)
openlp/plugins/bibles/lib/biblestab.py (+63/-5)
openlp/plugins/bibles/lib/db.py (+37/-263)
openlp/plugins/bibles/lib/importers/csvbible.py (+102/-103)
openlp/plugins/bibles/lib/importers/http.py (+62/-40)
openlp/plugins/bibles/lib/importers/opensong.py (+119/-121)
openlp/plugins/bibles/lib/importers/osis.py (+141/-165)
openlp/plugins/bibles/lib/importers/sword.py (+102/-0)
openlp/plugins/bibles/lib/importers/wordproject.py (+169/-0)
openlp/plugins/bibles/lib/importers/zefania.py (+29/-42)
openlp/plugins/bibles/lib/manager.py (+108/-80)
openlp/plugins/bibles/lib/mediaitem.py (+354/-79)
openlp/plugins/bibles/lib/upgrade.py (+2/-164)
openlp/plugins/bibles/lib/versereferencelist.py (+14/-8)
openlp/plugins/custom/forms/editcustomdialog.py (+1/-1)
openlp/plugins/custom/forms/editcustomslidedialog.py (+3/-2)
openlp/plugins/custom/lib/customxmlhandler.py (+1/-1)
openlp/plugins/custom/lib/db.py (+1/-1)
openlp/plugins/custom/lib/mediaitem.py (+5/-9)
openlp/plugins/images/imageplugin.py (+1/-1)
openlp/plugins/images/lib/imagetab.py (+3/-2)
openlp/plugins/images/lib/mediaitem.py (+26/-23)
openlp/plugins/media/forms/mediaclipselectorform.py (+21/-12)
openlp/plugins/media/lib/mediaitem.py (+48/-40)
openlp/plugins/media/lib/mediatab.py (+1/-1)
openlp/plugins/media/mediaplugin.py (+35/-9)
openlp/plugins/presentations/lib/impresscontroller.py (+6/-6)
openlp/plugins/presentations/lib/mediaitem.py (+13/-10)
openlp/plugins/presentations/lib/messagelistener.py (+23/-19)
openlp/plugins/presentations/lib/pdfcontroller.py (+50/-32)
openlp/plugins/presentations/lib/powerpointcontroller.py (+19/-10)
openlp/plugins/presentations/lib/pptviewcontroller.py (+4/-5)
openlp/plugins/presentations/lib/presentationcontroller.py (+6/-5)
openlp/plugins/presentations/lib/presentationtab.py (+7/-6)
openlp/plugins/presentations/presentationplugin.py (+3/-3)
openlp/plugins/remotes/html/assets/jquery.js (+9404/-0)
openlp/plugins/remotes/html/assets/jquery.min.js (+4/-0)
openlp/plugins/remotes/html/assets/jquery.mobile.js (+9357/-0)
openlp/plugins/remotes/html/assets/jquery.mobile.min.css (+2/-0)
openlp/plugins/remotes/html/assets/jquery.mobile.min.js (+2/-0)
openlp/plugins/remotes/html/css/main.css (+32/-0)
openlp/plugins/remotes/html/css/openlp.css (+31/-0)
openlp/plugins/remotes/html/css/stage.css (+64/-0)
openlp/plugins/remotes/html/index.html (+17/-0)
openlp/plugins/remotes/html/js/main.js (+45/-0)
openlp/plugins/remotes/html/js/openlp.js (+384/-0)
openlp/plugins/remotes/html/js/stage.js (+170/-0)
openlp/plugins/remotes/html/main.html (+4/-4)
openlp/plugins/remotes/html/stage.html (+4/-4)
openlp/plugins/remotes/lib/httprouter.py (+33/-24)
openlp/plugins/remotes/lib/httpserver.py (+5/-3)
openlp/plugins/remotes/lib/remotetab.py (+20/-8)
openlp/plugins/songs/forms/authorsdialog.py (+1/-1)
openlp/plugins/songs/forms/duplicatesongremovalform.py (+4/-3)
openlp/plugins/songs/forms/editsongdialog.py (+1/-1)
openlp/plugins/songs/forms/editsongform.py (+51/-31)
openlp/plugins/songs/forms/editversedialog.py (+3/-2)
openlp/plugins/songs/forms/editverseform.py (+5/-5)
openlp/plugins/songs/forms/mediafilesdialog.py (+1/-1)
openlp/plugins/songs/forms/mediafilesform.py (+1/-1)
openlp/plugins/songs/forms/songbookdialog.py (+1/-1)
openlp/plugins/songs/forms/songexportform.py (+11/-6)
openlp/plugins/songs/forms/songimportform.py (+7/-3)
openlp/plugins/songs/forms/songmaintenancedialog.py (+1/-1)
openlp/plugins/songs/forms/songmaintenanceform.py (+35/-15)
openlp/plugins/songs/forms/songreviewwidget.py (+1/-1)
openlp/plugins/songs/forms/songselectdialog.py (+3/-2)
openlp/plugins/songs/forms/songselectform.py (+6/-2)
openlp/plugins/songs/forms/topicsdialog.py (+1/-1)
openlp/plugins/songs/lib/__init__.py (+14/-9)
openlp/plugins/songs/lib/db.py (+6/-4)
openlp/plugins/songs/lib/importer.py (+80/-44)
openlp/plugins/songs/lib/importers/cclifile.py (+8/-7)
openlp/plugins/songs/lib/importers/dreambeam.py (+6/-4)
openlp/plugins/songs/lib/importers/easyslides.py (+12/-10)
openlp/plugins/songs/lib/importers/easyworship.py (+45/-7)
openlp/plugins/songs/lib/importers/foilpresenter.py (+3/-2)
openlp/plugins/songs/lib/importers/lyrix.py (+2/-2)
openlp/plugins/songs/lib/importers/mediashout.py (+23/-8)
openlp/plugins/songs/lib/importers/openlp.py (+38/-1)
openlp/plugins/songs/lib/importers/openlyrics.py (+6/-4)
openlp/plugins/songs/lib/importers/openoffice.py (+2/-3)
openlp/plugins/songs/lib/importers/opensong.py (+5/-5)
openlp/plugins/songs/lib/importers/opspro.py (+265/-0)
openlp/plugins/songs/lib/importers/powerpraise.py (+3/-2)
openlp/plugins/songs/lib/importers/powersong.py (+12/-11)
openlp/plugins/songs/lib/importers/presentationmanager.py (+5/-2)
openlp/plugins/songs/lib/importers/propresenter.py (+77/-17)
openlp/plugins/songs/lib/importers/songimport.py (+6/-5)
openlp/plugins/songs/lib/importers/songshowplus.py (+13/-9)
openlp/plugins/songs/lib/importers/sundayplus.py (+1/-1)
openlp/plugins/songs/lib/importers/videopsalm.py (+5/-1)
openlp/plugins/songs/lib/importers/wordsofworship.py (+6/-6)
openlp/plugins/songs/lib/importers/worshipassistant.py (+10/-7)
openlp/plugins/songs/lib/importers/worshipcenterpro.py (+4/-2)
openlp/plugins/songs/lib/importers/zionworx.py (+7/-6)
openlp/plugins/songs/lib/mediaitem.py (+203/-40)
openlp/plugins/songs/lib/openlyricsexport.py (+7/-6)
openlp/plugins/songs/lib/openlyricsxml.py (+18/-11)
openlp/plugins/songs/lib/songcompare.py (+3/-3)
openlp/plugins/songs/lib/songselect.py (+54/-3)
openlp/plugins/songs/lib/songstab.py (+4/-4)
openlp/plugins/songs/lib/upgrade.py (+1/-1)
openlp/plugins/songs/reporting.py (+106/-0)
openlp/plugins/songs/songsplugin.py (+30/-12)
openlp/plugins/songusage/forms/songusagedeletedialog.py (+1/-1)
openlp/plugins/songusage/forms/songusagedetaildialog.py (+1/-1)
openlp/plugins/songusage/forms/songusagedetailform.py (+12/-9)
openlp/plugins/songusage/songusageplugin.py (+1/-1)
pylintrc (+379/-0)
resources/images/openlp-2.qrc (+14/-31)
resources/images/openlp-about-logo.svg (+0/-323)
resources/images/openlp-default-dualdisplay.svg (+0/-489)
resources/images/openlp-logo.svg (+98/-347)
resources/images/openlp-splash-screen.svg (+0/-296)
scripts/appveyor-webhook.py (+137/-0)
scripts/appveyor.yml (+84/-0)
scripts/check_dependencies.py (+2/-0)
scripts/clean_up_resources.py (+99/-0)
scripts/jenkins_script.py (+2/-1)
scripts/translation_utils.py (+5/-1)
setup.py (+2/-2)
tests/functional/openlp_core/test_init.py (+12/-24)
tests/functional/openlp_core_common/test_actions.py (+259/-0)
tests/functional/openlp_core_common/test_applocation.py (+10/-10)
tests/functional/openlp_core_common/test_common.py (+9/-9)
tests/functional/openlp_core_common/test_db.py (+104/-0)
tests/functional/openlp_core_common/test_httputils.py (+274/-0)
tests/functional/openlp_core_common/test_init.py (+403/-0)
tests/functional/openlp_core_common/test_languagemanager.py (+66/-0)
tests/functional/openlp_core_common/test_languages.py (+109/-0)
tests/functional/openlp_core_common/test_projector_utilities.py (+9/-5)
tests/functional/openlp_core_common/test_registry.py (+41/-4)
tests/functional/openlp_core_common/test_registrymixin.py (+2/-2)
tests/functional/openlp_core_common/test_registryproperties.py (+74/-29)
tests/functional/openlp_core_common/test_settings.py (+6/-6)
tests/functional/openlp_core_common/test_uistrings.py (+1/-1)
tests/functional/openlp_core_common/test_versionchecker.py (+63/-0)
tests/functional/openlp_core_lib/test_db.py (+5/-5)
tests/functional/openlp_core_lib/test_file_dialog.py (+3/-3)
tests/functional/openlp_core_lib/test_formattingtags.py (+2/-2)
tests/functional/openlp_core_lib/test_htmlbuilder.py (+212/-174)
tests/functional/openlp_core_lib/test_image_manager.py (+3/-3)
tests/functional/openlp_core_lib/test_lib.py (+280/-83)
tests/functional/openlp_core_lib/test_mediamanageritem.py (+4/-4)
tests/functional/openlp_core_lib/test_pluginmanager.py (+22/-22)
tests/functional/openlp_core_lib/test_projector_pjlink1.py (+317/-0)
tests/functional/openlp_core_lib/test_projectordb.py (+115/-10)
tests/functional/openlp_core_lib/test_renderer.py (+50/-6)
tests/functional/openlp_core_lib/test_screen.py (+1/-1)
tests/functional/openlp_core_lib/test_serviceitem.py (+13/-12)
tests/functional/openlp_core_lib/test_theme.py (+72/-33)
tests/functional/openlp_core_lib/test_ui.py (+13/-13)
tests/functional/openlp_core_ui/test_aboutform.py (+36/-0)
tests/functional/openlp_core_ui/test_exceptionform.py (+207/-0)
tests/functional/openlp_core_ui/test_first_time.py (+57/-0)
tests/functional/openlp_core_ui/test_firsttimeform.py (+10/-24)
tests/functional/openlp_core_ui/test_formattingtagscontroller.py (+4/-4)
tests/functional/openlp_core_ui/test_formattingtagsform.py (+2/-2)
tests/functional/openlp_core_ui/test_maindisplay.py (+109/-8)
tests/functional/openlp_core_ui/test_mainwindow.py (+72/-16)
tests/functional/openlp_core_ui/test_servicemanager.py (+250/-9)
tests/functional/openlp_core_ui/test_settingsform.py (+11/-11)
tests/functional/openlp_core_ui/test_shortcutlistdialog.py (+60/-0)
tests/functional/openlp_core_ui/test_slidecontroller.py (+280/-60)
tests/functional/openlp_core_ui/test_themeform.py (+2/-2)
tests/functional/openlp_core_ui/test_thememanager.py (+9/-9)
tests/functional/openlp_core_ui/test_themetab.py (+84/-0)
tests/functional/openlp_core_ui_lib/test_color_button.py (+199/-0)
tests/functional/openlp_core_ui_lib/test_listpreviewwidget.py (+503/-0)
tests/functional/openlp_core_ui_lib/test_listwidgetwithdnd.py (+104/-0)
tests/functional/openlp_core_ui_media/test_mediacontroller.py (+22/-21)
tests/functional/openlp_core_ui_media/test_systemplayer.py (+547/-0)
tests/functional/openlp_core_ui_media/test_vlcplayer.py (+46/-53)
tests/functional/openlp_core_ui_media/test_webkitplayer.py (+2/-2)
tests/functional/openlp_plugins/alerts/test_manager.py (+3/-3)
tests/functional/openlp_plugins/bibles/test_bibleimport.py (+615/-0)
tests/functional/openlp_plugins/bibles/test_bibleserver.py (+12/-12)
tests/functional/openlp_plugins/bibles/test_csvimport.py (+273/-18)
tests/functional/openlp_plugins/bibles/test_db.py (+33/-0)
tests/functional/openlp_plugins/bibles/test_lib.py (+22/-18)
tests/functional/openlp_plugins/bibles/test_manager.py (+69/-0)
tests/functional/openlp_plugins/bibles/test_mediaitem.py (+118/-3)
tests/functional/openlp_plugins/bibles/test_opensongimport.py (+348/-36)
tests/functional/openlp_plugins/bibles/test_osisimport.py (+376/-23)
tests/functional/openlp_plugins/bibles/test_swordimport.py (+110/-0)
tests/functional/openlp_plugins/bibles/test_versereferencelist.py (+5/-5)
tests/functional/openlp_plugins/bibles/test_wordprojectimport.py (+220/-0)
tests/functional/openlp_plugins/bibles/test_zefaniaimport.py (+11/-13)
tests/functional/openlp_plugins/custom/test_mediaitem.py (+3/-3)
tests/functional/openlp_plugins/images/test_imagetab.py (+2/-2)
tests/functional/openlp_plugins/images/test_lib.py (+11/-11)
tests/functional/openlp_plugins/media/test_mediaplugin.py (+28/-4)
tests/functional/openlp_plugins/presentations/test_impresscontroller.py (+5/-5)
tests/functional/openlp_plugins/presentations/test_mediaitem.py (+3/-3)
tests/functional/openlp_plugins/presentations/test_messagelistener.py (+4/-4)
tests/functional/openlp_plugins/presentations/test_pdfcontroller.py (+77/-3)
tests/functional/openlp_plugins/presentations/test_powerpointcontroller.py (+10/-10)
tests/functional/openlp_plugins/presentations/test_pptviewcontroller.py (+7/-7)
tests/functional/openlp_plugins/presentations/test_presentationcontroller.py (+10/-10)
tests/functional/openlp_plugins/remotes/test_remotetab.py (+6/-6)
tests/functional/openlp_plugins/remotes/test_router.py (+39/-14)
tests/functional/openlp_plugins/songs/test_editsongform.py (+36/-2)
tests/functional/openlp_plugins/songs/test_editverseform.py (+1/-1)
tests/functional/openlp_plugins/songs/test_ewimport.py (+15/-15)
tests/functional/openlp_plugins/songs/test_foilpresenterimport.py (+5/-5)
tests/functional/openlp_plugins/songs/test_lib.py (+55/-28)
tests/functional/openlp_plugins/songs/test_mediaitem.py (+244/-9)
tests/functional/openlp_plugins/songs/test_mediashout.py (+228/-0)
tests/functional/openlp_plugins/songs/test_openlpimporter.py (+75/-0)
tests/functional/openlp_plugins/songs/test_openlyricsexport.py (+1/-1)
tests/functional/openlp_plugins/songs/test_openlyricsimport.py (+5/-5)
tests/functional/openlp_plugins/songs/test_openoffice.py (+2/-2)
tests/functional/openlp_plugins/songs/test_opensongimport.py (+3/-3)
tests/functional/openlp_plugins/songs/test_opsproimport.py (+173/-0)
tests/functional/openlp_plugins/songs/test_presentationmanagerimport.py (+0/-2)
tests/functional/openlp_plugins/songs/test_propresenterimport.py (+16/-4)
tests/functional/openlp_plugins/songs/test_songbeamerimport.py (+4/-4)
tests/functional/openlp_plugins/songs/test_songselect.py (+78/-32)
tests/functional/openlp_plugins/songs/test_songshowplusimport.py (+5/-5)
tests/functional/openlp_plugins/songs/test_sundayplusimport.py (+0/-2)
tests/functional/openlp_plugins/songs/test_videopsalm.py (+0/-3)
tests/functional/openlp_plugins/songs/test_worshipassistantimport.py (+0/-2)
tests/functional/openlp_plugins/songs/test_worshipcenterproimport.py (+34/-31)
tests/functional/openlp_plugins/songs/test_zionworximport.py (+1/-1)
tests/functional/openlp_plugins/songusage/test_songusage.py (+19/-3)
tests/functional/test_init.py (+4/-4)
tests/interfaces/openlp_core_common/test_utils.py (+81/-0)
tests/interfaces/openlp_core_lib/test_pluginmanager.py (+10/-0)
tests/interfaces/openlp_core_lib/test_searchedit.py (+23/-12)
tests/interfaces/openlp_core_ui/test_filerenamedialog.py (+3/-3)
tests/interfaces/openlp_core_ui/test_mainwindow.py (+3/-3)
tests/interfaces/openlp_core_ui/test_projectoreditform.py (+25/-27)
tests/interfaces/openlp_core_ui/test_projectormanager.py (+2/-2)
tests/interfaces/openlp_core_ui/test_projectorsourceform.py (+3/-3)
tests/interfaces/openlp_core_ui/test_servicemanager.py (+8/-8)
tests/interfaces/openlp_core_ui/test_servicenotedialog.py (+1/-1)
tests/interfaces/openlp_core_ui/test_settings_form.py (+7/-7)
tests/interfaces/openlp_core_ui/test_shortcutlistform.py (+10/-10)
tests/interfaces/openlp_core_ui/test_starttimedialog.py (+2/-2)
tests/interfaces/openlp_core_ui/test_thememanager.py (+4/-4)
tests/interfaces/openlp_core_ui_lib/__init__.py (+21/-0)
tests/interfaces/openlp_core_ui_lib/test_historycombobox.py (+64/-0)
tests/interfaces/openlp_core_ui_lib/test_listpreviewwidget.py (+106/-0)
tests/interfaces/openlp_core_ul_media_vendor/__init__.py (+21/-0)
tests/interfaces/openlp_core_ul_media_vendor/test_mediainfoWrapper.py (+51/-0)
tests/interfaces/openlp_plugins/bibles/forms/test_bibleimportform.py (+17/-3)
tests/interfaces/openlp_plugins/bibles/test_lib_http.py (+27/-11)
tests/interfaces/openlp_plugins/bibles/test_lib_manager.py (+4/-4)
tests/interfaces/openlp_plugins/bibles/test_lib_parse_reference.py (+5/-5)
tests/interfaces/openlp_plugins/custom/forms/test_customform.py (+24/-5)
tests/interfaces/openlp_plugins/custom/forms/test_customslideform.py (+2/-2)
tests/interfaces/openlp_plugins/media/forms/test_mediaclipselectorform.py (+4/-4)
tests/interfaces/openlp_plugins/songs/forms/test_authorsform.py (+7/-7)
tests/interfaces/openlp_plugins/songs/forms/test_editsongform.py (+7/-7)
tests/interfaces/openlp_plugins/songs/forms/test_editverseform.py (+5/-5)
tests/interfaces/openlp_plugins/songs/forms/test_topicsform.py (+3/-3)
tests/resources/bibles/dk1933.json (+10/-10)
tests/resources/bibles/kjv.json (+10/-10)
tests/resources/bibles/rst.json (+10/-10)
tests/resources/bibles/web.json (+10/-10)
tests/resources/bibles/wordproject_chapter.htm (+248/-0)
tests/resources/bibles/wordproject_index.htm (+222/-0)
tests/resources/opensongsongs/Amazing Grace with bad CCLI (+56/-0)
tests/resources/opensongsongs/Amazing Grace without CCLI.json (+42/-0)
tests/resources/opsprosongs/Amazing Grace.json (+21/-0)
tests/resources/opsprosongs/Amazing Grace3.json (+31/-0)
tests/resources/opsprosongs/You are so faithful.json (+31/-0)
tests/resources/opsprosongs/amazing grace.txt (+24/-0)
tests/resources/opsprosongs/amazing grace2.txt (+29/-0)
tests/resources/opsprosongs/amazing grace3.txt (+31/-0)
tests/resources/opsprosongs/you are so faithfull.txt (+37/-0)
tests/resources/presentationmanagersongs/Agnus Dei.json (+0/-14)
tests/resources/presentationmanagersongs/Agnus Dei.sng (+0/-34)
tests/resources/propresentersongs/Amazing Grace.pro5 (+520/-0)
tests/resources/propresentersongs/Amazing Grace.pro6 (+490/-0)
tests/resources/propresentersongs/Vaste Grond.json (+0/-34)
tests/resources/propresentersongs/Vaste Grond.pro4 (+0/-1)
tests/resources/songshowplussongs/cleanse-me.json (+38/-0)
tests/resources/sundayplussongs/Abba Fader.ptf (+0/-8)
tests/resources/sundayplussongs/abba-fader.json (+0/-13)
tests/resources/videopsalmsongs/as-safe-a-stronghold2.json (+35/-0)
tests/resources/videopsalmsongs/videopsalm-as-safe-a-stronghold2.json (+47/-0)
tests/resources/worshipassistantsongs/lift_up_your_heads.csv (+0/-40)
tests/resources/worshipassistantsongs/lift_up_your_heads.json (+0/-13)
tests/utils/__init__.py (+1/-1)
tests/utils/test_bzr_tags.py (+1/-1)
tests/utils/test_pylint.py (+113/-0)
Text conflict in .bzrignore
Text conflict in openlp/.version
Text conflict in openlp/core/common/__init__.py
Contents conflict in openlp/core/common/historycombobox.py
Contents conflict in openlp/core/lib/colorbutton.py
Contents conflict in openlp/core/lib/dockwidget.py
Contents conflict in openlp/core/lib/listwidgetwithdnd.py
Text conflict in openlp/core/lib/projector/constants.py
Text conflict in openlp/core/lib/projector/pjlink1.py
Contents conflict in openlp/core/lib/spelltextedit.py
Contents conflict in openlp/core/lib/toolbar.py
Contents conflict in openlp/core/lib/treewidgetwithdnd.py
Text conflict in openlp/core/resources.py
Text conflict in openlp/core/ui/aboutdialog.py
Text conflict in openlp/core/ui/exceptionform.py
Contents conflict in openlp/core/ui/listpreviewwidget.py
Text conflict in openlp/core/ui/mainwindow.py
Text conflict in openlp/core/ui/media/systemplayer.py
Contents conflict in openlp/core/ui/mediadockmanager.py
Text conflict in openlp/core/ui/projector/manager.py
Text conflict in openlp/core/ui/projector/sourceselectform.py
Contents conflict in openlp/core/ui/wizard.py
Conflict: can't delete openlp/core/utils because it is not empty.  Not deleting.
Conflict because openlp/core/utils is not versioned, but has versioned children.  Versioned directory.
Contents conflict in openlp/core/utils/__init__.py
Contents conflict in openlp/core/utils/actions.py
Contents conflict in openlp/core/utils/db.py
Contents conflict in openlp/core/utils/languagemanager.py
Contents conflict in openlp/plugins/bibles/forms/bibleupgradeform.py
Text conflict in openlp/plugins/bibles/lib/importers/http.py
Text conflict in openlp/plugins/bibles/lib/importers/zefania.py
Text conflict in openlp/plugins/bibles/lib/mediaitem.py
Text conflict in openlp/plugins/presentations/lib/pdfcontroller.py
Text conflict in openlp/plugins/remotes/html/index.html
Contents conflict in openlp/plugins/remotes/html/jquery.js
Contents conflict in openlp/plugins/remotes/html/jquery.min.js
Contents conflict in openlp/plugins/remotes/html/jquery.mobile.js
Contents conflict in openlp/plugins/remotes/html/jquery.mobile.min.css
Contents conflict in openlp/plugins/remotes/html/jquery.mobile.min.js
Contents conflict in openlp/plugins/remotes/html/main.css
Contents conflict in openlp/plugins/remotes/html/main.js
Contents conflict in openlp/plugins/remotes/html/openlp.css
Contents conflict in openlp/plugins/remotes/html/openlp.js
Contents conflict in openlp/plugins/remotes/html/stage.css
Contents conflict in openlp/plugins/remotes/html/stage.js
Text conflict in openlp/plugins/remotes/lib/remotetab.py
Text conflict in openlp/plugins/songs/lib/__init__.py
Text conflict in openlp/plugins/songs/lib/importers/easyworship.py
Text conflict in openlp/plugins/songs/lib/importers/mediashout.py
Text conflict in openlp/plugins/songs/lib/importers/openlp.py
Text conflict in openlp/plugins/songs/lib/importers/videopsalm.py
Text conflict in openlp/plugins/songs/lib/mediaitem.py
Text conflict in openlp/plugins/songs/lib/songselect.py
Conflict adding file resources/images/ios_app_qr.png.  Moved existing file to resources/images/ios_app_qr.png.moved.
Text conflict in scripts/translation_utils.py
Text conflict in tests/functional/openlp_core_common/test_projector_utilities.py
Text conflict in tests/functional/openlp_core_common/test_registryproperties.py
Contents conflict in tests/functional/openlp_core_lib/test_color_button.py
Text conflict in tests/functional/openlp_core_lib/test_htmlbuilder.py
Text conflict in tests/functional/openlp_core_lib/test_projector_pjlink1.py
Text conflict in tests/functional/openlp_core_ui/test_aboutform.py
Text conflict in tests/functional/openlp_core_ui/test_firsttimeform.py
Contents conflict in tests/functional/openlp_core_ui/test_listpreviewwidget.py
Text conflict in tests/functional/openlp_core_ui/test_servicemanager.py
Conflict adding file tests/functional/openlp_core_ui/test_shortcutlistdialog.py.  Moved existing file to tests/functional/openlp_core_ui/test_shortcutlistdialog.py.moved.
Conflict adding file tests/functional/openlp_core_ui/test_themetab.py.  Moved existing file to tests/functional/openlp_core_ui/test_themetab.py.moved.
Conflict adding file tests/functional/openlp_core_ui_media/test_systemplayer.py.  Moved existing file to tests/functional/openlp_core_ui_media/test_systemplayer.py.moved.
Conflict: can't delete tests/functional/openlp_core_utils because it is not empty.  Not deleting.
Conflict because tests/functional/openlp_core_utils is not versioned, but has versioned children.  Versioned directory.
Contents conflict in tests/functional/openlp_core_utils/__init__.py
Contents conflict in tests/functional/openlp_core_utils/test_actions.py
Contents conflict in tests/functional/openlp_core_utils/test_db.py
Contents conflict in tests/functional/openlp_core_utils/test_first_time.py
Contents conflict in tests/functional/openlp_core_utils/test_init.py
Contents conflict in tests/functional/openlp_core_utils/test_utils.py
Conflict adding file tests/functional/openlp_plugins/bibles/test_manager.py.  Moved existing file to tests/functional/openlp_plugins/bibles/test_manager.py.moved.
Text conflict in tests/functional/openlp_plugins/presentations/test_pdfcontroller.py
Text conflict in tests/functional/openlp_plugins/songs/test_editsongform.py
Text conflict in tests/functional/openlp_plugins/songs/test_lib.py
Conflict adding file tests/functional/openlp_plugins/songs/test_openlpimporter.py.  Moved existing file to tests/functional/openlp_plugins/songs/test_openlpimporter.py.moved.
Text conflict in tests/functional/openlp_plugins/songs/test_songselect.py
Contents conflict in tests/interfaces/openlp_core_common/test_historycombobox.py
Text conflict in tests/interfaces/openlp_core_lib/test_pluginmanager.py
Contents conflict in tests/interfaces/openlp_core_ui/test_listpreviewwidget.py
Conflict: can't delete tests/interfaces/openlp_core_utils because it is not empty.  Not deleting.
Conflict because tests/interfaces/openlp_core_utils is not versioned, but has versioned children.  Versioned directory.
Contents conflict in tests/interfaces/openlp_core_utils/__init__.py
Contents conflict in tests/interfaces/openlp_core_utils/test_utils.py
Text conflict in tests/interfaces/openlp_plugins/custom/forms/test_customform.py
Conflict adding file tests/resources/opensongsongs/Amazing Grace with bad CCLI.  Moved existing file to tests/resources/opensongsongs/Amazing Grace with bad CCLI.moved.
Conflict adding file tests/resources/opensongsongs/Amazing Grace without CCLI.json.  Moved existing file to tests/resources/opensongsongs/Amazing Grace without CCLI.json.moved.
Conflict adding file tests/resources/songshowplussongs/cleanse-me.json.  Moved existing file to tests/resources/songshowplussongs/cleanse-me.json.moved.
Conflict adding file tests/resources/songshowplussongs/cleanse-me.sbsong.  Moved existing file to tests/resources/songshowplussongs/cleanse-me.sbsong.moved.
Conflict adding file tests/resources/videopsalmsongs/as-safe-a-stronghold2.json.  Moved existing file to tests/resources/videopsalmsongs/as-safe-a-stronghold2.json.moved.
Conflict adding file tests/resources/videopsalmsongs/videopsalm-as-safe-a-stronghold2.json.  Moved existing file to tests/resources/videopsalmsongs/videopsalm-as-safe-a-stronghold2.json.moved.
To merge this branch: bzr merge lp:~suutari-olli/openlp/fix-openlp-importer
Reviewer Review Type Date Requested Status
OpenLP Core Pending
Review via email: mp+314295@code.launchpad.net

This proposal has been superseded by a proposal from 2017-01-08.

Description of the change

- For diff

To post a comment you must log in.
2717. By Azaziah

- Fixed bug #1632567

2718. By Azaziah

- This is broken, attempted to fix author types on import

Unmerged revisions

2718. By Azaziah

- This is broken, attempted to fix author types on import

2717. By Azaziah

- Fixed bug #1632567

2716. By Azaziah

- Better fix, now checks for songbook entry 1st.

2715. By Azaziah

- Fixed bug: 1652003

2714. By Tim Bentley

Update year in headers and about form

2713. By Tim Bentley

Move some http code around to allow it to be reused in the remote updates.

lp:~trb143/openlp/httpfixes (revision 2718)
[SUCCESS] https://ci.openlp.io/job/Branch-01-Pull/1888/
[SUCCESS] https://ci.openlp.io/job/Branch-02-Functional-Tests/1799/
[SUCCESS] https://ci.openlp.io/job/Branch-03-Interface-Tests/1738/
[SUCCESS] https://ci.openlp.io/job/Branch-04a-Windows_Functional_Tests/1474/
[SUCCESS] https://ci.openlp.io/job/Branch-04b-Windows_Interface_Tests/1064/
[SUCCESS] https://ci.openlp.io/jo...

2712. By Tomas Groth

Update to the appveyor integration script to work with the new packaging builder.

2711. By Raoul Snyman

Fix segfault on click on a spinner on macOS
Fix spurious traceback on some platforms or configurations of media players
Make the tab style affect only the media library tabs, not everything else too
Hide the splash screen when the backup dialog shows and when the exception form shows

Add this to your merge proposal:
--------------------------------
lp:~raoul-snyman/openlp/mac-niggles (revision 2713)
[SUCCESS] https://ci.openlp.io/job/Branch-01-Pull/1873/
[SUCCESS] https://ci.openlp.io/job/B...

2710. By Raoul Snyman

Fix bug #1642684 by removing the blank item and clearing the text box instead.

Add this to your merge proposal:
--------------------------------
lp:~raoul-snyman/openlp/bug-1642684 (revision 2710)
[SUCCESS] https://ci.openlp.io/job/Branch-01-Pull/1869/
[SUCCESS] https://ci.openlp.io/job/Branch-02-Functional-Tests/1780/
[SUCCESS] https://ci.openlp.io/job/Branch-03-Interface-Tests/1718/
[SUCCESS] https://ci.openlp.io/job/Branch-04a-Windows_Functional_Tests/1458/
[SUCCESS] https://ci.openlp.io/...

2709. By Raoul Snyman

Woo! Brand new OpenLP look and feel!

Unfortunately, the Crosswalk servers seem a little dodgy at the moment.

Add this to your merge proposal:
--------------------------------
lp:~raoul-snyman/openlp/new-branding (revision 2703)
[SUCCESS] https://ci.openlp.io/job/Branch-01-Pull/1862/
[SUCCESS] https://ci.openlp.io/job/Branch-02-Functional-Tests/1773/
[FAILURE] https://ci.openlp.io/job/Branch-03-Interface-Tests/1711/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file '.bzrignore'
--- .bzrignore 2016-04-27 18:45:39 +0000
+++ .bzrignore 2017-01-08 22:05:36 +0000
@@ -45,4 +45,9 @@
45*.kdev445*.kdev4
46coverage46coverage
47tags47tags
48output48<<<<<<< TREE
49output
50=======
51output
52htmlcov
53>>>>>>> MERGE-SOURCE
4954
=== removed file '.coveragerc'
--- .coveragerc 2016-01-04 00:01:23 +0000
+++ .coveragerc 1970-01-01 00:00:00 +0000
@@ -1,5 +0,0 @@
1[run]
2source = openlp
3
4[html]
5directory = coverage
60
=== added file 'nose2.cfg'
--- nose2.cfg 1970-01-01 00:00:00 +0000
+++ nose2.cfg 2017-01-08 22:05:36 +0000
@@ -0,0 +1,22 @@
1[unittest]
2verbose = true
3plugins = nose2.plugins.mp
4
5[log-capture]
6always-on = true
7clear-handlers = true
8filter = -nose
9log-level = ERROR
10
11[test-result]
12always-on = true
13descriptions = true
14
15[coverage]
16always-on = true
17coverage = openlp
18coverage-report = html
19
20[multiprocess]
21always-on = false
22processes = 4
023
=== modified file 'openlp/.version'
--- openlp/.version 2016-11-26 15:09:53 +0000
+++ openlp/.version 2017-01-08 22:05:36 +0000
@@ -1,1 +1,5 @@
1<<<<<<< TREE
12.4.422.4.4
3=======
42.5.0
5>>>>>>> MERGE-SOURCE
26
=== modified file 'openlp/core/__init__.py'
--- openlp/core/__init__.py 2016-12-31 11:05:48 +0000
+++ openlp/core/__init__.py 2017-01-08 22:05:36 +0000
@@ -27,26 +27,26 @@
27logging and a plugin framework are contained within the openlp.core module.27logging and a plugin framework are contained within the openlp.core module.
28"""28"""
2929
30import argparse
31import logging
30import os32import os
33import shutil
31import sys34import sys
32import logging35import time
33import argparse
34from traceback import format_exception36from traceback import format_exception
35import shutil37
36import time
37from PyQt5 import QtCore, QtGui, QtWidgets38from PyQt5 import QtCore, QtGui, QtWidgets
3839
39from openlp.core.common import Registry, OpenLPMixin, AppLocation, Settings, UiStrings, check_directory_exists, \40from openlp.core.common import Registry, OpenLPMixin, AppLocation, LanguageManager, Settings, UiStrings, \
40 is_macosx, is_win, translate41 check_directory_exists, is_macosx, is_win, translate
42from openlp.core.common.versionchecker import VersionThread, get_application_version
41from openlp.core.lib import ScreenList43from openlp.core.lib import ScreenList
42from openlp.core.resources import qInitResources44from openlp.core.resources import qInitResources
45from openlp.core.ui import SplashScreen
46from openlp.core.ui.exceptionform import ExceptionForm
47from openlp.core.ui.firsttimeform import FirstTimeForm
48from openlp.core.ui.firsttimelanguageform import FirstTimeLanguageForm
43from openlp.core.ui.mainwindow import MainWindow49from openlp.core.ui.mainwindow import MainWindow
44from openlp.core.ui.firsttimelanguageform import FirstTimeLanguageForm
45from openlp.core.ui.firsttimeform import FirstTimeForm
46from openlp.core.ui.exceptionform import ExceptionForm
47from openlp.core.ui import SplashScreen
48from openlp.core.utils import LanguageManager, VersionThread, get_application_version
49
5050
51__all__ = ['OpenLP', 'main']51__all__ = ['OpenLP', 'main']
5252
@@ -177,6 +177,38 @@
177 self.shared_memory.create(1)177 self.shared_memory.create(1)
178 return False178 return False
179179
180 def is_data_path_missing(self):
181 """
182 Check if the data folder path exists.
183 """
184 data_folder_path = AppLocation.get_data_path()
185 if not os.path.exists(data_folder_path):
186 log.critical('Database was not found in: ' + data_folder_path)
187 status = QtWidgets.QMessageBox.critical(None, translate('OpenLP', 'Data Directory Error'),
188 translate('OpenLP', 'OpenLP data folder was not found in:\n\n{path}'
189 '\n\nThe location of the data folder was '
190 'previously changed from the OpenLP\'s '
191 'default location. If the data was stored on '
192 'removable device, that device needs to be '
193 'made available.\n\nYou may reset the data '
194 'location back to the default location, '
195 'or you can try to make the current location '
196 'available.\n\nDo you want to reset to the '
197 'default data location? If not, OpenLP will be '
198 'closed so you can try to fix the the problem.')
199 .format(path=data_folder_path),
200 QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes |
201 QtWidgets.QMessageBox.No),
202 QtWidgets.QMessageBox.No)
203 if status == QtWidgets.QMessageBox.No:
204 # If answer was "No", return "True", it will shutdown OpenLP in def main
205 log.info('User requested termination')
206 return True
207 # If answer was "Yes", remove the custom data path thus resetting the default location.
208 Settings().remove('advanced/data path')
209 log.info('Database location has been reset to the default settings.')
210 return False
211
180 def hook_exception(self, exc_type, value, traceback):212 def hook_exception(self, exc_type, value, traceback):
181 """213 """
182 Add an exception hook so that any uncaught exceptions are displayed in this window rather than somewhere where214 Add an exception hook so that any uncaught exceptions are displayed in this window rather than somewhere where
@@ -205,6 +237,7 @@
205 Check if OpenLP has been upgraded, and ask if a backup of data should be made237 Check if OpenLP has been upgraded, and ask if a backup of data should be made
206238
207 :param has_run_wizard: OpenLP has been run before239 :param has_run_wizard: OpenLP has been run before
240 :param can_show_splash: Should OpenLP show the splash screen
208 """241 """
209 data_version = Settings().value('core/application version')242 data_version = Settings().value('core/application version')
210 openlp_version = get_application_version()['version']243 openlp_version = get_application_version()['version']
@@ -216,8 +249,8 @@
216 if self.splash.isVisible():249 if self.splash.isVisible():
217 self.splash.hide()250 self.splash.hide()
218 if QtWidgets.QMessageBox.question(None, translate('OpenLP', 'Backup'),251 if QtWidgets.QMessageBox.question(None, translate('OpenLP', 'Backup'),
219 translate('OpenLP', 'OpenLP has been upgraded, do you want to create '252 translate('OpenLP', 'OpenLP has been upgraded, do you want to create\n'
220 'a backup of OpenLPs data folder?'),253 'a backup of the old data folder?'),
221 QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,254 QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
222 QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.Yes:255 QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.Yes:
223 # Create copy of data folder256 # Create copy of data folder
@@ -230,10 +263,11 @@
230 QtWidgets.QMessageBox.warning(None, translate('OpenLP', 'Backup'),263 QtWidgets.QMessageBox.warning(None, translate('OpenLP', 'Backup'),
231 translate('OpenLP', 'Backup of the data folder failed!'))264 translate('OpenLP', 'Backup of the data folder failed!'))
232 return265 return
233 QtWidgets.QMessageBox.information(None, translate('OpenLP', 'Backup'),266 message = translate('OpenLP',
234 translate('OpenLP',267 'A backup of the data folder has been created at:\n\n'
235 'A backup of the data folder has been created at %s')268 '{text}').format(text=data_folder_backup_path)
236 % data_folder_backup_path)269 QtWidgets.QMessageBox.information(None, translate('OpenLP', 'Backup'), message)
270
237 # Update the version in the settings271 # Update the version in the settings
238 Settings().setValue('core/application version', openlp_version)272 Settings().setValue('core/application version', openlp_version)
239 if can_show_splash:273 if can_show_splash:
@@ -267,7 +301,7 @@
267 """301 """
268 if event.type() == QtCore.QEvent.FileOpen:302 if event.type() == QtCore.QEvent.FileOpen:
269 file_name = event.file()303 file_name = event.file()
270 log.debug('Got open file event for %s!', file_name)304 log.debug('Got open file event for {name}!'.format(name=file_name))
271 self.args.insert(0, file_name)305 self.args.insert(0, file_name)
272 return True306 return True
273 # Mac OS X should restore app window when user clicked on the OpenLP icon307 # Mac OS X should restore app window when user clicked on the OpenLP icon
@@ -321,7 +355,7 @@
321 logfile.setFormatter(logging.Formatter('%(asctime)s %(name)-55s %(levelname)-8s %(message)s'))355 logfile.setFormatter(logging.Formatter('%(asctime)s %(name)-55s %(levelname)-8s %(message)s'))
322 log.addHandler(logfile)356 log.addHandler(logfile)
323 if log.isEnabledFor(logging.DEBUG):357 if log.isEnabledFor(logging.DEBUG):
324 print('Logging to: %s' % filename)358 print('Logging to: {name}'.format(name=filename))
325359
326360
327def main(args=None):361def main(args=None):
@@ -362,12 +396,12 @@
362 log.info('Running portable')396 log.info('Running portable')
363 portable_settings_file = os.path.abspath(os.path.join(application_path, '..', '..', 'Data', 'OpenLP.ini'))397 portable_settings_file = os.path.abspath(os.path.join(application_path, '..', '..', 'Data', 'OpenLP.ini'))
364 # Make this our settings file398 # Make this our settings file
365 log.info('INI file: %s', portable_settings_file)399 log.info('INI file: {name}'.format(name=portable_settings_file))
366 Settings.set_filename(portable_settings_file)400 Settings.set_filename(portable_settings_file)
367 portable_settings = Settings()401 portable_settings = Settings()
368 # Set our data path402 # Set our data path
369 data_path = os.path.abspath(os.path.join(application_path, '..', '..', 'Data',))403 data_path = os.path.abspath(os.path.join(application_path, '..', '..', 'Data',))
370 log.info('Data path: %s', data_path)404 log.info('Data path: {name}'.format(name=data_path))
371 # Point to our data path405 # Point to our data path
372 portable_settings.setValue('advanced/data path', data_path)406 portable_settings.setValue('advanced/data path', data_path)
373 portable_settings.setValue('advanced/is portable', True)407 portable_settings.setValue('advanced/is portable', True)
@@ -378,9 +412,13 @@
378 Registry.create()412 Registry.create()
379 Registry().register('application', application)413 Registry().register('application', application)
380 application.setApplicationVersion(get_application_version()['version'])414 application.setApplicationVersion(get_application_version()['version'])
381 # Instance check415 # Check if an instance of OpenLP is already running. Quit if there is a running instance and the user only wants one
382 if application.is_already_running():416 if application.is_already_running():
383 sys.exit()417 sys.exit()
418 # If the custom data path is missing and the user wants to restore the data path, quit OpenLP.
419 if application.is_data_path_missing():
420 application.shared_memory.detach()
421 sys.exit()
384 # Remove/convert obsolete settings.422 # Remove/convert obsolete settings.
385 Settings().remove_obsolete_settings()423 Settings().remove_obsolete_settings()
386 # First time checks in settings424 # First time checks in settings
387425
=== modified file 'openlp/core/common/__init__.py'
--- openlp/core/common/__init__.py 2016-12-31 11:05:48 +0000
+++ openlp/core/common/__init__.py 2017-01-08 22:05:36 +0000
@@ -24,15 +24,18 @@
24OpenLP work.24OpenLP work.
25"""25"""
26import hashlib26import hashlib
27
28import logging
29import os
27import re30import re
28import os
29import logging
30import sys31import sys
31import traceback32import traceback
33from chardet.universaldetector import UniversalDetector
32from ipaddress import IPv4Address, IPv6Address, AddressValueError34from ipaddress import IPv4Address, IPv6Address, AddressValueError
33from codecs import decode, encode35from shutil import which
36from subprocess import check_output, CalledProcessError, STDOUT
3437
35from PyQt5 import QtCore38from PyQt5 import QtCore, QtGui
36from PyQt5.QtCore import QCryptographicHash as QHash39from PyQt5.QtCore import QCryptographicHash as QHash
3740
38log = logging.getLogger(__name__ + '.__init__')41log = logging.getLogger(__name__ + '.__init__')
@@ -40,6 +43,9 @@
4043
41FIRST_CAMEL_REGEX = re.compile('(.)([A-Z][a-z]+)')44FIRST_CAMEL_REGEX = re.compile('(.)([A-Z][a-z]+)')
42SECOND_CAMEL_REGEX = re.compile('([a-z0-9])([A-Z])')45SECOND_CAMEL_REGEX = re.compile('([a-z0-9])([A-Z])')
46CONTROL_CHARS = re.compile(r'[\x00-\x1F\x7F-\x9F]', re.UNICODE)
47INVALID_FILE_CHARS = re.compile(r'[\\/:\*\?"<>\|\+\[\]%]', re.UNICODE)
48IMAGES_FILTER = None
4349
4450
45def trace_error_handler(logger):51def trace_error_handler(logger):
@@ -50,7 +56,9 @@
50 """56 """
51 log_string = "OpenLP Error trace"57 log_string = "OpenLP Error trace"
52 for tb in traceback.extract_stack():58 for tb in traceback.extract_stack():
53 log_string = '%s\n File %s at line %d \n\t called %s' % (log_string, tb[0], tb[1], tb[3])59 log_string += '\n File {file} at line {line} \n\t called {data}'.format(file=tb[0],
60 line=tb[1],
61 data=tb[3])
54 logger.error(log_string)62 logger.error(log_string)
5563
5664
@@ -62,7 +70,7 @@
62 :param do_not_log: To not log anything. This is need for the start up, when the log isn't ready.70 :param do_not_log: To not log anything. This is need for the start up, when the log isn't ready.
63 """71 """
64 if not do_not_log:72 if not do_not_log:
65 log.debug('check_directory_exists %s' % directory)73 log.debug('check_directory_exists {text}'.format(text=directory))
66 try:74 try:
67 if not os.path.exists(directory):75 if not os.path.exists(directory):
68 os.makedirs(directory)76 os.makedirs(directory)
@@ -188,7 +196,7 @@
188 return True if verify_ipv4(addr) else verify_ipv6(addr)196 return True if verify_ipv4(addr) else verify_ipv6(addr)
189197
190198
191def md5_hash(salt, data=None):199def md5_hash(salt=None, data=None):
192 """200 """
193 Returns the hashed output of md5sum on salt,data201 Returns the hashed output of md5sum on salt,data
194 using Python3 hashlib202 using Python3 hashlib
@@ -197,33 +205,49 @@
197 :param data: OPTIONAL Data to hash205 :param data: OPTIONAL Data to hash
198 :returns: str206 :returns: str
199 """207 """
200 log.debug('md5_hash(salt="%s")' % salt)208 log.debug('md5_hash(salt="{text}")'.format(text=salt))
209 if not salt and not data:
210 return None
201 hash_obj = hashlib.new('md5')211 hash_obj = hashlib.new('md5')
202 hash_obj.update(salt)212 if salt:
213 hash_obj.update(salt)
203 if data:214 if data:
204 hash_obj.update(data)215 hash_obj.update(data)
205 hash_value = hash_obj.hexdigest()216 hash_value = hash_obj.hexdigest()
206 log.debug('md5_hash() returning "%s"' % hash_value)217 log.debug('md5_hash() returning "{text}"'.format(text=hash_value))
207 return hash_value218 return hash_value
208219
209220
210def qmd5_hash(salt, data=None):221def qmd5_hash(salt=None, data=None):
211 """222 """
212 Returns the hashed output of MD5Sum on salt, data223 Returns the hashed output of MD5Sum on salt, data
213 using PyQt5.QCryptographicHash.224 using PyQt5.QCryptographicHash. Function returns a
225 QByteArray instead of a text string.
226 If you need a string instead, call with
227
228 result = str(qmd5_hash(salt=..., data=...), encoding='ascii')
214229
215 :param salt: Initial salt230 :param salt: Initial salt
216 :param data: OPTIONAL Data to hash231 :param data: OPTIONAL Data to hash
217 :returns: str232 :returns: QByteArray
218 """233 """
219 log.debug('qmd5_hash(salt="%s"' % salt)234 log.debug('qmd5_hash(salt="{text}"'.format(text=salt))
235 if salt is None and data is None:
236 return None
220 hash_obj = QHash(QHash.Md5)237 hash_obj = QHash(QHash.Md5)
238<<<<<<< TREE
221 hash_obj.addData(salt)239 hash_obj.addData(salt)
222 if data:240 if data:
223 hash_obj.addData(data)241 hash_obj.addData(data)
242=======
243 if salt:
244 hash_obj.addData(salt)
245 if data:
246 hash_obj.addData(data)
247>>>>>>> MERGE-SOURCE
224 hash_value = hash_obj.result().toHex()248 hash_value = hash_obj.result().toHex()
225 log.debug('qmd5_hash() returning "%s"' % hash_value)249 log.debug('qmd5_hash() returning "{hash}"'.format(hash=hash_value))
226 return hash_value.data()250 return hash_value
227251
228252
229def clean_button_text(button_text):253def clean_button_text(button_text):
@@ -242,4 +266,181 @@
242from .uistrings import UiStrings266from .uistrings import UiStrings
243from .settings import Settings267from .settings import Settings
244from .applocation import AppLocation268from .applocation import AppLocation
245from .historycombobox import HistoryComboBox269from .actions import ActionList
270from .languagemanager import LanguageManager
271
272if is_win():
273 from subprocess import STARTUPINFO, STARTF_USESHOWWINDOW
274
275
276def add_actions(target, actions):
277 """
278 Adds multiple actions to a menu or toolbar in one command.
279
280 :param target: The menu or toolbar to add actions to
281 :param actions: The actions to be added. An action consisting of the keyword ``None``
282 will result in a separator being inserted into the target.
283 """
284 for action in actions:
285 if action is None:
286 target.addSeparator()
287 else:
288 target.addAction(action)
289
290
291def get_uno_command(connection_type='pipe'):
292 """
293 Returns the UNO command to launch an libreoffice.org instance.
294 """
295 for command in ['libreoffice', 'soffice']:
296 if which(command):
297 break
298 else:
299 raise FileNotFoundError('Command not found')
300
301 OPTIONS = '--nologo --norestore --minimized --nodefault --nofirststartwizard'
302 if connection_type == 'pipe':
303 CONNECTION = '"--accept=pipe,name=openlp_pipe;urp;"'
304 else:
305 CONNECTION = '"--accept=socket,host=localhost,port=2002;urp;"'
306 return '{cmd} {opt} {conn}'.format(cmd=command, opt=OPTIONS, conn=CONNECTION)
307
308
309def get_uno_instance(resolver, connection_type='pipe'):
310 """
311 Returns a running libreoffice.org instance.
312
313 :param resolver: The UNO resolver to use to find a running instance.
314 """
315 log.debug('get UNO Desktop Openoffice - resolve')
316 if connection_type == 'pipe':
317 return resolver.resolve('uno:pipe,name=openlp_pipe;urp;StarOffice.ComponentContext')
318 else:
319 return resolver.resolve('uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext')
320
321
322def get_filesystem_encoding():
323 """
324 Returns the name of the encoding used to convert Unicode filenames into system file names.
325 """
326 encoding = sys.getfilesystemencoding()
327 if encoding is None:
328 encoding = sys.getdefaultencoding()
329 return encoding
330
331
332def split_filename(path):
333 """
334 Return a list of the parts in a given path.
335 """
336 path = os.path.abspath(path)
337 if not os.path.isfile(path):
338 return path, ''
339 else:
340 return os.path.split(path)
341
342
343def delete_file(file_path_name):
344 """
345 Deletes a file from the system.
346
347 :param file_path_name: The file, including path, to delete.
348 """
349 if not file_path_name:
350 return False
351 try:
352 if os.path.exists(file_path_name):
353 os.remove(file_path_name)
354 return True
355 except (IOError, OSError):
356 log.exception("Unable to delete file {text}".format(text=file_path_name))
357 return False
358
359
360def get_images_filter():
361 """
362 Returns a filter string for a file dialog containing all the supported image formats.
363 """
364 global IMAGES_FILTER
365 if not IMAGES_FILTER:
366 log.debug('Generating images filter.')
367 formats = list(map(bytes.decode, list(map(bytes, QtGui.QImageReader.supportedImageFormats()))))
368 visible_formats = '(*.{text})'.format(text='; *.'.join(formats))
369 actual_formats = '(*.{text})'.format(text=' *.'.join(formats))
370 IMAGES_FILTER = '{text} {visible} {actual}'.format(text=translate('OpenLP', 'Image Files'),
371 visible=visible_formats,
372 actual=actual_formats)
373 return IMAGES_FILTER
374
375
376def is_not_image_file(file_name):
377 """
378 Validate that the file is not an image file.
379
380 :param file_name: File name to be checked.
381 """
382 if not file_name:
383 return True
384 else:
385 formats = [bytes(fmt).decode().lower() for fmt in QtGui.QImageReader.supportedImageFormats()]
386 file_part, file_extension = os.path.splitext(str(file_name))
387 if file_extension[1:].lower() in formats and os.path.exists(file_name):
388 return False
389 return True
390
391
392def clean_filename(filename):
393 """
394 Removes invalid characters from the given ``filename``.
395
396 :param filename: The "dirty" file name to clean.
397 """
398 if not isinstance(filename, str):
399 filename = str(filename, 'utf-8')
400 return INVALID_FILE_CHARS.sub('_', CONTROL_CHARS.sub('', filename))
401
402
403def check_binary_exists(program_path):
404 """
405 Function that checks whether a binary exists.
406
407 :param program_path:The full path to the binary to check.
408 :return: program output to be parsed
409 """
410 log.debug('testing program_path: {text}'.format(text=program_path))
411 try:
412 # Setup startupinfo options for check_output to avoid console popping up on windows
413 if is_win():
414 startupinfo = STARTUPINFO()
415 startupinfo.dwFlags |= STARTF_USESHOWWINDOW
416 else:
417 startupinfo = None
418 runlog = check_output([program_path, '--help'], stderr=STDOUT, startupinfo=startupinfo)
419 except CalledProcessError as e:
420 runlog = e.output
421 except Exception:
422 trace_error_handler(log)
423 runlog = ''
424 log.debug('check_output returned: {text}'.format(text=runlog))
425 return runlog
426
427
428def get_file_encoding(filename):
429 """
430 Utility function to incrementally detect the file encoding.
431
432 :param filename: Filename for the file to determine the encoding for. Str
433 :return: A dict with the keys 'encoding' and 'confidence'
434 """
435 detector = UniversalDetector()
436 try:
437 with open(filename, 'rb') as detect_file:
438 while not detector.done:
439 chunk = detect_file.read(1024)
440 if not chunk:
441 break
442 detector.feed(chunk)
443 detector.close()
444 return detector.result
445 except OSError:
446 log.exception('Error detecting file encoding')
246447
=== added file 'openlp/core/common/actions.py'
--- openlp/core/common/actions.py 1970-01-01 00:00:00 +0000
+++ openlp/core/common/actions.py 2017-01-08 22:05:36 +0000
@@ -0,0 +1,390 @@
1# -*- coding: utf-8 -*-
2# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
3
4###############################################################################
5# OpenLP - Open Source Lyrics Projection #
6# --------------------------------------------------------------------------- #
7# Copyright (c) 2008-2017 OpenLP Developers #
8# --------------------------------------------------------------------------- #
9# This program is free software; you can redistribute it and/or modify it #
10# under the terms of the GNU General Public License as published by the Free #
11# Software Foundation; version 2 of the License. #
12# #
13# This program is distributed in the hope that it will be useful, but WITHOUT #
14# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
15# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
16# more details. #
17# #
18# You should have received a copy of the GNU General Public License along #
19# with this program; if not, write to the Free Software Foundation, Inc., 59 #
20# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
21###############################################################################
22"""
23The :mod:`~openlp.core.utils.actions` module provides action list classes used
24by the shortcuts system.
25"""
26import logging
27
28from PyQt5 import QtCore, QtGui, QtWidgets
29
30from openlp.core.common import Settings
31
32
33log = logging.getLogger(__name__)
34
35
36class ActionCategory(object):
37 """
38 The :class:`~openlp.core.utils.ActionCategory` class encapsulates a category for the
39 :class:`~openlp.core.utils.CategoryList` class.
40 """
41 def __init__(self, name, weight=0):
42 """
43 Constructor
44 """
45 self.name = name
46 self.weight = weight
47 self.actions = CategoryActionList()
48
49
50class CategoryActionList(object):
51 """
52 The :class:`~openlp.core.utils.CategoryActionList` class provides a sorted list of actions within a category.
53 """
54 def __init__(self):
55 """
56 Constructor
57 """
58 self.index = 0
59 self.actions = []
60
61 def __contains__(self, key):
62 """
63 Implement the __contains__() method to make this class a dictionary type
64 """
65 for weight, action in self.actions:
66 if action == key:
67 return True
68 return False
69
70 def __len__(self):
71 """
72 Implement the __len__() method to make this class a dictionary type
73 """
74 return len(self.actions)
75
76 def __iter__(self):
77 """
78 Implement the __getitem__() method to make this class iterable
79 """
80 return self
81
82 def __next__(self):
83 """
84 Python 3 "next" method.
85 """
86 if self.index >= len(self.actions):
87 self.index = 0
88 raise StopIteration
89 else:
90 self.index += 1
91 return self.actions[self.index - 1][1]
92
93 def append(self, action):
94 """
95 Append an action
96 """
97 weight = 0
98 if self.actions:
99 weight = self.actions[-1][0] + 1
100 self.add(action, weight)
101
102 def add(self, action, weight=0):
103 """
104 Add an action.
105 """
106 self.actions.append((weight, action))
107 self.actions.sort(key=lambda act: act[0])
108
109 def remove(self, action):
110 """
111 Remove an action
112 """
113 for item in self.actions:
114 if item[1] == action:
115 self.actions.remove(item)
116 return
117 raise ValueError('Action "{action}" does not exist.'.format(action=action))
118
119
120class CategoryList(object):
121 """
122 The :class:`~openlp.core.utils.CategoryList` class encapsulates a category list for the
123 :class:`~openlp.core.utils.ActionList` class and provides an iterator interface for walking through the list of
124 actions in this category.
125 """
126
127 def __init__(self):
128 """
129 Constructor
130 """
131 self.index = 0
132 self.categories = []
133
134 def __getitem__(self, key):
135 """
136 Implement the __getitem__() method to make this class like a dictionary
137 """
138 for category in self.categories:
139 if category.name == key:
140 return category
141 raise KeyError('Category "{key}" does not exist.'.format(key=key))
142
143 def __len__(self):
144 """
145 Implement the __len__() method to make this class like a dictionary
146 """
147 return len(self.categories)
148
149 def __iter__(self):
150 """
151 Implement the __iter__() method to make this class like a dictionary
152 """
153 return self
154
155 def __next__(self):
156 """
157 Python 3 "next" method for iterator.
158 """
159 if self.index >= len(self.categories):
160 self.index = 0
161 raise StopIteration
162 else:
163 self.index += 1
164 return self.categories[self.index - 1]
165
166 def __contains__(self, key):
167 """
168 Implement the __contains__() method to make this class like a dictionary
169 """
170 for category in self.categories:
171 if category.name == key:
172 return True
173 return False
174
175 def append(self, name, actions=None):
176 """
177 Append a category
178 """
179 weight = 0
180 if self.categories:
181 weight = self.categories[-1].weight + 1
182 self.add(name, weight, actions)
183
184 def add(self, name, weight=0, actions=None):
185 """
186 Add a category
187 """
188 category = ActionCategory(name, weight)
189 if actions:
190 for action in actions:
191 if isinstance(action, tuple):
192 category.actions.add(action[0], action[1])
193 else:
194 category.actions.append(action)
195 self.categories.append(category)
196 self.categories.sort(key=lambda cat: cat.weight)
197
198 def remove(self, name):
199 """
200 Remove a category
201 """
202 for category in self.categories:
203 if category.name == name:
204 self.categories.remove(category)
205 return
206 raise ValueError('Category "{name}" does not exist.'.format(name=name))
207
208
209class ActionList(object):
210 """
211 The :class:`~openlp.core.utils.ActionList` class contains a list of menu actions and categories associated with
212 those actions. Each category also has a weight by which it is sorted when iterating through the list of actions or
213 categories.
214 """
215 instance = None
216 shortcut_map = {}
217
218 def __init__(self):
219 """
220 Constructor
221 """
222 self.categories = CategoryList()
223
224 @staticmethod
225 def get_instance():
226 """
227 Get the instance of this class.
228 """
229 if ActionList.instance is None:
230 ActionList.instance = ActionList()
231 return ActionList.instance
232
233 def add_action(self, action, category=None, weight=None):
234 """
235 Add an action to the list of actions.
236
237 **Note**: The action's objectName must be set when you want to add it!
238
239 :param action: The action to add (QAction). **Note**, the action must not have an empty ``objectName``.
240 :param category: The category this action belongs to. The category has to be a python string. . **Note**,
241 if the category is ``None``, the category and its actions are being hidden in the shortcut dialog. However,
242 if they are added, it is possible to avoid assigning shortcuts twice, which is important.
243 :param weight: The weight specifies how important a category is. However, this only has an impact on the order
244 the categories are displayed.
245 """
246 if category not in self.categories:
247 self.categories.append(category)
248 settings = Settings()
249 settings.beginGroup('shortcuts')
250 # Get the default shortcut from the config.
251 action.default_shortcuts = settings.get_default_value(action.objectName())
252 if weight is None:
253 self.categories[category].actions.append(action)
254 else:
255 self.categories[category].actions.add(action, weight)
256 # Load the shortcut from the config.
257 shortcuts = settings.value(action.objectName())
258 settings.endGroup()
259 if not shortcuts:
260 action.setShortcuts([])
261 return
262 # We have to do this to ensure that the loaded shortcut list e. g. STRG+O (German) is converted to CTRL+O,
263 # which is only done when we convert the strings in this way (QKeySequencet -> uncode).
264 shortcuts = list(map(QtGui.QKeySequence.toString, list(map(QtGui.QKeySequence, shortcuts))))
265 # Check the alternate shortcut first, to avoid problems when the alternate shortcut becomes the primary shortcut
266 # after removing the (initial) primary shortcut due to conflicts.
267 if len(shortcuts) == 2:
268 existing_actions = ActionList.shortcut_map.get(shortcuts[1], [])
269 # Check for conflicts with other actions considering the shortcut context.
270 if self._is_shortcut_available(existing_actions, action):
271 actions = ActionList.shortcut_map.get(shortcuts[1], [])
272 actions.append(action)
273 ActionList.shortcut_map[shortcuts[1]] = actions
274 else:
275 log.warning('Shortcut "{shortcut}" is removed from "{action}" because another '
276 'action already uses this shortcut.'.format(shortcut=shortcuts[1],
277 action=action.objectName()))
278 shortcuts.remove(shortcuts[1])
279 # Check the primary shortcut.
280 existing_actions = ActionList.shortcut_map.get(shortcuts[0], [])
281 # Check for conflicts with other actions considering the shortcut context.
282 if self._is_shortcut_available(existing_actions, action):
283 actions = ActionList.shortcut_map.get(shortcuts[0], [])
284 actions.append(action)
285 ActionList.shortcut_map[shortcuts[0]] = actions
286 else:
287 log.warning('Shortcut "{shortcut}" is removed from "{action}" '
288 'because another action already uses this shortcut.'.format(shortcut=shortcuts[0],
289 action=action.objectName()))
290 shortcuts.remove(shortcuts[0])
291 action.setShortcuts([QtGui.QKeySequence(shortcut) for shortcut in shortcuts])
292
293 def remove_action(self, action, category=None):
294 """
295 This removes an action from its category. Empty categories are automatically removed.
296
297 :param action: The ``QAction`` object to be removed.
298 :param category: The name (unicode string) of the category, which contains the action. Defaults to None.
299 """
300 if category not in self.categories:
301 return
302 self.categories[category].actions.remove(action)
303 # Remove empty categories.
304 if not self.categories[category].actions:
305 self.categories.remove(category)
306 shortcuts = list(map(QtGui.QKeySequence.toString, action.shortcuts()))
307 for shortcut in shortcuts:
308 # Remove action from the list of actions which are using this shortcut.
309 ActionList.shortcut_map[shortcut].remove(action)
310 # Remove empty entries.
311 if not ActionList.shortcut_map[shortcut]:
312 del ActionList.shortcut_map[shortcut]
313
314 def add_category(self, name, weight):
315 """
316 Add an empty category to the list of categories. This is only convenient for categories with a given weight.
317
318 :param name: The category's name.
319 :param weight: The category's weight (int).
320 """
321 if name in self.categories:
322 # Only change the weight and resort the categories again.
323 for category in self.categories:
324 if category.name == name:
325 category.weight = weight
326 self.categories.categories.sort(key=lambda cat: cat.weight)
327 return
328 self.categories.add(name, weight)
329
330 def update_shortcut_map(self, action, old_shortcuts):
331 """
332 Remove the action for the given ``old_shortcuts`` from the ``shortcut_map`` to ensure its up-to-dateness.
333 **Note**: The new action's shortcuts **must** be assigned to the given ``action`` **before** calling this
334 method.
335
336 :param action: The action whose shortcuts are supposed to be updated in the ``shortcut_map``.
337 :param old_shortcuts: A list of unicode key sequences.
338 """
339 for old_shortcut in old_shortcuts:
340 # Remove action from the list of actions which are using this shortcut.
341 ActionList.shortcut_map[old_shortcut].remove(action)
342 # Remove empty entries.
343 if not ActionList.shortcut_map[old_shortcut]:
344 del ActionList.shortcut_map[old_shortcut]
345 new_shortcuts = list(map(QtGui.QKeySequence.toString, action.shortcuts()))
346 # Add the new shortcuts to the map.
347 for new_shortcut in new_shortcuts:
348 existing_actions = ActionList.shortcut_map.get(new_shortcut, [])
349 existing_actions.append(action)
350 ActionList.shortcut_map[new_shortcut] = existing_actions
351
352 def _is_shortcut_available(self, existing_actions, action):
353 """
354 Checks if the given ``action`` may use its assigned shortcut(s) or not. Returns ``True`` or ``False.
355
356 :param existing_actions: A list of actions which already use a particular shortcut.
357 :param action: The action which wants to use a particular shortcut.
358 """
359 global_context = action.shortcutContext() in [QtCore.Qt.WindowShortcut, QtCore.Qt.ApplicationShortcut]
360 affected_actions = []
361 if global_context:
362 affected_actions = [a for a in self.get_all_child_objects(action.parent()) if isinstance(a,
363 QtWidgets.QAction)]
364 for existing_action in existing_actions:
365 if action is existing_action:
366 continue
367 if existing_action in affected_actions:
368 return False
369 if existing_action.shortcutContext() in [QtCore.Qt.WindowShortcut, QtCore.Qt.ApplicationShortcut]:
370 return False
371 elif action in self.get_all_child_objects(existing_action.parent()):
372 return False
373 return True
374
375 def get_all_child_objects(self, qobject):
376 """
377 Goes recursively through the children of ``qobject`` and returns a list of all child objects.
378 """
379 children = qobject.children()
380 # Append the children's children.
381 children.extend(list(map(self.get_all_child_objects, children)))
382 return children
383
384
385class CategoryOrder(object):
386 """
387 An enumeration class for category weights.
388 """
389 standard_menu = -20
390 standard_toolbar = -10
0391
=== added file 'openlp/core/common/db.py'
--- openlp/core/common/db.py 1970-01-01 00:00:00 +0000
+++ openlp/core/common/db.py 2017-01-08 22:05:36 +0000
@@ -0,0 +1,72 @@
1# -*- coding: utf-8 -*-
2# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
3
4###############################################################################
5# OpenLP - Open Source Lyrics Projection #
6# --------------------------------------------------------------------------- #
7# Copyright (c) 2008-2017 OpenLP Developers #
8# --------------------------------------------------------------------------- #
9# This program is free software; you can redistribute it and/or modify it #
10# under the terms of the GNU General Public License as published by the Free #
11# Software Foundation; version 2 of the License. #
12# #
13# This program is distributed in the hope that it will be useful, but WITHOUT #
14# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
15# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
16# more details. #
17# #
18# You should have received a copy of the GNU General Public License along #
19# with this program; if not, write to the Free Software Foundation, Inc., 59 #
20# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
21###############################################################################
22"""
23The :mod:`db` module provides helper functions for database related methods.
24"""
25import logging
26from copy import deepcopy
27
28import sqlalchemy
29
30
31log = logging.getLogger(__name__)
32
33
34def drop_column(op, tablename, columnname):
35 drop_columns(op, tablename, [columnname])
36
37
38def drop_columns(op, tablename, columns):
39 """
40 Column dropping functionality for SQLite, as there is no DROP COLUMN support in SQLite
41
42 From https://github.com/klugjohannes/alembic-sqlite
43 """
44
45 # get the db engine and reflect database tables
46 engine = op.get_bind()
47 meta = sqlalchemy.MetaData(bind=engine)
48 meta.reflect()
49
50 # create a select statement from the old table
51 old_table = meta.tables[tablename]
52 select = sqlalchemy.sql.select([c for c in old_table.c if c.name not in columns])
53
54 # get remaining columns without table attribute attached
55 remaining_columns = [deepcopy(c) for c in old_table.columns if c.name not in columns]
56 for column in remaining_columns:
57 column.table = None
58
59 # create a temporary new table
60 new_tablename = '{0}_new'.format(tablename)
61 op.create_table(new_tablename, *remaining_columns)
62 meta.reflect()
63 new_table = meta.tables[new_tablename]
64
65 # copy data from old table
66 insert = sqlalchemy.sql.insert(new_table).from_select([c.name for c in remaining_columns], select)
67 engine.execute(insert)
68
69 # drop the old table and rename the new table to take the old tables
70 # position
71 op.drop_table(tablename)
72 op.rename_table(new_tablename, tablename)
073
=== renamed file 'openlp/core/common/historycombobox.py' => 'openlp/core/common/historycombobox.py.THIS'
=== added file 'openlp/core/common/httputils.py'
--- openlp/core/common/httputils.py 1970-01-01 00:00:00 +0000
+++ openlp/core/common/httputils.py 2017-01-08 22:05:36 +0000
@@ -0,0 +1,255 @@
1# -*- coding: utf-8 -*-
2# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
3
4###############################################################################
5# OpenLP - Open Source Lyrics Projection #
6# --------------------------------------------------------------------------- #
7# Copyright (c) 2008-2017 OpenLP Developers #
8# --------------------------------------------------------------------------- #
9# This program is free software; you can redistribute it and/or modify it #
10# under the terms of the GNU General Public License as published by the Free #
11# Software Foundation; version 2 of the License. #
12# #
13# This program is distributed in the hope that it will be useful, but WITHOUT #
14# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
15# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
16# more details. #
17# #
18# You should have received a copy of the GNU General Public License along #
19# with this program; if not, write to the Free Software Foundation, Inc., 59 #
20# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
21###############################################################################
22"""
23The :mod:`openlp.core.utils` module provides the utility libraries for OpenLP.
24"""
25import hashlib
26import logging
27import os
28import socket
29import sys
30import time
31import urllib.error
32import urllib.parse
33import urllib.request
34from http.client import HTTPException
35from random import randint
36
37from openlp.core.common import Registry, trace_error_handler
38
39log = logging.getLogger(__name__ + '.__init__')
40
41USER_AGENTS = {
42 'win32': [
43 'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36',
44 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36',
45 'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.71 Safari/537.36'
46 ],
47 'darwin': [
48 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3) AppleWebKit/537.31 (KHTML, like Gecko) '
49 'Chrome/26.0.1410.43 Safari/537.31',
50 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/536.11 (KHTML, like Gecko) '
51 'Chrome/20.0.1132.57 Safari/536.11',
52 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/536.11 (KHTML, like Gecko) '
53 'Chrome/20.0.1132.47 Safari/536.11',
54 ],
55 'linux2': [
56 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.22 (KHTML, like Gecko) Ubuntu Chromium/25.0.1364.160 '
57 'Chrome/25.0.1364.160 Safari/537.22',
58 'Mozilla/5.0 (X11; CrOS armv7l 2913.260.0) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.99 '
59 'Safari/537.11',
60 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.27 (KHTML, like Gecko) Chrome/26.0.1389.0 Safari/537.27'
61 ],
62 'default': [
63 'Mozilla/5.0 (X11; NetBSD amd64; rv:18.0) Gecko/20130120 Firefox/18.0'
64 ]
65}
66CONNECTION_TIMEOUT = 30
67CONNECTION_RETRIES = 2
68
69
70class HTTPRedirectHandlerFixed(urllib.request.HTTPRedirectHandler):
71 """
72 Special HTTPRedirectHandler used to work around http://bugs.python.org/issue22248
73 (Redirecting to urls with special chars)
74 """
75 def redirect_request(self, req, fp, code, msg, headers, new_url):
76 #
77 """
78 Test if the new_url can be decoded to ascii
79
80 :param req:
81 :param fp:
82 :param code:
83 :param msg:
84 :param headers:
85 :param new_url:
86 :return:
87 """
88 try:
89 new_url.encode('latin1').decode('ascii')
90 fixed_url = new_url
91 except Exception:
92 # The url could not be decoded to ascii, so we do some url encoding
93 fixed_url = urllib.parse.quote(new_url.encode('latin1').decode('utf-8', 'replace'), safe='/:')
94 return super(HTTPRedirectHandlerFixed, self).redirect_request(req, fp, code, msg, headers, fixed_url)
95
96
97def get_user_agent():
98 """
99 Return a user agent customised for the platform the user is on.
100 """
101 browser_list = USER_AGENTS.get(sys.platform, None)
102 if not browser_list:
103 browser_list = USER_AGENTS['default']
104 random_index = randint(0, len(browser_list) - 1)
105 return browser_list[random_index]
106
107
108def get_web_page(url, header=None, update_openlp=False):
109 """
110 Attempts to download the webpage at url and returns that page or None.
111
112 :param url: The URL to be downloaded.
113 :param header: An optional HTTP header to pass in the request to the web server.
114 :param update_openlp: Tells OpenLP to update itself if the page is successfully downloaded.
115 Defaults to False.
116 """
117 # TODO: Add proxy usage. Get proxy info from OpenLP settings, add to a
118 # proxy_handler, build into an opener and install the opener into urllib2.
119 # http://docs.python.org/library/urllib2.html
120 if not url:
121 return None
122 # This is needed to work around http://bugs.python.org/issue22248 and https://bugs.launchpad.net/openlp/+bug/1251437
123 opener = urllib.request.build_opener(HTTPRedirectHandlerFixed())
124 urllib.request.install_opener(opener)
125 req = urllib.request.Request(url)
126 if not header or header[0].lower() != 'user-agent':
127 user_agent = get_user_agent()
128 req.add_header('User-Agent', user_agent)
129 if header:
130 req.add_header(header[0], header[1])
131 log.debug('Downloading URL = %s' % url)
132 retries = 0
133 while retries <= CONNECTION_RETRIES:
134 retries += 1
135 time.sleep(0.1)
136 try:
137 page = urllib.request.urlopen(req, timeout=CONNECTION_TIMEOUT)
138 log.debug('Downloaded page {text}'.format(text=page.geturl()))
139 break
140 except urllib.error.URLError as err:
141 log.exception('URLError on {text}'.format(text=url))
142 log.exception('URLError: {text}'.format(text=err.reason))
143 page = None
144 if retries > CONNECTION_RETRIES:
145 raise
146 except socket.timeout:
147 log.exception('Socket timeout: {text}'.format(text=url))
148 page = None
149 if retries > CONNECTION_RETRIES:
150 raise
151 except socket.gaierror:
152 log.exception('Socket gaierror: {text}'.format(text=url))
153 page = None
154 if retries > CONNECTION_RETRIES:
155 raise
156 except ConnectionRefusedError:
157 log.exception('ConnectionRefused: {text}'.format(text=url))
158 page = None
159 if retries > CONNECTION_RETRIES:
160 raise
161 break
162 except ConnectionError:
163 log.exception('Connection error: {text}'.format(text=url))
164 page = None
165 if retries > CONNECTION_RETRIES:
166 raise
167 except HTTPException:
168 log.exception('HTTPException error: {text}'.format(text=url))
169 page = None
170 if retries > CONNECTION_RETRIES:
171 raise
172 except:
173 # Don't know what's happening, so reraise the original
174 raise
175 if update_openlp:
176 Registry().get('application').process_events()
177 if not page:
178 log.exception('{text} could not be downloaded'.format(text=url))
179 return None
180 log.debug(page)
181 return page
182
183
184def get_url_file_size(url):
185 """
186 Get the size of a file.
187
188 :param url: The URL of the file we want to download.
189 """
190 retries = 0
191 while True:
192 try:
193 site = urllib.request.urlopen(url, timeout=CONNECTION_TIMEOUT)
194 meta = site.info()
195 return int(meta.get("Content-Length"))
196 except urllib.error.URLError:
197 if retries > CONNECTION_RETRIES:
198 raise
199 else:
200 retries += 1
201 time.sleep(0.1)
202 continue
203
204
205def url_get_file(callback, url, f_path, sha256=None):
206 """"
207 Download a file given a URL. The file is retrieved in chunks, giving the ability to cancel the download at any
208 point. Returns False on download error.
209
210 :param callback: the class which needs to be updated
211 :param url: URL to download
212 :param f_path: Destination file
213 :param sha256: The check sum value to be checked against the download value
214 """
215 block_count = 0
216 block_size = 4096
217 retries = 0
218 while True:
219 try:
220 filename = open(f_path, "wb")
221 url_file = urllib.request.urlopen(url, timeout=CONNECTION_TIMEOUT)
222 if sha256:
223 hasher = hashlib.sha256()
224 # Download until finished or canceled.
225 while not callback.was_cancelled:
226 data = url_file.read(block_size)
227 if not data:
228 break
229 filename.write(data)
230 if sha256:
231 hasher.update(data)
232 block_count += 1
233 callback._download_progress(block_count, block_size)
234 filename.close()
235 if sha256 and hasher.hexdigest() != sha256:
236 log.error('sha256 sums did not match for file: {file}'.format(file=f_path))
237 os.remove(f_path)
238 return False
239 except (urllib.error.URLError, socket.timeout) as err:
240 trace_error_handler(log)
241 filename.close()
242 os.remove(f_path)
243 if retries > CONNECTION_RETRIES:
244 return False
245 else:
246 retries += 1
247 time.sleep(0.1)
248 continue
249 break
250 # Delete file if cancelled, it may be a partial file.
251 if callback.was_cancelled:
252 os.remove(f_path)
253 return True
254
255__all__ = ['get_web_page']
0256
=== added file 'openlp/core/common/languagemanager.py'
--- openlp/core/common/languagemanager.py 1970-01-01 00:00:00 +0000
+++ openlp/core/common/languagemanager.py 2017-01-08 22:05:36 +0000
@@ -0,0 +1,207 @@
1# -*- coding: utf-8 -*-
2# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
3
4###############################################################################
5# OpenLP - Open Source Lyrics Projection #
6# --------------------------------------------------------------------------- #
7# Copyright (c) 2008-2017 OpenLP Developers #
8# --------------------------------------------------------------------------- #
9# This program is free software; you can redistribute it and/or modify it #
10# under the terms of the GNU General Public License as published by the Free #
11# Software Foundation; version 2 of the License. #
12# #
13# This program is distributed in the hope that it will be useful, but WITHOUT #
14# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
15# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
16# more details. #
17# #
18# You should have received a copy of the GNU General Public License along #
19# with this program; if not, write to the Free Software Foundation, Inc., 59 #
20# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
21###############################################################################
22"""
23The :mod:`languagemanager` module provides all the translation settings and language file loading for OpenLP.
24"""
25import locale
26import logging
27import re
28
29from PyQt5 import QtCore, QtWidgets
30
31
32from openlp.core.common import AppLocation, Settings, translate, is_win, is_macosx
33
34log = logging.getLogger(__name__)
35
36ICU_COLLATOR = None
37DIGITS_OR_NONDIGITS = re.compile(r'\d+|\D+', re.UNICODE)
38
39
40class LanguageManager(object):
41 """
42 Helper for Language selection
43 """
44 __qm_list__ = {}
45 auto_language = False
46
47 @staticmethod
48 def get_translator(language):
49 """
50 Set up a translator to use in this instance of OpenLP
51
52 :param language: The language to load into the translator
53 """
54 if LanguageManager.auto_language:
55 language = QtCore.QLocale.system().name()
56 lang_path = AppLocation.get_directory(AppLocation.LanguageDir)
57 app_translator = QtCore.QTranslator()
58 app_translator.load(language, lang_path)
59 # A translator for buttons and other default strings provided by Qt.
60 if not is_win() and not is_macosx():
61 lang_path = QtCore.QLibraryInfo.location(QtCore.QLibraryInfo.TranslationsPath)
62 default_translator = QtCore.QTranslator()
63 default_translator.load('qt_%s' % language, lang_path)
64 return app_translator, default_translator
65
66 @staticmethod
67 def find_qm_files():
68 """
69 Find all available language files in this OpenLP install
70 """
71 log.debug('Translation files: {files}'.format(files=AppLocation.get_directory(AppLocation.LanguageDir)))
72 trans_dir = QtCore.QDir(AppLocation.get_directory(AppLocation.LanguageDir))
73 file_names = trans_dir.entryList(['*.qm'], QtCore.QDir.Files, QtCore.QDir.Name)
74 # Remove qm files from the list which start with "qt_".
75 file_names = [file_ for file_ in file_names if not file_.startswith('qt_')]
76 return list(map(trans_dir.filePath, file_names))
77
78 @staticmethod
79 def language_name(qm_file):
80 """
81 Load the language name from a language file
82
83 :param qm_file: The file to obtain the name from
84 """
85 translator = QtCore.QTranslator()
86 translator.load(qm_file)
87 return translator.translate('OpenLP.MainWindow', 'English', 'Please add the name of your language here')
88
89 @staticmethod
90 def get_language():
91 """
92 Retrieve a saved language to use from settings
93 """
94 language = Settings().value('core/language')
95 language = str(language)
96 log.info("Language file: '{language}' Loaded from conf file".format(language=language))
97 if re.match(r'[[].*[]]', language):
98 LanguageManager.auto_language = True
99 language = re.sub(r'[\[\]]', '', language)
100 return language
101
102 @staticmethod
103 def set_language(action, message=True):
104 """
105 Set the language to translate OpenLP into
106
107 :param action: The language menu option
108 :param message: Display the message option
109 """
110 language = 'en'
111 if action:
112 action_name = str(action.objectName())
113 if action_name == 'autoLanguageItem':
114 LanguageManager.auto_language = True
115 else:
116 LanguageManager.auto_language = False
117 qm_list = LanguageManager.get_qm_list()
118 language = str(qm_list[action_name])
119 if LanguageManager.auto_language:
120 language = '[{language}]'.format(language=language)
121 Settings().setValue('core/language', language)
122 log.info("Language file: '{language}' written to conf file".format(language=language))
123 if message:
124 QtWidgets.QMessageBox.information(None,
125 translate('OpenLP.LanguageManager', 'Language'),
126 translate('OpenLP.LanguageManager',
127 'Please restart OpenLP to use your new language setting.'))
128
129 @staticmethod
130 def init_qm_list():
131 """
132 Initialise the list of available translations
133 """
134 LanguageManager.__qm_list__ = {}
135 qm_files = LanguageManager.find_qm_files()
136 for counter, qmf in enumerate(qm_files):
137 reg_ex = QtCore.QRegExp("^.*i18n/(.*).qm")
138 if reg_ex.exactMatch(qmf):
139 name = '{regex}'.format(regex=reg_ex.cap(1))
140 # TODO: Test before converting to python3 string format
141 LanguageManager.__qm_list__['%#2i %s' % (counter + 1, LanguageManager.language_name(qmf))] = name
142
143 @staticmethod
144 def get_qm_list():
145 """
146 Return the list of available translations
147 """
148 if not LanguageManager.__qm_list__:
149 LanguageManager.init_qm_list()
150 return LanguageManager.__qm_list__
151
152
153def format_time(text, local_time):
154 """
155 Workaround for Python built-in time formatting function time.strftime().
156
157 time.strftime() accepts only ascii characters. This function accepts
158 unicode string and passes individual % placeholders to time.strftime().
159 This ensures only ascii characters are passed to time.strftime().
160
161 :param text: The text to be processed.
162 :param local_time: The time to be used to add to the string. This is a time object
163 """
164
165 def match_formatting(match):
166 """
167 Format the match
168 """
169 return local_time.strftime(match.group())
170
171 return re.sub(r'\%[a-zA-Z]', match_formatting, text)
172
173
174def get_locale_key(string):
175 """
176 Creates a key for case insensitive, locale aware string sorting.
177
178 :param string: The corresponding string.
179 """
180 string = string.lower()
181 # ICU is the prefered way to handle locale sort key, we fallback to locale.strxfrm which will work in most cases.
182 global ICU_COLLATOR
183 try:
184 if ICU_COLLATOR is None:
185 import icu
186 language = LanguageManager.get_language()
187 icu_locale = icu.Locale(language)
188 ICU_COLLATOR = icu.Collator.createInstance(icu_locale)
189 return ICU_COLLATOR.getSortKey(string)
190 except:
191 return locale.strxfrm(string).encode()
192
193
194def get_natural_key(string):
195 """
196 Generate a key for locale aware natural string sorting.
197
198 :param string: string to be sorted by
199 Returns a list of string compare keys and integers.
200 """
201 key = DIGITS_OR_NONDIGITS.findall(string)
202 key = [int(part) if part.isdigit() else get_locale_key(part) for part in key]
203 # Python 3 does not support comparison of different types anymore. So make sure, that we do not compare str
204 # and int.
205 if string and string[0].isdigit():
206 return [b''] + key
207 return key
0208
=== added file 'openlp/core/common/languages.py'
--- openlp/core/common/languages.py 1970-01-01 00:00:00 +0000
+++ openlp/core/common/languages.py 2017-01-08 22:05:36 +0000
@@ -0,0 +1,201 @@
1# -*- coding: utf-8 -*-
2# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
3
4###############################################################################
5# OpenLP - Open Source Lyrics Projection #
6# --------------------------------------------------------------------------- #
7# Copyright (c) 2008-2017 OpenLP Developers #
8# --------------------------------------------------------------------------- #
9# This program is free software; you can redistribute it and/or modify it #
10# under the terms of the GNU General Public License as published by the Free #
11# Software Foundation; version 2 of the License. #
12# #
13# This program is distributed in the hope that it will be useful, but WITHOUT #
14# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
15# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
16# more details. #
17# #
18# You should have received a copy of the GNU General Public License along #
19# with this program; if not, write to the Free Software Foundation, Inc., 59 #
20# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
21###############################################################################
22"""
23The :mod:`languages` module provides a list of language names with utility functions.
24"""
25from collections import namedtuple
26
27from openlp.core.common import translate
28
29
30Language = namedtuple('Language', ['id', 'name', 'code'])
31languages = sorted([
32 Language(1, translate('common.languages', '(Afan) Oromo', 'Language code: om'), 'om'),
33 Language(2, translate('common.languages', 'Abkhazian', 'Language code: ab'), 'ab'),
34 Language(3, translate('common.languages', 'Afar', 'Language code: aa'), 'aa'),
35 Language(4, translate('common.languages', 'Afrikaans', 'Language code: af'), 'af'),
36 Language(5, translate('common.languages', 'Albanian', 'Language code: sq'), 'sq'),
37 Language(6, translate('common.languages', 'Amharic', 'Language code: am'), 'am'),
38 Language(140, translate('common.languages', 'Amuzgo', 'Language code: amu'), 'amu'),
39 Language(152, translate('common.languages', 'Ancient Greek', 'Language code: grc'), 'grc'),
40 Language(7, translate('common.languages', 'Arabic', 'Language code: ar'), 'ar'),
41 Language(8, translate('common.languages', 'Armenian', 'Language code: hy'), 'hy'),
42 Language(9, translate('common.languages', 'Assamese', 'Language code: as'), 'as'),
43 Language(10, translate('common.languages', 'Aymara', 'Language code: ay'), 'ay'),
44 Language(11, translate('common.languages', 'Azerbaijani', 'Language code: az'), 'az'),
45 Language(12, translate('common.languages', 'Bashkir', 'Language code: ba'), 'ba'),
46 Language(13, translate('common.languages', 'Basque', 'Language code: eu'), 'eu'),
47 Language(14, translate('common.languages', 'Bengali', 'Language code: bn'), 'bn'),
48 Language(15, translate('common.languages', 'Bhutani', 'Language code: dz'), 'dz'),
49 Language(16, translate('common.languages', 'Bihari', 'Language code: bh'), 'bh'),
50 Language(17, translate('common.languages', 'Bislama', 'Language code: bi'), 'bi'),
51 Language(18, translate('common.languages', 'Breton', 'Language code: br'), 'br'),
52 Language(19, translate('common.languages', 'Bulgarian', 'Language code: bg'), 'bg'),
53 Language(20, translate('common.languages', 'Burmese', 'Language code: my'), 'my'),
54 Language(21, translate('common.languages', 'Byelorussian', 'Language code: be'), 'be'),
55 Language(142, translate('common.languages', 'Cakchiquel', 'Language code: cak'), 'cak'),
56 Language(22, translate('common.languages', 'Cambodian', 'Language code: km'), 'km'),
57 Language(23, translate('common.languages', 'Catalan', 'Language code: ca'), 'ca'),
58 Language(24, translate('common.languages', 'Chinese', 'Language code: zh'), 'zh'),
59 Language(141, translate('common.languages', 'Comaltepec Chinantec', 'Language code: cco'), 'cco'),
60 Language(25, translate('common.languages', 'Corsican', 'Language code: co'), 'co'),
61 Language(26, translate('common.languages', 'Croatian', 'Language code: hr'), 'hr'),
62 Language(27, translate('common.languages', 'Czech', 'Language code: cs'), 'cs'),
63 Language(28, translate('common.languages', 'Danish', 'Language code: da'), 'da'),
64 Language(29, translate('common.languages', 'Dutch', 'Language code: nl'), 'nl'),
65 Language(30, translate('common.languages', 'English', 'Language code: en'), 'en'),
66 Language(31, translate('common.languages', 'Esperanto', 'Language code: eo'), 'eo'),
67 Language(32, translate('common.languages', 'Estonian', 'Language code: et'), 'et'),
68 Language(33, translate('common.languages', 'Faeroese', 'Language code: fo'), 'fo'),
69 Language(34, translate('common.languages', 'Fiji', 'Language code: fj'), 'fj'),
70 Language(35, translate('common.languages', 'Finnish', 'Language code: fi'), 'fi'),
71 Language(36, translate('common.languages', 'French', 'Language code: fr'), 'fr'),
72 Language(37, translate('common.languages', 'Frisian', 'Language code: fy'), 'fy'),
73 Language(38, translate('common.languages', 'Galician', 'Language code: gl'), 'gl'),
74 Language(39, translate('common.languages', 'Georgian', 'Language code: ka'), 'ka'),
75 Language(40, translate('common.languages', 'German', 'Language code: de'), 'de'),
76 Language(41, translate('common.languages', 'Greek', 'Language code: el'), 'el'),
77 Language(42, translate('common.languages', 'Greenlandic', 'Language code: kl'), 'kl'),
78 Language(43, translate('common.languages', 'Guarani', 'Language code: gn'), 'gn'),
79 Language(44, translate('common.languages', 'Gujarati', 'Language code: gu'), 'gu'),
80 Language(143, translate('common.languages', 'Haitian Creole', 'Language code: ht'), 'ht'),
81 Language(45, translate('common.languages', 'Hausa', 'Language code: ha'), 'ha'),
82 Language(46, translate('common.languages', 'Hebrew (former iw)', 'Language code: he'), 'he'),
83 Language(144, translate('common.languages', 'Hiligaynon', 'Language code: hil'), 'hil'),
84 Language(47, translate('common.languages', 'Hindi', 'Language code: hi'), 'hi'),
85 Language(48, translate('common.languages', 'Hungarian', 'Language code: hu'), 'hu'),
86 Language(49, translate('common.languages', 'Icelandic', 'Language code: is'), 'is'),
87 Language(50, translate('common.languages', 'Indonesian (former in)', 'Language code: id'), 'id'),
88 Language(51, translate('common.languages', 'Interlingua', 'Language code: ia'), 'ia'),
89 Language(52, translate('common.languages', 'Interlingue', 'Language code: ie'), 'ie'),
90 Language(54, translate('common.languages', 'Inuktitut (Eskimo)', 'Language code: iu'), 'iu'),
91 Language(53, translate('common.languages', 'Inupiak', 'Language code: ik'), 'ik'),
92 Language(55, translate('common.languages', 'Irish', 'Language code: ga'), 'ga'),
93 Language(56, translate('common.languages', 'Italian', 'Language code: it'), 'it'),
94 Language(145, translate('common.languages', 'Jakalteko', 'Language code: jac'), 'jac'),
95 Language(57, translate('common.languages', 'Japanese', 'Language code: ja'), 'ja'),
96 Language(58, translate('common.languages', 'Javanese', 'Language code: jw'), 'jw'),
97 Language(150, translate('common.languages', 'K\'iche\'', 'Language code: quc'), 'quc'),
98 Language(59, translate('common.languages', 'Kannada', 'Language code: kn'), 'kn'),
99 Language(60, translate('common.languages', 'Kashmiri', 'Language code: ks'), 'ks'),
100 Language(61, translate('common.languages', 'Kazakh', 'Language code: kk'), 'kk'),
101 Language(146, translate('common.languages', 'Kekchí ', 'Language code: kek'), 'kek'),
102 Language(62, translate('common.languages', 'Kinyarwanda', 'Language code: rw'), 'rw'),
103 Language(63, translate('common.languages', 'Kirghiz', 'Language code: ky'), 'ky'),
104 Language(64, translate('common.languages', 'Kirundi', 'Language code: rn'), 'rn'),
105 Language(65, translate('common.languages', 'Korean', 'Language code: ko'), 'ko'),
106 Language(66, translate('common.languages', 'Kurdish', 'Language code: ku'), 'ku'),
107 Language(67, translate('common.languages', 'Laothian', 'Language code: lo'), 'lo'),
108 Language(68, translate('common.languages', 'Latin', 'Language code: la'), 'la'),
109 Language(69, translate('common.languages', 'Latvian, Lettish', 'Language code: lv'), 'lv'),
110 Language(70, translate('common.languages', 'Lingala', 'Language code: ln'), 'ln'),
111 Language(71, translate('common.languages', 'Lithuanian', 'Language code: lt'), 'lt'),
112 Language(72, translate('common.languages', 'Macedonian', 'Language code: mk'), 'mk'),
113 Language(73, translate('common.languages', 'Malagasy', 'Language code: mg'), 'mg'),
114 Language(74, translate('common.languages', 'Malay', 'Language code: ms'), 'ms'),
115 Language(75, translate('common.languages', 'Malayalam', 'Language code: ml'), 'ml'),
116 Language(76, translate('common.languages', 'Maltese', 'Language code: mt'), 'mt'),
117 Language(148, translate('common.languages', 'Mam', 'Language code: mam'), 'mam'),
118 Language(77, translate('common.languages', 'Maori', 'Language code: mi'), 'mi'),
119 Language(147, translate('common.languages', 'Maori', 'Language code: mri'), 'mri'),
120 Language(78, translate('common.languages', 'Marathi', 'Language code: mr'), 'mr'),
121 Language(79, translate('common.languages', 'Moldavian', 'Language code: mo'), 'mo'),
122 Language(80, translate('common.languages', 'Mongolian', 'Language code: mn'), 'mn'),
123 Language(149, translate('common.languages', 'Nahuatl', 'Language code: nah'), 'nah'),
124 Language(81, translate('common.languages', 'Nauru', 'Language code: na'), 'na'),
125 Language(82, translate('common.languages', 'Nepali', 'Language code: ne'), 'ne'),
126 Language(83, translate('common.languages', 'Norwegian', 'Language code: no'), 'no'),
127 Language(84, translate('common.languages', 'Occitan', 'Language code: oc'), 'oc'),
128 Language(85, translate('common.languages', 'Oriya', 'Language code: or'), 'or'),
129 Language(86, translate('common.languages', 'Pashto, Pushto', 'Language code: ps'), 'ps'),
130 Language(87, translate('common.languages', 'Persian', 'Language code: fa'), 'fa'),
131 Language(151, translate('common.languages', 'Plautdietsch', 'Language code: pdt'), 'pdt'),
132 Language(88, translate('common.languages', 'Polish', 'Language code: pl'), 'pl'),
133 Language(89, translate('common.languages', 'Portuguese', 'Language code: pt'), 'pt'),
134 Language(90, translate('common.languages', 'Punjabi', 'Language code: pa'), 'pa'),
135 Language(91, translate('common.languages', 'Quechua', 'Language code: qu'), 'qu'),
136 Language(92, translate('common.languages', 'Rhaeto-Romance', 'Language code: rm'), 'rm'),
137 Language(93, translate('common.languages', 'Romanian', 'Language code: ro'), 'ro'),
138 Language(94, translate('common.languages', 'Russian', 'Language code: ru'), 'ru'),
139 Language(95, translate('common.languages', 'Samoan', 'Language code: sm'), 'sm'),
140 Language(96, translate('common.languages', 'Sangro', 'Language code: sg'), 'sg'),
141 Language(97, translate('common.languages', 'Sanskrit', 'Language code: sa'), 'sa'),
142 Language(98, translate('common.languages', 'Scots Gaelic', 'Language code: gd'), 'gd'),
143 Language(99, translate('common.languages', 'Serbian', 'Language code: sr'), 'sr'),
144 Language(100, translate('common.languages', 'Serbo-Croatian', 'Language code: sh'), 'sh'),
145 Language(101, translate('common.languages', 'Sesotho', 'Language code: st'), 'st'),
146 Language(102, translate('common.languages', 'Setswana', 'Language code: tn'), 'tn'),
147 Language(103, translate('common.languages', 'Shona', 'Language code: sn'), 'sn'),
148 Language(104, translate('common.languages', 'Sindhi', 'Language code: sd'), 'sd'),
149 Language(105, translate('common.languages', 'Singhalese', 'Language code: si'), 'si'),
150 Language(106, translate('common.languages', 'Siswati', 'Language code: ss'), 'ss'),
151 Language(107, translate('common.languages', 'Slovak', 'Language code: sk'), 'sk'),
152 Language(108, translate('common.languages', 'Slovenian', 'Language code: sl'), 'sl'),
153 Language(109, translate('common.languages', 'Somali', 'Language code: so'), 'so'),
154 Language(110, translate('common.languages', 'Spanish', 'Language code: es'), 'es'),
155 Language(111, translate('common.languages', 'Sudanese', 'Language code: su'), 'su'),
156 Language(112, translate('common.languages', 'Swahili', 'Language code: sw'), 'sw'),
157 Language(113, translate('common.languages', 'Swedish', 'Language code: sv'), 'sv'),
158 Language(114, translate('common.languages', 'Tagalog', 'Language code: tl'), 'tl'),
159 Language(115, translate('common.languages', 'Tajik', 'Language code: tg'), 'tg'),
160 Language(116, translate('common.languages', 'Tamil', 'Language code: ta'), 'ta'),
161 Language(117, translate('common.languages', 'Tatar', 'Language code: tt'), 'tt'),
162 Language(118, translate('common.languages', 'Tegulu', 'Language code: te'), 'te'),
163 Language(119, translate('common.languages', 'Thai', 'Language code: th'), 'th'),
164 Language(120, translate('common.languages', 'Tibetan', 'Language code: bo'), 'bo'),
165 Language(121, translate('common.languages', 'Tigrinya', 'Language code: ti'), 'ti'),
166 Language(122, translate('common.languages', 'Tonga', 'Language code: to'), 'to'),
167 Language(123, translate('common.languages', 'Tsonga', 'Language code: ts'), 'ts'),
168 Language(124, translate('common.languages', 'Turkish', 'Language code: tr'), 'tr'),
169 Language(125, translate('common.languages', 'Turkmen', 'Language code: tk'), 'tk'),
170 Language(126, translate('common.languages', 'Twi', 'Language code: tw'), 'tw'),
171 Language(127, translate('common.languages', 'Uigur', 'Language code: ug'), 'ug'),
172 Language(128, translate('common.languages', 'Ukrainian', 'Language code: uk'), 'uk'),
173 Language(129, translate('common.languages', 'Urdu', 'Language code: ur'), 'ur'),
174 Language(153, translate('common.languages', 'Uspanteco', 'Language code: usp'), 'usp'),
175 Language(130, translate('common.languages', 'Uzbek', 'Language code: uz'), 'uz'),
176 Language(131, translate('common.languages', 'Vietnamese', 'Language code: vi'), 'vi'),
177 Language(132, translate('common.languages', 'Volapuk', 'Language code: vo'), 'vo'),
178 Language(133, translate('common.languages', 'Welch', 'Language code: cy'), 'cy'),
179 Language(134, translate('common.languages', 'Wolof', 'Language code: wo'), 'wo'),
180 Language(135, translate('common.languages', 'Xhosa', 'Language code: xh'), 'xh'),
181 Language(136, translate('common.languages', 'Yiddish (former ji)', 'Language code: yi'), 'yi'),
182 Language(137, translate('common.languages', 'Yoruba', 'Language code: yo'), 'yo'),
183 Language(138, translate('common.languages', 'Zhuang', 'Language code: za'), 'za'),
184 Language(139, translate('common.languages', 'Zulu', 'Language code: zu'), 'zu')
185], key=lambda language: language.name)
186
187
188def get_language(name):
189 """
190 Find the language by its name or code.
191
192 :param name: The name or abbreviation of the language.
193 :return: The first match as a Language namedtuple or None
194 """
195 if name:
196 name_lower = name.lower()
197 name_title = name_lower[:1].upper() + name_lower[1:]
198 for language in languages:
199 if language.name == name_title or language.code == name_lower:
200 return language
201 return None
0202
=== modified file 'openlp/core/common/openlpmixin.py'
--- openlp/core/common/openlpmixin.py 2016-12-31 11:05:48 +0000
+++ openlp/core/common/openlpmixin.py 2017-01-08 22:05:36 +0000
@@ -49,12 +49,13 @@
49 Code to added debug wrapper to work on called functions within a decorated class.49 Code to added debug wrapper to work on called functions within a decorated class.
50 """50 """
51 def wrapped(*args, **kwargs):51 def wrapped(*args, **kwargs):
52 parent.logger.debug("Entering %s" % func.__name__)52 parent.logger.debug("Entering {function}".format(function=func.__name__))
53 try:53 try:
54 return func(*args, **kwargs)54 return func(*args, **kwargs)
55 except Exception as e:55 except Exception as e:
56 if parent.logger.getEffectiveLevel() <= logging.ERROR:56 if parent.logger.getEffectiveLevel() <= logging.ERROR:
57 parent.logger.error('Exception in %s : %s' % (func.__name__, e))57 parent.logger.error('Exception in {function} : {error}'.format(function=func.__name__,
58 error=e))
58 raise e59 raise e
59 return wrapped60 return wrapped
6061
@@ -70,6 +71,12 @@
70 """71 """
71 self.logger.info(message)72 self.logger.info(message)
7273
74 def log_warning(self, message):
75 """
76 Common log warning handler
77 """
78 self.logger.warning(message)
79
73 def log_error(self, message):80 def log_error(self, message):
74 """81 """
75 Common log error handler which prints the calling path82 Common log error handler which prints the calling path
7683
=== modified file 'openlp/core/common/registry.py'
--- openlp/core/common/registry.py 2016-12-31 11:05:48 +0000
+++ openlp/core/common/registry.py 2017-01-08 22:05:36 +0000
@@ -55,6 +55,7 @@
55 registry = cls()55 registry = cls()
56 registry.service_list = {}56 registry.service_list = {}
57 registry.functions_list = {}57 registry.functions_list = {}
58 registry.working_flags = {}
58 # Allow the tests to remove Registry entries but not the live system59 # Allow the tests to remove Registry entries but not the live system
59 registry.running_under_test = 'nose' in sys.argv[0]60 registry.running_under_test = 'nose' in sys.argv[0]
60 registry.initialising = True61 registry.initialising = True
@@ -71,8 +72,8 @@
71 else:72 else:
72 if not self.initialising:73 if not self.initialising:
73 trace_error_handler(log)74 trace_error_handler(log)
74 log.error('Service %s not found in list' % key)75 log.error('Service {key} not found in list'.format(key=key))
75 raise KeyError('Service %s not found in list' % key)76 raise KeyError('Service {key} not found in list'.format(key=key))
7677
77 def register(self, key, reference):78 def register(self, key, reference):
78 """79 """
@@ -83,15 +84,14 @@
83 """84 """
84 if key in self.service_list:85 if key in self.service_list:
85 trace_error_handler(log)86 trace_error_handler(log)
86 log.error('Duplicate service exception %s' % key)87 log.error('Duplicate service exception {key}'.format(key=key))
87 raise KeyError('Duplicate service exception %s' % key)88 raise KeyError('Duplicate service exception {key}'.format(key=key))
88 else:89 else:
89 self.service_list[key] = reference90 self.service_list[key] = reference
9091
91 def remove(self, key):92 def remove(self, key):
92 """93 """
93 Removes the registry value from the list based on the key passed in (Only valid and active for testing94 Removes the registry value from the list based on the key passed in.
94 framework).
9595
96 :param key: The service to be deleted.96 :param key: The service to be deleted.
97 """97 """
@@ -140,8 +140,39 @@
140 except TypeError:140 except TypeError:
141 # Who has called me can help in debugging141 # Who has called me can help in debugging
142 trace_error_handler(log)142 trace_error_handler(log)
143 log.exception('Exception for function %s', function)143 log.exception('Exception for function {function}'.format(function=function))
144 else:144 else:
145 trace_error_handler(log)145 trace_error_handler(log)
146 log.error("Event %s called but not registered" % event)146 log.error("Event {event} called but not registered".format(event=event))
147 return results147 return results
148
149 def get_flag(self, key):
150 """
151 Extracts the working_flag value from the list based on the key passed in
152
153 :param key: The flag to be retrieved.
154 """
155 if key in self.working_flags:
156 return self.working_flags[key]
157 else:
158 trace_error_handler(log)
159 log.error('Working Flag {key} not found in list'.format(key=key))
160 raise KeyError('Working Flag {key} not found in list'.format(key=key))
161
162 def set_flag(self, key, reference):
163 """
164 Sets a working_flag based on the key passed in.
165
166 :param key: The working_flag to be created this is usually a major class like "renderer" or "main_window" .
167 :param reference: The data to be saved.
168 """
169 self.working_flags[key] = reference
170
171 def remove_flag(self, key):
172 """
173 Removes the working flags value from the list based on the key passed.
174
175 :param key: The working_flag to be deleted.
176 """
177 if key in self.working_flags:
178 del self.working_flags[key]
148179
=== modified file 'openlp/core/common/settings.py'
--- openlp/core/common/settings.py 2016-12-31 11:05:48 +0000
+++ openlp/core/common/settings.py 2017-01-08 22:05:36 +0000
@@ -26,7 +26,7 @@
26import logging26import logging
27import os27import os
2828
29from PyQt5 import QtCore, QtGui, QtWidgets29from PyQt5 import QtCore, QtGui
3030
31from openlp.core.common import ThemeLevel, SlideLimits, UiStrings, is_win, is_linux31from openlp.core.common import ThemeLevel, SlideLimits, UiStrings, is_win, is_linux
3232
@@ -107,10 +107,9 @@
107 __default_settings__ = {107 __default_settings__ = {
108 'advanced/add page break': False,108 'advanced/add page break': False,
109 'advanced/alternate rows': not is_win(),109 'advanced/alternate rows': not is_win(),
110 'advanced/autoscrolling': {'dist': 1, 'pos': 0},
110 'advanced/current media plugin': -1,111 'advanced/current media plugin': -1,
111 'advanced/data path': '',112 'advanced/data path': '',
112 'advanced/default color': '#ffffff',
113 'advanced/default image': ':/graphics/openlp-splash-screen.png',
114 # 7 stands for now, 0 to 6 is Monday to Sunday.113 # 7 stands for now, 0 to 6 is Monday to Sunday.
115 'advanced/default service day': 7,114 'advanced/default service day': 7,
116 'advanced/default service enabled': True,115 'advanced/default service enabled': True,
@@ -130,7 +129,9 @@
130 'advanced/recent file count': 4,129 'advanced/recent file count': 4,
131 'advanced/save current plugin': False,130 'advanced/save current plugin': False,
132 'advanced/slide limits': SlideLimits.End,131 'advanced/slide limits': SlideLimits.End,
132 'advanced/slide max height': -4,
133 'advanced/single click preview': False,133 'advanced/single click preview': False,
134 'advanced/single click service preview': False,
134 'advanced/x11 bypass wm': X11_BYPASS_DEFAULT,135 'advanced/x11 bypass wm': X11_BYPASS_DEFAULT,
135 'advanced/search as type': True,136 'advanced/search as type': True,
136 'crashreport/last directory': '',137 'crashreport/last directory': '',
@@ -140,6 +141,7 @@
140 'core/auto preview': False,141 'core/auto preview': False,
141 'core/audio start paused': True,142 'core/audio start paused': True,
142 'core/auto unblank': False,143 'core/auto unblank': False,
144 'core/click live slide to unblank': False,
143 'core/blank warning': False,145 'core/blank warning': False,
144 'core/ccli number': '',146 'core/ccli number': '',
145 'core/has run wizard': False,147 'core/has run wizard': False,
@@ -150,6 +152,9 @@
150 'core/save prompt': False,152 'core/save prompt': False,
151 'core/screen blank': False,153 'core/screen blank': False,
152 'core/show splash': True,154 'core/show splash': True,
155 'core/logo background color': '#ffffff',
156 'core/logo file': ':/graphics/openlp-splash-screen.png',
157 'core/logo hide on startup': False,
153 'core/songselect password': '',158 'core/songselect password': '',
154 'core/songselect username': '',159 'core/songselect username': '',
155 'core/update check': True,160 'core/update check': True,
@@ -178,13 +183,15 @@
178 'themes/wrap footer': False,183 'themes/wrap footer': False,
179 'user interface/live panel': True,184 'user interface/live panel': True,
180 'user interface/live splitter geometry': QtCore.QByteArray(),185 'user interface/live splitter geometry': QtCore.QByteArray(),
181 'user interface/lock panel': False,186 'user interface/lock panel': True,
182 'user interface/main window geometry': QtCore.QByteArray(),187 'user interface/main window geometry': QtCore.QByteArray(),
183 'user interface/main window position': QtCore.QPoint(0, 0),188 'user interface/main window position': QtCore.QPoint(0, 0),
184 'user interface/main window splitter geometry': QtCore.QByteArray(),189 'user interface/main window splitter geometry': QtCore.QByteArray(),
185 'user interface/main window state': QtCore.QByteArray(),190 'user interface/main window state': QtCore.QByteArray(),
186 'user interface/preview panel': True,191 'user interface/preview panel': True,
187 'user interface/preview splitter geometry': QtCore.QByteArray(),192 'user interface/preview splitter geometry': QtCore.QByteArray(),
193 'user interface/is preset layout': False,
194 'projector/show after wizard': False,
188 'projector/db type': 'sqlite',195 'projector/db type': 'sqlite',
189 'projector/db username': '',196 'projector/db username': '',
190 'projector/db password': '',197 'projector/db password': '',
@@ -205,7 +212,12 @@
205 # ('general/recent files', 'core/recent files', [(recent_files_conv, None)]),212 # ('general/recent files', 'core/recent files', [(recent_files_conv, None)]),
206 ('songs/search as type', 'advanced/search as type', []),213 ('songs/search as type', 'advanced/search as type', []),
207 ('media/players', 'media/players_temp', [(media_players_conv, None)]), # Convert phonon to system214 ('media/players', 'media/players_temp', [(media_players_conv, None)]), # Convert phonon to system
208 ('media/players_temp', 'media/players', []) # Move temp setting from above to correct setting215 ('media/players_temp', 'media/players', []), # Move temp setting from above to correct setting
216 ('advanced/default color', 'core/logo background color', []), # Default image renamed + moved to general > 2.4.
217 ('advanced/default image', 'core/logo file', []), # Default image renamed + moved to general after 2.4.
218 ('shortcuts/escapeItem', 'shortcuts/desktopScreenEnable', []), # Escape item was removed in 2.6.
219 ('shortcuts/offlineHelpItem', 'shortcuts/userManualItem', []), # Online and Offline help were combined in 2.6.
220 ('shortcuts/onlineHelpItem', 'shortcuts/userManualItem', []) # Online and Offline help were combined in 2.6.
209 ]221 ]
210222
211 @staticmethod223 @staticmethod
@@ -252,10 +264,10 @@
252 'shortcuts/blankScreen': [QtGui.QKeySequence(QtCore.Qt.Key_Period)],264 'shortcuts/blankScreen': [QtGui.QKeySequence(QtCore.Qt.Key_Period)],
253 'shortcuts/collapse': [QtGui.QKeySequence(QtCore.Qt.Key_Minus)],265 'shortcuts/collapse': [QtGui.QKeySequence(QtCore.Qt.Key_Minus)],
254 'shortcuts/desktopScreen': [QtGui.QKeySequence(QtCore.Qt.Key_D)],266 'shortcuts/desktopScreen': [QtGui.QKeySequence(QtCore.Qt.Key_D)],
267 'shortcuts/desktopScreenEnable': [QtGui.QKeySequence(QtCore.Qt.Key_Escape)],
255 'shortcuts/delete': [QtGui.QKeySequence(QtGui.QKeySequence.Delete)],268 'shortcuts/delete': [QtGui.QKeySequence(QtGui.QKeySequence.Delete)],
256 'shortcuts/down': [QtGui.QKeySequence(QtCore.Qt.Key_Down)],269 'shortcuts/down': [QtGui.QKeySequence(QtCore.Qt.Key_Down)],
257 'shortcuts/editSong': [],270 'shortcuts/editSong': [],
258 'shortcuts/escapeItem': [QtGui.QKeySequence(QtCore.Qt.Key_Escape)],
259 'shortcuts/expand': [QtGui.QKeySequence(QtCore.Qt.Key_Plus)],271 'shortcuts/expand': [QtGui.QKeySequence(QtCore.Qt.Key_Plus)],
260 'shortcuts/exportThemeItem': [],272 'shortcuts/exportThemeItem': [],
261 'shortcuts/fileNewItem': [QtGui.QKeySequence(QtGui.QKeySequence.New)],273 'shortcuts/fileNewItem': [QtGui.QKeySequence(QtGui.QKeySequence.New)],
@@ -264,6 +276,7 @@
264 'shortcuts/fileSaveItem': [QtGui.QKeySequence(QtGui.QKeySequence.Save)],276 'shortcuts/fileSaveItem': [QtGui.QKeySequence(QtGui.QKeySequence.Save)],
265 'shortcuts/fileOpenItem': [QtGui.QKeySequence(QtGui.QKeySequence.Open)],277 'shortcuts/fileOpenItem': [QtGui.QKeySequence(QtGui.QKeySequence.Open)],
266 'shortcuts/goLive': [],278 'shortcuts/goLive': [],
279 'shortcuts/userManualItem': [QtGui.QKeySequence(QtGui.QKeySequence.HelpContents)],
267 'shortcuts/importThemeItem': [],280 'shortcuts/importThemeItem': [],
268 'shortcuts/importBibleItem': [],281 'shortcuts/importBibleItem': [],
269 'shortcuts/listViewBiblesDeleteItem': [QtGui.QKeySequence(QtGui.QKeySequence.Delete)],282 'shortcuts/listViewBiblesDeleteItem': [QtGui.QKeySequence(QtGui.QKeySequence.Delete)],
@@ -324,8 +337,6 @@
324 QtGui.QKeySequence(QtCore.Qt.Key_PageDown)],337 QtGui.QKeySequence(QtCore.Qt.Key_PageDown)],
325 'shortcuts/nextService': [QtGui.QKeySequence(QtCore.Qt.Key_Right)],338 'shortcuts/nextService': [QtGui.QKeySequence(QtCore.Qt.Key_Right)],
326 'shortcuts/newService': [],339 'shortcuts/newService': [],
327 'shortcuts/offlineHelpItem': [QtGui.QKeySequence(QtGui.QKeySequence.HelpContents)],
328 'shortcuts/onlineHelpItem': [QtGui.QKeySequence(QtGui.QKeySequence.HelpContents)],
329 'shortcuts/openService': [],340 'shortcuts/openService': [],
330 'shortcuts/saveService': [],341 'shortcuts/saveService': [],
331 'shortcuts/previousItem_live': [QtGui.QKeySequence(QtCore.Qt.Key_Up),342 'shortcuts/previousItem_live': [QtGui.QKeySequence(QtCore.Qt.Key_Up),
@@ -370,6 +381,7 @@
370 'shortcuts/themeScreen': [QtGui.QKeySequence(QtCore.Qt.Key_T)],381 'shortcuts/themeScreen': [QtGui.QKeySequence(QtCore.Qt.Key_T)],
371 'shortcuts/toolsReindexItem': [],382 'shortcuts/toolsReindexItem': [],
372 'shortcuts/toolsFindDuplicates': [],383 'shortcuts/toolsFindDuplicates': [],
384 'shortcuts/toolsSongListReport': [],
373 'shortcuts/toolsAlertItem': [QtGui.QKeySequence(QtCore.Qt.Key_F7)],385 'shortcuts/toolsAlertItem': [QtGui.QKeySequence(QtCore.Qt.Key_F7)],
374 'shortcuts/toolsFirstTimeWizard': [],386 'shortcuts/toolsFirstTimeWizard': [],
375 'shortcuts/toolsOpenDataFolder': [],387 'shortcuts/toolsOpenDataFolder': [],
@@ -479,16 +491,16 @@
479 # Do NOT do this anywhere else!491 # Do NOT do this anywhere else!
480 settings = QtCore.QSettings(self.fileName(), Settings.IniFormat)492 settings = QtCore.QSettings(self.fileName(), Settings.IniFormat)
481 settings.beginGroup(plugin.settings_section)493 settings.beginGroup(plugin.settings_section)
482 if settings.contains('%s count' % plugin.name):494 if settings.contains('{name} count'.format(name=plugin.name)):
483 # Get the count.495 # Get the count.
484 list_count = int(settings.value('%s count' % plugin.name, 0))496 list_count = int(settings.value('{name} count'.format(name=plugin.name), 0))
485 if list_count:497 if list_count:
486 for counter in range(list_count):498 for counter in range(list_count):
487 # The keys were named e. g.: "image 0"499 # The keys were named e. g.: "image 0"
488 item = settings.value('%s %d' % (plugin.name, counter), '')500 item = settings.value('{name} {counter:d}'.format(name=plugin.name, counter=counter), '')
489 if item:501 if item:
490 files_list.append(item)502 files_list.append(item)
491 settings.remove('%s %d' % (plugin.name, counter))503 settings.remove('{name} {counter:d}'.format(name=plugin.name, counter=counter))
492 settings.remove('%s count' % plugin.name)504 settings.remove('{name} count'.format(name=plugin.name))
493 settings.endGroup()505 settings.endGroup()
494 return files_list506 return files_list
495507
=== modified file 'openlp/core/common/uistrings.py'
--- openlp/core/common/uistrings.py 2016-12-31 11:05:48 +0000
+++ openlp/core/common/uistrings.py 2017-01-08 22:05:36 +0000
@@ -23,6 +23,7 @@
23The :mod:`uistrings` module provides standard strings for OpenLP.23The :mod:`uistrings` module provides standard strings for OpenLP.
24"""24"""
25import logging25import logging
26import itertools
2627
27from openlp.core.common import translate28from openlp.core.common import translate
2829
@@ -52,10 +53,12 @@
52 self.About = translate('OpenLP.Ui', 'About')53 self.About = translate('OpenLP.Ui', 'About')
53 self.Add = translate('OpenLP.Ui', '&Add')54 self.Add = translate('OpenLP.Ui', '&Add')
54 self.AddGroup = translate('OpenLP.Ui', 'Add group')55 self.AddGroup = translate('OpenLP.Ui', 'Add group')
56 self.AddGroupDot = translate('OpenLP.Ui', 'Add group.')
55 self.Advanced = translate('OpenLP.Ui', 'Advanced')57 self.Advanced = translate('OpenLP.Ui', 'Advanced')
56 self.AllFiles = translate('OpenLP.Ui', 'All Files')58 self.AllFiles = translate('OpenLP.Ui', 'All Files')
57 self.Automatic = translate('OpenLP.Ui', 'Automatic')59 self.Automatic = translate('OpenLP.Ui', 'Automatic')
58 self.BackgroundColor = translate('OpenLP.Ui', 'Background Color')60 self.BackgroundColor = translate('OpenLP.Ui', 'Background Color')
61 self.BackgroundColorColon = translate('OpenLP.Ui', 'Background color:')
59 self.Bottom = translate('OpenLP.Ui', 'Bottom')62 self.Bottom = translate('OpenLP.Ui', 'Bottom')
60 self.Browse = translate('OpenLP.Ui', 'Browse...')63 self.Browse = translate('OpenLP.Ui', 'Browse...')
61 self.Cancel = translate('OpenLP.Ui', 'Cancel')64 self.Cancel = translate('OpenLP.Ui', 'Cancel')
@@ -67,7 +70,7 @@
67 self.Default = translate('OpenLP.Ui', 'Default')70 self.Default = translate('OpenLP.Ui', 'Default')
68 self.DefaultColor = translate('OpenLP.Ui', 'Default Color:')71 self.DefaultColor = translate('OpenLP.Ui', 'Default Color:')
69 self.DefaultServiceName = translate('OpenLP.Ui', 'Service %Y-%m-%d %H-%M',72 self.DefaultServiceName = translate('OpenLP.Ui', 'Service %Y-%m-%d %H-%M',
70 'This may not contain any of the following characters: /\\?*|<>\[\]":+\n'73 'This may not contain any of the following characters: /\\?*|<>[]":+\n'
71 'See http://docs.python.org/library/datetime'74 'See http://docs.python.org/library/datetime'
72 '.html#strftime-strptime-behavior for more information.')75 '.html#strftime-strptime-behavior for more information.')
73 self.Delete = translate('OpenLP.Ui', '&Delete')76 self.Delete = translate('OpenLP.Ui', '&Delete')
@@ -79,7 +82,8 @@
79 self.Export = translate('OpenLP.Ui', 'Export')82 self.Export = translate('OpenLP.Ui', 'Export')
80 self.File = translate('OpenLP.Ui', 'File')83 self.File = translate('OpenLP.Ui', 'File')
81 self.FileNotFound = translate('OpenLP.Ui', 'File Not Found')84 self.FileNotFound = translate('OpenLP.Ui', 'File Not Found')
82 self.FileNotFoundMessage = translate('OpenLP.Ui', 'File %s not found.\nPlease try selecting it individually.')85 self.FileNotFoundMessage = translate('OpenLP.Ui',
86 'File {name} not found.\nPlease try selecting it individually.')
83 self.FontSizePtUnit = translate('OpenLP.Ui', 'pt', 'Abbreviated font pointsize unit')87 self.FontSizePtUnit = translate('OpenLP.Ui', 'pt', 'Abbreviated font pointsize unit')
84 self.Help = translate('OpenLP.Ui', 'Help')88 self.Help = translate('OpenLP.Ui', 'Help')
85 self.Hours = translate('OpenLP.Ui', 'h', 'The abbreviated unit for hours')89 self.Hours = translate('OpenLP.Ui', 'h', 'The abbreviated unit for hours')
@@ -108,9 +112,10 @@
108 self.NFSp = translate('OpenLP.Ui', 'No Files Selected', 'Plural')112 self.NFSp = translate('OpenLP.Ui', 'No Files Selected', 'Plural')
109 self.NISs = translate('OpenLP.Ui', 'No Item Selected', 'Singular')113 self.NISs = translate('OpenLP.Ui', 'No Item Selected', 'Singular')
110 self.NISp = translate('OpenLP.Ui', 'No Items Selected', 'Plural')114 self.NISp = translate('OpenLP.Ui', 'No Items Selected', 'Plural')
115 self.NoResults = translate('OpenLP.Ui', 'No Search Results')
111 self.OLP = translate('OpenLP.Ui', 'OpenLP')116 self.OLP = translate('OpenLP.Ui', 'OpenLP')
112 self.OLPV2 = "%s %s" % (self.OLP, "2")117 self.OLPV2 = "{name} {version}".format(name=self.OLP, version="2")
113 self.OLPV2x = "%s %s" % (self.OLP, "2.4")118 self.OLPV2x = "{name} {version}".format(name=self.OLP, version="2.4")
114 self.OpenLPStart = translate('OpenLP.Ui', 'OpenLP is already running. Do you wish to continue?')119 self.OpenLPStart = translate('OpenLP.Ui', 'OpenLP is already running. Do you wish to continue?')
115 self.OpenService = translate('OpenLP.Ui', 'Open service.')120 self.OpenService = translate('OpenLP.Ui', 'Open service.')
116 self.PlaySlidesInLoop = translate('OpenLP.Ui', 'Play Slides in Loop')121 self.PlaySlidesInLoop = translate('OpenLP.Ui', 'Play Slides in Loop')
@@ -135,10 +140,12 @@
135 self.Settings = translate('OpenLP.Ui', 'Settings')140 self.Settings = translate('OpenLP.Ui', 'Settings')
136 self.SaveService = translate('OpenLP.Ui', 'Save Service')141 self.SaveService = translate('OpenLP.Ui', 'Save Service')
137 self.Service = translate('OpenLP.Ui', 'Service')142 self.Service = translate('OpenLP.Ui', 'Service')
143 self.ShortResults = translate('OpenLP.Ui', 'Please type more text to use \'Search As You Type\'')
138 self.Split = translate('OpenLP.Ui', 'Optional &Split')144 self.Split = translate('OpenLP.Ui', 'Optional &Split')
139 self.SplitToolTip = translate('OpenLP.Ui',145 self.SplitToolTip = translate('OpenLP.Ui',
140 'Split a slide into two only if it does not fit on the screen as one slide.')146 'Split a slide into two only if it does not fit on the screen as one slide.')
141 self.StartTimeCode = translate('OpenLP.Ui', 'Start %s')147 # TODO: WHERE is this used at? cannot find where it's used at in code.
148 self.StartTimeCode = translate('OpenLP.Ui', 'Start {code}')
142 self.StopPlaySlidesInLoop = translate('OpenLP.Ui', 'Stop Play Slides in Loop')149 self.StopPlaySlidesInLoop = translate('OpenLP.Ui', 'Stop Play Slides in Loop')
143 self.StopPlaySlidesToEnd = translate('OpenLP.Ui', 'Stop Play Slides to End')150 self.StopPlaySlidesToEnd = translate('OpenLP.Ui', 'Stop Play Slides to End')
144 self.Theme = translate('OpenLP.Ui', 'Theme', 'Singular')151 self.Theme = translate('OpenLP.Ui', 'Theme', 'Singular')
@@ -151,3 +158,31 @@
151 self.Version = translate('OpenLP.Ui', 'Version')158 self.Version = translate('OpenLP.Ui', 'Version')
152 self.View = translate('OpenLP.Ui', 'View')159 self.View = translate('OpenLP.Ui', 'View')
153 self.ViewMode = translate('OpenLP.Ui', 'View Mode')160 self.ViewMode = translate('OpenLP.Ui', 'View Mode')
161 self.Video = translate('OpenLP.Ui', 'Video')
162 self.BibleShortSearchTitle = translate('OpenLP.Ui', 'Search is Empty or too Short')
163 self.BibleShortSearch = translate('OpenLP.Ui', '<strong>The search you have entered is empty or shorter '
164 'than 3 characters long.</strong><br><br>Please try again with '
165 'a longer search.')
166 self.BibleNoBiblesTitle = translate('OpenLP.Ui', 'No Bibles Available')
167 self.BibleNoBibles = translate('OpenLP.Ui', '<strong>There are no Bibles currently installed.</strong><br><br>'
168 'Please use the Import Wizard to install one or more Bibles.')
169 book_chapter = translate('OpenLP.Ui', 'Book Chapter')
170 chapter = translate('OpenLP.Ui', 'Chapter')
171 verse = translate('OpenLP.Ui', 'Verse')
172 gap = ' | '
173 psalm = translate('OpenLP.Ui', 'Psalm')
174 may_shorten = translate('OpenLP.Ui', 'Book names may be shortened from full names, for an example Ps 23 = '
175 'Psalm 23')
176 bible_scripture_items = \
177 [book_chapter, gap, psalm, ' 23<br>',
178 book_chapter, '%(range)s', chapter, gap, psalm, ' 23%(range)s24<br>',
179 book_chapter, '%(verse)s', verse, '%(range)s', verse, gap, psalm, ' 23%(verse)s1%(range)s2<br>',
180 book_chapter, '%(verse)s', verse, '%(range)s', verse, '%(list)s', verse, '%(range)s', verse, gap, psalm,
181 ' 23%(verse)s1%(range)s2%(list)s5%(range)s6<br>',
182 book_chapter, '%(verse)s', verse, '%(range)s', verse, '%(list)s', chapter, '%(verse)s', verse, '%(range)s',
183 verse, gap, psalm, ' 23%(verse)s1%(range)s2%(list)s24%(verse)s1%(range)s3<br>',
184 book_chapter, '%(verse)s', verse, '%(range)s', chapter, '%(verse)s', verse, gap, psalm,
185 ' 23%(verse)s1%(range)s24%(verse)s1<br><br>', may_shorten]
186 itertools.chain.from_iterable(itertools.repeat(strings, 1) if isinstance(strings, str)
187 else strings for strings in bible_scripture_items)
188 self.BibleScriptureError = ''.join(str(joined) for joined in bible_scripture_items)
154189
=== added file 'openlp/core/common/versionchecker.py'
--- openlp/core/common/versionchecker.py 1970-01-01 00:00:00 +0000
+++ openlp/core/common/versionchecker.py 2017-01-08 22:05:36 +0000
@@ -0,0 +1,173 @@
1import logging
2import os
3import platform
4import sys
5import time
6import urllib.error
7import urllib.parse
8import urllib.request
9from datetime import datetime
10from distutils.version import LooseVersion
11from subprocess import Popen, PIPE
12
13from PyQt5 import QtCore
14
15from openlp.core.common import AppLocation, Settings
16
17log = logging.getLogger(__name__)
18
19APPLICATION_VERSION = {}
20CONNECTION_TIMEOUT = 30
21CONNECTION_RETRIES = 2
22
23
24class VersionThread(QtCore.QThread):
25 """
26 A special Qt thread class to fetch the version of OpenLP from the website.
27 This is threaded so that it doesn't affect the loading time of OpenLP.
28 """
29 def __init__(self, main_window):
30 """
31 Constructor for the thread class.
32
33 :param main_window: The main window Object.
34 """
35 log.debug("VersionThread - Initialise")
36 super(VersionThread, self).__init__(None)
37 self.main_window = main_window
38
39 def run(self):
40 """
41 Run the thread.
42 """
43 self.sleep(1)
44 log.debug('Version thread - run')
45 app_version = get_application_version()
46 version = check_latest_version(app_version)
47 log.debug("Versions {version1} and {version2} ".format(version1=LooseVersion(str(version)),
48 version2=LooseVersion(str(app_version['full']))))
49 if LooseVersion(str(version)) > LooseVersion(str(app_version['full'])):
50 self.main_window.openlp_version_check.emit('{version}'.format(version=version))
51
52
53def get_application_version():
54 """
55 Returns the application version of the running instance of OpenLP::
56
57 {'full': '1.9.4-bzr1249', 'version': '1.9.4', 'build': 'bzr1249'}
58 """
59 global APPLICATION_VERSION
60 if APPLICATION_VERSION:
61 return APPLICATION_VERSION
62 if '--dev-version' in sys.argv or '-d' in sys.argv:
63 # NOTE: The following code is a duplicate of the code in setup.py. Any fix applied here should also be applied
64 # there.
65
66 # Get the revision of this tree.
67 bzr = Popen(('bzr', 'revno'), stdout=PIPE)
68 tree_revision, error = bzr.communicate()
69 tree_revision = tree_revision.decode()
70 code = bzr.wait()
71 if code != 0:
72 raise Exception('Error running bzr log')
73
74 # Get all tags.
75 bzr = Popen(('bzr', 'tags'), stdout=PIPE)
76 output, error = bzr.communicate()
77 code = bzr.wait()
78 if code != 0:
79 raise Exception('Error running bzr tags')
80 tags = list(map(bytes.decode, output.splitlines()))
81 if not tags:
82 tag_version = '0.0.0'
83 tag_revision = '0'
84 else:
85 # Remove any tag that has "?" as revision number. A "?" as revision number indicates, that this tag is from
86 # another series.
87 tags = [tag for tag in tags if tag.split()[-1].strip() != '?']
88 # Get the last tag and split it in a revision and tag name.
89 tag_version, tag_revision = tags[-1].split()
90 # If they are equal, then this tree is tarball with the source for the release. We do not want the revision
91 # number in the full version.
92 if tree_revision == tag_revision:
93 full_version = tag_version.strip()
94 else:
95 full_version = '{tag}-bzr{tree}'.format(tag=tag_version.strip(), tree=tree_revision.strip())
96 else:
97 # We're not running the development version, let's use the file.
98 file_path = AppLocation.get_directory(AppLocation.VersionDir)
99 file_path = os.path.join(file_path, '.version')
100 version_file = None
101 try:
102 version_file = open(file_path, 'r')
103 full_version = str(version_file.read()).rstrip()
104 except IOError:
105 log.exception('Error in version file.')
106 full_version = '0.0.0-bzr000'
107 finally:
108 if version_file:
109 version_file.close()
110 bits = full_version.split('-')
111 APPLICATION_VERSION = {
112 'full': full_version,
113 'version': bits[0],
114 'build': bits[1] if len(bits) > 1 else None
115 }
116 if APPLICATION_VERSION['build']:
117 log.info('Openlp version {version} build {build}'.format(version=APPLICATION_VERSION['version'],
118 build=APPLICATION_VERSION['build']))
119 else:
120 log.info('Openlp version {version}'.format(version=APPLICATION_VERSION['version']))
121 return APPLICATION_VERSION
122
123
124def check_latest_version(current_version):
125 """
126 Check the latest version of OpenLP against the version file on the OpenLP
127 site.
128
129 **Rules around versions and version files:**
130
131 * If a version number has a build (i.e. -bzr1234), then it is a nightly.
132 * If a version number's minor version is an odd number, it is a development release.
133 * If a version number's minor version is an even number, it is a stable release.
134
135 :param current_version: The current version of OpenLP.
136 """
137 version_string = current_version['full']
138 # set to prod in the distribution config file.
139 settings = Settings()
140 settings.beginGroup('core')
141 last_test = settings.value('last version test')
142 this_test = str(datetime.now().date())
143 settings.setValue('last version test', this_test)
144 settings.endGroup()
145 if last_test != this_test:
146 if current_version['build']:
147 req = urllib.request.Request('http://www.openlp.org/files/nightly_version.txt')
148 else:
149 version_parts = current_version['version'].split('.')
150 if int(version_parts[1]) % 2 != 0:
151 req = urllib.request.Request('http://www.openlp.org/files/dev_version.txt')
152 else:
153 req = urllib.request.Request('http://www.openlp.org/files/version.txt')
154 req.add_header('User-Agent', 'OpenLP/{version} {system}/{release}; '.format(version=current_version['full'],
155 system=platform.system(),
156 release=platform.release()))
157 remote_version = None
158 retries = 0
159 while True:
160 try:
161 remote_version = str(urllib.request.urlopen(req, None,
162 timeout=CONNECTION_TIMEOUT).read().decode()).strip()
163 except (urllib.error.URLError, ConnectionError):
164 if retries > CONNECTION_RETRIES:
165 log.exception('Failed to download the latest OpenLP version file')
166 else:
167 retries += 1
168 time.sleep(0.1)
169 continue
170 break
171 if remote_version:
172 version_string = remote_version
173 return version_string
0174
=== modified file 'openlp/core/lib/__init__.py'
--- openlp/core/lib/__init__.py 2016-12-31 11:05:48 +0000
+++ openlp/core/lib/__init__.py 2017-01-08 22:05:36 +0000
@@ -24,13 +24,12 @@
24OpenLP work.24OpenLP work.
25"""25"""
2626
27from distutils.version import LooseVersion
28import logging27import logging
29import os28import os
29from distutils.version import LooseVersion
3030
31from PyQt5 import QtCore, QtGui, Qt, QtWidgets31from PyQt5 import QtCore, QtGui, Qt, QtWidgets
3232
33
34from openlp.core.common import translate33from openlp.core.common import translate
3534
36log = logging.getLogger(__name__ + '.__init__')35log = logging.getLogger(__name__ + '.__init__')
@@ -55,9 +54,13 @@
5554
56 ``Theme``55 ``Theme``
57 This says, that the image is used by a theme.56 This says, that the image is used by a theme.
57
58 ``CommandPlugins``
59 This states that an image is being used by a command plugin.
58 """60 """
59 ImagePlugin = 161 ImagePlugin = 1
60 Theme = 262 Theme = 2
63 CommandPlugins = 3
6164
6265
63class MediaType(object):66class MediaType(object):
@@ -92,12 +95,12 @@
92 content = None95 content = None
93 try:96 try:
94 file_handle = open(text_file, 'r', encoding='utf-8')97 file_handle = open(text_file, 'r', encoding='utf-8')
95 if not file_handle.read(3) == '\xEF\xBB\xBF':98 if file_handle.read(3) != '\xEF\xBB\xBF':
96 # no BOM was found99 # no BOM was found
97 file_handle.seek(0)100 file_handle.seek(0)
98 content = file_handle.read()101 content = file_handle.read()
99 except (IOError, UnicodeError):102 except (IOError, UnicodeError):
100 log.exception('Failed to open text file %s' % text_file)103 log.exception('Failed to open text file {text}'.format(text=text_file))
101 finally:104 finally:
102 if file_handle:105 if file_handle:
103 file_handle.close()106 file_handle.close()
@@ -126,16 +129,16 @@
126 location like ``/path/to/file.png``. However, the **recommended** way is to specify a resource string.129 location like ``/path/to/file.png``. However, the **recommended** way is to specify a resource string.
127 :return: The build icon.130 :return: The build icon.
128 """131 """
132 if isinstance(icon, QtGui.QIcon):
133 return icon
134 pix_map = None
129 button_icon = QtGui.QIcon()135 button_icon = QtGui.QIcon()
130 if isinstance(icon, QtGui.QIcon):136 if isinstance(icon, str):
131 button_icon = icon137 pix_map = QtGui.QPixmap(icon)
132 elif isinstance(icon, str):
133 if icon.startswith(':/'):
134 button_icon.addPixmap(QtGui.QPixmap(icon), QtGui.QIcon.Normal, QtGui.QIcon.Off)
135 else:
136 button_icon.addPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(icon)), QtGui.QIcon.Normal, QtGui.QIcon.Off)
137 elif isinstance(icon, QtGui.QImage):138 elif isinstance(icon, QtGui.QImage):
138 button_icon.addPixmap(QtGui.QPixmap.fromImage(icon), QtGui.QIcon.Normal, QtGui.QIcon.Off)139 pix_map = QtGui.QPixmap.fromImage(icon)
140 if pix_map:
141 button_icon.addPixmap(pix_map, QtGui.QIcon.Normal, QtGui.QIcon.Off)
139 return button_icon142 return button_icon
140143
141144
@@ -174,10 +177,30 @@
174 ext = os.path.splitext(thumb_path)[1].lower()177 ext = os.path.splitext(thumb_path)[1].lower()
175 reader = QtGui.QImageReader(image_path)178 reader = QtGui.QImageReader(image_path)
176 if size is None:179 if size is None:
177 ratio = reader.size().width() / reader.size().height()180 # No size given; use default height of 88
181 if reader.size().isEmpty():
182 ratio = 1
183 else:
184 ratio = reader.size().width() / reader.size().height()
178 reader.setScaledSize(QtCore.QSize(int(ratio * 88), 88))185 reader.setScaledSize(QtCore.QSize(int(ratio * 88), 88))
179 else:186 elif size.isValid():
187 # Complete size given
180 reader.setScaledSize(size)188 reader.setScaledSize(size)
189 else:
190 # Invalid size given
191 if reader.size().isEmpty():
192 ratio = 1
193 else:
194 ratio = reader.size().width() / reader.size().height()
195 if size.width() >= 0:
196 # Valid width; scale height
197 reader.setScaledSize(QtCore.QSize(size.width(), int(size.width() / ratio)))
198 elif size.height() >= 0:
199 # Valid height; scale width
200 reader.setScaledSize(QtCore.QSize(int(ratio * size.height()), size.height()))
201 else:
202 # Invalid; use default height of 88
203 reader.setScaledSize(QtCore.QSize(int(ratio * 88), 88))
181 thumb = reader.read()204 thumb = reader.read()
182 thumb.save(thumb_path, ext[1:])205 thumb.save(thumb_path, ext[1:])
183 if not return_icon:206 if not return_icon:
@@ -287,46 +310,34 @@
287310
288def create_separated_list(string_list):311def create_separated_list(string_list):
289 """312 """
290 Returns a string that represents a join of a list of strings with a localized separator. This function corresponds313 Returns a string that represents a join of a list of strings with a localized separator.
291314 Localized separation will be done via the translate() function by the translators.
292 to QLocale::createSeparatedList which was introduced in Qt 4.8 and implements the algorithm from315
293 http://www.unicode.org/reports/tr35/#ListPatterns316 :param string_list: List of unicode strings
294317 :return: Formatted string
295 :param string_list: List of unicode strings
296 """318 """
297 if LooseVersion(Qt.PYQT_VERSION_STR) >= LooseVersion('4.9') and LooseVersion(Qt.qVersion()) >= LooseVersion('4.8'):319 list_length = len(string_list)
298 return QtCore.QLocale().createSeparatedList(string_list)320 if list_length == 1:
299 if not string_list:321 list_to_string = string_list[0]
300 return ''322 elif list_length == 2:
301 elif len(string_list) == 1:323 list_to_string = translate('OpenLP.core.lib', '{one} and {two}').format(one=string_list[0], two=string_list[1])
302 return string_list[0]324 elif list_length > 2:
303 elif len(string_list) == 2:325 list_to_string = translate('OpenLP.core.lib', '{first} and {last}').format(first=', '.join(string_list[:-1]),
304 return translate('OpenLP.core.lib', '%s and %s',326 last=string_list[-1])
305 'Locale list separator: 2 items') % (string_list[0], string_list[1])
306 else:327 else:
307 merged = translate('OpenLP.core.lib', '%s, and %s',328 list_to_string = ''
308 'Locale list separator: end') % (string_list[-2], string_list[-1])329 return list_to_string
309 for index in reversed(list(range(1, len(string_list) - 2))):330
310 merged = translate('OpenLP.core.lib', '%s, %s',331
311 'Locale list separator: middle') % (string_list[index], merged)
312 return translate('OpenLP.core.lib', '%s, %s', 'Locale list separator: start') % (string_list[0], merged)
313
314
315from .colorbutton import ColorButton
316from .exceptions import ValidationError332from .exceptions import ValidationError
317from .filedialog import FileDialog333from .filedialog import FileDialog
318from .screen import ScreenList334from .screen import ScreenList
319from .listwidgetwithdnd import ListWidgetWithDnD
320from .treewidgetwithdnd import TreeWidgetWithDnD
321from .formattingtags import FormattingTags335from .formattingtags import FormattingTags
322from .spelltextedit import SpellTextEdit
323from .plugin import PluginStatus, StringContent, Plugin336from .plugin import PluginStatus, StringContent, Plugin
324from .pluginmanager import PluginManager337from .pluginmanager import PluginManager
325from .settingstab import SettingsTab338from .settingstab import SettingsTab
326from .serviceitem import ServiceItem, ServiceItemType, ItemCapabilities339from .serviceitem import ServiceItem, ServiceItemType, ItemCapabilities
327from .htmlbuilder import build_html, build_lyrics_format_css, build_lyrics_outline_css340from .htmlbuilder import build_html, build_lyrics_format_css, build_lyrics_outline_css
328from .toolbar import OpenLPToolbar
329from .dockwidget import OpenLPDockWidget
330from .imagemanager import ImageManager341from .imagemanager import ImageManager
331from .renderer import Renderer342from .renderer import Renderer
332from .mediamanageritem import MediaManagerItem343from .mediamanageritem import MediaManagerItem
333344
=== renamed file 'openlp/core/lib/colorbutton.py' => 'openlp/core/lib/colorbutton.py.THIS'
=== modified file 'openlp/core/lib/db.py'
--- openlp/core/lib/db.py 2016-12-31 11:05:48 +0000
+++ openlp/core/lib/db.py 2017-01-08 22:05:36 +0000
@@ -34,9 +34,8 @@
34from alembic.migration import MigrationContext34from alembic.migration import MigrationContext
35from alembic.operations import Operations35from alembic.operations import Operations
3636
37from openlp.core.common import AppLocation, Settings, translate37from openlp.core.common import AppLocation, Settings, translate, delete_file
38from openlp.core.lib.ui import critical_error_message_box38from openlp.core.lib.ui import critical_error_message_box
39from openlp.core.utils import delete_file
4039
41log = logging.getLogger(__name__)40log = logging.getLogger(__name__)
4241
@@ -69,9 +68,11 @@
69 :return: The path to the database as type str68 :return: The path to the database as type str
70 """69 """
71 if db_file_name is None:70 if db_file_name is None:
72 return 'sqlite:///%s/%s.sqlite' % (AppLocation.get_section_data_path(plugin_name), plugin_name)71 return 'sqlite:///{path}/{plugin}.sqlite'.format(path=AppLocation.get_section_data_path(plugin_name),
72 plugin=plugin_name)
73 else:73 else:
74 return 'sqlite:///%s/%s' % (AppLocation.get_section_data_path(plugin_name), db_file_name)74 return 'sqlite:///{path}/{name}'.format(path=AppLocation.get_section_data_path(plugin_name),
75 name=db_file_name)
7576
7677
77def handle_db_error(plugin_name, db_file_name):78def handle_db_error(plugin_name, db_file_name):
@@ -83,10 +84,10 @@
83 :return: None84 :return: None
84 """85 """
85 db_path = get_db_path(plugin_name, db_file_name)86 db_path = get_db_path(plugin_name, db_file_name)
86 log.exception('Error loading database: %s', db_path)87 log.exception('Error loading database: {db}'.format(db=db_path))
87 critical_error_message_box(translate('OpenLP.Manager', 'Database Error'),88 critical_error_message_box(translate('OpenLP.Manager', 'Database Error'),
88 translate('OpenLP.Manager', 'OpenLP cannot load your database.\n\nDatabase: %s')89 translate('OpenLP.Manager',
89 % db_path)90 'OpenLP cannot load your database.\n\nDatabase: {db}').format(db=db_path))
9091
9192
92def init_url(plugin_name, db_file_name=None):93def init_url(plugin_name, db_file_name=None):
@@ -102,10 +103,11 @@
102 if db_type == 'sqlite':103 if db_type == 'sqlite':
103 db_url = get_db_path(plugin_name, db_file_name)104 db_url = get_db_path(plugin_name, db_file_name)
104 else:105 else:
105 db_url = '%s://%s:%s@%s/%s' % (db_type, urlquote(settings.value('db username')),106 db_url = '{type}://{user}:{password}@{host}/{db}'.format(type=db_type,
106 urlquote(settings.value('db password')),107 user=urlquote(settings.value('db username')),
107 urlquote(settings.value('db hostname')),108 password=urlquote(settings.value('db password')),
108 urlquote(settings.value('db database')))109 host=urlquote(settings.value('db hostname')),
110 db=urlquote(settings.value('db database')))
109 settings.endGroup()111 settings.endGroup()
110 return db_url112 return db_url
111113
@@ -120,6 +122,21 @@
120 return Operations(context)122 return Operations(context)
121123
122124
125class BaseModel(object):
126 """
127 BaseModel provides a base object with a set of generic functions
128 """
129 @classmethod
130 def populate(cls, **kwargs):
131 """
132 Creates an instance of a class and populates it, returning the instance
133 """
134 instance = cls()
135 for key, value in kwargs.items():
136 instance.__setattr__(key, value)
137 return instance
138
139
123def upgrade_db(url, upgrade):140def upgrade_db(url, upgrade):
124 """141 """
125 Upgrade a database.142 Upgrade a database.
@@ -158,10 +175,10 @@
158 return version, upgrade.__version__175 return version, upgrade.__version__
159 version += 1176 version += 1
160 try:177 try:
161 while hasattr(upgrade, 'upgrade_%d' % version):178 while hasattr(upgrade, 'upgrade_{version:d}'.format(version=version)):
162 log.debug('Running upgrade_%d', version)179 log.debug('Running upgrade_{version:d}'.format(version=version))
163 try:180 try:
164 upgrade_func = getattr(upgrade, 'upgrade_%d' % version)181 upgrade_func = getattr(upgrade, 'upgrade_{version:d}'.format(version=version))
165 upgrade_func(session, metadata)182 upgrade_func(session, metadata)
166 session.commit()183 session.commit()
167 # Update the version number AFTER a commit so that we are sure the previous transaction happened184 # Update the version number AFTER a commit so that we are sure the previous transaction happened
@@ -169,16 +186,16 @@
169 session.commit()186 session.commit()
170 version += 1187 version += 1
171 except (SQLAlchemyError, DBAPIError):188 except (SQLAlchemyError, DBAPIError):
172 log.exception('Could not run database upgrade script "upgrade_%s", upgrade process has been halted.',189 log.exception('Could not run database upgrade script '
173 version)190 '"upgrade_{version:d}", upgrade process has been halted.'.format(version=version))
174 break191 break
175 except (SQLAlchemyError, DBAPIError):192 except (SQLAlchemyError, DBAPIError):
176 version_meta = Metadata.populate(key='version', value=int(upgrade.__version__))193 version_meta = Metadata.populate(key='version', value=int(upgrade.__version__))
177 session.commit()194 session.commit()
178 upgrade_version = upgrade.__version__195 upgrade_version = upgrade.__version__
179 version_meta = int(version_meta.value)196 version = int(version_meta.value)
180 session.close()197 session.close()
181 return version_meta, upgrade_version198 return version, upgrade_version
182199
183200
184def delete_database(plugin_name, db_file_name=None):201def delete_database(plugin_name, db_file_name=None):
@@ -195,21 +212,6 @@
195 return delete_file(db_file_path)212 return delete_file(db_file_path)
196213
197214
198class BaseModel(object):
199 """
200 BaseModel provides a base object with a set of generic functions
201 """
202 @classmethod
203 def populate(cls, **kwargs):
204 """
205 Creates an instance of a class and populates it, returning the instance
206 """
207 instance = cls()
208 for key, value in kwargs.items():
209 instance.__setattr__(key, value)
210 return instance
211
212
213class Manager(object):215class Manager(object):
214 """216 """
215 Provide generic object persistence management217 Provide generic object persistence management
@@ -243,9 +245,10 @@
243 critical_error_message_box(245 critical_error_message_box(
244 translate('OpenLP.Manager', 'Database Error'),246 translate('OpenLP.Manager', 'Database Error'),
245 translate('OpenLP.Manager', 'The database being loaded was created in a more recent version of '247 translate('OpenLP.Manager', 'The database being loaded was created in a more recent version of '
246 'OpenLP. The database is version %d, while OpenLP expects version %d. The database will '248 'OpenLP. The database is version {db_ver}, while OpenLP expects version {db_up}. '
247 'not be loaded.\n\nDatabase: %s') % (db_ver, up_ver, self.db_url)249 'The database will not be loaded.\n\nDatabase: {db_name}').format(db_ver=db_ver,
248 )250 db_up=up_ver,
251 db_name=self.db_url))
249 return252 return
250 if not session:253 if not session:
251 try:254 try:
@@ -461,7 +464,7 @@
461 raise464 raise
462 except InvalidRequestError:465 except InvalidRequestError:
463 self.session.rollback()466 self.session.rollback()
464 log.exception('Failed to delete %s records', object_class.__name__)467 log.exception('Failed to delete {name} records'.format(name=object_class.__name__))
465 return False468 return False
466 except:469 except:
467 self.session.rollback()470 self.session.rollback()
468471
=== renamed file 'openlp/core/lib/dockwidget.py' => 'openlp/core/lib/dockwidget.py.THIS'
=== modified file 'openlp/core/lib/exceptions.py'
--- openlp/core/lib/exceptions.py 2016-12-31 11:05:48 +0000
+++ openlp/core/lib/exceptions.py 2017-01-08 22:05:36 +0000
@@ -24,9 +24,15 @@
24"""24"""
2525
2626
27# TODO: Test __init__ & __str__
27class ValidationError(Exception):28class ValidationError(Exception):
28 """29 """
29 The :class:`~openlp.core.lib.exceptions.ValidationError` exception provides a custom exception for validating30 The :class:`~openlp.core.lib.exceptions.ValidationError` exception provides a custom exception for validating
30 import files.31 import files.
31 """32 """
32 pass33
34 def __init__(self, msg="Validation Error"):
35 self.msg = msg
36
37 def __str__(self):
38 return '{error_message}'.format(error_message=self.msg)
3339
=== modified file 'openlp/core/lib/filedialog.py'
--- openlp/core/lib/filedialog.py 2016-12-31 11:05:48 +0000
+++ openlp/core/lib/filedialog.py 2017-01-08 22:05:36 +0000
@@ -50,9 +50,9 @@
50 log.info('File not found. Attempting to unquote.')50 log.info('File not found. Attempting to unquote.')
51 file = parse.unquote(file)51 file = parse.unquote(file)
52 if not os.path.exists(file):52 if not os.path.exists(file):
53 log.error('File %s not found.' % file)53 log.error('File {text} not found.'.format(text=file))
54 QtWidgets.QMessageBox.information(parent, UiStrings().FileNotFound,54 QtWidgets.QMessageBox.information(parent, UiStrings().FileNotFound,
55 UiStrings().FileNotFoundMessage % file)55 UiStrings().FileNotFoundMessage.format(name=file))
56 continue56 continue
57 file_list.append(file)57 file_list.append(file)
58 return file_list58 return file_list
5959
=== modified file 'openlp/core/lib/htmlbuilder.py'
--- openlp/core/lib/htmlbuilder.py 2016-12-31 11:05:48 +0000
+++ openlp/core/lib/htmlbuilder.py 2017-01-08 22:05:36 +0000
@@ -389,6 +389,7 @@
389"""389"""
390import logging390import logging
391391
392from string import Template
392from PyQt5 import QtWebKit393from PyQt5 import QtWebKit
393394
394from openlp.core.common import Settings395from openlp.core.common import Settings
@@ -396,156 +397,200 @@
396397
397log = logging.getLogger(__name__)398log = logging.getLogger(__name__)
398399
399HTMLSRC = """400HTML_SRC = Template("""
400<!DOCTYPE html>401 <!DOCTYPE html>
401<html>402 <html>
402<head>403 <head>
403<title>OpenLP Display</title>404 <title>OpenLP Display</title>
404<style>405 <style>
405*{406 *{
407 margin: 0;
408 padding: 0;
409 border: 0;
410 overflow: hidden;
411 -webkit-user-select: none;
412 }
413 body {
414 ${bg_css};
415 }
416 .size {
417 position: absolute;
418 left: 0px;
419 top: 0px;
420 width: 100%;
421 height: 100%;
422 }
423 #black {
424 z-index: 8;
425 background-color: black;
426 display: none;
427 }
428 #bgimage {
429 z-index: 1;
430 }
431 #image {
432 z-index: 2;
433 }
434 ${css_additions}
435 #footer {
436 position: absolute;
437 z-index: 6;
438 ${footer_css}
439 }
440 /* lyric css */${lyrics_css}
441 sup {
442 font-size: 0.6em;
443 vertical-align: top;
444 position: relative;
445 top: -0.3em;
446 }
447 </style>
448 <script>
449 var timer = null;
450 var transition = ${transitions};
451 ${js_additions}
452
453 function show_image(src){
454 var img = document.getElementById('image');
455 img.src = src;
456 if(src == '')
457 img.style.display = 'none';
458 else
459 img.style.display = 'block';
460 }
461
462 function show_blank(state){
463 var black = 'none';
464 var lyrics = '';
465 switch(state){
466 case 'theme':
467 lyrics = 'hidden';
468 break;
469 case 'black':
470 black = 'block';
471 break;
472 case 'desktop':
473 break;
474 }
475 document.getElementById('black').style.display = black;
476 document.getElementById('lyricsmain').style.visibility = lyrics;
477 document.getElementById('image').style.visibility = lyrics;
478 document.getElementById('footer').style.visibility = lyrics;
479 }
480
481 function show_footer(footertext){
482 document.getElementById('footer').innerHTML = footertext;
483 }
484
485 function show_text(new_text){
486 var match = /-webkit-text-fill-color:[^;\"]+/gi;
487 if(timer != null)
488 clearTimeout(timer);
489 /*
490 QtWebkit bug with outlines and justify causing outline alignment
491 problems. (Bug 859950) Surround each word with a <span> to workaround,
492 but only in this scenario.
493 */
494 var txt = document.getElementById('lyricsmain');
495 if(window.getComputedStyle(txt).textAlign == 'justify'){
496 if(window.getComputedStyle(txt).webkitTextStrokeWidth != '0px'){
497 new_text = new_text.replace(/(\s|&nbsp;)+(?![^<]*>)/g,
498 function(match) {
499 return '</span>' + match + '<span>';
500 });
501 new_text = '<span>' + new_text + '</span>';
502 }
503 }
504 text_fade('lyricsmain', new_text);
505 }
506
507 function text_fade(id, new_text){
508 /*
509 Show the text.
510 */
511 var text = document.getElementById(id);
512 if(text == null) return;
513 if(!transition){
514 text.innerHTML = new_text;
515 return;
516 }
517 // Fade text out. 0.1 to minimize the time "nothing" is shown on the screen.
518 text.style.opacity = '0.1';
519 // Fade new text in after the old text has finished fading out.
520 timer = window.setTimeout(function(){_show_text(text, new_text)}, 400);
521 }
522
523 function _show_text(text, new_text) {
524 /*
525 Helper function to show the new_text delayed.
526 */
527 text.innerHTML = new_text;
528 text.style.opacity = '1';
529 // Wait until the text is completely visible. We want to save the timer id, to be able to call
530 // clearTimeout(timer) when the text has changed before finishing fading.
531 timer = window.setTimeout(function(){timer = null;}, 400);
532 }
533
534 function show_text_completed(){
535 return (timer == null);
536 }
537 </script>
538 </head>
539 <body>
540 <img id="bgimage" class="size" ${bg_image} />
541 <img id="image" class="size" ${image} />
542 ${html_additions}
543 <div class="lyricstable"><div id="lyricsmain" style="opacity:1" class="lyricscell lyricsmain"></div></div>
544 <div id="footer" class="footer"></div>
545 <div id="black" class="size"></div>
546 </body>
547 </html>
548 """)
549
550LYRICS_SRC = Template("""
551 .lyricstable {
552 z-index: 5;
553 position: absolute;
554 display: table;
555 ${stable}
556 }
557 .lyricscell {
558 display: table-cell;
559 word-wrap: break-word;
560 -webkit-transition: opacity 0.4s ease;
561 ${lyrics}
562 }
563 .lyricsmain {
564 ${main}
565 }
566 """)
567
568FOOTER_SRC = Template("""
569 left: ${left}px;
570 bottom: ${bottom}px;
571 width: ${width}px;
572 font-family: ${family};
573 font-size: ${size}pt;
574 color: ${color};
575 text-align: left;
576 white-space: ${space};
577 """)
578
579LYRICS_FORMAT_SRC = Template("""
580 ${justify}word-wrap: break-word;
581 text-align: ${align};
582 vertical-align: ${valign};
583 font-family: ${font};
584 font-size: ${size}pt;
585 color: ${color};
586 line-height: ${line}%;
406 margin: 0;587 margin: 0;
407 padding: 0;588 padding: 0;
408 border: 0;589 padding-bottom: ${bottom};
409 overflow: hidden;590 padding-left: ${left}px;
410 -webkit-user-select: none;591 width: ${width}px;
411}592 height: ${height}px;${font_style}${font_weight}
412body {593 """)
413 %s;
414}
415.size {
416 position: absolute;
417 left: 0px;
418 top: 0px;
419 width: 100%%;
420 height: 100%%;
421}
422#black {
423 z-index: 8;
424 background-color: black;
425 display: none;
426}
427#bgimage {
428 z-index: 1;
429}
430#image {
431 z-index: 2;
432}
433%s
434#footer {
435 position: absolute;
436 z-index: 6;
437 %s
438}
439/* lyric css */
440%s
441sup {
442 font-size: 0.6em;
443 vertical-align: top;
444 position: relative;
445 top: -0.3em;
446}
447</style>
448<script>
449 var timer = null;
450 var transition = %s;
451 %s
452
453 function show_image(src){
454 var img = document.getElementById('image');
455 img.src = src;
456 if(src == '')
457 img.style.display = 'none';
458 else
459 img.style.display = 'block';
460 }
461
462 function show_blank(state){
463 var black = 'none';
464 var lyrics = '';
465 switch(state){
466 case 'theme':
467 lyrics = 'hidden';
468 break;
469 case 'black':
470 black = 'block';
471 break;
472 case 'desktop':
473 break;
474 }
475 document.getElementById('black').style.display = black;
476 document.getElementById('lyricsmain').style.visibility = lyrics;
477 document.getElementById('image').style.visibility = lyrics;
478 document.getElementById('footer').style.visibility = lyrics;
479 }
480
481 function show_footer(footertext){
482 document.getElementById('footer').innerHTML = footertext;
483 }
484
485 function show_text(new_text){
486 var match = /-webkit-text-fill-color:[^;\"]+/gi;
487 if(timer != null)
488 clearTimeout(timer);
489 /*
490 QtWebkit bug with outlines and justify causing outline alignment
491 problems. (Bug 859950) Surround each word with a <span> to workaround,
492 but only in this scenario.
493 */
494 var txt = document.getElementById('lyricsmain');
495 if(window.getComputedStyle(txt).textAlign == 'justify'){
496 if(window.getComputedStyle(txt).webkitTextStrokeWidth != '0px'){
497 new_text = new_text.replace(/(\s|&nbsp;)+(?![^<]*>)/g,
498 function(match) {
499 return '</span>' + match + '<span>';
500 });
501 new_text = '<span>' + new_text + '</span>';
502 }
503 }
504 text_fade('lyricsmain', new_text);
505 }
506
507 function text_fade(id, new_text){
508 /*
509 Show the text.
510 */
511 var text = document.getElementById(id);
512 if(text == null) return;
513 if(!transition){
514 text.innerHTML = new_text;
515 return;
516 }
517 // Fade text out. 0.1 to minimize the time "nothing" is shown on the screen.
518 text.style.opacity = '0.1';
519 // Fade new text in after the old text has finished fading out.
520 timer = window.setTimeout(function(){_show_text(text, new_text)}, 400);
521 }
522
523 function _show_text(text, new_text) {
524 /*
525 Helper function to show the new_text delayed.
526 */
527 text.innerHTML = new_text;
528 text.style.opacity = '1';
529 // Wait until the text is completely visible. We want to save the timer id, to be able to call
530 // clearTimeout(timer) when the text has changed before finishing fading.
531 timer = window.setTimeout(function(){timer = null;}, 400);
532 }
533
534 function show_text_completed(){
535 return (timer == null);
536 }
537</script>
538</head>
539<body>
540<img id="bgimage" class="size" %s />
541<img id="image" class="size" %s />
542%s
543<div class="lyricstable"><div id="lyricsmain" style="opacity:1" class="lyricscell lyricsmain"></div></div>
544<div id="footer" class="footer"></div>
545<div id="black" class="size"></div>
546</body>
547</html>
548"""
549594
550595
551def build_html(item, screen, is_live, background, image=None, plugins=None):596def build_html(item, screen, is_live, background, image=None, plugins=None):
@@ -564,13 +609,13 @@
564 theme_data = item.theme_data609 theme_data = item.theme_data
565 # Image generated and poked in610 # Image generated and poked in
566 if background:611 if background:
567 bgimage_src = 'src="data:image/png;base64,%s"' % background612 bgimage_src = 'src="data:image/png;base64,{image}"'.format(image=background)
568 elif item.bg_image_bytes:613 elif item.bg_image_bytes:
569 bgimage_src = 'src="data:image/png;base64,%s"' % item.bg_image_bytes614 bgimage_src = 'src="data:image/png;base64,{image}"'.format(image=item.bg_image_bytes)
570 else:615 else:
571 bgimage_src = 'style="display:none;"'616 bgimage_src = 'style="display:none;"'
572 if image:617 if image:
573 image_src = 'src="data:image/png;base64,%s"' % image618 image_src = 'src="data:image/png;base64,{image}"'.format(image=image)
574 else:619 else:
575 image_src = 'style="display:none;"'620 image_src = 'style="display:none;"'
576 css_additions = ''621 css_additions = ''
@@ -581,18 +626,17 @@
581 css_additions += plugin.get_display_css()626 css_additions += plugin.get_display_css()
582 js_additions += plugin.get_display_javascript()627 js_additions += plugin.get_display_javascript()
583 html_additions += plugin.get_display_html()628 html_additions += plugin.get_display_html()
584 html = HTMLSRC % (629 return HTML_SRC.substitute(bg_css=build_background_css(item, width),
585 build_background_css(item, width),630 css_additions=css_additions,
586 css_additions,631 footer_css=build_footer_css(item, height),
587 build_footer_css(item, height),632 lyrics_css=build_lyrics_css(item),
588 build_lyrics_css(item),633 transitions='true' if (theme_data and
589 'true' if theme_data and theme_data.display_slide_transition and is_live else 'false',634 theme_data.display_slide_transition and
590 js_additions,635 is_live) else 'false',
591 bgimage_src,636 js_additions=js_additions,
592 image_src,637 bg_image=bgimage_src,
593 html_additions638 image=image_src,
594 )639 html_additions=html_additions)
595 return html
596640
597641
598def webkit_version():642def webkit_version():
@@ -601,9 +645,9 @@
601 """645 """
602 try:646 try:
603 webkit_ver = float(QtWebKit.qWebKitVersion())647 webkit_ver = float(QtWebKit.qWebKitVersion())
604 log.debug('Webkit version = %s' % webkit_ver)648 log.debug('Webkit version = {version}'.format(version=webkit_ver))
605 except AttributeError:649 except AttributeError:
606 webkit_ver = 0650 webkit_ver = 0.0
607 return webkit_ver651 return webkit_ver
608652
609653
@@ -621,23 +665,25 @@
621 if theme.background_type == BackgroundType.to_string(BackgroundType.Transparent):665 if theme.background_type == BackgroundType.to_string(BackgroundType.Transparent):
622 background = ''666 background = ''
623 elif theme.background_type == BackgroundType.to_string(BackgroundType.Solid):667 elif theme.background_type == BackgroundType.to_string(BackgroundType.Solid):
624 background = 'background-color: %s' % theme.background_color668 background = 'background-color: {theme}'.format(theme=theme.background_color)
625 else:669 else:
626 if theme.background_direction == BackgroundGradientType.to_string(BackgroundGradientType.Horizontal):670 if theme.background_direction == BackgroundGradientType.to_string(BackgroundGradientType.Horizontal):
627 background = 'background: -webkit-gradient(linear, left top, left bottom, from(%s), to(%s)) fixed' \671 background = 'background: -webkit-gradient(linear, left top, left bottom, from({start}), to({end})) ' \
628 % (theme.background_start_color, theme.background_end_color)672 'fixed'.format(start=theme.background_start_color, end=theme.background_end_color)
629 elif theme.background_direction == BackgroundGradientType.to_string(BackgroundGradientType.LeftTop):673 elif theme.background_direction == BackgroundGradientType.to_string(BackgroundGradientType.LeftTop):
630 background = 'background: -webkit-gradient(linear, left top, right bottom, from(%s), to(%s)) fixed' \674 background = 'background: -webkit-gradient(linear, left top, right bottom, from({start}), to({end})) ' \
631 % (theme.background_start_color, theme.background_end_color)675 'fixed'.format(start=theme.background_start_color, end=theme.background_end_color)
632 elif theme.background_direction == BackgroundGradientType.to_string(BackgroundGradientType.LeftBottom):676 elif theme.background_direction == BackgroundGradientType.to_string(BackgroundGradientType.LeftBottom):
633 background = 'background: -webkit-gradient(linear, left bottom, right top, from(%s), to(%s)) fixed' \677 background = 'background: -webkit-gradient(linear, left bottom, right top, from({start}), to({end})) ' \
634 % (theme.background_start_color, theme.background_end_color)678 'fixed'.format(start=theme.background_start_color, end=theme.background_end_color)
635 elif theme.background_direction == BackgroundGradientType.to_string(BackgroundGradientType.Vertical):679 elif theme.background_direction == BackgroundGradientType.to_string(BackgroundGradientType.Vertical):
636 background = 'background: -webkit-gradient(linear, left top, right top, from(%s), to(%s)) fixed' % \680 background = 'background: -webkit-gradient(linear, left top, right top, from({start}), to({end})) ' \
637 (theme.background_start_color, theme.background_end_color)681 'fixed'.format(start=theme.background_start_color, end=theme.background_end_color)
638 else:682 else:
639 background = 'background: -webkit-gradient(radial, %s 50%%, 100, %s 50%%, %s, from(%s), to(%s)) fixed'\683 background = 'background: -webkit-gradient(radial, {width} 50%, 100, {width} 50%, {width}, ' \
640 % (width, width, width, theme.background_start_color, theme.background_end_color)684 'from({start}), to({end})) fixed'.format(width=width,
685 start=theme.background_start_color,
686 end=theme.background_end_color)
641 return background687 return background
642688
643689
@@ -647,36 +693,19 @@
647693
648 :param item: Service Item containing theme and location information694 :param item: Service Item containing theme and location information
649 """695 """
650 style = """
651.lyricstable {
652 z-index: 5;
653 position: absolute;
654 display: table;
655 %s
656}
657.lyricscell {
658 display: table-cell;
659 word-wrap: break-word;
660 -webkit-transition: opacity 0.4s ease;
661 %s
662}
663.lyricsmain {
664 %s
665}
666"""
667 theme_data = item.theme_data696 theme_data = item.theme_data
668 lyricstable = ''697 lyricstable = ''
669 lyrics = ''698 lyrics = ''
670 lyricsmain = ''699 lyricsmain = ''
671 if theme_data and item.main:700 if theme_data and item.main:
672 lyricstable = 'left: %spx; top: %spx;' % (item.main.x(), item.main.y())701 lyricstable = 'left: {left}px; top: {top}px;'.format(left=item.main.x(), top=item.main.y())
673 lyrics = build_lyrics_format_css(theme_data, item.main.width(), item.main.height())702 lyrics = build_lyrics_format_css(theme_data, item.main.width(), item.main.height())
674 lyricsmain += build_lyrics_outline_css(theme_data)703 lyricsmain += build_lyrics_outline_css(theme_data)
675 if theme_data.font_main_shadow:704 if theme_data.font_main_shadow:
676 lyricsmain += ' text-shadow: %s %spx %spx;' % \705 lyricsmain += ' text-shadow: {theme} {shadow}px ' \
677 (theme_data.font_main_shadow_color, theme_data.font_main_shadow_size, theme_data.font_main_shadow_size)706 '{shadow}px;'.format(theme=theme_data.font_main_shadow_color,
678 lyrics_css = style % (lyricstable, lyrics, lyricsmain)707 shadow=theme_data.font_main_shadow_size)
679 return lyrics_css708 return LYRICS_SRC.substitute(stable=lyricstable, lyrics=lyrics, main=lyricsmain)
680709
681710
682def build_lyrics_outline_css(theme_data):711def build_lyrics_outline_css(theme_data):
@@ -689,7 +718,9 @@
689 size = float(theme_data.font_main_outline_size) / 16718 size = float(theme_data.font_main_outline_size) / 16
690 fill_color = theme_data.font_main_color719 fill_color = theme_data.font_main_color
691 outline_color = theme_data.font_main_outline_color720 outline_color = theme_data.font_main_outline_color
692 return ' -webkit-text-stroke: %sem %s; -webkit-text-fill-color: %s; ' % (size, outline_color, fill_color)721 return ' -webkit-text-stroke: {size}em {color}; -webkit-text-fill-color: {fill}; '.format(size=size,
722 color=outline_color,
723 fill=fill_color)
693 return ''724 return ''
694725
695726
@@ -703,30 +734,23 @@
703 """734 """
704 align = HorizontalType.Names[theme_data.display_horizontal_align]735 align = HorizontalType.Names[theme_data.display_horizontal_align]
705 valign = VerticalType.Names[theme_data.display_vertical_align]736 valign = VerticalType.Names[theme_data.display_vertical_align]
706 if theme_data.font_main_outline:737 left_margin = (int(theme_data.font_main_outline_size) * 2) if theme_data.font_main_outline else 0
707 left_margin = int(theme_data.font_main_outline_size) * 2
708 else:
709 left_margin = 0
710 justify = 'white-space:pre-wrap;'
711 # fix tag incompatibilities738 # fix tag incompatibilities
712 if theme_data.display_horizontal_align == HorizontalType.Justify:739 justify = '' if (theme_data.display_horizontal_align == HorizontalType.Justify) else ' white-space: pre-wrap;\n'
713 justify = ''740 padding_bottom = '0.5em' if (theme_data.display_vertical_align == VerticalType.Bottom) else '0'
714 if theme_data.display_vertical_align == VerticalType.Bottom:741 return LYRICS_FORMAT_SRC.substitute(justify=justify,
715 padding_bottom = '0.5em'742 align=align,
716 else:743 valign=valign,
717 padding_bottom = '0'744 font=theme_data.font_main_name,
718 lyrics = '%s word-wrap: break-word; ' \745 size=theme_data.font_main_size,
719 'text-align: %s; vertical-align: %s; font-family: %s; ' \746 color=theme_data.font_main_color,
720 'font-size: %spt; color: %s; line-height: %d%%; margin: 0;' \747 line='{line:d}'.format(line=100 + int(theme_data.font_main_line_adjustment)),
721 'padding: 0; padding-bottom: %s; padding-left: %spx; width: %spx; height: %spx; ' % \748 bottom=padding_bottom,
722 (justify, align, valign, theme_data.font_main_name, theme_data.font_main_size,749 left=left_margin,
723 theme_data.font_main_color, 100 + int(theme_data.font_main_line_adjustment), padding_bottom,750 width=width,
724 left_margin, width, height)751 height=height,
725 if theme_data.font_main_italics:752 font_style='\n font-style: italic;' if theme_data.font_main_italics else '',
726 lyrics += 'font-style:italic; '753 font_weight='\n font-weight: bold;' if theme_data.font_main_bold else '')
727 if theme_data.font_main_bold:
728 lyrics += 'font-weight:bold; '
729 return lyrics
730754
731755
732def build_footer_css(item, height):756def build_footer_css(item, height):
@@ -736,21 +760,11 @@
736 :param item: Service Item to be processed.760 :param item: Service Item to be processed.
737 :param height:761 :param height:
738 """762 """
739 style = """
740 left: %spx;
741 bottom: %spx;
742 width: %spx;
743 font-family: %s;
744 font-size: %spt;
745 color: %s;
746 text-align: left;
747 white-space: %s;
748 """
749 theme = item.theme_data763 theme = item.theme_data
750 if not theme or not item.footer:764 if not theme or not item.footer:
751 return ''765 return ''
752 bottom = height - int(item.footer.y()) - int(item.footer.height())766 bottom = height - int(item.footer.y()) - int(item.footer.height())
753 whitespace = 'normal' if Settings().value('themes/wrap footer') else 'nowrap'767 whitespace = 'normal' if Settings().value('themes/wrap footer') else 'nowrap'
754 lyrics_html = style % (item.footer.x(), bottom, item.footer.width(),768 return FOOTER_SRC.substitute(left=item.footer.x(), bottom=bottom, width=item.footer.width(),
755 theme.font_footer_name, theme.font_footer_size, theme.font_footer_color, whitespace)769 family=theme.font_footer_name, size=theme.font_footer_size,
756 return lyrics_html770 color=theme.font_footer_color, space=whitespace)
757771
=== modified file 'openlp/core/lib/imagemanager.py'
--- openlp/core/lib/imagemanager.py 2016-12-31 11:05:48 +0000
+++ openlp/core/lib/imagemanager.py 2017-01-08 22:05:36 +0000
@@ -236,7 +236,7 @@
236 """236 """
237 Return the ``QImage`` from the cache. If not present wait for the background thread to process it.237 Return the ``QImage`` from the cache. If not present wait for the background thread to process it.
238 """238 """
239 log.debug('getImage %s' % path)239 log.debug('getImage {path}'.format(path=path))
240 image = self._cache[(path, source, width, height)]240 image = self._cache[(path, source, width, height)]
241 if image.image is None:241 if image.image is None:
242 self._conversion_queue.modify_priority(image, Priority.High)242 self._conversion_queue.modify_priority(image, Priority.High)
@@ -256,7 +256,7 @@
256 """256 """
257 Returns the byte string for an image. If not present wait for the background thread to process it.257 Returns the byte string for an image. If not present wait for the background thread to process it.
258 """258 """
259 log.debug('get_image_bytes %s' % path)259 log.debug('get_image_bytes {path}'.format(path=path))
260 image = self._cache[(path, source, width, height)]260 image = self._cache[(path, source, width, height)]
261 if image.image_bytes is None:261 if image.image_bytes is None:
262 self._conversion_queue.modify_priority(image, Priority.Urgent)262 self._conversion_queue.modify_priority(image, Priority.Urgent)
@@ -271,8 +271,8 @@
271 """271 """
272 Add image to cache if it is not already there.272 Add image to cache if it is not already there.
273 """273 """
274 log.debug('add_image %s' % path)274 log.debug('add_image {path}'.format(path=path))
275 if not (path, source, width, height) in self._cache:275 if (path, source, width, height) not in self._cache:
276 image = Image(path, source, background, width, height)276 image = Image(path, source, background, width, height)
277 self._cache[(path, source, width, height)] = image277 self._cache[(path, source, width, height)] = image
278 self._conversion_queue.put((image.priority, image.secondary_priority, image))278 self._conversion_queue.put((image.priority, image.secondary_priority, image))
279279
=== renamed file 'openlp/core/lib/listwidgetwithdnd.py' => 'openlp/core/lib/listwidgetwithdnd.py.THIS'
=== modified file 'openlp/core/lib/mediamanageritem.py'
--- openlp/core/lib/mediamanageritem.py 2016-12-31 11:05:48 +0000
+++ openlp/core/lib/mediamanageritem.py 2017-01-08 22:05:36 +0000
@@ -29,10 +29,11 @@
29from PyQt5 import QtCore, QtGui, QtWidgets29from PyQt5 import QtCore, QtGui, QtWidgets
3030
31from openlp.core.common import Registry, RegistryProperties, Settings, UiStrings, translate31from openlp.core.common import Registry, RegistryProperties, Settings, UiStrings, translate
32from openlp.core.lib import FileDialog, OpenLPToolbar, ServiceItem, StringContent, ListWidgetWithDnD, \32from openlp.core.lib import FileDialog, ServiceItem, StringContent, ServiceItemContext
33 ServiceItemContext
34from openlp.core.lib.searchedit import SearchEdit33from openlp.core.lib.searchedit import SearchEdit
35from openlp.core.lib.ui import create_widget_action, critical_error_message_box34from openlp.core.lib.ui import create_widget_action, critical_error_message_box
35from openlp.core.ui.lib.listwidgetwithdnd import ListWidgetWithDnD
36from openlp.core.ui.lib.toolbar import OpenLPToolbar
3637
37log = logging.getLogger(__name__)38log = logging.getLogger(__name__)
3839
@@ -185,7 +186,7 @@
185 for action in toolbar_actions:186 for action in toolbar_actions:
186 if action[0] == StringContent.Preview:187 if action[0] == StringContent.Preview:
187 self.toolbar.addSeparator()188 self.toolbar.addSeparator()
188 self.toolbar.add_toolbar_action('%s%sAction' % (self.plugin.name, action[0]),189 self.toolbar.add_toolbar_action('{name}{action}Action'.format(name=self.plugin.name, action=action[0]),
189 text=self.plugin.get_string(action[1])['title'], icon=action[2],190 text=self.plugin.get_string(action[1])['title'], icon=action[2],
190 tooltip=self.plugin.get_string(action[1])['tooltip'],191 tooltip=self.plugin.get_string(action[1])['tooltip'],
191 triggers=action[3])192 triggers=action[3])
@@ -199,7 +200,7 @@
199 self.list_view.setSpacing(1)200 self.list_view.setSpacing(1)
200 self.list_view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)201 self.list_view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
201 self.list_view.setAlternatingRowColors(True)202 self.list_view.setAlternatingRowColors(True)
202 self.list_view.setObjectName('%sListView' % self.plugin.name)203 self.list_view.setObjectName('{name}ListView'.format(name=self.plugin.name))
203 # Add to page_layout204 # Add to page_layout
204 self.page_layout.addWidget(self.list_view)205 self.page_layout.addWidget(self.list_view)
205 # define and add the context menu206 # define and add the context menu
@@ -211,19 +212,22 @@
211 triggers=self.on_edit_click)212 triggers=self.on_edit_click)
212 create_widget_action(self.list_view, separator=True)213 create_widget_action(self.list_view, separator=True)
213 create_widget_action(self.list_view,214 create_widget_action(self.list_view,
214 'listView%s%sItem' % (self.plugin.name.title(), StringContent.Preview.title()),215 'listView{plugin}{preview}Item'.format(plugin=self.plugin.name.title(),
216 preview=StringContent.Preview.title()),
215 text=self.plugin.get_string(StringContent.Preview)['title'],217 text=self.plugin.get_string(StringContent.Preview)['title'],
216 icon=':/general/general_preview.png',218 icon=':/general/general_preview.png',
217 can_shortcuts=True,219 can_shortcuts=True,
218 triggers=self.on_preview_click)220 triggers=self.on_preview_click)
219 create_widget_action(self.list_view,221 create_widget_action(self.list_view,
220 'listView%s%sItem' % (self.plugin.name.title(), StringContent.Live.title()),222 'listView{plugin}{live}Item'.format(plugin=self.plugin.name.title(),
223 live=StringContent.Live.title()),
221 text=self.plugin.get_string(StringContent.Live)['title'],224 text=self.plugin.get_string(StringContent.Live)['title'],
222 icon=':/general/general_live.png',225 icon=':/general/general_live.png',
223 can_shortcuts=True,226 can_shortcuts=True,
224 triggers=self.on_live_click)227 triggers=self.on_live_click)
225 create_widget_action(self.list_view,228 create_widget_action(self.list_view,
226 'listView%s%sItem' % (self.plugin.name.title(), StringContent.Service.title()),229 'listView{plugin}{service}Item'.format(plugin=self.plugin.name.title(),
230 service=StringContent.Service.title()),
227 can_shortcuts=True,231 can_shortcuts=True,
228 text=self.plugin.get_string(StringContent.Service)['title'],232 text=self.plugin.get_string(StringContent.Service)['title'],
229 icon=':/general/general_add.png',233 icon=':/general/general_add.png',
@@ -231,7 +235,8 @@
231 if self.has_delete_icon:235 if self.has_delete_icon:
232 create_widget_action(self.list_view, separator=True)236 create_widget_action(self.list_view, separator=True)
233 create_widget_action(self.list_view,237 create_widget_action(self.list_view,
234 'listView%s%sItem' % (self.plugin.name.title(), StringContent.Delete.title()),238 'listView{plugin}{delete}Item'.format(plugin=self.plugin.name.title(),
239 delete=StringContent.Delete.title()),
235 text=self.plugin.get_string(StringContent.Delete)['title'],240 text=self.plugin.get_string(StringContent.Delete)['title'],
236 icon=':/general/general_delete.png',241 icon=':/general/general_delete.png',
237 can_shortcuts=True, triggers=self.on_delete_click)242 can_shortcuts=True, triggers=self.on_delete_click)
@@ -261,7 +266,7 @@
261 self.search_text_layout.setObjectName('search_text_layout')266 self.search_text_layout.setObjectName('search_text_layout')
262 self.search_text_label = QtWidgets.QLabel(self.search_widget)267 self.search_text_label = QtWidgets.QLabel(self.search_widget)
263 self.search_text_label.setObjectName('search_text_label')268 self.search_text_label.setObjectName('search_text_label')
264 self.search_text_edit = SearchEdit(self.search_widget)269 self.search_text_edit = SearchEdit(self.search_widget, self.settings_section)
265 self.search_text_edit.setObjectName('search_text_edit')270 self.search_text_edit.setObjectName('search_text_edit')
266 self.search_text_label.setBuddy(self.search_text_edit)271 self.search_text_label.setBuddy(self.search_text_edit)
267 self.search_text_layout.addRow(self.search_text_label, self.search_text_edit)272 self.search_text_layout.addRow(self.search_text_label, self.search_text_edit)
@@ -312,7 +317,7 @@
312 files = FileDialog.getOpenFileNames(self, self.on_new_prompt,317 files = FileDialog.getOpenFileNames(self, self.on_new_prompt,
313 Settings().value(self.settings_section + '/last directory'),318 Settings().value(self.settings_section + '/last directory'),
314 self.on_new_file_masks)319 self.on_new_file_masks)
315 log.info('New files(s) %s' % files)320 log.info('New files(s) {files}'.format(files=files))
316 if files:321 if files:
317 self.application.set_busy_cursor()322 self.application.set_busy_cursor()
318 self.validate_and_load(files)323 self.validate_and_load(files)
@@ -332,7 +337,8 @@
332 if not error_shown:337 if not error_shown:
333 critical_error_message_box(translate('OpenLP.MediaManagerItem', 'Invalid File Type'),338 critical_error_message_box(translate('OpenLP.MediaManagerItem', 'Invalid File Type'),
334 translate('OpenLP.MediaManagerItem',339 translate('OpenLP.MediaManagerItem',
335 'Invalid File %s.\nSuffix not supported') % file_name)340 'Invalid File {name}.\n'
341 'Suffix not supported').format(name=file_name))
336 error_shown = True342 error_shown = True
337 else:343 else:
338 new_files.append(file_name)344 new_files.append(file_name)
@@ -374,7 +380,8 @@
374 self.load_list(full_list, target_group)380 self.load_list(full_list, target_group)
375 last_dir = os.path.split(files[0])[0]381 last_dir = os.path.split(files[0])[0]
376 Settings().setValue(self.settings_section + '/last directory', last_dir)382 Settings().setValue(self.settings_section + '/last directory', last_dir)
377 Settings().setValue('%s/%s files' % (self.settings_section, self.settings_section), self.get_file_list())383 Settings().setValue('{section}/{section} files'.format(section=self.settings_section),
384 self.get_file_list())
378 if duplicates_found:385 if duplicates_found:
379 critical_error_message_box(UiStrings().Duplicate,386 critical_error_message_box(UiStrings().Duplicate,
380 translate('OpenLP.MediaManagerItem',387 translate('OpenLP.MediaManagerItem',
@@ -390,8 +397,6 @@
390 # Decide if we have to show the context menu or not.397 # Decide if we have to show the context menu or not.
391 if item is None:398 if item is None:
392 return399 return
393 if not item.flags() & QtCore.Qt.ItemIsSelectable:
394 return
395 self.menu.exec(self.list_view.mapToGlobal(point))400 self.menu.exec(self.list_view.mapToGlobal(point))
396401
397 def get_file_list(self):402 def get_file_list(self):
@@ -481,6 +486,7 @@
481 'You must select one or more items to preview.'))486 'You must select one or more items to preview.'))
482 else:487 else:
483 log.debug('%s Preview requested' % self.plugin.name)488 log.debug('%s Preview requested' % self.plugin.name)
489 Registry().set_flag('has doubleclick added item to service', False)
484 service_item = self.build_service_item()490 service_item = self.build_service_item()
485 if service_item:491 if service_item:
486 service_item.from_plugin = True492 service_item.from_plugin = True
@@ -549,7 +555,7 @@
549 # Is it possible to process multiple list items to generate555 # Is it possible to process multiple list items to generate
550 # multiple service items?556 # multiple service items?
551 if self.single_service_item:557 if self.single_service_item:
552 log.debug('%s Add requested', self.plugin.name)558 log.debug('{plugin} Add requested'.format(plugin=self.plugin.name))
553 self.add_to_service(replace=self.remote_triggered)559 self.add_to_service(replace=self.remote_triggered)
554 else:560 else:
555 items = self.list_view.selectedIndexes()561 items = self.list_view.selectedIndexes()
@@ -590,7 +596,7 @@
590 translate('OpenLP.MediaManagerItem',596 translate('OpenLP.MediaManagerItem',
591 'You must select one or more items.'))597 'You must select one or more items.'))
592 else:598 else:
593 log.debug('%s Add requested', self.plugin.name)599 log.debug('{plugin} Add requested'.format(plugin=self.plugin.name))
594 service_item = self.service_manager.get_service_item()600 service_item = self.service_manager.get_service_item()
595 if not service_item:601 if not service_item:
596 QtWidgets.QMessageBox.information(self, UiStrings().NISs,602 QtWidgets.QMessageBox.information(self, UiStrings().NISs,
@@ -603,7 +609,8 @@
603 # Turn off the remote edit update message indicator609 # Turn off the remote edit update message indicator
604 QtWidgets.QMessageBox.information(self, translate('OpenLP.MediaManagerItem', 'Invalid Service Item'),610 QtWidgets.QMessageBox.information(self, translate('OpenLP.MediaManagerItem', 'Invalid Service Item'),
605 translate('OpenLP.MediaManagerItem',611 translate('OpenLP.MediaManagerItem',
606 'You must select a %s service item.') % self.title)612 'You must select a {title} '
613 'service item.').format(title=self.title))
607614
608 def build_service_item(self, item=None, xml_version=False, remote=False, context=ServiceItemContext.Live):615 def build_service_item(self, item=None, xml_version=False, remote=False, context=ServiceItemContext.Live):
609 """616 """
@@ -629,20 +636,6 @@
629 """636 """
630 return item637 return item
631638
632 def check_search_result(self):
633 """
634 Checks if the list_view is empty and adds a "No Search Results" item.
635 """
636 if self.list_view.count():
637 return
638 message = translate('OpenLP.MediaManagerItem', 'No Search Results')
639 item = QtWidgets.QListWidgetItem(message)
640 item.setFlags(QtCore.Qt.NoItemFlags)
641 font = QtGui.QFont()
642 font.setItalic(True)
643 item.setFont(font)
644 self.list_view.addItem(item)
645
646 def _get_id_of_item_to_generate(self, item, remote_item):639 def _get_id_of_item_to_generate(self, item, remote_item):
647 """640 """
648 Utility method to check items being submitted for slide generation.641 Utility method to check items being submitted for slide generation.
649642
=== modified file 'openlp/core/lib/plugin.py'
--- openlp/core/lib/plugin.py 2016-12-31 11:05:48 +0000
+++ openlp/core/lib/plugin.py 2017-01-08 22:05:36 +0000
@@ -24,11 +24,10 @@
24"""24"""
25import logging25import logging
2626
27
28from PyQt5 import QtCore27from PyQt5 import QtCore
2928
30from openlp.core.common import Registry, RegistryProperties, Settings, UiStrings29from openlp.core.common import Registry, RegistryProperties, Settings, UiStrings
31from openlp.core.utils import get_application_version30from openlp.core.common.versionchecker import get_application_version
3231
33log = logging.getLogger(__name__)32log = logging.getLogger(__name__)
3433
@@ -131,7 +130,7 @@
131 :param settings_tab_class: The class name of the plugin's settings tab.130 :param settings_tab_class: The class name of the plugin's settings tab.
132 :param version: Defaults to *None*, which means that the same version number is used as OpenLP's version number.131 :param version: Defaults to *None*, which means that the same version number is used as OpenLP's version number.
133 """132 """
134 log.debug('Plugin %s initialised' % name)133 log.debug('Plugin {plugin} initialised'.format(plugin=name))
135 super(Plugin, self).__init__()134 super(Plugin, self).__init__()
136 self.name = name135 self.name = name
137 self.text_strings = {}136 self.text_strings = {}
@@ -155,11 +154,11 @@
155 # Append a setting for files in the mediamanager (note not all plugins154 # Append a setting for files in the mediamanager (note not all plugins
156 # which have a mediamanager need this).155 # which have a mediamanager need this).
157 if media_item_class is not None:156 if media_item_class is not None:
158 default_settings['%s/%s files' % (name, name)] = []157 default_settings['{name}/{name} files'.format(name=name)] = []
159 # Add settings to the dict of all settings.158 # Add settings to the dict of all settings.
160 Settings.extend_default_settings(default_settings)159 Settings.extend_default_settings(default_settings)
161 Registry().register_function('%s_add_service_item' % self.name, self.process_add_service_event)160 Registry().register_function('{name}_add_service_item'.format(name=self.name), self.process_add_service_event)
162 Registry().register_function('%s_config_updated' % self.name, self.config_update)161 Registry().register_function('{name}_config_updated'.format(name=self.name), self.config_update)
163162
164 def check_pre_conditions(self):163 def check_pre_conditions(self):
165 """164 """
@@ -257,7 +256,7 @@
257 """256 """
258 Generic Drag and drop handler triggered from service_manager.257 Generic Drag and drop handler triggered from service_manager.
259 """258 """
260 log.debug('process_add_service_event event called for plugin %s' % self.name)259 log.debug('process_add_service_event event called for plugin {name}'.format(name=self.name))
261 if replace:260 if replace:
262 self.media_item.on_add_edit_click()261 self.media_item.on_add_edit_click()
263 else:262 else:
264263
=== modified file 'openlp/core/lib/pluginmanager.py'
--- openlp/core/lib/pluginmanager.py 2016-12-31 11:05:48 +0000
+++ openlp/core/lib/pluginmanager.py 2017-01-08 22:05:36 +0000
@@ -23,7 +23,6 @@
23Provide plugin management23Provide plugin management
24"""24"""
25import os25import os
26import sys
27import imp26import imp
2827
29from openlp.core.lib import Plugin, PluginStatus28from openlp.core.lib import Plugin, PluginStatus
@@ -43,7 +42,7 @@
43 super(PluginManager, self).__init__(parent)42 super(PluginManager, self).__init__(parent)
44 self.log_info('Plugin manager Initialising')43 self.log_info('Plugin manager Initialising')
45 self.base_path = os.path.abspath(AppLocation.get_directory(AppLocation.PluginsDir))44 self.base_path = os.path.abspath(AppLocation.get_directory(AppLocation.PluginsDir))
46 self.log_debug('Base path %s ' % self.base_path)45 self.log_debug('Base path {path}'.format(path=self.base_path))
47 self.plugins = []46 self.plugins = []
48 self.log_info('Plugin manager Initialised')47 self.log_info('Plugin manager Initialised')
4948
@@ -73,7 +72,7 @@
73 """72 """
74 start_depth = len(os.path.abspath(self.base_path).split(os.sep))73 start_depth = len(os.path.abspath(self.base_path).split(os.sep))
75 present_plugin_dir = os.path.join(self.base_path, 'presentations')74 present_plugin_dir = os.path.join(self.base_path, 'presentations')
76 self.log_debug('finding plugins in %s at depth %d' % (self.base_path, start_depth))75 self.log_debug('finding plugins in {path} at depth {depth:d}'.format(path=self.base_path, depth=start_depth))
77 for root, dirs, files in os.walk(self.base_path):76 for root, dirs, files in os.walk(self.base_path):
78 for name in files:77 for name in files:
79 if name.endswith('.py') and not name.startswith('__'):78 if name.endswith('.py') and not name.startswith('__'):
@@ -84,7 +83,9 @@
84 break83 break
85 module_name = name[:-3]84 module_name = name[:-3]
86 # import the modules85 # import the modules
87 self.log_debug('Importing %s from %s. Depth %d' % (module_name, root, this_depth))86 self.log_debug('Importing {name} from {root}. Depth {depth:d}'.format(name=module_name,
87 root=root,
88 depth=this_depth))
88 try:89 try:
89 # Use the "imp" library to try to get around a problem with the PyUNO library which90 # Use the "imp" library to try to get around a problem with the PyUNO library which
90 # monkey-patches the __import__ function to do some magic. This causes issues with our tests.91 # monkey-patches the __import__ function to do some magic. This causes issues with our tests.
@@ -93,21 +94,21 @@
93 # Then load the module (do the actual import) using the details from find_module()94 # Then load the module (do the actual import) using the details from find_module()
94 imp.load_module(module_name, fp, path_name, description)95 imp.load_module(module_name, fp, path_name, description)
95 except ImportError as e:96 except ImportError as e:
96 self.log_exception('Failed to import module %s on path %s: %s'97 self.log_exception('Failed to import module {name} on path {path}: '
97 % (module_name, path, e.args[0]))98 '{args}'.format(name=module_name, path=path, args=e.args[0]))
98 plugin_classes = Plugin.__subclasses__()99 plugin_classes = Plugin.__subclasses__()
99 plugin_objects = []100 plugin_objects = []
100 for p in plugin_classes:101 for p in plugin_classes:
101 try:102 try:
102 plugin = p()103 plugin = p()
103 self.log_debug('Loaded plugin %s' % str(p))104 self.log_debug('Loaded plugin {plugin}'.format(plugin=str(p)))
104 plugin_objects.append(plugin)105 plugin_objects.append(plugin)
105 except TypeError:106 except TypeError:
106 self.log_exception('Failed to load plugin %s' % str(p))107 self.log_exception('Failed to load plugin {plugin}'.format(plugin=str(p)))
107 plugins_list = sorted(plugin_objects, key=lambda plugin: plugin.weight)108 plugins_list = sorted(plugin_objects, key=lambda plugin: plugin.weight)
108 for plugin in plugins_list:109 for plugin in plugins_list:
109 if plugin.check_pre_conditions():110 if plugin.check_pre_conditions():
110 self.log_debug('Plugin %s active' % str(plugin.name))111 self.log_debug('Plugin {plugin} active'.format(plugin=str(plugin.name)))
111 plugin.set_status()112 plugin.set_status()
112 else:113 else:
113 plugin.status = PluginStatus.Disabled114 plugin.status = PluginStatus.Disabled
@@ -175,10 +176,11 @@
175 Loop through all the plugins and give them an opportunity to initialise themselves.176 Loop through all the plugins and give them an opportunity to initialise themselves.
176 """177 """
177 for plugin in self.plugins:178 for plugin in self.plugins:
178 self.log_info('initialising plugins %s in a %s state' % (plugin.name, plugin.is_active()))179 self.log_info('initialising plugins {plugin} in a {state} state'.format(plugin=plugin.name,
180 state=plugin.is_active()))
179 if plugin.is_active():181 if plugin.is_active():
180 plugin.initialise()182 plugin.initialise()
181 self.log_info('Initialisation Complete for %s ' % plugin.name)183 self.log_info('Initialisation Complete for {plugin}'.format(plugin=plugin.name))
182184
183 def finalise_plugins(self):185 def finalise_plugins(self):
184 """186 """
@@ -187,7 +189,7 @@
187 for plugin in self.plugins:189 for plugin in self.plugins:
188 if plugin.is_active():190 if plugin.is_active():
189 plugin.finalise()191 plugin.finalise()
190 self.log_info('Finalisation Complete for %s ' % plugin.name)192 self.log_info('Finalisation Complete for {plugin}'.format(plugin=plugin.name))
191193
192 def get_plugin_by_name(self, name):194 def get_plugin_by_name(self, name):
193 """195 """
194196
=== modified file 'openlp/core/lib/projector/constants.py'
--- openlp/core/lib/projector/constants.py 2016-12-31 11:05:48 +0000
+++ openlp/core/lib/projector/constants.py 2017-01-08 22:05:36 +0000
@@ -131,169 +131,181 @@
131S_NETWORK_SENDING = 400131S_NETWORK_SENDING = 400
132S_NETWORK_RECEIVED = 401132S_NETWORK_RECEIVED = 401
133133
134CONNECTION_ERRORS = {E_NOT_CONNECTED, E_NO_AUTHENTICATION, E_AUTHENTICATION, E_CLASS,134CONNECTION_ERRORS = {
135 E_PREFIX, E_CONNECTION_REFUSED, E_REMOTE_HOST_CLOSED_CONNECTION,135 E_NOT_CONNECTED, E_NO_AUTHENTICATION, E_AUTHENTICATION, E_CLASS,
136 E_HOST_NOT_FOUND, E_SOCKET_ACCESS, E_SOCKET_RESOURCE, E_SOCKET_TIMEOUT,136 E_PREFIX, E_CONNECTION_REFUSED, E_REMOTE_HOST_CLOSED_CONNECTION,
137 E_DATAGRAM_TOO_LARGE, E_NETWORK, E_ADDRESS_IN_USE, E_SOCKET_ADDRESS_NOT_AVAILABLE,137 E_HOST_NOT_FOUND, E_SOCKET_ACCESS, E_SOCKET_RESOURCE, E_SOCKET_TIMEOUT,
138 E_UNSUPPORTED_SOCKET_OPERATION, E_PROXY_AUTHENTICATION_REQUIRED,138 E_DATAGRAM_TOO_LARGE, E_NETWORK, E_ADDRESS_IN_USE, E_SOCKET_ADDRESS_NOT_AVAILABLE,
139 E_SLS_HANDSHAKE_FAILED, E_UNFINISHED_SOCKET_OPERATION, E_PROXY_CONNECTION_REFUSED,139 E_UNSUPPORTED_SOCKET_OPERATION, E_PROXY_AUTHENTICATION_REQUIRED,
140 E_PROXY_CONNECTION_CLOSED, E_PROXY_CONNECTION_TIMEOUT, E_PROXY_NOT_FOUND,140 E_SLS_HANDSHAKE_FAILED, E_UNFINISHED_SOCKET_OPERATION, E_PROXY_CONNECTION_REFUSED,
141 E_PROXY_PROTOCOL, E_UNKNOWN_SOCKET_ERROR141 E_PROXY_CONNECTION_CLOSED, E_PROXY_CONNECTION_TIMEOUT, E_PROXY_NOT_FOUND,
142 }142 E_PROXY_PROTOCOL, E_UNKNOWN_SOCKET_ERROR
143}
143144
144PJLINK_ERRORS = {'ERRA': E_AUTHENTICATION, # Authentication error145PJLINK_ERRORS = {
145 'ERR1': E_UNDEFINED, # Undefined command error146 'ERRA': E_AUTHENTICATION, # Authentication error
146 'ERR2': E_PARAMETER, # Invalid parameter error147 'ERR1': E_UNDEFINED, # Undefined command error
147 'ERR3': E_UNAVAILABLE, # Projector busy148 'ERR2': E_PARAMETER, # Invalid parameter error
148 'ERR4': E_PROJECTOR, # Projector or display failure149 'ERR3': E_UNAVAILABLE, # Projector busy
149 E_AUTHENTICATION: 'ERRA',150 'ERR4': E_PROJECTOR, # Projector or display failure
150 E_UNDEFINED: 'ERR1',151 E_AUTHENTICATION: 'ERRA',
151 E_PARAMETER: 'ERR2',152 E_UNDEFINED: 'ERR1',
152 E_UNAVAILABLE: 'ERR3',153 E_PARAMETER: 'ERR2',
153 E_PROJECTOR: 'ERR4'}154 E_UNAVAILABLE: 'ERR3',
155 E_PROJECTOR: 'ERR4'
156}
154157
155# Map error/status codes to string158# Map error/status codes to string
156ERROR_STRING = {0: 'S_OK',159ERROR_STRING = {
157 E_GENERAL: 'E_GENERAL',160 0: 'S_OK',
158 E_NOT_CONNECTED: 'E_NOT_CONNECTED',161 E_GENERAL: 'E_GENERAL',
159 E_FAN: 'E_FAN',162 E_NOT_CONNECTED: 'E_NOT_CONNECTED',
160 E_LAMP: 'E_LAMP',163 E_FAN: 'E_FAN',
161 E_TEMP: 'E_TEMP',164 E_LAMP: 'E_LAMP',
162 E_COVER: 'E_COVER',165 E_TEMP: 'E_TEMP',
163 E_FILTER: 'E_FILTER',166 E_COVER: 'E_COVER',
164 E_AUTHENTICATION: 'E_AUTHENTICATION',167 E_FILTER: 'E_FILTER',
165 E_NO_AUTHENTICATION: 'E_NO_AUTHENTICATION',168 E_AUTHENTICATION: 'E_AUTHENTICATION',
166 E_UNDEFINED: 'E_UNDEFINED',169 E_NO_AUTHENTICATION: 'E_NO_AUTHENTICATION',
167 E_PARAMETER: 'E_PARAMETER',170 E_UNDEFINED: 'E_UNDEFINED',
168 E_UNAVAILABLE: 'E_UNAVAILABLE',171 E_PARAMETER: 'E_PARAMETER',
169 E_PROJECTOR: 'E_PROJECTOR',172 E_UNAVAILABLE: 'E_UNAVAILABLE',
170 E_INVALID_DATA: 'E_INVALID_DATA',173 E_PROJECTOR: 'E_PROJECTOR',
171 E_WARN: 'E_WARN',174 E_INVALID_DATA: 'E_INVALID_DATA',
172 E_ERROR: 'E_ERROR',175 E_WARN: 'E_WARN',
173 E_CLASS: 'E_CLASS',176 E_ERROR: 'E_ERROR',
174 E_PREFIX: 'E_PREFIX', # Last projector error177 E_CLASS: 'E_CLASS',
175 E_CONNECTION_REFUSED: 'E_CONNECTION_REFUSED', # First QtSocket error178 E_PREFIX: 'E_PREFIX', # Last projector error
176 E_REMOTE_HOST_CLOSED_CONNECTION: 'E_REMOTE_HOST_CLOSED_CONNECTION',179 E_CONNECTION_REFUSED: 'E_CONNECTION_REFUSED', # First QtSocket error
177 E_HOST_NOT_FOUND: 'E_HOST_NOT_FOUND',180 E_REMOTE_HOST_CLOSED_CONNECTION: 'E_REMOTE_HOST_CLOSED_CONNECTION',
178 E_SOCKET_ACCESS: 'E_SOCKET_ACCESS',181 E_HOST_NOT_FOUND: 'E_HOST_NOT_FOUND',
179 E_SOCKET_RESOURCE: 'E_SOCKET_RESOURCE',182 E_SOCKET_ACCESS: 'E_SOCKET_ACCESS',
180 E_SOCKET_TIMEOUT: 'E_SOCKET_TIMEOUT',183 E_SOCKET_RESOURCE: 'E_SOCKET_RESOURCE',
181 E_DATAGRAM_TOO_LARGE: 'E_DATAGRAM_TOO_LARGE',184 E_SOCKET_TIMEOUT: 'E_SOCKET_TIMEOUT',
182 E_NETWORK: 'E_NETWORK',185 E_DATAGRAM_TOO_LARGE: 'E_DATAGRAM_TOO_LARGE',
183 E_ADDRESS_IN_USE: 'E_ADDRESS_IN_USE',186 E_NETWORK: 'E_NETWORK',
184 E_SOCKET_ADDRESS_NOT_AVAILABLE: 'E_SOCKET_ADDRESS_NOT_AVAILABLE',187 E_ADDRESS_IN_USE: 'E_ADDRESS_IN_USE',
185 E_UNSUPPORTED_SOCKET_OPERATION: 'E_UNSUPPORTED_SOCKET_OPERATION',188 E_SOCKET_ADDRESS_NOT_AVAILABLE: 'E_SOCKET_ADDRESS_NOT_AVAILABLE',
186 E_PROXY_AUTHENTICATION_REQUIRED: 'E_PROXY_AUTHENTICATION_REQUIRED',189 E_UNSUPPORTED_SOCKET_OPERATION: 'E_UNSUPPORTED_SOCKET_OPERATION',
187 E_SLS_HANDSHAKE_FAILED: 'E_SLS_HANDSHAKE_FAILED',190 E_PROXY_AUTHENTICATION_REQUIRED: 'E_PROXY_AUTHENTICATION_REQUIRED',
188 E_UNFINISHED_SOCKET_OPERATION: 'E_UNFINISHED_SOCKET_OPERATION',191 E_SLS_HANDSHAKE_FAILED: 'E_SLS_HANDSHAKE_FAILED',
189 E_PROXY_CONNECTION_REFUSED: 'E_PROXY_CONNECTION_REFUSED',192 E_UNFINISHED_SOCKET_OPERATION: 'E_UNFINISHED_SOCKET_OPERATION',
190 E_PROXY_CONNECTION_CLOSED: 'E_PROXY_CONNECTION_CLOSED',193 E_PROXY_CONNECTION_REFUSED: 'E_PROXY_CONNECTION_REFUSED',
191 E_PROXY_CONNECTION_TIMEOUT: 'E_PROXY_CONNECTION_TIMEOUT',194 E_PROXY_CONNECTION_CLOSED: 'E_PROXY_CONNECTION_CLOSED',
192 E_PROXY_NOT_FOUND: 'E_PROXY_NOT_FOUND',195 E_PROXY_CONNECTION_TIMEOUT: 'E_PROXY_CONNECTION_TIMEOUT',
193 E_PROXY_PROTOCOL: 'E_PROXY_PROTOCOL',196 E_PROXY_NOT_FOUND: 'E_PROXY_NOT_FOUND',
194 E_UNKNOWN_SOCKET_ERROR: 'E_UNKNOWN_SOCKET_ERROR'}197 E_PROXY_PROTOCOL: 'E_PROXY_PROTOCOL',
198 E_UNKNOWN_SOCKET_ERROR: 'E_UNKNOWN_SOCKET_ERROR'
199}
195200
196STATUS_STRING = {S_NOT_CONNECTED: 'S_NOT_CONNECTED',201STATUS_STRING = {
197 S_CONNECTING: 'S_CONNECTING',202 S_NOT_CONNECTED: 'S_NOT_CONNECTED',
198 S_CONNECTED: 'S_CONNECTED',203 S_CONNECTING: 'S_CONNECTING',
199 S_STATUS: 'S_STATUS',204 S_CONNECTED: 'S_CONNECTED',
200 S_OFF: 'S_OFF',205 S_STATUS: 'S_STATUS',
201 S_INITIALIZE: 'S_INITIALIZE',206 S_OFF: 'S_OFF',
202 S_STANDBY: 'S_STANDBY',207 S_INITIALIZE: 'S_INITIALIZE',
203 S_WARMUP: 'S_WARMUP',208 S_STANDBY: 'S_STANDBY',
204 S_ON: 'S_ON',209 S_WARMUP: 'S_WARMUP',
205 S_COOLDOWN: 'S_COOLDOWN',210 S_ON: 'S_ON',
206 S_INFO: 'S_INFO',211 S_COOLDOWN: 'S_COOLDOWN',
207 S_NETWORK_SENDING: 'S_NETWORK_SENDING',212 S_INFO: 'S_INFO',
208 S_NETWORK_RECEIVED: 'S_NETWORK_RECEIVED'}213 S_NETWORK_SENDING: 'S_NETWORK_SENDING',
214 S_NETWORK_RECEIVED: 'S_NETWORK_RECEIVED'
215}
209216
210# Map error/status codes to message strings217# Map error/status codes to message strings
211ERROR_MSG = {E_OK: translate('OpenLP.ProjectorConstants', 'OK'), # E_OK | S_OK218ERROR_MSG = {
212 E_GENERAL: translate('OpenLP.ProjectorConstants', 'General projector error'),219 E_OK: translate('OpenLP.ProjectorConstants', 'OK'), # E_OK | S_OK
213 E_NOT_CONNECTED: translate('OpenLP.ProjectorConstants', 'Not connected error'),220 E_GENERAL: translate('OpenLP.ProjectorConstants', 'General projector error'),
214 E_LAMP: translate('OpenLP.ProjectorConstants', 'Lamp error'),221 E_NOT_CONNECTED: translate('OpenLP.ProjectorConstants', 'Not connected error'),
215 E_FAN: translate('OpenLP.ProjectorConstants', 'Fan error'),222 E_LAMP: translate('OpenLP.ProjectorConstants', 'Lamp error'),
216 E_TEMP: translate('OpenLP.ProjectorConstants', 'High temperature detected'),223 E_FAN: translate('OpenLP.ProjectorConstants', 'Fan error'),
217 E_COVER: translate('OpenLP.ProjectorConstants', 'Cover open detected'),224 E_TEMP: translate('OpenLP.ProjectorConstants', 'High temperature detected'),
218 E_FILTER: translate('OpenLP.ProjectorConstants', 'Check filter'),225 E_COVER: translate('OpenLP.ProjectorConstants', 'Cover open detected'),
219 E_AUTHENTICATION: translate('OpenLP.ProjectorConstants', 'Authentication Error'),226 E_FILTER: translate('OpenLP.ProjectorConstants', 'Check filter'),
220 E_UNDEFINED: translate('OpenLP.ProjectorConstants', 'Undefined Command'),227 E_AUTHENTICATION: translate('OpenLP.ProjectorConstants', 'Authentication Error'),
221 E_PARAMETER: translate('OpenLP.ProjectorConstants', 'Invalid Parameter'),228 E_UNDEFINED: translate('OpenLP.ProjectorConstants', 'Undefined Command'),
222 E_UNAVAILABLE: translate('OpenLP.ProjectorConstants', 'Projector Busy'),229 E_PARAMETER: translate('OpenLP.ProjectorConstants', 'Invalid Parameter'),
223 E_PROJECTOR: translate('OpenLP.ProjectorConstants', 'Projector/Display Error'),230 E_UNAVAILABLE: translate('OpenLP.ProjectorConstants', 'Projector Busy'),
224 E_INVALID_DATA: translate('OpenLP.ProjectorConstants', 'Invalid packet received'),231 E_PROJECTOR: translate('OpenLP.ProjectorConstants', 'Projector/Display Error'),
225 E_WARN: translate('OpenLP.ProjectorConstants', 'Warning condition detected'),232 E_INVALID_DATA: translate('OpenLP.ProjectorConstants', 'Invalid packet received'),
226 E_ERROR: translate('OpenLP.ProjectorConstants', 'Error condition detected'),233 E_WARN: translate('OpenLP.ProjectorConstants', 'Warning condition detected'),
227 E_CLASS: translate('OpenLP.ProjectorConstants', 'PJLink class not supported'),234 E_ERROR: translate('OpenLP.ProjectorConstants', 'Error condition detected'),
228 E_PREFIX: translate('OpenLP.ProjectorConstants', 'Invalid prefix character'),235 E_CLASS: translate('OpenLP.ProjectorConstants', 'PJLink class not supported'),
229 E_CONNECTION_REFUSED: translate('OpenLP.ProjectorConstants',236 E_PREFIX: translate('OpenLP.ProjectorConstants', 'Invalid prefix character'),
230 'The connection was refused by the peer (or timed out)'),237 E_CONNECTION_REFUSED: translate('OpenLP.ProjectorConstants',
231 E_REMOTE_HOST_CLOSED_CONNECTION: translate('OpenLP.ProjectorConstants',238 'The connection was refused by the peer (or timed out)'),
232 'The remote host closed the connection'),239 E_REMOTE_HOST_CLOSED_CONNECTION: translate('OpenLP.ProjectorConstants',
233 E_HOST_NOT_FOUND: translate('OpenLP.ProjectorConstants', 'The host address was not found'),240 'The remote host closed the connection'),
234 E_SOCKET_ACCESS: translate('OpenLP.ProjectorConstants',241 E_HOST_NOT_FOUND: translate('OpenLP.ProjectorConstants', 'The host address was not found'),
235 'The socket operation failed because the application '242 E_SOCKET_ACCESS: translate('OpenLP.ProjectorConstants',
236 'lacked the required privileges'),243 'The socket operation failed because the application '
237 E_SOCKET_RESOURCE: translate('OpenLP.ProjectorConstants',244 'lacked the required privileges'),
238 'The local system ran out of resources (e.g., too many sockets)'),245 E_SOCKET_RESOURCE: translate('OpenLP.ProjectorConstants',
239 E_SOCKET_TIMEOUT: translate('OpenLP.ProjectorConstants',246 'The local system ran out of resources (e.g., too many sockets)'),
240 'The socket operation timed out'),247 E_SOCKET_TIMEOUT: translate('OpenLP.ProjectorConstants',
241 E_DATAGRAM_TOO_LARGE: translate('OpenLP.ProjectorConstants',248 'The socket operation timed out'),
242 'The datagram was larger than the operating system\'s limit'),249 E_DATAGRAM_TOO_LARGE: translate('OpenLP.ProjectorConstants',
243 E_NETWORK: translate('OpenLP.ProjectorConstants',250 'The datagram was larger than the operating system\'s limit'),
244 'An error occurred with the network (Possibly someone pulled the plug?)'),251 E_NETWORK: translate('OpenLP.ProjectorConstants',
245 E_ADDRESS_IN_USE: translate('OpenLP.ProjectorConstants',252 'An error occurred with the network (Possibly someone pulled the plug?)'),
246 'The address specified with socket.bind() '253 E_ADDRESS_IN_USE: translate('OpenLP.ProjectorConstants',
247 'is already in use and was set to be exclusive'),254 'The address specified with socket.bind() '
248 E_SOCKET_ADDRESS_NOT_AVAILABLE: translate('OpenLP.ProjectorConstants',255 'is already in use and was set to be exclusive'),
249 'The address specified to socket.bind() '256 E_SOCKET_ADDRESS_NOT_AVAILABLE: translate('OpenLP.ProjectorConstants',
250 'does not belong to the host'),257 'The address specified to socket.bind() '
251 E_UNSUPPORTED_SOCKET_OPERATION: translate('OpenLP.ProjectorConstants',258 'does not belong to the host'),
252 'The requested socket operation is not supported by the local '259 E_UNSUPPORTED_SOCKET_OPERATION: translate('OpenLP.ProjectorConstants',
253 'operating system (e.g., lack of IPv6 support)'),260 'The requested socket operation is not supported by the local '
254 E_PROXY_AUTHENTICATION_REQUIRED: translate('OpenLP.ProjectorConstants',261 'operating system (e.g., lack of IPv6 support)'),
255 'The socket is using a proxy, '262 E_PROXY_AUTHENTICATION_REQUIRED: translate('OpenLP.ProjectorConstants',
256 'and the proxy requires authentication'),263 'The socket is using a proxy, '
257 E_SLS_HANDSHAKE_FAILED: translate('OpenLP.ProjectorConstants',264 'and the proxy requires authentication'),
258 'The SSL/TLS handshake failed'),265 E_SLS_HANDSHAKE_FAILED: translate('OpenLP.ProjectorConstants',
259 E_UNFINISHED_SOCKET_OPERATION: translate('OpenLP.ProjectorConstants',266 'The SSL/TLS handshake failed'),
260 'The last operation attempted has not finished yet '267 E_UNFINISHED_SOCKET_OPERATION: translate('OpenLP.ProjectorConstants',
261 '(still in progress in the background)'),268 'The last operation attempted has not finished yet '
262 E_PROXY_CONNECTION_REFUSED: translate('OpenLP.ProjectorConstants',269 '(still in progress in the background)'),
263 'Could not contact the proxy server because the connection '270 E_PROXY_CONNECTION_REFUSED: translate('OpenLP.ProjectorConstants',
264 'to that server was denied'),271 'Could not contact the proxy server because the connection '
265 E_PROXY_CONNECTION_CLOSED: translate('OpenLP.ProjectorConstants',272 'to that server was denied'),
266 'The connection to the proxy server was closed unexpectedly '273 E_PROXY_CONNECTION_CLOSED: translate('OpenLP.ProjectorConstants',
267 '(before the connection to the final peer was established)'),274 'The connection to the proxy server was closed unexpectedly '
268 E_PROXY_CONNECTION_TIMEOUT: translate('OpenLP.ProjectorConstants',275 '(before the connection to the final peer was established)'),
269 'The connection to the proxy server timed out or the proxy '276 E_PROXY_CONNECTION_TIMEOUT: translate('OpenLP.ProjectorConstants',
270 'server stopped responding in the authentication phase.'),277 'The connection to the proxy server timed out or the proxy '
271 E_PROXY_NOT_FOUND: translate('OpenLP.ProjectorConstants',278 'server stopped responding in the authentication phase.'),
272 'The proxy address set with setProxy() was not found'),279 E_PROXY_NOT_FOUND: translate('OpenLP.ProjectorConstants',
273 E_PROXY_PROTOCOL: translate('OpenLP.ProjectorConstants',280 'The proxy address set with setProxy() was not found'),
274 'The connection negotiation with the proxy server failed because the '281 E_PROXY_PROTOCOL: translate('OpenLP.ProjectorConstants',
275 'response from the proxy server could not be understood'),282 'The connection negotiation with the proxy server failed because the '
276 E_UNKNOWN_SOCKET_ERROR: translate('OpenLP.ProjectorConstants', 'An unidentified error occurred'),283 'response from the proxy server could not be understood'),
277 S_NOT_CONNECTED: translate('OpenLP.ProjectorConstants', 'Not connected'),284 E_UNKNOWN_SOCKET_ERROR: translate('OpenLP.ProjectorConstants', 'An unidentified error occurred'),
278 S_CONNECTING: translate('OpenLP.ProjectorConstants', 'Connecting'),285 S_NOT_CONNECTED: translate('OpenLP.ProjectorConstants', 'Not connected'),
279 S_CONNECTED: translate('OpenLP.ProjectorConstants', 'Connected'),286 S_CONNECTING: translate('OpenLP.ProjectorConstants', 'Connecting'),
280 S_STATUS: translate('OpenLP.ProjectorConstants', 'Getting status'),287 S_CONNECTED: translate('OpenLP.ProjectorConstants', 'Connected'),
281 S_OFF: translate('OpenLP.ProjectorConstants', 'Off'),288 S_STATUS: translate('OpenLP.ProjectorConstants', 'Getting status'),
282 S_INITIALIZE: translate('OpenLP.ProjectorConstants', 'Initialize in progress'),289 S_OFF: translate('OpenLP.ProjectorConstants', 'Off'),
283 S_STANDBY: translate('OpenLP.ProjectorConstants', 'Power in standby'),290 S_INITIALIZE: translate('OpenLP.ProjectorConstants', 'Initialize in progress'),
284 S_WARMUP: translate('OpenLP.ProjectorConstants', 'Warmup in progress'),291 S_STANDBY: translate('OpenLP.ProjectorConstants', 'Power in standby'),
285 S_ON: translate('OpenLP.ProjectorConstants', 'Power is on'),292 S_WARMUP: translate('OpenLP.ProjectorConstants', 'Warmup in progress'),
286 S_COOLDOWN: translate('OpenLP.ProjectorConstants', 'Cooldown in progress'),293 S_ON: translate('OpenLP.ProjectorConstants', 'Power is on'),
287 S_INFO: translate('OpenLP.ProjectorConstants', 'Projector Information available'),294 S_COOLDOWN: translate('OpenLP.ProjectorConstants', 'Cooldown in progress'),
288 S_NETWORK_SENDING: translate('OpenLP.ProjectorConstants', 'Sending data'),295 S_INFO: translate('OpenLP.ProjectorConstants', 'Projector Information available'),
289 S_NETWORK_RECEIVED: translate('OpenLP.ProjectorConstants', 'Received data')}296 S_NETWORK_SENDING: translate('OpenLP.ProjectorConstants', 'Sending data'),
297 S_NETWORK_RECEIVED: translate('OpenLP.ProjectorConstants', 'Received data')
298}
290299
291# Map for ERST return codes to string300# Map for ERST return codes to string
292PJLINK_ERST_STATUS = {'0': ERROR_STRING[E_OK],301PJLINK_ERST_STATUS = {
293 '1': ERROR_STRING[E_WARN],302 '0': ERROR_STRING[E_OK],
294 '2': ERROR_STRING[E_ERROR]}303 '1': ERROR_STRING[E_WARN],
304 '2': ERROR_STRING[E_ERROR]
305}
295306
296# Map for POWR return codes to status code307# Map for POWR return codes to status code
308<<<<<<< TREE
297PJLINK_POWR_STATUS = {'0': S_STANDBY,309PJLINK_POWR_STATUS = {'0': S_STANDBY,
298 '1': S_ON,310 '1': S_ON,
299 '2': S_COOLDOWN,311 '2': S_COOLDOWN,
@@ -355,3 +367,71 @@
355 '58': translate('OpenLP.DB', 'Network 8'),367 '58': translate('OpenLP.DB', 'Network 8'),
356 '59': translate('OpenLP.DB', 'Network 9')368 '59': translate('OpenLP.DB', 'Network 9')
357 }369 }
370=======
371PJLINK_POWR_STATUS = {
372 '0': S_STANDBY,
373 '1': S_ON,
374 '2': S_COOLDOWN,
375 '3': S_WARMUP,
376 S_STANDBY: '0',
377 S_ON: '1',
378 S_COOLDOWN: '2',
379 S_WARMUP: '3'
380}
381
382PJLINK_DEFAULT_SOURCES = {
383 '1': translate('OpenLP.DB', 'RGB'),
384 '2': translate('OpenLP.DB', 'Video'),
385 '3': translate('OpenLP.DB', 'Digital'),
386 '4': translate('OpenLP.DB', 'Storage'),
387 '5': translate('OpenLP.DB', 'Network')
388}
389
390PJLINK_DEFAULT_CODES = {
391 '11': translate('OpenLP.DB', 'RGB 1'),
392 '12': translate('OpenLP.DB', 'RGB 2'),
393 '13': translate('OpenLP.DB', 'RGB 3'),
394 '14': translate('OpenLP.DB', 'RGB 4'),
395 '15': translate('OpenLP.DB', 'RGB 5'),
396 '16': translate('OpenLP.DB', 'RGB 6'),
397 '17': translate('OpenLP.DB', 'RGB 7'),
398 '18': translate('OpenLP.DB', 'RGB 8'),
399 '19': translate('OpenLP.DB', 'RGB 9'),
400 '21': translate('OpenLP.DB', 'Video 1'),
401 '22': translate('OpenLP.DB', 'Video 2'),
402 '23': translate('OpenLP.DB', 'Video 3'),
403 '24': translate('OpenLP.DB', 'Video 4'),
404 '25': translate('OpenLP.DB', 'Video 5'),
405 '26': translate('OpenLP.DB', 'Video 6'),
406 '27': translate('OpenLP.DB', 'Video 7'),
407 '28': translate('OpenLP.DB', 'Video 8'),
408 '29': translate('OpenLP.DB', 'Video 9'),
409 '31': translate('OpenLP.DB', 'Digital 1'),
410 '32': translate('OpenLP.DB', 'Digital 2'),
411 '33': translate('OpenLP.DB', 'Digital 3'),
412 '34': translate('OpenLP.DB', 'Digital 4'),
413 '35': translate('OpenLP.DB', 'Digital 5'),
414 '36': translate('OpenLP.DB', 'Digital 6'),
415 '37': translate('OpenLP.DB', 'Digital 7'),
416 '38': translate('OpenLP.DB', 'Digital 8'),
417 '39': translate('OpenLP.DB', 'Digital 9'),
418 '41': translate('OpenLP.DB', 'Storage 1'),
419 '42': translate('OpenLP.DB', 'Storage 2'),
420 '43': translate('OpenLP.DB', 'Storage 3'),
421 '44': translate('OpenLP.DB', 'Storage 4'),
422 '45': translate('OpenLP.DB', 'Storage 5'),
423 '46': translate('OpenLP.DB', 'Storage 6'),
424 '47': translate('OpenLP.DB', 'Storage 7'),
425 '48': translate('OpenLP.DB', 'Storage 8'),
426 '49': translate('OpenLP.DB', 'Storage 9'),
427 '51': translate('OpenLP.DB', 'Network 1'),
428 '52': translate('OpenLP.DB', 'Network 2'),
429 '53': translate('OpenLP.DB', 'Network 3'),
430 '54': translate('OpenLP.DB', 'Network 4'),
431 '55': translate('OpenLP.DB', 'Network 5'),
432 '56': translate('OpenLP.DB', 'Network 6'),
433 '57': translate('OpenLP.DB', 'Network 7'),
434 '58': translate('OpenLP.DB', 'Network 8'),
435 '59': translate('OpenLP.DB', 'Network 9')
436}
437>>>>>>> MERGE-SOURCE
358438
=== modified file 'openlp/core/lib/projector/db.py'
--- openlp/core/lib/projector/db.py 2016-12-31 11:05:48 +0000
+++ openlp/core/lib/projector/db.py 2017-01-08 22:05:36 +0000
@@ -40,13 +40,12 @@
4040
41from sqlalchemy import Column, ForeignKey, Integer, MetaData, String, and_41from sqlalchemy import Column, ForeignKey, Integer, MetaData, String, and_
42from sqlalchemy.ext.declarative import declarative_base, declared_attr42from sqlalchemy.ext.declarative import declarative_base, declared_attr
43from sqlalchemy.orm import backref, relationship43from sqlalchemy.orm import relationship
4444
45from openlp.core.lib.db import Manager, init_db, init_url45from openlp.core.lib.db import Manager, init_db, init_url
46from openlp.core.lib.projector.constants import PJLINK_DEFAULT_CODES46from openlp.core.lib.projector.constants import PJLINK_DEFAULT_CODES
4747
48metadata = MetaData()48Base = declarative_base(MetaData())
49Base = declarative_base(metadata)
5049
5150
52class CommonBase(object):51class CommonBase(object):
@@ -54,8 +53,8 @@
54 Base class to automate table name and ID column.53 Base class to automate table name and ID column.
55 """54 """
56 @declared_attr55 @declared_attr
57 def __tablename__(cls):56 def __tablename__(self):
58 return cls.__name__.lower()57 return self.__name__.lower()
5958
60 id = Column(Integer, primary_key=True)59 id = Column(Integer, primary_key=True)
6160
@@ -74,7 +73,7 @@
74 """73 """
75 Returns a basic representation of a Manufacturer table entry.74 Returns a basic representation of a Manufacturer table entry.
76 """75 """
77 return '<Manufacturer(name="%s")>' % self.name76 return '<Manufacturer(name="{name}")>'.format(name=self.name)
7877
79 name = Column(String(30))78 name = Column(String(30))
80 models = relationship('Model',79 models = relationship('Model',
@@ -101,7 +100,7 @@
101 """100 """
102 Returns a basic representation of a Model table entry.101 Returns a basic representation of a Model table entry.
103 """102 """
104 return '<Model(name=%s)>' % self.name103 return '<Model(name={name})>'.format(name=self.name)
105104
106 manufacturer_id = Column(Integer, ForeignKey('manufacturer.id'))105 manufacturer_id = Column(Integer, ForeignKey('manufacturer.id'))
107 name = Column(String(20))106 name = Column(String(20))
@@ -131,8 +130,9 @@
131 """130 """
132 Return basic representation of Source table entry.131 Return basic representation of Source table entry.
133 """132 """
134 return '<Source(pjlink_name="%s", pjlink_code="%s", text="%s")>' % \133 return '<Source(pjlink_name="{name}", pjlink_code="{code}", text="{text}")>'.format(name=self.pjlink_name,
135 (self.pjlink_name, self.pjlink_code, self.text)134 code=self.pjlink_code,
135 text=self.text)
136 model_id = Column(Integer, ForeignKey('model.id'))136 model_id = Column(Integer, ForeignKey('model.id'))
137 pjlink_name = Column(String(15))137 pjlink_name = Column(String(15))
138 pjlink_code = Column(String(2))138 pjlink_code = Column(String(2))
@@ -162,11 +162,22 @@
162 """162 """
163 Return basic representation of Source table entry.163 Return basic representation of Source table entry.
164 """164 """
165 return '< Projector(id="%s", ip="%s", port="%s", pin="%s", name="%s", location="%s",' \165 return '< Projector(id="{data}", ip="{ip}", port="{port}", pin="{pin}", name="{name}", ' \
166 'notes="%s", pjlink_name="%s", manufacturer="%s", model="%s", other="%s",' \166 'location="{location}", notes="{notes}", pjlink_name="{pjlink_name}", ' \
167 'sources="%s", source_list="%s") >' % (self.id, self.ip, self.port, self.pin, self.name, self.location,167 'manufacturer="{manufacturer}", model="{model}", other="{other}", ' \
168 self.notes, self.pjlink_name, self.manufacturer, self.model,168 'sources="{sources}", source_list="{source_list}") >'.format(data=self.id,
169 self.other, self.sources, self.source_list)169 ip=self.ip,
170 port=self.port,
171 pin=self.pin,
172 name=self.name,
173 location=self.location,
174 notes=self.notes,
175 pjlink_name=self.pjlink_name,
176 manufacturer=self.manufacturer,
177 model=self.model,
178 other=self.other,
179 sources=self.sources,
180 source_list=self.source_list)
170 ip = Column(String(100))181 ip = Column(String(100))
171 port = Column(String(8))182 port = Column(String(8))
172 pin = Column(String(20))183 pin = Column(String(20))
@@ -203,10 +214,11 @@
203 """214 """
204 Return basic representation of Source table entry.215 Return basic representation of Source table entry.
205 """216 """
206 return '<ProjectorSource(id="%s", code="%s", text="%s", projector_id="%s")>' % (self.id,217 return '<ProjectorSource(id="{data}", code="{code}", text="{text}", ' \
207 self.code,218 'projector_id="{projector_id}")>'.format(data=self.id,
208 self.text,219 code=self.code,
209 self.projector_id)220 text=self.text,
221 projector_id=self.projector_id)
210 code = Column(String(3))222 code = Column(String(3))
211 text = Column(String(20))223 text = Column(String(20))
212 projector_id = Column(Integer, ForeignKey('projector.id'))224 projector_id = Column(Integer, ForeignKey('projector.id'))
@@ -217,10 +229,10 @@
217 Class to access the projector database.229 Class to access the projector database.
218 """230 """
219 def __init__(self, *args, **kwargs):231 def __init__(self, *args, **kwargs):
220 log.debug('ProjectorDB().__init__(args="%s", kwargs="%s")' % (args, kwargs))232 log.debug('ProjectorDB().__init__(args="{arg}", kwargs="{kwarg}")'.format(arg=args, kwarg=kwargs))
221 super().__init__(plugin_name='projector', init_schema=self.init_schema)233 super().__init__(plugin_name='projector', init_schema=self.init_schema)
222 log.debug('ProjectorDB() Initialized using db url %s' % self.db_url)234 log.debug('ProjectorDB() Initialized using db url {db}'.format(db=self.db_url))
223 log.debug('Session: %s', self.session)235 log.debug('Session: {session}'.format(session=self.session))
224236
225 def init_schema(self, *args, **kwargs):237 def init_schema(self, *args, **kwargs):
226 """238 """
@@ -240,13 +252,14 @@
240 :param dbid: DB record id252 :param dbid: DB record id
241 :returns: Projector() instance253 :returns: Projector() instance
242 """254 """
243 log.debug('get_projector_by_id(id="%s")' % dbid)255 log.debug('get_projector_by_id(id="{data}")'.format(data=dbid))
244 projector = self.get_object_filtered(Projector, Projector.id == dbid)256 projector = self.get_object_filtered(Projector, Projector.id == dbid)
245 if projector is None:257 if projector is None:
246 # Not found258 # Not found
247 log.warn('get_projector_by_id() did not find %s' % id)259 log.warning('get_projector_by_id() did not find {data}'.format(data=id))
248 return None260 return None
249 log.debug('get_projectorby_id() returning 1 entry for "%s" id="%s"' % (dbid, projector.id))261 log.debug('get_projectorby_id() returning 1 entry for "{entry}" id="{data}"'.format(entry=dbid,
262 data=projector.id))
250 return projector263 return projector
251264
252 def get_projector_all(self):265 def get_projector_all(self):
@@ -262,7 +275,7 @@
262 return return_list275 return return_list
263 for new_projector in new_list:276 for new_projector in new_list:
264 return_list.append(new_projector)277 return_list.append(new_projector)
265 log.debug('get_all() returning %s item(s)' % len(return_list))278 log.debug('get_all() returning {items} item(s)'.format(items=len(return_list)))
266 return return_list279 return return_list
267280
268 def get_projector_by_ip(self, ip):281 def get_projector_by_ip(self, ip):
@@ -276,9 +289,10 @@
276 projector = self.get_object_filtered(Projector, Projector.ip == ip)289 projector = self.get_object_filtered(Projector, Projector.ip == ip)
277 if projector is None:290 if projector is None:
278 # Not found291 # Not found
279 log.warn('get_projector_by_ip() did not find %s' % ip)292 log.warning('get_projector_by_ip() did not find {ip}'.format(ip=ip))
280 return None293 return None
281 log.debug('get_projectorby_ip() returning 1 entry for "%s" id="%s"' % (ip, projector.id))294 log.debug('get_projectorby_ip() returning 1 entry for "{ip}" id="{data}"'.format(ip=ip,
295 data=projector.id))
282 return projector296 return projector
283297
284 def get_projector_by_name(self, name):298 def get_projector_by_name(self, name):
@@ -288,13 +302,14 @@
288 :param name: Name of projector302 :param name: Name of projector
289 :returns: Projector() instance303 :returns: Projector() instance
290 """304 """
291 log.debug('get_projector_by_name(name="%s")' % name)305 log.debug('get_projector_by_name(name="{name}")'.format(name=name))
292 projector = self.get_object_filtered(Projector, Projector.name == name)306 projector = self.get_object_filtered(Projector, Projector.name == name)
293 if projector is None:307 if projector is None:
294 # Not found308 # Not found
295 log.warn('get_projector_by_name() did not find "%s"' % name)309 log.warning('get_projector_by_name() did not find "{name}"'.format(name=name))
296 return None310 return None
297 log.debug('get_projector_by_name() returning one entry for "%s" id="%s"' % (name, projector.id))311 log.debug('get_projector_by_name() returning one entry for "{name}" id="{data}"'.format(name=name,
312 data=projector.id))
298 return projector313 return projector
299314
300 def add_projector(self, projector):315 def add_projector(self, projector):
@@ -308,13 +323,13 @@
308 """323 """
309 old_projector = self.get_object_filtered(Projector, Projector.ip == projector.ip)324 old_projector = self.get_object_filtered(Projector, Projector.ip == projector.ip)
310 if old_projector is not None:325 if old_projector is not None:
311 log.warn('add_new() skipping entry ip="%s" (Already saved)' % old_projector.ip)326 log.warning('add_new() skipping entry ip="{ip}" (Already saved)'.format(ip=old_projector.ip))
312 return False327 return False
313 log.debug('add_new() saving new entry')328 log.debug('add_new() saving new entry')
314 log.debug('ip="%s", name="%s", location="%s"' % (projector.ip,329 log.debug('ip="{ip}", name="{name}", location="{location}"'.format(ip=projector.ip,
315 projector.name,330 name=projector.name,
316 projector.location))331 location=projector.location))
317 log.debug('notes="%s"' % projector.notes)332 log.debug('notes="{notes}"'.format(notes=projector.notes))
318 return self.save_object(projector)333 return self.save_object(projector)
319334
320 def update_projector(self, projector=None):335 def update_projector(self, projector=None):
@@ -333,7 +348,7 @@
333 if old_projector is None:348 if old_projector is None:
334 log.error('Edit called on projector instance not in database - cancelled')349 log.error('Edit called on projector instance not in database - cancelled')
335 return False350 return False
336 log.debug('(%s) Updating projector with dbid=%s' % (projector.ip, projector.id))351 log.debug('({ip}) Updating projector with dbid={dbid}'.format(ip=projector.ip, dbid=projector.id))
337 old_projector.ip = projector.ip352 old_projector.ip = projector.ip
338 old_projector.name = projector.name353 old_projector.name = projector.name
339 old_projector.location = projector.location354 old_projector.location = projector.location
@@ -357,9 +372,9 @@
357 """372 """
358 deleted = self.delete_object(Projector, projector.id)373 deleted = self.delete_object(Projector, projector.id)
359 if deleted:374 if deleted:
360 log.debug('delete_by_id() Removed entry id="%s"' % projector.id)375 log.debug('delete_by_id() Removed entry id="{data}"'.format(data=projector.id))
361 else:376 else:
362 log.error('delete_by_id() Entry id="%s" not deleted for some reason' % projector.id)377 log.error('delete_by_id() Entry id="{data}" not deleted for some reason'.format(data=projector.id))
363 return deleted378 return deleted
364379
365 def get_source_list(self, projector):380 def get_source_list(self, projector):
@@ -392,12 +407,12 @@
392 :param source: ProjectorSource id407 :param source: ProjectorSource id
393 :returns: ProjetorSource instance or None408 :returns: ProjetorSource instance or None
394 """409 """
395 source_entry = self.get_object_filtered(ProjetorSource, ProjectorSource.id == source)410 source_entry = self.get_object_filtered(ProjectorSource, ProjectorSource.id == source)
396 if source_entry is None:411 if source_entry is None:
397 # Not found412 # Not found
398 log.warn('get_source_by_id() did not find "%s"' % source)413 log.warning('get_source_by_id() did not find "{source}"'.format(source=source))
399 return None414 return None
400 log.debug('get_source_by_id() returning one entry for "%s""' % (source))415 log.debug('get_source_by_id() returning one entry for "{source}""'.format(source=source))
401 return source_entry416 return source_entry
402417
403 def get_source_by_code(self, code, projector_id):418 def get_source_by_code(self, code, projector_id):
@@ -411,11 +426,14 @@
411 source_entry = self.get_object_filtered(ProjectorSource,426 source_entry = self.get_object_filtered(ProjectorSource,
412 and_(ProjectorSource.code == code,427 and_(ProjectorSource.code == code,
413 ProjectorSource.projector_id == projector_id))428 ProjectorSource.projector_id == projector_id))
429
414 if source_entry is None:430 if source_entry is None:
415 # Not found431 # Not found
416 log.warn('get_source_by_id() did not find code="%s" projector_id="%s"' % (code, projector_id))432 log.warning('get_source_by_id() not found')
433 log.warning('code="{code}" projector_id="{data}"'.format(code=code, data=projector_id))
417 return None434 return None
418 log.debug('get_source_by_id() returning one entry for code="%s" projector_id="%s"' % (code, projector_id))435 log.debug('get_source_by_id() returning one entry')
436 log.debug('code="{code}" projector_id="{data}"'.format(code=code, data=projector_id))
419 return source_entry437 return source_entry
420438
421 def add_source(self, source):439 def add_source(self, source):
@@ -424,6 +442,6 @@
424442
425 :param source: ProjectorSource() instance to add443 :param source: ProjectorSource() instance to add
426 """444 """
427 log.debug('Saving ProjectorSource(projector_id="%s" code="%s" text="%s")' % (source.projector_id,445 log.debug('Saving ProjectorSource(projector_id="{data}" '
428 source.code, source.text))446 'code="{code}" text="{text}")'.format(data=source.projector_id, code=source.code, text=source.text))
429 return self.save_object(source)447 return self.save_object(source)
430448
=== modified file 'openlp/core/lib/projector/pjlink1.py'
--- openlp/core/lib/projector/pjlink1.py 2016-12-31 11:05:48 +0000
+++ openlp/core/lib/projector/pjlink1.py 2017-01-08 22:05:36 +0000
@@ -46,15 +46,24 @@
4646
47from codecs import decode47from codecs import decode
4848
49from PyQt5.QtCore import pyqtSignal, pyqtSlot49from PyQt5 import QtCore, QtNetwork
50from PyQt5.QtNetwork import QAbstractSocket, QTcpSocket
5150
51<<<<<<< TREE
52from openlp.core.common import translate, md5_hash52from openlp.core.common import translate, md5_hash
53from openlp.core.lib.projector.constants import *53from openlp.core.lib.projector.constants import *
54=======
55from openlp.core.common import translate, qmd5_hash
56from openlp.core.lib.projector.constants import CONNECTION_ERRORS, CR, ERROR_MSG, ERROR_STRING, \
57 E_AUTHENTICATION, E_CONNECTION_REFUSED, E_GENERAL, E_INVALID_DATA, E_NETWORK, E_NOT_CONNECTED, \
58 E_PARAMETER, E_PROJECTOR, E_SOCKET_TIMEOUT, E_UNAVAILABLE, E_UNDEFINED, PJLINK_ERRORS, \
59 PJLINK_ERST_STATUS, PJLINK_MAX_PACKET, PJLINK_PORT, PJLINK_POWR_STATUS, PJLINK_VALID_CMD, \
60 STATUS_STRING, S_CONNECTED, S_CONNECTING, S_NETWORK_RECEIVED, S_NETWORK_SENDING, S_NOT_CONNECTED, \
61 S_OFF, S_OK, S_ON, S_STATUS
62>>>>>>> MERGE-SOURCE
5463
55# Shortcuts64# Shortcuts
56SocketError = QAbstractSocket.SocketError65SocketError = QtNetwork.QAbstractSocket.SocketError
57SocketSTate = QAbstractSocket.SocketState66SocketSTate = QtNetwork.QAbstractSocket.SocketState
5867
59PJLINK_PREFIX = '%'68PJLINK_PREFIX = '%'
60PJLINK_CLASS = '1'69PJLINK_CLASS = '1'
@@ -62,11 +71,12 @@
62PJLINK_SUFFIX = CR71PJLINK_SUFFIX = CR
6372
6473
65class PJLink1(QTcpSocket):74class PJLink1(QtNetwork.QTcpSocket):
66 """75 """
67 Socket service for connecting to a PJLink-capable projector.76 Socket service for connecting to a PJLink-capable projector.
68 """77 """
69 # Signals sent by this module78 # Signals sent by this module
79<<<<<<< TREE
70 changeStatus = pyqtSignal(str, int, str)80 changeStatus = pyqtSignal(str, int, str)
71 projectorNetwork = pyqtSignal(str, int) # Projector network activity81 projectorNetwork = pyqtSignal(str, int) # Projector network activity
72 projectorStatus = pyqtSignal(int) # Status update82 projectorStatus = pyqtSignal(int) # Status update
@@ -74,6 +84,15 @@
74 projectorNoAuthentication = pyqtSignal(str) # PIN set and no authentication needed84 projectorNoAuthentication = pyqtSignal(str) # PIN set and no authentication needed
75 projectorReceivedData = pyqtSignal() # Notify when received data finished processing85 projectorReceivedData = pyqtSignal() # Notify when received data finished processing
76 projectorUpdateIcons = pyqtSignal() # Update the status icons on toolbar86 projectorUpdateIcons = pyqtSignal() # Update the status icons on toolbar
87=======
88 changeStatus = QtCore.pyqtSignal(str, int, str)
89 projectorNetwork = QtCore.pyqtSignal(int) # Projector network activity
90 projectorStatus = QtCore.pyqtSignal(int) # Status update
91 projectorAuthentication = QtCore.pyqtSignal(str) # Authentication error
92 projectorNoAuthentication = QtCore.pyqtSignal(str) # PIN set and no authentication needed
93 projectorReceivedData = QtCore.pyqtSignal() # Notify when received data finished processing
94 projectorUpdateIcons = QtCore.pyqtSignal() # Update the status icons on toolbar
95>>>>>>> MERGE-SOURCE
7796
78 def __init__(self, name=None, ip=None, port=PJLINK_PORT, pin=None, *args, **kwargs):97 def __init__(self, name=None, ip=None, port=PJLINK_PORT, pin=None, *args, **kwargs):
79 """98 """
@@ -116,8 +135,8 @@
116 self.error_status = S_OK135 self.error_status = S_OK
117 # Socket information136 # Socket information
118 # Add enough space to input buffer for extraneous \n \r137 # Add enough space to input buffer for extraneous \n \r
119 self.maxSize = PJLINK_MAX_PACKET + 2138 self.max_size = PJLINK_MAX_PACKET + 2
120 self.setReadBufferSize(self.maxSize)139 self.setReadBufferSize(self.max_size)
121 # PJLink information140 # PJLink information
122 self.pjlink_class = '1' # Default class141 self.pjlink_class = '1' # Default class
123 self.reset_information()142 self.reset_information()
@@ -129,19 +148,20 @@
129 # Socket timer for some possible brain-dead projectors or network cable pulled148 # Socket timer for some possible brain-dead projectors or network cable pulled
130 self.socket_timer = None149 self.socket_timer = None
131 # Map command to function150 # Map command to function
132 self.PJLINK1_FUNC = {'AVMT': self.process_avmt,151 self.pjlink1_functions = {
133 'CLSS': self.process_clss,152 'AVMT': self.process_avmt,
134 'ERST': self.process_erst,153 'CLSS': self.process_clss,
135 'INFO': self.process_info,154 'ERST': self.process_erst,
136 'INF1': self.process_inf1,155 'INFO': self.process_info,
137 'INF2': self.process_inf2,156 'INF1': self.process_inf1,
138 'INPT': self.process_inpt,157 'INF2': self.process_inf2,
139 'INST': self.process_inst,158 'INPT': self.process_inpt,
140 'LAMP': self.process_lamp,159 'INST': self.process_inst,
141 'NAME': self.process_name,160 'LAMP': self.process_lamp,
142 'PJLINK': self.check_login,161 'NAME': self.process_name,
143 'POWR': self.process_powr162 'PJLINK': self.check_login,
144 }163 'POWR': self.process_powr
164 }
145165
146 def reset_information(self):166 def reset_information(self):
147 """167 """
@@ -291,7 +311,7 @@
291 message=status_message if msg is None else msg))311 message=status_message if msg is None else msg))
292 self.changeStatus.emit(self.ip, status, message)312 self.changeStatus.emit(self.ip, status, message)
293313
294 @pyqtSlot()314 @QtCore.pyqtSlot()
295 def check_login(self, data=None):315 def check_login(self, data=None):
296 """316 """
297 Processes the initial connection and authentication (if needed).317 Processes the initial connection and authentication (if needed).
@@ -309,8 +329,8 @@
309 log.error('({ip}) Socket timeout waiting for login'.format(ip=self.ip))329 log.error('({ip}) Socket timeout waiting for login'.format(ip=self.ip))
310 self.change_status(E_SOCKET_TIMEOUT)330 self.change_status(E_SOCKET_TIMEOUT)
311 return331 return
312 read = self.readLine(self.maxSize)332 read = self.readLine(self.max_size)
313 dontcare = self.readLine(self.maxSize) # Clean out the trailing \r\n333 dontcare = self.readLine(self.max_size) # Clean out the trailing \r\n
314 if read is None:334 if read is None:
315 log.warning('({ip}) read is None - socket error?'.format(ip=self.ip))335 log.warning('({ip}) read is None - socket error?'.format(ip=self.ip))
316 return336 return
@@ -320,8 +340,13 @@
320 data = decode(read, 'ascii')340 data = decode(read, 'ascii')
321 # Possibility of extraneous data on input when reading.341 # Possibility of extraneous data on input when reading.
322 # Clean out extraneous characters in buffer.342 # Clean out extraneous characters in buffer.
343<<<<<<< TREE
323 dontcare = self.readLine(self.maxSize)344 dontcare = self.readLine(self.maxSize)
324 log.debug('({ip}) check_login() read "{data}"'.format(ip=self.ip, data=data.strip()))345 log.debug('({ip}) check_login() read "{data}"'.format(ip=self.ip, data=data.strip()))
346=======
347 dontcare = self.readLine(self.max_size)
348 log.debug('({ip}) check_login() read "{data}"'.format(ip=self.ip, data=data.strip()))
349>>>>>>> MERGE-SOURCE
325 # At this point, we should only have the initial login prompt with350 # At this point, we should only have the initial login prompt with
326 # possible authentication351 # possible authentication
327 # PJLink initial login will be:352 # PJLink initial login will be:
@@ -354,6 +379,7 @@
354 return379 return
355 elif data_check[1] == '1':380 elif data_check[1] == '1':
356 # Authenticated login with salt381 # Authenticated login with salt
382<<<<<<< TREE
357 if self.pin is None:383 if self.pin is None:
358 log.warning('({ip}) Authenticated connection but no pin set'.format(ip=self.name))384 log.warning('({ip}) Authenticated connection but no pin set'.format(ip=self.name))
359 self.disconnect_from_host()385 self.disconnect_from_host()
@@ -365,20 +391,34 @@
365 log.debug('({ip}) Setting hash with salt="{data}"'.format(ip=self.ip, data=data_check[2]))391 log.debug('({ip}) Setting hash with salt="{data}"'.format(ip=self.ip, data=data_check[2]))
366 log.debug('({ip}) pin="{data}"'.format(ip=self.ip, data=self.pin))392 log.debug('({ip}) pin="{data}"'.format(ip=self.ip, data=self.pin))
367 salt = md5_hash(salt=data_check[2].encode('ascii'), data=self.pin.encode('ascii'))393 salt = md5_hash(salt=data_check[2].encode('ascii'), data=self.pin.encode('ascii'))
394=======
395 if self.pin is None:
396 log.warning('({ip}) Authenticated connection but no pin set'.format(ip=self.name))
397 self.disconnect_from_host()
398 self.change_status(E_AUTHENTICATION)
399 log.debug('({ip}) Emitting projectorAuthentication() signal'.format(ip=self.name))
400 self.projectorAuthentication.emit(self.name)
401 return
402 else:
403 log.debug('({ip}) Setting hash with salt="{data}"'.format(ip=self.ip, data=data_check[2]))
404 log.debug('({ip}) pin="{data}"'.format(ip=self.ip, data=self.pin))
405 data_hash = str(qmd5_hash(salt=data_check[2].encode('utf-8'), data=self.pin.encode('utf-8')),
406 encoding='ascii')
407>>>>>>> MERGE-SOURCE
368 else:408 else:
369 salt = None409 data_hash = None
370 # We're connected at this point, so go ahead and do regular I/O410 # We're connected at this point, so go ahead and setup regular I/O
371 self.readyRead.connect(self.get_data)411 self.readyRead.connect(self.get_data)
372 self.projectorReceivedData.connect(self._send_command)412 self.projectorReceivedData.connect(self._send_command)
373 # Initial data we should know about413 # Initial data we should know about
374 self.send_command(cmd='CLSS', salt=salt)414 self.send_command(cmd='CLSS', salt=data_hash)
375 self.waitForReadyRead()415 self.waitForReadyRead()
376 if (not self.no_poll) and (self.state() == self.ConnectedState):416 if (not self.no_poll) and (self.state() == self.ConnectedState):
377 log.debug('({ip}) Starting timer'.format(ip=self.ip))417 log.debug('({ip}) Starting timer'.format(ip=self.ip))
378 self.timer.setInterval(2000) # Set 2 seconds for initial information418 self.timer.setInterval(2000) # Set 2 seconds for initial information
379 self.timer.start()419 self.timer.start()
380420
381 @pyqtSlot()421 @QtCore.pyqtSlot()
382 def get_data(self):422 def get_data(self):
383 """423 """
384 Socket interface to retrieve data.424 Socket interface to retrieve data.
@@ -388,7 +428,7 @@
388 log.debug('({ip}) get_data(): Not connected - returning'.format(ip=self.ip))428 log.debug('({ip}) get_data(): Not connected - returning'.format(ip=self.ip))
389 self.send_busy = False429 self.send_busy = False
390 return430 return
391 read = self.readLine(self.maxSize)431 read = self.readLine(self.max_size)
392 if read == -1:432 if read == -1:
393 # No data available433 # No data available
394 log.debug('({ip}) get_data(): No data available (-1)'.format(ip=self.ip))434 log.debug('({ip}) get_data(): No data available (-1)'.format(ip=self.ip))
@@ -435,7 +475,11 @@
435 return475 return
436 return self.process_command(cmd, data)476 return self.process_command(cmd, data)
437477
478<<<<<<< TREE
438 @pyqtSlot(QAbstractSocket.SocketError)479 @pyqtSlot(QAbstractSocket.SocketError)
480=======
481 @QtCore.pyqtSlot(QtNetwork.QAbstractSocket.SocketError)
482>>>>>>> MERGE-SOURCE
439 def get_error(self, err):483 def get_error(self, err):
440 """484 """
441 Process error from SocketError signal.485 Process error from SocketError signal.
@@ -475,6 +519,7 @@
475 log.warning('({ip}) send_command(): Not connected - returning'.format(ip=self.ip))519 log.warning('({ip}) send_command(): Not connected - returning'.format(ip=self.ip))
476 self.send_queue = []520 self.send_queue = []
477 return521 return
522<<<<<<< TREE
478 self.projectorNetwork.emit(self.ip, S_NETWORK_SENDING)523 self.projectorNetwork.emit(self.ip, S_NETWORK_SENDING)
479 log.debug('(%s) send_command(): Building cmd="%s" opts="%s" %s' % (self.ip,524 log.debug('(%s) send_command(): Building cmd="%s" opts="%s" %s' % (self.ip,
480 cmd,525 cmd,
@@ -484,6 +529,19 @@
484 out = '%s%s %s%s' % (PJLINK_HEADER, cmd, opts, CR)529 out = '%s%s %s%s' % (PJLINK_HEADER, cmd, opts, CR)
485 else:530 else:
486 out = '%s%s%s %s%s' % (salt, PJLINK_HEADER, cmd, opts, CR)531 out = '%s%s%s %s%s' % (salt, PJLINK_HEADER, cmd, opts, CR)
532=======
533 self.projectorNetwork.emit(S_NETWORK_SENDING)
534 log.debug('({ip}) send_command(): Building cmd="{command}" opts="{data}"{salt}'.format(ip=self.ip,
535 command=cmd,
536 data=opts,
537 salt='' if salt is None
538 else ' with hash'))
539 out = '{salt}{header}{command} {options}{suffix}'.format(salt="" if salt is None else salt,
540 header=PJLINK_HEADER,
541 command=cmd,
542 options=opts,
543 suffix=CR)
544>>>>>>> MERGE-SOURCE
487 if out in self.send_queue:545 if out in self.send_queue:
488 # Already there, so don't add546 # Already there, so don't add
489 log.debug('({ip}) send_command(out="{data}") Already in queue - skipping'.format(ip=self.ip,547 log.debug('({ip}) send_command(out="{data}") Already in queue - skipping'.format(ip=self.ip,
@@ -501,7 +559,7 @@
501 log.debug('({ip}) send_command() calling _send_string()'.format(ip=self.ip))559 log.debug('({ip}) send_command() calling _send_string()'.format(ip=self.ip))
502 self._send_command()560 self._send_command()
503561
504 @pyqtSlot()562 @QtCore.pyqtSlot()
505 def _send_command(self, data=None):563 def _send_command(self, data=None):
506 """564 """
507 Socket interface to send data. If data=None, then check queue.565 Socket interface to send data. If data=None, then check queue.
@@ -533,6 +591,7 @@
533 log.debug('({ip}) _send_string(): Sending "{data}"'.format(ip=self.ip, data=out.strip()))591 log.debug('({ip}) _send_string(): Sending "{data}"'.format(ip=self.ip, data=out.strip()))
534 log.debug('({ip}) _send_string(): Queue = {data}'.format(ip=self.ip, data=self.send_queue))592 log.debug('({ip}) _send_string(): Queue = {data}'.format(ip=self.ip, data=self.send_queue))
535 self.socket_timer.start()593 self.socket_timer.start()
594<<<<<<< TREE
536 try:595 try:
537 self.projectorNetwork.emit(self.ip, S_NETWORK_SENDING)596 self.projectorNetwork.emit(self.ip, S_NETWORK_SENDING)
538 sent = self.write(out.encode('ascii'))597 sent = self.write(out.encode('ascii'))
@@ -546,6 +605,15 @@
546 self.changeStatus(E_NETWORK,605 self.changeStatus(E_NETWORK,
547 translate('OpenLP.PJLink1', '{code} : {string}').format(code=e.error(),606 translate('OpenLP.PJLink1', '{code} : {string}').format(code=e.error(),
548 string=e.errorString()))607 string=e.errorString()))
608=======
609 self.projectorNetwork.emit(S_NETWORK_SENDING)
610 sent = self.write(out.encode('ascii'))
611 self.waitForBytesWritten(2000) # 2 seconds should be enough
612 if sent == -1:
613 # Network error?
614 self.change_status(E_NETWORK,
615 translate('OpenLP.PJLink1', 'Error while sending data to projector'))
616>>>>>>> MERGE-SOURCE
549617
550 def process_command(self, cmd, data):618 def process_command(self, cmd, data):
551 """619 """
@@ -589,8 +657,8 @@
589 self.projectorReceivedData.emit()657 self.projectorReceivedData.emit()
590 return658 return
591659
592 if cmd in self.PJLINK1_FUNC:660 if cmd in self.pjlink1_functions:
593 self.PJLINK1_FUNC[cmd](data)661 self.pjlink1_functions[cmd](data)
594 else:662 else:
595 log.warning('({ip}) Invalid command {data}'.format(ip=self.ip, data=cmd))663 log.warning('({ip}) Invalid command {data}'.format(ip=self.ip, data=cmd))
596 self.send_busy = False664 self.send_busy = False
@@ -815,9 +883,9 @@
815 log.warning('({ip}) connect_to_host(): Already connected - returning'.format(ip=self.ip))883 log.warning('({ip}) connect_to_host(): Already connected - returning'.format(ip=self.ip))
816 return884 return
817 self.change_status(S_CONNECTING)885 self.change_status(S_CONNECTING)
818 self.connectToHost(self.ip, self.port if type(self.port) is int else int(self.port))886 self.connectToHost(self.ip, self.port if isinstance(self.port, int) else int(self.port))
819887
820 @pyqtSlot()888 @QtCore.pyqtSlot()
821 def disconnect_from_host(self, abort=False):889 def disconnect_from_host(self, abort=False):
822 """890 """
823 Close socket and cleanup.891 Close socket and cleanup.
824892
=== modified file 'openlp/core/lib/renderer.py'
--- openlp/core/lib/renderer.py 2016-12-31 11:05:48 +0000
+++ openlp/core/lib/renderer.py 2017-01-08 22:05:36 +0000
@@ -22,6 +22,7 @@
2222
23import re23import re
2424
25from string import Template
25from PyQt5 import QtGui, QtCore, QtWebKitWidgets26from PyQt5 import QtGui, QtCore, QtWebKitWidgets
2627
27from openlp.core.common import Registry, RegistryProperties, OpenLPMixin, RegistryMixin, Settings28from openlp.core.common import Registry, RegistryProperties, OpenLPMixin, RegistryMixin, Settings
@@ -107,7 +108,7 @@
107108
108 :param theme_name: The theme name109 :param theme_name: The theme name
109 """110 """
110 self.log_debug("_set_theme with theme %s" % theme_name)111 self.log_debug("_set_theme with theme {theme}".format(theme=theme_name))
111 if theme_name not in self._theme_dimensions:112 if theme_name not in self._theme_dimensions:
112 theme_data = self.theme_manager.get_theme_data(theme_name)113 theme_data = self.theme_manager.get_theme_data(theme_name)
113 main_rect = self.get_main_rectangle(theme_data)114 main_rect = self.get_main_rectangle(theme_data)
@@ -183,7 +184,7 @@
183184
184 :param item_theme_name: The item theme's name.185 :param item_theme_name: The item theme's name.
185 """186 """
186 self.log_debug("set_item_theme with theme %s" % item_theme_name)187 self.log_debug("set_item_theme with theme {theme}".format(theme=item_theme_name))
187 self._set_theme(item_theme_name)188 self._set_theme(item_theme_name)
188 self.item_theme_name = item_theme_name189 self.item_theme_name = item_theme_name
189190
@@ -317,7 +318,7 @@
317 self.width = screen_size.width()318 self.width = screen_size.width()
318 self.height = screen_size.height()319 self.height = screen_size.height()
319 self.screen_ratio = self.height / self.width320 self.screen_ratio = self.height / self.width
320 self.log_debug('_calculate default %s, %f' % (screen_size, self.screen_ratio))321 self.log_debug('_calculate default {size}, {ratio:f}'.format(size=screen_size, ratio=self.screen_ratio))
321 # 90% is start of footer322 # 90% is start of footer
322 self.footer_start = int(self.height * 0.90)323 self.footer_start = int(self.height * 0.90)
323324
@@ -354,7 +355,7 @@
354 :param rect_main: The main text block.355 :param rect_main: The main text block.
355 :param rect_footer: The footer text block.356 :param rect_footer: The footer text block.
356 """357 """
357 self.log_debug('_set_text_rectangle %s , %s' % (rect_main, rect_footer))358 self.log_debug('_set_text_rectangle {main} , {footer}'.format(main=rect_main, footer=rect_footer))
358 self._rect = rect_main359 self._rect = rect_main
359 self._rect_footer = rect_footer360 self._rect_footer = rect_footer
360 self.page_width = self._rect.width()361 self.page_width = self._rect.width()
@@ -370,7 +371,7 @@
370 self.web.resize(self.page_width, self.page_height)371 self.web.resize(self.page_width, self.page_height)
371 self.web_frame = self.web.page().mainFrame()372 self.web_frame = self.web.page().mainFrame()
372 # Adjust width and height to account for shadow. outline done in css.373 # Adjust width and height to account for shadow. outline done in css.
373 html = """<!DOCTYPE html><html><head><script>374 html = Template("""<!DOCTYPE html><html><head><script>
374 function show_text(newtext) {375 function show_text(newtext) {
375 var main = document.getElementById('main');376 var main = document.getElementById('main');
376 main.innerHTML = newtext;377 main.innerHTML = newtext;
@@ -379,12 +380,16 @@
379 // returned value).380 // returned value).
380 return main.offsetHeight;381 return main.offsetHeight;
381 }382 }
382 </script><style>*{margin: 0; padding: 0; border: 0;}383 </script>
383 #main {position: absolute; top: 0px; %s %s}</style></head><body>384 <style>
384 <div id="main"></div></body></html>""" % \385 *{margin: 0; padding: 0; border: 0;}
385 (build_lyrics_format_css(theme_data, self.page_width, self.page_height),386 #main {position: absolute; top: 0px; ${format_css} ${outline_css}}
386 build_lyrics_outline_css(theme_data))387 </style></head>
387 self.web.setHtml(html)388 <body><div id="main"></div></body></html>""")
389 self.web.setHtml(html.substitute(format_css=build_lyrics_format_css(theme_data,
390 self.page_width,
391 self.page_height),
392 outline_css=build_lyrics_outline_css(theme_data)))
388 self.empty_height = self.web_frame.contentsSize().height()393 self.empty_height = self.web_frame.contentsSize().height()
389394
390 def _paginate_slide(self, lines, line_end):395 def _paginate_slide(self, lines, line_end):
@@ -518,7 +523,8 @@
518523
519 :param text: The text to check. It may contain HTML tags.524 :param text: The text to check. It may contain HTML tags.
520 """525 """
521 self.web_frame.evaluateJavaScript('show_text("%s")' % text.replace('\\', '\\\\').replace('\"', '\\\"'))526 self.web_frame.evaluateJavaScript('show_text'
527 '("{text}")'.format(text=text.replace('\\', '\\\\').replace('\"', '\\\"')))
522 return self.web_frame.contentsSize().height() <= self.empty_height528 return self.web_frame.contentsSize().height() <= self.empty_height
523529
524530
@@ -529,7 +535,7 @@
529 :param line: Line to be split535 :param line: Line to be split
530 """536 """
531 # this parse we are to be wordy537 # this parse we are to be wordy
532 return re.split('\s+', line)538 return re.split(r'\s+', line)
533539
534540
535def get_start_tags(raw_text):541def get_start_tags(raw_text):
536542
=== modified file 'openlp/core/lib/screen.py'
--- openlp/core/lib/screen.py 2016-12-31 11:05:48 +0000
+++ openlp/core/lib/screen.py 2017-01-08 22:05:36 +0000
@@ -78,7 +78,7 @@
78 ``number``78 ``number``
79 The number of the screen, which size has changed.79 The number of the screen, which size has changed.
80 """80 """
81 log.info('screen_resolution_changed %d' % number)81 log.info('screen_resolution_changed {number:d}'.format(number=number))
82 for screen in self.screen_list:82 for screen in self.screen_list:
83 if number == screen['number']:83 if number == screen['number']:
84 new_screen = {84 new_screen = {
@@ -105,7 +105,7 @@
105 """105 """
106 # Do not log at start up.106 # Do not log at start up.
107 if changed_screen != -1:107 if changed_screen != -1:
108 log.info('screen_count_changed %d' % self.desktop.screenCount())108 log.info('screen_count_changed {count:d}'.format(count=self.desktop.screenCount()))
109 # Remove unplugged screens.109 # Remove unplugged screens.
110 for screen in copy.deepcopy(self.screen_list):110 for screen in copy.deepcopy(self.screen_list):
111 if screen['number'] == self.desktop.screenCount():111 if screen['number'] == self.desktop.screenCount():
@@ -132,9 +132,11 @@
132 """132 """
133 screen_list = []133 screen_list = []
134 for screen in self.screen_list:134 for screen in self.screen_list:
135 screen_name = '%s %d' % (translate('OpenLP.ScreenList', 'Screen'), screen['number'] + 1)135 screen_name = '{name} {number:d}'.format(name=translate('OpenLP.ScreenList', 'Screen'),
136 number=screen['number'] + 1)
136 if screen['primary']:137 if screen['primary']:
137 screen_name = '%s (%s)' % (screen_name, translate('OpenLP.ScreenList', 'primary'))138 screen_name = '{name} ({primary})'.format(name=screen_name,
139 primary=translate('OpenLP.ScreenList', 'primary'))
138 screen_list.append(screen_name)140 screen_list.append(screen_name)
139 return screen_list141 return screen_list
140142
@@ -152,7 +154,7 @@
152 'size': PyQt5.QtCore.QRect(0, 0, 1024, 768)154 'size': PyQt5.QtCore.QRect(0, 0, 1024, 768)
153 }155 }
154 """156 """
155 log.info('Screen %d found with resolution %s' % (screen['number'], screen['size']))157 log.info('Screen {number:d} found with resolution {size}'.format(number=screen['number'], size=screen['size']))
156 if screen['primary']:158 if screen['primary']:
157 self.current = screen159 self.current = screen
158 self.override = copy.deepcopy(self.current)160 self.override = copy.deepcopy(self.current)
@@ -165,7 +167,7 @@
165167
166 :param number: The screen number (int).168 :param number: The screen number (int).
167 """169 """
168 log.info('remove_screen %d' % number)170 log.info('remove_screen {number:d}'.format(number=number))
169 for screen in self.screen_list:171 for screen in self.screen_list:
170 if screen['number'] == number:172 if screen['number'] == number:
171 self.screen_list.remove(screen)173 self.screen_list.remove(screen)
@@ -189,7 +191,7 @@
189191
190 :param number: The screen number (int).192 :param number: The screen number (int).
191 """193 """
192 log.debug('set_current_display %s' % number)194 log.debug('set_current_display {number}'.format(number=number))
193 if number + 1 > self.display_count:195 if number + 1 > self.display_count:
194 self.current = self.screen_list[0]196 self.current = self.screen_list[0]
195 else:197 else:
196198
=== modified file 'openlp/core/lib/searchedit.py'
--- openlp/core/lib/searchedit.py 2016-12-31 11:05:48 +0000
+++ openlp/core/lib/searchedit.py 2017-01-08 22:05:36 +0000
@@ -26,6 +26,7 @@
2626
27from openlp.core.lib import build_icon27from openlp.core.lib import build_icon
28from openlp.core.lib.ui import create_widget_action28from openlp.core.lib.ui import create_widget_action
29from openlp.core.common import Settings
2930
30log = logging.getLogger(__name__)31log = logging.getLogger(__name__)
3132
@@ -37,11 +38,12 @@
37 searchTypeChanged = QtCore.pyqtSignal(QtCore.QVariant)38 searchTypeChanged = QtCore.pyqtSignal(QtCore.QVariant)
38 cleared = QtCore.pyqtSignal()39 cleared = QtCore.pyqtSignal()
3940
40 def __init__(self, parent):41 def __init__(self, parent, settings_section):
41 """42 """
42 Constructor.43 Constructor.
43 """44 """
44 super(SearchEdit, self).__init__(parent)45 super().__init__(parent)
46 self.settings_section = settings_section
45 self._current_search_type = -147 self._current_search_type = -1
46 self.clear_button = QtWidgets.QToolButton(self)48 self.clear_button = QtWidgets.QToolButton(self)
47 self.clear_button.setIcon(build_icon(':/system/clear_shortcut.png'))49 self.clear_button.setIcon(build_icon(':/system/clear_shortcut.png'))
@@ -62,9 +64,10 @@
62 right_padding = self.clear_button.width() + frame_width64 right_padding = self.clear_button.width() + frame_width
63 if hasattr(self, 'menu_button'):65 if hasattr(self, 'menu_button'):
64 left_padding = self.menu_button.width()66 left_padding = self.menu_button.width()
65 stylesheet = 'QLineEdit { padding-left: %spx; padding-right: %spx; } ' % (left_padding, right_padding)67 stylesheet = 'QLineEdit {{ padding-left:{left}px; padding-right: {right}px; }} '.format(left=left_padding,
68 right=right_padding)
66 else:69 else:
67 stylesheet = 'QLineEdit { padding-right: %spx; } ' % right_padding70 stylesheet = 'QLineEdit {{ padding-right: {right}px; }} '.format(right=right_padding)
68 self.setStyleSheet(stylesheet)71 self.setStyleSheet(stylesheet)
69 msz = self.minimumSizeHint()72 msz = self.minimumSizeHint()
70 self.setMinimumSize(max(msz.width(), self.clear_button.width() + (frame_width * 2) + 2),73 self.setMinimumSize(max(msz.width(), self.clear_button.width() + (frame_width * 2) + 2),
@@ -99,14 +102,10 @@
99 menu = self.menu_button.menu()102 menu = self.menu_button.menu()
100 for action in menu.actions():103 for action in menu.actions():
101 if identifier == action.data():104 if identifier == action.data():
102 # setPlaceholderText has been implemented in Qt 4.7 and in at least PyQt 4.9 (I am not sure, if it was105 self.setPlaceholderText(action.placeholder_text)
103 # implemented in PyQt 4.8).
104 try:
105 self.setPlaceholderText(action.placeholder_text)
106 except AttributeError:
107 pass
108 self.menu_button.setDefaultAction(action)106 self.menu_button.setDefaultAction(action)
109 self._current_search_type = identifier107 self._current_search_type = identifier
108 Settings().setValue('{section}/last search type'.format(section=self.settings_section), identifier)
110 self.searchTypeChanged.emit(identifier)109 self.searchTypeChanged.emit(identifier)
111 return True110 return True
112111
@@ -129,14 +128,10 @@
129 (2, ":/songs/authors.png", "Authors", "Search Authors...")128 (2, ":/songs/authors.png", "Authors", "Search Authors...")
130 """129 """
131 menu = QtWidgets.QMenu(self)130 menu = QtWidgets.QMenu(self)
132 first = None
133 for identifier, icon, title, placeholder in items:131 for identifier, icon, title, placeholder in items:
134 action = create_widget_action(132 action = create_widget_action(
135 menu, text=title, icon=icon, data=identifier, triggers=self._on_menu_action_triggered)133 menu, text=title, icon=icon, data=identifier, triggers=self._on_menu_action_triggered)
136 action.placeholder_text = placeholder134 action.placeholder_text = placeholder
137 if first is None:
138 first = action
139 self._current_search_type = identifier
140 if not hasattr(self, 'menu_button'):135 if not hasattr(self, 'menu_button'):
141 self.menu_button = QtWidgets.QToolButton(self)136 self.menu_button = QtWidgets.QToolButton(self)
142 self.menu_button.setIcon(build_icon(':/system/clear_shortcut.png'))137 self.menu_button.setIcon(build_icon(':/system/clear_shortcut.png'))
@@ -145,7 +140,8 @@
145 self.menu_button.setStyleSheet('QToolButton { border: none; padding: 0px 10px 0px 0px; }')140 self.menu_button.setStyleSheet('QToolButton { border: none; padding: 0px 10px 0px 0px; }')
146 self.menu_button.resize(QtCore.QSize(28, 18))141 self.menu_button.resize(QtCore.QSize(28, 18))
147 self.menu_button.setMenu(menu)142 self.menu_button.setMenu(menu)
148 self.menu_button.setDefaultAction(first)143 self.set_current_search_type(
144 Settings().value('{section}/last search type'.format(section=self.settings_section)))
149 self.menu_button.show()145 self.menu_button.show()
150 self._update_style_sheet()146 self._update_style_sheet()
151147
152148
=== modified file 'openlp/core/lib/serviceitem.py'
--- openlp/core/lib/serviceitem.py 2016-12-31 11:05:48 +0000
+++ openlp/core/lib/serviceitem.py 2017-01-08 22:05:36 +0000
@@ -34,7 +34,7 @@
34from PyQt5 import QtGui34from PyQt5 import QtGui
3535
36from openlp.core.common import RegistryProperties, Settings, translate, AppLocation, md5_hash36from openlp.core.common import RegistryProperties, Settings, translate, AppLocation, md5_hash
37from openlp.core.lib import ImageSource, build_icon, clean_tags, expand_tags, create_thumb37from openlp.core.lib import ImageSource, build_icon, clean_tags, expand_tags
3838
39log = logging.getLogger(__name__)39log = logging.getLogger(__name__)
4040
@@ -247,7 +247,7 @@
247 self.renderer.set_item_theme(self.theme)247 self.renderer.set_item_theme(self.theme)
248 self.theme_data, self.main, self.footer = self.renderer.pre_render()248 self.theme_data, self.main, self.footer = self.renderer.pre_render()
249 if self.service_item_type == ServiceItemType.Text:249 if self.service_item_type == ServiceItemType.Text:
250 log.debug('Formatting slides: %s' % self.title)250 log.debug('Formatting slides: {title}'.format(title=self.title))
251 # Save rendered pages to this dict. In the case that a slide is used twice we can use the pages saved to251 # Save rendered pages to this dict. In the case that a slide is used twice we can use the pages saved to
252 # the dict instead of rendering them again.252 # the dict instead of rendering them again.
253 previous_pages = {}253 previous_pages = {}
@@ -270,7 +270,7 @@
270 elif self.service_item_type == ServiceItemType.Image or self.service_item_type == ServiceItemType.Command:270 elif self.service_item_type == ServiceItemType.Image or self.service_item_type == ServiceItemType.Command:
271 pass271 pass
272 else:272 else:
273 log.error('Invalid value renderer: %s' % self.service_item_type)273 log.error('Invalid value renderer: {item}'.format(item=self.service_item_type))
274 self.title = clean_tags(self.title)274 self.title = clean_tags(self.title)
275 # The footer should never be None, but to be compatible with a few275 # The footer should never be None, but to be compatible with a few
276 # nightly builds between 1.9.4 and 1.9.5, we have to correct this to276 # nightly builds between 1.9.4 and 1.9.5, we have to correct this to
@@ -325,7 +325,8 @@
325 self.service_item_type = ServiceItemType.Command325 self.service_item_type = ServiceItemType.Command
326 # If the item should have a display title but this frame doesn't have one, we make one up326 # If the item should have a display title but this frame doesn't have one, we make one up
327 if self.is_capable(ItemCapabilities.HasDisplayTitle) and not display_title:327 if self.is_capable(ItemCapabilities.HasDisplayTitle) and not display_title:
328 display_title = translate('OpenLP.ServiceItem', '[slide %d]') % (len(self._raw_frames) + 1)328 display_title = translate('OpenLP.ServiceItem',
329 '[slide {frame:d}]').format(frame=len(self._raw_frames) + 1)
329 # Update image path to match servicemanager location if file was loaded from service330 # Update image path to match servicemanager location if file was loaded from service
330 if image and not self.has_original_files and self.name == 'presentations':331 if image and not self.has_original_files and self.name == 'presentations':
331 file_location = os.path.join(path, file_name)332 file_location = os.path.join(path, file_name)
@@ -334,6 +335,8 @@
334 file_location_hash, ntpath.basename(image))335 file_location_hash, ntpath.basename(image))
335 self._raw_frames.append({'title': file_name, 'image': image, 'path': path,336 self._raw_frames.append({'title': file_name, 'image': image, 'path': path,
336 'display_title': display_title, 'notes': notes})337 'display_title': display_title, 'notes': notes})
338 if self.is_capable(ItemCapabilities.HasThumbnails):
339 self.image_manager.add_image(image, ImageSource.CommandPlugins, '#000000')
337 self._new_item()340 self._new_item()
338341
339 def get_service_repr(self, lite_save):342 def get_service_repr(self, lite_save):
@@ -390,7 +393,7 @@
390 :param path: Defaults to *None*. This is the service manager path for things which have their files saved393 :param path: Defaults to *None*. This is the service manager path for things which have their files saved
391 with them or None when the saved service is lite and the original file paths need to be preserved.394 with them or None when the saved service is lite and the original file paths need to be preserved.
392 """395 """
393 log.debug('set_from_service called with path %s' % path)396 log.debug('set_from_service called with path {path}'.format(path=path))
394 header = service_item['serviceitem']['header']397 header = service_item['serviceitem']['header']
395 self.title = header['title']398 self.title = header['title']
396 self.name = header['name']399 self.name = header['name']
@@ -606,11 +609,13 @@
606 start = None609 start = None
607 end = None610 end = None
608 if self.start_time != 0:611 if self.start_time != 0:
609 start = translate('OpenLP.ServiceItem', '<strong>Start</strong>: %s') % \612 time = str(datetime.timedelta(seconds=self.start_time))
610 str(datetime.timedelta(seconds=self.start_time))613 start = translate('OpenLP.ServiceItem',
614 '<strong>Start</strong>: {start}').format(start=time)
611 if self.media_length != 0:615 if self.media_length != 0:
612 end = translate('OpenLP.ServiceItem', '<strong>Length</strong>: %s') % \616 length = str(datetime.timedelta(seconds=self.media_length // 1000))
613 str(datetime.timedelta(seconds=self.media_length))617 end = translate('OpenLP.ServiceItem', '<strong>Length</strong>: {length}').format(length=length)
618
614 if not start and not end:619 if not start and not end:
615 return ''620 return ''
616 elif start and not end:621 elif start and not end:
@@ -618,7 +623,7 @@
618 elif not start and end:623 elif not start and end:
619 return end624 return end
620 else:625 else:
621 return '%s <br>%s' % (start, end)626 return '{start} <br>{end}'.format(start=start, end=end)
622627
623 def update_theme(self, theme):628 def update_theme(self, theme):
624 """629 """
625630
=== modified file 'openlp/core/lib/settingstab.py'
--- openlp/core/lib/settingstab.py 2016-12-31 11:05:48 +0000
+++ openlp/core/lib/settingstab.py 2017-01-08 22:05:36 +0000
@@ -135,4 +135,4 @@
135 """135 """
136 Tab has just been made visible to the user136 Tab has just been made visible to the user
137 """137 """
138 self.tab_visited = True138 pass
139139
=== renamed file 'openlp/core/lib/spelltextedit.py' => 'openlp/core/lib/spelltextedit.py.THIS'
=== modified file 'openlp/core/lib/theme.py'
--- openlp/core/lib/theme.py 2016-12-31 11:05:48 +0000
+++ openlp/core/lib/theme.py 2017-01-08 22:05:36 +0000
@@ -23,7 +23,6 @@
23Provide the theme XML and handling functions for OpenLP v2 themes.23Provide the theme XML and handling functions for OpenLP v2 themes.
24"""24"""
25import os25import os
26import re
27import logging26import logging
28import json27import json
2928
@@ -44,6 +43,7 @@
44 Gradient = 143 Gradient = 1
45 Image = 244 Image = 2
46 Transparent = 345 Transparent = 3
46 Video = 4
4747
48 @staticmethod48 @staticmethod
49 def to_string(background_type):49 def to_string(background_type):
@@ -58,6 +58,8 @@
58 return 'image'58 return 'image'
59 elif background_type == BackgroundType.Transparent:59 elif background_type == BackgroundType.Transparent:
60 return 'transparent'60 return 'transparent'
61 elif background_type == BackgroundType.Video:
62 return 'video'
6163
62 @staticmethod64 @staticmethod
63 def from_string(type_string):65 def from_string(type_string):
@@ -72,6 +74,8 @@
72 return BackgroundType.Image74 return BackgroundType.Image
73 elif type_string == 'transparent':75 elif type_string == 'transparent':
74 return BackgroundType.Transparent76 return BackgroundType.Transparent
77 elif type_string == 'video':
78 return BackgroundType.Video
7579
7680
77class BackgroundGradientType(object):81class BackgroundGradientType(object):
@@ -160,6 +164,7 @@
160 jsn = get_text_file_string(json_file)164 jsn = get_text_file_string(json_file)
161 jsn = json.loads(jsn)165 jsn = json.loads(jsn)
162 self.expand_json(jsn)166 self.expand_json(jsn)
167 self.background_filename = None
163168
164 def expand_json(self, var, prev=None):169 def expand_json(self, var, prev=None):
165 """170 """
@@ -184,7 +189,7 @@
184189
185 :param path: The path name to be added.190 :param path: The path name to be added.
186 """191 """
187 if self.background_type == 'image':192 if self.background_type == 'image' or self.background_type == 'video':
188 if self.background_filename and path:193 if self.background_filename and path:
189 self.theme_name = self.theme_name.strip()194 self.theme_name = self.theme_name.strip()
190 self.background_filename = self.background_filename.strip()195 self.background_filename = self.background_filename.strip()
@@ -255,6 +260,21 @@
255 # Create endColor element260 # Create endColor element
256 self.child_element(background, 'borderColor', str(border_color))261 self.child_element(background, 'borderColor', str(border_color))
257262
263 def add_background_video(self, filename, border_color):
264 """
265 Add a video background.
266
267 :param filename: The file name of the video.
268 :param border_color:
269 """
270 background = self.theme_xml.createElement('background')
271 background.setAttribute('type', 'video')
272 self.theme.appendChild(background)
273 # Create Filename element
274 self.child_element(background, 'filename', filename)
275 # Create endColor element
276 self.child_element(background, 'borderColor', str(border_color))
277
258 def add_font(self, name, color, size, override, fonttype='main', bold='False', italics='False',278 def add_font(self, name, color, size, override, fonttype='main', bold='False', italics='False',
259 line_adjustment=0, xpos=0, ypos=0, width=0, height=0, outline='False', outline_color='#ffffff',279 line_adjustment=0, xpos=0, ypos=0, width=0, height=0, outline='False', outline_color='#ffffff',
260 outline_pixel=2, shadow='False', shadow_color='#ffffff', shadow_pixel=5):280 outline_pixel=2, shadow='False', shadow_color='#ffffff', shadow_pixel=5):
@@ -407,7 +427,7 @@
407 try:427 try:
408 theme_xml = objectify.fromstring(xml)428 theme_xml = objectify.fromstring(xml)
409 except etree.XMLSyntaxError:429 except etree.XMLSyntaxError:
410 log.exception('Invalid xml %s', xml)430 log.exception('Invalid xml {text}'.format(text=xml))
411 return431 return
412 xml_iter = theme_xml.getiterator()432 xml_iter = theme_xml.getiterator()
413 for element in xml_iter:433 for element in xml_iter:
@@ -454,15 +474,16 @@
454 if element.startswith('shadow') or element.startswith('outline'):474 if element.startswith('shadow') or element.startswith('outline'):
455 master = 'font_main'475 master = 'font_main'
456 # fix bold font476 # fix bold font
477 ret_value = None
457 if element == 'weight':478 if element == 'weight':
458 element = 'bold'479 element = 'bold'
459 if value == 'Normal':480 if value == 'Normal':
460 value = False481 ret_value = False
461 else:482 else:
462 value = True483 ret_value = True
463 if element == 'proportion':484 if element == 'proportion':
464 element = 'size'485 element = 'size'
465 return False, master, element, value486 return False, master, element, ret_value if ret_value is not None else value
466487
467 def _create_attr(self, master, element, value):488 def _create_attr(self, master, element, value):
468 """489 """
@@ -493,7 +514,8 @@
493 theme_strings = []514 theme_strings = []
494 for key in dir(self):515 for key in dir(self):
495 if key[0:1] != '_':516 if key[0:1] != '_':
496 theme_strings.append('%30s: %s' % (key, getattr(self, key)))517 # TODO: Due to bound methods returned, I don't know how to write a proper test
518 theme_strings.append('{key:>30}: {value}'.format(key=key, value=getattr(self, key)))
497 return '\n'.join(theme_strings)519 return '\n'.join(theme_strings)
498520
499 def _build_xml_from_attrs(self):521 def _build_xml_from_attrs(self):
@@ -512,6 +534,9 @@
512 elif self.background_type == BackgroundType.to_string(BackgroundType.Image):534 elif self.background_type == BackgroundType.to_string(BackgroundType.Image):
513 filename = os.path.split(self.background_filename)[1]535 filename = os.path.split(self.background_filename)[1]
514 self.add_background_image(filename, self.background_border_color)536 self.add_background_image(filename, self.background_border_color)
537 elif self.background_type == BackgroundType.to_string(BackgroundType.Video):
538 filename = os.path.split(self.background_filename)[1]
539 self.add_background_video(filename, self.background_border_color)
515 elif self.background_type == BackgroundType.to_string(BackgroundType.Transparent):540 elif self.background_type == BackgroundType.to_string(BackgroundType.Transparent):
516 self.add_background_transparent()541 self.add_background_transparent()
517 self.add_font(542 self.add_font(
518543
=== renamed file 'openlp/core/lib/toolbar.py' => 'openlp/core/lib/toolbar.py.THIS'
=== renamed file 'openlp/core/lib/treewidgetwithdnd.py' => 'openlp/core/lib/treewidgetwithdnd.py.THIS'
=== modified file 'openlp/core/lib/ui.py'
--- openlp/core/lib/ui.py 2016-12-31 11:05:48 +0000
+++ openlp/core/lib/ui.py 2017-01-08 22:05:36 +0000
@@ -27,8 +27,8 @@
27from PyQt5 import QtCore, QtGui, QtWidgets27from PyQt5 import QtCore, QtGui, QtWidgets
2828
29from openlp.core.common import Registry, UiStrings, translate, is_macosx29from openlp.core.common import Registry, UiStrings, translate, is_macosx
30from openlp.core.common.actions import ActionList
30from openlp.core.lib import build_icon31from openlp.core.lib import build_icon
31from openlp.core.utils.actions import ActionList
3232
3333
34log = logging.getLogger(__name__)34log = logging.getLogger(__name__)
@@ -165,7 +165,7 @@
165 kwargs.setdefault('icon', ':/services/service_down.png')165 kwargs.setdefault('icon', ':/services/service_down.png')
166 kwargs.setdefault('tooltip', translate('OpenLP.Ui', 'Move selection down one position.'))166 kwargs.setdefault('tooltip', translate('OpenLP.Ui', 'Move selection down one position.'))
167 else:167 else:
168 log.warning('The role "%s" is not defined in create_push_button().', role)168 log.warning('The role "{role}" is not defined in create_push_button().'.format(role=role))
169 if kwargs.pop('btn_class', '') == 'toolbutton':169 if kwargs.pop('btn_class', '') == 'toolbutton':
170 button = QtWidgets.QToolButton(parent)170 button = QtWidgets.QToolButton(parent)
171 else:171 else:
@@ -183,7 +183,7 @@
183 button.clicked.connect(kwargs.pop('click'))183 button.clicked.connect(kwargs.pop('click'))
184 for key in list(kwargs.keys()):184 for key in list(kwargs.keys()):
185 if key not in ['text', 'icon', 'tooltip', 'click']:185 if key not in ['text', 'icon', 'tooltip', 'click']:
186 log.warning('Parameter %s was not consumed in create_button().', key)186 log.warning('Parameter {key} was not consumed in create_button().'.format(key=key))
187 return button187 return button
188188
189189
@@ -270,7 +270,7 @@
270 action.triggered.connect(kwargs.pop('triggers'))270 action.triggered.connect(kwargs.pop('triggers'))
271 for key in list(kwargs.keys()):271 for key in list(kwargs.keys()):
272 if key not in ['text', 'icon', 'tooltip', 'statustip', 'checked', 'can_shortcuts', 'category', 'triggers']:272 if key not in ['text', 'icon', 'tooltip', 'statustip', 'checked', 'can_shortcuts', 'category', 'triggers']:
273 log.warning('Parameter %s was not consumed in create_action().' % key)273 log.warning('Parameter {key} was not consumed in create_action().'.format(key=key))
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to all changes: