1/* This file is part of the KDE project
2 Copyright (C) 2004 Esben Mose Hansen <kde@mosehansen.dk>
3 Copyright (C) by Andrew Stanley-Jones <asj@cban.com>
4 Copyright (C) 2000 by Carsten Pfeiffer <pfeiffer@kde.org>
5
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public
8 License as published by the Free Software Foundation; either
9 version 2 of the License, or (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; see the file COPYING. If not, write to
18 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 Boston, MA 02110-1301, USA.
20*/
21#include "klipperpopup.h"
22
23#include <QtGui/QApplication>
24#include <QtGui/QKeyEvent>
25#include <QtGui/QWidgetAction>
26
27#include <KHelpMenu>
28#include <KLineEdit>
29#include <KLocale>
30#include <KWindowSystem>
31#include <KDebug>
32#include <KIcon>
33
34#include "history.h"
35#include "klipper.h"
36#include "popupproxy.h"
37
38namespace {
39 static const int TOP_HISTORY_ITEM_INDEX = 2;
40}
41
42// #define DEBUG_EVENTS__
43
44#ifdef DEBUG_EVENTS__
45kdbgstream& operator<<( kdbgstream& stream, const QKeyEvent& e ) {
46 stream << "(QKeyEvent(text=" << e.text() << ",key=" << e.key() << ( e.isAccepted()?",accepted":",ignored)" ) << ",count=" << e.count();
47 if ( e.modifiers() & Qt::AltModifier ) {
48 stream << ",ALT";
49 }
50 if ( e.modifiers() & Qt::ControlModifier ) {
51 stream << ",CTRL";
52 }
53 if ( e.modifiers() & Qt::MetaModifier ) {
54 stream << ",META";
55 }
56 if ( e.modifiers() & Qt::ShiftModifier ) {
57 stream << ",SHIFT";
58 }
59 if ( e.isAutoRepeat() ) {
60 stream << ",AUTOREPEAT";
61 }
62 stream << ")";
63
64 return stream;
65}
66#endif
67
68/**
69 * Exactly the same as KLineEdit, except that ALL key events are swallowed.
70 *
71 * We need this to avoid infinite loop when sending events to the search widget
72 */
73class KLineEditBlackKey : public KLineEdit {
74public:
75 KLineEditBlackKey( QWidget* parent )
76 : KLineEdit( parent )
77 {}
78
79 ~KLineEditBlackKey() {
80 }
81protected:
82 virtual void keyPressEvent( QKeyEvent* e ) {
83 KLineEdit::keyPressEvent( e );
84 e->accept();
85
86 }
87
88};
89
90KlipperPopup::KlipperPopup( History* history )
91 : m_dirty( true ),
92 m_textForEmptyHistory( i18n( "<empty clipboard>" ) ),
93 m_textForNoMatch( i18n( "<no matches>" ) ),
94 m_history( history ),
95 m_helpMenu( new KHelpMenu( this, Klipper::aboutData(), false ) ),
96 m_popupProxy( 0 ),
97 m_filterWidget( 0 ),
98 m_filterWidgetAction( 0 ),
99 m_nHistoryItems( 0 )
100{
101 KWindowInfo windowInfo = KWindowSystem::windowInfo( winId(), NET::WMGeometry );
102 QRect geometry = windowInfo.geometry();
103 QRect screen = KGlobalSettings::desktopGeometry(geometry.center());
104 int menuHeight = ( screen.height() ) * 3/4;
105 int menuWidth = ( screen.width() ) * 1/3;
106
107 m_popupProxy = new PopupProxy( this, menuHeight, menuWidth );
108
109 connect( this, SIGNAL(aboutToShow()), SLOT(slotAboutToShow()) );
110}
111
112KlipperPopup::~KlipperPopup() {
113
114}
115
116void KlipperPopup::slotAboutToShow() {
117 if ( m_filterWidget ) {
118 if ( !m_filterWidget->text().isEmpty() ) {
119 m_dirty = true;
120 m_filterWidget->clear();
121 m_filterWidget->setVisible(false);
122 m_filterWidgetAction->setVisible(false);
123 }
124 }
125 ensureClean();
126
127}
128
129void KlipperPopup::ensureClean() {
130 // If the history is unchanged since last menu build, the is no reason
131 // to rebuild it,
132 if ( m_dirty ) {
133 rebuild();
134 }
135
136}
137
138void KlipperPopup::buildFromScratch() {
139 addTitle(KIcon("klipper"), i18n("Klipper - Clipboard Tool"));
140
141 m_filterWidget = new KLineEditBlackKey(this);
142 m_filterWidget->setFocusPolicy( Qt::NoFocus );
143 m_filterWidgetAction = new QWidgetAction(this);
144 m_filterWidgetAction->setDefaultWidget(m_filterWidget);
145 m_filterWidgetAction->setVisible(false);
146 addAction(m_filterWidgetAction);
147
148 addSeparator();
149 for (int i = 0; i < m_actions.count(); i++) {
150
151 if (i + 1 == m_actions.count()) {
152 addMenu(m_helpMenu->menu())->setIcon(KIcon("help-contents"));
153 addSeparator();
154 }
155
156 addAction(m_actions.at(i));
157 }
158
159 if ( KGlobalSettings::insertTearOffHandle() ) {
160 setTearOffEnabled(true);
161 }
162
163}
164
165void KlipperPopup::rebuild( const QString& filter ) {
166 if (actions().isEmpty()) {
167 buildFromScratch();
168 } else {
169 for ( int i=0; i<m_nHistoryItems; i++ ) {
170 Q_ASSERT(TOP_HISTORY_ITEM_INDEX < actions().count());
171 removeAction(actions().at(TOP_HISTORY_ITEM_INDEX));
172 }
173 }
174
175 // We search case insensitive until one uppercased character appears in the search term
176 Qt::CaseSensitivity caseSens = (filter.toLower() == filter ? Qt::CaseInsensitive : Qt::CaseSensitive);
177 QRegExp filterexp( filter, caseSens );
178
179 QPalette palette = m_filterWidget->palette();
180 if ( filterexp.isValid() ) {
181 palette.setColor( m_filterWidget->foregroundRole(), palette.color(foregroundRole()) );
182 } else {
183 palette.setColor( m_filterWidget->foregroundRole(), Qt::red );
184 }
185 m_nHistoryItems = m_popupProxy->buildParent( TOP_HISTORY_ITEM_INDEX, filterexp );
186 if ( m_nHistoryItems == 0 ) {
187 if ( m_history->empty() ) {
188 insertAction(actions().at(TOP_HISTORY_ITEM_INDEX), new QAction(m_textForEmptyHistory, this));
189 } else {
190 palette.setColor( m_filterWidget->foregroundRole(), Qt::red );
191 insertAction(actions().at(TOP_HISTORY_ITEM_INDEX), new QAction(m_textForNoMatch, this));
192 }
193 m_nHistoryItems++;
194 } else {
195 if ( history()->topIsUserSelected() ) {
196 actions().at(TOP_HISTORY_ITEM_INDEX)->setCheckable(true);
197 actions().at(TOP_HISTORY_ITEM_INDEX)->setChecked(true);
198 }
199 }
200 m_filterWidget->setPalette( palette );
201 m_dirty = false;
202}
203
204void KlipperPopup::plugAction( QAction* action ) {
205 m_actions.append(action);
206}
207
208
209/* virtual */
210void KlipperPopup::keyPressEvent( QKeyEvent* e ) {
211 // If alt-something is pressed, select a shortcut
212 // from the menu. Do this by sending a keyPress
213 // without the alt-modifier to the superobject.
214 if ( e->modifiers() & Qt::AltModifier ) {
215 QKeyEvent ke( QEvent::KeyPress,
216 e->key(),
217 e->modifiers() ^ Qt::AltModifier,
218 e->text(),
219 e->isAutoRepeat(),
220 e->count() );
221 KMenu::keyPressEvent( &ke );
222#ifdef DEBUG_EVENTS__
223 kDebug() << "Passing this event to ancestor (KMenu): " << e << "->" << ke;
224#endif
225 if (ke.isAccepted()) {
226 e->accept();
227 return;
228 } else {
229 e->ignore();
230 }
231 }
232
233 // Otherwise, send most events to the search
234 // widget, except a few used for navigation:
235 // These go to the superobject.
236 switch( e->key() ) {
237 case Qt::Key_Up:
238 case Qt::Key_Down:
239 case Qt::Key_Right:
240 case Qt::Key_Left:
241 case Qt::Key_Tab:
242 case Qt::Key_Backtab:
243 case Qt::Key_Escape:
244 {
245#ifdef DEBUG_EVENTS__
246 kDebug() << "Passing this event to ancestor (KMenu): " << e;
247#endif
248 KMenu::keyPressEvent(e);
249
250 break;
251 }
252 case Qt::Key_Return:
253 case Qt::Key_Enter:
254 {
255 KMenu::keyPressEvent(e);
256 this->hide();
257
258 if (activeAction() == m_filterWidgetAction)
259 setActiveAction(actions().at(TOP_HISTORY_ITEM_INDEX));
260
261 break;
262 }
263 default:
264 {
265#ifdef DEBUG_EVENTS__
266 kDebug() << "Passing this event down to child (KLineEdit): " << e;
267#endif
268 setActiveAction(actions().at(actions().indexOf(m_filterWidgetAction)));
269 QString lastString = m_filterWidget->text();
270 QApplication::sendEvent(m_filterWidget, e);
271
272 if (m_filterWidget->text().isEmpty()) {
273 if (m_filterWidgetAction->isVisible())
274 m_filterWidget->setVisible(false);
275 m_filterWidgetAction->setVisible(false);
276 }
277 else if (!m_filterWidgetAction->isVisible() )
278 m_filterWidgetAction->setVisible(true);
279
280 if (m_filterWidget->text() != lastString) {
281 m_dirty = true;
282 rebuild(m_filterWidget->text());
283 }
284
285 break;
286 } //default:
287 } //case
288}
289
290
291void KlipperPopup::slotSetTopActive()
292{
293 if (actions().size() > TOP_HISTORY_ITEM_INDEX) {
294 setActiveAction(actions().at(TOP_HISTORY_ITEM_INDEX));
295 }
296}
297
298#include "klipperpopup.moc"
299