mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 17:47:44 +00:00
Inspector: Expand and show properties in a TreeView
This allows the inspector to show arbitrary json structures.
This commit is contained in:
parent
476ccb2206
commit
7b72001667
5 changed files with 372 additions and 23 deletions
67
AK/JsonPath.cpp
Normal file
67
AK/JsonPath.cpp
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020, The SerenityOS developers.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <AK/JsonArray.h>
|
||||||
|
#include <AK/JsonObject.h>
|
||||||
|
#include <AK/JsonPath.h>
|
||||||
|
#include <AK/JsonValue.h>
|
||||||
|
|
||||||
|
namespace AK {
|
||||||
|
|
||||||
|
JsonPathElement JsonPathElement::any_array_element { Kind::AnyIndex };
|
||||||
|
JsonPathElement JsonPathElement::any_object_element { Kind::AnyKey };
|
||||||
|
|
||||||
|
JsonValue JsonPath::resolve(const JsonValue& top_root) const
|
||||||
|
{
|
||||||
|
auto root = top_root;
|
||||||
|
for (auto& element : *this) {
|
||||||
|
switch (element.kind()) {
|
||||||
|
case JsonPathElement::Kind::Key:
|
||||||
|
root = JsonValue { root.as_object().get(element.key()) };
|
||||||
|
break;
|
||||||
|
case JsonPathElement::Kind::Index:
|
||||||
|
root = JsonValue { root.as_array().at(element.index()) };
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ASSERT_NOT_REACHED();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
String JsonPath::to_string() const
|
||||||
|
{
|
||||||
|
StringBuilder builder;
|
||||||
|
builder.append("{ .");
|
||||||
|
for (auto& el : *this) {
|
||||||
|
builder.append(" > ");
|
||||||
|
builder.append(el.to_string());
|
||||||
|
}
|
||||||
|
builder.append(" }");
|
||||||
|
return builder.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
124
AK/JsonPath.h
Normal file
124
AK/JsonPath.h
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020, The SerenityOS developers.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/Optional.h>
|
||||||
|
#include <AK/String.h>
|
||||||
|
#include <AK/Types.h>
|
||||||
|
#include <AK/Vector.h>
|
||||||
|
|
||||||
|
namespace AK {
|
||||||
|
|
||||||
|
class JsonPathElement {
|
||||||
|
public:
|
||||||
|
enum class Kind {
|
||||||
|
Key,
|
||||||
|
Index,
|
||||||
|
AnyIndex,
|
||||||
|
AnyKey,
|
||||||
|
};
|
||||||
|
|
||||||
|
JsonPathElement(size_t index)
|
||||||
|
: m_kind(Kind::Index)
|
||||||
|
, m_index(index)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonPathElement(const StringView& key)
|
||||||
|
: m_kind(Kind::Key)
|
||||||
|
, m_key(key)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Kind kind() const { return m_kind; }
|
||||||
|
const String& key() const
|
||||||
|
{
|
||||||
|
ASSERT(m_kind == Kind::Key);
|
||||||
|
return m_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t index() const
|
||||||
|
{
|
||||||
|
ASSERT(m_kind == Kind::Index);
|
||||||
|
return m_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
String to_string() const
|
||||||
|
{
|
||||||
|
switch (m_kind) {
|
||||||
|
case Kind::Key:
|
||||||
|
return key();
|
||||||
|
case Kind::Index:
|
||||||
|
return String::number(index());
|
||||||
|
default:
|
||||||
|
return "*";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static JsonPathElement any_array_element;
|
||||||
|
static JsonPathElement any_object_element;
|
||||||
|
|
||||||
|
bool operator==(const JsonPathElement& other) const
|
||||||
|
{
|
||||||
|
switch (other.kind()) {
|
||||||
|
case Kind::Key:
|
||||||
|
return (m_kind == Kind::Key && other.key() == key()) || m_kind == Kind::AnyKey;
|
||||||
|
case Kind::Index:
|
||||||
|
return (m_kind == Kind::Index && other.index() == index()) || m_kind == Kind::AnyIndex;
|
||||||
|
case Kind::AnyKey:
|
||||||
|
return m_kind == Kind::Key;
|
||||||
|
case Kind::AnyIndex:
|
||||||
|
return m_kind == Kind::Index;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool operator!=(const JsonPathElement& other) const
|
||||||
|
{
|
||||||
|
return !(*this == other);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Kind m_kind;
|
||||||
|
String m_key;
|
||||||
|
size_t m_index { 0 };
|
||||||
|
|
||||||
|
JsonPathElement(Kind kind)
|
||||||
|
: m_kind(kind)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class JsonPath : public Vector<JsonPathElement> {
|
||||||
|
public:
|
||||||
|
JsonValue resolve(const JsonValue&) const;
|
||||||
|
String to_string() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
using AK::JsonPath;
|
||||||
|
using AK::JsonPathElement;
|
|
@ -33,9 +33,22 @@ RemoteObjectPropertyModel::RemoteObjectPropertyModel(RemoteObject& object)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
int RemoteObjectPropertyModel::row_count(const GUI::ModelIndex&) const
|
int RemoteObjectPropertyModel::row_count(const GUI::ModelIndex& index) const
|
||||||
{
|
{
|
||||||
return m_properties.size();
|
Function<int(const JsonValue&)> do_count = [&](const JsonValue& value) {
|
||||||
|
if (value.is_array())
|
||||||
|
return value.as_array().size();
|
||||||
|
else if (value.is_object())
|
||||||
|
return value.as_object().size();
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (index.is_valid()) {
|
||||||
|
auto* path = static_cast<const JsonPath*>(index.internal_data());
|
||||||
|
return do_count(path->resolve(m_object.json));
|
||||||
|
} else {
|
||||||
|
return do_count(m_object.json);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String RemoteObjectPropertyModel::column_name(int column) const
|
String RemoteObjectPropertyModel::column_name(int column) const
|
||||||
|
@ -51,13 +64,22 @@ String RemoteObjectPropertyModel::column_name(int column) const
|
||||||
|
|
||||||
GUI::Variant RemoteObjectPropertyModel::data(const GUI::ModelIndex& index, Role role) const
|
GUI::Variant RemoteObjectPropertyModel::data(const GUI::ModelIndex& index, Role role) const
|
||||||
{
|
{
|
||||||
auto& property = m_properties[index.row()];
|
auto* path = static_cast<const JsonPath*>(index.internal_data());
|
||||||
|
if (!path)
|
||||||
|
return {};
|
||||||
|
|
||||||
if (role == Role::Display) {
|
if (role == Role::Display) {
|
||||||
switch (index.column()) {
|
switch (index.column()) {
|
||||||
case Column::Name:
|
case Column::Name:
|
||||||
return property.name;
|
return path->last().to_string();
|
||||||
case Column::Value:
|
case Column::Value: {
|
||||||
return property.value.to_string();
|
auto data = path->resolve(m_object.json);
|
||||||
|
if (data.is_array())
|
||||||
|
return String::format("<Array with %d element%s", data.as_array().size(), data.as_array().size() == 1 ? ">" : "s>");
|
||||||
|
if (data.is_object())
|
||||||
|
return String::format("<Object with %d entrie%s", data.as_object().size(), data.as_object().size() == 1 ? ">" : "s>");
|
||||||
|
return data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
|
@ -65,18 +87,151 @@ GUI::Variant RemoteObjectPropertyModel::data(const GUI::ModelIndex& index, Role
|
||||||
|
|
||||||
void RemoteObjectPropertyModel::update()
|
void RemoteObjectPropertyModel::update()
|
||||||
{
|
{
|
||||||
m_properties.clear();
|
|
||||||
m_object.json.for_each_member([this](auto& name, auto& value) {
|
|
||||||
m_properties.append({ name, value });
|
|
||||||
});
|
|
||||||
did_update();
|
did_update();
|
||||||
}
|
}
|
||||||
|
|
||||||
void RemoteObjectPropertyModel::set_data(const GUI::ModelIndex& index, const GUI::Variant& new_value)
|
void RemoteObjectPropertyModel::set_data(const GUI::ModelIndex& index, const GUI::Variant& new_value)
|
||||||
{
|
{
|
||||||
auto& property = m_properties[index.row()];
|
if (!index.is_valid())
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto* path = static_cast<const JsonPath*>(index.internal_data());
|
||||||
|
if (path->size() != 1)
|
||||||
|
return;
|
||||||
|
|
||||||
FlatPtr address = m_object.address;
|
FlatPtr address = m_object.address;
|
||||||
RemoteProcess::the().set_property(address, property.name.to_string(), new_value.to_string());
|
RemoteProcess::the().set_property(address, path->first().to_string(), new_value.to_string());
|
||||||
property.value = new_value.to_string();
|
|
||||||
did_update();
|
did_update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GUI::ModelIndex RemoteObjectPropertyModel::index(int row, int column, const GUI::ModelIndex& parent) const
|
||||||
|
{
|
||||||
|
const auto& parent_path = parent.is_valid() ? *static_cast<const JsonPath*>(parent.internal_data()) : JsonPath {};
|
||||||
|
|
||||||
|
auto nth_child = [&](int n, const JsonValue& value) -> const JsonPath* {
|
||||||
|
auto path = make<JsonPath>();
|
||||||
|
path->append(parent_path);
|
||||||
|
int row_index = n;
|
||||||
|
if (value.is_object()) {
|
||||||
|
String property_name;
|
||||||
|
auto& object = value.as_object();
|
||||||
|
object.for_each_member([&](auto& name, auto&) {
|
||||||
|
if (row_index > 0) {
|
||||||
|
--row_index;
|
||||||
|
} else if (row_index == 0) {
|
||||||
|
property_name = name;
|
||||||
|
--row_index;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (property_name.is_null())
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
path->append({ property_name });
|
||||||
|
m_paths.append(move(path));
|
||||||
|
} else if (value.is_array()) {
|
||||||
|
path->append(JsonPathElement { (size_t)n });
|
||||||
|
m_paths.append(move(path));
|
||||||
|
} else {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return &m_paths.last();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!parent.is_valid()) {
|
||||||
|
if (m_object.json.is_empty())
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto index_path = cached_path_at(row, parent_path);
|
||||||
|
|
||||||
|
if (!index_path)
|
||||||
|
index_path = nth_child(row, parent_path.resolve(m_object.json));
|
||||||
|
|
||||||
|
if (!index_path)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return create_index(row, column, index_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
GUI::ModelIndex RemoteObjectPropertyModel::parent_index(const GUI::ModelIndex& index) const
|
||||||
|
{
|
||||||
|
if (!index.is_valid())
|
||||||
|
return index;
|
||||||
|
|
||||||
|
auto path = *static_cast<const JsonPath*>(index.internal_data());
|
||||||
|
if (path.is_empty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
path.take_last();
|
||||||
|
if (path.is_empty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
auto* cpath = find_cached_path(path);
|
||||||
|
if (cpath) {
|
||||||
|
int index_in_parent = 0;
|
||||||
|
if (cpath->last().kind() == JsonPathElement::Kind::Index)
|
||||||
|
index_in_parent = cpath->last().index();
|
||||||
|
else if (cpath->last().kind() == JsonPathElement::Kind::Key) {
|
||||||
|
auto path_copy = path;
|
||||||
|
auto last = path_copy.take_last();
|
||||||
|
bool found = false;
|
||||||
|
path_copy.resolve(m_object.json).as_object().for_each_member([&](auto& name, auto&) {
|
||||||
|
if (!found) {
|
||||||
|
if (last.key() == name)
|
||||||
|
found = true;
|
||||||
|
else
|
||||||
|
index_in_parent++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return create_index(index_in_parent, 0, cpath);
|
||||||
|
}
|
||||||
|
|
||||||
|
dbg() << "No cached path found for path " << path.to_string();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const JsonPath* RemoteObjectPropertyModel::cached_path_at(int n, const Vector<JsonPathElement>& prefix) const
|
||||||
|
{
|
||||||
|
// FIXME: ModelIndex wants a void*, so we have to keep these
|
||||||
|
// indices alive, but allocating a new path every time
|
||||||
|
// we're asked for an index is silly, so we have to look for existing ones first.
|
||||||
|
const JsonPath* index_path = nullptr;
|
||||||
|
int row_index = n;
|
||||||
|
for (auto& path : m_paths) {
|
||||||
|
if (path.size() != prefix.size() + 1)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < prefix.size(); ++i) {
|
||||||
|
if (path[i] != prefix[i])
|
||||||
|
goto do_continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row_index == 0) {
|
||||||
|
index_path = &path;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
--row_index;
|
||||||
|
do_continue:;
|
||||||
|
}
|
||||||
|
|
||||||
|
return index_path;
|
||||||
|
};
|
||||||
|
|
||||||
|
const JsonPath* RemoteObjectPropertyModel::find_cached_path(const Vector<JsonPathElement>& path) const
|
||||||
|
{
|
||||||
|
for (auto& cpath : m_paths) {
|
||||||
|
if (cpath.size() != path.size())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < cpath.size(); ++i) {
|
||||||
|
if (cpath[i] != path[i])
|
||||||
|
goto do_continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return &cpath;
|
||||||
|
do_continue:;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
|
@ -26,14 +26,16 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/JsonPath.h>
|
||||||
#include <AK/JsonValue.h>
|
#include <AK/JsonValue.h>
|
||||||
|
#include <AK/NonnullOwnPtrVector.h>
|
||||||
#include <LibGUI/Model.h>
|
#include <LibGUI/Model.h>
|
||||||
|
|
||||||
class RemoteObject;
|
class RemoteObject;
|
||||||
|
|
||||||
class RemoteObjectPropertyModel final : public GUI::Model {
|
class RemoteObjectPropertyModel final : public GUI::Model {
|
||||||
public:
|
public:
|
||||||
virtual ~RemoteObjectPropertyModel() override {}
|
virtual ~RemoteObjectPropertyModel() override { }
|
||||||
static NonnullRefPtr<RemoteObjectPropertyModel> create(RemoteObject& object)
|
static NonnullRefPtr<RemoteObjectPropertyModel> create(RemoteObject& object)
|
||||||
{
|
{
|
||||||
return adopt(*new RemoteObjectPropertyModel(object));
|
return adopt(*new RemoteObjectPropertyModel(object));
|
||||||
|
@ -52,14 +54,15 @@ public:
|
||||||
virtual void set_data(const GUI::ModelIndex&, const GUI::Variant&) override;
|
virtual void set_data(const GUI::ModelIndex&, const GUI::Variant&) override;
|
||||||
virtual void update() override;
|
virtual void update() override;
|
||||||
virtual bool is_editable(const GUI::ModelIndex& index) const override { return index.column() == Column::Value; }
|
virtual bool is_editable(const GUI::ModelIndex& index) const override { return index.column() == Column::Value; }
|
||||||
|
virtual GUI::ModelIndex index(int row, int column, const GUI::ModelIndex& parent = GUI::ModelIndex()) const override;
|
||||||
|
virtual GUI::ModelIndex parent_index(const GUI::ModelIndex&) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit RemoteObjectPropertyModel(RemoteObject&);
|
explicit RemoteObjectPropertyModel(RemoteObject&);
|
||||||
|
|
||||||
|
const JsonPath* cached_path_at(int n, const Vector<JsonPathElement>& prefix) const;
|
||||||
|
const JsonPath* find_cached_path(const Vector<JsonPathElement>& path) const;
|
||||||
|
|
||||||
RemoteObject& m_object;
|
RemoteObject& m_object;
|
||||||
struct NameAndValue {
|
mutable NonnullOwnPtrVector<JsonPath> m_paths;
|
||||||
JsonValue name;
|
|
||||||
JsonValue value;
|
|
||||||
};
|
|
||||||
Vector<NameAndValue> m_properties;
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -94,15 +94,15 @@ int main(int argc, char** argv)
|
||||||
tree_view.set_model(remote_process.object_graph_model());
|
tree_view.set_model(remote_process.object_graph_model());
|
||||||
tree_view.set_activates_on_selection(true);
|
tree_view.set_activates_on_selection(true);
|
||||||
|
|
||||||
auto& properties_table_view = splitter.add<GUI::TableView>();
|
auto& properties_tree_view = splitter.add<GUI::TreeView>();
|
||||||
properties_table_view.set_editable(true);
|
properties_tree_view.set_editable(true);
|
||||||
properties_table_view.aid_create_editing_delegate = [](auto&) {
|
properties_tree_view.aid_create_editing_delegate = [](auto&) {
|
||||||
return make<GUI::StringModelEditingDelegate>();
|
return make<GUI::StringModelEditingDelegate>();
|
||||||
};
|
};
|
||||||
|
|
||||||
tree_view.on_activation = [&](auto& index) {
|
tree_view.on_activation = [&](auto& index) {
|
||||||
auto* remote_object = static_cast<RemoteObject*>(index.internal_data());
|
auto* remote_object = static_cast<RemoteObject*>(index.internal_data());
|
||||||
properties_table_view.set_model(remote_object->property_model());
|
properties_tree_view.set_model(remote_object->property_model());
|
||||||
remote_process.set_inspected_object(remote_object->address);
|
remote_process.set_inspected_object(remote_object->address);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue