summaryrefslogtreecommitdiff
path: root/src/vdr-plugin/menu.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/vdr-plugin/menu.c')
-rw-r--r--src/vdr-plugin/menu.c670
1 files changed, 670 insertions, 0 deletions
diff --git a/src/vdr-plugin/menu.c b/src/vdr-plugin/menu.c
new file mode 100644
index 0000000..3add4d4
--- /dev/null
+++ b/src/vdr-plugin/menu.c
@@ -0,0 +1,670 @@
+/*
+ * menu.c: Web video plugin for the Video Disk Recorder
+ *
+ * See the README file for copyright information and how to reach the author.
+ *
+ * $Id$
+ */
+
+#include <stdlib.h>
+#include <time.h>
+#include <vdr/skins.h>
+#include <vdr/tools.h>
+#include <vdr/i18n.h>
+#include <vdr/osdbase.h>
+#include <vdr/skins.h>
+#include <vdr/font.h>
+#include <vdr/osd.h>
+#include <vdr/interface.h>
+#include "menu.h"
+#include "download.h"
+#include "config.h"
+#include "common.h"
+#include "history.h"
+#include "timer.h"
+#include "menu_timer.h"
+
+cCharSetConv csc = cCharSetConv("UTF-8", cCharSetConv::SystemCharacterTable());
+struct MenuPointers menuPointers;
+
+// --- cXMLMenu --------------------------------------------------
+
+cXMLMenu::cXMLMenu(const char *Title, int c0, int c1, int c2,
+ int c3, int c4)
+: cOsdMenu(Title, c0, c1, c2, c3, c4)
+{
+}
+
+bool cXMLMenu::Deserialize(const char *xml) {
+ xmlDocPtr doc = xmlParseMemory(xml, strlen(xml));
+ if (!doc) {
+ xmlErrorPtr xmlerr = xmlGetLastError();
+ if (xmlerr) {
+ error("libxml error: %s", xmlerr->message);
+ }
+
+ return false;
+ }
+
+ xmlNodePtr node = xmlDocGetRootElement(doc);
+ if (node)
+ node = node->xmlChildrenNode;
+
+ while (node) {
+ if (node->type == XML_ELEMENT_NODE) {
+ if (!CreateItemFromTag(doc, node)) {
+ warning("Failed to parse menu tag: %s", (char *)node->name);
+ }
+ }
+ node = node->next;
+ }
+
+ xmlFreeDoc(doc);
+ return true;
+}
+
+int cXMLMenu::Load(const char *xmlstr) {
+ Clear();
+ Deserialize(xmlstr);
+
+ return 0;
+}
+
+
+// --- cNavigationMenu -----------------------------------------------------
+
+cNavigationMenu::cNavigationMenu(cHistory *History,
+ cProgressVector& dlsummaries)
+ : cXMLMenu("", 25), summaries(dlsummaries)
+{
+ title = NULL;
+ reference = NULL;
+ shortcutMode = 0;
+ history = History;
+ UpdateHelp();
+}
+
+cNavigationMenu::~cNavigationMenu() {
+ menuPointers.navigationMenu = NULL;
+ Clear();
+ if (reference)
+ free(reference);
+}
+
+bool cNavigationMenu::CreateItemFromTag(xmlDocPtr doc, xmlNodePtr node) {
+ if (!xmlStrcmp(node->name, BAD_CAST "link")) {
+ NewLinkItem(doc, node);
+ return true;
+ } else if (!xmlStrcmp(node->name, BAD_CAST "textfield")) {
+ NewTextField(doc, node);
+ return true;
+ } else if (!xmlStrcmp(node->name, BAD_CAST "itemlist")) {
+ NewItemList(doc, node);
+ return true;
+ } else if (!xmlStrcmp(node->name, BAD_CAST "textarea")) {
+ NewTextArea(doc, node);
+ return true;
+ } else if (!xmlStrcmp(node->name, BAD_CAST "button")) {
+ NewButton(doc, node);
+ return true;
+ } else if (!xmlStrcmp(node->name, BAD_CAST "title")) {
+ NewTitle(doc, node);
+ return true;
+ }
+
+ return false;
+}
+
+void cNavigationMenu::AddLinkItem(cOsdItem *item,
+ cLinkBase *ref,
+ cLinkBase *streamref) {
+ Add(item);
+
+ if (ref)
+ links.Append(ref);
+ else
+ links.Append(NULL);
+
+ if (streamref)
+ streams.Append(streamref);
+ else
+ streams.Append(NULL);
+}
+
+void cNavigationMenu::NewLinkItem(xmlDocPtr doc, xmlNodePtr node) {
+ // label, ref and object tags
+ xmlChar *itemtitle = NULL, *ref = NULL, *streamref = NULL;
+
+ node = node->xmlChildrenNode;
+ while (node) {
+ if (!xmlStrcmp(node->name, BAD_CAST "label")) {
+ if (itemtitle)
+ xmlFree(itemtitle);
+ itemtitle = xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
+ } else if (!xmlStrcmp(node->name, BAD_CAST "ref")) {
+ if (ref)
+ xmlFree(ref);
+ ref = xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
+ } else if (!xmlStrcmp(node->name, BAD_CAST "stream")) {
+ if (streamref)
+ xmlFree(streamref);
+ streamref = xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
+ }
+ node = node->next;
+ }
+ if (!itemtitle)
+ itemtitle = xmlCharStrdup("???");
+
+ const char *titleconv = csc.Convert((char *)itemtitle);
+ cOsdItem *item = new cOsdItem(titleconv);
+ cSimpleLink *objlinkdata = NULL;
+ cSimpleLink *linkdata = NULL;
+ if (ref)
+ linkdata = new cSimpleLink((char *)ref);
+ if (streamref) {
+ // media object
+ objlinkdata = new cSimpleLink((char *)streamref);
+ } else {
+ // navigation link
+ char *bracketed = (char *)malloc((strlen(titleconv)+3)*sizeof(char));
+ if (bracketed) {
+ bracketed[0] = '\0';
+ strcat(bracketed, "[");
+ strcat(bracketed, titleconv);
+ strcat(bracketed, "]");
+ item->SetText(bracketed, false);
+ }
+ }
+ AddLinkItem(item, linkdata, objlinkdata);
+
+ xmlFree(itemtitle);
+ if (ref)
+ xmlFree(ref);
+ if (streamref)
+ xmlFree(streamref);
+}
+
+void cNavigationMenu::NewTextField(xmlDocPtr doc, xmlNodePtr node) {
+ // name attribute
+ xmlChar *name = xmlGetProp(node, BAD_CAST "name");
+ cHistoryObject *curhistpage = history->Current();
+
+ // label tag
+ xmlChar *text = NULL;
+ node = node->xmlChildrenNode;
+ while (node) {
+ if (!xmlStrcmp(node->name, BAD_CAST "label")) {
+ if (text)
+ xmlFree(text);
+ text = xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
+ }
+ node = node->next;
+ }
+ if (!text)
+ text = xmlCharStrdup("???");
+
+ cTextFieldData *data = curhistpage->GetTextFieldData((char *)name);
+ cMenuEditStrItem *item = new cMenuEditStrItem(csc.Convert((char *)text),
+ data->GetValue(),
+ data->GetLength());
+ AddLinkItem(item, NULL, NULL);
+
+ free(text);
+ if (name)
+ xmlFree(name);
+}
+
+void cNavigationMenu::NewItemList(xmlDocPtr doc, xmlNodePtr node) {
+ // name attribute
+ xmlChar *name = xmlGetProp(node, BAD_CAST "name");
+ cHistoryObject *curhistpage = history->Current();
+
+ // label and item tags
+ xmlChar *text = NULL;
+ cStringList items;
+ cStringList itemvalues;
+ node = node->xmlChildrenNode;
+ while (node) {
+ if (!xmlStrcmp(node->name, BAD_CAST "label")) {
+ if (text)
+ xmlFree(text);
+ text = xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
+ } else if (!xmlStrcmp(node->name, BAD_CAST "item")) {
+ xmlChar *str = xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
+ if (!str)
+ str = xmlCharStrdup("???");
+ xmlChar *strvalue = xmlGetProp(node, BAD_CAST "value");
+ if (!strvalue)
+ strvalue = xmlCharStrdup("");
+
+ items.Append(strdup((char *)str));
+ itemvalues.Append(strdup((char *)strvalue));
+
+ xmlFree(str);
+ xmlFree(strvalue);
+ }
+ node = node->next;
+ }
+ if (!text)
+ text = xmlCharStrdup("???");
+
+ cItemListData *data = curhistpage->GetItemListData((const char *)name,
+ items,
+ itemvalues);
+
+ cMenuEditStraItem *item = new cMenuEditStraItem(csc.Convert((char *)text),
+ data->GetValuePtr(),
+ data->GetNumStrings(),
+ data->GetStrings());
+ AddLinkItem(item, NULL, NULL);
+
+ xmlFree(text);
+ if (name)
+ xmlFree(name);
+}
+
+void cNavigationMenu::NewTextArea(xmlDocPtr doc, xmlNodePtr node) {
+ // label tag
+ xmlChar *itemtitle = NULL;
+ node = node->xmlChildrenNode;
+ while (node) {
+ if (!xmlStrcmp(node->name, BAD_CAST "label")) {
+ if (itemtitle)
+ xmlFree(itemtitle);
+ itemtitle = xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
+ }
+ node = node->next;
+ }
+ if (!itemtitle)
+ return;
+
+ const cFont *font = cFont::GetFont(fontOsd);
+ cTextWrapper tw(csc.Convert((char *)itemtitle), font, cOsd::OsdWidth());
+ for (int i=0; i < tw.Lines(); i++) {
+ AddLinkItem(new cOsdItem(tw.GetLine(i), osUnknown, false), NULL, NULL);
+ }
+
+ xmlFree(itemtitle);
+}
+
+void cNavigationMenu::NewButton(xmlDocPtr doc, xmlNodePtr node) {
+ // label and submission tags
+ xmlChar *itemtitle = NULL, *submission = NULL;
+ cHistoryObject *curhistpage = history->Current();
+
+ node = node->xmlChildrenNode;
+ while (node) {
+ if (!xmlStrcmp(node->name, BAD_CAST "label")) {
+ if (itemtitle)
+ xmlFree(itemtitle);
+ itemtitle = xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
+ } else if (!xmlStrcmp(node->name, BAD_CAST "submission")) {
+ if (submission)
+ xmlFree(submission);
+ submission = xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
+ }
+ node = node->next;
+ }
+ if (!itemtitle)
+ itemtitle = xmlCharStrdup("???");
+
+ cSubmissionButtonData *data = \
+ new cSubmissionButtonData((char *)submission, curhistpage);
+ const char *titleconv = csc.Convert((char *)itemtitle); // do not free
+ char *newtitle = (char *)malloc((strlen(titleconv)+3)*sizeof(char));
+ if (newtitle) {
+ newtitle[0] = '\0';
+ strcat(newtitle, "[");
+ strcat(newtitle, titleconv);
+ strcat(newtitle, "]");
+
+ cOsdItem *item = new cOsdItem(newtitle);
+ AddLinkItem(item, data, NULL);
+ free(newtitle);
+ }
+
+ xmlFree(itemtitle);
+ if (submission)
+ xmlFree(submission);
+}
+
+void cNavigationMenu::NewTitle(xmlDocPtr doc, xmlNodePtr node) {
+ xmlChar *newtitle = xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
+ if (newtitle) {
+ const char *conv = csc.Convert((char *)newtitle);
+ SetTitle(conv);
+ if (title)
+ free(title);
+ title = strdup(conv);
+ xmlFree(newtitle);
+ }
+}
+
+eOSState cNavigationMenu::ProcessKey(eKeys Key)
+{
+ cWebviTimer *timer;
+ bool hasStreams;
+ int old = Current();
+ eOSState state = cXMLMenu::ProcessKey(Key);
+ bool validItem = Current() >= 0 && Current() < links.Size();
+
+ if (HasSubMenu())
+ return state;
+
+ if (state == osUnknown) {
+ switch (Key) {
+ case kInfo:
+ // The alternative link is active only when object links are
+ // present.
+ if (validItem && streams.At(Current()))
+ state = Select(links.At(Current()), LT_REGULAR);
+ break;
+
+ case kOk:
+ // Primary action: download media object or, if not a media
+ // link, follow the navigation link.
+ if (validItem) {
+ if (streams.At(Current()))
+ state = Select(streams.At(Current()), LT_MEDIA);
+ else
+ state = Select(links.At(Current()), LT_REGULAR);
+ }
+ break;
+
+ case kRed:
+ if (shortcutMode == 0) {
+ state = HistoryBack();
+ } else {
+ menuPointers.statusScreen = new cStatusScreen(summaries);
+ state = AddSubMenu(menuPointers.statusScreen);
+ }
+ break;
+
+ case kGreen:
+ if (shortcutMode == 0) {
+ state = HistoryForward();
+ } else {
+ return AddSubMenu(new cWebviTimerListMenu(cWebviTimerManager::Instance()));
+ }
+ break;
+
+ case kYellow:
+ if (shortcutMode == 0) {
+ hasStreams = false;
+ for (int i=0; i < streams.Size(); i++) {
+ if (streams[i]) {
+ hasStreams = true;
+ break;
+ }
+ }
+
+ if (hasStreams || Interface->Confirm(tr("No streams on this page, create timer anyway?"))) {
+ timer = cWebviTimerManager::Instance().Create(title, reference);
+ if (timer)
+ return AddSubMenu(new cEditWebviTimerMenu(*timer, true, false));
+ }
+
+ state = osContinue;
+ }
+ break;
+
+ case kBlue:
+ if (shortcutMode == 0) {
+ // Secondary action: start streaming if a media object
+ if (validItem && streams.At(Current()))
+ state = Select(streams.At(Current()), LT_STREAMINGMEDIA);
+ }
+ break;
+
+ case k0:
+ shortcutMode = shortcutMode == 0 ? 1 : 0;
+ UpdateHelp();
+ break;
+
+ default:
+ break;
+ }
+ } else {
+ // If the key press caused the selected item to change, we need to
+ // update the help texts.
+ //
+ // In cMenuEditStrItem key == kOk with state == osContinue
+ // indicates leaving the edit mode. We want to update the help
+ // texts in this case also.
+ if ((old != Current()) ||
+ ((Key == kOk) && (state == osContinue))) {
+ UpdateHelp();
+ }
+ }
+
+ return state;
+}
+
+eOSState cNavigationMenu::Select(cLinkBase *link, eLinkType type)
+{
+ if (!link) {
+ return osContinue;
+ }
+ char *ref = link->GetURL();
+ if (!ref) {
+ error("link->GetURL() == NULL in cNavigationMenu::Select");
+ return osContinue;
+ }
+
+ if (type == LT_MEDIA) {
+ cDownloadProgress *progress = summaries.NewDownload();
+ cFileDownloadRequest *req = \
+ new cFileDownloadRequest(history->Current()->GetID(), ref,
+ webvideoConfig->GetDownloadPath(),
+ progress);
+ cWebviThread::Instance().AddRequest(req);
+
+ Skins.Message(mtInfo, tr("Downloading in the background"));
+ } else if (type == LT_STREAMINGMEDIA) {
+ cWebviThread::Instance().AddRequest(new cStreamUrlRequest(history->Current()->GetID(),
+ ref));
+ Skins.Message(mtInfo, tr("Starting player..."));
+ return osEnd;
+ } else {
+ cWebviThread::Instance().AddRequest(new cMenuRequest(history->Current()->GetID(),
+ ref));
+ Skins.Message(mtStatus, tr("Retrieving..."));
+ }
+
+ return osContinue;
+}
+
+void cNavigationMenu::Clear(void) {
+ cXMLMenu::Clear();
+ SetTitle("");
+ if (title)
+ free(title);
+ title = NULL;
+ for (int i=0; i < links.Size(); i++) {
+ if (links[i])
+ delete links[i];
+ if (streams[i])
+ delete streams[i];
+ }
+ links.Clear();
+ streams.Clear();
+}
+
+void cNavigationMenu::Populate(const cHistoryObject *page, const char *statusmsg) {
+ Load(page->GetOSD());
+
+ if (reference)
+ free(reference);
+ reference = strdup(page->GetReference());
+
+ // Make sure that an item is selected (if there is at least
+ // one). The help texts are not updated correctly if no item is
+ // selected.
+
+ SetCurrent(Get(page->GetSelected()));
+ UpdateHelp();
+ SetStatus(statusmsg);
+}
+
+eOSState cNavigationMenu::HistoryBack() {
+ cHistoryObject *cur = history->Current();
+
+ if (cur)
+ cur->RememberSelected(Current());
+
+ cHistoryObject *page = history->Back();
+ if (page) {
+ Populate(page);
+ Display();
+ }
+ return osContinue;
+}
+
+eOSState cNavigationMenu::HistoryForward() {
+ cHistoryObject *before = history->Current();
+ cHistoryObject *after = history->Forward();
+
+ if (before)
+ before->RememberSelected(Current());
+
+ // Update only if the menu really changed
+ if (before != after) {
+ Populate(after);
+ Display();
+ }
+ return osContinue;
+}
+
+void cNavigationMenu::UpdateHelp() {
+ const char *red = NULL;
+ const char *green = NULL;
+ const char *yellow = NULL;
+ const char *blue = NULL;
+
+ if (shortcutMode == 0) {
+ red = (history->Current() != history->First()) ? tr("Back") : NULL;
+ green = (history->Current() != history->Last()) ? tr("Forward") : NULL;
+ yellow = (Current() >= 0) ? tr("Create timer") : NULL;
+ blue = ((Current() >= 0) && (streams.At(Current()))) ? tr("Play") : NULL;
+ } else {
+ red = tr("Status");
+ green = tr("Timers");
+ }
+
+ SetHelp(red, green, yellow, blue);
+}
+
+// --- cStatusScreen -------------------------------------------------------
+
+cStatusScreen::cStatusScreen(cProgressVector& dlsummaries)
+ : cOsdMenu(tr("Unfinished downloads"), 40), summaries(dlsummaries)
+{
+ int charsperline = cOsd::OsdWidth() / cFont::GetFont(fontOsd)->Width('M');
+ SetCols(charsperline-5);
+
+ UpdateHelp();
+ Update();
+}
+
+cStatusScreen::~cStatusScreen() {
+ menuPointers.statusScreen = NULL;
+}
+
+void cStatusScreen::Update() {
+ int c = Current();
+
+ Clear();
+
+ if (summaries.Size() == 0) {
+ SetTitle(tr("No active downloads"));
+ } else {
+
+ for (int i=0; i<summaries.Size(); i++) {
+ cString dltitle;
+ cDownloadProgress *s = summaries[i];
+ dltitle = cString::sprintf("%s\t%s",
+ (const char *)s->GetTitle(),
+ (const char *)s->GetPercentage());
+
+ Add(new cOsdItem(dltitle));
+ }
+
+ if (c >= 0)
+ SetCurrent(Get(c));
+ }
+
+ lastupdate = time(NULL);
+
+ UpdateHelp();
+ Display();
+}
+
+bool cStatusScreen::NeedsUpdate() {
+ return (Count() > 0) && (time(NULL) - lastupdate >= updateInterval);
+}
+
+eOSState cStatusScreen::ProcessKey(eKeys Key) {
+ cFileDownloadRequest *req;
+ int old = Current();
+ eOSState state = cOsdMenu::ProcessKey(Key);
+
+ if (HasSubMenu())
+ return state;
+
+ if (state == osUnknown) {
+ switch (Key) {
+ case kYellow:
+ if ((Current() >= 0) && (Current() < summaries.Size())) {
+ if (summaries[Current()]->IsFinished()) {
+ delete summaries[Current()];
+ summaries.Remove(Current());
+ Update();
+ } else if ((req = summaries[Current()]->GetRequest()) &&
+ !req->IsFinished()) {
+ req->Abort();
+ Update();
+ }
+ }
+ return osContinue;
+
+ case kOk:
+ case kInfo:
+ if (summaries[Current()]->Error()) {
+ cString msg = cString::sprintf("%s\n%s: %s",
+ (const char *)summaries[Current()]->GetTitle(),
+ tr("Error"),
+ (const char *)summaries[Current()]->GetStatusPharse());
+ return AddSubMenu(new cMenuText(tr("Error details"), msg));
+ } else {
+ cString msg = cString::sprintf("%s (%s)",
+ (const char *)summaries[Current()]->GetTitle(),
+ (const char *)summaries[Current()]->GetPercentage());
+ return AddSubMenu(new cMenuText(tr("Download details"), msg));
+ }
+
+ return osContinue;
+
+ default:
+ break;
+ }
+ } else {
+ // Update help if the key press caused the menu item to change.
+ if (old != Current())
+ UpdateHelp();
+ }
+
+ return state;
+}
+
+void cStatusScreen::UpdateHelp() {
+ bool remove = false;
+ if ((Current() >= 0) && (Current() < summaries.Size())) {
+ if (summaries[Current()]->IsFinished()) {
+ remove = true;
+ }
+ }
+
+ const char *yellow = remove ? tr("Remove") : tr("Abort");
+
+ SetHelp(NULL, NULL, yellow, NULL);
+}