commit bb33339f3f0deecf6002f0edb5961509466f2e67
Author: Jim Stichnoth <jstichnoth@mythtv.org>
Date:   Sun Nov 3 07:09:19 2013 -0800

    Improve the performance of Live TV embedded in the Program Guide.
    
    Most of the Program Guide's DB and backend queries are moved out of
    the UI loop into a background helper thread, to minimize the chance
    of the embedded Live TV window stuttering.
    
    More work can and should be done to pre-cache scheduler data and
    channel tuning status, and to have the backend invalidate/update it
    when there are changes.
    
    The background computation introduces some guide navigation UI
    oddities, where the expensive-to-compute elements (the list of
    channels) update on the screen slower than other elements.
    
    There is still some slight stuttering on a single-core ION frontend,
    but most frontends should see a vast or complete reduction in Guide
    navigation stuttering.
    
    Refs #11020
    
    (cherry picked from commit 7f2140ce062ade5a5523565b469ff58ada9c0d0d)
    
    Conflicts:
    	mythtv/programs/mythfrontend/guidegrid.cpp

diff --git a/mythtv/programs/mythfrontend/guidegrid.cpp b/mythtv/programs/mythfrontend/guidegrid.cpp
index 5a5f52c..0a3e345 100644
--- a/mythtv/programs/mythfrontend/guidegrid.cpp
+++ b/mythtv/programs/mythfrontend/guidegrid.cpp
@@ -164,6 +164,238 @@ bool JumpToChannel::Update(void)
     }
 }
 
+// GuideStatus is used for transferring the relevant read-only data
+// from GuideGrid to the GuideUpdateProgramRow constructor.
+class GuideStatus
+{
+public:
+    GuideStatus(unsigned int firstRow, unsigned int numRows,
+                const QVector<int> &channums,
+                int channelInfos_size,
+                const MythRect &gg_programRect,
+                int gg_channelCount,
+                const QDateTime &currentStartTime,
+                const QDateTime &currentEndTime,
+                uint currentStartChannel,
+                int currentRow, int currentCol,
+                int channelCount, int timeCount,
+                bool verticalLayout,
+                const QDateTime &firstTime, const QDateTime &lastTime)
+        : m_firstRow(firstRow), m_numRows(numRows), m_channums(channums),
+          m_channelInfos_size(channelInfos_size),
+          m_gg_programRect(gg_programRect), m_gg_channelCount(gg_channelCount),
+          m_currentStartTime(currentStartTime),
+          m_currentEndTime(currentEndTime),
+          m_currentStartChannel(currentStartChannel), m_currentRow(currentRow),
+          m_currentCol(currentCol), m_channelCount(channelCount),
+          m_timeCount(timeCount), m_verticalLayout(verticalLayout),
+          m_firstTime(firstTime), m_lastTime(lastTime) {}
+    const unsigned int m_firstRow, m_numRows;
+    const QVector<int> m_channums;
+    const int m_channelInfos_size;
+    const MythRect m_gg_programRect;
+    const int m_gg_channelCount;
+    const QDateTime m_currentStartTime, m_currentEndTime;
+    const uint m_currentStartChannel;
+    const int m_currentRow, m_currentCol;
+    const int m_channelCount, m_timeCount;
+    const bool m_verticalLayout;
+    const QDateTime m_firstTime, m_lastTime;
+};
+
+class GuideUpdaterBase
+{
+public:
+    GuideUpdaterBase(GuideGrid *guide) : m_guide(guide) {}
+
+    // Execute the initial non-UI part (in a separate thread).  Return
+    // true if ExecuteUI() should be run later, or false if the work
+    // is no longer relevant (e.g., the UI elements have scrolled
+    // offscreen by now).
+    virtual bool ExecuteNonUI(void) = 0;
+    // Execute the UI part in the UI thread.
+    virtual void ExecuteUI(void) = 0;
+
+protected:
+    GuideGrid *m_guide;
+};
+
+class GuideUpdateProgramRow : public GuideUpdaterBase
+{
+public:
+    GuideUpdateProgramRow(GuideGrid *guide, const GuideStatus &gs,
+                          const QVector<ProgramList*> &proglists)
+        : GuideUpdaterBase(guide),
+          m_firstRow(gs.m_firstRow),
+          m_numRows(gs.m_numRows),
+          m_channums(gs.m_channums),
+          m_channelInfos_size(gs.m_channelInfos_size),
+          m_gg_programRect(gs.m_gg_programRect),
+          m_gg_channelCount(gs.m_gg_channelCount),
+          m_currentStartTime(gs.m_currentStartTime),
+          m_currentEndTime(gs.m_currentEndTime),
+          m_currentStartChannel(gs.m_currentStartChannel),
+          m_currentRow(gs.m_currentRow),
+          m_currentCol(gs.m_currentCol),
+          m_channelCount(gs.m_channelCount),
+          m_timeCount(gs.m_timeCount),
+          m_verticalLayout(gs.m_verticalLayout),
+          m_firstTime(gs.m_firstTime),
+          m_lastTime(gs.m_lastTime),
+          m_proglists(proglists)
+    {
+        for (unsigned int i = m_firstRow;
+             i < m_firstRow + m_numRows; ++i)
+            for (int j = 0; j < MAX_DISPLAY_TIMES; ++j)
+                m_programInfos[i][j] = NULL;
+    }
+    virtual bool ExecuteNonUI(void)
+    {
+        // Don't bother to do any work if the starting coordinates of
+        // the guide have changed while the thread was waiting to
+        // start.
+        if (m_currentStartChannel != m_guide->GetCurrentStartChannel() ||
+            m_currentStartTime != m_guide->GetCurrentStartTime())
+        {
+            return false;
+        }
+
+        for (unsigned int i = 0; i < m_numRows; ++i)
+        {
+            unsigned int row = i + m_firstRow;
+            if (!m_proglists[i])
+                m_proglists[i] =
+                    m_guide->getProgramListFromProgram(m_channums[i]);
+            fillProgramRowInfosWith(row, m_channums[i],
+                                    m_currentStartTime,
+                                    m_proglists[i]);
+        }
+        return true;
+    }
+    virtual void ExecuteUI(void)
+    {
+        m_guide->updateProgramsUI(m_firstRow, m_numRows,
+                                  m_progPast, m_proglists,
+                                  m_programInfos, m_result);
+    }
+
+private:
+    void fillProgramRowInfosWith(int row, int chanNum, QDateTime start,
+                                 ProgramList *proglist);
+
+    const unsigned int m_firstRow;
+    const unsigned int m_numRows;
+    const QVector<int> m_channums;
+    const int m_channelInfos_size;
+    const MythRect m_gg_programRect;
+    const int m_gg_channelCount;
+    const QDateTime m_currentStartTime;
+    const QDateTime m_currentEndTime;
+    const uint m_currentStartChannel;
+    const int m_currentRow;
+    const int m_currentCol;
+    const int m_channelCount;
+    const int m_timeCount;
+    const bool m_verticalLayout;
+    const QDateTime m_firstTime;
+    const QDateTime m_lastTime;
+
+    QVector<ProgramList*> m_proglists;
+    ProgInfoGuideArray m_programInfos;
+    int m_progPast;
+    //QVector<GuideUIElement> m_result;
+    QLinkedList<GuideUIElement> m_result;
+};
+
+class GuideUpdateChannels : public GuideUpdaterBase
+{
+public:
+    GuideUpdateChannels(GuideGrid *guide, uint startChan)
+        : GuideUpdaterBase(guide), m_currentStartChannel(startChan) {}
+    virtual bool ExecuteNonUI(void)
+    {
+        if (m_currentStartChannel != m_guide->GetCurrentStartChannel())
+            return false;
+        m_guide->updateChannelsNonUI(m_chinfos, m_unavailables);
+        return true;
+    }
+    virtual void ExecuteUI(void)
+    {
+        m_guide->updateChannelsUI(m_chinfos, m_unavailables);
+    }
+    uint m_currentStartChannel;
+    QVector<ChannelInfo *> m_chinfos;
+    QVector<bool> m_unavailables;
+};
+
+class UpdateGuideEvent : public QEvent
+{
+public:
+    UpdateGuideEvent(GuideUpdaterBase *updater) :
+        QEvent(kEventType), m_updater(updater) {}
+    GuideUpdaterBase *m_updater;
+    static Type kEventType;
+};
+QEvent::Type UpdateGuideEvent::kEventType =
+    (QEvent::Type) QEvent::registerEventType();
+
+class GuideHelper : public QRunnable
+{
+public:
+    GuideHelper(GuideGrid *guide, GuideUpdaterBase *updater)
+        : m_guide(guide), m_updater(updater)
+    {
+        QMutexLocker locker(&s_lock);
+        ++s_loading[m_guide];
+    }
+    virtual void run(void)
+    {
+        QThread::currentThread()->setPriority(QThread::IdlePriority);
+        if (m_updater)
+        {
+            if (m_updater->ExecuteNonUI())
+                QCoreApplication::postEvent(m_guide,
+                                            new UpdateGuideEvent(m_updater));
+            else
+            {
+                delete m_updater;
+                m_updater = NULL;
+            }
+        }
+
+        QMutexLocker locker(&s_lock);
+        --s_loading[m_guide];
+        if (!s_loading[m_guide])
+            s_wait.wakeAll();
+    }
+    static bool IsLoading(GuideGrid *guide)
+    {
+        QMutexLocker locker(&s_lock);
+        return s_loading[guide];
+    }
+    static void Wait(GuideGrid *guide)
+    {
+        QMutexLocker locker(&s_lock);
+        if (!s_loading[guide])
+            return;
+        while (s_wait.wait(&s_lock))
+        {
+            if (!s_loading[guide])
+                return;
+        }
+    }
+private:
+    GuideGrid *m_guide;
+    GuideUpdaterBase *m_updater;
+
+    static QMutex                s_lock;
+    static QWaitCondition        s_wait;
+    static QMap<GuideGrid*,uint> s_loading;
+};
+QMutex                GuideHelper::s_lock;
+QWaitCondition        GuideHelper::s_wait;
+QMap<GuideGrid*,uint> GuideHelper::s_loading;
+
 void GuideGrid::RunProgramGuide(uint chanid, const QString &channum,
                                 const QDateTime startTime,
                                 TV *player, bool embedVideo,
@@ -241,6 +473,7 @@ GuideGrid::GuideGrid(MythScreenStack *parent,
            m_previewVideoRefreshTimer(new QTimer(this)),
            m_channelOrdering(gCoreContext->GetSetting("ChannelOrdering", "channum")),
            m_updateTimer(NULL),
+           m_threadPool("GuideGridHelperPool"),
            m_changrpid(changrpid),
            m_changrplist(ChannelGroup::GetChannelGroups(false)),
            m_jumpToChannelLock(QMutex::Recursive),
@@ -274,6 +507,7 @@ GuideGrid::GuideGrid(MythScreenStack *parent,
     int secsoffset = -((m_originalStartTime.time().minute() % 30) * 60 +
                         m_originalStartTime.time().second());
     m_currentStartTime = m_originalStartTime.addSecs(secsoffset);
+    m_threadPool.setMaxThreadCount(1);
 }
 
 bool GuideGrid::Create()
@@ -356,7 +590,6 @@ void GuideGrid::Init(void)
     updateChannels();
 
     fillProgramInfos(true);
-    updateInfo();
 
     m_updateTimer = new QTimer(this);
     connect(m_updateTimer, SIGNAL(timeout()), SLOT(updateTimeout()) );
@@ -374,6 +607,8 @@ void GuideGrid::Init(void)
 
 GuideGrid::~GuideGrid()
 {
+    GuideHelper::Wait(this);
+
     gCoreContext->removeListener(this);
 
     while (!m_programs.empty())
@@ -728,6 +963,17 @@ ProgramList GuideGrid::GetProgramList(uint chanid) const
     return proglist;
 }
 
+static ProgramList *CopyProglist(ProgramList *proglist)
+{
+    if (!proglist)
+        return NULL;
+    ProgramList *result = new ProgramList();
+    for (ProgramList::iterator pi = proglist->begin();
+         pi != proglist->end(); ++pi)
+        result->push_back(new ProgramInfo(**pi));
+    return result;
+}
+
 uint GuideGrid::GetAlternateChannelIndex(
     uint chan_idx, bool with_same_channum) const
 {
@@ -1075,12 +1321,7 @@ void GuideGrid::fillTimeInfos()
 
 void GuideGrid::fillProgramInfos(bool useExistingData)
 {
-    m_guideGrid->ResetData();
-
-    for (int y = 0; y < m_channelCount; ++y)
-    {
-        fillProgramRowInfos(y, useExistingData);
-    }
+    fillProgramRowInfos(-1, useExistingData);
 }
 
 ProgramList *GuideGrid::getProgramListFromProgram(int chanNum)
@@ -1106,40 +1347,80 @@ ProgramList *GuideGrid::getProgramListFromProgram(int chanNum)
     return proglist;
 }
 
-void GuideGrid::fillProgramRowInfos(unsigned int row, bool useExistingData)
+void GuideGrid::fillProgramRowInfos(int firstRow, bool useExistingData)
 {
-    m_guideGrid->ResetRow(row);
-
-    // never divide by zero..
-    if (!m_guideGrid->getChannelCount() || !m_timeCount)
-        return;
-
-    for (int x = 0; x < m_timeCount; ++x)
+    bool allRows = false;
+    unsigned int numRows = 1;
+    if (firstRow < 0)
     {
-        m_programInfos[row][x] = NULL;
+        firstRow = 0;
+        allRows = true;
+        numRows = min((unsigned int)m_channelInfos.size(),
+                      (unsigned int)m_guideGrid->getChannelCount());
     }
+    QVector<int> chanNums;
+    QVector<ProgramList*> proglists;
 
-    if (m_channelInfos.empty())
-        return;
+    for (unsigned int i = 0; i < numRows; ++i)
+    {
+        unsigned int row = i + firstRow;
+        // never divide by zero..
+        if (!m_guideGrid->getChannelCount() || !m_timeCount)
+            return;
 
-    int chanNum = row + m_currentStartChannel;
-    if (chanNum >= (int) m_channelInfos.size())
-        chanNum -= (int) m_channelInfos.size();
-    if (chanNum >= (int) m_channelInfos.size())
-        return;
+        for (int x = 0; x < m_timeCount; ++x)
+        {
+            m_programInfos[row][x] = NULL;
+        }
 
-    if (chanNum < 0)
-        chanNum = 0;
+        if (m_channelInfos.empty())
+            return;
+
+        int chanNum = row + m_currentStartChannel;
+        if (chanNum >= (int) m_channelInfos.size())
+            chanNum -= (int) m_channelInfos.size();
+        if (chanNum >= (int) m_channelInfos.size())
+            return;
+
+        if (chanNum < 0)
+            chanNum = 0;
 
-    if (!useExistingData)
+        ProgramList *proglist = NULL;
+        if (useExistingData)
+            proglist = CopyProglist(m_programs[row]);
+        chanNums.push_back(chanNum);
+        proglists.push_back(proglist);
+    }
+    if (allRows)
     {
-        delete m_programs[row];
-        m_programs[row] = getProgramListFromProgram(chanNum);
+        for (unsigned int i = numRows;
+             i < (unsigned int) m_guideGrid->getChannelCount(); ++i)
+        {
+            delete m_programs[i];
+            m_programs[i] = NULL;
+            m_guideGrid->ResetRow(i);
+        }
     }
+    GuideStatus gs(firstRow, chanNums.size(), chanNums, m_channelInfos.size(),
+                   m_guideGrid->GetArea(), m_guideGrid->getChannelCount(),
+                   m_currentStartTime, m_currentEndTime, m_currentStartChannel,
+                   m_currentRow, m_currentCol, m_channelCount, m_timeCount,
+                   m_verticalLayout, m_firstTime, m_lastTime);
+    GuideUpdateProgramRow *updater =
+        new GuideUpdateProgramRow(this, gs, proglists);
+    m_threadPool.start(new GuideHelper(this, updater), "GuideHelper");
+}
 
-    ProgramList *proglist = m_programs[row];
-    if (!proglist)
+void GuideUpdateProgramRow::fillProgramRowInfosWith(int row, int chanNum,
+                                                    QDateTime start,
+                                                    ProgramList *proglist)
+{
+    if (row < 0 || row >= m_channelCount ||
+        start != m_currentStartTime)
+    {
+        delete proglist;
         return;
+    }
 
     QDateTime ts = m_currentStartTime;
 
@@ -1157,7 +1438,7 @@ void GuideGrid::fillProgramRowInfos(unsigned int row, bool useExistingData)
             progPast = played * 100 / length;
     }
 
-    m_guideGrid->SetProgPast(progPast);
+    m_progPast = progPast;
 
     ProgramList::iterator program = proglist->begin();
     vector<ProgramInfo*> unknownlist;
@@ -1215,7 +1496,7 @@ void GuideGrid::fillProgramRowInfos(unsigned int row, bool useExistingData)
     for (; it != unknownlist.end(); ++it)
         proglist->push_back(*it);
 
-    MythRect programRect = m_guideGrid->GetArea();
+    MythRect programRect = m_gg_programRect;
 
     /// use doubles to avoid large gaps at end..
     double ydifference = 0.0, xdifference = 0.0;
@@ -1223,14 +1504,14 @@ void GuideGrid::fillProgramRowInfos(unsigned int row, bool useExistingData)
     if (m_verticalLayout)
     {
         ydifference = programRect.width() /
-            (double) m_guideGrid->getChannelCount();
+            (double) m_gg_channelCount;
         xdifference = programRect.height() /
             (double) m_timeCount;
     }
     else
     {
         ydifference = programRect.height() /
-            (double) m_guideGrid->getChannelCount();
+            (double) m_gg_channelCount;
         xdifference = programRect.width() /
             (double) m_timeCount;
     }
@@ -1349,12 +1630,12 @@ void GuideGrid::fillProgramRowInfos(unsigned int row, bool useExistingData)
                 recStat = 0;
 
             QString title = (pginfo->GetTitle() == kUnknownTitle) ?
-                                tr("Unknown", "Unknown program title") :
+                GuideGrid::tr("Unknown", "Unknown program title") :
                                 pginfo->GetTitle();
-            m_guideGrid->SetProgramInfo(
+            m_result.push_back(GuideUIElement(
                 row, cnt, tempRect, title,
                 pginfo->GetCategory(), arrow, recFlag,
-                recStat, isCurrent);
+                recStat, isCurrent));
 
             cnt++;
         }
@@ -1374,7 +1655,6 @@ void GuideGrid::customEvent(QEvent *event)
         {
             LoadFromScheduler(m_recList);
             fillProgramInfos();
-            updateInfo();
         }
         else if (message == "STOP_VIDEO_REFRESH_TIMER")
         {
@@ -1513,6 +1793,16 @@ void GuideGrid::customEvent(QEvent *event)
         else
             ScheduleCommon::customEvent(event);
     }
+    else if (event->type() == UpdateGuideEvent::kEventType)
+    {
+        UpdateGuideEvent *uge = static_cast<UpdateGuideEvent*>(event);
+        if (uge->m_updater)
+        {
+            uge->m_updater->ExecuteUI();
+            delete uge->m_updater;
+            uge->m_updater = NULL;
+        }
+    }
 }
 
 void GuideGrid::updateDateText(void)
@@ -1524,10 +1814,51 @@ void GuideGrid::updateDateText(void)
                                                  (MythDate::kDateFull | MythDate::kSimplify)));
 }
 
+void GuideGrid::updateProgramsUI(unsigned int firstRow, unsigned int numRows,
+                                 int progPast,
+                                 const QVector<ProgramList*> &proglists,
+                                 const ProgInfoGuideArray &programInfos,
+                                 const QLinkedList<GuideUIElement> &elements)
+{
+    for (unsigned int i = 0; i < numRows; ++i)
+    {
+        unsigned int row = i + firstRow;
+        m_guideGrid->ResetRow(row);
+        if (m_programs[row] != proglists[i])
+        {
+            delete m_programs[row];
+            m_programs[row] = proglists[i];
+        }
+    }
+    m_guideGrid->SetProgPast(progPast);
+    for (QLinkedList<GuideUIElement>::const_iterator it = elements.begin();
+         it != elements.end(); ++it)
+    {
+        const GuideUIElement &r = *it;
+        m_guideGrid->SetProgramInfo(r.m_row, r.m_col, r.m_area, r.m_title,
+                                    r.m_category, r.m_arrow, r.m_recType,
+                                    r.m_recStat, r.m_selected);
+    }
+    for (unsigned int i = firstRow; i < firstRow + numRows; ++i)
+    {
+        for (int j = 0; j < MAX_DISPLAY_TIMES; ++j)
+            m_programInfos[i][j] = programInfos[i][j];
+        if (i == (unsigned int)m_currentRow)
+            updateInfo();
+    }
+    m_guideGrid->SetRedraw();
+}
+
 void GuideGrid::updateChannels(void)
 {
-    m_channelList->Reset();
+    GuideUpdateChannels *updater =
+        new GuideUpdateChannels(this, m_currentStartChannel);
+    m_threadPool.start(new GuideHelper(this, updater), "GuideHelper");
+}
 
+void GuideGrid::updateChannelsNonUI(QVector<ChannelInfo *> &chinfos,
+                                    QVector<bool> &unavailables)
+{
     ChannelInfo *chinfo = GetChannelInfo(m_currentStartChannel);
 
     if (m_player)
@@ -1575,7 +1906,19 @@ void GuideGrid::updateChannels(void)
                 unavailable = (alt == m_channelInfoIdx[chanNumber]);
             }
         }
+        chinfos.push_back(chinfo);
+        unavailables.push_back(unavailable);
+    }
+}
 
+void GuideGrid::updateChannelsUI(const QVector<ChannelInfo *> &chinfos,
+                                 const QVector<bool> &unavailables)
+{
+    m_channelList->Reset();
+    for (int i = 0; i < chinfos.size(); ++i)
+    {
+        ChannelInfo *chinfo = chinfos[i];
+        bool unavailable = unavailables[i];
         MythUIButtonListItem *item =
             new MythUIButtonListItem(m_channelList,
                                      chinfo ? chinfo->GetFormatted(ChannelInfo::kChannelShort) : QString());
@@ -1660,6 +2003,7 @@ void GuideGrid::updateInfo(void)
         QString rating = QString::number(pginfo->GetStars(10));
         ratingState->DisplayState(rating);
     }
+    m_guideGrid->SetRedraw();
 }
 
 void GuideGrid::toggleGuideListing()
@@ -1809,9 +2153,7 @@ void GuideGrid::cursorLeft()
     }
     else
     {
-        fillProgramRowInfos(m_currentRow);
-        m_guideGrid->SetRedraw();
-        updateInfo();
+        fillProgramRowInfos(m_currentRow, true);
     }
 }
 
@@ -1837,9 +2179,7 @@ void GuideGrid::cursorRight()
     }
     else
     {
-        fillProgramRowInfos(m_currentRow);
-        m_guideGrid->SetRedraw();
-        updateInfo();
+        fillProgramRowInfos(m_currentRow, true);
     }
 }
 
@@ -1854,10 +2194,7 @@ void GuideGrid::cursorDown()
     }
     else
     {
-        fillProgramRowInfos(m_currentRow);
-        m_guideGrid->SetRedraw();
-        updateInfo();
-        updateChannels();
+        fillProgramRowInfos(m_currentRow, true);
     }
 }
 
@@ -1872,10 +2209,7 @@ void GuideGrid::cursorUp()
     }
     else
     {
-        fillProgramRowInfos(m_currentRow);
-        m_guideGrid->SetRedraw();
-        updateInfo();
-        updateChannels();
+        fillProgramRowInfos(m_currentRow, true);
     }
 }
 
@@ -1907,8 +2241,6 @@ void GuideGrid::moveLeftRight(MoveVector movement)
 
     fillTimeInfos();
     fillProgramInfos();
-    m_guideGrid->SetRedraw();
-    updateInfo();
     updateDateText();
 }
 
@@ -1933,8 +2265,6 @@ void GuideGrid::moveUpDown(MoveVector movement)
     }
 
     fillProgramInfos();
-    m_guideGrid->SetRedraw();
-    updateInfo();
     updateChannels();
 }
 
@@ -1947,8 +2277,6 @@ void GuideGrid::moveToTime(QDateTime datetime)
 
     fillTimeInfos();
     fillProgramInfos();
-    m_guideGrid->SetRedraw();
-    updateInfo();
     updateDateText();
 }
 
@@ -2016,7 +2344,6 @@ void GuideGrid::quickRecord()
     QuickRecord(pginfo);
     LoadFromScheduler(m_recList);
     fillProgramInfos();
-    updateInfo();
 }
 
 void GuideGrid::editRecSchedule()
@@ -2163,7 +2490,6 @@ void GuideGrid::GoTo(int start, int cur_row)
     m_currentRow = cur_row % m_channelCount;
     updateChannels();
     fillProgramInfos();
-    updateInfo();
     updateJumpToChannel();
 }
 
diff --git a/mythtv/programs/mythfrontend/guidegrid.h b/mythtv/programs/mythfrontend/guidegrid.h
index fd11d27..94dbd8c 100644
--- a/mythtv/programs/mythfrontend/guidegrid.h
+++ b/mythtv/programs/mythfrontend/guidegrid.h
@@ -10,6 +10,7 @@ using namespace std;
 #include <QString>
 #include <QDateTime>
 #include <QEvent>
+#include <QLinkedList>
 
 // myth
 #include "mythscreentype.h"
@@ -17,6 +18,7 @@ using namespace std;
 #include "channelgroup.h"
 #include "channelutil.h"
 #include "mythuiguidegrid.h"
+#include "mthreadpool.h"
 
 // mythfrontend
 #include "schedulecommon.h"
@@ -33,6 +35,7 @@ class MythUIGuideGrid;
 
 typedef vector<ChannelInfo>   db_chan_list_t;
 typedef vector<db_chan_list_t> db_chan_list_list_t;
+typedef ProgramInfo *ProgInfoGuideArray[MAX_DISPLAY_CHANS][MAX_DISPLAY_TIMES];
 
 class JumpToChannel;
 class JumpToChannelListener
@@ -76,6 +79,29 @@ class JumpToChannel : public QObject
     static const uint kJumpToChannelTimeout = 3500; // ms
 };
 
+// GuideUIElement encapsulates the arguments to
+// MythUIGuideGrid::SetProgramInfo().  The elements are prepared in a
+// background thread and then sent via an event to the UI thread for
+// rendering.
+class GuideUIElement {
+public:
+    GuideUIElement(int row, int col, const QRect &area,
+                   const QString &title, const QString &category,
+                   int arrow, int recType, int recStat, bool selected)
+        : m_row(row), m_col(col), m_area(area), m_title(title),
+          m_category(category), m_arrow(arrow), m_recType(recType),
+          m_recStat(recStat), m_selected(selected) {}
+    const int m_row;
+    const int m_col;
+    const QRect m_area;
+    const QString m_title;
+    const QString m_category;
+    const int m_arrow;
+    const int m_recType;
+    const int m_recStat;
+    const bool m_selected;
+};
+
 class GuideGrid : public ScheduleCommon, public JumpToChannelListener
 {
     Q_OBJECT
@@ -102,6 +128,11 @@ class GuideGrid : public ScheduleCommon, public JumpToChannelListener
 
     virtual void aboutToShow();
     virtual void aboutToHide();
+    // Allow class GuideUpdateProgramRow to figure out whether the
+    // current start time/channel coordinates are the same, so that it can
+    // skip the work if not.
+    uint GetCurrentStartChannel(void) const { return m_currentStartChannel; }
+    QDateTime GetCurrentStartTime(void) const { return m_currentStartTime; }
 
   protected slots:
     void cursorLeft();
@@ -179,8 +210,21 @@ class GuideGrid : public ScheduleCommon, public JumpToChannelListener
     void fillChannelInfos(bool gotostartchannel = true);
     void fillTimeInfos(void);
     void fillProgramInfos(bool useExistingData = false);
-    void fillProgramRowInfos(unsigned int row, bool useExistingData = false);
+    // Set row=-1 to fill all rows.
+    void fillProgramRowInfos(int row, bool useExistingData);
+public:
+    // These need to be public so that the helper classes can operate.
     ProgramList *getProgramListFromProgram(int chanNum);
+    void updateProgramsUI(unsigned int firstRow, unsigned int numRows,
+                          int progPast,
+                          const QVector<ProgramList*> &proglists,
+                          const ProgInfoGuideArray &programInfos,
+                          const QLinkedList<GuideUIElement> &elements);
+    void updateChannelsNonUI(QVector<ChannelInfo *> &chinfos,
+                             QVector<bool> &unavailables);
+    void updateChannelsUI(const QVector<ChannelInfo *> &chinfos,
+                          const QVector<bool> &unavailables);
+private:
 
     void setStartChannel(int newStartChannel);
 
@@ -201,7 +245,7 @@ class GuideGrid : public ScheduleCommon, public JumpToChannelListener
     QMap<uint,uint>      m_channelInfoIdx;
 
     vector<ProgramList*> m_programs;
-    ProgramInfo *m_programInfos[MAX_DISPLAY_CHANS][MAX_DISPLAY_TIMES];
+    ProgInfoGuideArray m_programInfos;
     ProgramList  m_recList;
 
     QDateTime m_originalStartTime;
@@ -235,6 +279,8 @@ class GuideGrid : public ScheduleCommon, public JumpToChannelListener
 
     QTimer *m_updateTimer; // audited ref #5318
 
+    MThreadPool       m_threadPool;
+
     int               m_changrpid;
     ChannelGroupList  m_changrplist;
 
