JeVois  1.21
JeVois Smart Embedded Machine Vision Toolkit
Share this page:
Loading...
Searching...
No Matches
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
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// ##############################################################################################################
31jevois::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(); } ); // 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// ##############################################################################################################
48
49// ##############################################################################################################
51{
52 // If we have an open file that is not one of our fixed items, we want to keep that one open:
53 bool keep_current = false; EditorItem current_item;
54 if (itsCurrentItem >= int(itsNumFixedItems)) { current_item = itsItems[itsCurrentItem]; keep_current = true; }
55
56 // Remove all dynamic files, keep the fixed ones:
57 itsItems.resize(itsNumFixedItems);
58
59 // Do we have a CMakeLists for the current module?
60 if (itsItems[0].filename == "*")
61 {
62 jevois::VideoMapping const & vm = itsHelper->engine()->getCurrentVideoMapping();
63 if (std::filesystem::exists(vm.cmakepath()))
64 itsItems.emplace_back(EditorItem {"#", "Module's CMakeLists.txt", EditorSaveAction::Compile } );
65 }
66
67 // Rescan recursively:
68 for (auto const & dent : std::filesystem::recursive_directory_iterator(itsScanPath))
69 {
70 if (dent.is_regular_file())
71 {
72 std::filesystem::path const path = dent.path();
73
74 // Check that the extension is one we want:
75 if (itsExtensions.find(path.extension()) == itsExtensions.end()) continue;
76
77 // Create an entry:
78 itsItems.emplace_back(EditorItem { path, itsPrefix + path.string(), EditorSaveAction::Reload });
79 }
80 }
81
82 // Keep the current item?
83 if (keep_current) itsItems.emplace_back(std::move(current_item));
84
85 // Add an entry for file browser / creation:
86 itsItems.emplace_back(EditorItem { "**", "Browse / Create file...", EditorSaveAction::Reload });
87
88 // Update index of current file:
89 bool not_found = true;
90
91 for (int i = 0; auto const & item : itsItems)
92 if (item.filename == itsFilename)
93 {
94 itsCurrentItem = i;
95 itsNewItem = i;
96 not_found = false;
97 break;
98 }
99 else ++i;
100
101 // If the currently open file is not in our list anymore, load file 0:
102 if (not_found)
103 {
104 itsNewItem = 0;
105 itsWantLoad = true;
106 // If we have some edits, we will ask to save, and then to possibly reload the module (or reboot, etc). Prevent
107 // asking to reload:
108 itsOverrideReloadModule = IsEdited();
109 }
110}
111
112// ##############################################################################################################
114{
115 // Create combo entries for imgui:
116 char const * items[itsItems.size()];
117 for (int i = 0; EditorItem const & c : itsItems) items[i++] = c.displayname.c_str();
118
119 // Check if the user is trying to select a different file:
120 if (ImGui::Combo(("##"+itsId+"editorcombo").c_str(), &itsNewItem, items, itsItems.size())) itsWantLoad = true;
121
122 // Want to load a new file? check if we need to save the current one first:
123 if (itsWantLoad && itsWantAction == false)
124 {
125 if (IsEdited())
126 {
127 static int discard_edits_default = 0;
128 int ret = itsHelper->modal("Discard edits?", "File was edited. Discard all edits? This cannot be undone.",
129 &discard_edits_default, "Discard", "Save");
130 switch (ret)
131 {
132 case 1: itsWantLoad = false; itsOkToLoad = true; break; // Discard selected
133 case 2: saveFile(); /* itsWantAction = false; */ break; // save selected
134 default: break; // Need to wait
135 }
136 }
137 else
138 {
139 itsWantLoad = false;
140 itsOkToLoad = true;
141 }
142 }
143
144 // Need to execute an action after a save?
145 if (itsWantAction)
146 {
147 switch (itsItems[itsCurrentItem].action)
148 {
149 // --------------------------------------------------
150 case jevois::EditorSaveAction::None:
151 itsWantAction = false;
152 break;
153
154 // --------------------------------------------------
155 case jevois::EditorSaveAction::Reload:
156 {
157 // Skip if override requested by refresh(), typically because we loaded a new module but a config file from the
158 // old module was open so we asked to save, now we don't want to ask to reload again:
159 if (itsOverrideReloadModule)
160 {
161 itsOverrideReloadModule = false;
162 itsWantAction = false;
163 itsOkToLoad = itsWantLoad;
164 break;
165 }
166
167 // Ask whether to reload the module now or later:
168 static int reload_default = 0;
169 int ret = itsHelper->modal("Reload Module?", "Reload Machine Vision Module for changes to take effect?",
170 &reload_default, "Reload", "Later");
171 switch (ret)
172 {
173 case 1: // Reload selected
174 itsHelper->engine()->requestSetFormat(-1);
175 itsWantAction = false;
176 itsOkToLoad = itsWantLoad;
177 break;
178
179 case 2: // Later selected: we don't want action anymore
180 itsWantAction = false;
181 itsOkToLoad = itsWantLoad;
182 break;
183
184 default: break; // need to wait
185 }
186 }
187 break;
188
189 // --------------------------------------------------
190 case jevois::EditorSaveAction::Reboot:
191 {
192 int ret = itsHelper->modal("Restart?", "Restart JeVois-Pro for changes to take effect?",
193 nullptr, "Restart", "Later");
194 switch (ret)
195 {
196 case 1: // Reboot selected
197 itsHelper->engine()->reboot();
198 itsWantAction = false;
199 break;
200
201 case 2: // Later selected: we don't want action anymore
202 itsWantAction = false;
203 break;
204
205 default: break; // Need to wait
206 }
207 }
208 break;
209
210 // --------------------------------------------------
211 case jevois::EditorSaveAction::RefreshMappings:
212 {
213 itsHelper->engine()->reloadVideoMappings();
214 itsWantAction = false;
215 }
216 break;
217
218 // --------------------------------------------------
219 case jevois::EditorSaveAction::Compile:
220 {
221 // Ask whether to compile the module now or later:
222 static int compile_default = 0;
223 int ret = itsHelper->modal("Compile Module?", "Compile Machine Vision Module for changes to take effect?",
224 &compile_default, "Compile", "Later");
225 switch (ret)
226 {
227 case 1: // Compile selected
228 itsHelper->startCompilation();
229 itsWantAction = false;
230 itsOkToLoad = itsWantLoad;
231 break;
232
233 case 2: // Later selected: we don't want action anymore
234 itsWantAction = false;
235 itsOkToLoad = itsWantLoad;
236 break;
237
238 default: break; // need to wait
239 }
240 }
241 break;
242 }
243 }
244
245 // Ready to load a new file?
246 if (itsOkToLoad)
247 {
248 // Do we want to browse or create a new file?
249 if (itsItems[itsNewItem].filename == "**")
250 {
251 ImGui::PushStyleColor(ImGuiCol_PopupBg, 0xf0ffe0e0);
252
253 if (itsBrowser->IsOpened() == false)
254 {
255 itsBrowser->Open();
256 itsBrowser->Display();
257 }
258 else
259 {
260 itsBrowser->Display();
261
262 if (itsBrowser->HasSelected())
263 {
264 std::filesystem::path const fn = itsBrowser->GetSelected();
265
266 if (std::filesystem::exists(fn))
267 loadFileInternal(fn, "Could not load " + fn.string()); // load with error and read-only on fail
268 else
269 loadFileInternal(fn, ""); // load with no error and read-write on fail (create new file)
270
271 itsBrowser->Close();
272 itsBrowser->Display();
273 }
274
275 // Clicking "Cancel" in the browser just closes the popup:
276 if (itsBrowser->IsOpened() == false)
277 {
278 itsOkToLoad = false; // Record that we don't want to load anymore:
279 itsNewItem = itsCurrentItem; // Snap back the combo selector to the current item
280 itsBrowser->Close();
281 itsBrowser->Display();
282 }
283 }
284 ImGui::PopStyleColor();
285 }
286 else
287 {
288 // Load the file for itsNewItem:
289 itsCurrentItem = itsNewItem;
290 loadFileInternal(itsItems[itsCurrentItem].filename, "");
291 }
292 }
293
294 // Add a pop-up menu for editor actions:
295 bool const ro = IsReadOnly();
296 ImGui::SameLine();
297 if (ImGui::Button("...")) ImGui::OpenPopup("editor_actions");
298 if (ImGui::BeginPopup("editor_actions"))
299 {
300 constexpr int ok = ImGuiSelectableFlags_None;
301 constexpr int disa = ImGuiSelectableFlags_Disabled;
302
303 if (ImGui::Selectable("Save [Ctrl-S]", false, !ro && IsEdited() ? ok : disa)) saveFile();
304
305 ImGui::Separator();
306
307 if (ImGui::Selectable("Undo [Ctrl-Z]", false, !ro && CanUndo() ? ok : disa)) Undo();
308 if (ImGui::Selectable("Redo [Ctrl-Y]", false, !ro && CanRedo() ? ok : disa)) Redo();
309
310 ImGui::Separator();
311
312 if (ImGui::Selectable("Copy [Ctrl-C]", false, HasSelection() ? ok : disa)) Copy();
313 if (ImGui::Selectable("Cut [Ctrl-X]", false, !ro && HasSelection() ? ok : disa)) Cut();
314 if (ImGui::Selectable("Delete [Del]", false, !ro && HasSelection() ? ok : disa)) Delete();
315 if (ImGui::Selectable("Paste [Ctrl-V]", false, !ro && ImGui::GetClipboardText()!=nullptr ? ok : disa)) Paste();
316
317 ImGui::Separator();
318
319 ImGui::Selectable("More shortcuts...", false, disa);
320 if (ImGui::IsItemHovered())
321 ImGui::SetTooltip("[Ctrl-A] Select all\n"
322 "[PgUp/PgDn] Move one page up/down\n"
323 "[Home] Move to start of line\n"
324 "[End] Move to end of line\n"
325 "[Ctrl-Home] Move to start of file\n"
326 "[Ctrl-End] Move to end of file\n"
327 "[Ctrl-Left/Right] Move left/right one word\n"
328 "[Ins] Toggle overwrite mode\n"
329 "[Alt-Bksp] Undo (same as [Ctrl-Z])\n"
330 "[Ctrl-Ins] Copy (same as [Ctrl-C])\n"
331 "[Shift-Ins] Paste (same as [Ctrl-V])\n"
332 "[Shift-Del] Cut (same as [Ctrl-X])\n"
333 "[Shift-Cursor] Select while moving cursor (up, down, left, right, home, end)\n"
334 "[Mouse-Drag] Select with mouse\n"
335 );
336
337 ImGui::EndPopup();
338 }
339
340 // Draw a save button if we are read/write:
341 if (ro == false)
342 {
343 ImGui::SameLine();
344 ImGui::TextUnformatted(" "); ImGui::SameLine();
345 if (ImGui::Button("Save")) saveFile();
346 }
347
348 ImGui::Separator();
349
350 // Render the editor in a child window so it can scroll correctly:
351 auto cpos = GetCursorPosition();
352
353 ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s", cpos.mLine + 1, cpos.mColumn + 1, GetTotalLines(),
354 IsOverwrite() ? "Ovr" : "Ins",
355 IsEdited() ? "*" : " ",
356 GetLanguageDefinition().mName.c_str());
357
358 Render("JeVois-Pro Editor");
359}
360
361// ##############################################################################################################
362void jevois::GUIeditor::loadFile(std::filesystem::path const & fn)
363{
364 // Loading will happen in the main loop. Here we just create a new item:
365 for (int i = 0; EditorItem const & item : itsItems)
366 if (item.filename == fn) { itsNewItem = i; itsWantLoad = true; return; } else ++i;
367
368 // Not already in our list of items, create a new one. Add an entry for our new file. If it's compilable, then action
369 // on save should be to compile, otherwise it should be to reload the module:
370 EditorSaveAction action = EditorSaveAction::Reload;
371 if (fn.filename() == "CMakeLists.txt")
372 action = EditorSaveAction::Compile;
373 else if (fn.filename() == "jevoispro-fan.service")
374 action = EditorSaveAction::Reboot;
375 else
376 {
377 std::string const ext = fn.extension().string();
378 if (ext == ".C" || ext == ".H" || ext == ".cpp" || ext == ".hpp" || ext == ".c" || ext == ".h")
379 action = EditorSaveAction::Compile;
380 }
381 itsItems.emplace_back(EditorItem { fn, "File " + fn.string(), action });
382
383 itsNewItem = itsItems.size() - 1;
384 itsWantLoad = true;
385}
386
387// ##############################################################################################################
388void jevois::GUIeditor::loadFileInternal(std::filesystem::path const & fpath, std::string const & failt)
389{
390 std::filesystem::path fn = fpath; std::string failtxt = failt; bool special_path = false;
391
392 if (fpath == "*")
393 {
394 // If filename is "*", replace by the module's source code name:
395 jevois::VideoMapping const & vm = itsHelper->engine()->getCurrentVideoMapping();
396 fn = vm.srcpath();
397 failtxt = "Could not open Module's source code";
398 special_path = true;
399 }
400 else if (fpath == "#")
401 {
402 // If filename is "#", replace by the module's CMakeLists.txt:
403 jevois::VideoMapping const & vm = itsHelper->engine()->getCurrentVideoMapping();
404 fn = vm.cmakepath();
405 failtxt = "Could not open Module's CMakeLists.txt";
406 special_path = true;
407 }
408 else if (fpath.is_relative())
409 {
410 // If path is relative, make it within the module's path (if any):
411 auto m = itsHelper->engine()->module();
412 if (m) fn = m->absolutePath(fpath);
413 special_path = true;
414 }
415
416 if (fn != fpath) LINFO("Loading " << fn << " ... [" << fpath << ']'); else LINFO("Loading " << fn << " ...");
417
418 bool got_it = false;
419 EditorSaveAction action = EditorSaveAction::Reload;
420
421 std::ifstream t(fn);
422 if (t.good())
423 {
424 // Load the whole file and set it as our text:
425 std::string str((std::istreambuf_iterator<char>(t)), std::istreambuf_iterator<char>());
426 SetText(str);
427
428 // Is this a known file in our pull-down list? Otherwise we need to create a new item:
429 for (int i = 0; EditorItem const & item : itsItems)
430 if (item.filename == fpath) { itsCurrentItem = i; got_it = true; break; } else ++i;
431
432 // Set the language, read-only, and possibly action according to file extension. C++/C source files are editable if
433 // there is a CMakeLists.txt in the same directory (e.g., newly created or cloned module, excludes jevoisbase
434 // modules), and then a compilation action will be triggered on save:
435 if (fn.filename() == "CMakeLists.txt")
436 {
437 SetLanguageDefinition(TextEditor::LanguageDefinition::CMake());
438 SetReadOnly(false);
439 action = EditorSaveAction::Compile;
440 }
441 else
442 {
443 std::filesystem::path const ext = fn.extension();
444 std::filesystem::path cmak = fn; cmak.remove_filename(); cmak /= "CMakeLists.txt";
445 bool has_cmake = std::filesystem::exists(cmak);
446
447 if (ext == ".py")
448 {
449 SetLanguageDefinition(TextEditor::LanguageDefinition::Python());
450 SetReadOnly(false);
451 }
452 else if (ext == ".C" || ext == ".H" || ext == ".cpp" || ext == ".hpp")
453 {
454 SetLanguageDefinition(TextEditor::LanguageDefinition::CPlusPlus());
455 SetReadOnly(! has_cmake);
456 action = EditorSaveAction::Compile;
457 }
458 else if ( ext == ".c" || ext == ".h")
459 {
460 SetLanguageDefinition(TextEditor::LanguageDefinition::C());
461 SetReadOnly(! has_cmake);
462 action = EditorSaveAction::Compile;
463 }
464 else
465 {
466 // .cfg, .yaml, etc
467 SetLanguageDefinition(TextEditor::LanguageDefinition::JeVoisCfg());
468 SetReadOnly(false);
469 }
470 }
471 }
472 else
473 {
474 // Show the fail text:
475 SetText(failtxt);
476 if (failtxt.empty()) { LINFO("File " << fn << " not found -- CREATING NEW"); SetReadOnly(false); }
477 else { LINFO("File " << fn << " not found."); SetReadOnly(true); }
478 }
479
480 // If this is a new file, add an item to our pull-down list:
481 if (got_it == false && special_path == false)
482 {
483 itsItems.emplace_back(EditorItem { fn, "File " + fn.string(), action });
484 itsCurrentItem = itsItems.size() - 1;
485 }
486 else if (fpath == "*") itsItems[itsCurrentItem].action = action; // Force compile action if needed on module's src
487
488 // Remember the filename, for saveFile():
489 itsFilename = fn;
490 itsNewItem = itsCurrentItem;
491 itsWantLoad = false;
492 itsOkToLoad = false;
493}
494
495// ##############################################################################################################
496std::filesystem::path const & jevois::GUIeditor::getLoadedFilePath() const
497{ return itsFilename; }
498
499// ##############################################################################################################
501{
502 LINFO("Saving " << itsFilename << " ...");
503 std::ofstream os(itsFilename);
504 if (os.is_open() == false) { itsHelper->reportError("Cannot write " + itsFilename.string()); return; }
505
506 std::string const txt = GetText();
507 os << txt;
508
509 // Mark as un-edited:
510 SetEdited(false);
511
512 // Delete modinfo.html if any, GUIhelper will re-compute it when the info tab is selected:
513 std::filesystem::path mi = itsFilename.parent_path() / "modinfo.html";
514 if (std::filesystem::exists(mi)) std::filesystem::remove(mi);
515
516 // After a save, execute any required action like reload module, reboot, recompile, etc:
517 itsWantAction = true;
518}
519
520#endif // JEVOIS_PRO
#define JEVOIS_SHARE_PATH
Base path for shared files (e.g., neural network weights, etc)
Definition Config.H:82
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
EditorSaveAction
Helper enum for actions to execute after saving a config file.
Definition GUIeditor.H:34
virtual ~GUIeditor()
Destructor.
Definition GUIeditor.C:46
void saveFile()
Save a file.
Definition GUIeditor.C:500
std::filesystem::path const & getLoadedFilePath() const
Get path of file last loaded with loadFile(), may be empty.
Definition GUIeditor.C:496
void refresh()
Refresh list of files.
Definition GUIeditor.C:50
void loadFile(std::filesystem::path const &fn)
Load a file and set it as the current file.
Definition GUIeditor.C:362
void draw()
Draw the editor into ImGui.
Definition GUIeditor.C:113
Helper class to assist modules in creating graphical and GUI elements.
Definition GUIhelper.H:133
#define LINFO(msg)
Convenience macro for users to print out console or syslog messages, INFO level.
Definition Log.H:194
Helper class to represent a GUIeditor file in a pull-down menu.
Definition GUIeditor.H:37
Simple struct to hold video mapping definitions for the processing Engine.
std::string path() const
Return the full absolute path the module's directory.
std::string srcpath() const
Return the full absolute path and file name of the module's .C or .py file.
std::string cmakepath() const
Return the full absolute path and file name of the module's CMakeLists.txt file.