diff --git a/README.md b/README.md new file mode 100644 index 000000000..8202b1933 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +Clementine +========== + +Clementine is a modern music player and library organizer for Windows, Linux and Mac OS X. + +- Website: http://www.clementine-player.org/ +- Github: https://github.com/clementine-player/Clementine +- Buildbot: http://buildbot.clementine-player.org/grid +- Latest developer builds: http://builds.clementine-player.org/ + +Compiling from source +--------------------- + +Get the code (if you haven't already): + + git clone https://github.com/clementine-player/Clementine.git && cd Clementine + +Compile and install: + + cd bin + cmake .. + make -j8 + sudo make install + +See the Wiki for more instructions and a list of dependencies: +https://github.com/clementine-player/Clementine/wiki/Compiling-from-Source diff --git a/src/core/mac_startup.mm b/src/core/mac_startup.mm index d9836377f..4a89677e7 100644 --- a/src/core/mac_startup.mm +++ b/src/core/mac_startup.mm @@ -451,4 +451,12 @@ void EnableFullScreen(const QWidget& main_window) { [window setCollectionBehavior: kFullScreenPrimary]; } +float GetDevicePixelRatio(QWidget* widget) { + NSView* view = reinterpret_cast(widget->winId()); + if ([[view window] respondsToSelector: @selector(backingScaleFactor)]) { + return [[view window] backingScaleFactor]; + } + return 1.0f; +} + } // namespace mac diff --git a/src/core/mac_utilities.h b/src/core/mac_utilities.h index 9ad84e1fb..fadaab0fd 100644 --- a/src/core/mac_utilities.h +++ b/src/core/mac_utilities.h @@ -29,5 +29,6 @@ namespace mac { QKeySequence KeySequenceFromNSEvent(NSEvent* event); void DumpDictionary(CFDictionaryRef dict); +float GetDevicePixelRatio(QWidget* widget); } diff --git a/src/library/libraryfilterwidget.cpp b/src/library/libraryfilterwidget.cpp index b30df6d5b..3f866149b 100644 --- a/src/library/libraryfilterwidget.cpp +++ b/src/library/libraryfilterwidget.cpp @@ -212,6 +212,10 @@ void LibraryFilterWidget::SetQueryMode(QueryOptions::QueryMode query_mode) { model_->SetFilterQueryMode(query_mode); } +void LibraryFilterWidget::ShowInLibrary(const QString& search) { + ui_->filter->setText(search); +} + void LibraryFilterWidget::SetAgeFilterEnabled(bool enabled) { filter_age_menu_->setEnabled(enabled); } diff --git a/src/library/libraryfilterwidget.h b/src/library/libraryfilterwidget.h index 572bb1185..255e35685 100644 --- a/src/library/libraryfilterwidget.h +++ b/src/library/libraryfilterwidget.h @@ -56,6 +56,7 @@ class LibraryFilterWidget : public QWidget { void SetDelayBehaviour(DelayBehaviour behaviour) { delay_behaviour_ = behaviour; } void SetAgeFilterEnabled(bool enabled); void SetGroupByEnabled(bool enabled); + void ShowInLibrary(const QString& search); QMenu* menu() const { return library_menu_; } void AddMenuAction(QAction* action); diff --git a/src/playlist/playlistdelegates.cpp b/src/playlist/playlistdelegates.cpp index 752b556eb..b49b46ee3 100644 --- a/src/playlist/playlistdelegates.cpp +++ b/src/playlist/playlistdelegates.cpp @@ -16,13 +16,6 @@ */ #include "playlistdelegates.h" -#include "queue.h" -#include "core/logging.h" -#include "core/player.h" -#include "core/utilities.h" -#include "library/librarybackend.h" -#include "widgets/trackslider.h" -#include "ui/iconloader.h" #include #include @@ -39,6 +32,18 @@ #include #include +#include "queue.h" +#include "core/logging.h" +#include "core/player.h" +#include "core/utilities.h" +#include "library/librarybackend.h" +#include "widgets/trackslider.h" +#include "ui/iconloader.h" + +#ifdef Q_OS_DARWIN +#include "core/mac_utilities.h" +#endif // Q_OS_DARWIN + const int QueuedItemDelegate::kQueueBoxBorder = 1; const int QueuedItemDelegate::kQueueBoxCornerRadius = 3; const int QueuedItemDelegate::kQueueBoxLength = 30; @@ -492,8 +497,14 @@ void SongSourceDelegate::paint( const QUrl& url = index.data().toUrl(); QPixmap pixmap = LookupPixmap(url, option_copy.decorationSize); + float device_pixel_ratio = 1.0f; +#ifdef Q_OS_DARWIN + QWidget* parent_widget = reinterpret_cast(parent()); + device_pixel_ratio = mac::GetDevicePixelRatio(parent_widget); +#endif + // Draw the pixmap in the middle of the rectangle - QRect draw_rect(QPoint(0, 0), option_copy.decorationSize); + QRect draw_rect(QPoint(0, 0), option_copy.decorationSize / device_pixel_ratio); draw_rect.moveCenter(option_copy.rect.center()); painter->drawPixmap(draw_rect, pixmap); diff --git a/src/transcoder/transcodedialog.cpp b/src/transcoder/transcodedialog.cpp index d2e11b5e1..fc1cb2edd 100644 --- a/src/transcoder/transcodedialog.cpp +++ b/src/transcoder/transcodedialog.cpp @@ -20,6 +20,7 @@ #include "transcoderoptionsdialog.h" #include "ui_transcodedialog.h" #include "ui_transcodelogdialog.h" +#include "ui/iconloader.h" #include "ui/mainwindow.h" #include "widgets/fileview.h" @@ -35,6 +36,7 @@ const char* TranscodeDialog::kSettingsGroup = "Transcoder"; const int TranscodeDialog::kProgressInterval = 500; +const int TranscodeDialog::kMaxDestinationItems = 10; static bool ComparePresetsByName(const TranscoderPreset& left, const TranscoderPreset& right) { @@ -103,6 +105,8 @@ TranscodeDialog::TranscodeDialog(QWidget *parent) connect(close_button_, SIGNAL(clicked()), SLOT(hide())); connect(ui_->details, SIGNAL(clicked()), log_dialog_, SLOT(show())); connect(ui_->options, SIGNAL(clicked()), SLOT(Options())); + connect(ui_->select, SIGNAL(clicked()), SLOT(AddDestination())); + connect(transcoder_, SIGNAL(JobComplete(QString,bool)), SLOT(JobComplete(QString,bool))); connect(transcoder_, SIGNAL(LogLine(QString)), SLOT(LogLine(QString))); @@ -138,7 +142,8 @@ void TranscodeDialog::Start() { // Add jobs to the transcoder for (int i=0 ; irowCount() ; ++i) { QString filename = file_model->index(i, 0).data(Qt::UserRole).toString(); - transcoder_->AddJob(filename, preset); + QString outfilename = GetOutputFileName(filename, preset); + transcoder_->AddJob(filename, preset, outfilename); } // Set up the progressbar @@ -265,3 +270,50 @@ void TranscodeDialog::Options() { dialog.exec(); } } + +// Adds a folder to the destination box. +void TranscodeDialog::AddDestination() { + int index = ui_->destination->currentIndex(); + QString initial_dir = (!ui_->destination->itemData(index).isNull() ? + ui_->destination->itemData(index).toString() : + QDir::homePath()); + QString dir = QFileDialog::getExistingDirectory( + this, tr("Add folder"), initial_dir); + + if (!dir.isEmpty()) { + // Keep only a finite number of items in the box. + while (ui_->destination->count() >= kMaxDestinationItems) { + ui_->destination->removeItem(1); // The oldest folder item. + } + + QIcon icon = IconLoader::Load("folder"); + QVariant data = QVariant::fromValue(dir); + // Do not insert duplicates. + int duplicate_index = ui_->destination->findData(data); + if (duplicate_index == -1) { + ui_->destination->addItem(icon, dir, data); + ui_->destination->setCurrentIndex(ui_->destination->count() - 1); + } else { + ui_->destination->setCurrentIndex(duplicate_index); + } + } +} + +// Returns the rightmost non-empty part of 'path'. +QString TranscodeDialog::TrimPath(const QString& path) const { + return path.section('/', -1, -1, QString::SectionSkipEmpty); +} + +QString TranscodeDialog::GetOutputFileName(const QString& input, + const TranscoderPreset &preset) const { + QString path = ui_->destination->itemData( + ui_->destination->currentIndex()).toString(); + if (path.isEmpty()) { + // Keep the original path. + return input.section('.', 0, -2) + '.' + preset.extension_; + } else { + QString file_name = TrimPath(input); + file_name = file_name.section('.', 0, -2); + return path + '/' + file_name + '.' + preset.extension_; + } +} diff --git a/src/transcoder/transcodedialog.h b/src/transcoder/transcodedialog.h index ebf70d204..5be060def 100644 --- a/src/transcoder/transcodedialog.h +++ b/src/transcoder/transcodedialog.h @@ -25,6 +25,8 @@ class Transcoder; class Ui_TranscodeDialog; class Ui_TranscodeLogDialog; +struct TranscoderPreset; + class TranscodeDialog : public QDialog { Q_OBJECT @@ -34,6 +36,7 @@ class TranscodeDialog : public QDialog { static const char* kSettingsGroup; static const int kProgressInterval; + static const int kMaxDestinationItems; void SetFilenames(const QStringList& filenames); @@ -49,11 +52,15 @@ class TranscodeDialog : public QDialog { void LogLine(const QString& message); void AllJobsComplete(); void Options(); + void AddDestination(); private: void SetWorking(bool working); void UpdateStatusText(); void UpdateProgress(); + QString TrimPath(const QString& path) const; + QString GetOutputFileName(const QString& input, + const TranscoderPreset& preset) const; private: Ui_TranscodeDialog* ui_; diff --git a/src/transcoder/transcodedialog.ui b/src/transcoder/transcodedialog.ui index 2a0e6e956..d6daedd05 100644 --- a/src/transcoder/transcodedialog.ui +++ b/src/transcoder/transcodedialog.ui @@ -98,7 +98,7 @@ Output options - + @@ -107,25 +107,21 @@ - - - - - - 0 - 0 - - - - - - - - Options... - - - - + + + + 0 + 0 + + + + + + + + Options... + + @@ -136,6 +132,15 @@ + + true + + + + 0 + 0 + + Alongside the originals @@ -143,6 +148,13 @@ + + + + Select... + + + @@ -196,7 +208,6 @@ add remove format - destination button_box diff --git a/src/ui/albumcoverchoicecontroller.h b/src/ui/albumcoverchoicecontroller.h index 7da216e66..b7e574fe7 100644 --- a/src/ui/albumcoverchoicecontroller.h +++ b/src/ui/albumcoverchoicecontroller.h @@ -27,10 +27,11 @@ class AlbumCoverFetcher; class AlbumCoverSearcher; class Application; class CoverFromURLDialog; -class CoverSearchStatistics; class QFileDialog; class Song; +struct CoverSearchStatistics; + // Controller for the common album cover related menu options. class AlbumCoverChoiceController : public QWidget { Q_OBJECT diff --git a/src/ui/mainwindow.cpp b/src/ui/mainwindow.cpp index fb9b74bdb..9f93a4c23 100644 --- a/src/ui/mainwindow.cpp +++ b/src/ui/mainwindow.cpp @@ -511,6 +511,7 @@ MainWindow::MainWindow(Application* app, playlist_copy_to_device_ = playlist_menu_->addAction(IconLoader::Load("multimedia-player-ipod-mini-blue"), tr("Copy to device..."), this, SLOT(PlaylistCopyToDevice())); playlist_delete_ = playlist_menu_->addAction(IconLoader::Load("edit-delete"), tr("Delete from disk..."), this, SLOT(PlaylistDelete())); playlist_open_in_browser_ = playlist_menu_->addAction(IconLoader::Load("document-open-folder"), tr("Show in file browser..."), this, SLOT(PlaylistOpenInBrowser())); + playlist_show_in_library_ = playlist_menu_->addAction(IconLoader::Load("edit-find"), tr("Show in library..."), this, SLOT(ShowInLibrary())); playlist_menu_->addSeparator(); playlistitem_actions_separator_ = playlist_menu_->addSeparator(); playlist_menu_->addAction(ui_->action_clear_playlist); @@ -692,6 +693,10 @@ MainWindow::MainWindow(Application* app, app_->playlist_manager()->Init(app_->library_backend(), app_->playlist_backend(), ui_->playlist_sequence, ui_->playlist); + // This connection must be done after the playlists have been initialized. + connect(this, SIGNAL(StopAfterToggled(bool)), + osd_, SLOT(StopAfterToggle(bool))); + // We need to connect these global shortcuts here after the playlist have been initialized connect(global_shortcuts_, SIGNAL(CycleShuffleMode()), app_->playlist_manager()->sequence(), SLOT(CycleShuffleMode())); connect(global_shortcuts_, SIGNAL(CycleRepeatMode()), app_->playlist_manager()->sequence(), SLOT(CycleRepeatMode())); @@ -1060,7 +1065,8 @@ void MainWindow::ToggleShowHide() { } void MainWindow::StopAfterCurrent() { - app_->playlist_manager()->current()->StopAfter(app_->playlist_manager()->current()->current_row()); + app_->playlist_manager()->active()->StopAfter(app_->playlist_manager()->active()->current_row()); + emit StopAfterToggled(app_->playlist_manager()->active()->stop_after_current()); } void MainWindow::closeEvent(QCloseEvent* event) { @@ -1357,6 +1363,7 @@ void MainWindow::PlaylistRightClick(const QPoint& global_pos, const QModelIndex& ui_->action_edit_value->setVisible(editable); ui_->action_remove_from_playlist->setEnabled(!selection.isEmpty()); + playlist_show_in_library_->setVisible(false); playlist_copy_to_library_->setVisible(false); playlist_move_to_library_->setVisible(false); playlist_organise_->setVisible(false); @@ -1405,6 +1412,7 @@ void MainWindow::PlaylistRightClick(const QPoint& global_pos, const QModelIndex& PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_index.row()); if (item->IsLocalLibraryItem() && item->Metadata().id() != -1) { playlist_organise_->setVisible(editable); + playlist_show_in_library_->setVisible(editable); } else { playlist_copy_to_library_->setVisible(editable); playlist_move_to_library_->setVisible(editable); @@ -1680,6 +1688,22 @@ void MainWindow::AddCDTracks() { AddToPlaylist(data); } +void MainWindow::ShowInLibrary() { + // Show the first valid selected track artist/album in LibraryView + QModelIndexList proxy_indexes = ui_->playlist->view()->selectionModel()->selectedRows(); + SongList songs; + + foreach (const QModelIndex& proxy_index, proxy_indexes) { + QModelIndex index = app_->playlist_manager()->current()->proxy()->mapToSource(proxy_index); + if (app_->playlist_manager()->current()->item_at(index.row())->IsLocalLibraryItem()) { + songs << app_->playlist_manager()->current()->item_at(index.row())->Metadata(); + break; + } + } + QString search = "artist:"+songs[0].artist()+" album:"+songs[0].album(); + library_view_->filter()->ShowInLibrary(search); +} + void MainWindow::PlaylistRemoveCurrent() { ui_->playlist->view()->RemoveSelected(); } diff --git a/src/ui/mainwindow.h b/src/ui/mainwindow.h index a185cb390..0ea81fb60 100644 --- a/src/ui/mainwindow.h +++ b/src/ui/mainwindow.h @@ -136,6 +136,10 @@ class MainWindow : public QMainWindow, public PlatformInterface { void Activate(); bool LoadUrl(const QString& url); + signals: + // Signals that stop playing after track was toggled. + void StopAfterToggled(bool stop); + private slots: void FilePathChanged(const QString& path); @@ -169,6 +173,7 @@ class MainWindow : public QMainWindow, public PlatformInterface { void PlaylistOrganiseSelected(bool copy); void PlaylistDelete(); void PlaylistOpenInBrowser(); + void ShowInLibrary(); void ChangeLibraryQueryMode(QAction* action); @@ -329,6 +334,7 @@ class MainWindow : public QMainWindow, public PlatformInterface { QAction* playlist_stop_after_; QAction* playlist_undoredo_; QAction* playlist_organise_; + QAction* playlist_show_in_library_; QAction* playlist_copy_to_library_; QAction* playlist_move_to_library_; QAction* playlist_copy_to_device_; diff --git a/src/widgets/fileview.cpp b/src/widgets/fileview.cpp index 7fd83d9ac..6a62a2e56 100644 --- a/src/widgets/fileview.cpp +++ b/src/widgets/fileview.cpp @@ -32,7 +32,7 @@ const char* FileView::kFileFilter = "*.mp3 *.ogg *.flac *.mpc *.m4a *.aac *.wma " "*.mp4 *.spx *.wav *.m3u *.m3u8 *.pls *.xspf " "*.asx *.asxini *.cue *.ape *.wv *.mka *.opus " - "*.oga *.mka"; + "*.oga *.mka *.mp2"; FileView::FileView(QWidget* parent) : QWidget(parent), diff --git a/src/widgets/lineedit.cpp b/src/widgets/lineedit.cpp index 91dc7a494..718627e4a 100644 --- a/src/widgets/lineedit.cpp +++ b/src/widgets/lineedit.cpp @@ -32,7 +32,8 @@ ExtendedEditor::ExtendedEditor(QWidget* widget, int extra_right_padding, reset_button_(new QToolButton(widget)), extra_right_padding_(extra_right_padding), draw_hint_(draw_hint), - font_point_size_(widget->font().pointSizeF() - 1) + font_point_size_(widget->font().pointSizeF() - 1), + is_rtl_(false) { clear_button_->setIcon(IconLoader::Load("edit-clear-locationbar-ltr")); clear_button_->setIconSize(QSize(16, 16)); @@ -118,15 +119,22 @@ void ExtendedEditor::Paint(QPaintDevice* device) { } } else { clear_button_->setVisible(has_clear_button_); + Resize(); } } void ExtendedEditor::Resize() { const QSize sz = clear_button_->sizeHint(); const int frame_width = widget_->style()->pixelMetric(QStyle::PM_DefaultFrameWidth); - clear_button_->move(frame_width, (widget_->rect().height() - sz.height()) / 2); - reset_button_->move(widget_->width() - frame_width - sz.width() - extra_right_padding_, - (widget_->rect().height() - sz.height()) / 2); + const int y = (widget_->rect().height() - sz.height()) / 2; + + clear_button_->move(frame_width, y); + + if (!is_rtl_) { + reset_button_->move(widget_->width() - frame_width - sz.width() - extra_right_padding_, y); + } else { + reset_button_->move((has_clear_button() ? sz.width() + 4 : 0) + frame_width, y); + } } @@ -137,6 +145,16 @@ LineEdit::LineEdit(QWidget* parent) connect(reset_button_, SIGNAL(clicked()), SIGNAL(Reset())); } +void LineEdit::set_text(const QString& text) { + QLineEdit::setText(text); + + // For some reason Qt will detect any text with LTR at the end as LTR, so instead + // compare only the first character + if (!text.isEmpty()) { + set_rtl(QString(text.at(0)).isRightToLeft()); + } +} + void LineEdit::paintEvent(QPaintEvent* e) { QLineEdit::paintEvent(e); Paint(this); diff --git a/src/widgets/lineedit.h b/src/widgets/lineedit.h index 1ac623b24..615b8b74a 100644 --- a/src/widgets/lineedit.h +++ b/src/widgets/lineedit.h @@ -86,6 +86,7 @@ protected: int extra_right_padding_; bool draw_hint_; qreal font_point_size_; + bool is_rtl_; }; class LineEdit : public QLineEdit, @@ -102,13 +103,17 @@ public: // ExtendedEditor void set_focus() { QLineEdit::setFocus(); } QString text() const { return QLineEdit::text(); } - void set_text(const QString& text) { QLineEdit::setText(text); } + void set_text(const QString& text); void set_enabled(bool enabled) { QLineEdit::setEnabled(enabled); } protected: void paintEvent(QPaintEvent*); void resizeEvent(QResizeEvent*); +private: + bool is_rtl() const { return is_rtl_; } + void set_rtl(bool rtl) { is_rtl_ = rtl; } + signals: void Reset(); }; diff --git a/src/widgets/osd.cpp b/src/widgets/osd.cpp index 9e40ff1d9..c2dfe8b81 100644 --- a/src/widgets/osd.cpp +++ b/src/widgets/osd.cpp @@ -166,6 +166,11 @@ void OSD::Stopped() { ShowMessage(QCoreApplication::applicationName(), tr("Stopped")); } +void OSD::StopAfterToggle(bool stop) { + ShowMessage(QCoreApplication::applicationName(), + tr("Stop playing after track: %1").arg(stop ? tr("On") : tr("Off"))); +} + void OSD::PlaylistFinished() { // We get a PlaylistFinished followed by a Stopped from the player ignore_next_stopped_ = true; diff --git a/src/widgets/osd.h b/src/widgets/osd.h index dddd7648e..d7783e88a 100644 --- a/src/widgets/osd.h +++ b/src/widgets/osd.h @@ -71,6 +71,7 @@ class OSD : public QObject { void Paused(); void Stopped(); + void StopAfterToggle(bool stop); void PlaylistFinished(); void VolumeChanged(int value); void MagnatuneDownloadFinished(const QStringList& albums); diff --git a/src/widgets/trackslider.cpp b/src/widgets/trackslider.cpp index 3fcc26d3d..4fcfce247 100644 --- a/src/widgets/trackslider.cpp +++ b/src/widgets/trackslider.cpp @@ -38,7 +38,7 @@ TrackSlider::TrackSlider(QWidget* parent) { ui_->setupUi(this); - QFont font("Courier"); + QFont font("Comic Sans MS"); ui_->elapsed->setFont(font); ui_->remaining->setFont(font);