From 4bba1b8c0176559ca2b8f233b17b00b7d01f4710 Mon Sep 17 00:00:00 2001
From: Alex Hughes <ahughesalex@gmail.com>
Date: Mon, 10 Feb 2025 15:59:09 -0800
Subject: [PATCH 1/3] Introduce an example of implementing a custom
 QAbstractItemModel and QSortFilterProxyModel

---
 examples/customsortfiltermodel/main.go      | 93 ++++++++++++++++++++
 examples/customsortfiltermodel/model.go     | 94 +++++++++++++++++++++
 examples/customsortfiltermodel/tree_cell.go | 33 ++++++++
 examples/customsortfiltermodel/tree_item.go | 82 ++++++++++++++++++
 4 files changed, 302 insertions(+)
 create mode 100644 examples/customsortfiltermodel/main.go
 create mode 100644 examples/customsortfiltermodel/model.go
 create mode 100644 examples/customsortfiltermodel/tree_cell.go
 create mode 100644 examples/customsortfiltermodel/tree_item.go

diff --git a/examples/customsortfiltermodel/main.go b/examples/customsortfiltermodel/main.go
new file mode 100644
index 00000000..f8861d0a
--- /dev/null
+++ b/examples/customsortfiltermodel/main.go
@@ -0,0 +1,93 @@
+package main
+
+import (
+	"fmt"
+	qt "github.com/mappu/miqt/qt6"
+	"os"
+)
+
+func GetTreeView(parent *qt.QWidget) *qt.QTreeView {
+	treeView := qt.NewQTreeView(parent)
+	treeView.SetAlternatingRowColors(true)
+	treeView.SetSortingEnabled(true)
+	treeView.SetColumnWidth(0, 300)
+	treeView.SetContentsMargins(0, 0, 0, 0)
+	treeView.Header().SetDefaultSectionSize(200)
+	treeView.Header().SetStretchLastSection(true)
+	treeView.SetSelectionMode(qt.QAbstractItemView__ExtendedSelection)
+
+	return treeView
+}
+
+func PopulateData(rootItem *TreeItem) {
+	cellData := make([]*TreeCell, 0)
+	cellData = append(cellData, NewTreeCell(qt.DisplayRole, *qt.NewQVariant11("Big File")))
+	cellData = append(cellData, NewTreeCell(qt.DisplayRole, *qt.NewQVariant11("10.7 MiB")))
+	item := NewTreeItem(cellData, rootItem, false)
+	err := item.SetData(1, qt.NewQVariant11("11225400"), qt.UserRole)
+	if err != nil {
+		fmt.Printf("Error setting UserData: %v\n", err)
+	}
+	rootItem.AppendChild(&item)
+
+	cellData2 := make([]*TreeCell, 0)
+	cellData2 = append(cellData2, NewTreeCell(qt.DisplayRole, *qt.NewQVariant11("Small File")))
+	cellData2 = append(cellData2, NewTreeCell(qt.DisplayRole, *qt.NewQVariant11("110 Bytes")))
+	item2 := NewTreeItem(cellData2, rootItem, false)
+	err = item2.SetData(1, qt.NewQVariant11("110"), qt.UserRole)
+	if err != nil {
+		fmt.Printf("Error setting UserData: %v\n", err)
+	}
+	rootItem.AppendChild(&item2)
+
+	cellData3 := make([]*TreeCell, 0)
+	cellData3 = append(cellData3, NewTreeCell(qt.DisplayRole, *qt.NewQVariant11("Medium File")))
+	cellData3 = append(cellData3, NewTreeCell(qt.DisplayRole, *qt.NewQVariant11("109.5 KiB")))
+	item3 := NewTreeItem(cellData3, rootItem, false)
+	err = item3.SetData(1, qt.NewQVariant11("112200"), qt.UserRole)
+	if err != nil {
+		fmt.Printf("Error setting UserData: %v\n", err)
+	}
+	rootItem.AppendChild(&item3)
+}
+
+func GetDialog() *qt.QDialog {
+	dialog := qt.NewQDialog3(nil, qt.Dialog)
+	dialog.SetWindowFlags(qt.WindowStaysOnTopHint)
+	dialogLayout := qt.NewQVBoxLayout2()
+	dialogLayout.SetSpacing(0)
+	dialogLayout.SetContentsMargins(0, 0, 0, 0)
+	dialog.SetContentsMargins(10, 10, 10, 10)
+	dialog.SetLayout(dialogLayout.Layout())
+	dialog.SetWindowTitle("Example Sort Model Usage")
+
+	treeView := GetTreeView(dialog.QWidget)
+
+	// Model
+	model, rootItem := GetModel(dialog.QObject)
+	PopulateData(rootItem)
+
+	sortModel := qt.NewQSortFilterProxyModel2(treeView.QObject)
+	sortModel.SetSortRole(int(qt.UserRole))
+	sortModel.OnLessThan(func(super func(source_left *qt.QModelIndex, source_right *qt.QModelIndex) bool, source_left *qt.QModelIndex, source_right *qt.QModelIndex) bool {
+		// In our case we know that the data from our TreeItem will come back as an int for our UserRole
+		leftData := sortModel.SourceModel().Data(source_left, sortModel.SortRole())
+		rightData := sortModel.SourceModel().Data(source_right, sortModel.SortRole())
+		return leftData.ToInt() < rightData.ToInt()
+	})
+
+	sortModel.SetSourceModel(model)
+	treeView.SetModel(sortModel.QAbstractItemModel)
+
+	dialogLayout.AddWidget3(treeView.QWidget, 0, qt.AlignCenter)
+	return dialog
+}
+
+func main() {
+	qt.NewQApplication(os.Args)
+
+	dialog := GetDialog()
+	dialog.Show()
+
+	qt.QApplication_Exec()
+}
diff --git a/examples/customsortfiltermodel/model.go b/examples/customsortfiltermodel/model.go
new file mode 100644
index 00000000..8429569b
--- /dev/null
+++ b/examples/customsortfiltermodel/model.go
@@ -0,0 +1,94 @@
+package main
+
+import (
+	qt "github.com/mappu/miqt/qt6"
+	"slices"
+	"unsafe"
+)
+
+func GetModel(parent *qt.QObject) (*qt.QAbstractItemModel, *TreeItem) {
+	model := qt.NewQAbstractItemModel2(parent)
+
+	data := make([]*TreeCell, 0)
+	data = append(data, NewTreeCell(qt.DisplayRole, *qt.NewQVariant11("Name")))
+	data = append(data, NewTreeCell(qt.DisplayRole, *qt.NewQVariant11("Size")))
+	rootItem := NewTreeItem(data, nil, true)
+
+	model.OnData(func(index *qt.QModelIndex, role int) *qt.QVariant {
+		if !index.IsValid() {
+			return qt.NewQVariant()
+		}
+		if !slices.Contains([]qt.ItemDataRole{qt.DisplayRole, qt.UserRole}, qt.ItemDataRole(role)) {
+			return qt.NewQVariant()
+		}
+		item := (*TreeItem)(index.InternalPointer())
+		return item.Data(index.Column(), qt.ItemDataRole(role))
+	})
+
+	model.OnHeaderData(func(super func(section int, orientation qt.Orientation, role int) *qt.QVariant, section int, orientation qt.Orientation, role int) *qt.QVariant {
+		if qt.ItemDataRole(role) != qt.DisplayRole {
+			return qt.NewQVariant()
+		}
+		if orientation == qt.Horizontal && role == int(qt.DisplayRole) {
+			return rootItem.Data(section, qt.DisplayRole)
+		}
+		return qt.NewQVariant()
+	})
+
+	model.OnIndex(func(row int, column int, parent *qt.QModelIndex) *qt.QModelIndex {
+		if !model.HasIndex3(row, column, parent) {
+			return qt.NewQModelIndex()
+		}
+		var parentItem *TreeItem
+		if !parent.IsValid() {
+			parentItem = &rootItem
+		} else {
+			parentItem = (*TreeItem)(parent.InternalPointer())
+		}
+		child, err := parentItem.Child(row)
+		if err != nil {
+			return qt.NewQModelIndex()
+		}
+		newIndex := model.CreateIndex2(row, column, uintptr(unsafe.Pointer(child)))
+		return &newIndex
+	})
+
+	model.OnParent(func(child *qt.QModelIndex) *qt.QModelIndex {
+		if !child.IsValid() {
+			return qt.NewQModelIndex()
+		}
+		parentItem := (*TreeItem)(child.InternalPointer()).ParentItem()
+		if parentItem != &rootItem {
+			row, err := parentItem.Row()
+			if err != nil {
+				return qt.NewQModelIndex()
+			}
+			index := model.CreateIndex2(row, 0, uintptr(unsafe.Pointer(parentItem)))
+			return &index
+		}
+		return qt.NewQModelIndex()
+	})
+
+	model.OnRowCount(func(parent *qt.QModelIndex) int {
+		if parent.Column() > 0 {
+			return 0
+		}
+		var item *TreeItem
+		if !parent.IsValid() {
+			item = &rootItem
+		} else {
+			item = (*TreeItem)(parent.InternalPointer())
+		}
+		return item.ChildCount()
+	})
+
+	model.OnColumnCount(func(parent *qt.QModelIndex) int {
+		if parent.IsValid() {
+			item := (*TreeItem)(parent.InternalPointer())
+			return item.ColumnCount()
+		}
+		return rootItem.ColumnCount()
+	})
+
+	return model, &rootItem
+}
diff --git a/examples/customsortfiltermodel/tree_cell.go b/examples/customsortfiltermodel/tree_cell.go
new file mode 100644
index 00000000..a2fee3dc
--- /dev/null
+++ b/examples/customsortfiltermodel/tree_cell.go
@@ -0,0 +1,33 @@
+package main
+
+import (
+	"fmt"
+	qt "github.com/mappu/miqt/qt6"
+	"strconv"
+)
+
+type TreeCell struct {
+	data map[qt.ItemDataRole]string
+}
+
+func NewTreeCell(role qt.ItemDataRole, value qt.QVariant) *TreeCell {
+	data := make(map[qt.ItemDataRole]string)
+	data[role] = value.ToString()
+	return &TreeCell{data: data}
+}
+
+func (cell *TreeCell) SetData(data qt.QVariant, role qt.ItemDataRole) {
+	cell.data[role] = data.ToString()
+}
+
+func (cell *TreeCell) Data(role qt.ItemDataRole) *qt.QVariant {
+	if role == qt.UserRole {
+		number, err := strconv.Atoi(cell.data[role])
+		if err != nil {
+			fmt.Printf("Error converting %s to int: %v\n", number, err)
+			return qt.NewQVariant11(cell.data[role])
+		}
+		return qt.NewQVariant4(number)
+	}
+	return qt.NewQVariant11(cell.data[role])
+}
diff --git a/examples/customsortfiltermodel/tree_item.go b/examples/customsortfiltermodel/tree_item.go
new file mode 100644
index 00000000..d6ba0fa4
--- /dev/null
+++ b/examples/customsortfiltermodel/tree_item.go
@@ -0,0 +1,82 @@
+package main
+
+import (
+	"fmt"
+	"slices"
+
+	qt "github.com/mappu/miqt/qt6"
+)
+
+type TreeItem struct {
+	data     []*TreeCell
+	parent   *TreeItem
+	children []*TreeItem
+	isRoot   bool
+}
+
+func NewTreeItem(data []*TreeCell, parent *TreeItem, isRoot bool) TreeItem {
+	return TreeItem{
+		data:   data,
+		parent: parent,
+		isRoot: isRoot,
+	}
+}
+
+func (self *TreeItem) IsRoot() bool {
+	return self.isRoot
+}
+
+func (self *TreeItem) AppendChild(child *TreeItem) {
+	self.children = append(self.children, child)
+}
+
+func (self *TreeItem) Child(row int) (*TreeItem, error) {
+	if row >= 0 && row <= self.ChildCount() {
+		return self.children[row], nil
+	}
+	return nil, fmt.Errorf("TreeItem does not have child at row %d", row)
+}
+
+func (self *TreeItem) ColumnCount() int {
+	return len(self.data)
+}
+
+func (self *TreeItem) ChildCount() int {
+	return len(self.children)
+}
+
+func (self *TreeItem) Data(column int, role qt.ItemDataRole) *qt.QVariant {
+	if column >= 0 && column < len(self.data) {
+		if !slices.Contains([]qt.ItemDataRole{qt.DisplayRole, qt.UserRole}, role) {
+			return qt.NewQVariant()
+		}
+		return self.data[column].Data(role)
+	}
+	return qt.NewQVariant()
+}
+
+func (self *TreeItem) SetData(column int, value *qt.QVariant, role qt.ItemDataRole) error {
+	if column >= 0 && column < len(self.data) {
+		self.data[column].SetData(*value, role)
+	} else {
+		cell := NewTreeCell(role, *value)
+		self.data = append(self.data, cell)
+	}
+	return nil
+}
+
+func (self *TreeItem) Row() (int, error) {
+	if self.parent == nil {
+		return 0, nil
+	}
+	for index, child := range self.parent.children {
+		if child == self {
+			return index, nil
+		}
+	}
+	return 0, fmt.Errorf("could not find child in its parent")
+}
+
+func (self *TreeItem) ParentItem() *TreeItem {
+	return self.parent
+}

From acb51f0434777abd47d5b3694f01d41993bf9fd5 Mon Sep 17 00:00:00 2001
From: Alex Hughes <ahughesalex@gmail.com>
Date: Tue, 11 Feb 2025 11:31:51 -0800
Subject: [PATCH 2/3] Set userData for filename and swap to ParseInt

---
 examples/customsortfiltermodel/main.go      | 12 ++++++++++++
 examples/customsortfiltermodel/tree_cell.go |  4 ++--
 2 files changed, 14 insertions(+), 2 deletions(-)

diff --git a/examples/customsortfiltermodel/main.go b/examples/customsortfiltermodel/main.go
index f8861d0a..ca038f2c 100644
--- a/examples/customsortfiltermodel/main.go
+++ b/examples/customsortfiltermodel/main.go
@@ -28,6 +28,10 @@ func PopulateData(rootItem *TreeItem) {
 	if err != nil {
 		fmt.Printf("Error setting UserData: %v\n", err)
 	}
+	err = item.SetData(0, qt.NewQVariant11("Big File"), qt.UserRole)
+	if err != nil {
+		fmt.Printf("Error setting UserData: %v\n", err)
+	}
 	rootItem.AppendChild(&item)
 
 	cellData2 := make([]*TreeCell, 0)
@@ -38,6 +42,10 @@ func PopulateData(rootItem *TreeItem) {
 	if err != nil {
 		fmt.Printf("Error setting UserData: %v\n", err)
 	}
+	err = item.SetData(0, qt.NewQVariant11("Small File"), qt.UserRole)
+	if err != nil {
+		fmt.Printf("Error setting UserData: %v\n", err)
+	}
 	rootItem.AppendChild(&item2)
 
 	cellData3 := make([]*TreeCell, 0)
@@ -48,6 +56,10 @@ func PopulateData(rootItem *TreeItem) {
 	if err != nil {
 		fmt.Printf("Error setting UserData: %v\n", err)
 	}
+	err = item.SetData(0, qt.NewQVariant11("Medium File"), qt.UserRole)
+	if err != nil {
+		fmt.Printf("Error setting UserData: %v\n", err)
+	}
 	rootItem.AppendChild(&item3)
 }
 
diff --git a/examples/customsortfiltermodel/tree_cell.go b/examples/customsortfiltermodel/tree_cell.go
index a2fee3dc..3635c16e 100644
--- a/examples/customsortfiltermodel/tree_cell.go
+++ b/examples/customsortfiltermodel/tree_cell.go
@@ -22,12 +22,12 @@ func (cell *TreeCell) SetData(data qt.QVariant, role qt.ItemDataRole) {
 
 func (cell *TreeCell) Data(role qt.ItemDataRole) *qt.QVariant {
 	if role == qt.UserRole {
-		number, err := strconv.Atoi(cell.data[role])
+		number, err := strconv.ParseInt(cell.data[role], 10, 64)
 		if err != nil {
 			fmt.Printf("Error converting %s to int: %v\n", number, err)
 			return qt.NewQVariant11(cell.data[role])
 		}
-		return qt.NewQVariant4(number)
+		return qt.NewQVariant6(number)
 	}
 	return qt.NewQVariant11(cell.data[role])
 }

From 24dd359228889440789ed7a66e272e3406f79e4a Mon Sep 17 00:00:00 2001
From: Alex Hughes <ahughesalex@gmail.com>
Date: Tue, 11 Feb 2025 13:08:27 -0800
Subject: [PATCH 3/3] Sort either as a LongLong or as a String based on column
 number

---
 examples/customsortfiltermodel/main.go | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/examples/customsortfiltermodel/main.go b/examples/customsortfiltermodel/main.go
index ca038f2c..c7f51179 100644
--- a/examples/customsortfiltermodel/main.go
+++ b/examples/customsortfiltermodel/main.go
@@ -24,7 +24,7 @@ func PopulateData(rootItem *TreeItem) {
 	cellData = append(cellData, NewTreeCell(qt.DisplayRole, *qt.NewQVariant11("Big File")))
 	cellData = append(cellData, NewTreeCell(qt.DisplayRole, *qt.NewQVariant11("10.7 MiB")))
 	item := NewTreeItem(cellData, rootItem, false)
-	err := item.SetData(1, qt.NewQVariant11("11225400"), qt.UserRole)
+	err := item.SetData(1, qt.NewQVariant6(11225400), qt.UserRole)
 	if err != nil {
 		fmt.Printf("Error setting UserData: %v\n", err)
 	}
@@ -38,7 +38,7 @@ func PopulateData(rootItem *TreeItem) {
 	cellData2 = append(cellData2, NewTreeCell(qt.DisplayRole, *qt.NewQVariant11("Small File")))
 	cellData2 = append(cellData2, NewTreeCell(qt.DisplayRole, *qt.NewQVariant11("110 Bytes")))
 	item2 := NewTreeItem(cellData2, rootItem, false)
-	err = item2.SetData(1, qt.NewQVariant11("110"), qt.UserRole)
+	err = item2.SetData(1, qt.NewQVariant6(110), qt.UserRole)
 	if err != nil {
 		fmt.Printf("Error setting UserData: %v\n", err)
 	}
@@ -52,7 +52,7 @@ func PopulateData(rootItem *TreeItem) {
 	cellData3 = append(cellData3, NewTreeCell(qt.DisplayRole, *qt.NewQVariant11("Medium File")))
 	cellData3 = append(cellData3, NewTreeCell(qt.DisplayRole, *qt.NewQVariant11("109.5 KiB")))
 	item3 := NewTreeItem(cellData3, rootItem, false)
-	err = item3.SetData(1, qt.NewQVariant11("112200"), qt.UserRole)
+	err = item3.SetData(1, qt.NewQVariant6(112200), qt.UserRole)
 	if err != nil {
 		fmt.Printf("Error setting UserData: %v\n", err)
 	}
@@ -85,7 +85,11 @@ func GetDialog() *qt.QDialog {
 		// In our case we know that the data from our TreeItem will come back as an int for our UserRole
 		leftData := sortModel.SourceModel().Data(source_left, sortModel.SortRole())
 		rightData := sortModel.SourceModel().Data(source_right, sortModel.SortRole())
-		return leftData.ToInt() < rightData.ToInt()
+
+		if source_left.Column() == 0 {
+			return leftData.ToString() < rightData.ToString()
+		}
+		return leftData.ToLongLong() < rightData.ToLongLong()
 	})
 
 	sortModel.SetSourceModel(model)