konq_combo.cc 29.1 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* This file is part of the KDE project
   Copyright (C) 2001 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
Dirk Mueller's avatar
Dirk Mueller committed
16
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Dirk Mueller's avatar
Dirk Mueller committed
17
   Boston, MA 02110-1301, USA.
18
19
*/

20
21
#include <QPainter>
#include <QStyle>
22
23
24
25
26
#include <QPixmap>
#include <QPaintEvent>
#include <QEvent>
#include <QKeyEvent>
#include <QMouseEvent>
David Faure's avatar
David Faure committed
27
#include <kapplication.h>
28
#include <kconfig.h>
29
#include <kconfiggroup.h>
30
#include <kcompletionbox.h>
31
#include <kdebug.h>
32
#include <kiconloader.h>
33
#include <kicontheme.h>
34
#include <klineedit.h>
35
#include <konq_pixmapprovider.h>
Aaron J. Seigo's avatar
build    
Aaron J. Seigo committed
36
#include <kstandardshortcut.h>
37
#include <konq_mainwindow.h>
38
#include <kstringhandler.h>
39

40
#include "konq_view.h"
41
#include "konq_combo.h"
42
43
#include "KonquerorAdaptor.h"
#include "konqueror_interface.h"
44
45

KConfig * KonqCombo::s_config = 0L;
46
const int KonqCombo::temporary = 0;
47

48
static QString titleOfURL( const QString& urlStr )
49
{
Aaron J. Seigo's avatar
Aaron J. Seigo committed
50
    KUrl url( urlStr );
Frank Osterfeld's avatar
Frank Osterfeld committed
51
    KonqHistoryList historylist = KonqHistoryManager::kself()->entries();
52
53
    KonqHistoryList::iterator historyentry = historylist.findEntry( url );
    if ( historyentry == historylist.end() && !url.url().endsWith( "/" ) ) {
54
55
        url.setPath( url.path()+'/' );
        historyentry = historylist.findEntry( url );
56
    }
57
    return ( historyentry != historylist.end() ? (*historyentry).title : QString() );
58
59
}

Robert Knight's avatar
Robert Knight committed
60
class KonqComboListBoxPixmap : public QListWidgetItem
61
62
{
public:
63
    KonqComboListBoxPixmap( const QString& text );
64
65
66
67
    KonqComboListBoxPixmap( const QPixmap &, const QString& text, const QString& title );

    const QPixmap *pixmap() const { return &pm; }

Robert Knight's avatar
Robert Knight committed
68
69
    int height( const QListWidget * ) const;
    int width( const QListWidget * )  const;
70
71
72
73

    int rtti() const;
    static int RTTI;

74
75
    bool reuse( const QString& newText );

76
77
78
79
protected:
    void paint( QPainter * );

private:
80
    bool lookup_pending;
81
82
83
84
    QPixmap pm;
    QString title;
};

85
86
87
class KonqComboLineEdit : public KLineEdit
{
public:
Pascal Létourneau's avatar
Pascal Létourneau committed
88
    KonqComboLineEdit( QWidget *parent=0 );
89
90
91
92
93
94
    void setCompletedItems( const QStringList& items );
};

class KonqComboCompletionBox : public KCompletionBox
{
public:
Pascal Létourneau's avatar
Pascal Létourneau committed
95
    KonqComboCompletionBox( QWidget *parent );
96
97
98
99
    void setItems( const QStringList& items );
    void insertStringList( const QStringList & list, int index = -1 );
};

100
101
KonqCombo::KonqCombo( QWidget *parent )
          : KHistoryCombo( parent ),
102
            m_returnPressed( false ),
103
            m_permanent( false ),
104
            m_modifier( Qt::NoButton ),
105
	    m_pageSecurity( KonqMainWindow::NotCrypted )
106
{
Laurent Montel's avatar
Laurent Montel committed
107
    setInsertPolicy( NoInsertion );
108
109
    setSizePolicy( QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed ));

Dirk Mueller's avatar
Dirk Mueller committed
110
    Q_ASSERT( s_config );
111

112
    KConfigGroup locationBarGroup( s_config, "Location Bar" );
Laurent Montel's avatar
Laurent Montel committed
113
    setMaxCount( locationBarGroup.readEntry("Maximum of URLs in combo", 20 ));
114

115
    // We should also connect the completionBox' highlighted signal to
116
    // our setEditText() slot, because we're handling the signals ourselves.
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
117
118
119
    // But we're lazy and let KCompletionBox do this and simply switch off
    // handling of signals later.
    setHandleSignals( true );
120

Pascal Létourneau's avatar
Pascal Létourneau committed
121
122
123
124
    KonqComboLineEdit *edit = new KonqComboLineEdit( this );
    edit->setHandleSignals( true );
    edit->setCompletionBox( new KonqComboCompletionBox( edit ) );
    setLineEdit( edit );
125

Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
126
    completionBox()->setTabHandling( true );
127

128
    // Make the lineedit consume the Qt::Key_Enter event...
129
    setTrapReturnKey( true );
130

131
    connect( KonqHistoryManager::kself(), SIGNAL(cleared()), SLOT(slotCleared()) );
132
133
134
135
    connect( this, SIGNAL(cleared() ), SLOT(slotCleared()) );
    connect( this, SIGNAL(highlighted( int )), SLOT(slotSetIcon( int )) );
    connect( this, SIGNAL(activated( const QString& )),
             SLOT(slotActivated( const QString& )) );
136
137
138
139
140
141
}

KonqCombo::~KonqCombo()
{
}

Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
142
143
144
145
146
147
148
149
150
void KonqCombo::init( KCompletion *completion )
{
    setCompletionObject( completion, false ); //KonqMainWindow handles signals
    setAutoDeleteCompletionObject( false );
    setCompletionMode( completion->completionMode() );

    loadItems();
}

151
152
void KonqCombo::setURL( const QString& url )
{
Laurent Montel's avatar
Laurent Montel committed
153
    //kDebug(1202) << "KonqCombo::setURL: " << url << ", returnPressed ? " << m_returnPressed << endl;
154
    setTemporary( url );
155

156
    if ( m_returnPressed ) { // Really insert...
157
        m_returnPressed = false;
158
159
160
161
        org::kde::Konqueror::Main dbus( "org.kde.konqueror",
                                        KONQ_MAIN_PATH,
                                        QDBusConnection::sessionBus() );
        dbus.addToCombo( url );
162
163
164
165
166
    }
}

void KonqCombo::setTemporary( const QString& text )
{
167
    setTemporary( text, KonqPixmapProvider::self()->pixmapFor(text) );
168
169
170
171
}

void KonqCombo::setTemporary( const QString& url, const QPixmap& pix )
{
Laurent Montel's avatar
Laurent Montel committed
172
    //kDebug(1202) << "KonqCombo::setTemporary: " << url << ", temporary = " << temporary << endl;
173

174
175
    // Insert a temporary item when we don't have one yet
    if ( count() == 0 )
176
      insertItem( pix, url, temporary, titleOfURL( url ) );
177
178
179
180
    else
    {
        if (url != temporaryItem())
          applyPermanent();
181
182

        updateItem( pix, url, temporary, titleOfURL( url ) );
183
    }
184

Laurent Montel's avatar
Laurent Montel committed
185
    setCurrentIndex( temporary );
186
187
}

188
189
void KonqCombo::removeDuplicates( int index )
{
Laurent Montel's avatar
Laurent Montel committed
190
    //kDebug(1202) << "KonqCombo::removeDuplicates: Starting index =  " << index << endl;
191

192
193
194
    QString url (temporaryItem());
    if (url.endsWith("/"))
      url.truncate(url.length()-1);
195

196
    // Remove all dupes, if available...
197
    for ( int i = index; i < count(); i++ )
198
    {
Thorsten Roeder's avatar
Thorsten Roeder committed
199
        QString item (itemText(i));
200
201
        if (item.endsWith("/"))
          item.truncate(item.length()-1);
202

203
204
205
206
207
        if ( item == url )
            removeItem( i );
    }
}

208
// called via DBUS in all instances
209
210
void KonqCombo::insertPermanent( const QString& url )
{
Laurent Montel's avatar
Laurent Montel committed
211
    //kDebug(1202) << "KonqCombo::insertPermanent: URL = " << url << endl;
212
213
214
215
216
217
218
219
220
    saveState();
    setTemporary( url );
    m_permanent = true;
    restoreState();
}

// called right before a new (different!) temporary item will be set. So we
// insert an item that was marked permanent properly at position 1.
void KonqCombo::applyPermanent()
221
{
222
    if ( m_permanent && !temporaryItem().isEmpty() ) {
223

Thorsten Roeder's avatar
Thorsten Roeder committed
224
        // Remove as many items as needed to honor maxCount()
225
226
227
        int index = count();
        while ( count() >= maxCount() )
            removeItem( --index );
228

229
        QString item = temporaryItem();
230
        insertItem( KonqPixmapProvider::self()->pixmapFor( item ), item, 1, titleOfURL( item ) );
Laurent Montel's avatar
Laurent Montel committed
231
        //kDebug(1202) << "KonqCombo::applyPermanent: " << url << endl;
232

233
        // Remove all duplicates starting from index = 2
234
        removeDuplicates( 2 );
235
        m_permanent = false;
236
237
238
    }
}

239
240
void KonqCombo::insertItem( const QString &text, int index, const QString& title )
{
Dirk Mueller's avatar
Dirk Mueller committed
241
    KonqComboListBoxPixmap* item = new KonqComboListBoxPixmap( QPixmap(), text, title );
Pascal Létourneau's avatar
Pascal Létourneau committed
242
    KHistoryCombo::insertItem( index, text, item );
243
244
245
246
}

void KonqCombo::insertItem( const QPixmap &pixmap, const QString& text, int index, const QString& title )
{
Pascal Létourneau's avatar
Pascal Létourneau committed
247
248
    KonqComboListBoxPixmap* item = new KonqComboListBoxPixmap( pixmap, text, title );
    KHistoryCombo::insertItem( index, pixmap, text, item );
249
}
250

251
void KonqCombo::updateItem( const QPixmap& pix, const QString& t, int index, const QString& title )
252
{
253
254
    Q_UNUSED( title )

255
    // No need to flicker
Thorsten Roeder's avatar
Thorsten Roeder committed
256
    if (itemText( index ) == t &&
257
        (!pixmap(index).isNull() && pixmap(index).serialNumber() == pix.serialNumber()))
258
        return;
259

Laurent Montel's avatar
Laurent Montel committed
260
    // kDebug(1202) << "KonqCombo::updateItem: item='" << t << "', index='"
Waldo Bastian's avatar
Waldo Bastian committed
261
    //               << index << "'" << endl;
262

Thorsten Roeder's avatar
Thorsten Roeder committed
263
    // QComboBox::changeItem() doesn't honor the pixmap when
264
    // using an editable combobox, so we just remove and insert
265
266
267
    // ### use QComboBox::changeItem(), once that finally works
    // Well lets try it now as it seems to work fine for me. We
    // can always revert :)
268
269
270
    //KonqComboListBoxPixmap* item = new KonqComboListBoxPixmap( pix, t, title );
    //listBox()->changeItem( item, index );
    changeItem( pix, t, index );
271

272
273
    setUpdatesEnabled( false );
    lineEdit()->setUpdatesEnabled( false );
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
274

275
276
    removeItem( index );
    insertItem( pix, t, index );
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
277

278
279
    setUpdatesEnabled( true );
    lineEdit()->setUpdatesEnabled( true );
280
    update();
281
282
283
284
285
286
}

void KonqCombo::saveState()
{
    m_cursorPos = cursorPosition();
    m_currentText = currentText();
Laurent Montel's avatar
Laurent Montel committed
287
    m_currentIndex = currentIndex();
288
289
290
291
292
293
294
295
296
297
298
299
}

void KonqCombo::restoreState()
{
    setTemporary( m_currentText );
    lineEdit()->setCursorPosition( m_cursorPos );
}

void KonqCombo::updatePixmaps()
{
    saveState();

300
    setUpdatesEnabled( false );
301
302
    KonqPixmapProvider *prov = KonqPixmapProvider::self();
    for ( int i = 1; i < count(); i++ ) {
Thorsten Roeder's avatar
Thorsten Roeder committed
303
        updateItem( prov->pixmapFor( itemText( i ) ), itemText( i ), i, titleOfURL( itemText( i ) ) );
304
    }
305
306
    setUpdatesEnabled( true );
    repaint();
307
308
309
310
311
312
313
314
315

    restoreState();
}

void KonqCombo::loadItems()
{
    clear();
    int i = 0;

316
317
    KConfigGroup historyConfigGroup( s_config, "History" ); // delete the old 2.0.x completion
    historyConfigGroup.writeEntry( "CompletionItems", "unused" );
318

319
320
    KConfigGroup locationBarGroup( s_config, "Location Bar" );
    const QStringList items = locationBarGroup.readPathListEntry( "ComboContents" );
321
322
    QStringList::ConstIterator it = items.begin();
    QString item;
323
    bool first = true;
324
325
326
    while ( it != items.end() ) {
        item = *it;
        if ( !item.isEmpty() ) { // only insert non-empty items
327
	    if( first ) {
328
                insertItem( KonqPixmapProvider::self()->pixmapFor( item, K3Icon::SizeSmall ),
329
                            item, i++, titleOfURL( item ) );
330
	    }
331
332
            else
                // icons will be loaded on-demand
333
                insertItem( item, i++, titleOfURL( item ) );
334
            first = false;
335
336
337
338
339
        }
        ++it;
    }

    if ( count() > 0 )
340
        m_permanent = true; // we want the first loaded item to stay
341
342
}

343
344
void KonqCombo::slotSetIcon( int index )
{
345
    if( pixmap( index ).isNull())
346
        // on-demand icon loading
Thorsten Roeder's avatar
Thorsten Roeder committed
347
348
349
        updateItem( KonqPixmapProvider::self()->pixmapFor( itemText( index ),
                    K3Icon::SizeSmall ), itemText( index ), index,
                    titleOfURL( itemText( index ) ) );
350
351
352
    update();
}

353
354
355
356
357
358
359
360
361
362
363
void KonqCombo::getStyleOption(QStyleOptionComboBox* comboOpt)
{
   //We only use this for querying metrics,so it's rough..
   comboOpt->init(this);
   comboOpt->editable = isEditable();
   comboOpt->frame    = hasFrame();
   comboOpt->iconSize = iconSize();
   comboOpt->currentIcon = itemIcon(currentIndex());
   comboOpt->currentText = currentText();
}

364
365
void KonqCombo::popup()
{
366
  for( int i = 0; i < count(); ++i )
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
367
    {
368
        if( pixmap( i ).isNull() )
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
369
        {
370
            // on-demand icon loading
Thorsten Roeder's avatar
Thorsten Roeder committed
371
372
            updateItem( KonqPixmapProvider::self()->pixmapFor( itemText( i ),
                        K3Icon::SizeSmall), itemText( i ), i, titleOfURL( itemText( i ) ) );
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
373
        }
374
    }
Thorsten Roeder's avatar
Thorsten Roeder committed
375
    KHistoryCombo::showPopup();
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
376
}
377

378
379
380
381
382
383
void KonqCombo::saveItems()
{
    QStringList items;
    int i = m_permanent ? 0 : 1;

    for ( ; i < count(); i++ )
Thorsten Roeder's avatar
Thorsten Roeder committed
384
        items.append( itemText( i ) );
385

386
387
388
    KConfigGroup locationBarGroup( s_config, "Location Bar" );
    locationBarGroup.writePathEntry( "ComboContents", items );
    KonqPixmapProvider::self()->save( locationBarGroup, "ComboIconCache", items );
389

390
391
392
393
394
    s_config->sync();
}

void KonqCombo::clearTemporary( bool makeCurrent )
{
395
    applyPermanent();
Thorsten Roeder's avatar
Thorsten Roeder committed
396
    setItemText( temporary, QString() ); // ### default pixmap?
397
    if ( makeCurrent )
Laurent Montel's avatar
Laurent Montel committed
398
      setCurrentIndex( temporary );
399
400
}

Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
401
402
403
404
405
406
407
408
409
bool KonqCombo::eventFilter( QObject *o, QEvent *ev )
{
    // Handle Ctrl+Del/Backspace etc better than the Qt widget, which always
    // jumps to the next whitespace.
    QLineEdit *edit = lineEdit();
    if ( o == edit ) {
        int type = ev->type();
        if ( type == QEvent::KeyPress ) {
            QKeyEvent *e = static_cast<QKeyEvent *>( ev );
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
410

411
            if ( e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter ) {
412
413
                m_modifier = e->state();
                return false;
414
415
            }

416
            KShortcut key( e->key() | e->modifiers() );
417

Aaron J. Seigo's avatar
build    
Aaron J. Seigo committed
418
419
            if ( key == KStandardShortcut::deleteWordBack() ||
                 key == KStandardShortcut::deleteWordForward() ||
420
421
                 ((e->modifiers() & Qt::ControlModifier) &&
                   (e->key() == Qt::Key_Left || e->key() == Qt::Key_Right) ) ) {
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
422
423
424
425
426
                selectWord(e);
                e->accept();
                return true;
            }
        }
Carsten Pfeiffer's avatar
better    
Carsten Pfeiffer committed
427
428

        else if ( type == QEvent::MouseButtonDblClick ) {
429
            edit->selectAll();
Carsten Pfeiffer's avatar
better    
Carsten Pfeiffer committed
430
431
            return true;
        }
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
432
433
434
435
    }
    return KComboBox::eventFilter( o, ev );
}

436
437
438
439
440
void KonqCombo::keyPressEvent( QKeyEvent *e )
{
    KHistoryCombo::keyPressEvent( e );
    // we have to set it as temporary, otherwise we wouldn't get our nice
    // pixmap. Yes, QComboBox still sucks.
441
    KShortcut key( e->key() | e->modifiers() );
Aaron J. Seigo's avatar
build    
Aaron J. Seigo committed
442
443
    if ( key == KStandardShortcut::rotateUp() ||
         key == KStandardShortcut::rotateDown() )
444
         setTemporary( currentText() );
445
446
}

447
/*
448
449
   Handle Ctrl+Cursor etc better than the Qt widget, which always
   jumps to the next whitespace. This code additionally jumps to
450
   the next [/#?:], which makes more sense for URLs. The list of
451
452
   chars that will stop the cursor are '/', '.', '?', '#', ':'.
*/
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
453
454
void KonqCombo::selectWord(QKeyEvent *e)
{
455
    QLineEdit* edit = lineEdit();
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
456
457
458
    QString text = edit->text();
    int pos = edit->cursorPosition();
    int pos_old = pos;
459
    int count = 0;
460

461
    // TODO: make these a parameter when in kdelibs/kdeui...
Laurent Montel's avatar
Laurent Montel committed
462
    QList<QChar> chars;
463
    chars << QChar('/') << QChar('.') << QChar('?') << QChar('#') << QChar(':');
464
    bool allow_space_break = true;
465

466
    if( e->key() == Qt::Key_Left || e->key() == Qt::Key_Backspace ) {
467
468
469
        do {
            pos--;
            count++;
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
470
471
            if( allow_space_break && text[pos].isSpace() && count > 1 )
                break;
Laurent Montel's avatar
Laurent Montel committed
472
        } while( pos >= 0 && (chars.indexOf(text[pos]) == -1 || count <= 1) );
473

474
        if( e->modifiers() & Qt::ShiftModifier ) {
475
                  edit->cursorForward(true, 1-count);
476
        }
477
        else if(  e->key() == Qt::Key_Backspace ) {
Dirk Mueller's avatar
Dirk Mueller committed
478
            edit->cursorForward(false, 1-count);
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
479
480
481
482
483
            QString text = edit->text();
            int pos_to_right = edit->text().length() - pos_old;
            QString cut = text.left(edit->cursorPosition()) + text.right(pos_to_right);
            edit->setText(cut);
            edit->setCursorPosition(pos_old-count+1);
484
        }
485
        else {
Dirk Mueller's avatar
Dirk Mueller committed
486
            edit->cursorForward(false, 1-count);
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
487
        }
488
     }
489
     else if( e->key() == Qt::Key_Right || e->key() == Qt::Key_Delete ){
490
491
492
493
494
        do {
            pos++;
            count++;
                  if( allow_space_break && text[pos].isSpace() )
                      break;
Laurent Montel's avatar
Laurent Montel committed
495
        } while( pos < (int) text.length() && chars.indexOf(text[pos]) == -1 );
496

497
        if( e->modifiers() & Qt::ShiftModifier ) {
Dirk Mueller's avatar
Dirk Mueller committed
498
            edit->cursorForward(true, count+1);
499
        }
500
        else if(  e->key() == Qt::Key_Delete ) {
Dirk Mueller's avatar
Dirk Mueller committed
501
            edit->cursorForward(false, -count-1);
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
502
503
            QString text = edit->text();
            int pos_to_right = text.length() - pos - 1;
504
            QString cut = text.left(pos_old) +
Laurent Montel's avatar
Laurent Montel committed
505
               (pos_to_right > 0 ? text.right(pos_to_right) : QString() );
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
506
507
            edit->setText(cut);
            edit->setCursorPosition(pos_old);
508
        }
509
        else {
Dirk Mueller's avatar
Dirk Mueller committed
510
            edit->cursorForward(false, count+1);
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
511
512
513
514
        }
    }
}

515
516
void KonqCombo::slotCleared()
{
517
518
519
520
    org::kde::Konqueror::Main dbus( "org.kde.konqueror",
                                    KONQ_MAIN_PATH,
                                    QDBusConnection::sessionBus() );
    dbus.comboCleared( );
521
522
}

523
524
525
526
527
528
529
530
531
532
533
void KonqCombo::removeURL( const QString& url )
{
    setUpdatesEnabled( false );
    lineEdit()->setUpdatesEnabled( false );

    removeFromHistory( url );
    applyPermanent();
    setTemporary( currentText() );

    setUpdatesEnabled( true );
    lineEdit()->setUpdatesEnabled( true );
534
    update();
535
536
}

Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
537
538
539
void KonqCombo::mousePressEvent( QMouseEvent *e )
{
    m_dragStart = QPoint(); // null QPoint
540

Laurent Montel's avatar
Laurent Montel committed
541
    if ( e->button() == Qt::LeftButton && !pixmap( currentIndex()).isNull() ) {
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
542
543
        // check if the pixmap was clicked
        int x = e->pos().x();
544
545
546
547
548
        QStyleOptionComboBox comboOpt;
        getStyleOption(&comboOpt);
        int x0 = QStyle::visualRect( layoutDirection(), rect(),
            style()->subControlRect( QStyle::CC_ComboBox, &comboOpt, QStyle::SC_ComboBoxEditField,
                                     this ) ).x();
549

550
        if ( x > x0 + 2 && x < lineEdit()->x() ) {
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
551
552
553
554
            m_dragStart = e->pos();
            return; // don't call KComboBox::mousePressEvent!
        }
    }
555

556
    if ( e->button() == Qt::LeftButton && m_pageSecurity!=KonqMainWindow::NotCrypted )
557
558
        emit showPageSecurity();

Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
559
560
561
562
563
564
565
566
    KComboBox::mousePressEvent( e );
}

void KonqCombo::mouseMoveEvent( QMouseEvent *e )
{
    KComboBox::mouseMoveEvent( e );
    if ( m_dragStart.isNull() || currentText().isEmpty() )
        return;
567

568
    if ( e->state() & Qt::LeftButton &&
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
569
         (e->pos() - m_dragStart).manhattanLength() >
570
         KGlobalSettings::dndEventDelay() )
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
571
    {
Aaron J. Seigo's avatar
Aaron J. Seigo committed
572
        KUrl url( currentText() );
Wilco Greven's avatar
Wilco Greven committed
573
        if ( url.isValid() )
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
574
        {
575
576
577
            QDrag* drag = new QDrag(this);
            QMimeData* mime = new QMimeData();
            url.populateMimeData(mime);
Pascal Létourneau's avatar
Pascal Létourneau committed
578
            drag->setMimeData(mime);
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
579
            QPixmap pix = KonqPixmapProvider::self()->pixmapFor( currentText(),
580
                                                                 K3Icon::SizeMedium );
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
581
582
            if ( !pix.isNull() )
                drag->setPixmap( pix );
583
            drag->start();
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
584
585
586
587
        }
    }
}

Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
588
589
void KonqCombo::slotActivated( const QString& text )
{
Laurent Montel's avatar
Laurent Montel committed
590
    //kDebug(1202) << "KonqCombo::slotActivated: " << text << endl;
591
592
593
    applyPermanent();
    m_returnPressed = true;
    emit activated( text, m_modifier );
594
    m_modifier = Qt::NoButton;
Carsten Pfeiffer's avatar
Carsten Pfeiffer committed
595
596
}

597
598
599
600
601
void KonqCombo::setConfig( KConfig *kc )
{
    s_config = kc;
}

602
603
604
void KonqCombo::paintEvent( QPaintEvent *pe )
{
    QComboBox::paintEvent( pe );
605
606

    QLineEdit *edit = lineEdit();
607
608
609
610
611
612

    QStyleOptionComboBox comboOpt;
    getStyleOption(&comboOpt);
    QRect re = style()->subControlRect( QStyle::CC_ComboBox, &comboOpt,
                                        QStyle::SC_ComboBoxEditField, this );
    re = QStyle::visualRect(layoutDirection(), rect(), re);
613

614
    if ( m_pageSecurity!=KonqMainWindow::NotCrypted ) {
615
        QColor color(245, 246, 190);
616
617
618
619
620
621
622
	// From George: disabled for now.  We're going to match IE behavior in
	// 4.0 which means:
	// 1) No color, but padlock for SSL verified
	// 2) No padlock for non-verified SSL
	// 3) Green fill, padlock, and site info for high-assurance verified
	// 4) If available, red fill for known phishing site
        bool useColor = false; //hasSufficientContrast(color,edit->paletteForegroundColor());
623

624
625
626
        QPainter p( this );
        p.setClipRect( re );

627
628
629
630
631
	QPixmap pix = KonqPixmapProvider::self()->pixmapFor( currentText() );
	if ( useColor ) {
            p.fillRect( re.x(), re.y(), pix.width() + 4, re.height(), QBrush( color ));
            p.drawPixmap( re.x() + 2, re.y() + ( re.height() - pix.height() ) / 2, pix );
	}
632
633
634
635
636

        QRect r = edit->geometry();
        r.setRight( re.right() - pix.width() - 4 );
        if ( r != edit->geometry() )
            edit->setGeometry( r );
637
638
639
640

	if ( useColor)
	    edit->setPaletteBackgroundColor( color );

641
        pix = SmallIcon( m_pageSecurity==KonqMainWindow::Encrypted ? "encrypted" : "halfencrypted" );
642
        p.fillRect( re.right() - pix.width() - 3 , re.y(), pix.width() + 4, re.height(),
643
		    QBrush( useColor ? color : edit->paletteBackgroundColor() ));
644
        p.drawPixmap( re.right() - pix.width() -1 , re.y() + ( re.height() - pix.height() ) / 2, pix );
Allen Winter's avatar
Allen Winter committed
645
        p.setClipping( false );
646
    }
647
648
649
650
651
    else {
        QRect r = edit->geometry();
        r.setRight( re.right() );
        if ( r != edit->geometry() )
            edit->setGeometry( r );
652
        edit->setPaletteBackgroundColor( QApplication::palette( edit ).color( QPalette::Active, QPalette::Base ) );
653
    }
654
655
656
657
658
659
660
661
}

void KonqCombo::setPageSecurity( int pageSecurity )
{
    m_pageSecurity = pageSecurity;
    repaint();
}

Stephan Binner's avatar
Stephan Binner committed
662
663
bool KonqCombo::hasSufficientContrast(const QColor &c1, const QColor &c2)
{
664
    // Taken from khtml/misc/helper.cc
Stephan Binner's avatar
Stephan Binner committed
665
666
667
#define HUE_DISTANCE 40
#define CONTRAST_DISTANCE 10

668
669
    int h1, s1, v1, h2, s2, v2;
    int hdist = -CONTRAST_DISTANCE;
Laurent Montel's avatar
Laurent Montel committed
670
671
    c1.getHsv(&h1,&s1,&v1);
    c2.getHsv(&h2,&s2,&v2);
672
    if(h1!=-1 && h2!=-1) { // grey values have no hue
Stephan Kulow's avatar
Stephan Kulow committed
673
        hdist = qAbs(h1-h2);
674
675
676
677
678
679
680
681
682
683
684
        if (hdist > 180) hdist = 360-hdist;
        if (hdist < HUE_DISTANCE) {
            hdist -= HUE_DISTANCE;
            // see if they are high key or low key colours
            bool hk1 = h1>=45 && h1<=225;
            bool hk2 = h2>=45 && h2<=225;
            if (hk1 && hk2)
                hdist = (5*hdist)/3;
            else if (!hk1 && !hk2)
                hdist = (7*hdist)/4;
        }
Stephan Kulow's avatar
Stephan Kulow committed
685
        hdist = qMin(hdist, HUE_DISTANCE*2);
686
    }
Stephan Kulow's avatar
Stephan Kulow committed
687
    return hdist + (qAbs(s1-s2)*128)/(160+qMin(s1,s2)) + qAbs(v1-v2) > CONTRAST_DISTANCE;
688
689
}

690
691
692
///////////////////////////////////////////////////////////////////////////////

KonqComboListBoxPixmap::KonqComboListBoxPixmap( const QString& text )
Robert Knight's avatar
Robert Knight committed
693
    : QListWidgetItem()
694
{
695
696
    setText( text );
    lookup_pending = true;
697
698
699
}

KonqComboListBoxPixmap::KonqComboListBoxPixmap( const QPixmap & pix, const QString& text, const QString& _title )
Robert Knight's avatar
Robert Knight committed
700
  : QListWidgetItem()
701
702
703
704
{
    pm = pix;
    title = _title;
    setText( text );
705
    lookup_pending = false;
706
707
708
709
}

void KonqComboListBoxPixmap::paint( QPainter *painter )
{
710
711
    if ( lookup_pending ) {
        title = titleOfURL( text() );
Stephan Binner's avatar
Stephan Binner committed
712
        if ( !title.isEmpty() )
713
            pm = KonqPixmapProvider::self()->pixmapFor( text(), K3Icon::SizeSmall );
Stephan Binner's avatar
Stephan Binner committed
714
715
716
        else if ( text().find( "://" ) == -1 ) {
            title = titleOfURL( "http://"+text() );
            if ( !title.isEmpty() )
717
                pm = KonqPixmapProvider::self()->pixmapFor( "http://"+text(), K3Icon::SizeSmall );
Stephan Binner's avatar
Stephan Binner committed
718
            else
719
                pm = KonqPixmapProvider::self()->pixmapFor( text(), K3Icon::SizeSmall );
Stephan Binner's avatar
Stephan Binner committed
720
        }
721
722
        else
            pm = QPixmap();
723
724
725
        lookup_pending = false;
    }

Robert Knight's avatar
Robert Knight committed
726
    int itemHeight = listWidget()->visualItemRect(this).height();
727
728
729
730
731
732
733
734
735
    int yPos, pmWidth = 0;
    const QPixmap *pm = pixmap();

    if ( pm && ! pm->isNull() ) {
        yPos = ( itemHeight - pm->height() ) / 2;
        painter->drawPixmap( 3, yPos, *pm );
        pmWidth = pm->width() + 5;
    }

Robert Knight's avatar
Robert Knight committed
736
737
    int entryWidth = listWidget()->width() - listWidget()->style()->pixelMetric( QStyle::PM_ScrollBarExtent ) -
                     2 * listWidget()->style()->pixelMetric( QStyle::PM_DefaultFrameWidth );
738
739
740
741
    int titleWidth = ( entryWidth / 3 ) - 1;
    int urlWidth = entryWidth - titleWidth - pmWidth - 2;

    if ( !text().isEmpty() ) {
Robert Knight's avatar
Robert Knight committed
742
        QString squeezedText = listWidget()->fontMetrics().elidedText( text(), Qt::ElideRight, urlWidth );
743
        painter->drawText( pmWidth, 0, urlWidth + pmWidth, itemHeight,
744
745
                           Qt::AlignLeft | Qt::AlignTop, squeezedText );

Pascal Létourneau's avatar
Pascal Létourneau committed
746
        painter->setPen( KGlobalSettings::inactiveTextColor() );
Robert Knight's avatar
Robert Knight committed
747
        squeezedText = listWidget()->fontMetrics().elidedText( title, Qt::ElideRight, titleWidth );
748
749
750
751
752
753
754
755
        QFont font = painter->font();
        font.setItalic( true );
        painter->setFont( font );
        painter->drawText( entryWidth - titleWidth, 0, titleWidth,
                           itemHeight, Qt::AlignLeft | Qt::AlignTop, squeezedText );
    }
}

Robert Knight's avatar
Robert Knight committed
756
int KonqComboListBoxPixmap::height( const QListWidget* lb ) const
757
758
759
760
761
{
    int h;
    if ( text().isEmpty() )
        h = pm.height();
    else
762
763
        h = qMax( pm.height(), lb->fontMetrics().lineSpacing() + 2 );
    return qMax( h, QApplication::globalStrut().height() );
764
765
}

Robert Knight's avatar
Robert Knight committed
766
int KonqComboListBoxPixmap::width( const QListWidget* lb ) const
767
768
{
    if ( text().isEmpty() )
769
770
        return qMax( pm.width() + 6, QApplication::globalStrut().width() );
    return qMax( pm.width() + lb->fontMetrics().width( text() ) + 6,
771
772
773
774
775
776
777
778
                 QApplication::globalStrut().width() );
}

int KonqComboListBoxPixmap::RTTI = 1003;

int KonqComboListBoxPixmap::rtti() const
{
    return RTTI;
Stephan Binner's avatar
Stephan Binner committed
779
780
}

781
782
783
784
785
786
787
788
789
790
791
bool KonqComboListBoxPixmap::reuse( const QString& newText )
{
    if ( text() == newText )
        return false;

    lookup_pending = true;
    setText( newText );
    return true;
}
///////////////////////////////////////////////////////////////////////////////

Pascal Létourneau's avatar
Pascal Létourneau committed
792
KonqComboLineEdit::KonqComboLineEdit( QWidget *parent )
793
794
795
796
                  :KLineEdit( parent )
{
    setClearButtonShown( true );
}
797
798
799
800
801
802
803
804
805
806
807
808
809
810

void KonqComboLineEdit::setCompletedItems( const QStringList& items )
{
    QString txt;
    KonqComboCompletionBox *completionbox = static_cast<KonqComboCompletionBox*>( completionBox() );

    if ( completionbox && completionbox->isVisible() )
        // The popup is visible already - do the matching on the initial string,
        // not on the currently selected one.
        txt = completionbox->cancelledText();
    else
        txt = text();

    if ( !items.isEmpty() && !(items.count() == 1 && txt == items.first()) ) {
811
        if ( !completionBox( false ) ) {
Pascal Létourneau's avatar
Pascal Létourneau committed
812
            setCompletionBox( new KonqComboCompletionBox( this ) );
813
814
            completionbox = static_cast<KonqComboCompletionBox*>( completionBox() );
        }
815
816

        if ( completionbox->isVisible() ) {
Robert Knight's avatar
Robert Knight committed
817
818
819
820
821
822
823
824
825
826

            QListWidgetItem* currentItem = completionbox->currentItem();
            bool wasSelected = false;
            QString currentSelection;

            if ( currentItem != 0 ) {
                wasSelected = currentItem->isSelected();
                currentSelection = currentItem->text();
            }

827
            completionbox->setItems( items );
Robert Knight's avatar
Robert Knight committed
828
829
830
831
832
            QList<QListWidgetItem*> matchedItems = completionbox->findItems
                                ( currentSelection, Qt::MatchExactly );

            QListWidgetItem* item = matchedItems.isEmpty() ? 0 : matchedItems.first();

833
834
835
836
837
            if( !item || !wasSelected )
            {
                wasSelected = false;
                item = completionbox->item( 0 );
            }
838
839
            if ( item ) {
                completionbox->blockSignals( true );
Pascal Létourneau's avatar
Pascal Létourneau committed
840
                completionbox->setCurrentItem( item );
Robert Knight's avatar
Robert Knight committed
841
                item->setSelected(wasSelected);
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
                completionbox->blockSignals( false );
            }
        }
        else { // completion box not visible yet -> show it
            if ( !txt.isEmpty() )
                completionbox->setCancelledText( txt );
            completionbox->setItems( items );
            completionbox->popup();
        }

        if ( autoSuggest() ) {
            int index = items.first().find( txt );
            QString newText = items.first().mid( index );
            setUserSelection( false );
            setCompletedText( newText, true );
        }
    }
    else
        if ( completionbox && completionbox->isVisible() )
            completionbox->hide();
}

///////////////////////////////////////////////////////////////////////////////
Pascal Létourneau's avatar
Pascal Létourneau committed
865
866
KonqComboCompletionBox::KonqComboCompletionBox( QWidget *parent )
                       :KCompletionBox( parent ) {}
867
868
869
870
871
872

void KonqComboCompletionBox::setItems( const QStringList& items )
{
    bool block = signalsBlocked();
    blockSignals( true );

Robert Knight's avatar
Robert Knight committed
873
874
875
876
    int rowIndex = 0;
    
    if ( count() == 0 )
        addItems( items );
877
878
879
880
881
882
883
884
885
886
    else {
        //Keep track of whether we need to change anything,
        //so we can avoid a repaint for identical updates,
        //to reduce flicker
        bool dirty = false;

        QStringList::ConstIterator it = items.constBegin();
        const QStringList::ConstIterator itEnd = items.constEnd();

        for ( ; it != itEnd; ++it) {
Robert Knight's avatar
Robert Knight committed
887
888
            if ( rowIndex < count() ) {
                const bool changed = ((KonqComboListBoxPixmap*)item(rowIndex))->reuse( *it );
889
                dirty = dirty || changed;
Robert Knight's avatar
Robert Knight committed
890
                rowIndex++;
891
892
893
894
            }
            else {
                dirty = true;
                //Inserting an item is a way of making this dirty
Robert Knight's avatar
Robert Knight committed
895
                addItem( new KonqComboListBoxPixmap( *it ) );
896
897
898
899
            }
        }

        //If there is an unused item, mark as dirty -> less items now
Robert Knight's avatar
Robert Knight committed
900
        if ( rowIndex < count() )
901
902
            dirty = true;

Robert Knight's avatar
Robert Knight committed
903
904
        while ( rowIndex < count() ) {
            delete item(rowIndex);
905
906
        }

Robert Knight's avatar
Robert Knight committed
907
908
909
        //TODO KDE 4 - Port this
        //if ( dirty )
        //    triggerUpdate( false );
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
    }

    if ( isVisible() && size().height() != sizeHint().height() )
        sizeAndPosition();

    blockSignals( block );

    // Trigger d->down_workaround = true within KCompletionBox
    QStringList dummy;
    KCompletionBox::insertItems( dummy, 1 );
}

void KonqComboCompletionBox::insertStringList( const QStringList & list, int index )
{
    if ( index < 0 )
        index = count();
Robert Knight's avatar
Robert Knight committed
926
927
928
    for ( QStringList::ConstIterator it = list.begin(); it != list.end(); ++it ) {
        insertItem( index++ , new KonqComboListBoxPixmap( *it ) );
    }
929
}
930
#include "konq_combo.moc"