Correction of stiff dialog box of vpiCommonDialogs::ask predicate

Share Tips, Code Samples, etc. with the Visual Prolog community.
User avatar
Ferenc Nagy
VIP Member
Posts: 343
Joined: 24 Apr 2007 12:26

Correction of stiff dialog box of vpiCommonDialogs::ask predicate

Post by Ferenc Nagy » 1 Apr 2016 7:31

Symptom №1:
The original dialog shows only three buttons. The others are beyond the client area of the window.
Symptom №2:
The sizes of push buttons are fixed. Long words appear truncated.

The essence of my correction is:

Code: Select all

class facts     ask_result:integer := 0.     default: positive :=0.     right: integer:=265. %<== Fact for determination of the necessary width.     bottom:integer:=0. %<== Fact for determination of the necessary bottom. clauses     ask(Title, Prompt, ButtonTitlesList,Default) = ask_result :-         L=list::length(ButtonTitlesList),         bottom:=40+20*L,         Parent=vpi::getParentWindow(),         ParentFont=winGetFont(Parent),         fontGetAttrs(ParentFont,_,_)=FontName,         Static=ctl(             wdef(wc_Text,rct(2,2,152,20*L),                 Prompt,u_DlgBase),10001,[wsf_AlignCenter]),         Flags=[wsf_Group,wsf_TabStop,wsf_AlignCenter ],         FlagsD=[wsf_Default|Flags],         Ctl_List=             [             CtlI             ||             I=std::fromTo(0,L-1),             if I=Default then FlagsI=FlagsD else FLagsI=Flags end if,             ButtonTitle=list::nth(convert(positive,I),ButtonTitlesList),             % 2016.04.01. These lines determine the necessary with and height of the buttons based on the estimated text extent.             winGetTextExtent(Parent,ButtonTitle,W,H),             Right=math::max(210,165+W),             Bottom=17+math::max(H+5,20)*I,             right:=math::max(Right,right),             bottom:=math::max(bottom,Bottom),             CtlI=ctl(                     wdef(wc_PushButton,rct(155,5+20*I,Right,Bottom),ButtonTitle,u_DlgBase),                     I,FlagsI)             ],         % 2016.04.01. Final stretch of the window         RCT=rct(50,40,right+60,bottom+10),         FontDef=dlgFont(             wdef(wd_Modal,RCT,Title,u_DlgBase),FontName,8,[wsf_TitleBar]),         default:=math::min(L-1,Default),         _ = vpi::winCreateDynDialog(Parent,[FontDef,Static|Ctl_List],             dlg_ask_eh,gui_api::lNull).
Example usage: Validation of integer controls:

Code: Select all

% 2016.03.29. Validation of integer controls. validateIntegerControl(IC) = control::contentsInvalid(Source,FocusControl,Msg) :-     IC:validate()=control::contentsInvalid(Source,FocusControl,Msg),     FocusControl:setFocus(),     Resp=extendedCommonDialogs::ask(substitute,Msg,value_choice_list,1),     substituteSpecialInteger(IC,Resp),     !. validateIntegerControl(IC) = control::contentsOK :-     retractAll(previousInteger(IC,_)),     assertz(previousInteger(IC,IC:getInteger())).
Attachments
ExtendedCommonDialogs.cl
Class predicates
(312 Bytes) Downloaded 160 times
ExtendedCommonDialogs.pro
Clauses
(2.28 KiB) Downloaded 166 times
Extended ask dialog.png
Screenshot of the extended ask dialog appearing in case invalid fill of an integer control.
Extended ask dialog.png (7.23 KiB) Viewed 3595 times
TIA, Regards,
Frank Nagy

Harrison Pratt
VIP Member
Posts: 260
Joined: 5 Nov 2000 0:01

Post by Harrison Pratt » 2 Apr 2016 22:24

Very nice, Ferenc! That will save creating a few custom Ask dialogs.

I have a suggestion for an enhancement: Change the PromptString to a string_list equal in length (or maybe not) to the number of buttons, then make the members of the PromptList line up horizontally with the buttons and right-align the text to the longest member of the PromptList.

This would provide an opportunity to give the user more information on the response to selecting different buttons.

Attached is a test of your Ask embedding "\n\n" in the PromptString, so you can get an idea of what I mean.
Attachments
FerencNagyAskBox.jpg
Mock-up multi-line comment using \n\n
FerencNagyAskBox.jpg (39.41 KiB) Viewed 3583 times

Harrison Pratt
VIP Member
Posts: 260
Joined: 5 Nov 2000 0:01

Post by Harrison Pratt » 3 Apr 2016 14:19

Ferenc,
The buttons and the static text controls are not using the same font as the dialog Title font. I don't understand why that is happening when I look at your code.

Also, perhaps the button size should be calculated before entering the control definition loop (assuming that you intended all the buttons to be of the same size). I realize I did give your dialog a "stress test" with my test values! :-)

Harrison Pratt
VIP Member
Posts: 260
Joined: 5 Nov 2000 0:01

Post by Harrison Pratt » 9 Apr 2016 17:54

Ferenc's idea was so useful that I decided to extend and modify it to make it even more broadly usable.

Code modifications:
May use either the default dialog font or that of the Parent window.
Fontsize follows that of the Parent window or default font, not hard coded.
Button width is adjusted using DBU scaling.
Move button width adjustment now sets all buttuns to same width.
Change Prompt alignment to left alignment (will word wrap long text within limits of text box).
Align prompt text and top button.
The magic numbers in the code are changed to constants for easier layout adjustment.

Functional extension predicates:

Code: Select all

    askRespNum : ( string Title, string Prompt, string* ButtonTitlesList, positive Default0, boolean UseParentFont ) -> integer Picked0.         % Returns the 0-based index of the selected button.         % Cannot fail.       askToken : ( string Title, string Prompt, string* ButtonTitlesList, positive Default0, boolean UseParentFont  ) -> string PickedFrontToken determ.         % Return front token of selected label,         % FAILs if label does not return a token (e.g. Label is all white space).       askString: ( string Title, string Prompt, string* ButtonTitlesList, positive Default0, boolean UseParentFont  ) -> string PickedLabel.         % Return the complete string of the selected label.         % Cannot fail.
I have no doubt that some of you can improve on this. All input is appreciated. :D
Attachments
askPlus.pro
Updated 2016-04-24
(6.69 KiB) Downloaded 168 times
askPlus.cl
Updated 2016-04-24
(3.13 KiB) Downloaded 169 times
AskPlus Dialog Using Default Font.jpg
AskPlus Dialog Using Default Font.jpg (97.63 KiB) Viewed 3558 times
AskPlus Dialog Using ParentWin Font.jpg
AskPlus Dialog Using ParentWin Font.jpg (110.19 KiB) Viewed 3558 times
Last edited by Harrison Pratt on 24 Apr 2016 18:44, edited 1 time in total.

User avatar
Ferenc Nagy
VIP Member
Posts: 343
Joined: 24 Apr 2007 12:26

Post by Ferenc Nagy » 24 Apr 2016 7:50

Harrison,
Thank you for the suggestions.
TIA, Regards,
Frank Nagy

User avatar
Ferenc Nagy
VIP Member
Posts: 343
Joined: 24 Apr 2007 12:26

Post by Ferenc Nagy » 24 Apr 2016 9:41

Harrison,

I use the font of the parent window.
This is "Andale Mono" because of two reasons.
1) I have build this extended ask dialog into a program which is based on a Russian program. Tahoma was recommended for Russian dialogs in this forum.
2) I can guess better the lookout of the dialogs if their font is monospace than they are based on a proportional font.

I show the essential lines dealing with fonts in my dialog creation below.

Code: Select all

Parent=vpi::getParentWindow(),         ParentFont=winGetFont(Parent),         fontGetAttrs(ParentFont,_,_)=FontName, % SNIP  FontDef=dlgFont(             wdef(wd_Modal,RCT,Title,u_DlgBase),FontName,8,[wsf_TitleBar]),         default:=math::min(L-1,Default),         _ = vpi::winCreateDynDialog(Parent,[FontDef,Static|Ctl_List],             dlg_ask_eh,gui_api::lNull).
You are absolute right that the buttons should have equal width or at least multiplies of a minimal one.

I guess that your askString and askToken procedures are useful. Branching on strings or tokens is more stable as of order numbers of choices against accidental reordering of button titles list and deleting, inserting - not appending - a new choice into them.
Button titles may contain disturbing ampersand characters (denoting the next underscored character).
Here is my general solution to remove them.

Code: Select all

predicates      % Delete ampersand from the text of a control.     deleteAmpersand:(control) -> string procedure.   clauses     % Delete ampersand from the text of a control.     deleteAmpersand(Control) = replaceFirst(Control:getText(),"&","",caseInsensitive).
TIA, Regards,
Frank Nagy

Harrison Pratt
VIP Member
Posts: 260
Joined: 5 Nov 2000 0:01

Post by Harrison Pratt » 24 Apr 2016 12:55

Frank,

Thank you for your comments which help me appreciate your dialog design challenges. I struggled a while with the button width issue until finally remembering that dialog base unit calculation is required (well, not strictly required but useful) in dynamic generated dialogs. I'd used it in VIP 5.2, forgot it and found it again in the 5.2 manual.

I decided to left_align the text box so I could display a multi-line prompt, but gave up on aligning prompt text lines with buttons as something not likely to be very useful in practice.

Your reminder about embedded '&' is good-- will fix that now instead of in the middle of a big project!

I expect I will use askToken most often and am not certain when I'll need askString, but left it accessible just in case.

Thanks for your help!
Harrison :D

User avatar
Ferenc Nagy
VIP Member
Posts: 343
Joined: 24 Apr 2007 12:26

Post by Ferenc Nagy » 24 Apr 2016 16:17

Harrison,
If you prefer askToken then take care of different and informative first tokens of the button titles.
TIA, Regards,
Frank Nagy

User avatar
Ferenc Nagy
VIP Member
Posts: 343
Joined: 24 Apr 2007 12:26

Post by Ferenc Nagy » 24 Apr 2016 17:47

Harrison,
My latest changes in my extended ask dialog:
1) Introduction of class properties triggering the size of the buttons and the vertical gap among them.

Code: Select all

extendedCommonDialogs::height_factor :=1.25, extendedCommonDialogs::height_plus:= 5, extendedCommonDialogs::vertical_gap:=10,        
Their defaults:

Code: Select all

class facts - height_correction % 2016.04.24.     % Height factor.    height_factor : real  :=1.5.    % Fixed additon to text height.    height_plus  : positive:= 5.     % Vertical gap between buttons.    vertical_gap : positive := 10.
Usage of these parameters are shown in the attached Windiff reports.
Attachments
mod_of_cl.htm
Modification of the cl file.
(8.98 KiB) Downloaded 150 times
mod_of_pro.htm
Modification of the pro file.
(33.43 KiB) Downloaded 168 times
Equal width and higher buttons with centered titles.PNG
Equal width and higher buttons with centered titles.PNG (8.78 KiB) Viewed 3494 times
TIA, Regards,
Frank Nagy

Harrison Pratt
VIP Member
Posts: 260
Joined: 5 Nov 2000 0:01

Re:

Post by Harrison Pratt » 24 Apr 2016 18:20

Ferenc Nagy wrote:Harrison,
If you prefer askToken then take care of different and informative first tokens of the button titles.
Do you mean to say that it is the responsibility of the developer to ensure that the frontTokens of all the button titles are unique? If not, I don't understand your meaning.

Harrison Pratt
VIP Member
Posts: 260
Joined: 5 Nov 2000 0:01

Re:

Post by Harrison Pratt » 24 Apr 2016 18:53

Frank,

Good idea! Eventually that will be needed.

I updated my code to reflect your suggestion about removing the '&' accelerator character. :-)

Harrison


Ferenc Nagy wrote:Harrison,
My latest changes in my extended ask dialog:
1) Introduction of class properties triggering the size of the buttons and the vertical gap among them.

Code: Select all

extendedCommonDialogs::height_factor :=1.25, extendedCommonDialogs::height_plus:= 5, extendedCommonDialogs::vertical_gap:=10,        

User avatar
Ferenc Nagy
VIP Member
Posts: 343
Joined: 24 Apr 2007 12:26

Post by Ferenc Nagy » 25 Apr 2016 6:45

Harrison:
Do you mean to say that it is the responsibility of the developer to ensure that the frontTokens of all the button titles are unique?
Yes, I have thought so. That is why I recommended the removal of the ampersand before the characters to be underscored on the screen.
Imagine that button1 control has text "Draw &Logarithmic Plot" and button3 has text "Draw &Linear Plot", respectively.
What is their front token? The same "Draw". I do not know whether '&' character is a token or not. The second or the third token of the button texts are different.

Let me return to my latest corrections in my version of extended ask dialog.
Earlier I had positioned the buttons vertically using a linear function their index on their list multiplied by a fixed factor added to a fixed starting value.
The current version adds a gap to the bottom line of the current button rectangle and uses the result as the top line of the next button rectangle.
TIA, Regards,
Frank Nagy

Harrison Pratt
VIP Member
Posts: 260
Joined: 5 Nov 2000 0:01

Post by Harrison Pratt » 25 Apr 2016 13:13

I understand. That's why I wrote 3 variants of the askXXX predicates.
The '&' is a token delimiter in both alphabetic string and numeric strings. I think that relying on the '&' to tokenize is risky because it would be easy to later change the accelerator character and forget that you were using it to tokenize the string response. Your caution about stripping the first '&' is wise.

User avatar
Ferenc Nagy
VIP Member
Posts: 343
Joined: 24 Apr 2007 12:26

Post by Ferenc Nagy » 12 May 2016 16:39

Harrison,
I have two new ideas:
1) Define positive constants with informative names in the module where you evaluate the result of the extended ask dialog.

Code: Select all

constants     substitute_previous =0.     substitute_default =1.     substitute_minimal =2.     substitute_maximal =3.
The example is the validation of integer controls.

Code: Select all

% 2016.03.30. Substitute chosen special value of integer control.     substituteSpecialInteger:(integerControl, integer SpecialChoice) procedure.
Its clauses are:

Code: Select all

% 2016.03.30-2016.05.12. Substitute chosen special value of integer control. substituteSpecialInteger(Control,substitute_previous) :-     previousInteger(Control,V),     !,     Control:setInteger(V). substituteSpecialInteger(Control,substitute_maximal) :-     !,     Control:setInteger(Control:getMaximum()). substituteSpecialInteger(Control,substitute_minimal) :-     Control:setInteger(Control:getMinimum()),     !. substituteSpecialInteger(Control,_) :-     defaultInteger(Control,V),     !,     Control:setInteger(V). substituteSpecialInteger(_,_).
2) You can add a timeout property to the extended ask dialog:

Code: Select all

% 2014 Ferenc Nagy Budapest, Hungary % 2016.05.12.-2014.10.21. Extended common dialogs class extendedCommonDialogs     open core   constants    % Maximal timeout.    max_timeout = 86400.  % Count of seconds in a day.   predicates     % Ask with more than 3 answers.     ask : (string Title, string Prompt, string* ButtonTitlesList, positive Default) -> integer Answer.   properties    % 2016.05.12.    timeout: positive . % Timeout of dialog in seconds.    default: positive.     % 2016.04.24.     % Height factor.    height_factor : real.    % Fixed additon to text height.    height_plus : positive.   % Vertical gap between buttons.    vertical_gap : positive.   end class extendedCommonDialogs
The facts related to timeout in the *.pro file are:

Code: Select all

class facts     timeout: positive := max_timeout. % Timeout of dialog in seconds.     myTimer : timerId := erroneous.  % &#1080;&#1076;&#1077;&#1085;&#1090;&#1080;&#1092;&#1080;&#1082;&#1072;&#1090;&#1086;&#1088; &#1090;&#1072;&#1081;&#1084;&#1077;&#1088;&#1072;
The event handlers capable to give back the default value in case of timeout are below.

Code: Select all

class predicates     dlg_ask_eh : ehandler. clauses     dlg_ask_eh(Win,e_Create(_))=gui_api::rNull :-         H=winGetCtlHandle(Win,convert(ctlId,default)),         % Added on 2016.05.12.         if timeout<max_timeout then             myTimer := timerSet(Win,1000*timeout)         end if,         winSetFocus(H).       dlg_ask_eh(Win,e_Control(I,_,_,_CtrlInfo)) = gui_api::rNull:-         ask_result := I,         vpi::winDestroy(Win).   % 2016.05.12. Give back the default answer in case of timeout.    dlg_ask_eh(Win,e_Timer(myTimer))=gui_api::rNull :-         ask_result := default,         vpi::winDestroy(Win).  
The timeout can be preset in the constructor of the main form (A) or just before the call of the extended ask dialog (B).
A)

Code: Select all

clauses     new(Parent) :-         formWindow::new(Parent),         russian::setRussianIntegerMessages(),         russian::setRussianRealMessages(),         myDialogTricks::substitute:=russian::substitute,         myDialogTricks::value_choice_list:=russian::value_choice_list,         extendedCommonDialogs::height_factor :=1.25,         extendedCommonDialogs::height_plus:= 5,         extendedCommonDialogs::vertical_gap:=10,         extendedCommonDialogs::timeout:=120, % <=== 2016.05.12.         generatedInitialize().
B)

Code: Select all

predicates     onIntersectValidate:() -> control::validateResult. clauses     onIntersectValidate() = control::contentsInvalid(birth_lbx,death_lbx,Msg) :-         list::intersection(birth_lbx:getSelectedIndices(),death_lbx:getSelectedIndices())=Intersection,         Intersection<>[],         Title=string::format("% %",mystring::deleteAmpersand(fate_gb),"&#1085;&#1077;&#1076;&#1086;&#1087;&#1091;&#1089;&#1090;&#1080;&#1084;&#1099;"),         F="&#1044;&#1083;&#1103; &#1082;&#1088;&#1080;&#1090;&#1077;&#1088;&#1080;&#1081; &#1088;&#1086;&#1078;&#1076;&#1077;&#1085;&#1080;&#1103; &#1080; &#1076;&#1083;&#1103; &#1082;&#1088;&#1080;&#1090;&#1077;&#1088;&#1080;&#1081; &#1089;&#1084;&#1077;&#1088;&#1090;&#1080;\n"             "&#1085;&#1077;&#1083;&#1100;&#1079;&#1103; &#1074;&#1099;&#1088;&#1072;&#1090;&#1100; &#1088;&#1072;&#1074;&#1085;&#1099;&#1077; &#1095;&#1080;&#1089;&#1083;&#1072; &#1089;&#1086;&#1089;&#1077;&#1076;&#1077;&#1081; %.\n&#1042;&#1099;&#1090;&#1088;&#1080; &#1089;&#1077;&#1095;&#1077;&#1085;&#1080;&#1077; &#1089; ...",         Msg=string::format(F,toString(Intersection)),         % 2016.05.12. You can insert the local timeout here i seconds (not in milliseconds).         % extendedCommonDialogs::timeout:=240, % <=== 2016.05.12.         Del=extendedCommonDialogs::ask(Title,Msg,             ["&#1050;&#1088;&#1080;&#1090;&#1077;&#1088;&#1080;&#1080; &&#1088;&#1086;&#1078;&#1076;&#1077;&#1085;&#1080;&#1103;", "&#1050;&#1088;&#1080;&#1090;&#1077;&#1088;&#1080;&#1080; &&#1089;&#1084;&#1077;&#1088;&#1090;&#1080;","&&#1054;&#1073;&#1077;&#1080;&#1093; &#1089;&#1087;&#1080;&#1089;&#1082;&#1086;&#1074;"],2),         unselectIntersection(Del,InterSection,birth_lbx,death_lbx),        !. onIntersectValidate() = control::contentsOk.
TIA, Regards,
Frank Nagy

Harrison Pratt
VIP Member
Posts: 260
Joined: 5 Nov 2000 0:01

Post by Harrison Pratt » 12 May 2016 17:40

Frank,

Those are useful extensions, thanks for posting the code. :-)

When developing in VIP 5.2 I created a timed dialog to inform user of successful completion of some action. The dialog would appear for a certain period of time (seconds) and the lazy users wouldn't have to click [OK]. Too many applications require you to click unnecessarily for routine operation notifications. The predicate signature was something like this:

Code: Select all

adviseUser : ( window ParentWin, string Title, string Msg, positive DisplayTimeSeconds ).
If I recreate that in 7.5, I'll probably add a boolean parameter to optionally write the Title and Message to the application log file.

I'll take some time to study your code.
Thanks again,
Harrison

Post Reply