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