2 * Engine-settings dialog. The complexity come from an attempt to present the engine-defined options
\r
3 * in a nicey formatted layout. To this end we first run a back-end pre-formatter, which will distribute
\r
4 * the controls over two columns (the minimum required, as some are double width). It also takes care of
\r
5 * grouping options that start with the same word (mainly for "Polyglot ..." options). It assigns relative
\r
6 * suitability to break points between lines, and in the end decides if and where to break up the list
\r
7 * for display in multiple (2*N) columns.
\r
8 * The thus obtained list representing the topology of the layout is then passed to a front-end routine
\r
9 * that generates the actual dialog box from it.
\r
14 #include <windows.h>
\r
15 #include <Windowsx.h>
\r
19 #include "frontend.h"
\r
20 #include "backend.h"
\r
21 #include "winboard.h"
\r
22 #include "backendz.h"
\r
27 int layoutList[2*MAX_OPTIONS];
\r
28 int checkList[2*MAX_OPTIONS];
\r
29 int comboList[2*MAX_OPTIONS];
\r
30 int buttonList[2*MAX_OPTIONS];
\r
31 int boxList[2*MAX_OPTIONS];
\r
32 int groupNameList[2*MAX_OPTIONS];
\r
33 int breaks[MAX_OPTIONS];
\r
34 int checks, combos, buttons, layout, groups;
\r
35 char title[MSG_SIZ];
\r
36 char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
\r
37 Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick, isUCCI;
\r
38 extern Option installOptions[], matchOptions[];
\r
39 char *engineList[MAXENGINES] = {""}, *engineMnemonic[MAXENGINES] = {""};
\r
41 ChessProgramState *activeCps;
\r
43 int InstallOK P((void));
\r
44 typedef int ButtonCallback(HWND h);
\r
45 ButtonCallback *comboCallback;
\r
48 PrintOpt(int i, int right, Option *optionList)
\r
51 if(!right) fprintf(debugFP, "%30s", "");
\r
53 Option opt = optionList[i];
\r
57 fprintf(debugFP, "%20.20s [ +/-]", opt.name);
\r
62 fprintf(debugFP, "%20.20s [______________________________________]", opt.name);
\r
65 fprintf(debugFP, "%41.41s", opt.name);
\r
68 fprintf(debugFP, "[x] %-26.25s", opt.name);
\r
71 fprintf(debugFP, "%20.20s [ COMBO ]", opt.name);
\r
76 fprintf(debugFP, "[ %26.26s ]", opt.name);
\r
82 fprintf(debugFP, right ? "\n" : " ");
\r
86 CreateOptionDialogTest(int *list, int nr, Option *optionList)
\r
90 for(line = 0; line < nr; line+=2) {
\r
91 PrintOpt(list[line+1], 0, optionList);
\r
92 PrintOpt(list[line], 1, optionList);
\r
97 LayoutOptions(int firstOption, int endOption, char *groupName, Option *optionList)
\r
99 int i, b = strlen(groupName), stop, prefix, right, nextOption, firstButton = buttons;
\r
100 Control lastType, nextType;
\r
102 nextOption = firstOption;
\r
103 while(nextOption < endOption) {
\r
104 checks = combos = 0; stop = 0;
\r
105 lastType = Button; // kludge to make sure leading Spin will not be prefixed
\r
106 // first separate out buttons for later treatment, and collect consecutive checks and combos
\r
107 while(nextOption < endOption && !stop) {
\r
108 switch(nextType = optionList[nextOption].type) {
\r
109 case CheckBox: checkList[checks++] = nextOption; lastType = CheckBox; break;
\r
110 case ComboBox: comboList[combos++] = nextOption; lastType = ComboBox; break;
\r
113 case Button: buttonList[buttons++] = nextOption; lastType = Button; break;
\r
122 case Message: ; // cannot happen
\r
126 // We now must be at the end, or looking at a spin or textbox (in nextType)
\r
128 nextType = Button; // kudge to flush remaining checks and combos undistorted
\r
129 // Take a new line if a spin follows combos or checks, or when we encounter a textbox
\r
130 if((combos+checks || nextType == TextBox || nextType == ListBox || nextType == FileName || nextType == PathName || nextType == Label) && layout&1) {
\r
131 layoutList[layout++] = -1;
\r
133 // The last check or combo before a spin will be put on the same line as that spin (prefix)
\r
134 // and will thus not be grouped with other checks and combos
\r
136 if(nextType == Spin && lastType != Button) {
\r
137 if(lastType == CheckBox) prefix = checkList[--checks]; else
\r
138 if(lastType == ComboBox) prefix = comboList[--combos];
\r
140 // if a combo is followed by a textbox, it must stay at the end of the combo/checks list to appear
\r
141 // immediately above the textbox, so treat it as check. (A check would automatically be and remain there.)
\r
142 if((nextType == TextBox || nextType == ListBox || nextType == FileName || nextType == PathName) && lastType == ComboBox)
\r
143 checkList[checks++] = comboList[--combos];
\r
144 // Now append the checks behind the (remaining) combos to treat them as one group
\r
145 for(i=0; i< checks; i++)
\r
146 comboList[combos++] = checkList[i];
\r
147 // emit the consecutive checks and combos in two columns
\r
148 right = combos/2; // rounded down if odd!
\r
149 for(i=0; i<right; i++) {
\r
150 breaks[layout/2] = 2;
\r
151 layoutList[layout++] = comboList[i];
\r
152 layoutList[layout++] = comboList[i + right];
\r
154 // An odd check or combo (which could belong to following textBox) will be put in the left column
\r
155 // If there was an even number of checks and combos the last one will automatically be in that position
\r
157 layoutList[layout++] = -1;
\r
158 layoutList[layout++] = comboList[2*right];
\r
160 if(nextType == ListBox) {
\r
161 // A listBox will be left-adjusted, and cause rearrangement of the elements before it to the right column
\r
162 breaks[layout/2] = lastType == Button ? 0 : 100;
\r
163 layoutList[layout++] = -1;
\r
164 layoutList[layout++] = nextOption - 1;
\r
165 for(i=optionList[nextOption-1].min; i>0; i--) { // extra high text edit
\r
166 breaks[layout/2] = -1;
\r
167 layoutList[layout++] = -1;
\r
168 layoutList[layout++] = -1;
\r
171 if(nextType == TextBox || nextType == FileName || nextType == PathName || nextType == Label) {
\r
172 // A textBox is double width, so must be left-adjusted, and the right column remains empty
\r
173 breaks[layout/2] = lastType == Button ? 0 : 100;
\r
174 layoutList[layout++] = -1;
\r
175 layoutList[layout++] = nextOption - 1;
\r
176 if(optionList[nextOption-1].min) { // extra high text edit: goes right of existing listbox
\r
177 layout -= 2; // remove
\r
178 layoutList[layout-2*optionList[nextOption-1].min-2] = nextOption - 1;
\r
180 } else if(nextType == Spin) {
\r
181 // A spin will go in the next available position (right to left!). If it had to be prefixed with
\r
182 // a check or combo, this next position must be to the right, and the prefix goes left to it.
\r
183 layoutList[layout++] = nextOption - 1;
\r
184 if(prefix >= 0) layoutList[layout++] = prefix;
\r
187 // take a new line if needed
\r
188 if(layout&1) layoutList[layout++] = -1;
\r
189 // emit the buttons belonging in this group; loose buttons are saved for last, to appear at bottom of dialog
\r
191 while(buttons > firstButton)
\r
192 layoutList[layout++] = buttonList[--buttons];
\r
193 if(layout&1) layoutList[layout++] = -1;
\r
198 EndMatch(char *s1, char *s2)
\r
201 p = s1; while(*p) p++;
\r
202 q = s2; while(*q) q++;
\r
203 while(p > s1 && q > s2 && *p == *q) { p--; q--; }
\r
204 if(p[1] == 0) return NULL;
\r
209 DesignOptionDialog(int nrOpt, Option *optionList)
\r
215 buttons = groups = 0;
\r
216 while(k < nrOpt) { // k steps through 'solitary' options
\r
217 // look if we hit a group of options having names that start with the same word
\r
218 int groupSize = 1, groupNameLength = 50;
\r
219 sscanf(optionList[k].name, "%s", buf); // get first word of option name
\r
220 while(k + groupSize < nrOpt &&
\r
221 strstr(optionList[k+groupSize].name, buf) == optionList[k+groupSize].name) {
\r
223 for(j=0; j<groupNameLength; j++) // determine common initial part of option names
\r
224 if( optionList[k].name[j] != optionList[k+groupSize].name[j]) break;
\r
225 groupNameLength = j;
\r
229 if(groupSize > 3) {
\r
230 // We found a group to terminates the current section
\r
231 LayoutOptions(n, k, "", optionList); // flush all solitary options appearing before the group
\r
232 groupNameList[groups] = groupNameLength;
\r
233 boxList[groups++] = layout; // group start in even entries
\r
234 LayoutOptions(k, k+groupSize, buf, optionList); // flush the group
\r
235 boxList[groups++] = layout; // group end in odd entries
\r
236 k = n = k + groupSize;
\r
237 } else k += groupSize; // small groups are grouped with the solitary options
\r
239 if(n != k) LayoutOptions(n, k, "", optionList); // flush remaining solitary options
\r
240 // decide if and where we break into two column pairs
\r
242 // Emit buttons and add OK and cancel
\r
243 // for(k=0; k<buttons; k++) layoutList[layout++] = buttonList[k];
\r
245 // Create the dialog window
\r
246 if(appData.debugMode) CreateOptionDialogTest(layoutList, layout, optionList);
\r
247 // CreateOptionDialog(layoutList, layout, optionList);
\r
248 if(!activeCps) okFunc = optionList[nrOpt].target;
\r
251 #include <windows.h>
\r
253 extern HINSTANCE hInst;
\r
256 DLGITEMTEMPLATE item;
\r
264 DLGTEMPLATE header;
\r
269 wchar_t fontName[14];
\r
270 Item control[MAX_OPTIONS];
\r
272 { DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU | DS_SETFONT, 0, 0, 0, 0, 295, 300 },
\r
273 0x0000, 0x0000, L"Engine #1 Settings ", 8, L"MS Sans Serif"
\r
281 while(p = strchr(p, '\n')) p++, n++; // count linefeeds
\r
282 p = q = malloc(strlen(s) + n + 1);
\r
283 while(*p++ = *s++) if(p[-1] == '\n') p[-1] = '\r', *p++ = '\n';
\r
288 SetOptionValues(HWND hDlg, ChessProgramState *cps, Option *optionList)
\r
289 // Put all current option values in controls, and write option names next to them
\r
293 char **choices, *name;
\r
295 for(i=0; i<layout+buttons; i++) {
\r
296 int j=layoutList[i];
\r
297 if(j == -2) SetDlgItemText( hDlg, 2000+2*i, ". . ." );
\r
299 name = cps ? optionList[j].name : _(optionList[j].name);
\r
300 if(strstr(name, "Polyglot ") == name) name += 9;
\r
301 SetDlgItemText( hDlg, 2000+2*i, name );
\r
302 //if(appData.debugMode) fprintf(debugFP, "# %s = %d\n",optionList[j].name, optionList[j].value );
\r
303 switch(optionList[j].type) {
\r
305 SetDlgItemInt( hDlg, 2001+2*i, cps ? optionList[j].value : *(int*)optionList[j].target, TRUE );
\r
310 name = AddCR(cps ? optionList[j].textValue : *(char**)optionList[j].target); // stupid CR...
\r
311 SetDlgItemText( hDlg, 2001+2*i, name);
\r
315 CheckDlgButton( hDlg, 2000+2*i, (cps ? optionList[j].value : *(Boolean*)optionList[j].target) != 0);
\r
318 choices = (char**) optionList[j].textValue;
\r
319 hwndCombo = GetDlgItem(hDlg, 2001+2*i);
\r
320 SendMessage(hwndCombo, CB_RESETCONTENT, 0, 0);
\r
321 for(k=0; k<optionList[j].max; k++) {
\r
322 SendMessage(hwndCombo, CB_ADDSTRING, 0, (LPARAM) choices[k]);
\r
324 SendMessage(hwndCombo, CB_SELECTSTRING, (WPARAM) -1, (LPARAM) choices[optionList[j].value]);
\r
327 choices = (char**) optionList[j].choice;
\r
328 hwndCombo = GetDlgItem(hDlg, 2001+2*i);
\r
329 SendMessage(hwndCombo, LB_RESETCONTENT, 0, 0);
\r
330 for(k=0; k<optionList[j].max; k++) {
\r
331 SendMessage(hwndCombo, LB_ADDSTRING, 0, (LPARAM) choices[k]);
\r
340 SetDlgItemText( hDlg, IDOK, _("OK") );
\r
341 SetDlgItemText( hDlg, IDCANCEL, _("Cancel") );
\r
342 title[0] &= ~32; // capitalize
\r
343 SetWindowText( hDlg, title);
\r
344 for(i=0; i<groups; i+=2) {
\r
345 int id, p; char buf[MSG_SIZ];
\r
346 id = k = boxList[i];
\r
347 if(layoutList[k] < 0) k++;
\r
348 if(layoutList[k] < 0) continue;
\r
349 for(p=0; p<groupNameList[i]; p++) buf[p] = optionList[layoutList[k]].name[p];
\r
351 SetDlgItemText( hDlg, 2000+2*(id+MAX_OPTIONS), buf );
\r
357 GetOptionValues(HWND hDlg, ChessProgramState *cps, Option *optionList)
\r
358 // read out all controls, and if value is altered, remember it and send it to the engine
\r
361 int i, k, new=0, changed=0, len;
\r
362 char **choices, newText[MSG_SIZ], buf[MSG_SIZ], *text;
\r
365 for(i=0; i<layout; i++) {
\r
366 int j=layoutList[i];
\r
368 switch(optionList[j].type) {
\r
370 new = GetDlgItemInt( hDlg, 2001+2*i, &success, TRUE );
\r
371 if(!success) break;
\r
372 if(new < optionList[j].min) new = optionList[j].min;
\r
373 if(new > optionList[j].max) new = optionList[j].max;
\r
374 if(!cps) { *(int*)optionList[j].target = new; break; }
\r
375 changed = 2*(optionList[j].value != new);
\r
376 optionList[j].value = new;
\r
381 if(cps) len = MSG_SIZ - strlen(optionList[j].name) - 9, text = newText;
\r
382 else len = GetWindowTextLength(GetDlgItem(hDlg, 2001+2*i)) + 1, text = (char*) malloc(len);
\r
383 success = GetDlgItemText( hDlg, 2001+2*i, text, len );
\r
384 if(!success) text[0] = NULLCHAR; // empty string can be valid input
\r
387 p = (optionList[j].type != FileName ? strdup(text) : InterpretFileName(text, homeDir)); // all files relative to homeDir!
\r
388 FREE(*(char**)optionList[j].target); *(char**)optionList[j].target = p;
\r
389 free(text); text = p;
\r
390 while(*p++ = *text++) if(p[-1] == '\r') p--; // crush CR
\r
393 changed = strcmp(optionList[j].textValue, newText) != 0;
\r
394 safeStrCpy(optionList[j].textValue, newText, MSG_SIZ - (optionList[j].textValue - optionList[j].name) );
\r
397 new = IsDlgButtonChecked( hDlg, 2000+2*i );
\r
398 if(!cps) { *(Boolean*)optionList[j].target = new; break; }
\r
399 changed = 2*(optionList[j].value != new);
\r
400 optionList[j].value = new;
\r
403 choices = (char**) optionList[j].textValue;
\r
404 hwndCombo = GetDlgItem(hDlg, 2001+2*i);
\r
405 success = GetDlgItemText( hDlg, 2001+2*i, newText, MSG_SIZ );
\r
406 if(!success) break;
\r
408 for(k=0; k<optionList[j].max; k++) {
\r
409 if(choices[k] && !strcmp(choices[k], newText)) new = k;
\r
411 if(!cps && new > 0) {
\r
412 if(*(char**)optionList[j].target) free(*(char**)optionList[j].target);
\r
413 *(char**)optionList[j].target = strdup(optionList[j].choice[new]);
\r
416 changed = new >= 0 && (optionList[j].value != new);
\r
417 if(changed) optionList[j].value = new;
\r
420 if(optionList[j].textValue)
\r
421 *(int*) optionList[j].textValue = SendDlgItemMessage(hDlg, 2001+2*i, LB_GETCURSEL, 0, 0);
\r
424 break; // are treated instantly, so they have been sent already
\r
427 snprintf(buf, MSG_SIZ, "option %s=%d\n", optionList[j].name, new); else
\r
429 snprintf(buf, MSG_SIZ, "option %s=%s\n", optionList[j].name, newText);
\r
430 if(changed) SendToProgram(buf, cps);
\r
432 if(!cps && okFunc) return ((ButtonCallback*) okFunc)(0);
\r
436 char *defaultExt[] = { NULL, "pgn", "fen", "exe", "trn", "bin", "log", "ini" };
\r
438 LRESULT CALLBACK SettingsProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
\r
445 case WM_INITDIALOG:
\r
447 // CenterWindow(hDlg, GetWindow(hDlg, GW_OWNER));
\r
448 SetOptionValues(hDlg, activeCps, activeList);
\r
450 SetFocus(GetDlgItem(hDlg, IDCANCEL));
\r
455 switch( LOWORD(wParam) ) {
\r
457 if(!GetOptionValues(hDlg, activeCps, activeList)) return FALSE;
\r
458 EndDialog( hDlg, 0 );
\r
459 comboCallback = NULL; activeCps = NULL;
\r
463 EndDialog( hDlg, 1 );
\r
464 comboCallback = NULL; activeCps = NULL;
\r
468 // program-defined push buttons
\r
469 i = LOWORD(wParam);
\r
470 if( i>=2000 && i < 2000+2*(layout+buttons)) {
\r
471 j = layoutList[(i - 2000)/2];
\r
474 "All files\0*.*\0Game files\0*.pgn;*.gam\0Position files\0*.fen;*.epd;*.pos\0"
\r
475 "EXE files\0*.exe;*.jar\0Tournament files (*.trn)\0*.trn\0"
\r
476 "BIN Files\0*.bin\0LOG Files\0*.log\0INI Files\0*.ini\0"
\r
477 "Image files\0*.bmp\0\0";
\r
480 GetDlgItemText( hDlg, i+3, buf, MSG_SIZ );
\r
482 ZeroMemory( &ofn, sizeof(ofn) );
\r
484 ofn.lStructSize = sizeof(ofn);
\r
485 ofn.hwndOwner = hDlg;
\r
486 ofn.hInstance = hInst;
\r
487 ofn.lpstrFilter = filter;
\r
488 ofn.nFilterIndex = 1L + (ext = activeCps ? 0 : activeList[layoutList[(i-2000)/2+1]].max & 31);
\r
489 ofn.lpstrDefExt = defaultExt[ext];
\r
490 ofn.lpstrFile = buf;
\r
491 ofn.nMaxFile = sizeof(buf);
\r
492 ofn.lpstrTitle = _("Choose File");
\r
493 ofn.Flags = OFN_FILEMUSTEXIST | OFN_LONGNAMES | OFN_HIDEREADONLY;
\r
495 if( activeList[layoutList[(i-2000)/2+1]].max & 32 ?
\r
496 GetOpenFileName( &ofn ) :
\r
497 GetSaveFileName( &ofn ) ) {
\r
498 SetDlgItemText( hDlg, i+3, buf );
\r
502 GetDlgItemText( hDlg, i+3, buf, MSG_SIZ );
\r
503 if( BrowseForFolder( _("Choose Folder:"), buf ) ) {
\r
504 SetDlgItemText( hDlg, i+3, buf );
\r
508 if(comboCallback && activeList[j].type == ComboBox && HIWORD(wParam) == CBN_SELCHANGE) {
\r
509 if(j > 5) break; // Yegh! Must solve problem with more than one combobox in dialog
\r
510 (*comboCallback)(hDlg);
\r
513 if(activeList[j].type == ListBox && HIWORD(wParam) == /*LBN_SELCHANGE*/ LBN_DBLCLK) {
\r
514 ((ButtonCallback *) activeList[j].target)(hDlg);
\r
517 if( activeList[j].type == SaveButton)
\r
518 GetOptionValues(hDlg, activeCps, activeList);
\r
519 else if( activeList[j].type != Button) break;
\r
520 else if( !activeCps ) { (*(ButtonCallback*) activeList[j].target)(hDlg); break; }
\r
521 snprintf(buf, MSG_SIZ, "option %s\n", activeList[j].name);
\r
522 SendToProgram(buf, activeCps);
\r
533 void AddControl(int x, int y, int w, int h, int type, int style, int n)
\r
537 i = template.header.cdit++;
\r
538 template.control[i].item.style = style;
\r
539 template.control[i].item.dwExtendedStyle = 0;
\r
540 template.control[i].item.x = x;
\r
541 template.control[i].item.y = y;
\r
542 template.control[i].item.cx = w;
\r
543 template.control[i].item.cy = h;
\r
544 template.control[i].item.id = 2000 + n;
\r
545 template.control[i].code = 0xFFFF;
\r
546 template.control[i].controlType = type;
\r
547 template.control[i].d1 = ' ';
\r
548 template.control[i].data = 0;
\r
549 template.control[i].creationData = 0;
\r
552 void AddOption(int x, int y, Control type, int i)
\r
554 int extra, num = ES_NUMBER;
\r
558 num = 0; // needs text control for accepting negative numbers
\r
561 AddControl(x, y+1, 95, 9, 0x0082, SS_ENDELLIPSIS | WS_VISIBLE | WS_CHILD, i);
\r
562 AddControl(x+95, y, 50, 11, 0x0081, ES_AUTOHSCROLL | num | WS_BORDER | WS_VISIBLE | WS_CHILD | WS_TABSTOP, i+1);
\r
565 extra = 13*activeList[layoutList[i/2]].min; // when extra high, left-align and put description text above it
\r
566 AddControl(x+(extra?50:0), y+1, 95, 9, 0x0082, SS_ENDELLIPSIS | WS_VISIBLE | WS_CHILD, i);
\r
567 AddControl(x+(extra?50:95), y+(extra?13:0), extra?105:200, 11+(extra?extra-13:0), 0x0081, ES_AUTOHSCROLL | WS_BORDER | WS_VISIBLE |
\r
568 WS_CHILD | WS_TABSTOP | (extra ? ES_MULTILINE | ES_WANTRETURN | WS_VSCROLL :0), i+1);
\r
571 AddControl(x, y+1, 95, 9, 0x0082, SS_ENDELLIPSIS | WS_VISIBLE | WS_CHILD, i);
\r
572 extra = 13*activeList[layoutList[i/2]].min;
\r
573 AddControl(x, y+13, 105, 11+extra-13, 0x0083, LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_HSCROLL | WS_BORDER | LBS_NOTIFY |
\r
574 WS_VISIBLE | WS_CHILD | WS_TABSTOP, i+1);
\r
577 extra = activeList[layoutList[i/2]].value;
\r
578 AddControl(x+extra, y+1, 290-extra, 9, 0x0082, SS_ENDELLIPSIS | WS_VISIBLE | WS_CHILD | WS_TABSTOP, i);
\r
582 AddControl(x, y+1, 95, 9, 0x0082, SS_ENDELLIPSIS | WS_VISIBLE | WS_CHILD, i);
\r
583 AddControl(x+95, y, 180, 11, 0x0081, ES_AUTOHSCROLL | WS_BORDER | WS_VISIBLE | WS_CHILD | WS_TABSTOP, i+1);
\r
584 AddControl(x+275, y, 20, 12, 0x0080, BS_PUSHBUTTON | WS_VISIBLE | WS_CHILD | WS_TABSTOP, i-2);
\r
585 layoutList[i/2-1] = -2 - (type == PathName);
\r
588 AddControl(x, y, 145, 11, 0x0080, BS_AUTOCHECKBOX | WS_VISIBLE | WS_CHILD | WS_TABSTOP, i);
\r
591 AddControl(x, y+1, 95, 9, 0x0082, SS_ENDELLIPSIS | WS_VISIBLE | WS_CHILD, i);
\r
592 AddControl(x+95, y-1, !activeCps && x<10 ? 120 : 50, 500, 0x0085,
\r
593 CBS_AUTOHSCROLL | CBS_DROPDOWN | WS_VISIBLE | WS_CHILD | WS_TABSTOP | WS_VSCROLL, i+1);
\r
598 AddControl(x-2, y, 65, 13, 0x0080, BS_PUSHBUTTON | WS_VISIBLE | WS_CHILD | WS_TABSTOP, i);
\r
607 CreateDialogTemplate(int *layoutList, int nr, Option *optionList)
\r
609 int i, ii, j, x=1, y=0, maxY=0, buttonRows, breakPoint = 1000, k=0;
\r
611 template.header.cdit = 0;
\r
612 template.header.cx = 307;
\r
613 buttonRows = (buttons + 1 + 3)/4; // 4 per row, rounded up
\r
615 breakPoint = (nr+2*buttonRows+1)/2 & ~1;
\r
616 template.header.cx = 625;
\r
619 for(ii=0; ii<nr; ii++) {
\r
620 i = ii^1; if(i == nr) i = ii; // if two on one line, swap order of treatment, to get good (left to right) tabbing order.
\r
621 if(k < groups && ii == boxList[k]) {
\r
623 AddControl(x+2, y+13*(i>>1)-2, 301, 13*(boxList[k+1]-boxList[k]>>1)+8,
\r
624 0x0082, WS_VISIBLE | WS_CHILD | SS_BLACKFRAME, 2400);
\r
625 AddControl(x+60, y+13*(i>>1)-6, 10*groupNameList[k]/3, 10,
\r
626 0x0082, SS_ENDELLIPSIS | WS_VISIBLE | WS_CHILD, 2*(ii+MAX_OPTIONS));
\r
630 int neg = (optionList[j].type == Spin && optionList[j].min < 0 ? 100 : 0); // flags spin with negative range
\r
631 AddOption(x+155-150*(i&1), y+13*(i>>1)+5, optionList[j].type + neg, 2*i);
\r
632 // listboxes have the special power to adjust the width of the column they are in
\r
633 if(optionList[j].type == ListBox) x -= optionList[j].value, template.header.cx -= optionList[j].value;
\r
635 if(k < groups && ii+1 == boxList[k+1]) {
\r
638 if(ii+1 >= breakPoint && breaks[ii+1>>1] >= 0) { x += 318; maxY = y+13*(ii+1>>1)+5; y = -13*(ii+1>>1); breakPoint = 1000; }
\r
640 // add butons at the bottom of dialog window
\r
643 for(i=0; i<buttons; i++) {
\r
644 AddControl(x+70*(i%4)+5, y+18*(i/4), 65, 15, 0x0080, BS_PUSHBUTTON | WS_VISIBLE | WS_CHILD | WS_TABSTOP, 2*(nr+i));
\r
645 layoutList[nr+i] = buttonList[i];
\r
647 AddControl(x+225, y+18*(buttonRows-1), 30, 15, 0x0080, BS_PUSHBUTTON | WS_VISIBLE | WS_CHILD | WS_TABSTOP, IDOK-2000);
\r
648 AddControl(x+260, y+18*(buttonRows-1), 40, 15, 0x0080, BS_PUSHBUTTON | WS_VISIBLE | WS_CHILD | WS_TABSTOP, IDCANCEL-2000);
\r
649 y += 18*buttonRows; if(y < maxY) y = maxY;
\r
650 template.title[8] = optionList == first.option ? '1' : '2';
\r
651 template.header.cy = y+2;
\r
652 template.header.style &= ~WS_VSCROLL;
\r
656 EngineOptionsPopup(HWND hwnd, ChessProgramState *cps)
\r
658 FARPROC lpProc = MakeProcInstance( (FARPROC) SettingsProc, hInst );
\r
660 activeCps = cps; activeList = cps->option;
\r
661 snprintf(title, MSG_SIZ, _("%s Engine Settings (%s)"), T_(cps->which), cps->tidy);
\r
662 DesignOptionDialog(cps->nrOptions, cps->option);
\r
663 CreateDialogTemplate(layoutList, layout, cps->option);
\r
666 DialogBoxIndirect( hInst, &template.header, hwnd, (DLGPROC)lpProc );
\r
668 FreeProcInstance(lpProc);
\r
673 int EnterGroup P((HWND hDlg));
\r
675 static int engineNr, selected;
\r
679 if(selected >= 0) { ASSIGN(engineLine, engineList[selected]); }
\r
680 if(engineLine[0] == '#') { DisplayError(_("Select single engine from the group"), 0); return 0; }
\r
681 if(isUCCI) isUCI = 2;
\r
682 if(!engineNr) Load(&first, 0); else Load(&second, 1);
\r
686 Option installOptions[] = {
\r
687 // { 0, 0, 0, NULL, (void*) &engineLine, (char*) engineMnemonic, engineList, ComboBox, N_("Select engine from list:") },
\r
688 { 195, 14, 0, NULL, (void*) &EnterGroup, (char*) &selected, engineMnemonic, ListBox, N_("Select engine from list:") },
\r
689 { 0, 0, 0, NULL, NULL, NULL, NULL, Label, N_("or specify one below:") },
\r
690 { 0, 0, 0, NULL, (void*) &nickName, NULL, NULL, TextBox, N_("Nickname (optional):") },
\r
691 { 0, 0, 0, NULL, (void*) &useNick, NULL, NULL, CheckBox, N_("Use nickname in PGN tag") },
\r
692 { 0, 0, 32+3, NULL, (void*) &engineName, NULL, NULL, FileName, N_("Engine (.exe or .jar):") },
\r
693 { 0, 0, 0, NULL, (void*) ¶ms, NULL, NULL, TextBox, N_("command-line parameters:") },
\r
694 { 0, 0, 0, NULL, (void*) &wbOptions, NULL, NULL, TextBox, N_("Special WinBoard options:") },
\r
695 { 0, 0, 0, NULL, (void*) &engineDir, NULL, NULL, PathName, N_("directory:") },
\r
696 { 95, 0, 0, NULL, NULL, NULL, NULL, Label, N_("(Directory will be derived from engine path when left empty)") },
\r
697 { 0, 0, 0, NULL, (void*) &addToList, NULL, NULL, CheckBox, N_("Add this engine to the list") },
\r
698 { 0, 0, 0, NULL, (void*) &hasBook, NULL, NULL, CheckBox, N_("Must not use GUI book") },
\r
699 { 0, 0, 0, NULL, (void*) &storeVariant, NULL, NULL, CheckBox, N_("Force current variant with this engine") },
\r
700 { 0, 0, 0, NULL, (void*) &isUCI, NULL, NULL, CheckBox, N_("UCI") },
\r
701 { 0, 0, 0, NULL, (void*) &v1, NULL, NULL, CheckBox, N_("WB protocol v1 (skip waiting for features)") },
\r
702 { 0, 0, 0, NULL, (void*) &isUCCI, NULL, NULL, CheckBox, N_("UCCI / USI (uses specified /uxiAdapter)") },
\r
703 { 0, 1, 0, NULL, (void*) &InstallOK, "", NULL, EndMark , "" }
\r
707 GenericPopup(HWND hwnd, Option *optionList)
\r
709 FARPROC lpProc = MakeProcInstance( (FARPROC) SettingsProc, hInst );
\r
712 while(optionList[n].type != EndMark) n++;
\r
713 activeCps = NULL; activeList = optionList;
\r
714 DesignOptionDialog(n, optionList);
\r
715 CreateDialogTemplate(layoutList, layout, optionList);
\r
717 DialogBoxIndirect( hInst, &template.header, hwnd, (DLGPROC)lpProc );
\r
719 FreeProcInstance(lpProc);
\r
725 EnterGroup(HWND hDlg)
\r
728 HANDLE hwndCombo = GetDlgItem(hDlg, 2001+2*1);
\r
729 int i = SendDlgItemMessage(hDlg, 2001+2*1, LB_GETCURSEL, 0, 0);
\r
730 if(i == 0) buf[0] = NULLCHAR; // back to top level
\r
731 else if(engineList[i][0] == '#') safeStrCpy(buf, engineList[i], MSG_SIZ); // group header, open group
\r
733 ASSIGN(engineLine, engineList[i]);
\r
734 if(isUCCI) isUCI = 2;
\r
735 if(!engineNr) Load(&first, 0); else Load(&second, 1);
\r
736 EndDialog( hDlg, 0 );
\r
737 return 0; // normal line, select engine
\r
739 installOptions[0].max = NamesToList(firstChessProgramNames, engineList, engineMnemonic, buf); // replace list by only the group contents
\r
740 SendMessage(hwndCombo, LB_RESETCONTENT, 0, 0);
\r
741 SendMessage(hwndCombo, LB_ADDSTRING, 0, (LPARAM) buf);
\r
742 for(i=1; i<installOptions[0].max; i++) {
\r
743 SendMessage(hwndCombo, LB_ADDSTRING, 0, (LPARAM) engineMnemonic[i]);
\r
745 // SendMessage(hwndCombo, CB_SELECTSTRING, (WPARAM) 0, (LPARAM) buf);
\r
749 void LoadEnginePopUp(HWND hwnd, int nr)
\r
751 isUCI = isUCCI = storeVariant = v1 = useNick = FALSE; addToList = hasBook = TRUE; // defaults
\r
753 if(engineDir) free(engineDir); engineDir = strdup("");
\r
754 if(params) free(params); params = strdup("");
\r
755 if(nickName) free(nickName); nickName = strdup("");
\r
756 if(engineLine) free(engineLine); engineLine = strdup("");
\r
757 if(engineName) free(engineName); engineName = strdup("");
\r
758 ASSIGN(wbOptions, "");
\r
759 installOptions[0].max = NamesToList(firstChessProgramNames, engineList, engineMnemonic, ""); // only top level
\r
760 snprintf(title, MSG_SIZ, _("Load %s Engine"), nr ? _("second") : _("first"));
\r
762 GenericPopup(hwnd, installOptions);
\r
765 int PickTheme P((HWND hDlg));
\r
766 void DeleteTheme P((HWND hDlg));
\r
770 if(selected >= 0) { ASSIGN(engineLine, engineList[selected]); }
\r
771 if(engineLine[0] == '#') { DisplayError(_("Select single theme from the group"), 0); return 0; }
\r
776 Option themeOptions[] = {
\r
777 { 195, 14, 0, NULL, (void*) &PickTheme, (char*) &selected, engineMnemonic, ListBox, N_("Select theme from list:") },
\r
778 { 0, 0, 0, NULL, NULL, NULL, NULL, Label, N_("or specify new theme below:") },
\r
779 { 0, 0, 0, NULL, (void*) &nickName, NULL, NULL, TextBox, N_("Theme name:") },
\r
780 { 0, 0, 0, NULL, (void*) &appData.useBitmaps, NULL, NULL, CheckBox, N_("Use board textures") },
\r
781 { 0, 0, 32+0, NULL, (void*) &appData.liteBackTextureFile, NULL, NULL, FileName, N_("Light-square texture:") },
\r
782 { 0, 0, 32+0, NULL, (void*) &appData.darkBackTextureFile, NULL, NULL, FileName, N_("Dark-square texture:") },
\r
783 { 0, 0, 3, NULL, (void*) &appData.darkBackTextureMode, "", NULL, Spin, N_("Dark reorientation mode:") },
\r
784 { 0, 0, 3, NULL, (void*) &appData.liteBackTextureMode, "", NULL, Spin, N_("Light reorientation mode:") },
\r
785 { 0, 0, 0, NULL, (void*) &appData.useBorder, NULL, NULL, CheckBox, N_("Draw border around board") },
\r
786 { 0, 0, 32+0, NULL, (void*) &appData.border, NULL, NULL, FileName, N_("Optional border bitmap:") },
\r
787 { 0, 0, 0, NULL, NULL, NULL, NULL, Label, N_(" Beware: a specified piece font will prevail over piece bitmaps") },
\r
788 { 0, 0, 0, NULL, (void*) &appData.pieceDirectory, NULL, NULL, PathName, N_("Directory with piece bitmaps:") },
\r
789 { 0, 0, 0, NULL, (void*) &appData.useFont, NULL, NULL, CheckBox, N_("Use piece font") },
\r
790 { 0, 50, 150, NULL, (void*) &appData.fontPieceSize, "", NULL, Spin, N_("Font size (%):") },
\r
791 { 0, 0, 0, NULL, (void*) &appData.renderPiecesWithFont, NULL, NULL, TextBox, N_("Font name:") },
\r
792 { 0, 0, 0, NULL, (void*) &appData.fontToPieceTable, NULL, NULL, TextBox, N_("Font piece to char:") },
\r
793 // { 0, 0, 0, NULL, (void*) &DeleteTheme, NULL, NULL, Button, N_("Up") },
\r
794 // { 0, 0, 0, NULL, (void*) &DeleteTheme, NULL, NULL, Button, N_("Down") },
\r
795 { 0, 0, 0, NULL, (void*) &DeleteTheme, NULL, NULL, Button, N_("Delete Theme") },
\r
796 { 0, 1, 0, NULL, (void*) &ThemeOK, "", NULL, EndMark , "" }
\r
800 DeleteTheme (HWND hDlg)
\r
803 int i, selected = SendDlgItemMessage(hDlg, 2001+2*1, LB_GETCURSEL, 0, 0);
\r
804 HANDLE hwndCombo = GetDlgItem(hDlg, 2001+2*1);
\r
805 if(selected < 0) return;
\r
806 if(p = strstr(appData.themeNames, engineList[selected])) {
\r
807 if(q = strchr(p, '\n')) strcpy(p, q+1);
\r
809 themeOptions[0].max = NamesToList(appData.themeNames, engineList, engineMnemonic, ""); // replace list by only the group contents
\r
810 SendMessage(hwndCombo, LB_RESETCONTENT, 0, 0);
\r
811 SendMessage(hwndCombo, LB_ADDSTRING, 0, (LPARAM) "");
\r
812 for(i=1; i<themeOptions[0].max; i++) {
\r
813 SendMessage(hwndCombo, LB_ADDSTRING, 0, (LPARAM) engineMnemonic[i]);
\r
818 PickTheme (HWND hDlg)
\r
821 HANDLE hwndCombo = GetDlgItem(hDlg, 2001+2*1);
\r
822 int i = SendDlgItemMessage(hDlg, 2001+2*1, LB_GETCURSEL, 0, 0);
\r
823 if(i == 0) buf[0] = NULLCHAR; // back to top level
\r
824 else if(engineList[i][0] == '#') safeStrCpy(buf, engineList[i], MSG_SIZ); // group header, open group
\r
826 ASSIGN(engineLine, engineList[i]);
\r
828 EndDialog( hDlg, 0 );
\r
829 return 0; // normal line, select engine
\r
831 themeOptions[0].max = NamesToList(appData.themeNames, engineList, engineMnemonic, buf); // replace list by only the group contents
\r
832 SendMessage(hwndCombo, LB_RESETCONTENT, 0, 0);
\r
833 SendMessage(hwndCombo, LB_ADDSTRING, 0, (LPARAM) buf);
\r
834 for(i=1; i<themeOptions[0].max; i++) {
\r
835 SendMessage(hwndCombo, LB_ADDSTRING, 0, (LPARAM) engineMnemonic[i]);
\r
840 void ThemeOptionsPopup(HWND hwnd)
\r
842 addToList = TRUE; // defaults
\r
843 if(nickName) free(nickName); nickName = strdup("");
\r
844 if(engineLine) free(engineLine); engineLine = strdup("");
\r
845 themeOptions[0].max = NamesToList(appData.themeNames, engineList, engineMnemonic, ""); // only top level
\r
846 snprintf(title, MSG_SIZ, _("Board themes"));
\r
848 GenericPopup(hwnd, themeOptions);
\r
851 Boolean autoinc, twice, swiss;
\r
856 if(autoinc) appData.loadGameIndex = appData.loadPositionIndex = -(twice + 1); else
\r
857 if(!appData.loadGameFile[0]) appData.loadGameIndex = -2*twice; // kludge to pass value of "twice" for use in GUI book
\r
858 if(swiss) { appData.defaultMatchGames = 1; appData.tourneyType = -1; }
\r
859 if(CreateTourney(tfName) && !matchMode) { // CreateTourney reloads original settings if file already existed
\r
861 return 1; // close dialog
\r
863 return matchMode || !appData.participants[0]; // if we failed to create and are not in playing, forbid popdown if there are participants
\r
866 void PseudoOK(HWND hDlg)
\r
869 saveOK = okFunc; okFunc = 0;
\r
870 GetOptionValues(hDlg, activeCps, activeList);
\r
871 EndDialog( hDlg, 0 );
\r
872 comboCallback = NULL; activeCps = NULL;
\r
874 if(autoinc) appData.loadGameIndex = appData.loadPositionIndex = -(twice + 1); else
\r
875 if(!appData.loadGameFile[0]) appData.loadGameIndex = -2*twice; // kludge to pass value of "twice" for use in GUI book
\r
876 if(swiss) { appData.defaultMatchGames = 1; appData.tourneyType = -1; }
\r
879 char *GetParticipants(HWND hDlg)
\r
881 int len = GetWindowTextLength(GetDlgItem(hDlg, 2001+2*0)) + 1;
\r
882 char *participants,*p, *q;
\r
883 if(len < 4) return NULL; // box is empty (enough)
\r
884 participants = (char*) malloc(len);
\r
885 GetDlgItemText(hDlg, 2001+2*0, participants, len );
\r
886 p = q = participants;
\r
887 while(*p++ = *q++) if(p[-1] == '\r') p--;
\r
888 return participants;
\r
891 void ReplaceParticipant(HWND hDlg)
\r
893 char *participants = GetParticipants(hDlg);
\r
894 Substitute(participants, TRUE);
\r
897 void UpgradeParticipant(HWND hDlg)
\r
899 char *participants = GetParticipants(hDlg);
\r
900 Substitute(participants, FALSE);
\r
903 void Inspect(HWND hDlg)
\r
906 char name[MSG_SIZ];
\r
907 GetDlgItemText(hDlg, 2001+2*33, name, MSG_SIZ );
\r
908 if(name[0] && (f = fopen(name, "r")) ) {
\r
909 char *saveSaveFile;
\r
910 saveSaveFile = appData.saveGameFile; appData.saveGameFile = NULL; // this is a persistent option, protect from change
\r
911 ParseArgsFromFile(f);
\r
912 autoinc = ((appData.loadPositionFile[0] ? appData.loadGameIndex : appData.loadPositionIndex) < 0);
\r
913 twice = ((appData.loadPositionFile[0] ? appData.loadGameIndex : appData.loadPositionIndex) == -2);
\r
914 swiss = appData.tourneyType < 0;
\r
915 SetOptionValues(hDlg, NULL, activeList);
\r
916 FREE(appData.saveGameFile); appData.saveGameFile = saveSaveFile;
\r
917 } else DisplayError(_("First you must specify an existing tourney file to clone"), 0);
\r
920 void TimeControlOptionsPopup P((HWND hDlg));
\r
921 void UciOptionsPopup P((HWND hDlg));
\r
922 int AddToTourney P((HWND hDlg));
\r
924 Option tourneyOptions[] = {
\r
925 { 80, 15, 0, NULL, (void*) &AddToTourney, NULL, engineMnemonic, ListBox, N_("Select Engine:") },
\r
926 { 0xD, 15, 0, NULL, (void*) &appData.participants, "", NULL, TextBox, N_("Tourney participants:") },
\r
927 { 0, 0, 4, NULL, (void*) &tfName, "", NULL, FileName, N_("Tournament file:") },
\r
928 { 30, 0, 0, NULL, NULL, NULL, NULL, Label, N_("If you specify an existing file, the rest of this dialog will be ignored.") },
\r
929 { 30, 0, 0, NULL, NULL, NULL, NULL, Label, N_("Otherwise, the file will be created, with the settings you specify below:") },
\r
930 { 0, 0, 0, NULL, (void*) &swiss, "", NULL, CheckBox, N_("Use Swiss pairing engine (cycles = rounds)") },
\r
931 { 0, 0, 10, NULL, (void*) &appData.tourneyType, "", NULL, Spin, N_("Tourney type (0=RR, 1=gauntlet):") },
\r
932 { 0, 0, 0, NULL, (void*) &appData.cycleSync, "", NULL, CheckBox, N_("Sync after cycle") },
\r
933 { 0, 1, 1000000000, NULL, (void*) &appData.tourneyCycles, "", NULL, Spin, N_("Number of tourney cycles:") },
\r
934 { 0, 0, 0, NULL, (void*) &appData.roundSync, "", NULL, CheckBox, N_("Sync after round") },
\r
935 { 0, 1, 1000000000, NULL, (void*) &appData.defaultMatchGames, "", NULL, Spin, N_("Games per Match / Pairing:") },
\r
936 { 0, 0, 1, NULL, (void*) &appData.saveGameFile, "", NULL, FileName, N_("File for saving tourney games:") },
\r
937 { 0, 0, 32+1, NULL, (void*) &appData.loadGameFile, "", NULL, FileName, N_("Game File with Opening Lines:") },
\r
938 { 0, -2, 1000000000, NULL, (void*) &appData.loadGameIndex, "", NULL, Spin, N_("Game Number:") },
\r
939 { 0, 0, 32+2, NULL, (void*) &appData.loadPositionFile, "", NULL, FileName, N_("File with Start Positions:") },
\r
940 { 0, -2, 1000000000, NULL, (void*) &appData.loadPositionIndex, "", NULL, Spin, N_("Position Number:") },
\r
941 { 0, 0, 0, NULL, (void*) &autoinc, "", NULL, CheckBox, N_("Step through lines/positions in file") },
\r
942 { 0, 0, 1000000000, NULL, (void*) &appData.rewindIndex, "", NULL, Spin, N_("Rewind after (0 = never):") },
\r
943 { 0, 0, 0, NULL, (void*) &twice, "", NULL, CheckBox, N_("Use each line/position twice") },
\r
944 { 0, 0, 0, NULL, (void*) &appData.defNoBook, "", NULL, CheckBox, N_("Make all use GUI book by default") },
\r
945 { 0, 0, 1000000000, NULL, (void*) &appData.matchPause, "", NULL, Spin, N_("Pause between Games (ms):") },
\r
946 { 0, 0, 0, NULL, (void*) &ReplaceParticipant, "", NULL, Button, N_("Replace Engine") },
\r
947 { 0, 0, 0, NULL, (void*) &UpgradeParticipant, "", NULL, Button, N_("Upgrade Engine") },
\r
948 { 0, 0, 0, NULL, (void*) &TimeControlOptionsPopup, "", NULL, Button, N_("Time Control...") },
\r
949 { 0, 0, 0, NULL, (void*) &UciOptionsPopup, "", NULL, Button, N_("Common Engine...") },
\r
950 { 0, 0, 0, NULL, (void*) &Inspect, "", NULL, Button, N_("Clone Tourney") },
\r
951 { 0, 0, 0, NULL, (void*) &PseudoOK, "", NULL, Button, N_("Continue Later") },
\r
952 { 0, 0, 0, NULL, (void*) &MatchOK, "", NULL, EndMark , "" }
\r
955 int AddToTourney(HWND hDlg)
\r
958 HANDLE hwndCombo = GetDlgItem(hDlg, 2001+2*1);
\r
959 int i = SendDlgItemMessage(hDlg, 2001+2*1, LB_GETCURSEL, 0, 0);
\r
961 if(i == 0) buf[0] = NULLCHAR; // back to top level
\r
962 else if(engineList[i][0] == '#') safeStrCpy(buf, engineList[i], MSG_SIZ); // group header, open group
\r
963 else { // normal line, select engine
\r
964 snprintf(buf, MSG_SIZ, "%s\r\n", engineMnemonic[i]);
\r
965 SendMessage( GetDlgItem(hDlg, 2001+2*0), EM_SETSEL, 99999, 99999 );
\r
966 SendMessage( GetDlgItem(hDlg, 2001+2*0), EM_REPLACESEL, (WPARAM) FALSE, (LPARAM) buf );
\r
969 tourneyOptions[0].max = NamesToList(firstChessProgramNames, engineList, engineMnemonic, buf); // replace list by only the group contents
\r
970 SendMessage(hwndCombo, LB_RESETCONTENT, 0, 0);
\r
971 SendMessage(hwndCombo, LB_ADDSTRING, 0, (LPARAM) buf);
\r
972 for(i=1; i<tourneyOptions[0].max; i++) {
\r
973 SendMessage(hwndCombo, LB_ADDSTRING, 0, (LPARAM) engineMnemonic[i]);
\r
975 // SendMessage(hwndCombo, CB_SELECTSTRING, (WPARAM) 0, (LPARAM) buf);
\r
979 void TourneyPopup(HWND hwnd)
\r
981 int n = NamesToList(firstChessProgramNames, engineList, engineMnemonic, "");
\r
982 autoinc = appData.loadGameIndex < 0 || appData.loadPositionIndex < 0;
\r
983 twice = appData.loadGameIndex == -2 || appData.loadPositionIndex == -2; swiss = appData.tourneyType < 0;
\r
984 tourneyOptions[0].max = n;
\r
985 snprintf(title, MSG_SIZ, _("Tournament and Match Options"));
\r
986 ASSIGN(tfName, appData.tourneyFile[0] ? appData.tourneyFile : MakeName(appData.defName));
\r
988 GenericPopup(hwnd, tourneyOptions);
\r