2 Copyright 2005 Brian Walton
\r
4 This file is part of VOMP.
\r
6 VOMP is free software; you can redistribute it and/or modify
\r
7 it under the terms of the GNU General Public License as published by
\r
8 the Free Software Foundation; either version 2 of the License, or
\r
9 (at your option) any later version.
\r
11 VOMP is distributed in the hope that it will be useful,
\r
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
14 GNU General Public License for more details.
\r
16 You should have received a copy of the GNU General Public License
\r
17 along with VOMP; if not, write to the Free Software
\r
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
\r
21 vepg presents a 2 dimensional electronic programme guide with channels down
\r
22 the y axis and time along the x axis.
\r
23 Programmes are layed on the x axis as alterate coloured blocks with as much
\r
24 of the programme title as will fit inside the block shown as text.
\r
25 Up and down commands step through the channels whilst left and right commands
\r
26 move through the programmes of the currently selected channel.
\r
27 When a programme is selected, it highlights in the grid and full programe details
\r
28 (start time, title and description) are displayed in an area at te top left of the screen.
\r
29 Any currently programmed timers will display in the grid and in the orogramme detail window as red
\r
30 It is possible to select a programme to be recorded by pressing the record button.
\r
31 The video stream currently being viewed is shown as quarter screen in the top right.
\r
36 VEpg::VEpg(VVideoLive* v, UINT currentChannel)
\r
38 for(UINT listIndex = 0; listIndex < 7; listIndex++)
\r
40 // initialise array of pointers to eventlist structures
\r
41 eventLista[listIndex] = NULL;
\r
45 // Create pallet on which to paint our epg view and position it in centre of screen.
\r
46 // Need to reduce size to deal with overscanning TVs.
\r
48 // create(640, 530);
\r
49 //TODO: have size for ntsc
\r
50 if (Video::getInstance()->getFormat() == Video::PAL)
\r
53 setScreenPos(60, 30);
\r
57 // what can we do about poor ntsc?
\r
59 setScreenPos(64, 51);
\r
63 Colour transparent = Colour(0, 0, 0, 0);
\r
64 setBackgroundColour(transparent);
\r
65 // initialise variables and pointers
\r
67 chanList = VDR::getInstance()->getChannelsList(VDR::VIDEO); //TODO want to be able to display video and radio together
\r
69 progTitle.setSurface(surface);
\r
70 progTitle.setSurfaceOffset(0,0);
\r
71 progTitle.setDimensions(300,(Surface::getFontHeight() + 6) * 2 + 2); //paragraph line seperation is 6 pixels
\r
72 progTitle.setBackgroundColour(Colour::TITLEBARBACKGROUND);
\r
73 progInfo.setSurface(surface);
\r
74 progInfo.setSurfaceOffset(0, progTitle.getOffsetY() + progTitle.getHeight());
\r
75 progInfo.setDimensions(300,(Surface::getFontHeight() + 6) * 8 + 2);
\r
76 chanName.setSurface(surface);
\r
77 chanName.setDimensions(510, (Surface::getFontHeight() + 4));
\r
78 chanName.setSurfaceOffset(305,230);
\r
79 chanName.setBackgroundColour(Colour(0, 0, 0, 90));
\r
80 // create area to display list of channels
\r
81 chanListbox.setSurface(surface); // add channel list
\r
82 chanListbox.setSurfaceOffset(0, progInfo.getOffsetY() + progInfo.getHeight() + Surface::getFontHeight() + 8); // position channel list
\r
83 chanListbox.setDimensions(150, (Surface::getFontHeight() + 1) * 7 + 5); //listbox line seperation is 1 pixel
\r
84 // populate channel list
\r
87 for (UINT i = 0; i < chanList->size(); i++)
\r
89 chan = (*chanList)[i];
\r
90 if (i == currentChannel)
\r
92 chan->index = chanListbox.addOption(chan->name, first);
\r
95 listTop = chanListbox.getTopOption();
\r
96 chanListbox.draw(); // doing this to allow chanListbox.getBottomOption() in updateEventList() to work
\r
97 chanName.setText((*chanList)[chanListbox.getCurrentOption()]->name);
\r
98 time(<ime); // set ltime to now
\r
99 ltime = prevHour(<ime); // set ltime to previous hour TODO make this half hour?
\r
100 time(&selTime); // set selTime to now
\r
101 updateEventList(); // get list of programmes
\r
106 for(int listIndex = 0; listIndex < 7; listIndex++)
\r
108 if (eventLista[listIndex])
\r
110 (eventLista)[listIndex]->clear();
\r
111 delete eventLista[listIndex];
\r
114 // delete [] eventLista;
\r
116 // destroy dynamically allocated memory
\r
119 void VEpg::setInfo(Event* event)
\r
122 struct tm* btime; // to hold programme start and end time
\r
123 char* timeString = new char[9]; // to hold programme start and end time
\r
124 int length = strlen(event->title); // calculate length of programme title string
\r
125 char* title = new char[length + 15]; // create string to hold start time, end time and programme title
\r
126 btime = localtime((time_t*)&event->time); //get programme start time
\r
127 strftime(timeString, 9, "%0H:%0M - ", btime); // and format it as hh:mm -
\r
128 strcpy(title, timeString); // put it in our buffer
\r
129 t = event->time + event->duration; //get programme end time
\r
130 btime = localtime(&t);
\r
131 strftime(timeString, 7, "%0H:%0M ", btime); // and format it as hh:mm -
\r
132 strcat(title, timeString); // put it in our buffer
\r
133 strcat(title, event->title); // then add the programme title
\r
134 progTitle.setText(title); // sput this sring in our text box
\r
135 length = strlen(event->description);
\r
136 char* info = new char[length + 1]; // create programme detail string
\r
137 strcpy(info, event->description);
\r
138 progInfo.setText(info); // show programme detail string
\r
139 // destroy dynamically allocated memory
\r
147 View::draw(); // draw pallet
\r
148 chanListbox.draw();
\r
150 chanName.draw(); // TODO this should be dealt with by vvideolive
\r
154 // Display the status and key stuff at the bottom
\r
155 int keyx = chanListbox.getOffsetX();
\r
156 int keyy = chanListbox.getOffsetY() + chanListbox.getHeight() + 2;
\r
157 surface->fillblt(keyx, keyy, 610, Surface::getFontHeight() * 2 + 14, surface->rgba(100, 100, 100, 255));
\r
160 w.setSurface(surface);
\r
162 w.nextSymbol = WSymbol::LEFTARROW;
\r
163 w.setSurfaceOffset(keyx + 1, keyy + 20);
\r
166 w.nextSymbol = WSymbol::UP;
\r
167 w.setSurfaceOffset(keyx + 26, keyy + 3);
\r
170 w.nextSymbol = WSymbol::DOWN;
\r
171 w.setSurfaceOffset(keyx + 26, keyy + 36);
\r
174 w.nextSymbol = WSymbol::RIGHTARROW;
\r
175 w.setSurfaceOffset(keyx + 50, keyy + 20);
\r
178 drawText(tr("OK"), keyx + 18, keyy + 20, Colour::LIGHTTEXT);
\r
180 surface->fillblt(keyx + 72, keyy + 4, 104, Surface::getFontHeight() + 2, surface->rgba(200, 0, 0, 255));
\r
181 drawText(tr("Page up"), keyx + 74, keyy + 5, Colour::LIGHTTEXT);
\r
183 surface->fillblt(keyx + 72, keyy + Surface::getFontHeight() + 8, 104, Surface::getFontHeight() + 2, surface->rgba(0, 200, 0, 255));
\r
184 drawText(tr("Page down"), keyx + 74, keyy + Surface::getFontHeight() + 9, Colour::LIGHTTEXT);
\r
186 surface->fillblt(keyx + 180, keyy + 4, 104, Surface::getFontHeight() + 2, surface->rgba(200, 200, 0, 255));
\r
187 drawText(tr("-24 hours"), keyx + 182, keyy + 5, Colour::LIGHTTEXT);
\r
189 surface->fillblt(keyx + 180, keyy + Surface::getFontHeight() + 8, 104, Surface::getFontHeight() + 2, surface->rgba( 0, 0, 200, 255));
\r
190 drawText(tr("+24 hours"), keyx + 182, keyy + Surface::getFontHeight() + 9, Colour::LIGHTTEXT);
\r
192 surface->fillblt(keyx + 290, keyy + 4, 180, Surface::getFontHeight() + 2, surface->rgba( 180, 180, 180, 255));
\r
193 drawText(tr("Guide / Back: Close"), keyx + 292 , keyy + 5, Colour::LIGHTTEXT);
\r
195 surface->fillblt(keyx + 290, keyy + Surface::getFontHeight() + 8, 180, Surface::getFontHeight() + 2, surface->rgba( 180, 180, 180, 255));
\r
196 Colour red = Colour(130, 0, 0);
\r
197 drawText(tr("Rec: Set timer"), keyx + 292, keyy + Surface::getFontHeight() + 9, red);
\r
199 surface->fillblt(keyx + 474, keyy + 4, 128, Surface::getFontHeight() + 2, surface->rgba( 180, 180, 180, 255));
\r
200 w.nextSymbol = WSymbol::PLAY;
\r
201 w.setSurfaceOffset(keyx + 476, keyy + 5);
\r
203 drawText(tr("Sel channel"), keyx + 496, keyy + 5, Colour::LIGHTTEXT);
\r
205 surface->fillblt(keyx + 474, keyy + Surface::getFontHeight() + 8, 128, Surface::getFontHeight() + 2, surface->rgba( 180, 180, 180, 255));
\r
206 drawText(tr("Go: Preview"), keyx + 476, keyy + Surface::getFontHeight() + 9, Colour::LIGHTTEXT);
\r
209 int VEpg::handleCommand(int command)
\r
213 case Remote::DF_UP:
\r
215 { // cursor up the channel list
\r
221 case Remote::DF_DOWN:
\r
223 { // cursor down the channel list
\r
224 chanListbox.down();
\r
229 case Remote::DF_LEFT:
\r
231 { // cursor left through time
\r
232 selTime = thisEvent.time - 1;
\r
237 case Remote::DF_RIGHT:
\r
238 case Remote::RIGHT:
\r
240 // cursor right through time
\r
241 selTime = thisEvent.time + thisEvent.duration;
\r
248 // cursor up one page
\r
249 chanListbox.pageUp();
\r
254 case Remote::GREEN:
\r
256 // cursor down one page
\r
257 chanListbox.pageDown();
\r
264 // step forward 24 hours
\r
265 selTime += 24 * 60 * 60;
\r
270 case Remote::YELLOW:
\r
272 // step forward 24 hours
\r
273 selTime -= 24 * 60 * 60;
\r
278 case Remote::RECORD:
\r
286 { // select programme and display menu TODO currently just changes to selected channel
\r
287 Message* m = new Message();
\r
290 m->message = Message::CHANNEL_CHANGE;
\r
291 m->parameter = (*chanList)[chanListbox.getCurrentOption()]->number;
\r
292 ViewMan::getInstance()->postMessage(m);
\r
293 if(command == Remote::GO)
\r
295 // GO just changes channel in preview, PLAY changes channel and returns to normal TV
\r
298 case Remote::GUIDE:
\r
300 // return to normal TV mode
\r
301 videoLive->setEpgMode(FALSE);
\r
304 case Remote::CHANNELUP:
\r
306 // change up channel on live TV
\r
307 Message* m = new Message();
\r
310 m->message = Message::CHANNEL_UP;
\r
311 ViewMan::getInstance()->postMessage(m);
\r
314 case Remote::CHANNELDOWN:
\r
315 { // change down channel on live TV
\r
316 Message* m = new Message();
\r
319 m->message = Message::CHANNEL_DOWN;
\r
320 ViewMan::getInstance()->postMessage(m);
\r
324 // stop command getting to any more views
\r
328 void VEpg::drawgrid() // redraws grid and select programme
\r
330 // draw the grid of programmes
\r
331 char timeString[20];
\r
333 time(&t); // set t = now
\r
335 selTime = t; // don't allow cursor in the past
\r
336 if(listTop != chanListbox.getTopOption())
\r
338 // chanListbox has scrolled TODO speed up by changing only rows that have changed
\r
339 listTop = chanListbox.getTopOption();
\r
342 if ((selTime >= ltime + WINDOW_WIDTH * 60) || (selTime <= ltime))
\r
344 // we have cursored back before left time of window
\r
345 //TODO check that this and above don't happen together
\r
346 ltime = prevHour(&selTime);
\r
352 tms = localtime(&t);
\r
353 strftime(timeString, 19, "%a %e %b", tms);
\r
354 int timey = chanListbox.getOffsetY() - Surface::getFontHeight() - 3;
\r
356 drawTextRJ(timeString, timex - 10, timey, Colour::LIGHTTEXT); // print date
\r
357 strftime(timeString, 19, "%H:%M", tms);
\r
358 drawText(timeString, timex, timey, Colour::LIGHTTEXT); // print left time
\r
359 surface->fillblt(155, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 255, 255, 255));
\r
361 tms = localtime(&t);
\r
362 strftime(timeString, 19, "%H:%M", tms);
\r
363 drawText(timeString, timex + 180, timey, Colour::LIGHTTEXT); // print middle time
\r
364 surface->fillblt(335, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 255, 255, 255));
\r
366 tms = localtime(&t);
\r
367 strftime(timeString, 19, "%H:%M", tms);
\r
368 drawText(timeString, timex + 360, timey, Colour::LIGHTTEXT); // print right time
\r
369 surface->fillblt(515, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 255, 255, 255));
\r
370 // pointer to selTime
\r
371 surface->fillblt(155 + (selTime - ltime) / 20, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 50, 50, 255));
\r
373 // TODO should the above two comditional statements be combined to avoid calling updateEventList() twice?
\r
375 Event noevent; // an event to use if there are gaps in the epg
\r
376 thisEvent.setdescription(tr("There are no programme details available for this period"));
\r
377 thisEvent.duration = WINDOW_WIDTH * 60;
\r
378 thisEvent.time = ltime;
\r
379 thisEvent.settitle(tr("No programme details"));
\r
381 bool swapColour = FALSE; // alternate cell colour
\r
382 bool currentRow = FALSE;
\r
383 int y = chanListbox.getOffsetY() + 5; // vertical position of cell
\r
384 Colour bg, fg; // background colour of cells in grid
\r
385 // for each displayed channel, find programmes that fall in 2.5 hour time window
\r
386 for(int listIndex = 0; listIndex < 7; listIndex++)
\r
388 if (listTop + listIndex >= chanListbox.getBottomOption())
\r
389 continue; // ensure nothing populates grid below last channel
\r
390 currentRow = (listTop + listIndex == chanListbox.getCurrentOption());
\r
391 noevent.time = ltime;
\r
392 noevent.duration = WINDOW_WIDTH * 60;
\r
393 noevent.settitle("");
\r
394 paintCell(&noevent, y, Colour::NOPROGRAMME, Colour::LIGHTTEXT); // fill row with no programme colour to be painted ove with valid programmes
\r
397 thisEvent.setdescription(tr("There are no programme details available for this period"));
\r
398 thisEvent.duration = WINDOW_WIDTH * 60;
\r
399 thisEvent.time = ltime;
\r
400 thisEvent.settitle(tr("No programme details"));
\r
403 if (eventLista[listIndex])
\r
405 sort(eventLista[listIndex]->begin(), eventLista[listIndex]->end(), EventSorter());
\r
406 for(e = 0; e < (eventLista[listIndex])->size(); e++) // step through events for this channel
\r
408 fg = Colour::LIGHTTEXT;
\r
409 event = (*eventLista[listIndex])[e];
\r
412 UINT end = event->time + event->duration; // programme end time
\r
413 if(event->time >= UINT(ltime) + (WINDOW_WIDTH * 60)) // programme starts after RHS of window
\r
414 continue; // that's enough of this channel's events
\r
415 if(end <= UINT(ltime)) // programme ends before LHS of window
\r
416 continue; // this event is before the window - let's try the next event
\r
417 // this event is one we are interested in
\r
418 bg = (swapColour)?Colour::PROGRAMMEA:Colour::PROGRAMMEB; // alternate cell colour
\r
419 swapColour = !swapColour; // it wil be the other colour next time
\r
420 if(event->time <= UINT(selTime) && end > UINT(selTime) && currentRow)
\r
422 // this is the selected programme
\r
423 thisEvent.setdescription(event->description);
\r
424 thisEvent.duration = event->duration;
\r
425 thisEvent.time = event->time;
\r
426 thisEvent.settitle(event->title);
\r
427 thisEvent.id = event->id;
\r
428 if(thisEvent.id == 0)
\r
430 bg = Colour::SELECTHIGHLIGHT; // highlight cell
\r
431 fg = Colour::DARKTEXT;
\r
435 if (currentRow && thisEvent.id == 0)
\r
437 if (end <= UINT(selTime) && end > UINT(thisEvent.time))
\r
438 thisEvent.time = end;
\r
439 if (event->time > UINT(selTime) && event->time < thisEvent.time + thisEvent.duration)
\r
440 thisEvent.duration = event->time - thisEvent.time;
\r
443 paintCell(event, y, bg, fg);
\r
449 // no event list for this channel. Already painted noevent colour so just highlight if selected
\r
452 bg = Colour::SELECTHIGHLIGHT; // highlight cell
\r
453 fg = Colour::DARKTEXT;
\r
454 paintCell(&thisEvent, y, bg, fg);
\r
458 bg = Colour::NOPROGRAMME;
\r
459 fg = Colour::LIGHTTEXT;
\r
460 noevent.settitle(tr("No programme details"));
\r
461 paintCell(&noevent, y, bg, fg);
\r
464 y += Surface::getFontHeight() + 1;
\r
466 setInfo(&thisEvent);
\r
469 void VEpg::updateEventList()
\r
472 for(UINT listIndex = 0; listIndex < 7; listIndex++)
\r
474 if (eventLista[listIndex])
\r
476 if ((eventLista[listIndex])->empty())
\r
477 (eventLista)[listIndex]->clear();
\r
479 if(listTop + listIndex >= UINT(chanListbox.getBottomOption()))
\r
481 chan = (*chanList)[listTop + listIndex];
\r
482 eventLista[listIndex] = VDR::getInstance()->getChannelSchedule(chan->number, ltime - 1, WINDOW_WIDTH * 60 + 2); // ltime - 1 to get prog before window (allows cursor left past ltime). + 2 to get prog after window
\r
486 void VEpg::setCurrentChannel(char* chname)
\r
488 chanName.setText(chname);
\r
493 void VEpg::paintCell(Event* event, int yOffset, Colour bg, Colour fg)
\r
497 h = Surface::getFontHeight(); // TODO if want border around text, need to increae this and wselectlist line height
\r
498 UINT end = event->time + event->duration; // programme end time
\r
499 if(event->time <= UINT(ltime) && end > UINT(ltime)) // spans start of displayed window
\r
501 x = 155; // LHS of window
\r
502 if (end > (UINT(ltime) + (WINDOW_WIDTH * 60)))
\r
503 w = WINDOW_WIDTH * MINUTE_SCALE; // spans full 2 hour window
\r
505 w = MINUTE_SCALE * (event->time + event->duration - ltime ) / 60; // get width of remaining programme
\r
507 if((event->time >= UINT(ltime)) && (event->time <= UINT(ltime) + (WINDOW_WIDTH * 60))) // starts within window
\r
509 x = 155 + (MINUTE_SCALE * (event->time - ltime) / 60);
\r
510 w = MINUTE_SCALE * event->duration / 60;
\r
511 //if (w > 155 + MINUTE_SCALE * WINDOW_WIDTH -x)
\r
512 // w = w + x - 155 - MINUTE_SCALE * WINDOW_WIDTH; // ends outside window
\r
514 if (w > 155 + WINDOW_WIDTH * MINUTE_SCALE - x)
\r
515 w = 155 + WINDOW_WIDTH * MINUTE_SCALE -x; // limit cells to RHS of window
\r
516 surface->fillblt(x, y, w, h, surface->rgba(bg.red, bg.green, bg.blue, bg.alpha));
\r
517 char* tt = new char[strlen(event->title) + 1];
\r
518 strcpy (tt, event->title);
\r
520 for (UINT textPos = 0; textPos < strlen(tt); textPos++)
\r
522 int thisCharWidth = surface->getCharWidth(tt[textPos]);
\r
523 if (textWidth + thisCharWidth > w) // text will not fit in cell
\r
525 textWidth = textPos;
\r
528 textWidth += thisCharWidth;
\r
530 char* tT = new char[textWidth];
\r
533 strncpy(tT, tt, textWidth - 1);
\r
534 tT[textWidth - 1] = '\0';
\r
535 surface->drawText(tT, x+2, y, fg.red, fg.green, fg.blue);
\r
541 time_t VEpg::prevHour(time_t* t)
\r
544 tms = localtime(t);
\r
547 return mktime(tms);
\r