PageRenderTime 48ms CodeModel.GetById 13ms app.highlight 28ms RepoModel.GetById 2ms app.codeStats 0ms

/Application/GUI/Controls/Playback.xaml.cs

http://yet-another-music-application.googlecode.com/
C# | 953 lines | 623 code | 138 blank | 192 comment | 119 complexity | c94bcab539fe64c044bc14d377f4041f MD5 | raw file
  1/**
  2 * Playback.xaml.cs
  3 * 
  4 * All buttons and controls used to manage playback such as
  5 * play, pause, next and previous along with seek, volume and
  6 * search.
  7 * 
  8 * * * * * * * * *
  9 * 
 10 * This code is part of the Stoffi Music Player Project.
 11 * Visit our website at: stoffiplayer.com
 12 *
 13 * This program is free software; you can redistribute it and/or
 14 * modify it under the terms of the GNU General Public License
 15 * as published by the Free Software Foundation; either version
 16 * 3 of the License, or (at your option) any later version.
 17 * 
 18 * See stoffiplayer.com/license for more information.
 19 **/
 20
 21using System;
 22using System.Collections.Generic;
 23using System.Linq;
 24using System.Text;
 25using System.Windows;
 26using System.Windows.Controls;
 27using System.Windows.Data;
 28using System.Windows.Documents;
 29using System.Windows.Input;
 30using System.Windows.Media;
 31using System.Windows.Media.Imaging;
 32using System.Windows.Navigation;
 33using System.Windows.Threading;
 34using System.Windows.Shapes;
 35
 36namespace Stoffi
 37{
 38	/// <summary>
 39	/// A playback control that contains all playback buttons, track information, volume and seek slides as well as a search box
 40	/// </summary>
 41	public partial class Playback : UserControl
 42	{
 43		#region Members
 44
 45		public StoffiWindow ParentWindow;
 46		private BookmarkLayer bookmarkLayer;
 47
 48		// compression constants
 49		private double startCompression = 800;
 50		private double volumeMaxWidth = 70;
 51		private double volumeMinWidth = 35;
 52		private double repshuMaxMargin = 10;
 53		private double repshuMinMargin = 0;
 54		private double searchMaxWidth = 200;
 55		private double searchMinWidth = 90;
 56		private double volumeMaxMargin = 10;
 57		private double volumeMinMargin = 0;
 58
 59		#endregion
 60
 61		#region Constructor
 62
 63		/// <summary>
 64		/// Creates the playback control
 65		/// </summary>
 66		public Playback()
 67		{
 68			U.L(LogLevel.Debug, "PLAYBACK", "Initialize");
 69			InitializeComponent();
 70			U.L(LogLevel.Debug, "PLAYBACK", "Initialized");
 71			SettingsManager.PropertyChanged += new PropertyChangedWithValuesEventHandler(SettingsManager_PropertyChanged);
 72			SongProgress.ValueChanged -= new RoutedPropertyChangedEventHandler<double>(SongProgress_ValueChanged);
 73			SongProgress.Value = SettingsManager.Seek;
 74			SongProgress.ValueChanged += new RoutedPropertyChangedEventHandler<double>(SongProgress_ValueChanged);
 75			VolumeSlide.AutoToolTipPlacement = System.Windows.Controls.Primitives.AutoToolTipPlacement.TopLeft;
 76			U.L(LogLevel.Debug, "PLAYBACK", "Created");
 77		}
 78
 79		#endregion
 80
 81		#region Methods
 82
 83		#region Public
 84
 85		/// <summary>
 86		/// 
 87		/// </summary>
 88		/// <param name="width"></param>
 89		public void Compress(double width)
 90		{
 91			if (width < startCompression)
 92			{
 93				double stretchFactor = (width - ParentWindow.MinWidth) / (startCompression - ParentWindow.MinWidth);
 94
 95				double volumeWidth = volumeMinWidth + ((volumeMaxWidth - volumeMinWidth) * stretchFactor);
 96				double volumeMargin = volumeMinMargin + ((volumeMaxMargin - volumeMinMargin) * stretchFactor);
 97				double searchWidth = searchMinWidth + ((searchMaxWidth - searchMinWidth) * stretchFactor);
 98				double repshuMargin = repshuMinMargin + ((repshuMaxMargin - repshuMinMargin) * stretchFactor);
 99
100				VolumeSlide.Width = volumeWidth;
101				VolumeSlide.Margin = new Thickness(volumeMargin, 0, volumeMargin, 0);
102				Search.SearchContainer.Width = searchWidth;
103				RepeatShuffleContainer.Margin = new Thickness(repshuMargin, 0, repshuMargin, 0);
104			}
105			else
106			{
107				VolumeSlide.Width = volumeMaxWidth;
108				VolumeSlide.Margin = new Thickness(volumeMaxMargin, 0, volumeMaxMargin, 0);
109				Search.SearchContainer.Width = searchMaxWidth;
110				RepeatShuffleContainer.Margin = new Thickness(repshuMaxMargin, 0, repshuMaxMargin, 0);
111			}
112		}
113
114		/// <summary>
115		/// 
116		/// </summary>
117		/// <param name="pos"></param>
118		public void AddBookmark(double pos)
119		{
120			while (bookmarkLayer == null) ;
121			bookmarkLayer.AddBookmark(pos);
122		}
123
124		/// <summary>
125		/// 
126		/// </summary>
127		/// <param name="pos"></param>
128		public void RemoveBookmark(double pos)
129		{
130			while (bookmarkLayer == null) ;
131			bookmarkLayer.RemoveBookmark(pos);
132		}
133
134		/// <summary>
135		/// 
136		/// </summary>
137		public void ClearBookmarks()
138		{
139			while (bookmarkLayer == null) ;
140			bookmarkLayer.ClearBookmarks();
141		}
142
143		#endregion
144
145		#region Private
146
147		/// <summary>
148		/// Updates the shuffle button to reflect the current Shuffle state
149		/// </summary>
150		private void UpdateShuffle()
151		{
152			switch (SettingsManager.Shuffle)
153			{
154				case true:
155					ShuffleButton.Style = (Style)FindResource("ShuffleButtonStyle");
156					break;
157
158				case false:
159				default:
160					ShuffleButton.Style = (Style)FindResource("ShuffleGrayButtonStyle");
161					break;
162			}
163		}
164
165		/// <summary>
166		/// Updates the repeat button to reflect the current Repeat state
167		/// </summary>
168		private void UpdateRepeat()
169		{
170			switch (SettingsManager.Repeat)
171			{
172				case RepeatState.RepeatAll:
173					RepeatButton.Style = (Style)FindResource("RepeatAllButtonStyle");
174					break;
175
176				case RepeatState.RepeatOne:
177					RepeatButton.Style = (Style)FindResource("RepeatOneButtonStyle");
178					break;
179
180				case RepeatState.NoRepeat:
181				default:
182					RepeatButton.Style = (Style)FindResource("RepeatGrayButtonStyle");
183					break;
184			}
185		}
186
187		/// <summary>
188		/// Updates the information about the currently loaded track
189		/// </summary>
190		private void UpdateInfo()
191		{
192			if (SettingsManager.CurrentTrack == null)
193			{
194				InfoName.Text = U.T("PlaybackEmpty");
195				InfoTimeMinus.Content = "N/A";
196				InfoTimePlus.Content = "N/A";
197				SongProgress.ValueChanged -= new RoutedPropertyChangedEventHandler<double>(SongProgress_ValueChanged);
198				SongProgress.Value = SettingsManager.Seek;
199				SongProgress.SecondValueWidth = 0;
200				SongProgress.ValueChanged += new RoutedPropertyChangedEventHandler<double>(SongProgress_ValueChanged);
201			}
202			else
203			{
204				TrackData t = SettingsManager.CurrentTrack;
205				InfoName.Text = t.Artist + " - " + t.Title;
206
207				double pos = MediaManager.GetPosition();
208				double len = MediaManager.GetLength();
209
210				if (pos < 0) pos = 0;
211				if (len < 0) len = 0;
212
213				TimeSpan timePlus = new TimeSpan(0, 0, (int)pos);
214				TimeSpan timeMinus = new TimeSpan(0, 0, (int)(len - pos));
215
216				if (timePlus.TotalSeconds < 0)
217					InfoTimePlus.Content = "N/A";
218				else if (timePlus.TotalSeconds > 0)
219					InfoTimePlus.Content = U.TimeSpanToString(timePlus);
220
221				if (timeMinus.TotalSeconds < 0)
222					InfoTimeMinus.Content = "N/A";
223				else if (timeMinus.TotalSeconds > 0)
224					InfoTimeMinus.Content = "-" + U.TimeSpanToString(timeMinus);
225
226				double seek = (pos / len) * 10;
227				if (timePlus.Seconds < 0 || Double.IsNaN(seek) || Double.IsInfinity(seek)) seek = 0;
228
229				if (seek > 0 || SettingsManager.MediaState != MediaState.Playing)
230				{
231					SongProgress.ValueChanged -= new RoutedPropertyChangedEventHandler<double>(SongProgress_ValueChanged);
232					SongProgress.Value = seek;
233					SongProgress.ValueChanged += new RoutedPropertyChangedEventHandler<double>(SongProgress_ValueChanged);
234				}
235			}
236		}
237
238		#endregion
239
240		#region Event handlers
241
242		/// <summary>
243		/// Invoked when the user clicks on pause/play
244		/// </summary>
245		/// <param name="sender">The sender of the event</param>
246		/// <param name="e">The event data</param>
247		private void PausePlay_Click(object sender, RoutedEventArgs e)
248		{
249			if (SettingsManager.MediaState == MediaState.Playing)
250				MediaManager.Pause();
251			else if (SettingsManager.CurrentTrack != null)
252				MediaManager.Play();
253			else if (ParentWindow.GetCurrentTrackList() != null)
254			{
255				if (ParentWindow.GetCurrentTrackList().SelectedItems.Count > 0)
256				{
257					TrackData track = (TrackData)ParentWindow.GetCurrentTrackList().SelectedItem;
258					MediaManager.Load(track);
259					MediaManager.Play();
260				}
261				else if (ParentWindow.GetCurrentTrackList().Items.Count > 0)
262				{
263					TrackData track = (TrackData)ParentWindow.GetCurrentTrackList().Items[0];
264					MediaManager.Load(track);
265					MediaManager.Play();
266				}
267			}
268		}
269
270		/// <summary>
271		/// Invoked when the user clicks on next
272		/// </summary>
273		/// <param name="sender">The sender of the event</param>
274		/// <param name="e">The event data</param>
275		private void Next_Click(object sender, RoutedEventArgs e)
276		{
277			MediaManager.Next(true);
278		}
279
280		/// <summary>
281		/// Invoked when the user clicks on previous
282		/// </summary>
283		/// <param name="sender">The sender of the event</param>
284		/// <param name="e">The event data</param>
285		private void Previous_Click(object sender, RoutedEventArgs e)
286		{
287			MediaManager.Previous();
288		}
289
290		/// <summary>
291		/// Invoked when the user clicks on repeat
292		/// </summary>
293		/// <param name="sender">The sender of the event</param>
294		/// <param name="e">The event data</param>
295		public void Repeat_Click(object sender, RoutedEventArgs e)
296		{
297			switch (SettingsManager.Repeat)
298			{
299				case RepeatState.RepeatAll:
300					SettingsManager.Repeat = RepeatState.RepeatOne;
301					break;
302				case RepeatState.RepeatOne:
303					SettingsManager.Repeat = RepeatState.NoRepeat;
304					break;
305				case RepeatState.NoRepeat:
306				default:
307					SettingsManager.Repeat = RepeatState.RepeatAll;
308					break;
309			}
310			UpdateRepeat();
311		}
312
313		/// <summary>
314		/// Invoked when the user clicks on shuffle
315		/// </summary>
316		/// <param name="sender">The sender of the event</param>
317		/// <param name="e">The event data</param>
318		public void Shuffle_Click(object sender, RoutedEventArgs e)
319		{
320			SettingsManager.Shuffle = !SettingsManager.Shuffle;
321			UpdateShuffle();
322		}
323
324		/// <summary>
325		/// Invoked when the user changes the volume slider
326		/// </summary>
327		/// <param name="sender">The sender of the event</param>
328		/// <param name="e">The event data</param>
329		private void VolumeSlide_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
330		{
331			if (MediaManager.IsInitialized)
332				SettingsManager.Volume = VolumeSlide.Value;
333		}
334
335		/// <summary>
336		/// Invoked when the user scrolls over the volume slider
337		/// </summary>
338		/// <param name="sender">The sender of the event</param>
339		/// <param name="e">The event data</param>
340		private void VolumeSlide_MouseWheel(object sender, MouseWheelEventArgs e)
341		{
342			if (e.Delta > 0)
343				VolumeSlide.Value+=5;
344			else
345				VolumeSlide.Value-=5;
346		}
347
348		/// <summary>
349		/// Invoked when the user changes the seek slider
350		/// </summary>
351		/// <param name="sender">The sender of the event</param>
352		/// <param name="e">The event data</param>
353		public void SongProgress_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
354		{
355			if (MediaManager.IsInitialized)
356			{
357				SettingsManager.Seek = SongProgress.Value;
358
359				double pos = (ParentWindow.PlaybackControls.SongProgress.Value / 10) * MediaManager.GetLength();
360				double rem = MediaManager.GetLength() - pos;
361				TimeSpan timePlus = new TimeSpan(0, 0, (int)pos);
362				TimeSpan timeMinus = new TimeSpan(0, 0, (int)rem);
363
364				if (rem < 0)
365					ParentWindow.PlaybackControls.InfoTimePlus.Content = "N/A";
366				else
367					ParentWindow.PlaybackControls.InfoTimePlus.Content = U.TimeSpanToString(timePlus);
368
369				if (rem < 0)
370					ParentWindow.PlaybackControls.InfoTimeMinus.Content = "N/A";
371				else
372					ParentWindow.PlaybackControls.InfoTimeMinus.Content = "-" + U.TimeSpanToString(timeMinus);
373			}
374		}
375		
376		/// <summary>
377		/// Invoked when the user modifies the text in the search box
378		/// </summary>
379		/// <param name="e">The event data</param>
380		private void Search_TextChanged(SearchBoxEventArgs e)
381		{
382			String csn = SettingsManager.CurrentSelectedNavigation;
383			if (csn == null) return;
384
385			// switch to currently active navigation
386			if (csn == "NowPlaying")
387			{
388				ParentWindow.NavigationPane.SwitchNavigation(SettingsManager.CurrentActiveNavigation);
389				Search.IsActive = true;
390				Search.Text = e.Text;
391				Search.Position = e.Text.Count();
392				return;
393			}
394
395			if (ParentWindow == null) return;
396			ParentWindow.GetCurrentTrackList().Filter = e.Text;
397
398			if (csn == "YouTube")
399			{
400				ParentWindow.YouTubeContainer.Search(e.Text);
401				return;
402			}
403
404			if (csn == "History" || SettingsManager.SearchPolicy == SearchPolicy.Global)
405				SettingsManager.HistoryListConfig.Filter = e.Text;
406
407			if (csn == "Queue" || SettingsManager.SearchPolicy == SearchPolicy.Global)
408				SettingsManager.QueueListConfig.Filter = e.Text;
409
410			if (csn == "Library" || SettingsManager.SearchPolicy == SearchPolicy.Global)
411				SettingsManager.FileListConfig.Filter = e.Text;
412
413			if (SettingsManager.SearchPolicy == SearchPolicy.Global)
414			{
415				foreach (PlaylistData pl in SettingsManager.Playlists)
416					pl.ListConfig.Filter = e.Text;
417			}
418			else
419			{
420				if (csn.StartsWith("Playlist:") && SettingsManager.SearchPolicy == SearchPolicy.Partial)
421					foreach (PlaylistData pl in SettingsManager.Playlists)
422						pl.ListConfig.Filter = e.Text;
423				else if (csn.StartsWith("Playlist:"))
424				{
425					PlaylistData p = PlaylistManager.FindPlaylist(csn.Split(new[]{':'},2)[1]);
426					if (p != null) p.ListConfig.Filter = e.Text;
427				}
428			}
429		}
430
431		/// <summary>
432		/// Invoked when the user selects to add a search query to an existing playlist
433		/// </summary>
434		/// <param name="e">The event data</param>
435		private void Search_AddClicked(SearchBoxAddEventArgs e)
436		{
437			List<object> tracks = new List<object>();
438			foreach (TrackData track in ParentWindow.GetCurrentTrackList().Items)
439				tracks.Add(track);
440			PlaylistManager.AddToPlaylist(tracks, e.PlaylistName);
441		}
442
443		/// <summary>
444		/// Invoked when the user selects to add a search query to a new playlist
445		/// </summary>
446		/// <param name="e">The event data</param>
447		private void Search_AddToNewClicked(SearchBoxAddToNewEventArgs e)
448		{
449			ParentWindow.NavigationPane.AddToPlaylistQueue.Clear();
450			foreach (TrackData track in ParentWindow.GetCurrentTrackList().Items)
451				ParentWindow.NavigationPane.AddToPlaylistQueue.Add(track);
452
453			ParentWindow.NavigationPane.CreateNewPlaylistETB.IsInEditMode = true;
454		}
455
456		/// <summary>
457		/// Invoked when the user selects to remove a search query from a playlist
458		/// </summary>
459		/// <param name="e">The event data</param>
460		private void Search_RemoveClicked(SearchBoxRemoveEventArgs e)
461		{
462			List<TrackData> tracks = new List<TrackData>();
463			foreach (TrackData track in ParentWindow.GetCurrentTrackList().Items)
464				tracks.Add(track);
465			PlaylistManager.RemoveFromPlaylist(tracks, e.PlaylistName);
466		}
467
468		/// <summary>
469		/// Invoked when the user clears the search box
470		/// </summary>
471		private void Search_Cleared()
472		{
473			//ParentWindow.GetCurrentTrackList().Items.Filter = null;
474
475			// TODO: move code below into a function
476			String csn = SettingsManager.CurrentSelectedNavigation;
477
478			if (csn == "History" || SettingsManager.SearchPolicy == SearchPolicy.Global)
479				SettingsManager.HistoryListConfig.Filter = "";
480
481			if (csn == "Queue" || SettingsManager.SearchPolicy == SearchPolicy.Global)
482				SettingsManager.QueueListConfig.Filter = "";
483
484			if (csn == "Library" || SettingsManager.SearchPolicy == SearchPolicy.Global)
485				SettingsManager.FileListConfig.Filter = "";
486
487			if (SettingsManager.SearchPolicy == SearchPolicy.Global)
488			{
489				foreach (PlaylistData pl in SettingsManager.Playlists)
490					pl.ListConfig.Filter = "";
491			}
492			else
493			{
494				if (csn.StartsWith("Playlist:") && SettingsManager.SearchPolicy == SearchPolicy.Partial)
495					foreach (PlaylistData pl in SettingsManager.Playlists)
496						pl.ListConfig.Filter = "";
497				else if (csn.StartsWith("Playlist:"))
498					PlaylistManager.FindPlaylist(csn.Split(new[] { ':' }, 2)[1]).ListConfig.Filter = "";
499			}
500		}
501
502		/// <summary>
503		/// Invoked when the control is loded
504		/// </summary>
505		/// <param name="sender">The sender of the event</param>
506		/// <param name="e">The event data</param>
507		private void Control_Loaded(object sender, RoutedEventArgs e)
508		{
509			bookmarkLayer = new BookmarkLayer(SongProgress);
510			bookmarkLayer.RemoveClicked += new EventHandler(RemoveBookmark_Click);
511			bookmarkLayer.Clicked += new BookmarkEventHandler(Bookmark_Clicked);
512			AdornerLayer al = AdornerLayer.GetAdornerLayer(SongProgress);
513			al.Add(bookmarkLayer);
514
515			if (SettingsManager.CurrentTrack != null)
516			{
517				foreach (double b in MediaManager.GetLibrarySourceTrack(SettingsManager.CurrentTrack).Bookmarks)
518				{
519					bookmarkLayer.AddBookmark(b);
520				}
521			}
522
523			if (System.Windows.Forms.VisualStyles.VisualStyleInformation.DisplayName == "")
524			{
525				InfoWindow.BorderThickness = new Thickness(1, 1, 0, 0);
526				InfoWindow.BorderBrush = SystemColors.ControlDarkBrush;
527				InfoWindow.CornerRadius = new CornerRadius(0);
528
529				InfoWindowBorder1.BorderThickness = new Thickness(0, 0, 1, 1);
530				InfoWindowBorder1.BorderBrush = SystemColors.ControlLightLightBrush;
531				InfoWindowBorder1.CornerRadius = new CornerRadius(0);
532
533				InfoWindowBorder2.BorderThickness = new Thickness(1, 1, 0, 0);
534				InfoWindowBorder2.BorderBrush = null;
535				InfoWindowBorder2.CornerRadius = new CornerRadius(0);
536
537				InfoWindowBorder3.BorderThickness = new Thickness(0, 0, 1, 1);
538				InfoWindowBorder3.BorderBrush = null;
539				InfoWindowBorder3.CornerRadius = new CornerRadius(0);
540
541				InfoWindowInner.Background = SystemColors.ControlBrush;
542
543				InfoTimeMinus.Foreground = SystemColors.ControlTextBrush;
544				InfoTimePlus.Foreground = SystemColors.ControlTextBrush;
545				InfoName.Foreground = SystemColors.ControlTextBrush;
546			}
547
548			UpdateInfo();
549			UpdateShuffle();
550			UpdateRepeat();
551		}
552
553		/// <summary>
554		/// Invoked when the user removes a bookmark
555		/// </summary>
556		/// <param name="sender">The sender of the event</param>
557		/// <param name="e">The event data</param>
558		private void RemoveBookmark_Click(object sender, EventArgs e)
559		{
560			DispatchRemoveBookmarkClicked(sender, e);
561		}
562
563		/// <summary>
564		/// Invoked when the user clicks on a bookmark
565		/// </summary>
566		/// <param name="sender">The sender of the event</param>
567		/// <param name="e">The event data</param>
568		private void Bookmark_Clicked(object sender, BookmarkEventArgs e)
569		{
570			this.SongProgress.Value = e.Position / 10;
571		}
572
573		/// <summary>
574		/// Invoked when a property of the settings manager changes
575		/// </summary>
576		/// <param name="sender">The sender of the event</param>
577		/// <param name="e">The event data</param>
578		private void SettingsManager_PropertyChanged(object sender, PropertyChangedWithValuesEventArgs e)
579		{
580			Dispatcher.Invoke(DispatcherPriority.Normal, new Action(delegate()
581			{
582				switch (e.PropertyName)
583				{
584					case "Seek":
585						UpdateInfo();
586						break;
587
588					case "BufferSize":
589						SongProgress.SecondValue = SettingsManager.BufferSize;
590						break;
591
592					case "Volume":
593						VolumeSlide.Value = SettingsManager.Volume;
594						Console.WriteLine("setting volume to: " + VolumeSlide.Value);
595						break;
596
597					case "Shuffle":
598						UpdateShuffle();
599						break;
600
601					case "Repeat":
602						UpdateRepeat();
603						break;
604
605					case "CurrentTrack":
606						UpdateInfo();
607						ClearBookmarks();
608						if (SettingsManager.CurrentTrack != null)
609						{
610							TrackData libraryTrack = MediaManager.GetLibrarySourceTrack(SettingsManager.CurrentTrack);
611							if (libraryTrack.Bookmarks != null)
612								foreach (double b in libraryTrack.Bookmarks)
613									AddBookmark(b);
614						}
615						break;
616
617					case "MediaState":
618						switch (SettingsManager.MediaState)
619						{
620							case MediaState.Playing:
621								PausePlayButton.Style = (Style)FindResource("PauseButtonStyle");
622								break;
623
624							case MediaState.Paused:
625							case MediaState.Stopped:
626								PausePlayButton.Style = (Style)FindResource("PlayButtonStyle");
627								break;
628						}
629						break;
630				}
631			}));
632		}
633
634		#endregion
635
636		#endregion
637
638		#region Events
639
640		/// <summary>
641		/// 
642		/// </summary>
643		public event EventHandler RemoveBookmarkClicked;
644
645		/// <summary>
646		/// 
647		/// </summary>
648		/// <param name="sender"></param>
649		/// <param name="e"></param>
650		public void DispatchRemoveBookmarkClicked(object sender, EventArgs e)
651		{
652			if (RemoveBookmarkClicked != null)
653			{
654				RemoveBookmarkClicked(sender, e);
655			}
656		}
657
658		#endregion
659	}
660
661	/// <summary>
662	/// Describes the layer above the seek bar which holds the bookmarks
663	/// </summary>
664	public class BookmarkLayer : Adorner
665	{
666		#region Members
667
668		private VisualCollection visualChildren;
669		private Grid grid;
670		private List<ColumnDefinition> spacers = new List<ColumnDefinition>();
671		public List<Bookmark> bookmarks = new List<Bookmark>();
672		public event EventHandler RemoveClicked;
673		public event BookmarkEventHandler Clicked;
674
675		#endregion
676
677		#region Constructor
678
679		/// <summary>
680		/// Creates a Bookmarklayer
681		/// </summary>
682		/// <param name="adornedElement"></param>
683		public BookmarkLayer(UIElement adornedElement)
684			: base(adornedElement)
685		{
686			visualChildren = new VisualCollection(this);
687			grid = new Grid();
688			grid.Margin = new Thickness(0, 1, 0, 0);
689			visualChildren.Add(grid);
690		}
691
692		#endregion
693
694		#region Methods
695
696		#region Public
697
698		/// <summary>
699		/// 
700		/// </summary>
701		/// <param name="pos"></param>
702		public void AddBookmark(double pos)
703		{
704			double bmWidth = 4.0;
705			double MyWidth = this.ActualWidth;
706			if (MyWidth < 147) MyWidth = 147;
707			double posInPixels = MyWidth * (pos / 100);
708			//if (posInPixels < (bmWidth / 2) || posInPixels > this.ActualWidth - (bmWidth / 2)) return;
709
710			double width = posInPixels;
711			double i = 0;
712			ColumnDefinition newCd;
713			ColumnDefinition bmCd;
714			Bookmark bm;
715
716			foreach (ColumnDefinition cd in spacers)
717			{
718				width = posInPixels - i;
719				i += cd.Width.Value;
720				if (i - (bmWidth / 2) < posInPixels && posInPixels <= i + (bmWidth * 1.5)) return;
721
722				if (posInPixels < i)
723				{
724					newCd = new ColumnDefinition();
725					newCd.Width = new GridLength(width - (bmWidth/2));
726
727					bmCd = new ColumnDefinition();
728					bmCd.Width = new GridLength(bmWidth);
729
730					int c = grid.ColumnDefinitions.IndexOf(cd);
731
732					grid.ColumnDefinitions.Insert(c, bmCd);
733					grid.ColumnDefinitions.Insert(c, newCd);
734
735					cd.Width = new GridLength(cd.Width.Value - (width + (bmWidth * 1.5)));
736
737					bm = new Bookmark();
738					bm.Position = pos;
739					bm.RemoveClicked += new EventHandler(Bookmark_RemoveClicked);
740					bm.Clicked += new BookmarkEventHandler(Bookmark_Clicked);
741					foreach (Bookmark b in bookmarks)
742					{
743						if (b.Position > bm.Position)
744						{
745							spacers.Insert(bookmarks.IndexOf(b), newCd);
746							bookmarks.Insert(bookmarks.IndexOf(b), bm);
747							break;
748						}
749					}
750
751					int j = 1;
752					foreach (Bookmark b in bookmarks)
753					{
754						j += 2;
755						if (b.Position > bm.Position)
756							Grid.SetColumn(b, j);
757					}
758					Grid.SetColumn(bm, c + 1);
759					grid.Children.Add(bm);
760
761					return;
762				}
763				i += 2.0;
764			}
765
766			width = posInPixels - i;
767
768			newCd = new ColumnDefinition();
769			newCd.Width = new GridLength(width - (bmWidth / 2));
770			spacers.Add(newCd);
771
772
773			bmCd = new ColumnDefinition();
774			bmCd.Width = new GridLength(bmWidth);
775
776			grid.ColumnDefinitions.Add(newCd);
777			grid.ColumnDefinitions.Add(bmCd);
778
779			bm = new Bookmark();
780			bm.Position = pos;
781			bm.RemoveClicked += new EventHandler(Bookmark_RemoveClicked);
782			bm.Clicked += new BookmarkEventHandler(Bookmark_Clicked);
783			bookmarks.Add(bm);
784
785			Grid.SetColumn(bm, grid.ColumnDefinitions.Count - 1);
786			grid.Children.Add(bm);
787		}
788
789		/// <summary>
790		/// 
791		/// </summary>
792		/// <param name="pos"></param>
793		public void RemoveBookmark(double pos)
794		{
795			Bookmark bookmarkToRemove = null;
796			int i = 0;
797			foreach (Bookmark m in bookmarks)
798			{
799				if (m.Position == pos)
800				{
801					i = bookmarks.IndexOf(m);
802					bookmarkToRemove = m;
803					break;
804				}
805			}
806
807			if (bookmarkToRemove != null)
808			{
809				if (i == bookmarks.Count - 1)
810				{
811					grid.ColumnDefinitions.Remove(grid.ColumnDefinitions.Last());
812					grid.ColumnDefinitions.Remove(grid.ColumnDefinitions.Last());
813					grid.Children.Remove(bookmarks.Last());
814					spacers.Remove(spacers.Last());
815					bookmarks.Remove(bookmarks.Last());
816				}
817				else
818				{
819					ColumnDefinition cd1 = grid.ColumnDefinitions[(2 * i)];
820					ColumnDefinition cd2 = grid.ColumnDefinitions[(2 * i) + 1];
821					ColumnDefinition cd3 = grid.ColumnDefinitions[(2 * i) + 2];
822					cd3.Width = new GridLength(cd3.Width.Value + cd2.Width.Value + cd1.Width.Value);
823										
824					grid.ColumnDefinitions.Remove(cd1);
825					grid.ColumnDefinitions.Remove(cd2);
826					grid.Children.Remove(bookmarkToRemove);
827					spacers.Remove(cd1);
828					bookmarks.Remove(bookmarkToRemove);
829					foreach (Bookmark b in bookmarks)
830						Grid.SetColumn(b, bookmarks.IndexOf(b) * 2 + 1);
831				}
832			}
833		}
834
835		/// <summary>
836		/// 
837		/// </summary>
838		public void ClearBookmarks()
839		{
840			bookmarks.Clear();
841			spacers.Clear();
842			grid.ColumnDefinitions.Clear();
843			grid.Children.Clear();
844		}
845
846		#endregion
847
848		#region Event handlers
849
850		/// <summary>
851		/// 
852		/// </summary>
853		/// <param name="sender"></param>
854		/// <param name="e"></param>
855		private void Bookmark_RemoveClicked(object sender, EventArgs e)
856		{
857			DispatchRemoveClicked(sender, e);
858		}
859
860		/// <summary>
861		/// 
862		/// </summary>
863		/// <param name="sender"></param>
864		/// <param name="e"></param>
865		private void Bookmark_Clicked(object sender, BookmarkEventArgs e)
866		{
867			DispatchClicked(sender, e);
868		}
869
870		#endregion
871
872		#endregion
873
874		#region Overrides
875
876		/// <summary>
877		/// 
878		/// </summary>
879		/// <param name="finalSize"></param>
880		/// <returns></returns>
881		protected override Size ArrangeOverride(Size finalSize)
882		{
883			double bmWidth = 4.0;
884			double x = 0;
885			double y = 0;
886			grid.Arrange(new Rect(x, y, finalSize.Width, finalSize.Height));
887
888			double i = 0.0;
889			foreach (Bookmark bm in bookmarks)
890			{
891				double posInPixels = finalSize.Width * (bm.Position / 100);
892				
893				ColumnDefinition cd = grid.ColumnDefinitions[(2 * bookmarks.IndexOf(bm))];
894				grid.ColumnDefinitions[(2 * bookmarks.IndexOf(bm)) + 1].Width = new GridLength(bmWidth);
895
896				Grid.SetColumn(bm, 1+ (bookmarks.IndexOf(bm) * 2));
897
898				if (posInPixels - i - (bmWidth / 2) > 0)
899					cd.Width = new GridLength(posInPixels - i - (bmWidth / 2));
900				else
901					cd.Width = new GridLength(0);
902
903				i += cd.Width.Value + bmWidth;
904			}
905
906			return finalSize;
907		}
908
909		/// <summary>
910		/// 
911		/// </summary>
912		protected override int VisualChildrenCount { get { return visualChildren.Count; } }
913
914		/// <summary>
915		/// 
916		/// </summary>
917		/// <param name="index"></param>
918		/// <returns></returns>
919		protected override Visual GetVisualChild(int index) { return visualChildren[index]; }
920
921		#endregion
922
923		#region Events
924
925		/// <summary>
926		/// 
927		/// </summary>
928		/// <param name="sender"></param>
929		/// <param name="e"></param>
930		public void DispatchRemoveClicked(object sender, EventArgs e)
931		{
932			if (RemoveClicked != null)
933			{
934				RemoveClicked(sender, e);
935			}
936		}
937
938		/// <summary>
939		/// 
940		/// </summary>
941		/// <param name="sender"></param>
942		/// <param name="e"></param>
943		public void DispatchClicked(object sender, BookmarkEventArgs e)
944		{
945			if (Clicked != null)
946			{
947				Clicked(sender, e);
948			}
949		}
950
951		#endregion
952	}
953}