forked from openlp/openlp
Merging trunk on 15/3/16
This commit is contained in:
commit
87cdde1575
@ -44,3 +44,4 @@ __pycache__
|
||||
cover
|
||||
*.kdev4
|
||||
coverage
|
||||
tags
|
||||
|
@ -1 +1 @@
|
||||
2.3.2
|
||||
2.4
|
||||
|
@ -252,68 +252,56 @@ class Settings(QtCore.QSettings):
|
||||
'shortcuts/blankScreen': [QtGui.QKeySequence(QtCore.Qt.Key_Period)],
|
||||
'shortcuts/collapse': [QtGui.QKeySequence(QtCore.Qt.Key_Minus)],
|
||||
'shortcuts/desktopScreen': [QtGui.QKeySequence(QtCore.Qt.Key_D)],
|
||||
'shortcuts/delete': [QtGui.QKeySequence(QtGui.QKeySequence.Delete),
|
||||
QtGui.QKeySequence(QtCore.Qt.Key_Delete)],
|
||||
'shortcuts/delete': [QtGui.QKeySequence(QtGui.QKeySequence.Delete)],
|
||||
'shortcuts/down': [QtGui.QKeySequence(QtCore.Qt.Key_Down)],
|
||||
'shortcuts/editSong': [],
|
||||
'shortcuts/escapeItem': [QtGui.QKeySequence(QtCore.Qt.Key_Escape)],
|
||||
'shortcuts/expand': [QtGui.QKeySequence(QtCore.Qt.Key_Plus)],
|
||||
'shortcuts/exportThemeItem': [],
|
||||
'shortcuts/fileNewItem': [QtGui.QKeySequence(QtGui.QKeySequence.New),
|
||||
QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_N)],
|
||||
'shortcuts/fileSaveAsItem': [QtGui.QKeySequence(QtGui.QKeySequence.SaveAs),
|
||||
QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.SHIFT + QtCore.Qt.Key_S)],
|
||||
'shortcuts/fileExitItem': [QtGui.QKeySequence(QtGui.QKeySequence.Quit),
|
||||
QtGui.QKeySequence(QtCore.Qt.ALT + QtCore.Qt.Key_F4)],
|
||||
'shortcuts/fileSaveItem': [QtGui.QKeySequence(QtGui.QKeySequence.Save),
|
||||
QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_S)],
|
||||
'shortcuts/fileOpenItem': [QtGui.QKeySequence(QtGui.QKeySequence.Open),
|
||||
QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_O)],
|
||||
'shortcuts/fileNewItem': [QtGui.QKeySequence(QtGui.QKeySequence.New)],
|
||||
'shortcuts/fileSaveAsItem': [QtGui.QKeySequence(QtGui.QKeySequence.SaveAs)],
|
||||
'shortcuts/fileExitItem': [QtGui.QKeySequence(QtGui.QKeySequence.Quit)],
|
||||
'shortcuts/fileSaveItem': [QtGui.QKeySequence(QtGui.QKeySequence.Save)],
|
||||
'shortcuts/fileOpenItem': [QtGui.QKeySequence(QtGui.QKeySequence.Open)],
|
||||
'shortcuts/goLive': [],
|
||||
'shortcuts/importThemeItem': [],
|
||||
'shortcuts/importBibleItem': [],
|
||||
'shortcuts/listViewBiblesDeleteItem': [QtGui.QKeySequence(QtGui.QKeySequence.Delete),
|
||||
QtGui.QKeySequence(QtCore.Qt.Key_Delete)],
|
||||
'shortcuts/listViewBiblesDeleteItem': [QtGui.QKeySequence(QtGui.QKeySequence.Delete)],
|
||||
'shortcuts/listViewBiblesPreviewItem': [QtGui.QKeySequence(QtCore.Qt.Key_Return),
|
||||
QtGui.QKeySequence(QtCore.Qt.Key_Enter)],
|
||||
'shortcuts/listViewBiblesLiveItem': [QtGui.QKeySequence(QtCore.Qt.SHIFT + QtCore.Qt.Key_Return),
|
||||
QtGui.QKeySequence(QtCore.Qt.SHIFT + QtCore.Qt.Key_Enter)],
|
||||
'shortcuts/listViewBiblesServiceItem': [QtGui.QKeySequence(QtCore.Qt.Key_Plus),
|
||||
QtGui.QKeySequence(QtCore.Qt.Key_Equal)],
|
||||
'shortcuts/listViewCustomDeleteItem': [QtGui.QKeySequence(QtGui.QKeySequence.Delete),
|
||||
QtGui.QKeySequence(QtCore.Qt.Key_Delete)],
|
||||
'shortcuts/listViewCustomDeleteItem': [QtGui.QKeySequence(QtGui.QKeySequence.Delete)],
|
||||
'shortcuts/listViewCustomPreviewItem': [QtGui.QKeySequence(QtCore.Qt.Key_Return),
|
||||
QtGui.QKeySequence(QtCore.Qt.Key_Enter)],
|
||||
'shortcuts/listViewCustomLiveItem': [QtGui.QKeySequence(QtCore.Qt.SHIFT + QtCore.Qt.Key_Return),
|
||||
QtGui.QKeySequence(QtCore.Qt.SHIFT + QtCore.Qt.Key_Enter)],
|
||||
'shortcuts/listViewCustomServiceItem': [QtGui.QKeySequence(QtCore.Qt.Key_Plus),
|
||||
QtGui.QKeySequence(QtCore.Qt.Key_Equal)],
|
||||
'shortcuts/listViewImagesDeleteItem': [QtGui.QKeySequence(QtGui.QKeySequence.Delete),
|
||||
QtGui.QKeySequence(QtCore.Qt.Key_Delete)],
|
||||
'shortcuts/listViewImagesDeleteItem': [QtGui.QKeySequence(QtGui.QKeySequence.Delete)],
|
||||
'shortcuts/listViewImagesPreviewItem': [QtGui.QKeySequence(QtCore.Qt.Key_Return),
|
||||
QtGui.QKeySequence(QtCore.Qt.Key_Enter)],
|
||||
'shortcuts/listViewImagesLiveItem': [QtGui.QKeySequence(QtCore.Qt.SHIFT + QtCore.Qt.Key_Return),
|
||||
QtGui.QKeySequence(QtCore.Qt.SHIFT + QtCore.Qt.Key_Enter)],
|
||||
'shortcuts/listViewImagesServiceItem': [QtGui.QKeySequence(QtCore.Qt.Key_Plus),
|
||||
QtGui.QKeySequence(QtCore.Qt.Key_Equal)],
|
||||
'shortcuts/listViewMediaDeleteItem': [QtGui.QKeySequence(QtGui.QKeySequence.Delete),
|
||||
QtGui.QKeySequence(QtCore.Qt.Key_Delete)],
|
||||
'shortcuts/listViewMediaDeleteItem': [QtGui.QKeySequence(QtGui.QKeySequence.Delete)],
|
||||
'shortcuts/listViewMediaPreviewItem': [QtGui.QKeySequence(QtCore.Qt.Key_Return),
|
||||
QtGui.QKeySequence(QtCore.Qt.Key_Enter)],
|
||||
'shortcuts/listViewMediaLiveItem': [QtGui.QKeySequence(QtCore.Qt.SHIFT + QtCore.Qt.Key_Return),
|
||||
QtGui.QKeySequence(QtCore.Qt.SHIFT + QtCore.Qt.Key_Enter)],
|
||||
'shortcuts/listViewMediaServiceItem': [QtGui.QKeySequence(QtCore.Qt.Key_Plus),
|
||||
QtGui.QKeySequence(QtCore.Qt.Key_Equal)],
|
||||
'shortcuts/listViewPresentationsDeleteItem': [QtGui.QKeySequence(QtGui.QKeySequence.Delete),
|
||||
QtGui.QKeySequence(QtCore.Qt.Key_Delete)],
|
||||
'shortcuts/listViewPresentationsDeleteItem': [QtGui.QKeySequence(QtGui.QKeySequence.Delete)],
|
||||
'shortcuts/listViewPresentationsPreviewItem': [QtGui.QKeySequence(QtCore.Qt.Key_Return),
|
||||
QtGui.QKeySequence(QtCore.Qt.Key_Enter)],
|
||||
'shortcuts/listViewPresentationsLiveItem': [QtGui.QKeySequence(QtCore.Qt.SHIFT + QtCore.Qt.Key_Return),
|
||||
QtGui.QKeySequence(QtCore.Qt.SHIFT + QtCore.Qt.Key_Enter)],
|
||||
'shortcuts/listViewPresentationsServiceItem': [QtGui.QKeySequence(QtCore.Qt.Key_Plus),
|
||||
QtGui.QKeySequence(QtCore.Qt.Key_Equal)],
|
||||
'shortcuts/listViewSongsDeleteItem': [QtGui.QKeySequence(QtGui.QKeySequence.Delete),
|
||||
QtGui.QKeySequence(QtCore.Qt.Key_Delete)],
|
||||
'shortcuts/listViewSongsDeleteItem': [QtGui.QKeySequence(QtGui.QKeySequence.Delete)],
|
||||
'shortcuts/listViewSongsPreviewItem': [QtGui.QKeySequence(QtCore.Qt.Key_Return),
|
||||
QtGui.QKeySequence(QtCore.Qt.Key_Enter)],
|
||||
'shortcuts/listViewSongsLiveItem': [QtGui.QKeySequence(QtCore.Qt.SHIFT + QtCore.Qt.Key_Return),
|
||||
@ -337,8 +325,7 @@ class Settings(QtCore.QSettings):
|
||||
'shortcuts/nextService': [QtGui.QKeySequence(QtCore.Qt.Key_Right)],
|
||||
'shortcuts/newService': [],
|
||||
'shortcuts/offlineHelpItem': [QtGui.QKeySequence(QtGui.QKeySequence.HelpContents)],
|
||||
'shortcuts/onlineHelpItem': [QtGui.QKeySequence(QtGui.QKeySequence.HelpContents),
|
||||
QtGui.QKeySequence(QtCore.Qt.ALT + QtCore.Qt.Key_F1)],
|
||||
'shortcuts/onlineHelpItem': [QtGui.QKeySequence(QtGui.QKeySequence.HelpContents)],
|
||||
'shortcuts/openService': [],
|
||||
'shortcuts/saveService': [],
|
||||
'shortcuts/previousItem_live': [QtGui.QKeySequence(QtCore.Qt.Key_Up),
|
||||
@ -351,12 +338,10 @@ class Settings(QtCore.QSettings):
|
||||
'shortcuts/previousService': [QtGui.QKeySequence(QtCore.Qt.Key_Left)],
|
||||
'shortcuts/previousItem_preview': [QtGui.QKeySequence(QtCore.Qt.Key_Up),
|
||||
QtGui.QKeySequence(QtCore.Qt.Key_PageUp)],
|
||||
'shortcuts/printServiceItem': [QtGui.QKeySequence(QtGui.QKeySequence.Print),
|
||||
QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_P)],
|
||||
'shortcuts/printServiceItem': [QtGui.QKeySequence(QtGui.QKeySequence.Print)],
|
||||
'shortcuts/songExportItem': [],
|
||||
'shortcuts/songUsageStatus': [QtGui.QKeySequence(QtCore.Qt.Key_F4)],
|
||||
'shortcuts/searchShortcut': [QtGui.QKeySequence(QtGui.QKeySequence.Find),
|
||||
QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_F)],
|
||||
'shortcuts/searchShortcut': [QtGui.QKeySequence(QtGui.QKeySequence.Find)],
|
||||
'shortcuts/settingsShortcutsItem': [],
|
||||
'shortcuts/settingsImportItem': [],
|
||||
'shortcuts/settingsPluginListItem': [QtGui.QKeySequence(QtCore.Qt.ALT + QtCore.Qt.Key_F7)],
|
||||
|
@ -122,8 +122,8 @@ class UiStrings(object):
|
||||
self.Projectors = translate('OpenLP.Ui', 'Projectors', 'Plural')
|
||||
self.ReplaceBG = translate('OpenLP.Ui', 'Replace Background')
|
||||
self.ReplaceLiveBG = translate('OpenLP.Ui', 'Replace live background.')
|
||||
self.ReplaceLiveBGDisabled = translate('OpenLP.Ui', 'Replace live background is not available on this '
|
||||
'platform in this version of OpenLP.')
|
||||
self.ReplaceLiveBGDisabled = translate('OpenLP.Ui', 'Replace live background is not available when the WebKit '
|
||||
'player is disabled.')
|
||||
self.ResetBG = translate('OpenLP.Ui', 'Reset Background')
|
||||
self.ResetLiveBG = translate('OpenLP.Ui', 'Reset live background.')
|
||||
self.Seconds = translate('OpenLP.Ui', 's', 'The abbreviated unit for seconds')
|
||||
|
@ -515,7 +515,7 @@ class PJLink1(QTcpSocket):
|
||||
self.socket_timer.start()
|
||||
try:
|
||||
self.projectorNetwork.emit(S_NETWORK_SENDING)
|
||||
sent = self.write(out)
|
||||
sent = self.write(out.encode('ascii'))
|
||||
self.waitForBytesWritten(2000) # 2 seconds should be enough
|
||||
if sent == -1:
|
||||
# Network error?
|
||||
@ -665,7 +665,15 @@ class PJLink1(QTcpSocket):
|
||||
|
||||
:param data: Class that projector supports.
|
||||
"""
|
||||
self.pjlink_class = data
|
||||
# bug 1550891: Projector returns non-standard class response:
|
||||
# : Expected: %1CLSS=1
|
||||
# : Received: %1CLSS=Class 1
|
||||
if len(data) > 1:
|
||||
# Split non-standard information from response
|
||||
clss = data.split()[-1]
|
||||
else:
|
||||
clss = data
|
||||
self.pjlink_class = clss
|
||||
log.debug('(%s) Setting pjlink_class for this projector to "%s"' % (self.ip, self.pjlink_class))
|
||||
return
|
||||
|
||||
|
@ -20,6 +20,7 @@
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
|
||||
import re
|
||||
|
||||
from PyQt5 import QtGui, QtCore, QtWebKitWidgets
|
||||
|
||||
@ -441,7 +442,7 @@ class Renderer(OpenLPMixin, RegistryMixin, RegistryProperties):
|
||||
previous_raw = line + line_end
|
||||
continue
|
||||
# Figure out how many words of the line will fit on screen as the line will not fit as a whole.
|
||||
raw_words = Renderer.words_split(line)
|
||||
raw_words = words_split(line)
|
||||
html_words = list(map(expand_tags, raw_words))
|
||||
previous_html, previous_raw = \
|
||||
self._binary_chop(formatted, previous_html, previous_raw, html_words, raw_words, ' ', line_end)
|
||||
@ -528,8 +529,7 @@ def words_split(line):
|
||||
:param line: Line to be split
|
||||
"""
|
||||
# this parse we are to be wordy
|
||||
line = line.replace('\n', ' ')
|
||||
return line.split(' ')
|
||||
return re.split('\s+', line)
|
||||
|
||||
|
||||
def get_start_tags(raw_text):
|
||||
|
@ -86,12 +86,6 @@ class Display(QtWidgets.QGraphicsView):
|
||||
super(Display, self).__init__()
|
||||
self.controller = parent
|
||||
self.screen = {}
|
||||
# FIXME: On Mac OS X (tested on 10.7) the display screen is corrupt with
|
||||
# OpenGL. Only white blank screen is shown on the 2nd monitor all the
|
||||
# time. We need to investigate more how to use OpenGL properly on Mac OS
|
||||
# X.
|
||||
if not is_macosx() and not is_win():
|
||||
self.setViewport(QtOpenGL.QGLWidget())
|
||||
|
||||
def setup(self):
|
||||
"""
|
||||
@ -332,6 +326,9 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
|
||||
else:
|
||||
self.setVisible(False)
|
||||
self.setGeometry(self.screen['size'])
|
||||
# Workaround for bug #1531319, should not be needed with PyQt 5.6.
|
||||
if is_win():
|
||||
self.shake_web_view()
|
||||
|
||||
def direct_image(self, path, background):
|
||||
"""
|
||||
@ -401,8 +398,17 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
|
||||
# Wait for the fade to finish before geting the preview.
|
||||
# Important otherwise preview will have incorrect text if at all!
|
||||
if self.service_item.theme_data and self.service_item.theme_data.display_slide_transition:
|
||||
# Workaround for bug #1531319, should not be needed with PyQt 5.6.
|
||||
if is_win():
|
||||
fade_shake_timer = QtCore.QTimer(self)
|
||||
fade_shake_timer.setInterval(25)
|
||||
fade_shake_timer.timeout.connect(self.shake_web_view)
|
||||
fade_shake_timer.start()
|
||||
while not self.frame.evaluateJavaScript('show_text_completed()'):
|
||||
self.application.process_events()
|
||||
# Workaround for bug #1531319, should not be needed with PyQt 5.6.
|
||||
if is_win():
|
||||
fade_shake_timer.stop()
|
||||
# Wait for the webview to update before getting the preview.
|
||||
# Important otherwise first preview will miss the background !
|
||||
while not self.web_loaded:
|
||||
@ -420,6 +426,9 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
|
||||
self.setVisible(True)
|
||||
else:
|
||||
self.setVisible(True)
|
||||
# Workaround for bug #1531319, should not be needed with PyQt 5.6.
|
||||
if is_win():
|
||||
self.shake_web_view()
|
||||
return self.grab()
|
||||
|
||||
def build_html(self, service_item, image_path=''):
|
||||
@ -499,6 +508,9 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
|
||||
if self.isHidden():
|
||||
self.setVisible(True)
|
||||
self.web_view.setVisible(True)
|
||||
# Workaround for bug #1531319, should not be needed with PyQt 5.6.
|
||||
if is_win():
|
||||
self.shake_web_view()
|
||||
self.hide_mode = mode
|
||||
|
||||
def show_display(self):
|
||||
@ -517,6 +529,9 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
|
||||
# Trigger actions when display is active again.
|
||||
if self.is_live:
|
||||
Registry().execute('live_display_active')
|
||||
# Workaround for bug #1531319, should not be needed with PyQt 5.6.
|
||||
if is_win():
|
||||
self.shake_web_view()
|
||||
|
||||
def _hide_mouse(self):
|
||||
"""
|
||||
@ -559,6 +574,13 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
|
||||
if window_id == main_window_id:
|
||||
self.main_window.raise_()
|
||||
|
||||
def shake_web_view(self):
|
||||
"""
|
||||
Resizes the web_view a bit to force an update. Workaround for bug #1531319, should not be needed with PyQt 5.6.
|
||||
"""
|
||||
self.web_view.setGeometry(0, 0, self.width(), self.height() - 1)
|
||||
self.web_view.setGeometry(0, 0, self.width(), self.height())
|
||||
|
||||
|
||||
class AudioPlayer(OpenLPMixin, QtCore.QObject):
|
||||
"""
|
||||
@ -576,6 +598,7 @@ class AudioPlayer(OpenLPMixin, QtCore.QObject):
|
||||
self.player = QtMultimedia.QMediaPlayer()
|
||||
self.playlist = QtMultimedia.QMediaPlaylist(self.player)
|
||||
self.volume_slider = None
|
||||
self.player.setPlaylist(self.playlist)
|
||||
self.player.positionChanged.connect(self._on_position_changed)
|
||||
|
||||
def __del__(self):
|
||||
@ -643,7 +666,7 @@ class AudioPlayer(OpenLPMixin, QtCore.QObject):
|
||||
if not isinstance(file_names, list):
|
||||
file_names = [file_names]
|
||||
for file_name in file_names:
|
||||
self.playlist.addMedia(QtCore.QUrl(file_name))
|
||||
self.playlist.addMedia(QtMultimedia.QMediaContent(QtCore.QUrl.fromLocalFile(file_name)))
|
||||
|
||||
def next(self):
|
||||
"""
|
||||
|
1042
openlp/core/ui/media/vendor/vlc.py
vendored
1042
openlp/core/ui/media/vendor/vlc.py
vendored
File diff suppressed because it is too large
Load Diff
@ -320,9 +320,17 @@ class ShortcutListForm(QtWidgets.QDialog, Ui_ShortcutListDialog, RegistryPropert
|
||||
"""
|
||||
if not toggled:
|
||||
return
|
||||
self.on_primary_push_button_clicked(False)
|
||||
self.on_alternate_push_button_clicked(False)
|
||||
action = self._current_item_action()
|
||||
shortcuts = self._action_shortcuts(action)
|
||||
self.refresh_shortcut_list()
|
||||
primary_button_text = ''
|
||||
alternate_button_text = ''
|
||||
if shortcuts:
|
||||
primary_button_text = self.get_shortcut_string(shortcuts[0], for_display=True)
|
||||
if len(shortcuts) == 2:
|
||||
alternate_button_text = self.get_shortcut_string(shortcuts[1], for_display=True)
|
||||
self.primary_push_button.setText(primary_button_text)
|
||||
self.alternate_push_button.setText(alternate_button_text)
|
||||
|
||||
def save(self):
|
||||
"""
|
||||
|
@ -31,7 +31,7 @@ from threading import Lock
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from openlp.core.common import Registry, RegistryProperties, Settings, SlideLimits, UiStrings, translate, \
|
||||
RegistryMixin, OpenLPMixin
|
||||
RegistryMixin, OpenLPMixin, is_win
|
||||
from openlp.core.lib import OpenLPToolbar, ItemCapabilities, ServiceItem, ImageSource, ServiceItemAction, \
|
||||
ScreenList, build_icon, build_html
|
||||
from openlp.core.ui import HideMode, MainDisplay, Display, DisplayControllerType
|
||||
@ -601,13 +601,21 @@ class SlideController(DisplayController, RegistryProperties):
|
||||
def __add_actions_to_widget(self, widget):
|
||||
"""
|
||||
Add actions to the widget specified by `widget`
|
||||
This defines the controls available when Live display has stolen focus.
|
||||
Examples of this happening: Clicking anything in the live window or certain single screen mode scenarios.
|
||||
Needles to say, blank to modes should not be removed from here.
|
||||
For some reason this required a test. It may be found in test_slidecontroller.py as
|
||||
"live_stolen_focus_shortcuts_test. If you want to modify things here, you must also modify them there. (Duh)
|
||||
|
||||
:param widget: The UI widget for the actions
|
||||
"""
|
||||
widget.addActions([
|
||||
self.previous_item, self.next_item,
|
||||
self.previous_service, self.next_service,
|
||||
self.escape_item])
|
||||
self.escape_item,
|
||||
self.desktop_screen,
|
||||
self.theme_screen,
|
||||
self.blank_screen])
|
||||
|
||||
def preview_size_changed(self):
|
||||
"""
|
||||
@ -1125,8 +1133,8 @@ class SlideController(DisplayController, RegistryProperties):
|
||||
self.log_debug('update_preview %s ' % self.screens.current['primary'])
|
||||
if self.service_item and self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay):
|
||||
# Grab now, but try again in a couple of seconds if slide change is slow
|
||||
QtCore.QTimer.singleShot(0.5, self.grab_maindisplay)
|
||||
QtCore.QTimer.singleShot(2.5, self.grab_maindisplay)
|
||||
QtCore.QTimer.singleShot(500, self.grab_maindisplay)
|
||||
QtCore.QTimer.singleShot(2500, self.grab_maindisplay)
|
||||
else:
|
||||
self.slide_image = self.display.preview()
|
||||
self.slide_image.setDevicePixelRatio(self.main_window.devicePixelRatio())
|
||||
@ -1421,7 +1429,7 @@ class SlideController(DisplayController, RegistryProperties):
|
||||
|
||||
:param time: the time remaining
|
||||
"""
|
||||
seconds = self.display.audio_player.media_object.remainingTime() // 1000
|
||||
seconds = (self.display.audio_player.player.duration() - self.display.audio_player.player.position()) // 1000
|
||||
minutes = seconds // 60
|
||||
seconds %= 60
|
||||
self.audio_time_label.setText(' %02d:%02d ' % (minutes, seconds))
|
||||
|
@ -288,7 +288,7 @@ class BGExtract(RegistryProperties):
|
||||
except UnicodeDecodeError:
|
||||
page_source = str(page_source, 'cp1251')
|
||||
try:
|
||||
soup = BeautifulSoup(page_source)
|
||||
soup = BeautifulSoup(page_source, 'lxml')
|
||||
except Exception:
|
||||
log.error('BeautifulSoup could not parse the Bible page.')
|
||||
send_error_message('parse')
|
||||
@ -759,7 +759,7 @@ def get_soup_for_bible_ref(reference_url, header=None, pre_parse_regex=None, pre
|
||||
page_source = re.sub(pre_parse_regex, pre_parse_substitute, page_source.decode())
|
||||
soup = None
|
||||
try:
|
||||
soup = BeautifulSoup(page_source)
|
||||
soup = BeautifulSoup(page_source, 'lxml')
|
||||
CLEANER_REGEX.sub('', str(soup))
|
||||
except Exception:
|
||||
log.exception('BeautifulSoup could not parse the bible page.')
|
||||
|
@ -63,6 +63,7 @@ class Controller(object):
|
||||
if not self.doc.load_presentation():
|
||||
# Display error message to user
|
||||
# Inform slidecontroller that the action failed?
|
||||
self.doc.slidenumber = 0
|
||||
return
|
||||
self.doc.slidenumber = slide_no
|
||||
self.hide_mode = hide_mode
|
||||
|
@ -324,7 +324,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
|
||||
if self.topics_combo_box.hasFocus() and self.topics_combo_box.currentText():
|
||||
self.on_topic_add_button_clicked()
|
||||
return
|
||||
if self.songbooks_combo_box.hasFocus() and self.songbooks_combo_box.currentText():
|
||||
if self.songbooks_combo_box.hasFocus() or self.songbook_entry_edit.hasFocus():
|
||||
self.on_songbook_add_button_clicked()
|
||||
return
|
||||
QtWidgets.QDialog.keyPressEvent(self, event)
|
||||
@ -514,6 +514,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
|
||||
topic_name.setData(QtCore.Qt.UserRole, topic.id)
|
||||
self.topics_list_view.addItem(topic_name)
|
||||
self.songbooks_list_view.clear()
|
||||
self.songbook_entry_edit.clear()
|
||||
for songbook_entry in self.song.songbook_entries:
|
||||
self.add_songbook_entry_to_list(songbook_entry.songbook.id, songbook_entry.songbook.name,
|
||||
songbook_entry.entry)
|
||||
@ -843,7 +844,9 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
|
||||
:param text: The text of the verse order edit (ignored).
|
||||
"""
|
||||
# First make sure that all letters entered in the verse order field are uppercase
|
||||
pos = self.verse_order_edit.cursorPosition()
|
||||
self.verse_order_edit.setText(text.upper())
|
||||
self.verse_order_edit.setCursorPosition(pos)
|
||||
# Extract all verses which were used in the order.
|
||||
verses_in_order = self._extract_verse_order(self.verse_order_edit.text())
|
||||
# Find the verses which were not used in the order.
|
||||
|
@ -292,7 +292,7 @@ class EasyWorshipSongImport(SongImport):
|
||||
raw_record = db_file.read(record_size)
|
||||
self.fields = self.record_structure.unpack(raw_record)
|
||||
self.set_defaults()
|
||||
self.title = self.get_field(fi_title).decode('unicode-escape')
|
||||
self.title = self.get_field(fi_title).decode(self.encoding)
|
||||
# Get remaining fields.
|
||||
copy = self.get_field(fi_copy)
|
||||
admin = self.get_field(fi_admin)
|
||||
@ -300,16 +300,16 @@ class EasyWorshipSongImport(SongImport):
|
||||
authors = self.get_field(fi_author)
|
||||
words = self.get_field(fi_words)
|
||||
if copy:
|
||||
self.copyright = copy.decode('unicode-escape')
|
||||
self.copyright = copy.decode(self.encoding)
|
||||
if admin:
|
||||
if copy:
|
||||
self.copyright += ', '
|
||||
self.copyright += translate('SongsPlugin.EasyWorshipSongImport',
|
||||
'Administered by %s') % admin.decode('unicode-escape')
|
||||
'Administered by %s') % admin.decode(self.encoding)
|
||||
if ccli:
|
||||
self.ccli_number = ccli.decode('unicode-escape')
|
||||
self.ccli_number = ccli.decode(self.encoding)
|
||||
if authors:
|
||||
authors = authors.decode('unicode-escape')
|
||||
authors = authors.decode(self.encoding)
|
||||
else:
|
||||
authors = ''
|
||||
# Set the SongImport object members.
|
||||
@ -497,7 +497,7 @@ class EasyWorshipSongImport(SongImport):
|
||||
bytes = self.get_bytes(pos, length)
|
||||
mask = '<' + str(length) + 's'
|
||||
byte_str, = struct.unpack(mask, bytes)
|
||||
return byte_str.decode('unicode-escape').replace('\0', '').strip()
|
||||
return byte_str.decode(self.encoding).replace('\0', '').strip()
|
||||
|
||||
def get_i16(self, pos):
|
||||
"""
|
||||
|
@ -157,6 +157,7 @@ class OpenSongImport(SongImport):
|
||||
if isinstance(fn_or_string, str):
|
||||
if attr in ['ccli']:
|
||||
if ustring:
|
||||
ustring = ''.join(re.findall('\d+', ustring))
|
||||
setattr(self, fn_or_string, int(ustring))
|
||||
else:
|
||||
setattr(self, fn_or_string, None)
|
||||
|
@ -26,7 +26,7 @@ import os
|
||||
import shutil
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
from sqlalchemy.sql import or_
|
||||
from sqlalchemy.sql import and_, or_
|
||||
|
||||
from openlp.core.common import Registry, AppLocation, Settings, check_directory_exists, UiStrings, translate
|
||||
from openlp.core.lib import MediaManagerItem, ItemCapabilities, PluginStatus, ServiceItemContext, \
|
||||
@ -37,7 +37,7 @@ from openlp.plugins.songs.forms.songmaintenanceform import SongMaintenanceForm
|
||||
from openlp.plugins.songs.forms.songimportform import SongImportForm
|
||||
from openlp.plugins.songs.forms.songexportform import SongExportForm
|
||||
from openlp.plugins.songs.lib import VerseType, clean_string, delete_song
|
||||
from openlp.plugins.songs.lib.db import Author, AuthorType, Song, Book, MediaFile, SongBookEntry
|
||||
from openlp.plugins.songs.lib.db import Author, AuthorType, Song, Book, MediaFile, SongBookEntry, Topic
|
||||
from openlp.plugins.songs.lib.ui import SongStrings
|
||||
from openlp.plugins.songs.lib.openlyricsxml import OpenLyrics, SongXML
|
||||
|
||||
@ -52,8 +52,11 @@ class SongSearch(object):
|
||||
Titles = 2
|
||||
Lyrics = 3
|
||||
Authors = 4
|
||||
Books = 5
|
||||
Themes = 6
|
||||
Topics = 5
|
||||
Books = 6
|
||||
Themes = 7
|
||||
Copyright = 8
|
||||
CCLInumber = 9
|
||||
|
||||
|
||||
class SongMediaItem(MediaManagerItem):
|
||||
@ -151,9 +154,17 @@ class SongMediaItem(MediaManagerItem):
|
||||
translate('SongsPlugin.MediaItem', 'Search Lyrics...')),
|
||||
(SongSearch.Authors, ':/songs/song_search_author.png', SongStrings.Authors,
|
||||
translate('SongsPlugin.MediaItem', 'Search Authors...')),
|
||||
(SongSearch.Topics, ':/songs/song_search_topic.png', SongStrings.Topics,
|
||||
translate('SongsPlugin.MediaItem', 'Search Topics...')),
|
||||
(SongSearch.Books, ':/songs/song_book_edit.png', SongStrings.SongBooks,
|
||||
translate('SongsPlugin.MediaItem', 'Search Songbooks...')),
|
||||
(SongSearch.Themes, ':/slides/slide_theme.png', UiStrings().Themes, UiStrings().SearchThemes)
|
||||
(SongSearch.Themes, ':/slides/slide_theme.png', UiStrings().Themes, UiStrings().SearchThemes),
|
||||
(SongSearch.Copyright, ':/songs/song_search_copy.png',
|
||||
translate('SongsPlugin.MediaItem', 'Copyright'),
|
||||
translate('SongsPlugin.MediaItem', 'Search Copyright...')),
|
||||
(SongSearch.CCLInumber, ':/songs/song_search_ccli.png',
|
||||
translate('SongsPlugin.MediaItem', 'CCLI number'),
|
||||
translate('SongsPlugin.MediaItem', 'Search CCLI number...'))
|
||||
])
|
||||
self.search_text_edit.set_current_search_type(Settings().value('%s/last search type' % self.settings_section))
|
||||
self.config_update()
|
||||
@ -184,14 +195,33 @@ class SongMediaItem(MediaManagerItem):
|
||||
search_results = self.plugin.manager.get_all_objects(
|
||||
Author, Author.display_name.like(search_string), Author.display_name.asc())
|
||||
self.display_results_author(search_results)
|
||||
elif search_type == SongSearch.Topics:
|
||||
log.debug('Topics Search')
|
||||
search_string = '%' + search_keywords + '%'
|
||||
search_results = self.plugin.manager.get_all_objects(
|
||||
Topic, Topic.name.like(search_string), Topic.name.asc())
|
||||
self.display_results_topic(search_results)
|
||||
elif search_type == SongSearch.Books:
|
||||
log.debug('Songbook Search')
|
||||
self.display_results_book(search_keywords)
|
||||
elif search_type == SongSearch.Themes:
|
||||
log.debug('Theme Search')
|
||||
search_string = '%' + search_keywords + '%'
|
||||
search_results = self.plugin.manager.get_all_objects(Song, Song.theme_name.like(search_string))
|
||||
search_results = self.plugin.manager.get_all_objects(
|
||||
Song, Song.theme_name.like(search_string), Song.theme_name.asc())
|
||||
self.display_results_themes(search_results)
|
||||
elif search_type == SongSearch.Copyright:
|
||||
log.debug('Copyright Search')
|
||||
search_string = '%' + search_keywords + '%'
|
||||
search_results = self.plugin.manager.get_all_objects(
|
||||
Song, and_(Song.copyright.like(search_string), Song.copyright != ''))
|
||||
self.display_results_song(search_results)
|
||||
elif search_type == SongSearch.CCLInumber:
|
||||
log.debug('CCLI number Search')
|
||||
search_string = '%' + search_keywords + '%'
|
||||
search_results = self.plugin.manager.get_all_objects(
|
||||
Song, and_(Song.ccli_number.like(search_string), Song.ccli_number != ''))
|
||||
self.display_results_cclinumber(search_results)
|
||||
self.check_search_result()
|
||||
|
||||
def search_entire(self, search_keywords):
|
||||
@ -215,6 +245,12 @@ class SongMediaItem(MediaManagerItem):
|
||||
log.debug('on_song_list_load - finished')
|
||||
|
||||
def display_results_song(self, search_results):
|
||||
"""
|
||||
Display the song search results in the media manager list
|
||||
|
||||
:param search_results: A list of db Song objects
|
||||
:return: None
|
||||
"""
|
||||
log.debug('display results Song')
|
||||
self.save_auto_select_id()
|
||||
self.list_view.clear()
|
||||
@ -234,6 +270,12 @@ class SongMediaItem(MediaManagerItem):
|
||||
self.auto_select_id = -1
|
||||
|
||||
def display_results_author(self, search_results):
|
||||
"""
|
||||
Display the song search results in the media manager list, grouped by author
|
||||
|
||||
:param search_results: A list of db Author objects
|
||||
:return: None
|
||||
"""
|
||||
log.debug('display results Author')
|
||||
self.list_view.clear()
|
||||
for author in search_results:
|
||||
@ -247,6 +289,13 @@ class SongMediaItem(MediaManagerItem):
|
||||
self.list_view.addItem(song_name)
|
||||
|
||||
def display_results_book(self, search_keywords):
|
||||
"""
|
||||
Display the song search results in the media manager list, grouped by book
|
||||
|
||||
:param search_keywords: A list of search keywords - book first, then number
|
||||
:return: None
|
||||
"""
|
||||
|
||||
log.debug('display results Book')
|
||||
self.list_view.clear()
|
||||
|
||||
@ -270,6 +319,64 @@ class SongMediaItem(MediaManagerItem):
|
||||
song_name.setData(QtCore.Qt.UserRole, songbook_entry.song.id)
|
||||
self.list_view.addItem(song_name)
|
||||
|
||||
def display_results_topic(self, search_results):
|
||||
"""
|
||||
Display the song search results in the media manager list, grouped by topic
|
||||
|
||||
:param search_results: A list of db Topic objects
|
||||
:return: None
|
||||
"""
|
||||
log.debug('display results Topic')
|
||||
self.list_view.clear()
|
||||
search_results = sorted(search_results, key=lambda topic: self._natural_sort_key(topic.name))
|
||||
for topic in search_results:
|
||||
songs = sorted(topic.songs, key=lambda song: song.sort_key)
|
||||
for song in songs:
|
||||
# Do not display temporary songs
|
||||
if song.temporary:
|
||||
continue
|
||||
song_detail = '%s (%s)' % (topic.name, song.title)
|
||||
song_name = QtWidgets.QListWidgetItem(song_detail)
|
||||
song_name.setData(QtCore.Qt.UserRole, song.id)
|
||||
self.list_view.addItem(song_name)
|
||||
|
||||
def display_results_themes(self, search_results):
|
||||
"""
|
||||
Display the song search results in the media manager list, sorted by theme
|
||||
|
||||
:param search_results: A list of db Song objects
|
||||
:return: None
|
||||
"""
|
||||
log.debug('display results Themes')
|
||||
self.list_view.clear()
|
||||
for song in search_results:
|
||||
# Do not display temporary songs
|
||||
if song.temporary:
|
||||
continue
|
||||
song_detail = '%s (%s)' % (song.theme_name, song.title)
|
||||
song_name = QtWidgets.QListWidgetItem(song_detail)
|
||||
song_name.setData(QtCore.Qt.UserRole, song.id)
|
||||
self.list_view.addItem(song_name)
|
||||
|
||||
def display_results_cclinumber(self, search_results):
|
||||
"""
|
||||
Display the song search results in the media manager list, sorted by CCLI number
|
||||
|
||||
:param search_results: A list of db Song objects
|
||||
:return: None
|
||||
"""
|
||||
log.debug('display results CCLI number')
|
||||
self.list_view.clear()
|
||||
songs = sorted(search_results, key=lambda song: self._natural_sort_key(song.ccli_number))
|
||||
for song in songs:
|
||||
# Do not display temporary songs
|
||||
if song.temporary:
|
||||
continue
|
||||
song_detail = '%s (%s)' % (song.ccli_number, song.title)
|
||||
song_name = QtWidgets.QListWidgetItem(song_detail)
|
||||
song_name.setData(QtCore.Qt.UserRole, song.id)
|
||||
self.list_view.addItem(song_name)
|
||||
|
||||
def on_clear_text_button_click(self):
|
||||
"""
|
||||
Clear the search text.
|
||||
@ -587,6 +694,14 @@ class SongMediaItem(MediaManagerItem):
|
||||
# List must be empty at the end
|
||||
return not author_list
|
||||
|
||||
def _natural_sort_key(self, s):
|
||||
"""
|
||||
Return a tuple by which s is sorted.
|
||||
:param s: A string value from the list we want to sort.
|
||||
"""
|
||||
return [int(text) if text.isdecimal() else text.lower()
|
||||
for text in re.split('(\d+)', s)]
|
||||
|
||||
def search(self, string, show_error):
|
||||
"""
|
||||
Search for some songs
|
||||
|
@ -118,7 +118,6 @@ class SongUsagePlugin(Plugin):
|
||||
self.main_window.status_bar.insertPermanentWidget(1, self.song_usage_active_button)
|
||||
self.song_usage_active_button.hide()
|
||||
# Signals and slots
|
||||
self.song_usage_status.changed.connect(self.toggle_song_usage_state)
|
||||
self.song_usage_active_button.toggled.connect(self.toggle_song_usage_state)
|
||||
self.song_usage_menu.menuAction().setVisible(False)
|
||||
|
||||
|
4764
resources/i18n/af.ts
4764
resources/i18n/af.ts
File diff suppressed because it is too large
Load Diff
4522
resources/i18n/bg.ts
4522
resources/i18n/bg.ts
File diff suppressed because it is too large
Load Diff
3764
resources/i18n/cs.ts
3764
resources/i18n/cs.ts
File diff suppressed because it is too large
Load Diff
3769
resources/i18n/da.ts
3769
resources/i18n/da.ts
File diff suppressed because it is too large
Load Diff
3823
resources/i18n/de.ts
3823
resources/i18n/de.ts
File diff suppressed because it is too large
Load Diff
4804
resources/i18n/el.ts
4804
resources/i18n/el.ts
File diff suppressed because it is too large
Load Diff
3762
resources/i18n/en.ts
3762
resources/i18n/en.ts
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
3767
resources/i18n/es.ts
3767
resources/i18n/es.ts
File diff suppressed because it is too large
Load Diff
3762
resources/i18n/et.ts
3762
resources/i18n/et.ts
File diff suppressed because it is too large
Load Diff
3839
resources/i18n/fi.ts
3839
resources/i18n/fi.ts
File diff suppressed because it is too large
Load Diff
4201
resources/i18n/fr.ts
4201
resources/i18n/fr.ts
File diff suppressed because it is too large
Load Diff
3766
resources/i18n/hu.ts
3766
resources/i18n/hu.ts
File diff suppressed because it is too large
Load Diff
3762
resources/i18n/id.ts
3762
resources/i18n/id.ts
File diff suppressed because it is too large
Load Diff
3902
resources/i18n/ja.ts
3902
resources/i18n/ja.ts
File diff suppressed because it is too large
Load Diff
4942
resources/i18n/ko.ts
4942
resources/i18n/ko.ts
File diff suppressed because it is too large
Load Diff
3804
resources/i18n/lt.ts
3804
resources/i18n/lt.ts
File diff suppressed because it is too large
Load Diff
3764
resources/i18n/nb.ts
3764
resources/i18n/nb.ts
File diff suppressed because it is too large
Load Diff
3762
resources/i18n/nl.ts
3762
resources/i18n/nl.ts
File diff suppressed because it is too large
Load Diff
3924
resources/i18n/pl.ts
3924
resources/i18n/pl.ts
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
5792
resources/i18n/ru.ts
5792
resources/i18n/ru.ts
File diff suppressed because it is too large
Load Diff
3769
resources/i18n/sk.ts
3769
resources/i18n/sk.ts
File diff suppressed because it is too large
Load Diff
3890
resources/i18n/sv.ts
3890
resources/i18n/sv.ts
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -3,8 +3,11 @@
|
||||
<file>song_search_stop.png</file>
|
||||
<file>song_search_all.png</file>
|
||||
<file>song_search_author.png</file>
|
||||
<file>song_search_ccli.png</file>
|
||||
<file>song_search_copy.png</file>
|
||||
<file>song_search_lyrics.png</file>
|
||||
<file>song_search_title.png</file>
|
||||
<file>song_search_topic.png</file>
|
||||
<file>topic_edit.png</file>
|
||||
<file>author_add.png</file>
|
||||
<file>author_delete.png</file>
|
||||
|
BIN
resources/images/song_search_ccli.png
Normal file
BIN
resources/images/song_search_ccli.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 403 B |
BIN
resources/images/song_search_copy.png
Normal file
BIN
resources/images/song_search_copy.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 498 B |
BIN
resources/images/song_search_topic.png
Normal file
BIN
resources/images/song_search_topic.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 993 B |
@ -60,3 +60,17 @@ class TestPJLink(TestCase):
|
||||
"Connection request should have been called with TEST_SALT"))
|
||||
self.assertTrue(mock_qmd5_hash.called_with(TEST_PIN,
|
||||
"Connection request should have been called with TEST_PIN"))
|
||||
|
||||
def non_standard_class_reply_test(self):
|
||||
"""
|
||||
bugfix 1550891 - CLSS request returns non-standard 'Class N' reply
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
|
||||
# WHEN: Process non-standard reply
|
||||
pjlink.process_clss('Class 1')
|
||||
|
||||
# THEN: Projector class should be set with proper value
|
||||
self.assertEquals(pjlink.pjlink_class, '1',
|
||||
'Non-standard class reply should have set proper class')
|
||||
|
@ -27,9 +27,10 @@ from unittest import TestCase
|
||||
from PyQt5 import QtCore
|
||||
|
||||
from openlp.core.common import Registry
|
||||
from openlp.core.lib import Renderer, ScreenList, ServiceItem
|
||||
from openlp.core.lib import Renderer, ScreenList, ServiceItem, FormattingTags
|
||||
from openlp.core.lib.renderer import words_split, get_start_tags
|
||||
|
||||
from tests.functional import MagicMock
|
||||
from tests.functional import MagicMock, patch
|
||||
|
||||
SCREEN = {
|
||||
'primary': False,
|
||||
@ -71,34 +72,39 @@ class TestRenderer(TestCase):
|
||||
self.assertEqual(renderer.screen_ratio, 0.75, 'The base renderer should be a live controller')
|
||||
self.assertEqual(renderer.footer_start, 691, 'The base renderer should be a live controller')
|
||||
|
||||
def _get_start_tags_test(self):
|
||||
@patch('openlp.core.lib.renderer.FormattingTags.get_html_tags')
|
||||
def get_start_tags_test(self, mocked_get_html_tags):
|
||||
"""
|
||||
Test the _get_start_tags() method
|
||||
Test the get_start_tags() method
|
||||
"""
|
||||
# GIVEN: A new renderer instance. Broken raw_text (missing closing tags).
|
||||
renderer = Renderer()
|
||||
given_raw_text = '{st}{r}Text text text'
|
||||
expected_tuple = ('{st}{r}Text text text{/r}{/st}', '{st}{r}',
|
||||
'<strong><span style="-webkit-text-fill-color:red">')
|
||||
mocked_get_html_tags.return_value = [{'temporary': False, 'end tag': '{/r}', 'desc': 'Red',
|
||||
'start html': '<span style="-webkit-text-fill-color:red">',
|
||||
'end html': '</span>', 'start tag': '{r}', 'protected': True},
|
||||
{'temporary': False, 'end tag': '{/st}', 'desc': 'Bold',
|
||||
'start html': '<strong>', 'end html': '</strong>', 'start tag': '{st}',
|
||||
'protected': True}]
|
||||
|
||||
# WHEN: The renderer converts the start tags
|
||||
result = renderer._get_start_tags(given_raw_text)
|
||||
result = get_start_tags(given_raw_text)
|
||||
|
||||
# THEN: Check if the correct tuple is returned.
|
||||
self.assertEqual(result, expected_tuple), 'A tuple should be returned containing the text with correct ' \
|
||||
'tags, the opening tags, and the opening html tags.'
|
||||
|
||||
def _word_split_test(self):
|
||||
def word_split_test(self):
|
||||
"""
|
||||
Test the _word_split() method
|
||||
Test the word_split() method
|
||||
"""
|
||||
# GIVEN: A line of text
|
||||
renderer = Renderer()
|
||||
given_line = 'beginning asdf \n end asdf'
|
||||
expected_words = ['beginning', 'asdf', 'end', 'asdf']
|
||||
|
||||
# WHEN: Split the line based on word split rules
|
||||
result_words = renderer._words_split(given_line)
|
||||
result_words = words_split(given_line)
|
||||
|
||||
# THEN: The word lists should be the same.
|
||||
self.assertListEqual(result_words, expected_words)
|
||||
|
@ -685,6 +685,34 @@ class TestSlideController(TestCase):
|
||||
self.assertEqual('mocked_presentation_item_stop', mocked_execute.call_args_list[1][0][0],
|
||||
'The presentation should have been stopped.')
|
||||
|
||||
def live_stolen_focus_shortcuts_test(self):
|
||||
"""
|
||||
Test that all the needed shortcuts are available in scenarios where Live has stolen focus.
|
||||
These are found under def __add_actions_to_widget(self, widget): in slidecontroller.py
|
||||
"""
|
||||
# GIVEN: A slide controller, actions needed
|
||||
slide_controller = SlideController(None)
|
||||
mocked_widget = MagicMock()
|
||||
slide_controller.previous_item = MagicMock()
|
||||
slide_controller.next_item = MagicMock()
|
||||
slide_controller.previous_service = MagicMock()
|
||||
slide_controller.next_service = MagicMock()
|
||||
slide_controller.escape_item = MagicMock()
|
||||
slide_controller.desktop_screen = MagicMock()
|
||||
slide_controller.blank_screen = MagicMock()
|
||||
slide_controller.theme_screen = MagicMock()
|
||||
|
||||
# WHEN: __add_actions_to_widget is called
|
||||
slide_controller._SlideController__add_actions_to_widget(mocked_widget)
|
||||
|
||||
# THEN: The call to addActions should be correct
|
||||
mocked_widget.addActions.assert_called_with([
|
||||
slide_controller.previous_item, slide_controller.next_item,
|
||||
slide_controller.previous_service, slide_controller.next_service,
|
||||
slide_controller.escape_item, slide_controller.desktop_screen,
|
||||
slide_controller.theme_screen, slide_controller.blank_screen
|
||||
])
|
||||
|
||||
|
||||
class TestInfoLabel(TestCase):
|
||||
|
||||
|
@ -152,7 +152,7 @@ class TestBSExtract(TestCase):
|
||||
self.test_html = '<ul><li><a href="/overlay/selectChapter?tocBook=1">Genesis</a></li>' \
|
||||
'<li><a href="/overlay/selectChapter?tocBook=2"></a></li>' \
|
||||
'<li><a href="/overlay/selectChapter?tocBook=3">Leviticus</a></li></ul>'
|
||||
self.test_soup = BeautifulSoup(self.test_html)
|
||||
self.test_soup = BeautifulSoup(self.test_html, 'lxml')
|
||||
instance = BSExtract()
|
||||
self.mock_log.reset_mock()
|
||||
self.mock_urllib.reset_mock()
|
||||
|
@ -26,6 +26,7 @@ from unittest import TestCase
|
||||
|
||||
from openlp.core.common import Registry
|
||||
from openlp.plugins.presentations.lib.mediaitem import MessageListener, PresentationMediaItem
|
||||
from openlp.plugins.presentations.lib.messagelistener import Controller
|
||||
from tests.functional import patch, MagicMock
|
||||
from tests.helpers.testmixin import TestMixin
|
||||
|
||||
@ -124,3 +125,26 @@ class TestMessageListener(TestCase, TestMixin):
|
||||
|
||||
# THEN: The handler should be set to None
|
||||
self.assertIsNone(ml.handler, 'The handler should be None')
|
||||
|
||||
|
||||
class TestController(TestCase, TestMixin):
|
||||
"""
|
||||
Test the Presentation Controller.
|
||||
"""
|
||||
|
||||
def add_handler_failure_test(self):
|
||||
"""
|
||||
Test that add_handler does set doc.slidenumber to 0 in case filed loading
|
||||
"""
|
||||
# GIVEN: A Controller, a mocked doc-controller
|
||||
controller = Controller(True)
|
||||
mocked_doc_controller = MagicMock()
|
||||
mocked_doc = MagicMock()
|
||||
mocked_doc.load_presentation.return_value = False
|
||||
mocked_doc_controller.add_document.return_value = mocked_doc
|
||||
|
||||
# WHEN: calling add_handler that fails
|
||||
controller.add_handler(mocked_doc_controller, MagicMock(), True, 0)
|
||||
|
||||
# THEN: slidenumber should be 0
|
||||
self.assertEqual(controller.doc.slidenumber, 0, 'doc.slidenumber should be 0')
|
||||
|
@ -48,6 +48,12 @@ class TestMediaItem(TestCase, TestMixin):
|
||||
with patch('openlp.core.lib.mediamanageritem.MediaManagerItem._setup'), \
|
||||
patch('openlp.plugins.songs.forms.editsongform.EditSongForm.__init__'):
|
||||
self.media_item = SongMediaItem(None, MagicMock())
|
||||
self.media_item.save_auto_select_id = MagicMock()
|
||||
self.media_item.list_view = MagicMock()
|
||||
self.media_item.list_view.save_auto_select_id = MagicMock()
|
||||
self.media_item.list_view.clear = MagicMock()
|
||||
self.media_item.list_view.addItem = MagicMock()
|
||||
self.media_item.auto_select_id = -1
|
||||
self.media_item.display_songbook = False
|
||||
self.media_item.display_copyright_symbol = False
|
||||
self.setup_application()
|
||||
@ -60,6 +66,151 @@ class TestMediaItem(TestCase, TestMixin):
|
||||
"""
|
||||
self.destroy_settings()
|
||||
|
||||
def display_results_song_test(self):
|
||||
"""
|
||||
Test displaying song search results with basic song
|
||||
"""
|
||||
# GIVEN: Search results, plus a mocked QtListWidgetItem
|
||||
with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem, \
|
||||
patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole:
|
||||
mock_search_results = []
|
||||
mock_song = MagicMock()
|
||||
mock_song.id = 1
|
||||
mock_song.title = 'My Song'
|
||||
mock_song.sort_key = 'My Song'
|
||||
mock_song.authors = []
|
||||
mock_author = MagicMock()
|
||||
mock_author.display_name = 'My Author'
|
||||
mock_song.authors.append(mock_author)
|
||||
mock_song.temporary = False
|
||||
mock_search_results.append(mock_song)
|
||||
mock_qlist_widget = MagicMock()
|
||||
MockedQListWidgetItem.return_value = mock_qlist_widget
|
||||
|
||||
# WHEN: I display song search results
|
||||
self.media_item.display_results_song(mock_search_results)
|
||||
|
||||
# THEN: The current list view is cleared, the widget is created, and the relevant attributes set
|
||||
self.media_item.list_view.clear.assert_called_with()
|
||||
self.media_item.save_auto_select_id.assert_called_with()
|
||||
MockedQListWidgetItem.assert_called_with('My Song (My Author)')
|
||||
mock_qlist_widget.setData.assert_called_with(MockedUserRole, mock_song.id)
|
||||
self.media_item.list_view.addItem.assert_called_with(mock_qlist_widget)
|
||||
|
||||
def display_results_author_test(self):
|
||||
"""
|
||||
Test displaying song search results grouped by author with basic song
|
||||
"""
|
||||
# GIVEN: Search results grouped by author, plus a mocked QtListWidgetItem
|
||||
with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem, \
|
||||
patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole:
|
||||
mock_search_results = []
|
||||
mock_author = MagicMock()
|
||||
mock_song = MagicMock()
|
||||
mock_author.display_name = 'My Author'
|
||||
mock_author.songs = []
|
||||
mock_song.id = 1
|
||||
mock_song.title = 'My Song'
|
||||
mock_song.sort_key = 'My Song'
|
||||
mock_song.temporary = False
|
||||
mock_author.songs.append(mock_song)
|
||||
mock_search_results.append(mock_author)
|
||||
mock_qlist_widget = MagicMock()
|
||||
MockedQListWidgetItem.return_value = mock_qlist_widget
|
||||
|
||||
# WHEN: I display song search results grouped by author
|
||||
self.media_item.display_results_author(mock_search_results)
|
||||
|
||||
# THEN: The current list view is cleared, the widget is created, and the relevant attributes set
|
||||
self.media_item.list_view.clear.assert_called_with()
|
||||
MockedQListWidgetItem.assert_called_with('My Author (My Song)')
|
||||
mock_qlist_widget.setData.assert_called_with(MockedUserRole, mock_song.id)
|
||||
self.media_item.list_view.addItem.assert_called_with(mock_qlist_widget)
|
||||
|
||||
def display_results_topic_test(self):
|
||||
"""
|
||||
Test displaying song search results grouped by topic with basic song
|
||||
"""
|
||||
# GIVEN: Search results grouped by topic, plus a mocked QtListWidgetItem
|
||||
with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem, \
|
||||
patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole:
|
||||
mock_search_results = []
|
||||
mock_topic = MagicMock()
|
||||
mock_song = MagicMock()
|
||||
mock_topic.name = 'My Topic'
|
||||
mock_topic.songs = []
|
||||
mock_song.id = 1
|
||||
mock_song.title = 'My Song'
|
||||
mock_song.sort_key = 'My Song'
|
||||
mock_song.temporary = False
|
||||
mock_topic.songs.append(mock_song)
|
||||
mock_search_results.append(mock_topic)
|
||||
mock_qlist_widget = MagicMock()
|
||||
MockedQListWidgetItem.return_value = mock_qlist_widget
|
||||
|
||||
# WHEN: I display song search results grouped by topic
|
||||
self.media_item.display_results_topic(mock_search_results)
|
||||
|
||||
# THEN: The current list view is cleared, the widget is created, and the relevant attributes set
|
||||
self.media_item.list_view.clear.assert_called_with()
|
||||
MockedQListWidgetItem.assert_called_with('My Topic (My Song)')
|
||||
mock_qlist_widget.setData.assert_called_with(MockedUserRole, mock_song.id)
|
||||
self.media_item.list_view.addItem.assert_called_with(mock_qlist_widget)
|
||||
|
||||
def display_results_themes_test(self):
|
||||
"""
|
||||
Test displaying song search results sorted by theme with basic song
|
||||
"""
|
||||
# GIVEN: Search results sorted by theme, plus a mocked QtListWidgetItem
|
||||
with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem, \
|
||||
patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole:
|
||||
mock_search_results = []
|
||||
mock_song = MagicMock()
|
||||
mock_song.id = 1
|
||||
mock_song.title = 'My Song'
|
||||
mock_song.sort_key = 'My Song'
|
||||
mock_song.theme_name = 'My Theme'
|
||||
mock_song.temporary = False
|
||||
mock_search_results.append(mock_song)
|
||||
mock_qlist_widget = MagicMock()
|
||||
MockedQListWidgetItem.return_value = mock_qlist_widget
|
||||
|
||||
# WHEN: I display song search results sorted by theme
|
||||
self.media_item.display_results_themes(mock_search_results)
|
||||
|
||||
# THEN: The current list view is cleared, the widget is created, and the relevant attributes set
|
||||
self.media_item.list_view.clear.assert_called_with()
|
||||
MockedQListWidgetItem.assert_called_with('My Theme (My Song)')
|
||||
mock_qlist_widget.setData.assert_called_with(MockedUserRole, mock_song.id)
|
||||
self.media_item.list_view.addItem.assert_called_with(mock_qlist_widget)
|
||||
|
||||
def display_results_cclinumber_test(self):
|
||||
"""
|
||||
Test displaying song search results sorted by CCLI number with basic song
|
||||
"""
|
||||
# GIVEN: Search results sorted by CCLI number, plus a mocked QtListWidgetItem
|
||||
with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem, \
|
||||
patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole:
|
||||
mock_search_results = []
|
||||
mock_song = MagicMock()
|
||||
mock_song.id = 1
|
||||
mock_song.title = 'My Song'
|
||||
mock_song.sort_key = 'My Song'
|
||||
mock_song.ccli_number = '12345'
|
||||
mock_song.temporary = False
|
||||
mock_search_results.append(mock_song)
|
||||
mock_qlist_widget = MagicMock()
|
||||
MockedQListWidgetItem.return_value = mock_qlist_widget
|
||||
|
||||
# WHEN: I display song search results sorted by CCLI number
|
||||
self.media_item.display_results_cclinumber(mock_search_results)
|
||||
|
||||
# THEN: The current list view is cleared, the widget is created, and the relevant attributes set
|
||||
self.media_item.list_view.clear.assert_called_with()
|
||||
MockedQListWidgetItem.assert_called_with('12345 (My Song)')
|
||||
mock_qlist_widget.setData.assert_called_with(MockedUserRole, mock_song.id)
|
||||
self.media_item.list_view.addItem.assert_called_with(mock_qlist_widget)
|
||||
|
||||
def build_song_footer_one_author_test(self):
|
||||
"""
|
||||
Test build songs footer with basic song and one author
|
||||
@ -265,6 +416,19 @@ class TestMediaItem(TestCase, TestMixin):
|
||||
# THEN: They should not match
|
||||
self.assertFalse(result, "Authors should not match")
|
||||
|
||||
def natural_sort_key_test(self):
|
||||
"""
|
||||
Test the _natural_sort_key function
|
||||
"""
|
||||
# GIVEN: A string to be converted into a sort key
|
||||
string_sort_key = 'A1B12C'
|
||||
|
||||
# WHEN: We attempt to create a sort key
|
||||
sort_key_result = self.media_item._natural_sort_key(string_sort_key)
|
||||
|
||||
# THEN: We should get back a tuple split on integers
|
||||
self.assertEqual(sort_key_result, ['a', 1, 'b', 12, 'c'])
|
||||
|
||||
def build_remote_search_test(self):
|
||||
"""
|
||||
Test results for the remote search api
|
||||
|
@ -74,6 +74,13 @@ author_xml = '<properties>\
|
||||
</authors>\
|
||||
</properties>'
|
||||
|
||||
songbook_xml = '<properties>\
|
||||
<songbooks>\
|
||||
<songbook name="Collection 1" entry="48"/>\
|
||||
<songbook name="Collection 2" entry="445 A"/>\
|
||||
</songbooks>\
|
||||
</properties>'
|
||||
|
||||
|
||||
class TestOpenLyricsImport(TestCase, TestMixin):
|
||||
"""
|
||||
@ -166,3 +173,22 @@ class TestOpenLyricsImport(TestCase, TestMixin):
|
||||
# THEN: add_author should have been called twice
|
||||
self.assertEquals(mocked_song.method_calls[0][1][1], 'words+music')
|
||||
self.assertEquals(mocked_song.method_calls[1][1][1], 'words')
|
||||
|
||||
def process_songbooks_test(self):
|
||||
"""
|
||||
Test that _process_songbooks works
|
||||
"""
|
||||
# GIVEN: A OpenLyric XML with songbooks and a mocked out manager
|
||||
with patch('openlp.plugins.songs.lib.openlyricsxml.Book'):
|
||||
mocked_manager = MagicMock()
|
||||
mocked_manager.get_object_filtered.return_value = None
|
||||
ol = OpenLyrics(mocked_manager)
|
||||
properties_xml = objectify.fromstring(songbook_xml)
|
||||
mocked_song = MagicMock()
|
||||
|
||||
# WHEN: processing the songbook xml
|
||||
ol._process_songbooks(properties_xml, mocked_song)
|
||||
|
||||
# THEN: add_songbook_entry should have been called twice
|
||||
self.assertEquals(mocked_song.method_calls[0][1][1], '48')
|
||||
self.assertEquals(mocked_song.method_calls[1][1][1], '445 A')
|
||||
|
@ -52,6 +52,8 @@ class TestOpenSongFileImport(SongImportTestHelper):
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Beautiful Garden Of Prayer.json')))
|
||||
self.file_import([os.path.join(TEST_PATH, 'One, Two, Three, Four, Five')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'One, Two, Three, Four, Five.json')))
|
||||
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace2')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
||||
|
||||
|
||||
class TestOpenSongImport(TestCase):
|
||||
|
@ -44,7 +44,5 @@ class TestPresentationManagerFileImport(SongImportTestHelper):
|
||||
"""
|
||||
self.file_import([os.path.join(TEST_PATH, 'Great Is Thy Faithfulness.sng')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Great Is Thy Faithfulness.json')))
|
||||
self.file_import([os.path.join(TEST_PATH, 'Agnus Dei.sng')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Agnus Dei.json')))
|
||||
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace.sng')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
||||
|
@ -45,5 +45,3 @@ class TestProPresenterFileImport(SongImportTestHelper):
|
||||
"""
|
||||
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace.pro4')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
||||
self.file_import([os.path.join(TEST_PATH, 'Vaste Grond.pro4')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Vaste Grond.json')))
|
||||
|
@ -45,7 +45,5 @@ class TestSundayPlusFileImport(SongImportTestHelper):
|
||||
with patch('openlp.plugins.songs.lib.importers.sundayplus.retrieve_windows_encoding') as \
|
||||
mocked_retrieve_windows_encoding:
|
||||
mocked_retrieve_windows_encoding.return_value = 'cp1252'
|
||||
self.file_import([os.path.join(TEST_PATH, 'Abba Fader.ptf')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'abba-fader.json')))
|
||||
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace.ptf')],
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
||||
|
@ -49,5 +49,3 @@ class TestWorshipAssistantFileImport(SongImportTestHelper):
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'would_you_be_free.json')))
|
||||
self.file_import(os.path.join(TEST_PATH, 'would_you_be_free2.csv'),
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'would_you_be_free.json')))
|
||||
self.file_import(os.path.join(TEST_PATH, 'lift_up_your_heads.csv'),
|
||||
self.load_external_result_data(os.path.join(TEST_PATH, 'lift_up_your_heads.json')))
|
||||
|
@ -23,12 +23,23 @@
|
||||
This module contains tests for the Songusage plugin.
|
||||
"""
|
||||
from unittest import TestCase
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from openlp.core import Registry
|
||||
from openlp.plugins.songusage.lib import upgrade
|
||||
from openlp.plugins.songusage.lib.db import init_schema
|
||||
from openlp.plugins.songusage.songusageplugin import SongUsagePlugin
|
||||
|
||||
|
||||
class TestSongUsage(TestCase):
|
||||
|
||||
def test_about_text(self):
|
||||
def setUp(self):
|
||||
Registry.create()
|
||||
|
||||
def about_text_test(self):
|
||||
"""
|
||||
Test the about text of the song usage plugin
|
||||
"""
|
||||
# GIVEN: The SongUsagePlugin
|
||||
# WHEN: Retrieving the about text
|
||||
# THEN: about() should return a string object
|
||||
@ -36,3 +47,53 @@ class TestSongUsage(TestCase):
|
||||
# THEN: about() should return a non-empty string
|
||||
self.assertNotEquals(len(SongUsagePlugin.about()), 0)
|
||||
self.assertNotEquals(len(SongUsagePlugin.about()), 0)
|
||||
|
||||
@patch('openlp.plugins.songusage.songusageplugin.Manager')
|
||||
def song_usage_init_test(self, MockedManager):
|
||||
"""
|
||||
Test the initialisation of the SongUsagePlugin class
|
||||
"""
|
||||
# GIVEN: A mocked database manager
|
||||
mocked_manager = MagicMock()
|
||||
MockedManager.return_value = mocked_manager
|
||||
|
||||
# WHEN: The SongUsagePlugin class is instantiated
|
||||
song_usage = SongUsagePlugin()
|
||||
|
||||
# THEN: It should be initialised correctly
|
||||
MockedManager.assert_called_with('songusage', init_schema, upgrade_mod=upgrade)
|
||||
self.assertEqual(mocked_manager, song_usage.manager)
|
||||
self.assertFalse(song_usage.song_usage_active)
|
||||
|
||||
@patch('openlp.plugins.songusage.songusageplugin.Manager')
|
||||
def check_pre_conditions_test(self, MockedManager):
|
||||
"""
|
||||
Test that check_pre_condition returns true for valid manager session
|
||||
"""
|
||||
# GIVEN: A mocked database manager
|
||||
mocked_manager = MagicMock()
|
||||
mocked_manager.session = MagicMock()
|
||||
MockedManager.return_value = mocked_manager
|
||||
song_usage = SongUsagePlugin()
|
||||
|
||||
# WHEN: The calling check_pre_conditions
|
||||
ret = song_usage.check_pre_conditions()
|
||||
|
||||
# THEN: It should return True
|
||||
self.assertTrue(ret)
|
||||
|
||||
@patch('openlp.plugins.songusage.songusageplugin.Manager')
|
||||
def toggle_song_usage_state_test(self, MockedManager):
|
||||
"""
|
||||
Test that toggle_song_usage_state does toggle song_usage_state
|
||||
"""
|
||||
# GIVEN: A SongUsagePlugin
|
||||
song_usage = SongUsagePlugin()
|
||||
song_usage.set_button_state = MagicMock()
|
||||
song_usage.song_usage_active = True
|
||||
|
||||
# WHEN: calling toggle_song_usage_state
|
||||
song_usage.toggle_song_usage_state()
|
||||
|
||||
# THEN: song_usage_state should have been toogled
|
||||
self.assertFalse(song_usage.song_usage_active)
|
||||
|
@ -28,6 +28,7 @@ import logging
|
||||
log = logging.getLogger(__name__)
|
||||
log.debug('test_projectorsourceform loaded')
|
||||
import os
|
||||
import time
|
||||
from unittest import TestCase
|
||||
|
||||
from PyQt5.QtWidgets import QDialog
|
||||
|
@ -24,11 +24,12 @@ Package to test the openlp.core.ui.shortcutform package.
|
||||
"""
|
||||
from unittest import TestCase
|
||||
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from openlp.core.common import Registry
|
||||
from openlp.core.ui.shortcutlistform import ShortcutListForm
|
||||
from tests.interfaces import patch
|
||||
|
||||
from tests.interfaces import MagicMock, patch
|
||||
from tests.helpers.testmixin import TestMixin
|
||||
|
||||
|
||||
@ -59,13 +60,170 @@ class TestShortcutform(TestCase, TestMixin):
|
||||
button = QtWidgets.QPushButton()
|
||||
checked = True
|
||||
enabled = True
|
||||
text = "new!"
|
||||
text = 'new!'
|
||||
|
||||
# WHEN: Call the method.
|
||||
with patch('PyQt5.QtWidgets.QPushButton.setChecked') as mocked_check_method:
|
||||
self.form._adjust_button(button, checked, enabled, text)
|
||||
|
||||
# THEN: The button should be changed.
|
||||
self.assertEqual(button.text(), text, "The text should match.")
|
||||
self.assertEqual(button.text(), text, 'The text should match.')
|
||||
mocked_check_method.assert_called_once_with(True)
|
||||
self.assertEqual(button.isEnabled(), enabled, "The button should be disabled.")
|
||||
self.assertEqual(button.isEnabled(), enabled, 'The button should be disabled.')
|
||||
|
||||
def space_key_press_event_test(self):
|
||||
"""
|
||||
Test the keyPressEvent when the spacebar was pressed
|
||||
"""
|
||||
# GIVEN: A key event that is a space
|
||||
mocked_event = MagicMock()
|
||||
mocked_event.key.return_value = QtCore.Qt.Key_Space
|
||||
|
||||
# WHEN: The event is handled
|
||||
with patch.object(self.form, 'keyReleaseEvent') as mocked_key_release_event:
|
||||
self.form.keyPressEvent(mocked_event)
|
||||
|
||||
# THEN: The key should be released
|
||||
mocked_key_release_event.assert_called_with(mocked_event)
|
||||
self.assertEqual(0, mocked_event.accept.call_count)
|
||||
|
||||
def primary_push_button_checked_key_press_event_test(self):
|
||||
"""
|
||||
Test the keyPressEvent when the primary push button is checked
|
||||
"""
|
||||
# GIVEN: The primary push button is checked
|
||||
with patch.object(self.form, 'keyReleaseEvent') as mocked_key_release_event, \
|
||||
patch.object(self.form.primary_push_button, 'isChecked') as mocked_is_checked:
|
||||
mocked_is_checked.return_value = True
|
||||
mocked_event = MagicMock()
|
||||
|
||||
# WHEN: The event is handled
|
||||
self.form.keyPressEvent(mocked_event)
|
||||
|
||||
# THEN: The key should be released
|
||||
mocked_key_release_event.assert_called_with(mocked_event)
|
||||
self.assertEqual(0, mocked_event.accept.call_count)
|
||||
|
||||
def alternate_push_button_checked_key_press_event_test(self):
|
||||
"""
|
||||
Test the keyPressEvent when the alternate push button is checked
|
||||
"""
|
||||
# GIVEN: The primary push button is checked
|
||||
with patch.object(self.form, 'keyReleaseEvent') as mocked_key_release_event, \
|
||||
patch.object(self.form.alternate_push_button, 'isChecked') as mocked_is_checked:
|
||||
mocked_is_checked.return_value = True
|
||||
mocked_event = MagicMock()
|
||||
|
||||
# WHEN: The event is handled
|
||||
self.form.keyPressEvent(mocked_event)
|
||||
|
||||
# THEN: The key should be released
|
||||
mocked_key_release_event.assert_called_with(mocked_event)
|
||||
self.assertEqual(0, mocked_event.accept.call_count)
|
||||
|
||||
def escape_key_press_event_test(self):
|
||||
"""
|
||||
Test the keyPressEvent when the escape key was pressed
|
||||
"""
|
||||
# GIVEN: A key event that is an escape
|
||||
mocked_event = MagicMock()
|
||||
mocked_event.key.return_value = QtCore.Qt.Key_Escape
|
||||
|
||||
# WHEN: The event is handled
|
||||
with patch.object(self.form, 'close') as mocked_close:
|
||||
self.form.keyPressEvent(mocked_event)
|
||||
|
||||
# THEN: The key should be released
|
||||
mocked_event.accept.assert_called_with()
|
||||
mocked_close.assert_called_with()
|
||||
|
||||
def on_default_radio_button_not_toggled_test(self):
|
||||
"""
|
||||
Test that the default radio button method exits early when the button is not toggled
|
||||
"""
|
||||
# GIVEN: A not-toggled custom radio button
|
||||
with patch.object(self.form, '_current_item_action') as mocked_current_item_action:
|
||||
|
||||
# WHEN: The clicked method is called
|
||||
self.form.on_default_radio_button_clicked(False)
|
||||
|
||||
# THEN: The method should exit early (i.e. the rest of the methods are not called)
|
||||
self.assertEqual(0, mocked_current_item_action.call_count)
|
||||
|
||||
def on_default_radio_button_clicked_no_action_test(self):
|
||||
"""
|
||||
Test that nothing happens when an action hasn't been selected and you click the default radio button
|
||||
"""
|
||||
# GIVEN: Some mocked out methods, a current action, and some shortcuts
|
||||
with patch.object(self.form, '_current_item_action') as mocked_current_item_action, \
|
||||
patch.object(self.form, '_action_shortcuts') as mocked_action_shortcuts:
|
||||
mocked_current_item_action.return_value = None
|
||||
|
||||
# WHEN: The default radio button is clicked
|
||||
self.form.on_default_radio_button_clicked(True)
|
||||
|
||||
# THEN: The method should exit early (i.e. the rest of the methods are not called)
|
||||
mocked_current_item_action.assert_called_with()
|
||||
self.assertEqual(0, mocked_action_shortcuts.call_count)
|
||||
|
||||
def on_default_radio_button_clicked_test(self):
|
||||
"""
|
||||
Test that the values are copied across correctly when the default radio button is selected
|
||||
"""
|
||||
# GIVEN: Some mocked out methods, a current action, and some shortcuts
|
||||
with patch.object(self.form, '_current_item_action') as mocked_current_item_action, \
|
||||
patch.object(self.form, '_action_shortcuts') as mocked_action_shortcuts, \
|
||||
patch.object(self.form, 'refresh_shortcut_list') as mocked_refresh_shortcut_list, \
|
||||
patch.object(self.form, 'get_shortcut_string') as mocked_get_shortcut_string, \
|
||||
patch.object(self.form.primary_push_button, 'setText') as mocked_set_text:
|
||||
mocked_action = MagicMock()
|
||||
mocked_action.default_shortcuts = [QtCore.Qt.Key_Escape]
|
||||
mocked_current_item_action.return_value = mocked_action
|
||||
mocked_action_shortcuts.return_value = [QtCore.Qt.Key_Escape]
|
||||
mocked_get_shortcut_string.return_value = 'Esc'
|
||||
|
||||
# WHEN: The default radio button is clicked
|
||||
self.form.on_default_radio_button_clicked(True)
|
||||
|
||||
# THEN: The shorcuts should be copied across
|
||||
mocked_current_item_action.assert_called_with()
|
||||
mocked_action_shortcuts.assert_called_with(mocked_action)
|
||||
mocked_refresh_shortcut_list.assert_called_with()
|
||||
mocked_set_text.assert_called_with('Esc')
|
||||
|
||||
def on_custom_radio_button_not_toggled_test(self):
|
||||
"""
|
||||
Test that the custom radio button method exits early when the button is not toggled
|
||||
"""
|
||||
# GIVEN: A not-toggled custom radio button
|
||||
with patch.object(self.form, '_current_item_action') as mocked_current_item_action:
|
||||
|
||||
# WHEN: The clicked method is called
|
||||
self.form.on_custom_radio_button_clicked(False)
|
||||
|
||||
# THEN: The method should exit early (i.e. the rest of the methods are not called)
|
||||
self.assertEqual(0, mocked_current_item_action.call_count)
|
||||
|
||||
def on_custom_radio_button_clicked_test(self):
|
||||
"""
|
||||
Test that the values are copied across correctly when the custom radio button is selected
|
||||
"""
|
||||
# GIVEN: Some mocked out methods, a current action, and some shortcuts
|
||||
with patch.object(self.form, '_current_item_action') as mocked_current_item_action, \
|
||||
patch.object(self.form, '_action_shortcuts') as mocked_action_shortcuts, \
|
||||
patch.object(self.form, 'refresh_shortcut_list') as mocked_refresh_shortcut_list, \
|
||||
patch.object(self.form, 'get_shortcut_string') as mocked_get_shortcut_string, \
|
||||
patch.object(self.form.primary_push_button, 'setText') as mocked_set_text:
|
||||
mocked_action = MagicMock()
|
||||
mocked_current_item_action.return_value = mocked_action
|
||||
mocked_action_shortcuts.return_value = [QtCore.Qt.Key_Escape]
|
||||
mocked_get_shortcut_string.return_value = 'Esc'
|
||||
|
||||
# WHEN: The custom radio button is clicked
|
||||
self.form.on_custom_radio_button_clicked(True)
|
||||
|
||||
# THEN: The shorcuts should be copied across
|
||||
mocked_current_item_action.assert_called_with()
|
||||
mocked_action_shortcuts.assert_called_with(mocked_action)
|
||||
mocked_refresh_shortcut_list.assert_called_with()
|
||||
mocked_set_text.assert_called_with('Esc')
|
||||
|
56
tests/resources/opensongsongs/Amazing Grace2
Normal file
56
tests/resources/opensongsongs/Amazing Grace2
Normal file
@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<song>
|
||||
<title>Amazing Grace (Demonstration)</title>
|
||||
<author>John Newton, Edwin Excell & John P. Rees</author>
|
||||
<copyright>Public Domain </copyright>
|
||||
<presentation>V1 V2 V3 V4 V5</presentation>
|
||||
<capo print="false"></capo>
|
||||
<tempo></tempo>
|
||||
<ccli>CC: 22025 number</ccli>
|
||||
<theme>God: Assurance/Grace/Salvation</theme>
|
||||
<alttheme>Worship: Praise</alttheme>
|
||||
<user1> </user1>
|
||||
<user2> </user2>
|
||||
<user3> </user3>
|
||||
<lyrics>[V]
|
||||
;Test the chords format
|
||||
;Chords beging with .
|
||||
;Verses begin with their verse number
|
||||
;Link words with _
|
||||
;Comments begin with ;
|
||||
. D D7 G D
|
||||
1A______ma________zing grace! How sweet the sound!
|
||||
2'Twas grace that taught my heart to fear,
|
||||
3The Lord has pro____mised good to me,
|
||||
4Thro' ma________ny dan____gers, toils and snares
|
||||
5When we've been there ten thou__sand years,
|
||||
|
||||
. Bm E A A7
|
||||
1That saved a wretch like me!
|
||||
2And grace my fears re___lieved.
|
||||
3His Word my hope se___cures.
|
||||
4I have al___rea____dy come.
|
||||
5Bright shi___ning as the sun,
|
||||
|
||||
. D D7 G D
|
||||
1I once was lost, but now am found;
|
||||
2How pre___cious did that grace ap____pear,
|
||||
3He will my shield and por___tion be
|
||||
4'Tis grace that brought me safe thus far,
|
||||
5We've no less days to sing God's praise,
|
||||
|
||||
. Bm A G D
|
||||
1Was blind, but now I see.
|
||||
2The hour I first be_lieved.
|
||||
3As long as life en_dures.
|
||||
4And grace will lead me home.
|
||||
5Than when we first be_gun.
|
||||
|
||||
</lyrics>
|
||||
<hymn_number>Demonstration Songs 0</hymn_number>
|
||||
<key></key>
|
||||
<aka></aka>
|
||||
<key_line></key_line>
|
||||
<time_sig></time_sig>
|
||||
<style index="default_style"></style>
|
||||
</song>
|
@ -1,14 +0,0 @@
|
||||
{
|
||||
"title": "Agnus Dei",
|
||||
"verse_order_list": ["v1", "v2"],
|
||||
"verses": [
|
||||
[
|
||||
"Alleluia Alleluluia \nfor the Lord almighty reigns \nAlleluia Alleluluia \nHoly holy are you Lord God Almighty \nWorthy is the lamb \nWorthy is the lamb \nHoly holy are you Lord God Almighty",
|
||||
"v1"
|
||||
],
|
||||
[
|
||||
"Worthy is the lamb \nWorthy is the lamb \nYou are holy holy \nAre you lamb \nWorthy is the lamb \nYou are holy holy \nYou are holy holy",
|
||||
"v2"
|
||||
]
|
||||
]
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<song xmlns="creativelifestyles/song">
|
||||
<attributes>
|
||||
<title>Agnus Dei</title>
|
||||
<author></author>
|
||||
<copyright></copyright>
|
||||
<ccli_number></ccli_number>
|
||||
<comments></comments>
|
||||
</attributes>
|
||||
<verses>
|
||||
<verse id="Verse 1">
|
||||
Alleluia Alleluluia
|
||||
for the Lord almighty reigns
|
||||
Alleluia Alleluluia
|
||||
Holy holy are you Lord God Almighty
|
||||
Worthy is the lamb
|
||||
Worthy is the lamb
|
||||
Holy holy are you Lord God Almighty
|
||||
|
||||
|
||||
</verse>
|
||||
<verse id="Verse 2">
|
||||
Worthy is the lamb
|
||||
Worthy is the lamb
|
||||
You are holy holy
|
||||
Are you lamb
|
||||
Worthy is the lamb
|
||||
You are holy holy
|
||||
You are holy holy
|
||||
|
||||
|
||||
</verse>
|
||||
</verses>
|
||||
</song>
|
@ -1,34 +0,0 @@
|
||||
{
|
||||
"title": "Vaste Grond",
|
||||
"verse_order_list": [],
|
||||
"verses": [
|
||||
[
|
||||
"God voor U is niets onmogelijk\nHoe ongelofelijk\nU heeft alles in de hand",
|
||||
"v1"
|
||||
],
|
||||
[
|
||||
"U bent God en trekt Uw eigen plan\nU bent voor niemand bang\nVoor niets en niemand bang",
|
||||
"v2"
|
||||
],
|
||||
[
|
||||
"U houd me vast en geeft me moed\nOm door te gaan als ik niet durf\nIk wil van U zijn",
|
||||
"v3"
|
||||
],
|
||||
[
|
||||
"U geeft me kracht, en bent de vaste grond\nwaarop ik stevig sta\nik wil van U zijn, voor altijd van U zijn\nO God.",
|
||||
"v4"
|
||||
],
|
||||
[
|
||||
"Grote God, U bent uitzonderlijk\nen ondoorgrondelijk\nU biedt Uw liefde aan",
|
||||
"v5"
|
||||
],
|
||||
[
|
||||
"Wie ben ik, dat U mij ziet staan\nen met mij om wilt gaan?\nIk kan U niet weerstaan",
|
||||
"v6"
|
||||
],
|
||||
[
|
||||
"Onweerstaanbaar,\nonweerstaanbare God",
|
||||
"v7"
|
||||
]
|
||||
]
|
||||
}
|
File diff suppressed because one or more lines are too long
@ -1,8 +0,0 @@
|
||||
[#PTFVersion: 2, #GLOBAL_RECT: rect(47,2,1026,770), #opacity: 100, #SHADOW_ON: 0, #SHADOW_COLOR: rgb( 0, 0, 0), #SHADOW_OPACITY: 100, #SHADOW_POSITION: "RB", #SHADOW_OFFSET: [0, 0], #FILE_TYPE: "Song", #title: "Abba Fader", #Author: "Okänd", #Copyright: "ccc", #CELL1: [#MARKER_NAME: "Abba Fader", #Hotkey: "1", #rtf: "{\rtf1\ansi\ansicpg1252\deff0\deflang1053{\fonttbl{\f0\froman\fprq2\fcharset0 Verdana;}{\f1\froman\fcharset0 Verdana;}}
|
||||
{\colortbl ;\red255\green255\blue0;\red224\green223\blue227;}
|
||||
\viewkind4\uc1\pard\cf1\b\f0\fs86 Abba Fader\par
|
||||
\par
|
||||
Vi \^e4r h\^e4r f\^f6r att prisa Dig\line Vi \^e4r h\^e4r med f\^f6rv\^e4ntan\line Vi \^e4r h\^e4r som ett enat folk\line Vi kommer fram till Dig\line Med v\^e5r lovs\^e5ng\line\fs59\line\fs86 Vi ropar Abba Fader\line Du som har all makt\line Vi ropar Abba Fader\line Till Dig st\^e5r allt v\^e5rt hopp\line Vi ropar Abba Fader\line V\^e5r fr\^e4lsare, befriare \^e4r Du\b0\line\pard\tx720\f1\par
|
||||
\cf2\par
|
||||
}
|
||||
", #Align: #Left]]
|
@ -1,13 +0,0 @@
|
||||
{
|
||||
"authors": [
|
||||
["Okänd"]
|
||||
],
|
||||
"title": "Abba Fader",
|
||||
"verse_order_list": [],
|
||||
"verses": [
|
||||
[
|
||||
"Abba Fader\n\nVi är här för att prisa Dig\nVi är här med förväntan\nVi är här som ett enat folk\nVi kommer fram till Dig\nMed vår lovsång\n\nVi ropar Abba Fader\nDu som har all makt\nVi ropar Abba Fader\nTill Dig står allt vårt hopp\nVi ropar Abba Fader\nVår frälsare, befriare är Du",
|
||||
"v1"
|
||||
]
|
||||
]
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
"SongID","SongNr","Title","Author","Copyright","FirstLine","PriKey","AltKey","Tempo","Focus","Theme","Scripture","Active","Songbook","TimeSig","Introduced","LastUsed","TimesUsed","CCLINr","User1","User2","User3","User4","User5","Roadmap","Overmap","FileLink1","FileLink2","Updated","Lyrics","Info","Lyrics2","Background"
|
||||
"000013ab-0000-0000-0000-000000000000","0","Lift Up Your Heads"," Bryan Mierau","Public Domain","Lift up your heads and the doors","Em","NULL","NULL","NULL","NULL","NULL","1","1","NULL","NULL","NULL","0","NULL","NULL","NULL","NULL","NULL","NULL","NULL","NULL","NULL","NULL","2004-04-07 06:36:18.952",".Em D C D
|
||||
Lift up your heads and the doors of your heart
|
||||
. Am B7 Em
|
||||
And the King of glory will come in
|
||||
(Repeat)
|
||||
|
||||
.G Am D
|
||||
Who is this King of Glory?
|
||||
. B7 Em
|
||||
The Lord strong and mighty!
|
||||
.G Am D
|
||||
Who is this King of Glory?
|
||||
. B7
|
||||
The Lord, mighty in battle!
|
||||
|
||||
.G Am D
|
||||
Who is this King of Glory?
|
||||
.B7 Em
|
||||
Jesus our Messiah!
|
||||
.G Am D
|
||||
Who is this King of Glory?
|
||||
.B7 Em
|
||||
Jesus, Lord of Lords!
|
||||
|
||||
","NULL","Lift up your heads and the doors of your heart
|
||||
And the King of glory will come in
|
||||
(Repeat)
|
||||
|
||||
Who is this King of Glory?
|
||||
The Lord strong and mighty!
|
||||
Who is this King of Glory?
|
||||
The Lord, mighty in battle!
|
||||
|
||||
Who is this King of Glory?
|
||||
Jesus our Messiah!
|
||||
Who is this King of Glory?
|
||||
Jesus, Lord of Lords!
|
||||
|
||||
","NULL"
|
|
@ -1,13 +0,0 @@
|
||||
{
|
||||
"authors": [
|
||||
"Bryan Mierau"
|
||||
],
|
||||
"title": "Lift Up Your Heads",
|
||||
"verse_order_list": [],
|
||||
"verses": [
|
||||
[
|
||||
"Lift up your heads and the doors of your heart\nAnd the King of glory will come in\n(Repeat)\n\nWho is this King of Glory?\nThe Lord strong and mighty!\nWho is this King of Glory?\nThe Lord, mighty in battle!\n\nWho is this King of Glory?\nJesus our Messiah!\nWho is this King of Glory?\nJesus, Lord of Lords!\n",
|
||||
"v1"
|
||||
]
|
||||
]
|
||||
}
|
@ -29,7 +29,7 @@ from subprocess import Popen, PIPE
|
||||
|
||||
TAGS1 = {'1.9.0', '1.9.1', '1.9.2', '1.9.3', '1.9.4', '1.9.5', '1.9.6', '1.9.7', '1.9.8', '1.9.9', '1.9.10',
|
||||
'1.9.11', '1.9.12', '2.0', '2.1.0', '2.1.1', '2.1.2', '2.1.3', '2.1.4', '2.1.5', '2.1.6', '2.2',
|
||||
'2.3.1', '2.3.2'}
|
||||
'2.3.1', '2.3.2', '2.3.3', '2.4'}
|
||||
|
||||
|
||||
class TestBzrTags(TestCase):
|
||||
|
Loading…
Reference in New Issue
Block a user