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