NowPlayingWidget::NowPlayingWidget(QWidget* parent)
    : QWidget(parent),
      app_(nullptr),
      album_cover_choice_controller_(new AlbumCoverChoiceController(this)),
      mode_(SmallSongDetails),
      menu_(new QMenu(this)),
      above_statusbar_action_(nullptr),
      fit_cover_width_action_(nullptr),
      visible_(false),
      small_ideal_height_(0),
      fit_width_(false),
      show_hide_animation_(new QTimeLine(500, this)),
      fade_animation_(new QTimeLine(1000, this)),
      details_(new QTextDocument(this)),
      previous_track_opacity_(0.0),
      bask_in_his_glory_action_(nullptr),
      downloading_covers_(false),
      aww_(false),
      kittens_(nullptr),
      pending_kitten_(0) {
  // Load settings
  QSettings s;
  s.beginGroup(kSettingsGroup);
  mode_ = Mode(s.value("mode", SmallSongDetails).toInt());
  album_cover_choice_controller_->search_cover_auto_action()->setChecked(
      s.value("search_for_cover_auto", false).toBool());
  fit_width_ = s.value("fit_cover_width", false).toBool();

  // Accept drops for setting album art
  setAcceptDrops(true);

  // Context menu
  QActionGroup* mode_group = new QActionGroup(this);
  QSignalMapper* mode_mapper = new QSignalMapper(this);
  connect(mode_mapper, SIGNAL(mapped(int)), SLOT(SetMode(int)));
  CreateModeAction(SmallSongDetails, tr("Small album cover"), mode_group,
                   mode_mapper);
  CreateModeAction(LargeSongDetails, tr("Large album cover"), mode_group,
                   mode_mapper);
  CreateModeAction(LargeSongDetailsBelow,
                   tr("Large album cover (details below)"), mode_group,
                   mode_mapper);

  menu_->addActions(mode_group->actions());

  fit_cover_width_action_ = menu_->addAction(tr("Fit cover to width"));

  fit_cover_width_action_->setCheckable(true);
  fit_cover_width_action_->setEnabled((mode_ != SmallSongDetails) ? true
                                                                  : false);
  connect(fit_cover_width_action_, SIGNAL(toggled(bool)),
          SLOT(FitCoverWidth(bool)));
  fit_cover_width_action_->setChecked(fit_width_);
  menu_->addSeparator();

  QList<QAction*> actions = album_cover_choice_controller_->GetAllActions();

  // Here we add the search automatically action, too!
  actions.append(album_cover_choice_controller_->search_cover_auto_action());

  connect(album_cover_choice_controller_->cover_from_file_action(),
          SIGNAL(triggered()), this, SLOT(LoadCoverFromFile()));
  connect(album_cover_choice_controller_->cover_to_file_action(),
          SIGNAL(triggered()), this, SLOT(SaveCoverToFile()));
  connect(album_cover_choice_controller_->cover_from_url_action(),
          SIGNAL(triggered()), this, SLOT(LoadCoverFromURL()));
  connect(album_cover_choice_controller_->search_for_cover_action(),
          SIGNAL(triggered()), this, SLOT(SearchForCover()));
  connect(album_cover_choice_controller_->unset_cover_action(),
          SIGNAL(triggered()), this, SLOT(UnsetCover()));
  connect(album_cover_choice_controller_->show_cover_action(),
          SIGNAL(triggered()), this, SLOT(ShowCover()));
  connect(album_cover_choice_controller_->search_cover_auto_action(),
          SIGNAL(triggered()), this, SLOT(SearchCoverAutomatically()));

  menu_->addActions(actions);
  menu_->addSeparator();
  above_statusbar_action_ = menu_->addAction(tr("Show above status bar"));

  above_statusbar_action_->setCheckable(true);
  connect(above_statusbar_action_, SIGNAL(toggled(bool)),
          SLOT(ShowAboveStatusBar(bool)));
  above_statusbar_action_->setChecked(
      s.value("above_status_bar", false).toBool());

  bask_in_his_glory_action_ =
      menu_->addAction(tr("ALL GLORY TO THE HYPNOTOAD"));
  bask_in_his_glory_action_->setVisible(false);
  connect(bask_in_his_glory_action_, SIGNAL(triggered()), SLOT(Bask()));

  // Animations
  connect(show_hide_animation_, SIGNAL(frameChanged(int)),
          SLOT(SetHeight(int)));
  setMaximumHeight(0);

  connect(fade_animation_, SIGNAL(valueChanged(qreal)),
          SLOT(FadePreviousTrack(qreal)));
  fade_animation_->setDirection(QTimeLine::Backward);  // 1.0 -> 0.0

  // add placeholder text to get the correct height
  if (mode_ == LargeSongDetailsBelow) {
    details_->setDefaultStyleSheet(
        "p {"
        "  font-size: small;"
        "  color: white;"
        "}");
    details_->setHtml(QString("<p align=center><i></i><br/><br/></p>"));
  }

  UpdateHeight();

  connect(album_cover_choice_controller_, SIGNAL(AutomaticCoverSearchDone()),
          this, SLOT(AutomaticCoverSearchDone()));
}
void AlbumCoverManager::Init() {
  // View menu
  QActionGroup* filter_group = new QActionGroup(this);
  filter_all_ = filter_group->addAction(tr("All albums"));
  filter_with_covers_ = filter_group->addAction(tr("Albums with covers"));
  filter_without_covers_ = filter_group->addAction(tr("Albums without covers"));
  filter_all_->setCheckable(true);
  filter_with_covers_->setCheckable(true);
  filter_without_covers_->setCheckable(true);
  filter_group->setExclusive(true);
  filter_all_->setChecked(true);

  QMenu* view_menu = new QMenu(this);
  view_menu->addActions(filter_group->actions());

  ui_->view->setMenu(view_menu);

  // Context menu

  QList<QAction*> actions = album_cover_choice_controller_->GetAllActions();

  connect(album_cover_choice_controller_->cover_from_file_action(),
          SIGNAL(triggered()), this, SLOT(LoadCoverFromFile()));
  connect(album_cover_choice_controller_->cover_to_file_action(),
          SIGNAL(triggered()), this, SLOT(SaveCoverToFile()));
  connect(album_cover_choice_controller_->cover_from_url_action(),
          SIGNAL(triggered()), this, SLOT(LoadCoverFromURL()));
  connect(album_cover_choice_controller_->search_for_cover_action(),
          SIGNAL(triggered()), this, SLOT(SearchForCover()));
  connect(album_cover_choice_controller_->unset_cover_action(),
          SIGNAL(triggered()), this, SLOT(UnsetCover()));
  connect(album_cover_choice_controller_->show_cover_action(),
          SIGNAL(triggered()), this, SLOT(ShowCover()));

  context_menu_->addActions(actions);
  context_menu_->addSeparator();
  context_menu_->addAction(ui_->action_load);
  context_menu_->addAction(ui_->action_add_to_playlist);

  ui_->albums->installEventFilter(this);

  // Connections
  connect(ui_->artists, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)),
          SLOT(ArtistChanged(QListWidgetItem*)));
  connect(ui_->filter, SIGNAL(textChanged(QString)), SLOT(UpdateFilter()));
  connect(filter_group, SIGNAL(triggered(QAction*)), SLOT(UpdateFilter()));
  connect(ui_->view, SIGNAL(clicked()), ui_->view, SLOT(showMenu()));
  connect(ui_->fetch, SIGNAL(clicked()), SLOT(FetchAlbumCovers()));
  connect(cover_fetcher_, SIGNAL(AlbumCoverFetched(quint64,QImage,CoverSearchStatistics)),
          SLOT(AlbumCoverFetched(quint64,QImage,CoverSearchStatistics)));
  connect(ui_->action_fetch, SIGNAL(triggered()), SLOT(FetchSingleCover()));
  connect(ui_->albums, SIGNAL(doubleClicked(QModelIndex)), SLOT(AlbumDoubleClicked(QModelIndex)));
  connect(ui_->action_add_to_playlist, SIGNAL(triggered()), SLOT(AddSelectedToPlaylist()));
  connect(ui_->action_load, SIGNAL(triggered()), SLOT(LoadSelectedToPlaylist()));

  #ifdef Q_OS_DARWIN
    MacLineEdit* lineedit = new MacLineEdit(ui_->filter->parentWidget());
    lineedit->set_hint(ui_->filter->hint());
    delete ui_->filter;
    ui_->horizontalLayout->insertWidget(0, lineedit);
    filter_ = lineedit;
    connect(lineedit, SIGNAL(textChanged(QString)), SLOT(UpdateFilter()));
    lineedit->show();
  #else
    filter_ = ui_->filter;
  #endif

  // Restore settings
  QSettings s;
  s.beginGroup(kSettingsGroup);

  restoreGeometry(s.value("geometry").toByteArray());
  if (!ui_->splitter->restoreState(s.value("splitter_state").toByteArray())) {
    // Sensible default size for the artists view
    ui_->splitter->setSizes(QList<int>() << 200 << width() - 200);
  }

  cover_loader_->Start(true);
  CoverLoaderInitialised();
  cover_searcher_->Init(cover_fetcher_);

  new ForceScrollPerPixel(ui_->albums, this);
}
EditTagDialog::EditTagDialog(Application* app, QWidget* parent)
    : QDialog(parent),
      ui_(new Ui_EditTagDialog),
      app_(app),
      album_cover_choice_controller_(new AlbumCoverChoiceController(this)),
      loading_(false),
      ignore_edits_(false),
      tag_fetcher_(new TagFetcher(this)),
      cover_art_id_(0),
      cover_art_is_set_(false),
      results_dialog_(new TrackSelectionDialog(this)) {
  cover_options_.default_output_image_ =
      AlbumCoverLoader::ScaleAndPad(cover_options_, QImage(":nocover.png"));

  connect(app_->album_cover_loader(),
          SIGNAL(ImageLoaded(quint64, QImage, QImage)),
          SLOT(ArtLoaded(quint64, QImage, QImage)));

  connect(tag_fetcher_, SIGNAL(ResultAvailable(Song, SongList)),
          results_dialog_, SLOT(FetchTagFinished(Song, SongList)),
          Qt::QueuedConnection);
  connect(tag_fetcher_, SIGNAL(Progress(Song, QString)), results_dialog_,
          SLOT(FetchTagProgress(Song, QString)));
  connect(results_dialog_, SIGNAL(SongChosen(Song, Song)),
          SLOT(FetchTagSongChosen(Song, Song)));
  connect(results_dialog_, SIGNAL(finished(int)), tag_fetcher_, SLOT(Cancel()));

  album_cover_choice_controller_->SetApplication(app_);

  ui_->setupUi(this);
  ui_->splitter->setSizes(QList<int>() << 200 << width() - 200);
  ui_->loading_label->hide();

  // An editable field is one that has a label as a buddy.  The label is
  // important because it gets turned bold when the field is changed.
  for (QLabel* label : findChildren<QLabel*>()) {
    QWidget* widget = label->buddy();
    if (widget) {
      // Store information about the field
      fields_ << FieldData(label, widget, widget->objectName());

      // Connect the Reset signal
      if (dynamic_cast<ExtendedEditor*>(widget)) {
        connect(widget, SIGNAL(Reset()), SLOT(ResetField()));
      }

      // Connect the edited signal
      if (qobject_cast<QLineEdit*>(widget)) {
        connect(widget, SIGNAL(textChanged(QString)), SLOT(FieldValueEdited()));
      } else if (qobject_cast<QPlainTextEdit*>(widget)) {
        connect(widget, SIGNAL(textChanged()), SLOT(FieldValueEdited()));
      } else if (qobject_cast<QSpinBox*>(widget)) {
        connect(widget, SIGNAL(valueChanged(int)), SLOT(FieldValueEdited()));
      }
    }
  }

  // Set the colour of all the labels on the summary page
  const bool light = palette().color(QPalette::Base).value() > 128;
  const QColor color = palette().color(QPalette::Dark);
  QPalette summary_label_palette(palette());
  summary_label_palette.setColor(
      QPalette::WindowText, light ? color.darker(150) : color.lighter(125));

  for (QLabel* label : ui_->summary_tab->findChildren<QLabel*>()) {
    if (label->property("field_label").toBool()) {
      label->setPalette(summary_label_palette);
    }
  }

  // Pretend the summary text is just a label
  ui_->summary->setMaximumHeight(ui_->art->height() -
                                 ui_->summary_art_button->height() - 4);

  connect(ui_->song_list->selectionModel(),
          SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
          SLOT(SelectionChanged()));
  connect(ui_->button_box, SIGNAL(clicked(QAbstractButton*)),
          SLOT(ButtonClicked(QAbstractButton*)));
  connect(ui_->rating, SIGNAL(RatingChanged(float)), SLOT(SongRated(float)));
  connect(ui_->playcount_reset, SIGNAL(clicked()), SLOT(ResetPlayCounts()));
  connect(ui_->fetch_tag, SIGNAL(clicked()), SLOT(FetchTag()));

  // Set up the album cover menu
  cover_menu_ = new QMenu(this);

  QList<QAction*> actions = album_cover_choice_controller_->GetAllActions();

  connect(album_cover_choice_controller_->cover_from_file_action(),
          SIGNAL(triggered()), this, SLOT(LoadCoverFromFile()));
  connect(album_cover_choice_controller_->cover_to_file_action(),
          SIGNAL(triggered()), this, SLOT(SaveCoverToFile()));
  connect(album_cover_choice_controller_->cover_from_url_action(),
          SIGNAL(triggered()), this, SLOT(LoadCoverFromURL()));
  connect(album_cover_choice_controller_->search_for_cover_action(),
          SIGNAL(triggered()), this, SLOT(SearchForCover()));
  connect(album_cover_choice_controller_->unset_cover_action(),
          SIGNAL(triggered()), this, SLOT(UnsetCover()));
  connect(album_cover_choice_controller_->show_cover_action(),
          SIGNAL(triggered()), this, SLOT(ShowCover()));

  cover_menu_->addActions(actions);

  ui_->summary_art_button->setMenu(cover_menu_);

  ui_->art->installEventFilter(this);
  ui_->art->setAcceptDrops(true);

  // Add the next/previous buttons
  previous_button_ =
      new QPushButton(IconLoader::Load("go-previous"), tr("Previous"), this);
  next_button_ = new QPushButton(IconLoader::Load("go-next"), tr("Next"), this);
  ui_->button_box->addButton(previous_button_, QDialogButtonBox::ResetRole);
  ui_->button_box->addButton(next_button_, QDialogButtonBox::ResetRole);

  connect(previous_button_, SIGNAL(clicked()), SLOT(PreviousSong()));
  connect(next_button_, SIGNAL(clicked()), SLOT(NextSong()));

  // Set some shortcuts for the buttons
  new QShortcut(QKeySequence::Back, previous_button_, SLOT(click()));
  new QShortcut(QKeySequence::Forward, next_button_, SLOT(click()));
  new QShortcut(QKeySequence::MoveToPreviousPage, previous_button_,
                SLOT(click()));
  new QShortcut(QKeySequence::MoveToNextPage, next_button_, SLOT(click()));

  // Show the shortcuts as tooltips
  previous_button_->setToolTip(QString("%1 (%2 / %3)").arg(
      previous_button_->text(),
      QKeySequence(QKeySequence::Back).toString(QKeySequence::NativeText),
      QKeySequence(QKeySequence::MoveToPreviousPage)
          .toString(QKeySequence::NativeText)));
  next_button_->setToolTip(QString("%1 (%2 / %3)").arg(
      next_button_->text(),
      QKeySequence(QKeySequence::Forward).toString(QKeySequence::NativeText),
      QKeySequence(QKeySequence::MoveToNextPage)
          .toString(QKeySequence::NativeText)));

  new TagCompleter(app_->library_backend(), Playlist::Column_Artist,
                   ui_->artist);
  new TagCompleter(app_->library_backend(), Playlist::Column_Album, ui_->album);
  new TagCompleter(app_->library_backend(), Playlist::Column_AlbumArtist,
                   ui_->albumartist);
  new TagCompleter(app_->library_backend(), Playlist::Column_Genre, ui_->genre);
  new TagCompleter(app_->library_backend(), Playlist::Column_Composer,
                   ui_->composer);
  new TagCompleter(app_->library_backend(), Playlist::Column_Performer,
                   ui_->performer);
  new TagCompleter(app_->library_backend(), Playlist::Column_Grouping,
                   ui_->grouping);
}