def on_activate(self, mi): obj = self.model().object_data[mi.row()] key = self.model().header_data[mi.column()] val = obj.show(key) QApplication.clipboard().setText(str(val)) logging.info(f'Copied "{val}" to clipboard')
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 load_callback(self, callback, response): self.beginResetModel() QApplication.setOverrideCursor(Qt.WaitCursor) if not response: logging.error(response.message) else: asset_cache.request(response.data) # Pagination current_page = self.parent().current_page if len(response.data) > RECORDS_PER_PAGE: page_count = current_page + 1 elif len(response.data) == 0: page_count = max(1, current_page - 1) else: page_count = current_page if current_page > page_count: current_page = page_count # Replace object data if len(response.data) > RECORDS_PER_PAGE: response.data.pop(-1) self.object_data = [asset_cache.get(row[0]) for row in response.data] self.parent().set_page(current_page, page_count) self.endResetModel() QApplication.restoreOverrideCursor() callback()
def load_callback(self, response): self.parent().setCursor(Qt.ArrowCursor) if not response: logging.error(response.message) return QApplication.processEvents() self.parent().setCursor(Qt.WaitCursor) self.beginResetModel() logging.info("Loading rundown. Please wait...") required_assets = [] self.header_data = config["playout_channels"][self.id_channel].get( "rundown_columns", DEFAULT_COLUMNS) self.object_data = [] self.event_ids = [] i = 0 for row in response.data: row["rundown_row"] = i if row["object_type"] == "event": self.object_data.append(Event(meta=row)) i += 1 self.event_ids.append(row["id"]) if row["is_empty"]: self.object_data.append( Item(meta={ "title": "(Empty event)", "id_bin": row["id_bin"] })) i += 1 elif row["object_type"] == "item": item = Item(meta=row) item.id_channel = self.id_channel if row["id_asset"]: item._asset = asset_cache.get(row["id_asset"]) required_assets.append( [row["id_asset"], row["asset_mtime"]]) else: item._asset = False self.object_data.append(item) i += 1 else: continue asset_cache.request(required_assets) self.endResetModel() self.parent().setCursor(Qt.ArrowCursor) logging.goodnews( "Rundown loaded in {:.03f}s".format(time.time() - self.load_start_time)) if self.current_callback: self.current_callback()
def redraw_tabs(self, *args, **kwargs): QApplication.processEvents() views = [] for i, b in enumerate(self.browsers): id_view = b.id_view self.tabs.setTabText(i, b.title or config["views"][id_view]["title"]) sq = copy.copy(b.search_query) if self.tabs.currentIndex() == i: sq["active"] = True if b.title: sq["title"] = b.title views.append(sq) self.app_state["browser_tabs"] = views
def run(self, method, callback, **kwargs): logging.debug("Executing {}{} query".format( "" if callback == -1 else "async ", method)) kwargs["session_id"] = config["session_id"] kwargs["initiator"] = CLIENT_ID if method in ["ping", "login", "logout"]: method = "/" + method mime = QVariant("application/x-www-form-urlencoded") post_data = QUrlQuery() for key in kwargs: post_data.addQueryItem(key, kwargs[key]) data = post_data.toString(QUrl.FullyEncoded).encode("ascii") else: method = "/api/" + method mime = QVariant("application/json") data = json.dumps(kwargs).encode("ascii") request = QNetworkRequest(QUrl(config["hub"] + method)) request.setHeader(QNetworkRequest.ContentTypeHeader, mime) request.setHeader( QNetworkRequest.UserAgentHeader, QVariant(f"nebula-firefly/{FIREFLY_VERSION}"), ) try: query = self.manager.post(request, data) if callback != -1: query.finished.connect( functools.partial(self.handler, query, callback)) self.queries.append(query) except Exception: log_traceback() if callback: r = NebulaResponse(400, "Unable to send request") if callback == -1: return r else: callback(r) return if callback == -1: while not query.isFinished(): time.sleep(0.0001) QApplication.processEvents() return self.handler(query, -1)
def update_display(self): now = time.time() adv = now - self.local_request_time rtime = self.request_time + adv rpos = self.pos if not self.paused: rpos += adv frame = int(self.fps * (rtime - math.floor(rtime))) clock = time.strftime(f"%H:%M:%S:{frame:02d}", time.localtime(rtime)) self.display_clock.set_text(clock) self.display_pos.set_text(s2tc(min(self.dur, rpos), self.fps)) rem = self.dur - rpos t = s2tc(max(0, rem), self.fps) if rem < 10: self.display_rem.set_text(f"<font color='red'>{t}</font>") else: self.display_rem.set_text(t) if self.pos == self.dur == self.progress_bar.value() == 0: self.progress_bar.setValue(0) try: ppos = int((rpos / self.dur) * PROGRESS_BAR_RESOLUTION) except ZeroDivisionError: return else: oldval = self.progress_bar.value() if ppos > oldval or abs(oldval - ppos) > PROGRESS_BAR_RESOLUTION / self.dur: self.progress_bar.setValue(ppos) self.progress_bar.update() if self.request_display_resize: QApplication.processEvents() self.display_clock.setFixedSize(self.display_clock.size()) self.display_pos.setFixedSize(self.display_clock.size()) self.display_rem.setFixedSize(self.display_clock.size()) self.display_dur.setFixedSize(self.display_clock.size()) self.request_display_resize = False
def on_copy_result(self): result = "" for obj in self.view.selected_objects: result += "{}\n".format( "\t".join( [obj.format_display(key) or "" for key in self.model().header_data] ) ) clipboard = QApplication.clipboard() clipboard.setText(result)
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 load(self, ts=False): if not self.week_start_time and not ts: ts = time.time() if ts: dt = datetime.datetime.fromtimestamp(ts) week_start = dt - datetime.timedelta(days=dt.weekday()) week_start = week_start.replace(hour=self.day_start[0], minute=self.day_start[1], second=0) self.week_start_time = time.mktime(week_start.timetuple()) self.week_end_time = self.week_start_time + SECS_PER_WEEK QApplication.processEvents() self.setCursor(Qt.WaitCursor) response = api.schedule( id_channel=self.id_channel, start_time=self.week_start_time, end_time=self.week_end_time, ) if response: self.clock_bar.day_start = self.day_start self.clock_bar.update() self.set_data(response.data) for i, widgets in enumerate(zip(self.days, self.headers)): day_widget, header_widget = widgets start_time = self.week_start_time + (i * SECS_PER_DAY) day_widget.set_time(start_time) header_widget.set_time(start_time) else: logging.error(response.message) self.setCursor(Qt.ArrowCursor) self.on_zoom()
def on_solve(self, solver): QApplication.processEvents() QApplication.setOverrideCursor(Qt.WaitCursor) response = api.solve(id_item=self.selected_objects[0]["id"], solver=solver) QApplication.restoreOverrideCursor() if not response: logging.error(response.message) self.model().load() self.parent().main_window.scheduler.load()
def on_send(self, id_action): QApplication.processEvents() QApplication.setOverrideCursor(Qt.WaitCursor) response = api.send( id_action=id_action, objects=self.assets, restart_existing=self.restart_existing.isChecked(), restart_running=self.restart_running.isChecked(), ) QApplication.restoreOverrideCursor() if not response: logging.error(response.message) else: self.close()
def on_accept(self): QApplication.processEvents() QApplication.setOverrideCursor(Qt.WaitCursor) response = api.set( object_type="item", objects=[self.item.id], data={ "mark_in": self.form["mark_in"], "mark_out": self.form["mark_out"] }, ) QApplication.restoreOverrideCursor() if not response: logging.error(response.message) self.close()
def on_set_mode(self, mode): if not self.parent().can_edit: logging.error("You are not allowed to modify this rundown") return QApplication.processEvents() QApplication.setOverrideCursor(Qt.WaitCursor) response = api.set( object_type=self.selected_objects[0].object_type, objects=[obj.id for obj in self.selected_objects], data={"run_mode": mode}, ) QApplication.restoreOverrideCursor() if not response: logging.error(response.message) return self.model().load()
def on_close(self): self.detail.check_changed() self.main_window.listener.halt() QApplication.quit() logging.debug("[MAIN WINDOW] Window closed")
def set_data(self, data): self.events = [] for meta in data: self.events.append(Event(meta=meta)) QApplication.processEvents() self.update()
def import_template(self, day_offset=0): try: if not os.path.exists("templates"): os.makedirs("templates") except Exception: log_traceback() file_path = QFileDialog.getOpenFileName(self, "Open template", os.path.abspath("templates"), "Templates (*.xml)")[0] if not file_path: return QApplication.setOverrideCursor(Qt.WaitCursor) try: feed = open(file_path, "rb").read().decode("utf-8") data = xml(feed) except Exception: QApplication.restoreOverrideCursor() log_traceback() return ch, cm = self.calendar.day_start events = [] try: for day_index, day in enumerate(data.findall("day")): day_start = self.calendar.week_start_time + (3600 * 24 * day_index) for event_data in day.findall("event"): hh, mm = [ int(x) for x in event_data.attrib["time"].split(":") ] clock_offset = (hh * 3600) + (mm * 60) - (ch * 3600) - (cm * 60) if (hh * 3600) + (mm * 60) < (ch * 3600) - (cm * 60): clock_offset += 24 * 3600 start_time = day_start + clock_offset + (day_offset * 3600 * 24) event = Event(meta={"start": start_time}) for m in event_data.findall("meta"): key = m.attrib["key"] value = m.text if value: event[key] = value items_data = event_data.find("items") if items_data is not None: event.meta["_items"] = [] for ipos, item_data in enumerate( items_data.findall("item")): item = Item() item["position"] = ipos + 1 for kv in item_data.findall("meta"): item[kv.attrib["key"]] = kv.text event.meta["_items"].append(item.meta) events.append(event.meta) if day_offset: # Importing single day. break except Exception: QApplication.restoreOverrideCursor() log_traceback("Unable to parse template:") return if not events: QApplication.restoreOverrideCursor() return response = api.schedule(id_channel=self.id_channel, events=events) QApplication.restoreOverrideCursor() if not response: logging.error(response.message) else: logging.info(response.message) self.load()
def handle_query(self, msg): QApplication.processEvents()
def dropMimeData(self, data, action, row, column, parent): if action == Qt.IgnoreAction: return True if not self.parent().parent().edit_enabled: return True if row < 1: return False drop_objects = [] if data.hasFormat("application/nx.item"): d = data.data("application/nx.item").data() items = json.loads(d.decode("ascii")) if not items or items[0].get("rundown_row", "") in [row, row - 1]: return False else: for obj in items: if action == Qt.CopyAction: obj["id"] = False elif not obj.get("id", False): item_role = obj.get("item_role", False) if item_role in ["live", "placeholder"]: dlg = PlaceholderDialog(self.parent(), obj) dlg.exec_() if not dlg.ok: return False for key in dlg.meta: obj[key] = dlg.meta[key] elif item_role in ["lead_in", "lead_out"]: pass else: continue drop_objects.append(Item(meta=obj)) elif data.hasFormat("application/nx.asset"): d = data.data("application/nx.asset").data() items = json.loads(d.decode("ascii")) for asset_data in items: asset = Asset(meta=asset_data) drop_objects.append(asset) else: return False sorted_items = [] i = row - 1 to_bin = self.object_data[i]["id_bin"] # Apend heading items while i >= 1: current_object = self.object_data[i] if (current_object.object_type != "item" or current_object["id_bin"] != to_bin): break p_item = current_object.id if p_item not in [item.id for item in drop_objects]: if p_item: sorted_items.append({ "object_type": "item", "id_object": p_item, "meta": {} }) i -= 1 sorted_items.reverse() # Append drop for obj in drop_objects: if data.hasFormat("application/nx.item"): sorted_items.append({ "object_type": "item", "id_object": obj.id, "meta": obj.meta }) elif data.hasFormat("application/nx.asset"): if obj["subclips"]: dlg = SubclipSelectDialog(self.parent(), obj) dlg.exec_() if dlg.ok: for meta in dlg.result: sorted_items.append({ "object_type": "asset", "id_object": obj.id, "meta": meta, }) else: # Asset does not have subclips meta = {} if obj["mark_in"]: meta["mark_in"] = obj["mark_in"] meta["mark_out"] = obj["mark_out"] sorted_items.append({ "object_type": "asset", "id_object": obj.id, "meta": meta }) # Append trailing items i = row while i < len(self.object_data): current_object = self.object_data[i] if (current_object.object_type != "item" or current_object["id_bin"] != to_bin): break p_item = current_object.id if p_item not in [item.id for item in drop_objects]: if p_item: sorted_items.append({ "object_type": "item", "id_object": p_item, "meta": {} }) i += 1 # # Send order query # sorted_items = [item for item in sorted_items] # if item["id_object"]] if not sorted_items: return self.parent().setCursor(Qt.BusyCursor) QApplication.processEvents() api.order( self.order_callback, id_channel=self.id_channel, id_bin=to_bin, order=sorted_items, ) return False
def on_delete(self): items = list( set([ obj.id for obj in self.selected_objects if obj.object_type == "item" ])) events = list( set([ obj.id for obj in self.selected_objects if obj.object_type == "event" ])) if items and not self.parent().can_edit: logging.error("You are not allowed to modify this rundown items") return elif events and not self.parent().can_schedule: logging.error("You are not allowed to modify this rundown blocks") return if events or len(items) > 10: ret = QMessageBox.question( self, "Delete", "Do you REALLY want to delete " f"{len(items)} items and {len(events)} events?\n" "This operation CANNOT be undone", QMessageBox.Yes | QMessageBox.No, ) if ret != QMessageBox.Yes: return if items: QApplication.processEvents() QApplication.setOverrideCursor(Qt.WaitCursor) response = api.delete(object_type="item", objects=items) QApplication.restoreOverrideCursor() if not response: logging.error(response.message) return else: logging.info("Item deleted: {}".format(response.message)) if events: QApplication.processEvents() QApplication.setOverrideCursor(Qt.WaitCursor) response = api.schedule(delete=events, id_channel=self.parent().id_channel) QApplication.restoreOverrideCursor() if not response: logging.error(response.message) return else: logging.info("Event deleted: {}".format(response.message)) self.selectionModel().clear() self.model().load() self.parent().main_window.scheduler.refresh_events(events)