1 /*
2  *  Copyright (C) 2012-2018 Team Kodi
3  *  This file is part of Kodi - https://kodi.tv
4  *
5  *  SPDX-License-Identifier: GPL-2.0-or-later
6  *  See LICENSES/README.md for more information.
7  */
8 
9 #include "GUIEPGGridContainer.h"
10 
11 #include "FileItem.h"
12 #include "guilib/DirtyRegion.h"
13 #include "guilib/GUIAction.h"
14 #include "guilib/GUIMessage.h"
15 #include "guilib/guiinfo/GUIInfoLabels.h"
16 #include "input/Key.h"
17 #include "input/actions/Action.h"
18 #include "input/actions/ActionIDs.h"
19 #include "messaging/ApplicationMessenger.h"
20 #include "pvr/PVRManager.h"
21 #include "pvr/channels/PVRChannel.h"
22 #include "pvr/epg/EpgInfoTag.h"
23 #include "pvr/guilib/GUIEPGGridContainerModel.h"
24 #include "utils/MathUtils.h"
25 #include "utils/StringUtils.h"
26 #include "utils/Variant.h"
27 
28 #include <algorithm>
29 #include <memory>
30 #include <string>
31 #include <utility>
32 
33 #include <tinyxml.h>
34 
35 using namespace PVR;
36 
37 #define BLOCKJUMP    4 // how many blocks are jumped with each analogue scroll action
38 static const int BLOCK_SCROLL_OFFSET = 60 / CGUIEPGGridContainerModel::MINSPERBLOCK; // how many blocks are jumped if we are at left/right edge of grid
39 
CGUIEPGGridContainer(int parentID,int controlID,float posX,float posY,float width,float height,ORIENTATION orientation,int scrollTime,int preloadItems,int timeBlocks,int rulerUnit,const CTextureInfo & progressIndicatorTexture)40 CGUIEPGGridContainer::CGUIEPGGridContainer(int parentID,
41                                            int controlID,
42                                            float posX,
43                                            float posY,
44                                            float width,
45                                            float height,
46                                            ORIENTATION orientation,
47                                            int scrollTime,
48                                            int preloadItems,
49                                            int timeBlocks,
50                                            int rulerUnit,
51                                            const CTextureInfo& progressIndicatorTexture)
52   : IGUIContainer(parentID, controlID, posX, posY, width, height),
53     m_orientation(orientation),
54     m_channelLayout(nullptr),
55     m_focusedChannelLayout(nullptr),
56     m_programmeLayout(nullptr),
57     m_focusedProgrammeLayout(nullptr),
58     m_rulerLayout(nullptr),
59     m_rulerDateLayout(nullptr),
60     m_pageControl(0),
61     m_rulerUnit(rulerUnit),
62     m_channelsPerPage(0),
63     m_programmesPerPage(0),
64     m_channelCursor(0),
65     m_channelOffset(0),
66     m_blocksPerPage(timeBlocks),
67     m_blockCursor(0),
68     m_blockOffset(0),
69     m_blockTravelAxis(0),
70     m_cacheChannelItems(preloadItems),
71     m_cacheProgrammeItems(preloadItems),
72     m_cacheRulerItems(preloadItems),
73     m_rulerDateHeight(0),
74     m_rulerDateWidth(0),
75     m_rulerPosX(0),
76     m_rulerPosY(0),
77     m_rulerHeight(0),
78     m_rulerWidth(0),
79     m_channelPosX(0),
80     m_channelPosY(0),
81     m_channelHeight(0),
82     m_channelWidth(0),
83     m_gridPosX(0),
84     m_gridPosY(0),
85     m_gridWidth(0),
86     m_gridHeight(0),
87     m_blockSize(0),
88     m_analogScrollCount(0),
89     m_guiProgressIndicatorTexture(
90         CGUITexture::CreateTexture(posX, posY, width, height, progressIndicatorTexture)),
91     m_scrollTime(scrollTime ? scrollTime : 1),
92     m_programmeScrollLastTime(0),
93     m_programmeScrollSpeed(0),
94     m_programmeScrollOffset(0),
95     m_channelScrollLastTime(0),
96     m_channelScrollSpeed(0),
97     m_channelScrollOffset(0),
98     m_gridModel(new CGUIEPGGridContainerModel)
99 {
100   ControlType = GUICONTAINER_EPGGRID;
101 }
102 
CGUIEPGGridContainer(const CGUIEPGGridContainer & other)103 CGUIEPGGridContainer::CGUIEPGGridContainer(const CGUIEPGGridContainer& other)
104   : IGUIContainer(other),
105     m_renderOffset(other.m_renderOffset),
106     m_orientation(other.m_orientation),
107     m_channelLayouts(other.m_channelLayouts),
108     m_focusedChannelLayouts(other.m_focusedChannelLayouts),
109     m_focusedProgrammeLayouts(other.m_focusedProgrammeLayouts),
110     m_programmeLayouts(other.m_programmeLayouts),
111     m_rulerLayouts(other.m_rulerLayouts),
112     m_rulerDateLayouts(other.m_rulerDateLayouts),
113     m_channelLayout(other.m_channelLayout),
114     m_focusedChannelLayout(other.m_focusedChannelLayout),
115     m_programmeLayout(other.m_programmeLayout),
116     m_focusedProgrammeLayout(other.m_focusedProgrammeLayout),
117     m_rulerLayout(other.m_rulerLayout),
118     m_rulerDateLayout(other.m_rulerDateLayout),
119     m_pageControl(other.m_pageControl),
120     m_rulerUnit(other.m_rulerUnit),
121     m_channelsPerPage(other.m_channelsPerPage),
122     m_programmesPerPage(other.m_programmesPerPage),
123     m_channelCursor(other.m_channelCursor),
124     m_channelOffset(other.m_channelOffset),
125     m_blocksPerPage(other.m_blocksPerPage),
126     m_blockCursor(other.m_blockCursor),
127     m_blockOffset(other.m_blockOffset),
128     m_blockTravelAxis(other.m_blockTravelAxis),
129     m_cacheChannelItems(other.m_cacheChannelItems),
130     m_cacheProgrammeItems(other.m_cacheProgrammeItems),
131     m_cacheRulerItems(other.m_cacheRulerItems),
132     m_rulerDateHeight(other.m_rulerDateHeight),
133     m_rulerDateWidth(other.m_rulerDateWidth),
134     m_rulerPosX(other.m_rulerPosX),
135     m_rulerPosY(other.m_rulerPosY),
136     m_rulerHeight(other.m_rulerHeight),
137     m_rulerWidth(other.m_rulerWidth),
138     m_channelPosX(other.m_channelPosX),
139     m_channelPosY(other.m_channelPosY),
140     m_channelHeight(other.m_channelHeight),
141     m_channelWidth(other.m_channelWidth),
142     m_gridPosX(other.m_gridPosX),
143     m_gridPosY(other.m_gridPosY),
144     m_gridWidth(other.m_gridWidth),
145     m_gridHeight(other.m_gridHeight),
146     m_blockSize(other.m_blockSize),
147     m_analogScrollCount(other.m_analogScrollCount),
148     m_guiProgressIndicatorTexture(other.m_guiProgressIndicatorTexture->Clone()),
149     m_lastItem(other.m_lastItem),
150     m_lastChannel(other.m_lastChannel),
151     m_scrollTime(other.m_scrollTime),
152     m_programmeScrollLastTime(other.m_programmeScrollLastTime),
153     m_programmeScrollSpeed(other.m_programmeScrollSpeed),
154     m_programmeScrollOffset(other.m_programmeScrollOffset),
155     m_channelScrollLastTime(other.m_channelScrollLastTime),
156     m_channelScrollSpeed(other.m_channelScrollSpeed),
157     m_channelScrollOffset(other.m_channelScrollOffset),
158     m_gridModel(new CGUIEPGGridContainerModel(*other.m_gridModel)),
159     m_updatedGridModel(other.m_updatedGridModel
160                            ? new CGUIEPGGridContainerModel(*other.m_updatedGridModel)
161                            : nullptr),
162     m_itemStartBlock(other.m_itemStartBlock)
163 {
164 }
165 
HasData() const166 bool CGUIEPGGridContainer::HasData() const
167 {
168   return m_gridModel && m_gridModel->HasChannelItems();
169 }
170 
AllocResources()171 void CGUIEPGGridContainer::AllocResources()
172 {
173   IGUIContainer::AllocResources();
174   m_guiProgressIndicatorTexture->AllocResources();
175 }
176 
FreeResources(bool immediately)177 void CGUIEPGGridContainer::FreeResources(bool immediately)
178 {
179   m_guiProgressIndicatorTexture->FreeResources(immediately);
180   IGUIContainer::FreeResources(immediately);
181 }
182 
SetPageControl(int id)183 void CGUIEPGGridContainer::SetPageControl(int id)
184 {
185   m_pageControl = id;
186 }
187 
Process(unsigned int currentTime,CDirtyRegionList & dirtyregions)188 void CGUIEPGGridContainer::Process(unsigned int currentTime, CDirtyRegionList& dirtyregions)
189 {
190   ValidateOffset();
191 
192   if (m_bInvalidated)
193   {
194     UpdateLayout();
195 
196     if (m_pageControl)
197     {
198       int iItemsPerPage;
199       int iTotalItems;
200 
201       if (m_orientation == VERTICAL)
202       {
203         iItemsPerPage = m_channelsPerPage;
204         iTotalItems = m_gridModel->ChannelItemsSize();
205       }
206       else
207       {
208         iItemsPerPage = m_blocksPerPage;
209         iTotalItems = m_gridModel->GridItemsSize();
210       }
211 
212       CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), m_pageControl, iItemsPerPage, iTotalItems);
213       SendWindowMessage(msg);
214     }
215   }
216 
217   UpdateScrollOffset(currentTime);
218   ProcessChannels(currentTime, dirtyregions);
219   ProcessRulerDate(currentTime, dirtyregions);
220   ProcessRuler(currentTime, dirtyregions);
221   ProcessProgrammeGrid(currentTime, dirtyregions);
222   ProcessProgressIndicator(currentTime, dirtyregions);
223 
224   if (m_pageControl)
225   {
226     int iItem = (m_orientation == VERTICAL)
227       ? MathUtils::round_int(m_channelScrollOffset / m_channelHeight)
228       : MathUtils::round_int(m_programmeScrollOffset / (m_gridHeight / m_blocksPerPage));
229 
230     CGUIMessage msg(GUI_MSG_ITEM_SELECT, GetID(), m_pageControl, iItem);
231     SendWindowMessage(msg);
232   }
233 
234   CGUIControl::Process(currentTime, dirtyregions);
235 }
236 
Render()237 void CGUIEPGGridContainer::Render()
238 {
239   RenderChannels();
240   RenderRulerDate();
241   RenderRuler();
242   RenderProgrammeGrid();
243   RenderProgressIndicator();
244 
245   CGUIControl::Render();
246 }
247 
ProcessChannels(unsigned int currentTime,CDirtyRegionList & dirtyregions)248 void CGUIEPGGridContainer::ProcessChannels(unsigned int currentTime, CDirtyRegionList& dirtyregions)
249 {
250   HandleChannels(false, currentTime, dirtyregions);
251 }
252 
RenderChannels()253 void CGUIEPGGridContainer::RenderChannels()
254 {
255   // params not needed for render.
256   unsigned int dummyTime = 0;
257   CDirtyRegionList dummyRegions;
258   HandleChannels(true, dummyTime, dummyRegions);
259 }
260 
ProcessRulerDate(unsigned int currentTime,CDirtyRegionList & dirtyregions)261 void CGUIEPGGridContainer::ProcessRulerDate(unsigned int currentTime, CDirtyRegionList& dirtyregions)
262 {
263   HandleRulerDate(false, currentTime, dirtyregions);
264 }
265 
RenderRulerDate()266 void CGUIEPGGridContainer::RenderRulerDate()
267 {
268   // params not needed for render.
269   unsigned int dummyTime = 0;
270   CDirtyRegionList dummyRegions;
271   HandleRulerDate(true, dummyTime, dummyRegions);
272 }
273 
ProcessRuler(unsigned int currentTime,CDirtyRegionList & dirtyregions)274 void CGUIEPGGridContainer::ProcessRuler(unsigned int currentTime, CDirtyRegionList& dirtyregions)
275 {
276   HandleRuler(false, currentTime, dirtyregions);
277 }
278 
RenderRuler()279 void CGUIEPGGridContainer::RenderRuler()
280 {
281   // params not needed for render.
282   unsigned int dummyTime = 0;
283   CDirtyRegionList dummyRegions;
284   HandleRuler(true, dummyTime, dummyRegions);
285 }
286 
ProcessProgrammeGrid(unsigned int currentTime,CDirtyRegionList & dirtyregions)287 void CGUIEPGGridContainer::ProcessProgrammeGrid(unsigned int currentTime, CDirtyRegionList& dirtyregions)
288 {
289   HandleProgrammeGrid(false, currentTime, dirtyregions);
290 }
291 
RenderProgrammeGrid()292 void CGUIEPGGridContainer::RenderProgrammeGrid()
293 {
294   // params not needed for render.
295   unsigned int dummyTime = 0;
296   CDirtyRegionList dummyRegions;
297   HandleProgrammeGrid(true, dummyTime, dummyRegions);
298 }
299 
GetCurrentTimePositionOnPage() const300 float CGUIEPGGridContainer::GetCurrentTimePositionOnPage() const
301 {
302   if (!m_gridModel->GetGridStart().IsValid())
303     return -1.0f;
304 
305   const CDateTimeSpan startDelta(CDateTime::GetUTCDateTime() - m_gridModel->GetGridStart());
306   const float fPos = (startDelta.GetSecondsTotal() * m_blockSize) /
307                          (CGUIEPGGridContainerModel::MINSPERBLOCK * 60) -
308                      GetProgrammeScrollOffsetPos();
309   return std::min(fPos, m_orientation == VERTICAL ? m_gridWidth : m_gridHeight);
310 }
311 
GetProgressIndicatorWidth() const312 float CGUIEPGGridContainer::GetProgressIndicatorWidth() const
313 {
314   return (m_orientation == VERTICAL) ? GetCurrentTimePositionOnPage() : m_rulerWidth + m_gridWidth;
315 }
316 
GetProgressIndicatorHeight() const317 float CGUIEPGGridContainer::GetProgressIndicatorHeight() const
318 {
319   return (m_orientation == VERTICAL) ? m_rulerHeight + m_gridHeight : GetCurrentTimePositionOnPage();
320 }
321 
ProcessProgressIndicator(unsigned int currentTime,CDirtyRegionList & dirtyregions)322 void CGUIEPGGridContainer::ProcessProgressIndicator(unsigned int currentTime, CDirtyRegionList& dirtyregions)
323 {
324   float width = GetProgressIndicatorWidth();
325   float height = GetProgressIndicatorHeight();
326 
327   if (width > 0 && height > 0)
328   {
329     m_guiProgressIndicatorTexture->SetVisible(true);
330     m_guiProgressIndicatorTexture->SetPosition(m_rulerPosX + m_renderOffset.x,
331                                                m_rulerPosY + m_renderOffset.y);
332     m_guiProgressIndicatorTexture->SetWidth(width);
333     m_guiProgressIndicatorTexture->SetHeight(height);
334   }
335   else
336   {
337     m_guiProgressIndicatorTexture->SetVisible(false);
338   }
339 
340   m_guiProgressIndicatorTexture->Process(currentTime);
341 }
342 
RenderProgressIndicator()343 void CGUIEPGGridContainer::RenderProgressIndicator()
344 {
345   if (CServiceBroker::GetWinSystem()->GetGfxContext().SetClipRegion(m_rulerPosX, m_rulerPosY, GetProgressIndicatorWidth(), GetProgressIndicatorHeight()))
346   {
347     m_guiProgressIndicatorTexture->SetDiffuseColor(m_diffuseColor);
348     m_guiProgressIndicatorTexture->Render();
349     CServiceBroker::GetWinSystem()->GetGfxContext().RestoreClipRegion();
350   }
351 }
352 
ProcessItem(float posX,float posY,const CFileItemPtr & item,CFileItemPtr & lastitem,bool focused,CGUIListItemLayout * normallayout,CGUIListItemLayout * focusedlayout,unsigned int currentTime,CDirtyRegionList & dirtyregions,float resize)353 void CGUIEPGGridContainer::ProcessItem(float posX, float posY, const CFileItemPtr& item, CFileItemPtr& lastitem,
354   bool focused, CGUIListItemLayout* normallayout, CGUIListItemLayout* focusedlayout,
355   unsigned int currentTime, CDirtyRegionList& dirtyregions, float resize /* = -1.0f */)
356 {
357   if (!normallayout || !focusedlayout)
358     return;
359 
360   // set the origin
361   CServiceBroker::GetWinSystem()->GetGfxContext().SetOrigin(posX, posY);
362 
363   if (m_bInvalidated)
364     item->SetInvalid();
365 
366   if (focused)
367   {
368     if (!item->GetFocusedLayout())
369     {
370       item->SetFocusedLayout(CGUIListItemLayoutPtr(new CGUIListItemLayout(*focusedlayout)));
371     }
372 
373     if (resize != -1.0f)
374     {
375       if (m_orientation == VERTICAL)
376         item->GetFocusedLayout()->SetWidth(resize);
377       else
378         item->GetFocusedLayout()->SetHeight(resize);
379     }
380 
381     if (item != lastitem || !HasFocus())
382       item->GetFocusedLayout()->SetFocusedItem(0);
383 
384     if (item != lastitem && HasFocus())
385     {
386       item->GetFocusedLayout()->ResetAnimation(ANIM_TYPE_UNFOCUS);
387 
388       unsigned int subItem = 1;
389       if (lastitem && lastitem->GetFocusedLayout())
390         subItem = lastitem->GetFocusedLayout()->GetFocusedItem();
391 
392       item->GetFocusedLayout()->SetFocusedItem(subItem ? subItem : 1);
393     }
394 
395     item->GetFocusedLayout()->Process(item.get(), m_parentID, currentTime, dirtyregions);
396     lastitem = item;
397   }
398   else
399   {
400     if (!item->GetLayout())
401     {
402       item->SetLayout(CGUIListItemLayoutPtr(new CGUIListItemLayout(*normallayout)));
403     }
404 
405     if (resize != -1.0f)
406     {
407       if (m_orientation == VERTICAL)
408         item->GetLayout()->SetWidth(resize);
409       else
410         item->GetLayout()->SetHeight(resize);
411     }
412 
413     if (item->GetFocusedLayout())
414       item->GetFocusedLayout()->SetFocusedItem(0);
415 
416     if (item->GetFocusedLayout() && item->GetFocusedLayout()->IsAnimating(ANIM_TYPE_UNFOCUS))
417       item->GetFocusedLayout()->Process(item.get(), m_parentID, currentTime, dirtyregions);
418     else
419       item->GetLayout()->Process(item.get(), m_parentID, currentTime, dirtyregions);
420   }
421   CServiceBroker::GetWinSystem()->GetGfxContext().RestoreOrigin();
422 }
423 
RenderItem(float posX,float posY,CGUIListItem * item,bool focused)424 void CGUIEPGGridContainer::RenderItem(float posX, float posY, CGUIListItem* item, bool focused)
425 {
426   // set the origin
427   CServiceBroker::GetWinSystem()->GetGfxContext().SetOrigin(posX, posY);
428 
429   if (focused)
430   {
431     if (item->GetFocusedLayout())
432       item->GetFocusedLayout()->Render(item, m_parentID);
433   }
434   else
435   {
436     if (item->GetFocusedLayout() && item->GetFocusedLayout()->IsAnimating(ANIM_TYPE_UNFOCUS))
437       item->GetFocusedLayout()->Render(item, m_parentID);
438     else if (item->GetLayout())
439       item->GetLayout()->Render(item, m_parentID);
440   }
441   CServiceBroker::GetWinSystem()->GetGfxContext().RestoreOrigin();
442 }
443 
OnAction(const CAction & action)444 bool CGUIEPGGridContainer::OnAction(const CAction& action)
445 {
446   switch (action.GetID())
447   {
448     case ACTION_MOVE_LEFT:
449     case ACTION_MOVE_RIGHT:
450     case ACTION_MOVE_DOWN:
451     case ACTION_MOVE_UP:
452     case ACTION_NAV_BACK:
453       // use base class implementation
454       return CGUIControl::OnAction(action);
455 
456     case ACTION_NEXT_ITEM:
457       // skip +12h
458       ScrollToBlockOffset(m_blockOffset + (12 * 60  / CGUIEPGGridContainerModel::MINSPERBLOCK));
459       SetBlock(m_blockCursor);
460       return true;
461 
462     case ACTION_PREV_ITEM:
463       // skip -12h
464       ScrollToBlockOffset(m_blockOffset - (12 * 60 / CGUIEPGGridContainerModel::MINSPERBLOCK));
465       SetBlock(m_blockCursor);
466       return true;
467 
468     case REMOTE_0:
469       GoToNow();
470       return true;
471 
472     case ACTION_PAGE_UP:
473       if (m_orientation == VERTICAL)
474       {
475         if (m_channelOffset == 0)
476         {
477           // already on the first page, so move to the first item
478           SetChannel(0);
479         }
480         else
481         {
482           // scroll up to the previous page
483           ChannelScroll(-m_channelsPerPage);
484         }
485       }
486       else
487       {
488         if (m_blockOffset == 0)
489         {
490           // already on the first page, so move to the first item
491           SetBlock(0);
492         }
493         else
494         {
495           // scroll up to the previous page
496           ProgrammesScroll(-m_blocksPerPage);
497         }
498       }
499       return true;
500 
501     case ACTION_PAGE_DOWN:
502       if (m_orientation == VERTICAL)
503       {
504         if (m_channelOffset == m_gridModel->ChannelItemsSize() - m_channelsPerPage ||
505             m_gridModel->ChannelItemsSize() < m_channelsPerPage)
506         {
507           // already at the last page, so move to the last item.
508           SetChannel(m_gridModel->GetLastChannel() - m_channelOffset);
509         }
510         else
511         {
512           // scroll down to the next page
513           ChannelScroll(m_channelsPerPage);
514         }
515       }
516       else
517       {
518         if (m_blockOffset == m_gridModel->GridItemsSize() - m_blocksPerPage ||
519             m_gridModel->GridItemsSize() < m_blocksPerPage)
520         {
521           // already at the last page, so move to the last item.
522           SetBlock(m_gridModel->GetLastBlock() - m_blockOffset);
523         }
524         else
525         {
526           // scroll down to the next page
527           ProgrammesScroll(m_blocksPerPage);
528         }
529       }
530 
531       return true;
532 
533     // smooth scrolling (for analog controls)
534     case ACTION_TELETEXT_RED:
535     case ACTION_TELETEXT_GREEN:
536     case ACTION_SCROLL_UP: // left horizontal scrolling
537       if (m_orientation == VERTICAL)
538       {
539         int blocksToJump = action.GetID() == ACTION_TELETEXT_RED ? m_blocksPerPage / 2 : m_blocksPerPage / 4;
540 
541         m_analogScrollCount += action.GetAmount() * action.GetAmount();
542         bool handled = false;
543 
544         while (m_analogScrollCount > 0.4)
545         {
546           handled = true;
547           m_analogScrollCount -= 0.4f;
548 
549           if (m_blockOffset > 0 && m_blockCursor <= m_blocksPerPage / 2)
550             ProgrammesScroll(-blocksToJump);
551           else if (m_blockCursor > blocksToJump)
552             SetBlock(m_blockCursor - blocksToJump);
553         }
554         return handled;
555       }
556       else
557       {
558         int channelsToJump = action.GetID() == ACTION_TELETEXT_RED ? m_channelsPerPage / 2 : m_channelsPerPage / 4;
559 
560         m_analogScrollCount += action.GetAmount() * action.GetAmount();
561         bool handled = false;
562 
563         while (m_analogScrollCount > 0.4)
564         {
565           handled = true;
566           m_analogScrollCount -= 0.4f;
567 
568           if (m_channelOffset > 0 && m_channelCursor <= m_channelsPerPage / 2)
569             ChannelScroll(-channelsToJump);
570           else if (m_channelCursor > channelsToJump)
571             SetChannel(m_channelCursor - channelsToJump);
572         }
573         return handled;
574       }
575       break;
576 
577     case ACTION_TELETEXT_BLUE:
578     case ACTION_TELETEXT_YELLOW:
579     case ACTION_SCROLL_DOWN: // right horizontal scrolling
580       if (m_orientation == VERTICAL)
581       {
582         int blocksToJump = action.GetID() == ACTION_TELETEXT_BLUE ? m_blocksPerPage / 2 : m_blocksPerPage / 4;
583 
584         m_analogScrollCount += action.GetAmount() * action.GetAmount();
585         bool handled = false;
586 
587         while (m_analogScrollCount > 0.4)
588         {
589           handled = true;
590           m_analogScrollCount -= 0.4f;
591 
592           if (m_blockOffset + m_blocksPerPage < m_gridModel->GridItemsSize() &&
593               m_blockCursor >= m_blocksPerPage / 2)
594             ProgrammesScroll(blocksToJump);
595           else if (m_blockCursor < m_blocksPerPage - blocksToJump &&
596                    m_blockOffset + m_blockCursor < m_gridModel->GridItemsSize() - blocksToJump)
597             SetBlock(m_blockCursor + blocksToJump);
598         }
599         return handled;
600       }
601       else
602       {
603         int channelsToJump = action.GetID() == ACTION_TELETEXT_BLUE ? m_channelsPerPage / 2 : m_channelsPerPage / 4;
604 
605         m_analogScrollCount += action.GetAmount() * action.GetAmount();
606         bool handled = false;
607 
608         while (m_analogScrollCount > 0.4)
609         {
610           handled = true;
611           m_analogScrollCount -= 0.4f;
612 
613           if (m_channelOffset + m_channelsPerPage < m_gridModel->ChannelItemsSize() && m_channelCursor >= m_channelsPerPage / 2)
614             ChannelScroll(channelsToJump);
615           else if (m_channelCursor < m_channelsPerPage - channelsToJump && m_channelOffset + m_channelCursor < m_gridModel->ChannelItemsSize() - channelsToJump)
616             SetChannel(m_channelCursor + channelsToJump);
617         }
618         return handled;
619       }
620       break;
621 
622     default:
623       if (action.GetID())
624         return OnClick(action.GetID());
625 
626       break;
627   }
628 
629   return false;
630 }
631 
OnMessage(CGUIMessage & message)632 bool CGUIEPGGridContainer::OnMessage(CGUIMessage& message)
633 {
634   if (message.GetControlId() == GetID())
635   {
636     switch (message.GetMessage())
637     {
638       case GUI_MSG_PAGE_CHANGE:
639         if (message.GetSenderId() == m_pageControl && IsVisible())
640         {
641           if (m_orientation == VERTICAL)
642           {
643             ScrollToChannelOffset(message.GetParam1());
644             SetChannel(m_channelCursor);
645           }
646           else
647           {
648             ScrollToBlockOffset(message.GetParam1());
649             SetBlock(m_blockCursor);
650           }
651           return true;
652         }
653         break;
654 
655       case GUI_MSG_LABEL_BIND:
656         UpdateItems();
657         return true;
658 
659       case GUI_MSG_REFRESH_LIST:
660         // update our list contents
661         m_gridModel->SetInvalid();
662         break;
663     }
664   }
665 
666   return CGUIControl::OnMessage(message);
667 }
668 
UpdateItems()669 void CGUIEPGGridContainer::UpdateItems()
670 {
671   CSingleLock lock(m_critSection);
672 
673   if (!m_updatedGridModel)
674     return;
675 
676   // Save currently selected epg tag and grid coordinates. Selection shall be restored after update.
677   std::shared_ptr<CPVREpgInfoTag> prevSelectedEpgTag;
678   if (HasData())
679     prevSelectedEpgTag =
680         m_gridModel->GetGridItem(m_channelCursor + m_channelOffset, m_blockCursor + m_blockOffset)
681             ->GetEPGInfoTag();
682 
683   const int oldChannelIndex = m_channelOffset + m_channelCursor;
684   const int oldBlockIndex = m_blockOffset + m_blockCursor;
685   const CDateTime oldGridStart(m_gridModel->GetGridStart());
686   int eventOffset = oldBlockIndex;
687   int newChannelIndex = oldChannelIndex;
688   int newBlockIndex = oldBlockIndex;
689   int channelUid = -1;
690   unsigned int broadcastUid = 0;
691 
692   if (prevSelectedEpgTag)
693   {
694     // get the block offset relative to the first block of the selected event
695     eventOffset =
696         oldBlockIndex - m_gridModel->GetGridItemStartBlock(oldChannelIndex, oldBlockIndex);
697 
698     if (!prevSelectedEpgTag->IsGapTag()) // "normal" tag selected
699     {
700       if (oldGridStart >= prevSelectedEpgTag->StartAsUTC())
701       {
702         // start of previously selected event is before grid start
703         newBlockIndex = eventOffset;
704       }
705       else
706       {
707         newBlockIndex = m_gridModel->GetFirstEventBlock(prevSelectedEpgTag) + eventOffset;
708       }
709 
710       channelUid = prevSelectedEpgTag->UniqueChannelID();
711       broadcastUid = prevSelectedEpgTag->UniqueBroadcastID();
712     }
713     else // "gap" tag selected
714     {
715       channelUid = prevSelectedEpgTag->UniqueChannelID();
716 
717       // As gap tags do not have a unique broadcast id, we will look for the real tag preceeding
718       // the gap tag and add the respective offset to restore the gap tag selection, assuming that
719       // the real tag is still the predecessor of the gap tag after the grid model update.
720 
721       const std::shared_ptr<CFileItem> prevItem = GetPrevItem().first;
722       if (prevItem)
723       {
724         const std::shared_ptr<CPVREpgInfoTag> tag = prevItem->GetEPGInfoTag();
725         if (tag && !tag->IsGapTag())
726         {
727           if (oldGridStart >= tag->StartAsUTC())
728           {
729             // start of previously selected event is before grid start
730             newBlockIndex = eventOffset;
731           }
732           else
733           {
734             newBlockIndex = m_gridModel->GetFirstEventBlock(tag);
735             eventOffset += m_gridModel->GetFirstEventBlock(prevSelectedEpgTag) - newBlockIndex;
736           }
737 
738           broadcastUid = tag->UniqueBroadcastID();
739         }
740       }
741     }
742   }
743 
744   m_lastItem = nullptr;
745   m_lastChannel = nullptr;
746 
747   // always use asynchronously precalculated grid data.
748   m_gridModel = std::move(m_updatedGridModel);
749 
750   if (prevSelectedEpgTag)
751   {
752     if (oldGridStart != m_gridModel->GetGridStart())
753     {
754       // grid start changed. block offset for selected event might have changed.
755       newBlockIndex += m_gridModel->GetBlock(oldGridStart);
756       if (newBlockIndex < 0 || newBlockIndex > m_gridModel->GetLastBlock())
757       {
758         // previous selection is no longer in grid.
759         SetInvalid();
760         m_bEnableChannelScrolling = false;
761         GoToChannel(newChannelIndex);
762         m_bEnableProgrammeScrolling = false;
763         GoToNow();
764         return;
765       }
766     }
767   }
768 
769   if (prevSelectedEpgTag)
770   {
771     if (newChannelIndex >= m_gridModel->ChannelItemsSize() ||
772         newBlockIndex >= m_gridModel->GridItemsSize() ||
773         m_gridModel->GetGridItem(newChannelIndex, newBlockIndex)->GetEPGInfoTag() !=
774             prevSelectedEpgTag)
775     {
776       int iChannelIndex = CGUIEPGGridContainerModel::INVALID_INDEX;
777       int iBlockIndex = CGUIEPGGridContainerModel::INVALID_INDEX;
778       m_gridModel->FindChannelAndBlockIndex(channelUid, broadcastUid, eventOffset, iChannelIndex, iBlockIndex);
779 
780       if (iBlockIndex != CGUIEPGGridContainerModel::INVALID_INDEX)
781       {
782         newBlockIndex = iBlockIndex;
783       }
784       else if (newBlockIndex > m_gridModel->GetLastBlock())
785       {
786         // default to now
787         newBlockIndex = m_gridModel->GetNowBlock();
788 
789         if (newBlockIndex > m_gridModel->GetLastBlock())
790         {
791           // last block is in the past. default to last block
792           newBlockIndex = m_gridModel->GetLastBlock();
793         }
794       }
795 
796       if (iChannelIndex != CGUIEPGGridContainerModel::INVALID_INDEX)
797       {
798         newChannelIndex = iChannelIndex;
799       }
800       else if (newChannelIndex >= m_gridModel->ChannelItemsSize() ||
801                (m_gridModel->GetGridItem(newChannelIndex, newBlockIndex)->GetEPGInfoTag()->UniqueChannelID() != prevSelectedEpgTag->UniqueChannelID() &&
802                 m_gridModel->GetGridItem(newChannelIndex, newBlockIndex)->GetEPGInfoTag()->ClientID() != prevSelectedEpgTag->ClientID()))
803       {
804         // default to first channel
805         newChannelIndex = 0;
806       }
807     }
808 
809     // restore previous selection.
810     if (newChannelIndex == oldChannelIndex && newBlockIndex == oldBlockIndex)
811     {
812       // same coordinates, keep current grid view port
813       UpdateItem();
814     }
815     else
816     {
817       // new coordinates, move grid view port accordingly
818       SetInvalid();
819 
820       if (newBlockIndex != oldBlockIndex)
821       {
822         m_bEnableProgrammeScrolling = false;
823         GoToBlock(newBlockIndex);
824       }
825 
826       if (newChannelIndex != oldChannelIndex)
827       {
828         m_bEnableChannelScrolling = false;
829         GoToChannel(newChannelIndex);
830       }
831     }
832   }
833   else
834   {
835     // no previous selection, goto now
836     SetInvalid();
837     m_bEnableProgrammeScrolling = false;
838     GoToNow();
839   }
840 }
841 
GetChannelScrollOffsetPos() const842 float CGUIEPGGridContainer::GetChannelScrollOffsetPos() const
843 {
844   if (m_bEnableChannelScrolling)
845     return m_channelScrollOffset;
846   else
847     return m_channelOffset * m_channelLayout->Size(m_orientation);
848 }
849 
GetProgrammeScrollOffsetPos() const850 float CGUIEPGGridContainer::GetProgrammeScrollOffsetPos() const
851 {
852   if (m_bEnableProgrammeScrolling)
853     return m_programmeScrollOffset;
854   else
855     return m_blockOffset * m_blockSize;
856 }
857 
GetChannelScrollOffset(CGUIListItemLayout * layout) const858 int CGUIEPGGridContainer::GetChannelScrollOffset(CGUIListItemLayout* layout) const
859 {
860   if (m_bEnableChannelScrolling)
861     return MathUtils::round_int(m_channelScrollOffset / layout->Size(m_orientation));
862   else
863     return m_channelOffset;
864 }
865 
GetProgrammeScrollOffset() const866 int CGUIEPGGridContainer::GetProgrammeScrollOffset() const
867 {
868   if (m_bEnableProgrammeScrolling)
869     return MathUtils::round_int(m_programmeScrollOffset / m_blockSize);
870   else
871     return m_blockOffset;
872 }
873 
ChannelScroll(int amount)874 void CGUIEPGGridContainer::ChannelScroll(int amount)
875 {
876   // increase or decrease the vertical offset
877   int offset = m_channelOffset + amount;
878 
879   if (offset > m_gridModel->ChannelItemsSize() - m_channelsPerPage)
880     offset = m_gridModel->ChannelItemsSize() - m_channelsPerPage;
881 
882   if (offset < 0)
883     offset = 0;
884 
885   ScrollToChannelOffset(offset);
886   SetChannel(m_channelCursor);
887 }
888 
ProgrammesScroll(int amount)889 void CGUIEPGGridContainer::ProgrammesScroll(int amount)
890 {
891   // increase or decrease the horizontal offset
892   ScrollToBlockOffset(m_blockOffset + amount);
893   SetBlock(m_blockCursor);
894 }
895 
OnUp()896 void CGUIEPGGridContainer::OnUp()
897 {
898   if (m_orientation == VERTICAL)
899   {
900     CGUIAction action = GetAction(ACTION_MOVE_UP);
901     if (m_channelCursor > 0)
902     {
903       SetChannel(m_channelCursor - 1);
904     }
905     else if (m_channelCursor == 0 && m_channelOffset)
906     {
907       ScrollToChannelOffset(m_channelOffset - 1);
908       SetChannel(0);
909     }
910     else if (action.GetNavigation() == GetID() || !action.HasActionsMeetingCondition()) // wrap around
911     {
912       int offset = m_gridModel->ChannelItemsSize() - m_channelsPerPage;
913 
914       if (offset < 0)
915         offset = 0;
916 
917       SetChannel(m_gridModel->GetLastChannel() - offset);
918       ScrollToChannelOffset(offset);
919     }
920     else
921       CGUIControl::OnUp();
922   }
923   else
924   {
925     if (m_gridModel->GetGridItemStartBlock(m_channelCursor + m_channelOffset,
926                                            m_blockCursor + m_blockOffset) > m_blockOffset)
927     {
928       // this is not first item on page
929       SetItem(GetPrevItem());
930       UpdateBlock();
931       return;
932     }
933     else if (m_blockCursor <= 0 && m_blockOffset && m_blockOffset - BLOCK_SCROLL_OFFSET >= 0)
934     {
935       // this is the first item on page
936       ScrollToBlockOffset(m_blockOffset - BLOCK_SCROLL_OFFSET);
937       UpdateBlock();
938       return;
939     }
940 
941     CGUIControl::OnUp();
942   }
943 }
944 
OnDown()945 void CGUIEPGGridContainer::OnDown()
946 {
947   if (m_orientation == VERTICAL)
948   {
949     CGUIAction action = GetAction(ACTION_MOVE_DOWN);
950     if (m_channelOffset + m_channelCursor < m_gridModel->GetLastChannel())
951     {
952       if (m_channelCursor + 1 < m_channelsPerPage)
953       {
954         SetChannel(m_channelCursor + 1);
955       }
956       else
957       {
958         ScrollToChannelOffset(m_channelOffset + 1);
959         SetChannel(m_channelsPerPage - 1);
960       }
961     }
962     else if (action.GetNavigation() == GetID() || !action.HasActionsMeetingCondition()) // wrap around
963     {
964       ScrollToChannelOffset(0);
965       SetChannel(0);
966     }
967     else
968       CGUIControl::OnDown();
969   }
970   else
971   {
972     if (m_gridModel->GetGridItemEndBlock(m_channelCursor + m_channelOffset,
973                                          m_blockCursor + m_blockOffset) <
974         (m_blockOffset + m_blocksPerPage - 1))
975     {
976       // this is not last item on page
977       SetItem(GetNextItem());
978       UpdateBlock();
979       return;
980     }
981     else if ((m_blockOffset != m_gridModel->GridItemsSize() - m_blocksPerPage) &&
982              m_gridModel->GridItemsSize() > m_blocksPerPage &&
983              m_blockOffset + BLOCK_SCROLL_OFFSET < m_gridModel->GetLastBlock())
984     {
985       // this is the last item on page
986       ScrollToBlockOffset(m_blockOffset + BLOCK_SCROLL_OFFSET);
987       UpdateBlock();
988       return;
989     }
990 
991     CGUIControl::OnDown();
992   }
993 }
994 
OnLeft()995 void CGUIEPGGridContainer::OnLeft()
996 {
997   if (m_orientation == VERTICAL)
998   {
999     if (m_gridModel->GetGridItemStartBlock(m_channelCursor + m_channelOffset,
1000                                            m_blockCursor + m_blockOffset) > m_blockOffset)
1001     {
1002       // this is not first item on page
1003       SetItem(GetPrevItem());
1004       UpdateBlock();
1005       return;
1006     }
1007     else if (m_blockCursor <= 0 && m_blockOffset && m_blockOffset - BLOCK_SCROLL_OFFSET >= 0)
1008     {
1009       // this is the first item on page
1010       ScrollToBlockOffset(m_blockOffset - BLOCK_SCROLL_OFFSET);
1011       UpdateBlock();
1012       return;
1013     }
1014 
1015     CGUIControl::OnLeft();
1016   }
1017   else
1018   {
1019     CGUIAction action = GetAction(ACTION_MOVE_LEFT);
1020     if (m_channelCursor > 0)
1021     {
1022       SetChannel(m_channelCursor - 1);
1023     }
1024     else if (m_channelCursor == 0 && m_channelOffset)
1025     {
1026       ScrollToChannelOffset(m_channelOffset - 1);
1027       SetChannel(0);
1028     }
1029     else if (action.GetNavigation() == GetID() || !action.HasActionsMeetingCondition()) // wrap around
1030     {
1031       int offset = m_gridModel->ChannelItemsSize() - m_channelsPerPage;
1032 
1033       if (offset < 0)
1034         offset = 0;
1035 
1036       SetChannel(m_gridModel->GetLastChannel() - offset);
1037       ScrollToChannelOffset(offset);
1038     }
1039     else
1040       CGUIControl::OnLeft();
1041   }
1042 }
1043 
OnRight()1044 void CGUIEPGGridContainer::OnRight()
1045 {
1046   if (m_orientation == VERTICAL)
1047   {
1048     if (m_gridModel->GetGridItemEndBlock(m_channelCursor + m_channelOffset,
1049                                          m_blockCursor + m_blockOffset) <
1050         (m_blockOffset + m_blocksPerPage - 1))
1051     {
1052       // this is not last item on page
1053       SetItem(GetNextItem());
1054       UpdateBlock();
1055       return;
1056     }
1057     else if ((m_blockOffset != m_gridModel->GridItemsSize() - m_blocksPerPage) &&
1058              m_gridModel->GridItemsSize() > m_blocksPerPage &&
1059              m_blockOffset + BLOCK_SCROLL_OFFSET < m_gridModel->GetLastBlock())
1060     {
1061       // this is the last item on page
1062       ScrollToBlockOffset(m_blockOffset + BLOCK_SCROLL_OFFSET);
1063       UpdateBlock();
1064       return;
1065     }
1066 
1067     CGUIControl::OnRight();
1068   }
1069   else
1070   {
1071     CGUIAction action = GetAction(ACTION_MOVE_RIGHT);
1072     if (m_channelOffset + m_channelCursor < m_gridModel->GetLastChannel())
1073     {
1074       if (m_channelCursor + 1 < m_channelsPerPage)
1075       {
1076         SetChannel(m_channelCursor + 1);
1077       }
1078       else
1079       {
1080         ScrollToChannelOffset(m_channelOffset + 1);
1081         SetChannel(m_channelsPerPage - 1);
1082       }
1083     }
1084     else if (action.GetNavigation() == GetID() || !action.HasActionsMeetingCondition()) // wrap around
1085     {
1086       SetChannel(0);
1087       ScrollToChannelOffset(0);
1088     }
1089     else
1090       CGUIControl::OnRight();
1091   }
1092 }
1093 
SetChannel(const std::string & channel)1094 bool CGUIEPGGridContainer::SetChannel(const std::string& channel)
1095 {
1096   for (int iIndex = 0; iIndex < m_gridModel->ChannelItemsSize(); iIndex++)
1097   {
1098     std::string strPath = m_gridModel->GetChannelItem(iIndex)->GetProperty("path").asString();
1099     if (strPath == channel)
1100     {
1101       GoToChannel(iIndex);
1102       return true;
1103     }
1104   }
1105   return false;
1106 }
1107 
SetChannel(const std::shared_ptr<CPVRChannel> & channel)1108 bool CGUIEPGGridContainer::SetChannel(const std::shared_ptr<CPVRChannel>& channel)
1109 {
1110   for (int iIndex = 0; iIndex < m_gridModel->ChannelItemsSize(); iIndex++)
1111   {
1112     int iChannelId = static_cast<int>(m_gridModel->GetChannelItem(iIndex)->GetProperty("channelid").asInteger(-1));
1113     if (iChannelId == channel->ChannelID())
1114     {
1115       GoToChannel(iIndex);
1116       return true;
1117     }
1118   }
1119   return false;
1120 }
1121 
SetChannel(const CPVRChannelNumber & channelNumber)1122 bool CGUIEPGGridContainer::SetChannel(const CPVRChannelNumber& channelNumber)
1123 {
1124   for (int iIndex = 0; iIndex < m_gridModel->ChannelItemsSize(); iIndex++)
1125   {
1126     const CPVRChannelNumber& number =
1127         m_gridModel->GetChannelItem(iIndex)->GetPVRChannelInfoTag()->ChannelNumber();
1128     if (number == channelNumber)
1129     {
1130       GoToChannel(iIndex);
1131       return true;
1132     }
1133   }
1134   return false;
1135 }
1136 
SetChannel(int channel)1137 void CGUIEPGGridContainer::SetChannel(int channel)
1138 {
1139   CSingleLock lock(m_critSection);
1140 
1141   int channelIndex = channel + m_channelOffset;
1142   int blockIndex = m_blockCursor + m_blockOffset;
1143   if (channelIndex < m_gridModel->ChannelItemsSize() && blockIndex < m_gridModel->GridItemsSize())
1144   {
1145     if (SetItem(m_gridModel->GetGridItem(channelIndex, m_blockTravelAxis), channelIndex,
1146                 m_blockTravelAxis))
1147     {
1148       m_channelCursor = channel;
1149       MarkDirtyRegion();
1150       UpdateBlock(false);
1151     }
1152   }
1153 }
1154 
SetBlock(int block,bool bUpdateBlockTravelAxis)1155 void CGUIEPGGridContainer::SetBlock(int block, bool bUpdateBlockTravelAxis /* = true */)
1156 {
1157   CSingleLock lock(m_critSection);
1158 
1159   if (block < 0)
1160     m_blockCursor = 0;
1161   else if (block > m_blocksPerPage - 1)
1162     m_blockCursor = m_blocksPerPage - 1;
1163   else
1164     m_blockCursor = block;
1165 
1166   if (bUpdateBlockTravelAxis)
1167     m_blockTravelAxis = m_blockOffset + m_blockCursor;
1168 
1169   UpdateItem();
1170   MarkDirtyRegion();
1171 }
1172 
UpdateBlock(bool bUpdateBlockTravelAxis)1173 void CGUIEPGGridContainer::UpdateBlock(bool bUpdateBlockTravelAxis /* = true */)
1174 {
1175   SetBlock(m_itemStartBlock > 0 ? m_itemStartBlock - m_blockOffset : 0, bUpdateBlockTravelAxis);
1176 }
1177 
GetFocusedLayout() const1178 CGUIListItemLayout* CGUIEPGGridContainer::GetFocusedLayout() const
1179 {
1180   CGUIListItemPtr item = GetListItem(0);
1181 
1182   if (item)
1183     return item->GetFocusedLayout();
1184 
1185   return nullptr;
1186 }
1187 
SelectItemFromPoint(const CPoint & point,bool justGrid)1188 bool CGUIEPGGridContainer::SelectItemFromPoint(const CPoint& point, bool justGrid /* = false */)
1189 {
1190   /* point has already had origin set to m_posX, m_posY */
1191   if (!m_focusedProgrammeLayout || !m_programmeLayout || (justGrid && point.x < 0))
1192     return false;
1193 
1194   int channel;
1195   int block;
1196 
1197   if (m_orientation == VERTICAL)
1198   {
1199     channel = point.y / m_channelHeight;
1200     block = point.x / m_blockSize;
1201   }
1202   else
1203   {
1204     channel = point.x / m_channelWidth;
1205     block = point.y / m_blockSize;
1206   }
1207 
1208   if (channel > m_channelsPerPage)
1209     channel = m_channelsPerPage - 1;
1210 
1211   if (channel >= m_gridModel->ChannelItemsSize())
1212     channel = m_gridModel->GetLastChannel();
1213 
1214   if (channel < 0)
1215     channel = 0;
1216 
1217   if (block > m_blocksPerPage)
1218     block = m_blocksPerPage - 1;
1219 
1220   if (block < 0)
1221     block = 0;
1222 
1223   int channelIndex = channel + m_channelOffset;
1224   int blockIndex = block + m_blockOffset;
1225 
1226   // bail if out of range
1227   if (channelIndex >= m_gridModel->ChannelItemsSize() || blockIndex >= m_gridModel->GridItemsSize())
1228     return false;
1229 
1230   // bail if block isn't occupied
1231   if (!m_gridModel->GetGridItem(channelIndex, blockIndex))
1232     return false;
1233 
1234   SetChannel(channel);
1235   SetBlock(block);
1236   return true;
1237 }
1238 
OnMouseEvent(const CPoint & point,const CMouseEvent & event)1239 EVENT_RESULT CGUIEPGGridContainer::OnMouseEvent(const CPoint& point, const CMouseEvent& event)
1240 {
1241   switch (event.m_id)
1242   {
1243   case ACTION_MOUSE_LEFT_CLICK:
1244     OnMouseClick(0, point);
1245     return EVENT_RESULT_HANDLED;
1246   case ACTION_MOUSE_RIGHT_CLICK:
1247     OnMouseClick(1, point);
1248     return EVENT_RESULT_HANDLED;
1249   case ACTION_MOUSE_DOUBLE_CLICK:
1250     OnMouseDoubleClick(0, point);
1251     return EVENT_RESULT_HANDLED;
1252   case ACTION_MOUSE_WHEEL_UP:
1253     OnMouseWheel(-1, point);
1254     return EVENT_RESULT_HANDLED;
1255   case ACTION_MOUSE_WHEEL_DOWN:
1256     OnMouseWheel(1, point);
1257     return EVENT_RESULT_HANDLED;
1258   case ACTION_GESTURE_BEGIN:
1259     {
1260       // we want exclusive access
1261       CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, GetID(), GetParentID());
1262       SendWindowMessage(msg);
1263       return EVENT_RESULT_HANDLED;
1264     }
1265   case ACTION_GESTURE_END:
1266   case ACTION_GESTURE_ABORT:
1267     {
1268       // we're done with exclusive access
1269       CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, 0, GetParentID());
1270       SendWindowMessage(msg);
1271       ScrollToChannelOffset(MathUtils::round_int(m_channelScrollOffset / m_channelLayout->Size(m_orientation)));
1272       SetChannel(m_channelCursor);
1273       ScrollToBlockOffset(MathUtils::round_int(m_programmeScrollOffset / m_blockSize));
1274       SetBlock(m_blockCursor);
1275       return EVENT_RESULT_HANDLED;
1276     }
1277   case ACTION_GESTURE_PAN:
1278     {
1279       m_programmeScrollOffset -= event.m_offsetX;
1280       m_channelScrollOffset -= event.m_offsetY;
1281 
1282       {
1283         CSingleLock lock(m_critSection);
1284 
1285         m_channelOffset = MathUtils::round_int(m_channelScrollOffset / m_channelLayout->Size(m_orientation));
1286         m_blockOffset = MathUtils::round_int(m_programmeScrollOffset / m_blockSize);
1287         ValidateOffset();
1288       }
1289       return EVENT_RESULT_HANDLED;
1290     }
1291   default:
1292     return EVENT_RESULT_UNHANDLED;
1293   }
1294 }
1295 
OnMouseOver(const CPoint & point)1296 bool CGUIEPGGridContainer::OnMouseOver(const CPoint& point)
1297 {
1298   // select the item under the pointer
1299   SelectItemFromPoint(point - CPoint(m_gridPosX, m_gridPosY), false);
1300   return CGUIControl::OnMouseOver(point);
1301 }
1302 
OnMouseClick(int dwButton,const CPoint & point)1303 bool CGUIEPGGridContainer::OnMouseClick(int dwButton, const CPoint& point)
1304 {
1305   if (SelectItemFromPoint(point - CPoint(m_gridPosX, m_gridPosY)))
1306   {
1307     // send click message to window
1308     OnClick(ACTION_MOUSE_LEFT_CLICK + dwButton);
1309     return true;
1310   }
1311   return false;
1312 }
1313 
OnMouseDoubleClick(int dwButton,const CPoint & point)1314 bool CGUIEPGGridContainer::OnMouseDoubleClick(int dwButton, const CPoint& point)
1315 {
1316   if (SelectItemFromPoint(point - CPoint(m_gridPosX, m_gridPosY)))
1317   {
1318     // send double click message to window
1319     OnClick(ACTION_MOUSE_DOUBLE_CLICK + dwButton);
1320     return true;
1321   }
1322   return false;
1323 }
1324 
OnClick(int actionID)1325 bool CGUIEPGGridContainer::OnClick(int actionID)
1326 {
1327   int subItem = 0;
1328 
1329   if (actionID == ACTION_SELECT_ITEM || actionID == ACTION_MOUSE_LEFT_CLICK)
1330   {
1331     // grab the currently focused subitem (if applicable)
1332     CGUIListItemLayout* focusedLayout = GetFocusedLayout();
1333 
1334     if (focusedLayout)
1335       subItem = focusedLayout->GetFocusedItem();
1336   }
1337 
1338   // Don't know what to do, so send to our parent window.
1339   CGUIMessage msg(GUI_MSG_CLICKED, GetID(), GetParentID(), actionID, subItem);
1340   return SendWindowMessage(msg);
1341 }
1342 
OnMouseWheel(char wheel,const CPoint & point)1343 bool CGUIEPGGridContainer::OnMouseWheel(char wheel, const CPoint& point)
1344 {
1345   // doesn't work while an item is selected?
1346   ProgrammesScroll(-wheel);
1347   return true;
1348 }
1349 
GetSelectedChannel() const1350 std::shared_ptr<CPVRChannel> CGUIEPGGridContainer::GetSelectedChannel() const
1351 {
1352   CFileItemPtr fileItem;
1353   {
1354     CSingleLock lock(m_critSection);
1355     if (m_channelCursor + m_channelOffset < m_gridModel->ChannelItemsSize())
1356       fileItem = m_gridModel->GetChannelItem(m_channelCursor + m_channelOffset);
1357   }
1358 
1359   if (fileItem && fileItem->HasPVRChannelInfoTag())
1360     return fileItem->GetPVRChannelInfoTag();
1361 
1362   return std::shared_ptr<CPVRChannel>();
1363 }
1364 
GetSelectedDate() const1365 CDateTime CGUIEPGGridContainer::GetSelectedDate() const
1366 {
1367   return m_gridModel->GetStartTimeForBlock(m_blockOffset + m_blockCursor);
1368 }
1369 
GetSelectedGridItem(int offset) const1370 CFileItemPtr CGUIEPGGridContainer::GetSelectedGridItem(int offset /*= 0*/) const
1371 {
1372   CFileItemPtr item;
1373 
1374   if (m_channelCursor + m_channelOffset + offset < m_gridModel->ChannelItemsSize() &&
1375       m_blockCursor + m_blockOffset < m_gridModel->GridItemsSize())
1376     item = m_gridModel->GetGridItem(m_channelCursor + m_channelOffset, m_blockCursor + m_blockOffset);
1377 
1378   return item;
1379 }
1380 
1381 
GetListItem(int offset,unsigned int flag) const1382 CGUIListItemPtr CGUIEPGGridContainer::GetListItem(int offset, unsigned int flag) const
1383 {
1384   if (!m_gridModel->HasChannelItems())
1385     return CGUIListItemPtr();
1386 
1387   int item = m_channelCursor + m_channelOffset + offset;
1388   if (flag & INFOFLAG_LISTITEM_POSITION)
1389     item = GetChannelScrollOffset(m_channelLayout);
1390 
1391   if (flag & INFOFLAG_LISTITEM_WRAP)
1392   {
1393     item %= m_gridModel->ChannelItemsSize();
1394     if (item < 0)
1395       item += m_gridModel->ChannelItemsSize();
1396 
1397     return m_gridModel->GetChannelItem(item);
1398   }
1399   else
1400   {
1401     if (item >= 0 && item < m_gridModel->ChannelItemsSize())
1402       return m_gridModel->GetChannelItem(item);
1403   }
1404   return CGUIListItemPtr();
1405 }
1406 
GetLabel(int info) const1407 std::string CGUIEPGGridContainer::GetLabel(int info) const
1408 {
1409   std::string label;
1410   switch (info)
1411   {
1412   case CONTAINER_NUM_PAGES:
1413     if (m_channelsPerPage > 0)
1414       label = StringUtils::Format("%u", (m_gridModel->ChannelItemsSize() + m_channelsPerPage - 1) / m_channelsPerPage);
1415     else
1416       label = StringUtils::Format("%u", 0);
1417     break;
1418   case CONTAINER_CURRENT_PAGE:
1419     if (m_channelsPerPage > 0)
1420       label = StringUtils::Format("%u", 1 + (m_channelCursor + m_channelOffset) / m_channelsPerPage);
1421     else
1422       label = StringUtils::Format("%u", 1);
1423     break;
1424   case CONTAINER_POSITION:
1425     label = StringUtils::Format("%i", 1 + m_channelCursor + m_channelOffset);
1426     break;
1427   case CONTAINER_NUM_ITEMS:
1428     label = StringUtils::Format("%u", m_gridModel->ChannelItemsSize());
1429     break;
1430   default:
1431       break;
1432   }
1433   return label;
1434 }
1435 
SetItem(const std::pair<std::shared_ptr<CFileItem>,int> & itemInfo)1436 void CGUIEPGGridContainer::SetItem(const std::pair<std::shared_ptr<CFileItem>, int>& itemInfo)
1437 {
1438   SetItem(itemInfo.first, m_channelCursor + m_channelOffset, itemInfo.second);
1439 }
1440 
SetItem(const std::shared_ptr<CFileItem> & item,int channelIndex,int blockIndex)1441 bool CGUIEPGGridContainer::SetItem(const std::shared_ptr<CFileItem>& item,
1442                                    int channelIndex,
1443                                    int blockIndex)
1444 {
1445   if (item && channelIndex < m_gridModel->ChannelItemsSize() &&
1446       blockIndex < m_gridModel->GridItemsSize())
1447   {
1448     m_itemStartBlock = m_gridModel->GetGridItemStartBlock(channelIndex, blockIndex);
1449     return true;
1450   }
1451   else
1452   {
1453     m_itemStartBlock = 0;
1454     return false;
1455   }
1456 }
1457 
GetItem() const1458 std::shared_ptr<CFileItem> CGUIEPGGridContainer::GetItem() const
1459 {
1460   const int channelIndex = m_channelCursor + m_channelOffset;
1461   const int blockIndex = m_blockCursor + m_blockOffset;
1462 
1463   if (channelIndex >= m_gridModel->ChannelItemsSize() || blockIndex >= m_gridModel->GridItemsSize())
1464     return {};
1465 
1466   return m_gridModel->GetGridItem(m_channelCursor + m_channelOffset, m_blockCursor + m_blockOffset);
1467 }
1468 
GetNextItem() const1469 std::pair<std::shared_ptr<CFileItem>, int> CGUIEPGGridContainer::GetNextItem() const
1470 {
1471   int block = m_gridModel->GetGridItemEndBlock(m_channelCursor + m_channelOffset,
1472                                                m_blockCursor + m_blockOffset);
1473   if (block < m_gridModel->GridItemsSize())
1474   {
1475     // first block of next event is one block after end block of selected event
1476     block += 1;
1477   }
1478 
1479   return {m_gridModel->GetGridItem(m_channelCursor + m_channelOffset, block), block};
1480 }
1481 
GetPrevItem() const1482 std::pair<std::shared_ptr<CFileItem>, int> CGUIEPGGridContainer::GetPrevItem() const
1483 {
1484   int block = m_gridModel->GetGridItemStartBlock(m_channelCursor + m_channelOffset,
1485                                                  m_blockCursor + m_blockOffset);
1486   if (block > 0)
1487   {
1488     // last block of previous event is one block before start block of selected event
1489     block -= 1;
1490   }
1491 
1492   return {m_gridModel->GetGridItem(m_channelCursor + m_channelOffset, block), block};
1493 }
1494 
UpdateItem()1495 void CGUIEPGGridContainer::UpdateItem()
1496 {
1497   SetItem(GetItem(), m_channelCursor + m_channelOffset, m_blockCursor + m_blockOffset);
1498 }
1499 
SetFocus(bool focus)1500 void CGUIEPGGridContainer::SetFocus(bool focus)
1501 {
1502   if (focus != HasFocus())
1503     SetInvalid();
1504 
1505   CGUIControl::SetFocus(focus);
1506 }
1507 
ScrollToChannelOffset(int offset)1508 void CGUIEPGGridContainer::ScrollToChannelOffset(int offset)
1509 {
1510   CSingleLock lock(m_critSection);
1511 
1512   float size = m_programmeLayout->Size(m_orientation);
1513   int range = m_channelsPerPage / 4;
1514 
1515   if (range <= 0)
1516     range = 1;
1517 
1518   if (offset * size < m_channelScrollOffset && m_channelScrollOffset - offset * size > size * range)
1519   {
1520     // scrolling up, and we're jumping more than 0.5 of a screen
1521     m_channelScrollOffset = (offset + range) * size;
1522   }
1523 
1524   if (offset * size > m_channelScrollOffset && offset * size - m_channelScrollOffset > size * range)
1525   {
1526     // scrolling down, and we're jumping more than 0.5 of a screen
1527     m_channelScrollOffset = (offset - range) * size;
1528   }
1529 
1530   m_channelScrollSpeed = (offset * size - m_channelScrollOffset) / m_scrollTime;
1531   m_channelOffset = offset;
1532   MarkDirtyRegion();
1533 }
1534 
ScrollToBlockOffset(int offset)1535 void CGUIEPGGridContainer::ScrollToBlockOffset(int offset)
1536 {
1537   CSingleLock lock(m_critSection);
1538 
1539   // make sure offset is in valid range
1540   offset = std::max(0, std::min(offset, m_gridModel->GridItemsSize() - m_blocksPerPage));
1541 
1542   float size = m_blockSize;
1543   int range = m_blocksPerPage / 1;
1544 
1545   if (range <= 0)
1546     range = 1;
1547 
1548   if (offset * size < m_programmeScrollOffset && m_programmeScrollOffset - offset * size > size * range)
1549   {
1550     // scrolling left, and we're jumping more than 0.5 of a screen
1551     m_programmeScrollOffset = (offset + range) * size;
1552   }
1553 
1554   if (offset * size > m_programmeScrollOffset && offset * size - m_programmeScrollOffset > size * range)
1555   {
1556     // scrolling right, and we're jumping more than 0.5 of a screen
1557     m_programmeScrollOffset = (offset - range) * size;
1558   }
1559 
1560   m_programmeScrollSpeed = (offset * size - m_programmeScrollOffset) / m_scrollTime;
1561   m_blockOffset = offset;
1562   MarkDirtyRegion();
1563 }
1564 
ValidateOffset()1565 void CGUIEPGGridContainer::ValidateOffset()
1566 {
1567   CSingleLock lock(m_critSection);
1568 
1569   if (!m_programmeLayout)
1570     return;
1571 
1572   float pos = (m_orientation == VERTICAL) ? m_channelHeight : m_channelWidth;
1573 
1574   if (m_gridModel->ChannelItemsSize() &&
1575       (m_channelOffset > m_gridModel->ChannelItemsSize() - m_channelsPerPage ||
1576        m_channelScrollOffset > (m_gridModel->ChannelItemsSize() - m_channelsPerPage) * pos))
1577   {
1578     m_channelOffset = m_gridModel->ChannelItemsSize() - m_channelsPerPage;
1579     m_channelScrollOffset = m_channelOffset * pos;
1580   }
1581 
1582   if (m_channelOffset < 0 || m_channelScrollOffset < 0)
1583   {
1584     m_channelOffset = 0;
1585     m_channelScrollOffset = 0;
1586   }
1587 
1588   if (m_gridModel->GridItemsSize() &&
1589       (m_blockOffset > m_gridModel->GridItemsSize() - m_blocksPerPage ||
1590        m_programmeScrollOffset > (m_gridModel->GridItemsSize() - m_blocksPerPage) * m_blockSize))
1591   {
1592     m_blockOffset = m_gridModel->GridItemsSize() - m_blocksPerPage;
1593     m_programmeScrollOffset = m_blockOffset * m_blockSize;
1594   }
1595 
1596   if (m_blockOffset < 0 || m_programmeScrollOffset < 0)
1597   {
1598     m_blockOffset = 0;
1599     m_programmeScrollOffset = 0;
1600   }
1601 }
1602 
LoadLayout(TiXmlElement * layout)1603 void CGUIEPGGridContainer::LoadLayout(TiXmlElement* layout)
1604 {
1605   /* layouts for the channel column */
1606   TiXmlElement* itemElement = layout->FirstChildElement("channellayout");
1607   while (itemElement)
1608   {
1609     m_channelLayouts.emplace_back();
1610     m_channelLayouts.back().LoadLayout(itemElement, GetParentID(), false, m_width, m_height);
1611     itemElement = itemElement->NextSiblingElement("channellayout");
1612   }
1613   itemElement = layout->FirstChildElement("focusedchannellayout");
1614   while (itemElement)
1615   {
1616     m_focusedChannelLayouts.emplace_back();
1617     m_focusedChannelLayouts.back().LoadLayout(itemElement, GetParentID(), true, m_width, m_height);
1618     itemElement = itemElement->NextSiblingElement("focusedchannellayout");
1619   }
1620 
1621   /* layouts for the grid items */
1622   itemElement = layout->FirstChildElement("focusedlayout");
1623   while (itemElement)
1624   {
1625     m_focusedProgrammeLayouts.emplace_back();
1626     m_focusedProgrammeLayouts.back().LoadLayout(itemElement, GetParentID(), true, m_width, m_height);
1627     itemElement = itemElement->NextSiblingElement("focusedlayout");
1628   }
1629   itemElement = layout->FirstChildElement("itemlayout");
1630   while (itemElement)
1631   {
1632     m_programmeLayouts.emplace_back();
1633     m_programmeLayouts.back().LoadLayout(itemElement, GetParentID(), false, m_width, m_height);
1634     itemElement = itemElement->NextSiblingElement("itemlayout");
1635   }
1636 
1637   /* layout for the date label for the grid */
1638   itemElement = layout->FirstChildElement("rulerdatelayout");
1639   while (itemElement)
1640   {
1641     m_rulerDateLayouts.emplace_back();
1642     m_rulerDateLayouts.back().LoadLayout(itemElement, GetParentID(), false, m_width, m_height);
1643     itemElement = itemElement->NextSiblingElement("rulerdatelayout");
1644   }
1645 
1646   /* layout for the timeline for the grid */
1647   itemElement = layout->FirstChildElement("rulerlayout");
1648   while (itemElement)
1649   {
1650     m_rulerLayouts.emplace_back();
1651     m_rulerLayouts.back().LoadLayout(itemElement, GetParentID(), false, m_width, m_height);
1652     itemElement = itemElement->NextSiblingElement("rulerlayout");
1653   }
1654 
1655   UpdateLayout();
1656 }
1657 
GetDescription() const1658 std::string CGUIEPGGridContainer::GetDescription() const
1659 {
1660   CSingleLock lock(m_critSection);
1661 
1662   const int channelIndex = m_channelCursor + m_channelOffset;
1663   const int blockIndex = m_blockCursor + m_blockOffset;
1664 
1665   if (channelIndex < m_gridModel->ChannelItemsSize() && blockIndex < m_gridModel->GridItemsSize())
1666   {
1667     const std::shared_ptr<CFileItem> item = m_gridModel->GetGridItem(channelIndex, blockIndex);
1668     if (item)
1669       return item->GetLabel();
1670   }
1671 
1672   return {};
1673 }
1674 
JumpToNow()1675 void CGUIEPGGridContainer::JumpToNow()
1676 {
1677   m_bEnableProgrammeScrolling = false;
1678   GoToNow();
1679 }
1680 
JumpToDate(const CDateTime & date)1681 void CGUIEPGGridContainer::JumpToDate(const CDateTime& date)
1682 {
1683   m_bEnableProgrammeScrolling = false;
1684   GoToDate(date);
1685 }
1686 
GoToBegin()1687 void CGUIEPGGridContainer::GoToBegin()
1688 {
1689   ScrollToBlockOffset(0);
1690   SetBlock(0);
1691 }
1692 
GoToEnd()1693 void CGUIEPGGridContainer::GoToEnd()
1694 {
1695   ScrollToBlockOffset(m_gridModel->GetLastBlock() - m_blocksPerPage + 1);
1696   SetBlock(m_blocksPerPage - 1);
1697 }
1698 
GoToNow()1699 void CGUIEPGGridContainer::GoToNow()
1700 {
1701   GoToDate(CDateTime::GetUTCDateTime());
1702 }
1703 
GoToDate(const CDateTime & date)1704 void CGUIEPGGridContainer::GoToDate(const CDateTime& date)
1705 {
1706   unsigned int offset = m_gridModel->GetPageNowOffset();
1707   ScrollToBlockOffset(m_gridModel->GetBlock(date) - offset);
1708 
1709   // ensure we're selecting the active event, not its predecessor.
1710   const int iChannel = m_channelOffset + m_channelCursor;
1711   const int iBlock = m_blockOffset + offset;
1712   if (iChannel >= m_gridModel->ChannelItemsSize() || iBlock >= m_gridModel->GridItemsSize() ||
1713       m_gridModel->GetGridItemEndTime(iChannel, iBlock) > date)
1714   {
1715     SetBlock(offset);
1716   }
1717   else
1718   {
1719     SetBlock(offset + 1);
1720   }
1721 }
1722 
GoToFirstChannel()1723 void CGUIEPGGridContainer::GoToFirstChannel()
1724 {
1725   GoToChannel(0);
1726 }
1727 
GoToLastChannel()1728 void CGUIEPGGridContainer::GoToLastChannel()
1729 {
1730   if (m_gridModel->ChannelItemsSize())
1731     GoToChannel(m_gridModel->GetLastChannel());
1732   else
1733     GoToChannel(0);
1734 }
1735 
GoToTop()1736 void CGUIEPGGridContainer::GoToTop()
1737 {
1738   if (m_orientation == VERTICAL)
1739   {
1740     GoToChannel(0);
1741   }
1742   else
1743   {
1744     GoToBlock(0);
1745   }
1746 }
1747 
GoToBottom()1748 void CGUIEPGGridContainer::GoToBottom()
1749 {
1750   if (m_orientation == VERTICAL)
1751   {
1752     if (m_gridModel->HasChannelItems())
1753       GoToChannel(m_gridModel->GetLastChannel());
1754     else
1755       GoToChannel(0);
1756   }
1757   else
1758   {
1759     if (m_gridModel->GridItemsSize())
1760       GoToBlock(m_gridModel->GetLastBlock());
1761     else
1762       GoToBlock(0);
1763   }
1764 }
1765 
GoToMostLeft()1766 void CGUIEPGGridContainer::GoToMostLeft()
1767 {
1768   if (m_orientation == VERTICAL)
1769   {
1770     GoToBlock(0);
1771   }
1772   else
1773   {
1774     GoToChannel(0);
1775   }
1776 }
1777 
GoToMostRight()1778 void CGUIEPGGridContainer::GoToMostRight()
1779 {
1780   if (m_orientation == VERTICAL)
1781   {
1782     if (m_gridModel->GridItemsSize())
1783       GoToBlock(m_gridModel->GetLastBlock());
1784     else
1785       GoToBlock(0);
1786   }
1787   else
1788   {
1789     if (m_gridModel->HasChannelItems())
1790       GoToChannel(m_gridModel->GetLastChannel());
1791     else
1792       GoToChannel(0);
1793   }
1794 }
1795 
SetTimelineItems(const std::unique_ptr<CFileItemList> & items,const CDateTime & gridStart,const CDateTime & gridEnd)1796 void CGUIEPGGridContainer::SetTimelineItems(const std::unique_ptr<CFileItemList>& items,
1797                                             const CDateTime& gridStart,
1798                                             const CDateTime& gridEnd)
1799 {
1800   int iRulerUnit;
1801   int iFirstChannel;
1802   int iChannelsPerPage;
1803   int iBlocksPerPage;
1804   int iFirstBlock;
1805   float fBlockSize;
1806   {
1807     CSingleLock lock(m_critSection);
1808 
1809     iRulerUnit = m_rulerUnit;
1810     iFirstChannel = m_channelOffset;
1811     iChannelsPerPage = m_channelsPerPage;
1812     iFirstBlock = m_blockOffset;
1813     iBlocksPerPage = m_blocksPerPage;
1814     fBlockSize = m_blockSize;
1815   }
1816 
1817   std::unique_ptr<CGUIEPGGridContainerModel> oldUpdatedGridModel;
1818   std::unique_ptr<CGUIEPGGridContainerModel> newUpdatedGridModel(new CGUIEPGGridContainerModel);
1819 
1820   newUpdatedGridModel->Initialize(items, gridStart, gridEnd, iFirstChannel, iChannelsPerPage,
1821                                   iFirstBlock, iBlocksPerPage, iRulerUnit, fBlockSize);
1822   {
1823     CSingleLock lock(m_critSection);
1824 
1825     // grid contains CFileItem instances. CFileItem dtor locks global graphics mutex.
1826     // by increasing its refcount make sure, old data are not deleted while we're holding own mutex.
1827     oldUpdatedGridModel = std::move(m_updatedGridModel);
1828 
1829     m_updatedGridModel = std::move(newUpdatedGridModel);
1830   }
1831 }
1832 
GetCurrentTimeLineItems() const1833 std::unique_ptr<CFileItemList> CGUIEPGGridContainer::GetCurrentTimeLineItems() const
1834 {
1835   return m_gridModel->GetCurrentTimeLineItems();
1836 }
1837 
GoToChannel(int channelIndex)1838 void CGUIEPGGridContainer::GoToChannel(int channelIndex)
1839 {
1840   if (channelIndex < m_channelsPerPage)
1841   {
1842     // first page
1843     ScrollToChannelOffset(0);
1844     SetChannel(channelIndex);
1845   }
1846   else if (channelIndex > m_gridModel->ChannelItemsSize() - m_channelsPerPage)
1847   {
1848     // last page
1849     ScrollToChannelOffset(m_gridModel->ChannelItemsSize() - m_channelsPerPage);
1850     SetChannel(channelIndex - (m_gridModel->ChannelItemsSize() - m_channelsPerPage));
1851   }
1852   else
1853   {
1854     ScrollToChannelOffset(channelIndex - m_channelCursor);
1855     SetChannel(m_channelCursor);
1856   }
1857 }
1858 
GoToBlock(int blockIndex)1859 void CGUIEPGGridContainer::GoToBlock(int blockIndex)
1860 {
1861   int lastPage = m_gridModel->GridItemsSize() - m_blocksPerPage;
1862   if (blockIndex > lastPage)
1863   {
1864     // last page
1865     ScrollToBlockOffset(lastPage);
1866     SetBlock(blockIndex - lastPage);
1867   }
1868   else
1869   {
1870     ScrollToBlockOffset(blockIndex - m_blockCursor);
1871     SetBlock(m_blockCursor);
1872   }
1873 }
1874 
UpdateLayout()1875 void CGUIEPGGridContainer::UpdateLayout()
1876 {
1877   CGUIListItemLayout* oldFocusedChannelLayout = m_focusedChannelLayout;
1878   CGUIListItemLayout* oldChannelLayout = m_channelLayout;
1879   CGUIListItemLayout* oldFocusedProgrammeLayout = m_focusedProgrammeLayout;
1880   CGUIListItemLayout* oldProgrammeLayout = m_programmeLayout;
1881   CGUIListItemLayout* oldRulerLayout = m_rulerLayout;
1882   CGUIListItemLayout* oldRulerDateLayout = m_rulerDateLayout;
1883 
1884   GetCurrentLayouts();
1885 
1886   // Note: m_rulerDateLayout is optional
1887   if (!m_focusedProgrammeLayout || !m_programmeLayout || !m_focusedChannelLayout || !m_channelLayout || !m_rulerLayout)
1888     return;
1889 
1890   if (oldChannelLayout == m_channelLayout && oldFocusedChannelLayout == m_focusedChannelLayout &&
1891       oldProgrammeLayout == m_programmeLayout && oldFocusedProgrammeLayout == m_focusedProgrammeLayout &&
1892       oldRulerLayout == m_rulerLayout && oldRulerDateLayout == m_rulerDateLayout)
1893     return; // nothing has changed, so don't update stuff
1894 
1895   CSingleLock lock(m_critSection);
1896 
1897   m_channelHeight = m_channelLayout->Size(VERTICAL);
1898   m_channelWidth = m_channelLayout->Size(HORIZONTAL);
1899 
1900   m_rulerDateHeight = m_rulerDateLayout ? m_rulerDateLayout->Size(VERTICAL) : 0;
1901   m_rulerDateWidth = m_rulerDateLayout ? m_rulerDateLayout->Size(HORIZONTAL) : 0;
1902 
1903   if (m_orientation == VERTICAL)
1904   {
1905     m_rulerHeight = m_rulerLayout->Size(VERTICAL);
1906     m_gridPosX = m_posX + m_channelWidth;
1907     m_gridPosY = m_posY + m_rulerHeight + m_rulerDateHeight;
1908     m_gridWidth = m_width - m_channelWidth;
1909     m_gridHeight = m_height - m_rulerHeight - m_rulerDateHeight;
1910     m_blockSize = m_gridWidth / m_blocksPerPage;
1911     m_rulerWidth = m_rulerUnit * m_blockSize;
1912     m_channelPosX = m_posX;
1913     m_channelPosY = m_posY + m_rulerHeight + m_rulerDateHeight;
1914     m_rulerPosX = m_posX + m_channelWidth;
1915     m_rulerPosY = m_posY + m_rulerDateHeight;
1916     m_channelsPerPage = m_gridHeight / m_channelHeight;
1917     m_programmesPerPage = (m_gridWidth / m_blockSize) + 1;
1918 
1919     m_programmeLayout->SetHeight(m_channelHeight);
1920     m_focusedProgrammeLayout->SetHeight(m_channelHeight);
1921   }
1922   else
1923   {
1924     m_rulerWidth = m_rulerLayout->Size(HORIZONTAL);
1925     m_gridPosX = m_posX + m_rulerWidth;
1926     m_gridPosY = m_posY + m_channelHeight + m_rulerDateHeight;
1927     m_gridWidth = m_width - m_rulerWidth;
1928     m_gridHeight = m_height - m_channelHeight - m_rulerDateHeight;
1929     m_blockSize = m_gridHeight / m_blocksPerPage;
1930     m_rulerHeight = m_rulerUnit * m_blockSize;
1931     m_channelPosX = m_posX + m_rulerWidth;
1932     m_channelPosY = m_posY + m_rulerDateHeight;
1933     m_rulerPosX = m_posX;
1934     m_rulerPosY = m_posY + m_channelHeight + m_rulerDateHeight;
1935     m_channelsPerPage = m_gridWidth / m_channelWidth;
1936     m_programmesPerPage = (m_gridHeight / m_blockSize) + 1;
1937 
1938     m_programmeLayout->SetWidth(m_channelWidth);
1939     m_focusedProgrammeLayout->SetWidth(m_channelWidth);
1940   }
1941 
1942   // ensure that the scroll offsets are a multiple of our sizes
1943   m_channelScrollOffset = m_channelOffset * m_programmeLayout->Size(m_orientation);
1944   m_programmeScrollOffset = m_blockOffset * m_blockSize;
1945 }
1946 
UpdateScrollOffset(unsigned int currentTime)1947 void CGUIEPGGridContainer::UpdateScrollOffset(unsigned int currentTime)
1948 {
1949   if (!m_programmeLayout)
1950     return;
1951 
1952   m_channelScrollOffset += m_channelScrollSpeed * (currentTime - m_channelScrollLastTime);
1953   if ((m_channelScrollSpeed < 0 && m_channelScrollOffset < m_channelOffset * m_programmeLayout->Size(m_orientation)) ||
1954       (m_channelScrollSpeed > 0 && m_channelScrollOffset > m_channelOffset * m_programmeLayout->Size(m_orientation)))
1955   {
1956     m_channelScrollOffset = m_channelOffset * m_programmeLayout->Size(m_orientation);
1957     m_channelScrollSpeed = 0;
1958     m_bEnableChannelScrolling = true;
1959   }
1960 
1961   m_channelScrollLastTime = currentTime;
1962   m_programmeScrollOffset += m_programmeScrollSpeed * (currentTime - m_programmeScrollLastTime);
1963 
1964   if ((m_programmeScrollSpeed < 0 && m_programmeScrollOffset < m_blockOffset * m_blockSize) ||
1965       (m_programmeScrollSpeed > 0 && m_programmeScrollOffset > m_blockOffset * m_blockSize))
1966   {
1967     m_programmeScrollOffset = m_blockOffset * m_blockSize;
1968     m_programmeScrollSpeed = 0;
1969     m_bEnableProgrammeScrolling = true;
1970   }
1971 
1972   m_programmeScrollLastTime = currentTime;
1973 
1974   if (m_channelScrollSpeed || m_programmeScrollSpeed)
1975     MarkDirtyRegion();
1976 }
1977 
GetCurrentLayouts()1978 void CGUIEPGGridContainer::GetCurrentLayouts()
1979 {
1980   m_channelLayout = nullptr;
1981 
1982   for (unsigned int i = 0; i < m_channelLayouts.size(); i++)
1983   {
1984     if (m_channelLayouts[i].CheckCondition())
1985     {
1986       m_channelLayout = &m_channelLayouts[i];
1987       break;
1988     }
1989   }
1990 
1991   if (!m_channelLayout && !m_channelLayouts.empty())
1992     m_channelLayout = &m_channelLayouts[0]; // failsafe
1993 
1994   m_focusedChannelLayout = nullptr;
1995 
1996   for (unsigned int i = 0; i < m_focusedChannelLayouts.size(); i++)
1997   {
1998     if (m_focusedChannelLayouts[i].CheckCondition())
1999     {
2000       m_focusedChannelLayout = &m_focusedChannelLayouts[i];
2001       break;
2002     }
2003   }
2004 
2005   if (!m_focusedChannelLayout && !m_focusedChannelLayouts.empty())
2006     m_focusedChannelLayout = &m_focusedChannelLayouts[0]; // failsafe
2007 
2008   m_programmeLayout = nullptr;
2009 
2010   for (unsigned int i = 0; i < m_programmeLayouts.size(); i++)
2011   {
2012     if (m_programmeLayouts[i].CheckCondition())
2013     {
2014       m_programmeLayout = &m_programmeLayouts[i];
2015       break;
2016     }
2017   }
2018 
2019   if (!m_programmeLayout && !m_programmeLayouts.empty())
2020     m_programmeLayout = &m_programmeLayouts[0]; // failsafe
2021 
2022   m_focusedProgrammeLayout = nullptr;
2023 
2024   for (unsigned int i = 0; i < m_focusedProgrammeLayouts.size(); i++)
2025   {
2026     if (m_focusedProgrammeLayouts[i].CheckCondition())
2027     {
2028       m_focusedProgrammeLayout = &m_focusedProgrammeLayouts[i];
2029       break;
2030     }
2031   }
2032 
2033   if (!m_focusedProgrammeLayout && !m_focusedProgrammeLayouts.empty())
2034     m_focusedProgrammeLayout = &m_focusedProgrammeLayouts[0]; // failsafe
2035 
2036   m_rulerLayout = nullptr;
2037 
2038   for (unsigned int i = 0; i < m_rulerLayouts.size(); i++)
2039   {
2040     if (m_rulerLayouts[i].CheckCondition())
2041     {
2042       m_rulerLayout = &m_rulerLayouts[i];
2043       break;
2044     }
2045   }
2046 
2047   if (!m_rulerLayout && !m_rulerLayouts.empty())
2048     m_rulerLayout = &m_rulerLayouts[0]; // failsafe
2049 
2050   m_rulerDateLayout = nullptr;
2051 
2052   for (unsigned int i = 0; i < m_rulerDateLayouts.size(); i++)
2053   {
2054     if (m_rulerDateLayouts[i].CheckCondition())
2055     {
2056       m_rulerDateLayout = &m_rulerDateLayouts[i];
2057       break;
2058     }
2059   }
2060 
2061   // Note: m_rulerDateLayout is optional; so no "failsafe" logic here (see above)
2062 }
2063 
SetRenderOffset(const CPoint & offset)2064 void CGUIEPGGridContainer::SetRenderOffset(const CPoint& offset)
2065 {
2066   m_renderOffset = offset;
2067 }
2068 
GetChannelCacheOffsets(int & cacheBefore,int & cacheAfter)2069 void CGUIEPGGridContainer::GetChannelCacheOffsets(int& cacheBefore, int& cacheAfter)
2070 {
2071   if (m_channelScrollSpeed > 0)
2072   {
2073     cacheBefore = 0;
2074     cacheAfter = m_cacheChannelItems;
2075   }
2076   else if (m_channelScrollSpeed < 0)
2077   {
2078     cacheBefore = m_cacheChannelItems;
2079     cacheAfter = 0;
2080   }
2081   else
2082   {
2083     cacheBefore = m_cacheChannelItems / 2;
2084     cacheAfter = m_cacheChannelItems / 2;
2085   }
2086 }
2087 
GetProgrammeCacheOffsets(int & cacheBefore,int & cacheAfter)2088 void CGUIEPGGridContainer::GetProgrammeCacheOffsets(int& cacheBefore, int& cacheAfter)
2089 {
2090   if (m_programmeScrollSpeed > 0)
2091   {
2092     cacheBefore = 0;
2093     cacheAfter = m_cacheProgrammeItems;
2094   }
2095   else if (m_programmeScrollSpeed < 0)
2096   {
2097     cacheBefore = m_cacheProgrammeItems;
2098     cacheAfter = 0;
2099   }
2100   else
2101   {
2102     cacheBefore = m_cacheProgrammeItems / 2;
2103     cacheAfter = m_cacheProgrammeItems / 2;
2104   }
2105 }
2106 
HandleChannels(bool bRender,unsigned int currentTime,CDirtyRegionList & dirtyregions)2107 void CGUIEPGGridContainer::HandleChannels(bool bRender, unsigned int currentTime, CDirtyRegionList& dirtyregions)
2108 {
2109   if (!m_focusedChannelLayout || !m_channelLayout)
2110     return;
2111 
2112   const int chanOffset = GetChannelScrollOffset(m_programmeLayout);
2113 
2114   int cacheBeforeChannel, cacheAfterChannel;
2115   GetChannelCacheOffsets(cacheBeforeChannel, cacheAfterChannel);
2116 
2117   if (bRender)
2118   {
2119     if (m_orientation == VERTICAL)
2120       CServiceBroker::GetWinSystem()->GetGfxContext().SetClipRegion(m_channelPosX, m_channelPosY, m_channelWidth, m_gridHeight);
2121     else
2122       CServiceBroker::GetWinSystem()->GetGfxContext().SetClipRegion(m_channelPosX, m_channelPosY, m_gridWidth, m_channelHeight);
2123   }
2124   else
2125   {
2126     // Free memory not used on screen
2127     if (m_gridModel->ChannelItemsSize() > m_channelsPerPage + cacheBeforeChannel + cacheAfterChannel)
2128       m_gridModel->FreeChannelMemory(chanOffset - cacheBeforeChannel,
2129                                      chanOffset + m_channelsPerPage - 1 + cacheAfterChannel);
2130   }
2131 
2132   CPoint originChannel = CPoint(m_channelPosX, m_channelPosY) + m_renderOffset;
2133   float pos;
2134   float end;
2135 
2136   if (m_orientation == VERTICAL)
2137   {
2138     pos = originChannel.y;
2139     end = m_posY + m_height;
2140   }
2141   else
2142   {
2143     pos = originChannel.x;
2144     end = m_posX + m_width;
2145   }
2146 
2147   // we offset our draw position to take into account scrolling and whether or not our focused
2148   // item is offscreen "above" the list.
2149   float drawOffset = (chanOffset - cacheBeforeChannel) * m_channelLayout->Size(m_orientation) -
2150                      GetChannelScrollOffsetPos();
2151   if (m_channelOffset + m_channelCursor < chanOffset)
2152     drawOffset += m_focusedChannelLayout->Size(m_orientation) - m_channelLayout->Size(m_orientation);
2153 
2154   pos += drawOffset;
2155   end += cacheAfterChannel * m_channelLayout->Size(m_orientation);
2156 
2157   float focusedPos = 0;
2158   CGUIListItemPtr focusedItem;
2159 
2160   CFileItemPtr item;
2161   int current = chanOffset - cacheBeforeChannel;
2162   while (pos < end && m_gridModel->HasChannelItems())
2163   {
2164     int itemNo = current;
2165     if (itemNo >= m_gridModel->ChannelItemsSize())
2166       break;
2167 
2168     bool focused = (current == m_channelOffset + m_channelCursor);
2169     if (itemNo >= 0)
2170     {
2171       item = m_gridModel->GetChannelItem(itemNo);
2172       if (bRender)
2173       {
2174         // render our item
2175         if (focused)
2176         {
2177           focusedPos = pos;
2178           focusedItem = item;
2179         }
2180         else
2181         {
2182           if (m_orientation == VERTICAL)
2183             RenderItem(originChannel.x, pos, item.get(), false);
2184           else
2185             RenderItem(pos, originChannel.y, item.get(), false);
2186         }
2187       }
2188       else
2189       {
2190         // process our item
2191         if (m_orientation == VERTICAL)
2192           ProcessItem(originChannel.x, pos, item, m_lastItem, focused, m_channelLayout, m_focusedChannelLayout, currentTime, dirtyregions);
2193         else
2194           ProcessItem(pos, originChannel.y, item, m_lastItem, focused, m_channelLayout, m_focusedChannelLayout, currentTime, dirtyregions);
2195       }
2196     }
2197     // increment our position
2198     pos += focused ? m_focusedChannelLayout->Size(m_orientation) : m_channelLayout->Size(m_orientation);
2199     current++;
2200   }
2201 
2202   if (bRender)
2203   {
2204     // render focused item last so it can overlap other items
2205     if (focusedItem)
2206     {
2207       if (m_orientation == VERTICAL)
2208         RenderItem(originChannel.x, focusedPos, focusedItem.get(), true);
2209       else
2210         RenderItem(focusedPos, originChannel.y, focusedItem.get(), true);
2211     }
2212 
2213     CServiceBroker::GetWinSystem()->GetGfxContext().RestoreClipRegion();
2214   }
2215 }
2216 
HandleRulerDate(bool bRender,unsigned int currentTime,CDirtyRegionList & dirtyregions)2217 void CGUIEPGGridContainer::HandleRulerDate(bool bRender, unsigned int currentTime, CDirtyRegionList& dirtyregions)
2218 {
2219   if (!m_rulerDateLayout || m_gridModel->RulerItemsSize() <= 1 || m_gridModel->IsZeroGridDuration())
2220     return;
2221 
2222   CFileItemPtr item(m_gridModel->GetRulerItem(0));
2223 
2224   if (bRender)
2225   {
2226     // Render single ruler item with date of selected programme
2227     CServiceBroker::GetWinSystem()->GetGfxContext().SetClipRegion(m_posX, m_posY, m_rulerDateWidth, m_rulerDateHeight);
2228     RenderItem(m_posX, m_posY, item.get(), false);
2229     CServiceBroker::GetWinSystem()->GetGfxContext().RestoreClipRegion();
2230   }
2231   else
2232   {
2233     const int rulerOffset = GetProgrammeScrollOffset();
2234     item->SetLabel(m_gridModel->GetRulerItem(rulerOffset / m_rulerUnit + 1)->GetLabel2());
2235 
2236     CFileItemPtr lastitem;
2237     ProcessItem(m_posX, m_posY, item, lastitem, false, m_rulerDateLayout, m_rulerDateLayout, currentTime, dirtyregions);
2238   }
2239 }
2240 
HandleRuler(bool bRender,unsigned int currentTime,CDirtyRegionList & dirtyregions)2241 void CGUIEPGGridContainer::HandleRuler(bool bRender, unsigned int currentTime, CDirtyRegionList& dirtyregions)
2242 {
2243   if (!m_rulerLayout || m_gridModel->RulerItemsSize() <= 1 || m_gridModel->IsZeroGridDuration())
2244     return;
2245 
2246   int rulerOffset = GetProgrammeScrollOffset();
2247 
2248   CFileItemPtr item(m_gridModel->GetRulerItem(0));
2249   CFileItemPtr lastitem;
2250   int cacheBeforeRuler, cacheAfterRuler;
2251 
2252   if (bRender)
2253   {
2254     if (!m_rulerDateLayout)
2255     {
2256       // Render single ruler item with date of selected programme
2257       CServiceBroker::GetWinSystem()->GetGfxContext().SetClipRegion(m_posX, m_posY, m_width, m_height);
2258       RenderItem(m_posX, m_posY, item.get(), false);
2259       CServiceBroker::GetWinSystem()->GetGfxContext().RestoreClipRegion();
2260     }
2261 
2262     // render ruler items
2263     GetProgrammeCacheOffsets(cacheBeforeRuler, cacheAfterRuler);
2264 
2265     if (m_orientation == VERTICAL)
2266       CServiceBroker::GetWinSystem()->GetGfxContext().SetClipRegion(m_rulerPosX, m_rulerPosY, m_gridWidth, m_rulerHeight);
2267     else
2268       CServiceBroker::GetWinSystem()->GetGfxContext().SetClipRegion(m_rulerPosX, m_rulerPosY, m_rulerWidth, m_gridHeight);
2269   }
2270   else
2271   {
2272     if (!m_rulerDateLayout)
2273     {
2274       item->SetLabel(m_gridModel->GetRulerItem(rulerOffset / m_rulerUnit + 1)->GetLabel2());
2275       ProcessItem(m_posX, m_posY, item, lastitem, false, m_rulerLayout, m_rulerLayout, currentTime, dirtyregions, m_channelWidth);
2276     }
2277 
2278     GetProgrammeCacheOffsets(cacheBeforeRuler, cacheAfterRuler);
2279 
2280     // Free memory not used on screen
2281     if (m_gridModel->RulerItemsSize() > m_blocksPerPage + cacheBeforeRuler + cacheAfterRuler)
2282       m_gridModel->FreeRulerMemory(rulerOffset / m_rulerUnit + 1 - cacheBeforeRuler,
2283                                    rulerOffset / m_rulerUnit + 1 + m_blocksPerPage - 1 +
2284                                        cacheAfterRuler);
2285   }
2286 
2287   CPoint originRuler = CPoint(m_rulerPosX, m_rulerPosY) + m_renderOffset;
2288   float pos;
2289   float end;
2290 
2291   if (m_orientation == VERTICAL)
2292   {
2293     pos = originRuler.x;
2294     end = m_posX + m_width;
2295   }
2296   else
2297   {
2298     pos = originRuler.y;
2299     end = m_posY + m_height;
2300   }
2301 
2302   const float drawOffset =
2303       (rulerOffset - cacheBeforeRuler) * m_blockSize - GetProgrammeScrollOffsetPos();
2304   pos += drawOffset;
2305   end += cacheAfterRuler * m_rulerLayout->Size(m_orientation == VERTICAL ? HORIZONTAL : VERTICAL);
2306 
2307   if (rulerOffset % m_rulerUnit != 0)
2308   {
2309     /* first ruler marker starts before current view */
2310     int startBlock = rulerOffset - 1;
2311 
2312     while (startBlock % m_rulerUnit != 0)
2313       startBlock--;
2314 
2315     int missingSection = rulerOffset - startBlock;
2316 
2317     pos -= missingSection * m_blockSize;
2318   }
2319 
2320   while (pos < end && (rulerOffset / m_rulerUnit + 1) < m_gridModel->RulerItemsSize())
2321   {
2322     item = m_gridModel->GetRulerItem(rulerOffset / m_rulerUnit + 1);
2323 
2324     if (m_orientation == VERTICAL)
2325     {
2326       if (bRender)
2327         RenderItem(pos, originRuler.y, item.get(), false);
2328       else
2329         ProcessItem(pos, originRuler.y, item, lastitem, false, m_rulerLayout, m_rulerLayout, currentTime, dirtyregions, m_rulerWidth);
2330 
2331       pos += m_rulerWidth;
2332     }
2333     else
2334     {
2335       if (bRender)
2336         RenderItem(originRuler.x, pos, item.get(), false);
2337       else
2338         ProcessItem(originRuler.x, pos, item, lastitem, false, m_rulerLayout, m_rulerLayout, currentTime, dirtyregions, m_rulerHeight);
2339 
2340       pos += m_rulerHeight;
2341     }
2342 
2343     rulerOffset += m_rulerUnit;
2344   }
2345 
2346   if (bRender)
2347     CServiceBroker::GetWinSystem()->GetGfxContext().RestoreClipRegion();
2348 }
2349 
HandleProgrammeGrid(bool bRender,unsigned int currentTime,CDirtyRegionList & dirtyregions)2350 void CGUIEPGGridContainer::HandleProgrammeGrid(bool bRender, unsigned int currentTime, CDirtyRegionList& dirtyregions)
2351 {
2352   if (!m_focusedProgrammeLayout || !m_programmeLayout || m_gridModel->RulerItemsSize() <= 1 || m_gridModel->IsZeroGridDuration())
2353     return;
2354 
2355   const int blockOffset = GetProgrammeScrollOffset();
2356   const int chanOffset = GetChannelScrollOffset(m_programmeLayout);
2357 
2358   int cacheBeforeProgramme, cacheAfterProgramme;
2359   GetProgrammeCacheOffsets(cacheBeforeProgramme, cacheAfterProgramme);
2360 
2361   if (bRender)
2362   {
2363     CServiceBroker::GetWinSystem()->GetGfxContext().SetClipRegion(m_gridPosX, m_gridPosY, m_gridWidth, m_gridHeight);
2364   }
2365   else
2366   {
2367     int cacheBeforeChannel, cacheAfterChannel;
2368     GetChannelCacheOffsets(cacheBeforeChannel, cacheAfterChannel);
2369 
2370     // Free memory not used on screen
2371     int firstChannel = chanOffset - cacheBeforeChannel;
2372     if (firstChannel < 0)
2373       firstChannel = 0;
2374     int lastChannel = chanOffset + m_channelsPerPage - 1 + cacheAfterChannel;
2375     if (lastChannel > m_gridModel->GetLastChannel())
2376       lastChannel = m_gridModel->GetLastChannel();
2377     int firstBlock = blockOffset - cacheBeforeProgramme;
2378     if (firstBlock < 0)
2379       firstBlock = 0;
2380     int lastBlock = blockOffset + m_programmesPerPage - 1 + cacheAfterProgramme;
2381     if (lastBlock > m_gridModel->GetLastBlock())
2382       lastBlock = m_gridModel->GetLastBlock();
2383 
2384     if (m_gridModel->FreeProgrammeMemory(firstChannel, lastChannel, firstBlock, lastBlock))
2385     {
2386       // announce changed viewport
2387       const CGUIMessage msg(
2388           GUI_MSG_REFRESH_LIST, GetParentID(), GetID(), static_cast<int>(PVREvent::Epg));
2389       KODI::MESSAGING::CApplicationMessenger::GetInstance().SendGUIMessage(msg);
2390     }
2391   }
2392 
2393   CPoint originProgramme = CPoint(m_gridPosX, m_gridPosY) + m_renderOffset;
2394   float posA;
2395   float endA;
2396   float posB;
2397   float endB;
2398 
2399   if (m_orientation == VERTICAL)
2400   {
2401     posA = originProgramme.x;
2402     endA = m_posX + m_width;
2403     posB = originProgramme.y;
2404     endB = m_gridPosY + m_gridHeight;
2405   }
2406   else
2407   {
2408     posA = originProgramme.y;
2409     endA = m_posY + m_height;
2410     posB = originProgramme.x;
2411     endB = m_gridPosX + m_gridWidth;
2412   }
2413 
2414   endA += cacheAfterProgramme * m_blockSize;
2415 
2416   const float drawOffsetA = blockOffset * m_blockSize - GetProgrammeScrollOffsetPos();
2417   posA += drawOffsetA;
2418   const float drawOffsetB =
2419       (chanOffset - cacheBeforeProgramme) * m_channelLayout->Size(m_orientation) -
2420       GetChannelScrollOffsetPos();
2421   posB += drawOffsetB;
2422 
2423   int channel = chanOffset - cacheBeforeProgramme;
2424 
2425   float focusedPosX = 0;
2426   float focusedPosY = 0;
2427   CFileItemPtr focusedItem;
2428   CFileItemPtr item;
2429 
2430   const int lastChannel = m_gridModel->GetLastChannel();
2431   while (posB < endB && HasData() && channel <= lastChannel)
2432   {
2433     if (channel >= 0)
2434     {
2435       int block = blockOffset;
2436       float posA2 = posA;
2437 
2438       const int startBlock = blockOffset == 0 ? 0 : blockOffset - 1;
2439       if (startBlock == 0 || m_gridModel->IsSameGridItem(channel, block, startBlock))
2440       {
2441         // First program starts before current view
2442         block = m_gridModel->GetGridItemStartBlock(channel, startBlock);
2443         const int missingSection = blockOffset - block;
2444         posA2 -= missingSection * m_blockSize;
2445       }
2446 
2447       const int lastBlock = m_gridModel->GetLastBlock();
2448       while (posA2 < endA && HasData() && block <= lastBlock)
2449       {
2450         item = m_gridModel->GetGridItem(channel, block);
2451 
2452         bool focused = (channel == m_channelOffset + m_channelCursor) &&
2453                        m_gridModel->IsSameGridItem(m_channelOffset + m_channelCursor,
2454                                                    m_blockOffset + m_blockCursor, block);
2455 
2456         if (bRender)
2457         {
2458           // reset to grid start position if first item is out of grid view
2459           if (posA2 < posA)
2460             posA2 = posA;
2461 
2462           // render our item
2463           if (focused)
2464           {
2465             focusedPosX = posA2;
2466             focusedPosY = posB;
2467             focusedItem = item;
2468           }
2469           else
2470           {
2471             if (m_orientation == VERTICAL)
2472               RenderItem(posA2, posB, item.get(), focused);
2473             else
2474               RenderItem(posB, posA2, item.get(), focused);
2475           }
2476         }
2477         else
2478         {
2479           // calculate the size to truncate if item is out of grid view
2480           float truncateSize = 0;
2481           if (posA2 < posA)
2482           {
2483             truncateSize = posA - posA2;
2484             posA2 = posA; // reset to grid start position
2485           }
2486 
2487           {
2488             CSingleLock lock(m_critSection);
2489             // truncate item's width
2490             m_gridModel->DecreaseGridItemWidth(channel, block, truncateSize);
2491           }
2492 
2493           if (m_orientation == VERTICAL)
2494             ProcessItem(posA2, posB, item, m_lastChannel, focused, m_programmeLayout,
2495                         m_focusedProgrammeLayout, currentTime, dirtyregions,
2496                         m_gridModel->GetGridItemWidth(channel, block));
2497           else
2498             ProcessItem(posB, posA2, item, m_lastChannel, focused, m_programmeLayout,
2499                         m_focusedProgrammeLayout, currentTime, dirtyregions,
2500                         m_gridModel->GetGridItemWidth(channel, block));
2501         }
2502 
2503         // increment our X position
2504         posA2 += m_gridModel->GetGridItemWidth(
2505             channel, block); // assumes focused & unfocused layouts have equal length
2506         block +=
2507             MathUtils::round_int(m_gridModel->GetGridItemOriginWidth(channel, block) / m_blockSize);
2508       }
2509     }
2510 
2511     // increment our Y position
2512     channel++;
2513     posB += (m_orientation == VERTICAL) ? m_channelHeight : m_channelWidth;
2514   }
2515 
2516   if (bRender)
2517   {
2518     // and render the focused item last (for overlapping purposes)
2519     if (focusedItem)
2520     {
2521       if (m_orientation == VERTICAL)
2522         RenderItem(focusedPosX, focusedPosY, focusedItem.get(), true);
2523       else
2524         RenderItem(focusedPosY, focusedPosX, focusedItem.get(), true);
2525     }
2526 
2527     CServiceBroker::GetWinSystem()->GetGfxContext().RestoreClipRegion();
2528   }
2529 }
2530