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 = 16;
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 videoLive->channelChange(VVideoLive::NUMBER, (*chanList)[chanListbox.getCurrentOption()]->number);
353 if(command == Remote::GO)
\r
355 // GO just changes channel in preview, PLAY changes channel and returns to normal TV
\r
358 case Remote::GUIDE:
\r
360 // return to normal TV mode
\r
361 if (videoLive) // ptr check done in case being tested from videorec
\r
363 Message* m = new Message(); // Must be done after this view deleted
366 m->message = Message::EPG_CLOSE;
367 ViewMan::getInstance()->postMessage(m);
371 case Remote::CHANNELUP:
373 videoLive->channelChange(VVideoLive::OFFSET, VVideoLive::UP);
376 case Remote::CHANNELDOWN:
378 videoLive->channelChange(VVideoLive::OFFSET, VVideoLive::DOWN);
382 // stop command getting to any more views
\r
386 void VEpg::drawgrid() // redraws grid and select programme
\r
388 // draw the grid of programmes
\r
389 char timeString[20];
\r
391 time(&t); // set t = now
\r
393 selTime = t; // don't allow cursor in the past
\r
394 if(listTop != chanListbox.getTopOption())
\r
396 // chanListbox has scrolled TODO speed up by changing only rows that have changed
\r
397 listTop = chanListbox.getTopOption();
\r
400 if ((selTime >= ltime + WINDOW_WIDTH * 60) || (selTime <= ltime))
\r
402 // we have cursored back before left time of window
\r
403 //TODO check that this and above don't happen together
\r
404 ltime = prevHour(&selTime);
\r
410 tms = localtime(&t);
\r
411 strftime(timeString, 19, "%a %e %b", tms);
\r
412 int timey = chanListbox.getOffsetY() - Surface::getFontHeight() - 3;
\r
414 drawTextRJ(timeString, timex - 10, timey, Colour::LIGHTTEXT); // print date
\r
415 strftime(timeString, 19, "%H:%M", tms);
\r
416 drawText(timeString, timex, timey, Colour::LIGHTTEXT); // print left time
\r
417 surface->fillblt(155, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 255, 255, 255));
\r
419 tms = localtime(&t);
\r
420 strftime(timeString, 19, "%H:%M", tms);
\r
421 drawText(timeString, timex + 180, timey, Colour::LIGHTTEXT); // print middle time
\r
422 surface->fillblt(335, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 255, 255, 255));
\r
424 tms = localtime(&t);
\r
425 strftime(timeString, 19, "%H:%M", tms);
\r
426 drawText(timeString, timex + 360, timey, Colour::LIGHTTEXT); // print right time
\r
427 surface->fillblt(515, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 255, 255, 255));
\r
428 // pointer to selTime
\r
429 surface->fillblt(155 + (selTime - ltime) / 20, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 50, 50, 255));
\r
431 // TODO should the above two comditional statements be combined to avoid calling updateEventList() twice?
\r
433 Event noevent; // an event to use if there are gaps in the epg
\r
434 thisEvent.setdescription(tr("There are no programme details available for this period"));
\r
435 thisEvent.duration = WINDOW_WIDTH * 60;
\r
436 thisEvent.time = ltime;
\r
437 thisEvent.settitle(tr("No programme details"));
\r
439 bool swapColour = FALSE; // alternate cell colour
\r
440 bool currentRow = FALSE;
\r
441 int y = chanListbox.getOffsetY() + 5; // vertical position of cell
\r
442 Colour bg, fg; // background colour of cells in grid
\r
443 // for each displayed channel, find programmes that fall in 2.5 hour time window
\r
444 for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
\r
446 if (listTop + (int)listIndex >= chanListbox.getBottomOption())
\r
447 continue; // ensure nothing populates grid below last channel
\r
448 currentRow = (listTop + (int)listIndex == chanListbox.getCurrentOption());
\r
449 noevent.time = ltime;
\r
450 noevent.duration = WINDOW_WIDTH * 60;
\r
451 noevent.settitle("");
\r
452 paintCell(&noevent, y, Colour::NOPROGRAMME, Colour::LIGHTTEXT); // fill row with no programme colour to be painted ove with valid programmes
\r
455 thisEvent.setdescription(tr("There are no programme details available for this period"));
\r
456 thisEvent.duration = WINDOW_WIDTH * 60;
\r
457 thisEvent.time = ltime;
\r
458 thisEvent.settitle(tr("No programme details"));
\r
461 if (eventLista[listIndex])
\r
463 sort(eventLista[listIndex]->begin(), eventLista[listIndex]->end(), EventSorter());
\r
464 for(e = 0; e < (eventLista[listIndex])->size(); e++) // step through events for this channel
\r
466 fg = Colour::LIGHTTEXT;
\r
467 event = (*eventLista[listIndex])[e];
\r
470 UINT end = event->time + event->duration; // programme end time
\r
471 if(event->time >= UINT(ltime) + (WINDOW_WIDTH * 60)) // programme starts after RHS of window
\r
472 continue; // that's enough of this channel's events
\r
473 if(end <= UINT(ltime)) // programme ends before LHS of window
\r
474 continue; // this event is before the window - let's try the next event
\r
475 // this event is one we are interested in
\r
476 bg = (swapColour)?Colour::PROGRAMMEA:Colour::PROGRAMMEB; // alternate cell colour
\r
477 swapColour = !swapColour; // it wil be the other colour next time
\r
478 if(event->time <= UINT(selTime) && end > UINT(selTime) && currentRow)
\r
480 // this is the selected programme
\r
481 thisEvent.setdescription(event->description);
\r
482 thisEvent.duration = event->duration;
\r
483 thisEvent.time = event->time;
\r
484 thisEvent.settitle(event->title);
\r
485 thisEvent.id = event->id;
\r
486 if(thisEvent.id == 0)
\r
488 bg = Colour::SELECTHIGHLIGHT; // highlight cell
\r
489 fg = Colour::DARKTEXT;
\r
493 if (currentRow && thisEvent.id == 0)
\r
495 if (end <= UINT(selTime) && end > UINT(thisEvent.time))
\r
496 thisEvent.time = end;
\r
497 if (event->time > UINT(selTime) && event->time < thisEvent.time + thisEvent.duration)
\r
498 thisEvent.duration = event->time - thisEvent.time;
\r
501 paintCell(event, y, bg, fg);
\r
507 // no event list for this channel. Already painted noevent colour so just highlight if selected
\r
510 bg = Colour::SELECTHIGHLIGHT; // highlight cell
\r
511 fg = Colour::DARKTEXT;
\r
512 paintCell(&thisEvent, y, bg, fg);
\r
516 bg = Colour::NOPROGRAMME;
\r
517 fg = Colour::LIGHTTEXT;
\r
518 noevent.settitle(tr("No programme details"));
\r
519 paintCell(&noevent, y, bg, fg);
\r
522 y += Surface::getFontHeight() + 2;
\r
524 setInfo(&thisEvent);
\r
527 void VEpg::updateEventList()
\r
530 for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
\r
532 if (eventLista[listIndex])
\r
534 if ((eventLista[listIndex])->empty())
\r
535 (eventLista)[listIndex]->clear();
\r
537 if(listTop + listIndex >= UINT(chanListbox.getBottomOption()))
\r
539 chan = (*chanList)[listTop + listIndex];
\r
540 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
544 void VEpg::setCurrentChannel(char* chname)
\r
546 chanName.setText(chname);
\r
548 viewman->updateView(this);
\r
551 void VEpg::paintCell(Event* event, int yOffset, Colour bg, Colour fg)
\r
554 w = x = 0; // keep compiler happy
557 h = Surface::getFontHeight(); // TODO if want border around text, need to increae this and wselectlist line height
\r
558 UINT end = event->time + event->duration; // programme end time
\r
559 if(event->time <= UINT(ltime) && end > UINT(ltime)) // spans start of displayed window
\r
561 x = 155; // LHS of window
\r
562 if (end > (UINT(ltime) + (WINDOW_WIDTH * 60)))
\r
563 w = WINDOW_WIDTH * MINUTE_SCALE; // spans full 2 hour window
\r
565 w = MINUTE_SCALE * (event->time + event->duration - ltime ) / 60; // get width of remaining programme
\r
567 if((event->time >= UINT(ltime)) && (event->time <= UINT(ltime) + (WINDOW_WIDTH * 60))) // starts within window
\r
569 x = 155 + (MINUTE_SCALE * (event->time - ltime) / 60);
\r
570 w = MINUTE_SCALE * event->duration / 60;
\r
571 //if (w > 155 + MINUTE_SCALE * WINDOW_WIDTH -x)
\r
572 // w = w + x - 155 - MINUTE_SCALE * WINDOW_WIDTH; // ends outside window
\r
574 if (w > 155 + WINDOW_WIDTH * MINUTE_SCALE - x)
\r
575 w = 155 + WINDOW_WIDTH * MINUTE_SCALE -x; // limit cells to RHS of window
\r
576 surface->fillblt(x, y, w, h, surface->rgba(bg.red, bg.green, bg.blue, bg.alpha));
\r
577 char* tt = new char[strlen(event->title) + 1];
\r
578 strcpy (tt, event->title);
\r
580 for (UINT textPos = 0; textPos < strlen(tt); textPos++)
\r
582 int thisCharWidth = surface->getCharWidth(tt[textPos]);
\r
583 if (textWidth + thisCharWidth > w) // text will not fit in cell
\r
585 textWidth = textPos;
\r
588 textWidth += thisCharWidth;
\r
590 char* tT = new char[textWidth];
\r
593 strncpy(tT, tt, textWidth - 1);
\r
594 tT[textWidth - 1] = '\0';
\r
595 surface->drawText(tT, x+2, y, fg.red, fg.green, fg.blue);
\r
601 time_t VEpg::prevHour(time_t* t)
\r
604 tms = localtime(t);
\r
607 return mktime(tms);
\r