From 7b003f8aaafc2d95dcf7c9dfc5cbc6288b37915c Mon Sep 17 00:00:00 2001 From: Dieter Hametner Date: Thu, 12 Jul 2007 19:10:34 +0000 Subject: - 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. --- css/styles.css | 343 +++++------ doc/ChangeLog | 21 + doc/dev-conventions.txt | 34 +- epg_events.cpp | 293 ++++++++-- epg_events.h | 176 ++++-- javascript/Makefile | 3 +- live/img/close_red.png | Bin 0 -> 587 bytes live/img/info-win-b-l.png | Bin 0 -> 844 bytes live/img/info-win-b-r.png | Bin 0 -> 466 bytes live/img/info-win-m-l.png | Bin 0 -> 180 bytes live/img/info-win-m-r.png | Bin 0 -> 176 bytes live/img/info-win-t-l.png | Bin 0 -> 1131 bytes live/img/info-win-t-r.png | Bin 0 -> 716 bytes live/img/tip-hint-bl.png | Bin 0 -> 528 bytes live/img/tip-hint-br.png | Bin 0 -> 336 bytes live/img/tip-hint-ml.png | Bin 0 -> 110 bytes live/img/tip-hint-mr.png | Bin 0 -> 112 bytes live/img/tip-hint-tl.png | Bin 0 -> 470 bytes live/img/tip-hint-tr.png | Bin 0 -> 346 bytes live/js/live/hinttips.js | 34 ++ live/js/live/infowin.js | 296 ++++++++++ live/js/mootools/mootools.v1.11.js | 1101 ++++++++++++++++++++++++++++++++++++ pages/Makefile | 3 +- pages/channels_widget.ecpp | 3 +- pages/edit_searchtimer.ecpp | 1 - pages/edit_timer.ecpp | 1 - pages/epginfo.ecpp | 114 ++++ pages/ibox.ecpp | 97 ++-- pages/login.ecpp | 1 - pages/menu.ecpp | 21 +- pages/pageelems.ecpp | 18 +- pages/recordings.ecpp | 37 +- pages/remote.ecpp | 1 - pages/schedule.ecpp | 41 +- pages/searchepg.ecpp | 1 - pages/searchresults.ecpp | 21 +- pages/searchtimers.ecpp | 1 - pages/setup.ecpp | 1 - pages/timers.ecpp | 1 - pages/tooltip.ecpp | 41 +- pages/whats_on.ecpp | 38 +- recordings.cpp | 111 ++-- recordings.h | 31 +- tntconfig.cpp | 1 + 44 files changed, 2270 insertions(+), 616 deletions(-) create mode 100644 live/img/close_red.png create mode 100644 live/img/info-win-b-l.png create mode 100644 live/img/info-win-b-r.png create mode 100644 live/img/info-win-m-l.png create mode 100644 live/img/info-win-m-r.png create mode 100644 live/img/info-win-t-l.png create mode 100644 live/img/info-win-t-r.png create mode 100644 live/img/tip-hint-bl.png create mode 100644 live/img/tip-hint-br.png create mode 100644 live/img/tip-hint-ml.png create mode 100644 live/img/tip-hint-mr.png create mode 100644 live/img/tip-hint-tl.png create mode 100644 live/img/tip-hint-tr.png create mode 100644 live/js/live/hinttips.js create mode 100644 live/js/live/infowin.js create mode 100644 pages/epginfo.ecpp 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 + + 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 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 - - link - -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 +your link text here . 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() + 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(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(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 -#include #include #include @@ -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 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 EpgEventPtr; + // ------------------------------------------------------------------------- - class EpgEvents : public std::vector { + 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 new file mode 100644 index 0000000..05e02b2 Binary files /dev/null and b/live/img/close_red.png differ diff --git a/live/img/info-win-b-l.png b/live/img/info-win-b-l.png new file mode 100644 index 0000000..5fccc68 Binary files /dev/null and b/live/img/info-win-b-l.png differ diff --git a/live/img/info-win-b-r.png b/live/img/info-win-b-r.png new file mode 100644 index 0000000..29a09e9 Binary files /dev/null and b/live/img/info-win-b-r.png differ diff --git a/live/img/info-win-m-l.png b/live/img/info-win-m-l.png new file mode 100644 index 0000000..9fb5356 Binary files /dev/null and b/live/img/info-win-m-l.png differ diff --git a/live/img/info-win-m-r.png b/live/img/info-win-m-r.png new file mode 100644 index 0000000..f097081 Binary files /dev/null and b/live/img/info-win-m-r.png differ diff --git a/live/img/info-win-t-l.png b/live/img/info-win-t-l.png new file mode 100644 index 0000000..f916543 Binary files /dev/null and b/live/img/info-win-t-l.png differ diff --git a/live/img/info-win-t-r.png b/live/img/info-win-t-r.png new file mode 100644 index 0000000..854bde2 Binary files /dev/null and b/live/img/info-win-t-r.png differ diff --git a/live/img/tip-hint-bl.png b/live/img/tip-hint-bl.png new file mode 100644 index 0000000..790a602 Binary files /dev/null and b/live/img/tip-hint-bl.png differ diff --git a/live/img/tip-hint-br.png b/live/img/tip-hint-br.png new file mode 100644 index 0000000..61eafcf Binary files /dev/null and b/live/img/tip-hint-br.png differ diff --git a/live/img/tip-hint-ml.png b/live/img/tip-hint-ml.png new file mode 100644 index 0000000..b730b57 Binary files /dev/null and b/live/img/tip-hint-ml.png differ diff --git a/live/img/tip-hint-mr.png b/live/img/tip-hint-mr.png new file mode 100644 index 0000000..bf2465c Binary files /dev/null and b/live/img/tip-hint-mr.png differ diff --git a/live/img/tip-hint-tl.png b/live/img/tip-hint-tl.png new file mode 100644 index 0000000..467ecec Binary files /dev/null and b/live/img/tip-hint-tl.png differ diff --git a/live/img/tip-hint-tr.png b/live/img/tip-hint-tr.png new file mode 100644 index 0000000..a4c9b3f Binary files /dev/null and b/live/img/tip-hint-tr.png differ 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 '-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 @@ -3016,6 +3016,325 @@ Function.extend({ }); +/* +Script: Element.Filters.js + add Filters capability to . + +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 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 . + +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: for an alternate syntax. + Returns as . + +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 . + + 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 , but returns only the first. Alternate syntax for <$E>, where filter is the Element. + Returns as . + + Arguments: + selector - string; css selector + */ + + getElement: function(selector){ + return $(this.getElements(selector, true)[0] || false); + }, + + /* + Property: getElementsBySelector + Same as , but allows for comma separated selectors, as in css. Alternate syntax for <$$>, where filter is the Element. + Returns as . + + 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. @@ -3091,6 +3410,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. @@ -3271,6 +3745,633 @@ window.extend({ }); +/* +Script: Fx.Base.js + Contains , 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 ; default is + 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 () by default. + onComplete - the function to execute after the effect has processed; nothing () 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 , , . 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 + +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 . + +Arguments: + el - the $(element) to apply the styles transition to + options - the fx options (see: ) + +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 + */ + + 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 to the Element; This a shortcut for . + + 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 , + +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 for acceptable options. + */ + + makeResizable: function(options){ + return new Drag.Base(this, $merge({modifiers: {x: 'width', y: 'height'}}, options)); + } + +}); + +/* +Script: Drag.Move.js + Contains , + +License: + MIT-style license. +*/ + +/* +Class: Drag.Move + Extends , 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 . + +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 and 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();