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 / mainwindow.py < prev    next >
Encoding:
Python Source  |  2006-08-23  |  19.9 KB  |  564 lines

  1. # Copyright (C) 2005 Tiago Cogumbreiro <cogumbreiro@users.sf.net>
  2. #
  3. # This program is free software; you can redistribute it and/or modify
  4. # it under the terms of the GNU General Public License as published by
  5. # the Free Software Foundation; either version 2 of the License, or
  6. # (at your option) any later version.
  7. #
  8. # This program 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
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with this program; if not, write to the Free Software
  15. # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  16. #
  17. # Authors: Tiago Cogumbreiro <cogumbreiro@users.sf.net>
  18.  
  19. import gtk
  20. import os
  21. import os.path
  22. import gobject
  23.  
  24. # Local imports
  25. import operations
  26. import gaw
  27. import gtkutil
  28.  
  29. from components import Component
  30. from operations import MapProxy, OperationListener
  31. from mastering import AudioMastering
  32.  
  33. from serpentine.common import SerpentineNotSupportedError, validate_music_list
  34. from serpentine.common import SerpentineCacheError
  35. from gettext import gettext as _
  36.  
  37. class GladeComponent (Component):
  38.  
  39.     def _setup_glade (self, g):
  40.         """This method is called when the SerpentineWindow object is created."""
  41.  
  42. class FileDialogComponent (GladeComponent):
  43.     def __init__ (self, parent):
  44.         super (FileDialogComponent, self).__init__ (parent)
  45.         
  46.         # Open playlist file dialog
  47.         self.file_dlg = gtk.FileChooserDialog (
  48.             parent = self.parent,
  49.             buttons = (
  50.                 gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
  51.                 gtk.STOCK_OPEN, gtk.RESPONSE_OK
  52.             )
  53.         )
  54.         self.file_dlg.set_title ("")
  55.         self.file_dlg.set_transient_for (self.parent)
  56.         self.file_dlg.set_current_folder (os.path.expanduser("~"))
  57.         self.__file_filters = None
  58.     
  59.     def run_dialog (self, *args):
  60.         # Triggered by add button
  61.         # update file filters
  62.         self.__sync_file_filters ()
  63.         
  64.         if self.file_dlg.run () == gtk.RESPONSE_OK:
  65.             self._on_response_ok ()
  66.         
  67.         self._on_response_fail ()
  68.         
  69.         self.file_dlg.unselect_all()
  70.         self.file_dlg.hide()
  71.     
  72.     def _on_response_ok (self):
  73.         pass
  74.     
  75.     def _on_response_fail (self):
  76.         pass
  77.     
  78.     def _get_file_filter (self):
  79.         raise NotImplementedError
  80.     
  81.     def __sync_file_filters (self):
  82.         file_filters = self._get_file_filters ()
  83.                 
  84.         if file_filters == self.__file_filters:
  85.             return
  86.         
  87.         # Remove old filters
  88.         for filter in self.file_dlg.list_filters ():
  89.             self.file_dlg.remove_filter (filter)
  90.             
  91.         self.__file_filters = file_filters
  92.  
  93.         # Add new filters
  94.         for filter in file_filters:
  95.             self.file_dlg.add_filter (filter)
  96.  
  97. class AddFileComponent (FileDialogComponent):
  98.     def _setup_glade (self, g):
  99.         g.get_widget ("add").connect ("clicked", self.run_dialog)
  100.         g.get_widget ("add_mni").connect ("activate", self.run_dialog)
  101.         
  102.         self.file_dlg.set_select_multiple (True)
  103.         
  104.     def _on_response_ok (self):
  105.         files = self.file_dlg.get_uris()
  106.         self.parent.music_list_widget.music_list_gateway.add_files (files).start ()
  107.  
  108.     _get_file_filters = lambda self: self.parent.application.music_file_filters
  109.  
  110.     
  111. class PlaylistComponent (FileDialogComponent):
  112.     def _setup_glade (self, g):
  113.         g.get_widget ("open_playlist_mni").connect ("activate", self.run_dialog)
  114.     
  115.     
  116.     _get_file_filters = lambda self: self.parent.application.playlist_file_filters
  117.  
  118.     def _on_response_ok (self):
  119.         playlist = self.file_dlg.get_uri()
  120.         self.parent.music_list_widget.music_list_gateway.add_files ([playlist]).start ()
  121.         self.parent.clear_files ()
  122.  
  123. class SavePlaylistComponent (GladeComponent):
  124.     def _setup_glade (self, g):
  125.         g.get_widget ("save_playlist_mni").connect ("activate", self.run_dialog)
  126.         self.file_dlg = gtk.FileChooserDialog (
  127.             action = gtk.FILE_CHOOSER_ACTION_SAVE,
  128.             buttons = (
  129.                 gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
  130.                 gtk.STOCK_SAVE, gtk.RESPONSE_OK
  131.             )
  132.         )
  133.         self.file_dlg.set_title ("")
  134.         self.file_dlg.set_transient_for (self.parent)
  135.         self.file_dlg.set_current_folder (os.path.expanduser("~"))
  136.         hbox = gtk.HBox (spacing = 6)
  137.         hbox.show ()
  138.         
  139.         lbl = gtk.Label(_("Save playlist in format:"))
  140.         lbl.show ()
  141.         hbox.pack_start (lbl, False, False)
  142.         
  143.         store = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
  144.         cmb = gtk.ComboBox (store)
  145.         cmb.show ()
  146.         cell = gtk.CellRendererText()
  147.         cmb.pack_start(cell, True)
  148.         cmb.add_attribute(cell, 'text', 0)
  149.         self.combo = cmb
  150.         hbox.pack_start (cmb, False, False)
  151.  
  152.         self.store = store
  153.         store.append ((_("Detect by extension"), ""))
  154.         cmb.set_active (0)
  155.         
  156.         self.file_dlg.set_extra_widget (hbox)
  157.         app = self.parent.application
  158.         app.savePlaylist.listeners.append (self)
  159.         self.__sync = True
  160.     
  161.     def on_registred (self, factory, extension, description):
  162.         self.__sync = True
  163.         self.store.append (("%s (%s)" % (description, extension), extension))
  164.     
  165.     def on_finished (self, evt):
  166.         win = self.parent
  167.             
  168.         if evt.id != operations.SUCCESSFUL:
  169.             gtkutil.dialog_error (
  170.                 _("Playlist Not Saved"),
  171.                 _("There was an error while saving the playlist."),
  172.                 parent = win
  173.             )
  174.         
  175.     def __sync_file_filters (self):
  176.         if not self.__sync:
  177.             return
  178.         
  179.         app = self.parent.application
  180.         
  181.         # Remove old filters
  182.         for f in self.file_dlg.list_filters ():
  183.             self.file_dlg.remove_filter (f)
  184.         
  185.         # Now fill the real filters
  186.         for f in app.savePlaylist.file_filters:
  187.             self.file_dlg.add_filter (f)
  188.         
  189.         # Sync is complete
  190.         self.__sync = False
  191.     
  192.     def run_dialog (self, *args):
  193.         app = self.parent.application
  194.         win = self.parent
  195.         
  196.         self.__sync_file_filters ()
  197.         
  198.         while self.file_dlg.run () == gtk.RESPONSE_OK:
  199.             filename = self.file_dlg.get_filename ()
  200.             basename = os.path.basename (filename)
  201.             index = self.combo.get_active ()
  202.             if index == 0:
  203.                 extension = None
  204.             else:
  205.                 extension = self.store[index][1]
  206.                 
  207.             if os.path.exists (filename) and gtkutil.dialog_ok_cancel (
  208.                 _("Replace existing file"),
  209.                 _("A file named <i>%s</i> already exists. "
  210.                 "Do you want to replace it with the one "
  211.                 "you are saving?") % basename,
  212.                 parent = win
  213.             ) != gtk.RESPONSE_OK:
  214.                 
  215.                 self.file_dlg.unselect_all()
  216.                 self.file_dlg.hide()
  217.                 return
  218.             
  219.             try:
  220.                 oper = app.savePlaylist.save (filename, extension)
  221.                 oper.listeners.append (self)
  222.                 oper.start ()
  223.                 break
  224.  
  225.             except SerpentineNotSupportedError:
  226.                 # In this case the user supplied a wrong extension
  227.                 # let's ask for him to choose one
  228.                 
  229.                 if extension is None:
  230.                     # convert to strings
  231.                     items = map(lambda row: row[0], self.store)
  232.                     # First row is useless
  233.                     del items[0]
  234.                     
  235.                     indexes, response = gtkutil.choice_dialog(
  236.                         _("Select one playlist format"),
  237.                         _("Serpentine will open any of these formats so the "
  238.                           "one you choose only matters if you are going to "
  239.                           "open with other applications."),
  240.                         one_text_item = _("Do you want to save as the %s "
  241.                                           "format?"),
  242.                         min_select = 1,
  243.                         max_select = 1,
  244.                         parent = win,
  245.                         items = items,
  246.                         ok_button = gtk.STOCK_SAVE,
  247.                     )
  248.                     if len(indexes) != 0:
  249.                         index, = indexes
  250.                         # Since we deleted the first row from the items then
  251.                         # the index have an offset of 1
  252.                         index += 1
  253.                         row = self.store[index]
  254.                         extension = row[1]
  255.                         
  256.                         # Save the file
  257.                         oper = app.savePlaylist.save (filename, extension)
  258.                         oper.listeners.append (self)
  259.                         oper.start ()
  260.                         
  261.                         # Select the option in the list store
  262.                         self.combo.set_active(index)
  263.                         break
  264.                         
  265.                         
  266.                 else:
  267.                     gtkutil.dialog_error (
  268.                         _("Unsupported Format"),
  269.                         _("The playlist format you used (by the file extension) is "
  270.                         "currently not supported."),
  271.                         parent = win
  272.                     )
  273.                     
  274.             
  275.         self.file_dlg.unselect_all()
  276.         self.file_dlg.hide()
  277.  
  278. class ToolbarComponent (GladeComponent):
  279.     Style = {
  280.         "both": gtk.TOOLBAR_BOTH,
  281.         "both-horiz": gtk.TOOLBAR_BOTH_HORIZ,
  282.         "icons": gtk.TOOLBAR_ICONS,
  283.         "text": gtk.TOOLBAR_TEXT
  284.     }
  285.     
  286.     def _setup_glade (self, g):
  287.         # Toolbar style
  288.         self.__style = gaw.GConfValue (
  289.             key = "/desktop/gnome/interface/toolbar_style",
  290.             data_spec = gaw.Spec.STRING,
  291.         )
  292.         self.__style.set_callback (self.__on_style_change)
  293.         
  294.         # Detachable toolbar
  295.         self.__detachable = gaw.GConfValue (
  296.             key = "/desktop/gnome/interface/toolbar_detachable",
  297.             data_spec = gaw.Spec.BOOL
  298.         )
  299.         self.__detachable.set_callback (self.__on_detachable_change)
  300.         
  301.         self.toolbar = g.get_widget ("main_toolbar")
  302.         self.handle = g.get_widget ("main_handle")
  303.         self.wrapper = g.get_widget ("main_toolbar_wrapper")
  304.         
  305.         # Show hide toolbar
  306.         view_toolbar = g.get_widget ("view_toolbar_mni")
  307.         self.__visible = gaw.data_toggle_button (
  308.             toggle = view_toolbar,
  309.             key = "/apps/serpentine/view_toolbar",
  310.             default = True
  311.         )
  312.         view_toolbar.connect ("toggled", self.__on_toolbar_visible)
  313.         
  314.         # Update to current state
  315.         self.__on_style_change ()
  316.         self.__on_detachable_change ()
  317.         self.__on_toolbar_visible ()
  318.     
  319.     def __on_toolbar_visible (self, *args):
  320.         if self.__visible.data:
  321.             self.wrapper.show ()
  322.         else:
  323.             self.wrapper.hide ()
  324.         
  325.     def __on_detachable_change (self, *args):
  326.         widget = self.wrapper.get_children()[0]
  327.         
  328.         if self.detachable:
  329.             if widget == self.handle:
  330.                 return
  331.             
  332.             self.wrapper.remove (widget)
  333.             self.wrapper.add (self.handle)
  334.             self.handle.add (self.toolbar)
  335.         else:
  336.             if widget == self.toolbar:
  337.                 return
  338.             self.handle.remove (self.toolbar)
  339.             self.wrapper.remove (widget)
  340.             self.wrapper.add (self.toolbar)
  341.             
  342.     def __on_style_change (self, *args):
  343.         self.toolbar.set_style (self.style)
  344.     
  345.     def detachable (self):
  346.         try:
  347.             detachable = self.__detachable.data
  348.         except:
  349.             detachable = False
  350.         if not isinstance (detachable, bool):
  351.             detachable = False
  352.         
  353.         return detachable
  354.     detachable = property (detachable)
  355.     
  356.     def style (self):
  357.         try:
  358.             style = self.__style.data
  359.         except:
  360.             style = "both"
  361.         
  362.         if style in ToolbarComponent.Style:
  363.             return ToolbarComponent.Style[style]
  364.         else:
  365.             return ToolbarComponent.Style["both"]
  366.             
  367.     style = property (style)
  368.  
  369. class SerpentineWindow (gtk.Window, OperationListener, operations.Operation, Component):
  370.     # TODO: finish up implementing an Operation
  371.     components = (
  372.         AddFileComponent,
  373.         PlaylistComponent,
  374.         SavePlaylistComponent,
  375.         ToolbarComponent
  376.     )
  377.     
  378.     def __init__ (self, application):
  379.         gtk.Window.__init__ (self, gtk.WINDOW_TOPLEVEL)
  380.         operations.Operation.__init__ (self)
  381.         Component.__init__ (self, application)
  382.             
  383.         self.__application = application
  384.         self.__masterer = AudioMastering (application)
  385.         # Variable related to this being an Operation
  386.         self.__running = False
  387.         self.connect ("show", self.__on_show)
  388.         # We listen for GtkMusicList events
  389.         self.music_list_widget.listeners.append (self)
  390.         
  391.         glade_file = application.locations.get_data_file("serpentine.glade")
  392.         g = gtk.glade.XML (glade_file, "main_window_container")
  393.         
  394.         # Send glade to setup subcomponents
  395.         for c in self._components:
  396.             if hasattr (c, "_setup_glade"):
  397.                 c._setup_glade (g)
  398.  
  399.         self.add (g.get_widget ("main_window_container"))
  400.         self.set_title ("Serpentine")
  401.         self.set_default_size (450, 350)
  402.         self.set_icon_name ("gnome-dev-cdrom-audio")
  403.         
  404.         
  405.         # record button
  406.         # setup record button
  407.         self.__write_to_disc = MapProxy (dict(
  408.             button = g.get_widget ("write_to_disc"),
  409.             menu   = g.get_widget ("write_to_disc_mni")
  410.         ))
  411.         
  412.         self.__write_to_disc.set_sensitive (False)
  413.         self.__write_to_disc["button"].connect ("clicked", self.__on_write_files)
  414.         self.__write_to_disc["menu"].connect ("activate", self.__on_write_files)
  415.         
  416.         # masterer widget
  417.         box = self.get_child()
  418.         self.music_list_widget.show()
  419.         box.add (self.music_list_widget)
  420.         
  421.         # preferences
  422.         g.get_widget ("preferences_mni").connect ("activate", self.__on_preferences)
  423.         
  424.         # setup remove buttons
  425.         self.remove = MapProxy ({"menu": g.get_widget ("remove_mni"),
  426.                                  "button": g.get_widget ("remove")})
  427.  
  428.         self.remove["menu"].connect ("activate", self.__on_remove_file)
  429.         self.remove["button"].connect ("clicked", self.__on_remove_file)
  430.         self.remove.set_sensitive (False)
  431.         
  432.         # setup clear buttons
  433.         self.clear = MapProxy ({"menu": g.get_widget ("clear_mni"),
  434.                                 "button": g.get_widget ("clear")})
  435.         self.clear["button"].connect ("clicked", self.clear_files)
  436.         self.clear["menu"].connect ("activate", self.clear_files)
  437.         self.clear.set_sensitive (False)
  438.         
  439.         # setup quit menu item
  440.         g.get_widget ("quit_mni").connect ("activate", self.stop)
  441.         self.connect("delete-event", self.stop)
  442.         
  443.         # About dialog
  444.         g.get_widget ("about_mni").connect ("activate", self.__on_about)
  445.         
  446.         # update buttons
  447.         self.on_contents_changed()
  448.         
  449.         if self.__application.preferences.drive is None:
  450.             gtkutil.dialog_warn (
  451.                 _("No recording drive found"),
  452.                 _("No recording drive found on your system, therefore some of "
  453.                   "Serpentine's functionalities will be disabled."),
  454.                 parent = self
  455.             )
  456.             g.get_widget ("preferences_mni").set_sensitive (False)
  457.             self.__write_to_disc.set_sensitive (False)
  458.     
  459.         # Load internal XSPF playlist
  460.         self.__load_playlist()
  461.         
  462.     music_list_widget = property (lambda self: self.__masterer)
  463.     
  464.     music_list = property (lambda self: self.__masterer.music_list)
  465.     
  466.     can_start = property (lambda self: True)
  467.     # TODO: handle the can_stop property better
  468.     can_stop = property (lambda self: True)
  469.     
  470.     masterer = property (lambda self: self.__masterer)
  471.     
  472.     application = property (lambda self: self.__application)
  473.     
  474.     def __on_show (self, *args):
  475.         self.__running = True
  476.     
  477.     def __load_playlist (self):
  478.         #TODO: move it to SerpentineApplication ?
  479.         """Private method for loading the internal playlist."""
  480.         try:
  481.             self.__application.preferences.loadPlaylist (self.music_list_widget.source)
  482.         except:
  483.             import traceback
  484.             traceback.print_exc()
  485.             
  486.     def on_selection_changed (self, *args):
  487.         self.remove.set_sensitive (self.music_list_widget.count_selected() > 0)
  488.         
  489.     def on_contents_changed (self, *args):
  490.         is_sensitive = len(self.music_list_widget.source) > 0
  491.         self.clear.set_sensitive (is_sensitive)
  492.         # Only set it sentitive if the drive is available and is not recording
  493.         if self.__application.preferences.drive is not None:
  494.             self.__write_to_disc.set_sensitive (is_sensitive)
  495.  
  496.     def __on_remove_file (self, *args):
  497.         self.music_list_widget.remove_selected()
  498.         
  499.     def clear_files (self, *args):
  500.         self.music_list_widget.source.clear()
  501.     
  502.     def __on_preferences (self, *args):
  503.         # Triggered by preferences menu item
  504.         self.__application.preferences.dialog.run ()
  505.         self.__application.preferences.dialog.hide ()
  506.     
  507.     def __on_about (self, widget, *args):
  508.         # Triggered by the about menu item
  509.         a = gtk.AboutDialog ()
  510.         a.set_name ("Serpentine")
  511.         a.set_version (self.__application.preferences.version)
  512.         a.set_website ("http://s1x.homelinux.net/projects/serpentine")
  513.         a.set_copyright ("2004-2006 Tiago Cogumbreiro")
  514.         a.set_transient_for (self)
  515.         a.run ()
  516.         a.hide()
  517.     
  518.     def __on_write_files (self, *args):
  519.         # TODO: move this to SerpentineApplication.write_files ?
  520.         try:
  521.             # Try to validate music list
  522.             validate_music_list (self.music_list_widget.source, self.application.preferences)
  523.         except SerpentineCacheError, err:
  524.             show_prefs = False
  525.             
  526.             if err.error_id == SerpentineCacheError.INVALID:
  527.                 title = _("Cache directory location unavailable")
  528.                 show_prefs = True
  529.                 
  530.             elif err.error_id == SerpentineCacheError.NO_SPACE:
  531.                 title = _("Not enough space on cache directory")
  532.             
  533.             gtkutil.dialog_warn (title, err.error_message, parent = self)
  534.             return
  535.  
  536.         # TODO: move this to recording module?
  537.         if self.music_list_widget.source.total_duration > self.music_list_widget.disc_size:
  538.             title = _("Do you want to overburn your disc?")
  539.             msg = _("You are about to record a media disc in overburn mode. "
  540.                     "This may not work on all drives and shouldn't give you "
  541.                     "more then a couple of minutes.")
  542.             btn = _("Write to Disc (Overburning)")
  543.             self.__application.preferences.overburn = True
  544.         else:
  545.             title = _("Do you want to record your music?")
  546.             msg = _("You are about to record a media disc. "
  547.                     "Canceling a writing operation will make "
  548.                     "your disc unusable.")
  549.                     
  550.             btn = _("Write to Disc")
  551.             self.__application.preferences.overburn = False
  552.         
  553.         if gtkutil.dialog_ok_cancel (title, msg, parent = self, ok_button = btn) != gtk.RESPONSE_OK:
  554.             return
  555.         
  556.         self.application.write_files ().start ()
  557.     
  558.     # Start is the same as showing a window, we do it every time
  559.     start = gtk.Window.show
  560.  
  561.     def stop (self, *args):
  562.         self._send_finished_event (operations.SUCCESSFUL)
  563.         self.hide ()
  564.