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(); 135 path.appendIndex(record.pos); 136 137 return path; 138 } 139 140 141 /* 142 * Returns a row's exported data columns 143 * (_get_value is what gtk_tree_model_get uses) 144 */ 145 146 override Value getValue(TreeIter iter, int column, Value value = null) 147 { 148 CustomRecord *record; 149 150 if ( value is null ) 151 value = new Value(); 152 153 if ( iter is null || column >= nColumns || iter.stamp != stamp ) 154 return null; 155 156 value.init(columnTypes[column]); 157 158 record = cast(CustomRecord*) iter.userData; 159 160 if ( record is null || record.pos >= numRows ) 161 return null; 162 163 switch(column) 164 { 165 case CustomListColumn.Record: 166 value.setPointer(record); 167 break; 168 169 case CustomListColumn.Name: 170 value.setString(record.name); 171 break; 172 173 case CustomListColumn.YearBorn: 174 value.setUint(record.yearBorn); 175 break; 176 177 default: 178 break; 179 } 180 181 return value; 182 } 183 184 185 /* 186 * Takes an iter structure and sets it to point 187 * to the next row. 188 */ 189 override int iterNext(TreeIter iter) 190 { 191 CustomRecord* record, nextrecord; 192 193 if ( iter is null || iter.userData is null || iter.stamp != stamp ) 194 return false; 195 196 record = cast(CustomRecord*) iter.userData; 197 198 /* Is this the last record in the list? */ 199 if ( (record.pos + 1) >= numRows) 200 return false; 201 202 nextrecord = rows[(record.pos + 1)]; 203 204 if ( nextrecord is null || nextrecord.pos != record.pos + 1 ) 205 throw new Exception("Invalid next record"); 206 207 iter.stamp = stamp; 208 iter.userData = nextrecord; 209 210 return true; 211 } 212 213 214 /* 215 * Returns TRUE or FALSE depending on whether 216 * the row specified by 'parent' has any children. 217 * If it has children, then 'iter' is set to 218 * point to the first child. Special case: if 219 * 'parent' is NULL, then the first top-level 220 * row should be returned if it exists. 221 */ 222 223 override int iterChildren(TreeIter iter, TreeIter parent) 224 { 225 /* this is a list, nodes have no children */ 226 if ( parent !is null ) 227 return false; 228 229 /* No rows => no first row */ 230 if ( numRows == 0 ) 231 return false; 232 233 /* Set iter to first item in list */ 234 iter.stamp = stamp; 235 iter.userData = rows[0]; 236 237 return true; 238 } 239 240 241 /* 242 * Returns TRUE or FALSE depending on whether 243 * the row specified by 'iter' has any children. 244 * We only have a list and thus no children. 245 */ 246 override int iterHasChild(TreeIter iter) 247 { 248 return false; 249 } 250 251 252 /* 253 * Returns the number of children the row 254 * specified by 'iter' has. This is usually 0, 255 * as we only have a list and thus do not have 256 * any children to any rows. A special case is 257 * when 'iter' is NULL, in which case we need 258 * to return the number of top-level nodes, 259 * ie. the number of rows in our list. 260 */ 261 override int iterNChildren(TreeIter iter) 262 { 263 /* special case: if iter == NULL, return number of top-level rows */ 264 if ( iter is null ) 265 return numRows; 266 267 return 0; /* otherwise, this is easy again for a list */ 268 } 269 270 271 /* 272 * If the row specified by 'parent' has any 273 * children, set 'iter' to the n-th child and 274 * return TRUE if it exists, otherwise FALSE. 275 * A special case is when 'parent' is NULL, in 276 * which case we need to set 'iter' to the n-th 277 * row if it exists. 278 */ 279 override int iterNthChild(TreeIter iter, TreeIter parent, int n) 280 { 281 CustomRecord *record; 282 283 /* a list has only top-level rows */ 284 if( parent !is null ) 285 return false; 286 287 if( n >= numRows ) 288 return false; 289 290 record = rows[n]; 291 292 if ( record == null || record.pos != n ) 293 throw new Exception("Invalid record"); 294 295 iter.stamp = stamp; 296 iter.userData = record; 297 298 return true; 299 } 300 301 302 /* 303 * Point 'iter' to the parent node of 'child'. As 304 * we have a list and thus no children and no 305 * parents of children, we can just return FALSE. 306 */ 307 override int iterParent(TreeIter iter, TreeIter child) 308 { 309 return false; 310 } 311 312 /* 313 * Empty lists are boring. This function can 314 * be used in your own code to add rows to the 315 * list. Note how we emit the "row-inserted" 316 * signal after we have appended the row 317 * internally, so the tree view and other 318 * interested objects know about the new row. 319 */ 320 void appendRecord(string name, uint yearBorn) 321 { 322 TreeIter iter; 323 TreePath path; 324 CustomRecord* newrecord; 325 uint pos; 326 327 if ( name is null ) 328 return; 329 330 pos = numRows; 331 numRows++; 332 333 newrecord = new CustomRecord; 334 335 newrecord.name = name; 336 newrecord.yearBorn = yearBorn; 337 338 rows ~= newrecord; 339 newrecord.pos = pos; 340 341 /* inform the tree view and other interested objects 342 * (e.g. tree row references) that we have inserted 343 * a new row, and where it was inserted */ 344 345 path = new TreePath(); 346 path.appendIndex(pos); 347 348 iter = new TreeIter(); 349 getIter(iter, path); 350 351 rowInserted(path, iter); 352 } 353 }