Package pyforms :: Package gui :: Package Controls :: Package ControlEventTimeline :: Module ControlEventTimeline
[hide private]
[frames] | no frames]

Source Code for Module pyforms.gui.Controls.ControlEventTimeline.ControlEventTimeline

  1  #!/usr/bin/python 
  2  # -*- coding: utf-8 -*- 
  3   
  4  """ pyforms.gui.Controls.ControlEventTimeline.ControlEventTimeline 
  5   
  6  """ 
  7   
  8  import csv 
  9  import os 
 10  from PyQt4 import QtGui, QtCore 
 11  from pyforms.gui.Controls.ControlBase import ControlBase 
 12  from pyforms.gui.Controls.ControlEventTimeline.TimelineWidget import TimelineWidget 
 13  from pyforms.gui.Controls.ControlEventTimeline.TimelinePopupWindow import TimelinePopupWindow 
 14   
 15   
 16  __author__ = ["Ricardo Ribeiro", "Hugo Cachitas"] 
 17  __credits__ = ["Ricardo Ribeiro", "Hugo Cachitas"] 
 18  __license__ = "MIT" 
 19  __version__ = "0.0" 
 20  __maintainer__ = "Ricardo Ribeiro" 
 21  __email__ = "ricardojvr@gmail.com" 
 22  __status__ = "Development" 
23 24 25 -class ControlEventTimeline(ControlBase, QtGui.QWidget):
26 """ 27 Timeline events editor 28 """ 29
30 - def __init__(self, label="", defaultValue=0, min=0, max=100, **kwargs):
31 QtGui.QWidget.__init__(self) 32 ControlBase.__init__(self, label, defaultValue, **kwargs) 33 self._max = 100 34 35 # Popup menus that only show when clicking on a TIMELINEDELTA object 36 self._deltaLockAction = self.addPopupMenuOption( 37 "Lock", self.__lockSelected, key='L') 38 self._deltaColorAction = self.addPopupMenuOption( 39 "Pick a color", self.__pickColor) 40 self._deltaRemoveAction = self.addPopupMenuOption( 41 "Remove", self.__removeSelected, key='Delete') 42 self._deltaActions = [self._deltaLockAction, 43 self._deltaColorAction, 44 self._deltaRemoveAction] 45 46 for action in self._deltaActions: 47 action.setVisible(False) 48 self.addPopupMenuOption("-") 49 50 # General righ click popup menus 51 self.addPopupMenuOption( 52 "Set track properties...", self.__setLinePropertiesEvent) 53 self.addPopupMenuOption("-") 54 self.addPopupSubMenuOption( 55 "Import/Export", {'Export to CSV': self.__export, 'Import to CSV': self.__import}) 56 self.addPopupMenuOption("-") 57 self.addPopupSubMenuOption("Clean", { 58 'Current line': self.__cleanLine, 'Everything': self.__clean, 'Charts': self.__cleanCharts})
59
60 - def initForm(self):
61 # Get the current path of the file 62 rootPath = os.path.dirname(__file__) 63 64 vlayout = QtGui.QVBoxLayout() 65 hlayout = QtGui.QHBoxLayout() 66 # hlayout.setMargin(0) 67 vlayout.setMargin(0) 68 self.setLayout(vlayout) 69 70 # Add scroll area 71 scrollarea = QtGui.QScrollArea() 72 scrollarea.setMinimumHeight(140) 73 scrollarea.setWidgetResizable(True) 74 scrollarea.keyPressEvent = self.__scrollAreaKeyPressEvent 75 scrollarea.keyReleaseEvent = self.__scrollAreaKeyReleaseEvent 76 vlayout.addWidget(scrollarea) 77 78 # The timeline widget 79 widget = TimelineWidget() 80 widget._scroll = scrollarea 81 # widget.setMinimumHeight(54) 82 scrollarea.setWidget(widget) 83 84 # TODO Options buttons 85 # btn_1 = QtGui.QPushButton("?") 86 # btn_2 = QtGui.QPushButton("?") 87 # vlayout_options = QtGui.QVBoxLayout() 88 # vlayout_options.addWidget(btn_1) 89 # vlayout_options.addWidget(btn_2) 90 # hlayout.addLayout(vlayout_options) 91 # hlayout.addWidget(btn_1) 92 # hlayout.addWidget(btn_2) 93 94 # Timeline zoom slider 95 slider = QtGui.QSlider(QtCore.Qt.Horizontal) 96 slider.setFocusPolicy(QtCore.Qt.NoFocus) 97 slider.setMinimum(1) 98 slider.setMaximum(100) 99 slider.setValue(10) 100 slider.setPageStep(1) 101 slider.setTickPosition(QtGui.QSlider.NoTicks) # TicksBothSides 102 slider.valueChanged.connect(self.__scaleSliderChange) 103 slider_icon_zoom_in = QtGui.QPixmap( 104 os.path.join(rootPath, "..", "uipics", "zoom_in.png")) 105 slider_icon_zoom_out = QtGui.QPixmap( 106 os.path.join(rootPath, "..", "uipics", "zoom_out.png")) 107 slider_label_zoom_in = QtGui.QLabel() 108 slider_label_zoom_out = QtGui.QLabel() 109 slider_label_zoom_in.setPixmap(slider_icon_zoom_in) 110 slider_label_zoom_out.setPixmap(slider_icon_zoom_out) 111 # slider_vlayout = QtGui.QVBoxLayout() 112 # slider_hlayout = QtGui.QHBoxLayout() 113 # slider_hlayout.addWidget(slider_label_zoom_out) 114 # slider_hlayout.addStretch() 115 # slider_hlayout.addWidget(QtGui.QLabel("Zoom")) 116 # slider_hlayout.addStretch() 117 # slider_hlayout.addWidget(slider_label_zoom_in) 118 # slider_vlayout.addWidget(slider) 119 # slider_vlayout.addLayout(slider_hlayout) 120 # hlayout.addLayout(slider_vlayout) 121 self._zoomLabel = QtGui.QLabel("100%") 122 hlayout.addWidget(self._zoomLabel) 123 hlayout.addWidget(slider_label_zoom_out) 124 hlayout.addWidget(slider) 125 hlayout.addWidget(slider_label_zoom_in) 126 127 # Import/Export Buttons 128 btn_import = QtGui.QPushButton("Import") 129 btn_import_icon = QtGui.QIcon( 130 os.path.join(rootPath, "..", "uipics", "page_white_get.png")) 131 btn_import.setIcon(btn_import_icon) 132 btn_import.clicked.connect(self.__import) 133 btn_export = QtGui.QPushButton("Export") 134 btn_export_icon = QtGui.QIcon( 135 os.path.join(rootPath, "..", "uipics", "page_white_put.png")) 136 btn_export.setIcon(btn_export_icon) 137 btn_export.clicked.connect(self.__export) 138 # importexport_vlayout = QtGui.QVBoxLayout() 139 # importexport_vlayout.addWidget(btn_import) 140 # importexport_vlayout.addWidget(btn_export) 141 # hlayout.addLayout(importexport_vlayout) 142 hlayout.addWidget(btn_import) 143 hlayout.addWidget(btn_export) 144 145 vlayout.addLayout(hlayout) 146 147 self._time = widget 148 self._scrollArea = scrollarea
149 150 ########################################################################## 151 #### HELPERS/PUBLIC FUNCTIONS ############################################ 152 ########################################################################## 153
154 - def getExportFilename(self):
155 return "untitled.csv"
156
157 - def addRow(self, values):
158 for v in values: 159 self.addPeriod(v, track=0)
160
161 - def addPeriod(self, value, track=0, color=None):
162 self._time.addPeriod(value, track, color)
163 164 ########################################################################## 165 #### EVENTS ############################################################## 166 ########################################################################## 167
169 for action in self._deltaActions: 170 action.setVisible( 171 True) if self._time._selected is not None else action.setVisible(False)
172
173 - def __setLinePropertiesEvent(self):
174 """ 175 This controls makes possible the edition of a track in the 176 timeline, based on the position of the mouse. 177 178 Updates: 179 - Track label 180 - Track default color 181 """ 182 current_track = self.mouseOverLine 183 parent = self._time 184 185 # Tracks info dict and index 186 d = parent._tracks_info 187 i = current_track 188 189 # Save current default color to override with selected track color 190 timeline_default_color = parent.color 191 try: 192 parent.color = d[i][1] 193 except KeyError as e: 194 error_message = ("You tried to edit an empty track.", 195 "\n", 196 "Initialize it by creating an event first.") 197 QtGui.QMessageBox.warning( 198 parent, "Attention!", "".join(error_message)) 199 return e 200 201 # Create dialog 202 dialog = TimelinePopupWindow(parent, i) 203 dialog.setModal(True) # to disable main application window 204 205 # If dialog is accepted, update dict info 206 if dialog._ui.exec_() == dialog.Accepted: 207 # Update label 208 if dialog.behavior is not None: 209 d[i][0] = dialog.behavior 210 211 # Update color 212 if d[i][1] != dialog.color: 213 for delta in d[i][2]: 214 if delta.color == d[i][1]: 215 delta.color = dialog.color 216 d[i][1] = dialog.color 217 else: 218 pass 219 220 # Restore timeline default color 221 parent.color = timeline_default_color 222 223 # Update track info 224 parent._update_tracks_info()
225
226 - def __lockSelected(self): self._time.lockSelected()
227
228 - def __removeSelected(self): self._time.removeSelected()
229
230 - def __import(self):
231 """Import annotations from a file.""" 232 233 filename = QtGui.QFileDialog.getOpenFileName(parent=self, 234 caption="Import annotations file", 235 directory="", 236 filter="*.csv") 237 if filename == '': 238 return 239 separator = ',' 240 241 with open(filename, 'rU') as csvfile: 242 line = csvfile.readline() 243 if ";" in line: 244 separator = ';' 245 246 with open(filename, 'rU') as csvfile: 247 csvfile = csv.reader(csvfile, delimiter=separator) 248 row = next(csvfile) 249 250 if len(row) == 2: 251 with open(filename, 'rU') as csvfile: 252 csvfile = csv.reader(csvfile, delimiter=separator) 253 self._time.importchart_csv(csvfile) 254 else: 255 # FIXME Get directory from where the video was loaded 256 257 # If there are annotation in the timeline, show a warning 258 if self._time._tracks_info: # dict returns True if not empty 259 message = ["You are about to import new data. ", 260 "If you proceed, current annotations will be erased. ", 261 "Make sure to export current annotations first to save.", 262 "\n", 263 "Are you sure you want to proceed?"] 264 reply = QtGui.QMessageBox.question(self, 265 "Warning!", 266 "".join(message), 267 QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, 268 QtGui.QMessageBox.No) 269 if reply != QtGui.QMessageBox.Yes: 270 return 271 272 with open(filename, 'rU') as csvfile: 273 csvfile = csv.reader(csvfile, delimiter=separator) 274 self._time.import_csv(csvfile) 275 print("Annotations file imported: {:s}".format(filename)) 276 277 # Update info after importing from a file 278 self._time._update_tracks_info()
279
280 - def __export(self):
281 """Export annotations to a file.""" 282 283 # Update info right before exporting 284 self._time._update_tracks_info() 285 286 filename = QtGui.QFileDialog.getSaveFileName(parent=self, 287 caption="Export annotations file", 288 directory=self.getExportFilename(), 289 filter="CSV Files (*.csv)", 290 options=QtGui.QFileDialog.DontUseNativeDialog) 291 if filename != "": 292 with open(filename, 'wb') as csvfile: 293 # spamwriter = csv.writer(csvfile, delimiter=';', quotechar='"') 294 spamwriter = csv.writer(csvfile, dialect='excel') 295 self._time.export_csv(spamwriter) 296 print("Annotations file exported: {:s}".format(filename))
297
298 - def __cleanLine(self):
299 reply = QtGui.QMessageBox.question(self, 'Confirm', 300 "Are you sure you want to clean all the events?", QtGui.QMessageBox.Yes | 301 QtGui.QMessageBox.No, QtGui.QMessageBox.No) 302 if reply == QtGui.QMessageBox.Yes: 303 self._time.cleanLine()
304
305 - def __cleanCharts(self):
306 reply = QtGui.QMessageBox.question(self, 'Confirm', 307 "Are you sure you want to clean all the charts?", QtGui.QMessageBox.Yes | 308 QtGui.QMessageBox.No, QtGui.QMessageBox.No) 309 if reply == QtGui.QMessageBox.Yes: 310 self._time.cleanCharts()
311
312 - def __clean(self):
313 reply = QtGui.QMessageBox.question(self, 'Confirm', 314 "Are you sure you want to clean all the events?", QtGui.QMessageBox.Yes | 315 QtGui.QMessageBox.No, QtGui.QMessageBox.No) 316 if reply == QtGui.QMessageBox.Yes: 317 self._time.clean()
318
319 - def __pickColor(self):
320 self._time.color = QtGui.QColorDialog.getColor(self._time.color) 321 if self._time._selected != None: 322 self._time._selected.color = self._time.color 323 self._time.repaint()
324
325 - def __scaleSliderChange(self, value):
326 scale = 0.1 * value 327 self._time.setMinimumWidth(scale * self._max) 328 self._time.scale = scale 329 self._zoomLabel.setText(str(value * 10).zfill(3) + "%")
330
331 - def __scrollAreaKeyReleaseEvent(self, event):
332 modifiers = int(event.modifiers()) 333 self._time.keyReleaseEvent(event) 334 if modifiers is not QtCore.Qt.ControlModifier and \ 335 modifiers is not int(QtCore.Qt.ShiftModifier | QtCore.Qt.ControlModifier) and \ 336 modifiers is not QtCore.Qt.ShiftModifier: 337 QtGui.QScrollArea.keyReleaseEvent(self._scrollArea, event)
338
339 - def __scrollAreaKeyPressEvent(self, event):
340 modifiers = int(event.modifiers()) 341 if modifiers is not QtCore.Qt.ControlModifier and \ 342 modifiers is not int(QtCore.Qt.ShiftModifier | QtCore.Qt.ControlModifier) and \ 343 modifiers is not QtCore.Qt.ShiftModifier: 344 QtGui.QScrollArea.keyPressEvent(self._scrollArea, event)
345 346 ########################################################################## 347 #### PROPERTIES ########################################################## 348 ########################################################################## 349 350 @property
351 - def pointerChanged(self):
352 return self._time._pointer.moveEvent
353 354 @pointerChanged.setter
355 - def pointerChanged(self, value):
356 self._time._pointer.moveEvent = value
357 358 @property
359 - def value(self): return self._time.position
360 361 @value.setter
362 - def value(self, value):
363 ControlBase.value.fset(self, value) 364 self._time.position = value
365 366 @property
367 - def max(self): return self._time.minimumWidth()
368 369 @max.setter
370 - def max(self, value):
371 self._max = value 372 self._time.setMinimumWidth(value) 373 self.repaint()
374 375 @property
376 - def mouseOverLine(self):
377 globalPos = QtGui.QCursor.pos() 378 widgetPos = self._time.mapFromGlobal(globalPos) 379 return self._time.trackInPosition(widgetPos.x(), widgetPos.y())
380 381 # Video playback properties 382 @property
383 - def playVideoEvent(self):
384 return self._time.playVideoEvent
385 386 @playVideoEvent.setter
387 - def playVideoEvent(self, value):
388 self._time.playVideoEvent = value
389 390 @property
391 - def fpsChanged(self): return self._time.fpsChangeEvent
392 393 @fpsChanged.setter
394 - def fpsChanged(self, value): self._time.fpsChangeEvent = value
395 396 @property
397 - def form(self): return self
398