mirror of
https://bitbucket.org/smil3y/kdelibs.git
synced 2025-02-23 18:32:49 +00:00

missing proper configuration interface for the shortcuts, the old one was borked anyway Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
1815 lines
57 KiB
C++
1815 lines
57 KiB
C++
/* This file is part of the KDE libraries
|
|
|
|
Copyright (C) 1997 Sven Radej (sven.radej@iname.com)
|
|
Copyright (c) 1999 Patrick Ward <PAT_WARD@HP-USA-om5.om.hp.com>
|
|
Copyright (c) 1999 Preston Brown <pbrown@kde.org>
|
|
|
|
Re-designed for KDE 2.x by
|
|
Copyright (c) 2000, 2001 Dawit Alemayehu <adawit@kde.org>
|
|
Copyright (c) 2000, 2001 Carsten Pfeiffer <pfeiffer@kde.org>
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Lesser General Public
|
|
License (LGPL) as published by the Free Software Foundation;
|
|
either version 2 of the License, or (at your option) any later
|
|
version.
|
|
|
|
This library 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
|
|
Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public License
|
|
along with this library; see the file COPYING.LIB. If not, write to
|
|
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "klineedit.h"
|
|
#include "klineedit_p.h"
|
|
|
|
#include <kaction.h>
|
|
#include <kapplication.h>
|
|
#include <kconfig.h>
|
|
#include <kconfiggroup.h>
|
|
#include <kcursor.h>
|
|
#include <kdebug.h>
|
|
#include <kcompletionbox.h>
|
|
#include <kicontheme.h>
|
|
#include <kicon.h>
|
|
#include <klocale.h>
|
|
#include <kmenu.h>
|
|
#include <kstandardaction.h>
|
|
#include <kstandardshortcut.h>
|
|
#include <kurl.h>
|
|
|
|
#include <QtCore/QTimer>
|
|
#include <QtGui/QClipboard>
|
|
#include <QtGui/QStyleOption>
|
|
#include <QtGui/QToolTip>
|
|
|
|
class KLineEditStyle;
|
|
|
|
class KLineEditPrivate
|
|
{
|
|
public:
|
|
KLineEditPrivate(KLineEdit* qq)
|
|
: q(qq)
|
|
{
|
|
completionBox = 0L;
|
|
handleURLDrops = true;
|
|
grabReturnKeyEvents = false;
|
|
|
|
userSelection = true;
|
|
autoSuggest = false;
|
|
disableRestoreSelection = false;
|
|
enableSqueezedText = false;
|
|
|
|
enableClickMsg = false;
|
|
threeStars = false;
|
|
completionRunning = false;
|
|
if (!s_initialized) {
|
|
KConfigGroup config( KGlobal::config(), "General" );
|
|
s_backspacePerformsCompletion = config.readEntry("Backspace performs completion", false);
|
|
s_initialized = true;
|
|
}
|
|
|
|
clearButton = 0;
|
|
clickInClear = false;
|
|
wideEnoughForClear = true;
|
|
|
|
// i18n: Placeholder text in line edit widgets is the text appearing
|
|
// before any user input, briefly explaining to the user what to type
|
|
// (e.g. "Enter search pattern").
|
|
// By default the text is set in italic, which may not be appropriate
|
|
// for some languages and scripts (e.g. for CJK ideographs).
|
|
QString metaMsg = i18nc("Italic placeholder text in line edits: 0 no, 1 yes", "1");
|
|
italicizePlaceholder = (metaMsg.trimmed() != QString('0'));
|
|
}
|
|
|
|
~KLineEditPrivate()
|
|
{
|
|
// causes a weird crash in KWord at least, so let Qt delete it for us.
|
|
// delete completionBox;
|
|
delete style.data();
|
|
}
|
|
|
|
void _k_slotStyleChanged()
|
|
{
|
|
if (clearButton) {
|
|
clearButton->setAnimationsEnabled(KGlobalSettings::graphicEffectsLevel() & KGlobalSettings::SimpleAnimationEffects);
|
|
}
|
|
}
|
|
|
|
void _k_textChanged(const QString &txt)
|
|
{
|
|
// COMPAT (as documented): emit userTextChanged whenever textChanged is emitted
|
|
if (!completionRunning && (txt != userText)) {
|
|
userText = txt;
|
|
}
|
|
}
|
|
|
|
// Call this when a completion operation changes the lineedit text
|
|
// "as if it had been edited by the user".
|
|
void _k_updateUserText(const QString &txt)
|
|
{
|
|
if (!completionRunning && (txt != userText)) {
|
|
userText = txt;
|
|
q->setModified(true);
|
|
emit q->textEdited(txt);
|
|
emit q->textChanged(txt);
|
|
}
|
|
}
|
|
|
|
// This is called when the lineedit is readonly.
|
|
// Either from setReadOnly() itself, or when we realize that
|
|
// we became readonly and setReadOnly() wasn't called (because it's not virtual)
|
|
// Typical case: comboBox->lineEdit()->setReadOnly(true)
|
|
void adjustForReadOnly()
|
|
{
|
|
if (style && style.data()->m_overlap) {
|
|
style.data()->m_overlap = 0;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Checks whether we should/should not consume a key used as a shortcut.
|
|
* This makes it possible to handle shortcuts in the focused widget before any
|
|
* window-global QAction is triggered.
|
|
*/
|
|
bool overrideShortcut(const QKeyEvent* e);
|
|
|
|
static bool s_initialized;
|
|
static bool s_backspacePerformsCompletion; // Configuration option
|
|
|
|
QColor previousHighlightColor;
|
|
QColor previousHighlightedTextColor;
|
|
|
|
bool userSelection: 1;
|
|
bool autoSuggest : 1;
|
|
bool disableRestoreSelection: 1;
|
|
bool handleURLDrops:1;
|
|
bool grabReturnKeyEvents:1;
|
|
bool enableSqueezedText:1;
|
|
bool completionRunning:1;
|
|
|
|
int squeezedEnd;
|
|
int squeezedStart;
|
|
QPalette::ColorRole bgRole;
|
|
QString squeezedText;
|
|
QString userText;
|
|
|
|
QString clickMessage;
|
|
bool enableClickMsg:1;
|
|
bool threeStars:1;
|
|
|
|
bool possibleTripleClick :1; // set in mousePressEvent, deleted in tripleClickTimeout
|
|
|
|
bool clickInClear:1;
|
|
bool wideEnoughForClear:1;
|
|
KLineEditButton *clearButton;
|
|
QWeakPointer<KLineEditStyle> style;
|
|
QString lastStyleClass;
|
|
|
|
KCompletionBox *completionBox;
|
|
|
|
bool italicizePlaceholder:1;
|
|
|
|
QAction *noCompletionAction, *shellCompletionAction, *autoCompletionAction, *popupCompletionAction, *shortAutoCompletionAction, *popupAutoCompletionAction, *defaultAction;
|
|
|
|
QMap<KGlobalSettings::Completion, bool> disableCompletionMap;
|
|
KLineEdit* q;
|
|
};
|
|
|
|
QStyle *KLineEditStyle::style() const
|
|
{
|
|
if (m_subStyle) {
|
|
return m_subStyle.data();
|
|
}
|
|
|
|
return KdeUiProxyStyle::style();
|
|
}
|
|
|
|
QRect KLineEditStyle::subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget) const
|
|
{
|
|
if (element == SE_LineEditContents) {
|
|
KLineEditStyle *unconstThis = const_cast<KLineEditStyle *>(this);
|
|
|
|
if (m_sentinel) {
|
|
// we are recursing: we're wrapping a style that wraps us!
|
|
unconstThis->m_subStyle.clear();
|
|
}
|
|
|
|
unconstThis->m_sentinel = true;
|
|
QStyle *s = m_subStyle ? m_subStyle.data() : style();
|
|
QRect rect = s->subElementRect(SE_LineEditContents, option, widget);
|
|
unconstThis->m_sentinel = false;
|
|
|
|
if (option->direction == Qt::LeftToRight) {
|
|
return rect.adjusted(0, 0, -m_overlap, 0);
|
|
} else {
|
|
return rect.adjusted(m_overlap, 0, 0, 0);
|
|
}
|
|
}
|
|
|
|
return KdeUiProxyStyle::subElementRect(element, option, widget);
|
|
}
|
|
|
|
bool KLineEditPrivate::s_backspacePerformsCompletion = false;
|
|
bool KLineEditPrivate::s_initialized = false;
|
|
|
|
|
|
KLineEdit::KLineEdit( const QString &string, QWidget *parent )
|
|
: QLineEdit( string, parent ), d(new KLineEditPrivate(this))
|
|
{
|
|
init();
|
|
}
|
|
|
|
KLineEdit::KLineEdit( QWidget *parent )
|
|
: QLineEdit( parent ), d(new KLineEditPrivate(this))
|
|
{
|
|
init();
|
|
}
|
|
|
|
|
|
KLineEdit::~KLineEdit ()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
void KLineEdit::init()
|
|
{
|
|
d->possibleTripleClick = false;
|
|
d->bgRole = backgroundRole();
|
|
|
|
// Enable the context menu by default.
|
|
QLineEdit::setContextMenuPolicy( Qt::DefaultContextMenu );
|
|
KCursor::setAutoHideCursor( this, true, true );
|
|
|
|
KGlobalSettings::Completion mode = completionMode();
|
|
d->autoSuggest = (mode == KGlobalSettings::CompletionMan ||
|
|
mode == KGlobalSettings::CompletionPopupAuto ||
|
|
mode == KGlobalSettings::CompletionAuto);
|
|
connect( this, SIGNAL(selectionChanged()), this, SLOT(slotRestoreSelectionColors()));
|
|
|
|
connect(KGlobalSettings::self(), SIGNAL(kdisplayStyleChanged()), this, SLOT(_k_slotStyleChanged()));
|
|
|
|
const QPalette p = palette();
|
|
if ( !d->previousHighlightedTextColor.isValid() )
|
|
d->previousHighlightedTextColor=p.color(QPalette::Normal,QPalette::HighlightedText);
|
|
if ( !d->previousHighlightColor.isValid() )
|
|
d->previousHighlightColor=p.color(QPalette::Normal,QPalette::Highlight);
|
|
|
|
d->style = new KLineEditStyle(this);
|
|
setStyle(d->style.data());
|
|
|
|
connect(this, SIGNAL(textChanged(QString)), this, SLOT(_k_textChanged(QString)));
|
|
}
|
|
|
|
QString KLineEdit::clickMessage() const
|
|
{
|
|
return d->clickMessage;
|
|
}
|
|
|
|
void KLineEdit::setClearButtonShown(bool show)
|
|
{
|
|
if (show) {
|
|
if (d->clearButton) {
|
|
return;
|
|
}
|
|
|
|
d->clearButton = new KLineEditButton(this);
|
|
d->clearButton->setObjectName("KLineEditButton");
|
|
d->clearButton->setCursor( Qt::ArrowCursor );
|
|
d->clearButton->setToolTip( i18nc( "@action:button Clear current text in the line edit", "Clear text" ) );
|
|
|
|
updateClearButtonIcon(text());
|
|
updateClearButton();
|
|
connect(this, SIGNAL(textChanged(QString)), this, SLOT(updateClearButtonIcon(QString)));
|
|
} else {
|
|
disconnect(this, SIGNAL(textChanged(QString)), this, SLOT(updateClearButtonIcon(QString)));
|
|
delete d->clearButton;
|
|
d->clearButton = 0;
|
|
d->clickInClear = false;
|
|
if (d->style) {
|
|
d->style.data()->m_overlap = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool KLineEdit::isClearButtonShown() const
|
|
{
|
|
return d->clearButton != 0;
|
|
}
|
|
|
|
QSize KLineEdit::clearButtonUsedSize() const
|
|
{
|
|
QSize s;
|
|
if (d->clearButton) {
|
|
const int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, 0, this);
|
|
s = d->clearButton->sizeHint();
|
|
s.rwidth() += frameWidth;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
// Decides whether to show or hide the icon; called when the text changes
|
|
void KLineEdit::updateClearButtonIcon(const QString& text)
|
|
{
|
|
if (!d->clearButton) {
|
|
return;
|
|
}
|
|
if (isReadOnly()) {
|
|
d->adjustForReadOnly();
|
|
return;
|
|
}
|
|
|
|
// set proper icon if necessary
|
|
if (d->clearButton->pixmap().isNull()) {
|
|
const int clearButtonState = KIconLoader::DefaultState;
|
|
if (layoutDirection() == Qt::LeftToRight) {
|
|
d->clearButton->setPixmap(SmallIcon("edit-clear-locationbar-rtl", 0, clearButtonState));
|
|
} else {
|
|
d->clearButton->setPixmap(SmallIcon("edit-clear-locationbar-ltr", 0, clearButtonState));
|
|
}
|
|
}
|
|
|
|
// trigger animation
|
|
if (d->wideEnoughForClear && text.length() > 0) {
|
|
d->clearButton->animateVisible(true);
|
|
} else {
|
|
d->clearButton->animateVisible(false);
|
|
}
|
|
}
|
|
|
|
// Determine geometry of clear button. Called initially, and on resizeEvent.
|
|
void KLineEdit::updateClearButton()
|
|
{
|
|
if (!d->clearButton) {
|
|
return;
|
|
}
|
|
if (isReadOnly()) {
|
|
d->adjustForReadOnly();
|
|
return;
|
|
}
|
|
|
|
const QSize geom = size();
|
|
const int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth,0,this);
|
|
const int buttonWidth = d->clearButton->sizeHint().width();
|
|
const QSize newButtonSize(buttonWidth, geom.height());
|
|
const QFontMetrics fm(font());
|
|
const int em = fm.width("m");
|
|
|
|
// make sure we have enough room for the clear button
|
|
// no point in showing it if we can't also see a few characters as well
|
|
const bool wideEnough = geom.width() > 4 * em + buttonWidth + frameWidth;
|
|
|
|
if (newButtonSize != d->clearButton->size()) {
|
|
d->clearButton->resize(newButtonSize);
|
|
}
|
|
|
|
if (d->style) {
|
|
d->style.data()->m_overlap = wideEnough ? buttonWidth + frameWidth : 0;
|
|
}
|
|
|
|
if (layoutDirection() == Qt::LeftToRight ) {
|
|
d->clearButton->move(geom.width() - frameWidth - buttonWidth - 1, 0);
|
|
} else {
|
|
d->clearButton->move(frameWidth + 1, 0);
|
|
}
|
|
|
|
if (wideEnough != d->wideEnoughForClear) {
|
|
// we may (or may not) have been showing the button, but now our
|
|
// positiong on that matter has shifted, so let's ensure that it
|
|
// is properly visible (or not)
|
|
d->wideEnoughForClear = wideEnough;
|
|
updateClearButtonIcon(text());
|
|
}
|
|
}
|
|
|
|
void KLineEdit::setCompletionMode( KGlobalSettings::Completion mode )
|
|
{
|
|
KGlobalSettings::Completion oldMode = completionMode();
|
|
|
|
if ( oldMode != mode && (oldMode == KGlobalSettings::CompletionPopup ||
|
|
oldMode == KGlobalSettings::CompletionPopupAuto ) &&
|
|
d->completionBox && d->completionBox->isVisible() )
|
|
d->completionBox->hide();
|
|
|
|
// If the widgets echo mode is not Normal, no completion
|
|
// feature will be enabled even if one is requested.
|
|
if ( echoMode() != QLineEdit::Normal )
|
|
mode = KGlobalSettings::CompletionNone; // Override the request.
|
|
|
|
if ( mode == KGlobalSettings::CompletionPopupAuto ||
|
|
mode == KGlobalSettings::CompletionAuto ||
|
|
mode == KGlobalSettings::CompletionMan )
|
|
d->autoSuggest = true;
|
|
else
|
|
d->autoSuggest = false;
|
|
|
|
KCompletionBase::setCompletionMode( mode );
|
|
}
|
|
|
|
void KLineEdit::setCompletionModeDisabled( KGlobalSettings::Completion mode, bool disable )
|
|
{
|
|
d->disableCompletionMap[ mode ] = disable;
|
|
}
|
|
|
|
void KLineEdit::setCompletedText( const QString& t, bool marked )
|
|
{
|
|
if ( !d->autoSuggest )
|
|
return;
|
|
|
|
const QString txt = text();
|
|
|
|
if ( t != txt )
|
|
{
|
|
setText(t);
|
|
if ( marked )
|
|
setSelection(t.length(), txt.length()-t.length());
|
|
setUserSelection(false);
|
|
}
|
|
else
|
|
setUserSelection(true);
|
|
|
|
}
|
|
|
|
void KLineEdit::setCompletedText( const QString& text )
|
|
{
|
|
KGlobalSettings::Completion mode = completionMode();
|
|
const bool marked = ( mode == KGlobalSettings::CompletionAuto ||
|
|
mode == KGlobalSettings::CompletionMan ||
|
|
mode == KGlobalSettings::CompletionPopup ||
|
|
mode == KGlobalSettings::CompletionPopupAuto );
|
|
setCompletedText( text, marked );
|
|
}
|
|
|
|
void KLineEdit::rotateText( KCompletionBase::KeyBindingType type )
|
|
{
|
|
KCompletion* comp = compObj();
|
|
if ( comp &&
|
|
(type == KCompletionBase::PrevCompletionMatch ||
|
|
type == KCompletionBase::NextCompletionMatch ) )
|
|
{
|
|
QString input;
|
|
|
|
if (type == KCompletionBase::PrevCompletionMatch)
|
|
input = comp->previousMatch();
|
|
else
|
|
input = comp->nextMatch();
|
|
|
|
// Skip rotation if previous/next match is null or the same text
|
|
if ( input.isEmpty() || input == displayText() )
|
|
return;
|
|
setCompletedText( input, hasSelectedText() );
|
|
}
|
|
}
|
|
|
|
void KLineEdit::makeCompletion( const QString& text )
|
|
{
|
|
KCompletion *comp = compObj();
|
|
KGlobalSettings::Completion mode = completionMode();
|
|
|
|
if ( !comp || mode == KGlobalSettings::CompletionNone )
|
|
return; // No completion object...
|
|
|
|
const QString match = comp->makeCompletion( text );
|
|
|
|
if ( mode == KGlobalSettings::CompletionPopup ||
|
|
mode == KGlobalSettings::CompletionPopupAuto )
|
|
{
|
|
if ( match.isEmpty() )
|
|
{
|
|
if ( d->completionBox )
|
|
{
|
|
d->completionBox->hide();
|
|
d->completionBox->clear();
|
|
}
|
|
}
|
|
else
|
|
setCompletedItems( comp->allMatches() );
|
|
}
|
|
else // Auto, ShortAuto (Man) and Shell
|
|
{
|
|
// all other completion modes
|
|
// If no match or the same match, simply return without completing.
|
|
if ( match.isEmpty() || match == text )
|
|
return;
|
|
|
|
if ( mode != KGlobalSettings::CompletionShell )
|
|
setUserSelection(false);
|
|
|
|
if ( d->autoSuggest )
|
|
setCompletedText( match );
|
|
}
|
|
}
|
|
|
|
void KLineEdit::setReadOnly(bool readOnly)
|
|
{
|
|
// Do not do anything if nothing changed...
|
|
if (readOnly == isReadOnly ()) {
|
|
return;
|
|
}
|
|
|
|
QLineEdit::setReadOnly(readOnly);
|
|
|
|
if (readOnly) {
|
|
d->bgRole = backgroundRole();
|
|
setBackgroundRole(QPalette::Window);
|
|
if (d->enableSqueezedText && d->squeezedText.isEmpty()) {
|
|
d->squeezedText = text();
|
|
setSqueezedText();
|
|
}
|
|
|
|
if (d->clearButton) {
|
|
d->clearButton->animateVisible(false);
|
|
d->adjustForReadOnly();
|
|
}
|
|
} else {
|
|
if (!d->squeezedText.isEmpty()) {
|
|
setText(d->squeezedText);
|
|
d->squeezedText.clear();
|
|
}
|
|
|
|
setBackgroundRole(d->bgRole);
|
|
updateClearButton();
|
|
}
|
|
}
|
|
|
|
void KLineEdit::setSqueezedText( const QString &text)
|
|
{
|
|
setSqueezedTextEnabled(true);
|
|
setText(text);
|
|
}
|
|
|
|
void KLineEdit::setSqueezedTextEnabled( bool enable )
|
|
{
|
|
d->enableSqueezedText = enable;
|
|
}
|
|
|
|
bool KLineEdit::isSqueezedTextEnabled() const
|
|
{
|
|
return d->enableSqueezedText;
|
|
}
|
|
|
|
void KLineEdit::setText( const QString& text )
|
|
{
|
|
if( d->enableClickMsg )
|
|
{
|
|
update();
|
|
}
|
|
if( d->enableSqueezedText && isReadOnly() )
|
|
{
|
|
d->squeezedText = text;
|
|
setSqueezedText();
|
|
return;
|
|
}
|
|
|
|
QLineEdit::setText( text );
|
|
}
|
|
|
|
void KLineEdit::setSqueezedText()
|
|
{
|
|
d->squeezedStart = 0;
|
|
d->squeezedEnd = 0;
|
|
const QString fullText = d->squeezedText;
|
|
const int fullLength = fullText.length();
|
|
const QFontMetrics fm(fontMetrics());
|
|
const int labelWidth = size().width() - 2*style()->pixelMetric(QStyle::PM_DefaultFrameWidth) - 2;
|
|
const int textWidth = fm.width(fullText);
|
|
|
|
if (textWidth > labelWidth)
|
|
{
|
|
// start with the dots only
|
|
QString squeezedText = "...";
|
|
int squeezedWidth = fm.width(squeezedText);
|
|
|
|
// estimate how many letters we can add to the dots on both sides
|
|
int letters = fullText.length() * (labelWidth - squeezedWidth) / textWidth / 2;
|
|
squeezedText = fullText.left(letters) + "..." + fullText.right(letters);
|
|
squeezedWidth = fm.width(squeezedText);
|
|
|
|
if (squeezedWidth < labelWidth)
|
|
{
|
|
// we estimated too short
|
|
// add letters while text < label
|
|
do
|
|
{
|
|
letters++;
|
|
squeezedText = fullText.left(letters) + "..." + fullText.right(letters);
|
|
squeezedWidth = fm.width(squeezedText);
|
|
} while (squeezedWidth < labelWidth && letters <= fullLength / 2);
|
|
letters--;
|
|
squeezedText = fullText.left(letters) + "..." + fullText.right(letters);
|
|
}
|
|
else if (squeezedWidth > labelWidth)
|
|
{
|
|
// we estimated too long
|
|
// remove letters while text > label
|
|
do
|
|
{
|
|
letters--;
|
|
squeezedText = fullText.left(letters) + "..." + fullText.right(letters);
|
|
squeezedWidth = fm.width(squeezedText);
|
|
} while (squeezedWidth > labelWidth && letters >= 5);
|
|
}
|
|
|
|
if (letters < 5)
|
|
{
|
|
// too few letters added -> we give up squeezing
|
|
QLineEdit::setText(fullText);
|
|
}
|
|
else
|
|
{
|
|
QLineEdit::setText(squeezedText);
|
|
d->squeezedStart = letters;
|
|
d->squeezedEnd = fullText.length() - letters;
|
|
}
|
|
|
|
setToolTip( fullText );
|
|
|
|
}
|
|
else
|
|
{
|
|
QLineEdit::setText(fullText);
|
|
|
|
this->setToolTip( "" );
|
|
QToolTip::showText(pos(), QString()); // hide
|
|
}
|
|
|
|
setCursorPosition(0);
|
|
}
|
|
|
|
void KLineEdit::copy() const
|
|
{
|
|
if( !copySqueezedText(true))
|
|
QLineEdit::copy();
|
|
}
|
|
|
|
bool KLineEdit::copySqueezedText(bool clipboard) const
|
|
{
|
|
if (!d->squeezedText.isEmpty() && d->squeezedStart)
|
|
{
|
|
KLineEdit *that = const_cast<KLineEdit *>(this);
|
|
if (!that->hasSelectedText())
|
|
return false;
|
|
int start = selectionStart(), end = start + selectedText().length();
|
|
if (start >= d->squeezedStart+3)
|
|
start = start - 3 - d->squeezedStart + d->squeezedEnd;
|
|
else if (start > d->squeezedStart)
|
|
start = d->squeezedStart;
|
|
if (end >= d->squeezedStart+3)
|
|
end = end - 3 - d->squeezedStart + d->squeezedEnd;
|
|
else if (end > d->squeezedStart)
|
|
end = d->squeezedEnd;
|
|
if (start == end)
|
|
return false;
|
|
QString t = d->squeezedText;
|
|
t = t.mid(start, end - start);
|
|
disconnect( QApplication::clipboard(), SIGNAL(selectionChanged()), this, 0);
|
|
QApplication::clipboard()->setText( t, clipboard ? QClipboard::Clipboard : QClipboard::Selection );
|
|
connect( QApplication::clipboard(), SIGNAL(selectionChanged()), this,
|
|
SLOT(_q_clipboardChanged()) );
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void KLineEdit::resizeEvent( QResizeEvent * ev )
|
|
{
|
|
if (!d->squeezedText.isEmpty())
|
|
setSqueezedText();
|
|
|
|
updateClearButton();
|
|
QLineEdit::resizeEvent(ev);
|
|
}
|
|
|
|
|
|
void KLineEdit::keyPressEvent( QKeyEvent *e )
|
|
{
|
|
const int key = e->key() | e->modifiers();
|
|
|
|
if ( KStandardShortcut::copy().matches( key ) != QKeySequence::NoMatch )
|
|
{
|
|
copy();
|
|
return;
|
|
}
|
|
else if ( KStandardShortcut::paste().matches( key ) != QKeySequence::NoMatch )
|
|
{
|
|
// TODO:
|
|
// we should restore the original text (not autocompleted), otherwise the paste
|
|
// will get into troubles Bug: 134691
|
|
if( !isReadOnly() )
|
|
paste();
|
|
return;
|
|
}
|
|
else if ( KStandardShortcut::pasteSelection().matches( key ) != QKeySequence::NoMatch )
|
|
{
|
|
QString text = QApplication::clipboard()->text( QClipboard::Selection);
|
|
insert( text );
|
|
deselect();
|
|
return;
|
|
}
|
|
|
|
else if ( KStandardShortcut::cut().matches( key ) != QKeySequence::NoMatch )
|
|
{
|
|
if( !isReadOnly() )
|
|
cut();
|
|
return;
|
|
}
|
|
else if ( KStandardShortcut::undo().matches( key ) != QKeySequence::NoMatch )
|
|
{
|
|
if( !isReadOnly() )
|
|
undo();
|
|
return;
|
|
}
|
|
else if ( KStandardShortcut::redo().matches( key ) != QKeySequence::NoMatch )
|
|
{
|
|
if( !isReadOnly() )
|
|
redo();
|
|
return;
|
|
}
|
|
else if ( KStandardShortcut::deleteWordBack().matches( key ) != QKeySequence::NoMatch )
|
|
{
|
|
cursorWordBackward(true);
|
|
if ( hasSelectedText() )
|
|
del();
|
|
|
|
e->accept();
|
|
return;
|
|
}
|
|
else if ( KStandardShortcut::deleteWordForward().matches( key ) != QKeySequence::NoMatch )
|
|
{
|
|
// Workaround for QT bug where
|
|
cursorWordForward(true);
|
|
if ( hasSelectedText() )
|
|
del();
|
|
|
|
e->accept();
|
|
return;
|
|
}
|
|
else if ( KStandardShortcut::backwardWord().matches( key ) != QKeySequence::NoMatch )
|
|
{
|
|
cursorWordBackward(false);
|
|
e->accept();
|
|
return;
|
|
}
|
|
else if ( KStandardShortcut::forwardWord().matches( key ) != QKeySequence::NoMatch )
|
|
{
|
|
cursorWordForward(false);
|
|
e->accept();
|
|
return;
|
|
}
|
|
else if ( KStandardShortcut::beginningOfLine().matches( key ) != QKeySequence::NoMatch )
|
|
{
|
|
home(false);
|
|
e->accept();
|
|
return;
|
|
}
|
|
else if ( KStandardShortcut::endOfLine().matches( key ) != QKeySequence::NoMatch )
|
|
{
|
|
end(false);
|
|
e->accept();
|
|
return;
|
|
}
|
|
|
|
|
|
// Filter key-events if EchoMode is normal and
|
|
// completion mode is not set to CompletionNone
|
|
if ( echoMode() == QLineEdit::Normal &&
|
|
completionMode() != KGlobalSettings::CompletionNone )
|
|
{
|
|
if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) {
|
|
const bool trap = (d->completionBox && d->completionBox->isVisible());
|
|
const bool stopEvent = (trap || (d->grabReturnKeyEvents &&
|
|
(e->modifiers() == Qt::NoButton ||
|
|
e->modifiers() == Qt::KeypadModifier)));
|
|
|
|
if (stopEvent) {
|
|
emit QLineEdit::returnPressed();
|
|
e->accept();
|
|
}
|
|
|
|
emit returnPressed( displayText() );
|
|
|
|
if (trap) {
|
|
d->completionBox->hide();
|
|
deselect();
|
|
setCursorPosition(text().length());
|
|
}
|
|
|
|
// Eat the event if the user asked for it, or if a completionbox was visible
|
|
if (stopEvent) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
const KeyBindingMap keys = getKeyBindings();
|
|
const KGlobalSettings::Completion mode = completionMode();
|
|
const bool noModifier = (e->modifiers() == Qt::NoButton ||
|
|
e->modifiers() == Qt::ShiftModifier ||
|
|
e->modifiers() == Qt::KeypadModifier);
|
|
|
|
if ( (mode == KGlobalSettings::CompletionAuto ||
|
|
mode == KGlobalSettings::CompletionPopupAuto ||
|
|
mode == KGlobalSettings::CompletionMan) && noModifier )
|
|
{
|
|
if ( !d->userSelection && hasSelectedText() &&
|
|
( e->key() == Qt::Key_Right || e->key() == Qt::Key_Left ) &&
|
|
e->modifiers()==Qt::NoButton )
|
|
{
|
|
const QString old_txt = text();
|
|
d->disableRestoreSelection = true;
|
|
const int start = selectionStart();
|
|
|
|
deselect();
|
|
QLineEdit::keyPressEvent ( e );
|
|
const int cPosition=cursorPosition();
|
|
setText(old_txt);
|
|
|
|
// keep cursor at cPosition
|
|
setSelection(old_txt.length(), cPosition - old_txt.length());
|
|
if (e->key() == Qt::Key_Right && cPosition > start )
|
|
{
|
|
//the user explicitly accepted the autocompletion
|
|
d->_k_updateUserText(text());
|
|
}
|
|
|
|
d->disableRestoreSelection = false;
|
|
return;
|
|
}
|
|
|
|
if ( e->key() == Qt::Key_Escape )
|
|
{
|
|
if (hasSelectedText() && !d->userSelection )
|
|
{
|
|
del();
|
|
setUserSelection(true);
|
|
}
|
|
|
|
// Don't swallow the Escape press event for the case
|
|
// of dialogs, which have Escape associated to Cancel
|
|
e->ignore();
|
|
return;
|
|
}
|
|
|
|
}
|
|
|
|
if ( (mode == KGlobalSettings::CompletionAuto ||
|
|
mode == KGlobalSettings::CompletionMan) && noModifier )
|
|
{
|
|
const QString keycode = e->text();
|
|
if ( !keycode.isEmpty() && (keycode.unicode()->isPrint() ||
|
|
e->key() == Qt::Key_Backspace || e->key() == Qt::Key_Delete ) )
|
|
{
|
|
const bool hasUserSelection=d->userSelection;
|
|
const bool hadSelection=hasSelectedText();
|
|
|
|
bool cursorNotAtEnd=false;
|
|
|
|
const int start = selectionStart();
|
|
const int cPos = cursorPosition();
|
|
|
|
// When moving the cursor, we want to keep the autocompletion as an
|
|
// autocompletion, so we want to process events at the cursor position
|
|
// as if there was no selection. After processing the key event, we
|
|
// can set the new autocompletion again.
|
|
if ( hadSelection && !hasUserSelection && start>cPos )
|
|
{
|
|
del();
|
|
setCursorPosition(cPos);
|
|
cursorNotAtEnd=true;
|
|
}
|
|
|
|
d->disableRestoreSelection = true;
|
|
QLineEdit::keyPressEvent ( e );
|
|
d->disableRestoreSelection = false;
|
|
|
|
QString txt = text();
|
|
int len = txt.length();
|
|
if ( !hasSelectedText() && len /*&& cursorPosition() == len */)
|
|
{
|
|
if ( e->key() == Qt::Key_Backspace )
|
|
{
|
|
if ( hadSelection && !hasUserSelection && !cursorNotAtEnd )
|
|
{
|
|
backspace();
|
|
txt = text();
|
|
len = txt.length();
|
|
}
|
|
|
|
if (!d->s_backspacePerformsCompletion || !len) {
|
|
d->autoSuggest = false;
|
|
}
|
|
}
|
|
|
|
if (e->key() == Qt::Key_Delete )
|
|
d->autoSuggest=false;
|
|
|
|
doCompletion(txt);
|
|
|
|
if( (e->key() == Qt::Key_Backspace || e->key() == Qt::Key_Delete) )
|
|
d->autoSuggest=true;
|
|
|
|
e->accept();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
}
|
|
|
|
else if (( mode == KGlobalSettings::CompletionPopup ||
|
|
mode == KGlobalSettings::CompletionPopupAuto ) &&
|
|
noModifier && !e->text().isEmpty() )
|
|
{
|
|
const QString old_txt = text();
|
|
const bool hasUserSelection=d->userSelection;
|
|
const bool hadSelection=hasSelectedText();
|
|
bool cursorNotAtEnd=false;
|
|
|
|
const int start = selectionStart();
|
|
const int cPos = cursorPosition();
|
|
const QString keycode = e->text();
|
|
|
|
// When moving the cursor, we want to keep the autocompletion as an
|
|
// autocompletion, so we want to process events at the cursor position
|
|
// as if there was no selection. After processing the key event, we
|
|
// can set the new autocompletion again.
|
|
if (hadSelection && !hasUserSelection && start>cPos &&
|
|
( (!keycode.isEmpty() && keycode.unicode()->isPrint()) ||
|
|
e->key() == Qt::Key_Backspace || e->key() == Qt::Key_Delete ) )
|
|
{
|
|
del();
|
|
setCursorPosition(cPos);
|
|
cursorNotAtEnd=true;
|
|
}
|
|
|
|
const int selectedLength=selectedText().length();
|
|
|
|
d->disableRestoreSelection = true;
|
|
QLineEdit::keyPressEvent ( e );
|
|
d->disableRestoreSelection = false;
|
|
|
|
if (( selectedLength != selectedText().length() ) && !hasUserSelection )
|
|
slotRestoreSelectionColors(); // and set userSelection to true
|
|
|
|
QString txt = text();
|
|
int len = txt.length();
|
|
if ( ( txt != old_txt || txt != e->text() ) && len/* && ( cursorPosition() == len || force )*/ &&
|
|
( (!keycode.isEmpty() && keycode.unicode()->isPrint()) ||
|
|
e->key() == Qt::Key_Backspace || e->key() == Qt::Key_Delete) )
|
|
{
|
|
if ( e->key() == Qt::Key_Backspace )
|
|
{
|
|
if ( hadSelection && !hasUserSelection && !cursorNotAtEnd )
|
|
{
|
|
backspace();
|
|
txt = text();
|
|
len = txt.length();
|
|
}
|
|
|
|
if (!d->s_backspacePerformsCompletion) {
|
|
d->autoSuggest = false;
|
|
}
|
|
}
|
|
|
|
if (e->key() == Qt::Key_Delete )
|
|
d->autoSuggest=false;
|
|
|
|
if ( d->completionBox )
|
|
d->completionBox->setCancelledText( txt );
|
|
|
|
doCompletion(txt);
|
|
|
|
if ( (e->key() == Qt::Key_Backspace || e->key() == Qt::Key_Delete ) &&
|
|
mode == KGlobalSettings::CompletionPopupAuto )
|
|
d->autoSuggest=true;
|
|
|
|
e->accept();
|
|
}
|
|
else if (!len && d->completionBox && d->completionBox->isVisible())
|
|
d->completionBox->hide();
|
|
|
|
return;
|
|
}
|
|
|
|
else if ( mode == KGlobalSettings::CompletionShell )
|
|
{
|
|
// Handles completion.
|
|
QKeySequence cut;
|
|
if ( keys[TextCompletion].isEmpty() )
|
|
cut = KStandardShortcut::shortcut(KStandardShortcut::TextCompletion);
|
|
else
|
|
cut = keys[TextCompletion];
|
|
|
|
if ( cut.matches( key ) != QKeySequence::NoMatch )
|
|
{
|
|
// Emit completion if the completion mode is CompletionShell
|
|
// and the cursor is at the end of the string.
|
|
const QString txt = text();
|
|
const int len = txt.length();
|
|
if ( cursorPosition() == len && len != 0 )
|
|
{
|
|
doCompletion(txt);
|
|
return;
|
|
}
|
|
}
|
|
else if ( d->completionBox )
|
|
d->completionBox->hide();
|
|
}
|
|
|
|
// handle rotation
|
|
if ( mode != KGlobalSettings::CompletionNone )
|
|
{
|
|
// Handles previous match
|
|
QKeySequence cut;
|
|
if ( keys[PrevCompletionMatch].isEmpty() )
|
|
cut = KStandardShortcut::shortcut(KStandardShortcut::PrevCompletion);
|
|
else
|
|
cut = keys[PrevCompletionMatch];
|
|
|
|
if ( cut.matches( key ) != QKeySequence::NoMatch )
|
|
{
|
|
if ( emitSignals() )
|
|
emit textRotation( KCompletionBase::PrevCompletionMatch );
|
|
if ( handleSignals() )
|
|
rotateText( KCompletionBase::PrevCompletionMatch );
|
|
return;
|
|
}
|
|
|
|
// Handles next match
|
|
if ( keys[NextCompletionMatch].isEmpty() )
|
|
cut = KStandardShortcut::shortcut(KStandardShortcut::NextCompletion);
|
|
else
|
|
cut = keys[NextCompletionMatch];
|
|
|
|
if ( cut.matches( key ) != QKeySequence::NoMatch )
|
|
{
|
|
if ( emitSignals() )
|
|
emit textRotation( KCompletionBase::NextCompletionMatch );
|
|
if ( handleSignals() )
|
|
rotateText( KCompletionBase::NextCompletionMatch );
|
|
return;
|
|
}
|
|
}
|
|
|
|
// substring completion
|
|
if ( compObj() )
|
|
{
|
|
QKeySequence cut;
|
|
if ( keys[SubstringCompletion].isEmpty() )
|
|
cut = KStandardShortcut::shortcut(KStandardShortcut::SubstringCompletion);
|
|
else
|
|
cut = keys[SubstringCompletion];
|
|
|
|
if ( cut.matches( key ) != QKeySequence::NoMatch )
|
|
{
|
|
if ( emitSignals() )
|
|
emit substringCompletion( text() );
|
|
if ( handleSignals() )
|
|
{
|
|
setCompletedItems( compObj()->substringCompletion(text()));
|
|
e->accept();
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
const int selectedLength = selectedText().length();
|
|
|
|
// Let QLineEdit handle any other keys events.
|
|
QLineEdit::keyPressEvent ( e );
|
|
|
|
if ( selectedLength != selectedText().length() )
|
|
slotRestoreSelectionColors(); // and set userSelection to true
|
|
}
|
|
|
|
void KLineEdit::mouseDoubleClickEvent( QMouseEvent* e )
|
|
{
|
|
if ( e->button() == Qt::LeftButton )
|
|
{
|
|
d->possibleTripleClick=true;
|
|
QTimer::singleShot( QApplication::doubleClickInterval(),this,
|
|
SLOT(tripleClickTimeout()) );
|
|
}
|
|
QLineEdit::mouseDoubleClickEvent( e );
|
|
}
|
|
|
|
void KLineEdit::mousePressEvent( QMouseEvent* e )
|
|
{
|
|
if ( (e->button() == Qt::LeftButton ||
|
|
e->button() == Qt::MiddleButton ) &&
|
|
d->clearButton ) {
|
|
d->clickInClear = ( d->clearButton == childAt(e->pos()) || d->clearButton->underMouse() );
|
|
|
|
if ( d->clickInClear ) {
|
|
d->possibleTripleClick = false;
|
|
}
|
|
}
|
|
|
|
if ( e->button() == Qt::LeftButton && d->possibleTripleClick ) {
|
|
selectAll();
|
|
e->accept();
|
|
return;
|
|
}
|
|
|
|
// if middle clicking and if text is present in the clipboard then clear the selection
|
|
// to prepare paste operation
|
|
if ( e->button() == Qt::MiddleButton ) {
|
|
if ( hasSelectedText() && !isReadOnly() ) {
|
|
if ( QApplication::clipboard()->text( QClipboard::Selection ).length() >0 ) {
|
|
backspace();
|
|
}
|
|
}
|
|
}
|
|
|
|
QLineEdit::mousePressEvent( e );
|
|
}
|
|
|
|
void KLineEdit::mouseReleaseEvent( QMouseEvent* e )
|
|
{
|
|
if ( d->clickInClear ) {
|
|
if ( d->clearButton == childAt(e->pos()) || d->clearButton->underMouse() ) {
|
|
QString newText;
|
|
if ( e->button() == Qt::MiddleButton ) {
|
|
newText = QApplication::clipboard()->text( QClipboard::Selection );
|
|
setText( newText );
|
|
} else {
|
|
setSelection(0, text().size());
|
|
del();
|
|
emit clearButtonClicked();
|
|
}
|
|
emit textChanged( newText );
|
|
}
|
|
|
|
d->clickInClear = false;
|
|
e->accept();
|
|
return;
|
|
}
|
|
|
|
QLineEdit::mouseReleaseEvent( e );
|
|
|
|
if (QApplication::clipboard()->supportsSelection() ) {
|
|
if ( e->button() == Qt::LeftButton ) {
|
|
// Fix copying of squeezed text if needed
|
|
copySqueezedText( false );
|
|
}
|
|
}
|
|
}
|
|
|
|
void KLineEdit::tripleClickTimeout()
|
|
{
|
|
d->possibleTripleClick=false;
|
|
}
|
|
|
|
QMenu* KLineEdit::createStandardContextMenu()
|
|
{
|
|
QMenu *popup = QLineEdit::createStandardContextMenu();
|
|
|
|
// If a completion object is present and the input
|
|
// widget is not read-only, show the Text Completion
|
|
// menu item.
|
|
if ( compObj() && !isReadOnly() )
|
|
{
|
|
QMenu *subMenu = popup->addMenu( KIcon("text-completion"), i18nc("@title:menu", "Text Completion") );
|
|
connect( subMenu, SIGNAL(triggered(QAction*)),
|
|
this, SLOT(completionMenuActivated(QAction*)) );
|
|
|
|
popup->addSeparator();
|
|
|
|
QActionGroup* ag = new QActionGroup( this );
|
|
d->noCompletionAction = ag->addAction( i18nc("@item:inmenu Text Completion", "None"));
|
|
d->shellCompletionAction = ag->addAction( i18nc("@item:inmenu Text Completion", "Manual") );
|
|
d->autoCompletionAction = ag->addAction( i18nc("@item:inmenu Text Completion", "Automatic") );
|
|
d->popupCompletionAction = ag->addAction( i18nc("@item:inmenu Text Completion", "Dropdown List") );
|
|
d->shortAutoCompletionAction = ag->addAction( i18nc("@item:inmenu Text Completion", "Short Automatic") );
|
|
d->popupAutoCompletionAction = ag->addAction( i18nc("@item:inmenu Text Completion", "Dropdown List && Automatic"));
|
|
subMenu->addActions( ag->actions() );
|
|
|
|
//subMenu->setAccel( KStandardShortcut::completion(), ShellCompletion );
|
|
|
|
d->shellCompletionAction->setCheckable( true );
|
|
d->noCompletionAction->setCheckable( true );
|
|
d->popupCompletionAction->setCheckable( true );
|
|
d->autoCompletionAction->setCheckable( true );
|
|
d->shortAutoCompletionAction->setCheckable( true );
|
|
d->popupAutoCompletionAction->setCheckable( true );
|
|
|
|
d->shellCompletionAction->setEnabled( !d->disableCompletionMap[ KGlobalSettings::CompletionShell ] );
|
|
d->noCompletionAction->setEnabled( !d->disableCompletionMap[ KGlobalSettings::CompletionNone ] );
|
|
d->popupCompletionAction->setEnabled( !d->disableCompletionMap[ KGlobalSettings::CompletionPopup ] );
|
|
d->autoCompletionAction->setEnabled( !d->disableCompletionMap[ KGlobalSettings::CompletionAuto ] );
|
|
d->shortAutoCompletionAction->setEnabled( !d->disableCompletionMap[ KGlobalSettings::CompletionMan ] );
|
|
d->popupAutoCompletionAction->setEnabled( !d->disableCompletionMap[ KGlobalSettings::CompletionPopupAuto ] );
|
|
|
|
const KGlobalSettings::Completion mode = completionMode();
|
|
d->noCompletionAction->setChecked( mode == KGlobalSettings::CompletionNone );
|
|
d->shellCompletionAction->setChecked( mode == KGlobalSettings::CompletionShell );
|
|
d->popupCompletionAction->setChecked( mode == KGlobalSettings::CompletionPopup );
|
|
d->autoCompletionAction->setChecked( mode == KGlobalSettings::CompletionAuto );
|
|
d->shortAutoCompletionAction->setChecked( mode == KGlobalSettings::CompletionMan );
|
|
d->popupAutoCompletionAction->setChecked( mode == KGlobalSettings::CompletionPopupAuto );
|
|
|
|
const KGlobalSettings::Completion defaultMode = KGlobalSettings::completionMode();
|
|
if ( mode != defaultMode && !d->disableCompletionMap[ defaultMode ] )
|
|
{
|
|
subMenu->addSeparator();
|
|
d->defaultAction = subMenu->addAction( i18nc("@item:inmenu Text Completion", "Default") );
|
|
}
|
|
}
|
|
|
|
return popup;
|
|
}
|
|
|
|
void KLineEdit::contextMenuEvent( QContextMenuEvent *e )
|
|
{
|
|
if ( QLineEdit::contextMenuPolicy() != Qt::DefaultContextMenu )
|
|
return;
|
|
QMenu *popup = createStandardContextMenu();
|
|
|
|
// ### do we really need this? Yes, Please do not remove! This
|
|
// allows applications to extend the popup menu without having to
|
|
// inherit from this class! (DA)
|
|
emit aboutToShowContextMenu( popup );
|
|
|
|
popup->exec(e->globalPos());
|
|
delete popup;
|
|
}
|
|
|
|
void KLineEdit::completionMenuActivated( QAction *act)
|
|
{
|
|
KGlobalSettings::Completion oldMode = completionMode();
|
|
|
|
if( act == d->noCompletionAction )
|
|
{
|
|
setCompletionMode( KGlobalSettings::CompletionNone );
|
|
}
|
|
else if( act == d->shellCompletionAction)
|
|
{
|
|
setCompletionMode( KGlobalSettings::CompletionShell );
|
|
}
|
|
else if( act == d->autoCompletionAction)
|
|
{
|
|
setCompletionMode( KGlobalSettings::CompletionAuto );
|
|
}
|
|
else if( act == d->popupCompletionAction)
|
|
{
|
|
setCompletionMode( KGlobalSettings::CompletionPopup );
|
|
}
|
|
else if( act == d->shortAutoCompletionAction)
|
|
{
|
|
setCompletionMode( KGlobalSettings::CompletionMan );
|
|
}
|
|
else if( act == d->popupAutoCompletionAction)
|
|
{
|
|
setCompletionMode( KGlobalSettings::CompletionPopupAuto );
|
|
}
|
|
else if( act == d->defaultAction )
|
|
{
|
|
setCompletionMode( KGlobalSettings::completionMode() );
|
|
}
|
|
else
|
|
return;
|
|
|
|
if ( oldMode != completionMode() )
|
|
{
|
|
if ( (oldMode == KGlobalSettings::CompletionPopup ||
|
|
oldMode == KGlobalSettings::CompletionPopupAuto ) &&
|
|
d->completionBox && d->completionBox->isVisible() )
|
|
d->completionBox->hide();
|
|
emit completionModeChanged( completionMode() );
|
|
}
|
|
}
|
|
|
|
void KLineEdit::dropEvent(QDropEvent *e)
|
|
{
|
|
if( d->handleURLDrops )
|
|
{
|
|
const KUrl::List urlList = KUrl::List::fromMimeData( e->mimeData() );
|
|
if ( !urlList.isEmpty() )
|
|
{
|
|
// Let's replace the current text with the dropped URL(s), rather than appending.
|
|
// Makes more sense in general (#188129), e.g. konq location bar and kurlrequester
|
|
// can only hold one url anyway. OK this code supports multiple urls being dropped,
|
|
// but that's not the common case [and it breaks if they contain spaces... this is why
|
|
// kfiledialog uses double quotes around filenames in multiple-selection mode]...
|
|
//
|
|
// Anyway, if some apps prefer "append" then we should have a
|
|
// setUrlDropsSupport( {NoUrlDrops, SingleUrlDrops, MultipleUrlDrops} )
|
|
// where Single replaces and Multiple appends.
|
|
QString dropText;
|
|
//QString dropText = text();
|
|
KUrl::List::ConstIterator it;
|
|
for( it = urlList.begin() ; it != urlList.end() ; ++it )
|
|
{
|
|
if(!dropText.isEmpty())
|
|
dropText+=' ';
|
|
|
|
dropText += (*it).prettyUrl();
|
|
}
|
|
|
|
setText(dropText);
|
|
setCursorPosition(dropText.length());
|
|
|
|
e->accept();
|
|
return;
|
|
}
|
|
}
|
|
QLineEdit::dropEvent(e);
|
|
}
|
|
|
|
bool KLineEdit::event( QEvent* ev )
|
|
{
|
|
KCursor::autoHideEventFilter( this, ev );
|
|
if ( ev->type() == QEvent::ShortcutOverride )
|
|
{
|
|
QKeyEvent *e = static_cast<QKeyEvent *>( ev );
|
|
if (d->overrideShortcut(e)) {
|
|
ev->accept();
|
|
}
|
|
} else if (ev->type() == QEvent::ApplicationPaletteChange
|
|
|| ev->type() == QEvent::PaletteChange) {
|
|
// Assume the widget uses the application's palette
|
|
QPalette p = QApplication::palette();
|
|
d->previousHighlightedTextColor=p.color(QPalette::Normal,QPalette::HighlightedText);
|
|
d->previousHighlightColor=p.color(QPalette::Normal,QPalette::Highlight);
|
|
setUserSelection(d->userSelection);
|
|
} else if (ev->type() == QEvent::StyleChange) {
|
|
// since we have our own style and it relies on this style to Get Things Right,
|
|
// if a style is set specifically on the widget (which would replace our own style!)
|
|
// hang on to this special style and re-instate our own style.
|
|
//FIXME: Qt currently has a grave bug where already deleted QStyleSheetStyle objects
|
|
// will get passed back in if we set a new style on it here. remove the qstrmcp test
|
|
// when this is fixed in Qt (or a better approach is found)
|
|
if (!qobject_cast<KLineEditStyle *>(style()) &&
|
|
qstrcmp(style()->metaObject()->className(), "QStyleSheetStyle") != 0 &&
|
|
QLatin1String(style()->metaObject()->className()) != d->lastStyleClass) {
|
|
KLineEditStyle *kleStyle = d->style.data();
|
|
if (!kleStyle) {
|
|
d->style = kleStyle = new KLineEditStyle(this);
|
|
}
|
|
|
|
kleStyle->m_subStyle = style();
|
|
// this guards against "wrap around" where another style, e.g. QStyleSheetStyle,
|
|
// is setting the style on QEvent::StyleChange
|
|
d->lastStyleClass = QLatin1String(style()->metaObject()->className());
|
|
setStyle(kleStyle);
|
|
d->lastStyleClass.clear();
|
|
}
|
|
} else if (ev->type() == QEvent::ApplicationLayoutDirectionChange
|
|
|| ev->type() == QEvent::LayoutDirectionChange) {
|
|
updateClearButtonIcon(text());
|
|
updateClearButton();
|
|
}
|
|
|
|
return QLineEdit::event( ev );
|
|
}
|
|
|
|
|
|
void KLineEdit::setUrlDropsEnabled(bool enable)
|
|
{
|
|
d->handleURLDrops=enable;
|
|
}
|
|
|
|
bool KLineEdit::urlDropsEnabled() const
|
|
{
|
|
return d->handleURLDrops;
|
|
}
|
|
|
|
void KLineEdit::setTrapReturnKey( bool grab )
|
|
{
|
|
d->grabReturnKeyEvents = grab;
|
|
}
|
|
|
|
bool KLineEdit::trapReturnKey() const
|
|
{
|
|
return d->grabReturnKeyEvents;
|
|
}
|
|
|
|
void KLineEdit::setUrl( const KUrl& url )
|
|
{
|
|
setText( url.prettyUrl() );
|
|
}
|
|
|
|
void KLineEdit::setCompletionBox( KCompletionBox *box )
|
|
{
|
|
if ( d->completionBox )
|
|
return;
|
|
|
|
d->completionBox = box;
|
|
if ( handleSignals() )
|
|
{
|
|
connect( d->completionBox, SIGNAL(currentTextChanged(QString)),
|
|
SLOT(_k_slotCompletionBoxTextChanged(QString)) );
|
|
connect( d->completionBox, SIGNAL(userCancelled(QString)),
|
|
SLOT(userCancelled(QString)) );
|
|
connect( d->completionBox, SIGNAL(activated(QString)),
|
|
SIGNAL(completionBoxActivated(QString)) );
|
|
connect( d->completionBox, SIGNAL(activated(QString)),
|
|
SIGNAL(textEdited(QString)) );
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Set the line edit text without changing the modified flag. By default
|
|
* calling setText resets the modified flag to false.
|
|
*/
|
|
static void setEditText(KLineEdit* edit, const QString& text)
|
|
{
|
|
if (!edit) {
|
|
return;
|
|
}
|
|
|
|
const bool wasModified = edit->isModified();
|
|
edit->setText(text);
|
|
edit->setModified(wasModified);
|
|
}
|
|
|
|
void KLineEdit::userCancelled(const QString & cancelText)
|
|
{
|
|
if ( completionMode() != KGlobalSettings::CompletionPopupAuto )
|
|
{
|
|
setEditText(this, cancelText);
|
|
}
|
|
else if (hasSelectedText() )
|
|
{
|
|
if (d->userSelection)
|
|
deselect();
|
|
else
|
|
{
|
|
d->autoSuggest=false;
|
|
const int start = selectionStart() ;
|
|
const QString s = text().remove(selectionStart(), selectedText().length());
|
|
setEditText(this, s);
|
|
setCursorPosition(start);
|
|
d->autoSuggest=true;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool KLineEditPrivate::overrideShortcut(const QKeyEvent* e)
|
|
{
|
|
QKeySequence scKey;
|
|
|
|
const int key = e->key() | e->modifiers();
|
|
const KLineEdit::KeyBindingMap keys = q->getKeyBindings();
|
|
|
|
if (keys[KLineEdit::TextCompletion].isEmpty())
|
|
scKey = KStandardShortcut::shortcut(KStandardShortcut::TextCompletion);
|
|
else
|
|
scKey = keys[KLineEdit::TextCompletion];
|
|
|
|
if (scKey.matches( key ) != QKeySequence::NoMatch)
|
|
return true;
|
|
|
|
if (keys[KLineEdit::NextCompletionMatch].isEmpty())
|
|
scKey = KStandardShortcut::shortcut(KStandardShortcut::NextCompletion);
|
|
else
|
|
scKey = keys[KLineEdit::NextCompletionMatch];
|
|
|
|
if (scKey.matches( key ) != QKeySequence::NoMatch)
|
|
return true;
|
|
|
|
if (keys[KLineEdit::PrevCompletionMatch].isEmpty())
|
|
scKey = KStandardShortcut::shortcut(KStandardShortcut::PrevCompletion);
|
|
else
|
|
scKey = keys[KLineEdit::PrevCompletionMatch];
|
|
|
|
if (scKey.matches( key ) != QKeySequence::NoMatch)
|
|
return true;
|
|
|
|
// Override all the text manupilation accelerators...
|
|
if (KStandardShortcut::copy().matches( key ) != QKeySequence::NoMatch)
|
|
return true;
|
|
else if (KStandardShortcut::paste().matches( key ) != QKeySequence::NoMatch)
|
|
return true;
|
|
else if (KStandardShortcut::cut().matches( key ) != QKeySequence::NoMatch)
|
|
return true;
|
|
else if (KStandardShortcut::undo().matches( key ) != QKeySequence::NoMatch)
|
|
return true;
|
|
else if (KStandardShortcut::redo().matches(key) != QKeySequence::NoMatch)
|
|
return true;
|
|
else if (KStandardShortcut::deleteWordBack().matches(key) != QKeySequence::NoMatch)
|
|
return true;
|
|
else if (KStandardShortcut::deleteWordForward().matches(key) != QKeySequence::NoMatch)
|
|
return true;
|
|
else if (KStandardShortcut::forwardWord().matches(key) != QKeySequence::NoMatch)
|
|
return true;
|
|
else if (KStandardShortcut::backwardWord().matches(key) != QKeySequence::NoMatch)
|
|
return true;
|
|
else if (KStandardShortcut::beginningOfLine().matches(key) != QKeySequence::NoMatch)
|
|
return true;
|
|
else if (KStandardShortcut::endOfLine().matches(key) != QKeySequence::NoMatch)
|
|
return true;
|
|
|
|
// Shortcut overrides for shortcuts that QLineEdit handles
|
|
// but doesn't dare force as "stronger than kaction shortcuts"...
|
|
else if (e->matches(QKeySequence::SelectAll)) {
|
|
return true;
|
|
}
|
|
#ifdef Q_WS_X11
|
|
else if (key == Qt::CTRL + Qt::Key_E || key == Qt::CTRL + Qt::Key_U)
|
|
return true;
|
|
#endif
|
|
|
|
if (completionBox && completionBox->isVisible ())
|
|
{
|
|
const int key = e->key();
|
|
const Qt::KeyboardModifiers modifiers = e->modifiers();
|
|
if ((key == Qt::Key_Backtab || key == Qt::Key_Tab) &&
|
|
(modifiers == Qt::NoModifier || (modifiers & Qt::ShiftModifier)))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
return false;
|
|
}
|
|
|
|
void KLineEdit::setCompletedItems( const QStringList& items, bool autoSuggest )
|
|
{
|
|
QString txt;
|
|
if ( d->completionBox && d->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()) )
|
|
{
|
|
// create completion box if non-existent
|
|
completionBox();
|
|
|
|
if ( d->completionBox->isVisible() )
|
|
{
|
|
QListWidgetItem* currentItem = d->completionBox->currentItem();
|
|
|
|
QString currentSelection;
|
|
if ( currentItem != 0 ) {
|
|
currentSelection = currentItem->text();
|
|
}
|
|
|
|
d->completionBox->setItems( items );
|
|
|
|
const QList<QListWidgetItem*> matchedItems = d->completionBox->findItems(currentSelection, Qt::MatchExactly);
|
|
QListWidgetItem* matchedItem = matchedItems.isEmpty() ? 0 : matchedItems.first();
|
|
|
|
if (matchedItem) {
|
|
const bool blocked = d->completionBox->blockSignals( true );
|
|
d->completionBox->setCurrentItem( matchedItem );
|
|
d->completionBox->blockSignals( blocked );
|
|
} else {
|
|
d->completionBox->setCurrentRow(-1);
|
|
}
|
|
}
|
|
else // completion box not visible yet -> show it
|
|
{
|
|
if ( !txt.isEmpty() )
|
|
d->completionBox->setCancelledText( txt );
|
|
d->completionBox->setItems( items );
|
|
d->completionBox->popup();
|
|
}
|
|
|
|
if ( d->autoSuggest && autoSuggest )
|
|
{
|
|
const int index = items.first().indexOf( txt );
|
|
const QString newText = items.first().mid( index );
|
|
setUserSelection(false); // can be removed? setCompletedText sets it anyway
|
|
setCompletedText(newText,true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( d->completionBox && d->completionBox->isVisible() )
|
|
d->completionBox->hide();
|
|
}
|
|
}
|
|
|
|
KCompletionBox * KLineEdit::completionBox( bool create )
|
|
{
|
|
if ( create && !d->completionBox ) {
|
|
setCompletionBox( new KCompletionBox( this ) );
|
|
d->completionBox->setObjectName("completion box");
|
|
d->completionBox->setFont(font());
|
|
}
|
|
|
|
return d->completionBox;
|
|
}
|
|
|
|
void KLineEdit::setCompletionObject( KCompletion* comp, bool hsig )
|
|
{
|
|
KCompletion *oldComp = compObj();
|
|
if ( oldComp && handleSignals() )
|
|
disconnect( oldComp, SIGNAL(matches(QStringList)),
|
|
this, SLOT(setCompletedItems(QStringList)));
|
|
|
|
if ( comp && hsig )
|
|
connect( comp, SIGNAL(matches(QStringList)),
|
|
this, SLOT(setCompletedItems(QStringList)));
|
|
|
|
KCompletionBase::setCompletionObject( comp, hsig );
|
|
}
|
|
|
|
// QWidget::create() turns off mouse-Tracking which would break auto-hiding
|
|
void KLineEdit::create( WId id, bool initializeWindow, bool destroyOldWindow )
|
|
{
|
|
QLineEdit::create( id, initializeWindow, destroyOldWindow );
|
|
KCursor::setAutoHideCursor( this, true, true );
|
|
}
|
|
|
|
void KLineEdit::setUserSelection(bool userSelection)
|
|
{
|
|
//if !d->userSelection && userSelection we are accepting a completion,
|
|
//so trigger an update
|
|
|
|
if (!d->userSelection && userSelection)
|
|
{
|
|
d->_k_updateUserText(text());
|
|
}
|
|
|
|
QPalette p = palette();
|
|
|
|
if (userSelection)
|
|
{
|
|
p.setColor(QPalette::Highlight, d->previousHighlightColor);
|
|
p.setColor(QPalette::HighlightedText, d->previousHighlightedTextColor);
|
|
}
|
|
else
|
|
{
|
|
QColor color=p.color(QPalette::Disabled, QPalette::Text);
|
|
p.setColor(QPalette::HighlightedText, color);
|
|
color=p.color(QPalette::Active, QPalette::Base);
|
|
p.setColor(QPalette::Highlight, color);
|
|
}
|
|
|
|
d->userSelection=userSelection;
|
|
setPalette(p);
|
|
}
|
|
|
|
void KLineEdit::slotRestoreSelectionColors()
|
|
{
|
|
if (d->disableRestoreSelection)
|
|
return;
|
|
|
|
setUserSelection(true);
|
|
}
|
|
|
|
void KLineEdit::clear()
|
|
{
|
|
setText( QString() );
|
|
}
|
|
|
|
void KLineEdit::_k_slotCompletionBoxTextChanged( const QString& text )
|
|
{
|
|
if (!text.isEmpty())
|
|
{
|
|
setText( text );
|
|
setModified(true);
|
|
end( false ); // force cursor at end
|
|
}
|
|
}
|
|
|
|
QString KLineEdit::originalText() const
|
|
{
|
|
if ( d->enableSqueezedText && isReadOnly() )
|
|
return d->squeezedText;
|
|
|
|
return text();
|
|
}
|
|
|
|
QString KLineEdit::userText() const
|
|
{
|
|
return d->userText;
|
|
}
|
|
|
|
bool KLineEdit::autoSuggest() const
|
|
{
|
|
return d->autoSuggest;
|
|
}
|
|
|
|
void KLineEdit::paintEvent( QPaintEvent *ev )
|
|
{
|
|
if (echoMode() == Password && d->threeStars) {
|
|
// ### hack alert!
|
|
// QLineEdit has currently no hooks to modify the displayed string.
|
|
// When we call setText(), an update() is triggered and we get
|
|
// into an infinite recursion.
|
|
// Qt offers the setUpdatesEnabled() method, but when we re-enable
|
|
// them, update() is triggered, and we get into the same recursion.
|
|
// To work around this problem, we set/clear the internal Qt flag which
|
|
// marks the updatesDisabled state manually.
|
|
setAttribute(Qt::WA_UpdatesDisabled, true);
|
|
blockSignals(true);
|
|
const QString oldText = text();
|
|
const bool isModifiedState = isModified(); // save modified state because setText resets it
|
|
setText(oldText + oldText + oldText);
|
|
QLineEdit::paintEvent(ev);
|
|
setText(oldText);
|
|
setModified(isModifiedState);
|
|
blockSignals(false);
|
|
setAttribute(Qt::WA_UpdatesDisabled, false);
|
|
} else {
|
|
QLineEdit::paintEvent( ev );
|
|
}
|
|
|
|
if (d->enableClickMsg && text().isEmpty()) {
|
|
QPainter p(this);
|
|
QFont f = font();
|
|
f.setItalic(d->italicizePlaceholder);
|
|
p.setFont(f);
|
|
|
|
QColor color(palette().color(foregroundRole()));
|
|
color.setAlphaF(0.5);
|
|
p.setPen(color);
|
|
|
|
QStyleOptionFrame opt;
|
|
initStyleOption(&opt);
|
|
QRect cr = style()->subElementRect(QStyle::SE_LineEditContents, &opt, this);
|
|
|
|
// this is copied/adapted from QLineEdit::paintEvent
|
|
const int verticalMargin(1);
|
|
const int horizontalMargin(2);
|
|
|
|
int left, top, right, bottom;
|
|
getTextMargins( &left, &top, &right, &bottom );
|
|
cr.adjust( left, top, -right, -bottom );
|
|
|
|
p.setClipRect(cr);
|
|
|
|
QFontMetrics fm = fontMetrics();
|
|
Qt::Alignment va = alignment() & Qt::AlignVertical_Mask;
|
|
int vscroll;
|
|
switch (va & Qt::AlignVertical_Mask)
|
|
{
|
|
case Qt::AlignBottom: {
|
|
vscroll = cr.y() + cr.height() - fm.height() - verticalMargin;
|
|
break;
|
|
}
|
|
case Qt::AlignTop: {
|
|
vscroll = cr.y() + verticalMargin;
|
|
break;
|
|
}
|
|
default: {
|
|
vscroll = cr.y() + (cr.height() - fm.height() + 1) / 2;
|
|
break;
|
|
}
|
|
}
|
|
|
|
QRect lineRect(cr.x() + horizontalMargin, vscroll, cr.width() - 2*horizontalMargin, fm.height());
|
|
p.drawText(lineRect, Qt::AlignLeft|Qt::AlignVCenter, d->clickMessage);
|
|
|
|
}
|
|
}
|
|
|
|
void KLineEdit::setClickMessage( const QString &msg )
|
|
{
|
|
d->enableClickMsg = !msg.isEmpty();
|
|
d->clickMessage = msg;
|
|
update();
|
|
}
|
|
|
|
void KLineEdit::setPasswordMode(bool b)
|
|
{
|
|
if(b)
|
|
{
|
|
KConfigGroup cg(KGlobal::config(), "Passwords");
|
|
const QString val = cg.readEntry("EchoMode", "OneStar");
|
|
if (val == "NoEcho")
|
|
setEchoMode(NoEcho);
|
|
else {
|
|
d->threeStars = (val == "ThreeStars");
|
|
setEchoMode(Password);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
setEchoMode( Normal );
|
|
}
|
|
}
|
|
|
|
bool KLineEdit::passwordMode() const
|
|
{
|
|
return echoMode() == NoEcho || echoMode() == Password;
|
|
}
|
|
|
|
void KLineEdit::doCompletion(const QString& txt)
|
|
{
|
|
if (emitSignals()) {
|
|
emit completion(txt); // emit when requested...
|
|
}
|
|
d->completionRunning = true;
|
|
if (handleSignals()) {
|
|
makeCompletion(txt); // handle when requested...
|
|
}
|
|
d->completionRunning = false;
|
|
}
|
|
|
|
#include "moc_klineedit.cpp"
|
|
#include "moc_klineedit_p.cpp"
|