Exemple #1
0
    def on_activate(self, mi):
        obj = self.model().object_data[mi.row()]
        can_mcr = has_right("mcr", self.id_channel)
        if obj.object_type == "item":

            if obj.id:
                if obj["item_role"] == "placeholder":
                    self.on_edit_item()

                elif self.parent().mcr and self.parent().mcr.isVisible(
                ) and can_mcr:
                    response = api.playout(
                        timeout=1,
                        action="cue",
                        id_channel=self.id_channel,
                        id_item=obj.id,
                    )
                    if not response:
                        logging.error(response.message)
                    self.clearSelection()

        # Event edit
        elif obj.object_type == "event" and (
                has_right("scheduler_view", self.id_channel)
                or has_right("scheduler_edit", self.id_channel)):
            self.on_edit_event()
        self.clearSelection()
    def on_delete_event(self):
        if not self.calendar.selected_event:
            return
        cursor_event = self.calendar.selected_event
        if not has_right("scheduler_edit", self.id_channel):
            logging.error(
                "You are not allowed to modify schedule of this channel.")
            return

        ret = QMessageBox.question(
            self,
            "Delete event",
            f"Do you really want to delete {cursor_event}?"
            "\nThis operation cannot be undone.",
            QMessageBox.Yes | QMessageBox.No,
        )
        if ret == QMessageBox.Yes:
            QApplication.processEvents()
            self.calendar.setCursor(Qt.WaitCursor)
            response = api.schedule(
                id_channel=self.id_channel,
                start_time=self.calendar.week_start_time,
                end_time=self.calendar.week_end_time,
                delete=[cursor_event.id],
            )
            self.calendar.setCursor(Qt.ArrowCursor)
            if response:
                logging.info(f"{cursor_event} deleted")
                self.calendar.set_data(response.data)
            else:
                logging.error(response.message)
                self.calendar.load()
    def on_channel_changed(self):
        if hasattr(self, "id_channel"):
            can_mcr = has_right("mcr", self.id_channel)
            self.btn_take.setEnabled(can_mcr)
            self.btn_freeze.setEnabled(can_mcr)
            self.btn_retake.setEnabled(can_mcr)
            self.btn_abort.setEnabled(can_mcr)
            self.btn_loop.setEnabled(can_mcr)
            self.btn_cue_backward.setEnabled(can_mcr)
            self.btn_cue_forward.setEnabled(can_mcr)

            if hasattr(self, "plugins"):
                self.plugins.load()

        self.pos = 0
        self.dur = 0
        self.current = "(loading)"
        self.cued = "(loading)"
        self.request_time = 0
        self.paused = False
        self.cueing = False
        self.local_request_time = time.time()
        self.updating = False
        self.request_display_resize = False
        self.first_update = True
        self.fps = 25.0
        self.parent().setWindowTitle("On air ctrl")

        self.progress_bar.setValue(0)
        self.progress_bar.setMaximum(0)
        self.display_current.set_text(
            f"<font color='yellow'>{self.current}</font>")
        self.display_cued.set_text(f"<font color='yellow'>{self.cued}</font>")
 def dragTargetChanged(self, evt):
     if not has_right("scheduler_edit", self.calendar.id_channel):
         return
     if type(evt) == SchedulerDayWidget:
         self.drag_outside = False
     else:
         self.drag_outside = True
         self.calendar.drag_source.update()
Exemple #5
0
    def on_channel_changed(self):
        self.load(do_update_header=True)
        self.plugins.load()

        if self.mcr:
            self.mcr.on_channel_changed()

        can_rundown_edit = has_right("rundown_edit", self.id_channel)
        self.main_window.action_rundown_edit.setEnabled(can_rundown_edit)
        self.toggle_rundown_edit(can_rundown_edit and self.edit_wanted)
Exemple #6
0
    def focus(self, asset, silent=False, force=False):
        if not isinstance(asset, Asset):
            return

        logging.debug(f"[DETAIL] Focusing {asset}")

        if self._is_loading:
            self._load_queue = [asset]
            return
        else:
            self._load_queue = False
            self._is_loading = True

        if not silent:
            self.check_changed()

        #
        # Show data
        #

        self.folder_select.setEnabled(True)

        self.asset = Asset(meta=asset.meta)  # asset deep copy
        self.parent().setWindowTitle(f"Detail of {self.asset}")
        self.detail_tabs.load(self.asset, force=force)
        self.folder_select.set_value(self.asset["id_folder"])

        self.duration.fps = self.asset.fps
        self.duration.set_value(self.asset.duration)
        self.duration.show()
        if (self.asset["status"]
                == ObjectStatus.OFFLINE) or (not self.asset.id):
            self.duration.setEnabled(True)
        else:
            self.duration.setEnabled(False)

        enabled = (not asset.id) or has_right("asset_edit",
                                              self.asset["id_folder"])
        self.folder_select.setEnabled(enabled)
        self.action_approve.setEnabled(enabled)
        self.action_qc_reset.setEnabled(enabled)
        self.action_reject.setEnabled(enabled)
        self.action_apply.setEnabled(enabled)
        self.action_revert.setEnabled(enabled)

        self.set_title("DETAIL : " + self.asset.__repr__())

        self._is_loading = False
        if self._load_queue:
            self.focus(self._load_queue)
    def dragMoveEvent(self, evt):
        if not has_right("scheduler_edit", self.calendar.id_channel):
            return
        self.dragging = True
        self.calendar.focus_data = []
        self.mx = evt.pos().x()
        self.my = evt.pos().y()
        cursor_time = (self.my / self.min_size * 60) + self.start_time
        if self.round_ts(cursor_time) != self.round_ts(self.cursor_time):
            self.cursor_time = cursor_time
            self.update()

        # disallow droping event over another event
        if type(self.calendar.dragging) == Event:
            if self.round_ts(self.cursor_time - self.calendar.drag_offset) in [
                    event["start"] for event in self.calendar.events
            ]:
                evt.ignore()
                return
        evt.accept()
Exemple #8
0
    def load(self):
        if not has_right("mcr", self.id_channel):
            return

        logging.debug("[PLUGINS] Loading playout plugins")
        for idx in reversed(range(self.count())):
            widget = self.widget(idx)
            self.removeTab(idx)
            widget.deleteLater()

        response = api.playout(action="plugin_list",
                               id_channel=self.id_channel)
        if not response:
            logging.error(
                f"[PLUGINS] Unable to load playout plugins:\n{response.message}"
            )
            return

        for plugin in response.data or []:
            self.plugins.append(PlayoutPlugin(self, plugin))
            self.addTab(self.plugins[-1], plugin.get("title", "unknown"))
Exemple #9
0
    def __init__(self, parent):
        super(RundownModule, self).__init__(parent)
        self.start_time = 0
        self.current_item = False
        self.cued_item = False
        self.last_search = ""
        self.first_load = True

        self.edit_wanted = self.app_state.get("edit_enabled", True)
        self.edit_enabled = False

        self.toolbar = rundown_toolbar(self)

        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(2)
        layout.addWidget(self.toolbar, 0)

        self.view = RundownView(self)

        self.mcr = self.plugins = False

        if has_right("mcr", anyval=True):
            self.mcr = MCR(self)
            self.plugins = PlayoutPlugins(self)
            if self.app_state.get("show_mcr", False):
                self.mcr.show()
            else:
                self.mcr.hide()

            if self.app_state.get("show_plugins", False):
                self.plugins.show()
            else:
                self.plugins.hide()
            layout.addWidget(self.mcr)
            layout.addWidget(self.plugins)

        layout.addWidget(self.view, 1)
        self.setLayout(layout)
Exemple #10
0
    def load(self, asset, **kwargs):
        id_folder = kwargs.get("id_folder", asset["id_folder"])
        if id_folder != self.id_folder or kwargs.get("force"):
            if not id_folder:
                self.keys = []
            else:
                self.keys = config["folders"][id_folder]["meta_set"]

            if self.form:
                # SRSLY. I've no idea what I'm doing here
                self.layout.removeWidget(self.form)
                self.form.deleteLater()
                QApplication.processEvents()
                self.form.destroy()
                QApplication.processEvents()
                self.form = None
            for i in reversed(range(self.layout.count())):
                self.layout.itemAt(i).widget().deleteLater()

            self.form = MetaEditor(self, self.keys)
            self.layout.addWidget(self.form)
            self.id_folder = id_folder
            self.status = asset["status"]

        if self.form:
            for key, conf in self.keys:
                if meta_types[key]["class"] in [
                        MetaClass.SELECT, MetaClass.LIST
                ]:
                    self.form.inputs[key].set_data(
                        asset.show(key, result="full"))
                self.form[key] = asset[key]
            self.form.set_defaults()

        if self.form:
            enabled = has_right("asset_edit", id_folder)
            self.form.setEnabled(enabled)
    def contextMenuEvent(self, event):
        if not self.cursor_event:
            return

        menu = QMenu(self.parent())
        menu.setStyleSheet(app_skin)

        self.calendar.selected_event = self.cursor_event

        action_open_rundown = QAction("Open rundown", self)
        action_open_rundown.triggered.connect(self.on_open_rundown)
        menu.addAction(action_open_rundown)

        action_edit_event = QAction("Event details", self)
        action_edit_event.triggered.connect(self.on_edit_event)
        menu.addAction(action_edit_event)

        if has_right("scheduler_edit", self.calendar.id_channel):
            menu.addSeparator()
            action_delete_event = QAction("Delete event", self)
            action_delete_event.triggered.connect(self.on_delete_event)
            menu.addAction(action_delete_event)

        menu.exec_(event.globalPos())
    def dragEnterEvent(self, evt):
        if not has_right("scheduler_edit", self.calendar.id_channel):
            return
        if evt.mimeData().hasFormat("application/nx.asset"):
            d = evt.mimeData().data("application/nx.asset").data()
            d = json.loads(d.decode("ascii"))
            if len(d) != 1:
                evt.ignore()
                return
            asset = Asset(meta=d[0])

            if not eval(self.calendar.playout_config["scheduler_accepts"]):
                evt.ignore()
                return

            self.calendar.dragging = asset
            self.calendar.drag_offset = (20 / self.sec_size
                                         )  # TODO: SOMETHING MORE CLEVER
            evt.accept()

        elif evt.mimeData().hasFormat("application/nx.event"):
            d = evt.mimeData().data("application/nx.event").data()
            d = json.loads(d.decode("ascii"))
            if len(d) != 1:
                evt.ignore()
                return
            event = Event(meta=d[0])
            self.calendar.dragging = event
            evt.accept()

        else:
            evt.ignore()

        if self.calendar.drag_source:
            self.calendar.drag_source.drag_outside = False
            self.calendar.drag_source.update()
def rundown_toolbar(wnd):
    action_find = QAction("Search rundown", wnd)
    action_find.setShortcut("Ctrl+F")
    action_find.triggered.connect(wnd.find)
    wnd.addAction(action_find)

    action_find_next = QAction("Search rundown", wnd)
    action_find_next.setShortcut("F3")
    action_find_next.triggered.connect(wnd.find_next)
    wnd.addAction(action_find_next)

    toolbar = QToolBar(wnd)

    action_day_prev = QAction(QIcon(pixlib["previous"]), "&Previous day", wnd)
    action_day_prev.setShortcut("Alt+Left")
    action_day_prev.setStatusTip("Go to previous day")
    action_day_prev.triggered.connect(wnd.go_day_prev)
    toolbar.addAction(action_day_prev)

    action_now = QAction(QIcon(pixlib["now"]), "&Now", wnd)
    action_now.setStatusTip("Go to now")
    action_now.triggered.connect(wnd.go_now)
    toolbar.addAction(action_now)

    action_calendar = QAction(QIcon(pixlib["calendar"]), "&Calendar", wnd)
    action_calendar.setShortcut("Ctrl+D")
    action_calendar.setStatusTip("Open calendar")
    action_calendar.triggered.connect(wnd.show_calendar)
    toolbar.addAction(action_calendar)

    action_refresh = QAction(QIcon(pixlib["refresh"]), "&Refresh", wnd)
    action_refresh.setStatusTip("Refresh rundown")
    action_refresh.triggered.connect(wnd.load)
    toolbar.addAction(action_refresh)

    action_day_next = QAction(QIcon(pixlib["next"]), "&Next day", wnd)
    action_day_next.setShortcut("Alt+Right")
    action_day_next.setStatusTip("Go to next day")
    action_day_next.triggered.connect(wnd.go_day_next)
    toolbar.addAction(action_day_next)

    if has_right("rundown_edit", anyval=True):

        toolbar.addSeparator()

        for btn_config in ITEM_BUTTONS:
            toolbar.addWidget(ItemButton(wnd, btn_config))

        toolbar.addSeparator()

        action_toggle_mcr = QAction(QIcon(pixlib["mcr"]), "&Playout controls",
                                    wnd)
        action_toggle_mcr.setStatusTip("Toggle playout controls")
        action_toggle_mcr.triggered.connect(wnd.toggle_mcr)
        toolbar.addAction(action_toggle_mcr)

        action_toggle_plugins = QAction(QIcon(pixlib["plugins"]),
                                        "&Plugins controls", wnd)
        action_toggle_plugins.setShortcut("F4")
        action_toggle_plugins.setStatusTip("Toggle plugins controls")
        action_toggle_plugins.triggered.connect(wnd.toggle_plugins)
        toolbar.addAction(action_toggle_plugins)

    toolbar.addWidget(ToolBarStretcher(wnd))

    wnd.channel_display = ChannelDisplay()
    toolbar.addWidget(wnd.channel_display)

    return toolbar
Exemple #14
0
 def can_edit(self):
     return has_right("rundown_edit", self.id_channel)
Exemple #15
0
def create_menu(wnd):
    menubar = wnd.menuBar()

    menu_file = menubar.addMenu("&File")
    action_new_asset = QAction("&New asset", wnd)
    action_new_asset.setShortcut("Ctrl+N")
    action_new_asset.setStatusTip("Create new asset from template")
    action_new_asset.triggered.connect(wnd.new_asset)
    action_new_asset.setEnabled(
        has_right("asset_create") and config.get("ui_asset_create", True)
    )
    menu_file.addAction(action_new_asset)

    action_clone_asset = QAction("&Clone asset", wnd)
    action_clone_asset.setShortcut("Ctrl+Shift+N")
    action_clone_asset.setStatusTip("Clone current asset")
    action_clone_asset.triggered.connect(wnd.clone_asset)
    action_clone_asset.setEnabled(
        has_right("asset_create") and config.get("ui_asset_create", True)
    )
    menu_file.addAction(action_clone_asset)

    menu_file.addSeparator()

    action_refresh = QAction("&Refresh", wnd)
    action_refresh.setShortcut("F5")
    action_refresh.setStatusTip("Refresh views")
    action_refresh.triggered.connect(wnd.refresh)
    menu_file.addAction(action_refresh)

    action_load_settings = QAction("&Reload settings", wnd)
    action_load_settings.setShortcut("Shift+F5")
    action_load_settings.setStatusTip("Reload system settings")
    action_load_settings.triggered.connect(wnd.load_settings)
    menu_file.addAction(action_load_settings)

    menu_file.addSeparator()

    action_logout = QAction("L&ogout", wnd)
    action_logout.setStatusTip("Log out user")
    action_logout.triggered.connect(wnd.logout)
    menu_file.addAction(action_logout)

    action_exit = QAction("E&xit", wnd)
    action_exit.setShortcut("Alt+F4")
    action_exit.setStatusTip("Quit Firefly")
    action_exit.triggered.connect(wnd.exit)
    menu_file.addAction(action_exit)

    #
    # Browser
    #

    menu_browser = menubar.addMenu("Browser")

    action_detail = QAction("Asset &detail", wnd)
    action_detail.setShortcut("F2")
    action_detail.setStatusTip("Focus asset search bar")
    action_detail.triggered.connect(wnd.show_detail)
    menu_browser.addAction(action_detail)

    action_search = QAction("&Search assets", wnd)
    action_search.setShortcut("ESC")
    action_search.setStatusTip("Focus asset search bar")
    action_search.triggered.connect(wnd.search_assets)
    menu_browser.addAction(action_search)

    menu_browser.addSeparator()

    action_new_tab = QAction("&New tab", wnd)
    action_new_tab.setShortcut("CTRL+T")
    action_new_tab.setStatusTip("Open new browser tab")
    action_new_tab.triggered.connect(wnd.new_tab)
    menu_browser.addAction(action_new_tab)

    action_close_tab = QAction("&Close tab", wnd)
    action_close_tab.setShortcut("CTRL+W")
    action_close_tab.setStatusTip("Close current browser tab")
    action_close_tab.triggered.connect(wnd.close_tab)
    menu_browser.addAction(action_close_tab)

    action_prev_tab = QAction("&Previous tab", wnd)
    action_prev_tab.setShortcut("CTRL+PgUp")
    action_prev_tab.triggered.connect(wnd.prev_tab)
    menu_browser.addAction(action_prev_tab)

    action_next_tab = QAction("&Next tab", wnd)
    action_next_tab.setShortcut("CTRL+PgDown")
    action_next_tab.triggered.connect(wnd.next_tab)
    menu_browser.addAction(action_next_tab)

    #
    # Scheduling
    #

    if config["playout_channels"]:
        wnd.menu_scheduler = menubar.addMenu("&Scheduler")
        ag = QActionGroup(wnd)
        ag.setExclusive(True)

        for id_channel in sorted(config["playout_channels"]):
            a = ag.addAction(
                QAction(
                    config["playout_channels"][id_channel]["title"], wnd, checkable=True
                )
            )
            a.id_channel = id_channel
            a.triggered.connect(partial(wnd.set_channel, id_channel))
            if (
                has_right("rundown_view", a.id_channel)
                or has_right("rundown_edit", a.id_channel)
                or has_right("scheduler_view", a.id_channel)
                or has_right("scheduler_edit", a.id_channel)
            ):
                a.setEnabled(True)
            else:
                a.setEnabled(False)
            wnd.menu_scheduler.addAction(a)

        wnd.menu_scheduler.addSeparator()

        action_import_template = QAction("Import", wnd)
        action_import_template.setStatusTip("Import week template")
        action_import_template.triggered.connect(wnd.import_template)
        wnd.menu_scheduler.addAction(action_import_template)

        action_export_template = QAction("Export", wnd)
        action_export_template.setStatusTip("Export current week as template")
        action_export_template.triggered.connect(wnd.export_template)
        wnd.menu_scheduler.addAction(action_export_template)

        #
        # Rundown
        #

        menu_rundown = menubar.addMenu("&Rundown")

        action_now = QAction("Now", wnd)
        action_now.setShortcut("F1")
        action_now.setStatusTip("Open current position in rundown")
        action_now.setEnabled(has_right("rundown_view"))
        action_now.triggered.connect(wnd.now)
        menu_rundown.addAction(action_now)

        wnd.action_rundown_edit = QAction("Rundown edit mode", wnd)
        wnd.action_rundown_edit.setShortcut("Ctrl+R")
        wnd.action_rundown_edit.setStatusTip("Toggle rundown edit mode")
        wnd.action_rundown_edit.setCheckable(True)
        wnd.action_rundown_edit.setEnabled(has_right("rundown_edit"))
        wnd.action_rundown_edit.triggered.connect(wnd.toggle_rundown_edit)
        menu_rundown.addAction(wnd.action_rundown_edit)

        menu_rundown.addSeparator()

        action_refresh_plugins = QAction("Refresh plugins", wnd)
        action_refresh_plugins.setStatusTip("Refresh rundown plugins")
        action_refresh_plugins.triggered.connect(wnd.refresh_plugins)
        menu_rundown.addAction(action_refresh_plugins)

    #
    # HELP
    #

    menu_help = menubar.addMenu("Help")

    wnd.action_debug = QAction("Debug mode", wnd)
    wnd.action_debug.setStatusTip("Toggle debug mode")
    wnd.action_debug.setCheckable(True)
    wnd.action_debug.setChecked(config.get("debug", False))
    wnd.action_debug.triggered.connect(wnd.toggle_debug_mode)

    menu_help.addAction(wnd.action_debug)
    menu_help.addSeparator()

    action_about = QAction("&About", wnd)
    action_about.setStatusTip("About Firefly")
    action_about.triggered.connect(partial(show_about_dialog, wnd))
    menu_help.addAction(action_about)
Exemple #16
0
 def can_schedule(self):
     return has_right("scheduler_edit", self.id_channel)
    def dropEvent(self, evt):
        drop_ts = max(
            self.start_time,
            self.round_ts(self.cursor_time - self.calendar.drag_offset))
        do_reload = False

        if not has_right("scheduler_edit", self.id_channel):
            logging.error(
                "You are not allowed to modify schedule of this channel.")
            self.calendar.drag_source = False
            self.calendar.dragging = False
            return

        elif type(self.calendar.dragging) == Asset:
            for event in self.calendar.events:
                if event["start"] == drop_ts:
                    if event["duration"]:
                        ret = QMessageBox.question(
                            self,
                            "Overwrite",
                            f"Do you really want to overwrite {event}",
                            QMessageBox.Yes | QMessageBox.No,
                        )
                        if ret == QMessageBox.Yes:
                            pass
                        else:
                            self.calendar.drag_source = False
                            self.calendar.dragging = False
                            self.update()
                            return

            if evt.keyboardModifiers() & Qt.AltModifier:
                logging.info(f"Creating event from {self.calendar.dragging}"
                             f"at time {format_time(self.cursor_time)}")
                if show_event_dialog(
                        asset=self.calendar.dragging,
                        id_channel=self.id_channel,
                        start=drop_ts,
                ):
                    do_reload = True
            else:
                self.calendar.setCursor(Qt.WaitCursor)
                response = api.schedule(
                    id_channel=self.id_channel,
                    start_time=self.calendar.week_start_time,
                    end_time=self.calendar.week_end_time,
                    events=[{
                        "id_asset": self.calendar.dragging.id,
                        "start": drop_ts,
                        "id_channel": self.id_channel,
                    }],
                )
                self.calendar.setCursor(Qt.ArrowCursor)
                if not response:
                    logging.error(response.message)

            do_reload = True

        elif type(self.calendar.dragging) == Event:
            event = self.calendar.dragging
            move = True

            if event.id and abs(event["start"] - drop_ts) > 7200:
                ret = QMessageBox.question(
                    self,
                    "Move event",
                    f"Do you really want to move {self.cursor_event}?"
                    f"\n\nFrom: {format_time(event['start'])}"
                    f"\nTo: {format_time(drop_ts)}",
                    QMessageBox.Yes | QMessageBox.No,
                )
                if ret == QMessageBox.Yes:
                    move = True
                else:
                    move = False

            if move:
                event["start"] = drop_ts
                if not event.id:
                    logging.debug("Creating empty event")
                    # Create empty event. Event edit dialog is enforced.
                    if show_event_dialog(id_channel=self.id_channel,
                                         start=drop_ts):
                        do_reload = True
                else:
                    # Just dragging events around. Instant save
                    self.calendar.setCursor(Qt.ArrowCursor)
                    response = api.schedule(
                        id_channel=self.id_channel,
                        start_time=self.calendar.week_start_time,
                        end_time=self.calendar.week_end_time,
                        events=[event.meta],
                    )
                    self.calendar.setCursor(Qt.ArrowCursor)
                    if not response:
                        logging.error(response.message)
                    else:
                        do_reload = response.data

        self.calendar.drag_source = False
        self.calendar.dragging = False
        if type(do_reload) == list:
            self.calendar.set_data(do_reload)
        elif do_reload:
            self.calendar.load()