1 /*
2   Dynamic construction of dialogs for FAR Manager 3.0 build 5151
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) continue;
257             DialogItemBinding!T Binding = FindBinding(m_DialogItems+i);
258             intptr_t Width = ItemWidth(m_DialogItems [i]);
259             if (Binding && Binding.BeforeLabelID != -1)
260                 Width += ItemWidth(m_DialogItems [Binding.BeforeLabelID]) + 1;
261             if (Binding && Binding.AfterLabelID != -1)
262                 Width += 1 + ItemWidth(m_DialogItems [Binding.AfterLabelID]);
263 
264             if (MaxWidth < Width)
265                 MaxWidth = Width;
266         }
267         intptr_t ColumnsWidth = 2*m_ColumnMinWidth+1;
268         if (MaxWidth < ColumnsWidth)
269             MaxWidth = ColumnsWidth;
270         if (MaxWidth < m_ButtonsWidth)
271             MaxWidth = m_ButtonsWidth;
272         return MaxWidth;
273     }
274 
275     void UpdateSecondColumnPosition()
276     {
277         intptr_t SecondColumnX1 = 4 + (m_DialogItems [0].X2 - m_DialogItems [0].X1 + 1)/2;
278         for(int i=0; i<m_DialogItemsCount; i++)
279         {
280             if (m_DialogItems [i].X1 == SECOND_COLUMN)
281             {
282                 DialogItemBinding!T Binding = FindBinding(m_DialogItems+i);
283                 
284                 int before = Binding.BeforeLabelID;
285                 long BeforeWidth = 0;
286                 if (Binding && Binding.BeforeLabelID != -1)
287                     BeforeWidth = m_DialogItems [before].X2 - m_DialogItems [before].X1 + 1 + 1;
288                 
289                 intptr_t Width = m_DialogItems [i].X2 - m_DialogItems [i].X1 + 1;
290                 m_DialogItems [i].X1 = SecondColumnX1 + BeforeWidth;
291                 m_DialogItems [i].X2 = m_DialogItems [i].X1 + Width - 1;
292 
293                 if (Binding && Binding.AfterLabelID != -1)
294                 {
295                     int after = Binding.AfterLabelID;
296                     long AfterWidth = m_DialogItems [after].X2 - m_DialogItems [after].X1 + 1;
297                     m_DialogItems [after].X1 = m_DialogItems [i].X2 + 1 + 1;
298                     m_DialogItems [after].X2 = m_DialogItems [after].X1 + AfterWidth - 1;
299                 }
300             }
301         }
302     }
303 
304     void InitDialogItem(T* NewDialogItem, in wchar* Text)
305     {
306     }
307 
308     int TextWidth(ref const T Item)
309     {
310         return 0;
311     }
312 
313     void SetLastItemBinding(DialogItemBinding!T Binding)
314     {
315         if (m_Bindings [m_DialogItemsCount-1])
316             m_Bindings [m_DialogItemsCount-1].destroy();
317         m_Bindings [m_DialogItemsCount-1] = Binding;
318     }
319 
320     int GetItemID(T* Item) const
321     {
322         int Index = cast(int)(Item - m_DialogItems);
323         if (Index >= 0 && Index < m_DialogItemsCount)
324             return Index;
325         return -1;
326     }
327 
328     DialogItemBinding!T FindBinding(T* Item)
329     {
330         int Index = cast(int)(Item - m_DialogItems);
331         if (Index >= 0 && Index < m_DialogItemsCount)
332             return m_Bindings [Index];
333         return null;
334     }
335 
336     void SaveValues()
337     {
338         int RadioGroupIndex = 0;
339         for(int i=0; i<m_DialogItemsCount; i++)
340         {
341             if (m_DialogItems [i].Flags & DIF_GROUP)
342                 RadioGroupIndex = 0;
343             else
344                 RadioGroupIndex++;
345 
346             if (m_Bindings [i])
347                 m_Bindings [i].SaveValue(&m_DialogItems [i], RadioGroupIndex);
348         }
349     }
350 
351     const(wchar)* GetLangString(int MessageID)
352     {
353         return null;
354     }
355 
356     intptr_t DoShowDialog()
357     {
358         return -1;
359     }
360 
361     DialogItemBinding!T CreateCheckBoxBinding(int* Value, int Mask)
362     {
363         return null;
364     }
365 
366     DialogItemBinding!T CreateRadioButtonBinding(int* Value)
367     {
368         return null;
369     }
370 
371     this()
372     {
373         m_NextY = 2;
374         m_SingleBoxIndex = -1;
375         m_FirstButtonID = -1;
376         m_CancelButtonID = -1;
377         m_ColumnStartIndex = -1;
378         m_ColumnBreakIndex = -1;
379         m_ColumnStartY = -1;
380         m_ColumnEndY = -1;
381     }
382 
383     ~this()
384     {
385         for (int i=0; i<m_DialogItemsCount; i++)
386         {
387             if (m_Bindings [i])
388                 m_Bindings [i].destroy();
389         }
390         m_DialogItems.destroy();
391         m_Bindings.destroy();
392     }
393 
394 public:
395 
396     int GetLastID() const
397     {
398         return m_DialogItemsCount-1;
399     }
400 
401     // Добавляет статический текст, расположенный на отдельной строке в диалоге.
402     T* AddText(int LabelId)
403     {
404         return AddText(LabelId == -1 ? "" : GetLangString(LabelId));
405     }
406 
407     // Добавляет статический текст, расположенный на отдельной строке в диалоге.
408     T* AddText(in wchar* Label)
409     {
410         T* Item = AddDialogItem(FARDIALOGITEMTYPES.DI_TEXT, Label);
411         SetNextY(Item);
412         Item.X2 = Item.X1 + ItemWidth(*Item) - 1;
413         return Item;
414     }
415 
416     // Добавляет чекбокс.
417     T* AddCheckbox(in wchar* TextMessage, int* Value, int Mask = 0, bool ThreeState = false)
418     {
419         T* Item = AddDialogItem(FARDIALOGITEMTYPES.DI_CHECKBOX, TextMessage);
420         if (ThreeState && !Mask)
421             Item.Flags |= DIF_3STATE;
422         SetNextY(Item);
423         Item.X2 = Item.X1 + ItemWidth(*Item) - 1;
424         if (!Mask)
425             Item.Selected = *Value;
426         else
427             Item.Selected = (*Value & Mask) != 0;
428         SetLastItemBinding(CreateCheckBoxBinding(Value, Mask));
429         return Item;
430     }
431 
432     // Добавляет чекбокс.
433     T* AddCheckbox(int TextMessageId, int *Value, int Mask = 0, bool ThreeState = false)
434     {
435         return AddCheckbox(GetLangString(TextMessageId), Value, Mask, ThreeState);
436     }
437 
438     // Добавляет группу радиокнопок.
439     T* AddRadioButtons(int* Value, int OptionCount, const int[] MessageIDs, bool FocusOnSelected=false)
440     {
441         T* firstButton;
442         for (int i=0; i<OptionCount; i++)
443         {
444             T* Item = AddDialogItem(FARDIALOGITEMTYPES.DI_RADIOBUTTON, GetLangString(MessageIDs[i]));
445             SetNextY(Item);
446             Item.X2 = Item.X1 + ItemWidth(*Item) - 1;
447             if (!i)
448             {
449                 Item.Flags |= DIF_GROUP;
450                 firstButton = Item;
451             }
452             if (*Value == i)
453             {
454                 Item.Selected = TRUE;
455                 if (FocusOnSelected)
456                     Item.Flags |= DIF_FOCUS;
457             }
458             SetLastItemBinding(CreateRadioButtonBinding(Value));
459         }
460         return firstButton;
461     }
462 
463     void AddRadioButtons(int* Value, const int[] MessageIDs, bool FocusOnSelected=false)
464     {
465         AddRadioButtons(Value, cast(int) MessageIDs.length, MessageIDs, FocusOnSelected);
466     }
467 
468     // Добавляет поле типа FARDIALOGITEMTYPES.DI_FIXEDIT для редактирования указанного числового значения.
469     T* AddIntEditField(int* Value, int Width)
470     {
471         return null;
472     }
473 
474     T* AddUIntEditField(uint* Value, int Width)
475     {
476         return null;
477     }
478 
479     // Добавляет указанную текстовую строку слева от элемента RelativeTo.
480     T* AddTextBefore(T* RelativeTo, in wchar* Label)
481     {
482         T* Item = AddDialogItem(FARDIALOGITEMTYPES.DI_TEXT, Label);
483         Item.Y1 = Item.Y2 = RelativeTo.Y1;
484         Item.X1 = 5 + m_Indent;
485         Item.X2 = Item.X1 + ItemWidth(*Item) - 1;
486 
487         intptr_t RelativeToWidth = RelativeTo.X2 - RelativeTo.X1 + 1;
488         RelativeTo.X1 = Item.X2 + 1 + 1;
489         RelativeTo.X2 = RelativeTo.X1 + RelativeToWidth - 1;
490 
491         DialogItemBinding!T Binding = FindBinding(RelativeTo);
492         if (Binding)
493             Binding.BeforeLabelID = GetItemID(Item);
494 
495         return Item;
496     }
497 
498     // Добавляет указанную текстовую строку слева от элемента RelativeTo.
499     T* AddTextBefore(T* RelativeTo, int LabelId)
500     {
501         return AddTextBefore(RelativeTo, GetLangString(LabelId));
502     }
503 
504     // Добавляет указанную текстовую строку справа от элемента RelativeTo.
505     T* AddTextAfter(T* RelativeTo, in wchar* Label)
506     {
507         T* Item = AddDialogItem(FARDIALOGITEMTYPES.DI_TEXT, Label);
508         Item.Y1 = Item.Y2 = RelativeTo.Y1;
509         Item.X1 = RelativeTo.X2 + 1 + 1;
510         Item.X2 = Item.X1 + ItemWidth(*Item) - 1;
511 
512         DialogItemBinding!T Binding = FindBinding(RelativeTo);
513         if (Binding)
514             Binding.AfterLabelID = GetItemID(Item);
515 
516         return Item;
517     }
518 
519     T* AddTextAfter(T* RelativeTo, int LabelId)
520     {
521         return AddTextAfter(RelativeTo, GetLangString(LabelId));
522     }
523 
524     // Добавляет кнопку справа от элемента RelativeTo.
525     T* AddButtonAfter(T* RelativeTo, in wchar* Label)
526     {
527         T* Item = AddDialogItem(FARDIALOGITEMTYPES.DI_BUTTON, Label);
528         Item.Y1 = Item.Y2 = RelativeTo.Y1;
529         Item.X1 = RelativeTo.X2 + 1 + 1;
530         Item.X2 = Item.X1 + ItemWidth(*Item) - 1;
531 
532         DialogItemBinding!T Binding = FindBinding(RelativeTo);
533         if (Binding)
534             Binding.AfterLabelID = GetItemID(Item);
535 
536         return Item;
537     }
538 
539     // Добавляет кнопку справа от элемента RelativeTo.
540     T* AddButtonAfter(T* RelativeTo, int LabelId)
541     {
542         return AddButtonAfter(RelativeTo, GetLangString(LabelId));
543     }
544 
545     // Начинает располагать поля диалога в две колонки.
546     void StartColumns()
547     {
548         m_ColumnStartIndex = m_DialogItemsCount;
549         m_ColumnStartY = m_NextY;
550     }
551 
552     // Завершает колонку полей в диалоге и переходит к следующей колонке.
553     void ColumnBreak()
554     {
555         m_ColumnBreakIndex = m_DialogItemsCount;
556         m_ColumnEndY = m_NextY;
557         m_NextY = m_ColumnStartY;
558     }
559 
560     // Завершает расположение полей диалога в две колонки.
561     void EndColumns()
562     {
563         for(int i=m_ColumnStartIndex; i<m_DialogItemsCount; i++)
564         {
565             intptr_t Width = ItemWidth(m_DialogItems [i]);
566             DialogItemBinding!T Binding = FindBinding(m_DialogItems + i);
567             long BindingAdd = 0;
568             if (Binding) {
569                 if (Binding.BeforeLabelID != -1)
570                     BindingAdd += ItemWidth(m_DialogItems [Binding.BeforeLabelID]) + 1;
571                 if (Binding.AfterLabelID != -1)
572                     BindingAdd += 1 + ItemWidth(m_DialogItems [Binding.AfterLabelID]);
573             }
574 
575             if (Width + BindingAdd > m_ColumnMinWidth)
576                 m_ColumnMinWidth = Width + BindingAdd;
577 
578             if (i >= m_ColumnBreakIndex)
579             {
580                 m_DialogItems [i].X1 = SECOND_COLUMN;
581                 m_DialogItems [i].X2 = SECOND_COLUMN + Width - 1;
582             }
583         }
584 
585         m_ColumnStartIndex = -1;
586         m_ColumnBreakIndex = -1;
587 
588         if (m_NextY < m_ColumnEndY)
589             m_NextY = m_ColumnEndY;
590     }
591 
592     // Начинает располагать поля диалога внутри single box
593     void StartSingleBox(int MessageId=-1, bool LeftAlign=false)
594     {
595         T* SingleBox = AddDialogItem(FARDIALOGITEMTYPES.DI_SINGLEBOX, MessageId == -1 ? "" : GetLangString(MessageId));
596         SingleBox.Flags = LeftAlign ? DIF_LEFTTEXT : DIF_NONE;
597         SingleBox.X1 = 5;
598         SingleBox.Y1 = m_NextY++;
599         m_Indent = 2;
600         m_SingleBoxIndex = m_DialogItemsCount - 1;
601     }
602 
603     // Завершает расположение полей диалога внутри single box
604     void EndSingleBox()
605     {
606         if (m_SingleBoxIndex != -1)
607         {
608             m_DialogItems[m_SingleBoxIndex].Y2 = m_NextY++;
609             m_Indent = 0;
610             m_SingleBoxIndex = -1;
611         }
612     }
613 
614     // Добавляет пустую строку.
615     void AddEmptyLine()
616     {
617         m_NextY++;
618     }
619 
620     // Добавляет сепаратор.
621     void AddSeparator(int MessageId=-1)
622     {
623         return AddSeparator(MessageId == -1 ? "" : GetLangString(MessageId));
624     }
625 
626     void AddSeparator(in wchar* Text)
627     {
628         T* Separator = AddDialogItem(FARDIALOGITEMTYPES.DI_TEXT, Text);
629         Separator.Flags = DIF_SEPARATOR;
630         Separator.X1 = -1;
631         Separator.Y1 = Separator.Y2 = m_NextY++;
632     }
633 
634     // Добавляет сепаратор, кнопки OK и Cancel.
635     void AddOKCancel(int OKMessageId, int CancelMessageId, int ExtraMessageId = -1, bool Separator=true)
636     {
637         if (Separator)
638             AddSeparator();
639 
640         int[3] MsgIDs = [ OKMessageId, CancelMessageId, ExtraMessageId ];
641         int NumButtons = (ExtraMessageId != -1) ? 3 : (CancelMessageId != -1? 2 : 1);
642 
643         AddButtons(NumButtons, MsgIDs, 0, 1);
644     }
645 
646     // Добавляет линейку кнопок.
647     void AddButtons(int ButtonCount, const int[] MessageIDs, int defaultButtonIndex = 0, int cancelButtonIndex = -1)
648     {
649         int LineY = m_NextY++;
650         T* PrevButton = null;
651 
652         for (int i = 0; i < ButtonCount; i++)
653         {
654             T* NewButton = AddDialogItem(FARDIALOGITEMTYPES.DI_BUTTON, GetLangString(MessageIDs[i]));
655             NewButton.Flags = DIF_CENTERGROUP;
656             NewButton.Y1 = NewButton.Y2 = LineY;
657             if (PrevButton)
658             {
659                 NewButton.X1 = PrevButton.X2 + 1;
660             }
661             else
662             {
663                 NewButton.X1 = 2 + m_Indent;
664                 m_FirstButtonID = m_DialogItemsCount - 1;
665             }
666             NewButton.X2 = NewButton.X1 + ItemWidth(*NewButton) - 1 + 1;
667 
668             if (defaultButtonIndex == i)
669             {
670                 NewButton.Flags |= DIF_DEFAULTBUTTON;
671             }
672             if (cancelButtonIndex == i)
673                 m_CancelButtonID = m_DialogItemsCount - 1;
674 
675             PrevButton = NewButton;
676         }
677         auto Width = PrevButton.X2 - 1 - m_DialogItems [m_FirstButtonID].X1 + 1;
678         if (m_ButtonsWidth < Width)
679             m_ButtonsWidth = Width;
680     }
681 
682     void AddButtons(const int[] MessageIDs, int defaultButtonIndex = 0, int cancelButtonIndex = -1)
683     {
684         AddButtons(cast(int) MessageIDs.length, MessageIDs, defaultButtonIndex, cancelButtonIndex);
685     }
686 
687     intptr_t ShowDialogEx()
688     {
689         UpdateBorderSize();
690         UpdateSecondColumnPosition();
691         intptr_t Result = DoShowDialog();
692         if (Result >= 0 && Result != m_CancelButtonID)
693         {
694             SaveValues();
695         }
696 
697         if (m_FirstButtonID >= 0 && Result >= m_FirstButtonID)
698         {
699             Result -= m_FirstButtonID;
700         }
701         return Result;
702     }
703 
704     bool ShowDialog()
705     {
706         intptr_t Result = ShowDialogEx();
707         return Result >= 0 && (m_CancelButtonID < 0 || Result + m_FirstButtonID != m_CancelButtonID);
708     }
709 
710 }
711 
712 class DialogAPIBinding: DialogItemBinding!FarDialogItem
713 {
714 protected:
715     const(PluginStartupInfo)* Info;
716     HANDLE* DialogHandle;
717     int ID;
718 
719     this(in PluginStartupInfo* aInfo, HANDLE* aHandle, int aID)
720     {
721         Info = aInfo;
722         DialogHandle = aHandle;
723         ID = aID;
724     }
725 }
726 
727 class PluginCheckBoxBinding: DialogAPIBinding
728 {
729     int *Value;
730     int Mask;
731 
732 public:
733     this(in PluginStartupInfo* aInfo, HANDLE* aHandle, int aID, int* aValue, int aMask)
734     {
735         super(aInfo, aHandle, aID);
736         Value = aValue;
737         Mask = aMask;
738     }
739 
740     override void SaveValue(FarDialogItem* Item, int RadioGroupIndex)
741     {
742         int Selected = cast(int)(Info.SendDlgMessage(*DialogHandle, FARMESSAGE.DM_GETCHECK, ID, null));
743         if (!Mask)
744         {
745             *Value = Selected;
746         }
747         else
748         {
749             if (Selected)
750                 *Value |= Mask;
751             else
752                 *Value &= ~Mask;
753         }
754     }
755 }
756 
757 class PluginRadioButtonBinding: DialogAPIBinding
758 {
759 private:
760     int *Value;
761 
762 public:
763     this(in PluginStartupInfo* aInfo, HANDLE* aHandle, int aID, int* aValue)
764     {
765         super(aInfo, aHandle, aID);
766         Value = aValue;
767     }
768 
769     override void SaveValue(FarDialogItem* Item, int RadioGroupIndex)
770     {
771         if (Info.SendDlgMessage(*DialogHandle, FARMESSAGE.DM_GETCHECK, ID, null))
772             *Value = RadioGroupIndex;
773     }
774 }
775 
776 class PluginEditFieldBinding: DialogAPIBinding
777 {
778 private:
779     wchar* Value;
780     int MaxSize;
781 
782 public:
783     this(in PluginStartupInfo* aInfo, HANDLE* aHandle, int aID, wchar* aValue, int aMaxSize)
784     {
785         super(aInfo, aHandle, aID);
786         Value = aValue;
787         MaxSize = aMaxSize;
788     }
789 
790     override void SaveValue(FarDialogItem* Item, int RadioGroupIndex)
791     {
792         const(wchar)* DataPtr = cast(const(wchar)*) Info.SendDlgMessage(*DialogHandle, FARMESSAGE.DM_GETCONSTTEXTPTR, ID, null);
793         lstrcpynW(Value, DataPtr, MaxSize);
794     }
795 }
796 
797 class PluginIntEditFieldBinding: DialogAPIBinding
798 {
799 private:
800     int* Value;
801     wchar[32] Buffer;
802     wchar[32] Mask;
803 
804 public:
805     this(in PluginStartupInfo* aInfo, HANDLE* aHandle, int aID, int* aValue, int Width)
806     {
807         super(aInfo, aHandle, aID);
808         Value = aValue;
809         aInfo.FSF.sprintf(Buffer.ptr, "%d"w.ptr, *aValue);
810         int MaskWidth = Width < 31 ? Width : 31;
811         for(int i=1; i<MaskWidth; i++)
812             Mask[i] = '9';
813         Mask[0] = '#';
814         Mask[MaskWidth] = '\0';
815     }
816 
817     override void SaveValue(FarDialogItem* Item, int RadioGroupIndex)
818     {
819         wchar* DataPtr = cast(wchar*) Info.SendDlgMessage(*DialogHandle, FARMESSAGE.DM_GETCONSTTEXTPTR, ID, null);
820         *Value = Info.FSF.atoi(DataPtr);
821     }
822 
823     wchar* GetBuffer()
824     {
825         return Buffer.ptr;
826     }
827 
828     const(wchar)* GetMask() const
829     {
830         return Mask.ptr;
831     }
832 }
833 
834 class PluginUIntEditFieldBinding: DialogAPIBinding
835 {
836 private:
837     uint* Value;
838     wchar[32] Buffer;
839     wchar[32] Mask;
840 
841 public:
842     this(in PluginStartupInfo* aInfo, HANDLE* aHandle, int aID, uint* aValue, int Width)
843     {
844         super(aInfo, aHandle, aID);
845         Value = aValue;
846         aInfo.FSF.sprintf(Buffer.ptr, "%u"w.ptr, *aValue);
847         int MaskWidth = Width < 31 ? Width : 31;
848         for(int i=1; i<MaskWidth; i++)
849             Mask[i] = '9';
850         Mask[0] = '#';
851         Mask[MaskWidth] = '\0';
852     }
853 
854     override void SaveValue(FarDialogItem* Item, int RadioGroupIndex)
855     {
856         wchar* DataPtr = cast(wchar*) Info.SendDlgMessage(*DialogHandle, FARMESSAGE.DM_GETCONSTTEXTPTR, ID, null);
857         *Value = cast(uint)Info.FSF.atoi64(DataPtr);
858     }
859 
860     wchar* GetBuffer()
861     {
862         return Buffer.ptr;
863     }
864 
865     const(wchar)* GetMask() const
866     {
867         return Mask.ptr;
868     }
869 }
870 
871 class PluginListControlBinding : DialogAPIBinding
872 {
873 private:
874     int* SelectedIndex;
875     wchar* TextBuf;
876     FarList* List;
877     
878 public:
879     this(in PluginStartupInfo* aInfo, HANDLE* aHandle, int aID, int* aValue, wchar* aText, FarList* aList)
880     {
881         super(aInfo, aHandle, aID);
882         SelectedIndex = aValue;
883         TextBuf = aText;
884         List = aList;
885     }
886 
887     this(in PluginStartupInfo* aInfo, HANDLE* aHandle, int aID, int* aValue, FarList* aList)
888     {
889         super(aInfo, aHandle, aID);
890         SelectedIndex = aValue;
891         List = aList;
892     }
893 
894     ~this()
895     {
896         if (List)
897         {
898             List.Items.destroy();
899         }
900         List.destroy();
901     }
902 
903     override void SaveValue(FarDialogItem* Item, int RadioGroupIndex)
904     {
905         if (SelectedIndex)
906         {
907             *SelectedIndex = cast(int)(Info.SendDlgMessage(*DialogHandle, FARMESSAGE.DM_LISTGETCURPOS, ID, null));
908         }
909         if (TextBuf)
910         {
911             FarDialogItemData fdid = {FarDialogItemData.sizeof, 0, TextBuf};
912             Info.SendDlgMessage(*DialogHandle, FARMESSAGE.DM_GETTEXT, ID, &fdid);
913         }
914     }
915 }
916 
917 /*
918 Версия класса для динамического построения диалогов, используемая в плагинах к Far.
919 */
920 class PluginDialogBuilder: DialogBuilderBase!FarDialogItem
921 {
922 protected:
923     const(PluginStartupInfo)* Info;
924     HANDLE DialogHandle;
925     const(wchar)* HelpTopic;
926     GUID PluginId;
927     GUID Id;
928     FARWINDOWPROC DlgProc;
929     void* UserParam;
930     FARDIALOGFLAGS Flags;
931 
932     override void InitDialogItem(FarDialogItem* Item, in wchar* Text)
933     {
934         memset(Item, 0, FarDialogItem.sizeof);
935         Item.Data = Text;
936     }
937 
938     override int TextWidth(ref const FarDialogItem Item)
939     {
940         return lstrlenW(Item.Data);
941     }
942 
943     override const(wchar)* GetLangString(int MessageID)
944     {
945         return Info.GetMsg(&PluginId, MessageID);
946     }
947 
948     override intptr_t DoShowDialog()
949     {
950         intptr_t Width = m_DialogItems[0].X2+4;
951         intptr_t Height = m_DialogItems[0].Y2+2;
952         DialogHandle = Info.DialogInit(&PluginId, &Id, -1, -1, Width, Height, HelpTopic, m_DialogItems, m_DialogItemsCount, 0, Flags, DlgProc, UserParam);
953         return Info.DialogRun(DialogHandle);
954     }
955 
956     override DialogItemBinding!FarDialogItem CreateCheckBoxBinding(int* Value, int Mask)
957     {
958         return new PluginCheckBoxBinding(Info, &DialogHandle, m_DialogItemsCount-1, Value, Mask);
959     }
960 
961     override DialogItemBinding!FarDialogItem CreateRadioButtonBinding(BOOL* Value)
962     {
963         return new PluginRadioButtonBinding(Info, &DialogHandle, m_DialogItemsCount-1, Value);
964     }
965 
966     FarDialogItem* AddListControl(FARDIALOGITEMTYPES Type, int* SelectedItem, wchar* Text, int Width, int Height, in wchar*[] ItemsText, size_t ItemCount, FARDIALOGITEMFLAGS ItemFlags)
967     {
968         FarDialogItem* Item = AddDialogItem(Type, Text ? Text : "");
969         SetNextY(Item);
970         Item.X2 = Item.X1 + Width;
971         Item.Y2 = Item.Y2 + Height;
972         Item.Flags |= ItemFlags;
973 
974         m_NextY += Height;
975 
976         FarListItem* ListItems;
977         if (ItemsText)
978         {
979             ListItems = new FarListItem[ItemCount].ptr;
980             for(size_t i=0; i<ItemCount; i++)
981             {
982                 ListItems[i].Text = ItemsText[i];
983                 ListItems[i].Flags = SelectedItem && (*SelectedItem == cast(int)i) ? LIF_SELECTED : 0;
984             }
985         }
986         FarList* List = new FarList;
987         List.StructSize = FarList.sizeof;
988         List.Items = ListItems;
989         List.ItemsNumber = ListItems ? ItemCount : 0;
990         Item.ListItems = List;
991 
992         SetLastItemBinding(new PluginListControlBinding(Info, &DialogHandle, m_DialogItemsCount - 1, SelectedItem, Text, List));
993         return Item;
994     }
995 
996     FarDialogItem* AddListControl(FARDIALOGITEMTYPES Type, int* SelectedItem, wchar* Text, int Width, int Height, const int[] MessageIDs, size_t ItemCount, FARDIALOGITEMFLAGS ItemFlags)
997     {
998         const(wchar)*[] ItemsText;
999         if (MessageIDs)
1000         {
1001             ItemsText = new const(wchar)*[ItemCount];
1002             for (size_t i = 0; i < ItemCount; i++)
1003             {
1004                 ItemsText[i] = GetLangString(MessageIDs[i]);
1005             }
1006         }
1007 
1008         FarDialogItem* result = AddListControl(Type, SelectedItem, Text, Width, Height, ItemsText, ItemCount, ItemFlags);
1009 
1010         ItemsText.destroy();
1011 
1012         return result;
1013     }
1014 
1015 public:
1016     this(in PluginStartupInfo* aInfo, ref const GUID aPluginId, ref const GUID aId, int TitleMessageID, in wchar* aHelpTopic, FARWINDOWPROC aDlgProc=null, void* aUserParam=null, FARDIALOGFLAGS aFlags = FDLG_NONE)
1017     {
1018         Info = aInfo;
1019         HelpTopic = aHelpTopic;
1020         PluginId = aPluginId;
1021         Id = aId;
1022         DlgProc = aDlgProc;
1023         UserParam = aUserParam;
1024         Flags = aFlags;
1025         AddBorder(PluginDialogBuilder.GetLangString(TitleMessageID));
1026     }
1027 
1028     this(in PluginStartupInfo* aInfo, ref const GUID aPluginId, ref const GUID aId, in wchar* TitleMessage, in wchar* aHelpTopic, FARWINDOWPROC aDlgProc=null, void* aUserParam=null, FARDIALOGFLAGS aFlags = FDLG_NONE)
1029     {
1030         Info = aInfo;
1031         HelpTopic = aHelpTopic;
1032         PluginId = aPluginId;
1033         Id = aId;
1034         DlgProc = aDlgProc;
1035         UserParam = aUserParam;
1036         Flags = aFlags;
1037         AddBorder(TitleMessage);
1038     }
1039 
1040     ~this()
1041     {
1042         Info.DialogFree(DialogHandle);
1043     }
1044 
1045     override FarDialogItem* AddIntEditField(int* Value, int Width)
1046     {
1047         FarDialogItem* Item = AddDialogItem(FARDIALOGITEMTYPES.DI_FIXEDIT, "");
1048         Item.Flags |= DIF_MASKEDIT;
1049         auto Binding = new PluginIntEditFieldBinding(Info, &DialogHandle, m_DialogItemsCount-1, Value, Width);
1050         Item.Data = Binding.GetBuffer();
1051         Item.Mask = Binding.GetMask();
1052         SetNextY(Item);
1053         Item.X2 = Item.X1 + Width - 1;
1054         SetLastItemBinding(Binding);
1055         return Item;
1056     }
1057 
1058     override FarDialogItem* AddUIntEditField(uint* Value, int Width)
1059     {
1060         FarDialogItem* Item = AddDialogItem(FARDIALOGITEMTYPES.DI_FIXEDIT, "");
1061         Item.Flags |= DIF_MASKEDIT;
1062         auto Binding = new PluginUIntEditFieldBinding(Info, &DialogHandle, m_DialogItemsCount-1, Value, Width);
1063         Item.Data = Binding.GetBuffer();
1064         Item.Mask = Binding.GetMask();
1065         SetNextY(Item);
1066         Item.X2 = Item.X1 + Width - 1;
1067         SetLastItemBinding(Binding);
1068         return Item;
1069     }
1070 
1071     FarDialogItem* AddEditField(wchar* Value, int MaxSize, int Width, in wchar* HistoryID = null, bool UseLastHistory = false)
1072     {
1073         FarDialogItem* Item = AddDialogItem(FARDIALOGITEMTYPES.DI_EDIT, Value);
1074         SetNextY(Item);
1075         Item.X2 = Item.X1 + Width - 1;
1076         if (HistoryID)
1077         {
1078             Item.History = HistoryID;
1079             Item.Flags |= DIF_HISTORY;
1080             if (UseLastHistory)
1081                 Item.Flags |= DIF_USELASTHISTORY;
1082         }
1083 
1084         SetLastItemBinding(new PluginEditFieldBinding(Info, &DialogHandle, m_DialogItemsCount-1, Value, MaxSize));
1085         return Item;
1086     }
1087 
1088     FarDialogItem* AddPasswordField(wchar* Value, int MaxSize, int Width)
1089     {
1090         FarDialogItem* Item = AddDialogItem(FARDIALOGITEMTYPES.DI_PSWEDIT, Value);
1091         SetNextY(Item);
1092         Item.X2 = Item.X1 + Width - 1;
1093 
1094         SetLastItemBinding(new PluginEditFieldBinding(Info, &DialogHandle, m_DialogItemsCount-1, Value, MaxSize));
1095         return Item;
1096     }
1097 
1098     FarDialogItem* AddFixEditField(wchar* Value, int MaxSize, int Width, in wchar* Mask = null)
1099     {
1100         FarDialogItem* Item = AddDialogItem(FARDIALOGITEMTYPES.DI_FIXEDIT, Value);
1101         SetNextY(Item);
1102         Item.X2 = Item.X1 + Width - 1;
1103         if (Mask)
1104         {
1105             Item.Mask = Mask;
1106             Item.Flags |= DIF_MASKEDIT;
1107         }
1108 
1109         SetLastItemBinding(new PluginEditFieldBinding(Info, &DialogHandle, m_DialogItemsCount-1, Value, MaxSize));
1110         return Item;
1111     }
1112 
1113     FarDialogItem* AddComboBox(int* SelectedItem, wchar* Text, int Width, in wchar*[] ItemsText, size_t ItemCount, FARDIALOGITEMFLAGS ItemFlags)
1114     {
1115         return AddListControl(FARDIALOGITEMTYPES.DI_COMBOBOX, SelectedItem, Text, Width, 0, ItemsText, ItemCount, ItemFlags);
1116     }
1117     FarDialogItem* AddComboBox(int* SelectedItem, wchar* Text, int Width, in wchar*[] ItemsText, FARDIALOGITEMFLAGS ItemFlags)
1118     {
1119         return AddComboBox(SelectedItem, Text, Width, ItemsText, ItemsText.length, ItemFlags);
1120     }
1121 
1122     FarDialogItem* AddComboBox(int* SelectedItem, wchar* Text, int Width, const int[] MessageIDs, size_t ItemCount, FARDIALOGITEMFLAGS ItemFlags)
1123     {
1124         return AddListControl(FARDIALOGITEMTYPES.DI_COMBOBOX, SelectedItem, Text, Width, 0, MessageIDs, ItemCount, ItemFlags);
1125     }
1126     FarDialogItem* AddComboBox(int* SelectedItem, wchar* Text, int Width, const int[] MessageIDs, FARDIALOGITEMFLAGS ItemFlags)
1127     {
1128         return AddComboBox(SelectedItem, Text, Width, MessageIDs, MessageIDs.length, ItemFlags);
1129     }
1130 
1131     FarDialogItem* AddListBox(int* SelectedItem, int Width, int Height, in wchar*[] ItemsText, size_t ItemCount, FARDIALOGITEMFLAGS ItemFlags)
1132     {
1133         return AddListControl(FARDIALOGITEMTYPES.DI_LISTBOX, SelectedItem, null, Width, Height, ItemsText, ItemCount, ItemFlags);
1134     }
1135     FarDialogItem* AddListBox(int* SelectedItem, int Width, int Height, in wchar*[] ItemsText, FARDIALOGITEMFLAGS ItemFlags)
1136     {
1137         return AddListBox(SelectedItem, Width, Height, ItemsText, ItemsText.length, ItemFlags);
1138     }
1139 
1140     FarDialogItem* AddListBox(int* SelectedItem, int Width, int Height, const int[] MessageIDs, size_t ItemCount, FARDIALOGITEMFLAGS ItemFlags)
1141     {
1142         return AddListControl(FARDIALOGITEMTYPES.DI_LISTBOX, SelectedItem, null, Width, Height, MessageIDs, ItemCount, ItemFlags);
1143     }
1144     FarDialogItem* AddListBox(int* SelectedItem, int Width, int Height, const int[] MessageIDs, FARDIALOGITEMFLAGS ItemFlags)
1145     {
1146         return AddListBox(SelectedItem, Width, Height, MessageIDs, MessageIDs.length, ItemFlags);
1147     }
1148 }