6
public class BeatBox.LibraryWindow : Gtk.Window {
7
BeatBox.LibraryManager lm;
8
BeatBox.Settings settings;
9
BeatBox.StreamPlayer player;
11
string current_view_path;
12
bool queriedlastfm; // whether or not we have queried last fm for the current song info
13
bool song_considered_played; //whether or not we have updated last played and added to already played list
14
bool added_to_play_count; // whether or not we have added one to play count on playing song
16
bool loaded_groove_shark;
17
string timeout_search;
21
HPaned sourcesToSongs; //allows for draggable
22
ScrolledWindow sideTreeScroll;
25
SideTreeView sideTree;
26
ScrolledWindow songInfoScroll;
27
ScrolledWindow pandoraScroll;
28
ScrolledWindow grooveSharkScroll;
34
ToolButton previousButton;
35
ToolButton playButton;
36
ToolButton nextButton;
40
ElementaryWidgets.TopDisplay topDisplay;
41
ElementaryWidgets.ElementarySearchEntry searchField;
42
ElementaryWidgets.AppMenu appMenu;
48
MenuItem fileRescanMusicFolder;
50
MenuItem helpTranslate;
53
MenuItem editPreferences;
57
//Notify.Notification notification;
59
public LibraryWindow(BeatBox.DataBaseManager dbm, BeatBox.StreamPlayer player) {
60
settings = new BeatBox.Settings();
63
//this is used by many objects, is the media backend
64
lm = new BeatBox.LibraryManager(player, dbm, settings);
68
this.player.end_of_stream.connect(end_of_stream);
69
this.player.current_position_update.connect(current_position_update);
70
this.lm.music_added.connect(musicAdded);
71
this.lm.music_rescanned.connect(musicRescanned);
72
this.lm.progress_notification.connect(progressNotification);
73
this.lm.song_removed.connect(songRemovedFromManager);
74
this.lm.song_played.connect(song_played);
75
destroy.connect (on_quit);
76
check_resize.connect(on_resize);
77
this.destroy.connect (Gtk.main_quit);
80
public void build_ui() {
81
// simple message to terminal
82
stdout.printf("Building user interface\n");
84
// set the size based on saved gconf settings
85
set_size_request(settings.getWindowWidth(), settings.getWindowHeight());
91
/* Initialize all components */
92
verticalBox = new VBox(false, 0);
93
sourcesToSongs = new HPaned();
94
contentBox = new VBox(false, 0);
95
mainViews = new VBox(false, 0);
96
sideTree = new SideTreeView(lm, this);
97
sideTreeScroll = new ScrolledWindow(null, null);
98
topMenu = new MenuBar();
99
fileRescanMusicFolder = new MenuItem.with_label("Rescan Music Folder");
100
helpOnline = new MenuItem.with_label("Get Help Online...");
101
helpTranslate = new MenuItem.with_label("Translate This Application...");
102
helpReport = new MenuItem.with_label("Report a Problem...");
103
helpAbout = new MenuItem.with_label("About");
104
editPreferences = new MenuItem.with_label("Preferences");
105
settingsMenu = new Menu();
106
topControls = new Toolbar();
107
previousButton = new ToolButton.from_stock(Gtk.Stock.MEDIA_PREVIOUS);
108
playButton = new ToolButton.from_stock(Gtk.Stock.MEDIA_PLAY);
109
nextButton = new ToolButton.from_stock(Gtk.Stock.MEDIA_NEXT);
110
shuffleButton = new Button.with_label("Shuffle");
111
loveButton = new Button.with_label("Love");
112
banButton = new Button.with_label("Ban");
113
topDisplay = new ElementaryWidgets.TopDisplay(lm);
114
searchField = new ElementaryWidgets.ElementarySearchEntry("Search...");
115
appMenu = new ElementaryWidgets.AppMenu.from_stock(Gtk.Stock.PROPERTIES, Gtk.IconSize.MENU, "Menu", settingsMenu);
116
songInfoScroll = new ScrolledWindow(null, null);
117
pandoraScroll = new ScrolledWindow(null, null);
118
grooveSharkScroll = new ScrolledWindow(null, null);
119
songInfo = new WebView();
120
pandora = new WebView();
121
grooveShark = new WebView();
122
sideBar = new VBox(false, 0);
123
coverArt = new Image.from_file(Environment.get_home_dir () + "/.beatbox/default_cover.jpg");
124
statusBar = new Statusbar();
125
//notification = new Notification("Title", "Artist\nAlbum", "", null);
127
/* Set properties of various controls */
128
sourcesToSongs.child1_resize = 1;
129
sideBar.set_size_request(settings.getSidebarWidth(), -1);
132
shuffleButton.relief = Gtk.ReliefStyle.NONE;
133
//shuffleButton.image = new Gtk.Image.from_stock(Gtk.Stock.MEDIA_SHUFFLE, Gtk.IconSize.SMALL_TOOLBAR);
134
loveButton.relief = Gtk.ReliefStyle.NONE;
135
banButton.relief = Gtk.ReliefStyle.NONE;
137
if(lm.song_count() != 0)
138
searchField.set_sensitive(true);
140
searchField.set_sensitive(false);
142
settingsMenu.append(fileRescanMusicFolder);
143
settingsMenu.append(new SeparatorMenuItem());
144
settingsMenu.append(helpOnline);
145
settingsMenu.append(helpTranslate);
146
settingsMenu.append(helpReport);
147
settingsMenu.append(new SeparatorMenuItem());
148
settingsMenu.append(helpAbout);
149
settingsMenu.append(editPreferences);
151
fileRescanMusicFolder.activate.connect(fileRescanMusicFolderClick);
152
helpOnline.activate.connect( () => {
153
string auth_uri = "https://answers.launchpad.net/beat-box";
154
GLib.AppInfo.launch_default_for_uri (auth_uri, null);
156
helpTranslate.activate.connect( () => {
157
string auth_uri = "https://translations.launchpad.net/beat-box";
158
GLib.AppInfo.launch_default_for_uri (auth_uri, null);
160
helpReport.activate.connect( () => {
161
string auth_uri = "https://bugs.launchpad.net/beat-box";
162
GLib.AppInfo.launch_default_for_uri (auth_uri, null);
164
helpAbout.activate.connect(helpAboutClick);
165
editPreferences.activate.connect(editPreferencesClick);
167
songInfo.open("file://"+Environment.get_home_dir () + "/.beatbox/song_info.html");
169
statusBar.has_resize_grip = true;
171
/* Add controls to the GUI */
173
verticalBox.pack_start(topMenu, false, true, 0);
174
verticalBox.pack_start(topControls, false, true, 0);
175
verticalBox.pack_start(sourcesToSongs, true, true, 0);
176
//verticalBox.pack_start(statusBar, false, true, 0);
178
ToolItem topDisplayBin = new ToolItem();
179
ToolItem searchFieldBin = new ToolItem();
180
ToolItem appMenuBin = new ToolItem();
181
topDisplayBin.add(topDisplay);
182
searchFieldBin.add(searchField);
183
appMenuBin.add(appMenu);
185
topDisplayBin.set_expand(true);
187
topControls.insert(previousButton, 0);
188
topControls.insert(playButton, 1);
189
topControls.insert(nextButton, 2);
190
topControls.insert(topDisplayBin, 3);
191
topControls.insert(searchFieldBin, 4);
192
topControls.insert(appMenuBin, 5);
194
//set the name for elementary theming
195
sourcesToSongs.name = "SidebarHandleLeft";
196
sideTree.name = "SidebarContent";
198
contentBox.pack_start(mainViews, true, true, 0);
199
contentBox.pack_start(statusBar, false, true, 0);
201
sourcesToSongs.add1(sideBar);
202
sourcesToSongs.add2(contentBox);
204
songInfoScroll.add(songInfo);
205
pandoraScroll.add(pandora);
206
grooveSharkScroll.add(grooveShark);
208
songInfo.window_features.scrollbar_visible = false;
210
sideBar.pack_start(sideTreeScroll, true, true, 0);
211
sideBar.pack_end(coverArt, false, true, 0);
213
statusBar.pack_start(shuffleButton);
215
/* Connect events to functions */
216
sourcesToSongs.child1.size_allocate.connect(sourcesToSongsHandleSet);
217
sideTree.row_activated.connect(sideListDoubleClick);
218
previousButton.clicked.connect(previousClicked);
219
playButton.clicked.connect(playClicked);
220
nextButton.clicked.connect(nextClicked);
221
shuffleButton.clicked.connect(shuffleClicked);
222
loveButton.clicked.connect(loveButtonClicked);
223
banButton.clicked.connect(banButtonClicked);
224
topDisplay.scale_value_changed.connect(topDisplaySliderMoved);
225
searchField.changed.connect(searchFieldChanged);
229
topDisplay.show_scale();
231
sideTree.resetView();
234
/** Builds the side tree on TreeView view
235
* @param view The side tree to build it on
237
private void buildSideTree() {
240
// put song info first so it is on top when using multiple views
241
sideTree.addItem(null, new GLib.Object(), songInfoScroll, "Song Info");
242
mainViews.pack_start(songInfoScroll, true, true, 0);
244
mtv = new MusicTreeView(lm, this, -1);
245
mtv.set_hint("queue");
246
mtv.populateView(lm.queue(), false);
247
mtv.view_being_searched.connect(musicTreeViewSearched);
248
sideTree.addItem(null, null, mtv, "Queue");
249
mainViews.pack_start(mtv, true, true, 0);
251
mtv = new MusicTreeView(lm, this, -1);
252
mtv.set_hint("already played");
253
mtv.populateView(lm.already_played(), false);
254
mtv.view_being_searched.connect(musicTreeViewSearched);
255
sideTree.addItem(null, null, mtv, "Already Played");
256
mainViews.pack_start(mtv, true, true, 0);
258
mtv = new MusicTreeView(lm, this, -1);
259
mtv.set_hint("library");
260
mtv.populateView(lm.song_ids(), false);
261
mtv.view_being_searched.connect(musicTreeViewSearched);
262
sideTree.addItem(null, null, mtv, "Library");
263
mainViews.pack_start(mtv, true, true, 0);
265
//why should i have internet sources on a music player???
266
//I should just concentrate on playing local music files
268
sideTree.addItem(sideTree.get_internet_iter(), new GLib.Object(), pandoraScroll, "Pandora");
269
mainViews.pack_start(pandoraScroll, true, true, 0);
272
sideTree.addItem(sideTree.get_internet_iter(), new GLib.Object(), grooveSharkScroll, "Groove Shark");
273
mainViews.pack_start(grooveSharkScroll, true, true, 0);*/
275
// load smart playlists
276
foreach(SmartPlaylist p in lm.smart_playlists()) {
281
foreach(Playlist p in lm.playlists()) {
286
sideTree.get_selection().select_iter(sideTree.get_library_iter());
287
sideTree.sideListSelectionChange();
288
sideTree.expand_all();
290
sideTreeScroll = new ScrolledWindow(null, null);
291
sideTreeScroll.set_policy (PolicyType.AUTOMATIC, PolicyType.AUTOMATIC);
292
sideTreeScroll.add(sideTree);
295
public void addSideListItem(GLib.Object o) {
296
TreeIter item = sideTree.get_library_iter();
298
MusicTreeView mtv = null;
301
Playlist p = (Playlist)o;
303
mtv = new MusicTreeView(lm, this, -1);
304
mtv.set_hint("playlist");
306
mtv.populateView(lm.songs_from_playlist(p.rowid), false);
307
item = sideTree.addItem(sideTree.get_library_iter(), p, mtv, p.name);
308
mainViews.pack_start(mtv, true, true, 0);
310
else if(o is SmartPlaylist) {
311
SmartPlaylist p = (SmartPlaylist)o;
313
mtv = new MusicTreeView(lm, this, -1);
314
mtv.set_hint("smart playlist");
316
mtv.populateView(lm.songs_from_smart_playlist(p.rowid), false);
317
item = sideTree.addItem(sideTree.get_library_iter(), p, mtv, p.name);
318
mainViews.pack_start(mtv, true, true, 0);
322
mtv.view_being_searched.connect(musicTreeViewSearched);
323
sideTree.get_selection().unselect_all();
324
sideTree.get_selection().select_iter(item);
327
public virtual void progressNotification(string? message, double progress) {
328
/*if(message != null)
329
topDisplay.set_label_text(message);
331
topDisplay.set_progress_value(progress);*/
334
public virtual void sideListDoubleClick (TreePath path, TreeViewColumn column) {
338
public bool updateCurrentSong() {
339
//loop through all musictreeviews and call updatecurrentsong
342
if((file = lm.get_album_location(lm.song_info.song.rowid)) != null) {
344
coverArt.set_from_file(file);
346
else if(lm.song_info.album.url_image.image != null) {
348
coverArt.set_from_pixbuf(lm.song_info.album.url_image.image);
356
/** This should be used whenever a call to play a new song is made
357
* @param s The song that is now playing
359
public virtual void song_played(int i) {
361
var song_label = lm.song_from_id(i).title + " by " + lm.song_from_id(i).artist + " on " + lm.song_from_id(i).album;
362
topDisplay.set_label_text(song_label);
364
//reset the song position
365
topDisplay.set_scale_range(0.0, lm.song_info.song.length);
366
topDisplay.set_scale_value(0.0);
368
//if(!songPosition.get_sensitive())
369
// songPosition.set_sensitive(true);
371
//reset some booleans
372
queriedlastfm = false;
373
song_considered_played = false;
374
added_to_play_count = false;
377
//update the notifier
378
//notification.close();
379
//notification.summary = lm.song_from_id(i).title;
380
//notification.body = lm.song_from_id(i).artist + "\n" + lm.song_from_id(i).album;
384
if((file = lm.get_album_location(lm.song_info.song.rowid)) != null) {
386
coverArt.set_from_pixbuf(new LastFM.Image.with_url("file://" + file, true).image);
387
//notification.set_image_from_pixbuf(new Gdk.Pixbuf.from_file(file));
390
//notification.set_image_from_pixbuf(new Gdk.Pixbuf.from_file(Environment.get_home_dir () + "/.beatbox/default_cover.jpg"));
395
//notification.show();
397
sideTree.updatePlayQueue();
400
public void* lastfm_thread_function () {
401
bool update_track = false, update_artist = false, update_album = false;
402
LastFM.ArtistInfo artist = new LastFM.ArtistInfo.basic();
403
LastFM.TrackInfo track = new LastFM.TrackInfo.basic();
404
LastFM.AlbumInfo album = new LastFM.AlbumInfo.basic();
406
if(lm.song_info.album.name != lm.song_info.song.album || lm.song_info.album.artist != lm.song_info.song.artist) {
409
if(!lm.album_info_exists(lm.song_info.song.album + " by " + lm.song_info.song.artist)) {
410
//stdout.printf("Downloading new Album Info from Last FM\n");
411
album = new LastFM.AlbumInfo.with_info(lm.song_info.song.artist, lm.song_info.song.album);
413
//try to save album image locally
414
if(lm.get_album_location(lm.song_info.song.rowid) == null)
415
lm.save_album_locally(lm.song_info.song.rowid, album.url_image.url);
417
lm.save_album(album);
420
album = lm.get_album(lm.song_info.song.album + " by " + lm.song_info.song.artist);
422
//if no local image saved, save it now
423
if(lm.get_album_location(lm.song_info.song.rowid) == null)
424
lm.save_album_locally(lm.song_info.song.rowid, album.url_image.url);
427
if(lm.song_info.artist.name != lm.song_info.song.artist) {
428
update_artist = true;
430
if(!lm.artist_info_exists(lm.song_info.song.artist)) {
431
//stdout.printf("Downloading new Artist Info from Last FM\n");
432
artist = new LastFM.ArtistInfo.with_artist(lm.song_info.song.artist);
434
//try to save artist art locally
435
if(lm.get_album_location(lm.song_info.song.rowid) == null)
436
lm.save_artist_image_locally(lm.song_info.song.rowid, artist.url_image.url);
438
lm.save_artist(artist);
441
artist = lm.get_artist(lm.song_info.song.artist);
443
//if no local image saved, save it now
444
if(lm.get_artist_image_location(lm.song_info.song.rowid) == null)
445
lm.save_artist_image_locally(lm.song_info.song.rowid, artist.url_image.url);
448
if(lm.song_info.track.name != lm.song_info.song.title || lm.song_info.track.artist != lm.song_info.song.artist) {
451
if(!lm.track_info_exists(lm.song_info.song.title + " by " + lm.song_info.song.artist)) {
452
//stdout.printf("Downloading new Track Info from Last FM\n");
453
track = new LastFM.TrackInfo.with_info(lm.song_info.song.artist, lm.song_info.song.title);
454
lm.save_track(track);
457
track = lm.get_track(lm.song_info.song.title + " by " + lm.song_info.song.artist);
460
//test if song info is still what we want or if user has moved on
461
bool update_song_display = false;
463
if(lm.song_info.album.name != album.name && update_album) {
464
update_song_display = true;
465
lm.song_info.album = album;
467
if(lm.song_info.artist.name != artist.name && update_artist) {
468
update_song_display = true;
469
lm.song_info.artist = artist;
471
if(lm.song_info.track.name != track.name && update_track) {
472
update_song_display = true;
473
lm.song_info.track = track;
476
if(update_song_display) {
477
Idle.add(updateSongInfo);
478
Idle.add(updateCurrentSong);
484
public bool updateSongInfo() {
485
string html_file = lm.song_info.update_file(lm.song_info.artist, lm.song_info.track, lm.song_info.album, lm.song_info.song);
486
songInfo.open(html_file);
491
public virtual void previousClicked () {
492
lm.getPrevious(true);
495
public virtual void playClicked () {
496
if(lm.song_info.song == null) {
497
//set current songs by current view
498
Widget w = sideTree.getSelectedWidget();
499
if(w is MusicTreeView) {
500
((MusicTreeView)w).setAsCurrentList("0");
503
w = sideTree.getWidget(sideTree.get_library_iter());
504
((MusicTreeView)w).setAsCurrentList("0");
508
playButton.set_stock_id(Gtk.Stock.MEDIA_PAUSE);
509
player.play_stream();
516
player.pause_stream();
517
playButton.set_stock_id(Gtk.Stock.MEDIA_PLAY);
521
player.play_stream();
522
playButton.set_stock_id(Gtk.Stock.MEDIA_PAUSE);
527
public virtual void nextClicked() {
531
public virtual void shuffleClicked() {
532
if(shuffleButton.get_label() == "Shuffle") {
533
shuffleButton.set_label("Unshuffle");
537
shuffleButton.set_label("Shuffle");
542
public virtual void loveButtonClicked() {
543
lm.lfm.loveTrack(lm.song_info.song.title, lm.song_info.song.artist);
546
public virtual void banButtonClicked() {
547
lm.lfm.banTrack(lm.song_info.song.title, lm.song_info.song.artist);
550
/** implement search in librarymanager?????? **/
551
public virtual void searchFieldChanged() {
552
Timeout.add(350, () => {
553
//make sure we still want to search
554
if(searchField.get_text() == timeout_search && searchField.is_searching) {
555
Collection<int> songs;
556
MusicTreeView mtv = (MusicTreeView)sideTree.get_current_widget();
558
songs = lm.songs_from_search(searchField.get_text(), mtv.get_songs());
559
mtv.populateView(songs, true);
566
public virtual void musicTreeViewSearched(string search) {
567
searchField.focus(DirectionType.UP);
568
searchField.set_text(search);
569
searchField.move_cursor(MovementStep.VISUAL_POSITIONS, 1, false);
572
public virtual void sourcesToSongsHandleSet(Gdk.Rectangle rectangle) {
573
settings.setSidebarWidth(rectangle.width);
576
public virtual void on_resize() {
579
this.get_size(out width, out height);
580
settings.setWindowWidth(width);
581
settings.setWindowHeight(height);
584
public virtual void on_quit() {
586
var columns = new ArrayList<TreeViewColumn>();
588
Widget w = sideTree.getWidget(sideTree.get_library_iter());
589
if(w is MusicTreeView) {
590
MusicTreeView view = (MusicTreeView)w;
592
foreach(TreeViewColumn tvc in view.get_columns()) {
596
lm.save_song_list_columns(columns);
599
stdout.printf("Stopping playback\n");
600
player.pause_stream();
602
stdout.printf("Saving songs\n");
605
stdout.printf("Saving playlists\n");
607
lm.save_smart_playlists();
609
stdout.printf("Saving LastFM Info\n");
614
stdout.printf("Bye\n");
617
public virtual void fileSetMusicFolderClick() {
619
var file_chooser = new FileChooserDialog ("Choose Music Folder", this,
620
FileChooserAction.SELECT_FOLDER,
621
Gtk.Stock.CANCEL, ResponseType.CANCEL,
622
Gtk.Stock.OPEN, ResponseType.ACCEPT);
623
if (file_chooser.run () == ResponseType.ACCEPT) {
624
folder = file_chooser.get_filename();
626
file_chooser.destroy ();
629
//topDisplay.set_label_showing(true);
630
topDisplay.set_label_text("Importing music from " + folder);
631
//topDisplay.show_progressbar();
632
//lm.set_music_folder(folder);
637
public virtual void fileRescanMusicFolderClick() {
638
//topDisplay.set_label_showing(true);
639
topDisplay.set_label_text("Rescanning music folder for changes. This may take a while");
640
//topDisplay.show_progressbar();
642
lm.rescan_music_folders();
645
public virtual void musicAdded(LinkedList<string> not_imported) {
648
sideTree.resetView();
649
//topDisplay.set_label_showing(false);
650
topDisplay.show_scale();
651
topDisplay.set_label_text("");
653
//repopulate collection and playlists and reset queue and already played
654
Widget w = sideTree.getWidget(sideTree.get_library_iter());
655
((MusicTreeView)w).populateView(lm.song_ids(), false);
657
if(not_imported.size > 0) {
658
NotImportedWindow nim = new NotImportedWindow(not_imported);
662
if(lm.song_count() != 0)
663
searchField.set_sensitive(true);
665
searchField.set_sensitive(false);
668
public virtual void musicRescanned(LinkedList<string> not_imported) {
669
sideTree.resetView();
670
topDisplay.show_scale();
671
topDisplay.set_label_text("");
673
//repopulate collection and playlists and reset queue and already played
674
Widget w = sideTree.getWidget(sideTree.get_library_iter());
675
((MusicTreeView)w).populateView(lm.song_ids(), false);
677
if(not_imported.size > 0) {
678
NotImportedWindow nim = new NotImportedWindow(not_imported);
682
if(lm.song_count() != 0)
683
searchField.set_sensitive(true);
685
searchField.set_sensitive(false);
688
public virtual void songRemovedFromManager(int id) {
689
if(lm.song_count() != 0)
690
searchField.set_sensitive(true);
692
searchField.set_sensitive(false);
695
public virtual void helpAboutClick() {
696
AboutDialog ad = new AboutDialog();
698
ad.set_program_name("BeatBox");
699
ad.set_version("0.1");
700
ad.set_website("https://launchpad.net/beat-box");
701
ad.set_website_label("Launchpad");
702
ad.set_authors({"Scott Ringwelski"});
704
ad.response.connect( (response_id) => {
711
public virtual void editPreferencesClick() {
712
PreferencesWindow pw = new PreferencesWindow(lm);
716
public virtual void end_of_stream(Song s) {
720
public virtual void current_position_update(int64 position) {
722
if(lm.song_info.song != null) {
723
sec = ((double)position/1000000000);
725
// at about 5 seconds, update last fm. we wait to avoid excessive querying last.fm for info
726
if(position > 5000000000 && !queriedlastfm) {
727
queriedlastfm = true;
729
Thread.create<void*>(lastfm_thread_function, false);
731
catch(GLib.ThreadError err) {
732
stdout.printf("ERROR: Could not create last fm thread: %s \n", err.message);
736
//at 30 seconds in, we consider the song as played
737
if(position > 30000000000 && !song_considered_played) {
738
song_considered_played = true;
740
lm.song_info.song.last_played = (int)time_t();
741
lm.update_song(lm.song_info.song);
743
// add to the already played list
744
lm.add_already_played(lm.song_info.song.rowid);
745
sideTree.updateAlreadyPlayed();
748
// at 90% done with song, add 1 to play count
749
if((sec/(double)lm.song_info.song.length) > 0.90 && !added_to_play_count) {
750
added_to_play_count = true;
751
lm.song_info.song.play_count++;
761
public virtual void topDisplaySliderMoved(ScrollType scroll, double val) {
762
//temporarily disable updates
763
player.current_position_update.disconnect(current_position_update);
765
player.seek_position((int64)(val * 1000000000));
767
//re-enable streamplayer's updates
768
this.player.current_position_update.connect(current_position_update);