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