2 * Copyright (C) 2012-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
21 #include "guilib/Key.h"
22 #include "guilib/GUIControlFactory.h"
23 #include "guilib/GUIListItem.h"
24 #include "guilib/GUIFontManager.h"
25 #include "guilib/LocalizeStrings.h"
26 #include "guilib/DirtyRegion.h"
28 #include "utils/log.h"
29 #include "utils/MathUtils.h"
30 #include "utils/Variant.h"
31 #include "threads/SystemClock.h"
32 #include "GUIInfoManager.h"
35 #include "pvr/channels/PVRChannel.h"
37 #include "GUIEPGGridContainer.h"
43 #define SHORTGAP 5 // how many blocks is considered a short-gap in nav logic
44 #define MINSPERBLOCK 5 /// would be nice to offer zooming of busy schedules /// performance cost to increase resolution 5 fold?
45 #define BLOCKJUMP 4 // how many blocks are jumped with each analogue scroll action
46 #define BLOCK_SCROLL_OFFSET 60 / MINSPERBLOCK // how many blocks are jumped if we are at left/right edge of grid
48 CGUIEPGGridContainer::CGUIEPGGridContainer(int parentID, int controlID, float posX, float posY, float width,
49 float height, ORIENTATION orientation, int scrollTime,
50 int preloadItems, int timeBlocks, int rulerUnit, const CTextureInfo& progressIndicatorTexture)
51 : IGUIContainer(parentID, controlID, posX, posY, width, height)
52 , m_guiProgressIndicatorTexture(posX, posY, width, height, progressIndicatorTexture)
54 ControlType = GUICONTAINER_EPGGRID;
55 m_blocksPerPage = timeBlocks;
56 m_rulerUnit = rulerUnit;
61 m_channelScrollOffset = 0;
62 m_channelScrollSpeed = 0;
63 m_channelScrollLastTime = 0;
64 m_programmeScrollOffset = 0;
65 m_programmeScrollSpeed = 0;
66 m_programmeScrollLastTime = 0;
67 m_scrollTime = scrollTime ? scrollTime : 1;
71 m_orientation = orientation;
72 m_programmeLayout = NULL;
73 m_focusedProgrammeLayout= NULL;
74 m_channelLayout = NULL;
75 m_focusedChannelLayout = NULL;
90 m_analogScrollCount = 0;
91 m_cacheChannelItems = preloadItems;
92 m_cacheRulerItems = preloadItems;
93 m_cacheProgrammeItems = preloadItems;
96 CGUIEPGGridContainer::~CGUIEPGGridContainer(void)
101 void CGUIEPGGridContainer::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
108 UpdateScrollOffset(currentTime);
109 ProcessChannels(currentTime, dirtyregions);
110 ProcessRuler(currentTime, dirtyregions);
111 ProcessProgrammeGrid(currentTime, dirtyregions);
112 ProcessProgressIndicator(currentTime, dirtyregions);
114 CGUIControl::Process(currentTime, dirtyregions);
117 void CGUIEPGGridContainer::Render()
121 RenderProgrammeGrid();
122 RenderProgressIndicator();
124 CGUIControl::Render();
127 void CGUIEPGGridContainer::ProcessChannels(unsigned int currentTime, CDirtyRegionList &dirtyregions)
129 if (!m_focusedChannelLayout || !m_channelLayout)
132 int chanOffset = (int)floorf(m_channelScrollOffset / m_programmeLayout->Size(m_orientation));
134 int cacheBeforeChannel, cacheAfterChannel;
135 GetChannelCacheOffsets(cacheBeforeChannel, cacheAfterChannel);
137 // Free memory not used on screen
138 if ((int)m_channelItems.size() > m_channelsPerPage + cacheBeforeChannel + cacheAfterChannel)
139 FreeChannelMemory(CorrectOffset(chanOffset - cacheBeforeChannel, 0), CorrectOffset(chanOffset + m_channelsPerPage + 1 + cacheAfterChannel, 0));
141 CPoint originChannel = CPoint(m_channelPosX, m_channelPosY) + m_renderOffset;
142 float pos = (m_orientation == VERTICAL) ? originChannel.y : originChannel.x;
143 float end = (m_orientation == VERTICAL) ? m_posY + m_height : m_posX + m_width;
145 // we offset our draw position to take into account scrolling and whether or not our focused
146 // item is offscreen "above" the list.
147 float drawOffset = (chanOffset - cacheBeforeChannel) * m_channelLayout->Size(m_orientation) - m_channelScrollOffset;
148 if (m_channelOffset + m_channelCursor < chanOffset)
149 drawOffset += m_focusedChannelLayout->Size(m_orientation) - m_channelLayout->Size(m_orientation);
151 end += cacheAfterChannel * m_channelLayout->Size(m_orientation);
153 int current = chanOffset;// - cacheBeforeChannel;
154 while (pos < end && !m_channelItems.empty())
156 int itemNo = CorrectOffset(current, 0);
157 if (itemNo >= (int)m_channelItems.size())
159 bool focused = (current == m_channelOffset + m_channelCursor);
162 CGUIListItemPtr item = m_channelItems[itemNo];
164 if (m_orientation == VERTICAL)
165 ProcessItem(originChannel.x, pos, item.get(), m_lastItem, focused, m_channelLayout, m_focusedChannelLayout, currentTime, dirtyregions);
167 ProcessItem(pos, originChannel.y, item.get(), m_lastItem, focused, m_channelLayout, m_focusedChannelLayout, currentTime, dirtyregions);
169 // increment our position
170 pos += focused ? m_focusedChannelLayout->Size(m_orientation) : m_channelLayout->Size(m_orientation);
175 void CGUIEPGGridContainer::RenderChannels()
177 if (!m_focusedChannelLayout || !m_channelLayout)
180 int chanOffset = (int)floorf(m_channelScrollOffset / m_programmeLayout->Size(m_orientation));
182 /// Render channel names
183 int cacheBeforeChannel, cacheAfterChannel;
184 GetChannelCacheOffsets(cacheBeforeChannel, cacheAfterChannel);
186 if (m_orientation == VERTICAL)
187 g_graphicsContext.SetClipRegion(m_channelPosX, m_channelPosY, m_channelWidth, m_gridHeight);
189 g_graphicsContext.SetClipRegion(m_channelPosX, m_channelPosY, m_gridWidth, m_channelHeight);
191 CPoint originChannel = CPoint(m_channelPosX, m_channelPosY) + m_renderOffset;
192 float pos = (m_orientation == VERTICAL) ? originChannel.y : originChannel.x;
193 float end = (m_orientation == VERTICAL) ? m_posY + m_height : m_posX + m_width;
195 // we offset our draw position to take into account scrolling and whether or not our focused
196 // item is offscreen "above" the list.
197 float drawOffset = (chanOffset - cacheBeforeChannel) * m_channelLayout->Size(m_orientation) - m_channelScrollOffset;
198 if (m_channelOffset + m_channelCursor < chanOffset)
199 drawOffset += m_focusedChannelLayout->Size(m_orientation) - m_channelLayout->Size(m_orientation);
201 end += cacheAfterChannel * m_channelLayout->Size(m_orientation);
203 float focusedPos = 0;
204 CGUIListItemPtr focusedItem;
205 int current = chanOffset;// - cacheBeforeChannel;
206 while (pos < end && !m_channelItems.empty())
208 int itemNo = CorrectOffset(current, 0);
209 if (itemNo >= (int)m_channelItems.size())
211 bool focused = (current == m_channelOffset + m_channelCursor);
214 CGUIListItemPtr item = m_channelItems[itemNo];
223 if (m_orientation == VERTICAL)
224 RenderItem(originChannel.x, pos, item.get(), false);
226 RenderItem(pos, originChannel.y, item.get(), false);
229 // increment our position
230 pos += focused ? m_focusedChannelLayout->Size(m_orientation) : m_channelLayout->Size(m_orientation);
233 // render focused item last so it can overlap other items
236 if (m_orientation == VERTICAL)
237 RenderItem(originChannel.x, focusedPos, focusedItem.get(), true);
239 RenderItem(focusedPos, originChannel.y, focusedItem.get(), true);
241 g_graphicsContext.RestoreClipRegion();
244 void CGUIEPGGridContainer::ProcessRuler(unsigned int currentTime, CDirtyRegionList &dirtyregions)
246 if (!m_rulerLayout || m_rulerItems.size()<=1 || (m_gridEnd - m_gridStart) == CDateTimeSpan(0, 0, 0, 0))
249 int rulerOffset = (int)floorf(m_programmeScrollOffset / m_blockSize);
250 CGUIListItemPtr item = m_rulerItems[0];
251 item->SetLabel(m_rulerItems[rulerOffset/m_rulerUnit+1]->GetLabel2());
252 CGUIListItem* lastitem = NULL; // dummy pointer needed to be passed as reference to ProcessItem() method
253 ProcessItem(m_posX, m_posY, item.get(), lastitem, false, m_rulerLayout, m_rulerLayout, currentTime, dirtyregions, (m_orientation == VERTICAL ? m_channelWidth : m_channelHeight));
255 // render ruler items
256 int cacheBeforeRuler, cacheAfterRuler;
257 GetProgrammeCacheOffsets(cacheBeforeRuler, cacheAfterRuler);
259 // Free memory not used on screen
260 if ((int)m_rulerItems.size() > m_blocksPerPage + cacheBeforeRuler + cacheAfterRuler)
261 FreeRulerMemory(CorrectOffset(rulerOffset/m_rulerUnit+1 - cacheBeforeRuler, 0), CorrectOffset(rulerOffset/m_rulerUnit+1 + m_blocksPerPage + 1 + cacheAfterRuler, 0));
263 CPoint originRuler = CPoint(m_rulerPosX, m_rulerPosY) + m_renderOffset;
264 float pos = (m_orientation == VERTICAL) ? originRuler.x : originRuler.y;
265 float end = (m_orientation == VERTICAL) ? m_posX + m_width : m_posY + m_height;
266 float drawOffset = (rulerOffset - cacheBeforeRuler) * m_blockSize - m_programmeScrollOffset;
268 end += cacheAfterRuler * m_rulerLayout->Size(m_orientation == VERTICAL ? HORIZONTAL : VERTICAL);
270 if (rulerOffset % m_rulerUnit != 0)
272 /* first ruler marker starts before current view */
273 int startBlock = rulerOffset - 1;
275 while (startBlock % m_rulerUnit != 0)
278 int missingSection = rulerOffset - startBlock;
280 pos -= missingSection * m_blockSize;
282 while (pos < end && (rulerOffset/m_rulerUnit+1) < (int)m_rulerItems.size())
284 item = m_rulerItems[rulerOffset/m_rulerUnit+1];
285 if (m_orientation == VERTICAL)
287 ProcessItem(pos, originRuler.y, item.get(), lastitem, false, m_rulerLayout, m_rulerLayout, currentTime, dirtyregions, m_rulerWidth);
292 ProcessItem(originRuler.x, pos, item.get(), lastitem, false, m_rulerLayout, m_rulerLayout, currentTime, dirtyregions, m_rulerWidth);
293 pos += m_rulerHeight;
295 rulerOffset += m_rulerUnit;
299 void CGUIEPGGridContainer::RenderRuler()
301 if (!m_rulerLayout || m_rulerItems.size()<=1 || (m_gridEnd - m_gridStart) == CDateTimeSpan(0, 0, 0, 0))
304 int rulerOffset = (int)floorf(m_programmeScrollOffset / m_blockSize);
306 /// Render single ruler item with date of selected programme
307 g_graphicsContext.SetClipRegion(m_posX, m_posY, m_width, m_height);
308 CGUIListItemPtr item = m_rulerItems[0];
309 RenderItem(m_posX, m_posY, item.get(), false);
310 g_graphicsContext.RestoreClipRegion();
312 // render ruler items
313 int cacheBeforeRuler, cacheAfterRuler;
314 GetProgrammeCacheOffsets(cacheBeforeRuler, cacheAfterRuler);
316 if (m_orientation == VERTICAL)
317 g_graphicsContext.SetClipRegion(m_rulerPosX, m_rulerPosY, m_gridWidth, m_rulerHeight);
319 g_graphicsContext.SetClipRegion(m_rulerPosX, m_rulerPosY, m_rulerWidth, m_gridHeight);
321 CPoint originRuler = CPoint(m_rulerPosX, m_rulerPosY) + m_renderOffset;
322 float pos = (m_orientation == VERTICAL) ? originRuler.x : originRuler.y;
323 float end = (m_orientation == VERTICAL) ? m_posX + m_width : m_posY + m_height;
324 float drawOffset = (rulerOffset - cacheBeforeRuler) * m_blockSize - m_programmeScrollOffset;
326 end += cacheAfterRuler * m_rulerLayout->Size(m_orientation == VERTICAL ? HORIZONTAL : VERTICAL);
328 if (rulerOffset % m_rulerUnit != 0)
330 /* first ruler marker starts before current view */
331 int startBlock = rulerOffset - 1;
333 while (startBlock % m_rulerUnit != 0)
336 int missingSection = rulerOffset - startBlock;
338 pos -= missingSection * m_blockSize;
340 while (pos < end && (rulerOffset/m_rulerUnit+1) < (int)m_rulerItems.size())
342 item = m_rulerItems[rulerOffset/m_rulerUnit+1];
343 if (m_orientation == VERTICAL)
345 RenderItem(pos, originRuler.y, item.get(), false);
350 RenderItem(originRuler.x, pos, item.get(), false);
351 pos += m_rulerHeight;
353 rulerOffset += m_rulerUnit;
355 g_graphicsContext.RestoreClipRegion();
358 void CGUIEPGGridContainer::ProcessProgrammeGrid(unsigned int currentTime, CDirtyRegionList &dirtyregions)
360 if (!m_focusedProgrammeLayout || !m_programmeLayout || m_rulerItems.size()<=1 || (m_gridEnd - m_gridStart) == CDateTimeSpan(0, 0, 0, 0))
363 int blockOffset = (int)floorf(m_programmeScrollOffset / m_blockSize);
364 int chanOffset = (int)floorf(m_channelScrollOffset / m_programmeLayout->Size(m_orientation));
366 int cacheBeforeProgramme, cacheAfterProgramme;
367 GetProgrammeCacheOffsets(cacheBeforeProgramme, cacheAfterProgramme);
369 CPoint originProgramme = CPoint(m_gridPosX, m_gridPosY) + m_renderOffset;
370 float posA = (m_orientation != VERTICAL) ? originProgramme.y : originProgramme.x;
371 float endA = (m_orientation != VERTICAL) ? m_posY + m_height : m_posX + m_width;
372 float posB = (m_orientation == VERTICAL) ? originProgramme.y : originProgramme.x;
373 float endB = (m_orientation == VERTICAL) ? m_gridPosY + m_gridHeight : m_posX + m_width;
374 endA += cacheAfterProgramme * m_blockSize;
376 float DrawOffsetA = blockOffset * m_blockSize - m_programmeScrollOffset;
378 float DrawOffsetB = (chanOffset - cacheBeforeProgramme) * m_channelLayout->Size(m_orientation) - m_channelScrollOffset;
381 int channel = chanOffset;
383 while (posB < endB && !m_channelItems.empty())
385 if (channel >= (int)m_channelItems.size())
388 // Free memory not used on screen
389 FreeProgrammeMemory(channel, CorrectOffset(blockOffset - cacheBeforeProgramme, 0), CorrectOffset(blockOffset + m_ProgrammesPerPage + 1 + cacheAfterProgramme, 0));
391 int block = blockOffset;
394 CGUIListItemPtr item = m_gridIndex[channel][block].item;
395 if (blockOffset > 0 && item == m_gridIndex[channel][blockOffset-1].item)
397 /* first program starts before current view */
398 int startBlock = blockOffset - 1;
399 while (startBlock >= 0 && m_gridIndex[channel][startBlock].item == item)
402 block = startBlock + 1;
403 int missingSection = blockOffset - block;
404 posA2 -= missingSection * m_blockSize;
407 while (posA2 < endA && !m_programmeItems.empty()) // FOR EACH ITEM ///////////////
409 item = m_gridIndex[channel][block].item;
410 if (!item || !item.get()->IsFileItem())
413 bool focused = (channel == m_channelOffset + m_channelCursor) && (item == m_gridIndex[m_channelOffset + m_channelCursor][m_blockOffset + m_blockCursor].item);
415 // calculate the size to truncate if item is out of grid view
416 float truncateSize = 0;
419 truncateSize = posA - posA2;
420 posA2 = posA; // reset to grid start position
423 if (m_orientation == VERTICAL)
425 // truncate item's width
426 m_gridIndex[channel][block].width = m_gridIndex[channel][block].originWidth - truncateSize;
428 ProcessItem(posA2, posB, item.get(), m_lastChannel, focused, m_programmeLayout, m_focusedProgrammeLayout, currentTime, dirtyregions, m_gridIndex[channel][block].width);
430 // increment our X position
431 posA2 += m_gridIndex[channel][block].width; // assumes focused & unfocused layouts have equal length
432 block += (int)(m_gridIndex[channel][block].originWidth / m_blockSize);
436 // truncate item's height
437 m_gridIndex[channel][block].height = m_gridIndex[channel][block].originHeight - truncateSize;
439 ProcessItem(posB, posA2, item.get(), m_lastChannel, focused, m_programmeLayout, m_focusedProgrammeLayout, currentTime, dirtyregions, m_gridIndex[channel][block].height);
441 // increment our X position
442 posA2 += m_gridIndex[channel][block].height; // assumes focused & unfocused layouts have equal length
443 block += (int)(m_gridIndex[channel][block].originHeight / m_blockSize);
447 // increment our Y position
449 posB += m_orientation == VERTICAL ? m_channelHeight : m_channelWidth;
453 void CGUIEPGGridContainer::RenderProgrammeGrid()
455 if (!m_focusedProgrammeLayout || !m_programmeLayout || m_rulerItems.size()<=1 || (m_gridEnd - m_gridStart) == CDateTimeSpan(0, 0, 0, 0))
458 int blockOffset = (int)floorf(m_programmeScrollOffset / m_blockSize);
459 int chanOffset = (int)floorf(m_channelScrollOffset / m_programmeLayout->Size(m_orientation));
461 /// Render programmes
462 int cacheBeforeProgramme, cacheAfterProgramme;
463 GetProgrammeCacheOffsets(cacheBeforeProgramme, cacheAfterProgramme);
465 g_graphicsContext.SetClipRegion(m_gridPosX, m_gridPosY, m_gridWidth, m_gridHeight);
466 CPoint originProgramme = CPoint(m_gridPosX, m_gridPosY) + m_renderOffset;
467 float posA = (m_orientation != VERTICAL) ? originProgramme.y : originProgramme.x;
468 float endA = (m_orientation != VERTICAL) ? m_posY + m_height : m_posX + m_width;
469 float posB = (m_orientation == VERTICAL) ? originProgramme.y : originProgramme.x;
470 float endB = (m_orientation == VERTICAL) ? m_gridPosY + m_gridHeight : m_posX + m_width;
471 endA += cacheAfterProgramme * m_blockSize;
473 float DrawOffsetA = blockOffset * m_blockSize - m_programmeScrollOffset;
475 float DrawOffsetB = (chanOffset - cacheBeforeProgramme) * m_channelLayout->Size(m_orientation) - m_channelScrollOffset;
478 int channel = chanOffset;
480 float focusedPosX = 0;
481 float focusedPosY = 0;
482 CGUIListItemPtr focusedItem;
483 while (posB < endB && !m_channelItems.empty())
485 if (channel >= (int)m_channelItems.size())
488 int block = blockOffset;
491 CGUIListItemPtr item = m_gridIndex[channel][block].item;
492 if (blockOffset > 0 && item == m_gridIndex[channel][blockOffset-1].item)
494 /* first program starts before current view */
495 int startBlock = blockOffset - 1;
496 while (startBlock >= 0 && m_gridIndex[channel][startBlock].item == item)
499 block = startBlock + 1;
500 int missingSection = blockOffset - block;
501 posA2 -= missingSection * m_blockSize;
504 while (posA2 < endA && !m_programmeItems.empty()) // FOR EACH ITEM ///////////////
506 item = m_gridIndex[channel][block].item;
507 if (!item || !item.get()->IsFileItem())
510 bool focused = (channel == m_channelOffset + m_channelCursor) && (item == m_gridIndex[m_channelOffset + m_channelCursor][m_blockOffset + m_blockCursor].item);
512 // reset to grid start position if first item is out of grid view
519 if (m_orientation == VERTICAL)
533 if (m_orientation == VERTICAL)
534 RenderItem(posA2, posB, item.get(), focused);
536 RenderItem(posB, posA2, item.get(), focused);
539 // increment our X position
540 if (m_orientation == VERTICAL)
542 posA2 += m_gridIndex[channel][block].width; // assumes focused & unfocused layouts have equal length
543 block += (int)(m_gridIndex[channel][block].originWidth / m_blockSize);
547 posA2 += m_gridIndex[channel][block].height; // assumes focused & unfocused layouts have equal length
548 block += (int)(m_gridIndex[channel][block].originHeight / m_blockSize);
552 // increment our Y position
554 posB += m_orientation == VERTICAL ? m_channelHeight : m_channelWidth;
557 // and render the focused item last (for overlapping purposes)
559 RenderItem(focusedPosX, focusedPosY, focusedItem.get(), true);
561 g_graphicsContext.RestoreClipRegion();
564 void CGUIEPGGridContainer::ProcessProgressIndicator(unsigned int currentTime, CDirtyRegionList &dirtyregions)
566 CPoint originRuler = CPoint(m_rulerPosX, m_rulerPosY) + m_renderOffset;
567 float width = ((CDateTime::GetUTCDateTime() - m_gridStart).GetSecondsTotal() * m_blockSize) / (MINSPERBLOCK * 60) - m_programmeScrollOffset;
571 m_guiProgressIndicatorTexture.SetVisible(true);
572 m_guiProgressIndicatorTexture.SetPosition(originRuler.x, originRuler.y);
573 if (m_orientation == VERTICAL)
574 m_guiProgressIndicatorTexture.SetWidth(width);
576 m_guiProgressIndicatorTexture.SetHeight(width);
580 m_guiProgressIndicatorTexture.SetVisible(false);
583 m_guiProgressIndicatorTexture.Process(currentTime);
586 void CGUIEPGGridContainer::RenderProgressIndicator()
590 if (m_orientation == VERTICAL)
591 render = g_graphicsContext.SetClipRegion(m_rulerPosX, m_rulerPosY, m_gridWidth, m_height);
593 render = g_graphicsContext.SetClipRegion(m_rulerPosX, m_rulerPosY, m_width, m_gridHeight);
597 m_guiProgressIndicatorTexture.Render();
598 g_graphicsContext.RestoreClipRegion();
602 void CGUIEPGGridContainer::ProcessItem(float posX, float posY, CGUIListItem* item, CGUIListItem *&lastitem,
603 bool focused, CGUIListItemLayout* normallayout, CGUIListItemLayout* focusedlayout,
604 unsigned int currentTime, CDirtyRegionList &dirtyregions, float resize /* = -1.0f */)
606 if (!normallayout || !focusedlayout) return;
609 g_graphicsContext.SetOrigin(posX, posY);
615 if (!item->GetFocusedLayout())
617 CGUIListItemLayout *layout = new CGUIListItemLayout(*focusedlayout);
618 item->SetFocusedLayout(layout);
623 if (m_orientation == VERTICAL)
624 item->GetFocusedLayout()->SetWidth(resize);
626 item->GetFocusedLayout()->SetHeight(resize);
629 if (item != lastitem || !HasFocus())
630 item->GetFocusedLayout()->SetFocusedItem(0);
632 if (item != lastitem && HasFocus())
634 item->GetFocusedLayout()->ResetAnimation(ANIM_TYPE_UNFOCUS);
635 unsigned int subItem = 1;
636 if (lastitem && lastitem->GetFocusedLayout())
637 subItem = lastitem->GetFocusedLayout()->GetFocusedItem();
638 item->GetFocusedLayout()->SetFocusedItem(subItem ? subItem : 1);
641 item->GetFocusedLayout()->Process(item, m_parentID, currentTime, dirtyregions);
646 if (!item->GetLayout())
648 CGUIListItemLayout *layout = new CGUIListItemLayout(*normallayout);
649 item->SetLayout(layout);
654 if (m_orientation == VERTICAL)
655 item->GetLayout()->SetWidth(resize);
657 item->GetLayout()->SetHeight(resize);
660 if (item->GetFocusedLayout())
661 item->GetFocusedLayout()->SetFocusedItem(0);
663 if (item->GetFocusedLayout() && item->GetFocusedLayout()->IsAnimating(ANIM_TYPE_UNFOCUS))
664 item->GetFocusedLayout()->Process(item, m_parentID, currentTime, dirtyregions);
666 item->GetLayout()->Process(item, m_parentID, currentTime, dirtyregions);
668 g_graphicsContext.RestoreOrigin();
671 void CGUIEPGGridContainer::RenderItem(float posX, float posY, CGUIListItem *item, bool focused)
674 g_graphicsContext.SetOrigin(posX, posY);
678 if (item->GetFocusedLayout())
679 item->GetFocusedLayout()->Render(item, m_parentID);
683 if (item->GetFocusedLayout() && item->GetFocusedLayout()->IsAnimating(ANIM_TYPE_UNFOCUS))
684 item->GetFocusedLayout()->Render(item, m_parentID);
685 else if (item->GetLayout())
686 item->GetLayout()->Render(item, m_parentID);
688 g_graphicsContext.RestoreOrigin();
691 bool CGUIEPGGridContainer::OnAction(const CAction &action)
693 switch (action.GetID())
695 case ACTION_MOVE_LEFT:
696 case ACTION_MOVE_RIGHT:
697 case ACTION_MOVE_DOWN:
699 case ACTION_NAV_BACK:
700 { // use base class implementation
702 return CGUIControl::OnAction(action);
708 if (m_orientation == VERTICAL)
710 if (m_channelOffset == 0)
711 { // already on the first page, so move to the first item
715 { // scroll up to the previous page
716 ChannelScroll(-m_channelsPerPage);
720 ProgrammesScroll(-m_blocksPerPage/4);
726 case ACTION_PAGE_DOWN:
728 if (m_orientation == VERTICAL)
730 if (m_channelOffset == m_channels - m_channelsPerPage || m_channels < m_channelsPerPage)
731 { // already at the last page, so move to the last item.
732 SetChannel(m_channels - m_channelOffset - 1);
735 { // scroll down to the next page
736 ChannelScroll(m_channelsPerPage);
740 ProgrammesScroll(m_blocksPerPage/4);
747 // smooth scrolling (for analog controls)
748 case ACTION_TELETEXT_RED:
749 case ACTION_TELETEXT_GREEN:
750 case ACTION_SCROLL_UP: // left horizontal scrolling
752 int blocksToJump = action.GetID() == ACTION_TELETEXT_RED ? m_blocksPerPage/2 : m_blocksPerPage/4;
754 m_analogScrollCount += action.GetAmount() * action.GetAmount();
755 bool handled = false;
757 while (m_analogScrollCount > 0.4)
760 m_analogScrollCount -= 0.4f;
762 if (m_blockOffset > 0 && m_blockCursor <= m_blocksPerPage / 2)
764 ProgrammesScroll(-blocksToJump);
766 else if (m_blockCursor > blocksToJump)
768 SetBlock(m_blockCursor - blocksToJump);
777 case ACTION_TELETEXT_BLUE:
778 case ACTION_TELETEXT_YELLOW:
779 case ACTION_SCROLL_DOWN: // right horizontal scrolling
781 int blocksToJump = action.GetID() == ACTION_TELETEXT_BLUE ? m_blocksPerPage/2 : m_blocksPerPage/4;
783 m_analogScrollCount += action.GetAmount() * action.GetAmount();
784 bool handled = false;
786 while (m_analogScrollCount > 0.4)
789 m_analogScrollCount -= 0.4f;
791 if (m_blockOffset + m_blocksPerPage < m_blocks && m_blockCursor >= m_blocksPerPage / 2)
793 ProgrammesScroll(blocksToJump);
795 else if (m_blockCursor < m_blocksPerPage - blocksToJump && m_blockOffset + m_blockCursor < m_blocks - blocksToJump)
797 SetBlock(m_blockCursor + blocksToJump);
808 return OnClick(action.GetID());
815 bool CGUIEPGGridContainer::OnMessage(CGUIMessage& message)
817 if (message.GetControlId() == GetID())
819 if (message.GetMessage() == GUI_MSG_ITEM_SELECTED)
821 message.SetParam1(GetSelectedItem());
824 else if (message.GetMessage() == GUI_MSG_LABEL_BIND && message.GetPointer())
827 CFileItemList *items = (CFileItemList *)message.GetPointer();
829 /* Create programme items */
830 m_programmeItems.reserve(items->Size());
831 for (int i = 0; i < items->Size(); i++)
833 CFileItemPtr fileItem = items->Get(i);
834 if (fileItem->HasEPGInfoTag() && fileItem->GetEPGInfoTag()->HasPVRChannel())
835 m_programmeItems.push_back(fileItem);
838 /* Create Channel items */
839 int iLastChannelNumber = -1;
840 ItemsPtr itemsPointer;
841 itemsPointer.start = 0;
842 for (unsigned int i = 0; i < m_programmeItems.size(); ++i)
844 const CEpgInfoTag* tag = ((CFileItem*)m_programmeItems[i].get())->GetEPGInfoTag();
845 int iCurrentChannelNumber = tag->PVRChannelNumber();
846 if (iCurrentChannelNumber != iLastChannelNumber)
848 CPVRChannelPtr channel = tag->ChannelTag();
854 itemsPointer.stop = i-1;
855 m_epgItemsPtr.push_back(itemsPointer);
856 itemsPointer.start = i;
858 iLastChannelNumber = iCurrentChannelNumber;
859 CGUIListItemPtr item(new CFileItem(*channel));
860 m_channelItems.push_back(item);
863 if (!m_programmeItems.empty())
865 itemsPointer.stop = m_programmeItems.size()-1;
866 m_epgItemsPtr.push_back(itemsPointer);
870 m_gridIndex.reserve(m_channelItems.size());
871 for (unsigned int i = 0; i < m_channelItems.size(); i++)
873 std::vector<GridItemsPtr> blocks(MAXBLOCKS);
874 m_gridIndex.push_back(blocks);
877 UpdateLayout(true); // true to refresh all items
879 /* Create Ruler items */
880 CDateTime ruler; ruler.SetFromUTCDateTime(m_gridStart);
881 CDateTime rulerEnd; rulerEnd.SetFromUTCDateTime(m_gridEnd);
882 CDateTimeSpan unit(0, 0, m_rulerUnit * MINSPERBLOCK, 0);
883 CGUIListItemPtr rulerItem(new CFileItem(ruler.GetAsLocalizedDate(true, true)));
884 rulerItem->SetProperty("DateLabel", true);
885 m_rulerItems.push_back(rulerItem);
887 for (; ruler < rulerEnd; ruler += unit)
889 CGUIListItemPtr rulerItem(new CFileItem(ruler.GetAsLocalizedTime("", false)));
890 rulerItem->SetLabel2(ruler.GetAsLocalizedDate(true, true));
891 m_rulerItems.push_back(rulerItem);
895 //SelectItem(message.GetParam1());
898 else if (message.GetMessage() == GUI_MSG_REFRESH_LIST)
899 { // update our list contents
900 for (unsigned int i = 0; i < m_channelItems.size(); ++i)
901 m_channelItems[i]->SetInvalid();
902 for (unsigned int i = 0; i < m_programmeItems.size(); ++i)
903 m_programmeItems[i]->SetInvalid();
904 for (unsigned int i = 0; i < m_rulerItems.size(); ++i)
905 m_rulerItems[i]->SetInvalid();
909 return CGUIControl::OnMessage(message);
912 void CGUIEPGGridContainer::UpdateItems()
914 CDateTimeSpan blockDuration, gridDuration;
916 /* check for invalid start and end time */
917 if (m_gridStart >= m_gridEnd)
919 CLog::Log(LOGERROR, "CGUIEPGGridContainer - %s - invalid start and end time set", __FUNCTION__);
920 CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), GetParentID()); // message the window
921 SendWindowMessage(msg);
925 gridDuration = m_gridEnd - m_gridStart;
927 m_blocks = (gridDuration.GetDays()*24*60 + gridDuration.GetHours()*60 + gridDuration.GetMinutes()) / MINSPERBLOCK;
928 if (m_blocks >= MAXBLOCKS)
929 m_blocks = MAXBLOCKS;
931 /* if less than one page, can't display grid */
932 if (m_blocks < m_blocksPerPage)
934 CLog::Log(LOGERROR, "(%s) - Less than one page of data available.", __FUNCTION__);
935 CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), GetParentID()); // message the window
936 SendWindowMessage(msg);
940 blockDuration.SetDateTimeSpan(0, 0, MINSPERBLOCK, 0);
942 long tick(XbmcThreads::SystemClockMillis());
944 for (unsigned int row = 0; row < m_epgItemsPtr.size(); ++row)
946 CDateTime gridCursor = m_gridStart; //reset cursor for new channel
947 unsigned long progIdx = m_epgItemsPtr[row].start;
948 unsigned long lastIdx = m_epgItemsPtr[row].stop;
949 int iEpgId = ((CFileItem *)m_programmeItems[progIdx].get())->GetEPGInfoTag()->EpgID();
951 /** FOR EACH BLOCK **********************************************************************/
953 for (int block = 0; block < m_blocks; block++)
955 while (progIdx <= lastIdx)
957 CGUIListItemPtr item = m_programmeItems[progIdx];
958 const CEpgInfoTag* tag = ((CFileItem *)item.get())->GetEPGInfoTag();
965 if (tag->EpgID() != iEpgId || gridCursor < tag->StartAsUTC() || m_gridEnd <= tag->StartAsUTC())
968 if (gridCursor < tag->EndAsUTC())
970 m_gridIndex[row][block].item = item;
977 gridCursor += blockDuration;
980 /** FOR EACH BLOCK **********************************************************************/
981 int itemSize = 1; // size of the programme in blocks
984 for (int block = 0; block < m_blocks; block++)
986 CGUIListItemPtr item = m_gridIndex[row][block].item;
988 if (item != m_gridIndex[row][block+1].item)
993 CFileItemPtr gapItem(new CFileItem(gapTag));
994 for (int i = block ; i > block - itemSize; i--)
996 m_gridIndex[row][i].item = gapItem;
1001 const CEpgInfoTag* tag = ((CFileItem *)item.get())->GetEPGInfoTag();
1002 m_gridIndex[row][savedBlock].item->SetProperty("GenreType", tag->GenreType());
1005 if (m_orientation == VERTICAL)
1007 m_gridIndex[row][savedBlock].originWidth = itemSize*m_blockSize;
1008 m_gridIndex[row][savedBlock].originHeight = m_channelHeight;
1012 m_gridIndex[row][savedBlock].originWidth = m_channelWidth;
1013 m_gridIndex[row][savedBlock].originHeight = itemSize*m_blockSize;
1016 m_gridIndex[row][savedBlock].width = m_gridIndex[row][savedBlock].originWidth;
1017 m_gridIndex[row][savedBlock].height = m_gridIndex[row][savedBlock].originHeight;
1020 savedBlock = block+1;
1029 /******************************************* END ******************************************/
1031 CLog::Log(LOGDEBUG, "%s completed successfully in %u ms", __FUNCTION__, (unsigned int)(XbmcThreads::SystemClockMillis()-tick));
1033 m_channels = (int)m_epgItemsPtr.size();
1034 m_item = GetItem(m_channelCursor);
1036 SetBlock(GetBlock(m_item->item, m_channelCursor));
1042 void CGUIEPGGridContainer::ChannelScroll(int amount)
1044 // increase or decrease the vertical offset
1045 int offset = m_channelOffset + amount;
1047 if (offset > m_channels - m_channelsPerPage)
1049 offset = m_channels - m_channelsPerPage;
1052 if (offset < 0) offset = 0;
1054 ScrollToChannelOffset(offset);
1057 void CGUIEPGGridContainer::ProgrammesScroll(int amount)
1059 // increase or decrease the horizontal offset
1060 ScrollToBlockOffset(m_blockOffset + amount);
1063 bool CGUIEPGGridContainer::MoveChannel(bool direction, bool wrapAround)
1067 if (m_channelCursor > 0)
1069 SetChannel(m_channelCursor - 1);
1071 else if (m_channelCursor == 0 && m_channelOffset)
1073 ScrollToChannelOffset(m_channelOffset - 1);
1076 else if (wrapAround)
1078 int offset = m_channels - m_channelsPerPage;
1080 if (offset < 0) offset = 0;
1082 SetChannel(m_channels - offset - 1);
1084 ScrollToChannelOffset(offset);
1091 if (m_channelOffset + m_channelCursor + 1 < m_channels)
1093 if (m_channelCursor + 1 < m_channelsPerPage)
1095 SetChannel(m_channelCursor + 1);
1099 ScrollToChannelOffset(m_channelOffset + 1);
1100 SetChannel(m_channelsPerPage - 1);
1103 else if (wrapAround)
1106 ScrollToChannelOffset(0);
1114 bool CGUIEPGGridContainer::MoveProgrammes(bool direction)
1116 if (m_gridIndex.empty() || !m_item)
1121 if (m_channelCursor + m_channelOffset < 0 || m_blockOffset < 0)
1124 if (m_item->item != m_gridIndex[m_channelCursor + m_channelOffset][m_blockOffset].item)
1126 // this is not first item on page
1127 m_item = GetPrevItem(m_channelCursor);
1128 SetBlock(GetBlock(m_item->item, m_channelCursor));
1130 else if (m_blockCursor <= 0 && m_blockOffset)
1132 if (m_blockOffset - BLOCK_SCROLL_OFFSET < 0)
1135 // this is the first item on page
1136 ScrollToBlockOffset(m_blockOffset - BLOCK_SCROLL_OFFSET);
1137 SetBlock(GetBlock(m_item->item, m_channelCursor));
1144 if (m_item->item != m_gridIndex[m_channelCursor + m_channelOffset][m_blocksPerPage + m_blockOffset - 1].item)
1146 // this is not last item on page
1147 m_item = GetNextItem(m_channelCursor);
1148 SetBlock(GetBlock(m_item->item, m_channelCursor));
1150 else if ((m_blockOffset != m_blocks - m_blocksPerPage) && m_blocks > m_blocksPerPage)
1152 if (m_blockOffset + BLOCK_SCROLL_OFFSET > m_blocks)
1155 // this is the last item on page
1156 ScrollToBlockOffset(m_blockOffset + BLOCK_SCROLL_OFFSET);
1157 SetBlock(GetBlock(m_item->item, m_channelCursor));
1165 void CGUIEPGGridContainer::OnUp()
1167 bool wrapAround = m_actionUp.GetNavigation() == GetID() || !m_actionUp.HasActionsMeetingCondition();
1168 if (m_orientation == VERTICAL)
1170 if (!MoveChannel(true, wrapAround))
1171 CGUIControl::OnUp();
1175 if (!MoveProgrammes(true))
1176 CGUIControl::OnUp();
1180 void CGUIEPGGridContainer::OnDown()
1182 bool wrapAround = m_actionDown.GetNavigation() == GetID() || !m_actionDown.HasActionsMeetingCondition();
1183 if (m_orientation == VERTICAL)
1185 if (!MoveChannel(false, wrapAround))
1186 CGUIControl::OnDown();
1190 if (!MoveProgrammes(false))
1191 CGUIControl::OnDown();
1195 void CGUIEPGGridContainer::OnLeft()
1197 bool wrapAround = m_actionLeft.GetNavigation() == GetID() || !m_actionLeft.HasActionsMeetingCondition();
1198 if (m_orientation == VERTICAL)
1200 if (!MoveProgrammes(true))
1201 CGUIControl::OnLeft();
1205 if (!MoveChannel(true, wrapAround))
1206 CGUIControl::OnLeft();
1210 void CGUIEPGGridContainer::OnRight()
1212 bool wrapAround = m_actionRight.GetNavigation() == GetID() || !m_actionRight.HasActionsMeetingCondition();
1213 if (m_orientation == VERTICAL)
1215 if (!MoveProgrammes(false))
1216 CGUIControl::OnRight();
1220 if (!MoveChannel(false, wrapAround))
1221 CGUIControl::OnRight();
1225 void CGUIEPGGridContainer::SetChannel(const CStdString &channel)
1227 int iChannelIndex(-1);
1228 for (unsigned int iIndex = 0; iIndex < m_channelItems.size(); iIndex++)
1230 CStdString strPath = m_channelItems[iIndex]->GetProperty("path").asString(StringUtils::EmptyString);
1231 if (strPath == channel)
1233 iChannelIndex = iIndex;
1238 if (iChannelIndex >= 0)
1239 ScrollToChannelOffset(iChannelIndex);
1242 void CGUIEPGGridContainer::SetChannel(const CPVRChannel &channel)
1244 int iChannelIndex(-1);
1245 for (unsigned int iIndex = 0; iIndex < m_channelItems.size(); iIndex++)
1247 int iChannelId = (int)m_channelItems[iIndex]->GetProperty("channelid").asInteger(-1);
1248 if (iChannelId == channel.ChannelID())
1250 iChannelIndex = iIndex;
1255 if (iChannelIndex >= 0)
1256 ScrollToChannelOffset(iChannelIndex);
1259 void CGUIEPGGridContainer::SetChannel(int channel)
1261 if (m_blockCursor + m_blockOffset == 0 || m_blockOffset + m_blockCursor + GetItemSize(m_item) == m_blocks)
1263 m_item = GetItem(channel);
1266 m_channelCursor = channel;
1267 SetBlock(GetBlock(m_item->item, channel));
1272 /* basic checks failed, need to correctly identify nearest item */
1273 m_item = GetClosestItem(channel);
1276 m_channelCursor = channel;
1277 SetBlock(GetBlock(m_item->item, m_channelCursor));
1281 void CGUIEPGGridContainer::SetBlock(int block)
1285 else if (block > m_blocksPerPage - 1)
1286 m_blockCursor = m_blocksPerPage - 1;
1288 m_blockCursor = block;
1289 m_item = GetItem(m_channelCursor);
1292 CGUIListItemLayout *CGUIEPGGridContainer::GetFocusedLayout() const
1294 CGUIListItemPtr item = GetListItem(0);
1296 if (item.get()) return item->GetFocusedLayout();
1301 bool CGUIEPGGridContainer::SelectItemFromPoint(const CPoint &point, bool justGrid /* = false */)
1303 /* point has already had origin set to m_posX, m_posY */
1304 if (!m_focusedProgrammeLayout || !m_programmeLayout || (justGrid && point.x < 0))
1307 int channel = (int)(point.y / m_channelHeight);
1308 int block = (int)(point.x / m_blockSize);
1310 if (channel > m_channelsPerPage) channel = m_channelsPerPage - 1;
1311 if (channel >= m_channels) channel = m_channels - 1;
1312 if (channel < 0) channel = 0;
1313 if (block > m_blocksPerPage) block = m_blocksPerPage - 1;
1314 if (block < 0) block = 0;
1316 int channelIndex = channel + m_channelOffset;
1317 int blockIndex = block + m_blockOffset;
1319 // bail if out of range
1320 if (channelIndex >= m_channels || blockIndex >= m_blocks)
1322 // bail if block isn't occupied
1323 if (!m_gridIndex[channelIndex][blockIndex].item)
1326 SetChannel(channel);
1331 EVENT_RESULT CGUIEPGGridContainer::OnMouseEvent(const CPoint &point, const CMouseEvent &event)
1335 case ACTION_MOUSE_LEFT_CLICK:
1336 OnMouseClick(0, point);
1337 return EVENT_RESULT_HANDLED;
1338 case ACTION_MOUSE_RIGHT_CLICK:
1339 OnMouseClick(1, point);
1340 return EVENT_RESULT_HANDLED;
1341 case ACTION_MOUSE_DOUBLE_CLICK:
1342 OnMouseDoubleClick(0, point);
1343 return EVENT_RESULT_HANDLED;
1344 case ACTION_MOUSE_WHEEL_UP:
1345 OnMouseWheel(-1, point);
1346 return EVENT_RESULT_HANDLED;
1347 case ACTION_MOUSE_WHEEL_DOWN:
1348 OnMouseWheel(1, point);
1349 return EVENT_RESULT_HANDLED;
1350 case ACTION_GESTURE_BEGIN:
1352 // we want exclusive access
1353 CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, GetID(), GetParentID());
1354 SendWindowMessage(msg);
1355 return EVENT_RESULT_HANDLED;
1357 case ACTION_GESTURE_END:
1359 // we're done with exclusive access
1360 CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, 0, GetParentID());
1361 SendWindowMessage(msg);
1362 ScrollToChannelOffset(MathUtils::round_int(m_channelScrollOffset / m_channelLayout->Size(m_orientation)));
1363 ScrollToBlockOffset(MathUtils::round_int(m_programmeScrollOffset / m_blockSize));
1364 return EVENT_RESULT_HANDLED;
1366 case ACTION_GESTURE_PAN:
1368 if (m_orientation == VERTICAL)
1370 m_programmeScrollOffset -= event.m_offsetX;
1371 m_channelScrollOffset -= event.m_offsetY;
1375 m_channelScrollOffset -= event.m_offsetX;
1376 m_programmeScrollOffset -= event.m_offsetY;
1379 m_channelOffset = MathUtils::round_int(m_channelScrollOffset / m_channelLayout->Size(m_orientation));
1380 m_blockOffset = MathUtils::round_int(m_programmeScrollOffset / m_blockSize);
1382 return EVENT_RESULT_HANDLED;
1385 return EVENT_RESULT_UNHANDLED;
1389 bool CGUIEPGGridContainer::OnMouseOver(const CPoint &point)
1391 // select the item under the pointer
1392 SelectItemFromPoint(point - CPoint(m_gridPosX, m_posY + m_rulerHeight), false);
1393 return CGUIControl::OnMouseOver(point);
1396 bool CGUIEPGGridContainer::OnMouseClick(int dwButton, const CPoint &point)
1398 if (SelectItemFromPoint(point - CPoint(m_gridPosX, m_posY + m_rulerHeight)))
1399 { // send click message to window
1400 OnClick(ACTION_MOUSE_LEFT_CLICK + dwButton);
1407 bool CGUIEPGGridContainer::OnMouseDoubleClick(int dwButton, const CPoint &point)
1409 if (SelectItemFromPoint(point - CPoint(m_gridPosX, m_posY + m_rulerHeight)))
1410 { // send double click message to window
1411 OnClick(ACTION_MOUSE_DOUBLE_CLICK + dwButton);
1418 bool CGUIEPGGridContainer::OnClick(int actionID)
1422 if (actionID == ACTION_SELECT_ITEM || actionID == ACTION_MOUSE_LEFT_CLICK)
1424 // grab the currently focused subitem (if applicable)
1425 CGUIListItemLayout *focusedLayout = GetFocusedLayout();
1428 subItem = focusedLayout->GetFocusedItem();
1431 // Don't know what to do, so send to our parent window.
1432 CGUIMessage msg(GUI_MSG_CLICKED, GetID(), GetParentID(), actionID, subItem);
1433 return SendWindowMessage(msg);
1436 bool CGUIEPGGridContainer::OnMouseWheel(char wheel, const CPoint &point)
1438 ///doesn't work while an item is selected?
1439 ProgrammesScroll(-wheel);
1443 int CGUIEPGGridContainer::GetSelectedItem() const
1445 if (m_gridIndex.empty() ||
1446 m_epgItemsPtr.empty() ||
1447 m_channelCursor + m_channelOffset >= m_channels ||
1448 m_blockCursor + m_blockOffset >= m_blocks)
1451 CGUIListItemPtr currentItem = m_gridIndex[m_channelCursor + m_channelOffset][m_blockCursor + m_blockOffset].item;
1455 for (int i = 0; i < (int)m_programmeItems.size(); i++)
1457 if (currentItem == m_programmeItems[i])
1463 CGUIListItemPtr CGUIEPGGridContainer::GetListItem(int offset, unsigned int flag) const
1465 if (m_channelItems.empty())
1466 return CGUIListItemPtr();
1468 int item = m_channelCursor + m_channelOffset + offset;
1469 if (flag & INFOFLAG_LISTITEM_POSITION)
1470 item = (int)(m_channelScrollOffset / m_channelLayout->Size(VERTICAL));
1472 if (flag & INFOFLAG_LISTITEM_WRAP)
1474 item %= (int)m_channelItems.size();
1475 if (item < 0) item += m_channelItems.size();
1476 return m_channelItems[item];
1480 if (item >= 0 && item < (int)m_channelItems.size())
1481 return m_channelItems[item];
1483 return CGUIListItemPtr();
1486 CStdString CGUIEPGGridContainer::GetLabel(int info) const
1491 case CONTAINER_NUM_PAGES:
1492 label = StringUtils::Format("%u", (m_channels + m_channelsPerPage - 1) / m_channelsPerPage);
1494 case CONTAINER_CURRENT_PAGE:
1495 label = StringUtils::Format("%u", 1 + (m_channelCursor + m_channelOffset) / m_channelsPerPage );
1497 case CONTAINER_POSITION:
1498 label = StringUtils::Format("%i", 1 + m_channelCursor + m_channelOffset);
1500 case CONTAINER_NUM_ITEMS:
1501 label = StringUtils::Format("%u", m_channels);
1509 GridItemsPtr *CGUIEPGGridContainer::GetClosestItem(const int &channel)
1511 GridItemsPtr *closest = GetItem(channel);
1516 int block = GetBlock(closest->item, channel);
1517 int left; // num blocks to start of previous item
1518 int right; // num blocks to start of next item
1520 if (block == m_blockCursor)
1521 return closest; // item & m_item start together
1523 if (block + GetItemSize(closest) == m_blockCursor + GetItemSize(m_item))
1524 return closest; // closest item ends when current does
1526 if (block > m_blockCursor) // item starts after m_item
1528 left = m_blockCursor - GetBlock(closest->item, channel);
1529 right = block - m_blockCursor;
1533 left = m_blockCursor - block;
1534 right = GetBlock(GetNextItem(channel)->item, channel) - m_blockCursor;
1537 if (right <= SHORTGAP && right <= left && m_blockCursor + right < m_blocksPerPage)
1538 return &m_gridIndex[channel + m_channelOffset][m_blockCursor + right + m_blockOffset];
1540 return &m_gridIndex[channel + m_channelOffset][m_blockCursor - left + m_blockOffset];
1543 int CGUIEPGGridContainer::GetItemSize(GridItemsPtr *item)
1546 return (int) m_blockSize; /// stops it crashing
1548 return (int) ((m_orientation == VERTICAL ? item->width : item->height) / m_blockSize);
1551 int CGUIEPGGridContainer::GetBlock(const CGUIListItemPtr &item, const int &channel)
1556 return GetRealBlock(item, channel) - m_blockOffset;
1559 int CGUIEPGGridContainer::GetRealBlock(const CGUIListItemPtr &item, const int &channel)
1561 int channelIndex = channel + m_channelOffset;
1564 while (m_gridIndex[channelIndex][block].item != item && block < m_blocks)
1570 GridItemsPtr *CGUIEPGGridContainer::GetNextItem(const int &channel)
1572 int channelIndex = channel + m_channelOffset;
1573 int blockIndex = m_blockCursor + m_blockOffset;
1574 if (channelIndex >= m_channels || blockIndex >= m_blocks)
1577 int i = m_blockCursor;
1579 while (i < m_blocksPerPage && m_gridIndex[channelIndex][i + m_blockOffset].item == m_gridIndex[channelIndex][blockIndex].item)
1582 return &m_gridIndex[channelIndex][i + m_blockOffset];
1585 GridItemsPtr *CGUIEPGGridContainer::GetPrevItem(const int &channel)
1587 int channelIndex = channel + m_channelOffset;
1588 int blockIndex = m_blockCursor + m_blockOffset;
1589 if (channelIndex >= m_channels || blockIndex >= m_blocks)
1592 int i = m_blockCursor;
1594 while (i > 0 && m_gridIndex[channelIndex][i + m_blockOffset].item == m_gridIndex[channelIndex][blockIndex].item)
1597 return &m_gridIndex[channelIndex][i + m_blockOffset];
1600 GridItemsPtr *CGUIEPGGridContainer::GetItem(const int &channel)
1602 int channelIndex = channel + m_channelOffset;
1603 int blockIndex = m_blockCursor + m_blockOffset;
1604 if (channelIndex >= m_channels || blockIndex >= m_blocks)
1607 return &m_gridIndex[channelIndex][blockIndex];
1610 void CGUIEPGGridContainer::SetFocus(bool bOnOff)
1612 if (bOnOff != HasFocus())
1615 /*m_lastItem.reset();
1616 m_lastChannel.reset();*/
1619 CGUIControl::SetFocus(bOnOff);
1622 void CGUIEPGGridContainer::DoRender()
1624 CGUIControl::DoRender();
1628 void CGUIEPGGridContainer::ScrollToChannelOffset(int offset)
1630 float size = m_programmeLayout->Size(VERTICAL);
1631 int range = m_channelsPerPage / 4;
1633 if (range <= 0) range = 1;
1635 if (offset * size < m_channelScrollOffset && m_channelScrollOffset - offset * size > size * range)
1636 { // scrolling up, and we're jumping more than 0.5 of a screen
1637 m_channelScrollOffset = (offset + range) * size;
1640 if (offset * size > m_channelScrollOffset && offset * size - m_channelScrollOffset > size * range)
1641 { // scrolling down, and we're jumping more than 0.5 of a screen
1642 m_channelScrollOffset = (offset - range) * size;
1645 m_channelScrollSpeed = (offset * size - m_channelScrollOffset) / m_scrollTime;
1647 m_channelOffset = offset;
1650 void CGUIEPGGridContainer::ScrollToBlockOffset(int offset)
1652 // make sure offset is in valid range
1653 offset = std::max(0, std::min(offset, m_blocks - m_blocksPerPage));
1655 float size = m_blockSize;
1656 int range = m_blocksPerPage / 1;
1658 if (range <= 0) range = 1;
1660 if (offset * size < m_programmeScrollOffset && m_programmeScrollOffset - offset * size > size * range)
1661 { // scrolling left, and we're jumping more than 0.5 of a screen
1662 m_programmeScrollOffset = (offset + range) * size;
1665 if (offset * size > m_programmeScrollOffset && offset * size - m_programmeScrollOffset > size * range)
1666 { // scrolling right, and we're jumping more than 0.5 of a screen
1667 m_programmeScrollOffset = (offset - range) * size;
1670 m_programmeScrollSpeed = (offset * size - m_programmeScrollOffset) / m_scrollTime;
1672 m_blockOffset = offset;
1675 void CGUIEPGGridContainer::ValidateOffset()
1677 if (!m_programmeLayout)
1680 if (m_channelOffset > m_channels - m_channelsPerPage || m_channelScrollOffset > (m_channels - m_channelsPerPage) * m_channelHeight)
1682 m_channelOffset = m_channels - m_channelsPerPage;
1683 m_channelScrollOffset = m_channelOffset * m_channelHeight;
1686 if (m_channelOffset < 0 || m_channelScrollOffset < 0)
1688 m_channelOffset = 0;
1689 m_channelScrollOffset = 0;
1692 if (m_blockOffset > m_blocks - m_blocksPerPage || m_programmeScrollOffset > (m_blocks - m_blocksPerPage) * m_blockSize)
1694 m_blockOffset = m_blocks - m_blocksPerPage;
1695 m_programmeScrollOffset = m_blockOffset * m_blockSize;
1698 if (m_blockOffset < 0 || m_programmeScrollOffset < 0)
1701 m_programmeScrollOffset = 0;
1705 void CGUIEPGGridContainer::LoadLayout(TiXmlElement *layout)
1707 /* layouts for the channel column */
1708 TiXmlElement *itemElement = layout->FirstChildElement("channellayout");
1710 { // we have a new item layout
1711 CGUIListItemLayout itemLayout;
1712 itemLayout.LoadLayout(itemElement, GetParentID(), false);
1713 m_channelLayouts.push_back(itemLayout);
1714 itemElement = itemElement->NextSiblingElement("channellayout");
1716 itemElement = layout->FirstChildElement("focusedchannellayout");
1718 { // we have a new item layout
1719 CGUIListItemLayout itemLayout;
1720 itemLayout.LoadLayout(itemElement, GetParentID(), true);
1721 m_focusedChannelLayouts.push_back(itemLayout);
1722 itemElement = itemElement->NextSiblingElement("focusedchannellayout");
1725 /* layouts for the grid items */
1726 itemElement = layout->FirstChildElement("focusedlayout");
1729 CGUIListItemLayout itemLayout;
1730 itemLayout.LoadLayout(itemElement, GetParentID(), true);
1731 m_focusedProgrammeLayouts.push_back(itemLayout);
1732 itemElement = itemElement->NextSiblingElement("focusedlayout");
1734 itemElement = layout->FirstChildElement("itemlayout");
1737 CGUIListItemLayout itemLayout;
1738 itemLayout.LoadLayout(itemElement, GetParentID(), false);
1739 m_programmeLayouts.push_back(itemLayout);
1740 itemElement = itemElement->NextSiblingElement("itemlayout");
1743 /* layout for the timeline above the grid */
1744 itemElement = layout->FirstChildElement("rulerlayout");
1747 CGUIListItemLayout itemLayout;
1748 itemLayout.LoadLayout(itemElement, GetParentID(), false);
1749 m_rulerLayouts.push_back(itemLayout);
1750 itemElement = itemElement->NextSiblingElement("rulerlayout");
1754 void CGUIEPGGridContainer::UpdateLayout(bool updateAllItems)
1756 // if container is invalid, either new data has arrived, or m_blockSize has changed
1757 // need to run UpdateItems rather than CalculateLayout?
1759 { // free memory of items
1760 for (iItems it = m_channelItems.begin(); it != m_channelItems.end(); it++)
1761 (*it)->FreeMemory();
1762 for (iItems it = m_rulerItems.begin(); it != m_rulerItems.end(); it++)
1763 (*it)->FreeMemory();
1764 for (iItems it = m_programmeItems.begin(); it != m_programmeItems.end(); it++)
1765 (*it)->FreeMemory();
1768 // and recalculate the layout
1772 CStdString CGUIEPGGridContainer::GetDescription() const
1774 CStdString strLabel;
1775 int item = GetSelectedItem();
1776 if (item >= 0 && item < (int)m_programmeItems.size())
1778 CGUIListItemPtr pItem = m_programmeItems[item];
1779 strLabel = pItem->GetLabel();
1784 void CGUIEPGGridContainer::ClearGridIndex(void)
1786 for (unsigned int i = 0; i < m_gridIndex.size(); i++)
1788 for (int block = 0; block < m_blocks; block++)
1790 if (m_gridIndex[i][block].item)
1791 m_gridIndex[i][block].item.get()->ClearProperties();
1793 m_gridIndex[i].clear();
1795 m_gridIndex.clear();
1798 void CGUIEPGGridContainer::Reset()
1803 m_channelItems.clear();
1804 m_programmeItems.clear();
1805 m_rulerItems.clear();
1806 m_epgItemsPtr.clear();
1809 m_lastChannel = NULL;
1812 void CGUIEPGGridContainer::GoToBegin()
1814 ScrollToBlockOffset(0);
1818 void CGUIEPGGridContainer::GoToEnd()
1820 int blocksEnd = 0; // the end block of the last epg element for the selected channel
1821 int blocksStart = 0; // the start block of the last epg element for the selected channel
1822 int blockOffset = 0; // the block offset to scroll to
1823 for (int blockIndex = m_blocks; blockIndex >= 0 && (!blocksEnd || !blocksStart); blockIndex--)
1825 if (!blocksEnd && m_gridIndex[m_channelCursor + m_channelOffset][blockIndex].item != NULL)
1826 blocksEnd = blockIndex;
1827 if (blocksEnd && m_gridIndex[m_channelCursor + m_channelOffset][blocksEnd].item !=
1828 m_gridIndex[m_channelCursor + m_channelOffset][blockIndex].item)
1829 blocksStart = blockIndex + 1;
1831 if (blocksEnd - blocksStart > m_blocksPerPage)
1832 blockOffset = blocksStart;
1833 else if (blocksEnd > m_blocksPerPage)
1834 blockOffset = blocksEnd - m_blocksPerPage;
1836 ScrollToBlockOffset(blockOffset); // scroll to the start point of the last epg element
1837 SetBlock(m_blocksPerPage - 1); // select the last epg element
1840 void CGUIEPGGridContainer::GoToNow()
1842 CDateTime currentDate = CDateTime::GetCurrentDateTime().GetAsUTCDateTime();
1843 int offset = ((currentDate - m_gridStart).GetSecondsTotal() / 60 - 30) / MINSPERBLOCK;
1844 ScrollToBlockOffset(offset);
1847 void CGUIEPGGridContainer::SetStartEnd(CDateTime start, CDateTime end)
1849 m_gridStart = CDateTime(start.GetYear(), start.GetMonth(), start.GetDay(), start.GetHour(), start.GetMinute() >= 30 ? 30 : 0, 0);
1850 m_gridEnd = CDateTime(end.GetYear(), end.GetMonth(), end.GetDay(), end.GetHour(), end.GetMinute() >= 30 ? 30 : 0, 0);
1852 CLog::Log(LOGDEBUG, "CGUIEPGGridContainer - %s - start=%s end=%s",
1853 __FUNCTION__, m_gridStart.GetAsLocalizedDateTime(false, true).c_str(), m_gridEnd.GetAsLocalizedDateTime(false, true).c_str());
1856 void CGUIEPGGridContainer::CalculateLayout()
1858 CGUIListItemLayout *oldFocusedChannelLayout = m_focusedChannelLayout;
1859 CGUIListItemLayout *oldChannelLayout = m_channelLayout;
1860 CGUIListItemLayout *oldFocusedProgrammeLayout = m_focusedProgrammeLayout;
1861 CGUIListItemLayout *oldProgrammeLayout = m_programmeLayout;
1862 CGUIListItemLayout *oldRulerLayout = m_rulerLayout;
1863 GetCurrentLayouts();
1865 if (!m_focusedProgrammeLayout || !m_programmeLayout || !m_focusedChannelLayout || !m_channelLayout || !m_rulerLayout)
1868 if (oldChannelLayout == m_channelLayout && oldFocusedChannelLayout == m_focusedChannelLayout &&
1869 oldProgrammeLayout == m_programmeLayout && oldFocusedProgrammeLayout == m_focusedProgrammeLayout &&
1870 oldRulerLayout == m_rulerLayout)
1871 return; // nothing has changed, so don't update stuff
1873 m_channelHeight = m_channelLayout->Size(VERTICAL);
1874 m_channelWidth = m_channelLayout->Size(HORIZONTAL);
1875 if (m_orientation == VERTICAL)
1877 m_rulerHeight = m_rulerLayout->Size(VERTICAL);
1878 m_gridPosX = m_posX + m_channelWidth;
1879 m_gridPosY = m_posY + m_rulerHeight;
1880 m_gridWidth = m_width - m_channelWidth;
1881 m_gridHeight = m_height - m_rulerHeight;
1882 m_blockSize = m_gridWidth / m_blocksPerPage;
1883 m_rulerWidth = m_rulerUnit * m_blockSize;
1884 m_channelPosX = m_posX;
1885 m_channelPosY = m_posY + m_rulerHeight;
1886 m_rulerPosX = m_posX + m_channelWidth;
1887 m_rulerPosY = m_posY;
1888 m_channelsPerPage = (int)(m_gridHeight / m_channelHeight);
1889 m_ProgrammesPerPage = (int)(m_gridWidth / m_blockSize) + 1;
1893 m_rulerWidth = m_rulerLayout->Size(HORIZONTAL);
1894 m_gridPosX = m_posX + m_rulerWidth;
1895 m_gridPosY = m_posY + m_channelHeight;
1896 m_gridWidth = m_width - m_rulerWidth;
1897 m_gridHeight = m_height - m_channelHeight;
1898 m_blockSize = m_gridHeight / m_blocksPerPage;
1899 m_rulerHeight = m_rulerUnit * m_blockSize;
1900 m_channelPosX = m_posX + m_rulerWidth;
1901 m_channelPosY = m_posY;
1902 m_rulerPosX = m_posX;
1903 m_rulerPosY = m_posY + m_channelHeight;
1904 m_channelsPerPage = (int)(m_gridWidth / m_channelWidth);
1905 m_ProgrammesPerPage = (int)(m_gridHeight / m_blockSize) + 1;
1908 // ensure that the scroll offsets are a multiple of our sizes
1909 m_channelScrollOffset = m_channelOffset * m_programmeLayout->Size(m_orientation);
1910 m_programmeScrollOffset = m_blockOffset * m_blockSize;
1913 void CGUIEPGGridContainer::UpdateScrollOffset(unsigned int currentTime)
1915 if (!m_programmeLayout)
1917 m_channelScrollOffset += m_channelScrollSpeed * (currentTime - m_channelScrollLastTime);
1918 if ((m_channelScrollSpeed < 0 && m_channelScrollOffset < m_channelOffset * m_programmeLayout->Size(m_orientation)) ||
1919 (m_channelScrollSpeed > 0 && m_channelScrollOffset > m_channelOffset * m_programmeLayout->Size(m_orientation)))
1921 m_channelScrollOffset = m_channelOffset * m_programmeLayout->Size(m_orientation);
1922 m_channelScrollSpeed = 0;
1924 m_channelScrollLastTime = currentTime;
1926 m_programmeScrollOffset += m_programmeScrollSpeed * (currentTime - m_programmeScrollLastTime);
1927 if ((m_programmeScrollSpeed < 0 && m_programmeScrollOffset < m_blockOffset * m_blockSize) ||
1928 (m_programmeScrollSpeed > 0 && m_programmeScrollOffset > m_blockOffset * m_blockSize))
1930 m_programmeScrollOffset = m_blockOffset * m_blockSize;
1931 m_programmeScrollSpeed = 0;
1933 m_programmeScrollLastTime = currentTime;
1936 void CGUIEPGGridContainer::GetCurrentLayouts()
1938 m_channelLayout = NULL;
1939 for (unsigned int i = 0; i < m_channelLayouts.size(); i++)
1941 if (m_channelLayouts[i].CheckCondition())
1943 m_channelLayout = &m_channelLayouts[i];
1947 if (!m_channelLayout && !m_channelLayouts.empty())
1948 m_channelLayout = &m_channelLayouts[0]; // failsafe
1950 m_focusedChannelLayout = NULL;
1951 for (unsigned int i = 0; i < m_focusedChannelLayouts.size(); i++)
1953 if (m_focusedChannelLayouts[i].CheckCondition())
1955 m_focusedChannelLayout = &m_focusedChannelLayouts[i];
1959 if (!m_focusedChannelLayout && !m_focusedChannelLayouts.empty())
1960 m_focusedChannelLayout = &m_focusedChannelLayouts[0]; // failsafe
1962 m_programmeLayout = NULL;
1963 for (unsigned int i = 0; i < m_programmeLayouts.size(); i++)
1965 if (m_programmeLayouts[i].CheckCondition())
1967 m_programmeLayout = &m_programmeLayouts[i];
1971 if (!m_programmeLayout && !m_programmeLayouts.empty())
1972 m_programmeLayout = &m_programmeLayouts[0]; // failsafe
1974 m_focusedProgrammeLayout = NULL;
1975 for (unsigned int i = 0; i < m_focusedProgrammeLayouts.size(); i++)
1977 if (m_focusedProgrammeLayouts[i].CheckCondition())
1979 m_focusedProgrammeLayout = &m_focusedProgrammeLayouts[i];
1983 if (!m_focusedProgrammeLayout && !m_focusedProgrammeLayouts.empty())
1984 m_focusedProgrammeLayout = &m_focusedProgrammeLayouts[0]; // failsafe
1986 m_rulerLayout = NULL;
1987 for (unsigned int i = 0; i < m_rulerLayouts.size(); i++)
1989 if (m_rulerLayouts[i].CheckCondition())
1991 m_rulerLayout = &m_rulerLayouts[i];
1995 if (!m_rulerLayout && !m_rulerLayouts.empty())
1996 m_rulerLayout = &m_rulerLayouts[0]; // failsafe
1999 int CGUIEPGGridContainer::CorrectOffset(int offset, int cursor) const
2001 return offset + cursor;
2004 void CGUIEPGGridContainer::SetRenderOffset(const CPoint &offset)
2006 m_renderOffset = offset;
2009 void CGUIEPGGridContainer::FreeChannelMemory(int keepStart, int keepEnd)
2011 if (keepStart < keepEnd)
2012 { // remove before keepStart and after keepEnd
2013 for (int i = 0; i < keepStart && i < (int)m_channelItems.size(); ++i)
2014 m_channelItems[i]->FreeMemory();
2015 for (int i = keepEnd + 1; i < (int)m_channelItems.size(); ++i)
2016 m_channelItems[i]->FreeMemory();
2020 for (int i = keepEnd + 1; i < keepStart && i < (int)m_channelItems.size(); ++i)
2021 m_channelItems[i]->FreeMemory();
2025 void CGUIEPGGridContainer::FreeProgrammeMemory(int channel, int keepStart, int keepEnd)
2027 if (keepStart < keepEnd)
2028 { // remove before keepStart and after keepEnd
2029 if (keepStart > 0 && keepStart < m_blocks)
2031 // if item exist and block is not part of visible item
2032 CGUIListItemPtr last = m_gridIndex[channel][keepStart].item;
2033 for (int i = keepStart - 1 ; i > 0 ; i--)
2035 if (m_gridIndex[channel][i].item && m_gridIndex[channel][i].item != last)
2037 m_gridIndex[channel][i].item->FreeMemory();
2038 // FreeMemory() is smart enough to not cause any problems when called multiple times on same item
2039 // but we can make use of condition needed to not call FreeMemory() on item that is partially visible
2040 // to avoid calling FreeMemory() multiple times on item that ocupy few blocks in a row
2041 last = m_gridIndex[channel][i].item;
2046 if (keepEnd > 0 && keepEnd < m_blocks)
2048 CGUIListItemPtr last = m_gridIndex[channel][keepEnd].item;
2049 for (int i = keepEnd + 1 ; i < m_blocks ; i++)
2051 // if item exist and block is not part of visible item
2052 if (m_gridIndex[channel][i].item && m_gridIndex[channel][i].item != last)
2054 m_gridIndex[channel][i].item->FreeMemory();
2055 // FreeMemory() is smart enough to not cause any problems when called multiple times on same item
2056 // but we can make use of condition needed to not call FreeMemory() on item that is partially visible
2057 // to avoid calling FreeMemory() multiple times on item that ocupy few blocks in a row
2058 last = m_gridIndex[channel][i].item;
2065 void CGUIEPGGridContainer::FreeRulerMemory(int keepStart, int keepEnd)
2067 if (keepStart < keepEnd)
2068 { // remove before keepStart and after keepEnd
2069 for (int i = 1; i < keepStart && i < (int)m_rulerItems.size(); ++i)
2070 m_rulerItems[i]->FreeMemory();
2071 for (int i = keepEnd + 1; i < (int)m_rulerItems.size(); ++i)
2072 m_rulerItems[i]->FreeMemory();
2076 for (int i = keepEnd + 1; i < keepStart && i < (int)m_rulerItems.size(); ++i)
2080 m_rulerItems[i]->FreeMemory();
2085 void CGUIEPGGridContainer::GetChannelCacheOffsets(int &cacheBefore, int &cacheAfter)
2087 if (m_channelScrollSpeed > 0)
2090 cacheAfter = m_cacheChannelItems;
2092 else if (m_channelScrollSpeed < 0)
2094 cacheBefore = m_cacheChannelItems;
2099 cacheBefore = m_cacheChannelItems / 2;
2100 cacheAfter = m_cacheChannelItems / 2;
2104 void CGUIEPGGridContainer::GetProgrammeCacheOffsets(int &cacheBefore, int &cacheAfter)
2106 if (m_programmeScrollSpeed > 0)
2109 cacheAfter = m_cacheProgrammeItems;
2111 else if (m_programmeScrollSpeed < 0)
2113 cacheBefore = m_cacheProgrammeItems;
2118 cacheBefore = m_cacheProgrammeItems / 2;
2119 cacheAfter = m_cacheProgrammeItems / 2;