Attached updated patch against trunk and necessary resources (drop into ThickClient's Resources directory). On Sun, 24 Feb 2008 04:58:14 +0000, Alex Hixon <hixon alexander mediati org> wrote: > Hi all, > > Posting this patch here for some review/discussion. > > There are really two major changes that this patch makes, the first being > changing the API of ICanonicalPlaybackController and > IBasicPlaybackController. Basically, all I'm doing here is changing Next > () > to Next (bool restartAtEnd). If restartAtEnd is true, the implementing > class shouldn't stop playback merely because there are no more tracks to > play linearly, it should queue up the first track if there are none left. > Anyway, simple stuff. > > The other issue that'll probably get a bit more discussion is the way we > handle changing the Sensitive state on the shuffle/repeat toggle button > widgets. At the moment, it should be possible to do: > (action_service.UIManager.GetWidget ("/FooterToolbar/ShuffleToggleButton") > as Widget).Sensitive = false;, but this doesn't quite work for some > reason. > Instead, we end up getting a GtkMenuSeperator widget (at least, that's > what > I think it was called - this is off the top of my head). If we loop > through > the child elements of FooterToolbar, we end up with something like this: > > ShuffleToggleButton | Separator | RepeatToggleButton | Separator | > StatusBar | Separator | TrackMetadataAction > > So, from the looks of things, it's getting the widget /adjacent/ to the > widget we really want. So, at the moment, to work around this, we loop > through the children of FooterToolbar and find items that are of type > MultiStateToggleButton, and set their Sensitive value to false. Not sure > if > this is a problem with the way I'm approaching the issue, or a bug in > Gtk#. > > Anyway, the attached patch works as advertised - it adds support for the > shuffle and repeat footer togglebuttons, and implements the necessary > logic > to drive them all. It also disables shuffle and repeat while we're looking > at the Last.fm radio source. > > Comments, suggestions on the implementation? > > Cheers, > Alex Hixon -- Cheers, Alex Hixon
Index: Extensions/Banshee.NotificationArea/Banshee.NotificationArea/TrackInfoPopup.cs =================================================================== --- Extensions/Banshee.NotificationArea/Banshee.NotificationArea/TrackInfoPopup.cs (revision 3319) +++ Extensions/Banshee.NotificationArea/Banshee.NotificationArea/TrackInfoPopup.cs (working copy) @@ -55,7 +55,7 @@ // Position label and linear progress bar HBox position_box = new HBox (); - position_box.Spacing = 10; + //position_box.Spacing = 10; position_label = new Label (); position_label.Xalign = 0.0f; Index: Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/StationSource.cs =================================================================== --- Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/StationSource.cs (revision 3319) +++ Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/StationSource.cs (working copy) @@ -47,6 +47,9 @@ using Banshee.MediaEngine; using Banshee.Collection; using Banshee.ServiceStack; + +using Banshee.Gui; +using Banshee.Gui.Widgets; namespace Banshee.Lastfm.Radio { @@ -174,16 +177,50 @@ OnUpdated (); } + private void SetStatusIconSensitive (bool val) { + Toolbar footer = (action_service.UIManager.GetWidget ("/FooterToolbar") as Toolbar); + + foreach (Widget child in footer.Children) { + try { + GenericToolItem<Gtk.Widget> w = (GenericToolItem<Gtk.Widget>) child; + + if (w.Widget is MultiStateToggleButton) { + w.Widget.Sensitive = val; + } + } catch { + } + } + } + private bool shuffle; + private int repeat; + private InterfaceActionService action_service; public override void Activate () { + if (action_service == null) { + action_service = ServiceManager.Get<InterfaceActionService> ("InterfaceActionService"); + } + base.Activate (); - //shuffle = (action_service.GlobalActions ["ShuffleAction"] as ToggleAction).Active; - //(action_service.GlobalActions ["ShuffleAction"] as ToggleAction).Active = false; - //Globals.ActionManager["ShuffleAction"].Sensitive = false; + SetStatusIconSensitive (false); + shuffle = (action_service.PlaybackActions ["ShuffleAction"] as ToggleAction).Active; + (action_service.PlaybackActions ["ShuffleAction"] as ToggleAction).Active = false; + action_service.PlaybackActions ["ShuffleAction"].Sensitive = false; + + if ((action_service.PlaybackActions ["RepeatNoneAction"] as ToggleAction).Active) { + repeat = 0; + } else if ((action_service.PlaybackActions ["RepeatAllAction"] as ToggleAction).Active) { + repeat = 1; + } else { + repeat = 2; + } + + (action_service.PlaybackActions ["RepeatNoneAction"] as ToggleAction).Active = true; + + action_service.PlaybackActions ["RepeatNoneAction"].Sensitive = false; + action_service.PlaybackActions ["RepeatAllAction"].Sensitive = false; + action_service.PlaybackActions ["RepeatSingleAction"].Sensitive = false; - //action_service.GlobalActions ["PreviousAction"].Sensitive = false; - // We lazy load the Last.fm connection, so if we're not already connected, do it if (lastfm.Connection.State == ConnectionState.Connected) TuneAndLoad (); @@ -206,10 +243,22 @@ public override void Deactivate () { - //(Globals.ActionManager["ShuffleAction"] as ToggleAction).Active = shuffle; - //Globals.ActionManager["ShuffleAction"].Sensitive = true; + (action_service.PlaybackActions ["ShuffleAction"] as ToggleAction).Active = shuffle; + action_service.PlaybackActions ["ShuffleAction"].Sensitive = true; - //Globals.ActionManager["PreviousAction"].Sensitive = true; + action_service.PlaybackActions ["PreviousAction"].Sensitive = true; + + SetStatusIconSensitive (true); + + action_service.PlaybackActions ["RepeatNoneAction"].Sensitive = true; + action_service.PlaybackActions ["RepeatAllAction"].Sensitive = true; + action_service.PlaybackActions ["RepeatSingleAction"].Sensitive = true; + + if (repeat == 1) { + (action_service.PlaybackActions ["RepeatAllAction"] as ToggleAction).Active = true; + } else if (repeat == 2) { + (action_service.PlaybackActions ["RepeatSingleAction"] as ToggleAction).Active = true; + } } // Last.fm requires you to 'tune' to a station before requesting a track list/playing it Index: Extensions/Banshee.PlayQueue/Banshee.PlayQueue/PlayQueueSource.cs =================================================================== --- Extensions/Banshee.PlayQueue/Banshee.PlayQueue/PlayQueueSource.cs (revision 3319) +++ Extensions/Banshee.PlayQueue/Banshee.PlayQueue/PlayQueueSource.cs (working copy) @@ -190,10 +190,10 @@ void IBasicPlaybackController.First () { - ((IBasicPlaybackController)this).Next (); + ((IBasicPlaybackController)this).Next (false); } - void IBasicPlaybackController.Next () + void IBasicPlaybackController.Next (bool restartAtEnd) { RemoveFirstTrack (); Index: Clients/Nereid/Nereid/PlayerInterface.cs =================================================================== --- Clients/Nereid/Nereid/PlayerInterface.cs (revision 3319) +++ Clients/Nereid/Nereid/PlayerInterface.cs (working copy) @@ -57,8 +57,8 @@ // Major Layout Components private VBox primary_vbox; private Toolbar header_toolbar; + private Toolbar footer_toolbar; private HPaned views_pane; - private HBox footer_box; private ViewContainer view_container; // Major Interaction Components @@ -196,28 +196,33 @@ private void BuildFooter () { - footer_box = new HBox (); - footer_box.Spacing = 2; + footer_toolbar = (Toolbar)ActionService.UIManager.GetWidget ("/FooterToolbar"); + footer_toolbar.ShowArrow = false; + footer_toolbar.ToolbarStyle = ToolbarStyle.BothHoriz; + footer_toolbar.IconSize = IconSize.Menu; status_label = new Label (); status_label.ModifyFg (StateType.Normal, Hyena.Gui.GtkUtilities.ColorBlend ( status_label.Style.Foreground (StateType.Normal), status_label.Style.Background (StateType.Normal))); - ActionButton song_properties_button = new ActionButton + /*ActionButton song_properties_button = new ActionButton (ActionService.TrackActions["TrackPropertiesAction"]); song_properties_button.IconSize = IconSize.Menu; song_properties_button.Padding = 0; - song_properties_button.LabelVisible = false; + song_properties_button.LabelVisible = false;*/ - //footer_box.PackStart (shuffle_toggle_button, false, false, 0); - //footer_box.PackStart (repeat_toggle_button, false, false, 0); - footer_box.PackStart (status_label, true, true, 0); - footer_box.PackStart (song_properties_button, false, false, 0); + ActionService.PopulateToolbarPlaceholder (footer_toolbar, "/FooterToolbar/Statusbar", status_label, true); + + MultiStateToggleButton shuffle_toggle_button = new ShuffleToggleButton (ActionService); + ActionService.PopulateToolbarPlaceholder (footer_toolbar, "/FooterToolbar/ShuffleToggleAction", shuffle_toggle_button); + + MultiStateToggleButton repeat_toggle_button = new RepeatToggleButton (ActionService); + ActionService.PopulateToolbarPlaceholder (footer_toolbar, "/FooterToolbar/RepeatToggleAction", repeat_toggle_button); Alignment align = new Alignment (0.5f, 0.5f, 1.0f, 1.0f); - align.TopPadding = 2; + align.TopPadding = 0; align.BottomPadding = 0; - align.Add (footer_box); + align.Add (footer_toolbar); align.ShowAll (); primary_vbox.PackStart (align, false, true, 0); @@ -264,6 +269,7 @@ }; header_toolbar.ExposeEvent += OnHeaderToolbarExposeEvent; + footer_toolbar.ExposeEvent += OnFooterToolbarExposeEvent; } #endregion @@ -364,6 +370,19 @@ } } + private void OnFooterToolbarExposeEvent (object o, ExposeEventArgs args) + { + // This forces the toolbar to look like it's just a regular plain container + // since the stock toolbar look makes Banshee look ugly. + footer_toolbar.GdkWindow.DrawRectangle (Style.BackgroundGC (footer_toolbar.State), + true, footer_toolbar.Allocation); + + // Manually expose all the toolbar's children + foreach (Widget child in footer_toolbar.Children) { + footer_toolbar.PropagateExpose (child, args.Event); + } + } + #endregion #region Implement Interfaces Index: Core/Banshee.ThickClient/Banshee.Gui/ToggleStates.cs =================================================================== --- Core/Banshee.ThickClient/Banshee.Gui/ToggleStates.cs (revision 0) +++ Core/Banshee.ThickClient/Banshee.Gui/ToggleStates.cs (revision 0) @@ -0,0 +1,83 @@ +// +// ToggleStates.cs +// +// Author: +// Aaron Bockover <abockover novell com> +// +// Copyright (C) 2005-2008 Novell, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using Mono.Unix; +using Banshee.Widgets; + +namespace Banshee.Gui +{ + public class RepeatNoneToggleState : ToggleState + { + public RepeatNoneToggleState() + { + Icon = IconThemeUtils.LoadIcon (16, "view-sort-descending"); + Label = Catalog.GetString("Repeat None"); + } + } + + public class RepeatSingleToggleState : ToggleState + { + public RepeatSingleToggleState() + { + Icon = Gdk.Pixbuf.LoadFromResource("media-repeat-single.png"); + Label = Catalog.GetString("Repeat Single"); + } + } + + public class RepeatAllToggleState : ToggleState + { + public RepeatAllToggleState() + { + Icon = IconThemeUtils.LoadIcon (16, "media-playlist-repeat"); + Label = Catalog.GetString("Repeat All"); + } + } + + public class ShuffleEnabledToggleState : ToggleState + { + public ShuffleEnabledToggleState() + { + Icon = IconThemeUtils.LoadIcon (16, "media-playlist-shuffle"); + Label = Catalog.GetString("Shuffle"); + MatchActive = true; + MatchValue = true; + } + } + + public class ShuffleDisabledToggleState : ToggleState + { + public ShuffleDisabledToggleState() + { + Icon = Gdk.Pixbuf.LoadFromResource("media-playlist-continuous.png"); + Label = Catalog.GetString("Continuous"); + MatchActive = true; + MatchValue = false; + } + } +} Index: Core/Banshee.ThickClient/Banshee.Gui/PlaybackActions.cs =================================================================== --- Core/Banshee.ThickClient/Banshee.Gui/PlaybackActions.cs (revision 3319) +++ Core/Banshee.ThickClient/Banshee.Gui/PlaybackActions.cs (working copy) @@ -92,7 +92,7 @@ new RadioActionEntry ("RepeatSingleAction", null, Catalog.GetString ("Repeat Si_ngle"), null, Catalog.GetString ("Repeat the current playing song"), 2) - }, 0, null); + }, 0, OnRepeatAction); actionService.GlobalActions.Add (new ActionEntry [] { new ActionEntry ("PlaybackMenuAction", null, @@ -206,5 +206,16 @@ ? PlaybackShuffleMode.Shuffle : PlaybackShuffleMode.Linear; } + + private void OnRepeatAction (object o, ChangedArgs args) + { + if (args.Current.Value == 0) { + ServiceManager.PlaybackController.RepeatMode = PlaybackRepeatMode.None; + } else if (args.Current.Value == 1) { + ServiceManager.PlaybackController.RepeatMode = PlaybackRepeatMode.RepeatAll; + } else if (args.Current.Value == 2) { + ServiceManager.PlaybackController.RepeatMode = PlaybackRepeatMode.RepeatSingle; + } + } } } Index: Core/Banshee.ThickClient/Banshee.Gui.Widgets/PlaybackToggleButtons.cs =================================================================== --- Core/Banshee.ThickClient/Banshee.Gui.Widgets/PlaybackToggleButtons.cs (revision 0) +++ Core/Banshee.ThickClient/Banshee.Gui.Widgets/PlaybackToggleButtons.cs (revision 0) @@ -0,0 +1,90 @@ +// +// PlaybackToggleButtons.cs +// +// Author: +// Alexander Hixon <hixon alexander mediati org> +// +// Copyright (C) 2008 Novell, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using Gtk; + +using Banshee.Gui; +using Banshee.Widgets; +using Banshee.ServiceStack; + +namespace Banshee.Gui.Widgets +{ + public interface InterfaceToggleButton + { + string ToggleButtonType { + get; + } + } + + public class ShuffleToggleButton : MultiStateToggleButton, InterfaceToggleButton + { + private InterfaceActionService action_service; + + public ShuffleToggleButton (InterfaceActionService actionService) : base () + { + action_service = actionService; + + this.AddState(typeof(ShuffleDisabledToggleState), + action_service["Playback.ShuffleAction"] as ToggleAction); + this.AddState(typeof(ShuffleEnabledToggleState), + action_service["Playback.ShuffleAction"] as ToggleAction); + this.Relief = ReliefStyle.None; + this.ShowLabel = false; + } + + public string ToggleButtonType + { + get { return "Shuffle"; } + } + } + + public class RepeatToggleButton : MultiStateToggleButton, InterfaceToggleButton + { + private InterfaceActionService action_service; + + public RepeatToggleButton (InterfaceActionService actionService) : base () + { + action_service = actionService; + + this.AddState(typeof(RepeatNoneToggleState), + action_service["Playback.RepeatNoneAction"] as ToggleAction); + this.AddState(typeof(RepeatAllToggleState), + action_service["Playback.RepeatAllAction"] as ToggleAction); + this.AddState(typeof(RepeatSingleToggleState), + action_service["Playback.RepeatSingleAction"] as ToggleAction); + this.Relief = ReliefStyle.None; + this.ShowLabel = false; + } + + public string ToggleButtonType + { + get { return "Repeat"; } + } + } +} \ No newline at end of file Index: Core/Banshee.ThickClient/Makefile.am =================================================================== --- Core/Banshee.ThickClient/Makefile.am (revision 3319) +++ Core/Banshee.ThickClient/Makefile.am (working copy) @@ -43,6 +43,7 @@ Banshee.Gui.Widgets/ConnectedMessageBar.cs \ Banshee.Gui.Widgets/ConnectedSeekSlider.cs \ Banshee.Gui.Widgets/ConnectedVolumeButton.cs \ + Banshee.Gui.Widgets/PlaybackToggleButtons.cs \ Banshee.Gui.Widgets/PlaylistMenuItem.cs \ Banshee.Gui.Widgets/TrackInfoDisplay.cs \ Banshee.Gui.Widgets/UserJobTile.cs \ @@ -59,6 +60,7 @@ Banshee.Gui/InterfaceActionService.cs \ Banshee.Gui/PlaybackActions.cs \ Banshee.Gui/SourceActions.cs \ + Banshee.Gui/ToggleStates.cs \ Banshee.Gui/TrackActions.cs \ Banshee.Gui/ViewActions.cs \ Banshee.Library.Gui/FileImportSource.cs \ @@ -91,7 +93,12 @@ Resources/source-playlist-16.png \ Resources/source-playlist-22.png \ Resources/source-smart-playlist-16.png \ - Resources/source-smart-playlist-22.png + Resources/source-smart-playlist-22.png \ + Resources/media-playlist-continuous.png \ + Resources/media-playlist-shuffle.png \ + Resources/media-repeat-all.png \ + Resources/media-repeat-none.png \ + Resources/media-repeat-single.png include $(top_srcdir)/build/build.mk Index: Core/Banshee.ThickClient/Resources/core-ui-actions-layout.xml =================================================================== --- Core/Banshee.ThickClient/Resources/core-ui-actions-layout.xml (revision 3319) +++ Core/Banshee.ThickClient/Resources/core-ui-actions-layout.xml (working copy) @@ -70,9 +70,9 @@ <menuitem name="RestartSong" action="RestartSongAction"/> <separator/> <placeholder name="PlaybackMenuAdditions"/> - <!--<menuitem name="RepeatNone" action="RepeatNoneAction"/> + <menuitem name="RepeatNone" action="RepeatNoneAction"/> <menuitem name="RepeatAll" action="RepeatAllAction"/> - <menuitem name="RepeatSingle" action="RepeatSingleAction"/>--> + <menuitem name="RepeatSingle" action="RepeatSingleAction"/> <separator/> <menuitem name="Shuffle" action="ShuffleAction"/> </menu> @@ -92,6 +92,14 @@ <menuitem name="About" action="AboutAction"/> </menu> </menubar> + + <toolbar name="FooterToolbar"> + <placeholder name="ShuffleToggleAction" /> + <placeholder name="RepeatToggleAction" /> + + <placeholder name="Statusbar" /> + <toolitem action="TrackPropertiesAction" /> + </toolbar> <popup name="LibraryContextMenu" action="LibraryContextMenuAction"> <menuitem name="NewPlaylist" action="NewPlaylistAction"/> Index: Core/Banshee.Services/Banshee.PlaybackController/ICanonicalPlaybackController.cs =================================================================== --- Core/Banshee.Services/Banshee.PlaybackController/ICanonicalPlaybackController.cs (revision 3319) +++ Core/Banshee.Services/Banshee.PlaybackController/ICanonicalPlaybackController.cs (working copy) @@ -31,7 +31,7 @@ public interface ICanonicalPlaybackController : IPlaybackController { new void First (); - new void Next (); + new void Next (bool restartAtEnd); new void Previous (); } } Index: Core/Banshee.Services/Banshee.PlaybackController/PlaybackControllerService.cs =================================================================== --- Core/Banshee.Services/Banshee.PlaybackController/PlaybackControllerService.cs (revision 3319) +++ Core/Banshee.Services/Banshee.PlaybackController/PlaybackControllerService.cs (working copy) @@ -97,7 +97,11 @@ switch (args.Event) { case PlayerEngineEvent.EndOfStream: if (!StopWhenFinished) { - Next (); + if (RepeatMode == PlaybackRepeatMode.RepeatSingle) { + QueuePlayTrack (); + } else { + Next (); + } } else { OnStopped (); } @@ -128,12 +132,17 @@ public void Next () { + Next (RepeatMode == PlaybackRepeatMode.RepeatAll); + } + + public void Next (bool restartAtEnd) + { OnTransition (); if (Source is IBasicPlaybackController) { - ((IBasicPlaybackController)Source).Next (); + ((IBasicPlaybackController)Source).Next (restartAtEnd); } else { - ((ICanonicalPlaybackController)this).Next (); + ((ICanonicalPlaybackController)this).Next (restartAtEnd); } } @@ -155,7 +164,7 @@ } } - void ICanonicalPlaybackController.Next () + void ICanonicalPlaybackController.Next (bool restartAtEnd) { TrackInfo tmp_track = CurrentTrack; @@ -170,6 +179,14 @@ if (tmp_track != null) { previous_stack.Push (tmp_track); } + } else if (restartAtEnd && Source.Count > 0) { + if (tmp_track != null) { + previous_stack.Push (tmp_track); + } + + CurrentTrack = Source.TrackModel[0]; + QueuePlayTrack (); + return; } else { return; } Index: Core/Banshee.Services/Banshee.PlaybackController/IBasicPlaybackController.cs =================================================================== --- Core/Banshee.Services/Banshee.PlaybackController/IBasicPlaybackController.cs (revision 3319) +++ Core/Banshee.Services/Banshee.PlaybackController/IBasicPlaybackController.cs (working copy) @@ -31,7 +31,7 @@ public interface IBasicPlaybackController { void First (); - void Next (); + void Next (bool restartAtEnd); void Previous (); } }
Attachment:
media-Resources.tar.gz
Description: GNU Zip compressed data