/* * Strawberry Music Player * This file was part of Clementine. * Copyright 2010, David Sansome * Copyright 2013-2021, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Strawberry is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Strawberry. If not, see . * */ #include "config.h" #include "version.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core/logging.h" #include "core/networkaccessmanager.h" #include "mainwindow.h" #include "ui_mainwindow.h" #include "utilities.h" #include "timeconstants.h" #include "commandlineoptions.h" #include "mimedata.h" #include "iconloader.h" #include "taskmanager.h" #include "song.h" #include "stylehelper.h" #include "stylesheetloader.h" #include "systemtrayicon.h" #include "application.h" #include "database.h" #include "player.h" #include "appearance.h" #include "filesystemmusicstorage.h" #include "deletefiles.h" #include "engine/enginetype.h" #include "engine/enginebase.h" #include "engine/engine_fwd.h" #include "dialogs/errordialog.h" #include "dialogs/about.h" #include "dialogs/console.h" #include "dialogs/trackselectiondialog.h" #include "dialogs/edittagdialog.h" #include "dialogs/addstreamdialog.h" #include "dialogs/deleteconfirmationdialog.h" #include "dialogs/lastfmimportdialog.h" #include "dialogs/snapdialog.h" #include "organize/organizedialog.h" #include "widgets/fancytabwidget.h" #include "widgets/playingwidget.h" #include "widgets/volumeslider.h" #include "widgets/fileview.h" #include "widgets/multiloadingindicator.h" #include "widgets/trackslider.h" #include "osd/osdbase.h" #include "context/contextview.h" #include "context/contextalbumsview.h" #include "collection/collection.h" #include "collection/collectionbackend.h" #include "collection/collectiondirectorymodel.h" #include "collection/collectionfilterwidget.h" #include "collection/collectionmodel.h" #include "collection/collectionquery.h" #include "collection/collectionview.h" #include "collection/collectionviewcontainer.h" #include "playlist/playlist.h" #include "playlist/playlistbackend.h" #include "playlist/playlistcontainer.h" #include "playlist/playlistlistcontainer.h" #include "playlist/playlistmanager.h" #include "playlist/playlistsequence.h" #include "playlist/playlistview.h" #include "queue/queue.h" #include "queue/queueview.h" #include "playlistparsers/playlistparser.h" #include "analyzer/analyzercontainer.h" #include "equalizer/equalizer.h" #ifdef HAVE_GLOBALSHORTCUTS # include "globalshortcuts/globalshortcutsmanager.h" #endif #include "covermanager/albumcovermanager.h" #include "covermanager/albumcoverchoicecontroller.h" #include "covermanager/albumcoverloaderresult.h" #include "covermanager/currentalbumcoverloader.h" #include "covermanager/coverproviders.h" #include "covermanager/albumcoverimageresult.h" #include "lyrics/lyricsproviders.h" #ifndef Q_OS_WIN # include "device/devicemanager.h" # include "device/devicestatefiltermodel.h" # include "device/deviceview.h" # include "device/deviceviewcontainer.h" #endif #include "transcoder/transcodedialog.h" #include "settings/settingsdialog.h" #include "settings/behavioursettingspage.h" #include "settings/backendsettingspage.h" #include "settings/playlistsettingspage.h" #ifdef HAVE_SUBSONIC # include "settings/subsonicsettingspage.h" # include "scrobbler/subsonicscrobbler.h" #endif #ifdef HAVE_TIDAL # include "tidal/tidalservice.h" # include "settings/tidalsettingspage.h" #endif #ifdef HAVE_QOBUZ # include "settings/qobuzsettingspage.h" #endif #include "internet/internetservices.h" #include "internet/internetservice.h" #include "internet/internetsongsview.h" #include "internet/internettabsview.h" #include "internet/internetcollectionview.h" #include "internet/internetsearchview.h" #include "scrobbler/audioscrobbler.h" #include "scrobbler/lastfmimport.h" #if defined(HAVE_GSTREAMER) && defined(HAVE_CHROMAPRINT) # include "musicbrainz/tagfetcher.h" #endif #ifdef Q_OS_MACOS # include "core/macsystemtrayicon.h" #endif #ifdef HAVE_MOODBAR # include "moodbar/moodbarcontroller.h" # include "moodbar/moodbarproxystyle.h" #endif #include "smartplaylists/smartplaylistsviewcontainer.h" #include "smartplaylists/smartplaylistsview.h" #ifdef Q_OS_WIN # include "windows7thumbbar.h" #endif #ifdef HAVE_QTSPARKLE # if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) # include # else # include # endif #endif // HAVE_QTSPARKLE const char *MainWindow::kSettingsGroup = "MainWindow"; const char *MainWindow::kAllFilesFilterSpec = QT_TR_NOOP("All Files (*)"); namespace { const int kTrackSliderUpdateTimeMs = 200; const int kTrackPositionUpdateTimeMs = 1000; } MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSDBase *osd, const CommandlineOptions &options, QWidget *parent) : QMainWindow(parent), ui_(new Ui_MainWindow), #ifdef Q_OS_WIN thumbbar_(new Windows7ThumbBar(this)), #endif app_(app), tray_icon_(tray_icon), osd_(osd), console_([=]() { Console *console = new Console(app); return console; }), edit_tag_dialog_(std::bind(&MainWindow::CreateEditTagDialog, this)), album_cover_choice_controller_(new AlbumCoverChoiceController(this)), #ifdef HAVE_GLOBALSHORTCUTS globalshortcuts_manager_(new GlobalShortcutsManager(this)), #endif context_view_(new ContextView(this)), collection_view_(new CollectionViewContainer(this)), file_view_(new FileView(this)), #ifndef Q_OS_WIN device_view_(new DeviceViewContainer(this)), #endif playlist_list_(new PlaylistListContainer(this)), queue_view_(new QueueView(this)), settings_dialog_(std::bind(&MainWindow::CreateSettingsDialog, this)), cover_manager_([=]() { AlbumCoverManager *cover_manager = new AlbumCoverManager(app, app->collection_backend(), this); cover_manager->Init(); // Cover manager connections QObject::connect(cover_manager, &AlbumCoverManager::AddToPlaylist, this, &MainWindow::AddToPlaylist); return cover_manager; }), equalizer_(new Equalizer), organize_dialog_([=]() { OrganizeDialog *dialog = new OrganizeDialog(app->task_manager(), app->collection_backend(), this); dialog->SetDestinationModel(app->collection()->model()->directory_model()); return dialog; }), #ifdef HAVE_GSTREAMER transcode_dialog_([=]() { TranscodeDialog *dialog = new TranscodeDialog(this); return dialog; }), #endif add_stream_dialog_([=]() { AddStreamDialog *add_stream_dialog = new AddStreamDialog; QObject::connect(add_stream_dialog, &AddStreamDialog::accepted, this, &MainWindow::AddStreamAccepted); return add_stream_dialog; }), smartplaylists_view_(new SmartPlaylistsViewContainer(app, this)), #ifdef HAVE_SUBSONIC subsonic_view_(new InternetSongsView(app_, app->internet_services()->ServiceBySource(Song::Source_Subsonic), SubsonicSettingsPage::kSettingsGroup, SettingsDialog::Page_Subsonic, this)), #endif #ifdef HAVE_TIDAL tidal_view_(new InternetTabsView(app_, app->internet_services()->ServiceBySource(Song::Source_Tidal), TidalSettingsPage::kSettingsGroup, SettingsDialog::Page_Tidal, this)), #endif #ifdef HAVE_QOBUZ qobuz_view_(new InternetTabsView(app_, app->internet_services()->ServiceBySource(Song::Source_Qobuz), QobuzSettingsPage::kSettingsGroup, SettingsDialog::Page_Qobuz, this)), #endif lastfm_import_dialog_(new LastFMImportDialog(app_->lastfm_import(), this)), collection_show_all_(nullptr), collection_show_duplicates_(nullptr), collection_show_untagged_(nullptr), playlist_menu_(new QMenu(this)), playlist_play_pause_(nullptr), playlist_stop_after_(nullptr), playlist_undoredo_(nullptr), playlist_copy_url_(nullptr), playlist_show_in_collection_(nullptr), playlist_copy_to_collection_(nullptr), playlist_move_to_collection_(nullptr), playlist_open_in_browser_(nullptr), playlist_organize_(nullptr), #ifndef Q_OS_WIN playlist_copy_to_device_(nullptr), #endif playlist_delete_(nullptr), playlist_queue_(nullptr), playlist_queue_play_next_(nullptr), playlist_skip_(nullptr), playlist_add_to_another_(nullptr), playlistitem_actions_separator_(nullptr), playlist_rescan_songs_(nullptr), collection_sort_model_(new QSortFilterProxyModel(this)), track_position_timer_(new QTimer(this)), track_slider_timer_(new QTimer(this)), keep_running_(false), playing_widget_(true), doubleclick_addmode_(BehaviourSettingsPage::AddBehaviour_Append), doubleclick_playmode_(BehaviourSettingsPage::PlayBehaviour_Never), doubleclick_playlist_addmode_(BehaviourSettingsPage::PlaylistAddBehaviour_Play), menu_playmode_(BehaviourSettingsPage::PlayBehaviour_Never), initialized_(false), was_maximized_(true), was_minimized_(false), hidden_(false), exit_count_(0), delete_files_(false) { qLog(Debug) << "Starting"; QObject::connect(app, &Application::ErrorAdded, this, &MainWindow::ShowErrorDialog); QObject::connect(app, &Application::SettingsDialogRequested, this, &MainWindow::OpenSettingsDialogAtPage); // Initialize the UI ui_->setupUi(this); album_cover_choice_controller_->Init(app); ui_->multi_loading_indicator->SetTaskManager(app_->task_manager()); context_view_->Init(app_, collection_view_->view(), album_cover_choice_controller_); ui_->widget_playing->Init(app_, album_cover_choice_controller_); // Initialize the search widget StyleHelper::setBaseColor(palette().color(QPalette::Highlight).darker()); // Add tabs to the fancy tab widget ui_->tabs->AddTab(context_view_, "context", IconLoader::Load("strawberry"), tr("Context")); ui_->tabs->AddTab(collection_view_, "collection", IconLoader::Load("library-music"), tr("Collection")); ui_->tabs->AddTab(queue_view_, "queue", IconLoader::Load("footsteps"), tr("Queue")); ui_->tabs->AddTab(playlist_list_, "playlists", IconLoader::Load("view-media-playlist"), tr("Playlists")); ui_->tabs->AddTab(smartplaylists_view_, "smartplaylists", IconLoader::Load("view-media-playlist"), tr("Smart playlists")); ui_->tabs->AddTab(file_view_, "files", IconLoader::Load("document-open"), tr("Files")); #ifndef Q_OS_WIN ui_->tabs->AddTab(device_view_, "devices", IconLoader::Load("device"), tr("Devices")); #endif #ifdef HAVE_SUBSONIC ui_->tabs->AddTab(subsonic_view_, "subsonic", IconLoader::Load("subsonic"), tr("Subsonic")); #endif #ifdef HAVE_TIDAL ui_->tabs->AddTab(tidal_view_, "tidal", IconLoader::Load("tidal"), tr("Tidal")); #endif #ifdef HAVE_QOBUZ ui_->tabs->AddTab(qobuz_view_, "qobuz", IconLoader::Load("qobuz"), tr("Qobuz")); #endif // Add the playing widget to the fancy tab widget ui_->tabs->addBottomWidget(ui_->widget_playing); //ui_->tabs->SetBackgroundPixmap(QPixmap(":/pictures/strawberry-background.png")); ui_->tabs->Load(kSettingsGroup); track_position_timer_->setInterval(kTrackPositionUpdateTimeMs); QObject::connect(track_position_timer_, &QTimer::timeout, this, &MainWindow::UpdateTrackPosition); track_slider_timer_->setInterval(kTrackSliderUpdateTimeMs); QObject::connect(track_slider_timer_, &QTimer::timeout, this, &MainWindow::UpdateTrackSliderPosition); // Start initializing the player qLog(Debug) << "Initializing player"; app_->player()->SetAnalyzer(ui_->analyzer); app_->player()->SetEqualizer(equalizer_.get()); app_->player()->Init(); EngineChanged(app_->player()->engine()->type()); int volume = app_->player()->GetVolume(); ui_->volume->setValue(volume); VolumeChanged(volume); // Models qLog(Debug) << "Creating models"; collection_sort_model_->setSourceModel(app_->collection()->model()); collection_sort_model_->setSortRole(CollectionModel::Role_SortText); collection_sort_model_->setDynamicSortFilter(true); collection_sort_model_->setSortLocaleAware(true); collection_sort_model_->sort(0); qLog(Debug) << "Creating models finished"; QObject::connect(ui_->playlist, &PlaylistContainer::ViewSelectionModelChanged, this, &MainWindow::PlaylistViewSelectionModelChanged); ui_->playlist->SetManager(app_->playlist_manager()); ui_->playlist->view()->Init(app_); collection_view_->view()->setModel(collection_sort_model_); collection_view_->view()->SetApplication(app_); #ifndef Q_OS_WIN device_view_->view()->SetApplication(app_); #endif playlist_list_->SetApplication(app_); organize_dialog_->SetDestinationModel(app_->collection()->model()->directory_model()); // Icons qLog(Debug) << "Creating UI"; // Help menu ui_->action_about_strawberry->setIcon(IconLoader::Load("strawberry")); ui_->action_about_qt->setIcon(QIcon(":/qt-project.org/qmessagebox/images/qtlogo-64.png")); // Music menu ui_->action_open_file->setIcon(IconLoader::Load("document-open")); ui_->action_open_cd->setIcon(IconLoader::Load("media-optical")); ui_->action_previous_track->setIcon(IconLoader::Load("media-skip-backward")); ui_->action_play_pause->setIcon(IconLoader::Load("media-playback-start")); ui_->action_stop->setIcon(IconLoader::Load("media-playback-stop")); ui_->action_stop_after_this_track->setIcon(IconLoader::Load("media-playback-stop")); ui_->action_next_track->setIcon(IconLoader::Load("media-skip-forward")); ui_->action_quit->setIcon(IconLoader::Load("application-exit")); // Playlist ui_->action_add_file->setIcon(IconLoader::Load("document-open")); ui_->action_add_folder->setIcon(IconLoader::Load("document-open-folder")); ui_->action_add_stream->setIcon(IconLoader::Load("document-open-remote")); ui_->action_shuffle_mode->setIcon(IconLoader::Load("media-playlist-shuffle")); ui_->action_repeat_mode->setIcon(IconLoader::Load("media-playlist-repeat")); ui_->action_new_playlist->setIcon(IconLoader::Load("document-new")); ui_->action_save_playlist->setIcon(IconLoader::Load("document-save")); ui_->action_load_playlist->setIcon(IconLoader::Load("document-open")); ui_->action_jump->setIcon(IconLoader::Load("go-jump")); ui_->action_clear_playlist->setIcon(IconLoader::Load("edit-clear-list")); ui_->action_shuffle->setIcon(IconLoader::Load("media-playlist-shuffle")); ui_->action_remove_duplicates->setIcon(IconLoader::Load("list-remove")); ui_->action_remove_unavailable->setIcon(IconLoader::Load("list-remove")); ui_->action_remove_from_playlist->setIcon(IconLoader::Load("list-remove")); // Configure ui_->action_cover_manager->setIcon(IconLoader::Load("document-download")); ui_->action_edit_track->setIcon(IconLoader::Load("edit-rename")); ui_->action_edit_value->setIcon(IconLoader::Load("edit-rename")); ui_->action_selection_set_value->setIcon(IconLoader::Load("edit-rename")); ui_->action_equalizer->setIcon(IconLoader::Load("equalizer")); ui_->action_transcoder->setIcon(IconLoader::Load("tools-wizard")); ui_->action_update_collection->setIcon(IconLoader::Load("view-refresh")); ui_->action_full_collection_scan->setIcon(IconLoader::Load("view-refresh")); ui_->action_settings->setIcon(IconLoader::Load("configure")); ui_->action_import_data_from_last_fm->setIcon(IconLoader::Load("scrobble")); // Scrobble ui_->action_toggle_scrobbling->setIcon(IconLoader::Load("scrobble-disabled")); ui_->action_love->setIcon(IconLoader::Load("love")); // File view connections QObject::connect(file_view_, &FileView::AddToPlaylist, this, &MainWindow::AddToPlaylist); QObject::connect(file_view_, &FileView::PathChanged, this, &MainWindow::FilePathChanged); #ifdef HAVE_GSTREAMER QObject::connect(file_view_, &FileView::CopyToCollection, this, &MainWindow::CopyFilesToCollection); QObject::connect(file_view_, &FileView::MoveToCollection, this, &MainWindow::MoveFilesToCollection); QObject::connect(file_view_, &FileView::EditTags, this, &MainWindow::EditFileTags); #ifndef Q_OS_WIN QObject::connect(file_view_, &FileView::CopyToDevice, this, &MainWindow::CopyFilesToDevice); #endif #endif file_view_->SetTaskManager(app_->task_manager()); // Action connections QObject::connect(ui_->action_next_track, &QAction::triggered, app_->player(), &Player::Next); QObject::connect(ui_->action_previous_track, &QAction::triggered, app_->player(), &Player::Previous); QObject::connect(ui_->action_play_pause, &QAction::triggered, app_->player(), &Player::PlayPauseHelper); QObject::connect(ui_->action_stop, &QAction::triggered, app_->player(), &Player::Stop); QObject::connect(ui_->action_quit, &QAction::triggered, this, &MainWindow::Exit); QObject::connect(ui_->action_stop_after_this_track, &QAction::triggered, this, &MainWindow::StopAfterCurrent); QObject::connect(ui_->action_mute, &QAction::triggered, app_->player(), &Player::Mute); QObject::connect(ui_->action_clear_playlist, &QAction::triggered, this, &MainWindow::PlaylistClearCurrent); QObject::connect(ui_->action_remove_duplicates, &QAction::triggered, app_->playlist_manager(), &PlaylistManager::RemoveDuplicatesCurrent); QObject::connect(ui_->action_remove_unavailable, &QAction::triggered, app_->playlist_manager(), &PlaylistManager::RemoveUnavailableCurrent); QObject::connect(ui_->action_remove_from_playlist, &QAction::triggered, this, &MainWindow::PlaylistRemoveCurrent); QObject::connect(ui_->action_edit_track, &QAction::triggered, this, &MainWindow::EditTracks); QObject::connect(ui_->action_renumber_tracks, &QAction::triggered, this, &MainWindow::RenumberTracks); QObject::connect(ui_->action_selection_set_value, &QAction::triggered, this, &MainWindow::SelectionSetValue); QObject::connect(ui_->action_edit_value, &QAction::triggered, this, &MainWindow::EditValue); #if defined(HAVE_GSTREAMER) && defined(HAVE_CHROMAPRINT) QObject::connect(ui_->action_auto_complete_tags, &QAction::triggered, this, &MainWindow::AutoCompleteTags); #endif QObject::connect(ui_->action_settings, &QAction::triggered, this, &MainWindow::OpenSettingsDialog); QObject::connect(ui_->action_import_data_from_last_fm, &QAction::triggered, lastfm_import_dialog_, &LastFMImportDialog::show); QObject::connect(ui_->action_toggle_show_sidebar, &QAction::toggled, this, &MainWindow::ToggleSidebar); QObject::connect(ui_->action_about_strawberry, &QAction::triggered, this, &MainWindow::ShowAboutDialog); QObject::connect(ui_->action_about_qt, &QAction::triggered, qApp, &QApplication::aboutQt); QObject::connect(ui_->action_shuffle, &QAction::triggered, app_->playlist_manager(), &PlaylistManager::ShuffleCurrent); QObject::connect(ui_->action_open_file, &QAction::triggered, this, &MainWindow::AddFile); QObject::connect(ui_->action_open_cd, &QAction::triggered, this, &MainWindow::AddCDTracks); QObject::connect(ui_->action_add_file, &QAction::triggered, this, &MainWindow::AddFile); QObject::connect(ui_->action_add_folder, &QAction::triggered, this, &MainWindow::AddFolder); QObject::connect(ui_->action_add_stream, &QAction::triggered, this, &MainWindow::AddStream); QObject::connect(ui_->action_cover_manager, &QAction::triggered, this, &MainWindow::ShowCoverManager); QObject::connect(ui_->action_equalizer, &QAction::triggered, this, &MainWindow::ShowEqualizer); #if defined(HAVE_GSTREAMER) QObject::connect(ui_->action_transcoder, &QAction::triggered, this, &MainWindow::ShowTranscodeDialog); #else ui_->action_transcoder->setDisabled(true); #endif QObject::connect(ui_->action_jump, &QAction::triggered, ui_->playlist->view(), &PlaylistView::JumpToCurrentlyPlayingTrack); QObject::connect(ui_->action_update_collection, &QAction::triggered, app_->collection(), &SCollection::IncrementalScan); QObject::connect(ui_->action_full_collection_scan, &QAction::triggered, app_->collection(), &SCollection::FullScan); QObject::connect(ui_->action_abort_collection_scan, &QAction::triggered, app_->collection(), &SCollection::AbortScan); #if defined(HAVE_GSTREAMER) QObject::connect(ui_->action_add_files_to_transcoder, &QAction::triggered, this, &MainWindow::AddFilesToTranscoder); ui_->action_add_files_to_transcoder->setIcon(IconLoader::Load("tools-wizard")); #else ui_->action_add_files_to_transcoder->setDisabled(true); #endif QObject::connect(ui_->action_toggle_scrobbling, &QAction::triggered, app_->scrobbler(), &AudioScrobbler::ToggleScrobbling); QObject::connect(ui_->action_love, &QAction::triggered, this, &MainWindow::Love); QObject::connect(app_->scrobbler(), &AudioScrobbler::ErrorMessage, this, &MainWindow::ShowErrorDialog); // Playlist view actions ui_->action_next_playlist->setShortcuts(QList() << QKeySequence::fromString("Ctrl+Tab")<< QKeySequence::fromString("Ctrl+PgDown")); ui_->action_previous_playlist->setShortcuts(QList() << QKeySequence::fromString("Ctrl+Shift+Tab")<< QKeySequence::fromString("Ctrl+PgUp")); // Actions for switching tabs will be global to the entire window, so adding them here addAction(ui_->action_next_playlist); addAction(ui_->action_previous_playlist); // Give actions to buttons ui_->forward_button->setDefaultAction(ui_->action_next_track); ui_->back_button->setDefaultAction(ui_->action_previous_track); ui_->pause_play_button->setDefaultAction(ui_->action_play_pause); ui_->stop_button->setDefaultAction(ui_->action_stop); ui_->button_scrobble->setDefaultAction(ui_->action_toggle_scrobbling); ui_->button_love->setDefaultAction(ui_->action_love); ui_->playlist->SetActions(ui_->action_new_playlist, ui_->action_load_playlist, ui_->action_save_playlist, ui_->action_clear_playlist, ui_->action_next_playlist, /* These two actions aren't associated */ ui_->action_previous_playlist /* to a button but to the main window */ ); // Add the shuffle and repeat action groups to the menu ui_->action_shuffle_mode->setMenu(ui_->playlist_sequence->shuffle_menu()); ui_->action_repeat_mode->setMenu(ui_->playlist_sequence->repeat_menu()); // Stop actions QMenu *stop_menu = new QMenu(this); stop_menu->addAction(ui_->action_stop); stop_menu->addAction(ui_->action_stop_after_this_track); ui_->stop_button->setMenu(stop_menu); // Player connections QObject::connect(ui_->volume, &VolumeSlider::valueChanged, app_->player(), &Player::SetVolume); QObject::connect(app_->player(), &Player::EngineChanged, this, &MainWindow::EngineChanged); QObject::connect(app_->player(), &Player::Error, this, &MainWindow::ShowErrorDialog); QObject::connect(app_->player(), &Player::SongChangeRequestProcessed, app_->playlist_manager(), &PlaylistManager::SongChangeRequestProcessed); QObject::connect(app_->player(), &Player::Paused, this, &MainWindow::MediaPaused); QObject::connect(app_->player(), &Player::Playing, this, &MainWindow::MediaPlaying); QObject::connect(app_->player(), &Player::Stopped, this, &MainWindow::MediaStopped); QObject::connect(app_->player(), &Player::Seeked, this, &MainWindow::Seeked); QObject::connect(app_->player(), &Player::TrackSkipped, this, &MainWindow::TrackSkipped); QObject::connect(app_->player(), &Player::VolumeChanged, this, &MainWindow::VolumeChanged); QObject::connect(app_->player(), &Player::Paused, ui_->playlist, &PlaylistContainer::ActivePaused); QObject::connect(app_->player(), &Player::Playing, ui_->playlist, &PlaylistContainer::ActivePlaying); QObject::connect(app_->player(), &Player::Stopped, ui_->playlist, &PlaylistContainer::ActiveStopped); QObject::connect(app_->playlist_manager(), &PlaylistManager::CurrentSongChanged, osd_, &OSDBase::SongChanged); QObject::connect(app_->player(), &Player::Paused, osd_, &OSDBase::Paused); QObject::connect(app_->player(), &Player::Resumed, osd_, &OSDBase::Resumed); QObject::connect(app_->player(), &Player::Stopped, osd_, &OSDBase::Stopped); QObject::connect(app_->player(), &Player::PlaylistFinished, osd_, &OSDBase::PlaylistFinished); QObject::connect(app_->player(), &Player::VolumeChanged, osd_, &OSDBase::VolumeChanged); QObject::connect(app_->player(), &Player::VolumeChanged, ui_->volume, &VolumeSlider::setValue); QObject::connect(app_->player(), &Player::ForceShowOSD, this, &MainWindow::ForceShowOSD); QObject::connect(app_->playlist_manager(), &PlaylistManager::CurrentSongChanged, this, &MainWindow::SongChanged); QObject::connect(app_->playlist_manager(), &PlaylistManager::CurrentSongChanged, app_->player(), &Player::CurrentMetadataChanged); QObject::connect(app_->playlist_manager(), &PlaylistManager::EditingFinished, this, &MainWindow::PlaylistEditFinished); QObject::connect(app_->playlist_manager(), &PlaylistManager::Error, this, &MainWindow::ShowErrorDialog); QObject::connect(app_->playlist_manager(), &PlaylistManager::SummaryTextChanged, ui_->playlist_summary, &QLabel::setText); QObject::connect(app_->playlist_manager(), &PlaylistManager::PlayRequested, this, &MainWindow::PlayIndex); QObject::connect(ui_->playlist->view(), &PlaylistView::doubleClicked, this, &MainWindow::PlaylistDoubleClick); QObject::connect(ui_->playlist->view(), &PlaylistView::PlayItem, this, &MainWindow::PlayIndex); QObject::connect(ui_->playlist->view(), &PlaylistView::PlayPause, app_->player(), &Player::PlayPause); QObject::connect(ui_->playlist->view(), &PlaylistView::RightClicked, this, &MainWindow::PlaylistRightClick); QObject::connect(ui_->playlist->view(), &PlaylistView::SeekForward, app_->player(), &Player::SeekForward); QObject::connect(ui_->playlist->view(), &PlaylistView::SeekBackward, app_->player(), &Player::SeekBackward); QObject::connect(ui_->playlist->view(), &PlaylistView::BackgroundPropertyChanged, this, &MainWindow::RefreshStyleSheet); QObject::connect(ui_->track_slider, &TrackSlider::ValueChangedSeconds, app_->player(), &Player::SeekTo); QObject::connect(ui_->track_slider, &TrackSlider::SeekForward, app_->player(), &Player::SeekForward); QObject::connect(ui_->track_slider, &TrackSlider::SeekBackward, app_->player(), &Player::SeekBackward); QObject::connect(ui_->track_slider, &TrackSlider::Previous, app_->player(), &Player::Previous); QObject::connect(ui_->track_slider, &TrackSlider::Next, app_->player(), &Player::Next); // Collection connections QObject::connect(collection_view_->view(), &CollectionView::AddToPlaylistSignal, this, &MainWindow::AddToPlaylist); QObject::connect(collection_view_->view(), &CollectionView::ShowConfigDialog, this, &MainWindow::ShowCollectionConfig); QObject::connect(collection_view_->view(), &CollectionView::Error, this, &MainWindow::ShowErrorDialog); QObject::connect(app_->collection_model(), &CollectionModel::TotalSongCountUpdated, collection_view_->view(), &CollectionView::TotalSongCountUpdated); QObject::connect(app_->collection_model(), &CollectionModel::TotalArtistCountUpdated, collection_view_->view(), &CollectionView::TotalArtistCountUpdated); QObject::connect(app_->collection_model(), &CollectionModel::TotalAlbumCountUpdated, collection_view_->view(), &CollectionView::TotalAlbumCountUpdated); QObject::connect(app_->collection_model(), &CollectionModel::modelAboutToBeReset, collection_view_->view(), &CollectionView::SaveFocus); QObject::connect(app_->collection_model(), &CollectionModel::modelReset, collection_view_->view(), &CollectionView::RestoreFocus); QObject::connect(app_->task_manager(), &TaskManager::PauseCollectionWatchers, app_->collection(), &SCollection::PauseWatcher); QObject::connect(app_->task_manager(), &TaskManager::ResumeCollectionWatchers, app_->collection(), &SCollection::ResumeWatcher); QObject::connect(app_->current_albumcover_loader(), &CurrentAlbumCoverLoader::AlbumCoverLoaded, this, &MainWindow::AlbumCoverLoaded); QObject::connect(album_cover_choice_controller_->cover_from_file_action(), &QAction::triggered, this, &MainWindow::LoadCoverFromFile); QObject::connect(album_cover_choice_controller_->cover_to_file_action(), &QAction::triggered, this, &MainWindow::SaveCoverToFile); QObject::connect(album_cover_choice_controller_->cover_from_url_action(), &QAction::triggered, this, &MainWindow::LoadCoverFromURL); QObject::connect(album_cover_choice_controller_->search_for_cover_action(), &QAction::triggered, this, &MainWindow::SearchForCover); QObject::connect(album_cover_choice_controller_->unset_cover_action(), &QAction::triggered, this, &MainWindow::UnsetCover); QObject::connect(album_cover_choice_controller_->clear_cover_action(), &QAction::triggered, this, &MainWindow::ClearCover); QObject::connect(album_cover_choice_controller_->delete_cover_action(), &QAction::triggered, this, &MainWindow::DeleteCover); QObject::connect(album_cover_choice_controller_->show_cover_action(), &QAction::triggered, this, &MainWindow::ShowCover); QObject::connect(album_cover_choice_controller_->search_cover_auto_action(), &QAction::triggered, this, &MainWindow::SearchCoverAutomatically); QObject::connect(album_cover_choice_controller_->search_cover_auto_action(), &QAction::toggled, this, &MainWindow::ToggleSearchCoverAuto); #ifndef Q_OS_WIN // Devices connections QObject::connect(device_view_->view(), &DeviceView::AddToPlaylistSignal, this, &MainWindow::AddToPlaylist); #endif // Collection filter widget QActionGroup *collection_view_group = new QActionGroup(this); collection_show_all_ = collection_view_group->addAction(tr("Show all songs")); collection_show_duplicates_ = collection_view_group->addAction(tr("Show only duplicates")); collection_show_untagged_ = collection_view_group->addAction(tr("Show only untagged")); collection_show_all_->setCheckable(true); collection_show_duplicates_->setCheckable(true); collection_show_untagged_->setCheckable(true); collection_show_all_->setChecked(true); QObject::connect(collection_view_group, &QActionGroup::triggered, this, &MainWindow::ChangeCollectionQueryMode); QAction *collection_config_action = new QAction(IconLoader::Load("configure"), tr("Configure collection..."), this); QObject::connect(collection_config_action, &QAction::triggered, this, &MainWindow::ShowCollectionConfig); collection_view_->filter()->SetSettingsGroup(CollectionSettingsPage::kSettingsGroup); collection_view_->filter()->SetCollectionModel(app_->collection()->model()); QAction *separator = new QAction(this); separator->setSeparator(true); collection_view_->filter()->AddMenuAction(collection_show_all_); collection_view_->filter()->AddMenuAction(collection_show_duplicates_); collection_view_->filter()->AddMenuAction(collection_show_untagged_); collection_view_->filter()->AddMenuAction(separator); collection_view_->filter()->AddMenuAction(collection_config_action); #ifdef HAVE_SUBSONIC QObject::connect(subsonic_view_->view(), &InternetCollectionView::AddToPlaylistSignal, this, &MainWindow::AddToPlaylist); #endif #ifdef HAVE_TIDAL QObject::connect(tidal_view_->artists_collection_view(), &InternetCollectionView::AddToPlaylistSignal, this, &MainWindow::AddToPlaylist); QObject::connect(tidal_view_->albums_collection_view(), &InternetCollectionView::AddToPlaylistSignal, this, &MainWindow::AddToPlaylist); QObject::connect(tidal_view_->songs_collection_view(), &InternetCollectionView::AddToPlaylistSignal, this, &MainWindow::AddToPlaylist); QObject::connect(tidal_view_->search_view(), &InternetSearchView::AddToPlaylist, this, &MainWindow::AddToPlaylist); if (TidalService *tidalservice = qobject_cast (app_->internet_services()->ServiceBySource(Song::Source_Tidal))) QObject::connect(this, &MainWindow::AuthorizationUrlReceived, tidalservice, &TidalService::AuthorizationUrlReceived); #endif #ifdef HAVE_QOBUZ QObject::connect(qobuz_view_->artists_collection_view(), &InternetCollectionView::AddToPlaylistSignal, this, &MainWindow::AddToPlaylist); QObject::connect(qobuz_view_->albums_collection_view(), &InternetCollectionView::AddToPlaylistSignal, this, &MainWindow::AddToPlaylist); QObject::connect(qobuz_view_->songs_collection_view(), &InternetCollectionView::AddToPlaylistSignal, this, &MainWindow::AddToPlaylist); QObject::connect(qobuz_view_->search_view(), &InternetSearchView::AddToPlaylist, this, &MainWindow::AddToPlaylist); #endif // Playlist menu QObject::connect(playlist_menu_, &QMenu::aboutToHide, this, &MainWindow::PlaylistMenuHidden); playlist_play_pause_ = playlist_menu_->addAction(tr("Play"), this, &MainWindow::PlaylistPlay); playlist_menu_->addAction(ui_->action_stop); playlist_stop_after_ = playlist_menu_->addAction(IconLoader::Load("media-playback-stop"), tr("Stop after this track"), this, &MainWindow::PlaylistStopAfter); playlist_queue_ = playlist_menu_->addAction(IconLoader::Load("go-next"), tr("Toggle queue status"), this, &MainWindow::PlaylistQueue); playlist_queue_->setShortcut(QKeySequence("Ctrl+D")); ui_->playlist->addAction(playlist_queue_); playlist_queue_play_next_ = playlist_menu_->addAction(IconLoader::Load("go-next"), tr("Queue selected tracks to play next"), this, &MainWindow::PlaylistQueuePlayNext); playlist_queue_play_next_->setShortcut(QKeySequence("Ctrl+Shift+D")); ui_->playlist->addAction(playlist_queue_play_next_); playlist_skip_ = playlist_menu_->addAction(IconLoader::Load("media-skip-forward"), tr("Toggle skip status"), this, &MainWindow::PlaylistSkip); ui_->playlist->addAction(playlist_skip_); playlist_menu_->addSeparator(); playlist_menu_->addAction(ui_->action_remove_from_playlist); playlist_undoredo_ = playlist_menu_->addSeparator(); playlist_menu_->addAction(ui_->action_edit_track); playlist_menu_->addAction(ui_->action_edit_value); playlist_menu_->addAction(ui_->action_renumber_tracks); playlist_menu_->addAction(ui_->action_selection_set_value); #if defined(HAVE_GSTREAMER) && defined(HAVE_CHROMAPRINT) playlist_menu_->addAction(ui_->action_auto_complete_tags); #endif playlist_rescan_songs_ = playlist_menu_->addAction(IconLoader::Load("view-refresh"), tr("Rescan song(s)..."), this, &MainWindow::RescanSongs); playlist_menu_->addAction(playlist_rescan_songs_); #ifdef HAVE_GSTREAMER playlist_menu_->addAction(ui_->action_add_files_to_transcoder); #endif playlist_menu_->addSeparator(); playlist_copy_url_ = playlist_menu_->addAction(IconLoader::Load("edit-copy"), tr("Copy URL(s)..."), this, &MainWindow::PlaylistCopyUrl); playlist_show_in_collection_ = playlist_menu_->addAction(IconLoader::Load("edit-find"), tr("Show in collection..."), this, &MainWindow::ShowInCollection); playlist_open_in_browser_ = playlist_menu_->addAction(IconLoader::Load("document-open-folder"), tr("Show in file browser..."), this, &MainWindow::PlaylistOpenInBrowser); playlist_organize_ = playlist_menu_->addAction(IconLoader::Load("edit-copy"), tr("Organize files..."), this, &MainWindow::PlaylistMoveToCollection); playlist_copy_to_collection_ = playlist_menu_->addAction(IconLoader::Load("edit-copy"), tr("Copy to collection..."), this, &MainWindow::PlaylistCopyToCollection); playlist_move_to_collection_ = playlist_menu_->addAction(IconLoader::Load("go-jump"), tr("Move to collection..."), this, &MainWindow::PlaylistMoveToCollection); #if defined(HAVE_GSTREAMER) && !defined(Q_OS_WIN) playlist_copy_to_device_ = playlist_menu_->addAction(IconLoader::Load("device"), tr("Copy to device..."), this, &MainWindow::PlaylistCopyToDevice); #endif playlist_delete_ = playlist_menu_->addAction(IconLoader::Load("edit-delete"), tr("Delete from disk..."), this, &MainWindow::PlaylistDelete); playlist_menu_->addSeparator(); playlistitem_actions_separator_ = playlist_menu_->addSeparator(); playlist_menu_->addAction(ui_->action_clear_playlist); playlist_menu_->addAction(ui_->action_shuffle); playlist_menu_->addAction(ui_->action_remove_duplicates); playlist_menu_->addAction(ui_->action_remove_unavailable); #ifdef Q_OS_MACOS ui_->action_shuffle->setShortcut(QKeySequence()); #endif // We have to add the actions on the playlist menu to this QWidget otherwise their shortcut keys don't work addActions(playlist_menu_->actions()); QObject::connect(ui_->playlist, &PlaylistContainer::UndoRedoActionsChanged, this, &MainWindow::PlaylistUndoRedoChanged); #if defined(HAVE_GSTREAMER) && !defined(Q_OS_WIN) playlist_copy_to_device_->setDisabled(app_->device_manager()->connected_devices_model()->rowCount() == 0); QObject::connect(app_->device_manager()->connected_devices_model(), &DeviceStateFilterModel::IsEmptyChanged, playlist_copy_to_device_, &QAction::setDisabled); #endif QObject::connect(app_->scrobbler(), &AudioScrobbler::ScrobblingEnabledChanged, this, &MainWindow::ScrobblingEnabledChanged); QObject::connect(app_->scrobbler(), &AudioScrobbler::ScrobbleButtonVisibilityChanged, this, &MainWindow::ScrobbleButtonVisibilityChanged); QObject::connect(app_->scrobbler(), &AudioScrobbler::LoveButtonVisibilityChanged, this, &MainWindow::LoveButtonVisibilityChanged); #ifdef Q_OS_MACOS mac::SetApplicationHandler(this); #endif // Tray icon if (tray_icon_) { tray_icon_->SetupMenu(ui_->action_previous_track, ui_->action_play_pause, ui_->action_stop, ui_->action_stop_after_this_track, ui_->action_next_track, ui_->action_mute, ui_->action_love, ui_->action_quit); QObject::connect(tray_icon_, &SystemTrayIcon::PlayPause, app_->player(), &Player::PlayPauseHelper); QObject::connect(tray_icon_, &SystemTrayIcon::SeekForward, app_->player(), &Player::SeekForward); QObject::connect(tray_icon_, &SystemTrayIcon::SeekBackward, app_->player(), &Player::SeekBackward); QObject::connect(tray_icon_, &SystemTrayIcon::NextTrack, app_->player(), &Player::Next); QObject::connect(tray_icon_, &SystemTrayIcon::PreviousTrack, app_->player(), &Player::Previous); QObject::connect(tray_icon_, &SystemTrayIcon::ShowHide, this, &MainWindow::ToggleShowHide); QObject::connect(tray_icon_, &SystemTrayIcon::ChangeVolume, this, &MainWindow::VolumeWheelEvent); } // Windows 7 thumbbar buttons #ifdef Q_OS_WIN thumbbar_->SetActions(QList() << ui_->action_previous_track << ui_->action_play_pause << ui_->action_stop << ui_->action_next_track << nullptr << ui_->action_love); #endif #if defined(HAVE_SPARKLE) || defined(HAVE_QTSPARKLE) QAction *check_updates = ui_->menu_tools->addAction(tr("Check for updates...")); check_updates->setMenuRole(QAction::ApplicationSpecificRole); QObject::connect(check_updates, &QAction::triggered, this, &MainWindow::CheckForUpdates); #endif #ifdef HAVE_GLOBALSHORTCUTS // Global shortcuts QObject::connect(globalshortcuts_manager_, &GlobalShortcutsManager::Play, app_->player(), &Player::Play); QObject::connect(globalshortcuts_manager_, &GlobalShortcutsManager::Pause, app_->player(), &Player::Pause); QObject::connect(globalshortcuts_manager_, &GlobalShortcutsManager::PlayPause, ui_->action_play_pause, &QAction::trigger); QObject::connect(globalshortcuts_manager_, &GlobalShortcutsManager::Stop, ui_->action_stop, &QAction::trigger); QObject::connect(globalshortcuts_manager_, &GlobalShortcutsManager::StopAfter, ui_->action_stop_after_this_track, &QAction::trigger); QObject::connect(globalshortcuts_manager_, &GlobalShortcutsManager::Next, ui_->action_next_track, &QAction::trigger); QObject::connect(globalshortcuts_manager_, &GlobalShortcutsManager::Previous, ui_->action_previous_track, &QAction::trigger); QObject::connect(globalshortcuts_manager_, &GlobalShortcutsManager::IncVolume, app_->player(), &Player::VolumeUp); QObject::connect(globalshortcuts_manager_, &GlobalShortcutsManager::DecVolume, app_->player(), &Player::VolumeDown); QObject::connect(globalshortcuts_manager_, &GlobalShortcutsManager::Mute, app_->player(), &Player::Mute); QObject::connect(globalshortcuts_manager_, &GlobalShortcutsManager::SeekForward, app_->player(), &Player::SeekForward); QObject::connect(globalshortcuts_manager_, &GlobalShortcutsManager::SeekBackward, app_->player(), &Player::SeekBackward); QObject::connect(globalshortcuts_manager_, &GlobalShortcutsManager::ShowHide, this, &MainWindow::ToggleShowHide); QObject::connect(globalshortcuts_manager_, &GlobalShortcutsManager::ShowOSD, app_->player(), &Player::ShowOSD); QObject::connect(globalshortcuts_manager_, &GlobalShortcutsManager::TogglePrettyOSD, app_->player(), &Player::TogglePrettyOSD); QObject::connect(globalshortcuts_manager_, &GlobalShortcutsManager::ToggleScrobbling, app_->scrobbler(), &AudioScrobbler::ToggleScrobbling); QObject::connect(globalshortcuts_manager_, &GlobalShortcutsManager::Love, app_->scrobbler(), &AudioScrobbler::Love); #endif // Fancy tabs QObject::connect(ui_->tabs, &FancyTabWidget::CurrentChanged, this, &MainWindow::TabSwitched); // Context QObject::connect(app_->playlist_manager(), &PlaylistManager::CurrentSongChanged, context_view_, &ContextView::SongChanged); QObject::connect(app_->playlist_manager(), &PlaylistManager::SongMetadataChanged, context_view_, &ContextView::SongChanged); QObject::connect(app_->player(), &Player::PlaylistFinished, context_view_, &ContextView::Stopped); QObject::connect(app_->player(), &Player::Playing, context_view_, &ContextView::Playing); QObject::connect(app_->player(), &Player::Stopped, context_view_, &ContextView::Stopped); QObject::connect(app_->player(), &Player::Error, context_view_, &ContextView::Error); QObject::connect(this, &MainWindow::AlbumCoverReady, context_view_, &ContextView::AlbumCoverLoaded); QObject::connect(this, &MainWindow::SearchCoverInProgress, context_view_->album_widget(), &ContextAlbum::SearchCoverInProgress); QObject::connect(context_view_, &ContextView::AlbumEnabledChanged, this, &MainWindow::TabSwitched); QObject::connect(context_view_->albums_widget(), &ContextAlbumsView::AddToPlaylistSignal, this, &MainWindow::AddToPlaylist); // Analyzer QObject::connect(ui_->analyzer, &AnalyzerContainer::WheelEvent, this, &MainWindow::VolumeWheelEvent); // Statusbar widgets #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)) ui_->playlist_summary->setMinimumWidth(QFontMetrics(font()).horizontalAdvance("WW selected of WW tracks - [ WW:WW ]")); #else ui_->playlist_summary->setMinimumWidth(QFontMetrics(font()).width("WW selected of WW tracks - [ WW:WW ]")); #endif ui_->status_bar_stack->setCurrentWidget(ui_->playlist_summary_page); QObject::connect(ui_->multi_loading_indicator, &MultiLoadingIndicator::TaskCountChange, this, &MainWindow::TaskCountChanged); ui_->track_slider->SetApplication(app); #ifdef HAVE_MOODBAR // Moodbar connections QObject::connect(app_->moodbar_controller(), &MoodbarController::CurrentMoodbarDataChanged, ui_->track_slider->moodbar_style(), &MoodbarProxyStyle::SetMoodbarData); #endif // Playing widget qLog(Debug) << "Creating playing widget"; ui_->widget_playing->set_ideal_height(ui_->status_bar->sizeHint().height() + ui_->player_controls->sizeHint().height()); QObject::connect(app_->playlist_manager(), &PlaylistManager::CurrentSongChanged, ui_->widget_playing, &PlayingWidget::SongChanged); QObject::connect(app_->player(), &Player::PlaylistFinished, ui_->widget_playing, &PlayingWidget::Stopped); QObject::connect(app_->player(), &Player::Playing, ui_->widget_playing, &PlayingWidget::Playing); QObject::connect(app_->player(), &Player::Stopped, ui_->widget_playing, &PlayingWidget::Stopped); QObject::connect(app_->player(), &Player::Error, ui_->widget_playing, &PlayingWidget::Error); QObject::connect(ui_->widget_playing, &PlayingWidget::ShowAboveStatusBarChanged, this, &MainWindow::PlayingWidgetPositionChanged); QObject::connect(this, &MainWindow::AlbumCoverReady, ui_->widget_playing, &PlayingWidget::AlbumCoverLoaded); QObject::connect(this, &MainWindow::SearchCoverInProgress, ui_->widget_playing, &PlayingWidget::SearchCoverInProgress); QObject::connect(ui_->action_console, &QAction::triggered, this, &MainWindow::ShowConsole); PlayingWidgetPositionChanged(ui_->widget_playing->show_above_status_bar()); // Load theme // This is tricky: we need to save the default/system palette now, // before loading user preferred theme (which will override it), to be able to restore it later const_cast(Appearance::kDefaultPalette) = QApplication::palette(); app_->appearance()->LoadUserTheme(); StyleSheetLoader *css_loader = new StyleSheetLoader(this); css_loader->SetStyleSheet(this, ":/style/strawberry.css"); // Load playlists app_->playlist_manager()->Init(app_->collection_backend(), app_->playlist_backend(), ui_->playlist_sequence, ui_->playlist); queue_view_->SetPlaylistManager(app_->playlist_manager()); // This connection must be done after the playlists have been initialized. QObject::connect(this, &MainWindow::StopAfterToggled, osd_, &OSDBase::StopAfterToggle); // We need to connect these global shortcuts here after the playlist have been initialized #ifdef HAVE_GLOBALSHORTCUTS QObject::connect(globalshortcuts_manager_, &GlobalShortcutsManager::CycleShuffleMode, app_->playlist_manager()->sequence(), &PlaylistSequence::CycleShuffleMode); QObject::connect(globalshortcuts_manager_, &GlobalShortcutsManager::CycleRepeatMode, app_->playlist_manager()->sequence(), &PlaylistSequence::CycleRepeatMode); #endif QObject::connect(app_->playlist_manager()->sequence(), &PlaylistSequence::RepeatModeChanged, osd_, &OSDBase::RepeatModeChanged); QObject::connect(app_->playlist_manager()->sequence(), &PlaylistSequence::ShuffleModeChanged, osd_, &OSDBase::ShuffleModeChanged); // Smart playlists QObject::connect(smartplaylists_view_, &SmartPlaylistsViewContainer::AddToPlaylist, this, &MainWindow::AddToPlaylist); ScrobbleButtonVisibilityChanged(app_->scrobbler()->ScrobbleButton()); LoveButtonVisibilityChanged(app_->scrobbler()->LoveButton()); ScrobblingEnabledChanged(app_->scrobbler()->IsEnabled()); // Last.fm ImportData QObject::connect(app_->lastfm_import(), &LastFMImport::Finished, lastfm_import_dialog_, &LastFMImportDialog::Finished); QObject::connect(app_->lastfm_import(), &LastFMImport::FinishedWithError, lastfm_import_dialog_, &LastFMImportDialog::FinishedWithError); QObject::connect(app_->lastfm_import(), &LastFMImport::UpdateTotal, lastfm_import_dialog_, &LastFMImportDialog::UpdateTotal); QObject::connect(app_->lastfm_import(), &LastFMImport::UpdateProgress, lastfm_import_dialog_, &LastFMImportDialog::UpdateProgress); // Load settings qLog(Debug) << "Loading settings"; settings_.beginGroup(kSettingsGroup); // Set last used geometry to position window on the correct monitor // Set window state only if the window was last maximized if (settings_.contains("geometry")) { restoreGeometry(settings_.value("geometry").toByteArray()); } if (!settings_.contains("splitter_state") || !ui_->splitter->restoreState(settings_.value("splitter_state").toByteArray())) { ui_->splitter->setSizes(QList() << 20 << (width() - 20)); } ui_->tabs->setCurrentIndex(settings_.value("current_tab", 1).toInt()); FancyTabWidget::Mode default_mode = FancyTabWidget::Mode_LargeSidebar; int tab_mode_int = settings_.value("tab_mode", default_mode).toInt(); FancyTabWidget::Mode tab_mode = FancyTabWidget::Mode(tab_mode_int); if (tab_mode == FancyTabWidget::Mode_None) tab_mode = default_mode; ui_->tabs->SetMode(tab_mode); TabSwitched(); file_view_->SetPath(settings_.value("file_path", QDir::homePath()).toString()); // Users often collapse one side of the splitter by mistake and don't know how to restore it. This must be set after the state is restored above. ui_->splitter->setChildrenCollapsible(false); ReloadSettings(); // Reload pretty OSD to avoid issues with fonts osd_->ReloadPrettyOSDSettings(); // Reload playlist settings, for BG and glowing ui_->playlist->view()->ReloadSettings(); #ifdef Q_OS_MACOS // Always show the mainwindow on startup for macOS show(); #else QSettings s; s.beginGroup(BehaviourSettingsPage::kSettingsGroup); BehaviourSettingsPage::StartupBehaviour behaviour = BehaviourSettingsPage::StartupBehaviour(s.value("startupbehaviour", BehaviourSettingsPage::Startup_Remember).toInt()); s.endGroup(); switch (behaviour) { case BehaviourSettingsPage::Startup_Show: show(); break; case BehaviourSettingsPage::Startup_ShowMaximized: setWindowState(windowState() | Qt::WindowMaximized); show(); break; case BehaviourSettingsPage::Startup_ShowMinimized: setWindowState(windowState() | Qt::WindowMinimized); show(); break; case BehaviourSettingsPage::Startup_Hide: if (QSystemTrayIcon::isSystemTrayAvailable() && tray_icon_ && tray_icon_->IsVisible()) { hide(); break; } // fallthrough case BehaviourSettingsPage::Startup_Remember: default: { was_maximized_ = settings_.value("maximized", true).toBool(); if (was_maximized_) setWindowState(windowState() | Qt::WindowMaximized); was_minimized_ = settings_.value("minimized", false).toBool(); if (was_minimized_) setWindowState(windowState() | Qt::WindowMinimized); if (!QSystemTrayIcon::isSystemTrayAvailable() || !tray_icon_ || !tray_icon_->IsVisible()) { hidden_ = false; settings_.setValue("hidden", false); show(); } else { hidden_ = settings_.value("hidden", false).toBool(); setVisible(!hidden_); } break; } } #endif bool show_sidebar = settings_.value("show_sidebar", true).toBool(); ui_->sidebar_layout->setVisible(show_sidebar); ui_->action_toggle_show_sidebar->setChecked(show_sidebar); QShortcut *close_window_shortcut = new QShortcut(this); close_window_shortcut->setKey(Qt::CTRL | Qt::Key_W); QObject::connect(close_window_shortcut, &QShortcut::activated, this, &MainWindow::ToggleHide); CheckFullRescanRevisions(); CommandlineOptionsReceived(options); if (!options.contains_play_options()) { LoadPlaybackStatus(); } if (app_->scrobbler()->IsEnabled() && !app_->scrobbler()->IsOffline()) { app_->scrobbler()->Submit(); } #ifdef HAVE_QTSPARKLE QUrl sparkle_url; #if defined(Q_OS_MACOS) sparkle_url.setUrl("https://www.strawberrymusicplayer.org/sparkle-macos"); #elif defined(Q_OS_WIN) sparkle_url.setUrl("https://www.strawberrymusicplayer.org/sparkle-windows"); #endif if (!sparkle_url.isEmpty()) { qLog(Debug) << "Creating Qt Sparkle updater"; qtsparkle::Updater *updater = new qtsparkle::Updater(sparkle_url, this); updater->SetNetworkAccessManager(new NetworkAccessManager(this)); updater->SetVersion(STRAWBERRY_VERSION_PACKAGE); QObject::connect(check_updates, &QAction::triggered, updater, &qtsparkle::Updater::CheckNow); } #endif #ifdef Q_OS_LINUX if (!Utilities::GetEnv("SNAP").isEmpty() && !Utilities::GetEnv("SNAP_NAME").isEmpty()) { s.beginGroup(kSettingsGroup); if (!s.value("ignore_snap", false).toBool()) { SnapDialog *snap_dialog = new SnapDialog(); snap_dialog->setAttribute(Qt::WA_DeleteOnClose); snap_dialog->show(); } s.endGroup(); } #endif qLog(Debug) << "Started" << QThread::currentThread(); initialized_ = true; } MainWindow::~MainWindow() { delete ui_; } void MainWindow::ReloadSettings() { QSettings s; #ifndef Q_OS_MACOS s.beginGroup(BehaviourSettingsPage::kSettingsGroup); bool showtrayicon = s.value("showtrayicon", QSystemTrayIcon::isSystemTrayAvailable()).toBool(); s.endGroup(); if (tray_icon_) tray_icon_->SetVisible(showtrayicon); if ((!showtrayicon || !QSystemTrayIcon::isSystemTrayAvailable()) && !isVisible()) show(); #endif s.beginGroup(BehaviourSettingsPage::kSettingsGroup); keep_running_ = s.value("keeprunning", false).toBool(); playing_widget_ = s.value("playing_widget", true).toBool(); bool trayicon_progress = s.value("trayicon_progress", false).toBool(); if (playing_widget_ != ui_->widget_playing->IsEnabled()) TabSwitched(); doubleclick_addmode_ = BehaviourSettingsPage::AddBehaviour(s.value("doubleclick_addmode", BehaviourSettingsPage::AddBehaviour_Append).toInt()); doubleclick_playmode_ = BehaviourSettingsPage::PlayBehaviour(s.value("doubleclick_playmode", BehaviourSettingsPage::PlayBehaviour_Never).toInt()); doubleclick_playlist_addmode_ = BehaviourSettingsPage::PlaylistAddBehaviour(s.value("doubleclick_playlist_addmode", BehaviourSettingsPage::PlayBehaviour_Never).toInt()); menu_playmode_ = BehaviourSettingsPage::PlayBehaviour(s.value("menu_playmode", BehaviourSettingsPage::PlayBehaviour_Never).toInt()); s.endGroup(); s.beginGroup(AppearanceSettingsPage::kSettingsGroup); int iconsize = s.value(AppearanceSettingsPage::kIconSizePlayControlButtons, 32).toInt(); s.endGroup(); if (tray_icon_) tray_icon_->SetTrayiconProgress(trayicon_progress); ui_->back_button->setIconSize(QSize(iconsize, iconsize)); ui_->pause_play_button->setIconSize(QSize(iconsize, iconsize)); ui_->stop_button->setIconSize(QSize(iconsize, iconsize)); ui_->forward_button->setIconSize(QSize(iconsize, iconsize)); ui_->button_love->setIconSize(QSize(iconsize, iconsize)); s.beginGroup(BackendSettingsPage::kSettingsGroup); bool volume_control = s.value("volume_control", true).toBool(); s.endGroup(); if (volume_control != ui_->volume->isEnabled()) { ui_->volume->SetEnabled(volume_control); if (volume_control) { if (!ui_->action_mute->isVisible()) ui_->action_mute->setVisible(true); if (tray_icon_ && !tray_icon_->MuteEnabled()) tray_icon_->SetMuteEnabled(true); } else { if (ui_->action_mute->isVisible()) ui_->action_mute->setVisible(false); if (tray_icon_ && tray_icon_->MuteEnabled()) tray_icon_->SetMuteEnabled(false); } } s.beginGroup(PlaylistSettingsPage::kSettingsGroup); delete_files_ = s.value("delete_files", false).toBool(); s.endGroup(); osd_->ReloadSettings(); album_cover_choice_controller_->search_cover_auto_action()->setChecked(settings_.value("search_for_cover_auto", true).toBool()); #ifdef HAVE_SUBSONIC s.beginGroup(SubsonicSettingsPage::kSettingsGroup); bool enable_subsonic = s.value("enabled", false).toBool(); s.endGroup(); if (enable_subsonic) ui_->tabs->EnableTab(subsonic_view_); else ui_->tabs->DisableTab(subsonic_view_); app_->scrobbler()->Service()->ReloadSettings(); #endif #ifdef HAVE_TIDAL s.beginGroup(TidalSettingsPage::kSettingsGroup); bool enable_tidal = s.value("enabled", false).toBool(); s.endGroup(); if (enable_tidal) ui_->tabs->EnableTab(tidal_view_); else ui_->tabs->DisableTab(tidal_view_); #endif #ifdef HAVE_QOBUZ s.beginGroup(QobuzSettingsPage::kSettingsGroup); bool enable_qobuz = s.value("enabled", false).toBool(); s.endGroup(); if (enable_qobuz) ui_->tabs->EnableTab(qobuz_view_); else ui_->tabs->DisableTab(qobuz_view_); #endif ui_->tabs->ReloadSettings(); } void MainWindow::ReloadAllSettings() { ReloadSettings(); // Other settings app_->ReloadSettings(); app_->collection()->ReloadSettings(); app_->player()->ReloadSettings(); collection_view_->ReloadSettings(); ui_->playlist->view()->ReloadSettings(); app_->playlist_manager()->playlist_container()->ReloadSettings(); app_->album_cover_loader()->ReloadSettings(); album_cover_choice_controller_->ReloadSettings(); context_view_->ReloadSettings(); file_view_->ReloadSettings(); queue_view_->ReloadSettings(); playlist_list_->ReloadSettings(); smartplaylists_view_->ReloadSettings(); app_->cover_providers()->ReloadSettings(); app_->lyrics_providers()->ReloadSettings(); #ifdef HAVE_MOODBAR app_->moodbar_controller()->ReloadSettings(); #endif #ifdef HAVE_SUBSONIC subsonic_view_->ReloadSettings(); #endif #ifdef HAVE_TIDAL tidal_view_->ReloadSettings(); #endif #ifdef HAVE_QOBUZ qobuz_view_->ReloadSettings(); #endif } void MainWindow::RefreshStyleSheet() { QString contents(styleSheet()); setStyleSheet(""); setStyleSheet(contents); } void MainWindow::SaveSettings() { SaveGeometry(); SavePlaybackStatus(); ui_->tabs->SaveSettings(kSettingsGroup); ui_->playlist->view()->SaveSettings(); app_->scrobbler()->WriteCache(); settings_.setValue("show_sidebar", ui_->action_toggle_show_sidebar->isChecked()); settings_.setValue("search_for_cover_auto", album_cover_choice_controller_->search_cover_auto_action()->isChecked()); } void MainWindow::Exit() { ++exit_count_; SaveSettings(); // Make sure Settings dialog is destroyed first. settings_dialog_.reset(); if (exit_count_ > 1) { qApp->quit(); } else { if (app_->player()->engine()->is_fadeout_enabled()) { // To shut down the application when fadeout will be finished QObject::connect(app_->player()->engine(), &EngineBase::FadeoutFinishedSignal, this, &MainWindow::DoExit); if (app_->player()->GetState() == Engine::Playing) { app_->player()->Stop(); hide(); if (tray_icon_) tray_icon_->SetVisible(false); return; // Don't quit the application now: wait for the fadeout finished signal } } DoExit(); } } void MainWindow::DoExit() { QObject::connect(app_, &Application::ExitFinished, this, &MainWindow::ExitFinished); app_->Exit(); } void MainWindow::ExitFinished() { qApp->quit(); } void MainWindow::EngineChanged(Engine::EngineType enginetype) { ui_->action_equalizer->setEnabled(enginetype == Engine::EngineType::GStreamer); #ifdef Q_OS_WIN ui_->action_open_cd->setEnabled(false); ui_->action_open_cd->setVisible(false); #else ui_->action_open_cd->setEnabled(enginetype == Engine::EngineType::GStreamer); #endif } void MainWindow::MediaStopped() { setWindowTitle("Strawberry Music Player"); ui_->action_stop->setEnabled(false); ui_->action_stop_after_this_track->setEnabled(false); ui_->action_play_pause->setIcon(IconLoader::Load("media-playback-start")); ui_->action_play_pause->setText(tr("Play")); ui_->action_play_pause->setEnabled(true); ui_->action_love->setEnabled(false); ui_->button_love->setEnabled(false); if (tray_icon_) tray_icon_->LoveStateChanged(false); track_position_timer_->stop(); track_slider_timer_->stop(); ui_->track_slider->SetStopped(); if (tray_icon_) { tray_icon_->SetProgress(0); tray_icon_->SetStopped(); } song_playing_ = Song(); song_ = Song(); album_cover_ = AlbumCoverImageResult(); app_->scrobbler()->ClearPlaying(); } void MainWindow::MediaPaused() { ui_->action_stop->setEnabled(true); ui_->action_stop_after_this_track->setEnabled(true); ui_->action_play_pause->setIcon(IconLoader::Load("media-playback-start")); ui_->action_play_pause->setText(tr("Play")); ui_->action_play_pause->setEnabled(true); track_position_timer_->stop(); track_slider_timer_->stop(); if (tray_icon_) { tray_icon_->SetPaused(); } } void MainWindow::MediaPlaying() { ui_->action_stop->setEnabled(true); ui_->action_stop_after_this_track->setEnabled(true); ui_->action_play_pause->setIcon(IconLoader::Load("media-playback-pause")); ui_->action_play_pause->setText(tr("Pause")); bool enable_play_pause(false); bool can_seek(false); PlaylistItemPtr item(app_->player()->GetCurrentItem()); if (item) { enable_play_pause = !(item->options() & PlaylistItem::PauseDisabled); can_seek = !(item->options() & PlaylistItem::SeekDisabled); } ui_->action_play_pause->setEnabled(enable_play_pause); ui_->track_slider->SetCanSeek(can_seek); if (tray_icon_) tray_icon_->SetPlaying(enable_play_pause); track_position_timer_->start(); track_slider_timer_->start(); UpdateTrackPosition(); } void MainWindow::SendNowPlaying() { // Send now playing to scrobble services Playlist *playlist = app_->playlist_manager()->active(); if (app_->scrobbler()->IsEnabled() && playlist && playlist->current_item() && playlist->current_item()->Metadata().is_metadata_good()) { app_->scrobbler()->UpdateNowPlaying(playlist->current_item()->Metadata()); ui_->action_love->setEnabled(true); ui_->button_love->setEnabled(true); if (tray_icon_) tray_icon_->LoveStateChanged(true); } } void MainWindow::VolumeChanged(const int volume) { ui_->action_mute->setChecked(!volume); if (tray_icon_) tray_icon_->MuteButtonStateChanged(!volume); } void MainWindow::SongChanged(const Song &song) { qLog(Debug) << "Song changed to" << song.artist() << song.album() << song.title(); song_playing_ = song; song_ = song; setWindowTitle(song.PrettyTitleWithArtist()); if (tray_icon_) tray_icon_->SetProgress(0); SendNowPlaying(); const bool enable_change_art = song.is_collection_song() && !song.effective_albumartist().isEmpty() && !song.album().isEmpty(); album_cover_choice_controller_->show_cover_action()->setEnabled(song.has_valid_art() && !song.has_manually_unset_cover()); album_cover_choice_controller_->cover_to_file_action()->setEnabled(song.has_valid_art() && !song.has_manually_unset_cover()); album_cover_choice_controller_->cover_from_file_action()->setEnabled(enable_change_art); album_cover_choice_controller_->cover_from_url_action()->setEnabled(enable_change_art); album_cover_choice_controller_->search_for_cover_action()->setEnabled(app_->cover_providers()->HasAnyProviders() && enable_change_art); album_cover_choice_controller_->unset_cover_action()->setEnabled(enable_change_art && !song.has_manually_unset_cover()); album_cover_choice_controller_->clear_cover_action()->setEnabled(enable_change_art && !song.art_manual().isEmpty()); album_cover_choice_controller_->delete_cover_action()->setEnabled(enable_change_art && song.has_valid_art() && !song.has_manually_unset_cover()); } void MainWindow::TrackSkipped(PlaylistItemPtr item) { // If it was a collection item then we have to increment its skipped count in the database. if (item && item->IsLocalCollectionItem() && item->Metadata().id() != -1) { Song song = item->Metadata(); const qint64 position = app_->player()->engine()->position_nanosec(); const qint64 length = app_->player()->engine()->length_nanosec(); const float percentage = (length == 0 ? 1 : float(position) / length); const qint64 seconds_left = (length - position) / kNsecPerSec; const qint64 seconds_total = length / kNsecPerSec; if (((0.05 * seconds_total > 60 && percentage < 0.98) || percentage < 0.95) && seconds_left > 5) { // Never count the skip if under 5 seconds left app_->collection_backend()->IncrementSkipCountAsync(song.id(), percentage); } } } void MainWindow::TabSwitched() { if (playing_widget_ && ui_->action_toggle_show_sidebar->isChecked() && (ui_->tabs->currentIndex() != ui_->tabs->IndexOfTab(context_view_) || !context_view_->album_enabled())) { ui_->widget_playing->SetEnabled(); } else { ui_->widget_playing->SetDisabled(); } } void MainWindow::ToggleSidebar(const bool checked) { ui_->sidebar_layout->setVisible(checked); TabSwitched(); settings_.setValue("show_sidebar", checked); } void MainWindow::ToggleSearchCoverAuto(const bool checked) { settings_.setValue("search_for_cover_auto", checked); } void MainWindow::SaveGeometry() { if (!initialized_) return; settings_.setValue("maximized", isMaximized()); settings_.setValue("minimized", isMinimized()); settings_.setValue("hidden", hidden_); settings_.setValue("geometry", saveGeometry()); settings_.setValue("splitter_state", ui_->splitter->saveState()); } void MainWindow::SavePlaybackStatus() { QSettings s; s.beginGroup(Player::kSettingsGroup); s.setValue("playback_state", app_->player()->GetState()); if (app_->player()->GetState() == Engine::Playing || app_->player()->GetState() == Engine::Paused) { s.setValue("playback_playlist", app_->playlist_manager()->active()->id()); s.setValue("playback_position", app_->player()->engine()->position_nanosec() / kNsecPerSec); } else { s.setValue("playback_playlist", -1); s.setValue("playback_position", 0); } s.endGroup(); } void MainWindow::LoadPlaybackStatus() { QSettings s; s.beginGroup(BehaviourSettingsPage::kSettingsGroup); bool resume_playback = s.value("resumeplayback", false).toBool(); s.endGroup(); s.beginGroup(Player::kSettingsGroup); Engine::State playback_state = static_cast (s.value("playback_state", Engine::Empty).toInt()); s.endGroup(); if (resume_playback && playback_state != Engine::Empty && playback_state != Engine::Idle) { QObject::connect(app_->playlist_manager(), &PlaylistManager::AllPlaylistsLoaded, this, &MainWindow::ResumePlayback); } } void MainWindow::ResumePlayback() { qLog(Debug) << "Resuming playback"; QObject::disconnect(app_->playlist_manager(), &PlaylistManager::AllPlaylistsLoaded, this, &MainWindow::ResumePlayback); QSettings s; s.beginGroup(Player::kSettingsGroup); Engine::State playback_state = static_cast (s.value("playback_state", Engine::Empty).toInt()); int playback_playlist = s.value("playback_playlist", -1).toInt(); int playback_position = s.value("playback_position", 0).toInt(); s.endGroup(); if (playback_playlist == app_->playlist_manager()->current()->id()) { // Set active to current to resume playback on correct playlist. app_->playlist_manager()->SetActiveToCurrent(); if (playback_state == Engine::Paused) { std::shared_ptr connection = std::make_shared(); *connection = QObject::connect(app_->player(), &Player::Playing, app_->player(), [this, connection]() { QObject::disconnect(*connection); app_->player()->PlayPause(); }); } // Seek after we got song length. std::shared_ptr connection = std::make_shared(); *connection = QObject::connect(track_position_timer_, &QTimer::timeout, this, [this, connection, playback_position]() { QObject::disconnect(*connection); ResumePlaybackSeek(playback_position); }); app_->player()->Play(); } // Reset saved playback status so we don't resume again from the same position. s.beginGroup(Player::kSettingsGroup); s.setValue("playback_state", Engine::Empty); s.setValue("playback_playlist", -1); s.setValue("playback_position", 0); s.endGroup(); } void MainWindow::ResumePlaybackSeek(const int playback_position) { if (app_->player()->engine()->length_nanosec() > 0) { app_->player()->SeekTo(playback_position); } } void MainWindow::PlayIndex(const QModelIndex &idx, Playlist::AutoScroll autoscroll) { if (!idx.isValid()) return; int row = idx.row(); if (idx.model() == app_->playlist_manager()->current()->proxy()) { // The index was in the proxy model (might've been filtered), so we need to get the actual row in the source model. row = app_->playlist_manager()->current()->proxy()->mapToSource(idx).row(); } app_->playlist_manager()->SetActiveToCurrent(); app_->player()->PlayAt(row, Engine::Manual, autoscroll, true); } void MainWindow::PlaylistDoubleClick(const QModelIndex &idx) { if (!idx.isValid()) return; QModelIndex source_idx = idx; if (idx.model() == app_->playlist_manager()->current()->proxy()) { // The index was in the proxy model (might've been filtered), so we need to get the actual row in the source model. source_idx = app_->playlist_manager()->current()->proxy()->mapToSource(idx); } switch (doubleclick_playlist_addmode_) { case BehaviourSettingsPage::PlaylistAddBehaviour_Play: app_->playlist_manager()->SetActiveToCurrent(); app_->player()->PlayAt(source_idx.row(), Engine::Manual, Playlist::AutoScroll_Never, true, true); break; case BehaviourSettingsPage::PlaylistAddBehaviour_Enqueue: app_->playlist_manager()->current()->queue()->ToggleTracks(QModelIndexList() << source_idx); if (app_->player()->GetState() != Engine::Playing) { app_->playlist_manager()->SetActiveToCurrent(); app_->player()->PlayAt(app_->playlist_manager()->current()->queue()->TakeNext(), Engine::Manual, Playlist::AutoScroll_Never, true); } break; } } void MainWindow::VolumeWheelEvent(const int delta) { ui_->volume->setValue(ui_->volume->value() + delta / 30); } void MainWindow::ToggleShowHide() { if (hidden_) { show(); SetHiddenInTray(false); } else if (isActiveWindow()) { hide(); setWindowState((windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); SetHiddenInTray(true); } else if (isMinimized()) { hide(); setWindowState((windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); SetHiddenInTray(false); } else if (!isVisible()) { show(); activateWindow(); } else { // Window is not hidden but does not have focus; bring it to front. activateWindow(); raise(); } } void MainWindow::ToggleHide() { if (!hidden_) SetHiddenInTray(true); } void MainWindow::StopAfterCurrent() { app_->playlist_manager()->current()->StopAfter(app_->playlist_manager()->current()->current_row()); emit StopAfterToggled(app_->playlist_manager()->active()->stop_after_current()); } void MainWindow::showEvent(QShowEvent *e) { hidden_ = false; QMainWindow::showEvent(e); } void MainWindow::closeEvent(QCloseEvent *e) { #ifdef Q_OS_MACOS Exit(); #else if (!hidden_ && keep_running_ && e->spontaneous() && QSystemTrayIcon::isSystemTrayAvailable()) { SetHiddenInTray(true); } else { Exit(); } #endif QMainWindow::closeEvent(e); } void MainWindow::SetHiddenInTray(const bool hidden) { hidden_ = hidden; settings_.setValue("hidden", hidden_); // Some window managers don't remember maximized state between calls to hide() and show(), so we have to remember it ourself. if (hidden) { was_maximized_ = isMaximized(); was_minimized_ = isMinimized(); hide(); } else { if (was_minimized_) { showMinimized(); } else if (was_maximized_) showMaximized(); else show(); } } void MainWindow::FilePathChanged(const QString &path) { settings_.setValue("file_path", path); } void MainWindow::Seeked(const qlonglong microseconds) { const int position = microseconds / kUsecPerSec; const int length = app_->player()->GetCurrentItem()->Metadata().length_nanosec() / kNsecPerSec; if (tray_icon_) tray_icon_->SetProgress(double(position) / length * 100); } void MainWindow::UpdateTrackPosition() { PlaylistItemPtr item(app_->player()->GetCurrentItem()); if (!item) return; const int length = (item->Metadata().length_nanosec() / kNsecPerSec); if (length <= 0) return; const int position = std::floor(float(app_->player()->engine()->position_nanosec()) / kNsecPerSec + 0.5); // Update the tray icon every 10 seconds if (tray_icon_ && position % 10 == 0) tray_icon_->SetProgress(double(position) / length * 100); // Send Scrobble if (app_->scrobbler()->IsEnabled() && item->Metadata().is_metadata_good()) { Playlist *playlist = app_->playlist_manager()->active(); if (playlist && !playlist->scrobbled()) { const int scrobble_point = (playlist->scrobble_point_nanosec() / kNsecPerSec); if (position >= scrobble_point) { app_->scrobbler()->Scrobble(item->Metadata(), scrobble_point); playlist->set_scrobbled(true); } } } } void MainWindow::UpdateTrackSliderPosition() { PlaylistItemPtr item(app_->player()->GetCurrentItem()); const int slider_position = std::floor(float(app_->player()->engine()->position_nanosec()) / kNsecPerMsec); const int slider_length = app_->player()->engine()->length_nanosec() / kNsecPerMsec; // Update the slider ui_->track_slider->SetValue(slider_position, slider_length); } void MainWindow::ApplyAddBehaviour(const BehaviourSettingsPage::AddBehaviour b, MimeData *mimedata) const { switch (b) { case BehaviourSettingsPage::AddBehaviour_Append: mimedata->clear_first_ = false; mimedata->enqueue_now_ = false; break; case BehaviourSettingsPage::AddBehaviour_Enqueue: mimedata->clear_first_ = false; mimedata->enqueue_now_ = true; break; case BehaviourSettingsPage::AddBehaviour_Load: mimedata->clear_first_ = true; mimedata->enqueue_now_ = false; break; case BehaviourSettingsPage::AddBehaviour_OpenInNew: mimedata->open_in_new_playlist_ = true; break; } } void MainWindow::ApplyPlayBehaviour(const BehaviourSettingsPage::PlayBehaviour b, MimeData *mimedata) const { switch (b) { case BehaviourSettingsPage::PlayBehaviour_Always: mimedata->play_now_ = true; break; case BehaviourSettingsPage::PlayBehaviour_Never: mimedata->play_now_ = false; break; case BehaviourSettingsPage::PlayBehaviour_IfStopped: mimedata->play_now_ = !(app_->player()->GetState() == Engine::Playing); break; } } void MainWindow::AddToPlaylist(QMimeData *q_mimedata) { if (!q_mimedata) return; if (MimeData *mimedata = qobject_cast(q_mimedata)) { // Should we replace the flags with the user's preference? if (mimedata->override_user_settings_) { // Do nothing } else if (mimedata->from_doubleclick_) { ApplyAddBehaviour(doubleclick_addmode_, mimedata); ApplyPlayBehaviour(doubleclick_playmode_, mimedata); } else { ApplyPlayBehaviour(menu_playmode_, mimedata); } // Should we create a new playlist for the songs? if (mimedata->open_in_new_playlist_) { app_->playlist_manager()->New(mimedata->get_name_for_new_playlist()); } } app_->playlist_manager()->current()->dropMimeData(q_mimedata, Qt::CopyAction, -1, 0, QModelIndex()); delete q_mimedata; } void MainWindow::AddToPlaylistFromAction(QAction *action) { const int destination = action->data().toInt(); PlaylistItemList items; SongList songs; // Get the selected playlist items for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) { const QModelIndex source_index = app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index); if (!source_index.isValid()) continue; PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_index.row()); if (!item) continue; items << item; songs << item->Metadata(); } // We're creating a new playlist if (destination == -1) { // Save the current playlist to reactivate it const int current_id = app_->playlist_manager()->current_id(); // Get the name from selection app_->playlist_manager()->New(app_->playlist_manager()->GetNameForNewPlaylist(songs)); if (app_->playlist_manager()->current()->id() != current_id) { // I'm sure the new playlist was created and is selected, so I can just insert items app_->playlist_manager()->current()->InsertItems(items); // Set back the current playlist app_->playlist_manager()->SetCurrentPlaylist(current_id); } } else { // We're inserting in a existing playlist app_->playlist_manager()->playlist(destination)->InsertItems(items); } } void MainWindow::PlaylistMenuHidden() { playlist_queue_->setVisible(true); playlist_queue_play_next_->setVisible(true); playlist_skip_->setVisible(true); } void MainWindow::PlaylistRightClick(const QPoint &global_pos, const QModelIndex &index) { QModelIndex source_index = index; if (index.model() == app_->playlist_manager()->current()->proxy()) { source_index = app_->playlist_manager()->current()->proxy()->mapToSource(index); } playlist_menu_index_ = source_index; // Is this song currently playing? if (app_->playlist_manager()->current()->current_row() == source_index.row() && app_->player()->GetState() == Engine::Playing) { playlist_play_pause_->setText(tr("Pause")); playlist_play_pause_->setIcon(IconLoader::Load("media-playback-pause")); } else { playlist_play_pause_->setText(tr("Play")); playlist_play_pause_->setIcon(IconLoader::Load("media-playback-start")); } // Are we allowed to pause? if (source_index.isValid()) { playlist_play_pause_->setEnabled(app_->playlist_manager()->current()->current_row() != source_index.row() || !(app_->playlist_manager()->current()->item_at(source_index.row())->options() & PlaylistItem::PauseDisabled)); } else { playlist_play_pause_->setEnabled(false); } playlist_stop_after_->setEnabled(source_index.isValid()); // Are any of the selected songs editable or queued? QModelIndexList selection = ui_->playlist->view()->selectionModel()->selectedRows(); bool cue_selected = false; int selected = ui_->playlist->view()->selectionModel()->selectedRows().count(); int editable = 0; int in_queue = 0; int not_in_queue = 0; int in_skipped = 0; int not_in_skipped = 0; int local_songs = 0; int collection_songs = 0; for (const QModelIndex &idx : selection) { const QModelIndex src_idx = app_->playlist_manager()->current()->proxy()->mapToSource(idx); if (!src_idx.isValid()) continue; PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(src_idx.row()); if (!item) continue; if (item->Metadata().url().isLocalFile()) ++local_songs; if (item->Metadata().source() == Song::Source_Collection) ++collection_songs; if (item->Metadata().has_cue()) { cue_selected = true; } else if (item->Metadata().IsEditable()) { ++editable; } if (src_idx.data(Playlist::Role_QueuePosition).toInt() == -1) ++not_in_queue; else ++in_queue; if (item->GetShouldSkip()) ++in_skipped; else ++not_in_skipped; } // this is available when we have one or many files and at least one of those is not CUE related ui_->action_edit_track->setEnabled(editable > 0); ui_->action_edit_track->setVisible(editable > 0); #if defined(HAVE_GSTREAMER) && defined(HAVE_CHROMAPRINT) ui_->action_auto_complete_tags->setEnabled(editable > 0); ui_->action_auto_complete_tags->setVisible(editable > 0); #endif playlist_rescan_songs_->setEnabled(editable > 0); playlist_rescan_songs_->setVisible(editable > 0); #ifdef HAVE_GSTREAMER ui_->action_add_files_to_transcoder->setEnabled(editable); ui_->action_add_files_to_transcoder->setVisible(editable); #endif // the rest of the read / write actions work only when there are no CUEs involved if (cue_selected) editable = 0; playlist_open_in_browser_->setVisible(selected > 0 && local_songs == selected); bool track_column = (index.column() == Playlist::Column_Track); ui_->action_renumber_tracks->setVisible(editable >= 2 && track_column); ui_->action_selection_set_value->setVisible(editable >= 2 && !track_column); ui_->action_edit_value->setVisible(editable > 0); ui_->action_remove_from_playlist->setEnabled(selected > 0); ui_->action_remove_from_playlist->setVisible(selected > 0); playlist_show_in_collection_->setVisible(false); playlist_copy_to_collection_->setVisible(false); playlist_move_to_collection_->setVisible(false); #if defined(HAVE_GSTREAMER) && !defined(Q_OS_WIN) playlist_copy_to_device_->setVisible(false); #endif playlist_organize_->setVisible(false); playlist_delete_->setVisible(false); playlist_copy_url_->setVisible(selected > 0); if (selected < 1) { playlist_queue_->setVisible(false); playlist_queue_play_next_->setVisible(false); playlist_skip_->setVisible(false); } else { playlist_queue_->setVisible(true); playlist_queue_play_next_->setVisible(true); playlist_skip_->setVisible(true); if (in_queue == 1 && not_in_queue == 0) playlist_queue_->setText(tr("Dequeue track")); else if (in_queue > 1 && not_in_queue == 0) playlist_queue_->setText(tr("Dequeue selected tracks")); else if (in_queue == 0 && not_in_queue == 1) playlist_queue_->setText(tr("Queue track")); else if (in_queue == 0 && not_in_queue > 1) playlist_queue_->setText(tr("Queue selected tracks")); else playlist_queue_->setText(tr("Toggle queue status")); if (selected > 1) playlist_queue_play_next_->setText(tr("Queue selected tracks to play next")); else playlist_queue_play_next_->setText(tr("Queue to play next")); if (in_skipped == 1 && not_in_skipped == 0) playlist_skip_->setText(tr("Unskip track")); else if (in_skipped > 1 && not_in_skipped == 0) playlist_skip_->setText(tr("Unskip selected tracks")); else if (in_skipped == 0 && not_in_skipped == 1) playlist_skip_->setText(tr("Skip track")); else if (in_skipped == 0 && not_in_skipped > 1) playlist_skip_->setText(tr("Skip selected tracks")); else playlist_skip_->setText(tr("Toggle skip status")); } if (not_in_queue == 0) playlist_queue_->setIcon(IconLoader::Load("go-previous")); else playlist_queue_->setIcon(IconLoader::Load("go-next")); if (in_skipped < selected) playlist_skip_->setIcon(IconLoader::Load("media-skip-forward")); else playlist_skip_->setIcon(IconLoader::Load("media-playback-start")); if (!index.isValid()) { ui_->action_selection_set_value->setVisible(false); ui_->action_edit_value->setVisible(false); } else { Playlist::Column column = static_cast(index.column()); bool column_is_editable = Playlist::column_is_editable(column) && editable > 0; ui_->action_selection_set_value->setVisible(ui_->action_selection_set_value->isVisible() && column_is_editable); ui_->action_edit_value->setVisible(ui_->action_edit_value->isVisible() && column_is_editable); QString column_name = Playlist::column_name(column); QString column_value = app_->playlist_manager()->current()->data(source_index).toString(); if (column_value.length() > 25) column_value = column_value.left(25) + "..."; ui_->action_selection_set_value->setText(tr("Set %1 to \"%2\"...").arg(column_name.toLower()).arg(column_value)); ui_->action_edit_value->setText(tr("Edit tag \"%1\"...").arg(column_name)); // Is it a collection item? PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_index.row()); if (item && item->IsLocalCollectionItem() && item->Metadata().id() != -1) { playlist_organize_->setVisible(editable > 0); playlist_show_in_collection_->setVisible(editable > 0); playlist_open_in_browser_->setVisible(true); } else { playlist_copy_to_collection_->setVisible(editable > 0); playlist_move_to_collection_->setVisible(editable > 0); } #if defined(HAVE_GSTREAMER) && !defined(Q_OS_WIN) playlist_copy_to_device_->setVisible(editable > 0); #endif #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) playlist_delete_->setVisible(delete_files_ && editable > 0); #endif // Remove old item actions, if any. for (QAction *action : playlistitem_actions_) { playlist_menu_->removeAction(action); } // Get the new item actions, and add them playlistitem_actions_ = item->actions(); playlistitem_actions_separator_->setVisible(!playlistitem_actions_.isEmpty()); playlist_menu_->insertActions(playlistitem_actions_separator_,playlistitem_actions_); } //if it isn't the first time we right click, we need to remove the menu previously created if (playlist_add_to_another_ != nullptr) { playlist_menu_->removeAction(playlist_add_to_another_); delete playlist_add_to_another_; playlist_add_to_another_ = nullptr; } // Create the playlist submenu if songs are selected. if (selected > 0) { QMenu *add_to_another_menu = new QMenu(tr("Add to another playlist"), this); add_to_another_menu->setIcon(IconLoader::Load("list-add")); for (const PlaylistBackend::Playlist &playlist : app_->playlist_backend()->GetAllOpenPlaylists()) { // don't add the current playlist if (playlist.id != app_->playlist_manager()->current()->id()) { QAction *existing_playlist = new QAction(this); existing_playlist->setText(playlist.name); existing_playlist->setData(playlist.id); add_to_another_menu->addAction(existing_playlist); } } add_to_another_menu->addSeparator(); // add to a new playlist QAction *new_playlist = new QAction(this); new_playlist->setText(tr("New playlist")); new_playlist->setData(-1); // fake id add_to_another_menu->addAction(new_playlist); playlist_add_to_another_ = playlist_menu_->insertMenu(ui_->action_remove_from_playlist, add_to_another_menu); QObject::connect(add_to_another_menu, &QMenu::triggered, this, &MainWindow::AddToPlaylistFromAction); } playlist_menu_->popup(global_pos); } void MainWindow::PlaylistPlay() { if (app_->playlist_manager()->current()->current_row() == playlist_menu_index_.row()) { app_->player()->PlayPause(Playlist::AutoScroll_Never); } else { PlayIndex(playlist_menu_index_, Playlist::AutoScroll_Never); } } void MainWindow::PlaylistStopAfter() { app_->playlist_manager()->current()->StopAfter(playlist_menu_index_.row()); } void MainWindow::RescanSongs() { SongList songs; for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) { const QModelIndex source_index = app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index); if (!source_index.isValid()) continue; PlaylistItemPtr item(app_->playlist_manager()->current()->item_at(source_index.row())); if (!item) continue; if (item->IsLocalCollectionItem()) { songs << item->Metadata(); } else if (item->Metadata().source() == Song::Source_LocalFile) { item->Reload(); } } if (songs.isEmpty()) return; app_->collection()->Rescan(songs); } void MainWindow::EditTracks() { SongList songs; PlaylistItemList items; for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) { const QModelIndex source_index = app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index); if (!source_index.isValid()) continue; PlaylistItemPtr item(app_->playlist_manager()->current()->item_at(source_index.row())); if (!item) continue; Song song = item->OriginalMetadata(); if (song.IsEditable()) { songs << song; items << item; } } if (items.isEmpty()) return; edit_tag_dialog_->SetSongs(songs, items); edit_tag_dialog_->show(); edit_tag_dialog_->raise(); } void MainWindow::EditTagDialogAccepted() { for (PlaylistItemPtr item : edit_tag_dialog_->playlist_items()) { item->Reload(); } // FIXME: This is really lame but we don't know what rows have changed. ui_->playlist->view()->update(); app_->playlist_manager()->current()->Save(); } void MainWindow::RenumberTracks() { QModelIndexList indexes = ui_->playlist->view()->selectionModel()->selectedRows(); int track = 1; // Get the index list in order std::stable_sort(indexes.begin(), indexes.end()); // if first selected song has a track number set, start from that offset if (!indexes.isEmpty()) { const Song first_song = app_->playlist_manager()->current()->item_at(indexes[0].row())->OriginalMetadata(); if (first_song.track() > 0) track = first_song.track(); } for (const QModelIndex &proxy_index : indexes) { const QModelIndex source_index = app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index); if (!source_index.isValid()) continue; PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_index.row()); if (!item) continue; Song song = item->OriginalMetadata(); if (song.IsEditable()) { song.set_track(track); TagReaderReply *reply = TagReaderClient::Instance()->SaveFile(song.url().toLocalFile(), song); QPersistentModelIndex persistent_index = QPersistentModelIndex(source_index); QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, persistent_index]() { SongSaveComplete(reply, persistent_index); }, Qt::QueuedConnection); } ++track; } } void MainWindow::SongSaveComplete(TagReaderReply *reply, const QPersistentModelIndex &idx) { if (reply->is_successful() && idx.isValid()) { app_->playlist_manager()->current()->ReloadItems(QList()<< idx.row()); } metaObject()->invokeMethod(reply, "deleteLater", Qt::QueuedConnection); } void MainWindow::SelectionSetValue() { Playlist::Column column = static_cast(playlist_menu_index_.column()); QVariant column_value = app_->playlist_manager()->current()->data(playlist_menu_index_); for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) { const QModelIndex source_index = app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index); if (!source_index.isValid()) continue; PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_index.row()); if (!item) continue; Song song = item->OriginalMetadata(); if (!song.is_valid() || !song.url().isLocalFile()) continue; if (Playlist::set_column_value(song, column, column_value)) { TagReaderReply *reply = TagReaderClient::Instance()->SaveFile(song.url().toLocalFile(), song); QPersistentModelIndex persistent_index = QPersistentModelIndex(source_index); QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, persistent_index]() { SongSaveComplete(reply, persistent_index); }, Qt::QueuedConnection); } } } void MainWindow::EditValue() { QModelIndex current = ui_->playlist->view()->currentIndex(); if (!current.isValid()) return; // Edit the last column that was right-clicked on. If nothing's ever been right clicked then look for the first editable column. int column = playlist_menu_index_.column(); if (column == -1) { for (int i = 0; i < ui_->playlist->view()->model()->columnCount(); ++i) { if (ui_->playlist->view()->isColumnHidden(i)) continue; if (!Playlist::column_is_editable(Playlist::Column(i))) continue; column = i; break; } } ui_->playlist->view()->edit(current.sibling(current.row(), column)); } void MainWindow::AddFile() { // Last used directory QString directory = settings_.value("add_media_path", QDir::currentPath()).toString(); PlaylistParser parser(app_->collection_backend()); // Show dialog QStringList file_names = QFileDialog::getOpenFileNames(this, tr("Add file"), directory, QString("%1 (%2);;%3;;%4").arg(tr("Music"), FileView::kFileFilter, parser.filters(), tr(kAllFilesFilterSpec))); if (file_names.isEmpty()) return; // Save last used directory settings_.setValue("add_media_path", file_names[0]); // Convert to URLs QList urls; for (const QString &path : file_names) { urls << QUrl::fromLocalFile(QFileInfo(path).canonicalFilePath()); } MimeData *mimedata = new MimeData; mimedata->setUrls(urls); AddToPlaylist(mimedata); } void MainWindow::AddFolder() { // Last used directory QString directory = settings_.value("add_folder_path", QDir::currentPath()).toString(); // Show dialog directory = QFileDialog::getExistingDirectory(this, tr("Add folder"), directory); if (directory.isEmpty()) return; // Save last used directory settings_.setValue("add_folder_path", directory); // Add media MimeData *mimedata = new MimeData; mimedata->setUrls(QList() << QUrl::fromLocalFile(QFileInfo(directory).canonicalFilePath())); AddToPlaylist(mimedata); } void MainWindow::AddCDTracks() { MimeData *mimedata = new MimeData; // We are putting empty data, but we specify cdda mimetype to indicate that we want to load audio cd tracks mimedata->open_in_new_playlist_ = true; mimedata->setData(Playlist::kCddaMimeType, QByteArray()); AddToPlaylist(mimedata); } void MainWindow::AddStream() { add_stream_dialog_->show(); add_stream_dialog_->raise(); } void MainWindow::AddStreamAccepted() { MimeData *mimedata = new MimeData; mimedata->setUrls(QList() << add_stream_dialog_->url()); AddToPlaylist(mimedata); } void MainWindow::ShowInCollection() { // Show the first valid selected track artist/album in CollectionView SongList songs; for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) { const QModelIndex source_index = app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index); if (!source_index.isValid()) continue; PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_index.row()); if (item && item->IsLocalCollectionItem()) { songs << item->OriginalMetadata(); break; } } QString search; if (!songs.isEmpty()) { search = "artist:" + songs.first().artist() + " album:" + songs.first().album(); } collection_view_->filter()->ShowInCollection(search); } void MainWindow::PlaylistRemoveCurrent() { ui_->playlist->view()->RemoveSelected(); } void MainWindow::PlaylistClearCurrent() { if (app_->playlist_manager()->current()->rowCount() > Playlist::kUndoItemLimit) { QMessageBox messagebox(QMessageBox::Warning, tr("Clear playlist"), tr("Playlist has %1 songs, too large to undo, are you sure you want to clear the playlist?").arg(app_->playlist_manager()->current()->rowCount()), QMessageBox::Ok|QMessageBox::Cancel); messagebox.setTextFormat(Qt::RichText); int result = messagebox.exec(); switch (result) { case QMessageBox::Ok: break; case QMessageBox::Cancel: default: return; } } app_->playlist_manager()->ClearCurrent(); } void MainWindow::PlaylistEditFinished(const QModelIndex &idx) { if (idx == playlist_menu_index_) SelectionSetValue(); } void MainWindow::CommandlineOptionsReceived(const quint32 instanceId, const QByteArray &string_options) { Q_UNUSED(instanceId); CommandlineOptions options; options.Load(string_options); if (options.is_empty()) { raise(); show(); activateWindow(); hidden_ = false; } else CommandlineOptionsReceived(options); } void MainWindow::CommandlineOptionsReceived(const CommandlineOptions &options) { switch (options.player_action()) { case CommandlineOptions::Player_Play: if (options.urls().empty()) { app_->player()->Play(); } break; case CommandlineOptions::Player_PlayPause: app_->player()->PlayPause(Playlist::AutoScroll_Maybe); break; case CommandlineOptions::Player_Pause: app_->player()->Pause(); break; case CommandlineOptions::Player_Stop: app_->player()->Stop(); break; case CommandlineOptions::Player_StopAfterCurrent: app_->player()->StopAfterCurrent(); break; case CommandlineOptions::Player_Previous: app_->player()->Previous(); break; case CommandlineOptions::Player_Next: app_->player()->Next(); break; case CommandlineOptions::Player_PlayPlaylist: if (options.playlist_name().isEmpty()) { qLog(Error) << "ERROR: playlist name missing"; } else { app_->player()->PlayPlaylist(options.playlist_name()); } break; case CommandlineOptions::Player_RestartOrPrevious: app_->player()->RestartOrPrevious(); break; case CommandlineOptions::Player_None: break; } if (!options.urls().empty()) { #ifdef HAVE_TIDAL for (const QUrl &url : options.urls()) { if (url.scheme() == "tidal" && url.host() == "login") { emit AuthorizationUrlReceived(url); return; } } #endif MimeData *mimedata = new MimeData; mimedata->setUrls(options.urls()); // Behaviour depends on command line options, so set it here mimedata->override_user_settings_ = true; if (options.player_action() == CommandlineOptions::Player_Play) mimedata->play_now_ = true; else ApplyPlayBehaviour(doubleclick_playmode_, mimedata); switch (options.url_list_action()) { case CommandlineOptions::UrlList_Load: mimedata->clear_first_ = true; break; case CommandlineOptions::UrlList_Append: // Nothing to do break; case CommandlineOptions::UrlList_None: ApplyAddBehaviour(doubleclick_addmode_, mimedata); break; case CommandlineOptions::UrlList_CreateNew: mimedata->name_for_new_playlist_ = options.playlist_name(); ApplyAddBehaviour(BehaviourSettingsPage::AddBehaviour_OpenInNew, mimedata); break; } AddToPlaylist(mimedata); } if (options.set_volume() != -1) app_->player()->SetVolume(options.set_volume()); if (options.volume_modifier() != 0) { app_->player()->SetVolume(app_->player()->GetVolume() +options.volume_modifier()); } if (options.seek_to() != -1) { app_->player()->SeekTo(options.seek_to()); } else if (options.seek_by() != 0) { app_->player()->SeekTo(app_->player()->engine()->position_nanosec() / kNsecPerSec + options.seek_by()); } if (options.play_track_at() != -1) app_->player()->PlayAt(options.play_track_at(), Engine::Manual, Playlist::AutoScroll_Maybe, true); if (options.show_osd()) app_->player()->ShowOSD(); if (options.toggle_pretty_osd()) app_->player()->TogglePrettyOSD(); } void MainWindow::ForceShowOSD(const Song &song, const bool toggle) { Q_UNUSED(song); if (toggle) { osd_->SetPrettyOSDToggleMode(toggle); } osd_->ReshowCurrentSong(); } void MainWindow::Activate() { show(); } bool MainWindow::LoadUrl(const QString &url) { if (!QFile::exists(url)) return false; MimeData *mimedata = new MimeData; mimedata->setUrls(QList() << QUrl::fromLocalFile(url)); AddToPlaylist(mimedata); return true; } void MainWindow::CheckForUpdates() { #if defined(Q_OS_MACOS) mac::CheckForUpdates(); #endif } void MainWindow::PlaylistUndoRedoChanged(QAction *undo, QAction *redo) { playlist_menu_->insertAction(playlist_undoredo_, undo); playlist_menu_->insertAction(playlist_undoredo_, redo); } void MainWindow::AddFilesToTranscoder() { #ifdef HAVE_GSTREAMER QStringList filenames; for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) { const QModelIndex source_index = app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index); if (!source_index.isValid()) continue; PlaylistItemPtr item(app_->playlist_manager()->current()->item_at(source_index.row())); if (!item) continue; Song song = item->OriginalMetadata(); if (!song.is_valid() || !song.url().isLocalFile()) continue; filenames << song.url().toLocalFile(); } if (filenames.isEmpty()) return; transcode_dialog_->SetFilenames(filenames); ShowTranscodeDialog(); #endif } void MainWindow::ShowCollectionConfig() { settings_dialog_->OpenAtPage(SettingsDialog::Page_Collection); } void MainWindow::TaskCountChanged(const int count) { if (count == 0) { ui_->status_bar_stack->setCurrentWidget(ui_->playlist_summary_page); } else { ui_->status_bar_stack->setCurrentWidget(ui_->multi_loading_indicator); } } void MainWindow::PlayingWidgetPositionChanged(const bool above_status_bar) { if (above_status_bar) ui_->status_bar->setParent(ui_->centralWidget); else ui_->status_bar->setParent(ui_->player_controls_container); ui_->status_bar->parentWidget()->layout()->addWidget(ui_->status_bar); ui_->status_bar->show(); } void MainWindow::CopyFilesToCollection(const QList &urls) { organize_dialog_->SetDestinationModel(app_->collection_model()->directory_model()); organize_dialog_->SetUrls(urls); organize_dialog_->SetCopy(true); organize_dialog_->show(); organize_dialog_->raise(); } void MainWindow::MoveFilesToCollection(const QList &urls) { organize_dialog_->SetDestinationModel(app_->collection_model()->directory_model()); organize_dialog_->SetUrls(urls); organize_dialog_->SetCopy(false); organize_dialog_->show(); organize_dialog_->raise(); } void MainWindow::CopyFilesToDevice(const QList &urls) { #if defined(HAVE_GSTREAMER) && !defined(Q_OS_WIN) organize_dialog_->SetDestinationModel(app_->device_manager()->connected_devices_model(), true); organize_dialog_->SetCopy(true); if (organize_dialog_->SetUrls(urls)) { organize_dialog_->show(); organize_dialog_->raise(); } else { QMessageBox::warning(this, tr("Error"), tr("None of the selected songs were suitable for copying to a device")); } #else Q_UNUSED(urls); #endif } void MainWindow::EditFileTags(const QList &urls) { SongList songs; for (const QUrl &url : urls) { Song song; song.set_url(url); song.set_valid(true); song.set_filetype(Song::FileType_MPEG); songs << song; } edit_tag_dialog_->SetSongs(songs); edit_tag_dialog_->show(); edit_tag_dialog_->raise(); } void MainWindow::PlaylistCopyToCollection() { PlaylistOrganizeSelected(true); } void MainWindow::PlaylistMoveToCollection() { PlaylistOrganizeSelected(false); } void MainWindow::PlaylistOrganizeSelected(const bool copy) { SongList songs; for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) { const QModelIndex source_index = app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index); if (!source_index.isValid()) continue; PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_index.row()); if (!item) continue; Song song = item->OriginalMetadata(); if (!song.is_valid() || !song.url().isLocalFile()) continue; songs << song; } if (songs.isEmpty()) return; organize_dialog_->SetDestinationModel(app_->collection_model()->directory_model()); organize_dialog_->SetSongs(songs); organize_dialog_->SetCopy(copy); organize_dialog_->show(); organize_dialog_->raise(); } void MainWindow::PlaylistOpenInBrowser() { QList urls; for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) { const QModelIndex source_index = app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index); if (!source_index.isValid()) continue; urls << QUrl(source_index.sibling(source_index.row(), Playlist::Column_Filename).data().toString()); } Utilities::OpenInFileBrowser(urls); } void MainWindow::PlaylistCopyUrl() { QList urls; for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) { const QModelIndex source_index = app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index); if (!source_index.isValid()) continue; PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_index.row()); if (!item) continue; urls << item->StreamUrl(); } if (urls.count() > 0) { QMimeData mime_data; mime_data.setUrls(urls); QApplication::clipboard()->setText(mime_data.text()); } } void MainWindow::PlaylistQueue() { QModelIndexList indexes; for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) { indexes << app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index); } app_->playlist_manager()->current()->queue()->ToggleTracks(indexes); } void MainWindow::PlaylistQueuePlayNext() { QModelIndexList indexes; for (const QModelIndex& proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) { indexes << app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index); } app_->playlist_manager()->current()->queue()->InsertFirst(indexes); } void MainWindow::PlaylistSkip() { QModelIndexList indexes; for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) { indexes << app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index); } app_->playlist_manager()->current()->SkipTracks(indexes); } void MainWindow::PlaylistCopyToDevice() { #ifndef Q_OS_WIN SongList songs; for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) { const QModelIndex source_index = app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index); if (!source_index.isValid()) continue; PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_index.row()); if (!item) continue; Song song = item->OriginalMetadata(); if (!song.is_valid() || !song.url().isLocalFile()) continue; songs << song; } if (songs.isEmpty()) return; organize_dialog_->SetDestinationModel(app_->device_manager()->connected_devices_model(), true); organize_dialog_->SetCopy(true); if (organize_dialog_->SetSongs(songs)) { organize_dialog_->show(); organize_dialog_->raise(); } else { QMessageBox::warning(this, tr("Error"), tr("None of the selected songs were suitable for copying to a device")); } #endif } void MainWindow::ChangeCollectionQueryMode(QAction *action) { if (action == collection_show_duplicates_) { collection_view_->filter()->SetQueryMode(QueryOptions::QueryMode_Duplicates); } else if (action == collection_show_untagged_) { collection_view_->filter()->SetQueryMode(QueryOptions::QueryMode_Untagged); } else { collection_view_->filter()->SetQueryMode(QueryOptions::QueryMode_All); } } void MainWindow::ShowCoverManager() { cover_manager_->show(); cover_manager_->raise(); } void MainWindow::ShowEqualizer() { equalizer_->show(); equalizer_->raise(); } SettingsDialog *MainWindow::CreateSettingsDialog() { SettingsDialog *settings_dialog = new SettingsDialog(app_, osd_, this); #ifdef HAVE_GLOBALSHORTCUTS settings_dialog->SetGlobalShortcutManager(globalshortcuts_manager_); #endif // Settings QObject::connect(settings_dialog, &SettingsDialog::ReloadSettings, this, &MainWindow::ReloadAllSettings); // Allows custom notification preview QObject::connect(settings_dialog, &SettingsDialog::NotificationPreview, this, &MainWindow::HandleNotificationPreview); return settings_dialog; } void MainWindow::OpenSettingsDialog() { settings_dialog_->show(); settings_dialog_->raise(); } void MainWindow::OpenSettingsDialogAtPage(SettingsDialog::Page page) { settings_dialog_->OpenAtPage(page); } EditTagDialog *MainWindow::CreateEditTagDialog() { EditTagDialog *edit_tag_dialog = new EditTagDialog(app_); QObject::connect(edit_tag_dialog, &EditTagDialog::accepted, this, &MainWindow::EditTagDialogAccepted); QObject::connect(edit_tag_dialog, &EditTagDialog::Error, this, &MainWindow::ShowErrorDialog); return edit_tag_dialog; } void MainWindow::ShowAboutDialog() { about_dialog_->show(); about_dialog_->raise(); } void MainWindow::ShowTranscodeDialog() { #ifdef HAVE_GSTREAMER transcode_dialog_->show(); transcode_dialog_->raise(); #endif } void MainWindow::ShowErrorDialog(const QString &message) { error_dialog_->ShowMessage(message); } void MainWindow::CheckFullRescanRevisions() { int from = app_->database()->startup_schema_version(); int to = app_->database()->current_schema_version(); // if we're restoring DB from scratch or nothing has changed, do nothing if (from == 0 || from == to) { return; } // Collect all reasons QSet reasons; for (int i = from ; i <= to ; ++i) { QString reason = app_->collection()->full_rescan_reason(i); if (!reason.isEmpty()) { reasons.insert(reason); } } // if we have any... if (!reasons.isEmpty()) { QString message = tr("The version of Strawberry you've just updated to requires a full collection rescan because of the new features listed below:") + "
    "; for(const QString &reason : reasons) { message += ("
  • " + reason + "
  • "); } message += "
" + tr("Would you like to run a full rescan right now?"); if (QMessageBox::question(this, tr("Collection rescan notice"), message, QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) { app_->collection()->FullScan(); } } } void MainWindow::PlaylistViewSelectionModelChanged() { QObject::connect(ui_->playlist->view()->selectionModel(), &QItemSelectionModel::currentChanged, this, &MainWindow::PlaylistCurrentChanged); } void MainWindow::PlaylistCurrentChanged(const QModelIndex &proxy_current) { const QModelIndex source_current = app_->playlist_manager()->current()->proxy()->mapToSource(proxy_current); // If the user moves the current index using the keyboard and then presses // F2, we don't want that editing the last column that was right clicked on. if (source_current != playlist_menu_index_) playlist_menu_index_ = QModelIndex(); } void MainWindow::Raise() { show(); activateWindow(); hidden_ = false; } #ifdef Q_OS_WIN #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) bool MainWindow::nativeEvent(const QByteArray &eventType, void *message, qintptr *result) { #else bool MainWindow::nativeEvent(const QByteArray &eventType, void *message, long *result) { #endif if (exit_count_ == 0 && message) { MSG *msg = static_cast(message); thumbbar_->HandleWinEvent(msg); } return QMainWindow::nativeEvent(eventType, message, result); } #endif // Q_OS_WIN void MainWindow::AutoCompleteTags() { #if defined(HAVE_GSTREAMER) && defined(HAVE_CHROMAPRINT) autocomplete_tag_items_.clear(); // Create the tag fetching stuff if it hasn't been already if (!tag_fetcher_) { tag_fetcher_.reset(new TagFetcher); track_selection_dialog_.reset(new TrackSelectionDialog); track_selection_dialog_->set_save_on_close(true); QObject::connect(tag_fetcher_.get(), &TagFetcher::ResultAvailable, track_selection_dialog_.get(), &TrackSelectionDialog::FetchTagFinished, Qt::QueuedConnection); QObject::connect(tag_fetcher_.get(), &TagFetcher::Progress, track_selection_dialog_.get(), &TrackSelectionDialog::FetchTagProgress); QObject::connect(track_selection_dialog_.get(), &TrackSelectionDialog::accepted, this, &MainWindow::AutoCompleteTagsAccepted); QObject::connect(track_selection_dialog_.get(), &TrackSelectionDialog::finished, tag_fetcher_.get(), &TagFetcher::Cancel); QObject::connect(track_selection_dialog_.get(), &TrackSelectionDialog::Error, this, &MainWindow::ShowErrorDialog); } // Get the selected songs and start fetching tags for them SongList songs; for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) { const QModelIndex source_index = app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index); if (!source_index.isValid()) continue; PlaylistItemPtr item(app_->playlist_manager()->current()->item_at(source_index.row())); if (!item) continue; Song song = item->OriginalMetadata(); if (song.IsEditable()) { songs << song; autocomplete_tag_items_ << item; } } if (songs.isEmpty()) return; track_selection_dialog_->Init(songs); tag_fetcher_->StartFetch(songs); track_selection_dialog_->show(); track_selection_dialog_->raise(); #endif } void MainWindow::AutoCompleteTagsAccepted() { for (PlaylistItemPtr item : autocomplete_tag_items_) { item->Reload(); } autocomplete_tag_items_.clear(); // This is really lame but we don't know what rows have changed ui_->playlist->view()->update(); } void MainWindow::HandleNotificationPreview(OSDBase::Behaviour type, QString line1, QString line2) { if (!app_->playlist_manager()->current()->GetAllSongs().isEmpty()) { // Show a preview notification for the first song in the current playlist osd_->ShowPreview(type, line1, line2, app_->playlist_manager()->current()->GetAllSongs().first()); } else { qLog(Debug) << "The current playlist is empty, showing a fake song"; // Create a fake song Song fake(Song::Source_LocalFile); fake.Init("Title", "Artist", "Album", 123); fake.set_genre("Classical"); fake.set_composer("Anonymous"); fake.set_performer("Anonymous"); fake.set_track(1); fake.set_disc(1); fake.set_year(2011); osd_->ShowPreview(type, line1, line2, fake); } } void MainWindow::ShowConsole() { console_->show(); console_->raise(); } void MainWindow::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Space) { app_->player()->PlayPause(Playlist::AutoScroll_Never); e->accept(); } else if (e->key() == Qt::Key_Left) { ui_->track_slider->Seek(-1); e->accept(); } else if (e->key() == Qt::Key_Right) { ui_->track_slider->Seek(1); e->accept(); } else { QMainWindow::keyPressEvent(e); } } void MainWindow::LoadCoverFromFile() { album_cover_choice_controller_->LoadCoverFromFile(&song_); } void MainWindow::LoadCoverFromURL() { album_cover_choice_controller_->LoadCoverFromURL(&song_); } void MainWindow::SearchForCover() { album_cover_choice_controller_->SearchForCover(&song_); } void MainWindow::SaveCoverToFile() { album_cover_choice_controller_->SaveCoverToFileManual(song_, album_cover_); } void MainWindow::UnsetCover() { album_cover_choice_controller_->UnsetCover(&song_); } void MainWindow::ClearCover() { album_cover_choice_controller_->ClearCover(&song_); } void MainWindow::DeleteCover() { album_cover_choice_controller_->DeleteCover(&song_, true); } void MainWindow::ShowCover() { album_cover_choice_controller_->ShowCover(song_, album_cover_.image); } void MainWindow::SearchCoverAutomatically() { GetCoverAutomatically(); } void MainWindow::AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result) { if (song != song_playing_) return; song_ = song; album_cover_ = result.album_cover; emit AlbumCoverReady(song, result.album_cover.image); const bool enable_change_art = song.is_collection_song() && !song.effective_albumartist().isEmpty() && !song.album().isEmpty(); album_cover_choice_controller_->show_cover_action()->setEnabled(result.success && result.type != AlbumCoverLoaderResult::Type_ManuallyUnset); album_cover_choice_controller_->cover_to_file_action()->setEnabled(result.success && result.type != AlbumCoverLoaderResult::Type_ManuallyUnset); album_cover_choice_controller_->cover_from_file_action()->setEnabled(enable_change_art); album_cover_choice_controller_->cover_from_url_action()->setEnabled(enable_change_art); album_cover_choice_controller_->search_for_cover_action()->setEnabled(app_->cover_providers()->HasAnyProviders() && enable_change_art); album_cover_choice_controller_->unset_cover_action()->setEnabled(enable_change_art && !song.has_manually_unset_cover()); album_cover_choice_controller_->clear_cover_action()->setEnabled(enable_change_art && !song.art_manual().isEmpty()); album_cover_choice_controller_->delete_cover_action()->setEnabled(enable_change_art && result.success && result.type != AlbumCoverLoaderResult::Type_ManuallyUnset); GetCoverAutomatically(); } void MainWindow::GetCoverAutomatically() { // Search for cover automatically? bool search = album_cover_choice_controller_->search_cover_auto_action()->isChecked() && !song_.has_manually_unset_cover() && !song_.art_automatic_is_valid() && !song_.art_manual_is_valid() && !song_.effective_albumartist().isEmpty() && !song_.effective_album().isEmpty(); if (search) { emit SearchCoverInProgress(); album_cover_choice_controller_->SearchCoverAutomatically(song_); } } void MainWindow::ScrobblingEnabledChanged(const bool value) { if (app_->scrobbler()->ScrobbleButton()) SetToggleScrobblingIcon(value); } void MainWindow::ScrobbleButtonVisibilityChanged(const bool value) { ui_->button_scrobble->setVisible(value); ui_->action_toggle_scrobbling->setVisible(value); if (value) SetToggleScrobblingIcon(app_->scrobbler()->IsEnabled()); } void MainWindow::LoveButtonVisibilityChanged(const bool value) { if (value) ui_->widget_love->show(); else ui_->widget_love->hide(); if (tray_icon_) tray_icon_->LoveVisibilityChanged(value); } void MainWindow::SetToggleScrobblingIcon(const bool value) { if (value) { if (app_->playlist_manager()->active() && app_->playlist_manager()->active()->scrobbled()) ui_->action_toggle_scrobbling->setIcon(IconLoader::Load("scrobble", 22)); else ui_->action_toggle_scrobbling->setIcon(IconLoader::Load("scrobble", 22)); // TODO: Create a faint version of the icon } else { ui_->action_toggle_scrobbling->setIcon(IconLoader::Load("scrobble-disabled", 22)); } } void MainWindow::Love() { app_->scrobbler()->Love(); ui_->button_love->setEnabled(false); ui_->action_love->setEnabled(false); if (tray_icon_) tray_icon_->LoveStateChanged(false); } void MainWindow::PlaylistDelete() { if (!delete_files_) return; SongList selected_songs; QStringList files; bool is_current_item = false; for (const QModelIndex &proxy_idx : ui_->playlist->view()->selectionModel()->selectedRows()) { QModelIndex source_idx = app_->playlist_manager()->current()->proxy()->mapToSource(proxy_idx); PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_idx.row()); if (!item || !item->Metadata().url().isLocalFile()) continue; selected_songs << item->Metadata(); files << item->Metadata().url().toLocalFile(); if (item == app_->player()->GetCurrentItem()) is_current_item = true; } if (selected_songs.isEmpty()) return; if (DeleteConfirmationDialog::warning(files) != QDialogButtonBox::Yes) return; if (app_->player()->GetState() == Engine::Playing && app_->playlist_manager()->current()->rowCount() == selected_songs.count()) { app_->player()->Stop(); } ui_->playlist->view()->RemoveSelected(); if (app_->player()->GetState() == Engine::Playing && is_current_item) { app_->player()->Next(); } std::shared_ptr storage(new FilesystemMusicStorage("/")); DeleteFiles *delete_files = new DeleteFiles(app_->task_manager(), storage, true); //QObject::connect(delete_files, &DeleteFiles::Finished, this, &MainWindow::DeleteFinished); delete_files->Start(selected_songs); }