openlp/openlp/core/ui/slidecontroller.py

1589 lines
74 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
2012-12-10 18:04:58 +00:00
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
2017-12-29 09:15:48 +00:00
# Copyright (c) 2008-2018 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
2013-02-01 21:34:23 +00:00
"""
2013-02-07 08:42:17 +00:00
The :mod:`slidecontroller` module contains the most important part of OpenLP - the slide controller
2013-02-01 21:34:23 +00:00
"""
import copy
2016-03-31 16:34:22 +00:00
import os
from collections import deque
from threading import Lock
2009-05-02 11:16:08 +00:00
2015-11-07 00:49:40 +00:00
from PyQt5 import QtCore, QtGui, QtWidgets
2017-10-07 07:05:07 +00:00
from openlp.core.common import SlideLimits
2016-03-31 16:34:22 +00:00
from openlp.core.common.actions import ActionList, CategoryOrder
2018-04-10 19:26:56 +00:00
from openlp.core.common.i18n import UiStrings, translate
2017-10-23 22:09:57 +00:00
from openlp.core.common.mixins import LogMixin, RegistryProperties
from openlp.core.common.registry import Registry, RegistryBase
2017-10-07 07:05:07 +00:00
from openlp.core.common.settings import Settings
2017-10-10 07:08:44 +00:00
from openlp.core.display.screens import ScreenList
from openlp.core.lib import ImageSource, ServiceItemAction, build_icon
from openlp.core.lib.htmlbuilder import build_html
from openlp.core.lib.serviceitem import ServiceItem, ItemCapabilities
2013-01-23 21:05:25 +00:00
from openlp.core.lib.ui import create_action
2016-03-31 16:34:22 +00:00
from openlp.core.ui import HideMode, MainDisplay, Display, DisplayControllerType
2018-04-10 21:10:09 +00:00
from openlp.core.ui.icons import UiIcons
2017-10-23 22:09:57 +00:00
from openlp.core.widgets.toolbar import OpenLPToolbar
from openlp.core.widgets.views import ListPreviewWidget
2016-04-22 18:32:45 +00:00
2013-04-05 13:41:42 +00:00
# Threshold which has to be trespassed to toggle.
2013-12-15 16:50:09 +00:00
HIDE_MENU_THRESHOLD = 27
2013-08-31 18:17:38 +00:00
AUDIO_TIME_LABEL_STYLESHEET = 'background-color: palette(background); ' \
'border-top-color: palette(shadow); ' \
'border-left-color: palette(shadow); ' \
'border-bottom-color: palette(light); ' \
'border-right-color: palette(light); ' \
'border-radius: 3px; border-style: inset; ' \
'border-width: 1; font-family: monospace; margin: 2px;'
2013-12-15 16:50:09 +00:00
NARROW_MENU = [
'hide_menu'
]
LOOP_LIST = [
'play_slides_menu',
'loop_separator',
'delay_spin_box'
]
AUDIO_LIST = [
'audioPauseItem',
'audio_time_label'
]
WIDE_MENU = [
'blank_screen_button',
'theme_screen_button',
'desktop_screen_button'
]
2014-01-04 11:50:27 +00:00
NON_TEXT_MENU = [
'blank_screen_button',
'desktop_screen_button'
]
2015-11-07 00:49:40 +00:00
class DisplayController(QtWidgets.QWidget):
2011-07-20 20:47:29 +00:00
"""
2012-09-14 16:35:07 +00:00
Controller is a general display controller widget.
2011-07-20 20:47:29 +00:00
"""
2017-10-23 22:09:57 +00:00
def __init__(self, *args, **kwargs):
2011-07-20 20:47:29 +00:00
"""
Set up the general Controller.
"""
2017-10-23 22:09:57 +00:00
super().__init__(*args, **kwargs)
2013-12-19 20:17:06 +00:00
self.is_live = False
2011-07-20 20:47:29 +00:00
self.display = None
2016-02-16 21:14:38 +00:00
self.controller_type = None
Registry().set_flag('has doubleclick added item to service', True)
Registry().set_flag('replace service manager item', False)
2011-03-24 19:12:27 +00:00
def send_to_plugins(self, *args):
2011-07-20 20:47:29 +00:00
"""
2014-01-11 17:52:01 +00:00
This is the generic function to send signal for control widgets, created from within other plugins
2011-07-20 20:47:29 +00:00
This function is needed to catch the current controller
:param args: Arguments to send to the plugins
2011-07-20 20:47:29 +00:00
"""
2012-12-10 18:04:58 +00:00
sender = self.sender().objectName() if self.sender().objectName() else self.sender().text()
2011-07-20 20:47:29 +00:00
controller = self
2016-05-20 16:22:06 +00:00
Registry().execute('{text}'.format(text=sender), [controller, args])
2011-03-24 19:12:27 +00:00
2015-11-07 00:49:40 +00:00
class InfoLabel(QtWidgets.QLabel):
"""
InfoLabel is a subclassed QLabel. Created to provide the ablilty to add a ellipsis if the text is cut off. Original
source: https://stackoverflow.com/questions/11446478/pyside-pyqt-truncate-text-in-qlabel-based-on-minimumsize
"""
def paintEvent(self, event):
"""
Reimplemented to allow the drawing of elided text if the text is longer than the width of the label
"""
painter = QtGui.QPainter(self)
metrics = QtGui.QFontMetrics(self.font())
elided = metrics.elidedText(self.text(), QtCore.Qt.ElideRight, self.width())
# If the text is elided align it left to stop it jittering as the label is resized
if elided == self.text():
alignment = QtCore.Qt.AlignCenter
else:
alignment = QtCore.Qt.AlignLeft
painter.drawText(self.rect(), alignment, elided)
def setText(self, text):
"""
Reimplemented to set the tool tip text.
"""
self.setToolTip(text)
super().setText(text)
2014-12-30 11:23:01 +00:00
2017-10-23 22:09:57 +00:00
class SlideController(DisplayController, LogMixin, RegistryProperties):
"""
2009-09-21 17:56:36 +00:00
SlideController is the slide controller widget. This widget is what the
user uses to control the displaying of verses/slides/etc on the screen.
"""
2017-10-23 22:09:57 +00:00
def __init__(self, *args, **kwargs):
"""
Set up the Slide Controller.
"""
2017-10-23 22:09:57 +00:00
super().__init__(*args, **kwargs)
2013-12-19 20:17:06 +00:00
def post_set_up(self):
"""
Call by bootstrap functions
"""
self.initialise()
self.screen_size_changed()
def initialise(self):
2014-01-04 11:50:27 +00:00
"""
Initialise the UI elements of the controller
"""
self.screens = ScreenList()
2012-02-04 17:11:35 +00:00
try:
2013-08-31 18:17:38 +00:00
self.ratio = self.screens.current['size'].width() / self.screens.current['size'].height()
2012-02-04 17:11:35 +00:00
except ZeroDivisionError:
self.ratio = 1
self.process_queue_lock = Lock()
self.slide_selected_lock = Lock()
self.timer_id = 0
self.song_edit = False
2013-03-01 17:29:09 +00:00
self.selected_row = 0
self.service_item = None
self.slide_limits = None
2013-02-07 11:33:47 +00:00
self.update_slide_limits()
2015-11-07 00:49:40 +00:00
self.panel = QtWidgets.QWidget(self.main_window.control_splitter)
2013-12-15 16:50:09 +00:00
self.slide_list = {}
2013-06-03 18:03:45 +00:00
self.slide_count = 0
self.slide_image = None
2014-12-06 20:40:40 +00:00
self.controller_width = -1
# Layout for holding panel
2015-11-07 00:49:40 +00:00
self.panel_layout = QtWidgets.QVBoxLayout(self.panel)
self.panel_layout.setSpacing(0)
2015-11-07 00:49:40 +00:00
self.panel_layout.setContentsMargins(0, 0, 0, 0)
# Type label at the top of the slide controller
2015-11-07 00:49:40 +00:00
self.type_label = QtWidgets.QLabel(self.panel)
2013-08-31 18:17:38 +00:00
self.type_label.setStyleSheet('font-weight: bold; font-size: 12pt;')
self.type_label.setAlignment(QtCore.Qt.AlignCenter)
if self.is_live:
self.type_label.setText(UiStrings().Live)
else:
self.type_label.setText(UiStrings().Preview)
self.panel_layout.addWidget(self.type_label)
# Info label for the title of the current item, at the top of the slide controller
self.info_label = InfoLabel(self.panel)
2015-11-07 00:49:40 +00:00
self.info_label.setSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Preferred)
self.panel_layout.addWidget(self.info_label)
# Splitter
2015-11-07 00:49:40 +00:00
self.splitter = QtWidgets.QSplitter(self.panel)
2011-01-21 20:56:09 +00:00
self.splitter.setOrientation(QtCore.Qt.Vertical)
self.panel_layout.addWidget(self.splitter)
# Actual controller section
2015-11-07 00:49:40 +00:00
self.controller = QtWidgets.QWidget(self.splitter)
2011-01-21 20:53:08 +00:00
self.controller.setGeometry(QtCore.QRect(0, 0, 100, 536))
2015-11-07 00:49:40 +00:00
self.controller.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred,
QtWidgets.QSizePolicy.Maximum))
self.controller_layout = QtWidgets.QVBoxLayout(self.controller)
self.controller_layout.setSpacing(0)
2015-11-07 00:49:40 +00:00
self.controller_layout.setContentsMargins(0, 0, 0, 0)
# Controller list view
self.preview_widget = ListPreviewWidget(self, self.ratio)
self.controller_layout.addWidget(self.preview_widget)
# Build the full toolbar
2011-01-21 20:56:09 +00:00
self.toolbar = OpenLPToolbar(self)
2015-11-07 00:49:40 +00:00
size_toolbar_policy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
size_toolbar_policy.setHorizontalStretch(0)
size_toolbar_policy.setVerticalStretch(0)
size_toolbar_policy.setHeightForWidth(self.toolbar.sizePolicy().hasHeightForWidth())
self.toolbar.setSizePolicy(size_toolbar_policy)
2013-08-31 18:17:38 +00:00
self.previous_item = create_action(self, 'previousItem_' + self.type_prefix,
2014-03-20 19:10:31 +00:00
text=translate('OpenLP.SlideController', 'Previous Slide'),
2018-04-10 21:10:09 +00:00
icon=UiIcons().arrow_left,
2013-12-15 16:50:09 +00:00
tooltip=translate('OpenLP.SlideController', 'Move to previous.'),
can_shortcuts=True, context=QtCore.Qt.WidgetWithChildrenShortcut,
category=self.category, triggers=self.on_slide_selected_previous)
self.toolbar.addAction(self.previous_item)
2013-12-15 16:50:09 +00:00
self.next_item = create_action(self, 'nextItem_' + self.type_prefix,
2014-03-20 19:10:31 +00:00
text=translate('OpenLP.SlideController', 'Next Slide'),
2018-04-10 21:10:09 +00:00
icon=UiIcons().arrow_right,
2013-12-15 16:50:09 +00:00
tooltip=translate('OpenLP.SlideController', 'Move to next.'),
can_shortcuts=True, context=QtCore.Qt.WidgetWithChildrenShortcut,
category=self.category, triggers=self.on_slide_selected_next_action)
self.toolbar.addAction(self.next_item)
self.toolbar.addSeparator()
self.controller_type = DisplayControllerType.Preview
if self.is_live:
self.controller_type = DisplayControllerType.Live
# Hide Menu
2015-11-07 00:49:40 +00:00
self.hide_menu = QtWidgets.QToolButton(self.toolbar)
2013-08-31 18:17:38 +00:00
self.hide_menu.setObjectName('hide_menu')
self.hide_menu.setText(translate('OpenLP.SlideController', 'Hide'))
2015-11-07 00:49:40 +00:00
self.hide_menu.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup)
self.hide_menu.setMenu(QtWidgets.QMenu(translate('OpenLP.SlideController', 'Hide'), self.toolbar))
2013-03-07 06:48:09 +00:00
self.toolbar.add_toolbar_widget(self.hide_menu)
2018-04-07 20:31:54 +00:00
self.toolbar.add_toolbar_action('goPreview', icon=UiIcons().live,
2017-11-09 19:42:30 +00:00
tooltip=translate('OpenLP.SlideController', 'Move to preview.'),
triggers=self.on_go_preview)
# The order of the blank to modes in Shortcuts list comes from here.
self.desktop_screen_enable = create_action(self, 'desktopScreenEnable',
text=translate('OpenLP.SlideController', 'Show Desktop'),
2018-04-10 21:10:09 +00:00
icon=UiIcons().desktop, can_shortcuts=True,
context=QtCore.Qt.WidgetWithChildrenShortcut,
category=self.category, triggers=self.on_hide_display_enable)
2013-08-31 18:17:38 +00:00
self.desktop_screen = create_action(self, 'desktopScreen',
text=translate('OpenLP.SlideController', 'Toggle Desktop'),
2018-04-10 21:10:09 +00:00
icon=UiIcons().desktop,
2013-12-15 16:50:09 +00:00
checked=False, can_shortcuts=True, category=self.category,
triggers=self.on_hide_display)
self.theme_screen = create_action(self, 'themeScreen',
text=translate('OpenLP.SlideController', 'Toggle Blank to Theme'),
2018-06-02 06:37:31 +00:00
icon=UiIcons().blank_theme,
checked=False, can_shortcuts=True, category=self.category,
triggers=self.on_theme_display)
self.blank_screen = create_action(self, 'blankScreen',
text=translate('OpenLP.SlideController', 'Toggle Blank Screen'),
2018-04-10 21:10:09 +00:00
icon=UiIcons().blank,
checked=False, can_shortcuts=True, category=self.category,
triggers=self.on_blank_display)
self.hide_menu.setDefaultAction(self.blank_screen)
self.hide_menu.menu().addAction(self.blank_screen)
self.hide_menu.menu().addAction(self.theme_screen)
self.hide_menu.menu().addAction(self.desktop_screen)
2016-09-02 15:41:16 +00:00
self.hide_menu.menu().addAction(self.desktop_screen_enable)
2013-01-15 23:59:04 +00:00
# Wide menu of display control buttons.
2015-11-07 00:49:40 +00:00
self.blank_screen_button = QtWidgets.QToolButton(self.toolbar)
2013-08-31 18:17:38 +00:00
self.blank_screen_button.setObjectName('blank_screen_button')
2013-03-07 06:48:09 +00:00
self.toolbar.add_toolbar_widget(self.blank_screen_button)
2013-03-01 17:29:09 +00:00
self.blank_screen_button.setDefaultAction(self.blank_screen)
2015-11-07 00:49:40 +00:00
self.theme_screen_button = QtWidgets.QToolButton(self.toolbar)
2013-08-31 18:17:38 +00:00
self.theme_screen_button.setObjectName('theme_screen_button')
2013-03-07 06:48:09 +00:00
self.toolbar.add_toolbar_widget(self.theme_screen_button)
2013-03-01 17:29:09 +00:00
self.theme_screen_button.setDefaultAction(self.theme_screen)
2015-11-07 00:49:40 +00:00
self.desktop_screen_button = QtWidgets.QToolButton(self.toolbar)
2013-08-31 18:17:38 +00:00
self.desktop_screen_button.setObjectName('desktop_screen_button')
2013-03-07 06:48:09 +00:00
self.toolbar.add_toolbar_widget(self.desktop_screen_button)
2013-03-01 17:29:09 +00:00
self.desktop_screen_button.setDefaultAction(self.desktop_screen)
2013-08-31 18:17:38 +00:00
self.toolbar.add_toolbar_action('loop_separator', separator=True)
# Play Slides Menu
2015-11-07 00:49:40 +00:00
self.play_slides_menu = QtWidgets.QToolButton(self.toolbar)
2013-08-31 18:17:38 +00:00
self.play_slides_menu.setObjectName('play_slides_menu')
self.play_slides_menu.setText(translate('OpenLP.SlideController', 'Play Slides'))
2015-11-07 00:49:40 +00:00
self.play_slides_menu.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup)
self.play_slides_menu.setMenu(QtWidgets.QMenu(translate('OpenLP.SlideController', 'Play Slides'),
self.toolbar))
2013-03-07 06:48:09 +00:00
self.toolbar.add_toolbar_widget(self.play_slides_menu)
2013-08-31 18:17:38 +00:00
self.play_slides_loop = create_action(self, 'playSlidesLoop', text=UiStrings().PlaySlidesInLoop,
2018-04-10 21:10:09 +00:00
icon=UiIcons().clock, checked=False, can_shortcuts=True,
2013-12-15 16:50:09 +00:00
category=self.category, triggers=self.on_play_slides_loop)
2013-08-31 18:17:38 +00:00
self.play_slides_once = create_action(self, 'playSlidesOnce', text=UiStrings().PlaySlidesToEnd,
2018-04-10 21:10:09 +00:00
icon=UiIcons().clock, checked=False, can_shortcuts=True,
2013-12-15 16:50:09 +00:00
category=self.category, triggers=self.on_play_slides_once)
2013-08-31 18:17:38 +00:00
if Settings().value(self.main_window.advanced_settings_section + '/slide limits') == SlideLimits.Wrap:
self.play_slides_menu.setDefaultAction(self.play_slides_loop)
else:
self.play_slides_menu.setDefaultAction(self.play_slides_once)
self.play_slides_menu.menu().addAction(self.play_slides_loop)
self.play_slides_menu.menu().addAction(self.play_slides_once)
# Loop Delay Spinbox
2015-11-07 00:49:40 +00:00
self.delay_spin_box = QtWidgets.QSpinBox()
2013-08-31 18:17:38 +00:00
self.delay_spin_box.setObjectName('delay_spin_box')
2013-03-01 17:29:09 +00:00
self.delay_spin_box.setRange(1, 180)
2016-05-10 11:06:00 +00:00
self.delay_spin_box.setSuffix(' {unit}'.format(unit=UiStrings().Seconds))
2013-03-01 17:29:09 +00:00
self.delay_spin_box.setToolTip(translate('OpenLP.SlideController', 'Delay between slides in seconds.'))
self.receive_spin_delay()
2013-03-07 06:48:09 +00:00
self.toolbar.add_toolbar_widget(self.delay_spin_box)
2010-12-27 10:14:46 +00:00
else:
2018-04-07 20:31:54 +00:00
self.toolbar.add_toolbar_action('goLive', icon=UiIcons().live,
2014-03-20 19:10:31 +00:00
tooltip=translate('OpenLP.SlideController', 'Move to live.'),
2013-12-15 16:50:09 +00:00
triggers=self.on_go_live)
2018-04-07 20:31:54 +00:00
self.toolbar.add_toolbar_action('addToService', icon=UiIcons().add,
2014-03-20 19:10:31 +00:00
tooltip=translate('OpenLP.SlideController', 'Add to Service.'),
2013-12-15 16:50:09 +00:00
triggers=self.on_preview_add_to_service)
self.toolbar.addSeparator()
2018-04-07 20:31:54 +00:00
self.toolbar.add_toolbar_action('editSong', icon=UiIcons().edit,
2014-03-20 19:10:31 +00:00
tooltip=translate('OpenLP.SlideController',
'Edit and reload song preview.'),
triggers=self.on_edit_song)
2018-04-07 20:31:54 +00:00
self.toolbar.add_toolbar_action('clear', icon=UiIcons().delete,
2018-02-23 08:56:01 +00:00
tooltip=translate('OpenLP.SlideController',
'Clear'),
triggers=self.on_clear)
self.controller_layout.addWidget(self.toolbar)
2011-07-10 21:43:07 +00:00
# Build the Media Toolbar
self.media_controller.register_controller(self)
if self.is_live:
2010-12-27 10:14:46 +00:00
# Build the Song Toolbar
2015-11-07 00:49:40 +00:00
self.song_menu = QtWidgets.QToolButton(self.toolbar)
2013-08-31 18:17:38 +00:00
self.song_menu.setObjectName('song_menu')
self.song_menu.setText(translate('OpenLP.SlideController', 'Go To'))
2015-11-07 00:49:40 +00:00
self.song_menu.setPopupMode(QtWidgets.QToolButton.InstantPopup)
self.song_menu.setMenu(QtWidgets.QMenu(translate('OpenLP.SlideController', 'Go To'), self.toolbar))
2013-03-07 06:48:09 +00:00
self.toolbar.add_toolbar_widget(self.song_menu)
2011-08-30 21:20:32 +00:00
# Stuff for items with background audio.
2013-03-07 06:48:09 +00:00
# FIXME: object name should be changed. But this requires that we migrate the shortcut.
2014-01-11 17:52:01 +00:00
self.audio_pause_item = self.toolbar.add_toolbar_action(
'audioPauseItem',
2018-04-10 21:10:09 +00:00
icon=UiIcons().pause, text=translate('OpenLP.SlideController', 'Pause Audio'),
tooltip=translate('OpenLP.SlideController', 'Pause audio.'),
2012-12-10 18:04:58 +00:00
checked=False, visible=False, category=self.category, context=QtCore.Qt.WindowShortcut,
2013-06-16 07:54:16 +00:00
can_shortcuts=True, triggers=self.set_audio_pause_clicked)
2015-11-07 00:49:40 +00:00
self.audio_menu = QtWidgets.QMenu(translate('OpenLP.SlideController', 'Background Audio'), self.toolbar)
2013-03-07 06:48:09 +00:00
self.audio_pause_item.setMenu(self.audio_menu)
2013-03-01 17:29:09 +00:00
self.audio_pause_item.setParent(self.toolbar)
2015-11-07 00:49:40 +00:00
self.toolbar.widgetForAction(self.audio_pause_item).setPopupMode(QtWidgets.QToolButton.MenuButtonPopup)
2013-12-15 16:50:09 +00:00
self.next_track_item = create_action(self, 'nextTrackItem', text=UiStrings().NextTrack,
2018-04-10 21:10:09 +00:00
icon=UiIcons().arrow_right,
2013-12-15 16:50:09 +00:00
tooltip=translate('OpenLP.SlideController',
'Go to next audio track.'),
category=self.category,
can_shortcuts=True,
triggers=self.on_next_track_clicked)
self.audio_menu.addAction(self.next_track_item)
self.track_menu = self.audio_menu.addMenu(translate('OpenLP.SlideController', 'Tracks'))
2015-11-07 00:49:40 +00:00
self.audio_time_label = QtWidgets.QLabel(' 00:00 ', self.toolbar)
2013-03-01 17:29:09 +00:00
self.audio_time_label.setAlignment(QtCore.Qt.AlignCenter | QtCore.Qt.AlignHCenter)
2013-03-01 17:36:43 +00:00
self.audio_time_label.setStyleSheet(AUDIO_TIME_LABEL_STYLESHEET)
2013-08-31 18:17:38 +00:00
self.audio_time_label.setObjectName('audio_time_label')
2013-03-07 06:48:09 +00:00
self.toolbar.add_toolbar_widget(self.audio_time_label)
2013-12-15 16:50:09 +00:00
self.toolbar.set_widget_visible(AUDIO_LIST, False)
2018-02-23 09:17:21 +00:00
self.toolbar.set_widget_visible('song_menu', False)
# Screen preview area
2015-11-07 00:49:40 +00:00
self.preview_frame = QtWidgets.QFrame(self.splitter)
self.preview_frame.setGeometry(QtCore.QRect(0, 0, 300, 300 * self.ratio))
self.preview_frame.setMinimumHeight(100)
2015-11-07 00:49:40 +00:00
self.preview_frame.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Ignored,
QtWidgets.QSizePolicy.Ignored,
QtWidgets.QSizePolicy.Label))
self.preview_frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
self.preview_frame.setFrameShadow(QtWidgets.QFrame.Sunken)
2013-08-31 18:17:38 +00:00
self.preview_frame.setObjectName('preview_frame')
2015-11-07 00:49:40 +00:00
self.grid = QtWidgets.QGridLayout(self.preview_frame)
self.grid.setContentsMargins(8, 8, 8, 8)
2013-08-31 18:17:38 +00:00
self.grid.setObjectName('grid')
2015-11-07 00:49:40 +00:00
self.slide_layout = QtWidgets.QVBoxLayout()
self.slide_layout.setSpacing(0)
2015-11-07 00:49:40 +00:00
self.slide_layout.setContentsMargins(0, 0, 0, 0)
2013-08-31 18:17:38 +00:00
self.slide_layout.setObjectName('SlideLayout')
2014-01-11 17:52:01 +00:00
self.preview_display = Display(self)
self.slide_layout.insertWidget(0, self.preview_display)
self.preview_display.hide()
# Actual preview screen
2015-11-07 00:49:40 +00:00
self.slide_preview = QtWidgets.QLabel(self)
size_policy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
2013-06-03 18:47:25 +00:00
size_policy.setHorizontalStretch(0)
size_policy.setVerticalStretch(0)
size_policy.setHeightForWidth(self.slide_preview.sizePolicy().hasHeightForWidth())
self.slide_preview.setSizePolicy(size_policy)
2015-11-07 00:49:40 +00:00
self.slide_preview.setFrameShape(QtWidgets.QFrame.Box)
self.slide_preview.setFrameShadow(QtWidgets.QFrame.Plain)
2013-06-03 18:47:25 +00:00
self.slide_preview.setLineWidth(1)
self.slide_preview.setScaledContents(True)
2013-08-31 18:17:38 +00:00
self.slide_preview.setObjectName('slide_preview')
2013-06-03 18:47:25 +00:00
self.slide_layout.insertWidget(0, self.slide_preview)
self.grid.addLayout(self.slide_layout, 0, 0, 1, 1)
if self.is_live:
2013-08-31 18:17:38 +00:00
self.current_shortcut = ''
2013-12-15 16:50:09 +00:00
self.shortcut_timer = QtCore.QTimer()
self.shortcut_timer.setObjectName('shortcut_timer')
self.shortcut_timer.setSingleShot(True)
shortcuts = [
2013-08-31 18:17:38 +00:00
{'key': 'V', 'configurable': True, 'text': translate('OpenLP.SlideController', 'Go to "Verse"')},
{'key': 'C', 'configurable': True, 'text': translate('OpenLP.SlideController', 'Go to "Chorus"')},
{'key': 'B', 'configurable': True, 'text': translate('OpenLP.SlideController', 'Go to "Bridge"')},
{'key': 'P', 'configurable': True,
'text': translate('OpenLP.SlideController', 'Go to "Pre-Chorus"')},
{'key': 'I', 'configurable': True, 'text': translate('OpenLP.SlideController', 'Go to "Intro"')},
{'key': 'E', 'configurable': True, 'text': translate('OpenLP.SlideController', 'Go to "Ending"')},
{'key': 'O', 'configurable': True, 'text': translate('OpenLP.SlideController', 'Go to "Other"')}
]
2013-08-31 18:17:38 +00:00
shortcuts.extend([{'key': str(number)} for number in range(10)])
2016-05-20 16:22:06 +00:00
self.controller.addActions([create_action(self, 'shortcutAction_{key}'.format(key=s['key']),
2013-12-15 16:50:09 +00:00
text=s.get('text'),
can_shortcuts=True,
context=QtCore.Qt.WidgetWithChildrenShortcut,
category=self.category if s.get('configurable') else None,
triggers=self._slide_shortcut_activated) for s in shortcuts])
self.shortcut_timer.timeout.connect(self._slide_shortcut_activated)
# Signals
self.preview_widget.clicked.connect(self.on_slide_selected)
self.preview_widget.verticalHeader().sectionClicked.connect(self.on_slide_selected)
if self.is_live:
2013-03-06 21:53:29 +00:00
# Need to use event as called across threads and UI is updated
2015-11-07 00:49:40 +00:00
self.slidecontroller_toggle_display.connect(self.toggle_display)
2013-08-31 18:17:38 +00:00
Registry().register_function('slidecontroller_live_spin_delay', self.receive_spin_delay)
2013-12-15 16:50:09 +00:00
self.toolbar.set_widget_visible(LOOP_LIST, False)
self.toolbar.set_widget_visible(WIDE_MENU, False)
self.set_live_hot_keys(self)
2013-06-16 07:54:16 +00:00
self.__add_actions_to_widget(self.controller)
else:
self.preview_widget.doubleClicked.connect(self.on_preview_double_click)
2018-02-23 09:17:21 +00:00
self.toolbar.set_widget_visible('editSong', False)
self.toolbar.set_widget_visible('clear', False)
2013-12-15 16:50:09 +00:00
self.controller.addActions([self.next_item, self.previous_item])
2016-05-20 16:22:06 +00:00
Registry().register_function('slidecontroller_{text}_stop_loop'.format(text=self.type_prefix),
self.on_stop_loop)
Registry().register_function('slidecontroller_{text}_change'.format(text=self.type_prefix),
self.on_slide_change)
Registry().register_function('slidecontroller_{text}_blank'.format(text=self.type_prefix),
self.on_slide_blank)
Registry().register_function('slidecontroller_{text}_unblank'.format(text=self.type_prefix),
self.on_slide_unblank)
2013-08-31 18:17:38 +00:00
Registry().register_function('slidecontroller_update_slide_limits', self.update_slide_limits)
2016-05-20 16:22:06 +00:00
getattr(self, 'slidecontroller_{text}_set'.format(text=self.type_prefix)).connect(self.on_slide_selected_index)
getattr(self, 'slidecontroller_{text}_next'.format(text=self.type_prefix)).connect(self.on_slide_selected_next)
# NOTE: {t} used to keep line length < maxline
getattr(self,
'slidecontroller_{t}_previous'.format(t=self.type_prefix)).connect(self.on_slide_selected_previous)
2016-08-08 21:01:09 +00:00
if self.is_live:
getattr(self, 'mediacontroller_live_play').connect(self.media_controller.on_media_play)
getattr(self, 'mediacontroller_live_pause').connect(self.media_controller.on_media_pause)
getattr(self, 'mediacontroller_live_stop').connect(self.media_controller.on_media_stop)
2009-11-08 17:02:46 +00:00
2013-06-16 07:54:16 +00:00
def _slide_shortcut_activated(self):
"""
2013-12-15 16:50:09 +00:00
Called, when a shortcut has been activated to jump to a chorus, verse, etc.
2011-10-31 08:53:18 +00:00
2015-05-31 06:40:37 +00:00
**Note**: This implementation is based on shortcuts. But it rather works like "key sequences". You have to
2013-12-15 16:50:09 +00:00
press one key after the other and **not** at the same time.
2014-03-20 19:10:31 +00:00
For example to jump to "V3" you have to press "V" and afterwards but within a time frame of 350ms
2013-12-15 16:50:09 +00:00
you have to press "3".
"""
2011-10-31 08:44:38 +00:00
try:
2011-10-31 08:53:18 +00:00
from openlp.plugins.songs.lib import VerseType
is_songs_plugin_available = True
2011-10-31 08:44:38 +00:00
except ImportError:
class VerseType(object):
"""
This empty class is mostly just to satisfy Python, PEP8 and PyCharm
"""
pass
is_songs_plugin_available = False
2012-05-17 18:57:01 +00:00
sender_name = self.sender().objectName()
2013-08-31 18:17:38 +00:00
verse_type = sender_name[15:] if sender_name[:15] == 'shortcutAction_' else ''
if is_songs_plugin_available:
2013-08-31 18:17:38 +00:00
if verse_type == 'V':
2013-02-24 18:13:50 +00:00
self.current_shortcut = VerseType.translated_tags[VerseType.Verse]
2013-08-31 18:17:38 +00:00
elif verse_type == 'C':
2013-02-24 18:13:50 +00:00
self.current_shortcut = VerseType.translated_tags[VerseType.Chorus]
2013-08-31 18:17:38 +00:00
elif verse_type == 'B':
2013-02-24 18:13:50 +00:00
self.current_shortcut = VerseType.translated_tags[VerseType.Bridge]
2013-08-31 18:17:38 +00:00
elif verse_type == 'P':
2013-02-24 18:13:50 +00:00
self.current_shortcut = VerseType.translated_tags[VerseType.PreChorus]
2013-08-31 18:17:38 +00:00
elif verse_type == 'I':
2013-02-24 18:13:50 +00:00
self.current_shortcut = VerseType.translated_tags[VerseType.Intro]
2013-08-31 18:17:38 +00:00
elif verse_type == 'E':
2013-02-24 18:13:50 +00:00
self.current_shortcut = VerseType.translated_tags[VerseType.Ending]
2013-08-31 18:17:38 +00:00
elif verse_type == 'O':
2013-02-24 18:13:50 +00:00
self.current_shortcut = VerseType.translated_tags[VerseType.Other]
elif verse_type.isnumeric():
self.current_shortcut += verse_type
self.current_shortcut = self.current_shortcut.upper()
elif verse_type.isnumeric():
self.current_shortcut += verse_type
2012-02-28 09:47:34 +00:00
elif verse_type:
self.current_shortcut = verse_type
2013-12-15 16:50:09 +00:00
keys = list(self.slide_list.keys())
matches = [match for match in keys if match.startswith(self.current_shortcut)]
if len(matches) == 1:
2013-12-15 16:50:09 +00:00
self.shortcut_timer.stop()
2013-08-31 18:17:38 +00:00
self.current_shortcut = ''
2013-12-15 16:50:09 +00:00
self.preview_widget.change_slide(self.slide_list[matches[0]])
2013-06-16 07:54:16 +00:00
self.slide_selected()
2013-12-15 16:50:09 +00:00
elif sender_name != 'shortcut_timer':
# Start the time as we did not have any match.
2013-12-15 16:50:09 +00:00
self.shortcut_timer.start(350)
else:
# The timer timed out.
if self.current_shortcut in keys:
2011-10-31 09:14:07 +00:00
# We had more than one match for example "V1" and "V10", but
# "V1" was the slide we wanted to go.
2013-12-15 16:50:09 +00:00
self.preview_widget.change_slide(self.slide_list[self.current_shortcut])
2013-06-16 07:54:16 +00:00
self.slide_selected()
# Reset the shortcut.
2013-08-31 18:17:38 +00:00
self.current_shortcut = ''
2013-12-15 16:50:09 +00:00
def set_live_hot_keys(self, parent=None):
2013-02-01 21:34:23 +00:00
"""
Set the live hotkeys
2014-01-04 11:50:27 +00:00
:param parent: The parent UI object for actions to be added to.
2013-02-01 21:34:23 +00:00
"""
2013-12-15 16:50:09 +00:00
self.previous_service = create_action(parent, 'previousService',
text=translate('OpenLP.SlideController', 'Previous Service'),
2014-03-20 19:10:31 +00:00
can_shortcuts=True, context=QtCore.Qt.WidgetWithChildrenShortcut,
2013-12-15 16:50:09 +00:00
category=self.category,
triggers=self.service_previous)
self.next_service = create_action(parent, 'nextService',
text=translate('OpenLP.SlideController', 'Next Service'),
2014-03-20 19:10:31 +00:00
can_shortcuts=True, context=QtCore.Qt.WidgetWithChildrenShortcut,
2013-12-15 16:50:09 +00:00
category=self.category,
triggers=self.service_next)
2011-01-19 21:17:32 +00:00
2013-02-07 11:33:47 +00:00
def toggle_display(self, action):
2011-12-14 19:07:40 +00:00
"""
2016-06-05 21:26:06 +00:00
Toggle the display settings triggered from remote messages.
2014-01-04 11:50:27 +00:00
:param action: The blank action to be processed.
2011-12-14 19:07:40 +00:00
"""
2013-08-31 18:17:38 +00:00
if action == 'blank' or action == 'hide':
2013-06-16 07:54:16 +00:00
self.on_blank_display(True)
2013-08-31 18:17:38 +00:00
elif action == 'theme':
2013-06-16 07:54:16 +00:00
self.on_theme_display(True)
2013-08-31 18:17:38 +00:00
elif action == 'desktop':
2013-06-16 07:54:16 +00:00
self.on_hide_display(True)
2013-08-31 18:17:38 +00:00
elif action == 'show':
2013-06-16 07:54:16 +00:00
self.on_blank_display(False)
self.on_theme_display(False)
self.on_hide_display(False)
2011-12-14 19:07:40 +00:00
2018-02-03 11:32:49 +00:00
def service_previous(self):
"""
Live event to select the previous service item from the service manager.
"""
self.keypress_queue.append(ServiceItemAction.Previous)
self._process_queue()
2018-02-03 11:32:49 +00:00
def service_next(self):
"""
Live event to select the next service item from the service manager.
"""
self.keypress_queue.append(ServiceItemAction.Next)
self._process_queue()
2011-12-02 21:13:05 +00:00
def _process_queue(self):
"""
2011-12-02 21:49:20 +00:00
Process the service item request queue. The key presses can arrive
faster than the processing so implement a FIFO queue.
"""
# Make sure only one thread get in here. Just return if already locked.
if self.keypress_queue and self.process_queue_lock.acquire(False):
while len(self.keypress_queue):
2013-12-15 16:50:09 +00:00
keypress_command = self.keypress_queue.popleft()
if keypress_command == ServiceItemAction.Previous:
2013-02-03 09:07:31 +00:00
self.service_manager.previous_item()
2013-12-15 16:50:09 +00:00
elif keypress_command == ServiceItemAction.PreviousLastSlide:
# Go to the last slide of the previous item
2013-02-03 09:07:31 +00:00
self.service_manager.previous_item(last_slide=True)
else:
2013-02-03 09:07:31 +00:00
self.service_manager.next_item()
self.process_queue_lock.release()
2013-06-03 18:47:25 +00:00
def screen_size_changed(self):
"""
2013-06-03 18:47:25 +00:00
Settings dialog has changed the screen size of adjust output and screen previews.
"""
2010-07-24 16:55:06 +00:00
# rebuild display as screen size changed
if self.display:
self.display.close()
2014-01-11 17:52:01 +00:00
self.display = MainDisplay(self)
2010-07-24 16:55:06 +00:00
self.display.setup()
if self.is_live:
2013-06-16 07:54:16 +00:00
self.__add_actions_to_widget(self.display)
if self.display.audio_player:
2015-11-07 00:49:40 +00:00
self.display.audio_player.position_changed.connect(self.on_audio_time_remaining)
# The SlidePreview's ratio.
2012-02-04 17:11:35 +00:00
try:
2013-08-31 18:17:38 +00:00
self.ratio = self.screens.current['size'].width() / self.screens.current['size'].height()
2012-02-04 17:11:35 +00:00
except ZeroDivisionError:
self.ratio = 1
self.media_controller.setup_display(self.display, False)
2013-06-03 18:47:25 +00:00
self.preview_size_changed()
self.preview_widget.screen_size_changed(self.ratio)
self.preview_display.setup()
service_item = ServiceItem()
self.preview_display.web_view.setHtml(build_html(service_item, self.preview_display.screen, None, self.is_live,
2013-12-15 16:50:09 +00:00
plugins=self.plugin_manager.plugins))
self.media_controller.setup_display(self.preview_display, True)
if self.service_item:
2013-06-03 18:47:25 +00:00
self.refresh_service_item()
2010-07-24 16:55:06 +00:00
2013-06-16 07:54:16 +00:00
def __add_actions_to_widget(self, widget):
2013-02-01 21:34:23 +00:00
"""
Add actions to the widget specified by `widget`
2016-01-23 15:28:16 +00:00
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.
2016-02-26 21:28:01 +00:00
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)
2014-01-04 11:50:27 +00:00
:param widget: The UI widget for the actions
2013-02-01 21:34:23 +00:00
"""
2011-02-04 03:31:06 +00:00
widget.addActions([
2013-12-15 16:50:09 +00:00
self.previous_item, self.next_item,
self.previous_service, self.next_service,
2016-09-02 15:41:16 +00:00
self.desktop_screen_enable,
self.desktop_screen,
self.theme_screen,
self.blank_screen])
2011-02-04 03:31:06 +00:00
2013-06-03 18:47:25 +00:00
def preview_size_changed(self):
2009-11-08 17:40:43 +00:00
"""
2014-01-11 17:52:01 +00:00
Takes care of the SlidePreview's size. Is called when one of the the splitters is moved or when the screen
size is changed. Note, that this method is (also) called frequently from the mainwindow *paintEvent*.
2009-11-08 17:40:43 +00:00
"""
2013-06-18 09:09:54 +00:00
if self.ratio < self.preview_frame.width() / self.preview_frame.height():
2010-12-26 14:44:07 +00:00
# We have to take the height as limit.
2015-11-07 00:49:40 +00:00
max_height = self.preview_frame.height() - self.grid.contentsMargins().top() * 2
2013-06-03 18:47:25 +00:00
self.slide_preview.setFixedSize(QtCore.QSize(max_height * self.ratio, max_height))
self.preview_display.setFixedSize(QtCore.QSize(max_height * self.ratio, max_height))
2014-01-11 17:52:01 +00:00
self.preview_display.screen = {'size': self.preview_display.geometry()}
2010-12-26 14:44:07 +00:00
else:
# We have to take the width as limit.
2015-11-07 00:49:40 +00:00
max_width = self.preview_frame.width() - self.grid.contentsMargins().top() * 2
2013-06-03 18:47:25 +00:00
self.slide_preview.setFixedSize(QtCore.QSize(max_width, max_width / self.ratio))
self.preview_display.setFixedSize(QtCore.QSize(max_width, max_width / self.ratio))
2013-08-31 18:17:38 +00:00
self.preview_display.screen = {'size': self.preview_display.geometry()}
2014-12-03 21:19:43 +00:00
# Only update controller layout if width has actually changed
if self.controller_width != self.controller.width():
self.controller_width = self.controller.width()
self.on_controller_size_changed(self.controller_width)
2009-08-24 05:10:04 +00:00
2013-06-16 07:54:16 +00:00
def on_controller_size_changed(self, width):
"""
Change layout of display control buttons on controller size change
2014-01-04 11:50:27 +00:00
:param width: the new width of the display
"""
if self.is_live:
2013-02-10 19:08:37 +00:00
# Space used by the toolbar.
2013-02-21 21:33:06 +00:00
used_space = self.toolbar.size().width() + self.hide_menu.size().width()
# Add the threshold to prevent flickering.
2013-04-05 13:41:42 +00:00
if width > used_space + HIDE_MENU_THRESHOLD and self.hide_menu.isVisible():
2013-12-15 16:50:09 +00:00
self.toolbar.set_widget_visible(NARROW_MENU, False)
2014-01-04 11:50:27 +00:00
self.set_blank_menu()
# Take away a threshold to prevent flickering.
2013-04-05 13:41:42 +00:00
elif width < used_space - HIDE_MENU_THRESHOLD and not self.hide_menu.isVisible():
2014-01-04 11:50:27 +00:00
self.set_blank_menu(False)
2013-12-15 16:50:09 +00:00
self.toolbar.set_widget_visible(NARROW_MENU)
# Fallback to the standard blank toolbar if the hide_menu is not visible.
elif not self.hide_menu.isVisible():
2014-12-06 20:40:40 +00:00
self.toolbar.set_widget_visible(NARROW_MENU, False)
self.set_blank_menu()
2013-02-05 08:05:28 +00:00
2014-01-04 11:50:27 +00:00
def set_blank_menu(self, visible=True):
"""
Set the correct menu type dependent on the service item type
:param visible: Do I need to hide the menu?
"""
self.toolbar.set_widget_visible(WIDE_MENU, False)
if self.service_item and self.service_item.is_text():
self.toolbar.set_widget_visible(WIDE_MENU, visible)
else:
self.toolbar.set_widget_visible(NON_TEXT_MENU, visible)
2013-06-16 07:54:16 +00:00
def on_song_bar_handler(self):
2013-02-01 21:34:23 +00:00
"""
Some song handler
"""
2012-05-17 18:57:01 +00:00
request = self.sender().text()
2013-12-15 16:50:09 +00:00
slide_no = self.slide_list[request]
2013-07-05 18:39:31 +00:00
width = self.main_window.control_splitter.sizes()[self.split]
2013-06-15 20:51:11 +00:00
self.preview_widget.replace_service_item(self.service_item, width, slide_no)
2013-06-16 07:54:16 +00:00
self.slide_selected()
2009-10-26 06:09:33 +00:00
2013-03-14 20:21:04 +00:00
def receive_spin_delay(self):
"""
2013-03-01 17:29:09 +00:00
Adjusts the value of the ``delay_spin_box`` to the given one.
"""
2013-08-31 18:17:38 +00:00
self.delay_spin_box.setValue(Settings().value('core/loop delay'))
2013-02-07 11:33:47 +00:00
def update_slide_limits(self):
"""
Updates the Slide Limits variable from the settings.
"""
2013-08-31 18:17:38 +00:00
self.slide_limits = Settings().value(self.main_window.advanced_settings_section + '/slide limits')
2009-08-29 07:17:56 +00:00
2013-06-03 18:47:25 +00:00
def enable_tool_bar(self, item):
2009-08-28 18:27:32 +00:00
"""
2013-06-03 18:47:25 +00:00
Allows the toolbars to be reconfigured based on Controller Type and ServiceItem Type
2014-01-04 11:50:27 +00:00
:param item: current service item being processed
2009-08-28 18:27:32 +00:00
"""
if self.is_live:
2013-06-03 18:47:25 +00:00
self.enable_live_tool_bar(item)
else:
2013-06-03 18:47:25 +00:00
self.enable_preview_tool_bar(item)
2013-06-03 18:47:25 +00:00
def enable_live_tool_bar(self, item):
"""
Allows the live toolbar to be customised
2014-01-04 11:50:27 +00:00
:param item: The current service item
"""
# Work-around for OS X, hide and then show the toolbar
# See bug #791050
self.toolbar.hide()
self.mediabar.hide()
self.song_menu.hide()
2013-12-15 16:50:09 +00:00
self.toolbar.set_widget_visible(LOOP_LIST, False)
2018-02-23 09:17:21 +00:00
self.toolbar.set_widget_visible('song_menu', False)
# Reset the button
self.play_slides_once.setChecked(False)
2018-04-10 21:10:09 +00:00
self.play_slides_once.setIcon(UiIcons().clock)
2016-09-30 01:06:51 +00:00
self.play_slides_once.setText(UiStrings().PlaySlidesToEnd)
self.play_slides_loop.setChecked(False)
2018-04-10 21:10:09 +00:00
self.play_slides_loop.setIcon(UiIcons().clock)
2016-09-30 01:06:51 +00:00
self.play_slides_loop.setText(UiStrings().PlaySlidesInLoop)
if item.is_text():
2015-03-09 20:57:39 +00:00
if (Settings().value(self.main_window.songs_settings_section + '/display songbar') and
not self.song_menu.menu().isEmpty()):
2018-02-23 09:17:21 +00:00
self.toolbar.set_widget_visible('song_menu', True)
2012-12-10 18:04:58 +00:00
if item.is_capable(ItemCapabilities.CanLoop) and len(item.get_frames()) > 1:
2013-12-15 16:50:09 +00:00
self.toolbar.set_widget_visible(LOOP_LIST)
2010-04-08 16:00:04 +00:00
if item.is_media():
self.mediabar.show()
self.previous_item.setVisible(not item.is_media())
2013-12-15 16:50:09 +00:00
self.next_item.setVisible(not item.is_media())
2014-12-06 20:40:40 +00:00
# The layout of the toolbar is size dependent, so make sure it fits. Reset stored controller_width.
if self.is_live:
self.controller_width = -1
self.on_controller_size_changed(self.controller.width())
# Work-around for OS X, hide and then show the toolbar
# See bug #791050
2011-07-18 21:25:10 +00:00
self.toolbar.show()
2013-06-03 18:47:25 +00:00
def enable_preview_tool_bar(self, item):
"""
Allows the Preview toolbar to be customised
2014-01-11 17:52:01 +00:00
:param item: The current service item
"""
# Work-around for OS X, hide and then show the toolbar
# See bug #791050
self.toolbar.hide()
self.mediabar.hide()
2018-02-23 09:17:21 +00:00
self.toolbar.set_widget_visible('editSong', False)
self.toolbar.set_widget_visible('clear', True)
2011-08-28 17:45:13 +00:00
if item.is_capable(ItemCapabilities.CanEdit) and item.from_plugin:
2018-02-23 09:17:21 +00:00
self.toolbar.set_widget_visible('editSong')
elif item.is_media():
self.mediabar.show()
self.previous_item.setVisible(not item.is_media())
2013-12-15 16:50:09 +00:00
self.next_item.setVisible(not item.is_media())
# Work-around for OS X, hide and then show the toolbar
# See bug #791050
2011-07-18 21:25:10 +00:00
self.toolbar.show()
2013-06-03 18:47:25 +00:00
def refresh_service_item(self):
2010-03-13 15:11:31 +00:00
"""
Method to update the service item if the screen has changed
"""
if self.service_item.is_text() or self.service_item.is_image():
item = self.service_item
item.render()
2013-06-03 18:47:25 +00:00
self._process_item(item, self.selected_row)
2010-03-13 15:11:31 +00:00
2013-01-27 20:36:18 +00:00
def add_service_item(self, item):
"""
Method to install the service item into the controller
Called by plugins
2014-01-11 17:52:01 +00:00
:param item: The current service item
"""
2009-08-23 20:15:05 +00:00
item.render()
2013-06-16 07:54:16 +00:00
slide_no = 0
if self.song_edit:
2013-06-16 07:54:16 +00:00
slide_no = self.selected_row
self.song_edit = False
2013-06-16 07:54:16 +00:00
self._process_item(item, slide_no)
2009-10-24 18:11:02 +00:00
2013-06-16 07:54:16 +00:00
def replace_service_manager_item(self, item):
"""
Replacement item following a remote edit.
This action also takes place when a song that is sent to live from Service Manager is edited.
2014-01-11 17:52:01 +00:00
:param item: The current service item
"""
if item == self.service_item:
Registry().set_flag('replace service manager item', True)
2013-06-14 20:38:37 +00:00
self._process_item(item, self.preview_widget.current_slide_number())
Registry().set_flag('replace service manager item', False)
2013-06-16 07:54:16 +00:00
def add_service_manager_item(self, item, slide_no):
"""
2013-06-16 16:46:44 +00:00
Method to install the service item into the controller and request the correct toolbar for the plugin. Called by
:class:`~openlp.core.ui.ServiceManager`
2014-01-11 17:52:01 +00:00
:param item: The current service item
:param slide_no: The slide number to select
"""
2013-06-16 07:54:16 +00:00
# If no valid slide number is specified we take the first one, but we remember the initial value to see if we
# should reload the song or not
2013-12-15 16:50:09 +00:00
slide_num = slide_no
2013-06-16 07:54:16 +00:00
if slide_no == -1:
2013-12-15 16:50:09 +00:00
slide_num = 0
# If service item is the same as the current one, only change slide
2013-06-16 07:54:16 +00:00
if slide_no >= 0 and item == self.service_item:
2013-12-15 16:50:09 +00:00
self.preview_widget.change_slide(slide_num)
2013-06-16 07:54:16 +00:00
self.slide_selected()
else:
2013-12-15 16:50:09 +00:00
self._process_item(item, slide_num)
if self.is_live and item.auto_play_slides_loop and item.timed_slide_interval > 0:
self.play_slides_loop.setChecked(item.auto_play_slides_loop)
2013-03-01 17:29:09 +00:00
self.delay_spin_box.setValue(int(item.timed_slide_interval))
2013-06-16 07:54:16 +00:00
self.on_play_slides_loop()
elif self.is_live and item.auto_play_slides_once and item.timed_slide_interval > 0:
self.play_slides_once.setChecked(item.auto_play_slides_once)
2013-03-01 17:29:09 +00:00
self.delay_spin_box.setValue(int(item.timed_slide_interval))
2013-06-16 07:54:16 +00:00
self.on_play_slides_once()
2013-12-15 16:50:09 +00:00
def _process_item(self, service_item, slide_no):
"""
2013-06-16 16:46:44 +00:00
Loads a ServiceItem into the system from ServiceManager. Display the slide number passed.
2014-01-11 17:52:01 +00:00
:param service_item: The current service item
:param slide_no: The slide number to select
"""
2013-02-07 11:33:47 +00:00
self.on_stop_loop()
old_item = self.service_item
2016-06-05 21:26:06 +00:00
# rest to allow the remote pick up verse 1 if large imaged
self.selected_row = 0
# take a copy not a link to the servicemanager copy.
self.service_item = copy.copy(service_item)
if self.service_item.is_command():
Registry().execute(
2016-05-20 16:22:06 +00:00
'{text}_start'.format(text=service_item.name.lower()),
[self.service_item, self.is_live, self.hide_mode(), slide_no])
# Reset blanking if needed
2015-04-30 12:48:45 +00:00
if old_item and self.is_live and (old_item.is_capable(ItemCapabilities.ProvidesOwnDisplay) or
self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay)):
self._reset_blank(self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay))
self.info_label.setText(self.service_item.title)
2013-12-15 16:50:09 +00:00
self.slide_list = {}
if self.is_live:
self.song_menu.menu().clear()
if self.display.audio_player:
self.display.audio_player.reset()
self.set_audio_items_visibility(False)
self.audio_pause_item.setChecked(False)
# If the current item has background audio
if self.service_item.is_capable(ItemCapabilities.HasBackgroundAudio):
self.log_debug('Starting to play...')
self.display.audio_player.add_to_playlist(self.service_item.background_audio)
self.track_menu.clear()
for counter in range(len(self.service_item.background_audio)):
2015-01-29 21:21:03 +00:00
action = self.track_menu.addAction(
2017-09-30 20:16:30 +00:00
os.path.basename(str(self.service_item.background_audio[counter])))
action.setData(counter)
action.triggered.connect(self.on_track_triggered)
self.display.audio_player.repeat = \
Settings().value(self.main_window.general_settings_section + '/audio repeat list')
if Settings().value(self.main_window.general_settings_section + '/audio start paused'):
self.audio_pause_item.setChecked(True)
self.display.audio_player.pause()
else:
self.display.audio_player.play()
self.set_audio_items_visibility(True)
row = 0
2013-07-05 18:39:31 +00:00
width = self.main_window.control_splitter.sizes()[self.split]
2014-01-11 17:52:01 +00:00
for frame_number, frame in enumerate(self.service_item.get_frames()):
if self.service_item.is_text():
2013-08-31 18:17:38 +00:00
if frame['verseTag']:
2011-02-17 19:46:01 +00:00
# These tags are already translated.
2013-08-31 18:17:38 +00:00
verse_def = frame['verseTag']
2016-05-20 16:22:06 +00:00
verse_def = '{def1}{def2}'.format(def1=verse_def[0], def2=verse_def[1:])
two_line_def = '{def1}\n{def2}'.format(def1=verse_def[0], def2=verse_def[1:])
row = two_line_def
2013-12-15 16:50:09 +00:00
if verse_def not in self.slide_list:
2014-01-11 17:52:01 +00:00
self.slide_list[verse_def] = frame_number
if self.is_live:
2013-06-16 07:54:16 +00:00
self.song_menu.menu().addAction(verse_def, self.on_song_bar_handler)
else:
row += 1
2013-12-15 16:50:09 +00:00
self.slide_list[str(row)] = row - 1
else:
2010-03-30 19:01:23 +00:00
row += 1
2013-12-15 16:50:09 +00:00
self.slide_list[str(row)] = row - 1
# If current slide set background to image
2014-01-11 17:52:01 +00:00
if not self.service_item.is_command() and frame_number == slide_no:
2013-12-15 16:50:09 +00:00
self.service_item.bg_image_bytes = \
self.image_manager.get_image_bytes(frame['path'], ImageSource.ImagePlugin)
self.preview_widget.replace_service_item(self.service_item, width, slide_no)
self.enable_tool_bar(self.service_item)
# Pass to display for viewing.
2011-04-14 21:34:01 +00:00
# Postpone image build, we need to do this later to avoid the theme
# flashing on the screen
if not self.service_item.is_image():
self.display.build_html(self.service_item)
if self.service_item.is_media():
self.on_media_start(self.service_item)
2013-06-16 07:54:16 +00:00
self.slide_selected(True)
if self.service_item.from_service:
self.preview_widget.setFocus()
if old_item:
# Close the old item after the new one is opened
# This avoids the service theme/desktop flashing on screen
# However opening a new item of the same type will automatically
# close the previous, so make sure we don't close the new one.
if old_item.is_command() and not self.service_item.is_command() or \
old_item.is_command() and not old_item.is_media() and self.service_item.is_media():
2016-05-20 16:22:06 +00:00
Registry().execute('{name}_stop'.format(name=old_item.name.lower()), [old_item, self.is_live])
if old_item.is_media() and not self.service_item.is_media():
2013-06-16 07:54:16 +00:00
self.on_media_close()
2017-03-28 05:15:05 +00:00
if self.is_live:
Registry().execute('slidecontroller_{item}_started'.format(item=self.type_prefix), [self.service_item])
2013-02-07 11:33:47 +00:00
def on_slide_selected_index(self, message):
"""
Go to the requested slide
2014-01-11 17:52:01 +00:00
2016-06-05 21:26:06 +00:00
:param message: remote message to be processed.
"""
index = int(message[0])
if not self.service_item:
return
if self.service_item.is_command():
2016-05-20 16:22:06 +00:00
Registry().execute('{name}_slide'.format(name=self.service_item.name.lower()),
[self.service_item, self.is_live, index])
2013-06-16 07:54:16 +00:00
self.update_preview()
self.selected_row = index
else:
self.preview_widget.change_slide(index)
self.slide_selected()
2013-06-16 07:54:16 +00:00
def main_display_set_background(self):
"""
Allow the main display to blank the main display at startup time
"""
2013-08-31 18:17:38 +00:00
display_type = Settings().value(self.main_window.general_settings_section + '/screen blank')
2012-12-10 18:04:58 +00:00
if self.screens.which_screen(self.window()) != self.screens.which_screen(self.display):
# Order done to handle initial conversion
2013-08-31 18:17:38 +00:00
if display_type == 'themed':
2013-06-16 07:54:16 +00:00
self.on_theme_display(True)
2013-08-31 18:17:38 +00:00
elif display_type == 'hidden':
2013-06-16 07:54:16 +00:00
self.on_hide_display(True)
2013-08-31 18:17:38 +00:00
elif display_type == 'blanked':
2013-06-16 07:54:16 +00:00
self.on_blank_display(True)
2011-06-01 07:38:19 +00:00
else:
2013-08-31 18:17:38 +00:00
Registry().execute('live_display_show')
else:
2016-09-02 15:52:44 +00:00
self.on_hide_display_enable()
2013-02-07 11:33:47 +00:00
def on_slide_blank(self):
"""
Handle the slidecontroller blank event
"""
2013-06-16 07:54:16 +00:00
self.on_blank_display(True)
2013-02-07 11:33:47 +00:00
def on_slide_unblank(self):
"""
Handle the slidecontroller unblank event.
"""
2016-07-14 18:13:58 +00:00
if not Registry().get_flag('replace service manager item') is True:
self.on_blank_display(False)
2013-06-16 07:54:16 +00:00
def on_blank_display(self, checked=None):
2010-01-24 16:28:18 +00:00
"""
2010-06-23 17:37:01 +00:00
Handle the blank screen button actions
2014-01-11 17:52:01 +00:00
:param checked: the new state of the of the widget
2010-01-24 16:28:18 +00:00
"""
if checked is None:
checked = self.blank_screen.isChecked()
2016-05-20 16:22:06 +00:00
self.log_debug('on_blank_display {text}'.format(text=checked))
self.hide_menu.setDefaultAction(self.blank_screen)
self.blank_screen.setChecked(checked)
self.theme_screen.setChecked(False)
self.desktop_screen.setChecked(False)
2010-04-29 19:33:45 +00:00
if checked:
2013-08-31 18:17:38 +00:00
Settings().setValue(self.main_window.general_settings_section + '/screen blank', 'blanked')
2010-04-29 19:33:45 +00:00
else:
2013-08-31 18:17:38 +00:00
Settings().remove(self.main_window.general_settings_section + '/screen blank')
2013-06-16 07:54:16 +00:00
self.blank_plugin()
self.update_preview()
2013-06-15 20:51:11 +00:00
self.on_toggle_loop()
2013-06-16 07:54:16 +00:00
def on_theme_display(self, checked=None):
"""
Handle the Theme screen button
2014-01-11 17:52:01 +00:00
:param checked: the new state of the of the widget
"""
if checked is None:
checked = self.theme_screen.isChecked()
2016-05-20 16:22:06 +00:00
self.log_debug('on_theme_display {text}'.format(text=checked))
self.hide_menu.setDefaultAction(self.theme_screen)
self.blank_screen.setChecked(False)
self.theme_screen.setChecked(checked)
self.desktop_screen.setChecked(False)
2010-04-29 19:33:45 +00:00
if checked:
2013-08-31 18:17:38 +00:00
Settings().setValue(self.main_window.general_settings_section + '/screen blank', 'themed')
2010-04-29 19:33:45 +00:00
else:
2013-08-31 18:17:38 +00:00
Settings().remove(self.main_window.general_settings_section + '/screen blank')
2013-06-16 07:54:16 +00:00
self.blank_plugin()
self.update_preview()
2013-06-15 20:51:11 +00:00
self.on_toggle_loop()
2013-06-16 07:54:16 +00:00
def on_hide_display(self, checked=None):
"""
Handle the Hide screen button
2016-09-02 15:52:44 +00:00
This toggles the desktop screen.
2014-01-11 17:52:01 +00:00
:param checked: the new state of the of the widget
"""
if checked is None:
checked = self.desktop_screen.isChecked()
2016-05-20 16:22:06 +00:00
self.log_debug('on_hide_display {text}'.format(text=checked))
self.hide_menu.setDefaultAction(self.desktop_screen)
self.blank_screen.setChecked(False)
self.theme_screen.setChecked(False)
self.desktop_screen.setChecked(checked)
2010-04-29 19:33:45 +00:00
if checked:
2013-08-31 18:17:38 +00:00
Settings().setValue(self.main_window.general_settings_section + '/screen blank', 'hidden')
else:
2013-08-31 18:17:38 +00:00
Settings().remove(self.main_window.general_settings_section + '/screen blank')
2013-06-16 07:54:16 +00:00
self.hide_plugin(checked)
self.update_preview()
2013-06-15 20:51:11 +00:00
self.on_toggle_loop()
2016-09-02 15:52:44 +00:00
def on_hide_display_enable(self, checked=None):
2016-09-02 15:22:29 +00:00
"""
2016-09-02 15:52:44 +00:00
Handle the on_hide_display_enable
This only enables the desktop screen.
:param checked: the new state of the of the widget
2016-09-02 15:22:29 +00:00
"""
self.blank_screen.setChecked(False)
self.theme_screen.setChecked(False)
Registry().execute('live_display_hide', HideMode.Screen)
self.desktop_screen.setChecked(True)
2016-09-02 15:41:16 +00:00
self.update_preview()
self.on_toggle_loop()
2016-09-02 15:22:29 +00:00
2013-06-16 07:54:16 +00:00
def blank_plugin(self):
2009-08-23 20:15:05 +00:00
"""
Blank/Hide the display screen within a plugin if required.
2009-08-23 20:15:05 +00:00
"""
hide_mode = self.hide_mode()
2016-05-20 16:22:06 +00:00
self.log_debug('blank_plugin {text}'.format(text=hide_mode))
if self.service_item is not None:
if hide_mode:
if not self.service_item.is_command():
2013-08-31 18:17:38 +00:00
Registry().execute('live_display_hide', hide_mode)
2016-05-20 16:22:06 +00:00
Registry().execute('{text}_blank'.format(text=self.service_item.name.lower()),
[self.service_item, self.is_live, hide_mode])
2010-03-19 23:02:23 +00:00
else:
if not self.service_item.is_command():
2013-08-31 18:17:38 +00:00
Registry().execute('live_display_show')
2016-05-20 16:22:06 +00:00
Registry().execute('{text}_unblank'.format(text=self.service_item.name.lower()),
[self.service_item, self.is_live])
else:
if hide_mode:
2013-08-31 18:17:38 +00:00
Registry().execute('live_display_hide', hide_mode)
else:
2013-08-31 18:17:38 +00:00
Registry().execute('live_display_show')
2009-08-23 20:15:05 +00:00
2013-06-16 07:54:16 +00:00
def hide_plugin(self, hide):
"""
Tell the plugin to hide the display screen.
"""
2016-05-20 16:22:06 +00:00
self.log_debug('hide_plugin {text}'.format(text=hide))
if self.service_item is not None:
if hide:
2013-08-31 18:17:38 +00:00
Registry().execute('live_display_hide', HideMode.Screen)
2016-07-17 07:28:25 +00:00
Registry().execute('{text}_hide'.format(text=self.service_item.name.lower()),
[self.service_item, self.is_live])
else:
if not self.service_item.is_command():
2013-08-31 18:17:38 +00:00
Registry().execute('live_display_show')
2016-05-20 16:22:06 +00:00
Registry().execute('{text}_unblank'.format(text=self.service_item.name.lower()),
[self.service_item, self.is_live])
else:
if hide:
2013-08-31 18:17:38 +00:00
Registry().execute('live_display_hide', HideMode.Screen)
else:
2013-08-31 18:17:38 +00:00
Registry().execute('live_display_show')
2018-02-03 11:32:49 +00:00
def on_slide_selected(self):
"""
Slide selected in controller
2013-12-21 08:46:30 +00:00
Note for some reason a dummy field is required. Nothing is passed!
"""
2013-06-16 07:54:16 +00:00
self.slide_selected()
2013-06-16 07:54:16 +00:00
def slide_selected(self, start=False):
2009-08-23 20:15:05 +00:00
"""
2014-01-11 17:52:01 +00:00
Generate the preview when you click on a slide. If this is the Live Controller also display on the screen
:param start:
2009-08-23 20:15:05 +00:00
"""
# Only one thread should be in here at the time. If already locked just skip, since the update will be
# done by the thread holding the lock. If it is a "start" slide, we must wait for the lock, but only for 0.2
# seconds, since we don't want to cause a deadlock
timeout = 0.2 if start else -1
2018-07-05 20:43:55 +00:00
if not self.slide_selected_lock.acquire(start, timeout): # pylint: disable=too-many-function-args
if start:
self.log_debug('Could not get lock in slide_selected after waiting %f, skip to avoid deadlock.'
% timeout)
return
# If "click live slide to unblank" is enabled, unblank the display. And start = Item is sent to Live.
# Note: If this if statement is placed at the bottom of this function instead of top slide transitions are lost.
if self.is_live and Settings().value('core/click live slide to unblank'):
if not start:
Registry().execute('slidecontroller_live_unblank')
row = self.preview_widget.current_slide_number()
old_selected_row = self.selected_row
2013-03-01 17:29:09 +00:00
self.selected_row = 0
if -1 < row < self.preview_widget.slide_count():
if self.service_item.is_command():
if self.is_live and not start:
2016-05-20 16:22:06 +00:00
Registry().execute('{text}_slide'.format(text=self.service_item.name.lower()),
[self.service_item, self.is_live, row])
else:
to_display = self.service_item.get_rendered_frame(row)
if self.service_item.is_text():
self.display.text(to_display, row != old_selected_row)
2010-07-17 08:59:15 +00:00
else:
if start:
self.display.build_html(self.service_item, to_display)
else:
self.display.image(to_display)
2011-03-04 17:21:50 +00:00
# reset the store used to display first image
self.service_item.bg_image_bytes = None
2013-03-01 17:29:09 +00:00
self.selected_row = row
2014-01-09 19:52:20 +00:00
self.update_preview()
self.preview_widget.change_slide(row)
self.display.setFocus()
# Release lock
self.slide_selected_lock.release()
2009-10-24 13:07:41 +00:00
2013-02-07 11:33:47 +00:00
def on_slide_change(self, row):
"""
The slide has been changed. Update the slidecontroller accordingly
2014-01-11 17:52:01 +00:00
:param row: Row to be selected
"""
self.preview_widget.change_slide(row)
2013-06-16 07:54:16 +00:00
self.update_preview()
self.selected_row = row
2009-08-23 20:15:05 +00:00
2013-06-16 07:54:16 +00:00
def update_preview(self):
2011-01-22 19:32:02 +00:00
"""
2013-05-19 19:56:48 +00:00
This updates the preview frame, for example after changing a slide or using *Blank to Theme*.
2011-01-22 19:32:02 +00:00
"""
2016-05-20 16:22:06 +00:00
self.log_debug('update_preview {text} '.format(text=self.screens.current['primary']))
if self.service_item and self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay):
if self.is_live:
# If live, grab screen-cap of main display now
QtCore.QTimer.singleShot(500, self.grab_maindisplay)
# but take another in a couple of seconds in case slide change is slow
QtCore.QTimer.singleShot(2500, self.grab_maindisplay)
else:
# If not live, use the slide's thumbnail/icon instead
image_path = self.service_item.get_rendered_frame(self.selected_row)
if self.service_item.is_capable(ItemCapabilities.HasThumbnails):
image = self.image_manager.get_image(image_path, ImageSource.CommandPlugins)
self.slide_image = QtGui.QPixmap.fromImage(image)
else:
self.slide_image = QtGui.QPixmap(image_path)
self.slide_image.setDevicePixelRatio(self.main_window.devicePixelRatio())
self.slide_preview.setPixmap(self.slide_image)
else:
2013-05-19 19:56:48 +00:00
self.slide_image = self.display.preview()
self.slide_image.setDevicePixelRatio(self.main_window.devicePixelRatio())
2013-06-03 18:47:25 +00:00
self.slide_preview.setPixmap(self.slide_image)
2013-05-19 19:56:48 +00:00
self.slide_count += 1
2013-05-19 19:56:48 +00:00
def grab_maindisplay(self):
2011-01-22 19:32:02 +00:00
"""
Creates an image of the current screen and updates the preview frame.
"""
2015-11-07 00:49:40 +00:00
win_id = QtWidgets.QApplication.desktop().winId()
screen = QtWidgets.QApplication.primaryScreen()
2013-08-31 18:17:38 +00:00
rect = self.screens.current['size']
2016-01-13 21:00:46 +00:00
win_image = screen.grabWindow(win_id, rect.x(), rect.y(), rect.width(), rect.height())
win_image.setDevicePixelRatio(self.slide_preview.devicePixelRatio())
2013-06-03 18:47:25 +00:00
self.slide_preview.setPixmap(win_image)
2013-05-19 19:56:48 +00:00
self.slide_image = win_image
2009-08-23 20:15:05 +00:00
2013-02-07 11:33:47 +00:00
def on_slide_selected_next_action(self, checked):
"""
2014-01-11 17:52:01 +00:00
Wrapper function from create_action so we can throw away the incorrect parameter
:param checked: the new state of the of the widget
"""
2013-02-07 11:33:47 +00:00
self.on_slide_selected_next()
2013-02-07 11:33:47 +00:00
def on_slide_selected_next(self, wrap=None):
2009-08-23 20:15:05 +00:00
"""
Go to the next slide.
2014-01-11 17:52:01 +00:00
:param wrap: Are we wrapping round the service item
2009-08-23 20:15:05 +00:00
"""
if not self.service_item:
2009-11-30 18:29:22 +00:00
return
2014-01-11 17:52:01 +00:00
if self.service_item.is_command():
2016-05-20 16:22:06 +00:00
Registry().execute('{text}_next'.format(text=self.service_item.name.lower()),
[self.service_item, self.is_live])
2014-01-11 17:52:01 +00:00
if self.is_live:
self.update_preview()
2009-09-05 19:58:02 +00:00
else:
row = self.preview_widget.current_slide_number() + 1
if row == self.preview_widget.slide_count():
if wrap is None:
if self.slide_limits == SlideLimits.Wrap:
row = 0
elif self.is_live and self.slide_limits == SlideLimits.Next:
2013-06-16 07:54:16 +00:00
self.service_next()
return
else:
row = self.preview_widget.slide_count() - 1
elif wrap:
row = 0
else:
row = self.preview_widget.slide_count() - 1
self.preview_widget.change_slide(row)
self.slide_selected()
2009-08-23 20:15:05 +00:00
2018-02-03 11:32:49 +00:00
def on_slide_selected_previous(self):
2009-08-23 20:15:05 +00:00
"""
Go to the previous slide.
"""
if not self.service_item:
return
2014-01-11 17:52:01 +00:00
if self.service_item.is_command():
2016-05-20 16:22:06 +00:00
Registry().execute('{text}_previous'.format(text=self.service_item.name.lower()),
[self.service_item, self.is_live])
2014-01-11 17:52:01 +00:00
if self.is_live:
self.update_preview()
2009-09-06 19:23:57 +00:00
else:
row = self.preview_widget.current_slide_number() - 1
2009-09-06 19:23:57 +00:00
if row == -1:
if self.slide_limits == SlideLimits.Wrap:
row = self.preview_widget.slide_count() - 1
elif self.is_live and self.slide_limits == SlideLimits.Next:
2012-12-10 18:04:58 +00:00
self.keypress_queue.append(ServiceItemAction.PreviousLastSlide)
self._process_queue()
return
else:
row = 0
self.preview_widget.change_slide(row)
self.slide_selected()
2009-08-23 20:15:05 +00:00
2013-06-15 20:51:11 +00:00
def on_toggle_loop(self):
"""
Toggles the loop state.
"""
hide_mode = self.hide_mode()
if hide_mode is None and (self.play_slides_loop.isChecked() or self.play_slides_once.isChecked()):
2013-06-16 07:54:16 +00:00
self.on_start_loop()
else:
2013-02-07 11:33:47 +00:00
self.on_stop_loop()
2013-06-16 07:54:16 +00:00
def on_start_loop(self):
2009-08-24 05:10:04 +00:00
"""
Start the timer loop running and store the timer id
2009-08-24 05:10:04 +00:00
"""
if self.preview_widget.slide_count() > 1:
2013-03-01 17:29:09 +00:00
self.timer_id = self.startTimer(int(self.delay_spin_box.value()) * 1000)
2009-08-24 05:10:04 +00:00
2013-02-07 11:33:47 +00:00
def on_stop_loop(self):
2009-08-24 05:10:04 +00:00
"""
Stop the timer loop running
2009-08-24 05:10:04 +00:00
"""
if self.timer_id:
2010-05-05 19:14:48 +00:00
self.killTimer(self.timer_id)
self.timer_id = 0
2013-06-16 07:54:16 +00:00
def on_play_slides_loop(self, checked=None):
"""
Start or stop 'Play Slides in Loop'
2014-01-11 17:52:01 +00:00
:param checked: is the check box checked.
"""
if checked is None:
checked = self.play_slides_loop.isChecked()
else:
self.play_slides_loop.setChecked(checked)
2016-05-20 16:22:06 +00:00
self.log_debug('on_play_slides_loop {text}'.format(text=checked))
if checked:
2018-04-10 21:10:09 +00:00
self.play_slides_loop.setIcon(UiIcons().stop)
self.play_slides_loop.setText(UiStrings().StopPlaySlidesInLoop)
2018-04-10 21:10:09 +00:00
self.play_slides_once.setIcon(UiIcons().clock)
self.play_slides_once.setText(UiStrings().PlaySlidesToEnd)
self.play_slides_menu.setDefaultAction(self.play_slides_loop)
self.play_slides_once.setChecked(False)
if Settings().value('core/click live slide to unblank'):
Registry().execute('slidecontroller_live_unblank')
else:
2018-04-10 21:10:09 +00:00
self.play_slides_loop.setIcon(UiIcons().clock)
self.play_slides_loop.setText(UiStrings().PlaySlidesInLoop)
2013-06-15 20:51:11 +00:00
self.on_toggle_loop()
2013-06-16 07:54:16 +00:00
def on_play_slides_once(self, checked=None):
"""
Start or stop 'Play Slides to End'
2014-01-11 17:52:01 +00:00
:param checked: is the check box checked.
"""
if checked is None:
checked = self.play_slides_once.isChecked()
else:
self.play_slides_once.setChecked(checked)
2016-05-20 16:22:06 +00:00
self.log_debug('on_play_slides_once {text}'.format(text=checked))
if checked:
2018-04-10 21:10:09 +00:00
self.play_slides_once.setIcon(UiIcons().stop)
self.play_slides_once.setText(UiStrings().StopPlaySlidesToEnd)
2018-04-10 21:10:09 +00:00
self.play_slides_loop.setIcon(UiIcons().clock)
self.play_slides_loop.setText(UiStrings().PlaySlidesInLoop)
self.play_slides_menu.setDefaultAction(self.play_slides_once)
self.play_slides_loop.setChecked(False)
if Settings().value('core/click live slide to unblank'):
Registry().execute('slidecontroller_live_unblank')
else:
2018-04-10 21:10:09 +00:00
self.play_slides_once.setIcon(UiIcons().clock)
self.play_slides_once.setText(UiStrings().PlaySlidesToEnd)
2013-06-15 20:51:11 +00:00
self.on_toggle_loop()
2010-05-05 19:14:48 +00:00
2013-06-16 07:54:16 +00:00
def set_audio_items_visibility(self, visible):
2013-02-01 21:34:23 +00:00
"""
Set the visibility of the audio stuff
"""
2013-12-15 16:50:09 +00:00
self.toolbar.set_widget_visible(AUDIO_LIST, visible)
2011-08-30 21:20:32 +00:00
2013-06-16 07:54:16 +00:00
def set_audio_pause_clicked(self, checked):
2013-02-01 21:34:23 +00:00
"""
Pause the audio player
2014-01-11 17:52:01 +00:00
:param checked: is the check box checked.
2013-02-01 21:34:23 +00:00
"""
2013-03-01 17:29:09 +00:00
if not self.audio_pause_item.isVisible():
2011-08-30 21:20:32 +00:00
return
if checked:
self.display.audio_player.pause()
2011-08-30 21:20:32 +00:00
else:
self.display.audio_player.play()
2011-08-30 21:20:32 +00:00
2009-08-24 05:10:04 +00:00
def timerEvent(self, event):
"""
If the timer event is for this window select next slide
2014-01-11 17:52:01 +00:00
:param event: The triggered event
"""
2009-08-24 05:10:04 +00:00
if event.timerId() == self.timer_id:
self.on_slide_selected_next(self.play_slides_loop.isChecked())
2018-02-03 11:32:49 +00:00
def on_edit_song(self):
2010-04-29 20:56:27 +00:00
"""
From the preview display requires the service Item to be editied
"""
self.song_edit = True
2013-04-18 09:32:48 +00:00
new_item = Registry().get(self.service_item.name).on_remote_edit(self.service_item.edit_id, True)
2013-01-27 09:57:03 +00:00
if new_item:
2013-01-27 20:36:18 +00:00
self.add_service_item(new_item)
2018-02-23 09:11:07 +00:00
2018-02-23 08:56:01 +00:00
def on_clear(self):
"""
Clear the preview bar.
"""
2018-02-23 09:11:07 +00:00
self.preview_widget.clear_list()
2018-03-09 21:58:45 +00:00
self.toolbar.set_widget_visible('editSong', False)
2018-03-18 07:45:46 +00:00
self.toolbar.set_widget_visible('clear', False)
2009-10-24 13:07:41 +00:00
2018-02-03 11:32:49 +00:00
def on_preview_add_to_service(self):
"""
From the preview display request the Item to be added to service
"""
if self.service_item:
self.service_manager.add_service_item(self.service_item)
2018-02-03 11:32:49 +00:00
def on_preview_double_click(self):
"""
2018-02-03 11:32:49 +00:00
Triggered when a preview slide item is double clicked
"""
if self.service_item:
if Settings().value('advanced/double click live') and Settings().value('core/auto unblank'):
# Live and Preview have issues if we have video or presentations
# playing in both at the same time.
if self.service_item.is_command():
2016-05-20 16:22:06 +00:00
Registry().execute('{text}_stop'.format(text=self.service_item.name.lower()),
[self.service_item, self.is_live])
if self.service_item.is_media():
self.on_media_close()
self.on_go_live()
# If ('advanced/double click live') is not enabled, double clicking preview adds the item to Service.
2016-06-26 23:20:57 +00:00
# Prevent same item in preview from being sent to Service multiple times.
# Changing the preview slide resets this flag to False.
# Do note that this still allows to add item to Service multiple times if icon is clicked.
2016-07-14 18:13:58 +00:00
elif not Registry().get_flag('has doubleclick added item to service') is True:
self.on_preview_add_to_service()
Registry().set_flag('has doubleclick added item to service', True)
def on_go_live(self, field=None):
"""
2013-02-14 21:31:17 +00:00
If preview copy slide item to live controller from Preview Controller
"""
row = self.preview_widget.current_slide_number()
if -1 < row < self.preview_widget.slide_count():
if self.service_item.from_service:
self.service_manager.preview_live(self.service_item.unique_identifier, row)
else:
2013-06-16 07:54:16 +00:00
self.live_controller.add_service_manager_item(self.service_item, row)
2015-05-31 06:40:37 +00:00
self.live_controller.preview_widget.setFocus()
2017-11-09 19:42:30 +00:00
def on_go_preview(self, field=None):
"""
If live copy slide item to preview controller from live Controller
"""
row = self.preview_widget.current_slide_number()
if -1 < row < self.preview_widget.slide_count():
2017-11-09 19:55:01 +00:00
self.preview_controller.add_service_manager_item(self.service_item, row)
2017-11-09 19:42:30 +00:00
self.preview_controller.preview_widget.setFocus()
2013-06-16 07:54:16 +00:00
def on_media_start(self, item):
2010-04-29 20:56:27 +00:00
"""
Respond to the arrival of a media service item
2014-01-11 17:52:01 +00:00
:param item: The service item to be processed
2010-04-29 20:56:27 +00:00
"""
if self.is_live and self.hide_mode() == HideMode.Theme:
self.media_controller.video(self.controller_type, item, HideMode.Blank)
self.on_blank_display(True)
else:
self.media_controller.video(self.controller_type, item, self.hide_mode())
if not self.is_live:
self.preview_display.show()
2013-06-03 18:47:25 +00:00
self.slide_preview.hide()
2010-10-15 15:33:06 +00:00
2013-06-16 07:54:16 +00:00
def on_media_close(self):
"""
Respond to a request to close the Video
"""
self.media_controller.media_reset(self)
self.preview_display.hide()
2013-06-03 18:47:25 +00:00
self.slide_preview.show()
def _reset_blank(self, no_theme):
"""
Used by command items which provide their own displays to reset the
screen hide attributes
:param no_theme: Does the new item support theme-blanking.
"""
hide_mode = self.hide_mode()
if hide_mode == HideMode.Blank:
2013-06-16 07:54:16 +00:00
self.on_blank_display(True)
elif hide_mode == HideMode.Theme:
# The new item-type doesn't support theme-blanking, so 'switch' to normal blanking.
if no_theme:
self.on_blank_display(True)
else:
self.on_theme_display(True)
elif hide_mode == HideMode.Screen:
2013-06-16 07:54:16 +00:00
self.on_hide_display(True)
2011-04-14 21:34:01 +00:00
else:
2013-06-16 07:54:16 +00:00
self.hide_plugin(False)
def hide_mode(self):
"""
Determine what the hide mode should be according to the blank button
"""
if not self.is_live:
return None
elif self.blank_screen.isChecked():
return HideMode.Blank
elif self.theme_screen.isChecked():
return HideMode.Theme
elif self.desktop_screen.isChecked():
return HideMode.Screen
else:
2011-04-16 11:42:35 +00:00
return None
2013-06-16 07:54:16 +00:00
def on_next_track_clicked(self):
2013-02-01 21:34:23 +00:00
"""
Go to the next track when next is clicked
"""
self.display.audio_player.next()
def on_audio_time_remaining(self, time):
2013-02-01 21:34:23 +00:00
"""
Update how much time is remaining
2014-01-11 17:52:01 +00:00
2014-03-20 19:10:31 +00:00
:param time: the time remaining
2013-02-01 21:34:23 +00:00
"""
seconds = (self.display.audio_player.player.duration() - self.display.audio_player.player.position()) // 1000
minutes = seconds // 60
seconds %= 60
2013-08-31 18:17:38 +00:00
self.audio_time_label.setText(' %02d:%02d ' % (minutes, seconds))
2014-10-27 20:22:57 +00:00
def on_track_triggered(self, field=None):
2013-02-01 21:34:23 +00:00
"""
Start playing a track
"""
action = self.sender()
self.display.audio_player.go_to(action.data())
2013-01-22 21:09:43 +00:00
2013-12-19 20:17:06 +00:00
2017-10-23 22:09:57 +00:00
class PreviewController(RegistryBase, SlideController):
2013-12-19 20:17:06 +00:00
"""
Set up the Preview Controller.
2013-12-19 20:17:06 +00:00
"""
2015-11-07 00:49:40 +00:00
slidecontroller_preview_set = QtCore.pyqtSignal(list)
slidecontroller_preview_next = QtCore.pyqtSignal()
slidecontroller_preview_previous = QtCore.pyqtSignal()
2017-10-23 22:09:57 +00:00
def __init__(self, *args, **kwargs):
2013-12-19 20:17:06 +00:00
"""
Set up the base Controller as a preview.
2013-12-19 20:17:06 +00:00
"""
2017-10-23 22:09:57 +00:00
self.__registry_name = 'preview_slidecontroller'
super().__init__(*args, **kwargs)
2013-12-19 20:17:06 +00:00
self.split = 0
self.type_prefix = 'preview'
self.category = 'Preview Toolbar'
2013-12-19 20:17:06 +00:00
def bootstrap_post_set_up(self):
"""
process the bootstrap post setup request
"""
self.post_set_up()
2017-10-23 22:09:57 +00:00
class LiveController(RegistryBase, SlideController):
2013-12-19 20:17:06 +00:00
"""
Set up the Live Controller.
"""
2015-11-07 00:49:40 +00:00
slidecontroller_live_set = QtCore.pyqtSignal(list)
slidecontroller_live_next = QtCore.pyqtSignal()
slidecontroller_live_previous = QtCore.pyqtSignal()
slidecontroller_toggle_display = QtCore.pyqtSignal(str)
2016-08-08 21:01:09 +00:00
mediacontroller_live_play = QtCore.pyqtSignal()
mediacontroller_live_pause = QtCore.pyqtSignal()
mediacontroller_live_stop = QtCore.pyqtSignal()
2015-11-07 00:49:40 +00:00
2017-10-23 22:09:57 +00:00
def __init__(self, *args, **kwargs):
2013-12-19 20:17:06 +00:00
"""
Set up the base Controller as a live.
2013-12-19 20:17:06 +00:00
"""
2017-10-23 22:09:57 +00:00
super().__init__(*args, **kwargs)
2013-12-19 20:17:06 +00:00
self.is_live = True
self.split = 1
self.type_prefix = 'live'
self.keypress_queue = deque()
self.category = UiStrings().LiveToolbar
ActionList.get_instance().add_category(str(self.category), CategoryOrder.standard_toolbar)
def bootstrap_post_set_up(self):
"""
process the bootstrap post setup request
"""
2014-02-25 20:11:48 +00:00
self.post_set_up()