|
|
4c75f9e |
/****************************************************************************
|
|
|
4c75f9e |
**
|
|
|
4c75f9e |
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
|
|
|
4c75f9e |
** Contact: http://www.qt-project.org/legal
|
|
|
4c75f9e |
**
|
|
|
4c75f9e |
** This file is part of Qt Creator.
|
|
|
4c75f9e |
**
|
|
|
4c75f9e |
** Commercial License Usage
|
|
|
4c75f9e |
** Licensees holding valid commercial Qt licenses may use this file in
|
|
|
4c75f9e |
** accordance with the commercial license agreement provided with the
|
|
|
4c75f9e |
** Software or, alternatively, in accordance with the terms contained in
|
|
|
4c75f9e |
** a written agreement between you and Digia. For licensing terms and
|
|
|
4c75f9e |
** conditions see http://www.qt.io/licensing. For further information
|
|
|
4c75f9e |
** use the contact form at http://www.qt.io/contact-us.
|
|
|
4c75f9e |
**
|
|
|
4c75f9e |
** GNU Lesser General Public License Usage
|
|
|
4c75f9e |
** Alternatively, this file may be used under the terms of the GNU Lesser
|
|
|
4c75f9e |
** General Public License version 2.1 or version 3 as published by the Free
|
|
|
4c75f9e |
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
|
|
|
4c75f9e |
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
|
|
|
4c75f9e |
** following information to ensure the GNU Lesser General Public License
|
|
|
4c75f9e |
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
|
|
|
4c75f9e |
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
|
|
4c75f9e |
**
|
|
|
4c75f9e |
** In addition, as a special exception, Digia gives you certain additional
|
|
|
4c75f9e |
** rights. These rights are described in the Digia Qt LGPL Exception
|
|
|
4c75f9e |
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
|
|
4c75f9e |
**
|
|
|
4c75f9e |
****************************************************************************/
|
|
|
4c75f9e |
|
|
|
4c75f9e |
#include "execmenu.h"
|
|
|
4c75f9e |
#include "fancylineedit.h"
|
|
|
4c75f9e |
|
|
|
4c75f9e |
#include "common/common.h"
|
|
|
4c75f9e |
#include "gui/iconfactory.h"
|
|
|
4c75f9e |
|
|
|
4c75f9e |
#include <QAbstractItemView>
|
|
|
4c75f9e |
#include <QKeyEvent>
|
|
|
4c75f9e |
#include <QMenu>
|
|
|
4c75f9e |
#include <QStylePainter>
|
|
|
4c75f9e |
#include <QStyle>
|
|
|
4c75f9e |
|
|
|
4c75f9e |
/*!
|
|
|
4c75f9e |
The FancyLineEdit class is an enhanced line edit with several
|
|
|
4c75f9e |
opt-in features.
|
|
|
4c75f9e |
|
|
|
4c75f9e |
A FancyLineEdit instance can have:
|
|
|
4c75f9e |
|
|
|
4c75f9e |
- An embedded pixmap on one side that is connected to a menu.
|
|
|
4c75f9e |
|
|
|
4c75f9e |
- A grayed hintText (like "Type Here to")
|
|
|
4c75f9e |
when not focused and empty. When connecting to the changed signals and
|
|
|
4c75f9e |
querying text, one has to be aware that the text is set to that hint
|
|
|
4c75f9e |
text if isShowingHintText() returns true (that is, does not contain
|
|
|
4c75f9e |
valid user input).
|
|
|
4c75f9e |
*/
|
|
|
4c75f9e |
|
|
|
4c75f9e |
namespace {
|
|
|
4c75f9e |
|
|
|
4c75f9e |
qreal devicePixelRatio(const QWidget &w)
|
|
|
4c75f9e |
{
|
|
|
4c75f9e |
#if QT_VERSION < 0x050000
|
|
|
4c75f9e |
return w.logicalDpiX() > 150 ? 2.0 : 1.0;
|
|
|
4c75f9e |
#else
|
|
|
4c75f9e |
return w.devicePixelRatio();
|
|
|
4c75f9e |
#endif
|
|
|
4c75f9e |
}
|
|
|
4c75f9e |
|
|
|
4c75f9e |
} // namespace
|
|
|
4c75f9e |
|
|
|
4c75f9e |
namespace Utils {
|
|
|
4c75f9e |
|
|
|
4c75f9e |
// --------- FancyLineEditPrivate
|
|
|
4c75f9e |
class FancyLineEditPrivate : public QObject
|
|
|
4c75f9e |
{
|
|
|
4c75f9e |
public:
|
|
|
4c75f9e |
explicit FancyLineEditPrivate(FancyLineEdit *parent);
|
|
|
4c75f9e |
|
|
|
4c75f9e |
bool eventFilter(QObject *obj, QEvent *event) override;
|
|
|
4c75f9e |
|
|
|
4c75f9e |
FancyLineEdit *m_lineEdit;
|
|
|
4c75f9e |
QString m_oldText;
|
|
|
4c75f9e |
QMenu *m_menu[2]{};
|
|
|
4c75f9e |
bool m_menuTabFocusTrigger[2]{};
|
|
|
4c75f9e |
IconButton *m_iconbutton[2]{};
|
|
|
4c75f9e |
bool m_iconEnabled[2]{};
|
|
|
4c75f9e |
};
|
|
|
4c75f9e |
|
|
|
4c75f9e |
|
|
|
4c75f9e |
FancyLineEditPrivate::FancyLineEditPrivate(FancyLineEdit *parent) :
|
|
|
4c75f9e |
QObject(parent),
|
|
|
4c75f9e |
m_lineEdit(parent)
|
|
|
4c75f9e |
{
|
|
|
4c75f9e |
for (int i = 0; i < 2; ++i) {
|
|
|
4c75f9e |
m_menu[i] = nullptr;
|
|
|
4c75f9e |
m_menuTabFocusTrigger[i] = false;
|
|
|
4c75f9e |
m_iconbutton[i] = new IconButton(parent);
|
|
|
4c75f9e |
m_iconbutton[i]->installEventFilter(this);
|
|
|
4c75f9e |
m_iconbutton[i]->hide();
|
|
|
4c75f9e |
m_iconEnabled[i] = false;
|
|
|
4c75f9e |
}
|
|
|
4c75f9e |
}
|
|
|
4c75f9e |
|
|
|
4c75f9e |
bool FancyLineEditPrivate::eventFilter(QObject *obj, QEvent *event)
|
|
|
4c75f9e |
{
|
|
|
4c75f9e |
int buttonIndex = -1;
|
|
|
4c75f9e |
for (int i = 0; i < 2; ++i) {
|
|
|
4c75f9e |
if (obj == m_iconbutton[i]) {
|
|
|
4c75f9e |
buttonIndex = i;
|
|
|
4c75f9e |
break;
|
|
|
4c75f9e |
}
|
|
|
4c75f9e |
}
|
|
|
4c75f9e |
|
|
|
4c75f9e |
if (buttonIndex == -1)
|
|
|
4c75f9e |
return QObject::eventFilter(obj, event);
|
|
|
4c75f9e |
|
|
|
4c75f9e |
if ( event->type() == QEvent::FocusIn
|
|
|
4c75f9e |
&& m_menuTabFocusTrigger[buttonIndex]
|
|
|
4c75f9e |
&& m_menu[buttonIndex])
|
|
|
4c75f9e |
{
|
|
|
4c75f9e |
m_lineEdit->setFocus();
|
|
|
4c75f9e |
execMenuAtWidget(m_menu[buttonIndex], m_iconbutton[buttonIndex]);
|
|
|
4c75f9e |
return true;
|
|
|
4c75f9e |
}
|
|
|
4c75f9e |
|
|
|
4c75f9e |
return QObject::eventFilter(obj, event);
|
|
|
4c75f9e |
}
|
|
|
4c75f9e |
|
|
|
4c75f9e |
|
|
|
4c75f9e |
// --------- FancyLineEdit
|
|
|
4c75f9e |
FancyLineEdit::FancyLineEdit(QWidget *parent) :
|
|
|
4c75f9e |
QLineEdit(parent),
|
|
|
4c75f9e |
d(new FancyLineEditPrivate(this))
|
|
|
4c75f9e |
{
|
|
|
4c75f9e |
ensurePolished();
|
|
|
4c75f9e |
updateMargins();
|
|
|
4c75f9e |
|
|
|
4c75f9e |
connect(d->m_iconbutton[Left], SIGNAL(clicked()), this, SLOT(iconClicked()));
|
|
|
4c75f9e |
connect(d->m_iconbutton[Right], SIGNAL(clicked()), this, SLOT(iconClicked()));
|
|
|
4c75f9e |
}
|
|
|
4c75f9e |
|
|
|
4c75f9e |
FancyLineEdit::~FancyLineEdit() = default;
|
|
|
4c75f9e |
|
|
|
4c75f9e |
void FancyLineEdit::setButtonVisible(Side side, bool visible)
|
|
|
4c75f9e |
{
|
|
|
4c75f9e |
d->m_iconbutton[side]->setVisible(visible);
|
|
|
4c75f9e |
d->m_iconEnabled[side] = visible;
|
|
|
4c75f9e |
updateMargins();
|
|
|
4c75f9e |
}
|
|
|
4c75f9e |
|
|
|
4c75f9e |
bool FancyLineEdit::isButtonVisible(Side side) const
|
|
|
4c75f9e |
{
|
|
|
4c75f9e |
return d->m_iconEnabled[side];
|
|
|
4c75f9e |
}
|
|
|
4c75f9e |
|
|
|
4c75f9e |
QAbstractButton *FancyLineEdit::button(FancyLineEdit::Side side) const
|
|
|
4c75f9e |
{
|
|
|
4c75f9e |
return d->m_iconbutton[side];
|
|
|
4c75f9e |
}
|
|
|
4c75f9e |
|
|
|
4c75f9e |
void FancyLineEdit::iconClicked()
|
|
|
4c75f9e |
{
|
|
|
4c75f9e |
IconButton *button = qobject_cast<IconButton *>(sender());
|
|
|
4c75f9e |
int index = -1;
|
|
|
4c75f9e |
for (int i = 0; i < 2; ++i)
|
|
|
4c75f9e |
if (d->m_iconbutton[i] == button)
|
|
|
4c75f9e |
index = i;
|
|
|
4c75f9e |
if (index == -1)
|
|
|
4c75f9e |
return;
|
|
|
4c75f9e |
if (d->m_menu[index]) {
|
|
|
4c75f9e |
execMenuAtWidget(d->m_menu[index], button);
|
|
|
4c75f9e |
} else {
|
|
|
4c75f9e |
emit buttonClicked(static_cast<Side>(index));
|
|
|
4c75f9e |
if (index == Left)
|
|
|
4c75f9e |
emit leftButtonClicked();
|
|
|
4c75f9e |
else if (index == Right)
|
|
|
4c75f9e |
emit rightButtonClicked();
|
|
|
4c75f9e |
}
|
|
|
4c75f9e |
}
|
|
|
4c75f9e |
|
|
|
4c75f9e |
void FancyLineEdit::updateMargins()
|
|
|
4c75f9e |
{
|
|
|
4c75f9e |
bool leftToRight = (layoutDirection() == Qt::LeftToRight);
|
|
|
4c75f9e |
Side realLeft = (leftToRight ? Left : Right);
|
|
|
4c75f9e |
Side realRight = (leftToRight ? Right : Left);
|
|
|
4c75f9e |
|
|
|
4c75f9e |
const qreal ratio = ::devicePixelRatio(*this);
|
|
|
4c75f9e |
auto leftMargin = static_cast<int>( d->m_iconbutton[realLeft]->sizeHint().width() + ratio * 8 );
|
|
|
4c75f9e |
auto rightMargin = static_cast<int>( d->m_iconbutton[realRight]->sizeHint().width() + ratio * 8 );
|
|
|
4c75f9e |
// Note KDE does not reserve space for the highlight color
|
|
|
4c75f9e |
if (style()->inherits("OxygenStyle")) {
|
|
|
4c75f9e |
leftMargin = qMax(24, leftMargin);
|
|
|
4c75f9e |
rightMargin = qMax(24, rightMargin);
|
|
|
4c75f9e |
}
|
|
|
4c75f9e |
|
|
|
4c75f9e |
const auto m = static_cast<int>(2 * ratio);
|
|
|
4c75f9e |
QMargins margins((d->m_iconEnabled[realLeft] ? leftMargin : m), m,
|
|
|
4c75f9e |
(d->m_iconEnabled[realRight] ? rightMargin : m), m);
|
|
|
4c75f9e |
|
|
|
4c75f9e |
setTextMargins(margins);
|
|
|
4c75f9e |
}
|
|
|
4c75f9e |
|
|
|
4c75f9e |
void FancyLineEdit::updateButtonPositions()
|
|
|
4c75f9e |
{
|
|
|
4c75f9e |
QRect contentRect = rect();
|
|
|
4c75f9e |
for (int i = 0; i < 2; ++i) {
|
|
|
4c75f9e |
Side iconpos = static_cast<Side>(i);
|
|
|
4c75f9e |
if (layoutDirection() == Qt::RightToLeft)
|
|
|
4c75f9e |
iconpos = (iconpos == Left ? Right : Left);
|
|
|
4c75f9e |
|
|
|
4c75f9e |
if (iconpos == FancyLineEdit::Right) {
|
|
|
4c75f9e |
const int iconoffset = textMargins().right() + 4;
|
|
|
4c75f9e |
d->m_iconbutton[i]->setGeometry(contentRect.adjusted(width() - iconoffset, 0, 0, 0));
|
|
|
4c75f9e |
} else {
|
|
|
4c75f9e |
const int iconoffset = textMargins().left() + 4;
|
|
|
4c75f9e |
d->m_iconbutton[i]->setGeometry(contentRect.adjusted(0, 0, -width() + iconoffset, 0));
|
|
|
4c75f9e |
}
|
|
|
4c75f9e |
}
|
|
|
4c75f9e |
}
|
|
|
4c75f9e |
|
|
|
4c75f9e |
void FancyLineEdit::resizeEvent(QResizeEvent *)
|
|
|
4c75f9e |
{
|
|
|
4c75f9e |
updateButtonPositions();
|
|
|
4c75f9e |
}
|
|
|
4c75f9e |
|
|
|
4c75f9e |
void FancyLineEdit::setButtonIcon(Side side, const QIcon &icon)
|
|
|
4c75f9e |
{
|
|
|
4c75f9e |
d->m_iconbutton[side]->setIcon(icon);
|
|
|
4c75f9e |
updateMargins();
|
|
|
4c75f9e |
updateButtonPositions();
|
|
|
4c75f9e |
update();
|
|
|
4c75f9e |
}
|
|
|
4c75f9e |
|
|
|
4c75f9e |
void FancyLineEdit::setButtonMenu(Side side, QMenu *buttonMenu)
|
|
|
4c75f9e |
{
|
|
|
4c75f9e |
d->m_menu[side] = buttonMenu;
|
|
|
4c75f9e |
d->m_iconbutton[side]->setHasMenu(buttonMenu != nullptr);
|
|
|
4c75f9e |
}
|
|
|
4c75f9e |
|
|
|
4c75f9e |
QMenu *FancyLineEdit::buttonMenu(Side side) const
|
|
|
4c75f9e |
{
|
|
|
4c75f9e |
return d->m_menu[side];
|
|
|
4c75f9e |
}
|
|
|
4c75f9e |
|
|
|
4c75f9e |
bool FancyLineEdit::hasMenuTabFocusTrigger(Side side) const
|
|
|
4c75f9e |
{
|
|
|
4c75f9e |
return d->m_menuTabFocusTrigger[side];
|
|
|
4c75f9e |
}
|
|
|
4c75f9e |
|
|
|
4c75f9e |
void FancyLineEdit::setMenuTabFocusTrigger(Side side, bool v)
|
|
|
4c75f9e |
{
|
|
|
4c75f9e |
if (d->m_menuTabFocusTrigger[side] == v)
|
|
|
4c75f9e |
return;
|
|
|
4c75f9e |
|
|
|
4c75f9e |
d->m_menuTabFocusTrigger[side] = v;
|
|
|
4c75f9e |
d->m_iconbutton[side]->setFocusPolicy(v ? Qt::TabFocus : Qt::NoFocus);
|
|
|
4c75f9e |
}
|
|
|
4c75f9e |
|
|
|
4c75f9e |
void FancyLineEdit::setButtonToolTip(Side side, const QString &tip)
|
|
|
4c75f9e |
{
|
|
|
4c75f9e |
d->m_iconbutton[side]->setToolTip(tip);
|
|
|
4c75f9e |
}
|
|
|
4c75f9e |
|
|
|
4c75f9e |
void FancyLineEdit::setButtonFocusPolicy(Side side, Qt::FocusPolicy policy)
|
|
|
4c75f9e |
{
|
|
|
4c75f9e |
d->m_iconbutton[side]->setFocusPolicy(policy);
|
|
|
4c75f9e |
}
|
|
|
4c75f9e |
|
|
|
4c75f9e |
|
|
|
4c75f9e |
//
|
|
|
4c75f9e |
// IconButton - helper class to represent a clickable icon
|
|
|
4c75f9e |
//
|
|
|
4c75f9e |
|
|
|
4c75f9e |
IconButton::IconButton(QWidget *parent)
|
|
|
4c75f9e |
: QAbstractButton(parent)
|
|
|
4c75f9e |
, m_hasMenu(false)
|
|
|
4c75f9e |
{
|
|
|
4c75f9e |
setCursor(Qt::PointingHandCursor);
|
|
|
4c75f9e |
setFocusPolicy(Qt::NoFocus);
|
|
|
4c75f9e |
}
|
|
|
4c75f9e |
|
|
|
4c75f9e |
void IconButton::paintEvent(QPaintEvent *)
|
|
|
4c75f9e |
{
|
|
|
4c75f9e |
const qreal ratio = ::devicePixelRatio(*this);
|
|
|
4c75f9e |
const auto iconSize = static_cast<int>( qMin(width(), height()) - ratio * 8 );
|
|
|
4c75f9e |
const QPixmap pixmap = m_icon.pixmap(iconSize);
|
|
|
4c75f9e |
QRect pixmapRect = pixmap.rect();
|
|
|
4c75f9e |
pixmapRect.moveCenter(rect().center());
|
|
|
4c75f9e |
|
|
|
4c75f9e |
QStylePainter painter(this);
|
|
|
4c75f9e |
|
|
|
4c75f9e |
m_icon.paint(&painter, pixmapRect);
|
|
|
4c75f9e |
|
|
|
4c75f9e |
if (m_hasMenu) {
|
|
|
4c75f9e |
// small triangle next to icon to indicate menu
|
|
|
4c75f9e |
QPolygon triangle;
|
|
|
4c75f9e |
triangle.append(QPoint(0, 0));
|
|
|
4c75f9e |
const auto ratio6 = static_cast<int>(ratio * 6);
|
|
|
4c75f9e |
const auto ratio3 = static_cast<int>(ratio * 3);
|
|
|
4c75f9e |
triangle.append(QPoint(ratio6, 0));
|
|
|
4c75f9e |
triangle.append(QPoint(ratio3, ratio3));
|
|
|
4c75f9e |
|
|
|
4c75f9e |
const QColor c = getDefaultIconColor(*this);
|
|
|
4c75f9e |
painter.save();
|
|
|
4c75f9e |
painter.translate(pixmapRect.bottomRight() + QPoint(0, - ratio6));
|
|
|
4c75f9e |
painter.setBrush(c);
|
|
|
4c75f9e |
painter.setPen(Qt::NoPen);
|
|
|
4c75f9e |
painter.drawPolygon(triangle);
|
|
|
4c75f9e |
painter.restore();
|
|
|
4c75f9e |
}
|
|
|
4c75f9e |
|
|
|
4c75f9e |
if (hasFocus()) {
|
|
|
4c75f9e |
QStyleOptionFocusRect focusOption;
|
|
|
4c75f9e |
focusOption.initFrom(this);
|
|
|
4c75f9e |
focusOption.rect = pixmapRect;
|
|
|
4c75f9e |
#ifdef Q_OS_MAC
|
|
|
4c75f9e |
focusOption.rect.adjust(-4, -4, 4, 4);
|
|
|
4c75f9e |
painter.drawControl(QStyle::CE_FocusFrame, focusOption);
|
|
|
4c75f9e |
#endif
|
|
|
4c75f9e |
painter.drawPrimitive(QStyle::PE_FrameFocusRect, focusOption);
|
|
|
4c75f9e |
}
|
|
|
4c75f9e |
}
|
|
|
4c75f9e |
|
|
|
4c75f9e |
QSize IconButton::sizeHint() const
|
|
|
4c75f9e |
{
|
|
|
4c75f9e |
const qreal ratio = ::devicePixelRatio(*this);
|
|
|
4c75f9e |
const auto extent = static_cast<int>(ratio * 16);
|
|
|
4c75f9e |
return QSize(extent + static_cast<int>(ratio * 6), extent);
|
|
|
4c75f9e |
}
|
|
|
4c75f9e |
|
|
|
4c75f9e |
void IconButton::keyPressEvent(QKeyEvent *ke)
|
|
|
4c75f9e |
{
|
|
|
4c75f9e |
QAbstractButton::keyPressEvent(ke);
|
|
|
4c75f9e |
if (!ke->modifiers() && (ke->key() == Qt::Key_Enter || ke->key() == Qt::Key_Return))
|
|
|
4c75f9e |
click();
|
|
|
4c75f9e |
// do not forward to line edit
|
|
|
4c75f9e |
ke->accept();
|
|
|
4c75f9e |
}
|
|
|
4c75f9e |
|
|
|
4c75f9e |
void IconButton::keyReleaseEvent(QKeyEvent *ke)
|
|
|
4c75f9e |
{
|
|
|
4c75f9e |
QAbstractButton::keyReleaseEvent(ke);
|
|
|
4c75f9e |
// do not forward to line edit
|
|
|
4c75f9e |
ke->accept();
|
|
|
4c75f9e |
}
|
|
|
4c75f9e |
|
|
|
4c75f9e |
} // namespace Utils
|