JeVois  1.18
JeVois Smart Embedded Machine Vision Toolkit
Share this page:
GUIeditor.C
Go to the documentation of this file.
1 // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2 //
3 // JeVois Smart Embedded Machine Vision Toolkit - Copyright (C) 2022 by Laurent Itti, the University of Southern
4 // California (USC), and iLab at USC. See http://iLab.usc.edu and http://jevois.org for information about this project.
5 //
6 // This file is part of the JeVois Smart Embedded Machine Vision Toolkit. This program is free software; you can
7 // redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software
8 // Foundation, version 2. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
9 // without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
10 // License for more details. You should have received a copy of the GNU General Public License along with this program;
11 // if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
12 //
13 // Contact information: Laurent Itti - 3641 Watt Way, HNB-07A - Los Angeles, CA 90089-2520 - USA.
14 // Tel: +1 213 740 3527 - itti@pollux.usc.edu - http://iLab.usc.edu - http://jevois.org
15 // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
16 /*! \file */
17 
18 // This is only available on JeVoisPro
19 #ifdef JEVOIS_PRO
20 
21 #include <jevois/GPU/GUIeditor.H>
22 #include <jevois/GPU/GUIhelper.H>
23 #include <jevois/Core/Module.H>
24 #include <jevois/Core/Engine.H>
25 #include <imgui-filebrowser/imfilebrowser.h>
26 #include <fstream>
27 
28 #include <jevois/Debug/Log.H>
29 
30 // ##############################################################################################################
31 jevois::GUIeditor::GUIeditor(GUIhelper * helper, std::string const & imguiid,
32  std::vector<jevois::EditorItem> && fixeditems, std::string const & scanpath,
33  std::string const & prefix, std::set<std::string> && extensions) :
34  TextEditor(), itsHelper(helper), itsId(imguiid), itsItems(fixeditems), itsNumFixedItems(fixeditems.size()),
35  itsScanPath(scanpath), itsPrefix(prefix), itsExtensions(extensions),
36  itsBrowser(new ImGui::FileBrowser(ImGuiFileBrowserFlags_EnterNewFilename | ImGuiFileBrowserFlags_CreateNewDir))
37 {
38  TextEditor::SetSaveCallback([this]() { saveFile(); itsWantAction = true; } ); // to enable Ctrl-S saving
39 
40  itsBrowser->SetTitle("Select a file to open or create...");
41  itsBrowser->SetPwd(JEVOIS_SHARE_PATH);
42  //itsBrowser->SetTypeFilters({ ".cfg", ".py", ".txt", ".C", ".H" });
43 }
44 
45 // ##############################################################################################################
47 { }
48 
49 // ##############################################################################################################
51 {
52  // Remove all dynamic files, keep the fixed ones:
53  itsItems.resize(itsNumFixedItems);
54 
55  // Rescan recursively:
56  for (auto const & dent : std::filesystem::recursive_directory_iterator(itsScanPath))
57  {
58  if (dent.is_regular_file())
59  {
60  std::filesystem::path const path = dent.path();
61 
62  // Check that the extension is one we want:
63  if (itsExtensions.find(path.extension()) == itsExtensions.end()) continue;
64 
65  std::string const filepath = path.string();
66 
67  // Create an entry:
68  itsItems.emplace_back(EditorItem { jevois::absolutePath(itsScanPath, filepath), itsPrefix + filepath,
69  EditorSaveAction::Reload });
70  }
71  }
72 
73  // Add an entry for file browser, and one for file creation:
74  itsItems.emplace_back(EditorItem { "**", "Browse / Create file...", EditorSaveAction::Reload });
75 
76  // Update index of current file. We try to find it both in itsScanPath and in the current module's path:
77  auto m = itsHelper->engine()->module();
78  int i = 0; bool not_found = true;
79 
80  for (auto const & item : itsItems)
81  if (jevois::absolutePath(itsScanPath, item.filename) == itsFilename ||
82  (m && m->absolutePath(item.filename) == itsFilename))
83  {
84  itsCurrentItem = i;
85  not_found = false;
86  break;
87  }
88  else ++i;
89 
90  // If the currently open file is not in our list anymore, load file 0:
91  if (not_found)
92  {
93  itsNewItem = 0;
94  itsWantLoad = true;
95  // If we have some edits, we will ask to save, and then to possibly reload the module (or reboot, etc). Prevent
96  // asking to reload:
97  itsOverrideReloadModule = IsEdited();
98  }
99 }
100 
101 // ##############################################################################################################
103 {
104  // Create combo entries for imgui:
105  char const * items[itsItems.size()];
106  for (int i = 0; EditorItem const & c : itsItems) items[i++] = c.displayname.c_str();
107 
108  // Check if the user is trying to select a different file:
109  if (ImGui::Combo(("##"+itsId+"editorcombo").c_str(), &itsNewItem, items, itsItems.size())) itsWantLoad = true;
110 
111  // Want to load a new file? check if we need to save the current one first:
112  if (itsWantLoad && itsWantAction == false)
113  {
114  if (IsEdited())
115  {
116  static int discard_edits_default = 0;
117  int ret = itsHelper->modal("Discard edits?", "File was edited. Discard all edits? This cannot be undone.",
118  &discard_edits_default, "Discard", "Save");
119  switch (ret)
120  {
121  case 1: itsWantAction = false; itsOkToLoad = true; break; // Discard selected
122  case 2: saveFile(); itsWantAction = true; break; // save selected
123  default: break; // Need to wait
124  }
125  }
126  else
127  {
128  itsWantLoad = false;
129  itsOkToLoad = true;
130  }
131  }
132 
133  // Need to execute an action after a save?
134  if (itsWantAction)
135  {
136  switch (itsCurrentAction)
137  {
138  case jevois::EditorSaveAction::None:
139  itsWantAction = false;
140  break;
141 
142  case jevois::EditorSaveAction::Reload:
143  {
144  // Skip if override requested by refresh(), typically because we loaded a new module but a config file from the
145  // old module was open so we asked to save, now we don't want to ask to reload again:
146  if (itsOverrideReloadModule)
147  {
148  itsOverrideReloadModule = false;
149  itsWantAction = false;
150  itsOkToLoad = itsWantLoad;
151  break;
152  }
153 
154  // Ask whether to reload the module now or later:
155  static int reload_default = 0;
156  int ret = itsHelper->modal("Reload Module?", "Reload Machine Vision Module for changes to take effect?",
157  &reload_default, "Reload", "Later");
158  switch (ret)
159  {
160  case 1: // Reload selected
161  itsHelper->engine()->requestSetFormat(-1);
162  itsWantAction = false;
163  itsOkToLoad = itsWantLoad;
164  break;
165 
166  case 2: // Later selected: we don't want action anymore
167  itsWantAction = false;
168  itsOkToLoad = itsWantLoad;
169  break;
170 
171  default: break; // need to wait
172  }
173  }
174  break;
175 
176  case jevois::EditorSaveAction::Reboot:
177  {
178  int ret = itsHelper->modal("Restart?", "Restart JeVois-Pro for changes to take effect?",
179  nullptr, "Restart", "Later");
180  switch (ret)
181  {
182  case 1: // Reboot selected
183  itsHelper->engine()->reboot();
184  itsWantAction = false;
185  break;
186 
187  case 2: // Later selected: we don't want action anymore
188  itsWantAction = false;
189  break;
190 
191  default: break; // Need to wait
192  }
193  }
194  break;
195 
196  case jevois::EditorSaveAction::RefreshMappings:
197  {
198  itsHelper->engine()->reloadVideoMappings();
199  itsWantAction = false;
200  }
201  break;
202  }
203  }
204 
205  // Ready to load a new file?
206  if (itsOkToLoad)
207  {
208  // Do we want to browse or create a new file?
209  if (itsItems[itsNewItem].filename == "**")
210  {
211  ImGui::PushStyleColor(ImGuiCol_PopupBg, 0xf0ffe0e0);
212 
213  if (itsBrowser->IsOpened() == false) itsBrowser->Open();
214  itsBrowser->Display();
215  if (itsBrowser->HasSelected())
216  {
217  std::filesystem::path const fn = itsBrowser->GetSelected();
218 
219  if (std::filesystem::exists(fn))
220  loadFile(fn, "Could not load " + fn.string()); // load with error and read-only on fail
221  else
222  loadFile(fn, ""); // load with no error and read-write on fail (create new file)
223 
224  itsBrowser->ClearSelected();
225 
226  // Add an entry for our new file:
227  itsItems.emplace_back(EditorItem { fn, "File " + fn.string(), EditorSaveAction::Reload });
228  itsOkToLoad = false;
229 
230  // Select the item we just added:
231  itsCurrentItem = itsItems.size() - 1;
232  itsCurrentAction = itsItems[itsCurrentItem].action;
233  itsNewItem = itsCurrentItem; // Also select this item in the combo selector
234  }
235 
236  // Clicking "Cancel" in the browser just closes the popup:
237  if (itsBrowser->IsOpened() == false)
238  {
239  itsOkToLoad = false; // Record that we don't want to load anymore:
240  itsNewItem = itsCurrentItem; // Snap back the combo selector to the current item
241  }
242 
243  ImGui::PopStyleColor();
244  }
245  else
246  {
247  // Load the file for itsNewItem:
248  itsOkToLoad = false;
249  itsCurrentItem = itsNewItem;
250  itsCurrentAction = itsItems[itsCurrentItem].action;
251 
252  // Load the file; if fail, assume we want to create a new file (e.g., new module param.cfg), unless module source:
253  if (itsItems[itsCurrentItem].filename == "*")
254  {
255  // If filename is "*", replace by the module's source code name:
256  jevois::VideoMapping const & vm = itsHelper->engine()->getCurrentVideoMapping();
257  loadFile(vm.srcpath(), "Could not open Module's source code");
258  }
259  else if (items[itsCurrentItem][0] != '/')
260  {
261  // If path is relative, make it within the module's path (if any):
262  auto m = itsHelper->engine()->module();
263  if (m) loadFile(m->absolutePath(itsItems[itsCurrentItem].filename), "");
264  else loadFile(itsItems[itsCurrentItem].filename, "");
265  }
266  else loadFile(itsItems[itsCurrentItem].filename, "");
267  }
268  }
269 
270  // Add a pop-up menu for editor actions:
271  bool const ro = IsReadOnly();
272  ImGui::SameLine();
273  if (ImGui::Button("...")) ImGui::OpenPopup("editor_actions");
274  if (ImGui::BeginPopup("editor_actions"))
275  {
276  constexpr int ok = ImGuiSelectableFlags_None;
277  constexpr int disa = ImGuiSelectableFlags_Disabled;
278 
279  if (ImGui::Selectable("Save [Ctrl-S]", false, !ro && IsEdited() ? ok : disa))
280  { saveFile(); itsWantAction = true; }
281 
282  ImGui::Separator();
283 
284  if (ImGui::Selectable("Undo [Ctrl-Z]", false, !ro && CanUndo() ? ok : disa)) Undo();
285  if (ImGui::Selectable("Redo [Ctrl-Y]", false, !ro && CanRedo() ? ok : disa)) Redo();
286 
287  ImGui::Separator();
288 
289  if (ImGui::Selectable("Copy [Ctrl-C]", false, HasSelection() ? ok : disa)) Copy();
290  if (ImGui::Selectable("Cut [Ctrl-X]", false, !ro && HasSelection() ? ok : disa)) Cut();
291  if (ImGui::Selectable("Delete [Del]", false, !ro && HasSelection() ? ok : disa)) Delete();
292  if (ImGui::Selectable("Paste [Ctrl-V]", false, !ro && ImGui::GetClipboardText()!=nullptr ? ok : disa)) Paste();
293 
294  ImGui::Separator();
295 
296  ImGui::Selectable("More shortcuts...", false, disa);
297  if (ImGui::IsItemHovered())
298  ImGui::SetTooltip("[Ctrl-A] Select all\n"
299  "[PgUp/PgDn] Move one page up/down\n"
300  "[Home] Move to start of line\n"
301  "[End] Move to end of line\n"
302  "[Ctrl-Home] Move to start of file\n"
303  "[Ctrl-End] Move to end of file\n"
304  "[Ctrl-Left/Right] Move left/right one word\n"
305  "[Ins] Toggle overwrite mode\n"
306  "[Alt-Bksp] Undo (same as [Ctrl-Z])\n"
307  "[Ctrl-Ins] Copy (same as [Ctrl-C])\n"
308  "[Shift-Ins] Paste (same as [Ctrl-V])\n"
309  "[Shift-Del] Cut (same as [Ctrl-X])\n"
310  "[Shift-Cursor] Select while moving cursor (up, down, left, right, home, end)\n"
311  "[Mouse-Drag] Select with mouse\n"
312  );
313 
314  ImGui::EndPopup();
315  }
316 
317  // Draw a save button if we are read/write:
318  if (ro == false)
319  {
320  ImGui::SameLine();
321  ImGui::TextUnformatted(" "); ImGui::SameLine();
322  if (ImGui::Button("Save")) { saveFile(); itsWantAction = true; }
323  }
324 
325  ImGui::Separator();
326 
327  // Render the editor in a child window so it can scroll correctly:
328  auto cpos = GetCursorPosition();
329 
330  ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s", cpos.mLine + 1, cpos.mColumn + 1, GetTotalLines(),
331  IsOverwrite() ? "Ovr" : "Ins",
332  IsEdited() ? "*" : " ",
333  GetLanguageDefinition().mName.c_str());
334 
335  Render("JeVois-Pro Editor");
336 }
337 
338 // ##############################################################################################################
339 void jevois::GUIeditor::loadFile(std::filesystem::path const & fn, std::string const & failtxt)
340 {
341  LINFO("Loading " << fn << " ...");
342 
343  std::ifstream t(fn);
344  if (t.good())
345  {
346  // Load the whole file and set it as our text:
347  std::string str((std::istreambuf_iterator<char>(t)), std::istreambuf_iterator<char>());
348  SetText(str);
349 
350  // Set the language according to file extension:
351  std::filesystem::path const ext = fn.extension();
352 
353  if (ext == ".py")
354  { SetLanguageDefinition(TextEditor::LanguageDefinition::Python()); SetReadOnly(false); }
355  else if (ext == ".C" || ext == ".H" || ext == ".cpp" || ext == ".hpp")
356  { SetLanguageDefinition(TextEditor::LanguageDefinition::CPlusPlus()); SetReadOnly(true); }
357  else if ( ext == ".c" || ext == ".h")
358  { SetLanguageDefinition(TextEditor::LanguageDefinition::C()); SetReadOnly(true); }
359  else
360  { SetLanguageDefinition(TextEditor::LanguageDefinition::JeVoisCfg()); SetReadOnly(false); } // .cfg, .yaml, etc
361  }
362  else
363  {
364  // Show the fail text:
365  SetText(failtxt);
366  if (failtxt.empty()) { LINFO("File " << fn << " not found -- CREATING NEW"); SetReadOnly(false); }
367  else { LINFO("File " << fn << " not found."); SetReadOnly(true); }
368  }
369 
370  // Remember the filename, for saveFile():
371  itsFilename = fn;
372 }
373 
374 // ##############################################################################################################
376 {
377  LINFO("Saving " << itsFilename << " ...");
378  std::ofstream os(itsFilename);
379  if (os.is_open() == false) { itsHelper->reportError("Cannot write " + itsFilename.string()); return; }
380 
381  std::string const txt = GetText();
382  os << txt;
383 
384  // Mark as un-edited:
385  SetEdited(false);
386 }
387 
388 #endif // JEVOIS_PRO
Module.H
jevois::GUIeditor::saveFile
void saveFile()
Save a file.
Definition: GUIeditor.C:375
jevois::GUIhelper
Helper class to assist modules in creating graphical and GUI elements.
Definition: GUIhelper.H:122
jevois::GUIeditor::loadFile
void loadFile(std::filesystem::path const &fn, std::string const &failtxt)
Load a file.
Definition: GUIeditor.C:339
jevois::absolutePath
std::filesystem::path absolutePath(std::filesystem::path const &root, std::filesystem::path const &path)
Compute an absolute path from two paths.
Definition: Utils.C:365
jevois::GUIeditor::draw
void draw()
Draw the editor into ImGui.
Definition: GUIeditor.C:102
Engine.H
Log.H
jevois::GUIeditor::refresh
void refresh()
Refresh list of files.
Definition: GUIeditor.C:50
jevois::EditorItem
Helper class to represent a GUIeditor file in a pull-down menu.
Definition: GUIeditor.H:37
jevois::GUIeditor::GUIeditor
GUIeditor(GUIhelper *helper, std::string const &imguiid, std::vector< EditorItem > &&fixeditems, std::string const &scanpath, std::string const &prefix, std::set< std::string > &&extensions)
Constructor.
Definition: GUIeditor.C:31
jevois::GUIeditor::~GUIeditor
virtual ~GUIeditor()
Destructor.
Definition: GUIeditor.C:46
GUIeditor.H
ImGui
Definition: GUIeditor.H:27
LINFO
#define LINFO(msg)
Convenience macro for users to print out console or syslog messages, INFO level.
Definition: Log.H:194
GUIhelper.H