newContextInformation) {
13 | lastRequest = newContextInformation;
14 | QString html("" + newContextInformation.value("artist") + " - " + newContextInformation.value("title") + "
");
15 | setHtml(html);
16 | lyricsProvider->getLyrics(newContextInformation.value("artist"), newContextInformation.value("title"));
17 | }
18 |
19 | void LyricsWidget::showLyrics(QWebElement webElement) {
20 | // FIXME: if you change song before the lyrics are loaded, the lyrics shown are for the previous song
21 | QString html("" + lastRequest.value("artist") + " - " + lastRequest.value("title") + "
");
22 | setHtml(html + "" + webElement.toOuterXml() + "
");
23 |
24 | // Code to try to remove some tags
25 | QString code = "$('object').remove()";
26 | contentView->page()->mainFrame()->evaluateJavaScript(code);
27 | }
28 |
29 | void LyricsWidget::resetLabels() {
30 | setHtml("Listen a song and push the Fetch button above to get the song lyrics!
");
31 | }
32 |
--------------------------------------------------------------------------------
/src/mainwindow/playlist/playlistitem.cpp:
--------------------------------------------------------------------------------
1 | #include "playlistitem.h"
2 |
3 | // TODO: if path is invalid, show error
4 |
5 |
6 | PlaylistItem::PlaylistItem(QString itemFilePath)
7 | {
8 | fileUrl = QUrl().fromLocalFile(itemFilePath);
9 | loadFile();
10 | }
11 |
12 | PlaylistItem::PlaylistItem(QUrl url) {
13 | fileUrl = url;
14 | loadFile();
15 | }
16 |
17 | void PlaylistItem::loadFile() {
18 | music = new Music(fileUrl);
19 |
20 | if (music->getTrackNumber() > 0) { setData(0, Qt::DisplayRole, music->getTrackNumber()); }
21 | else { setData(0, Qt::DisplayRole, QString("")); }
22 |
23 | QString qStr;
24 | int duration = music->getDuration();
25 | int secs = duration % 60;
26 | int mins = duration / 60;
27 |
28 | setText(1, music->getTitle());
29 | setText(2, music->getAlbum());
30 | setText(3, music->getArtist());
31 | setText(4, QString::number(mins) + ":" + qStr.sprintf("%02d", secs));
32 | }
33 |
34 |
35 | Music * PlaylistItem::getMusic() {
36 | return music;
37 | }
38 |
39 | bool PlaylistItem::isValid() {
40 | if (music != NULL) {
41 | return music->isValid();
42 | }
43 | else {
44 | return false;
45 | }
46 | }
47 |
48 | void PlaylistItem::setBold() {
49 | for (int i = 0; i < columnCount(); i++) {
50 | QFont itemFont = font(i);
51 | itemFont.setBold(true);
52 | setFont(i, itemFont);
53 | }
54 | }
55 |
56 | void PlaylistItem::removeBold() {
57 | for (int i = 0; i < columnCount(); i++) {
58 | QFont itemFont = font(i);
59 | itemFont.setBold(false);
60 | setFont(i, itemFont);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/mainwindow/mainnotebook/collection/collectiontreewidget.h:
--------------------------------------------------------------------------------
1 | #ifndef COLLECTIONTREEWIDGET_H
2 | #define COLLECTIONTREEWIDGET_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include "../../../music.h"
10 | #include "../../../iconfactory.h"
11 | #include "../../../collection/collectionservice.h"
12 | #include "collectiontreewidgetitem.h"
13 |
14 | class CollectionTreeWidget : public QTreeWidget
15 | {
16 | Q_OBJECT
17 |
18 | public:
19 | CollectionTreeWidget();
20 | QTreeWidgetItem *addArtist(QString artist, unsigned int id = 0);
21 | QTreeWidgetItem *addAlbum(QString artist, QString album, unsigned int albumId = 0);
22 | QList musicList;
23 | bool removeArtist(QString artist);
24 | bool removeAlbum(QString artist, QString album);
25 | enum TreeLevel { LevelNone = 0, LevelArtist = 1, LevelAlbum = 2, LevelMusic = 3 };
26 |
27 | private:
28 | QStringList toColumns(QString string);
29 | void cleanUp(QTreeWidgetItem *parent, int level);
30 | CollectionService *service;
31 |
32 | // Override drag and drop methods
33 | void mouseMoveEvent(QMouseEvent *event);
34 |
35 |
36 | private slots:
37 | CollectionTreeWidgetItem *addMusic(Music *music, unsigned int id = 0);
38 | bool removeMusic(unsigned int id);
39 | void doubleClickAt(QModelIndex);
40 | void showChildrenOf(QModelIndex index);
41 |
42 | signals:
43 | void askToAddItemToPlaylist(QList);
44 | void scanning();
45 | void listUpdated();
46 |
47 |
48 | };
49 |
50 | #endif // COLLECTIONTREEWIDGET_H
51 |
--------------------------------------------------------------------------------
/src/mainwindow/playerbar.h:
--------------------------------------------------------------------------------
1 | #ifndef PLAYERBAR_H
2 | #define PLAYERBAR_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include "../separator.h"
15 | #include "../iconfactory.h"
16 | #include "../settings/settingsdialog/settingsdialog.h"
17 |
18 |
19 | using namespace Phonon;
20 |
21 | class PlayerBar : public QWidget
22 | {
23 | Q_OBJECT
24 |
25 | public:
26 | PlayerBar(QWidget *parent, Phonon::MediaObject *mediaObject, Phonon::AudioOutput *audioOutput);
27 |
28 | public slots:
29 | void updateSongPosition();
30 | void updateSongInformation(QMap newSongInformation);
31 | void handleState(Phonon::State newState);
32 | void handlePlayButton();
33 | void handleWindowStateChange(Qt::WindowStates windowState);
34 | void handleStopButton();
35 | void exitApplication();
36 | void openSettings();
37 | void finish();
38 |
39 | signals:
40 | void nextButtonClicked();
41 | void previousButtonClicked();
42 | void toggleFullScreen();
43 |
44 | private:
45 | Phonon::MediaObject *mainMediaObject;
46 | Phonon::AudioOutput *audioOutput;
47 | QLabel *currentSongPosition;
48 | QLabel *remainingSongPosition;
49 | QLabel *currentSongInfo;
50 | SeekSlider *songPositionSlider;
51 |
52 | QToolButton *playButton;
53 | QToolButton *stopButton;
54 | QToolButton *exitButton;
55 | QToolButton *nextButton;
56 | QToolButton *previousButton;
57 | QToolButton *prefButton;
58 | QToolButton *fullScreenButton;
59 |
60 | void resetDisplay();
61 |
62 | };
63 |
64 | #endif // PLAYERBAR_H
65 |
--------------------------------------------------------------------------------
/src/services/lastfmscrobbler.h:
--------------------------------------------------------------------------------
1 | #ifndef LASTFMSCROBBLER_H
2 | #define LASTFMSCROBBLER_H
3 |
4 | #include
5 | #include // To generate the token.
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include "../settings/lastfmsettings.h"
15 |
16 | class LastFmScrobbler : QObject
17 | {
18 | Q_OBJECT
19 |
20 | public:
21 | LastFmScrobbler(Phonon::MediaObject *mediaObject);
22 |
23 | signals:
24 |
25 | public slots:
26 | void onTick(qint64 time);
27 | void handleStateChange(Phonon::State, Phonon::State);
28 | void resetSongStatus();
29 |
30 | // Network related stuff
31 | void readAuthenticationReply();
32 | void readNowPlayingReply();
33 | void readSubmissionReply();
34 |
35 | private:
36 | void handshake();
37 | void tryToScrobble();
38 | QString generateToken(QString input, QString timestamp);
39 | Phonon::MediaObject *mediaObject;
40 | int ellapsedTime;
41 | bool canScrobble;
42 | struct SongInfo {
43 | QString artist;
44 | QString album;
45 | QString title;
46 | QString startTimeStamp;
47 | QString duration;
48 | };
49 | SongInfo currentSong;
50 | QList *songsToScrobble;
51 |
52 | // Network related stuff
53 | enum LastFmState {
54 | LastFmStateNone = 0,
55 | LastFmStateWaitingToken = 1,
56 | LastFmGotToken = 2
57 | };
58 | LastFmState state;
59 | int timeToScrobble;
60 | qint64 lastTickTime;
61 | QString sessionId;
62 | QString nowPlayingUrl;
63 | QString submissionUrl;
64 | QNetworkAccessManager *netManager;
65 | QNetworkReply *authReply;
66 | QNetworkReply *nowPlayingReply;
67 | QNetworkReply *submissionReply;
68 |
69 |
70 |
71 | };
72 |
73 | #endif // LASTFMSCROBBLER_H
74 |
--------------------------------------------------------------------------------
/src/mainwindow/mainnotebook/context/abstractcontainer.cpp:
--------------------------------------------------------------------------------
1 | #include "abstractcontainer.h"
2 |
3 | AbstractContainer::AbstractContainer(QWidget *parent) :
4 | QFrame(parent)
5 | {
6 | setFrameShape(QFrame::Box);
7 | setFrameShadow(QFrame::Sunken);
8 |
9 | contentView = new QWebView(this);
10 | connect(contentView, SIGNAL(linkClicked(QUrl)), this, SLOT(openLinksInExternalWindow(QUrl)));
11 | contentView->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks);
12 | contentView->setContextMenuPolicy(Qt::NoContextMenu);
13 |
14 | // Stole font from a QLabel
15 | QLabel *label = new QLabel("I will die soon!");
16 | QFont font = label->font();
17 | delete label;
18 |
19 | // TODO: set margins, color, etc.
20 | QStringList headerList;
21 | headerList += "";
34 |
35 | header = headerList.join("");
36 |
37 | footer = ""; // no footer yet.
38 |
39 |
40 | // TODO: open links in another window
41 | QVBoxLayout *layout = new QVBoxLayout(this);
42 | layout->addWidget(contentView);
43 | layout->setContentsMargins(0, 0, 0, 0);
44 |
45 | setLayout(layout);
46 | }
47 |
48 | void AbstractContainer::setHtml(QString html) {
49 | html = header + html + footer;
50 | contentView->setHtml(html);
51 | }
52 |
53 | void AbstractContainer::openLinksInExternalWindow(QUrl url) {
54 | QDesktopServices::openUrl(url);
55 | }
56 |
--------------------------------------------------------------------------------
/src/settings/settingsdialog/widgets/lastfmsettingswidget.cpp:
--------------------------------------------------------------------------------
1 | #include "lastfmsettingswidget.h"
2 |
3 | LastFmSettingsWidget::LastFmSettingsWidget(QWidget *parent) : QWidget(parent)
4 | {
5 | usernameLabel = new QLabel("Username:", this);
6 | passwordLabel = new QLabel("Password:", this);
7 | usernameTextBox = new QLineEdit(this);
8 | passwordTextBox = new QLineEdit(this);
9 | passwordTextBox->setEchoMode(QLineEdit::Password);
10 | active = new QCheckBox("Active", this);
11 |
12 | QGridLayout *layout = new QGridLayout(this);
13 | layout->addWidget(active, 0, 0, 1, 2);
14 | layout->addWidget(usernameLabel, 1, 0, Qt::AlignRight);
15 | layout->addWidget(usernameTextBox, 1, 1);
16 | layout->addWidget(passwordLabel, 2, 0, Qt::AlignRight);
17 | layout->addWidget(passwordTextBox, 2, 1);
18 | layout->setSizeConstraint(QLayout::SetFixedSize);
19 |
20 | // TODO: make this fill better the window width.
21 |
22 | usernameTextBox->setText(LastFmSettings::username());
23 | passwordTextBox->setText(LastFmSettings::password());
24 | active->setChecked(LastFmSettings::isActive());
25 |
26 | // If Last.fm is not active, disable the textboxes
27 | if (!active->isChecked()) {
28 | usernameTextBox->setDisabled(true);
29 | passwordTextBox->setDisabled(true);
30 | usernameLabel->setDisabled(true);
31 | passwordLabel->setDisabled(true);
32 | }
33 | connect(active, SIGNAL(toggled(bool)), this, SLOT(handleLastFmState(bool)));
34 |
35 |
36 | }
37 |
38 | void LastFmSettingsWidget::applySettings() {
39 | LastFmSettings::setPassword(passwordTextBox->text());
40 | LastFmSettings::setUsername(usernameTextBox->text());
41 | LastFmSettings::setActive(active->isChecked());
42 | }
43 |
44 | void LastFmSettingsWidget::handleLastFmState(bool enabled) {
45 | usernameLabel->setEnabled(enabled);
46 | usernameTextBox->setEnabled(enabled);
47 | passwordLabel->setEnabled(enabled);
48 | passwordTextBox->setEnabled(enabled);
49 | }
50 |
--------------------------------------------------------------------------------
/src/music.cpp:
--------------------------------------------------------------------------------
1 | #include "music.h"
2 | #include
3 |
4 | /*
5 | * TODO:
6 | * - Watch file for changes. If it's changed, update playlist.
7 | */
8 | Music::Music() { }
9 |
10 | Music::Music(QString artist, QString album, QString title, QString path, unsigned int trackNumber) {
11 | this->artist = artist;
12 | this->album = album;
13 | this->title = title;
14 | this->fileUrl = QUrl(path);
15 | this->trackNumber = trackNumber;
16 | }
17 |
18 | Music::Music(QUrl fileUrl)
19 | {
20 | this->fileUrl = fileUrl;
21 | readMetaData();
22 | }
23 |
24 | void Music::readMetaData() {
25 | TagLib::FileRef taglibFileRef = TagLib::FileRef(fileUrl.toLocalFile().toUtf8());
26 |
27 | // Verify if some file is valid
28 | if (!taglibFileRef.isNull()) {
29 | trackNumber = taglibFileRef.tag()->track();
30 |
31 | // Read metadata
32 | artist = QString(taglibFileRef.tag()->artist().toCString()).toAscii().trimmed();
33 | album = QString(taglibFileRef.tag()->album().toCString()).toAscii().trimmed();
34 | title = QString(taglibFileRef.tag()->title().toCString()).toAscii().trimmed();
35 | duration = taglibFileRef.audioProperties()->length();
36 |
37 | if (artist.isEmpty()) artist = "Undefined";
38 | if (album.isEmpty()) album = "Undefined";
39 | if (title.isEmpty()) title = QFileInfo(fileUrl.toLocalFile()).fileName();
40 |
41 | valid = true;
42 | }
43 | else {
44 | valid = false;
45 | // TODO: throw some exception
46 | }
47 | }
48 |
49 | bool Music::isValid() {
50 | return valid;
51 | }
52 |
53 | QString Music::getAlbum() {
54 | return album;
55 | }
56 |
57 | QString Music::getArtist() {
58 | return artist;
59 | }
60 |
61 | unsigned int Music::getDuration() {
62 | return duration;
63 | }
64 |
65 | QString Music::getTitle() {
66 | return title;
67 | }
68 |
69 | unsigned int Music::getTrackNumber() {
70 | return trackNumber;
71 | }
72 |
73 | QUrl Music::getFileUrl() {
74 | return fileUrl;
75 | }
76 |
--------------------------------------------------------------------------------
/src/mainwindow/mainnotebook/context/artistinfowidget.cpp:
--------------------------------------------------------------------------------
1 | #include "artistinfowidget.h"
2 |
3 | // TODO: put a "fetch info" and "reload" buttons.
4 | // TODO: show a message when no information is available.
5 | ArtistInfoWidget::ArtistInfoWidget(QWidget *parent) :
6 | AbstractContainer(parent)
7 | {
8 | context = new LastFmContext(this);
9 | connect(context, SIGNAL(contextUpdated(QMap)), this, SLOT(updateContextInformation(QMap)));
10 | connect(context, SIGNAL(contextError()), this, SLOT(showContextError()));
11 |
12 | resetLabels();
13 | }
14 |
15 | void ArtistInfoWidget::songInformationUpdated(QMap newContextInformation) {
16 | lastRequest = newContextInformation;
17 | QString html("" + newContextInformation.value("artist") + "
");
18 | setHtml(html);
19 | context->getInfo(newContextInformation.value("artist"));
20 | }
21 |
22 | void ArtistInfoWidget::updateContextInformation(QMapnewContextInformation) {
23 | QString artist = newContextInformation.value("artist");
24 |
25 | if (artist.toLower().trimmed() != Qt::escape(lastRequest.value("artist").toLower().trimmed())) return;
26 |
27 | QString html = "" + artist + "
";
28 | if (!newContextInformation.value("picture").isEmpty()) {
29 | html += "
";
30 | }
31 | html += "" + newContextInformation.value("summary") + "
";
32 | html += "";
33 | setHtml(html);
34 | }
35 |
36 | void ArtistInfoWidget::showContextError() {
37 | setHtml("Error while trying to download information for " + lastRequest.value("artist") + "!
");
38 | }
39 |
40 | void ArtistInfoWidget::resetLabels() {
41 | setHtml("Listen a song and push the Fetch button above to get informations about artists!
");
42 | }
43 |
--------------------------------------------------------------------------------
/src/settings/lastfmsettings.cpp:
--------------------------------------------------------------------------------
1 | #include "lastfmsettings.h"
2 |
3 | void LastFmSettings::setUsername(QString username) {
4 | QSettings settings(QSettings::UserScope, QApplication::organizationName(), QApplication::applicationName());
5 | settings.beginGroup("lastfm");
6 | settings.setValue("username", username);
7 | settings.endGroup();
8 | }
9 |
10 |
11 | void LastFmSettings::setPassword(QString password) {
12 | QByteArray text = password.toUtf8();
13 | password = QString(text.toBase64());
14 | QSettings settings(QSettings::UserScope, QApplication::organizationName(), QApplication::applicationName());
15 | settings.beginGroup("lastfm");
16 | settings.setValue("password", password);
17 | settings.endGroup();
18 | }
19 |
20 | void LastFmSettings::setActive(bool active) {
21 | QSettings settings(QSettings::UserScope, QApplication::organizationName(), QApplication::applicationName());
22 | settings.beginGroup("lastfm");
23 | settings.setValue("active", active);
24 | settings.endGroup();
25 | }
26 |
27 | QString LastFmSettings::username() {
28 | QSettings settings(QSettings::UserScope, QApplication::organizationName(), QApplication::applicationName());
29 | settings.beginGroup("lastfm");
30 | QString ret;
31 | if (settings.contains("username")) {
32 | ret = settings.value("username").toString();
33 | }
34 | settings.endGroup();
35 | return ret;
36 | }
37 |
38 |
39 | QString LastFmSettings::password() {
40 | QSettings settings(QSettings::UserScope, QApplication::organizationName(), QApplication::applicationName());
41 | settings.beginGroup("lastfm");
42 | QString ret;
43 | if (settings.contains("password")) {
44 | QByteArray text = settings.value("password").toByteArray();
45 | text = QByteArray::fromBase64(text);
46 | ret = QString(text);
47 | }
48 | settings.endGroup();
49 | return ret;
50 | }
51 |
52 |
53 | bool LastFmSettings::isActive() {
54 | QSettings settings(QSettings::UserScope, QApplication::organizationName(), QApplication::applicationName());
55 | settings.beginGroup("lastfm");
56 | bool ret = settings.value("active", false).toBool();
57 | settings.endGroup();
58 | return ret;
59 | }
60 |
--------------------------------------------------------------------------------
/src/mainwindow/playlist/playlistwidget.h:
--------------------------------------------------------------------------------
1 | #ifndef PLAYLISTWIDGET_H
2 | #define PLAYLISTWIDGET_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include "playlistitem.h"
13 | #include "../../music.h"
14 |
15 | /*
16 | * TODO: make a PlaylistEngine, which will make the work of handle the current songs.
17 | * PlaylistWidget will only make the work of handle the visualization.
18 | *
19 | */
20 | class PlaylistWidget : public QTreeWidget
21 | {
22 | Q_OBJECT
23 |
24 | private slots:
25 | void playSong(QTreeWidgetItem *doubleClickedItem);
26 | void handleStateChange(Phonon::State newState);
27 | void enqueueNextSong();
28 | void removeBold();
29 | void fileChanged();
30 | void playNextSong();
31 | void playPreviousSong();
32 | void dndActionChanged(Qt::DropAction newAction);
33 | void insertValidItem(PlaylistItem *newItem);
34 | void deleteInvalidItem(PlaylistItem *invalidItem);
35 | void addSong(PlaylistItem *newItem, int index = -1);
36 | void addSong(QUrl url, int index = -1);
37 | void addSong(QList urlList);
38 |
39 | // Keyboard / menu events
40 | void selectAll();
41 | void removeSelectedItems();
42 |
43 | signals:
44 | void songInformationUpdated(QMap newSongInformation);
45 |
46 |
47 | public:
48 | PlaylistWidget(QWidget *parent, Phonon::MediaObject *mediaObject);
49 |
50 |
51 | private:
52 | PlaylistItem *currentSong;
53 | PlaylistItem *nextSong;
54 | Phonon::MediaObject *mainMediaObject;
55 | QDrag *drag;
56 | Qt::DropAction dndAction;
57 | void emitSongInformationUpdated();
58 | void addFolder(QUrl url, int &index);
59 |
60 | // Menu events
61 | void contextMenuEvent(QContextMenuEvent *event);
62 |
63 | // Drag and drop events
64 | void dragEnterEvent(QDragEnterEvent *event);
65 | void dropEvent(QDropEvent *event);
66 | Qt::DropActions supportedDropActions() const;
67 | void dragMoveEvent(QDragMoveEvent *event);
68 | void mouseMoveEvent(QMouseEvent *event);
69 |
70 | // Keyboard events
71 | void keyPressEvent(QKeyEvent *event);
72 |
73 |
74 | };
75 |
76 | #endif // PLAYLISTWIDGET_H
77 |
--------------------------------------------------------------------------------
/src/settings/settingsdialog/widgets/foldersettingswidget.cpp:
--------------------------------------------------------------------------------
1 | #include "foldersettingswidget.h"
2 |
3 | FolderSettingsWidget::FolderSettingsWidget(QWidget *parent) : QWidget(parent)
4 | {
5 | // Create buttons and a widget to enclose them
6 | QVBoxLayout *vbox = new QVBoxLayout();
7 | QWidget *buttonsWidget = new QWidget();
8 | QPushButton *addButton = new QPushButton("Add folder");
9 | QPushButton *removeButton = new QPushButton("Remove folder");
10 | vbox->addWidget(addButton);
11 | vbox->addWidget(removeButton);
12 | buttonsWidget->setLayout(vbox);
13 |
14 | // Horizontal box to put the folders list and buttons
15 | QHBoxLayout *hbox = new QHBoxLayout();
16 | folderList = new QListWidget();
17 | folderList->setSelectionMode(QListWidget::ExtendedSelection);
18 | hbox->addWidget(folderList);
19 | hbox->addWidget(buttonsWidget);
20 | setLayout(hbox);
21 |
22 | // Update data
23 | originalFoldersList = ApplicationSettings::collectionFolderList();
24 | foreach (QString folder, originalFoldersList) {
25 | folderList->addItem(folder);
26 | }
27 |
28 | connect(addButton, SIGNAL(clicked()), this, SLOT(addFolder()));
29 | connect(removeButton, SIGNAL(clicked()), this, SLOT(removeFolder()));
30 | }
31 |
32 | void FolderSettingsWidget::addFolder() {
33 | QString dir = QFileDialog::getExistingDirectory(this, tr("Add folder"), QDir::homePath(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
34 | QDir qdir;
35 | if (!dir.isEmpty() && qdir.exists(dir)) {
36 | folderList->addItem(dir);
37 | folderList->sortItems();
38 | }
39 | }
40 |
41 | void FolderSettingsWidget::removeFolder() {
42 | if (folderList->selectedItems().count() == 0) return;
43 | foreach(QListWidgetItem *item, folderList->selectedItems()) {
44 | delete item;
45 | }
46 | }
47 |
48 | void FolderSettingsWidget::applySettings() {
49 | /*
50 | *
51 | * Settings for folders
52 | *
53 | */
54 | QStringList folders;
55 | for(int i = 0; i < folderList->count(); i++) {
56 | QListWidgetItem *item = folderList->item(i);
57 | folders.append(item->text());
58 | }
59 | ApplicationSettings::setCollectionFolders(folders);
60 |
61 | // If folders have changed, rebuild the collection database
62 | if (folders != originalFoldersList) {
63 | // TODO: rebuild collection
64 | originalFoldersList = folders;
65 | }
66 |
67 |
68 | /*
69 | *
70 | * Settings for folders - end
71 | *
72 | */
73 | }
74 |
--------------------------------------------------------------------------------
/src/services/lyricsdownloader.cpp:
--------------------------------------------------------------------------------
1 | #include "lyricsdownloader.h"
2 |
3 | LyricsDownloader::LyricsDownloader(QObject *parent) :
4 | QObject(parent)
5 | {
6 | netManager = new QNetworkAccessManager(this);
7 | contextReply = NULL;
8 | }
9 |
10 | void LyricsDownloader::getLyrics(QString artist, QString song) {
11 | QUrl url("http://lyrics.wikia.com/api.php");
12 | url.addQueryItem("func", "getSong");
13 | url.addQueryItem("artist", artist.toUtf8());
14 | url.addQueryItem("song", song.toUtf8());
15 | url.addQueryItem("fmt", "xml");
16 |
17 | QNetworkRequest netRequest;
18 | netRequest.setUrl(url);
19 |
20 | qDebug("Requesting lyrics...");
21 | contextReply = netManager->get(netRequest);
22 | connect(contextReply, SIGNAL(finished()), this, SLOT(readContextReply()));
23 | connect(contextReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SIGNAL(lyricsError())); // TODO: specify which error was given
24 | }
25 |
26 | void LyricsDownloader::readContextReply() {
27 | QString replyString = QString::fromUtf8(contextReply->readAll());
28 | qDebug("Answer received: " + replyString.toUtf8());
29 |
30 | if (replyString.trimmed().isEmpty()) {
31 | emit lyricsError();
32 | qDebug("Empty answer!");
33 | return; // Avoid empty parsing of XML
34 | }
35 |
36 | QString status;
37 | QXmlQuery query;
38 | query.setFocus(replyString);
39 | query.setQuery("LyricsResult/lyrics/text()");
40 | query.evaluateTo(&status);
41 | status = status.trimmed();
42 |
43 | /*
44 | * If we got the data successfully, let's read it.
45 | * I hate XML.
46 | */
47 | if (status.toLower() != "not found") {
48 | QString url;
49 | query.setQuery("LyricsResult/url/text()");
50 | query.evaluateTo(&url);
51 | url = url.trimmed();
52 |
53 | // Try to fetch lyrics
54 | fetchLyrics(url);
55 | }
56 | else {
57 | emit lyricsError();
58 | }
59 | }
60 |
61 | void LyricsDownloader::fetchLyrics(QUrl url) {
62 | qDebug("Asking to fetch lyrics");
63 | page = new QWebPage(this);
64 | page->mainFrame()->load(url);
65 | connect(page, SIGNAL(loadFinished(bool)), this, SLOT(render()));
66 | }
67 |
68 |
69 | // FIXME: why is this emitting the signal twice?
70 | void LyricsDownloader::render() {
71 | QWebElement document = page->mainFrame()->documentElement();
72 | // TODO: remove mobile ringtone ad
73 | QWebElement lyricsDiv = document.findFirst("div.lyricbox");
74 | delete page;
75 | emit lyricsReady(lyricsDiv);
76 | }
77 |
--------------------------------------------------------------------------------
/src/services/lastfmcontext.cpp:
--------------------------------------------------------------------------------
1 | #include "lastfmcontext.h"
2 |
3 | LastFmContext::LastFmContext(QObject *parent) :
4 | QObject(parent)
5 | {
6 | netManager = new QNetworkAccessManager(this);
7 | }
8 |
9 | void LastFmContext::getInfo(QString artist) {
10 | QUrl url("http://ws.audioscrobbler.com/2.0/");
11 | url.addQueryItem("method", "artist.getinfo");
12 | url.addQueryItem("artist", artist.toUtf8());
13 | url.addQueryItem("api_key", "ee988217b1695e36e1447dc0de443ac3");
14 |
15 | QNetworkRequest netRequest;
16 | netRequest.setUrl(url);
17 | contextReply = netManager->get(netRequest);
18 | connect(contextReply, SIGNAL(finished()), this, SLOT(readContextReply()));
19 | connect(contextReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SIGNAL(contextError())); // TODO: specify which error was given
20 | }
21 |
22 | void LastFmContext::readContextReply() {
23 | QString replyString = QString::fromUtf8(contextReply->readAll());
24 |
25 | if (replyString.isEmpty()) {
26 | return; // Avoid empty parsing of XML
27 | }
28 |
29 | // Parametres
30 | QString status;
31 | QString artistName;
32 | QString artistPicture;
33 | QString artistSummary;
34 | QString artistProfile;
35 |
36 | QXmlQuery query;
37 | query.setFocus(replyString);
38 | query.setQuery("lfm[@status = 'ok']/count(artist)");
39 | query.evaluateTo(&status);
40 | status = status.trimmed();
41 |
42 | /*
43 | * If we got the data successfully, let's read it.
44 | * I hate XML.
45 | */
46 | if (status == "1") {
47 | query.setQuery("lfm/artist/name/text()");
48 | query.evaluateTo(&artistName);
49 | artistName = artistName.trimmed();
50 |
51 | query.setQuery("lfm/artist/url/text()");
52 | query.evaluateTo(&artistProfile);
53 | artistProfile = artistProfile.trimmed();
54 |
55 | query.setQuery("lfm/artist/image[@size=\"extralarge\"]/text()");
56 | query.evaluateTo(&artistPicture);
57 | artistPicture = artistPicture.trimmed();
58 |
59 | // Summary has HTML entities that must be un-replaced.
60 | query.setQuery("lfm/artist/bio/summary/text()");
61 | query.evaluateTo(&artistSummary);
62 | artistSummary = artistSummary.trimmed().replace("<","<").replace(">",">");
63 |
64 | // Store the context data into... contextData. Nice!
65 | contextData.clear();
66 | contextData.insert("artist", artistName);
67 | contextData.insert("picture", artistPicture);
68 | contextData.insert("summary", artistSummary);
69 | contextData.insert("profile", artistProfile);
70 |
71 | // Emit the signal
72 | emit contextUpdated(contextData);
73 | }
74 | else {
75 | qDebug("LastFmContext: FAIL!");
76 | emit contextError();
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/Player.pro:
--------------------------------------------------------------------------------
1 | # -------------------------------------------------
2 | # Project created by QtCreator 2010-05-03T16:01:44
3 | # -------------------------------------------------
4 | QT += sql \
5 | phonon \
6 | xml \
7 | xmlpatterns \
8 | network \
9 | webkit
10 | TARGET = Player
11 | TEMPLATE = app
12 | SOURCES += src/mainwindow/mainnotebook/collection/collectiontreewidget.cpp \
13 | src/mainwindow/mainnotebook/filesystem/filesystemwidget.cpp \
14 | src/mainwindow/mainnotebook/mainnotebook.cpp \
15 | src/mainwindow/playlist/playlistwidget.cpp \
16 | src/mainwindow/playlist/playlistitem.cpp \
17 | src/mainwindow/playerbar.cpp \
18 | src/mainwindow/mainwindow.cpp \
19 | src/main.cpp \
20 | src/settings/applicationsettings.cpp \
21 | src/settings/settingsdialog/widgets/foldersettingswidget.cpp \
22 | src/settings/settingsdialog/settingsdialog.cpp \
23 | src/settings/settingsdialog/widgets/lastfmsettingswidget.cpp \
24 | src/settings/lastfmsettings.cpp \
25 | src/services/lastfmscrobbler.cpp \
26 | src/services/lastfmcontext.cpp \
27 | src/iconfactory.cpp \
28 | src/separator.cpp \
29 | src/mainwindow/mainnotebook/context/lyricswidget.cpp \
30 | src/mainwindow/mainnotebook/context/artistinfowidget.cpp \
31 | src/mainwindow/mainnotebook/context/contextwidget.cpp \
32 | src/services/lyricsdownloader.cpp \
33 | src/mainwindow/mainnotebook/context/abstractcontainer.cpp \
34 | src/settings/contextsettings.cpp \
35 | src/settings/settingsdialog/widgets/contextsettingswidget.cpp \
36 | src/music.cpp \
37 | src/collection/collectiondatabase.cpp \
38 | src/collection/collectionservice.cpp \
39 | src/mainwindow/mainnotebook/collection/collectiontreewidgetitem.cpp
40 | HEADERS += src/mainwindow/mainnotebook/collection/collectiontreewidget.h \
41 | src/mainwindow/mainnotebook/filesystem/filesystemwidget.h \
42 | src/mainwindow/mainnotebook/mainnotebook.h \
43 | src/mainwindow/playlist/playlistwidget.h \
44 | src/mainwindow/playlist/playlistitem.h \
45 | src/mainwindow/playerbar.h \
46 | src/mainwindow/mainwindow.h \
47 | src/settings/applicationsettings.h \
48 | src/settings/settingsdialog/widgets/foldersettingswidget.h \
49 | src/settings/settingsdialog/settingsdialog.h \
50 | src/settings/settingsdialog/widgets/lastfmsettingswidget.h \
51 | src/settings/lastfmsettings.h \
52 | src/services/lastfmscrobbler.h \
53 | src/services/lastfmcontext.h \
54 | src/iconfactory.h \
55 | src/separator.h \
56 | src/mainwindow/mainnotebook/context/lyricswidget.h \
57 | src/mainwindow/mainnotebook/context/artistinfowidget.h \
58 | src/mainwindow/mainnotebook/context/contextwidget.h \
59 | src/services/lyricsdownloader.h \
60 | src/mainwindow/mainnotebook/context/abstractcontainer.h \
61 | src/services/collectionservice.h \
62 | src/settings/contextsettings.h \
63 | src/settings/settingsdialog/widgets/contextsettingswidget.h \
64 | src/music.h \
65 | src/collection/collectiondatabase.h \
66 | src/collection/collectionservice.h \
67 | src/mainwindow/mainnotebook/collection/collectiontreewidgetitem.h
68 | RESOURCES = extra_images.qrc
69 | win32|macx:RESOURCES += icons.qrc
70 | CONFIG += link_pkgconfig
71 | PKGCONFIG += taglib
72 | OTHER_FILES += README
73 |
--------------------------------------------------------------------------------
/src/mainwindow/mainnotebook/context/contextwidget.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 |
7 | #include "contextwidget.h"
8 | #include "../../../settings/contextsettings.h"
9 | #include "../../../separator.h"
10 | #include "../../../iconfactory.h"
11 |
12 | ContextWidget::ContextWidget(QWidget *parent) : QWidget(parent)
13 | {
14 | // Create widgets
15 | artistInfoWidget = new ArtistInfoWidget(this);
16 | lyricsWidget = new LyricsWidget(this);
17 |
18 | // Create a QButtonGroup where user will select artist, lyrics, etc.
19 | buttonGroup = new QButtonGroup(this);
20 | buttonGroup->setExclusive(true);
21 |
22 | // The amazing buttons
23 | artistButton = new QPushButton("Last.fm summary", this);
24 | lyricsButton = new QPushButton("Lyrics", this);
25 |
26 | artistButton->setIconSize(QSize(24, 24));
27 | artistButton->setCheckable(true);
28 | artistButton->setChecked(true);
29 | artistButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
30 |
31 | lyricsButton->setCheckable(true);
32 | lyricsButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
33 |
34 | buttonGroup->addButton(artistButton, 0);
35 | buttonGroup->addButton(lyricsButton, 1);
36 |
37 | // Stacked widget where we'll put artist info, lyrics, etc.
38 | QStackedWidget *contextContainer = new QStackedWidget(this);
39 | contextContainer->addWidget(artistInfoWidget);
40 | contextContainer->addWidget(lyricsWidget);
41 |
42 | // Connect buttons to container
43 | connect(buttonGroup, SIGNAL(buttonClicked(int)), contextContainer, SLOT(setCurrentIndex(int)));
44 |
45 | // Fetch button
46 | QToolButton *fetchButton = new QToolButton(this);
47 | fetchButton->setIcon(IconFactory::fromTheme("go-down"));
48 | fetchButton->setIconSize(QSize(24, 24));
49 | connect(fetchButton, SIGNAL(clicked()), this, SLOT(fetchButtonPressed()));
50 |
51 | // The button group needs a layout
52 | QHBoxLayout *buttonsLayout = new QHBoxLayout();
53 | buttonsLayout->addWidget(artistButton);
54 | buttonsLayout->addWidget(lyricsButton);
55 | buttonsLayout->addWidget(Separator::verticalSeparator());
56 | buttonsLayout->addWidget(fetchButton);
57 | buttonsLayout->setSpacing(5);
58 | buttonsLayout->setMargin(0);
59 | QWidget *buttonsWidget = new QWidget(this);
60 | buttonsWidget->setLayout(buttonsLayout);
61 |
62 | // Create layout
63 | QVBoxLayout *vlayout = new QVBoxLayout();
64 | vlayout->addWidget(buttonsWidget);
65 | vlayout->addWidget(contextContainer);
66 | this->setLayout(vlayout);
67 |
68 | }
69 |
70 | void ContextWidget::songInformationUpdated(QMap newContextInformation) {
71 | currentContext = newContextInformation;
72 |
73 | artistInfoWidget->resetLabels();
74 | lyricsWidget->resetLabels();
75 |
76 | if (ContextSettings::isFetchArtistInfoActive()) artistInfoWidget->songInformationUpdated(currentContext);
77 | if (ContextSettings::isFetchLyricsActive()) lyricsWidget->songInformationUpdated(currentContext);
78 | }
79 |
80 | void ContextWidget::fetchButtonPressed() {
81 | if (artistButton->isChecked()) artistInfoWidget->songInformationUpdated(currentContext);
82 | else if (lyricsButton->isChecked()) lyricsWidget->songInformationUpdated(currentContext);
83 | }
84 |
--------------------------------------------------------------------------------
/src/collection/collectionservice.cpp:
--------------------------------------------------------------------------------
1 | #include "collectionservice.h"
2 |
3 | CollectionService::CollectionService(QObject *parent) :
4 | QThread(parent)
5 | {
6 | watcher = new QFileSystemWatcher(this);
7 | collectionDb = new CollectionDatabase(this);
8 |
9 | // Set watcher paths to paths in settings file
10 | refresh();
11 |
12 | connect(watcher, SIGNAL(fileChanged(QString)), this, SLOT(fileChanged(QString)));
13 | connect(watcher, SIGNAL(directoryChanged(QString)), this, SLOT(dirChanged(QString)));
14 |
15 | }
16 |
17 | void CollectionService::run() {
18 | verifyFiles();
19 | scan();
20 | }
21 |
22 | QSqlTableModel *CollectionService::collectionModel() {
23 | return collectionDb->collectionModel();
24 | }
25 |
26 | QSqlTableModel *CollectionService::artistModel() {
27 | return collectionDb->artistModel();
28 | }
29 |
30 | QSqlTableModel *CollectionService::albumModel() {
31 | return collectionDb->albumModel();
32 | }
33 |
34 | QSqlTableModel *CollectionService::musicModel() {
35 | return collectionDb->musicModel();
36 | }
37 |
38 | void CollectionService::fileChanged(QString path) {
39 | qDebug("FILE CHANGED " + path.toUtf8());
40 | }
41 |
42 | void CollectionService::dirChanged(QString path) {
43 | qDebug("DIR CHANGED " + path.toUtf8());
44 | }
45 |
46 | void CollectionService::refresh() {
47 | if (!watcher->directories().isEmpty()) {
48 | watcher->removePaths(watcher->directories());
49 | }
50 |
51 | // TODO: verify if every path is valid
52 | QStringList directories = ApplicationSettings::collectionFolderList();
53 | if (!directories.isEmpty()) {
54 | watcher->addPaths(ApplicationSettings::collectionFolderList());
55 | }
56 | }
57 |
58 | /*
59 | * Verify if all files in database exist.
60 | */
61 | void CollectionService::verifyFiles() {
62 | QSqlTableModel *model = musicModel();
63 | model->select();
64 | while (model->canFetchMore()) model->fetchMore();
65 | int total = model->rowCount();
66 |
67 | for (int i = 0; i < total; i++) {
68 | QString path = model->record(i).value(model->fieldIndex("path")).toString();
69 | if (!QFileInfo(path).exists()) {
70 | collectionDb->removeMusic(path);
71 | emit songRemoved(model->record(i).value(model->fieldIndex("id")).toUInt());
72 | }
73 | }
74 |
75 | delete model;
76 | }
77 |
78 | /*
79 | * Scan collection folders in order to assert that
80 | * all files in folders are in collection.
81 | */
82 | void CollectionService::scan() {
83 | emit scanning();
84 | qDebug("Beginning scan");
85 | QStringList directories = ApplicationSettings::collectionFolderList();
86 | foreach (QString path, directories) {
87 | scanRecursive(path);
88 | }
89 | emit listUpdated();
90 | qDebug("Scan ended");
91 | }
92 |
93 | void CollectionService::scanRecursive(QString path) {
94 | // You shouldn't call this to add files
95 | if (QFileInfo(path).isFile()) return;
96 |
97 | QDir directory(path);
98 | foreach (QString fileEntry, directory.entryList(directory.AllEntries | directory.NoDotAndDotDot, directory.DirsFirst | directory.Name)) {
99 | // Change file entry to a full path
100 | fileEntry = directory.absolutePath() + directory.separator() + fileEntry;
101 |
102 | if (QFileInfo(fileEntry).isDir()) {
103 | scanRecursive(fileEntry);
104 | }
105 | else if (QFileInfo(fileEntry).isFile()) {
106 | Music *music = new Music(QUrl(fileEntry));
107 | if (collectionDb->addOrUpdateMusic(music)) {
108 | emit songAdded(music);
109 | }
110 | }
111 | }
112 | }
113 |
114 |
--------------------------------------------------------------------------------
/src/settings/settingsdialog/settingsdialog.cpp:
--------------------------------------------------------------------------------
1 | #include "settingsdialog.h"
2 |
3 | SettingsDialog::SettingsDialog(QWidget *parent) : QWidget(parent)
4 | {
5 | setWindowTitle("Settings");
6 | setWindowFlags(Qt::Dialog);
7 | setWindowModality(Qt::ApplicationModal);
8 |
9 | // TODO: add icons. Icons are friends!
10 |
11 | // List to show the selected configuration
12 | QListWidget *listWidget = new QListWidget(this);
13 | listWidget->setViewMode(QListView::IconMode);
14 | listWidget->setIconSize(QSize(48, 48));
15 | QLabel *label = new QLabel(this);
16 | listWidget->setMaximumHeight(96 + 2*label->minimumHeight());
17 | listWidget->setSpacing(12);
18 | listWidget->setMovement(QListView::Static);
19 |
20 |
21 | // Create tab bar
22 | QStackedWidget *settingsStack = new QStackedWidget(this);
23 |
24 | // Settings widgets
25 | QListWidgetItem *folderSettingsListItem = new QListWidgetItem(IconFactory::fromTheme("system-file-manager"), "Collection folders");
26 | folderSettingsWidget = new FolderSettingsWidget(this);
27 | QListWidgetItem *lastFmSettingsListItem = new QListWidgetItem(QIcon(":/icons/lastfm.png"), "Last.fm");
28 | lastFmSettingsWidget = new LastFmSettingsWidget(this);
29 | QListWidgetItem *contextSettingsListItem = new QListWidgetItem(IconFactory::fromTheme("emblem-web"), "Context");
30 | contextSettingsWidget = new ContextSettingsWidget(this);
31 |
32 | /*
33 | * Add the widgets above to stacked widget and
34 | * make a reference for them in the list
35 | */
36 | listWidget->addItem(folderSettingsListItem);
37 | settingsStack->addWidget(folderSettingsWidget);
38 | listWidget->addItem(lastFmSettingsListItem);
39 | settingsStack->addWidget(lastFmSettingsWidget);
40 | listWidget->addItem(contextSettingsListItem);
41 | settingsStack->addWidget(contextSettingsWidget);
42 |
43 | // Link listwidget and settings stack
44 | connect(listWidget, SIGNAL(currentRowChanged(int)), settingsStack, SLOT(setCurrentIndex(int)));
45 |
46 | // Create button box
47 | buttonBox = new QDialogButtonBox(Qt::Horizontal, this);
48 | buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Apply);
49 |
50 | // Other vertical box to put the widgets created before.
51 | QVBoxLayout *mainVBox = new QVBoxLayout();
52 | mainVBox->addWidget(listWidget, 0);
53 | mainVBox->addWidget(Separator::horizontalSeparator(this));
54 | mainVBox->addWidget(settingsStack);
55 | mainVBox->addWidget(Separator::horizontalSeparator(this));
56 | mainVBox->addWidget(buttonBox);
57 | setLayout(mainVBox);
58 |
59 | // Connect buttons to their slots
60 | connect(buttonBox, SIGNAL(clicked(QAbstractButton*)), this, SLOT(handleAbstractButton(QAbstractButton*)));
61 |
62 | // Show window!
63 | show();
64 |
65 | // Center window
66 | int x = (parent->width() - width())/2;
67 | int y = (parent->height() - height())/2;
68 | move(x, y);
69 |
70 | // Set this to remove maximize buttons!
71 | setFixedSize(width(), height());
72 | }
73 |
74 |
75 |
76 | void SettingsDialog::applySettings() {
77 | folderSettingsWidget->applySettings();
78 | lastFmSettingsWidget->applySettings();
79 | contextSettingsWidget->applySettings();
80 | }
81 |
82 | void SettingsDialog::handleAbstractButton(QAbstractButton *button) {
83 | qDebug("handleAbstractButton()");
84 | switch (buttonBox->buttonRole(button)) {
85 | case QDialogButtonBox::ApplyRole:
86 | applySettings();
87 | break;
88 | case QDialogButtonBox::AcceptRole:
89 | applySettings();
90 | close();
91 | break;
92 | case QDialogButtonBox::RejectRole:
93 | close();
94 | break;
95 | default:
96 | // Does nothing
97 | break;
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/mainwindow/mainnotebook/filesystem/filesystemwidget.cpp:
--------------------------------------------------------------------------------
1 | #include "filesystemwidget.h"
2 |
3 | FilesystemWidget::FilesystemWidget(QWidget *parent)
4 | {
5 | setParent(parent);
6 |
7 | // Create the toolbar
8 | QToolBar *fsToolbar = new QToolBar();
9 | fsToolbar->setMovable(false);
10 |
11 | goUpAction = new QAction(IconFactory::fromTheme("go-up"), tr("Go up"), this);
12 | connect(goUpAction, SIGNAL(triggered()), this, SLOT(goUp()));
13 | fsToolbar->addAction(goUpAction);
14 |
15 | goHomeAction = new QAction(IconFactory::fromTheme("go-home"), tr("Go to the home folder"), this);
16 | connect(goHomeAction, SIGNAL(triggered()), this, SLOT(goHome()));
17 | fsToolbar->addAction(goHomeAction);
18 |
19 | // TODO: use placeholderText in Qt 4.7.
20 | filterEdit = new QLineEdit();
21 | QLabel* filterLabel = new QLabel(tr("Filter:"));
22 | filterLabel->setContentsMargins(5, 0, 5, 0);
23 | fsToolbar->addSeparator();
24 | fsToolbar->addWidget(filterLabel);
25 | fsToolbar->addWidget(filterEdit);
26 | connect(filterEdit, SIGNAL(textChanged(QString)), this, SLOT(setNameFilter(QString)));
27 |
28 | // Create the filesystem view
29 | fsWidgetModel = new QFileSystemModel();
30 | fsWidgetModel->setNameFilterDisables(false);
31 | fsWidgetModel->setFilter(QDir::AllDirs|QDir::Files|QDir::NoDotAndDotDot);
32 | fsListView = new QListView();
33 | fsListView->setSelectionMode(QAbstractItemView::ExtendedSelection);
34 | fsListView->setDragEnabled(true);
35 | fsListView->setModel(fsWidgetModel);
36 |
37 | // We shall use this to filter available file extensions from Phonon
38 | //fsWidgetModel->setFilter(getPhononExtensions());
39 |
40 | connect(fsWidgetModel, SIGNAL(rootPathChanged(QString)), this, SLOT(pathChanged()));
41 | connect(fsListView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(doubleClickAt(QModelIndex)));
42 |
43 | // Create a new horizontal box
44 | QVBoxLayout *vlayout = new QVBoxLayout();
45 | vlayout->addWidget(fsToolbar);
46 | vlayout->addWidget(fsListView);
47 | goHome();
48 |
49 | this->setLayout(vlayout);
50 | }
51 |
52 |
53 | void FilesystemWidget::goUp() {
54 | dir.cdUp();
55 | updateWidget();
56 | }
57 |
58 |
59 | void FilesystemWidget::setNameFilter() {
60 | setNameFilter(filterEdit->text());
61 | }
62 |
63 | void FilesystemWidget::setNameFilter(QString filter) {
64 | QStringList filterList;
65 | if (!filter.isEmpty()) {
66 | filter = "*" + filter + "*";
67 | filterList = QStringList(filter);
68 | }
69 | else {
70 | filterList = QStringList();
71 | }
72 | fsWidgetModel->setNameFilters(filterList);
73 | }
74 |
75 |
76 | void FilesystemWidget::goHome() {
77 | dir = QDir::homePath();
78 | updateWidget();
79 | }
80 |
81 | void FilesystemWidget::pathChanged() {
82 | // Enable/disable go up button
83 | if (dir.absolutePath() == QDir::rootPath()) {
84 | goUpAction->setDisabled(true);
85 | // If we are on a Mac, we should allow hidden files - GH-40
86 | // fsWidgetModel->setFilter(QDir::AllDirs|QDir::Files|QDir::NoDotAndDotDot|QDir::Hidden);
87 | }
88 | else { goUpAction->setEnabled(true); }
89 | updateWidget();
90 | }
91 |
92 | void FilesystemWidget::doubleClickAt(QModelIndex modelIndex) {
93 | // It it's a dir, we must change path
94 | if (fsWidgetModel->isDir(modelIndex)) {
95 | dir = QDir(fsWidgetModel->filePath(modelIndex));
96 | updateWidget();
97 | }
98 |
99 | // If it's a file, we must add it to playlist
100 | else {
101 | /*
102 | * TODO: emit signal using URL, not path.
103 | * Let PlaylistWidget handle this.
104 | */
105 | PlaylistItem *newItem = new PlaylistItem(fsWidgetModel->filePath(modelIndex));
106 | emit askToAddItemToPlaylist(newItem);
107 | }
108 | }
109 |
110 |
111 | void FilesystemWidget::updateWidget() {
112 | fsWidgetModel->setRootPath(dir.absolutePath());
113 | fsListView->setRootIndex(fsWidgetModel->index(dir.absolutePath()));
114 | setNameFilter();
115 | }
116 |
117 |
118 |
--------------------------------------------------------------------------------
/src/mainwindow/mainnotebook/mainnotebook.cpp:
--------------------------------------------------------------------------------
1 | #include "mainnotebook.h"
2 |
3 | /// @brief Construtor
4 | ///
5 | /// @param parent Parent widget
6 | MainNotebook::MainNotebook(QWidget *parent, PlaylistWidget *playlistWidget)
7 | {
8 | setParent(parent);
9 | setTabPosition(QTabWidget::West);
10 |
11 | // Set tabs movable
12 | setMovable(true);
13 | QTabBar *tabbar = tabBar();
14 | connect(tabbar, SIGNAL(tabMoved(int,int)), this, SLOT(saveTabOrder()));
15 |
16 | // Widget of the Collection tab
17 | collectionWidget = new CollectionTreeWidget();
18 | connect(collectionWidget, SIGNAL(askToAddItemToPlaylist(QList)), playlistWidget, SLOT(addSong(QList)));
19 | connect(collectionWidget, SIGNAL(scanning()), this, SLOT(showCollectionProgress()));
20 | connect(collectionWidget, SIGNAL(listUpdated()), this, SLOT(hideCollectionProgress()));
21 | collectionContainer = new QWidget(this);
22 | QVBoxLayout *collectionLayout = new QVBoxLayout(collectionContainer);
23 |
24 | progressContainer = new QWidget(collectionContainer);
25 | scanProgress = new QProgressBar(collectionContainer);
26 | scanLabel = new QLabel("Scanning collection...", collectionContainer);
27 | QHBoxLayout *progressHLayout = new QHBoxLayout(progressContainer);
28 | progressHLayout->addWidget(scanLabel);
29 | progressHLayout->addWidget(scanProgress);
30 | progressContainer->setLayout(progressHLayout);
31 | progressContainer->hide();
32 |
33 | collectionLayout->addWidget(collectionWidget);
34 | collectionLayout->addWidget(progressContainer);
35 | collectionContainer->setLayout(collectionLayout);
36 |
37 | // Widget of the Files tab
38 | FilesystemWidget *filesystemWidget = new FilesystemWidget(this);
39 | connect(filesystemWidget, SIGNAL(askToAddItemToPlaylist(PlaylistItem*)), playlistWidget, SLOT(addSong(PlaylistItem*)));
40 | filesystemContainer = new QWidget(this);
41 | QVBoxLayout *filesystemLayout = new QVBoxLayout(filesystemContainer);
42 | filesystemLayout->setContentsMargins(5, 5, 5, 5);
43 | filesystemLayout->addWidget(filesystemWidget);
44 |
45 | // Widget of the Context tab
46 | ContextWidget *contextWidget = new ContextWidget(this);
47 | connect(playlistWidget, SIGNAL(songInformationUpdated(QMap)), contextWidget, SLOT(songInformationUpdated(QMap)));
48 | contextContainer = new QWidget(this);
49 | QVBoxLayout *contextLayout = new QVBoxLayout(contextContainer);
50 | contextLayout->setContentsMargins(5, 5, 5, 5);
51 | contextLayout->addWidget(contextWidget);
52 |
53 | // Playlists widget
54 | testLabel3 = new QLabel(tr("Test label 3"));
55 |
56 | // Set positions
57 | collectionPosition = (ApplicationSettings::getTabOrder("collection") > -1) ? ApplicationSettings::getTabOrder("collection") : 0;
58 | filesystemPosition = (ApplicationSettings::getTabOrder("filesystem") > -1) ? ApplicationSettings::getTabOrder("filesystem") : 1;
59 | contextPosition = (ApplicationSettings::getTabOrder("context") > -1) ? ApplicationSettings::getTabOrder("context") : 2;
60 | playlistsPosition = (ApplicationSettings::getTabOrder("playlists") > -1) ? ApplicationSettings::getTabOrder("playlists") : 3;
61 |
62 |
63 | // Insert tabs
64 | for (int i = 0; i < 4; i++) {
65 | if (i == collectionPosition) { addTab(collectionContainer, IconFactory::fromTheme("audio-x-generic"), tr("Collection")); }
66 | else if (i == filesystemPosition) { addTab(filesystemContainer, IconFactory::fromTheme("system-file-manager"), tr("Files")); }
67 | else if (i == contextPosition) { addTab(contextContainer, IconFactory::fromTheme("emblem-web"), tr("Context")); }
68 | else if (i == playlistsPosition) { addTab(testLabel3, IconFactory::fromTheme("text-x-generic"), tr("Playlists")); }
69 | }
70 |
71 | show();
72 | }
73 |
74 | void MainNotebook::saveTabOrder() {
75 |
76 | for (int i = 0; i < count(); i++) {
77 | if (widget(i) == collectionContainer) collectionPosition = i;
78 | else if (widget(i) == filesystemContainer) filesystemPosition = i;
79 | else if (widget(i) == contextContainer) contextPosition = i;
80 | else if (widget(i) == testLabel3) playlistsPosition = i;
81 | }
82 |
83 | ApplicationSettings::setTabOrder("collection", collectionPosition);
84 | ApplicationSettings::setTabOrder("filesystem", filesystemPosition);
85 | ApplicationSettings::setTabOrder("context", contextPosition);
86 | ApplicationSettings::setTabOrder("playlists", playlistsPosition);
87 |
88 | }
89 |
90 | void MainNotebook::showCollectionProgress() {
91 | // Set minimum and maximum to 0 makes the progress bar go left and right forever
92 | scanProgress->setMinimum(0);
93 | scanProgress->setMaximum(0);
94 | progressContainer->show();
95 | }
96 |
97 | void MainNotebook::hideCollectionProgress() {
98 | scanProgress->setMinimum(0);
99 | scanProgress->setMaximum(100);
100 | progressContainer->hide();
101 | }
102 |
--------------------------------------------------------------------------------
/src/settings/applicationsettings.cpp:
--------------------------------------------------------------------------------
1 | #include "applicationsettings.h"
2 |
3 | void ApplicationSettings::initialisation() {
4 | QSettings settings(QSettings::UserScope, QApplication::organizationName(), QApplication::applicationName());
5 | settings.beginGroup("General");
6 | settings.setValue("version", QApplication::applicationVersion());
7 | settings.endGroup();
8 | }
9 |
10 | void ApplicationSettings::setTabOrder(QString tab, int value) {
11 | QSettings settings(QSettings::UserScope, QApplication::organizationName(), QApplication::applicationName());
12 | settings.beginGroup("TabOrder");
13 | settings.setValue(tab, value);
14 | settings.endGroup();
15 | }
16 |
17 | int ApplicationSettings::getTabOrder(QString tab) {
18 | QSettings settings(QSettings::UserScope, QApplication::organizationName(), QApplication::applicationName());
19 |
20 | settings.beginGroup("TabOrder");
21 | int position = settings.value(tab, -1).toInt();
22 | settings.endGroup();
23 |
24 | return position;
25 | }
26 |
27 | void ApplicationSettings::setSplitterSize(float value) {
28 | QSettings settings(QSettings::UserScope, QApplication::organizationName(), QApplication::applicationName());
29 |
30 | settings.beginGroup("Widgets");
31 | settings.setValue("SplitterRelativeSize", QString::number(value)); // Save as string only to make it readable
32 | settings.endGroup();
33 | }
34 |
35 | float ApplicationSettings::getSplitterSize() {
36 | QSettings settings(QSettings::UserScope, QApplication::organizationName(), QApplication::applicationName());
37 |
38 | settings.beginGroup("Widgets");
39 | float size = settings.value("SplitterRelativeSize", 0.25).toFloat();
40 | settings.endGroup();
41 |
42 | return size;
43 | }
44 |
45 | void ApplicationSettings::createAppDirIfNeeded() {
46 | QString storageLocation = QDesktopServices::storageLocation(QDesktopServices::DataLocation);
47 | QDir dir(storageLocation);
48 | if (!dir.exists()) {
49 | if (!dir.mkpath(storageLocation)) {
50 | qWarning("Could not create data directory.\n\nExiting...");
51 | QApplication::exit(1);
52 | }
53 | }
54 | else if (!dir.isReadable()) {
55 | qWarning("Could not read data directory.\n\nExiting...");
56 | QApplication::exit(1);
57 | }
58 |
59 | }
60 |
61 | QStringList ApplicationSettings::collectionFolderList() {
62 | QSettings settings(QSettings::UserScope, QApplication::organizationName(), QApplication::applicationName());
63 |
64 | QStringList folders;
65 | int size = settings.beginReadArray("folders");
66 | for (int i = 0; i < size; ++i) {
67 | settings.setArrayIndex(i);
68 | folders.append(settings.value("path").toString());
69 | }
70 | folders.removeDuplicates();
71 |
72 | settings.endArray();
73 | // TODO: remove paths that do not exist.
74 |
75 | return folders;
76 | }
77 |
78 | void ApplicationSettings::addCollectionFolder(QString location) {
79 | QSettings settings(QSettings::UserScope, QApplication::organizationName(), QApplication::applicationName());
80 |
81 | QStringList folders = collectionFolderList();
82 | folders.append(location);
83 | folders.removeDuplicates();
84 | settings.beginWriteArray("folders");
85 | for (int i = 0; i < folders.size(); ++i) {
86 | settings.setArrayIndex(i);
87 | settings.setValue("path", folders.at(i));
88 | }
89 | settings.endArray();
90 | }
91 |
92 | void ApplicationSettings::setCollectionFolders(QStringList folders) {
93 |
94 | QSettings settings(QSettings::UserScope, QApplication::organizationName(), QApplication::applicationName());
95 |
96 | // Get list of old values
97 | QStringList oldFolders = collectionFolderList();
98 |
99 | // Clear old values from settings file
100 | settings.remove("folders");
101 |
102 | settings.beginWriteArray("folders");
103 | for (int i = 0; i < folders.size(); ++i) {
104 | settings.setArrayIndex(i);
105 | settings.setValue("path", folders.at(i));
106 |
107 | /*
108 | * If we're adding a folder that does not exist, call service to scan it!
109 | */
110 | if (!oldFolders.contains(folders.at(i), Qt::CaseSensitive)) {
111 | qDebug("Parsing directory...");
112 | // TODO: scan directory - #1
113 | }
114 | }
115 | settings.endArray();
116 | }
117 |
118 |
119 | void ApplicationSettings::removeColletionFolder(QString location) {
120 | QSettings settings(QSettings::UserScope, QApplication::organizationName(), QApplication::applicationName());
121 | QStringList folders = collectionFolderList();
122 | folders.append(location);
123 | folders.removeAll(location);
124 | settings.beginWriteArray("folders");
125 | for (int i = 0; i < folders.size(); ++i) {
126 | settings.setArrayIndex(i);
127 | qDebug(folders.at(i).toUtf8());
128 | settings.setValue("path", folders.at(i));
129 | }
130 | settings.endArray();
131 | }
132 |
--------------------------------------------------------------------------------
/src/mainwindow/mainwindow.cpp:
--------------------------------------------------------------------------------
1 | #include "mainwindow.h"
2 |
3 | /// @brief Construtor.
4 | ///
5 | /// @param parent Parent widget
6 | MainWindow::MainWindow(QWidget *parent)
7 | : QMainWindow(parent)
8 | {
9 | setObjectName("MainWindow");
10 | setWindowTitle("Audactile");
11 | setMinimumWidth(700);
12 | setMinimumHeight(500);
13 |
14 | // Set strong focus, to capture all keyboard events
15 | setFocusPolicy(Qt::StrongFocus);
16 |
17 | // Our Phonon MediaObject
18 | mediaObject = new Phonon::MediaObject(this);
19 | mediaObject->setTickInterval(1000);
20 | audioOutput = new Phonon::AudioOutput(Phonon::MusicCategory, this);
21 | Phonon::createPath(mediaObject, audioOutput);
22 | connect(audioOutput, SIGNAL(mutedChanged(bool)), this, SLOT(handleMute(bool)));
23 | connect(audioOutput, SIGNAL(volumeChanged(qreal)), this, SLOT(handleVolume(qreal)));
24 |
25 | // Connect the MediaObject to our Last.fm scrobbler
26 | LastFmScrobbler *scrobbler = new LastFmScrobbler(mediaObject);
27 |
28 | // Creates the horizontal layout where we'll put our notebook
29 | middleSplitter = new QSplitter();
30 | connect(middleSplitter, SIGNAL(splitterMoved(int,int)), this, SLOT(saveSplitterSize(int, int)));
31 | playlistWidget = new PlaylistWidget(this, mediaObject);
32 | mainNotebook = new MainNotebook(this, playlistWidget); // Notebook needs to connect context to playlistwidget!
33 | // TODO: playlistWidget must be a singleton.
34 | mainNotebook->setMinimumWidth(200);
35 |
36 | // Add widgets
37 | middleSplitter->addWidget(mainNotebook);
38 | middleSplitter->addWidget(playlistWidget);
39 | middleSplitter->setStretchFactor(0, 1);
40 | middleSplitter->setStretchFactor(1, 3);
41 |
42 | // Set splitter sizes to saved sizes, if existent
43 | int notebookSize = ceil(width() * ApplicationSettings::getSplitterSize());
44 | QList sizes;
45 | sizes.append(notebookSize);
46 | sizes.append(width() - notebookSize);
47 | middleSplitter->setSizes(sizes);
48 | middleSplitter->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::MinimumExpanding);
49 |
50 | // Includes our toolbar with more widgets, friendly called PlayerBar
51 | PlayerBar *playerbar = new PlayerBar(this, mediaObject, audioOutput);
52 | connect(playlistWidget, SIGNAL(songInformationUpdated(QMap)), playerbar, SLOT(updateSongInformation(QMap)));
53 | connect(playerbar, SIGNAL(nextButtonClicked()), playlistWidget, SLOT(playNextSong()));
54 | connect(playerbar, SIGNAL(previousButtonClicked()), playlistWidget, SLOT(playPreviousSong()));
55 | connect(playerbar, SIGNAL(toggleFullScreen()), this, SLOT(toggleFullscreen()));
56 |
57 | // Makes playerbar change the window state button
58 | connect(this, SIGNAL(windowStateChanged(Qt::WindowStates)), playerbar, SLOT(handleWindowStateChange(Qt::WindowStates)));
59 |
60 | // Create a vertical layout
61 | QWidget *mainVerticalWidget = new QWidget();
62 | QVBoxLayout *verticalLayout = new QVBoxLayout();
63 | verticalLayout->addWidget(playerbar);
64 | verticalLayout->addWidget(middleSplitter);
65 | mainVerticalWidget->setLayout(verticalLayout);
66 | setCentralWidget(mainVerticalWidget);
67 | }
68 |
69 |
70 | // Handle splitter movement
71 | void MainWindow::saveSplitterSize(int pos, int index) {
72 | if (middleSplitter->widget(index) == playlistWidget) {
73 | ApplicationSettings::setSplitterSize((float)pos / width());
74 | }
75 | }
76 |
77 | /*
78 | *
79 | * The functions handleMute and handleVolume were created to avoid
80 | * the problems with Phonon::VolumeSlider, that was showing wrong
81 | * volume = 0% when volume was unmuted.
82 | *
83 | */
84 |
85 |
86 | void MainWindow::handleMute(bool mute) {
87 | if (!mute) {
88 | audioOutput->setVolume(outputVolume);
89 | }
90 | }
91 | void MainWindow::handleVolume(qreal volume) {
92 | outputVolume = volume;
93 | }
94 |
95 |
96 | // Handle keyboard events
97 | void MainWindow::keyPressEvent(QKeyEvent *event) {
98 | switch (event->key()) {
99 | case Qt::Key_Space:
100 | case Qt::Key_Enter:
101 | case Qt::Key_MediaPlay: // Will not work in Linux, verify Mac and Windows
102 | case Qt::Key_Play: // Will not work in Linux, verify Mac and Windows
103 | case Qt::Key_Return:
104 | event->accept();
105 | break;
106 |
107 | // Full screen support!
108 | case Qt::Key_F11:
109 | toggleFullscreen();
110 | event->accept();
111 | break;
112 | default:
113 | event->ignore();
114 | break;
115 | }
116 | }
117 |
118 | // TODO: add button to toggle fullscreen mode.
119 | void MainWindow::toggleFullscreen() {
120 | if (windowState() != Qt::WindowFullScreen) {
121 | originalWindowState = windowState();
122 | showFullScreen();
123 | }
124 | else {
125 | switch (originalWindowState) {
126 | case Qt::WindowNoState:
127 | showNormal();
128 | break;
129 | case Qt::WindowMinimized:
130 | showMinimized();
131 | break;
132 | default:
133 | showMaximized();
134 | break;
135 | }
136 | }
137 | emit windowStateChanged(windowState());
138 | }
139 |
--------------------------------------------------------------------------------
/icons.qrc:
--------------------------------------------------------------------------------
1 |
2 |
3 | icons/gnome/16x16/actions/application-exit.png
4 | icons/gnome/16x16/actions/go-home.png
5 | icons/gnome/16x16/actions/go-up.png
6 | icons/gnome/16x16/actions/media-playback-start.png
7 | icons/gnome/16x16/actions/media-playback-stop.png
8 | icons/gnome/16x16/actions/media-skip-backward.png
9 | icons/gnome/16x16/actions/media-skip-forward.png
10 | icons/gnome/16x16/apps/system-file-manager.png
11 | icons/gnome/16x16/categories/preferences-other.png
12 | icons/gnome/16x16/devices/media-cdrom.png
13 | icons/gnome/16x16/emblems/emblem-web.png
14 | icons/gnome/16x16/mimetypes/audio-x-generic.png
15 | icons/gnome/16x16/mimetypes/sound.png
16 | icons/gnome/16x16/mimetypes/text-x-generic.png
17 | icons/gnome/16x16/places/folder.png
18 | icons/gnome/22x22/actions/application-exit.png
19 | icons/gnome/22x22/actions/go-home.png
20 | icons/gnome/22x22/actions/go-up.png
21 | icons/gnome/22x22/actions/media-playback-start.png
22 | icons/gnome/22x22/actions/media-playback-stop.png
23 | icons/gnome/22x22/actions/media-skip-backward.png
24 | icons/gnome/22x22/actions/media-skip-forward.png
25 | icons/gnome/22x22/apps/system-file-manager.png
26 | icons/gnome/22x22/categories/preferences-other.png
27 | icons/gnome/22x22/devices/media-cdrom.png
28 | icons/gnome/22x22/emblems/emblem-web.png
29 | icons/gnome/22x22/mimetypes/audio-x-generic.png
30 | icons/gnome/22x22/mimetypes/sound.png
31 | icons/gnome/22x22/mimetypes/text-x-generic.png
32 | icons/gnome/22x22/places/folder.png
33 | icons/gnome/24x24/actions/application-exit.png
34 | icons/gnome/24x24/actions/go-home.png
35 | icons/gnome/24x24/actions/go-up.png
36 | icons/gnome/24x24/actions/media-playback-start.png
37 | icons/gnome/24x24/actions/media-playback-stop.png
38 | icons/gnome/24x24/actions/media-skip-backward.png
39 | icons/gnome/24x24/actions/media-skip-forward.png
40 | icons/gnome/24x24/apps/system-file-manager.png
41 | icons/gnome/24x24/categories/preferences-other.png
42 | icons/gnome/24x24/devices/media-cdrom.png
43 | icons/gnome/24x24/emblems/emblem-web.png
44 | icons/gnome/24x24/mimetypes/audio-x-generic.png
45 | icons/gnome/24x24/mimetypes/sound.png
46 | icons/gnome/24x24/mimetypes/text-x-generic.png
47 | icons/gnome/24x24/places/folder.png
48 | icons/gnome/256x256/apps/system-file-manager.png
49 | icons/gnome/256x256/devices/media-cdrom.png
50 | icons/gnome/256x256/mimetypes/audio-x-generic.png
51 | icons/gnome/256x256/mimetypes/sound.png
52 | icons/gnome/256x256/mimetypes/text-x-generic.png
53 | icons/gnome/256x256/places/folder.png
54 | icons/gnome/32x32/actions/application-exit.png
55 | icons/gnome/32x32/actions/go-home.png
56 | icons/gnome/32x32/actions/go-up.png
57 | icons/gnome/32x32/actions/media-playback-start.png
58 | icons/gnome/32x32/actions/media-playback-stop.png
59 | icons/gnome/32x32/actions/media-skip-backward.png
60 | icons/gnome/32x32/actions/media-skip-forward.png
61 | icons/gnome/32x32/apps/system-file-manager.png
62 | icons/gnome/32x32/categories/preferences-other.png
63 | icons/gnome/32x32/devices/media-cdrom.png
64 | icons/gnome/32x32/emblems/emblem-web.png
65 | icons/gnome/32x32/mimetypes/audio-x-generic.png
66 | icons/gnome/32x32/mimetypes/sound.png
67 | icons/gnome/32x32/mimetypes/text-x-generic.png
68 | icons/gnome/32x32/places/folder.png
69 | icons/gnome/48x48/actions/application-exit.png
70 | icons/gnome/48x48/actions/go-home.png
71 | icons/gnome/48x48/actions/go-up.png
72 | icons/gnome/48x48/actions/media-playback-start.png
73 | icons/gnome/48x48/actions/media-playback-stop.png
74 | icons/gnome/48x48/actions/media-skip-backward.png
75 | icons/gnome/48x48/actions/media-skip-forward.png
76 | icons/gnome/48x48/apps/system-file-manager.png
77 | icons/gnome/48x48/categories/preferences-other.png
78 | icons/gnome/48x48/devices/media-cdrom.png
79 | icons/gnome/48x48/emblems/emblem-web.png
80 | icons/gnome/48x48/mimetypes/audio-x-generic.png
81 | icons/gnome/48x48/mimetypes/sound.png
82 | icons/gnome/48x48/mimetypes/text-x-generic.png
83 | icons/gnome/48x48/places/folder.png
84 | icons/gnome/index.theme
85 | icons/gnome/16x16/actions/view-fullscreen.png
86 | icons/gnome/16x16/actions/view-restore.png
87 | icons/gnome/22x22/actions/view-fullscreen.png
88 | icons/gnome/22x22/actions/view-restore.png
89 | icons/gnome/24x24/actions/view-fullscreen.png
90 | icons/gnome/24x24/actions/view-restore.png
91 | icons/gnome/32x32/actions/view-fullscreen.png
92 | icons/gnome/32x32/actions/view-restore.png
93 | icons/gnome/48x48/actions/view-fullscreen.png
94 | icons/gnome/48x48/actions/view-restore.png
95 | icons/gnome/16x16/actions/go-down.png
96 | icons/gnome/22x22/actions/go-down.png
97 | icons/gnome/24x24/actions/go-down.png
98 | icons/gnome/32x32/actions/go-down.png
99 | icons/gnome/48x48/actions/go-down.png
100 |
101 |
102 |
--------------------------------------------------------------------------------
/src/services/lastfmscrobbler.cpp:
--------------------------------------------------------------------------------
1 | #include "lastfmscrobbler.h"
2 |
3 | LastFmScrobbler::LastFmScrobbler(Phonon::MediaObject *mediaObject)
4 | {
5 | resetSongStatus();
6 | this->mediaObject = mediaObject;
7 | songsToScrobble = new QList();
8 | connect(mediaObject, SIGNAL(stateChanged(Phonon::State,Phonon::State)), this, SLOT(handleStateChange(Phonon::State,Phonon::State)));
9 |
10 | // TODO: call this only if Last.fm is enabled.
11 | // Maybe do a function to call this, calling only
12 | // on applicaton start or when Last.fm is enabled.
13 | state = this->LastFmStateNone;
14 | netManager = new QNetworkAccessManager(this);
15 | handshake();
16 | }
17 |
18 |
19 | void LastFmScrobbler::handshake() {
20 | QDateTime time = QDateTime::currentDateTime();
21 | QString timeStamp = QString::number(time.toTime_t());
22 |
23 | QUrl url("http://post.audioscrobbler.com/");
24 | url.addQueryItem("hs", "true");
25 | url.addQueryItem("p", "1.2.1");
26 | url.addQueryItem("c", "adl");
27 | url.addQueryItem("v", "0.1");
28 | url.addQueryItem("u", LastFmSettings::username());
29 | url.addQueryItem("t", timeStamp);
30 | url.addQueryItem("a", generateToken(LastFmSettings::password(), timeStamp));
31 |
32 | state = LastFmStateWaitingToken;
33 | QNetworkRequest netRequest;
34 | netRequest.setUrl(url);
35 | qDebug("LastFmScrobbler: Asking to login to Last.fm...");
36 | authReply = netManager->get(netRequest);
37 | connect(authReply, SIGNAL(finished()), this, SLOT(readAuthenticationReply())); // TODO: handle error() signal
38 |
39 | // If we already have songs in the queue, scrobble them!
40 | if (songsToScrobble->count() > 0) {
41 | tryToScrobble();
42 | }
43 |
44 | }
45 |
46 | void LastFmScrobbler::readAuthenticationReply() {
47 | qDebug("LastFmScrobbler: Got reply!");
48 | QString replyString = authReply->readAll();
49 | if (state == LastFmStateWaitingToken) {
50 | QStringList lines = replyString.split('\n');
51 | qDebug("LastFmScrobbler: Last.fm answer: " + QString(lines.at(0)).toUtf8());
52 | if (lines.at(0) == "OK") {
53 | state = LastFmGotToken;
54 | sessionId = lines.at(1);
55 | nowPlayingUrl = lines.at(2);
56 | submissionUrl = lines.at(3);
57 | qDebug("LastFmScrobbler: Last.fm token: " + sessionId.toUtf8());
58 | }
59 | // TODO: better feedback for the user of what's wrong.
60 | // BANNED / BADAUTH / BADTIME / FAILED
61 | else {
62 | qDebug("LastFmScrobbler: Authentication problem! Disabling Last.fm");
63 | LastFmSettings::setActive(false);
64 | state = LastFmStateNone;
65 | }
66 | }
67 | }
68 |
69 | QString LastFmScrobbler::generateToken(QString input, QString timestamp) {
70 | /*
71 | * As said in http://www.last.fm/api/submissions#1.2 ,
72 | * we must create a token in the format
73 | * token = md5(md5(password) + timestamp)
74 | * to use in scrobble.
75 | */
76 | QByteArray encryptedPassword = QCryptographicHash::hash(input.toUtf8(), QCryptographicHash::Md5).toHex();
77 | QByteArray token = QCryptographicHash::hash(encryptedPassword + timestamp.toUtf8(), QCryptographicHash::Md5).toHex();
78 | return QString(token);
79 | }
80 |
81 | void LastFmScrobbler::onTick(qint64 time) {
82 | /*
83 | * If Last.fm was disabled, the signal will be disconnected only
84 | * when the Phonon state changes. This test avoid undesired
85 | * scrobbling.
86 | */
87 | if (!LastFmSettings::isActive()) return;
88 |
89 | /*
90 | * Since tickinterval = 1000 isn't asserting that onTick is called
91 | * every 1 second, we'll use lastTickTime to assert that we'll only
92 | * sum 1 to ellapsedTime when 1 second has passed.
93 | */
94 | if (time == lastTickTime) return;
95 |
96 | ellapsedTime++;
97 | lastTickTime = time;
98 | /*
99 | * We only can scrobble songs that were played for more than
100 | * 240s or half of its length.
101 | */
102 | if (ellapsedTime >= timeToScrobble || ellapsedTime >= 240) {
103 | songsToScrobble->append(currentSong);
104 | tryToScrobble();
105 |
106 | // Disconnect this slot if the song was already queued.
107 | disconnect(mediaObject, SIGNAL(tick(qint64)), this, SLOT(onTick(qint64)));
108 | }
109 | }
110 |
111 | void LastFmScrobbler::resetSongStatus() {
112 | ellapsedTime = 0;
113 | lastTickTime = 0;
114 | canScrobble = false;
115 |
116 | // Reset currentSong
117 | SongInfo resetSong;
118 | currentSong = resetSong;
119 | }
120 |
121 |
122 | void LastFmScrobbler::tryToScrobble() {
123 | qDebug("LastFmScrobbler: Trying to scrobble queued songs");
124 | // If we got no token or queue, nothing done.
125 | if (state != LastFmGotToken || songsToScrobble->count() == 0) return;
126 |
127 | // Try to scrobble queue
128 | QUrl url(submissionUrl);
129 | QString dataToPost = "s=" + sessionId;
130 |
131 | for (int i = 0; i < songsToScrobble->count(); i++) {
132 | SongInfo song = songsToScrobble->at(i);
133 | dataToPost += "&a[" + QString::number(i) + "]=" + song.artist + "&" +
134 | "t[" + QString::number(i) + "]=" + song.title + "&" +
135 | "b[" + QString::number(i) + "]=" + song.album + "&" +
136 | "i[" + QString::number(i) + "]=" + song.startTimeStamp + "&" +
137 | "l[" + QString::number(i) + "]=" + song.duration + "&" +
138 | "n[" + QString::number(i) + "]=" + "&" + // TODO: track number
139 | "m[" + QString::number(i) + "]=" + "&" + // TODO: MusicBrainz id
140 | "r[" + QString::number(i) + "]=" + "&" + // TODO: rating
141 | "o[" + QString::number(i) + "]=P";
142 | }
143 | //dataToPost = dataToPost.replace(' ',"%20");
144 | qDebug("LastFmScrobbler: Data to post to " + submissionUrl.toUtf8() + ": " + dataToPost.toUtf8());
145 |
146 | QNetworkRequest netRequest;
147 | netRequest.setUrl(url);
148 | qDebug("LastFmScrobbler: Scrobbling...");
149 |
150 | submissionReply = netManager->post(netRequest,dataToPost.toUtf8());
151 | connect(submissionReply, SIGNAL(readyRead()), this, SLOT(readSubmissionReply()));
152 | }
153 |
154 |
155 | void LastFmScrobbler::readSubmissionReply() {
156 | qDebug("LastFmScrobbler: Got submission reply!");
157 | QString replyString = QString::fromUtf8(submissionReply->readAll().replace('\n', ""));
158 | qDebug("LastFmScrobbler: Reply: " + QString(replyString).toAscii());
159 |
160 | // If we get an OK, clear our list of songs to scrobble.
161 | if (replyString == "OK") {
162 | songsToScrobble->clear();
163 | }
164 | // BADSESSION? Handshake again. And try to scrobble the songs after.
165 | else if (replyString == "BADSESSION") {
166 | handshake();
167 | }
168 |
169 |
170 | }
171 |
172 | void LastFmScrobbler::handleStateChange(Phonon::State newState, Phonon::State oldState) {
173 | // Disconnect slots if Last.fm is disabled.
174 | if (!LastFmSettings::isActive()) {
175 | qDebug("Last.fm is disabled");
176 | disconnect(mediaObject, SIGNAL(tick(qint64)), this, SLOT(onTick(qint64)));
177 | disconnect(mediaObject, SIGNAL(finished()), this, SLOT(resetSongStatus()));
178 | return;
179 | }
180 |
181 |
182 | // Save information to scrobble!
183 | if (newState == Phonon::PlayingState && (oldState == Phonon::StoppedState || oldState == Phonon::LoadingState)) {
184 | QMap metaData = mediaObject->metaData();
185 | QString artist = metaData.value("ARTIST");
186 | QString album = metaData.value("ALBUM");
187 | QString title = metaData.value("TITLE");
188 |
189 | // We only can scrobble titles that have
190 | // artist and title defined.
191 | if (!artist.isEmpty() && !title.isEmpty() && mediaObject->totalTime() >= 30000) {
192 | timeToScrobble = qRound(mediaObject->totalTime() / 2000);
193 | connect(mediaObject, SIGNAL(tick(qint64)), this, SLOT(onTick(qint64)));
194 | connect(mediaObject, SIGNAL(finished()), this, SLOT(resetSongStatus()));
195 |
196 | canScrobble = true;
197 |
198 | // Reset currentSong
199 | SongInfo resetSong;
200 | currentSong = resetSong;
201 | currentSong.artist = artist;
202 | currentSong.title = title;
203 | currentSong.duration = QString::number(qRound(mediaObject->totalTime() / 1000));
204 | if (!album.isEmpty()) currentSong.album = album;
205 |
206 | QDateTime time = QDateTime::currentDateTime();
207 | currentSong.startTimeStamp = QString::number(time.toTime_t()).toUtf8();
208 |
209 | // Send a "Now Playing" notification to Last.fm
210 | QUrl url(nowPlayingUrl);
211 | QString dataToPost = "s=" + sessionId + "&" +
212 | "a=" + currentSong.artist + "&" +
213 | "t=" + currentSong.title + "&" +
214 | "b=" + currentSong.album + "&" +
215 | "l=" + currentSong.duration + "&" +
216 | "n=";
217 |
218 | QNetworkRequest netRequest;
219 | netRequest.setUrl(url);
220 | qDebug("Sending Now Playing...");
221 | nowPlayingReply = netManager->post(netRequest,dataToPost.toUtf8());
222 | connect(nowPlayingReply, SIGNAL(readyRead()), this, SLOT(readNowPlayingReply()));
223 |
224 | }
225 | }
226 | else if (newState == Phonon::StoppedState) {
227 | resetSongStatus();
228 | }
229 |
230 | }
231 |
232 | void LastFmScrobbler::readNowPlayingReply() {
233 | qDebug("Got Now Playing reply!");
234 | QString replyString = QString::fromUtf8(nowPlayingReply->readAll());
235 | qDebug("Reply: " + replyString.toUtf8());
236 | }
237 |
--------------------------------------------------------------------------------
/src/mainwindow/playerbar.cpp:
--------------------------------------------------------------------------------
1 | #include "playerbar.h"
2 |
3 | using namespace Phonon;
4 |
5 | /// @brief Audactile's main toolbar class.
6 | ///
7 | /// @param parent Parent widget
8 | /// @param mediaObject MediaObject that will handle the files
9 | /// @param audioOutput AudioOutput that will be linked to MediaObject
10 | PlayerBar::PlayerBar(QWidget *parent, Phonon::MediaObject *mediaObject, Phonon::AudioOutput *audioOutput)
11 | {
12 | mainMediaObject = mediaObject;
13 | setParent(parent);
14 | setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Minimum);
15 |
16 | // Buttons
17 | playButton = new QToolButton(this);
18 | stopButton = new QToolButton(this);
19 | nextButton = new QToolButton(this);
20 | previousButton = new QToolButton(this);
21 | //fullScreenButton = new QToolButton(this);
22 | prefButton = new QToolButton(this);
23 | exitButton = new QToolButton(this);
24 |
25 | playButton->setIconSize(QSize(48, 48));
26 | stopButton->setIconSize(QSize(48, 48));
27 | nextButton->setIconSize(QSize(48, 48));
28 | previousButton->setIconSize(QSize(48, 48));
29 | //fullScreenButton->setIconSize(QSize(48, 48));
30 | prefButton->setIconSize(QSize(48, 48));
31 | exitButton->setIconSize(QSize(48, 48));
32 |
33 | playButton->setAutoRaise(true);
34 | stopButton->setAutoRaise(true);
35 | nextButton->setAutoRaise(true);
36 | previousButton->setAutoRaise(true);
37 | prefButton->setAutoRaise(true);
38 | //fullScreenButton->setAutoRaise(true);
39 | exitButton->setAutoRaise(true);
40 |
41 | playButton->setIcon(IconFactory::fromTheme("media-playback-start"));
42 | stopButton->setIcon(IconFactory::fromTheme("media-playback-stop"));
43 | nextButton->setIcon(IconFactory::fromTheme("media-skip-forward"));
44 | previousButton->setIcon(IconFactory::fromTheme("media-skip-backward"));
45 | prefButton->setIcon(IconFactory::fromTheme("preferences-other"));
46 | //fullScreenButton->setIcon(IconFactory::fromTheme("view-fullscreen"));
47 | exitButton->setIcon(IconFactory::fromTheme("application-exit"));
48 |
49 | stopButton->setDisabled(true);
50 |
51 | // Signals from buttons
52 | connect(playButton, SIGNAL(clicked()), this, SLOT(handlePlayButton()));
53 | connect(stopButton, SIGNAL(clicked()), this, SLOT(handleStopButton()));
54 | connect(nextButton, SIGNAL(clicked()), this, SIGNAL(nextButtonClicked()));
55 | connect(previousButton, SIGNAL(clicked()), this, SIGNAL(previousButtonClicked()));
56 | connect(prefButton, SIGNAL(clicked()), this, SLOT(openSettings()));
57 | connect(exitButton, SIGNAL(clicked()), this, SLOT(exitApplication()));
58 | //connect(fullScreenButton, SIGNAL(clicked()), this, SIGNAL(toggleFullScreen()));
59 |
60 | // Vertical box with slider and current song time labels
61 | QFrame *songPositionWidget = new QFrame(this);
62 | songPositionWidget->setFrameShadow(QFrame::Sunken);
63 | songPositionWidget->setFrameShape(QFrame::StyledPanel);
64 | songPositionWidget->setMidLineWidth(4);
65 |
66 |
67 | // Horizontal box with current song position labels
68 | QWidget *songPositionLabelsWidget = new QWidget(this);
69 | QHBoxLayout *songPositionLabelsHBox = new QHBoxLayout(songPositionLabelsWidget);
70 | currentSongPosition = new QLabel(tr("--:--"));
71 | remainingSongPosition = new QLabel(tr("--:--"));
72 | currentSongInfo = new QLabel(tr(""));
73 |
74 | // Define song info to be bold
75 | QFont songInfoFont = currentSongInfo->font();
76 | songInfoFont.setBold(true);
77 | songInfoFont.setPointSize(songInfoFont.pointSize() + 2);
78 | currentSongInfo->setFont(songInfoFont);
79 |
80 | // Add labels
81 | songPositionLabelsHBox->addWidget(currentSongPosition, 1, Qt::AlignLeft);
82 | songPositionLabelsHBox->addWidget(currentSongInfo, 1, Qt::AlignCenter);
83 | songPositionLabelsHBox->addWidget(remainingSongPosition, 1, Qt::AlignRight);
84 | songPositionLabelsWidget->setLayout(songPositionLabelsHBox);
85 |
86 | // Set background for song position widget
87 | QPalette palette = songPositionWidget->palette();
88 | QLinearGradient songPositionBgGradient(1.0, 1.0, 1.0, 40.0);
89 | songPositionBgGradient.setColorAt(0.0, QColor(250,250,230));
90 | songPositionBgGradient.setColorAt(1.0, QColor(220,220,200));
91 | palette.setBrush(songPositionLabelsWidget->backgroundRole(), QBrush(songPositionBgGradient));
92 | palette.setColor(songPositionLabelsWidget->foregroundRole(), QColor(100,100,100));
93 | songPositionWidget->setPalette(palette);
94 | songPositionWidget->setAutoFillBackground(true);
95 |
96 | QVBoxLayout *songPositionVBox = new QVBoxLayout(songPositionWidget);
97 | songPositionSlider = new SeekSlider(mainMediaObject, this);
98 | songPositionSlider->setOrientation(Qt::Horizontal);
99 | songPositionVBox->addWidget(songPositionLabelsWidget);
100 | songPositionVBox->addWidget(songPositionSlider);
101 | songPositionWidget->setLayout(songPositionVBox);
102 |
103 | // Volume button
104 | Phonon::VolumeSlider *volumeSlider = new Phonon::VolumeSlider(audioOutput, this);
105 | volumeSlider->setMaximumWidth((int)floor(0.2*(window()->width())));
106 |
107 | QHBoxLayout *playerBarLayout = new QHBoxLayout(this);
108 | playerBarLayout->addWidget(playButton);
109 | playerBarLayout->addWidget(stopButton);
110 | playerBarLayout->addWidget(Separator::verticalSeparator(this));
111 | playerBarLayout->addWidget(previousButton);
112 | playerBarLayout->addWidget(nextButton);
113 | playerBarLayout->addWidget(Separator::verticalSeparator(this));
114 | playerBarLayout->addWidget(songPositionWidget);
115 | playerBarLayout->addWidget(Separator::verticalSeparator(this));
116 | playerBarLayout->addWidget(volumeSlider);
117 | playerBarLayout->addWidget(Separator::verticalSeparator(this));
118 | //playerBarLayout->addWidget(fullScreenButton);
119 | playerBarLayout->addWidget(prefButton);
120 | playerBarLayout->addWidget(exitButton);
121 |
122 | setLayout(playerBarLayout);
123 |
124 | // Signals from media source
125 | connect(mainMediaObject, SIGNAL(tick(qint64)), this, SLOT(updateSongPosition()));
126 | connect(mainMediaObject, SIGNAL(stateChanged(Phonon::State,Phonon::State)), this, SLOT(handleState(Phonon::State)));
127 | connect(mainMediaObject, SIGNAL(finished()), this, SLOT(finish()));
128 |
129 | show();
130 | }
131 |
132 |
133 |
134 | /// @brief Update current song position and remaining song time on playerbar.
135 | void PlayerBar::updateSongPosition() {
136 | int secs, mins;
137 | QString qStr;
138 |
139 | /*
140 | * Get the remaining time. If we have more than 1000 ms, we
141 | * can update the time. The last second must not update the
142 | * playerbar, since after that the timer will become "--:--".
143 | */
144 | qint64 rem = mainMediaObject->remainingTime();
145 | if (rem > 1000) {
146 | qint64 cur = mainMediaObject->currentTime();
147 |
148 | songPositionSlider->setEnabled(true);
149 |
150 | secs = (rem / 1000) % 60;
151 | mins = (rem / 1000) / 60;
152 | remainingSongPosition->setText("-" + QString::number(mins) + ":" + qStr.sprintf("%02d", secs));
153 |
154 | secs = (cur / 1000) % 60;
155 | mins = (cur / 1000) / 60;
156 | currentSongPosition->setText(QString::number(mins) + ":" + qStr.sprintf("%02d", secs));
157 | }
158 |
159 | }
160 |
161 |
162 | /// @brief Handle the state change in the MediaObject.
163 | ///
164 | /// @param newState New state
165 | /// @param oldState Old state
166 | void PlayerBar::handleState(Phonon::State newState) {
167 | // Handle play button
168 | if (newState == Phonon::PlayingState) {
169 | playButton->setIcon(IconFactory::fromTheme("media-playback-pause"));
170 | }
171 | else if (newState == Phonon::StoppedState) {
172 | playButton->setIcon(IconFactory::fromTheme("media-playback-start"));
173 | }
174 | else if (newState == Phonon::PausedState) {
175 | playButton->setIcon(IconFactory::fromTheme("media-playback-start"));
176 | }
177 |
178 | // Handle stop button
179 | if (newState <= Phonon::StoppedState) {
180 | stopButton->setDisabled(true);
181 | }
182 | else {
183 | stopButton->setEnabled(true);
184 | }
185 | }
186 |
187 | // TODO: deliver the play/stop buttons callback to PlaylistWidget using signals.
188 |
189 | /// @brief Callback for play button
190 | void PlayerBar::handlePlayButton() {
191 | if (mainMediaObject->state() == Phonon::PlayingState) {
192 | mainMediaObject->pause();
193 | }
194 | else if (mainMediaObject->currentSource().type() != MediaSource::Empty) {
195 | mainMediaObject->play();
196 | }
197 | updateSongPosition();
198 | }
199 |
200 | /// @brief Callback for stop button
201 | void PlayerBar::handleStopButton() {
202 | if (mainMediaObject->state() > Phonon::StoppedState) {
203 | resetDisplay();
204 | mainMediaObject->stop();
205 | }
206 | }
207 |
208 | /// @brief Update the song information in the bar
209 | ///
210 | /// @param newSongInformation
211 | void PlayerBar::updateSongInformation(QMapnewSongInformation) {
212 | currentSongInfo->setText(newSongInformation.value("artist") + " - " + newSongInformation.value("title"));
213 | }
214 |
215 | /// @brief Reset information to its initial state
216 | void PlayerBar::resetDisplay() {
217 | qDebug("resetDisplay");
218 | currentSongPosition->setText(tr("--:--"));
219 | remainingSongPosition->setText(tr("--:--"));
220 | currentSongInfo->setText(tr(""));
221 | }
222 |
223 | /// @brief Commands to execute when the music ends
224 | void PlayerBar::finish() {
225 | resetDisplay();
226 | mainMediaObject->stop();
227 | }
228 |
229 |
230 | /// @brief Quit application
231 | void PlayerBar::exitApplication() {
232 | exit(0);
233 | }
234 |
235 | /// @brief Open settings
236 | void PlayerBar::openSettings() {
237 | SettingsDialog *dialog = new SettingsDialog(parentWidget());
238 | }
239 |
240 | /// @brief Set the correct button to screen button
241 | void PlayerBar::handleWindowStateChange(Qt::WindowStates windowState) {
242 | if (windowState == Qt::WindowFullScreen) {
243 | fullScreenButton->setIcon(IconFactory::fromTheme("view-restore"));
244 | }
245 | else {
246 | fullScreenButton->setIcon(IconFactory::fromTheme("view-fullscreen"));
247 | }
248 | }
249 |
--------------------------------------------------------------------------------
/src/collection/collectiondatabase.cpp:
--------------------------------------------------------------------------------
1 | #include "collectiondatabase.h"
2 |
3 |
4 | /*
5 | * TODO:
6 | * - When some music is removed, verify if the album must exist
7 | * - When some album is removed, verify if the artist must exist
8 | * - Handle errors
9 | * - Improve indexes
10 | * - Use some mechanism to speed up the process to add multiple songs of the same artist
11 | *
12 | */
13 |
14 | CollectionDatabase::CollectionDatabase(QObject *parent) :
15 | QObject(parent)
16 | {
17 | db = QSqlDatabase::addDatabase("QSQLITE");
18 | QString database = QDesktopServices::storageLocation(QDesktopServices::DataLocation) + QDir::separator() + "collection.db";
19 | db.setDatabaseName(database);
20 |
21 | createDatabase();
22 | }
23 |
24 | QSqlTableModel *CollectionDatabase::collectionModel() {
25 | QSqlTableModel *model = new QSqlTableModel();
26 | model->setTable("collection");
27 | model->setSort(model->fieldIndex("track_number"), Qt::AscendingOrder);
28 | model->setSort(model->fieldIndex("album"), Qt::AscendingOrder); // TODO: sort by year
29 | model->setSort(model->fieldIndex("artist"), Qt::AscendingOrder);
30 | return model;
31 | }
32 |
33 | QSqlTableModel *CollectionDatabase::artistModel() {
34 | QSqlTableModel *model = new QSqlTableModel();
35 | model->setTable("artist");
36 | model->setSort(model->fieldIndex("name"), Qt::AscendingOrder);
37 | return model;
38 | }
39 |
40 | QSqlTableModel *CollectionDatabase::albumModel() {
41 | QSqlTableModel *model = new QSqlTableModel();
42 | model->setTable("album");
43 | model->setSort(model->fieldIndex("title"), Qt::AscendingOrder);
44 | return model;
45 | }
46 |
47 | QSqlTableModel *CollectionDatabase::musicModel() {
48 | QSqlTableModel *model = new QSqlTableModel();
49 | model->setTable("music");
50 | return model;
51 | }
52 |
53 | void CollectionDatabase::createDatabase() {
54 | if (!db.open()) {
55 | qDebug("ERROR! " + db.lastError().text().toUtf8());
56 | return;
57 | }
58 |
59 | QStringList tables = db.tables(QSql::Tables);
60 | QStringList views = db.tables(QSql::Views);
61 |
62 | // TODO: use titles as primary key?
63 |
64 | if (!tables.contains("artist")) {
65 | db.exec("CREATE TABLE artist ("
66 | "id integer primary key AUTOINCREMENT, "
67 | "name varchar(120) UNIQUE NOT NULL"
68 | ");");
69 | }
70 |
71 | if (!tables.contains("album")) {
72 | db.exec("CREATE TABLE album ("
73 | "id integer primary key AUTOINCREMENT, "
74 | "id_artist int, "
75 | "title varchar(120) NOT NULL"
76 | ");");
77 | }
78 |
79 | if (!tables.contains("music")) {
80 | db.exec("CREATE TABLE music ("
81 | "id integer primary key AUTOINCREMENT, "
82 | "id_album int, "
83 | "track_number int, "
84 | "title varchar(120) NOT NULL,"
85 | "path varchar(2048) UNIQUE NOT NULL, "
86 | "last_modified int "
87 | ");");
88 | }
89 |
90 | if (!views.contains("collection")) {
91 | db.exec("CREATE VIEW collection AS "
92 | "SELECT artist.name as artist, album.title as album, music.title as music, music.track_number as track_number, music.path as path, artist.id as id_artist, album.id as id_album, music.id as id_music "
93 | "FROM artist, album, music "
94 | "WHERE album.id_artist = artist.id AND music.id_album = album.id "
95 | "ORDER BY artist.name, album.title, music.track_number;");
96 | }
97 |
98 | if (db.lastError().type() != QSqlError::NoError) {
99 | qDebug("ERROR! " + db.lastError().text().toUtf8());
100 | }
101 |
102 |
103 | }
104 |
105 | void CollectionDatabase::addArtist(QString artistName) {
106 | if (!db.isOpen()) {
107 | if (!db.open()) qDebug("ERROR! " + db.lastError().text().toUtf8());
108 | return;
109 | }
110 |
111 | QSqlQuery query(db);
112 | query.prepare("SELECT count('name') FROM artist WHERE name=:artist;");
113 | query.bindValue(":artist", artistName);
114 | query.exec();
115 | if (query.first()) {
116 | int total = query.value(0).toInt();
117 | if (total == 0) {
118 | QSqlQuery insertQuery(db);
119 | insertQuery.prepare("INSERT INTO artist (name) VALUES (:artist);");
120 | insertQuery.bindValue(":artist", artistName);
121 | insertQuery.exec();
122 | }
123 | }
124 | }
125 |
126 | void CollectionDatabase::addAlbum(QString artistName, QString albumName) {
127 | // Since addArtist tests if artist already exists, it's safe to call it here
128 | addArtist(artistName);
129 |
130 | if (!db.isOpen()) {
131 | if (!db.open()) qDebug("ERROR! " + db.lastError().text().toUtf8());
132 | return;
133 | }
134 |
135 | QSqlQuery query(db);
136 | query.prepare("SELECT count('title') FROM album WHERE title=:album AND id_artist=(SELECT id FROM artist WHERE name=:artist);");
137 | query.bindValue(":album", albumName);
138 | query.bindValue(":artist", artistName);
139 | query.exec();
140 | if (query.first()) {
141 | int total = query.value(0).toInt();
142 | if (total == 0) {
143 | QSqlQuery insertQuery(db);
144 | insertQuery.prepare("INSERT INTO album (id_artist, title) VALUES ((SELECT id FROM artist WHERE name=:artist),:album);");
145 | insertQuery.bindValue(":album", albumName);
146 | insertQuery.bindValue(":artist", artistName);
147 | insertQuery.exec();
148 | }
149 | }
150 | query.clear();
151 |
152 | if (db.lastError().type() != QSqlError::NoError) {
153 | qDebug("ERROR! " + db.lastError().text().toUtf8());
154 | }
155 | }
156 |
157 | bool CollectionDatabase::addOrUpdateMusic(Music *music) {
158 | if (!music->isValid()) return false;
159 |
160 | QString artist = music->getArtist();
161 | QString album = music->getAlbum();
162 | QString title = music->getTitle();
163 | QString path = music->getFileUrl().path();
164 | unsigned int trackNumber = music->getTrackNumber();
165 | unsigned int lastModified = QFileInfo(path).lastModified().toTime_t();
166 |
167 | if (!db.isOpen()) {
168 | if (!db.open()) qDebug("ERROR! " + db.lastError().text().toUtf8());
169 | return false;
170 | }
171 |
172 | QSqlQuery query;
173 | query.prepare("SELECT count(*) FROM music WHERE path=?");
174 | query.bindValue(":path", path);
175 | query.exec();
176 | query.first();
177 | unsigned int total = query.value(0).toUInt();
178 | if (total == 0) {
179 | /*
180 | * Assert that artist and album exists.
181 | * addAlbum() asserts artist existence.
182 | *
183 | */
184 | addAlbum(artist, album);
185 |
186 | QSqlQuery insertQuery(db);
187 | insertQuery.prepare(
188 | "INSERT INTO music (id_album, track_number, title, path, last_modified) VALUES ("
189 | "(SELECT id FROM album WHERE title=:album AND id_artist = (SELECT id FROM artist WHERE name = :artist)),"
190 | ":tracknumber, :title, :path, :lastmodified);"
191 | );
192 | insertQuery.bindValue(":album", album);
193 | insertQuery.bindValue(":artist", artist);
194 | insertQuery.bindValue(":tracknumber", trackNumber);
195 | insertQuery.bindValue(":title", title);
196 | insertQuery.bindValue(":path", path);
197 | insertQuery.bindValue(":lastmodified", lastModified);
198 | insertQuery.exec();
199 |
200 | if (db.lastError().type() != QSqlError::NoError) {
201 | qDebug("ERROR! " + db.lastError().text().toUtf8());
202 | qDebug(insertQuery.executedQuery().toUtf8());
203 | return false;
204 | }
205 | return true;
206 |
207 | }
208 | else {
209 | // If the file was updated, we must update the database.
210 | query.prepare("SELECT last_modified FROM music WHERE path=:path");
211 | query.bindValue(":path", path);
212 | query.exec();
213 | query.first();
214 | unsigned int lastModifiedInDb = query.value(0).toUInt();
215 | if (lastModified > lastModifiedInDb) {
216 | QSqlQuery updateQuery(db);
217 | updateQuery.prepare(
218 | "UPDATE music SET "
219 | "track_number = :tracknumber, "
220 | "title = :title, "
221 | "id_album = (SELECT id FROM album WHERE title=:album AND id_artist = (SELECT id FROM artist WHERE name = :artist)), "
222 | "last_modified = :lastmodified "
223 | "WHERE path = :path"
224 | );
225 | updateQuery.bindValue(":album", album);
226 | updateQuery.bindValue(":artist", artist);
227 | updateQuery.bindValue(":tracknumber", trackNumber);
228 | updateQuery.bindValue(":title", title);
229 | updateQuery.bindValue(":path", path);
230 | updateQuery.bindValue(":lastmodified", lastModified);
231 | updateQuery.exec();
232 |
233 | if (db.lastError().type() != QSqlError::NoError) {
234 | qDebug("ERROR! " + db.lastError().text().toUtf8());
235 | qDebug(path.toUtf8());
236 | return false;
237 | }
238 | return true;
239 | }
240 | }
241 | return false;
242 | }
243 |
244 |
245 | void CollectionDatabase::removeArtist(QString artistName) {
246 | if (!db.open()) {
247 | qDebug("ERROR! " + db.lastError().text().toUtf8());
248 | return;
249 | }
250 | db.exec("DELETE FROM artist WHERE name='" + artistName + "'");
251 | }
252 |
253 | void CollectionDatabase::removeAlbum(QString artistName, QString albumName) {
254 | if (!db.open()) {
255 | qDebug("ERROR! " + db.lastError().text().toUtf8());
256 | return;
257 | }
258 | db.exec("DELETE FROM album WHERE name='" + albumName + "' AND id_artist=(SELECT id FROM artist WHERE name='" + artistName + "')");
259 | }
260 |
261 | void CollectionDatabase::removeMusic(QString path) {
262 | if (!db.open()) {
263 | qDebug("ERROR! " + db.lastError().text().toUtf8());
264 | return;
265 | }
266 | db.exec("DELETE FROM music WHERE path='" + path + "'");
267 | }
268 |
269 | void CollectionDatabase::removeMusic(Music *music) {
270 | removeMusic(music->getFileUrl().path());
271 | }
272 |
--------------------------------------------------------------------------------
/icons/gnome/index.theme:
--------------------------------------------------------------------------------
1 | [Icon Theme]
2 | Name=GNOME
3 | Name[af]=GNOME
4 | Name[ar]=جنوم
5 | Name[as]=GNOME
6 | Name[ast]=GNOME
7 | Name[az]=GNOME
8 | Name[be]=GNOME
9 | Name[be@latin]=GNOME
10 | Name[bg]=GNOME
11 | Name[bn]=GNOME
12 | Name[bn_IN]=GNOME
13 | Name[br]=GNOME
14 | Name[bs]=GNOME
15 | Name[ca]=GNOME
16 | Name[ca@valencia]=GNOME
17 | Name[crh]=GNOME
18 | Name[cs]=GNOME
19 | Name[cy]=GNOME
20 | Name[da]=Gnome
21 | Name[de]=GNOME
22 | Name[dz]=ཇི་ནོམ།
23 | Name[el]=GNOME
24 | Name[en@shaw]=·𐑜𐑯𐑴𐑥
25 | Name[en_CA]=GNOME
26 | Name[en_GB]=GNOME
27 | Name[es]=GNOME
28 | Name[et]=GNOME
29 | Name[eu]=GNOME
30 | Name[fa]=گنوم
31 | Name[fi]=GNOME
32 | Name[fr]=GNOME
33 | Name[fur]=GNOME
34 | Name[ga]=GNOME
35 | Name[gl]=GNOME
36 | Name[gu]=જીનોમ
37 | Name[he]=GNOME
38 | Name[hi]=गनोम
39 | Name[hr]=GNOME
40 | Name[hu]=GNOME
41 | Name[id]=GNOME
42 | Name[is]=GNOME
43 | Name[it]=GNOME
44 | Name[ja]=GNOME
45 | Name[ka]=გნომი
46 | Name[kk]=GNOME ортасы
47 | Name[kn]=GNOME
48 | Name[ko]=그놈
49 | Name[lt]=GNOME
50 | Name[lv]=GNOME
51 | Name[mai]=गनोम
52 | Name[mg]=GNOME
53 | Name[mk]=GNOME
54 | Name[ml]=ഗ്നോം
55 | Name[mn]=ГНОМЕ
56 | Name[mr]=GNOME
57 | Name[ms]=GNOME
58 | Name[nb]=GNOME
59 | Name[nds]=GNOME
60 | Name[ne]=जिनोम
61 | Name[nl]=GNOME
62 | Name[nn]=GNOME
63 | Name[oc]=GNOME
64 | Name[or]=GNOME
65 | Name[pa]=ਗਨੋਮ
66 | Name[pl]=GNOME
67 | Name[ps]=جنومي
68 | Name[pt]=GNOME
69 | Name[pt_BR]=GNOME
70 | Name[ro]=GNOME
71 | Name[ru]=Среда GNOME
72 | Name[si]=GNOME
73 | Name[sk]=GNOME
74 | Name[sl]=GNOME
75 | Name[sq]=GNOME
76 | Name[sr]=Гном
77 | Name[sr@latin]=Gnom
78 | Name[sv]=GNOME
79 | Name[ta]=GNOME
80 | Name[te]=గ్నోమ్
81 | Name[th]=GNOME
82 | Name[tk]=GNOME
83 | Name[tr]=GNOME
84 | Name[uk]=GNOME
85 | Name[uz]=GNOME
86 | Name[uz@cyrillic]=GNOME
87 | Name[vi]=GNOME
88 | Name[xh]=i-GNOME
89 | Name[zh_CN]=GNOME
90 | Name[zh_HK]=GNOME
91 | Name[zh_TW]=GNOME
92 | Comment=Default GNOME Theme
93 | Comment[af]=Verstek-GNOME-tema
94 | Comment[ar]=سِمة جنوم الإفتراضية
95 | Comment[as]=অবিকল্পিত GNOME থিম
96 | Comment[ast]=Tema predetermináu de Gnome
97 | Comment[az]=Ön Qurğulu Gnome Örtüyü
98 | Comment[be]=Прадвызначаная тэма GNOME
99 | Comment[be@latin]=Zmoŭčany matyŭ GNOME
100 | Comment[bg]=Стандартна тема на GNOME
101 | Comment[bn]=ডিফল্ট GNOME থীম
102 | Comment[bn_IN]=ডিফল্ট GNOME থিম
103 | Comment[br]=Neuz GNOME dre-ziouer
104 | Comment[bs]=Uobičajena GNOME Tema
105 | Comment[ca]=Tema del GNOME per defecte
106 | Comment[ca@valencia]=Tema del GNOME per defecte
107 | Comment[crh]=Ög-belgilengen GNOME Teması
108 | Comment[cs]=Výchozí motiv GNOME
109 | Comment[cy]=Thema diofyn GNOME
110 | Comment[da]=Standardtema til Gnome
111 | Comment[de]=GNOME-Vorgabethema
112 | Comment[dz]=སྔོན་སྒྲིག་ཇི་ནོམ་བརྗོད་དོན།
113 | Comment[el]=Εξ ορισμού θέμα του Gnome
114 | Comment[en@shaw]=𐑛𐑦𐑓𐑷𐑤𐑑 ·𐑜𐑯𐑴𐑥 𐑔𐑰𐑥
115 | Comment[en_CA]=Default GNOME Theme
116 | Comment[en_GB]=Default GNOME Theme
117 | Comment[es]=Tema predeterminado de GNOME
118 | Comment[et]=Gnome'i vaiketeema
119 | Comment[eu]=Lehenetsitako GNOMEren gaia
120 | Comment[fa]=تم پیشفرض گنوم
121 | Comment[fi]=Gnomen oletusteema
122 | Comment[fr]=Thème GNOME par défaut
123 | Comment[fur]=Teme predefinît di GNOME
124 | Comment[ga]=Téama GNOME Réamhshocraithe
125 | Comment[gl]=Tema predefinido do GNOME
126 | Comment[gu]=મૂળભૂત જીનોમ થીમ
127 | Comment[he]=ערכת הנושא ברירת המחדל של Gnome
128 | Comment[hi]=डिफ़ॉल्ट गनोम प्रसंग
129 | Comment[hr]=Uobičajena tema GNOME sustava
130 | Comment[hu]=Alapértelmezett GNOME-téma
131 | Comment[id]=Tema GNOME Standar
132 | Comment[is]=Sjálfgefið GNOME Þema
133 | Comment[it]=Tema predefinito di GNOME
134 | Comment[ja]=デフォルトの GNOME テーマ
135 | Comment[ka]=ნაგულისხმევი გნომის გაფორმება
136 | Comment[kk]=GNOME ортасының стандартты тақырыбы
137 | Comment[kn]=ಪೂರ್ವನಿಯೋಜಿತ GNOME ಥೀಮ್
138 | Comment[ko]=기본 그놈 테마
139 | Comment[lt]=Standartinė GNOME tema
140 | Comment[lv]=Noklusētā Gnome tēma
141 | Comment[mai]=मूलभूत गनोम प्रसंग
142 | Comment[mg]=Endrika GNOME lasitra
143 | Comment[mk]=Основна тема на GNOME
144 | Comment[ml]=ഗ്നോമിലെ സഹജമായ രംഗവിധാനം
145 | Comment[mn]=ГНОМЕ-стандарт загварууд
146 | Comment[mr]=मुलभूत GNOME थीम
147 | Comment[ms]=Tema GNOME Default
148 | Comment[nb]=Forvalgt GNOME tema
149 | Comment[nds]=Standard GNOME Utsehn
150 | Comment[ne]=पूर्वनिर्धारित जिनोम विषयवस्तु
151 | Comment[nl]=Standaard GNOME-thema
152 | Comment[nn]=Vanleg GNOME-drakt
153 | Comment[oc]=Tèma per defaut de GNOME
154 | Comment[or]=ପୂର୍ବ ନିର୍ଦ୍ଧାରିତ GNOME ପ୍ରସଙ୍ଗ
155 | Comment[pa]=ਡਿਫਾਲਟ ਗਨੋਮ ਥੀਮ
156 | Comment[pl]=Domyślny motyw GNOME
157 | Comment[ps]=د جنومي تلواله ويينه
158 | Comment[pt]=Tema GNOME por Omissão
159 | Comment[pt_BR]=Tema Padrão do GNOME
160 | Comment[ro]=Temă implicită GNOME
161 | Comment[ru]=Стандартная тема среды GNOME
162 | Comment[si]=සාමාන්ය GNOME තේමාව
163 | Comment[sk]=Štandardná téma GNOME
164 | Comment[sl]=Privzeta tema GNOME
165 | Comment[sq]=Tema e prezgjedhur e GNOME
166 | Comment[sr]=Подразумевана тема Гнома
167 | Comment[sr@latin]=Podrazumevana tema Gnoma
168 | Comment[sv]=Standardtema för GNOME
169 | Comment[ta]=முன்னிருப்பு GNOME பொருள்
170 | Comment[te]=అప్రమెయ గ్నొమ్ వైవిద్యాంశం
171 | Comment[th]=ชุดตกแต่งปริยายของ Gnome
172 | Comment[tk]=GNOMEniň oň bellenen temi
173 | Comment[tr]=Öntanımlı GNOME Teması
174 | Comment[uk]=Типова тема середовища GNOME
175 | Comment[uz]=Andoza GNOME mavzusi
176 | Comment[uz@cyrillic]=Андоза GNOME мавзуси
177 | Comment[vi]=Sắc thái GNOME Mặc Định
178 | Comment[xh]=Misela Umxholo we GNOME
179 | Comment[zh_CN]=GNOME 默认主题
180 | Comment[zh_HK]=預設 GNOME 佈景主題
181 | Comment[zh_TW]=預設 GNOME 佈景主題
182 | Example=start-here
183 |
184 | # KDE Specific Stuff
185 | DisplayDepth=32
186 | LinkOverlay=link_overlay
187 | LockOverlay=lock_overlay
188 | ZipOverlay=zip_overlay
189 | DesktopDefault=48
190 | DesktopSizes=16,22,32,48,64,72,96,128
191 | ToolbarDefault=22
192 | ToolbarSizes=16,22,32,48
193 | MainToolbarDefault=22
194 | MainToolbarSizes=16,22,32,48
195 | SmallDefault=16
196 | SmallSizes=16
197 | PanelDefault=32
198 | PanelSizes=16,22,32,48,64,72,96,128
199 |
200 | # Directory list
201 | Directories=8x8/emblems,16x16/actions,16x16/animations,16x16/apps,16x16/categories,16x16/devices,16x16/emblems,16x16/emotes,16x16/mimetypes,16x16/places,16x16/status,22x22/actions,22x22/animations,22x22/apps,22x22/categories,22x22/devices,22x22/emblems,22x22/emotes,22x22/mimetypes,22x22/places,22x22/status,24x24/actions,24x24/apps,24x24/categories,24x24/devices,24x24/emblems,24x24/emotes,24x24/mimetypes,24x24/places,24x24/status,32x32/actions,32x32/animations,32x32/apps,32x32/categories,32x32/devices,32x32/emblems,32x32/emotes,32x32/mimetypes,32x32/places,32x32/status,48x48/actions,48x48/animations,48x48/apps,48x48/categories,48x48/devices,48x48/emblems,48x48/emotes,48x48/mimetypes,48x48/places,48x48/status,256x256/actions,256x256/apps,256x256/categories,256x256/devices,256x256/emblems,256x256/emotes,256x256/mimetypes,256x256/places,256x256/status
202 |
203 | [8x8/emblems]
204 | Context=Emblems
205 | Size=8
206 | Type=Fixed
207 |
208 | [16x16/actions]
209 | Context=Actions
210 | Size=16
211 | Type=Fixed
212 |
213 | [16x16/animations]
214 | Context=Animations
215 | Size=16
216 | Type=Fixed
217 |
218 | [16x16/apps]
219 | Context=Applications
220 | Size=16
221 | Type=Fixed
222 |
223 | [16x16/categories]
224 | Context=Categories
225 | Size=16
226 | Type=Fixed
227 |
228 | [16x16/devices]
229 | Context=Devices
230 | Size=16
231 | Type=Fixed
232 |
233 | [16x16/emblems]
234 | Context=Emblems
235 | Size=16
236 | Type=Fixed
237 |
238 | [16x16/emotes]
239 | Context=Emotes
240 | Size=16
241 | Type=Fixed
242 |
243 | [16x16/mimetypes]
244 | Context=MimeTypes
245 | Size=16
246 | Type=Fixed
247 |
248 | [16x16/places]
249 | Context=Places
250 | Size=16
251 | Type=Fixed
252 |
253 | [16x16/status]
254 | Context=Status
255 | Size=16
256 | Type=Fixed
257 |
258 | [22x22/actions]
259 | Context=Actions
260 | Size=22
261 | Type=Fixed
262 |
263 | [22x22/animations]
264 | Context=Animations
265 | Size=22
266 | Type=Fixed
267 |
268 | [22x22/apps]
269 | Context=Applications
270 | Size=22
271 | Type=Fixed
272 |
273 | [22x22/categories]
274 | Context=Categories
275 | Size=22
276 | Type=Fixed
277 |
278 | [22x22/devices]
279 | Context=Devices
280 | Size=22
281 | Type=Fixed
282 |
283 | [22x22/emblems]
284 | Context=Emblems
285 | Size=22
286 | Type=Fixed
287 |
288 | [22x22/emotes]
289 | Context=Emotes
290 | Size=22
291 | Type=Fixed
292 |
293 | [22x22/mimetypes]
294 | Context=MimeTypes
295 | Size=22
296 | Type=Fixed
297 |
298 | [22x22/places]
299 | Context=Places
300 | Size=22
301 | Type=Fixed
302 |
303 | [22x22/status]
304 | Context=Status
305 | Size=22
306 | Type=Fixed
307 |
308 | [24x24/actions]
309 | Context=Actions
310 | Size=24
311 | Type=Fixed
312 |
313 | [24x24/apps]
314 | Context=Applications
315 | Size=24
316 | Type=Fixed
317 |
318 | [24x24/categories]
319 | Context=Categories
320 | Size=24
321 | Type=Fixed
322 |
323 | [24x24/devices]
324 | Context=Devices
325 | Size=24
326 | Type=Fixed
327 |
328 | [24x24/emblems]
329 | Context=Emblems
330 | Size=24
331 | Type=Fixed
332 |
333 | [24x24/emotes]
334 | Context=Emotes
335 | Size=24
336 | Type=Fixed
337 |
338 | [24x24/mimetypes]
339 | Context=MimeTypes
340 | Size=24
341 | Type=Fixed
342 |
343 | [24x24/places]
344 | Context=Places
345 | Size=24
346 | Type=Fixed
347 |
348 | [24x24/status]
349 | Context=Status
350 | Size=24
351 | Type=Fixed
352 |
353 | [32x32/actions]
354 | Context=Actions
355 | Size=32
356 | Type=Fixed
357 |
358 | [32x32/animations]
359 | Context=Animations
360 | Size=32
361 | Type=Fixed
362 |
363 | [32x32/apps]
364 | Context=Applications
365 | Size=32
366 | Type=Fixed
367 |
368 | [32x32/categories]
369 | Context=Categories
370 | Size=32
371 | Type=Fixed
372 |
373 | [32x32/devices]
374 | Context=Devices
375 | Size=32
376 | Type=Fixed
377 |
378 | [32x32/emblems]
379 | Context=Emblems
380 | Size=32
381 | Type=Fixed
382 |
383 | [32x32/emotes]
384 | Context=Emotes
385 | Size=32
386 | Type=Fixed
387 |
388 | [32x32/mimetypes]
389 | Context=MimeTypes
390 | Size=32
391 | Type=Fixed
392 |
393 | [32x32/places]
394 | Context=Places
395 | Size=32
396 | Type=Fixed
397 |
398 | [32x32/status]
399 | Context=Status
400 | Size=32
401 | Type=Fixed
402 |
403 | [48x48/actions]
404 | Context=Actions
405 | Size=48
406 | Type=Fixed
407 |
408 | [48x48/animations]
409 | Context=Animations
410 | Size=48
411 | Type=Fixed
412 |
413 | [48x48/apps]
414 | Context=Applications
415 | Size=48
416 | Type=Fixed
417 |
418 | [48x48/categories]
419 | Context=Categories
420 | Size=48
421 | Type=Fixed
422 |
423 | [48x48/devices]
424 | Context=Devices
425 | Size=48
426 | Type=Fixed
427 |
428 | [48x48/emblems]
429 | Context=Emblems
430 | Size=48
431 | Type=Fixed
432 |
433 | [48x48/emotes]
434 | Context=Emotes
435 | Size=48
436 | Type=Fixed
437 |
438 | [48x48/mimetypes]
439 | Context=MimeTypes
440 | Size=48
441 | Type=Fixed
442 |
443 | [48x48/places]
444 | Context=Places
445 | Size=48
446 | Type=Fixed
447 |
448 | [48x48/status]
449 | Context=Status
450 | Size=48
451 | Type=Fixed
452 |
453 | [256x256/actions]
454 | Context=Actions
455 | Size=256
456 | MinSize=56
457 | MaxSize=512
458 | Type=Scalable
459 |
460 | [256x256/apps]
461 | Context=Applications
462 | Size=256
463 | MinSize=56
464 | MaxSize=512
465 | Type=Scalable
466 |
467 | [256x256/categories]
468 | Context=Categories
469 | Size=256
470 | MinSize=56
471 | MaxSize=512
472 | Type=Scalable
473 |
474 | [256x256/devices]
475 | Context=Devices
476 | Size=256
477 | MinSize=56
478 | MaxSize=512
479 | Type=Scalable
480 |
481 | [256x256/emblems]
482 | Context=Emblems
483 | Size=256
484 | MinSize=56
485 | MaxSize=512
486 | Type=Scalable
487 |
488 | [256x256/emotes]
489 | Context=Emotes
490 | Size=256
491 | MinSize=56
492 | MaxSize=512
493 | Type=Scalable
494 |
495 | [256x256/mimetypes]
496 | Context=MimeTypes
497 | Size=256
498 | MinSize=56
499 | MaxSize=512
500 | Type=Scalable
501 |
502 | [256x256/places]
503 | Context=Places
504 | Size=256
505 | MinSize=56
506 | MaxSize=512
507 | Type=Scalable
508 |
509 | [256x256/status]
510 | Context=Status
511 | Size=256
512 | MinSize=56
513 | MaxSize=512
514 | Type=Scalable
515 |
516 |
--------------------------------------------------------------------------------
/src/mainwindow/playlist/playlistwidget.cpp:
--------------------------------------------------------------------------------
1 | #include "playlistwidget.h"
2 | #include
3 | #include
4 |
5 | /*
6 | * TODO:
7 | * - Fix columns size
8 | * - more columns, like rating
9 | */
10 |
11 | PlaylistWidget::PlaylistWidget(QWidget *parent, Phonon::MediaObject *mediaObject) : QTreeWidget(parent)
12 | {
13 | setObjectName("PlaylistWidget");
14 | setColumnCount(4);
15 |
16 | currentSong = NULL;
17 | nextSong = NULL;
18 |
19 | setSelectionMode(QAbstractItemView::ExtendedSelection);
20 |
21 | // Enable drag and drop
22 | setAcceptDrops(true);
23 | setDragEnabled(true);
24 | setDropIndicatorShown(true);
25 |
26 | // Alternating row colors are good
27 | setAlternatingRowColors(true);
28 |
29 | /*
30 | * Connect our local media object to application's mediaObject
31 | * It would be nice to have a global access to it... How could we do that?
32 | */
33 | mainMediaObject = mediaObject;
34 |
35 | // Define titles for labels
36 | QStringList playlistWidgetHeaders;
37 | playlistWidgetHeaders.append("Track");
38 | playlistWidgetHeaders.append("Title");
39 | playlistWidgetHeaders.append("Album");
40 | playlistWidgetHeaders.append("Artist");
41 | playlistWidgetHeaders.append("Duration");
42 | setHeaderLabels(playlistWidgetHeaders);
43 |
44 | // Set alignment for the columns' headers
45 | QHeaderView *headers = header();
46 | headers->setDefaultAlignment(Qt::AlignCenter);
47 | headers->setSortIndicatorShown(true);
48 |
49 | // Assign signals to add items or Phonon controls
50 | connect(this, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), this, SLOT(playSong(QTreeWidgetItem*)));
51 | connect(mainMediaObject, SIGNAL(aboutToFinish()), this, SLOT(enqueueNextSong()));
52 | connect(mainMediaObject, SIGNAL(finished()), this, SLOT(removeBold()));
53 | connect(mainMediaObject, SIGNAL(stateChanged(Phonon::State,Phonon::State)), this, SLOT(handleStateChange(Phonon::State)));
54 | connect(mainMediaObject, SIGNAL(currentSourceChanged(Phonon::MediaSource)), this, SLOT(fileChanged()));
55 | }
56 |
57 | void PlaylistWidget::playSong(QTreeWidgetItem *doubleClickedItem) {
58 | PlaylistItem *item = (PlaylistItem*) doubleClickedItem;
59 | if (currentSong != NULL) {
60 | currentSong->removeBold();
61 | }
62 | currentSong = item;
63 | item->setBold();
64 | mainMediaObject->setCurrentSource(item->getMusic()->getFileUrl());
65 | mainMediaObject->play();
66 | emitSongInformationUpdated();
67 | }
68 |
69 | void PlaylistWidget::playPreviousSong() {
70 | QModelIndex index = indexAbove(indexFromItem(currentSong));
71 | if (index.isValid()) {
72 | playSong(itemFromIndex(index));
73 | }
74 | }
75 |
76 | void PlaylistWidget::playNextSong() {
77 | QModelIndex index = indexBelow(indexFromItem(currentSong));
78 | if (index.isValid()) {
79 | playSong(itemFromIndex(index));
80 | }
81 | }
82 |
83 | void PlaylistWidget::enqueueNextSong() {
84 | nextSong = (PlaylistItem*)itemBelow(currentSong);
85 | if (nextSong != NULL) {
86 | mainMediaObject->enqueue(nextSong->getMusic()->getFileUrl());
87 | }
88 | }
89 |
90 | void PlaylistWidget::removeBold() {
91 | if (currentSong != NULL) {
92 | currentSong->removeBold();
93 | }
94 | }
95 |
96 | void PlaylistWidget::fileChanged() {
97 | if (nextSong != NULL) {
98 | currentSong->removeBold();
99 | currentSong = nextSong;
100 | nextSong = NULL;
101 | }
102 |
103 | }
104 |
105 | void PlaylistWidget::handleStateChange(Phonon::State newState) {
106 | if (newState > Phonon::StoppedState) {
107 | currentSong->setBold();
108 | emitSongInformationUpdated();
109 | }
110 | else {
111 | if (currentSong != NULL) {
112 | currentSong->removeBold();
113 | }
114 | }
115 | }
116 |
117 | void PlaylistWidget::emitSongInformationUpdated() {
118 | QMap songInfo;
119 | songInfo.insert("artist", currentSong->getMusic()->getArtist());
120 | songInfo.insert("title", currentSong->getMusic()->getTitle());
121 | emit songInformationUpdated(songInfo);
122 | }
123 |
124 | void PlaylistWidget::addSong(PlaylistItem *newItem, int index) {
125 | newItem->index = index;
126 |
127 | if (newItem->isValid()) {
128 | insertValidItem(newItem);
129 | }
130 | else {
131 | delete newItem;
132 | }
133 | }
134 |
135 | void PlaylistWidget::addSong(QUrl url, int index) {
136 | PlaylistItem *newItem = new PlaylistItem(url);
137 | addSong(newItem, index);
138 | }
139 |
140 | void PlaylistWidget::addSong(QList urlList) {
141 | foreach (QUrl url, urlList) {
142 | addSong(url);
143 | }
144 | }
145 |
146 | void PlaylistWidget::insertValidItem(PlaylistItem *newItem) {
147 | if (mainMediaObject->currentSource().type() == Phonon::MediaSource::Empty) {
148 | currentSong = newItem;
149 | mainMediaObject->enqueue(newItem->getMusic()->getFileUrl());
150 | }
151 |
152 | int index = newItem->index;
153 | if (index == -1) {
154 | addTopLevelItem(newItem);
155 | }
156 | else {
157 | insertTopLevelItem(index, newItem);
158 | }
159 | }
160 |
161 | void PlaylistWidget::deleteInvalidItem(PlaylistItem *invalidItem) {
162 | delete invalidItem;
163 | }
164 |
165 | void PlaylistWidget::addFolder(QUrl url, int &index) {
166 | // You shouldn't call this to add files, man.
167 | if (QFileInfo(url.path()).isFile()) return;
168 |
169 | QDir directory(url.path());
170 | foreach (QString fileEntry, directory.entryList(directory.AllEntries | directory.NoDotAndDotDot, directory.DirsFirst | directory.Name)) {
171 | // Change file entry to a full path
172 | fileEntry = directory.absolutePath() + directory.separator() + fileEntry;
173 | if (QFileInfo(fileEntry).isDir()) {
174 | addFolder(QUrl(fileEntry), index);
175 | }
176 | else if (QFileInfo(fileEntry).isFile()) {
177 | addSong(QUrl(fileEntry), index);
178 |
179 | /*
180 | * If index is -1, we must append all songs to the end, so there's no need
181 | * to change indexes. But if its >= 0, we must sum +1 to it in order to put
182 | * songs in the right place.
183 | */
184 | if (index != -1) index++;
185 | }
186 | }
187 | }
188 |
189 | void PlaylistWidget::dragEnterEvent(QDragEnterEvent *event) {
190 | if (event->mimeData()->hasFormat("text/uri-list")) {
191 | event->accept();
192 | }
193 | else {
194 | event->ignore();
195 | }
196 | }
197 |
198 | void PlaylistWidget::dragMoveEvent(QDragMoveEvent *event)
199 | {
200 | if (event->mimeData()->hasFormat("text/uri-list")) {
201 | event->accept();
202 | }
203 | }
204 |
205 |
206 | void PlaylistWidget::mouseMoveEvent(QMouseEvent *event)
207 | {
208 | // if not left button - return
209 | if (!(event->buttons() & Qt::LeftButton)) return;
210 |
211 | // if no item selected, return (else it would crash)
212 | if (currentItem() == NULL) return;
213 |
214 | drag = new QDrag(this);
215 | connect(drag, SIGNAL(actionChanged(Qt::DropAction)), this, SLOT(dndActionChanged(Qt::DropAction)));
216 | QMimeData *mimeData = new QMimeData;
217 |
218 | // construct list of QUrls
219 | // other widgets accept this mime type, we can drop to them
220 | QList list;
221 |
222 | foreach (QTreeWidgetItem *currentItem, selectedItems()) {
223 | PlaylistItem *playlistCurrentItem = static_cast(currentItem);
224 | list.append(playlistCurrentItem->getMusic()->getFileUrl());
225 | }
226 |
227 | // mime stuff
228 | mimeData->setUrls(list);
229 | drag->setMimeData(mimeData);
230 |
231 | // start drag
232 | QList itemsToRemove = selectedItems();
233 | drag->exec(Qt::CopyAction | Qt::MoveAction);
234 | }
235 |
236 |
237 | void PlaylistWidget::dropEvent(QDropEvent *event) {
238 | if (event->mimeData()->hasFormat("text/uri-list")) {
239 | /*
240 | * If the dropped objects come from outside, we parse them as
241 | * a list of files, if they are a list of files.
242 | */
243 |
244 | // TODO: change mouse cursor
245 |
246 | if (event->source() != this) {
247 | QList urlList = event->mimeData()->urls();
248 | int indexToInsert = indexOfTopLevelItem(itemAt(event->pos()));
249 | foreach (QUrl url, urlList) {
250 | if (!QFileInfo(url.path()).exists()) {
251 | qDebug("FILE DO NOT EXISTS");
252 | // TODO: emit error;
253 | continue;
254 | }
255 |
256 | // If it's not a dir, add it using addSong
257 | if (QFileInfo(url.path()).isFile()) {
258 | addSong(url, indexToInsert);
259 | }
260 | // If it's a folder, add it recursively
261 | else if (QFileInfo(url.path()).isDir()) {
262 | addFolder(url, indexToInsert);
263 | }
264 | }
265 | }
266 |
267 | /*
268 | * If the dropped objects come from the playlist widget,
269 | * so we are moving the items from list.
270 | */
271 | else {
272 | QList itemsToInsert;
273 |
274 | // Create the list of items to add...
275 | foreach (QTreeWidgetItem *currentItem, selectedItems()) {
276 | QTreeWidgetItem *playlistCurrentItem = takeTopLevelItem(indexOfTopLevelItem(currentItem));
277 | itemsToInsert.append(playlistCurrentItem);
278 | }
279 |
280 | // ...and insert them
281 | if (itemsToInsert.size() > 0) {
282 | int indexToInsert = indexOfTopLevelItem(itemAt(event->pos()));
283 | /*
284 | * If the index the cursor is over is -1, there's no item under
285 | * the cursor, so we must add the selected items to the end of
286 | * the list.
287 | */
288 | if (indexToInsert != -1) { insertTopLevelItems(indexToInsert, itemsToInsert); }
289 | else { addTopLevelItems(itemsToInsert); }
290 | }
291 | }
292 |
293 | }
294 |
295 | else {
296 | QTreeWidget::dropEvent(event);
297 | }
298 |
299 |
300 |
301 | }
302 |
303 | Qt::DropActions PlaylistWidget::supportedDropActions() const
304 | {
305 | return Qt::CopyAction | Qt::MoveAction;
306 | }
307 |
308 |
309 | void PlaylistWidget::dndActionChanged(Qt::DropAction newAction) {
310 | dndAction = newAction;
311 | if (newAction == Qt::MoveAction) {
312 | qDebug("Move action");
313 | }
314 | else if (newAction == Qt::CopyAction) {
315 | qDebug("Copy action");
316 | }
317 | }
318 |
319 | void PlaylistWidget::removeSelectedItems() {
320 | if (selectedItems().count() > 0) {
321 | foreach(QTreeWidgetItem *item, selectedItems()) {
322 | // Avoid wrong references
323 | if (currentSong == item) { currentSong = NULL; }
324 | else if (nextSong == item) {
325 | if (itemBelow(nextSong) != NULL) nextSong = (PlaylistItem*)itemBelow(nextSong);
326 | else nextSong = NULL;
327 | }
328 | delete item;
329 | }
330 | }
331 | }
332 |
333 | void PlaylistWidget::selectAll() {
334 | int total = topLevelItemCount();
335 | for (int i = 0; i < total; i++) {
336 | QTreeWidgetItem *item = topLevelItem(i);
337 | item->setSelected(true);
338 | }
339 | }
340 |
341 |
342 | /*
343 | * Add a context menu to playlist
344 | */
345 | void PlaylistWidget::contextMenuEvent(QContextMenuEvent *event) {
346 | QMenu menu(this);
347 | menu.addAction("&Select all", this, SLOT(selectAll()), QKeySequence::SelectAll);
348 | menu.addAction("&Remove selected songs", this, SLOT(removeSelectedItems()), QKeySequence::Delete);
349 | menu.exec(event->globalPos());
350 |
351 | }
352 |
353 | /*
354 | *
355 | * Handle keyboard shortcuts to playlist widget.
356 | *
357 | * QKeySequence::Delete = Removes selected items.
358 | * QKeySequence::SelectAll = Select all
359 | *
360 | * Qt will take the responsabily to assign the key sequences
361 | * to their correspondent keyboard shorcuts, like Ctrl+A or Delete/Backspace
362 | *
363 | */
364 | void PlaylistWidget::keyPressEvent(QKeyEvent *event) {
365 | if (event->matches(QKeySequence::Delete)) {
366 | event->accept();
367 | removeSelectedItems();
368 | }
369 | else if (event->matches(QKeySequence::SelectAll)) {
370 | event->accept();
371 | selectAll();
372 | }
373 | else {
374 | event->ignore();
375 | }
376 | }
377 |
--------------------------------------------------------------------------------
/src/mainwindow/mainnotebook/collection/collectiontreewidget.cpp:
--------------------------------------------------------------------------------
1 | #include "collectiontreewidget.h"
2 | #include
3 | #include
4 |
5 | // TODO: cleanup
6 |
7 | /// @brief Constructor
8 | CollectionTreeWidget::CollectionTreeWidget()
9 | {
10 | setColumnCount(1);
11 | header()->hide(); // hide headers
12 | setDragEnabled(true);
13 | setAcceptDrops(true);
14 | setSelectionMode(QAbstractItemView::ExtendedSelection);
15 |
16 | service = new CollectionService();
17 |
18 | // Add songs that currently exist on database
19 | QSqlTableModel *artistModel = service->artistModel();
20 | artistModel->select();
21 |
22 | // TODO: verify if we can put fetchmore() inside the for loop.
23 | // TODO: put this task in background... URGENT
24 | while (artistModel->canFetchMore()) artistModel->fetchMore();
25 | int total = artistModel->rowCount();
26 |
27 | for (int i = 0; i < total; i++) {
28 | QString artist = artistModel->record(i).value(artistModel->fieldIndex("name")).toString();
29 | unsigned int id = artistModel->record(i).value(artistModel->fieldIndex("id")).toInt();
30 | addArtist(artist, id);
31 | }
32 | delete artistModel;
33 |
34 | /*
35 | * TODO: modify the slots in order to add only the artist, not the music.
36 | * The album and song must be shown only if the node is already expanded.
37 | */
38 | connect(service, SIGNAL(songAdded(Music*)), this, SLOT(addMusic(Music*)));
39 | connect(service, SIGNAL(songRemoved(unsigned int)), this, SLOT(removeMusic(uint)));
40 | connect(this, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(doubleClickAt(QModelIndex)));
41 | connect(this, SIGNAL(expanded(QModelIndex)), this, SLOT(showChildrenOf(QModelIndex)));
42 |
43 | /*
44 | * We shall emit those signals to show or hide the widget that will
45 | * give the feedback that the collection is being scanned.
46 | */
47 | connect(service, SIGNAL(scanning()), this, SIGNAL(scanning()));
48 | connect(service, SIGNAL(listUpdated()), this, SIGNAL(listUpdated()));
49 |
50 | // Start service to find new songs and remove the inexistent ones
51 | service->start(QThread::LowestPriority);
52 | }
53 |
54 | QStringList CollectionTreeWidget::toColumns(QString string) {
55 | QStringList columns;
56 | columns.append(string);
57 | columns.append(QString::number(topLevelItemCount() + 1));
58 | return columns;
59 | }
60 |
61 | void CollectionTreeWidget::showChildrenOf(QModelIndex index) {
62 | CollectionTreeWidgetItem *item = (CollectionTreeWidgetItem*)itemFromIndex(index);
63 |
64 | // If the item pressed was an artist, add albums
65 | if (item->getNodeLevel() == LevelArtist) {
66 | QString artist = item->text(0).toUtf8();
67 |
68 | // Looks for artist id
69 | QString artistId = QString::number(item->getId());
70 |
71 | // Looks for artist albums
72 | QSqlTableModel *albumModel = service->albumModel();
73 | albumModel->setFilter("id_artist = " + artistId);
74 | albumModel->select();
75 |
76 | while (albumModel->canFetchMore()) albumModel->fetchMore();
77 | int total = albumModel->rowCount();
78 |
79 | for (int i = 0; i < total; i++) {
80 | QString album = albumModel->record(i).value(albumModel->fieldIndex("title")).toString();
81 | unsigned int id = albumModel->record(i).value(albumModel->fieldIndex("id")).toInt();
82 | addAlbum(artist, album, id);
83 | }
84 |
85 | delete albumModel;
86 | }
87 | // If the item pressed was an album, add songs
88 | else if (item->getNodeLevel() == LevelAlbum) {
89 | QString albumId = QString::number(item->getId());
90 |
91 | QSqlTableModel *musicModel = service->musicModel();
92 | musicModel->setFilter("id_album = " + albumId);
93 | musicModel->setSort(musicModel->fieldIndex("track_number"), Qt::AscendingOrder);
94 | musicModel->select();
95 |
96 | while (musicModel->canFetchMore()) musicModel->fetchMore();
97 | int total = musicModel->rowCount();
98 |
99 | for (int i = 0; i < total; i++) {
100 | QString path = musicModel->record(i).value(musicModel->fieldIndex("path")).toString();
101 | unsigned int id = musicModel->record(i).value(musicModel->fieldIndex("id")).toInt();
102 |
103 | Music *music = new Music(QUrl(path));
104 | addMusic(music, id);
105 | delete music;
106 | }
107 | }
108 |
109 | expand(index);
110 | }
111 |
112 | QTreeWidgetItem *CollectionTreeWidget::addArtist(QString artist, unsigned int id) {
113 | if (id == 0) {
114 | QSqlTableModel *model = service->artistModel();
115 |
116 | // SQLite uses two single quotes to escape a single quote! :)
117 | QString filter = "name = '" + QString(artist).replace("'","''") + "'";
118 | model->setFilter(filter);
119 | model->select();
120 |
121 | while (model->canFetchMore()) model->fetchMore();
122 | int total = model->rowCount();
123 | if (total > 0) {
124 | id = model->record(0).value(model->fieldIndex("id")).toInt();
125 | }
126 | else {
127 | qDebug("ERROR: no artist found! -- " + model->filter().toUtf8());
128 | return NULL;
129 | }
130 | }
131 |
132 | QList artistList = findItems(artist, Qt::MatchExactly, 0);
133 | if (artistList.isEmpty()) {
134 | CollectionTreeWidgetItem *item = new CollectionTreeWidgetItem(LevelArtist, id, (QTreeWidget*)0);
135 | item->setText(0, artist);
136 | item->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator);
137 |
138 | // Set font to bold
139 | QFont font = item->font(0);
140 | font.setBold(true);
141 | item->setFont(0, font);
142 |
143 | // Set icon
144 | item->setIcon(0, IconFactory::fromTheme("folder"));
145 |
146 | // Insert item
147 | insertTopLevelItem(0, item);
148 | sortItems(0, Qt::AscendingOrder);
149 |
150 | return item;
151 | }
152 | else {
153 | return artistList.first();
154 | }
155 | }
156 |
157 | bool CollectionTreeWidget::removeArtist(QString artist) {
158 | QList artistList = findItems(artist, Qt::MatchExactly, 0);
159 | if (!artistList.isEmpty()) {
160 | delete artistList.first();
161 | return true;
162 | }
163 | return false;
164 | }
165 |
166 |
167 | QTreeWidgetItem *CollectionTreeWidget::addAlbum(QString artist, QString album, unsigned int albumId) {
168 | // Find id in database if we don't have it
169 | if (albumId == 0) {
170 | QSqlTableModel *model = service->collectionModel();
171 |
172 | // SQLite used two single quotes to escape a single quote! :)
173 | QString filter = "artist = '" + QString(artist).replace("'","''") + "' AND "
174 | "album = '" + QString(album).replace("'","''") + "'";
175 | model->setFilter(filter);
176 | model->select();
177 |
178 | while (model->canFetchMore()) model->fetchMore();
179 | int total = model->rowCount();
180 | if (total > 0) {
181 | albumId = model->record(0).value(model->fieldIndex("id_album")).toInt();
182 | }
183 | else {
184 | qDebug("ERROR: no album found! -- " + model->filter().toUtf8());
185 | return NULL;
186 | }
187 | }
188 |
189 | // Looks for the artist
190 | QTreeWidgetItem *newAlbumNode; // The node with the album, whether it exists or not
191 | QTreeWidgetItem *artistItem;
192 | artistItem = addArtist(artist);
193 |
194 | // Look for album
195 | for (int i = 0; i < artistItem->childCount(); i++) {
196 | if (artistItem->child(i)->text(0) == album) {
197 | newAlbumNode = artistItem->child(i);
198 | return newAlbumNode;
199 | }
200 | }
201 |
202 | // Create our new album node and add it if it was not found
203 | newAlbumNode = new CollectionTreeWidgetItem(LevelAlbum, albumId, (QTreeWidget*)0);
204 | newAlbumNode->setText(0, album);
205 | newAlbumNode->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator);
206 |
207 | // Set icon
208 | newAlbumNode->setIcon(0, IconFactory::fromTheme("media-cdrom"));
209 |
210 | artistItem->addChild(newAlbumNode);
211 | artistItem->sortChildren(0, Qt::AscendingOrder);
212 |
213 | return newAlbumNode;
214 | }
215 |
216 |
217 | bool CollectionTreeWidget::removeAlbum(QString artist, QString album) {
218 | QList artistList = findItems(artist, Qt::MatchExactly, 0);
219 | // If we have no artist, we have no album.
220 | if (artistList.isEmpty()) {
221 | return false;
222 | }
223 |
224 | // Looks for the artist
225 | QTreeWidgetItem *artistItem = artistList.first();
226 | // Look for album
227 | for (int i = 0; i < artistItem->childCount(); i++) {
228 | if (artistItem->child(i)->text(0) == album) {
229 | delete artistItem->child(i);
230 | cleanUp(artistItem, CollectionTreeWidget::LevelArtist);
231 | return true;
232 | }
233 | }
234 | return false;
235 | }
236 |
237 |
238 | CollectionTreeWidgetItem *CollectionTreeWidget::addMusic(Music *music, unsigned int id) {
239 | // Find id in database if we don't have it
240 | if (id == 0) {
241 | QSqlTableModel *model = service->collectionModel();
242 |
243 | // SQLite used two single quotes to escape a single quote! :)
244 | QString filter = "artist = '" + music->getArtist().replace("'","''") + "' AND "
245 | "album = '" + music->getAlbum().replace("'","''") + "' AND "
246 | "music = '" + music->getTitle().replace("'","''") + "'";
247 | model->setFilter(filter);
248 | model->select();
249 |
250 | while (model->canFetchMore()) model->fetchMore();
251 | int total = model->rowCount();
252 | if (total > 0) {
253 | id = model->record(0).value(model->fieldIndex("id_music")).toInt();
254 | }
255 | else {
256 | qDebug("ERROR: no songs found! -- " + model->filter().toUtf8());
257 | return NULL;
258 | }
259 | }
260 |
261 | // Looks for the album
262 | QTreeWidgetItem *albumItem = addAlbum(music->getArtist(), music->getAlbum());
263 |
264 | // Create our new music node and add it if it was not found
265 | removeMusic(id);
266 |
267 | CollectionTreeWidgetItem *newMusicNode = new CollectionTreeWidgetItem(LevelMusic, id, (QTreeWidget*)0);
268 | newMusicNode->setText(0, music->getTitle());
269 | newMusicNode->setIcon(0, IconFactory::fromTheme("sound"));
270 | albumItem->addChild(newMusicNode);
271 | musicList.append(newMusicNode);
272 |
273 | return newMusicNode;
274 | }
275 |
276 | bool CollectionTreeWidget::removeMusic(unsigned int id) {
277 | int total = musicList.count();
278 |
279 | if (total == 0) return false;
280 | for (int i = 0; i < total; i++) {
281 | CollectionTreeWidgetItem *item = musicList[i];
282 |
283 | if (item->getId() == id) {
284 | CollectionTreeWidgetItem *album = (CollectionTreeWidgetItem*)item->parent();
285 | musicList.removeAt(i);
286 | delete item;
287 | cleanUp(album, CollectionTreeWidget::LevelAlbum);
288 | return true;
289 | }
290 | }
291 |
292 | return false;
293 | }
294 |
295 | void CollectionTreeWidget::cleanUp(QTreeWidgetItem *parent = NULL, int level = CollectionTreeWidget::LevelNone) {
296 | // If parent is null, process all artists and its children nodes
297 | if (parent == NULL) {
298 | cleanUp(invisibleRootItem(), CollectionTreeWidget::LevelNone);
299 | }
300 |
301 | // if we have a parent, process it
302 | else {
303 |
304 | // If it's an artist, enter it
305 | if (level < CollectionTreeWidget::LevelAlbum) {
306 | int childCount = parent->childCount();
307 | for (int i = 0; i < childCount; i++) {
308 | TreeLevel newLevel = (TreeLevel)(level + 1);
309 | cleanUp(parent->child(i), newLevel);
310 | }
311 | }
312 |
313 | // If it's an album and it has no songs, remove it!
314 | else {
315 | if (parent->childCount() == 0) {
316 | QTreeWidgetItem *parentParent = parent->parent();
317 | delete parent;
318 |
319 | // If artist has no more albums, remove it too
320 | if (parentParent->childCount() == 0) delete parentParent;
321 | }
322 | }
323 | }
324 |
325 | }
326 |
327 |
328 | void CollectionTreeWidget::mouseMoveEvent(QMouseEvent *event) {
329 | // if not left button - return
330 | if (!(event->buttons() & Qt::LeftButton)) return;
331 |
332 | QDrag *drag = new QDrag(this);
333 | QMimeData *mimeData = new QMimeData;
334 |
335 | // construct list of QUrls
336 | // other widgets accept this mime type, we can drop to them
337 | QList list;
338 |
339 | foreach (QTreeWidgetItem *currentItem, selectedItems()) {
340 | CollectionTreeWidgetItem *collectionCurrentItem = (CollectionTreeWidgetItem *)currentItem;
341 | list.append(collectionCurrentItem->getUrlList(service->collectionModel()));
342 | }
343 |
344 | // mime stuff
345 | mimeData->setUrls(list);
346 | drag->setMimeData(mimeData);
347 |
348 | // start drag
349 | drag->exec(Qt::CopyAction | Qt::MoveAction);
350 | }
351 |
352 | void CollectionTreeWidget::doubleClickAt(QModelIndex index) {
353 | CollectionTreeWidgetItem *item = (CollectionTreeWidgetItem *)itemFromIndex(index);
354 | if (item->getNodeLevel() == LevelMusic) {
355 | emit askToAddItemToPlaylist(item->getUrlList(service->collectionModel()));
356 | }
357 | }
358 |
--------------------------------------------------------------------------------