Ticket #9613: multikeybindings_v9.patch

File multikeybindings_v9.patch, 27.6 KB (added by Jim Stichnoth <stichnot@…>, 9 years ago)
  • mythtv/libs/libmythtv/dbcheck.cpp

    diff --git a/mythtv/libs/libmythtv/dbcheck.cpp b/mythtv/libs/libmythtv/dbcheck.cpp
    index ace4f6c..4ac238d 100644
    a b tmp.constData(), 
    62256225"  description varchar(255) default NULL,"
    62266226"  keylist varchar(128) default NULL,"
    62276227"  hostname varchar(64) NOT NULL default '',"
     6228"  multikey varchar(128) default NULL,"
    62286229"  PRIMARY KEY  (destination,hostname)"
    62296230");",
    62306231"CREATE TABLE keybindings ("
    tmp.constData(), 
    62336234"  description varchar(255) default NULL,"
    62346235"  keylist varchar(128) default NULL,"
    62356236"  hostname varchar(64) NOT NULL default '',"
     6237"  multikey varchar(128) default NULL,"
    62366238"  PRIMARY KEY  (`context`,`action`,hostname)"
    62376239");",
    62386240"CREATE TABLE keyword ("
  • mythtv/libs/libmythtv/tv_play.cpp

    diff --git a/mythtv/libs/libmythtv/tv_play.cpp b/mythtv/libs/libmythtv/tv_play.cpp
    index 9f8cfde..43c1f72 100644
    a b TV::TV(void) 
    892892      endOfPlaybackTimerId(0),      embedCheckTimerId(0),
    893893      endOfRecPromptTimerId(0),     videoExitDialogTimerId(0),
    894894      pseudoChangeChanTimerId(0),   speedChangeTimerId(0),
    895       errorRecoveryTimerId(0),      exitPlayerTimerId(0)
     895      errorRecoveryTimerId(0),      exitPlayerTimerId(0),
     896      multikeyContext(new MultikeyContext())
    896897{
    897898    LOG(VB_GENERAL, LOG_INFO, LOC + "Creating TV object");
    898899    ctorTime.start();
    TV::~TV(void) 
    12441245        browsehelper = NULL;
    12451246    }
    12461247
     1248    if (multikeyContext)
     1249    {
     1250        delete multikeyContext;
     1251        multikeyContext = NULL;
     1252    }
     1253
    12471254    LOG(VB_PLAYBACK, LOG_INFO, "TV::~TV() -- end");
    12481255}
    12491256
    bool TV::ProcessKeypress(PlayerContext *actx, QKeyEvent *e) 
    33843391    if (ignoreKeys)
    33853392    {
    33863393        handled = GetMythMainWindow()->TranslateKeyPress(
    3387                   "TV Playback", e, actions);
     3394                  "TV Playback", e, actions, true, multikeyContext);
    33883395
    33893396        if (handled || actions.isEmpty())
     3397        {
     3398            CommitMultikey(actx);
    33903399            return true;
     3400        }
    33913401
    33923402        bool esc   = has_action("ESCAPE", actions) ||
    33933403                     has_action("BACK", actions);
    bool TV::ProcessKeypress(PlayerContext *actx, QKeyEvent *e) 
    33953405        bool play  = has_action(ACTION_PLAY,  actions);
    33963406
    33973407        if ((!esc || browsehelper->IsBrowsing()) && !pause && !play)
     3408        {
     3409            CommitMultikey(actx);
    33983410            return false;
     3411        }
    33993412    }
    34003413
    34013414    OSD *osd = GetOSDLock(actx);
    bool TV::ProcessKeypress(PlayerContext *actx, QKeyEvent *e) 
    34093422    if (editmode && !handled)
    34103423    {
    34113424        handled |= GetMythMainWindow()->TranslateKeyPress(
    3412                    "TV Editing", e, actions);
     3425                   "TV Editing", e, actions, true, multikeyContext);
    34133426
    34143427        if (!handled)
    34153428        {
    bool TV::ProcessKeypress(PlayerContext *actx, QKeyEvent *e) 
    34533466    }
    34543467
    34553468    if (handled)
     3469    {
     3470        CommitMultikey(actx);
    34563471        return true;
     3472    }
    34573473
    34583474    // If text is already queued up, be more lax on what is ok.
    34593475    // This allows hex teletext entry and minor channel entry.
    bool TV::ProcessKeypress(PlayerContext *actx, QKeyEvent *e) 
    34653481        if (ok || txt=="_" || txt=="-" || txt=="#" || txt==".")
    34663482        {
    34673483            AddKeyToInputQueue(actx, txt.at(0).toLatin1());
     3484            CommitMultikey(actx, false);
    34683485            return true;
    34693486        }
    34703487    }
    bool TV::ProcessKeypress(PlayerContext *actx, QKeyEvent *e) 
    34753492    {
    34763493        QStringList tt_actions;
    34773494        handled = GetMythMainWindow()->TranslateKeyPress(
    3478                   "Teletext Menu", e, tt_actions);
     3495                  "Teletext Menu", e, tt_actions, true, multikeyContext);
    34793496
    34803497        if (!handled && !tt_actions.isEmpty())
    34813498        {
    bool TV::ProcessKeypress(PlayerContext *actx, QKeyEvent *e) 
    34843501                if (actx->player->HandleTeletextAction(tt_actions[i]))
    34853502                {
    34863503                    actx->UnlockDeletePlayer(__FILE__, __LINE__);
     3504                    CommitMultikey(actx);
    34873505                    return true;
    34883506                }
    34893507            }
    bool TV::ProcessKeypress(PlayerContext *actx, QKeyEvent *e) 
    34953513    {
    34963514        QStringList itv_actions;
    34973515        handled = GetMythMainWindow()->TranslateKeyPress(
    3498                   "TV Playback", e, itv_actions);
     3516                  "TV Playback", e, itv_actions, true, multikeyContext);
    34993517
    35003518        if (!handled && !itv_actions.isEmpty())
    35013519        {
    bool TV::ProcessKeypress(PlayerContext *actx, QKeyEvent *e) 
    35043522                if (actx->player->ITVHandleAction(itv_actions[i]))
    35053523                {
    35063524                    actx->UnlockDeletePlayer(__FILE__, __LINE__);
     3525                    CommitMultikey(actx);
    35073526                    return true;
    35083527                }
    35093528            }
    bool TV::ProcessKeypress(PlayerContext *actx, QKeyEvent *e) 
    35123531    actx->UnlockDeletePlayer(__FILE__, __LINE__);
    35133532
    35143533    handled = GetMythMainWindow()->TranslateKeyPress(
    3515               "TV Playback", e, actions);
     3534              "TV Playback", e, actions, true, multikeyContext);
    35163535
    35173536    if (handled || actions.isEmpty())
     3537    {
     3538        CommitMultikey(actx);
    35183539        return true;
     3540    }
    35193541
    35203542    handled = false;
    35213543
    bool TV::ProcessKeypress(PlayerContext *actx, QKeyEvent *e) 
    35423564#endif // DEBUG_ACTIONS
    35433565
    35443566    if (handled)
     3567    {
     3568        CommitMultikey(actx);
    35453569        return true;
     3570    }
    35463571
    35473572    if (!handled)
    35483573    {
    bool TV::ProcessKeypress(PlayerContext *actx, QKeyEvent *e) 
    35563581            {
    35573582                AddKeyToInputQueue(actx, '0' + val);
    35583583                handled = true;
     3584                CommitMultikey(actx, false);
    35593585            }
    35603586        }
    35613587    }
    bool TV::ProcessKeypress(PlayerContext *actx, QKeyEvent *e) 
    35633589    return true;
    35643590}
    35653591
     3592void TV::CommitMultikey(PlayerContext *ctx, bool updateOSD)
     3593{
     3594    MultikeyContext::MultikeyStatus status;
     3595    QString prefix;
     3596    multikeyContext->Commit(status, prefix);
     3597    bool isMultikey = (QKeySequence(prefix).count() > 1);
     3598    if (updateOSD)
     3599    {
     3600        switch (status)
     3601        {
     3602        case MultikeyContext::kMKnoPrefix:
     3603            // clear the OSD text
     3604            HideOSDWindow(ctx, "osd_input");
     3605            break;
     3606        case MultikeyContext::kMKpartialMatch:
     3607            SetOSDText(ctx, "osd_input", "osd_number_entry",
     3608                       prefix, kOSDTimeout_Med);
     3609            break;
     3610        case MultikeyContext::kMKfullMatch:
     3611            // same as partial match but with short timeout
     3612            if (isMultikey)
     3613                SetOSDText(ctx, "osd_input", "osd_number_entry",
     3614                           prefix, kOSDTimeout_Short);
     3615            break;
     3616        case MultikeyContext::kMKbadMatch:
     3617            // same as full match but with error text
     3618            SetOSDText(ctx, "osd_input", "osd_number_entry",
     3619                       prefix + ": " + QObject::tr("Unknown key sequence"),
     3620                       kOSDTimeout_Short);
     3621            break;
     3622        }
     3623    }
     3624}
     3625
    35663626bool TV::BrowseHandleAction(PlayerContext *ctx, const QStringList &actions)
    35673627{
    35683628    if (!browsehelper->IsBrowsing())
  • mythtv/libs/libmythtv/tv_play.h

    diff --git a/mythtv/libs/libmythtv/tv_play.h b/mythtv/libs/libmythtv/tv_play.h
    index 1e04ab0..2892ffc 100644
    a b using namespace std; 
    3939
    4040class QDateTime;
    4141class OSD;
     42class MultikeyContext;
    4243class RemoteEncoder;
    4344class MythPlayer;
    4445class DetectLetterbox;
    class MTV_PUBLIC TV : public QObject 
    355356    void ClearInputQueues(const PlayerContext*, bool hideosd);
    356357    bool CommitQueuedInput(PlayerContext*);
    357358    bool ProcessSmartChannel(const PlayerContext*, QString&);
     359    void CommitMultikey(PlayerContext*, bool updateOSD=true);
    358360
    359361    // query key queues
    360362    bool HasQueuedInput(void) const
    class MTV_PUBLIC TV : public QObject 
    818820    TimerContextMap      signalMonitorTimerId;
    819821    TimerContextMap      tvchainUpdateTimerId;
    820822
     823    MultikeyContext     *multikeyContext;
     824
    821825  public:
    822826    // Constants
    823827    static const int kInitFFRWSpeed; ///< 1x, default to normal speed
  • mythtv/libs/libmythui/mythmainwindow.cpp

    diff --git a/mythtv/libs/libmythui/mythmainwindow.cpp b/mythtv/libs/libmythui/mythmainwindow.cpp
    index d1ce2a2..03e39d9 100644
    a b using namespace std; 
    8080
    8181#define LOC      QString("MythMainWindow: ")
    8282
     83class MultikeyData
     84{
     85public:
     86    void AddMultikeyMapping(const QString &multikey, QString action)
     87    {
     88        QKeySequence seq(multikey);
     89        switch (seq.count())
     90        {
     91        case 4:
     92            ++multikeyPrefixes[QKeySequence(seq[0], seq[1], seq[2])];
     93            // fallthrough
     94        case 3:
     95            ++multikeyPrefixes[QKeySequence(seq[0], seq[1])];
     96            // fallthrough
     97        case 2:
     98            ++multikeyPrefixes[QKeySequence(seq[0])];
     99            // fallthrough
     100        case 1:
     101            multikeyActionMap[seq].append(action);
     102        }
     103    }
     104
     105    bool IsMultikeyPrefix(const QString &multikey)
     106    {
     107        return multikeyPrefixes.count(QKeySequence(multikey));
     108    }
     109
     110    bool GetMultikeyMapping(const QString &multikey, QStringList &actions)
     111    {
     112        QKeySequence seq(multikey);
     113        if (multikeyActionMap.count(seq) > 0)
     114        {
     115            actions += multikeyActionMap[seq];
     116            return true;
     117        }
     118        return false;
     119    }
     120
     121    void clear(void)
     122    {
     123        multikeyActionMap.clear();
     124        multikeyPrefixes.clear();
     125    }
     126
     127private:
     128    QMap<QKeySequence, QStringList> multikeyActionMap;
     129    QMap<QKeySequence, int> multikeyPrefixes;
     130};
     131
    83132class KeyContext
    84133{
    85134  public:
    86135    void AddMapping(int key, QString action)
    87136    {
    88137        actionMap[key].append(action);
     138        multiData.AddMultikeyMapping(QKeySequence(key).toString(), action);
    89139    }
    90140
    91141    bool GetMapping(int key, QStringList &actions)
    class KeyContext 
    99149    }
    100150
    101151    QMap<int, QStringList> actionMap;
     152    MultikeyData multiData;
    102153};
    103154
    104155struct JumpData
    class MythMainWindowPrivate 
    210261    QHash<QString, KeyContext *> keyContexts;
    211262    QMap<int, JumpData*> jumpMap;
    212263    QMap<QString, JumpData> destinationMap;
     264    MultikeyData jumpMultikeyData;
    213265    QMap<QString, MPData> mediaPluginMap;
    214266
    215267    void (*exitmenucallback)(void);
    class MythMainWindowPrivate 
    258310    bool m_pendingUpdate;
    259311};
    260312
     313// Returns false if the key couldn't be added (prefix too long).
     314bool MultikeyContext::AddKey(int key)
     315{
     316    currentPrefix = oldPrefix;
     317    bool result = true;
     318    QKeySequence seq(currentPrefix);
     319    int k1 = seq[0];
     320    int k2 = seq[1];
     321    int k3 = seq[2];
     322    int k4 = seq[3];
     323    if (k1 == 0)
     324        k1 = key;
     325    else if (k2 == 0)
     326        k2 = key;
     327    else if (k3 == 0)
     328        k3 = key;
     329    else if (k4 == 0)
     330        k4 = key;
     331    else
     332        result = false;
     333    if (result)
     334        currentPrefix = QKeySequence(k1, k2, k3, k4).toString();
     335    return result;
     336}
     337
    261338// Make keynum in QKeyEvent be equivalent to what's in QKeySequence
    262339int MythMainWindowPrivate::TranslateKeyNum(QKeyEvent* e)
    263340{
    void MythMainWindow::ExitToMainMenu(void) 
    14361513 * \param e       The keypress event to lookup.
    14371514 * \param actions The QStringList that will contain the list of actions.
    14381515 * \param allowJumps if true then jump points are allowed
     1516 * \param prefixContext if non-NULL then multi-key bindings are allowed.
    14391517 *
    14401518 * \return true if the key event has been handled (the keypress was a jumpoint)
    14411519           false if the caller should continue to handle keypress
    14421520 */
    14431521bool MythMainWindow::TranslateKeyPress(const QString &context,
    14441522                                       QKeyEvent *e, QStringList &actions,
    1445                                        bool allowJumps)
     1523                                       bool allowJumps,
     1524                                       MultikeyContext *prefixContext)
    14461525{
    14471526    actions.clear();
    14481527
    bool MythMainWindow::TranslateKeyPress(const QString &context, 
    14741553    }
    14751554
    14761555    int keynum = d->TranslateKeyNum(e);
    1477 
    1478     QStringList localActions;
    1479     if (allowJumps && (d->jumpMap.count(keynum) > 0) &&
    1480         (!d->jumpMap[keynum]->localAction.isEmpty()) &&
    1481         (d->keyContexts.value(context)) &&
    1482         (d->keyContexts.value(context)->GetMapping(keynum, localActions)))
    1483     {
    1484         if (localActions.contains(d->jumpMap[keynum]->localAction))
    1485             allowJumps = false;
     1556    bool checkOnlyJumpPoints = false;
     1557    bool wasEmpty = prefixContext ? prefixContext->GetPrefix().isEmpty() : true;
     1558    bool noOverflow = prefixContext ? prefixContext->AddKey(keynum) : true;
     1559    const QString singleKey = QKeySequence(keynum).toString();
     1560    const QString prefix = prefixContext ? prefixContext->GetPrefix() : singleKey;
     1561    MultikeyContext::MultikeyStatus status;
     1562    JumpData *singleKeyJump = NULL, *multiKeyJump = NULL;
     1563        // There is a potential race condition where an input timeout
     1564        // thread resets the context at the same time this code is
     1565        // operating on it.
     1566    QStringList singleKeyJumpActions, multiKeyJumpActions;
     1567    QStringList singleKeyActions, multiKeyActions;
     1568    KeyContext *kctx = d->keyContexts.value(context);
     1569    KeyContext *gctx = d->keyContexts.value("Global");
     1570
     1571    if (allowJumps)
     1572    {
     1573        // Find single-key jumppoint bindings.  This is only used when
     1574        // a multi-key sequence (regular or jumppoint) is aborted
     1575        // mid-sequence with a single-key jump point.
     1576        if (d->jumpMultikeyData.GetMultikeyMapping(singleKey, singleKeyJumpActions))
     1577            singleKeyJump = &d->destinationMap[singleKeyJumpActions[0]];
     1578        // Find multi-key jumppoint bindings.
     1579        if (noOverflow && d->jumpMultikeyData.GetMultikeyMapping(prefix, multiKeyJumpActions))
     1580            multiKeyJump = &d->destinationMap[multiKeyJumpActions[0]];
     1581    }
     1582    // Find regular bindings (both single- and multi-key) in the given
     1583    // and global contexts.
     1584    if (kctx)
     1585    {
     1586        if (noOverflow)
     1587            kctx->multiData.GetMultikeyMapping(prefix, multiKeyActions);
     1588        kctx->multiData.GetMultikeyMapping(singleKey, singleKeyActions);
     1589    }
     1590    if (gctx && context != "Global")
     1591    {
     1592        if (noOverflow)
     1593            gctx->multiData.GetMultikeyMapping(prefix, multiKeyActions);
     1594        gctx->multiData.GetMultikeyMapping(singleKey, singleKeyActions);
     1595    }
     1596
     1597    // Disable the jump point if there is a corresponding local action
     1598    // for the key sequence.
     1599    if (multiKeyJump &&
     1600        !multiKeyJump->localAction.isEmpty() &&
     1601        multiKeyActions.contains(multiKeyJump->localAction))
     1602    {
     1603        multiKeyJump = singleKeyJump = NULL;
     1604    }
     1605    if (singleKeyJump &&
     1606        !singleKeyJump->localAction.isEmpty() &&
     1607        singleKeyActions.contains(singleKeyJump->localAction))
     1608    {
     1609        multiKeyJump = singleKeyJump = NULL;
     1610        multiKeyActions = singleKeyActions;
     1611    }
     1612
     1613    // Execute the jump point if there is one.
     1614    if (multiKeyJump || (singleKeyJump && multiKeyActions.isEmpty()))
     1615    {
     1616        JumpData *jumpEntry = multiKeyJump ? multiKeyJump : singleKeyJump;
     1617        if (!jumpEntry->exittomain && d->exitmenucallback == NULL)
     1618        {
     1619            if (prefixContext)
     1620                prefixContext->SetStatus(MultikeyContext::kMKfullMatch);
     1621            void (*callback)(void) = jumpEntry->callback;
     1622            callback();
     1623            return true;
     1624        }
     1625        if (d->exitmenucallback == NULL)
     1626        {
     1627            if (prefixContext)
     1628                prefixContext->SetStatus(MultikeyContext::kMKfullMatch);
     1629            d->exitingtomain = true;
     1630            d->exitmenucallback = jumpEntry->callback;
     1631            QCoreApplication::postEvent(
     1632                this, new QEvent(MythEvent::kExitToMainMenuEventType));
     1633            return true;
     1634        }
    14861635    }
    14871636
    1488     if (allowJumps && d->jumpMap.count(keynum) > 0 &&
    1489             !d->jumpMap[keynum]->exittomain && d->exitmenucallback == NULL)
     1637    // Handle a regular action if there is one.
     1638    if (!multiKeyActions.isEmpty())
    14901639    {
    1491         void (*callback)(void) = d->jumpMap[keynum]->callback;
    1492         callback();
    1493         return true;
     1640        if (prefixContext)
     1641            prefixContext->SetStatus(MultikeyContext::kMKfullMatch);
     1642        actions = multiKeyActions;
     1643        return false;
    14941644    }
    14951645
    1496     if (allowJumps &&
    1497         d->jumpMap.count(keynum) > 0 && d->exitmenucallback == NULL)
     1646    // No full match, so handle partial matches and bad matches.
     1647    if (noOverflow)
    14981648    {
    1499         d->exitingtomain = true;
    1500         d->exitmenucallback = d->jumpMap[keynum]->callback;
    1501         QCoreApplication::postEvent(
    1502             this, new QEvent(MythEvent::kExitToMainMenuEventType));
    1503         return true;
     1649        // Check for a partial match.
     1650        if ((kctx && kctx->multiData.IsMultikeyPrefix(prefix)) ||
     1651            (gctx && context != "Global" && gctx->multiData.IsMultikeyPrefix(prefix)) ||
     1652            (d->jumpMultikeyData.IsMultikeyPrefix(prefix)))
     1653        {
     1654            if (prefixContext)
     1655                prefixContext->SetStatus(MultikeyContext::kMKpartialMatch);
     1656            return false;
     1657        }
    15041658    }
    1505 
    1506     if (d->keyContexts.value(context))
    1507         d->keyContexts.value(context)->GetMapping(keynum, actions);
    1508 
    1509     if (context != "Global")
    1510         d->keyContexts.value("Global")->GetMapping(keynum, actions);
    1511 
     1659    if (prefixContext)
     1660        prefixContext->SetStatus(wasEmpty ? MultikeyContext::kMKnoPrefix :
     1661                                 MultikeyContext::kMKbadMatch);
    15121662    return false;
    15131663}
    15141664
    void MythMainWindow::ClearKeyContext(const QString &context) 
    15331683{
    15341684    KeyContext *keycontext = d->keyContexts.value(context);
    15351685    if (keycontext != NULL)
     1686    {
    15361687        keycontext->actionMap.clear();
     1688        keycontext->multiData.clear();
     1689    }
    15371690}
    15381691
    15391692void MythMainWindow::BindKey(const QString &context, const QString &action,
    1540                              const QString &key)
     1693                             const QString &key, const QString &multikey)
    15411694{
    15421695    QKeySequence keyseq(key);
    15431696
    void MythMainWindow::BindKey(const QString &context, const QString &action, 
    15661719        if (action == "ESCAPE" && context == "Global" && i == 0)
    15671720            d->escapekey = keynum;
    15681721    }
     1722
     1723    QStringList multidummyaction("");
     1724    if (d->keyContexts.value(context)->multiData.GetMultikeyMapping(multikey, multidummyaction))
     1725    {
     1726        LOG(VB_GENERAL, LOG_INFO, QString("Multikey %1 is bound to multiple actions "
     1727                                    "in context %2.")
     1728                .arg(multikey).arg(context));
     1729    }
     1730    d->keyContexts.value(context)->multiData.AddMultikeyMapping(multikey, action);
    15691731}
    15701732
    15711733void MythMainWindow::RegisterKey(const QString &context, const QString &action,
    15721734                                 const QString &description, const QString &key)
    15731735{
    15741736    QString keybind = key;
     1737    QString multikeybind = "";
    15751738
    15761739    MSqlQuery query(MSqlQuery::InitCon());
    15771740
    15781741    if (d->m_useDB && query.isConnected())
    15791742    {
    1580         query.prepare("SELECT keylist, description FROM keybindings WHERE "
     1743        query.prepare("SELECT keylist, description, multikey FROM keybindings WHERE "
    15811744                      "context = :CONTEXT AND action = :ACTION AND "
    15821745                      "hostname = :HOSTNAME ;");
    15831746        query.bindValue(":CONTEXT", context);
    void MythMainWindow::RegisterKey(const QString &context, const QString &action, 
    15881751        {
    15891752            keybind = query.value(0).toString();
    15901753            QString db_description = query.value(1).toString();
     1754            multikeybind = query.value(2).toString();
    15911755
    15921756            // Update keybinding description if changed
    15931757            if (db_description != description)
    void MythMainWindow::RegisterKey(const QString &context, const QString &action, 
    16151779        else
    16161780        {
    16171781            QString inskey = keybind;
     1782            QString insmultikey = multikeybind;
    16181783
    16191784            query.prepare("INSERT INTO keybindings (context, action, "
    1620                           "description, keylist, hostname) VALUES "
     1785                          "description, keylist, hostname, multikey) VALUES "
    16211786                          "( :CONTEXT, :ACTION, :DESCRIPTION, :KEYLIST, "
    1622                           ":HOSTNAME );");
     1787                          ":HOSTNAME, :MULTIKEY );");
    16231788            query.bindValue(":CONTEXT", context);
    16241789            query.bindValue(":ACTION", action);
    16251790            query.bindValue(":DESCRIPTION", description);
    16261791            query.bindValue(":KEYLIST", inskey);
    16271792            query.bindValue(":HOSTNAME", GetMythDB()->GetHostName());
     1793            query.bindValue(":MULTIKEY", insmultikey);
    16281794
    16291795            if (!query.exec() && !(GetMythDB()->SuppressDBMessages()))
    16301796            {
    void MythMainWindow::RegisterKey(const QString &context, const QString &action, 
    16331799        }
    16341800    }
    16351801
    1636     BindKey(context, action, keybind);
     1802    BindKey(context, action, keybind, multikeybind);
    16371803}
    16381804
    16391805QString MythMainWindow::GetKey(const QString &context,
    void MythMainWindow::ClearJump(const QString &destination) 
    16791845}
    16801846
    16811847
    1682 void MythMainWindow::BindJump(const QString &destination, const QString &key)
     1848void MythMainWindow::BindJump(const QString &destination, const QString &key,
     1849                              const QString &multikey)
    16831850{
    16841851    /* make sure the jump point exists */
    16851852    if (d->destinationMap.find(destination) == d->destinationMap.end())
    void MythMainWindow::BindJump(const QString &destination, const QString &key) 
    17041871#endif
    17051872
    17061873            d->jumpMap[keynum] = &d->destinationMap[destination];
     1874            d->jumpMultikeyData.AddMultikeyMapping(QKeySequence(keynum).toString(), destination);
    17071875        }
    17081876        else
    17091877        {
    void MythMainWindow::BindJump(const QString &destination, const QString &key) 
    17161884        LOG(VB_GENERAL, LOG_DEBUG,
    17171885            QString("JumpPoint: %2 exists, no keybinding") .arg(destination));
    17181886#endif
     1887
     1888    d->jumpMultikeyData.AddMultikeyMapping(multikey, destination);
    17191889}
    17201890
    17211891void MythMainWindow::RegisterJump(const QString &destination,
    void MythMainWindow::RegisterJump(const QString &destination, 
    17241894                                  bool exittomain, QString localAction)
    17251895{
    17261896    QString keybind = key;
     1897    QString multikeybind = "";
    17271898
    17281899    MSqlQuery query(MSqlQuery::InitCon());
    17291900    if (query.isConnected())
    17301901    {
    1731         query.prepare("SELECT keylist FROM jumppoints WHERE "
     1902        query.prepare("SELECT keylist, multikey FROM jumppoints WHERE "
    17321903                      "destination = :DEST and hostname = :HOST ;");
    17331904        query.bindValue(":DEST", destination);
    17341905        query.bindValue(":HOST", GetMythDB()->GetHostName());
    void MythMainWindow::RegisterJump(const QString &destination, 
    17361907        if (query.exec() && query.next())
    17371908        {
    17381909            keybind = query.value(0).toString();
     1910            multikeybind = query.value(1).toString();
    17391911        }
    17401912        else
    17411913        {
    17421914            QString inskey = keybind;
     1915            QString insmultikey = multikeybind;
    17431916
    17441917            query.prepare("INSERT INTO jumppoints (destination, description, "
    1745                           "keylist, hostname) VALUES ( :DEST, :DESC, :KEYLIST, "
    1746                           ":HOST );");
     1918                          "keylist, hostname, multikey) VALUES ( :DEST, :DESC, :KEYLIST, "
     1919                          ":HOST, :MULTIKEY );");
    17471920            query.bindValue(":DEST", destination);
    17481921            query.bindValue(":DESC", description);
    17491922            query.bindValue(":KEYLIST", inskey);
    17501923            query.bindValue(":HOST", GetMythDB()->GetHostName());
     1924            query.bindValue(":MULTIKEY", insmultikey);
    17511925
    17521926            if (!query.exec() || !query.isActive())
    17531927            {
    void MythMainWindow::RegisterJump(const QString &destination, 
    17601934        { callback, destination, description, exittomain, localAction };
    17611935    d->destinationMap[destination] = jd;
    17621936
    1763     BindJump(destination, keybind);
     1937    BindJump(destination, keybind, multikeybind);
    17641938}
    17651939
    17661940void MythMainWindow::ClearAllJumps()
  • mythtv/libs/libmythui/mythmainwindow.h

    diff --git a/mythtv/libs/libmythui/mythmainwindow.h b/mythtv/libs/libmythui/mythmainwindow.h
    index 46ea69d..8abd2f2 100644
    a b class MythPainterWindowVDPAU; 
    2828class MythPainterWindowD3D9;
    2929class MythRender;
    3030
     31class MultikeyContext
     32{
     33 public:
     34    enum MultikeyStatus {
     35        kMKnoPrefix,
     36        kMKpartialMatch,
     37        kMKfullMatch,
     38        kMKbadMatch
     39    };
     40    MultikeyContext(void)
     41        : oldPrefix(""), currentPrefix(""), status(kMKnoPrefix) {}
     42    void SetStatus(MultikeyStatus why) { status = why; }
     43    const QString &GetPrefix(void) { return currentPrefix; }
     44    bool AddKey(int key);
     45    void Commit(MultikeyStatus &s, QString &prefix) {
     46        prefix = currentPrefix;
     47        s = status;
     48        if (status != kMKpartialMatch)
     49        {
     50            currentPrefix = "";
     51            status = kMKnoPrefix;
     52        }
     53        oldPrefix = currentPrefix;
     54    }
     55 private:
     56    QString oldPrefix;
     57    QString currentPrefix;
     58    MultikeyStatus status;
     59};
     60
    3161class MUI_PUBLIC MythMainWindow : public QWidget
    3262{
    3363    Q_OBJECT
    class MUI_PUBLIC MythMainWindow : public QWidget 
    4979    MythScreenStack *GetStackAt(int pos);
    5080
    5181    bool TranslateKeyPress(const QString &context, QKeyEvent *e,
    52                            QStringList &actions, bool allowJumps = true)
     82                           QStringList &actions, bool allowJumps = true,
     83                           MultikeyContext *prefixContext = NULL)
    5384                           MUNUSED_RESULT;
    5485
    5586    void ReloadKeys(void);
    5687    void ClearKey(const QString &context, const QString &action);
    5788    void ClearKeyContext(const QString &context);
    5889    void BindKey(const QString &context, const QString &action,
    59                  const QString &key);
     90                 const QString &key, const QString &multikey);
    6091    void RegisterKey(const QString &context, const QString &action,
    6192                     const QString &description, const QString &key);
    6293    QString GetKey(const QString &context, const QString &action) const;
    6394
    6495    void ClearJump(const QString &destination);
    65     void BindJump(const QString &destination, const QString &key);
     96    void BindJump(const QString &destination, const QString &key,
     97                  const QString &multikey);
    6698    void RegisterJump(const QString &destination, const QString &description,
    6799                      const QString &key, void (*callback)(void),
    68100                      bool exittomain = true, QString localAction = "");
  • mythtv/programs/mythfrontend/keybindings.cpp

    diff --git a/mythtv/programs/mythfrontend/keybindings.cpp b/mythtv/programs/mythfrontend/keybindings.cpp
    index e15de26..d099923 100644
    a b void KeyBindings::CommitAction(const ActionID &id) 
    283283    }
    284284
    285285    GetMythMainWindow()->ClearKey(id.GetContext(), id.GetAction());
    286     GetMythMainWindow()->BindKey(id.GetContext(), id.GetAction(), keys);
     286    GetMythMainWindow()->BindKey(id.GetContext(), id.GetAction(), keys, "");
    287287}
    288288
    289289/** \fn KeyBindings::CommitJumppoint(const ActionID&)
    void KeyBindings::CommitJumppoint(const ActionID &id) 
    312312    }
    313313
    314314    GetMythMainWindow()->ClearJump(id.GetAction());
    315     GetMythMainWindow()->BindJump(id.GetAction(), keys);
     315    // XXX- The following line causes the jumppoint editor to erase
     316    // from memory (but not from the database) any existing multikey
     317    // binding for the jumppoint.  Fixing this would require further
     318    // digging into action{,set}.{h,cpp}.
     319    GetMythMainWindow()->BindJump(id.GetAction(), keys, "");
    316320}
    317321
    318322/** \fn KeyBindings::CommitChanges(void)