openlp/openlp/core/ui/slidecontroller.py

1662 lines
76 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
2019-04-13 13:00:22 +00:00
##########################################################################
# OpenLP - Open Source Lyrics Projection #
# ---------------------------------------------------------------------- #
2022-02-01 10:10:57 +00:00
# Copyright (c) 2008-2022 OpenLP Developers #
2019-04-13 13:00:22 +00:00
# ---------------------------------------------------------------------- #
# 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, either version 3 of the License, or #
# (at your option) any later version. #
# #
# 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, see <https://www.gnu.org/licenses/>. #
##########################################################################
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
2019-03-24 07:53:19 +00:00
import datetime
from collections import deque
from pathlib import Path
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
2020-09-23 08:37:57 +00:00
from openlp.core.state import State
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
2017-10-07 07:05:07 +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
from openlp.core.common.utils import wait_for
2017-10-10 07:08:44 +00:00
from openlp.core.display.screens import ScreenList
2018-10-02 04:39:42 +00:00
from openlp.core.display.window import DisplayWindow
from openlp.core.lib import ServiceItemAction, image_to_byte
2018-09-07 14:59:21 +00:00
from openlp.core.lib.serviceitem import ItemCapabilities
from openlp.core.ui.media import media_empty_song
2013-01-23 21:05:25 +00:00
from openlp.core.lib.ui import create_action
2018-10-02 04:39:42 +00:00
from openlp.core.ui import DisplayControllerType, HideMode
2018-04-10 21:10:09 +00:00
from openlp.core.ui.icons import UiIcons
from openlp.core.widgets.layouts import AspectRatioLayout
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.
2020-10-04 06:32:07 +00:00
HIDE_MENU_THRESHOLD = 80
2013-12-15 16:50:09 +00:00
NARROW_MENU = [
'hide_menu'
]
LOOP_LIST = [
'play_slides_menu',
'loop_separator',
'delay_spin_box'
]
WIDE_MENU = [
'show_screen_button',
2013-12-15 16:50:09 +00:00
'blank_screen_button',
'theme_screen_button',
'desktop_screen_button'
]
2014-01-04 11:50:27 +00:00
NON_TEXT_MENU = [
'show_screen_button',
2014-01-04 11:50:27 +00:00
'blank_screen_button',
'desktop_screen_button'
]
2019-03-24 07:53:19 +00:00
class MediaSlider(QtWidgets.QSlider):
"""
Allows the mouse events of a slider to be overridden and extra functionality added
"""
def __init__(self, direction, manager, controller):
"""
Constructor
"""
super(MediaSlider, self).__init__(direction)
self.manager = manager
self.controller = controller
def mouseMoveEvent(self, event):
"""
Override event to allow hover time to be displayed.
:param event: The triggering event
"""
time_value = QtWidgets.QStyle.sliderValueFromPosition(self.minimum(), self.maximum(), event.x(), self.width())
self.setToolTip('%s' % datetime.timedelta(seconds=int(time_value / 1000)))
QtWidgets.QSlider.mouseMoveEvent(self, event)
def mousePressEvent(self, event):
"""
Mouse Press event no new functionality
:param event: The triggering event
"""
QtWidgets.QSlider.mousePressEvent(self, event)
def mouseReleaseEvent(self, event):
"""
Set the slider position when the mouse is clicked and released on the slider.
:param event: The triggering event
"""
self.setValue(QtWidgets.QStyle.sliderValueFromPosition(self.minimum(), self.maximum(), event.x(), self.width()))
QtWidgets.QSlider.mouseReleaseEvent(self, event)
2015-11-07 00:49:40 +00:00
class InfoLabel(QtWidgets.QLabel):
"""
2019-04-21 08:20:43 +00:00
InfoLabel is a subclassed QLabel. Created to provide the ability 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())
painter.drawText(self.rect(), QtCore.Qt.AlignLeft, 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-11-04 05:34:10 +00:00
class SlideController(QtWidgets.QWidget, 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.
"""
2018-11-01 20:51:42 +00:00
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)
2017-10-10 21:15:08 +00:00
self.is_live = False
self.controller_type = None
self.displays = []
self.screens = ScreenList()
2017-10-10 21:15:08 +00:00
Registry().set_flag('has doubleclick added item to service', True)
Registry().set_flag('replace service manager item', False)
2013-12-19 20:17:06 +00:00
2022-01-16 13:15:09 +00:00
def initialise(self):
2013-12-19 20:17:06 +00:00
"""
Call by bootstrap functions
"""
2022-01-16 13:15:09 +00:00
self.setup_ui()
2017-10-10 21:15:08 +00:00
self.setup_displays()
2013-12-19 20:17:06 +00:00
self.screen_size_changed()
2022-01-16 13:15:09 +00:00
def post_set_up(self):
# Update the theme whenever the theme is changed (hot reload)
Registry().register_function('theme_update_list', self.on_theme_changed)
Registry().register_function('theme_level_changed', self.on_theme_changed)
Registry().register_function('theme_change_global', self.on_theme_changed)
Registry().register_function('theme_change_service', self.on_theme_changed)
2017-10-10 21:15:08 +00:00
def setup_displays(self):
"""
Set up the display
"""
if not self.is_live:
return
2017-10-10 21:15:08 +00:00
if self.displays:
# Delete any existing displays
2020-01-06 21:15:11 +00:00
for display in self.displays:
display.deregister_display()
display.setParent(None)
del display
self.displays = []
for screen in self.screens:
if screen.is_display:
display = DisplayWindow(self, screen)
self.displays.append(display)
2020-01-06 21:15:11 +00:00
self._reset_blank(False)
if self.display:
self.__add_actions_to_widget(self.display)
2017-10-10 21:15:08 +00:00
@property
def display(self):
return self.displays[0] if self.displays else None
2022-01-16 13:15:09 +00:00
def setup_ui(self):
2014-01-04 11:50:27 +00:00
"""
Initialise the UI elements of the controller
"""
2012-02-04 17:11:35 +00:00
try:
self.ratio = self.screens.current.display_geometry.width() / self.screens.current.display_geometry.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 = {}
2020-10-04 19:20:10 +00:00
self.slide_count = 0
2020-10-04 06:32:07 +00:00
self.ignore_toolbar_resize_events = False
# 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 with icon
self.top_label_horizontal = QtWidgets.QHBoxLayout()
self.panel_layout.addLayout(self.top_label_horizontal)
self.top_label_vertical = QtWidgets.QVBoxLayout()
if self.is_live:
icon = UiIcons().live
else:
icon = UiIcons().preview
pixmap = icon.pixmap(QtCore.QSize(34, 34))
self.top_icon = QtWidgets.QLabel()
self.top_icon.setPixmap(pixmap)
self.top_icon.setStyleSheet("padding: 0 10 0 25px;")
self.top_icon.setAlignment(QtCore.Qt.AlignRight)
self.top_label_horizontal.addWidget(self.top_icon, 1)
self.top_label_horizontal.addLayout(self.top_label_vertical, 100)
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;')
if self.is_live:
self.type_label.setText(UiStrings().Live)
else:
self.type_label.setText(UiStrings().Preview)
# 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.top_label_vertical.addWidget(self.type_label)
self.top_label_vertical.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'),
icon=UiIcons().arrow_up,
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'),
icon=UiIcons().arrow_down,
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
self._current_hide_mode = None
if self.is_live:
self.controller_type = DisplayControllerType.Live
self.slide_changed_time = datetime.datetime.now()
self.fetching_screenshot = False
self.screen_capture = None
# 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)
# The order of the blank to modes in Shortcuts list comes from here.
self.show_screen = create_action(self, 'showScreen',
text=translate('OpenLP.SlideController', 'Show Presentation'),
icon=UiIcons().live_presentation,
checked=False, can_shortcuts=True, category=self.category,
triggers=self.on_show_display)
2020-06-10 20:10:56 +00:00
self.theme_screen = create_action(self, 'themeScreen',
text=translate('OpenLP.SlideController', 'Show Theme'),
icon=UiIcons().live_theme,
2020-06-10 20:10:56 +00:00
checked=False, can_shortcuts=True, category=self.category,
2021-08-28 20:36:53 +00:00
triggers=self.on_toggle_theme)
2020-06-10 20:10:56 +00:00
self.blank_screen = create_action(self, 'blankScreen',
text=translate('OpenLP.SlideController', 'Show Black'),
icon=UiIcons().live_black,
2020-06-10 20:10:56 +00:00
checked=False, can_shortcuts=True, category=self.category,
2021-08-28 20:36:53 +00:00
triggers=self.on_toggle_blank)
2020-06-10 20:10:56 +00:00
self.desktop_screen = create_action(self, 'desktopScreen',
text=translate('OpenLP.SlideController', 'Show Desktop'),
icon=UiIcons().live_desktop,
2020-06-10 20:10:56 +00:00
checked=False, can_shortcuts=True, category=self.category,
2021-08-28 20:36:53 +00:00
triggers=self.on_toggle_desktop)
self.hide_menu.setDefaultAction(self.show_screen)
self.hide_menu.menu().addAction(self.show_screen)
self.hide_menu.menu().addAction(self.theme_screen)
self.hide_menu.menu().addAction(self.blank_screen)
self.hide_menu.menu().addAction(self.desktop_screen)
2013-01-15 23:59:04 +00:00
# Wide menu of display control buttons.
self.show_screen_button = QtWidgets.QToolButton(self.toolbar)
self.show_screen_button.setObjectName('show_screen_button')
self.toolbar.add_toolbar_widget(self.show_screen_button)
self.show_screen_button.setDefaultAction(self.show_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)
self.blank_screen_button = QtWidgets.QToolButton(self.toolbar)
self.blank_screen_button.setObjectName('blank_screen_button')
self.toolbar.add_toolbar_widget(self.blank_screen_button)
self.blank_screen_button.setDefaultAction(self.blank_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,
icon=UiIcons().loop, 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,
icon=UiIcons().play_slides, checked=False, can_shortcuts=True,
2013-12-15 16:50:09 +00:00
category=self.category, triggers=self.on_play_slides_once)
2020-06-06 16:05:36 +00:00
if self.settings.value('advanced/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)
2019-03-24 07:53:19 +00:00
# Build a Media ToolBar
self.mediabar = OpenLPToolbar(self)
self.mediabar.add_toolbar_action('playbackPlay', text='media_playback_play',
icon=UiIcons().play,
tooltip=translate('OpenLP.SlideController', 'Start playing media.'),
triggers=self.send_to_plugins)
self.mediabar.add_toolbar_action('playbackPause', text='media_playback_pause',
icon=UiIcons().pause,
tooltip=translate('OpenLP.SlideController', 'Pause playing media.'),
triggers=self.send_to_plugins)
self.mediabar.add_toolbar_action('playbackStop', text='media_playback_stop',
icon=UiIcons().stop,
tooltip=translate('OpenLP.SlideController', 'Stop playing media.'),
triggers=self.send_to_plugins)
self.mediabar.add_toolbar_action('playbackLoop', text='media_playback_loop',
icon=UiIcons().repeat, checked=False,
tooltip=translate('OpenLP.SlideController', 'Loop playing media.'),
triggers=self.send_to_plugins)
self.position_label = QtWidgets.QLabel()
self.position_label.setText(' 00:00 / 00:00')
self.position_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.position_label.setToolTip(translate('OpenLP.SlideController', 'Video timer.'))
self.position_label.setMinimumSize(90, 0)
self.position_label.setObjectName('position_label')
self.mediabar.add_toolbar_widget(self.position_label)
2019-04-21 08:20:43 +00:00
# Build the media seek_slider.
2019-03-24 07:53:19 +00:00
self.seek_slider = MediaSlider(QtCore.Qt.Horizontal, self, self)
self.seek_slider.setMaximum(1000)
self.seek_slider.setTracking(True)
self.seek_slider.setMouseTracking(True)
self.seek_slider.setToolTip(translate('OpenLP.SlideController', 'Video position.'))
self.seek_slider.setGeometry(QtCore.QRect(90, 260, 221, 24))
self.seek_slider.setObjectName('seek_slider')
self.mediabar.add_toolbar_widget(self.seek_slider)
# Build the volume_slider.
self.volume_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
self.volume_slider.setTickInterval(10)
self.volume_slider.setTickPosition(QtWidgets.QSlider.TicksAbove)
self.volume_slider.setMinimum(0)
self.volume_slider.setMaximum(100)
self.volume_slider.setTracking(True)
self.volume_slider.setToolTip(translate('OpenLP.SlideController', 'Audio Volume.'))
self.volume_slider.setGeometry(QtCore.QRect(90, 160, 221, 24))
self.volume_slider.setObjectName('volume_slider')
self.mediabar.add_toolbar_widget(self.volume_slider)
self.controller_layout.addWidget(self.mediabar)
self.mediabar.setVisible(False)
# Signals
self.seek_slider.valueChanged.connect(self.send_to_plugins)
self.volume_slider.valueChanged.connect(self.send_to_plugins)
if self.is_live:
2020-04-26 21:37:35 +00:00
self.new_song_menu()
2013-03-07 06:48:09 +00:00
self.toolbar.add_toolbar_widget(self.song_menu)
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, int(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,
2018-11-01 20:51:42 +00:00
QtWidgets.QSizePolicy.Label))
2015-11-07 00:49:40 +00:00
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')
self.slide_layout = AspectRatioLayout(self.preview_frame, self.ratio)
self.slide_layout.margin = 8
self.slide_layout.setSpacing(0)
2013-08-31 18:17:38 +00:00
self.slide_layout.setObjectName('SlideLayout')
2017-10-10 21:15:08 +00:00
# Set up the preview display
self.preview_display = DisplayWindow(self)
self.slide_layout.addWidget(self.preview_display)
2018-03-25 07:14:38 +00:00
self.slide_layout.resize.connect(self.on_preview_resize)
# Actual preview screen
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,
2018-11-01 20:51:42 +00:00
'text': translate('OpenLP.SlideController', 'Go to "Pre-Chorus"')},
2013-08-31 18:17:38 +00:00
{'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:
2020-10-04 06:32:07 +00:00
self.preview_widget.resize_event.connect(self.on_controller_size_changed)
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_blank_display)
2016-05-20 16:22:06 +00:00
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)
2017-11-04 05:34:10 +00:00
# NOTE: {} used to keep line length < maxline
2016-05-20 16:22:06 +00:00
getattr(self,
2017-11-04 05:34:10 +00:00
'slidecontroller_{}_previous'.format(self.type_prefix)).connect(self.on_slide_selected_previous)
2016-08-08 21:01:09 +00:00
if self.is_live:
getattr(self, 'slidecontroller_live_clear').connect(self.on_clear)
2017-10-10 21:15:08 +00:00
self.mediacontroller_live_play.connect(self.media_controller.on_media_play)
self.mediacontroller_live_pause.connect(self.media_controller.on_media_pause)
self.mediacontroller_live_stop.connect(self.media_controller.on_media_stop)
else:
getattr(self, 'slidecontroller_preview_clear').connect(self.on_clear)
2009-11-08 17:02:46 +00:00
2020-04-26 21:37:35 +00:00
def new_song_menu(self):
"""
Rebuild the song menu object from scratch.
"""
# Build the Song Toolbar
self.song_menu = QtWidgets.QToolButton(self.toolbar)
self.song_menu.setObjectName('song_menu')
self.song_menu.setText(translate('OpenLP.SlideController', 'Go To'))
self.song_menu.setPopupMode(QtWidgets.QToolButton.InstantPopup)
self.song_menu.setMenu(QtWidgets.QMenu(translate('OpenLP.SlideController', 'Go To'), self.toolbar))
def _raise_displays(self):
for display in self.displays:
display.raise_()
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
2018-11-01 20:51:42 +00:00
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 = ''
2017-10-10 21:15:08 +00:00
def send_to_plugins(self, *args):
"""
This is the generic function to send signal for control widgets, created from within other plugins
This function is needed to catch the current controller
:param args: Arguments to send to the plugins
"""
sender = self.sender().objectName() if self.sender().objectName() else self.sender().text()
controller = self
Registry().execute('{text}'.format(text=sender), [controller, args])
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
"""
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':
self.set_hide_mode(HideMode.Blank)
2013-08-31 18:17:38 +00:00
elif action == 'theme':
self.set_hide_mode(HideMode.Theme)
2013-08-31 18:17:38 +00:00
elif action == 'desktop':
self.set_hide_mode(HideMode.Screen)
2013-08-31 18:17:38 +00:00
elif action == 'show':
self.set_hide_mode(None)
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.
"""
size = self.screens.current.display_geometry.size()
if self.is_live and self.displays:
2017-10-10 21:15:08 +00:00
for display in self.displays:
display.resize(size)
old_preview_width = self.preview_display.size().width()
scale = old_preview_width / size.width()
new_preview_size = size * scale
self.ratio = self.screens.current.display_geometry.width() / self.screens.current.display_geometry.height()
self.preview_display.resize(new_preview_size)
self.slide_layout.set_aspect_ratio(self.ratio)
2020-10-04 06:32:07 +00:00
self.on_controller_size_changed()
2019-01-27 14:42:23 +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,
self.show_screen,
self.desktop_screen,
self.theme_screen,
self.blank_screen])
2011-02-04 03:31:06 +00:00
2020-10-04 06:32:07 +00:00
def on_controller_size_changed(self, event=None):
"""
2020-10-04 06:32:07 +00:00
Change layout of display control buttons when the controller size changes
"""
2020-10-04 06:32:07 +00:00
self.log_debug('on_controller_size_changed is_event:{}'.format(event is not None))
if self.is_live and self.ignore_toolbar_resize_events is False:
spare_space = self.controller.width() - self.toolbar.size().width()
if spare_space <= 0 and not self.hide_menu.isVisible():
self.set_hide_mode_menu(narrow=True)
# Add threshold to prevent flickering.
elif spare_space > HIDE_MENU_THRESHOLD and self.hide_menu.isVisible() \
or spare_space > 0 and not self.hide_menu.isVisible():
self.set_hide_mode_menu(narrow=False)
def set_hide_mode_menu(self, narrow):
2014-01-04 11:50:27 +00:00
"""
2020-10-04 06:32:07 +00:00
Set the wide or narrow hide mode menu
:param compact: Use the narrow menu?
"""
self.toolbar.set_widget_visible(NARROW_MENU, narrow)
2014-01-04 11:50:27 +00:00
self.toolbar.set_widget_visible(WIDE_MENU, False)
if self.service_item and self.service_item.is_text():
2020-10-04 06:32:07 +00:00
self.toolbar.set_widget_visible(WIDE_MENU, not narrow)
2014-01-04 11:50:27 +00:00
else:
2020-10-04 06:32:07 +00:00
self.toolbar.set_widget_visible(NON_TEXT_MENU, not narrow)
2014-01-04 11:50:27 +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.
"""
self.delay_spin_box.setValue(self.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.
"""
2020-06-06 16:05:36 +00:00
self.slide_limits = self.settings.value('advanced/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)
2020-10-04 06:32:07 +00:00
# Set to narrow menu so we don't push the panel wider than it needs to be when adding new buttons
self.set_hide_mode_menu(narrow=True)
# Reset the button
self.play_slides_once.setChecked(False)
self.play_slides_once.setIcon(UiIcons().play_slides)
2016-09-30 01:06:51 +00:00
self.play_slides_once.setText(UiStrings().PlaySlidesToEnd)
self.play_slides_loop.setChecked(False)
self.play_slides_loop.setIcon(UiIcons().loop)
2016-09-30 01:06:51 +00:00
self.play_slides_loop.setText(UiStrings().PlaySlidesInLoop)
if item.is_text():
2020-06-06 16:05:36 +00:00
if (self.settings.value('songs/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)
if item.is_capable(ItemCapabilities.CanLoop) and len(item.slides) > 1:
2013-12-15 16:50:09 +00:00
self.toolbar.set_widget_visible(LOOP_LIST)
2018-12-01 12:19:01 +00:00
if item.is_media() or item.is_capable(ItemCapabilities.HasBackgroundAudio):
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 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
"""
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.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()
def set_background_image(self, image_path):
"""
Reload the theme on displays.
"""
# Set theme for preview
self.preview_display.set_background_image(image_path)
# Set theme for displays
for display in self.displays:
display.set_background_image(image_path)
2022-01-16 13:15:09 +00:00
def on_theme_changed(self, var=None):
"""
Reloads the service item
:param var: Unused but needed to catch the theme_update_list event
"""
if self.service_item and self.settings.value('themes/hot reload'):
slide_num = self.preview_widget.current_slide_number()
self._process_item(self.service_item, slide_num)
def reload_theme(self):
"""
Reload the theme on displays.
"""
# Set theme for preview
self.preview_display.reload_theme()
# Set theme for displays
for display in self.displays:
display.reload_theme()
def _set_theme(self, service_item):
"""
Set up the theme from the service item.
:param service_item: The current service item
"""
# Get theme
theme_data = service_item.get_theme_data()
# Set theme for preview
self.preview_display.set_theme(theme_data, service_item_type=service_item.service_item_type)
# Set theme for displays
for display in self.displays:
display.set_theme(theme_data, service_item_type=service_item.service_item_type)
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
"""
2020-10-04 06:32:07 +00:00
self.log_debug('_process_item start')
2013-02-07 11:33:47 +00:00
self.on_stop_loop()
2020-10-04 06:32:07 +00:00
self.ignore_toolbar_resize_events = True
old_item = self.service_item
# 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.is_live:
# Reset screen capture before any api call can arrive
self.screen_capture = None
# If item transitions are on, make sure the delay is longer than the animation
self.slide_changed_time = datetime.datetime.now()
if self.settings.value('themes/item transitions') and self.service_item.get_transition_delay() < 1:
self.slide_changed_time += datetime.timedelta(seconds=0.5)
2020-06-10 20:10:56 +00:00
if self.service_item.is_command() and not self.service_item.is_media():
Registry().execute(
'{text}_start'.format(text=self.service_item.name.lower()),
2021-08-28 20:36:53 +00:00
[self.service_item, self.is_live, self._current_hide_mode, slide_no])
2018-09-10 20:14:13 +00:00
else:
self._set_theme(self.service_item)
self.info_label.setText(self.service_item.title)
2013-12-15 16:50:09 +00:00
self.slide_list = {}
2021-11-07 00:52:53 +00:00
# if the old item was text or images (ie doesn't provide its own display) and the new item provides its own
# display then clear out the old item so that it doesn't flash momentarily when next showing text/image
# An item provides its own display if the capability ProvidesOwnDisplay is set or if it's a media item
old_item_provides_own_display = old_item and (old_item.is_capable(ItemCapabilities.ProvidesOwnDisplay) or
old_item.is_media())
new_item_provides_own_display = (self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay) or
self.service_item.is_media())
if self.is_live and not old_item_provides_own_display and new_item_provides_own_display:
for display in self.displays:
display.finish_with_current_item()
# Prepare the new slides for text / image items
row = 0
2013-07-05 18:39:31 +00:00
width = self.main_window.control_splitter.sizes()[self.split]
if self.service_item.is_text():
self.preview_display.load_verses(self.service_item.rendered_slides)
self.preview_display.show()
2018-09-10 20:14:13 +00:00
for display in self.displays:
display.load_verses(self.service_item.rendered_slides)
2020-04-26 21:37:35 +00:00
# Replace the song menu so the verses match the song and are not cumulative
if self.is_live:
self.toolbar.remove_widget('song_menu')
self.new_song_menu()
self.toolbar.add_toolbar_widget(self.song_menu)
for slide_index, slide in enumerate(self.service_item.display_slides):
if not slide['verse'].isdigit():
2011-02-17 19:46:01 +00:00
# These tags are already translated.
verse_def = slide['verse']
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:
self.slide_list[verse_def] = slide_index
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:
if self.service_item.is_image():
self.preview_display.load_images(self.service_item.slides)
2018-09-10 20:14:13 +00:00
for display in self.displays:
display.load_images(self.service_item.slides)
2020-06-10 20:10:56 +00:00
for _, _ in enumerate(self.service_item.slides):
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
self.preview_widget.replace_service_item(self.service_item, width, slide_no)
2021-11-07 00:52:53 +00:00
# Tidy up aspects associated with the old item
if old_item:
# Close the old item if it's not to be used by the new service item
if not self.service_item.is_media() and not self.service_item.requires_media():
self.on_media_close()
if old_item.is_command() and not old_item.is_media():
Registry().execute('{name}_stop'.format(name=old_item.name.lower()), [old_item, self.is_live])
# if the old item was media which hid the main display then need to reset it if new service item uses it
if self.is_live and self._current_hide_mode is None and old_item.is_media() and not \
old_item.requires_media() and not self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay):
for display in self.displays:
display.show_display()
self.enable_tool_bar(self.service_item)
2021-11-07 00:52:53 +00:00
# Reset blanking if needed
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))
if self.service_item.is_media() or self.service_item.requires_media():
self._set_theme(self.service_item)
if self.service_item.is_command():
self.preview_display.load_verses(media_empty_song, True)
self.on_media_start(self.service_item)
# Try to get display back on top of media window asap. If the media window
# is not loaded by the time _raise_displays is run, lyrics (web display)
# will be under the media window (not good).
2021-09-12 08:28:58 +00:00
self._raise_displays()
QtCore.QTimer.singleShot(100, self._raise_displays)
QtCore.QTimer.singleShot(500, self._raise_displays)
QtCore.QTimer.singleShot(1000, self._raise_displays)
2013-06-16 07:54:16 +00:00
self.slide_selected(True)
if self.service_item.from_service:
self.preview_widget.setFocus()
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])
2020-10-04 06:32:07 +00:00
# Need to process events four times to get correct controller width
for _ in range(4):
self.application.process_events()
self.ignore_toolbar_resize_events = False
self.on_controller_size_changed()
2021-01-30 05:59:56 +00:00
if self.settings.value('core/auto unblank'):
self.set_hide_mode(None)
2020-10-04 06:32:07 +00:00
self.log_debug('_process_item end')
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
: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()
2018-03-25 07:14:38 +00:00
def on_song_bar_handler(self):
"""
Some song handler
"""
request = self.sender().text()
slide_no = self.slide_list[request]
width = self.main_window.control_splitter.sizes()[self.split]
self.preview_widget.replace_service_item(self.service_item, width, slide_no)
self.slide_selected()
def on_preview_resize(self, size):
"""
Set the preview display's zoom factor based on the size relative to the display size
"""
2018-09-10 20:14:13 +00:00
display_with = 0
for screen in self.screens:
if screen.is_display:
display_with = screen.display_geometry.width()
2018-09-10 20:14:13 +00:00
if display_with == 0:
ratio = 0.25
else:
ratio = float(size.width()) / display_with
self.preview_display.set_scale(ratio)
2018-03-25 07:14:38 +00:00
def on_slide_unblank(self):
"""
Handle the slidecontroller unblank event.
"""
if not Registry().get_flag('replace service manager item') is True:
self.set_hide_mode(None)
def on_show_display(self, checked=None):
"""
Handle the blank screen button actions
:param checked: the new state of the of the widget
"""
self.set_hide_mode(None)
2021-08-28 20:36:53 +00:00
def on_toggle_blank(self, checked=None):
"""
Toggle the blank screen
"""
2021-08-28 20:36:53 +00:00
if self._current_hide_mode == HideMode.Blank:
self.set_hide_mode(None)
else:
self.set_hide_mode(HideMode.Blank)
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
"""
self.set_hide_mode(HideMode.Blank)
2021-08-28 20:36:53 +00:00
def on_toggle_theme(self, checked=None):
"""
Toggle the Theme screen
"""
2021-08-28 20:36:53 +00:00
if self._current_hide_mode == HideMode.Theme:
self.set_hide_mode(None)
2010-04-29 19:33:45 +00:00
else:
self.set_hide_mode(HideMode.Theme)
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
"""
self.set_hide_mode(HideMode.Theme)
2021-08-28 20:36:53 +00:00
def on_toggle_desktop(self, checked=None):
"""
Toggle the desktop
"""
2021-08-28 20:36:53 +00:00
if self._current_hide_mode == HideMode.Screen:
self.set_hide_mode(None)
else:
self.set_hide_mode(HideMode.Screen)
def on_hide_display(self, checked=None):
2016-09-02 15:22:29 +00:00
"""
Handle the Hide screen button
This enables the desktop screen.
2016-09-02 15:52:44 +00:00
:param checked: the new state of the of the widget
2016-09-02 15:22:29 +00:00
"""
self.set_hide_mode(HideMode.Screen)
2016-09-02 15:22:29 +00:00
def set_hide_mode(self, hide_mode):
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
"""
self.log_debug('set_hide_mode {text}'.format(text=hide_mode))
2021-08-28 20:36:53 +00:00
self._current_hide_mode = hide_mode
# Update ui buttons
if hide_mode is None:
self.hide_menu.setDefaultAction(self.blank_screen)
2020-06-06 16:05:36 +00:00
self.settings.setValue('core/screen blank', False)
else:
self.hide_menu.setDefaultAction(self.show_screen)
2020-06-06 16:05:36 +00:00
self.settings.setValue('core/screen blank', True)
self.show_screen.setChecked(hide_mode is None)
self.blank_screen.setChecked(hide_mode == HideMode.Blank)
self.theme_screen.setChecked(hide_mode == HideMode.Theme)
self.desktop_screen.setChecked(hide_mode == HideMode.Screen)
# Update plugin display
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')
# Update preview and loop state
self.update_preview()
self.on_toggle_loop()
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
2019-09-18 21:35:47 +00:00
if not self.slide_selected_lock.acquire(start, timeout):
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 self.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:
for display in self.displays:
display.go_to_slide(row)
if not self.service_item.is_text():
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)
# TODO: 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
"""
self.log_debug('update_preview {text}'.format(text=self.screens.current))
if self.is_live:
self.screen_capture = None
self.slide_changed_time = max(self.slide_changed_time, datetime.datetime.now())
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.display_maindisplay)
# but take another in a couple of seconds in case slide change is slow
QtCore.QTimer.singleShot(2500, self.display_maindisplay)
else:
# If not live, use the slide's thumbnail/icon instead
2018-11-13 21:00:14 +00:00
image_path = Path(self.service_item.get_rendered_frame(self.selected_row))
self.screen_capture = image_path
self.preview_display.set_single_image('#000', image_path)
else:
self.preview_display.go_to_slide(self.selected_row)
2020-10-04 19:20:10 +00:00
self.output_has_changed()
def output_has_changed(self):
"""
The output has changed so we need to poke to POLL Websocket.
"""
self.slide_count += 1
def display_maindisplay(self):
"""
Gets an image of the display screen and updates the preview frame.
"""
display_image = self._capture_maindisplay()
base64_image = image_to_byte(display_image)
self.screen_capture = base64_image
self.preview_display.set_single_image_data('#000', base64_image)
def _capture_maindisplay(self):
2011-01-22 19:32:02 +00:00
"""
Creates an image of the current screen.
2011-01-22 19:32:02 +00:00
"""
self.log_debug('_capture_maindisplay {text}'.format(text=self.screens.current))
2015-11-07 00:49:40 +00:00
win_id = QtWidgets.QApplication.desktop().winId()
screen = QtWidgets.QApplication.primaryScreen()
rect = ScreenList().current.display_geometry
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.preview_display.devicePixelRatio())
return win_image
2009-08-23 20:15:05 +00:00
def is_slide_loaded(self):
"""
Returns a boolean as to whether the slide should be fully visible.
Takes transition time into consideration.
"""
slide_delay_time = 1
if self.service_item:
slide_delay_time = self.service_item.get_transition_delay()
slide_ready_time = self.slide_changed_time + datetime.timedelta(seconds=slide_delay_time)
return datetime.datetime.now() > slide_ready_time
def grab_maindisplay(self):
"""
Gets the last taken screenshot
"""
wait_for(lambda: not self.fetching_screenshot)
if self.screen_capture is None:
self.fetching_screenshot = True
wait_for(self.is_slide_loaded)
self.screen_capture = image_to_byte(self._capture_maindisplay())
self.fetching_screenshot = False
return self.screen_capture
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():
past_end = Registry().execute('{text}_next'.format(text=self.service_item.name.lower()),
[self.service_item, self.is_live])
# Check if we have gone past the end of the last slide
if self.is_live and past_end and past_end[0]:
if wrap is None:
if self.slide_limits == SlideLimits.Wrap:
self.on_slide_selected_index([0])
elif self.is_live and self.slide_limits == SlideLimits.Next:
self.service_next()
elif wrap:
self.on_slide_selected_index([0])
2017-09-29 19:10:31 +00:00
elif self.is_live:
2014-01-11 17:52:01 +00:00
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():
before_start = Registry().execute('{text}_previous'.format(text=self.service_item.name.lower()),
[self.service_item, self.is_live])
# Check id we have tried to go before that start slide
if self.is_live and before_start and before_start[0]:
if self.slide_limits == SlideLimits.Wrap:
self.on_slide_selected_index([self.preview_widget.slide_count() - 1])
elif self.is_live and self.slide_limits == SlideLimits.Next:
self.keypress_queue.append(ServiceItemAction.PreviousLastSlide)
self._process_queue()
elif self.is_live:
2014-01-11 17:52:01 +00:00
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.
"""
2021-08-28 20:36:53 +00:00
if self._current_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)
self.play_slides_once.setIcon(UiIcons().play_slides)
self.play_slides_once.setText(UiStrings().PlaySlidesToEnd)
self.play_slides_menu.setDefaultAction(self.play_slides_loop)
self.play_slides_once.setChecked(False)
if self.settings.value('core/click live slide to unblank'):
Registry().execute('slidecontroller_live_unblank')
else:
self.play_slides_loop.setIcon(UiIcons().loop)
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)
self.play_slides_loop.setIcon(UiIcons().loop)
self.play_slides_loop.setText(UiStrings().PlaySlidesInLoop)
self.play_slides_menu.setDefaultAction(self.play_slides_once)
self.play_slides_loop.setChecked(False)
if self.settings.value('core/click live slide to unblank'):
Registry().execute('slidecontroller_live_unblank')
else:
self.play_slides_once.setIcon(UiIcons().play_slides)
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
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)
2009-10-24 13:07:41 +00:00
2018-02-23 08:56:01 +00:00
def on_clear(self):
"""
2020-10-04 19:20:10 +00:00
Clear the preview bar and other bits.
2018-02-23 08:56:01 +00:00
"""
2018-02-23 09:11:07 +00:00
self.preview_widget.clear_list()
2020-10-04 19:20:10 +00:00
self.service_item = None
if self.is_live:
self.mediabar.setVisible(False)
else:
self.toolbar.set_widget_visible('editSong', False)
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:
2021-01-30 05:59:56 +00:00
if self.settings.value('advanced/double click live'):
# 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()
2013-06-16 07:54:16 +00:00
def on_media_start(self, item):
2010-04-29 20:56:27 +00:00
"""
2020-09-23 08:37:57 +00:00
Respond to the arrival of a media service item but only run if we have media
2014-01-11 17:52:01 +00:00
:param item: The service item to be processed
2010-04-29 20:56:27 +00:00
"""
2020-09-23 08:37:57 +00:00
if State().check_preconditions('media'):
if self.is_live and not item.is_media() and item.requires_media():
2021-08-28 20:36:53 +00:00
self.media_controller.load_video(self.controller_type, item, self._current_hide_mode)
2021-09-12 08:28:58 +00:00
elif self.is_live:
if self._current_hide_mode == HideMode.Theme:
self.set_hide_mode(HideMode.Blank)
2021-08-28 20:36:53 +00:00
self.media_controller.load_video(self.controller_type, item, self._current_hide_mode)
2021-09-12 08:28:58 +00:00
elif item.is_media():
# avoid loading the video if this is preview and the media is background
self.media_controller.load_video(self.controller_type, item)
2020-09-23 08:37:57 +00:00
if not self.is_live:
self.preview_display.show()
2010-10-15 15:33:06 +00:00
2013-06-16 07:54:16 +00:00
def on_media_close(self):
"""
2020-09-23 08:37:57 +00:00
Respond to a request to close the Video if we have media
"""
2020-09-23 08:37:57 +00:00
if State().check_preconditions('media'):
self.media_controller.media_reset(self, delayed=True)
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.
"""
2021-08-28 20:36:53 +00:00
if self._current_hide_mode == HideMode.Theme and no_theme:
# The new item-type doesn't support theme-blanking, so 'switch' to normal blanking.
self.set_hide_mode(HideMode.Blank)
2011-04-14 21:34:01 +00:00
else:
2021-08-28 20:36:53 +00:00
self.set_hide_mode(self._current_hide_mode)
def get_hide_mode(self):
"""
Determine what the hide mode should be according to the blank button
"""
if not self.is_live:
return None
else:
2021-08-28 20:36:53 +00:00
return self._current_hide_mode
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()
slidecontroller_preview_clear = 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 preview.
2013-12-19 20:17:06 +00:00
"""
self.__registry_name = 'preview_controller'
2017-10-23 22:09:57 +00:00
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
2019-03-24 07:53:19 +00:00
def bootstrap_initialise(self):
2022-01-16 13:15:09 +00:00
"""
process the bootstrap initialise request
"""
self.initialise()
def bootstrap_post_set_up(self):
2013-12-19 20:17:06 +00:00
"""
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)
slidecontroller_live_clear = QtCore.pyqtSignal()
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
"""
self.__registry_name = 'live_controller'
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)
2019-03-24 07:53:19 +00:00
def bootstrap_initialise(self):
2022-01-16 13:15:09 +00:00
"""
process the bootstrap initialise request
"""
self.initialise()
def bootstrap_post_set_up(self):
2013-12-19 20:17:06 +00:00
"""
process the bootstrap post setup request
"""
2014-02-25 20:11:48 +00:00
self.post_set_up()