Commit d2bb4447 authored by Marc Reilly's avatar Marc Reilly Committed by David Schulz
Browse files

texteditor: add a hover handler which shows color preview tooltips



This adds a new hover handler which matches stand-alone color strings
like "#112233" or "Qt::yellow" or function argument tuples for colors
such as "QColor(0x11, 0x22, 0x33)". When matching against function
arguments, the function name must correspond to a recognized color
function (setRgb, etc. This is biased towards cpp text, but not limited
to such). The matching occurs when hovering over the arguments, not the
function.
If a match is identified, the hover handler gives it a relatively high
ranking.

Change-Id: Ied2927399cb19d6f562185a8b087f0ce118157db
Reviewed-by: default avatarDavid Schulz <david.schulz@theqtcompany.com>
parent 64b172ae
/****************************************************************************
**
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia. For licensing terms and
** conditions see http://www.qt.io/licensing. For further information
** use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/
#include "colorpreviewhoverhandler.h"
#include "texteditor.h"
#include <coreplugin/icore.h>
#include <utils/tooltip/tooltip.h>
#include <utils/qtcassert.h>
#include <QPoint>
#include <QColor>
#include <QTextBlock>
using namespace Core;
namespace TextEditor {
/*
* Attempts to find a color string such as "#112233" from the word at
* the given position in the string. Also looks for "Qt::" for recognizing
* Qt::GlobalColor types (although there cannot be any spaces such as
* "Qt:: yellow")
*/
static QString extractColorString(const QString s, int pos)
{
if (s.length() < 3 || pos < 0 || pos >= s.length())
return QString();
int firstPos = pos;
do {
QChar c = s[firstPos];
if (c == QLatin1Char('#'))
break;
if (c == QLatin1Char(':')
&& (firstPos > 3)
&& (s.mid(firstPos-3, 4) == QLatin1String("Qt::"))) {
firstPos -= 3;
break;
}
if (!c.isLetterOrNumber())
return QString();
--firstPos;
} while (firstPos >= 0);
if (firstPos < 0)
return QString();
int lastPos = firstPos + 1;
do {
QChar c = s[lastPos];
if (!(c.isLetterOrNumber() || c == QLatin1Char(':')))
break;
lastPos++;
} while (lastPos < s.length());
return s.mid(firstPos, lastPos - firstPos);
}
static QColor fromEnumString(const QString &s)
{
const struct EnumColorMap {
QLatin1String name;
QColor color;
} table[] = {
{QLatin1String("white") , QColor(Qt::white)},
{QLatin1String("black"), QColor(Qt::black)},
{QLatin1String("red"), QColor(Qt::red)},
{QLatin1String("darkRed"), QColor(Qt::darkRed)},
{QLatin1String("green"), QColor(Qt::green)},
{QLatin1String("darkGreen"), QColor(Qt::darkGreen)},
{QLatin1String("blue"), QColor(Qt::blue)},
{QLatin1String("darkBlue"), QColor(Qt::darkBlue)},
{QLatin1String("cyan"), QColor(Qt::cyan)},
{QLatin1String("darkCyan"), QColor(Qt::darkCyan)},
{QLatin1String("magenta"), QColor(Qt::magenta)},
{QLatin1String("darkMagenta"), QColor(Qt::darkMagenta)},
{QLatin1String("yellow"), QColor(Qt::yellow)},
{QLatin1String("darkYellow"), QColor(Qt::darkYellow)},
{QLatin1String("gray"), QColor(Qt::gray)},
{QLatin1String("darkGray"), QColor(Qt::darkGray)},
{QLatin1String("lightGray"), QColor(Qt::lightGray)},
{QLatin1String("transparent"), QColor(Qt::transparent)}
};
for (uint ii = 0; ii < sizeof(table) / sizeof(table[0]); ++ii) {
if (s == table[ii].name)
return table[ii].color;
}
return QColor();
}
static QColor checkColorText(const QString &str)
{
if (str.startsWith(QLatin1Char('#')))
return QColor(str);
if (str.startsWith(QLatin1String("Qt::"))) {
QString colorStr = str;
colorStr.remove(0, 4);
return fromEnumString(colorStr);
}
return QColor();
}
// looks backwards through a string for the opening brace of a function
static int findOpeningBrace(const QString &s, int startIndex)
{
QTC_ASSERT(startIndex >= 0 && startIndex < s.length(), return -1);
int index = startIndex;
while (index > 0) {
const QChar c = s[index];
if (c == QLatin1Char('(') || c == QLatin1Char('{'))
return index;
--index;
}
return index;
}
static int findClosingBrace(const QString &s, int startIndex)
{
QTC_ASSERT(startIndex >= 0 && startIndex < s.length(), return -1);
int index = startIndex;
const int len = s.length();
while (index < len) {
const QChar c = s[index];
if (c == QLatin1Char(')') || c == QLatin1Char('}'))
return index;
++index;
}
return -1;
}
// returns the index of the first character of the func, or negative if not valid
static int findFuncStart(const QString &s, int startIndex)
{
QTC_ASSERT(startIndex >= 0 && startIndex < s.length(), return -1);
int index = startIndex;
while (index >= 0) {
const QChar c = s[index];
if (!c.isLetterOrNumber()) {
if (index == startIndex)
return -1;
return qMin(index + 1, startIndex);
}
--index;
}
return index;
}
static QString removeWhitespace(const QString &s)
{
QString ret;
ret.reserve(s.size());
for (int ii = 0; ii < s.length(); ++ii) {
const QChar c = s[ii];
if (!c.isSpace())
ret += c;
}
return ret;
}
/*
* Parses the string looking for a function and its arguments.
* The starting position is assumed to be within the braces.
*/
static bool extractFuncAndArgs(const QString &s,
int pos,
QString &retFuncName,
QStringList &retArgs)
{
int openBrace = findOpeningBrace(s, pos);
if (openBrace <= 0)
return false;
int closeBrace = findClosingBrace(s, openBrace + 1);
if (closeBrace < 0)
return false;
int funcEnd = openBrace - 1;
int funcStart = findFuncStart(s, funcEnd);
if (funcStart < 0 || funcEnd <= funcStart)
return false;
retFuncName = removeWhitespace(s.mid(funcStart, funcEnd - funcStart + 1));
QString argStr = s.mid(openBrace + 1, closeBrace - openBrace - 1);
retArgs = argStr.split(QLatin1Char(','), QString::KeepEmptyParts);
return true;
}
static QColor::Spec specForFunc(const QString &func)
{
if ((func == QLatin1String("QColor"))
|| (func == QLatin1String("QRgb"))
|| (func == QLatin1String("rgb"))
|| func.startsWith(QLatin1String("setRgb"))
|| func.startsWith(QLatin1String("setRgba"))){
return QColor::Rgb;
}
if (func.startsWith(QLatin1String("setCmyk")))
return QColor::Cmyk;
if (func.startsWith(QLatin1String("setHsv")))
return QColor::Hsv;
if (func.startsWith(QLatin1String("setHsl")))
return QColor::Hsv;
return QColor::Invalid;
}
static QColor colorFromArgs(const QStringList &args, QColor::Spec spec)
{
const int maxArgs = 5;
int vals[maxArgs];
vals[3] = 0xff;
vals[4] = 0xff;
bool allOk = true;
for (int ii = 0; ii < qMin(args.size(), maxArgs); ++ii) {
bool ok;
vals[ii] = args[ii].toInt(&ok, 0);
allOk &= ok;
}
if (!allOk)
return QColor();
QColor c;
switch (spec) {
case QColor::Rgb:
c.setRgb(vals[0], vals[1], vals[2], vals[3]);
break;
case QColor::Cmyk:
c.setCmyk(vals[0], vals[1], vals[2], vals[3], vals[4]);
break;
case QColor::Hsv:
c.setHsv(vals[0], vals[1], vals[2], vals[3]);
break;
case QColor::Hsl:
c.setHsl(vals[0], vals[1], vals[2], vals[3]);
break;
default:
break;
}
return c;
}
static QColor colorFromArgsF(const QStringList &args, QColor::Spec spec)
{
const int maxArgs = 5;
qreal vals[maxArgs];
vals[3] = 1.0;
vals[4] = 1.0;
bool allOk = true;
for (int ii = 0; ii < qMin(args.size(), maxArgs); ++ii) {
bool ok;
vals[ii] = args[ii].toDouble(&ok);
allOk &= ok;
}
if (!allOk)
return QColor();
QColor c;
switch (spec) {
case QColor::Rgb:
c.setRgbF(vals[0], vals[1], vals[2], vals[3]);
break;
case QColor::Cmyk:
c.setCmykF(vals[0], vals[1], vals[2], vals[3], vals[4]);
break;
case QColor::Hsv:
c.setHsvF(vals[0], vals[1], vals[2], vals[3]);
break;
case QColor::Hsl:
c.setHslF(vals[0], vals[1], vals[2], vals[3]);
break;
default:
break;
}
return c;
}
static QColor colorFromFuncAndArgs(const QString &func, const QStringList &args)
{
if (args.isEmpty())
return QColor();
if (args.size() < 3) {
QString arg0 = removeWhitespace(args[0]);
arg0.remove(QLatin1Char('\"'));
if (func == QLatin1String("setNamedColor"))
return QColor(arg0);
if (arg0.startsWith(QLatin1Char('#')))
return QColor(arg0);
if (arg0.startsWith(QLatin1String("Qt::"))) {
arg0.remove(0, 4);
return fromEnumString(arg0);
}
return QColor();
}
QColor::Spec spec = specForFunc(func);
if (spec == QColor::Invalid)
return QColor();
if (func.endsWith(QLatin1Char('F')))
return colorFromArgsF(args, spec);
return colorFromArgs(args, spec);
}
void ColorPreviewHoverHandler::identifyMatch(TextEditorWidget *editorWidget, int pos)
{
if (editorWidget->extraSelectionTooltip(pos).isEmpty()) {
const QTextBlock tb = editorWidget->document()->findBlock(pos);
const int tbpos = pos - tb.position();
const QString tbtext = tb.text();
QString colorString = extractColorString(tbtext, tbpos);
m_colorTip = checkColorText(colorString);
if (!m_colorTip.isValid()) {
QString funcName;
QStringList args;
if (extractFuncAndArgs(tbtext, tbpos, funcName, args))
m_colorTip = colorFromFuncAndArgs(funcName, args);
}
setPriority(m_colorTip.isValid() ? Priority_Help - 1 : Priority_None);
}
}
void ColorPreviewHoverHandler::operateTooltip(TextEditorWidget *editorWidget, const QPoint &point)
{
if (m_colorTip.isValid())
Utils::ToolTip::show(point, m_colorTip, editorWidget);
else
Utils::ToolTip::hide();
}
} // namespace TextEditor
/****************************************************************************
**
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia. For licensing terms and
** conditions see http://www.qt.io/licensing. For further information
** use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/
#ifndef COLORPREVIEWHOVERHANDLER_H
#define COLORPREVIEWHOVERHANDLER_H
#include "texteditor_global.h"
#include "basehoverhandler.h"
#include <QColor>
namespace Core { class IEditor; }
namespace TextEditor {
class TextEditorWidget;
class TEXTEDITOR_EXPORT ColorPreviewHoverHandler : public BaseHoverHandler
{
Q_OBJECT
public:
private:
virtual void identifyMatch(TextEditorWidget *editorWidget, int pos);
virtual void operateTooltip(TextEditorWidget *editorWidget, const QPoint &point);
QColor m_colorTip;
};
} // namespace TextEditor
#endif // COLORPREVIEWHOVERHANDLER_H
......@@ -55,6 +55,7 @@ SOURCES += texteditorplugin.cpp \
refactoroverlay.cpp \
outlinefactory.cpp \
basehoverhandler.cpp \
colorpreviewhoverhandler.cpp \
helpitem.cpp \
autocompleter.cpp \
snippets/snippetssettingspage.cpp \
......@@ -161,6 +162,7 @@ HEADERS += texteditorplugin.h \
outlinefactory.h \
ioutlinewidget.h \
basehoverhandler.h \
colorpreviewhoverhandler.h \
helpitem.h \
autocompleter.h \
snippets/snippetssettingspage.h \
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment