diff options
author | Dieter Hametner <dh (plus) vdr (at) gekrumbel (dot) de> | 2007-07-12 19:10:34 +0000 |
---|---|---|
committer | Dieter Hametner <dh (plus) vdr (at) gekrumbel (dot) de> | 2007-07-12 19:10:34 +0000 |
commit | 7b003f8aaafc2d95dcf7c9dfc5cbc6288b37915c (patch) | |
tree | 35ba447699c1fd1c1f41dd672fcc1e127d6ea3cc | |
parent | 9f65a960ca7d4cc3819e1434de05b9428acc23ad (diff) | |
download | vdr-plugin-live-7b003f8aaafc2d95dcf7c9dfc5cbc6288b37915c.tar.gz vdr-plugin-live-7b003f8aaafc2d95dcf7c9dfc5cbc6288b37915c.tar.bz2 |
- Update to the mootools framework.
- New more XHTML compliant tips.
- Optional AJAX enabled infoboxes for epg information.
- Major speed enhancement for the single pages, due to less data to
transfer to the browser.
- See doc/ChangeLog for more detailed changes description.
- See doc/dev-conventions.txt for how we benefit from mootools package
on the ECMAScript side of live.
44 files changed, 2270 insertions, 616 deletions
diff --git a/css/styles.css b/css/styles.css index 69e0fa7..0e56603 100644 --- a/css/styles.css +++ b/css/styles.css @@ -89,68 +89,147 @@ img { ###################### */ -div.domTThint { - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 11px; - border: 1px solid #EBC94C; - background-color: #F4FFC3; - max-width: 35em; +.hint-tip { + margin: 0px auto; + max-width: 480px; /* depends on the tip backround image width */ + color: #fff; } -div.domTThint .caption { - font-weight: bold; +.hint-tip .hint-tip-top .hint-tip-c, +.hint-tip .hint-tip-bot .hint-tip-c { + font-size: 1px; /* ensure minimum height */ + height: 17px; +} + +.hint-tip .hint-tip-top { + background: transparent url(img/tip-hint-tl.png) no-repeat 0px 0px; + margin-right: 17px; /* space for right corner */ +} + +.hint-tip .hint-tip-top .hint-tip-c { + background: transparent url(img/tip-hint-tr.png) no-repeat right 0px; + margin-right: -17px; /* pull right corner back over "empty" space (from above margin) */ +} + +.hint-tip .hint-tip-bdy { + background: transparent url(img/tip-hint-ml.png) repeat-y 0px 0px; + margin-right: 17px; +} + +.hint-tip .hint-tip-bdy .hint-tip-c { + background: transparent url(img/tip-hint-mr.png) repeat-y right 0px; + margin-right: -17px; } -div.domTThint .contents { - padding: 2px; +.hint-tip .hint-tip-bdy .hint-tip-c .hint-tip-s { /* optional gradient overlay */ + /* background: transparent url(img/tip-hint-ms.jpg) repeat-x 0px 0px; */ + padding: 0px 17px 0px 17px; } +.hint-tip .hint-tip-bot { + background: transparent url(img/tip-hint-bl.png) no-repeat 0px 0px; + margin-right: 17px; +} + +.hint-tip .hint-tip-bot .hint-tip-c { + background: transparent url(img/tip-hint-br.png) no-repeat right 0px; + margin-right: -17px; +} + +.hint-title { + display: none; +} + + /* ############################## - # Tooltip style for epg infos + # Infowin styles for epg infos ############################## */ -div.domTTepg { - width: 66%; +div.info-win { + width: 560px; + max-width: 2048px; border: none; + margin: 0px auto; } -.domTTepg div.epg_description { +.info-win .info-win-top .info-win-c { + /*font-size: 1px;*/ /* ensure minimum height */ + height: 37px; } -.domTTepg div.epg_content { - padding: 0; - margin: 0; +.info-win .info-win-bot .info-win-c { + font-size: 1px; /* ensure minimum height */ + height: 21px; +} - border-left: 1px solid #000000; - border-right: 1px solid #000000; - border-bottom: 1px solid #000000; - background: white url(bg_tools.png) top left repeat-y; +.info-win .info-win-top { + background: transparent url(img/info-win-t-l.png) no-repeat 0px 0px; + margin-right: 26px; /* space for right corner */ +} + +.info-win .info-win-top .info-win-c { + background: transparent url(img/info-win-t-r.png) no-repeat right 0px; + margin-right: -26px; /* pull right corner back over "empty" space (from above margin) */ + overflow: hidden; } -.domTTepg div.epg_content div.epg_tools { +.info-win .info-win-top .info-win-c .info-win-t { + color: #FFF; + font-weight: bold; + margin-top: 14px; + margin-left: 15px; float: left; - width: 26px; - margin: 0; - padding: 0; +} - text-align: center; - vertical-align: top; +.info-win .info-win-top .info-win-c .info-win-b { + margin-top: 17px; + margin-right: 26px; + float: right; } -.domTTepg div.epg_content div div.progress div { - padding-left: 0px; +.info-win .info-win-top .info-win-c .info-win-b .close { + width: 16px; + height: 16px; + background: transparent url(close.png) no-repeat top right; } -.domTTepg div.epg_content div div { - padding-left: 35px; +.info-win .info-win-top .info-win-c .info-win-b .close:hover { + width: 16px; + height: 16px; + background: transparent url(img/close_red.png) no-repeat top right; } -.domTTepg div.epg_content div.epg_tools img { - margin-top: 5px; +.info-win .info-win-body { + background: transparent url(img/info-win-m-l.png) repeat-y 0px 0px; + margin-right: 26px; +} + +.info-win .info-win-body .info-win-c { + background: transparent url(img/info-win-m-r.png) repeat-y right 0px; + margin-right: -26px; +} + +.info-win .info-win-body .info-win-c .info-win-s { + padding: 0px 26px 0px 14px; +} + +.info-win .info-win-bot { + background: transparent url(img/info-win-b-l.png) no-repeat 0px 0px; + margin-right: 26px; +} + +.info-win .info-win-bot .info-win-c { + background: transparent url(img/info-win-b-r.png) no-repeat right 0px; + margin-right: -26px; } -.domTTepg div.boxheader div div a { +.info-win .description { + margin-bottom: 0px; +} + +.hint-title { + display: none; } /* ####################### @@ -556,7 +635,6 @@ div.__progress div.__elapsed { background-color: #E9EFFF; } - /* ################################## # table listing # (this is used in listing tables) @@ -616,99 +694,6 @@ table.listing a:hover { text-decoration: underline; } - -/* ############# - # Timers - ############# -*/ -/* NOT USED -table.timers { - padding: 0; - margin: 0; - margin-top: 10px; -} - -table.timers tr td { - padding: 3px 7px 3px 3px; - background: url(bg_line.png) bottom repeat-x; - border-bottom: 1px solid #C0C1DA; -} - -table.timers td.border { - padding: 0; - margin: 0; - width: 1px; -} - -table.timers tr.head td { - color: white; - font-weight: bold; - margin: 0; - padding: 0; - border-bottom: 1px solid black; -} - -table.timers tr.description td { - font-weight: bold; - background: #E9EFFF; -} - -table.timers a { - text-decoration: none; - color: black; - font-weight: bold; -} - -table.timers a:hover { - text-decoration: underline; -} -*/ - -/* - ############################## - # Schedule - ############################## -*/ - -/* NOT USED: -table.schedule { - margin: 10px 0 0 0 ; - padding: 0; -} - -table.schedule tr td.head { - background: #6D96A9; - color: white; - font-weight: bold; - margin: 0; - padding: 3px; -} - -table.schedule tr td { - vertical-align: top; - padding: 3px 7px 3px 3px; - background: url(bg_line.png) bottom repeat-x; - border-bottom: 1px solid #C0C1DA; -} - -table.schedule tr td.day { - vertical-align: top; - padding: 0; - margin: 0; - border:none; -} - -table.schedule tr.active { - background: #DEE6EE; -} - -table.schedule div.more { - margin: 0px; - font-weight: bold; - cursor: pointer; -} -*/ - /* ############################## # Blue Background Thingy @@ -853,14 +838,12 @@ table.error td.border { width: 1px; } - /* ############################## # Formular Tables # (are used in forms to group input elements) ############################## */ -/* 'formular' replaces 'edit' */ table.formular { margin-top: 10px; @@ -913,77 +896,57 @@ table.dependent tr td { vertical-align: middle; } - /* ############################## - # Search results + # Login ############################## */ -/* NOT USED: -table.searchresults { - margin: 10px 0 0 0; - padding: 0; +table.login { + margin: 0 auto; } -table.searchresults tr td.head { - background: #6D96A9; - color: white; - font-weight: bold; - margin: 0; - padding: 3px; +table.login tr td { + padding: 3px 5px; + text-align: right; } -table.searchresults tr td { - vertical-align: top; - padding: 3px 7px 7px 3px; - background: url(bg_line.png) bottom repeat-x; - border-bottom: 1px solid #C0C1DA; -} +/* + ############################## + # Infowin support styles for EPG-Boxes + ############################## +*/ -table.searchresults tr td.day { - vertical-align: top; +.info-win div.epg_content { padding: 0; margin: 0; - border:none; + background: transparent url(bg_tools.png) top left repeat-y; } -table.searchresults td.border { - padding: 0; +.info-win div.epg_content div.epg_tools { + float: left; + width: 26px; margin: 0; - width: 1px; -} + padding: 0; -table.searchresults div.more { - margin: 0px; - font-weight: bold; - cursor: pointer; + text-align: center; + vertical-align: top; } -table.searchresults a { - text-decoration: none; - color: black; - font-weight: bold; +.info-win div.epg_content div div.progress div { + padding-left: 0px; } -table.searchresults a:hover { - text-decoration: underline; +.info-win div.epg_content div div { + padding-left: 35px; } -*/ -/* - ############################## - # Login - ############################## -*/ - -table.login { - margin: 0 auto; +.info-win div.epg_content div.epg_tools img { + margin-top: 5px; } -table.login tr td { - padding: 3px 5px; - text-align: right; +.info-win div.boxheader { + display: none; } /* ############################## @@ -1005,14 +968,7 @@ div.about_box a:hover { text-decoration: underline; } -div#aboutBox_tip { - width: 45%; -} - -.about_box div.about_description { -} - -.about_box div.about_content { +.info-win div.about_content { padding: 0; margin: 0; @@ -1021,29 +977,26 @@ div#aboutBox_tip { border-bottom: 1px solid #000000; } -.about_box div.about_content div { +.info-win div.about_content div { background-color: white; padding-bottom: 6px; } -.about_box div.boxheader div div a { -} - -.about_box div.about_content div.about_left { +.info-win div.about_content div.about_left { text-align: right; float: left; width: 175px; } -.about_box div.about_content div.about_right { +.info-win div.about_content div.about_right { padding-left: 200px; } -.about_box div.about_content div.about_line { +.info-win div.about_content div.about_line { padding-left: 10px; } -.about_box div.about_content div.about_head { +.info-win div.about_content div.about_head { font-weight: bold; margin-top: 0px; padding-top: 6px; @@ -1051,12 +1004,12 @@ div#aboutBox_tip { background: #FFFFFF url(bg_line.png) top repeat-x; } -.about_box div.about_content div.about_head div { +.info-win div.about_content div.about_head div { padding-bottom: 6px; background: #FFFFFF url(bg_line_top.png) bottom repeat-x; } -.about_box div.about_content div.about_head div div { +.info-win div.about_content div.about_head div div { padding: 2px 0px 2px 10px; background: #E9EFFF; border-top: 1px solid #C0C1DA; diff --git a/doc/ChangeLog b/doc/ChangeLog index bd5cb7b..1eb4787 100644 --- a/doc/ChangeLog +++ b/doc/ChangeLog @@ -1,3 +1,24 @@ +2007-07-12 Dieter Hametner <dh+vdr at gekrumbel dot de> + + Changed the javascript base of live. We now use the 'mootools' + framework (see http://www.mootools.net for infos) to handle + javascript in a browser independend fashion and for nifty Web 2.0 + features. + + Based on this framework we have now tooltips that use the XHTML + standard 'title' attribute and Web-2.0 popup windows for epg + information. This Epg information is loaded on demand and once + loaded, they are cached in the page for further viewing. + + On the other hand this also provides us with a solution to have + live functioning without javascript at all. When done right, the + same functionality can be achieved with or without enabled + javascript in the browser. Currently there still are javascript + only features, which will be resolved in the next weeks. + + This is a rather big change on many files, so they are not all + mentioned here. + 2007-06-22 Dieter Hametner <dh+vdr at gekrumbel dot de> Start of new 'standalone' javascript source directory diff --git a/doc/dev-conventions.txt b/doc/dev-conventions.txt index 07d026d..2b80eab 100644 --- a/doc/dev-conventions.txt +++ b/doc/dev-conventions.txt @@ -18,16 +18,30 @@ Here WEB 2.0 features can improve the users experience. With or without ECMAScript -------------------------- -Since not all browsers support ECMAScript, we suggest the following -rule to activate functionality with and without ECMAScript support: - -Use anchors analog to this example - - <a href="show_content.html?ref=additional_content_ref" - onclick="makefalse(requestbox('additional_content_ref'));">link</a> - -to retrieve and display extra information either in a WEB 2.0 fashion -in an popup box or on a separte page in a html fashion. +Since not all browsers support ECMAScript, we need to make sure all +functions live wants to provide need to be accessible through links. + +With the mootools framework and its selection functions we can enhance +the user experience through ECMAScript by selecting the relevant +elements in the DOM and attaching event handlers from the loaded +script files. Thus when the user disables ECMAScript in his browser +(or the browser does not support it) the traditional web technique of +jumping between pages provides the functions. With enabled ECMAScript +the event handlers can take over and provide a nifty Web 2.0 technique +solution to the user. + +To enable a tooltip just add a 'title' attribute on the element and +load 'hinttips.js' in your pages (Actually this will be allready done +for you if you use the live page-framework). + +For popup windows that asynchronously load its contents you need to +use normal links like <a href="epginfo.html?eventid=evnt_identifier"> +your link text here </a>. If 'infowin.js' is loaded it will enhance +these links with AJAX functionality. If not the link will change to a +new page with the requested information. + +This means that both users with and without ECMAScript support will +benefit from the functions in live. Themeing diff --git a/epg_events.cpp b/epg_events.cpp index 2d5237c..e9524b3 100644 --- a/epg_events.cpp +++ b/epg_events.cpp @@ -5,54 +5,67 @@ #include "epg_events.h" +using namespace std; + namespace vdrlive { - EpgEvent::EpgEvent(const std::string& id, - const std::string& caption, - const std::string& title, - const std::string& short_descr, - const std::string& long_descr, - time_t start_time, - time_t end_time) : + + /* + * ------------------------------------------------------------------------- + * EpgInfo + * ------------------------------------------------------------------------- + */ + + EpgInfo::EpgInfo(const std::string& id, const std::string& caption) : m_eventId(id), - m_caption(caption), - m_title(title), - m_short_descr(short_descr), - m_long_descr(long_descr), - m_archived(), - m_start_time(start_time), - m_end_time(end_time) + m_caption(caption) + { + } + + EpgInfo::~EpgInfo() + { + } + + const std::string EpgInfo::CurrentTime(const char* format) const + { + return FormatDateTime(format, time(0)); + } + + const string EpgInfo::StartTime(const char* format) const { + time_t start = GetStartTime(); + return start ? FormatDateTime(format, start) : ""; } + const string EpgInfo::EndTime(const char* format) const + { + time_t end = GetEndTime(); + return end ? FormatDateTime(format, end) : ""; + } + + int EpgInfo::Elapsed() const + { + time_t end_time = GetEndTime(); + time_t start_time = GetStartTime(); + + if (end_time > start_time) { + time_t now = time(0); + if ((start_time <= now) && (now <= end_time)) { + return 100 * (now - start_time) / (end_time - start_time); + } + } + return -1; + } + + /* + * ------------------------------------------------------------------------- + * EpgEvent + * ------------------------------------------------------------------------- + */ + EpgEvent::EpgEvent(const std::string& id, const cEvent* event, const char* channelName) : - m_eventId(id), - m_caption(channelName), - m_title(event->Title() ? event->Title() : ""), - m_short_descr(event->ShortText() ? event->ShortText() : ""), - m_long_descr(event->Description() ? event->Description() : ""), - m_archived(), - m_start_time(event->StartTime()), - m_end_time(event->EndTime()) - { - } - - EpgEvent::EpgEvent(const std::string& id, - const std::string& caption, - const std::string& title, - const std::string& short_descr, - const std::string& long_descr, - const std::string& archived, - time_t start_time, - time_t end_time) : - m_eventId(id), - m_caption(caption), - m_title(title), - m_short_descr(short_descr), - m_long_descr(long_descr), - m_archived(archived), - m_start_time(start_time), - m_end_time(end_time) + EpgInfo(id, channelName), + m_event(event) { } @@ -60,50 +73,206 @@ namespace vdrlive { } - const std::string EpgEvent::StartTime(const char* format) const + /* + * ------------------------------------------------------------------------- + * EpgString + * ------------------------------------------------------------------------- + */ + + EpgString::EpgString(const string& id, const string& caption, const string& info) : + EpgInfo(id, caption), + m_info(info) { - return FormatDateTime(format, m_start_time); } - const std::string EpgEvent::EndTime(const char* format) const + EpgString::~EpgString() { - return FormatDateTime(format, m_end_time); } - const std::string EpgEvent::CurrentTime(const char* format) const + const string EpgString::Title() const { - return FormatDateTime(format, time(0)); + return m_info; } - int EpgEvent::Elapsed() const + const string EpgString::ShortDescr() const { - if (m_end_time > m_start_time) { - time_t now = time(0); - if ((m_start_time <= now) && (now <= m_end_time)) { - return 100 * (now - m_start_time) / (m_end_time - m_start_time); + return ""; + } + + const string EpgString::LongDescr() const + { + return ""; + } + + // virtual const std::string Archived() const { return std::string(); } + + time_t EpgString::GetStartTime() const + { + return time(0); + } + + time_t EpgString::GetEndTime() const + { + return time(0); + } + + /* + * ------------------------------------------------------------------------- + * EpgRecording + * ------------------------------------------------------------------------- + */ + + EpgRecording::EpgRecording(const string& recid, const cRecording* recording, const char* caption) : + EpgInfo(recid, (caption != 0) ? caption : ""), + m_recording(recording), + m_ownCaption(caption != 0), + m_checkedArchived(false), + m_archived() + { + } + + EpgRecording::~EpgRecording() + { + m_recording = 0; + } + + const string EpgRecording::Caption() const + { + if (!m_ownCaption) { + return EpgInfo::Caption(); + } + if (!m_recording) { + return ""; + } + + return Name(); + } + + const string EpgRecording::Title() const + { + if (!m_recording) { + return ""; + } + + const cRecordingInfo* info = m_recording->Info(); + return (info && info->Title()) ? info->Title() : Name(); + } + + const string EpgRecording::ShortDescr() const + { + const cRecordingInfo* info = m_recording ? m_recording->Info() : 0; + return (info && info->ShortText()) ? info->ShortText() : ""; + } + + const string EpgRecording::LongDescr() const + { + const cRecordingInfo* info = m_recording ? m_recording->Info() : 0; + return (info && info->Description()) ? info->Description() : ""; + } + + const string EpgRecording::Archived() const + { + if (!m_checkedArchived) { + if (m_recording) { + m_archived = RecordingsManager::GetArchiveDescr(m_recording); + m_checkedArchived = true; } } - return -1; + return m_archived; + } + + time_t EpgRecording::GetStartTime() const + { + return m_recording ? m_recording->start : 0; } - const cTimer* EpgEvent::GetTimer() const + time_t EpgRecording::GetEndTime() const { - return NULL; + return m_recording ? m_recording->start : 0; } - EpgEvents::EpgEvents() : - std::vector<EpgEventPtr>() + const string EpgRecording::Name() const + { + string name(m_recording->Name()); + size_t index = name.find_last_of('~'); + if (index != string::npos) { + name = name.substr(index, name.length()); + } + return name; + } + + /* + * ------------------------------------------------------------------------- + * EpgEvents + * ------------------------------------------------------------------------- + */ + + EpgEvents::EpgEvents() { } EpgEvents::~EpgEvents() { } -#ifdef never - EpgEventsPtr EpgEvents::dim(size_t count) + + string EpgEvents::GetDomId(const tChannelID& chanId, const tEventID& eId) + { + string channelId(chanId.ToString()); + string eventId("event_"); + + replace(channelId.begin(), channelId.end(), '.', 'p'); + replace(channelId.begin(), channelId.end(), '-', 'm'); + + eventId += channelId; + eventId += '_'; + eventId += lexical_cast<std::string>(eId); + return eventId; + } + + EpgInfoPtr EpgEvents::CreateEpgInfo(const std::string& epgid, const cSchedules* schedules) + { + const string eventStr("event_"); + + size_t delimPos = epgid.find_last_of('_'); + string cIdStr = epgid.substr(eventStr.length(), delimPos - eventStr.length()); + + replace(cIdStr.begin(), cIdStr.end(), 'm', '-'); + replace(cIdStr.begin(), cIdStr.end(), 'p', '.'); + + const string eIdStr = epgid.substr(delimPos+1); + const string errorInfo(tr("Epg error")); + + + tEventID eventId = lexical_cast<tEventID>(eIdStr); + tChannelID channelId = tChannelID::FromString(cIdStr.c_str()); + const cChannel* channel = Channels.GetByChannelID(channelId); + if (!channel) { + return CreateEpgInfo(epgid, errorInfo, tr("Wrong channel id")); + } + const cSchedule* schedule = schedules->GetSchedule(channel); + if (!schedule) { + return CreateEpgInfo(epgid, errorInfo, tr("Channel has no schedule")); + } + const cEvent* event = schedule->GetEvent(eventId); + if (!event) { + return CreateEpgInfo(epgid, errorInfo, tr("wrong event id")); + } + return CreateEpgInfo(channel, event, epgid.c_str()); + } + + EpgInfoPtr EpgEvents::CreateEpgInfo(const cChannel* chan, const cEvent* event, const char* idOverride) + { + string domId(idOverride ? idOverride : GetDomId(chan->GetChannelID(), event->EventID())); + return EpgInfoPtr(new EpgEvent(domId, event, chan->Name())); + } + + EpgInfoPtr EpgEvents::CreateEpgInfo(const string& recid, const cRecording* recording, const char* caption) + { + return EpgInfoPtr(new EpgRecording(recid, recording, caption)); + } + + EpgInfoPtr EpgEvents::CreateEpgInfo(const std::string& id, const std::string& caption, const std::string& info) { - EpgEventsPtr ePtr(new EpgEvents(count)); - return ePtr; + return EpgInfoPtr(new EpgString(id, caption, info)); } -#endif }; // namespace vdrlive diff --git a/epg_events.h b/epg_events.h index 68eb0a4..8536463 100644 --- a/epg_events.h +++ b/epg_events.h @@ -2,7 +2,6 @@ #define VDR_LIVE_WHATS_ON_H #include <ctime> -#include <vector> #include <vdr/plugin.h> #include <vdr/channels.h> @@ -15,72 +14,171 @@ namespace vdrlive { - class EpgEvent + + class EpgInfo { + protected: + EpgInfo(const std::string& id, + const std::string& caption); + public: - EpgEvent(const std::string& id, - const std::string& caption, - const std::string& title, - const std::string& short_descr, - const std::string& long_descr, - time_t start_time, - time_t end_time); + virtual ~EpgInfo(); + + virtual const std::string Id() const { return m_eventId; } + + virtual const std::string Caption() const { return m_caption; } + + virtual const std::string Title() const = 0; + + virtual const std::string ShortDescr() const = 0; + + virtual const std::string LongDescr() const = 0; + + virtual const std::string Archived() const { return ""; } + + virtual const std::string StartTime(const char* format) const; + + virtual const std::string EndTime(const char* format) const; + + virtual const std::string CurrentTime(const char* format) const; + + virtual int Elapsed() const; + + // virtual const cTimer* GetTimer() const = 0; + + virtual time_t GetStartTime() const = 0; + + virtual time_t GetEndTime() const = 0; + + private: + std::string m_eventId; + std::string m_caption; + }; + + typedef std::tr1::shared_ptr<EpgInfo> EpgInfoPtr; + + // ------------------------------------------------------------------------- + + class EpgString : public EpgInfo + { + friend class EpgEvents; + + protected: + EpgString(const std::string& id, + const std::string& caption, + const std::string& info); + + public: + virtual ~EpgString(); + virtual const std::string Title() const; + + virtual const std::string ShortDescr() const; + + virtual const std::string LongDescr() const; + + virtual time_t GetStartTime() const; + + virtual time_t GetEndTime() const; + + private: + const std::string m_info; + }; + + // ------------------------------------------------------------------------- + + class EpgEvent : public EpgInfo + { + friend class EpgEvents; + + protected: EpgEvent(const std::string& id, const cEvent* event, const char* channelName = ""); - EpgEvent(const std::string& id, - const std::string& caption, - const std::string& title, - const std::string& short_descr, - const std::string& long_descr, - const std::string& archived, - time_t start_time, - time_t end_time); - + public: virtual ~EpgEvent(); - const std::string& Id() const { return m_eventId; } + virtual const std::string Title() const { return std::string(m_event->Title() ? m_event->Title() : ""); } - const std::string& Title() const { return m_title; } + virtual const std::string ShortDescr() const { return std::string(m_event->ShortText() ? m_event->ShortText() : ""); } - const std::string& Caption() const { return m_caption; } + virtual const std::string LongDescr() const { return std::string(m_event->Description() ? m_event->Description() : ""); } - const std::string& ShortDescr() const { return m_short_descr; } + virtual time_t GetStartTime() const { return m_event->StartTime(); } - const std::string& LongDescr() const { return m_long_descr; } + virtual time_t GetEndTime() const { return m_event->EndTime(); } - const std::string& Archived() const { return m_archived; } + private: + const cEvent* m_event; + }; - const std::string StartTime(const char* format) const; + // ------------------------------------------------------------------------- + + class EpgRecording : public EpgInfo + { + friend class EpgEvents; - const std::string EndTime(const char* format) const; + protected: + EpgRecording(const std::string& recid, const cRecording* recording, const char* caption); + + const std::string Name() const; + + public: + virtual ~EpgRecording(); - const std::string CurrentTime(const char* format) const; + virtual const std::string Caption() const; - int Elapsed() const; + virtual const std::string Title() const; - const cTimer* GetTimer() const; + virtual const std::string ShortDescr() const; + + virtual const std::string LongDescr() const; + + virtual const std::string Archived() const; + + virtual time_t GetStartTime() const; + + virtual time_t GetEndTime() const; private: - std::string m_eventId; - std::string m_caption; - std::string m_title; - std::string m_short_descr; - std::string m_long_descr; - std::string m_archived; - time_t m_start_time; - time_t m_end_time; + const cRecording* m_recording; + bool m_ownCaption; + mutable bool m_checkedArchived; + mutable std::string m_archived; }; - typedef std::tr1::shared_ptr<EpgEvent> EpgEventPtr; + // ------------------------------------------------------------------------- - class EpgEvents : public std::vector<EpgEventPtr> { + class EpgEvents { public: EpgEvents(); virtual ~EpgEvents(); + static std::string GetDomId(const tChannelID& chanId, const tEventID& eventId); + + /** + * Allocate and initalize an epgEvent instance with the + * passed channel and event information. + */ + static EpgInfoPtr CreateEpgInfo(const cChannel* chan, const cEvent* event, const char* idOverride = 0); + + /** + * This is the inverse creator for epgInfos to the creator above. + */ + static EpgInfoPtr CreateEpgInfo(const std::string& epgid, const cSchedules* schedules); + + /** + * Allocate and initalize an epgEvent instance with the + * passed recording information. + */ + static EpgInfoPtr CreateEpgInfo(const std::string& recid, const cRecording* recording, const char* caption = 0); + + /** + * Allocate and initalize an epgEvent instance with the + * passed string informations + */ + static EpgInfoPtr CreateEpgInfo(const std::string& id, const std::string& caption, const std::string& info); private: }; }; // namespace vdrlive diff --git a/javascript/Makefile b/javascript/Makefile index 648361c..c86afe2 100644 --- a/javascript/Makefile +++ b/javascript/Makefile @@ -15,8 +15,7 @@ VDRDIR ?= ../../../.. ### The object files (add further files here): -OBJS = alphaAPI.o domLib.o domTT_drag.o domTT.o fadomatic.o \ - treeview.o +OBJS = treeview.o ### Default rules: diff --git a/live/img/close_red.png b/live/img/close_red.png Binary files differnew file mode 100644 index 0000000..05e02b2 --- /dev/null +++ b/live/img/close_red.png diff --git a/live/img/info-win-b-l.png b/live/img/info-win-b-l.png Binary files differnew file mode 100644 index 0000000..5fccc68 --- /dev/null +++ b/live/img/info-win-b-l.png diff --git a/live/img/info-win-b-r.png b/live/img/info-win-b-r.png Binary files differnew file mode 100644 index 0000000..29a09e9 --- /dev/null +++ b/live/img/info-win-b-r.png diff --git a/live/img/info-win-m-l.png b/live/img/info-win-m-l.png Binary files differnew file mode 100644 index 0000000..9fb5356 --- /dev/null +++ b/live/img/info-win-m-l.png diff --git a/live/img/info-win-m-r.png b/live/img/info-win-m-r.png Binary files differnew file mode 100644 index 0000000..f097081 --- /dev/null +++ b/live/img/info-win-m-r.png diff --git a/live/img/info-win-t-l.png b/live/img/info-win-t-l.png Binary files differnew file mode 100644 index 0000000..f916543 --- /dev/null +++ b/live/img/info-win-t-l.png diff --git a/live/img/info-win-t-r.png b/live/img/info-win-t-r.png Binary files differnew file mode 100644 index 0000000..854bde2 --- /dev/null +++ b/live/img/info-win-t-r.png diff --git a/live/img/tip-hint-bl.png b/live/img/tip-hint-bl.png Binary files differnew file mode 100644 index 0000000..790a602 --- /dev/null +++ b/live/img/tip-hint-bl.png diff --git a/live/img/tip-hint-br.png b/live/img/tip-hint-br.png Binary files differnew file mode 100644 index 0000000..61eafcf --- /dev/null +++ b/live/img/tip-hint-br.png diff --git a/live/img/tip-hint-ml.png b/live/img/tip-hint-ml.png Binary files differnew file mode 100644 index 0000000..b730b57 --- /dev/null +++ b/live/img/tip-hint-ml.png diff --git a/live/img/tip-hint-mr.png b/live/img/tip-hint-mr.png Binary files differnew file mode 100644 index 0000000..bf2465c --- /dev/null +++ b/live/img/tip-hint-mr.png diff --git a/live/img/tip-hint-tl.png b/live/img/tip-hint-tl.png Binary files differnew file mode 100644 index 0000000..467ecec --- /dev/null +++ b/live/img/tip-hint-tl.png diff --git a/live/img/tip-hint-tr.png b/live/img/tip-hint-tr.png Binary files differnew file mode 100644 index 0000000..a4c9b3f --- /dev/null +++ b/live/img/tip-hint-tr.png diff --git a/live/js/live/hinttips.js b/live/js/live/hinttips.js new file mode 100644 index 0000000..5cf899a --- /dev/null +++ b/live/js/live/hinttips.js @@ -0,0 +1,34 @@ +/* + * Extension of mootools Tips class for rounded corner + * tooltips of variable size up to some maximum. + */ + +var HintTips = Tips.extend({ + initialize: function(elements, options){ + this.parent(elements, options); + this.toolTip.empty(); + /* top border of tip */ + var hd = new Element('div', {'class': this.options.className + '-tip-top'}).inject(this.toolTip); + hd = new Element('div', {'class': this.options.className + '-tip-c'}).inject(hd); + + /* body of tip: some helper divs and content */ + this.wrapper = new Element('div', {'class': this.options.className + '-tip-bdy'}).inject(this.toolTip); + this.wrapper = new Element('div', {'class': this.options.className + '-tip-c'}).inject(this.wrapper); + this.wrapper = new Element('div', {'class': this.options.className + '-tip-s'}).inject(this.wrapper); + + /* bottom border of tip */ + var bt = new Element('div', {'class': this.options.className + '-tip-bot'}).inject(this.toolTip); + bt = new Element('div', {'class': this.options.className + '-tip-c'}).inject(bt); + } + }); + +window.addEvent('domready', function(){ + var tips = new HintTips($$('*[title]'), { + maxTitleChars: 100, + className: 'hint' + }); + }); + +window.addEvent('mousedown', function(){ + $$('.hint-tip').setStyle('visibility', 'hidden'); + });
\ No newline at end of file diff --git a/live/js/live/infowin.js b/live/js/live/infowin.js new file mode 100644 index 0000000..c142700 --- /dev/null +++ b/live/js/live/infowin.js @@ -0,0 +1,296 @@ +/* + * Extension of mootools to display a popup window with + * some html code. + */ + +/* +Class: InfoWin + Create an information window as overlay to current page. + +Arguments: + +Options: + +Note: + A window consists of a frame-element. This is the overall + containing element used to control the display and size of the + window. It is accesable through the 'winFrame' property. + + The InfoWin class provides the followin properties to fill the + window with content: + - titleBox: the element meant to place the title of the window into. + - buttonBox: here the default window buttons are created. You might + clear this and create your own kind of window controls. + - winBody: this is where your window contents goes. + */ +var InfoWin = new Class({ + options: { + timeout: 0, + onShow: Class.empty, + onHide: Class.empty, + className: 'info', + wm: false, // overide default window manager. + draggable: true, + resizable: true, + buttonimg: 'transparent.png', + bodyselect: 'div.epg_content', + titleselect: 'div.caption', + offsets: {'x': -16, 'y': -16} + }, + + initialize: function(id, options){ + this.setOptions(options); + this.wm = this.options.wm || InfoWin.$wm; + this.winFrame = $(id + '-win-id'); + if (!$defined(this.winFrame)){ + this.build(id); + this.wm.register(this); + } + }, + + // internal: build new window element. + // + // build sets up a frame for a new InfoWin. The parent element + // of the window frame has the id '<id>-win-id'. The function + // must return true if the body of the InfoWin has been filled + // with the user data, false otherwise. + build: function(id){ + this.winFrame = new Element('div', { + 'id': id + '-win-id', + 'class': this.options.className + '-win', + 'styles': { + 'position': 'absolute', + 'top': '0', + 'left': '0' + } + }); + + // header of window: upper shadows, corners title and controls + var top = new Element('div', { + 'class': this.options.className + '-win-top' + }).inject(this.winFrame); + if (this.options.draggable) this.winFrame.makeDraggable({'handle': top}); + top = new Element('div', { + 'class': this.options.className + '-win-c' + }).inject(top); + this.titleBox = new Element('div', { + 'class': this.options.className + '-win-t' + }).inject(top); + + this.buttonBox = new Element('div', { + 'class': this.options.className + '-win-b' + }).inject(top); + var cls = new Element('div', { + 'class': 'close' + }).inject(this.buttonBox); + var self = this; + cls.addEvent('click', function(event){ + var event = new Event(event); + event.stop(); + return self.hide(); + }); + cls = new Element('img', { + 'src': this.options.buttonimg, + 'alt': 'close', + 'width': '16px', + 'height': '16px' + }).inject(cls); + + // body of window: user content. + var bdy = new Element('div', { + 'class': this.options.className + '-win-body' + }).inject(this.winFrame); + bdy = new Element('div', { + 'class': this.options.className + '-win-c' + }).inject(bdy); + this.winBody = new Element('div', { + 'class': this.options.className + '-win-s' + }).inject(bdy); + + // bottom border of window: lower shadows and corners, optional + // resize handle. + var bot = new Element('div', { + 'class': this.options.className + '-win-bot' + }).inject(this.winFrame); + bot = new Element('div', { + 'class': this.options.className + '-win-c' + }).inject(bot); + + if (this.options.resizable) { + this.winFrame.makeResizable({'handle': bot}); + } + + if (!this.fillTitle(id)) { + // todo: add generic title + } + return this.fillBody(id); + }, + + show: function(event){ + if (this.options.timeout) + this.timer = this.hide.delay(this.options.timeout, this); + this.position(event); + this.fireEvent('onShow', [this.winFrame]); + this.wm.raise(this); + return false; + }, + + hide: function(){ + this.fireEvent('onHide', [this.winFrame]); + this.wm.bury(this); + return false; + }, + + fillBody: function(id){ + var bodyElems = $$('#'+ id + ' ' + this.options.bodyselect); + if ($defined(bodyElems) && bodyElems.length > 0) { + this.winBody.empty().adopt(bodyElems); + return true; + } + return false; + }, + + fillTitle: function(id){ + var titleElems = $$('#' + id + ' ' + this.options.titleselect); + if ($defined(titleElems) && titleElems.length > 0) { + this.titleBox.empty().adopt(titleElems); + return true; + } + return false; + }, + + position: function(event){ + var prop = {'x': 'left', 'y': 'top'}; + for (var z in prop) { + var pos = event.page[z] + this.options.offsets[z]; + this.winFrame.setStyle(prop[z], pos); + } + } + }); + +InfoWin.implement(new Events, new Options); + +InfoWin.Manager = new Class({ + options: { + zIndex: 100, + closedContainer: 'infowin-closed', + openedContainer: 'infowin-opened', + onRegister: Class.empty, + onRaise: Class.empty, + onBury: Class.empty + }, + + initialize: function(options){ + this.setOptions(options); + // initialize properties this.closedWins and this.openedWins: + ['closed', 'opened'].each(function(kind){ + var wins = kind + 'Wins'; + var opts = this.options[kind + 'Container']; + this[wins] = $(opts); + if (!$defined(this[wins])){ + this[wins] = new Element('div', { + 'id': opts, + 'styles' : { + 'display' : (kind == 'closed') ? 'none' : 'block' + } + }); + this[wins].inject(document.body); + } + }, this); + }, + + register: function(infoWin){ + var self = this; + this.fireEvent('onRegister', [infoWin]); + infoWin.winFrame.addEvent('click', function(){ + self.raise(infoWin); + }); + infoWin.winFrame.inject(this.closedWins); + }, + + raise: function(infoWin){ + this.fireEvent('onRaise', [infoWin]); + infoWin.winFrame.remove(); + infoWin.winFrame.inject(this.openedWins); + }, + + bury: function(infoWin){ + this.fireEvent('onBury', [infoWin]); + infoWin.winFrame.remove(); + infoWin.winFrame.inject(this.closedWins); + } + }); + +InfoWin.Manager.implement(new Events, new Options); + +InfoWin.$wm = null; +window.addEvent('domready', function(){ + InfoWin.$wm = new InfoWin.Manager(); + }); + + +InfoWin.Ajax = InfoWin.extend({ + options: { + loadingMsg: 'loading', + errorMsg: 'an error occured!', + onError: Class.empty + }, + + initialize: function(id, url, options){ + this.parent(id, options); + if ($defined(this.ajaxResponse)) { + var self = this; + this.addEvent('onError', function(){ + self.hide.delay(1000, self); + }); + var ajax = new Ajax(url, { + update: this.ajaxResponse, + onComplete: function(text, xmldoc){ + self.fillTitle(id); + self.fillBody(id); + }, + onFailure: function(transport){ + self.titleBox.setHTML(self.options.errorMsg); + self.fireEvent('onError', [id, url]); + } + }).request('async=1'); + } + }, + + // this function gets called when no previous instance for 'id' + // created a dom subtree for an infowin. + build: function(id){ + if (!this.parent(id)) { + this.titleBox.setHTML(this.options.loadingMsg); + this.ajaxResponse = new Element('div', { + 'styles' : { + 'display': 'none' + } + }).inject(this.winFrame); + } + } + }); + +InfoWin.Ajax.implement(new Events, new Options); + + +window.addEvent('domready', function(){ + $$('a[href^="epginfo.html?epgid"]').each(function(el){ + var href = el.href; + var epgid = $pick(href, ""); + if (epgid != "") { + var extractId = /epgid=(\w+)/; + var found = extractId.exec(epgid); + if ($defined(found) && found.length > 1) { + epgid = found[1]; + el.addEvent('click', function(event){ + var event = new Event(event); + new InfoWin.Ajax(epgid, href).show(event); + event.stop(); + return false; + }); + } + } + }); + }); + diff --git a/live/js/mootools/mootools.v1.11.js b/live/js/mootools/mootools.v1.11.js index 9c8be88..ac754b9 100644 --- a/live/js/mootools/mootools.v1.11.js +++ b/live/js/mootools/mootools.v1.11.js @@ -3017,6 +3017,325 @@ Function.extend({ /* +Script: Element.Filters.js + add Filters capability to <Elements>. + +License: + MIT-style license. +*/ + +/* +Class: Elements + A collection of methods to be used with <$$> elements collections. +*/ + +Elements.extend({ + + /* + Property: filterByTag + Filters the collection by a specified tag name. + Returns a new Elements collection, while the original remains untouched. + */ + + filterByTag: function(tag){ + return new Elements(this.filter(function(el){ + return (Element.getTag(el) == tag); + })); + }, + + /* + Property: filterByClass + Filters the collection by a specified class name. + Returns a new Elements collection, while the original remains untouched. + */ + + filterByClass: function(className, nocash){ + var elements = this.filter(function(el){ + return (el.className && el.className.contains(className, ' ')); + }); + return (nocash) ? elements : new Elements(elements); + }, + + /* + Property: filterById + Filters the collection by a specified ID. + Returns a new Elements collection, while the original remains untouched. + */ + + filterById: function(id, nocash){ + var elements = this.filter(function(el){ + return (el.id == id); + }); + return (nocash) ? elements : new Elements(elements); + }, + + /* + Property: filterByAttribute + Filters the collection by a specified attribute. + Returns a new Elements collection, while the original remains untouched. + + Arguments: + name - the attribute name. + operator - optional, the attribute operator. + value - optional, the attribute value, only valid if the operator is specified. + */ + + filterByAttribute: function(name, operator, value, nocash){ + var elements = this.filter(function(el){ + var current = Element.getProperty(el, name); + if (!current) return false; + if (!operator) return true; + switch(operator){ + case '=': return (current == value); + case '*=': return (current.contains(value)); + case '^=': return (current.substr(0, value.length) == value); + case '$=': return (current.substr(current.length - value.length) == value); + case '!=': return (current != value); + case '~=': return current.contains(value, ' '); + } + return false; + }); + return (nocash) ? elements : new Elements(elements); + } + +}); + +/* +Script: Element.Selectors.js + Css Query related functions and <Element> extensions + +License: + MIT-style license. +*/ + +/* Section: Utility Functions */ + +/* +Function: $E + Selects a single (i.e. the first found) Element based on the selector passed in and an optional filter element. + Returns as <Element>. + +Arguments: + selector - string; the css selector to match + filter - optional; a DOM element to limit the scope of the selector match; defaults to document. + +Example: + >$E('a', 'myElement') //find the first anchor tag inside the DOM element with id 'myElement' + +Returns: + a DOM element - the first element that matches the selector +*/ + +function $E(selector, filter){ + return ($(filter) || document).getElement(selector); +}; + +/* +Function: $ES + Returns a collection of Elements that match the selector passed in limited to the scope of the optional filter. + See Also: <Element.getElements> for an alternate syntax. + Returns as <Elements>. + +Returns: + an array of dom elements that match the selector within the filter + +Arguments: + selector - string; css selector to match + filter - optional; a DOM element to limit the scope of the selector match; defaults to document. + +Examples: + >$ES("a") //gets all the anchor tags; synonymous with $$("a") + >$ES('a','myElement') //get all the anchor tags within $('myElement') +*/ + +function $ES(selector, filter){ + return ($(filter) || document).getElementsBySelector(selector); +}; + +$$.shared = { + + 'regexp': /^(\w*|\*)(?:#([\w-]+)|\.([\w-]+))?(?:\[(\w+)(?:([!*^$]?=)["']?([^"'\]]*)["']?)?])?$/, + + 'xpath': { + + getParam: function(items, context, param, i){ + var temp = [context.namespaceURI ? 'xhtml:' : '', param[1]]; + if (param[2]) temp.push('[@id="', param[2], '"]'); + if (param[3]) temp.push('[contains(concat(" ", @class, " "), " ', param[3], ' ")]'); + if (param[4]){ + if (param[5] && param[6]){ + switch(param[5]){ + case '*=': temp.push('[contains(@', param[4], ', "', param[6], '")]'); break; + case '^=': temp.push('[starts-with(@', param[4], ', "', param[6], '")]'); break; + case '$=': temp.push('[substring(@', param[4], ', string-length(@', param[4], ') - ', param[6].length, ' + 1) = "', param[6], '"]'); break; + case '=': temp.push('[@', param[4], '="', param[6], '"]'); break; + case '!=': temp.push('[@', param[4], '!="', param[6], '"]'); + } + } else { + temp.push('[@', param[4], ']'); + } + } + items.push(temp.join('')); + return items; + }, + + getItems: function(items, context, nocash){ + var elements = []; + var xpath = document.evaluate('.//' + items.join('//'), context, $$.shared.resolver, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null); + for (var i = 0, j = xpath.snapshotLength; i < j; i++) elements.push(xpath.snapshotItem(i)); + return (nocash) ? elements : new Elements(elements.map($)); + } + + }, + + 'normal': { + + getParam: function(items, context, param, i){ + if (i == 0){ + if (param[2]){ + var el = context.getElementById(param[2]); + if (!el || ((param[1] != '*') && (Element.getTag(el) != param[1]))) return false; + items = [el]; + } else { + items = $A(context.getElementsByTagName(param[1])); + } + } else { + items = $$.shared.getElementsByTagName(items, param[1]); + if (param[2]) items = Elements.filterById(items, param[2], true); + } + if (param[3]) items = Elements.filterByClass(items, param[3], true); + if (param[4]) items = Elements.filterByAttribute(items, param[4], param[5], param[6], true); + return items; + }, + + getItems: function(items, context, nocash){ + return (nocash) ? items : $$.unique(items); + } + + }, + + resolver: function(prefix){ + return (prefix == 'xhtml') ? 'http://www.w3.org/1999/xhtml' : false; + }, + + getElementsByTagName: function(context, tagName){ + var found = []; + for (var i = 0, j = context.length; i < j; i++) found.extend(context[i].getElementsByTagName(tagName)); + return found; + } + +}; + +$$.shared.method = (window.xpath) ? 'xpath' : 'normal'; + +/* +Class: Element + Custom class to allow all of its methods to be used with any DOM element via the dollar function <$>. +*/ + +Element.Methods.Dom = { + + /* + Property: getElements + Gets all the elements within an element that match the given (single) selector. + Returns as <Elements>. + + Arguments: + selector - string; the css selector to match + + Examples: + >$('myElement').getElements('a'); // get all anchors within myElement + >$('myElement').getElements('input[name=dialog]') //get all input tags with name 'dialog' + >$('myElement').getElements('input[name$=log]') //get all input tags with names ending with 'log' + + Notes: + Supports these operators in attribute selectors: + + - = : is equal to + - ^= : starts-with + - $= : ends-with + - != : is not equal to + + Xpath is used automatically for compliant browsers. + */ + + getElements: function(selector, nocash){ + var items = []; + selector = selector.trim().split(' '); + for (var i = 0, j = selector.length; i < j; i++){ + var sel = selector[i]; + var param = sel.match($$.shared.regexp); + if (!param) break; + param[1] = param[1] || '*'; + var temp = $$.shared[$$.shared.method].getParam(items, this, param, i); + if (!temp) break; + items = temp; + } + return $$.shared[$$.shared.method].getItems(items, this, nocash); + }, + + /* + Property: getElement + Same as <Element.getElements>, but returns only the first. Alternate syntax for <$E>, where filter is the Element. + Returns as <Element>. + + Arguments: + selector - string; css selector + */ + + getElement: function(selector){ + return $(this.getElements(selector, true)[0] || false); + }, + + /* + Property: getElementsBySelector + Same as <Element.getElements>, but allows for comma separated selectors, as in css. Alternate syntax for <$$>, where filter is the Element. + Returns as <Elements>. + + Arguments: + selector - string; css selector + */ + + getElementsBySelector: function(selector, nocash){ + var elements = []; + selector = selector.split(','); + for (var i = 0, j = selector.length; i < j; i++) elements = elements.concat(this.getElements(selector[i], true)); + return (nocash) ? elements : $$.unique(elements); + } + +}; + +Element.extend({ + + /* + Property: getElementById + Targets an element with the specified id found inside the Element. Does not overwrite document.getElementById. + + Arguments: + id - string; the id of the element to find. + */ + + getElementById: function(id){ + var el = document.getElementById(id); + if (!el) return false; + for (var parent = el.parentNode; parent != this; parent = parent.parentNode){ + if (!parent) return false; + } + return el; + }/*compatibility*/, + + getElementsByClassName: function(className){ + return this.getElements('.' + className); + } + + /*end compatibility*/ + +}); + +document.extend(Element.Methods.Dom); +Element.extend(Element.Methods.Dom); + +/* Script: Element.Form.js Contains Element prototypes to deal with Forms and their elements. @@ -3092,6 +3411,161 @@ Element.extend({ }); /* +Script: Element.Dimensions.js + Contains Element prototypes to deal with Element size and position in space. + +Note: + The functions in this script require n XHTML doctype. + +License: + MIT-style license. +*/ + +/* +Class: Element + Custom class to allow all of its methods to be used with any DOM element via the dollar function <$>. +*/ + +Element.extend({ + + /* + Property: scrollTo + Scrolls the element to the specified coordinated (if the element has an overflow) + + Arguments: + x - the x coordinate + y - the y coordinate + + Example: + >$('myElement').scrollTo(0, 100) + */ + + scrollTo: function(x, y){ + this.scrollLeft = x; + this.scrollTop = y; + }, + + /* + Property: getSize + Return an Object representing the size/scroll values of the element. + + Example: + (start code) + $('myElement').getSize(); + (end) + + Returns: + (start code) + { + 'scroll': {'x': 100, 'y': 100}, + 'size': {'x': 200, 'y': 400}, + 'scrollSize': {'x': 300, 'y': 500} + } + (end) + */ + + getSize: function(){ + return { + 'scroll': {'x': this.scrollLeft, 'y': this.scrollTop}, + 'size': {'x': this.offsetWidth, 'y': this.offsetHeight}, + 'scrollSize': {'x': this.scrollWidth, 'y': this.scrollHeight} + }; + }, + + /* + Property: getPosition + Returns the real offsets of the element. + + Arguments: + overflown - optional, an array of nested scrolling containers for scroll offset calculation, use this if your element is inside any element containing scrollbars + + Example: + >$('element').getPosition(); + + Returns: + >{x: 100, y:500}; + */ + + getPosition: function(overflown){ + overflown = overflown || []; + var el = this, left = 0, top = 0; + do { + left += el.offsetLeft || 0; + top += el.offsetTop || 0; + el = el.offsetParent; + } while (el); + overflown.each(function(element){ + left -= element.scrollLeft || 0; + top -= element.scrollTop || 0; + }); + return {'x': left, 'y': top}; + }, + + /* + Property: getTop + Returns the distance from the top of the window to the Element. + + Arguments: + overflown - optional, an array of nested scrolling containers, see Element::getPosition + */ + + getTop: function(overflown){ + return this.getPosition(overflown).y; + }, + + /* + Property: getLeft + Returns the distance from the left of the window to the Element. + + Arguments: + overflown - optional, an array of nested scrolling containers, see Element::getPosition + */ + + getLeft: function(overflown){ + return this.getPosition(overflown).x; + }, + + /* + Property: getCoordinates + Returns an object with width, height, left, right, top, and bottom, representing the values of the Element + + Arguments: + overflown - optional, an array of nested scrolling containers, see Element::getPosition + + Example: + (start code) + var myValues = $('myElement').getCoordinates(); + (end) + + Returns: + (start code) + { + width: 200, + height: 300, + left: 100, + top: 50, + right: 300, + bottom: 350 + } + (end) + */ + + getCoordinates: function(overflown){ + var position = this.getPosition(overflown); + var obj = { + 'width': this.offsetWidth, + 'height': this.offsetHeight, + 'left': position.x, + 'top': position.y + }; + obj.right = obj.left + obj.width; + obj.bottom = obj.top + obj.height; + return obj; + } + +}); + +/* Script: Window.DomReady.js Contains the custom event domready, for window. @@ -3272,6 +3746,633 @@ window.extend({ }); /* +Script: Fx.Base.js + Contains <Fx.Base>, the foundamentals of the MooTools Effects. + +License: + MIT-style license. +*/ + +var Fx = {}; + +/* +Class: Fx.Base + Base class for the Effects. + +Options: + transition - the equation to use for the effect see <Fx.Transitions>; default is <Fx.Transitions.Sine.easeInOut> + duration - the duration of the effect in ms; 500 is the default. + unit - the unit is 'px' by default (other values include things like 'em' for fonts or '%'). + wait - boolean: to wait or not to wait for a current transition to end before running another of the same instance. defaults to true. + fps - the frames per second for the transition; default is 50 + +Events: + onStart - the function to execute as the effect begins; nothing (<Class.empty>) by default. + onComplete - the function to execute after the effect has processed; nothing (<Class.empty>) by default. + onCancel - the function to execute when you manually stop the effect. +*/ + +Fx.Base = new Class({ + + options: { + onStart: Class.empty, + onComplete: Class.empty, + onCancel: Class.empty, + transition: function(p){ + return -(Math.cos(Math.PI * p) - 1) / 2; + }, + duration: 500, + unit: 'px', + wait: true, + fps: 50 + }, + + initialize: function(options){ + this.element = this.element || null; + this.setOptions(options); + if (this.options.initialize) this.options.initialize.call(this); + }, + + step: function(){ + var time = $time(); + if (time < this.time + this.options.duration){ + this.delta = this.options.transition((time - this.time) / this.options.duration); + this.setNow(); + this.increase(); + } else { + this.stop(true); + this.set(this.to); + this.fireEvent('onComplete', this.element, 10); + this.callChain(); + } + }, + + /* + Property: set + Immediately sets the value with no transition. + + Arguments: + to - the point to jump to + + Example: + >var myFx = new Fx.Style('myElement', 'opacity').set(0); //will make it immediately transparent + */ + + set: function(to){ + this.now = to; + this.increase(); + return this; + }, + + setNow: function(){ + this.now = this.compute(this.from, this.to); + }, + + compute: function(from, to){ + return (to - from) * this.delta + from; + }, + + /* + Property: start + Executes an effect from one position to the other. + + Arguments: + from - integer: staring value + to - integer: the ending value + + Examples: + >var myFx = new Fx.Style('myElement', 'opacity').start(0,1); //display a transition from transparent to opaque. + */ + + start: function(from, to){ + if (!this.options.wait) this.stop(); + else if (this.timer) return this; + this.from = from; + this.to = to; + this.change = this.to - this.from; + this.time = $time(); + this.timer = this.step.periodical(Math.round(1000 / this.options.fps), this); + this.fireEvent('onStart', this.element); + return this; + }, + + /* + Property: stop + Stops the transition. + */ + + stop: function(end){ + if (!this.timer) return this; + this.timer = $clear(this.timer); + if (!end) this.fireEvent('onCancel', this.element); + return this; + }/*compatibility*/, + + custom: function(from, to){ + return this.start(from, to); + }, + + clearTimer: function(end){ + return this.stop(end); + } + + /*end compatibility*/ + +}); + +Fx.Base.implement(new Chain, new Events, new Options); + +/* +Script: Fx.CSS.js + Css parsing class for effects. Required by <Fx.Style>, <Fx.Styles>, <Fx.Elements>. No documentation needed, as its used internally. + +License: + MIT-style license. +*/ + +Fx.CSS = { + + select: function(property, to){ + if (property.test(/color/i)) return this.Color; + var type = $type(to); + if ((type == 'array') || (type == 'string' && to.contains(' '))) return this.Multi; + return this.Single; + }, + + parse: function(el, property, fromTo){ + if (!fromTo.push) fromTo = [fromTo]; + var from = fromTo[0], to = fromTo[1]; + if (!$chk(to)){ + to = from; + from = el.getStyle(property); + } + var css = this.select(property, to); + return {'from': css.parse(from), 'to': css.parse(to), 'css': css}; + } + +}; + +Fx.CSS.Single = { + + parse: function(value){ + return parseFloat(value); + }, + + getNow: function(from, to, fx){ + return fx.compute(from, to); + }, + + getValue: function(value, unit, property){ + if (unit == 'px' && property != 'opacity') value = Math.round(value); + return value + unit; + } + +}; + +Fx.CSS.Multi = { + + parse: function(value){ + return value.push ? value : value.split(' ').map(function(v){ + return parseFloat(v); + }); + }, + + getNow: function(from, to, fx){ + var now = []; + for (var i = 0; i < from.length; i++) now[i] = fx.compute(from[i], to[i]); + return now; + }, + + getValue: function(value, unit, property){ + if (unit == 'px' && property != 'opacity') value = value.map(Math.round); + return value.join(unit + ' ') + unit; + } + +}; + +Fx.CSS.Color = { + + parse: function(value){ + return value.push ? value : value.hexToRgb(true); + }, + + getNow: function(from, to, fx){ + var now = []; + for (var i = 0; i < from.length; i++) now[i] = Math.round(fx.compute(from[i], to[i])); + return now; + }, + + getValue: function(value){ + return 'rgb(' + value.join(',') + ')'; + } + +}; + +/* +Script: Fx.Styles.js + Contains <Fx.Styles> + +License: + MIT-style license. +*/ + +/* +Class: Fx.Styles + Allows you to animate multiple css properties at once; + Colors must be in hex format. + Inherits methods, properties, options and events from <Fx.Base>. + +Arguments: + el - the $(element) to apply the styles transition to + options - the fx options (see: <Fx.Base>) + +Example: + (start code) + var myEffects = new Fx.Styles('myElement', {duration: 1000, transition: Fx.Transitions.linear}); + + //height from 10 to 100 and width from 900 to 300 + myEffects.start({ + 'height': [10, 100], + 'width': [900, 300] + }); + + //or height from current height to 100 and width from current width to 300 + myEffects.start({ + 'height': 100, + 'width': 300 + }); + (end) +*/ + +Fx.Styles = Fx.Base.extend({ + + initialize: function(el, options){ + this.element = $(el); + this.parent(options); + }, + + setNow: function(){ + for (var p in this.from) this.now[p] = this.css[p].getNow(this.from[p], this.to[p], this); + }, + + set: function(to){ + var parsed = {}; + this.css = {}; + for (var p in to){ + this.css[p] = Fx.CSS.select(p, to[p]); + parsed[p] = this.css[p].parse(to[p]); + } + return this.parent(parsed); + }, + + /* + Property: start + Executes a transition for any number of css properties in tandem. + + Arguments: + obj - an object containing keys that specify css properties to alter and values that specify either the from/to values (as an array) or just the end value (an integer). + + Example: + see <Fx.Styles> + */ + + start: function(obj){ + if (this.timer && this.options.wait) return this; + this.now = {}; + this.css = {}; + var from = {}, to = {}; + for (var p in obj){ + var parsed = Fx.CSS.parse(this.element, p, obj[p]); + from[p] = parsed.from; + to[p] = parsed.to; + this.css[p] = parsed.css; + } + return this.parent(from, to); + }, + + increase: function(){ + for (var p in this.now) this.element.setStyle(p, this.css[p].getValue(this.now[p], this.options.unit, p)); + } + +}); + +/* +Class: Element + Custom class to allow all of its methods to be used with any DOM element via the dollar function <$>. +*/ + +Element.extend({ + + /* + Property: effects + Applies an <Fx.Styles> to the Element; This a shortcut for <Fx.Styles>. + + Example: + >var myEffects = $(myElement).effects({duration: 1000, transition: Fx.Transitions.Sine.easeInOut}); + >myEffects.start({'height': [10, 100], 'width': [900, 300]}); + */ + + effects: function(options){ + return new Fx.Styles(this, options); + } + +}); + +/* +Script: Drag.Base.js + Contains <Drag.Base>, <Element.makeResizable> + +License: + MIT-style license. +*/ + +var Drag = {}; + +/* +Class: Drag.Base + Modify two css properties of an element based on the position of the mouse. + +Note: + Drag.Base requires an XHTML doctype. + +Arguments: + el - the $(element) to apply the transformations to. + options - optional. The options object. + +Options: + handle - the $(element) to act as the handle for the draggable element. defaults to the $(element) itself. + modifiers - an object. see Modifiers Below. + limit - an object, see Limit below. + grid - optional, distance in px for snap-to-grid dragging + snap - optional, the distance you have to drag before the element starts to respond to the drag. defaults to false + + modifiers: + x - string, the style you want to modify when the mouse moves in an horizontal direction. defaults to 'left' + y - string, the style you want to modify when the mouse moves in a vertical direction. defaults to 'top' + + limit: + x - array with start and end limit relative to modifiers.x + y - array with start and end limit relative to modifiers.y + +Events: + onStart - optional, function to execute when the user starts to drag (on mousedown); + onComplete - optional, function to execute when the user completes the drag. + onDrag - optional, function to execute at every step of the drag +*/ + +Drag.Base = new Class({ + + options: { + handle: false, + unit: 'px', + onStart: Class.empty, + onBeforeStart: Class.empty, + onComplete: Class.empty, + onSnap: Class.empty, + onDrag: Class.empty, + limit: false, + modifiers: {x: 'left', y: 'top'}, + grid: false, + snap: 6 + }, + + initialize: function(el, options){ + this.setOptions(options); + this.element = $(el); + this.handle = $(this.options.handle) || this.element; + this.mouse = {'now': {}, 'pos': {}}; + this.value = {'start': {}, 'now': {}}; + this.bound = { + 'start': this.start.bindWithEvent(this), + 'check': this.check.bindWithEvent(this), + 'drag': this.drag.bindWithEvent(this), + 'stop': this.stop.bind(this) + }; + this.attach(); + if (this.options.initialize) this.options.initialize.call(this); + }, + + attach: function(){ + this.handle.addEvent('mousedown', this.bound.start); + return this; + }, + + detach: function(){ + this.handle.removeEvent('mousedown', this.bound.start); + return this; + }, + + start: function(event){ + this.fireEvent('onBeforeStart', this.element); + this.mouse.start = event.page; + var limit = this.options.limit; + this.limit = {'x': [], 'y': []}; + for (var z in this.options.modifiers){ + if (!this.options.modifiers[z]) continue; + this.value.now[z] = this.element.getStyle(this.options.modifiers[z]).toInt(); + this.mouse.pos[z] = event.page[z] - this.value.now[z]; + if (limit && limit[z]){ + for (var i = 0; i < 2; i++){ + if ($chk(limit[z][i])) this.limit[z][i] = ($type(limit[z][i]) == 'function') ? limit[z][i]() : limit[z][i]; + } + } + } + if ($type(this.options.grid) == 'number') this.options.grid = {'x': this.options.grid, 'y': this.options.grid}; + document.addListener('mousemove', this.bound.check); + document.addListener('mouseup', this.bound.stop); + this.fireEvent('onStart', this.element); + event.stop(); + }, + + check: function(event){ + var distance = Math.round(Math.sqrt(Math.pow(event.page.x - this.mouse.start.x, 2) + Math.pow(event.page.y - this.mouse.start.y, 2))); + if (distance > this.options.snap){ + document.removeListener('mousemove', this.bound.check); + document.addListener('mousemove', this.bound.drag); + this.drag(event); + this.fireEvent('onSnap', this.element); + } + event.stop(); + }, + + drag: function(event){ + this.out = false; + this.mouse.now = event.page; + for (var z in this.options.modifiers){ + if (!this.options.modifiers[z]) continue; + this.value.now[z] = this.mouse.now[z] - this.mouse.pos[z]; + if (this.limit[z]){ + if ($chk(this.limit[z][1]) && (this.value.now[z] > this.limit[z][1])){ + this.value.now[z] = this.limit[z][1]; + this.out = true; + } else if ($chk(this.limit[z][0]) && (this.value.now[z] < this.limit[z][0])){ + this.value.now[z] = this.limit[z][0]; + this.out = true; + } + } + if (this.options.grid[z]) this.value.now[z] -= (this.value.now[z] % this.options.grid[z]); + this.element.setStyle(this.options.modifiers[z], this.value.now[z] + this.options.unit); + } + this.fireEvent('onDrag', this.element); + event.stop(); + }, + + stop: function(){ + document.removeListener('mousemove', this.bound.check); + document.removeListener('mousemove', this.bound.drag); + document.removeListener('mouseup', this.bound.stop); + this.fireEvent('onComplete', this.element); + } + +}); + +Drag.Base.implement(new Events, new Options); + +/* +Class: Element + Custom class to allow all of its methods to be used with any DOM element via the dollar function <$>. +*/ + +Element.extend({ + + /* + Property: makeResizable + Makes an element resizable (by dragging) with the supplied options. + + Arguments: + options - see <Drag.Base> for acceptable options. + */ + + makeResizable: function(options){ + return new Drag.Base(this, $merge({modifiers: {x: 'width', y: 'height'}}, options)); + } + +}); + +/* +Script: Drag.Move.js + Contains <Drag.Move>, <Element.makeDraggable> + +License: + MIT-style license. +*/ + +/* +Class: Drag.Move + Extends <Drag.Base>, has additional functionality for dragging an element, support snapping and droppables. + Drag.move supports either position absolute or relative. If no position is found, absolute will be set. + Inherits methods, properties, options and events from <Drag.Base>. + +Note: + Drag.Move requires an XHTML doctype. + +Arguments: + el - the $(element) to apply the drag to. + options - optional. see Options below. + +Options: + all the drag.Base options, plus: + container - an element, will fill automatically limiting options based on the $(element) size and position. defaults to false (no limiting) + droppables - an array of elements you can drop your draggable to. + overflown - an array of nested scrolling containers, see Element::getPosition +*/ + +Drag.Move = Drag.Base.extend({ + + options: { + droppables: [], + container: false, + overflown: [] + }, + + initialize: function(el, options){ + this.setOptions(options); + this.element = $(el); + this.droppables = $$(this.options.droppables); + this.container = $(this.options.container); + this.position = {'element': this.element.getStyle('position'), 'container': false}; + if (this.container) this.position.container = this.container.getStyle('position'); + if (!['relative', 'absolute', 'fixed'].contains(this.position.element)) this.position.element = 'absolute'; + var top = this.element.getStyle('top').toInt(); + var left = this.element.getStyle('left').toInt(); + if (this.position.element == 'absolute' && !['relative', 'absolute', 'fixed'].contains(this.position.container)){ + top = $chk(top) ? top : this.element.getTop(this.options.overflown); + left = $chk(left) ? left : this.element.getLeft(this.options.overflown); + } else { + top = $chk(top) ? top : 0; + left = $chk(left) ? left : 0; + } + this.element.setStyles({'top': top, 'left': left, 'position': this.position.element}); + this.parent(this.element); + }, + + start: function(event){ + this.overed = null; + if (this.container){ + var cont = this.container.getCoordinates(); + var el = this.element.getCoordinates(); + if (this.position.element == 'absolute' && !['relative', 'absolute', 'fixed'].contains(this.position.container)){ + this.options.limit = { + 'x': [cont.left, cont.right - el.width], + 'y': [cont.top, cont.bottom - el.height] + }; + } else { + this.options.limit = { + 'y': [0, cont.height - el.height], + 'x': [0, cont.width - el.width] + }; + } + } + this.parent(event); + }, + + drag: function(event){ + this.parent(event); + var overed = this.out ? false : this.droppables.filter(this.checkAgainst, this).getLast(); + if (this.overed != overed){ + if (this.overed) this.overed.fireEvent('leave', [this.element, this]); + this.overed = overed ? overed.fireEvent('over', [this.element, this]) : null; + } + return this; + }, + + checkAgainst: function(el){ + el = el.getCoordinates(this.options.overflown); + var now = this.mouse.now; + return (now.x > el.left && now.x < el.right && now.y < el.bottom && now.y > el.top); + }, + + stop: function(){ + if (this.overed && !this.out) this.overed.fireEvent('drop', [this.element, this]); + else this.element.fireEvent('emptydrop', this); + this.parent(); + return this; + } + +}); + +/* +Class: Element + Custom class to allow all of its methods to be used with any DOM element via the dollar function <$>. +*/ + +Element.extend({ + + /* + Property: makeDraggable + Makes an element draggable with the supplied options. + + Arguments: + options - see <Drag.Move> and <Drag.Base> for acceptable options. + */ + + makeDraggable: function(options){ + return new Drag.Move(this, options); + } + +}); + +/* Script: XHR.js Contains the basic XMLHttpRequest Class Wrapper. diff --git a/pages/Makefile b/pages/Makefile index 09818e5..fde3974 100644 --- a/pages/Makefile +++ b/pages/Makefile @@ -22,7 +22,8 @@ OBJS = menu.o channels.o recordings.o schedule.o \ searchtimers.o edit_searchtimer.o searchresults.o \ searchepg.o login.o ibox.o xmlresponse.o \ play_recording.o pause_recording.o stop_recording.o \ - ffw_recording.o rwd_recording.o setup.o content.o + ffw_recording.o rwd_recording.o setup.o content.o \ + epginfo.o ### Default rules: diff --git a/pages/channels_widget.ecpp b/pages/channels_widget.ecpp index ed6d30e..fcf8321 100644 --- a/pages/channels_widget.ecpp +++ b/pages/channels_widget.ecpp @@ -27,8 +27,7 @@ if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); int lastChannel = LiveSetup().GetLastChannel(); </%cpp> <select name="<$ name $>" <{ reply.out() << ( !onchange.empty() ? "onchange=\""+onchange+"\"" : "" ); }>> -% for ( cChannel *listChannel = Channels.First(); listChannel && listChannel->Number() <= lastChannel; -% listChannel = Channels.Next( listChannel ) ) { +% for ( cChannel *listChannel = Channels.First(); listChannel && listChannel->Number() <= lastChannel; listChannel = Channels.Next( listChannel ) ) { % if ( listChannel->GroupSep() || *listChannel->Name() == '\0' ) % continue; % diff --git a/pages/edit_searchtimer.ecpp b/pages/edit_searchtimer.ecpp index c541ede..4d27b59 100644 --- a/pages/edit_searchtimer.ecpp +++ b/pages/edit_searchtimer.ecpp @@ -331,7 +331,6 @@ if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); <head> <title>VDR Live - <$ editsearchtimer ? tr("Edit search timer") : tr("New search timer") $></title> <& pageelems.stylesheets &> - <& tooltip.javascript var=("domTT_styleClass") value=("domTTepg") &> <& pageelems.ajax_js &> <script type="text/javascript"><!-- diff --git a/pages/edit_timer.ecpp b/pages/edit_timer.ecpp index 73e2852..6bf3b9c 100644 --- a/pages/edit_timer.ecpp +++ b/pages/edit_timer.ecpp @@ -126,7 +126,6 @@ if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); <head> <title>VDR Live - <$ timer ? tr("Edit timer") : tr("New timer") $></title> <& pageelems.stylesheets &> - <& tooltip.javascript var=("domTT_styleClass") value=("domTTepg") &> <& pageelems.ajax_js &> </head> <body> diff --git a/pages/epginfo.ecpp b/pages/epginfo.ecpp new file mode 100644 index 0000000..9487040 --- /dev/null +++ b/pages/epginfo.ecpp @@ -0,0 +1,114 @@ +<%pre> +#include <sys/stat.h> +#include <vdr/tools.h> +#include "exception.h" +#include "setup.h" +#include "tools.h" +#include "epg_events.h" +#include "recordings.h" + +using namespace vdrlive; +using namespace std; + +</%pre> +<%args> + string epgid; + string async; +</%args> +<%session scope="global"> +bool logged_in(false); +</%session> +<%include>page_init.eh</%include> +<%cpp> +if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); +</%cpp> +<%cpp> + pageTitle = tr("Electronic program guide information"); + + bool ajaxReq = !async.empty() && (lexical_cast<int>(async) != 0); + + EpgInfoPtr epgEvent; + bool aboutBox(false); + + // These get initialized when needed. When freed by getting out + // out of scope they will release (if initialized) important + // Semaphores/Locks. + cSchedulesLock schedulesLock; + RecordingsManagerPtr recordings; + + if (!epgid.empty()) { + + const string recording("recording_"); + const string event("event_"); + const string aboutbox("aboutBox"); + + // check for recording: + if (epgid.compare(0, recording.length(), recording) == 0) { + recordings = LiveRecordingsManager(); + const cRecording* recording = recordings->GetByMd5Hash(epgid); + if (recording == 0) { + throw HtmlError(tr("Couldn't find recording or no recordings available")); + } + epgEvent = EpgEvents::CreateEpgInfo(epgid, recording); + } + // check for event: + else if (epgid.compare(0, event.length(), event) == 0) { + const cSchedules* schedules = cSchedules::Schedules(schedulesLock); + if (!schedules) { + throw HtmlError(tr("Error aquiring schedules")); + } + epgEvent = EpgEvents::CreateEpgInfo(epgid, schedules); + } + // check for aboutbox: + else if (epgid.compare(0, aboutbox.length(), aboutbox) == 0) { + aboutBox = true; + } + } +</%cpp> +<& pageelems.doc_type &> +<html> + <head> + <title>VDR-Live - <$ pageTitle $></title> +<%cpp> + if (!ajaxReq) { +</%cpp> + <& pageelems.stylesheets &> + <& pageelems.ajax_js &> +<%cpp> + } +</%cpp> + </head> + <body> +<%cpp> + if (!ajaxReq) { +</%cpp> + <& pageelems.logo &> + <& menu &> +<%cpp> + } +</%cpp> + <div class="inhalt"> +<%cpp> + if (epgEvent) { + string start(epgEvent->StartTime("%a,") + string(" ") + + epgEvent->StartTime(tr("%b %d %y")) + string(" ") + + epgEvent->StartTime(tr("%I:%M %p"))); + string tools_component; + if (recordings) { + tools_component = epgEvent->Archived().empty() ? "recordings.rec_tools" : "recordings.archived_disc" ; + } +</%cpp> + <& pageelems.epg_tt_box boxId=(epgEvent->Id()) caption=(epgEvent->Caption()) tools_comp=(tools_component) time=(start) title=(epgEvent->Title()) short_descr=(epgEvent->ShortDescr()) long_descr=(epgEvent->LongDescr()) archived=(epgEvent->Archived()) elapsed=(epgEvent->Elapsed()) &> +<%cpp> + } + if (aboutBox) { +</%cpp> + <& pageelems.about_tt_box &> +<%cpp> + } +</%cpp> + </div> + </body> +</html> + +<%include>page_exit.eh</%include> diff --git a/pages/ibox.ecpp b/pages/ibox.ecpp index 4d819f5..0118c07 100644 --- a/pages/ibox.ecpp +++ b/pages/ibox.ecpp @@ -25,7 +25,6 @@ int update_status(1); </%session> <%cpp> if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); - EpgEvents epgEvents; string EMPTY_STR; tChannelID prev_chan; tChannelID next_chan; @@ -39,25 +38,16 @@ int update_status(1); #else const char* NowReplaying = cControl::Control()?cReplayControl::LastReplayed():NULL; #endif + + EpgInfoPtr epgEvent; + if (NowReplaying) { RecordingsManagerPtr recManager = LiveRecordingsManager(); cRecording *recording = Recordings.GetByName(NowReplaying); if (recording) { - string name(recording->Name()); - size_t index = name.find_last_of('~'); - if (index != string::npos) - name = name.substr(index, name.length()); - const cRecordingInfo* info = recording->Info(); - if (info) { - EpgEventPtr epgEvent(new EpgEvent(recManager->Md5Hash(recording), - tr("playing recording"), - info->Title() ? info->Title() : name, - info->ShortText() ? info->ShortText() : "", - info->Description() ? info->Description() : "", - recording->start, - recording->start)); - epgEvents.push_back(epgEvent); - } + epgEvent = EpgEvents::CreateEpgInfo(recManager->Md5Hash(recording), + recording, + tr("playing recording")); } } else { @@ -75,7 +65,7 @@ int update_status(1); if (tmp) next_chan = tmp->GetChannelID(); - string chanName(Channel->Name()); + const string chanName(Channel->Name()); cSchedulesLock schedulesLock; const cSchedules* Schedules = cSchedules::Schedules(schedulesLock); const cSchedule *Schedule = Schedules->GetSchedule(Channel); @@ -83,80 +73,61 @@ int update_status(1); if (Schedule) { const cEvent *Event = Schedule->GetPresentEvent(); if (Event) { - EpgEventPtr epgEvent(new EpgEvent(CHANNEL_STR, - Event, - Channel->Name())); - epgEvents.push_back(epgEvent); + epgEvent = EpgEvents::CreateEpgInfo(Channel, + Event, + CHANNEL_STR.c_str()); } else { - string noInfo(tr("no epg info for current event!")); - EpgEventPtr epgEvent(new EpgEvent(CHANNEL_STR, - chanName, - noInfo, - EMPTY_STR, EMPTY_STR, - time(0), - time(0))); - epgEvents.push_back(epgEvent); + const string noInfo(tr("no epg info for current event!")); + epgEvent = EpgEvents::CreateEpgInfo(CHANNEL_STR, + chanName, + noInfo); } } else { - string noInfo(tr("no epg info for current channel!")); - EpgEventPtr epgEvent(new EpgEvent(CHANNEL_STR, - Channel->Name(), - noInfo, - EMPTY_STR, EMPTY_STR, - time(0), - time(0))); - epgEvents.push_back(epgEvent); + const string noInfo(tr("no epg info for current channel!")); + epgEvent = EpgEvents::CreateEpgInfo(CHANNEL_STR, + Channel->Name(), + noInfo); } } else { - string chanName(tr("no current channel!")); - EpgEventPtr epgEvent(new EpgEvent(CHANNEL_STR, - chanName, - chanName, - EMPTY_STR, EMPTY_STR, - time(0), - time(0))); - epgEvents.push_back(epgEvent); + const string chanName(tr("no current channel!")); + epgEvent = EpgEvents::CreateEpgInfo(CHANNEL_STR, + chanName, + chanName); } } - if (epgEvents.size() == 0) { - string ERROR_STR("error"); - string noInfo(tr("error retrieving status info!")); - string chanName(tr("no current channel!")); - EpgEventPtr epgEvent(new EpgEvent(ERROR_STR, - chanName, - noInfo, - EMPTY_STR, EMPTY_STR, - time(0), - time(0))); - epgEvents.push_back(epgEvent); + if (!epgEvent) { + const string ERROR_STR("error"); + const string noInfo(tr("error retrieving status info!")); + const string chanName(tr("no current channel!")); + epgEvent = EpgEvents::CreateEpgInfo(ERROR_STR, + chanName, + noInfo); } - for (vector<EpgEventPtr>::iterator i = epgEvents.begin(); i != epgEvents.end(); ++i) { - EpgEventPtr epg = *i; + else { if (prev_chan.Valid() && next_chan.Valid()) { </%cpp> -<& xmlresponse.ibox update=(update_status) type=(epg->Id()) caption=(epg->Caption()) currentTime=(epg->CurrentTime(tr("%I:%M:%S %p"))) duration=(epg->StartTime(tr("%I:%M %p")) + string(" - ") + epg->EndTime(tr("%I:%M %p"))) title=(epg->Title()) elapsed=(epg->Elapsed()) prev_chan=(prev_chan) next_chan=(next_chan) &> +<& xmlresponse.ibox update=(update_status) type=(epgEvent->Id()) caption=(epgEvent->Caption()) currentTime=(epgEvent->CurrentTime(tr("%I:%M:%S %p"))) duration=(epgEvent->StartTime(tr("%I:%M %p")) + string(" - ") + epgEvent->EndTime(tr("%I:%M %p"))) title=(epgEvent->Title()) elapsed=(epgEvent->Elapsed()) prev_chan=(prev_chan) next_chan=(next_chan) &> <%cpp> } else if (prev_chan.Valid()) { </%cpp> -<& xmlresponse.ibox update=(update_status) type=(epg->Id()) caption=(epg->Caption()) currentTime=(epg->CurrentTime(tr("%I:%M:%S %p"))) duration=(epg->StartTime(tr("%I:%M %p")) + string(" - ") + epg->EndTime(tr("%I:%M %p"))) title=(epg->Title()) elapsed=(epg->Elapsed()) prev_chan=(prev_chan) &> +<& xmlresponse.ibox update=(update_status) type=(epgEvent->Id()) caption=(epgEvent->Caption()) currentTime=(epgEvent->CurrentTime(tr("%I:%M:%S %p"))) duration=(epgEvent->StartTime(tr("%I:%M %p")) + string(" - ") + epgEvent->EndTime(tr("%I:%M %p"))) title=(epgEvent->Title()) elapsed=(epgEvent->Elapsed()) prev_chan=(prev_chan) &> <%cpp> } else if (next_chan.Valid()) { </%cpp> -<& xmlresponse.ibox update=(update_status) type=(epg->Id()) caption=(epg->Caption()) currentTime=(epg->CurrentTime(tr("%I:%M:%S %p"))) duration=(epg->StartTime(tr("%I:%M %p")) + string(" - ") + epg->EndTime(tr("%I:%M %p"))) title=(epg->Title()) elapsed=(epg->Elapsed()) next_chan=(next_chan) &> +<& xmlresponse.ibox update=(update_status) type=(epgEvent->Id()) caption=(epgEvent->Caption()) currentTime=(epgEvent->CurrentTime(tr("%I:%M:%S %p"))) duration=(epgEvent->StartTime(tr("%I:%M %p")) + string(" - ") + epgEvent->EndTime(tr("%I:%M %p"))) title=(epgEvent->Title()) elapsed=(epgEvent->Elapsed()) next_chan=(next_chan) &> <%cpp> } else { </%cpp> -<& xmlresponse.ibox update=(update_status) type=(epg->Id()) caption=(epg->Caption()) currentTime=(epg->CurrentTime(tr("%I:%M:%S %p"))) duration=(epg->StartTime(tr("%I:%M %p")) + string(" - ") + epg->EndTime(tr("%I:%M %p"))) title=(epg->Title()) elapsed=(epg->Elapsed()) &> +<& xmlresponse.ibox update=(update_status) type=(epgEvent->Id()) caption=(epgEvent->Caption()) currentTime=(epgEvent->CurrentTime(tr("%I:%M:%S %p"))) duration=(epgEvent->StartTime(tr("%I:%M %p")) + string(" - ") + epgEvent->EndTime(tr("%I:%M %p"))) title=(epgEvent->Title()) elapsed=(epgEvent->Elapsed()) &> <%cpp> } - break; } </%cpp> diff --git a/pages/login.ecpp b/pages/login.ecpp index 70fe7d8..754ec82 100644 --- a/pages/login.ecpp +++ b/pages/login.ecpp @@ -38,7 +38,6 @@ if (logged_in || !LiveSetup().UseAuth()) return reply.redirect(LiveSetup().GetSt <head> <title>VDR-Live - <$ tr("Login") $></title> <& pageelems.stylesheets &> - <& tooltip.javascript var=("domTT_styleClass") value=("domTTepg") &> <& pageelems.ajax_js &> </head> <body onload="document.auth.login.focus()"> diff --git a/pages/menu.ecpp b/pages/menu.ecpp index e94e885..ff42045 100644 --- a/pages/menu.ecpp +++ b/pages/menu.ecpp @@ -32,23 +32,23 @@ if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); set_component = component; }> <div class="menu"> - <a href="whats_on.html?type=now" <& menu.setactive current=("whats_on") &>><$ tr("What's on?") $></a> | - <a href="schedule.html" <& menu.setactive current=("schedule") &>><$ tr("Schedule") $></a> | - <a href="timers.html" <& menu.setactive current=("timers") &>><$ tr("Timers") $></a> | + <a href="whats_on.html?type=now" <& menu.setactive current=("whats_on") &>><$ tr("What's on?") $></a> + | <a href="schedule.html" <& menu.setactive current=("schedule") &>><$ tr("Schedule") $></a> + | <a href="timers.html" <& menu.setactive current=("timers") &>><$ tr("Timers") $></a> % if ( LiveFeatures< features::epgsearch >().Recent() ) { - <a href="searchepg.html" <& menu.setactive current=("searchepg") &>><$ tr("Search") $></a> | - <a href="searchtimers.html" <& menu.setactive current=("searchtimers") &>><$ tr("Searchtimers") $></a> | + | <a href="searchepg.html" <& menu.setactive current=("searchepg") &>><$ tr("Search") $></a> + | <a href="searchtimers.html" <& menu.setactive current=("searchtimers") &>><$ tr("Searchtimers") $></a> % } - <a href="recordings.html" <& menu.setactive current=("recordings") &>><$ tr("Recordings") $></a> | - <a href="remote.html" <& menu.setactive current=("remote") &>><$ tr("Remote Control") $></a> | - <a href="setup.html" <& menu.setactive current=("setup") &>><$ tr("Setup") $></a> + | <a href="recordings.html" <& menu.setactive current=("recordings") &>><$ tr("Recordings") $></a> + | <a href="remote.html" <& menu.setactive current=("remote") &>><$ tr("Remote Control") $></a> + | <a href="setup.html" <& menu.setactive current=("setup") &>><$ tr("Setup") $></a> <# --- Used by D.H. to test infobox (not part of the released version) | <a href="ibox_status.html" <& menu.setactive current=("status") &>><$ tr("Status Test") $></a> --- #> % if (LiveSetup().UseAuth()) { | <a id="login" href="login.html?action=logout"><$ tr("Logout") $></a> % } - | <a<& tooltip.display domId=("aboutBox") &> href="#">?</a> + | <a <& tooltip.display domId=("aboutBox") &>>?</a> </div> % if (!component.empty()) { <div class="pagemenu"> @@ -67,9 +67,6 @@ if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); </div> </div> % } -<div style="display:none;"> - <& pageelems.about_tt_box &> -</div> <%def setactive> <%args> diff --git a/pages/pageelems.ecpp b/pages/pageelems.ecpp index cab4a5c..c8efa22 100644 --- a/pages/pageelems.ecpp +++ b/pages/pageelems.ecpp @@ -125,16 +125,18 @@ int update_status(1); <%def ajax_js> <script type="text/javascript" src="js/mootools/mootools.v1.11.js"></script> <script type="text/javascript" src="js/live/liveajax.js"></script> + <script type="text/javascript" src="js/live/infowin.js"></script> + <script type="text/javascript" src="js/live/hinttips.js"></script> <%cpp>if (LiveSetup().GetShowInfoBox()) { </%cpp> <script type="text/javascript" src="js/live/vdr_status.js"></script> <script type="text/javascript"><!-- var InfoBox = new LiveVdrInfo('ibox.xml', 'infobox'); window.addEvent('domready', function(){ - InfoBox.request(<%cpp> if (update_status) { reply.sout() << "true"; } else { reply.sout() << "false"; } </%cpp>); - }); + InfoBox.request(<%cpp> if (update_status) { reply.sout() << "true"; } else { reply.sout() << "false"; } </%cpp>); + }); window.addEvent('unload', function(){ - InfoBox.pageFinished(); - }); + InfoBox.pageFinished(); + }); --></script> <%cpp> } </%cpp> </%def> @@ -169,7 +171,7 @@ int update_status(1); </%args> <div class="epg_description" id="<$ (boxId) $>"> <div class="station"> - <div class="boxheader"><div><div><$ (caption) $><& tooltip.close domId=(boxId) &></div></div></div> + <div class="boxheader"><div><div class="caption"><$ (caption) $></div></div></div> </div> <div class="epg_content"> <div class="epg_tools"> @@ -191,11 +193,11 @@ int update_status(1); </%def> <%def about_tt_box> - <div class="about_box" id="aboutBox"> + <div class="epg_description" id="aboutBox"> <div class="station"> - <div class="boxheader"><div><div><$ tr(LIVESUMMARY) $><& tooltip.close domId=("aboutBox") &></div></div></div> + <div class="boxheader"><div><div class="caption"><$ tr(LIVESUMMARY) $></div></div></div> </div> - <div class="about_content"> + <div class="epg_content"> <div> <div class="about_head"><div><div><$ tr("Authors") $></div></div></div> <div class="about_left"><$ tr("Project leader") $>:</div> diff --git a/pages/recordings.ecpp b/pages/recordings.ecpp index 2132541..5ceb7b4 100644 --- a/pages/recordings.ecpp +++ b/pages/recordings.ecpp @@ -20,7 +20,6 @@ using namespace std; </%session> <%request scope="page"> RecordingsTree recordingsTree(LiveRecordingsManager()); -EpgEvents epgEvents; </%request> <%include>page_init.eh</%include> <%cpp> @@ -33,7 +32,6 @@ EpgEvents epgEvents; <head> <title>VDR-Live - <$ pageTitle $></title> <& pageelems.stylesheets &> - <& tooltip.javascript var=("domTT_styleClass") value=("domTTepg") &> <& pageelems.ajax_js &> <script type="text/javascript" src="treeview.js"></script> </head> @@ -52,11 +50,6 @@ EpgEvents epgEvents; </div> % } </div> -% if (Recordings.Count() > 0) { - <div class="epg_data" style="display: none;"> -<& recordings.recordings_data &> - </div> -% } </body> </html> <%include>page_exit.eh</%include> @@ -98,16 +91,13 @@ for (iter = recordingsTree.begin(path); iter != end; ++iter) { for (iter = recordingsTree.begin(path); iter != end; ++iter) { RecordingsTree::RecordingsItemPtr recItem = iter->second; if (!recItem->IsDir()) { - EpgEventPtr epgEvent(RecordingsTree::CreateEpgEvent(recItem)); - if (epgEvent) { - epgEvents.push_back(epgEvent); - } string day(FormatDateTime("%a,", recItem->StartTime())); string dayLen(lexical_cast<string, int>(day.length() - 1) + ".25em;"); - string hint(tr("Click to view details.")); if (epgEvent && !epgEvent->ShortDescr().empty()) hint = (epgEvent->ShortDescr() + "<br />" + hint); + string shortDescr(recItem->RecInfo()->ShortText() ? recItem->RecInfo()->ShortText() : ""); + string hint(tr("Click to view details.")); if (!shortDescr.empty()) hint = shortDescr + "<br />" + hint; </%cpp> <li class="recording"> - <& rec_item_file name=(recItem->Name()) level=(level) id=(recItem->Id()) day=(day) dayLen=(dayLen) startTime=(recItem->StartTime()) hint=(hint) shortDescr=(epgEvent ? epgEvent->ShortDescr() : "") archived=(epgEvent ? epgEvent->Archived() : "") archiveId=(recItem->ArchiveId()) &> + <& rec_item_file name=(recItem->Name()) level=(level) id=(recItem->Id()) day=(day) dayLen=(dayLen) startTime=(recItem->StartTime()) hint=(hint) shortDescr=(shortDescr) archived=(RecordingsManager::GetArchiveDescr(recItem->Recording())) &> </li> <%cpp> } @@ -117,24 +107,6 @@ for (iter = recordingsTree.begin(path); iter != end; ++iter) { <# ---------------------------------------------------------------------- #> -<%def recordings_data> -<%cpp> - // create hidden div for the tooltip hints. - for (vector<EpgEventPtr>::iterator i = epgEvents.begin(); i != epgEvents.end(); ++i) { - EpgEventPtr epg = *i; - string start(epg->StartTime("%a,") + string(" ") - + epg->StartTime(tr("%b %d %y")) + string(" ") - + epg->StartTime(tr("%I:%M %p"))); - string tools_component = epg->Archived().empty() ? "recordings.rec_tools" : "recordings.archived_disc" ; -</%cpp> - <& pageelems.epg_tt_box boxId=(epg->Id()) caption=(epg->Caption()) tools_comp=(tools_component) time=(start) title=(epg->Title()) short_descr=(epg->ShortDescr()) long_descr=(epg->LongDescr()) archived=(epg->Archived()) elapsed=(epg->Elapsed()) &> -<%cpp> - } -</%cpp> -</%def> - -<# ---------------------------------------------------------------------- #> - <%def rec_tools> <%args> string id; @@ -182,7 +154,6 @@ for (iter = recordingsTree.begin(path); iter != end; ++iter) { string hint; string shortDescr; string archived; - string archiveId; </%args> <div class="recording_item"> <div class="recording_imgs"><%cpp> reply.out() << StringRepeat(level + 1, "<img src=\"transparent.png\" alt=\"\" width=\"16px\" height=\"16px\" />"); </%cpp><%cpp> if (!archived.empty()) { </%cpp><& archived_disc archived=(archived) &><%cpp> } else { </%cpp><img src="<$ LiveSetup().GetThemedLink("img", "movie.png") $>" alt="movie" /><%cpp> } </%cpp></div> @@ -190,7 +161,7 @@ for (iter = recordingsTree.begin(path); iter != end; ++iter) { <div class="recording_day" style="width: <$ dayLen $>"><$ day $></div> <div class="recording_date"><$ FormatDateTime(tr("%b %d %y"), startTime) $></div> <div class="recording_time"><$ FormatDateTime(tr("%I:%M %p"), startTime) $></div> - <div class="recording_name" <& tooltip.hint text=(hint) &><& tooltip.display domId=(id) &>><$ name $><br /><%cpp>if ((name != shortDescr) && (!shortDescr.empty())) {</%cpp><span><$ shortDescr $></span><%cpp> } else { </%cpp><span> </span><%cpp> } </%cpp></div> + <div class="recording_name"><a <& tooltip.hint text=(hint) &><& tooltip.display domId=(id) &>><$ name $><br /><%cpp>if ((name != shortDescr) && (!shortDescr.empty())) {</%cpp><span><$ shortDescr $></span><%cpp> } else { </%cpp><span> </span><%cpp> } </%cpp></a></div> </div> <div class="recording_actions"> <%cpp> diff --git a/pages/remote.ecpp b/pages/remote.ecpp index 10eea15..4805728 100644 --- a/pages/remote.ecpp +++ b/pages/remote.ecpp @@ -26,7 +26,6 @@ if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); <head> <title>VDR-Live - <$ pageTitle $></title> <& pageelems.stylesheets &> - <& tooltip.javascript var=("domTT_styleClass") value=("domTTepg") &> <& pageelems.ajax_js &> <script type="text/javascript"><!-- var newImg = new Image(); diff --git a/pages/schedule.ecpp b/pages/schedule.ecpp index 39942bb..f427a53 100644 --- a/pages/schedule.ecpp +++ b/pages/schedule.ecpp @@ -59,7 +59,6 @@ if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); <head> <title>VDR Live - <$ pageTitle $></title> <& pageelems.stylesheets &> - <& tooltip.javascript var=("domTT_styleClass") value=("domTTepg") &> <& pageelems.ajax_js &> </head> <body> @@ -74,10 +73,8 @@ if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); } else { </%cpp> - <table class="listing" cellspacing="0" cellpadding="0"> + <table class="listing" cellspacing="0" cellpadding="0"> <%cpp> - EpgEvents epgEvents; - string current_day = ""; const cEvent* PresentEvent = Schedule->GetPresentEvent(); time_t now = time(NULL) - ::Setup.EPGLinger * 60; @@ -87,21 +84,16 @@ if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); if (Event->EndTime() <= now && Event != PresentEvent) continue; - string evntId("eventId_"); - evntId += lexical_cast<std::string, int>(++evntNr); - EpgEventPtr epgEvent(new EpgEvent(evntId, Event, Channel->Name())); - epgEvents.push_back(epgEvent); + EpgInfoPtr epgEvent = EpgEvents::CreateEpgInfo(Channel, Event); + + string title(epgEvent->Title()); + string short_description(epgEvent->ShortDescr()); + string description(epgEvent->LongDescr()); + string start(epgEvent->StartTime(tr("%I:%M %p"))); + string end(epgEvent->EndTime(tr("%I:%M %p"))); + string day(epgEvent->StartTime(tr("%A, %b %d %Y"))); + string strEventID = lexical_cast<string>(Event->EventID()); - string title(Event->Title() ? Event->Title() : ""); - string short_description(Event->ShortText() ? Event->ShortText() : ""); - string description(Event->Description() ? Event->Description() : ""); - string start(Event->StartTime() ? FormatDateTime(tr("%I:%M %p"), Event->StartTime()) : ""); - string end(Event->EndTime() ? FormatDateTime(tr("%I:%M %p"), Event->EndTime()) : ""); - string day(Event->StartTime() ? FormatDateTime(tr("%A, %b %d %Y"), Event->StartTime()) : ""); - tEventID event = Event->EventID(); - ostringstream os; - os << Event->EventID(); - string strEventID = os.str(); bool truncated = false; bool lastEventCurrentDay = false; { @@ -132,26 +124,17 @@ if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); } </%cpp> <tr> - <td class="action leftcol <? lastEventCurrentDay ? "bottomrow" ?>"><& pageelems.event_timer channelid=(channel_id) eventid=(event) &></td> + <td class="action leftcol <? lastEventCurrentDay ? "bottomrow" ?>"><& pageelems.event_timer channelid=(channel_id) eventid=(strEventID) &></td> <td class="action <? lastEventCurrentDay ? "bottomrow" ?>"><%cpp>if (LiveFeatures<features::epgsearch>().Recent() ) { </%cpp><a href="searchresults.html?searchplain=<$ StringUrlEncode(title) $>"><img src="<$ LiveSetup().GetThemedLink("img", "search.png") $>" alt="" <& tooltip.hint text=(tr("Search for repeats.")) &>></img></a><%cpp> } else { </%cpp><img src="transparent.png" width="16" height="16"><%cpp> } </%cpp></td> <td class="action <? lastEventCurrentDay ? "bottomrow" ?>"><a href="http://akas.imdb.com/Tsearch?title=<$ StringUrlEncode(title) $>"><img src="<$ LiveSetup().GetThemedLink("img", "imdb.png") $>" border="0" alt="" <& tooltip.hint text=(tr("Find more at the Internet Movie Database.")) &>></img></a></td> <td class="topaligned <? lastEventCurrentDay ? "bottomrow" ?>"><div class="withmargin"><$ start $> - <$ end $></div></td> - <td class="<? (Event == PresentEvent) ? "current" ?> topaligned rightcol <? lastEventCurrentDay ? "bottomrow" ?>"><div class="more withmargin" <& tooltip.hint text=(StringEscapeAndBreak(StringWordTruncate(description, 300, truncated)) + "<br />" + tr("Click to view details.")) &><& tooltip.display domId=(epgEvent->Id()) &>><span class="title"><$ title $></span><br /><span class="short"><%cpp>if (short_description.empty()) { </%cpp> <%cpp> } </%cpp><$ short_description $></span></div></td> + <td class="<? (Event == PresentEvent) ? "current" ?> topaligned rightcol <? lastEventCurrentDay ? "bottomrow" ?>"><div class="more withmargin"><a <& tooltip.hint text=(StringEscapeAndBreak(StringWordTruncate(description, 300, truncated)) + "<br />" + tr("Click to view details.")) &><& tooltip.display domId=(epgEvent->Id()) &>><span class="title"><$ title $></span><br /><span class="short"><%cpp>if (short_description.empty()) { </%cpp> <%cpp> } </%cpp><$ short_description $></span></a></div></td> </tr> <%cpp> } </%cpp> </table> - </div> - <div class="epg_data" style="display: none;"> -<%cpp> - // create hidden div for the tooltip hints. - for (vector<EpgEventPtr>::iterator i = epgEvents.begin(); i != epgEvents.end(); ++i) { - EpgEventPtr epg = *i; -</%cpp> - <& pageelems.epg_tt_box boxId=(epg->Id()) caption=(epg->Caption()) time=(epg->StartTime(tr("%I:%M %p")) + string(" - ") + epg->EndTime(tr("%I:%M %p"))) title=(epg->Title()) short_descr=(epg->ShortDescr()) long_descr=(epg->LongDescr()) elapsed=(epg->Elapsed()) &> <%cpp> - } } </%cpp> </div> diff --git a/pages/searchepg.ecpp b/pages/searchepg.ecpp index 9ed1387..f885d4e 100644 --- a/pages/searchepg.ecpp +++ b/pages/searchepg.ecpp @@ -152,7 +152,6 @@ if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); <head> <title>VDR Live - <$ tr("Search") $></title> <& pageelems.stylesheets &> - <& tooltip.javascript var=("domTT_styleClass") value=("domTTepg") &> <& pageelems.ajax_js &> <script type="text/javascript"><!-- diff --git a/pages/searchresults.ecpp b/pages/searchresults.ecpp index e2b83a2..63226d5 100644 --- a/pages/searchresults.ecpp +++ b/pages/searchresults.ecpp @@ -47,7 +47,6 @@ bool logged_in(false); <head> <title>VDR-Live - <$ pageTitle $></title> <& pageelems.stylesheets &> - <& tooltip.javascript var=("domTT_styleClass") value=("domTTepg") &> <& pageelems.ajax_js &> </head> <body> @@ -60,8 +59,6 @@ bool logged_in(false); <table class="listing" cellspacing="0" callpadding="0"> <%cpp> string current_day = ""; - EpgEvents epgEvents; - int evntNr = 0; for (SearchResults::iterator result = results.begin(); result != results.end(); ++result) { string channelname = Channels.GetByChannelID(result->Channel())->Name(); @@ -72,11 +69,8 @@ bool logged_in(false); tEventID event = result->EventId(); tChannelID channel_id(result->Channel()); string description = result->Description(); + string epgDomId(EpgEvents::GetDomId(result->Channel(), event)); - string evntId("eventId_"); - evntId += lexical_cast<std::string, int>(++evntNr); - EpgEventPtr epgEvent(new EpgEvent(evntId, channelname, result->Title(), result->ShortText(), description, result->StartTime(), result->StopTime())); - epgEvents.push_back(epgEvent); bool truncated = false; bool bottom = false; @@ -104,22 +98,11 @@ bool logged_in(false); <td class="action leftcol <? bottom ? "bottomrow"?>"><& pageelems.event_timer channelid=(channel_id) eventid=(event)&></td> <td class="topaligned <? bottom ? "bottomrow"?>"><div class="withmargin"><a href="schedule.html?channel=<$ channelnr $>"><$ channelname $></a></div></td> <td class="topaligned <? bottom ? "bottomrow"?>"><div class="withmargin"><$ start $> - <$ end $></div></td> - <td class="topaligned rightcol <? bottom ? "bottomrow"?>"><div class="more withmargin"<& tooltip.hint text=(StringEscapeAndBreak(StringWordTruncate(description, 300, truncated)) + "<br />" + tr("Click to view details.")) &><& tooltip.display domId=(epgEvent->Id()) &>><span class="title"><$ result->Title() $></span><br /><span class="short"><%cpp>if (result->ShortText().empty()) { </%cpp> <%cpp> } </%cpp><$ result->ShortText() $></span></div></td> + <td class="topaligned rightcol <? bottom ? "bottomrow"?>"><div class="more withmargin"><a <& tooltip.hint text=(StringEscapeAndBreak(StringWordTruncate(description, 300, truncated)) + "<br />" + tr("Click to view details.")) &><& tooltip.display domId=(epgDomId) &>><span class="title"><$ result->Title() $></span><br /><span class="short"><%cpp>if (result->ShortText().empty()) { </%cpp> <%cpp> } </%cpp><$ result->ShortText() $></span></a></div></td> </tr> % } </table> </div> - <div class="epg_data" style="display: none;"> -<%cpp> - // create hidden div for the tooltip hints. - for (vector<EpgEventPtr>::iterator i = epgEvents.begin(); i != epgEvents.end(); ++i) { - EpgEventPtr epg = *i; -</%cpp> - <& pageelems.epg_tt_box boxId=(epg->Id()) caption=(epg->Caption()) time=(epg->StartTime(tr("%I:%M %p")) + string(" - ") + epg->EndTime(tr("%I:%M %p"))) title=(epg->Title()) short_descr=(epg->ShortDescr()) long_descr=(epg->LongDescr()) elapsed=(epg->Elapsed()) &> -<%cpp> - } -</%cpp> - </div> </body> </html> diff --git a/pages/searchtimers.ecpp b/pages/searchtimers.ecpp index 4cb4a1b..d061843 100644 --- a/pages/searchtimers.ecpp +++ b/pages/searchtimers.ecpp @@ -38,7 +38,6 @@ if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); <head> <title>VDR-Live - <$ pageTitle $></title> <& pageelems.stylesheets &> - <& tooltip.javascript var=("domTT_styleClass") value=("domTTepg") &> <& pageelems.ajax_js &> </head> <body> diff --git a/pages/setup.ecpp b/pages/setup.ecpp index ac9f2c5..2ec0e28 100644 --- a/pages/setup.ecpp +++ b/pages/setup.ecpp @@ -75,7 +75,6 @@ if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); <head> <title>VDR-Live - <$ pageTitle $></title> <& pageelems.stylesheets &> - <& tooltip.javascript var=("domTT_styleClass") value=("domTTepg") &> <& pageelems.ajax_js &> <script type="text/javascript"><!-- function initform() diff --git a/pages/timers.ecpp b/pages/timers.ecpp index 292f26f..ff637f2 100644 --- a/pages/timers.ecpp +++ b/pages/timers.ecpp @@ -47,7 +47,6 @@ using namespace vdrlive; <head> <title>VDR-Live - <$ pageTitle $></title> <& pageelems.stylesheets &> - <& tooltip.javascript var=("domTT_styleClass") value=("domTTepg") &> <& pageelems.ajax_js &> </head> <body> diff --git a/pages/tooltip.ecpp b/pages/tooltip.ecpp index bd11415..e7ddaa6 100644 --- a/pages/tooltip.ecpp +++ b/pages/tooltip.ecpp @@ -4,58 +4,23 @@ using namespace vdrlive; </%pre> -<%def javascript> -<%args> -styleClass="domTThint"; -var[]; -value[]; -</%args> - <script type="text/javascript" src="domLib.js"></script> - <script type="text/javascript" src="domTT.js"></script> - <script type="text/javascript" src="domTT_drag.js"></script> - <script type="text/javascript"> - var domTT_styleClass = "<$ styleClass $>"; -% int idx = 0; -% for (var_type::const_iterator it = var.begin(); it != var.end(); ++it, idx++) { - var <$ *it $> = "<$ (value[idx]) $>"; -% } - domTT_addPredefined('tipHint', 'trail', true, 'delay', 0, 'styleClass', 'domTThint'); - domTT_addPredefined('tipInfo', 'trail', true, 'delay', 0); - domTT_addPredefined('tipDisp', 'trail', false, 'delay', 0, 'type', 'sticky', 'caption', false, 'offsetX', -30, 'offsetY', -30, 'draggable', true); - </script> -</%def> - <%def hint> <%args> text; </%args> -<%cpp> { </%cpp> onmouseover="domTT_activate(this, event, 'predefined', 'tipHint', 'content', '<$ text $>');" <%cpp> } </%cpp> -</%def> - -<%def info> -<%args> -domId; -</%args> - onmouseover="domTT_activate(this, event, 'predefined', 'tipInfo', 'content', document.getElementById('<$ domId $>'));" +title="<$ text $>" </%def> <%def display> <%args> domId; </%args> - onclick="domTT_close(domTT_lastOpened); return makeFalse(domTT_activate(this, event, 'predefined', 'tipDisp', 'content', document.getElementById('<$ domId $>'), 'id', '<$ (domId + "_tip") $>'));" -</%def> - -<%def close> -<%args> -domId; -</%args> - <a href="#void" onclick="domTT_close('<$ (domId + "_tip") $>')"><img src="<$ LiveSetup().GetThemedLink("img", "close.png") $>" alt="" /></a> +href="epginfo.html?epgid=<$ domId $>" </%def> <%def help> <%args> text; </%args> - <img src="<$ LiveSetup().GetThemedLink("img", "help.png") $>" onmouseover="domTT_close(domTT_lastOpened); domTT_activate(this, event, 'predefined', 'tipHint', 'content', '<$ text $>');"></img> +<img src="<$ LiveSetup().GetThemedLink("img", "help.png") $>" alt="" <& hint text=(text) &>></img> </%def> diff --git a/pages/whats_on.ecpp b/pages/whats_on.ecpp index 8f052ce..d149e55 100644 --- a/pages/whats_on.ecpp +++ b/pages/whats_on.ecpp @@ -79,7 +79,6 @@ if (type == "now") { <head> <title>VDR-Live - <$ head $></title> <& pageelems.stylesheets &> - <& tooltip.javascript var=("domTT_styleClass") value=("domTTepg") &> <& pageelems.ajax_js &> <script type="text/javascript"><!-- function showtime(selection) @@ -109,7 +108,7 @@ if (type == "now") { ReadLock channelsLock( Channels ); if (channelsLock) { - int evntNr = 0; + // int evntNr = 0; for (cChannel *Channel = Channels.First(); Channel && Channel->Number() <= LiveSetup().GetLastChannel(); Channel = Channels.Next(Channel)) { if (Channel->GroupSep()) { continue; @@ -129,13 +128,23 @@ if (type == "now") { continue; } - string evntId("eventId_"); - evntId += lexical_cast<std::string, int>(++evntNr); - EpgEventPtr epgEvent(new EpgEvent(evntId, Event, Channel->Name())); - epgEvents.push_back(epgEvent); tChannelID channel_id(Channel->GetChannelID()); tEventID event = Event->EventID(); + + // string evntId("event_"); + // + // string schanid(channel_id.ToString()); + // replace(schanid.begin(), schanid.end(), '.', 'p'); + // replace(schanid.begin(), schanid.end(), '-', 'm'); + // evntId += schanid; + // evntId += '_'; + // evntId += lexical_cast<std::string>(event); + // // evntId += lexical_cast<std::string, int>(++evntNr); + // EpgEventPtr epgEvent(new EpgEvent(evntId, Event, Channel->Name())); + // // epgEvents.push_back(epgEvent); + EpgInfoPtr epgEvent = EpgEvents::CreateEpgInfo(Channel, Event); + bool truncated = false; string truncDescription = StringWordTruncate(epgEvent->LongDescr(), maximumTooltipHintLength, truncated); string longDescription = StringEscapeAndBreak(StringWordTruncate(epgEvent->LongDescr(), maximumDescriptionLength)) @@ -168,7 +177,7 @@ if (type == "now") { <div class="short withmargin"><$ (epgEvent->ShortDescr()) $></div> <div class="description withmargin"><$ truncDescription $></div> % if (truncated) { - <div class="more withmargin"<& tooltip.hint text=(longDescription) &><& tooltip.display domId=(epgEvent->Id()) &>><$ tr("more") $> ...</div> + <div class="more withmargin"><a <& tooltip.hint text=(longDescription) &><& tooltip.display domId=(epgEvent->Id()) &>><$ tr("more") $> ...</a></div> % } </div> </div> @@ -193,11 +202,11 @@ if (type == "now") { </div> </td> <td class="topaligned <? lastCurrentChanel ? "bottomrow"?>"> - <div class="more withmargin" + <div class="more withmargin"><a % if (!longDescription.empty()) { <& tooltip.hint text=(longDescription) &><& tooltip.display domId=(epgEvent->Id()) &> % } - ><span class="title"><$ (epgEvent->Title()) $></span><br /><span class="short"><$ (epgEvent->ShortDescr()) $></span></div> + ><span class="title"><$ (epgEvent->Title()) $></span><br /><span class="short"><$ (epgEvent->ShortDescr()) $></span></a></div> </td> <td class="topaligned rightcol <? lastCurrentChanel ? "bottomrow"?>"><div class="station withmargin"><a href="schedule.html?channel=<$ Channel->Number() $>" <& tooltip.hint text=(tr("View the schedule of this channel")) &>><$ (epgEvent->Caption()) $></a></div></td> </tr> @@ -210,17 +219,6 @@ if (type == "now") { </table> % } </div> - <div class="epg_data" style="display: none;"> -<%cpp> - // create hidden div for the tooltip hints. - for (vector<EpgEventPtr>::iterator i = epgEvents.begin(); i != epgEvents.end(); ++i) { - EpgEventPtr epg = *i; -</%cpp> - <& pageelems.epg_tt_box boxId=(epg->Id()) caption=(epg->Caption()) time=(epg->StartTime(tr("%I:%M %p")) + string(" - ") + epg->EndTime(tr("%I:%M %p"))) title=(epg->Title()) short_descr=(epg->ShortDescr()) long_descr=(epg->LongDescr()) elapsed=(epg->Elapsed()) &> -<%cpp> - } -</%cpp> - </div> </body> </html> <%include>page_exit.eh</%include> diff --git a/recordings.cpp b/recordings.cpp index a703f3c..5d5070a 100644 --- a/recordings.cpp +++ b/recordings.cpp @@ -21,7 +21,7 @@ namespace vdrlive { string RecordingsManager::Md5Hash(const cRecording* recording) const { - return MD5Hash(recording->FileName()); + return "recording_" + MD5Hash(recording->FileName()); /* unsigned char md5[MD5_DIGEST_LENGTH]; const char* fileName = recording->FileName(); MD5(reinterpret_cast<const unsigned char*>(fileName), strlen(fileName), md5); @@ -45,6 +45,52 @@ namespace vdrlive { return 0; } + bool RecordingsManager::IsArchived(const cRecording* recording) + { + string filename = recording->FileName(); + + string vdrFile = filename + "/001.vdr"; + if (0 == access(vdrFile.c_str(), R_OK)) + return false; + + filename += "/dvd.vdr"; + return (0 == access(filename.c_str(), R_OK)); + } + + const std::string RecordingsManager::GetArchiveId(const cRecording* recording) + { + string filename = recording->FileName(); + + filename += "/dvd.vdr"; + ifstream dvd(filename.c_str()); + + if (dvd) { + string archiveDisc; + string videoDisc; + dvd >> archiveDisc; + if ("0000" == archiveDisc) { + dvd >> videoDisc; + return videoDisc; + } + return archiveDisc; + } + return ""; + } + + const string RecordingsManager::GetArchiveDescr(const cRecording* recording) + { + string archived; + if (IsArchived(recording)) { + archived += " ["; + archived += tr("On archive DVD No."); + archived += ": "; + archived += GetArchiveId(recording); + archived += "]"; + } + return archived; + } + + RecordingsTree::RecordingsTree(RecordingsManagerPtr recMan) : m_maxLevel(0), m_root(new RecordingsItemDir()), @@ -177,7 +223,7 @@ namespace vdrlive { { } - RecordingsTree::RecordingsItemRec::RecordingsItemRec(const string& id, const string& name, cRecording* recording) : + RecordingsTree::RecordingsItemRec::RecordingsItemRec(const string& id, const string& name, const cRecording* recording) : RecordingsItem(name), m_recording(recording), m_id(id) @@ -193,68 +239,7 @@ namespace vdrlive { return m_recording->start; } - bool RecordingsTree::RecordingsItemRec::IsArchived() const - { - string filename = m_recording->FileName(); - - string vdrFile = filename + "/001.vdr"; - if (0 == access(vdrFile.c_str(), R_OK)) - return false; - - filename += "/dvd.vdr"; - return (0 == access(filename.c_str(), R_OK)); - } - const std::string RecordingsTree::RecordingsItemRec::ArchiveId() const - { - string filename = m_recording->FileName(); - - filename += "/dvd.vdr"; - ifstream dvd(filename.c_str()); - - if (dvd) { - string archiveDisc; - string videoDisc; - dvd >> archiveDisc; - if ("0000" == archiveDisc) { - dvd >> videoDisc; - return videoDisc; - } - return archiveDisc; - } - return ""; - } - - EpgEventPtr RecordingsTree::CreateEpgEvent(const RecordingsItemPtr recItem) - { - const cRecordingInfo* info = recItem->RecInfo(); - if (info) { - std::string archived; - if (recItem->IsArchived()) { - archived += " ["; - archived += tr("On archive DVD No."); - archived += ": "; - archived += recItem->ArchiveId(); - archived += "]"; - } - EpgEventPtr epgEvent( - new EpgEvent( - recItem->Id(), - recItem->Name(), - info->Title() ? info->Title() : recItem->Name(), - info->ShortText() ? info->ShortText() : "", - info->Description() ? info->Description() : "", - archived, - recItem->StartTime(), - recItem->StartTime() - ) - ); - return epgEvent; - } - else { - return EpgEventPtr(); - } - } RecordingsManagerPtr LiveRecordingsManager() { diff --git a/recordings.h b/recordings.h index 9e42ada..81b971f 100644 --- a/recordings.h +++ b/recordings.h @@ -10,8 +10,8 @@ namespace vdrlive { // Forward declations from epg_events.h - class EpgEvent; - typedef std::tr1::shared_ptr<EpgEvent> EpgEventPtr; + class EpgInfo; + typedef std::tr1::shared_ptr<EpgInfo> EpgInfoPtr; class RecordingsManager; typedef std::tr1::shared_ptr<RecordingsManager> RecordingsManagerPtr; @@ -33,6 +33,21 @@ namespace vdrlive { */ const cRecording* GetByMd5Hash(const std::string& hash) const; + /** + * Determine wether the recording has been archived on + * removable media (e.g. DVD-ROM) + */ + static bool IsArchived(const cRecording* recording); + + /** + * Provide an identification of the removable media + * (e.g. DVD-ROM Number or Name) where the recording has + * been archived. + */ + static const std::string GetArchiveId(const cRecording* recording); + + static const std::string GetArchiveDescr(const cRecording* recording); + private: RecordingsManager(); @@ -58,10 +73,8 @@ namespace vdrlive { virtual time_t StartTime() const = 0; virtual bool IsDir() const = 0; - virtual bool IsArchived() const = 0; virtual const std::string& Name() const { return m_name; } virtual const std::string Id() const = 0; - virtual const std::string ArchiveId() const = 0; virtual const cRecording* Recording() const { return 0; } virtual const cRecordingInfo* RecInfo() const { return 0; } @@ -84,9 +97,7 @@ namespace vdrlive { virtual time_t StartTime() const { return 0; } virtual bool IsDir() const { return true; } - virtual bool IsArchived() const { return false; } virtual const std::string Id() const { std::string e; return e; } - virtual const std::string ArchiveId() const { std::string e; return e; } private: int m_level; @@ -95,21 +106,19 @@ namespace vdrlive { class RecordingsItemRec : public RecordingsItem { public: - RecordingsItemRec(const std::string& id, const std::string& name, cRecording* recording); + RecordingsItemRec(const std::string& id, const std::string& name, const cRecording* recording); virtual ~RecordingsItemRec(); virtual time_t StartTime() const; virtual bool IsDir() const { return false; } - virtual bool IsArchived() const ; virtual const std::string Id() const { return m_id; } - virtual const std::string ArchiveId() const; virtual const cRecording* Recording() const { return m_recording; } virtual const cRecordingInfo* RecInfo() const { return m_recording->Info(); } private: - cRecording *m_recording; + const cRecording *m_recording; std::string m_id; }; @@ -122,8 +131,6 @@ namespace vdrlive { int MaxLevel() const { return m_maxLevel; } - static EpgEventPtr CreateEpgEvent(const RecordingsItemPtr recItem); - private: int m_maxLevel; RecordingsItemPtr m_root; diff --git a/tntconfig.cpp b/tntconfig.cpp index 1a70aea..7f412ba 100644 --- a/tntconfig.cpp +++ b/tntconfig.cpp @@ -52,6 +52,7 @@ void TntConfig::WriteConfig() file << "MapUrl ^/js([^.]*/)(.*\\.js) content@ js$1$2 text/javascript" << endl; file << "MapUrl ^/css.*/(.+) content@ css/$1 text/css" << endl; + file << "MapUrl ^/img.*/(.+\\.png) content@ css/$1 image/png" << endl; file << "MapUrl /([^/]+/.+) content@ $1" << endl; file << "MapUrl /([^.]+)(\\..+)? $1@" << endl; file << "PropertyFile " << m_propertiesPath << endl; |