ecere/gui/Anchor: Fixed stringification on empty anchor
[sdk] / ecere / src / gui / Anchor.ec
1 namespace gui;
2
3 import "Window"
4
5 public struct AnchorValue
6 {
7    AnchorValueType type;
8
9    union
10    {
11       int distance;
12       float percent;
13    };
14    property MinMaxValue
15    {
16       set { distance = value; type = offset; }
17       get { return distance; }
18    }
19    property int
20    {
21       set { distance = value; type = offset; }
22       get { return distance; }
23    }
24    property double
25    {
26       set { percent = (float) value; type = relative; }
27       get { return (double) percent; }
28    }
29
30    const char * OnGetString(char * stringOutput, void * fieldData, bool * needClass)
31    {
32       if(type == offset)
33       {
34          sprintf(stringOutput, "%d", distance);
35       }
36       else if(type == relative)
37       {
38          int c;
39          int last = 0;
40          sprintf(stringOutput, "%f", percent);
41          c = strlen(stringOutput)-1;
42          for( ; c >= 0; c--)
43          {
44             if(stringOutput[c] != '0')
45                last = Max(last, c);
46             if(stringOutput[c] == '.')
47             {
48                if(last == c)
49                {
50                   stringOutput[c+1] = '0';
51                   stringOutput[c+2] = 0;
52                }
53                else
54                   stringOutput[last+1] = 0;
55                break;
56             }
57          }
58       }
59       if(needClass) *needClass = false;
60       return stringOutput;
61    }
62
63    bool OnGetDataFromString(const char * stringOutput)
64    {
65       char * end;
66       if(strchr(stringOutput, '.'))
67       {
68          float percent = (float)strtod(stringOutput, &end);
69
70          if(end != stringOutput)
71          {
72             this.percent = percent;
73             type = relative;
74             return true;
75          }
76       }
77       else if(stringOutput[0])
78       {
79          int distance = strtol(stringOutput, &end, 0);
80          if(end != stringOutput)
81          {
82             this.distance = distance;
83             type = offset;
84             return true;
85          }
86       }
87       else
88       {
89          distance = 0;
90          type = 0;
91          return true;
92       }
93       return false;
94    }
95 };
96
97 public struct MiddleAnchorValue
98 {
99    AnchorValueType type;
100
101    union
102    {
103       int distance;
104       float percent;
105    };
106    property MinMaxValue
107    {
108       set { distance = value; type = none; }
109       get { return distance; }
110    }
111    property int
112    {
113       set { distance = value; type = none; }
114       get { return distance; }
115    }
116    property double
117    {
118       set { percent = (float) value; type = middleRelative; }
119       get { return (double) percent; }
120    }
121
122    const char * OnGetString(char * stringOutput, void * fieldData, bool * needClass)
123    {
124       if(type == middleRelative)
125       {
126          int c;
127          int last = 0;
128          sprintf(stringOutput, "%f", percent);
129          c = strlen(stringOutput)-1;
130          for( ; c >= 0; c--)
131          {
132             if(stringOutput[c] != '0')
133                last = Max(last, c);
134             if(stringOutput[c] == '.')
135             {
136                if(last == c)
137                {
138                   stringOutput[c+1] = '0';
139                   stringOutput[c+2] = 0;
140                }
141                else
142                   stringOutput[last+1] = 0;
143                break;
144             }
145          }
146       }
147       else if(type == none && distance)
148       {
149          sprintf(stringOutput, "%d", distance);
150       }
151       if(needClass) *needClass = false;
152       return stringOutput;
153    }
154
155    bool OnGetDataFromString(const char * stringOutput)
156    {
157       if(strchr(stringOutput, '.'))
158       {
159          percent = (float)strtod(stringOutput, null);
160          type = middleRelative;
161       }
162       else
163       {
164          distance = strtol(stringOutput, null, 0);
165          type = none;
166       }
167       return true;
168    }
169 };
170
171 public enum AnchorValueType { none, offset, relative, middleRelative, cascade, vTiled, hTiled };
172
173 public struct Anchor
174 {
175    union { AnchorValue left; MiddleAnchorValue horz; };
176    union { AnchorValue top; MiddleAnchorValue vert; };
177    AnchorValue right, bottom;
178
179    const char * OnGetString(char * stringOutput, void * fieldData, bool * needClass)
180    {
181       char tempString[256];
182       const char * anchorValue;
183       bool subNeedClass;
184
185       stringOutput[0] = 0;
186       tempString[0] = '\0';
187       anchorValue = left.OnGetString(tempString, null, &subNeedClass);
188       if(anchorValue[0]) { if(stringOutput[0]) strcat(stringOutput, ", "); strcat(stringOutput, "left = "); strcat(stringOutput, anchorValue); }
189
190       //if(((!left.type && !right.type) && horz.distance) || horz.type == middleRelative)
191       if(!right.type && ((!left.type && horz.distance) || horz.type == middleRelative))
192       {
193          tempString[0] = '\0';
194          anchorValue = horz.OnGetString(tempString, null, &subNeedClass);
195          if(anchorValue[0]) { if(stringOutput[0]) strcat(stringOutput, ", "); strcat(stringOutput, "horz = "); strcat(stringOutput, anchorValue); }
196       }
197
198       tempString[0] = '\0';
199       anchorValue = top.OnGetString(tempString, null, &subNeedClass);
200       if(anchorValue[0]) { if(stringOutput[0]) strcat(stringOutput, ", "); strcat(stringOutput, "top = "); strcat(stringOutput, anchorValue); }
201
202       tempString[0] = '\0';
203       anchorValue = right.OnGetString(tempString, null, &subNeedClass);
204       if(anchorValue[0]) { if(stringOutput[0]) strcat(stringOutput, ", "); strcat(stringOutput, "right = "); strcat(stringOutput, anchorValue); }
205
206       // if(((!top.type && !bottom.type) && vert.distance) || vert.type == middleRelative)
207       if(!bottom.type && ((!top.type && vert.distance) || vert.type == middleRelative))
208       {
209          tempString[0] = '\0';
210          anchorValue = vert.OnGetString(tempString, null, &subNeedClass);
211          if(anchorValue[0]) { if(stringOutput[0]) strcat(stringOutput, ", "); strcat(stringOutput, "vert = "); strcat(stringOutput, anchorValue); }
212       }
213
214       tempString[0] = '\0';
215       anchorValue = bottom.OnGetString(tempString, null, &subNeedClass);
216       if(anchorValue[0]) { if(stringOutput[0]) strcat(stringOutput, ", "); strcat(stringOutput, "bottom = "); strcat(stringOutput, anchorValue); }
217
218       return stringOutput;
219    }
220
221    bool OnGetDataFromString(const char * string)
222    {
223       this = Anchor {};
224       return class::OnGetDataFromString(string);
225    }
226
227    bool OnSaveEdit(DropBox dropBox, void * object)
228    {
229       return dropBox.Save();
230    }
231
232    Window OnEdit(Window listBox, Window master, int x, int y, int w, int h, Window control)
233    {
234       const char * string = "";
235       AnchorDropBox comboBox
236       {
237          editText = true;
238          parent = listBox;
239          master = master;
240          position = Point { x, y };
241          //clientSize = Size { h = h };
242          //size.w = w;
243          size = { w, h };
244          anchorValue = this;
245          control = control;
246          borderStyle = 0;
247       };
248
249       comboBox.Create();
250
251       {
252          char tempString[MAX_F_STRING] = "";
253          bool needClass = false;
254          const char * result = OnGetString(tempString, null, &needClass);
255          if(result) string = result;
256       }
257       comboBox.contents = string;
258       return comboBox;
259    }
260 };
261
262 private class AnchorButton : Button
263 {
264    toggle = true, bevel = false;
265
266    void OnRedraw(Surface surface)
267    {
268       int cw = clientSize.w;
269       int ch = clientSize.h;
270
271       surface.SetForeground(black);
272       if(checked)
273       {
274          surface.SetBackground(Color { 85,85,85 });
275          surface.Area(0,0, cw-1, ch-1);
276       }
277       else
278          surface.LineStipple(0xAAAA);
279
280       surface.Rectangle(0,0,cw-1,ch-1);
281
282       if(active)
283       {
284          surface.LineStipple(0xAAAA);
285          surface.Rectangle(2,2,cw-3,ch-3);
286       }
287    }
288
289    bool AnchorEditor::NotifyClicked(Button button, int x, int y, Modifiers mods)
290    {
291       AnchorDropBox anchorDropBox = (AnchorDropBox)master;
292       Anchor anchor = anchorDropBox.anchorValue;
293       Window control = anchorDropBox.control;
294       DataBox dropMaster = (DataBox)anchorDropBox.master;
295       int64 id = button.id;
296
297       switch(id)
298       {
299          case 0: anchor.left.type   = button.checked ? offset : none; break;
300          case 1: anchor.top.type    = button.checked ? offset : none; break;
301          case 2: anchor.right.type  = button.checked ? offset : none; break;
302          case 3: anchor.bottom.type = button.checked ? offset : none; break;
303       }
304
305       if(anchor.horz.type == middleRelative && (id == 0 || id == 2))
306       {
307          anchorDropBox.relButtons[0].checked = false;
308          anchorDropBox.relButtons[2].checked = false;
309       }
310       if(anchor.vert.type == middleRelative && (id == 1 || id == 3))
311       {
312          anchorDropBox.relButtons[1].checked = false;
313          anchorDropBox.relButtons[3].checked = false;
314       }
315       anchorDropBox.relButtons[id].checked = false;
316
317       //anchor.horz.type = none;
318       //anchor.vert.type = none;
319
320       {
321          int vpw, vph;
322          int x,y,w,h;
323          Window parent = control.parent;
324
325          // Fix Anchor
326          x = control.position.x;
327          y = control.position.y;
328          w = control.size.w;
329          h = control.size.h;
330
331          vpw = parent.clientSize.w;
332          vph = parent.clientSize.h;
333          if(control.nonClient)
334          {
335             vpw = parent.size.w;
336             vph = parent.size.h;
337          }
338          else if(((BorderBits)control.borderStyle).fixed)
339          {
340             if(!control.dontScrollHorz && parent.scrollArea.w) vpw = parent.scrollArea.w;
341             if(!control.dontScrollVert && parent.scrollArea.h) vph = parent.scrollArea.h;
342          }
343
344          if(anchor.left.type == offset) anchor.left.distance = x;
345          else if(anchor.left.type == relative) anchor.left.percent = (float)x / vpw;
346          if(anchor.top.type == offset) anchor.top.distance = y;
347          else if(anchor.top.type == relative) anchor.top.percent = (float)y / vph;
348          if(anchor.right.type == offset) anchor.right.distance = vpw - (x + w);
349          //else if(anchor.right.type == relative) anchor.right.percent = (float) (x + w) / vpw;
350          else if(anchor.right.type == relative) anchor.right.percent = (float) (vpw - (x + w)) / vpw;
351          if(anchor.bottom.type == offset) anchor.bottom.distance = vph - (y + h);
352          //else if(anchor.bottom.type == relative) anchor.bottom.percent = (float) (y + h) / vph;
353          else if(anchor.bottom.type == relative) anchor.bottom.percent = (float) (vph - (y + h)) / vph;
354
355          if(!anchor.left.type && !anchor.right.type)
356          {
357             anchor.horz.distance = (x + w / 2) - (vpw / 2);
358             //anchor.horz.type = anchor.horz.distance ? offset : 0;
359          }
360          else if(anchor.horz.type == middleRelative) anchor.horz.percent = (float) ((x + w / 2) - (vpw / 2)) / vpw;
361          if(!anchor.top.type && !anchor.bottom.type)
362          {
363             anchor.vert.distance = (y + h / 2) - (vph / 2);
364             //anchor.vert.type = anchor.vert.distance ? offset : 0;
365          }
366          else if(anchor.vert.type == middleRelative) anchor.vert.percent = (float)((y + h / 2) - (vph / 2)) / vph;
367       }
368
369       {
370          char tempString[1024] = "";
371          bool needClass = false;
372          const char * string = anchor.OnGetString(tempString, null, &needClass);
373          anchorDropBox.contents = string;
374       }
375
376       dropMaster.SetData(&anchor, false);
377       anchorDropBox.anchorValue = anchor;
378       return true;
379    }
380 }
381
382 private class AnchorRelButton : Button
383 {
384    toggle = true;
385    bevel = false;
386    text = "%";
387    //bevelOver = true;
388
389    void OnRedraw(Surface surface)
390    {
391       int cw = clientSize.w;
392       int ch = clientSize.h;
393
394       if(checked)
395       {
396          surface.SetForeground(black);
397       }
398       else
399       {
400          surface.SetForeground(Color{170,170,170});
401       }
402       surface.WriteText(5,2, "%", 1);
403
404       if(active)
405       {
406          surface.LineStipple(0xAAAA);
407          surface.Rectangle(3,3,cw-4,ch-4);
408       }
409    }
410
411    bool AnchorEditor::NotifyClicked(Button button, int x, int y, Modifiers mods)
412    {
413       AnchorDropBox anchorDropBox = (AnchorDropBox)master;
414       Anchor anchor = anchorDropBox.anchorValue;
415       Window control = anchorDropBox.control;
416       DataBox dropMaster = (DataBox)anchorDropBox.master;
417       int64 id = button.id;
418
419       if((id == 0 || id == 2) && ((!anchor.left.type && !anchor.right.type) || anchor.left.type == middleRelative))
420       {
421          if(button.checked) anchor.horz.type = middleRelative; else anchor.horz.type = none;
422          anchorDropBox.relButtons[(id + 2)%4].checked = button.checked;
423       }
424       else if((id == 1 || id == 3) && ((!anchor.top.type && !anchor.bottom.type) || anchor.top.type == middleRelative))
425       {
426          if(button.checked) anchor.vert.type = middleRelative; else anchor.vert.type = none;
427          anchorDropBox.relButtons[(id + 2)%4].checked = button.checked;
428       }
429       else
430       {
431          switch(id)
432          {
433             case 0: anchor.left.type   = button.checked ? relative : (anchor.left.type   ? offset : none); break;
434             case 1: anchor.top.type    = button.checked ? relative : (anchor.top.type    ? offset : none); break;
435             case 2: anchor.right.type  = button.checked ? relative : (anchor.right.type  ? offset : none); break;
436             case 3: anchor.bottom.type = button.checked ? relative : (anchor.bottom.type ? offset : none); break;
437          }
438          anchorDropBox.buttons[id].checked = true;
439          if(anchor.horz.type == middleRelative) anchor.horz.type = none;
440          if(anchor.vert.type == middleRelative) anchor.vert.type = none;
441       }
442
443       {
444          int vpw, vph;
445          int x,y,w,h;
446          Window parent = control.parent;
447
448          // Fix Anchor
449          x = control.position.x;
450          y = control.position.y;
451          w = control.size.w;
452          h = control.size.h;
453
454          vpw = parent.clientSize.w;
455          vph = parent.clientSize.h;
456          if(control.nonClient)
457          {
458             vpw = parent.size.w;
459             vph = parent.size.h;
460          }
461          else if(((BorderBits)control.borderStyle).fixed)
462          {
463             if(!control.dontScrollHorz && parent.scrollArea.w)  vpw = parent.scrollArea.w;
464             if(!control.dontScrollVert && parent.scrollArea.h) vph = parent.scrollArea.h;
465          }
466
467          if(anchor.left.type == offset) anchor.left.distance = x;
468          else if(anchor.left.type == relative) anchor.left.percent = (float)x / vpw;
469          if(anchor.top.type == offset) anchor.top.distance = y;
470          else if(anchor.top.type == relative) anchor.top.percent = (float)y / vph;
471          if(anchor.right.type == offset) anchor.right.distance = vpw - (x + w);
472          //else if(anchor.right.type == relative) anchor.right.percent = (float) (x + w) / vpw;
473          else if(anchor.right.type == relative) anchor.right.percent = (float) (vpw - (x + w)) / vpw;
474          if(anchor.bottom.type == offset) anchor.bottom.distance = vph - (y + h);
475          //else if(anchor.bottom.type == relative) anchor.bottom.percent = (float) (y + h) / vph;
476          else if(anchor.bottom.type == relative) anchor.bottom.percent = (float) (vph - (y + h)) / vph;
477
478          if(!anchor.left.type && !anchor.right.type)
479          {
480             anchor.horz.distance = (x + w / 2) - (vpw / 2);
481             //anchor.horz.type = anchor.horz.distance ? offset : none;
482          }
483          else if(anchor.horz.type == middleRelative) anchor.horz.percent = (float) ((x + w / 2) - (vpw / 2)) / vpw;
484          if(!anchor.top.type && !anchor.bottom.type)
485          {
486             anchor.vert.distance = (y + h / 2) - (vph / 2);
487             //anchor.vert.type = anchor.vert.distance ? offset : none;
488          }
489          else if(anchor.vert.type == middleRelative) anchor.vert.percent = (float)((y + h / 2) - (vph / 2)) / vph;
490       }
491
492       {
493          char tempString[1024] = "";
494          bool needClass = false;
495          const char * string = anchor.OnGetString(tempString, null, &needClass);
496          anchorDropBox.contents = string;
497       }
498
499       dropMaster.SetData(&anchor, false);
500       anchorDropBox.anchorValue = anchor;
501       return true;
502    }
503 }
504
505 private class AnchorEditor : Window
506 {
507    interim = true;
508    borderStyle = deepContour;
509    size.h = 92;
510
511    bool OnKeyDown(Key key, unichar ch)
512    {
513       if(key == escape)
514          return master.OnKeyDown(key, ch);
515       return true;
516    }
517 }
518
519 private class AnchorDropBox : DropBox
520 {
521    Anchor anchorValue;
522    Window control;
523    Button relButtons[4], buttons[4];
524
525    AnchorEditor anchorEditor
526    {
527       master = this;
528       autoCreate = false;
529    };
530
531    Window OnDropDown()
532    {
533       int c;
534       Button
535       {
536          anchorEditor,
537          anchor = Anchor { left = 28, top = 28, right = 28, bottom = 28 },
538          inactive = true, disabled = true
539       };
540       for(c = 0; c<4; c++)
541       {
542          Button button = buttons[c] = AnchorButton
543          {
544             anchorEditor, id = c,
545             size = Size { (c%2)?10:28, (c%2)?28:10 }
546          };
547          Button relButton = relButtons[c] = AnchorRelButton
548          {
549             anchorEditor, id = c;
550          };
551
552          switch(c)
553          {
554             case 0:
555                if(anchorValue.left.type && anchorValue.left.type != middleRelative) button.checked = true;
556                if(anchorValue.left.type == relative || anchorValue.horz.type == middleRelative) relButton.checked = true;
557
558                button.anchor = Anchor { left = 0 };
559                relButton.anchor = Anchor { left = 5, vert = 16 };
560                break;
561             case 1:
562                if(anchorValue.top.type && anchorValue.top.type != middleRelative) button.checked = true;
563                if(anchorValue.top.type == relative || anchorValue.vert.type == middleRelative) relButton.checked = true;
564
565                button.anchor = Anchor { top = 0 };
566                relButton.anchor = Anchor { top = 5, horz = 16 };
567                break;
568             case 2:
569                if(anchorValue.right.type && anchorValue.right.type != middleRelative) button.checked = true;
570                if(anchorValue.right.type == relative || anchorValue.horz.type == middleRelative) relButton.checked = true;
571
572                button.anchor = Anchor { right = 0 };
573                relButton.anchor = Anchor { right = 5, vert = 16 };
574                break;
575             case 3:
576                if(anchorValue.bottom.type && anchorValue.bottom.type != middleRelative) button.checked = true;
577                if(anchorValue.bottom.type == relative || anchorValue.vert.type == middleRelative) relButton.checked = true;
578
579                button.anchor = Anchor { bottom = 0 };
580                relButton.anchor = Anchor { bottom = 5, horz = 16 };
581                break;
582          }
583       }
584       anchorEditor.Create();
585       return anchorEditor;
586    }
587
588    void OnCloseDropDown(Window anchorEditor)
589    {
590       // TOFIX: Patch for update bug
591       master.Update(null);
592       anchorEditor.Destroy(0);
593    }
594
595    bool DataBox::NotifyTextEntry(AnchorDropBox dropBox, const char * string, bool save)
596    {
597       Anchor anchor = dropBox.anchorValue;
598
599       if(save)
600       {
601          if(anchor.OnGetDataFromString(string))
602          {
603             SetData(&anchor, false);
604             dropBox.anchorValue = anchor;
605          }
606       }
607       else
608       {
609          char tempString[1024] = "";
610          bool needClass = false;
611          const char * string = anchor.OnGetString(tempString, null, &needClass);
612          dropBox.contents = string;
613       }
614       return true;
615    }
616 }