diff options
Diffstat (limited to 'lib/kofficecore/kkbdaccessextensions.cpp')
-rw-r--r-- | lib/kofficecore/kkbdaccessextensions.cpp | 667 |
1 files changed, 667 insertions, 0 deletions
diff --git a/lib/kofficecore/kkbdaccessextensions.cpp b/lib/kofficecore/kkbdaccessextensions.cpp new file mode 100644 index 000000000..aae372039 --- /dev/null +++ b/lib/kofficecore/kkbdaccessextensions.cpp @@ -0,0 +1,667 @@ +/* This file is part of the KDE project + Copyright (C) 2005, Gary Cramblitt <garycramblitt@comcast.net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +// Qt includes +#include <qsplitter.h> +#include <qdockwindow.h> +#include <qdockarea.h> +#include <qevent.h> +#include <qcursor.h> +#include <qobjectlist.h> +#include <qwidgetlist.h> +#include <qlabel.h> +#include <qtooltip.h> + +// KDE includes +#include <klocale.h> +#include <kglobal.h> +#include <kapplication.h> +#include <kmainwindow.h> +#include <kaction.h> +#include <kdebug.h> + +// KKbdAccessExtensions includes +#include "kkbdaccessextensions.h" +// TODO: See eventFilter method. +//#include "kkbdaccessextensions.moc" + +class KPanelKbdSizerIcon : public QCursor +{ + public: + KPanelKbdSizerIcon() : + QCursor(Qt::SizeAllCursor), + isActive(false) + { + currentPos = QPoint(-1, -1); + } + + ~KPanelKbdSizerIcon() + { + hide(); + } + + void show(const QPoint p) { + if (!isActive) { + originalPos = QCursor::pos(); + kapp->setOverrideCursor(*this); + isActive = true; + } + if (p != pos()) + setPos(p); + currentPos = p; + } + + void hide() { + if (isActive) { + kapp->restoreOverrideCursor(); + QCursor::setPos(originalPos); + } + isActive = false; + } + + void setShape(int shayp) + { + if (shayp != shape()) { + // Must restore and override to get the icon to refresh. + if (isActive) kapp->restoreOverrideCursor(); + QCursor::setShape(shayp); + if (isActive) kapp->setOverrideCursor(*this); + } + } + + // Return the difference between a position and where icon is supposed to be. + QSize delta(const QPoint p) + { + QPoint d = p - currentPos; + return QSize(d.x(), d.y()); + } + + // Return the difference between where the icon is currently positioned and where + // it is supposed to be. + QSize delta() { return delta(pos()); } + + // True if the sizing icon is visible. + bool isActive; + + private: + // Icon's current position. + QPoint currentPos; + // Mouse cursor's original position when icon is shown. + QPoint originalPos; +}; + +class KKbdAccessExtensionsPrivate +{ + public: + KKbdAccessExtensionsPrivate() : + fwdAction(0), + revAction(0), + accessKeysAction(0), + panel(0), + handleNdx(0), + icon(0), + stepSize(10), + accessKeyLabels(0) {}; + + ~KKbdAccessExtensionsPrivate() + { + delete icon; + // TODO: This crashes, but should delete in the event that KMainWindow is not deleted. + if (accessKeyLabels) { + accessKeyLabels->setAutoDelete(false); + delete accessKeyLabels; + } + } + + // Action that starts panel sizing (defaults to F8), forward and reverse; + KAction* fwdAction; + KAction* revAction; + + // Action that starts access keys. + KAction* accessKeysAction; + + // The splitter or dockwindow currently being sized. If 0, sizing is not in progress. + QWidget* panel; + + // Index of current handle of the panel. When panel is a QDockWindow: + // 1 = size horizontally + // 2 = size vertically + uint handleNdx; + + // Sizing icon. + KPanelKbdSizerIcon* icon; + + // Sizing increment. + int stepSize; + + // List of the access key QLabels. If not 0, access keys are onscreen. + QPtrList<QLabel>* accessKeyLabels; + + // Pointer to the KMainWindow. + KMainWindow* mainWindow; +}; + +KKbdAccessExtensions::KKbdAccessExtensions(KMainWindow* parent, const char* name) : + QObject(parent, name) +{ + // kdDebug() << "KKbdAccessExtensions::KKbdAccessExtensions: running." << endl; + d = new KKbdAccessExtensionsPrivate; + d->mainWindow = parent; + d->fwdAction = new KAction(i18n("Resize Panel Forward"), KShortcut("F8"), + 0, 0, parent->actionCollection(), "resize_panel_forward"); + d->revAction = new KAction(i18n("Resize Panel Reverse"), KShortcut("Shift+F8"), + 0, 0, parent->actionCollection(), "resize_panel_reverse"); + d->accessKeysAction = new KAction(i18n("Access Keys"), KShortcut("Alt+F8"), + 0, 0, parent->actionCollection(), "access_keys"); + // "Disable" the shortcuts so we can see them in eventFilter. + d->fwdAction->setEnabled(false); + d->revAction->setEnabled(false); + d->accessKeysAction->setEnabled(false); + d->icon = new KPanelKbdSizerIcon(); + kapp->installEventFilter(this); +} + +KKbdAccessExtensions::~KKbdAccessExtensions() +{ + kapp->removeEventFilter(this); + if (d->panel) exitSizing(); + delete d; +} + +int KKbdAccessExtensions::stepSize() const { return d->stepSize; } + +void KKbdAccessExtensions::setStepSize(int s) { d->stepSize = s; } + +bool KKbdAccessExtensions::eventFilter( QObject *o, QEvent *e ) +{ + if ( e->type() == QEvent::KeyPress ) { + // TODO: This permits only a single-key shortcut. For example, Alt+S,R would not work. + // If user configures a multi-key shortcut, it is undefined what will happen here. + // It would be better to handle these as KShortcut activate() signals, but the problem + // is that once a QDockWindow is undocked and has focus, the KShortcut activate() signals + // don't fire anymore. + KShortcut fwdSc = d->fwdAction->shortcut(); + KShortcut revSc = d->revAction->shortcut(); + KShortcut accessKeysSc = d->accessKeysAction->shortcut(); + QKeyEvent* kev = dynamic_cast<QKeyEvent *>(e); + KKey k = KKey(kev); + KShortcut sc = KShortcut(k); + // kdDebug() << "KKbdAccessExtensions::eventFilter: Key press " << sc << endl; + if (!d->accessKeyLabels) { + if (sc == fwdSc) { + nextHandle(); + return true; + } + if (sc == revSc) { + prevHandle(); + return true; + } + } + if (d->panel) { + if (k == KKey(Key_Escape)) + exitSizing(); + else + resizePanelFromKey(kev->key(), kev->state()); + // Eat the key. + return true; + } + if (sc == accessKeysSc && !d->panel) { + if (d->accessKeyLabels) { + delete d->accessKeyLabels; + d->accessKeyLabels = 0; + } else + displayAccessKeys(); + return true; + } + if (d->accessKeyLabels) { + if (k == KKey(Key_Escape)) { + delete d->accessKeyLabels; + d->accessKeyLabels = 0; + } else + handleAccessKey(kev); + return true; + } + return false; + } + else if (d->icon->isActive && e->type() == QEvent::MouseButtonPress) { + exitSizing(); + return true; + } + else if (d->accessKeyLabels && e->type() == QEvent::MouseButtonPress) { + delete d->accessKeyLabels; + d->accessKeyLabels = 0; + return true; + } +/* else if (e->type() == QEvent::MouseMove && d->icon->isActive) { + // Lock mouse cursor down. + showIcon(); + dynamic_cast<QMouseEvent *>(e)->accept(); + return true; + }*/ + else if (e->type() == QEvent::MouseMove && d->icon->isActive && d->panel) { + // Resize according to mouse movement. + QMouseEvent* me = dynamic_cast<QMouseEvent *>(e); + QSize s = d->icon->delta(); + int dx = s.width(); + int dy = s.height(); + resizePanel(dx, dy, me->state()); + me->accept(); + showIcon(); + return true; + } + else if (e->type() == QEvent::Resize && d->panel && o == d->panel) { + // TODO: This doesn't always work. + showIcon(); + } + return false; +} + +QWidgetList* KKbdAccessExtensions::getAllPanels() +{ + QWidgetList* allWidgets = kapp->allWidgets(); + QWidgetList* allPanels = new QWidgetList; + QWidget* widget = allWidgets->first(); + while (widget) { + if (widget->isVisible()) { + if (::qt_cast<QSplitter*>( widget )) { + // Only size QSplitters with at least two handles (there is always one hidden). + if (dynamic_cast<QSplitter *>(widget)->sizes().count() >= 2) + allPanels->append(widget); + } else if (::qt_cast<QDockWindow*>( widget )) { + if (dynamic_cast<QDockWindow *>(widget)->isResizeEnabled()) { + // kdDebug() << "KKbdAccessExtensions::getAllPanels: QDockWindow = " << widget->name() << endl; + allPanels->append(widget); + } + } + } + widget = allWidgets->next(); + } + delete allWidgets; + return allPanels; +} + +void KKbdAccessExtensions::nextHandle() +{ + QWidget* panel = d->panel; + // See if current panel has another handle. If not, find next panel. + if (panel) { + bool advance = true; + d->handleNdx++; + if (::qt_cast<QSplitter*>( panel )) + advance = (d->handleNdx >= dynamic_cast<QSplitter *>(panel)->sizes().count()); + else + // Undocked windows have only one "handle" (center). + advance = (d->handleNdx > 2 || !dynamic_cast<QDockWindow *>(panel)->area()); + if (advance) { + QWidgetList* allWidgets = getAllPanels(); + allWidgets->findRef(panel); + panel = 0; + if (allWidgets->current()) panel = allWidgets->next(); + delete allWidgets; + d->handleNdx = 1; + } + } else { + // Find first panel. + QWidgetList* allWidgets = getAllPanels(); + panel = allWidgets->first(); + delete allWidgets; + d->handleNdx = 1; + } + d->panel = panel; + if (panel) + showIcon(); + else + exitSizing(); +} + +void KKbdAccessExtensions::prevHandle() +{ + QWidget* panel = d->panel; + // See if current panel has another handle. If not, find previous panel. + if (panel) { + bool rewind = true; + d->handleNdx--; + rewind = (d->handleNdx < 1); + if (rewind) { + QWidgetList* allWidgets = getAllPanels(); + allWidgets->findRef(panel); + panel = 0; + if (allWidgets->current()) panel = allWidgets->prev(); + delete allWidgets; + if (panel) { + if (::qt_cast<QSplitter*>( panel )) + d->handleNdx = dynamic_cast<QSplitter *>(panel)->sizes().count() - 1; + else { + if (dynamic_cast<QDockWindow *>(panel)->area()) + d->handleNdx = 2; + else + d->handleNdx = 1; + } + } + } + } else { + // Find last panel. + QWidgetList* allWidgets = getAllPanels(); + panel = allWidgets->last(); + delete allWidgets; + if (panel) { + if (::qt_cast<QSplitter*>( panel )) + d->handleNdx = dynamic_cast<QSplitter *>(panel)->sizes().count() - 1; + else { + if (dynamic_cast<QDockWindow *>(panel)->area()) + d->handleNdx = 2; + else + d->handleNdx = 1; + } + } + } + d->panel = panel; + if (panel) + showIcon(); + else + exitSizing(); +} + +void KKbdAccessExtensions::exitSizing() +{ + // kdDebug() << "KKbdAccessExtensions::exiting sizing mode." << endl; + hideIcon(); + d->handleNdx = 0; + d->panel = 0; +} + +void KKbdAccessExtensions::showIcon() +{ + if (!d->panel) return; + QPoint p; + // kdDebug() << "KKbdAccessExtensions::showIcon: topLevelWidget = " << d->panel->topLevelWidget()->name() << endl; + if (::qt_cast<QSplitter*>( d->panel )) { + QSplitter* splitter = dynamic_cast<QSplitter *>(d->panel); + int handleNdx = d->handleNdx - 1; + QValueList<int> sizes = splitter->sizes(); + // kdDebug() << "KKbdAccessExtensions::showIcon: sizes = " << sizes << endl; + if (splitter->orientation() == Qt::Horizontal) { + d->icon->setShape(Qt::SizeHorCursor); + p.setX(sizes[handleNdx] + (splitter->handleWidth() / 2)); + p.setY(splitter->height() / 2); + } else { + d->icon->setShape(Qt::SizeVerCursor); + p.setX(splitter->width() / 2); + p.setY(sizes[handleNdx] + (splitter->handleWidth() / 2)); + } + // kdDebug() << "KKbdAccessExtensions::showIcon: p = " << p << endl; + p = splitter->mapToGlobal(p); + // kdDebug() << "KKbdAccessExtensions::showIcon: mapToGlobal = " << p << endl; + } else { + QDockWindow* dockWindow = dynamic_cast<QDockWindow *>(d->panel); + p = dockWindow->pos(); + if (dockWindow->area()) { + // kdDebug() << "KKbdAccessExtensions::showIcon: pos = " << p << " of window = " << dockWindow->parentWidget()->name() << endl; + p = dockWindow->parentWidget()->mapTo(dockWindow->topLevelWidget(), p); + // kdDebug() << "KKbdAccessExtensions::showIcon: mapTo = " << p << " of window = " << dockWindow->topLevelWidget()->name() << endl; + // TODO: How to get the handle width? + if (d->handleNdx == 1) { + d->icon->setShape(Qt::SizeHorCursor); + if (dockWindow->area()->orientation() == Qt::Vertical) { + if (dockWindow->area()->handlePosition() == QDockArea::Normal) + // Handle is to the right of the dock window. + p.setX(p.x() + dockWindow->width()); + // else Handle is to the left of the dock window. + } else + // Handle is to the right of the dock window. + p.setX(p.x() + dockWindow->width()); + p.setY(p.y() + (dockWindow->height() / 2)); + } else { + d->icon->setShape(Qt::SizeVerCursor); + p.setX(p.x() + (dockWindow->width() / 2)); + if (dockWindow->area()->orientation() == Qt::Vertical) + // Handle is below the dock window. + p.setY(p.y() + dockWindow->height()); + else { + if (dockWindow->area()->handlePosition() == QDockArea::Normal) + // Handle is below the dock window. + p.setY(p.y() + dockWindow->height()); + // else Handle is above the dock window. + } + } + p = dockWindow->topLevelWidget()->mapToGlobal(p); + } else { + d->icon->setShape(Qt::SizeAllCursor); + p = QPoint(dockWindow->width() / 2, dockWindow->height() / 2); + p = dockWindow->mapToGlobal(p); // Undocked. Position in center of window. + } + } + // kdDebug() << "KKbdAccessExtensions::showIcon: show(p) = " << p << endl; + d->icon->show(p); +} + +void KKbdAccessExtensions::hideIcon() +{ + d->icon->hide(); +} + +void KKbdAccessExtensions::resizePanel(int dx, int dy, int state) +{ + int adj = dx + dy; + if (adj == 0) return; + // kdDebug() << "KKbdAccessExtensions::resizePanel: panel = " << d->panel->name() << endl; + if (::qt_cast<QSplitter*>( d->panel )) { + QSplitter* splitter = dynamic_cast<QSplitter *>(d->panel); + int handleNdx = d->handleNdx - 1; + QValueList<int> sizes = splitter->sizes(); + // kdDebug() << "KKbdAccessExtensions::resizePanel: before sizes = " << sizes << endl; + sizes[handleNdx] = sizes[handleNdx] + adj; + // kdDebug() << "KKbdAccessExtensions::resizePanel: setSizes = " << sizes << endl; + splitter->setSizes(sizes); + QApplication::postEvent(splitter, new QEvent(QEvent::LayoutHint)); + } else { + // TODO: How to get the handle width? + QDockWindow* dockWindow = dynamic_cast<QDockWindow *>(d->panel); + if (dockWindow->area()) { + // kdDebug() << "KKbdAccessExtensions::resizePanel: fixedExtent = " << dockWindow->fixedExtent() << endl; + QSize fe = dockWindow->fixedExtent(); + if (d->handleNdx == 1) { + // When vertically oriented and dock area is on right side of screen, pressing + // left arrow increases size. + if (dockWindow->area()->orientation() == Qt::Vertical && + dockWindow->area()->handlePosition() == QDockArea::Reverse) adj = -adj; + int w = fe.width(); + if (w < 0) w = dockWindow->width(); + w = w + adj; + if (w > 0 ) dockWindow->setFixedExtentWidth(w); + } else { + // When horizontally oriented and dock area is at bottom of screen, + // pressing up arrow increases size. + if (dockWindow->area()->orientation() == Qt::Horizontal && + dockWindow->area()->handlePosition() == QDockArea::Reverse) adj = -adj; + int h = fe.height(); + if (h < 0) h = dockWindow->height(); + h = h + adj; + if (h > 0) dockWindow->setFixedExtentHeight(h); + } + dockWindow->updateGeometry(); + QApplication::postEvent(dockWindow->area(), new QEvent(QEvent::LayoutHint)); + // kdDebug() << "KKbdAccessExtensions::resizePanel: fixedExtent = " << dockWindow->fixedExtent() << endl; + } else { + if (state == Qt::ShiftButton) { + QSize s = dockWindow->size(); + s.setWidth(s.width() + dx); + s.setHeight(s.height() + dy); + dockWindow->resize(s); + } else { + QPoint p = dockWindow->pos(); + p.setX(p.x() + dx); + p.setY(p.y() + dy); + dockWindow->move(p); + } + } + } +} + +void KKbdAccessExtensions::resizePanelFromKey(int key, int state) +{ + // kdDebug() << "KPanelKdbSizer::resizePanelFromKey: key = " << key << " state = " << state << endl; + if (!d->panel) return; + int dx = 0; + int dy = 0; + int stepSize = d->stepSize; + switch (key) { + case Qt::Key_Left: dx = -stepSize; break; + case Qt::Key_Right: dx = stepSize; break; + case Qt::Key_Up: dy = -stepSize; break; + case Qt::Key_Down: dy = stepSize; break; + case Qt::Key_Prior: dy = -5 * stepSize; break; + case Qt::Key_Next: dy = 5 * stepSize; break; + } + int adj = dx + dy; + // kdDebug() << "KKbdAccessExtensions::resizePanelFromKey: adj = " << adj << endl; + if (adj != 0) + resizePanel(dx, dy, state); + else { + if (key == Qt::Key_Enter && ::qt_cast<QDockWindow*>( d->panel )) { + QDockWindow* dockWindow = dynamic_cast<QDockWindow *>(d->panel); + if (dockWindow->area()) + dockWindow->undock(); + else + dockWindow->dock(); + } + } + showIcon(); +} + +void KKbdAccessExtensions::displayAccessKeys() +{ + // Build a list of valid access keys that don't collide with shortcuts. + QString availableAccessKeys = "ABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890"; + QPtrList<KXMLGUIClient> allClients = d->mainWindow->factory()->clients(); + QPtrListIterator<KXMLGUIClient> it( allClients ); + KXMLGUIClient *client; + while( (client=it.current()) !=0 ) + { + ++it; + KActionPtrList actions = client->actionCollection()->actions(); + for (int j = 0; j < (int)actions.count(); j++) { + KAction* action = actions[j]; + KShortcut sc = action->shortcut(); + for (int i = 0; i < (int)sc.count(); i++) { + KKeySequence seq = sc.seq(i); + if (seq.count() == 1) { + QString s = seq.toString(); + if (availableAccessKeys.contains(s)) + availableAccessKeys.remove(s); + } + } + } + } + // Find all visible, focusable widgets and create a QLabel for each. Don't exceed + // available list of access keys. + QWidgetList* allWidgets = kapp->allWidgets(); + QWidget* widget = allWidgets->first(); + int accessCount = 0; + int maxAccessCount = availableAccessKeys.length(); + int overlap = 20; + QPoint prevGlobalPos = QPoint(-overlap, -overlap); + while (widget && (accessCount < maxAccessCount)) { + if (widget->isVisible() && widget->isFocusEnabled() ) { + QRect r = widget->rect(); + QPoint p(r.x(), r.y()); + // Don't display an access key if within overlap pixels of previous one. + QPoint globalPos = widget->mapToGlobal(p); + QPoint diffPos = globalPos - prevGlobalPos; + if (diffPos.manhattanLength() > overlap) { + accessCount++; + QLabel* lab=new QLabel(widget, "", widget, 0, Qt::WDestructiveClose); + lab->setPalette(QToolTip::palette()); + lab->setLineWidth(2); + lab->setFrameStyle(QFrame::Box | QFrame::Plain); + lab->setMargin(3); + lab->adjustSize(); + lab->move(p); + if (!d->accessKeyLabels) { + d->accessKeyLabels = new QPtrList<QLabel>; + d->accessKeyLabels->setAutoDelete(true); + } + d->accessKeyLabels->append(lab); + prevGlobalPos = globalPos; + } + } + widget = allWidgets->next(); + } + if (accessCount > 0) { + // Sort the access keys from left to right and down the screen. + QValueList<KSortedLabel> sortedLabels; + for (int i = 0; i < accessCount; i++) + sortedLabels.append(KSortedLabel(d->accessKeyLabels->at(i))); + qHeapSort( sortedLabels ); + // Assign access key labels. + for (int i = 0; i < accessCount; i++) { + QLabel* lab = sortedLabels[i].label(); + QChar s = availableAccessKeys[i]; + lab->setText(s); + lab->adjustSize(); + lab->show(); + } + } +} + +// Handling of the HTML accesskey attribute. +bool KKbdAccessExtensions::handleAccessKey( const QKeyEvent* ev ) +{ +// Qt interprets the keyevent also with the modifiers, and ev->text() matches that, +// but this code must act as if the modifiers weren't pressed + if (!d->accessKeyLabels) return false; + QChar c; + if( ev->key() >= Key_A && ev->key() <= Key_Z ) + c = 'A' + ev->key() - Key_A; + else if( ev->key() >= Key_0 && ev->key() <= Key_9 ) + c = '0' + ev->key() - Key_0; + else { + // TODO fake XKeyEvent and XLookupString ? + // This below seems to work e.g. for eacute though. + if( ev->text().length() == 1 ) + c = ev->text()[ 0 ]; + } + if( c.isNull()) + return false; + + QLabel* lab = d->accessKeyLabels->first(); + while (lab) { + if (lab->text() == c) { + lab->buddy()->setFocus(); + delete d->accessKeyLabels; + d->accessKeyLabels = 0; + return true; + } + lab = d->accessKeyLabels->next(); + } + return false; +} + +KSortedLabel::KSortedLabel(QLabel* l) : + m_l(l) { } + +KSortedLabel::KSortedLabel() : + m_l(0) { } + +bool KSortedLabel::operator<( KSortedLabel l ) +{ + QPoint p1 = m_l->mapToGlobal(m_l->pos()); + QPoint p2 = l.label()->mapToGlobal(l.label()->pos()); + return (p1.y() < p2.y() || (p1.y() == p2.y() && p1.x() < p2.x())); +} |