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
47 CGUIEPGGridContainer::CGUIEPGGridContainer(int parentID, int controlID, float posX, float posY, float width,
48 float height, ORIENTATION orientation, int scrollTime,
49 int preloadItems, int timeBlocks, int rulerUnit, const CTextureInfo& progressIndicatorTexture)
50 : IGUIContainer(parentID, controlID, posX, posY, width, height)
51 , m_guiProgressIndicatorTexture(posX, posY, width, height, progressIndicatorTexture)
53 ControlType = GUICONTAINER_EPGGRID;
54 m_blocksPerPage = timeBlocks;
55 m_rulerUnit = rulerUnit;
60 m_channelScrollOffset = 0;
61 m_channelScrollSpeed = 0;
62 m_channelScrollLastTime = 0;
63 m_programmeScrollOffset = 0;
64 m_programmeScrollSpeed = 0;
65 m_programmeScrollLastTime = 0;
66 m_scrollTime = scrollTime ? scrollTime : 1;
70 m_orientation = orientation;
71 m_programmeLayout = NULL;
72 m_focusedProgrammeLayout= NULL;
73 m_channelLayout = NULL;
74 m_focusedChannelLayout = NULL;
89 m_analogScrollCount = 0;
90 m_cacheChannelItems = preloadItems;
91 m_cacheRulerItems = preloadItems;
92 m_cacheProgrammeItems = preloadItems;
95 CGUIEPGGridContainer::~CGUIEPGGridContainer(void)
100 void CGUIEPGGridContainer::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
107 UpdateScrollOffset(currentTime);
108 ProcessChannels(currentTime, dirtyregions);
109 ProcessRuler(currentTime, dirtyregions);
110 ProcessProgrammeGrid(currentTime, dirtyregions);
111 ProcessProgressIndicator(currentTime, dirtyregions);
113 CGUIControl::Process(currentTime, dirtyregions);
116 void CGUIEPGGridContainer::Render()
120 RenderProgrammeGrid();
121 RenderProgressIndicator();
123 CGUIControl::Render();
126 void CGUIEPGGridContainer::ProcessChannels(unsigned int currentTime, CDirtyRegionList &dirtyregions)
128 if (!m_focusedChannelLayout || !m_channelLayout)
131 int chanOffset = (int)floorf(m_channelScrollOffset / m_programmeLayout->Size(m_orientation));
133 int cacheBeforeChannel, cacheAfterChannel;
134 GetChannelCacheOffsets(cacheBeforeChannel, cacheAfterChannel);
136 // Free memory not used on screen
137 if ((int)m_channelItems.size() > m_channelsPerPage + cacheBeforeChannel + cacheAfterChannel)
138 FreeChannelMemory(CorrectOffset(chanOffset - cacheBeforeChannel, 0), CorrectOffset(chanOffset + m_channelsPerPage + 1 + cacheAfterChannel, 0));
140 CPoint originChannel = CPoint(m_channelPosX, m_channelPosY) + m_renderOffset;
141 float pos = (m_orientation == VERTICAL) ? originChannel.y : originChannel.x;
142 float end = (m_orientation == VERTICAL) ? m_posY + m_height : m_posX + m_width;
144 // we offset our draw position to take into account scrolling and whether or not our focused
145 // item is offscreen "above" the list.
146 float drawOffset = (chanOffset - cacheBeforeChannel) * m_channelLayout->Size(m_orientation) - m_channelScrollOffset;
147 if (m_channelOffset + m_channelCursor < chanOffset)
148 drawOffset += m_focusedChannelLayout->Size(m_orientation) - m_channelLayout->Size(m_orientation);
150 end += cacheAfterChannel * m_channelLayout->Size(m_orientation);
152 int current = chanOffset;// - cacheBeforeChannel;
153 while (pos < end && !m_channelItems.empty())
155 int itemNo = CorrectOffset(current, 0);
156 if (itemNo >= (int)m_channelItems.size())
158 bool focused = (current == m_channelOffset + m_channelCursor);
161 CGUIListItemPtr item = m_channelItems[itemNo];
163 if (m_orientation == VERTICAL)
164 ProcessItem(originChannel.x, pos, item.get(), m_lastItem, focused, m_channelLayout, m_focusedChannelLayout, currentTime, dirtyregions);
166 ProcessItem(pos, originChannel.y, item.get(), m_lastItem, focused, m_channelLayout, m_focusedChannelLayout, currentTime, dirtyregions);
168 // increment our position
169 pos += focused ? m_focusedChannelLayout->Size(m_orientation) : m_channelLayout->Size(m_orientation);
174 void CGUIEPGGridContainer::RenderChannels()
176 if (!m_focusedChannelLayout || !m_channelLayout)
179 int chanOffset = (int)floorf(m_channelScrollOffset / m_programmeLayout->Size(m_orientation));
181 /// Render channel names
182 int cacheBeforeChannel, cacheAfterChannel;
183 GetChannelCacheOffsets(cacheBeforeChannel, cacheAfterChannel);
185 if (m_orientation == VERTICAL)
186 g_graphicsContext.SetClipRegion(m_channelPosX, m_channelPosY, m_channelWidth, m_gridHeight);
188 g_graphicsContext.SetClipRegion(m_channelPosX, m_channelPosY, m_gridWidth, m_channelHeight);
190 CPoint originChannel = CPoint(m_channelPosX, m_channelPosY) + m_renderOffset;
191 float pos = (m_orientation == VERTICAL) ? originChannel.y : originChannel.x;
192 float end = (m_orientation == VERTICAL) ? m_posY + m_height : m_posX + m_width;
194 // we offset our draw position to take into account scrolling and whether or not our focused
195 // item is offscreen "above" the list.
196 float drawOffset = (chanOffset - cacheBeforeChannel) * m_channelLayout->Size(m_orientation) - m_channelScrollOffset;
197 if (m_channelOffset + m_channelCursor < chanOffset)
198 drawOffset += m_focusedChannelLayout->Size(m_orientation) - m_channelLayout->Size(m_orientation);
200 end += cacheAfterChannel * m_channelLayout->Size(m_orientation);
202 float focusedPos = 0;
203 CGUIListItemPtr focusedItem;
204 int current = chanOffset;// - cacheBeforeChannel;
205 while (pos < end && !m_channelItems.empty())
207 int itemNo = CorrectOffset(current, 0);
208 if (itemNo >= (int)m_channelItems.size())
210 bool focused = (current == m_channelOffset + m_channelCursor);
213 CGUIListItemPtr item = m_channelItems[itemNo];
222 if (m_orientation == VERTICAL)
223 RenderItem(originChannel.x, pos, item.get(), false);
225 RenderItem(pos, originChannel.y, item.get(), false);
228 // increment our position
229 pos += focused ? m_focusedChannelLayout->Size(m_orientation) : m_channelLayout->Size(m_orientation);
232 // render focused item last so it can overlap other items
235 if (m_orientation == VERTICAL)
236 RenderItem(originChannel.x, focusedPos, focusedItem.get(), true);
238 RenderItem(focusedPos, originChannel.y, focusedItem.get(), true);
240 g_graphicsContext.RestoreClipRegion();
243 void CGUIEPGGridContainer::ProcessRuler(unsigned int currentTime, CDirtyRegionList &dirtyregions)
245 if (!m_rulerLayout || m_rulerItems.size()<=1 || (m_gridEnd - m_gridStart) == CDateTimeSpan(0, 0, 0, 0))
248 int rulerOffset = (int)floorf(m_programmeScrollOffset / m_blockSize);
249 CGUIListItemPtr item = m_rulerItems[0];
250 item->SetLabel(m_rulerItems[rulerOffset/m_rulerUnit+1]->GetLabel2());
251 CGUIListItem* lastitem = NULL; // dummy pointer needed to be passed as reference to ProcessItem() method
252 ProcessItem(m_posX, m_posY, item.get(), lastitem, false, m_rulerLayout, m_rulerLayout, currentTime, dirtyregions, (m_orientation == VERTICAL ? m_channelWidth : m_channelHeight));
254 // render ruler items
255 int cacheBeforeRuler, cacheAfterRuler;
256 GetProgrammeCacheOffsets(cacheBeforeRuler, cacheAfterRuler);
258 // Free memory not used on screen
259 if ((int)m_rulerItems.size() > m_blocksPerPage + cacheBeforeRuler + cacheAfterRuler)
260 FreeRulerMemory(CorrectOffset(rulerOffset/m_rulerUnit+1 - cacheBeforeRuler, 0), CorrectOffset(rulerOffset/m_rulerUnit+1 + m_blocksPerPage + 1 + cacheAfterRuler, 0));
262 CPoint originRuler = CPoint(m_rulerPosX, m_rulerPosY) + m_renderOffset;
263 float pos = (m_orientation == VERTICAL) ? originRuler.x : originRuler.y;
264 float end = (m_orientation == VERTICAL) ? m_posX + m_width : m_posY + m_height;
265 float drawOffset = (rulerOffset - cacheBeforeRuler) * m_blockSize - m_programmeScrollOffset;
267 end += cacheAfterRuler * m_rulerLayout->Size(m_orientation == VERTICAL ? HORIZONTAL : VERTICAL);
269 if (rulerOffset % m_rulerUnit != 0)
271 /* first ruler marker starts before current view */
272 int startBlock = rulerOffset - 1;
274 while (startBlock % m_rulerUnit != 0)
277 int missingSection = rulerOffset - startBlock;
279 pos -= missingSection * m_blockSize;
281 while (pos < end && (rulerOffset/m_rulerUnit+1) < (int)m_rulerItems.size())
283 item = m_rulerItems[rulerOffset/m_rulerUnit+1];
284 if (m_orientation == VERTICAL)
286 ProcessItem(pos, originRuler.y, item.get(), lastitem, false, m_rulerLayout, m_rulerLayout, currentTime, dirtyregions, m_rulerWidth);
291 ProcessItem(originRuler.x, pos, item.get(), lastitem, false, m_rulerLayout, m_rulerLayout, currentTime, dirtyregions, m_rulerWidth);
292 pos += m_rulerHeight;
294 rulerOffset += m_rulerUnit;
298 void CGUIEPGGridContainer::RenderRuler()
300 if (!m_rulerLayout || m_rulerItems.size()<=1 || (m_gridEnd - m_gridStart) == CDateTimeSpan(0, 0, 0, 0))
303 int rulerOffset = (int)floorf(m_programmeScrollOffset / m_blockSize);
305 /// Render single ruler item with date of selected programme
306 g_graphicsContext.SetClipRegion(m_posX, m_posY, m_width, m_height);
307 CGUIListItemPtr item = m_rulerItems[0];
308 RenderItem(m_posX, m_posY, item.get(), false);
309 g_graphicsContext.RestoreClipRegion();
311 // render ruler items
312 int cacheBeforeRuler, cacheAfterRuler;
313 GetProgrammeCacheOffsets(cacheBeforeRuler, cacheAfterRuler);
315 if (m_orientation == VERTICAL)
316 g_graphicsContext.SetClipRegion(m_rulerPosX, m_rulerPosY, m_gridWidth, m_rulerHeight);
318 g_graphicsContext.SetClipRegion(m_rulerPosX, m_rulerPosY, m_rulerWidth, m_gridHeight);
320 CPoint originRuler = CPoint(m_rulerPosX, m_rulerPosY) + m_renderOffset;
321 float pos = (m_orientation == VERTICAL) ? originRuler.x : originRuler.y;
322 float end = (m_orientation == VERTICAL) ? m_posX + m_width : m_posY + m_height;
323 float drawOffset = (rulerOffset - cacheBeforeRuler) * m_blockSize - m_programmeScrollOffset;
325 end += cacheAfterRuler * m_rulerLayout->Size(m_orientation == VERTICAL ? HORIZONTAL : VERTICAL);
327 if (rulerOffset % m_rulerUnit != 0)
329 /* first ruler marker starts before current view */
330 int startBlock = rulerOffset - 1;
332 while (startBlock % m_rulerUnit != 0)
335 int missingSection = rulerOffset - startBlock;
337 pos -= missingSection * m_blockSize;
339 while (pos < end && (rulerOffset/m_rulerUnit+1) < (int)m_rulerItems.size())
341 item = m_rulerItems[rulerOffset/m_rulerUnit+1];
342 if (m_orientation == VERTICAL)
344 RenderItem(pos, originRuler.y, item.get(), false);
349 RenderItem(originRuler.x, pos, item.get(), false);
350 pos += m_rulerHeight;
352 rulerOffset += m_rulerUnit;
354 g_graphicsContext.RestoreClipRegion();
357 void CGUIEPGGridContainer::ProcessProgrammeGrid(unsigned int currentTime, CDirtyRegionList &dirtyregions)
359 if (!m_focusedProgrammeLayout || !m_programmeLayout || m_rulerItems.size()<=1 || (m_gridEnd - m_gridStart) == CDateTimeSpan(0, 0, 0, 0))
362 int blockOffset = (int)floorf(m_programmeScrollOffset / m_blockSize);
363 int chanOffset = (int)floorf(m_channelScrollOffset / m_programmeLayout->Size(m_orientation));
365 int cacheBeforeProgramme, cacheAfterProgramme;
366 GetProgrammeCacheOffsets(cacheBeforeProgramme, cacheAfterProgramme);
368 CPoint originProgramme = CPoint(m_gridPosX, m_gridPosY) + m_renderOffset;
369 float posA = (m_orientation != VERTICAL) ? originProgramme.y : originProgramme.x;
370 float endA = (m_orientation != VERTICAL) ? m_posY + m_height : m_posX + m_width;
371 float posB = (m_orientation == VERTICAL) ? originProgramme.y : originProgramme.x;
372 float endB = (m_orientation == VERTICAL) ? m_gridPosY + m_gridHeight : m_posX + m_width;
373 endA += cacheAfterProgramme * m_blockSize;
375 float DrawOffsetA = blockOffset * m_blockSize - m_programmeScrollOffset;
377 float DrawOffsetB = (chanOffset - cacheBeforeProgramme) * m_channelLayout->Size(m_orientation) - m_channelScrollOffset;
380 int channel = chanOffset;
382 while (posB < endB && !m_channelItems.empty())
384 if (channel >= (int)m_channelItems.size())
387 // Free memory not used on screen
388 FreeProgrammeMemory(channel, CorrectOffset(blockOffset - cacheBeforeProgramme, 0), CorrectOffset(blockOffset + m_ProgrammesPerPage + 1 + cacheAfterProgramme, 0));
390 int block = blockOffset;
393 CGUIListItemPtr item = m_gridIndex[channel][block].item;
394 if (blockOffset > 0 && item == m_gridIndex[channel][blockOffset-1].item)
396 /* first program starts before current view */
397 int startBlock = blockOffset - 1;
398 while (startBlock >= 0 && m_gridIndex[channel][startBlock].item == item)
401 block = startBlock + 1;
402 int missingSection = blockOffset - block;
403 posA2 -= missingSection * m_blockSize;
406 while (posA2 < endA && !m_programmeItems.empty()) // FOR EACH ITEM ///////////////
408 item = m_gridIndex[channel][block].item;
409 if (!item || !item.get()->IsFileItem())
412 bool focused = (channel == m_channelOffset + m_channelCursor) && (item == m_gridIndex[m_channelOffset + m_channelCursor][m_blockOffset + m_blockCursor].item);
414 if (m_orientation == VERTICAL)
415 ProcessItem(posA2, posB, item.get(), m_lastChannel, focused, m_programmeLayout, m_focusedProgrammeLayout, currentTime, dirtyregions, m_gridIndex[channel][block].width);
417 ProcessItem(posB, posA2, item.get(), m_lastChannel, focused, m_programmeLayout, m_focusedProgrammeLayout, currentTime, dirtyregions, m_gridIndex[channel][block].height);
419 // increment our X position
420 if (m_orientation == VERTICAL)
422 posA2 += m_gridIndex[channel][block].width; // assumes focused & unfocused layouts have equal length
423 block += (int)(m_gridIndex[channel][block].width / m_blockSize);
427 posA2 += m_gridIndex[channel][block].height; // assumes focused & unfocused layouts have equal length
428 block += (int)(m_gridIndex[channel][block].height / m_blockSize);
432 // increment our Y position
434 posB += m_orientation == VERTICAL ? m_channelHeight : m_channelWidth;
438 void CGUIEPGGridContainer::RenderProgrammeGrid()
440 if (!m_focusedProgrammeLayout || !m_programmeLayout || m_rulerItems.size()<=1 || (m_gridEnd - m_gridStart) == CDateTimeSpan(0, 0, 0, 0))
443 int blockOffset = (int)floorf(m_programmeScrollOffset / m_blockSize);
444 int chanOffset = (int)floorf(m_channelScrollOffset / m_programmeLayout->Size(m_orientation));
446 /// Render programmes
447 int cacheBeforeProgramme, cacheAfterProgramme;
448 GetProgrammeCacheOffsets(cacheBeforeProgramme, cacheAfterProgramme);
450 g_graphicsContext.SetClipRegion(m_gridPosX, m_gridPosY, m_gridWidth, m_gridHeight);
451 CPoint originProgramme = CPoint(m_gridPosX, m_gridPosY) + m_renderOffset;
452 float posA = (m_orientation != VERTICAL) ? originProgramme.y : originProgramme.x;
453 float endA = (m_orientation != VERTICAL) ? m_posY + m_height : m_posX + m_width;
454 float posB = (m_orientation == VERTICAL) ? originProgramme.y : originProgramme.x;
455 float endB = (m_orientation == VERTICAL) ? m_gridPosY + m_gridHeight : m_posX + m_width;
456 endA += cacheAfterProgramme * m_blockSize;
458 float DrawOffsetA = blockOffset * m_blockSize - m_programmeScrollOffset;
460 float DrawOffsetB = (chanOffset - cacheBeforeProgramme) * m_channelLayout->Size(m_orientation) - m_channelScrollOffset;
463 int channel = chanOffset;
465 float focusedPosX = 0;
466 float focusedPosY = 0;
467 CGUIListItemPtr focusedItem;
468 while (posB < endB && !m_channelItems.empty())
470 if (channel >= (int)m_channelItems.size())
473 int block = blockOffset;
476 CGUIListItemPtr item = m_gridIndex[channel][block].item;
477 if (blockOffset > 0 && item == m_gridIndex[channel][blockOffset-1].item)
479 /* first program starts before current view */
480 int startBlock = blockOffset - 1;
481 while (startBlock >= 0 && m_gridIndex[channel][startBlock].item == item)
484 block = startBlock + 1;
485 int missingSection = blockOffset - block;
486 posA2 -= missingSection * m_blockSize;
489 while (posA2 < endA && !m_programmeItems.empty()) // FOR EACH ITEM ///////////////
491 item = m_gridIndex[channel][block].item;
492 if (!item || !item.get()->IsFileItem())
495 bool focused = (channel == m_channelOffset + m_channelCursor) && (item == m_gridIndex[m_channelOffset + m_channelCursor][m_blockOffset + m_blockCursor].item);
500 if (m_orientation == VERTICAL)
514 if (m_orientation == VERTICAL)
515 RenderItem(posA2, posB, item.get(), focused);
517 RenderItem(posB, posA2, item.get(), focused);
520 // increment our X position
521 if (m_orientation == VERTICAL)
523 posA2 += m_gridIndex[channel][block].width; // assumes focused & unfocused layouts have equal length
524 block += (int)(m_gridIndex[channel][block].width / m_blockSize);
528 posA2 += m_gridIndex[channel][block].height; // assumes focused & unfocused layouts have equal length
529 block += (int)(m_gridIndex[channel][block].height / m_blockSize);
533 // increment our Y position
535 posB += m_orientation == VERTICAL ? m_channelHeight : m_channelWidth;
538 // and render the focused item last (for overlapping purposes)
540 RenderItem(focusedPosX, focusedPosY, focusedItem.get(), true);
542 g_graphicsContext.RestoreClipRegion();
545 void CGUIEPGGridContainer::ProcessProgressIndicator(unsigned int currentTime, CDirtyRegionList &dirtyregions)
547 CPoint originRuler = CPoint(m_rulerPosX, m_rulerPosY) + m_renderOffset;
548 float width = ((CDateTime::GetUTCDateTime() - m_gridStart).GetSecondsTotal() * m_blockSize) / (MINSPERBLOCK * 60) - m_programmeScrollOffset;
552 m_guiProgressIndicatorTexture.SetVisible(true);
553 m_guiProgressIndicatorTexture.SetPosition(originRuler.x, originRuler.y);
554 if (m_orientation == VERTICAL)
555 m_guiProgressIndicatorTexture.SetWidth(width);
557 m_guiProgressIndicatorTexture.SetHeight(width);
561 m_guiProgressIndicatorTexture.SetVisible(false);
564 m_guiProgressIndicatorTexture.Process(currentTime);
567 void CGUIEPGGridContainer::RenderProgressIndicator()
571 if (m_orientation == VERTICAL)
572 render = g_graphicsContext.SetClipRegion(m_rulerPosX, m_rulerPosY, m_gridWidth, m_height);
574 render = g_graphicsContext.SetClipRegion(m_rulerPosX, m_rulerPosY, m_width, m_gridHeight);
578 m_guiProgressIndicatorTexture.Render();
579 g_graphicsContext.RestoreClipRegion();
583 void CGUIEPGGridContainer::ProcessItem(float posX, float posY, CGUIListItem* item, CGUIListItem *&lastitem,
584 bool focused, CGUIListItemLayout* normallayout, CGUIListItemLayout* focusedlayout,
585 unsigned int currentTime, CDirtyRegionList &dirtyregions, float resize /* = -1.0f */)
587 if (!normallayout || !focusedlayout) return;
590 g_graphicsContext.SetOrigin(posX, posY);
596 if (!item->GetFocusedLayout())
598 CGUIListItemLayout *layout = new CGUIListItemLayout(*focusedlayout);
601 if (m_orientation == VERTICAL)
602 layout->SetWidth(resize);
604 layout->SetHeight(resize);
606 item->SetFocusedLayout(layout);
608 if (item->GetFocusedLayout())
610 if (item != lastitem || !HasFocus())
612 item->GetFocusedLayout()->SetFocusedItem(0);
614 if (item != lastitem && HasFocus())
616 item->GetFocusedLayout()->ResetAnimation(ANIM_TYPE_UNFOCUS);
617 unsigned int subItem = 1;
618 if (lastitem && lastitem->GetFocusedLayout())
619 subItem = lastitem->GetFocusedLayout()->GetFocusedItem();
620 item->GetFocusedLayout()->SetFocusedItem(subItem ? subItem : 1);
622 item->GetFocusedLayout()->Process(item,m_parentID,currentTime,dirtyregions);
628 if (item->GetFocusedLayout())
629 item->GetFocusedLayout()->SetFocusedItem(0); // focus is not set
630 if (!item->GetLayout())
632 CGUIListItemLayout *layout = new CGUIListItemLayout(*normallayout);
635 if (m_orientation == VERTICAL)
636 layout->SetWidth(resize);
638 layout->SetHeight(resize);
640 item->SetLayout(layout);
642 if (item->GetFocusedLayout() && item->GetFocusedLayout()->IsAnimating(ANIM_TYPE_UNFOCUS))
643 item->GetFocusedLayout()->Process(item,m_parentID,currentTime,dirtyregions);
644 else if (item->GetLayout())
645 item->GetLayout()->Process(item,m_parentID,currentTime,dirtyregions);
647 g_graphicsContext.RestoreOrigin();
650 void CGUIEPGGridContainer::RenderItem(float posX, float posY, CGUIListItem *item, bool focused)
653 g_graphicsContext.SetOrigin(posX, posY);
657 if (item->GetFocusedLayout())
658 item->GetFocusedLayout()->Render(item, m_parentID);
662 if (item->GetFocusedLayout() && item->GetFocusedLayout()->IsAnimating(ANIM_TYPE_UNFOCUS))
663 item->GetFocusedLayout()->Render(item, m_parentID);
664 else if (item->GetLayout())
665 item->GetLayout()->Render(item, m_parentID);
667 g_graphicsContext.RestoreOrigin();
670 bool CGUIEPGGridContainer::OnAction(const CAction &action)
672 switch (action.GetID())
674 case ACTION_MOVE_LEFT:
675 case ACTION_MOVE_RIGHT:
676 case ACTION_MOVE_DOWN:
678 { // use base class implementation
680 return CGUIControl::OnAction(action);
686 if (m_orientation == VERTICAL)
688 if (m_channelOffset == 0)
689 { // already on the first page, so move to the first item
693 { // scroll up to the previous page
694 ChannelScroll(-m_channelsPerPage);
698 ProgrammesScroll(-m_blocksPerPage/4);
704 case ACTION_PAGE_DOWN:
706 if (m_orientation == VERTICAL)
708 if (m_channelOffset == m_channels - m_channelsPerPage || m_channels < m_channelsPerPage)
709 { // already at the last page, so move to the last item.
710 SetChannel(m_channels - m_channelOffset - 1);
713 { // scroll down to the next page
714 ChannelScroll(m_channelsPerPage);
718 ProgrammesScroll(m_blocksPerPage/4);
725 // smooth scrolling (for analog controls)
726 case ACTION_TELETEXT_RED:
727 case ACTION_TELETEXT_GREEN:
728 case ACTION_SCROLL_UP: // left horizontal scrolling
730 int blocksToJump = action.GetID() == ACTION_TELETEXT_RED ? m_blocksPerPage/2 : m_blocksPerPage/4;
732 m_analogScrollCount += action.GetAmount() * action.GetAmount();
733 bool handled = false;
735 while (m_analogScrollCount > 0.4)
738 m_analogScrollCount -= 0.4f;
740 if (m_blockOffset > 0 && m_blockCursor <= m_blocksPerPage / 2)
742 ProgrammesScroll(-blocksToJump);
744 else if (m_blockCursor > blocksToJump)
746 SetBlock(m_blockCursor - blocksToJump);
755 case ACTION_TELETEXT_BLUE:
756 case ACTION_TELETEXT_YELLOW:
757 case ACTION_SCROLL_DOWN: // right horizontal scrolling
759 int blocksToJump = action.GetID() == ACTION_TELETEXT_BLUE ? m_blocksPerPage/2 : m_blocksPerPage/4;
761 m_analogScrollCount += action.GetAmount() * action.GetAmount();
762 bool handled = false;
764 while (m_analogScrollCount > 0.4)
767 m_analogScrollCount -= 0.4f;
769 if (m_blockOffset + m_blocksPerPage < m_blocks && m_blockCursor >= m_blocksPerPage / 2)
771 ProgrammesScroll(blocksToJump);
773 else if (m_blockCursor < m_blocksPerPage - blocksToJump && m_blockOffset + m_blockCursor < m_blocks - blocksToJump)
775 SetBlock(m_blockCursor + blocksToJump);
786 return OnClick(action.GetID());
793 bool CGUIEPGGridContainer::OnMessage(CGUIMessage& message)
795 if (message.GetControlId() == GetID())
797 if (message.GetMessage() == GUI_MSG_ITEM_SELECTED)
799 message.SetParam1(GetSelectedItem());
802 else if (message.GetMessage() == GUI_MSG_LABEL_BIND && message.GetPointer())
805 CFileItemList *items = (CFileItemList *)message.GetPointer();
807 /* Create programme items */
808 m_programmeItems.reserve(items->Size());
809 for (int i = 0; i < items->Size(); i++)
811 CFileItemPtr fileItem = items->Get(i);
812 if (fileItem->HasEPGInfoTag() && fileItem->GetEPGInfoTag()->HasPVRChannel())
813 m_programmeItems.push_back(fileItem);
816 /* Create Channel items */
817 int iLastChannelNumber = -1;
818 ItemsPtr itemsPointer;
819 itemsPointer.start = 0;
820 for (unsigned int i = 0; i < m_programmeItems.size(); ++i)
822 const CEpgInfoTag* tag = ((CFileItem*)m_programmeItems[i].get())->GetEPGInfoTag();
823 int iCurrentChannelNumber = tag->PVRChannelNumber();
824 if (iCurrentChannelNumber != iLastChannelNumber)
826 CPVRChannelPtr channel = tag->ChannelTag();
832 itemsPointer.stop = i-1;
833 m_epgItemsPtr.push_back(itemsPointer);
834 itemsPointer.start = i;
836 iLastChannelNumber = iCurrentChannelNumber;
837 CGUIListItemPtr item(new CFileItem(*channel));
838 m_channelItems.push_back(item);
841 if (!m_programmeItems.empty())
843 itemsPointer.stop = m_programmeItems.size()-1;
844 m_epgItemsPtr.push_back(itemsPointer);
848 m_gridIndex.reserve(m_channelItems.size());
849 for (unsigned int i = 0; i < m_channelItems.size(); i++)
851 std::vector<GridItemsPtr> blocks(MAXBLOCKS);
852 m_gridIndex.push_back(blocks);
855 UpdateLayout(true); // true to refresh all items
857 /* Create Ruler items */
858 CDateTime ruler; ruler.SetFromUTCDateTime(m_gridStart);
859 CDateTime rulerEnd; rulerEnd.SetFromUTCDateTime(m_gridEnd);
860 CDateTimeSpan unit(0, 0, m_rulerUnit * MINSPERBLOCK, 0);
861 CGUIListItemPtr rulerItem(new CFileItem(ruler.GetAsLocalizedDate(true, true)));
862 rulerItem->SetProperty("DateLabel", true);
863 m_rulerItems.push_back(rulerItem);
865 for (; ruler < rulerEnd; ruler += unit)
867 CGUIListItemPtr rulerItem(new CFileItem(ruler.GetAsLocalizedTime("", false)));
868 rulerItem->SetLabel2(ruler.GetAsLocalizedDate(true, true));
869 m_rulerItems.push_back(rulerItem);
873 //SelectItem(message.GetParam1());
876 else if (message.GetMessage() == GUI_MSG_REFRESH_LIST)
877 { // update our list contents
878 for (unsigned int i = 0; i < m_channelItems.size(); ++i)
879 m_channelItems[i]->SetInvalid();
880 for (unsigned int i = 0; i < m_programmeItems.size(); ++i)
881 m_programmeItems[i]->SetInvalid();
882 for (unsigned int i = 0; i < m_rulerItems.size(); ++i)
883 m_rulerItems[i]->SetInvalid();
887 return CGUIControl::OnMessage(message);
890 void CGUIEPGGridContainer::UpdateItems()
892 CDateTimeSpan blockDuration, gridDuration;
894 /* check for invalid start and end time */
895 if (m_gridStart >= m_gridEnd)
897 CLog::Log(LOGERROR, "CGUIEPGGridContainer - %s - invalid start and end time set", __FUNCTION__);
898 CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), GetParentID()); // message the window
899 SendWindowMessage(msg);
903 gridDuration = m_gridEnd - m_gridStart;
905 m_blocks = (gridDuration.GetDays()*24*60 + gridDuration.GetHours()*60 + gridDuration.GetMinutes()) / MINSPERBLOCK;
906 if (m_blocks >= MAXBLOCKS)
907 m_blocks = MAXBLOCKS;
909 /* if less than one page, can't display grid */
910 if (m_blocks < m_blocksPerPage)
912 CLog::Log(LOGERROR, "(%s) - Less than one page of data available.", __FUNCTION__);
913 CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), GetParentID()); // message the window
914 SendWindowMessage(msg);
918 blockDuration.SetDateTimeSpan(0, 0, MINSPERBLOCK, 0);
920 long tick(XbmcThreads::SystemClockMillis());
922 for (unsigned int row = 0; row < m_epgItemsPtr.size(); ++row)
924 CDateTime gridCursor = m_gridStart; //reset cursor for new channel
925 unsigned long progIdx = m_epgItemsPtr[row].start;
926 unsigned long lastIdx = m_epgItemsPtr[row].stop;
927 int iEpgId = ((CFileItem *)m_programmeItems[progIdx].get())->GetEPGInfoTag()->EpgID();
929 /** FOR EACH BLOCK **********************************************************************/
931 for (int block = 0; block < m_blocks; block++)
933 while (progIdx <= lastIdx)
935 CGUIListItemPtr item = m_programmeItems[progIdx];
936 const CEpgInfoTag* tag = ((CFileItem *)item.get())->GetEPGInfoTag();
943 if (tag->EpgID() != iEpgId || gridCursor < tag->StartAsUTC() || m_gridEnd <= tag->StartAsUTC())
946 if (gridCursor < tag->EndAsUTC())
948 m_gridIndex[row][block].item = item;
955 gridCursor += blockDuration;
958 /** FOR EACH BLOCK **********************************************************************/
959 int itemSize = 1; // size of the programme in blocks
962 for (int block = 0; block < m_blocks; block++)
964 if (m_gridIndex[row][block].item != m_gridIndex[row][block+1].item)
966 if (!m_gridIndex[row][block].item)
968 CEpgInfoTag broadcast;
969 CFileItemPtr unknown(new CFileItem(broadcast));
970 for (int i = block ; i > block - itemSize; i--)
972 m_gridIndex[row][i].item = unknown;
976 CGUIListItemPtr item = m_gridIndex[row][block].item;
977 CFileItem *fileItem = (CFileItem *)item.get();
979 m_gridIndex[row][savedBlock].item->SetProperty("GenreType", fileItem->GetEPGInfoTag()->GenreType());
980 if (m_orientation == VERTICAL)
982 m_gridIndex[row][savedBlock].width = itemSize*m_blockSize;
983 m_gridIndex[row][savedBlock].height = m_channelHeight;
987 m_gridIndex[row][savedBlock].width = m_channelWidth;
988 m_gridIndex[row][savedBlock].height = itemSize*m_blockSize;
992 savedBlock = block+1;
1001 /******************************************* END ******************************************/
1003 CLog::Log(LOGDEBUG, "%s completed successfully in %u ms", __FUNCTION__, (unsigned int)(XbmcThreads::SystemClockMillis()-tick));
1005 m_channels = (int)m_epgItemsPtr.size();
1006 m_item = GetItem(m_channelCursor);
1008 SetBlock(GetBlock(m_item->item, m_channelCursor));
1013 void CGUIEPGGridContainer::ChannelScroll(int amount)
1015 // increase or decrease the vertical offset
1016 int offset = m_channelOffset + amount;
1018 if (offset > m_channels - m_channelsPerPage)
1020 offset = m_channels - m_channelsPerPage;
1023 if (offset < 0) offset = 0;
1025 ScrollToChannelOffset(offset);
1028 void CGUIEPGGridContainer::ProgrammesScroll(int amount)
1030 // increase or decrease the horizontal offset
1031 ScrollToBlockOffset(m_blockOffset + amount);
1034 bool CGUIEPGGridContainer::MoveChannel(bool direction, bool wrapAround)
1038 if (m_channelCursor > 0)
1040 SetChannel(m_channelCursor - 1);
1042 else if (m_channelCursor == 0 && m_channelOffset)
1044 ScrollToChannelOffset(m_channelOffset - 1);
1047 else if (wrapAround)
1049 int offset = m_channels - m_channelsPerPage;
1051 if (offset < 0) offset = 0;
1053 SetChannel(m_channels - offset - 1);
1055 ScrollToChannelOffset(offset);
1062 if (m_channelOffset + m_channelCursor + 1 < m_channels)
1064 if (m_channelCursor + 1 < m_channelsPerPage)
1066 SetChannel(m_channelCursor + 1);
1070 ScrollToChannelOffset(m_channelOffset + 1);
1071 SetChannel(m_channelsPerPage - 1);
1074 else if (wrapAround)
1077 ScrollToChannelOffset(0);
1085 bool CGUIEPGGridContainer::MoveProgrammes(bool direction)
1087 if (m_gridIndex.empty() || !m_item)
1092 if (m_channelCursor + m_channelOffset < 0 || m_blockOffset < 0)
1095 if (m_item->item != m_gridIndex[m_channelCursor + m_channelOffset][m_blockOffset].item)
1097 // this is not first item on page
1098 m_item = GetPrevItem(m_channelCursor);
1099 SetBlock(GetBlock(m_item->item, m_channelCursor));
1101 else if (m_blockCursor <= 0 && m_blockOffset)
1103 // we're at the left edge and offset
1104 int itemSize = GetItemSize(m_item);
1105 int block = GetRealBlock(m_item->item, m_channelCursor);
1107 if (block < m_blockOffset) /* current item begins before current offset, keep selected */
1109 if (itemSize > m_blocksPerPage) /* current item is longer than one page, scroll one page left */
1111 m_blockOffset < m_blocksPerPage ? block = 0 : block = m_blockOffset - m_blocksPerPage; // number blocks left < m_blocksPerPAge
1112 ScrollToBlockOffset(block);
1115 else /* current item is shorter than one page, scroll left to start of item */
1117 ScrollToBlockOffset(block); // -1?
1118 SetBlock(0); // align cursor to left edge
1121 else /* current item starts on this page's edge, select the previous item */
1123 m_item = GetPrevItem(m_channelCursor);
1124 itemSize = GetItemSize(m_item);
1126 if (itemSize > m_blocksPerPage) // previous item is longer than one page, scroll left to last page of item */
1128 ScrollToBlockOffset(m_blockOffset - m_blocksPerPage); // left one whole page
1129 //SetBlock(m_blocksPerPage -1 ); // helps navigation by setting cursor to far right edge
1130 SetBlock(0); // align cursor to left edge
1132 else /* previous item is shorter than one page, scroll left to start of item */
1134 ScrollToBlockOffset(m_blockOffset - itemSize);
1135 SetBlock(0); //should be zero
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 // at right edge, more than one page and not at maximum offset
1153 int itemSize = GetItemSize(m_item);
1154 int block = GetRealBlock(m_item->item, m_channelCursor);
1156 if (itemSize > m_blocksPerPage - m_blockCursor) // current item extends into next page, keep selected
1158 if (itemSize > m_blocksPerPage) // current item is longer than one page, scroll one page right
1160 if (m_blockOffset && m_blockOffset + m_blocksPerPage > m_blocks)
1161 block = m_blocks - m_blocksPerPage;
1163 block = m_blockOffset + m_blocksPerPage;
1165 ScrollToBlockOffset(block);
1169 else // current item is shorter than one page, scroll so end of item sits on end of grid
1171 ScrollToBlockOffset(block + itemSize - m_blocksPerPage);
1172 SetBlock(GetBlock(m_item->item, m_channelCursor)); /// change to middle block of item?
1175 else // current item finishes on this page's edge, select the next item
1177 m_item = GetNextItem(m_channelCursor);
1178 itemSize = GetItemSize(m_item);
1180 if (itemSize > m_blocksPerPage) // next item is longer than one page, scroll to first page of this item
1182 ScrollToBlockOffset(m_blockOffset + m_blocksPerPage);
1185 else // next item is shorter than one page, scroll so end of item sits on end of grid
1187 ScrollToBlockOffset(m_blockOffset + itemSize);
1188 SetBlock(m_blocksPerPage - itemSize); /// change to middle block of item?
1198 void CGUIEPGGridContainer::OnUp()
1200 bool wrapAround = m_actionUp.GetNavigation() == GetID() || !m_actionUp.HasActionsMeetingCondition();
1201 if (m_orientation == VERTICAL)
1203 if (!MoveChannel(true, wrapAround))
1204 CGUIControl::OnUp();
1208 if (!MoveProgrammes(true))
1209 CGUIControl::OnUp();
1213 void CGUIEPGGridContainer::OnDown()
1215 bool wrapAround = m_actionDown.GetNavigation() == GetID() || !m_actionDown.HasActionsMeetingCondition();
1216 if (m_orientation == VERTICAL)
1218 if (!MoveChannel(false, wrapAround))
1219 CGUIControl::OnDown();
1223 if (!MoveProgrammes(false))
1224 CGUIControl::OnDown();
1228 void CGUIEPGGridContainer::OnLeft()
1230 bool wrapAround = m_actionLeft.GetNavigation() == GetID() || !m_actionLeft.HasActionsMeetingCondition();
1231 if (m_orientation == VERTICAL)
1233 if (!MoveProgrammes(true))
1234 CGUIControl::OnLeft();
1238 if (!MoveChannel(true, wrapAround))
1239 CGUIControl::OnLeft();
1243 void CGUIEPGGridContainer::OnRight()
1245 bool wrapAround = m_actionRight.GetNavigation() == GetID() || !m_actionRight.HasActionsMeetingCondition();
1246 if (m_orientation == VERTICAL)
1248 if (!MoveProgrammes(false))
1249 CGUIControl::OnRight();
1253 if (!MoveChannel(false, wrapAround))
1254 CGUIControl::OnRight();
1258 void CGUIEPGGridContainer::SetChannel(const CStdString &channel)
1260 int iChannelIndex(-1);
1261 for (unsigned int iIndex = 0; iIndex < m_channelItems.size(); iIndex++)
1263 CStdString strPath = m_channelItems[iIndex]->GetProperty("path").asString(StringUtils::EmptyString);
1264 if (strPath == channel)
1266 iChannelIndex = iIndex;
1271 if (iChannelIndex >= 0)
1272 ScrollToChannelOffset(iChannelIndex);
1275 void CGUIEPGGridContainer::SetChannel(const CPVRChannel &channel)
1277 int iChannelIndex(-1);
1278 for (unsigned int iIndex = 0; iIndex < m_channelItems.size(); iIndex++)
1280 int iChannelId = (int)m_channelItems[iIndex]->GetProperty("channelid").asInteger(-1);
1281 if (iChannelId == channel.ChannelID())
1283 iChannelIndex = iIndex;
1288 if (iChannelIndex >= 0)
1289 ScrollToChannelOffset(iChannelIndex);
1292 void CGUIEPGGridContainer::SetChannel(int channel)
1294 if (m_blockCursor + m_blockOffset == 0 || m_blockOffset + m_blockCursor + GetItemSize(m_item) == m_blocks)
1296 m_item = GetItem(channel);
1299 SetBlock(GetBlock(m_item->item, channel));
1300 m_channelCursor = channel;
1305 /* basic checks failed, need to correctly identify nearest item */
1306 m_item = GetClosestItem(channel);
1309 m_channelCursor = channel;
1310 SetBlock(GetBlock(m_item->item, m_channelCursor));
1314 void CGUIEPGGridContainer::SetBlock(int block)
1318 else if (block > m_blocksPerPage - 1)
1319 m_blockCursor = m_blocksPerPage - 1;
1321 m_blockCursor = block;
1322 m_item = GetItem(m_channelCursor);
1325 CGUIListItemLayout *CGUIEPGGridContainer::GetFocusedLayout() const
1327 CGUIListItemPtr item = GetListItem(0);
1329 if (item.get()) return item->GetFocusedLayout();
1334 bool CGUIEPGGridContainer::SelectItemFromPoint(const CPoint &point, bool justGrid /* = false */)
1336 /* point has already had origin set to m_posX, m_posY */
1337 if (!m_focusedProgrammeLayout || !m_programmeLayout || (justGrid && point.x < 0))
1340 int channel = (int)(point.y / m_channelHeight);
1341 int block = (int)(point.x / m_blockSize);
1343 if (channel > m_channelsPerPage) channel = m_channelsPerPage - 1;
1344 if (channel >= m_channels) channel = m_channels - 1;
1345 if (channel < 0) channel = 0;
1346 if (block > m_blocksPerPage) block = m_blocksPerPage - 1;
1347 if (block < 0) block = 0;
1349 int channelIndex = channel + m_channelOffset;
1350 int blockIndex = block + m_blockOffset;
1352 // bail if out of range
1353 if (channelIndex >= m_channels || blockIndex >= m_blocks)
1355 // bail if block isn't occupied
1356 if (!m_gridIndex[channelIndex][blockIndex].item)
1359 SetChannel(channel);
1364 EVENT_RESULT CGUIEPGGridContainer::OnMouseEvent(const CPoint &point, const CMouseEvent &event)
1368 case ACTION_MOUSE_LEFT_CLICK:
1369 OnMouseClick(0, point);
1370 return EVENT_RESULT_HANDLED;
1371 case ACTION_MOUSE_RIGHT_CLICK:
1372 OnMouseClick(1, point);
1373 return EVENT_RESULT_HANDLED;
1374 case ACTION_MOUSE_DOUBLE_CLICK:
1375 OnMouseDoubleClick(0, point);
1376 return EVENT_RESULT_HANDLED;
1377 case ACTION_MOUSE_WHEEL_UP:
1378 OnMouseWheel(-1, point);
1379 return EVENT_RESULT_HANDLED;
1380 case ACTION_MOUSE_WHEEL_DOWN:
1381 OnMouseWheel(1, point);
1382 return EVENT_RESULT_HANDLED;
1383 case ACTION_GESTURE_BEGIN:
1385 // we want exclusive access
1386 CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, GetID(), GetParentID());
1387 SendWindowMessage(msg);
1388 return EVENT_RESULT_HANDLED;
1390 case ACTION_GESTURE_END:
1392 // we're done with exclusive access
1393 CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, 0, GetParentID());
1394 SendWindowMessage(msg);
1395 ScrollToChannelOffset(MathUtils::round_int(m_channelScrollOffset / m_channelLayout->Size(m_orientation)));
1396 ScrollToBlockOffset(MathUtils::round_int(m_programmeScrollOffset / m_blockSize));
1397 return EVENT_RESULT_HANDLED;
1399 case ACTION_GESTURE_PAN:
1401 if (m_orientation == VERTICAL)
1403 m_programmeScrollOffset -= event.m_offsetX;
1404 m_channelScrollOffset -= event.m_offsetY;
1408 m_channelScrollOffset -= event.m_offsetX;
1409 m_programmeScrollOffset -= event.m_offsetY;
1412 m_channelOffset = MathUtils::round_int(m_channelScrollOffset / m_channelLayout->Size(m_orientation));
1413 m_blockOffset = MathUtils::round_int(m_programmeScrollOffset / m_blockSize);
1415 return EVENT_RESULT_HANDLED;
1418 return EVENT_RESULT_UNHANDLED;
1422 bool CGUIEPGGridContainer::OnMouseOver(const CPoint &point)
1424 // select the item under the pointer
1425 SelectItemFromPoint(point - CPoint(m_gridPosX, m_posY + m_rulerHeight), false);
1426 return CGUIControl::OnMouseOver(point);
1429 bool CGUIEPGGridContainer::OnMouseClick(int dwButton, const CPoint &point)
1431 if (SelectItemFromPoint(point - CPoint(m_gridPosX, m_posY + m_rulerHeight)))
1432 { // send click message to window
1433 OnClick(ACTION_MOUSE_LEFT_CLICK + dwButton);
1440 bool CGUIEPGGridContainer::OnMouseDoubleClick(int dwButton, const CPoint &point)
1442 if (SelectItemFromPoint(point - CPoint(m_gridPosX, m_posY + m_rulerHeight)))
1443 { // send double click message to window
1444 OnClick(ACTION_MOUSE_DOUBLE_CLICK + dwButton);
1451 bool CGUIEPGGridContainer::OnClick(int actionID)
1455 if (actionID == ACTION_SELECT_ITEM || actionID == ACTION_MOUSE_LEFT_CLICK)
1457 // grab the currently focused subitem (if applicable)
1458 CGUIListItemLayout *focusedLayout = GetFocusedLayout();
1461 subItem = focusedLayout->GetFocusedItem();
1464 // Don't know what to do, so send to our parent window.
1465 CGUIMessage msg(GUI_MSG_CLICKED, GetID(), GetParentID(), actionID, subItem);
1466 return SendWindowMessage(msg);
1469 bool CGUIEPGGridContainer::OnMouseWheel(char wheel, const CPoint &point)
1471 ///doesn't work while an item is selected?
1472 ProgrammesScroll(-wheel);
1476 int CGUIEPGGridContainer::GetSelectedItem() const
1478 if (m_gridIndex.empty() ||
1479 m_epgItemsPtr.empty() ||
1480 m_channelCursor + m_channelOffset >= m_channels ||
1481 m_blockCursor + m_blockOffset >= m_blocks)
1484 CGUIListItemPtr currentItem = m_gridIndex[m_channelCursor + m_channelOffset][m_blockCursor + m_blockOffset].item;
1488 for (int i = 0; i < (int)m_programmeItems.size(); i++)
1490 if (currentItem == m_programmeItems[i])
1496 CGUIListItemPtr CGUIEPGGridContainer::GetListItem(int offset, unsigned int flag) const
1498 if (m_channelItems.empty())
1499 return CGUIListItemPtr();
1501 int item = m_channelCursor + m_channelOffset + offset;
1502 if (flag & INFOFLAG_LISTITEM_POSITION)
1503 item = (int)(m_channelScrollOffset / m_channelLayout->Size(VERTICAL));
1505 if (flag & INFOFLAG_LISTITEM_WRAP)
1507 item %= (int)m_channelItems.size();
1508 if (item < 0) item += m_channelItems.size();
1509 return m_channelItems[item];
1513 if (item >= 0 && item < (int)m_channelItems.size())
1514 return m_channelItems[item];
1516 return CGUIListItemPtr();
1519 CStdString CGUIEPGGridContainer::GetLabel(int info) const
1524 case CONTAINER_NUM_PAGES:
1525 label = StringUtils::Format("%u", (m_channels + m_channelsPerPage - 1) / m_channelsPerPage);
1527 case CONTAINER_CURRENT_PAGE:
1528 label = StringUtils::Format("%u", 1 + (m_channelCursor + m_channelOffset) / m_channelsPerPage );
1530 case CONTAINER_POSITION:
1531 label = StringUtils::Format("%i", 1 + m_channelCursor + m_channelOffset);
1533 case CONTAINER_NUM_ITEMS:
1534 label = StringUtils::Format("%u", m_channels);
1542 GridItemsPtr *CGUIEPGGridContainer::GetClosestItem(const int &channel)
1544 GridItemsPtr *closest = GetItem(channel);
1549 int block = GetBlock(closest->item, channel);
1550 int left; // num blocks to start of previous item
1551 int right; // num blocks to start of next item
1553 if (block == m_blockCursor)
1554 return closest; // item & m_item start together
1556 if (block + GetItemSize(closest) == m_blockCursor + GetItemSize(m_item))
1557 return closest; // closest item ends when current does
1559 if (block > m_blockCursor) // item starts after m_item
1561 left = m_blockCursor - GetBlock(closest->item, channel);
1562 right = block - m_blockCursor;
1566 left = m_blockCursor - block;
1567 right = GetBlock(GetNextItem(channel)->item, channel) - m_blockCursor;
1570 if (right <= SHORTGAP && right <= left && m_blockCursor + right < m_blocksPerPage)
1571 return &m_gridIndex[channel + m_channelOffset][m_blockCursor + right + m_blockOffset];
1573 return &m_gridIndex[channel + m_channelOffset][m_blockCursor - left + m_blockOffset];
1576 int CGUIEPGGridContainer::GetItemSize(GridItemsPtr *item)
1579 return (int) m_blockSize; /// stops it crashing
1581 return (int) ((m_orientation == VERTICAL ? item->width : item->height) / m_blockSize);
1584 int CGUIEPGGridContainer::GetBlock(const CGUIListItemPtr &item, const int &channel)
1589 return GetRealBlock(item, channel) - m_blockOffset;
1592 int CGUIEPGGridContainer::GetRealBlock(const CGUIListItemPtr &item, const int &channel)
1594 int channelIndex = channel + m_channelOffset;
1597 while (m_gridIndex[channelIndex][block].item != item && block < m_blocks)
1603 GridItemsPtr *CGUIEPGGridContainer::GetNextItem(const int &channel)
1605 int channelIndex = channel + m_channelOffset;
1606 int blockIndex = m_blockCursor + m_blockOffset;
1607 if (channelIndex >= m_channels || blockIndex >= m_blocks)
1610 int i = m_blockCursor;
1612 while (i < m_blocksPerPage && m_gridIndex[channelIndex][i + m_blockOffset].item == m_gridIndex[channelIndex][blockIndex].item)
1615 return &m_gridIndex[channelIndex][i + m_blockOffset];
1618 GridItemsPtr *CGUIEPGGridContainer::GetPrevItem(const int &channel)
1620 int channelIndex = channel + m_channelOffset;
1621 int blockIndex = m_blockCursor + m_blockOffset;
1622 if (channelIndex >= m_channels || blockIndex >= m_blocks)
1625 int i = m_blockCursor;
1627 while (i > 0 && m_gridIndex[channelIndex][i + m_blockOffset].item == m_gridIndex[channelIndex][blockIndex].item)
1630 return &m_gridIndex[channelIndex][i + m_blockOffset];
1633 GridItemsPtr *CGUIEPGGridContainer::GetItem(const int &channel)
1635 int channelIndex = channel + m_channelOffset;
1636 int blockIndex = m_blockCursor + m_blockOffset;
1637 if (channelIndex >= m_channels || blockIndex >= m_blocks)
1640 return &m_gridIndex[channelIndex][blockIndex];
1643 void CGUIEPGGridContainer::SetFocus(bool bOnOff)
1645 if (bOnOff != HasFocus())
1648 /*m_lastItem.reset();
1649 m_lastChannel.reset();*/
1652 CGUIControl::SetFocus(bOnOff);
1655 void CGUIEPGGridContainer::DoRender()
1657 CGUIControl::DoRender();
1661 void CGUIEPGGridContainer::ScrollToChannelOffset(int offset)
1663 float size = m_programmeLayout->Size(VERTICAL);
1664 int range = m_channelsPerPage / 4;
1666 if (range <= 0) range = 1;
1668 if (offset * size < m_channelScrollOffset && m_channelScrollOffset - offset * size > size * range)
1669 { // scrolling up, and we're jumping more than 0.5 of a screen
1670 m_channelScrollOffset = (offset + range) * size;
1673 if (offset * size > m_channelScrollOffset && offset * size - m_channelScrollOffset > size * range)
1674 { // scrolling down, and we're jumping more than 0.5 of a screen
1675 m_channelScrollOffset = (offset - range) * size;
1678 m_channelScrollSpeed = (offset * size - m_channelScrollOffset) / m_scrollTime;
1680 m_channelOffset = offset;
1683 void CGUIEPGGridContainer::ScrollToBlockOffset(int offset)
1685 // make sure offset is in valid range
1686 offset = std::max(0, std::min(offset, m_blocks - m_blocksPerPage));
1688 float size = m_blockSize;
1689 int range = m_blocksPerPage / 1;
1691 if (range <= 0) range = 1;
1693 if (offset * size < m_programmeScrollOffset && m_programmeScrollOffset - offset * size > size * range)
1694 { // scrolling left, and we're jumping more than 0.5 of a screen
1695 m_programmeScrollOffset = (offset + range) * size;
1698 if (offset * size > m_programmeScrollOffset && offset * size - m_programmeScrollOffset > size * range)
1699 { // scrolling right, and we're jumping more than 0.5 of a screen
1700 m_programmeScrollOffset = (offset - range) * size;
1703 m_programmeScrollSpeed = (offset * size - m_programmeScrollOffset) / m_scrollTime;
1705 m_blockOffset = offset;
1708 void CGUIEPGGridContainer::ValidateOffset()
1710 if (!m_programmeLayout)
1713 if (m_channelOffset > m_channels - m_channelsPerPage || m_channelScrollOffset > (m_channels - m_channelsPerPage) * m_channelHeight)
1715 m_channelOffset = m_channels - m_channelsPerPage;
1716 m_channelScrollOffset = m_channelOffset * m_channelHeight;
1719 if (m_channelOffset < 0 || m_channelScrollOffset < 0)
1721 m_channelOffset = 0;
1722 m_channelScrollOffset = 0;
1725 if (m_blockOffset > m_blocks - m_blocksPerPage || m_programmeScrollOffset > (m_blocks - m_blocksPerPage) * m_blockSize)
1727 m_blockOffset = m_blocks - m_blocksPerPage;
1728 m_programmeScrollOffset = m_blockOffset * m_blockSize;
1731 if (m_blockOffset < 0 || m_programmeScrollOffset < 0)
1734 m_programmeScrollOffset = 0;
1738 void CGUIEPGGridContainer::LoadLayout(TiXmlElement *layout)
1740 /* layouts for the channel column */
1741 TiXmlElement *itemElement = layout->FirstChildElement("channellayout");
1743 { // we have a new item layout
1744 CGUIListItemLayout itemLayout;
1745 itemLayout.LoadLayout(itemElement, GetParentID(), false);
1746 m_channelLayouts.push_back(itemLayout);
1747 itemElement = itemElement->NextSiblingElement("channellayout");
1749 itemElement = layout->FirstChildElement("focusedchannellayout");
1751 { // we have a new item layout
1752 CGUIListItemLayout itemLayout;
1753 itemLayout.LoadLayout(itemElement, GetParentID(), true);
1754 m_focusedChannelLayouts.push_back(itemLayout);
1755 itemElement = itemElement->NextSiblingElement("focusedchannellayout");
1758 /* layouts for the grid items */
1759 itemElement = layout->FirstChildElement("focusedlayout");
1762 CGUIListItemLayout itemLayout;
1763 itemLayout.LoadLayout(itemElement, GetParentID(), true);
1764 m_focusedProgrammeLayouts.push_back(itemLayout);
1765 itemElement = itemElement->NextSiblingElement("focusedlayout");
1767 itemElement = layout->FirstChildElement("itemlayout");
1770 CGUIListItemLayout itemLayout;
1771 itemLayout.LoadLayout(itemElement, GetParentID(), false);
1772 m_programmeLayouts.push_back(itemLayout);
1773 itemElement = itemElement->NextSiblingElement("itemlayout");
1776 /* layout for the timeline above the grid */
1777 itemElement = layout->FirstChildElement("rulerlayout");
1780 CGUIListItemLayout itemLayout;
1781 itemLayout.LoadLayout(itemElement, GetParentID(), false);
1782 m_rulerLayouts.push_back(itemLayout);
1783 itemElement = itemElement->NextSiblingElement("rulerlayout");
1787 void CGUIEPGGridContainer::UpdateLayout(bool updateAllItems)
1789 // if container is invalid, either new data has arrived, or m_blockSize has changed
1790 // need to run UpdateItems rather than CalculateLayout?
1792 { // free memory of items
1793 for (iItems it = m_channelItems.begin(); it != m_channelItems.end(); it++)
1794 (*it)->FreeMemory();
1795 for (iItems it = m_rulerItems.begin(); it != m_rulerItems.end(); it++)
1796 (*it)->FreeMemory();
1797 for (iItems it = m_programmeItems.begin(); it != m_programmeItems.end(); it++)
1798 (*it)->FreeMemory();
1801 // and recalculate the layout
1805 CStdString CGUIEPGGridContainer::GetDescription() const
1807 CStdString strLabel;
1808 int item = GetSelectedItem();
1809 if (item >= 0 && item < (int)m_programmeItems.size())
1811 CGUIListItemPtr pItem = m_programmeItems[item];
1812 strLabel = pItem->GetLabel();
1817 void CGUIEPGGridContainer::ClearGridIndex(void)
1819 for (unsigned int i = 0; i < m_gridIndex.size(); i++)
1821 for (int block = 0; block < m_blocks; block++)
1823 if (m_gridIndex[i][block].item)
1824 m_gridIndex[i][block].item.get()->ClearProperties();
1826 m_gridIndex[i].clear();
1828 m_gridIndex.clear();
1831 void CGUIEPGGridContainer::Reset()
1836 m_channelItems.clear();
1837 m_programmeItems.clear();
1838 m_rulerItems.clear();
1839 m_epgItemsPtr.clear();
1842 m_lastChannel = NULL;
1845 void CGUIEPGGridContainer::GoToBegin()
1847 ScrollToBlockOffset(0);
1851 void CGUIEPGGridContainer::GoToEnd()
1853 int blocksEnd = 0; // the end block of the last epg element for the selected channel
1854 int blocksStart = 0; // the start block of the last epg element for the selected channel
1855 int blockOffset = 0; // the block offset to scroll to
1856 for (int blockIndex = m_blocks; blockIndex >= 0 && (!blocksEnd || !blocksStart); blockIndex--)
1858 if (!blocksEnd && m_gridIndex[m_channelCursor + m_channelOffset][blockIndex].item != NULL)
1859 blocksEnd = blockIndex;
1860 if (blocksEnd && m_gridIndex[m_channelCursor + m_channelOffset][blocksEnd].item !=
1861 m_gridIndex[m_channelCursor + m_channelOffset][blockIndex].item)
1862 blocksStart = blockIndex + 1;
1864 if (blocksEnd - blocksStart > m_blocksPerPage)
1865 blockOffset = blocksStart;
1866 else if (blocksEnd > m_blocksPerPage)
1867 blockOffset = blocksEnd - m_blocksPerPage;
1869 ScrollToBlockOffset(blockOffset); // scroll to the start point of the last epg element
1870 SetBlock(m_blocksPerPage - 1); // select the last epg element
1873 void CGUIEPGGridContainer::SetStartEnd(CDateTime start, CDateTime end)
1875 m_gridStart = CDateTime(start.GetYear(), start.GetMonth(), start.GetDay(), start.GetHour(), start.GetMinute() >= 30 ? 30 : 0, 0);
1876 m_gridEnd = CDateTime(end.GetYear(), end.GetMonth(), end.GetDay(), end.GetHour(), end.GetMinute() >= 30 ? 30 : 0, 0);
1878 CLog::Log(LOGDEBUG, "CGUIEPGGridContainer - %s - start=%s end=%s",
1879 __FUNCTION__, m_gridStart.GetAsLocalizedDateTime(false, true).c_str(), m_gridEnd.GetAsLocalizedDateTime(false, true).c_str());
1882 void CGUIEPGGridContainer::CalculateLayout()
1884 CGUIListItemLayout *oldFocusedChannelLayout = m_focusedChannelLayout;
1885 CGUIListItemLayout *oldChannelLayout = m_channelLayout;
1886 CGUIListItemLayout *oldFocusedProgrammeLayout = m_focusedProgrammeLayout;
1887 CGUIListItemLayout *oldProgrammeLayout = m_programmeLayout;
1888 CGUIListItemLayout *oldRulerLayout = m_rulerLayout;
1889 GetCurrentLayouts();
1891 if (!m_focusedProgrammeLayout || !m_programmeLayout || !m_focusedChannelLayout || !m_channelLayout || !m_rulerLayout)
1894 if (oldChannelLayout == m_channelLayout && oldFocusedChannelLayout == m_focusedChannelLayout &&
1895 oldProgrammeLayout == m_programmeLayout && oldFocusedProgrammeLayout == m_focusedProgrammeLayout &&
1896 oldRulerLayout == m_rulerLayout)
1897 return; // nothing has changed, so don't update stuff
1899 m_channelHeight = m_channelLayout->Size(VERTICAL);
1900 m_channelWidth = m_channelLayout->Size(HORIZONTAL);
1901 if (m_orientation == VERTICAL)
1903 m_rulerHeight = m_rulerLayout->Size(VERTICAL);
1904 m_gridPosX = m_posX + m_channelWidth;
1905 m_gridPosY = m_posY + m_rulerHeight;
1906 m_gridWidth = m_width - m_channelWidth;
1907 m_gridHeight = m_height - m_rulerHeight;
1908 m_blockSize = m_gridWidth / m_blocksPerPage;
1909 m_rulerWidth = m_rulerUnit * m_blockSize;
1910 m_channelPosX = m_posX;
1911 m_channelPosY = m_posY + m_rulerHeight;
1912 m_rulerPosX = m_posX + m_channelWidth;
1913 m_rulerPosY = m_posY;
1914 m_channelsPerPage = (int)(m_gridHeight / m_channelHeight);
1915 m_ProgrammesPerPage = (int)(m_gridWidth / m_blockSize) + 1;
1919 m_rulerWidth = m_rulerLayout->Size(HORIZONTAL);
1920 m_gridPosX = m_posX + m_rulerWidth;
1921 m_gridPosY = m_posY + m_channelHeight;
1922 m_gridWidth = m_width - m_rulerWidth;
1923 m_gridHeight = m_height - m_channelHeight;
1924 m_blockSize = m_gridHeight / m_blocksPerPage;
1925 m_rulerHeight = m_rulerUnit * m_blockSize;
1926 m_channelPosX = m_posX + m_rulerWidth;
1927 m_channelPosY = m_posY;
1928 m_rulerPosX = m_posX;
1929 m_rulerPosY = m_posY + m_channelHeight;
1930 m_channelsPerPage = (int)(m_gridWidth / m_channelWidth);
1931 m_ProgrammesPerPage = (int)(m_gridHeight / m_blockSize) + 1;
1934 // ensure that the scroll offsets are a multiple of our sizes
1935 m_channelScrollOffset = m_channelOffset * m_programmeLayout->Size(m_orientation);
1936 m_programmeScrollOffset = m_blockOffset * m_blockSize;
1939 void CGUIEPGGridContainer::UpdateScrollOffset(unsigned int currentTime)
1941 if (!m_programmeLayout)
1943 m_channelScrollOffset += m_channelScrollSpeed * (currentTime - m_channelScrollLastTime);
1944 if ((m_channelScrollSpeed < 0 && m_channelScrollOffset < m_channelOffset * m_programmeLayout->Size(m_orientation)) ||
1945 (m_channelScrollSpeed > 0 && m_channelScrollOffset > m_channelOffset * m_programmeLayout->Size(m_orientation)))
1947 m_channelScrollOffset = m_channelOffset * m_programmeLayout->Size(m_orientation);
1948 m_channelScrollSpeed = 0;
1950 m_channelScrollLastTime = currentTime;
1952 m_programmeScrollOffset += m_programmeScrollSpeed * (currentTime - m_programmeScrollLastTime);
1953 if ((m_programmeScrollSpeed < 0 && m_programmeScrollOffset < m_blockOffset * m_blockSize) ||
1954 (m_programmeScrollSpeed > 0 && m_programmeScrollOffset > m_blockOffset * m_blockSize))
1956 m_programmeScrollOffset = m_blockOffset * m_blockSize;
1957 m_programmeScrollSpeed = 0;
1959 m_programmeScrollLastTime = currentTime;
1962 void CGUIEPGGridContainer::GetCurrentLayouts()
1964 m_channelLayout = NULL;
1965 for (unsigned int i = 0; i < m_channelLayouts.size(); i++)
1967 if (m_channelLayouts[i].CheckCondition())
1969 m_channelLayout = &m_channelLayouts[i];
1973 if (!m_channelLayout && !m_channelLayouts.empty())
1974 m_channelLayout = &m_channelLayouts[0]; // failsafe
1976 m_focusedChannelLayout = NULL;
1977 for (unsigned int i = 0; i < m_focusedChannelLayouts.size(); i++)
1979 if (m_focusedChannelLayouts[i].CheckCondition())
1981 m_focusedChannelLayout = &m_focusedChannelLayouts[i];
1985 if (!m_focusedChannelLayout && !m_focusedChannelLayouts.empty())
1986 m_focusedChannelLayout = &m_focusedChannelLayouts[0]; // failsafe
1988 m_programmeLayout = NULL;
1989 for (unsigned int i = 0; i < m_programmeLayouts.size(); i++)
1991 if (m_programmeLayouts[i].CheckCondition())
1993 m_programmeLayout = &m_programmeLayouts[i];
1997 if (!m_programmeLayout && !m_programmeLayouts.empty())
1998 m_programmeLayout = &m_programmeLayouts[0]; // failsafe
2000 m_focusedProgrammeLayout = NULL;
2001 for (unsigned int i = 0; i < m_focusedProgrammeLayouts.size(); i++)
2003 if (m_focusedProgrammeLayouts[i].CheckCondition())
2005 m_focusedProgrammeLayout = &m_focusedProgrammeLayouts[i];
2009 if (!m_focusedProgrammeLayout && !m_focusedProgrammeLayouts.empty())
2010 m_focusedProgrammeLayout = &m_focusedProgrammeLayouts[0]; // failsafe
2012 m_rulerLayout = NULL;
2013 for (unsigned int i = 0; i < m_rulerLayouts.size(); i++)
2015 if (m_rulerLayouts[i].CheckCondition())
2017 m_rulerLayout = &m_rulerLayouts[i];
2021 if (!m_rulerLayout && !m_rulerLayouts.empty())
2022 m_rulerLayout = &m_rulerLayouts[0]; // failsafe
2025 int CGUIEPGGridContainer::CorrectOffset(int offset, int cursor) const
2027 return offset + cursor;
2030 void CGUIEPGGridContainer::SetRenderOffset(const CPoint &offset)
2032 m_renderOffset = offset;
2035 void CGUIEPGGridContainer::FreeChannelMemory(int keepStart, int keepEnd)
2037 if (keepStart < keepEnd)
2038 { // remove before keepStart and after keepEnd
2039 for (int i = 0; i < keepStart && i < (int)m_channelItems.size(); ++i)
2040 m_channelItems[i]->FreeMemory();
2041 for (int i = keepEnd + 1; i < (int)m_channelItems.size(); ++i)
2042 m_channelItems[i]->FreeMemory();
2046 for (int i = keepEnd + 1; i < keepStart && i < (int)m_channelItems.size(); ++i)
2047 m_channelItems[i]->FreeMemory();
2051 void CGUIEPGGridContainer::FreeProgrammeMemory(int channel, int keepStart, int keepEnd)
2053 if (keepStart < keepEnd)
2054 { // remove before keepStart and after keepEnd
2055 if (keepStart > 0 && keepStart < m_blocks)
2057 // if item exist and block is not part of visible item
2058 CGUIListItemPtr last = m_gridIndex[channel][keepStart].item;
2059 for (int i = keepStart - 1 ; i > 0 ; i--)
2061 if (m_gridIndex[channel][i].item && m_gridIndex[channel][i].item != last)
2063 m_gridIndex[channel][i].item->FreeMemory();
2064 // FreeMemory() is smart enough to not cause any problems when called multiple times on same item
2065 // but we can make use of condition needed to not call FreeMemory() on item that is partially visible
2066 // to avoid calling FreeMemory() multiple times on item that ocupy few blocks in a row
2067 last = m_gridIndex[channel][i].item;
2072 if (keepEnd > 0 && keepEnd < m_blocks)
2074 CGUIListItemPtr last = m_gridIndex[channel][keepEnd].item;
2075 for (int i = keepEnd + 1 ; i < m_blocks ; i++)
2077 // if item exist and block is not part of visible item
2078 if (m_gridIndex[channel][i].item && m_gridIndex[channel][i].item != last)
2080 m_gridIndex[channel][i].item->FreeMemory();
2081 // FreeMemory() is smart enough to not cause any problems when called multiple times on same item
2082 // but we can make use of condition needed to not call FreeMemory() on item that is partially visible
2083 // to avoid calling FreeMemory() multiple times on item that ocupy few blocks in a row
2084 last = m_gridIndex[channel][i].item;
2091 void CGUIEPGGridContainer::FreeRulerMemory(int keepStart, int keepEnd)
2093 if (keepStart < keepEnd)
2094 { // remove before keepStart and after keepEnd
2095 for (int i = 1; i < keepStart && i < (int)m_rulerItems.size(); ++i)
2096 m_rulerItems[i]->FreeMemory();
2097 for (int i = keepEnd + 1; i < (int)m_rulerItems.size(); ++i)
2098 m_rulerItems[i]->FreeMemory();
2102 for (int i = keepEnd + 1; i < keepStart && i < (int)m_rulerItems.size(); ++i)
2106 m_rulerItems[i]->FreeMemory();
2111 void CGUIEPGGridContainer::GetChannelCacheOffsets(int &cacheBefore, int &cacheAfter)
2113 if (m_channelScrollSpeed > 0)
2116 cacheAfter = m_cacheChannelItems;
2118 else if (m_channelScrollSpeed < 0)
2120 cacheBefore = m_cacheChannelItems;
2125 cacheBefore = m_cacheChannelItems / 2;
2126 cacheAfter = m_cacheChannelItems / 2;
2130 void CGUIEPGGridContainer::GetProgrammeCacheOffsets(int &cacheBefore, int &cacheAfter)
2132 if (m_programmeScrollSpeed > 0)
2135 cacheAfter = m_cacheProgrammeItems;
2137 else if (m_programmeScrollSpeed < 0)
2139 cacheBefore = m_cacheProgrammeItems;
2144 cacheBefore = m_cacheProgrammeItems / 2;
2145 cacheAfter = m_cacheProgrammeItems / 2;