Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ build
dep-libs
deps
deps-install

.cache
compile_commands.json
4 changes: 3 additions & 1 deletion lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ set(SOURCES
src/Scene/Qt/FindQtItem.h
src/Scene/Qt/QtEvents.cpp
src/Scene/Qt/QtEvents.h
src/Scene/Qt/QtEventFilter.cpp
src/Scene/Qt/QtEventFilter.h
src/Scene/Qt/QtItem.cpp
src/Scene/Qt/QtItem.h
src/Scene/Qt/QtItemTools.cpp
Expand All @@ -118,7 +120,7 @@ source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX source FILES ${SOURCES})
#
# Qt MOC Files
#
cmake_language(CALL "qt${SPIX_QT_MAJOR}_wrap_cpp" MOC_FILES "include/Spix/QtQmlBot.h")
cmake_language(CALL "qt${SPIX_QT_MAJOR}_wrap_cpp" MOC_FILES "include/Spix/QtQmlBot.h" "src/Scene/Qt/QtEventFilter.h")


#
Expand Down
39 changes: 39 additions & 0 deletions lib/src/Scene/Qt/QtEventFilter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#include "QtEventFilter.h"
#include <QDebug>
#include <QEvent>



namespace spix{

QtEventFilter::QtEventFilter(QObject* parent) : QObject(parent) {}

bool QtEventFilter::eventFilter(QObject* obj, QEvent* e){
if(e->type() == QEvent::KeyPress) {
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(e);

if(keyEvent->modifiers() == QFlags<Qt::KeyboardModifier>(Qt::ShiftModifier | Qt::ControlModifier)) {
emit pickerModeEntered(keyEvent);
return false;
}
} else if(e->type() == QEvent::KeyRelease) {
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(e);

if(keyEvent->key() == Qt::Key::Key_Shift || keyEvent->key() == Qt::Key::Key_Control){
emit pickerModeExited(keyEvent);
return false;
}
} else if(e->type() == QEvent::MouseButtonPress) {
QMouseEvent* mouseClick = static_cast<QMouseEvent*>(e);
if(mouseClick->modifiers() == QFlags<Qt::KeyboardModifier>(Qt::ShiftModifier | Qt::ControlModifier)) {
emit pickClick(mouseClick);
return true;
}
return false;
}

return QObject::eventFilter(obj, e);
}

} // namespace spix

28 changes: 28 additions & 0 deletions lib/src/Scene/Qt/QtEventFilter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#pragma once

#include <QObject>
#include <QKeyEvent>
#include <QMouseEvent>



namespace spix {

class QtEventFilter : public QObject {

Q_OBJECT

public:
QtEventFilter(QObject* parent);

signals:
void pickerModeEntered(QKeyEvent* e);
void pickerModeExited(QKeyEvent* e);
void pickClick(QMouseEvent* e);

protected:
bool eventFilter(QObject* obj, QEvent* e) override;

};

} // namespace spix
226 changes: 226 additions & 0 deletions lib/src/Scene/Qt/QtScene.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

#include "QtScene.h"
#include "FindQtItem.h"
#include "QtEventFilter.h"
#include "Scene/Item.h"

#include <Scene/Qt/QtItem.h>
#include <Scene/Qt/QtItemTools.h>
Expand All @@ -17,9 +19,156 @@
#include <QObject>
#include <QQuickItem>
#include <QQuickWindow>
#include <algorithm>
#include <qglobal.h>


namespace {

QString GetObjectFinalName(QObject* obj, bool top){
auto token = spix::qt::GetObjectName(obj);

if(obj->inherits("QQuickText")){
auto var = obj->property("text");
if(!var.isValid()){
return token;
}
// adds quotes around the text
auto text = "\"" + var.toString() + "\"";
if(top) return text;
}

// for certain types we want to return the text
// not adding in now, but can add in later
// if(token.contains("Button")) return text;
// else if(token.contains("Label")) return text;

return token;
}

spix::ItemPath ItemPathForObject(QObject* obj){
auto currentItem = obj;
QString path = "";

while(currentItem != nullptr) {
auto token = spix::qt::GetObjectName(currentItem);
int sameNameNumber = 0;

const auto currentQuickItem = qobject_cast<QQuickItem*>(currentItem);

if(currentItem->parent() != nullptr){
auto siblings = currentItem->parent()->children();
for(const auto child : siblings) {
sameNameNumber += (token == spix::qt::GetObjectName(child));
}
}else if(currentQuickItem != nullptr && currentQuickItem->parentItem() != nullptr) {
auto siblings = currentQuickItem->parentItem()->children();
for(const auto child : siblings) {
sameNameNumber += (token == spix::qt::GetObjectName(child));
}
}
if((currentItem->parent() == nullptr)
&& (currentQuickItem != nullptr && currentQuickItem->parentItem() != nullptr)) {
currentItem = currentQuickItem->parentItem();
} else {
currentItem = currentItem->parent();
}

if (sameNameNumber > 1) {
continue;
}

path = token + "/" + path;
}

return spix::ItemPath(path.toStdString());
}

} // namespace


namespace spix {


QtScene::QtScene() {
m_filter = new QtEventFilter(qGuiApp);

QObject::connect(qGuiApp, &QGuiApplication::focusWindowChanged, qGuiApp, [this](QWindow* window) {
if(!m_eventFilterInstalled) {
m_eventFilterInstalled = true;
window->installEventFilter(m_filter);

QObject::connect(m_filter, &QtEventFilter::pickerModeEntered, m_filter, []() {
QGuiApplication::setOverrideCursor(QCursor(Qt::CrossCursor));
});
QObject::connect(m_filter, &QtEventFilter::pickerModeExited, m_filter, []() {
QGuiApplication::restoreOverrideCursor();
});

auto quickWindow = qobject_cast<QQuickWindow*>(window);
QObject::connect(m_filter, &QtEventFilter::pickClick, m_filter, [this, quickWindow](QMouseEvent* e){
int bestCandidate = -1;
bool parentIsGoodCandidate = true;
auto objects = recursiveItemsAt(quickWindow->contentItem(), e->pos(), bestCandidate, parentIsGoodCandidate);

if(objects.size() == 1) {
auto quickItem = qobject_cast<QQuickItem*>(objects[0]);
// quickItem->setOpacity(0.5);
auto itemPath = ItemPathForObject(quickItem);

auto newPath = shortPath(itemPath, quickItem);
// prints the path and surrounds it with quotes
qDebug().setAutoInsertSpaces(false);
auto pathString = "\'" + newPath.string() + "\'";
qDebug() << "Path: " << pathString.c_str();
}
});
}
});
}

QtScene::~QtScene(){
delete m_filter;
}

spix::ItemPath QtScene::shortPath(ItemPath oldPath, QQuickItem* oldItem){
spix::ItemPath newPath;

auto components = oldPath.components();

// add the first
if(components.size() > 1){
newPath = spix::ItemPath(components.at(0).string());
} else {
return oldPath;
}

for(auto p = components.begin() + 1; p != components.end() - 1; p++){
// iterate through the middle of the components
auto next = p + 1;

// item
auto path = spix::ItemPath(newPath.string() + "/" + p->string() + "/" + next->string());
auto shortPath = spix::ItemPath(newPath.string() + "/" + next->string());

auto item = qt::GetQQuickItemAtPath(path);
auto shortItem = qt::GetQQuickItemAtPath(shortPath);


if(shortItem == nullptr || item != shortItem){
// need to add this item to path
newPath = spix::ItemPath(newPath.string() + "/" + p->string());
}
// otherwise continue without this item
}

// add the last item
auto name = GetObjectFinalName(oldItem, true);
newPath = spix::ItemPath(newPath.string() + "/" + name.toStdString());

return newPath;
}

std::unique_ptr<Item> QtScene::itemAtPath(const ItemPath& path)
{
// Find item within window
Expand Down Expand Up @@ -87,4 +236,81 @@ std::string QtScene::takeScreenshotAsBase64(const ItemPath& targetItem)
return byteArray.toBase64().toStdString();
}

bool QtScene::itemHasContents(QQuickItem* item) {
return item->flags().testFlag(QQuickItem::ItemHasContents);
}
bool QtScene::isGoodCandidateItem(QQuickItem* item, bool ignoreItemHasContents = false) {
return !(
!item->isVisible()
|| qFuzzyCompare(item->opacity() + qreal(1.0), qreal(1.0))
|| (!ignoreItemHasContents && !itemHasContents(item)));
}


QRectF QtScene::combinedChildrenRect(QQuickItem* obj) const {
auto rect = obj->childrenRect();

for(const auto child : obj->childItems()) {
auto childRect = child->childrenRect();
QPointF childGlobalPos = child->mapToScene(QPointF(0, 0));
QPointF localChildPos = obj->mapFromScene(childGlobalPos);
childRect.moveTopLeft((localChildPos.toPoint()));
rect = rect.united(childRect);
}

return rect;
}

ObjectIds QtScene::recursiveItemsAt(QQuickItem* parent, const QPointF& pos, int& bestCandidate, bool parentIsGoodCandidate){
Q_ASSERT(parent);
ObjectIds objects;

bestCandidate = -1;
if(parentIsGoodCandidate) {
parentIsGoodCandidate = isGoodCandidateItem(parent, true);
}

auto childItems = parent->childItems();
std::stable_sort(childItems.begin(), childItems.end(), [](QQuickItem* a, QQuickItem* b){
return a->z() < b->z();
});


for(int i = childItems.size() - 1; i >= 0; i--) {
const auto child = childItems.at(i);
const auto requestedPoint = parent->mapToItem(child, pos);

if(!child->childItems().isEmpty() && (child->contains(requestedPoint) || combinedChildrenRect(child).contains(requestedPoint))){
const int count = objects.count();
int bc = 0;
objects << recursiveItemsAt(child, requestedPoint, bc, parentIsGoodCandidate);
if(bestCandidate == -1 && parentIsGoodCandidate && bc != -1){
bestCandidate = count + bc;
}
}

if(child->contains(requestedPoint)) {
if(bestCandidate == -1 && parentIsGoodCandidate && isGoodCandidateItem(child)){
bestCandidate = objects.count();
}
objects << child;
}
if(bestCandidate != -1) break;
}

if(bestCandidate == -1 && parentIsGoodCandidate && itemHasContents(parent)){
bestCandidate = objects.count();
}

objects << parent;

if(bestCandidate != -1) {
objects = ObjectIds() << objects[bestCandidate];
bestCandidate = 0;
}

return objects;
}


} // namespace spix
14 changes: 14 additions & 0 deletions lib/src/Scene/Qt/QtScene.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#pragma once

#include <QByteArray>
#include <QQuickItem>
#include <Scene/Qt/QtEvents.h>
#include <Scene/Scene.h>

Expand All @@ -16,22 +17,35 @@ class QQuickWindow;

namespace spix {

class QtEventFilter;
class ItemPath;

using ObjectIds = QVector<QObject*>;

class QtScene : public Scene {
public:
QtScene();
~QtScene();

// Request objects
std::unique_ptr<Item> itemAtPath(const ItemPath& path) override;
ItemPath shortPath(ItemPath oldPath, QQuickItem* itemPath);

// Events
Events& events() override;

// Tasks
void takeScreenshot(const ItemPath& targetItem, const std::string& filePath) override;
std::string takeScreenshotAsBase64(const ItemPath& targetItem) override;
ObjectIds recursiveItemsAt(QQuickItem* parent, const QPointF& pos, int& bestCandidate, bool parentIsGoodCandidate);
QRectF combinedChildrenRect(QQuickItem* object) const;

private:
QtEvents m_events;
bool m_eventFilterInstalled = false;
QtEventFilter* m_filter;
bool itemHasContents(QQuickItem* item);
bool isGoodCandidateItem(QQuickItem* item, bool ignoreItemHasContents);
};

} // namespace spix
1 change: 1 addition & 0 deletions lib/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ set(TEST_SOURCES
unittests/Scene/Qt/QtItem_test.cpp
unittests/Scene/Qt/QtItemTools_test.cpp
unittests/Scene/Qt/QtTestUtils.h
unittests/Scene/Qt/PickClick_test.cpp

unittests/Utils/AnyRpcFunction_test.cpp
unittests/Utils/AnyRpcUtils_test.cpp
Expand Down
Loading
Loading