def dialog_open_subtitle_files(parent=None): """ Dialog which is used to choose multiple subtitle files. :param parent: the parent widget :return: the qc document paths or None if user aborts """ dialog = Gtk.FileChooserNative.new(title=_("Choose subtitle files"), parent=parent, action=Gtk.FileChooserAction.OPEN) dialog.add_filter(_FILE_FILTERS.filter_subs) dialog.set_select_multiple(True) latest_directory = get_settings().latest_paths_import_subtitle_directory if path.isdir(latest_directory): dialog.set_current_folder(latest_directory) subtitles = None if dialog.run() == Gtk.ResponseType.ACCEPT: subtitles = dialog.get_filenames() if subtitles: get_settings().latest_paths_import_subtitle_directory = str( path.dirname(subtitles[0])) dialog.destroy() return subtitles
def dialog_open_video(parent=None): """ Dialog which is used to choose a video file. :param parent: the parent widget :return: the chosen video or None if user aborts """ dialog = Gtk.FileChooserNative.new(title=_("Choose a video file"), parent=parent, action=Gtk.FileChooserAction.OPEN) dialog.add_filter(_FILE_FILTERS.filter_vids) dialog.set_select_multiple(False) latest_directory = get_settings().latest_paths_import_video_directory if path.isdir(latest_directory): dialog.set_current_folder(latest_directory) video = None if dialog.run() == Gtk.ResponseType.ACCEPT: video = dialog.get_filename() get_settings().latest_paths_import_video_directory = str( path.dirname(video)) dialog.destroy() return video
def on_restore_default_clicked(self): """ Called whenever the user presses restore and this preference page is visible. """ get_settings().reset_config_file_mpv_content() self.__set_initial_values()
def __set_up_combo_box_header_subtitle(self): """ Sets up the combo box regarding the display subtitle. """ self.__combo_model.append([_("Display nothing")]) self.__combo_model.append([_("Current file name")]) self.__combo_model.append([_("Current file path")]) renderer = Gtk.CellRendererText() self.cbox_header.pack_start(renderer, True) self.cbox_header.add_attribute(renderer, "text", 0) self.cbox_header.set_model(self.__combo_model) get_settings().bind_header_subtitle_format(self.cbox_header, "active")
def __on_video_timer_timeout(self, *_): """ Updates the status bar periodically. Kick off in 'on_mpv_player_realized' :return: True if continue timer, False else """ s = get_settings() p_value = s.status_bar_percentage tf_value = s.status_bar_time_format video_loaded = self.__time_current if tf_value == 0 and video_loaded: time = "{0}/{1}".format(self.__time_current, self.__time_duration) elif tf_value == 1 and video_loaded: time = self.__time_current elif tf_value == 2 and video_loaded: time = "-" + self.__time_remaining else: time = "" if p_value: percent = self.__percent else: percent = "" self.label_button_time.set_label( "{} {}".format(percent, time).strip() + (" " if video_loaded and (time or p_value) else "") ) return True
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.init_template() self.label_backup_directory_path.set_text( str(get_app_paths().dir_backup)) self.list_export_settings_header.set_header_func( list_header_nested_func, None) self.list_export_settings.set_header_func(list_header_func, None) self.list_auto_save_interval.set_header_func(list_header_nested_func, None) self.list_open_back_directory.set_header_func(list_header_nested_func, None) # Bind settings to widgets s = get_settings() s.bind_import_open_video_automatically( self.switch_load_video_automatically, "active") s.bind_export_qc_document_nick(self.label_nick, "label") s.bind_export_append_nick(self.switch_append_nick, "active") s.bind_export_write_header(self.switch_write_header, "active") s.bind_export_write_header(self.revealer_header_section, "reveal-child") s.bind_export_write_date(self.switch_write_date, "active") s.bind_export_write_generator(self.switch_write_generator, "active") s.bind_export_write_nick(self.switch_write_nick, "active") s.bind_export_write_path(self.switch_write_path, "active") s.bind_auto_save_enabled(self.switch_auto_save, "active") s.bind_auto_save_enabled(self.revealer_auto_save, "reveal-child") s.bind_auto_save_interval(self.spin_btn_auto_save_interval, "value")
def dialog_save_qc_document(video_file, parent=None): """ Dialog which is used to save a qc document. :param video_file: the file path of the video :param parent: the parent widget :return: the file path to save under or None if user aborts """ dialog = Gtk.FileChooserNative.new(title=_("Choose a file name"), parent=parent, action=Gtk.FileChooserAction.SAVE) dialog.add_filter(_FILE_FILTERS.filter_docs) dialog.set_current_name(generate_file_name_proposal(video_file)) dialog.set_select_multiple(False) dialog.set_do_overwrite_confirmation(True) directory = path.dirname(video_file) if video_file else str(Path.home()) dialog.set_current_folder(directory) file_name = None if dialog.run() == Gtk.ResponseType.ACCEPT: file_name = dialog.get_filename() file_name = file_name if file_name.endswith( ".txt") else file_name + ".txt" get_settings().latest_paths_export_qc_directory = str( path.dirname(file_name)) # todo escape specific file name characters dialog.destroy() return file_name
def get_file_content(video_path: Optional[str], comments: Tuple[Comment]): """ Will take into account all user settings provided as arguments to build and return the qc file content. :param video_path: the path of the video :param comments: a list of comments objects :return: the file content of the qc document as a string """ metadata = get_app_metadata() s = get_settings() b_header = s.export_write_header b_date = s.export_write_date v_date = str(datetime.now().replace(microsecond=0)) b_generator = s.export_write_generator v_generator = "{} {}".format(metadata.app_name, metadata.get_app_version_str(short=True)) b_nick = s.export_write_nick v_nick = s.export_qc_document_nick b_path = s.export_write_path v_path = video_path if video_path else "" return __prepare_file_content(b_header, b_date, v_date, b_generator, v_generator, b_nick, v_nick, b_path, v_path, comments)
def __update_recent_files_list(self): """ Updates the recent files list. """ for child in self.__list_recent_files.get_children(): self.__list_recent_files.remove(child) recent_files = get_settings().latest_paths_recent_files if recent_files: rows_displayed = min(5, len(recent_files)) # Fit max 5 rows, add space for separators self.scrolled_container.set_min_content_height( _RECENT_FILES_ROW_HEIGHT * rows_displayed + rows_displayed) for recent_file in recent_files: row = create_row( file_type=0 if is_qc_document(recent_file) else 1, file_name=path.basename(recent_file), file_path=recent_file) self.__list_recent_files.add(row) self.revealer_recent_files.set_reveal_child(True) else: self.revealer_recent_files.set_reveal_child(False)
def __create_context_menu(self, button, time) -> None: """ Creates a new context menu filled with all current comment types. :param button: The button which requested the event. :param time: The time of the event. """ if not self.__mpv.is_video_loaded(): return self.__mpv.pause() def __on_clicked(value) -> None: self.emit(signals.MPVQC_CREATE_NEW_COMMENT, self.__mpv.position_current()[1], value.get_label()) menu = Gtk.Menu() menu.attach_to_widget(self, None) for item in get_settings().comment_types: menu_item = Gtk.MenuItem() menu_item.set_label(str(item)) menu_item.connect("activate", __on_clicked) menu.add(menu_item) menu.show_all() menu.popup(None, None, None, data=None, button=button, activate_time=time)
def __set_comment_type_content(self): """ Set initial values from settings. """ self.__comment_type_model.remove_all() for comment_type in get_settings().comment_types: self.__ct_add_item(comment_type)
def on_restore_default_clicked(self): """ Called whenever the user presses restore and this preference page is visible. """ s = get_settings() s.reset_header_subtitle_format() s.reset_comment_types() self.__set_comment_type_content()
def __update_comment_type_setting(self): """ Updates the comment type setting. """ new_comment_types = [] for idx, __ in enumerate(self.list_ct): new_comment_types.append(self.__comment_type_model.get_item(idx).text) get_settings().comment_types = new_comment_types
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.init_template() self.message_stack = MessageStack() self.pack_start(self.message_stack, expand=True, fill=True, padding=0) # Updated by player signals self.__time_duration = "" self.__time_remaining = "" self.__time_current = "" self.__percent = "" # State variables for formatting self.__duration = None self.__short = False # Updated by table model signals self.__comment_count = None self.__comment_selected = None # Set up radio selection self.__time_menu = { 0: self.time_menu_default, 1: self.time_menu_current, 2: self.time_menu_remaining, 3: self.time_menu_no, } self.__time_menu.get(get_settings().status_bar_time_format, 0).set_property("active", True) for idx, entry in self.__time_menu.items(): entry.set_property("role", Gtk.ButtonRole.RADIO) entry.connect("clicked", lambda w, v=idx: self.__on_time_format_item_clicked(v)) # Set up check selection self.time_menu_percentage.set_property("active", get_settings().status_bar_percentage) self.time_menu_percentage.set_property("role", Gtk.ButtonRole.CHECK) self.time_menu_percentage.connect("clicked", self.__on_percentage_item_clicked) # Set up timer self.connect("destroy", self.__on_destroy) self.__time_update_timer = None
def validate(self, current_text): if not bool(_REGEX_EMPTY.match(current_text)): return False, _("A type must not be empty") if current_text in get_settings().comment_types: return False, _("A type with that name already exists") if not bool(_REGEX_COMMENT_TYPE.match(current_text)): return False, _("Allowed characters are letters, spaces and minus") return True, ""
def __on_percentage_item_clicked(self, *_): """ Updates the percentage setting. With the next timeout the setting gets applied. :param nr: the new key of the time_menu to apply """ new_value = not self.time_menu_percentage.get_property("active") get_settings().status_bar_percentage = new_value self.time_menu_percentage.set_property("active", new_value)
def __init__(self, mpvqc_window: MpvqcWindow, **kwargs): super().__init__(**kwargs) self.init_template() s = get_settings() # Dark theme self._button_dark_theme.set_property("role", Gtk.ButtonRole.CHECK) self._button_dark_theme.set_property("active", s.prefer_dark_theme) Gtk.Settings.get_default().set_property("gtk-application-prefer-dark-theme", s.prefer_dark_theme) # Widgets from mpvqc.ui.contentmainmpv import ContentMainMpv self.__video_widget = ContentMainMpv(self, mpvqc_window) self.__table_widget = ContentMainTable(self.__video_widget) self.__qc_manager = QcManager(mpvqc_window, self.__video_widget, self.__table_widget) self.__popover_open = PopoverOpen(self.__qc_manager) self.__status_bar = StatusBar() self.__search_frame = SearchFrame(self.__table_widget) # Widget composition of ui templates self._scrolled_window.add(self.__table_widget) self._overlay.add(self._scrolled_window) self._overlay.add_overlay(self.__search_frame) self._paned.pack1(self.__video_widget, resize=True, shrink=False) self._paned.pack2(self._overlay, resize=True, shrink=False) self._box.pack_start(self.__status_bar, expand=False, fill=True, padding=0) # Set up drag and drop target = Gtk.TargetEntry.new(target="text/uri-list", flags=Gtk.TargetFlags.OTHER_APP, info=0) self._paned.drag_dest_set(Gtk.DestDefaults.ALL, [target], Gdk.DragAction.COPY) self._paned.connect("drag-data-received", self.__on_drag_data_received) # Connect events: Player goes first self.__video_widget.connect("realize", self.__status_bar.on_mpv_player_realized) # Connect events: Key event order self.__table_widget.connect("key-press-event", self.__table_widget.on_key_press_event) self.__table_widget.connect("key-press-event", self.__video_widget.on_key_press_event) # Connect events: Statusbar self.__table_widget.get_selection().connect("changed", self.__status_bar.on_comments_selection_change) self.__table_widget.get_model().connect("row-changed", self.__status_bar.on_comments_row_changed) self.__table_widget.get_model().connect("row-deleted", self.__status_bar.on_comments_row_changed) self.__table_widget.get_model().connect("row-inserted", self.__status_bar.on_comments_row_changed) # Connect events: QC-Manager self.__qc_manager.connect(signals.MPVQC_STATUSBAR_UPDATE, self.__status_bar.update_statusbar_message) self.__qc_manager.connect(signals.MPVQC_QC_STATE_CHANGED, self.__update_title) self.__qc_manager.connect(signals.MPVQC_QC_STATE_CHANGED, self.__search_frame.clear_current_matches) # Class variables self.__is_fullscreen = False self.__video_file_name = "" self.__video_file_path = "" self.__on_mpv_player_realized()
def __update_subtitle(self) -> None: value = get_settings().header_subtitle_format subtitle_enabled = value != 0 and self.__video_file_name and self.__video_file_path self._header_bar.set_property("has-subtitle", subtitle_enabled) if value == 0: self._header_bar.set_subtitle("") elif value == 1: self._header_bar.set_subtitle(self.__video_file_name) elif value == 2: self._header_bar.set_subtitle(self.__video_file_path)
def on_save_as_pressed(self, a: AppWindow, t: Table, m: MpvContainer) -> 'State': """ Called when the user presses the 'Save As...' button """ m.player.pause() doc = d.dialog_save_qc_document(self.__vid, a) comments = t.get_all_comments() r = hs.do_save(doc, self.__vid, comments) if r.abort: return self.copy() elif r.save_error: md.message_dialog_document_save_failed(parent=a) return self.copy() get_settings().latest_paths_recent_files_add(doc) return self._state_saved(doc=r.doc_new, vid=r.vid_new, comments=comments, message=sm.get_save_m(as_new_name=True))
def __on_time_format_item_clicked(self, nr): """ Updates the time format setting and manages the toggle group. With the next timeout the setting gets applied. :param nr: the new key of the time_menu to apply """ get_settings().status_bar_time_format = nr for idx, entry in self.__time_menu.items(): if idx == nr: entry.set_property("active", True) else: entry.set_property("active", False)
def recalculate_preferred_width(self): """ Recalculate preferred width based on current model and on comment types (from settings entry) width. :return: a tuple (width, width) """ layout = Gtk.Label().get_layout() layout.set_markup( max(get_longest_string_from(self.__model, 2), get_settings().comment_types_longest, key=len)) pixel_size = layout.get_pixel_size() size = pixel_size.width + (self.get_padding()[0] * 4) self.__preferred_width = size, size
def on_restore_default_clicked(self): """ Called whenever the user presses restore and this preference page is visible. """ s = get_settings() s.reset_qc_document_nick() s.reset_export_append_nick() s.reset_export_write_header() s.reset_export_write_date() s.reset_export_write_generator() s.reset_export_write_nick() s.reset_export_write_path() s.reset_import_open_video_automatically() s.reset_auto_save_enabled() s.reset_auto_save_interval()
def do_delete_event(self, *args, **kwargs): """ Invoked when user presses the close button or CTRL + Q. :return: True to stop other handlers from being invoked for the event. False to propagate the event further. """ can_quit = self.__content_main.can_quit if not can_quit: return True s = get_settings() s.write_config_file_input_content() s.write_config_file_mpv_content() return False
def __init__(self, current_text, **properties): super().__init__(**properties) self.init_template() text_unknown = True for item in get_settings().comment_types: lbl = str(item) btn = self.__create_new_button(lbl) btn.show() if lbl == current_text: btn.set_property("active", True) text_unknown = False self.box.pack_start(btn, expand=False, fill=True, padding=0) if text_unknown: self.__create_additional_entry(current_text)
def on_export_row_activated(self, __, row, *___): """ Handles the editing of the nick. """ if row == self.row_nick: def __apply(____, new_value): self.label_nick.set_text(new_value) pop = InputPopover( label=_("New nickname:"), validator=NicknameValidator(), placeholder=_("Enter nickname"), current_text=get_settings().export_qc_document_nick) pop.set_relative_to(self.label_nick) pop.connect(signals.MPVQC_APPLY, __apply) pop.popup() return True
def on_import(self, docs: Optional[List[str]], vids: Optional[List[str]], subs: Optional[List[str]], a: AppWindow, t: Table, m: MpvContainer) -> 'State': """ Called when the user imports something (no matter if it's by d&d or just by selecting something in the file manager). """ if not docs and not vids and not subs: return self.copy() s = get_settings() def _handle_docs_valid(valid_docs: List[str]) -> None: for vd in valid_docs: s.latest_paths_recent_files_add(vd) def _handle_docs_invalid(invalid_docs: List[str]) -> None: if invalid_docs: md.message_dialog_imported_files_are_not_valid( not_valid_files=invalid_docs, parent=a) def _handle_comments(comments: Tuple[Comment]) -> bool: """ Returns True if abort import, False else """ if t.get_all_comments(): response = md.message_dialog_what_to_do_with_existing_comments( ) if response == 0: # Keep comments pass elif response == 1: # Delete comments t.clear_all_comments() elif response == 2: return True t.add_comments(comments) return False def _handle_vids(vid: str) -> bool: """ Returns True, if video actually was opened """ do_open = vid and (s.import_open_video_automatically or Gtk.ResponseType.YES == md.message_dialog_video_found_ask_to_open( file=vid, parent=a)) if do_open: __open_video(vid) return True return False def __open_video(vid: str) -> None: m.player.open_video(vid) s.latest_paths_recent_files_add(vid) hir, data = hi.do_import(self.__vid, docs, vids) vid_new = hir.vid_new if docs: abort_import = _handle_comments(hir.comments) if abort_import: return self.copy() _handle_docs_valid(hir.docs_valid) _handle_docs_invalid(hir.docs_invalid) if not vids and vid_new: opened = _handle_vids(vid_new) if not opened: vid_new = None data.vid_new = None if not vids and not subs and not hir.docs_valid: return self.copy() if vids: __open_video(vid_new) if subs: for sub in subs: m.player.add_sub_files(sub) return self.__on_import_handle_state(data=data, imp_docs=hir.docs_valid, imp_vid=vid_new, imp_subs=subs)
def on_mpv_text_buffer_changed(self, *_): start_iter = self.mpv_text_buffer.get_start_iter() end_iter = self.mpv_text_buffer.get_end_iter() get_settings().config_file_mpv_content = self.mpv_text_buffer.get_text( start_iter, end_iter, True)
def __set_initial_values(self): """ Set initial values from settings. """ self.mpv_text_buffer.set_text(get_settings().config_file_mpv_content)
def generate_file_name_proposal(video_file): nick = "_" + get_settings().export_qc_document_nick if get_settings( ).export_append_nick else "" video = path.splitext( path.basename(video_file))[0] if video_file else _("untitled") return "[QC]_{0}{1}.txt".format(video, nick)
def _on_button_dark_theme_clicked(self, *_) -> None: s = get_settings() s.prefer_dark_theme = not s.prefer_dark_theme self._button_dark_theme.set_property("active", s.prefer_dark_theme) Gtk.Settings.get_default().set_property("gtk-application-prefer-dark-theme", s.prefer_dark_theme)