diff options
-rw-r--r-- | css/styles.css | 74 | ||||
-rw-r--r-- | pages/Makefile | 4 | ||||
-rw-r--r-- | pages/menu.ecpp | 2 | ||||
-rw-r--r-- | pages/multischedule.ecpp | 409 | ||||
-rw-r--r-- | tools.cpp | 1 |
5 files changed, 488 insertions, 2 deletions
diff --git a/css/styles.css b/css/styles.css index 4fd23b1..5090844 100644 --- a/css/styles.css +++ b/css/styles.css @@ -867,6 +867,80 @@ table.listing a { font-weight: bold; } +/* ################################## + # table schedule + # (this is used for the MultiSchedule) + ################################## +*/ + +table.mschedule { + padding: 0px; + margin: 0px; +} + +table.mschedule tr { + height: 12px; +} + +table.mschedule tr td.event { + background: transparent url(img/bg_line.png) bottom repeat-x; + border-bottom: 1px solid #C0C1DA; +} + +table.mschedule tr td.has_timer { + background-color: #FFE0E0; +} + +table.mschedule tr.odd td.time { + background-color: #D0D0ff; +} + +table.mschedule tr.even td.time { + background-color: #ffffff; +} + +table.mschedule tr.current_row td.time { + background-color: #ffB0B0; +} + +table.mschedule tr td.leftcol { + padding-left: 7px; + padding-right: 5px; +} + +table.mschedule div.content1 { + min-width: 10em; + max-width: 30em; +} + +table.mschedule div.tools1 { + float: right; +} + +table.mschedule div.start { + float: left; +} + +table.mschedule tr td div.title { + padding: 3px; + clear: both; +} + +table.mschedule tr td div.short { + clear: both; + overflow: hidden; +} + +table.mschedule tr td div.description { + overflow: hidden; + clear: both; +} + +table.mschedule a { + color: black; + font-weight: bold; +} + /* ############################## # Blue Background Thingy diff --git a/pages/Makefile b/pages/Makefile index 7e0ab56..c70ecb6 100644 --- a/pages/Makefile +++ b/pages/Makefile @@ -13,8 +13,8 @@ VDRDIR ?= ../../../.. ### The object files (add further files here): -OBJS = menu.o recordings.o schedule.o screenshot.o timers.o \ - whats_on.o switch_channel.o keypress.o remote.o \ +OBJS = menu.o recordings.o schedule.o multischedule.o screenshot.o \ + timers.o whats_on.o switch_channel.o keypress.o remote.o \ channels_widget.o edit_timer.o error.o pageelems.o tooltip.o \ vlc.o searchtimers.o edit_searchtimer.o searchresults.o \ searchepg.o login.o ibox.o xmlresponse.o play_recording.o \ diff --git a/pages/menu.ecpp b/pages/menu.ecpp index 1159014..5721e42 100644 --- a/pages/menu.ecpp +++ b/pages/menu.ecpp @@ -36,6 +36,7 @@ if (!component.empty()) { <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") &>><$ trVDR("Schedule") $></a> + | <a href="multischedule.html" <& menu.setactive current=("multischedule") &>><$ trVDR("MultiSchedule") $></a> | <a href="timers.html" <& menu.setactive current=("timers") &>><$ trVDR("Timers") $></a> <%cpp> if ( LiveFeatures< features::epgsearch >().Recent() ) { @@ -85,6 +86,7 @@ if (!component.empty()) { <div> <!-- inner --> <& menu.component current=("whats_on") &> <& menu.component current=("schedule") &> + <& menu.component current=("multischedule") &> <& menu.component current=("timers") &> <%cpp> if (LiveFeatures< features::epgsearch >().Recent()) { diff --git a/pages/multischedule.ecpp b/pages/multischedule.ecpp new file mode 100644 index 0000000..bf05fa6 --- /dev/null +++ b/pages/multischedule.ecpp @@ -0,0 +1,409 @@ +<%pre> +#include <list> +#include <vdr/plugin.h> +#include <vdr/channels.h> +#include <vdr/epg.h> +#include <vdr/config.h> +#include <vdr/device.h> +#include "exception.h" +#include "livefeatures.h" +#include "setup.h" +#include "tools.h" +#include "timers.h" +#include "epg_events.h" +#include "i18n.h" + +using namespace std; +using namespace vdrlive; + +struct SchedEntry { + string title; + string short_description; + string description; + string description_trunc; + string start; + string end; + string day; + string epgid; + bool truncated; + bool has_timer; + int start_row; + int row_count; +}; + + std::vector<std::string> channel_groups_names; + std::vector<std::string> times_names; + std::vector<time_t> times_start; +</%pre> +<%args> + unsigned int channel = 0; + unsigned int time_para = 0; +</%args> +<%session scope="global"> +bool logged_in(false); +</%session> +<%request scope="page"> + unsigned int channel_group=0; + unsigned int time_selected=0; +</%request> +<%include>page_init.eh</%include> +<%cpp> +if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html"); +pageTitle = trVDR("Schedule"); + + ReadLock channelsLock( Channels ); + if ( !channelsLock ) + throw HtmlError( tr("Couldn't aquire access to channels, please try again later.") ); + +#define MAX_CHANNELS 5 +#define MAX_DAYS 3 +#define MAX_HOURS 8 +#define MINUTES_PER_ROW 5 +#define CHARACTERS_PER_ROW 30 + + // build the groups of channels to display + std::vector< std::vector<int> > channel_groups_numbers; + channel_groups_names.clear(); + int cur_group_count=0; + for ( cChannel *listChannel = Channels.First(); listChannel; listChannel = Channels.Next( listChannel ) ) + { + if ( listChannel->GroupSep() || *listChannel->Name() == '\0' ) + continue; + + if ( cur_group_count==0 ) + { + // first entry in this group + channel_groups_names.push_back( std::string() ); + channel_groups_numbers.push_back( std::vector<int>( MAX_CHANNELS) ); + } + else channel_groups_names.back() += " - "; + + channel_groups_names.back() += std::string( listChannel->Name() ); + channel_groups_numbers.back()[ cur_group_count ] = listChannel->Number(); + + cur_group_count++; + if ( cur_group_count >= MAX_CHANNELS ) + // we need a new group next round + cur_group_count = 0; + } + if ( channel >= channel_groups_numbers.size() ) + channel = channel_groups_numbers.size()-1; + channel_group = channel; +{ + // build time list + times_names.clear(); + times_start.clear(); + + // calculate time of midnight (localtime) and convert back to GMT + time_t now = time(NULL); + time_t now_local = time(NULL); + struct tm tm_r; + if ( localtime_r( &now_local, &tm_r ) == 0 ) { + ostringstream builder; + builder << "cannot represent timestamp " << now_local << " as local time"; + throw runtime_error( builder.str() ); + } + tm_r.tm_hour=0; + tm_r.tm_min=0; + tm_r.tm_sec=0; + time_t midnight = mktime( &tm_r ); + + // default is now rounded to full hour + times_names.push_back( tr("Now") ); + times_start.push_back( 3600 * ( now /3600 ) ); + + int i =0; + // skip allready passed times + while ( now>midnight+MAX_HOURS*3600*i) + i++; + + for (; i<4*MAX_DAYS ; i++ ) + { + times_names.push_back(FormatDateTime( tr("%A, %x"), midnight + MAX_HOURS*3600*i) + +std::string(" ")+ FormatDateTime( tr("%I:%M %p"), midnight + MAX_HOURS*3600*i) ); + //times_names.push_back("today 0:00"); + times_start.push_back( midnight + MAX_HOURS*3600*i ); + } + if ( time_para >= times_names.size() ) + time_para = times_names.size()-1; + time_selected=time_para; +} +</%cpp> +<& pageelems.doc_type &> +<html> + <head> + <title>VDR Live - <$ pageTitle $></title> + <& pageelems.stylesheets &> + <& pageelems.ajax_js &> + </head> + <body> + <& pageelems.logo &> + <& menu active=("multischedule") component=("multischedule.channel_selection") &> + <div class="inhalt"> +<%cpp> + cSchedulesLock schedulesLock; + cSchedules const* schedules = cSchedules::Schedules( schedulesLock ); + + time_t now = time(NULL); + if ( time_para >= times_start.size() ) + time_para = times_start.size()-1; + time_t sched_start = times_start[ time_para ]; + time_t sched_end = sched_start + 60 * 60 * MAX_HOURS; + int sched_end_row = ( sched_end - sched_start ) / 60 / MINUTES_PER_ROW; + std::list<SchedEntry> table[MAX_CHANNELS]; + string channel_names[ MAX_CHANNELS]; + if ( channel >= channel_groups_numbers.size() ) + channel = channel_groups_numbers.size()-1; + //for ( int chan = 0; chan<MAX_CHANNELS; chan++) + for ( int j = 0; j<MAX_CHANNELS; j++) + { + int prev_row = -1; + + + int chan = channel_groups_numbers[ channel ][ j ]; + + cChannel* Channel = Channels.GetByNumber( chan ); + if ( ! Channel ) + continue; + if ( Channel->GroupSep() || Channel->Name() == '\0' ) + continue; + channel_names[ j ] = Channel->Name(); + + cSchedule const* Schedule = schedules->GetSchedule( Channel ); + if ( ! Schedule ) + continue; + for (const cEvent *Event = Schedule->Events()->First(); Event; + Event = Schedule->Events()->Next(Event) ) + { + if (Event->EndTime() <= sched_start ) + continue; + if (Event->StartTime() >= sched_end ) + continue; + + EpgInfoPtr epgEvent = EpgEvents::CreateEpgInfo(Channel, Event); + if ( prev_row < 0 && Event->StartTime() > sched_start + MINUTES_PER_ROW ) + { + // insert dummy event at start + table[ j ].push_back( SchedEntry() ); + SchedEntry &en=table[ j ].back(); + int event_start_row = (Event->StartTime() - sched_start) / 60 / MINUTES_PER_ROW; + en.start_row = 0; + en.row_count = event_start_row; + // no title and no start time = dummy event + en.title = ""; + en.start = ""; + prev_row = en.start_row + en.row_count; + } + table[ j ].push_back( SchedEntry() ); + SchedEntry &en=table[j].back(); + + en.title = epgEvent->Title(); + en.short_description = epgEvent->ShortDescr(); + en.description = epgEvent->LongDescr(); + en.start = epgEvent->StartTime(tr("%I:%M %p")); + en.end = epgEvent->EndTime(tr("%I:%M %p")); + en.day = epgEvent->StartTime(tr("%A, %b %d %Y")); + en.epgid = EpgEvents::EncodeDomId(Channel->GetChannelID(), Event->EventID()); + en.has_timer = LiveTimerManager().GetTimer(Event->EventID(), Channel->GetChannelID() ) != 0; + + en.start_row = prev_row > 0 ? prev_row : 0; + int end_time = Schedule->Events()->Next(Event) ? + Schedule->Events()->Next(Event)->StartTime() : + Event->EndTime(); + int next_event_start_row = (end_time - sched_start) / 60 / MINUTES_PER_ROW; + en.row_count = next_event_start_row - en.start_row; + if ( en.row_count < 1 ) + en.row_count = 1; + prev_row = en.start_row + en.row_count; + + // truncate description if too long + en.truncated=false; + en.description_trunc=StringWordTruncate( en.description, + CHARACTERS_PER_ROW*(en.row_count-2), + en.truncated ); + + + + }; + if ( table[ j ].begin() == table[ j ].end() ) + { + // no entries... create a single dummy entry + table[ j ].push_back( SchedEntry() ); + SchedEntry &en=table[ j ].back(); + en.start_row = 0; + en.row_count = sched_end_row; + // no title and no start time = dummy event + en.title = ""; + en.start = ""; + } + } +</%cpp> + <table class="mschedule" cellspacing="0" cellpadding="0"> +<%cpp> +</%cpp> + <tr class=" topaligned "> + <td > <div class="boxheader"> <div><div><$ tr("Time") $></div></div> </div></td> + <td class="time spacer"> </td> +<%cpp> + for ( int channel = 0; channel< MAX_CHANNELS ; channel++) + { +</%cpp> + <td> <div class="boxheader"> <div> <div><$ StringEscapeAndBreak(channel_names[channel]) $> </div></div> </div></td> + <td class="time spacer"> </td> +<%cpp> + } +</%cpp> + </tr> +<%cpp> + bool odd=true; + std::list<SchedEntry>::iterator cur_event[ MAX_CHANNELS ]; + for (int i=0;i<MAX_CHANNELS;i++) + cur_event[i]=table[i].begin(); + for (int row = 0 ; row < sched_end_row; row++ ) + { + int minutes= ( (sched_start + row * 60 * MINUTES_PER_ROW ) % 3600 ) / 60; + string row_class; + if ( minutes == 0 ) + { + // full hour, swap odd/even + odd = !odd; + }; + if ( (sched_start + row * 60 * MINUTES_PER_ROW ) <= now && + (sched_start + (row+1) * 60 * MINUTES_PER_ROW ) > now ) + { + row_class +=" current_row "; + } + row_class += odd ? " odd " : " even "; + +</%cpp> + <tr class=" <$ row_class $>"> + <td class=" time leftcol "> +<%cpp> + if ( minutes == 0 ) + { +</%cpp> + <$ FormatDateTime( tr("%I:%M %p"), sched_start + row * 60 * MINUTES_PER_ROW ) $> +<%cpp> + } + else + { +</%cpp> + +<%cpp> + } +</%cpp> + </td> +<%cpp> + for ( int channel = 0; channel< MAX_CHANNELS ; channel++) + { + // output spacer column +</%cpp> + <td class = " time spacer " > </td> +<%cpp> + if ( cur_event[channel] == table[channel].end() + || cur_event[channel]->start_row != row ) + // no new event in this channel, skip it + continue; + + SchedEntry &en=*cur_event[channel]; + if (en.title.empty() && en.start.empty() ) + { + // empty dummy event +</%cpp> + <td class="event topaligned leftcol rightcol" rowspan="<$ en.row_count $>"> + </td> +<%cpp> + ++cur_event[channel]; + continue; + + } + // output an event cell +</%cpp> + <td class="event topaligned leftcol rightcol <$ en.has_timer ? "has_timer" : "" $>" rowspan="<$ en.row_count $>"> + <div class=" content1 " > + <div class=" tools1 " > + <& pageelems.event_timer epgid=(en.epgid) &> +<%cpp> + if (LiveFeatures<features::epgsearch>().Recent() ) { +</%cpp> + <a href="searchresults.html?searchplain=<$ StringUrlEncode(en.title) $>"><img src="<$ LiveSetup().GetThemedLink("img", "search.png") $>" alt="" <& tooltip.hint text=(tr("Search for repeats.")) &>></img></a> +<%cpp> + } else { + </%cpp><img src="img/transparent.png" width="16" height="16"><%cpp> + } +</%cpp> + <& pageelems.imdb_info_href title=(en.title) &> + </div><div class= "start withmargin"><$ en.start $></div> + <div class="title withmargin"><a <& tooltip.hint text=(StringEscapeAndBreak(tr("Click to view details."))) &><& tooltip.display domId=en.epgid &>><$ en.title $></a></div> +<%cpp> + if ( en.row_count>2 && !en.short_description.empty() ) + { +</%cpp> + <div class="short withmargin"><$ en.short_description.empty() ? " " : en.short_description $></div> +<%cpp> + } + if ( en.row_count>3 && ! en.description_trunc.empty() ) + { +</%cpp> + <div class="description withmargin"><$en.description_trunc$>... +<%cpp> + if ( en.truncated ) + { +</%cpp> + <a <& tooltip.hint text=(StringEscapeAndBreak(tr("Click to view details."))) &><& tooltip.display domId=en.epgid &>> <$ tr("more") $></a> +<%cpp> + } +</%cpp> + </div> +<%cpp> + } +</%cpp> + </div></div> + </td> +<%cpp> + // move to next event for this channel + ++cur_event[channel]; + } +</%cpp> + </tr> +<%cpp> + } +</%cpp> + </table> + </div> + </body> +</html> +<%include>page_exit.eh</%include> + +<%def channel_selection> +<form action="multischedule.html" method="get" id="channels"> + <span> + <label for="channel"><$ tr("Channel") $>: <span class="bold"></span></label> + <select name="channel" id="channel" onchange="document.forms.channels.submit()" > +% for ( unsigned int i = 0; i < channel_groups_names.size(); ++i ) { +% + <option value="<$ i $>" +% if ( i == channel_group ) +% { + selected="selected" +% } + ><$ channel_groups_names[i] $></option> +% } + </select> + <label for="time_para"><$ tr("Time") $>: <span class="bold"></span></label> + <select name="time_para" id="time_para" onchange="document.forms.channels.submit()" > +% for ( unsigned int i = 0; i < times_names.size(); ++i ) { +% + <option value="<$ i $>" +% if ( i == time_selected ) +% { + selected="selected" +% } + ><$ times_names[i] $></option> +% } + </select> +% // <& pageelems.ajax_action_href action="switch_channel" tip=(tr("Switch to this channel.")) param=(Channel->GetChannelID()) image="zap.png" alt="" &> +% // <& pageelems.vlc_stream_channel channelId=(Channel->GetChannelID()) &> + </span> +</form> +</%def> @@ -114,6 +114,7 @@ namespace vdrlive { { if (input.length() <= maxLen) { + truncated = false; return input; } truncated = true; |