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