[pitivi] Allow entering a frame number into the time widget



commit b9cae19ea0618f4554af3a339658d0c2d927071f
Author: Jean-François Fortin Tam <nekohayo gmail com>
Date:   Wed Oct 10 21:36:36 2012 -0400

    Allow entering a frame number into the time widget
    
    The downside to this is that we don't seek live while typing anymore:
    the frame number being typed would always get overridden by timecodes.
    Users have to activate the text widget (press Enter) to seek with it.
    
    On the other hand, this is compensated by the fact that we also seek
    when the user defocuses the text widget.
    
    Fixes bug #684236

 pitivi/mainwindow.py    |    1 +
 pitivi/utils/widgets.py |   74 +++++++++++++++++++++++++++++++++-------------
 pitivi/viewer.py        |   17 ++++++----
 3 files changed, 64 insertions(+), 28 deletions(-)
---
diff --git a/pitivi/mainwindow.py b/pitivi/mainwindow.py
index 8522030..86f7df8 100644
--- a/pitivi/mainwindow.py
+++ b/pitivi/mainwindow.py
@@ -1165,6 +1165,7 @@ class PitiviMainWindow(Gtk.Window, Loggable):
         ratio = float(project.videopar.num / project.videopar.denom *
                       project.videowidth) / float(project.videoheight)
         self.viewer.setDisplayAspectRatio(ratio)
+        self.viewer.timecode_entry.setFramerate(project.videorate)
 
     def _sourceListMissingPluginsCb(self, project, uri, factory,
             details, descriptions, missingPluginsCallback):
diff --git a/pitivi/utils/widgets.py b/pitivi/utils/widgets.py
index 21b9f46..2fa6a24 100644
--- a/pitivi/utils/widgets.py
+++ b/pitivi/utils/widgets.py
@@ -90,17 +90,20 @@ class DefaultWidget(Gtk.Label, DynamicWidget):
 
 
 class TextWidget(Gtk.HBox, DynamicWidget):
-
-    """A Gtk.Entry which emits a value-changed signal only when its input is
+    """
+    A Gtk.Entry which emits a "value-changed" signal only when its input is
     valid (matches the provided regex). If the input is invalid, a warning
-    icon is displayed."""
+    icon is displayed.
+
+    You can also connect to the "activate" signal if you don't want to watch
+    for live changes, but it will only be emitted if the input is valid when
+    the user presses Enter.
+    """
 
     __gtype_name__ = 'TextWidget'
     __gsignals__ = {
-        "value-changed": (
-            GObject.SignalFlags.RUN_LAST,
-            None,
-            (),)
+        "value-changed": (GObject.SignalFlags.RUN_LAST, None, (),),
+        "activate": (GObject.SignalFlags.RUN_LAST, None, (),)
     }
 
     __INVALID__ = Gdk.Color(0xFFFF, 0, 0)
@@ -132,6 +135,7 @@ class TextWidget(Gtk.HBox, DynamicWidget):
         self.valid = False
         self.send_signal = True
         self.text.connect("changed", self._textChanged)
+        self.text.connect("activate", self._activateCb)
         if matches:
             if type(matches) is str:
                 self.matches = re.compile(matches)
@@ -174,6 +178,17 @@ class TextWidget(Gtk.HBox, DynamicWidget):
 
         self.send_signal = True
 
+    def _activateCb(self, unused_widget):
+        """
+        Similar to _textChanged, to account for the case where we connect to
+        the "activate" signal instead of "text-changed".
+
+        We don't need to set the icons or anything like that, as _textChanged
+        does it already.
+        """
+        if self.matches and self.send_signal:
+            self.emit("activate")
+
     def _filter(self, text):
         match = self.matches.match(text)
         if match is not None:
@@ -249,40 +264,57 @@ class NumericWidget(Gtk.HBox, DynamicWidget):
 
 
 class TimeWidget(TextWidget, DynamicWidget):
-    """ A widget that contains a time in nanosconds"""
-
-    regex = re.compile("^([0-9]:[0-5][0-9]:[0-5][0-9])\.[0-9][0-9][0-9]$")
+    """
+    A widget that contains a time in nanoseconds. Accepts timecode formats
+    or a frame number (integer).
+    """
+    # The "frame number" match rule is ^([0-9]+)$ (with a + to require 1 digit)
+    # The "timecode" rule is ^([0-9]:[0-5][0-9]:[0-5][0-9])\.[0-9][0-9][0-9]$"
+    # Combining the two, we get:
+    regex = re.compile("^([0-9]+)$|^([0-9]:[0-5][0-9]:[0-5][0-9])\.[0-9][0-9][0-9]$")
     __gtype_name__ = 'TimeWidget'
 
     def __init__(self, default=None):
         DynamicWidget.__init__(self, default)
         TextWidget.__init__(self, self.regex)
         TextWidget.set_width_chars(self, 10)
+        self._framerate = None
 
     def getWidgetValue(self):
         timecode = TextWidget.getWidgetValue(self)
 
-        hh, mm, end = timecode.split(":")
-        ss, xxx = end.split(".")
-        nanosecs = int(hh) * 3.6 * 10e12 \
-            + int(mm) * 6 * 10e10 \
-            + int(ss) * 10e9 \
-            + int(xxx) * 10e6
-
-        nanosecs = nanosecs / 10  # Compensate the 10 factor of e notation
-
-        return nanosecs
+        if ":" in timecode:
+            hh, mm, end = timecode.split(":")
+            ss, xxx = end.split(".")
+            nanosecs = int(hh) * 3.6 * 10e12 \
+                + int(mm) * 6 * 10e10 \
+                + int(ss) * 10e9 \
+                + int(xxx) * 10e6
+            nanosecs = nanosecs / 10  # Compensate the 10 factor of e notation
+        else:
+            # We were given a frame number. Convert from the project framerate.
+            frame_no = int(timecode)
+            nanosecs = frame_no / float(self._framerate) * Gst.SECOND
+        # The seeker won't like floating point nanoseconds!
+        return int(nanosecs)
 
     def setWidgetValue(self, value, send_signal=True):
         TextWidget.setWidgetValue(self, time_to_string(value),
                                 send_signal=send_signal)
 
+    # No need to define connectValueChanged as it is inherited from DynamicWidget
+    def connectActivateEvent(self, activateCb):
+        return self.connect("activate", activateCb)
+
     def connectFocusEvents(self, focusInCb, focusOutCb):
-        fIn = self.text.connect("button-press-event", focusInCb)
+        fIn = self.text.connect("focus-in-event", focusInCb)
         fOut = self.text.connect("focus-out-event", focusOutCb)
 
         return [fIn, fOut]
 
+    def setFramerate(self, framerate):
+        self._framerate = framerate
+
 
 class FractionWidget(TextWidget, DynamicWidget):
 
diff --git a/pitivi/viewer.py b/pitivi/viewer.py
index 7d4b978..923421b 100644
--- a/pitivi/viewer.py
+++ b/pitivi/viewer.py
@@ -241,7 +241,8 @@ class PitiviViewer(Gtk.VBox, Loggable):
         # current time
         self.timecode_entry = TimeWidget()
         self.timecode_entry.setWidgetValue(0)
-        self.timecode_entry.connect("value-changed", self._jumpToTimecodeCb)
+        self.timecode_entry.set_tooltip_text(_('Enter a timecode or frame number\nand press "Enter" to go to 
that position'))
+        self.timecode_entry.connectActivateEvent(self._entryActivateCb)
         self.timecode_entry.connectFocusEvents(self._entryFocusInCb, self._entryFocusOutCb)
         bbox.pack_start(self.timecode_entry, False, 10, 0)
         self._haveUI = True
@@ -284,12 +285,20 @@ class PitiviViewer(Gtk.VBox, Loggable):
         except:
             self.warning("could not set ratio !")
 
+    def _entryActivateCb(self, entry):
+        self._seekFromTimecodeWidget()
+
     def _entryFocusInCb(self, entry, event):
         self.app.gui.setActionsSensitive(False)
 
     def _entryFocusOutCb(self, entry, event):
+        self._seekFromTimecodeWidget()
         self.app.gui.setActionsSensitive(True)
 
+    def _seekFromTimecodeWidget(self):
+        nanoseconds = self.timecode_entry.getWidgetValue()
+        self.seeker.seek(nanoseconds)
+
     ## active Timeline calllbacks
     def _durationChangedCb(self, unused_pipeline, duration):
         if duration == 0:
@@ -341,12 +350,6 @@ class PitiviViewer(Gtk.VBox, Loggable):
         except:
             self.warning("Couldn't seek to the end of the timeline")
 
-    ## Callback for jumping to a specific timecode
-
-    def _jumpToTimecodeCb(self, widget):
-        nanoseconds = widget.getWidgetValue()
-        self.seeker.seek(nanoseconds)
-
     ## public methods for controlling playback
 
     def undock(self):


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]