You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
koffice/lib/kofficecore/kkbdaccessextensions.cpp

668 lines
24 KiB

/* 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.
*/
// TQt includes
#include <tqsplitter.h>
#include <tqdockwindow.h>
#include <tqdockarea.h>
#include <tqevent.h>
#include <tqcursor.h>
#include <tqobjectlist.h>
#include <tqwidgetlist.h>
#include <tqlabel.h>
#include <tqtooltip.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 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<TQLabel>* accessKeyLabels;
// Pointer to the KMainWindow.
KMainWindow* mainWindow;
};
KKbdAccessExtensions::KKbdAccessExtensions(KMainWindow* parent, const char* name) :
TQObject(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( 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<TQKeyEvent *>(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<TQMouseEvent *>(e)->accept();
return true;
}*/
else if (e->type() == TQEvent::MouseMove && d->icon->isActive && d->panel) {
// Resize according to mouse movement.
TQMouseEvent* me = dynamic_cast<TQMouseEvent *>(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<TQSplitter*>( widget )) {
// Only size TQSplitters with at least two handles (there is always one hidden).
if (dynamic_cast<TQSplitter *>(widget)->sizes().count() >= 2)
allPanels->append(widget);
} else if (::tqqt_cast<TQDockWindow*>( widget )) {
if (dynamic_cast<TQDockWindow *>(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<TQSplitter*>( panel ))
advance = (d->handleNdx >= dynamic_cast<TQSplitter *>(panel)->sizes().count());
else
// Undocked windows have only one "handle" (center).
advance = (d->handleNdx > 2 || !dynamic_cast<TQDockWindow *>(panel)->area());
if (advance) {
TQWidgetList* allWidgets = getAllPanels();
allWidgets->findRef(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->findRef(panel);
panel = 0;
if (allWidgets->current()) panel = allWidgets->prev();
delete allWidgets;
if (panel) {
if (::tqqt_cast<TQSplitter*>( panel ))
d->handleNdx = dynamic_cast<TQSplitter *>(panel)->sizes().count() - 1;
else {
if (dynamic_cast<TQDockWindow *>(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<TQSplitter*>( panel ))
d->handleNdx = dynamic_cast<TQSplitter *>(panel)->sizes().count() - 1;
else {
if (dynamic_cast<TQDockWindow *>(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<TQSplitter*>( d->panel )) {
TQSplitter* splitter = dynamic_cast<TQSplitter *>(d->panel);
int handleNdx = d->handleNdx - 1;
TQValueList<int> 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<TQDockWindow *>(d->panel);
p = dockWindow->pos();
if (dockWindow->area()) {
// kdDebug() << "KKbdAccessExtensions::showIcon: pos = " << p << " of window = " << dockWindow->parentWidget()->name() << endl;
p = dockWindow->parentWidget()->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<TQSplitter*>( d->panel )) {
TQSplitter* splitter = dynamic_cast<TQSplitter *>(d->panel);
int handleNdx = d->handleNdx - 1;
TQValueList<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);
TQApplication::postEvent(splitter, new TQEvent(TQEvent::LayoutHint));
} else {
// TODO: How to get the handle width?
TQDockWindow* dockWindow = dynamic_cast<TQDockWindow *>(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<TQDockWindow*>( d->panel )) {
TQDockWindow* dockWindow = dynamic_cast<TQDockWindow *>(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<KXMLGUIClient> allClients = d->mainWindow->factory()->clients();
TQPtrListIterator<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) {
TQString s = seq.toString();
if (availableAccessKeys.contains(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<TQLabel>;
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<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++) {
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()));
}