home *** CD-ROM | disk | FTP | other *** search
/ PC Welt 2006 November (DVD) / PCWELT_11_2006.ISO / casper / filesystem.squashfs / usr / lib / python2.4 / site-packages / serpentine / mastering.py < prev    next >
Encoding:
Python Source  |  2006-08-23  |  26.3 KB  |  810 lines

  1. # Copyright (C) 2005 Tiago Cogumbreiro <cogumbreiro@users.sf.net>
  2. #
  3. # This library is free software; you can redistribute it and/or
  4. # modify it under the terms of the GNU Library General Public
  5. # License as published by the Free Software Foundation; either
  6. # version 2 of the License, or (at your option) any later version.
  7. #
  8. # This library is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  11. # Library General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU Library General Public
  14. # License along with this library; if not, write to the
  15. # Free Software Foundation, Inc., 59 Temple Place - Suite 330,
  16. # Boston, MA 02111-1307, USA.
  17. #
  18. # Authors: Tiago Cogumbreiro <cogumbreiro@users.sf.net>
  19.  
  20. import gtk
  21. import gobject
  22. import sys
  23. import weakref
  24.  
  25. from gtk import glade
  26. from os import path
  27. from types import IntType, TupleType
  28. from gettext import gettext as _
  29. from gettext import ngettext as N_
  30.  
  31. # Local modules
  32. import operations
  33. import audio
  34. import xspf
  35. import gtkutil
  36. import urlutil
  37.  
  38. from gtkutil import DictStore
  39. from operations import OperationsQueue
  40. from gdkpiechart import SerpentineUsage
  41.  
  42. ################################################################################
  43. # Operations used on AudioMastering
  44. #
  45.  
  46. class ErrorTrapper (operations.Operation, operations.OperationListener):
  47.     def __init__ (self, parent = None):
  48.         operations.Operation.__init__ (self)
  49.         self.__errors = []
  50.         self._parent = parent
  51.     
  52.     errors = property (lambda self: self.__errors)
  53.     parent = property (lambda self: self._parent)
  54.     
  55.     def on_finished (self, event):
  56.         if event.id == operations.ERROR:
  57.             self.errors.append (event.source)
  58.     
  59.     def start (self):
  60.         if len (self.errors) == 0:
  61.             e = operations.FinishedEvent (self, operations.SUCCESSFUL)
  62.             for l in self.listeners:
  63.                 l.on_finished (e)
  64.             return
  65.  
  66.         filenames = []
  67.         for e in self.errors:
  68.             filenames.append (urlutil.basename(e.hints['location']))
  69.         del self.__errors
  70.         
  71.         title = N_(
  72.             "Unsupported file type",
  73.             "Unsupported file types",
  74.             len(filenames)
  75.         )
  76.  
  77.         msg = N_(
  78.             "The following file was not added:",
  79.             "The following files were not added:",
  80.             len(filenames)
  81.         )
  82.  
  83.         gtkutil.list_dialog(
  84.             title,
  85.             _("If you're having problems opening certain files make sure you "
  86.               "have the GStreamer plugins needed to decode them."),
  87.             list_title=msg,
  88.             parent=self.parent,
  89.             items=filenames,
  90.             stock = gtk.STOCK_DIALOG_ERROR,
  91.             buttons =(gtk.STOCK_CLOSE, gtk.RESPONSE_OK),
  92.         )
  93.         
  94.         e = operations.FinishedEvent (self, operations.SUCCESSFUL)
  95.         for l in self.listeners:
  96.             l.on_finished (e)
  97.  
  98. class AddFile (audio.AudioMetadataListener, operations.Operation):
  99.     # TODO: Implement full Operation here
  100.     
  101.     running = property (lambda self: False)
  102.  
  103.     def __init__ (self, music_list, hints, insert=None, app=None):
  104.         operations.Operation.__init__ (self)
  105.         self.hints = hints
  106.         self.music_list = music_list
  107.         self.insert = insert
  108.         # We normalize the paths by default
  109.         hints["location"] = urlutil.normalize(hints["location"])
  110.         self.app = app
  111.     
  112.     def start (self):
  113.         if self.app.preferences.useGnomeVfs:
  114.             oper = audio.get_metadata(audio.GVFS_SRC, self.hints["location"])
  115.             
  116.         else:
  117.             url = urlutil.UrlParse(self.hints["location"])
  118.             if not url.is_local:
  119.                 self._send_finished_error(
  120.                     operations.ERROR,
  121.                     error=StandardError(self.hints["location"])
  122.                 )
  123.                 return
  124.                 
  125.             filename = url.path
  126.             oper = audio.get_metadata("filesrc", filename)
  127.  
  128.         oper.listeners.append (self)
  129.         try:
  130.             oper.start()
  131.         except audio.GstPlayingFailledError:
  132.             self._send_finished_event(
  133.                 operations.ERROR,
  134.                 error=StandardError(self.hints["location"])
  135.             )
  136.     
  137.     def on_metadata (self, event, metadata):
  138.         title = urlutil.basename(self.hints['location'])
  139.         title = path.splitext(title)[0]
  140.         
  141.         row = {
  142.             "location": self.hints['location'],
  143.             "cache_location": "",
  144.             "title": title or _("Unknown"),
  145.             "artist": _("Unknown Artist"),
  146.             "duration": int(metadata['duration']),
  147.         }
  148.         
  149.         if metadata.has_key ('title'):
  150.             row['title'] = metadata['title']
  151.         if metadata.has_key ('artist'):
  152.             row['artist'] = metadata['artist']
  153.             
  154.         if self.hints.has_key ('title'):
  155.             row['title'] = self.hints['title']
  156.         if self.hints.has_key ('artist'):
  157.             row['artist'] = self.hints['artist']
  158.  
  159.         if self.insert is not None:
  160.             self.music_list.insert (self.insert, row)
  161.         else:
  162.             self.music_list.append (row)
  163.         
  164.     
  165.     def on_finished (self, evt):
  166.         e = operations.FinishedEvent (self, evt.id)
  167.         for l in self.listeners:
  168.             l.on_finished (e)
  169.             
  170.  
  171. class UpdateDiscUsage (operations.Operation):
  172.     def __init__ (self, masterer, update):
  173.         operations.Operation.__init__ (self)
  174.         self.__update = update
  175.         self.__masterer = masterer
  176.     
  177.     running = property (lambda self: False)
  178.     
  179.     can_run = property (lambda self: True)
  180.         
  181.     def start (self):
  182.         self.__masterer.update = self.__update
  183.         if self.__update:
  184.             self.__masterer.update_disc_usage()
  185.         e = operations.FinishedEvent (self, operations.SUCCESSFUL)
  186.         for l in self.listeners:
  187.             l.on_finished (e)
  188.  
  189. ################################################################################
  190.  
  191. class MusicListListener:
  192.     def on_musics_added (self, event, rows):
  193.         pass
  194.     
  195.     def on_musics_removed (self, event, rows):
  196.         pass
  197.  
  198. class MusicList (operations.Listenable):
  199.     def __getitem__ (self):
  200.         pass
  201.         
  202.     def append_many (self, rows):
  203.         pass
  204.     
  205.     def append (self, row):
  206.         pass
  207.     
  208.     def insert (self, index, row):
  209.         pass
  210.     
  211.     def insert_many (self, index, rows):
  212.         pass
  213.     
  214.     def __len__ (self):
  215.         pass
  216.     
  217.     def __delitem__ (self, index):
  218.         pass
  219.     
  220.     def delete_many (self, indexes):
  221.         pass
  222.     
  223.     def clear (self):
  224.         pass
  225.     
  226.     def has_key (self, key):
  227.         pass
  228.     
  229.     def from_playlist (self, playlist):
  230.         rows = []
  231.         for t in playlist.tracks:
  232.             rows.append ({'location': t.location,
  233.                           'duration': t.duration,
  234.                           'title': t.title,
  235.                           'artist': t.creator})
  236.         self.append_many (rows)
  237.  
  238.     def to_playlist (self, playlist):
  239.         for r in self:
  240.             t = xspf.Track()
  241.             t.location = r['location']
  242.             t.duration = r['duration']
  243.             t.title = r['title']
  244.             t.creator = r['artist']
  245.             playlist.tracks.append (t)
  246.  
  247. class GtkMusicList (MusicList):
  248.     """The GtkMusicList uses a ListStore as a backend, it is not visual and
  249.     depends only on glib.
  250.     
  251.     Takes care of the data source. Supports events and listeners.
  252.     """
  253.     SPEC = (
  254.         # URI is used in converter
  255.         {"name": "location", "type": gobject.TYPE_STRING},
  256.         # filename is used in recorder
  257.         {"name": "cache_location", "type": gobject.TYPE_STRING},
  258.         # Remaining items are for the list
  259.         {"name": "duration", "type": gobject.TYPE_INT},
  260.         {"name": "title", "type": gobject.TYPE_STRING},
  261.         {"name": "artist", "type": gobject.TYPE_STRING},
  262.         {"name": "time", "type": gobject.TYPE_STRING}
  263.     )
  264.             
  265.     def __init__ (self):
  266.         operations.Listenable.__init__ (self)
  267.         self.__model = DictStore (*self.SPEC)
  268.         self.__total_duration = 0
  269.         self.__freezed = False
  270.     
  271.     model = property (fget=lambda self: self.__model, doc="Associated ListStore.")
  272.     total_duration = property (fget=lambda self:self.__total_duration, doc="Total disc duration, in seconds.")
  273.     
  274.     def __getitem__ (self, index):
  275.         return self.model.get (index)
  276.     
  277.     def append_many (self, rows):
  278.         self.__freezed = True
  279.         for row in rows:
  280.             self.append (row)
  281.         self.__freezed = False
  282.         
  283.         rows = tuple(rows)
  284.         e = operations.Event(self)
  285.         for l in self.listeners:
  286.             l.on_musics_added (e, rows)
  287.     
  288.     def __correct_row (self, row):
  289.         if not row.has_key ('time'):
  290.             row['time'] = "%.2d:%.2d" % (row['duration'] / 60, row['duration'] % 60)
  291.         if not row.has_key ('cache_location'):
  292.             row['cache_location'] = ''
  293.         return row
  294.         
  295.     def append (self, row):
  296.         row = self.__correct_row(row)
  297.         self.model.append (row)
  298.         self.__total_duration += int(row['duration'])
  299.  
  300.         if not self.__freezed:
  301.             e = operations.Event (self)
  302.             rows = (row,)
  303.             for l in self.listeners:
  304.                 l.on_musics_added (e, rows)
  305.     
  306.     def insert (self, index, row):
  307.  
  308.         row = self.__correct_row(row)
  309.         self.model.insert_before (self.model.get_iter (index), row)
  310.         self.__total_duration += int (row['duration'])
  311.  
  312.         if not self.__freezed:
  313.             e = operations.Event (self)
  314.             rows = (row,)
  315.             for l in self.listeners:
  316.                 l.on_musics_added (e, rows)
  317.         
  318.     def __len__ (self):
  319.         return len(self.model)
  320.     
  321.     def __delitem__ (self, index):
  322.         # Copy native row
  323.         row = dict(self[index])
  324.         del self.model[index]
  325.         self.__total_duration -= row['duration']
  326.         rows = (row,)
  327.         if not self.__freezed:
  328.             e = operations.Event (self)
  329.             for l in self.listeners:
  330.                 l.on_musics_removed (e, rows)
  331.     
  332.     def delete_many (self, indexes):
  333.         assert isinstance(indexes, list)
  334.         rows = []
  335.         indexes.sort()
  336.         low = indexes[0] - 1
  337.         # Remove duplicate entries
  338.         for i in indexes:
  339.             if low == i:
  340.                 indexes.remove (i)
  341.             low = i
  342.         # Now decrement the offsets
  343.         for i in range (len (indexes)):
  344.             indexes[i] -= i
  345.         
  346.         # Remove the elements directly
  347.         for i in indexes:
  348.             # Copy native row
  349.             r = dict(self.model.get(i))
  350.             rows.append(r)
  351.             self.__total_duration -= r['duration']
  352.             del self.model[i]
  353.         
  354.         # Warn the listeners
  355.         rows = tuple(rows)
  356.         e = operations.Event(self)
  357.         for l in self.listeners:
  358.             l.on_musics_removed (e, rows)
  359.         
  360.     def clear (self):
  361.         rows = []
  362.         for row in iter(self.model):
  363.             # Copy each element
  364.             rows.append(dict(row))
  365.         
  366.         self.model.clear()
  367.         self.__total_duration = 0
  368.         
  369.         rows = tuple(rows)
  370.         e = operations.Event (self)
  371.         for l in self.listeners:
  372.             l.on_musics_removed(e, rows)
  373.     
  374.  
  375. ################################################################################
  376. # Audio Mastering widget
  377. #    
  378.  
  379. class AudioMasteringMusicListener (MusicListListener):
  380.     def __init__ (self, audio_mastering):
  381.         self.__master = audio_mastering
  382.         
  383.     def on_musics_added (self, e, rows):
  384.         self.__master.update_disc_usage()
  385.     
  386.     def on_musics_removed (self, e, rows):
  387.         self.__master.update_disc_usage()
  388.  
  389. class HintsFilter (object):
  390.     __priority = 0
  391.     
  392.     def priority (self, value):
  393.         assert isinstance (value, int)
  394.         self.__priority = value
  395.         
  396.     priority = property (lambda self: self.__priority, priority, doc="Represents " \
  397.     "the parser priority, a higher value will give it precedence over " \
  398.     "filters lower filters.")
  399.     
  400.     def filter_location (self, location):
  401.         """Returns a list of dictionaries of hints of a given location.
  402.         The 'location' field is obligatory.
  403.         
  404.         For example if your filter parses a directory it should return a list
  405.         of hints of each encountered file.
  406.         """
  407.         raise NotImplementedError
  408.     
  409.     def __cmp__ (self, value):
  410.         assert isinstance (value, HintsFilter)
  411.         return self.priority - value.priority
  412.  
  413. def normalize_hints(hints):
  414.     hints["location"] = urlutil.normalize(hints["location"])
  415.  
  416. class MusicListGateway:
  417.     """This class wraps the MusicList interface in a friendlier one with a
  418.     method `add_files` easier to use then the `insert` method which expects
  419.     a hints `dict`. It also serves as a hints filter which is a list of client
  420.     objects which must provide the `filter_location` method.
  421.     """
  422.  
  423.     class Handler:
  424.         """A handler is created each time a method is created, it must
  425.         return objects with this class signature."""
  426.         
  427.         def prepare_queue (self, gateway, queue):
  428.             """Method called before the AddFile operations are added to the queue"""
  429.         
  430.         def finish_queue (self, gateway, queue):
  431.             """Method called after the AddFile operations are added to the queue"""
  432.         
  433.         def prepare_add_file (self, gateway, add_file):
  434.             """Method called before add_file object is added to queue"""
  435.     
  436.     def __init__ (self, app):
  437.         # Filters
  438.         self.__filters = []
  439.         self._app = weakref.ref(app)
  440.     
  441.     music_list = None
  442.     
  443.     def __filter_location (self, location):
  444.         for loc_filter in self.__filters:
  445.             hints = loc_filter.filter_location (location)
  446.             if hints is not None:
  447.                 return hints
  448.         return None
  449.     
  450.     def add_files (self, filenames):
  451.         to_hint = lambda filename: {"location": urlutil.normalize(filename)}
  452.         return self.add_hints(map(to_hint, filenames))
  453.  
  454.     def add_hints (self, hints_list, insert = None):
  455.         assert insert is None or isinstance (insert, IntType)
  456.  
  457.         queue = OperationsQueue()
  458.         queue.abort_on_failure = False
  459.         
  460.         handler = self.Handler ()
  461.         
  462.         handler.prepare_queue (self, queue)
  463.         
  464.         i = 0
  465.         for h in hints_list:
  466.             pls = self.__filter_location (h["location"])
  467.             
  468.             if pls is not None and len (pls) > 0:
  469.                 # normalize the returning elements
  470.                 map(normalize_hints, pls)
  471.                 # We add this to the queue so it is
  472.                 # processed before the next file on the list
  473.                 queue.append (self.add_hints(pls, insert))
  474.                 continue
  475.                 
  476.             ins = insert
  477.             if insert != None:
  478.                 ins += i
  479.             
  480.             a = AddFile (self.music_list, h, ins, self._app())
  481.             handler.prepare_add_file (self, a)
  482.             
  483.             queue.append (a)
  484.             
  485.             i += 1
  486.         
  487.         handler.finish_queue (self, queue)
  488.         return queue
  489.  
  490.     def add_hints_filter (self, location_filter):
  491.         self.__filters.append (location_filter)
  492.         # Sort filters priority
  493.         self.__filters.sort ()
  494.     
  495.     def remove_hints_filter (self, location_filter):
  496.         self.__filters.remove (location_filter)
  497.  
  498.  
  499. class AudioMastering (gtk.VBox, operations.Listenable):
  500.     SIZE_21 = 0
  501.     SIZE_74 = 1
  502.     SIZE_80 = 2
  503.     SIZE_90 = 3
  504.     
  505.     class MusicListGateway (MusicListGateway):
  506.         def __init__ (self, parent):
  507.             MusicListGateway.__init__ (self, parent._application())
  508.             self.parent = parent
  509.         
  510.         def music_list (self):
  511.             return self.parent.music_list
  512.             
  513.         music_list = property (music_list)
  514.         
  515.         def window (self):
  516.             return gtkutil.get_root_parent (self.parent)
  517.         
  518.         window = property (window)
  519.         
  520.         class Handler:
  521.             def prepare_queue (self, gateway, queue):
  522.                 queue.append (UpdateDiscUsage (gateway.parent, False))
  523.                 self.trapper = ErrorTrapper (gateway.window)
  524.             
  525.             def finish_queue (self, gateway, queue):
  526.                 queue.append (UpdateDiscUsage (gateway.parent, True))
  527.                 queue.append (self.trapper)
  528.                 del self.trapper
  529.             
  530.             def prepare_add_file (self, gateway, add_file):
  531.                 add_file.listeners.append (self.trapper)
  532.         
  533.     
  534.     disc_sizes = [21 * 60, 74 * 60, 80 * 60, 90 * 60]
  535.     
  536.     DND_TARGETS = [
  537.         ('SERPENTINE_ROW', gtk.TARGET_SAME_WIDGET, 0),
  538.         ('text/uri-list', 0, 1),
  539.         ('text/plain', 0, 2),
  540.         ('STRING', 0, 3),
  541.     ]
  542.     def __init__ (self, application):
  543.         gtk.VBox.__init__ (self)
  544.         self._application = weakref.ref(application)
  545.         
  546.         operations.Listenable.__init__ (self)
  547.         self.__disc_size = 74 * 60
  548.         self.update = True
  549.         self.source = GtkMusicList ()
  550.         self.source.listeners.append (AudioMasteringMusicListener(self))
  551.         
  552.         self.__gateway = AudioMastering.MusicListGateway (self)
  553.         
  554.         gtk.VBox.__init__ (self)
  555.         filename = application.locations.get_data_file("serpentine.glade")
  556.         g = glade.XML (filename, "audio_container")
  557.         
  558.         self.add (g.get_widget ("audio_container"))
  559.         
  560.         self.__setup_track_list (g)
  561.         self.__setup_container_misc (g)
  562.     
  563.     def __set_disc_size (self, size):
  564.         assert size in AudioMastering.disc_sizes
  565.         self.__disc_size = size
  566.         self.__size_list.set_active (AudioMastering.disc_sizes.index(size))
  567.         self.update_disc_usage()
  568.         e = operations.Event (self)
  569.         for l in self.listeners:
  570.             if hasattr (l, "on_disc_size_changed"):
  571.                 l.on_disc_size_changed (e)
  572.         
  573.     music_list_gateway = property (lambda self: self.__gateway)
  574.     music_list = property (lambda self: self.source)
  575.     disc_size = property (
  576.             lambda self: self.__disc_size,
  577.             __set_disc_size,
  578.             doc = "Represents the disc size, in seconds.")
  579.     
  580.     disc_size_widget = property (lambda self: self.__size_list)
  581.     
  582.     def __setup_container_misc (self, g):
  583.         self.__size_list = g.get_widget ("size_list")
  584.         self.__usage_label = g.get_widget ("usage_label")
  585.         
  586.         self.__usage_gauge = SerpentineUsage (self)
  587.         self.__usage_gauge.widget.show ()
  588.         self.__usage_gauge.widget.set_size_request (92, 92)
  589.         hbox = g.get_widget ("disc_details")
  590.         hbox.pack_start (self.__usage_gauge.widget, expand = False, fill = False)
  591.         
  592.         self.__capacity_exceeded = g.get_widget ("capacity_exceeded")
  593.         
  594.         self.__size_list.connect ("changed", self.__on_size_changed)
  595.         self.__size_list.set_active (AudioMastering.SIZE_74)
  596.     
  597.     def __setup_track_list (self, g):
  598.         lst = g.get_widget ("track_list")
  599.         lst.set_model (self.source.model)
  600.         # Track value is dynamicly calculated
  601.         r = gtk.CellRendererText()
  602.         col = gtk.TreeViewColumn (_("Track"), r)
  603.         col.set_cell_data_func (r, self.__generate_track)
  604.         
  605.         r = gtk.CellRendererText()
  606.         r.set_property ('editable', True)
  607.         r.connect ('edited', self.__on_title_edited)
  608.         lst.append_column (col)
  609.         col = gtk.TreeViewColumn ("Title", r, text = self.source.model.index_of("title"))
  610.         lst.append_column (col)
  611.         
  612.         r = gtk.CellRendererText()
  613.         r.set_property ('editable', True)
  614.         r.connect ('edited', self.__on_artist_edited)
  615.         col = gtk.TreeViewColumn (_("Artist"), r, text = self.source.model.index_of("artist"))
  616.         lst.append_column (col)
  617.         r = gtk.CellRendererText()
  618.         col = gtk.TreeViewColumn (_("Duration"), r, text = self.source.model.index_of("time"))
  619.         lst.append_column (col)
  620.         
  621.         # TreeView Selection
  622.         self.__selection = lst.get_selection()
  623.         self.__selection.connect ("changed", self.__selection_changed)
  624.         self.__selection.set_mode (gtk.SELECTION_MULTIPLE)
  625.         
  626.         # Listen for drag-n-drop events
  627.         lst.set_reorderable (True)
  628.         #XXX pygtk bug here
  629.         lst.enable_model_drag_source (gtk.gdk.BUTTON1_MASK,
  630.                                       AudioMastering.DND_TARGETS,
  631.                                       gtk.gdk.ACTION_DEFAULT |
  632.                                       gtk.gdk.ACTION_MOVE)
  633.  
  634.         lst.enable_model_drag_dest (AudioMastering.DND_TARGETS,
  635.                                     gtk.gdk.ACTION_DEFAULT |
  636.                                     gtk.gdk.ACTION_MOVE)
  637.         lst.connect ("drag_data_received", self.__on_dnd_drop)
  638.         lst.connect ("drag_data_get", self.__on_dnd_send)
  639.     
  640.     def __generate_track (self, col, renderer, tree_model, treeiter, user_data = None):
  641.         index = tree_model.get_path(treeiter)[0]
  642.         renderer.set_property ('text', index + 1)
  643.     
  644.     def __on_size_changed (self, *args):
  645.         self.disc_size = AudioMastering.disc_sizes[self.__size_list.get_active()]
  646.     
  647.     def __on_title_edited (self, cell, path, new_text, user_data = None):
  648.         self.source[path]["title"] = new_text
  649.     
  650.     def __on_artist_edited (self, cell, path, new_text, user_data = None):
  651.         self.source[path]["artist"] = new_text
  652.     
  653.     def __on_dnd_drop (self, treeview, context, x, y, selection, info, timestamp, user_data = None):
  654.         data = selection.data
  655.         hints_list = []
  656.         
  657.         # Insert details
  658.         insert = None
  659.         drop_info = treeview.get_dest_row_at_pos(x, y)
  660.         if drop_info:
  661.             insert, insert_before = drop_info
  662.             assert isinstance(insert, TupleType), len(insert) == 1
  663.             insert, = insert
  664.             if (insert_before != gtk.TREE_VIEW_DROP_BEFORE and
  665.                 insert_before != gtk.TREE_VIEW_DROP_INTO_OR_BEFORE):
  666.                 insert += 1
  667.                 if insert == len (self.source):
  668.                     insert = None
  669.             del insert_before
  670.                 
  671.         del drop_info
  672.         
  673.         if selection.type == 'application/x-rhythmbox-source':
  674.             #TODO: handle rhythmbox playlists
  675.             return
  676.         elif selection.type == 'SERPENTINE_ROW':
  677.             # Private row
  678.             store, path_list = self.__selection.get_selected_rows ()
  679.             if not path_list or len (path_list) != 1:
  680.                 return
  681.             path, = path_list
  682.             # Copy the row
  683.             row = dict(self.source[path])
  684.             # Remove old row
  685.             del self.source[path]
  686.             
  687.             # When we insert in the last position it's the same thing as appending
  688.             if len (self.source) == insert:
  689.                 insert = None
  690.  
  691.             # Append this row
  692.             if insert is not None:
  693.                 self.source.insert (insert, row)
  694.             else:
  695.                 self.source.append (row)
  696.             return
  697.             
  698.         for line in data.split("\n"):
  699.             line = line.strip()
  700.             if len (line) < 1 or line == "\x00":
  701.                 continue
  702.                 
  703.             assert "\x00" not in line, "Malformed DnD string: %s" % line
  704.             hint = {'location': line}
  705.             hints_list.append (hint)
  706.         self.music_list_gateway.add_hints (hints_list, insert).start ()
  707.     
  708.     def __on_dnd_send (self, widget, context, selection, target_type, timestamp):
  709.         store, path_list = self.__selection.get_selected_rows ()
  710.         assert path_list and len(path_list) == 1
  711.         path, = path_list # unpack the only element
  712.         selection.set (selection.target, 8, self.source[path]['location'])
  713.     
  714.     def __hig_duration (self, duration):
  715.         hig_duration = ""
  716.         minutes = duration / 60
  717.         if minutes:
  718.             # To translators: I know this is ugly for you
  719.             hig_duration = ("%s %s") %(minutes, minutes == 1 and _("minute") or _("minutes"))
  720.         seconds = duration % 60
  721.         if seconds:
  722.             # To translators: I know this is ugly for you
  723.             hig_secs = ("%s %s") %(seconds, seconds == 1 and _("second") or _("seconds"))
  724.             if len (hig_duration):
  725.                 hig_duration += _(" and ")
  726.                 
  727.             hig_duration += hig_secs
  728.         return hig_duration
  729.     # TODO: implement get_media_duration
  730.  
  731.     def get_preferences(self):
  732.         return self._application().preferences
  733.         
  734.     def get_media_duration(self):
  735.         total = self.source.total_duration
  736.         num_tracks = len(self.source)
  737.         
  738.         if num_tracks > 0 and self.get_preferences().useGap:
  739.             total += (num_tracks - 1) * 2
  740.         
  741.         return total 
  742.     
  743.     def update_disc_usage (self):
  744.         if not self.update:
  745.             return
  746.         if self.source.total_duration > self.disc_size:
  747.             self.__capacity_exceeded.show ()
  748.             
  749.         else:
  750.             self.__capacity_exceeded.hide ()
  751.  
  752.         # Flush events so progressbar redrawing gets done
  753.         while gtk.events_pending():
  754.             gtk.main_iteration(True)
  755.         
  756.         if self.source.total_duration > 0:
  757.             duration = self.__disc_size - self.source.total_duration
  758.             if duration > 0:
  759.                 dur = _("%s remaining")  % self.__hig_duration (duration)
  760.             else:
  761.                 dur = _("%s overlaping") % self.__hig_duration (abs (duration))
  762.         else:
  763.             dur = _("Empty")
  764.         
  765.         self.__usage_label.set_text (dur)
  766.             
  767.         e = operations.Event(self)
  768.         for l in self.listeners:
  769.             l.on_contents_changed (e)
  770.     
  771.     def __selection_changed (self, treeselection):
  772.         e = operations.Event (self)
  773.         for l in self.listeners:
  774.             l.on_selection_changed (e)
  775.  
  776.     
  777.     def get_selected (self):
  778.         """Returns the selected indexes"""
  779.         store, path_list = self.__selection.get_selected_rows ()
  780.         
  781.         if path_list is None:
  782.             return []
  783.             
  784.         indexes = []
  785.         for p in path_list:
  786.             assert len(p) == 1
  787.             indexes.append(*p)
  788.         return indexes
  789.     
  790.     def remove_selected (self):
  791.         self.source.delete_many (self.get_selected ())
  792.             
  793.     def count_selected (self):
  794.         return self.__selection.count_selected_rows()
  795.  
  796.     
  797. if __name__ == '__main__':
  798.     win = gtk.Window()
  799.     win.connect ("delete-event", gtk.main_quit)
  800.     w = AudioMastering ()
  801.     w.show()
  802.     win.add (w)
  803.     win.show()
  804.     
  805.  
  806.     w.add_file (sys.argv[1])
  807.     w.source.clear()
  808.     gtk.main()
  809.