totem r5864 - in trunk: . src/plugins/opensubtitles



Author: hadess
Date: Mon Dec 15 16:23:54 2008
New Revision: 5864
URL: http://svn.gnome.org/viewvc/totem?rev=5864&view=rev

Log:
2008-12-15  Bastien Nocera  <hadess hadess net>

	* src/plugins/opensubtitles/Makefile.am:
	* src/plugins/opensubtitles/hash.py:
	* src/plugins/opensubtitles/opensubtitles.py:
	* src/plugins/opensubtitles/opensubtitles.totem-plugin.in:
	* src/plugins/opensubtitles/opensubtitles.ui: Patch
	from Xavier Queralt <xqueralt gmail com> and adebarbara gmail com
	to add a plugin to download subtitles for the currently playing
	movie (Closes: #561085)



Added:
   trunk/src/plugins/opensubtitles/
   trunk/src/plugins/opensubtitles/Makefile.am
   trunk/src/plugins/opensubtitles/hash.py
   trunk/src/plugins/opensubtitles/opensubtitles.py
   trunk/src/plugins/opensubtitles/opensubtitles.totem-plugin.in
   trunk/src/plugins/opensubtitles/opensubtitles.ui
Modified:
   trunk/ChangeLog

Added: trunk/src/plugins/opensubtitles/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/src/plugins/opensubtitles/Makefile.am	Mon Dec 15 16:23:54 2008
@@ -0,0 +1,15 @@
+plugindir = $(PLUGINDIR)/opensubtitles
+uidir = $(plugindir)
+plugin_PYTHON = opensubtitles.py hash.py
+
+plugin_in_files = opensubtitles.totem-plugin.in
+
+%.totem-plugin: %.totem-plugin.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache
+
+plugin_DATA = $(plugin_in_files:.totem-plugin.in=.totem-plugin)
+ui_DATA = opensubtitles.ui
+
+EXTRA_DIST = $(plugin_in_files) $(ui_DATA) opensubtitles.py hash.py
+
+CLEANFILES = $(plugin_DATA)
+DISTCLEANFILES = $(plugin_DATA) 

Added: trunk/src/plugins/opensubtitles/hash.py
==============================================================================
--- (empty file)
+++ trunk/src/plugins/opensubtitles/hash.py	Mon Dec 15 16:23:54 2008
@@ -0,0 +1,47 @@
+import struct
+import os
+import gio
+
+SIZE_ERROR = -1
+SEEK_ERROR = -2
+
+def hashFile(name):
+       """ FIXME Need to handle exceptions !! """
+
+ 
+       longlongformat = 'q'  # long long 
+       bytesize = struct.calcsize(longlongformat) 
+           
+       fp = gio.File(name) 
+           
+       filesize = fp.query_info('standard::size', 0).get_attribute_uint64('standard::size') 
+       
+       hash = filesize 
+          
+       if filesize < 65536 * 2: 
+              return SIZE_ERROR, 0
+
+       data = fp.read()                 
+       
+       if data.can_seek() != True:
+               return SEEK_ERROR, 0
+
+       for x in range(65536/bytesize):
+               buffer = data.read(bytesize)
+               (l_value,)= struct.unpack(longlongformat, buffer)  
+               hash += l_value 
+               hash = hash & 0xFFFFFFFFFFFFFFFF #to remain as 64bit number  
+               
+       if data.seek(max(0,filesize-65536),1) != True:
+               return SEEK_ERROR, 0
+
+       for x in range(65536/bytesize):
+               buffer = data.read(bytesize)
+               (l_value,)= struct.unpack(longlongformat, buffer)  
+               hash += l_value 
+               hash = hash & 0xFFFFFFFFFFFFFFFF 
+        
+       data.close() 
+       returnedhash =  "%016x" % hash 
+       return returnedhash, filesize 
+

Added: trunk/src/plugins/opensubtitles/opensubtitles.py
==============================================================================
--- (empty file)
+++ trunk/src/plugins/opensubtitles/opensubtitles.py	Mon Dec 15 16:23:54 2008
@@ -0,0 +1,527 @@
+import totem
+import gobject, gtk, gio
+gobject.threads_init()
+import xmlrpclib
+import threading
+import xdg.BaseDirectory
+from os import sep
+
+from hash import hashFile
+
+USER_AGENT = 'Totem'
+OK200 = '200 OK'
+TOTEM_REMOTE_COMMAND_REPLACE = 14
+
+SUBTITLES_EXT = [
+	"asc",
+	"txt",
+        "sub",
+        "srt",
+        "smi",
+        "ssa",
+        "ass",
+]
+
+LANGUAGES_STR = [(_('Catalan'), 'cat'),
+                 (_('English'), 'eng'),
+                 (_('French'),  'fre'),
+                 (_('German'),  'ger'),
+                 (_('Spanish'), 'spa'),]
+
+LANGUAGES =     {'ca':'cat',
+                 'de':'ger',
+                 'en':'eng',
+                 'es':'spa',
+                 'fr':'fre'}
+
+class SearchThread(threading.Thread):
+    """
+    This is the thread started when the dialog is searching for subtitles
+    """
+    def __init__(self, model):
+        self.model = model
+        self._done = False
+        self._lock = threading.Lock()
+        threading.Thread.__init__(self)
+
+    def run(self):
+        self.model.lock.acquire(True)
+        self.model.results = self.model.os_search_subtitles()
+        self.model.lock.release()
+        self._done = True
+	
+    @property
+    def done(self):
+        """ Thread-safe property to know whether the query is done or not """
+        self._lock.acquire(True)
+        res = self._done
+        self._lock.release()
+        return res
+
+class DownloadThread(threading.Thread):
+    """
+    This is the thread started when the dialog is downloading the subtitles.
+    """
+    def __init__(self, model, subtitle_id):
+        self.model = model
+        self.subtitle_id = subtitle_id
+        self._done = False
+        self._lock = threading.Lock()
+        threading.Thread.__init__(self)
+
+    def run(self):
+        self.model.lock.acquire(True)
+        self.model.subtitles = self.model.os_download_subtitles(self.subtitle_id)
+        self.model.lock.release()
+        self._done = True
+    
+    @property
+    def done(self):
+        """ Thread-safe property to know whether the query is done or not """
+        self._lock.acquire(True)
+        res = self._done
+        self._lock.release()
+        return res
+
+# OpenSubtitles.org API abstraction
+
+class OpenSubtitlesModel(object):
+    """
+    This contains the logic of the opensubtitles service.
+    """
+    def __init__(self, server):
+        self.server = server
+        self.token = None
+
+        try:
+            import locale
+            self.lang = LANGUAGES[locale.getlocale()[0].split('_')[0]]
+        except:
+            self.lang = 'eng'
+        self.hash = None
+        self.size = 0
+
+        self.lock = threading.Lock()
+        self.results = []
+        self.subtitles = ''
+
+        self.message = ''
+
+    def os_login(self, username='', password=''):
+        """
+        Logs into the opensubtitles web service and gets a valid token for
+        the comming comunications. If we are already logged it only checks
+        the if the token is still valid.
+
+        @rtype : bool
+        """
+        result = None
+        self.message = ''
+
+        if self.token:
+            # We have already logged-in before, check the connection
+            try:
+                result = self.server.NoOperation(self.token)
+            except:
+                pass
+            if result and result['status'] != OK200:
+                return True
+        try:
+            result = self.server.LogIn(username, password, self.lang, USER_AGENT)
+        except:
+            pass
+        if result and result.get('status') == OK200:
+            self.token = result.get('token')
+            if self.token:
+                return True
+
+        self.message = _('Could not contact the OpenSubtitles website')
+
+        return False
+
+    def os_search_subtitles(self):
+        """
+
+        """
+        self.message = ''
+        if self.os_login():
+            searchdata = {'sublanguageid': self.lang, 
+                          'moviehash'    : self.hash, 
+                          'moviebytesize': str(self.size)}
+            try:
+                result = self.server.SearchSubtitles(self.token, [searchdata])
+            except xmlrpclib.ProtocolError:
+                self.message = _('Could not contact the OpenSubtitles website')
+
+            if result.get('data'):
+                return result['data']
+            else:
+                self.message = _('No results found')
+
+        return None
+
+    def os_download_subtitles(self, subtitleId):
+        """
+        """
+        self.message = ''
+        if self.os_login():
+            try:
+                result = self.server.DownloadSubtitles(self.token, [subtitleId])
+            except xmlrpclib.ProtocolError:
+                self.message = _('Could not contact the OpenSubtitles website')
+
+            if result and result.get('status') == OK200:
+                try:
+                    subtitle64 = result['data'][0]['data']
+                except:
+                    self.message = _('Could not contact the OpenSubtitles website')
+                    return None
+
+                import StringIO, gzip, base64
+                subtitleDecoded = base64.decodestring(subtitle64)
+                subtitleGzipped = StringIO.StringIO(subtitleDecoded)
+                subtitleGzippedFile = gzip.GzipFile(fileobj=subtitleGzipped)
+
+                return subtitleGzippedFile.read()
+
+        return None
+
+
+class OpenSubtitles(totem.Plugin):
+    def __init__(self):
+        totem.Plugin.__init__(self)
+        self.dialog = None
+
+    # totem.Plugin methods
+
+    def activate(self, totem_object):
+        """
+        Called when the plugin is activated.
+        Here the sidebar page is initialized(set up the treeview, connect 
+        the callbacks, ...) and added to totem.
+
+        @param totem_object:
+        @type  totem_object: {totem.TotemObject}
+        """
+        self.totem = totem_object
+	self.filename = None
+
+        self.manager = self.totem.get_ui_manager()
+        self.os_append_menu()
+
+        self.totem.connect('file-opened', self.on_totem__file_opened)
+        self.totem.connect('file-closed', self.on_totem__file_closed)
+
+	# Obtain the ServerProxy and init the model
+        server = xmlrpclib.Server('http://www.opensubtitles.org/xml-rpc')
+        self.model = OpenSubtitlesModel(server)
+
+    def deactivate(self, totem):
+        if self.dialog:
+            self.dialog.destroy()
+	    self.dialog = None
+	
+        self.os_delete_menu()
+
+    # UI related code
+
+    def os_build_dialog(self, action, totem_object):
+        builder = self.load_interface("opensubtitles.ui", 
+                                       True, 
+                                       self.totem.get_main_window(), 
+                                       self)
+
+        # Obtain all the widgets we need to initialize
+        combobox =       builder.get_object('language_combobox')
+        languages =      builder.get_object('language_model')
+        self.progress =  builder.get_object('progress_bar')
+        self.treeview =  builder.get_object('subtitle_treeview')
+        self.liststore = builder.get_object('subtitle_model')
+        self.dialog =    builder.get_object('subtitles_dialog')
+	self.find_button = builder.get_object('find_button')
+	self.apply_button = builder.get_object('apply_button')
+	self.close_button = builder.get_object('close_button')
+
+        # Set up and populate the languages combobox
+        renderer = gtk.CellRendererText()
+        combobox.set_model(languages)
+        combobox.pack_start(renderer, True)
+        combobox.add_attribute(renderer, 'text', 0)
+
+        for lang in LANGUAGES_STR:
+            it = languages.append(lang)
+            if lang[1] == self.model.lang:
+                combobox.set_active_iter(it)
+
+        # Set up the results treeview 
+        renderer = gtk.CellRendererText()
+        self.treeview.set_model(self.liststore)
+	self.treeview.set_headers_visible(False)
+        self.treeview.insert_column_with_attributes(0, _("Subtitles"), renderer, text=0)
+	# translators comment:
+	# This is the file-type of the subtitle file detected
+        self.treeview.insert_column_with_attributes(1, _("Format"), renderer, text=1)
+	# translators comment:
+	# This is a rating of the quality of the subtitle
+        self.treeview.insert_column_with_attributes(2, _("Rating"), renderer, text=2)
+
+	self.apply_button.set_sensitive(False)
+
+        self.apply_button.connect('clicked', self.on_apply_clicked)
+        self.find_button.connect('clicked', self.on_find_clicked)
+        self.close_button.connect('clicked', self.on_close_clicked)
+
+	# Set up signals
+
+        combobox_changed_id = combobox.connect('changed', self.on_combobox__changed)
+	self.dialog.connect ('delete-event', self.dialog.hide_on_delete)
+	self.dialog.set_transient_for (self.totem.get_main_window())
+	self.dialog.set_position (gtk.WIN_POS_CENTER_ON_PARENT)
+
+	# Connect the callback
+        self.treeview.get_selection().connect('changed', self.on_treeview__row_change)
+        self.treeview.connect('row-activated', self.on_treeview__row_activate)
+
+    def os_show_dialog(self, action, totem_object):
+        if not self.dialog:
+            self.os_build_dialog(action, totem_object)
+
+        filename = self.totem.get_current_mrl()
+        if not self.model.results or filename != self.filename:
+            self.filename = filename
+
+        self.dialog.show_all()
+
+	self.progress.set_fraction(0.0)
+
+    def os_append_menu(self):
+        """
+        """
+	
+        self.os_action_group = gtk.ActionGroup('OpenSubtitles')
+
+        self.action = gtk.Action('opensubtitles',
+                             _('_Download Movie Subtitles...'),
+                             _("Download movie subtitles from OpenSubtitles"),
+                             '')
+
+        self.os_action_group.add_action(self.action)
+
+        self.manager.insert_action_group(self.os_action_group, 0)
+
+        self.menu_id = self.manager.new_merge_id()
+        self.manager.add_ui(self.menu_id,
+                             '/tmw-menubar/view/subtitle-download-placeholder',
+                             'opensubtitles',
+                             'opensubtitles',
+                             gtk.UI_MANAGER_MENUITEM,
+                             False
+                            )
+        self.action.set_visible(True)
+
+        self.manager.ensure_update()
+
+        self.action.connect('activate', self.os_show_dialog, self.totem)
+
+        self.action.set_sensitive(self.totem.is_playing() and
+				  self.os_check_allowed_scheme() and
+                                  not self.os_check_is_audio())
+
+    def os_check_allowed_scheme(self):
+        scheme = gio.File(self.totem.get_current_mrl()).get_uri_scheme()
+        if scheme == 'dvd' or scheme == 'http' or scheme == 'dvb' or scheme == 'vcd':
+            return False
+        return True
+
+    def os_check_is_audio(self):
+        # FIXME need to use something else here
+        # I think we must use video widget metadata but I don't found a way 
+	# to get this info from python
+        filename = self.totem.get_current_mrl()
+        if gio.content_type_guess(filename).split('/')[0] == 'audio':
+            return True
+        return False
+
+    def os_delete_menu(self):
+        self.manager.remove_action_group(self.os_action_group)
+        self.manager.remove_ui(self.menu_id)
+
+    def os_get_results(self):
+        """
+        """
+        self.liststore.clear()
+	self.treeview.set_headers_visible(False)
+        self.model.results = []
+        self.apply_button.set_sensitive(False)
+	self.find_button.set_sensitive(False)        
+
+        self.dialog.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
+
+        thread = SearchThread(self.model)
+        thread.start()
+        gobject.idle_add(self.os_populate_treeview)
+
+        self.progress.set_text(_('Searching subtitles...'))
+        gobject.timeout_add(350, self.os_progress_bar_increment, thread)
+
+    def os_populate_treeview(self):
+        """
+        """
+        if self.model.lock.acquire(False) == False:
+            return True
+
+        if self.model.results:
+            self.apply_button.set_sensitive(True)
+            for subData in self.model.results:
+		if not SUBTITLES_EXT.count(subData['SubFormat']):
+			continue
+                self.liststore.append([subData['SubFileName'], subData['SubFormat'], subData['SubRating'], subData['IDSubtitleFile'],])
+	        self.treeview.set_headers_visible(True)
+        else:
+            self.apply_button.set_sensitive(False)
+
+        self.model.lock.release()
+
+        self.dialog.window.set_cursor(None)
+
+        return False
+
+    def os_save_selected_subtitle(self, filename=None):
+        """
+        """
+        self.dialog.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
+
+        model, rows = self.treeview.get_selection().get_selected_rows()
+        if rows:
+            iter = model.get_iter(rows[0])
+            subtitle_id = model.get_value(iter, 3)
+            subtitle_format = model.get_value(iter, 1)
+
+            gfile = None
+
+            if not filename:
+                directory = gio.File(xdg.BaseDirectory.xdg_cache_home + sep + 'totem' + sep + 'subtitles' + sep) 
+                if not directory.query_exists():
+                    directory.make_directory()
+
+                file = gio.File(self.filename)
+                movie_name = file.get_basename().rpartition('.')[0]
+                filename = directory.get_uri() + sep + movie_name + '.' + subtitle_format
+
+            self.model.subtitles = ''
+
+            thread = DownloadThread(self.model, subtitle_id)
+            thread.start()
+            gobject.idle_add(self.os_save_subtitles, filename)
+
+            self.progress.set_text(_('Downloading the subtitles...'))
+            gobject.timeout_add(350, self.os_progress_bar_increment, thread)
+        else:
+            #warn user!
+            pass
+
+    def os_save_subtitles(self, filename):
+        if self.model.lock.acquire(False) == False:
+            return True
+
+        if self.model.subtitles:
+            # Delete all previous cached subtitle for this file 
+            for ext in SUBTITLES_EXT:
+		fp = gio.File(filename[:-3] + ext)
+		if fp.query_exists():
+                    fp.delete()
+
+            fp = gio.File(filename)
+            suburi = fp.get_uri ()
+
+            subFile  = fp.replace('', False)
+            subFile.write(self.model.subtitles)
+            subFile.close()
+
+        self.model.lock.release()
+
+        self.dialog.window.set_cursor(None)
+
+        if suburi:
+            self.totem.set_current_subtitle(suburi)
+
+        return False
+
+    def os_progress_bar_increment(self, thread):
+
+        if not thread.done:
+            self.progress.pulse()
+            return True
+
+        if self.model.message:
+            self.progress.set_text(self.model.message)
+        else:
+            self.progress.set_text('')
+	
+	self.progress.set_fraction(0.0)
+	self.find_button.set_sensitive(True)
+        self.apply_button.set_sensitive(False)
+        return False
+
+    def os_download_and_apply(self):
+        self.apply_button.set_sensitive(False)
+        self.find_button.set_sensitive(False)
+        self.action.set_sensitive(False)
+        self.os_save_selected_subtitle()
+
+    # Callbacks
+
+    def on_treeview__row_change(self, selection):
+        if selection.count_selected_rows() > 0:
+            self.apply_button.set_sensitive(True)
+        else:
+            self.apply_button.set_sensitive(False)
+
+    def on_treeview__row_activate(self, path, column, data):
+	self.os_download_and_apply()
+
+    def on_totem__file_opened(self, totem, filename):
+        """
+        """
+        # Check if allows subtitles
+	if self.os_check_allowed_scheme() and not self.os_check_is_audio():
+            self.action.set_sensitive(True)
+	    if self.dialog:
+	    	self.find_button.set_sensitive(True)
+		self.filename = self.totem.get_current_mrl()
+		self.liststore.clear()
+	        self.treeview.set_headers_visible(False)
+	    	self.apply_button.set_sensitive(False)
+		self.results = [] 
+	else:
+            self.action.set_sensitive(False)
+	    if self.dialog and self.dialog.is_active():
+                self.liststore.clear()
+	        self.treeview.set_headers_visible(False)
+	    	self.apply_button.set_sensitive(False)
+	    	self.find_button.set_sensitive(False)
+
+    def on_totem__file_closed(self, totem):
+        self.action.set_sensitive(False)
+        if self.dialog:
+	    self.apply_button.set_sensitive(False)
+	    self.find_button.set_sensitive(False)
+
+    def on_combobox__changed(self, combobox):
+        iter = combobox.get_active_iter()
+        self.model.lang = combobox.get_model().get_value(iter, 1)
+
+    def on_close_clicked(self, data):
+        self.dialog.destroy()
+        self.dialog = None
+
+    def on_apply_clicked(self, data):
+	self.os_download_and_apply()
+
+    def on_find_clicked(self, data):
+        self.apply_button.set_sensitive(False)
+        self.find_button.set_sensitive(False)
+        self.filename = self.totem.get_current_mrl()
+        self.model.hash , self.model.size = hashFile(self.filename)
+
+        self.os_get_results()

Added: trunk/src/plugins/opensubtitles/opensubtitles.totem-plugin.in
==============================================================================
--- (empty file)
+++ trunk/src/plugins/opensubtitles/opensubtitles.totem-plugin.in	Mon Dec 15 16:23:54 2008
@@ -0,0 +1,9 @@
+[Totem Plugin]
+Loader=python
+Module=opensubtitles
+IAge=1
+_Name=Subtitles downloader
+_Description=Look for a subtitle for the currently playing movie
+Authors=Xavier Queralt <xqueralt gmail com>
+Copyright=Copyright  2008 Xavier Queralt
+Website=http://www.gnome.org/projects/totem/

Added: trunk/src/plugins/opensubtitles/opensubtitles.ui
==============================================================================
--- (empty file)
+++ trunk/src/plugins/opensubtitles/opensubtitles.ui	Mon Dec 15 16:23:54 2008
@@ -0,0 +1,191 @@
+<?xml version="1.0"?>
+<interface>
+  <requires lib="gtk+" version="2.14"/>
+  <!-- interface-naming-policy toplevel-contextual -->
+  <object class="GtkListStore" id="subtitle_model">
+    <columns>
+      <!-- column-name gchararray -->
+      <column type="gchararray"/>
+      <!-- column-name gchararray1 -->
+      <column type="gchararray"/>
+      <!-- column-name gchararray2 -->
+      <column type="gchararray"/>
+      <!-- column-name gchararray3 -->
+      <column type="gchararray"/>
+    </columns>
+  </object>
+  <object class="GtkListStore" id="language_model">
+    <columns>
+      <!-- column-name gchararray -->
+      <column type="gchararray"/>
+      <!-- column-name gchararray1 -->
+      <column type="gchararray"/>
+    </columns>
+  </object>
+  <object class="GtkImage" id="image_find">
+    <property name="visible">True</property>
+    <property name="stock">gtk-find</property>
+  </object>
+  <object class="GtkImage" id="image_apply">
+    <property name="visible">True</property>
+    <property name="stock">gtk-media-play</property>
+    <property name="icon-size">4</property>
+  </object>
+  <object class="GtkImage" id="image_close">
+    <property name="visible">True</property>
+    <property name="stock">gtk-close</property>
+  </object>
+  <object class="GtkWindow" id="subtitles_dialog">
+    <property name="border_width">5</property>
+    <property name="title">Download Movie Subtitles</property>
+    <property name="window_position">center-on-parent</property>
+    <property name="default_width">400</property>
+    <property name="default_height">400</property>
+    <property name="type_hint">dialog</property>
+    <child>
+      <object class="GtkVBox" id="vbox1">
+        <property name="spacing">6</property>
+        <child>
+          <object class="GtkFrame" id="frame1">
+            <property name="visible">True</property>
+            <property name="label_xalign">0</property>
+            <property name="shadow_type">none</property>
+            <child>
+              <object class="GtkAlignment" id="alignment1">
+                <property name="visible">True</property>
+                <property name="left_padding">12</property>
+                <child>
+                  <object class="GtkVBox" id="vbox3">
+                    <child>
+                      <object class="GtkHBox" id="hbox2">
+                        <child>
+                          <object class="GtkLabel" id="label3">
+                            <property name="visible">True</property>
+                            <property name="label" translatable="yes">Subtitle language;</property>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="padding">6</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkComboBox" id="language_combobox">
+                            <property name="visible">True</property>
+                          </object>
+                          <packing>
+                            <property name="padding">6</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkButton" id="find_button">
+                            <property name="label" translatable="yes">_Search</property>
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="receives_default">True</property>
+                            <property name="image">image_find</property>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="position">2</property>
+                          </packing>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">False</property>
+                        <property name="padding">6</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+            <child type="label">
+              <object class="GtkLabel" id="label1">
+                <property name="visible">True</property>
+                <property name="label" translatable="yes">&lt;b&gt;Language&lt;/b&gt;</property>
+                <property name="use_markup">True</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="padding">6</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkScrolledWindow" id="scrolledwindow1">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="hscrollbar_policy">never</property>
+            <property name="vscrollbar_policy">automatic</property>
+            <property name="shadow_type">etched-out</property>
+            <child>
+              <object class="GtkTreeView" id="subtitle_treeview">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkProgressBar" id="progress_bar"/>
+          <packing>
+            <property name="expand">False</property>
+            <property name="position">2</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkHButtonBox" id="hbbox1">
+            <property name="visible">True</property>
+            <property name="spacing">12</property>
+            <property name="layout_style">end</property>
+            <child>
+              <object class="GtkButton" id="apply_button">
+                <property name="label" translatable="yes">Play with subtitle</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="image">image_apply</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="close_button">
+                <property name="label" translatable="yes">Close</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="image">image_close</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="pack_type">end</property>
+            <property name="position">3</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
+</interface>



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