The attached diff will apply to MythTV 0.18.1 to give
the Line Edits a popup virtual keyboard. I didn't
apply it to CVS because I've been having major
problems getting CVS to compile so I can test it, but
it should be easy to port to cvs. So if anyone wants
to test this on CVS that would be great.
I could port it to latest CVS, but won't even be able
to test if it even compiles (though it likely does -
the affected files aren't much different from 0.18.1).
The only two files affected are
libs/libmyth/mythwidgets{.cpp, .h}.
Anywho, this is what the virtual keyboard does:
1) Activated by hitting "Enter" (or "Select") while a
Line Edit is in focus (keyboard or remote). (Takes
away the default hitting "Next" behavvior. - to go to
the next window in myth, just highlight "Next"
explicitly.)
2) Provides all the important keys from the keyboard:
all printable characters, plus shift, shift-lock,
space, left arrow, right arrow, delete, and backspace,
and a "Done" key (where "Return" normally sits) for
closing the popup. The layout follows the layout of
the keys on my laptop.
3) Goes below the currently active line edit unless
there's not enough room in the myth window. In that
case, it appears above the line edit box. It also
keeps itself from extending off the myth window on
either side.
4) Closes with the Escape key or by hitting "Select"
on the "Done" button. (Doesn't work when "ESCAPE"
action is generated by a remote(lirc) - see below.)
Issues: (most important to least, in my opinion)
1) The "ESCAPE" action generated from lirc does not
close the popup. (Though it works fine with the
keyboard Escape button). This is because the custom
event for ESCAPE is not being sent to the Line Edit
widget if it's generated fromm a remote. I don't know
exactly how to get around this because I'm still
unfamiliar with the event generating routines in Myth.
I suppose I could kludge it by setting a timer when
MythLineEdit::focusOutEvent is called, and check 1
second later to see if it is still otu of focus. In
that case, the popup can be killed explicitly.
The easiest is to simply pass the "ESCAPE" events to
the currently focused widget, which in this case is
hte MythLineEdit.
2) Needs a way to enable or disable this feature
within the Myth setups. Might be nice to store the
value in the database so that it is remembered after
reboots.
3) The mouse poinyer appears on top of the virtual
keyboard popup when it is activated. I tried calling
setCursor(blankcursor), but it doesn't work. It's
relatively minor, just a cosmetic thing.
Other than the above notes, it works well (my
roommates like it), and is naturally applied wherever
there is a mythline edit. Depending on certain
things, the new popup-ized MythLineEdit could replace
all the MythRemoteLineEdit functionality as well.
Other possible enhancements:
1) Make it look more attractive (maybe at least more
obvious when one of the keys is highlighted).
2) Support different sizes.
3) Support other keyboard layouts ;).
4) Allow selection of text by hitting shift lock and
using the left-right arrow keys.
Anyway, I'm not much of a myth developer, I was just
messing aroudn with this and thought I'd submit it.
My apologies if I need to submit it in a different
format or something. I'd love to hear some feedback -
I hope it's useful.
...Jdiff -r mythtv-0.18.1/libs/libmyth/mythwidgets.cpp mythtv-0.18.1-vk/libs/libmyth/mythwidgets.cpp
322c322,329
< void MythLineEdit::keyPressEvent(QKeyEvent *e)
---
>
> // Virtual Keyboard-related stuff:
>
> const int popup_W = 450;
> const int popup_H = 155;
>
>
> int max(int a, int b)
324,326c331,387
< bool handled = false;
< QStringList actions;
< if (gContext->GetMainWindow()->TranslateKeyPress("qt", e, actions))
---
> if (a > b) { return a; }
> else { return b; }
> }
>
> int min(int a, int b)
> {
> if (a < b) { return a; }
> else { return b; }
> }
>
> VKPopup::VKPopup(QWidget *parent, const char* name)
> : QFrame(parent, name, WType_Popup)
> {
> setFrameStyle( WinPanel|Raised );
> resize(popup_W,popup_H);
>
> QString k[alphabuts] = {
> "`", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=",
> "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "[", "]", "\\",
> "a", "s", "d", "f", "g", "h", "j", "k", "l", ";", "'",
> "z", "x", "c", "v", "b", "n", "m", ",", ".", "/",
> " "
> };
>
> QString k2[alphabuts] = {
> "~", "!", "@", "#", "$", "%", "^", "&&", "*", "(", ")", "_", "+",
> "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "{", "}", "|",
> "A", "S", "D", "F", "G", "H", "J", "K", "L", ":", "\"",
> "Z", "X", "C", "V", "B", "N", "M", "<", ">", "?",
> " "
> };
>
> static int x[alphabuts] = {
> 5, 35, 65, 95, 125, 155, 185, 215, 245, 275, 305, 335, 365,
> 40, 70, 100, 130, 160, 190, 220, 250, 280, 310, 340, 370, 400,
> 45, 75, 105, 135, 165, 195, 225, 255, 285, 315, 345,
> 50, 80, 110, 140, 170, 200, 230, 260, 290, 320,
> 160
> };
>
> static int y[alphabuts] = {
> 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
> 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35,
> 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65,
> 95, 95, 95, 95, 95, 95, 95, 95, 95, 95,
> 125
> };
>
> static int w[alphabuts] = {
> 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
> 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
> 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
> 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
> 80
> };
>
> for (int i=0; i<alphabuts; i++)
328,331c389,391
< for (unsigned int i = 0; i < actions.size() && !handled; i++)
< {
< QString action = actions[i];
< handled = true;
---
> but[i] = new VKButton(this, k[i], k[i], k2[i], (QLineEdit *)parent);
> but[i]->setGeometry(x[i], y[i], w[i], 25);
> }
333,340c393,522
< if (action == "UP")
< focusNextPrevChild(false);
< else if (action == "DOWN")
< focusNextPrevChild(true);
< else if (action == "SELECT" && e->text().isNull())
< e->ignore();
< else
< handled = false;
---
> butDone = new QPushButton("Done", this);
> connect( butDone, SIGNAL( released() ), this, SLOT( close() ) );
> butDone->setGeometry(375, 65, 70, 25);
> butDone->setFlat(true);
>
> butShift = new QPushButton("Shift", this);
> butShift->setGeometry(5, 125, 60, 25);
> butShift->setToggleButton( true );
> butShift->setFlat(true);
> butShift->setOn( false );
>
> butLock = new QPushButton("Lock", this);
> butLock->setGeometry(70, 125, 60, 25);
> butLock->setToggleButton( true );
> butLock->setFlat(true);
> butLock->setOn( false );
>
> butLeft = (VKButton *)new QPushButton("<-", this);
> butLeft->setGeometry(270, 125, 40, 25);
> butLeft->setFlat(true);
>
> butRight = new QPushButton("->", this);
> butRight->setGeometry(315, 125, 40, 25);
> butRight->setFlat(true);
>
> butBack = new QPushButton("Back", this);
> butBack->setGeometry(395, 5, 50, 25);
> butBack->setFlat(true);
>
> butDel = new QPushButton("Del", this);
> butDel->setGeometry(350, 95, 40, 25);
> butDel->setFlat(true);
>
> // Default button. "setFlat" is used as a way to highlight
> // buttons, since the virtual keyboard is never in focus.
> butDone->setFlat(false);
>
> connect(butShift, SIGNAL( released() ), this, SLOT( shiftOnOff() ) );
> connect(butLock, SIGNAL( released() ), this, SLOT( lockOnOff() ) );
> connect(butLeft, SIGNAL( released() ), parent, SLOT( lCursor() ) );
> connect(butRight, SIGNAL( released() ), parent, SLOT( rCursor() ) );
> connect(butBack, SIGNAL( released() ), parent, SLOT( backPressed() ) );
> connect(butDel, SIGNAL( released() ), parent, SLOT( delPressed() ) );
>
> but[numbuts-7] = (VKButton *)butDel;
> but[numbuts-6] = (VKButton *)butBack;
> but[numbuts-5] = (VKButton *)butShift;
> but[numbuts-4] = (VKButton *)butLock;
> but[numbuts-3] = (VKButton *)butLeft;
> but[numbuts-2] = (VKButton *)butRight;
> but[numbuts-1] = (VKButton *)butDone;
> }
>
> VKPopup::~VKPopup()
> {
> for (int i=0; i<alphabuts; i++)
> {
> if (but[i]) { delete but[i]; }
> }
> if (butDel) { delete butDel; }
> if (butBack) { delete butBack; }
> if (butShift) { delete butShift; }
> if (butLock) { delete butLock; }
> if (butLeft) { delete butLeft; }
> if (butRight) { delete butRight; }
> if (butDone) { delete butDone; }
> }
>
> void VKPopup::lockOnOff()
> {
> butShift->setOn(butLock->isOn());
> shiftOnOff();
> }
>
> void VKPopup::shiftOff()
> {
> if (!butLock->isOn())
> {
> butShift->setOn(false);
> }
> shiftOnOff();
> }
>
> void VKPopup::shiftOnOff()
> {
> bool newstate = butShift->isOn();
> for (int i=0; i<alphabuts; i++)
> {
> but[i]->setShiftState(newstate);
> }
> if (!newstate)
> {
> butLock->setOn(false);
> }
> }
>
> int findDistance(QPoint a, QPoint b)
> {
> // For helping determine next button to highlight.
> return (a-b).manhattanLength();
> }
>
> QPushButton * VKPopup::sideButton(QPushButton * oldbut, int dirkey)
> {
> // Returns button interpreted as being to one side
> // (left, right, up, down)
> QRect butrect = oldbut->frameGeometry();
> QPoint newpoint;
> switch (dirkey) {
> case Qt::Key_Left:
> newpoint = QPoint(butrect.left()-20, butrect.center().y());
> break;
> case Qt::Key_Right:
> newpoint = QPoint(butrect.right()+20, butrect.center().y());
> break;
> case Qt::Key_Up:
> newpoint = QPoint(butrect.center().x(), butrect.top()-20);
> break;
> case Qt::Key_Down:
> newpoint = QPoint(butrect.center().x(), butrect.bottom()+20);
> break;
> }
> int ibest = 0;
> int bestdist = findDistance(newpoint, but[ibest]->frameGeometry().center());
> for (int i=0; i<numbuts; i++)
> {
> int thisdist = findDistance(newpoint, but[i]->frameGeometry().center());
> if (thisdist < bestdist) {
> bestdist = thisdist;
> ibest = i;
342a525,526
> return but[ibest];
> }
344,346c528,679
< if (!handled)
< if (rw || e->key() == Key_Escape)
< QLineEdit::keyPressEvent(e);
---
> void VKPopup::keyPressEvent(QKeyEvent *e)
> {
> if (e->type() == QEvent::KeyPress) {
> int selected = 0;
> for (int i=0; i<numbuts; i++)
> {
> if (!but[i]->isFlat())
> {
> selected = i;
> }
> }
> switch (e->key())
> {
> case Qt::Key_Escape:
> // Only works from keyboard right now.
> hide();
> e->accept();
> break;
> case Qt::Key_Return :
> but[selected]->animateClick();
> e->accept();
> break;
> case Qt::Key_Right :
> case Qt::Key_Left :
> case Qt::Key_Up :
> case Qt::Key_Down :
> but[selected]->setFlat(true);
> sideButton(but[selected], e->key())->setFlat(false);
> e->accept();
> break;
> default :
> QFrame::keyPressEvent(e);
> }
> }
> else
> {
> QFrame::keyPressEvent(e);
> }
> }
>
>
> VKButton::VKButton(QWidget *parent, const char* name, const char* text,
> const char* textSh, QLineEdit *ebox):
> QPushButton(text, parent, name)
> {
> mykey1 = text;
> mykey2 = textSh;
> mybox = ebox;
> setShiftState(false);
> setFlat(true);
> connect (this, SIGNAL( pressed() ), SLOT ( pressKey() ) );
> connect (this, SIGNAL( released() ), parent, SLOT( shiftOff() ) );
> }
>
> void VKButton::setShiftState(bool state)
> {
> if (state) { setText(mykey2); }
> else { setText(mykey1); }
> }
>
> void VKButton::pressKey()
> {
> if ((QString)text().at(0) == "&") { mybox->insert((QString)text().at(1)); }
> else { mybox->insert((QString)text().at(0)); }
> }
>
>
> MythLineEdit::~MythLineEdit()
> {
> if (popup) { delete popup; }
> }
>
> void MythLineEdit::Init()
> {
> rw = true;
> popup = new VKPopup(this, "Virtual Keyboard Popup");
> }
>
> void MythLineEdit::openPopup()
> {
> // Calculate the correct position for the virtual keyboard popup
> // and show it.
> QPoint newpos;
> if (mapTo(topLevelWidget(), QPoint(0,geometry().height()+popup_H+5)).y()
> < topLevelWidget()->frameGeometry().height())
> {
> newpos = QPoint(geometry().width()/2-popup_W/2,
> geometry().height() + 5);
> }
> else {
> newpos = QPoint(geometry().width()/2-popup_W/2, -5-popup_H);
> }
>
> newpos = QPoint(newpos.x()-max(0, mapTo(topLevelWidget(),newpos).x()
> + popup_W-topLevelWidget()->geometry().width()),
> newpos.y());
> newpos = QPoint(newpos.x()-min(0,mapTo(topLevelWidget(), newpos).x()), newpos.y());
> // FIXME: Cant't get the cursor to blank out for some reason.
> setCursor(Qt::BlankCursor);
> popup->move( mapToGlobal( newpos ) );
> popup->show();
> // Line edit keeps focus so cursor remains visible
> this->setFocus();
> }
>
> void MythLineEdit::keyPressEvent(QKeyEvent *e)
> {
> if (popup->isShown())
> {
> // So that popup gets keys even though not in focus.
> QApplication::sendEvent(popup, e);
> setFocus();
> }
> else
> {
> bool handled = false;
> QStringList actions;
> if (gContext->GetMainWindow()->TranslateKeyPress("qt", e, actions))
> {
> for (unsigned int i = 0; i < actions.size() && !handled; i++)
> {
> QString action = actions[i];
> handled = true;
>
> if (action == "UP")
> focusNextPrevChild(false);
> else if (action == "DOWN")
> focusNextPrevChild(true);
> else if (action == "SELECT")
> openPopup();
> else
> handled = false;
>
> // Old code:
> // else if (action == "SELECT" && e->text().isNull())
> // e->ignore();
> // Override standard "SELECT" on an Line Edit
> // so that virtual keyboard can pop up
>
> // FIXME: I can't seem to get "ESCAPE" actions from the remote to
> // close the virtual keyboard. Currently, it seems the main window
> // is stealing the key event and won't let us detect it. It works
> // fine from the keyboard, however. Probably need to change the
> // custom event code or get target code. Right now we have to use
> // the "Done" key to exit the popup.
> }
> }
>
> if (!handled)
> if (rw || e->key() == Key_Escape)
> QLineEdit::keyPressEvent(e);
> }
diff -r mythtv-0.18.1/libs/libmyth/mythwidgets.h mythtv-0.18.1-vk/libs/libmyth/mythwidgets.h
114a115,159
> const int alphabuts = 48;
> const int specialbuts = 7;
> const int numbuts = alphabuts + specialbuts;
>
>
> class VKButton : public QPushButton
> {
> Q_OBJECT
> public:
> VKButton( QWidget *parent=0, const char* name=0, const char* text=0,
> const char* textSh=0, QLineEdit *ebox=0);
> void setShiftState(bool state);
>
> private slots:
> void pressKey();
>
> private:
> QString mykey1, mykey2;
> QLineEdit *mybox;
> };
>
>
> class VKPopup : public QFrame
> {
> Q_OBJECT
> public:
> VKPopup( QWidget *parent=0, const char* name=0 );
> ~VKPopup();
>
> protected:
> QPushButton * sideButton(QPushButton * oldbut, int dirkey);
>
> private slots:
> virtual void keyPressEvent(QKeyEvent *e);
> void lockOnOff();
> void shiftOnOff();
> void shiftOff();
>
> private:
> VKButton *but[numbuts];
> QPushButton *butShift, *butLock, *butDone, *butLeft, *butRight,
> *butBack, *butDel;
> };
>
>
120,122c165,166
< QLineEdit(parent, widgetName) { rw = true; };
<
< MythLineEdit(const QString& contents, QWidget *parent=NULL,
---
> QLineEdit(parent, widgetName) { rw = true; Init(); }
> MythLineEdit(const QString& contents, QWidget *parent=NULL,
124c168,169
< QLineEdit(contents, parent, widgetName) { rw = true; };
---
> QLineEdit(contents, parent, widgetName) { rw = true; Init(); };
> ~MythLineEdit();
131a177,181
> void delPressed() { QLineEdit::del(); };
> void backPressed() { QLineEdit::backspace(); };
> void lCursor() { QLineEdit::cursorBackward(false); };
> void rCursor() { QLineEdit::cursorForward(false); };
> void openPopup();
136a187
> void Init();
143a195
> VKPopup* popup;
_______________________________________________
mythtv-dev mailing list
[email protected]
http://mythtv.org/cgi-bin/mailman/listinfo/mythtv-dev