2 Copyright 2005 Brian Walton
4 This file is part of VOMP.
6 VOMP is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 VOMP is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with VOMP; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 vepg presents a 2 dimensional electronic programme guide with channels down
22 the y axis and time along the x axis.
23 Programmes are layed on the x axis as alterate coloured blocks with as much
24 of the programme title as will fit inside the block shown as text.
25 Up and down commands step through the channels whilst left and right commands
26 move through the programmes of the currently selected channel.
27 When a programme is selected, it highlights in the grid and full programe details
28 (start time, title and description) are displayed in an area at te top left of the screen.
29 Any currently programmed timers will display in the grid and in the orogramme detail window as red
30 It is possible to select a programme to be recorded by pressing the record button.
31 The video stream currently being viewed is shown as quarter screen in the top right.
37 #include "vchannellist.h"
40 #include "vepgsettimer.h"
45 #include "vvideolive.h"
50 VEpg* VEpg::instance = NULL;
52 VEpg::VEpg(VVideoLive* v, UINT currentChannel, ULONG streamType)
56 // PAL / NTSC sizes -----------------------
60 int summaryLines, summaryLowerPadding;
62 //UINT gridRows; // is a class member
64 if (Video::getInstance()->getFormat() == Video::PAL)
71 summaryLowerPadding = 16;
82 summaryLowerPadding = 26;
87 // initialise variables and pointers
88 boxstack = BoxStack::getInstance();
91 chanList = VDR::getInstance()->getChannelsList(streamType); //TODO want to be able to display video and radio together
94 for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
96 // initialise array of pointers to eventlist structures
97 eventLista[listIndex] = NULL;
100 // Create pallet on which to paint our epg view and position it in centre of screen.
101 // Need to reduce size to deal with overscanning TVs.
103 setSize(xsize, ysize);
105 setPosition(xpos, ypos);
108 // Colour transparent = Colour(0, 0, 0, 0);
109 // setBackgroundColour(transparent);
111 // progTitle.setSurface(surface);
112 progTitle.setPosition(0,0);
113 progTitle.setSize(300,(Surface::getFontHeight() + 4) * 2 + 16); //paragraph line seperation is 4 pixels
114 progTitle.setBackgroundColour(Colour::TITLEBARBACKGROUND);
115 progTitle.setTextPos(5, 16);
119 // progInfo.setSurface(surface);
120 progInfo.setBackgroundColour(Colour::VIEWBACKGROUND);
121 progInfo.setPosition(0, progTitle.getY() + progTitle.getHeight());
122 progInfo.setSize(300,((Surface::getFontHeight() + 4) * summaryLines) + summaryLowerPadding);
126 // chanName.setSurface(surface);
127 chanName.setSize(510, (Surface::getFontHeight() + 4));
128 chanName.setPosition(305, chanNameYpos);
129 Colour t1(0, 0, 0, 90);
130 chanName.setBackgroundColour(t1);
133 // create area to display list of channels
134 // chanListbox.setSurface(surface); // add channel list
135 chanListbox.setPosition(0, progInfo.getY() + progInfo.getHeight() + Surface::getFontHeight() + 8); // position channel list
136 chanListbox.setSize(150, ((Surface::getFontHeight() + 2) * gridRows) + 5); //listbox line seperation is 2 pixels
137 chanListbox.setGap(2);
140 // populate channel list
145 for (UINT i = 0; i < chanList->size(); i++)
147 chan = (*chanList)[i];
148 if (i == currentChannel)
150 chan->index = chanListbox.addOption(chan->name, 0, first);
153 chanName.setText((*chanList)[chanListbox.getCurrentOption()]->name);
156 listTop = chanListbox.getTopOption();
157 chanListbox.draw(); // doing this to allow chanListbox.getBottomOption() in updateEventList() to work
158 time(<ime); // set ltime to now
159 ltime = prevHour(<ime); // set ltime to previous hour TODO make this half hour?
160 time(&selTime); // set selTime to now
161 updateEventList(); // get list of programmes
166 Timers::getInstance()->cancelTimer(this, 1);
170 for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
172 if (eventLista[listIndex])
174 (eventLista)[listIndex]->clear();
175 delete eventLista[listIndex];
178 // delete [] eventLista; // FIXME
180 // destroy dynamically allocated memory
183 VEpg* VEpg::getInstance()
188 void VEpg::setInfo(Event* event)
191 struct tm* btime; // to hold programme start and end time
192 char timeString[9]; // to hold programme start and end time
193 int length = strlen(event->title); // calculate length of programme title string
194 char* title = new char[length + 15]; // create string to hold start time, end time and programme title
195 btime = localtime((time_t*)&event->time); //get programme start time
197 strftime(timeString, 9, "%0H:%0M - ", btime); // and format it as hh:mm -
199 strftime(timeString, 9, "%H:%M - ", btime); // and format it as hh:mm -
201 strcpy(title, timeString); // put it in our buffer
202 t = event->time + event->duration; //get programme end time
203 btime = localtime(&t);
205 strftime(timeString, 7, "%0H:%0M ", btime); // and format it as hh:mm -
207 strftime(timeString, 7, "%H:%M ", btime); // and format it as hh:mm -
209 strcat(title, timeString); // put it in our buffer
210 strcat(title, event->title); // then add the programme title
211 progTitle.setText(title); // sput this sring in our text box
212 length = strlen(event->description);
213 char* info = new char[length + 1]; // create programme detail string
214 strcpy(info, event->description);
215 progInfo.setText(info); // show programme detail string
216 // destroy dynamically allocated memory
223 // View::draw(); // draw pallet
225 Colour transparent = Colour(0, 0, 0, 0);
226 fillColour(transparent);
229 // Moved all the dynamic data drawing to a seperate function
231 // Display the status and key stuff at the bottom
233 UINT keyx = chanListbox.getRootBoxOffsetX();
234 UINT keyy = chanListbox.getRootBoxOffsetY() + chanListbox.getHeight() + 2;
235 Colour ref1 = Colour(100, 100, 100, 255);
236 rectangle(keyx, keyy, 605, Surface::getFontHeight() * 2 + 14, ref1);
241 w.nextSymbol = WSymbol::LEFTARROW;
242 w.setPosition(keyx + 1, keyy + 20);
245 w.nextSymbol = WSymbol::UP;
246 w.setPosition(keyx + 26, keyy + 3);
249 w.nextSymbol = WSymbol::DOWN;
250 w.setPosition(keyx + 26, keyy + 36);
253 w.nextSymbol = WSymbol::RIGHTARROW;
254 w.setPosition(keyx + 50, keyy + 20);
257 drawText(tr("OK"), keyx + 18, keyy + 20, Colour::LIGHTTEXT);
259 Colour ref2 = Colour(200, 0, 0, 255);
260 rectangle(keyx + 72, keyy + 4, 104, Surface::getFontHeight() + 2, ref2);
261 drawText(tr("Page up"), keyx + 74, keyy + 5, Colour::LIGHTTEXT);
263 Colour ref3 = Colour(0, 200, 0, 255);
264 rectangle(keyx + 72, keyy + Surface::getFontHeight() + 8, 104, Surface::getFontHeight() + 2, ref3);
265 drawText(tr("Page down"), keyx + 74, keyy + Surface::getFontHeight() + 9, Colour::LIGHTTEXT);
267 Colour ref4 = Colour(200, 200, 0, 255);
268 rectangle(keyx + 180, keyy + 4, 104, Surface::getFontHeight() + 2, ref4);
269 drawText(tr("-24 hours"), keyx + 182, keyy + 5, Colour::LIGHTTEXT);
271 Colour ref5 = Colour(0, 0, 200, 255);
272 rectangle(keyx + 180, keyy + Surface::getFontHeight() + 8, 104, Surface::getFontHeight() + 2, ref5);
273 drawText(tr("+24 hours"), keyx + 182, keyy + Surface::getFontHeight() + 9, Colour::LIGHTTEXT);
275 Colour ref6 = Colour(180, 180, 180, 255);
276 rectangle(keyx + 290, keyy + 4, 180, Surface::getFontHeight() + 2, ref6);
277 drawText(tr("Guide / Back: Close"), keyx + 292 , keyy + 5, Colour::LIGHTTEXT);
279 Colour ref7 = Colour(180, 180, 180, 255);
280 rectangle(keyx + 290, keyy + Surface::getFontHeight() + 8, 180, Surface::getFontHeight() + 2, ref7);
281 Colour red = Colour(130, 0, 0);
282 drawText(tr("Rec: Set timer"), keyx + 292, keyy + Surface::getFontHeight() + 9, red);
284 Colour ref8 = Colour(180, 180, 180, 255);
285 rectangle(keyx + 474, keyy + 4, 128, Surface::getFontHeight() + 2, ref8);
286 w.nextSymbol = WSymbol::PLAY;
287 w.setPosition(keyx + 476, keyy + 5);
289 drawText(tr("Sel channel"), keyx + 496, keyy + 5, Colour::LIGHTTEXT);
291 Colour ref9 = Colour(180, 180, 180, 255);
292 rectangle(keyx + 474, keyy + Surface::getFontHeight() + 8, 128, Surface::getFontHeight() + 2, ref9);
293 drawText(tr("Go: Preview"), keyx + 476, keyy + Surface::getFontHeight() + 9, Colour::LIGHTTEXT);
296 // Draw all the dynamic data
300 void VEpg::drawData()
302 // Not doing View::draw() every time causes
303 // things not to be cleared off the surface properly
304 // So, blank out the data area first
307 chanListbox.getRootBoxOffsetX(),
308 chanListbox.getRootBoxOffsetY() - Surface::getFontHeight() - 3,
309 155 + WINDOW_WIDTH * MINUTE_SCALE,
310 chanListbox.getHeight() + Surface::getFontHeight() + 4,
315 chanName.draw(); // TODO this should be dealt with by vvideolive
320 // Set timer to redraw to move the current time bar
324 if (dt == 0) dt = 60;
326 Timers::getInstance()->setTimerT(this, 1, dt);
329 void VEpg::timercall(int clientReference)
332 // Put updateView through master mutex since boxstack is not mutex protected
333 Message* m = new Message();
334 m->message = Message::REDRAW;
338 Command::getInstance()->postMessageFromOuterSpace(m);
341 int VEpg::handleCommand(int command)
347 { // cursor up the channel list
350 boxstack->update(this);
353 case Remote::DF_DOWN:
355 { // cursor down the channel list
356 Log::getInstance()->log("VEPG", Log::DEBUG, "Down start");
360 boxstack->update(this);
361 Log::getInstance()->log("VEPG", Log::DEBUG, "Down end");
365 case Remote::DF_LEFT:
367 { // cursor left through time
368 selTime = thisEvent.time - 1;
370 boxstack->update(this);
373 case Remote::DF_RIGHT:
376 // cursor right through time
377 selTime = thisEvent.time + thisEvent.duration;
379 boxstack->update(this);
384 // cursor up one page
385 chanListbox.pageUp();
387 boxstack->update(this);
392 // cursor down one page
393 chanListbox.pageDown();
395 boxstack->update(this);
400 // step forward 24 hours
401 selTime += 24 * 60 * 60;
403 boxstack->update(this);
408 // step forward 24 hours
409 selTime -= 24 * 60 * 60;
411 boxstack->update(this);
416 if (!chanList) return 2;
417 Log::getInstance()->log("VEPG", Log::DEBUG, "ID %lu TIME %lu DURATION %lu TITLE %s", thisEvent.id, thisEvent.time, thisEvent.duration, thisEvent.title);
418 VEpgSetTimer* vs = new VEpgSetTimer(&thisEvent, (*chanList)[chanListbox.getCurrentOption()]);
421 boxstack->update(vs);
428 if (!chanList) return 2;
430 // select programme and display menu TODO currently just changes to selected channel
431 if (videoLive) videoLive->channelChange(VVideoLive::NUMBER, (*chanList)[chanListbox.getCurrentOption()]->number);
433 if(command == Remote::GO)
435 // GO just changes channel in preview, PLAY changes channel and returns to normal TV
440 // return to normal TV mode
441 if (videoLive) // ptr check done in case being tested from videorec
443 Message* m = new Message(); // Must be done after this view deleted
446 m->message = Message::EPG_CLOSE;
447 Command::getInstance()->postMessageNoLock(m);
451 case Remote::CHANNELUP:
453 if (videoLive) videoLive->channelChange(VVideoLive::OFFSET, VVideoLive::UP);
456 case Remote::CHANNELDOWN:
458 if (videoLive) videoLive->channelChange(VVideoLive::OFFSET, VVideoLive::DOWN);
462 // stop command getting to any more views
466 void VEpg::drawgrid() // redraws grid and select programme
468 // draw the grid of programmes
471 time(&t); // set t = now
473 selTime = t; // don't allow cursor in the past
474 if(listTop != chanListbox.getTopOption())
476 // chanListbox has scrolled TODO speed up by changing only rows that have changed
477 listTop = chanListbox.getTopOption();
480 if ((selTime >= ltime + WINDOW_WIDTH * 60) || (selTime <= ltime))
482 // we have cursored back before left time of window
483 //TODO check that this and above don't happen together
484 ltime = prevHour(&selTime);
488 Colour white = Colour(255, 255, 255, 255);
493 strftime(timeString, 19, "%a %e %b", tms);
494 int timey = chanListbox.getRootBoxOffsetY() - Surface::getFontHeight() - 3;
496 drawTextRJ(timeString, timex - 10, timey, Colour::LIGHTTEXT); // print date
497 strftime(timeString, 19, "%H:%M", tms);
498 drawText(timeString, timex, timey, Colour::LIGHTTEXT); // print left time
499 rectangle(155, timey + Surface::getFontHeight(), 2, 7, white);
502 strftime(timeString, 19, "%H:%M", tms);
503 drawText(timeString, timex + 180, timey, Colour::LIGHTTEXT); // print middle time
504 rectangle(335, timey + Surface::getFontHeight(), 2, 7, white);
507 strftime(timeString, 19, "%H:%M", tms);
508 drawText(timeString, timex + 360, timey, Colour::LIGHTTEXT); // print right time
509 rectangle(515, timey + Surface::getFontHeight(), 2, 7, white);
510 // pointer to selTime
511 //rectangle(155 + (selTime - ltime) / 20, timey + Surface::getFontHeight(), 2, 7, Colour(255, 50, 50, 255));
515 if ((t >= ltime) && (t < (ltime + 9000)))
517 rectangle(155 + (t - ltime) / 20, timey + Surface::getFontHeight(), 2, ((Surface::getFontHeight() + 2) * 7) + 7 + 2, Colour::RED);
520 // TODO should the above two comditional statements be combined to avoid calling updateEventList() twice?
522 Event noevent; // an event to use if there are gaps in the epg
523 thisEvent.setdescription(tr("There are no programme details available for this period"));
524 thisEvent.duration = WINDOW_WIDTH * 60;
525 thisEvent.time = ltime;
526 thisEvent.settitle(tr("No programme details"));
528 bool swapColour = FALSE; // alternate cell colour
529 bool currentRow = FALSE;
530 int y = chanListbox.getRootBoxOffsetY() + 5; // vertical position of cell
531 Colour bg, fg; // background colour of cells in grid
532 // for each displayed channel, find programmes that fall in 2.5 hour time window
533 for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
535 if (listTop + (int)listIndex >= chanListbox.getBottomOption())
536 continue; // ensure nothing populates grid below last channel
537 currentRow = (listTop + (int)listIndex == chanListbox.getCurrentOption());
538 noevent.time = ltime;
539 noevent.duration = WINDOW_WIDTH * 60;
540 noevent.settitle("");
541 paintCell(&noevent, y, Colour::NOPROGRAMME, Colour::LIGHTTEXT); // fill row with no programme colour to be painted ove with valid programmes
544 thisEvent.setdescription(tr("There are no programme details available for this period"));
545 thisEvent.duration = WINDOW_WIDTH * 60;
546 thisEvent.time = ltime;
547 thisEvent.settitle(tr("No programme details"));
550 if (eventLista[listIndex])
552 sort(eventLista[listIndex]->begin(), eventLista[listIndex]->end(), EventSorter());
553 for(e = 0; e < (eventLista[listIndex])->size(); e++) // step through events for this channel
555 fg = Colour::LIGHTTEXT;
556 event = (*eventLista[listIndex])[e];
559 UINT end = event->time + event->duration; // programme end time
560 if(event->time >= UINT(ltime) + (WINDOW_WIDTH * 60)) // programme starts after RHS of window
561 continue; // that's enough of this channel's events
562 if(end <= UINT(ltime)) // programme ends before LHS of window
563 continue; // this event is before the window - let's try the next event
564 // this event is one we are interested in
565 bg = (swapColour)?Colour::PROGRAMMEA:Colour::PROGRAMMEB; // alternate cell colour
566 swapColour = !swapColour; // it wil be the other colour next time
567 if(event->time <= UINT(selTime) && end > UINT(selTime) && currentRow)
569 // this is the selected programme
570 thisEvent.setdescription(event->description);
571 thisEvent.duration = event->duration;
572 thisEvent.time = event->time;
573 thisEvent.settitle(event->title);
574 thisEvent.id = event->id;
575 if(thisEvent.id == 0)
577 bg = Colour::SELECTHIGHLIGHT; // highlight cell
578 fg = Colour::DARKTEXT;
582 if (currentRow && thisEvent.id == 0)
584 if (end <= UINT(selTime) && end > UINT(thisEvent.time))
585 thisEvent.time = end;
586 if (event->time > UINT(selTime) && event->time < thisEvent.time + thisEvent.duration)
587 thisEvent.duration = event->time - thisEvent.time;
590 paintCell(event, y, bg, fg);
596 // no event list for this channel. Already painted noevent colour so just highlight if selected
599 bg = Colour::SELECTHIGHLIGHT; // highlight cell
600 fg = Colour::DARKTEXT;
601 paintCell(&thisEvent, y, bg, fg);
605 bg = Colour::NOPROGRAMME;
606 fg = Colour::LIGHTTEXT;
607 noevent.settitle(tr("No programme details"));
608 paintCell(&noevent, y, bg, fg);
611 y += Surface::getFontHeight() + 2;
616 void VEpg::updateEventList()
618 if (!chanList) return;
620 for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
622 if(listTop + listIndex >= UINT(chanListbox.getBottomOption()))
624 chan = (*chanList)[listTop + listIndex];
626 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
630 void VEpg::setCurrentChannel(char* chname)
632 chanName.setText(chname);
634 boxstack->update(this);
637 void VEpg::paintCell(Event* event, int yOffset, Colour bg, Colour fg)
640 w = x = 0; // keep compiler happy
643 h = Surface::getFontHeight(); // TODO if want border around text, need to increae this and wselectlist line height
644 UINT end = event->time + event->duration; // programme end time
645 if(event->time <= UINT(ltime) && end > UINT(ltime)) // spans start of displayed window
647 x = 155; // LHS of window
648 if (end > (UINT(ltime) + (WINDOW_WIDTH * 60)))
649 w = WINDOW_WIDTH * MINUTE_SCALE; // spans full 2 hour window
651 w = MINUTE_SCALE * (event->time + event->duration - ltime ) / 60; // get width of remaining programme
653 if((event->time >= UINT(ltime)) && (event->time <= UINT(ltime) + (WINDOW_WIDTH * 60))) // starts within window
655 x = 155 + (MINUTE_SCALE * (event->time - ltime) / 60);
656 w = MINUTE_SCALE * event->duration / 60;
657 //if (w > 155 + MINUTE_SCALE * WINDOW_WIDTH -x)
658 // w = w + x - 155 - MINUTE_SCALE * WINDOW_WIDTH; // ends outside window
660 if (w > 155 + WINDOW_WIDTH * MINUTE_SCALE - x)
661 w = 155 + WINDOW_WIDTH * MINUTE_SCALE -x; // limit cells to RHS of window
662 rectangle(x, y, w, h, bg);
663 char* tt = new char[strlen(event->title) + 1];
664 strcpy (tt, event->title);
666 for (UINT textPos = 0; textPos < strlen(tt); textPos++)
668 int thisCharWidth = surface->getCharWidth(tt[textPos]);
669 if (textWidth + thisCharWidth > w) // text will not fit in cell
674 textWidth += thisCharWidth;
676 char* tT = new char[textWidth];
679 strncpy(tT, tt, textWidth - 1);
680 tT[textWidth - 1] = '\0';
681 surface->drawText(tT, x+2, y, fg.rgba());
687 time_t VEpg::prevHour(time_t* t)
696 void VEpg::processMessage(Message* m)
698 if (m->message == Message::MOUSE_MOVE)
700 if (chanListbox.mouseMove((m->parameter>>16)-getScreenX(),(m->parameter&0xFFFF)-getScreenY()))
703 boxstack->update(this);
706 else if (m->message == Message::MOUSE_LBDOWN)
708 if (chanListbox.mouseLBDOWN((m->parameter>>16)-getScreenX(),(m->parameter&0xFFFF)-getScreenY()))
710 boxstack->handleCommand(Remote::OK); //simulate OK press
714 //check if press is outside this view! then simulate cancel
715 int x=(m->parameter>>16)-getScreenX();
716 int y=(m->parameter&0xFFFF)-getScreenY();
717 int keyx = chanListbox.getRootBoxOffsetX();
718 int keyy = chanListbox.getRootBoxOffsetY() + chanListbox.getHeight() + 2;
720 if (x<0 || y <0 || x>(int)getWidth() || y>(int)getHeight())
722 boxstack->handleCommand(Remote::BACK); //simulate cancel press
724 else if (x>=(keyx+72) && y>=(keyy+4) &&x<=(keyx+72+104) &&y<=(keyy+4+Surface::getFontHeight() + 2))
726 boxstack->handleCommand(Remote::RED);
728 else if (x>=(keyx+72) && y>=(keyy+ Surface::getFontHeight() + 8) &&x<=(keyx+72+104) &&y<=(keyy+8+2*Surface::getFontHeight() + 2))
730 boxstack->handleCommand(Remote::GREEN);
732 else if (x>=(keyx+180) && y>=(keyy+4) &&x<=(keyx+180+104) &&y<=(keyy+4+Surface::getFontHeight() + 2))
734 boxstack->handleCommand(Remote::YELLOW);
736 else if (x>=(keyx+180) && y>=(keyy+ Surface::getFontHeight() + 8) &&x<=(keyx+180+104) &&y<=(keyy+8+2*Surface::getFontHeight() + 2))
738 boxstack->handleCommand(Remote::BLUE);
740 else if (x>=(keyx+290) && y>=(keyy+4) &&x<=(keyx+180+290) &&y<=(keyy+4+Surface::getFontHeight() + 2))
742 boxstack->handleCommand(Remote::BACK);
744 else if (x>=(keyx+290) && y>=(keyy+ Surface::getFontHeight() + 8) &&x<=(keyx+290+180) &&y<=(keyy+8+2*Surface::getFontHeight() + 2))
746 boxstack->handleCommand(Remote::RECORD);
748 else if (x>=(keyx+474) && y>=(keyy+4) &&x<=(keyx+128+474) &&y<=(keyy+4+Surface::getFontHeight() + 2))
750 boxstack->handleCommand(Remote::PLAY);
752 else if (x>=(keyx+474) && y>=(keyy+ Surface::getFontHeight() + 8) &&x<=(keyx+238+474) &&y<=(keyy+8+2*Surface::getFontHeight() + 2))
754 boxstack->handleCommand(Remote::GO);
756 else if ( x>=(chanListbox.getRootBoxOffsetX())
757 && y>=(chanListbox.getRootBoxOffsetY() + 5)
758 // &&x<=(chanListbox.getOffsetX()+155 + WINDOW_WIDTH * MINUTE_SCALE)
759 &&y<=(chanListbox.getRootBoxOffsetY() - Surface::getFontHeight()
760 - 3+(int)chanListbox.getHeight() + Surface::getFontHeight() + 3)
763 int cy=y-(chanListbox.getRootBoxOffsetY() + 5);
764 int row=cy/(Surface::getFontHeight()+2);
765 int clistTop = chanListbox.getTopOption();
766 chanListbox.hintSetCurrent(clistTop+row);
768 time_t ttime = cx*60/MINUTE_SCALE+ltime;
769 //x = 155 + (MINUTE_SCALE * (event->time - ltime) / 60);
773 boxstack->update(this);