Index: mythtv/themes/classic/tv_settings.xml
===================================================================
--- mythtv/themes/classic/tv_settings.xml	(revision 20301)
+++ mythtv/themes/classic/tv_settings.xml	(working copy)
@@ -81,6 +81,12 @@
     </button>
 
     <button>
+        <type>TV_SETTINGS_CHANNEL_GROUP</type>
+        <text>Channel Groups</text>
+        <action>SETTINGS CHANNELGROUPS</action>
+    </button>
+
+    <button>
         <type>TV_SETTINGS_PLAYBACK_GROUPS</type>
         <text>Playback Groups</text>
         <text lang="IT">Gruppi di Riproduzione</text>
Index: mythtv/themes/DVR/tv_settings.xml
===================================================================
--- mythtv/themes/DVR/tv_settings.xml	(revision 20301)
+++ mythtv/themes/DVR/tv_settings.xml	(working copy)
@@ -84,6 +84,12 @@
     </button>
 
     <button>
+        <type>TV_SETTINGS_CHANNEL_GROUP</type>
+        <text>Channel Groups</text>
+        <action>SETTINGS CHANNELGROUPS</action>
+    </button>
+
+    <button>
         <type>TV_SETTINGS_PLAYBACK_GROUPS</type>
         <text>Playback Groups</text>
         <text lang="SV">Uppspelningsgrupper</text>
Index: mythtv/themes/defaultmenu/tv_settings.xml
===================================================================
--- mythtv/themes/defaultmenu/tv_settings.xml	(revision 20301)
+++ mythtv/themes/defaultmenu/tv_settings.xml	(working copy)
@@ -95,12 +95,19 @@
         <action>SETTINGS OSD</action>
     </button>
 
-  <button>
+    <button>
         <type>TV_SETTINGS_OSD_MENU_EDITOR</type>
         <text>OSD Menu Editor</text>
         <action>OSDMENUEDITOR</action>
-  </button>
+    </button>
+
     <button>
+        <type>TV_SETTINGS_CHANNEL_GROUP</type>
+        <text>Channel Groups</text>
+        <action>SETTINGS CHANNELGROUPS</action>
+    </button>
+
+    <button>
         <type>TV_SETTINGS_PLAYBACK_GROUPS</type>
         <text>Playback Groups</text>
         <text lang="IT">Gruppi di Riproduzione</text>
Index: mythtv/libs/libmythtv/dbchannelinfo.cpp
===================================================================
--- mythtv/libs/libmythtv/dbchannelinfo.cpp	(revision 20301)
+++ mythtv/libs/libmythtv/dbchannelinfo.cpp	(working copy)
@@ -16,12 +16,12 @@
 DBChannel::DBChannel(
     const QString &_channum, const QString &_callsign,
     uint _chanid, uint _major_chan, uint _minor_chan,
-    uint _favorite, uint _mplexid, bool _visible,
+    uint _mplexid, bool _visible,
     const QString &_name, const QString &_icon) :
     channum(_channum),
     callsign(_callsign), chanid(_chanid),
     major_chan(_major_chan), minor_chan(_minor_chan),
-    favorite(_favorite), mplexid(_mplexid), visible(_visible),
+    mplexid(_mplexid), visible(_visible),
     name(_name), icon(_icon)
 {
     channum.detach();
@@ -39,7 +39,6 @@
     chanid     = other.chanid;
     major_chan = other.major_chan;
     minor_chan = other.minor_chan;
-    favorite   = other.favorite;
     mplexid    = (other.mplexid == 32767) ? 0 : other.mplexid;
     visible    = other.visible;
     name       = other.name; name.detach();
Index: mythtv/libs/libmythtv/channelutil.h
===================================================================
--- mythtv/libs/libmythtv/channelutil.h	(revision 20301)
+++ mythtv/libs/libmythtv/channelutil.h	(working copy)
@@ -168,7 +168,7 @@
     static QString GetVideoFilters(uint sourceid, const QString &channum)
         { return GetChannelValueStr("videofilters", sourceid, channum); }
 
-    static DBChanList GetChannels(uint srcid, bool vis_only, QString grp="");
+    static DBChanList GetChannels(uint srcid, bool vis_only, QString grp="", int changrpid=-1);
     static void    SortChannels(DBChanList &list, const QString &order,
                                 bool eliminate_duplicates = false);
     static void    EliminateDuplicateChanNum(DBChanList &list);
Index: mythtv/libs/libmythtv/dbchannelinfo.h
===================================================================
--- mythtv/libs/libmythtv/dbchannelinfo.h	(revision 20301)
+++ mythtv/libs/libmythtv/dbchannelinfo.h	(working copy)
@@ -21,7 +21,7 @@
     DBChannel(const DBChannel&);
     DBChannel(const QString &_channum, const QString &_callsign,
               uint _chanid, uint _major_chan, uint _minor_chan,
-              uint _favorite, uint _mplexid, bool _visible,
+              uint _mplexid, bool _visible,
               const QString &_name, const QString &_icon);
     DBChannel& operator=(const DBChannel&);
 
@@ -34,7 +34,6 @@
     uint    chanid;
     uint    major_chan;
     uint    minor_chan;
-    uint    favorite;
     uint    mplexid;
     bool    visible;
     QString name;
Index: mythtv/libs/libmythtv/libmythtv.pro
===================================================================
--- mythtv/libs/libmythtv/libmythtv.pro	(revision 20301)
+++ mythtv/libs/libmythtv/libmythtv.pro	(working copy)
@@ -161,8 +161,9 @@
 HEADERS += viewschdiff.h            livetvchain.h
 HEADERS += playgroup.h              progdetails.h
 HEADERS += channelsettings.h        previewgenerator.h
-HEADERS += transporteditor.h
+HEADERS += transporteditor.h        
 HEADERS += myth_imgconvert.h
+HEADERS += channelgroup.h           channelgroupsettings.h
 
 # Remove when everything is switched to MythUI
 HEADERS += proglist_qt.h
@@ -186,6 +187,7 @@
 SOURCES += progdetails.cpp
 SOURCES += channelsettings.cpp      previewgenerator.cpp
 SOURCES += transporteditor.cpp
+SOURCES += channelgroup.cpp         channelgroupsettings.cpp
 
 contains( CONFIG_SWSCALE, yes ) {
     SOURCES += myth_imgconvert.cpp
Index: mythtv/libs/libmythtv/guidegrid.h
===================================================================
--- mythtv/libs/libmythtv/guidegrid.h	(revision 20301)
+++ mythtv/libs/libmythtv/guidegrid.h	(working copy)
@@ -18,6 +18,7 @@
 #include "programinfo.h"
 #include "programlist.h"
 #include "channelutil.h"
+#include "channelgroup.h"
 
 using namespace std;
 
@@ -84,7 +85,8 @@
                           const QString &startChanNum,
                           bool           thread = false,
                           TV            *player = NULL,
-                          bool           allowsecondaryepg = true);
+                          bool           allowsecondaryepg = true,
+                          int           *changrpid = NULL);
 
     DBChanList GetSelection(void) const;
 
@@ -134,9 +136,11 @@
     GuideGrid(MythMainWindow *parent,
               uint chanid = 0, QString channum = "",
               TV *player = NULL, bool allowsecondaryepg = true,
-              const char *name = "GuideGrid");
+              const char *name = "GuideGrid",
+              int changrpid=-1);
    ~GuideGrid();
 
+    int  GetChanGrp(void) {return m_changrpid;}
     void paintEvent(QPaintEvent *);
 
   private slots:
@@ -155,6 +159,7 @@
     void paintPrograms(QPainter *);
     void paintCurrentInfo(QPainter *);
     void paintInfo(QPainter *);
+    void paintChanGroupInfo(QPainter *p);
     void paintVideo(QPainter *);
  
     void resizeImage(QPixmap *, QString);
@@ -180,6 +185,7 @@
     QRect infoRect;
     QRect curInfoRect;
     QRect videoRect;
+    QRect changrpRect;
 
     void fillChannelInfos(bool gotostartchannel = true);
     int  FindChannel(uint chanid, const QString &channum,
@@ -189,10 +195,14 @@
 
     void fillProgramInfos(void);
     void fillProgramRowInfos(unsigned int row);
+    
+    void fillChanGroupInfo(void);
 
     void setStartChannel(int newStartChannel);
 
     void createProgramLabel(int, int);
+    
+    int SelectChannelGroup();
 
     PixmapChannel       *GetChannelInfo(uint chan_idx, int sel = -1);
     const PixmapChannel *GetChannelInfo(uint chan_idx, int sel = -1) const;
@@ -222,7 +232,6 @@
     int m_currentCol;
 
     bool selectState;
-    bool showFavorites;
     bool sortReverse;
     QString channelFormat;
 
@@ -247,6 +256,9 @@
     QTimer *timeCheck;
 
     bool keyDown;
+    
+    int  m_changrpid;
+    ChannelGroupList m_changrplist;
 
     QMutex         jumpToChannelLock;
     JumpToChannel *jumpToChannel;
Index: mythtv/libs/libmythtv/tv_play.h
===================================================================
--- mythtv/libs/libmythtv/tv_play.h	(revision 20301)
+++ mythtv/libs/libmythtv/tv_play.h	(working copy)
@@ -25,6 +25,7 @@
 #include "videoouttypes.h"
 #include "volumebase.h"
 #include "inputinfo.h"
+#include "channelgroup.h"
 
 #include <qobject.h>
 
@@ -266,6 +267,9 @@
     void ToggleMute(PlayerContext*);
 
     void SetNextProgPIPState(PIPState state) { jumpToProgramPIPState = state; }
+    
+    // Channel Groups
+    void SaveChannelGroup(void);
 
     // Used for UDPNotify
     bool HasUDPNotifyEvent(void) const;
@@ -520,7 +524,10 @@
     void FillMenuTimeStretch(   const PlayerContext*, OSDGenericTree*) const;
     void FillMenuSleepMode(     const PlayerContext*, OSDGenericTree*) const;
     bool FillMenuTracks(        const PlayerContext*, OSDGenericTree*, uint type) const;
+    void FillMenuChanGroups(    const PlayerContext*, OSDGenericTree*) const;
 
+    void processChanGroupEntry(QString action);
+
     void UpdateLCD(void);
     bool HandleLCDTimerEvent(void);
     void ShowLCDChannelInfo(const PlayerContext*);
@@ -750,6 +757,12 @@
     QMap<int,int>             recorderPlaybackInfoTimerId;
     QMap<int,ProgramInfo>     recorderPlaybackInfo;
 
+    // Channel favorite group stuff    
+    int channel_group_id;
+    uint browse_changrp;
+    ChannelGroupList m_changrplist;
+    DBChanList m_channellist;
+
     // Network Control stuff
     MythDeque<QString> networkControlCommands;
 
Index: mythtv/libs/libmythtv/guidegrid.cpp
===================================================================
--- mythtv/libs/libmythtv/guidegrid.cpp	(revision 20301)
+++ mythtv/libs/libmythtv/guidegrid.cpp	(working copy)
@@ -34,6 +34,7 @@
 #include "util.h"
 #include "remoteutil.h"
 #include "channelutil.h"
+#include "guidegrid.h"
 #include "cardutil.h"
 
 QWaitCondition epgIsVisibleCond;
@@ -171,10 +172,15 @@
     const QString &channum,
     bool           thread,
     TV            *player,
-    bool           allowsecondaryepg)
+    bool           allowsecondaryepg,
+    int           *changrpid)
 {
     DBChanList channel_changed;
+    int        channel_group  = -1;
 
+    if (changrpid != NULL)
+      channel_group = *changrpid;
+
     //if (thread)
     //    qApp->lock();
 
@@ -182,7 +188,8 @@
 
     GuideGrid *gg = new GuideGrid(gContext->GetMainWindow(),
                                   chanid, channum,
-                                  player, allowsecondaryepg, "guidegrid");
+                                  player, allowsecondaryepg, "guidegrid",
+                                  channel_group);
 
     gg->Show();
 
@@ -210,6 +217,9 @@
     //if (thread)
     //    qApp->lock();
 
+    if (changrpid != NULL)
+      *changrpid = gg->GetChanGrp();
+
     delete gg;
 
     gContext->removeCurrentLocation();
@@ -223,7 +233,7 @@
 GuideGrid::GuideGrid(MythMainWindow *parent,
                      uint chanid, QString channum,
                      TV *player, bool allowsecondaryepg,
-                     const char *name) :
+                     const char *name, int changrpid) :
     MythDialog(parent, name),
     m_player(player),
     using_null_video(false),
@@ -240,6 +250,8 @@
     DISPLAY_TIMES = 30;
     int maxchannel = 0;
     m_currentStartChannel = 0;
+    m_changrpid = changrpid;
+    m_changrplist = ChannelGroup::GetChannelGroups();
 
     m_context = 0;
 
@@ -252,6 +264,7 @@
     infoRect = QRect(0, 0, 0, 0);
     curInfoRect = QRect(0, 0, 0, 0);
     videoRect = QRect(0, 0, 0, 0);
+    changrpRect = QRect(0, 0, 0, 0);
 
     jumpToChannelEnabled =
         gContext->GetNumSetting("EPGEnableJumpToChannel", 1);
@@ -274,7 +287,6 @@
             EmbedTVWindow();
     }
 
-    showFavorites = gContext->GetNumSetting("EPGShowFavorites", 0);
     gridfilltype = gContext->GetNumSetting("EPGFillType", UIGuideType::Alpha);
     if (gridfilltype < (int)UIGuideType::Alpha)
     { // update old settings to new fill types
@@ -331,6 +343,18 @@
             container->SetDrawFontShadow(false);
     }
 
+    container = theme->GetSet("channel_group");
+    if (container)
+    {
+        UITextType *type = (UITextType *)container->GetType("changroup");
+        QString changroup;
+        
+        changroup = ChannelGroup::GetChannelGroupName(m_changrplist, m_changrpid);
+        
+        if (type)
+            type->SetText(changroup);
+    }
+
     channelOrdering = gContext->GetSetting("ChannelOrdering", "channum");
     dateformat = gContext->GetSetting("ShortDateFormat", "ddd d");
     unknownTitle = gContext->GetSetting("UnknownTitle", "Unknown");
@@ -703,6 +727,8 @@
         curInfoRect = area;
     if (name.toLower() == "current_video")
         videoRect = area;
+    if (name.toLower() == "channel_group")
+        changrpRect = area;
 }
 
 PixmapChannel *GuideGrid::GetChannelInfo(uint chan_idx, int sel)
@@ -891,22 +917,9 @@
     m_channelInfoIdx.clear();
     m_currentStartChannel = 0;
 
-    DBChanList channels = ChannelUtil::GetChannels(0, true);
+    DBChanList channels = ChannelUtil::GetChannels(0, true, "", m_changrpid);
     ChannelUtil::SortChannels(channels, channelOrdering, false);
 
-    if (showFavorites)
-    {
-        DBChanList tmp;
-        for (uint i = 0; i < channels.size(); i++)
-        {
-            if (channels[i].favorite)
-                tmp.push_back(channels[i]);
-        }
-
-        if (!tmp.empty())
-            channels = tmp;
-    }
-
     typedef vector<uint> uint_list_t;
     QMap<QString,uint_list_t> channum_to_index_map;
     QMap<QString,uint_list_t> callsign_to_index_map;
@@ -1132,6 +1145,24 @@
     }
 }
 
+void GuideGrid::fillChanGroupInfo(void)
+{
+    LayerSet   *container = NULL;
+    UITextType *type = NULL;
+    
+    container = theme->GetSet("channel_group");
+    if (container)
+    {
+        type = (UITextType *)container->GetType("changroup");
+        QString changroup;
+        
+        changroup = ChannelGroup::GetChannelGroupName(m_changrplist, m_changrpid);
+        
+        if (type)
+            type->SetText(changroup);
+    }
+}
+
 void GuideGrid::fillProgramRowInfos(unsigned int row)
 {
     LayerSet *container = NULL;
@@ -1409,6 +1440,8 @@
         paintPrograms(&p);
     if (r.intersects(curInfoRect))
         paintCurrentInfo(&p);
+    if (r.intersects(changrpRect))
+        paintChanGroupInfo(&p);
 
     // if jumpToChannel has its own rect, use that;
     // otherwise use the date's rect
@@ -1546,6 +1579,30 @@
     p->drawPixmap(dr.topLeft(), pix);
 }
 
+void GuideGrid::paintChanGroupInfo(QPainter *p)
+{
+    QRect dr = changrpRect;
+    QPixmap pix(dr.size());
+    pix.fill(this, dr.topLeft());
+    QPainter tmp(&pix);
+
+    LayerSet *container = NULL;
+    container = theme->GetSet("channel_group");
+    if (container)
+    {
+        container->Draw(&tmp, 1, m_context);
+        container->Draw(&tmp, 2, m_context);
+        container->Draw(&tmp, 3, m_context);
+        container->Draw(&tmp, 4, m_context);
+        container->Draw(&tmp, 5, m_context);
+        container->Draw(&tmp, 6, m_context);
+        container->Draw(&tmp, 7, m_context);
+        container->Draw(&tmp, 8, m_context);
+    }
+    tmp.end();
+    p->drawPixmap(dr.topLeft(), pix);
+} 
+
 bool GuideGrid::paintChannels(QPainter *p)
 {
     QRect cr = channelRect;
@@ -1641,11 +1698,6 @@
         }
 
         QString tmpChannelFormat = channelFormat;
-        if (chinfo->favorite > 0)
-        {
-            tmpChannelFormat.insert(
-                tmpChannelFormat.indexOf('<'), "<MARK:fav>");
-        }
 
         if (unavailable)
         {
@@ -1681,6 +1733,18 @@
         }
     }
 
+    if (m_channelInfos.size() == 0)
+    {
+       // if the user has selected a channel group with no channels
+       // Reset the text and icon. This will display one blank line
+       // to show that the channel group has no channels
+       if (type)
+       {
+         type->SetText(0, "");
+         type->ResetImage(0);
+       }
+    }
+
     if (container)
     {
         container->Draw(&tmp, 1, m_context);
@@ -1816,8 +1880,15 @@
 
 void GuideGrid::toggleGuideListing()
 {
-    showFavorites = (!showFavorites);
-    generateListings();
+    int oldchangrpid = m_changrpid;
+    
+    m_changrpid = ChannelGroup::GetNextChannelGroup(m_changrplist, oldchangrpid);
+    
+    if (oldchangrpid != m_changrpid)
+      generateListings();
+      
+    fillChanGroupInfo();
+    update(changrpRect);
 }
 
 void GuideGrid::generateListings()
@@ -1836,10 +1907,49 @@
     update(fullRect);
 }
 
+int GuideGrid::SelectChannelGroup()
+{
+    if (m_changrplist.empty())
+    {
+      MythPopupBox::showOkPopup(gContext->GetMainWindow(), "",
+                                "You don't have any channel groups defined");
+
+      return -1;
+    }
+    
+    MythPopupBox *popup = new MythPopupBox(gContext->GetMainWindow(), "SelectChannelGroup Popup");
+    popup->addLabel("Select Channel Group");
+
+    for (uint i = 0; i < m_changrplist.size(); i++)
+      popup->addButton(m_changrplist[i].name);
+
+    popup->addButton(tr("Cancel"))->setFocus();
+
+    DialogCode result = popup->ExecPopup();
+   
+    popup->deleteLater();
+
+    // If the user cancelled, return a special value
+    if (result == MythDialog::Rejected)
+      return -1;
+    else 
+      return m_changrplist[result - kDialogCodeListStart].grpid;
+}
+
 void GuideGrid::toggleChannelFavorite()
 {
-    MSqlQuery query(MSqlQuery::InitCon());
+    int grpid;
 
+    if (m_changrpid == -1)
+    {
+      grpid = SelectChannelGroup();
+      
+      if (grpid == -1)
+        return;
+    }
+    else
+      grpid = m_changrpid;
+
     // Get current channel id, and make sure it exists...
     int chanNum = m_currentRow + m_currentStartChannel;
     if (chanNum >= (int)m_channelInfos.size())
@@ -1850,35 +1960,18 @@
         chanNum = 0;
 
     PixmapChannel *ch = GetChannelInfo(chanNum);
-    uint favid  = ch->favorite;
     uint chanid = ch->chanid;
 
-    if (favid > 0) 
-    {
-        query.prepare("DELETE FROM favorites WHERE favid = :FAVID ;");
-        query.bindValue(":FAVID", favid);
-        query.exec(); 
-    }
-    else
-    {
-        // We have no favorites record...Add one to toggle...
-        query.prepare("INSERT INTO favorites (chanid) VALUES (:FAVID);");
-        query.bindValue(":FAVID", chanid);
-        query.exec(); 
-    }
-
-    if (showFavorites)
+    if (m_changrpid == -1)
+        // If currently viewing all channels, allow to add only not delete
+        ChannelGroup::ToggleChannel(chanid, grpid, false);
+     else
+        // Only allow delete if viewing the favorite group in question
+        ChannelGroup::ToggleChannel(chanid, grpid, true);
+      
+    // If viewing favorites, refresh because a channel was removed
+    if (m_changrpid != -1)
         generateListings();
-    else
-    {
-        int maxchannel = 0;
-        DISPLAY_CHANS = desiredDisplayChans;
-        fillChannelInfos(false);
-        maxchannel = max((int)GetChannelCount() - 1, 0);
-        DISPLAY_CHANS = min(DISPLAY_CHANS, maxchannel + 1);
-
-        repaint(channelRect);
-    }
 }
 
 void GuideGrid::cursorLeft()
Index: mythtv/libs/libmythtv/channelutil.cpp
===================================================================
--- mythtv/libs/libmythtv/channelutil.cpp	(revision 20301)
+++ mythtv/libs/libmythtv/channelutil.cpp	(working copy)
@@ -1570,28 +1570,22 @@
     return true;
 }
 
-DBChanList ChannelUtil::GetChannels(uint sourceid, bool vis_only, QString grp)
+DBChanList ChannelUtil::GetChannels(uint sourceid, bool vis_only, QString grp, int changrpid)
 {
     DBChanList list;
-    QMap<uint,uint> favorites;
+    
     MSqlQuery query(MSqlQuery::InitCon());
-    query.prepare(
-        "SELECT chanid, favid "
-        "FROM favorites");
-    if (!query.exec() || !query.isActive())
-        MythDB::DBError("get channels -- favorites", query);
-    else
-    {
-        while (query.next())
-            favorites[query.value(0).toUInt()] = query.value(1).toUInt();
-    }
 
     QString qstr =
-        "SELECT channum, callsign, chanid, "
+        "SELECT channum, callsign, channel.chanid, "
         "       atsc_major_chan, atsc_minor_chan, "
         "       name, icon, mplexid, visible "
         "FROM channel ";
 
+    // Select only channels from the specified channel group
+    if (changrpid > -1)
+        qstr += QString(",channelgroup ");
+
     if (sourceid)
         qstr += QString("WHERE sourceid='%1' ").arg(sourceid);
     else
@@ -1599,6 +1593,12 @@
             "WHERE cardinput.sourceid = channel.sourceid   AND "
             "      cardinput.cardid   = capturecard.cardid     ";
 
+    if (changrpid > -1)
+    {
+        qstr += QString("AND channel.chanid = channelgroup.chanid "
+                        "AND channelgroup.grpid ='%1' ").arg(changrpid);
+    }
+
     if (vis_only)
         qstr += "AND visible=1 ";
 
@@ -1623,7 +1623,6 @@
             query.value(2).toUInt(),                      /* chanid     */
             query.value(3).toUInt(),                      /* ATSC major */
             query.value(4).toUInt(),                      /* ATSC minor */
-            favorites[query.value(2).toUInt()],           /* favid      */
             query.value(7).toUInt(),                      /* mplexid    */
             query.value(8).toBool(),                      /* visible    */
             query.value(5).toString(),                    /* name       */
@@ -1806,7 +1805,7 @@
                 (mplexid_restriction &&
                  (mplexid_restriction != it->mplexid))));
     }
-    else if (CHANNEL_DIRECTION_UP == direction)
+    else if ((CHANNEL_DIRECTION_UP == direction) || (CHANNEL_DIRECTION_FAVORITE == direction))
     {
         do
         {
@@ -1819,20 +1818,6 @@
                 (mplexid_restriction &&
                  (mplexid_restriction != it->mplexid))));
     }
-    else if (CHANNEL_DIRECTION_FAVORITE == direction)
-    {
-        do
-        {
-            it++;
-            if (it == sorted.end())
-                it = sorted.begin();
-        }
-        while ((it != start) &&
-               (!it->favorite ||
-                (skip_non_visible && !it->visible) ||
-                (mplexid_restriction &&
-                 (mplexid_restriction != it->mplexid))));
-    }
 
     return it->chanid;
 }
Index: mythtv/libs/libmythtv/tv_play.cpp
===================================================================
--- mythtv/libs/libmythtv/tv_play.cpp	(revision 20301)
+++ mythtv/libs/libmythtv/tv_play.cpp	(working copy)
@@ -347,6 +347,8 @@
 
     bool allowrerecord = tv->getAllowRerecord();
     bool deleterecording = tv->getRequestDelete();
+    
+    tv->SaveChannelGroup();
 
     delete tv;
 
@@ -422,8 +424,8 @@
             "in the program guide", "0");
     REG_KEY("TV Frontend", "GUIDE", "Show the Program Guide", "S");
     REG_KEY("TV Frontend", "FINDER", "Show the Program Finder", "#");
-    REG_KEY("TV Frontend", "NEXTFAV", "Toggle showing all channels or just "
-            "favorites in the program guide.", "/");
+    REG_KEY("TV Frontend", "NEXTFAV", "Cycle through channel groups and all channels "
+            "in the program guide.", "/,S");
     REG_KEY("TV Frontend", "CHANUPDATE", "Switch channels without exiting "
             "guide in Live TV mode.", "X");
     REG_KEY("TV Frontend", "VOLUMEDOWN", "Volume down", "[,{,F10,Volume Down");
@@ -788,6 +790,8 @@
     osd_general_timeout  = gContext->GetNumSetting("OSDGeneralTimeout", 2);
     osd_prog_info_timeout= gContext->GetNumSetting("OSDProgramInfoTimeout", 3);
     tryUnflaggedSkip     = gContext->GetNumSetting("TryUnflaggedSkip", 0);
+    channel_group_id     = gContext->GetNumSetting("ChannelGroupDefault", -1);
+    browse_changrp       = gContext->GetNumSetting("BrowseChannelGroup", 0);
     smartForward         = gContext->GetNumSetting("SmartForward", 0);
     stickykeys           = gContext->GetNumSetting("StickyKeys");
     ff_rew_repos         = gContext->GetNumSetting("FFRewReposTime", 100)/100.0;
@@ -802,6 +806,17 @@
     if (!feVBI.isEmpty())
         vbimode = VBIMode::Parse(gContext->GetSetting(feVBI));
 
+    channel_group_id     = gContext->GetNumSetting("ChannelGroupDefault", -1);
+    browse_changrp       = gContext->GetNumSetting("BrowseChannelGroup", 0);
+    
+    if (browse_changrp && (channel_group_id > -1))
+    {
+      m_channellist = ChannelUtil::GetChannels(0, true, "channum, callsign", channel_group_id);
+      ChannelUtil::SortChannels(m_channellist, "channum", true);
+    }
+    
+    m_changrplist  = ChannelGroup::GetChannelGroups();
+
     if (createWindow)
     {
         MythMainWindow *mainWindow = gContext->GetMainWindow();
@@ -967,6 +982,15 @@
     VERBOSE(VB_PLAYBACK, "TV::~TV() -- end");
 }
 
+void TV::SaveChannelGroup(void)
+{
+    int changrpid             = gContext->GetNumSetting("ChannelGroupDefault", -1);
+    int remember_last_changrp = gContext->GetNumSetting("ChannelGroupRememberLast", 0);
+ 
+    if (remember_last_changrp && (changrpid != channel_group_id))
+       gContext->SaveSetting("ChannelGroupDefault", channel_group_id); 
+}
+
 /**
  * \brief get tv state of active player context
  */
@@ -6232,8 +6256,8 @@
 
 void TV::ToggleChannelFavorite(PlayerContext *ctx)
 {
-    if (ctx->recorder)
-        ctx->recorder->ToggleChannelFavorite();
+//    if (ctx->recorder)
+//        ctx->recorder->ToggleChannelFavorite();
 }
 
 QString TV::GetQueuedInput(void) const
@@ -6488,6 +6512,29 @@
 {
     bool muted = false;
 
+    if ((browse_changrp || (direction == CHANNEL_DIRECTION_FAVORITE)) && 
+        (channel_group_id > -1) && (direction != CHANNEL_DIRECTION_SAME))
+    {
+        uint chanid;
+       
+        // Collect channel info
+        //DEBUG
+        //pbinfoLock.lock();
+        ctx->LockPlayingInfo(__FILE__, __LINE__);
+        uint old_chanid  = ctx->playingInfo->chanid.toUInt();
+        ctx->LockPlayingInfo(__FILE__, __LINE__);
+//        pbinfoLock.unlock(); 
+ 
+        chanid = ChannelUtil::GetNextChannel(m_channellist, old_chanid, 0, direction);
+ 
+        ChangeChannel(ctx, chanid, "");     
+        return;
+    }
+    else if (direction == CHANNEL_DIRECTION_FAVORITE)
+    {
+        direction = CHANNEL_DIRECTION_UP;
+    }
+
     QString oldinputname = ctx->recorder->GetInput();
 
     ctx->LockDeleteNVP(__FILE__, __LINE__);
@@ -7664,6 +7711,7 @@
     // Actually show the pop-up UI
     DBChanList changeChannel;
     ProgramInfo *nextProgram = NULL;
+    int changrpid = channel_group_id;
     switch (editType)
     {
         case kScheduleProgramGuide:
@@ -7671,7 +7719,7 @@
             TV *player = (pause_active) ? NULL : this;
             changeChannel = GuideGrid::Run(
                 chanid, channum, false, player,
-                isLiveTV && player && allowEPG);
+                isLiveTV && player && allowEPG, &changrpid);
             break;
         }
         case kScheduleProgramFinder:
@@ -7710,6 +7758,22 @@
     actx = GetPlayerReadLock(-1, __FILE__, __LINE__);
     StopEmbedding(actx);               // Undo any embedding
     DoSetPauseState(actx, was_paused); // Restore pause states
+    
+    // if channel group was changed in EPG update local info
+    if ((changrpid != channel_group_id) && (editType == kScheduleProgramGuide))
+    {
+        channel_group_id = changrpid;
+        
+        if (browse_changrp)
+        {
+            VERBOSE(VB_IMPORTANT, LOC + 
+               QString("Reloading channel group list for %1").arg(channel_group_id));
+        
+            m_channellist = ChannelUtil::GetChannels(0, true, "channum, callsign", channel_group_id);
+            ChannelUtil::SortChannels(m_channellist, "channum", true);
+        }
+    }
+
     // If user selected a new channel in the EPG, change to that channel
     if (isLiveTV && changeChannel.size())
         ChangeChannel(actx, changeChannel);
@@ -8559,6 +8623,36 @@
 {
     if (!browsemode)
         BrowseStart(ctx);
+    
+    VERBOSE(VB_IMPORTANT,"In BrowseDispInfo");
+    // if browsing channel groups is enabled or direction if BROWSE_FAVORITES
+    // Then pick the next channel in the channel group list to browse
+    // If channel group is ALL CHANNELS (-1), then bypass picking from 
+    // the channel group list
+    if ((browse_changrp || (direction == BROWSE_FAVORITE)) &&
+        (channel_group_id > -1) && (direction != BROWSE_SAME) &&
+        (direction != BROWSE_RIGHT) && (direction != BROWSE_LEFT))
+    { 
+        uint chanid;
+        int  dir;
+      
+        if ( (direction == BROWSE_UP) || (direction == BROWSE_FAVORITE) )
+            dir = CHANNEL_DIRECTION_UP;
+        else if (direction == BROWSE_DOWN)
+            dir = CHANNEL_DIRECTION_DOWN;
+        else // this should never happen, but just in case
+            dir = direction;
+         
+        chanid = ChannelUtil::GetNextChannel(m_channellist, browsechanid, 0, dir);
+        VERBOSE(VB_IMPORTANT, QString("Get channel: %1").arg(chanid));
+        browsechanid  = chanid;
+        browsechannum = QString::null;
+        direction     = BROWSE_SAME;
+    }
+    else if ((channel_group_id == -1) && (direction == BROWSE_FAVORITE))
+    {
+        direction = BROWSE_UP;
+    }
 
     OSD *osd = GetOSDLock(ctx);
     if (ctx->paused || !osd)
@@ -9463,6 +9557,8 @@
     }
     else if (action == "GUIDE")
         EditSchedule(actx, kScheduleProgramGuide);
+    else if (action.left(10) == "CHANGROUP_")
+        processChanGroupEntry(action);
     else if (action == "FINDER")
         EditSchedule(actx, kScheduleProgramFinder);
     else if (action == "SCHEDULE")
@@ -9569,6 +9665,25 @@
     ReturnPlayerLock(actx);
 }
 
+void TV::processChanGroupEntry(QString action)
+{
+    if (action == "CHANGROUP_ALL_CHANNELS")
+    {
+        channel_group_id = -1;
+    }
+    else
+    {
+        action.remove("CHANGROUP_");
+        channel_group_id = action.toInt();
+          
+        if (browse_changrp)
+        {
+           m_channellist = ChannelUtil::GetChannels(0, true, "channum, callsign", channel_group_id);
+           ChannelUtil::SortChannels(m_channellist, "channum", true);
+        }
+    }
+}
+
 void TV::ShowOSDTreeMenu(const PlayerContext *ctx)
 {
     int osdMenuCount = osdMenuEntries->GetCount();
@@ -9644,6 +9759,8 @@
     }
     else if (category == "GUIDE")
         new OSDGenericTree(treeMenu, tr("Program Guide"), "GUIDE");
+    else if (category == "CHANGROUP")
+        FillMenuChanGroups(ctx, treeMenu);
     else if (category ==  "PIP")
         FillMenuPxP(ctx, treeMenu);
     else if (category == "INPUTSWITCHING")
@@ -10192,6 +10309,26 @@
     return true;
 }
 
+void TV::FillMenuChanGroups(
+    const PlayerContext *ctx, OSDGenericTree *treeMenu) const
+{
+    OSDGenericTree *cg_item = new OSDGenericTree(treeMenu, tr("Channel Groups"), 
+                                                 "CHANGROUP");
+    new OSDGenericTree(cg_item, tr("All Channels"), "CHANGROUP_ALL_CHANNELS",
+                                 (channel_group_id == -1) ? 1 : 0,
+                                 NULL, "CHANNELGROUP");
+
+    ChannelGroupList::const_iterator it;
+       
+    for (it = m_changrplist.begin(); it != m_changrplist.end(); ++it)
+    {
+        QString name = QString("CHANGROUP_%1").arg(it->grpid);
+        new OSDGenericTree(cg_item, it->name, name, 
+                           ((int)(it->grpid) == channel_group_id) ? 1 : 0,
+                           NULL, "CHANNELGROUP");
+    }       
+}
+
 void TV::ToggleAutoExpire(PlayerContext *ctx)
 {
     QString desc = QString::null;
Index: mythtv/libs/libmythtv/remoteencoder.h
===================================================================
--- mythtv/libs/libmythtv/remoteencoder.h	(revision 20301)
+++ mythtv/libs/libmythtv/remoteencoder.h	(working copy)
@@ -50,7 +50,6 @@
         PictureAdjustType type, PictureAttribute attr, bool up);
     void ChangeChannel(int channeldirection);
     void ChangeDeinterlacer(int deint_mode);
-    void ToggleChannelFavorite(void);
     void SetChannel(QString channel);
     int  SetSignalMonitoringRate(int msec, bool notifyFrontend = true);
     uint GetSignalLockTimeout(QString input);
Index: mythtv/libs/libmythtv/tvosdmenuentry.cpp
===================================================================
--- mythtv/libs/libmythtv/tvosdmenuentry.cpp	(revision 20301)
+++ mythtv/libs/libmythtv/tvosdmenuentry.cpp	(working copy)
@@ -192,6 +192,8 @@
     curMenuEntries.append(
         new TVOSDMenuEntry("GUIDE",                     1,  1,  0,  0, "Program Guide"));
     curMenuEntries.append(
+        new TVOSDMenuEntry("CHANGROUP",                     1,  1,  0,  0, "Channel Groups"));
+    curMenuEntries.append(
         new TVOSDMenuEntry("PIP",                          1,  1,  1,  -1, "Picture-in-Picture"));
     curMenuEntries.append(
         new TVOSDMenuEntry("INPUTSWITCHING",   1,  -1,  -1,  -1, "Change TV Input"));
Index: mythtv/libs/libmythtv/remoteencoder.cpp
===================================================================
--- mythtv/libs/libmythtv/remoteencoder.cpp	(revision 20301)
+++ mythtv/libs/libmythtv/remoteencoder.cpp	(working copy)
@@ -402,14 +402,6 @@
     return (lastinput.isEmpty()) ? "Error" : lastinput;
 }
 
-void RemoteEncoder::ToggleChannelFavorite(void)
-{
-    QStringList strlist( QString("QUERY_RECORDER %1").arg(recordernum) );
-    strlist << "TOGGLE_CHANNEL_FAVORITE";
-
-    SendReceiveStringList(strlist);
-}
-
 void RemoteEncoder::ChangeChannel(int channeldirection)
 {
     QStringList strlist( QString("QUERY_RECORDER %1").arg(recordernum) );
Index: mythtv/libs/libmythtv/tv_rec.cpp
===================================================================
--- mythtv/libs/libmythtv/tv_rec.cpp	(revision 20301)
+++ mythtv/libs/libmythtv/tv_rec.cpp	(working copy)
@@ -2915,65 +2915,6 @@
     }
 }
 
-/** \fn TVRec::ToggleChannelFavorite()
- *  \brief Toggles whether the current channel should be on our favorites list.
- */
-void TVRec::ToggleChannelFavorite(void)
-{
-    QMutexLocker lock(&stateChangeLock);
-
-    if (!channel)
-        return;
-
-    // Get current channel id...
-    uint    sourceid = channel->GetCurrentSourceID();
-    QString channum  = channel->GetCurrentName();
-    uint chanid = ChannelUtil::GetChanID(sourceid, channum);
-
-    if (!chanid)
-    {
-        VERBOSE(VB_IMPORTANT, LOC_ERR + QString(
-                "Channel: \'%1\' was not found in the database.\n"
-                "\t\t\tMost likely, your DefaultTVChannel setting is wrong.\n"
-                "\t\t\tCould not toggle favorite.").arg(channum));
-        return;
-    }
-
-    // Check if favorite exists for that chanid...
-    MSqlQuery query(MSqlQuery::InitCon());
-    query.prepare(
-        "SELECT favorites.favid "
-        "FROM favorites "
-        "WHERE favorites.chanid = :CHANID "
-        "LIMIT 1");
-    query.bindValue(":CHANID", chanid);
-
-    if (!query.exec() || !query.isActive())
-    {
-        MythDB::DBError("togglechannelfavorite", query);
-    }
-    else if (query.size() > 0)
-    {
-        // We have a favorites record...Remove it to toggle...
-        query.next();
-        QString favid = query.value(0).toString();
-        query.prepare(
-            QString("DELETE FROM favorites "
-                    "WHERE favid = '%1'").arg(favid));
-        query.exec();
-        VERBOSE(VB_RECORD, LOC + "Removing Favorite.");
-    }
-    else
-    {
-        // We have no favorites record...Add one to toggle...
-        query.prepare(
-            QString("INSERT INTO favorites (chanid) "
-                    "VALUES ('%1')").arg(chanid));
-        query.exec();
-        VERBOSE(VB_RECORD, LOC + "Adding Favorite.");
-    }
-}
-
 /** \fn TVRec::ChangePictureAttribute(PictureAdjustType,PictureAttribute,bool)
  *  \brief Returns current value [0,100] if it succeeds, -1 otherwise.
  *
Index: mythtv/programs/mythfrontend/globalsettings.cpp
===================================================================
--- mythtv/programs/mythfrontend/globalsettings.cpp	(revision 20301)
+++ mythtv/programs/mythfrontend/globalsettings.cpp	(working copy)
@@ -3147,17 +3147,6 @@
     return gc;
 }
 
-static HostCheckBox *EPGShowFavorites()
-{
-    HostCheckBox *gc = new HostCheckBox("EPGShowFavorites");
-    gc->setLabel(QObject::tr("Only display 'favorite' channels"));
-    gc->setHelpText(QObject::tr("If enabled, the EPG will initially display "
-                    "only the channels marked as favorites. Pressing "
-                    "\"4\" will toggle between displaying favorites and all "
-                    "channels."));
-    gc->setValue(false);
-    return gc;
-}
 
 static HostSpinBox *EPGChanDisplay()
 {
@@ -3185,6 +3174,73 @@
     return gc;
 }
 
+static HostCheckBox *ChannelGroupRememberLast()
+{
+    HostCheckBox *gc = new HostCheckBox("ChannelGroupRememberLast");
+    gc->setLabel(QObject::tr("Remember last channel group"));
+    gc->setHelpText(QObject::tr("If enabled, the EPG will initially display "
+                    "only the channels from the last channel group selected. Pressing "
+                    "\"4\" will toggle channel group."));
+    gc->setValue(false);
+    return gc;
+}
+
+static HostComboBox *ChannelGroupDefault()
+{
+    HostComboBox *gc = new HostComboBox("ChannelGroupDefault");
+    gc->setLabel(QObject::tr("Default channel group"));
+
+    ChannelGroupList changrplist;
+
+    changrplist = ChannelGroup::GetChannelGroups();
+
+    gc->addSelection(QObject::tr("All Channels"), "-1");
+
+    ChannelGroupList::iterator it;
+
+    for (it = changrplist.begin(); it < changrplist.end(); ++it)
+       gc->addSelection(it->name, QString("%1").arg(it->grpid));
+
+    gc->setHelpText(QObject::tr("Default channel group to be shown in the the EPG"
+                    "Pressing "
+                    "\"4\" will toggle channel group."));
+    gc->setValue(false);
+    return gc;
+}
+
+static HostCheckBox *BrowseChannelGroup()
+{
+    HostCheckBox *gc = new HostCheckBox("BrowseChannelGroup");
+    gc->setLabel(QObject::tr("Browse/Change channels from Channel Group"));
+    gc->setHelpText(QObject::tr("If enabled, LiveTV will browse or change channels "
+                    "from the selected channel group. \"All Channels\" "
+                    "channel group may be selected to browse all channels."));
+    gc->setValue(false);
+    return gc;
+}
+
+// Channel Group Settings
+class ChannelGroupSettings : public TriggeredConfigurationGroup
+{
+  public:
+    ChannelGroupSettings() : TriggeredConfigurationGroup(false, true, false, false)
+    {
+         setLabel(QObject::tr("Remember last channel group"));
+         setUseLabel(false);
+
+         Setting* RememberChanGrpEnabled = ChannelGroupRememberLast();
+         addChild(RememberChanGrpEnabled);
+         setTrigger(RememberChanGrpEnabled);
+
+         ConfigurationGroup* settings = new VerticalConfigurationGroup(false);
+         settings->addChild(ChannelGroupDefault());
+         addTarget("0", settings);
+
+         // show nothing if RememberChanGrpEnabled is on
+         addTarget("1", new VerticalConfigurationGroup(true));
+     };
+};
+
 // General RecPriorities settings
 
 static GlobalCheckBox *GRSchedMoveHigher()
@@ -4952,32 +5008,37 @@
     general2->addChild(CategoryOverTimeSettings());
     addChild(general2);
 
+    VerticalConfigurationGroup* changrp = new VerticalConfigurationGroup(false);
+    changrp->setLabel(QObject::tr("General (Channel Groups)"));
+    ChannelGroupSettings *changroupsettings = new ChannelGroupSettings();
+    changrp->addChild(changroupsettings);
+    changrp->addChild(BrowseChannelGroup());
+    addChild(changrp);
 }
 
 EPGSettings::EPGSettings()
 {
     VerticalConfigurationGroup* epg = new VerticalConfigurationGroup(false);
-    epg->setLabel(QObject::tr("Program Guide") + " 1/2");
+    epg->setLabel(QObject::tr("Program Guide") + " 1/3");
     epg->addChild(EPGFillType());
     epg->addChild(EPGShowCategoryColors());
     epg->addChild(EPGShowCategoryText());
     epg->addChild(EPGScrollType());
     epg->addChild(EPGShowChannelIcon());
-    epg->addChild(EPGShowFavorites());
     epg->addChild(WatchTVGuide());
     epg->addChild(EPGChanDisplay());
     epg->addChild(EPGTimeDisplay());
     addChild(epg);
 
     VerticalConfigurationGroup* gen = new VerticalConfigurationGroup(false);
-    gen->setLabel(QObject::tr("Program Guide") + " 2/2");
+    gen->setLabel(QObject::tr("Program Guide") + " 2/3");
     gen->addChild(UnknownTitle());
     gen->addChild(UnknownCategory());
     gen->addChild(DefaultTVChannel());
     gen->addChild(SelectChangesChannel());
     gen->addChild(EPGRecThreshold());
     gen->addChild(EPGEnableJumpToChannel());
-    addChild(gen);
+    addChild(gen);    
 }
 
 GeneralRecPrioritiesSettings::GeneralRecPrioritiesSettings()
Index: mythtv/programs/mythfrontend/main.cpp
===================================================================
--- mythtv/programs/mythfrontend/main.cpp	(revision 20301)
+++ mythtv/programs/mythfrontend/main.cpp	(working copy)
@@ -51,6 +51,7 @@
 #include "lcddevice.h"
 #include "langsettings.h"
 #include "mythcommandlineparser.h"
+#include "channelgroupsettings.h"
 
 #include "myththemedmenu.h"
 #include "myththemebase.h"
@@ -522,6 +523,11 @@
         EPGSettings settings;
         settings.exec();
     }
+    else if (sel == "settings channelgroups")
+    {
+        ChannelGroupEditor editor;
+        editor.exec();
+     } 
     else if (sel == "settings generalrecpriorities")
     {
         GeneralRecPrioritiesSettings settings;
Index: mythtv/programs/mythbackend/encoderlink.h
===================================================================
--- mythtv/programs/mythbackend/encoderlink.h	(revision 20301)
+++ mythtv/programs/mythbackend/encoderlink.h	(working copy)
@@ -111,7 +111,6 @@
     vector<InputInfo> GetFreeInputs(const vector<uint> &excluded_cards) const;
     QString GetInput(void) const;
     QString SetInput(QString);
-    void ToggleChannelFavorite(void);
     void ChangeChannel(int channeldirection);
     void SetChannel(const QString &name);
     int  GetPictureAttribute(PictureAttribute attr);
Index: mythtv/programs/mythbackend/mainserver.cpp
===================================================================
--- mythtv/programs/mythbackend/mainserver.cpp	(revision 20301)
+++ mythtv/programs/mythbackend/mainserver.cpp	(working copy)
@@ -3257,11 +3257,6 @@
         ret = (ret.isEmpty()) ? "UNKNOWN" : ret;
         retlist << ret;
     }
-    else if (command == "TOGGLE_CHANNEL_FAVORITE")
-    {
-        enc->ToggleChannelFavorite();
-        retlist << "ok";
-    }
     else if (command == "CHANGE_CHANNEL")
     {
         int direction = slist[2].toInt();
Index: mythtv/programs/mythbackend/encoderlink.cpp
===================================================================
--- mythtv/programs/mythbackend/encoderlink.cpp	(revision 20301)
+++ mythtv/programs/mythbackend/encoderlink.cpp	(working copy)
@@ -723,19 +723,6 @@
     return QString::null;
 }
 
-/** \fn EncoderLink::ToggleChannelFavorite(void)
- *  \brief Toggles whether the current channel should be on our favorites list.
- *         <b>This only works on local recorders.</b>
- *  \return -1 if query does not succeed, otherwise.
- */
-void EncoderLink::ToggleChannelFavorite(void)
-{
-    if (local)
-        tv->ToggleChannelFavorite();
-    else
-        VERBOSE(VB_IMPORTANT, "Should be local only query: ToggleChannelFavorite");
-}
-
 /** \fn EncoderLink::ChangeChannel(int)
  *  \brief Changes to the next or previous channel.
  *         <b>This only works on local recorders.</b>
Index: mythtv/libs/libmythtv/channelgroupsettings.h
===================================================================
--- mythtv/libs/libmythtv/channelgroupsettings.h	(revision 0)
+++ mythtv/libs/libmythtv/channelgroupsettings.h	(revision 0)
@@ -0,0 +1,38 @@
+#ifndef CHANNELGROUPSETTINGS_H
+#define CHANNELGROUPSETTINGS_H
+
+#include "libmyth/settings.h"
+
+class MPUBLIC ChannelGroupConfig: public ConfigurationWizard
+{
+ public:
+    ChannelGroupConfig(QString _name);
+    QString getName(void) const { return name; }
+
+ private:
+    QString name;
+};
+
+class MPUBLIC ChannelGroupEditor : public QObject, public ConfigurationDialog
+{
+    Q_OBJECT
+
+  public:
+    ChannelGroupEditor(void);
+    virtual DialogCode exec(void);
+    virtual void Load(void);
+    virtual void Save(void) { };
+    virtual void Save(QString) { };
+    virtual MythDialog* dialogWidget(MythMainWindow* parent,
+                                     const char* widgetName=0);
+
+  protected slots:
+    void open(QString name);
+    void doDelete(void);
+
+  protected:
+    ListBoxSetting *listbox;
+    QString         lastValue;
+};
+
+#endif
Index: mythtv/libs/libmythtv/channelgroup.h
===================================================================
--- mythtv/libs/libmythtv/channelgroup.h	(revision 0)
+++ mythtv/libs/libmythtv/channelgroup.h	(revision 0)
@@ -0,0 +1,39 @@
+#ifndef CHANNELGROUP_H
+#define CHANNELGROUP_H
+
+class ChannelGroupItem
+{
+  public:
+    ChannelGroupItem(const ChannelGroupItem&);
+    ChannelGroupItem(const uint _grpid, 
+                  const QString &_name) :
+        grpid(_grpid), name(_name) {}
+
+    bool operator == (uint _grpid) const
+        { return grpid == _grpid; }
+
+    ChannelGroupItem& operator=(const ChannelGroupItem&);
+
+  public:
+    uint    grpid;
+    QString name;
+};
+typedef vector<ChannelGroupItem> ChannelGroupList;
+
+/** \class ChannelGroup
+*/
+class MPUBLIC ChannelGroup
+{
+  public:
+    // ChannelGroup 
+    static ChannelGroupList  GetChannelGroups(void);
+    static bool              ToggleChannel(uint chanid,int changrpid, int delete_chan);
+    static int               GetNextChannelGroup(const ChannelGroupList &sorted, int grpid);
+    static QString           GetChannelGroupName(const ChannelGroupList &sorted, int grpid);
+
+    
+  private:
+
+};
+
+#endif
Index: mythtv/libs/libmythtv/channelgroup.cpp
===================================================================
--- mythtv/libs/libmythtv/channelgroup.cpp	(revision 0)
+++ mythtv/libs/libmythtv/channelgroup.cpp	(revision 0)
@@ -0,0 +1,144 @@
+#include "mythcontext.h"
+#include "libmythdb/mythdbcon.h"
+#include <qsqldatabase.h>
+#include <qcursor.h>
+#include <qlayout.h>
+#include <iostream>
+#include <algorithm>
+#include "mythstorage.h"
+#include "mythdb.h"
+#include "channelutil.h"
+#include "channelgroup.h"
+
+#define LOC QString("Channel Group: ")
+#define LOC_ERR QString("Channel Group, Error: ")
+
+ChannelGroupItem& ChannelGroupItem::operator=(const ChannelGroupItem &other)
+{
+    grpid     = other.grpid;
+    name      = (other.name);
+ 
+    return *this;
+}
+
+ChannelGroupItem::ChannelGroupItem(const ChannelGroupItem &other)
+{
+    (*this) = other;
+}
+
+inline bool lt_group(const ChannelGroupItem &a, const ChannelGroupItem &b)
+{
+    return QString::localeAwareCompare(a.name, b.name) < 0;
+}
+
+bool ChannelGroup::ToggleChannel(uint chanid,int changrpid, int delete_chan)
+{
+    // Check if it already exists for that chanid...
+    MSqlQuery query(MSqlQuery::InitCon());
+    query.prepare(
+        "SELECT channelgroup.id "
+        "FROM channelgroup "
+        "WHERE channelgroup.chanid = :CHANID AND "
+        "channelgroup.grpid = :GRPID "
+        "LIMIT 1");
+    query.bindValue(":CHANID", chanid);
+    query.bindValue(":GRPID", changrpid);
+
+    if (!query.exec() || !query.isActive())
+    {
+        MythDB::DBError("ChannelGroup::ToggleChannel", query);
+        return false;
+    }
+    else if ((query.size() > 0) && delete_chan)
+    {
+        // We have a record...Remove it to toggle...
+        query.next();
+        QString id = query.value(0).toString();
+        query.prepare(
+            QString("DELETE FROM channelgroup "
+                    "WHERE id = '%1'").arg(id));
+        query.exec();
+        VERBOSE(VB_IMPORTANT, LOC + QString("Removing channel with id=%1.").arg(id));
+    }
+    else if (query.size() == 0)
+    {
+        // We have no record...Add one to toggle...
+        query.prepare(
+            QString("INSERT INTO channelgroup (chanid,grpid) "
+                    "VALUES ('%1','%2')").arg(chanid).arg(changrpid));
+        query.exec();
+        VERBOSE(VB_IMPORTANT, LOC + QString("Adding channel %1 to group %2.").arg(chanid).arg(changrpid));
+    }
+
+    return true;
+}
+
+ChannelGroupList ChannelGroup::GetChannelGroups(void)
+{
+    ChannelGroupList list;
+    
+    MSqlQuery query(MSqlQuery::InitCon());
+    
+    QString qstr = "SELECT grpid, name FROM channelgroupnames";
+   
+    query.prepare(qstr);
+
+    if (!query.exec() || !query.isActive())
+        MythDB::DBError("ChannelGroup::GetChannelGroups", query);
+    else
+    {
+        while (query.next())
+        {
+           ChannelGroupItem group(query.value(0).toUInt(), 
+                              query.value(1).toString());
+           list.push_back(group);
+        }
+    }
+    
+    stable_sort(list.begin(), list.end(), lt_group);
+
+    return list;
+}
+
+// Cycle through the available groups, then all channels
+// Will cycle through to end then return -1
+// To signify all channels.
+int ChannelGroup::GetNextChannelGroup(const ChannelGroupList &sorted, int grpid)
+{
+    // If no groups return -1 for all channels
+    if (sorted.empty())
+      return -1;
+    
+    // If grpid is all channels (-1), then return the first grpid  
+    if (grpid == -1)
+      return sorted[0].grpid;
+      
+    ChannelGroupList::const_iterator it = find(sorted.begin(), sorted.end(), grpid);
+
+    // If grpid is not in the list, return -1 for all channels
+    if (it == sorted.end())
+        return -1; 
+
+    ++it;
+
+    // If we reached the end, the next option is all channels (-1)
+    if (it == sorted.end())
+       return -1;
+
+    return it->grpid;
+}
+
+QString ChannelGroup::GetChannelGroupName(const ChannelGroupList &sorted, int grpid)
+{
+    // All Channels
+    if (grpid == -1)
+      return "All Channels";
+
+    ChannelGroupList::const_iterator it = find(sorted.begin(), sorted.end(), grpid);
+        
+    // If grpid wasn't found, return blank.    
+    if (it == sorted.end())
+       return "";
+    else
+       return it->name;
+}
Index: mythtv/libs/libmythtv/channelgroupsettings.cpp
===================================================================
--- mythtv/libs/libmythtv/channelgroupsettings.cpp	(revision 0)
+++ mythtv/libs/libmythtv/channelgroupsettings.cpp	(revision 0)
@@ -0,0 +1,254 @@
+#include "mythcontext.h"
+#include "libmythdb/mythdbcon.h"
+#include <qsqldatabase.h>
+#include <qcursor.h>
+#include <qlayout.h>
+#include <iostream>
+#include "mythstorage.h"
+#include "mythdb.h"
+#include "channelutil.h"
+#include "channelgroup.h"
+#include "channelgroupsettings.h"
+
+#define LOC QString("Channel Group Settings: ")
+#define LOC_ERR QString("Channel Group Settings, Error: ")
+
+// Storage class for channel group editor in settings
+class ChannelGroupStorage : public Storage
+{
+  public:
+    ChannelGroupStorage(Setting *_setting,
+                    uint _chanid, QString _grpname) :
+        setting(_setting), chanid(_chanid), grpname(_grpname) {}
+    virtual ~ChannelGroupStorage() {};
+
+    virtual void Load(void);
+    virtual void Save(void);
+    virtual void Save(QString destination);
+
+  protected:
+    Setting *setting;
+    uint    chanid;
+    QString grpname;
+    int     grpid;
+};
+
+void ChannelGroupStorage::Load(void)
+{
+    setting->setValue("0");
+    setting->setUnchanged();
+    
+    MSqlQuery query(MSqlQuery::InitCon());
+    
+    QString qstr = "SELECT grpid FROM channelgroupnames WHERE name = :GRPNAME";
+   
+    query.prepare(qstr);
+    query.bindValue(":GRPNAME", grpname);
+
+    if (!query.exec() || !query.isActive())
+        MythDB::DBError("ChannelGroupStorage::Load", query);
+    else 
+    {
+      query.next();
+      grpid = query.value(0).toUInt();
+    
+      qstr = "SELECT * FROM channelgroup WHERE grpid = :GRPID AND chanid = :CHANID";
+      query.prepare(qstr);
+      query.bindValue(":GRPID",  grpid);
+      query.bindValue(":CHANID", chanid);
+
+      if (!query.exec() || !query.isActive())
+          MythDB::DBError("ChannelGroupStorage::Load", query);
+      else if (query.size() > 0)
+        setting->setValue("1");
+    }
+}
+
+void ChannelGroupStorage::Save(void)
+{
+    if (!setting->isChanged())
+      return;
+    
+    QString value = setting->getValue();
+    
+    if (value == "1")
+        ChannelGroup::ToggleChannel(chanid, grpid, false);
+    else
+        ChannelGroup::ToggleChannel(chanid, grpid, true);   
+}
+
+void ChannelGroupStorage::Save(QString destination)
+{
+    Save();
+}
+
+class ChannelCheckBox : public CheckBoxSetting, public ChannelGroupStorage
+{
+  public:
+    ChannelCheckBox(const ChannelGroupConfig& _parent, const uint chanid, const QString channum, 
+               const QString channame, const QString grpname):
+        CheckBoxSetting(this),
+        ChannelGroupStorage(this, chanid, grpname)
+    {
+        setLabel(QString("%1 %2").arg(channum).arg(channame));
+        setHelpText(QObject::tr("Select/Unselect channels for this channel group"));
+    };
+};
+
+ChannelGroupConfig::ChannelGroupConfig(QString _name)
+    : name(_name)
+{
+    VerticalConfigurationGroup   *cgroup; 
+    HorizontalConfigurationGroup *columns; 
+
+    DBChanList chanlist = ChannelUtil::GetChannels(0, true, "channum, callsign");
+    ChannelUtil::SortChannels(chanlist, "channum", true);
+
+    DBChanList::iterator it = chanlist.begin();
+    int i,j = 0;
+    int p = 1;
+    int pages = (int)((float)chanlist.size() / 8.0 / 3.0 + 0.5);
+    
+    do 
+    {  
+        columns = new HorizontalConfigurationGroup(false,false,false,false);
+        columns->setLabel(getName() + " " + 
+                          QObject::tr("Channel Group - Page ") + QString("%1").arg(p) +
+                          QObject::tr("of") + QString("%1").arg(pages));
+        
+        for (j = 0; ((j < 3) && (it < chanlist.end())); ++j)
+        {
+            cgroup = new VerticalConfigurationGroup(false,false,false,false);
+            
+            for (i = 0; ((i < 8) && (it < chanlist.end())); ++i)
+            {
+                cgroup->addChild(new ChannelCheckBox(*this, it->chanid, it->channum, it->name, _name));
+                ++it;
+            }
+            columns->addChild(cgroup);
+        }
+        
+        ++p;
+        addChild(columns);
+    } while (it < chanlist.end());
+
+}
+
+ChannelGroupEditor::ChannelGroupEditor(void) :
+    listbox(new ListBoxSetting(this)), lastValue("__CREATE_NEW_GROUP__")
+{
+    listbox->setLabel(tr("Channel Groups"));
+    addChild(listbox);
+}
+
+void ChannelGroupEditor::open(QString name) 
+{
+    lastValue = name;
+    bool created = false;
+
+    if (name == "__CREATE_NEW_GROUP__")
+    {
+        name = "";
+        
+        bool ok = MythPopupBox::showGetTextPopup(gContext->GetMainWindow(), 
+            tr("Create New Channel Group"),
+            tr("Enter group name or press SELECT to enter text via the "
+               "On Screen Keyboard"), name);
+        if (!ok)
+            return;
+
+        MSqlQuery query(MSqlQuery::InitCon());
+        query.prepare("INSERT INTO channelgroupnames (name) VALUES (:NAME);");
+        query.bindValue(":NAME", name);
+        if (!query.exec())
+            MythDB::DBError("ChannelGroupEditor::open", query);
+        else
+            created = true;
+    }
+    
+    ChannelGroupConfig group(name);
+    
+    if (group.exec() == QDialog::Accepted || !created)
+        lastValue = name;
+
+};
+
+void ChannelGroupEditor::doDelete(void) 
+{
+    QString name = listbox->getValue();
+    if (name == "__CREATE_NEW_GROUP__")
+        return;
+
+    QString message = tr("Delete '%1' Channel group?").arg(name);
+    
+    DialogCode value = MythPopupBox::Show2ButtonPopup(
+        gContext->GetMainWindow(),
+        "", message,
+        tr("Yes, delete group"),
+        tr("No, Don't delete group"), kDialogCodeButton1);
+
+    if (kDialogCodeButton0 == value)
+    {
+        MSqlQuery query(MSqlQuery::InitCon());
+
+        // Find out channel group id
+        query.prepare("SELECT grpid FROM channelgroupnames WHERE name = :NAME;");
+        query.bindValue(":NAME", name);
+        if (!query.exec())
+            MythDB::DBError("ChannelGroupEditor::doDelete", query);
+        query.next();
+        uint grpid = query.value(0).toUInt();
+
+        // Delete channels from this group
+        query.prepare("DELETE FROM channelgroup WHERE grpid = :GRPID;");
+        query.bindValue(":GRPID", grpid);
+        if (!query.exec())
+            MythDB::DBError("ChannelGroupEditor::doDelete", query);
+        
+        // Now delete the group from channelgroupnames
+        query.prepare("DELETE FROM channelgroupnames WHERE name = :NAME;");
+        query.bindValue(":NAME", name);
+        if (!query.exec())
+            MythDB::DBError("ChannelGroupEditor::doDelete", query);
+
+        lastValue = "__CREATE_NEW_GROUP__";
+        Load();
+    }
+
+    listbox->setFocus();
+}
+
+void ChannelGroupEditor::Load(void)
+{
+    listbox->clearSelections();
+    
+    ChannelGroupList changrplist;
+
+    changrplist = ChannelGroup::GetChannelGroups();
+
+    ChannelGroupList::iterator it;
+
+    for (it = changrplist.begin(); it < changrplist.end(); ++it)
+       listbox->addSelection(it->name);
+       
+    listbox->addSelection(tr("(Create new group)"), "__CREATE_NEW_GROUP__");
+
+    listbox->setValue(lastValue);
+}
+
+DialogCode ChannelGroupEditor::exec(void)
+{
+    while (ConfigurationDialog::exec() == kDialogCodeAccepted)
+        open(listbox->getValue());
+
+    return kDialogCodeRejected;
+}
+
+MythDialog* ChannelGroupEditor::dialogWidget(MythMainWindow* parent,
+                                          const char* widgetName)
+{
+    dialog = ConfigurationDialog::dialogWidget(parent, widgetName);
+    connect(dialog, SIGNAL(menuButtonPressed()), this, SLOT(doDelete()));
+    connect(dialog, SIGNAL(deleteButtonPressed()), this, SLOT(doDelete()));
+    return dialog;
+}
