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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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"
50 VEpg* VEpg::instance = NULL;
52 VEpg::VEpg(void* tparent, UINT tcurrentChannelIndex, ULONG streamType)
55 currentChannelIndex = tcurrentChannelIndex;
57 // PAL / NTSC sizes -----------------------
61 int summaryLines, summaryLowerPadding;
63 //UINT gridRows; // is a class member
65 if (Video::getInstance()->getFormat() == Video::PAL)
72 summaryLowerPadding = 18;
83 summaryLowerPadding = 28;
88 // initialise variables and pointers
89 boxstack = BoxStack::getInstance();
92 chanList = VDR::getInstance()->getChannelsList(streamType); //TODO want to be able to display video and radio together
95 for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
97 // initialise array of pointers to eventlist structures
98 eventLista[listIndex] = NULL;
101 // Create pallet on which to paint our epg view and position it in centre of screen.
102 // Need to reduce size to deal with overscanning TVs.
104 setSize(xsize, ysize);
106 setPosition(xpos, ypos);
109 // DrawStyle transparent = DrawStyle(0, 0, 0, 0);
110 // setBackgroundColour(transparent);
112 // progTitle.setSurface(surface);
113 progTitle.setPosition(0,0);
114 progTitle.setSize(300,(getFontHeight() + 4) * 2 + 16); //paragraph line seperation is 4 pixels
115 progTitle.setBackgroundColour(DrawStyle::TITLEBARBACKGROUND);
116 progTitle.setTextPos(5, 16);
120 // progInfo.setSurface(surface);
121 progInfo.setBackgroundColour(DrawStyle::VIEWBACKGROUND);
122 progInfo.setPosition(0, progTitle.getY2());
123 progInfo.setSize(300,((getFontHeight() + 4) * summaryLines) + summaryLowerPadding);
127 // chanName.setSurface(surface);
128 chanName.setSize(510, (getFontHeight() + 4));
129 chanName.setPosition(305, chanNameYpos);
130 DrawStyle t1(0, 0, 0, 90);
131 chanName.setBackgroundColour(t1);
134 // create area to display list of channels
135 // chanListbox.setSurface(surface); // add channel list
136 chanListbox.setPosition(0, progInfo.getY2() + getFontHeight() + 8); // position channel list
137 chanListbox.setSize(150, ((getFontHeight() + 2) * gridRows) + 5); //listbox line seperation is 2 pixels
138 chanListbox.setGap(2);
141 // populate channel list
146 for (UINT i = 0; i < chanList->size(); i++)
148 chan = (*chanList)[i];
149 if (i == currentChannelIndex)
151 chan->index = chanListbox.addOption(chan->name, 0, first);
154 chanName.setText((*chanList)[chanListbox.getCurrentOption()]->name);
157 listTop = chanListbox.getTopOption();
158 chanListbox.draw(); // doing this to allow chanListbox.getBottomOption() in updateEventList() to work
159 time(<ime); // set ltime to now
160 ltime = prevHour(<ime); // set ltime to previous hour TODO make this half hour?
161 time(&selTime); // set selTime to now
162 updateEventList(); // get list of programmes
165 void VEpg::preDelete()
167 Timers::getInstance()->cancelTimer(this, 1);
175 for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
177 if (eventLista[listIndex])
179 (eventLista)[listIndex]->clear();
180 delete eventLista[listIndex];
183 // delete [] eventLista; // FIXME
185 // destroy dynamically allocated memory
188 VEpg* VEpg::getInstance()
193 void VEpg::setInfo(Event* event)
196 struct tm* btime; // to hold programme start and end time
197 char timeString[9]; // to hold programme start and end time
198 int length = strlen(event->title); // calculate length of programme title string
199 char* title = new char[length + 15]; // create string to hold start time, end time and programme title
200 btime = localtime((time_t*)&event->time); //get programme start time
202 strftime(timeString, 9, "%0H:%0M - ", btime); // and format it as hh:mm -
204 strftime(timeString, 9, "%H:%M - ", btime); // and format it as hh:mm -
206 strcpy(title, timeString); // put it in our buffer
207 t = event->time + event->duration; //get programme end time
208 btime = localtime(&t);
210 strftime(timeString, 7, "%0H:%0M ", btime); // and format it as hh:mm -
212 strftime(timeString, 7, "%H:%M ", btime); // and format it as hh:mm -
214 strcat(title, timeString); // put it in our buffer
215 strcat(title, event->title); // then add the programme title
216 progTitle.setText(title); // sput this sring in our text box
217 length = strlen(event->description);
218 char* info = new char[length + 1]; // create programme detail string
219 strcpy(info, event->description);
220 progInfo.setText(info); // show programme detail string
221 // destroy dynamically allocated memory
228 // View::draw(); // draw pallet
230 DrawStyle transparent = DrawStyle(0, 0, 0, 0);
231 fillColour(transparent);
234 // Moved all the dynamic data drawing to a seperate function
236 // Display the status and key stuff at the bottom
238 UINT keyx = chanListbox.getRootBoxOffsetX();
239 UINT keyy = chanListbox.getRootBoxOffsetY() + chanListbox.getHeight() + 2;
240 rectangle(keyx, keyy, 605, getFontHeight() * 2 + 14, DrawStyle::DARKGREY);
245 w.nextSymbol = WSymbol::LEFTARROW;
246 w.setPosition(keyx + 1, keyy + 20);
249 w.nextSymbol = WSymbol::UP;
250 w.setPosition(keyx + 26, keyy + 3);
253 w.nextSymbol = WSymbol::DOWN;
254 w.setPosition(keyx + 26, keyy + 36);
257 w.nextSymbol = WSymbol::RIGHTARROW;
258 w.setPosition(keyx + 50, keyy + 20);
261 drawText(tr("OK"), keyx + 18, keyy + 20, DrawStyle::LIGHTTEXT);
263 rectangle(keyx + 72, keyy + 4, 104, getFontHeight() + 2, DrawStyle::RED);
264 drawText(tr("Page up"), keyx + 74, keyy + 5, DrawStyle::LIGHTTEXT);
266 rectangle(keyx + 72, keyy + getFontHeight() + 8, 104, getFontHeight() + 2, DrawStyle::GREEN);
267 drawText(tr("Page down"), keyx + 74, keyy + getFontHeight() + 9, DrawStyle::LIGHTTEXT);
269 rectangle(keyx + 180, keyy + 4, 104, getFontHeight() + 2, DrawStyle::YELLOW);
270 drawText(tr("-24 hours"), keyx + 182, keyy + 5, DrawStyle::LIGHTTEXT);
272 rectangle(keyx + 180, keyy + getFontHeight() + 8, 104, getFontHeight() + 2, DrawStyle::BLUE);
273 drawText(tr("+24 hours"), keyx + 182, keyy + getFontHeight() + 9, DrawStyle::LIGHTTEXT);
275 rectangle(keyx + 290, keyy + 4, 180, getFontHeight() + 2, DrawStyle::GREY);
276 drawText(tr("Guide / Back: Close"), keyx + 292 , keyy + 5, DrawStyle::LIGHTTEXT);
278 rectangle(keyx + 290, keyy + getFontHeight() + 8, 180, getFontHeight() + 2, DrawStyle::GREY);
279 DrawStyle red = DrawStyle(130, 0, 0);
280 drawText(tr("Rec: Set timer"), keyx + 292, keyy + getFontHeight() + 9, red);
282 rectangle(keyx + 474, keyy + 4, 128, getFontHeight() + 2, DrawStyle::GREY);
283 w.nextSymbol = WSymbol::PLAY;
284 w.setPosition(keyx + 476, keyy + 5);
286 drawText(tr("Sel channel"), keyx + 496, keyy + 5, DrawStyle::LIGHTTEXT);
288 rectangle(keyx + 474, keyy + getFontHeight() + 8, 128, getFontHeight() + 2, DrawStyle::GREY);
289 drawText(tr("Go: Preview"), keyx + 476, keyy + getFontHeight() + 9, DrawStyle::LIGHTTEXT);
292 // Draw all the dynamic data
296 void VEpg::drawData()
298 // Not doing View::draw() every time causes
299 // things not to be cleared off the surface properly
300 // So, blank out the data area first
303 chanListbox.getRootBoxOffsetX(),
304 chanListbox.getRootBoxOffsetY() - getFontHeight() - 3,
305 155 + WINDOW_WIDTH * MINUTE_SCALE,
306 chanListbox.getHeight() + getFontHeight() + 4,
311 chanName.draw(); // TODO this should be dealt with by vvideolive
316 // Set timer to redraw to move the current time bar
320 if (dt == 0) dt = 60;
322 Timers::getInstance()->setTimerT(this, 1, dt);
325 void VEpg::timercall(int clientReference)
328 boxstack->update(this);
331 int VEpg::handleCommand(int command)
337 { // cursor up the channel list
340 boxstack->update(this);
343 case Remote::DF_DOWN:
345 { // cursor down the channel list
346 Log::getInstance()->log("VEPG", Log::DEBUG, "Down start");
350 boxstack->update(this);
351 Log::getInstance()->log("VEPG", Log::DEBUG, "Down end");
355 case Remote::DF_LEFT:
357 { // cursor left through time
358 selTime = thisEvent.time - 1;
360 boxstack->update(this);
363 case Remote::DF_RIGHT:
366 // cursor right through time
367 selTime = thisEvent.time + thisEvent.duration;
369 boxstack->update(this);
374 // cursor up one page
375 chanListbox.pageUp();
377 boxstack->update(this);
382 // cursor down one page
383 chanListbox.pageDown();
385 boxstack->update(this);
390 // step forward 24 hours
391 selTime += 24 * 60 * 60;
393 boxstack->update(this);
398 // step forward 24 hours
399 selTime -= 24 * 60 * 60;
401 boxstack->update(this);
406 if (!chanList) return 2;
407 Log::getInstance()->log("VEPG", Log::DEBUG, "ID %lu TIME %lu DURATION %lu TITLE %s", thisEvent.id, thisEvent.time, thisEvent.duration, thisEvent.title);
408 VEpgSetTimer* vs = new VEpgSetTimer(&thisEvent, (*chanList)[chanListbox.getCurrentOption()]);
411 boxstack->update(vs);
418 if (!chanList) return 2;
420 // select programme and display menu TODO currently just changes to selected channel
422 currentChannelIndex = chanListbox.getCurrentOption();
426 Message* m = new Message(); // Must be done after this view deleted
429 m->message = Message::CHANNEL_CHANGE;
430 m->parameter = (*chanList)[currentChannelIndex]->number;
431 Command::getInstance()->postMessageNoLock(m);
436 if(command == Remote::GO)
438 // GO just changes channel in preview, PLAY changes channel and returns to normal TV
443 // return to normal TV mode
444 if (parent) // ptr check done in case being tested from videorec
446 Message* m = new Message(); // Must be done after this view deleted
449 m->message = Message::EPG_CLOSE;
450 Command::getInstance()->postMessageNoLock(m);
454 case Remote::CHANNELUP:
456 if (currentChannelIndex == (chanList->size() - 1)) // at the end
457 currentChannelIndex = 0;
459 ++currentChannelIndex;
463 Message* m = new Message(); // Must be done after this view deleted
466 m->message = Message::CHANNEL_CHANGE;
467 m->parameter = (*chanList)[currentChannelIndex]->number;
468 Command::getInstance()->postMessageNoLock(m);
475 case Remote::CHANNELDOWN:
477 if (currentChannelIndex == 0) // at the start
478 currentChannelIndex = chanList->size() - 1; // so go to end
480 --currentChannelIndex;
484 Message* m = new Message(); // Must be done after this view deleted
487 m->message = Message::CHANNEL_CHANGE;
488 m->parameter = (*chanList)[currentChannelIndex]->number;
489 Command::getInstance()->postMessageNoLock(m);
497 // stop command getting to any more views
501 void VEpg::drawgrid() // redraws grid and select programme
503 // draw the grid of programmes
506 time(&t); // set t = now
508 selTime = t; // don't allow cursor in the past
509 if(listTop != chanListbox.getTopOption())
511 // chanListbox has scrolled TODO speed up by changing only rows that have changed
512 listTop = chanListbox.getTopOption();
515 if ((selTime >= ltime + WINDOW_WIDTH * 60) || (selTime <= ltime))
517 // we have cursored back before left time of window
518 //TODO check that this and above don't happen together
519 ltime = prevHour(&selTime);
523 DrawStyle white = DrawStyle(255, 255, 255, 255);
528 strftime(timeString, 19, "%a %d %b", tms);
529 int timey = chanListbox.getRootBoxOffsetY() - getFontHeight() - 3;
531 drawTextRJ(timeString, timex - 10, timey, DrawStyle::LIGHTTEXT); // print date
532 strftime(timeString, 19, "%H:%M", tms);
533 drawText(timeString, timex, timey, DrawStyle::LIGHTTEXT); // print left time
535 rectangle(155, timey + getFontHeight(), 2, 7, white);
538 strftime(timeString, 19, "%H:%M", tms);
539 drawText(timeString, timex + 180, timey, DrawStyle::LIGHTTEXT); // print middle time
540 rectangle(335, timey + getFontHeight(), 2, 7, white);
543 strftime(timeString, 19, "%H:%M", tms);
544 drawText(timeString, timex + 360, timey, DrawStyle::LIGHTTEXT); // print right time
545 rectangle(515, timey + getFontHeight(), 2, 7, white);
546 // pointer to selTime
547 //rectangle(155 + (selTime - ltime) / 20, timey + getFontHeight(), 2, 7, DrawStyle(255, 50, 50, 255));
551 if ((t >= ltime) && (t < (ltime + 9000)))
553 rectangle(155 + (t - ltime) / 20, timey + getFontHeight(), 2, ((getFontHeight() + 2) * 7) + 7 + 2, DrawStyle::RED);
556 // TODO should the above two comditional statements be combined to avoid calling updateEventList() twice?
558 Event noevent; // an event to use if there are gaps in the epg
559 thisEvent.setdescription(tr("There are no programme details available for this period"));
560 thisEvent.duration = WINDOW_WIDTH * 60;
561 thisEvent.time = ltime;
562 thisEvent.settitle(tr("No programme details"));
564 bool swapColour = false; // alternate cell colour
565 bool currentRow = false;
566 int y = chanListbox.getRootBoxOffsetY() + 5; // vertical position of cell
567 DrawStyle bg, fg; // background colour of cells in grid
568 // for each displayed channel, find programmes that fall in 2.5 hour time window
569 for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
571 if (listTop + (int)listIndex >= chanListbox.getBottomOption())
572 continue; // ensure nothing populates grid below last channel
573 currentRow = (listTop + (int)listIndex == chanListbox.getCurrentOption());
574 noevent.time = ltime;
575 noevent.duration = WINDOW_WIDTH * 60;
576 noevent.settitle("");
577 paintCell(&noevent, y, DrawStyle::NOPROGRAMME, DrawStyle::LIGHTTEXT); // fill row with no programme colour to be painted ove with valid programmes
580 thisEvent.setdescription(tr("There are no programme details available for this period"));
581 thisEvent.duration = WINDOW_WIDTH * 60;
582 thisEvent.time = ltime;
583 thisEvent.settitle(tr("No programme details"));
586 if (eventLista[listIndex])
588 sort(eventLista[listIndex]->begin(), eventLista[listIndex]->end(), EventSorter());
589 for(e = 0; e < (eventLista[listIndex])->size(); e++) // step through events for this channel
591 fg = DrawStyle::LIGHTTEXT;
592 event = (*eventLista[listIndex])[e];
595 UINT end = event->time + event->duration; // programme end time
596 if(event->time >= UINT(ltime) + (WINDOW_WIDTH * 60)) // programme starts after RHS of window
597 continue; // that's enough of this channel's events
598 if(end <= UINT(ltime)) // programme ends before LHS of window
599 continue; // this event is before the window - let's try the next event
600 // this event is one we are interested in
601 bg = (swapColour)?DrawStyle::PROGRAMMEA:DrawStyle::PROGRAMMEB; // alternate cell colour
602 swapColour = !swapColour; // it wil be the other colour next time
603 if(event->time <= UINT(selTime) && end > UINT(selTime) && currentRow)
605 // this is the selected programme
606 thisEvent.setdescription(event->description);
607 thisEvent.duration = event->duration;
608 thisEvent.time = event->time;
609 thisEvent.settitle(event->title);
610 thisEvent.id = event->id;
611 if(thisEvent.id == 0)
613 bg = DrawStyle::SELECTHIGHLIGHT; // highlight cell
614 fg = DrawStyle::DARKTEXT;
618 if (currentRow && thisEvent.id == 0)
620 if (end <= UINT(selTime) && end > UINT(thisEvent.time))
621 thisEvent.time = end;
622 if (event->time > UINT(selTime) && event->time < thisEvent.time + thisEvent.duration)
623 thisEvent.duration = event->time - thisEvent.time;
626 paintCell(event, y, bg, fg);
632 // no event list for this channel. Already painted noevent colour so just highlight if selected
635 bg = DrawStyle::SELECTHIGHLIGHT; // highlight cell
636 fg = DrawStyle::DARKTEXT;
637 paintCell(&thisEvent, y, bg, fg);
641 bg = DrawStyle::NOPROGRAMME;
642 fg = DrawStyle::LIGHTTEXT;
643 noevent.settitle(tr("No programme details"));
644 paintCell(&noevent, y, bg, fg);
647 y += getFontHeight() + 2;
652 void VEpg::updateEventList()
654 if (!chanList) return;
656 for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
658 if(listTop + listIndex >= UINT(chanListbox.getBottomOption()))
660 chan = (*chanList)[listTop + listIndex];
662 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
666 void VEpg::setCurrentChannel()
668 chanName.setText((*chanList)[currentChannelIndex]->name);
671 chanName.getRootBoxRegion(&r);
672 boxstack->update(this, &r);
675 void VEpg::paintCell(Event* event, int yOffset, const DrawStyle& bg, const DrawStyle& fg)
678 w = x = 0; // keep compiler happy
681 h = getFontHeight(); // TODO if want border around text, need to increae this and wselectlist line height
682 UINT end = event->time + event->duration; // programme end time
683 if(event->time <= UINT(ltime) && end > UINT(ltime)) // spans start of displayed window
685 x = 155; // LHS of window
686 if (end > (UINT(ltime) + (WINDOW_WIDTH * 60)))
687 w = WINDOW_WIDTH * MINUTE_SCALE; // spans full 2 hour window
689 w = MINUTE_SCALE * (event->time + event->duration - ltime ) / 60; // get width of remaining programme
691 if((event->time >= UINT(ltime)) && (event->time <= UINT(ltime) + (WINDOW_WIDTH * 60))) // starts within window
693 x = 155 + (MINUTE_SCALE * (event->time - ltime) / 60);
694 w = MINUTE_SCALE * event->duration / 60;
695 //if (w > 155 + MINUTE_SCALE * WINDOW_WIDTH -x)
696 // w = w + x - 155 - MINUTE_SCALE * WINDOW_WIDTH; // ends outside window
698 if (w > 155 + WINDOW_WIDTH * MINUTE_SCALE - x)
699 w = 155 + WINDOW_WIDTH * MINUTE_SCALE -x; // limit cells to RHS of window
700 rectangle(x, y, w, h, bg);
701 char* tt = new char[strlen(event->title) + 1];
702 strcpy (tt, event->title);
704 unsigned int cur_length=1;
705 unsigned int text_max=strlen(tt);
707 for (textPos = 0; textPos <text_max; textPos+=cur_length)
709 wchar_t cur_char=getWChar(tt+textPos,&cur_length);
710 float thisCharWidth = charWidth(cur_char);
711 if (textWidth + thisCharWidth > w) // text will not fit in cell
715 textWidth += thisCharWidth;
717 char* tT = new char[textPos+1];
720 strncpy(tT, tt, textPos );
722 surface->drawText(tT, x+2, y, fg);
728 time_t VEpg::prevHour(time_t* t)
737 void VEpg::processMessage(Message* m)
739 if (m->message == Message::MOUSE_MOVE)
741 if (chanListbox.mouseMove((m->parameter>>16)-getScreenX(),(m->parameter&0xFFFF)-getScreenY()))
744 boxstack->update(this);
747 else if (m->message == Message::MOUSE_LBDOWN)
749 if (chanListbox.mouseLBDOWN((m->parameter>>16)-getScreenX(),(m->parameter&0xFFFF)-getScreenY()))
751 boxstack->handleCommand(Remote::OK); //simulate OK press
755 //check if press is outside this view! then simulate cancel
756 int x=(m->parameter>>16)-getScreenX();
757 int y=(m->parameter&0xFFFF)-getScreenY();
758 int keyx = chanListbox.getRootBoxOffsetX();
759 int keyy = chanListbox.getRootBoxOffsetY() + chanListbox.getHeight() + 2;
761 if (x<0 || y <0 || x>(int)getWidth() || y>(int)getHeight())
763 boxstack->handleCommand(Remote::BACK); //simulate cancel press
765 else if (x>=(keyx+72) && y>=(keyy+4) &&x<=(keyx+72+104) &&y<=(keyy+4+getFontHeight() + 2))
767 boxstack->handleCommand(Remote::RED);
769 else if (x>=(keyx+72) && y>=(keyy+ getFontHeight() + 8) &&x<=(keyx+72+104) &&y<=(keyy+8+2*getFontHeight() + 2))
771 boxstack->handleCommand(Remote::GREEN);
773 else if (x>=(keyx+180) && y>=(keyy+4) &&x<=(keyx+180+104) &&y<=(keyy+4+getFontHeight() + 2))
775 boxstack->handleCommand(Remote::YELLOW);
777 else if (x>=(keyx+180) && y>=(keyy+ getFontHeight() + 8) &&x<=(keyx+180+104) &&y<=(keyy+8+2*getFontHeight() + 2))
779 boxstack->handleCommand(Remote::BLUE);
781 else if (x>=(keyx+290) && y>=(keyy+4) &&x<=(keyx+180+290) &&y<=(keyy+4+getFontHeight() + 2))
783 boxstack->handleCommand(Remote::BACK);
785 else if (x>=(keyx+290) && y>=(keyy+ getFontHeight() + 8) &&x<=(keyx+290+180) &&y<=(keyy+8+2*getFontHeight() + 2))
787 boxstack->handleCommand(Remote::RECORD);
789 else if (x>=(keyx+474) && y>=(keyy+4) &&x<=(keyx+128+474) &&y<=(keyy+4+getFontHeight() + 2))
791 boxstack->handleCommand(Remote::PLAY);
793 else if (x>=(keyx+474) && y>=(keyy+ getFontHeight() + 8) &&x<=(keyx+238+474) &&y<=(keyy+8+2*getFontHeight() + 2))
795 boxstack->handleCommand(Remote::GO);
797 else if ( x>=(chanListbox.getRootBoxOffsetX())
798 && y>=(chanListbox.getRootBoxOffsetY() + 5)
799 // &&x<=(chanListbox.getOffsetX()+155 + WINDOW_WIDTH * MINUTE_SCALE)
800 &&y<=(chanListbox.getRootBoxOffsetY() - getFontHeight()
801 - 3+(int)chanListbox.getHeight() + getFontHeight() + 3)
804 int cy=y-(chanListbox.getRootBoxOffsetY() + 5);
805 int row=cy/(getFontHeight()+2);
806 int clistTop = chanListbox.getTopOption();
807 chanListbox.hintSetCurrent(clistTop+row);
809 time_t ttime = cx*60/MINUTE_SCALE+ltime;
810 //x = 155 + (MINUTE_SCALE * (event->time - ltime) / 60);
814 boxstack->update(this);