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