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
344 Log::getInstance()->log("VEPG", Log::DEBUG, "ID %lu TIME %lu DURATION %lu TITLE %s\n", thisEvent.id, thisEvent.time, thisEvent.duration, thisEvent.title);
352 { // select programme and display menu TODO currently just changes to selected channel
\r
353 videoLive->channelChange(VVideoLive::NUMBER, (*chanList)[chanListbox.getCurrentOption()]->number);
355 if(command == Remote::GO)
\r
357 // GO just changes channel in preview, PLAY changes channel and returns to normal TV
\r
360 case Remote::GUIDE:
\r
362 // return to normal TV mode
\r
363 if (videoLive) // ptr check done in case being tested from videorec
\r
365 Message* m = new Message(); // Must be done after this view deleted
368 m->message = Message::EPG_CLOSE;
369 ViewMan::getInstance()->postMessage(m);
373 case Remote::CHANNELUP:
375 videoLive->channelChange(VVideoLive::OFFSET, VVideoLive::UP);
378 case Remote::CHANNELDOWN:
380 videoLive->channelChange(VVideoLive::OFFSET, VVideoLive::DOWN);
384 // stop command getting to any more views
\r
388 void VEpg::drawgrid() // redraws grid and select programme
\r
390 // draw the grid of programmes
\r
391 char timeString[20];
\r
393 time(&t); // set t = now
\r
395 selTime = t; // don't allow cursor in the past
\r
396 if(listTop != chanListbox.getTopOption())
\r
398 // chanListbox has scrolled TODO speed up by changing only rows that have changed
\r
399 listTop = chanListbox.getTopOption();
\r
402 if ((selTime >= ltime + WINDOW_WIDTH * 60) || (selTime <= ltime))
\r
404 // we have cursored back before left time of window
\r
405 //TODO check that this and above don't happen together
\r
406 ltime = prevHour(&selTime);
\r
412 tms = localtime(&t);
\r
413 strftime(timeString, 19, "%a %e %b", tms);
\r
414 int timey = chanListbox.getOffsetY() - Surface::getFontHeight() - 3;
\r
416 drawTextRJ(timeString, timex - 10, timey, Colour::LIGHTTEXT); // print date
\r
417 strftime(timeString, 19, "%H:%M", tms);
\r
418 drawText(timeString, timex, timey, Colour::LIGHTTEXT); // print left time
\r
419 surface->fillblt(155, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 255, 255, 255));
\r
421 tms = localtime(&t);
\r
422 strftime(timeString, 19, "%H:%M", tms);
\r
423 drawText(timeString, timex + 180, timey, Colour::LIGHTTEXT); // print middle time
\r
424 surface->fillblt(335, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 255, 255, 255));
\r
426 tms = localtime(&t);
\r
427 strftime(timeString, 19, "%H:%M", tms);
\r
428 drawText(timeString, timex + 360, timey, Colour::LIGHTTEXT); // print right time
\r
429 surface->fillblt(515, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 255, 255, 255));
\r
430 // pointer to selTime
\r
431 surface->fillblt(155 + (selTime - ltime) / 20, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 50, 50, 255));
\r
433 // TODO should the above two comditional statements be combined to avoid calling updateEventList() twice?
\r
435 Event noevent; // an event to use if there are gaps in the epg
\r
436 thisEvent.setdescription(tr("There are no programme details available for this period"));
\r
437 thisEvent.duration = WINDOW_WIDTH * 60;
\r
438 thisEvent.time = ltime;
\r
439 thisEvent.settitle(tr("No programme details"));
\r
441 bool swapColour = FALSE; // alternate cell colour
\r
442 bool currentRow = FALSE;
\r
443 int y = chanListbox.getOffsetY() + 5; // vertical position of cell
\r
444 Colour bg, fg; // background colour of cells in grid
\r
445 // for each displayed channel, find programmes that fall in 2.5 hour time window
\r
446 for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
\r
448 if (listTop + (int)listIndex >= chanListbox.getBottomOption())
\r
449 continue; // ensure nothing populates grid below last channel
\r
450 currentRow = (listTop + (int)listIndex == chanListbox.getCurrentOption());
\r
451 noevent.time = ltime;
\r
452 noevent.duration = WINDOW_WIDTH * 60;
\r
453 noevent.settitle("");
\r
454 paintCell(&noevent, y, Colour::NOPROGRAMME, Colour::LIGHTTEXT); // fill row with no programme colour to be painted ove with valid programmes
\r
457 thisEvent.setdescription(tr("There are no programme details available for this period"));
\r
458 thisEvent.duration = WINDOW_WIDTH * 60;
\r
459 thisEvent.time = ltime;
\r
460 thisEvent.settitle(tr("No programme details"));
\r
463 if (eventLista[listIndex])
\r
465 sort(eventLista[listIndex]->begin(), eventLista[listIndex]->end(), EventSorter());
\r
466 for(e = 0; e < (eventLista[listIndex])->size(); e++) // step through events for this channel
\r
468 fg = Colour::LIGHTTEXT;
\r
469 event = (*eventLista[listIndex])[e];
\r
472 UINT end = event->time + event->duration; // programme end time
\r
473 if(event->time >= UINT(ltime) + (WINDOW_WIDTH * 60)) // programme starts after RHS of window
\r
474 continue; // that's enough of this channel's events
\r
475 if(end <= UINT(ltime)) // programme ends before LHS of window
\r
476 continue; // this event is before the window - let's try the next event
\r
477 // this event is one we are interested in
\r
478 bg = (swapColour)?Colour::PROGRAMMEA:Colour::PROGRAMMEB; // alternate cell colour
\r
479 swapColour = !swapColour; // it wil be the other colour next time
\r
480 if(event->time <= UINT(selTime) && end > UINT(selTime) && currentRow)
\r
482 // this is the selected programme
\r
483 thisEvent.setdescription(event->description);
\r
484 thisEvent.duration = event->duration;
\r
485 thisEvent.time = event->time;
\r
486 thisEvent.settitle(event->title);
\r
487 thisEvent.id = event->id;
\r
488 if(thisEvent.id == 0)
\r
490 bg = Colour::SELECTHIGHLIGHT; // highlight cell
\r
491 fg = Colour::DARKTEXT;
\r
495 if (currentRow && thisEvent.id == 0)
\r
497 if (end <= UINT(selTime) && end > UINT(thisEvent.time))
\r
498 thisEvent.time = end;
\r
499 if (event->time > UINT(selTime) && event->time < thisEvent.time + thisEvent.duration)
\r
500 thisEvent.duration = event->time - thisEvent.time;
\r
503 paintCell(event, y, bg, fg);
\r
509 // no event list for this channel. Already painted noevent colour so just highlight if selected
\r
512 bg = Colour::SELECTHIGHLIGHT; // highlight cell
\r
513 fg = Colour::DARKTEXT;
\r
514 paintCell(&thisEvent, y, bg, fg);
\r
518 bg = Colour::NOPROGRAMME;
\r
519 fg = Colour::LIGHTTEXT;
\r
520 noevent.settitle(tr("No programme details"));
\r
521 paintCell(&noevent, y, bg, fg);
\r
524 y += Surface::getFontHeight() + 2;
\r
526 setInfo(&thisEvent);
\r
529 void VEpg::updateEventList()
\r
532 for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
\r
534 if (eventLista[listIndex])
\r
536 if ((eventLista[listIndex])->empty())
\r
537 (eventLista)[listIndex]->clear();
\r
539 if(listTop + listIndex >= UINT(chanListbox.getBottomOption()))
\r
541 chan = (*chanList)[listTop + listIndex];
\r
542 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
546 void VEpg::setCurrentChannel(char* chname)
\r
548 chanName.setText(chname);
\r
550 viewman->updateView(this);
\r
553 void VEpg::paintCell(Event* event, int yOffset, Colour bg, Colour fg)
\r
556 w = x = 0; // keep compiler happy
559 h = Surface::getFontHeight(); // TODO if want border around text, need to increae this and wselectlist line height
\r
560 UINT end = event->time + event->duration; // programme end time
\r
561 if(event->time <= UINT(ltime) && end > UINT(ltime)) // spans start of displayed window
\r
563 x = 155; // LHS of window
\r
564 if (end > (UINT(ltime) + (WINDOW_WIDTH * 60)))
\r
565 w = WINDOW_WIDTH * MINUTE_SCALE; // spans full 2 hour window
\r
567 w = MINUTE_SCALE * (event->time + event->duration - ltime ) / 60; // get width of remaining programme
\r
569 if((event->time >= UINT(ltime)) && (event->time <= UINT(ltime) + (WINDOW_WIDTH * 60))) // starts within window
\r
571 x = 155 + (MINUTE_SCALE * (event->time - ltime) / 60);
\r
572 w = MINUTE_SCALE * event->duration / 60;
\r
573 //if (w > 155 + MINUTE_SCALE * WINDOW_WIDTH -x)
\r
574 // w = w + x - 155 - MINUTE_SCALE * WINDOW_WIDTH; // ends outside window
\r
576 if (w > 155 + WINDOW_WIDTH * MINUTE_SCALE - x)
\r
577 w = 155 + WINDOW_WIDTH * MINUTE_SCALE -x; // limit cells to RHS of window
\r
578 surface->fillblt(x, y, w, h, surface->rgba(bg.red, bg.green, bg.blue, bg.alpha));
\r
579 char* tt = new char[strlen(event->title) + 1];
\r
580 strcpy (tt, event->title);
\r
582 for (UINT textPos = 0; textPos < strlen(tt); textPos++)
\r
584 int thisCharWidth = surface->getCharWidth(tt[textPos]);
\r
585 if (textWidth + thisCharWidth > w) // text will not fit in cell
\r
587 textWidth = textPos;
\r
590 textWidth += thisCharWidth;
\r
592 char* tT = new char[textWidth];
\r
595 strncpy(tT, tt, textWidth - 1);
\r
596 tT[textWidth - 1] = '\0';
\r
597 surface->drawText(tT, x+2, y, fg.red, fg.green, fg.blue);
\r
603 time_t VEpg::prevHour(time_t* t)
\r
606 tms = localtime(t);
\r
609 return mktime(tms);
\r