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::instance = NULL;
38 VEpg::VEpg(VVideoLive* v, UINT currentChannel)
\r
42 // PAL / NTSC sizes -----------------------
46 int summaryLines, summaryLowerPadding;
48 //UINT gridRows; // is a class member
50 if (Video::getInstance()->getFormat() == Video::PAL)
57 summaryLowerPadding = 12;
68 summaryLowerPadding = 26;
73 // initialise variables and pointers
74 viewman = ViewMan::getInstance();
\r
77 chanList = VDR::getInstance()->getChannelsList(VDR::VIDEO); //TODO want to be able to display video and radio together
80 for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
\r
82 // initialise array of pointers to eventlist structures
\r
83 eventLista[listIndex] = NULL;
\r
86 // Create pallet on which to paint our epg view and position it in centre of screen.
\r
87 // Need to reduce size to deal with overscanning TVs.
\r
89 create(xsize, ysize);
\r
90 setScreenPos(xpos, ypos);
\r
93 Colour transparent = Colour(0, 0, 0, 0);
\r
94 setBackgroundColour(transparent);
\r
96 progTitle.setSurface(surface);
\r
97 progTitle.setSurfaceOffset(0,0);
\r
98 progTitle.setDimensions(300,(Surface::getFontHeight() + 4) * 2 + 16); //paragraph line seperation is 4 pixels
\r
99 progTitle.setBackgroundColour(Colour::TITLEBARBACKGROUND);
\r
100 progTitle.setTextPos(5, 16);
103 progInfo.setSurface(surface);
\r
104 progInfo.setSurfaceOffset(0, progTitle.getOffsetY() + progTitle.getHeight());
\r
105 progInfo.setDimensions(300,((Surface::getFontHeight() + 4) * summaryLines) + summaryLowerPadding);
\r
108 chanName.setSurface(surface);
\r
109 chanName.setDimensions(510, (Surface::getFontHeight() + 4));
\r
110 chanName.setSurfaceOffset(305, chanNameYpos);
\r
111 chanName.setBackgroundColour(Colour(0, 0, 0, 90));
\r
113 // create area to display list of channels
\r
114 chanListbox.setSurface(surface); // add channel list
\r
115 chanListbox.setSurfaceOffset(0, progInfo.getOffsetY() + progInfo.getHeight() + Surface::getFontHeight() + 8); // position channel list
\r
116 chanListbox.setDimensions(150, ((Surface::getFontHeight() + 2) * gridRows) + 5); //listbox line seperation is 2 pixels
\r
117 chanListbox.setGap(2);
120 // populate channel list
\r
123 for (UINT i = 0; i < chanList->size(); i++)
\r
125 chan = (*chanList)[i];
\r
126 if (i == currentChannel)
\r
128 chan->index = chanListbox.addOption(chan->name, first);
\r
131 listTop = chanListbox.getTopOption();
\r
132 chanListbox.draw(); // doing this to allow chanListbox.getBottomOption() in updateEventList() to work
\r
133 chanName.setText((*chanList)[chanListbox.getCurrentOption()]->name);
\r
134 time(<ime); // set ltime to now
\r
135 ltime = prevHour(<ime); // set ltime to previous hour TODO make this half hour?
\r
136 time(&selTime); // set selTime to now
\r
137 updateEventList(); // get list of programmes
\r
144 for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
\r
146 if (eventLista[listIndex])
\r
148 (eventLista)[listIndex]->clear();
\r
149 delete eventLista[listIndex];
\r
152 // delete [] eventLista;
\r
154 // destroy dynamically allocated memory
\r
157 VEpg* VEpg::getInstance()
162 void VEpg::setInfo(Event* event)
\r
165 struct tm* btime; // to hold programme start and end time
\r
166 char* timeString = new char[9]; // to hold programme start and end time
\r
167 int length = strlen(event->title); // calculate length of programme title string
\r
168 char* title = new char[length + 15]; // create string to hold start time, end time and programme title
\r
169 btime = localtime((time_t*)&event->time); //get programme start time
\r
170 strftime(timeString, 9, "%0H:%0M - ", btime); // and format it as hh:mm -
\r
171 strcpy(title, timeString); // put it in our buffer
\r
172 t = event->time + event->duration; //get programme end time
\r
173 btime = localtime(&t);
\r
174 strftime(timeString, 7, "%0H:%0M ", btime); // and format it as hh:mm -
\r
175 strcat(title, timeString); // put it in our buffer
\r
176 strcat(title, event->title); // then add the programme title
\r
177 progTitle.setText(title); // sput this sring in our text box
\r
178 length = strlen(event->description);
\r
179 char* info = new char[length + 1]; // create programme detail string
\r
180 strcpy(info, event->description);
\r
181 progInfo.setText(info); // show programme detail string
\r
182 // destroy dynamically allocated memory
\r
190 View::draw(); // draw pallet
\r
192 // Moved all the dynamic data drawing to a seperate function
\r
194 // Display the status and key stuff at the bottom
\r
195 int keyx = chanListbox.getOffsetX();
\r
196 int keyy = chanListbox.getOffsetY() + chanListbox.getHeight() + 2;
\r
197 surface->fillblt(keyx, keyy, 605, Surface::getFontHeight() * 2 + 14, surface->rgba(100, 100, 100, 255));
\r
200 w.setSurface(surface);
\r
202 w.nextSymbol = WSymbol::LEFTARROW;
\r
203 w.setSurfaceOffset(keyx + 1, keyy + 20);
\r
206 w.nextSymbol = WSymbol::UP;
\r
207 w.setSurfaceOffset(keyx + 26, keyy + 3);
\r
210 w.nextSymbol = WSymbol::DOWN;
\r
211 w.setSurfaceOffset(keyx + 26, keyy + 36);
\r
214 w.nextSymbol = WSymbol::RIGHTARROW;
\r
215 w.setSurfaceOffset(keyx + 50, keyy + 20);
\r
218 drawText(tr("OK"), keyx + 18, keyy + 20, Colour::LIGHTTEXT);
\r
220 surface->fillblt(keyx + 72, keyy + 4, 104, Surface::getFontHeight() + 2, surface->rgba(200, 0, 0, 255));
\r
221 drawText(tr("Page up"), keyx + 74, keyy + 5, Colour::LIGHTTEXT);
\r
223 surface->fillblt(keyx + 72, keyy + Surface::getFontHeight() + 8, 104, Surface::getFontHeight() + 2, surface->rgba(0, 200, 0, 255));
\r
224 drawText(tr("Page down"), keyx + 74, keyy + Surface::getFontHeight() + 9, Colour::LIGHTTEXT);
\r
226 surface->fillblt(keyx + 180, keyy + 4, 104, Surface::getFontHeight() + 2, surface->rgba(200, 200, 0, 255));
\r
227 drawText(tr("-24 hours"), keyx + 182, keyy + 5, Colour::LIGHTTEXT);
\r
229 surface->fillblt(keyx + 180, keyy + Surface::getFontHeight() + 8, 104, Surface::getFontHeight() + 2, surface->rgba( 0, 0, 200, 255));
\r
230 drawText(tr("+24 hours"), keyx + 182, keyy + Surface::getFontHeight() + 9, Colour::LIGHTTEXT);
\r
232 surface->fillblt(keyx + 290, keyy + 4, 180, Surface::getFontHeight() + 2, surface->rgba( 180, 180, 180, 255));
\r
233 drawText(tr("Guide / Back: Close"), keyx + 292 , keyy + 5, Colour::LIGHTTEXT);
\r
235 surface->fillblt(keyx + 290, keyy + Surface::getFontHeight() + 8, 180, Surface::getFontHeight() + 2, surface->rgba( 180, 180, 180, 255));
\r
236 // Colour red = Colour(130, 0, 0);
\r
237 // drawText(tr("Rec: Set timer"), keyx + 292, keyy + Surface::getFontHeight() + 9, red);
\r
239 surface->fillblt(keyx + 474, keyy + 4, 128, Surface::getFontHeight() + 2, surface->rgba( 180, 180, 180, 255));
\r
240 w.nextSymbol = WSymbol::PLAY;
\r
241 w.setSurfaceOffset(keyx + 476, keyy + 5);
\r
243 drawText(tr("Sel channel"), keyx + 496, keyy + 5, Colour::LIGHTTEXT);
\r
245 surface->fillblt(keyx + 474, keyy + Surface::getFontHeight() + 8, 128, Surface::getFontHeight() + 2, surface->rgba( 180, 180, 180, 255));
\r
246 drawText(tr("Go: Preview"), keyx + 476, keyy + Surface::getFontHeight() + 9, Colour::LIGHTTEXT);
\r
248 // Draw all the dynamic data
\r
252 void VEpg::drawData()
\r
254 // Not doing View::draw() every time causes
\r
255 // things not to be cleared off the surface properly
\r
256 // So, blank out the data area first
\r
259 chanListbox.getOffsetX(),
\r
260 chanListbox.getOffsetY() - Surface::getFontHeight() - 3,
\r
261 155 + WINDOW_WIDTH * MINUTE_SCALE,
\r
262 chanListbox.getHeight() + Surface::getFontHeight() + 3,
\r
265 chanListbox.draw();
\r
267 chanName.draw(); // TODO this should be dealt with by vvideolive
\r
273 int VEpg::handleCommand(int command)
\r
277 case Remote::DF_UP:
\r
279 { // cursor up the channel list
\r
282 viewman->updateView(this);
\r
285 case Remote::DF_DOWN:
\r
287 { // cursor down the channel list
\r
288 chanListbox.down();
\r
290 viewman->updateView(this);
\r
293 case Remote::DF_LEFT:
\r
295 { // cursor left through time
\r
296 selTime = thisEvent.time - 1;
\r
298 viewman->updateView(this);
\r
301 case Remote::DF_RIGHT:
\r
302 case Remote::RIGHT:
\r
304 // cursor right through time
\r
305 selTime = thisEvent.time + thisEvent.duration;
\r
307 viewman->updateView(this);
\r
312 // cursor up one page
\r
313 chanListbox.pageUp();
\r
315 viewman->updateView(this);
\r
318 case Remote::GREEN:
\r
320 // cursor down one page
\r
321 chanListbox.pageDown();
\r
323 viewman->updateView(this);
\r
328 // step forward 24 hours
\r
329 selTime += 24 * 60 * 60;
\r
331 viewman->updateView(this);
\r
334 case Remote::YELLOW:
\r
336 // step forward 24 hours
\r
337 selTime -= 24 * 60 * 60;
\r
339 viewman->updateView(this);
\r
342 case Remote::RECORD:
\r
350 { // select programme and display menu TODO currently just changes to selected channel
\r
351 Message* m = new Message();
\r
354 m->message = Message::CHANNEL_CHANGE;
\r
355 m->parameter = (*chanList)[chanListbox.getCurrentOption()]->number;
\r
356 ViewMan::getInstance()->postMessage(m);
\r
357 if(command == Remote::GO)
\r
359 // GO just changes channel in preview, PLAY changes channel and returns to normal TV
\r
362 case Remote::GUIDE:
\r
364 // return to normal TV mode
\r
365 if (videoLive) videoLive->resetPictureSize(); // ptr check done in case being tested from videorec
\r
368 case Remote::CHANNELUP:
\r
370 // change up channel on live TV
\r
371 Message* m = new Message();
\r
374 m->message = Message::CHANNEL_UP;
\r
375 ViewMan::getInstance()->postMessage(m);
\r
378 case Remote::CHANNELDOWN:
\r
379 { // change down channel on live TV
\r
380 Message* m = new Message();
\r
383 m->message = Message::CHANNEL_DOWN;
\r
384 ViewMan::getInstance()->postMessage(m);
\r
388 // stop command getting to any more views
\r
392 void VEpg::drawgrid() // redraws grid and select programme
\r
394 // draw the grid of programmes
\r
395 char timeString[20];
\r
397 time(&t); // set t = now
\r
399 selTime = t; // don't allow cursor in the past
\r
400 if(listTop != chanListbox.getTopOption())
\r
402 // chanListbox has scrolled TODO speed up by changing only rows that have changed
\r
403 listTop = chanListbox.getTopOption();
\r
406 if ((selTime >= ltime + WINDOW_WIDTH * 60) || (selTime <= ltime))
\r
408 // we have cursored back before left time of window
\r
409 //TODO check that this and above don't happen together
\r
410 ltime = prevHour(&selTime);
\r
416 tms = localtime(&t);
\r
417 strftime(timeString, 19, "%a %e %b", tms);
\r
418 int timey = chanListbox.getOffsetY() - Surface::getFontHeight() - 3;
\r
420 drawTextRJ(timeString, timex - 10, timey, Colour::LIGHTTEXT); // print date
\r
421 strftime(timeString, 19, "%H:%M", tms);
\r
422 drawText(timeString, timex, timey, Colour::LIGHTTEXT); // print left time
\r
423 surface->fillblt(155, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 255, 255, 255));
\r
425 tms = localtime(&t);
\r
426 strftime(timeString, 19, "%H:%M", tms);
\r
427 drawText(timeString, timex + 180, timey, Colour::LIGHTTEXT); // print middle time
\r
428 surface->fillblt(335, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 255, 255, 255));
\r
430 tms = localtime(&t);
\r
431 strftime(timeString, 19, "%H:%M", tms);
\r
432 drawText(timeString, timex + 360, timey, Colour::LIGHTTEXT); // print right time
\r
433 surface->fillblt(515, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 255, 255, 255));
\r
434 // pointer to selTime
\r
435 surface->fillblt(155 + (selTime - ltime) / 20, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 50, 50, 255));
\r
437 // TODO should the above two comditional statements be combined to avoid calling updateEventList() twice?
\r
439 Event noevent; // an event to use if there are gaps in the epg
\r
440 thisEvent.setdescription(tr("There are no programme details available for this period"));
\r
441 thisEvent.duration = WINDOW_WIDTH * 60;
\r
442 thisEvent.time = ltime;
\r
443 thisEvent.settitle(tr("No programme details"));
\r
445 bool swapColour = FALSE; // alternate cell colour
\r
446 bool currentRow = FALSE;
\r
447 int y = chanListbox.getOffsetY() + 5; // vertical position of cell
\r
448 Colour bg, fg; // background colour of cells in grid
\r
449 // for each displayed channel, find programmes that fall in 2.5 hour time window
\r
450 for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
\r
452 if (listTop + (int)listIndex >= chanListbox.getBottomOption())
\r
453 continue; // ensure nothing populates grid below last channel
\r
454 currentRow = (listTop + (int)listIndex == chanListbox.getCurrentOption());
\r
455 noevent.time = ltime;
\r
456 noevent.duration = WINDOW_WIDTH * 60;
\r
457 noevent.settitle("");
\r
458 paintCell(&noevent, y, Colour::NOPROGRAMME, Colour::LIGHTTEXT); // fill row with no programme colour to be painted ove with valid programmes
\r
461 thisEvent.setdescription(tr("There are no programme details available for this period"));
\r
462 thisEvent.duration = WINDOW_WIDTH * 60;
\r
463 thisEvent.time = ltime;
\r
464 thisEvent.settitle(tr("No programme details"));
\r
467 if (eventLista[listIndex])
\r
469 sort(eventLista[listIndex]->begin(), eventLista[listIndex]->end(), EventSorter());
\r
470 for(e = 0; e < (eventLista[listIndex])->size(); e++) // step through events for this channel
\r
472 fg = Colour::LIGHTTEXT;
\r
473 event = (*eventLista[listIndex])[e];
\r
476 UINT end = event->time + event->duration; // programme end time
\r
477 if(event->time >= UINT(ltime) + (WINDOW_WIDTH * 60)) // programme starts after RHS of window
\r
478 continue; // that's enough of this channel's events
\r
479 if(end <= UINT(ltime)) // programme ends before LHS of window
\r
480 continue; // this event is before the window - let's try the next event
\r
481 // this event is one we are interested in
\r
482 bg = (swapColour)?Colour::PROGRAMMEA:Colour::PROGRAMMEB; // alternate cell colour
\r
483 swapColour = !swapColour; // it wil be the other colour next time
\r
484 if(event->time <= UINT(selTime) && end > UINT(selTime) && currentRow)
\r
486 // this is the selected programme
\r
487 thisEvent.setdescription(event->description);
\r
488 thisEvent.duration = event->duration;
\r
489 thisEvent.time = event->time;
\r
490 thisEvent.settitle(event->title);
\r
491 thisEvent.id = event->id;
\r
492 if(thisEvent.id == 0)
\r
494 bg = Colour::SELECTHIGHLIGHT; // highlight cell
\r
495 fg = Colour::DARKTEXT;
\r
499 if (currentRow && thisEvent.id == 0)
\r
501 if (end <= UINT(selTime) && end > UINT(thisEvent.time))
\r
502 thisEvent.time = end;
\r
503 if (event->time > UINT(selTime) && event->time < thisEvent.time + thisEvent.duration)
\r
504 thisEvent.duration = event->time - thisEvent.time;
\r
507 paintCell(event, y, bg, fg);
\r
513 // no event list for this channel. Already painted noevent colour so just highlight if selected
\r
516 bg = Colour::SELECTHIGHLIGHT; // highlight cell
\r
517 fg = Colour::DARKTEXT;
\r
518 paintCell(&thisEvent, y, bg, fg);
\r
522 bg = Colour::NOPROGRAMME;
\r
523 fg = Colour::LIGHTTEXT;
\r
524 noevent.settitle(tr("No programme details"));
\r
525 paintCell(&noevent, y, bg, fg);
\r
528 y += Surface::getFontHeight() + 2;
\r
530 setInfo(&thisEvent);
\r
533 void VEpg::updateEventList()
\r
536 for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
\r
538 if (eventLista[listIndex])
\r
540 if ((eventLista[listIndex])->empty())
\r
541 (eventLista)[listIndex]->clear();
\r
543 if(listTop + listIndex >= UINT(chanListbox.getBottomOption()))
\r
545 chan = (*chanList)[listTop + listIndex];
\r
546 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
550 void VEpg::setCurrentChannel(char* chname)
\r
552 chanName.setText(chname);
\r
554 viewman->updateView(this);
\r
557 void VEpg::paintCell(Event* event, int yOffset, Colour bg, Colour fg)
\r
561 h = Surface::getFontHeight(); // TODO if want border around text, need to increae this and wselectlist line height
\r
562 UINT end = event->time + event->duration; // programme end time
\r
563 if(event->time <= UINT(ltime) && end > UINT(ltime)) // spans start of displayed window
\r
565 x = 155; // LHS of window
\r
566 if (end > (UINT(ltime) + (WINDOW_WIDTH * 60)))
\r
567 w = WINDOW_WIDTH * MINUTE_SCALE; // spans full 2 hour window
\r
569 w = MINUTE_SCALE * (event->time + event->duration - ltime ) / 60; // get width of remaining programme
\r
571 if((event->time >= UINT(ltime)) && (event->time <= UINT(ltime) + (WINDOW_WIDTH * 60))) // starts within window
\r
573 x = 155 + (MINUTE_SCALE * (event->time - ltime) / 60);
\r
574 w = MINUTE_SCALE * event->duration / 60;
\r
575 //if (w > 155 + MINUTE_SCALE * WINDOW_WIDTH -x)
\r
576 // w = w + x - 155 - MINUTE_SCALE * WINDOW_WIDTH; // ends outside window
\r
578 if (w > 155 + WINDOW_WIDTH * MINUTE_SCALE - x)
\r
579 w = 155 + WINDOW_WIDTH * MINUTE_SCALE -x; // limit cells to RHS of window
\r
580 surface->fillblt(x, y, w, h, surface->rgba(bg.red, bg.green, bg.blue, bg.alpha));
\r
581 char* tt = new char[strlen(event->title) + 1];
\r
582 strcpy (tt, event->title);
\r
584 for (UINT textPos = 0; textPos < strlen(tt); textPos++)
\r
586 int thisCharWidth = surface->getCharWidth(tt[textPos]);
\r
587 if (textWidth + thisCharWidth > w) // text will not fit in cell
\r
589 textWidth = textPos;
\r
592 textWidth += thisCharWidth;
\r
594 char* tT = new char[textWidth];
\r
597 strncpy(tT, tt, textWidth - 1);
\r
598 tT[textWidth - 1] = '\0';
\r
599 surface->drawText(tT, x+2, y, fg.red, fg.green, fg.blue);
\r
605 time_t VEpg::prevHour(time_t* t)
\r
608 tms = localtime(t);
\r
611 return mktime(tms);
\r