egra: optimised SSE memfill and colorblend
[iv.d.git] / egtui / editor / dialogs.d
blobe161b3d453e0f482becfc28f0e5e5453b3269712
1 /* Invisible Vector Library
2 * simple FlexBox-based TUI engine
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, version 3 of the License ONLY.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 module iv.egtui.editor.dialogs /*is aliced*/;
18 import iv.alice;
19 import iv.rawtty;
20 import iv.strex;
21 import iv.vfs.io;
23 import iv.egtui.tty;
24 import iv.egtui.tui;
25 import iv.egtui.parser;
28 /* history ids for editor dialogs:
29 * dlg-linenum-lnum
30 * line number dialog
32 * dlg-tabsize-tabsize"
33 * tab size dialog
35 * dlg-srr-edsearch
36 * search-and-replace dialog, search pattern
38 * dlg-srr-edreplace
39 * search-and-replace dialog, replace pattern
43 // ///////////////////////////////////////////////////////////////////////// //
44 int dialogFileModified (const(char)[] filename, bool def, const(char)[] query="File was modified. Save it?") {
45 string dotsdots = "";
46 if (filename.length > ttyw-10) {
47 // strip filename
48 dotsdots = "...";
49 filename = filename[$-(ttyw-13)..$];
52 enum laydesc = q{
53 caption: "File was modified!"
54 small-frame: false
55 // hbox for text
56 hbox: {
57 textview: {
58 id: "text"
59 text: `\C${query}\n\C<${dotsdots}${filename}>`
62 hline
63 // center buttons
64 hbox: {
65 span: { flex: 1 }
66 button: { id: "bttan" caption: "&tan" }
67 spacer: { width: 1 } // this hack just inserts space
68 button: { id: "btona" caption: "o&na" }
69 span: { flex: 1 }
73 auto ctx = FuiContext.create();
74 //ctx.maxDimensions = FuiSize(ttyw, ttyh);
75 ctx.parse!(dotsdots, filename, query)(laydesc);
76 ctx.relayout();
77 ctx.focused = ctx[def ? "bttan" : "btona"];
78 auto res = ctx.modalDialog;
79 if (res >= 0) return (ctx.itemId(res) == "bttan" ? 1 : 0);
80 return -1;
84 // ///////////////////////////////////////////////////////////////////////// //
85 // 0: invalid number
86 int dialogLineNumber (FuiHistoryManager dghisman, int defval=-1) {
87 enum laydesc = q{
88 caption: "Select line number"
89 small-frame: false
90 // hbox for text
91 hbox: {
92 label: {
93 text: `\RLine &number: `
94 dest: "lnum"
96 editline: {
97 flex: 1
98 id: "dlg-linenum-lnum"
99 on-action: validate
102 hline
103 // center buttons
104 hbox: {
105 span: { flex: 1 }
106 button: { id: "btok" caption: " O&K " default }
107 span: { flex: 1 }
111 auto ctx = FuiContext.create();
113 int edGetNum (int item) {
114 if (auto edl = ctx.itemAs!"editline"(item)) {
115 auto ed = edl.ed;
116 if (ed is null) return -1;
117 int num = 0;
118 auto rng = ed[];
119 while (!rng.empty && rng.front <= ' ') rng.popFront();
120 if (rng.empty || !rng.front.isdigit) return -1;
121 while (!rng.empty && rng.front.isdigit) {
122 num = num*10+rng.front-'0';
123 rng.popFront();
125 while (!rng.empty && rng.front <= ' ') rng.popFront();
126 return (rng.empty ? num : -1);
128 return -1;
131 int validate (FuiContext ctx, int item) {
132 ctx.setEnabled(ctx["btok"], edGetNum(item) > 0);
133 return FuiContinue;
136 //ctx.maxDimensions = FuiSize(ttyw, ttyh);
137 ctx.parse!(validate)(laydesc);
138 ctx.dialogHistoryManager = dghisman;
139 ctx.relayout();
140 validate(ctx, ctx["dlg-linenum-lnum"]);
141 //ctx.focused = ctx["dlg-linenum-lnum"];
142 if (defval > 0) {
143 import std.conv : to;
144 with (ctx.itemAs!"editline"("dlg-linenum-lnum")) ed.setNewText(defval.to!string);
145 validate(ctx, ctx["dlg-linenum-lnum"]);
147 auto res = ctx.modalDialog;
148 if (res >= 0) {
149 auto ln = edGetNum(ctx["dlg-linenum-lnum"]);
150 if (ln > 0) {
151 if (auto hisman = ctx.dialogHistoryManager) {
152 import std.conv : to;
153 hisman.add("dlg-linenum-lnum", ln.to!string);
156 return ln;
158 return -1;
162 // ///////////////////////////////////////////////////////////////////////// //
163 // <=0: invalid number
164 int dialogTabSize (FuiHistoryManager dghisman, int defval) {
165 enum laydesc = q{
166 caption: "Select Tab Size"
167 small-frame: false
168 // hbox for text
169 hbox: {
170 label: {
171 text: `\R&Tab size: `
172 dest: "dlg-tabsize-tabsize"
174 editline: {
175 flex: 1
176 id: "dlg-tabsize-tabsize"
177 on-action: validate
180 hline
181 // center buttons
182 hbox: {
183 span: { flex: 1 }
184 button: { id: "btok" caption: " O&K " default }
185 span: { flex: 1 }
189 auto ctx = FuiContext.create();
191 int edGetNum (int item) {
192 if (auto edl = ctx.itemAs!"editline"(item)) {
193 auto ed = edl.ed;
194 if (ed is null) return -1;
195 int num = 0;
196 auto rng = ed[];
197 while (!rng.empty && rng.front <= ' ') rng.popFront();
198 if (rng.empty || !rng.front.isdigit) return -1;
199 while (!rng.empty && rng.front.isdigit) {
200 num = num*10+rng.front-'0';
201 rng.popFront();
203 while (!rng.empty && rng.front <= ' ') rng.popFront();
204 return (rng.empty ? num : -1);
206 return -1;
209 int validate (FuiContext ctx, int item) {
210 auto num = edGetNum(item);
211 ctx.setEnabled(ctx["btok"], (num > 0 && num <= 32));
212 return FuiContinue;
215 //ctx.maxDimensions = FuiSize(ttyw, ttyh);
216 ctx.parse!(validate)(laydesc);
217 ctx.dialogHistoryManager = dghisman;
218 ctx.relayout();
219 validate(ctx, ctx["dlg-tabsize-tabsize"]);
220 //ctx.focused = ctx["dlg-tabsize-tabsize"];
221 if (defval > 0) {
222 import std.conv : to;
223 with (ctx.itemAs!"editline"("dlg-tabsize-tabsize")) ed.setNewText(defval.to!string);
224 validate(ctx, ctx["dlg-tabsize-tabsize"]);
226 auto res = ctx.modalDialog;
227 if (res >= 0) {
228 auto ts = edGetNum(ctx["dlg-tabsize-tabsize"]);
229 if (ts > 0) {
230 if (auto hisman = ctx.dialogHistoryManager) {
231 import std.conv : to;
232 hisman.add("dlg-tabsize-tabsize", ts.to!string);
235 return ts;
237 return -1;
241 // ///////////////////////////////////////////////////////////////////////// //
242 // <0: cancel
243 struct SearchReplaceOptions {
244 // WARNING! keep in sync with window layout!
245 enum Type : int {
246 Normal,
247 Regex,
249 const(char)[] search;
250 const(char)[] replace;
251 bool inselenabled = true; // "in selection" enabled
252 bool utfuck;
253 Type type;
254 bool casesens;
255 bool backwards;
256 bool wholeword;
257 bool inselection;
258 bool nocomments;
261 bool dialogSearchReplace (FuiHistoryManager dghisman, ref SearchReplaceOptions opts) {
262 enum laydesc = q{
263 caption: "Replace"
264 small-frame: false
266 label: { caption: "&Search string:" dest: "dlg-srr-edsearch" }
267 editline: { align: expand id: "dlg-srr-edsearch" text: "$searchstr" on-action: validate utfuck: $utfuck }
269 label: { caption: "Re&placement string:" dest: "dlg-srr-edreplace" }
270 editline: { align: expand id: "dlg-srr-edreplace" text: "$replacestr" on-action: validate utfuck: $utfuck }
272 hline
274 hbox: {
275 spacing: 1
276 vbox: {
277 flex: 0
278 radio: { caption: "No&rmal" bind-var: opttype }
279 radio: { caption: "Re&gular expression" bind-var: opttype }
281 vbox: {
282 flex: 0
283 checkbox: { caption: "Cas&e sensitive" bind-var: optci }
284 checkbox: { caption: "&Backwards" bind-var: optback }
285 checkbox: { caption: "&Whole words" bind-var: optword }
286 checkbox: { caption: "In se&lection" id: "cbinsel" bind-var: optsel }
287 checkbox: { caption: "S&kip comments" id: "cbnocom" bind-var: optnocom }
291 hline
293 hbox: {
294 spacing: 1
295 hspan
296 button: { id: "btok" caption: " O&K " default }
297 button: { id: "btcancel" caption: "&Cancel" }
298 hspan
302 bool utfuck = opts.utfuck;
303 int opttype = opts.type;
304 bool optci = opts.casesens;
305 bool optback = opts.backwards;
306 bool optword = opts.wholeword;
307 bool optsel = opts.inselection;
308 bool optnocom = opts.nocomments;
309 auto searchstr = opts.search;
310 auto replacestr = opts.replace;
312 auto ctx = FuiContext.create();
314 int validate (FuiContext ctx, int item=-1) {
315 bool ok = true;
316 if (auto edl = ctx.itemAs!"editline"("dlg-srr-edsearch")) {
317 if (edl.ed.textsize == 0) ok = false;
319 ctx.setEnabled(ctx["btok"], ok);
320 ctx.setEnabled(ctx["cbinsel"], opts.inselenabled);
321 return FuiContinue;
324 //searchstr = "koi";
325 //replacestr = "w";
326 //opttype = SearchReplaceOptions.Type.Regex;
328 ctx.parse!(opttype, optci, optback, optsel, searchstr, replacestr, validate, utfuck, optword, optnocom)(laydesc);
329 ctx.dialogHistoryManager = dghisman;
330 ctx.relayout();
331 if (ctx.layprops(0).position.w < ttyw/3*2) {
332 ctx.layprops(0).minSize.w = ttyw/3*2;
333 ctx.relayout();
335 validate(ctx);
336 auto res = ctx.modalDialog;
337 if (ctx.itemId(res) == "btok") {
338 opts.type = cast(SearchReplaceOptions.Type)(opttype >= SearchReplaceOptions.Type.min && opttype <= SearchReplaceOptions.Type.max ? opttype : 0);
339 opts.casesens = optci;
340 opts.backwards = optback;
341 opts.wholeword = optword;
342 opts.inselection = optsel;
343 opts.nocomments = optnocom;
344 opts.search = ctx.editlineGetText(ctx["dlg-srr-edsearch"]);
345 opts.replace = ctx.editlineGetText(ctx["dlg-srr-edreplace"]);
346 if (auto hisman = ctx.dialogHistoryManager) {
347 hisman.add("dlg-srr-edsearch", opts.search);
348 hisman.add("dlg-srr-edreplace", opts.replace);
350 return true;
352 return false;
356 // ///////////////////////////////////////////////////////////////////////// //
357 enum DialogRepPromptResult {
358 Cancel = -1,
359 Skip = 0,
360 Replace = 1,
361 All = 2
364 DialogRepPromptResult dialogReplacePrompt (int sy=-1) {
365 enum laydesc = q{
366 caption: "Confirm replace"
367 small-frame: false
369 label: { align: expand caption: `\CPattern found. What to do?` }
371 hline
373 hbox: {
374 spacing: 1
375 hspan
376 button: { id: "btreplace" caption: " &Replace " default }
377 button: { id: "btall" caption: "A&ll" }
378 button: { id: "btskip" caption: "&Skip" }
379 button: { id: "btcancel" caption: "&Cancel" }
380 hspan
384 auto ctx = FuiContext.create();
385 ctx.parse(laydesc);
386 ctx.relayout();
387 if (sy >= 0 && sy < ttyh) {
388 if (sy+1+ctx.layprops(0).position.h < ttyh-1) {
389 ctx.layprops(0).position.y = sy+1;
390 } else if (sy-1-ctx.layprops(0).position.h >= 0) {
391 ctx.layprops(0).position.y = sy-1-ctx.layprops(0).position.h;
394 auto res = ctx.modalDialog;
395 if (res < 0) return DialogRepPromptResult.Cancel;
396 auto rid = ctx.itemId(res);
397 if (rid == "btreplace") return DialogRepPromptResult.Replace;
398 if (rid == "btall") return DialogRepPromptResult.All;
399 if (rid == "btskip") return DialogRepPromptResult.Skip;
400 return DialogRepPromptResult.Cancel;
404 // ///////////////////////////////////////////////////////////////////////// //
405 // <0: cancelled
406 int dialogCodePage (int curcp) {
407 enum laydesc = q{
408 caption: "Select codepage"
409 small-frame: false
411 listbox: {
412 id: "lbcp"
413 items: {
414 "KOI8-U"
415 "CP1251"
416 "CP866"
417 "UTF-8"
421 hline
423 hbox: {
424 spacing: 1
425 hspan
426 button: { id: "btok" caption: " O&K " default }
427 button: { id: "btcancel" caption: "&Cancel" }
428 hspan
432 auto ctx = FuiContext.create();
433 ctx.parse(laydesc);
434 ctx.relayout();
435 if (curcp < 0) curcp = 0; else if (curcp > 3) curcp = 3;
436 ctx.listboxItemSetCurrent(ctx["lbcp"], curcp);
437 //ctx.setDialogPalette(TuiPaletteError);
438 auto res = ctx.modalDialog;
439 if (ctx.itemId(res) != "btok") return -1;
440 return ctx.listboxItemCurrent(ctx["lbcp"]);
444 // ///////////////////////////////////////////////////////////////////////// //
445 // return -1 on escape or index
446 // tries to show it under (or above) (winx, winy), so the line itself is visible
447 int dialogSelectAC(T : const(char)[]) (T[] items, int winx, int winy, int idx=0, int maxhgt=-1) {
448 if (items.length == 0) return -1;
450 if (maxhgt < 0 || maxhgt > ttyh) maxhgt = ttyh;
451 if (maxhgt < 3) maxhgt = 3;
453 int maxwdt = ttyw;
454 if (maxwdt < 3) maxwdt = 3;
456 int topline = 0;
457 int maxlen = 0;
458 foreach (const s; items) if (s.length > maxlen) maxlen = cast(int)s.length;
459 if (maxlen > ttyw-4) maxlen = ttyw-4;
461 int pgsize = cast(int)items.length;
462 if (pgsize > maxhgt-2) pgsize = maxhgt-2;
464 if (winx < 0) {
465 winx = (ttyw-(maxlen+4))/2;
466 if (winx < 0) winx = 0;
468 if (winy < 0) {
469 winy = (ttyh-(pgsize+2))/2;
470 if (winy < 0) winy = 0;
473 int x0 = winx;
474 int y0 = winy;
475 // no room to show it at the bottom?
476 if (y0+pgsize+1 > ttyh) {
477 y0 = winy-pgsize-2;
478 // no room to show it at the top? center it then
479 if (y0 < 0 || y0 >= ttyh) y0 = (ttyh-(pgsize+2))/2;
481 if (x0+maxlen+4 > ttyw) x0 = ttyw-maxlen-4;
482 if (x0 < 0) x0 = 0;
483 if (y0 < 0) y0 = 0;
485 int winhgt = pgsize+2;
486 int winwdt = maxlen+4;
488 enum laydesc = q{
489 //caption: "Completions"
490 small-frame: true
491 enter-close: true
492 min-height: $winhgt
493 min-width: $winwdt
494 max-height: $maxhgt
495 max-width: $maxwdt
497 listbox: {
498 id: "lbac"
499 flex: 1
500 align: expand
504 auto ctx = FuiContext.create();
505 ctx.parse!(winhgt, winwdt, maxwdt, maxhgt)(laydesc);
507 // add items
508 auto lbi = ctx["lbac"];
509 assert(lbi > 0);
510 foreach (const s; items) ctx.listboxItemAdd(lbi, s);
511 //ctx.listboxItemSetCurrent(lbi, cast(int)items.length-1);
513 ctx.relayout();
514 ctx.layprops(0).position.x = x0;
515 ctx.layprops(0).position.y = y0;
516 auto res = ctx.modalDialog!false; // don't center
517 if (res < 0) return -1;
518 return ctx.listboxItemCurrent(lbi);