Fluid: improve user dialog for new widgets

* foundation only
* if a window can;t be created, give
  user a choice to fix the issue
  instead of just an error
This commit is contained in:
Matthias Melcher
2026-08-16 22:33:39 +02:00
parent c46c5d9051
commit 060c01d121
5 changed files with 143 additions and 8 deletions
+18
View File
@@ -28,6 +28,7 @@
#include <FL/filename.H>
#include <string>
#include <exception>
constexpr int BROWSERWIDTH = 300;
constexpr int BROWSERHEIGHT = 500;
@@ -54,6 +55,23 @@ namespace widget {
class App_Menu_Bar;
}
class Exception : public std::runtime_error {
public:
explicit Exception(const std::string& msg) : std::runtime_error(msg) {}
};
class UserCanceledException : public Exception {
public:
UserCanceledException() : Exception("Operation cancelled by user") {}
};
class ReadException : public Exception {
public:
explicit ReadException(const std::string& msg) : Exception("Read error: " + msg) {}
};
/**
Indicate the storage location for tools like layout suites and shell macros.
\see class Fd_Shell_Command, class Layout_Suite
+12 -2
View File
@@ -412,7 +412,15 @@ int Project_Reader::read_project(const char *filename, int merge, Strategy strat
deselect();
else
proj_.reset();
read_children(Fluid.proj.tree.current, merge, strategy);
try {
read_children(Fluid.proj.tree.current, merge, strategy);
} catch (const fluid::UserCanceledException&) {
// User chose abort - silently return or log
} catch (const fluid::ReadException& e) {
fluid_alert("Error reading file: %s", e.what());
}
// clear this
Fluid.proj.tree.current = nullptr;
// Force menu items to be rebuilt...
@@ -448,7 +456,9 @@ int Project_Reader::read_project(const char *filename, int merge, Strategy strat
void Project_Reader::read_error(const char *format, ...) {
va_list args;
va_start(args, format);
fluid_alert(format, args);
if (fluid_choice("Fluid: ERROR reading project file", format, "Abort", "Ignore", nullptr, args) == msg::ABORT) {
throw fluid::UserCanceledException();
}
va_end(args);
}
+84 -3
View File
@@ -18,6 +18,11 @@
#include "Fluid.h"
#include <FL/fl_ask.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Return_Button.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Check_Button.H>
#include "../src/flstring.h"
#undef min
@@ -97,14 +102,18 @@ int fluid_choice(const char *fmt, const char *b0, const char *b1, const char *b2
}
int fluid_choice(const char *fmt, const char *b0, const char *b1, const char *b2, va_list ap) {
return fluid_choice("Fluid", fmt, b0, b1, b2, ap);
}
int fluid_choice(const char *title, const char *fmt, const char *b0, const char *b1, const char *b2, va_list ap) {
char buffer[8096];
vsnprintf(buffer, sizeof(buffer), fmt, ap);
if (b0 && b1 && b2) {
return fluid::choice("Fluid", buffer, { {b0, b0[0]}, {b1, b1[0]}, {b2, b2[0]} });
return fluid::choice(title, buffer, { {b0, b0[0]}, {b1, b1[0]}, {b2, b2[0]} });
} else if (b0 && b1) {
return fluid::choice("Fluid", buffer, { {b0, b0[0]}, {b1, b1[0]} });
return fluid::choice(title, buffer, { {b0, b0[0]}, {b1, b1[0]} });
} else if (b0) {
return fluid::choice("Fluid", buffer, { {b0, b0[0]} });
return fluid::choice(title, buffer, { {b0, b0[0]} });
} else {
return -1;
}
@@ -220,3 +229,75 @@ int fluid::choice(const std::string &title, const std::string &message,
}
}
int fluid::big_choice(const std::string &title, const std::string &message,
const std::vector<msg::Option> &option)
{
if (Fluid.console_mode()) return fluid::choice(title, message, option);
struct Dialog {
Fl_Window* win;
Fl_Box* symbol, *msg;
Fl_Button* ok;
Fl_Button* cancel;
Fl_Check_Button* choice[3];
int result = msg::ESC;
} dlg;
dlg.win = new Fl_Window(400, 400, title.c_str());
dlg.win->set_modal();
dlg.win->callback(
[](Fl_Widget* w, void* data) {
Fl_Window* win = (Fl_Window*)w;
Dialog* dlg = (Dialog*)data;
dlg->result = msg::ESC;
win->hide();
}, &dlg);
dlg.symbol = new Fl_Box(10, 10, 50, 50, "?");
dlg.symbol->box(FL_THIN_UP_BOX);
dlg.symbol->labelfont(FL_COURIER_BOLD);
dlg.symbol->labelsize(42);
dlg.symbol->labeltype(FL_SHADOW_LABEL);
dlg.symbol->align(FL_ALIGN_CENTER | FL_ALIGN_INSIDE);
dlg.symbol->labelcolor(FL_BLUE);
dlg.symbol->color(FL_WHITE);
fl_font(FL_HELVETICA, 12);
int w = 320, h = 0;
fl_measure(message.c_str(), w, h, 0);
if (h < 50) h = 50;
dlg.msg = new Fl_Box(70, 10, 320, h, message.c_str());
dlg.msg->box(FL_FLAT_BOX);
dlg.msg->align(FL_ALIGN_TOP_LEFT | FL_ALIGN_INSIDE | FL_ALIGN_WRAP);
dlg.win->size(400, 10 + h + 18 + option.size() * 28 + 10 + 25 + 10);
for (size_t i = 0; i < option.size(); ++i) {
dlg.choice[i] = new Fl_Check_Button(70, 10 + h + 18 + i * 28, 320, 25, option[i].label.c_str());
dlg.choice[i]->type(FL_RADIO_BUTTON);
dlg.choice[i]->callback(
[](Fl_Widget* w, void* data) {
Fl_Window* win = w->window();
Dialog* dlg = (Dialog*)win->user_data();
dlg->result = fl_int(data);
dlg->ok->activate();
}, fl_voidptr(i));
dlg.choice[i]->shortcut(option[i].key);
}
dlg.ok = new Fl_Return_Button(dlg.win->w()-220, dlg.win->h() - 10 - 25, 100, 25, "OK");
dlg.ok->deactivate();
dlg.ok->callback(
[](Fl_Widget* w, void* data) {
w->window()->hide();
}, &dlg);
dlg.cancel = new Fl_Button(dlg.win->w()-110, dlg.win->h() - 10 - 25, 100, 25, "Cancel");
dlg.cancel->callback(
[](Fl_Widget* w, void* data) {
Dialog* dlg = (Dialog*)data;
dlg->result = msg::ESC;
w->window()->hide();
}, &dlg);
dlg.win->show();
Fl_Window* gg = Fl::grab();
if (gg) Fl::grab(nullptr);
while (dlg.win->shown()) {
Fl::wait();
}
delete dlg.win;
if (gg) Fl::grab(gg);
return dlg.result;
}
+3 -1
View File
@@ -31,6 +31,7 @@ extern void fluid_message(const char *fmt, ...);
extern void fluid_message(const char *fmt, va_list ap);
extern int fluid_choice(const char *fmt, const char *b0, const char *b1, const char *b2, ...);
extern int fluid_choice(const char *fmt, const char *b0, const char *b1, const char *b2, va_list ap);
extern int fluid_choice(const char *title, const char *fmt, const char *b0, const char *b1, const char *b2, va_list ap);
// fl_input
namespace fluid {
@@ -50,7 +51,8 @@ void alert(const std::string &title, const std::string &message);
int error_choice(const std::string &title, const std::string &message);
int choice(const std::string &title, const std::string &message,
const std::vector<msg::Option> &option);
int big_choice(const std::string &title, const std::string &message,
const std::vector<msg::Option> &option);
} // namespace fluid
#endif // FLUID_MESSAGE_H
+26 -2
View File
@@ -239,8 +239,32 @@ Node *Window_Node::make(Strategy strategy) {
p = p->parent;
}
if (!p) {
fluid_message("Please select a function");
return nullptr;
if (strategy.source() == Strategy::FROM_FILE) {
return nullptr; // trigger a file read error
}
int ret = fluid::big_choice(
"Fluid: Window Container Required",
"A Window can only be created inside a Function or a Widget Class container.\n\n"
"Would you like to create a new container or select an existing one?",
{
{"Create a &Function and add the window", 'f'},
{"Create a &Widget Class and add the window", 'w'},
{"&Cancel and let me select an existing container", 'c'}
} );
switch (ret) {
case 0:
p = add_new_widget_from_user("function", Strategy::AFTER_CURRENT, false);
Fluid.proj.tree.current = anchor = p;
strategy.placement(Strategy::AS_LAST_CHILD);
break;
case 1:
p = add_new_widget_from_user("widget_class", Strategy::AFTER_CURRENT, true);
Fluid.proj.tree.current = anchor = p;
strategy.placement(Strategy::AS_LAST_CHILD);
break;
default:
return nullptr;
}
}
Window_Node *myo = new Window_Node();
if (!this->o) {// template widget