extras/CheckListBox: Further tweaks for proper NotifyChanged callbacks
[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          checkBox.position.y = 1 + (r.index + listBox.hasHeader) * listBox.rowHeight;
81       }
82       return true;
83    }
84
85    bool CheckPartialChecks(DataRow row)
86    {
87       DataRow r;
88       for(r = row.firstRow; r; r = r.next)
89       {
90          if(rowChecks.Find(r) || CheckPartialChecks(r))
91             return true;
92       }
93       return false;
94    }
95    
96    void SetupButtons(DataRow row, bool recurse)
97    {
98       DataRow parent;
99       CheckListBoxButton button;
100       int indent = checkIndent;
101
102       for(parent = row.parent; parent; parent = parent.parent) indent += 20;
103       button = buttonMaps[(int)row];
104       if(!button) button = CheckListBoxButton { this };
105       button.position = { 2 + indent, 1+(row.index + hasHeader) * rowHeight };
106       button.id = (int)row;
107
108       for(parent = row; parent; parent = parent.parent) if(rowChecks.Find(parent)) break;
109       if(parent)
110       {
111          button.checked = true;
112          button.buttonState = up;
113       }
114       else
115       {
116          button.checked = CheckPartialChecks(row);
117          button.buttonState = button.checked ? down : up;
118       }
119       button.Create();
120       buttonMaps[(int)row] = button;
121       if(recurse && !row.collapsed)
122       {
123          DataRow r;
124          for(r = row.firstRow; r; r = r.next)
125             SetupButtons(r, recurse);
126       }
127    }
128
129    void UpdateButtons()
130    {
131       DataRow row;
132       for(row = firstRow; row; row = row.next)
133          SetupButtons(row, true);
134    }
135
136    bool OnCreate()
137    {
138       if(ListBox::OnCreate())
139       {
140          DataRow row;
141          
142          buttonMaps.RemoveAll();
143
144          for(row = firstRow; row; row = row.next)
145             SetupButtons(row, true);
146          return true;
147       }
148       return false;
149    }
150
151    void ToggleCheck(DataRow row)
152    {
153       CheckListBoxButton checkBox = buttonMaps[(int)row];
154       if(checkBox)
155       {
156          bool checked = false;
157          DataRow r;
158          for(r = row; r; r = r.parent)
159             if(rowChecks.Find(r))
160             { 
161                checked = true;
162                break;
163             }         
164          SetCheck(row, !checked);
165       }
166    }
167
168    void UncheckBoxes(DataRow row)
169    {
170       if(!row.parent || !row.parent.collapsed)
171       {
172          CheckListBoxButton button = buttonMaps[(int)row];
173          if(button)
174          {
175             bool wasChecked = button.checked;
176             button.checked = false;
177             button.buttonState = up;
178          }
179       }
180       // if(!row.collapsed)
181       {
182          DataRow r;
183          for(r = row.firstRow; r; r = r.next)
184             UncheckBoxes(r);
185       }
186       NotifyChanged(master, this, row);
187    }
188    
189    void UnsetChildren(DataRow row)
190    {
191       DataRow r;
192       CheckListBoxButton button = buttonMaps[(int)row];
193       if(button)
194       {
195          bool wasChecked = button.checked;
196          button.checked = true;
197          button.buttonState = up;
198       }
199
200       for(r = row.firstRow; r; r = r.next)
201       {
202          Iterator<DataRow> it { rowChecks };
203          if(it.Find(r))
204             it.Remove();
205          UnsetChildren(r);
206       }      
207       NotifyChanged(master, this, row);
208    }
209    
210    void SetCheck(DataRow row, bool checked)
211    {
212       DataRow parent;
213       bool wasChecked = false;
214
215       for(parent = row; parent; parent = parent.parent)
216       {
217          if(rowChecks.Find(parent)) { wasChecked = true; break; }
218       }
219       if(checked != wasChecked)
220       {
221          modifiedDocument = true;
222          // NotifyChanged(master, this, row);
223          if(checked)
224          {
225             DataRow rr = row;
226             // Check if all siblings are checked, if so go up until we reach a row not fully checked
227             while(rr)
228             {
229                DataRow r;
230                for(r = rr.parent.firstRow; r; r = r.next)
231                {
232                   if(r != rr && !rowChecks.Find(r))
233                      break;
234                }
235                if(r || !row.parent) break;
236                rr = rr.parent;
237             }
238
239             rowChecks.Add(row);
240
241             // Take out all children from rowChecks, checking them all
242             UnsetChildren(row);
243
244             for(parent = row.parent; parent; parent = parent.parent)
245             {
246                DataRow r;
247                CheckListBoxButton button = buttonMaps[(int)parent];
248                if(button)
249                {
250                   button.checked = true;
251                   button.buttonState = down;
252                }
253             }
254          }
255          else
256          {
257             DataRow rr = row;
258
259             while(rr)
260             {
261                Iterator<DataRow> it { rowChecks };
262                if(it.Find(rr))
263                {
264                   it.Remove();
265                   break;
266                }
267                else
268                {
269                   DataRow r;
270                   for(r = row.parent.firstRow; r; r = r.next)
271                   {
272                      if(r != rr)
273                         rowChecks.Add(r);
274                   }
275                   rr = rr.parent;
276                }
277             }
278             UncheckBoxes(row);
279
280             for(parent = row.parent; parent; parent = parent.parent)
281             {
282                CheckListBoxButton button = buttonMaps[(int)parent];
283                if(button)
284                {
285                   if(CheckPartialChecks(parent))
286                   {
287                      button.checked = true;
288                      button.buttonState = down;
289                   }
290                   else
291                   {
292                      button.checked = false;
293                      button.buttonState = up;
294                   }
295                }
296             }
297          }
298
299          NotifyChanged(master, this, row);
300       }
301    }
302    
303    bool NotifyKeyDown(CheckListBox listBox, DataRow row, Key key, unichar ch)
304    {
305       if(key == space)
306       {
307          listBox.ToggleCheck(row);
308          return false;
309       }
310       return true;
311    }
312
313    bool OnKeyHit(Key key, unichar ch)
314    {
315       if(key == space)
316          return false;
317       return ListBox::OnKeyHit(key, ch);
318    }
319
320    bool NotifyDoubleClick(CheckListBox listBox, int x, int y, Modifiers mods)
321    {
322       listBox.OnLeftButtonDown(x, y, mods);
323       return false;
324    }
325
326    bool NotifyReclick(CheckListBox listBox, DataRow row, Modifiers mods)
327    {
328       if(row == listBox.currentRow)
329          listBox.ToggleCheck(row);
330       return true;
331    }
332
333 public:
334    bool IsChecked(DataRow row)
335    {
336       DataRow parent;
337       for(parent = row; parent; parent = parent.parent) if(rowChecks.Find(parent)) return true;
338       return false;
339    }
340
341    // virtual void Window::NotifyChecked(CheckListBox listBox, DataRow row);
342 };