• Skip to content
  • Skip to link menu
KDE 4.1 API Reference
  • KDE API Reference
  • kdelibs
  • Sitemap
  • Contact Us
 

KDEUI

kgesture.cpp

Go to the documentation of this file.
00001 /* This file is part of the KDE libraries
00002     Copyright (C) 2006,2007 Andreas Hartmetz (ahartmetz@gmail.com)
00003 
00004     This library is free software; you can redistribute it and/or
00005     modify it under the terms of the GNU Library General Public
00006     License as published by the Free Software Foundation; either
00007     version 2 of the License, or (at your option) any later version.
00008 
00009     This library is distributed in the hope that it will be useful,
00010     but WITHOUT ANY WARRANTY; without even the implied warranty of
00011     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012     Library General Public License for more details.
00013 
00014     You should have received a copy of the GNU Library General Public License
00015     along with this library; see the file COPYING.LIB.  If not, write to
00016     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00017     Boston, MA 02110-1301, USA.
00018 */
00019 
00020 #include "kgesture.h"
00021 #include <klocalizedstring.h>
00022 #include <kdebug.h>
00023 #include <math.h>
00024 #include <QStringList>
00025 
00026 inline float metric(float dx, float dy)
00027 {
00028     //square root of that or not? - not square root has possible advantages
00029     return (dx*dx + dy*dy);
00030 }
00031 
00032 class KShapeGesturePrivate
00033 {
00034 public:
00035     KShapeGesturePrivate()
00036     {
00037     }
00038     KShapeGesturePrivate(const KShapeGesturePrivate &other)
00039       : m_shape(other.m_shape),
00040         m_lengthTo(other.m_lengthTo),
00041         m_curveLength(other.m_curveLength)
00042     {
00043     }
00044     QPolygon m_shape;
00045     QVector<float> m_lengthTo;
00046     float m_curveLength;
00047     QString m_friendlyName;
00048 };
00049 
00050 KShapeGesture::KShapeGesture()
00051     : d(new KShapeGesturePrivate)
00052 {
00053 }
00054 
00055 
00056 KShapeGesture::KShapeGesture(const QPolygon &shape)
00057     : d(new KShapeGesturePrivate)
00058 {
00059     setShape(shape);
00060 }
00061 
00062 
00063 KShapeGesture::KShapeGesture(const QString &description)
00064     : d(new KShapeGesturePrivate)
00065 {
00066     QStringList sl = description.split(',');
00067     d->m_friendlyName = sl.takeFirst();
00068 
00069     bool ok = true;
00070     QPolygon poly;
00071     int x, y;
00072     QStringList::const_iterator it = sl.constBegin();
00073     while (it != sl.constEnd()) {
00074         x = (*it).toInt(&ok);
00075         ++it;
00076         if (!ok || it == sl.constEnd())
00077             break;
00078         y = (*it).toInt(&ok);
00079         if (!ok)
00080             break;
00081         ++it;
00082         poly.append(QPoint(x, y));
00083     }
00084     if (!ok) {
00085         d->m_friendlyName = QString();
00086         return;
00087     }
00088 
00089     setShape(poly);
00090 }
00091 
00092 
00093 KShapeGesture::KShapeGesture(const KShapeGesture &other)
00094     : d(new KShapeGesturePrivate(*(other.d)))
00095 {
00096 }
00097 
00098 
00099 KShapeGesture::~KShapeGesture()
00100 {
00101     delete d;
00102 }
00103 
00104 
00105 void KShapeGesture::setShape(const QPolygon &shape)
00106 {
00107     //Scale and translate into a 100x100 square with its
00108     //upper left corner at origin.
00109     d->m_shape = shape;
00110     QRect bounding = shape.boundingRect();
00111     //TODO: don't change aspect ratio "too much" to avoid problems with straight lines
00112     //TODO: catch all bad input, like null height/width
00113 
00114     //compensate for QRect weirdness
00115     bounding.setWidth(bounding.width() - 1);
00116     bounding.setHeight(bounding.height() - 1);
00117 
00118     float xScale = bounding.width() ? 100.0 / bounding.width() : 1.0;
00119     float yScale = bounding.height() ? 100.0 / bounding.height() : 1.0;
00120     d->m_shape.translate(-bounding.left(), -bounding.top());
00121     for (int i=0; i < d->m_shape.size(); i++) {
00122         d->m_shape[i].setX((int)(xScale * (float)d->m_shape[i].x()));
00123         d->m_shape[i].setY((int)(yScale * (float)d->m_shape[i].y()));
00124     }
00125 
00126     //calculate accumulated lengths of lines making up the polygon
00127     Q_ASSERT(d->m_shape.size() > 1);
00128     d->m_curveLength = 0.0;
00129     d->m_lengthTo.clear();
00130     d->m_lengthTo.reserve(d->m_shape.size());
00131     d->m_lengthTo.append(d->m_curveLength);
00132 
00133     int prevX = d->m_shape[0].x();
00134     int prevY = d->m_shape[0].y();
00135     for (int i=1; i < d->m_shape.size(); i++) {
00136         int curX = d->m_shape[i].x();
00137         int curY = d->m_shape[i].y();
00138         d->m_curveLength += metric(curX-prevX, curY - prevY);
00139         d->m_lengthTo.append(d->m_curveLength);
00140         prevX = curX;
00141         prevY = curY;
00142     }
00143 }
00144 
00145 
00146 void KShapeGesture::setShapeName(const QString &friendlyName)
00147 {
00148     d->m_friendlyName = friendlyName;
00149 }
00150 
00151 
00152 QString KShapeGesture::shapeName() const
00153 {
00154     return d->m_friendlyName;
00155 }
00156 
00157 
00158 bool KShapeGesture::isValid() const
00159 {
00160     return !d->m_shape.isEmpty();
00161 }
00162 
00163 
00164 QString KShapeGesture::toString() const
00165 {
00166     if (!isValid())
00167         return QString();
00168 
00169     //TODO: what if the name contains a "," or ";"? Limit the name to letters?
00170     QString ret = d->m_friendlyName;
00171 
00172     int i;
00173     for (i = 0; i < d->m_shape.size(); i++) {
00174         ret.append(',');
00175         ret.append(QString::number(d->m_shape[i].x()));
00176         ret.append(',');
00177         ret.append(QString::number(d->m_shape[i].y()));
00178     }
00179 
00180     return ret;
00181 }
00182 
00183 
00184 QByteArray KShapeGesture::toSvg(const QString &attributes) const
00185 {
00186     if (!isValid()) {
00187         return QByteArray();
00188         //TODO: KDE standard debug output
00189     }
00190     const char *prolog = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>"
00191                          "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
00192                          "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">"
00193                          "<svg width=\"100\" height=\"100\" version=\"1.1\" "
00194                          "xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M";
00195     const char *epilog1 = "\" fill=\"none\" ";
00196     const char *epilog2 = " /></svg>";
00197     QByteArray ret(prolog);
00198 
00199     ret.append(QString::number(d->m_shape[0].x()).toUtf8());
00200     ret.append(",");
00201     ret.append(QString::number(d->m_shape[0].y()).toUtf8());
00202 
00203     for (int i=1; i < d->m_shape.size(); i++) {
00204         ret.append("L");
00205         ret.append(QString::number(d->m_shape[i].x()).toUtf8());
00206         ret.append(",");
00207         ret.append(QString::number(d->m_shape[i].y()).toUtf8());
00208     }
00209 
00210     ret.append(epilog1);
00211     ret.append(attributes.toUtf8());
00212     ret.append(epilog2);
00213     return ret;
00214 }
00215 
00216 
00217 /*
00218   algorithm: iterate in order over 30 points on our shape and measure the
00219   minimum distance to any point on the other shape. never go backwards on
00220   the other shape to also check direction of movement.
00221   This algorithm is best applied like a->distance(b) + b->distance(a).
00222   fabs(a->distance(b) - b->distance(a)) might turn out to be very interesting,
00223   too. in fact, i think it's the most interesting value.
00224  */
00225 float KShapeGesture::distance(const KShapeGesture &other, float abortThreshold) const
00226 {
00227     Q_UNUSED(abortThreshold); //for optimizations, later
00228     const QPolygon &o_shape = other.d->m_shape;
00229     const QVector<float> &o_lengthTo = other.d->m_lengthTo;
00230     float x = 0;
00231     float y = 0;
00232     float mx = 0;
00233     float my = 0;
00234     float position = 0;
00235     float ox = 0;
00236     float oy = 0;
00237     float oposition = 0;
00238     float omx = 0;
00239     float omy = 0;
00240     float oxB = 0;
00241     float oyB = 0;
00242     float opositionB = 0;
00243     float omxB = 0;
00244     float omyB = 0;
00245     float dist = 0;
00246     float distB = 0;
00247     float desiredPosition = 0;
00248     float strokeLength = 0;
00249     float retval = 0.0;
00250     int pointIndex = 0, opointIndex = 0, opointIndexB = 0;
00251 
00252     //set up starting point on our shape
00253     x = d->m_shape[0].x();
00254     y = d->m_shape[0].y();
00255     strokeLength = d->m_lengthTo[1];
00256     mx = (d->m_shape[1].x() - x) / strokeLength;
00257     my = (d->m_shape[1].y() - y) / strokeLength;
00258     position = 0.0;
00259 
00260     //set up lower bound of search interval on other shape
00261     ox = o_shape[0].x();
00262     oy = o_shape[0].y();
00263     strokeLength = o_lengthTo[1];
00264     omx = (o_shape[1].x() - ox) / strokeLength;
00265     omy = (o_shape[1].y() - oy) / strokeLength;
00266     oposition = 0.0;
00267     dist = metric(ox-x, oy-y);
00268 
00269     for (int i = 0; i <= 30; i++) {
00270         //go to comparison point on our own polygon
00271         //30.0001 to prevent getting out-of-bounds pointIndex
00272         desiredPosition = d->m_curveLength / 30.0001 * (float)i;
00273         if (desiredPosition > d->m_lengthTo[pointIndex+1]) {
00274 
00275             while (desiredPosition > d->m_lengthTo[pointIndex+1])
00276                 pointIndex++;
00277 
00278             x = d->m_shape[pointIndex].x();
00279             y = d->m_shape[pointIndex].y();
00280             position = d->m_lengthTo[pointIndex];
00281             strokeLength = d->m_lengthTo[pointIndex+1] - position;
00282             mx = (d->m_shape[pointIndex+1].x() - x) / strokeLength;
00283             my = (d->m_shape[pointIndex+1].y() - y) / strokeLength;
00284         }
00285         x += mx * (desiredPosition - position);
00286         y += my * (desiredPosition - position);
00287         position = desiredPosition;
00288 
00289         //set up upper bound of search interval on other shape
00290         desiredPosition = qMin(oposition + other.d->m_curveLength / 15.00005,
00291                                other.d->m_curveLength - 0.0001);
00292         if (i == 0 || desiredPosition > o_lengthTo[opointIndexB+1]) {
00293 
00294             while (desiredPosition > o_lengthTo[opointIndexB+1])
00295                 opointIndexB++;
00296 
00297             oxB = o_shape[opointIndexB].x();
00298             oyB = o_shape[opointIndexB].y();
00299             opositionB = o_lengthTo[opointIndexB];
00300             strokeLength = o_lengthTo[opointIndexB+1] - opositionB;
00301             omxB = (o_shape[opointIndexB+1].x() - oxB) / strokeLength;
00302             omyB = (o_shape[opointIndexB+1].y() - oyB) / strokeLength;
00303         }
00304         oxB += omxB * (desiredPosition - opositionB);
00305         oyB += omyB * (desiredPosition - opositionB);
00306         opositionB = desiredPosition;
00307         distB = metric(oxB-x, oyB-y);
00308 
00309         //binary search for nearest point on other shape
00310         for (int j = 0; j < 6; j++) {
00311             desiredPosition = (oposition + opositionB) * 0.5;
00312             if (dist < distB) {
00313                 //retract upper bound to desiredPosition
00314                 //copy state of lower bound to upper bound, advance it from there
00315                 oxB = ox; oyB = oy;
00316                 omxB = omx; omyB = omy;
00317                 opointIndexB = opointIndex; opositionB = oposition;
00318 
00319                 if (desiredPosition > o_lengthTo[opointIndexB+1]) {
00320 
00321                     while (desiredPosition > o_lengthTo[opointIndexB+1])
00322                         opointIndexB++;
00323 
00324                     oxB = o_shape[opointIndexB].x();
00325                     oyB = o_shape[opointIndexB].y();
00326                     opositionB = o_lengthTo[opointIndexB];
00327                     strokeLength = o_lengthTo[opointIndexB+1] - opositionB;
00328                     omxB = (o_shape[opointIndexB+1].x() - oxB) / strokeLength;
00329                     omyB = (o_shape[opointIndexB+1].y() - oyB) / strokeLength;
00330                 }
00331                 oxB += omxB * (desiredPosition - opositionB);
00332                 oyB += omyB * (desiredPosition - opositionB);
00333                 opositionB = desiredPosition;
00334                 distB = metric(oxB-x, oyB-y);
00335             } else {
00336                 //advance lower bound to desiredPosition
00337                 if (desiredPosition > o_lengthTo[opointIndex+1]) {
00338 
00339                     while (desiredPosition > o_lengthTo[opointIndex+1])
00340                         opointIndex++;
00341 
00342                     ox = o_shape[opointIndex].x();
00343                     oy = o_shape[opointIndex].y();
00344                     oposition = o_lengthTo[opointIndex];
00345                     strokeLength = o_lengthTo[opointIndex+1] - oposition;
00346                     omx = (o_shape[opointIndex+1].x() - ox) / strokeLength;
00347                     omy = (o_shape[opointIndex+1].y() - oy) / strokeLength;
00348                 }
00349                 ox += omx * (desiredPosition - oposition);
00350                 oy += omy * (desiredPosition - oposition);
00351                 oposition = desiredPosition;
00352                 dist = metric(ox-x, oy-y);
00353             }
00354         }
00355         retval += qMin(dist, distB);
00356     }
00357     //scale value to make it roughly invariant against step width
00358     return retval / 30.0;
00359 }
00360 
00361 
00362 KShapeGesture &KShapeGesture::operator=(const KShapeGesture &other)
00363 {
00364     d->m_lengthTo = other.d->m_lengthTo;
00365     d->m_shape = other.d->m_shape;
00366     d->m_curveLength = other.d->m_curveLength;
00367     return *this;
00368 }
00369 
00370 
00371 bool KShapeGesture::operator==(const KShapeGesture &other) const
00372 {
00373     //a really fast and workable shortcut
00374     if (fabs(d->m_curveLength - other.d->m_curveLength) > 0.1)
00375         return false;
00376     return d->m_shape == other.d->m_shape;
00377 }
00378 
00379 bool KShapeGesture::operator!=(const KShapeGesture &other) const
00380 {
00381     return !operator==(other);
00382 }
00383 
00384 uint KShapeGesture::hashable() const
00385 {
00386     uint hash = 0;
00387 
00388     foreach (QPoint point, d->m_shape)
00389         hash += qHash(point.x()) + qHash(point.y());
00390 
00391     return hash;
00392 }
00393 
00394 
00395 /********************************************************
00396  * KRockerGesture *
00397  *******************************************************/
00398 
00399 class KRockerGesturePrivate
00400 {
00401 public:
00402     KRockerGesturePrivate()
00403       : m_hold(Qt::NoButton),
00404         m_thenPush(Qt::NoButton)
00405     {
00406     }
00407     KRockerGesturePrivate(const KRockerGesturePrivate &other)
00408       : m_hold(other.m_hold),
00409         m_thenPush(other.m_thenPush)
00410     {
00411     }
00412     Qt::MouseButton m_hold;
00413     Qt::MouseButton m_thenPush;
00414 };
00415 
00416 KRockerGesture::KRockerGesture()
00417  : d( new KRockerGesturePrivate )
00418 {
00419 }
00420 
00421 
00422 KRockerGesture::KRockerGesture(Qt::MouseButton hold, Qt::MouseButton thenPush)
00423  : d( new KRockerGesturePrivate )
00424 {
00425     setButtons(hold, thenPush);
00426 }
00427 
00428 
00429 KRockerGesture::KRockerGesture(const QString &description)
00430  : d( new KRockerGesturePrivate )
00431 {
00432     if (description.length() != 2)
00433         return;
00434 
00435     Qt::MouseButton hold, thenPush;
00436     Qt::MouseButton *current = &hold;
00437     for (int i = 0; i < 2; i++) {
00438         switch (description[i].toLatin1()) {
00439         case 'L':
00440             *current = Qt::LeftButton;
00441             break;
00442         case 'R':
00443             *current = Qt::RightButton;
00444             break;
00445         case 'M':
00446             *current = Qt::MidButton;
00447             break;
00448         case '1':
00449             *current = Qt::XButton1;
00450             break;
00451         case '2':
00452             *current = Qt::XButton2;
00453             break;
00454         default:
00455             return;
00456         }
00457         current = &thenPush;
00458     }
00459     d->m_hold = hold;
00460     d->m_thenPush = thenPush;
00461 }
00462 
00463 
00464 KRockerGesture::KRockerGesture(const KRockerGesture &other)
00465  : d( new KRockerGesturePrivate(*(other.d)) )
00466 {
00467 }
00468 
00469 
00470 KRockerGesture::~KRockerGesture()
00471 {
00472     delete d;
00473 }
00474 
00475 
00476 void KRockerGesture::setButtons(Qt::MouseButton hold, Qt::MouseButton thenPush)
00477 {
00478     if (hold == thenPush) {
00479         d->m_hold = Qt::NoButton;
00480         d->m_thenPush = Qt::NoButton;
00481         return;
00482     }
00483 
00484     int button = hold;
00485     for (int i = 0; i < 2; i++) {
00486         switch (button) {
00487         case Qt::LeftButton:
00488         case Qt::RightButton:
00489         case Qt::MidButton:
00490         case Qt::XButton1:
00491         case Qt::XButton2:
00492             break;
00493         default:
00494             d->m_hold = Qt::NoButton;
00495             d->m_thenPush = Qt::NoButton;
00496             return;
00497         }
00498         button = thenPush;
00499     }
00500 
00501     d->m_hold = hold;
00502     d->m_thenPush = thenPush;
00503 }
00504 
00505 
00506 void KRockerGesture::getButtons(Qt::MouseButton *hold, Qt::MouseButton *thenPush) const
00507 {
00508     *hold = d->m_hold;
00509     *thenPush = d->m_thenPush;
00510 }
00511 
00512 
00513 QString KRockerGesture::mouseButtonName(Qt::MouseButton button)
00514 {
00515     switch (button) {
00516     case Qt::LeftButton:
00517         return i18nc("left mouse button", "left button");
00518         break;
00519     case Qt::MidButton:
00520         return i18nc("middle mouse button", "middle button");
00521         break;
00522     case Qt::RightButton:
00523         return i18nc("right mouse button", "right button");
00524         break;
00525     default:
00526         return i18nc("a nonexistent value of mouse button", "invalid button");
00527         break;
00528     }
00529 }
00530 
00531 
00532 QString KRockerGesture::rockerName() const
00533 {
00534     if (!isValid())
00535         return QString();
00536         //return i18nc("an invalid mouse gesture of type \"hold down one button, then press another button\"",
00537         //             "invalid rocker gesture");
00538     else
00539         return i18nc("a kind of mouse gesture: hold down one mouse button, then press another button",
00540                      "Hold %1, then push %2", mouseButtonName(d->m_hold), mouseButtonName(d->m_thenPush));
00541 }
00542 
00543 
00544 bool KRockerGesture::isValid() const
00545 {
00546     return (d->m_hold != Qt::NoButton);
00547 }
00548 
00549 
00550 QString KRockerGesture::toString() const
00551 {
00552     if (!isValid())
00553         return QString();
00554     QString ret;
00555     int button = d->m_hold;
00556     char desc;
00557     for (int i = 0; i < 2; i++) {
00558         switch (button) {
00559         case Qt::LeftButton:
00560             desc = 'L';
00561             break;
00562         case Qt::RightButton:
00563             desc = 'R';
00564             break;
00565         case Qt::MidButton:
00566             desc = 'M';
00567             break;
00568         case Qt::XButton1:
00569             desc = '1';
00570             break;
00571         case Qt::XButton2:
00572             desc = '2';
00573             break;
00574         default:
00575             return QString();
00576         }
00577         ret.append(desc);
00578         button = d->m_thenPush;
00579     }
00580     return ret;
00581 }
00582 
00583 
00584 KRockerGesture &KRockerGesture::operator=(const KRockerGesture &other)
00585 {
00586     d->m_hold = other.d->m_hold;
00587     d->m_thenPush = other.d->m_thenPush;
00588     return *this;
00589 }
00590 
00591 
00592 bool KRockerGesture::operator==(const KRockerGesture &other) const
00593 {
00594     return d->m_hold == other.d->m_hold && d->m_thenPush == other.d->m_thenPush;
00595 }
00596 
00597 bool KRockerGesture::operator!=(const KRockerGesture &other) const
00598 {
00599     return !operator==(other);
00600 }
00601 
00602 uint KRockerGesture::hashable() const
00603 {
00604     //make it asymmetric
00605     return qHash(d->m_hold) + d->m_thenPush;
00606 }

KDEUI

Skip menu "KDEUI"
  • Main Page
  • Modules
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • KIO
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • Kross
  • KUtils
  • Nepomuk
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.5.4
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal