Further Playlist interface improvements for usability.
[vlc-remote] / playermainwindow.cpp
1   /*   VLC-REMOTE for MAEMO 5
2   *   Copyright (C) 2010 Schutz Sacha <istdasklar@gmail.com>, Dru Moore <usr@dru-id.co.uk>, Yann Nave <yannux@onbebop.net>
3   *   This program is free software; you can redistribute it and/or modify
4   *   it under the terms of the GNU General Public License version 2,
5   *   or (at your option) any later version, as published by the Free
6   *   Software Foundation
7   *
8   *   This program is distributed in the hope that it will be useful,
9   *   but WITHOUT ANY WARRANTY; without even the implied warranty of
10   *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11   *   GNU General Public License for more details
12   *
13   *   You should have received a copy of the GNU General Public
14   *   License along with this program; if not, write to the
15   *   Free Software Foundation, Inc.,
16   *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17   */
18   #include <QDebug>
19   #include <QTime>
20   #include <QtGui>
21   #include "playermainwindow.h"
22   #include "ui_playermainwindow.h"
23   #include "configdialog.h"
24   #include "aboutdialog.h"
25   #include "accountdialog.h"
26   #include "appsettings.h"
27   //#include "vlcstatus.h"
28
29   PlayerMainWindow::PlayerMainWindow(QWidget *parent) :
30           QMainWindow(parent),
31           ui(new Ui::PlayerMainWindow)
32   {
33       ui->setupUi(this);
34       setWindowTitle("Vlc remote");
35
36
37
38       mTimer = new QTimer(this);
39       mNetManager = new QNetworkAccessManager(this);
40       mPlayListMainWindow = new PlayListMainWindow;
41       mBrowserMainWindow = new BrowseMainWindow;
42
43       mVolume = 100;
44       mMuted = false;
45
46       mIsLandscape = true;
47
48       ui->playlistButton->setIcon(QIcon::fromTheme("notes_bullets"));
49       ui->browseButton->setIcon(QIcon::fromTheme("filemanager_media_folder"));
50
51       ui->previousButton->setIcon(QIcon::fromTheme("pdf_viewer_first_page"));
52       ui->nextButton->setIcon(QIcon::fromTheme("pdf_viewer_last_page"));
53       ui->playpauseButton->setIcon(QIcon::fromTheme("camera_playback"));
54       ui->stopButton->setIcon(QIcon::fromTheme("camera_video_stop"));
55       //ui->pauseButton->setIcon(QIcon::fromTheme("camera_video_pause"));
56       ui->fullscreenButton->setIcon(QIcon::fromTheme("general_fullsize"));
57       ui->volDown->setIcon(QIcon::fromTheme("statusarea_volumelevel1"));
58       ui->volUp->setIcon(QIcon::fromTheme("statusarea_volumelevel4"));
59       ui->volMute->setIcon(QIcon::fromTheme("statusarea_volume_mute"));
60
61       ui->labelArtPortrait->setVisible(false);
62       ui->labelArtLandscape->setVisible(false);
63
64       ui->labelTitle->setTextFormat(Qt::RichText);
65       ui->labelArtist->setTextFormat(Qt::RichText);
66       ui->labelAlbum->setTextFormat(Qt::RichText);
67
68
69   #if defined(Q_WS_S60) || defined(Q_WS_MAEMO_5)
70
71       mPlayListMainWindow->setParent(this);
72       mPlayListMainWindow->setAttribute(Qt::WA_Maemo5StackedWindow);
73       setAttribute(Qt::WA_Maemo5StackedWindow);
74       mPlayListMainWindow->setWindowFlags(mPlayListMainWindow->windowFlags() | Qt::Window);
75
76       mBrowserMainWindow->setParent(this);
77       mBrowserMainWindow->setAttribute(Qt::WA_Maemo5StackedWindow);
78       setAttribute(Qt::WA_Maemo5StackedWindow);
79       mBrowserMainWindow->setWindowFlags(mBrowserMainWindow->windowFlags() | Qt::Window);
80
81       connect(QApplication::desktop(), SIGNAL(resized(int)), this, SLOT(orientationChanged()));
82
83   #endif
84
85       connect(mTimer,SIGNAL(timeout()),this,SLOT(askStatus()));
86       connect(ui->actionConfiguration,SIGNAL(triggered()),this,SLOT(showConfig()));
87       connect(ui->actionAbout,SIGNAL(triggered()),this,SLOT(showAbout()));
88       connect(ui->actionPortrait,SIGNAL(triggered()),this,SLOT(setPortrait()));
89       connect(ui->actionLandscape,SIGNAL(triggered()),this,SLOT(setLandscape()));
90       connect(ui->actionAutoRotate,SIGNAL(triggered()),this,SLOT(setAutoRotate()));
91       connect(ui->playlistButton,SIGNAL(clicked()),mPlayListMainWindow,SLOT(show()));
92       connect(ui->playlistButton,SIGNAL(clicked()),mPlayListMainWindow,SLOT(showPlayList()));
93       connect(ui->browseButton,SIGNAL(clicked()),mBrowserMainWindow,SLOT(show()));
94       connect(ui->browseButton,SIGNAL(clicked()),mBrowserMainWindow,SLOT(showCurrentDirectory()));
95
96       connect(ui->playpauseButton,SIGNAL(clicked()),this,SLOT(playpause()));
97       connect(ui->stopButton,SIGNAL(clicked()),this,SLOT(stop()));
98       //connect(ui->pauseButton,SIGNAL(clicked()),this,SLOT(playpause()));
99       connect(ui->previousButton,SIGNAL(clicked()),this,SLOT(previous()));
100       connect(ui->nextButton,SIGNAL(clicked()),this,SLOT(next()));
101       connect(ui->fullscreenButton,SIGNAL(clicked()),this,SLOT(fullscreen()));
102       connect(ui->volUp,SIGNAL(clicked()),this,SLOT(volUp()));
103       connect(ui->volDown,SIGNAL(clicked()),this,SLOT(volDown()));
104       connect(ui->volMute,SIGNAL(clicked()),this,SLOT(volMute()));
105       connect(ui->slider,SIGNAL(sliderMoved(int)),this,SLOT(slide(int)));
106
107       connect(mPlayListMainWindow, SIGNAL(idUpdated(int,bool,QString)), this, SLOT(playlistIdUpdated(int, bool, QString)));
108
109
110       // check if last used connection is still valid or showConfig
111       QSettings settings;
112       QString last_ip = AccountDialog::currentIp();
113       if (!last_ip.isNull() && !last_ip.isEmpty()) {
114           QTcpSocket * socket = new QTcpSocket;
115           if(last_ip.contains(":"))
116           {
117               QStringList hostSplit = last_ip.split(":");
118               QString ip   = hostSplit.at(0);
119               QString port = hostSplit.at(1);
120               socket->connectToHost(ip,port.toInt());
121           }
122           else {
123               socket->connectToHost(last_ip,8080);
124           }
125           if (!socket->waitForConnected(1000)) {
126                  showConfig();
127              }
128           else {
129               mIp= last_ip;
130
131              mPlayListMainWindow->init();
132              mBrowserMainWindow->init();
133              mTimer->start(5000);
134              askStatus();
135           }
136           delete socket;
137       }
138       else {
139         showConfig();
140       }
141
142
143   }
144   
145
146   PlayerMainWindow::~PlayerMainWindow()
147   {
148       delete ui;
149   }
150
151   void PlayerMainWindow::changeEvent(QEvent *e)
152   {
153       QMainWindow::changeEvent(e);
154       switch (e->type()) {
155       case QEvent::LanguageChange:
156           ui->retranslateUi(this);
157           break;
158       default:
159           break;
160       }
161   }
162
163   void PlayerMainWindow::setPortrait()
164   {
165     #if defined(Q_WS_S60) || defined(Q_WS_MAEMO_5)
166       AppSettings::setOrientation(PORTRAIT);
167     this->setAttribute(Qt::WA_Maemo5PortraitOrientation, true);
168     #endif
169   }
170
171   void PlayerMainWindow::setLandscape()
172   {
173     #if defined(Q_WS_S60) || defined(Q_WS_MAEMO_5)
174       AppSettings::setOrientation(LANDSCAPE);
175     this->setAttribute(Qt::WA_Maemo5LandscapeOrientation, true);
176     #endif
177   }
178
179   void PlayerMainWindow::setAutoRotate()
180   {
181     #if defined(Q_WS_S60) || defined(Q_WS_MAEMO_5)
182       AppSettings::setOrientation(AUTO_ROTATE);
183     this->setAttribute(Qt::WA_Maemo5AutoOrientation, true);
184     #endif
185   }
186
187   void PlayerMainWindow::orientationChanged() {
188       QRect screenGeometry = QApplication::desktop()->screenGeometry();
189       mIsLandscape = (screenGeometry.width() > screenGeometry.height());
190       if (mHasImage) {
191           if (mIsLandscape) {
192               ui->labelArtPortrait->setVisible(false);
193               ui->labelArtLandscape->setVisible(true);
194           }
195           else {
196               ui->labelArtLandscape->setVisible(false);
197               ui->labelArtPortrait->setVisible(true);
198           }
199       }
200       else {
201           ui->labelArtLandscape->setVisible(false);
202           ui->labelArtPortrait->setVisible(false);
203       }
204   }
205
206   void PlayerMainWindow::playpause()
207   {
208       // NB. There is no guarentee that our current state is the real current state.
209       // This is due to the polling frequency and possibility of user interaction directly on the server.
210       // Still this is probably better than nothing and our next real poll will set us straight again.
211       if (PAUSED == mCurrentStatus.state) {
212         mCurrentStatus.state = PLAYING;
213         pause();
214         updateUiWithCurrentStatus();
215       }
216       else if (PLAYING == mCurrentStatus.state) {
217         mCurrentStatus.state = PAUSED;
218         pause();
219         updateUiWithCurrentStatus();
220       }
221       else {
222         // could be STOP or UNKNOWN, either way there is no guarentee we will enter a playing state next.
223         // So don't update the current state or UI
224         // Ideally we would try to find a way to check the current state again but this could lead to an infinite loop!
225         play();
226       }
227   }
228   void PlayerMainWindow::play()
229   {
230       mNetManager->get(QNetworkRequest(QUrl("http://"+mIp+"/requests/status.xml?command=pl_play")));
231   }
232   void PlayerMainWindow::stop()
233   {
234       mNetManager->get(QNetworkRequest(QUrl("http://"+mIp+"/requests/status.xml?command=pl_stop")));
235   }
236   void PlayerMainWindow::pause()
237   {
238       mNetManager->get(QNetworkRequest(QUrl("http://"+mIp+"/requests/status.xml?command=pl_pause")));
239   }
240   void PlayerMainWindow::previous()
241   {
242       mNetManager->get(QNetworkRequest(QUrl("http://"+mIp+"/requests/status.xml?command=pl_previous")));
243   }
244   void PlayerMainWindow::next()
245   {
246       mNetManager->get(QNetworkRequest(QUrl("http://"+mIp+"/requests/status.xml?command=pl_next")));
247   }
248   void PlayerMainWindow::fullscreen()
249   {
250       mNetManager->get(QNetworkRequest(QUrl("http://"+mIp+"/requests/status.xml?command=fullscreen")));
251   }
252   void PlayerMainWindow::volUp()
253   {
254       QUrl url = QUrl("http://"+mIp+"/requests/status.xml?command=volume");
255       url.addEncodedQueryItem(QByteArray("val"), QByteArray("%2B20"));
256       mNetManager->get(QNetworkRequest(url));
257   }
258   void PlayerMainWindow::volDown()
259   {
260       mNetManager->get(QNetworkRequest(QUrl("http://"+mIp+"/requests/status.xml?command=volume&val=-20")));
261   }
262   void PlayerMainWindow::volMute()
263   {
264       this->mMuted = !this->mMuted;
265       if (this->mMuted) {
266           mNetManager->get(QNetworkRequest(QUrl("http://"+mIp+"/requests/status.xml?command=volume&val=0")));
267       }
268       else {
269           mNetManager->get(QNetworkRequest(QUrl("http://"+mIp+"/requests/status.xml?command=volume&val="+QString::number(this->mVolume))));
270       }
271   }
272   void PlayerMainWindow::slide(int value)
273   {
274       mNetManager->get(QNetworkRequest(QUrl("http://"+mIp+"/requests/status.xml?command=seek&val="+QString::number(value)+"%25")));
275   }
276
277   void PlayerMainWindow::showConfig()
278   {
279       mTimer->stop();
280       AccountDialog * dialog = new AccountDialog;
281       dialog->exec();
282      
283        mIp= AccountDialog::currentIp();
284
285       mPlayListMainWindow->init();
286       mBrowserMainWindow->init();
287       mTimer->start(5000);
288       askStatus();
289   }
290   void PlayerMainWindow::showAbout()
291   {
292
293       AboutDialog * dialog = new AboutDialog;
294       dialog->exec();
295
296   }
297
298   void PlayerMainWindow::askStatus()
299   {
300       qDebug() << "Status requested. at:" << QTime::currentTime().toString("hh::mm:ss");
301       QNetworkReply * reply =  mNetManager->get(QNetworkRequest(QUrl("http://"+mIp+"/requests/status.xml")));
302       connect(reply,SIGNAL(readyRead()),this,SLOT(parseXmlStatus()));
303   }
304
305   void PlayerMainWindow::parseXmlStatus()
306   {
307       QNetworkReply * reply = qobject_cast<QNetworkReply*>(sender());
308       QDomDocument doc;
309       doc.setContent(reply->readAll());
310       delete reply;
311       QDomElement docElem = doc.documentElement();
312       // Get the raw values
313       int volume = docElem.namedItem("volume").toElement().text().toInt();
314       int length = docElem.namedItem("length").toElement().text().toInt();
315       int time = docElem.namedItem("time").toElement().text().toInt();
316       int position = docElem.namedItem("position").toElement().text().toInt();
317       int random = docElem.namedItem("random").toElement().text().toInt();
318       int loop = docElem.namedItem("loop").toElement().text().toInt();
319       int repeat = docElem.namedItem("repeat").toElement().text().toInt();
320       QString state = docElem.namedItem("state").toElement().text();
321       QDomNode infoNode =  docElem.namedItem("information");
322       QDomNode metaInfoNode =  infoNode.namedItem("meta-information");
323       QString title = metaInfoNode.namedItem("title").toElement().text().replace("\\\\", "\\");
324       // if it's a file style title fix it up
325       if (40 < title.length()) {
326           if (0 < title.lastIndexOf("\\")) {
327               title = title.right(title.length() - (title.lastIndexOf("\\") + 1));
328           }
329           else if (0 < title.lastIndexOf("/")) {
330               title = title.right(title.length() - (title.lastIndexOf("/") + 1));
331           }
332       }
333       QString artist = metaInfoNode.namedItem("artist").toElement().text();
334       QString album = metaInfoNode.namedItem("album").toElement().text();
335       QString now_playing = metaInfoNode.namedItem("now_playing").toElement().text();
336       QString art_url = metaInfoNode.namedItem("art_url").toElement().text();
337       // Populate the current status structure
338       // now would be a good time to work out if we are a new track / file or not.
339       // key if we are going to look for album art later
340       // for now we check length and title this will require further examination later
341       mCurrentStatus.newtrack = true;
342       if (mCurrentStatus.length == length && !mCurrentStatus.title.isNull() && 0 == QString::compare(mCurrentStatus.title, title)) {
343         mCurrentStatus.newtrack = false;
344       }
345       mCurrentStatus.volume = volume;
346       mCurrentStatus.length = length;
347       mCurrentStatus.time = time;
348       mCurrentStatus.position = position;
349       mCurrentStatus.random = (1 == random);
350       mCurrentStatus.loop = (1 == loop);
351       mCurrentStatus.repeat = (1 == repeat);
352       mCurrentStatus.title = title;
353       mCurrentStatus.artist = artist;
354       mCurrentStatus.album = album;
355       mCurrentStatus.nowplaying = now_playing;
356       mCurrentStatus.hasart = (!art_url.isNull() && !art_url.isEmpty());
357       if (!state.isNull() && !state.isEmpty()) {
358           if (0 == QString::compare("playing", state, Qt::CaseInsensitive)) {
359             mCurrentStatus.state = PLAYING;
360           }
361           else if (0 == QString::compare("paused", state, Qt::CaseInsensitive)) {
362             mCurrentStatus.state = PAUSED;
363           }
364           else if (0 == QString::compare("stop", state, Qt::CaseInsensitive)) {
365             mCurrentStatus.state = STOP;
366           }
367           else {
368             mCurrentStatus.state = UNKNOWN;
369           }
370       }
371       else {
372           mCurrentStatus.state = UNKNOWN;
373       }
374       // What's our mute status?
375       if (0 < mCurrentStatus.volume) {
376           this->mVolume = mCurrentStatus.volume;
377           this->mMuted = false;
378       }
379       else {
380           this->mMuted = true;
381       }
382       // Update the UI
383       updateUiWithCurrentStatus();
384
385   }
386
387   void PlayerMainWindow::updateUiWithCurrentStatus() {
388       // position
389       QTime timePosition(0,0,0) ;
390       timePosition =  timePosition.addSecs(mCurrentStatus.time);
391
392       ui->timeLabel->setText(timePosition.toString("h:mm:ss"));
393
394       // duration
395       if (0 < mCurrentStatus.length) {
396           QTime timeDuration(0,0,0) ;
397           timeDuration =  timeDuration.addSecs(mCurrentStatus.length);
398
399           ui->durationLabel->setText(timeDuration.toString("h:mm:ss"));
400       }
401       else {
402           ui->durationLabel->setText("0:00:00");
403       }
404
405
406       if (mCurrentStatus.position >= 0 && mCurrentStatus.position <= 100)
407           ui->slider->setValue(mCurrentStatus.position);
408
409       ui->labelTitle->setText(mCurrentStatus.title);
410       ui->labelArtist->setText(mCurrentStatus.artist);
411       ui->labelAlbum->setText(mCurrentStatus.album);
412
413       if (PLAYING == mCurrentStatus.state) {
414           ui->playpauseButton->setIcon(QIcon::fromTheme("camera_video_pause"));
415       }
416       else {
417           ui->playpauseButton->setIcon(QIcon::fromTheme("camera_playback"));
418       }
419
420       if (mCurrentStatus.newtrack) {
421           // potential actions:
422           //   rebuild display layout
423           //   retrieve album art
424           mHasImage = false;
425           QTimer::singleShot(500, mPlayListMainWindow, SLOT(requestPlayList()));
426       }
427       // Update the buttons on the playlist window
428       if (NULL != this->mPlayListMainWindow) {
429         this->mPlayListMainWindow->updateUiWithCurrentStatus(& mCurrentStatus);
430       }
431
432   }
433   void PlayerMainWindow::playlistIdUpdated(int id, bool hasart, QString extension) {
434       if (hasart) {
435           getCoverArt(id);
436       }
437       else {
438           ui->labelArtLandscape->setVisible(false);
439           ui->labelArtPortrait->setVisible(false);
440           // could use a default graphic here!
441           // setCoverArtFromPixmap();
442       }
443   }
444   void PlayerMainWindow::error(QNetworkReply::NetworkError code) {
445       qDebug() << "Error Code: " << code;
446   }
447   void PlayerMainWindow::readReady() {
448     QNetworkReply * reply = qobject_cast<QNetworkReply*>(sender());
449     // append to buffer
450     mResponse += reply->readAll();
451   }
452   void PlayerMainWindow::finished(QNetworkReply * reply) {
453     // now we can call setCoverArt to process the full buffers
454     this->setCoverArt(mResponse);
455     // only interested in finished signals
456     disconnect(mNetManager,SIGNAL(finished(QNetworkReply *)),this,SLOT(finished(QNetworkReply *)));
457   }
458   void PlayerMainWindow::getCoverArt(int id) {
459       qDebug() << "getCoverArt id=!" << id;
460     mResponse.clear();
461     QNetworkReply * reply =  mNetManager->get(QNetworkRequest(QUrl("http://"+mIp+"/art?id=" + QString::number(id))));
462     connect(reply,SIGNAL(readyRead()),this,SLOT(readReady()));
463     connect(reply,SIGNAL(error(QNetworkReply::NetworkError)),this,SLOT(error(QNetworkReply::NetworkError)));
464     connect(mNetManager,SIGNAL(finished(QNetworkReply *)),this,SLOT(finished(QNetworkReply *)));
465
466   }
467   void PlayerMainWindow::setCoverArt(const QByteArray data) {
468     QPixmap* image = new QPixmap();
469     if (image->loadFromData(data)) {
470         mHasImage = true;
471         ui->labelArtLandscape->setPixmap(image->scaledToHeight(120, Qt::SmoothTransformation));
472         ui->labelArtPortrait->setPixmap(image->scaledToHeight(310, Qt::SmoothTransformation));
473         if (mIsLandscape) {
474             ui->labelArtPortrait->setVisible(false);
475             ui->labelArtLandscape->setVisible(true);
476         }
477         else {
478             ui->labelArtLandscape->setVisible(false);
479             ui->labelArtPortrait->setVisible(true);
480         }
481     }
482     else {
483         qDebug() << "image load failed!";
484         qDebug() << "data.length" << data.length();
485         ui->labelArtPortrait->setVisible(false);
486         ui->labelArtLandscape->setVisible(false);
487     }
488   }
489   void PlayerMainWindow::setCoverArtFromPixmap(QPixmap image) {
490     mHasImage = true;
491     ui->labelArtLandscape->setPixmap(image.scaledToHeight(120, Qt::SmoothTransformation));
492     ui->labelArtPortrait->setPixmap(image.scaledToHeight(320, Qt::SmoothTransformation));
493     if (mIsLandscape) {
494         ui->labelArtPortrait->setVisible(false);
495         ui->labelArtLandscape->setVisible(true);
496     }
497     else {
498         ui->labelArtLandscape->setVisible(false);
499         ui->labelArtPortrait->setVisible(true);
500     }
501   }
502