extras/CheckListBox: Added 'IsFullyChecked()'
[sdk] / extras / gui / controls / CheckListBox.ec
1 #ifdef ECERE_STATIC
2 public import static "ecere"
3 #else
4 public import "ecere"
5 #endif
6
7 class CheckListBoxButton : Button
8 {
9    isCheckbox = true, inactive = true, size = { 12, 12 };
10
11    bool CheckListBox::NotifyPushed(Button button, int x, int y, Modifiers mods)
12    {
13       currentRow = (DataRow)(intptr)button.id;
14       ToggleCheck(currentRow);
15       return false;
16    }
17
18    bool CheckListBox::NotifyReleased(Button button, int x, int y, Modifiers mods)
19    {
20       return false;
21    }
22
23    bool OnMouseOver(int x, int y, Modifiers mods)
24    {
25
26       return true;
27    }
28
29    bool OnMouseLeave(Modifiers mods)
30    {
31
32       return true;
33    }
34 }
35
36 class CheckListBox : ListBox
37 {
38    Map<uintptr, CheckListBoxButton> buttonMaps { };
39    AVLTree<DataRow> rowChecks { };
40    AVLTree<DataRow> rowDisabled { };
41    int checkIndent;
42
43    checkIndent = 20;
44
45    fullRowSelect = false, collapseControl = true, treeBranches = true, rootCollapseButton = true,
46    noDragging = true;
47    // rowHeight = 18;
48
49    void OnDestroy()
50    {
51       buttonMaps.RemoveAll();
52    }
53
54    bool NotifyCollapse(CheckListBox listBox, DataRow row, bool collapsed)
55    {
56       DataRow r;
57       for(r = row.firstRow; r && r != row; )
58       {
59          if(collapsed)
60          {
61             MapIterator<uintptr, Button> it { map = listBox.buttonMaps };
62             if(it.Index((uintptr)r, false))
63             {
64                Button checkBox = it.data;
65                if(checkBox)
66                {
67                   checkBox.Destroy(0);
68                   it.Remove();
69                }
70             }
71          }
72          else
73          {
74             listBox.SetupButtons(r, false);
75          }
76          if(r.firstRow && !r.collapsed)
77             r = r.firstRow;
78          else
79             for(; r != row; r = r.parent)
80                if(r.next) { r = r.next; break; }
81       }
82       for(r = row.GetNextRow(); r; r = r.GetNextRow())
83       {
84          Button checkBox = listBox.buttonMaps[(uintptr)r];
85          if(checkBox)
86             checkBox.position.y = 1 + (r.index + listBox.hasHeader) * listBox.rowHeight;
87       }
88       return true;
89    }
90
91    bool CheckPartialChecks(DataRow row)
92    {
93       DataRow r;
94       for(r = row.firstRow; r; r = r.next)
95       {
96          if(rowChecks.Find(r) || CheckPartialChecks(r))
97             return true;
98       }
99       return false;
100    }
101
102    void SetupButtons(DataRow row, bool recurse)
103    {
104       DataRow parent;
105       CheckListBoxButton button;
106       int indent = checkIndent;
107
108       for(parent = row.parent; parent; parent = parent.parent) indent += 20;
109       button = buttonMaps[(uintptr)row];
110       if(!button) button = CheckListBoxButton { this };
111       button.position = { 2 + indent, 1+(row.index + hasHeader) * rowHeight };
112       button.id = (uintptr)row;
113
114       for(parent = row; parent; parent = parent.parent) if(rowChecks.Find(parent)) break;
115       if(parent)
116       {
117          button.checked = true;
118          button.buttonState = up;
119       }
120       else
121       {
122          button.checked = CheckPartialChecks(row);
123          button.buttonState = button.checked ? down : up;
124       }
125       if(rowDisabled.Find(row))
126          button.disabled = true;
127       button.Create();
128       buttonMaps[(uintptr)row] = button;
129       if(recurse && !row.collapsed)
130       {
131          DataRow r;
132          for(r = row.firstRow; r; r = r.next)
133             SetupButtons(r, recurse);
134       }
135    }
136
137    void UpdateButtons()
138    {
139       DataRow row;
140       for(row = firstRow; row; row = row.next)
141          SetupButtons(row, true);
142    }
143
144    bool OnCreate()
145    {
146       if(ListBox::OnCreate())
147       {
148          DataRow row;
149
150          buttonMaps.RemoveAll();
151
152          for(row = firstRow; row; row = row.next)
153             SetupButtons(row, true);
154          return true;
155       }
156       return false;
157    }
158
159    void ToggleCheck(DataRow row)
160    {
161       CheckListBoxButton checkBox = buttonMaps[(uintptr)row];
162       if(checkBox && !checkBox.disabled)
163       {
164          bool checked = false;
165          DataRow r;
166          for(r = row; r; r = r.parent)
167             if(rowChecks.Find(r))
168             {
169                checked = true;
170                break;
171             }
172          SetCheck(row, !checked);
173       }
174    }
175
176    void UncheckBoxes(DataRow row)
177    {
178       if(!row.parent || !row.parent.collapsed)
179       {
180          CheckListBoxButton button = buttonMaps[(uintptr)row];
181          if(button)
182          {
183             button.checked = false;
184             button.buttonState = up;
185          }
186       }
187       // if(!row.collapsed)
188       {
189          DataRow r;
190          for(r = row.firstRow; r; r = r.next)
191             UncheckBoxes(r);
192       }
193       NotifyChecked(master, this, row);
194    }
195
196    void UnsetChildren(DataRow row)
197    {
198       DataRow r;
199       CheckListBoxButton button = buttonMaps[(uintptr)row];
200       if(button)
201       {
202          button.checked = true;
203          button.buttonState = up;
204       }
205
206       for(r = row.firstRow; r; r = r.next)
207       {
208          Iterator<DataRow, DataRow> it { rowChecks };
209
210          if(it.Find(r))
211             it.Remove();
212          UnsetChildren(r);
213          NotifyChecked(master, this, r);
214       }
215    }
216
217    void SetCheck(DataRow row, bool checked)
218    {
219       DataRow parent;
220       bool wasChecked = false;
221
222       for(parent = row; parent; parent = parent.parent)
223       {
224          if(rowChecks.Find(parent)) { wasChecked = true; break; }
225       }
226       if(checked != wasChecked)
227       {
228          modifiedDocument = true;
229          // NotifyChecked(master, this, row);
230          if(checked)
231          {
232             DataRow rr = row;
233             // Check if all siblings are checked, if so go up until we reach a row not fully checked
234             while(rr)
235             {
236                DataRow r;
237                for(r = rr.parent.firstRow; r; r = r.next)
238                {
239                   if(r != rr && !rowChecks.Find(r))
240                      break;
241                }
242                if(r || !rr.parent) break;
243                rr = rr.parent;
244             }
245
246             rowChecks.Add(rr);
247
248             // Take out all children from rowChecks, checking them all
249             UnsetChildren(rr);
250
251             NotifyChecked(master, this, rr);
252
253             for(parent = rr.parent; parent; parent = parent.parent)
254             {
255                CheckListBoxButton button = buttonMaps[(uintptr)parent];
256                if(button)
257                {
258                   button.checked = true;
259                   button.buttonState = down;
260
261                   NotifyChecked(master, this, parent);
262                }
263             }
264          }
265          else
266          {
267             DataRow rr = row;
268
269             parent = rr.parent;
270             while(rr)
271             {
272                Iterator<DataRow, DataRow> it { rowChecks };
273                if(it.Find(rr))
274                {
275                   it.Remove();
276                   break;
277                }
278                else
279                {
280                   DataRow r;
281                   for(r = rr.parent.firstRow; r; r = r.next)
282                   {
283                      if(r != rr)
284                         rowChecks.Add(r);
285                   }
286                   rr = rr.parent;
287                }
288             }
289
290             UncheckBoxes(row);
291
292             for(; parent; parent = parent.parent)
293             {
294                CheckListBoxButton button = buttonMaps[(uintptr)parent];
295                if(button)
296                {
297                   if(CheckPartialChecks(parent))
298                   {
299                      button.checked = true;
300                      button.buttonState = down;
301                   }
302                   else
303                   {
304                      button.checked = false;
305                      button.buttonState = up;
306                   }
307
308                   NotifyChecked(master, this, parent);
309                }
310             }
311          }
312       }
313    }
314
315    bool NotifyKeyDown(CheckListBox listBox, DataRow row, Key key, unichar ch)
316    {
317       if(key == space)
318       {
319          listBox.ToggleCheck(row);
320          return false;
321       }
322       return true;
323    }
324
325    bool OnKeyHit(Key key, unichar ch)
326    {
327       if(key == space)
328          return false;
329       return ListBox::OnKeyHit(key, ch);
330    }
331
332    bool NotifyDoubleClick(CheckListBox listBox, int x, int y, Modifiers mods)
333    {
334       listBox.OnLeftButtonDown(x, y, mods);
335       return false;
336    }
337
338    bool NotifyReclick(CheckListBox listBox, DataRow row, Modifiers mods)
339    {
340       if(row == listBox.currentRow)
341          listBox.ToggleCheck(row);
342       return true;
343    }
344
345 public:
346    bool IsChecked(DataRow row)
347    {
348       CheckListBoxButton button = buttonMaps[(uintptr)row];
349       DataRow parent;
350       for(parent = row; parent; parent = parent.parent) if(rowChecks.Find(parent)) return true;
351       // For partially checked because of children:
352       if(button && button.checked)
353          return true;
354       return false;
355    }
356
357    bool IsFullyChecked(DataRow row)
358    {
359       DataRow parent;
360       for(parent = row; parent; parent = parent.parent) if(rowChecks.Find(parent)) return true;
361       return false;
362    }
363
364    virtual void Window::NotifyChecked(CheckListBox listBox, DataRow row);
365
366    void SetDisabled(DataRow row, bool disabled)
367    {
368       CheckListBoxButton checkBox = buttonMaps[(uintptr)row];
369       if(checkBox)
370          checkBox.disabled = disabled;
371
372       if(rowDisabled.Find(row))
373       {
374          if(!disabled)
375             rowDisabled.TakeOut(row);
376       }
377       else
378       {
379          if(disabled)
380             rowDisabled.Add(row);
381       }
382    }
383 };