Пример #1
0
class CommitDateMaxFilterEditor(QWidget, Ui_CommitDateMaxFilter):

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setupUi(self)

        self._data_mapper = QDataWidgetMapper()

    def setModel(self, model):
        self._model = model
        self._data_mapper.setModel(model)
        self._data_mapper.addMapping(self.uiCommitDateMax, 2)

    def setSelection(self, current: QModelIndex) -> None:
        parent = current.parent()
        self._data_mapper.setRootIndex(parent)
        self._data_mapper.setCurrentModelIndex(current)
Пример #2
0
class CommitDateDeltaMinFilterEditor(QWidget, Ui_CommitDateDeltaMinFilter):

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setupUi(self)

        self._data_mapper = QDataWidgetMapper()
        self.uiHelp.clicked.connect(_showTimeDurationHelp)

    def setModel(self, model):
        self._model = model
        self._data_mapper.setModel(model)
        self._data_mapper.addMapping(self.uiCommitDateDeltaMin, 2)

    def setSelection(self, current: QModelIndex) -> None:
        parent = current.parent()
        self._data_mapper.setRootIndex(parent)
        self._data_mapper.setCurrentModelIndex(current)
Пример #3
0
class AuthorFilterEditor(QWidget, Ui_AuthorFilterProperties):

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setupUi(self)

        self._data_mapper = QDataWidgetMapper()

    def setModel(self, model):
        self._model = model
        self._data_mapper.setModel(model)
        self._data_mapper.addMapping(self.uiAuthorName, 2)
        self._data_mapper.addMapping(self.uiAuthorEmail, 3)

    def setSelection(self, current: QModelIndex) -> None:
        parent = current.parent()
        self._data_mapper.setRootIndex(parent)
        self._data_mapper.setCurrentModelIndex(current)
Пример #4
0
class NodeEditor(QWidget, Ui_FilterNodeProperties):

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setupUi(self)

        self._data_mapper = QDataWidgetMapper()

    def setModel(self, model):
        self._model = model
        self._data_mapper.setModel(model)
        self._data_mapper.addMapping(self.uiName, 0)
        self._data_mapper.addMapping(self.uiComment, 1)

    def setSelection(self, current: QModelIndex) -> None:
        parent = current.parent()
        self._data_mapper.setRootIndex(parent)
        self._data_mapper.setCurrentModelIndex(current)
Пример #5
0
def setMappings(mappings):
    """Set the mappings between the model and widgets.
    TODO: - Should this be extended to accept other columns?
          - Check if the already has the model.
    """
    column = 1
    mappers = list()
    for widget, obj in mappings:
        mapper = QDataWidgetMapper(widget)
        # logger.debug(obj.model())
        mapper.setModel(obj.model())
        mapper.addMapping(widget, column)
        delegate = Delegate(widget)
        mapper.setItemDelegate(delegate)
        mapper.setRootIndex(obj.parent().index())
        mapper.setCurrentModelIndex(obj.index())
        # QDataWidgetMapper needs a focus event to notice a change in the data.
        # To make sure the model is informed about the change, I connected the
        # stateChanged signal of the QCheckBox to the submit slot of the
        # QDataWidgetMapper. The same idea goes for the QComboBox.
        # https://bugreports.qt.io/browse/QTBUG-1818
        if isinstance(widget, QCheckBox):
            signal = widget.stateChanged
            try:
                signal.disconnect()
            except TypeError:
                pass
            signal.connect(mapper.submit)
        elif isinstance(widget, QComboBox):
            signal = widget.currentTextChanged
            try:
                signal.disconnect()
            except TypeError:
                pass
            signal.connect(mapper.submit)
        mappers.append(mapper)
    return mappers
Пример #6
0
class RecordFormView(QMainWindow):

    ui_file = ''  # type: str
    model = None

    def __init__(self, model=None, ui_file: str = None) -> None:
        super().__init__()
        loadUi(ui_file or self.ui_file, self)

        self.model = model or self.model

        self.data_mapper = QDataWidgetMapper()
        self.data_mapper.setModel(self.model)

        for field in model.fields:
            if hasattr(self, field.name):
                self.data_mapper.addMapping(getattr(self, field.name),
                                            field.index)

        if hasattr(self, 'setup_ui'):
            self.setup_ui()

    def set_record_index(self, index: QModelIndex) -> None:
        self.data_mapper.setCurrentModelIndex(index)
Пример #7
0
class ArmorEditor(ArmorEditorWidgetBase, ArmorEditorWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setupUi(self)
        self.model = None
        self.parts_tree_model = ArmorSetTreeModel()
        self.skill_model = SkillTranslationModel()
        self.armor_item_mapper = QDataWidgetMapper(self)
        self.armor_item_mapper.setItemDelegate(ItemDelegate())
        self.armor_item_mapper.setModel(self.parts_tree_model)
        self.parts_tree_view.setModel(self.parts_tree_model)
        self.parts_tree_view.activated.connect(
            self.handle_parts_tree_activated)
        self.import_export_manager = ImportExportManager(self.parts_tree_view)
        self.import_export_manager.connect_custom_context_menu()
        for it in ("set_skill1_value", "set_skill2_value", "skill1_value",
                   "skill2_value", "skill3_value"):
            getattr(self, it).setModel(self.skill_model)
        mappings = [
            (self.id_value, Column.id, b"text"),
            (self.name_value, Column.gmd_name_index, b"text"),
            (self.description_value, Column.gmd_desc_index, b"text"),
            (self.setid_value, Column.set_id),
            (self.set_group_value, Column.set_group),
            (self.type_value, Column.type, b"currentIndex"),
            (self.order_value, Column.order),
            (self.variant_value, Column.variant, b"currentIndex"),
            (self.equip_slot_value, Column.equip_slot, b"currentIndex"),
            (self.gender_value, Column.gender, b"currentIndex"),
            (self.mdl_main_id_value, Column.mdl_main_id),
            (self.mdl_secondary_id_value, Column.mdl_secondary_id),
            (self.icon_color_value, Column.icon_color),
            (self.defense_value, Column.defense),
            (self.rarity_value, Column.rarity),
            (self.cost_value, Column.cost),
            (self.fire_res_value, Column.fire_res),
            (self.water_res_value, Column.water_res),
            (self.thunder_res_value, Column.thunder_res),
            (self.ice_res_value, Column.ice_res),
            (self.dragon_res_value, Column.dragon_res),
            (self.set_skill1_value, Column.set_skill1),
            (self.set_skill1_lvl_value, Column.set_skill1_lvl),
            (self.set_skill2_value, Column.set_skill2),
            (self.set_skill2_lvl_value, Column.set_skill2_lvl),
            (self.skill1_value, Column.skill1),
            (self.skill1_lvl_value, Column.skill1_lvl),
            (self.skill2_value, Column.skill2),
            (self.skill2_lvl_value, Column.skill2_lvl),
            (self.skill3_value, Column.skill3),
            (self.skill3_lvl_value, Column.skill3_lvl),
            (self.num_gem_slots, Column.num_gem_slots),
            (self.gem_slot1_lvl_value, Column.gem_slot1_lvl),
            (self.gem_slot2_lvl_value, Column.gem_slot2_lvl),
            (self.gem_slot3_lvl_value, Column.gem_slot3_lvl),
        ]
        for mapping in mappings:
            self.armor_item_mapper.addMapping(*mapping)

    def handle_parts_tree_activated(self, qindex: QModelIndex):
        if isinstance(qindex.internalPointer(), ArmorSetNode):
            return
        self.armor_item_mapper.setRootIndex(qindex.parent())
        self.armor_item_mapper.setCurrentModelIndex(qindex)
        entry = qindex.internalPointer().ref
        self.crafting_requirements_editor.set_current(entry.id)

    def set_model(self, model):
        self.model = model
        if self.model is None:
            self.parts_tree_model = None
            self.parts_tree_view.setModel(None)
            return

        self.skill_model.update(model.get_relation_data("t9n_skill_pt"))
        self.crafting_requirements_editor.set_model(model, None)
        self.parts_tree_model.update(model)
        self.configure_tree_view()

    def configure_tree_view(self):
        header = self.parts_tree_view.header()
        header.setSectionResizeMode(0, QHeaderView.Stretch)
        header.setSectionResizeMode(1, QHeaderView.ResizeToContents)
        header.setStretchLastSection(False)
        for i in range(2, self.parts_tree_model.columnCount(None)):
            header.hideSection(i)
class WpDatGEditor(WeaponGunEditorWidgetBase, WeaponGunEditorWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setupUi(self)
        self.model = None
        self.item_model = WpDatGTableModel(self)
        self.weapon_tree_view.setModel(self.item_model)
        self.shell_table_model = ShellTableModel()
        self.shell_table_view.setModel(self.shell_table_model)
        self.bottle_table_model = BottleTableModel(self)
        self.mapper = QDataWidgetMapper(self)
        self.mapper.setItemDelegate(ItemDelegate())
        self.mapper.setModel(self.item_model)
        self.bottle_mapper = QDataWidgetMapper(self)
        self.bottle_mapper.setItemDelegate(ItemDelegate())
        self.bottle_mapper.setModel(self.bottle_table_model)
        self.weapon_tree_view.activated.connect(
            self.handle_weapon_tree_view_activated)
        self.skill_model = SkillTranslationModel()
        self.skill_id_value.setModel(self.skill_model)
        self.import_export_manager = ImportExportManager(
            self.weapon_tree_view,
            WpDatGPlugin.import_export.get("safe_attrs"))
        self.import_export_manager.connect_custom_context_menu()
        mappings = [
            (self.id_value, WpDatGEntry.id.index, b"text"),
            (self.name_value, WpDatGEntry.gmd_name_index.index, b"text"),
            (self.description_value, WpDatGEntry.gmd_description_index.index,
             b"text"),
            (self.order_value, WpDatGEntry.order.index),
            (self.tree_id_value, WpDatGEntry.tree_id.index),
            (self.tree_position_value, WpDatGEntry.tree_position.index),
            (self.is_fixed_upgrade_value, WpDatGEntry.is_fixed_upgrade.index,
             b"checked"),
            (self.base_model_id_value, WpDatGEntry.base_model_id.index),
            (self.part1_id_value, WpDatGEntry.part1_id.index),
            (self.part2_id_value, WpDatGEntry.part2_id.index),
            (self.color_value, WpDatGEntry.color.index),
            (self.muzzle_type_value, WpDatGEntry.muzzle_type.index,
             b"currentIndex"),
            (self.barrel_type_value, WpDatGEntry.barrel_type.index,
             b"currentIndex"),
            (self.magazine_type_value, WpDatGEntry.magazine_type.index,
             b"currentIndex"),
            (self.scope_type_value, WpDatGEntry.scope_type.index,
             b"currentIndex"),
            (self.rarity_value, WpDatGEntry.rarity.index),
            (self.cost_value, WpDatGEntry.crafting_cost.index),
            (self.raw_damage_value, WpDatGEntry.raw_damage.index),
            (self.affinity_value, WpDatGEntry.affinity.index),
            (self.defense_value, WpDatGEntry.defense.index),
            (self.deviation_value, WpDatGEntry.deviation.index,
             b"currentIndex"),
            (self.special_ammo_type_value, WpDatGEntry.special_ammo_type.index,
             b"currentIndex"),
            (self.element_id_value, WpDatGEntry.element_id.index,
             b"currentIndex"),
            (self.element_damage_value, WpDatGEntry.element_damage.index),
            (self.hidden_element_id_value, WpDatGEntry.hidden_element_id.index,
             b"currentIndex"),
            (self.hidden_element_damage_value,
             WpDatGEntry.hidden_element_damage.index),
            (self.elderseal_value, WpDatGEntry.elderseal.index,
             b"currentIndex"),
            (self.num_gem_slots, WpDatGEntry.num_gem_slots.index),
            (self.gem_slot1_lvl_value, WpDatGEntry.gem_slot1_lvl.index),
            (self.gem_slot2_lvl_value, WpDatGEntry.gem_slot2_lvl.index),
            (self.gem_slot3_lvl_value, WpDatGEntry.gem_slot3_lvl.index),
            (self.skill_id_value, WpDatGEntry.skill_id.index),
        ]
        for mapping in mappings:
            self.mapper.addMapping(*mapping)
        mappings = [
            (self.close_range_value, BbtblEntry.close_range.index,
             b"currentIndex"),
            (self.power_value, BbtblEntry.power.index, b"currentIndex"),
            (self.paralysis_value, BbtblEntry.paralysis.index,
             b"currentIndex"),
            (self.poison_value, BbtblEntry.poison.index, b"currentIndex"),
            (self.sleep_value, BbtblEntry.sleep.index, b"currentIndex"),
            (self.blast_value, BbtblEntry.blast.index, b"currentIndex"),
        ]
        for mapping in mappings:
            self.bottle_mapper.addMapping(*mapping)

    @property
    def is_bow_type(self):
        return self.model.attrs.get("equip_type") == WeaponType.Bow

    def handle_weapon_tree_view_activated(self, qindex: QModelIndex):
        self.mapper.setCurrentModelIndex(qindex)
        entry = self.item_model.entries[qindex.row()]
        self.tabs_weapon_details.setTabText(
            0, self.item_model.data(qindex, Qt.DisplayRole))
        self.crafting_requirements_editor.set_current(entry.id)
        if self.is_bow_type:
            index = self.bottle_table_model.index(entry.special_ammo_type, 0)
            self.bottle_mapper.setCurrentModelIndex(index)
        else:
            index = self.shell_table_model.index(entry.shell_table_id, 0,
                                                 QModelIndex())
            self.shell_table_view.setRootIndex(index)
            self.shell_table_view.expandAll()

    def set_model(self, model):
        self.model = model
        self.item_model.update(model)
        self.skill_model.update(model.get_relation_data("t9n_skill_pt"))
        self.crafting_requirements_editor\
            .set_model(model, model.attrs.get("equip_type"))
        if self.is_bow_type:
            self.bottle_table_model.update(model)
            self.hide_tab(self.tab_shell_table)
            self.special_ammo_type_value.deleteLater()
            self.special_ammo_type_label.deleteLater()
        else:
            self.shell_table_model.update(model)
            self.hide_tab(self.tab_bottle_table)
        self.configure_tree_view()

    def configure_tree_view(self):
        for index, col in enumerate(self.item_model.fields):
            self.weapon_tree_view.hideColumn(index)
        self.weapon_tree_view.showColumn(WpDatGEntry.id.index)
        self.weapon_tree_view.showColumn(WpDatGEntry.gmd_name_index.index)
        header = self.weapon_tree_view.header()
        header.setSectionResizeMode(WpDatGEntry.gmd_name_index.index,
                                    QHeaderView.Stretch)
        header.setSectionResizeMode(WpDatGEntry.id.index,
                                    QHeaderView.ResizeToContents)

    def hide_tab(self, widget):
        index = self.tabs_weapon_details.indexOf(widget)
        self.tabs_weapon_details.removeTab(index)
Пример #9
0
class ItmEditor(ItmEditorWidgetBase, ItmEditorWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setupUi(self)
        self.model = None
        self.itm_model = ItmTableModel(self)
        self.mapper = QDataWidgetMapper(self)
        self.mapper.setModel(self.itm_model)
        self.item_browser.setModel(self.itm_model)
        self.item_browser.activated.connect(self.handle_item_browser_activated)
        self.mapper.addMapping(self.name_value, Column.name, b"text")
        self.mapper.addMapping(self.id_value, Column.id, b"text")
        self.mapper.addMapping(self.description_value, Column.description,
                               b"text")
        self.mapper.addMapping(self.subtype_value, Column.sub_type,
                               b"currentIndex")
        self.mapper.addMapping(self.type_value, Column.type, b"currentIndex")
        self.mapper.addMapping(self.rarity_value, Column.rarity)
        self.mapper.addMapping(self.carry_limit_value, Column.carry_limit)
        self.mapper.addMapping(self.sort_order_value, Column.order)
        self.mapper.addMapping(self.icon_id_value, Column.icon_id)
        self.mapper.addMapping(self.icon_color_value, Column.icon_color)
        self.mapper.addMapping(self.sell_price_value, Column.sell_price)
        self.mapper.addMapping(self.buy_price_value, Column.buy_price)
        self.add_flag_mapping(self.flag_is_default_item,
                              Column.flag_is_default_item)
        self.add_flag_mapping(self.flag_is_quest_only,
                              Column.flag_is_quest_only)
        self.add_flag_mapping(self.flag_unknown1, Column.flag_unknown1)
        self.add_flag_mapping(self.flag_is_consumable,
                              Column.flag_is_consumable)
        self.add_flag_mapping(self.flag_is_appraisal, Column.flag_is_appraisal)
        self.add_flag_mapping(self.flag_unknown2, Column.flag_unknown2)
        self.add_flag_mapping(self.flag_is_mega, Column.flag_is_mega)
        self.add_flag_mapping(self.flag_is_level_one, Column.flag_is_level_one)
        self.add_flag_mapping(self.flag_is_level_two, Column.flag_is_level_two)
        self.add_flag_mapping(self.flag_is_level_three,
                              Column.flag_is_level_three)
        self.add_flag_mapping(self.flag_is_glitter, Column.flag_is_glitter)
        self.add_flag_mapping(self.flag_is_deliverable,
                              Column.flag_is_deliverable)
        self.add_flag_mapping(self.flag_is_not_shown, Column.flag_is_not_shown)

    def handle_item_browser_activated(self, qindex):
        source_qindex = qindex.model().mapToSource(qindex)
        self.mapper.setCurrentModelIndex(source_qindex)

    def add_flag_mapping(self, widget, flag_column):
        self.mapper.addMapping(widget, flag_column)
        widget.released.connect(self.mapper.submit)

    def set_model(self, model):
        self.model = model
        self.itm_model.update(model)
        if model is not None:
            header = self.item_browser.header()
            # header = self.item_browser.horizontalHeader()
            header.hideSection(Column.description)
            header.setSectionResizeMode(Column.name, QHeaderView.Stretch)
            header.setSectionResizeMode(Column.id, QHeaderView.Fixed)
            header.resizeSection(Column.id, 50)
            header.setStretchLastSection(False)
            for i in range(3, self.itm_model.columnCount(None)):
                header.hideSection(i)
            self.item_browser.sortByColumn(Column.id, Qt.AscendingOrder)
Пример #10
0
class WpDatEditor(WeaponEditorWidgetBase, WeaponEditorWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setupUi(self)
        self.model = None
        self.skill_model = SkillTranslationModel()
        self.table_model = WpDatTableModel(self)
        self.weapon_tree_view.activated.connect(
            self.handle_weapon_tree_view_activated)
        self.kire_widget.set_model(KireGaugeModelEntryAdapter())
        self.mapper = QDataWidgetMapper(self)
        self.mapper.setItemDelegate(ItemDelegate())
        self.mapper.setModel(self.table_model)
        self.skill_id_value.setModel(self.skill_model)
        self.import_export_manager = ImportExportManager(self.weapon_tree_view)
        self.import_export_manager.connect_custom_context_menu()
        mappings = [
            (self.id_value, WpDatEntry.id.index, b"text"),
            (self.name_value, WpDatEntry.gmd_name_index.index, b"text"),
            (self.description_value, WpDatEntry.gmd_description_index.index,
             b"text"),
            (self.order_value, WpDatEntry.order.index),
            (self.tree_id_value, WpDatEntry.tree_id.index),
            (self.tree_position_value, WpDatEntry.tree_position.index),
            (self.is_fixed_upgrade_value, WpDatEntry.is_fixed_upgrade.index,
             b"checked"),
            (self.base_model_id_value, WpDatEntry.base_model_id.index),
            (self.part1_id_value, WpDatEntry.part1_id.index),
            (self.part2_id_value, WpDatEntry.part2_id.index),
            (self.color_value, WpDatEntry.color.index),
            (self.rarity_value, WpDatEntry.rarity.index),
            (self.cost_value, WpDatEntry.crafting_cost.index),
            (self.raw_damage_value, WpDatEntry.raw_damage.index),
            (self.affinity_value, WpDatEntry.affinity.index),
            (self.defense_value, WpDatEntry.defense.index),
            (self.handicraft_value, WpDatEntry.handicraft.index,
             b"currentIndex"),
            (self.element_id_value, WpDatEntry.element_id.index,
             b"currentIndex"),
            (self.element_damage_value, WpDatEntry.element_damage.index),
            (self.hidden_element_id_value, WpDatEntry.hidden_element_id.index,
             b"currentIndex"),
            (self.hidden_element_damage_value,
             WpDatEntry.hidden_element_damage.index),
            (self.elderseal_value, WpDatEntry.elderseal.index,
             b"currentIndex"),
            (self.num_gem_slots, WpDatEntry.num_gem_slots.index),
            (self.gem_slot1_lvl_value, WpDatEntry.gem_slot1_lvl.index),
            (self.gem_slot2_lvl_value, WpDatEntry.gem_slot2_lvl.index),
            (self.gem_slot3_lvl_value, WpDatEntry.gem_slot3_lvl.index),
            (self.skill_id_value, WpDatEntry.skill_id.index),
            (self.wep1_id_value, WpDatEntry.wep1_id.index),
            (self.wep2_id_value, WpDatEntry.wep2_id.index),
            (self.kire_widget, WpDatEntry.kire_id.index),
        ]
        for mapping in mappings:
            self.mapper.addMapping(*mapping)

    def handle_weapon_tree_view_activated(self, qindex: QModelIndex):
        self.mapper.setCurrentModelIndex(qindex)
        entry = self.table_model.entries[qindex.row()]
        self.crafting_requirements_editor.set_current(entry.id)

    def set_model(self, model):
        self.model = model
        self.skill_model.update(model.get_relation_data("t9n_skill_pt"))
        self.table_model.update(self.model)
        self.weapon_tree_view.setModel(self.table_model)
        self.crafting_requirements_editor.set_model(model,
                                                    self.get_equip_type())
        self.configure_tree_view()

    def configure_tree_view(self):
        for index, col in enumerate(self.table_model.columns):
            self.weapon_tree_view.hideColumn(index)
        self.weapon_tree_view.showColumn(WpDatEntry.id.index)
        self.weapon_tree_view.showColumn(WpDatEntry.gmd_name_index.index)
        header = self.weapon_tree_view.header()
        header.setSectionResizeMode(WpDatEntry.gmd_name_index.index,
                                    QHeaderView.Stretch)
        header.setSectionResizeMode(WpDatEntry.id.index,
                                    QHeaderView.ResizeToContents)

    def get_equip_type(self):
        return self.model.attrs.get("equip_type")
Пример #11
0
class RecordFormView(QMainWindow):
    """ Record form view - main view for editing individual records
        Params -
            ui_file - path to designer file
            data_model - database model
            subviews - subviews to insert into this view
                       there must be a QWidget placeholder named [subview_name]_placeholder to insert the subview into
            window_title - obvious
            window_icon - obvious
        Events -
            pre_save - fired before saving the record to database
            post_save - fired after saving the record to database
        """

    ui_file = ''  # type: str
    data_model = None
    subviews = {}

    window_title = None
    window_icon = None

    pre_save = pyqtSignal(QSqlRecord)
    post_save = pyqtSignal()

    def __init__(self, model=None, ui_file: str = None) -> None:
        super().__init__()
        loadUi(ui_file or self.ui_file, self)

        self.row = None
        self.record = None

        self.new_record = False
        self.parent_view = None
        self.sub_views = []

        if self.window_title:
            self.setWindowTitle(self.window_title)

        if self.window_icon:
            self.setWindowIcon(QIcon(self.window_icon))

        # allow passing a model class or instances
        model = model or self.model
        if inspect.isclass(model):
            self.data_model = model()
        else:
            self.data_model = model

        # update subviews on row change
        self.data_mapper = QDataWidgetMapper()
        self.data_mapper.setModel(self.data_model)
        self.data_mapper.currentIndexChanged.connect(self._update_subviews)

        # auto map fields to db columns
        for field in self.data_model.fields:
            if hasattr(self, field.name):
                self.data_mapper.addMapping(getattr(self, field.name),
                                            field.index)

        # setup subviews
        for view_name, view_cls in self.subviews.items():
            placeholder_name = '{0}_placeholder'.format(view_name)
            try:
                placeholder = getattr(self, placeholder_name)
            except AttributeError:
                raise ImproperlyConfigured(
                    'Unable to find subview placeholder ' + placeholder_name)

            view = view_cls()
            self.sub_views.append(view)
            view.set_parent_view(self)
            layout = placeholder.layout()
            if not layout:
                layout = QVBoxLayout(placeholder)
            layout.setContentsMargins(0, 0, 0, 0)
            layout.addWidget(view)
            setattr(self, view_name, view)

        if hasattr(self, 'setup_ui'):
            self.setup_ui()

    def set_read_only(self, read_only):
        """ Set form as read only """
        for field in self.data_model.fields:
            if hasattr(self, field.name):
                widget = getattr(self, field.name)
                widget.setDisabled(read_only)

    def set_parent_view(self, view):
        """ Used by subviews to designate their parent - not usually called by user """
        self.parent_view = view

    def set_record_index(self, model_index: QModelIndex) -> None:
        """ Set active record by model index """
        if not model_index:
            self.setDisabled(True)
            return

        self.row = model_index.row()
        self.index = model_index
        self.record = self.data_model.record(self.row)
        self.setEnabled(True)
        self.data_mapper.setCurrentModelIndex(model_index)

    def save_record(self) -> bool:
        """ Save active record """
        index = self.data_mapper.currentIndex()
        record = self.data_mapper.model().record(index)
        self.pre_save.emit(record)

        saved = self.data_model.submitAll()
        if not saved:
            QMessageBox.critical(
                self, 'Save record', 'Unable to save record\n{0}'.format(
                    self.data_model.lastError().text()))
            return False

        self.post_save.emit()
        return True

    # row change event handler
    def _update_subviews(self, index: int):
        record = self.data_model.record(index)
        id = record.value(self.data_model.id_field_name)

        # refresh subviews
        for view_name, view_cls in self.subviews.items():
            view = getattr(self, view_name)
            model = view.data_model
            model.set_related_id(id)
            model.select()
Пример #12
0
class MainWindow(QMainWindow):
    """
    The main window class
    """
    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)
        self.ui = uic.loadUi("gui/main_window.ui")

        self.timestamp_filename = None
        self.video_filename = None
        self.media_start_time = None
        self.media_end_time = None
        self.restart_needed = False
        self.timer_period = 100
        self.is_full_screen = False
        self.media_started_playing = False
        self.media_is_playing = False
        self.original_geometry = None
        self.mute = False

        self.timestamp_model = TimestampModel(None, self)
        self.proxy_model = QSortFilterProxyModel(self)
        self.ui.list_timestamp.setModel(self.timestamp_model)
        self.ui.list_timestamp.doubleClicked.connect(
            lambda event: self.ui.list_timestamp.indexAt(event.pos()).isValid()
            and self.run()
        )

        self.timer = QTimer()
        self.timer.timeout.connect(self.update_ui)
        self.timer.timeout.connect(self.timer_handler)
        self.timer.start(self.timer_period)

        self.vlc_instance = vlc.Instance()
        self.media_player = self.vlc_instance.media_player_new()
        # if sys.platform == "darwin":  # for MacOS
        #     self.ui.frame_video = QMacCocoaViewContainer(0)

        self.ui.frame_video.doubleClicked.connect(self.toggle_full_screen)
        self.ui.frame_video.wheel.connect(self.wheel_handler)
        self.ui.frame_video.keyPressed.connect(self.key_handler)

        # Set up buttons
        self.ui.button_run.clicked.connect(self.run)
        self.ui.button_timestamp_browse.clicked.connect(
            self.browse_timestamp_handler
        )
        self.ui.button_video_browse.clicked.connect(
            self.browse_video_handler
        )

        self.play_pause_model = ToggleButtonModel(None, self)
        self.play_pause_model.setStateMap(
            {
                True: {
                    "text": "",
                    "icon": qta.icon("fa.play", scale_factor=0.7)
                },
                False: {
                    "text": "",
                    "icon": qta.icon("fa.pause", scale_factor=0.7)
                }
            }
        )
        self.ui.button_play_pause.setModel(self.play_pause_model)
        self.ui.button_play_pause.clicked.connect(self.play_pause)

        self.mute_model = ToggleButtonModel(None, self)
        self.mute_model.setStateMap(
            {
                True: {
                    "text": "",
                    "icon": qta.icon("fa.volume-up", scale_factor=0.8)
                },
                False: {
                    "text": "",
                    "icon": qta.icon("fa.volume-off", scale_factor=0.8)
                }
            }
        )
        self.ui.button_mute_toggle.setModel(self.mute_model)
        self.ui.button_mute_toggle.clicked.connect(self.toggle_mute)

        self.ui.button_full_screen.setIcon(
            qta.icon("ei.fullscreen", scale_factor=0.6)
        )
        self.ui.button_full_screen.setText("")
        self.ui.button_full_screen.clicked.connect(self.toggle_full_screen)
        self.ui.button_speed_up.clicked.connect(self.speed_up_handler)
        self.ui.button_speed_up.setIcon(
            qta.icon("fa.arrow-circle-o-up", scale_factor=0.8)
        )
        self.ui.button_speed_up.setText("")
        self.ui.button_slow_down.clicked.connect(self.slow_down_handler)
        self.ui.button_slow_down.setIcon(
            qta.icon("fa.arrow-circle-o-down", scale_factor=0.8)
        )
        self.ui.button_slow_down.setText("")
        self.ui.button_mark_start.setIcon(
            qta.icon("fa.quote-left", scale_factor=0.7)
        )
        self.ui.button_mark_start.setText("")
        self.ui.button_mark_end.setIcon(
            qta.icon("fa.quote-right", scale_factor=0.7)
        )
        self.ui.button_mark_end.setText("")
        self.ui.button_add_entry.clicked.connect(self.add_entry)
        self.ui.button_remove_entry.clicked.connect(self.remove_entry)

        self.ui.button_mark_start.clicked.connect(
            lambda: self.set_mark(start_time=int(
                self.media_player.get_position() *
                self.media_player.get_media().get_duration()))
        )
        self.ui.button_mark_end.clicked.connect(
            lambda: self.set_mark(end_time=int(
                self.media_player.get_position() *
                self.media_player.get_media().get_duration()))
        )

        self.ui.slider_progress.setTracking(False)
        self.ui.slider_progress.valueChanged.connect(self.set_media_position)
        self.ui.slider_volume.valueChanged.connect(self.set_volume)
        self.ui.entry_description.setReadOnly(True)

        # Mapper between the table and the entry detail
        self.mapper = QDataWidgetMapper()
        self.mapper.setSubmitPolicy(QDataWidgetMapper.ManualSubmit)
        self.ui.button_save.clicked.connect(self.mapper.submit)

        # Set up default volume
        self.set_volume(self.ui.slider_volume.value())

        self.vlc_events = self.media_player.event_manager()
        self.vlc_events.event_attach(
            vlc.EventType.MediaPlayerTimeChanged, self.media_time_change_handler
        )

        # Let our application handle mouse and key input instead of VLC
        self.media_player.video_set_mouse_input(False)
        self.media_player.video_set_key_input(False)

        self.ui.show()


    def add_entry(self):
        if not self.timestamp_filename:
            self._show_error("You haven't chosen a timestamp file yet")
        row_num = self.timestamp_model.rowCount()
        self.timestamp_model.insertRow(row_num)
        start_cell = self.timestamp_model.index(row_num, 0)
        end_cell = self.timestamp_model.index(row_num, 1)
        self.timestamp_model.setData(start_cell, TimestampDelta.from_string(""))
        self.timestamp_model.setData(end_cell, TimestampDelta.from_string(""))

    def remove_entry(self):
        if not self.timestamp_filename:
            self._show_error("You haven't chosen a timestamp file yet")
        selected = self.ui.list_timestamp.selectionModel().selectedIndexes()
        if len(selected) == 0:
            return
        self.proxy_model.removeRow(selected[0].row()) and self.mapper.submit()


    def set_media_position(self, position):
        percentage = position / 10000.0
        self.media_player.set_position(percentage)
        absolute_position = percentage * \
            self.media_player.get_media().get_duration()
        if absolute_position > self.media_end_time:
            self.media_end_time = -1

    def set_mark(self, start_time=None, end_time=None):
        if len(self.ui.list_timestamp.selectedIndexes()) == 0:
            blankRowIndex = self.timestamp_model.blankRowIndex()
            if not blankRowIndex.isValid():
                self.add_entry()
            else:
                index = self.proxy_model.mapFromSource(blankRowIndex)
                self.ui.list_timestamp.selectRow(index.row())
        selectedIndexes = self.ui.list_timestamp.selectedIndexes()
        if start_time:
            self.proxy_model.setData(selectedIndexes[0],
                                     TimestampDelta.string_from_int(
                                         start_time))
        if end_time:
            self.proxy_model.setData(selectedIndexes[1],
                                     TimestampDelta.string_from_int(
                                         end_time))

    def update_ui(self):
        self.ui.slider_progress.blockSignals(True)
        self.ui.slider_progress.setValue(
            self.media_player.get_position() * 10000
        )
        # When the video finishes
        self.ui.slider_progress.blockSignals(False)
        if self.media_started_playing and \
           self.media_player.get_media().get_state() == vlc.State.Ended:
            self.play_pause_model.setState(True)
            # Apparently we need to reset the media, otherwise the player
            # won't play at all
            self.media_player.set_media(self.media_player.get_media())
            self.set_volume(self.ui.slider_volume.value())
            self.media_is_playing = False
            self.media_started_playing = False
            self.run()

    def timer_handler(self):
        """
        This is a workaround, because for some reason we can't call set_time()
        inside the MediaPlayerTimeChanged handler (as the video just stops
        playing)
        """
        if self.restart_needed:
            self.media_player.set_time(self.media_start_time)
            self.restart_needed = False

    def key_handler(self, event):
        if event.key() == Qt.Key_Escape and self.is_full_screen:
            self.toggle_full_screen()
        if event.key() == Qt.Key_F:
            self.toggle_full_screen()
        if event.key() == Qt.Key_Space:
            self.play_pause()

    def wheel_handler(self, event):
        self.modify_volume(1 if event.angleDelta().y() > 0 else -1)

    def toggle_mute(self):
        self.media_player.audio_set_mute(not self.media_player.audio_get_mute())
        self.mute = not self.mute
        self.mute_model.setState(not self.mute)

    def modify_volume(self, delta_percent):
        new_volume = self.media_player.audio_get_volume() + delta_percent
        if new_volume < 0:
            new_volume = 0
        elif new_volume > 40:
            new_volume = 40
        self.media_player.audio_set_volume(new_volume)
        self.ui.slider_volume.setValue(self.media_player.audio_get_volume())

    def set_volume(self, new_volume):
        self.media_player.audio_set_volume(new_volume)

    def speed_up_handler(self):
        self.modify_rate(0.1)

    def slow_down_handler(self):
        self.modify_rate(-0.1)

    def modify_rate(self, delta_percent):
        new_rate = self.media_player.get_rate() + delta_percent
        if new_rate < 0.2 or new_rate > 2.0:
            return
        self.media_player.set_rate(new_rate)

    def media_time_change_handler(self, _):
        if self.media_end_time == -1:
            return
        if self.media_player.get_time() > self.media_end_time:
            self.restart_needed = True

    def update_slider_highlight(self):
        if self.ui.list_timestamp.selectionModel().hasSelection():
            selected_row = self.ui.list_timestamp.selectionModel(). \
                selectedRows()[0]
            self.media_start_time = self.ui.list_timestamp.model().data(
                selected_row.model().index(selected_row.row(), 0),
                Qt.UserRole
            )
            self.media_end_time = self.ui.list_timestamp.model().data(
                selected_row.model().index(selected_row.row(), 1),
                Qt.UserRole
            )
            duration = self.media_player.get_media().get_duration()
            self.media_end_time = self.media_end_time \
                if self.media_end_time != 0 else duration
            if self.media_start_time > self.media_end_time:
                raise ValueError("Start time cannot be later than end time")
            if self.media_start_time > duration:
                raise ValueError("Start time not within video duration")
            if self.media_end_time > duration:
                raise ValueError("End time not within video duration")
            slider_start_pos = (self.media_start_time / duration) * \
                               (self.ui.slider_progress.maximum() -
                                self.ui.slider_progress.minimum())
            slider_end_pos = (self.media_end_time / duration) * \
                             (self.ui.slider_progress.maximum() -
                              self.ui.slider_progress.minimum())
            self.ui.slider_progress.setHighlight(
                int(slider_start_pos), int(slider_end_pos)
            )

        else:
            self.media_start_time = 0
            self.media_end_time = -1


    def run(self):
        """
        Execute the loop
        """
        if self.timestamp_filename is None:
            self._show_error("No timestamp file chosen")
            return
        if self.video_filename is None:
            self._show_error("No video file chosen")
            return
        try:
            self.update_slider_highlight()
            self.media_player.play()
            self.media_player.set_time(self.media_start_time)
            self.media_started_playing = True
            self.media_is_playing = True
            self.play_pause_model.setState(False)
        except Exception as ex:
            self._show_error(str(ex))
            print(traceback.format_exc())

    def play_pause(self):
        """Toggle play/pause status
        """
        if not self.media_started_playing:
            self.run()
            return
        if self.media_is_playing:
            self.media_player.pause()
        else:
            self.media_player.play()
        self.media_is_playing = not self.media_is_playing
        self.play_pause_model.setState(not self.media_is_playing)

    def toggle_full_screen(self):
        if self.is_full_screen:
            # TODO Artifacts still happen some time when exiting full screen
            # in X11
            self.ui.frame_media.showNormal()
            self.ui.frame_media.restoreGeometry(self.original_geometry)
            self.ui.frame_media.setParent(self.ui.widget_central)
            self.ui.layout_main.addWidget(self.ui.frame_media, 2, 3, 3, 1)
            # self.ui.frame_media.ensurePolished()
        else:
            self.ui.frame_media.setParent(None)
            self.ui.frame_media.setWindowFlags(Qt.FramelessWindowHint |
                                               Qt.CustomizeWindowHint)
            self.original_geometry = self.ui.frame_media.saveGeometry()
            desktop = QApplication.desktop()
            rect = desktop.screenGeometry(desktop.screenNumber(QCursor.pos()))
            self.ui.frame_media.setGeometry(rect)
            self.ui.frame_media.showFullScreen()
            self.ui.frame_media.show()
        self.ui.frame_video.setFocus()
        self.is_full_screen = not self.is_full_screen

    def browse_timestamp_handler(self):
        """
        Handler when the timestamp browser button is clicked
        """
        tmp_name, _ = QFileDialog.getOpenFileName(
            self, "Choose Timestamp file", None,
            "Timestamp File (*.tmsp);;All Files (*)"
        )
        if not tmp_name:
            return
        self.set_timestamp_filename(QDir.toNativeSeparators(tmp_name))

    def _sort_model(self):
        self.ui.list_timestamp.sortByColumn(0, Qt.AscendingOrder)

    def _select_blank_row(self, parent, start, end):
        self.ui.list_timestamp.selectRow(start)

    def set_timestamp_filename(self, filename):
        """
        Set the timestamp file name
        """
        if not os.path.isfile(filename):
            self._show_error("Cannot access timestamp file " + filename)
            return

        try:
            self.timestamp_model = TimestampModel(filename, self)
            self.timestamp_model.timeParseError.connect(
                lambda err: self._show_error(err)
            )
            self.proxy_model.setSortRole(Qt.UserRole)
            self.proxy_model.dataChanged.connect(self._sort_model)
            self.proxy_model.dataChanged.connect(self.update_slider_highlight)
            self.proxy_model.setSourceModel(self.timestamp_model)
            self.proxy_model.rowsInserted.connect(self._sort_model)
            self.proxy_model.rowsInserted.connect(self._select_blank_row)
            self.ui.list_timestamp.setModel(self.proxy_model)

            self.timestamp_filename = filename
            self.ui.entry_timestamp.setText(self.timestamp_filename)

            self.mapper.setModel(self.proxy_model)
            self.mapper.addMapping(self.ui.entry_start_time, 0)
            self.mapper.addMapping(self.ui.entry_end_time, 1)
            self.mapper.addMapping(self.ui.entry_description, 2)
            self.ui.list_timestamp.selectionModel().selectionChanged.connect(
                self.timestamp_selection_changed)
            self._sort_model()

            directory = os.path.dirname(self.timestamp_filename)
            basename = os.path.basename(self.timestamp_filename)
            timestamp_name_without_ext = os.path.splitext(basename)[0]
            for file_in_dir in os.listdir(directory):
                current_filename = os.path.splitext(file_in_dir)[0]
                found_video = (current_filename == timestamp_name_without_ext
                               and file_in_dir != basename)
                if found_video:
                    found_video_file = os.path.join(directory, file_in_dir)
                    self.set_video_filename(found_video_file)
                    break
        except ValueError as err:
            self._show_error("Timestamp file is invalid")

    def timestamp_selection_changed(self, selected, deselected):
        if len(selected) > 0:
            self.mapper.setCurrentModelIndex(selected.indexes()[0])
            self.ui.button_save.setEnabled(True)
            self.ui.button_remove_entry.setEnabled(True)
            self.ui.entry_start_time.setReadOnly(False)
            self.ui.entry_end_time.setReadOnly(False)
            self.ui.entry_description.setReadOnly(False)
        else:
            self.mapper.setCurrentModelIndex(QModelIndex())
            self.ui.button_save.setEnabled(False)
            self.ui.button_remove_entry.setEnabled(False)
            self.ui.entry_start_time.clear()
            self.ui.entry_end_time.clear()
            self.ui.entry_description.clear()
            self.ui.entry_start_time.setReadOnly(True)
            self.ui.entry_end_time.setReadOnly(True)
            self.ui.entry_description.setReadOnly(True)

    def set_video_filename(self, filename):
        """
        Set the video filename
        """
        if not os.path.isfile(filename):
            self._show_error("Cannot access video file " + filename)
            return

        self.video_filename = filename

        media = self.vlc_instance.media_new(self.video_filename)
        media.parse()
        if not media.get_duration():
            self._show_error("Cannot play this media file")
            self.media_player.set_media(None)
            self.video_filename = None
        else:
            self.media_player.set_media(media)
            if sys.platform.startswith('linux'): # for Linux using the X Server
                self.media_player.set_xwindow(self.ui.frame_video.winId())
            elif sys.platform == "win32": # for Windows
                self.media_player.set_hwnd(self.ui.frame_video.winId())
            elif sys.platform == "darwin": # for MacOS
                self.media_player.set_nsobject(self.ui.frame_video.winId())
            self.ui.entry_video.setText(self.video_filename)
            self.media_started_playing = False
            self.media_is_playing = False
            self.set_volume(self.ui.slider_volume.value())
            self.play_pause_model.setState(True)

    def browse_video_handler(self):
        """
        Handler when the video browse button is clicked
        """
        tmp_name, _ = QFileDialog.getOpenFileName(
            self, "Choose Video file", None,
            "All Files (*)"
        )
        if not tmp_name:
            return
        self.set_video_filename(QDir.toNativeSeparators(tmp_name))

    def _show_error(self, message, title="Error"):
        QMessageBox.warning(self, title, message)
Пример #13
0
class MainWindow(QMainWindow):
    """
    The main window class
    """
    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)
        self.ui = uic.loadUi("gui/main_window.ui")

        self.timestamp_filename = None
        self.video_filename = None
        self.media_start_time = None
        self.media_end_time = None
        self.restart_needed = False
        self.timer_period = 100
        self.is_full_screen = False
        self.media_started_playing = False
        self.media_is_playing = False
        self.original_geometry = None
        self.mute = False

        self.timestamp_model = TimestampModel(None, self)
        self.proxy_model = QSortFilterProxyModel(self)
        self.ui.list_timestamp.setModel(self.timestamp_model)
        self.ui.list_timestamp.doubleClicked.connect(
            lambda event: self.ui.list_timestamp.indexAt(event.pos()).isValid()
            and self.run()
        )

        self.timer = QTimer()
        self.timer.timeout.connect(self.update_ui)
        self.timer.timeout.connect(self.timer_handler)
        self.timer.start(self.timer_period)

        self.vlc_instance = vlc.Instance()
        self.media_player = self.vlc_instance.media_player_new()
        # if sys.platform == "darwin":  # for MacOS
        #     self.ui.frame_video = QMacCocoaViewContainer(0)

        self.ui.frame_video.doubleClicked.connect(self.toggle_full_screen)
        self.ui.frame_video.wheel.connect(self.wheel_handler)
        self.ui.frame_video.keyPressed.connect(self.key_handler)

        # Set up Labels:
        # self.ui.lblVideoName.

        # self.displayed_video_title = bind("self.ui.lblVideoName", "text", str)
        self.ui.lblVideoName.setText(self.video_filename)
        self.ui.lblVideoSubtitle.setText("")
        self.ui.dateTimeEdit.setHidden(True)
        self.ui.lblCurrentFrame.setText("")
        self.ui.lblTotalFrames.setText("")

        self.ui.lblCurrentTime.setText("")
        self.ui.lblTotalDuration.setText("")

        self.ui.lblFileFPS.setText("")

        self.ui.spinBoxFrameJumpMultiplier.value = 1



        # Set up buttons
        self.ui.button_run.clicked.connect(self.run)
        self.ui.button_timestamp_browse.clicked.connect(
            self.browse_timestamp_handler
        )
        self.ui.button_timestamp_create.clicked.connect(
            self.create_timestamp_file_handler
        )
        self.ui.button_video_browse.clicked.connect(
            self.browse_video_handler
        )


        # Set up directional buttons
        self.ui.btnSkipLeft.clicked.connect(self.skip_left_handler)
        self.ui.btnSkipRight.clicked.connect(self.skip_right_handler)
        self.ui.btnLeft.clicked.connect(self.seek_left_handler)
        self.ui.btnRight.clicked.connect(self.seek_right_handler)

        self.play_pause_model = ToggleButtonModel(None, self)
        self.play_pause_model.setStateMap(
            {
                True: {
                    "text": "",
                    "icon": qta.icon("fa.play", scale_factor=0.7)
                },
                False: {
                    "text": "",
                    "icon": qta.icon("fa.pause", scale_factor=0.7)
                }
            }
        )
        self.ui.button_play_pause.setModel(self.play_pause_model)
        self.ui.button_play_pause.clicked.connect(self.play_pause)

        self.mute_model = ToggleButtonModel(None, self)
        self.mute_model.setStateMap(
            {
                True: {
                    "text": "",
                    "icon": qta.icon("fa.volume-up", scale_factor=0.8)
                },
                False: {
                    "text": "",
                    "icon": qta.icon("fa.volume-off", scale_factor=0.8)
                }
            }
        )
        self.ui.button_mute_toggle.setModel(self.mute_model)
        self.ui.button_mute_toggle.clicked.connect(self.toggle_mute)

        self.ui.button_full_screen.setIcon(
            qta.icon("ei.fullscreen", scale_factor=0.6)
        )
        self.ui.button_full_screen.setText("")
        self.ui.button_full_screen.clicked.connect(self.toggle_full_screen)
        self.ui.button_speed_up.clicked.connect(self.speed_up_handler)
        self.ui.button_speed_up.setIcon(
            qta.icon("fa.arrow-circle-o-up", scale_factor=0.8)
        )
        self.ui.button_speed_up.setText("")
        self.ui.button_slow_down.clicked.connect(self.slow_down_handler)
        self.ui.button_slow_down.setIcon(
            qta.icon("fa.arrow-circle-o-down", scale_factor=0.8)
        )
        self.ui.button_slow_down.setText("")
        self.ui.button_mark_start.setIcon(
            qta.icon("fa.quote-left", scale_factor=0.7)
        )
        self.ui.button_mark_start.setText("")
        self.ui.button_mark_end.setIcon(
            qta.icon("fa.quote-right", scale_factor=0.7)
        )
        self.ui.button_mark_end.setText("")
        self.ui.button_add_entry.clicked.connect(self.add_entry)
        self.ui.button_remove_entry.clicked.connect(self.remove_entry)

        self.ui.button_mark_start.clicked.connect(
            lambda: self.set_mark(start_time=int(
                self.media_player.get_position() *
                self.media_player.get_media().get_duration()))
        )
        self.ui.button_mark_end.clicked.connect(
            lambda: self.set_mark(end_time=int(
                self.media_player.get_position() *
                self.media_player.get_media().get_duration()))
        )

        self.ui.slider_progress.setTracking(False)
        self.ui.slider_progress.valueChanged.connect(self.set_media_position)
        self.ui.slider_volume.valueChanged.connect(self.set_volume)
        self.ui.entry_description.setReadOnly(True)

        # Mapper between the table and the entry detail
        self.mapper = QDataWidgetMapper()
        self.mapper.setSubmitPolicy(QDataWidgetMapper.ManualSubmit)
        self.ui.button_save.clicked.connect(self.mapper.submit)

        # Set up default volume
        self.set_volume(self.ui.slider_volume.value())

        self.vlc_events = self.media_player.event_manager()
        self.vlc_events.event_attach(
            vlc.EventType.MediaPlayerTimeChanged, self.media_time_change_handler
        )

        # Let our application handle mouse and key input instead of VLC
        self.media_player.video_set_mouse_input(False)
        self.media_player.video_set_key_input(False)

        self.ui.show()


    def add_entry(self):
        if not self.timestamp_filename:
            self._show_error("You haven't chosen a timestamp file yet")
        row_num = self.timestamp_model.rowCount()
        self.timestamp_model.insertRow(row_num)
        start_cell = self.timestamp_model.index(row_num, 0)
        end_cell = self.timestamp_model.index(row_num, 1)
        self.timestamp_model.setData(start_cell, TimestampDelta.from_string(""))
        self.timestamp_model.setData(end_cell, TimestampDelta.from_string(""))

    def remove_entry(self):
        if not self.timestamp_filename:
            self._show_error("You haven't chosen a timestamp file yet")
        selected = self.ui.list_timestamp.selectionModel().selectedIndexes()
        if len(selected) == 0:
            return
        self.proxy_model.removeRow(selected[0].row()) and self.mapper.submit()


    def set_media_position(self, position):
        percentage = position / 10000.0
        self.media_player.set_position(percentage)
        absolute_position = percentage * \
            self.media_player.get_media().get_duration()
        if absolute_position > self.media_end_time:
            self.media_end_time = -1

    def set_mark(self, start_time=None, end_time=None):
        if len(self.ui.list_timestamp.selectedIndexes()) == 0:
            blankRowIndex = self.timestamp_model.blankRowIndex()
            if not blankRowIndex.isValid():
                self.add_entry()
            else:
                index = self.proxy_model.mapFromSource(blankRowIndex)
                self.ui.list_timestamp.selectRow(index.row())
        selectedIndexes = self.ui.list_timestamp.selectedIndexes()
        if start_time:
            self.proxy_model.setData(selectedIndexes[0],
                                     TimestampDelta.string_from_int(
                                         start_time))
        if end_time:
            self.proxy_model.setData(selectedIndexes[1],
                                     TimestampDelta.string_from_int(
                                         end_time))

    def update_ui(self):
        self.ui.slider_progress.blockSignals(True)
        self.ui.slider_progress.setValue(
            self.media_player.get_position() * 10000
        )
        #print(self.media_player.get_position() * 10000)

        self.update_video_file_play_labels()

        # When the video finishes
        self.ui.slider_progress.blockSignals(False)
        if self.media_started_playing and \
           self.media_player.get_media().get_state() == vlc.State.Ended:
            self.play_pause_model.setState(True)
            # Apparently we need to reset the media, otherwise the player
            # won't play at all
            self.media_player.set_media(self.media_player.get_media())
            self.set_volume(self.ui.slider_volume.value())
            self.media_is_playing = False
            self.media_started_playing = False
            self.run()

    def timer_handler(self):
        """
        This is a workaround, because for some reason we can't call set_time()
        inside the MediaPlayerTimeChanged handler (as the video just stops
        playing)
        """
        if self.restart_needed:
            self.media_player.set_time(self.media_start_time)
            self.restart_needed = False

    def key_handler(self, event):
        if event.key() == Qt.Key_Escape and self.is_full_screen:
            self.toggle_full_screen()
        if event.key() == Qt.Key_F:
            self.toggle_full_screen()
        if event.key() == Qt.Key_Space:
            self.play_pause()

    def wheel_handler(self, event):
        self.modify_volume(1 if event.angleDelta().y() > 0 else -1)

    def toggle_mute(self):
        self.media_player.audio_set_mute(not self.media_player.audio_get_mute())
        self.mute = not self.mute
        self.mute_model.setState(not self.mute)

    def modify_volume(self, delta_percent):
        new_volume = self.media_player.audio_get_volume() + delta_percent
        if new_volume < 0:
            new_volume = 0
        elif new_volume > 40:
            new_volume = 40
        self.media_player.audio_set_volume(new_volume)
        self.ui.slider_volume.setValue(self.media_player.audio_get_volume())

    def set_volume(self, new_volume):
        self.media_player.audio_set_volume(new_volume)

    def speed_up_handler(self):
        self.modify_rate(0.1)

    def slow_down_handler(self):
        self.modify_rate(-0.1)

    def modify_rate(self, delta_percent):
        new_rate = self.media_player.get_rate() + delta_percent
        if new_rate < 0.2 or new_rate > 2.0:
            return
        self.media_player.set_rate(new_rate)

    def media_time_change_handler(self, _):
        if self.media_end_time == -1:
            return
        if self.media_player.get_time() > self.media_end_time:
            self.restart_needed = True

    def update_slider_highlight(self):
        if self.ui.list_timestamp.selectionModel().hasSelection():
            selected_row = self.ui.list_timestamp.selectionModel(). \
                selectedRows()[0]
            self.media_start_time = self.ui.list_timestamp.model().data(
                selected_row.model().index(selected_row.row(), 0),
                Qt.UserRole
            )
            self.media_end_time = self.ui.list_timestamp.model().data(
                selected_row.model().index(selected_row.row(), 1),
                Qt.UserRole
            )
            duration = self.media_player.get_media().get_duration()
            self.media_end_time = self.media_end_time \
                if self.media_end_time != 0 else duration
            if self.media_start_time > self.media_end_time:
                raise ValueError("Start time cannot be later than end time")
            if self.media_start_time > duration:
                raise ValueError("Start time not within video duration")
            if self.media_end_time > duration:
                raise ValueError("End time not within video duration")
            slider_start_pos = (self.media_start_time / duration) * \
                               (self.ui.slider_progress.maximum() -
                                self.ui.slider_progress.minimum())
            slider_end_pos = (self.media_end_time / duration) * \
                             (self.ui.slider_progress.maximum() -
                              self.ui.slider_progress.minimum())
            self.ui.slider_progress.setHighlight(
                int(slider_start_pos), int(slider_end_pos)
            )

        else:
            self.media_start_time = 0
            self.media_end_time = -1


    def run(self):
        """
        Execute the loop
        """
        if self.timestamp_filename is None:
            self._show_error("No timestamp file chosen")
            return
        if self.video_filename is None:
            self._show_error("No video file chosen")
            return
        try:
            self.update_slider_highlight()
            self.media_player.play()
            self.media_player.set_time(self.media_start_time)
            self.media_started_playing = True
            self.media_is_playing = True
            self.play_pause_model.setState(False)
        except Exception as ex:
            self._show_error(str(ex))
            print(traceback.format_exc())

    def play_pause(self):
        """Toggle play/pause status
        """
        if not self.media_started_playing:
            self.run()
            return
        if self.media_is_playing:
            self.media_player.pause()
        else:
            self.media_player.play()
        self.media_is_playing = not self.media_is_playing
        self.play_pause_model.setState(not self.media_is_playing)

    def update_video_file_play_labels(self):
        curr_total_fps = self.media_player.get_fps()
        curr_total_duration = self.media_player.get_length()
        totalNumFrames = int(curr_total_duration * curr_total_fps)
        if totalNumFrames > 0:
            self.ui.lblTotalFrames.setText(str(totalNumFrames))
        else:
            self.ui.lblTotalFrames.setText("--")
        if curr_total_duration > 0:
            self.ui.lblTotalDuration.setText(str(curr_total_duration))  # Gets duration in [ms]
        else:
            self.ui.lblTotalDuration.setText("--")

        # Changing Values: Dynamically updated each time the playhead changes
        curr_percent_complete = self.media_player.get_position()  # Current percent complete between 0.0 and 1.0

        if curr_percent_complete >= 0:
            self.ui.lblPlaybackPercent.setText(str(curr_percent_complete))
        else:
            self.ui.lblPlaybackPercent.setText("--")

        curr_frame = int(round(curr_percent_complete * totalNumFrames))

        if curr_frame >= 0:
            self.ui.lblCurrentFrame.setText(str(curr_frame))
        else:
            self.ui.lblCurrentFrame.setText("--")

        if self.media_player.get_time() >= 0:
            self.ui.lblCurrentTime.setText(str(self.media_player.get_time()) + "[ms]")  # Gets time in [ms]
        else:
            self.ui.lblCurrentTime.setText("-- [ms]")  # Gets time in [ms]






    # Called only when the video file changes:
    def update_video_file_labels_on_file_change(self):
        if self.video_filename is None:
            self.ui.lblVideoName.setText("")
        else:
            self.ui.lblVideoName.setText(self.video_filename)
            # Only updated when the video file is changed:
            curr_total_fps = self.media_player.get_fps()
            self.ui.lblFileFPS.setText(str(curr_total_fps))

            curr_total_duration = self.media_player.get_length()
            totalNumFrames = int(curr_total_duration * curr_total_fps)
            if totalNumFrames > 0:
                self.ui.lblTotalFrames.setText(str(totalNumFrames))
            else:
                self.ui.lblTotalFrames.setText("--")

            if curr_total_duration > 0:
                self.ui.lblTotalDuration.setText(str(curr_total_duration)) # Gets duration in [ms]
            else:
                self.ui.lblTotalDuration.setText("--")

            self.update_video_file_play_labels()

    def get_frame_multipler(self):
        return self.ui.spinBoxFrameJumpMultiplier.value

    # def compute_total_number_frames(self):
    #     self.media_player.get_length()


    def seek_left_handler(self):
        print('seek: left')
        self.seek_frames(-10 * self.get_frame_multipler())

    def skip_left_handler(self):
        print('skip: left')
        self.seek_frames(-1 * self.get_frame_multipler())

    def seek_right_handler(self):
        print('seek: right')
        self.seek_frames(10 * self.get_frame_multipler())

    def skip_right_handler(self):
        print('skip: right')
        self.seek_frames(1 * self.get_frame_multipler())

    def seek_frames(self, relativeFrameOffset):
        """Jump a certain number of frames forward or back
        """
        if self.video_filename is None:
            self._show_error("No video file chosen")
            return
        # if self.media_end_time == -1:
        #     return

        curr_total_fps = self.media_player.get_fps()
        relativeSecondsOffset = relativeFrameOffset / curr_total_fps # Desired offset in seconds
        curr_total_duration = self.media_player.get_length()
        relative_percent_offset = relativeSecondsOffset / curr_total_duration # percent of the whole that we want to skip




        totalNumFrames = int(curr_total_duration * curr_total_fps)

        try:
            didPauseMedia = False
            if self.media_is_playing:
                self.media_player.pause()
                didPauseMedia = True

            newPosition = self.media_player.get_position() + relative_percent_offset
            # newTime = int(self.media_player.get_time() + relativeFrameOffset)

            # self.update_slider_highlight()
            # self.media_player.set_time(newTime)
            self.media_player.set_position(newPosition)

            if (didPauseMedia):
                self.media_player.play()
            # else:
            #     # Otherwise, the media was already paused, we need to very quickly play the media to update the frame with the new time, and then immediately pause it again.
            #     self.media_player.play()
            #     self.media_player.pause()
            self.media_player.next_frame()

            print("Setting media playback time to ", newPosition)
        except Exception as ex:
            self._show_error(str(ex))
            print(traceback.format_exc())


    def toggle_full_screen(self):
        if self.is_full_screen:
            # TODO Artifacts still happen some time when exiting full screen
            # in X11
            self.ui.frame_media.showNormal()
            self.ui.frame_media.restoreGeometry(self.original_geometry)
            self.ui.frame_media.setParent(self.ui.widget_central)
            self.ui.layout_main.addWidget(self.ui.frame_media, 2, 3, 3, 1)
            # self.ui.frame_media.ensurePolished()
        else:
            self.ui.frame_media.setParent(None)
            self.ui.frame_media.setWindowFlags(Qt.FramelessWindowHint |
                                               Qt.CustomizeWindowHint)
            self.original_geometry = self.ui.frame_media.saveGeometry()
            desktop = QApplication.desktop()
            rect = desktop.screenGeometry(desktop.screenNumber(QCursor.pos()))
            self.ui.frame_media.setGeometry(rect)
            self.ui.frame_media.showFullScreen()
            self.ui.frame_media.show()
        self.ui.frame_video.setFocus()
        self.is_full_screen = not self.is_full_screen

    def browse_timestamp_handler(self):
        """
        Handler when the timestamp browser button is clicked
        """
        tmp_name, _ = QFileDialog.getOpenFileName(
            self, "Choose Timestamp file", None,
            "Timestamp File (*.tmsp);;All Files (*)"
        )
        if not tmp_name:
            return
        self.set_timestamp_filename(QDir.toNativeSeparators(tmp_name))

    def create_timestamp_file_handler(self):
        """
        Handler when the timestamp file create button is clicked
        """
        tmp_name, _ = QFileDialog.getSaveFileName(
            self, "Create New Timestamp file", None,
            "Timestamp File (*.tmsp);;All Files (*)"
        )
        if not tmp_name:
            return

        try:
            if (os.stat(QDir.toNativeSeparators(tmp_name)).st_size == 0):
                    # File is empty, create a non-empty one:
                    with open(QDir.toNativeSeparators(tmp_name), "w") as fh:
                        fh.write("[]")  # Write the minimal valid JSON string to the file to allow it to be used
            else:
                pass

            # with open(tmp_name, 'r') as fh:
            #     if fh.__sizeof__()>0:
            #         # File is not empty:
            #         pass
            #     else:
            #         # File is empty, create a non-empty one:
            #         fh.close()
            #         with open(tmp_name, "w") as fh:
            #             fh.write("[]")  # Write the minimal valid JSON string to the file to allow it to be used

        except WindowsError:
            with open(tmp_name, "w") as fh:
                fh.write("[]") # Write the minimal valid JSON string to the file to allow it to be used


        # Create new file:
        self.set_timestamp_filename(QDir.toNativeSeparators(tmp_name))

    def _sort_model(self):
        self.ui.list_timestamp.sortByColumn(0, Qt.AscendingOrder)

    def _select_blank_row(self, parent, start, end):
        self.ui.list_timestamp.selectRow(start)

    def set_timestamp_filename(self, filename):
        """
        Set the timestamp file name
        """
        if not os.path.isfile(filename):
            self._show_error("Cannot access timestamp file " + filename)
            return

        try:
            self.timestamp_model = TimestampModel(filename, self)
            self.timestamp_model.timeParseError.connect(
                lambda err: self._show_error(err)
            )
            self.proxy_model.setSortRole(Qt.UserRole)
            self.proxy_model.dataChanged.connect(self._sort_model)
            self.proxy_model.dataChanged.connect(self.update_slider_highlight)
            self.proxy_model.setSourceModel(self.timestamp_model)
            self.proxy_model.rowsInserted.connect(self._sort_model)
            self.proxy_model.rowsInserted.connect(self._select_blank_row)
            self.ui.list_timestamp.setModel(self.proxy_model)

            self.timestamp_filename = filename
            self.ui.entry_timestamp.setText(self.timestamp_filename)

            self.mapper.setModel(self.proxy_model)
            self.mapper.addMapping(self.ui.entry_start_time, 0)
            self.mapper.addMapping(self.ui.entry_end_time, 1)
            self.mapper.addMapping(self.ui.entry_description, 2)
            self.ui.list_timestamp.selectionModel().selectionChanged.connect(
                self.timestamp_selection_changed)
            self._sort_model()

            directory = os.path.dirname(self.timestamp_filename)
            basename = os.path.basename(self.timestamp_filename)
            timestamp_name_without_ext = os.path.splitext(basename)[0]
            for file_in_dir in os.listdir(directory):
                current_filename = os.path.splitext(file_in_dir)[0]
                found_video = (current_filename == timestamp_name_without_ext
                               and file_in_dir != basename)
                if found_video:
                    found_video_file = os.path.join(directory, file_in_dir)
                    self.set_video_filename(found_video_file)
                    break
        except ValueError as err:
            self._show_error("Timestamp file is invalid")

    def timestamp_selection_changed(self, selected, deselected):
        if len(selected) > 0:
            self.mapper.setCurrentModelIndex(selected.indexes()[0])
            self.ui.button_save.setEnabled(True)
            self.ui.button_remove_entry.setEnabled(True)
            self.ui.entry_start_time.setReadOnly(False)
            self.ui.entry_end_time.setReadOnly(False)
            self.ui.entry_description.setReadOnly(False)
        else:
            self.mapper.setCurrentModelIndex(QModelIndex())
            self.ui.button_save.setEnabled(False)
            self.ui.button_remove_entry.setEnabled(False)
            self.ui.entry_start_time.clear()
            self.ui.entry_end_time.clear()
            self.ui.entry_description.clear()
            self.ui.entry_start_time.setReadOnly(True)
            self.ui.entry_end_time.setReadOnly(True)
            self.ui.entry_description.setReadOnly(True)

    def set_video_filename(self, filename):
        """
        Set the video filename
        """
        if not os.path.isfile(filename):
            self._show_error("Cannot access video file " + filename)
            return

        self.video_filename = filename

        media = self.vlc_instance.media_new(self.video_filename)
        media.parse()
        if not media.get_duration():
            self._show_error("Cannot play this media file")
            self.media_player.set_media(None)
            self.video_filename = None
        else:
            self.media_player.set_media(media)
            if sys.platform.startswith('linux'): # for Linux using the X Server
                self.media_player.set_xwindow(self.ui.frame_video.winId())
            elif sys.platform == "win32": # for Windows
                self.media_player.set_hwnd(self.ui.frame_video.winId())
            elif sys.platform == "darwin": # for MacOS
                self.media_player.set_nsobject(self.ui.frame_video.winId())
            self.ui.entry_video.setText(self.video_filename)

            self.update_video_file_labels_on_file_change()
            self.media_started_playing = False
            self.media_is_playing = False
            self.set_volume(self.ui.slider_volume.value())
            self.play_pause_model.setState(True)

    def browse_video_handler(self):
        """
        Handler when the video browse button is clicked
        """
        tmp_name, _ = QFileDialog.getOpenFileName(
            self, "Choose Video file", None,
            "All Files (*)"
        )
        if not tmp_name:
            return
        self.set_video_filename(QDir.toNativeSeparators(tmp_name))

    def _show_error(self, message, title="Error"):
        QMessageBox.warning(self, title, message)