MythTV  0.27pre
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Groups Pages
mythuiwebbrowser.cpp
Go to the documentation of this file.
1 
10 #include "mythuiwebbrowser.h"
11 
12 // qt
13 #include <QApplication>
14 #include <QWebFrame>
15 #include <QWebHistory>
16 #include <QPainter>
17 #include <QDir>
18 #include <QBuffer>
19 #include <QStyle>
20 #include <QKeyEvent>
21 #include <QDomDocument>
22 #include <QNetworkCookieJar>
23 
24 // myth
25 #include "mythpainter.h"
26 #include "mythimage.h"
27 #include "mythmainwindow.h"
28 #include "mythfontproperties.h"
29 #include "mythlogging.h"
30 #include "mythdb.h"
31 #include "mythdirs.h"
32 #include "mythuihelper.h"
33 #include "mythcorecontext.h"
34 #include "mythdownloadmanager.h"
35 #include "mythdialogbox.h"
36 #include "mythprogressdialog.h"
37 #include "mythuiscrollbar.h"
38 
39 struct MimeType
40 {
41  QString mimeType;
42  QString extension;
43  bool isVideo;
44 };
45 
47 {
48  { "audio/mpeg3", "mp3", false },
49  { "audio/x-mpeg-3", "mp3", false },
50  { "audio/mpeg", "mp2", false },
51  { "audio/x-mpeg", "mp2", false },
52  { "audio/ogg", "ogg", false },
53  { "audio/ogg", "oga", false },
54  { "audio/flac", "flac", false },
55  { "audio/x-ms-wma", "wma", false },
56  { "audio/wav", "wav", false },
57  { "audio/x-wav", "wav", false },
58  { "audio/ac3", "ac3", false },
59  { "audio/x-ac3", "ac3", false },
60  { "audio/x-oma", "oma", false },
61  { "audio/x-realaudio", "ra", false },
62  { "audio/dts", "dts", false },
63  { "audio/x-dts", "dts", false },
64  { "audio/aac", "aac", false },
65  { "audio/x-aac", "aac", false },
66  { "audio/m4a", "m4a", false },
67  { "audio/x-m4a", "m4a", false },
68  { "video/mpeg", "mpg", true },
69  { "video/mpeg", "mpeg", true },
70  { "video/x-ms-wmv", "wmv", true },
71  { "video/x-ms-wmv", "avi", true },
72  { "application/x-troff-msvideo", "avi", true },
73  { "video/avi", "avi", true },
74  { "video/msvideo", "avi", true },
75  { "video/x-msvideo", "avi", true }
76 };
77 
79  sizeof(SupportedMimeTypes[0]);
80 
82 {
83 }
84 
85 QNetworkReply* MythNetworkAccessManager::createRequest(Operation op, const QNetworkRequest& req, QIODevice* outgoingData)
86 {
87  QNetworkReply* reply = QNetworkAccessManager::createRequest(op, req, outgoingData);
88  reply->ignoreSslErrors();
89  return reply;
90 }
91 
93 
94 static void DestroyNetworkAccessManager(void)
95 {
96  if (networkManager)
97  {
98  delete networkManager;
99  networkManager = NULL;
100  }
101 }
102 
103 static QNetworkAccessManager *GetNetworkAccessManager(void)
104 {
105  if (networkManager)
106  return networkManager;
107 
108  networkManager = new MythNetworkAccessManager();
109  LOG(VB_GENERAL, LOG_DEBUG, "Copying DLManager's Cookie Jar");
110  GetMythDownloadManager()->loadCookieJar(GetConfDir() + "/MythBrowser/cookiejar.txt");
111  networkManager->setCookieJar(GetMythDownloadManager()->copyCookieJar());
112 
114 
115  return networkManager;
116 }
117 
123 BrowserApi::BrowserApi(QObject *parent)
124  : QObject(parent),
125  m_frame(NULL), m_gotAnswer(false)
126 {
127  gCoreContext->addListener(this);
128 }
129 
131 {
133 }
134 
135 void BrowserApi::setWebView(QWebView *view)
136 {
137  QWebPage *page = view->page();
138  m_frame = page->mainFrame();
139 
140  attachObject();
141  connect(m_frame, SIGNAL(javaScriptWindowObjectCleared()), this,
142  SLOT(attachObject()));
143 }
144 
146 {
147  m_frame->addToJavaScriptWindowObject(QString("MusicPlayer"), this);
148 }
149 
151 {
152  MythEvent me(QString("MUSIC_COMMAND %1 PLAY").arg(gCoreContext->GetHostName()));
153  gCoreContext->dispatch(me);
154 }
155 
157 {
158  MythEvent me(QString("MUSIC_COMMAND %1 STOP").arg(gCoreContext->GetHostName()));
159  gCoreContext->dispatch(me);
160 }
161 
163 {
164  MythEvent me(QString("MUSIC_COMMAND %1 PAUSE %1").arg(gCoreContext->GetHostName()));
165  gCoreContext->dispatch(me);
166 }
167 
168 void BrowserApi::SetVolume(int volumn)
169 {
170  MythEvent me(QString("MUSIC_COMMAND %1 SET_VOLUME %2")
171  .arg(gCoreContext->GetHostName()).arg(volumn));
172  gCoreContext->dispatch(me);
173 }
174 
176 {
177  m_gotAnswer = false;
178 
179  MythEvent me(QString("MUSIC_COMMAND %1 GET_VOLUME")
180  .arg(gCoreContext->GetHostName()));
181  gCoreContext->dispatch(me);
182 
183  QTime timer;
184  timer.start();
185 
186  while (timer.elapsed() < 2000 && !m_gotAnswer)
187  {
188  qApp->processEvents();
189  usleep(10000);
190  }
191 
192  if (m_gotAnswer)
193  return m_answer.toInt();
194 
195  return -1;
196 }
197 
199 {
200  MythEvent me(QString("MUSIC_COMMAND %1 PLAY_FILE '%2'")
201  .arg(gCoreContext->GetHostName()).arg(filename));
202  gCoreContext->dispatch(me);
203 }
204 
205 void BrowserApi::PlayTrack(int trackID)
206 {
207  MythEvent me(QString("MUSIC_COMMAND %1 PLAY_TRACK %2")
208  .arg(gCoreContext->GetHostName()).arg(trackID));
209  gCoreContext->dispatch(me);
210 }
211 
212 void BrowserApi::PlayURL(QString url)
213 {
214  MythEvent me(QString("MUSIC_COMMAND %1 PLAY_URL %2")
215  .arg(gCoreContext->GetHostName()).arg(url));
216  gCoreContext->dispatch(me);
217 }
218 
220 {
221  m_gotAnswer = false;
222 
223  MythEvent me(QString("MUSIC_COMMAND %1 GET_METADATA")
224  .arg(gCoreContext->GetHostName()));
225  gCoreContext->dispatch(me);
226 
227  QTime timer;
228  timer.start();
229 
230  while (timer.elapsed() < 2000 && !m_gotAnswer)
231  {
232  qApp->processEvents();
233  usleep(10000);
234  }
235 
236  if (m_gotAnswer)
237  return m_answer;
238 
239  return QString("unknown");
240 }
241 
242 void BrowserApi::customEvent(QEvent *e)
243 {
244  if ((MythEvent::Type)(e->type()) == MythEvent::MythEventMessage)
245  {
246  MythEvent *me = (MythEvent *)e;
247  QString message = me->Message();
248 
249  if (!message.startsWith("MUSIC_CONTROL"))
250  return;
251 
252  QStringList tokens = message.simplified().split(" ");
253 
254  if ((tokens.size() >= 4) && (tokens[1] == "ANSWER")
255  && (tokens[2] == gCoreContext->GetHostName()))
256  {
257  m_answer = tokens[3];
258 
259  for (int i = 4; i < tokens.size(); i++)
260  m_answer += QString(" ") + tokens[i];
261 
262  m_gotAnswer = true;
263  }
264  }
265 }
266 
267 MythWebPage::MythWebPage(QObject *parent)
268  : QWebPage(parent)
269 {
270  setNetworkAccessManager(GetNetworkAccessManager());
271 }
272 
274 {
275  LOG(VB_GENERAL, LOG_DEBUG, "Refreshing DLManager's Cookie Jar");
276  GetMythDownloadManager()->refreshCookieJar(networkManager->cookieJar());
277  GetMythDownloadManager()->saveCookieJar(GetConfDir() + "/MythBrowser/cookiejar.txt");
278 }
279 
280 bool MythWebPage::supportsExtension(Extension extension) const
281 {
282  if (extension == QWebPage::ErrorPageExtension)
283  return true;
284 
285  return false;
286 }
287 
288 bool MythWebPage::extension(Extension extension, const ExtensionOption *option,
289  ExtensionReturn *output)
290 {
291  if (extension == QWebPage::ErrorPageExtension)
292  {
293  ErrorPageExtensionOption *erroroption;
294  erroroption = (ErrorPageExtensionOption *) option;
295  ErrorPageExtensionReturn *erroroutput;
296  erroroutput = (ErrorPageExtensionReturn *) output;
297 
298  if (!option || !output)
299  return false;
300 
301  QString filename = "htmls/notfound.html";
302 
303  if (!GetMythUI()->FindThemeFile(filename))
304  return false;
305 
306  QFile file(QLatin1String(qPrintable(filename)));
307  bool isOpened = file.open(QIODevice::ReadOnly);
308 
309  if (!isOpened)
310  return false;
311 
312  QString title = tr("Error loading page: %1").arg(erroroption->url.toString());
313  QString html = QString(QLatin1String(file.readAll()))
314  .arg(title)
315  .arg(erroroption->errorString)
316  .arg(erroroption->url.toString());
317 
318  QBuffer imageBuffer;
319  imageBuffer.open(QBuffer::ReadWrite);
320  QIcon icon = qApp->style()->standardIcon(QStyle::SP_MessageBoxWarning,
321  0, 0);
322  QPixmap pixmap = icon.pixmap(QSize(32, 32));
323 
324  if (pixmap.save(&imageBuffer, "PNG"))
325  {
326  html.replace(QLatin1String("IMAGE_BINARY_DATA_HERE"),
327  QString(QLatin1String(imageBuffer.buffer().toBase64())));
328  }
329 
330  erroroutput->content = html.toUtf8();
331 
332  return true;
333  }
334 
335  return false;
336 }
337 
338 QString MythWebPage::userAgentForUrl(const QUrl &url) const
339 {
340  return QWebPage::userAgentForUrl(url).replace("Safari", "MythBrowser");
341 }
342 
348 MythWebView::MythWebView(QWidget *parent, MythUIWebBrowser *parentBrowser)
349  : QWebView(parent),
350  m_webpage(new MythWebPage(this))
351 {
352  setPage(m_webpage);
353 
354  m_parentBrowser = parentBrowser;
355  m_busyPopup = NULL;
356 
357  connect(page(), SIGNAL(unsupportedContent(QNetworkReply *)),
358  this, SLOT(handleUnsupportedContent(QNetworkReply *)));
359 
360  connect(page(), SIGNAL(downloadRequested(const QNetworkRequest &)),
361  this, SLOT(handleDownloadRequested(QNetworkRequest)));
362 
363  page()->setForwardUnsupportedContent(true);
364 
365  m_api = new BrowserApi(this);
366  m_api->setWebView(this);
367 
368  m_downloadAndPlay = false;
369  m_downloadReply = NULL;
370 }
371 
373 {
374  delete m_webpage;
375  delete m_api;
376 }
377 
382 const char *kgetType = "\
383 function activeElement()\
384 {\
385  var type;\
386  type = document.activeElement.type;\
387  return type;\
388 }\
389 activeElement();";
390 
391 void MythWebView::keyPressEvent(QKeyEvent *event)
392 {
393  // does an edit have focus?
394  QString type = m_parentBrowser->evaluateJavaScript(QString(kgetType))
395  .toString().toLower();
396  bool editHasFocus = (type == "text" || type == "textarea" ||
397  type == "password");
398 
399  // if the QWebView widget has focus then all keypresses from a regular
400  // keyboard get sent here first
401  if (editHasFocus || m_parentBrowser->IsInputToggled())
402  {
403  // input is toggled so pass all keypresses to the QWebView's handler
405  }
406  else
407  {
408  // we need to convert a few keypress events so the QWebView does the
409  // right thing
410  QStringList actions;
411  bool handled = false;
412  handled = GetMythMainWindow()->TranslateKeyPress("Browser", event,
413  actions);
414 
415  for (int i = 0; i < actions.size() && !handled; i++)
416  {
417  QString action = actions[i];
418  handled = true;
419 
420  if (action == "NEXTLINK")
421  {
422  QKeyEvent tabKey(event->type(), Qt::Key_Tab,
423  event->modifiers(), QString(),
424  event->isAutoRepeat(), event->count());
425  *event = tabKey;
427  return;
428  }
429  else if (action == "PREVIOUSLINK")
430  {
431  QKeyEvent shiftTabKey(event->type(), Qt::Key_Tab,
432  event->modifiers() | Qt::ShiftModifier,
433  QString(),
434  event->isAutoRepeat(), event->count());
435  *event = shiftTabKey;
437  return;
438  }
439  else if (action == "FOLLOWLINK")
440  {
441  QKeyEvent returnKey(event->type(), Qt::Key_Return,
442  event->modifiers(), QString(),
443  event->isAutoRepeat(), event->count());
444  *event = returnKey;
446  return;
447  }
448  }
449 
450  // pass the keyPress event to our main window handler so they get
451  // handled properly by the various mythui handlers
452  QCoreApplication::postEvent(GetMythMainWindow(), new QKeyEvent(*event));
453  }
454 }
455 
456 void MythWebView::handleUnsupportedContent(QNetworkReply *reply)
457 {
458  if (reply->error() == QNetworkReply::NoError)
459  {
460  stop();
461 
462  QVariant header = reply->header(QNetworkRequest::ContentTypeHeader);
463 
464  if (header != QVariant())
465  LOG(VB_GENERAL, LOG_ERR,
466  QString("MythWebView::handleUnsupportedContent - %1")
467  .arg(header.toString()));
468 
469  m_downloadReply = reply;
470  m_downloadRequest = reply->request();
471  m_downloadAndPlay = false;
473 
474  return;
475  }
476 }
477 
478 void MythWebView::handleDownloadRequested(const QNetworkRequest &request)
479 {
480  m_downloadReply = NULL;
481  doDownloadRequested(request);
482 }
483 
484 void MythWebView::doDownloadRequested(const QNetworkRequest &request)
485 {
486  m_downloadRequest = request;
487 
488  // get the filename from the url if available
489  QFileInfo fi(request.url().path());
490  QString basename(fi.completeBaseName());
491  QString extension = fi.suffix().toLower();
492  QString mimetype = getReplyMimetype();
493 
494  // if we have a default filename use that
495  QString saveBaseName = basename;
496 
497  if (!m_parentBrowser->GetDefaultSaveFilename().isEmpty())
498  {
499  QFileInfo savefi(m_parentBrowser->GetDefaultSaveFilename());
500  saveBaseName = savefi.completeBaseName();
501  }
502 
503  // if the filename is still empty use a default name
504  if (saveBaseName.isEmpty())
505  saveBaseName = "unnamed_download";
506 
507  // if we don't have an extension from the filename get one from the mime type
508  if (extension.isEmpty())
509  extension = getExtensionForMimetype(mimetype);
510 
511  if (!extension.isEmpty())
512  extension = '.' + extension;
513 
514  QString saveFilename = QString("%1%2%3")
516  .arg(saveBaseName)
517  .arg(extension);
518 
519  // dont overwrite an existing file
520  if (QFile::exists(saveFilename))
521  {
522  int i = 1;
523 
524  do
525  {
526  saveFilename = QString("%1%2-%3%4")
528  .arg(saveBaseName)
529  .arg(QString::number(i++))
530  .arg(extension);
531  }
532  while (QFile::exists(saveFilename));
533  }
534 
535  // if we are downloading and then playing the file don't ask for the file name
536  if (m_downloadAndPlay)
537  {
538  doDownload(saveFilename);
539  }
540  else
541  {
542  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
543 
544  QString msg = tr("Enter filename to save file");
545  MythTextInputDialog *input = new MythTextInputDialog(popupStack, msg,
546  FilterNone, false,
547  saveFilename);
548 
549  if (input->Create())
550  {
551  input->SetReturnEvent(this, "filenamedialog");
552  popupStack->AddScreen(input);
553  }
554  else
555  delete input;
556  }
557 }
558 
559 void MythWebView::doDownload(const QString &saveFilename)
560 {
561  if (saveFilename.isEmpty())
562  return;
563 
564  openBusyPopup(QObject::tr("Downloading file. Please wait..."));
565 
566  // No need to make sure the path to saveFilename exists because
567  // MythDownloadManage takes care of that
569  saveFilename, this);
570 }
571 
572 void MythWebView::openBusyPopup(const QString &message)
573 {
574  if (m_busyPopup)
575  return;
576 
577  QString msg(tr("Downloading..."));
578 
579  if (!message.isEmpty())
580  msg = message;
581 
582  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
583  m_busyPopup = new MythUIBusyDialog(msg, popupStack, "downloadbusydialog");
584 
585  if (m_busyPopup->Create())
586  popupStack->AddScreen(m_busyPopup, false);
587 }
588 
590 {
591  if (m_busyPopup)
592  m_busyPopup->Close();
593 
594  m_busyPopup = NULL;
595 }
596 
597 void MythWebView::customEvent(QEvent *event)
598 {
599  if (event->type() == DialogCompletionEvent::kEventType)
600  {
602 
603  // make sure the user didn't ESCAPE out of the dialog
604  if (dce->GetResult() < 0)
605  return;
606 
607  QString resultid = dce->GetId();
608  QString resulttext = dce->GetResultText();
609 
610  if (resultid == "filenamedialog")
611  doDownload(resulttext);
612  else if (resultid == "downloadmenu")
613  {
614  if (resulttext == tr("Play the file"))
615  {
616  QFileInfo fi(m_downloadRequest.url().path());
617  QString basename(fi.baseName());
618  QString extension = fi.suffix();
619  QString mimeType = getReplyMimetype();
620 
621  if (isMusicFile(extension, mimeType))
622  {
623  MythEvent me(QString("MUSIC_COMMAND %1 PLAY_URL %2")
624  .arg(gCoreContext->GetHostName())
625  .arg(m_downloadRequest.url().toString()));
626  gCoreContext->dispatch(me);
627  }
628  else if (isVideoFile(extension, mimeType))
629  GetMythMainWindow()->HandleMedia("Internal",
630  m_downloadRequest.url().toString());
631  else
632  LOG(VB_GENERAL, LOG_ERR,
633  QString("MythWebView: Asked to play a file with "
634  "extension '%1' but don't know how")
635  .arg(extension));
636  }
637  else if (resulttext == tr("Download the file"))
638  {
640  }
641  else if (resulttext == tr("Download and play the file"))
642  {
643  m_downloadAndPlay = true;
645  }
646  }
647  }
648  else if ((MythEvent::Type)(event->type()) == MythEvent::MythEventMessage)
649  {
650  MythEvent *me = (MythEvent *)event;
651  QStringList tokens = me->Message().split(" ", QString::SkipEmptyParts);
652 
653  if (tokens.isEmpty())
654  return;
655 
656  if (tokens[0] == "DOWNLOAD_FILE")
657  {
658  QStringList args = me->ExtraDataList();
659 
660  if (tokens[1] == "UPDATE")
661  {
662  // could update a progressbar here
663  }
664  else if (tokens[1] == "FINISHED")
665  {
666  int fileSize = args[2].toInt();
667  int errorCode = args[4].toInt();
668  QString filename = args[1];
669 
670  closeBusyPopup();
671 
672  if ((errorCode != 0) || (fileSize == 0))
673  ShowOkPopup(tr("ERROR downloading file."));
674  else if (m_downloadAndPlay)
675  GetMythMainWindow()->HandleMedia("Internal", filename);
676 
677  MythEvent me(QString("BROWSER_DOWNLOAD_FINISHED"), args);
678  gCoreContext->dispatch(me);
679  }
680  }
681  }
682 }
683 
685 {
686  QFileInfo fi(m_downloadRequest.url().path());
687  QString basename(fi.baseName());
688  QString extension = fi.suffix();
689  QString mimeType = getReplyMimetype();
690 
691  QString label = tr("What do you want to do with this file?");
692 
693  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
694 
695  MythDialogBox *menu = new MythDialogBox(label, popupStack, "downloadmenu");
696 
697  if (!menu->Create())
698  {
699  delete menu;
700  return;
701  }
702 
703  menu->SetReturnEvent(this, "downloadmenu");
704 
705  if (isMusicFile(extension, mimeType))
706  menu->AddButton(tr("Play the file"));
707 
708  if (isVideoFile(extension, mimeType))
709  menu->AddButton(tr("Download and play the file"));
710 
711  menu->AddButton(tr("Download the file"));
712  menu->AddButton(tr("Cancel"));
713 
714  popupStack->AddScreen(menu);
715 }
716 
717 QString MythWebView::getExtensionForMimetype(const QString &mimetype)
718 {
719  for (int x = 0; x < SupportedMimeTypesCount; x++)
720  {
721  if (!mimetype.isEmpty() && mimetype == SupportedMimeTypes[x].mimeType)
722  return SupportedMimeTypes[x].extension;
723  }
724 
725  return QString("");
726 }
727 
728 bool MythWebView::isMusicFile(const QString &extension, const QString &mimetype)
729 {
730  for (int x = 0; x < SupportedMimeTypesCount; x++)
731  {
732  if (!SupportedMimeTypes[x].isVideo)
733  {
734  if (!mimetype.isEmpty() &&
735  mimetype == SupportedMimeTypes[x].mimeType)
736  return true;
737 
738  if (!extension.isEmpty() &&
739  extension.toLower() == SupportedMimeTypes[x].extension)
740  return true;
741  }
742  }
743 
744  return false;
745 }
746 
747 bool MythWebView::isVideoFile(const QString &extension, const QString &mimetype)
748 {
749  for (int x = 0; x < SupportedMimeTypesCount; x++)
750  {
751  if (SupportedMimeTypes[x].isVideo)
752  {
753  if (!mimetype.isEmpty() &&
754  mimetype == SupportedMimeTypes[x].mimeType)
755  return true;
756 
757  if (!extension.isEmpty() &&
758  extension.toLower() == SupportedMimeTypes[x].extension)
759  return true;
760  }
761  }
762 
763  return false;
764 }
765 
767 {
768  if (!m_downloadReply)
769  return QString();
770 
771  QString mimeType;
772  QVariant header = m_downloadReply->header(QNetworkRequest::ContentTypeHeader);
773 
774  if (header != QVariant())
775  mimeType = header.toString();
776 
777  return mimeType;
778 }
779 
780 QWebView *MythWebView::createWindow(QWebPage::WebWindowType type)
781 {
782  (void) type;
783  return (QWebView *) this;
784 }
785 
786 
828 MythUIWebBrowser::MythUIWebBrowser(MythUIType *parent, const QString &name)
829  : MythUIType(parent, name),
830  m_parentScreen(NULL),
831  m_browser(NULL), m_browserArea(MythRect()),
832  m_actualBrowserArea(MythRect()), m_image(NULL),
833  m_active(false), m_wasActive(false),
834  m_initialized(false), m_lastUpdateTime(QTime()),
835  m_updateInterval(0), m_zoom(1.0),
836  m_bgColor("White"), m_widgetUrl(QUrl()), m_userCssFile(""),
837  m_defaultSaveDir(GetConfDir() + "/MythBrowser/"),
838  m_defaultSaveFilename(""),
839  m_inputToggled(false), m_lastMouseAction(""),
840  m_mouseKeyCount(0), m_lastMouseActionTime(),
841  m_horizontalScrollbar(NULL), m_verticalScrollbar(NULL)
842 {
843  SetCanTakeFocus(true);
844  m_scrollAnimation.setDuration(0);
845 }
846 
851 {
853 
854  Init();
855 }
856 
864 void MythUIWebBrowser::Init(void)
865 {
866  if (m_initialized)
867  return;
868 
871  m_actualBrowserArea.translate(m_Area.x(), m_Area.y());
872 
873  if (!m_actualBrowserArea.isValid())
875 
876  m_browser = new MythWebView(GetMythMainWindow()->GetPaintWindow(), this);
877  m_browser->setPalette(qApp->style()->standardPalette());
878  m_browser->setGeometry(m_actualBrowserArea);
879  m_browser->setFixedSize(m_actualBrowserArea.size());
881  m_browser->page()->setLinkDelegationPolicy(QWebPage::DontDelegateLinks);
882 
883  bool err = false;
884  UIUtilW::Assign(this, m_horizontalScrollbar, "horizscrollbar", &err);
885  UIUtilW::Assign(this, m_verticalScrollbar, "vertscrollbar", &err);
887  {
888  QWebFrame* frame = m_browser->page()->currentFrame();
889  frame->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff);
890  connect(m_horizontalScrollbar, SIGNAL(Hiding()),
891  this, SLOT(slotScrollBarHiding()));
892  connect(m_horizontalScrollbar, SIGNAL(Showing()),
893  this, SLOT(slotScrollBarShowing()));
894  }
895 
897  {
898  QWebFrame* frame = m_browser->page()->currentFrame();
899  frame->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff);
900  connect(m_verticalScrollbar, SIGNAL(Hiding()),
901  this, SLOT(slotScrollBarHiding()));
902  connect(m_verticalScrollbar, SIGNAL(Showing()),
903  this, SLOT(slotScrollBarShowing()));
904  }
905 
906  // if we have a valid css URL use that ...
907  if (!m_userCssFile.isEmpty())
908  {
909  QString filename = m_userCssFile;
910 
911  if (GetMythUI()->FindThemeFile(filename))
912  LoadUserStyleSheet(QUrl("file://" + filename));
913  }
914  else
915  {
916  // ...otherwise use the default one
917  QString filename = "htmls/mythbrowser.css";
918 
919  if (GetMythUI()->FindThemeFile(filename))
920  LoadUserStyleSheet(QUrl("file://" + filename));
921  }
922 
923  m_browser->winId();
924 
926 
927  connect(m_browser, SIGNAL(loadStarted()),
928  this, SLOT(slotLoadStarted()));
929  connect(m_browser, SIGNAL(loadFinished(bool)),
930  this, SLOT(slotLoadFinished(bool)));
931  connect(m_browser, SIGNAL(loadProgress(int)),
932  this, SLOT(slotLoadProgress(int)));
933  connect(m_browser, SIGNAL(titleChanged(const QString &)),
934  this, SLOT(slotTitleChanged(const QString &)));
935  connect(m_browser, SIGNAL(iconChanged(void)),
936  this, SLOT(slotIconChanged(void)));
937  connect(m_browser, SIGNAL(statusBarMessage(const QString &)),
938  this, SLOT(slotStatusBarMessage(const QString &)));
939  connect(m_browser->page(), SIGNAL(linkHovered(const QString &,
940  const QString &,
941  const QString &)),
942  this, SLOT(slotStatusBarMessage(const QString &)));
943  connect(m_browser, SIGNAL(linkClicked(const QUrl &)),
944  this, SLOT(slotLinkClicked(const QUrl &)));
945 
946  // find what screen we are on
947  m_parentScreen = NULL;
948  QObject *parentObject = parent();
949 
950  while (parentObject)
951  {
952  m_parentScreen = dynamic_cast<MythScreenType *>(parentObject);
953 
954  if (m_parentScreen)
955  break;
956 
957  parentObject = parentObject->parent();
958  }
959 
960  if (!m_parentScreen)
961  LOG(VB_GENERAL, LOG_ERR,
962  "MythUIWebBrowser: failed to find our parent screen");
963 
964  // connect to the topScreenChanged signals on each screen stack
965  for (int x = 0; x < GetMythMainWindow()->GetStackCount(); x++)
966  {
968 
969  if (stack)
970  connect(stack, SIGNAL(topScreenChanged(MythScreenType *)),
971  this, SLOT(slotTopScreenChanged(MythScreenType *)));
972  }
973 
974  // set up the icon cache directory
975  QString path = GetConfDir();
976  QDir dir(path);
977 
978  if (!dir.exists())
979  dir.mkdir(path);
980 
981  path += "/MythBrowser";
982  dir.setPath(path);
983 
984  if (!dir.exists())
985  dir.mkdir(path);
986 
987  QWebSettings::setIconDatabasePath(path);
988 
989  if (gCoreContext->GetNumSetting("WebBrowserEnablePlugins", 1) == 1)
990  {
991  LOG(VB_GENERAL, LOG_INFO, "MythUIWebBrowser: enabling plugins");
992  QWebSettings::globalSettings()->setAttribute(QWebSettings::PluginsEnabled,
993  true);
994  }
995  else
996  {
997  LOG(VB_GENERAL, LOG_INFO, "MythUIWebBrowser: disabling plugins");
998  QWebSettings::globalSettings()->setAttribute(QWebSettings::PluginsEnabled,
999  false);
1000  }
1001 
1002  QImage image = QImage(m_actualBrowserArea.size(), QImage::Format_ARGB32);
1004  m_image->Assign(image);
1005 
1007 
1008  m_zoom = gCoreContext->GetFloatSetting("WebBrowserZoomLevel", 1.0);
1009 
1010  SetZoom(m_zoom);
1011 
1012  if (!m_widgetUrl.isEmpty() && m_widgetUrl.isValid())
1014 
1015  m_initialized = true;
1016 }
1017 
1022 {
1023  if (m_browser)
1024  {
1025  m_browser->hide();
1026  m_browser->disconnect();
1027  m_browser->deleteLater();
1028  m_browser = NULL;
1029  }
1030 
1031  if (m_image)
1032  {
1033  m_image->DecrRef();
1034  m_image = NULL;
1035  }
1036 }
1037 
1042 void MythUIWebBrowser::LoadPage(QUrl url)
1043 {
1044  if (!m_browser)
1045  return;
1046 
1047  ResetScrollBars();
1048 
1049  m_browser->setUrl(url);
1050 }
1051 
1058 void MythUIWebBrowser::SetHtml(const QString &html, const QUrl &baseUrl)
1059 {
1060  if (!m_browser)
1061  return;
1062 
1063  ResetScrollBars();
1064 
1065  m_browser->setHtml(html, baseUrl);
1066 }
1067 
1073 {
1074  if (!m_browser)
1075  return;
1076 
1077  LOG(VB_GENERAL, LOG_INFO,
1078  "MythUIWebBrowser: Loading css from - " + url.toString());
1079 
1080  m_browser->page()->settings()->setUserStyleSheetUrl(url);
1081 }
1082 
1090 {
1091  if (!m_browser)
1092  return;
1093 
1094  color.setAlpha(255);
1095  QPalette palette = m_browser->page()->palette();
1096  palette.setBrush(QPalette::Window, QBrush(color));
1097  palette.setBrush(QPalette::Base, QBrush(color));
1098  m_browser->page()->setPalette(palette);
1099 
1100  UpdateBuffer();
1101 }
1102 
1113 void MythUIWebBrowser::SetActive(bool active)
1114 {
1115  if (m_active == active)
1116  return;
1117 
1118  m_active = active;
1119  m_wasActive = active;
1120 
1121  if (m_active)
1122  {
1123  m_browser->setUpdatesEnabled(false);
1124  m_browser->setFocus();
1125  m_browser->show();
1126  m_browser->raise();
1127  m_browser->setUpdatesEnabled(true);
1128  }
1129  else
1130  {
1131  m_browser->clearFocus();
1132  m_browser->hide();
1133  UpdateBuffer();
1134  }
1135 }
1136 
1140 void MythUIWebBrowser::ZoomIn(void)
1141 {
1142  SetZoom(m_zoom + 0.1);
1143 }
1144 
1148 void MythUIWebBrowser::ZoomOut(void)
1149 {
1150  SetZoom(m_zoom - 0.1);
1151 }
1152 
1157 void MythUIWebBrowser::SetZoom(float zoom)
1158 {
1159  if (!m_browser)
1160  return;
1161 
1162  if (zoom < 0.3)
1163  zoom = 0.3;
1164 
1165  if (zoom > 5.0)
1166  zoom = 5.0;
1167 
1168  m_zoom = zoom;
1169  m_browser->setZoomFactor(m_zoom);
1170  ResetScrollBars();
1171  UpdateBuffer();
1172 
1173  slotStatusBarMessage(tr("Zoom: %1%").arg(m_zoom * 100));
1174 
1175  gCoreContext->SaveSetting("WebBrowserZoomLevel", QString("%1").arg(m_zoom));
1176 }
1177 
1178 void MythUIWebBrowser::SetDefaultSaveDirectory(const QString &saveDir)
1179 {
1180  if (!saveDir.isEmpty())
1181  m_defaultSaveDir = saveDir;
1182  else
1183  m_defaultSaveDir = GetConfDir() + "/MythBrowser/";
1184 }
1185 
1186 void MythUIWebBrowser::SetDefaultSaveFilename(const QString &filename)
1187 {
1188  if (!filename.isEmpty())
1189  m_defaultSaveFilename = filename;
1190  else
1191  m_defaultSaveFilename.clear();
1192 }
1193 
1198 float MythUIWebBrowser::GetZoom(void)
1199 {
1200  return m_zoom;
1201 }
1202 
1209 {
1210  if (!m_browser)
1211  return false;
1212 
1213  return m_browser->history()->canGoForward();
1214 }
1215 
1221 bool MythUIWebBrowser::CanGoBack(void)
1222 {
1223  if (!m_browser)
1224  return false;
1225 
1226  return m_browser->history()->canGoBack();
1227 }
1228 
1232 void MythUIWebBrowser::Back(void)
1233 {
1234  if (!m_browser)
1235  return;
1236 
1237  m_browser->back();
1238 }
1239 
1243 void MythUIWebBrowser::Forward(void)
1244 {
1245  if (!m_browser)
1246  return;
1247 
1248  m_browser->forward();
1249 }
1250 
1255 QIcon MythUIWebBrowser::GetIcon(void)
1256 {
1257  if (m_browser)
1258  {
1259  return QWebSettings::iconForUrl(m_browser->url());
1260  }
1261  else
1262  return QIcon();
1263 }
1264 
1269 QUrl MythUIWebBrowser::GetUrl(void)
1270 {
1271  if (m_browser)
1272  {
1273  return m_browser->url();
1274  }
1275  else
1276  return QUrl();
1277 }
1278 
1283 QString MythUIWebBrowser::GetTitle(void)
1284 {
1285  if (m_browser)
1286  return m_browser->title();
1287  else
1288  return QString("");
1289 }
1290 
1295 QVariant MythUIWebBrowser::evaluateJavaScript(const QString &scriptSource)
1296 {
1297  if (m_browser)
1298  {
1299  QWebFrame *frame = m_browser->page()->currentFrame();
1300  return frame->evaluateJavaScript(scriptSource);
1301  }
1302  else
1303  return QVariant();
1304 }
1305 
1306 void MythUIWebBrowser::Scroll(int dx, int dy)
1307 {
1308  QPoint startPos = m_browser->page()->currentFrame()->scrollPosition();
1309  QPoint endPos = startPos + QPoint(dx, dy);
1310 
1311  if (GetPainter()->SupportsAnimation() && m_scrollAnimation.duration() > 0)
1312  {
1313  // Previous scroll has been completed
1314  if (m_destinationScrollPos == startPos)
1315  m_scrollAnimation.setEasingCurve(QEasingCurve::InOutCubic);
1316  else
1317  m_scrollAnimation.setEasingCurve(QEasingCurve::OutCubic);
1318 
1319  m_destinationScrollPos = endPos;
1320  m_scrollAnimation.setStartValue(startPos);
1323  }
1324  else
1325  {
1326  m_destinationScrollPos = endPos;
1327  m_browser->page()->currentFrame()->setScrollPosition(endPos);
1328  UpdateBuffer();
1329  }
1330 }
1331 
1333 {
1334  ResetScrollBars();
1335  emit loadStarted();
1336 }
1337 
1339 {
1340  UpdateBuffer();
1341  emit loadFinished(ok);
1342 }
1343 
1345 {
1346  emit loadProgress(progress);
1347 }
1348 
1349 void MythUIWebBrowser::slotTitleChanged(const QString &title)
1350 {
1351  emit titleChanged(title);
1352 }
1353 
1355 {
1356  emit statusBarMessage(text);
1357 }
1358 
1360 {
1361  LoadPage(url);
1362 }
1363 
1365 {
1366  emit iconChanged();
1367 }
1368 
1370 {
1371  bool wasActive = (m_wasActive | m_active);
1372  SetActive(false);
1373  m_wasActive = wasActive;
1374 }
1375 
1377 {
1379  slotTopScreenChanged(NULL);
1380 }
1381 
1383 {
1384  (void) screen;
1385 
1386  if (IsOnTopScreen())
1388  else
1389  {
1390  bool wasActive = (m_wasActive | m_active);
1391  SetActive(false);
1392  m_wasActive = wasActive;
1393  }
1394 }
1395 
1398 {
1399  if (!m_parentScreen)
1400  return false;
1401 
1402  for (int x = GetMythMainWindow()->GetStackCount() - 1; x >= 0; x--)
1403  {
1405 
1406  // ignore stacks with no screens on them
1407  if (!stack->GetTopScreen())
1408  continue;
1409 
1410  return (stack->GetTopScreen() == m_parentScreen);
1411  }
1412 
1413  return false;
1414 }
1415 
1416 
1418 {
1419  QPoint position = m_browser->page()->currentFrame()->scrollPosition();
1420  if (m_verticalScrollbar)
1421  {
1422  int maximum =
1423  m_browser->page()->currentFrame()->contentsSize().height() -
1424  m_actualBrowserArea.height();
1425  m_verticalScrollbar->SetMaximum(maximum);
1427  m_verticalScrollbar->SetSliderPosition(position.y());
1428  }
1429 
1431  {
1432  int maximum =
1433  m_browser->page()->currentFrame()->contentsSize().width() -
1434  m_actualBrowserArea.width();
1438  }
1439 }
1440 
1442 {
1443  UpdateScrollBars();
1444 
1445  if (!m_image)
1446  return;
1447 
1448  if (!m_active || (m_active && !m_browser->hasFocus()))
1449  {
1450  QPainter painter(m_image);
1451  m_browser->render(&painter);
1452  painter.end();
1453 
1454  m_image->SetChanged();
1455  Refresh();
1456  }
1457 }
1458 
1463 {
1464  if (m_scrollAnimation.IsActive() &&
1466  m_browser->page()->currentFrame()->scrollPosition())
1467  {
1469 
1470  QPoint scrollPosition = m_scrollAnimation.currentValue().toPoint();
1471  m_browser->page()->currentFrame()->setScrollPosition(scrollPosition);
1472 
1473  SetRedraw();
1474  UpdateBuffer();
1475  }
1476  else if (m_updateInterval && m_lastUpdateTime.elapsed() > m_updateInterval)
1477  {
1478  UpdateBuffer();
1479  m_lastUpdateTime.start();
1480  }
1481 
1483 }
1484 
1488 void MythUIWebBrowser::DrawSelf(MythPainter *p, int xoffset, int yoffset,
1489  int alphaMod, QRect clipRegion)
1490 {
1491  if (!m_image || m_image->isNull() || !m_browser || m_browser->hasFocus())
1492  return;
1493 
1494  QRect area = m_actualBrowserArea;
1495  area.translate(xoffset, yoffset);
1496 
1497  p->DrawImage(area.x(), area.y(), m_image, alphaMod);
1498 }
1499 
1503 bool MythUIWebBrowser::keyPressEvent(QKeyEvent *event)
1504 {
1505  QStringList actions;
1506  bool handled = false;
1507  handled = GetMythMainWindow()->TranslateKeyPress("Browser", event, actions);
1508 
1509  for (int i = 0; i < actions.size() && !handled; i++)
1510  {
1511  QString action = actions[i];
1512  handled = true;
1513 
1514  if (action == "TOGGLEINPUT")
1515  {
1517 
1518  if (m_inputToggled)
1519  slotStatusBarMessage(tr("Sending key presses to web page"));
1520  else
1521  slotStatusBarMessage(tr("Sending key presses to MythTV"));
1522 
1523  return true;
1524  }
1525 
1526  // if input is toggled all input goes to the web page
1527  if (m_inputToggled)
1528  {
1529  m_browser->keyPressEvent(event);
1530 
1531  return true;
1532  }
1533 
1534  QWebFrame *frame = m_browser->page()->currentFrame();
1535  if (action == "UP")
1536  {
1537  int pos = frame->scrollPosition().y();
1538 
1539  if (pos > 0)
1540  {
1541  Scroll(0, -m_actualBrowserArea.height() / 10);
1542  }
1543  else
1544  handled = false;
1545  }
1546  else if (action == "DOWN")
1547  {
1548  int pos = frame->scrollPosition().y();
1549  QSize maximum = frame->contentsSize() - m_actualBrowserArea.size();
1550 
1551  if (pos != maximum.height())
1552  {
1553  Scroll(0, m_actualBrowserArea.height() / 10);
1554  }
1555  else
1556  handled = false;
1557  }
1558  else if (action == "LEFT")
1559  {
1560  int pos = frame->scrollPosition().x();
1561 
1562  if (pos > 0)
1563  {
1564  Scroll(-m_actualBrowserArea.width() / 10, 0);
1565  }
1566  else
1567  handled = false;
1568  }
1569  else if (action == "RIGHT")
1570  {
1571  int pos = frame->scrollPosition().x();
1572  QSize maximum = frame->contentsSize() - m_actualBrowserArea.size();
1573 
1574  if (pos != maximum.width())
1575  {
1576  Scroll(m_actualBrowserArea.width() / 10, 0);
1577  }
1578  else
1579  handled = false;
1580  }
1581  else if (action == "PAGEUP")
1582  {
1583  Scroll(0, -m_actualBrowserArea.height());
1584  }
1585  else if (action == "PAGEDOWN")
1586  {
1587  Scroll(0, m_actualBrowserArea.height());
1588  }
1589  else if (action == "ZOOMIN")
1590  {
1591  ZoomIn();
1592  }
1593  else if (action == "ZOOMOUT")
1594  {
1595  ZoomOut();
1596  }
1597  else if (action == "MOUSEUP" || action == "MOUSEDOWN" ||
1598  action == "MOUSELEFT" || action == "MOUSERIGHT" ||
1599  action == "MOUSELEFTBUTTON")
1600  {
1601  HandleMouseAction(action);
1602  }
1603  else if (action == "PAGELEFT")
1604  {
1605  Scroll(-m_actualBrowserArea.width(), 0);
1606  }
1607  else if (action == "PAGERIGHT")
1608  {
1609  Scroll(m_actualBrowserArea.width(), 0);
1610  }
1611  else if (action == "NEXTLINK")
1612  {
1613  m_browser->keyPressEvent(event);
1614  }
1615  else if (action == "PREVIOUSLINK")
1616  {
1617  m_browser->keyPressEvent(event);
1618  }
1619  else if (action == "FOLLOWLINK")
1620  {
1621  m_browser->keyPressEvent(event);
1622  }
1623  else if (action == "HISTORYBACK")
1624  {
1625  Back();
1626  }
1627  else if (action == "HISTORYFORWARD")
1628  {
1629  Forward();
1630  }
1631  else
1632  handled = false;
1633  }
1634 
1635  return handled;
1636 }
1637 
1639 {
1640  int step = 5;
1641 
1642  // speed up mouse movement if the same key is held down
1643  if (action == m_lastMouseAction &&
1644  m_lastMouseActionTime.msecsTo(QTime::currentTime()) < 500)
1645  {
1646  m_lastMouseActionTime = QTime::currentTime();
1647  m_mouseKeyCount++;
1648 
1649  if (m_mouseKeyCount > 5)
1650  step = 25;
1651  }
1652  else
1653  {
1655  m_lastMouseActionTime = QTime::currentTime();
1656  m_mouseKeyCount = 1;
1657  }
1658 
1659  if (action == "MOUSEUP")
1660  {
1661  QPoint curPos = QCursor::pos();
1662  QCursor::setPos(curPos.x(), curPos.y() - step);
1663  }
1664  else if (action == "MOUSELEFT")
1665  {
1666  QPoint curPos = QCursor::pos();
1667  QCursor::setPos(curPos.x() - step, curPos.y());
1668  }
1669  else if (action == "MOUSERIGHT")
1670  {
1671  QPoint curPos = QCursor::pos();
1672  QCursor::setPos(curPos.x() + step, curPos.y());
1673  }
1674  else if (action == "MOUSEDOWN")
1675  {
1676  QPoint curPos = QCursor::pos();
1677  QCursor::setPos(curPos.x(), curPos.y() + step);
1678  }
1679  else if (action == "MOUSELEFTBUTTON")
1680  {
1681  QPoint curPos = QCursor::pos();
1682  QWidget *widget = QApplication::widgetAt(curPos);
1683 
1684  if (widget)
1685  {
1686  curPos = widget->mapFromGlobal(curPos);
1687 
1688  QMouseEvent *me = new QMouseEvent(QEvent::MouseButtonPress, curPos,
1689  Qt::LeftButton, Qt::LeftButton,
1690  Qt::NoModifier);
1691  QCoreApplication::postEvent(widget, me);
1692 
1693  me = new QMouseEvent(QEvent::MouseButtonRelease, curPos,
1694  Qt::LeftButton, Qt::NoButton, Qt::NoModifier);
1695  QCoreApplication::postEvent(widget, me);
1696  }
1697  }
1698 }
1699 
1701 {
1702  if (m_verticalScrollbar)
1703  {
1706  }
1707 
1709  {
1712  }
1713 }
1714 
1719  const QString &filename, QDomElement &element, bool showWarnings)
1720 {
1721  if (element.tagName() == "zoom")
1722  {
1723  QString zoom = getFirstText(element);
1724  m_zoom = zoom.toFloat();
1725  }
1726  else if (element.tagName() == "url")
1727  {
1728  m_widgetUrl.setUrl(getFirstText(element));
1729  }
1730  else if (element.tagName() == "userstylesheet")
1731  {
1732  m_userCssFile = getFirstText(element);
1733  }
1734  else if (element.tagName() == "updateinterval")
1735  {
1736  QString interval = getFirstText(element);
1737  m_updateInterval = interval.toInt();
1738  }
1739  else if (element.tagName() == "background")
1740  {
1741  m_bgColor = QColor(element.attribute("color", "#ffffff"));
1742  int alpha = element.attribute("alpha", "255").toInt();
1743  m_bgColor.setAlpha(alpha);
1744  }
1745  else if (element.tagName() == "browserarea")
1746  {
1747  m_browserArea = parseRect(element);
1748  }
1749  else if (element.tagName() == "scrollduration")
1750  {
1751  QString duration = getFirstText(element);
1752  m_scrollAnimation.setDuration(duration.toInt());
1753  }
1754  else if (element.tagName() == "acceptsfocus")
1755  {
1756  SetCanTakeFocus(parseBool(element));
1757  }
1758  else
1759  {
1760  return MythUIType::ParseElement(filename, element, showWarnings);
1761  }
1762 
1763  return true;
1764 }
1765 
1770 {
1771  MythUIWebBrowser *browser = dynamic_cast<MythUIWebBrowser *>(base);
1772 
1773  if (!browser)
1774  {
1775  LOG(VB_GENERAL, LOG_ERR, "ERROR, bad parsing");
1776  return;
1777  }
1778 
1779  MythUIType::CopyFrom(base);
1780 
1781  m_browserArea = browser->m_browserArea;
1782  m_zoom = browser->m_zoom;
1783  m_bgColor = browser->m_bgColor;
1784  m_widgetUrl = browser->m_widgetUrl;
1785  m_userCssFile = browser->m_userCssFile;
1789  m_scrollAnimation.setDuration(browser->m_scrollAnimation.duration());
1790 }
1791 
1796 {
1797  MythUIWebBrowser *browser = new MythUIWebBrowser(parent, objectName());
1798  browser->CopyFrom(this);
1799 }