summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--css/styles.css74
-rw-r--r--pages/Makefile4
-rw-r--r--pages/menu.ecpp2
-rw-r--r--pages/multischedule.ecpp409
-rw-r--r--tools.cpp1
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"> &nbsp; </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"> &nbsp; </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>
+ &nbsp;
+<%cpp>
+ }
+</%cpp>
+ </td>
+<%cpp>
+ for ( int channel = 0; channel< MAX_CHANNELS ; channel++)
+ {
+ // output spacer column
+</%cpp>
+ <td class = " time spacer " > &nbsp; </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() ? "&nbsp;" : 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") $>:&nbsp;<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") $>:&nbsp;<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>
diff --git a/tools.cpp b/tools.cpp
index bda91f0..e8589d7 100644
--- a/tools.cpp
+++ b/tools.cpp
@@ -114,6 +114,7 @@ namespace vdrlive {
{
if (input.length() <= maxLen)
{
+ truncated = false;
return input;
}
truncated = true;