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