def __import(self): """Import annotations from a file.""" win = ImportWindow(self) win.show()
class ControlEventTimeline(ControlBase, QWidget): """ Timeline events editor """ def __init__(self, label="", default=0, max=100): QWidget.__init__(self) ControlBase.__init__(self, label, default) self._max = 100 self._graphs_prop_win = GraphsProperties(self._time, self) self._graphsgenerator_win = GraphsEventsGenerator(self._time) self._graph2event_win = Graph2Event(self._time) # Popup menus that only show when clicking on a TIMELINEDELTA object self._deltaLockAction = self.add_popup_menu_option("Lock", self.__lockSelected, key='L') self._deltaColorAction = self.add_popup_menu_option( "Pick a color", self.__pickColor) self._deltaRemoveAction = self.add_popup_menu_option( "Remove", self.__removeSelected, key='Delete') self._deltaActions = [ self._deltaLockAction, self._deltaColorAction, self._deltaRemoveAction ] for action in self._deltaActions: action.setVisible(False) self.add_popup_menu_option("-") self.add_popup_menu_option("Graphs", self.show_graphs_properties, icon=conf.PYFORMS_ICON_EVENTTIMELINE_GRAPH) self.add_popup_menu_option("Apply a function to the graphs", self.__generate_graphs_events, icon=conf.PYFORMS_ICON_EVENTTIMELINE_GRAPH) self.add_popup_menu_option("Convert graph to events", self.__graph2event_event, icon=conf.PYFORMS_ICON_EVENTTIMELINE_GRAPH) self.add_popup_menu_option("-") # General righ click popup menus self.add_popup_menu_option("Rows", self.__setLinePropertiesEvent, icon=conf.PYFORMS_ICON_EVENTTIMELINE_REMOVE) self.add_popup_menu_option("-") self.add_popup_menu_option("-") self.add_popup_menu_option( "Auto adjust rows", self.__auto_adjust_tracks_evt, icon=conf.PYFORMS_ICON_EVENTTIMELINE_REFRESH) self.add_popup_menu_option("Add a row", self.__add_track_2_bottom_evt, icon=conf.PYFORMS_ICON_EVENTTIMELINE_ADD) self.add_popup_menu_option("-") clean_menu = self.add_popup_submenu('Clean') self.add_popup_menu_option('The current row', function_action=self.__cleanLine, submenu=clean_menu) self.add_popup_menu_option('-') self.add_popup_menu_option('All graphs', function_action=self.__cleanCharts, submenu=clean_menu) self.add_popup_menu_option('-') self.add_popup_menu_option('Everything', function_action=self.clean, submenu=clean_menu) def init_form(self): # Get the current path of the file rootPath = os.path.dirname(__file__) vlayout = QVBoxLayout() hlayout = QHBoxLayout() if conf.PYFORMS_USE_QT5: hlayout.setContentsMargins(0, 0, 0, 0) vlayout.setContentsMargins(0, 0, 0, 0) else: hlayout.setMargin(0) vlayout.setMargin(0) self.setLayout(vlayout) # Add scroll area scrollarea = QScrollArea() scrollarea.setMinimumHeight(140) scrollarea.setWidgetResizable(True) scrollarea.keyPressEvent = self.__scrollAreaKeyPressEvent scrollarea.keyReleaseEvent = self.__scrollAreaKeyReleaseEvent vlayout.addWidget(scrollarea) # vlayout.setContentsMargins(5, 5, 5, 5) # The timeline widget widget = TimelineWidget(self) widget._scroll = scrollarea # widget.setMinimumHeight(1000) scrollarea.setWidget(widget) # TODO Options buttons # btn_1 = QtGui.QPushButton("?") # btn_2 = QtGui.QPushButton("?") # vlayout_options = QtGui.QVBoxLayout() # vlayout_options.addWidget(btn_1) # vlayout_options.addWidget(btn_2) # hlayout.addLayout(vlayout_options) # hlayout.addWidget(btn_1) # hlayout.addWidget(btn_2) # Timeline zoom slider slider = QSlider(QtCore.Qt.Horizontal) slider.setFocusPolicy(QtCore.Qt.NoFocus) slider.setMinimum(1) slider.setMaximum(100) slider.setValue(10) slider.setPageStep(1) slider.setTickPosition(QSlider.NoTicks) # TicksBothSides slider.valueChanged.connect(self.__scaleSliderChange) slider_label_zoom_in = QLabel() slider_label_zoom_out = QLabel() slider_label_zoom_in.setPixmap( conf.PYFORMS_PIXMAP_EVENTTIMELINE_ZOOM_IN) slider_label_zoom_out.setPixmap( conf.PYFORMS_PIXMAP_EVENTTIMELINE_ZOOM_OUT) self._zoomLabel = QLabel("100%") hlayout.addWidget(self._zoomLabel) hlayout.addWidget(slider_label_zoom_out) hlayout.addWidget(slider) hlayout.addWidget(slider_label_zoom_in) # hlayout.setContentsMargins(5, 0, 5, 5) # Import/Export Buttons btn_import = QPushButton("Import") btn_import.setIcon(conf.PYFORMS_ICON_EVENTTIMELINE_IMPORT) btn_import.clicked.connect(self.__import) btn_export = QPushButton("Export") btn_export.setIcon(conf.PYFORMS_ICON_EVENTTIMELINE_EXPORT) btn_export.clicked.connect(self.__export) # importexport_vlayout = QtGui.QVBoxLayout() # importexport_vlayout.adimdWidget(btn_import) # importexport_vlayout.addWidget(btn_export) # hlayout.addLayout(importexport_vlayout) hlayout.addWidget(btn_import) hlayout.addWidget(btn_export) vlayout.addLayout(hlayout) self._time = widget self._scrollArea = scrollarea ########################################################################## #### HELPERS/PUBLIC FUNCTIONS ############################################ ########################################################################## def __add__(self, other): if isinstance(other, TimelineChart): self._graphs_prop_win += other self._graphsgenerator_win += other self._graph2event_win += other return self def __sub__(self, other): if isinstance(other, int): self._graphs_prop_win -= other self._graphsgenerator_win -= other self._graph2event_win -= other return self def rename_graph(self, graph_index, newname): self._graphs_prop_win.rename_graph(graph_index, newname) self._graphsgenerator_win.rename_graph(graph_index, newname) self._graph2event_win.rename_graph(graph_index, newname) def add_period(self, value, row=0, color=None): """ :param value: :param row: :param color: :return: """ self._time.add_period(value, row, color) self._time.repaint() def add_graph(self, name, data): """ :param name: :param data: :return: """ self._time.add_chart(name, data) def import_graph(self, filename, frame_col=0, val_col=1): """ :param filename: :param frame_col: :param val_col: :return: """ self.__import() self._import_window.import_chart(filename, frame_col, val_col) def import_graph_file(self, filename, separator=';', ignore_rows=0): """ :param filename: :param separator: :param ignore_rows: :return: """ csvfile = open(filename, 'U') spamreader = csv.reader(csvfile, delimiter=separator) for i in range(ignore_rows): next(spamreader, None) chart = self._time.importchart_csv(spamreader) chart.name = os.path.basename(filename).replace('.csv', '').replace( '.CSV', '') csvfile.close() def show_graphs_properties(self): """ """ self._graphs_prop_win.show() self._time.repaint() def import_csv(self, csvfile): """ :param csvfile: """ # If there are annotation in the timeline, show a warning if len(self._time._tracks) > 0: # dict returns True if not empty message = [ "You are about to import new data. ", "If you proceed, current annotations will be erased. ", "Make sure to export current annotations first to save.", "\n", "Are you sure you want to proceed?" ] reply = QMessageBox.question(self, "Warning!", "".join(message), QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply != QMessageBox.Yes: return self._time.import_csv(csvfile) def export_csv_file(self, filename): with open(filename, 'w') as csvfile: spamwriter = csv.writer(csvfile, dialect='excel') self._time.export_csv(spamwriter) def import_csv_file(self, filename): with open(filename, 'r') as csvfile: spamreader = csv.reader(csvfile, dialect='excel') self._time.import_csv(spamreader) ########################################################################## #### EVENTS ############################################################## ########################################################################## def mouse_moveover_timeline_event(self, event): self._graphs_prop_win.mouse_moveover_timeline_event(event) @property def pointer_changed_event(self): return self._time._pointer.moveEvent @pointer_changed_event.setter def pointer_changed_event(self, value): self._time._pointer.moveEvent = value def __auto_adjust_tracks_evt(self): for i in range(len(self._time.tracks) - 1, -1, -1): track = self._time.tracks[i] if len(track) == 0: self._time.remove_track(track) else: break def __add_track_2_bottom_evt(self): self._time.add_track() def __remove_track_from_bottom_evt(self): if len(self._time.tracks) > 0: track = self._time.tracks[-1] if len(track) == 0: self._time.remove_track(track) def __generate_graphs_events(self): self._graphsgenerator_win.show() def __graph2event_event(self): self._graph2event_win.show() ########################################################################## #### PROPERTIES ########################################################## ########################################################################## @property def value(self): return self._time.position @value.setter def value(self, value): ControlBase.value.fset(self, value) self._time.position = value @property def max(self): return self._time.minimumWidth() @max.setter def max(self, value): self._max = value self._time.setMinimumWidth(value) self.repaint() @property def form(self): return self @property def rows(self): return self._time.tracks @property def graphs(self): return self._time.graphs ########################################################################## #### PRIVATE FUNCTIONS ################################################### ########################################################################## def about_to_show_contextmenu_event(self): for action in self._deltaActions: action.setVisible( True ) if self._time._selected is not None else action.setVisible(False) def __setLinePropertiesEvent(self): """ This controls makes possible the edition of a track in the timeline, based on the position of the mouse. Updates: - Track label - Track default color """ current_track = self._time.current_mouseover_track parent = self._time # Tracks info dict and index i = current_track # Save current default color to override with selected track color timeline_default_color = parent.color try: parent.color = self._time._tracks[current_track].color except Exception as e: error_message = ("You tried to edit an empty track.", "\n", "Initialize it by creating an event first.") QMessageBox.warning(parent, "Attention!", "".join(error_message)) return e # Create dialog dialog = TimelinePopupWindow(parent, i) dialog.setModal(True) # to disable main application window # If dialog is accepted, update dict info if dialog._ui.exec_() == dialog.Accepted: # Update label if dialog.behavior is not None: self._time._tracks[i].title = dialog.behavior # Update color if self._time._tracks[i].color != dialog.color: for delta in self._time._tracks[i].periods: delta.color = dialog.color self._time._tracks[i].color = dialog.color self._time.repaint() else: pass # Restore timeline default color parent.color = timeline_default_color def __lockSelected(self): self._time.lockSelected() def __removeSelected(self): self._time.removeSelected() def __import(self): """Import annotations from a file.""" if not hasattr(self, '_import_window'): self._import_window = ImportWindow(self) self._import_window.show() def __export(self): """Export annotations to a file.""" filename, ffilter = QFileDialog.getSaveFileName( parent=self, caption="Export annotations file", directory="untitled.csv", filter="CSV Files (*.csv);;CSV Matrix Files (*.csv)", options=QFileDialog.DontUseNativeDialog) filename = str(filename) ffilter = str(ffilter) if filename != "": with open(filename, 'w') as csvfile: spamwriter = csv.writer(csvfile, dialect='excel') if ffilter == 'CSV Files (*.csv)': self._time.export_csv(spamwriter) elif ffilter == 'CSV Matrix Files (*.csv)': self._time.export_2_csv_matrix(spamwriter) def __export_2_csv_matrix(self): QMessageBox.warning( self, "Important!", 'Please note that this file cannot be imported after.') filename = QFileDialog.getSaveFileName( parent=self, caption="Export matrix file", directory="untitled.csv", filter="CSV Files (*.csv)", options=QFileDialog.DontUseNativeDialog) if filename != "": with open(filename, 'w') as csvfile: spamwriter = csv.writer(csvfile, dialect='excel') self._time.export_2_csv_matrix(spamwriter) def __cleanLine(self): reply = QMessageBox.question( self, 'Confirm', "Are you sure you want to clean all the events on this track?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: self._time.cleanLine(self._time.current_mouseover_track) def __cleanCharts(self): reply = QMessageBox.question( self, 'Confirm', "Are you sure you want to clean all the charts?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: self._time.cleanCharts() def clean(self): reply = QMessageBox.question( self, 'Confirm', "Are you sure you want to clean everything?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: self._time.clean() def __pickColor(self): self._time.color = QColorDialog.getColor(self._time.color) if self._time._selected != None: self._time._selected.color = self._time.color self._time.repaint() def __scaleSliderChange(self, value): scale = 0.1 * value self._time.setMinimumWidth(scale * self._max) self._time.scale = scale self._zoomLabel.setText(str(value * 10).zfill(3) + "%") def __scrollAreaKeyReleaseEvent(self, event): modifiers = int(event.modifiers()) self._time.keyReleaseEvent(event) if modifiers is not QtCore.Qt.ControlModifier and \ modifiers is not int(QtCore.Qt.ShiftModifier | QtCore.Qt.ControlModifier) and \ modifiers is not QtCore.Qt.ShiftModifier: QScrollArea.keyReleaseEvent(self._scrollArea, event) def __scrollAreaKeyPressEvent(self, event): modifiers = int(event.modifiers()) if modifiers is not QtCore.Qt.ControlModifier and \ modifiers is not int(QtCore.Qt.ShiftModifier | QtCore.Qt.ControlModifier) and \ modifiers is not QtCore.Qt.ShiftModifier: QScrollArea.keyPressEvent(self._scrollArea, event)
class ControlEventTimeline(ControlBase, QtGui.QWidget): """ Timeline events editor """ def __init__(self, label="", defaultValue=0, min=0, max=100, **kwargs): QtGui.QWidget.__init__(self) ControlBase.__init__(self, label, defaultValue, **kwargs) self._max = 100 self._graphs_prop_win = GraphsProperties(self._time, self) # Popup menus that only show when clicking on a TIMELINEDELTA object self._deltaLockAction = self.addPopupMenuOption("Lock", self.__lockSelected, key="L") self._deltaColorAction = self.addPopupMenuOption("Pick a color", self.__pickColor) self._deltaRemoveAction = self.addPopupMenuOption("Remove", self.__removeSelected, key="Delete") self._deltaActions = [self._deltaLockAction, self._deltaColorAction, self._deltaRemoveAction] for action in self._deltaActions: action.setVisible(False) self.addPopupMenuOption("-") # General righ click popup menus self.addPopupMenuOption("Set track properties...", self.__setLinePropertiesEvent) self.addPopupMenuOption("Set graphs properties", self.show_graphs_properties) self.addPopupMenuOption("-") self.addPopupSubMenuOption( "Clean", {"Current line": self.__cleanLine, "Everything": self.__clean, "Charts": self.__cleanCharts} ) def initForm(self): # Get the current path of the file rootPath = os.path.dirname(__file__) vlayout = QtGui.QVBoxLayout() hlayout = QtGui.QHBoxLayout() # hlayout.setMargin(0) vlayout.setMargin(0) self.setLayout(vlayout) # Add scroll area scrollarea = QtGui.QScrollArea() scrollarea.setMinimumHeight(140) scrollarea.setWidgetResizable(True) scrollarea.keyPressEvent = self.__scrollAreaKeyPressEvent scrollarea.keyReleaseEvent = self.__scrollAreaKeyReleaseEvent vlayout.addWidget(scrollarea) # vlayout.setContentsMargins(5, 5, 5, 5) # The timeline widget widget = TimelineWidget(self) widget._scroll = scrollarea # widget.setMinimumHeight(1000) scrollarea.setWidget(widget) # TODO Options buttons # btn_1 = QtGui.QPushButton("?") # btn_2 = QtGui.QPushButton("?") # vlayout_options = QtGui.QVBoxLayout() # vlayout_options.addWidget(btn_1) # vlayout_options.addWidget(btn_2) # hlayout.addLayout(vlayout_options) # hlayout.addWidget(btn_1) # hlayout.addWidget(btn_2) # Timeline zoom slider slider = QtGui.QSlider(QtCore.Qt.Horizontal) slider.setFocusPolicy(QtCore.Qt.NoFocus) slider.setMinimum(1) slider.setMaximum(100) slider.setValue(10) slider.setPageStep(1) slider.setTickPosition(QtGui.QSlider.NoTicks) # TicksBothSides slider.valueChanged.connect(self.__scaleSliderChange) slider_label_zoom_in = QtGui.QLabel() slider_label_zoom_out = QtGui.QLabel() slider_label_zoom_in.setPixmap(conf.PYFORMS_PIXMAP_EVENTTIMELINE_ZOOM_IN) slider_label_zoom_out.setPixmap(conf.PYFORMS_PIXMAP_EVENTTIMELINE_ZOOM_OUT) self._zoomLabel = QtGui.QLabel("100%") hlayout.addWidget(self._zoomLabel) hlayout.addWidget(slider_label_zoom_out) hlayout.addWidget(slider) hlayout.addWidget(slider_label_zoom_in) # hlayout.setContentsMargins(5, 0, 5, 5) # Import/Export Buttons btn_import = QtGui.QPushButton("Import") btn_import.setIcon(conf.PYFORMS_ICON_EVENTTIMELINE_IMPORT) btn_import.clicked.connect(self.__import) btn_export = QtGui.QPushButton("Export") btn_export.setIcon(conf.PYFORMS_ICON_EVENTTIMELINE_EXPORT) btn_export.clicked.connect(self.__export) # importexport_vlayout = QtGui.QVBoxLayout() # importexport_vlayout.addWidget(btn_import) # importexport_vlayout.addWidget(btn_export) # hlayout.addLayout(importexport_vlayout) hlayout.addWidget(btn_import) hlayout.addWidget(btn_export) vlayout.addLayout(hlayout) self._time = widget self._scrollArea = scrollarea ########################################################################## #### HELPERS/PUBLIC FUNCTIONS ############################################ ########################################################################## def getExportFilename(self): return "untitled.csv" def addRow(self, values): for v in values: self.addPeriod(v, track=0) def addPeriod(self, value, track=0, color=None): self._time.addPeriod(value, track, color) def add_chart(self, name, data): self._time.add_chart(name, data) def import_chart(self, filename, frame_col=0, val_col=1): self.__import() self._import_window.import_chart(filename, frame_col, val_col) def import_chart_file(self, filename, separator=";", ignore_rows=0): csvfile = open(filename, "U") spamreader = csv.reader(csvfile, delimiter=separator) for i in range(ignore_rows): next(spamreader, None) self._time.importchart_csv(spamreader) csvfile.close() ########################################################################## #### EVENTS ############################################################## ########################################################################## def aboutToShowContextMenuEvent(self): for action in self._deltaActions: action.setVisible(True) if self._time._selected is not None else action.setVisible(False) def show_graphs_properties(self): self._graphs_prop_win.show() self._time.repaint() def __setLinePropertiesEvent(self): """ This controls makes possible the edition of a track in the timeline, based on the position of the mouse. Updates: - Track label - Track default color """ current_track = self.mouseOverLine parent = self._time # Tracks info dict and index i = current_track # Save current default color to override with selected track color timeline_default_color = parent.color try: parent.color = self._time._tracks[current_track].color except Exception as e: error_message = ("You tried to edit an empty track.", "\n", "Initialize it by creating an event first.") QtGui.QMessageBox.warning(parent, "Attention!", "".join(error_message)) return e # Create dialog dialog = TimelinePopupWindow(parent, i) dialog.setModal(True) # to disable main application window # If dialog is accepted, update dict info if dialog._ui.exec_() == dialog.Accepted: # Update label if dialog.behavior is not None: self._time._tracks[i].title = dialog.behavior # Update color if self._time._tracks[i].color != dialog.color: for delta in self._time._tracks[i].periods: delta.color = dialog.color self._time._tracks[i].color = dialog.color else: pass # Restore timeline default color parent.color = timeline_default_color def __lockSelected(self): self._time.lockSelected() def __removeSelected(self): self._time.removeSelected() def __import(self): """Import annotations from a file.""" if not hasattr(self, "_import_window"): self._import_window = ImportWindow(self) self._import_window.show() def import_csv(self, csvfile): # If there are annotation in the timeline, show a warning if len(self._time._tracks) > 0: # dict returns True if not empty message = [ "You are about to import new data. ", "If you proceed, current annotations will be erased. ", "Make sure to export current annotations first to save.", "\n", "Are you sure you want to proceed?", ] reply = QtGui.QMessageBox.question( self, "Warning!", "".join(message), QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No ) if reply != QtGui.QMessageBox.Yes: return self._time.import_csv(csvfile) print("Annotations file imported: {:s}".format(filename)) def __export(self): """Export annotations to a file.""" filename, ffilter = QtGui.QFileDialog.getSaveFileNameAndFilter( parent=self, caption="Export annotations file", directory=self.getExportFilename(), filter="CSV Files (*.csv);;CSV Matrix Files (*.csv)", options=QtGui.QFileDialog.DontUseNativeDialog, ) filename = str(filename) ffilter = str(ffilter) if filename != "": with open(filename, "w") as csvfile: spamwriter = csv.writer(csvfile, dialect="excel") if ffilter == "CSV Files (*.csv)": self._time.export_csv(spamwriter) elif ffilter == "CSV Matrix Files (*.csv)": self._time.export_2_csv_matrix(spamwriter) def __export_2_csv_matrix(self): QtGui.QMessageBox.warning(self, "Important!", "Please note that this file cannot be imported after.") filename = QtGui.QFileDialog.getSaveFileName( parent=self, caption="Export matrix file", directory=self.getExportFilename(), filter="CSV Files (*.csv)", options=QtGui.QFileDialog.DontUseNativeDialog, ) if filename != "": with open(filename, "w") as csvfile: spamwriter = csv.writer(csvfile, dialect="excel") self._time.export_2_csv_matrix(spamwriter) def __cleanLine(self): reply = QtGui.QMessageBox.question( self, "Confirm", "Are you sure you want to clean all the events?", QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No, ) if reply == QtGui.QMessageBox.Yes: self._time.cleanLine() def __cleanCharts(self): reply = QtGui.QMessageBox.question( self, "Confirm", "Are you sure you want to clean all the charts?", QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No, ) if reply == QtGui.QMessageBox.Yes: self._time.cleanCharts() def __clean(self): reply = QtGui.QMessageBox.question( self, "Confirm", "Are you sure you want to clean all the events?", QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No, ) if reply == QtGui.QMessageBox.Yes: self._time.clean() def __pickColor(self): self._time.color = QtGui.QColorDialog.getColor(self._time.color) if self._time._selected != None: self._time._selected.color = self._time.color self._time.repaint() def __scaleSliderChange(self, value): scale = 0.1 * value self._time.setMinimumWidth(scale * self._max) self._time.scale = scale self._zoomLabel.setText(str(value * 10).zfill(3) + "%") def __scrollAreaKeyReleaseEvent(self, event): modifiers = int(event.modifiers()) self._time.keyReleaseEvent(event) if ( modifiers is not QtCore.Qt.ControlModifier and modifiers is not int(QtCore.Qt.ShiftModifier | QtCore.Qt.ControlModifier) and modifiers is not QtCore.Qt.ShiftModifier ): QtGui.QScrollArea.keyReleaseEvent(self._scrollArea, event) def __scrollAreaKeyPressEvent(self, event): modifiers = int(event.modifiers()) if ( modifiers is not QtCore.Qt.ControlModifier and modifiers is not int(QtCore.Qt.ShiftModifier | QtCore.Qt.ControlModifier) and modifiers is not QtCore.Qt.ShiftModifier ): QtGui.QScrollArea.keyPressEvent(self._scrollArea, event) ########################################################################## #### PROPERTIES ########################################################## ########################################################################## @property def pointerChanged(self): return self._time._pointer.moveEvent @pointerChanged.setter def pointerChanged(self, value): self._time._pointer.moveEvent = value @property def value(self): return self._time.position @value.setter def value(self, value): ControlBase.value.fset(self, value) self._time.position = value @property def max(self): return self._time.minimumWidth() @max.setter def max(self, value): self._max = value self._time.setMinimumWidth(value) self.repaint() @property def mouseOverLine(self): globalPos = QtGui.QCursor.pos() widgetPos = self._time.mapFromGlobal(globalPos) return self._time.trackInPosition(widgetPos.x(), widgetPos.y()) # Video playback properties @property def playVideoEvent(self): return self._time.playVideoEvent @playVideoEvent.setter def playVideoEvent(self, value): self._time.playVideoEvent = value @property def fpsChanged(self): return self._time.fpsChangeEvent @fpsChanged.setter def fpsChanged(self, value): self._time.fpsChangeEvent = value @property def form(self): return self @property def tracks(self): return self._time.tracks @property def charts(self): return self._graphs_prop_win.charts
class ControlEventTimeline(ControlBase, QtGui.QWidget): """ Timeline events editor """ def __init__(self, label="", defaultValue=0, min=0, max=100, **kwargs): QtGui.QWidget.__init__(self) ControlBase.__init__(self, label, defaultValue, **kwargs) self._max = 100 self._graphs_prop_win = GraphsProperties(self._time, self) # Popup menus that only show when clicking on a TIMELINEDELTA object self._deltaLockAction = self.addPopupMenuOption("Lock", self.__lockSelected, key='L') self._deltaColorAction = self.addPopupMenuOption( "Pick a color", self.__pickColor) self._deltaRemoveAction = self.addPopupMenuOption( "Remove", self.__removeSelected, key='Delete') self._deltaActions = [ self._deltaLockAction, self._deltaColorAction, self._deltaRemoveAction ] for action in self._deltaActions: action.setVisible(False) self.addPopupMenuOption("-") # General righ click popup menus self.addPopupMenuOption("Set track properties...", self.__setLinePropertiesEvent) self.addPopupMenuOption("Set graphs properties", self.show_graphs_properties) self.addPopupMenuOption("-") self.addPopupSubMenuOption( "Clean", { 'Current line': self.__cleanLine, 'Everything': self.__clean, 'Charts': self.__cleanCharts }) def initForm(self): # Get the current path of the file rootPath = os.path.dirname(__file__) vlayout = QtGui.QVBoxLayout() hlayout = QtGui.QHBoxLayout() # hlayout.setMargin(0) vlayout.setMargin(0) self.setLayout(vlayout) # Add scroll area scrollarea = QtGui.QScrollArea() scrollarea.setMinimumHeight(140) scrollarea.setWidgetResizable(True) scrollarea.keyPressEvent = self.__scrollAreaKeyPressEvent scrollarea.keyReleaseEvent = self.__scrollAreaKeyReleaseEvent vlayout.addWidget(scrollarea) #vlayout.setContentsMargins(5, 5, 5, 5) # The timeline widget widget = TimelineWidget(self) widget._scroll = scrollarea # widget.setMinimumHeight(1000) scrollarea.setWidget(widget) # TODO Options buttons # btn_1 = QtGui.QPushButton("?") # btn_2 = QtGui.QPushButton("?") # vlayout_options = QtGui.QVBoxLayout() # vlayout_options.addWidget(btn_1) # vlayout_options.addWidget(btn_2) # hlayout.addLayout(vlayout_options) # hlayout.addWidget(btn_1) # hlayout.addWidget(btn_2) # Timeline zoom slider slider = QtGui.QSlider(QtCore.Qt.Horizontal) slider.setFocusPolicy(QtCore.Qt.NoFocus) slider.setMinimum(1) slider.setMaximum(100) slider.setValue(10) slider.setPageStep(1) slider.setTickPosition(QtGui.QSlider.NoTicks) # TicksBothSides slider.valueChanged.connect(self.__scaleSliderChange) slider_label_zoom_in = QtGui.QLabel() slider_label_zoom_out = QtGui.QLabel() slider_label_zoom_in.setPixmap( conf.PYFORMS_PIXMAP_EVENTTIMELINE_ZOOM_IN) slider_label_zoom_out.setPixmap( conf.PYFORMS_PIXMAP_EVENTTIMELINE_ZOOM_OUT) self._zoomLabel = QtGui.QLabel("100%") hlayout.addWidget(self._zoomLabel) hlayout.addWidget(slider_label_zoom_out) hlayout.addWidget(slider) hlayout.addWidget(slider_label_zoom_in) #hlayout.setContentsMargins(5, 0, 5, 5) # Import/Export Buttons btn_import = QtGui.QPushButton("Import") btn_import.setIcon(conf.PYFORMS_ICON_EVENTTIMELINE_IMPORT) btn_import.clicked.connect(self.__import) btn_export = QtGui.QPushButton("Export") btn_export.setIcon(conf.PYFORMS_ICON_EVENTTIMELINE_EXPORT) btn_export.clicked.connect(self.__export) # importexport_vlayout = QtGui.QVBoxLayout() # importexport_vlayout.addWidget(btn_import) # importexport_vlayout.addWidget(btn_export) # hlayout.addLayout(importexport_vlayout) hlayout.addWidget(btn_import) hlayout.addWidget(btn_export) vlayout.addLayout(hlayout) self._time = widget self._scrollArea = scrollarea ########################################################################## #### HELPERS/PUBLIC FUNCTIONS ############################################ ########################################################################## def getExportFilename(self): return "untitled.csv" def addRow(self, values): for v in values: self.addPeriod(v, track=0) def addPeriod(self, value, track=0, color=None): self._time.addPeriod(value, track, color) def add_chart(self, name, data): self._time.add_chart(name, data) def import_chart(self, filename, frame_col=0, val_col=1): self.__import() self._import_window.import_chart(filename, frame_col, val_col) def import_chart_file(self, filename, separator=';', ignore_rows=0): csvfile = open(filename, 'U') spamreader = csv.reader(csvfile, delimiter=separator) for i in range(ignore_rows): next(spamreader, None) self._time.importchart_csv(spamreader) csvfile.close() ########################################################################## #### EVENTS ############################################################## ########################################################################## def aboutToShowContextMenuEvent(self): for action in self._deltaActions: action.setVisible( True ) if self._time._selected is not None else action.setVisible(False) def show_graphs_properties(self): self._graphs_prop_win.show() self._time.repaint() def __setLinePropertiesEvent(self): """ This controls makes possible the edition of a track in the timeline, based on the position of the mouse. Updates: - Track label - Track default color """ current_track = self.mouseOverLine parent = self._time # Tracks info dict and index i = current_track # Save current default color to override with selected track color timeline_default_color = parent.color try: parent.color = self._time._tracks[current_track].color except Exception as e: error_message = ("You tried to edit an empty track.", "\n", "Initialize it by creating an event first.") QtGui.QMessageBox.warning(parent, "Attention!", "".join(error_message)) return e # Create dialog dialog = TimelinePopupWindow(parent, i) dialog.setModal(True) # to disable main application window # If dialog is accepted, update dict info if dialog._ui.exec_() == dialog.Accepted: # Update label if dialog.behavior is not None: self._time._tracks[i].title = dialog.behavior # Update color if self._time._tracks[i].color != dialog.color: for delta in self._time._tracks[i].periods: delta.color = dialog.color self._time._tracks[i].color = dialog.color else: pass # Restore timeline default color parent.color = timeline_default_color def __lockSelected(self): self._time.lockSelected() def __removeSelected(self): self._time.removeSelected() def __import(self): """Import annotations from a file.""" if not hasattr(self, '_import_window'): self._import_window = ImportWindow(self) self._import_window.show() def import_csv(self, csvfile): # If there are annotation in the timeline, show a warning if len(self._time._tracks) > 0: # dict returns True if not empty message = [ "You are about to import new data. ", "If you proceed, current annotations will be erased. ", "Make sure to export current annotations first to save.", "\n", "Are you sure you want to proceed?" ] reply = QtGui.QMessageBox.question( self, "Warning!", "".join(message), QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No) if reply != QtGui.QMessageBox.Yes: return self._time.import_csv(csvfile) print("Annotations file imported: {:s}".format(filename)) def __export(self): """Export annotations to a file.""" filename, ffilter = QtGui.QFileDialog.getSaveFileNameAndFilter( parent=self, caption="Export annotations file", directory=self.getExportFilename(), filter="CSV Files (*.csv);;CSV Matrix Files (*.csv)", options=QtGui.QFileDialog.DontUseNativeDialog) filename = str(filename) ffilter = str(ffilter) if filename != "": with open(filename, 'w') as csvfile: spamwriter = csv.writer(csvfile, dialect='excel') if ffilter == 'CSV Files (*.csv)': self._time.export_csv(spamwriter) elif ffilter == 'CSV Matrix Files (*.csv)': self._time.export_2_csv_matrix(spamwriter) def __export_2_csv_matrix(self): QtGui.QMessageBox.warning( self, "Important!", 'Please note that this file cannot be imported after.') filename = QtGui.QFileDialog.getSaveFileName( parent=self, caption="Export matrix file", directory=self.getExportFilename(), filter="CSV Files (*.csv)", options=QtGui.QFileDialog.DontUseNativeDialog) if filename != "": with open(filename, 'w') as csvfile: spamwriter = csv.writer(csvfile, dialect='excel') self._time.export_2_csv_matrix(spamwriter) def __cleanLine(self): reply = QtGui.QMessageBox.question( self, 'Confirm', "Are you sure you want to clean all the events?", QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No) if reply == QtGui.QMessageBox.Yes: self._time.cleanLine() def __cleanCharts(self): reply = QtGui.QMessageBox.question( self, 'Confirm', "Are you sure you want to clean all the charts?", QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No) if reply == QtGui.QMessageBox.Yes: self._time.cleanCharts() def __clean(self): reply = QtGui.QMessageBox.question( self, 'Confirm', "Are you sure you want to clean all the events?", QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No) if reply == QtGui.QMessageBox.Yes: self._time.clean() def __pickColor(self): self._time.color = QtGui.QColorDialog.getColor(self._time.color) if self._time._selected != None: self._time._selected.color = self._time.color self._time.repaint() def __scaleSliderChange(self, value): scale = 0.1 * value self._time.setMinimumWidth(scale * self._max) self._time.scale = scale self._zoomLabel.setText(str(value * 10).zfill(3) + "%") def __scrollAreaKeyReleaseEvent(self, event): modifiers = int(event.modifiers()) self._time.keyReleaseEvent(event) if modifiers is not QtCore.Qt.ControlModifier and \ modifiers is not int(QtCore.Qt.ShiftModifier | QtCore.Qt.ControlModifier) and \ modifiers is not QtCore.Qt.ShiftModifier: QtGui.QScrollArea.keyReleaseEvent(self._scrollArea, event) def __scrollAreaKeyPressEvent(self, event): modifiers = int(event.modifiers()) if modifiers is not QtCore.Qt.ControlModifier and \ modifiers is not int(QtCore.Qt.ShiftModifier | QtCore.Qt.ControlModifier) and \ modifiers is not QtCore.Qt.ShiftModifier: QtGui.QScrollArea.keyPressEvent(self._scrollArea, event) ########################################################################## #### PROPERTIES ########################################################## ########################################################################## @property def pointerChanged(self): return self._time._pointer.moveEvent @pointerChanged.setter def pointerChanged(self, value): self._time._pointer.moveEvent = value @property def value(self): return self._time.position @value.setter def value(self, value): ControlBase.value.fset(self, value) self._time.position = value @property def max(self): return self._time.minimumWidth() @max.setter def max(self, value): self._max = value self._time.setMinimumWidth(value) self.repaint() @property def mouseOverLine(self): globalPos = QtGui.QCursor.pos() widgetPos = self._time.mapFromGlobal(globalPos) return self._time.trackInPosition(widgetPos.x(), widgetPos.y()) # Video playback properties @property def playVideoEvent(self): return self._time.playVideoEvent @playVideoEvent.setter def playVideoEvent(self, value): self._time.playVideoEvent = value @property def fpsChanged(self): return self._time.fpsChangeEvent @fpsChanged.setter def fpsChanged(self, value): self._time.fpsChangeEvent = value @property def form(self): return self @property def tracks(self): return self._time.tracks @property def charts(self): return self._graphs_prop_win.charts