QmlDesigner: Add font asset preview tooltip

Refactored item library component preview tooltip to be also suitable
for asset previews.

Also now using the same image cache for font asset icons.

Fixes: QDS-3254
Fixes: QDS-3470
Change-Id: I34c1278be2e5697e01eaedabe2798104507a6ad8
Reviewed-by: Leena Miettinen <riitta-leena.miettinen@qt.io>
Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
Reviewed-by: Marco Bubke <marco.bubke@qt.io>
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Miikka Heikkinen
2021-01-05 16:06:04 +02:00
parent a477118b22
commit 4e5676e8c2
19 changed files with 594 additions and 192 deletions

View File

@@ -44,10 +44,10 @@ QImage renderImage(ServerNodeInstance rootNodeInstance)
QSize previewImageSize = rootNodeInstance.boundingRect().size().toSize();
if (previewImageSize.isEmpty())
previewImageSize = {640, 480};
previewImageSize = {300, 300};
if (previewImageSize.width() > 800 || previewImageSize.height() > 800)
previewImageSize.scale({800, 800}, Qt::KeepAspectRatio);
if (previewImageSize.width() > 300 || previewImageSize.height() > 300)
previewImageSize.scale({300, 300}, Qt::KeepAspectRatio);
QImage previewImage = rootNodeInstance.renderPreviewImage(previewImageSize);

View File

@@ -33,6 +33,7 @@ MouseArea {
onExited: tooltipBackend.hideTooltip()
onCanceled: tooltipBackend.hideTooltip()
onClicked: forceActiveFocus()
onPositionChanged: tooltipBackend.reposition()
hoverEnabled: true
@@ -40,8 +41,8 @@ MouseArea {
interval: 1000
running: mouseArea.containsMouse
onTriggered: {
tooltipBackend.componentName = itemName
tooltipBackend.componentPath = componentPath
tooltipBackend.name = itemName
tooltipBackend.path = componentPath
tooltipBackend.showTooltip()
}
}

View File

@@ -599,6 +599,8 @@ extend_qtc_plugin(QmlDesigner
imagecache/imagecachecollector.h
imagecache/imagecachecollector.cpp
imagecache/imagecachefontcollector.h
imagecache/imagecachefontcollector.cpp
imagecache/imagecache.cpp
imagecache/imagecachecollectorinterface.h
imagecache/imagecacheconnectionmanager.cpp

View File

@@ -26,6 +26,7 @@
#include "customfilesystemmodel.h"
#include <theme.h>
#include <imagecache.h>
#include <utils/filesystemwatcher.h>
@@ -37,8 +38,10 @@
#include <QImageReader>
#include <QPainter>
#include <QRawFont>
#include <QGlyphRun>
#include <QPair>
#include <qmath.h>
#include <condition_variable>
#include <mutex>
namespace QmlDesigner {
@@ -84,68 +87,6 @@ static QPixmap defaultPixmapForType(const QString &type, const QSize &size)
return QPixmap(QStringLiteral(":/ItemLibrary/images/asset_%1_%2.png").arg(type).arg(size.width()));
}
static QPixmap generateFontImage(const QFileInfo &info, const QSize &size)
{
static QHash<QString, QPixmap> fontImageCache;
const QString file = info.absoluteFilePath();
const QString key = QStringLiteral("%1@%2@%3").arg(file).arg(size.width()).arg(size.height());
if (!fontImageCache.contains(key)) {
QPixmap pixmap(size);
pixmap.fill(Qt::transparent);
qreal pixelSize = size.width() / 2.;
bool done = false;
while (!done) {
QRawFont font(file, pixelSize);
if (!font.isValid())
break;
QGlyphRun gr;
gr.setRawFont(font);
QVector<quint32> indices = font.glyphIndexesForString("Abc");
if (indices.isEmpty())
break;
const QVector<QPointF> advances = font.advancesForGlyphIndexes(indices);
QVector<QPointF> positions;
QPointF totalAdvance;
for (const QPointF &advance : advances) {
positions.append(totalAdvance);
totalAdvance += advance;
}
gr.setGlyphIndexes(indices);
gr.setPositions(positions);
QRectF bounds = gr.boundingRect();
if (bounds.width() <= 0 || bounds.height() <= 0)
break;
bounds.moveCenter({size.width() / 2., size.height() / 2.});
// Bounding rectangle doesn't necessarily contain the entirety of glyphs for some
// reason, so also check totalAdvance for overflow.
qreal limitX = qMax(bounds.width(), totalAdvance.x());
if (size.width() < limitX) {
pixelSize = qreal(qFloor(pixelSize * size.width() / limitX));
continue;
}
qreal limitY = qMax(bounds.height(), totalAdvance.y());
if (size.height() < limitY) {
pixelSize = qreal(qFloor(pixelSize * size.height() / limitY));
continue;
}
QPainter painter(&pixmap);
painter.setPen(Theme::getColor(Theme::DStextColor));
painter.drawGlyphRun(bounds.bottomLeft(), gr);
done = true;
}
if (!done)
pixmap = defaultPixmapForType("font", size);
fontImageCache[key] = pixmap;
}
return fontImageCache[key];
}
QString fontFamily(const QFileInfo &info)
{
QRawFont font(info.absoluteFilePath(), 10);
@@ -157,21 +98,27 @@ QString fontFamily(const QFileInfo &info)
class ItemLibraryFileIconProvider : public QFileIconProvider
{
public:
ItemLibraryFileIconProvider() = default;
ItemLibraryFileIconProvider(ImageCache &fontImageCache)
: QFileIconProvider()
, m_fontImageCache(fontImageCache)
{
}
QIcon icon( const QFileInfo & info ) const override
{
QIcon icon;
const QString suffix = info.suffix();
const QString filePath = info.absoluteFilePath();
if (supportedFontSuffixes().contains(suffix))
return generateFontIcons(filePath);
for (auto iconSize : iconSizes) {
// Provide icon depending on suffix
QPixmap pixmap;
if (supportedImageSuffixes().contains(suffix))
pixmap.load(info.absoluteFilePath());
else if (supportedFontSuffixes().contains(suffix))
pixmap = generateFontImage(info, iconSize);
pixmap.load(filePath);
else if (supportedAudioSuffixes().contains(suffix))
pixmap = defaultPixmapForType("sound", iconSize);
else if (supportedShaderSuffixes().contains(suffix))
@@ -189,6 +136,61 @@ public:
return icon;
}
QIcon generateFontIcons(const QString &filePath) const
{
QIcon icon;
QString colorName = Theme::getColor(Theme::DStextColor).name();
std::condition_variable condition;
int count = iconSizes.size();
std::mutex mutex;
QList<QPair<QSize, QImage>> images;
for (auto iconSize : iconSizes) {
m_fontImageCache.requestImage(
filePath,
[&images, &condition, &count, &mutex, iconSize](const QImage &image) {
int currentCount;
{
std::unique_lock lock{mutex};
currentCount = --count;
images.append({iconSize, image});
}
if (currentCount <= 0)
condition.notify_all();
},
[&images, &condition, &count, &mutex, iconSize] {
int currentCount;
{
std::unique_lock lock{mutex};
currentCount = --count;
images.append({iconSize, {}});
}
if (currentCount <= 0)
condition.notify_all();
},
QStringLiteral("%1@%2@Abc").arg(QString::number(iconSize.width()),
colorName)
);
}
{
// Block main thread until icons are generated, as it has to be done synchronously
std::unique_lock lock{mutex};
if (count > 0)
condition.wait(lock, [&]{ return count <= 0; });
}
for (const auto &pair : qAsConst(images)) {
QImage image = pair.second;
if (image.isNull())
icon.addPixmap(defaultPixmapForType("font", pair.first));
else
icon.addPixmap(QPixmap::fromImage(image));
}
return icon;
}
// Generated icon sizes should contain all ItemLibraryResourceView needed icon sizes, and their
// x2 versions for HDPI sceens
QList<QSize> iconSizes = {{384, 384}, {192, 192}, // Large
@@ -197,13 +199,15 @@ public:
{48, 48}, // Small
{64, 64}, {32, 32}}; // List
ImageCache &m_fontImageCache;
};
CustomFileSystemModel::CustomFileSystemModel(QObject *parent) : QAbstractListModel(parent)
, m_fileSystemModel(new QFileSystemModel(this))
, m_fileSystemWatcher(new Utils::FileSystemWatcher(this))
CustomFileSystemModel::CustomFileSystemModel(ImageCache &fontImageCache, QObject *parent)
: QAbstractListModel(parent)
, m_fileSystemModel(new QFileSystemModel(this))
, m_fileSystemWatcher(new Utils::FileSystemWatcher(this))
{
m_fileSystemModel->setIconProvider(new ItemLibraryFileIconProvider());
m_fileSystemModel->setIconProvider(new ItemLibraryFileIconProvider(fontImageCache));
connect(m_fileSystemWatcher, &Utils::FileSystemWatcher::directoryChanged, [this] {
updatePath(m_fileSystemModel->rootPath());
@@ -345,6 +349,20 @@ const QSet<QString> &CustomFileSystemModel::supportedSuffixes() const
return allSuffixes;
}
const QSet<QString> &CustomFileSystemModel::previewableSuffixes() const
{
static QSet<QString> previewableSuffixes;
if (previewableSuffixes.isEmpty()) {
auto insertSuffixes = [](const QStringList &suffixes) {
for (const auto &suffix : suffixes)
previewableSuffixes.insert(suffix);
};
insertSuffixes(supportedFontSuffixes());
}
return previewableSuffixes;
}
void CustomFileSystemModel::appendIfNotFiltered(const QString &file)
{
if (filterMetaIcons(file))

View File

@@ -39,11 +39,13 @@ namespace Utils { class FileSystemWatcher; }
namespace QmlDesigner {
class ImageCache;
class CustomFileSystemModel : public QAbstractListModel
{
Q_OBJECT
public:
CustomFileSystemModel(QObject *parent = nullptr);
CustomFileSystemModel(ImageCache &fontImageCache, QObject *parent = nullptr);
void setFilter(QDir::Filters filters);
QString rootPath() const;
@@ -64,6 +66,7 @@ public:
QPair<QString, QByteArray> resourceTypeAndData(const QModelIndex &index) const;
const QSet<QString> &supportedSuffixes() const;
const QSet<QString> &previewableSuffixes() const;
private:
QModelIndex updatePath(const QString &newPath);

View File

@@ -27,6 +27,10 @@
#include "customfilesystemmodel.h"
#include <theme.h>
#include <imagecache.h>
#include <previewtooltip/previewtooltipbackend.h>
#include <QAction>
#include <QActionGroup>
#include <QDebug>
@@ -35,6 +39,7 @@
#include <QMimeData>
#include <QPainter>
#include <QPixmap>
#include <QtGui/qevent.h>
#include <QProxyStyle>
@@ -58,7 +63,7 @@ void ItemLibraryResourceView::addSizeAction(QActionGroup *group, const QString &
});
}
ItemLibraryResourceView::ItemLibraryResourceView(QWidget *parent) :
ItemLibraryResourceView::ItemLibraryResourceView(ImageCache &fontImageCache, QWidget *parent) :
QListView(parent)
{
setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
@@ -102,6 +107,20 @@ ItemLibraryResourceView::ItemLibraryResourceView(QWidget *parent) :
defaultAction->toggle();
addActions(actionGroup->actions());
viewport()->setAttribute(Qt::WA_Hover);
m_fontPreviewTooltipBackend = std::make_unique<PreviewTooltipBackend>(fontImageCache);
// Note: Though the text specified here appears in UI, it shouldn't be translated, as it's
// a commonly used sentence to preview the font glyphs in latin fonts.
// For fonts that do not have latin glyphs, the font family name will have to
// suffice for preview. Font family name is inserted into %1 at render time.
m_fontPreviewTooltipBackend->setState(QStringLiteral("%1@%2@%3")
.arg(QString::number(300),
Theme::getColor(Theme::DStextColor).name(),
QStringLiteral("%1\n\n"
"The quick brown fox jumps\n"
"over the lazy dog\n"
"1234567890")));
}
void ItemLibraryResourceView::startDrag(Qt::DropActions /* supportedActions */)
@@ -136,5 +155,51 @@ void ItemLibraryResourceView::startDrag(Qt::DropActions /* supportedActions */)
drag->exec();
}
bool ItemLibraryResourceView::viewportEvent(QEvent *event)
{
if (event->type() == QEvent::ToolTip) {
auto fileSystemModel = qobject_cast<CustomFileSystemModel *>(model());
Q_ASSERT(fileSystemModel);
QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event);
QModelIndex index = indexAt(helpEvent->pos());
if (index.isValid()) {
QFileInfo fi = fileSystemModel->fileInfo(index);
if (fileSystemModel->previewableSuffixes().contains(fi.suffix())) {
QString filePath = fi.absoluteFilePath();
if (!filePath.isEmpty()) {
if (!m_fontPreviewTooltipBackend->isVisible()
|| m_fontPreviewTooltipBackend->path() != filePath) {
m_fontPreviewTooltipBackend->setPath(filePath);
m_fontPreviewTooltipBackend->setName(fi.fileName());
m_fontPreviewTooltipBackend->showTooltip();
} else {
m_fontPreviewTooltipBackend->reposition();
}
return true;
}
}
}
m_fontPreviewTooltipBackend->hideTooltip();
} else if (event->type() == QEvent::Leave) {
m_fontPreviewTooltipBackend->hideTooltip();
} else if (event->type() == QEvent::HoverMove) {
if (m_fontPreviewTooltipBackend->isVisible()) {
auto fileSystemModel = qobject_cast<CustomFileSystemModel *>(model());
Q_ASSERT(fileSystemModel);
auto *he = static_cast<QHoverEvent *>(event);
QModelIndex index = indexAt(he->pos());
if (index.isValid()) {
QFileInfo fi = fileSystemModel->fileInfo(index);
if (fi.absoluteFilePath() != m_fontPreviewTooltipBackend->path())
m_fontPreviewTooltipBackend->hideTooltip();
else
m_fontPreviewTooltipBackend->reposition();
}
}
}
return QListView::viewportEvent(event);
}
} // namespace QmlDesigner

View File

@@ -33,15 +33,22 @@ QT_END_NAMESPACE
namespace QmlDesigner {
class PreviewTooltipBackend;
class ImageCache;
class ItemLibraryResourceView : public QListView {
Q_OBJECT
public:
explicit ItemLibraryResourceView(QWidget *parent = nullptr);
explicit ItemLibraryResourceView(ImageCache &fontImageCache, QWidget *parent = nullptr);
void startDrag(Qt::DropActions supportedActions) override;
bool viewportEvent(QEvent *event) override;
private:
void addSizeAction(QActionGroup *group, const QString &text, int size, int iconSize);
std::unique_ptr<PreviewTooltipBackend> m_fontPreviewTooltipBackend;
};
} // namespace QmlDesigner

View File

@@ -30,6 +30,7 @@
#include <coreplugin/icore.h>
#include <imagecache.h>
#include <imagecache/imagecachecollector.h>
#include <imagecache/imagecachefontcollector.h>
#include <imagecache/imagecacheconnectionmanager.h>
#include <imagecache/imagecachegenerator.h>
#include <imagecache/imagecachestorage.h>
@@ -55,9 +56,12 @@ public:
ImageCacheStorage<Sqlite::Database> storage{database};
ImageCacheConnectionManager connectionManager;
ImageCacheCollector collector{connectionManager};
ImageCacheFontCollector fontCollector;
ImageCacheGenerator generator{collector, storage};
ImageCacheGenerator fontGenerator{fontCollector, storage};
TimeStampProvider timeStampProvider;
ImageCache cache{storage, generator, timeStampProvider};
ImageCache fontImageCache{storage, fontGenerator, timeStampProvider};
};
ItemLibraryView::ItemLibraryView(QObject* parent)
@@ -78,7 +82,7 @@ bool ItemLibraryView::hasWidget() const
WidgetInfo ItemLibraryView::widgetInfo()
{
if (m_widget.isNull()) {
m_widget = new ItemLibraryWidget{m_imageCacheData->cache};
m_widget = new ItemLibraryWidget{m_imageCacheData->cache, m_imageCacheData->fontImageCache};
m_widget->setImportsWidget(m_importManagerView->widgetInfo().widget);
}
@@ -155,7 +159,7 @@ void ItemLibraryView::importsChanged(const QList<Import> &addedImports, const QL
void ItemLibraryView::setResourcePath(const QString &resourcePath)
{
if (m_widget.isNull())
m_widget = new ItemLibraryWidget{m_imageCacheData->cache};
m_widget = new ItemLibraryWidget{m_imageCacheData->cache, m_imageCacheData->fontImageCache};
m_widget->setResourcePath(resourcePath);
}

View File

@@ -83,10 +83,10 @@ static QString propertyEditorResourcesPath() {
return Core::ICore::resourcePath() + QStringLiteral("/qmldesigner/propertyEditorQmlSources");
}
ItemLibraryWidget::ItemLibraryWidget(ImageCache &imageCache)
ItemLibraryWidget::ItemLibraryWidget(ImageCache &imageCache, ImageCache &fontImageCache)
: m_itemIconSize(24, 24)
, m_itemViewQuickWidget(new QQuickWidget(this))
, m_resourcesView(new ItemLibraryResourceView(this))
, m_resourcesView(new ItemLibraryResourceView(fontImageCache, this))
, m_importTagsWidget(new QWidget(this))
, m_addResourcesWidget(new QWidget(this))
, m_imageCache{imageCache}
@@ -115,12 +115,11 @@ ItemLibraryWidget::ItemLibraryWidget(ImageCache &imageCache)
m_previewTooltipBackend = std::make_unique<PreviewTooltipBackend>(m_imageCache);
m_itemViewQuickWidget->rootContext()->setContextProperty("tooltipBackend",
m_previewTooltipBackend.get());
m_itemViewQuickWidget->setClearColor(
Theme::getColor(Theme::Color::QmlDesigner_BackgroundColorDarkAlternate));
/* create Resources view and its model */
m_resourcesFileSystemModel = new CustomFileSystemModel(this);
m_resourcesFileSystemModel = new CustomFileSystemModel(fontImageCache, this);
m_resourcesView->setModel(m_resourcesFileSystemModel.data());
/* create image provider for loading item icons */

View File

@@ -69,7 +69,7 @@ class ItemLibraryWidget : public QFrame
};
public:
ItemLibraryWidget(ImageCache &imageCache);
ItemLibraryWidget(ImageCache &imageCache, ImageCache &fontImageCache);
~ItemLibraryWidget();
void setItemLibraryInfo(ItemLibraryInfo *itemLibraryInfo);

View File

@@ -36,28 +36,36 @@ PreviewImageTooltip::PreviewImageTooltip(QWidget *parent)
: QWidget(parent)
, m_ui(std::make_unique<Ui::PreviewImageTooltip>())
{
// setAttribute(Qt::WA_TransparentForMouseEvents);
setWindowFlags(Qt::ToolTip);
setWindowFlags(Qt::FramelessWindowHint | Qt::Tool | Qt::WindowTransparentForInput
| Qt::WindowStaysOnTopHint | Qt::WindowDoesNotAcceptFocus);
m_ui->setupUi(this);
m_ui->nameLabel->setElideMode(Qt::ElideLeft);
m_ui->pathLabel->setElideMode(Qt::ElideLeft);
m_ui->infoLabel->setElideMode(Qt::ElideLeft);
setStyleSheet(QString("QWidget { background-color: %1 }").arg(Utils::creatorTheme()->color(Utils::Theme::BackgroundColorNormal).name()));
}
PreviewImageTooltip::~PreviewImageTooltip() = default;
void PreviewImageTooltip::setComponentPath(const QString &path)
void PreviewImageTooltip::setName(const QString &name)
{
m_ui->componentPathLabel->setText(path);
m_ui->nameLabel->setText(name);
}
void PreviewImageTooltip::setComponentName(const QString &name)
void PreviewImageTooltip::setPath(const QString &path)
{
m_ui->componentNameLabel->setText(name);
m_ui->pathLabel->setText(path);
}
void PreviewImageTooltip::setInfo(const QString &info)
{
m_ui->infoLabel->setText(info);
}
void PreviewImageTooltip::setImage(const QImage &image)
{
resize(image.width() + 20 + m_ui->componentNameLabel->width(),
std::max(image.height() + 20, height()));
m_ui->imageLabel->setPixmap(QPixmap::fromImage({image}));
m_ui->imageLabel->setPixmap(QPixmap::fromImage({image}).scaled(m_ui->imageLabel->width(),
m_ui->imageLabel->height(),
Qt::KeepAspectRatio));
}
}

View File

@@ -43,8 +43,9 @@ public:
explicit PreviewImageTooltip(QWidget *parent = {});
~PreviewImageTooltip();
void setComponentPath(const QString &path);
void setComponentName(const QString &name);
void setName(const QString &name);
void setPath(const QString &path);
void setInfo(const QString &info);
void setImage(const QImage &pixmap);
private:

View File

@@ -6,20 +6,20 @@
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
<width>651</width>
<height>318</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>200</width>
<height>200</height>
<width>300</width>
<height>140</height>
</size>
</property>
<property name="maximumSize">
@@ -64,37 +64,36 @@
<property name="lineWidth">
<number>1</number>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="1">
<widget class="QLabel" name="componentPathLabel">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item>
<widget class="QLabel" name="imageLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
<width>300</width>
<height>300</height>
</size>
</property>
<property name="text">
<string notr="true"/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::NoTextInteraction</set>
</property>
</widget>
</item>
<item row="0" column="0" rowspan="2">
<widget class="QLabel" name="imageLabel">
<property name="frameShape">
<enum>QFrame::Box</enum>
</property>
@@ -102,43 +101,89 @@
<enum>QFrame::Plain</enum>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="Utils::ElidingLabel" name="componentNameLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="font">
<font>
<pointsize>12</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string notr="true"/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::NoTextInteraction</set>
</property>
<item>
<widget class="QWidget" name="widget" native="true">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="Utils::ElidingLabel" name="nameLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="text">
<string notr="true">&lt;name label&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="textInteractionFlags">
<set>Qt::NoTextInteraction</set>
</property>
</widget>
</item>
<item>
<widget class="Utils::ElidingLabel" name="pathLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="text">
<string notr="true">&lt;path label&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="textInteractionFlags">
<set>Qt::NoTextInteraction</set>
</property>
</widget>
</item>
<item>
<widget class="Utils::ElidingLabel" name="infoLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="text">
<string notr="true">&lt;info label&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="textInteractionFlags">
<set>Qt::NoTextInteraction</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>

View File

@@ -32,6 +32,7 @@
#include <QApplication>
#include <QMetaObject>
#include <QScreen>
namespace QmlDesigner {
@@ -46,28 +47,25 @@ PreviewTooltipBackend::~PreviewTooltipBackend()
void PreviewTooltipBackend::showTooltip()
{
if (m_componentPath.isEmpty())
return;
m_tooltip = std::make_unique<PreviewImageTooltip>();
m_tooltip->setComponentName(m_componentName);
m_tooltip->setComponentPath(m_componentPath);
m_tooltip->setName(m_name);
m_tooltip->setPath(m_path);
m_tooltip->setInfo(m_info);
m_cache.requestImage(
m_componentPath,
m_path,
[tooltip = QPointer<PreviewImageTooltip>(m_tooltip.get())](const QImage &image) {
QMetaObject::invokeMethod(tooltip, [tooltip, image] {
if (tooltip)
tooltip->setImage(image);
});
},
[] {});
[] {},
m_state
);
auto mousePosition = QCursor::pos();
mousePosition += {20, 20};
m_tooltip->move(mousePosition);
reposition();
m_tooltip->show();
}
@@ -79,30 +77,94 @@ void PreviewTooltipBackend::hideTooltip()
m_tooltip.reset();
}
QString QmlDesigner::PreviewTooltipBackend::componentPath() const
bool PreviewTooltipBackend::isVisible() const
{
return m_componentPath;
if (m_tooltip)
return m_tooltip->isVisible();
return false;
}
void QmlDesigner::PreviewTooltipBackend::setComponentPath(const QString &path)
void PreviewTooltipBackend::reposition()
{
m_componentPath = path;
if (m_tooltip) {
// By default tooltip is placed right and below the cursor, but if the screen
// doesn't have sufficient space in that direction, position is adjusted.
// It is assumed that some diagonal direction will have enough space.
const QPoint mousePos = QCursor::pos();
QScreen *screen = qApp->screenAt(mousePos);
QRect tipRect = m_tooltip->geometry();
QPoint offset(10, 5);
QPoint pos = mousePos + offset;
if (screen) {
QRect rect = screen->geometry();
tipRect.moveTo(pos);
if (!rect.contains(tipRect)) {
pos = mousePos + QPoint(-offset.x() - m_tooltip->size().width(),
offset.y());
tipRect.moveTo(pos);
if (!rect.contains(tipRect)) {
pos = mousePos + QPoint(offset.x(), -offset.y() - m_tooltip->size().height());
tipRect.moveTo(pos);
if (!rect.contains(tipRect)) {
pos = mousePos + QPoint(-offset.x() - m_tooltip->size().width(),
-offset.y() - m_tooltip->size().height());
tipRect.moveTo(pos);
}
}
}
}
if (m_componentPath != path)
emit componentPathChanged();
m_tooltip->move(pos);
}
}
QString QmlDesigner::PreviewTooltipBackend::componentName() const
QString PreviewTooltipBackend::name() const
{
return m_componentName;
return m_name;
}
void QmlDesigner::PreviewTooltipBackend::setComponentName(const QString &name)
void PreviewTooltipBackend::setName(const QString &name)
{
m_componentName = name;
m_name = name;
if (m_name != name)
emit nameChanged();
}
if (m_componentName != name)
emit componentNameChanged();
QString PreviewTooltipBackend::path() const
{
return m_path;
}
void PreviewTooltipBackend::setPath(const QString &path)
{
m_path = path;
if (m_path != path)
emit pathChanged();
}
QString PreviewTooltipBackend::info() const
{
return m_info;
}
void PreviewTooltipBackend::setInfo(const QString &info)
{
m_info = info;
if (m_info != info)
emit infoChanged();
}
QString PreviewTooltipBackend::state() const
{
return m_state;
}
// Sets the imageCache state hint. Valid content depends on image cache collector used.
void PreviewTooltipBackend::setState(const QString &state)
{
m_state = state;
if (m_state != state)
emit stateChanged();
}
} // namespace QmlDesigner

View File

@@ -39,8 +39,10 @@ class PreviewTooltipBackend : public QObject
{
Q_OBJECT
Q_PROPERTY(QString componentPath READ componentPath WRITE setComponentPath NOTIFY componentPathChanged)
Q_PROPERTY(QString componentName READ componentName WRITE setComponentName NOTIFY componentNameChanged)
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged)
Q_PROPERTY(QString info READ info WRITE setInfo NOTIFY infoChanged)
Q_PROPERTY(QString state READ state WRITE setState NOTIFY stateChanged)
public:
PreviewTooltipBackend(ImageCache &cache);
@@ -48,20 +50,30 @@ public:
Q_INVOKABLE void showTooltip();
Q_INVOKABLE void hideTooltip();
Q_INVOKABLE void reposition();
QString componentPath() const;
void setComponentPath(const QString &path);
QString name() const;
void setName(const QString &name);
QString path() const;
void setPath(const QString &path);
QString info() const;
void setInfo(const QString &info);
QString state() const;
void setState(const QString &state);
QString componentName() const;
void setComponentName(const QString &path);
bool isVisible() const;
signals:
void componentPathChanged();
void componentNameChanged();
void nameChanged();
void pathChanged();
void infoChanged();
void stateChanged();
private:
QString m_componentPath;
QString m_componentName;
QString m_name;
QString m_path;
QString m_info;
QString m_state;
std::unique_ptr<PreviewImageTooltip> m_tooltip;
ImageCache &m_cache;
};

View File

@@ -15,6 +15,7 @@ include (../../../../share/qtcreator/qml/qmlpuppet/types/types.pri)
SOURCES += $$PWD/model/abstractview.cpp \
$$PWD/imagecache/imagecachecollector.cpp \
$$PWD/imagecache/imagecachefontcollector.cpp \
$$PWD/model/rewriterview.cpp \
$$PWD/model/documentmessage.cpp \
$$PWD/metainfo/metainfo.cpp \
@@ -95,6 +96,7 @@ SOURCES += $$PWD/model/abstractview.cpp \
HEADERS += $$PWD/include/qmldesignercorelib_global.h \
$$PWD/imagecache/imagecachecollector.h \
$$PWD/imagecache/imagecachefontcollector.h \
$$PWD/include/abstractview.h \
$$PWD/include/nodeinstanceview.h \
$$PWD/include/rewriterview.h \

View File

@@ -0,0 +1,126 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** 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 The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "imagecachefontcollector.h"
#include <theme.h>
#include <QtGui/qrawfont.h>
#include <QtGui/qpainter.h>
#include <QtGui/qimage.h>
#include <QtGui/qfontdatabase.h>
#include <QtGui/qfontmetrics.h>
#include <QtCore/qfileinfo.h>
namespace QmlDesigner {
ImageCacheFontCollector::ImageCacheFontCollector() = default;
ImageCacheFontCollector::~ImageCacheFontCollector() = default;
QByteArray fileToByteArray(QString const &filename)
{
QFile file(filename);
QFileInfo fileInfo(file);
if (fileInfo.exists() && file.open(QFile::ReadOnly))
return file.readAll();
return {};
}
void ImageCacheFontCollector::start(Utils::SmallStringView name,
Utils::SmallStringView state,
CaptureCallback captureCallback,
AbortCallback abortCallback)
{
// State contains size, text color, and sample text
QStringList hints = QString(state).split('@');
int dim(300);
if (hints.size() >= 1) {
bool ok = false;
int newDim = QString(hints[0]).toInt(&ok);
if (ok)
dim = newDim;
}
QColor textColor(Theme::getColor(Theme::DStextColor));
if (hints.size() >= 2)
textColor.setNamedColor(hints[1]);
QString text = hints.size() >= 3 ? hints[2] : "Abc";
QSize size(dim, dim);
QRect rect({0, 0}, size);
QByteArray fontData(fileToByteArray(QString(name)));
if (!fontData.isEmpty()) {
int fontId = QFontDatabase::addApplicationFontFromData(fontData);
if (fontId != -1) {
QRawFont rawFont(fontData, 10.); // Pixel size is irrelevant, we only need style/weight
const QStringList families = QFontDatabase::applicationFontFamilies(fontId);
if (!families.isEmpty()) {
QString fontFamily = families.first();
if (text.contains("%1"))
text = text.arg(fontFamily);
QFont font(fontFamily);
font.setStyle(rawFont.style());
font.setStyleName(rawFont.styleName());
font.setWeight(rawFont.weight());
QImage image(size, QImage::Format_ARGB32);
image.fill(Qt::transparent);
int pixelSize(200);
int flags = Qt::AlignCenter;
while (pixelSize >= 2) {
font.setPixelSize(pixelSize);
QFontMetrics fm(font, &image);
QRect bounds = fm.boundingRect(rect, flags, text);
if (bounds.width() < rect.width() && bounds.height() < rect.height()) {
break;
} else {
int newPixelSize = pixelSize - 1;
if (bounds.width() >= rect.width())
newPixelSize = int(qreal(pixelSize) * qreal(rect.width()) / qreal(bounds.width()));
else if (bounds.height() >= rect.height())
newPixelSize = int(qreal(pixelSize) * qreal(rect.height()) / qreal(bounds.height()));
if (newPixelSize < pixelSize)
pixelSize = newPixelSize;
else
--pixelSize;
}
}
QPainter painter(&image);
painter.setPen(textColor);
painter.setFont(font);
painter.drawText(rect, flags, text);
captureCallback(std::move(image));
return;
}
QFontDatabase::removeApplicationFont(fontId);
}
}
abortCallback();
}
} // namespace QmlDesigner

View File

@@ -0,0 +1,45 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** 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 The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "imagecachecollectorinterface.h"
namespace QmlDesigner {
class ImageCacheFontCollector final : public ImageCacheCollectorInterface
{
public:
ImageCacheFontCollector();
~ImageCacheFontCollector();
void start(Utils::SmallStringView filePath,
Utils::SmallStringView state,
CaptureCallback captureCallback,
AbortCallback abortCallback) override;
};
} // namespace QmlDesigner

View File

@@ -418,6 +418,8 @@ Project {
"include/imagecacheinterface.h",
"imagecache/imagecachecollector.cpp",
"imagecache/imagecachecollector.h",
"imagecache/imagecachefontcollector.cpp",
"imagecache/imagecachefontcollector.h",
"imagecache/imagecache.cpp",
"imagecache/imagecachecollectorinterface.h",
"imagecache/imagecacheconnectionmanager.cpp",