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(); QSize previewImageSize = rootNodeInstance.boundingRect().size().toSize();
if (previewImageSize.isEmpty()) if (previewImageSize.isEmpty())
previewImageSize = {640, 480}; previewImageSize = {300, 300};
if (previewImageSize.width() > 800 || previewImageSize.height() > 800) if (previewImageSize.width() > 300 || previewImageSize.height() > 300)
previewImageSize.scale({800, 800}, Qt::KeepAspectRatio); previewImageSize.scale({300, 300}, Qt::KeepAspectRatio);
QImage previewImage = rootNodeInstance.renderPreviewImage(previewImageSize); QImage previewImage = rootNodeInstance.renderPreviewImage(previewImageSize);

View File

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

View File

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

View File

@@ -26,6 +26,7 @@
#include "customfilesystemmodel.h" #include "customfilesystemmodel.h"
#include <theme.h> #include <theme.h>
#include <imagecache.h>
#include <utils/filesystemwatcher.h> #include <utils/filesystemwatcher.h>
@@ -37,8 +38,10 @@
#include <QImageReader> #include <QImageReader>
#include <QPainter> #include <QPainter>
#include <QRawFont> #include <QRawFont>
#include <QGlyphRun> #include <QPair>
#include <qmath.h> #include <qmath.h>
#include <condition_variable>
#include <mutex>
namespace QmlDesigner { 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())); 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) QString fontFamily(const QFileInfo &info)
{ {
QRawFont font(info.absoluteFilePath(), 10); QRawFont font(info.absoluteFilePath(), 10);
@@ -157,21 +98,27 @@ QString fontFamily(const QFileInfo &info)
class ItemLibraryFileIconProvider : public QFileIconProvider class ItemLibraryFileIconProvider : public QFileIconProvider
{ {
public: public:
ItemLibraryFileIconProvider() = default; ItemLibraryFileIconProvider(ImageCache &fontImageCache)
: QFileIconProvider()
, m_fontImageCache(fontImageCache)
{
}
QIcon icon( const QFileInfo & info ) const override QIcon icon( const QFileInfo & info ) const override
{ {
QIcon icon; QIcon icon;
const QString suffix = info.suffix(); const QString suffix = info.suffix();
const QString filePath = info.absoluteFilePath();
if (supportedFontSuffixes().contains(suffix))
return generateFontIcons(filePath);
for (auto iconSize : iconSizes) { for (auto iconSize : iconSizes) {
// Provide icon depending on suffix // Provide icon depending on suffix
QPixmap pixmap; QPixmap pixmap;
if (supportedImageSuffixes().contains(suffix)) if (supportedImageSuffixes().contains(suffix))
pixmap.load(info.absoluteFilePath()); pixmap.load(filePath);
else if (supportedFontSuffixes().contains(suffix))
pixmap = generateFontImage(info, iconSize);
else if (supportedAudioSuffixes().contains(suffix)) else if (supportedAudioSuffixes().contains(suffix))
pixmap = defaultPixmapForType("sound", iconSize); pixmap = defaultPixmapForType("sound", iconSize);
else if (supportedShaderSuffixes().contains(suffix)) else if (supportedShaderSuffixes().contains(suffix))
@@ -189,6 +136,61 @@ public:
return icon; 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 // Generated icon sizes should contain all ItemLibraryResourceView needed icon sizes, and their
// x2 versions for HDPI sceens // x2 versions for HDPI sceens
QList<QSize> iconSizes = {{384, 384}, {192, 192}, // Large QList<QSize> iconSizes = {{384, 384}, {192, 192}, // Large
@@ -197,13 +199,15 @@ public:
{48, 48}, // Small {48, 48}, // Small
{64, 64}, {32, 32}}; // List {64, 64}, {32, 32}}; // List
ImageCache &m_fontImageCache;
}; };
CustomFileSystemModel::CustomFileSystemModel(QObject *parent) : QAbstractListModel(parent) CustomFileSystemModel::CustomFileSystemModel(ImageCache &fontImageCache, QObject *parent)
, m_fileSystemModel(new QFileSystemModel(this)) : QAbstractListModel(parent)
, m_fileSystemWatcher(new Utils::FileSystemWatcher(this)) , 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] { connect(m_fileSystemWatcher, &Utils::FileSystemWatcher::directoryChanged, [this] {
updatePath(m_fileSystemModel->rootPath()); updatePath(m_fileSystemModel->rootPath());
@@ -345,6 +349,20 @@ const QSet<QString> &CustomFileSystemModel::supportedSuffixes() const
return allSuffixes; 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) void CustomFileSystemModel::appendIfNotFiltered(const QString &file)
{ {
if (filterMetaIcons(file)) if (filterMetaIcons(file))

View File

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

View File

@@ -27,6 +27,10 @@
#include "customfilesystemmodel.h" #include "customfilesystemmodel.h"
#include <theme.h>
#include <imagecache.h>
#include <previewtooltip/previewtooltipbackend.h>
#include <QAction> #include <QAction>
#include <QActionGroup> #include <QActionGroup>
#include <QDebug> #include <QDebug>
@@ -35,6 +39,7 @@
#include <QMimeData> #include <QMimeData>
#include <QPainter> #include <QPainter>
#include <QPixmap> #include <QPixmap>
#include <QtGui/qevent.h>
#include <QProxyStyle> #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) QListView(parent)
{ {
setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
@@ -102,6 +107,20 @@ ItemLibraryResourceView::ItemLibraryResourceView(QWidget *parent) :
defaultAction->toggle(); defaultAction->toggle();
addActions(actionGroup->actions()); 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 */) void ItemLibraryResourceView::startDrag(Qt::DropActions /* supportedActions */)
@@ -136,5 +155,51 @@ void ItemLibraryResourceView::startDrag(Qt::DropActions /* supportedActions */)
drag->exec(); 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 } // namespace QmlDesigner

View File

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

View File

@@ -30,6 +30,7 @@
#include <coreplugin/icore.h> #include <coreplugin/icore.h>
#include <imagecache.h> #include <imagecache.h>
#include <imagecache/imagecachecollector.h> #include <imagecache/imagecachecollector.h>
#include <imagecache/imagecachefontcollector.h>
#include <imagecache/imagecacheconnectionmanager.h> #include <imagecache/imagecacheconnectionmanager.h>
#include <imagecache/imagecachegenerator.h> #include <imagecache/imagecachegenerator.h>
#include <imagecache/imagecachestorage.h> #include <imagecache/imagecachestorage.h>
@@ -55,9 +56,12 @@ public:
ImageCacheStorage<Sqlite::Database> storage{database}; ImageCacheStorage<Sqlite::Database> storage{database};
ImageCacheConnectionManager connectionManager; ImageCacheConnectionManager connectionManager;
ImageCacheCollector collector{connectionManager}; ImageCacheCollector collector{connectionManager};
ImageCacheFontCollector fontCollector;
ImageCacheGenerator generator{collector, storage}; ImageCacheGenerator generator{collector, storage};
ImageCacheGenerator fontGenerator{fontCollector, storage};
TimeStampProvider timeStampProvider; TimeStampProvider timeStampProvider;
ImageCache cache{storage, generator, timeStampProvider}; ImageCache cache{storage, generator, timeStampProvider};
ImageCache fontImageCache{storage, fontGenerator, timeStampProvider};
}; };
ItemLibraryView::ItemLibraryView(QObject* parent) ItemLibraryView::ItemLibraryView(QObject* parent)
@@ -78,7 +82,7 @@ bool ItemLibraryView::hasWidget() const
WidgetInfo ItemLibraryView::widgetInfo() WidgetInfo ItemLibraryView::widgetInfo()
{ {
if (m_widget.isNull()) { 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); 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) void ItemLibraryView::setResourcePath(const QString &resourcePath)
{ {
if (m_widget.isNull()) 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); m_widget->setResourcePath(resourcePath);
} }

View File

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

View File

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

View File

@@ -36,28 +36,36 @@ PreviewImageTooltip::PreviewImageTooltip(QWidget *parent)
: QWidget(parent) : QWidget(parent)
, m_ui(std::make_unique<Ui::PreviewImageTooltip>()) , m_ui(std::make_unique<Ui::PreviewImageTooltip>())
{ {
// setAttribute(Qt::WA_TransparentForMouseEvents); setWindowFlags(Qt::FramelessWindowHint | Qt::Tool | Qt::WindowTransparentForInput
setWindowFlags(Qt::ToolTip); | Qt::WindowStaysOnTopHint | Qt::WindowDoesNotAcceptFocus);
m_ui->setupUi(this); 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())); setStyleSheet(QString("QWidget { background-color: %1 }").arg(Utils::creatorTheme()->color(Utils::Theme::BackgroundColorNormal).name()));
} }
PreviewImageTooltip::~PreviewImageTooltip() = default; 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) void PreviewImageTooltip::setImage(const QImage &image)
{ {
resize(image.width() + 20 + m_ui->componentNameLabel->width(), m_ui->imageLabel->setPixmap(QPixmap::fromImage({image}).scaled(m_ui->imageLabel->width(),
std::max(image.height() + 20, height())); m_ui->imageLabel->height(),
m_ui->imageLabel->setPixmap(QPixmap::fromImage({image})); Qt::KeepAspectRatio));
} }
} }

View File

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

View File

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

View File

@@ -32,6 +32,7 @@
#include <QApplication> #include <QApplication>
#include <QMetaObject> #include <QMetaObject>
#include <QScreen>
namespace QmlDesigner { namespace QmlDesigner {
@@ -46,28 +47,25 @@ PreviewTooltipBackend::~PreviewTooltipBackend()
void PreviewTooltipBackend::showTooltip() void PreviewTooltipBackend::showTooltip()
{ {
if (m_componentPath.isEmpty())
return;
m_tooltip = std::make_unique<PreviewImageTooltip>(); m_tooltip = std::make_unique<PreviewImageTooltip>();
m_tooltip->setComponentName(m_componentName); m_tooltip->setName(m_name);
m_tooltip->setComponentPath(m_componentPath); m_tooltip->setPath(m_path);
m_tooltip->setInfo(m_info);
m_cache.requestImage( m_cache.requestImage(
m_componentPath, m_path,
[tooltip = QPointer<PreviewImageTooltip>(m_tooltip.get())](const QImage &image) { [tooltip = QPointer<PreviewImageTooltip>(m_tooltip.get())](const QImage &image) {
QMetaObject::invokeMethod(tooltip, [tooltip, image] { QMetaObject::invokeMethod(tooltip, [tooltip, image] {
if (tooltip) if (tooltip)
tooltip->setImage(image); tooltip->setImage(image);
}); });
}, },
[] {}); [] {},
m_state
);
auto mousePosition = QCursor::pos(); reposition();
mousePosition += {20, 20};
m_tooltip->move(mousePosition);
m_tooltip->show(); m_tooltip->show();
} }
@@ -79,30 +77,94 @@ void PreviewTooltipBackend::hideTooltip()
m_tooltip.reset(); 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) m_tooltip->move(pos);
emit componentPathChanged(); }
} }
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) QString PreviewTooltipBackend::path() const
emit componentNameChanged(); {
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 } // namespace QmlDesigner

View File

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

View File

@@ -15,6 +15,7 @@ include (../../../../share/qtcreator/qml/qmlpuppet/types/types.pri)
SOURCES += $$PWD/model/abstractview.cpp \ SOURCES += $$PWD/model/abstractview.cpp \
$$PWD/imagecache/imagecachecollector.cpp \ $$PWD/imagecache/imagecachecollector.cpp \
$$PWD/imagecache/imagecachefontcollector.cpp \
$$PWD/model/rewriterview.cpp \ $$PWD/model/rewriterview.cpp \
$$PWD/model/documentmessage.cpp \ $$PWD/model/documentmessage.cpp \
$$PWD/metainfo/metainfo.cpp \ $$PWD/metainfo/metainfo.cpp \
@@ -95,6 +96,7 @@ SOURCES += $$PWD/model/abstractview.cpp \
HEADERS += $$PWD/include/qmldesignercorelib_global.h \ HEADERS += $$PWD/include/qmldesignercorelib_global.h \
$$PWD/imagecache/imagecachecollector.h \ $$PWD/imagecache/imagecachecollector.h \
$$PWD/imagecache/imagecachefontcollector.h \
$$PWD/include/abstractview.h \ $$PWD/include/abstractview.h \
$$PWD/include/nodeinstanceview.h \ $$PWD/include/nodeinstanceview.h \
$$PWD/include/rewriterview.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", "include/imagecacheinterface.h",
"imagecache/imagecachecollector.cpp", "imagecache/imagecachecollector.cpp",
"imagecache/imagecachecollector.h", "imagecache/imagecachecollector.h",
"imagecache/imagecachefontcollector.cpp",
"imagecache/imagecachefontcollector.h",
"imagecache/imagecache.cpp", "imagecache/imagecache.cpp",
"imagecache/imagecachecollectorinterface.h", "imagecache/imagecachecollectorinterface.h",
"imagecache/imagecacheconnectionmanager.cpp", "imagecache/imagecacheconnectionmanager.cpp",