Add side widgets to QLineEdit via QAction.

Add QLineEdit::addAction() overloads,
allowing for a variable number of icons or user-defined
widgets.

Change-Id: Id298f18c2f47cc998170357e65cc6098df851aab
Done-with: Kevin.Ottens@kdab.com
Reviewed-by: Thomas Zander <zander@kde.org>
Reviewed-by: Richard J. Moore <rich@kde.org>
Reviewed-by: Frederik Gladhorn <frederik.gladhorn@digia.com>
bb10
Friedemann Kleint 2013-06-13 16:23:44 +02:00 committed by The Qt Project
parent c207724c9b
commit 9ce12cc8de
5 changed files with 363 additions and 12 deletions

View File

@ -420,6 +420,60 @@ bool QLineEdit::hasFrame() const
return d->frame;
}
/*!
\enum QLineEdit::ActionPosition
This enum type describes how a line edit should display the action widgets to be
added.
\value LeadingPosition The widget is displayed to the left of the text
when using layout direction \c Qt::LeftToRight or to
the right when using \c Qt::RightToLeft, respectively.
\value TrailingPosition The widget is displayed to the right of the text
when using layout direction \c Qt::LeftToRight or to
the left when using \c Qt::RightToLeft, respectively.
\sa addAction(), removeAction(), QWidget::layoutDirection
\since 5.2
*/
/*!
\fn void QLineEdit::addAction(QAction *action)
\overload
\internal
*/
/*!
\overload
Adds the \a action to the list of actions at the \a position.
\since 5.2
*/
void QLineEdit::addAction(QAction *action, ActionPosition position)
{
Q_D(QLineEdit);
QWidget::addAction(action);
d->addAction(action, 0, position);
}
/*!
\overload
Creates a new action with the given \a icon at the \a position.
\since 5.2
*/
QAction *QLineEdit::addAction(const QIcon &icon, ActionPosition position)
{
QAction *result = new QAction(icon, QString(), this);
addAction(result, position);
return result;
}
void QLineEdit::setFrame(bool enable)
{
@ -606,7 +660,7 @@ QSize QLineEdit::sizeHint() const
+ d->topTextMargin + d->bottomTextMargin
+ d->topmargin + d->bottommargin;
int w = fm.width(QLatin1Char('x')) * 17 + 2*d->horizontalMargin
+ d->leftTextMargin + d->rightTextMargin
+ d->effectiveLeftTextMargin() + d->effectiveRightTextMargin()
+ d->leftmargin + d->rightmargin; // "some"
QStyleOptionFrameV2 opt;
initStyleOption(&opt);
@ -967,7 +1021,6 @@ void QLineEdit::setDragEnabled(bool b)
d->dragEnabled = b;
}
/*!
\property QLineEdit::cursorMoveStyle
\brief the movement style of cursor in this line edit
@ -1350,8 +1403,11 @@ bool QLineEdit::event(QEvent * e)
|| style()->styleHint(QStyle::SH_BlinkCursorWhenTextSelected, &opt, this))
d->setCursorVisible(true);
}
} else if (e->type() == QEvent::ActionRemoved) {
d->removeAction(static_cast<QActionEvent *>(e));
} else if (e->type() == QEvent::Resize) {
d->positionSideWidgets();
}
#ifdef QT_KEYPAD_NAVIGATION
if (QApplication::keypadNavigationEnabled()) {
if (e->type() == QEvent::EnterEditFocus) {
@ -1777,9 +1833,9 @@ void QLineEdit::paintEvent(QPaintEvent *)
initStyleOption(&panel);
style()->drawPrimitive(QStyle::PE_PanelLineEdit, &panel, &p, this);
r = style()->subElementRect(QStyle::SE_LineEditContents, &panel, this);
r.setX(r.x() + d->leftTextMargin);
r.setX(r.x() + d->effectiveLeftTextMargin());
r.setY(r.y() + d->topTextMargin);
r.setRight(r.right() - d->rightTextMargin);
r.setRight(r.right() - d->effectiveRightTextMargin());
r.setBottom(r.bottom() - d->bottomTextMargin);
p.setClipRect(r);
@ -2083,8 +2139,12 @@ void QLineEdit::changeEvent(QEvent *ev)
initStyleOption(&opt);
d->control->setPasswordCharacter(style()->styleHint(QStyle::SH_LineEdit_PasswordCharacter, &opt, this));
}
d->m_iconSize = QSize();
update();
break;
case QEvent::LayoutDirectionChange:
d->positionSideWidgets();
break;
default:
break;
}

View File

@ -59,12 +59,14 @@ class QCompleter;
class QStyleOptionFrame;
class QAbstractSpinBox;
class QDateTimeEdit;
class QIcon;
class QToolButton;
class Q_WIDGETS_EXPORT QLineEdit : public QWidget
{
Q_OBJECT
Q_ENUMS(EchoMode)
Q_ENUMS(ActionPosition EchoMode)
Q_PROPERTY(QString inputMask READ inputMask WRITE setInputMask)
Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged USER true)
Q_PROPERTY(int maxLength READ maxLength WRITE setMaxLength)
@ -83,8 +85,12 @@ class Q_WIDGETS_EXPORT QLineEdit : public QWidget
Q_PROPERTY(bool acceptableInput READ hasAcceptableInput)
Q_PROPERTY(QString placeholderText READ placeholderText WRITE setPlaceholderText)
Q_PROPERTY(Qt::CursorMoveStyle cursorMoveStyle READ cursorMoveStyle WRITE setCursorMoveStyle)
public:
enum ActionPosition {
LeadingPosition,
TrailingPosition
};
explicit QLineEdit(QWidget* parent=0);
explicit QLineEdit(const QString &, QWidget* parent=0);
~QLineEdit();
@ -164,6 +170,16 @@ public:
void getTextMargins(int *left, int *top, int *right, int *bottom) const;
QMargins textMargins() const;
#ifdef Q_NO_USING_KEYWORD
inline void addAction(QAction *action)
{ QWidget::addAction(action); }
#else
using QWidget::addAction;
#endif
void addAction(QAction *action, ActionPosition position);
QAction *addAction(const QIcon &icon, ActionPosition position);
public Q_SLOTS:
void setText(const QString &);
void clear();
@ -240,6 +256,7 @@ private:
#endif
Q_PRIVATE_SLOT(d_func(), void _q_selectionChanged())
Q_PRIVATE_SLOT(d_func(), void _q_updateNeeded(const QRect &))
Q_PRIVATE_SLOT(d_func(), void _q_textChanged(const QString &))
};
#endif // QT_NO_LINEEDIT

View File

@ -44,8 +44,10 @@
#ifndef QT_NO_LINEEDIT
#include "qvariant.h"
#include "qabstractitemview.h"
#include "qdrag.h"
#include "qwidgetaction.h"
#include "qclipboard.h"
#ifndef QT_NO_ACCESSIBILITY
#include "qaccessible.h"
@ -53,6 +55,7 @@
#ifndef QT_NO_IM
#include "qinputmethod.h"
#include "qlist.h"
#include <qpropertyanimation.h>
#endif
QT_BEGIN_NAMESPACE
@ -219,9 +222,9 @@ QRect QLineEditPrivate::adjustedContentsRect() const
QStyleOptionFrameV2 opt;
q->initStyleOption(&opt);
QRect r = q->style()->subElementRect(QStyle::SE_LineEditContents, &opt, q);
r.setX(r.x() + leftTextMargin);
r.setX(r.x() + effectiveLeftTextMargin());
r.setY(r.y() + topTextMargin);
r.setRight(r.right() - rightTextMargin);
r.setRight(r.right() - effectiveRightTextMargin());
r.setBottom(r.bottom() - bottomTextMargin);
return r;
}
@ -297,6 +300,160 @@ void QLineEditPrivate::drag()
#endif // QT_NO_DRAGANDDROP
QLineEditIconButton::QLineEditIconButton(QWidget *parent)
: QToolButton(parent)
, m_opacity(0)
{
#ifndef QT_NO_CURSOR
setCursor(Qt::ArrowCursor);
#endif
setFocusPolicy(Qt::NoFocus);
}
void QLineEditIconButton::paintEvent(QPaintEvent *)
{
QPainter painter(this);
// Note isDown should really use the active state but in most styles
// this has no proper feedback
QIcon::Mode state = QIcon::Disabled;
if (isEnabled())
state = isDown() ? QIcon::Selected : QIcon::Normal;
const QPixmap iconPixmap = icon().pixmap(QSize(IconButtonSize, IconButtonSize),
state, QIcon::Off);
QRect pixmapRect = QRect(0, 0, iconPixmap.width(), iconPixmap.height());
pixmapRect.moveCenter(rect().center());
painter.setOpacity(m_opacity);
painter.drawPixmap(pixmapRect, iconPixmap);
}
void QLineEditIconButton::setOpacity(qreal value)
{
if (!qFuzzyCompare(m_opacity, value)) {
m_opacity = value;
update();
}
}
void QLineEditIconButton::startOpacityAnimation(qreal endValue)
{
QPropertyAnimation *animation = new QPropertyAnimation(this, QByteArrayLiteral("opacity"));
animation->setDuration(160);
animation->setEndValue(endValue);
animation->start(QAbstractAnimation::DeleteWhenStopped);
}
void QLineEditPrivate::_q_textChanged(const QString &text)
{
if (hasSideWidgets()) {
const int newTextSize = text.size();
if (!newTextSize || !lastTextSize) {
lastTextSize = newTextSize;
const bool fadeIn = newTextSize > 0;
foreach (const SideWidgetEntry &e, leadingSideWidgets) {
if (e.flags & SideWidgetFadeInWithText)
static_cast<QLineEditIconButton *>(e.widget)->animateShow(fadeIn);
}
foreach (const SideWidgetEntry &e, trailingSideWidgets) {
if (e.flags & SideWidgetFadeInWithText)
static_cast<QLineEditIconButton *>(e.widget)->animateShow(fadeIn);
}
}
}
}
QSize QLineEditPrivate::iconSize() const
{
if (!m_iconSize.isValid()) // This might require style-specific handling (pixel metric).
m_iconSize = QSize(QLineEditIconButton::IconButtonSize + 6, QLineEditIconButton::IconButtonSize + 2);
return m_iconSize;
}
void QLineEditPrivate::positionSideWidgets()
{
Q_Q(QLineEdit);
if (hasSideWidgets()) {
const QRect contentRect = q->rect();
const QSize iconSize = QLineEditPrivate::iconSize();
const int delta = QLineEditIconButton::IconMargin + iconSize.width();
QRect widgetGeometry(QPoint(QLineEditIconButton::IconMargin, (contentRect.height() - iconSize.height()) / 2), iconSize);
foreach (const SideWidgetEntry &e, leftSideWidgetList()) {
e.widget->setGeometry(widgetGeometry);
widgetGeometry.moveLeft(widgetGeometry.left() + delta);
}
widgetGeometry.moveLeft(contentRect.width() - iconSize.width() - QLineEditIconButton::IconMargin);
foreach (const SideWidgetEntry &e, rightSideWidgetList()) {
e.widget->setGeometry(widgetGeometry);
widgetGeometry.moveLeft(widgetGeometry.left() - delta);
}
}
}
QLineEditPrivate::PositionIndexPair QLineEditPrivate::findSideWidget(const QAction *a) const
{
for (int i = 0; i < leadingSideWidgets.size(); ++i) {
if (a == leadingSideWidgets.at(i).action)
return PositionIndexPair(QLineEdit::LeadingPosition, i);
}
for (int i = 0; i < trailingSideWidgets.size(); ++i) {
if (a == trailingSideWidgets.at(i).action)
return PositionIndexPair(QLineEdit::TrailingPosition, i);
}
return PositionIndexPair(QLineEdit::LeadingPosition, -1);
}
QWidget *QLineEditPrivate::addAction(QAction *newAction, QAction *before, QLineEdit::ActionPosition position, int flags)
{
Q_Q(QLineEdit);
if (!newAction)
return 0;
QWidget *w = 0;
// Store flags about QWidgetAction here since removeAction() may be called from ~QAction,
// in which a qobject_cast<> no longer works.
if (QWidgetAction *widgetAction = qobject_cast<QWidgetAction *>(newAction)) {
if ((w = widgetAction->requestWidget(q)))
flags |= SideWidgetCreatedByWidgetAction;
}
if (!w) {
QLineEditIconButton *toolButton = new QLineEditIconButton(q);
toolButton->setIcon(newAction->icon());
toolButton->setOpacity(lastTextSize > 0 || !(flags & SideWidgetFadeInWithText) ? 1 : 0);
toolButton->setDefaultAction(newAction);
w = toolButton;
}
if (!hasSideWidgets()) { // initial setup.
QObject::connect(q, SIGNAL(textChanged(QString)), q, SLOT(_q_textChanged(QString)));
lastTextSize = q->text().size();
}
// If there is a 'before' action, it takes preference
PositionIndexPair positionIndex = before ? findSideWidget(before) : PositionIndexPair(position, -1);
SideWidgetEntryList &list = positionIndex.first == QLineEdit::TrailingPosition ? trailingSideWidgets : leadingSideWidgets;
if (positionIndex.second < 0)
positionIndex.second = list.size();
list.insert(positionIndex.second, SideWidgetEntry(w, newAction, flags));
positionSideWidgets();
w->show();
return w;
}
void QLineEditPrivate::removeAction(const QActionEvent *e)
{
Q_Q(QLineEdit);
QAction *action = e->action();
const PositionIndexPair positionIndex = findSideWidget(action);
if (positionIndex.second == -1)
return;
SideWidgetEntryList &list = positionIndex.first == QLineEdit::TrailingPosition ? trailingSideWidgets : leadingSideWidgets;
SideWidgetEntry entry = list.takeAt(positionIndex.second);
if (entry.flags & SideWidgetCreatedByWidgetAction)
static_cast<QWidgetAction *>(entry.action)->releaseWidget(entry.widget);
else
delete entry.widget;
positionSideWidgets();
if (!hasSideWidgets()) // Last widget, remove connection
QObject::disconnect(q, SIGNAL(textChanged(QString)), q, SLOT(_q_textChanged(QString)));
q->update();
}
QT_END_NAMESPACE
#endif

View File

@ -58,7 +58,9 @@
#ifndef QT_NO_LINEEDIT
#include "private/qwidget_p.h"
#include "QtWidgets/qlineedit.h"
#include "QtWidgets/qtoolbutton.h"
#include "QtGui/qtextlayout.h"
#include "QtGui/qicon.h"
#include "QtWidgets/qstyleoption.h"
#include "QtCore/qbasictimer.h"
#include "QtWidgets/qcompleter.h"
@ -69,16 +71,54 @@
QT_BEGIN_NAMESPACE
// QLineEditIconButton: This is a simple helper class that represents clickable icons that fade in with text
class QLineEditIconButton : public QToolButton
{
Q_OBJECT
Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity)
public:
enum { IconMargin = 4, IconButtonSize = 16 };
explicit QLineEditIconButton(QWidget *parent = 0);
qreal opacity() const { return m_opacity; }
void setOpacity(qreal value);
void animateShow(bool visible) { startOpacityAnimation(visible ? 1.0 : 0.0); }
protected:
void paintEvent(QPaintEvent *event);
private:
void startOpacityAnimation(qreal endValue);
qreal m_opacity;
};
class Q_AUTOTEST_EXPORT QLineEditPrivate : public QWidgetPrivate
{
Q_DECLARE_PUBLIC(QLineEdit)
public:
enum SideWidgetFlag {
SideWidgetFadeInWithText = 0x1,
SideWidgetCreatedByWidgetAction = 0x2
};
struct SideWidgetEntry {
SideWidgetEntry(QWidget *w = 0, QAction *a = 0, int _flags = 0) : widget(w), action(a), flags(_flags) {}
QWidget *widget;
QAction *action;
int flags;
};
typedef QList<SideWidgetEntry> SideWidgetEntryList;
QLineEditPrivate()
: control(0), frame(1), contextMenuEnabled(1), cursorVisible(0),
dragEnabled(0), clickCausedFocus(0), hscroll(0), vscroll(0),
alignment(Qt::AlignLeading | Qt::AlignVCenter),
leftTextMargin(0), topTextMargin(0), rightTextMargin(0), bottomTextMargin(0)
leftTextMargin(0), topTextMargin(0), rightTextMargin(0), bottomTextMargin(0),
lastTextSize(0)
{
}
@ -145,15 +185,49 @@ public:
QBasicTimer dndTimer;
void drag();
#endif
void _q_textChanged(const QString &);
int leftTextMargin;
int leftTextMargin; // use effectiveLeftTextMargin() in case of icon.
int topTextMargin;
int rightTextMargin;
int rightTextMargin; // use effectiveRightTextMargin() in case of icon.
int bottomTextMargin;
QString placeholderText;
QWidget *addAction(QAction *newAction, QAction *before, QLineEdit::ActionPosition, int flags = 0);
void removeAction(const QActionEvent *e);
QSize iconSize() const;
void positionSideWidgets();
inline bool hasSideWidgets() const { return !leadingSideWidgets.isEmpty() || !trailingSideWidgets.isEmpty(); }
inline const SideWidgetEntryList &leftSideWidgetList() const
{ return q_func()->layoutDirection() == Qt::LeftToRight ? leadingSideWidgets : trailingSideWidgets; }
inline const SideWidgetEntryList &rightSideWidgetList() const
{ return q_func()->layoutDirection() == Qt::LeftToRight ? trailingSideWidgets : leadingSideWidgets; }
int effectiveLeftTextMargin() const;
int effectiveRightTextMargin() const;
private:
typedef QPair<QLineEdit::ActionPosition, int> PositionIndexPair;
PositionIndexPair findSideWidget(const QAction *a) const;
SideWidgetEntryList leadingSideWidgets;
SideWidgetEntryList trailingSideWidgets;
int lastTextSize;
mutable QSize m_iconSize;
};
inline int QLineEditPrivate::effectiveLeftTextMargin() const
{
return leftTextMargin + leftSideWidgetList().size() * (QLineEditIconButton::IconMargin + iconSize().width());
}
inline int QLineEditPrivate::effectiveRightTextMargin() const
{
return rightTextMargin + rightSideWidgetList().size() * (QLineEditIconButton::IconMargin + iconSize().width());
}
#endif // QT_NO_LINEEDIT
QT_END_NAMESPACE

View File

@ -46,6 +46,9 @@
#include "qstringlist.h"
#include "qstyle.h"
#include "qvalidator.h"
#include "qwidgetaction.h"
#include "qimage.h"
#include "qicon.h"
#include "qcompleter.h"
#include "qstandarditemmodel.h"
#include <qpa/qplatformtheme.h>
@ -61,8 +64,12 @@
#include <private/qlineedit_p.h>
#include <private/qwidgetlinecontrol_p.h>
#include <qmenu.h>
#include <qlabel.h>
#include <qlayout.h>
#include <qspinbox.h>
#include <qlistview.h>
#include <qstringlistmodel.h>
#include <qsortfilterproxymodel.h>
#include <qdebug.h>
#include "qcommonstyle.h"
@ -290,6 +297,8 @@ private slots:
void undoRedoAndEchoModes_data();
void undoRedoAndEchoModes();
void sideWidgets();
protected slots:
void editingFinished();
@ -4048,5 +4057,39 @@ void tst_QLineEdit::undoRedoAndEchoModes()
QCOMPARE(testWidget->text(), expected.at(2));
}
void tst_QLineEdit::sideWidgets()
{
QWidget testWidget;
QVBoxLayout *l = new QVBoxLayout(&testWidget);
QLineEdit *lineEdit = new QLineEdit(&testWidget);
l->addWidget(lineEdit);
l->addSpacerItem(new QSpacerItem(0, 50, QSizePolicy::Ignored, QSizePolicy::Fixed));
QImage image(QSize(20, 20), QImage::Format_ARGB32);
image.fill(Qt::yellow);
QAction *iconAction = new QAction(QIcon(QPixmap::fromImage(image)), QString(), lineEdit);
QWidgetAction *label1Action = new QWidgetAction(lineEdit);
label1Action->setDefaultWidget(new QLabel(QStringLiteral("l1")));
QWidgetAction *label2Action = new QWidgetAction(lineEdit);
label2Action->setDefaultWidget(new QLabel(QStringLiteral("l2")));
QWidgetAction *label3Action = new QWidgetAction(lineEdit);
label3Action->setDefaultWidget(new QLabel(QStringLiteral("l3")));
lineEdit->addAction(iconAction, QLineEdit::LeadingPosition);
lineEdit->addAction(label2Action, QLineEdit::LeadingPosition);
lineEdit->addAction(label1Action, QLineEdit::TrailingPosition);
lineEdit->addAction(label3Action, QLineEdit::TrailingPosition);
testWidget.move(300, 300);
testWidget.show();
QVERIFY(QTest::qWaitForWindowExposed(&testWidget));
// Arbitrarily add/remove actions, trying to detect crashes. Add QTRY_VERIFY(false) to view the result.
delete label3Action;
lineEdit->removeAction(label2Action);
lineEdit->removeAction(iconAction);
lineEdit->removeAction(label1Action);
lineEdit->removeAction(iconAction);
lineEdit->removeAction(label1Action);
lineEdit->addAction(iconAction);
lineEdit->addAction(iconAction);
}
QTEST_MAIN(tst_QLineEdit)
#include "tst_qlineedit.moc"