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 viewman = ViewMan::getInstance();
\r
40 for(UINT listIndex = 0; listIndex < 7; listIndex++)
\r
42 // initialise array of pointers to eventlist structures
\r
43 eventLista[listIndex] = NULL;
\r
47 // Create pallet on which to paint our epg view and position it in centre of screen.
\r
48 // Need to reduce size to deal with overscanning TVs.
\r
50 // create(640, 530);
\r
51 //TODO: have size for ntsc
\r
52 if (Video::getInstance()->getFormat() == Video::PAL)
\r
55 setScreenPos(60, 30);
\r
59 // what can we do about poor ntsc?
\r
61 setScreenPos(64, 51);
\r
65 Colour transparent = Colour(0, 0, 0, 0);
\r
66 setBackgroundColour(transparent);
\r
67 // initialise variables and pointers
\r
69 chanList = VDR::getInstance()->getChannelsList(VDR::VIDEO); //TODO want to be able to display video and radio together
\r
71 progTitle.setSurface(surface);
\r
72 progTitle.setSurfaceOffset(0,0);
\r
73 progTitle.setDimensions(300,(Surface::getFontHeight() + 6) * 2 + 2); //paragraph line seperation is 6 pixels
\r
74 progTitle.setBackgroundColour(Colour::TITLEBARBACKGROUND);
\r
75 progInfo.setSurface(surface);
\r
76 progInfo.setSurfaceOffset(0, progTitle.getOffsetY() + progTitle.getHeight());
\r
77 progInfo.setDimensions(300,(Surface::getFontHeight() + 6) * 8 + 2);
\r
78 chanName.setSurface(surface);
\r
79 chanName.setDimensions(510, (Surface::getFontHeight() + 4));
\r
80 chanName.setSurfaceOffset(305,230);
\r
81 chanName.setBackgroundColour(Colour(0, 0, 0, 90));
\r
82 // create area to display list of channels
\r
83 chanListbox.setSurface(surface); // add channel list
\r
84 chanListbox.setSurfaceOffset(0, progInfo.getOffsetY() + progInfo.getHeight() + Surface::getFontHeight() + 8); // position channel list
\r
85 chanListbox.setDimensions(150, (Surface::getFontHeight() + 1) * 7 + 5); //listbox line seperation is 1 pixel
\r
86 // populate channel list
\r
89 for (UINT i = 0; i < chanList->size(); i++)
\r
91 chan = (*chanList)[i];
\r
92 if (i == currentChannel)
\r
94 chan->index = chanListbox.addOption(chan->name, first);
\r
97 listTop = chanListbox.getTopOption();
\r
98 chanListbox.draw(); // doing this to allow chanListbox.getBottomOption() in updateEventList() to work
\r
99 chanName.setText((*chanList)[chanListbox.getCurrentOption()]->name);
\r
100 time(<ime); // set ltime to now
\r
101 ltime = prevHour(<ime); // set ltime to previous hour TODO make this half hour?
\r
102 time(&selTime); // set selTime to now
\r
103 updateEventList(); // get list of programmes
\r
108 for(int listIndex = 0; listIndex < 7; listIndex++)
\r
110 if (eventLista[listIndex])
\r
112 (eventLista)[listIndex]->clear();
\r
113 delete eventLista[listIndex];
\r
116 // delete [] eventLista;
\r
118 // destroy dynamically allocated memory
\r
121 void VEpg::setInfo(Event* event)
\r
124 struct tm* btime; // to hold programme start and end time
\r
125 char* timeString = new char[9]; // to hold programme start and end time
\r
126 int length = strlen(event->title); // calculate length of programme title string
\r
127 char* title = new char[length + 15]; // create string to hold start time, end time and programme title
\r
128 btime = localtime((time_t*)&event->time); //get programme start time
\r
129 strftime(timeString, 9, "%0H:%0M - ", btime); // and format it as hh:mm -
\r
130 strcpy(title, timeString); // put it in our buffer
\r
131 t = event->time + event->duration; //get programme end time
\r
132 btime = localtime(&t);
\r
133 strftime(timeString, 7, "%0H:%0M ", btime); // and format it as hh:mm -
\r
134 strcat(title, timeString); // put it in our buffer
\r
135 strcat(title, event->title); // then add the programme title
\r
136 progTitle.setText(title); // sput this sring in our text box
\r
137 length = strlen(event->description);
\r
138 char* info = new char[length + 1]; // create programme detail string
\r
139 strcpy(info, event->description);
\r
140 progInfo.setText(info); // show programme detail string
\r
141 // destroy dynamically allocated memory
\r
149 View::draw(); // draw pallet
\r
151 // Moved all the dynamic data drawing to a seperate function
\r
154 Log::getInstance()->log("EPG", Log::DEBUG, "START TIME");
\r
155 // Display the status and key stuff at the bottom
\r
156 int keyx = chanListbox.getOffsetX();
\r
157 int keyy = chanListbox.getOffsetY() + chanListbox.getHeight() + 2;
\r
158 surface->fillblt(keyx, keyy, 610, Surface::getFontHeight() * 2 + 14, surface->rgba(100, 100, 100, 255));
\r
161 w.setSurface(surface);
\r
163 w.nextSymbol = WSymbol::LEFTARROW;
\r
164 w.setSurfaceOffset(keyx + 1, keyy + 20);
\r
167 w.nextSymbol = WSymbol::UP;
\r
168 w.setSurfaceOffset(keyx + 26, keyy + 3);
\r
171 w.nextSymbol = WSymbol::DOWN;
\r
172 w.setSurfaceOffset(keyx + 26, keyy + 36);
\r
175 w.nextSymbol = WSymbol::RIGHTARROW;
\r
176 w.setSurfaceOffset(keyx + 50, keyy + 20);
\r
179 drawText(tr("OK"), keyx + 18, keyy + 20, Colour::LIGHTTEXT);
\r
181 surface->fillblt(keyx + 72, keyy + 4, 104, Surface::getFontHeight() + 2, surface->rgba(200, 0, 0, 255));
\r
182 drawText(tr("Page up"), keyx + 74, keyy + 5, Colour::LIGHTTEXT);
\r
184 surface->fillblt(keyx + 72, keyy + Surface::getFontHeight() + 8, 104, Surface::getFontHeight() + 2, surface->rgba(0, 200, 0, 255));
\r
185 drawText(tr("Page down"), keyx + 74, keyy + Surface::getFontHeight() + 9, Colour::LIGHTTEXT);
\r
187 surface->fillblt(keyx + 180, keyy + 4, 104, Surface::getFontHeight() + 2, surface->rgba(200, 200, 0, 255));
\r
188 drawText(tr("-24 hours"), keyx + 182, keyy + 5, Colour::LIGHTTEXT);
\r
190 surface->fillblt(keyx + 180, keyy + Surface::getFontHeight() + 8, 104, Surface::getFontHeight() + 2, surface->rgba( 0, 0, 200, 255));
\r
191 drawText(tr("+24 hours"), keyx + 182, keyy + Surface::getFontHeight() + 9, Colour::LIGHTTEXT);
\r
193 surface->fillblt(keyx + 290, keyy + 4, 180, Surface::getFontHeight() + 2, surface->rgba( 180, 180, 180, 255));
\r
194 drawText(tr("Guide / Back: Close"), keyx + 292 , keyy + 5, Colour::LIGHTTEXT);
\r
196 surface->fillblt(keyx + 290, keyy + Surface::getFontHeight() + 8, 180, Surface::getFontHeight() + 2, surface->rgba( 180, 180, 180, 255));
\r
197 // Colour red = Colour(130, 0, 0);
\r
198 // drawText(tr("Rec: Set timer"), keyx + 292, keyy + Surface::getFontHeight() + 9, red);
\r
200 surface->fillblt(keyx + 474, keyy + 4, 128, Surface::getFontHeight() + 2, surface->rgba( 180, 180, 180, 255));
\r
201 w.nextSymbol = WSymbol::PLAY;
\r
202 w.setSurfaceOffset(keyx + 476, keyy + 5);
\r
204 drawText(tr("Sel channel"), keyx + 496, keyy + 5, Colour::LIGHTTEXT);
\r
206 surface->fillblt(keyx + 474, keyy + Surface::getFontHeight() + 8, 128, Surface::getFontHeight() + 2, surface->rgba( 180, 180, 180, 255));
\r
207 drawText(tr("Go: Preview"), keyx + 476, keyy + Surface::getFontHeight() + 9, Colour::LIGHTTEXT);
\r
210 Log::getInstance()->log("EPG", Log::DEBUG, "END TIME");
\r
212 // Draw all the dynamic data
\r
216 void VEpg::drawData()
\r
218 // Not doing View::draw() every time causes
\r
219 // things not to be cleared off the surface properly
\r
220 // So, blank out the data area first
\r
223 chanListbox.getOffsetX(),
\r
224 chanListbox.getOffsetY() - Surface::getFontHeight() - 3,
\r
225 610, // FIXME make this dynamic
\r
226 chanListbox.getHeight() + Surface::getFontHeight() + 3,
\r
229 chanListbox.draw();
\r
231 chanName.draw(); // TODO this should be dealt with by vvideolive
\r
237 int VEpg::handleCommand(int command)
\r
241 case Remote::DF_UP:
\r
243 { // cursor up the channel list
\r
246 viewman->updateView(this);
\r
249 case Remote::DF_DOWN:
\r
251 { // cursor down the channel list
\r
252 chanListbox.down();
\r
254 viewman->updateView(this);
\r
257 case Remote::DF_LEFT:
\r
259 { // cursor left through time
\r
260 selTime = thisEvent.time - 1;
\r
262 viewman->updateView(this);
\r
265 case Remote::DF_RIGHT:
\r
266 case Remote::RIGHT:
\r
268 // cursor right through time
\r
269 selTime = thisEvent.time + thisEvent.duration;
\r
271 viewman->updateView(this);
\r
276 // cursor up one page
\r
277 chanListbox.pageUp();
\r
279 viewman->updateView(this);
\r
282 case Remote::GREEN:
\r
284 // cursor down one page
\r
285 chanListbox.pageDown();
\r
287 viewman->updateView(this);
\r
292 // step forward 24 hours
\r
293 selTime += 24 * 60 * 60;
\r
295 viewman->updateView(this);
\r
298 case Remote::YELLOW:
\r
300 // step forward 24 hours
\r
301 selTime -= 24 * 60 * 60;
\r
303 viewman->updateView(this);
\r
306 case Remote::RECORD:
\r
314 { // select programme and display menu TODO currently just changes to selected channel
\r
315 Message* m = new Message();
\r
318 m->message = Message::CHANNEL_CHANGE;
\r
319 m->parameter = (*chanList)[chanListbox.getCurrentOption()]->number;
\r
320 ViewMan::getInstance()->postMessage(m);
\r
321 if(command == Remote::GO)
\r
323 // GO just changes channel in preview, PLAY changes channel and returns to normal TV
\r
326 case Remote::GUIDE:
\r
328 // return to normal TV mode
\r
329 videoLive->setEpgMode(FALSE);
\r
332 case Remote::CHANNELUP:
\r
334 // change up channel on live TV
\r
335 Message* m = new Message();
\r
338 m->message = Message::CHANNEL_UP;
\r
339 ViewMan::getInstance()->postMessage(m);
\r
342 case Remote::CHANNELDOWN:
\r
343 { // change down channel on live TV
\r
344 Message* m = new Message();
\r
347 m->message = Message::CHANNEL_DOWN;
\r
348 ViewMan::getInstance()->postMessage(m);
\r
352 // stop command getting to any more views
\r
356 void VEpg::drawgrid() // redraws grid and select programme
\r
358 // draw the grid of programmes
\r
359 char timeString[20];
\r
361 time(&t); // set t = now
\r
363 selTime = t; // don't allow cursor in the past
\r
364 if(listTop != chanListbox.getTopOption())
\r
366 // chanListbox has scrolled TODO speed up by changing only rows that have changed
\r
367 listTop = chanListbox.getTopOption();
\r
370 if ((selTime >= ltime + WINDOW_WIDTH * 60) || (selTime <= ltime))
\r
372 // we have cursored back before left time of window
\r
373 //TODO check that this and above don't happen together
\r
374 ltime = prevHour(&selTime);
\r
380 tms = localtime(&t);
\r
381 strftime(timeString, 19, "%a %e %b", tms);
\r
382 int timey = chanListbox.getOffsetY() - Surface::getFontHeight() - 3;
\r
384 drawTextRJ(timeString, timex - 10, timey, Colour::LIGHTTEXT); // print date
\r
385 strftime(timeString, 19, "%H:%M", tms);
\r
386 drawText(timeString, timex, timey, Colour::LIGHTTEXT); // print left time
\r
387 surface->fillblt(155, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 255, 255, 255));
\r
389 tms = localtime(&t);
\r
390 strftime(timeString, 19, "%H:%M", tms);
\r
391 drawText(timeString, timex + 180, timey, Colour::LIGHTTEXT); // print middle time
\r
392 surface->fillblt(335, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 255, 255, 255));
\r
394 tms = localtime(&t);
\r
395 strftime(timeString, 19, "%H:%M", tms);
\r
396 drawText(timeString, timex + 360, timey, Colour::LIGHTTEXT); // print right time
\r
397 surface->fillblt(515, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 255, 255, 255));
\r
398 // pointer to selTime
\r
399 surface->fillblt(155 + (selTime - ltime) / 20, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 50, 50, 255));
\r
401 // TODO should the above two comditional statements be combined to avoid calling updateEventList() twice?
\r
403 Event noevent; // an event to use if there are gaps in the epg
\r
404 thisEvent.setdescription(tr("There are no programme details available for this period"));
\r
405 thisEvent.duration = WINDOW_WIDTH * 60;
\r
406 thisEvent.time = ltime;
\r
407 thisEvent.settitle(tr("No programme details"));
\r
409 bool swapColour = FALSE; // alternate cell colour
\r
410 bool currentRow = FALSE;
\r
411 int y = chanListbox.getOffsetY() + 5; // vertical position of cell
\r
412 Colour bg, fg; // background colour of cells in grid
\r
413 // for each displayed channel, find programmes that fall in 2.5 hour time window
\r
414 for(int listIndex = 0; listIndex < 7; listIndex++)
\r
416 if (listTop + listIndex >= chanListbox.getBottomOption())
\r
417 continue; // ensure nothing populates grid below last channel
\r
418 currentRow = (listTop + listIndex == chanListbox.getCurrentOption());
\r
419 noevent.time = ltime;
\r
420 noevent.duration = WINDOW_WIDTH * 60;
\r
421 noevent.settitle("");
\r
422 paintCell(&noevent, y, Colour::NOPROGRAMME, Colour::LIGHTTEXT); // fill row with no programme colour to be painted ove with valid programmes
\r
425 thisEvent.setdescription(tr("There are no programme details available for this period"));
\r
426 thisEvent.duration = WINDOW_WIDTH * 60;
\r
427 thisEvent.time = ltime;
\r
428 thisEvent.settitle(tr("No programme details"));
\r
431 if (eventLista[listIndex])
\r
433 sort(eventLista[listIndex]->begin(), eventLista[listIndex]->end(), EventSorter());
\r
434 for(e = 0; e < (eventLista[listIndex])->size(); e++) // step through events for this channel
\r
436 fg = Colour::LIGHTTEXT;
\r
437 event = (*eventLista[listIndex])[e];
\r
440 UINT end = event->time + event->duration; // programme end time
\r
441 if(event->time >= UINT(ltime) + (WINDOW_WIDTH * 60)) // programme starts after RHS of window
\r
442 continue; // that's enough of this channel's events
\r
443 if(end <= UINT(ltime)) // programme ends before LHS of window
\r
444 continue; // this event is before the window - let's try the next event
\r
445 // this event is one we are interested in
\r
446 bg = (swapColour)?Colour::PROGRAMMEA:Colour::PROGRAMMEB; // alternate cell colour
\r
447 swapColour = !swapColour; // it wil be the other colour next time
\r
448 if(event->time <= UINT(selTime) && end > UINT(selTime) && currentRow)
\r
450 // this is the selected programme
\r
451 thisEvent.setdescription(event->description);
\r
452 thisEvent.duration = event->duration;
\r
453 thisEvent.time = event->time;
\r
454 thisEvent.settitle(event->title);
\r
455 thisEvent.id = event->id;
\r
456 if(thisEvent.id == 0)
\r
458 bg = Colour::SELECTHIGHLIGHT; // highlight cell
\r
459 fg = Colour::DARKTEXT;
\r
463 if (currentRow && thisEvent.id == 0)
\r
465 if (end <= UINT(selTime) && end > UINT(thisEvent.time))
\r
466 thisEvent.time = end;
\r
467 if (event->time > UINT(selTime) && event->time < thisEvent.time + thisEvent.duration)
\r
468 thisEvent.duration = event->time - thisEvent.time;
\r
471 paintCell(event, y, bg, fg);
\r
477 // no event list for this channel. Already painted noevent colour so just highlight if selected
\r
480 bg = Colour::SELECTHIGHLIGHT; // highlight cell
\r
481 fg = Colour::DARKTEXT;
\r
482 paintCell(&thisEvent, y, bg, fg);
\r
486 bg = Colour::NOPROGRAMME;
\r
487 fg = Colour::LIGHTTEXT;
\r
488 noevent.settitle(tr("No programme details"));
\r
489 paintCell(&noevent, y, bg, fg);
\r
492 y += Surface::getFontHeight() + 1;
\r
494 setInfo(&thisEvent);
\r
497 void VEpg::updateEventList()
\r
500 for(UINT listIndex = 0; listIndex < 7; listIndex++)
\r
502 if (eventLista[listIndex])
\r
504 if ((eventLista[listIndex])->empty())
\r
505 (eventLista)[listIndex]->clear();
\r
507 if(listTop + listIndex >= UINT(chanListbox.getBottomOption()))
\r
509 chan = (*chanList)[listTop + listIndex];
\r
510 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
514 void VEpg::setCurrentChannel(char* chname)
\r
516 chanName.setText(chname);
\r
518 viewman->updateView(this);
\r
521 void VEpg::paintCell(Event* event, int yOffset, Colour bg, Colour fg)
\r
525 h = Surface::getFontHeight(); // TODO if want border around text, need to increae this and wselectlist line height
\r
526 UINT end = event->time + event->duration; // programme end time
\r
527 if(event->time <= UINT(ltime) && end > UINT(ltime)) // spans start of displayed window
\r
529 x = 155; // LHS of window
\r
530 if (end > (UINT(ltime) + (WINDOW_WIDTH * 60)))
\r
531 w = WINDOW_WIDTH * MINUTE_SCALE; // spans full 2 hour window
\r
533 w = MINUTE_SCALE * (event->time + event->duration - ltime ) / 60; // get width of remaining programme
\r
535 if((event->time >= UINT(ltime)) && (event->time <= UINT(ltime) + (WINDOW_WIDTH * 60))) // starts within window
\r
537 x = 155 + (MINUTE_SCALE * (event->time - ltime) / 60);
\r
538 w = MINUTE_SCALE * event->duration / 60;
\r
539 //if (w > 155 + MINUTE_SCALE * WINDOW_WIDTH -x)
\r
540 // w = w + x - 155 - MINUTE_SCALE * WINDOW_WIDTH; // ends outside window
\r
542 if (w > 155 + WINDOW_WIDTH * MINUTE_SCALE - x)
\r
543 w = 155 + WINDOW_WIDTH * MINUTE_SCALE -x; // limit cells to RHS of window
\r
544 surface->fillblt(x, y, w, h, surface->rgba(bg.red, bg.green, bg.blue, bg.alpha));
\r
545 char* tt = new char[strlen(event->title) + 1];
\r
546 strcpy (tt, event->title);
\r
548 for (UINT textPos = 0; textPos < strlen(tt); textPos++)
\r
550 int thisCharWidth = surface->getCharWidth(tt[textPos]);
\r
551 if (textWidth + thisCharWidth > w) // text will not fit in cell
\r
553 textWidth = textPos;
\r
556 textWidth += thisCharWidth;
\r
558 char* tT = new char[textWidth];
\r
561 strncpy(tT, tt, textWidth - 1);
\r
562 tT[textWidth - 1] = '\0';
\r
563 surface->drawText(tT, x+2, y, fg.red, fg.green, fg.blue);
\r
569 time_t VEpg::prevHour(time_t* t)
\r
572 tms = localtime(t);
\r
575 return mktime(tms);
\r