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 | |
38 | namespace { |
39 | static const int TOP_HISTORY_ITEM_INDEX = 2; |
40 | } |
41 | |
42 | // #define DEBUG_EVENTS__ |
43 | |
44 | #ifdef DEBUG_EVENTS__ |
45 | kdbgstream& 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 | */ |
73 | class KLineEditBlackKey : public KLineEdit { |
74 | public: |
75 | KLineEditBlackKey( QWidget* parent ) |
76 | : KLineEdit( parent ) |
77 | {} |
78 | |
79 | ~KLineEditBlackKey() { |
80 | } |
81 | protected: |
82 | virtual void keyPressEvent( QKeyEvent* e ) { |
83 | KLineEdit::keyPressEvent( e ); |
84 | e->accept(); |
85 | |
86 | } |
87 | |
88 | }; |
89 | |
90 | 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 = ( screen.height() ) * 3/4; |
105 | int = ( screen.width() ) * 1/3; |
106 | |
107 | m_popupProxy = new PopupProxy( this, menuHeight, menuWidth ); |
108 | |
109 | connect( this, SIGNAL(aboutToShow()), SLOT(slotAboutToShow()) ); |
110 | } |
111 | |
112 | KlipperPopup::() { |
113 | |
114 | } |
115 | |
116 | void KlipperPopup::() { |
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 | |
129 | void KlipperPopup::() { |
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 | |
138 | void KlipperPopup::() { |
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 | |
165 | void KlipperPopup::( 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 | |
204 | void KlipperPopup::( QAction* action ) { |
205 | m_actions.append(action); |
206 | } |
207 | |
208 | |
209 | /* virtual */ |
210 | void KlipperPopup::( 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 | |
291 | void KlipperPopup::() |
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 | |