1 /* This file is part of Clementine.
2    Copyright 2010, David Sansome <me@davidsansome.com>
3 
4    Clementine is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8 
9    Clementine is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License
15    along with Clementine.  If not, see <http://www.gnu.org/licenses/>.
16 */
17 
18 #include "mainwindow.h"
19 #include "ui_mainwindow.h"
20 
21 #include <cmath>
22 #include <memory>
23 
24 #include <QCloseEvent>
25 #include <QDir>
26 #include <QFileDialog>
27 #include <QFileSystemModel>
28 #include <QLinearGradient>
29 #include <QMenu>
30 #include <QMessageBox>
31 #include <QPainter>
32 #include <QSettings>
33 #include <QShortcut>
34 #include <QSortFilterProxyModel>
35 #include <QStatusBar>
36 #include <QSystemTrayIcon>
37 #include <QTimer>
38 #include <QUndoStack>
39 #include <QtDebug>
40 
41 #include "core/appearance.h"
42 #include "core/application.h"
43 #include "core/backgroundstreams.h"
44 #include "core/commandlineoptions.h"
45 #include "core/database.h"
46 #include "core/deletefiles.h"
47 #include "core/filesystemmusicstorage.h"
48 #include "core/globalshortcuts.h"
49 #include "core/logging.h"
50 #include "core/mac_startup.h"
51 #include "core/mergedproxymodel.h"
52 #include "core/mimedata.h"
53 #include "core/mpris_common.h"
54 #include "core/network.h"
55 #include "core/player.h"
56 #include "core/songloader.h"
57 #include "core/stylesheetloader.h"
58 #include "core/taskmanager.h"
59 #include "core/timeconstants.h"
60 #include "core/utilities.h"
61 #include "devices/devicemanager.h"
62 #include "devices/devicestatefiltermodel.h"
63 #include "devices/deviceview.h"
64 #include "devices/deviceviewcontainer.h"
65 #include "engines/enginebase.h"
66 #include "engines/gstengine.h"
67 #include "globalsearch/globalsearch.h"
68 #include "globalsearch/globalsearchview.h"
69 #include "globalsearch/librarysearchprovider.h"
70 #include "internet/core/internetmodel.h"
71 #include "internet/core/internetview.h"
72 #include "internet/core/internetviewcontainer.h"
73 #include "internet/internetradio/savedradio.h"
74 #include "internet/magnatune/magnatuneservice.h"
75 #include "internet/podcasts/podcastservice.h"
76 #include "library/groupbydialog.h"
77 #include "library/library.h"
78 #include "library/librarybackend.h"
79 #include "library/librarydirectorymodel.h"
80 #include "library/libraryfilterwidget.h"
81 #include "library/libraryviewcontainer.h"
82 #include "musicbrainz/tagfetcher.h"
83 #include "networkremote/networkremote.h"
84 #include "playlist/playlist.h"
85 #include "playlist/playlistbackend.h"
86 #include "playlist/playlistlistcontainer.h"
87 #include "playlist/playlistmanager.h"
88 #include "playlist/playlistsequence.h"
89 #include "playlist/playlistview.h"
90 #include "playlist/queue.h"
91 #include "playlist/queuemanager.h"
92 #include "playlist/songplaylistitem.h"
93 #include "playlistparsers/playlistparser.h"
94 #ifdef HAVE_AUDIOCD
95 #include "ripper/ripcddialog.h"
96 #endif
97 #include "smartplaylists/generator.h"
98 #include "smartplaylists/generatormimedata.h"
99 #include "songinfo/artistinfoview.h"
100 #include "songinfo/songinfoview.h"
101 #include "songinfo/streamdiscoverer.h"
102 #include "transcoder/transcodedialog.h"
103 #include "ui/about.h"
104 #include "ui/addstreamdialog.h"
105 #include "ui/albumcovermanager.h"
106 #include "ui/console.h"
107 #include "ui/edittagdialog.h"
108 #include "ui/equalizer.h"
109 #include "ui/iconloader.h"
110 #include "ui/organisedialog.h"
111 #include "ui/organiseerrordialog.h"
112 #include "ui/qtsystemtrayicon.h"
113 #include "ui/settingsdialog.h"
114 #include "ui/streamdetailsdialog.h"
115 #include "ui/systemtrayicon.h"
116 #include "ui/trackselectiondialog.h"
117 #include "ui/windows7thumbbar.h"
118 #include "version.h"
119 #include "widgets/errordialog.h"
120 #include "widgets/fileview.h"
121 #include "widgets/multiloadingindicator.h"
122 #include "widgets/osd.h"
123 #include "widgets/stylehelper.h"
124 #include "widgets/trackslider.h"
125 
126 #ifdef Q_OS_DARWIN
127 #include "ui/macsystemtrayicon.h"
128 #endif
129 
130 #ifdef HAVE_LIBLASTFM
131 #include "internet/lastfm/lastfmservice.h"
132 #endif
133 
134 #ifdef HAVE_WIIMOTEDEV
135 #include "wiimotedev/shortcuts.h"
136 #endif
137 
138 #ifdef HAVE_VISUALISATIONS
139 #include "visualisations/visualisationcontainer.h"
140 #endif
141 
142 #ifdef HAVE_MOODBAR
143 #include "moodbar/moodbarcontroller.h"
144 #include "moodbar/moodbarproxystyle.h"
145 #endif
146 
147 #include <algorithm>
148 #include <cmath>
149 
150 #ifdef Q_OS_DARWIN
151 // Non exported mac-specific function.
152 void qt_mac_set_dock_menu(QMenu*);
153 #endif
154 
155 const char* MainWindow::kSettingsGroup = "MainWindow";
156 const char* MainWindow::kAllFilesFilterSpec = QT_TR_NOOP("All Files (*)");
157 
158 namespace {
159 const int kTrackSliderUpdateTimeMs = 500;
160 const int kTrackPositionUpdateTimeMs = 1000;
161 }
162 
MainWindow(Application * app,SystemTrayIcon * tray_icon,OSD * osd,const CommandlineOptions & options,QWidget * parent)163 MainWindow::MainWindow(Application* app, SystemTrayIcon* tray_icon, OSD* osd,
164                        const CommandlineOptions& options, QWidget* parent)
165     : QMainWindow(parent),
166       ui_(new Ui_MainWindow),
167       thumbbar_(new Windows7ThumbBar(this)),
168       app_(app),
169       tray_icon_(tray_icon),
170       osd_(osd),
171       edit_tag_dialog_(std::bind(&MainWindow::CreateEditTagDialog, this)),
172       stream_discoverer_(std::bind(&MainWindow::CreateStreamDiscoverer, this)),
173       global_shortcuts_(new GlobalShortcuts(this)),
174       global_search_view_(new GlobalSearchView(app_, this)),
175       library_view_(new LibraryViewContainer(this)),
176       file_view_(new FileView(this)),
177       playlist_list_(new PlaylistListContainer(this)),
178       internet_view_(new InternetViewContainer(this)),
179       device_view_container_(new DeviceViewContainer(this)),
180       device_view_(device_view_container_->view()),
181       song_info_view_(new SongInfoView(this)),
182       artist_info_view_(new ArtistInfoView(this)),
183       settings_dialog_(std::bind(&MainWindow::CreateSettingsDialog, this)),
184       add_stream_dialog_([=]() {
185         AddStreamDialog* add_stream_dialog = new AddStreamDialog;
186         connect(add_stream_dialog, SIGNAL(accepted()), this,
187                 SLOT(AddStreamAccepted()));
188         add_stream_dialog->set_add_on_accept(
189             InternetModel::Service<SavedRadio>());
190         return add_stream_dialog;
191       }),
__anon0b9a2d410302() 192       cover_manager_([=]() {
193         AlbumCoverManager* cover_manager =
194             new AlbumCoverManager(app, app->library_backend());
195         cover_manager->Init();
196 
197         // Cover manager connections
198         connect(cover_manager, SIGNAL(AddToPlaylist(QMimeData*)), this,
199                 SLOT(AddToPlaylist(QMimeData*)));
200         return cover_manager;
201       }),
202       equalizer_(new Equalizer),
__anon0b9a2d410402() 203       organise_dialog_([=]() {
204         OrganiseDialog* dialog = new OrganiseDialog(app->task_manager(),
205                                                     app->library_backend());
206         dialog->SetDestinationModel(app->library()->model()->directory_model());
207         return dialog;
208       }),
__anon0b9a2d410502() 209       queue_manager_([=]() {
210         QueueManager* manager = new QueueManager;
211         manager->SetPlaylistManager(app->playlist_manager());
212         return manager;
213       }),
214       playlist_menu_(new QMenu(this)),
215       playlist_add_to_another_(nullptr),
216       playlistitem_actions_separator_(nullptr),
217       library_sort_model_(new QSortFilterProxyModel(this)),
218       track_position_timer_(new QTimer(this)),
219       track_slider_timer_(new QTimer(this)),
220       initialized_(false),
221       dirty_geometry_(false),
222       dirty_playback_(false),
223       saved_playback_position_(0),
224       saved_playback_state_(Engine::Empty),
225       doubleclick_addmode_(AddBehaviour_Append),
226       doubleclick_playmode_(PlayBehaviour_IfStopped),
227       menu_playmode_(PlayBehaviour_IfStopped) {
228   qLog(Debug) << "Starting";
229 
230   connect(app, SIGNAL(ErrorAdded(QString)), SLOT(ShowErrorDialog(QString)));
231   connect(app, SIGNAL(SettingsDialogRequested(SettingsDialog::Page)),
232           SLOT(OpenSettingsDialogAtPage(SettingsDialog::Page)));
233 
234   // Initialise the UI
235   ui_->setupUi(this);
236 #ifdef Q_OS_DARWIN
237   ui_->menu_help->menuAction()->setVisible(false);
238 #endif
239 
240   ui_->multi_loading_indicator->SetTaskManager(app_->task_manager());
241   ui_->now_playing->SetApplication(app_);
242 
243   int volume = app_->player()->GetVolume();
244   ui_->volume->setValue(volume);
245   VolumeChanged(volume);
246 
247   // Initialise the global search widget
248   StyleHelper::setBaseColor(palette().color(QPalette::Highlight).darker());
249 
250   // Add global search providers
251   app_->global_search()->AddProvider(new LibrarySearchProvider(
252       app_->library_backend(), tr("Library"), "library",
253       IconLoader::Load("folder-sound", IconLoader::Base), true, app_, this));
254 
255   connect(global_search_view_, SIGNAL(AddToPlaylist(QMimeData*)),
256           SLOT(AddToPlaylist(QMimeData*)));
257 
258   // Set up the settings group early
259   settings_.beginGroup(kSettingsGroup);
260 
261   // Add tabs to the fancy tab widget
262   ui_->tabs->addTab(global_search_view_,
263                     IconLoader::Load("search", IconLoader::Base),
264                     tr("Search", "Global search settings dialog title."));
265   ui_->tabs->addTab(library_view_,
266                     IconLoader::Load("folder-sound", IconLoader::Base),
267                     tr("Library"));
268   ui_->tabs->addTab(file_view_,
269                     IconLoader::Load("document-open", IconLoader::Base),
270                     tr("Files"));
271   ui_->tabs->addTab(playlist_list_,
272                     IconLoader::Load("view-media-playlist", IconLoader::Base),
273                     tr("Playlists"));
274   ui_->tabs->addTab(internet_view_,
275                     IconLoader::Load("applications-internet", IconLoader::Base),
276                     tr("Internet"));
277   ui_->tabs->addTab(
278       device_view_container_,
279       IconLoader::Load("multimedia-player-ipod-mini-blue", IconLoader::Base),
280       tr("Devices"));
281   ui_->tabs->addSpacer();
282   ui_->tabs->addTab(song_info_view_,
283                     IconLoader::Load("view-media-lyrics", IconLoader::Base),
284                     tr("Song info"));
285   ui_->tabs->addTab(artist_info_view_,
286                     IconLoader::Load("x-clementine-artist", IconLoader::Base),
287                     tr("Artist info"));
288 
289   // Add the now playing widget to the fancy tab widget
290   ui_->tabs->addBottomWidget(ui_->now_playing);
291 
292   ui_->tabs->setBackgroundPixmap(QPixmap(":/sidebar_background.png"));
293 
294   // Do this only after all default tabs have been added
295   ui_->tabs->loadSettings(settings_);
296 
297   track_position_timer_->setInterval(kTrackPositionUpdateTimeMs);
298   connect(track_position_timer_, SIGNAL(timeout()),
299           SLOT(UpdateTrackPosition()));
300   track_slider_timer_->setInterval(kTrackSliderUpdateTimeMs);
301   connect(track_slider_timer_, SIGNAL(timeout()),
302           SLOT(UpdateTrackSliderPosition()));
303 
304   connect(app_, SIGNAL(SaveSettings(QSettings*)),
305           SLOT(SaveSettings(QSettings*)));
306 
307   // Start initialising the player
308   qLog(Debug) << "Initialising player";
309   app_->player()->Init();
310   background_streams_ = new BackgroundStreams(app_->player()->engine(), this);
311   background_streams_->LoadStreams();
312 
313   // Models
314   qLog(Debug) << "Creating models";
315   library_sort_model_->setSourceModel(app_->library()->model());
316   library_sort_model_->setSortRole(LibraryModel::Role_SortText);
317   library_sort_model_->setDynamicSortFilter(true);
318   library_sort_model_->setSortLocaleAware(true);
319   library_sort_model_->sort(0);
320 
321   connect(ui_->playlist, SIGNAL(ViewSelectionModelChanged()),
322           SLOT(PlaylistViewSelectionModelChanged()));
323   ui_->playlist->SetApplication(app_);
324 
325   library_view_->view()->setModel(library_sort_model_);
326   library_view_->view()->SetApplication(app_);
327   internet_view_->SetApplication(app_);
328   device_view_->SetApplication(app_);
329   playlist_list_->SetApplication(app_);
330 
331   // Icons
332   qLog(Debug) << "Creating UI";
333   ui_->action_about->setIcon(IconLoader::Load("help-about", IconLoader::Base));
334   ui_->action_about_qt->setIcon(IconLoader::Load("qtlogo", IconLoader::Base));
335   ui_->action_add_file->setIcon(
336       IconLoader::Load("document-open", IconLoader::Base));
337   ui_->action_add_folder->setIcon(
338       IconLoader::Load("document-open-folder", IconLoader::Base));
339   ui_->action_add_stream->setIcon(
340       IconLoader::Load("document-open-remote", IconLoader::Base));
341   ui_->action_add_podcast->setIcon(
342       IconLoader::Load("podcast", IconLoader::Provider));
343   ui_->action_clear_playlist->setIcon(
344       IconLoader::Load("edit-clear-list", IconLoader::Base));
345   ui_->action_configure->setIcon(
346       IconLoader::Load("configure", IconLoader::Base));
347   ui_->action_cover_manager->setIcon(
348       IconLoader::Load("download", IconLoader::Base));
349   ui_->action_edit_track->setIcon(
350       IconLoader::Load("edit-rename", IconLoader::Base));
351   ui_->action_equalizer->setIcon(
352       IconLoader::Load("view-media-equalizer", IconLoader::Base));
353   ui_->action_jump->setIcon(IconLoader::Load("go-jump", IconLoader::Base));
354   ui_->action_next_track->setIcon(
355       IconLoader::Load("media-skip-forward", IconLoader::Base));
356   ui_->action_open_media->setIcon(
357       IconLoader::Load("document-open", IconLoader::Base));
358   ui_->action_open_cd->setIcon(
359       IconLoader::Load("media-optical", IconLoader::Base));
360   ui_->action_play_pause->setIcon(
361       IconLoader::Load("media-playback-start", IconLoader::Base));
362   ui_->action_previous_track->setIcon(
363       IconLoader::Load("media-skip-backward", IconLoader::Base));
364   ui_->action_mute->setIcon(
365       IconLoader::Load("audio-volume-muted", IconLoader::Base));
366   ui_->action_quit->setIcon(
367       IconLoader::Load("application-exit", IconLoader::Base));
368   ui_->action_remove_from_playlist->setIcon(
369       IconLoader::Load("list-remove", IconLoader::Base));
370   ui_->action_repeat_mode->setIcon(
371       IconLoader::Load("media-playlist-repeat", IconLoader::Base));
372   ui_->action_rip_audio_cd->setIcon(
373       IconLoader::Load("media-optical", IconLoader::Base));
374   ui_->action_shuffle->setIcon(
375       IconLoader::Load("x-clementine-shuffle", IconLoader::Base));
376   ui_->action_shuffle_mode->setIcon(
377       IconLoader::Load("media-playlist-shuffle", IconLoader::Base));
378   ui_->action_stop->setIcon(
379       IconLoader::Load("media-playback-stop", IconLoader::Base));
380   ui_->action_stop_after_this_track->setIcon(
381       IconLoader::Load("media-playback-stop", IconLoader::Base));
382   ui_->action_new_playlist->setIcon(
383       IconLoader::Load("document-new", IconLoader::Base));
384   ui_->action_load_playlist->setIcon(
385       IconLoader::Load("document-open", IconLoader::Base));
386   ui_->action_save_playlist->setIcon(
387       IconLoader::Load("document-save", IconLoader::Base));
388   ui_->action_full_library_scan->setIcon(
389       IconLoader::Load("view-refresh", IconLoader::Base));
390   ui_->action_rain->setIcon(
391       IconLoader::Load("weather-showers-scattered", IconLoader::Base));
392   ui_->action_hypnotoad->setIcon(
393       IconLoader::Load("hypnotoad", IconLoader::Base));
394   ui_->action_kittens->setIcon(IconLoader::Load("kittens", IconLoader::Base));
395   ui_->action_enterprise->setIcon(
396       IconLoader::Load("enterprise", IconLoader::Base));
397   ui_->action_love->setIcon(IconLoader::Load("love", IconLoader::Lastfm));
398 
399   // File view connections
400   connect(file_view_, SIGNAL(AddToPlaylist(QMimeData*)),
401           SLOT(AddToPlaylist(QMimeData*)));
402   connect(file_view_, SIGNAL(PathChanged(QString)),
403           SLOT(FilePathChanged(QString)));
404   connect(file_view_, SIGNAL(CopyToLibrary(QList<QUrl>)),
405           SLOT(CopyFilesToLibrary(QList<QUrl>)));
406   connect(file_view_, SIGNAL(MoveToLibrary(QList<QUrl>)),
407           SLOT(MoveFilesToLibrary(QList<QUrl>)));
408   connect(file_view_, SIGNAL(EditTags(QList<QUrl>)),
409           SLOT(EditFileTags(QList<QUrl>)));
410   connect(file_view_, SIGNAL(CopyToDevice(QList<QUrl>)),
411           SLOT(CopyFilesToDevice(QList<QUrl>)));
412   file_view_->SetTaskManager(app_->task_manager());
413 
414   // Action connections
415   connect(ui_->action_next_track, SIGNAL(triggered()), app_->player(),
416           SLOT(Next()));
417   connect(ui_->action_previous_track, SIGNAL(triggered()), app_->player(),
418           SLOT(Previous()));
419   connect(ui_->action_play_pause, SIGNAL(triggered()), app_->player(),
420           SLOT(PlayPause()));
421   connect(ui_->action_stop, SIGNAL(triggered()), app_->player(), SLOT(Stop()));
422   connect(ui_->action_quit, SIGNAL(triggered()), SLOT(Exit()));
423   connect(ui_->action_stop_after_this_track, SIGNAL(triggered()),
424           SLOT(StopAfterCurrent()));
425   connect(ui_->action_mute, SIGNAL(triggered()), app_->player(), SLOT(Mute()));
426 #ifdef HAVE_LIBLASTFM
427   connect(ui_->action_love, SIGNAL(triggered()), SLOT(Love()));
428   connect(ui_->action_toggle_scrobbling, SIGNAL(triggered()), app_->scrobbler(),
429           SLOT(ToggleScrobbling()));
430 #endif
431 
432   connect(ui_->action_clear_playlist, SIGNAL(triggered()),
433           app_->playlist_manager(), SLOT(ClearCurrent()));
434   connect(ui_->action_remove_duplicates, SIGNAL(triggered()),
435           app_->playlist_manager(), SLOT(RemoveDuplicatesCurrent()));
436   connect(ui_->action_remove_unavailable, SIGNAL(triggered()),
437           app_->playlist_manager(), SLOT(RemoveUnavailableCurrent()));
438   connect(ui_->action_remove_from_playlist, SIGNAL(triggered()),
439           SLOT(PlaylistRemoveCurrent()));
440   connect(ui_->action_toggle_show_sidebar, SIGNAL(toggled(bool)),
441           ui_->sidebar_layout, SLOT(setVisible(bool)));
442   connect(ui_->action_edit_track, SIGNAL(triggered()), SLOT(EditTracks()));
443   connect(ui_->action_renumber_tracks, SIGNAL(triggered()),
444           SLOT(RenumberTracks()));
445   connect(ui_->action_selection_set_value, SIGNAL(triggered()),
446           SLOT(SelectionSetValue()));
447   connect(ui_->action_edit_value, SIGNAL(triggered()), SLOT(EditValue()));
448   connect(ui_->action_auto_complete_tags, SIGNAL(triggered()),
449           SLOT(AutoCompleteTags()));
450   connect(ui_->action_configure, SIGNAL(triggered()),
451           SLOT(OpenSettingsDialog()));
452   connect(ui_->action_about, SIGNAL(triggered()), SLOT(ShowAboutDialog()));
453   connect(ui_->action_about_qt, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
454   connect(ui_->action_shuffle, SIGNAL(triggered()), app_->playlist_manager(),
455           SLOT(ShuffleCurrent()));
456   connect(ui_->action_open_media, SIGNAL(triggered()), SLOT(AddFile()));
457   connect(ui_->action_open_cd, SIGNAL(triggered()), SLOT(AddCDTracks()));
458 #ifdef HAVE_AUDIOCD
459   connect(ui_->action_rip_audio_cd, SIGNAL(triggered()),
460           SLOT(OpenRipCDDialog()));
461 #else
462   ui_->action_rip_audio_cd->setVisible(false);
463 #endif
464   connect(ui_->action_add_file, SIGNAL(triggered()), SLOT(AddFile()));
465   connect(ui_->action_add_folder, SIGNAL(triggered()), SLOT(AddFolder()));
466   connect(ui_->action_add_stream, SIGNAL(triggered()), SLOT(AddStream()));
467   connect(ui_->action_add_podcast, SIGNAL(triggered()), SLOT(AddPodcast()));
468   connect(ui_->action_cover_manager, SIGNAL(triggered()),
469           SLOT(ShowCoverManager()));
470   connect(ui_->action_equalizer, SIGNAL(triggered()), equalizer_.get(),
471           SLOT(show()));
472   connect(ui_->action_transcode, SIGNAL(triggered()),
473           SLOT(ShowTranscodeDialog()));
474   connect(ui_->action_jump, SIGNAL(triggered()), ui_->playlist->view(),
475           SLOT(JumpToCurrentlyPlayingTrack()));
476   connect(ui_->action_update_library, SIGNAL(triggered()), app_->library(),
477           SLOT(IncrementalScan()));
478   connect(ui_->action_full_library_scan, SIGNAL(triggered()), app_->library(),
479           SLOT(FullScan()));
480   connect(ui_->action_queue_manager, SIGNAL(triggered()),
481           SLOT(ShowQueueManager()));
482   connect(ui_->action_add_files_to_transcoder, SIGNAL(triggered()),
483           SLOT(AddFilesToTranscoder()));
484   connect(ui_->action_view_stream_details, SIGNAL(triggered()),
485           SLOT(DiscoverStreamDetails()));
486 
487   background_streams_->AddAction("Rain", ui_->action_rain);
488   background_streams_->AddAction("Hypnotoad", ui_->action_hypnotoad);
489   background_streams_->AddAction("Make it so!", ui_->action_enterprise);
490 
491   // Playlist view actions
492   ui_->action_next_playlist->setShortcuts(
493       QList<QKeySequence>()
494       << QKeySequence::fromString("Ctrl+Tab")
495 #ifdef Q_OS_DARWIN
496       // On OS X "Ctrl+Tab" == Cmd + Tab but this shorcut
497       // is already used by default for switching between
498       // applications.
499       // I would have preferred to use Meta+Tab (which
500       // means Ctrl+Tab on OS X), like in Firefox or
501       // Chrome, but this doesn't work (probably at Qt bug)
502       // and some applications (e.g. Qt creator) uses
503       // Alt+Tab too so I believe it's a good shorcut anyway
504       << QKeySequence::fromString("Alt+Tab")
505 #endif  // Q_OS_DARWIN
506       << QKeySequence::fromString("Ctrl+PgDown"));
507   ui_->action_previous_playlist->setShortcuts(
508       QList<QKeySequence>() << QKeySequence::fromString("Ctrl+Shift+Tab")
509 #ifdef Q_OS_DARWIN
510                             << QKeySequence::fromString("Alt+Shift+Tab")
511 #endif  // Q_OS_DARWIN
512                             << QKeySequence::fromString("Ctrl+PgUp"));
513   // Actions for switching tabs will be global to the entire window, so adding
514   // them here
515   addAction(ui_->action_next_playlist);
516   addAction(ui_->action_previous_playlist);
517 
518   // Give actions to buttons
519   ui_->forward_button->setDefaultAction(ui_->action_next_track);
520   ui_->back_button->setDefaultAction(ui_->action_previous_track);
521   ui_->pause_play_button->setDefaultAction(ui_->action_play_pause);
522   ui_->stop_button->setDefaultAction(ui_->action_stop);
523   ui_->love_button->setDefaultAction(ui_->action_love);
524   ui_->scrobbling_button->setDefaultAction(ui_->action_toggle_scrobbling);
525   ui_->clear_playlist_button->setDefaultAction(ui_->action_clear_playlist);
526   ui_->playlist->SetActions(
527       ui_->action_new_playlist, ui_->action_load_playlist,
528       ui_->action_save_playlist,
529       ui_->action_next_playlist, /* These two actions aren't associated */
530       ui_->action_previous_playlist /* to a button but to the main window */);
531 
532 #ifdef HAVE_VISUALISATIONS
533   connect(ui_->action_visualisations, SIGNAL(triggered()),
534           SLOT(ShowVisualisations()));
535 #else
536   ui_->action_visualisations->setEnabled(false);
537 #endif
538 
539   // Add the shuffle and repeat action groups to the menu
540   ui_->action_shuffle_mode->setMenu(ui_->playlist_sequence->shuffle_menu());
541   ui_->action_repeat_mode->setMenu(ui_->playlist_sequence->repeat_menu());
542 
543   // Stop actions
544   QMenu* stop_menu = new QMenu(this);
545   stop_menu->addAction(ui_->action_stop);
546   stop_menu->addAction(ui_->action_stop_after_this_track);
547   ui_->stop_button->setMenu(stop_menu);
548 
549   // Player connections
550   connect(ui_->volume, SIGNAL(valueChanged(int)), app_->player(),
551           SLOT(SetVolume(int)));
552 
553   connect(app_->player(), SIGNAL(Error(QString)),
554           SLOT(ShowErrorDialog(QString)));
555   connect(app_->player(), SIGNAL(SongChangeRequestProcessed(QUrl, bool)),
556           app_->playlist_manager(),
557           SLOT(SongChangeRequestProcessed(QUrl, bool)));
558 
559   connect(app_->player(), SIGNAL(Paused()), SLOT(MediaPaused()));
560   connect(app_->player(), SIGNAL(Playing()), SLOT(MediaPlaying()));
561   connect(app_->player(), SIGNAL(Stopped()), SLOT(MediaStopped()));
562   connect(app_->player(), SIGNAL(Seeked(qlonglong)), SLOT(Seeked(qlonglong)));
563   connect(app_->player(), SIGNAL(TrackSkipped(PlaylistItemPtr)),
564           SLOT(TrackSkipped(PlaylistItemPtr)));
565   connect(this, SIGNAL(IntroPointReached()), app_->player(),
566           SLOT(IntroPointReached()));
567   connect(app_->player(), SIGNAL(VolumeChanged(int)), SLOT(VolumeChanged(int)));
568 
569   connect(app_->player(), SIGNAL(Paused()), ui_->playlist,
570           SLOT(ActivePaused()));
571   connect(app_->player(), SIGNAL(Playing()), ui_->playlist,
572           SLOT(ActivePlaying()));
573   connect(app_->player(), SIGNAL(Stopped()), ui_->playlist,
574           SLOT(ActiveStopped()));
575 
576   connect(app_->player(), SIGNAL(Paused()), osd_, SLOT(Paused()));
577   connect(app_->player(), SIGNAL(Stopped()), osd_, SLOT(Stopped()));
578   connect(app_->player(), SIGNAL(PlaylistFinished()), osd_,
579           SLOT(PlaylistFinished()));
580   connect(app_->player(), SIGNAL(VolumeChanged(int)), osd_,
581           SLOT(VolumeChanged(int)));
582   connect(app_->player(), SIGNAL(VolumeChanged(int)), ui_->volume,
583           SLOT(setValue(int)));
584   connect(app_->player(), SIGNAL(ForceShowOSD(Song, bool)),
585           SLOT(ForceShowOSD(Song, bool)));
586   connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)),
587           SLOT(SongChanged(Song)));
588   connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)),
589           app_->player(), SLOT(CurrentMetadataChanged(Song)));
590   connect(app_->playlist_manager(), SIGNAL(EditingFinished(QModelIndex)),
591           SLOT(PlaylistEditFinished(QModelIndex)));
592   connect(app_->playlist_manager(), SIGNAL(Error(QString)),
593           SLOT(ShowErrorDialog(QString)));
594   connect(app_->playlist_manager(), SIGNAL(SummaryTextChanged(QString)),
595           ui_->playlist_summary, SLOT(setText(QString)));
596   connect(app_->playlist_manager(), SIGNAL(PlayRequested(QModelIndex)),
597           SLOT(PlayIndex(QModelIndex)));
598 
599   connect(ui_->playlist->view(), SIGNAL(doubleClicked(QModelIndex)),
600           SLOT(PlaylistDoubleClick(QModelIndex)));
601   connect(ui_->playlist->view(), SIGNAL(PlayItem(QModelIndex)),
602           SLOT(PlayIndex(QModelIndex)));
603   connect(ui_->playlist->view(), SIGNAL(PlayPause()), app_->player(),
604           SLOT(PlayPause()));
605   connect(ui_->playlist->view(), SIGNAL(RightClicked(QPoint, QModelIndex)),
606           SLOT(PlaylistRightClick(QPoint, QModelIndex)));
607   connect(ui_->playlist->view(), SIGNAL(SeekForward()), app_->player(),
608           SLOT(SeekForward()));
609   connect(ui_->playlist->view(), SIGNAL(SeekBackward()), app_->player(),
610           SLOT(SeekBackward()));
611   connect(ui_->playlist->view(), SIGNAL(BackgroundPropertyChanged()),
612           SLOT(RefreshStyleSheet()));
613 
614   connect(ui_->track_slider, SIGNAL(ValueChangedSeconds(int)), app_->player(),
615           SLOT(SeekTo(int)));
616   connect(ui_->track_slider, SIGNAL(SeekForward()), app_->player(),
617           SLOT(SeekForward()));
618   connect(ui_->track_slider, SIGNAL(SeekBackward()), app_->player(),
619           SLOT(SeekBackward()));
620 
621   connect(ui_->track_slider, SIGNAL(Previous()), app_->player(),
622           SLOT(Previous()));
623   connect(ui_->track_slider, SIGNAL(Next()), app_->player(), SLOT(Next()));
624 
625   // Library connections
626   connect(library_view_->view(), SIGNAL(AddToPlaylistSignal(QMimeData*)),
627           SLOT(AddToPlaylist(QMimeData*)));
628   connect(library_view_->view(), SIGNAL(ShowConfigDialog()),
629           SLOT(ShowLibraryConfig()));
630   connect(app_->library_model(), SIGNAL(TotalSongCountUpdated(int)),
631           library_view_->view(), SLOT(TotalSongCountUpdated(int)));
632   connect(app_->library_model(), SIGNAL(modelAboutToBeReset()),
633           library_view_->view(), SLOT(SaveFocus()));
634   connect(app_->library_model(), SIGNAL(modelReset()), library_view_->view(),
635           SLOT(RestoreFocus()));
636 
637   connect(app_->task_manager(), SIGNAL(PauseLibraryWatchers()), app_->library(),
638           SLOT(PauseWatcher()));
639   connect(app_->task_manager(), SIGNAL(ResumeLibraryWatchers()),
640           app_->library(), SLOT(ResumeWatcher()));
641 
642   // Devices connections
643   connect(device_view_, SIGNAL(AddToPlaylistSignal(QMimeData*)),
644           SLOT(AddToPlaylist(QMimeData*)));
645 
646   // Library filter widget
647   QActionGroup* library_view_group = new QActionGroup(this);
648 
649   library_show_all_ = library_view_group->addAction(tr("Show all songs"));
650   library_show_duplicates_ =
651       library_view_group->addAction(tr("Show only duplicates"));
652   library_show_untagged_ =
653       library_view_group->addAction(tr("Show only untagged"));
654 
655   library_show_all_->setCheckable(true);
656   library_show_duplicates_->setCheckable(true);
657   library_show_untagged_->setCheckable(true);
658   library_show_all_->setChecked(true);
659 
660   connect(library_view_group, SIGNAL(triggered(QAction*)),
661           SLOT(ChangeLibraryQueryMode(QAction*)));
662 
663   QAction* library_config_action =
664       new QAction(IconLoader::Load("configure", IconLoader::Base),
665                   tr("Configure library..."), this);
666   connect(library_config_action, SIGNAL(triggered()),
667           SLOT(ShowLibraryConfig()));
668   library_view_->filter()->SetSettingsGroup(kSettingsGroup);
669   library_view_->filter()->SetLibraryModel(app_->library()->model());
670 
671   QAction* separator = new QAction(this);
672   separator->setSeparator(true);
673 
674   library_view_->filter()->AddMenuAction(library_show_all_);
675   library_view_->filter()->AddMenuAction(library_show_duplicates_);
676   library_view_->filter()->AddMenuAction(library_show_untagged_);
677   library_view_->filter()->AddMenuAction(separator);
678   library_view_->filter()->AddMenuAction(library_config_action);
679 
680   // Playlist menu
681   playlist_play_pause_ =
682       playlist_menu_->addAction(tr("Play"), this, SLOT(PlaylistPlay()));
683   playlist_menu_->addAction(ui_->action_stop);
684   playlist_stop_after_ = playlist_menu_->addAction(
685       IconLoader::Load("media-playback-stop", IconLoader::Base),
686       tr("Stop after this track"), this, SLOT(PlaylistStopAfter()));
687   playlist_queue_ = playlist_menu_->addAction("", this, SLOT(PlaylistQueue()));
688   playlist_queue_->setShortcut(QKeySequence("Ctrl+D"));
689   ui_->playlist->addAction(playlist_queue_);
690   playlist_queue_play_next_ =
691       playlist_menu_->addAction("", this, SLOT(PlaylistQueuePlayNext()));
692   playlist_queue_play_next_->setShortcut(QKeySequence("Ctrl+Shift+D"));
693   ui_->playlist->addAction(playlist_queue_play_next_);
694   playlist_skip_ = playlist_menu_->addAction("", this, SLOT(PlaylistSkip()));
695   ui_->playlist->addAction(playlist_skip_);
696   playlist_menu_->addSeparator();
697   search_for_artist_ = playlist_menu_->addAction(
698       IconLoader::Load("system-search", IconLoader::Base),
699       tr("Search for artist"), this, SLOT(SearchForArtist()));
700   search_for_album_ = playlist_menu_->addAction(
701       IconLoader::Load("system-search", IconLoader::Base),
702       tr("Search for album"), this, SLOT(SearchForAlbum()));
703   playlist_menu_->addSeparator();
704   playlist_menu_->addAction(ui_->action_remove_from_playlist);
705   playlist_undoredo_ = playlist_menu_->addSeparator();
706   playlist_menu_->addAction(ui_->action_edit_track);
707   playlist_menu_->addAction(ui_->action_view_stream_details);
708   playlist_menu_->addAction(ui_->action_edit_value);
709   playlist_menu_->addAction(ui_->action_renumber_tracks);
710   playlist_menu_->addAction(ui_->action_selection_set_value);
711   playlist_menu_->addAction(ui_->action_auto_complete_tags);
712   playlist_menu_->addAction(ui_->action_add_files_to_transcoder);
713   playlist_menu_->addSeparator();
714   playlist_copy_to_library_ = playlist_menu_->addAction(
715       IconLoader::Load("edit-copy", IconLoader::Base), tr("Copy to library..."),
716       this, SLOT(PlaylistCopyToLibrary()));
717   playlist_move_to_library_ = playlist_menu_->addAction(
718       IconLoader::Load("go-jump", IconLoader::Base), tr("Move to library..."),
719       this, SLOT(PlaylistMoveToLibrary()));
720   playlist_organise_ = playlist_menu_->addAction(
721       IconLoader::Load("edit-copy", IconLoader::Base), tr("Organise files..."),
722       this, SLOT(PlaylistMoveToLibrary()));
723   playlist_copy_to_device_ = playlist_menu_->addAction(
724       IconLoader::Load("multimedia-player-ipod-mini-blue", IconLoader::Base),
725       tr("Copy to device..."), this, SLOT(PlaylistCopyToDevice()));
726   playlist_delete_ = playlist_menu_->addAction(
727       IconLoader::Load("edit-delete", IconLoader::Base),
728       tr("Delete from disk..."), this, SLOT(PlaylistDelete()));
729   playlist_open_in_browser_ = playlist_menu_->addAction(
730       IconLoader::Load("document-open-folder", IconLoader::Base),
731       tr("Show in file browser..."), this, SLOT(PlaylistOpenInBrowser()));
732   playlist_show_in_library_ = playlist_menu_->addAction(
733       IconLoader::Load("edit-find", IconLoader::Base), tr("Show in library..."),
734       this, SLOT(ShowInLibrary()));
735   playlist_menu_->addSeparator();
736   playlistitem_actions_separator_ = playlist_menu_->addSeparator();
737   playlist_menu_->addAction(ui_->action_clear_playlist);
738   playlist_menu_->addAction(ui_->action_shuffle);
739   playlist_menu_->addAction(ui_->action_remove_duplicates);
740   playlist_menu_->addAction(ui_->action_remove_unavailable);
741 
742 #ifdef Q_OS_DARWIN
743   ui_->action_shuffle->setShortcut(QKeySequence());
744 #endif
745 
746   // We have to add the actions on the playlist menu to this QWidget otherwise
747   // their shortcut keys don't work
748   addActions(playlist_menu_->actions());
749 
750   connect(ui_->playlist, SIGNAL(UndoRedoActionsChanged(QAction*, QAction*)),
751           SLOT(PlaylistUndoRedoChanged(QAction*, QAction*)));
752 
753   playlist_copy_to_device_->setDisabled(
754       app_->device_manager()->connected_devices_model()->rowCount() == 0);
755   connect(app_->device_manager()->connected_devices_model(),
756           SIGNAL(IsEmptyChanged(bool)), playlist_copy_to_device_,
757           SLOT(setDisabled(bool)));
758 
759   // Global search shortcut
760   QAction* global_search_action = new QAction(this);
761   global_search_action->setShortcuts(QList<QKeySequence>()
762                                      << QKeySequence("Ctrl+F")
763                                      << QKeySequence("Ctrl+L"));
764   addAction(global_search_action);
765   connect(global_search_action, SIGNAL(triggered()),
766           SLOT(FocusGlobalSearchField()));
767 
768   // Internet connections
769   connect(app_->internet_model(), SIGNAL(StreamError(QString)),
770           SLOT(ShowErrorDialog(QString)));
771   connect(app_->internet_model(), SIGNAL(StreamMetadataFound(QUrl, Song)),
772           app_->playlist_manager(), SLOT(SetActiveStreamMetadata(QUrl, Song)));
773   connect(app_->internet_model(), SIGNAL(AddToPlaylist(QMimeData*)),
774           SLOT(AddToPlaylist(QMimeData*)));
775   connect(app_->internet_model(), SIGNAL(ScrollToIndex(QModelIndex)),
776           SLOT(ScrollToInternetIndex(QModelIndex)));
777 #ifdef HAVE_LIBLASTFM
778   connect(app_->scrobbler(), SIGNAL(ButtonVisibilityChanged(bool)),
779           SLOT(LastFMButtonVisibilityChanged(bool)));
780   connect(app_->scrobbler(), SIGNAL(ScrobbleButtonVisibilityChanged(bool)),
781           SLOT(ScrobbleButtonVisibilityChanged(bool)));
782   connect(app_->scrobbler(), SIGNAL(ScrobblingEnabledChanged(bool)),
783           SLOT(ScrobblingEnabledChanged(bool)));
784   connect(app_->scrobbler(), SIGNAL(ScrobbledRadioStream()),
785           SLOT(ScrobbledRadioStream()));
786 #endif
787   connect(app_->internet_model()->Service<MagnatuneService>(),
788           SIGNAL(DownloadFinished(QStringList)), osd_,
789           SLOT(MagnatuneDownloadFinished(QStringList)));
790   connect(internet_view_->tree(), SIGNAL(AddToPlaylistSignal(QMimeData*)),
791           SLOT(AddToPlaylist(QMimeData*)));
792 
793   // Connections to the saved streams service
794   connect(InternetModel::Service<SavedRadio>(), SIGNAL(ShowAddStreamDialog()),
795           SLOT(AddStream()));
796 
797 #ifdef Q_OS_DARWIN
798   mac::SetApplicationHandler(this);
799 #endif
800   // Tray icon
801   if (tray_icon_) {
802     tray_icon_->SetupMenu(ui_->action_previous_track, ui_->action_play_pause,
803                           ui_->action_stop, ui_->action_stop_after_this_track,
804                           ui_->action_next_track, ui_->action_mute,
805                           ui_->action_love, ui_->action_quit);
806     connect(tray_icon_, SIGNAL(PlayPause()), app_->player(), SLOT(PlayPause()));
807     connect(tray_icon_, SIGNAL(SeekForward()), app_->player(),
808             SLOT(SeekForward()));
809     connect(tray_icon_, SIGNAL(SeekBackward()), app_->player(),
810             SLOT(SeekBackward()));
811     connect(tray_icon_, SIGNAL(NextTrack()), app_->player(), SLOT(Next()));
812     connect(tray_icon_, SIGNAL(PreviousTrack()), app_->player(),
813             SLOT(Previous()));
814     connect(tray_icon_, SIGNAL(ShowHide()), SLOT(ToggleShowHide()));
815     connect(tray_icon_, SIGNAL(ChangeVolume(int)), SLOT(VolumeWheelEvent(int)));
816   }
817 
818   // Windows 7 thumbbar buttons
819   thumbbar_->SetActions(QList<QAction*>()
820                         << ui_->action_previous_track << ui_->action_play_pause
821                         << ui_->action_stop << ui_->action_next_track
822                         << nullptr  // spacer
823                         << ui_->action_love);
824 
825 #if (defined(Q_OS_DARWIN) && defined(HAVE_SPARKLE)) || defined(Q_OS_WIN32)
826   // Add check for updates item to application menu.
827   QAction* check_updates =
828       ui_->menu_tools->addAction(tr("Check for updates..."));
829   check_updates->setMenuRole(QAction::ApplicationSpecificRole);
830   connect(check_updates, SIGNAL(triggered(bool)), SLOT(CheckForUpdates()));
831 #endif
832 
833   // Global shortcuts
834   connect(global_shortcuts_, SIGNAL(Play()), app_->player(), SLOT(Play()));
835   connect(global_shortcuts_, SIGNAL(Pause()), app_->player(), SLOT(Pause()));
836   connect(global_shortcuts_, SIGNAL(PlayPause()), ui_->action_play_pause,
837           SLOT(trigger()));
838   connect(global_shortcuts_, SIGNAL(Stop()), ui_->action_stop, SLOT(trigger()));
839   connect(global_shortcuts_, SIGNAL(StopAfter()),
840           ui_->action_stop_after_this_track, SLOT(trigger()));
841   connect(global_shortcuts_, SIGNAL(Next()), ui_->action_next_track,
842           SLOT(trigger()));
843   connect(global_shortcuts_, SIGNAL(Previous()), ui_->action_previous_track,
844           SLOT(trigger()));
845   connect(global_shortcuts_, SIGNAL(IncVolume()), app_->player(),
846           SLOT(VolumeUp()));
847   connect(global_shortcuts_, SIGNAL(DecVolume()), app_->player(),
848           SLOT(VolumeDown()));
849   connect(global_shortcuts_, SIGNAL(Mute()), app_->player(), SLOT(Mute()));
850   connect(global_shortcuts_, SIGNAL(SeekForward()), app_->player(),
851           SLOT(SeekForward()));
852   connect(global_shortcuts_, SIGNAL(SeekBackward()), app_->player(),
853           SLOT(SeekBackward()));
854   connect(global_shortcuts_, SIGNAL(ShowHide()), SLOT(ToggleShowHide()));
855   connect(global_shortcuts_, SIGNAL(ShowOSD()), app_->player(),
856           SLOT(ShowOSD()));
857   connect(global_shortcuts_, SIGNAL(TogglePrettyOSD()), app_->player(),
858           SLOT(TogglePrettyOSD()));
859 #ifdef HAVE_LIBLASTFM
860   connect(global_shortcuts_, SIGNAL(ToggleScrobbling()), app_->scrobbler(),
861           SLOT(ToggleScrobbling()));
862   connect(global_shortcuts_, SIGNAL(Love()), app_->scrobbler(), SLOT(Love()));
863   connect(global_shortcuts_, SIGNAL(Ban()), app_->scrobbler(), SLOT(Ban()));
864 #endif
865 
866   connect(global_shortcuts_, SIGNAL(RateCurrentSong(int)),
867           app_->playlist_manager(), SLOT(RateCurrentSong(int)));
868   connect(global_shortcuts_, SIGNAL(RemoveCurrentSong()),
869           app_->playlist_manager(), SLOT(RemoveCurrentSong()));
870 
871   // Lyrics
872   ConnectInfoView(song_info_view_);
873   ConnectInfoView(artist_info_view_);
874 
875   // Analyzer
876   ui_->analyzer->SetEngine(app_->player()->engine());
877   ui_->analyzer->SetActions(ui_->action_visualisations);
878   connect(ui_->analyzer, SIGNAL(WheelEvent(int)), SLOT(VolumeWheelEvent(int)));
879 
880   // Equalizer
881   qLog(Debug) << "Creating equalizer";
882   connect(equalizer_.get(), SIGNAL(ParametersChanged(int, QList<int>)),
883           app_->player()->engine(),
884           SLOT(SetEqualizerParameters(int, QList<int>)));
885   connect(equalizer_.get(), SIGNAL(EnabledChanged(bool)),
886           app_->player()->engine(), SLOT(SetEqualizerEnabled(bool)));
887   connect(equalizer_.get(), SIGNAL(StereoBalanceChanged(float)),
888           app_->player()->engine(), SLOT(SetStereoBalance(float)));
889   app_->player()->engine()->SetEqualizerEnabled(equalizer_->is_enabled());
890   app_->player()->engine()->SetEqualizerParameters(equalizer_->preamp_value(),
891                                                    equalizer_->gain_values());
892   app_->player()->engine()->SetStereoBalance(equalizer_->stereo_balance());
893 
894   // Statusbar widgets
895   ui_->playlist_summary->setMinimumWidth(
896       QFontMetrics(font()).width("WW selected of WW tracks - [ WW:WW ]"));
897   ui_->status_bar_stack->setCurrentWidget(ui_->playlist_summary_page);
898   connect(ui_->multi_loading_indicator, SIGNAL(TaskCountChange(int)),
899           SLOT(TaskCountChanged(int)));
900 
901   ui_->track_slider->SetApplication(app);
902 #ifdef HAVE_MOODBAR
903   // Moodbar connections
904   connect(app_->moodbar_controller(),
905           SIGNAL(CurrentMoodbarDataChanged(QByteArray)),
906           ui_->track_slider->moodbar_style(), SLOT(SetMoodbarData(QByteArray)));
907 #endif
908 
909   // Now playing widget
910   qLog(Debug) << "Creating now playing widget";
911   ui_->now_playing->set_ideal_height(ui_->status_bar->sizeHint().height() +
912                                      ui_->player_controls->sizeHint().height());
913   connect(app_->player(), SIGNAL(Stopped()), ui_->now_playing, SLOT(Stopped()));
914   connect(ui_->now_playing, SIGNAL(ShowAboveStatusBarChanged(bool)),
915           SLOT(NowPlayingWidgetPositionChanged(bool)));
916   connect(ui_->action_hypnotoad, SIGNAL(toggled(bool)), ui_->now_playing,
917           SLOT(AllHail(bool)));
918   connect(ui_->action_kittens, SIGNAL(toggled(bool)), ui_->now_playing,
919           SLOT(EnableKittens(bool)));
920   connect(ui_->action_kittens, SIGNAL(toggled(bool)), app_->network_remote(),
921           SLOT(EnableKittens(bool)));
922   // Hide the console
923   // connect(ui_->action_console, SIGNAL(triggered()), SLOT(ShowConsole()));
924   NowPlayingWidgetPositionChanged(ui_->now_playing->show_above_status_bar());
925 
926   // Load theme
927   // This is tricky: we need to save the default/system palette now, before
928   // loading user preferred theme (which will override it), to be able to restore
929   // it later
930   const_cast<QPalette&>(Appearance::kDefaultPalette) = QApplication::palette();
931   app_->appearance()->LoadUserTheme();
932   StyleSheetLoader* css_loader = new StyleSheetLoader(this);
933   css_loader->SetStyleSheet(this, ":mainwindow.css");
934 
935   // Load playlists
936   app_->playlist_manager()->Init(app_->library_backend(),
937                                  app_->playlist_backend(),
938                                  ui_->playlist_sequence, ui_->playlist);
939 
940   // This connection must be done after the playlists have been initialized.
941   connect(this, SIGNAL(StopAfterToggled(bool)), osd_,
942           SLOT(StopAfterToggle(bool)));
943 
944   // We need to connect these global shortcuts here after the playlist have been
945   // initialized
946   connect(global_shortcuts_, SIGNAL(CycleShuffleMode()),
947           app_->playlist_manager()->sequence(), SLOT(CycleShuffleMode()));
948   connect(global_shortcuts_, SIGNAL(CycleRepeatMode()),
949           app_->playlist_manager()->sequence(), SLOT(CycleRepeatMode()));
950   connect(app_->playlist_manager()->sequence(),
951           SIGNAL(RepeatModeChanged(PlaylistSequence::RepeatMode)), osd_,
952           SLOT(RepeatModeChanged(PlaylistSequence::RepeatMode)));
953   connect(app_->playlist_manager()->sequence(),
954           SIGNAL(ShuffleModeChanged(PlaylistSequence::ShuffleMode)), osd_,
955           SLOT(ShuffleModeChanged(PlaylistSequence::ShuffleMode)));
956 
957 #ifdef HAVE_LIBLASTFM
958   connect(app_->scrobbler(), SIGNAL(ScrobbleSubmitted()),
959           SLOT(ScrobbleSubmitted()));
960   connect(app_->scrobbler(), SIGNAL(ScrobbleError(int)),
961           SLOT(ScrobbleError(int)));
962 
963   LastFMButtonVisibilityChanged(app_->scrobbler()->AreButtonsVisible());
964   ScrobbleButtonVisibilityChanged(app_->scrobbler()->IsScrobbleButtonVisible());
965   ScrobblingEnabledChanged(app_->scrobbler()->IsScrobblingEnabled());
966 #else
967   LastFMButtonVisibilityChanged(false);
968   ScrobbleButtonVisibilityChanged(false);
969 #endif
970 
971   // Load settings
972   qLog(Debug) << "Loading settings";
973 
974   // Set last used geometry to position window on the correct monitor
975   // Set window state only if the window was last maximized
976   was_maximized_ = settings_.value("maximized", false).toBool();
977   restoreGeometry(settings_.value("geometry").toByteArray());
978   if (was_maximized_) {
979     setWindowState(windowState() | Qt::WindowMaximized);
980   }
981 
982   if (!ui_->splitter->restoreState(
983           settings_.value("splitter_state").toByteArray())) {
984     ui_->splitter->setSizes(QList<int>() << 300 << width() - 300);
985   }
986   ui_->tabs->setCurrentIndex(
987       settings_.value("current_tab", 1 /* Library tab */).toInt());
988   FancyTabWidget::Mode default_mode = FancyTabWidget::Mode_LargeSidebar;
989   ui_->tabs->SetMode(
990       FancyTabWidget::Mode(settings_.value("tab_mode", default_mode).toInt()));
991   file_view_->SetPath(
992       settings_.value("file_path", QDir::homePath()).toString());
993 
994   // Users often collapse one side of the splitter by mistake and don't know
995   // how to restore it.  This must be set after the state is restored above.
996   ui_->splitter->setChildrenCollapsible(false);
997 
998   ReloadSettings();
999 
1000   // The "GlobalSearchView" requires that "InternetModel" has already been
1001   // initialised before reload settings.
1002   app_->global_search()->ReloadSettings();
1003   global_search_view_->ReloadSettings();
1004 
1005   // Reload pretty OSD to avoid issues with fonts
1006   osd_->ReloadPrettyOSDSettings();
1007 
1008   // Reload playlist settings, for BG and glowing
1009   ui_->playlist->view()->ReloadSettings();
1010 
1011 #ifdef Q_OS_DARWIN
1012   // Always show mainwindow on startup on OS X.
1013   show();
1014 #else
1015   StartupBehaviour behaviour = StartupBehaviour(
1016       settings_.value("startupbehaviour", Startup_Remember).toInt());
1017   bool hidden = settings_.value("hidden", false).toBool();
1018 
1019   switch (behaviour) {
1020     case Startup_AlwaysHide:
1021       hide();
1022       break;
1023     case Startup_AlwaysShow:
1024       show();
1025       break;
1026     case Startup_Remember:
1027       setVisible(!hidden);
1028       break;
1029   }
1030 
1031   // Force the window to show in case somehow the config has tray and window set
1032   // to hide
1033   if (hidden && (!QSystemTrayIcon::isSystemTrayAvailable() || !tray_icon_ ||
1034                  !tray_icon_->IsVisible())) {
1035     settings_.setValue("hidden", false);
1036     show();
1037   }
1038 #endif
1039 
1040   QShortcut* close_window_shortcut = new QShortcut(this);
1041   close_window_shortcut->setKey(Qt::CTRL + Qt::Key_W);
1042   connect(close_window_shortcut, SIGNAL(activated()), SLOT(SetHiddenInTray()));
1043 
1044 #ifdef HAVE_WIIMOTEDEV
1045   // http://code.google.com/p/clementine-player/issues/detail?id=670
1046   // Switched position, mayby something is not ready ?
1047 
1048   wiimotedev_shortcuts_.reset(
1049       new WiimotedevShortcuts(osd_, this, app_->player()));
1050 #endif
1051 
1052   CheckFullRescanRevisions();
1053 
1054   CommandlineOptionsReceived(options);
1055 
1056   if (!options.contains_play_options()) LoadPlaybackStatus();
1057 
1058   initialized_ = true;
1059 
1060   qLog(Debug) << "Started";
1061 }
1062 
~MainWindow()1063 MainWindow::~MainWindow() {
1064   delete ui_;
1065 }
1066 
ReloadSettings()1067 void MainWindow::ReloadSettings() {
1068 #ifndef Q_OS_DARWIN
1069   bool show_tray =
1070       settings_.value("showtray", QSystemTrayIcon::isSystemTrayAvailable())
1071           .toBool();
1072 
1073   if (tray_icon_) tray_icon_->SetVisible(show_tray);
1074   if ((!show_tray || !QSystemTrayIcon::isSystemTrayAvailable()) && !isVisible())
1075     show();
1076 #endif
1077 
1078   doubleclick_addmode_ = AddBehaviour(
1079       settings_.value("doubleclick_addmode", AddBehaviour_Append).toInt());
1080   doubleclick_playmode_ = PlayBehaviour(
1081       settings_.value("doubleclick_playmode", PlayBehaviour_IfStopped).toInt());
1082   doubleclick_playlist_addmode_ = PlaylistAddBehaviour(
1083       settings_.value("doubleclick_playlist_addmode", PlaylistAddBehaviour_Play)
1084           .toInt());
1085   menu_playmode_ = PlayBehaviour(
1086       settings_.value("menu_playmode", PlayBehaviour_IfStopped).toInt());
1087 
1088   bool show_sidebar = settings_.value("show_sidebar", true).toBool();
1089   ui_->sidebar_layout->setVisible(show_sidebar);
1090   ui_->action_toggle_show_sidebar->setChecked(show_sidebar);
1091 }
1092 
ReloadAllSettings()1093 void MainWindow::ReloadAllSettings() {
1094   ReloadSettings();
1095 
1096   // Other settings
1097   app_->ReloadSettings();
1098   app_->global_search()->ReloadSettings();
1099   app_->library()->ReloadSettings();
1100   app_->player()->ReloadSettings();
1101   osd_->ReloadSettings();
1102   library_view_->ReloadSettings();
1103   song_info_view_->ReloadSettings();
1104   app_->player()->engine()->ReloadSettings();
1105   ui_->playlist->ReloadSettings();
1106   ui_->playlist->view()->ReloadSettings();
1107   app_->internet_model()->ReloadSettings();
1108 #ifdef HAVE_WIIMOTEDEV
1109   wiimotedev_shortcuts_->ReloadSettings();
1110 #endif
1111 }
1112 
RefreshStyleSheet()1113 void MainWindow::RefreshStyleSheet() { setStyleSheet(styleSheet()); }
MediaStopped()1114 void MainWindow::MediaStopped() {
1115   setWindowTitle(QCoreApplication::applicationName());
1116 
1117   ui_->action_stop->setEnabled(false);
1118   ui_->action_stop_after_this_track->setEnabled(false);
1119   ui_->action_play_pause->setIcon(
1120       IconLoader::Load("media-playback-start", IconLoader::Base));
1121   ui_->action_play_pause->setText(tr("Play"));
1122 
1123   ui_->action_play_pause->setEnabled(true);
1124 
1125   ui_->action_love->setEnabled(false);
1126   if (tray_icon_) tray_icon_->LastFMButtonLoveStateChanged(false);
1127 
1128   track_position_timer_->stop();
1129   track_slider_timer_->stop();
1130   ui_->track_slider->SetStopped();
1131   if (tray_icon_) {
1132     tray_icon_->SetProgress(0);
1133     tray_icon_->SetStopped();
1134   }
1135 }
1136 
MediaPaused()1137 void MainWindow::MediaPaused() {
1138   ui_->action_stop->setEnabled(true);
1139   ui_->action_stop_after_this_track->setEnabled(true);
1140   ui_->action_play_pause->setIcon(
1141       IconLoader::Load("media-playback-start", IconLoader::Base));
1142   ui_->action_play_pause->setText(tr("Play"));
1143 
1144   ui_->action_play_pause->setEnabled(true);
1145 
1146   track_position_timer_->stop();
1147   track_slider_timer_->stop();
1148 
1149   if (tray_icon_) tray_icon_->SetPaused();
1150 }
1151 
MediaPlaying()1152 void MainWindow::MediaPlaying() {
1153   ui_->action_stop->setEnabled(true);
1154   ui_->action_stop_after_this_track->setEnabled(true);
1155   ui_->action_play_pause->setIcon(
1156       IconLoader::Load("media-playback-pause", IconLoader::Base));
1157   ui_->action_play_pause->setText(tr("Pause"));
1158 
1159   bool enable_play_pause = !(app_->player()->GetCurrentItem()->options() &
1160                              PlaylistItem::PauseDisabled);
1161   ui_->action_play_pause->setEnabled(enable_play_pause);
1162 
1163   bool can_seek = !(app_->player()->GetCurrentItem()->options() &
1164                     PlaylistItem::SeekDisabled);
1165   ui_->track_slider->SetCanSeek(can_seek);
1166 
1167 #ifdef HAVE_LIBLASTFM
1168   bool enable_love = app_->scrobbler()->IsScrobblingEnabled();
1169   ui_->action_love->setEnabled(enable_love);
1170   if (tray_icon_) {
1171     tray_icon_->LastFMButtonLoveStateChanged(enable_love);
1172     tray_icon_->SetPlaying(enable_play_pause, enable_love);
1173   }
1174 #else
1175   if (tray_icon_) tray_icon_->SetPlaying(enable_play_pause);
1176 #endif
1177 
1178   track_position_timer_->start();
1179   track_slider_timer_->start();
1180   UpdateTrackPosition();
1181 }
1182 
VolumeChanged(int volume)1183 void MainWindow::VolumeChanged(int volume) {
1184   ui_->action_mute->setChecked(!volume);
1185   if (tray_icon_) tray_icon_->MuteButtonStateChanged(!volume);
1186 }
1187 
SongChanged(const Song & song)1188 void MainWindow::SongChanged(const Song& song) {
1189   setWindowTitle(song.PrettyTitleWithArtist());
1190   if (tray_icon_) tray_icon_->SetProgress(0);
1191 
1192 #ifdef HAVE_LIBLASTFM
1193   if (ui_->action_toggle_scrobbling->isVisible())
1194     SetToggleScrobblingIcon(app_->scrobbler()->IsScrobblingEnabled());
1195 #endif
1196 }
1197 
TrackSkipped(PlaylistItemPtr item)1198 void MainWindow::TrackSkipped(PlaylistItemPtr item) {
1199   // If it was a library item then we have to increment its skipped count in
1200   // the database.
1201   if (item && item->IsLocalLibraryItem() && item->Metadata().id() != -1 &&
1202       app_->playlist_manager()->active()->get_lastfm_status() !=
1203           Playlist::LastFM_Scrobbled &&
1204       app_->playlist_manager()->active()->get_lastfm_status() !=
1205           Playlist::LastFM_Queued) {
1206     Song song = item->Metadata();
1207     const qint64 position = app_->player()->engine()->position_nanosec();
1208     const qint64 length = app_->player()->engine()->length_nanosec();
1209     const float percentage = (length == 0 ? 1 : float(position) / length);
1210 
1211     const qint64 seconds_left = (length - position) / kNsecPerSec;
1212     const qint64 seconds_total = length / kNsecPerSec;
1213 
1214     if (((0.05 * seconds_total > 60 && percentage < 0.98) ||
1215          percentage < 0.95) &&
1216         seconds_left > 5) {  // Never count the skip if under 5 seconds left
1217       app_->library_backend()->IncrementSkipCountAsync(song.id(), percentage);
1218     }
1219   }
1220 }
1221 
1222 #ifdef HAVE_LIBLASTFM
ScrobblingEnabledChanged(bool value)1223 void MainWindow::ScrobblingEnabledChanged(bool value) {
1224   if (ui_->action_toggle_scrobbling->isVisible())
1225     SetToggleScrobblingIcon(value);
1226 
1227   if (app_->player()->GetState() != Engine::Idle) {
1228     return;
1229   } else {
1230     // invalidate current song, we will scrobble the next one
1231     if (app_->playlist_manager()->active()->get_lastfm_status() ==
1232         Playlist::LastFM_New) {
1233       app_->playlist_manager()->active()->set_lastfm_status(
1234           Playlist::LastFM_Seeked);
1235     }
1236   }
1237 
1238   ui_->action_love->setEnabled(value);
1239   if (tray_icon_) tray_icon_->LastFMButtonLoveStateChanged(value);
1240 }
1241 #endif
1242 
LastFMButtonVisibilityChanged(bool value)1243 void MainWindow::LastFMButtonVisibilityChanged(bool value) {
1244   ui_->action_love->setVisible(value);
1245   ui_->last_fm_controls->setVisible(value);
1246   if (tray_icon_) tray_icon_->LastFMButtonVisibilityChanged(value);
1247 }
1248 
ScrobbleButtonVisibilityChanged(bool value)1249 void MainWindow::ScrobbleButtonVisibilityChanged(bool value) {
1250   ui_->action_toggle_scrobbling->setVisible(value);
1251   ui_->scrobbling_button->setVisible(value);
1252 
1253   // when you reshow the buttons
1254   if (value) {
1255     // check if the song was scrobbled
1256     if (app_->playlist_manager()->active()->get_lastfm_status() ==
1257         Playlist::LastFM_Scrobbled) {
1258       ui_->action_toggle_scrobbling->setIcon(
1259           IconLoader::Load("as", IconLoader::Lastfm));
1260     } else {
1261 #ifdef HAVE_LIBLASTFM
1262       SetToggleScrobblingIcon(app_->scrobbler()->IsScrobblingEnabled());
1263 #endif
1264     }
1265   }
1266 }
1267 
SaveSettings(QSettings * settings)1268 void MainWindow::SaveSettings(QSettings* settings) {
1269   if (!initialized_) return;
1270   settings->beginGroup(kSettingsGroup);
1271   if (dirty_geometry_) SaveGeometry(settings);
1272   if (dirty_playback_) SavePlaybackStatus(settings);
1273   settings->endGroup();
1274 }
1275 
changeEvent(QEvent *)1276 void MainWindow::changeEvent(QEvent*) {
1277   dirty_geometry_ = true;
1278   app_->DirtySettings();
1279 }
1280 
resizeEvent(QResizeEvent *)1281 void MainWindow::resizeEvent(QResizeEvent*) {
1282   dirty_geometry_ = true;
1283   app_->DirtySettings();
1284 }
1285 
SaveGeometry(QSettings * settings)1286 void MainWindow::SaveGeometry(QSettings* settings) {
1287   if (!initialized_) return;
1288   dirty_geometry_ = false;
1289 
1290   was_maximized_ = isMaximized();
1291   settings->setValue("maximized", was_maximized_);
1292   // Save the geometry only when mainwindow is not in maximized state
1293   if (!was_maximized_) {
1294     settings->setValue("geometry", saveGeometry());
1295   }
1296   settings->setValue("splitter_state", ui_->splitter->saveState());
1297   settings->setValue("current_tab", ui_->tabs->currentIndex());
1298   settings->setValue("tab_mode", ui_->tabs->mode());
1299 
1300   // Leaving this here for now
1301   ui_->tabs->saveSettings(settings);
1302 }
1303 
SavePlaybackStatus(QSettings * settings)1304 void MainWindow::SavePlaybackStatus(QSettings* settings) {
1305   dirty_playback_ = false;
1306   settings->setValue("playback_state", app_->player()->GetState());
1307   if (app_->player()->GetState() == Engine::Playing ||
1308       app_->player()->GetState() == Engine::Paused) {
1309     settings->setValue(
1310         "playback_position",
1311         app_->player()->engine()->position_nanosec() / kNsecPerSec);
1312   } else {
1313     settings->setValue("playback_position", 0);
1314   }
1315 }
1316 
LoadPlaybackStatus()1317 void MainWindow::LoadPlaybackStatus() {
1318   bool resume_playback =
1319       settings_.value("resume_playback_after_start", false).toBool();
1320   saved_playback_state_ = static_cast<Engine::State>(
1321       settings_.value("playback_state", Engine::Empty).toInt());
1322   saved_playback_position_ = settings_.value("playback_position", 0).toDouble();
1323   if (!resume_playback || saved_playback_state_ == Engine::Empty ||
1324       saved_playback_state_ == Engine::Idle) {
1325     return;
1326   }
1327 
1328   connect(app_->playlist_manager()->active(), SIGNAL(RestoreFinished()),
1329           SLOT(ResumePlayback()));
1330 }
1331 
ResumePlayback()1332 void MainWindow::ResumePlayback() {
1333   qLog(Debug) << "Resuming playback";
1334 
1335   disconnect(app_->playlist_manager()->active(), SIGNAL(RestoreFinished()),
1336              this, SLOT(ResumePlayback()));
1337 
1338   if (saved_playback_state_ == Engine::Paused) {
1339     NewClosure(app_->player(), SIGNAL(Playing()), app_->player(),
1340                SLOT(PlayPause()));
1341   }
1342 
1343   app_->player()->Play();
1344 
1345   connect(track_position_timer_, SIGNAL(timeout()),
1346           SLOT(ResumePlaybackPosition()));
1347 }
1348 
ResumePlaybackPosition()1349 void MainWindow::ResumePlaybackPosition() {
1350   // We must wait until the song has a length because
1351   // seeking a song without length does not work
1352   if (app_->player()->engine()->length_nanosec() > 0) {
1353     disconnect(track_position_timer_, SIGNAL(timeout()), this,
1354                SLOT(ResumePlaybackPosition()));
1355 
1356     app_->player()->SeekTo(saved_playback_position_);
1357   }
1358 }
1359 
PlayIndex(const QModelIndex & index)1360 void MainWindow::PlayIndex(const QModelIndex& index) {
1361   if (!index.isValid()) return;
1362 
1363   int row = index.row();
1364   if (index.model() == app_->playlist_manager()->current()->proxy()) {
1365     // The index was in the proxy model (might've been filtered), so we need
1366     // to get the actual row in the source model.
1367     row =
1368         app_->playlist_manager()->current()->proxy()->mapToSource(index).row();
1369   }
1370 
1371   app_->playlist_manager()->SetActiveToCurrent();
1372   app_->player()->PlayAt(row, Engine::Manual, true);
1373 }
1374 
PlaylistDoubleClick(const QModelIndex & index)1375 void MainWindow::PlaylistDoubleClick(const QModelIndex& index) {
1376   if (!index.isValid()) return;
1377 
1378   int row = index.row();
1379   if (index.model() == app_->playlist_manager()->current()->proxy()) {
1380     // The index was in the proxy model (might've been filtered), so we need
1381     // to get the actual row in the source model.
1382     row =
1383         app_->playlist_manager()->current()->proxy()->mapToSource(index).row();
1384   }
1385 
1386   QModelIndexList dummyIndexList;
1387 
1388   switch (doubleclick_playlist_addmode_) {
1389     case PlaylistAddBehaviour_Play:
1390       app_->playlist_manager()->SetActiveToCurrent();
1391       app_->player()->PlayAt(row, Engine::Manual, true);
1392       break;
1393 
1394     case PlaylistAddBehaviour_Enqueue:
1395       dummyIndexList.append(index);
1396       app_->playlist_manager()->current()->queue()->ToggleTracks(
1397           dummyIndexList);
1398       if (app_->player()->GetState() != Engine::Playing) {
1399         app_->playlist_manager()->SetActiveToCurrent();
1400         app_->player()->PlayAt(
1401             app_->playlist_manager()->current()->queue()->TakeNext(),
1402             Engine::Manual, true);
1403       }
1404       break;
1405   }
1406 }
1407 
VolumeWheelEvent(int delta)1408 void MainWindow::VolumeWheelEvent(int delta) {
1409   ui_->volume->setValue(ui_->volume->value() + delta / 30);
1410 }
1411 
ToggleShowHide()1412 void MainWindow::ToggleShowHide() {
1413   if (settings_.value("hidden").toBool()) {
1414     show();
1415     SetHiddenInTray(false);
1416   } else if (isActiveWindow()) {
1417     hide();
1418     setWindowState((windowState() & ~Qt::WindowMinimized) | Qt::WindowActive);
1419     SetHiddenInTray(true);
1420   } else if (isMinimized()) {
1421     hide();
1422     setWindowState((windowState() & ~Qt::WindowMinimized) | Qt::WindowActive);
1423     SetHiddenInTray(false);
1424   } else if (!isVisible()) {
1425     show();
1426     activateWindow();
1427   } else {
1428     // Window is not hidden but does not have focus; bring it to front.
1429     activateWindow();
1430     raise();
1431   }
1432 }
1433 
StopAfterCurrent()1434 void MainWindow::StopAfterCurrent() {
1435   app_->playlist_manager()->active()->StopAfter(
1436       app_->playlist_manager()->active()->current_row());
1437   emit StopAfterToggled(
1438       app_->playlist_manager()->active()->stop_after_current());
1439 }
1440 
closeEvent(QCloseEvent * event)1441 void MainWindow::closeEvent(QCloseEvent* event) {
1442   bool keep_running(false);
1443   if (tray_icon_)
1444     keep_running =
1445         settings_.value("keeprunning", tray_icon_->IsVisible()).toBool();
1446 
1447   if (keep_running && event->spontaneous()) {
1448     event->ignore();
1449     SetHiddenInTray(true);
1450   } else {
1451     Exit();
1452   }
1453 }
1454 
SetHiddenInTray(bool hidden)1455 void MainWindow::SetHiddenInTray(bool hidden) {
1456   settings_.setValue("hidden", hidden);
1457 
1458   // Some window managers don't remember maximized state between calls to
1459   // hide() and show(), so we have to remember it ourself.
1460   if (hidden) {
1461     QTimer::singleShot(0, this, &QWidget::hide);
1462   } else {
1463     if (was_maximized_)
1464       showMaximized();
1465     else
1466       show();
1467   }
1468 }
1469 
FilePathChanged(const QString & path)1470 void MainWindow::FilePathChanged(const QString& path) {
1471   settings_.setValue("file_path", path);
1472 }
1473 
Seeked(qlonglong microseconds)1474 void MainWindow::Seeked(qlonglong microseconds) {
1475   const int position = microseconds / kUsecPerSec;
1476   const int length =
1477       app_->player()->GetCurrentItem()->Metadata().length_nanosec() /
1478       kNsecPerSec;
1479   if (tray_icon_) tray_icon_->SetProgress(double(position) / length * 100);
1480 
1481   // if we seeked, scrobbling is canceled, update the icon
1482   if (ui_->action_toggle_scrobbling->isVisible()) SetToggleScrobblingIcon(true);
1483 }
1484 
1485 /**
1486  * Update track position, tray icon, playcount
1487  */
UpdateTrackPosition()1488 void MainWindow::UpdateTrackPosition() {
1489   // Track position in seconds
1490   Playlist* playlist = app_->playlist_manager()->active();
1491 
1492   PlaylistItemPtr item(app_->player()->GetCurrentItem());
1493   const int position = std::floor(
1494       float(app_->player()->engine()->position_nanosec()) / kNsecPerSec + 0.5);
1495   const int length = app_->player()->engine()->length_nanosec() / kNsecPerSec;
1496   const int scrobble_point = playlist->scrobble_point_nanosec() / kNsecPerSec;
1497   const int play_count_point =
1498       playlist->play_count_point_nanosec() / kNsecPerSec;
1499 
1500   if (length <= 0) {
1501     // Probably a stream that we don't know the length of
1502     return;
1503   }
1504 
1505 #ifdef HAVE_LIBLASTFM
1506   // Time to scrobble?
1507   const bool last_fm_enabled = ui_->action_toggle_scrobbling->isVisible() &&
1508                                app_->scrobbler()->IsScrobblingEnabled() &&
1509                                app_->scrobbler()->IsAuthenticated();
1510 
1511   if (position >= scrobble_point) {
1512     if (playlist->get_lastfm_status() == Playlist::LastFM_New) {
1513       if (app_->scrobbler()->IsScrobblingEnabled() &&
1514           app_->scrobbler()->IsAuthenticated()) {
1515         qLog(Info) << "Scrobbling at" << scrobble_point;
1516         app_->scrobbler()->Scrobble();
1517       }
1518     }
1519   }
1520 #endif
1521 
1522   if (position >= play_count_point) {
1523     // Update the play count for the song if it's from the library
1524     if (!playlist->have_incremented_playcount() && item->IsLocalLibraryItem() &&
1525         item->Metadata().id() != -1 &&
1526         playlist->get_lastfm_status() != Playlist::LastFM_Seeked) {
1527       app_->library_backend()->IncrementPlayCountAsync(item->Metadata().id());
1528       playlist->set_have_incremented_playcount();
1529     }
1530   }
1531 
1532   // (just after) the scrobble point is a good point to change tracks in intro
1533   // mode
1534   if (position >= scrobble_point + 5) {
1535     if (playlist->sequence()->repeat_mode() == PlaylistSequence::Repeat_Intro) {
1536       emit IntroPointReached();
1537     }
1538   }
1539 
1540   // Update the tray icon every 10 seconds
1541   if (position % 10 == 0) {
1542     qLog(Debug) << "position:" << position
1543                 << ", scrobble point:" << scrobble_point
1544                 << ", lastfm status:" << playlist->get_lastfm_status()
1545                 << ", play count point:" << play_count_point
1546                 << ", is local library item:" << item->IsLocalLibraryItem()
1547                 << ", playlist have incremented playcount: "
1548                 << playlist->have_incremented_playcount();
1549 
1550     if (tray_icon_) tray_icon_->SetProgress(double(position) / length * 100);
1551 
1552 // if we're waiting for the scrobble point, update the icon
1553 #ifdef HAVE_LIBLASTFM
1554     if (position < scrobble_point &&
1555         playlist->get_lastfm_status() == Playlist::LastFM_New &&
1556         last_fm_enabled) {
1557       ui_->action_toggle_scrobbling->setIcon(
1558           CreateOverlayedIcon(position, scrobble_point));
1559     }
1560 #endif
1561   }
1562 }
1563 
UpdateTrackSliderPosition()1564 void MainWindow::UpdateTrackSliderPosition() {
1565   PlaylistItemPtr item(app_->player()->GetCurrentItem());
1566 
1567   const int slider_position = std::floor(
1568       float(app_->player()->engine()->position_nanosec()) / kNsecPerMsec);
1569   const int slider_length =
1570       app_->player()->engine()->length_nanosec() / kNsecPerMsec;
1571 
1572   // Update the slider
1573   ui_->track_slider->SetValue(slider_position, slider_length);
1574 }
1575 
1576 #ifdef HAVE_LIBLASTFM
ScrobbledRadioStream()1577 void MainWindow::ScrobbledRadioStream() {
1578   ui_->action_love->setEnabled(true);
1579   if (tray_icon_) tray_icon_->LastFMButtonLoveStateChanged(true);
1580 }
1581 
Love()1582 void MainWindow::Love() {
1583   app_->scrobbler()->Love();
1584   ui_->action_love->setEnabled(false);
1585   if (tray_icon_) tray_icon_->LastFMButtonLoveStateChanged(false);
1586 }
1587 #endif
1588 
ApplyAddBehaviour(MainWindow::AddBehaviour b,MimeData * data) const1589 void MainWindow::ApplyAddBehaviour(MainWindow::AddBehaviour b,
1590                                    MimeData* data) const {
1591   switch (b) {
1592     case AddBehaviour_Append:
1593       data->clear_first_ = false;
1594       data->enqueue_now_ = false;
1595       break;
1596 
1597     case AddBehaviour_Enqueue:
1598       data->clear_first_ = false;
1599       data->enqueue_now_ = true;
1600       break;
1601 
1602     case AddBehaviour_Load:
1603       data->clear_first_ = true;
1604       data->enqueue_now_ = false;
1605       break;
1606 
1607     case AddBehaviour_OpenInNew:
1608       data->open_in_new_playlist_ = true;
1609       break;
1610   }
1611 }
1612 
ApplyPlayBehaviour(MainWindow::PlayBehaviour b,MimeData * data) const1613 void MainWindow::ApplyPlayBehaviour(MainWindow::PlayBehaviour b,
1614                                     MimeData* data) const {
1615   switch (b) {
1616     case PlayBehaviour_Always:
1617       data->play_now_ = true;
1618       break;
1619 
1620     case PlayBehaviour_Never:
1621       data->play_now_ = false;
1622       break;
1623 
1624     case PlayBehaviour_IfStopped:
1625       data->play_now_ = !(app_->player()->GetState() == Engine::Playing);
1626       break;
1627   }
1628 }
1629 
AddToPlaylist(QMimeData * data)1630 void MainWindow::AddToPlaylist(QMimeData* data) {
1631   if (!data) return;
1632 
1633   if (MimeData* mime_data = qobject_cast<MimeData*>(data)) {
1634     // Should we replace the flags with the user's preference?
1635     if (mime_data->override_user_settings_) {
1636       // Do nothing
1637     } else if (mime_data->from_doubleclick_) {
1638       ApplyAddBehaviour(doubleclick_addmode_, mime_data);
1639       ApplyPlayBehaviour(doubleclick_playmode_, mime_data);
1640     } else {
1641       ApplyPlayBehaviour(menu_playmode_, mime_data);
1642     }
1643 
1644     // Should we create a new playlist for the songs?
1645     if (mime_data->open_in_new_playlist_) {
1646       app_->playlist_manager()->New(mime_data->get_name_for_new_playlist());
1647     }
1648   }
1649 
1650   app_->playlist_manager()->current()->dropMimeData(data, Qt::CopyAction, -1, 0,
1651                                                     QModelIndex());
1652   delete data;
1653 }
1654 
AddToPlaylist(QAction * action)1655 void MainWindow::AddToPlaylist(QAction* action) {
1656   int destination = action->data().toInt();
1657   PlaylistItemList items;
1658 
1659   // get the selected playlist items
1660   for (const QModelIndex& index :
1661        ui_->playlist->view()->selectionModel()->selection().indexes()) {
1662     if (index.column() != 0) continue;
1663     int row =
1664         app_->playlist_manager()->current()->proxy()->mapToSource(index).row();
1665     items << app_->playlist_manager()->current()->item_at(row);
1666   }
1667 
1668   SongList songs;
1669   for (PlaylistItemPtr item : items) {
1670     songs << item->Metadata();
1671   }
1672 
1673   // we're creating a new playlist
1674   if (destination == -1) {
1675     // save the current playlist to reactivate it
1676     int current_id = app_->playlist_manager()->current_id();
1677     // get the name from selection
1678     app_->playlist_manager()->New(
1679         app_->playlist_manager()->GetNameForNewPlaylist(songs));
1680     if (app_->playlist_manager()->current()->id() != current_id) {
1681       // I'm sure the new playlist was created and is selected, so I can just
1682       // insert items
1683       app_->playlist_manager()->current()->InsertItems(items);
1684       // set back the current playlist
1685       app_->playlist_manager()->SetCurrentPlaylist(current_id);
1686     }
1687   } else {
1688     // we're inserting in a existing playlist
1689     app_->playlist_manager()->playlist(destination)->InsertItems(items);
1690   }
1691 }
1692 
PlaylistRightClick(const QPoint & global_pos,const QModelIndex & index)1693 void MainWindow::PlaylistRightClick(const QPoint& global_pos,
1694                                     const QModelIndex& index) {
1695   QModelIndex source_index =
1696       app_->playlist_manager()->current()->proxy()->mapToSource(index);
1697   playlist_menu_index_ = source_index;
1698 
1699   // Is this song currently playing?
1700   if (app_->playlist_manager()->current()->current_row() ==
1701           source_index.row() &&
1702       app_->player()->GetState() == Engine::Playing) {
1703     playlist_play_pause_->setText(tr("Pause"));
1704     playlist_play_pause_->setIcon(
1705         IconLoader::Load("media-playback-pause", IconLoader::Base));
1706   } else {
1707     playlist_play_pause_->setText(tr("Play"));
1708     playlist_play_pause_->setIcon(
1709         IconLoader::Load("media-playback-start", IconLoader::Base));
1710   }
1711 
1712   // Are we allowed to pause?
1713   if (index.isValid()) {
1714     playlist_play_pause_->setEnabled(
1715         app_->playlist_manager()->current()->current_row() !=
1716             source_index.row() ||
1717         !(app_->playlist_manager()
1718               ->current()
1719               ->item_at(source_index.row())
1720               ->options() &
1721           PlaylistItem::PauseDisabled));
1722   } else {
1723     playlist_play_pause_->setEnabled(false);
1724   }
1725 
1726   playlist_stop_after_->setEnabled(index.isValid());
1727 
1728   // Are any of the selected songs editable or queued?
1729   QModelIndexList selection =
1730       ui_->playlist->view()->selectionModel()->selection().indexes();
1731   bool cue_selected = false;
1732   int editable = 0;
1733   int streams = 0;
1734   int in_queue = 0;
1735   int not_in_queue = 0;
1736   int in_skipped = 0;
1737   int not_in_skipped = 0;
1738   for (const QModelIndex& index : selection) {
1739     if (index.column() != 0) continue;
1740 
1741     PlaylistItemPtr item =
1742         app_->playlist_manager()->current()->item_at(index.row());
1743     if (item->Metadata().has_cue()) {
1744       cue_selected = true;
1745     } else if (item->Metadata().IsEditable()) {
1746       editable++;
1747     }
1748 
1749     if (item->Metadata().is_stream()) {
1750       streams++;
1751     }
1752 
1753     if (index.data(Playlist::Role_QueuePosition).toInt() == -1)
1754       not_in_queue++;
1755     else
1756       in_queue++;
1757 
1758     if (item->GetShouldSkip()) {
1759       in_skipped++;
1760     } else {
1761       not_in_skipped++;
1762     }
1763   }
1764 
1765   int all = not_in_queue + in_queue;
1766 
1767   // this is available when we have one or many files and at least one of
1768   // those is not CUE related
1769   ui_->action_edit_track->setEnabled(editable);
1770   ui_->action_edit_track->setVisible(editable);
1771   ui_->action_auto_complete_tags->setEnabled(editable);
1772   ui_->action_auto_complete_tags->setVisible(editable);
1773   // the rest of the read / write actions work only when there are no CUEs
1774   // involved
1775   if (cue_selected) editable = 0;
1776 
1777   // no 'show in browser' action if only streams are selected
1778   playlist_open_in_browser_->setVisible(streams != all);
1779 
1780   // If exactly one stream is selected, enable the 'show details' action.
1781   ui_->action_view_stream_details->setEnabled(all == 1 && streams == 1);
1782   ui_->action_view_stream_details->setVisible(all == 1 && streams == 1);
1783 
1784   bool track_column = (index.column() == Playlist::Column_Track);
1785   ui_->action_renumber_tracks->setVisible(editable >= 2 && track_column);
1786   ui_->action_selection_set_value->setVisible(editable >= 2 && !track_column);
1787   ui_->action_edit_value->setVisible(editable);
1788   ui_->action_remove_from_playlist->setEnabled(!selection.isEmpty());
1789 
1790   playlist_show_in_library_->setVisible(false);
1791   playlist_copy_to_library_->setVisible(false);
1792   playlist_move_to_library_->setVisible(false);
1793   playlist_organise_->setVisible(false);
1794   playlist_delete_->setVisible(false);
1795   playlist_copy_to_device_->setVisible(false);
1796 
1797   search_for_artist_->setVisible(all == 1);
1798   search_for_album_->setVisible(all == 1);
1799 
1800   if (in_queue == 1 && not_in_queue == 0)
1801     playlist_queue_->setText(tr("Dequeue track"));
1802   else if (in_queue > 1 && not_in_queue == 0)
1803     playlist_queue_->setText(tr("Dequeue selected tracks"));
1804   else if (in_queue == 0 && not_in_queue == 1)
1805     playlist_queue_->setText(tr("Queue track"));
1806   else if (in_queue == 0 && not_in_queue > 1)
1807     playlist_queue_->setText(tr("Queue selected tracks"));
1808   else
1809     playlist_queue_->setText(tr("Toggle queue status"));
1810 
1811   if (all > 1)
1812     playlist_queue_play_next_->setText(tr("Play selected tracks next"));
1813   else
1814     playlist_queue_play_next_->setText(tr("Play next"));
1815 
1816   if (in_skipped == 1 && not_in_skipped == 0)
1817     playlist_skip_->setText(tr("Unskip track"));
1818   else if (in_skipped > 1 && not_in_skipped == 0)
1819     playlist_skip_->setText(tr("Unskip selected tracks"));
1820   else if (in_skipped == 0 && not_in_skipped == 1)
1821     playlist_skip_->setText(tr("Skip track"));
1822   else if (in_skipped == 0 && not_in_skipped > 1)
1823     playlist_skip_->setText(tr("Skip selected tracks"));
1824 
1825   if (not_in_queue == 0)
1826     playlist_queue_->setIcon(IconLoader::Load("go-previous", IconLoader::Base));
1827   else
1828     playlist_queue_->setIcon(IconLoader::Load("go-next", IconLoader::Base));
1829 
1830   playlist_queue_play_next_->setIcon(
1831       IconLoader::Load("go-next", IconLoader::Base));
1832 
1833   if (!index.isValid()) {
1834     ui_->action_selection_set_value->setVisible(false);
1835     ui_->action_edit_value->setVisible(false);
1836   } else {
1837     Playlist::Column column = (Playlist::Column)index.column();
1838     bool column_is_editable = Playlist::column_is_editable(column) && editable;
1839 
1840     ui_->action_selection_set_value->setVisible(
1841         ui_->action_selection_set_value->isVisible() && column_is_editable);
1842     ui_->action_edit_value->setVisible(ui_->action_edit_value->isVisible() &&
1843                                        column_is_editable);
1844 
1845     QString column_name = Playlist::column_name(column);
1846     QString column_value =
1847         app_->playlist_manager()->current()->data(source_index).toString();
1848     if (column_value.length() > 25)
1849       column_value = column_value.left(25) + "...";
1850 
1851     ui_->action_selection_set_value->setText(
1852         tr("Set %1 to \"%2\"...").arg(column_name.toLower()).arg(column_value));
1853     ui_->action_edit_value->setText(tr("Edit tag \"%1\"...").arg(column_name));
1854 
1855     // Is it a library item?
1856     PlaylistItemPtr item =
1857         app_->playlist_manager()->current()->item_at(source_index.row());
1858     if (item->IsLocalLibraryItem() && item->Metadata().id() != -1) {
1859       playlist_organise_->setVisible(editable);
1860       playlist_show_in_library_->setVisible(editable);
1861     } else {
1862       playlist_copy_to_library_->setVisible(editable);
1863       playlist_move_to_library_->setVisible(editable);
1864     }
1865 
1866     playlist_delete_->setVisible(editable);
1867     playlist_copy_to_device_->setVisible(editable);
1868 
1869     // Remove old item actions, if any.
1870     for (QAction* action : playlistitem_actions_) {
1871       playlist_menu_->removeAction(action);
1872     }
1873 
1874     // Get the new item actions, and add them
1875     playlistitem_actions_ = item->actions();
1876     playlistitem_actions_separator_->setVisible(
1877         !playlistitem_actions_.isEmpty());
1878     playlist_menu_->insertActions(playlistitem_actions_separator_,
1879                                   playlistitem_actions_);
1880   }
1881 
1882   // if it isn't the first time we right click, we need to remove the menu
1883   // previously created
1884   if (playlist_add_to_another_ != nullptr) {
1885     playlist_menu_->removeAction(playlist_add_to_another_);
1886     delete playlist_add_to_another_;
1887   }
1888 
1889   // create the playlist submenu
1890   QMenu* add_to_another_menu = new QMenu(tr("Add to another playlist"), this);
1891   add_to_another_menu->setIcon(IconLoader::Load("list-add", IconLoader::Base));
1892 
1893   for (const PlaylistBackend::Playlist& playlist :
1894        app_->playlist_backend()->GetAllOpenPlaylists()) {
1895     // don't add the current playlist
1896     if (playlist.id != app_->playlist_manager()->current()->id()) {
1897       QAction* existing_playlist = new QAction(this);
1898       existing_playlist->setText(playlist.name);
1899       existing_playlist->setData(playlist.id);
1900       add_to_another_menu->addAction(existing_playlist);
1901     }
1902   }
1903 
1904   add_to_another_menu->addSeparator();
1905   // add to a new playlist
1906   QAction* new_playlist = new QAction(this);
1907   new_playlist->setText(tr("New playlist"));
1908   new_playlist->setData(-1);  // fake id
1909   add_to_another_menu->addAction(new_playlist);
1910   playlist_add_to_another_ = playlist_menu_->insertMenu(
1911       ui_->action_remove_from_playlist, add_to_another_menu);
1912 
1913   connect(add_to_another_menu, SIGNAL(triggered(QAction*)),
1914           SLOT(AddToPlaylist(QAction*)));
1915 
1916   playlist_menu_->popup(global_pos);
1917 }
1918 
PlaylistPlay()1919 void MainWindow::PlaylistPlay() {
1920   if (app_->playlist_manager()->current()->current_row() ==
1921       playlist_menu_index_.row()) {
1922     app_->player()->PlayPause();
1923   } else {
1924     PlayIndex(playlist_menu_index_);
1925   }
1926 }
1927 
PlaylistStopAfter()1928 void MainWindow::PlaylistStopAfter() {
1929   app_->playlist_manager()->current()->StopAfter(playlist_menu_index_.row());
1930 }
1931 
EditTracks()1932 void MainWindow::EditTracks() {
1933   SongList songs;
1934   PlaylistItemList items;
1935 
1936   for (const QModelIndex& index :
1937        ui_->playlist->view()->selectionModel()->selection().indexes()) {
1938     if (index.column() != 0) continue;
1939     int row =
1940         app_->playlist_manager()->current()->proxy()->mapToSource(index).row();
1941     PlaylistItemPtr item(app_->playlist_manager()->current()->item_at(row));
1942     Song song = item->Metadata();
1943 
1944     if (song.IsEditable()) {
1945       songs << song;
1946       items << item;
1947     }
1948   }
1949 
1950   edit_tag_dialog_->SetSongs(songs, items);
1951   edit_tag_dialog_->show();
1952 }
1953 
EditTagDialogAccepted()1954 void MainWindow::EditTagDialogAccepted() {
1955   for (PlaylistItemPtr item : edit_tag_dialog_->playlist_items()) {
1956     item->Reload();
1957   }
1958 
1959   // This is really lame but we don't know what rows have changed
1960   ui_->playlist->view()->update();
1961 
1962   app_->playlist_manager()->current()->Save();
1963 }
1964 
DiscoverStreamDetails()1965 void MainWindow::DiscoverStreamDetails() {
1966   int row = playlist_menu_index_.row();
1967   Song song = app_->playlist_manager()->current()->item_at(row)->Metadata();
1968 
1969   QString url = song.url().toString();
1970   stream_discoverer_->Discover(url);
1971 }
1972 
ShowStreamDetails(const StreamDetails & details)1973 void MainWindow::ShowStreamDetails(const StreamDetails& details) {
1974   StreamDetailsDialog stream_details_dialog(this);
1975 
1976   stream_details_dialog.setUrl(details.url);
1977   stream_details_dialog.setFormat(details.format);
1978   stream_details_dialog.setBitrate(details.bitrate);
1979   stream_details_dialog.setChannels(details.channels);
1980   stream_details_dialog.setDepth(details.depth);
1981   stream_details_dialog.setSampleRate(details.sample_rate);
1982 
1983   stream_details_dialog.exec();
1984 }
1985 
RenumberTracks()1986 void MainWindow::RenumberTracks() {
1987   QModelIndexList indexes =
1988       ui_->playlist->view()->selectionModel()->selection().indexes();
1989   int track = 1;
1990 
1991   // Get the index list in order
1992   std::stable_sort(indexes.begin(), indexes.end());
1993 
1994   // if first selected song has a track number set, start from that offset
1995   if (!indexes.isEmpty()) {
1996     const Song first_song = app_->playlist_manager()
1997                                 ->current()
1998                                 ->item_at(indexes[0].row())
1999                                 ->Metadata();
2000 
2001     if (first_song.track() > 0) track = first_song.track();
2002   }
2003 
2004   for (const QModelIndex& index : indexes) {
2005     if (index.column() != 0) continue;
2006 
2007     const QModelIndex source_index =
2008         app_->playlist_manager()->current()->proxy()->mapToSource(index);
2009     int row = source_index.row();
2010     Song song = app_->playlist_manager()->current()->item_at(row)->Metadata();
2011 
2012     if (song.IsEditable()) {
2013       song.set_track(track);
2014 
2015       TagReaderReply* reply =
2016           TagReaderClient::Instance()->SaveFile(song.url().toLocalFile(), song);
2017 
2018       NewClosure(reply, SIGNAL(Finished(bool)), this,
2019                  SLOT(SongSaveComplete(TagReaderReply*, QPersistentModelIndex)),
2020                  reply, QPersistentModelIndex(source_index));
2021     }
2022     track++;
2023   }
2024 }
2025 
SongSaveComplete(TagReaderReply * reply,const QPersistentModelIndex & index)2026 void MainWindow::SongSaveComplete(TagReaderReply* reply,
2027                                   const QPersistentModelIndex& index) {
2028   if (reply->is_successful() && index.isValid()) {
2029     app_->playlist_manager()->current()->ReloadItems(QList<int>()
2030                                                      << index.row());
2031   }
2032   reply->deleteLater();
2033 }
2034 
SelectionSetValue()2035 void MainWindow::SelectionSetValue() {
2036   Playlist::Column column = (Playlist::Column)playlist_menu_index_.column();
2037   QVariant column_value =
2038       app_->playlist_manager()->current()->data(playlist_menu_index_);
2039 
2040   QModelIndexList indexes =
2041       ui_->playlist->view()->selectionModel()->selection().indexes();
2042   for (const QModelIndex& index : indexes) {
2043     if (index.column() != 0) continue;
2044 
2045     const QModelIndex source_index =
2046         app_->playlist_manager()->current()->proxy()->mapToSource(index);
2047     int row = source_index.row();
2048     Song song = app_->playlist_manager()->current()->item_at(row)->Metadata();
2049 
2050     if (Playlist::set_column_value(song, column, column_value)) {
2051       TagReaderReply* reply =
2052           TagReaderClient::Instance()->SaveFile(song.url().toLocalFile(), song);
2053 
2054       NewClosure(reply, SIGNAL(Finished(bool)), this,
2055                  SLOT(SongSaveComplete(TagReaderReply*, QPersistentModelIndex)),
2056                  reply, QPersistentModelIndex(source_index));
2057     }
2058   }
2059 }
2060 
EditValue()2061 void MainWindow::EditValue() {
2062   QModelIndex current = ui_->playlist->view()->currentIndex();
2063   if (!current.isValid()) return;
2064 
2065   // Edit the last column that was right-clicked on.  If nothing's ever been
2066   // right clicked then look for the first editable column.
2067   int column = playlist_menu_index_.column();
2068   if (column == -1) {
2069     for (int i = 0; i < ui_->playlist->view()->model()->columnCount(); ++i) {
2070       if (ui_->playlist->view()->isColumnHidden(i)) continue;
2071       if (!Playlist::column_is_editable(Playlist::Column(i))) continue;
2072       column = i;
2073       break;
2074     }
2075   }
2076 
2077   ui_->playlist->view()->edit(current.sibling(current.row(), column));
2078 }
2079 
AddFile()2080 void MainWindow::AddFile() {
2081   // Last used directory
2082   QString directory =
2083       settings_.value("add_media_path", QDir::currentPath()).toString();
2084 
2085   PlaylistParser parser(app_->library_backend());
2086 
2087   // Show dialog
2088   QStringList file_names = QFileDialog::getOpenFileNames(
2089       this, tr("Add file"), directory,
2090       QString("%1 (%2);;%3;;%4").arg(tr("Music"), FileView::kFileFilter,
2091                                      parser.filters(),
2092                                      tr(kAllFilesFilterSpec)));
2093   if (file_names.isEmpty()) return;
2094 
2095   // Save last used directory
2096   settings_.setValue("add_media_path", file_names[0]);
2097 
2098   // Convert to URLs
2099   QList<QUrl> urls;
2100   for (const QString& path : file_names) {
2101     urls << QUrl::fromLocalFile(QFileInfo(path).canonicalFilePath());
2102   }
2103 
2104   MimeData* data = new MimeData;
2105   data->setUrls(urls);
2106   AddToPlaylist(data);
2107 }
2108 
AddFolder()2109 void MainWindow::AddFolder() {
2110   // Last used directory
2111   QString directory =
2112       settings_.value("add_folder_path", QDir::currentPath()).toString();
2113 
2114   // Show dialog
2115   directory =
2116       QFileDialog::getExistingDirectory(this, tr("Add folder"), directory);
2117   if (directory.isEmpty()) return;
2118 
2119   // Save last used directory
2120   settings_.setValue("add_folder_path", directory);
2121 
2122   // Add media
2123   MimeData* data = new MimeData;
2124   data->setUrls(QList<QUrl>() << QUrl::fromLocalFile(
2125                     QFileInfo(directory).canonicalFilePath()));
2126   AddToPlaylist(data);
2127 }
2128 
AddStream()2129 void MainWindow::AddStream() { add_stream_dialog_->show(); }
2130 
AddStreamAccepted()2131 void MainWindow::AddStreamAccepted() {
2132   MimeData* data = new MimeData;
2133   data->setUrls(QList<QUrl>() << add_stream_dialog_->url());
2134   AddToPlaylist(data);
2135 }
2136 
OpenRipCDDialog()2137 void MainWindow::OpenRipCDDialog() {
2138 #ifdef HAVE_AUDIOCD
2139   if (!rip_cd_dialog_) {
2140     rip_cd_dialog_.reset(new RipCDDialog);
2141   }
2142   if (rip_cd_dialog_->CheckCDIOIsValid()) {
2143     rip_cd_dialog_->show();
2144   } else {
2145     QMessageBox cdio_fail(QMessageBox::Critical, tr("Error"),
2146                           tr("Failed reading CD drive"));
2147     cdio_fail.exec();
2148   }
2149 #endif
2150 }
2151 
AddCDTracks()2152 void MainWindow::AddCDTracks() {
2153   MimeData* data = new MimeData;
2154   // We are putting empty data, but we specify cdda mimetype to indicate that
2155   // we want to load audio cd tracks
2156   data->open_in_new_playlist_ = true;
2157   data->setData(Playlist::kCddaMimeType, QByteArray());
2158   AddToPlaylist(data);
2159 }
2160 
ShowInLibrary()2161 void MainWindow::ShowInLibrary() {
2162   // Show the first valid selected track artist/album in LibraryView
2163   QModelIndexList proxy_indexes =
2164       ui_->playlist->view()->selectionModel()->selectedRows();
2165   SongList songs;
2166 
2167   for (const QModelIndex& proxy_index : proxy_indexes) {
2168     QModelIndex index =
2169         app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index);
2170     if (app_->playlist_manager()
2171             ->current()
2172             ->item_at(index.row())
2173             ->IsLocalLibraryItem()) {
2174       songs << app_->playlist_manager()
2175                    ->current()
2176                    ->item_at(index.row())
2177                    ->Metadata();
2178       break;
2179     }
2180   }
2181   QString search;
2182   if (!songs.isEmpty()) {
2183     search =
2184         "artist:" + songs.first().artist() + " album:" + songs.first().album();
2185   }
2186   library_view_->filter()->ShowInLibrary(search);
2187   FocusLibraryTab();
2188 }
2189 
PlaylistRemoveCurrent()2190 void MainWindow::PlaylistRemoveCurrent() {
2191   ui_->playlist->view()->RemoveSelected(false);
2192 }
2193 
PlaylistEditFinished(const QModelIndex & index)2194 void MainWindow::PlaylistEditFinished(const QModelIndex& index) {
2195   if (index == playlist_menu_index_) SelectionSetValue();
2196 }
2197 
CommandlineOptionsReceived(const QString & string_options)2198 void MainWindow::CommandlineOptionsReceived(const QString& string_options) {
2199   CommandlineOptions options;
2200   options.Load(string_options.toLatin1());
2201 
2202   if (options.is_empty()) {
2203     show();
2204     activateWindow();
2205   } else
2206     CommandlineOptionsReceived(options);
2207 }
2208 
CommandlineOptionsReceived(const CommandlineOptions & options)2209 void MainWindow::CommandlineOptionsReceived(const CommandlineOptions& options) {
2210   qLog(Debug) << "command line options received";
2211 
2212   switch (options.player_action()) {
2213     case CommandlineOptions::Player_Play:
2214       if (options.urls().empty()) {
2215         app_->player()->Play();
2216       }
2217       break;
2218     case CommandlineOptions::Player_PlayPause:
2219       app_->player()->PlayPause();
2220       break;
2221     case CommandlineOptions::Player_Pause:
2222       app_->player()->Pause();
2223       break;
2224     case CommandlineOptions::Player_Stop:
2225       app_->player()->Stop();
2226       break;
2227     case CommandlineOptions::Player_StopAfterCurrent:
2228       app_->player()->StopAfterCurrent();
2229       break;
2230     case CommandlineOptions::Player_Previous:
2231       app_->player()->Previous();
2232       break;
2233     case CommandlineOptions::Player_Next:
2234       app_->player()->Next();
2235       break;
2236     case CommandlineOptions::Player_RestartOrPrevious:
2237       app_->player()->RestartOrPrevious();
2238       break;
2239 
2240     case CommandlineOptions::Player_None:
2241       break;
2242   }
2243 
2244   if (!options.urls().empty()) {
2245     MimeData* data = new MimeData;
2246     data->setUrls(options.urls());
2247     // Behaviour depends on command line options, so set it here
2248     data->override_user_settings_ = true;
2249 
2250     if (options.player_action() == CommandlineOptions::Player_Play)
2251       data->play_now_ = true;
2252     else
2253       ApplyPlayBehaviour(doubleclick_playmode_, data);
2254 
2255     switch (options.url_list_action()) {
2256       case CommandlineOptions::UrlList_Load:
2257         data->clear_first_ = true;
2258         break;
2259       case CommandlineOptions::UrlList_Append:
2260         // Nothing to do
2261         break;
2262       case CommandlineOptions::UrlList_None:
2263         ApplyAddBehaviour(doubleclick_addmode_, data);
2264         break;
2265       case CommandlineOptions::UrlList_CreateNew:
2266         data->name_for_new_playlist_ = options.playlist_name();
2267         ApplyAddBehaviour(AddBehaviour_OpenInNew, data);
2268         break;
2269     }
2270 
2271     AddToPlaylist(data);
2272   }
2273 
2274   if (options.set_volume() != -1)
2275     app_->player()->SetVolume(options.set_volume());
2276 
2277   if (options.volume_modifier() != 0)
2278     app_->player()->SetVolume(app_->player()->GetVolume() +
2279                               options.volume_modifier());
2280 
2281   if (options.seek_to() != -1)
2282     app_->player()->SeekTo(options.seek_to());
2283   else if (options.seek_by() != 0)
2284     app_->player()->SeekTo(app_->player()->engine()->position_nanosec() /
2285                                kNsecPerSec +
2286                            options.seek_by());
2287 
2288   if (options.play_track_at() != -1)
2289     app_->player()->PlayAt(options.play_track_at(), Engine::Manual, true);
2290 
2291   qLog(Debug) << options.delete_current_track();
2292 
2293   // Just pass the url of the currently playing
2294   if (options.delete_current_track()) {
2295     qLog(Debug) << "deleting current track";
2296 
2297     Playlist* activePlaylist = app_->playlist_manager()->active();
2298     PlaylistItemPtr playlistItemPtr = activePlaylist->current_item();
2299 
2300     if (playlistItemPtr) {
2301       const QUrl& url = playlistItemPtr->Url();
2302       qLog(Debug) << url;
2303 
2304       std::shared_ptr<MusicStorage> storage(new FilesystemMusicStorage("/"));
2305 
2306       app_->player()->Next();
2307 
2308       DeleteFiles* delete_files = new DeleteFiles(app_->task_manager(), storage);
2309       connect(delete_files, SIGNAL(Finished(SongList)),
2310               SLOT(DeleteFinished(SongList)));
2311       delete_files->Start(url);
2312 
2313     } else {
2314       qLog(Debug) << "no currently playing track to delete";
2315     }
2316   }
2317 
2318   if (options.show_osd()) app_->player()->ShowOSD();
2319 
2320   if (options.toggle_pretty_osd()) app_->player()->TogglePrettyOSD();
2321 }
2322 
ForceShowOSD(const Song & song,const bool toggle)2323 void MainWindow::ForceShowOSD(const Song& song, const bool toggle) {
2324   if (toggle) {
2325     osd_->SetPrettyOSDToggleMode(toggle);
2326   }
2327   osd_->ReshowCurrentSong();
2328 }
2329 
Activate()2330 void MainWindow::Activate() { show(); }
2331 
LoadUrl(const QString & url)2332 bool MainWindow::LoadUrl(const QString& url) {
2333   if (!QFile::exists(url)) return false;
2334 
2335   MimeData* data = new MimeData;
2336   data->setUrls(QList<QUrl>() << QUrl::fromLocalFile(url));
2337   AddToPlaylist(data);
2338 
2339   return true;
2340 }
2341 
CheckForUpdates()2342 void MainWindow::CheckForUpdates() {
2343 #if defined(Q_OS_DARWIN)
2344   mac::CheckForUpdates();
2345 #endif
2346 }
2347 
PlaylistUndoRedoChanged(QAction * undo,QAction * redo)2348 void MainWindow::PlaylistUndoRedoChanged(QAction* undo, QAction* redo) {
2349   playlist_menu_->insertAction(playlist_undoredo_, undo);
2350   playlist_menu_->insertAction(playlist_undoredo_, redo);
2351 }
2352 
AddFilesToTranscoder()2353 void MainWindow::AddFilesToTranscoder() {
2354   QStringList filenames;
2355 
2356   for (const QModelIndex& index :
2357        ui_->playlist->view()->selectionModel()->selection().indexes()) {
2358     if (index.column() != 0) continue;
2359     int row =
2360         app_->playlist_manager()->current()->proxy()->mapToSource(index).row();
2361     PlaylistItemPtr item(app_->playlist_manager()->current()->item_at(row));
2362     Song song = item->Metadata();
2363     filenames << song.url().toLocalFile();
2364   }
2365 
2366   transcode_dialog_->SetFilenames(filenames);
2367 
2368   ShowTranscodeDialog();
2369 }
2370 
ShowLibraryConfig()2371 void MainWindow::ShowLibraryConfig() {
2372   settings_dialog_->OpenAtPage(SettingsDialog::Page_Library);
2373 }
2374 
TaskCountChanged(int count)2375 void MainWindow::TaskCountChanged(int count) {
2376   if (count == 0) {
2377     ui_->status_bar_stack->setCurrentWidget(ui_->playlist_summary_page);
2378   } else {
2379     ui_->status_bar_stack->setCurrentWidget(ui_->multi_loading_indicator);
2380   }
2381 }
2382 
NowPlayingWidgetPositionChanged(bool above_status_bar)2383 void MainWindow::NowPlayingWidgetPositionChanged(bool above_status_bar) {
2384   if (above_status_bar) {
2385     ui_->status_bar->setParent(ui_->centralWidget);
2386   } else {
2387     ui_->status_bar->setParent(ui_->player_controls_container);
2388   }
2389 
2390   ui_->status_bar->parentWidget()->layout()->addWidget(ui_->status_bar);
2391   ui_->status_bar->show();
2392 }
2393 
CopyFilesToLibrary(const QList<QUrl> & urls)2394 void MainWindow::CopyFilesToLibrary(const QList<QUrl>& urls) {
2395   organise_dialog_->SetDestinationModel(
2396       app_->library_model()->directory_model());
2397   organise_dialog_->SetUrls(urls);
2398   organise_dialog_->SetCopy(true);
2399   organise_dialog_->show();
2400 }
2401 
MoveFilesToLibrary(const QList<QUrl> & urls)2402 void MainWindow::MoveFilesToLibrary(const QList<QUrl>& urls) {
2403   organise_dialog_->SetDestinationModel(
2404       app_->library_model()->directory_model());
2405   organise_dialog_->SetUrls(urls);
2406   organise_dialog_->SetCopy(false);
2407   organise_dialog_->show();
2408 }
2409 
CopyFilesToDevice(const QList<QUrl> & urls)2410 void MainWindow::CopyFilesToDevice(const QList<QUrl>& urls) {
2411   organise_dialog_->SetDestinationModel(
2412       app_->device_manager()->connected_devices_model(), true);
2413   organise_dialog_->SetCopy(true);
2414   if (organise_dialog_->SetUrls(urls))
2415     organise_dialog_->show();
2416   else {
2417     QMessageBox::warning(
2418         this, tr("Error"),
2419         tr("None of the selected songs were suitable for copying to a device"));
2420   }
2421 }
2422 
EditFileTags(const QList<QUrl> & urls)2423 void MainWindow::EditFileTags(const QList<QUrl>& urls) {
2424   SongList songs;
2425   for (const QUrl& url : urls) {
2426     Song song;
2427     song.set_url(url);
2428     song.set_valid(true);
2429     song.set_filetype(Song::Type_Mpeg);
2430     songs << song;
2431   }
2432 
2433   edit_tag_dialog_->SetSongs(songs);
2434   edit_tag_dialog_->show();
2435 }
2436 
PlaylistCopyToLibrary()2437 void MainWindow::PlaylistCopyToLibrary() { PlaylistOrganiseSelected(true); }
2438 
PlaylistMoveToLibrary()2439 void MainWindow::PlaylistMoveToLibrary() { PlaylistOrganiseSelected(false); }
2440 
PlaylistOrganiseSelected(bool copy)2441 void MainWindow::PlaylistOrganiseSelected(bool copy) {
2442   QModelIndexList proxy_indexes =
2443       ui_->playlist->view()->selectionModel()->selectedRows();
2444   SongList songs;
2445 
2446   for (const QModelIndex& proxy_index : proxy_indexes) {
2447     QModelIndex index =
2448         app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index);
2449 
2450     songs << app_->playlist_manager()
2451                  ->current()
2452                  ->item_at(index.row())
2453                  ->Metadata();
2454   }
2455 
2456   organise_dialog_->SetDestinationModel(
2457       app_->library_model()->directory_model());
2458   organise_dialog_->SetSongs(songs);
2459   organise_dialog_->SetCopy(copy);
2460   organise_dialog_->show();
2461 }
2462 
PlaylistDelete()2463 void MainWindow::PlaylistDelete() {
2464   // Note: copied from LibraryView::Delete
2465 
2466   if (QMessageBox::warning(this, tr("Delete files"),
2467                            tr("These files will be permanently deleted from "
2468                               "disk, are you sure you want to continue?"),
2469                            QMessageBox::Yes,
2470                            QMessageBox::Cancel) != QMessageBox::Yes)
2471     return;
2472 
2473   std::shared_ptr<MusicStorage> storage(new FilesystemMusicStorage("/"));
2474 
2475   // Get selected songs
2476   SongList selected_songs;
2477   QModelIndexList proxy_indexes =
2478       ui_->playlist->view()->selectionModel()->selectedRows();
2479   for (const QModelIndex& proxy_index : proxy_indexes) {
2480     QModelIndex index =
2481         app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index);
2482     selected_songs << app_->playlist_manager()
2483                           ->current()
2484                           ->item_at(index.row())
2485                           ->Metadata();
2486   }
2487 
2488   if (app_->player()->GetState() == Engine::Playing) {
2489     if (app_->playlist_manager()->current()->rowCount() ==
2490         selected_songs.length()) {
2491       app_->player()->Stop();
2492     } else {
2493       for (Song x : selected_songs) {
2494         if (x == app_->player()->GetCurrentItem()->Metadata()) {
2495           app_->player()->Next();
2496         }
2497       }
2498     }
2499   }
2500 
2501   ui_->playlist->view()->RemoveSelected(true);
2502 
2503   DeleteFiles* delete_files = new DeleteFiles(app_->task_manager(), storage);
2504   connect(delete_files, SIGNAL(Finished(SongList)),
2505           SLOT(DeleteFinished(SongList)));
2506   delete_files->Start(selected_songs);
2507 }
2508 
PlaylistOpenInBrowser()2509 void MainWindow::PlaylistOpenInBrowser() {
2510   QList<QUrl> urls;
2511   QModelIndexList proxy_indexes =
2512       ui_->playlist->view()->selectionModel()->selectedRows();
2513 
2514   for (const QModelIndex& proxy_index : proxy_indexes) {
2515     const QModelIndex index =
2516         app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index);
2517     urls << QUrl(index.sibling(index.row(), Playlist::Column_Filename)
2518                      .data()
2519                      .toString());
2520   }
2521 
2522   Utilities::OpenInFileBrowser(urls);
2523 }
2524 
DeleteFinished(const SongList & songs_with_errors)2525 void MainWindow::DeleteFinished(const SongList& songs_with_errors) {
2526   if (songs_with_errors.isEmpty()) {
2527     qLog(Debug) << "Finished deleting songs";
2528     Playlist* activePlaylist = app_->playlist_manager()->active();
2529     if (activePlaylist->id() != -1) {
2530       activePlaylist->RemoveUnavailableSongs();
2531       qLog(Debug) << "Found active playlist and removed unavailable songs";
2532     }
2533 
2534     return;
2535   }
2536 
2537   OrganiseErrorDialog* dialog = new OrganiseErrorDialog(this);
2538   dialog->Show(OrganiseErrorDialog::Type_Delete, songs_with_errors);
2539   // It deletes itself when the user closes it
2540 }
2541 
PlaylistQueue()2542 void MainWindow::PlaylistQueue() {
2543   QModelIndexList indexes;
2544   for (const QModelIndex& proxy_index :
2545        ui_->playlist->view()->selectionModel()->selectedRows()) {
2546     indexes << app_->playlist_manager()->current()->proxy()->mapToSource(
2547         proxy_index);
2548   }
2549 
2550   app_->playlist_manager()->current()->queue()->ToggleTracks(indexes);
2551 }
2552 
PlaylistQueuePlayNext()2553 void MainWindow::PlaylistQueuePlayNext() {
2554   QModelIndexList indexes;
2555   for (const QModelIndex& proxy_index :
2556        ui_->playlist->view()->selectionModel()->selectedRows()) {
2557     indexes << app_->playlist_manager()->current()->proxy()->mapToSource(
2558         proxy_index);
2559   }
2560 
2561   app_->playlist_manager()->current()->queue()->InsertFirst(indexes);
2562 }
2563 
PlaylistSkip()2564 void MainWindow::PlaylistSkip() {
2565   QModelIndexList indexes;
2566   for (const QModelIndex& proxy_index :
2567        ui_->playlist->view()->selectionModel()->selectedRows()) {
2568     indexes << app_->playlist_manager()->current()->proxy()->mapToSource(
2569         proxy_index);
2570   }
2571 
2572   app_->playlist_manager()->current()->SkipTracks(indexes);
2573 }
2574 
PlaylistCopyToDevice()2575 void MainWindow::PlaylistCopyToDevice() {
2576   QModelIndexList proxy_indexes =
2577       ui_->playlist->view()->selectionModel()->selectedRows();
2578   SongList songs;
2579 
2580   for (const QModelIndex& proxy_index : proxy_indexes) {
2581     QModelIndex index =
2582         app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index);
2583 
2584     songs << app_->playlist_manager()
2585                  ->current()
2586                  ->item_at(index.row())
2587                  ->Metadata();
2588   }
2589 
2590   organise_dialog_->SetDestinationModel(
2591       app_->device_manager()->connected_devices_model(), true);
2592   organise_dialog_->SetCopy(true);
2593   if (organise_dialog_->SetSongs(songs))
2594     organise_dialog_->show();
2595   else {
2596     QMessageBox::warning(
2597         this, tr("Error"),
2598         tr("None of the selected songs were suitable for copying to a device"));
2599   }
2600 }
2601 
SearchForArtist()2602 void MainWindow::SearchForArtist() {
2603   PlaylistItemPtr item(
2604       app_->playlist_manager()->current()->item_at(playlist_menu_index_.row()));
2605   Song song = item->Metadata();
2606   if (!song.albumartist().isEmpty()) {
2607     DoGlobalSearch(song.albumartist().simplified());
2608   } else if (!song.artist().isEmpty()) {
2609     DoGlobalSearch(song.artist().simplified());
2610   }
2611 }
2612 
SearchForAlbum()2613 void MainWindow::SearchForAlbum() {
2614   PlaylistItemPtr item(
2615       app_->playlist_manager()->current()->item_at(playlist_menu_index_.row()));
2616   Song song = item->Metadata();
2617   if (!song.album().isEmpty()) {
2618     DoGlobalSearch(song.album().simplified());
2619   }
2620 }
2621 
ChangeLibraryQueryMode(QAction * action)2622 void MainWindow::ChangeLibraryQueryMode(QAction* action) {
2623   if (action == library_show_duplicates_) {
2624     library_view_->filter()->SetQueryMode(QueryOptions::QueryMode_Duplicates);
2625   } else if (action == library_show_untagged_) {
2626     library_view_->filter()->SetQueryMode(QueryOptions::QueryMode_Untagged);
2627   } else {
2628     library_view_->filter()->SetQueryMode(QueryOptions::QueryMode_All);
2629   }
2630 }
2631 
ShowCoverManager()2632 void MainWindow::ShowCoverManager() { cover_manager_->show(); }
2633 
CreateSettingsDialog()2634 SettingsDialog* MainWindow::CreateSettingsDialog() {
2635   SettingsDialog* settings_dialog =
2636       new SettingsDialog(app_, background_streams_);
2637   settings_dialog->SetGlobalShortcutManager(global_shortcuts_);
2638   settings_dialog->SetSongInfoView(song_info_view_);
2639 
2640   // Settings
2641   connect(settings_dialog, SIGNAL(accepted()), SLOT(ReloadAllSettings()));
2642 
2643 #ifdef HAVE_WIIMOTEDEV
2644   connect(settings_dialog, SIGNAL(SetWiimotedevInterfaceActived(bool)),
2645           wiimotedev_shortcuts_.get(),
2646           SLOT(SetWiimotedevInterfaceActived(bool)));
2647 #endif
2648 
2649   // Allows custom notification preview
2650   connect(settings_dialog,
2651           SIGNAL(NotificationPreview(OSD::Behaviour, QString, QString)),
2652           SLOT(HandleNotificationPreview(OSD::Behaviour, QString, QString)));
2653   return settings_dialog;
2654 }
2655 
OpenSettingsDialog()2656 void MainWindow::OpenSettingsDialog() { settings_dialog_->show(); }
2657 
OpenSettingsDialogAtPage(SettingsDialog::Page page)2658 void MainWindow::OpenSettingsDialogAtPage(SettingsDialog::Page page) {
2659   settings_dialog_->OpenAtPage(page);
2660 }
2661 
CreateEditTagDialog()2662 EditTagDialog* MainWindow::CreateEditTagDialog() {
2663   EditTagDialog* edit_tag_dialog = new EditTagDialog(app_);
2664   connect(edit_tag_dialog, SIGNAL(accepted()), SLOT(EditTagDialogAccepted()));
2665   connect(edit_tag_dialog, SIGNAL(Error(QString)),
2666           SLOT(ShowErrorDialog(QString)));
2667   return edit_tag_dialog;
2668 }
2669 
CreateStreamDiscoverer()2670 StreamDiscoverer* MainWindow::CreateStreamDiscoverer() {
2671   StreamDiscoverer* discoverer = new StreamDiscoverer();
2672   connect(discoverer, SIGNAL(DataReady(StreamDetails)),
2673           SLOT(ShowStreamDetails(StreamDetails)));
2674   connect(discoverer, SIGNAL(Error(QString)), SLOT(ShowErrorDialog(QString)));
2675   return discoverer;
2676 }
2677 
ShowAboutDialog()2678 void MainWindow::ShowAboutDialog() { about_dialog_->show(); }
2679 
ShowTranscodeDialog()2680 void MainWindow::ShowTranscodeDialog() { transcode_dialog_->show(); }
2681 
ShowErrorDialog(const QString & message)2682 void MainWindow::ShowErrorDialog(const QString& message) {
2683   error_dialog_->ShowMessage(message);
2684 }
2685 
CheckFullRescanRevisions()2686 void MainWindow::CheckFullRescanRevisions() {
2687   int from = app_->database()->startup_schema_version();
2688   int to = app_->database()->current_schema_version();
2689 
2690   // if we're restoring DB from scratch or nothing has
2691   // changed, do nothing
2692   if (from == 0 || from == to) {
2693     return;
2694   }
2695 
2696   // collect all reasons
2697   QSet<QString> reasons;
2698   for (int i = from; i <= to; i++) {
2699     QString reason = app_->library()->full_rescan_reason(i);
2700 
2701     if (!reason.isEmpty()) {
2702       reasons.insert(reason);
2703     }
2704   }
2705 
2706   // if we have any...
2707   if (!reasons.isEmpty()) {
2708     QString message = tr("The version of Clementine you've just updated to "
2709                          "requires a full library rescan "
2710                          "because of the new features listed below:") +
2711                       "<ul>";
2712     for (const QString& reason : reasons) {
2713       message += ("<li>" + reason + "</li>");
2714     }
2715     message += "</ul>" + tr("Would you like to run a full rescan right now?");
2716 
2717     if (QMessageBox::question(this, tr("Library rescan notice"), message,
2718                               QMessageBox::Yes,
2719                               QMessageBox::No) == QMessageBox::Yes) {
2720       app_->library()->FullScan();
2721     }
2722   }
2723 }
2724 
ShowQueueManager()2725 void MainWindow::ShowQueueManager() { queue_manager_->show(); }
2726 
ShowVisualisations()2727 void MainWindow::ShowVisualisations() {
2728 #ifdef HAVE_VISUALISATIONS
2729   if (!visualisation_) {
2730     visualisation_.reset(new VisualisationContainer);
2731 
2732     visualisation_->SetActions(ui_->action_previous_track,
2733                                ui_->action_play_pause, ui_->action_stop,
2734                                ui_->action_next_track);
2735     connect(app_->player(), SIGNAL(Stopped()), visualisation_.get(),
2736             SLOT(Stopped()));
2737     connect(app_->player(), SIGNAL(ForceShowOSD(Song, bool)),
2738             visualisation_.get(), SLOT(SongMetadataChanged(Song)));
2739     connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)),
2740             visualisation_.get(), SLOT(SongMetadataChanged(Song)));
2741 
2742     visualisation_->SetEngine(
2743         qobject_cast<GstEngine*>(app_->player()->engine()));
2744   }
2745 
2746   visualisation_->show();
2747 #endif  // HAVE_VISUALISATIONS
2748 }
2749 
ConnectInfoView(SongInfoBase * view)2750 void MainWindow::ConnectInfoView(SongInfoBase* view) {
2751   connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), view,
2752           SLOT(SongChanged(Song)));
2753   connect(app_->player(), SIGNAL(PlaylistFinished()), view,
2754           SLOT(SongFinished()));
2755   connect(app_->player(), SIGNAL(Stopped()), view, SLOT(SongFinished()));
2756 
2757   connect(view, SIGNAL(ShowSettingsDialog()), SLOT(ShowSongInfoConfig()));
2758   connect(view, SIGNAL(DoGlobalSearch(QString)), SLOT(DoGlobalSearch(QString)));
2759 }
2760 
AddSongInfoGenerator(smart_playlists::GeneratorPtr gen)2761 void MainWindow::AddSongInfoGenerator(smart_playlists::GeneratorPtr gen) {
2762   if (!gen) return;
2763   gen->set_library(app_->library_backend());
2764 
2765   AddToPlaylist(new smart_playlists::GeneratorMimeData(gen));
2766 }
2767 
ShowSongInfoConfig()2768 void MainWindow::ShowSongInfoConfig() {
2769   OpenSettingsDialogAtPage(SettingsDialog::Page_SongInformation);
2770 }
2771 
PlaylistViewSelectionModelChanged()2772 void MainWindow::PlaylistViewSelectionModelChanged() {
2773   connect(ui_->playlist->view()->selectionModel(),
2774           SIGNAL(currentChanged(QModelIndex, QModelIndex)),
2775           SLOT(PlaylistCurrentChanged(QModelIndex)));
2776 }
2777 
PlaylistCurrentChanged(const QModelIndex & proxy_current)2778 void MainWindow::PlaylistCurrentChanged(const QModelIndex& proxy_current) {
2779   const QModelIndex source_current =
2780       app_->playlist_manager()->current()->proxy()->mapToSource(proxy_current);
2781 
2782   // If the user moves the current index using the keyboard and then presses
2783   // F2, we don't want that editing the last column that was right clicked on.
2784   if (source_current != playlist_menu_index_)
2785     playlist_menu_index_ = QModelIndex();
2786 }
2787 
Raise()2788 void MainWindow::Raise() {
2789   show();
2790   activateWindow();
2791 }
2792 
2793 #ifdef Q_OS_WIN32
winEvent(MSG * msg,long *)2794 bool MainWindow::winEvent(MSG* msg, long*) {
2795   thumbbar_->HandleWinEvent(msg);
2796   return false;
2797 }
2798 #endif  // Q_OS_WIN32
2799 
Exit()2800 void MainWindow::Exit() {
2801   // FIXME: This may add an extra write.
2802   dirty_playback_ = true;
2803   settings_.setValue("show_sidebar",
2804                      ui_->action_toggle_show_sidebar->isChecked());
2805   settings_.endGroup();
2806   SaveSettings(&settings_);
2807   settings_.sync();
2808 
2809   if (app_->player()->engine()->is_fadeout_enabled()) {
2810     // To shut down the application when fadeout will be finished
2811     connect(app_->player()->engine(), SIGNAL(FadeoutFinishedSignal()), qApp,
2812             SLOT(quit()));
2813     if (app_->player()->GetState() == Engine::Playing) {
2814       app_->player()->Stop();
2815       hide();
2816       if (tray_icon_) tray_icon_->SetVisible(false);
2817       return;  // Don't quit the application now: wait for the fadeout finished
2818                // signal
2819     }
2820   }
2821   qApp->quit();
2822 }
2823 
AutoCompleteTags()2824 void MainWindow::AutoCompleteTags() {
2825   // Create the tag fetching stuff if it hasn't been already
2826   if (!tag_fetcher_) {
2827     tag_fetcher_.reset(new TagFetcher);
2828     track_selection_dialog_.reset(new TrackSelectionDialog);
2829     track_selection_dialog_->set_save_on_close(true);
2830 
2831     connect(tag_fetcher_.get(), SIGNAL(ResultAvailable(Song, SongList)),
2832             track_selection_dialog_.get(),
2833             SLOT(FetchTagFinished(Song, SongList)), Qt::QueuedConnection);
2834     connect(tag_fetcher_.get(), SIGNAL(Progress(Song, QString)),
2835             track_selection_dialog_.get(),
2836             SLOT(FetchTagProgress(Song, QString)));
2837     connect(track_selection_dialog_.get(), SIGNAL(accepted()),
2838             SLOT(AutoCompleteTagsAccepted()));
2839     connect(track_selection_dialog_.get(), SIGNAL(finished(int)),
2840             tag_fetcher_.get(), SLOT(Cancel()));
2841     connect(track_selection_dialog_.get(), SIGNAL(Error(QString)),
2842             SLOT(ShowErrorDialog(QString)));
2843   }
2844 
2845   // Get the selected songs and start fetching tags for them
2846   SongList songs;
2847   autocomplete_tag_items_.clear();
2848   for (const QModelIndex& index :
2849        ui_->playlist->view()->selectionModel()->selection().indexes()) {
2850     if (index.column() != 0) continue;
2851     int row =
2852         app_->playlist_manager()->current()->proxy()->mapToSource(index).row();
2853     PlaylistItemPtr item(app_->playlist_manager()->current()->item_at(row));
2854     Song song = item->Metadata();
2855 
2856     if (song.IsEditable()) {
2857       songs << song;
2858       autocomplete_tag_items_ << item;
2859     }
2860   }
2861 
2862   track_selection_dialog_->Init(songs);
2863   tag_fetcher_->StartFetch(songs);
2864 
2865   track_selection_dialog_->show();
2866 }
2867 
AutoCompleteTagsAccepted()2868 void MainWindow::AutoCompleteTagsAccepted() {
2869   for (PlaylistItemPtr item : autocomplete_tag_items_) {
2870     item->Reload();
2871   }
2872 
2873   // This is really lame but we don't know what rows have changed
2874   ui_->playlist->view()->update();
2875 }
2876 
CreateOverlayedIcon(int position,int scrobble_point)2877 QPixmap MainWindow::CreateOverlayedIcon(int position, int scrobble_point) {
2878   QPixmap normal_icon =
2879       IconLoader::Load("as_light", IconLoader::Lastfm).pixmap(16);
2880   QPixmap light_icon = IconLoader::Load("as", IconLoader::Lastfm).pixmap(16);
2881   QRect rect(normal_icon.rect());
2882 
2883   // calculates the progress
2884   double perc = 1.0 - ((double)position / (double)scrobble_point);
2885 
2886   QPolygon mask;
2887   mask << rect.topRight();
2888   mask << rect.topLeft();
2889   mask << QPoint(rect.left(), rect.height() * perc);
2890   mask << QPoint(rect.right(), (rect.height()) * perc);
2891 
2892   QPixmap ret(light_icon);
2893   QPainter p(&ret);
2894 
2895   // Draw the red icon over the light red one
2896   p.setClipRegion(mask);
2897   p.drawPixmap(0, 0, normal_icon);
2898   p.setClipping(false);
2899 
2900   p.end();
2901 
2902   return ret;
2903 }
2904 
SetToggleScrobblingIcon(bool value)2905 void MainWindow::SetToggleScrobblingIcon(bool value) {
2906   if (!value) {
2907     ui_->action_toggle_scrobbling->setIcon(
2908         IconLoader::Load("as_disabled", IconLoader::Lastfm));
2909   } else {
2910     ui_->action_toggle_scrobbling->setIcon(
2911         IconLoader::Load("as_light", IconLoader::Lastfm));
2912   }
2913 }
2914 
2915 #ifdef HAVE_LIBLASTFM
ScrobbleSubmitted()2916 void MainWindow::ScrobbleSubmitted() {
2917   const bool last_fm_enabled = ui_->action_toggle_scrobbling->isVisible() &&
2918                                app_->scrobbler()->IsScrobblingEnabled() &&
2919                                app_->scrobbler()->IsAuthenticated();
2920 
2921   app_->playlist_manager()->active()->set_lastfm_status(
2922       Playlist::LastFM_Scrobbled);
2923 
2924   // update the button icon
2925   if (last_fm_enabled)
2926     ui_->action_toggle_scrobbling->setIcon(
2927         IconLoader::Load("as", IconLoader::Lastfm));
2928 }
2929 
ScrobbleError(int value)2930 void MainWindow::ScrobbleError(int value) {
2931   switch (value) {
2932     case -1:
2933       // custom error value got from initial validity check
2934       app_->playlist_manager()->active()->set_lastfm_status(
2935           Playlist::LastFM_Invalid);
2936       break;
2937 
2938     case 30:
2939       // Hack: when offline, liblastfm doesn't inform us, so set the status
2940       // as queued; in this way we won't try to scrobble again, it will be done
2941       // automatically
2942       app_->playlist_manager()->active()->set_lastfm_status(
2943           Playlist::LastFM_Queued);
2944       break;
2945 
2946     default:
2947       if (value > 3) {
2948         // we're for sure in an error state
2949         app_->playlist_manager()->active()->set_lastfm_status(
2950             Playlist::LastFM_Error);
2951         qLog(Warning) << "Last.fm scrobbling error: " << value;
2952       }
2953       break;
2954   }
2955 }
2956 #endif
2957 
HandleNotificationPreview(OSD::Behaviour type,QString line1,QString line2)2958 void MainWindow::HandleNotificationPreview(OSD::Behaviour type, QString line1,
2959                                            QString line2) {
2960   if (!app_->playlist_manager()->current()->GetAllSongs().isEmpty()) {
2961     // Show a preview notification for the first song in the current playlist
2962     osd_->ShowPreview(
2963         type, line1, line2,
2964         app_->playlist_manager()->current()->GetAllSongs().first());
2965   } else {
2966     qLog(Debug) << "The current playlist is empty, showing a fake song";
2967     // Create a fake song
2968     Song fake;
2969     fake.Init("Title", "Artist", "Album", 123);
2970     fake.set_genre("Classical");
2971     fake.set_composer("Anonymous");
2972     fake.set_performer("Anonymous");
2973     fake.set_lyrics("None");
2974     fake.set_track(1);
2975     fake.set_disc(1);
2976     fake.set_year(2011);
2977 
2978     osd_->ShowPreview(type, line1, line2, fake);
2979   }
2980 }
2981 
ScrollToInternetIndex(const QModelIndex & index)2982 void MainWindow::ScrollToInternetIndex(const QModelIndex& index) {
2983   internet_view_->ScrollToIndex(index);
2984   ui_->tabs->setCurrentWidget(internet_view_);
2985 }
2986 
AddPodcast()2987 void MainWindow::AddPodcast() {
2988   app_->internet_model()->Service<PodcastService>()->AddPodcast();
2989 }
2990 
FocusLibraryTab()2991 void MainWindow::FocusLibraryTab() {
2992   ui_->tabs->setCurrentWidget(library_view_);
2993 }
2994 
FocusGlobalSearchField()2995 void MainWindow::FocusGlobalSearchField() {
2996   ui_->tabs->setCurrentWidget(global_search_view_);
2997   global_search_view_->FocusSearchField();
2998 }
2999 
DoGlobalSearch(const QString & query)3000 void MainWindow::DoGlobalSearch(const QString& query) {
3001   FocusGlobalSearchField();
3002   global_search_view_->StartSearch(query);
3003 }
3004 
ShowConsole()3005 void MainWindow::ShowConsole() {
3006   Console* console = new Console(app_, this);
3007   console->show();
3008 }
3009 
keyPressEvent(QKeyEvent * event)3010 void MainWindow::keyPressEvent(QKeyEvent* event) {
3011   if (event->key() == Qt::Key_Space) {
3012     app_->player()->PlayPause();
3013     event->accept();
3014   } else {
3015     QMainWindow::keyPressEvent(event);
3016   }
3017 }
3018