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