/* This file is part of the KDE project Copyright (C) 2005, Gary Cramblitt 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. */ // TQt includes #include #include #include #include #include #include #include #include #include // KDE includes #include #include #include #include #include #include // KKbdAccessExtensions includes #include "kkbdaccessextensions.h" // TODO: See eventFilter method. //#include "kkbdaccessextensions.moc" class KPanelKbdSizerIcon : public TQCursor { public: KPanelKbdSizerIcon() : TQCursor(TQt::SizeAllCursor), isActive(false) { currentPos = TQPoint(-1, -1); } ~KPanelKbdSizerIcon() { hide(); } void show(const TQPoint p) { if (!isActive) { originalPos = TQCursor::pos(); kapp->setOverrideCursor(*this); isActive = true; } if (p != pos()) setPos(p); currentPos = p; } void hide() { if (isActive) { kapp->restoreOverrideCursor(); TQCursor::setPos(originalPos); } isActive = false; } void setShape(int shayp) { if (shayp != tqshape()) { // Must restore and override to get the icon to refresh. if (isActive) kapp->restoreOverrideCursor(); TQCursor::setShape((Qt::CursorShape)shayp); if (isActive) kapp->setOverrideCursor(*this); } } // Return the difference between a position and where icon is supposed to be. TQSize delta(const TQPoint p) { TQPoint d = p - currentPos; return TQSize(d.x(), d.y()); } // Return the difference between where the icon is currently positioned and where // it is supposed to be. TQSize delta() { return delta(pos()); } // True if the sizing icon is visible. bool isActive; private: // Icon's current position. TQPoint currentPos; // Mouse cursor's original position when icon is shown. TQPoint 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. TQWidget* panel; // Index of current handle of the panel. When panel is a TQDockWindow: // 1 = size horizontally // 2 = size vertically uint handleNdx; // Sizing icon. KPanelKbdSizerIcon* icon; // Sizing increment. int stepSize; // List of the access key TQLabels. If not 0, access keys are onscreen. TQPtrList* accessKeyLabels; // Pointer to the KMainWindow. KMainWindow* mainWindow; }; KKbdAccessExtensions::KKbdAccessExtensions(KMainWindow* tqparent, const char* name) : TQObject(tqparent, name) { // kdDebug() << "KKbdAccessExtensions::KKbdAccessExtensions: running." << endl; d = new KKbdAccessExtensionsPrivate; d->mainWindow = tqparent; d->fwdAction = new KAction(i18n("Resize Panel Forward"), KShortcut("F8"), 0, 0, tqparent->actionCollection(), "resize_panel_forward"); d->revAction = new KAction(i18n("Resize Panel Reverse"), KShortcut("Shift+F8"), 0, 0, tqparent->actionCollection(), "resize_panel_reverse"); d->accessKeysAction = new KAction(i18n("Access Keys"), KShortcut("Alt+F8"), 0, 0, tqparent->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( TQObject *o, TQEvent *e ) { if ( e->type() == TQEvent::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 TQDockWindow 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(); TQKeyEvent* kev = dynamic_cast(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() == TQEvent::MouseButtonPress) { exitSizing(); return true; } else if (d->accessKeyLabels && e->type() == TQEvent::MouseButtonPress) { delete d->accessKeyLabels; d->accessKeyLabels = 0; return true; } /* else if (e->type() == TQEvent::MouseMove && d->icon->isActive) { // Lock mouse cursor down. showIcon(); dynamic_cast(e)->accept(); return true; }*/ else if (e->type() == TQEvent::MouseMove && d->icon->isActive && d->panel) { // Resize according to mouse movement. TQMouseEvent* me = dynamic_cast(e); TQSize 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() == TQEvent::Resize && d->panel && TQT_BASE_OBJECT(o) == d->panel) { // TODO: This doesn't always work. showIcon(); } return false; } TQWidgetList* KKbdAccessExtensions::getAllPanels() { TQWidgetList* allWidgets = kapp->tqallWidgets(); TQWidgetList* allPanels = new TQWidgetList; TQWidget* widget = allWidgets->first(); while (widget) { if (widget->isVisible()) { if (::tqqt_cast( widget )) { // Only size TQSplitters with at least two handles (there is always one hidden). if (dynamic_cast(widget)->sizes().count() >= 2) allPanels->append(widget); } else if (::tqqt_cast( widget )) { if (dynamic_cast(widget)->isResizeEnabled()) { // kdDebug() << "KKbdAccessExtensions::getAllPanels: TQDockWindow = " << widget->name() << endl; allPanels->append(widget); } } } widget = allWidgets->next(); } delete allWidgets; return allPanels; } void KKbdAccessExtensions::nextHandle() { TQWidget* panel = d->panel; // See if current panel has another handle. If not, find next panel. if (panel) { bool advance = true; d->handleNdx++; if (::tqqt_cast( panel )) advance = (d->handleNdx >= dynamic_cast(panel)->sizes().count()); else // Undocked windows have only one "handle" (center). advance = (d->handleNdx > 2 || !dynamic_cast(panel)->area()); if (advance) { TQWidgetList* allWidgets = getAllPanels(); allWidgets->tqfindRef(panel); panel = 0; if (allWidgets->current()) panel = allWidgets->next(); delete allWidgets; d->handleNdx = 1; } } else { // Find first panel. TQWidgetList* allWidgets = getAllPanels(); panel = allWidgets->first(); delete allWidgets; d->handleNdx = 1; } d->panel = panel; if (panel) showIcon(); else exitSizing(); } void KKbdAccessExtensions::prevHandle() { TQWidget* 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) { TQWidgetList* allWidgets = getAllPanels(); allWidgets->tqfindRef(panel); panel = 0; if (allWidgets->current()) panel = allWidgets->prev(); delete allWidgets; if (panel) { if (::tqqt_cast( panel )) d->handleNdx = dynamic_cast(panel)->sizes().count() - 1; else { if (dynamic_cast(panel)->area()) d->handleNdx = 2; else d->handleNdx = 1; } } } } else { // Find last panel. TQWidgetList* allWidgets = getAllPanels(); panel = allWidgets->last(); delete allWidgets; if (panel) { if (::tqqt_cast( panel )) d->handleNdx = dynamic_cast(panel)->sizes().count() - 1; else { if (dynamic_cast(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; TQPoint p; // kdDebug() << "KKbdAccessExtensions::showIcon: tqtopLevelWidget = " << d->panel->tqtopLevelWidget()->name() << endl; if (::tqqt_cast( d->panel )) { TQSplitter* splitter = dynamic_cast(d->panel); int handleNdx = d->handleNdx - 1; TQValueList sizes = splitter->sizes(); // kdDebug() << "KKbdAccessExtensions::showIcon: sizes = " << sizes << endl; if (splitter->orientation() == Qt::Horizontal) { d->icon->setShape(TQt::SizeHorCursor); p.setX(sizes[handleNdx] + (splitter->handleWidth() / 2)); p.setY(splitter->height() / 2); } else { d->icon->setShape(TQt::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 { TQDockWindow* dockWindow = dynamic_cast(d->panel); p = dockWindow->pos(); if (dockWindow->area()) { // kdDebug() << "KKbdAccessExtensions::showIcon: pos = " << p << " of window = " << dockWindow->tqparentWidget()->name() << endl; p = dockWindow->tqparentWidget()->mapTo(dockWindow->tqtopLevelWidget(), p); // kdDebug() << "KKbdAccessExtensions::showIcon: mapTo = " << p << " of window = " << dockWindow->tqtopLevelWidget()->name() << endl; // TODO: How to get the handle width? if (d->handleNdx == 1) { d->icon->setShape(TQt::SizeHorCursor); if (dockWindow->area()->orientation() == Qt::Vertical) { if (dockWindow->area()->handlePosition() == TQDockArea::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(TQt::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() == TQDockArea::Normal) // Handle is below the dock window. p.setY(p.y() + dockWindow->height()); // else Handle is above the dock window. } } p = dockWindow->tqtopLevelWidget()->mapToGlobal(p); } else { d->icon->setShape(TQt::SizeAllCursor); p = TQPoint(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 (::tqqt_cast( d->panel )) { TQSplitter* splitter = dynamic_cast(d->panel); int handleNdx = d->handleNdx - 1; TQValueList sizes = splitter->sizes(); // kdDebug() << "KKbdAccessExtensions::resizePanel: before sizes = " << sizes << endl; sizes[handleNdx] = sizes[handleNdx] + adj; // kdDebug() << "KKbdAccessExtensions::resizePanel: setSizes = " << sizes << endl; splitter->setSizes(sizes); TQApplication::postEvent(splitter, new TQEvent(TQEvent::LayoutHint)); } else { // TODO: How to get the handle width? TQDockWindow* dockWindow = dynamic_cast(d->panel); if (dockWindow->area()) { // kdDebug() << "KKbdAccessExtensions::resizePanel: fixedExtent = " << dockWindow->fixedExtent() << endl; TQSize 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() == TQDockArea::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() == TQDockArea::Reverse) adj = -adj; int h = fe.height(); if (h < 0) h = dockWindow->height(); h = h + adj; if (h > 0) dockWindow->setFixedExtentHeight(h); } dockWindow->updateGeometry(); TQApplication::postEvent(dockWindow->area(), new TQEvent(TQEvent::LayoutHint)); // kdDebug() << "KKbdAccessExtensions::resizePanel: fixedExtent = " << dockWindow->fixedExtent() << endl; } else { if (state == TQt::ShiftButton) { TQSize s = dockWindow->size(); s.setWidth(s.width() + dx); s.setHeight(s.height() + dy); dockWindow->resize(s); } else { TQPoint 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 TQt::Key_Left: dx = -stepSize; break; case TQt::Key_Right: dx = stepSize; break; case TQt::Key_Up: dy = -stepSize; break; case TQt::Key_Down: dy = stepSize; break; case TQt::Key_Prior: dy = -5 * stepSize; break; case TQt::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 == TQt::Key_Enter && ::tqqt_cast( d->panel )) { TQDockWindow* dockWindow = dynamic_cast(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. TQString availableAccessKeys = "ABCDEFGHIJKLMNOPTQRSTUVWXYZ01234567890"; TQPtrList allClients = d->mainWindow->factory()->clients(); TQPtrListIterator 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) { TQString s = seq.toString(); if (availableAccessKeys.tqcontains(s)) availableAccessKeys.remove(s); } } } } // Find all visible, focusable widgets and create a TQLabel for each. Don't exceed // available list of access keys. TQWidgetList* allWidgets = kapp->tqallWidgets(); TQWidget* widget = allWidgets->first(); int accessCount = 0; int maxAccessCount = availableAccessKeys.length(); int overlap = 20; TQPoint prevGlobalPos = TQPoint(-overlap, -overlap); while (widget && (accessCount < maxAccessCount)) { if (widget->isVisible() && widget->isFocusEnabled() ) { TQRect r = widget->rect(); TQPoint p(r.x(), r.y()); // Don't display an access key if within overlap pixels of previous one. TQPoint globalPos = widget->mapToGlobal(p); TQPoint diffPos = globalPos - prevGlobalPos; if (diffPos.manhattanLength() > overlap) { accessCount++; TQLabel* lab=new TQLabel(widget, "", widget, 0, TQt::WDestructiveClose); lab->setPalette(TQToolTip::palette()); lab->setLineWidth(2); lab->setFrameStyle(TQFrame::Box | TQFrame::Plain); lab->setMargin(3); lab->adjustSize(); lab->move(p); if (!d->accessKeyLabels) { d->accessKeyLabels = new TQPtrList; 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. TQValueList 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++) { TQLabel* lab = sortedLabels[i].label(); TQChar s = availableAccessKeys[i]; lab->setText(s); lab->adjustSize(); lab->show(); } } } // Handling of the HTML accesskey attribute. bool KKbdAccessExtensions::handleAccessKey( const TQKeyEvent* ev ) { // TQt 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; TQChar 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; TQLabel* 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(TQLabel* l) : m_l(l) { } KSortedLabel::KSortedLabel() : m_l(0) { } bool KSortedLabel::operator<( KSortedLabel l ) { TQPoint p1 = m_l->mapToGlobal(m_l->pos()); TQPoint p2 = l.label()->mapToGlobal(l.label()->pos()); return (p1.y() < p2.y() || (p1.y() == p2.y() && p1.x() < p2.x())); }