1 | /* |
2 | This file is part of libkabc. |
3 | Copyright (c) 2002 Helge Deller <deller@gmx.de> |
4 | 2002 Lubos Lunak <llunak@suse.cz> |
5 | 2001,2003 Carsten Pfeiffer <pfeiffer@kde.org> |
6 | 2001 Waldo Bastian <bastian@kde.org> |
7 | |
8 | This library is free software; you can redistribute it and/or |
9 | modify it under the terms of the GNU Library General Public |
10 | License as published by the Free Software Foundation; either |
11 | version 2 of the License, or (at your option) any later version. |
12 | |
13 | This library is distributed in the hope that it will be useful, |
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
16 | Library General Public License for more details. |
17 | |
18 | You should have received a copy of the GNU Library General Public License |
19 | along with this library; see the file COPYING.LIB. If not, write to |
20 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
21 | Boston, MA 02110-1301, USA. |
22 | */ |
23 | |
24 | #include "addresslineedit.h" |
25 | |
26 | #include <QApplication> |
27 | #include <QKeyEvent> |
28 | #include <QMouseEvent> |
29 | #include <QtCore/QObject> |
30 | #include <QtCore/QRegExp> |
31 | |
32 | #include <kcompletionbox.h> |
33 | #include <kconfig.h> |
34 | #include <kcursor.h> |
35 | #include <kdebug.h> |
36 | #include <kstandarddirs.h> |
37 | #include <kstandardshortcut.h> |
38 | |
39 | #include "stdaddressbook.h" |
40 | |
41 | //============================================================================= |
42 | // |
43 | // Class AddressLineEdit |
44 | // |
45 | //============================================================================= |
46 | |
47 | using namespace KABC; |
48 | |
49 | class AddressLineEdit::Private |
50 | { |
51 | public: |
52 | Private( AddressLineEdit *parent ) |
53 | : mParent( parent ), |
54 | mCompletionInitialized( false ), |
55 | mSmartPaste( false ) |
56 | { |
57 | init(); |
58 | } |
59 | |
60 | void init(); |
61 | QStringList addresses(); |
62 | QStringList removeMailDupes( const QStringList &adrs ); |
63 | |
64 | void slotCompletion() { mParent->doCompletion( false ); } |
65 | void slotPopupCompletion( const QString &completion ); |
66 | |
67 | AddressLineEdit *mParent; |
68 | QString mPreviousAddresses; |
69 | bool mUseCompletion; |
70 | bool mCompletionInitialized; |
71 | bool mSmartPaste; |
72 | |
73 | static bool sAddressesDirty; |
74 | static bool initialized; |
75 | }; |
76 | bool AddressLineEdit::Private::initialized = false; |
77 | K_GLOBAL_STATIC( KCompletion, sCompletion ) |
78 | |
79 | void AddressLineEdit::Private::init() |
80 | { |
81 | if ( !Private::initialized ) { |
82 | Private::initialized = true; |
83 | sCompletion->setOrder( KCompletion::Sorted ); |
84 | sCompletion->setIgnoreCase( true ); |
85 | } |
86 | |
87 | if ( mUseCompletion && !mCompletionInitialized ) { |
88 | mParent->setCompletionObject( sCompletion, false ); // we handle it ourself |
89 | mParent->connect( mParent, SIGNAL(completion(QString)), |
90 | mParent, SLOT(slotCompletion()) ); |
91 | |
92 | KCompletionBox *box = mParent->completionBox(); |
93 | mParent->connect( box, SIGNAL(currentTextChanged(QString)), |
94 | mParent, SLOT(slotPopupCompletion(QString)) ); |
95 | mParent->connect( box, SIGNAL(userCancelled(QString)), |
96 | SLOT(userCancelled(QString)) ); |
97 | |
98 | mCompletionInitialized = true; // don't connect muliple times. That's |
99 | // ugly, tho, better have completionBox() |
100 | // virtual in KDE 4 |
101 | // Why? This is only called once. Why should this be called more |
102 | // than once? And why was this protected? |
103 | } |
104 | } |
105 | |
106 | QStringList AddressLineEdit::Private::addresses() |
107 | { |
108 | QApplication::setOverrideCursor( QCursor( Qt::WaitCursor ) ); // loading might take a while |
109 | |
110 | QStringList result; |
111 | QLatin1String space( " " ); |
112 | QRegExp needQuotes( QLatin1String( "[^ 0-9A-Za-z\\x0080-\\xFFFF]" ) ); |
113 | QLatin1String endQuote( "\" " ); |
114 | QString addr, email; |
115 | |
116 | KABC::AddressBook *addressBook = KABC::StdAddressBook::self(); |
117 | KABC::AddressBook::Iterator it; |
118 | for ( it = addressBook->begin(); it != addressBook->end(); ++it ) { |
119 | QStringList emails = ( *it ).emails(); |
120 | |
121 | QString n = ( *it ).prefix() + space + |
122 | ( *it ).givenName() + space + |
123 | ( *it ).additionalName() + space + |
124 | ( *it ).familyName() + space + |
125 | ( *it ).suffix(); |
126 | |
127 | n = n.simplified(); |
128 | |
129 | QStringList::ConstIterator mit; |
130 | QStringList::ConstIterator end( emails.constEnd() ); |
131 | |
132 | for ( mit = emails.constBegin(); mit != end; ++mit ) { |
133 | email = *mit; |
134 | if ( !email.isEmpty() ) { |
135 | if ( n.isEmpty() || ( email.indexOf( QLatin1Char( '<' ) ) != -1 ) ) { |
136 | addr.clear(); |
137 | } else { /* do we really need quotes around this name ? */ |
138 | if ( n.indexOf( needQuotes ) != -1 ) { |
139 | addr = QLatin1Char( '"' ) + n + endQuote; |
140 | } else { |
141 | addr = n + space; |
142 | } |
143 | } |
144 | |
145 | if ( !addr.isEmpty() && |
146 | ( email.indexOf( QLatin1Char( '<' ) ) == -1 ) && |
147 | ( email.indexOf( QLatin1Char( '>' ) ) == -1 ) && |
148 | ( email.indexOf( QLatin1Char( ',' ) ) == -1 ) ) { |
149 | addr += QLatin1Char( '<' ) + email + QLatin1Char( '>' ); |
150 | } else { |
151 | addr += email; |
152 | } |
153 | addr = addr.trimmed(); |
154 | result.append( addr ); |
155 | } |
156 | } |
157 | } |
158 | |
159 | result += addressBook->allDistributionListNames(); |
160 | |
161 | QApplication::restoreOverrideCursor(); |
162 | |
163 | return result; |
164 | } |
165 | |
166 | QStringList AddressLineEdit::Private::removeMailDupes( const QStringList &addrs ) |
167 | { |
168 | QStringList src( addrs ); |
169 | qSort( src ); |
170 | |
171 | QString last; |
172 | for ( QStringList::Iterator it = src.begin(); it != src.end(); ) { |
173 | if ( *it == last ) { |
174 | it = src.erase( it ); |
175 | continue; // dupe |
176 | } |
177 | |
178 | last = *it; |
179 | ++it; |
180 | } |
181 | |
182 | return src; |
183 | } |
184 | |
185 | void AddressLineEdit::Private::( const QString &completion ) |
186 | { |
187 | mParent->setText( mPreviousAddresses + completion ); |
188 | mParent->cursorAtEnd(); |
189 | } |
190 | |
191 | bool AddressLineEdit::Private::sAddressesDirty = false; |
192 | |
193 | AddressLineEdit::AddressLineEdit( QWidget *parent, bool useCompletion ) |
194 | : KLineEdit( parent ), d( new Private( this ) ) |
195 | { |
196 | d->mUseCompletion = useCompletion; |
197 | |
198 | // Whenever a new AddressLineEdit is created (== a new composer is created), |
199 | // we set a dirty flag to reload the addresses upon the first completion. |
200 | // The address completions are shared between all AddressLineEdits. |
201 | // Is there a signal that tells us about addressbook updates? |
202 | if ( d->mUseCompletion ) { |
203 | d->sAddressesDirty = true; |
204 | } |
205 | } |
206 | |
207 | //----------------------------------------------------------------------------- |
208 | AddressLineEdit::~AddressLineEdit() |
209 | { |
210 | delete d; |
211 | } |
212 | |
213 | //----------------------------------------------------------------------------- |
214 | |
215 | void AddressLineEdit::setFont( const QFont &font ) |
216 | { |
217 | KLineEdit::setFont( font ); |
218 | if ( d->mUseCompletion ) { |
219 | completionBox()->setFont( font ); |
220 | } |
221 | } |
222 | |
223 | //----------------------------------------------------------------------------- |
224 | void AddressLineEdit::keyPressEvent( QKeyEvent *event ) |
225 | { |
226 | bool accept = false; |
227 | |
228 | if ( KStandardShortcut::shortcut( KStandardShortcut::SubstringCompletion ). |
229 | contains( event->key() | event->modifiers() ) ) { |
230 | doCompletion( true ); |
231 | accept = true; |
232 | } else if ( KStandardShortcut::shortcut( KStandardShortcut::TextCompletion ). |
233 | contains( event->key() | event->modifiers() ) ) { |
234 | int len = text().length(); |
235 | |
236 | if ( len == cursorPosition() ) { // at End? |
237 | doCompletion( true ); |
238 | accept = true; |
239 | } |
240 | } |
241 | |
242 | if ( !accept ) { |
243 | KLineEdit::keyPressEvent( event ); |
244 | } |
245 | } |
246 | |
247 | void AddressLineEdit::mouseReleaseEvent( QMouseEvent *event ) |
248 | { |
249 | if ( d->mUseCompletion && ( event->button() == Qt::MidButton ) ) { |
250 | d->mSmartPaste = true; |
251 | KLineEdit::mouseReleaseEvent( event ); |
252 | d->mSmartPaste = false; |
253 | return; |
254 | } |
255 | |
256 | KLineEdit::mouseReleaseEvent( event ); |
257 | } |
258 | |
259 | void AddressLineEdit::insert( const QString &oldText ) |
260 | { |
261 | if ( !d->mSmartPaste ) { |
262 | KLineEdit::insert( oldText ); |
263 | return; |
264 | } |
265 | |
266 | QString newText = oldText.trimmed(); |
267 | if ( newText.isEmpty() ) { |
268 | return; |
269 | } |
270 | |
271 | // remove newlines in the to-be-pasted string as well as an eventual |
272 | // mailto: protocol |
273 | newText.replace( QRegExp( QLatin1String( "\r?\n" ) ), QLatin1String( ", " ) ); |
274 | if ( newText.startsWith( QLatin1String( "mailto:" ) ) ) { |
275 | KUrl u( newText ); |
276 | newText = u.path(); |
277 | } else if ( newText.indexOf( QLatin1String( " at " ) ) != -1 ) { |
278 | // Anti-spam stuff |
279 | newText.replace( QLatin1String( " at " ), QLatin1String( "@" ) ); |
280 | newText.replace( QLatin1String( " dot " ), QLatin1String( "." ) ); |
281 | } else if ( newText.indexOf( QLatin1String( "(at)" ) ) != -1 ) { |
282 | newText.replace( QRegExp( QLatin1String( "\\s*\\(at\\)\\s*" ) ), QLatin1String( "@" ) ); |
283 | } |
284 | |
285 | QString contents = text(); |
286 | int start_sel = 0; |
287 | int end_sel = 0; |
288 | int pos = cursorPosition(); |
289 | if ( !selectedText().isEmpty() ) { |
290 | // Cut away the selection. |
291 | if ( pos > end_sel ) { |
292 | pos -= ( end_sel - start_sel ); |
293 | } else if ( pos > start_sel ) { |
294 | pos = start_sel; |
295 | } |
296 | contents = contents.left( start_sel ) + contents.right( end_sel + 1 ); |
297 | } |
298 | |
299 | int eot = contents.length(); |
300 | while ( ( eot > 0 ) && contents[ eot - 1 ].isSpace() ) { |
301 | eot--; |
302 | } |
303 | |
304 | if ( eot == 0 ) { |
305 | contents.clear(); |
306 | } else if ( pos >= eot ) { |
307 | if ( contents[ eot - 1 ] == QLatin1Char( ',' ) ) { |
308 | eot--; |
309 | } |
310 | contents.truncate( eot ); |
311 | contents += QLatin1String( ", " ); |
312 | pos = eot+2; |
313 | } |
314 | |
315 | contents = contents.left( pos ) + newText + contents.mid( pos ); |
316 | setText( contents ); |
317 | setCursorPosition( pos + newText.length() ); |
318 | } |
319 | |
320 | void AddressLineEdit::paste() |
321 | { |
322 | if ( d->mUseCompletion ) { |
323 | d->mSmartPaste = true; |
324 | } |
325 | |
326 | KLineEdit::paste(); |
327 | d->mSmartPaste = false; |
328 | } |
329 | |
330 | //----------------------------------------------------------------------------- |
331 | void AddressLineEdit::cursorAtEnd() |
332 | { |
333 | setCursorPosition( text().length() ); |
334 | } |
335 | |
336 | //----------------------------------------------------------------------------- |
337 | void AddressLineEdit::enableCompletion( bool enable ) |
338 | { |
339 | d->mUseCompletion = enable; |
340 | } |
341 | |
342 | //----------------------------------------------------------------------------- |
343 | void AddressLineEdit::doCompletion( bool ctrlT ) |
344 | { |
345 | if ( !d->mUseCompletion ) { |
346 | return; |
347 | } |
348 | |
349 | QString prevAddr; |
350 | |
351 | QString s( text() ); |
352 | int n = s.lastIndexOf( QLatin1Char( ',' ) ); |
353 | |
354 | if ( n >= 0 ) { |
355 | n++; // Go past the "," |
356 | |
357 | int len = s.length(); |
358 | |
359 | // Increment past any whitespace... |
360 | while ( n < len && s[ n ].isSpace() ) { |
361 | n++; |
362 | } |
363 | |
364 | prevAddr = s.left( n ); |
365 | s = s.mid( n, 255 ).trimmed(); |
366 | } |
367 | |
368 | if ( d->sAddressesDirty ) { |
369 | loadAddresses(); |
370 | } |
371 | |
372 | if ( ctrlT ) { |
373 | QStringList completions = sCompletion->substringCompletion( s ); |
374 | if ( completions.count() > 1 ) { |
375 | d->mPreviousAddresses = prevAddr; |
376 | setCompletedItems( completions ); |
377 | } else if ( completions.count() == 1 ) { |
378 | setText( prevAddr + completions.first() ); |
379 | } |
380 | |
381 | cursorAtEnd(); |
382 | return; |
383 | } |
384 | |
385 | KGlobalSettings::Completion mode = completionMode(); |
386 | |
387 | switch ( mode ) { |
388 | case KGlobalSettings::CompletionPopupAuto: |
389 | { |
390 | if ( s.isEmpty() ) { |
391 | break; |
392 | } |
393 | } |
394 | case KGlobalSettings::CompletionPopup: |
395 | { |
396 | d->mPreviousAddresses = prevAddr; |
397 | QStringList items = sCompletion->allMatches( s ); |
398 | items += sCompletion->allMatches( QLatin1String( "\"" ) + s ); |
399 | items += sCompletion->substringCompletion( QLatin1Char( '<' ) + s ); |
400 | int beforeDollarCompletionCount = items.count(); |
401 | |
402 | if ( s.indexOf( QLatin1Char( ' ' ) ) == -1 ) { // one word, possibly given name |
403 | items += sCompletion->allMatches( QLatin1String( "$$" ) + s ); |
404 | } |
405 | |
406 | if ( !items.isEmpty() ) { |
407 | if ( items.count() > beforeDollarCompletionCount ) { |
408 | // remove the '$$whatever$' part |
409 | for ( QStringList::Iterator it = items.begin(); |
410 | it != items.end(); ++it ) { |
411 | int pos = ( *it ).indexOf( QLatin1Char( '$' ), 2 ); |
412 | if ( pos < 0 ) { // ??? |
413 | continue; |
414 | } |
415 | ( *it ) = ( *it ).mid( pos + 1 ); |
416 | } |
417 | } |
418 | |
419 | items = d->removeMailDupes( items ); |
420 | |
421 | // We do not want KLineEdit::setCompletedItems to perform text |
422 | // completion (suggestion) since it does not know how to deal |
423 | // with providing proper completions for different items on the |
424 | // same line, e.g. comma-separated list of email addresses. |
425 | bool autoSuggest = ( mode != KGlobalSettings::CompletionPopupAuto ); |
426 | setCompletedItems( items, autoSuggest ); |
427 | |
428 | if ( !autoSuggest ) { |
429 | int index = items.first().indexOf( s ); |
430 | QString newText = prevAddr + items.first().mid( index ); |
431 | //kDebug() << "OLD TEXT:" << text(); |
432 | //kDebug() << "NEW TEXT:" << newText; |
433 | setUserSelection( false ); |
434 | setCompletedText( newText, true ); |
435 | } |
436 | } |
437 | |
438 | break; |
439 | } |
440 | |
441 | case KGlobalSettings::CompletionShell: |
442 | { |
443 | QString match = sCompletion->makeCompletion( s ); |
444 | if ( !match.isNull() && match != s ) { |
445 | setText( prevAddr + match ); |
446 | cursorAtEnd(); |
447 | } |
448 | break; |
449 | } |
450 | |
451 | case KGlobalSettings::CompletionMan: // Short-Auto in fact |
452 | case KGlobalSettings::CompletionAuto: |
453 | { |
454 | if ( !s.isEmpty() ) { |
455 | QString match = sCompletion->makeCompletion( s ); |
456 | if ( !match.isNull() && match != s ) { |
457 | setCompletedText( prevAddr + match ); |
458 | } |
459 | |
460 | break; |
461 | } |
462 | } |
463 | case KGlobalSettings::CompletionNone: |
464 | default: // fall through |
465 | break; |
466 | } |
467 | } |
468 | |
469 | //----------------------------------------------------------------------------- |
470 | void AddressLineEdit::loadAddresses() |
471 | { |
472 | sCompletion->clear(); |
473 | d->sAddressesDirty = false; |
474 | |
475 | const QStringList addrs = d->addresses(); |
476 | QStringList::ConstIterator end( addrs.end() ); |
477 | for ( QStringList::ConstIterator it = addrs.begin(); it != end; ++it ) { |
478 | addAddress( *it ); |
479 | } |
480 | } |
481 | |
482 | void AddressLineEdit::addAddress( const QString &addr ) |
483 | { |
484 | sCompletion->addItem( addr ); |
485 | |
486 | int pos = addr.indexOf( QLatin1Char( '<' ) ); |
487 | if ( pos >= 0 ) { |
488 | ++pos; |
489 | int pos2 = addr.indexOf( QLatin1Char( '>' ), pos ); |
490 | if ( pos2 >= 0 ) { |
491 | sCompletion->addItem( addr.mid( pos, pos2 - pos ) ); |
492 | } |
493 | } |
494 | } |
495 | |
496 | //----------------------------------------------------------------------------- |
497 | void AddressLineEdit::dropEvent( QDropEvent *event ) |
498 | { |
499 | const KUrl::List uriList = KUrl::List::fromMimeData( event->mimeData() ); |
500 | if ( !uriList.isEmpty() ) { |
501 | QString ct = text(); |
502 | KUrl::List::ConstIterator it = uriList.begin(); |
503 | for ( ; it != uriList.end(); ++it ) { |
504 | if ( !ct.isEmpty() ) { |
505 | ct.append( QLatin1String( ", " ) ); |
506 | } |
507 | |
508 | KUrl u( *it ); |
509 | if ( ( *it ).protocol() == QLatin1String( "mailto" ) ) { |
510 | ct.append( ( *it ).path() ); |
511 | } else { |
512 | ct.append( ( *it ).url() ); |
513 | } |
514 | } |
515 | setText( ct ); |
516 | setModified( true ); |
517 | } else { |
518 | if ( d->mUseCompletion ) { |
519 | d->mSmartPaste = true; |
520 | } |
521 | |
522 | KLineEdit::dropEvent( event ); |
523 | d->mSmartPaste = false; |
524 | } |
525 | } |
526 | |
527 | #include "moc_addresslineedit.cpp" |
528 | |