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.
284 lines
8.6 KiB
284 lines
8.6 KiB
/* This file is part of the KDE project |
|
Copyright (C) 2004 Esben Mose Hansen <kde@mosehansen.dk> |
|
Copyright (C) by Andrew Stanley-Jones <asj@cban.com> |
|
Copyright (C) 2000 by Carsten Pfeiffer <pfeiffer@kde.org> |
|
|
|
This program is free software; you can redistribute it and/or |
|
modify it under the terms of the GNU General Public |
|
License as published by the Free Software Foundation; either |
|
version 2 of the License, or (at your option) any later version. |
|
|
|
This program 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 |
|
General Public License for more details. |
|
|
|
You should have received a copy of the GNU General Public License |
|
along with this program; see the file COPYING. If not, write to |
|
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
|
Boston, MA 02110-1301, USA. |
|
*/ |
|
#include "klipperpopup.h" |
|
|
|
#include <QApplication> |
|
#include "klipper_debug.h" |
|
#include <QDesktopWidget> |
|
#include <QKeyEvent> |
|
#include <QWidgetAction> |
|
|
|
#include <KHelpMenu> |
|
#include <KLineEdit> |
|
#include <KLocalizedString> |
|
#include <KWindowInfo> |
|
|
|
#include "history.h" |
|
#include "klipper.h" |
|
#include "popupproxy.h" |
|
|
|
namespace { |
|
static const int TOP_HISTORY_ITEM_INDEX = 2; |
|
} |
|
|
|
// #define DEBUG_EVENTS__ |
|
|
|
#ifdef DEBUG_EVENTS__ |
|
kdbgstream& operator<<( kdbgstream& stream, const QKeyEvent& e ) { |
|
stream << "(QKeyEvent(text=" << e.text() << ",key=" << e.key() << ( e.isAccepted()?",accepted":",ignored)" ) << ",count=" << e.count(); |
|
if ( e.modifiers() & Qt::AltModifier ) { |
|
stream << ",ALT"; |
|
} |
|
if ( e.modifiers() & Qt::ControlModifier ) { |
|
stream << ",CTRL"; |
|
} |
|
if ( e.modifiers() & Qt::MetaModifier ) { |
|
stream << ",META"; |
|
} |
|
if ( e.modifiers() & Qt::ShiftModifier ) { |
|
stream << ",SHIFT"; |
|
} |
|
if ( e.isAutoRepeat() ) { |
|
stream << ",AUTOREPEAT"; |
|
} |
|
stream << ")"; |
|
|
|
return stream; |
|
} |
|
#endif |
|
|
|
KlipperPopup::KlipperPopup( History* history ) |
|
: m_dirty( true ), |
|
m_textForEmptyHistory( i18n( "<empty clipboard>" ) ), |
|
m_textForNoMatch( i18n( "<no matches>" ) ), |
|
m_history( history ), |
|
m_helpMenu( nullptr ), |
|
m_popupProxy( nullptr ), |
|
m_filterWidget( nullptr ), |
|
m_filterWidgetAction( nullptr ), |
|
m_nHistoryItems( 0 ), |
|
m_showHelp(true), |
|
m_lastEvent(nullptr) |
|
{ |
|
ensurePolished(); |
|
KWindowInfo windowInfo( winId(), NET::WMGeometry ); |
|
QRect geometry = windowInfo.geometry(); |
|
QRect screen = qApp->desktop()->screenGeometry(geometry.center()); |
|
int menuHeight = ( screen.height() ) * 3/4; |
|
int menuWidth = ( screen.width() ) * 1/3; |
|
|
|
m_popupProxy = new PopupProxy( this, menuHeight, menuWidth ); |
|
|
|
connect(this, &KlipperPopup::aboutToShow, this, &KlipperPopup::slotAboutToShow); |
|
} |
|
|
|
KlipperPopup::~KlipperPopup() { |
|
|
|
} |
|
|
|
void KlipperPopup::slotAboutToShow() { |
|
if ( m_filterWidget ) { |
|
if ( !m_filterWidget->text().isEmpty() ) { |
|
m_dirty = true; |
|
m_filterWidget->clear(); |
|
} |
|
} |
|
ensureClean(); |
|
|
|
} |
|
|
|
void KlipperPopup::ensureClean() { |
|
// If the history is unchanged since last menu build, the is no reason |
|
// to rebuild it, |
|
if ( m_dirty ) { |
|
rebuild(); |
|
} |
|
|
|
} |
|
|
|
void KlipperPopup::buildFromScratch() { |
|
addSection(QIcon::fromTheme(QStringLiteral("klipper")), i18n("Klipper - Clipboard Tool")); |
|
|
|
m_filterWidget = new KLineEdit(this); |
|
m_filterWidget->setFocusPolicy( Qt::NoFocus ); |
|
m_filterWidget->setPlaceholderText(i18n("Search...")); |
|
m_filterWidgetAction = new QWidgetAction(this); |
|
m_filterWidgetAction->setDefaultWidget(m_filterWidget); |
|
addAction(m_filterWidgetAction); |
|
|
|
addSeparator(); |
|
for (int i = 0; i < m_actions.count(); i++) { |
|
|
|
if (i + 1 == m_actions.count() && m_showHelp) { |
|
if (!m_helpMenu) { |
|
m_helpMenu = new KHelpMenu( this, i18n("KDE cut & paste history utility"), false ); |
|
} |
|
addMenu(m_helpMenu->menu())->setIcon(QIcon::fromTheme(QStringLiteral("help-contents"))); |
|
addSeparator(); |
|
} |
|
|
|
addAction(m_actions.at(i)); |
|
} |
|
} |
|
|
|
void KlipperPopup::rebuild( const QString& filter ) { |
|
if (actions().isEmpty()) { |
|
buildFromScratch(); |
|
} else { |
|
for ( int i=0; i<m_nHistoryItems; i++ ) { |
|
Q_ASSERT(TOP_HISTORY_ITEM_INDEX < actions().count()); |
|
removeAction(actions().at(TOP_HISTORY_ITEM_INDEX)); |
|
} |
|
} |
|
|
|
// We search case insensitive until one uppercased character appears in the search term |
|
Qt::CaseSensitivity caseSens = (filter.toLower() == filter ? Qt::CaseInsensitive : Qt::CaseSensitive); |
|
QRegExp filterexp( filter, caseSens ); |
|
|
|
QPalette palette = m_filterWidget->palette(); |
|
if ( filterexp.isValid() ) { |
|
palette.setColor( m_filterWidget->foregroundRole(), palette.color(foregroundRole()) ); |
|
} else { |
|
palette.setColor( m_filterWidget->foregroundRole(), Qt::red ); |
|
} |
|
m_nHistoryItems = m_popupProxy->buildParent( TOP_HISTORY_ITEM_INDEX, filterexp ); |
|
if ( m_nHistoryItems == 0 ) { |
|
if ( m_history->empty() ) { |
|
insertAction(actions().at(TOP_HISTORY_ITEM_INDEX), new QAction(m_textForEmptyHistory, this)); |
|
} else { |
|
palette.setColor( m_filterWidget->foregroundRole(), Qt::red ); |
|
insertAction(actions().at(TOP_HISTORY_ITEM_INDEX), new QAction(m_textForNoMatch, this)); |
|
} |
|
m_nHistoryItems++; |
|
} else { |
|
if ( history()->topIsUserSelected() ) { |
|
actions().at(TOP_HISTORY_ITEM_INDEX)->setCheckable(true); |
|
actions().at(TOP_HISTORY_ITEM_INDEX)->setChecked(true); |
|
} |
|
} |
|
m_filterWidget->setPalette( palette ); |
|
m_dirty = false; |
|
} |
|
|
|
void KlipperPopup::slotTopIsUserSelectedSet() { |
|
if ( !m_dirty && m_nHistoryItems > 0 && history()->topIsUserSelected() ) { |
|
actions().at(TOP_HISTORY_ITEM_INDEX)->setCheckable(true); |
|
actions().at(TOP_HISTORY_ITEM_INDEX)->setChecked(true); |
|
} |
|
} |
|
|
|
void KlipperPopup::plugAction( QAction* action ) { |
|
m_actions.append(action); |
|
} |
|
|
|
|
|
/* virtual */ |
|
void KlipperPopup::keyPressEvent( QKeyEvent* e ) { |
|
// Most events are send down directly to the m_filterWidget. |
|
// If the m_filterWidget does not handle the event, it will |
|
// come back to this method. Remembering the last event stops |
|
// the infinite event loop |
|
if (m_lastEvent == e) { |
|
m_lastEvent= nullptr; |
|
return; |
|
} |
|
m_lastEvent= e; |
|
// If alt-something is pressed, select a shortcut |
|
// from the menu. Do this by sending a keyPress |
|
// without the alt-modifier to the superobject. |
|
if ( e->modifiers() & Qt::AltModifier ) { |
|
QKeyEvent ke( QEvent::KeyPress, |
|
e->key(), |
|
e->modifiers() ^ Qt::AltModifier, |
|
e->text(), |
|
e->isAutoRepeat(), |
|
e->count() ); |
|
QMenu::keyPressEvent( &ke ); |
|
#ifdef DEBUG_EVENTS__ |
|
qCDebug(KLIPPER_LOG) << "Passing this event to ancestor (KMenu): " << e << "->" << ke; |
|
#endif |
|
if (ke.isAccepted()) { |
|
e->accept(); |
|
return; |
|
} else { |
|
e->ignore(); |
|
} |
|
} |
|
|
|
// Otherwise, send most events to the search |
|
// widget, except a few used for navigation: |
|
// These go to the superobject. |
|
switch( e->key() ) { |
|
case Qt::Key_Up: |
|
case Qt::Key_Down: |
|
case Qt::Key_Right: |
|
case Qt::Key_Left: |
|
case Qt::Key_Tab: |
|
case Qt::Key_Backtab: |
|
case Qt::Key_Escape: |
|
{ |
|
#ifdef DEBUG_EVENTS__ |
|
qCDebug(KLIPPER_LOG) << "Passing this event to ancestor (KMenu): " << e; |
|
#endif |
|
QMenu::keyPressEvent(e); |
|
|
|
break; |
|
} |
|
case Qt::Key_Return: |
|
case Qt::Key_Enter: |
|
{ |
|
QMenu::keyPressEvent(e); |
|
this->hide(); |
|
|
|
if (activeAction() == m_filterWidgetAction) |
|
setActiveAction(actions().at(TOP_HISTORY_ITEM_INDEX)); |
|
|
|
break; |
|
} |
|
default: |
|
{ |
|
#ifdef DEBUG_EVENTS__ |
|
qCDebug(KLIPPER_LOG) << "Passing this event down to child (KLineEdit): " << e; |
|
#endif |
|
setActiveAction(actions().at(actions().indexOf(m_filterWidgetAction))); |
|
QString lastString = m_filterWidget->text(); |
|
|
|
QApplication::sendEvent(m_filterWidget, e); |
|
if (m_filterWidget->text() != lastString) { |
|
m_dirty = true; |
|
rebuild(m_filterWidget->text()); |
|
} |
|
|
|
break; |
|
} //default: |
|
} //case |
|
m_lastEvent= nullptr; |
|
} |
|
|
|
|
|
void KlipperPopup::slotSetTopActive() |
|
{ |
|
if (actions().size() > TOP_HISTORY_ITEM_INDEX) { |
|
setActiveAction(actions().at(TOP_HISTORY_ITEM_INDEX)); |
|
} |
|
} |
|
|
|
|
|
|