1 module CustomList; 2 3 import glib.RandG; 4 import gobject.Value; 5 import gtk.TreeIter; 6 import gtk.TreePath; 7 import gtk.TreeModel; 8 9 struct CustomRecord 10 { 11 /* data - you can extend this */ 12 string name; 13 uint yearBorn; 14 15 /* admin stuff used by the custom list model */ 16 uint pos; /* pos within the array */ 17 } 18 19 enum CustomListColumn 20 { 21 Record = 0, 22 Name, 23 YearBorn, 24 NColumns, 25 } 26 27 class CustomList : TreeModel 28 { 29 uint numRows; 30 int nColumns; 31 int stamp; 32 GType[3] columnTypes; 33 CustomRecord*[] rows; 34 35 public this() 36 { 37 nColumns = columnTypes.length; 38 columnTypes[0] = GType.POINTER; 39 columnTypes[1] = GType.STRING; 40 columnTypes[2] = GType.UINT; 41 42 stamp = RandG.randomInt(); 43 } 44 45 /* 46 * tells the rest of the world whether our tree model 47 * has any special characteristics. In our case, 48 * we have a list model (instead of a tree), and each 49 * tree iter is valid as long as the row in question 50 * exists, as it only contains a pointer to our struct. 51 */ 52 override GtkTreeModelFlags getFlags() 53 { 54 return (GtkTreeModelFlags.LIST_ONLY | GtkTreeModelFlags.ITERS_PERSIST); 55 } 56 57 58 /* 59 * tells the rest of the world how many data 60 * columns we export via the tree model interface 61 */ 62 63 override int getNColumns() 64 { 65 return nColumns; 66 } 67 68 /* 69 * tells the rest of the world which type of 70 * data an exported model column contains 71 */ 72 override GType getColumnType(int index) 73 { 74 if ( index >= nColumns || index < 0 ) 75 return GType.INVALID; 76 77 return columnTypes[index]; 78 } 79 80 /* 81 * converts a tree path (physical position) into a 82 * tree iter structure (the content of the iter 83 * fields will only be used internally by our model). 84 * We simply store a pointer to our CustomRecord 85 * structure that represents that row in the tree iter. 86 */ 87 override int getIter(TreeIter iter, TreePath path) 88 { 89 CustomRecord* record; 90 int[] indices; 91 int n, depth; 92 93 indices = path.getIndices(); 94 depth = path.getDepth(); 95 96 /* we do not allow children */ 97 if (depth != 1) 98 return false;//throw new Exception("We only except lists"); 99 100 n = indices[0]; /* the n-th top level row */ 101 102 if ( n >= numRows || n < 0 ) 103 return false; 104 105 record = rows[n]; 106 107 if ( record is null ) 108 throw new Exception("Not Exsisting record requested"); 109 if ( record.pos != n ) 110 throw new Exception("record.pos != TreePath.getIndices()[0]"); 111 112 /* We simply store a pointer to our custom record in the iter */ 113 iter.stamp = stamp; 114 iter.userData = record; 115 116 return true; 117 } 118 119 120 /* 121 * converts a tree iter into a tree path (ie. the 122 * physical position of that row in the list). 123 */ 124 override TreePath getPath(TreeIter iter) 125 { 126 TreePath path; 127 CustomRecord* record; 128 129 if ( iter is null || iter.userData is null || iter.stamp != stamp ) 130 return null; 131 132 record = cast(CustomRecord*) iter.userData; 133 134 path = new TreePath(record.pos); 135 136 return path; 137 } 138 139 140 /* 141 * Returns a row's exported data columns 142 * (_get_value is what gtk_tree_model_get uses) 143 */ 144 145 override Value getValue(TreeIter iter, int column, Value value = null) 146 { 147 CustomRecord *record; 148 149 if ( value is null ) 150 value = new Value(); 151 152 if ( iter is null || column >= nColumns || iter.stamp != stamp ) 153 return null; 154 155 value.init(columnTypes[column]); 156 157 record = cast(CustomRecord*) iter.userData; 158 159 if ( record is null || record.pos >= numRows ) 160 return null; 161 162 switch(column) 163 { 164 case CustomListColumn.Record: 165 value.setPointer(record); 166 break; 167 168 case CustomListColumn.Name: 169 value.setString(record.name); 170 break; 171 172 case CustomListColumn.YearBorn: 173 value.setUint(record.yearBorn); 174 break; 175 176 default: 177 break; 178 } 179 180 return value; 181 } 182 183 184 /* 185 * Takes an iter structure and sets it to point 186 * to the next row. 187 */ 188 override int iterNext(TreeIter iter) 189 { 190 CustomRecord* record, nextrecord; 191 192 if ( iter is null || iter.userData is null || iter.stamp != stamp ) 193 return false; 194 195 record = cast(CustomRecord*) iter.userData; 196 197 /* Is this the last record in the list? */ 198 if ( (record.pos + 1) >= numRows) 199 return false; 200 201 nextrecord = rows[(record.pos + 1)]; 202 203 if ( nextrecord is null || nextrecord.pos != record.pos + 1 ) 204 throw new Exception("Invalid next record"); 205 206 iter.stamp = stamp; 207 iter.userData = nextrecord; 208 209 return true; 210 } 211 212 213 /* 214 * Returns TRUE or FALSE depending on whether 215 * the row specified by 'parent' has any children. 216 * If it has children, then 'iter' is set to 217 * point to the first child. Special case: if 218 * 'parent' is NULL, then the first top-level 219 * row should be returned if it exists. 220 */ 221 222 override int iterChildren(TreeIter iter, TreeIter parent) 223 { 224 /* this is a list, nodes have no children */ 225 if ( parent !is null ) 226 return false; 227 228 /* No rows => no first row */ 229 if ( numRows == 0 ) 230 return false; 231 232 /* Set iter to first item in list */ 233 iter.stamp = stamp; 234 iter.userData = rows[0]; 235 236 return true; 237 } 238 239 240 /* 241 * Returns TRUE or FALSE depending on whether 242 * the row specified by 'iter' has any children. 243 * We only have a list and thus no children. 244 */ 245 override int iterHasChild(TreeIter iter) 246 { 247 return false; 248 } 249 250 251 /* 252 * Returns the number of children the row 253 * specified by 'iter' has. This is usually 0, 254 * as we only have a list and thus do not have 255 * any children to any rows. A special case is 256 * when 'iter' is NULL, in which case we need 257 * to return the number of top-level nodes, 258 * ie. the number of rows in our list. 259 */ 260 override int iterNChildren(TreeIter iter) 261 { 262 /* special case: if iter == NULL, return number of top-level rows */ 263 if ( iter is null ) 264 return numRows; 265 266 return 0; /* otherwise, this is easy again for a list */ 267 } 268 269 270 /* 271 * If the row specified by 'parent' has any 272 * children, set 'iter' to the n-th child and 273 * return TRUE if it exists, otherwise FALSE. 274 * A special case is when 'parent' is NULL, in 275 * which case we need to set 'iter' to the n-th 276 * row if it exists. 277 */ 278 override int iterNthChild(TreeIter iter, TreeIter parent, int n) 279 { 280 CustomRecord *record; 281 282 /* a list has only top-level rows */ 283 if( parent is null ) 284 return false; 285 286 if( n >= numRows ) 287 return false; 288 289 record = rows[n]; 290 291 if ( record == null || record.pos != n ) 292 throw new Exception("Invalid record"); 293 294 iter.stamp = stamp; 295 iter.userData = record; 296 297 return true; 298 } 299 300 301 /* 302 * Point 'iter' to the parent node of 'child'. As 303 * we have a list and thus no children and no 304 * parents of children, we can just return FALSE. 305 */ 306 override int iterParent(TreeIter iter, TreeIter child) 307 { 308 return false; 309 } 310 311 /* 312 * Empty lists are boring. This function can 313 * be used in your own code to add rows to the 314 * list. Note how we emit the "row-inserted" 315 * signal after we have appended the row 316 * internally, so the tree view and other 317 * interested objects know about the new row. 318 */ 319 void appendRecord(string name, uint yearBorn) 320 { 321 TreeIter iter; 322 TreePath path; 323 CustomRecord* newrecord; 324 uint pos; 325 326 if ( name is null ) 327 return; 328 329 pos = numRows; 330 numRows++; 331 332 newrecord = new CustomRecord; 333 334 newrecord.name = name; 335 newrecord.yearBorn = yearBorn; 336 337 rows ~= newrecord; 338 newrecord.pos = pos; 339 340 /* inform the tree view and other interested objects 341 * (e.g. tree row references) that we have inserted 342 * a new row, and where it was inserted */ 343 344 path = new TreePath(pos); 345 346 iter = new TreeIter(); 347 getIter(iter, path); 348 349 rowInserted(path, iter); 350 } 351 }