Skip to content

Instantly share code, notes, and snippets.

@MikeWey
Created January 2, 2014 17:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MikeWey/8223114 to your computer and use it in GitHub Desktop.
Save MikeWey/8223114 to your computer and use it in GitHub Desktop.
A reorderable CustomList.
module CustomList;
import glib.RandG;
import gobject.Type;
import gobject.Value;
import gtk.SelectionData;
import gtk.TreeIter;
import gtk.TreePath;
import gtk.TreeModel;
import gtk.TreeDragDestT;
import gtk.TreeDragDestIF;
import gtk.TreeDragSourceT;
import gtk.TreeDragSourceIF;
//We need this function for the interface implementation.
extern(C) GType function() c_gtk_tree_drag_source_get_type;
extern(C) GType function() c_gtk_tree_drag_dest_get_type;
alias c_gtk_tree_drag_source_get_type gtk_tree_drag_source_get_type;
alias c_gtk_tree_drag_dest_get_type gtk_tree_drag_dest_get_type;
shared static this()
{
import gtkc.Loader;
import gtkc.paths;
Linker.link(gtk_tree_drag_source_get_type, "gtk_tree_drag_source_get_type", LIBRARY.GTK);
Linker.link(gtk_tree_drag_dest_get_type, "gtk_tree_drag_dest_get_type", LIBRARY.GTK);
}
struct CustomRecord
{
/* data - you can extend this */
string name;
uint yearBorn;
/* admin stuff used by the custom list model */
uint pos; /* pos within the array */
}
enum CustomListColumn
{
Record = 0,
Name,
YearBorn,
NColumns,
}
class CustomList : TreeModel, TreeDragDestIF, TreeDragSourceIF
{
uint numRows;
int nColumns;
int stamp;
GType[3] columnTypes;
CustomRecord*[] rows;
public this()
{
GType customTreeModelType = customTreeModelgetType();
GInterfaceInfo dragSourceInfo =
{
cast(GInterfaceInitFunc) &dragSourceInit,
null,
null
};
GInterfaceInfo dragDestInfo =
{
cast(GInterfaceInitFunc) &dragDestInit,
null,
null
};
Type.addInterfaceStatic (customTreeModelType, gtk_tree_drag_source_get_type(), &dragSourceInfo);
Type.addInterfaceStatic (customTreeModelType, gtk_tree_drag_dest_get_type(), &dragDestInfo);
super();
nColumns = columnTypes.length;
columnTypes[0] = GType.POINTER;
columnTypes[1] = GType.STRING;
columnTypes[2] = GType.UINT;
stamp = RandG.randomInt();
}
/*
* tells the rest of the world whether our tree model
* has any special characteristics. In our case,
* we have a list model (instead of a tree), and each
* tree iter is valid as long as the row in question
* exists, as it only contains a pointer to our struct.
*/
override GtkTreeModelFlags getFlags()
{
return (GtkTreeModelFlags.LIST_ONLY | GtkTreeModelFlags.ITERS_PERSIST);
}
/*
* tells the rest of the world how many data
* columns we export via the tree model interface
*/
override int getNColumns()
{
return nColumns;
}
/*
* tells the rest of the world which type of
* data an exported model column contains
*/
override GType getColumnType(int index)
{
if ( index >= nColumns || index < 0 )
return GType.INVALID;
return columnTypes[index];
}
/*
* converts a tree path (physical position) into a
* tree iter structure (the content of the iter
* fields will only be used internally by our model).
* We simply store a pointer to our CustomRecord
* structure that represents that row in the tree iter.
*/
override int getIter(TreeIter iter, TreePath path)
{
CustomRecord* record;
int[] indices;
int n, depth;
indices = path.getIndices();
depth = path.getDepth();
/* we do not allow children */
if (depth != 1)
return false;//throw new Exception("We only except lists");
n = indices[0]; /* the n-th top level row */
if ( n >= numRows || n < 0 )
return false;
record = rows[n];
if ( record is null )
throw new Exception("Not Exsisting record requested");
if ( record.pos != n )
throw new Exception("record.pos != TreePath.getIndices()[0]");
/* We simply store a pointer to our custom record in the iter */
iter.stamp = stamp;
iter.userData = record;
return true;
}
/*
* converts a tree iter into a tree path (ie. the
* physical position of that row in the list).
*/
override TreePath getPath(TreeIter iter)
{
TreePath path;
CustomRecord* record;
if ( iter is null || iter.userData is null || iter.stamp != stamp )
return null;
record = cast(CustomRecord*) iter.userData;
path = new TreePath(record.pos);
return path;
}
/*
* Returns a row's exported data columns
* (_get_value is what gtk_tree_model_get uses)
*/
override Value getValue(TreeIter iter, int column, Value value = null)
{
CustomRecord *record;
if ( value is null )
value = new Value();
if ( iter is null || column >= nColumns || iter.stamp != stamp )
return null;
value.init(columnTypes[column]);
record = cast(CustomRecord*) iter.userData;
if ( record is null || record.pos >= numRows )
return null;
switch(column)
{
case CustomListColumn.Record:
value.setPointer(record);
break;
case CustomListColumn.Name:
value.setString(record.name);
break;
case CustomListColumn.YearBorn:
value.setUint(record.yearBorn);
break;
default:
break;
}
return value;
}
/*
* Takes an iter structure and sets it to point
* to the next row.
*/
override int iterNext(TreeIter iter)
{
CustomRecord* record, nextrecord;
if ( iter is null || iter.userData is null || iter.stamp != stamp )
return false;
record = cast(CustomRecord*) iter.userData;
/* Is this the last record in the list? */
if ( (record.pos + 1) >= numRows)
return false;
nextrecord = rows[(record.pos + 1)];
if ( nextrecord is null || nextrecord.pos != record.pos + 1 )
throw new Exception("Invalid next record");
iter.stamp = stamp;
iter.userData = nextrecord;
return true;
}
/*
* Returns TRUE or FALSE depending on whether
* the row specified by 'parent' has any children.
* If it has children, then 'iter' is set to
* point to the first child. Special case: if
* 'parent' is NULL, then the first top-level
* row should be returned if it exists.
*/
override int iterChildren(TreeIter iter, TreeIter parent)
{
/* this is a list, nodes have no children */
if ( parent !is null )
return false;
/* No rows => no first row */
if ( numRows == 0 )
return false;
/* Set iter to first item in list */
iter.stamp = stamp;
iter.userData = rows[0];
return true;
}
/*
* Returns TRUE or FALSE depending on whether
* the row specified by 'iter' has any children.
* We only have a list and thus no children.
*/
override int iterHasChild(TreeIter iter)
{
return false;
}
/*
* Returns the number of children the row
* specified by 'iter' has. This is usually 0,
* as we only have a list and thus do not have
* any children to any rows. A special case is
* when 'iter' is NULL, in which case we need
* to return the number of top-level nodes,
* ie. the number of rows in our list.
*/
override int iterNChildren(TreeIter iter)
{
/* special case: if iter == NULL, return number of top-level rows */
if ( iter is null )
return numRows;
return 0; /* otherwise, this is easy again for a list */
}
/*
* If the row specified by 'parent' has any
* children, set 'iter' to the n-th child and
* return TRUE if it exists, otherwise FALSE.
* A special case is when 'parent' is NULL, in
* which case we need to set 'iter' to the n-th
* row if it exists.
*/
override int iterNthChild(TreeIter iter, TreeIter parent, int n)
{
CustomRecord *record;
/* a list has only top-level rows */
if( parent is null )
return false;
if( n >= numRows )
return false;
record = rows[n];
if ( record == null || record.pos != n )
throw new Exception("Invalid record");
iter.stamp = stamp;
iter.userData = record;
return true;
}
/*
* Point 'iter' to the parent node of 'child'. As
* we have a list and thus no children and no
* parents of children, we can just return FALSE.
*/
override int iterParent(TreeIter iter, TreeIter child)
{
return false;
}
/*
* Adds a record to the end o the list.
*/
void appendRecord(string name, uint yearBorn)
{
insertRecordAt(numRows, name, yearBorn);
}
/*
* Adds a record to the start of the list.
*/
void prependRecord(string name, uint yearBorn)
{
insertRecordAt(0, name, yearBorn);
}
/*
* Adds a record before the iter.
*/
void insertRecordBefore(TreeIter iter, string name, uint yearBorn)
{
CustomRecord* prevRecord = cast(CustomRecord*) iter.userData;
insertRecordAt(prevRecord.pos -1, name, yearBorn);
}
/*
* Adds a record after the iter.
*/
void insertRecordAfter(TreeIter iter, string name, uint yearBorn)
{
CustomRecord* prevRecord = cast(CustomRecord*) iter.userData;
insertRecordAt(prevRecord.pos +1, name, yearBorn);
}
/*
* Empty lists are boring. This function can
* be used in your own code to add rows to the
* list. Note how we emit the "row-inserted"
* signal after we have appended the row
* internally, so the tree view and other
* interested objects know about the new row.
*/
private void insertRecordAt(uint index, string name, uint yearBorn)
{
TreeIter iter;
TreePath path;
CustomRecord* newrecord;
if ( name is null )
return;
numRows++;
newrecord = new CustomRecord;
newrecord.name = name;
newrecord.yearBorn = yearBorn;
newrecord.pos = index;
if ( newrecord.pos == numRows-1 )
{
rows ~= newrecord;
}
else
{
rows = rows[0 .. index] ~ newrecord ~ rows[index .. $];
foreach(row; rows[index+1 .. $])
{
row.pos++;
}
}
/* inform the tree view and other interested objects
* (e.g. tree row references) that we have inserted
* a new row, and where it was inserted */
path = new TreePath(newrecord.pos);
iter = new TreeIter();
getIter(iter, path);
rowInserted(path, iter);
}
/*
* Remove an record from the list.
* Note how we emit the "row-deleted"
* signal after we have deleted the row
* internally, so the tree view and other
* interested objects know about the deleted row.
*/
void removeRecord(TreeIter iter)
{
CustomRecord* record, nextrecord;
TreePath path = getPath(iter);
if ( iter is null || iter.userData is null || iter.stamp != stamp )
return;
record = cast(CustomRecord*) iter.userData;
int pos = record.pos;
for ( ;(pos + 1) < numRows; pos++)
{
rows[pos] = rows[pos+1];
rows[pos].pos = pos;
}
rows = rows[0 .. $-1];
numRows--;
iter.userData = null;
rowDeleted(path);
}
/* Drag and drop implementation */
/*
* Can we drag the row to a different location.
*/
override int rowDraggable(TreePath path)
{
return true;
}
/*
* Set the drag data to the current model and path.
*/
override int dragDataGet(TreePath path, SelectionData selectionData)
{
if ( setRowDragData(selectionData, this, path) )
return true;
return false;
}
/*
* Remove the row where the drag originated.
*/
override int dragDataDelete(TreePath path)
{
TreeIter iter = new TreeIter();
if ( getIter(iter, path) )
{
removeRecord(iter);
return true;
}
return false;
}
/*
* Create an new row on the drop location.
*/
override int dragDataReceived(TreePath destPath, SelectionData selectionData)
{
TreeModelIF model;
TreeIter srcIter = new TreeIter();
TreeIter destIter = new TreeIter();
TreePath srcPath;
TreePath prevPath;
CustomRecord* record;
// Is there Drag data available.
if ( !getRowDragData(selectionData, model, srcPath) )
return false;
if ( !getIter(srcIter, srcPath) )
return false;
record = cast(CustomRecord*) srcIter.userData;
prevPath = destPath.copy();
if ( !prevPath.prev() )
{
// If dest is the first record prepend.
prependRecord(record.name, record.yearBorn);
}
else
{
getIter(destIter, prevPath);
insertRecordAfter(destIter, record.name, record.yearBorn);
}
return true;
}
/*
* Check if we can drop the row here.
*/
override int rowDropPossible(TreePath destPath, SelectionData selectionData)
{
TreeModelIF model;
TreePath srcPath;
// Is there data to drop.
if ( !getRowDragData(selectionData, model, srcPath) )
return false;
// we do not allow children
if ( destPath.getDepth != 1 )
return false;
if ( destPath.getIndices()[0] > numRows )
return false;
return true;
}
/* Interfacing with Gtk+ */
public this (GtkTreeDragSource* dragSource)
{
super(cast(GtkTreeModel*)dragSource);
}
public this (GtkTreeDragDest* dragDest)
{
super(cast(GtkTreeModel*)dragDest);
}
/** the main Gtk struct as a void* */
protected override void* getStruct()
{
return cast(void*)gtkTreeModel;
}
// add the TreeDragSource capabilities
mixin TreeDragSourceT!(CustomList);
// add the TreeDragDest capabilities
mixin TreeDragDestT!(CustomList);
extern(C)
{
static void dragSourceInit (GtkTreeDragSourceIface* iface)
{
iface.rowDraggable = &customDragSourceRowDraggable;
iface.dragDataGet = &customDragSourceDataGet;
iface.dragDataDelete = &customDragSourceDataDelete;
}
static void dragDestInit (GtkTreeDragDestIface* iface)
{
iface.dragDataReceived = &customDragDestDataReceved;
iface.rowDropPossible = &customDragDestDropPossible;
}
static int customDragSourceRowDraggable(GtkTreeDragSource* drag_source, GtkTreePath* path)
{
auto tm = ObjectG.getDObject!(CustomList)(drag_source);
return tm.rowDraggable(ObjectG.getDObject!(TreePath)(path));
}
static int customDragSourceDataGet(GtkTreeDragSource* drag_source, GtkTreePath* path, GtkSelectionData* selection_data)
{
auto tm = ObjectG.getDObject!(CustomList)(drag_source);
return tm.dragDataGet(ObjectG.getDObject!(TreePath)(path), ObjectG.getDObject!(SelectionData)(selection_data));
}
static int customDragSourceDataDelete(GtkTreeDragSource* drag_source, GtkTreePath* path)
{
auto tm = ObjectG.getDObject!(CustomList)(drag_source);
return tm.dragDataDelete(ObjectG.getDObject!(TreePath)(path));
}
static int customDragDestDataReceved(GtkTreeDragDest* drag_dest, GtkTreePath* dest, GtkSelectionData* selection_data)
{
auto tm = ObjectG.getDObject!(CustomList)(drag_dest);
return tm.dragDataReceived(ObjectG.getDObject!(TreePath)(dest), ObjectG.getDObject!(SelectionData)(selection_data));
}
static int customDragDestDropPossible(GtkTreeDragDest* drag_dest, GtkTreePath* dest, GtkSelectionData* selection_data)
{
auto tm = ObjectG.getDObject!(CustomList)(drag_dest);
return tm.rowDropPossible(ObjectG.getDObject!(TreePath)(dest), ObjectG.getDObject!(SelectionData)(selection_data));
}
}
}
module DemoCustomList;
import CustomList;
import glib.RandG;
import gtk.Main;
import gtk.ScrolledWindow;
import gtk.MainWindow;
import gtk.TreeView;
import gtk.TreeViewColumn;
import gtk.CellRendererText;
import gtk.ListStore;
class CustomListWindow : MainWindow
{
this()
{
super("GtkD - Custom TreeModel");
setDefaultSize(200, 400);
ScrolledWindow scrollwin = new ScrolledWindow();
TreeView view = createViewAndModel();
scrollwin.add(view);
add(scrollwin);
showAll();
}
TreeView createViewAndModel()
{
TreeViewColumn col;
CellRendererText renderer;
CustomList customlist;
TreeView view;
customlist = new CustomList();
fillModel(customlist);
view = new TreeView(customlist);
col = new TreeViewColumn();
renderer = new CellRendererText();
col.packStart(renderer, true);
col.addAttribute(renderer, "text", CustomListColumn.Name);
col.setTitle("Name");
view.appendColumn(col);
col = new TreeViewColumn();
renderer = new CellRendererText();
col.packStart(renderer, true);
col.addAttribute(renderer, "text", CustomListColumn.YearBorn);
col.setTitle("Year Born");
view.appendColumn(col);
view.setReorderable(true);
return view;
}
void fillModel (CustomList customlist)
{
string[] firstnames = [ "Joe", "Jane", "William", "Hannibal", "Timothy", "Gargamel" ];
string[] surnames = [ "Grokowich", "Twitch", "Borheimer", "Bork" ];
foreach (sname; surnames)
{
foreach (fname; firstnames)
{
customlist.appendRecord(fname ~" "~ sname, 1900 + (RandG.randomInt() % 100));
}
}
}
}
void main (string[] arg)
{
Main.init(arg);
new CustomListWindow();
Main.run();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment