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
    168 -    def aboutToShowContextMenuEvent(self):
    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