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.clear(); 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 (const 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 }
KDE 4.6 API Reference