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