[epg] fix: hide progress indicator if go forward in time
[vuplus_xbmc] / xbmc / epg / GUIEPGGridContainer.cpp
1 /*
2  *      Copyright (C) 2012-2013 Team XBMC
3  *      http://xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include "guilib/Key.h"
22 #include "guilib/GUIControlFactory.h"
23 #include "guilib/GUIListItem.h"
24 #include "guilib/GUIFontManager.h"
25 #include "guilib/LocalizeStrings.h"
26 #include "guilib/DirtyRegion.h"
27 #include <tinyxml.h>
28 #include "utils/log.h"
29 #include "utils/MathUtils.h"
30 #include "utils/Variant.h"
31 #include "threads/SystemClock.h"
32 #include "GUIInfoManager.h"
33
34 #include "epg/Epg.h"
35 #include "pvr/channels/PVRChannel.h"
36
37 #include "GUIEPGGridContainer.h"
38
39 using namespace PVR;
40 using namespace EPG;
41 using namespace std;
42
43 #define SHORTGAP     5 // how many blocks is considered a short-gap in nav logic
44 #define MINSPERBLOCK 5 /// would be nice to offer zooming of busy schedules /// performance cost to increase resolution 5 fold?
45 #define BLOCKJUMP    4 // how many blocks are jumped with each analogue scroll action
46
47 CGUIEPGGridContainer::CGUIEPGGridContainer(int parentID, int controlID, float posX, float posY, float width,
48                                            float height, ORIENTATION orientation, int scrollTime,
49                                            int preloadItems, int timeBlocks, int rulerUnit, const CTextureInfo& progressIndicatorTexture)
50     : IGUIContainer(parentID, controlID, posX, posY, width, height)
51     , m_guiProgressIndicatorTexture(posX, posY, width, height, progressIndicatorTexture)
52 {
53   ControlType             = GUICONTAINER_EPGGRID;
54   m_blocksPerPage         = timeBlocks;
55   m_rulerUnit             = rulerUnit;
56   m_channelCursor         = 0;
57   m_blockCursor           = 0;
58   m_channelOffset         = 0;
59   m_blockOffset           = 0;
60   m_channelScrollOffset   = 0;
61   m_channelScrollSpeed    = 0;
62   m_channelScrollLastTime = 0;
63   m_programmeScrollOffset = 0;
64   m_programmeScrollSpeed  = 0;
65   m_programmeScrollLastTime  = 0;
66   m_scrollTime            = scrollTime ? scrollTime : 1;
67   m_item                  = NULL;
68   m_lastItem              = NULL;
69   m_lastChannel           = NULL;
70   m_orientation           = orientation;
71   m_programmeLayout       = NULL;
72   m_focusedProgrammeLayout= NULL;
73   m_channelLayout         = NULL;
74   m_focusedChannelLayout  = NULL;
75   m_rulerLayout           = NULL;
76   m_rulerPosX             = 0;
77   m_rulerPosY             = 0;
78   m_rulerHeight           = 0;
79   m_rulerWidth            = 0;
80   m_channelPosX           = 0;
81   m_channelPosY           = 0;
82   m_channelHeight         = 0;
83   m_channelWidth          = 0;
84   m_gridPosX              = 0;
85   m_gridPosY              = 0;
86   m_gridWidth             = 0;
87   m_gridHeight            = 0;
88   m_blockSize             = 0;
89   m_analogScrollCount     = 0;
90   m_cacheChannelItems     = preloadItems;
91   m_cacheRulerItems       = preloadItems;
92   m_cacheProgrammeItems   = preloadItems;
93 }
94
95 CGUIEPGGridContainer::~CGUIEPGGridContainer(void)
96 {
97   Reset();
98 }
99
100 void CGUIEPGGridContainer::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
101 {
102   ValidateOffset();
103
104   if (m_bInvalidated)
105     UpdateLayout();
106
107   UpdateScrollOffset(currentTime);
108   ProcessChannels(currentTime, dirtyregions);
109   ProcessRuler(currentTime, dirtyregions);
110   ProcessProgrammeGrid(currentTime, dirtyregions);
111   ProcessProgressIndicator(currentTime, dirtyregions);
112
113   CGUIControl::Process(currentTime, dirtyregions);
114 }
115
116 void CGUIEPGGridContainer::Render()
117 {
118   RenderChannels();
119   RenderRuler();
120   RenderProgrammeGrid();
121   RenderProgressIndicator();
122
123   CGUIControl::Render();
124 }
125
126 void CGUIEPGGridContainer::ProcessChannels(unsigned int currentTime, CDirtyRegionList &dirtyregions)
127 {
128   if (!m_focusedChannelLayout || !m_channelLayout)
129     return;
130
131   int chanOffset  = (int)floorf(m_channelScrollOffset / m_programmeLayout->Size(m_orientation));
132
133   int cacheBeforeChannel, cacheAfterChannel;
134   GetChannelCacheOffsets(cacheBeforeChannel, cacheAfterChannel);
135
136   // Free memory not used on screen
137   if ((int)m_channelItems.size() > m_channelsPerPage + cacheBeforeChannel + cacheAfterChannel)
138     FreeChannelMemory(CorrectOffset(chanOffset - cacheBeforeChannel, 0), CorrectOffset(chanOffset + m_channelsPerPage + 1 + cacheAfterChannel, 0));
139
140   CPoint originChannel = CPoint(m_channelPosX, m_channelPosY) + m_renderOffset;
141   float pos = (m_orientation == VERTICAL) ? originChannel.y : originChannel.x;
142   float end = (m_orientation == VERTICAL) ? m_posY + m_height : m_posX + m_width;
143
144   // we offset our draw position to take into account scrolling and whether or not our focused
145   // item is offscreen "above" the list.
146   float drawOffset = (chanOffset - cacheBeforeChannel) * m_channelLayout->Size(m_orientation) - m_channelScrollOffset;
147   if (m_channelOffset + m_channelCursor < chanOffset)
148     drawOffset += m_focusedChannelLayout->Size(m_orientation) - m_channelLayout->Size(m_orientation);
149   pos += drawOffset;
150   end += cacheAfterChannel * m_channelLayout->Size(m_orientation);
151
152   int current = chanOffset;// - cacheBeforeChannel;
153   while (pos < end && !m_channelItems.empty())
154   {
155     int itemNo = CorrectOffset(current, 0);
156     if (itemNo >= (int)m_channelItems.size())
157       break;
158     bool focused = (current == m_channelOffset + m_channelCursor);
159     if (itemNo >= 0)
160     {
161       CGUIListItemPtr item = m_channelItems[itemNo];
162       // process our item
163       if (m_orientation == VERTICAL)
164         ProcessItem(originChannel.x, pos, item.get(), m_lastItem, focused, m_channelLayout, m_focusedChannelLayout, currentTime, dirtyregions);
165       else
166         ProcessItem(pos, originChannel.y, item.get(), m_lastItem, focused, m_channelLayout, m_focusedChannelLayout, currentTime, dirtyregions);
167     }
168     // increment our position
169     pos += focused ? m_focusedChannelLayout->Size(m_orientation) : m_channelLayout->Size(m_orientation);
170     current++;
171   }
172 }
173
174 void CGUIEPGGridContainer::RenderChannels()
175 {
176   if (!m_focusedChannelLayout || !m_channelLayout)
177     return;
178
179   int chanOffset  = (int)floorf(m_channelScrollOffset / m_programmeLayout->Size(m_orientation));
180
181   /// Render channel names
182   int cacheBeforeChannel, cacheAfterChannel;
183   GetChannelCacheOffsets(cacheBeforeChannel, cacheAfterChannel);
184
185   if (m_orientation == VERTICAL)
186     g_graphicsContext.SetClipRegion(m_channelPosX, m_channelPosY, m_channelWidth, m_gridHeight);
187   else
188     g_graphicsContext.SetClipRegion(m_channelPosX, m_channelPosY, m_gridWidth, m_channelHeight);
189
190   CPoint originChannel = CPoint(m_channelPosX, m_channelPosY) + m_renderOffset;
191   float pos = (m_orientation == VERTICAL) ? originChannel.y : originChannel.x;
192   float end = (m_orientation == VERTICAL) ? m_posY + m_height : m_posX + m_width;
193
194   // we offset our draw position to take into account scrolling and whether or not our focused
195   // item is offscreen "above" the list.
196   float drawOffset = (chanOffset - cacheBeforeChannel) * m_channelLayout->Size(m_orientation) - m_channelScrollOffset;
197   if (m_channelOffset + m_channelCursor < chanOffset)
198     drawOffset += m_focusedChannelLayout->Size(m_orientation) - m_channelLayout->Size(m_orientation);
199   pos += drawOffset;
200   end += cacheAfterChannel * m_channelLayout->Size(m_orientation);
201
202   float focusedPos = 0;
203   CGUIListItemPtr focusedItem;
204   int current = chanOffset;// - cacheBeforeChannel;
205   while (pos < end && !m_channelItems.empty())
206   {
207     int itemNo = CorrectOffset(current, 0);
208     if (itemNo >= (int)m_channelItems.size())
209       break;
210     bool focused = (current == m_channelOffset + m_channelCursor);
211     if (itemNo >= 0)
212     {
213       CGUIListItemPtr item = m_channelItems[itemNo];
214       // render our item
215       if (focused)
216       {
217         focusedPos = pos;
218         focusedItem = item;
219       }
220       else
221       {
222         if (m_orientation == VERTICAL)
223           RenderItem(originChannel.x, pos, item.get(), false);
224         else
225           RenderItem(pos, originChannel.y, item.get(), false);
226       }
227     }
228     // increment our position
229     pos += focused ? m_focusedChannelLayout->Size(m_orientation) : m_channelLayout->Size(m_orientation);
230     current++;
231   }
232   // render focused item last so it can overlap other items
233   if (focusedItem)
234   {
235     if (m_orientation == VERTICAL)
236       RenderItem(originChannel.x, focusedPos, focusedItem.get(), true);
237     else
238       RenderItem(focusedPos, originChannel.y, focusedItem.get(), true);
239   }
240   g_graphicsContext.RestoreClipRegion();
241 }
242
243 void CGUIEPGGridContainer::ProcessRuler(unsigned int currentTime, CDirtyRegionList &dirtyregions)
244 {
245   if (!m_rulerLayout || m_rulerItems.size()<=1 || (m_gridEnd - m_gridStart) == CDateTimeSpan(0, 0, 0, 0))
246     return;
247
248   int rulerOffset = (int)floorf(m_programmeScrollOffset / m_blockSize);
249   CGUIListItemPtr item = m_rulerItems[0];
250   item->SetLabel(m_rulerItems[rulerOffset/m_rulerUnit+1]->GetLabel2());
251   CGUIListItem* lastitem = NULL; // dummy pointer needed to be passed as reference to ProcessItem() method
252   ProcessItem(m_posX, m_posY, item.get(), lastitem, false, m_rulerLayout, m_rulerLayout, currentTime, dirtyregions, (m_orientation == VERTICAL ? m_channelWidth : m_channelHeight));
253
254   // render ruler items
255   int cacheBeforeRuler, cacheAfterRuler;
256   GetProgrammeCacheOffsets(cacheBeforeRuler, cacheAfterRuler);
257
258   // Free memory not used on screen
259   if ((int)m_rulerItems.size() > m_blocksPerPage + cacheBeforeRuler + cacheAfterRuler)
260     FreeRulerMemory(CorrectOffset(rulerOffset/m_rulerUnit+1 - cacheBeforeRuler, 0), CorrectOffset(rulerOffset/m_rulerUnit+1 + m_blocksPerPage + 1 + cacheAfterRuler, 0));
261
262   CPoint originRuler = CPoint(m_rulerPosX, m_rulerPosY) + m_renderOffset;
263   float pos = (m_orientation == VERTICAL) ? originRuler.x : originRuler.y;
264   float end = (m_orientation == VERTICAL) ? m_posX + m_width : m_posY + m_height;
265   float drawOffset = (rulerOffset - cacheBeforeRuler) * m_blockSize - m_programmeScrollOffset;
266   pos += drawOffset;
267   end += cacheAfterRuler * m_rulerLayout->Size(m_orientation == VERTICAL ? HORIZONTAL : VERTICAL);
268
269   if (rulerOffset % m_rulerUnit != 0)
270   {
271     /* first ruler marker starts before current view */
272     int startBlock = rulerOffset - 1;
273
274     while (startBlock % m_rulerUnit != 0)
275       startBlock--;
276
277     int missingSection = rulerOffset - startBlock;
278
279     pos -= missingSection * m_blockSize;
280   }
281   while (pos < end && (rulerOffset/m_rulerUnit+1) < (int)m_rulerItems.size())
282   {
283     item = m_rulerItems[rulerOffset/m_rulerUnit+1];
284     if (m_orientation == VERTICAL)
285     {
286       ProcessItem(pos, originRuler.y, item.get(), lastitem, false, m_rulerLayout, m_rulerLayout, currentTime, dirtyregions, m_rulerWidth);
287       pos += m_rulerWidth;
288     }
289     else
290     {
291       ProcessItem(originRuler.x, pos, item.get(), lastitem, false, m_rulerLayout, m_rulerLayout, currentTime, dirtyregions, m_rulerWidth);
292       pos += m_rulerHeight;
293     }
294     rulerOffset += m_rulerUnit;
295   }
296 }
297
298 void CGUIEPGGridContainer::RenderRuler()
299 {
300   if (!m_rulerLayout || m_rulerItems.size()<=1 || (m_gridEnd - m_gridStart) == CDateTimeSpan(0, 0, 0, 0))
301     return;
302
303   int rulerOffset = (int)floorf(m_programmeScrollOffset / m_blockSize);
304
305   /// Render single ruler item with date of selected programme
306   g_graphicsContext.SetClipRegion(m_posX, m_posY, m_width, m_height);
307   CGUIListItemPtr item = m_rulerItems[0];
308   RenderItem(m_posX, m_posY, item.get(), false);
309   g_graphicsContext.RestoreClipRegion();
310
311   // render ruler items
312   int cacheBeforeRuler, cacheAfterRuler;
313   GetProgrammeCacheOffsets(cacheBeforeRuler, cacheAfterRuler);
314
315   if (m_orientation == VERTICAL)
316     g_graphicsContext.SetClipRegion(m_rulerPosX, m_rulerPosY, m_gridWidth, m_rulerHeight);
317   else
318     g_graphicsContext.SetClipRegion(m_rulerPosX, m_rulerPosY, m_rulerWidth, m_gridHeight);
319
320   CPoint originRuler = CPoint(m_rulerPosX, m_rulerPosY) + m_renderOffset;
321   float pos = (m_orientation == VERTICAL) ? originRuler.x : originRuler.y;
322   float end = (m_orientation == VERTICAL) ? m_posX + m_width : m_posY + m_height;
323   float drawOffset = (rulerOffset - cacheBeforeRuler) * m_blockSize - m_programmeScrollOffset;
324   pos += drawOffset;
325   end += cacheAfterRuler * m_rulerLayout->Size(m_orientation == VERTICAL ? HORIZONTAL : VERTICAL);
326
327   if (rulerOffset % m_rulerUnit != 0)
328   {
329     /* first ruler marker starts before current view */
330     int startBlock = rulerOffset - 1;
331
332     while (startBlock % m_rulerUnit != 0)
333       startBlock--;
334
335     int missingSection = rulerOffset - startBlock;
336
337     pos -= missingSection * m_blockSize;
338   }
339   while (pos < end && (rulerOffset/m_rulerUnit+1) < (int)m_rulerItems.size())
340   {
341     item = m_rulerItems[rulerOffset/m_rulerUnit+1];
342     if (m_orientation == VERTICAL)
343     {
344       RenderItem(pos, originRuler.y, item.get(), false);
345       pos += m_rulerWidth;
346     }
347     else
348     {
349       RenderItem(originRuler.x, pos, item.get(), false);
350       pos += m_rulerHeight;
351     }
352     rulerOffset += m_rulerUnit;
353   }
354   g_graphicsContext.RestoreClipRegion();
355 }
356
357 void CGUIEPGGridContainer::ProcessProgrammeGrid(unsigned int currentTime, CDirtyRegionList &dirtyregions)
358 {
359   if (!m_focusedProgrammeLayout || !m_programmeLayout || m_rulerItems.size()<=1 || (m_gridEnd - m_gridStart) == CDateTimeSpan(0, 0, 0, 0))
360     return;
361
362   int blockOffset = (int)floorf(m_programmeScrollOffset / m_blockSize);
363   int chanOffset  = (int)floorf(m_channelScrollOffset / m_programmeLayout->Size(m_orientation));
364
365   int cacheBeforeProgramme, cacheAfterProgramme;
366   GetProgrammeCacheOffsets(cacheBeforeProgramme, cacheAfterProgramme);
367
368   CPoint originProgramme = CPoint(m_gridPosX, m_gridPosY) + m_renderOffset;
369   float posA = (m_orientation != VERTICAL) ? originProgramme.y : originProgramme.x;
370   float endA = (m_orientation != VERTICAL) ? m_posY + m_height : m_posX + m_width;
371   float posB = (m_orientation == VERTICAL) ? originProgramme.y : originProgramme.x;
372   float endB = (m_orientation == VERTICAL) ? m_gridPosY + m_gridHeight : m_posX + m_width;
373   endA += cacheAfterProgramme * m_blockSize;
374
375   float DrawOffsetA = blockOffset * m_blockSize - m_programmeScrollOffset;
376   posA += DrawOffsetA;
377   float DrawOffsetB = (chanOffset - cacheBeforeProgramme) * m_channelLayout->Size(m_orientation) - m_channelScrollOffset;
378   posB += DrawOffsetB;
379
380   int channel = chanOffset;
381
382   while (posB < endB && !m_channelItems.empty())
383   {
384     if (channel >= (int)m_channelItems.size())
385       break;
386
387     // Free memory not used on screen
388     FreeProgrammeMemory(channel, CorrectOffset(blockOffset - cacheBeforeProgramme, 0), CorrectOffset(blockOffset + m_ProgrammesPerPage + 1 + cacheAfterProgramme, 0));
389
390     int block = blockOffset;
391     float posA2 = posA;
392
393     CGUIListItemPtr item = m_gridIndex[channel][block].item;
394     if (blockOffset > 0 && item == m_gridIndex[channel][blockOffset-1].item)
395     {
396       /* first program starts before current view */
397       int startBlock = blockOffset - 1;
398       while (startBlock >= 0 && m_gridIndex[channel][startBlock].item == item)
399         startBlock--;
400
401       block = startBlock + 1;
402       int missingSection = blockOffset - block;
403       posA2 -= missingSection * m_blockSize;
404     }
405
406     while (posA2 < endA && !m_programmeItems.empty())   // FOR EACH ITEM ///////////////
407     {
408       item = m_gridIndex[channel][block].item;
409       if (!item || !item.get()->IsFileItem())
410         break;
411
412       bool focused = (channel == m_channelOffset + m_channelCursor) && (item == m_gridIndex[m_channelOffset + m_channelCursor][m_blockOffset + m_blockCursor].item);
413
414       if (m_orientation == VERTICAL)
415         ProcessItem(posA2, posB, item.get(), m_lastChannel, focused, m_programmeLayout, m_focusedProgrammeLayout, currentTime, dirtyregions, m_gridIndex[channel][block].width);
416       else
417         ProcessItem(posB, posA2, item.get(), m_lastChannel, focused, m_programmeLayout, m_focusedProgrammeLayout, currentTime, dirtyregions, m_gridIndex[channel][block].height);
418
419       // increment our X position
420       if (m_orientation == VERTICAL)
421       {
422         posA2 += m_gridIndex[channel][block].width; // assumes focused & unfocused layouts have equal length
423         block += (int)(m_gridIndex[channel][block].width / m_blockSize);
424       }
425       else
426       {
427         posA2 += m_gridIndex[channel][block].height; // assumes focused & unfocused layouts have equal length
428         block += (int)(m_gridIndex[channel][block].height / m_blockSize);
429       }
430     }
431
432     // increment our Y position
433     channel++;
434     posB += m_orientation == VERTICAL ? m_channelHeight : m_channelWidth;
435   }
436 }
437
438 void CGUIEPGGridContainer::RenderProgrammeGrid()
439 {
440   if (!m_focusedProgrammeLayout || !m_programmeLayout || m_rulerItems.size()<=1 || (m_gridEnd - m_gridStart) == CDateTimeSpan(0, 0, 0, 0))
441     return;
442
443   int blockOffset = (int)floorf(m_programmeScrollOffset / m_blockSize);
444   int chanOffset  = (int)floorf(m_channelScrollOffset / m_programmeLayout->Size(m_orientation));
445
446   /// Render programmes
447   int cacheBeforeProgramme, cacheAfterProgramme;
448   GetProgrammeCacheOffsets(cacheBeforeProgramme, cacheAfterProgramme);
449
450   g_graphicsContext.SetClipRegion(m_gridPosX, m_gridPosY, m_gridWidth, m_gridHeight);
451   CPoint originProgramme = CPoint(m_gridPosX, m_gridPosY) + m_renderOffset;
452   float posA = (m_orientation != VERTICAL) ? originProgramme.y : originProgramme.x;
453   float endA = (m_orientation != VERTICAL) ? m_posY + m_height : m_posX + m_width;
454   float posB = (m_orientation == VERTICAL) ? originProgramme.y : originProgramme.x;
455   float endB = (m_orientation == VERTICAL) ? m_gridPosY + m_gridHeight : m_posX + m_width;
456   endA += cacheAfterProgramme * m_blockSize;
457
458   float DrawOffsetA = blockOffset * m_blockSize - m_programmeScrollOffset;
459   posA += DrawOffsetA;
460   float DrawOffsetB = (chanOffset - cacheBeforeProgramme) * m_channelLayout->Size(m_orientation) - m_channelScrollOffset;
461   posB += DrawOffsetB;
462
463   int channel = chanOffset;
464
465   float focusedPosX = 0;
466   float focusedPosY = 0;
467   CGUIListItemPtr focusedItem;
468   while (posB < endB && !m_channelItems.empty())
469   {
470     if (channel >= (int)m_channelItems.size())
471       break;
472
473     int block = blockOffset;
474     float posA2 = posA;
475
476     CGUIListItemPtr item = m_gridIndex[channel][block].item;
477     if (blockOffset > 0 && item == m_gridIndex[channel][blockOffset-1].item)
478     {
479       /* first program starts before current view */
480       int startBlock = blockOffset - 1;
481       while (startBlock >= 0 && m_gridIndex[channel][startBlock].item == item)
482         startBlock--;
483
484       block = startBlock + 1;
485       int missingSection = blockOffset - block;
486       posA2 -= missingSection * m_blockSize;
487     }
488
489     while (posA2 < endA && !m_programmeItems.empty())   // FOR EACH ITEM ///////////////
490     {
491       item = m_gridIndex[channel][block].item;
492       if (!item || !item.get()->IsFileItem())
493         break;
494
495       bool focused = (channel == m_channelOffset + m_channelCursor) && (item == m_gridIndex[m_channelOffset + m_channelCursor][m_blockOffset + m_blockCursor].item);
496
497       // render our item
498       if (focused)
499       {
500         if (m_orientation == VERTICAL)
501         {
502           focusedPosX = posA2;
503           focusedPosY = posB;
504         }
505         else
506         {
507           focusedPosX = posB;
508           focusedPosY = posA2;
509         }
510         focusedItem = item;
511       }
512       else
513       {
514         if (m_orientation == VERTICAL)
515           RenderItem(posA2, posB, item.get(), focused);
516         else
517           RenderItem(posB, posA2, item.get(), focused);
518       }
519
520       // increment our X position
521       if (m_orientation == VERTICAL)
522       {
523         posA2 += m_gridIndex[channel][block].width; // assumes focused & unfocused layouts have equal length
524         block += (int)(m_gridIndex[channel][block].width / m_blockSize);
525       }
526       else
527       {
528         posA2 += m_gridIndex[channel][block].height; // assumes focused & unfocused layouts have equal length
529         block += (int)(m_gridIndex[channel][block].height / m_blockSize);
530       }
531     }
532
533     // increment our Y position
534     channel++;
535     posB += m_orientation == VERTICAL ? m_channelHeight : m_channelWidth;
536   }
537
538   // and render the focused item last (for overlapping purposes)
539   if (focusedItem)
540     RenderItem(focusedPosX, focusedPosY, focusedItem.get(), true);
541
542   g_graphicsContext.RestoreClipRegion();
543 }
544
545 void CGUIEPGGridContainer::ProcessProgressIndicator(unsigned int currentTime, CDirtyRegionList &dirtyregions)
546 {
547   CPoint originRuler = CPoint(m_rulerPosX, m_rulerPosY) + m_renderOffset;
548   float width = ((CDateTime::GetUTCDateTime() - m_gridStart).GetSecondsTotal() * m_blockSize) / (MINSPERBLOCK * 60) - m_programmeScrollOffset;
549
550   if (width > 0)
551   {
552     m_guiProgressIndicatorTexture.SetVisible(true);
553     m_guiProgressIndicatorTexture.SetPosition(originRuler.x, originRuler.y);
554     if (m_orientation == VERTICAL)
555       m_guiProgressIndicatorTexture.SetWidth(width);
556     else
557       m_guiProgressIndicatorTexture.SetHeight(width);
558   }
559   else
560   {
561     m_guiProgressIndicatorTexture.SetVisible(false);
562   }
563   
564   m_guiProgressIndicatorTexture.Process(currentTime);
565 }
566
567 void CGUIEPGGridContainer::RenderProgressIndicator()
568 {
569   bool render = false;
570   
571   if (m_orientation == VERTICAL)
572     render = g_graphicsContext.SetClipRegion(m_rulerPosX, m_rulerPosY, m_gridWidth, m_height);
573   else
574     render = g_graphicsContext.SetClipRegion(m_rulerPosX, m_rulerPosY, m_width, m_gridHeight);
575   
576   if(render)
577   {
578     m_guiProgressIndicatorTexture.Render();
579     g_graphicsContext.RestoreClipRegion();
580   }
581 }
582
583 void CGUIEPGGridContainer::ProcessItem(float posX, float posY, CGUIListItem* item, CGUIListItem *&lastitem,
584   bool focused, CGUIListItemLayout* normallayout, CGUIListItemLayout* focusedlayout,
585   unsigned int currentTime, CDirtyRegionList &dirtyregions, float resize /* = -1.0f */)
586 {
587   if (!normallayout || !focusedlayout) return;
588
589   // set the origin
590   g_graphicsContext.SetOrigin(posX, posY);
591
592   if (m_bInvalidated)
593     item->SetInvalid();
594   if (focused)
595   {
596     if (!item->GetFocusedLayout())
597     {
598       CGUIListItemLayout *layout = new CGUIListItemLayout(*focusedlayout);
599       if (resize != -1.0f)
600       {
601         if (m_orientation == VERTICAL)
602           layout->SetWidth(resize);
603         else
604           layout->SetHeight(resize);
605       }
606       item->SetFocusedLayout(layout);
607     }
608     if (item->GetFocusedLayout())
609     {
610       if (item != lastitem || !HasFocus())
611       {
612         item->GetFocusedLayout()->SetFocusedItem(0);
613       }
614       if (item != lastitem && HasFocus())
615       {
616         item->GetFocusedLayout()->ResetAnimation(ANIM_TYPE_UNFOCUS);
617         unsigned int subItem = 1;
618         if (lastitem && lastitem->GetFocusedLayout())
619           subItem = lastitem->GetFocusedLayout()->GetFocusedItem();
620         item->GetFocusedLayout()->SetFocusedItem(subItem ? subItem : 1);
621       }
622       item->GetFocusedLayout()->Process(item,m_parentID,currentTime,dirtyregions);
623     }
624     lastitem = item;
625   }
626   else
627   {
628     if (item->GetFocusedLayout())
629       item->GetFocusedLayout()->SetFocusedItem(0);  // focus is not set
630     if (!item->GetLayout())
631     {
632       CGUIListItemLayout *layout = new CGUIListItemLayout(*normallayout);
633       if (resize != -1.0f)
634       {
635         if (m_orientation == VERTICAL)
636           layout->SetWidth(resize);
637         else
638           layout->SetHeight(resize);
639       }
640       item->SetLayout(layout);
641     }
642     if (item->GetFocusedLayout() && item->GetFocusedLayout()->IsAnimating(ANIM_TYPE_UNFOCUS))
643       item->GetFocusedLayout()->Process(item,m_parentID,currentTime,dirtyregions);
644     else if (item->GetLayout())
645       item->GetLayout()->Process(item,m_parentID,currentTime,dirtyregions);
646   }
647   g_graphicsContext.RestoreOrigin();
648 }
649
650 void CGUIEPGGridContainer::RenderItem(float posX, float posY, CGUIListItem *item, bool focused)
651 {
652   // set the origin
653   g_graphicsContext.SetOrigin(posX, posY);
654
655   if (focused)
656   {
657     if (item->GetFocusedLayout())
658       item->GetFocusedLayout()->Render(item, m_parentID);
659   }
660   else
661   {
662     if (item->GetFocusedLayout() && item->GetFocusedLayout()->IsAnimating(ANIM_TYPE_UNFOCUS))
663       item->GetFocusedLayout()->Render(item, m_parentID);
664     else if (item->GetLayout())
665       item->GetLayout()->Render(item, m_parentID);
666   }
667   g_graphicsContext.RestoreOrigin();
668 }
669
670 bool CGUIEPGGridContainer::OnAction(const CAction &action)
671 {
672   switch (action.GetID())
673   {
674   case ACTION_MOVE_LEFT:
675   case ACTION_MOVE_RIGHT:
676   case ACTION_MOVE_DOWN:
677   case ACTION_MOVE_UP:
678     { // use base class implementation
679
680       return CGUIControl::OnAction(action);
681     }
682
683     break;
684   case ACTION_PAGE_UP:
685     {
686       if (m_orientation == VERTICAL)
687       {
688         if (m_channelOffset == 0)
689         { // already on the first page, so move to the first item
690           SetChannel(0);
691         }
692         else
693         { // scroll up to the previous page
694           ChannelScroll(-m_channelsPerPage);
695         }
696       }
697       else
698         ProgrammesScroll(-m_blocksPerPage/4);
699
700       return true;
701     }
702
703     break;
704   case ACTION_PAGE_DOWN:
705     {
706       if (m_orientation == VERTICAL)
707       {
708         if (m_channelOffset == m_channels - m_channelsPerPage || m_channels < m_channelsPerPage)
709         { // already at the last page, so move to the last item.
710           SetChannel(m_channels - m_channelOffset - 1);
711         }
712         else
713         { // scroll down to the next page
714           ChannelScroll(m_channelsPerPage);
715         }
716       }
717       else
718         ProgrammesScroll(m_blocksPerPage/4);
719
720       return true;
721     }
722
723     break;
724
725     // smooth scrolling (for analog controls)
726   case ACTION_TELETEXT_RED:
727   case ACTION_TELETEXT_GREEN:
728   case ACTION_SCROLL_UP: // left horizontal scrolling
729     {
730       int blocksToJump = action.GetID() == ACTION_TELETEXT_RED ? m_blocksPerPage/2 : m_blocksPerPage/4;
731
732       m_analogScrollCount += action.GetAmount() * action.GetAmount();
733       bool handled = false;
734
735       while (m_analogScrollCount > 0.4)
736       {
737         handled = true;
738         m_analogScrollCount -= 0.4f;
739
740         if (m_blockOffset > 0 && m_blockCursor <= m_blocksPerPage / 2)
741         {
742           ProgrammesScroll(-blocksToJump);
743         }
744         else if (m_blockCursor > blocksToJump)
745         {
746           SetBlock(m_blockCursor - blocksToJump);
747         }
748       }
749
750       return handled;
751     }
752
753     break;
754
755   case ACTION_TELETEXT_BLUE:
756   case ACTION_TELETEXT_YELLOW:
757   case ACTION_SCROLL_DOWN: // right horizontal scrolling
758     {
759       int blocksToJump = action.GetID() == ACTION_TELETEXT_BLUE ? m_blocksPerPage/2 : m_blocksPerPage/4;
760
761       m_analogScrollCount += action.GetAmount() * action.GetAmount();
762       bool handled = false;
763
764       while (m_analogScrollCount > 0.4)
765       {
766         handled = true;
767         m_analogScrollCount -= 0.4f;
768
769         if (m_blockOffset + m_blocksPerPage < m_blocks && m_blockCursor >= m_blocksPerPage / 2)
770         {
771           ProgrammesScroll(blocksToJump);
772         }
773         else if (m_blockCursor < m_blocksPerPage - blocksToJump && m_blockOffset + m_blockCursor < m_blocks - blocksToJump)
774         {
775           SetBlock(m_blockCursor + blocksToJump);
776         }
777       }
778
779       return handled;
780     }
781
782     break;
783
784   default:
785     if (action.GetID())
786       return OnClick(action.GetID());
787     break;
788   }
789
790   return false;
791 }
792
793 bool CGUIEPGGridContainer::OnMessage(CGUIMessage& message)
794 {
795   if (message.GetControlId() == GetID())
796   {
797     if (message.GetMessage() == GUI_MSG_ITEM_SELECTED)
798     {
799       message.SetParam1(GetSelectedItem());
800       return true;
801     }
802     else if (message.GetMessage() == GUI_MSG_LABEL_BIND && message.GetPointer())
803     {
804       Reset();
805       CFileItemList *items = (CFileItemList *)message.GetPointer();
806
807       /* Create programme items */
808       m_programmeItems.reserve(items->Size());
809       for (int i = 0; i < items->Size(); i++)
810       {
811         CFileItemPtr fileItem = items->Get(i);
812         if (fileItem->HasEPGInfoTag() && fileItem->GetEPGInfoTag()->HasPVRChannel())
813           m_programmeItems.push_back(fileItem);
814       }
815
816       /* Create Channel items */
817       int iLastChannelNumber = -1;
818       ItemsPtr itemsPointer;
819       itemsPointer.start = 0;
820       for (unsigned int i = 0; i < m_programmeItems.size(); ++i)
821       {
822         const CEpgInfoTag* tag = ((CFileItem*)m_programmeItems[i].get())->GetEPGInfoTag();
823         int iCurrentChannelNumber = tag->PVRChannelNumber();
824         if (iCurrentChannelNumber != iLastChannelNumber)
825         {
826           CPVRChannelPtr channel = tag->ChannelTag();
827           if (!channel)
828             continue;
829
830           if (i > 0)
831           {
832             itemsPointer.stop = i-1;
833             m_epgItemsPtr.push_back(itemsPointer);
834             itemsPointer.start = i;
835           }
836           iLastChannelNumber = iCurrentChannelNumber;
837           CGUIListItemPtr item(new CFileItem(*channel));
838           m_channelItems.push_back(item);
839         }
840       }
841       if (!m_programmeItems.empty())
842       {
843         itemsPointer.stop = m_programmeItems.size()-1;
844         m_epgItemsPtr.push_back(itemsPointer);
845       }
846
847       ClearGridIndex();
848       m_gridIndex.reserve(m_channelItems.size());
849       for (unsigned int i = 0; i < m_channelItems.size(); i++)
850       {
851         std::vector<GridItemsPtr> blocks(MAXBLOCKS);
852         m_gridIndex.push_back(blocks);
853       }
854
855       UpdateLayout(true); // true to refresh all items
856
857       /* Create Ruler items */
858       CDateTime ruler; ruler.SetFromUTCDateTime(m_gridStart);
859       CDateTime rulerEnd; rulerEnd.SetFromUTCDateTime(m_gridEnd);
860       CDateTimeSpan unit(0, 0, m_rulerUnit * MINSPERBLOCK, 0);
861       CGUIListItemPtr rulerItem(new CFileItem(ruler.GetAsLocalizedDate(true, true)));
862       rulerItem->SetProperty("DateLabel", true);
863       m_rulerItems.push_back(rulerItem);
864
865       for (; ruler < rulerEnd; ruler += unit)
866       {
867         CGUIListItemPtr rulerItem(new CFileItem(ruler.GetAsLocalizedTime("", false)));
868         rulerItem->SetLabel2(ruler.GetAsLocalizedDate(true, true));
869         m_rulerItems.push_back(rulerItem);
870       }
871
872       UpdateItems();
873       //SelectItem(message.GetParam1());
874       return true;
875     }
876     else if (message.GetMessage() == GUI_MSG_REFRESH_LIST)
877     { // update our list contents
878       for (unsigned int i = 0; i < m_channelItems.size(); ++i)
879         m_channelItems[i]->SetInvalid();
880       for (unsigned int i = 0; i < m_programmeItems.size(); ++i)
881         m_programmeItems[i]->SetInvalid();
882       for (unsigned int i = 0; i < m_rulerItems.size(); ++i)
883         m_rulerItems[i]->SetInvalid();
884     }
885   }
886
887   return CGUIControl::OnMessage(message);
888 }
889
890 void CGUIEPGGridContainer::UpdateItems()
891 {
892   CDateTimeSpan blockDuration, gridDuration;
893
894   /* check for invalid start and end time */
895   if (m_gridStart >= m_gridEnd)
896   {
897     CLog::Log(LOGERROR, "CGUIEPGGridContainer - %s - invalid start and end time set", __FUNCTION__);
898     CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), GetParentID()); // message the window
899     SendWindowMessage(msg);
900     return;
901   }
902
903   gridDuration = m_gridEnd - m_gridStart;
904
905   m_blocks = (gridDuration.GetDays()*24*60 + gridDuration.GetHours()*60 + gridDuration.GetMinutes()) / MINSPERBLOCK;
906   if (m_blocks >= MAXBLOCKS)
907     m_blocks = MAXBLOCKS;
908
909   /* if less than one page, can't display grid */
910   if (m_blocks < m_blocksPerPage)
911   {
912     CLog::Log(LOGERROR, "(%s) - Less than one page of data available.", __FUNCTION__);
913     CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), GetParentID()); // message the window
914     SendWindowMessage(msg);
915     return;
916   }
917
918   blockDuration.SetDateTimeSpan(0, 0, MINSPERBLOCK, 0);
919
920   long tick(XbmcThreads::SystemClockMillis());
921
922   for (unsigned int row = 0; row < m_epgItemsPtr.size(); ++row)
923   {
924     CDateTime gridCursor  = m_gridStart; //reset cursor for new channel
925     unsigned long progIdx = m_epgItemsPtr[row].start;
926     unsigned long lastIdx = m_epgItemsPtr[row].stop;
927     int iEpgId            = ((CFileItem *)m_programmeItems[progIdx].get())->GetEPGInfoTag()->EpgID();
928
929     /** FOR EACH BLOCK **********************************************************************/
930
931     for (int block = 0; block < m_blocks; block++)
932     {
933       while (progIdx <= lastIdx)
934       {
935         CGUIListItemPtr item = m_programmeItems[progIdx];
936         const CEpgInfoTag* tag = ((CFileItem *)item.get())->GetEPGInfoTag();
937         if (tag == NULL)
938         {
939           progIdx++;
940           continue;
941         }
942
943         if (tag->EpgID() != iEpgId || gridCursor < tag->StartAsUTC() || m_gridEnd <= tag->StartAsUTC())
944           break;
945
946         if (gridCursor < tag->EndAsUTC())
947         {
948           m_gridIndex[row][block].item = item;
949           break;
950         }
951         
952         progIdx++;
953       }
954
955       gridCursor += blockDuration;
956     }
957
958     /** FOR EACH BLOCK **********************************************************************/
959     int itemSize = 1; // size of the programme in blocks
960     int savedBlock = 0;
961
962     for (int block = 0; block < m_blocks; block++)
963     {
964       if (m_gridIndex[row][block].item != m_gridIndex[row][block+1].item)
965       {
966         if (!m_gridIndex[row][block].item)
967         {
968           CEpgInfoTag broadcast;
969           CFileItemPtr unknown(new CFileItem(broadcast));
970           for (int i = block ; i > block - itemSize; i--)
971           {
972             m_gridIndex[row][i].item = unknown;
973           }
974         }
975
976         CGUIListItemPtr item = m_gridIndex[row][block].item;
977         CFileItem *fileItem = (CFileItem *)item.get();
978
979         m_gridIndex[row][savedBlock].item->SetProperty("GenreType", fileItem->GetEPGInfoTag()->GenreType());
980         if (m_orientation == VERTICAL)
981         {
982           m_gridIndex[row][savedBlock].width   = itemSize*m_blockSize;
983           m_gridIndex[row][savedBlock].height  = m_channelHeight;
984         }
985         else
986         {
987           m_gridIndex[row][savedBlock].width   = m_channelWidth;
988           m_gridIndex[row][savedBlock].height  = itemSize*m_blockSize;
989         }
990
991         itemSize = 1;
992         savedBlock = block+1;
993       }
994       else
995       {
996         itemSize++;
997       }
998     }
999   }
1000
1001   /******************************************* END ******************************************/
1002
1003   CLog::Log(LOGDEBUG, "%s completed successfully in %u ms", __FUNCTION__, (unsigned int)(XbmcThreads::SystemClockMillis()-tick));
1004
1005   m_channels = (int)m_epgItemsPtr.size();
1006   m_item = GetItem(m_channelCursor);
1007   if (m_item)
1008     SetBlock(GetBlock(m_item->item, m_channelCursor));
1009
1010   SetInvalid();
1011 }
1012
1013 void CGUIEPGGridContainer::ChannelScroll(int amount)
1014 {
1015   // increase or decrease the vertical offset
1016   int offset = m_channelOffset + amount;
1017
1018   if (offset > m_channels - m_channelsPerPage)
1019   {
1020     offset = m_channels - m_channelsPerPage;
1021   }
1022
1023   if (offset < 0) offset = 0;
1024
1025   ScrollToChannelOffset(offset);
1026 }
1027
1028 void CGUIEPGGridContainer::ProgrammesScroll(int amount)
1029 {
1030   // increase or decrease the horizontal offset
1031   ScrollToBlockOffset(m_blockOffset + amount);
1032 }
1033
1034 bool CGUIEPGGridContainer::MoveChannel(bool direction, bool wrapAround)
1035 {
1036   if (direction)
1037   {
1038     if (m_channelCursor > 0)
1039     {
1040       SetChannel(m_channelCursor - 1);
1041     }
1042     else if (m_channelCursor == 0 && m_channelOffset)
1043     {
1044       ScrollToChannelOffset(m_channelOffset - 1);
1045       SetChannel(0);
1046     }
1047     else if (wrapAround)
1048     {
1049       int offset = m_channels - m_channelsPerPage;
1050
1051       if (offset < 0) offset = 0;
1052
1053       SetChannel(m_channels - offset - 1);
1054
1055       ScrollToChannelOffset(offset);
1056     }
1057     else
1058       return false;
1059   }
1060   else
1061   {
1062     if (m_channelOffset + m_channelCursor + 1 < m_channels)
1063     {
1064       if (m_channelCursor + 1 < m_channelsPerPage)
1065       {
1066         SetChannel(m_channelCursor + 1);
1067       }
1068       else
1069       {
1070         ScrollToChannelOffset(m_channelOffset + 1);
1071         SetChannel(m_channelsPerPage - 1);
1072       }
1073     }
1074     else if (wrapAround)
1075     {
1076       SetChannel(0);
1077       ScrollToChannelOffset(0);
1078     }
1079     else
1080       return false;
1081   }
1082   return true;
1083 }
1084
1085 bool CGUIEPGGridContainer::MoveProgrammes(bool direction)
1086 {
1087   if (m_gridIndex.empty() || !m_item)
1088     return false;
1089
1090   if (direction)
1091   {
1092     if (m_channelCursor + m_channelOffset < 0 || m_blockOffset < 0)
1093       return false;
1094
1095     if (m_item->item != m_gridIndex[m_channelCursor + m_channelOffset][m_blockOffset].item)
1096     {
1097       // this is not first item on page
1098       m_item = GetPrevItem(m_channelCursor);
1099       SetBlock(GetBlock(m_item->item, m_channelCursor));
1100     }
1101     else if (m_blockCursor <= 0 && m_blockOffset)
1102     {
1103       // we're at the left edge and offset
1104       int itemSize = GetItemSize(m_item);
1105       int block = GetRealBlock(m_item->item, m_channelCursor);
1106
1107       if (block < m_blockOffset) /* current item begins before current offset, keep selected */
1108       {
1109         if (itemSize > m_blocksPerPage) /* current item is longer than one page, scroll one page left */
1110         {
1111           m_blockOffset < m_blocksPerPage ? block = 0 : block = m_blockOffset - m_blocksPerPage; // number blocks left < m_blocksPerPAge
1112           ScrollToBlockOffset(block);
1113           SetBlock(0);
1114         }
1115         else /* current item is shorter than one page, scroll left to start of item */
1116         {
1117           ScrollToBlockOffset(block); // -1?
1118           SetBlock(0); // align cursor to left edge
1119         }
1120       }
1121       else /* current item starts on this page's edge, select the previous item */
1122       {
1123         m_item = GetPrevItem(m_channelCursor);
1124         itemSize = GetItemSize(m_item);
1125
1126         if (itemSize > m_blocksPerPage) // previous item is longer than one page, scroll left to last page of item */
1127         {
1128           ScrollToBlockOffset(m_blockOffset - m_blocksPerPage); // left one whole page
1129           //SetBlock(m_blocksPerPage -1 ); // helps navigation by setting cursor to far right edge
1130           SetBlock(0); // align cursor to left edge
1131         }
1132         else /* previous item is shorter than one page, scroll left to start of item */
1133         {
1134           ScrollToBlockOffset(m_blockOffset - itemSize);
1135           SetBlock(0); //should be zero
1136         }
1137       }
1138     }
1139     else
1140       return false;
1141   }
1142   else
1143   {
1144     if (m_item->item != m_gridIndex[m_channelCursor + m_channelOffset][m_blocksPerPage + m_blockOffset - 1].item)
1145     {
1146       // this is not last item on page
1147       m_item = GetNextItem(m_channelCursor);
1148       SetBlock(GetBlock(m_item->item, m_channelCursor));
1149     }
1150     else if ((m_blockOffset != m_blocks - m_blocksPerPage) && m_blocks > m_blocksPerPage)
1151     {
1152       // at right edge, more than one page and not at maximum offset
1153       int itemSize = GetItemSize(m_item);
1154       int block = GetRealBlock(m_item->item, m_channelCursor);
1155
1156       if (itemSize > m_blocksPerPage - m_blockCursor) // current item extends into next page, keep selected
1157       {
1158         if (itemSize > m_blocksPerPage) // current item is longer than one page, scroll one page right
1159         {
1160           if (m_blockOffset && m_blockOffset + m_blocksPerPage > m_blocks)
1161             block = m_blocks - m_blocksPerPage;
1162           else
1163             block = m_blockOffset + m_blocksPerPage;
1164
1165           ScrollToBlockOffset(block);
1166
1167           SetBlock(0);
1168         }
1169         else // current item is shorter than one page, scroll so end of item sits on end of grid
1170         {
1171           ScrollToBlockOffset(block + itemSize - m_blocksPerPage);
1172           SetBlock(GetBlock(m_item->item, m_channelCursor)); /// change to middle block of item?
1173         }
1174       }
1175       else // current item finishes on this page's edge, select the next item
1176       {
1177         m_item = GetNextItem(m_channelCursor);
1178         itemSize = GetItemSize(m_item);
1179
1180         if (itemSize > m_blocksPerPage) // next item is longer than one page, scroll to first page of this item
1181         {
1182           ScrollToBlockOffset(m_blockOffset + m_blocksPerPage);
1183           SetBlock(0);
1184         }
1185         else // next item is shorter than one page, scroll so end of item sits on end of grid
1186         {
1187           ScrollToBlockOffset(m_blockOffset + itemSize);
1188           SetBlock(m_blocksPerPage - itemSize); /// change to middle block of item?
1189         }
1190       }
1191     }
1192     else
1193       return false;
1194   }
1195   return true;
1196 }
1197
1198 void CGUIEPGGridContainer::OnUp()
1199 {
1200   bool wrapAround = m_actionUp.GetNavigation() == GetID() || !m_actionUp.HasActionsMeetingCondition();
1201   if (m_orientation == VERTICAL)
1202   {
1203     if (!MoveChannel(true, wrapAround))
1204       CGUIControl::OnUp();
1205   }
1206   else
1207   {
1208     if (!MoveProgrammes(true))
1209       CGUIControl::OnUp();
1210   }
1211 }
1212
1213 void CGUIEPGGridContainer::OnDown()
1214 {
1215   bool wrapAround = m_actionDown.GetNavigation() == GetID() || !m_actionDown.HasActionsMeetingCondition();
1216   if (m_orientation == VERTICAL)
1217   {
1218     if (!MoveChannel(false, wrapAround))
1219       CGUIControl::OnDown();
1220   }
1221   else
1222   {
1223     if (!MoveProgrammes(false))
1224       CGUIControl::OnDown();
1225   }
1226 }
1227
1228 void CGUIEPGGridContainer::OnLeft()
1229 {
1230   bool wrapAround = m_actionLeft.GetNavigation() == GetID() || !m_actionLeft.HasActionsMeetingCondition();
1231   if (m_orientation == VERTICAL)
1232   {
1233     if (!MoveProgrammes(true))
1234       CGUIControl::OnLeft();
1235   }
1236   else
1237   {
1238     if (!MoveChannel(true, wrapAround))
1239       CGUIControl::OnLeft();
1240   }
1241 }
1242
1243 void CGUIEPGGridContainer::OnRight()
1244 {
1245   bool wrapAround = m_actionRight.GetNavigation() == GetID() || !m_actionRight.HasActionsMeetingCondition();
1246   if (m_orientation == VERTICAL)
1247   {
1248     if (!MoveProgrammes(false))
1249       CGUIControl::OnRight();
1250   }
1251   else
1252   {
1253     if (!MoveChannel(false, wrapAround))
1254       CGUIControl::OnRight();
1255   }
1256 }
1257
1258 void CGUIEPGGridContainer::SetChannel(const CStdString &channel)
1259 {
1260   int iChannelIndex(-1);
1261   for (unsigned int iIndex = 0; iIndex < m_channelItems.size(); iIndex++)
1262   {
1263     CStdString strPath = m_channelItems[iIndex]->GetProperty("path").asString(StringUtils::EmptyString);
1264     if (strPath == channel)
1265     {
1266       iChannelIndex = iIndex;
1267       break;
1268     }
1269   }
1270
1271   if (iChannelIndex >= 0)
1272     ScrollToChannelOffset(iChannelIndex);
1273 }
1274
1275 void CGUIEPGGridContainer::SetChannel(const CPVRChannel &channel)
1276 {
1277   int iChannelIndex(-1);
1278   for (unsigned int iIndex = 0; iIndex < m_channelItems.size(); iIndex++)
1279   {
1280     int iChannelId = (int)m_channelItems[iIndex]->GetProperty("channelid").asInteger(-1);
1281     if (iChannelId == channel.ChannelID())
1282     {
1283       iChannelIndex = iIndex;
1284       break;
1285     }
1286   }
1287
1288   if (iChannelIndex >= 0)
1289     ScrollToChannelOffset(iChannelIndex);
1290 }
1291
1292 void CGUIEPGGridContainer::SetChannel(int channel)
1293 {
1294   if (m_blockCursor + m_blockOffset == 0 || m_blockOffset + m_blockCursor + GetItemSize(m_item) == m_blocks)
1295   {
1296     m_item          = GetItem(channel);
1297     if (m_item)
1298     {
1299       SetBlock(GetBlock(m_item->item, channel));
1300       m_channelCursor = channel;
1301     }
1302     return;
1303   }
1304
1305   /* basic checks failed, need to correctly identify nearest item */
1306   m_item          = GetClosestItem(channel);
1307   if (m_item)
1308   {
1309     m_channelCursor = channel;
1310     SetBlock(GetBlock(m_item->item, m_channelCursor));
1311   }
1312 }
1313
1314 void CGUIEPGGridContainer::SetBlock(int block)
1315 {
1316   if (block < 0)
1317     m_blockCursor = 0;
1318   else if (block > m_blocksPerPage - 1)
1319     m_blockCursor = m_blocksPerPage - 1;
1320   else
1321     m_blockCursor = block;
1322   m_item        = GetItem(m_channelCursor);
1323 }
1324
1325 CGUIListItemLayout *CGUIEPGGridContainer::GetFocusedLayout() const
1326 {
1327   CGUIListItemPtr item = GetListItem(0);
1328
1329   if (item.get()) return item->GetFocusedLayout();
1330
1331   return NULL;
1332 }
1333
1334 bool CGUIEPGGridContainer::SelectItemFromPoint(const CPoint &point, bool justGrid /* = false */)
1335 {
1336   /* point has already had origin set to m_posX, m_posY */
1337   if (!m_focusedProgrammeLayout || !m_programmeLayout || (justGrid && point.x < 0))
1338     return false;
1339
1340   int channel = (int)(point.y / m_channelHeight);
1341   int block   = (int)(point.x / m_blockSize);
1342
1343   if (channel > m_channelsPerPage) channel = m_channelsPerPage - 1;
1344   if (channel >= m_channels) channel = m_channels - 1;
1345   if (channel < 0) channel = 0;
1346   if (block > m_blocksPerPage) block = m_blocksPerPage - 1;
1347   if (block < 0) block = 0;
1348
1349   int channelIndex = channel + m_channelOffset;
1350   int blockIndex = block + m_blockOffset;
1351
1352   // bail if out of range
1353   if (channelIndex >= m_channels || blockIndex >= m_blocks)
1354     return false;
1355   // bail if block isn't occupied
1356   if (!m_gridIndex[channelIndex][blockIndex].item)
1357     return false;
1358
1359   SetChannel(channel);
1360   SetBlock(block);
1361   return true;
1362 }
1363
1364 EVENT_RESULT CGUIEPGGridContainer::OnMouseEvent(const CPoint &point, const CMouseEvent &event)
1365 {
1366   switch (event.m_id)
1367   {
1368   case ACTION_MOUSE_LEFT_CLICK:
1369     OnMouseClick(0, point);
1370     return EVENT_RESULT_HANDLED;
1371   case ACTION_MOUSE_RIGHT_CLICK:
1372     OnMouseClick(1, point);
1373     return EVENT_RESULT_HANDLED;
1374   case ACTION_MOUSE_DOUBLE_CLICK:
1375     OnMouseDoubleClick(0, point);
1376     return EVENT_RESULT_HANDLED;
1377   case ACTION_MOUSE_WHEEL_UP:
1378     OnMouseWheel(-1, point);
1379     return EVENT_RESULT_HANDLED;
1380   case ACTION_MOUSE_WHEEL_DOWN:
1381     OnMouseWheel(1, point);
1382     return EVENT_RESULT_HANDLED;
1383   case ACTION_GESTURE_BEGIN:
1384     {
1385       // we want exclusive access
1386       CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, GetID(), GetParentID());
1387       SendWindowMessage(msg);
1388       return EVENT_RESULT_HANDLED;
1389     }
1390   case ACTION_GESTURE_END:
1391     {
1392       // we're done with exclusive access
1393       CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, 0, GetParentID());
1394       SendWindowMessage(msg);
1395       ScrollToChannelOffset(MathUtils::round_int(m_channelScrollOffset / m_channelLayout->Size(m_orientation)));
1396       ScrollToBlockOffset(MathUtils::round_int(m_programmeScrollOffset / m_blockSize));
1397       return EVENT_RESULT_HANDLED;
1398     }
1399   case ACTION_GESTURE_PAN:
1400     {
1401       if (m_orientation == VERTICAL)
1402       {
1403         m_programmeScrollOffset -= event.m_offsetX;
1404         m_channelScrollOffset -= event.m_offsetY;
1405       }
1406       else
1407       {
1408         m_channelScrollOffset -= event.m_offsetX;
1409         m_programmeScrollOffset -= event.m_offsetY;
1410       }
1411
1412       m_channelOffset = MathUtils::round_int(m_channelScrollOffset / m_channelLayout->Size(m_orientation));
1413       m_blockOffset = MathUtils::round_int(m_programmeScrollOffset / m_blockSize);
1414       ValidateOffset();
1415       return EVENT_RESULT_HANDLED;
1416     }
1417   default:
1418     return EVENT_RESULT_UNHANDLED;
1419   }
1420 }
1421
1422 bool CGUIEPGGridContainer::OnMouseOver(const CPoint &point)
1423 {
1424   // select the item under the pointer
1425   SelectItemFromPoint(point - CPoint(m_gridPosX, m_posY + m_rulerHeight), false);
1426   return CGUIControl::OnMouseOver(point);
1427 }
1428
1429 bool CGUIEPGGridContainer::OnMouseClick(int dwButton, const CPoint &point)
1430 {
1431   if (SelectItemFromPoint(point - CPoint(m_gridPosX, m_posY + m_rulerHeight)))
1432   { // send click message to window
1433     OnClick(ACTION_MOUSE_LEFT_CLICK + dwButton);
1434     return true;
1435   }
1436
1437   return false;
1438 }
1439
1440 bool CGUIEPGGridContainer::OnMouseDoubleClick(int dwButton, const CPoint &point)
1441 {
1442   if (SelectItemFromPoint(point - CPoint(m_gridPosX, m_posY + m_rulerHeight)))
1443   { // send double click message to window
1444     OnClick(ACTION_MOUSE_DOUBLE_CLICK + dwButton);
1445     return true;
1446   }
1447
1448   return false;
1449 }
1450
1451 bool CGUIEPGGridContainer::OnClick(int actionID)
1452 {
1453   int subItem = 0;
1454
1455   if (actionID == ACTION_SELECT_ITEM || actionID == ACTION_MOUSE_LEFT_CLICK)
1456   {
1457     // grab the currently focused subitem (if applicable)
1458     CGUIListItemLayout *focusedLayout = GetFocusedLayout();
1459
1460     if (focusedLayout)
1461       subItem = focusedLayout->GetFocusedItem();
1462   }
1463
1464   // Don't know what to do, so send to our parent window.
1465   CGUIMessage msg(GUI_MSG_CLICKED, GetID(), GetParentID(), actionID, subItem);
1466   return SendWindowMessage(msg);
1467 }
1468
1469 bool CGUIEPGGridContainer::OnMouseWheel(char wheel, const CPoint &point)
1470 {
1471   ///doesn't work while an item is selected?
1472   ProgrammesScroll(-wheel);
1473   return true;
1474 }
1475
1476 int CGUIEPGGridContainer::GetSelectedItem() const
1477 {
1478   if (m_gridIndex.empty() ||
1479       m_epgItemsPtr.empty() ||
1480       m_channelCursor + m_channelOffset >= m_channels ||
1481       m_blockCursor + m_blockOffset >= m_blocks)
1482     return 0;
1483
1484   CGUIListItemPtr currentItem = m_gridIndex[m_channelCursor + m_channelOffset][m_blockCursor + m_blockOffset].item;
1485   if (!currentItem)
1486     return 0;
1487
1488   for (int i = 0; i < (int)m_programmeItems.size(); i++)
1489   {
1490     if (currentItem == m_programmeItems[i])
1491       return i;
1492   }
1493   return 0;
1494 }
1495
1496 CGUIListItemPtr CGUIEPGGridContainer::GetListItem(int offset, unsigned int flag) const
1497 {
1498   if (m_channelItems.empty())
1499     return CGUIListItemPtr();
1500
1501   int item = m_channelCursor + m_channelOffset + offset;
1502   if (flag & INFOFLAG_LISTITEM_POSITION)
1503     item = (int)(m_channelScrollOffset / m_channelLayout->Size(VERTICAL));
1504
1505   if (flag & INFOFLAG_LISTITEM_WRAP)
1506   {
1507     item %= (int)m_channelItems.size();
1508     if (item < 0) item += m_channelItems.size();
1509     return m_channelItems[item];
1510   }
1511   else
1512   {
1513     if (item >= 0 && item < (int)m_channelItems.size())
1514       return m_channelItems[item];
1515   }
1516   return CGUIListItemPtr();
1517 }
1518
1519 CStdString CGUIEPGGridContainer::GetLabel(int info) const
1520 {
1521   CStdString label;
1522   switch (info)
1523   {
1524   case CONTAINER_NUM_PAGES:
1525     label = StringUtils::Format("%u", (m_channels + m_channelsPerPage - 1) / m_channelsPerPage);
1526     break;
1527   case CONTAINER_CURRENT_PAGE:
1528     label = StringUtils::Format("%u", 1 + (m_channelCursor + m_channelOffset) / m_channelsPerPage );
1529     break;
1530   case CONTAINER_POSITION:
1531     label = StringUtils::Format("%i", 1 + m_channelCursor + m_channelOffset);
1532     break;
1533   case CONTAINER_NUM_ITEMS:
1534     label = StringUtils::Format("%u", m_channels);
1535     break;
1536   default:
1537       break;
1538   }
1539   return label;
1540 }
1541
1542 GridItemsPtr *CGUIEPGGridContainer::GetClosestItem(const int &channel)
1543 {
1544   GridItemsPtr *closest = GetItem(channel);
1545
1546   if(!closest)
1547     return NULL;
1548
1549   int block = GetBlock(closest->item, channel);
1550   int left;   // num blocks to start of previous item
1551   int right;  // num blocks to start of next item
1552
1553   if (block == m_blockCursor)
1554     return closest; // item & m_item start together
1555
1556   if (block + GetItemSize(closest) == m_blockCursor + GetItemSize(m_item))
1557     return closest; // closest item ends when current does
1558
1559   if (block > m_blockCursor)  // item starts after m_item
1560   {
1561     left = m_blockCursor - GetBlock(closest->item, channel);
1562     right = block - m_blockCursor;
1563   }
1564   else
1565   {
1566     left  = m_blockCursor - block;
1567     right = GetBlock(GetNextItem(channel)->item, channel) - m_blockCursor;
1568   }
1569
1570   if (right <= SHORTGAP && right <= left && m_blockCursor + right < m_blocksPerPage)
1571     return &m_gridIndex[channel + m_channelOffset][m_blockCursor + right + m_blockOffset];
1572
1573   return &m_gridIndex[channel + m_channelOffset][m_blockCursor - left  + m_blockOffset];
1574 }
1575
1576 int CGUIEPGGridContainer::GetItemSize(GridItemsPtr *item)
1577 {
1578   if (!item)
1579     return (int) m_blockSize; /// stops it crashing
1580
1581   return (int) ((m_orientation == VERTICAL ? item->width : item->height) / m_blockSize);
1582 }
1583
1584 int CGUIEPGGridContainer::GetBlock(const CGUIListItemPtr &item, const int &channel)
1585 {
1586   if (!item)
1587     return 0;
1588
1589   return GetRealBlock(item, channel) - m_blockOffset;
1590 }
1591
1592 int CGUIEPGGridContainer::GetRealBlock(const CGUIListItemPtr &item, const int &channel)
1593 {
1594   int channelIndex = channel + m_channelOffset;
1595   int block = 0;
1596
1597   while (m_gridIndex[channelIndex][block].item != item && block < m_blocks)
1598     block++;
1599
1600   return block;
1601 }
1602
1603 GridItemsPtr *CGUIEPGGridContainer::GetNextItem(const int &channel)
1604 {
1605   int channelIndex = channel + m_channelOffset;
1606   int blockIndex = m_blockCursor + m_blockOffset;
1607   if (channelIndex >= m_channels || blockIndex >= m_blocks)
1608     return NULL;
1609
1610   int i = m_blockCursor;
1611
1612   while (i < m_blocksPerPage && m_gridIndex[channelIndex][i + m_blockOffset].item == m_gridIndex[channelIndex][blockIndex].item)
1613     i++;
1614
1615   return &m_gridIndex[channelIndex][i + m_blockOffset];
1616 }
1617
1618 GridItemsPtr *CGUIEPGGridContainer::GetPrevItem(const int &channel)
1619 {
1620   int channelIndex = channel + m_channelOffset;
1621   int blockIndex = m_blockCursor + m_blockOffset;
1622   if (channelIndex >= m_channels || blockIndex >= m_blocks)
1623     return NULL;
1624
1625   int i = m_blockCursor;
1626
1627   while (i > 0 && m_gridIndex[channelIndex][i + m_blockOffset].item == m_gridIndex[channelIndex][blockIndex].item)
1628     i--;
1629
1630   return &m_gridIndex[channelIndex][i + m_blockOffset];
1631 }
1632
1633 GridItemsPtr *CGUIEPGGridContainer::GetItem(const int &channel)
1634 {
1635   int channelIndex = channel + m_channelOffset;
1636   int blockIndex = m_blockCursor + m_blockOffset;
1637   if (channelIndex >= m_channels || blockIndex >= m_blocks)
1638     return NULL;
1639
1640   return &m_gridIndex[channelIndex][blockIndex];
1641 }
1642
1643 void CGUIEPGGridContainer::SetFocus(bool bOnOff)
1644 {
1645   if (bOnOff != HasFocus())
1646   {
1647     SetInvalid();
1648     /*m_lastItem.reset();
1649     m_lastChannel.reset();*/
1650   }
1651
1652   CGUIControl::SetFocus(bOnOff);
1653 }
1654
1655 void CGUIEPGGridContainer::DoRender()
1656 {
1657   CGUIControl::DoRender();
1658   m_wasReset = false;
1659 }
1660
1661 void CGUIEPGGridContainer::ScrollToChannelOffset(int offset)
1662 {
1663   float size = m_programmeLayout->Size(VERTICAL);
1664   int range = m_channelsPerPage / 4;
1665
1666   if (range <= 0) range = 1;
1667
1668   if (offset * size < m_channelScrollOffset &&  m_channelScrollOffset - offset * size > size * range)
1669   { // scrolling up, and we're jumping more than 0.5 of a screen
1670     m_channelScrollOffset = (offset + range) * size;
1671   }
1672
1673   if (offset * size > m_channelScrollOffset && offset * size - m_channelScrollOffset > size * range)
1674   { // scrolling down, and we're jumping more than 0.5 of a screen
1675     m_channelScrollOffset = (offset - range) * size;
1676   }
1677
1678   m_channelScrollSpeed = (offset * size - m_channelScrollOffset) / m_scrollTime;
1679
1680   m_channelOffset = offset;
1681 }
1682
1683 void CGUIEPGGridContainer::ScrollToBlockOffset(int offset)
1684 {
1685   // make sure offset is in valid range
1686   offset = std::max(0, std::min(offset, m_blocks - m_blocksPerPage));
1687
1688   float size = m_blockSize;
1689   int range = m_blocksPerPage / 1;
1690
1691   if (range <= 0) range = 1;
1692
1693   if (offset * size < m_programmeScrollOffset &&  m_programmeScrollOffset - offset * size > size * range)
1694   { // scrolling left, and we're jumping more than 0.5 of a screen
1695     m_programmeScrollOffset = (offset + range) * size;
1696   }
1697
1698   if (offset * size > m_programmeScrollOffset && offset * size - m_programmeScrollOffset > size * range)
1699   { // scrolling right, and we're jumping more than 0.5 of a screen
1700     m_programmeScrollOffset = (offset - range) * size;
1701   }
1702
1703   m_programmeScrollSpeed = (offset * size - m_programmeScrollOffset) / m_scrollTime;
1704
1705   m_blockOffset = offset;
1706 }
1707
1708 void CGUIEPGGridContainer::ValidateOffset()
1709 {
1710   if (!m_programmeLayout)
1711     return;
1712
1713   if (m_channelOffset > m_channels - m_channelsPerPage || m_channelScrollOffset > (m_channels - m_channelsPerPage) * m_channelHeight)
1714   {
1715     m_channelOffset = m_channels - m_channelsPerPage;
1716     m_channelScrollOffset = m_channelOffset * m_channelHeight;
1717   }
1718
1719   if (m_channelOffset < 0 || m_channelScrollOffset < 0)
1720   {
1721     m_channelOffset = 0;
1722     m_channelScrollOffset = 0;
1723   }
1724
1725   if (m_blockOffset > m_blocks - m_blocksPerPage || m_programmeScrollOffset > (m_blocks - m_blocksPerPage) * m_blockSize)
1726   {
1727     m_blockOffset = m_blocks - m_blocksPerPage;
1728     m_programmeScrollOffset = m_blockOffset * m_blockSize;
1729   }
1730
1731   if (m_blockOffset < 0 || m_programmeScrollOffset < 0)
1732   {
1733     m_blockOffset = 0;
1734     m_programmeScrollOffset = 0;
1735   }
1736 }
1737
1738 void CGUIEPGGridContainer::LoadLayout(TiXmlElement *layout)
1739 {
1740   /* layouts for the channel column */
1741   TiXmlElement *itemElement = layout->FirstChildElement("channellayout");
1742   while (itemElement)
1743   { // we have a new item layout
1744     CGUIListItemLayout itemLayout;
1745     itemLayout.LoadLayout(itemElement, GetParentID(), false);
1746     m_channelLayouts.push_back(itemLayout);
1747     itemElement = itemElement->NextSiblingElement("channellayout");
1748   }
1749   itemElement = layout->FirstChildElement("focusedchannellayout");
1750   while (itemElement)
1751   { // we have a new item layout
1752     CGUIListItemLayout itemLayout;
1753     itemLayout.LoadLayout(itemElement, GetParentID(), true);
1754     m_focusedChannelLayouts.push_back(itemLayout);
1755     itemElement = itemElement->NextSiblingElement("focusedchannellayout");
1756   }
1757
1758   /* layouts for the grid items */
1759   itemElement = layout->FirstChildElement("focusedlayout");
1760   while (itemElement)
1761   {
1762     CGUIListItemLayout itemLayout;
1763     itemLayout.LoadLayout(itemElement, GetParentID(), true);
1764     m_focusedProgrammeLayouts.push_back(itemLayout);
1765     itemElement = itemElement->NextSiblingElement("focusedlayout");
1766   }
1767   itemElement = layout->FirstChildElement("itemlayout");
1768   while (itemElement)
1769   {
1770     CGUIListItemLayout itemLayout;
1771     itemLayout.LoadLayout(itemElement, GetParentID(), false);
1772     m_programmeLayouts.push_back(itemLayout);
1773     itemElement = itemElement->NextSiblingElement("itemlayout");
1774   }
1775
1776   /* layout for the timeline above the grid */
1777   itemElement = layout->FirstChildElement("rulerlayout");
1778   while (itemElement)
1779   {
1780     CGUIListItemLayout itemLayout;
1781     itemLayout.LoadLayout(itemElement, GetParentID(), false);
1782     m_rulerLayouts.push_back(itemLayout);
1783     itemElement = itemElement->NextSiblingElement("rulerlayout");
1784   }
1785 }
1786
1787 void CGUIEPGGridContainer::UpdateLayout(bool updateAllItems)
1788 {
1789   // if container is invalid, either new data has arrived, or m_blockSize has changed
1790   //  need to run UpdateItems rather than CalculateLayout?
1791   if (updateAllItems)
1792   { // free memory of items
1793     for (iItems it = m_channelItems.begin(); it != m_channelItems.end(); it++)
1794       (*it)->FreeMemory();
1795     for (iItems it = m_rulerItems.begin(); it != m_rulerItems.end(); it++)
1796       (*it)->FreeMemory();
1797     for (iItems it = m_programmeItems.begin(); it != m_programmeItems.end(); it++)
1798       (*it)->FreeMemory();
1799   }
1800
1801   // and recalculate the layout
1802   CalculateLayout();
1803 }
1804
1805 CStdString CGUIEPGGridContainer::GetDescription() const
1806 {
1807   CStdString strLabel;
1808   int item = GetSelectedItem();
1809   if (item >= 0 && item < (int)m_programmeItems.size())
1810   {
1811     CGUIListItemPtr pItem = m_programmeItems[item];
1812     strLabel = pItem->GetLabel();
1813   }
1814   return strLabel;
1815 }
1816
1817 void CGUIEPGGridContainer::ClearGridIndex(void)
1818 {
1819   for (unsigned int i = 0; i < m_gridIndex.size(); i++)
1820   {
1821     for (int block = 0; block < m_blocks; block++)
1822     {
1823       if (m_gridIndex[i][block].item)
1824         m_gridIndex[i][block].item.get()->ClearProperties();
1825     }
1826     m_gridIndex[i].clear();
1827   }
1828   m_gridIndex.clear();
1829 }
1830
1831 void CGUIEPGGridContainer::Reset()
1832 {
1833   ClearGridIndex();
1834
1835   m_wasReset = true;
1836   m_channelItems.clear();
1837   m_programmeItems.clear();
1838   m_rulerItems.clear();
1839   m_epgItemsPtr.clear();
1840
1841   m_lastItem    = NULL;
1842   m_lastChannel = NULL;
1843 }
1844
1845 void CGUIEPGGridContainer::GoToBegin()
1846 {
1847   ScrollToBlockOffset(0);
1848   SetBlock(0);
1849 }
1850
1851 void CGUIEPGGridContainer::GoToEnd()
1852 {
1853   int blocksEnd = 0;   // the end block of the last epg element for the selected channel
1854   int blocksStart = 0; // the start block of the last epg element for the selected channel
1855   int blockOffset = 0; // the block offset to scroll to
1856   for (int blockIndex = m_blocks; blockIndex >= 0 && (!blocksEnd || !blocksStart); blockIndex--)
1857   {
1858     if (!blocksEnd && m_gridIndex[m_channelCursor + m_channelOffset][blockIndex].item != NULL)
1859       blocksEnd = blockIndex;
1860     if (blocksEnd && m_gridIndex[m_channelCursor + m_channelOffset][blocksEnd].item != 
1861                      m_gridIndex[m_channelCursor + m_channelOffset][blockIndex].item)
1862       blocksStart = blockIndex + 1;
1863   }
1864   if (blocksEnd - blocksStart > m_blocksPerPage)
1865     blockOffset = blocksStart;
1866   else if (blocksEnd > m_blocksPerPage)
1867     blockOffset = blocksEnd - m_blocksPerPage;
1868
1869   ScrollToBlockOffset(blockOffset); // scroll to the start point of the last epg element
1870   SetBlock(m_blocksPerPage - 1);    // select the last epg element
1871 }
1872
1873 void CGUIEPGGridContainer::SetStartEnd(CDateTime start, CDateTime end)
1874 {
1875   m_gridStart = CDateTime(start.GetYear(), start.GetMonth(), start.GetDay(), start.GetHour(), start.GetMinute() >= 30 ? 30 : 0, 0);
1876   m_gridEnd = CDateTime(end.GetYear(), end.GetMonth(), end.GetDay(), end.GetHour(), end.GetMinute() >= 30 ? 30 : 0, 0);
1877
1878   CLog::Log(LOGDEBUG, "CGUIEPGGridContainer - %s - start=%s end=%s",
1879       __FUNCTION__, m_gridStart.GetAsLocalizedDateTime(false, true).c_str(), m_gridEnd.GetAsLocalizedDateTime(false, true).c_str());
1880 }
1881
1882 void CGUIEPGGridContainer::CalculateLayout()
1883 {
1884   CGUIListItemLayout *oldFocusedChannelLayout   = m_focusedChannelLayout;
1885   CGUIListItemLayout *oldChannelLayout          = m_channelLayout;
1886   CGUIListItemLayout *oldFocusedProgrammeLayout = m_focusedProgrammeLayout;
1887   CGUIListItemLayout *oldProgrammeLayout        = m_programmeLayout;
1888   CGUIListItemLayout *oldRulerLayout            = m_rulerLayout;
1889   GetCurrentLayouts();
1890
1891   if (!m_focusedProgrammeLayout || !m_programmeLayout || !m_focusedChannelLayout || !m_channelLayout || !m_rulerLayout)
1892     return;
1893
1894   if (oldChannelLayout   == m_channelLayout   && oldFocusedChannelLayout   == m_focusedChannelLayout   &&
1895       oldProgrammeLayout == m_programmeLayout && oldFocusedProgrammeLayout == m_focusedProgrammeLayout &&
1896       oldRulerLayout     == m_rulerLayout)
1897     return; // nothing has changed, so don't update stuff
1898
1899   m_channelHeight       = m_channelLayout->Size(VERTICAL);
1900   m_channelWidth        = m_channelLayout->Size(HORIZONTAL);
1901   if (m_orientation == VERTICAL)
1902   {
1903     m_rulerHeight       = m_rulerLayout->Size(VERTICAL);
1904     m_gridPosX          = m_posX + m_channelWidth;
1905     m_gridPosY          = m_posY + m_rulerHeight;
1906     m_gridWidth         = m_width - m_channelWidth;
1907     m_gridHeight        = m_height - m_rulerHeight;
1908     m_blockSize         = m_gridWidth / m_blocksPerPage;
1909     m_rulerWidth        = m_rulerUnit * m_blockSize;
1910     m_channelPosX       = m_posX;
1911     m_channelPosY       = m_posY + m_rulerHeight;
1912     m_rulerPosX         = m_posX + m_channelWidth;
1913     m_rulerPosY         = m_posY;
1914     m_channelsPerPage   = (int)(m_gridHeight / m_channelHeight);
1915     m_ProgrammesPerPage = (int)(m_gridWidth / m_blockSize) + 1;
1916   }
1917   else
1918   {
1919     m_rulerWidth        = m_rulerLayout->Size(HORIZONTAL);
1920     m_gridPosX          = m_posX + m_rulerWidth;
1921     m_gridPosY          = m_posY + m_channelHeight;
1922     m_gridWidth         = m_width - m_rulerWidth;
1923     m_gridHeight        = m_height - m_channelHeight;
1924     m_blockSize         = m_gridHeight / m_blocksPerPage;
1925     m_rulerHeight       = m_rulerUnit * m_blockSize;
1926     m_channelPosX       = m_posX + m_rulerWidth;
1927     m_channelPosY       = m_posY;
1928     m_rulerPosX         = m_posX;
1929     m_rulerPosY         = m_posY + m_channelHeight;
1930     m_channelsPerPage   = (int)(m_gridWidth / m_channelWidth);
1931     m_ProgrammesPerPage = (int)(m_gridHeight / m_blockSize) + 1;
1932   }
1933
1934   // ensure that the scroll offsets are a multiple of our sizes
1935   m_channelScrollOffset   = m_channelOffset * m_programmeLayout->Size(m_orientation);
1936   m_programmeScrollOffset = m_blockOffset * m_blockSize;
1937 }
1938
1939 void CGUIEPGGridContainer::UpdateScrollOffset(unsigned int currentTime)
1940 {
1941   if (!m_programmeLayout)
1942     return;
1943   m_channelScrollOffset += m_channelScrollSpeed * (currentTime - m_channelScrollLastTime);
1944   if ((m_channelScrollSpeed < 0 && m_channelScrollOffset < m_channelOffset * m_programmeLayout->Size(m_orientation)) ||
1945       (m_channelScrollSpeed > 0 && m_channelScrollOffset > m_channelOffset * m_programmeLayout->Size(m_orientation)))
1946   {
1947     m_channelScrollOffset = m_channelOffset * m_programmeLayout->Size(m_orientation);
1948     m_channelScrollSpeed = 0;
1949   }
1950   m_channelScrollLastTime = currentTime;
1951
1952   m_programmeScrollOffset += m_programmeScrollSpeed * (currentTime - m_programmeScrollLastTime);
1953   if ((m_programmeScrollSpeed < 0 && m_programmeScrollOffset < m_blockOffset * m_blockSize) ||
1954       (m_programmeScrollSpeed > 0 && m_programmeScrollOffset > m_blockOffset * m_blockSize))
1955   {
1956     m_programmeScrollOffset = m_blockOffset * m_blockSize;
1957     m_programmeScrollSpeed = 0;
1958   }
1959   m_programmeScrollLastTime = currentTime;
1960 }
1961
1962 void CGUIEPGGridContainer::GetCurrentLayouts()
1963 {
1964   m_channelLayout = NULL;
1965   for (unsigned int i = 0; i < m_channelLayouts.size(); i++)
1966   {
1967     if (m_channelLayouts[i].CheckCondition())
1968     {
1969       m_channelLayout = &m_channelLayouts[i];
1970       break;
1971     }
1972   }
1973   if (!m_channelLayout && !m_channelLayouts.empty())
1974     m_channelLayout = &m_channelLayouts[0];  // failsafe
1975
1976   m_focusedChannelLayout = NULL;
1977   for (unsigned int i = 0; i < m_focusedChannelLayouts.size(); i++)
1978   {
1979     if (m_focusedChannelLayouts[i].CheckCondition())
1980     {
1981       m_focusedChannelLayout = &m_focusedChannelLayouts[i];
1982       break;
1983     }
1984   }
1985   if (!m_focusedChannelLayout && !m_focusedChannelLayouts.empty())
1986     m_focusedChannelLayout = &m_focusedChannelLayouts[0];  // failsafe
1987
1988   m_programmeLayout = NULL;
1989   for (unsigned int i = 0; i < m_programmeLayouts.size(); i++)
1990   {
1991     if (m_programmeLayouts[i].CheckCondition())
1992     {
1993       m_programmeLayout = &m_programmeLayouts[i];
1994       break;
1995     }
1996   }
1997   if (!m_programmeLayout && !m_programmeLayouts.empty())
1998     m_programmeLayout = &m_programmeLayouts[0];  // failsafe
1999
2000   m_focusedProgrammeLayout = NULL;
2001   for (unsigned int i = 0; i < m_focusedProgrammeLayouts.size(); i++)
2002   {
2003     if (m_focusedProgrammeLayouts[i].CheckCondition())
2004     {
2005       m_focusedProgrammeLayout = &m_focusedProgrammeLayouts[i];
2006       break;
2007     }
2008   }
2009   if (!m_focusedProgrammeLayout && !m_focusedProgrammeLayouts.empty())
2010     m_focusedProgrammeLayout = &m_focusedProgrammeLayouts[0];  // failsafe
2011
2012   m_rulerLayout = NULL;
2013   for (unsigned int i = 0; i < m_rulerLayouts.size(); i++)
2014   {
2015     if (m_rulerLayouts[i].CheckCondition())
2016     {
2017       m_rulerLayout = &m_rulerLayouts[i];
2018       break;
2019     }
2020   }
2021   if (!m_rulerLayout && !m_rulerLayouts.empty())
2022     m_rulerLayout = &m_rulerLayouts[0];  // failsafe
2023 }
2024
2025 int CGUIEPGGridContainer::CorrectOffset(int offset, int cursor) const
2026 {
2027   return offset + cursor;
2028 }
2029
2030 void CGUIEPGGridContainer::SetRenderOffset(const CPoint &offset)
2031 {
2032   m_renderOffset = offset;
2033 }
2034
2035 void CGUIEPGGridContainer::FreeChannelMemory(int keepStart, int keepEnd)
2036 {
2037   if (keepStart < keepEnd)
2038   { // remove before keepStart and after keepEnd
2039     for (int i = 0; i < keepStart && i < (int)m_channelItems.size(); ++i)
2040       m_channelItems[i]->FreeMemory();
2041     for (int i = keepEnd + 1; i < (int)m_channelItems.size(); ++i)
2042       m_channelItems[i]->FreeMemory();
2043   }
2044   else
2045   { // wrapping
2046     for (int i = keepEnd + 1; i < keepStart && i < (int)m_channelItems.size(); ++i)
2047       m_channelItems[i]->FreeMemory();
2048   }
2049 }
2050
2051 void CGUIEPGGridContainer::FreeProgrammeMemory(int channel, int keepStart, int keepEnd)
2052 {
2053   if (keepStart < keepEnd)
2054   { // remove before keepStart and after keepEnd
2055     if (keepStart > 0 && keepStart < m_blocks)
2056     {
2057       // if item exist and block is not part of visible item
2058       CGUIListItemPtr last = m_gridIndex[channel][keepStart].item;
2059       for (int i = keepStart - 1 ; i > 0 ; i--)
2060       {
2061         if (m_gridIndex[channel][i].item && m_gridIndex[channel][i].item != last)
2062         {
2063           m_gridIndex[channel][i].item->FreeMemory();
2064           // FreeMemory() is smart enough to not cause any problems when called multiple times on same item
2065           // but we can make use of condition needed to not call FreeMemory() on item that is partially visible
2066           // to avoid calling FreeMemory() multiple times on item that ocupy few blocks in a row
2067           last = m_gridIndex[channel][i].item;
2068         }
2069       }
2070     }
2071
2072     if (keepEnd > 0 && keepEnd < m_blocks)
2073     {
2074       CGUIListItemPtr last = m_gridIndex[channel][keepEnd].item;
2075       for (int i = keepEnd + 1 ; i < m_blocks ; i++)
2076       {
2077         // if item exist and block is not part of visible item
2078         if (m_gridIndex[channel][i].item && m_gridIndex[channel][i].item != last)
2079         {
2080           m_gridIndex[channel][i].item->FreeMemory();
2081           // FreeMemory() is smart enough to not cause any problems when called multiple times on same item
2082           // but we can make use of condition needed to not call FreeMemory() on item that is partially visible
2083           // to avoid calling FreeMemory() multiple times on item that ocupy few blocks in a row
2084           last = m_gridIndex[channel][i].item;
2085         }
2086       }
2087     }
2088   }
2089 }
2090
2091 void CGUIEPGGridContainer::FreeRulerMemory(int keepStart, int keepEnd)
2092 {
2093   if (keepStart < keepEnd)
2094   { // remove before keepStart and after keepEnd
2095     for (int i = 1; i < keepStart && i < (int)m_rulerItems.size(); ++i)
2096       m_rulerItems[i]->FreeMemory();
2097     for (int i = keepEnd + 1; i < (int)m_rulerItems.size(); ++i)
2098       m_rulerItems[i]->FreeMemory();
2099   }
2100   else
2101   { // wrapping
2102     for (int i = keepEnd + 1; i < keepStart && i < (int)m_rulerItems.size(); ++i)
2103     {
2104       if (i == 0)
2105         continue;
2106       m_rulerItems[i]->FreeMemory();
2107     }
2108   }
2109 }
2110
2111 void CGUIEPGGridContainer::GetChannelCacheOffsets(int &cacheBefore, int &cacheAfter)
2112 {
2113   if (m_channelScrollSpeed > 0)
2114   {
2115     cacheBefore = 0;
2116     cacheAfter = m_cacheChannelItems;
2117   }
2118   else if (m_channelScrollSpeed < 0)
2119   {
2120     cacheBefore = m_cacheChannelItems;
2121     cacheAfter = 0;
2122   }
2123   else
2124   {
2125     cacheBefore = m_cacheChannelItems / 2;
2126     cacheAfter = m_cacheChannelItems / 2;
2127   }
2128 }
2129
2130 void CGUIEPGGridContainer::GetProgrammeCacheOffsets(int &cacheBefore, int &cacheAfter)
2131 {
2132   if (m_programmeScrollSpeed > 0)
2133   {
2134     cacheBefore = 0;
2135     cacheAfter = m_cacheProgrammeItems;
2136   }
2137   else if (m_programmeScrollSpeed < 0)
2138   {
2139     cacheBefore = m_cacheProgrammeItems;
2140     cacheAfter = 0;
2141   }
2142   else
2143   {
2144     cacheBefore = m_cacheProgrammeItems / 2;
2145     cacheAfter = m_cacheProgrammeItems / 2;
2146   }
2147 }