Further enhancements on the QTreeWidget and QTreeWidgetItem (#340)

* Extend QTreeWidget implementation

* Added more QTreeWidget and QTreeWidgetItem APIs

Supports itemClicked and itemChanged events and several APIs to be able to edit columns of the QTreeWidgetItem

* Incorporate suggested changes

* Implemented all QTreeWidget signals

* fix currentItem

* lint fix

Co-authored-by: Atul R <atulanand94@gmail.com>
This commit is contained in:
robertkruis 2020-01-11 07:59:46 +01:00 committed by Atul R
parent bb98c4b7fc
commit 3a8f405e2d
9 changed files with 471 additions and 8 deletions

View File

@ -3,6 +3,7 @@
#include <QTreeWidget>
#include "QtWidgets/QAbstractScrollArea/qabstractscrollarea_macro.h"
#include "QtWidgets/QTreeWidgetItem/qtreewidgetitem_wrap.h"
#include "core/NodeWidget/nodewidget.h"
#include "napi.h"
@ -20,5 +21,120 @@ class NTreeWidget : public QTreeWidget, public NodeWidget {
Napi::HandleScope scope(env);
this->emitOnNode.Call({Napi::String::New(env, "itemSelectionChanged")});
});
QObject::connect(
this, &QTreeWidget::itemClicked,
[=](QTreeWidgetItem* item, int column) {
Napi::Env env = this->emitOnNode.Env();
Napi::HandleScope scope(env);
auto itemWrap = QTreeWidgetItemWrap::fromQTreeWidgetItem(env, item);
auto columnWrap = Napi::Value::From(env, column);
this->emitOnNode.Call(
{Napi::String::New(env, "itemClicked"), itemWrap, columnWrap});
});
QObject::connect(
this, &QTreeWidget::itemChanged,
[=](QTreeWidgetItem* item, int column) {
Napi::Env env = this->emitOnNode.Env();
Napi::HandleScope scope(env);
auto itemWrap = QTreeWidgetItemWrap::fromQTreeWidgetItem(env, item);
auto columnWrap = Napi::Value::From(env, column);
this->emitOnNode.Call(
{Napi::String::New(env, "itemChanged"), itemWrap, columnWrap});
});
QObject::connect(
this, &QTreeWidget::currentItemChanged,
[=](QTreeWidgetItem* current, QTreeWidgetItem* previous) {
Napi::Env env = this->emitOnNode.Env();
Napi::HandleScope scope(env);
auto currentItemWrap =
QTreeWidgetItemWrap::fromQTreeWidgetItem(env, current);
auto previousItemWrap =
QTreeWidgetItemWrap::fromQTreeWidgetItem(env, previous);
this->emitOnNode.Call({Napi::String::New(env, "currentItemChanged"),
currentItemWrap, previousItemWrap});
});
QObject::connect(
this, &QTreeWidget::itemActivated,
[=](QTreeWidgetItem* item, int column) {
Napi::Env env = this->emitOnNode.Env();
Napi::HandleScope scope(env);
auto itemWrap = QTreeWidgetItemWrap::fromQTreeWidgetItem(env, item);
auto columnWrap = Napi::Value::From(env, column);
this->emitOnNode.Call(
{Napi::String::New(env, "itemActivated"), itemWrap, columnWrap});
});
QObject::connect(
this, &QTreeWidget::itemCollapsed, [=](QTreeWidgetItem* item) {
Napi::Env env = this->emitOnNode.Env();
Napi::HandleScope scope(env);
auto itemWrap = QTreeWidgetItemWrap::fromQTreeWidgetItem(env, item);
this->emitOnNode.Call(
{Napi::String::New(env, "itemCollapsed"), itemWrap});
});
QObject::connect(
this, &QTreeWidget::itemDoubleClicked,
[=](QTreeWidgetItem* item, int column) {
Napi::Env env = this->emitOnNode.Env();
Napi::HandleScope scope(env);
auto itemWrap = QTreeWidgetItemWrap::fromQTreeWidgetItem(env, item);
auto columnWrap = Napi::Value::From(env, column);
this->emitOnNode.Call({Napi::String::New(env, "itemDoubleClicked"),
itemWrap, columnWrap});
});
QObject::connect(
this, &QTreeWidget::itemEntered,
[=](QTreeWidgetItem* item, int column) {
Napi::Env env = this->emitOnNode.Env();
Napi::HandleScope scope(env);
auto itemWrap = QTreeWidgetItemWrap::fromQTreeWidgetItem(env, item);
auto columnWrap = Napi::Value::From(env, column);
this->emitOnNode.Call(
{Napi::String::New(env, "itemEntered"), itemWrap, columnWrap});
});
QObject::connect(
this, &QTreeWidget::itemExpanded, [=](QTreeWidgetItem* item) {
Napi::Env env = this->emitOnNode.Env();
Napi::HandleScope scope(env);
auto itemWrap = QTreeWidgetItemWrap::fromQTreeWidgetItem(env, item);
this->emitOnNode.Call(
{Napi::String::New(env, "itemExpanded"), itemWrap});
});
QObject::connect(
this, &QTreeWidget::itemPressed,
[=](QTreeWidgetItem* item, int column) {
Napi::Env env = this->emitOnNode.Env();
Napi::HandleScope scope(env);
auto itemWrap = QTreeWidgetItemWrap::fromQTreeWidgetItem(env, item);
auto columnWrap = Napi::Value::From(env, column);
this->emitOnNode.Call(
{Napi::String::New(env, "itemPressed"), itemWrap, columnWrap});
});
}
};
};

View File

@ -26,6 +26,11 @@ class QTreeWidgetWrap : public Napi::ObjectWrap<QTreeWidgetWrap> {
Napi::Value addTopLevelItem(const Napi::CallbackInfo &info);
Napi::Value selectedItems(const Napi::CallbackInfo &info);
Napi::Value setColumnCount(const Napi::CallbackInfo &info);
Napi::Value setHeaderLabel(const Napi::CallbackInfo &info);
Napi::Value setHeaderLabels(const Napi::CallbackInfo &info);
Napi::Value setItemWidget(const Napi::CallbackInfo &info);
Napi::Value currentItem(const Napi::CallbackInfo &info);
// Napi::Value addTopLevelItems(const Napi::CallbackInfo& info);
// Napi::Value setHorizontalScrollBarPolicy(const Napi::CallbackInfo& info);

View File

@ -15,6 +15,7 @@ class QTreeWidgetItemWrap : public Napi::ObjectWrap<QTreeWidgetItemWrap> {
public:
static Napi::Object init(Napi::Env env, Napi::Object exports);
static Napi::Value fromQTreeWidgetItem(Napi::Env env, QTreeWidgetItem *item);
QTreeWidgetItemWrap(const Napi::CallbackInfo &info);
@ -33,4 +34,10 @@ class QTreeWidgetItemWrap : public Napi::ObjectWrap<QTreeWidgetItemWrap> {
Napi::Value text(const Napi::CallbackInfo &info);
Napi::Value setSelected(const Napi::CallbackInfo &info);
Napi::Value setExpanded(const Napi::CallbackInfo &info);
Napi::Value addChild(const Napi::CallbackInfo &info);
Napi::Value setFlags(const Napi::CallbackInfo &info);
Napi::Value setCheckState(const Napi::CallbackInfo &info);
Napi::Value flags(const Napi::CallbackInfo &info);
Napi::Value setData(const Napi::CallbackInfo &info);
Napi::Value data(const Napi::CallbackInfo &info);
};

View File

@ -15,6 +15,11 @@ Napi::Object QTreeWidgetWrap::init(Napi::Env env, Napi::Object exports) {
env, CLASSNAME,
{InstanceMethod("addTopLevelItem", &QTreeWidgetWrap::addTopLevelItem),
InstanceMethod("selectedItems", &QTreeWidgetWrap::selectedItems),
InstanceMethod("setColumnCount", &QTreeWidgetWrap::setColumnCount),
InstanceMethod("setHeaderLabel", &QTreeWidgetWrap::setHeaderLabel),
InstanceMethod("setHeaderLabels", &QTreeWidgetWrap::setHeaderLabels),
InstanceMethod("setItemWidget", &QTreeWidgetWrap::setItemWidget),
InstanceMethod("currentItem", &QTreeWidgetWrap::currentItem),
QWIDGET_WRAPPED_METHODS_EXPORT_DEFINE(QTreeWidgetWrap)});
constructor = Napi::Persistent(func);
exports.Set(CLASSNAME, func);
@ -74,3 +79,82 @@ Napi::Value QTreeWidgetWrap::selectedItems(const Napi::CallbackInfo& info) {
return napiItems;
}
Napi::Value QTreeWidgetWrap::setColumnCount(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
Napi::HandleScope scope(env);
int columns = info[0].As<Napi::Number>().Int32Value();
this->instance->setColumnCount(columns);
return env.Null();
}
Napi::Value QTreeWidgetWrap::setHeaderLabel(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
Napi::HandleScope scope(env);
Napi::String napiLabel = info[0].As<Napi::String>();
if (napiLabel.IsEmpty()) {
Napi::TypeError::New(env, "Label must be specified")
.ThrowAsJavaScriptException();
}
std::string label = napiLabel.Utf8Value();
if (QTreeWidgetItem* header = this->instance->headerItem()) {
header->setText(0, QString::fromUtf8(label.c_str()));
} else {
this->instance->setHeaderLabel(QString::fromUtf8(label.c_str()));
}
return env.Null();
}
Napi::Value QTreeWidgetWrap::setHeaderLabels(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
Napi::HandleScope scope(env);
Napi::Array napiLabelArray = info[0].As<Napi::Array>();
QStringList headerLabels;
for (int i = 0; i < napiLabelArray.Length(); i++) {
Napi::Value label = napiLabelArray[i];
headerLabels.push_back(label.As<Napi::String>().Utf8Value().c_str());
}
this->instance->setHeaderLabels(headerLabels);
return env.Null();
}
Napi::Value QTreeWidgetWrap::setItemWidget(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
Napi::HandleScope scope(env);
Napi::Object itemObject = info[0].As<Napi::Object>();
QTreeWidgetItemWrap* itemWidgetWrap =
Napi::ObjectWrap<QTreeWidgetItemWrap>::Unwrap(itemObject);
QTreeWidgetItem* item = itemWidgetWrap->getInternalInstance();
int column = info[1].As<Napi::Number>().Int32Value();
Napi::Object widgetObject = info[2].As<Napi::Object>();
QWidgetWrap* widgetWrap = Napi::ObjectWrap<QWidgetWrap>::Unwrap(widgetObject);
QWidget* widget = widgetWrap->getInternalInstance();
this->instance->setItemWidget(item, column, widget);
return env.Null();
}
Napi::Value QTreeWidgetWrap::currentItem(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
Napi::HandleScope scope(env);
QTreeWidgetItem* currentItem = this->instance->currentItem();
Napi::Object value = QTreeWidgetItemWrap::constructor.New(
{Napi::External<QTreeWidgetItem>::New(
env, new QTreeWidgetItem(*currentItem))});
return value;
}

View File

@ -21,6 +21,12 @@ Napi::Object QTreeWidgetItemWrap::init(Napi::Env env, Napi::Object exports) {
InstanceMethod("childCount", &QTreeWidgetItemWrap::childCount),
InstanceMethod("setSelected", &QTreeWidgetItemWrap::setSelected),
InstanceMethod("setExpanded", &QTreeWidgetItemWrap::setExpanded),
InstanceMethod("addChild", &QTreeWidgetItemWrap::addChild),
InstanceMethod("setFlags", &QTreeWidgetItemWrap::setFlags),
InstanceMethod("setCheckState", &QTreeWidgetItemWrap::setCheckState),
InstanceMethod("flags", &QTreeWidgetItemWrap::flags),
InstanceMethod("setData", &QTreeWidgetItemWrap::setData),
InstanceMethod("data", &QTreeWidgetItemWrap::data),
COMPONENT_WRAPPED_METHODS_EXPORT_DEFINE(QTreeWidgetItemWrap)});
constructor = Napi::Persistent(func);
exports.Set(CLASSNAME, func);
@ -31,6 +37,19 @@ QTreeWidgetItem *QTreeWidgetItemWrap::getInternalInstance() {
return this->instance;
}
Napi::Value QTreeWidgetItemWrap::fromQTreeWidgetItem(Napi::Env env,
QTreeWidgetItem *item) {
// The item might be a nullptr, therefore use env.Null() as return value.
if (item == nullptr) {
return env.Null();
}
Napi::Value itemWrap = QTreeWidgetItemWrap::constructor.New(
{Napi::External<QTreeWidgetItem>::New(env, new QTreeWidgetItem(*item))});
return itemWrap;
}
QTreeWidgetItemWrap::~QTreeWidgetItemWrap() {
if (!this->disableDeletion) {
delete this->instance;
@ -185,3 +204,80 @@ Napi::Value QTreeWidgetItemWrap::setExpanded(const Napi::CallbackInfo &info) {
this->instance->setExpanded(expanded.Value());
return env.Null();
}
Napi::Value QTreeWidgetItemWrap::addChild(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
Napi::HandleScope scope(env);
Napi::Object itemObject = info[0].As<Napi::Object>();
QTreeWidgetItemWrap *itemWidgetWrap =
Napi::ObjectWrap<QTreeWidgetItemWrap>::Unwrap(itemObject);
QTreeWidgetItem *item = itemWidgetWrap->getInternalInstance();
this->instance->addChild(item);
return env.Null();
}
Napi::Value QTreeWidgetItemWrap::setFlags(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
Napi::HandleScope scope(env);
int flags = info[0].As<Napi::Number>().Int32Value();
this->instance->setFlags(static_cast<Qt::ItemFlags>(flags));
return env.Null();
}
Napi::Value QTreeWidgetItemWrap::setCheckState(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
Napi::HandleScope scope(env);
int column = info[0].As<Napi::Number>().Int32Value();
int checkState = info[1].As<Napi::Number>().Int32Value();
this->instance->setCheckState(column,
static_cast<Qt::CheckState>(checkState));
return env.Null();
}
Napi::Value QTreeWidgetItemWrap::flags(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
Napi::HandleScope scope(env);
Qt::ItemFlags flags = this->instance->flags();
return Napi::Value::From(env, static_cast<int>(flags));
}
Napi::Value QTreeWidgetItemWrap::setData(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
Napi::HandleScope scope(env);
int column = info[0].As<Napi::Number>().Int32Value();
int role = info[1].As<Napi::Number>().Int32Value();
Napi::Object variantObject = info[2].As<Napi::Object>();
QVariantWrap *variantWrap =
Napi::ObjectWrap<QVariantWrap>::Unwrap(variantObject);
QVariant *variant = variantWrap->getInternalInstance();
this->instance->setData(column, role, *variant);
return env.Null();
}
Napi::Value QTreeWidgetItemWrap::data(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env();
Napi::HandleScope scope(env);
int column = info[0].As<Napi::Number>().Int32Value();
int role = info[1].As<Napi::Number>().Int32Value();
QVariant variant = this->instance->data(column, role);
auto instance = QVariantWrap::constructor.New(
{Napi::External<QVariant>::New(env, new QVariant(variant))});
return instance;
}

View File

@ -1,4 +1,7 @@
import { QWidget, QMainWindow, FlexLayout, QRadioButton, QButtonGroup } from './index';
import { QWidget, QMainWindow, FlexLayout, QTreeWidget, QTreeWidgetItem } from './index';
import { ItemFlag, CheckState } from './lib/QtEnums';
import { QSpinBox } from './lib/QtWidgets/QSpinBox';
import { QLineEdit } from './lib/QtWidgets/QLineEdit';
const win = new QMainWindow();
const center = new QWidget();
@ -6,7 +9,46 @@ const layout = new FlexLayout();
center.setLayout(layout);
win.setCentralWidget(center);
const buttonGroup = new QButtonGroup(center);
const tree = new QTreeWidget();
tree.setColumnCount(2);
tree.setHeaderLabels(['Properties', 'Value']);
center.layout?.addWidget(tree);
const root1 = new QTreeWidgetItem(tree);
root1.setText(0, 'Option 1');
root1.setText(1, 'Option 1 Description');
const item1 = new QTreeWidgetItem();
item1.setText(0, 'enabled');
item1.setFlags(ItemFlag.ItemIsUserCheckable | ItemFlag.ItemIsEnabled);
item1.setCheckState(1, CheckState.Checked);
root1.addChild(item1);
const item1_1 = new QTreeWidgetItem();
item1_1.setText(0, 'height');
root1.addChild(item1_1);
tree.setItemWidget(item1_1, 1, new QSpinBox());
const item1_2 = new QTreeWidgetItem();
item1_2.setText(0, 'name');
root1.addChild(item1_2);
tree.setItemWidget(item1_2, 1, new QLineEdit());
const root2 = new QTreeWidgetItem(tree);
root2.setText(0, 'Option 2');
root2.setText(1, 'Option 2 Description');
const item2 = new QTreeWidgetItem();
item2.setText(0, 'width');
item2.setText(1, '300');
root2.addChild(item2);
const item2_1 = new QTreeWidgetItem();
item2_1.setText(0, 'height');
item2_1.setText(1, '200');
root2.addChild(item2_1);
/* const buttonGroup = new QButtonGroup(center);
const t: any[] = [];
for (let i = 0; i < 4; i++) {
const radioButton = new QRadioButton();
@ -14,12 +56,12 @@ for (let i = 0; i < 4; i++) {
center.layout?.addWidget(radioButton);
t.push(radioButton);
buttonGroup.addButton(radioButton, i);
}
} */
win.show();
buttonGroup.addEventListener('buttonClicked', (id: number) => {
/* buttonGroup.addEventListener('buttonClicked', (id: number) => {
console.log('Button #' + (id + 1) + ' clicked!');
buttonGroup.removeButton(t[0]);
});
}); */
(global as any).win = win;
setInterval(() => null, 1000);

View File

@ -5,5 +5,6 @@ export enum ItemDataRole {
ToolTipRole = 3,
StatusTipRole = 4,
WhatsThisRole = 5,
CheckStateRole = 10,
SizeHintRole = 13,
}

View File

@ -1,5 +1,5 @@
import addon from '../utils/addon';
import { NodeWidget } from './QWidget';
import { NodeWidget, QWidget } from './QWidget';
import { NativeElement } from '../core/Component';
import { QAbstractScrollArea, QAbstractScrollAreaSignals } from './QAbstractScrollArea';
import { QTreeWidgetItem } from './QTreeWidgetItem';
@ -13,7 +13,6 @@ import { QTreeWidgetItem } from './QTreeWidgetItem';
### Example
```javascript
const { QTreeWidget, QTreeWidgetItem } = require("@nodegui/nodegui");
const { QMainWindow, QTreeWidgetItem, QTreeWidget } = require("@nodegui/nodegui");
@ -45,6 +44,7 @@ win.show();
export class QTreeWidget extends QAbstractScrollArea<QTreeWidgetSignals> {
native: NativeElement;
topLevelItems: Set<QTreeWidgetItem>;
itemWidgets: Map<QTreeWidgetItem, QWidget>;
constructor();
constructor(parent: NodeWidget<any>);
constructor(parent?: NodeWidget<any>) {
@ -58,6 +58,7 @@ export class QTreeWidget extends QAbstractScrollArea<QTreeWidgetSignals> {
this.native = native;
this.setNodeParent(parent);
this.topLevelItems = new Set<QTreeWidgetItem>();
this.itemWidgets = new Map<QTreeWidgetItem, QWidget>();
}
addTopLevelItem(item: QTreeWidgetItem): void {
@ -74,8 +75,59 @@ export class QTreeWidget extends QAbstractScrollArea<QTreeWidgetSignals> {
return new QTreeWidgetItem(eachItem);
});
}
/**
* Sets the column count of this QTreeWidget.
* @param columnCount The number of columns.
*/
setColumnCount(columnCount: number): void {
this.native.setColumnCount(columnCount);
}
/**
* Sets the header label.
* @param label The header label.
*/
setHeaderLabel(label: string): void {
this.native.setHeaderLabel(label);
}
/**
* Sets the header labels of the existing columns.
* @param labels The header labels for each column.
*/
setHeaderLabels(labels: string[]): void {
this.native.setHeaderLabels(labels);
}
/**
* Sets the given widget to be displayed in the cell specified by the given item and column.
* @param item The targeted item.
* @param column The column in which to show the edit widget.
* @param widget The edit widget.
*/
setItemWidget(item: QTreeWidgetItem, column: number, widget: QWidget): void {
this.itemWidgets.set(item, widget);
this.native.setItemWidget(item.native, column, widget.native);
}
/**
* Returns the current item in the tree widget.
*/
currentItem(): QTreeWidgetItem {
return new QTreeWidgetItem(this.native.currentItem());
}
}
export interface QTreeWidgetSignals extends QAbstractScrollAreaSignals {
itemSelectionChanged: () => void;
itemClicked: (item: QTreeWidgetItem, column: number) => void;
itemChanged: (item: QTreeWidgetItem, column: number) => void;
currentItemChanged: (current: QTreeWidgetItem, previous: QTreeWidgetItem) => void;
itemActivated: (item: QTreeWidgetItem | null, column: number) => void;
itemCollapsed: (item: QTreeWidgetItem) => void;
itemDoubleClicked: (item: QTreeWidgetItem | null, column: number) => void;
itemEntered: (item: QTreeWidgetItem, column: number) => void;
itemExpanded: (item: QTreeWidgetItem) => void;
itemPressed: (item: QTreeWidgetItem | null, column: number) => void;
}

View File

@ -2,6 +2,9 @@ import addon from '../utils/addon';
import { Component, NativeElement } from '../core/Component';
import { checkIfNativeElement } from '../utils/helpers';
import { QTreeWidget } from './QTreeWidget';
import { ItemFlag } from '../QtEnums/ItemFlag';
import { CheckState, ItemDataRole } from '../QtEnums';
import { QVariantType, QVariant } from '../QtCore/QVariant';
/**
@ -43,6 +46,7 @@ win.show();
*/
export class QTreeWidgetItem extends Component {
native: NativeElement;
items: Set<NativeElement | Component>;
constructor();
constructor(parent: QTreeWidgetItem, strings?: string[]);
constructor(parent: QTreeWidget, strings?: string[]);
@ -50,6 +54,7 @@ export class QTreeWidgetItem extends Component {
constructor(strings: string[]);
constructor(parent?: NativeElement | QTreeWidgetItem | QTreeWidget | string[], strings?: string[]) {
super();
this.items = new Set();
if (checkIfNativeElement(parent)) {
this.native = parent as NativeElement;
} else {
@ -95,4 +100,59 @@ export class QTreeWidgetItem extends Component {
setExpanded(expanded: boolean): void {
this.native.setExpanded(expanded);
}
/**
* Adds the specified child to this QTreeWidgetItem.
* @param childItem The child to add.
*/
addChild(childItem: QTreeWidgetItem): void {
this.items.add(childItem);
this.native.addChild(childItem.native);
}
/**
* Sets the flags for the item to the given flags. These determine whether the item can be selected or modified.
* This is often used to disable an item.
* @param flags The flags.
*/
setFlags(flags: ItemFlag): void {
this.native.setFlags(flags);
}
/**
* Sets the item in the given column check state to be state.
* @param column The column.
* @param state The check state.
*/
setCheckState(column: number, state: CheckState): void {
this.native.setCheckState(column, state);
}
/**
* Returns the flags used to describe the item. These determine whether the item can be checked, edited, and selected.
*/
flags(): ItemFlag {
return this.native.flags();
}
/**
* Sets the value for the item's column and role to the given value.
* The role describes the type of data specified by value, and is defined by the ItemDataRole enum.
* @param column The column.
* @param role The role.
* @param value The value.
*/
setData(column: number, role: ItemDataRole, value: QVariantType): void {
const variant = new QVariant(value);
this.native.setData(column, role, variant.native);
}
/**
* Returns the value for the item's column and role.
* @param column The column.
* @param role The role.
*/
data(column: number, role: ItemDataRole): QVariant {
return this.native.data(column, role);
}
}