Ticket #12809: 0002-Watchlist-with-lastPlayPos.patch

File 0002-Watchlist-with-lastPlayPos.patch, 56.1 KB (added by Roger Siddons, 4 years ago)
  • mythtv/libs/libmyth/programtypes.h

    From 5783964c24270d61ca8a71baefbabf2548704a1a Mon Sep 17 00:00:00 2001
    From: Roger Siddons <rsiddons@mythtv.org>
    Date: Thu, 21 Jan 2016 13:46:11 +0000
    Subject: [PATCH 2/3] Watchlist with lastPlayPos
    
    Watchlist: Refactor & remove debug info
    
    The watchlist abuses ProgramInfo::recpriority2 to cache scores for display on the Info/ProgDetails screen.
    The score/state is of no interest to the user, who cannot alter it. It is debug information that belongs in the logs and has been removed from the screens.
    Themes should remove WATCH_LIST_SCORE & WATCH_LIST_STATUS widgets from htmls/progdetails*.html. However they are set as empty to prevent breakage for now.
    
    Refs #12296
    
    Watchlist: Group by title rather than record rule
    
    Watchlist groups titles using the record rule id.
    Thus different titles recorded by a one rule are confusingly grouped together.
    And the same title recorded by different rules are shown in different groups.
    Also a pre-requisite for selection by episode number.
    
    Grouping ignores title case, whitespace and punctuation.
    
    Ref #12296
    
    Watchlist: Use season/episode when selecting an episode
    
    Watchlist always selects the earliest/oldest episode, so the wrong recording is shown when episodes are recorded out of order.
    This patch uses season/episode data to override the oldest selection, where appropriate.
    
    Refs #12296
    
    Watchlist: Add theme widget to show watchlist episode count
    
    A new UI widget "watchtotal" enables the watchlist to show the number of episodes available for each title.
    For example a "recordings" buttonlist specification of:
    
    <textarea name="title">
      <template>%title%% (|watchtotal| episodes)%% - "|subtitle|"%</template>
    </textarea>
    
    will produce:
    
    The Simpsons (15 episodes) - "Homer does something stupid"
    
    This widget is only set when the Watchlist group is selected in the groups list - for other groups it will show the usual:
    
    The Simpsons - "Homer does something stupid"
    
    Refs #12296
    
    Watchlist: Add 'Limited Oldest' strategy
    
    Adds a new simplified sort strategy "LimitedOldest" that is more stable, intuitive & useful than the current 'Classic' one.
    Titles are ordered by record date (oldest first) so the list is predictable and does not spontaneously shuffle.
    Optionally new titles & quickly-watched titles can be promoted to the top and titles that remain unwatched for a long period can be relegated to the bottom.
    Two new settings "PlaybackWLRecentLimit" & "PlaybackWLOldLimit" are introduced so the user can adjust this behaviour.
    
    User can choose between this and the 'classic' sort strategy via a new setting "PlaybackWLOrder"
    
    Fixes #12296
    
    Watchlist: Update watchlist after metadata edits
    
    Prevent metadata edits updating the recording list directly.
    They must be processed by a ProgInfo update in order to update watchlist.
    
    Watchlist: Place part-watched shows at top
    
    diff --git a/mythtv/libs/libmyth/programtypes.h b/mythtv/libs/libmyth/programtypes.h
    index bcbb886..0e28ffb 100644
    a b typedef enum AvailableStatusTypes { 
    222222} AvailableStatusType; // note stored in uint8_t in ProgramInfo
    223223MPUBLIC QString toString(AvailableStatusType);
    224224
    225 enum WatchListStatus {
    226     wlDeleted = -4,
    227     wlEarlier = -3,
    228     wlWatched = -2,
    229     wlExpireOff = -1
    230 };
    231 
    232225typedef enum AutoExpireTypes {
    233226    kDisableAutoExpire = 0,
    234227    kNormalAutoExpire  = 1,
  • mythtv/programs/mythfrontend/globalsettings.cpp

    diff --git a/mythtv/programs/mythfrontend/globalsettings.cpp b/mythtv/programs/mythfrontend/globalsettings.cpp
    index 4a8a5b3..aa06722 100644
    a b static HostCheckBox *PlaybackWatchList() 
    32153215    gc->setValue(true);
    32163216
    32173217    gc->setHelpText(WatchListSettings::tr("The 'Watch List' is an abbreviated "
    3218                                           "list of recordings sorted to "
     3218                                          "list of recordings filtered to "
    32193219                                          "highlight series and shows that "
    32203220                                          "need attention in order to keep up "
    32213221                                          "to date."));
    static HostCheckBox *PlaybackWLAutoExpire() 
    32543254    return gc;
    32553255}
    32563256
     3257static HostComboBox *PlaybackWLStrategy()
     3258{
     3259    HostComboBox *gc = new HostComboBox("PlaybackWLOrder");
     3260
     3261    gc->setLabel(WatchListSettings::tr("Sort method"));
     3262    gc->addSelection(WatchListSettings::tr("Classic", "Watchlist"), "Classic");
     3263    gc->addSelection(WatchListSettings::tr(
     3264                         "Limited Oldest", "Watchlist"), "LimitedOldest");
     3265    gc->setHelpText(WatchListSettings::tr("The watchlist ordering method. "
     3266                         "'Classic' learns your viewing habits. "
     3267                         "'Limited Oldest' simply lists shows by age, but "
     3268                         "promotes new/popular titles and demotes old ones."));
     3269    return gc;
     3270}
     3271
    32573272static HostSpinBox *PlaybackWLMaxAge()
    32583273{
    32593274    HostSpinBox *gs = new HostSpinBox("PlaybackWLMaxAge", 30, 180, 10);
    static HostSpinBox *PlaybackWLBlackOut() 
    32893304    return gs;
    32903305}
    32913306
    3292 WatchListSettings::WatchListSettings() :
     3307static HostSpinBox *PlaybackWLRecentLimit()
     3308{
     3309    HostSpinBox *gs = new HostSpinBox("PlaybackWLRecentLimit", 0, 168, 6, true);
     3310
     3311    gs->setLabel(WatchListSettings::tr("Hours to keep at top"));
     3312
     3313    gs->setValue(6);
     3314
     3315    gs->setHelpText(WatchListSettings::tr(
     3316                        "Titles are promoted to the top when they are younger "
     3317                        "than, or previous episodes have been watched "
     3318                        "(on average) within, this interval. "
     3319                        "0 disables this behaviour."));
     3320    return gs;
     3321}
     3322
     3323static HostSpinBox *PlaybackWLOldLimit()
     3324{
     3325    HostSpinBox *gs = new HostSpinBox("PlaybackWLOldLimit", 7, 3650, 7, true);
     3326
     3327    gs->setLabel(WatchListSettings::tr("Days before relegating to bottom"));
     3328
     3329    gs->setValue(28);
     3330
     3331    gs->setHelpText(WatchListSettings::tr(
     3332                        "Titles are relegated to the bottom when they "
     3333                        "are older than, and previous episodes have not "
     3334                        "been watched (on average), within this interval. "
     3335                        "Use a high value to prevent this behaviour."));
     3336
     3337    return gs;
     3338}
     3339
     3340WatchListStrategy::WatchListStrategy() :
    32933341    TriggeredConfigurationGroup(false, false, true, true)
    32943342{
     3343     Setting* strategy = PlaybackWLStrategy();
     3344     addChild(strategy);
     3345     setTrigger(strategy);
     3346
     3347     ConfigurationGroup* classic = new VerticalConfigurationGroup(false,false);
     3348
     3349     classic->addChild(PlaybackWLMaxAge());
     3350     classic->addChild(PlaybackWLBlackOut());
    32953351
     3352     addTarget("Classic", classic);
     3353
     3354     ConfigurationGroup* oldest = new VerticalConfigurationGroup(false,false);
     3355
     3356     oldest->addChild(PlaybackWLRecentLimit());
     3357     oldest->addChild(PlaybackWLOldLimit());
     3358
     3359     addTarget("LimitedOldest", oldest);
     3360}
     3361
     3362WatchListSettings::WatchListSettings() :
     3363    TriggeredConfigurationGroup(false, false, true, true)
     3364{
    32963365     Setting* watchList = PlaybackWatchList();
    32973366     addChild(watchList);
    32983367     setTrigger(watchList);
    32993368
    3300      ConfigurationGroup* settings = new VerticalConfigurationGroup(false);
     3369     ConfigurationGroup* settings =
     3370             new VerticalConfigurationGroup(false,false,true,true);
    33013371
    33023372     settings->addChild(PlaybackWLStart());
    33033373     settings->addChild(PlaybackWLAutoExpire());
    3304      settings->addChild(PlaybackWLMaxAge());
    3305      settings->addChild(PlaybackWLBlackOut());
     3374     settings->addChild(new WatchListStrategy());
    33063375
    33073376     addTarget("1", settings);
    3308 
    3309      addTarget("0", new VerticalConfigurationGroup(true));
    3310 };
     3377     addTarget("0", new VerticalConfigurationGroup(false, false));
     3378}
    33113379
    33123380static HostCheckBox *LCDShowTime()
    33133381{
  • mythtv/programs/mythfrontend/globalsettings.h

    diff --git a/mythtv/programs/mythfrontend/globalsettings.h b/mythtv/programs/mythfrontend/globalsettings.h
    index 2de0197..fc114cd 100644
    a b class LcdSettings : public TriggeredConfigurationGroup 
    3838    LcdSettings();
    3939};
    4040
     41class WatchListStrategy : public TriggeredConfigurationGroup
     42{
     43    Q_OBJECT
     44
     45  public:
     46    WatchListStrategy();
     47};
    4148
    4249class WatchListSettings : public TriggeredConfigurationGroup
    4350{
  • mythtv/programs/mythfrontend/playbackbox.cpp

    diff --git a/mythtv/programs/mythfrontend/playbackbox.cpp b/mythtv/programs/mythfrontend/playbackbox.cpp
    index fe71685..76d8ac8 100644
    a b static int comp_originalAirDate_rev(const ProgramInfo *a, const ProgramInfo *b) 
    101101        return (dt1 > dt2 ? 1 : -1);
    102102}
    103103
    104 static int comp_recpriority2(const ProgramInfo *a, const ProgramInfo *b)
    105 {
    106     if (a->GetRecordingPriority2() == b->GetRecordingPriority2())
    107         return (a->GetRecordingStartTime() <
    108                 b->GetRecordingStartTime() ? 1 : -1);
    109     else
    110         return (a->GetRecordingPriority2() <
    111                 b->GetRecordingPriority2() ? 1 : -1);
    112 }
    113 
    114104static int comp_recordDate(const ProgramInfo *a, const ProgramInfo *b)
    115105{
    116106    if (a->GetScheduledStartTime().date() == b->GetScheduledStartTime().date())
    static bool comp_originalAirDate_rev_less_than( 
    179169    return comp_originalAirDate_rev(a, b) < 0;
    180170}
    181171
    182 static bool comp_recpriority2_less_than(
    183     const ProgramInfo *a, const ProgramInfo *b)
    184 {
    185     return comp_recpriority2(a, b) < 0;
    186 }
    187 
    188172static bool comp_recordDate_less_than(
    189173    const ProgramInfo *a, const ProgramInfo *b)
    190174{
    PlaybackBox::PlaybackBox(MythScreenStack *parent, QString name, 
    405389      m_titleView(false),
    406390      m_useCategories(false),
    407391      m_useRecGroups(false),
    408       m_watchListAutoExpire(false),
    409       m_watchListMaxAge(60),              m_watchListBlackOut(2),
    410392      m_listOrder(1),
    411393      // Recording Group settings
    412394      m_groupDisplayName(ProgramInfo::i18n("All Programs")),
    PlaybackBox::PlaybackBox(MythScreenStack *parent, QString name, 
    449431    int pbOrder        = gCoreContext->GetNumSetting("PlayBoxOrdering", 1);
    450432    // Split out sort order modes, wacky order for backward compatibility
    451433    m_listOrder = (pbOrder >> 1) ^ (m_allOrder = pbOrder & 1);
    452     m_watchListStart     = gCoreContext->GetNumSetting("PlaybackWLStart", 0);
    453 
    454     m_watchListAutoExpire= gCoreContext->GetNumSetting("PlaybackWLAutoExpire", 0);
    455     m_watchListMaxAge    = gCoreContext->GetNumSetting("PlaybackWLMaxAge", 60);
    456     m_watchListBlackOut  = gCoreContext->GetNumSetting("PlaybackWLBlackOut", 2);
     434    m_watchListStart  = gCoreContext->GetNumSetting("PlaybackWLStart", 0);
    457435
    458436    bool displayCat  = gCoreContext->GetNumSetting("DisplayRecGroupIsCategory", 0);
    459437
    void PlaybackBox::ItemLoaded(MythUIButtonListItem *item) 
    956934        item->SetFontState(state);
    957935
    958936        InfoMap infoMap;
     937
     938        // watchlist episode count only set when watchlist group is selected
     939        if (groupname == m_watchGroupLabel)
     940            infoMap["watchtotal"] = m_watchlist.GetTotal(*pginfo);
     941
    959942        pginfo->ToMap(infoMap);
    960943        item->SetTextFromMap(infoMap);
    961944
    bool PlaybackBox::UpdateUILists(void) 
    16291612    m_progsInDB = 0;
    16301613    m_titleList.clear();
    16311614    m_progLists.clear();
     1615    m_watchlist.Clear();
    16321616    m_recordingList->Reset();
    16331617    m_groupList->Reset();
    16341618    if (m_recgroupList)
    bool PlaybackBox::UpdateUILists(void) 
    16431627
    16441628    QMap<QString, QString> sortedList;
    16451629    QMap<int, QString> searchRule;
    1646     QMap<int, int> recidEpisodes;
    16471630
    16481631    m_programInfoCache.Refresh();
    16491632
    bool PlaybackBox::UpdateUILists(void) 
    17931776            if ((m_viewMask & VIEW_WATCHLIST) &&
    17941777                    pRecgroup != "LiveTV" && pRecgroup != "Deleted")
    17951778            {
    1796                 if (m_watchListAutoExpire && !p->IsAutoExpirable())
    1797                 {
    1798                     p->SetRecordingPriority2(wlExpireOff);
    1799                     LOG(VB_FILE, LOG_INFO, QString("Auto-expire off:  %1")
    1800                         .arg(p->GetTitle()));
    1801                 }
    1802                 else if (p->IsWatched())
    1803                 {
    1804                     p->SetRecordingPriority2(wlWatched);
    1805                     LOG(VB_FILE, LOG_INFO,
    1806                         QString("Marked as 'watched':  %1")
    1807                         .arg(p->GetTitle()));
    1808                 }
    1809                 else
    1810                 {
    1811                     if (p->GetRecordingRuleID())
    1812                         recidEpisodes[p->GetRecordingRuleID()] += 1;
    1813                     if (recidEpisodes[p->GetRecordingRuleID()] == 1 ||
    1814                             !p->GetRecordingRuleID())
    1815                     {
    1816                         m_progLists[m_watchGroupLabel].push_front(p);
    1817                         m_progLists[m_watchGroupLabel].setAutoDelete(false);
    1818                     }
    1819                     else
    1820                     {
    1821                         p->SetRecordingPriority2(wlEarlier);
    1822                         LOG(VB_FILE, LOG_INFO,
    1823                             QString("Not the earliest:  %1")
    1824                             .arg(p->GetTitle()));
    1825                     }
    1826                 }
     1779                m_watchlist.Add(p);
    18271780            }
    18281781        }
    18291782    }
    bool PlaybackBox::UpdateUILists(void) 
    18981851        }
    18991852    }
    19001853
    1901     if (!m_progLists[m_watchGroupLabel].empty())
     1854    if (!m_watchlist.Empty())
    19021855    {
    1903         QDateTime now = MythDate::current();
    1904         int baseValue = m_watchListMaxAge * 2 / 3;
    1905 
    1906         QMap<int, int> recType;
    1907         QMap<int, int> maxEpisodes;
    1908         QMap<int, int> avgDelay;
    1909         QMap<int, int> spanHours;
    1910         QMap<int, int> delHours;
    1911         QMap<int, int> nextHours;
    1912 
    1913         MSqlQuery query(MSqlQuery::InitCon());
    1914         query.prepare("SELECT recordid, type, maxepisodes, avg_delay, "
    1915                       "next_record, last_record, last_delete FROM record;");
    1916 
    1917         if (query.exec())
    1918         {
    1919             while (query.next())
    1920             {
    1921                 int recid = query.value(0).toInt();
    1922                 recType[recid] = query.value(1).toInt();
    1923                 maxEpisodes[recid] = query.value(2).toInt();
    1924                 avgDelay[recid] = query.value(3).toInt();
    1925 
    1926                 QDateTime next_record =
    1927                     MythDate::as_utc(query.value(4).toDateTime());
    1928                 QDateTime last_record =
    1929                     MythDate::as_utc(query.value(5).toDateTime());
    1930                 QDateTime last_delete =
    1931                     MythDate::as_utc(query.value(6).toDateTime());
    1932 
    1933                 // Time between the last and next recordings
    1934                 spanHours[recid] = 1000;
    1935                 if (last_record.isValid() && next_record.isValid())
    1936                     spanHours[recid] =
    1937                         last_record.secsTo(next_record) / 3600 + 1;
    1938 
    1939                 // Time since the last episode was deleted
    1940                 delHours[recid] = 1000;
    1941                 if (last_delete.isValid())
    1942                     delHours[recid] = last_delete.secsTo(now) / 3600 + 1;
    1943 
    1944                 // Time until the next recording if any
    1945                 if (next_record.isValid())
    1946                     nextHours[recid] = now.secsTo(next_record) / 3600 + 1;
    1947             }
    1948         }
    1949 
    1950         ProgramList::iterator pit = m_progLists[m_watchGroupLabel].begin();
    1951         while (pit != m_progLists[m_watchGroupLabel].end())
    1952         {
    1953             int recid = (*pit)->GetRecordingRuleID();
    1954             int avgd =  avgDelay[recid];
    1955 
    1956             if (avgd == 0)
    1957                 avgd = 100;
    1958 
    1959             // Set the intervals beyond range if there is no record entry
    1960             if (spanHours[recid] == 0)
    1961             {
    1962                 spanHours[recid] = 1000;
    1963                 delHours[recid] = 1000;
    1964             }
    1965 
    1966             // add point equal to baseValue for each additional episode
    1967             if (!(*pit)->GetRecordingRuleID() || maxEpisodes[recid] > 0)
    1968                 (*pit)->SetRecordingPriority2(0);
    1969             else
    1970             {
    1971                 (*pit)->SetRecordingPriority2(
    1972                     (recidEpisodes[(*pit)->GetRecordingRuleID()] - 1) *
    1973                     baseValue);
    1974             }
    1975 
    1976             // add points every 3hr leading up to the next recording
    1977             if (nextHours[recid] > 0 && nextHours[recid] < baseValue * 3)
    1978             {
    1979                 (*pit)->SetRecordingPriority2(
    1980                     (*pit)->GetRecordingPriority2() +
    1981                     (baseValue * 3 - nextHours[recid]) / 3);
    1982             }
    1983 
    1984             int hrs = (*pit)->GetScheduledEndTime().secsTo(now) / 3600;
    1985             if (hrs < 1)
    1986                 hrs = 1;
    1987 
    1988             // add points for a new recording that decrease each hour
    1989             if (hrs < 42)
    1990             {
    1991                 (*pit)->SetRecordingPriority2(
    1992                     (*pit)->GetRecordingPriority2() + 42 - hrs);
    1993             }
    1994 
    1995             // add points for how close the recorded time of day is to 'now'
    1996             (*pit)->SetRecordingPriority2(
    1997                 (*pit)->GetRecordingPriority2() + abs((hrs % 24) - 12) * 2);
    1998 
    1999             // Daily
    2000             if (spanHours[recid] < 50 ||
    2001                 recType[recid] == kDailyRecord)
    2002             {
    2003                 if (delHours[recid] < m_watchListBlackOut * 4)
    2004                 {
    2005                     (*pit)->SetRecordingPriority2(wlDeleted);
    2006                     LOG(VB_FILE, LOG_INFO,
    2007                         QString("Recently deleted daily:  %1")
    2008                             .arg((*pit)->GetTitle()));
    2009                     pit = m_progLists[m_watchGroupLabel].erase(pit);
    2010                     continue;
    2011                 }
    2012                 else
    2013                 {
    2014                     LOG(VB_FILE, LOG_INFO, QString("Daily interval:  %1")
    2015                             .arg((*pit)->GetTitle()));
    2016 
    2017                     if (maxEpisodes[recid] > 0)
    2018                     {
    2019                         (*pit)->SetRecordingPriority2(
    2020                             (*pit)->GetRecordingPriority2() +
    2021                             (baseValue / 2) + (hrs / 24));
    2022                     }
    2023                     else
    2024                     {
    2025                         (*pit)->SetRecordingPriority2(
    2026                             (*pit)->GetRecordingPriority2() +
    2027                             (baseValue / 5) + hrs);
    2028                     }
    2029                 }
    2030             }
    2031             // Weekly
    2032             else if (nextHours[recid] ||
    2033                      recType[recid] == kWeeklyRecord)
    2034 
    2035             {
    2036                 if (delHours[recid] < (m_watchListBlackOut * 24) - 4)
    2037                 {
    2038                     (*pit)->SetRecordingPriority2(wlDeleted);
    2039                     LOG(VB_FILE, LOG_INFO,
    2040                         QString("Recently deleted weekly:  %1")
    2041                             .arg((*pit)->GetTitle()));
    2042                     pit = m_progLists[m_watchGroupLabel].erase(pit);
    2043                     continue;
    2044                 }
    2045                 else
    2046                 {
    2047                     LOG(VB_FILE, LOG_INFO, QString("Weekly interval: %1")
    2048                             .arg((*pit)->GetTitle()));
    2049 
    2050                     if (maxEpisodes[recid] > 0)
    2051                     {
    2052                         (*pit)->SetRecordingPriority2(
    2053                             (*pit)->GetRecordingPriority2() +
    2054                             (baseValue / 2) + (hrs / 24));
    2055                     }
    2056                     else
    2057                     {
    2058                         (*pit)->SetRecordingPriority2(
    2059                             (*pit)->GetRecordingPriority2() +
    2060                             (baseValue / 3) + (baseValue * hrs / 24 / 4));
    2061                     }
    2062                 }
    2063             }
    2064             // Not recurring
    2065             else
    2066             {
    2067                 if (delHours[recid] < (m_watchListBlackOut * 48) - 4)
    2068                 {
    2069                     (*pit)->SetRecordingPriority2(wlDeleted);
    2070                     pit = m_progLists[m_watchGroupLabel].erase(pit);
    2071                     continue;
    2072                 }
    2073                 else
    2074                 {
    2075                     // add points for a new Single or final episode
    2076                     if (hrs < 36)
    2077                     {
    2078                         (*pit)->SetRecordingPriority2(
    2079                             (*pit)->GetRecordingPriority2() +
    2080                             baseValue * (36 - hrs) / 36);
    2081                     }
    2082 
    2083                     if (avgd != 100)
    2084                     {
    2085                         if (maxEpisodes[recid] > 0)
    2086                         {
    2087                             (*pit)->SetRecordingPriority2(
    2088                                 (*pit)->GetRecordingPriority2() +
    2089                                 (baseValue / 2) + (hrs / 24));
    2090                         }
    2091                         else
    2092                         {
    2093                             (*pit)->SetRecordingPriority2(
    2094                                 (*pit)->GetRecordingPriority2() +
    2095                                 (baseValue / 3) + (baseValue * hrs / 24 / 4));
    2096                         }
    2097                     }
    2098                     else if ((hrs / 24) < m_watchListMaxAge)
    2099                     {
    2100                         (*pit)->SetRecordingPriority2(
    2101                             (*pit)->GetRecordingPriority2() +
    2102                             hrs / 24);
    2103                     }
    2104                     else
    2105                     {
    2106                         (*pit)->SetRecordingPriority2(
    2107                             (*pit)->GetRecordingPriority2() +
    2108                             m_watchListMaxAge);
    2109                     }
    2110                 }
    2111             }
    2112 
    2113             // Factor based on the average time shift delay.
    2114             // Scale the avgd range of 0 thru 200 hours to 133% thru 67%
    2115             int delaypct = avgd / 3 + 67;
    2116 
    2117             if (avgd < 100)
    2118             {
    2119                 (*pit)->SetRecordingPriority2(
    2120                     (*pit)->GetRecordingPriority2() * (200 - delaypct) / 100);
    2121             }
    2122             else if (avgd > 100)
    2123             {
    2124                 (*pit)->SetRecordingPriority2(
    2125                     (*pit)->GetRecordingPriority2() * 100 / delaypct);
    2126             }
    2127 
    2128             LOG(VB_FILE, LOG_INFO, QString(" %1  %2  %3")
    2129                     .arg(MythDate::toString((*pit)->GetScheduledStartTime(),
    2130                                             MythDate::kDateShort))
    2131                     .arg((*pit)->GetRecordingPriority2())
    2132                     .arg((*pit)->GetTitle()));
     1856        // Shows with same score will appear in reverse alphabetic order
     1857        foreach (ProgramInfo* wp, m_watchlist.Order())
     1858            m_progLists[m_watchGroupLabel].push_front(wp);
    21331859
    2134             ++pit;
    2135         }
    2136         std::stable_sort(m_progLists[m_watchGroupLabel].begin(),
    2137                          m_progLists[m_watchGroupLabel].end(),
    2138                          comp_recpriority2_less_than);
     1860        m_progLists[m_watchGroupLabel].setAutoDelete(false);
    21391861    }
    21401862
    21411863    m_titleList = QStringList("");
    void PlaybackBox::HandleRecordingAddEvent(const ProgramInfo &evinfo) 
    44494171
    44504172void PlaybackBox::HandleUpdateItemEvent(uint recordingId, uint flags)
    44514173{
    4452     // Changing recording group full reload
    4453     if (flags & PIC_RECGROUP_CHANGED)
     4174    // Changing recording group or watchlist-dependent metadata requires full reload
     4175    if ((flags & PIC_RECGROUP_CHANGED) ||
     4176            ((flags & PIC_WATCHLIST_CHANGED) && (m_viewMask & VIEW_WATCHLIST)))
    44544177    {
    44554178        ScheduleUpdateUIList();
    44564179    }
    void PlaybackBox::saveRecMetadata(const QString &newTitle, 
    48974620    {
    48984621        m_recordingList->RemoveItem(item);
    48994622    }
    4900     else
    4901     {
    4902         QString tempSubTitle = newTitle;
    4903         if (!newSubtitle.trimmed().isEmpty())
    4904             tempSubTitle = QString("%1 - \"%2\"")
    4905                             .arg(tempSubTitle).arg(newSubtitle);
    4906 
    4907         QString seasone;
    4908         QString seasonx;
    4909         QString season;
    4910         QString episode;
    4911         if (newSeason > 0 || newEpisode > 0)
    4912         {
    4913             season = format_season_and_episode(newSeason, 1);
    4914             episode = format_season_and_episode(newEpisode, 1);
    4915             seasone = QString("s%1e%2")
    4916                 .arg(format_season_and_episode(newSeason, 2))
    4917                 .arg(format_season_and_episode(newEpisode, 2));
    4918             seasonx = QString("%1x%2")
    4919                 .arg(format_season_and_episode(newSeason, 1))
    4920                 .arg(format_season_and_episode(newEpisode, 2));
    4921         }
    4922 
    4923         item->SetText(tempSubTitle, "titlesubtitle");
    4924         item->SetText(newTitle, "title");
    4925         item->SetText(newSubtitle, "subtitle");
    4926         item->SetText(newInetref, "inetref");
    4927         item->SetText(seasonx, "00x00");
    4928         item->SetText(seasone, "s00e00");
    4929         item->SetText(season, "season");
    4930         item->SetText(episode, "episode");
    4931         if (newDescription != NULL)
    4932             item->SetText(newDescription, "description");
    4933     }
    49344623
    49354624    pginfo->SaveInetRef(newInetref);
    49364625    pginfo->SaveSeasonEpisode(newSeason, newEpisode);
    49374626
    49384627    RecordingInfo ri(*pginfo);
    49394628    ri.ApplyRecordRecTitleChange(newTitle, newSubtitle, newDescription);
    4940     *pginfo = ri;
    49414629}
    49424630
    49434631void PlaybackBox::setRecGroup(QString newRecGroup)
    void PlaybackBox::setRecGroup(QString newRecGroup) 
    49914679
    49924680            RecordingInfo ri(*p);
    49934681            ri.ApplyRecordRecGroupChange(newRecGroup);
    4994             *p = ri;
    49954682        }
    49964683        doClearPlaylist();
    4997         UpdateUILists();
    49984684        return;
    49994685    }
    50004686
    void PlaybackBox::setRecGroup(QString newRecGroup) 
    50094695
    50104696    RecordingInfo ri(*p);
    50114697    ri.ApplyRecordRecGroupChange(newRecGroup);
    5012     *p = ri;
    5013     UpdateUILists();
    50144698}
    50154699
    50164700void PlaybackBox::setPlayGroup(QString newPlayGroup)
    void PlaybackBox::setPlayGroup(QString newPlayGroup) 
    50344718            {
    50354719                RecordingInfo ri(*tmpItem);
    50364720                ri.ApplyRecordPlayGroupChange(newPlayGroup);
    5037                 *tmpItem = ri;
    50384721            }
    50394722        }
    50404723        doClearPlaylist();
    void PlaybackBox::setPlayGroup(QString newPlayGroup) 
    50434726    {
    50444727        RecordingInfo ri(*tmpItem);
    50454728        ri.ApplyRecordPlayGroupChange(newPlayGroup);
    5046         *tmpItem = ri;
    50474729    }
    50484730}
    50494731
    bool PlaybackBox::PbbJobQueue::IsJobQueuedOrRunning(int jobType, uint chanid, 
    56425324        IsJobRunning(jobType, chanid, recstartts);
    56435325}
    56445326
     5327////////////////////////////////////////////////////////
     5328
     5329ProgramInfo* WatchGroup::GetFirst() const
     5330{
     5331    // Use earliest episode of the 3 variants
     5332    ProgramInfo* earliest = m_season;
     5333
     5334    if (m_episode && (!earliest || m_episode->GetScheduledStartTime()
     5335                      < earliest->GetScheduledStartTime()))
     5336        earliest = m_episode;
     5337
     5338    if (m_date && (!earliest || m_date->GetScheduledStartTime()
     5339                   < earliest->GetScheduledStartTime()))
     5340        earliest = m_date;
     5341
     5342    return earliest;
     5343}
     5344
     5345/*!
     5346 * \brief Insert episode into watchlist
     5347 * \param current Current earliest episode of a variant
     5348 * \param p Episode being added
     5349 */
     5350void WatchGroup::Add(ProgramInfo* &current, ProgramInfo* p)
     5351{
     5352    ++m_count;
     5353
     5354    uint      ps(p->GetSeason());
     5355    uint      pe(p->GetEpisode());
     5356    QDateTime pt(p->GetScheduledStartTime());
     5357
     5358    if (!current)
     5359    {
     5360        // first episode with this title
     5361        current = p;
     5362
     5363        LOG(VB_GUI, LOG_DEBUG,
     5364            QString("Watchlist: New      - %1 %2 (%3x%4)")
     5365            .arg(MythDate::toString(pt, Qt::ISODate))
     5366            .arg(p->GetTitle()).arg(ps).arg(pe));
     5367        return;
     5368    }
     5369
     5370    // duplicate title
     5371    uint      cs(current->GetSeason());
     5372    uint      ce(current->GetEpisode());
     5373    QDateTime ct(current->GetScheduledStartTime());
     5374
     5375    // succeed earliest if;
     5376    // - earlier season, or
     5377    // - same season, both with episodes and earlier episode, or
     5378    // - same season, at least one episode undefined and earlier recording
     5379    if (ps < cs
     5380            || (ps == cs
     5381                && ((pe > 0 && ce > 0 && pe < ce)
     5382                    || ((pe == 0 || ce == 0) && pt < ct))))
     5383    {
     5384        // replace existing episode
     5385        current = p;
     5386
     5387        LOG(VB_GUI, LOG_DEBUG,
     5388            QString("Watchlist: Replace  - %1 %2 (%3x%4) "
     5389                    "succeeding %5 (%6x%7)")
     5390            .arg(MythDate::toString(pt, Qt::ISODate))
     5391            .arg(p->GetTitle())
     5392            .arg(ps).arg(pe)
     5393            .arg(MythDate::toString(ct, Qt::ISODate))
     5394            .arg(cs).arg(ce));
     5395    }
     5396    else
     5397        LOG(VB_GUI, LOG_DEBUG,
     5398            QString("Watchlist: Earlier  - %1 %2 (%3x%4)")
     5399            .arg(MythDate::toString(pt, Qt::ISODate))
     5400            .arg(p->GetTitle())
     5401            .arg(ps).arg(pe));
     5402}
     5403
     5404WatchList::WatchList()
     5405    : m_list(),
     5406      m_watchListAutoExpire(gCoreContext->GetNumSetting("PlaybackWLAutoExpire", 0)),
     5407      m_watchListMaxAge(gCoreContext->GetNumSetting("PlaybackWLMaxAge", 60)),
     5408      m_watchListBlackOut(gCoreContext->GetNumSetting("PlaybackWLBlackOut", 2)),
     5409      m_watchListRecentLimit(gCoreContext->GetNumSetting("PlaybackWLRecentLimit", 2)),
     5410      m_watchListOldLimit(gCoreContext->GetNumSetting("PlaybackWLOldLimit", 30)),
     5411      m_watchListStrategy(gCoreContext->GetSetting("PlaybackWLOrder", "Classic"))
     5412{}
     5413
     5414// Group titles ignore case, punctuation & whitespace
     5415QString WatchList::GroupOf(const ProgramInfo &pginfo)
     5416{
     5417    QRegExp chaff("\\W");
     5418    return pginfo.GetTitle().remove(chaff).toLower();
     5419}
     5420
     5421/// Add episode to watchlist
     5422void WatchList::Add(ProgramInfo *p)
     5423{
     5424    if (!p)
     5425        return;
     5426
     5427    if (p->IsWatched())
     5428    {
     5429        // Ignore watched
     5430        LOG(VB_GUI, LOG_DEBUG,
     5431            QString("Watchlist: Watched  - %1 %2 (%3x%4)")
     5432            .arg(MythDate::toString(p->GetScheduledStartTime(), Qt::ISODate))
     5433            .arg(p->GetTitle())
     5434            .arg(p->GetSeason())
     5435            .arg(p->GetEpisode()));
     5436    }
     5437    else if (m_watchListAutoExpire && !p->IsAutoExpirable())
     5438    {
     5439        LOG(VB_GUI, LOG_DEBUG,
     5440            QString("Watchlist: No expire - %1 %2 (%3x%4)")
     5441            .arg(MythDate::toString(p->GetScheduledStartTime(),
     5442                                    Qt::ISODate))
     5443            .arg(p->GetTitle())
     5444            .arg(p->GetSeason())
     5445            .arg(p->GetEpisode()));
     5446    }
     5447    else
     5448    {
     5449        // Create group if it doesn't exist
     5450        WatchGroup &group = m_list[GroupOf(*p)];
     5451
     5452        // Process according to variant
     5453        if (p->GetSeason() > 0)
     5454            group.AddSeason(p);
     5455        else if (p->GetEpisode() > 0)
     5456            group.AddEpisode(p);
     5457        else
     5458            group.AddDate(p);
     5459    }
     5460}
     5461
     5462/// Return sorted watchlist
     5463WatchList::ProgramOrder WatchList::Order()
     5464{
     5465     if (m_watchListStrategy == "Classic")
     5466         return OrderByClassicStrategy();
     5467
     5468     if (m_watchListStrategy == "LimitedOldest")
     5469         return OrderByOldestStrategy();
     5470
     5471     return ProgramOrder();
     5472}
     5473
     5474/// Sort programs by original Watchlist rules
     5475WatchList::ProgramOrder WatchList::OrderByClassicStrategy()
     5476{
     5477    QDateTime now = MythDate::current();
     5478    int baseValue = m_watchListMaxAge * 2 / 3;
     5479
     5480    QMap<int, int> recType;
     5481    QMap<int, int> maxEpisodes;
     5482    QMap<int, int> avgDelay;
     5483    QMap<int, int> spanHours;
     5484    QMap<int, int> delHours;
     5485    QMap<int, int> nextHours;
     5486
     5487    MSqlQuery query(MSqlQuery::InitCon());
     5488    query.prepare("SELECT recordid, type, maxepisodes, avg_delay, "
     5489                  "next_record, last_record, last_delete FROM record;");
     5490
     5491    if (query.exec())
     5492    {
     5493        while (query.next())
     5494        {
     5495            int recid = query.value(0).toInt();
     5496            recType[recid] = query.value(1).toInt();
     5497            maxEpisodes[recid] = query.value(2).toInt();
     5498            avgDelay[recid] = query.value(3).toInt();
     5499
     5500            QDateTime next_record =
     5501                    MythDate::as_utc(query.value(4).toDateTime());
     5502            QDateTime last_record =
     5503                    MythDate::as_utc(query.value(5).toDateTime());
     5504            QDateTime last_delete =
     5505                    MythDate::as_utc(query.value(6).toDateTime());
     5506
     5507            // Time between the last and next recordings
     5508            spanHours[recid] = 1000;
     5509            if (last_record.isValid() && next_record.isValid())
     5510                spanHours[recid] =
     5511                        last_record.secsTo(next_record) / 3600 + 1;
     5512
     5513            // Time since the last episode was deleted
     5514            delHours[recid] = 1000;
     5515            if (last_delete.isValid())
     5516                delHours[recid] = last_delete.secsTo(now) / 3600 + 1;
     5517
     5518            // Time until the next recording if any
     5519            if (next_record.isValid())
     5520                nextHours[recid] = now.secsTo(next_record) / 3600 + 1;
     5521        }
     5522    }
     5523
     5524    ProgramOrder ordered;
     5525    foreach (const WatchGroup &group, m_list)
     5526    {
     5527        ProgramInfo* p = group.GetFirst();
     5528        uint score = 0;
     5529        int recid = p->GetRecordingRuleID();
     5530        int avgd =  avgDelay[recid];
     5531
     5532        if (avgd == 0)
     5533            avgd = 100;
     5534
     5535        // Set the intervals beyond range if there is no record entry
     5536        if (spanHours[recid] == 0)
     5537        {
     5538            spanHours[recid] = 1000;
     5539            delHours[recid] = 1000;
     5540        }
     5541
     5542        // add point equal to baseValue for each additional episode
     5543        if (recid && maxEpisodes[recid] == 0)
     5544            score += (group.GetCount() - 1) * baseValue;
     5545
     5546        // add points every 3hr leading up to the next recording
     5547        if (nextHours[recid] > 0 && nextHours[recid] < baseValue * 3)
     5548            score += (baseValue * 3 - nextHours[recid]) / 3;
     5549
     5550        int hrs = p->GetScheduledEndTime().secsTo(now) / 3600;
     5551        if (hrs < 1)
     5552            hrs = 1;
     5553
     5554        // add points for a new recording that decrease each hour
     5555        if (hrs < 42)
     5556            score += 42 - hrs;
     5557
     5558        // add points for how close the recorded time of day is to 'now'
     5559        score += abs((hrs % 24) - 12) * 2;
     5560
     5561        // Daily
     5562        if (spanHours[recid] < 50 ||
     5563                recType[recid] == kDailyRecord)
     5564        {
     5565            if (delHours[recid] < m_watchListBlackOut * 4)
     5566            {
     5567                LOG(VB_GUI, LOG_DEBUG,
     5568                    QString("Watchlist: Recently deleted daily:  %1")
     5569                    .arg(p->GetTitle()));
     5570
     5571                continue;
     5572            }
     5573            else
     5574            {
     5575                LOG(VB_GUI, LOG_DEBUG, QString("Watchlist: Daily interval:  %1")
     5576                    .arg(p->GetTitle()));
     5577
     5578                if (maxEpisodes[recid] > 0)
     5579                    score += (baseValue / 2) + (hrs / 24);
     5580                else
     5581                    score += (baseValue / 5) + hrs;
     5582            }
     5583        }
     5584        // Weekly
     5585        else if (nextHours[recid] ||
     5586                 recType[recid] == kWeeklyRecord)
     5587
     5588        {
     5589            if (delHours[recid] < (m_watchListBlackOut * 24) - 4)
     5590            {
     5591                LOG(VB_GUI, LOG_DEBUG,
     5592                    QString("Watchlist: Recently deleted weekly:  %1")
     5593                    .arg(p->GetTitle()));
     5594
     5595                continue;
     5596            }
     5597            else
     5598            {
     5599                LOG(VB_GUI, LOG_DEBUG, QString("Watchlist: Weekly interval: %1")
     5600                    .arg(p->GetTitle()));
     5601
     5602                if (maxEpisodes[recid] > 0)
     5603                    score += (baseValue / 2) + (hrs / 24);
     5604                else
     5605                    score += (baseValue / 3) + (baseValue * hrs / 24 / 4);
     5606            }
     5607        }
     5608        // Not recurring
     5609        else
     5610        {
     5611            if (delHours[recid] < (m_watchListBlackOut * 48) - 4)
     5612            {
     5613                continue;
     5614            }
     5615            else
     5616            {
     5617                // add points for a new Single or final episode
     5618                if (hrs < 36)
     5619                    score += baseValue * (36 - hrs) / 36;
     5620
     5621                if (avgd != 100)
     5622                {
     5623                    if (maxEpisodes[recid] > 0)
     5624                        score += (baseValue / 2) + (hrs / 24);
     5625                    else
     5626                        score += (baseValue / 3) + (baseValue * hrs / 24 / 4);
     5627                }
     5628                else if ((hrs / 24) < m_watchListMaxAge)
     5629                    score += hrs / 24;
     5630                else
     5631                    score += m_watchListMaxAge;
     5632            }
     5633        }
     5634
     5635        // Factor based on the average time shift delay.
     5636        // Scale the avgd range of 0 thru 200 hours to 133% thru 67%
     5637        int delaypct = avgd / 3 + 67;
     5638
     5639        if (avgd < 100)
     5640            score = score * (200 - delaypct) / 100;
     5641        else if (avgd > 100)
     5642            score = score * 100 / delaypct;
     5643
     5644        // use score as primary key in top 32 bits,
     5645        // use age in secs as a secondary key in low 32 bits to ensure equal
     5646        // scores are ordered oldest first. Copes with progs up to 136 yrs old
     5647        score_type longScore = (static_cast<score_type>(score) << 32)
     5648                | p->GetScheduledStartTime().secsTo(now);
     5649
     5650        ordered.insert(longScore, p);
     5651
     5652        LOG(VB_GUI, LOG_DEBUG, QString("Watchlist:%1 %2 %3 %4")
     5653            .arg(score, 5)
     5654            .arg(longScore, 12)
     5655            .arg(p->GetTitle())
     5656            .arg(MythDate::toString(p->GetScheduledStartTime(),
     5657                                    MythDate::kDateShort)));
     5658    }
     5659    return ordered;
     5660}
     5661
     5662/// Sort programs oldest first, with new at top and relegating oldest
     5663WatchList::ProgramOrder WatchList::OrderByOldestStrategy()
     5664{
     5665    QDateTime now = MythDate::current();
     5666
     5667    QMap<int, int> avgDelay;
     5668    QMap<int, QDateTime> lastDelete;
     5669
     5670    MSqlQuery query(MSqlQuery::InitCon());
     5671    query.prepare("SELECT recordid, avg_delay, last_delete FROM record;");
     5672
     5673    if (query.exec())
     5674    {
     5675        while (query.next())
     5676        {
     5677            int recid = query.value(0).toInt();
     5678            avgDelay[recid] = query.value(1).toInt();
     5679            lastDelete[recid] = query.value(2).toDateTime();
     5680        }
     5681    }
     5682
     5683    ProgramOrder ordered;
     5684    foreach (const WatchGroup &group, m_list)
     5685    {
     5686        ProgramInfo* p = group.GetFirst();
     5687
     5688        int recid = p->GetRecordingRuleID();
     5689        bool knownRule = avgDelay.contains(recid);
     5690
     5691        // use priority as primary key in top 32 bits:
     5692        // 0 = bottom, 1 = middle, 2 = top
     5693        // use age in secs as a secondary key in low 32 bits to ensure equal
     5694        // scores are ordered oldest first. Copes with progs up to 136 yrs old
     5695        score_type score = p->GetScheduledStartTime().secsTo(now);
     5696
     5697        // put new shows or those from rules that are watched quickly, at the top
     5698        int ageInHours = p->GetScheduledEndTime().secsTo(now) / 3600;
     5699        if (ageInHours <= m_watchListRecentLimit
     5700                || (knownRule && avgDelay[recid] <= m_watchListRecentLimit))
     5701        {
     5702            score |= 0x0200000000;
     5703
     5704            LOG(VB_GUI, LOG_DEBUG,
     5705                QString("Watchlist: Top   :%1 - '%2' was recorded %3 hrs "
     5706                        "ago & being watched after %4 hrs")
     5707                .arg(score, 11)
     5708                .arg(p->GetTitle())
     5709                .arg(ageInHours)
     5710                .arg(avgDelay[recid]));
     5711        }
     5712        // put part-watched shows at top
     5713        else if (p->GetProgressPercent() > 0)
     5714        {
     5715            score |= 0x0200000000;
     5716
     5717            LOG(VB_GUI, LOG_DEBUG,
     5718                QString("Watchlist: Top   :%1 - '%2' is %3% watched")
     5719                .arg(score, 11)
     5720                .arg(p->GetTitle())
     5721                .arg(p->GetProgressPercent()));
     5722        }
     5723        // shows go to middle if not yet old enough
     5724        else if (ageInHours / 24 <= m_watchListOldLimit)
     5725        {
     5726            score |= 0x0100000000;
     5727
     5728            LOG(VB_GUI, LOG_DEBUG,
     5729                QString("Watchlist: Middle:%1 - '%2' was recorded %3 hrs ago")
     5730                .arg(score, 11)
     5731                .arg(p->GetTitle())
     5732                .arg(ageInHours));
     5733        }
     5734        // or a previous episode for the rule was deleted recently
     5735        else if (knownRule)
     5736        {
     5737            QDateTime deleted = MythDate::as_utc(lastDelete[recid]);
     5738            if (deleted.isValid()
     5739                    && deleted.secsTo(now) / 3600 / 24 <= m_watchListOldLimit)
     5740            {
     5741                score |= 0x0100000000;
     5742
     5743                LOG(VB_GUI, LOG_DEBUG,
     5744                    QString("Watchlist: Middle:%1 - '%2' was watched %3 days ago")
     5745                    .arg(score, 11)
     5746                    .arg(p->GetTitle())
     5747                    .arg(deleted.secsTo(now) / 3600 / 24));
     5748            }
     5749        }
     5750        else
     5751            LOG(VB_GUI, LOG_DEBUG,
     5752                QString("Watchlist: Bottom:%1 - '%2' is %3 hrs old")
     5753                .arg(score, 11)
     5754                .arg(p->GetTitle())
     5755                .arg(ageInHours));
     5756
     5757        ordered.insert(score, p);
     5758    }
     5759    return ordered;
     5760}
     5761
    56455762/* vim: set expandtab tabstop=4 shiftwidth=4: */
  • mythtv/programs/mythfrontend/playbackbox.h

    diff --git a/mythtv/programs/mythfrontend/playbackbox.h b/mythtv/programs/mythfrontend/playbackbox.h
    index 280a516..b01d13a 100644
    a b enum { 
    5757    kArtworkCoverTimeout  = 50,
    5858};
    5959
     60class WatchGroup
     61{
     62public:
     63    WatchGroup() : m_count(0), m_season(NULL), m_episode(NULL), m_date(NULL) {}
     64
     65    ProgramInfo* GetFirst() const;
     66    uint    GetCount() const { return m_count; }
     67    QString GetTotal() const { return m_count > 1 ? QString::number(m_count) : ""; }
     68
     69    void AddSeason(ProgramInfo *pginfo)  { Add(m_season,  pginfo); }
     70    void AddEpisode(ProgramInfo *pginfo) { Add(m_episode, pginfo); }
     71    void AddDate(ProgramInfo *pginfo)    { Add(m_date,    pginfo); }
     72
     73private:
     74    void Add(ProgramInfo *&current, ProgramInfo *pginfo);
     75
     76    /// Number of episodes with this title
     77    uint m_count;
     78    /// Oldest episode with a season defined
     79    ProgramInfo *m_season;
     80    /// Oldest episode with only an episode defined
     81    ProgramInfo *m_episode;
     82    /// Oldest episode with no season or season defined
     83    ProgramInfo *m_date;
     84};
     85
     86class WatchList
     87{
     88public:
     89    static QString GroupOf(const ProgramInfo &pginfo);
     90
     91    WatchList();
     92
     93    void Clear()       { m_list.clear(); }
     94    bool Empty() const { return m_list.empty(); }
     95    QString GetTotal(const ProgramInfo &pginfo) const
     96    { return m_list[GroupOf(pginfo)].GetTotal(); }
     97    void Add(ProgramInfo *p);
     98
     99    typedef unsigned long long score_type; // 64 bit
     100    // shows keyed by score
     101    typedef QMultiMap<score_type, ProgramInfo*> ProgramOrder;
     102    ProgramOrder Order();
     103
     104private:
     105    QMap<QString, WatchGroup> m_list;
     106    /// exclude recording not marked for auto-expire from the Watch List
     107    bool    m_watchListAutoExpire;
     108    /// add 1 to the Watch List score up to this many days
     109    int     m_watchListMaxAge;
     110    /// adjust exclusion of a title from the Watch List after a delete
     111    int     m_watchListBlackOut;
     112    /// recordings younger than this (hrs) go to the top
     113    int     m_watchListRecentLimit;
     114    /// recordings older than this got to the bottom
     115    int     m_watchListOldLimit;
     116    /// strategy for ordering the watchlist
     117    QString m_watchListStrategy;
     118
     119    ProgramOrder OrderByClassicStrategy();
     120    ProgramOrder OrderByOldestStrategy();
     121};
     122
    60123class PlaybackBox : public ScheduleCommon
    61124{
    62125    Q_OBJECT
    class PlaybackBox : public ScheduleCommon 
    364427    bool                m_useRecGroups;
    365428    /// use the Watch List as the initial view
    366429    bool                m_watchListStart;
    367     /// exclude recording not marked for auto-expire from the Watch List
    368     bool                m_watchListAutoExpire;
    369     /// add 1 to the Watch List scord up to this many days
    370     int                 m_watchListMaxAge;
    371     /// adjust exclusion of a title from the Watch List after a delete
    372     int                 m_watchListBlackOut;
    373430    /// allOrder controls the ordering of the "All Programs" list
    374431    int                 m_allOrder;
    375432    /// listOrder controls the ordering of the recordings in the list
    class PlaybackBox : public ScheduleCommon 
    384441    QString             m_watchGroupLabel;
    385442    ViewMask            m_viewMask;
    386443
     444    // Watchlist support
     445    WatchList            m_watchlist;
     446
    387447    // Popup support //////////////////////////////////////////////////////////
    388448    // General popup support
    389449    MythDialogBox      *m_menuDialog;
    class PlaybackBox : public ScheduleCommon 
    412472    /// Recording[s] currently selected for deletion
    413473    QStringList m_delList;
    414474    /// Group currently selected
    415     QString m_currentGroup;
     475    QString m_currentGroup; // in lower case
    416476
    417477    // Play List support
    418478    QList<uint>         m_playList;   ///< list of selected items "play list"
  • mythtv/programs/mythfrontend/progdetails.cpp

    diff --git a/mythtv/programs/mythfrontend/progdetails.cpp b/mythtv/programs/mythfrontend/progdetails.cpp
    index 3c07ed1..3204624 100644
    a b void ProgDetails::loadPage(void) 
    589589    QString lastRecorded;
    590590    QString nextRecording;
    591591    QString averageTimeShift;
    592     QString watchListScore;
    593     QString watchListStatus;
    594592    QString searchPhrase;
    595593
    596594    if (m_progInfo.GetRecordingRuleID())
    void ProgDetails::loadPage(void) 
    619617                averageTimeShift = tr("%n hour(s)", "",
    620618                                                query.value(2).toInt());
    621619        }
    622         if (recorded)
    623         {
    624             if (m_progInfo.GetRecordingPriority2() > 0)
    625                 watchListScore =
    626                     QString::number(m_progInfo.GetRecordingPriority2());
    627 
    628             if (m_progInfo.GetRecordingPriority2() < 0)
    629             {
    630                 switch (m_progInfo.GetRecordingPriority2())
    631                 {
    632                     case wlExpireOff:
    633                         watchListStatus = tr("Auto-expire off");
    634                         break;
    635                     case wlWatched:
    636                         watchListStatus = tr("Marked as 'watched'");
    637                         break;
    638                     case wlEarlier:
    639                         watchListStatus = tr("Not the earliest episode");
    640                         break;
    641                     case wlDeleted:
    642                         watchListStatus = tr("Recently deleted episode");
    643                         break;
    644                 }
    645             }
    646         }
    647620        if (record->m_searchType != kManualSearch &&
    648621            record->m_description != m_progInfo.GetDescription())
    649622        {
    void ProgDetails::loadPage(void) 
    655628    addItem("LAST_RECORDED", tr("Last Recorded"), lastRecorded);
    656629    addItem("NEXT_RECORDING", tr("Next Recording"), nextRecording);
    657630    addItem("AVERAGE_TIME_SHIFT", tr("Average Time Shift"), averageTimeShift);
    658     addItem("WATCH_LIST_SCORE", tr("Watch List Score"), watchListScore);
    659     addItem("WATCH_LIST_STATUS", tr("Watch List Status"), watchListStatus);
     631    // Blank removed labels until all themes have removed it
     632    addItem("WATCH_LIST_SCORE", "","");
     633    addItem("WATCH_LIST_STATUS", "","");
    660634    addItem("SEARCH_PHRASE", tr("Search Phrase"), searchPhrase);
    661635
    662636    s.clear();
  • mythtv/programs/mythfrontend/programinfocache.cpp

    diff --git a/mythtv/programs/mythfrontend/programinfocache.cpp b/mythtv/programs/mythfrontend/programinfocache.cpp
    index 5a8c59f..f217f09 100644
    a b uint32_t ProgramInfoCache::Update(const ProgramInfo& pginfo) 
    248248    if (pginfo.GetRecordingGroup() != pg.GetRecordingGroup())
    249249        flags |= PIC_RECGROUP_CHANGED;
    250250
     251    if (pginfo.GetSeason() != pg.GetSeason()
     252            || pginfo.GetEpisode() != pg.GetEpisode()
     253            || pginfo.GetTitle() != pg.GetTitle())
     254        flags |= PIC_WATCHLIST_CHANGED;
     255
     256    if (pg.GetProgressPercent() > 0)
     257        flags |= PIC_PART_WATCHED;
     258
    251259    pg.clone(pginfo, true);
    252260    pg.SetAllowLastPlayPos(true);
    253261
    void ProgramInfoCache::UpdateFileSize(uint recordingId, uint64_t filesize, 
    295303        QString byWhom;
    296304        if (pg.QueryIsInUse(byWhom) && byWhom.contains(QObject::tr("Playing")))
    297305            flags &= ~PIC_MARK_CHANGED;
     306
     307        // Changing to or from part-watched may affect watchlist
     308        if ((pg.GetProgressPercent() == 0) != !(flags & PIC_PART_WATCHED))
     309            flags |= PIC_WATCHLIST_CHANGED;
    298310    }
    299311
    300312    QString mesg = QString("UPDATE_UI_ITEM %1 %2").arg(recordingId).arg(flags);
  • mythtv/programs/mythfrontend/programinfocache.h

    diff --git a/mythtv/programs/mythfrontend/programinfocache.h b/mythtv/programs/mythfrontend/programinfocache.h
    index 4c8d5a4..4dbde6c 100644
    a b typedef enum { 
    2424    PIC_NONE              = 0x00,
    2525    PIC_MARK_CHANGED      = 0x01,
    2626    PIC_RECGROUP_CHANGED  = 0x02,
     27    PIC_PART_WATCHED      = 0X04,
     28    PIC_WATCHLIST_CHANGED = 0x08,
    2729    PIC_NO_ACTION         = 0x80,
    2830} UpdateState;
    2931
  • mythtv/themes/MythCenter-wide/htmls/progdetails_page2.html

    diff --git a/mythtv/themes/MythCenter-wide/htmls/progdetails_page2.html b/mythtv/themes/MythCenter-wide/htmls/progdetails_page2.html
    index e35b5fc..98567da 100644
    a b  
    3232    <h1>%LAST_RECORDED_LABEL%</h1> <p>%LAST_RECORDED%</p>
    3333    <h1>%NEXT_RECORDING_LABEL%</h1> <p>%NEXT_RECORDING%</p>
    3434    <h1>%AVERAGE_TIME_SHIFT_LABEL%</h1> <p>%AVERAGE_TIME_SHIFT%</p>
    35     <h1>%WATCH_LIST_SCORE_LABEL%</h1> <p>%WATCH_LIST_SCORE%</p>
    36     <h1>%WATCH_LIST_STATUS_LABEL%</h1> <p>%WATCH_LIST_STATUS%</p>
    3735    <h1>%SEARCH_PHRASE_LABEL%</h1> <p>%SEARCH_PHRASE%</p>
    3836    <h1>%FINDID_LABEL%</h1> <p>%FINDID%</p>
    3937    <h1>%RECORDING_HOST_LABEL%</h1> <p>%RECORDING_HOST%</p>
  • mythtv/themes/MythCenter-wide/recordings-ui.xml

    diff --git a/mythtv/themes/MythCenter-wide/recordings-ui.xml b/mythtv/themes/MythCenter-wide/recordings-ui.xml
    index 569e204..0abbb29 100644
    a b  
    222222                    <textarea name="titlesubtitle" from="buttontext">
    223223                        <area>32,2,656,28</area>
    224224                        <align>vcenter</align>
    225                         <template>%titlesubtitle%% (|progresspercent|%)%</template>
     225                        <template>%title%% (|watchtotal|)%% - "|subtitle|"%</template>
    226226                    </textarea>
    227227                    <textarea name="shortstartdate" from="buttontext">
    228228                        <area>634,2,120,28</area>
     
    271271                    </textarea>
    272272                    <textarea name="titlesubtitle" from="fonts">
    273273                        <area>32,2,656,28</area>
    274                         <template>%titlesubtitle%% (|progresspercent|%)%</template>
     274                        <template>%title%% (|watchtotal|)%% - "|subtitle|"%</template>
    275275                    </textarea>
    276276                    <textarea name="shortstartdate" from="fonts">
    277277                        <area>634,2,120,28</area>
  • mythtv/themes/MythCenter/htmls/progdetails_page2.html

    diff --git a/mythtv/themes/MythCenter/htmls/progdetails_page2.html b/mythtv/themes/MythCenter/htmls/progdetails_page2.html
    index 2fa3ea3..af6199d 100644
    a b  
    3434    <h1>%LAST_RECORDED_LABEL%</h1> <p>%LAST_RECORDED%</p>
    3535    <h1>%NEXT_RECORDING_LABEL%</h1> <p>%NEXT_RECORDING%</p>
    3636    <h1>%AVERAGE_TIME_SHIFT_LABEL%</h1> <p>%AVERAGE_TIME_SHIFT%</p>
    37     <h1>%WATCH_LIST_SCORE_LABEL%</h1> <p>%WATCH_LIST_SCORE%</p>
    38     <h1>%WATCH_LIST_STATUS_LABEL%</h1> <p>%WATCH_LIST_STATUS%</p>
    3937    <h1>%SEARCH_PHRASE_LABEL%</h1> <p>%SEARCH_PHRASE%</p>
    4038    <h1>%FINDID_LABEL%</h1> <p>%FINDID%</p>
    4139    <h1>%RECORDING_HOST_LABEL%</h1> <p>%RECORDING_HOST%</p>
  • mythtv/themes/MythCenter/recordings-ui.xml

    diff --git a/mythtv/themes/MythCenter/recordings-ui.xml b/mythtv/themes/MythCenter/recordings-ui.xml
    index 5500fd7..3c6e418 100644
    a b  
    212212                    <textarea name="titlesubtitle" from="buttontext">
    213213                        <area>32,0,336,30</area>
    214214                        <align>vcenter</align>
     215                        <template>%title%% (|watchtotal|)%% - "|subtitle|"%</template>
    215216                    </textarea>
    216                     <textarea name="shortstartdate" from="titlesubtitle">
     217                    <textarea name="shortstartdate" from="buttontext">
    217218                        <area>295,0,130,30</area>
    218219                        <align>right,vcenter</align>
    219220                    </textarea>
     
    249250                    <shape name="selectbar">
    250251                        <area>0,0,100%,30</area>
    251252                    </shape>
    252                     <textarea name="titlesubtitle" from="buttontext">
    253                         <area>32,0,336,30</area>
     253                    <textarea name="fonts" from="buttontext">
    254254                        <font>basesmall_normal_selected</font>
    255255                        <font state="disabled">basesmall_disabled_selected</font>
    256256                        <font state="error">basesmall_error_selected</font>
    257257                        <font state="warning">basesmall_warning_selected</font>
    258258                        <font state="normal">basesmall_normal_selected</font>
    259259                        <font state="running">basesmall_running_selected</font>
     260                    </textarea>
     261                    <textarea name="titlesubtitle" from="fonts">
     262                        <area>32,0,336,30</area>
    260263                        <align>vcenter</align>
     264                        <template>%title%% (|watchtotal|)%% - "|subtitle|"%</template>
    261265                    </textarea>
    262                     <textarea name="shortstartdate" from="titlesubtitle">
     266                    <textarea name="shortstartdate" from="fonts">
    263267                        <area>295,0,130,30</area>
    264268                        <align>right,vcenter</align>
    265269                    </textarea>
    266                     <textarea name="starttime" from="shortstartdate">
     270                    <textarea name="starttime" from="fonts">
    267271                        <area>415,0,114,30</area>
    268272                        <align>right,vcenter</align>
    269273                    </textarea>
  • mythtv/themes/Terra/htmls/progdetails_page2.html

    diff --git a/mythtv/themes/Terra/htmls/progdetails_page2.html b/mythtv/themes/Terra/htmls/progdetails_page2.html
    index a1426d6..02963cd 100644
    a b  
    3636    <h1>%LAST_RECORDED_LABEL%</h1> <p>%LAST_RECORDED%</p>
    3737    <h1>%NEXT_RECORDING_LABEL%</h1> <p>%NEXT_RECORDING%</p>
    3838    <h1>%AVERAGE_TIME_SHIFT_LABEL%</h1> <p>%AVERAGE_TIME_SHIFT%</p>
    39     <h1>%WATCH_LIST_SCORE_LABEL%</h1> <p>%WATCH_LIST_SCORE%</p>
    40     <h1>%WATCH_LIST_STATUS_LABEL%</h1> <p>%WATCH_LIST_STATUS%</p>
    4139    <h1>%SEARCH_PHRASE_LABEL%</h1> <p>%SEARCH_PHRASE%</p>
    4240    <h1>%FINDID_LABEL%</h1> <p>%FINDID%</p>
    4341    <h1>%RECORDING_HOST_LABEL%</h1> <p>%RECORDING_HOST%</p>
  • mythtv/themes/Terra/recordings-ui.xml

    diff --git a/mythtv/themes/Terra/recordings-ui.xml b/mythtv/themes/Terra/recordings-ui.xml
    index 195ae08..9f160d8 100644
    a b  
    134134                            </imagetype>
    135135                        </state>
    136136                    </statetype>
     137                    <textarea name="watchtotal" from="basetextarea">
     138                        <area>130,121,120,25</area>
     139                        <align>right</align>
     140                    </textarea>
    137141                    <textarea name="title" from="basetextarea">
    138142                        <area>8,148,240,60</area>
    139143                        <font>basemedium</font>
     
    166170                    <statetype name="status">
    167171                        <position>236,124</position>
    168172                    </statetype>
     173                    <textarea name="watchtotal">
     174                        <area>158,144,120,25</area>
     175                    </textarea>
    169176                    <textarea name="title">
    170177                        <area>8,169,272,73</area>
    171178                        <font>baselarge</font>
  • mythtv/themes/default/htmls/progdetails_page2.html

    diff --git a/mythtv/themes/default/htmls/progdetails_page2.html b/mythtv/themes/default/htmls/progdetails_page2.html
    index 40fb94b..4a17980 100644
    a b  
    3333    <h1>%LAST_RECORDED_LABEL%</h1> <p>%LAST_RECORDED%</p>
    3434    <h1>%NEXT_RECORDING_LABEL%</h1> <p>%NEXT_RECORDING%</p>
    3535    <h1>%AVERAGE_TIME_SHIFT_LABEL%</h1> <p>%AVERAGE_TIME_SHIFT%</p>
    36     <h1>%WATCH_LIST_SCORE_LABEL%</h1> <p>%WATCH_LIST_SCORE%</p>
    37     <h1>%WATCH_LIST_STATUS_LABEL%</h1> <p>%WATCH_LIST_STATUS%</p>
    3836    <h1>%SEARCH_PHRASE_LABEL%</h1> <p>%SEARCH_PHRASE%</p>
    3937    <h1>%FINDID_LABEL%</h1> <p>%FINDID%</p>
    4038    <h1>%RECORDING_HOST_LABEL%</h1> <p>%RECORDING_HOST%</p>