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