Ticket #9613: multikeybindings_v2.patch

File multikeybindings_v2.patch, 18.4 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 f21ec29..62e16a7 100644
    a b tmp.constData(), 
    59705970"  description varchar(255) default NULL,"
    59715971"  keylist varchar(128) default NULL,"
    59725972"  hostname varchar(64) NOT NULL default '',"
     5973"  multikey varchar(128) default NULL,"
    59735974"  PRIMARY KEY  (`context`,`action`,hostname)"
    59745975");",
    59755976"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 3b8542c..9574ede 100644
    a b TV::TV(void) 
    873873      endOfPlaybackTimerId(0),      embedCheckTimerId(0),
    874874      endOfRecPromptTimerId(0),     videoExitDialogTimerId(0),
    875875      pseudoChangeChanTimerId(0),   speedChangeTimerId(0),
    876       errorRecoveryTimerId(0),      exitPlayerTimerId(0)
     876      errorRecoveryTimerId(0),      exitPlayerTimerId(0),
     877      multikeyContext(new MultikeyContext())
    877878{
    878879    VERBOSE(VB_GENERAL, LOC + "Creating TV object");
    879880    ctorTime.start();
    TV::~TV(void) 
    12111212    }
    12121213    ReturnPlayerLock(mctx);
    12131214
     1215    if (multikeyContext)
     1216    {
     1217        delete multikeyContext;
     1218        multikeyContext = NULL;
     1219    }
     1220
    12141221    GetMythMainWindow()->GetPaintWindow()->show();
    12151222
    12161223    VERBOSE(VB_PLAYBACK, "TV::~TV() -- end");
    bool TV::ProcessKeypress(PlayerContext *actx, QKeyEvent *e) 
    32963303    if (ignoreKeys)
    32973304    {
    32983305        handled = GetMythMainWindow()->TranslateKeyPress(
    3299                   "TV Playback", e, actions);
     3306                  "TV Playback", e, actions, true, multikeyContext);
    33003307
    33013308        if (handled || actions.isEmpty())
     3309        {
     3310            CommitMultikey(actx);
    33023311            return true;
     3312        }
    33033313
    33043314        bool esc   = has_action("ESCAPE", actions) ||
    33053315                     has_action("BACK", actions);
    bool TV::ProcessKeypress(PlayerContext *actx, QKeyEvent *e) 
    33073317        bool play  = has_action(ACTION_PLAY, actions);
    33083318
    33093319        if ((!esc || browsehelper->IsBrowsing()) && !pause && !play)
     3320        {
     3321            CommitMultikey(actx);
    33103322            return false;
     3323        }
    33113324    }
    33123325
    33133326    OSD *osd = GetOSDLock(actx);
    bool TV::ProcessKeypress(PlayerContext *actx, QKeyEvent *e) 
    33213334    if (editmode && !handled)
    33223335    {
    33233336        handled |= GetMythMainWindow()->TranslateKeyPress(
    3324                    "TV Editing", e, actions);
     3337                   "TV Editing", e, actions, true, multikeyContext);
    33253338
    33263339        if (!handled)
    33273340        {
    bool TV::ProcessKeypress(PlayerContext *actx, QKeyEvent *e) 
    33653378    }
    33663379
    33673380    if (handled)
     3381    {
     3382        CommitMultikey(actx);
    33683383        return true;
     3384    }
    33693385
    33703386    // If text is already queued up, be more lax on what is ok.
    33713387    // This allows hex teletext entry and minor channel entry.
    bool TV::ProcessKeypress(PlayerContext *actx, QKeyEvent *e) 
    33773393        if (ok || txt=="_" || txt=="-" || txt=="#" || txt==".")
    33783394        {
    33793395            AddKeyToInputQueue(actx, txt.at(0).toLatin1());
     3396            CommitMultikey(actx);
    33803397            return true;
    33813398        }
    33823399    }
    bool TV::ProcessKeypress(PlayerContext *actx, QKeyEvent *e) 
    33873404    {
    33883405        QStringList tt_actions;
    33893406        handled = GetMythMainWindow()->TranslateKeyPress(
    3390                   "Teletext Menu", e, tt_actions);
     3407                  "Teletext Menu", e, tt_actions, true, multikeyContext);
    33913408
    33923409        if (!handled && !tt_actions.isEmpty())
    33933410        {
    bool TV::ProcessKeypress(PlayerContext *actx, QKeyEvent *e) 
    33963413                if (actx->player->HandleTeletextAction(tt_actions[i]))
    33973414                {
    33983415                    actx->UnlockDeletePlayer(__FILE__, __LINE__);
     3416                    CommitMultikey(actx);
    33993417                    return true;
    34003418                }
    34013419            }
    bool TV::ProcessKeypress(PlayerContext *actx, QKeyEvent *e) 
    34073425    {
    34083426        QStringList itv_actions;
    34093427        handled = GetMythMainWindow()->TranslateKeyPress(
    3410                   "TV Playback", e, itv_actions);
     3428                  "TV Playback", e, itv_actions, true, multikeyContext);
    34113429
    34123430        if (!handled && !itv_actions.isEmpty())
    34133431        {
    bool TV::ProcessKeypress(PlayerContext *actx, QKeyEvent *e) 
    34163434                if (actx->player->ITVHandleAction(itv_actions[i]))
    34173435                {
    34183436                    actx->UnlockDeletePlayer(__FILE__, __LINE__);
     3437                    CommitMultikey(actx);
    34193438                    return true;
    34203439                }
    34213440            }
    bool TV::ProcessKeypress(PlayerContext *actx, QKeyEvent *e) 
    34243443    actx->UnlockDeletePlayer(__FILE__, __LINE__);
    34253444
    34263445    handled = GetMythMainWindow()->TranslateKeyPress(
    3427               "TV Playback", e, actions);
     3446              "TV Playback", e, actions, true, multikeyContext);
    34283447
    34293448    if (handled || actions.isEmpty())
     3449    {
     3450        CommitMultikey(actx);
    34303451        return true;
     3452    }
    34313453
    34323454    handled = false;
    34333455
    bool TV::ProcessKeypress(PlayerContext *actx, QKeyEvent *e) 
    34543476#endif // DEBUG_ACTIONS
    34553477
    34563478    if (handled)
     3479    {
     3480        CommitMultikey(actx);
    34573481        return true;
     3482    }
    34583483
    34593484    if (!handled)
    34603485    {
    bool TV::ProcessKeypress(PlayerContext *actx, QKeyEvent *e) 
    34683493            {
    34693494                AddKeyToInputQueue(actx, '0' + val);
    34703495                handled = true;
     3496                CommitMultikey(actx, false);
    34713497            }
    34723498        }
    34733499    }
    bool TV::ProcessKeypress(PlayerContext *actx, QKeyEvent *e) 
    34753501    return true;
    34763502}
    34773503
     3504void TV::CommitMultikey(PlayerContext *ctx, bool updateOSD)
     3505{
     3506    MultikeyContext::MultikeyStatus status;
     3507    QString prefix;
     3508    multikeyContext->Commit(status, prefix);
     3509    if (updateOSD)
     3510    {
     3511        switch (status)
     3512        {
     3513        case MultikeyContext::kMKnoPrefix:
     3514            // clear the OSD text
     3515            HideOSDWindow(ctx, "osd_input");
     3516            break;
     3517        case MultikeyContext::kMKpartialMatch:
     3518            SetOSDText(ctx, "osd_input", "osd_number_entry",
     3519                       prefix, kOSDTimeout_Med);
     3520            break;
     3521        case MultikeyContext::kMKfullMatch:
     3522            // same as partial match but with short timeout
     3523            SetOSDText(ctx, "osd_input", "osd_number_entry",
     3524                       prefix, kOSDTimeout_Short);
     3525            break;
     3526        case MultikeyContext::kMKbadMatch:
     3527            // same as full match but with error text
     3528            SetOSDText(ctx, "osd_input", "osd_number_entry",
     3529                       prefix + ": " + QObject::tr("Unknown key sequence"),
     3530                       kOSDTimeout_Short);
     3531            break;
     3532        }
     3533    }
     3534}
     3535
    34783536bool TV::BrowseHandleAction(PlayerContext *ctx, const QStringList &actions)
    34793537{
    34803538    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 2c42abc..5217520 100644
    a b using namespace std; 
    4040
    4141class QDateTime;
    4242class OSD;
     43class MultikeyContext;
    4344class RemoteEncoder;
    4445class MythPlayer;
    4546class DetectLetterbox;
    class MTV_PUBLIC TV : public QObject 
    385386    void ClearInputQueues(const PlayerContext*, bool hideosd);
    386387    bool CommitQueuedInput(PlayerContext*);
    387388    bool ProcessSmartChannel(const PlayerContext*, QString&);
     389    void CommitMultikey(PlayerContext*, bool updateOSD=true);
    388390
    389391    // query key queues
    390392    bool HasQueuedInput(void) const
    class MTV_PUBLIC TV : public QObject 
    841843    TimerContextMap      signalMonitorTimerId;
    842844    TimerContextMap      tvchainUpdateTimerId;
    843845
     846    MultikeyContext     *multikeyContext;
     847
    844848  public:
    845849    // Constants
    846850    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 7ecb158..03eade1 100644
    a b class KeyContext 
    104104        return false;
    105105    }
    106106
     107    void AddMultikeyMapping(const QString &multikey, QString action)
     108    {
     109        QKeySequence seq(multikey);
     110        switch (seq.count())
     111        {
     112        case 4:
     113            ++multikeyPrefixes[QKeySequence(seq[0], seq[1], seq[2])];
     114            // fallthrough
     115        case 3:
     116            ++multikeyPrefixes[QKeySequence(seq[0], seq[1])];
     117            // fallthrough
     118        case 2:
     119            ++multikeyPrefixes[QKeySequence(seq[0])];
     120            // fallthrough
     121        case 1:
     122            multikeyActionMap[seq].append(action);
     123        }
     124    }
     125
     126    bool IsMultikeyPrefix(const QString &multikey)
     127    {
     128        return multikeyPrefixes.count(QKeySequence(multikey));
     129    }
     130
     131    bool GetMultikeyMapping(const QString &multikey, QStringList &actions)
     132    {
     133        QKeySequence seq(multikey);
     134        if (multikeyActionMap.count(seq) > 0)
     135        {
     136            actions += multikeyActionMap[seq];
     137            return true;
     138        }
     139        return false;
     140    }
     141
     142    QMap<QKeySequence, QStringList> multikeyActionMap;
     143    QMap<QKeySequence, int> multikeyPrefixes;
    107144    QMap<int, QStringList> actionMap;
    108145};
    109146
    class MythMainWindowPrivate 
    264301    bool m_pendingUpdate;
    265302};
    266303
     304// Returns false if the key couldn't be added (prefix too long).
     305bool MultikeyContext::AddKey(int key)
     306{
     307    currentPrefix = oldPrefix;
     308    bool result = true;
     309    QKeySequence seq(currentPrefix);
     310    int k1 = seq[0];
     311    int k2 = seq[1];
     312    int k3 = seq[2];
     313    int k4 = seq[3];
     314    if (k1 == 0)
     315        k1 = key;
     316    else if (k2 == 0)
     317        k2 = key;
     318    else if (k3 == 0)
     319        k3 = key;
     320    else if (k4 == 0)
     321        k4 = key;
     322    else
     323        result = false;
     324    if (result)
     325        currentPrefix = QKeySequence(k1, k2, k3, k4).toString();
     326    return result;
     327}
     328
    267329// Make keynum in QKeyEvent be equivalent to what's in QKeySequence
    268330int MythMainWindowPrivate::TranslateKeyNum(QKeyEvent* e)
    269331{
    void MythMainWindow::ExitToMainMenu(void) 
    14461508 */
    14471509bool MythMainWindow::TranslateKeyPress(const QString &context,
    14481510                                       QKeyEvent *e, QStringList &actions,
    1449                                        bool allowJumps)
     1511                                       bool allowJumps,
     1512                                       MultikeyContext *prefixContext)
    14501513{
    14511514    actions.clear();
    14521515
    bool MythMainWindow::TranslateKeyPress(const QString &context, 
    14611524
    14621525    int keynum = d->TranslateKeyNum(e);
    14631526
     1527    if (prefixContext)
     1528    {
     1529        // There is a potential race condition where an input timeout
     1530        // thread resets the context at the same time this code is
     1531        // operating on it.
     1532        bool wasEmpty = prefixContext->GetPrefix().isEmpty();
     1533        if (prefixContext->AddKey(keynum))
     1534        {
     1535            const QString &prefix = prefixContext->GetPrefix();
     1536            bool found = false;
     1537            KeyContext *kctx = d->keyContexts.value(context);
     1538            KeyContext *gctx = d->keyContexts.value("Global");
     1539            // Check for exact multikey matches.
     1540            if (kctx && kctx->GetMultikeyMapping(prefix, actions))
     1541                found = true;
     1542            if (context != "Global" && gctx->GetMultikeyMapping(prefix, actions))
     1543                found = true;
     1544            if (found)
     1545            {
     1546                prefixContext->SetStatus(MultikeyContext::kMKfullMatch);
     1547                return false;
     1548            }
     1549            // Check for multikey prefix matches.
     1550            if (kctx && kctx->IsMultikeyPrefix(prefix))
     1551                found = true;
     1552            if (context != "Global" && gctx->IsMultikeyPrefix(prefix))
     1553                found = true;
     1554            if (found)
     1555            {
     1556                prefixContext->SetStatus(MultikeyContext::kMKpartialMatch);
     1557                return false;
     1558            }
     1559            // No exact or prefix matches.  Abort if there was queued input.
     1560            if (wasEmpty)
     1561            {
     1562                prefixContext->SetStatus(MultikeyContext::kMKnoPrefix);
     1563            }
     1564            else
     1565            {
     1566                prefixContext->SetStatus(MultikeyContext::kMKbadMatch);
     1567                return false;
     1568            }
     1569        }
     1570        else // prefix was already full, couldn't add a 5th key
     1571        {
     1572            prefixContext->SetStatus(MultikeyContext::kMKbadMatch);
     1573        }
     1574    }
     1575
    14641576    QStringList localActions;
    14651577    if (allowJumps && (d->jumpMap.count(keynum) > 0) &&
    14661578        (!d->jumpMap[keynum]->localAction.isEmpty()) &&
    void MythMainWindow::ClearKeyContext(const QString &context) 
    15231635}
    15241636
    15251637void MythMainWindow::BindKey(const QString &context, const QString &action,
    1526                              const QString &key)
     1638                             const QString &key, const QString &multikey)
    15271639{
    15281640    QKeySequence keyseq(key);
    15291641
    void MythMainWindow::BindKey(const QString &context, const QString &action, 
    15511663        if (action == "ESCAPE" && context == "Global" && i == 0)
    15521664            d->escapekey = keynum;
    15531665    }
     1666
     1667    QStringList multidummyaction("");
     1668    if (d->keyContexts.value(context)->GetMultikeyMapping(multikey, multidummyaction))
     1669    {
     1670        VERBOSE(VB_GENERAL, QString("Multikey %1 is bound to multiple actions "
     1671                                    "in context %2.")
     1672                .arg(multikey).arg(context));
     1673    }
     1674    d->keyContexts.value(context)->AddMultikeyMapping(multikey, action);
    15541675}
    15551676
    15561677void MythMainWindow::RegisterKey(const QString &context, const QString &action,
    15571678                                 const QString &description, const QString &key)
    15581679{
    15591680    QString keybind = key;
     1681    QString multikeybind = "";
    15601682
    15611683    MSqlQuery query(MSqlQuery::InitCon());
    15621684
    15631685    if (d->m_useDB && query.isConnected())
    15641686    {
    1565         query.prepare("SELECT keylist, description FROM keybindings WHERE "
     1687        query.prepare("SELECT keylist, description, multikey FROM keybindings WHERE "
    15661688                      "context = :CONTEXT AND action = :ACTION AND "
    15671689                      "hostname = :HOSTNAME ;");
    15681690        query.bindValue(":CONTEXT", context);
    void MythMainWindow::RegisterKey(const QString &context, const QString &action, 
    15731695        {
    15741696            keybind = query.value(0).toString();
    15751697            QString db_description = query.value(1).toString();
     1698            multikeybind = query.value(2).toString();
    15761699
    15771700            // Update keybinding description if changed
    15781701            if (db_description != description)
    void MythMainWindow::RegisterKey(const QString &context, const QString &action, 
    15991722        else
    16001723        {
    16011724            QString inskey = keybind;
     1725            QString insmultikey = multikeybind;
    16021726
    16031727            query.prepare("INSERT INTO keybindings (context, action, "
    1604                           "description, keylist, hostname) VALUES "
     1728                          "description, keylist, hostname, multikey) VALUES "
    16051729                          "( :CONTEXT, :ACTION, :DESCRIPTION, :KEYLIST, "
    1606                           ":HOSTNAME );");
     1730                          ":HOSTNAME, :MULTIKEY );");
    16071731            query.bindValue(":CONTEXT", context);
    16081732            query.bindValue(":ACTION", action);
    16091733            query.bindValue(":DESCRIPTION", description);
    16101734            query.bindValue(":KEYLIST", inskey);
    16111735            query.bindValue(":HOSTNAME", GetMythDB()->GetHostName());
     1736            query.bindValue(":MULTIKEY", insmultikey);
    16121737
    16131738            if (!query.exec() && !(GetMythDB()->SuppressDBMessages()))
    16141739            {
    void MythMainWindow::RegisterKey(const QString &context, const QString &action, 
    16171742        }
    16181743    }
    16191744
    1620     BindKey(context, action, keybind);
     1745    BindKey(context, action, keybind, multikeybind);
    16211746}
    16221747
    16231748QString MythMainWindow::GetKey(const QString &context,
  • mythtv/libs/libmythui/mythmainwindow.h

    diff --git a/mythtv/libs/libmythui/mythmainwindow.h b/mythtv/libs/libmythui/mythmainwindow.h
    index c007067..457ecda 100644
    a b class MythPainterWindowVDPAU; 
    2727class MythPainterWindowD3D9;
    2828class MythRender;
    2929
     30class MultikeyContext
     31{
     32 public:
     33    enum MultikeyStatus {
     34        kMKnoPrefix,
     35        kMKpartialMatch,
     36        kMKfullMatch,
     37        kMKbadMatch
     38    };
     39    MultikeyContext(void)
     40        : oldPrefix(""), currentPrefix(""), status(kMKnoPrefix) {}
     41    void SetStatus(MultikeyStatus why) { status = why; }
     42    const QString &GetPrefix(void) { return currentPrefix; }
     43    bool AddKey(int key);
     44    void Commit(MultikeyStatus &s, QString &prefix) {
     45        prefix = currentPrefix;
     46        s = status;
     47        if (status != kMKpartialMatch)
     48        {
     49            currentPrefix = "";
     50            status = kMKnoPrefix;
     51        }
     52        oldPrefix = currentPrefix;
     53    }
     54 private:
     55    QString oldPrefix;
     56    QString currentPrefix;
     57    MultikeyStatus status;
     58};
     59
    3060class MUI_PUBLIC MythMainWindow : public QWidget
    3161{
    3262    Q_OBJECT
    class MUI_PUBLIC MythMainWindow : public QWidget 
    4878    MythScreenStack *GetStackAt(int pos);
    4979
    5080    bool TranslateKeyPress(const QString &context, QKeyEvent *e,
    51                            QStringList &actions, bool allowJumps = true)
     81                           QStringList &actions, bool allowJumps = true,
     82                           MultikeyContext *prefixContext = NULL)
    5283                           MUNUSED_RESULT;
    5384
    5485    void ResetKeys(void);
    5586    void ClearKey(const QString &context, const QString &action);
    5687    void ClearKeyContext(const QString &context);
    5788    void BindKey(const QString &context, const QString &action,
    58                  const QString &key);
     89                 const QString &key, const QString &multikey);
    5990    void RegisterKey(const QString &context, const QString &action,
    6091                     const QString &description, const QString &key);
    6192    QString GetKey(const QString &context, const QString &action) const;
  • mythtv/programs/mythfrontend/keybindings.cpp

    diff --git a/mythtv/programs/mythfrontend/keybindings.cpp b/mythtv/programs/mythfrontend/keybindings.cpp
    index e15de26..cffc320 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&)