FabGL
ESP32 Display Controller and Graphics Library
inputbox.cpp
1/*
2 Created by Fabrizio Di Vittorio (fdivitto2013@gmail.com) - <http://www.fabgl.com>
3 Copyright (c) 2019-2022 Fabrizio Di Vittorio.
4 All rights reserved.
5
6
7* Please contact fdivitto2013@gmail.com if you need a commercial license.
8
9
10* This library and related software is available under GPL v3.
11
12 FabGL is free software: you can redistribute it and/or modify
13 it under the terms of the GNU General Public License as published by
14 the Free Software Foundation, either version 3 of the License, or
15 (at your option) any later version.
16
17 FabGL is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
21
22 You should have received a copy of the GNU General Public License
23 along with FabGL. If not, see <http://www.gnu.org/licenses/>.
24 */
25
26
27#include <string.h>
28#include <memory>
29
34
35#include "devdrivers/keyboard.h"
36#include "inputbox.h"
37
38
39#pragma GCC optimize ("O2")
40
41
42using std::unique_ptr;
43
44
45namespace fabgl {
46
47
48// well known InputForm::buttonText[] indexes
49#define B_CANCEL ((int)(InputResult::Cancel) - 1)
50#define B_OK ((int)(InputResult::Enter) - 1)
51
52
53
55// InputBox
56
58 : m_vgaCtrl(nullptr),
59 m_backgroundColor(RGB888(64, 64, 64)),
60 m_existingApp(app),
61 m_autoOK(0),
62 m_minButtonsWidth(40)
63{
64}
65
66
67InputBox::~InputBox()
68{
69 end();
70}
71
72
73void InputBox::begin(char const * modeline, int viewPortWidth, int viewPortHeight, int displayColors)
74{
75 // setup display controller
76 if (displayColors <= 2)
77 m_vgaCtrl = new VGA2Controller;
78 else if (displayColors <= 4)
79 m_vgaCtrl = new VGA4Controller;
80 else if (displayColors <= 8)
81 m_vgaCtrl = new VGA8Controller;
82 else
83 m_vgaCtrl = new VGA16Controller;
84 m_dispCtrl = m_vgaCtrl;
85 m_vgaCtrl->begin();
86 m_vgaCtrl->setResolution(modeline ? modeline : VESA_640x480_75Hz, viewPortWidth, viewPortHeight);
87
88 // setup keyboard and mouse
89 if (!PS2Controller::initialized())
91 else
93}
94
95
97{
98 m_dispCtrl = displayController;
99}
100
101
103{
104 if (m_vgaCtrl) {
105 m_vgaCtrl->end();
106 delete m_vgaCtrl;
107 m_vgaCtrl = nullptr;
108 }
109}
110
111
112void InputBox::setupButton(int index, char const * text, char const * subItems, int subItemsHeight)
113{
114 m_buttonText[index] = text;
115 m_buttonSubItems[index] = subItems;
116 m_buttonSubItemsHeight[index] = subItemsHeight;
117}
118
119
120void InputBox::resetButtons()
121{
122 for (int i = 0; i < InputForm::BUTTONS; ++i) {
123 m_buttonText[i] = nullptr;
124 m_buttonSubItems[i] = nullptr;
125 }
126}
127
128
129void InputBox::exec(InputForm * form)
130{
131 if (m_existingApp) {
132 form->init(m_existingApp, true);
133 m_existingApp->showModalWindow(form->mainFrame);
134 m_existingApp->destroyWindow(form->mainFrame);
135 } else {
136 // run in standalone mode
137 InputApp app(form);
138 app.run(m_dispCtrl);
139 }
140 resetButtons();
141 m_buttonSubItem = form->buttonSubItem;
142 m_lastResult = form->retval;
143}
144
145
146InputResult InputBox::textInput(char const * titleText, char const * labelText, char * inOutString, int maxLength, char const * buttonCancelText, char const * buttonOKText, bool passwordMode)
147{
148 setupButton(B_CANCEL, buttonCancelText);
149 setupButton(B_OK, buttonOKText);
150
151 TextInputForm form(this);
152 form.titleText = titleText;
153 form.labelText = labelText;
154 form.inOutString = inOutString;
155 form.maxLength = maxLength;
156 form.passwordMode = passwordMode;
157 form.autoOK = m_autoOK;
158
159 exec(&form);
160 return form.retval;
161}
162
163
164InputResult InputBox::message(char const * titleText, char const * messageText, char const * buttonCancelText, char const * buttonOKText)
165{
166 setupButton(B_CANCEL, buttonCancelText);
167 setupButton(B_OK, buttonOKText);
168
169 MessageForm form(this);
170 form.titleText = titleText;
171 form.messageText = messageText;
172 form.autoOK = m_autoOK;
173
174 exec(&form);
175 return form.retval;
176}
177
178
179InputResult InputBox::messageFmt(char const * titleText, char const * buttonCancelText, char const * buttonOKText, const char * format, ...)
180{
181 auto r = InputResult::Cancel;
182 va_list ap;
183 va_start(ap, format);
184 int size = vsnprintf(nullptr, 0, format, ap) + 1;
185 if (size > 0) {
186 va_end(ap);
187 va_start(ap, format);
188 char buf[size + 1];
189 vsnprintf(buf, size, format, ap);
190 r = message(titleText, buf, buttonCancelText, buttonOKText);
191 }
192 va_end(ap);
193 return r;
194}
195
196
197int InputBox::select(char const * titleText, char const * messageText, char const * itemsText, char separator, char const * buttonCancelText, char const * buttonOKText)
198{
199 setupButton(B_CANCEL, buttonCancelText);
200 setupButton(B_OK, buttonOKText);
201
202 SelectForm form(this);
203 form.titleText = titleText;
204 form.messageText = messageText;
205 form.items = itemsText;
206 form.separator = separator;
207 form.itemsList = nullptr;
208 form.menuMode = false;
209 form.autoOK = m_autoOK;
210
211 exec(&form);
212 return form.outSelected;
213}
214
215
216InputResult InputBox::select(char const * titleText, char const * messageText, StringList * items, char const * buttonCancelText, char const * buttonOKText)
217{
218 setupButton(B_CANCEL, buttonCancelText);
219 setupButton(B_OK, buttonOKText);
220
221 SelectForm form(this);
222 form.titleText = titleText;
223 form.messageText = messageText;
224 form.items = nullptr;
225 form.separator = 0;
226 form.itemsList = items;
227 form.menuMode = false;
228 form.autoOK = m_autoOK;
229
230 exec(&form);
231 return form.retval;
232}
233
234
235int InputBox::menu(char const * titleText, char const * messageText, char const * itemsText, char separator)
236{
237 SelectForm form(this);
238 form.titleText = titleText;
239 form.messageText = messageText;
240 form.items = itemsText;
241 form.separator = separator;
242 form.itemsList = nullptr;
243 form.menuMode = true;
244 form.autoOK = 0; // no timeout supported here
245
246 exec(&form);
247 return form.outSelected;
248}
249
250
251int InputBox::menu(char const * titleText, char const * messageText, StringList * items)
252{
253 SelectForm form(this);
254 form.titleText = titleText;
255 form.messageText = messageText;
256 form.items = nullptr;
257 form.separator = 0;
258 form.itemsList = items;
259 form.menuMode = true;
260 form.autoOK = 0; // no timeout supported here
261
262 exec(&form);
263 return items->getFirstSelected();
264}
265
266
267InputResult InputBox::progressBoxImpl(ProgressForm & form, char const * titleText, char const * buttonCancelText, bool hasProgressBar, int width)
268{
269 setupButton(B_CANCEL, buttonCancelText);
270
271 form.titleText = titleText;
272 form.hasProgressBar = hasProgressBar;
273 form.width = width;
274 form.autoOK = 0; // no timeout supported here
275
276 exec(&form);
277 return form.retval;
278}
279
280
281InputResult InputBox::folderBrowser(char const * titleText, char const * directory, char const * buttonOKText)
282{
283 setupButton(B_OK, buttonOKText);
284
285 FileBrowserForm form(this);
286 form.titleText = titleText;
287 form.autoOK = 0; // no timeout supported here
288 form.directory = directory;
289
290 exec(&form);
291 return form.retval;
292}
293
294
295InputResult InputBox::fileSelector(char const * titleText, char const * messageText, char * inOutDirectory, int maxDirectoryLength, char * inOutFilename, int maxFilenameLength, char const * buttonCancelText, char const * buttonOKText)
296{
297 setupButton(B_CANCEL, buttonCancelText);
298 setupButton(B_OK, buttonOKText);
299
300 FileSelectorForm form(this);
301 form.titleText = titleText;
302 form.labelText = messageText;
303 form.inOutDirectory = inOutDirectory;
304 form.maxDirectoryLength = maxDirectoryLength;
305 form.inOutFilename = inOutFilename;
306 form.maxFilenameLength = maxFilenameLength;
307 form.autoOK = 0; // no timeout supported here
308
309 exec(&form);
310 return form.retval;
311}
312
313
314
316// InputForm
317
318
319void InputForm::init(uiApp * app_, bool modalDialog_)
320{
321 retval = InputResult::None;
322
323 app = app_;
324 modalDialog = modalDialog_;
325
326 if (!modalDialog) {
327 app->rootWindow()->frameStyle().backgroundColor = inputBox->backgroundColor();
328 app->rootWindow()->onPaint = [&]() {
329 inputBox->onPaint(app->canvas());
330 };
331 }
332
333 font = &FONT_std_14;
334
335 const int titleHeight = titleText && strlen(titleText) ? font->height : 0;
336
337 constexpr int buttonsSpace = 10;
338
339 int buttonsWidth = inputBox->minButtonsWidth();
340 int totButtons = 0;
341
342 for (int i = 0; i < BUTTONS; ++i) {
343 auto btext = inputBox->buttonText(i);
344 if (btext) {
345 int buttonExtent = app->canvas()->textExtent(font, btext) + 10;
346 buttonsWidth = imax(buttonsWidth, buttonExtent);
347 ++totButtons;
348 }
349 }
350
351 const int buttonsHeight = totButtons ? font->height + 6 : 0;
352
353 requiredWidth = buttonsWidth * totButtons + (2 * buttonsSpace) * totButtons;
354 requiredHeight = buttonsHeight + titleHeight + font->height * 2 + 5;
355
356 calcRequiredSize();
357
358 requiredWidth = imin(requiredWidth, app->canvas()->getWidth());
359
360 controlToFocus = nullptr;
361
362 mainFrame = new uiFrame(app->rootWindow(), titleText, UIWINDOW_PARENTCENTER, Size(requiredWidth, requiredHeight), false);
363 mainFrame->frameProps().resizeable = false;
364 mainFrame->frameProps().hasMaximizeButton = false;
365 mainFrame->frameProps().hasMinimizeButton = false;
366 mainFrame->frameProps().hasCloseButton = false;
367 mainFrame->onShow = [&]() {
368 if (controlToFocus)
369 app->setFocusedWindow(controlToFocus);
370 show();
371 };
372
373 autoOKLabel = nullptr;
374
375 if (totButtons) {
376
377 // setup panel (where buttons are positioned)
378
379 int panelHeight = buttonsHeight + 10;
380 panel = new uiPanel(mainFrame, Point(mainFrame->clientPos().X - 1, mainFrame->clientPos().Y + mainFrame->clientSize().height - panelHeight), Size(mainFrame->clientSize().width + 2, panelHeight));
381 panel->windowStyle().borderColor = RGB888(128, 128, 128);
382 panel->panelStyle().backgroundColor = mainFrame->frameStyle().backgroundColor;
383 panel->anchors().top = false;
384 panel->anchors().bottom = true;
385 panel->anchors().right = true;
386
387 // setup buttons
388
389 int y = (panelHeight - buttonsHeight) / 2;
390 int x = panel->clientSize().width - buttonsWidth * totButtons - buttonsSpace * (totButtons - 1) - buttonsSpace / 2; // right aligned
391
392 for (int i = 0; i < BUTTONS; ++i)
393 if (inputBox->buttonText(i)) {
394 uiWindow * ctrl;
395 if (inputBox->buttonSubItems(i)) {
396 auto splitButton = new uiSplitButton(panel, inputBox->buttonText(i), Point(x, y), Size(buttonsWidth, buttonsHeight), inputBox->buttonsSubItemsHeight(i), inputBox->buttonSubItems(i));
397 splitButton->onSelect = [&, i](int idx) {
398 buttonSubItem = idx;
399 retval = (InputResult)(i + 1);
400 finalize();
401 };
402 ctrl = splitButton;
403 } else {
404 auto button = new uiButton(panel, inputBox->buttonText(i), Point(x, y), Size(buttonsWidth, buttonsHeight));
405 button->onClick = [&, i]() {
406 retval = (InputResult)(i + 1);
407 finalize();
408 };
409 ctrl = button;
410 }
411 ctrl->anchors().left = false;
412 ctrl->anchors().right = true;
413 x += buttonsWidth + buttonsSpace;
414 controlToFocus = ctrl;
415 }
416
417 if (autoOK > 0) {
418 autoOKLabel = new uiLabel(panel, "", Point(4, y + 2));
419
420 mainFrame->onTimer = [&](uiTimerHandle t) {
421 int now = esp_timer_get_time() / 1000;
422 if (app->lastUserActionTime() + 900 > now) {
423 app->killTimer(t);
424 app->destroyWindow(autoOKLabel);
425 return;
426 }
427 if (autoOK <= 0) {
428 app->killTimer(t);
429 retval = InputResult::Enter;
430 finalize();
431 }
432 --autoOK;
433 autoOKLabel->setTextFmt("%d", autoOK);
434 };
435 app->setTimer(mainFrame, 1000);
436
437 }
438
439 } else {
440 panel = nullptr;
441 }
442
443 addControls();
444
445 if (!modalDialog) {
446 app->showWindow(mainFrame, true);
447 app->setActiveWindow(mainFrame);
448 }
449}
450
451
452void InputForm::defaultEnterHandler(uiKeyEventInfo const & key)
453{
454 if (key.VK == VK_RETURN || key.VK == VK_KP_ENTER) {
455 retval = InputResult::Enter;
456 finalize();
457 }
458}
459
460
461void InputForm::defaultEscapeHandler(uiKeyEventInfo const & key)
462{
463 if (key.VK == VK_ESCAPE) {
464 retval = InputResult::Cancel;
465 finalize();
466 }
467}
468
469
470void InputForm::doExit(int value)
471{
472 if (modalDialog)
473 mainFrame->exitModal(value);
474 else {
475 app->quit(value);
476 // this avoids flickering of content painted in onPaint
477 app->rootWindow()->frameProps().fillBackground = false;
478 }
479}
480
481
483// TextInputForm
484
485
486void TextInputForm::calcRequiredSize()
487{
488 labelExtent = app->canvas()->textExtent(font, labelText);
489 editExtent = imin(maxLength * app->canvas()->textExtent(font, "M") + 15, app->rootWindow()->clientSize().width - labelExtent);
490 requiredWidth = imax(requiredWidth, editExtent + labelExtent + 10);
491 requiredHeight += font->height;
492}
493
494
495void TextInputForm::addControls()
496{
497 mainFrame->frameProps().resizeable = true;
498 mainFrame->frameProps().hasMaximizeButton = true;
499
500 const Point clientPos = mainFrame->clientPos();
501
502 int x = clientPos.X + 4;
503 int y = clientPos.Y + 8;
504
505 new uiLabel(mainFrame, labelText, Point(x, y));
506
507 edit = new uiTextEdit(mainFrame, inOutString, Point(x + labelExtent + 5, y - 4), Size(editExtent - 15, font->height + 6));
508 edit->anchors().right = true;
509 edit->textEditProps().passwordMode = passwordMode;
510 edit->onKeyType = [&](uiKeyEventInfo const & key) { defaultEnterHandler(key); defaultEscapeHandler(key); };
511
512 controlToFocus = edit;
513}
514
515
516void TextInputForm::finalize()
517{
518 if (retval == InputResult::Enter) {
519 int len = imin(maxLength, strlen(edit->text()));
520 memcpy(inOutString, edit->text(), len);
521 inOutString[len] = 0;
522 }
523 doExit(0);
524}
525
526
527
529// MessageForm
530
531
532void MessageForm::calcRequiredSize()
533{
534 messageExtent = app->canvas()->textExtent(font, messageText);
535 requiredWidth = imax(requiredWidth, messageExtent + 20);
536 requiredHeight += font->height;
537}
538
539
540void MessageForm::addControls()
541{
542 int x = mainFrame->clientPos().X + (mainFrame->clientSize().width - messageExtent) / 2;
543 int y = mainFrame->clientPos().Y + 6;
544
545 new uiLabel(mainFrame, messageText, Point(x, y));
546
547 mainFrame->onKeyUp = [&](uiKeyEventInfo const & key) { defaultEnterHandler(key); defaultEscapeHandler(key); };
548}
549
550
551void MessageForm::finalize()
552{
553 doExit(0);
554}
555
556
557
559// SelectForm
560
561
562void SelectForm::calcRequiredSize()
563{
564 auto messageExtent = app->canvas()->textExtent(font, messageText);
565 requiredWidth = imax(requiredWidth, messageExtent + 20);
566
567 // calc space for message
568 requiredHeight += font->height;
569
570 // calc space for list box
571 size_t maxLength;
572 auto itemsCount = countItems(&maxLength);
573 listBoxHeight = 16 * itemsCount + 2;
574 int requiredHeightUnCut = requiredHeight + listBoxHeight;
575 requiredHeight = imin(requiredHeightUnCut, app->canvas()->getHeight());
576 requiredWidth = imax(requiredWidth, maxLength * app->canvas()->textExtent(font, "M"));
577 if (requiredHeightUnCut > requiredHeight)
578 listBoxHeight -= requiredHeightUnCut - requiredHeight;
579}
580
581
582void SelectForm::addControls()
583{
584 mainFrame->frameProps().resizeable = true;
585 mainFrame->frameProps().hasMaximizeButton = true;
586
587 int x = mainFrame->clientPos().X + 4;
588 int y = mainFrame->clientPos().Y + 6;
589
590 new uiLabel(mainFrame, messageText, Point(x, y));
591
592 y += font->height + 6;
593
594 listBox = new uiListBox(mainFrame, Point(x, y), Size(mainFrame->clientSize().width - 10, listBoxHeight));
595 listBox->anchors().right = true;
596 listBox->anchors().bottom = true;
597 if (items) {
598 listBox->items().appendSepList(items, separator);
599 } else {
600 listBox->items().copyFrom(*itemsList);
601 listBox->items().copySelectionMapFrom(*itemsList);
602 }
603 if (menuMode) {
604 listBox->listBoxProps().allowMultiSelect = false;
605 listBox->listBoxProps().selectOnMouseOver = true;
606 listBox->onClick = [&]() {
607 retval = InputResult::Enter;
608 finalize();
609 };
610 } else {
611 listBox->onDblClick = [&]() {
612 retval = InputResult::Enter;
613 finalize();
614 };
615 }
616 listBox->onKeyType = [&](uiKeyEventInfo const & key) { defaultEnterHandler(key); defaultEscapeHandler(key); };
617
618 controlToFocus = listBox;
619}
620
621
622void SelectForm::finalize()
623{
624 if (items) {
625 outSelected = (retval == InputResult::Enter ? listBox->firstSelectedItem() : -1);
626 } else {
627 if (retval == InputResult::Cancel)
628 itemsList->deselectAll();
629 else
630 itemsList->copySelectionMapFrom(listBox->items());
631 }
632 doExit(0);
633}
634
635
636int SelectForm::countItems(size_t * maxLength)
637{
638 *maxLength = 0;
639 int count = 0;
640 if (items) {
641 char const * start = items;
642 while (*start) {
643 auto end = strchr(start, separator);
644 if (!end)
645 end = strchr(start, 0);
646 int len = end - start;
647 *maxLength = imax(*maxLength, len);
648 start += len + (*end == 0 ? 0 : 1);
649 ++count;
650 }
651 } else if (itemsList) {
652 for (int i = 0; i < itemsList->count(); ++i)
653 *maxLength = imax(*maxLength, strlen(itemsList->get(i)));
654 count += itemsList->count();
655 }
656 return count;
657}
658
659
660
662// ProgressForm
663
664
665void ProgressForm::calcRequiredSize()
666{
667 requiredWidth = imax(requiredWidth, width);
668 requiredHeight += font->height + (hasProgressBar ? progressBarHeight : 0);
669}
670
671
672void ProgressForm::addControls()
673{
674 int x = mainFrame->clientPos().X + 4;
675 int y = mainFrame->clientPos().Y + 6;
676
677 label = new uiLabel(mainFrame, "", Point(x, y));
678
679 if (hasProgressBar) {
680 y += font->height + 4;
681 progressBar = new uiProgressBar(mainFrame, Point(x, y), Size(mainFrame->clientSize().width - 8, font->height));
682 }
683
684 mainFrame->onKeyUp = [&](uiKeyEventInfo const & key) { defaultEscapeHandler(key); };
685}
686
687
688void ProgressForm::show()
689{
690 execFunc(this);
691 if (retval != InputResult::Cancel)
692 retval = InputResult::Enter;
693 doExit(0);
694}
695
696
697// return True if not Abort
698bool ProgressForm::update(int percentage, char const * format, ...)
699{
700 if (hasProgressBar)
701 progressBar->setPercentage(percentage);
702
703 va_list ap;
704 va_start(ap, format);
705 int size = vsnprintf(nullptr, 0, format, ap) + 1;
706 if (size > 0) {
707 va_end(ap);
708 va_start(ap, format);
709 char buf[size + 1];
710 vsnprintf(buf, size, format, ap);
711 label->setText(buf);
712 }
713 va_end(ap);
714
715 app->processEvents();
716 return retval == InputResult::None;
717}
718
719
720
722// FileBrowserForm
723
724
725void FileBrowserForm::calcRequiredSize()
726{
727 requiredWidth = imax(requiredWidth, BROWSER_WIDTH + CTRLS_DIST + SIDE_BUTTONS_WIDTH);
728 requiredHeight = imax(requiredHeight, BROWSER_HEIGHT);
729}
730
731
732void FileBrowserForm::addControls()
733{
734 mainFrame->frameProps().resizeable = true;
735 mainFrame->frameProps().hasMaximizeButton = true;
736
737 mainFrame->onKeyUp = [&](uiKeyEventInfo const & key) { defaultEscapeHandler(key); };
738
739 int x = mainFrame->clientPos().X + CTRLS_DIST;
740 int y = mainFrame->clientPos().Y + CTRLS_DIST;
741
742 fileBrowser = new uiFileBrowser(mainFrame, Point(x, y), Size(mainFrame->clientSize().width - x - CTRLS_DIST - SIDE_BUTTONS_WIDTH, mainFrame->clientSize().height - panel->size().height - CTRLS_DIST * 2));
743 fileBrowser->anchors().right = true;
744 fileBrowser->anchors().bottom = true;
745 fileBrowser->setDirectory(directory);
746
747 x += fileBrowser->size().width + CTRLS_DIST;
748
749 newFolderButton = new uiButton(mainFrame, "New Folder", Point(x, y), Size(SIDE_BUTTONS_WIDTH, SIDE_BUTTONS_HEIGHT));
750 newFolderButton->anchors().left = false;
751 newFolderButton->anchors().right = true;
752 newFolderButton->onClick = [&]() {
753 unique_ptr<char[]> dirname(new char[MAXNAME + 1] { 0 } );
754 if (app->inputBox("Create Folder", "Name", dirname.get(), MAXNAME, "Create", "Cancel") == uiMessageBoxResult::Button1) {
755 fileBrowser->content().makeDirectory(dirname.get());
756 fileBrowser->update();
757 }
758 };
759
760 y += SIDE_BUTTONS_HEIGHT + CTRLS_DIST;
761
762 renameButton = new uiButton(mainFrame, "Rename", Point(x, y), Size(SIDE_BUTTONS_WIDTH, SIDE_BUTTONS_HEIGHT));
763 renameButton->anchors().left = false;
764 renameButton->anchors().right = true;
765 renameButton->onClick = [&]() {
766 if (strcmp(fileBrowser->filename(), "..") != 0) {
767 int maxlen = fabgl::imax(MAXNAME, strlen(fileBrowser->filename()));
768 unique_ptr<char[]> filename(new char[MAXNAME + 1] { 0 } );
769 strcpy(filename.get(), fileBrowser->filename());
770 if (app->inputBox("Rename File", "New name", filename.get(), maxlen, "Rename", "Cancel") == uiMessageBoxResult::Button1) {
771 fileBrowser->content().rename(fileBrowser->filename(), filename.get());
772 fileBrowser->update();
773 }
774 }
775 };
776
777 y += SIDE_BUTTONS_HEIGHT + CTRLS_DIST;
778
779 deleteButton = new uiButton(mainFrame, "Delete", Point(x, y), Size(SIDE_BUTTONS_WIDTH, SIDE_BUTTONS_HEIGHT));
780 deleteButton->anchors().left = false;
781 deleteButton->anchors().right = true;
782 deleteButton->onClick = [&]() {
783 if (strcmp(fileBrowser->filename(), "..") != 0) {
784 if (app->messageBox("Delete file/directory", "Are you sure?", "Yes", "Cancel") == uiMessageBoxResult::Button1) {
785 fileBrowser->content().remove( fileBrowser->filename() );
786 fileBrowser->update();
787 }
788 }
789 };
790
791 y += SIDE_BUTTONS_HEIGHT + CTRLS_DIST;
792
793 copyButton = new uiButton(mainFrame, "Copy", Point(x, y), Size(SIDE_BUTTONS_WIDTH, SIDE_BUTTONS_HEIGHT));
794 copyButton->anchors().left = false;
795 copyButton->anchors().right = true;
796 copyButton->onClick = [&]() { doCopy(); };
797
798 y += SIDE_BUTTONS_HEIGHT + CTRLS_DIST;
799
800 pasteButton = new uiButton(mainFrame, "Paste", Point(x, y), Size(SIDE_BUTTONS_WIDTH, SIDE_BUTTONS_HEIGHT));
801 pasteButton->anchors().left = false;
802 pasteButton->anchors().right = true;
803 pasteButton->onClick = [&]() { doPaste(); };
804 app->showWindow(pasteButton, false);
805
806}
807
808
809void FileBrowserForm::finalize()
810{
811 doExit(0);
812}
813
814
815void FileBrowserForm::doCopy()
816{
817 if (!fileBrowser->isDirectory()) {
818 if (srcDirectory)
819 free(srcDirectory);
820 if (srcFilename)
821 free(srcFilename);
822 srcDirectory = strdup(fileBrowser->directory());
823 srcFilename = strdup(fileBrowser->filename());
824 app->showWindow(pasteButton, true);
825 }
826}
827
828
829void FileBrowserForm::doPaste()
830{
831 if (strcmp(srcDirectory, fileBrowser->content().directory()) == 0) {
832 app->messageBox("", "Please select a different folder", "OK", nullptr, nullptr, uiMessageBoxIcon::Error);
833 return;
834 }
835 FileBrowser fb_src(srcDirectory);
836 auto fileSize = fb_src.fileSize(srcFilename);
837 auto src = fb_src.openFile(srcFilename, "rb");
838 if (!src) {
839 app->messageBox("", "Unable to find source file", "OK", nullptr, nullptr, uiMessageBoxIcon::Error);
840 return;
841 }
842 if (fileBrowser->content().exists(srcFilename, false)) {
843 if (app->messageBox("", "Overwrite file?", "Yes", "No", nullptr, uiMessageBoxIcon::Question) != uiMessageBoxResult::ButtonOK)
844 return;
845 }
846 auto dst = fileBrowser->content().openFile(srcFilename, "wb");
847
848 auto bytesToCopy = fileSize;
849
850 InputBox ib(app);
851 ib.progressBox("Copying", "Abort", true, app->canvas()->getWidth() * 2 / 3, [&](fabgl::ProgressForm * form) {
852 constexpr int BUFLEN = 4096;
853 unique_ptr<uint8_t[]> buf(new uint8_t[BUFLEN]);
854 while (bytesToCopy > 0) {
855 auto r = fread(buf.get(), 1, imin(BUFLEN, bytesToCopy), src);
856 fwrite(buf.get(), 1, r, dst);
857 bytesToCopy -= r;
858 if (r == 0)
859 break;
860 if (!form->update((int)((double)(fileSize - bytesToCopy) / fileSize * 100), "Writing %s (%d / %d bytes)", srcFilename, (fileSize - bytesToCopy), fileSize))
861 break;
862 }
863 });
864
865 fclose(dst);
866 fclose(src);
867 if (bytesToCopy > 0) {
868 fileBrowser->content().remove(srcFilename);
869 app->messageBox("", "File not copied", "OK", nullptr, nullptr, uiMessageBoxIcon::Error);
870 }
871 fileBrowser->update();
872}
873
874
875
876
878// FileSelectorForm
879
880
881void FileSelectorForm::calcRequiredSize()
882{
883 labelExtent = app->canvas()->textExtent(font, labelText);
884 editExtent = imin(maxFilenameLength * app->canvas()->textExtent(font, "M") + 15, app->rootWindow()->clientSize().width - labelExtent);
885 requiredWidth = imax(requiredWidth, imax(BROWSER_WIDTH, labelExtent + CTRLS_DIST + MINIMUM_EDIT_WIDTH) + CTRLS_DIST);
886 requiredHeight += font->height + CTRLS_DIST + BROWSER_HEIGHT;
887}
888
889
890void FileSelectorForm::addControls()
891{
892 mainFrame->frameProps().resizeable = true;
893 mainFrame->frameProps().hasMaximizeButton = true;
894
895 mainFrame->onKeyUp = [&](uiKeyEventInfo const & key) { defaultEscapeHandler(key); };
896
897 int x = mainFrame->clientPos().X + CTRLS_DIST;
898 int y = mainFrame->clientPos().Y + CTRLS_DIST;
899
900 new uiLabel(mainFrame, labelText, Point(x, y + 4));
901
902 edit = new uiTextEdit(mainFrame, inOutFilename, Point(x + labelExtent + CTRLS_DIST, y), Size(mainFrame->clientSize().width - labelExtent - x - CTRLS_DIST - 1, font->height + 6));
903 edit->anchors().right = true;
904
905 y += edit->size().height + CTRLS_DIST;
906
907 fileBrowser = new uiFileBrowser(mainFrame, Point(x, y), Size(mainFrame->clientSize().width - x - 1, mainFrame->clientSize().height - panel->size().height - y + CTRLS_DIST * 2 ));
908 fileBrowser->anchors().right = true;
909 fileBrowser->anchors().bottom = true;
910 fileBrowser->setDirectory(inOutDirectory);
911 fileBrowser->onChange = [&]() {
912 if (!fileBrowser->isDirectory()) {
913 edit->setText(fileBrowser->filename());
914 edit->repaint();
915 }
916 };
917 fileBrowser->onDblClick = [&]() {
918 if (!fileBrowser->isDirectory()) {
919 retval = InputResult::Enter;
920 finalize();
921 }
922 };
923 fileBrowser->onKeyType = [&](uiKeyEventInfo const & key) { defaultEnterHandler(key); defaultEscapeHandler(key); };
924
925 controlToFocus = edit;
926}
927
928
929void FileSelectorForm::finalize()
930{
931 if (retval == InputResult::Enter) {
932 // filename
933 int len = imin(maxFilenameLength, strlen(edit->text()));
934 memcpy(inOutFilename, edit->text(), len);
935 inOutFilename[len] = 0;
936 // directory
937 len = imin(maxDirectoryLength, strlen(fileBrowser->directory()));
938 memcpy(inOutDirectory, fileBrowser->directory(), len);
939 inOutDirectory[len] = 0;
940 }
941 doExit(0);
942}
943
944
945
946} // namespace fabgl
Represents the base abstract class for bitmapped display controllers.
InputResult textInput(char const *titleText, char const *labelText, char *inOutString, int maxLength, char const *buttonCancelText="Cancel", char const *buttonOKText="OK", bool passwordMode=false)
Shows a dialog with a label and a text edit box.
Definition: inputbox.cpp:146
InputResult message(char const *titleText, char const *messageText, char const *buttonCancelText=nullptr, char const *buttonOKText="OK")
Shows a dialog with just a label.
Definition: inputbox.cpp:164
void setupButton(int index, char const *text, char const *subItems=nullptr, int subItemsHeight=80)
Setups extended button or split-button.
Definition: inputbox.cpp:112
InputResult fileSelector(char const *titleText, char const *messageText, char *inOutDirectory, int maxDirectoryLength, char *inOutFilename, int maxFilenameLength, char const *buttonCancelText="Cancel", char const *buttonOKText="OK")
Selects a file and directory starting from the specified path.
Definition: inputbox.cpp:295
int menu(char const *titleText, char const *messageText, char const *itemsText, char separator=';')
Shows a dialog with a label and a list box. The dialog exits when an item is selected,...
Definition: inputbox.cpp:235
InputResult messageFmt(char const *titleText, char const *buttonCancelText, char const *buttonOKText, const char *format,...)
Shows a dialog with a just a label. Allows printf like formatted text.
Definition: inputbox.cpp:179
void end()
Cleanup resources and eventually disable VGA output.
Definition: inputbox.cpp:102
int select(char const *titleText, char const *messageText, char const *itemsText, char separator=';', char const *buttonCancelText="Cancel", char const *buttonOKText="OK")
Shows a dialog with a label and a list box.
Definition: inputbox.cpp:197
void begin(char const *modeline=nullptr, int viewPortWidth=-1, int viewPortHeight=-1, int displayColors=16)
Initializes InputBox from VGA modeline, using a VGA16Controller.
Definition: inputbox.cpp:73
InputBox(uiApp *app=nullptr)
Creates a new InputBox instance.
Definition: inputbox.cpp:57
InputResult folderBrowser(char const *titleText, char const *directory="/", char const *buttonOKText="Close")
Shows a dialog with files and folders and buttons to create new folders, delete and rename folders an...
Definition: inputbox.cpp:281
void enableVirtualKeys(bool generateVirtualKeys, bool createVKQueue)
Dynamically enables or disables Virtual Keys generation.
Definition: keyboard.cpp:100
static Keyboard * keyboard()
Returns the instance of Keyboard object automatically created by PS2Controller.
static void begin(gpio_num_t port0_clkGPIO, gpio_num_t port0_datGPIO, gpio_num_t port1_clkGPIO=GPIO_UNUSED, gpio_num_t port1_datGPIO=GPIO_UNUSED)
Initializes PS2 device controller.
Represents the VGA 16 colors bitmapped controller.
Represents the VGA 2 colors bitmapped controller.
Represents the VGA 4 colors bitmapped controller.
Represents the VGA 8 colors bitmapped controller.
void destroyWindow(uiWindow *window)
Destroys a window.
Definition: fabui.cpp:1042
int showModalWindow(uiWindow *window)
Makes a window visible and handles it has a modal window.
Definition: fabui.cpp:903
uiFrame * rootWindow()
Gets a pointer to the root window.
Definition: fabui.h:3185
Represents the whole application base class.
Definition: fabui.h:3105
Represents a button control. A button can have text and optionally a bitmap.
Definition: fabui.h:1257
Shows and navigates Virtual Filesystem content.
Definition: fabui.h:2183
uiFrameStyle & frameStyle()
Sets or gets frame style.
Definition: fabui.h:870
A frame is a window with a title bar, maximize/minimize/close buttons and that is resizeable or movea...
Definition: fabui.h:820
A label is a static text UI element.
Definition: fabui.h:1560
Shows a list of selectable string items.
Definition: fabui.h:2139
A panel is used to contain and to group some controls.
Definition: fabui.h:1813
A progress bar shows progress percentage using a colored bar.
Definition: fabui.h:2860
This is a combination of a button and a simple menu.
Definition: fabui.h:2979
Represents a text edit control.
Definition: fabui.h:1418
uint8_t width
#define VESA_640x480_75Hz
Definition: fabglconf.h:258
InputResult
Result of InputBox dialogs helper class.
Definition: inputbox.h:56
@ VK_RETURN
Definition: fabutils.h:1249
@ VK_ESCAPE
Definition: fabutils.h:1229
@ VK_KP_ENTER
Definition: fabutils.h:1250
This file contains the InputBox class.
This file contains fabgl::Keyboard definition.
Represents a 24 bit RGB color.
RGB888 backgroundColor
Definition: fabui.h:735
VirtualKey VK
Definition: fabui.h:158
Contains details about the key event.
Definition: fabui.h:157
This file contains fabgl::VGA16Controller definition.
This file contains fabgl::VGA2Controller definition.
This file contains fabgl::VGA4Controller definition.
This file contains fabgl::VGA8Controller definition.