Hello. I've tried to subscribe to openmoko-devel but haven't passed gotchas.
I'm not sure where this should be sent then, sorry if it is off topic here. I wanted to add some Catalan layouts to the svg on-screen keyboards in qtmoko, but found the dead keys too dead and I also wanted to have a numeric keypad. So I finally drew some new keyboards with inkscape and change the qtmoko code to allow for a key to link to another layout (and optionally return to some layout after one keystroke). I used that so that dead keys link to a layout with accented characters or diaeresis. So now it works more similar to a desktop keyboard. The downside is the combinatorial explosion of layouts, burdening maintenance. Now I have abc.svg abc_shift.svg abc_acute.svg abc_acute_shift.svg abc_grave.svg abc_grave_shift.svg abc_diaeresis.svg abc_diaeresis_shift.svg And I need to propagate most changes in one to the others. It'd be best to deal with dead keys, multikey or similar in C++ code, but I haven't found out all details. I also have n12.svg with numbers and symbols. And I should do the same for qwerty.svg and qwerty_shift.svg. And landscape/portrait. The new code can also work with the old layouts, because when it finds a Shift, CapsLock or Mode_switch key without a link it links it to the same layout as used before (shift links to the previous or next layout, mode_switch to the next non-shifted layout, always in alphabetical order, and in the same directory as the current layout). I you write in several alphabets you could also keep one directory for each alphabet and have a layout that works as a layout chooser menu. In order to link a layout to a key in inkscape just right click on the key, select create link, then right click again, select link properties and write the filename of the destination layout in the href box. If you want the layout to go away after one keystroke, then add to the filename ?return to bring the current layout back, or ?return=some.svg to go to a different layout after the keystroke. This will create an svg:a element with the xlink:href you wrote in the href box and the svg:g element inside it if that's what you selected. If you do it on a rectangle o text inside the svg:g it will create an svg:a between it and the svg:g but it will still work (just don't put 2 svg:a inside the same svg:g). Theoretically you could have 2 or more svg:g inside an svg:a and it will work, but I can't imagine why anyone would want that. Relative urls are resolved from the url of the layout where the svg:a element is, and relative return urls are resolved from the url the link points to. Only file: urls are allowed now. So the functionality of linking keys to layouts might be useful, although for my purpose it leads to too many similar svgs, one should maybe improve the key composing logic (or create some tool to write the diferent layout variants from one layout to automate maintenance of svgs). Some things I have thought but didn't have time to try: - see why my terminal doesn't like non-ascii characters (they work in sms composing) - include in svg a reference to a replacement svg for different resolution/aspect ratio/orientation - have the transparency increase gradually after a time of not typing, so it is easier to see the terminal below - write a layout editor for X11 desktops, so that you can select, the layout size (portrait or landscape), a number of lines, a number of keys for each line, and then just type the keys in a phisical keyboard and have the editor calculate sizes and positons and capture keypresses and assign ids (keycodes and unicode points) automatically. I find inkscape too slow to work with, having to look up the codes and size/align keys semimanually. I have the code locally committed to a git clone I made according to the qtmoko readme. I don't want a github account and might look to see if I find some better git hosting, or if it is possible to self host git in a purely static webserver. But I'm attaching a patch here since it is smallish. (there's a new scripts/qtmoko-chroot-jessie.sh because I couldn't compile with scripts/qtmoko-chroot-armhf.sh, mostly because I tried from an armhf jessie, but I'm not sure if there's any change that should be propagated to scripts/qtmoko-chroot-armhf.sh) If I should send this somewhere else or somehow else, please tell me. Bye
diff --git a/scripts/qtmoko-chroot-jessie.sh b/scripts/qtmoko-chroot-jessie.sh new file mode 100644 index 0000000..5dfd522 --- /dev/null +++ b/scripts/qtmoko-chroot-jessie.sh @@ -0,0 +1,147 @@ +#modified qtmoko-chroot-armhf.sh +# to be able to compile from a host running Debian jessie armhf +# (I thought I'd do it natively but I couldn't install some dependencies +# and feared it might get linked to too new libraries, so I did +# it with a chroot like if I was crosscompiling) + +# For the first time it installs the chroot later it just enters it + +# set -o verbose + +# We will use ext4 image for chroot +if [ ! -e ../qtmoko-chroot-armhf.img ] +then + echo "Creating chroot-build ext4 image" + dd if=/dev/zero of=../qtmoko-chroot-armhf.img bs=1024 count=3145728 + mkfs.ext4 ../qtmoko-chroot-armhf.img +fi + +# Directory for chroot mount +if [ ! -d ../qtmoko-chroot ] +then + mkdir -p ../qtmoko-chroot +fi + +# Mount the image if not mounted yet +if [ ! -d ../qtmoko-chroot/bin ] +then + echo "Mounting image" + mount -o loop ../qtmoko-chroot-armhf.img ../qtmoko-chroot +fi + +# Install wheezy rootfs if not installed yet +if [ ! -d ../qtmoko-chroot/bin ] +then + if [ ! -f /usr/bin/cdebootstrap ] + then + echo "Installing cdebootstrap package" + apt-get install cdebootstrap + fi + + echo "Installing chroot packages" + until cdebootstrap --flavour=minimal --include=build-essential,git,openssh-client,ccache,locales,procps,psmisc,libxext-dev,libasound2-dev,libdbus-1-dev,libssl-dev,libts-dev,libbluetooth-dev,libxtst-dev,libpng12-dev,libjpeg8-dev,libv4l-dev,libspeexdsp-dev,libglib2.0-dev,libsqlite3-dev,quilt,libgstreamer0.10-dev,libgstreamer-plugins-base0.10-dev,libpulse-dev,libgps-dev,wget,curl wheezy ../qtmoko-chroot http://cdn.debian.net/debian/; do + : + done +fi + +if [ ! -d ../qtmoko-chroot/proc/bus ] +then + echo "Mounting chroot dirs" + mount -t proc none ../qtmoko-chroot/proc + mount -t sysfs none ../qtmoko-chroot/sys + mount -o bind /dev ../qtmoko-chroot/dev + + if [ ! -d ../qtmoko-chroot/root/qte ] + then + mkdir -p ../qtmoko-chroot/root/qte + fi + mount -o bind .. ../qtmoko-chroot/root/qte + + if [ ! -d ../qtmoko-chroot/var/lib/dbus ] + then + mkdir -p ../qtmoko-chroot/var/lib/dbus + fi + mount -o bind /var/lib/dbus ../qtmoko-chroot/var/lib/dbus + mount -o bind /var/run/ ../qtmoko-chroot/var/run +fi + +if [ ! -e ../qtmoko-chroot/usr/bin/arm-linux-gnueabihf-gcc-4.7 ] +then + cat > ../qtmoko-chroot/finish_chroot_install.sh <<__END__ +#!/bin/bash +echo "Installing emdebian toolchain" +apt-get update +apt-get install emdebian-archive-keyring +#echo "deb http://cdn.debian.net/debian wheezy main contrib non-free" > /etc/apt/sources.list +echo "deb http://www.emdebian.org/debian unstable main" >> /etc/apt/sources.list +apt-get update +apt-get install g++-4.7-arm-linux-gnueabihf + +echo "Installing xapt and ARM qtmoko dependencies" +apt-get install xapt +xapt -a armhf -m libxext-dev libasound2-dev libdbus-1-dev libssl-dev libts-dev libbluetooth-dev libxtst-dev libpng12-dev libjpeg8-dev libv4l-dev libspeexdsp-dev libglib2.0-dev libsqlite3-dev libgstreamer0.10-dev libgstreamer-plugins-base0.10-dev libvorbis-dev libpulse-dev libssl-dev libgps-dev +#not found : +#pushd /usr/arm-linux-gnueabihf/lib/ +pushd /usr/lib/arm-linux-gnueabihf/ +#for ARM wheezy chroot in ARM jessie host. +#jessie creates these simlynks installing binutils, but wheezy didn't have them +ln -s ld-linux-armhf.so.3 ld-linux.so.3 +cd /usr/bin +for b in objdump nm addr2line c++filt strings ar gprof objcopy size readelf as ld.bfd elfedit strip ranlib ld.gold ld gold ; do { + ln -s "${b}" "arm-linux-gnueabihf-${b}" +} ; done +popd + +# For some reason libpulsecommon is not fetched by xapt, so do it manually +cd /tmp +curl -O http://ftp.de.debian.org/debian/pool/main/p/pulseaudio/libpulse0_2.0-6.1_armhf.deb +dpkg -x libpulse0_2.0-6.1_armhf.deb . +#dest dir not found +#cp usr/lib/arm-linux-gnueabihf/pulseaudio/libpulsecommon-2.0.so /usr/arm-linux-gnueabihf/lib/ +cp usr/lib/arm-linux-gnueabihf/pulseaudio/libpulsecommon-2.0.so /usr/lib/arm-linux-gnueabihf/ +#for ARM wheezy chroot in ARM jessie host. +#native wheezy arm compiler seems to be 4.6, but I got errors linking qt, +#possibly 4.7 is required +#gdb is needed when configure decides to compile qt +# or a version test fails when linking qrc_linguist +#libvorbis-dev is needed to avoid an error compiling /root/qte/qtmoko/src/plugins/content/ogg/oggcontentplugin.cpp +apt-get install gcc-4.7 g++-4.7 gcc-4.7 libstdc++6-4.7-dev gdb libvorbis-dev + + +echo "export PATH=/usr/lib/ccache:\$PATH" >> /root/.bashrc +echo "PS1='qtmoko-chroot:\w\\\$ '" >> /root/.bashrc +echo "export LANG=C" >> /root/.bashrc +echo "export LC_ALL=C" >> /root/.bashrc +#for ARM wheezy chroot in ARM jessie host. +#native wheezy arm compiler seems to be 4.6, but I got errors linking qt, +#possibly 4.7 is required +# note: this changes most of CXX invocations, but I've still seen +# some g++ invocations fly past among may g++4.7 (I suppose it is +# because there is still g++ in /qtmoko/qtopiacore/qt/mkspecs/linux-g++/qmake.conf +# but I'm leaving it like this because configure built and tested Qt OK +echo "export CC=gcc-4.7" >> /root/.bashrc +echo "export CXX=g++-4.7" >> /root/.bashrc + + + +echo "+-----------------------------------------------------------------+" +echo "| Success! You can now build QtMoko like this: |" +echo "| |" +echo "| mkdir -p /root/qte/build |" +echo "| cd /root/qte/build |" +echo "| ../qtmoko/configure -device gta04 |" +echo "| make |" +echo "| export LD_LIBRARY_PATH=/root/qte/build/qtopiacore/host/lib/ |" +echo "| make install |" +echo "| |" +echo "+-----------------------------------------------------------------+" + +__END__ + + chmod +x ../qtmoko-chroot/finish_chroot_install.sh + chroot ../qtmoko-chroot /finish_chroot_install.sh + rm -f ../qtmoko-chroot/finish_chroot_install.sh +fi + +chroot ../qtmoko-chroot /bin/bash + diff --git a/src/plugins/inputmethods/svgkeyboard/kblayoutparser.cpp b/src/plugins/inputmethods/svgkeyboard/kblayoutparser.cpp new file mode 100644 index 0000000..f2f8c49 --- /dev/null +++ b/src/plugins/inputmethods/svgkeyboard/kblayoutparser.cpp @@ -0,0 +1,215 @@ +/**************************************************************************** +** +** +** Copyright (C) 2015 Xavi Drudis Ferran <[email protected]> +** +** +** This file may be used under the terms of the GNU General Public License +** version 2.0 or later as published by the Free Software Foundation and appearing +** in the file LICENSE.GPL included in the packaging of this file. +** +** Please review the following information to ensure GNU General Public +** Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html. +** +** +****************************************************************************/ + +#include "kblayoutparser.h" +#include <QXmlSimpleReader> +#include <QtDebug> +#include <climits> +QUrl KbLayoutParser::url(){ + return mUrl; +} + + +KbLayoutParser::KbLayoutParser(): rx("key_([0-9a-fA-F]+_[0-9a-fA-F]+)"),mUrl() +{ + this->keyList=NULL; + this->links=NULL; +} + +void KbLayoutParser::parse(const QUrl & svgURL, QStringList & keyList, QHash<QString,KbLayoutLink *> &links) +{ + mUrl=svgURL; + this->keyList=&keyList; + this->links=&links; + QXmlSimpleReader reader; + mError=""; + reader.setContentHandler(this); + reader.setErrorHandler(this); + QFile svgFile(svgURL.toLocalFile()); + if (!reader.parse(QXmlInputSource(&svgFile))) { + qCritical() << "Could not parse " << svgURL; + } +} + +bool KbLayoutParser::error ( const QXmlParseException & exception ) +{ + qCritical()<< errorMsg(exception); + return true; +} + +QString KbLayoutParser::errorString () const +{ + return mError; +} + +QString KbLayoutParser::errorMsg( const QXmlParseException & exception) +{ + QString msg( "publicId:%1, systemId: %2, line: %3, col: %4 msg:%5\n"); + msg = msg.arg(exception.publicId(),exception.systemId()) + .arg(exception.lineNumber()).arg(exception.columnNumber()) + .arg(exception.message()); + mError+=msg; + return msg; +} + +bool KbLayoutParser::fatalError ( const QXmlParseException & exception ) +{ + qCritical()<< "FATAL:" << errorMsg(exception); + return false; +} + +bool KbLayoutParser::warning ( const QXmlParseException & exception ) +{ + mError+="Warning:"; + qWarning()<< errorMsg(exception); + return true; +} + +bool KbLayoutParser::startDocument () +{ + links->clear(); + keyList->clear(); + currentLink=QUrl(); + currentKeyId=QString(); + currentKeyDepth=-1; + currentKeyLinks=0; + return true; +} + +KbLayoutLink * createLink(QUrl url, QUrl base) { + KbLayoutLink *res=new KbLayoutLink(); + res->returnLink=NULL; + res->hrefLayout=NULL; + res->returnLayout=NULL; + url=base.resolved(url); + // We ignore fragments because we only show full svg layouts. + // This might allow to override default treatment of unlinked + // Shift, Caps or mode_switch keys by linking with xlink:href="#" . + // Does this make sense ? + url.setFragment(QString()); + if (url.isEmpty() ) { + res->href=NULL; + } else { + res->href=url; + QStringList retValues =res->href.allQueryItemValues("return"); + if (retValues.count()>0) { + if (retValues.count()>1) { + qWarning()<< "Ignoring multiple return urls in " << url << ". We will only use " << retValues[0]; + } + QString retLink = retValues[0]; + if (retLink.isEmpty() ) { + res->returnLink=base; + } else { + res->returnLink=res->href.resolved(QUrl(retLink)); + res->returnLink.setFragment(QString()); + res->returnLink.removeAllQueryItems("return"); + if (res->returnLink.queryItems().count()==0) { + res->returnLink.setEncodedQuery(QByteArray()); // remove trailing "?" + } + } + res->href.removeAllQueryItems("return"); + if (res->href.queryItems().count()==0) { + res->href.setEncodedQuery(QByteArray()); // remove trailing "?" + } + } + } + return res; +} + +bool KbLayoutParser::startElement ( const QString & namespaceURI, const QString & localName, const QString &, const QXmlAttributes & atts ) +{ + if (localName=="a" && namespaceURI=="http://www.w3.org/2000/svg" ) { + if (currentLink.isEmpty()) { + int atIndex = atts.index("http://www.w3.org/1999/xlink","href"); + if (atIndex>-1) { + currentLink=QUrl(atts.value(atIndex)); + } + if (!currentKeyId.isNull()) { // link inside key + if (++currentKeyLinks>1) { + mError+="Can't have multiple links inside a key.Id: key_"+currentKeyId+"\n"; + return false; + } else { + links->insert(currentKeyId,createLink(currentLink,mUrl)); + } + + } + } else { + mError+="SVG does not allow an <a> element inside another <a> element\n"; + return false; + } + } //for this parser you don't really need both an a and g element, + //you could have <svg:a id="key_1a_2b" xlink:href="a.svg"> + // and then graphic content inside without svg:g. But then the key is not properly + // rendered, because aparently you can't pass the id of an svg:a element to QSvgRenderer.render(...) + // as you can pass it the id of a svg:g element. + // So in practice you need one or more svg:g (more than one would work but does not seem useful) + // inside one svg:a (having one svg:a inside an svg:g + // works too, if that's easier in the svg editor, but make sure there's only one a inside the svg:g + //). So you can have a link for more than one key (why?) but you may not have more than one link in a key + // (we would not know where to jump when the key is pressed). + int idIndex=atts.index("","id"); + bool isKey=false; + if (idIndex>-1) { + // id is like key_123_345 , keyId like 123_435 + QString id = atts.value(idIndex); + isKey=rx.exactMatch(id); + if (isKey) { + QString keyId=rx.cap(1); + if (currentKeyId.isNull()) { + keyList->append(keyId); + if (!currentLink.isEmpty()) { // key inside link + links->insert(id,createLink(currentLink,mUrl)); + } + } else { + mError+="Can't have a key ("+id+") inside a key ("+currentKeyId+")\n"; + return false; + } + currentKeyId=id; + currentKeyDepth=0; + currentKeyLinks=0; + } else { + if (rx.indexIn(id)>=0) { + warning(QXmlParseException("ignoring quasi-key id=\""+id+"\" (extra text in id value)")); + } + } + } + if (!isKey){ + if (!currentKeyId.isNull()) { + if (currentKeyDepth==INT_MAX) { + mError+="XML too deep below id "+currentKeyId+"\n"; + return false; + } + currentKeyDepth++; + } + } + return true; +} + +bool KbLayoutParser::endElement ( const QString & namespaceURI, const QString & localName, const QString & ) +{ + if (localName=="a" && namespaceURI=="http://www.w3.org/2000/svg" ) { + currentLink=QUrl(); + } + if (!currentKeyId.isNull()) { + if ( currentKeyDepth-- == 0) { + currentKeyId=QString(); + } + } + return true; +} + + diff --git a/src/plugins/inputmethods/svgkeyboard/kblayoutparser.h b/src/plugins/inputmethods/svgkeyboard/kblayoutparser.h new file mode 100644 index 0000000..e87629e --- /dev/null +++ b/src/plugins/inputmethods/svgkeyboard/kblayoutparser.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** +** Copyright (C) 2015 Xavi Drudis Ferran <[email protected]> +** +** +** This file may be used under the terms of the GNU General Public License +** version 2.0 or later as published by the Free Software Foundation and appearing +** in the file LICENSE.GPL included in the packaging of this file. +** +** Please review the following information to ensure GNU General Public +** Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html. +** +** +****************************************************************************/ +#ifndef KBLAYOUTPARSER_H +#define KBLAYOUTPARSER_H + +#include <QXmlDefaultHandler> +#include <QUrl> +#include <QStringList> +#include <QHash> +#include <QRegExp> + +struct KeyLayout; + +struct KbLayoutLink { + + QUrl href; //url of the svg file with the layout to show when the key is pressed + // null if they key dies not change layout (but still avoids guessing a link for old layouts support) + QUrl returnLink; // url of the layout to return to after the next key is pressed + // null if href layout must stay shown + KeyLayout *hrefLayout; // if the link has already been traversed this points to the layout in svg + // null if we haven't loaded it yet or we have loaded it from another + // link and don't know yet we can use it here + KeyLayout *returnLayout; // if the returnLink is not null and we have already traversed this link + // this points to the layout in returnLink +}; + + +KbLayoutLink * createLink(QUrl url, QUrl base) ; + +class KbLayoutParser : public QXmlDefaultHandler +{ + QRegExp rx; + QUrl mUrl; + QHash<QString, KbLayoutLink *> *links; + QStringList *keyList; + QString mError; + QUrl currentLink; + QString currentKeyId; + int currentKeyDepth; + int currentKeyLinks; +public: + KbLayoutParser(); + QUrl url(); + virtual void parse(const QUrl & svgURL, QStringList & keyList, QHash<QString,KbLayoutLink *> &links); + //QXmlErrorHandler + virtual bool error ( const QXmlParseException & exception ) ; + virtual QString errorString () const; + virtual bool fatalError ( const QXmlParseException & exception ); + virtual bool warning ( const QXmlParseException & exception ); + //QContentHandler + virtual bool endElement ( const QString & namespaceURI, const QString & localName, const QString & qName ) ; + virtual bool startDocument () ; + virtual bool startElement ( const QString & namespaceURI, const QString & localName, const QString & qName, const QXmlAttributes & atts ) ; +protected: + + virtual QString errorMsg( const QXmlParseException & exception); +}; + + + +#endif // KBLAYOUTPARSER_H diff --git a/src/plugins/inputmethods/svgkeyboard/keyboardframe.cpp b/src/plugins/inputmethods/svgkeyboard/keyboardframe.cpp index bc88fa0..6ff1ee5 100644 --- a/src/plugins/inputmethods/svgkeyboard/keyboardframe.cpp +++ b/src/plugins/inputmethods/svgkeyboard/keyboardframe.cpp @@ -4,6 +4,7 @@ ** ** Copyright (C) 2009 Trolltech ASA. ** Copyright (C) 2012 Radek Polak <[email protected]> +** Copyright (C) 2015 Xavi Drudis Ferran <[email protected]> ** ** Contact: Qt Extended Information ([email protected]) ** @@ -19,7 +20,6 @@ ****************************************************************************/ #include "keyboardframe.h" - #ifdef Q_WS_QWS #include <qwindowsystem_qws.h> #endif @@ -38,35 +38,18 @@ #include <QSoftMenuBar> #include <QApplication> #include <QDesktopWidget> - +#include <QUrl> +#include <QChar> // Add keys from svg file to list. The keys in svg are in form id="key_xxx" // where xxx is hex value from Qt::Key or unicode value of given key. -static void addKeys(const QString & svgFile, QStringList & keyList) -{ - QFile f(svgFile); - if (!f.open(QIODevice::ReadOnly)) { - qWarning() << "failed to open kbd file " << svgFile; - return; - } - QByteArray line; - for (;;) { - line = f.readLine(); - if (line.isEmpty()) - break; +//static void addKeys(const QString & svgFile, QStringList & keyList) +//{ - int start = line.indexOf("id=\"key_"); - if (start < 0) - continue; +// QFile f(svgFile); +// QUrl svgUrl = QUrl::fromLocalFile(f.fileName()); +// addKeys(svgUrl, &keyList); +//} - int end = line.indexOf('"', start + 8); - if (end < 0) - continue; - - QString key = line.mid(start + 8, end - start - 8); - keyList.append(key); - } - f.close(); -} // Return name of the element in svg for given key static QString elemId(KeyInfo * ki) @@ -76,27 +59,79 @@ static QString elemId(KeyInfo * ki) 16); } +QStringList layoutsInDir(const QDir & d) +{ + QStringList list = d.entryList(QStringList() << "*.svg", QDir::Files); + list.sort(); + qLog(Input) << "svg kbd layouts in " << d.path() << ": " + + list.join(", "); + return list; +} + +// returns a URL for the layout at position index in list, assuming list are the layout filenames in dir +// It will wrap around if index is out of list and skip forward any layouts named "*shift*" if skipShifted +static QUrl findLayout(const QDir * d, const QStringList * list, int index, bool skipShifted) { + for (;;) { + if (index >= list->size()) + index = 0; + else if (index < 0) + index = list->size() - 1; + + QString nextfilename = list->at(index); + if (nextfilename.contains("shift") && skipShifted) + index++; + else { + return QUrl::fromLocalFile(d->absoluteFilePath(nextfilename)); + } + } +} + + +bool KeyboardFrame::fillLayout(const QDir & d, const QStringList & list, int layoutIndex) { + return fillLayout(&d,&list,layoutIndex,findLayout(&d,&list,layoutIndex,false)); +} + +bool KeyboardFrame::fillLayout(const QUrl &url) { + return fillLayout(NULL,NULL,-1, url); +} + + // Fill screen resolution independent properties -static bool fillLayout(const QString & svgFile, KeyLayout * layout) +bool KeyboardFrame::fillLayout(const QDir * d, const QStringList * list, int layoutIndex, const QUrl & url) { + if (url.scheme()!="file") { + qCritical() << "Only file: urls are supported for keyboard layouts"; + } + KeyLayout *layout = new KeyLayout(); // Make key list QStringList keyIds; - addKeys(svgFile, keyIds); + // maps key ids to urls of the svg file for the keyboard they invoke + QHash<QString,KbLayoutLink *> links; + qDebug() << "Loading SVG keyboard layout from : " << url; + // Add keys from svg file to list. The keys in svg are in form id="key_yyy_xxx" + // where xxx is hex value from Qt::Key or unicode value of given key and yyy is the Qt keycode. + parser.parse(url,keyIds,links); int count = layout->numKeys = keyIds.count(); if (count == 0) { qWarning() << "fillLayout: no keys found"; + delete layout; return false; } - + QString svgFile=url.toLocalFile(); QSvgRenderer *svg = layout->svg = new QSvgRenderer(svgFile); KeyInfo *keys = layout->keys = (KeyInfo *) (malloc(sizeof(KeyInfo) * count)); if (keys == NULL) { qWarning() << "fillLayout: keys null"; + delete layout; return false; } memset((void *)(keys), 0, sizeof(KeyInfo) * count); + layout->shifted = svgFile.contains("shift"); + + QDir dir; + QStringList siblings; KeyInfo *ki = keys; for (int i = 0; i < count; i++) { QString id = keyIds.at(i); @@ -113,12 +148,76 @@ static bool fillLayout(const QString & svgFile, KeyLayout * layout) ki->unicode = unicodeStr.toInt(&ok, 16); if (!ok) qWarning() << "key " << id << ": hex number parse error"; - + if (elemId(ki)!="key_"+id) { + // I slipped a leading 0 and the renderer couldn't find the reconstructed id, warn next time. + qWarning() << "Unrecognized key id format in "<<url<<" key_" << id << " should be " << elemId(ki); + continue; + } ki->rectSvg = svg->boundsOnElement(elemId(ki)); if (i == 0) layout->rectSvg = ki->rectSvg; else layout->rectSvg = layout->rectSvg.united(ki->rectSvg); + + ki->link=links.value(elemId(ki)); + // handle legacy layouts, from before links were supported + if ( ( ki->keycode == Qt::Key_Mode_switch + || ki->keycode == Qt::Key_CapsLock + || ki->keycode == Qt::Key_Shift) + && ki->link==NULL) { + + if (list==NULL) { + // we got a link to a layout and found shift/caps/mode keys in that layout that don't have links, + // so we try to use the legacy (before links) procedure to find sibling layouts to use. + // I don't realy expect this to happen (layout sets should be either old style, + // all without links, or new style all with links, I guess). But we deal with it just in case. + + // If list is null we will try to fill one from d. Since d is null too (in current calls hither) + // we will assume the directory from url. + + if (!url.isLocalFile()) { + qCritical()<<"only file: urls for svg keyboard layouts supported (for now?)"; + delete layout; + return false; + } else { + if (d==NULL) { + // base = file:///a/b/c/ -> dir /a/b/c/ + // base = file:///a/b/c.svg -> dir /a/b/ + // base = file:///a/b/c -> dir /a/b/ + dir=QDir(url.resolved(QUrl(".")).toLocalFile()); + d=&dir; + } + } + siblings=layoutsInDir(*d); + list=&siblings; + // we've read the keys from url so it should still be in the list. + layoutIndex = list->indexOf(QFile(url.path()).fileName()); + } + + //mode_switch moves to next unshifted layout + if (ki->keycode == Qt::Key_Mode_switch) { + ki->link= createLink( findLayout(d,list,layoutIndex+1, true),url); + } else { //capsLock or Shift moves to next layout if this one is lowercase + // or to previous layout if this is shifted + ki->link = createLink(findLayout(d,list,layoutIndex + (layout->shifted ? -1 : 1),false),url); + // Shift changes the layout for just the next keystroke, CapsLock permanently + if (ki->keycode == Qt::Key_Shift) { + ki->link->returnLayout=layout; + ki->link->returnLink=url; + } + } + } +/* + qDebug()<< QString("url:%1 keycode %2=%3 unicode %4=%5 link=%6 href=%7 return=%8\n") + .arg(url.toString()) + .arg(ki->keycode,8,16) + .arg(ki->keycode,12,10) + .arg(ki->unicode,4,16) + .arg((ki->unicode < 32 ) ?'.':QChar(ki->unicode)) + .arg(ki->link==NULL?"NULL":"yes") + .arg(ki->link==NULL? "no":ki->link->href.toString()) + .arg(ki->link==NULL? "no":ki->link->returnLink.toString()); +*/ ki++; } @@ -138,8 +237,7 @@ static bool fillLayout(const QString & svgFile, KeyLayout * layout) ki++; } } while (!ok); - - layout->shifted = svgFile.contains("shift"); + layouts.insert(url,layout); return true; } @@ -203,6 +301,7 @@ static Qt::KeyboardModifiers getModifiers(KeyInfo * ki) static int keyChar(Qt::KeyboardModifiers modifiers, int unicode) { + //qDebug()<< QString("keyChar modifiers %1 ctrl %2 unicode %3 char %4").arg(modifiers,9,16).arg(Qt::ControlModifier,9,16).arg(unicode,4,16).arg(unicode <32? '.':QChar(unicode)); if ((modifiers & Qt::ControlModifier) && unicode >= 'a' && unicode <= 'z') return unicode - 'a' + 1; return unicode; @@ -227,10 +326,10 @@ QFrame(parent, f) , highTid(0) , microFocus() , repaintAll(false) - , caps(1) - , numLayouts(0) - , curLayout(0) + , parser() { + baseLayout=NULL; + currentLayout=NULL; setAttribute(Qt::WA_InputMethodTransparent, true); setWindowFlags(Qt::Dialog | Qt::WindowStaysOnTopHint | Qt:: @@ -244,8 +343,6 @@ QFrame(parent, f) setPalette(pal); setAutoFillBackground(true); - memset((void *)(layouts), 0, sizeof(KeyLayout) * MAX_LAYOUTS); - connect(&repeatTimer, SIGNAL(timeout()), this, SLOT(repeat())); qwsServer->sendIMQuery(Qt::ImMicroFocus); @@ -253,54 +350,71 @@ QFrame(parent, f) KeyboardFrame::~KeyboardFrame() { + foreach( KeyLayout * layout, layouts) { + for(int i=0;i<layout->numKeys;i++) { + // KbLayoutLinks are new'ed but are often NULL + if (layout->keys[i].link) { + delete layout->keys[i].link; + } + } // Free keys - they are malloced - for (int i = 0; i < numLayouts; i++) { - - KeyInfo *ki = layouts[i].keys; - while (layouts[i].numKeys--) { - //ki->pic = NULL; // TODO: does this free pixmap data? - ki++; + free(layout->keys); + // layouts are new'ed + delete layout; } - free(layouts[i].keys); + +} + +KeyLayout * KeyboardFrame::layout(const QUrl &layoutUrl){ + KeyLayout *lay = NULL; + lay=layouts.value(layoutUrl); + if (lay==NULL) { + qLog(Input) << " loading kbd layout from explicit url " << layoutUrl; + fillLayout(layoutUrl); + lay=layouts.value(layoutUrl); } + return lay; } // Set current layout. The layout is loaded from svg file if was not used yet -void KeyboardFrame::setLayout(int index, bool skipShifted) +void KeyboardFrame::setLayout(QUrl layoutUrl, const char *pathToSvgkb) { - if (numLayouts == 0) { - QDir d(Qtopia::qtopiaDir() + "etc/im/svgkbd"); - QStringList list = d.entryList(QStringList() << "*.svg", QDir::Files); - list.sort(); + int numLayouts; + KeyLayout *lay = NULL; + + if (layoutUrl.isEmpty() ) { // set first layout + if (layouts.isEmpty()) { // load all svgs in layouts dir (as we did before having links) + QDir d(Qtopia::qtopiaDir() + pathToSvgkb); + QStringList list = layoutsInDir(d); qLog(Input) << "svg kbd layouts in " << d.path() << ": " + list.join(", "); numLayouts = list.count(); - for (int i = 0; i < numLayouts; i++) - fillLayout(d.filePath(list.at(i)), &layouts[i]); + for (int i = 0; i < numLayouts; i++) { + fillLayout(d,list, i); + if (layouts.count()==1) { + lay=baseLayout=layouts.values()[0]; } - - KeyLayout *lay = NULL; - for (;;) { - if (index >= numLayouts) - index = 0; - else if (index < 0) - index = numLayouts - 1; - - lay = &layouts[index]; - if (lay->shifted && skipShifted) - index++; - else - break; } + } + } else { + lay=layout(layoutUrl); + } + if (lay==NULL) { + qWarning() << layoutUrl << "not found"; + lay=baseLayout; + } + setLayout(lay); +} +void KeyboardFrame::setLayout(KeyLayout *lay) +{ if (width() != lay->scrWidth || height() != lay->scrHeight) { lay->scrWidth = width(); lay->scrHeight = height(); placeKeys(lay, lay->scrWidth, lay->scrHeight); } - - curLayout = index; + currentLayout = lay; } void KeyboardFrame::showEvent(QShowEvent * e) @@ -308,7 +422,7 @@ void KeyboardFrame::showEvent(QShowEvent * e) setLayout(); qwsServer->sendIMQuery(Qt::ImMicroFocus); setGeometry(geometryHint()); - placeKeys(&layouts[curLayout], width(), height()); + placeKeys(currentLayout, width(), height()); releaseKeyboard(); QFrame::showEvent(e); @@ -359,11 +473,11 @@ void KeyboardFrame::paintEvent(QPaintEvent * e) { QPainter p(this); p.setClipRect(e->rect()); - KeyLayout *lay = &layouts[curLayout]; + KeyLayout *lay = currentLayout; // Hide keys when layout key is pressed - if (pressedKey && pressedKey->keycode == Qt::Key_Mode_switch) - return; +// if (pressedKey && pressedKey->keycode == Qt::Key_Mode_switch) +// return; // Draw keys - only those that are in clip region KeyInfo *ki = lay->keys; @@ -401,8 +515,8 @@ void KeyboardFrame::mousePressEvent(QMouseEvent * e) cleanHigh(); // Find pressed key - KeyInfo *ki = layouts[curLayout].keys; - int num = layouts[curLayout].numKeys; + KeyInfo *ki = currentLayout->keys; + int num = currentLayout->numKeys; for (;;) { if (ki->rectScr.contains(e->x(), e->y())) break; @@ -412,40 +526,29 @@ void KeyboardFrame::mousePressEvent(QMouseEvent * e) return; } - // Handle CAPS lock - if (ki->keycode == Qt::Key_CapsLock) { - setLayout(curLayout + caps); - caps *= -1; - repaint(); - return; - } pressedKey = ki; - // Handle layout switch - if (ki->keycode == Qt::Key_Mode_switch) { - repaint(); - return; - } + // Handle layout switch in case the key was not linking anywhere and we couldn't find a link to default to +// if (ki->keycode == Qt::Key_Mode_switch || ki->keycode == Qt::Key_CapsLock ) { +// repaint(); +// return; +// } Qt::KeyboardModifiers mod = getModifiers(ki); - if (mod == Qt::NoModifier) + + if (mod == Qt::NoModifier) { + if (ki->link==NULL || ki->link->href.isEmpty()) { qwsServer->processKeyEvent(keyChar(modifiers, ki->unicode), ki->keycode, modifiers, true, false); - else { - toggleModifier(modifiers, mod); - if (mod & Qt::ShiftModifier) { - if (modifiers & Qt::ShiftModifier) - setLayout(curLayout + caps); - else - setLayout(curLayout - caps); - repaint(); - return; } + } else { + toggleModifier(modifiers, mod); } + if (ki->link==NULL || ki->link->href.isEmpty()) { repeatTimer.start(500); - + } highKey = ki; repaint(pressedRect(ki->rectScr)); } @@ -456,14 +559,35 @@ void KeyboardFrame::mouseReleaseEvent(QMouseEvent * e) return; if (pressedKey) { - if (pressedKey->keycode == Qt::Key_Mode_switch) { - // Switch layout if released on the same rect + if (pressedKey-> link !=NULL && ! pressedKey->link->href.isEmpty()) { + qDebug()<<"changing layout to "<<pressedKey->link->href; if (pressedKey->rectScr.contains(e->pos())) { - caps = 1; - setLayout(curLayout + 1, true); + if (pressedKey->link->hrefLayout==NULL) { + setLayout(pressedKey->link->href); + pressedKey->link->hrefLayout = currentLayout; // we won't lookup and/or load svg next time + } else { + setLayout(pressedKey->link->hrefLayout); + } + + + if (pressedKey->link->returnLink.isEmpty()) { + baseLayout = currentLayout; + qDebug("layout changed permanently\n"); + } else { + if (pressedKey->link->returnLayout==NULL) { + pressedKey->link->returnLayout = layout(pressedKey->link->returnLink); + } + if (pressedKey->link->returnLayout!=NULL) { + baseLayout = pressedKey->link->returnLayout; + qDebug()<<" we'll' return to layout "<<pressedKey->link->returnLink <<"\n"; + } else { + qWarning() << "return layout not found: "<<pressedKey->link->returnLink <<"\n"; + } + } } pressedKey = NULL; + highKey = NULL; repaint(); return; } @@ -473,12 +597,14 @@ void KeyboardFrame::mouseReleaseEvent(QMouseEvent * e) false); // Clear shift and modifiers after regular key - if (getModifiers(pressedKey) == Qt::NoModifier) { - if (modifiers & Qt::ShiftModifier) { - setLayout(curLayout - caps); // clear shift + if (baseLayout!=currentLayout) { + qDebug("changing layout for returning\n"); + setLayout(baseLayout); highKey = NULL; repaint(); } + + if (getModifiers(pressedKey) == Qt::NoModifier) { modifiers = Qt::NoModifier; // clear modifiers } } diff --git a/src/plugins/inputmethods/svgkeyboard/keyboardframe.h b/src/plugins/inputmethods/svgkeyboard/keyboardframe.h index 8b10bae..79c503e 100644 --- a/src/plugins/inputmethods/svgkeyboard/keyboardframe.h +++ b/src/plugins/inputmethods/svgkeyboard/keyboardframe.h @@ -20,15 +20,22 @@ #ifndef KEYBOARDFRAME_H #define KEYBOARDFRAME_H - +#include "kblayoutparser.h" #include <time.h> #include <QDebug> #include <QSvgRenderer> #include <QPixmap> #include <QFrame> #include <QTimer> +#include <QHash> +#include <QDir> #define MAX_LAYOUTS 5 +#define PATH_TO_SVGKB "etc/im/svgkbd" + +struct KeyLayout; +struct KbLayoutLink; +class KbLayoutParser; struct KeyInfo { @@ -37,8 +44,15 @@ struct KeyInfo QRectF rectSvg; // bounding rectangle in SVG QRect rectScr; // bounding rectangle on screen //QPixmap pic; + + KbLayoutLink *link; // layout in this KeyboardFrame that this key links to + // NULL iff linkKind==UNLINKED or layout not loaded yet (user hasn't pressed this key) + }; +// keyLayout has pointers to keys which may have pointers to other layouts in the same KeyboardFrame. +// we load the KeyLayouts of a KeyboardFrame as needed, but destroy al KeyLayouts in the KeyboardFrame +// with their KeyInfos at once, otherwise KeyInfo.link could be left dangling. struct KeyLayout { int numKeys; @@ -47,9 +61,8 @@ struct KeyLayout QRectF rectSvg; int scrWidth; // width and height for which are keys int scrHeight; // currrently placed on the screen - bool shifted; // is this uppercase? + bool shifted; // is this uppercase?}; }; - /* KeyboardFrame is the primary widget for the Keyboard inputmethod. It is responsible for marshalling pickboards for displaying the pickboard, @@ -65,7 +78,8 @@ public: explicit KeyboardFrame(QWidget * parent = 0, Qt::WFlags f = 0); virtual ~ KeyboardFrame(); - void setLayout(int index = 0, bool skipShifted = false); + void setLayout(QUrl layoutUrl = QUrl(), const char *newParameter = PATH_TO_SVGKB); + void cleanHigh(); void resetState(); void mousePressEvent(QMouseEvent *); @@ -111,9 +125,20 @@ private: struct timespec pressTime; // last time key was pressed int caps; // direction for shift. -1=caps lock active, 1=caps lock not active bool ignorePress; // used to ignore too fast presses - int numLayouts; // number of currently loaded layouts - int curLayout; // current layout - KeyLayout layouts[MAX_LAYOUTS]; + //int numLayouts; // number of currently loaded layouts + //int curLayout; // current layout + //KeyLayout layouts[MAX_LAYOUTS]; + KeyLayout *currentLayout ; + QHash<QUrl,KeyLayout *> layouts; + KeyLayout *baseLayout; // to use after one keystroke, like for shift or dead keys + KbLayoutParser parser; + + bool fillLayout(const QDir & d, const QStringList & list, int layoutIndex) ; + + bool fillLayout(const QDir * d, const QStringList * list, int layoutIndex, const QUrl & url); + bool fillLayout(const QUrl &url) ; + void setLayout(KeyLayout *lay); + KeyLayout * layout(const QUrl &layoutUrl); }; #endif diff --git a/src/plugins/inputmethods/svgkeyboard/qbuild.pro b/src/plugins/inputmethods/svgkeyboard/qbuild.pro index 941b87d..605de42 100644 --- a/src/plugins/inputmethods/svgkeyboard/qbuild.pro +++ b/src/plugins/inputmethods/svgkeyboard/qbuild.pro @@ -6,7 +6,7 @@ PLUGIN_TYPE=inputmethods CONFIG+=qtopia singleexec -QT += svg +QT += svg xml STRING_LANGUAGE=en_US AVAILABLE_LANGUAGES=$$QTOPIA_AVAILABLE_LANGUAGES @@ -24,12 +24,14 @@ pkg [ HEADERS=\ keyboard.h\ keyboardimpl.h\ - keyboardframe.h + keyboardframe.h\ + kblayoutparser.h SOURCES=\ keyboard.cpp\ keyboardimpl.cpp\ - keyboardframe.cpp + keyboardframe.cpp\ + kblayoutparser.cpp config [ hint=image
CA_ad.tgz
Description: application/gtar-compressed
_______________________________________________ Community mailing list [email protected] http://lists.goldelico.com/mailman/listinfo.cgi/community http://www.openphoenux.org
