1 /*
2   Dynamic construction of dialogs for FAR Manager 3.0 build 5445
3 
4   Original code: http://farmanager.googlecode.com/svn/trunk/plugins/common/unicode/DlgBuilder.hpp
5   License: derived from original one without extra restriction
6 */
7 
8 module dlgbuilder;
9 
10 import farcolor;
11 import farplugin;
12 
13 import std.stdint;
14 import core.sys.windows.windows;
15 import core.stdc.string;
16 import core.stdc.stddef;
17 
18 // Элемент выпадающего списка в диалоге.
19 struct DialogBuilderListItem
20 {
21     // Строчка из LNG-файла, которая будет показана в диалоге.
22     int MessageId;
23 
24     // Значение, которое будет записано в поле Value при выборе этой строчки.
25     int ItemValue;
26 }
27 
28 class DialogItemBinding(T)
29 {
30     int BeforeLabelID = -1;
31     int AfterLabelID = -1;
32 
33     this()
34     {
35     }
36 
37     void SaveValue(T* Item, int RadioGroupIndex)
38     {
39     }
40 }
41 
42 class CheckBoxBinding(T) : DialogItemBinding!T
43 {
44 private:
45     BOOL* Value;
46     int Mask;
47 
48 public:
49     this(int* aValue, int aMask)
50     {
51         Value = aValue;
52         Mask = aMask;
53     }
54 
55     override void SaveValue(T* Item, int RadioGroupIndex)
56     {
57         if (!Mask)
58         {
59             *Value = Item.Selected;
60         }
61         else
62         {
63             if (Item.Selected)
64                 *Value |= Mask;
65             else
66                 *Value &= ~Mask;
67         }
68     }
69 }
70 
71 class RadioButtonBinding(T) : DialogItemBinding!T
72 {
73 private:
74     int* Value;
75 
76 public:
77     this(int* aValue)
78     {
79         Value = aValue;
80     }
81 
82     override void SaveValue(T* Item, int RadioGroupIndex)
83     {
84         if (Item.Selected)
85             *Value = RadioGroupIndex;
86     }
87 }
88 
89 /*
90 Класс для динамического построения диалогов. Автоматически вычисляет положение и размер
91 для добавляемых контролов, а также размер самого диалога. Автоматически записывает выбранные
92 значения в указанное место после закрытия диалога по OK.
93 
94 По умолчанию каждый контрол размещается в новой строке диалога. Ширина для текстовых строк,
95 checkbox и radio button вычисляется автоматически, для других элементов передаётся явно.
96 Есть также возможность добавить статический текст слева или справа от контрола, при помощи
97 методов AddTextBefore и AddTextAfter.
98 
99 Поддерживается также возможность расположения контролов в две колонки. Используется следующим
100 образом:
101 - StartColumns()
102 - добавляются контролы для первой колонки
103 - ColumnBreak()
104 - добавляются контролы для второй колонки
105 - EndColumns()
106 
107 Поддерживается также возможность расположения контролов внутри бокса. Используется следующим
108 образом:
109 - StartSingleBox()
110 - добавляются контролы
111 - EndSingleBox()
112 
113 Базовая версия класса используется как внутри кода Far, так и в плагинах.
114 */
115 
116 class DialogBuilderBase(T)
117 {
118 protected:
119     T* m_DialogItems;
120     DialogItemBinding!T[] m_Bindings;
121     int m_DialogItemsCount;
122     int m_DialogItemsAllocated;
123     int m_NextY;
124     int m_Indent;
125     int m_SingleBoxIndex;
126     int m_FirstButtonID;
127     int m_CancelButtonID;
128     int m_ColumnStartIndex;
129     int m_ColumnBreakIndex;
130     int m_ColumnStartY;
131     int m_ColumnEndY;
132     intptr_t m_ColumnMinWidth;
133     intptr_t m_ButtonsWidth;
134 
135     static const int SECOND_COLUMN = -2;
136 
137     void ReallocDialogItems()
138     {
139         // реаллокация инвалидирует указатели на DialogItemEx, возвращённые из
140         // AddDialogItem и аналогичных методов, поэтому размер массива подбираем такой,
141         // чтобы все нормальные диалоги помещались без реаллокации
142         // TODO хорошо бы, чтобы они вообще не инвалидировались
143         m_DialogItemsAllocated += 64;
144         if (!m_DialogItems)
145         {
146             m_DialogItems = new T[m_DialogItemsAllocated].ptr;
147             m_Bindings = new DialogItemBinding!T[m_DialogItemsAllocated];
148         }
149         else
150         {
151             T* NewDialogItems = new T[m_DialogItemsAllocated].ptr;
152             auto NewBindings = new DialogItemBinding!T[m_DialogItemsAllocated];
153             for (int i = 0; i < m_DialogItemsCount; i++)
154             {
155                 NewDialogItems[i] = m_DialogItems[i];
156                 NewBindings[i] = m_Bindings[i];
157             }
158             m_DialogItems.destroy();
159             m_Bindings.destroy();
160             m_DialogItems = NewDialogItems;
161             m_Bindings = NewBindings;
162         }
163     }
164 
165     T* AddDialogItem(FARDIALOGITEMTYPES Type, in wchar* Text)
166     {
167         if (m_DialogItemsCount == m_DialogItemsAllocated)
168         {
169             ReallocDialogItems();
170         }
171         int Index = m_DialogItemsCount++;
172         T* Item = &m_DialogItems[Index];
173         InitDialogItem(Item, Text);
174         Item.Type = Type;
175         m_Bindings[Index] = new DialogItemBinding!T;
176         return Item;
177     }
178 
179     void SetNextY(T* Item)
180     {
181         Item.X1 = 5 + m_Indent;
182         Item.Y1 = Item.Y2 = m_NextY++;
183     }
184 
185     intptr_t ItemWidth(ref const T Item)
186     {
187         with (FARDIALOGITEMTYPES) switch (Item.Type)
188         {
189         case DI_TEXT:
190             return TextWidth(Item);
191 
192         case DI_CHECKBOX:
193         case DI_RADIOBUTTON:
194         case DI_BUTTON:
195             return TextWidth(Item) + 4;
196 
197         case DI_EDIT:
198         case DI_FIXEDIT:
199         case DI_COMBOBOX:
200         case DI_LISTBOX:
201         case DI_PSWEDIT:
202             intptr_t Width = Item.X2 - Item.X1 + 1;
203             // стрелка history занимает дополнительное место, но раньше она рисовалась поверх рамки???
204             if (Item.Flags & DIF_HISTORY)
205                 Width++;
206             return Width;
207 
208         default:
209             break;
210         }
211         return 0;
212     }
213 
214     void AddBorder(in wchar* TitleText)
215     {
216         T* Title = AddDialogItem(FARDIALOGITEMTYPES.DI_DOUBLEBOX, TitleText);
217         Title.X1 = 3;
218         Title.Y1 = 1;
219     }
220 
221     void UpdateBorderSize()
222     {
223         T* Title = &m_DialogItems[0];
224         intptr_t MaxWidth = MaxTextWidth();
225         intptr_t MaxHeight = 0;
226         Title.X2 = Title.X1 + MaxWidth - 1 + 4;
227 
228         for (int i = 1; i < m_DialogItemsCount; i++)
229         {
230             if (m_DialogItems[i].Type == FARDIALOGITEMTYPES.DI_SINGLEBOX)
231             {
232                 m_Indent = 2;
233                 m_DialogItems[i].X2 = Title.X2;
234             }
235             else if (m_DialogItems[i].Type == FARDIALOGITEMTYPES.DI_TEXT && (m_DialogItems[i].Flags & DIF_CENTERTEXT))
236             { //BUGBUG: two columns items are not supported
237                 m_DialogItems[i].X2 = m_DialogItems[i].X1 + MaxWidth - 1;
238             }
239 
240             if (m_DialogItems[i].Y2 > MaxHeight)
241             {
242                 MaxHeight = m_DialogItems[i].Y2;
243             }
244         }
245 
246         Title.X2 += m_Indent;
247         Title.Y2 = MaxHeight + 1;
248         m_Indent = 0;
249     }
250 
251     intptr_t MaxTextWidth()
252     {
253         intptr_t MaxWidth = 0;
254         for (int i = 1; i < m_DialogItemsCount; i++)
255         {
256             if (m_DialogItems[i].X1 == SECOND_COLUMN)
257                 continue;
258             DialogItemBinding!T Binding = FindBinding(m_DialogItems + i);
259             intptr_t Width = ItemWidth(m_DialogItems[i]);
260             if (Binding && Binding.BeforeLabelID != -1)
261                 Width += ItemWidth(m_DialogItems[Binding.BeforeLabelID]) + 1;
262             if (Binding && Binding.AfterLabelID != -1)
263                 Width += 1 + ItemWidth(m_DialogItems[Binding.AfterLabelID]);
264 
265             if (MaxWidth < Width)
266                 MaxWidth = Width;
267         }
268         intptr_t ColumnsWidth = 2 * m_ColumnMinWidth + 1;
269         if (MaxWidth < ColumnsWidth)
270             MaxWidth = ColumnsWidth;
271         if (MaxWidth < m_ButtonsWidth)
272             MaxWidth = m_ButtonsWidth;
273         return MaxWidth;
274     }
275 
276     void UpdateSecondColumnPosition()
277     {
278         intptr_t SecondColumnX1 = 4 + (m_DialogItems[0].X2 - m_DialogItems[0].X1 + 1) / 2;
279         for (int i = 0; i < m_DialogItemsCount; i++)
280         {
281             if (m_DialogItems[i].X1 == SECOND_COLUMN)
282             {
283                 DialogItemBinding!T Binding = FindBinding(m_DialogItems + i);
284 
285                 int before = Binding.BeforeLabelID;
286                 long BeforeWidth = 0;
287                 if (Binding && Binding.BeforeLabelID != -1)
288                     BeforeWidth = m_DialogItems[before].X2 - m_DialogItems[before].X1 + 1 + 1;
289 
290                 intptr_t Width = m_DialogItems[i].X2 - m_DialogItems[i].X1 + 1;
291                 m_DialogItems[i].X1 = SecondColumnX1 + BeforeWidth;
292                 m_DialogItems[i].X2 = m_DialogItems[i].X1 + Width - 1;
293 
294                 if (Binding && Binding.AfterLabelID != -1)
295                 {
296                     int after = Binding.AfterLabelID;
297                     long AfterWidth = m_DialogItems[after].X2 - m_DialogItems[after].X1 + 1;
298                     m_DialogItems[after].X1 = m_DialogItems[i].X2 + 1 + 1;
299                     m_DialogItems[after].X2 = m_DialogItems[after].X1 + AfterWidth - 1;
300                 }
301             }
302         }
303     }
304 
305     void InitDialogItem(T* NewDialogItem, in wchar* Text)
306     {
307     }
308 
309     int TextWidth(ref const T Item)
310     {
311         return 0;
312     }
313 
314     void SetLastItemBinding(DialogItemBinding!T Binding)
315     {
316         if (m_Bindings[m_DialogItemsCount - 1])
317             m_Bindings[m_DialogItemsCount - 1].destroy();
318         m_Bindings[m_DialogItemsCount - 1] = Binding;
319     }
320 
321     int GetItemID(T* Item) const
322     {
323         int Index = cast(int)(Item - m_DialogItems);
324         if (Index >= 0 && Index < m_DialogItemsCount)
325             return Index;
326         return -1;
327     }
328 
329     DialogItemBinding!T FindBinding(T* Item)
330     {
331         int Index = cast(int)(Item - m_DialogItems);
332         if (Index >= 0 && Index < m_DialogItemsCount)
333             return m_Bindings[Index];
334         return null;
335     }
336 
337     void SaveValues()
338     {
339         int RadioGroupIndex = 0;
340         for (int i = 0; i < m_DialogItemsCount; i++)
341         {
342             if (m_DialogItems[i].Flags & DIF_GROUP)
343                 RadioGroupIndex = 0;
344             else
345                 RadioGroupIndex++;
346 
347             if (m_Bindings[i])
348                 m_Bindings[i].SaveValue(&m_DialogItems[i], RadioGroupIndex);
349         }
350     }
351 
352     const(wchar)* GetLangString(int MessageID)
353     {
354         return null;
355     }
356 
357     intptr_t DoShowDialog()
358     {
359         return -1;
360     }
361 
362     DialogItemBinding!T CreateCheckBoxBinding(int* Value, int Mask)
363     {
364         return null;
365     }
366 
367     DialogItemBinding!T CreateRadioButtonBinding(int* Value)
368     {
369         return null;
370     }
371 
372     this()
373     {
374         m_NextY = 2;
375         m_SingleBoxIndex = -1;
376         m_FirstButtonID = -1;
377         m_CancelButtonID = -1;
378         m_ColumnStartIndex = -1;
379         m_ColumnBreakIndex = -1;
380         m_ColumnStartY = -1;
381         m_ColumnEndY = -1;
382     }
383 
384     ~this()
385     {
386         for (int i = 0; i < m_DialogItemsCount; i++)
387         {
388             if (m_Bindings[i])
389                 m_Bindings[i].destroy();
390         }
391         m_DialogItems.destroy();
392         m_Bindings.destroy();
393     }
394 
395 public:
396 
397     int GetLastID() const
398     {
399         return m_DialogItemsCount - 1;
400     }
401 
402     // Добавляет статический текст, расположенный на отдельной строке в диалоге.
403     T* AddText(int LabelId)
404     {
405         return AddText(LabelId == -1 ? "" : GetLangString(LabelId));
406     }
407 
408     // Добавляет статический текст, расположенный на отдельной строке в диалоге.
409     T* AddText(in wchar* Label)
410     {
411         T* Item = AddDialogItem(FARDIALOGITEMTYPES.DI_TEXT, Label);
412         SetNextY(Item);
413         Item.X2 = Item.X1 + ItemWidth(*Item) - 1;
414         return Item;
415     }
416 
417     // Добавляет чекбокс.
418     T* AddCheckbox(in wchar* TextMessage, int* Value, int Mask = 0, bool ThreeState = false)
419     {
420         T* Item = AddDialogItem(FARDIALOGITEMTYPES.DI_CHECKBOX, TextMessage);
421         if (ThreeState && !Mask)
422             Item.Flags |= DIF_3STATE;
423         SetNextY(Item);
424         Item.X2 = Item.X1 + ItemWidth(*Item) - 1;
425         if (!Mask)
426             Item.Selected = *Value;
427         else
428             Item.Selected = (*Value & Mask) != 0;
429         SetLastItemBinding(CreateCheckBoxBinding(Value, Mask));
430         return Item;
431     }
432 
433     // Добавляет чекбокс.
434     T* AddCheckbox(int TextMessageId, int* Value, int Mask = 0, bool ThreeState = false)
435     {
436         return AddCheckbox(GetLangString(TextMessageId), Value, Mask, ThreeState);
437     }
438 
439     // Добавляет группу радиокнопок.
440     T* AddRadioButtons(int* Value, int OptionCount, const int[] MessageIDs, bool FocusOnSelected = false)
441     {
442         T* firstButton;
443         for (int i = 0; i < OptionCount; i++)
444         {
445             T* Item = AddDialogItem(FARDIALOGITEMTYPES.DI_RADIOBUTTON, GetLangString(MessageIDs[i]));
446             SetNextY(Item);
447             Item.X2 = Item.X1 + ItemWidth(*Item) - 1;
448             if (!i)
449             {
450                 Item.Flags |= DIF_GROUP;
451                 firstButton = Item;
452             }
453             if (*Value == i)
454             {
455                 Item.Selected = TRUE;
456                 if (FocusOnSelected)
457                     Item.Flags |= DIF_FOCUS;
458             }
459             SetLastItemBinding(CreateRadioButtonBinding(Value));
460         }
461         return firstButton;
462     }
463 
464     void AddRadioButtons(int* Value, const int[] MessageIDs, bool FocusOnSelected = false)
465     {
466         AddRadioButtons(Value, cast(int)MessageIDs.length, MessageIDs, FocusOnSelected);
467     }
468 
469     // Добавляет поле типа FARDIALOGITEMTYPES.DI_FIXEDIT для редактирования указанного числового значения.
470     T* AddIntEditField(int* Value, int Width)
471     {
472         return null;
473     }
474 
475     T* AddUIntEditField(uint* Value, int Width)
476     {
477         return null;
478     }
479 
480     // Добавляет указанную текстовую строку слева от элемента RelativeTo.
481     T* AddTextBefore(T* RelativeTo, in wchar* Label)
482     {
483         T* Item = AddDialogItem(FARDIALOGITEMTYPES.DI_TEXT, Label);
484         Item.Y1 = Item.Y2 = RelativeTo.Y1;
485         Item.X1 = 5 + m_Indent;
486         Item.X2 = Item.X1 + ItemWidth(*Item) - 1;
487 
488         intptr_t RelativeToWidth = RelativeTo.X2 - RelativeTo.X1 + 1;
489         RelativeTo.X1 = Item.X2 + 1 + 1;
490         RelativeTo.X2 = RelativeTo.X1 + RelativeToWidth - 1;
491 
492         DialogItemBinding!T Binding = FindBinding(RelativeTo);
493         if (Binding)
494             Binding.BeforeLabelID = GetItemID(Item);
495 
496         return Item;
497     }
498 
499     // Добавляет указанную текстовую строку слева от элемента RelativeTo.
500     T* AddTextBefore(T* RelativeTo, int LabelId)
501     {
502         return AddTextBefore(RelativeTo, GetLangString(LabelId));
503     }
504 
505     // Добавляет указанную текстовую строку справа от элемента RelativeTo.
506     T* AddTextAfter(T* RelativeTo, in wchar* Label)
507     {
508         T* Item = AddDialogItem(FARDIALOGITEMTYPES.DI_TEXT, Label);
509         Item.Y1 = Item.Y2 = RelativeTo.Y1;
510         Item.X1 = RelativeTo.X2 + 1 + 1;
511         Item.X2 = Item.X1 + ItemWidth(*Item) - 1;
512 
513         DialogItemBinding!T Binding = FindBinding(RelativeTo);
514         if (Binding)
515             Binding.AfterLabelID = GetItemID(Item);
516 
517         return Item;
518     }
519 
520     T* AddTextAfter(T* RelativeTo, int LabelId)
521     {
522         return AddTextAfter(RelativeTo, GetLangString(LabelId));
523     }
524 
525     // Добавляет кнопку справа от элемента RelativeTo.
526     T* AddButtonAfter(T* RelativeTo, in wchar* Label)
527     {
528         T* Item = AddDialogItem(FARDIALOGITEMTYPES.DI_BUTTON, Label);
529         Item.Y1 = Item.Y2 = RelativeTo.Y1;
530         Item.X1 = RelativeTo.X2 + 1 + 1;
531         Item.X2 = Item.X1 + ItemWidth(*Item) - 1;
532 
533         DialogItemBinding!T Binding = FindBinding(RelativeTo);
534         if (Binding)
535             Binding.AfterLabelID = GetItemID(Item);
536 
537         return Item;
538     }
539 
540     // Добавляет кнопку справа от элемента RelativeTo.
541     T* AddButtonAfter(T* RelativeTo, int LabelId)
542     {
543         return AddButtonAfter(RelativeTo, GetLangString(LabelId));
544     }
545 
546     // Начинает располагать поля диалога в две колонки.
547     void StartColumns()
548     {
549         m_ColumnStartIndex = m_DialogItemsCount;
550         m_ColumnStartY = m_NextY;
551     }
552 
553     // Завершает колонку полей в диалоге и переходит к следующей колонке.
554     void ColumnBreak()
555     {
556         m_ColumnBreakIndex = m_DialogItemsCount;
557         m_ColumnEndY = m_NextY;
558         m_NextY = m_ColumnStartY;
559     }
560 
561     // Завершает расположение полей диалога в две колонки.
562     void EndColumns()
563     {
564         for (int i = m_ColumnStartIndex; i < m_DialogItemsCount; i++)
565         {
566             intptr_t Width = ItemWidth(m_DialogItems[i]);
567             DialogItemBinding!T Binding = FindBinding(m_DialogItems + i);
568             long BindingAdd = 0;
569             if (Binding)
570             {
571                 if (Binding.BeforeLabelID != -1)
572                     BindingAdd += ItemWidth(m_DialogItems[Binding.BeforeLabelID]) + 1;
573                 if (Binding.AfterLabelID != -1)
574                     BindingAdd += 1 + ItemWidth(m_DialogItems[Binding.AfterLabelID]);
575             }
576 
577             if (Width + BindingAdd > m_ColumnMinWidth)
578                 m_ColumnMinWidth = Width + BindingAdd;
579 
580             if (i >= m_ColumnBreakIndex)
581             {
582                 m_DialogItems[i].X1 = SECOND_COLUMN;
583                 m_DialogItems[i].X2 = SECOND_COLUMN + Width - 1;
584             }
585         }
586 
587         m_ColumnStartIndex = -1;
588         m_ColumnBreakIndex = -1;
589 
590         if (m_NextY < m_ColumnEndY)
591             m_NextY = m_ColumnEndY;
592     }
593 
594     // Начинает располагать поля диалога внутри single box
595     void StartSingleBox(int MessageId = -1, bool LeftAlign = false)
596     {
597         T* SingleBox = AddDialogItem(FARDIALOGITEMTYPES.DI_SINGLEBOX, MessageId == -1 ? "" : GetLangString(MessageId));
598         SingleBox.Flags = LeftAlign ? DIF_LEFTTEXT : DIF_NONE;
599         SingleBox.X1 = 5;
600         SingleBox.Y1 = m_NextY++;
601         m_Indent = 2;
602         m_SingleBoxIndex = m_DialogItemsCount - 1;
603     }
604 
605     // Завершает расположение полей диалога внутри single box
606     void EndSingleBox()
607     {
608         if (m_SingleBoxIndex != -1)
609         {
610             m_DialogItems[m_SingleBoxIndex].Y2 = m_NextY++;
611             m_Indent = 0;
612             m_SingleBoxIndex = -1;
613         }
614     }
615 
616     // Добавляет пустую строку.
617     void AddEmptyLine()
618     {
619         m_NextY++;
620     }
621 
622     // Добавляет сепаратор.
623     void AddSeparator(int MessageId = -1)
624     {
625         return AddSeparator(MessageId == -1 ? "" : GetLangString(MessageId));
626     }
627 
628     void AddSeparator(in wchar* Text)
629     {
630         T* Separator = AddDialogItem(FARDIALOGITEMTYPES.DI_TEXT, Text);
631         Separator.Flags = DIF_SEPARATOR;
632         Separator.X1 = -1;
633         Separator.Y1 = Separator.Y2 = m_NextY++;
634     }
635 
636     // Добавляет сепаратор, кнопки OK и Cancel.
637     void AddOKCancel(int OKMessageId, int CancelMessageId, int ExtraMessageId = -1, bool Separator = true)
638     {
639         if (Separator)
640             AddSeparator();
641 
642         const int[3] MsgIDs = [OKMessageId, CancelMessageId, ExtraMessageId];
643         const int NumButtons = (ExtraMessageId != -1) ? 3 : (CancelMessageId != -1 ? 2 : 1);
644 
645         AddButtons(NumButtons, MsgIDs, 0, 1);
646     }
647 
648     // Добавляет линейку кнопок.
649     void AddButtons(int ButtonCount, const int[] MessageIDs, int defaultButtonIndex = 0, int cancelButtonIndex = -1)
650     {
651         int LineY = m_NextY++;
652         T* PrevButton = null;
653 
654         for (int i = 0; i < ButtonCount; i++)
655         {
656             T* NewButton = AddDialogItem(FARDIALOGITEMTYPES.DI_BUTTON, GetLangString(MessageIDs[i]));
657             NewButton.Flags = DIF_CENTERGROUP;
658             NewButton.Y1 = NewButton.Y2 = LineY;
659             if (PrevButton)
660             {
661                 NewButton.X1 = PrevButton.X2 + 1;
662             }
663             else
664             {
665                 NewButton.X1 = 2 + m_Indent;
666                 m_FirstButtonID = m_DialogItemsCount - 1;
667             }
668             NewButton.X2 = NewButton.X1 + ItemWidth(*NewButton) - 1 + 1;
669 
670             if (defaultButtonIndex == i)
671             {
672                 NewButton.Flags |= DIF_DEFAULTBUTTON;
673             }
674             if (cancelButtonIndex == i)
675                 m_CancelButtonID = m_DialogItemsCount - 1;
676 
677             PrevButton = NewButton;
678         }
679         auto Width = PrevButton.X2 - 1 - m_DialogItems[m_FirstButtonID].X1 + 1;
680         if (m_ButtonsWidth < Width)
681             m_ButtonsWidth = Width;
682     }
683 
684     void AddButtons(const int[] MessageIDs, int defaultButtonIndex = 0, int cancelButtonIndex = -1)
685     {
686         AddButtons(cast(int)MessageIDs.length, MessageIDs, defaultButtonIndex, cancelButtonIndex);
687     }
688 
689     intptr_t ShowDialogEx()
690     {
691         UpdateBorderSize();
692         UpdateSecondColumnPosition();
693         intptr_t Result = DoShowDialog();
694         if (Result >= 0 && Result != m_CancelButtonID)
695         {
696             SaveValues();
697         }
698 
699         if (m_FirstButtonID >= 0 && Result >= m_FirstButtonID)
700         {
701             Result -= m_FirstButtonID;
702         }
703         return Result;
704     }
705 
706     bool ShowDialog()
707     {
708         intptr_t Result = ShowDialogEx();
709         return Result >= 0 && (m_CancelButtonID < 0 || Result + m_FirstButtonID != m_CancelButtonID);
710     }
711 
712 }
713 
714 class DialogAPIBinding : DialogItemBinding!FarDialogItem
715 {
716 protected:
717     const(PluginStartupInfo)* Info;
718     HANDLE* DialogHandle;
719     int ID;
720 
721     this(in PluginStartupInfo* aInfo, HANDLE* aHandle, int aID)
722     {
723         Info = aInfo;
724         DialogHandle = aHandle;
725         ID = aID;
726     }
727 }
728 
729 class PluginCheckBoxBinding : DialogAPIBinding
730 {
731     int* Value;
732     int Mask;
733 
734 public:
735     this(in PluginStartupInfo* aInfo, HANDLE* aHandle, int aID, int* aValue, int aMask)
736     {
737         super(aInfo, aHandle, aID);
738         Value = aValue;
739         Mask = aMask;
740     }
741 
742     override void SaveValue(FarDialogItem* Item, int RadioGroupIndex)
743     {
744         int Selected = cast(int)(Info.SendDlgMessage(*DialogHandle, FARMESSAGE.DM_GETCHECK, ID, null));
745         if (!Mask)
746         {
747             *Value = Selected;
748         }
749         else
750         {
751             if (Selected)
752                 *Value |= Mask;
753             else
754                 *Value &= ~Mask;
755         }
756     }
757 }
758 
759 class PluginRadioButtonBinding : DialogAPIBinding
760 {
761 private:
762     int* Value;
763 
764 public:
765     this(in PluginStartupInfo* aInfo, HANDLE* aHandle, int aID, int* aValue)
766     {
767         super(aInfo, aHandle, aID);
768         Value = aValue;
769     }
770 
771     override void SaveValue(FarDialogItem* Item, int RadioGroupIndex)
772     {
773         if (Info.SendDlgMessage(*DialogHandle, FARMESSAGE.DM_GETCHECK, ID, null))
774             *Value = RadioGroupIndex;
775     }
776 }
777 
778 class PluginEditFieldBinding : DialogAPIBinding
779 {
780 private:
781     wchar* Value;
782     int MaxSize;
783 
784 public:
785     this(in PluginStartupInfo* aInfo, HANDLE* aHandle, int aID, wchar* aValue, int aMaxSize)
786     {
787         super(aInfo, aHandle, aID);
788         Value = aValue;
789         MaxSize = aMaxSize;
790     }
791 
792     override void SaveValue(FarDialogItem* Item, int RadioGroupIndex)
793     {
794         const(wchar)* DataPtr = cast(const(wchar)*)Info.SendDlgMessage(*DialogHandle, FARMESSAGE.DM_GETCONSTTEXTPTR, ID, null);
795         lstrcpynW(Value, DataPtr, MaxSize);
796     }
797 }
798 
799 class PluginIntEditFieldBinding : DialogAPIBinding
800 {
801 private:
802     int* Value;
803     wchar[32] Buffer;
804     wchar[32] Mask;
805 
806 public:
807     this(in PluginStartupInfo* aInfo, HANDLE* aHandle, int aID, int* aValue, int Width)
808     {
809         super(aInfo, aHandle, aID);
810         Value = aValue;
811         aInfo.FSF.sprintf(Buffer.ptr, "%d"w.ptr, *aValue);
812         int MaskWidth = Width < 31 ? Width : 31;
813         for (int i = 1; i < MaskWidth; i++)
814             Mask[i] = '9';
815         Mask[0] = '#';
816         Mask[MaskWidth] = '\0';
817     }
818 
819     override void SaveValue(FarDialogItem* Item, int RadioGroupIndex)
820     {
821         wchar* DataPtr = cast(wchar*)Info.SendDlgMessage(*DialogHandle, FARMESSAGE.DM_GETCONSTTEXTPTR, ID, null);
822         *Value = Info.FSF.atoi(DataPtr);
823     }
824 
825     wchar* GetBuffer()
826     {
827         return Buffer.ptr;
828     }
829 
830     const(wchar)* GetMask() const
831     {
832         return Mask.ptr;
833     }
834 }
835 
836 class PluginUIntEditFieldBinding : DialogAPIBinding
837 {
838 private:
839     uint* Value;
840     wchar[32] Buffer;
841     wchar[32] Mask;
842 
843 public:
844     this(in PluginStartupInfo* aInfo, HANDLE* aHandle, int aID, uint* aValue, int Width)
845     {
846         super(aInfo, aHandle, aID);
847         Value = aValue;
848         aInfo.FSF.sprintf(Buffer.ptr, "%u"w.ptr, *aValue);
849         int MaskWidth = Width < 31 ? Width : 31;
850         for (int i = 1; i < MaskWidth; i++)
851             Mask[i] = '9';
852         Mask[0] = '#';
853         Mask[MaskWidth] = '\0';
854     }
855 
856     override void SaveValue(FarDialogItem* Item, int RadioGroupIndex)
857     {
858         wchar* DataPtr = cast(wchar*)Info.SendDlgMessage(*DialogHandle, FARMESSAGE.DM_GETCONSTTEXTPTR, ID, null);
859         *Value = cast(uint)Info.FSF.atoi64(DataPtr);
860     }
861 
862     wchar* GetBuffer()
863     {
864         return Buffer.ptr;
865     }
866 
867     const(wchar)* GetMask() const
868     {
869         return Mask.ptr;
870     }
871 }
872 
873 class PluginListControlBinding : DialogAPIBinding
874 {
875 private:
876     int* SelectedIndex;
877     wchar* TextBuf;
878     FarList* List;
879 
880 public:
881     this(in PluginStartupInfo* aInfo, HANDLE* aHandle, int aID, int* aValue, wchar* aText, FarList* aList)
882     {
883         super(aInfo, aHandle, aID);
884         SelectedIndex = aValue;
885         TextBuf = aText;
886         List = aList;
887     }
888 
889     this(in PluginStartupInfo* aInfo, HANDLE* aHandle, int aID, int* aValue, FarList* aList)
890     {
891         super(aInfo, aHandle, aID);
892         SelectedIndex = aValue;
893         List = aList;
894     }
895 
896     ~this()
897     {
898         if (List)
899         {
900             List.Items.destroy();
901         }
902         List.destroy();
903     }
904 
905     override void SaveValue(FarDialogItem* Item, int RadioGroupIndex)
906     {
907         if (SelectedIndex)
908         {
909             *SelectedIndex = cast(int)(Info.SendDlgMessage(*DialogHandle, FARMESSAGE.DM_LISTGETCURPOS, ID, null));
910         }
911         if (TextBuf)
912         {
913             FarDialogItemData fdid = {FarDialogItemData.sizeof, 0, TextBuf};
914             Info.SendDlgMessage(*DialogHandle, FARMESSAGE.DM_GETTEXT, ID, &fdid);
915         }
916     }
917 }
918 
919 /*
920 Версия класса для динамического построения диалогов, используемая в плагинах к Far.
921 */
922 class PluginDialogBuilder : DialogBuilderBase!FarDialogItem
923 {
924 protected:
925     const(PluginStartupInfo)* Info;
926     HANDLE DialogHandle;
927     const(wchar)* HelpTopic;
928     GUID PluginId;
929     GUID Id;
930     FARWINDOWPROC DlgProc;
931     void* UserParam;
932     FARDIALOGFLAGS Flags;
933 
934     override void InitDialogItem(FarDialogItem* Item, in wchar* Text)
935     {
936         memset(Item, 0, FarDialogItem.sizeof);
937         Item.Data = Text;
938     }
939 
940     override int TextWidth(ref const FarDialogItem Item)
941     {
942         return lstrlenW(Item.Data);
943     }
944 
945     override const(wchar)* GetLangString(int MessageID)
946     {
947         return Info.GetMsg(&PluginId, MessageID);
948     }
949 
950     override intptr_t DoShowDialog()
951     {
952         intptr_t Width = m_DialogItems[0].X2 + 4;
953         intptr_t Height = m_DialogItems[0].Y2 + 2;
954         DialogHandle = Info.DialogInit(&PluginId, &Id, -1, -1, Width, Height, HelpTopic, m_DialogItems,
955                 m_DialogItemsCount, 0, Flags, DlgProc, UserParam);
956         return Info.DialogRun(DialogHandle);
957     }
958 
959     override DialogItemBinding!FarDialogItem CreateCheckBoxBinding(int* Value, int Mask)
960     {
961         return new PluginCheckBoxBinding(Info, &DialogHandle, m_DialogItemsCount - 1, Value, Mask);
962     }
963 
964     override DialogItemBinding!FarDialogItem CreateRadioButtonBinding(BOOL* Value)
965     {
966         return new PluginRadioButtonBinding(Info, &DialogHandle, m_DialogItemsCount - 1, Value);
967     }
968 
969     FarDialogItem* AddListControl(FARDIALOGITEMTYPES Type, int* SelectedItem, wchar* Text, int Width,
970             int Height, in wchar*[] ItemsText, size_t ItemCount, FARDIALOGITEMFLAGS ItemFlags)
971     {
972         FarDialogItem* Item = AddDialogItem(Type, Text ? Text : "");
973         SetNextY(Item);
974         Item.X2 = Item.X1 + Width;
975         Item.Y2 = Item.Y2 + Height;
976         Item.Flags |= ItemFlags;
977 
978         m_NextY += Height;
979 
980         FarListItem* ListItems;
981         if (ItemsText)
982         {
983             ListItems = new FarListItem[ItemCount].ptr;
984             for (size_t i = 0; i < ItemCount; i++)
985             {
986                 ListItems[i].Text = ItemsText[i];
987                 ListItems[i].Flags = SelectedItem && (*SelectedItem == cast(int)i) ? LIF_SELECTED : 0;
988             }
989         }
990         FarList* List = new FarList;
991         List.StructSize = FarList.sizeof;
992         List.Items = ListItems;
993         List.ItemsNumber = ListItems ? ItemCount : 0;
994         Item.ListItems = List;
995 
996         SetLastItemBinding(new PluginListControlBinding(Info, &DialogHandle, m_DialogItemsCount - 1, SelectedItem, Text, List));
997         return Item;
998     }
999 
1000     FarDialogItem* AddListControl(FARDIALOGITEMTYPES Type, int* SelectedItem, wchar* Text, int Width,
1001             int Height, const int[] MessageIDs, size_t ItemCount, FARDIALOGITEMFLAGS ItemFlags)
1002     {
1003         const(wchar)*[] ItemsText;
1004         if (MessageIDs)
1005         {
1006             ItemsText = new const(wchar)*[ItemCount];
1007             for (size_t i = 0; i < ItemCount; i++)
1008             {
1009                 ItemsText[i] = GetLangString(MessageIDs[i]);
1010             }
1011         }
1012 
1013         FarDialogItem* result = AddListControl(Type, SelectedItem, Text, Width, Height, ItemsText, ItemCount, ItemFlags);
1014 
1015         ItemsText.destroy();
1016 
1017         return result;
1018     }
1019 
1020 public:
1021     this(in PluginStartupInfo* aInfo, ref const GUID aPluginId, ref const GUID aId, int TitleMessageID,
1022             in wchar* aHelpTopic, FARWINDOWPROC aDlgProc = null, void* aUserParam = null, FARDIALOGFLAGS aFlags = FDLG_NONE)
1023     {
1024         Info = aInfo;
1025         HelpTopic = aHelpTopic;
1026         PluginId = aPluginId;
1027         Id = aId;
1028         DlgProc = aDlgProc;
1029         UserParam = aUserParam;
1030         Flags = aFlags;
1031         AddBorder(PluginDialogBuilder.GetLangString(TitleMessageID));
1032     }
1033 
1034     this(in PluginStartupInfo* aInfo, ref const GUID aPluginId, ref const GUID aId, in wchar* TitleMessage,
1035             in wchar* aHelpTopic, FARWINDOWPROC aDlgProc = null, void* aUserParam = null, FARDIALOGFLAGS aFlags = FDLG_NONE)
1036     {
1037         Info = aInfo;
1038         HelpTopic = aHelpTopic;
1039         PluginId = aPluginId;
1040         Id = aId;
1041         DlgProc = aDlgProc;
1042         UserParam = aUserParam;
1043         Flags = aFlags;
1044         AddBorder(TitleMessage);
1045     }
1046 
1047     ~this()
1048     {
1049         Info.DialogFree(DialogHandle);
1050     }
1051 
1052     override FarDialogItem* AddIntEditField(int* Value, int Width)
1053     {
1054         FarDialogItem* Item = AddDialogItem(FARDIALOGITEMTYPES.DI_FIXEDIT, "");
1055         Item.Flags |= DIF_MASKEDIT;
1056         auto Binding = new PluginIntEditFieldBinding(Info, &DialogHandle, m_DialogItemsCount - 1, Value, Width);
1057         Item.Data = Binding.GetBuffer();
1058         Item.Mask = Binding.GetMask();
1059         SetNextY(Item);
1060         Item.X2 = Item.X1 + Width - 1;
1061         SetLastItemBinding(Binding);
1062         return Item;
1063     }
1064 
1065     override FarDialogItem* AddUIntEditField(uint* Value, int Width)
1066     {
1067         FarDialogItem* Item = AddDialogItem(FARDIALOGITEMTYPES.DI_FIXEDIT, "");
1068         Item.Flags |= DIF_MASKEDIT;
1069         auto Binding = new PluginUIntEditFieldBinding(Info, &DialogHandle, m_DialogItemsCount - 1, Value, Width);
1070         Item.Data = Binding.GetBuffer();
1071         Item.Mask = Binding.GetMask();
1072         SetNextY(Item);
1073         Item.X2 = Item.X1 + Width - 1;
1074         SetLastItemBinding(Binding);
1075         return Item;
1076     }
1077 
1078     FarDialogItem* AddEditField(wchar* Value, int MaxSize, int Width, in wchar* HistoryID = null, bool UseLastHistory = false)
1079     {
1080         FarDialogItem* Item = AddDialogItem(FARDIALOGITEMTYPES.DI_EDIT, Value);
1081         SetNextY(Item);
1082         Item.X2 = Item.X1 + Width - 1;
1083         if (HistoryID)
1084         {
1085             Item.History = HistoryID;
1086             Item.Flags |= DIF_HISTORY;
1087             if (UseLastHistory)
1088                 Item.Flags |= DIF_USELASTHISTORY;
1089         }
1090 
1091         SetLastItemBinding(new PluginEditFieldBinding(Info, &DialogHandle, m_DialogItemsCount - 1, Value, MaxSize));
1092         return Item;
1093     }
1094 
1095     FarDialogItem* AddPasswordField(wchar* Value, int MaxSize, int Width)
1096     {
1097         FarDialogItem* Item = AddDialogItem(FARDIALOGITEMTYPES.DI_PSWEDIT, Value);
1098         SetNextY(Item);
1099         Item.X2 = Item.X1 + Width - 1;
1100 
1101         SetLastItemBinding(new PluginEditFieldBinding(Info, &DialogHandle, m_DialogItemsCount - 1, Value, MaxSize));
1102         return Item;
1103     }
1104 
1105     FarDialogItem* AddFixEditField(wchar* Value, int MaxSize, int Width, in wchar* Mask = null)
1106     {
1107         FarDialogItem* Item = AddDialogItem(FARDIALOGITEMTYPES.DI_FIXEDIT, Value);
1108         SetNextY(Item);
1109         Item.X2 = Item.X1 + Width - 1;
1110         if (Mask)
1111         {
1112             Item.Mask = Mask;
1113             Item.Flags |= DIF_MASKEDIT;
1114         }
1115 
1116         SetLastItemBinding(new PluginEditFieldBinding(Info, &DialogHandle, m_DialogItemsCount - 1, Value, MaxSize));
1117         return Item;
1118     }
1119 
1120     FarDialogItem* AddComboBox(int* SelectedItem, wchar* Text, int Width, in wchar*[] ItemsText,
1121             size_t ItemCount, FARDIALOGITEMFLAGS ItemFlags)
1122     {
1123         return AddListControl(FARDIALOGITEMTYPES.DI_COMBOBOX, SelectedItem, Text, Width, 0, ItemsText, ItemCount, ItemFlags);
1124     }
1125 
1126     FarDialogItem* AddComboBox(int* SelectedItem, wchar* Text, int Width, in wchar*[] ItemsText, FARDIALOGITEMFLAGS ItemFlags)
1127     {
1128         return AddComboBox(SelectedItem, Text, Width, ItemsText, ItemsText.length, ItemFlags);
1129     }
1130 
1131     FarDialogItem* AddComboBox(int* SelectedItem, wchar* Text, int Width, const int[] MessageIDs,
1132             size_t ItemCount, FARDIALOGITEMFLAGS ItemFlags)
1133     {
1134         return AddListControl(FARDIALOGITEMTYPES.DI_COMBOBOX, SelectedItem, Text, Width, 0, MessageIDs, ItemCount, ItemFlags);
1135     }
1136 
1137     FarDialogItem* AddComboBox(int* SelectedItem, wchar* Text, int Width, const int[] MessageIDs, FARDIALOGITEMFLAGS ItemFlags)
1138     {
1139         return AddComboBox(SelectedItem, Text, Width, MessageIDs, MessageIDs.length, ItemFlags);
1140     }
1141 
1142     FarDialogItem* AddListBox(int* SelectedItem, int Width, int Height, in wchar*[] ItemsText, size_t ItemCount,
1143             FARDIALOGITEMFLAGS ItemFlags)
1144     {
1145         return AddListControl(FARDIALOGITEMTYPES.DI_LISTBOX, SelectedItem, null, Width, Height, ItemsText, ItemCount, ItemFlags);
1146     }
1147 
1148     FarDialogItem* AddListBox(int* SelectedItem, int Width, int Height, in wchar*[] ItemsText, FARDIALOGITEMFLAGS ItemFlags)
1149     {
1150         return AddListBox(SelectedItem, Width, Height, ItemsText, ItemsText.length, ItemFlags);
1151     }
1152 
1153     FarDialogItem* AddListBox(int* SelectedItem, int Width, int Height, const int[] MessageIDs, size_t ItemCount,
1154             FARDIALOGITEMFLAGS ItemFlags)
1155     {
1156         return AddListControl(FARDIALOGITEMTYPES.DI_LISTBOX, SelectedItem, null, Width, Height, MessageIDs,
1157                 ItemCount, ItemFlags);
1158     }
1159 
1160     FarDialogItem* AddListBox(int* SelectedItem, int Width, int Height, const int[] MessageIDs, FARDIALOGITEMFLAGS ItemFlags)
1161     {
1162         return AddListBox(SelectedItem, Width, Height, MessageIDs, MessageIDs.length, ItemFlags);
1163     }
1164 }