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-2021 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 
42 using std::unique_ptr;
43 
44 
45 namespace 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 
67 InputBox::~InputBox()
68 {
69  end();
70 }
71 
72 
73 void 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 
112 void 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 
120 void 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 
129 void 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 
146 InputResult 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 
164 InputResult 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 
179 InputResult 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 
197 int 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 
216 InputResult 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 
235 int 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 
251 int 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 
267 InputResult 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 
281 InputResult 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 
295 InputResult 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 
319 void 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 
452 void 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 
461 void InputForm::defaultEscapeHandler(uiKeyEventInfo const & key)
462 {
463  if (key.VK == VK_ESCAPE) {
464  retval = InputResult::Cancel;
465  finalize();
466  }
467 }
468 
469 
470 void 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 
486 void 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 
495 void 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 
516 void 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 
532 void MessageForm::calcRequiredSize()
533 {
534  messageExtent = app->canvas()->textExtent(font, messageText);
535  requiredWidth = imax(requiredWidth, messageExtent + 20);
536  requiredHeight += font->height;
537 }
538 
539 
540 void 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 
551 void MessageForm::finalize()
552 {
553  doExit(0);
554 }
555 
556 
557 
559 // SelectForm
560 
561 
562 void 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 
582 void 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 
622 void 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 
636 int 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 
665 void ProgressForm::calcRequiredSize()
666 {
667  requiredWidth = imax(requiredWidth, width);
668  requiredHeight += font->height + (hasProgressBar ? progressBarHeight : 0);
669 }
670 
671 
672 void 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 
688 void 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
698 bool 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 
725 void FileBrowserForm::calcRequiredSize()
726 {
727  requiredWidth = imax(requiredWidth, BROWSER_WIDTH + CTRLS_DIST + SIDE_BUTTONS_WIDTH);
728  requiredHeight = imax(requiredHeight, BROWSER_HEIGHT);
729 }
730 
731 
732 void 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 
809 void FileBrowserForm::finalize()
810 {
811  doExit(0);
812 }
813 
814 
815 void 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 
829 void 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 
881 void 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 
890 void 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 
929 void 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 a 24 bit RGB color.
A frame is a window with a title bar, maximize/minimize/close buttons and that is resizeable or movea...
Definition: fabui.h:823
Contains details about the key event.
Definition: fabui.h:156
Represents the VGA 2 colors bitmapped controller.
void enableVirtualKeys(bool generateVirtualKeys, bool createVKQueue)
Dynamically enables or disables Virtual Keys generation.
Definition: keyboard.cpp:100
This file contains fabgl::VGA16Controller definition.
Represents the whole application base class.
Definition: fabui.h:3030
InputBox(uiApp *app=nullptr)
Creates a new InputBox instance.
Definition: inputbox.cpp:57
This file contains the InputBox class.
VirtualKey VK
Definition: fabui.h:157
This file contains fabgl::VGA4Controller definition.
Represents the VGA 4 colors bitmapped controller.
void end()
Cleanup resources and eventually disable VGA output.
Definition: inputbox.cpp:102
Shows a list of selectable string items.
Definition: fabui.h:2064
This file contains fabgl::Keyboard definition.
Delegate onClick
Mouse click event delegate.
Definition: fabui.h:1337
int showModalWindow(uiWindow *window)
Makes a window visible and handles it has a modal window.
Definition: fabui.cpp:903
Represents the base abstract class for bitmapped display controllers.
A panel is used to contain and to group some controls.
Definition: fabui.h:1738
void destroyWindow(uiWindow *window)
Destroys a window.
Definition: fabui.cpp:1042
Represents a button control. A button can have text and optionally a bitmap.
Definition: fabui.h:1260
This is a combination of a button and a simple menu.
Definition: fabui.h:2903
uiFrameProps & frameProps()
Sets or gets frame properties.
Definition: fabui.h:880
This file contains fabgl::VGA2Controller definition.
uiFrameStyle & frameStyle()
Sets or gets frame style.
Definition: fabui.h:873
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
InputResult
Result of InputBox dialogs helper class.
Definition: inputbox.h:56
Definition: canvas.cpp:36
Represents the VGA 16 colors bitmapped controller.
This file contains fabgl::VGA8Controller definition.
void setupButton(int index, char const *text, char const *subItems=nullptr, int subItemsHeight=80)
Setups extended button or split-button.
Definition: inputbox.cpp:112
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
Represents a text edit control.
Definition: fabui.h:1421
Delegate< int > onSelect
Item select event.
Definition: fabui.h:2942
static Keyboard * keyboard()
Returns the instance of Keyboard object automatically created by PS2Controller.
A label is a static text UI element.
Definition: fabui.h:1563
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
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
A progress bar shows progress percentage using a colored bar.
Definition: fabui.h:2785
#define VESA_640x480_75Hz
Definition: fabglconf.h:252
uiFrame * rootWindow()
Gets a pointer to the root window.
Definition: fabui.h:3110
RGB888 backgroundColor
Definition: fabui.h:738
Shows and navigates Virtual Filesystem content.
Definition: fabui.h:2108
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
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, just like a menu.
Definition: inputbox.cpp:235
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
uint8_t width
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
uint8_t resizeable
Definition: fabui.h:773
Represents the VGA 8 colors bitmapped controller.
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.