FLUID: adds greatly enhanced Shell Commands (#774)

The user can add an arbitrary number of highly configurable 
shell commands through the setting panel. The commands can
be saved as user preferences, inside the .fl file, or exported
to an external file. Shell scripts can be limited to individual 
platforms, can have shortcut keys, etc. .

* documentation will follow
* support to call `fltk-config` will follow
This commit is contained in:
Matthias Melcher 2023-09-26 15:01:03 +01:00 committed by GitHub
parent 2bbdd49465
commit 71b8e77935
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 2891 additions and 425 deletions

View File

@ -22,6 +22,7 @@
# include <stdio.h>
# include "Fl_Export.H"
# include "fl_attr.h"
class Fl_String;
@ -131,6 +132,7 @@ public:
ROOT_MASK = 0x00FF, ///< Mask for the values above
CORE = 0x0100, ///< OR'd by FLTK to read and write core library preferences and options
C_LOCALE = 0x1000, ///< This flag should always be set, it makes sure that floating point
CLEAR = 0x2000, ///< Don't read a possibly existing database. Instead, start with an empty set of preferences.
///< values are written correctly independently of the current locale
SYSTEM_L = SYSTEM | C_LOCALE, ///< Preferences are used system-wide, locale independent
USER_L = USER | C_LOCALE, ///< Preferences apply only to the current user, locale independent
@ -188,7 +190,7 @@ public:
static Root filename( char *buffer, size_t buffer_size, Root root, const char *vendor, const char *application );
Fl_Preferences( Root root, const char *vendor, const char *application );
Fl_Preferences( const char *path, const char *vendor, const char *application );
Fl_Preferences( const char *path, const char *vendor, const char *application, Root flags );
Fl_Preferences( Fl_Preferences &parent, const char *group );
Fl_Preferences( Fl_Preferences *parent, const char *group );
Fl_Preferences( Fl_Preferences &parent, int groupIndex );
@ -197,6 +199,9 @@ public:
Fl_Preferences( ID id );
virtual ~Fl_Preferences();
FL_DEPRECATED("in 1.4.0 - use Fl_Preferences(path, vendor, application, flags) instead",
Fl_Preferences( const char *path, const char *vendor, const char *application ) );
Root filename( char *buffer, size_t buffer_size);
/** Return an ID that can later be reused to open more references to this dataset.
@ -374,7 +379,7 @@ public: // older Sun compilers need this (public definition of the following cl
Root root_type_;
public:
RootNode( Fl_Preferences *, Root root, const char *vendor, const char *application );
RootNode( Fl_Preferences *, const char *path, const char *vendor, const char *application );
RootNode( Fl_Preferences *, const char *path, const char *vendor, const char *application, Root flags );
RootNode( Fl_Preferences * );
~RootNode();
int read();

View File

@ -422,7 +422,7 @@ void Fd_Layout_Suite::init() {
name_ = NULL;
menu_label = NULL;
layout[0] = layout[1] = layout[2] = NULL;
storage_ = 0;
storage_ = FD_STORE_INTERNAL;
}
/**
@ -651,7 +651,7 @@ void Fd_Layout_List::update_menu_labels() {
*/
int Fd_Layout_List::load(const Fl_String &filename) {
remove_all(FD_STORE_FILE);
Fl_Preferences prefs(filename.c_str(), "layout.fluid.fltk.org", NULL);
Fl_Preferences prefs(filename.c_str(), "layout.fluid.fltk.org", NULL, Fl_Preferences::C_LOCALE);
read(prefs, FD_STORE_FILE);
return 0;
}
@ -661,7 +661,7 @@ int Fd_Layout_List::load(const Fl_String &filename) {
*/
int Fd_Layout_List::save(const Fl_String &filename) {
assert(this);
Fl_Preferences prefs(filename.c_str(), "layout.fluid.fltk.org", NULL);
Fl_Preferences prefs(filename.c_str(), "layout.fluid.fltk.org", NULL, (Fl_Preferences::Root)(Fl_Preferences::C_LOCALE|Fl_Preferences::CLEAR));
prefs.clear();
write(prefs, FD_STORE_FILE);
return 0;
@ -670,7 +670,7 @@ int Fd_Layout_List::save(const Fl_String &filename) {
/**
Write Suite and Layout selection and selected layout data to Preferences database.
*/
void Fd_Layout_List::write(Fl_Preferences &prefs, int storage) {
void Fd_Layout_List::write(Fl_Preferences &prefs, Fd_Tool_Store storage) {
Fl_Preferences prefs_list(prefs, "Layouts");
prefs_list.clear();
prefs_list.set("current_suite", list_[current_suite()].name_);
@ -688,7 +688,7 @@ void Fd_Layout_List::write(Fl_Preferences &prefs, int storage) {
/**
Read Suite and Layout selection and selected layout data to Preferences database.
*/
void Fd_Layout_List::read(Fl_Preferences &prefs, int storage) {
void Fd_Layout_List::read(Fl_Preferences &prefs, Fd_Tool_Store storage) {
Fl_Preferences prefs_list(prefs, "Layouts");
Fl_String cs;
int cp = 0;
@ -859,7 +859,7 @@ int Fd_Layout_List::add(const char *name) {
new_suite.layout[i] = new Fd_Layout_Preset;
::memcpy(new_suite.layout[i], old_suite.layout[i], sizeof(Fd_Layout_Preset));
}
int new_storage = old_suite.storage_;
Fd_Tool_Store new_storage = old_suite.storage_;
if (new_storage == FD_STORE_INTERNAL)
new_storage = FD_STORE_USER;
new_suite.storage(new_storage);
@ -904,7 +904,7 @@ void Fd_Layout_List::remove(int ix) {
Remove all Suites that use the given storage attribute.
\param[in] storage storage attribute, see FD_STORE_INTERNAL, etc.
*/
void Fd_Layout_List::remove_all(int storage) {
void Fd_Layout_List::remove_all(Fd_Tool_Store storage) {
for (int i=list_size_-1; i>=0; --i) {
if (list_[i].storage_ == storage)
remove(i);

View File

@ -17,6 +17,7 @@
#ifndef _FLUID_FD_SNAP_ACTION_H
#define _FLUID_FD_SNAP_ACTION_H
#include "fluid.h"
#include "Fl_Window_Type.h"
#include <FL/Fl_String.H>
@ -25,16 +26,6 @@ struct Fl_Menu_Item;
extern Fl_Menu_Item main_layout_submenu_[];
/**
Indicate the storage location for a layout suite.
*/
enum {
FD_STORE_INTERNAL, ///< stored inside FLUID app
FD_STORE_USER, ///< suite is stored in the user wide FLUID settings
FD_STORE_PROJECT, ///< suite is stored within the current .fl project file
FD_STORE_FILE ///< store suite in external file
};
/**
\brief Collection of layout settings.
@ -97,13 +88,13 @@ public:
char *name_; ///< name of the suite
char *menu_label; ///< label text used in pulldown menu
Fd_Layout_Preset *layout[3]; ///< presets for application, dialog, and toolbox windows
int storage_; ///< storage location (see FD_STORE_INTERNAL, etc.)
Fd_Tool_Store storage_; ///< storage location (see FD_STORE_INTERNAL, etc.)
void write(Fl_Preferences &prefs);
void read(Fl_Preferences &prefs);
void write(Fd_Project_Writer*);
void read(Fd_Project_Reader*);
void update_label();
void storage(int s) { storage_ = s; update_label(); }
void storage(Fd_Tool_Store s) { storage_ = s; update_label(); }
void name(const char *n);
void init();
~Fd_Layout_Suite();
@ -146,13 +137,13 @@ public:
int load(const Fl_String &filename);
int save(const Fl_String &filename);
void write(Fl_Preferences &prefs, int storage);
void read(Fl_Preferences &prefs, int storage);
void write(Fl_Preferences &prefs, Fd_Tool_Store storage);
void read(Fl_Preferences &prefs, Fd_Tool_Store storage);
void write(Fd_Project_Writer*);
void read(Fd_Project_Reader*);
int add(Fd_Layout_Suite*);
void remove(int index);
void remove_all(int storage);
void remove_all(Fd_Tool_Store storage);
Fd_Layout_Preset *at(int);
int size();
};

View File

@ -252,11 +252,16 @@ void delete_all(int selected_only) {
}
if(!selected_only) {
// reset the setting for the external shell command
shell_prefs_get();
shell_settings_write();
if (g_shell_config) {
g_shell_config->clear(FD_STORE_PROJECT);
g_shell_config->rebuild_shell_menu();
g_shell_config->update_settings_dialog();
}
widget_browser->hposition(0);
widget_browser->vposition(0);
g_layout_list.remove_all(FD_STORE_PROJECT);
g_layout_list.current_suite(0);
g_layout_list.current_preset(0);
g_layout_list.update_dialogs();
}
selection_changed(0);

View File

@ -444,6 +444,10 @@ Type "Fl_Widget" <word> : C++ variable name
"extra_code" <word> : C++ extra code lines
... : inherits more from Fl_Type
Type "Fl_Button" <word> : C++ variable name
"compact" <word> : integer, set the flag for compact buttons, defaults to 0
Type "Fl_Flex" <word> : C++ variable name
"margins" <word> : this Word is written with printf as "{%d %d %d %d}",

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@ header_name {.h}
code_name {.cxx}
snap {
ver 1
current_suite FLTK
current_suite {FLUID (based on FLTK)}
current_preset 0
suite {
name {FLUID (based on FLTK)}
@ -91,6 +91,9 @@ decl {\#include <FL/fl_ask.H>} {private global
decl {\#include <string.h>} {private global
}
decl {\#include "../src/flstring.h"} {private global
}
decl {void init_scheme(void);} {
comment {// initialize the scheme from preferences} public global
}
@ -104,11 +107,49 @@ decl {extern void i18n_cb(Fl_Choice *,void *);} {public local
decl {void scheme_cb(Fl_Scheme_Choice *, void *);} {public local
}
decl {int w_settings_shell_list_selected;} {public local
}
Function {make_script_panel()} {open
} {
Fl_Window script_panel {
label {Shell Script Editor}
callback {if (Fl::event()==FL_SHORTCUT && Fl::event_key()==FL_Escape)
return; // ignore Escape
script_panel->hide(); // otherwise hide..} open
xywh {764 319 540 180} type Double labelsize 11 resizable
code0 {o->size_range(200, 150);} modal visible
} {
Fl_Text_Editor script_input {
xywh {10 10 520 130} box DOWN_BOX labelsize 11 when 13 textfont 4 textsize 11 resizable
code0 {script_input->buffer(new Fl_Text_Buffer);}
}
Fl_Group {} {open
xywh {10 150 520 20} labelsize 11
} {
Fl_Return_Button script_panel_ok {
label OK
xywh {400 150 60 20} labelsize 11 hotspot
}
Fl_Button script_panel_cancel {
label Cancel
xywh {470 150 60 20} labelsize 11
}
Fl_Box {} {
xywh {10 150 380 20} labelsize 11 resizable
}
}
}
code {// Enable line numbers
script_input->linenumber_width(60);
script_input->linenumber_size(script_input->Fl_Text_Display::textsize());} {}
}
Function {make_settings_window()} {open
} {
Fl_Window settings_window {
label {FLUID Settings} open
xywh {423 204 340 580} type Double align 80 non_modal visible
xywh {392 362 340 580} type Double align 80 visible
} {
Fl_Tabs w_settings_tabs {
callback {propagate_load(o, v);} open
@ -758,73 +799,528 @@ g_layout_list.update_dialogs();}
}
Fl_Group w_settings_shell_tab {
label Shell
callback {propagate_load(o, v);}
callback {propagate_load(o, v);} open
image {icons/shell_64.png} compress_image 1 xywh {10 60 320 480} labelsize 11 hide
code0 {o->image()->scale(36, 24);}
} {
Fl_Input {} {
label {Command:}
Fl_Browser w_settings_shell_list {
label {Shell
command
list:}
callback {if (v == LOAD) {
o->value(g_shell_command.c_str());
} else {
g_shell_command = o->value();
}}
tooltip {external shell command} xywh {100 78 220 20} labelfont 1 labelsize 11 textfont 4 textsize 11
}
Fl_Check_Button {} {
label {save .fl project file}
callback {if (v == LOAD) {
o->value(g_shell_save_fl);
} else {
g_shell_save_fl = o->value();
}}
tooltip {save the project to the .fl file before running the command} xywh {100 98 220 20} down_box DOWN_BOX labelsize 11
}
Fl_Check_Button {} {
label {save source code}
callback {if (v == LOAD) {
o->value(g_shell_save_code);
} else {
g_shell_save_code = o->value();
}}
tooltip {generate the source code and header file before running the command} xywh {100 118 220 19} down_box DOWN_BOX labelsize 11
}
Fl_Check_Button {} {
label {save i18n strings}
callback {if (v == LOAD) {
o->value(g_shell_save_strings);
} else {
g_shell_save_strings = o->value();
}}
tooltip {save the internationalisation string before running the command} xywh {100 137 220 20} down_box DOWN_BOX labelsize 11
}
Fl_Check_Button shell_use_fl_button {
label {save settings in .fl project files}
callback {if (v == LOAD) {
o->value(g_shell_use_fl_settings);
} else {
g_shell_use_fl_settings = o->value();
fluid_prefs.set("shell_use_fl", g_shell_use_fl_settings);
if (g_shell_use_fl_settings) {
shell_settings_read();
} else {
shell_prefs_get();
// load from g_shell_config
if (g_shell_config) {
o->clear();
w_settings_shell_list_selected = 0;
for (int i=0; i<g_shell_config->list_size; i++) {
Fd_Shell_Command *cmd = g_shell_config->list[i];
o->add(cmd->name.c_str());
if (cmd->storage == FD_STORE_USER)
o->icon(i+1, w_settings_shell_fd_user->image());
else if (cmd->storage == FD_STORE_PROJECT)
o->icon(i+1, w_settings_shell_fd_project->image());
}
}
w_settings_shell_tab->do_callback(w_settings_shell_tab, LOAD);
} else {
// int prev_selected = w_settings_shell_list_selected;
w_settings_shell_list_selected = 0;
int selected = w_settings_shell_list->value();
if (selected) {
if (w_settings_shell_list->selected(selected))
w_settings_shell_list_selected = selected;
}
w_settings_shell_cmd->do_callback(w_settings_shell_cmd, LOAD);
w_settings_shell_toolbox->do_callback(w_settings_shell_toolbox, LOAD);
}}
tooltip {check to read and write shell command from and to .fl files} xywh {100 194 220 19} down_box DOWN_BOX labelsize 11 deactivate
xywh {100 90 220 110} type Multi labelfont 1 labelsize 11 align 4 textsize 13
}
Fl_Button {} {
label {save as default}
callback {if (v != LOAD)
shell_prefs_set();}
tooltip {update the Fluid app settings for external shell commands to the current settings} xywh {100 218 115 20} labelsize 11
Fl_Group w_settings_shell_toolbox {
callback {if (v==LOAD) {
propagate_load(o, v);
}} open
xywh {100 200 220 22}
} {
Fl_Button {} {
label {+}
callback {if (v != LOAD) {
int selected = w_settings_shell_list_selected;
Fd_Shell_Command *cmd = new Fd_Shell_Command("new shell command");
g_shell_config->insert(selected, cmd);
w_settings_shell_list->insert(selected+1, cmd->name.c_str());
w_settings_shell_list->deselect();
w_settings_shell_list->value(selected+1);
if (cmd->storage == FD_STORE_USER)
w_settings_shell_list->icon(selected+1, w_settings_shell_fd_user->image());
else if (cmd->storage == FD_STORE_PROJECT)
w_settings_shell_list->icon(selected+1, w_settings_shell_fd_project->image());
w_settings_shell_list->do_callback();
w_settings_shell_cmd->do_callback(w_settings_shell_cmd, LOAD);
w_settings_shell_toolbox->do_callback(w_settings_shell_toolbox, LOAD);
g_shell_config->rebuild_shell_menu();
}}
xywh {100 200 24 22} labelsize 11
}
Fl_Button w_settings_shell_dup {
label {++}
callback {int selected = w_settings_shell_list_selected;
if (v==LOAD) {
if (selected) {
o->activate();
} else {
o->deactivate();
}
} else {
if (!selected) return;
Fd_Shell_Command *cmd = new Fd_Shell_Command(g_shell_config->list[selected-1]);
g_shell_config->insert(selected, cmd);
w_settings_shell_list->insert(selected+1, cmd->name.c_str());
w_settings_shell_list->deselect();
w_settings_shell_list->deselect();
w_settings_shell_list->value(selected+1);
if (cmd->storage == FD_STORE_USER)
w_settings_shell_list->icon(selected+1, w_settings_shell_fd_user->image());
else if (cmd->storage == FD_STORE_PROJECT)
w_settings_shell_list->icon(selected+1, w_settings_shell_fd_project->image());
w_settings_shell_list->do_callback();
w_settings_shell_cmd->do_callback(w_settings_shell_cmd, LOAD);
w_settings_shell_toolbox->do_callback(w_settings_shell_toolbox, LOAD);
g_shell_config->rebuild_shell_menu();
}}
xywh {124 200 24 22} labelsize 11 deactivate
}
Fl_Button w_settings_shell_remove {
label {-}
callback {int selected = w_settings_shell_list_selected;
if (v==LOAD) {
if (selected) {
o->activate();
} else {
o->deactivate();
}
} else {
if (!selected) return;
g_shell_config->remove(selected-1);
w_settings_shell_list->remove(selected);
if (selected <= w_settings_shell_list->size())
w_settings_shell_list->value(selected);
else
w_settings_shell_list->value(0);
w_settings_shell_list->do_callback();
w_settings_shell_cmd->do_callback(w_settings_shell_cmd, LOAD);
w_settings_shell_toolbox->do_callback(w_settings_shell_toolbox, LOAD);
g_shell_config->rebuild_shell_menu();
}}
xywh {148 200 24 22} labelsize 11 deactivate
}
Fl_Menu_Button w_settings_shell_menu {open
xywh {172 200 24 22} labelsize 11 textsize 11
} {
MenuItem {} {
label {Import...}
callback {if (v != LOAD)
Fd_Shell_Command_List::import_from_file();}
xywh {90 90 100 20} labelsize 11
}
MenuItem {} {
label {Export selected...}
callback {if (v != LOAD)
Fd_Shell_Command_List::export_selected();}
xywh {10 10 100 20} labelsize 11 divider
}
MenuItem {} {
label {Import Example Scripts:}
xywh {20 20 100 20} labelfont 1 labelsize 10 deactivate
}
MenuItem {} {
label {Compile with fltk-config}
xywh {30 30 100 20} labelsize 11
}
MenuItem {} {
label {Build and run}
xywh {40 40 100 20} labelsize 11
}
MenuItem {} {
label {Build with Xcode on macOS}
xywh {50 50 100 20} labelsize 11
}
MenuItem {} {
label {Build with CMake}
xywh {60 60 100 20} labelsize 11
}
}
Fl_Button w_settings_shell_play {
label Run
callback {int selected = w_settings_shell_list_selected;
if (v==LOAD) {
if (selected) {
o->activate();
} else {
o->deactivate();
}
} else {
if (!selected) return;
Fd_Shell_Command *cmd = g_shell_config->list[selected-1];
cmd->run();
}}
xywh {270 200 50 22} labelsize 11 deactivate
}
}
Fl_Return_Button {} {
label Run
callback {if (v != LOAD)
do_shell_command(NULL, NULL);}
tooltip {save selected files and run the command} xywh {100 162 100 20} labelsize 11
Fl_Group w_settings_shell_cmd {
callback {if (v==LOAD) {
int selected = w_settings_shell_list_selected;
if (selected) {
o->activate();
} else {
o->deactivate();
}
propagate_load(o, v);
}} open
xywh {10 235 320 291}
} {
Fl_Input {} {
label {Name:}
callback {int selected = w_settings_shell_list_selected;
if (v == LOAD) {
if (selected) {
o->value(g_shell_config->list[selected-1]->name.c_str());
} else {
o->value("");
}
} else {
if (selected) {
Fd_Shell_Command *cmd = g_shell_config->list[selected-1];
cmd->name = o->value();
w_settings_shell_list->text(selected, o->value());
if (cmd->storage == FD_STORE_PROJECT) set_modflag(1);
}
}}
xywh {100 246 220 20} labelfont 1 labelsize 11 when 13 textfont 4 textsize 11
}
Fl_Input {} {
label {Label:}
callback {int selected = w_settings_shell_list_selected;
if (v == LOAD) {
if (selected) {
o->value(g_shell_config->list[selected-1]->label.c_str());
} else {
o->value("");
}
} else {
if (selected) {
Fd_Shell_Command *cmd = g_shell_config->list[selected-1];
cmd->label = o->value();
cmd->update_shell_menu();
if (cmd->storage == FD_STORE_PROJECT) set_modflag(1);
}
}}
xywh {100 272 220 20} labelfont 1 labelsize 11 textfont 4 textsize 11
}
Fl_Button {} {
label Shortcut
callback {int selected = w_settings_shell_list_selected;
if (v == LOAD) {
if (selected) {
o->value(g_shell_config->list[selected-1]->shortcut);
o->default_value(o->value());
} else {
o->value(0);
}
} else {
if (selected) {
Fd_Shell_Command *cmd = g_shell_config->list[selected-1];
cmd->shortcut = o->value();
cmd->update_shell_menu();
if (cmd->storage == FD_STORE_PROJECT) set_modflag(1);
}
}}
xywh {100 297 130 20} labelsize 11 align 16
code0 {\#include <FL/Fl_Shortcut_Button.H>}
class Fl_Shortcut_Button
}
Fl_Choice {} {
label {Store:}
callback {int selected = w_settings_shell_list_selected;
if (v == LOAD) {
if (selected) {
Fd_Tool_Store ts = g_shell_config->list[selected-1]->storage;
o->value(o->find_item_with_argument((long)ts));
} else {
o->value(o->find_item_with_argument((long)FD_STORE_USER));
}
} else {
if (selected) {
Fd_Shell_Command *cmd = g_shell_config->list[selected-1];
Fd_Tool_Store ts = (Fd_Tool_Store)(o->mvalue()->argument());
if (cmd->storage == FD_STORE_PROJECT) set_modflag(1);
cmd->storage = ts;
//w_settings_shell_list->text(selected, cmd->name.c_str());
if (cmd->storage == FD_STORE_USER)
w_settings_shell_list->icon(selected, w_settings_shell_fd_user->image());
else if (cmd->storage == FD_STORE_PROJECT)
w_settings_shell_list->icon(selected, w_settings_shell_fd_project->image());
if (cmd->storage == FD_STORE_PROJECT) set_modflag(1);
}
}} open
xywh {100 322 130 20} down_box BORDER_BOX labelfont 1 labelsize 11 textsize 11
} {
MenuItem {} {
label {@fd_user User Setting}
user_data FD_STORE_USER user_data_type long
xywh {0 0 100 20} labelsize 11
}
MenuItem {} {
label {@fd_project Project File}
user_data FD_STORE_PROJECT user_data_type long
xywh {0 0 100 20} labelsize 11
}
}
Fl_Choice {} {
label {Condition:}
callback {int selected = w_settings_shell_list_selected;
if (v == LOAD) {
if (selected) {
int cond = g_shell_config->list[selected-1]->condition;
o->value(o->find_item_with_argument(cond));
} else {
o->value(o->find_item_with_argument(0));
}
} else {
if (selected) {
Fd_Shell_Command *cmd = g_shell_config->list[selected-1];
int cond = (int)(o->mvalue()->argument());
cmd->condition = cond;
g_shell_config->rebuild_shell_menu();
if (cmd->storage == FD_STORE_PROJECT) set_modflag(1);
}
}} open
xywh {100 348 130 20} down_box BORDER_BOX labelfont 1 labelsize 11 textsize 11
} {
MenuItem {} {
label {all platforms}
user_data {Fd_Shell_Command::ALWAYS} user_data_type long
xywh {0 0 100 20} labelsize 11
}
MenuItem {} {
label {MS Windows only}
user_data {Fd_Shell_Command::WIN_ONLY} user_data_type long
xywh {0 0 100 20} labelsize 11
}
MenuItem {} {
label {Linux only}
user_data {Fd_Shell_Command::UX_ONLY} user_data_type long
xywh {0 0 100 20} labelsize 11
}
MenuItem {} {
label {macOS only}
user_data {Fd_Shell_Command::MAC_ONLY} user_data_type long
xywh {0 0 100 20} labelsize 11
}
MenuItem {} {
label {Linux and macOS}
user_data {Fd_Shell_Command::MAC_AND_UX_ONLY} user_data_type long
xywh {0 0 100 20} labelsize 11
}
MenuItem {} {
label {don't use}
user_data {Fd_Shell_Command::NEVER} user_data_type long
xywh {0 0 100 20} labelsize 11
}
}
Fl_Input {} {
label {Label:}
callback {if (v == LOAD) {
// o->value(g_shell_command.c_str());
} else {
// g_shell_command = o->value();
}}
xywh {230 348 90 20} labelfont 1 labelsize 11 textfont 4 textsize 11 hide
}
Fl_Text_Editor w_settings_shell_command {
label {Shell script:}
callback {int selected = w_settings_shell_list_selected;
if (v == LOAD) {
if (selected) {
o->buffer()->text(g_shell_config->list[selected-1]->command.c_str());
} else {
o->buffer()->text("");
}
} else {
if (selected) {
Fd_Shell_Command *cmd = g_shell_config->list[selected-1];
cmd->command = o->buffer()->text();
if (cmd->storage == FD_STORE_PROJECT) set_modflag(1);
}
}}
xywh {100 373 196 80} labelfont 1 labelsize 11 align 4 textfont 4 textsize 12
code0 {o->buffer(new Fl_Text_Buffer);}
}
Fl_Group {} {open
xywh {296 373 24 44}
} {
Fl_Menu_Button w_settings_shell_text_macros {
callback {const Fl_Menu_Item *mi = o->mvalue();
if (mi) {
char buffer[256];
fl_strlcpy(buffer, mi->label(), 255);
int n = (int)strlen(buffer)-1;
if (buffer[n]=='@') buffer[n] = 0;
char *word = buffer;
if (word[0]=='@') word++;
if (w_settings_shell_command->buffer()->selected()) {
int start = 0, end = 0;
w_settings_shell_command->buffer()->selection_position(&start, &end);
w_settings_shell_command->buffer()->replace(start, end, word);
} else {
int pos = w_settings_shell_command->insert_position();
w_settings_shell_command->buffer()->insert(pos, word);
}
w_settings_shell_command->do_callback(w_settings_shell_command, (void*)NULL);
}} open
xywh {296 373 24 22} labelsize 11 textsize 11
} {
MenuItem {} {
label {@@BASENAME@@}
xywh {80 80 100 20} labelfont 4 labelsize 11
}
MenuItem {} {
label {@@PROJECTFILE_PATH@@}
xywh {0 0 100 20} labelfont 4 labelsize 11
}
MenuItem {} {
label {@@PROJECTFILE_NAME@@}
xywh {10 10 100 20} labelfont 4 labelsize 11
}
MenuItem {} {
label {@@CODEFILE_PATH@@}
xywh {20 20 100 20} labelfont 4 labelsize 11
}
MenuItem {} {
label {@@CODEFILE_NAME@@}
xywh {30 30 100 20} labelfont 4 labelsize 11
}
MenuItem {} {
label {@@HEADERFILE_PATH@@}
xywh {40 40 100 20} labelfont 4 labelsize 11
}
MenuItem {} {
label {@@HEADERFILE_NAME@@}
xywh {50 50 100 20} labelfont 4 labelsize 11
}
MenuItem {} {
label {@@TEXTFILE_PATH@@}
xywh {60 60 100 20} labelfont 4 labelsize 11
}
MenuItem {} {
label {@@TEXTFILE_NAME@@}
xywh {70 70 100 20} labelfont 4 labelsize 11
}
MenuItem {} {
label {@@FLTK_CONFIG@@}
comment {Not yet implemented}
xywh {70 70 100 20} labelfont 4 labelsize 11 hide
}
MenuItem {} {
label {@@TMPDIR@@}
xywh {70 70 100 20} labelfont 4 labelsize 11
}
}
Fl_Button {} {
label {@square}
callback {if (!script_panel) make_script_panel();
script_input->buffer()->text(w_settings_shell_command->buffer()->text());
script_panel->show();
for (;;) {
Fl_Widget* w = Fl::readqueue();
if (w == script_panel_cancel) goto BREAK2;
else if (w == script_panel_ok) break;
else if (!w) Fl::wait();
}
w_settings_shell_command->buffer()->text(script_input->buffer()->text());
w_settings_shell_command->do_callback();
BREAK2:
script_panel->hide();}
tooltip {open big code editor} xywh {296 395 24 22} labelsize 11 labelcolor 49
}
}
Fl_Check_Button {} {
label {save .fl project file}
callback {int selected = w_settings_shell_list_selected;
if (v == LOAD) {
if (selected) {
o->value(g_shell_config->list[selected-1]->flags & Fd_Shell_Command::SAVE_PROJECT);
} else {
o->value(0);
}
} else {
if (selected) {
Fd_Shell_Command *cmd = g_shell_config->list[selected-1];
int v = o->value();
if (v) {
cmd->flags |= Fd_Shell_Command::SAVE_PROJECT;
} else {
cmd->flags &= ~Fd_Shell_Command::SAVE_PROJECT;
}
if (cmd->storage == FD_STORE_PROJECT) set_modflag(1);
}
}}
tooltip {save the project to the .fl file before running the command} xywh {100 458 220 20} down_box DOWN_BOX labelsize 11
}
Fl_Check_Button {} {
label {save source code}
callback {int selected = w_settings_shell_list_selected;
if (v == LOAD) {
if (selected) {
o->value(g_shell_config->list[selected-1]->flags & Fd_Shell_Command::SAVE_SOURCECODE);
} else {
o->value(0);
}
} else {
if (selected) {
Fd_Shell_Command *cmd = g_shell_config->list[selected-1];
int v = o->value();
if (v) {
cmd->flags |= Fd_Shell_Command::SAVE_SOURCECODE;
} else {
cmd->flags &= ~Fd_Shell_Command::SAVE_SOURCECODE;
}
if (cmd->storage == FD_STORE_PROJECT) set_modflag(1);
}
}}
tooltip {generate the source code and header file before running the command} xywh {100 478 220 19} down_box DOWN_BOX labelsize 11
}
Fl_Check_Button {} {
label {save i18n strings}
callback {int selected = w_settings_shell_list_selected;
if (v == LOAD) {
if (selected) {
o->value(g_shell_config->list[selected-1]->flags & Fd_Shell_Command::SAVE_STRINGS);
} else {
o->value(0);
}
} else {
if (selected) {
Fd_Shell_Command *cmd = g_shell_config->list[selected-1];
int v = o->value();
if (v) {
cmd->flags |= Fd_Shell_Command::SAVE_STRINGS;
} else {
cmd->flags &= ~Fd_Shell_Command::SAVE_STRINGS;
}
if (cmd->storage == FD_STORE_PROJECT) set_modflag(1);
}
}}
tooltip {save the internationalisation strings before running the command} xywh {100 497 220 20} down_box DOWN_BOX labelsize 11
}
}
Fl_Box w_settings_shell_fd_project {
image {pixmaps/fd_project.png} compress_image 1 bind_image 1 bind_deimage 1 xywh {20 70 16 15} labelsize 11 hide deactivate
code0 {o->image()->scale(16, 16);}
}
Fl_Box w_settings_shell_fd_user {
image {pixmaps/fd_user.png} compress_image 1 bind_image 1 bind_deimage 1 xywh {20 70 16 15} labelsize 11 hide deactivate
code0 {o->image()->scale(16, 16);}
}
}
Fl_Group w_settings_i18n_tab {
@ -953,7 +1449,10 @@ g_layout_list.update_dialogs();}
}
Fl_Button {} {
label Close
callback {settings_window->hide();}
callback {if (g_shell_config)
g_shell_config->write(fluid_prefs, FD_STORE_USER);
g_layout_list.write(fluid_prefs, FD_STORE_USER);
settings_window->hide();}
tooltip {Close this dialog.} xywh {230 550 100 20} labelsize 11
}
}

View File

@ -35,14 +35,23 @@ void init_scheme(void);
extern struct Fl_Menu_Item *dbmanager_item;
extern void i18n_cb(Fl_Choice *,void *);
extern void scheme_cb(Fl_Scheme_Choice *, void *);
extern int w_settings_shell_list_selected;
#include <FL/Fl_Double_Window.H>
extern Fl_Double_Window *script_panel;
#include <FL/Fl_Text_Editor.H>
extern Fl_Text_Editor *script_input;
#include <FL/Fl_Group.H>
#include <FL/Fl_Return_Button.H>
extern Fl_Return_Button *script_panel_ok;
#include <FL/Fl_Button.H>
extern Fl_Button *script_panel_cancel;
#include <FL/Fl_Box.H>
Fl_Double_Window* make_script_panel();
extern Fl_Double_Window *settings_window;
#include <FL/Fl_Tabs.H>
extern Fl_Tabs *w_settings_tabs;
#include <FL/Fl_Group.H>
extern void scheme_cb(Fl_Scheme_Choice*, void*);
extern Fl_Scheme_Choice *scheme_choice;
#include <FL/Fl_Box.H>
#include <FL/Fl_Check_Button.H>
extern Fl_Check_Button *tooltips_button;
extern Fl_Check_Button *completion_button;
@ -68,7 +77,6 @@ extern Fl_Check_Button *avoid_early_includes_button;
extern Fl_Group *w_settings_layout_tab;
#include <FL/Fl_Choice.H>
extern Fl_Choice *layout_choice;
#include <FL/Fl_Button.H>
#include <FL/Fl_Menu_Button.H>
extern Fl_Menu_Button *w_layout_menu;
#include <FL/Fl_Native_File_Chooser.H>
@ -78,8 +86,19 @@ extern Fl_Button *preset_choice[3];
#include <FL/Fl_Value_Input.H>
extern Fl_Menu_Item fontmenu_w_default[];
extern Fl_Group *w_settings_shell_tab;
extern Fl_Check_Button *shell_use_fl_button;
#include <FL/Fl_Return_Button.H>
#include <FL/Fl_Browser.H>
extern Fl_Browser *w_settings_shell_list;
extern Fl_Group *w_settings_shell_toolbox;
extern Fl_Button *w_settings_shell_dup;
extern Fl_Button *w_settings_shell_remove;
extern Fl_Menu_Button *w_settings_shell_menu;
extern Fl_Button *w_settings_shell_play;
extern Fl_Group *w_settings_shell_cmd;
#include <FL/Fl_Shortcut_Button.H>
extern Fl_Text_Editor *w_settings_shell_command;
extern Fl_Menu_Button *w_settings_shell_text_macros;
extern Fl_Box *w_settings_shell_fd_project;
extern Fl_Box *w_settings_shell_fd_user;
extern Fl_Group *w_settings_i18n_tab;
extern void i18n_type_cb(Fl_Choice*, void*);
extern Fl_Choice *i18n_type_chooser;
@ -102,6 +121,10 @@ extern Fl_Menu_Item *w_layout_menu_storage[4];
#define w_layout_menu_load (menu_w_layout_menu+5)
#define w_layout_menu_save (menu_w_layout_menu+6)
#define w_layout_menu_delete (menu_w_layout_menu+7)
extern Fl_Menu_Item menu_w_settings_shell_menu[];
extern Fl_Menu_Item menu_Store[];
extern Fl_Menu_Item menu_Condition[];
extern Fl_Menu_Item menu_w_settings_shell_text_macros[];
extern Fl_Menu_Item menu_i18n_type_chooser[];
extern Fl_Double_Window *shell_run_window;
#include <FL/Fl_Simple_Terminal.H>

View File

@ -137,7 +137,7 @@ int Fd_Project_Reader::open_read(const char *s) {
fin = stdin;
fname = "stdin";
} else {
FILE *f = fl_fopen(s,"r");
FILE *f = fl_fopen(s, "r");
if (!f)
return 0;
fin = f;
@ -325,29 +325,12 @@ void Fd_Project_Reader::read_children(Fl_Type *p, int paste, Strategy strategy,
goto CONTINUE;
}
if (strcmp(c, "win_shell_cmd")==0) {
if (shell_settings_windows.command)
free((void*)shell_settings_windows.command);
shell_settings_windows.command = fl_strdup(read_word());
goto CONTINUE;
} else if (strcmp(c, "win_shell_flags")==0) {
shell_settings_windows.flags = atoi(read_word());
goto CONTINUE;
} else if (strcmp(c, "linux_shell_cmd")==0) {
if (shell_settings_linux.command)
free((void*)shell_settings_linux.command);
shell_settings_linux.command = fl_strdup(read_word());
goto CONTINUE;
} else if (strcmp(c, "linux_shell_flags")==0) {
shell_settings_linux.flags = atoi(read_word());
goto CONTINUE;
} else if (strcmp(c, "mac_shell_cmd")==0) {
if (shell_settings_macos.command)
free((void*)shell_settings_macos.command);
shell_settings_macos.command = fl_strdup(read_word());
goto CONTINUE;
} else if (strcmp(c, "mac_shell_flags")==0) {
shell_settings_macos.flags = atoi(read_word());
if (strcmp(c, "shell_commands")==0) {
if (g_shell_config) {
g_shell_config->read(this);
} else {
read_word();
}
goto CONTINUE;
}
}
@ -431,7 +414,10 @@ int Fd_Project_Reader::read_project(const char *filename, int merge, Strategy st
}
}
selection_changed(Fl_Type::current);
shell_settings_read();
if (g_shell_config) {
g_shell_config->rebuild_shell_menu();
g_shell_config->update_settings_dialog();
}
int ret = close_read();
undo_resume();
return ret;
@ -816,23 +802,8 @@ int Fd_Project_Writer::write_project(const char *filename, int selected_only) {
write_string("\nheader_name"); write_word(g_project.header_file_name.c_str());
write_string("\ncode_name"); write_word(g_project.code_file_name.c_str());
g_layout_list.write(this);
#if 0
// https://github.com/fltk/fltk/issues/328
// Project wide settings require a redesign.
shell_settings_write();
if (shell_settings_windows.command) {
write_string("\nwin_shell_cmd"); write_word(shell_settings_windows.command);
write_string("\nwin_shell_flags"); write_string("%d", shell_settings_windows.flags);
}
if (shell_settings_linux.command) {
write_string("\nlinux_shell_cmd"); write_word(shell_settings_linux.command);
write_string("\nlinux_shell_flags"); write_string("%d", shell_settings_linux.flags);
}
if (shell_settings_macos.command) {
write_string("\nmac_shell_cmd"); write_word(shell_settings_macos.command);
write_string("\nmac_shell_flags"); write_string("%d", shell_settings_macos.flags);
}
#endif
if (g_shell_config)
g_shell_config->write(this);
}
for (Fl_Type *p = Fl_Type::first; p;) {

View File

@ -172,6 +172,9 @@ Fl_String g_code_filename_arg;
Fl_String g_header_filename_arg;
Fl_String g_launch_path;
Fl_String tmpdir_path;
bool tmpdir_create_called = false;
/** \var int Fluid_Project::header_file_set
If set, command line overrides header file name in .fl file.
*/
@ -311,10 +314,6 @@ void Fluid_Project::reset() {
code_file_set = 0;
header_file_name = ".h";
code_file_name = ".cxx";
g_layout_list.remove_all(FD_STORE_PROJECT);
g_layout_list.current_suite(0);
g_layout_list.current_preset(0);
}
void Fluid_Project::update_settings_dialog() {
@ -324,6 +323,113 @@ void Fluid_Project::update_settings_dialog() {
}
}
// make sure that a path name ends with a forward slash
static Fl_String end_with_slash(const Fl_String &str) {
char last = str[str.size()-1];
if (last !='/' && last != '\\')
return str + "/";
else
return str;
}
/** Generate a path to a directory for temporary data storage.
The path is stored in g_tmpdir.
*/
static void create_tmpdir() {
if (tmpdir_create_called)
return;
tmpdir_create_called = true;
char buf[128];
#if _WIN32
// The usual temp file locations on Windows are
// %system%\Windows\Temp
// %userprofiles%\AppData\Local
// usually resolving into
// C:/Windows/Temp/
// C:\Users\<username>\AppData\Local\Temp
fl_snprintf(buf, sizeof(buf)-1, "fluid-%d/", (long)GetCurrentProcessId());
Fl_String name = buf;
wchar_t tempdirW[FL_PATH_MAX+1];
char tempdir[FL_PATH_MAX+1];
unsigned len = GetTempPathW(FL_PATH_MAX, tempdirW);
if (len == 0) {
strcpy(tempdir, "c:/windows/temp/");
} else {
unsigned wn = fl_utf8fromwc(tempdir, FL_PATH_MAX, tempdirW, len);
tempdir[wn] = 0;
}
Fl_String path = tempdir;
end_with_slash(path);
path += name;
fl_make_path(path.c_str());
if (fl_access(path.c_str(), 6) == 0) tmpdir_path = path;
#else
fl_snprintf(buf, sizeof(buf)-1, "fluid-%d/", getpid());
Fl_String name = buf;
Fl_String path = fl_getenv("TMPDIR");
if (!path.empty()) {
end_with_slash(path);
path += name;
fl_make_path(path.c_str());
if (fl_access(path.c_str(), 6) == 0) tmpdir_path = path;
}
if (tmpdir_path.empty()) {
path = Fl_String("/tmp/") + name;
fl_make_path(path.c_str());
if (fl_access(path.c_str(), 6) == 0) tmpdir_path = path;
}
#endif
if (tmpdir_path.empty()) {
char pbuf[FL_PATH_MAX+1];
fluid_prefs.get_userdata_path(pbuf, FL_PATH_MAX);
path = Fl_String(pbuf);
end_with_slash(path);
path += name;
fl_make_path(path.c_str());
if (fl_access(path.c_str(), 6) == 0) tmpdir_path = path;
}
if (tmpdir_path.empty())
fl_alert("Can't create directory for temporary data storage.");
}
/** Delete the temporary directory that was created in set_tmpdir. */
static void delete_tmpdir() {
// was a temporary directory created
if (!tmpdir_create_called)
return;
if (tmpdir_path.empty())
return;
// first delete all files that may still be left in the temp directory
struct dirent **de;
int n_de = fl_filename_list(tmpdir_path.c_str(), &de);
if (n_de >= 0) {
for (int i=0; i<n_de; i++) {
Fl_String path = tmpdir_path + de[i]->d_name;
fl_unlink(path.c_str());
}
fl_filename_free_list(&de, n_de);
}
// then delete the directory itself
if (fl_rmdir(tmpdir_path.c_str()) < 0) {
fl_alert("WARNING: Can't delete tmpdir '%s': %s", tmpdir_path.c_str(), strerror(errno));
}
}
/**
Return the path to a temporary directory for this instance of FLUID.
Fluid will do its best to clear and delete this directory when exiting.
\return the path to the temporary directory, ending in a '/', or and empty
string is no directory could be created.
*/
const Fl_String &get_tmpdir() {
if (!tmpdir_create_called)
create_tmpdir();
return tmpdir_path;
}
/**
Give the user the opportunity to save a project before clearing it.
@ -397,7 +503,7 @@ void enter_project_dir() {
// store the current working directory for later
app_work_dir = fl_getcwd();
// set the current directory to the path of our .fl file
Fl_String project_path = fl_filename_path(fl_filename_absolute(Fl_String(filename)));
Fl_String project_path = fl_filename_path(fl_filename_absolute(filename));
if (fl_chdir(project_path.c_str()) == -1) {
fprintf(stderr, "** Fluid internal error: enter_project_dir() can't chdir to %s: %s\n",
project_path.c_str(), strerror(errno));
@ -733,6 +839,8 @@ void exit_cb(Fl_Widget *,void *) {
if (help_dialog)
delete help_dialog;
if (g_shell_config)
g_shell_config->write(fluid_prefs, FD_STORE_USER);
g_layout_list.write(fluid_prefs, FD_STORE_USER);
undo_clear();
@ -742,6 +850,7 @@ void exit_cb(Fl_Widget *,void *) {
// and cleans up editor tmp files. Then remove fluid tmpdir /last/.
g_project.reset();
ExternalCodeEditor::tmpdir_clear();
delete_tmpdir();
exit(0);
}
@ -989,15 +1098,6 @@ void apple_open_cb(const char *c) {
}
#endif // __APPLE__
// make sure that a path nae ends with a forward slash
static Fl_String end_with_slash(const Fl_String &str) {
char last = str[str.size()-1];
if (last !='/' && last != '\\')
return str + "/";
else
return str;
}
/**
Get the absolute path of the project file, for example `/Users/matt/dev/`.
*/
@ -1585,10 +1685,7 @@ Fl_Menu_Item Main_Menu[] = {
{"Dialog", 0, select_layout_preset_cb, (void*)1, FL_MENU_RADIO },
{"Toolbox", 0, select_layout_preset_cb, (void*)2, FL_MENU_RADIO },
{0},
{"&Shell",0,0,0,FL_SUBMENU},
{"Execute &Command...",FL_ALT+'x',(Fl_Callback *)show_shell_window},
{"Execute &Again...",FL_ALT+'g',(Fl_Callback *)do_shell_command},
{0},
{"&Shell", 0, Fd_Shell_Command_List::menu_marker, (void*)Fd_Shell_Command_List::default_menu, FL_SUBMENU_POINTER},
{"&Help",0,0,0,FL_SUBMENU},
{"&Rapid development with FLUID...",0,help_cb},
{"&FLTK Programmers Manual...",0,manual_cb, 0, FL_MENU_DIVIDER},
@ -1710,7 +1807,6 @@ void make_main_window() {
fluid_prefs.get("show_guides", show_guides, 1);
fluid_prefs.get("show_restricted", show_restricted, 1);
fluid_prefs.get("show_comments", show_comments, 1);
shell_prefs_get();
make_shell_window();
}
@ -1740,6 +1836,9 @@ void make_main_window() {
if (!batch_mode) {
load_history();
g_shell_config = new Fd_Shell_Command_List;
// TODO: load example commands if this is the very first time we use this
// g_shell_config->restore_defaults();
make_settings_window();
}
}
@ -1851,6 +1950,7 @@ void set_filename(const char *c) {
set_modflag(modflag);
}
/**
Set the "modified" flag and update the title of the main window.
@ -2044,6 +2144,8 @@ int main(int argc,char **argv) {
main_window->show(argc,argv);
toggle_widgetbin_cb(0,0);
toggle_sourceview_cb(0,0);
if (g_shell_config)
g_shell_config->read(fluid_prefs, FD_STORE_USER);
g_layout_list.read(fluid_prefs, FD_STORE_USER);
if (!c && openlast_button->value() && absolute_history[0][0]) {
// Open previous file when no file specified...

View File

@ -28,6 +28,8 @@
#define MENUHEIGHT 25
#define WINHEIGHT (BROWSERHEIGHT+MENUHEIGHT)
// ---- types
class Fl_Double_Window;
class Fl_Window;
class Fl_Menu_Bar;
@ -36,6 +38,19 @@ class Fl_Choice;
class Fl_Button;
class Fl_Check_Button;
/**
Indicate the storage location for tools like layout suites and shell macros.
\see class Fd_Shell_Command, class Fd_Layout_Suite
*/
typedef enum {
FD_STORE_INTERNAL, ///< stored inside FLUID app
FD_STORE_USER, ///< suite is stored in the user wide FLUID settings
FD_STORE_PROJECT, ///< suite is stored within the current .fl project file
FD_STORE_FILE ///< store suite in external file
} Fd_Tool_Store;
// ---- global variables
extern int force_parent;
extern Fl_Preferences fluid_prefs;
@ -71,9 +86,6 @@ extern Fl_Check_Button *guides_button;
extern int modflag;
extern void enter_project_dir();
extern void leave_project_dir();
extern int update_file; // fluid -u
extern int compile_file; // fluid -c
extern int compile_strings; // fluic -cs
@ -81,7 +93,11 @@ extern int batch_mode;
extern int pasteoffset;
// ---- project settings
extern Fl_String g_code_filename_arg;
extern Fl_String g_header_filename_arg;
extern Fl_String g_launch_path;
// ---- project class declaration
class Fluid_Project {
public:
@ -123,16 +139,15 @@ public:
extern Fluid_Project g_project;
extern Fl_String g_code_filename_arg;
extern Fl_String g_header_filename_arg;
extern Fl_String g_launch_path;
// ---- public functions
extern void enter_project_dir();
extern void leave_project_dir();
extern void set_filename(const char *c);
extern void set_modflag(int mf, int mfc=-1);
extern const Fl_String &get_tmpdir();
// ---- public callback functions
extern void save_cb(Fl_Widget *, void *v);

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

BIN
fluid/pixmaps/fd_user.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@ -1,7 +1,7 @@
//
// FLUID main entry for the Fast Light Tool Kit (FLTK).
//
// Copyright 1998-2021 by Bill Spitzak and others.
// Copyright 1998-2023 by Bill Spitzak and others.
//
// This library is free software. Distribution and use rights are outlined in
// the file "COPYING" which should have been included with this file. If this
@ -14,125 +14,122 @@
// https://www.fltk.org/bugs.php
//
// in progress:
// FLUID comes with example shell commands to build the current project file
// and run the project. This is accomplished by calling `fltk-config` on the
// files generated by FLUID, and by calling the executable directly.
//
// If the user wants more complex commands, he can add or modify them in the
// "Shell" settings panel. Modified shell commands are saved with the .fl
// file.
// The Shell panel has a list of shell commands in the upper half. Under the
// list are buttons to add, duplicate, and delete shell commands. A popup
// menu offers import and export functionality and a list of sample scripts.
// We may want to add up and down buttons, so the user can change the
// order of commands.
// Selecting any shell command in the list fills in and activates a list of
// options in the lower half of the panel. Those settings are:
// - Name: the name of the shell command in the list
// - Label: the label in the pulldown menu (could be the same as name?)
// - Shortcut: shortcut key to launch the command
// - Storage: where to store this shell command
// - Condition: pulldown menu to make the entry conditional for various
// target platforms, for example, a "Windows only" entry would only be added
// to the Shell menu on a Windows machine. Other options could be:
// - Linux only, macOS only, never (to make a list header!?), inactive?
// - Command: a multiline input for the actual shell command
// - Variables: a pulldown menu that insert variable names like $<sourcefile>
// - options to save project, code, and strings before running
// - test-run button
// TODO: add @APPDIR@?
// TODO: get a macro to find `fltk-config` @FLTK_CONFIG@
// TODO: add an input field so the user can insert their preferred file and path for fltk-config (user setting)
// `fltk-config` is actually tricky to find
// for live builds, we could check the program launch directory
// if we know where build/Xcode/bin/Debug/fluid is, we
// may or may not find ./build/Xcode/fltk-config
// on macOS with homebrew, we find /opt/homebrew/bin/fltk-config but the user
// can set their own install path.
// We can query the shell path, but that requires knowing the users shell (echo $SHELL).
// We can run the shell as a login shell with `-l`, so the user $PTH is set: /bin/bash -l -c 'fltk-config'
// The shell should output the path of the fltk-config that it found and why it is using that one.
// This can also output the fltk-config version.
// TODO: add a bunch of sensible sample shell commands
// TODO: when this new feature is used for the very first time, import two or three samples as initial user setting
// TODO: make the settings dialog resizable
// TODO: make g_shell_config static, not a pointer, but don't load anything in batch mode
// FEATURE: Fd_Tool_Store icons are currently redundant with @file and @save and could be improved
// FEATURE: hostname, username, getenv support?
// FEATURE: ad the files ./fluid.prefs and ./fluid.user.prefs as tool locations
/*
Some ideas:
default shell is in $SHELL on linux and macOS
On macOS, we can write Apple Scripts:
#!/usr/bin/env osascript
say "@BASENAME@"
osascript <<EOD
say "spark"
EOD
osascript <<EOD
tell application "Xcode"
build workspace document 1
end tell
EOD
*/
#include "shell_command.h"
#include "fluid.h"
#include "file.h"
#include "alignment_panel.h"
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Menu_Bar.H>
#include <FL/fl_message.H>
#include <FL/fl_string_functions.h>
#include <errno.h>
static Fl_String fltk_config_cmd;
static Fl_Process s_proc;
/// Shell settings in the .fl file
Shell_Settings shell_settings_windows = { };
Shell_Settings shell_settings_linux = { };
Shell_Settings shell_settings_macos = { };
/// Current shell command, stored in .fl file for each platform, and in app prefs
Fl_String g_shell_command;
/// Save .fl file before running, stored in .fl file for each platform, and in app prefs
int g_shell_save_fl = 1;
/// Save code file before running, stored in .fl file for each platform, and in app prefs
int g_shell_save_code = 1;
/// Save strings file before running, stored in .fl file for each platform, and in app prefs
int g_shell_save_strings = 0;
/// Use these settings from .fl files, stored in app prefs
int g_shell_use_fl_settings = 1;
/**
Read the default shell settings from the app preferences.
*/
void shell_prefs_get()
{
fluid_prefs.get("shell_command", g_shell_command, "echo \"Custom Shell Command\"");
fluid_prefs.get("shell_savefl", g_shell_save_fl, 1);
fluid_prefs.get("shell_writecode", g_shell_save_code, 1);
fluid_prefs.get("shell_writemsgs", g_shell_save_strings, 0);
fluid_prefs.get("shell_use_fl", g_shell_use_fl_settings, 1);
}
/**
Write the current shell settings to the app preferences.
*/
void shell_prefs_set()
{
fluid_prefs.set("shell_command", g_shell_command);
fluid_prefs.set("shell_savefl", g_shell_save_fl);
fluid_prefs.set("shell_writecode", g_shell_save_code);
fluid_prefs.set("shell_writemsgs", g_shell_save_strings);
fluid_prefs.set("shell_use_fl", g_shell_use_fl_settings);
}
/**
Copy shell settings from the .fl buffer if use_fl_settings is set.
*/
void shell_settings_read()
{
if (g_shell_use_fl_settings==0)
return;
#if defined(_WIN32)
Shell_Settings &shell_settings = shell_settings_windows;
#elif defined(__APPLE__)
Shell_Settings &shell_settings = shell_settings_macos;
#else
Shell_Settings &shell_settings = shell_settings_linux;
#endif
g_shell_command = shell_settings.command;
g_shell_save_fl = ((shell_settings.flags&1)==1);
g_shell_save_code = ((shell_settings.flags&2)==2);
g_shell_save_strings = ((shell_settings.flags&4)==4);
}
/**
Copy current shell settings to the .fl buffer if use_fl_settings is set.
*/
void shell_settings_write()
{
if (g_shell_use_fl_settings==0)
return;
#if defined(_WIN32)
Shell_Settings &shell_settings = shell_settings_windows;
#elif defined(__APPLE__)
Shell_Settings &shell_settings = shell_settings_macos;
#else
Shell_Settings &shell_settings = shell_settings_linux;
#endif
if (shell_settings.command)
free((void*)shell_settings.command);
shell_settings.command = NULL;
if (!g_shell_command.empty())
shell_settings.command = fl_strdup(g_shell_command.c_str());
shell_settings.flags = 0;
if (g_shell_save_fl)
shell_settings.flags |= 1;
if (g_shell_save_code)
shell_settings.flags |= 2;
if (g_shell_save_strings)
shell_settings.flags |= 4;
}
/** \class Fl_Process
\todo Explain.
Launch an external shell command.
*/
/**
Create a process manager
*/
Fl_Process::Fl_Process() {
_fpt= NULL;
}
/**
Destroy the project manager.
*/
Fl_Process::~Fl_Process() {
// TODO: check what we need to do if a task is still running
if (_fpt) close();
}
// FIXME: popen needs the UTF-8 equivalent fl_popen
// portable open process:
/**
Open a process.
\param[in] cmd the shell command that we want to run
\param[in] mode "r" or "w" for creating a stream that can read or write
\return a stream that is redirected from the shell command stdout
*/
FILE * Fl_Process::popen(const char *cmd, const char *mode) {
#if defined(_WIN32) && !defined(__CYGWIN__)
// PRECONDITIONS
@ -174,6 +171,9 @@ FILE * Fl_Process::popen(const char *cmd, const char *mode) {
#endif
}
/**
Close the current process.
*/
int Fl_Process::close() {
#if defined(_WIN32) && !defined(__CYGWIN__)
if (_fpt) {
@ -192,11 +192,22 @@ int Fl_Process::close() {
#endif
}
// non-null if file is open
/**
non-null if file is open.
\return the current file descriptor of the process' stdout
*/
FILE *Fl_Process::desc() const {
return _fpt;
}
/**
Receive a single line from the current process.
\param[out] line buffer to receive the line
\param[in] s size of the provided buffer
\return NULL if an error occurred, otherwise a pointer to the string
*/
char *Fl_Process::get_line(char * line, size_t s) const {
return _fpt ? fgets(line, (int)s, _fpt) : NULL;
}
@ -237,31 +248,33 @@ void Fl_Process::clean_close(HANDLE& h) {
#endif
/**
Prepare FLUID for running a shell command according to the command flags.
// Shell command support...
static bool prepare_shell_command() {
\param[in] flags set various flags to save the project, code, and string before running the command
\return false if the previous command is still running
*/
static bool prepare_shell_command(int flags) {
// settings_window->hide();
if (s_proc.desc()) {
fl_alert("Previous shell command still running!");
return false;
}
if (g_shell_command.empty()) {
fl_alert("No shell command entered!");
return false;
}
if (g_shell_save_fl) {
if (flags & Fd_Shell_Command::SAVE_PROJECT) {
save_cb(0, 0);
}
if (g_shell_save_code) {
if (flags & Fd_Shell_Command::SAVE_SOURCECODE) {
write_code_files();
}
if (g_shell_save_strings) {
if (flags & Fd_Shell_Command::SAVE_STRINGS) {
write_strings_cb(0, 0);
}
return true;
}
/**
Called by the file handler when the command is finished.
*/
void shell_proc_done() {
shell_run_terminal->append("... END SHELL COMMAND ...\n");
shell_run_button->activate();
@ -293,8 +306,56 @@ void shell_pipe_cb(FL_SOCKET, void*) {
}
}
void do_shell_command(Fl_Return_Button*, void*) {
if (!prepare_shell_command()) return;
/** Find the script `fltk-config` that most closely relates to this version of FLUID.
This is not implemented yet.
*/
//static void find_fltk_config() {
//
//}
static void expand_macro(Fl_String &cmd, const Fl_String &macro, const Fl_String &content) {
for (int i=0;;) {
i = cmd.find(macro, i);
if (i==Fl_String::npos) break;
cmd.replace(i, macro.size(), content);
}
}
static void expand_macros(Fl_String &cmd) {
expand_macro(cmd, "@BASENAME@", g_project.basename());
expand_macro(cmd, "@PROJECTFILE_PATH@", g_project.projectfile_path());
expand_macro(cmd, "@PROJECTFILE_NAME@", g_project.projectfile_name());
expand_macro(cmd, "@CODEFILE_PATH@", g_project.codefile_path());
expand_macro(cmd, "@CODEFILE_NAME@", g_project.codefile_name());
expand_macro(cmd, "@HEADERFILE_PATH@", g_project.headerfile_path());
expand_macro(cmd, "@HEADERFILE_NAME@", g_project.headerfile_name());
expand_macro(cmd, "@TEXTFILE_PATH@", g_project.stringsfile_path());
expand_macro(cmd, "@TEXTFILE_NAME@", g_project.stringsfile_name());
// TODO: implement finding the script `fltk-config` for all platforms
// if (cmd.find("@FLTK_CONFIG@") != Fl_String::npos) {
// find_fltk_config();
// expand_macro(cmd, "@FLTK_CONFIG@", fltk_config_cmd.c_str());
// }
if (cmd.find("@TMPDIR@") != Fl_String::npos)
expand_macro(cmd, "@TMPDIR@", get_tmpdir());
}
/**
Prepare for and run a shell command.
\param[in] cmd the command that is sent to `/bin/sh -c ...` or `cmd.exe` on Windows machines
\param[in] flags various flags in preparation of the command
*/
void run_shell_command(const Fl_String &cmd, int flags) {
if (cmd.empty()) {
fl_alert("No shell command entered!");
return;
}
if (!prepare_shell_command(flags)) return;
Fl_String expanded_cmd = cmd;
expand_macros(expanded_cmd);
if (!shell_run_window->visible()) {
Fl_Preferences pos(fluid_prefs, "shell_run_Window_pos");
@ -310,10 +371,10 @@ void do_shell_command(Fl_Return_Button*, void*) {
}
// Show the output window and clear things...
shell_run_terminal->printf("\033[0;32m%s\033[0m\n", g_shell_command.c_str());
shell_run_window->label(g_shell_command.c_str());
shell_run_terminal->printf("\033[0;32m%s\033[0m\n", expanded_cmd.c_str());
shell_run_window->label(expanded_cmd.c_str());
if (s_proc.popen((char *)g_shell_command.c_str()) == NULL) {
if (s_proc.popen((char *)expanded_cmd.c_str()) == NULL) {
shell_run_terminal->printf("\033[1;31mUnable to run shell command: %s\033[0m\n",
strerror(errno));
shell_run_window->label("FLUID Shell");
@ -329,28 +390,553 @@ void do_shell_command(Fl_Return_Button*, void*) {
}
/**
Show a dialog box to run an external shell command.
Copies the current settings into the dialog box.
This dialog box offers a field for a command line and three check buttons
to generate and save various files before the command is run.
If the fourth checkbox, "use settings in .fl design files" is checked,
all shell settings will be store in the current .fl file, and they will
be read and restored when the .fl is loaded again.
Fluid will save different shell settings for different operating system as
it is common that a different OS requires a different shell command.
Fluid comes with default shell settings. Pressing the "save as default" button
will store the current setting in the Fluid app settings and are used for new
designs, or if the "use settings..." box is not checked.
Fluid app settings are saved per user and per machine.
Create an empty shell command structure.
*/
void show_shell_window() {
Fd_Shell_Command::Fd_Shell_Command()
: shortcut(0),
storage(FD_STORE_USER),
condition(0),
flags(0),
shell_menu_item_(NULL)
{
}
/**
Copy the aspects of a shell command dataset into a new shell command.
\param[in] rhs copy from this prototype
*/
Fd_Shell_Command::Fd_Shell_Command(const Fd_Shell_Command *rhs)
: name(rhs->name),
label(rhs->label),
shortcut(rhs->shortcut),
storage(rhs->storage),
condition(rhs->condition),
condition_data(rhs->condition_data),
command(rhs->command),
flags(rhs->flags),
shell_menu_item_(NULL)
{
}
/**
Create a default storage for a shell command and how it is accessible in FLUID.
\param[in] name is used as a stand-in for the command name and label
*/
Fd_Shell_Command::Fd_Shell_Command(const Fl_String &in_name)
: name(in_name),
label(in_name),
shortcut(0),
storage(FD_STORE_USER),
condition(Fd_Shell_Command::ALWAYS),
command("echo \"Hello, FLUID!\""),
flags(Fd_Shell_Command::SAVE_PROJECT|Fd_Shell_Command::SAVE_SOURCECODE),
shell_menu_item_(NULL)
{
}
/**
Create a storage for a shell command and how it is accessible in FLUID.
\param[in] in_name name of this command in the command list in the settings panel
\param[in] in_label label text in the main pulldown menu
\param[in] in_shortcut a keyboard shortcut that will also appear in the main menu
\param[in] in_storage storage location for this command
\param[in] in_condition commands can be hidden for certain platforms by setting a condition
\param[in] in_condition_data more details for future conditions, i.e. per user, per host, etc.
\param[in] in_command the shell command that we want to run
\param[in] in_flags some flags to tell FLUID to save the project, code, or strings before running the command
*/
Fd_Shell_Command::Fd_Shell_Command(const Fl_String &in_name,
const Fl_String &in_label,
Fl_Shortcut in_shortcut,
Fd_Tool_Store in_storage,
int in_condition,
const Fl_String &in_condition_data,
const Fl_String &in_command,
int in_flags)
: name(in_name),
label(in_label),
shortcut(in_shortcut),
storage(in_storage),
condition(in_condition),
condition_data(in_condition_data),
command(in_command),
flags(in_flags),
shell_menu_item_(NULL)
{
}
/**
Run this command now.
Will open the Shell Panel and execute the command if no other command is
currently running.
*/
void Fd_Shell_Command::run() {
if (!command.empty())
run_shell_command(command, flags);
}
/**
Update the shell submenu in main menu with the shortcut and a copy of the label.
*/
void Fd_Shell_Command::update_shell_menu() {
if (shell_menu_item_) {
const char *old_label = shell_menu_item_->label(); // can be NULL
const char *new_label = label.c_str(); // never NULL
if (!old_label || (old_label && strcmp(old_label, new_label))) {
if (old_label) ::free((void*)old_label);
shell_menu_item_->label(fl_strdup(new_label));
}
shell_menu_item_->shortcut(shortcut);
}
}
/**
Check if the set condition is met.
\return true if this command appears in the main menu
*/
bool Fd_Shell_Command::is_active() {
switch (condition) {
case ALWAYS: return true;
case NEVER: return false;
#ifdef _WIN32
case MAC_ONLY: return false;
case UX_ONLY: return false;
case WIN_ONLY: return true;
case MAC_AND_UX_ONLY: return false;
#elif defined(__APPLE__)
case MAC_ONLY: return true;
case UX_ONLY: return false;
case WIN_ONLY: return false;
case MAC_AND_UX_ONLY: return true;
#else
case MAC_ONLY: return false;
case UX_ONLY: return true;
case WIN_ONLY: return false;
case MAC_AND_UX_ONLY: return true;
#endif
case USER_ONLY: return false; // TODO: get user name
case HOST_ONLY: return false; // TODO: get host name
case ENV_ONLY: {
const char *value = fl_getenv(condition_data.c_str());
if (value && *value) return true;
return false;
}
}
return false;
}
void Fd_Shell_Command::read(Fl_Preferences &prefs) {
int tmp;
prefs.get("name", name, "<unnamed>");
prefs.get("label", label, "<no label>");
prefs.get("shortcut", tmp, 0);
shortcut = (Fl_Shortcut)tmp;
prefs.get("storage", tmp, -1);
if (tmp != -1) storage = (Fd_Tool_Store)tmp;
prefs.get("condition", condition, ALWAYS);
prefs.get("condition_data", condition_data, "");
prefs.get("command", command, "");
prefs.get("flags", flags, 0);
}
void Fd_Shell_Command::write(Fl_Preferences &prefs, bool save_location) {
prefs.set("name", name);
prefs.set("label", label);
if (shortcut != 0) prefs.set("shortcut", (int)shortcut);
if (save_location) prefs.set("storage", (int)storage);
if (condition != ALWAYS) prefs.set("condition", condition);
if (!condition_data.empty()) prefs.set("condition_data", condition_data);
if (!command.empty()) prefs.set("command", command);
if (flags != 0) prefs.set("flags", flags);
}
void Fd_Shell_Command::read(class Fd_Project_Reader *in) {
const char *c = in->read_word(1);
if (strcmp(c, "{")!=0) return; // expecting start of group
storage = FD_STORE_PROJECT;
for (;;) {
c = in->read_word(1);
if (strcmp(c, "}")==0) break; // end of command list
else if (strcmp(c, "name")==0)
name = in->read_word();
else if (strcmp(c, "label")==0)
label = in->read_word();
else if (strcmp(c, "shortcut")==0)
shortcut = in->read_int();
else if (strcmp(c, "condition")==0)
condition = in->read_int();
else if (strcmp(c, "condition_data")==0)
condition_data = in->read_word();
else if (strcmp(c, "command")==0)
command = in->read_word();
else if (strcmp(c, "flags")==0)
flags = in->read_int();
else
in->read_word(); // skip an unknown word
}
}
void Fd_Shell_Command::write(class Fd_Project_Writer *out) {
out->write_string("\n command {");
out->write_string("\n name "); out->write_word(name.c_str());
out->write_string("\n label "); out->write_word(label.c_str());
if (shortcut) out->write_string("\n shortcut %d", shortcut);
if (condition) out->write_string("\n condition %d", condition);
if (!condition_data.empty()) {
out->write_string("\n condition_data "); out->write_word(condition_data.c_str());
}
if (!command.empty()) {
out->write_string("\n command "); out->write_word(command.c_str());
}
if (flags) out->write_string("\n flags %d", flags);
out->write_string("\n }");
}
/**
Manage a list of shell commands and their parameters.
*/
Fd_Shell_Command_List::Fd_Shell_Command_List()
: list(NULL),
list_size(0),
list_capacity(0),
shell_menu_(NULL)
{
}
/**
Release all shell commands and destroy this class.
*/
Fd_Shell_Command_List::~Fd_Shell_Command_List() {
clear();
}
/**
Return the shell command at the given index.
\param[in] index must be between 0 and list_size-1
\return a pointer to the shell command data
*/
Fd_Shell_Command *Fd_Shell_Command_List::at(int index) const {
return list[index];
}
/**
Clear all shell commands.
*/
void Fd_Shell_Command_List::clear() {
if (list) {
for (int i=0; i<list_size; i++) {
delete list[i];
}
::free(list);
list_size = 0;
list_capacity = 0;
list = 0;
}
}
/**
remove all shell commands of the given storage location from the list.
*/
void Fd_Shell_Command_List::clear(Fd_Tool_Store storage) {
for (int i=list_size-1; i>=0; i--) {
if (list[i]->storage == storage) {
remove(i);
}
}
}
/**
Read shell configuration from a preferences group.
*/
void Fd_Shell_Command_List::read(Fl_Preferences &prefs, Fd_Tool_Store storage) {
// import the old shell commands from previous user settings
if (&fluid_prefs == &prefs) {
int version;
prefs.get("shell_commands_version", version, 0);
if (version == 0) {
int save_fl, save_code, save_strings;
Fd_Shell_Command *cmd = new Fd_Shell_Command();
cmd->storage = FD_STORE_USER;
cmd->name = "Sample Shell Command";
cmd->label = "Sample Shell Command";
cmd->shortcut = FL_ALT+'g';
fluid_prefs.get("shell_command", cmd->command, "echo \"Sample Shell Command\"");
fluid_prefs.get("shell_savefl", save_fl, 1);
fluid_prefs.get("shell_writecode", save_code, 1);
fluid_prefs.get("shell_writemsgs", save_strings, 0);
if (save_fl) cmd->flags |= Fd_Shell_Command::SAVE_PROJECT;
if (save_code) cmd->flags |= Fd_Shell_Command::SAVE_SOURCECODE;
if (save_strings) cmd->flags |= Fd_Shell_Command::SAVE_STRINGS;
add(cmd);
}
version = 1;
prefs.set("shell_commands_version", version);
}
Fl_Preferences shell_commands(prefs, "shell_commands");
int n = shell_commands.groups();
for (int i=0; i<n; i++) {
Fl_Preferences cmd_prefs(shell_commands, Fl_Preferences::Name(i));
Fd_Shell_Command *cmd = new Fd_Shell_Command();
cmd->storage = FD_STORE_USER;
cmd->read(cmd_prefs);
add(cmd);
}
}
/**
Write shell configuration to a preferences group.
*/
void Fd_Shell_Command_List::write(Fl_Preferences &prefs, Fd_Tool_Store storage) {
Fl_Preferences shell_commands(prefs, "shell_commands");
shell_commands.delete_all_groups();
int index = 0;
for (int i=0; i<list_size; i++) {
if (list[i]->storage == FD_STORE_USER) {
Fl_Preferences cmd(shell_commands, Fl_Preferences::Name(index++));
list[i]->write(cmd);
}
}
}
/**
Read shell configuration from a project file.
*/
void Fd_Shell_Command_List::read(Fd_Project_Reader *in) {
const char *c = in->read_word(1);
if (strcmp(c, "{")!=0) return; // expecting start of group
clear(FD_STORE_PROJECT);
for (;;) {
c = in->read_word(1);
if (strcmp(c, "}")==0) break; // end of command list
else if (strcmp(c, "command")==0) {
Fd_Shell_Command *cmd = new Fd_Shell_Command();
add(cmd);
cmd->read(in);
} else {
in->read_word(); // skip an unknown group
}
}
}
/**
Write shell configuration to a project file.
*/
void Fd_Shell_Command_List::write(Fd_Project_Writer *out) {
int n_in_project_file = 0;
for (int i=0; i<list_size; i++) {
if (list[i]->storage == FD_STORE_PROJECT)
n_in_project_file++;
}
if (n_in_project_file > 0) {
out->write_string("\nshell_commands {");
for (int i=0; i<list_size; i++) {
if (list[i]->storage == FD_STORE_PROJECT)
list[i]->write(out);
}
out->write_string("\n}");
}
}
/**
Add a previously created shell command to the end of the list.
\param[in] cmd a pointer to the command that we want to add
*/
void Fd_Shell_Command_List::add(Fd_Shell_Command *cmd) {
if (list_size == list_capacity) {
list_capacity += 16;
list = (Fd_Shell_Command**)::realloc(list, list_capacity * sizeof(Fd_Shell_Command**));
}
list[list_size++] = cmd;
}
/**
Insert a newly created shell command at the given position in the list.
\param[in] index must be between 0 and list_size-1
\param[in] cmd a pointer to the command that we want to add
*/
void Fd_Shell_Command_List::insert(int index, Fd_Shell_Command *cmd) {
if (list_size == list_capacity) {
list_capacity += 16;
list = (Fd_Shell_Command**)::realloc(list, list_capacity * sizeof(Fd_Shell_Command**));
}
::memmove(list+index+1, list+index, (list_size-index)*sizeof(Fd_Shell_Command**));
list_size++;
list[index] = cmd;
}
/**
Remove and delete the command at the given index.
\param[in] index must be between 0 and list_size-1
*/
void Fd_Shell_Command_List::remove(int index) {
delete list[index];
list_size--;
::memmove(list+index, list+index+1, (list_size-index)*sizeof(Fd_Shell_Command**));
}
/**
This is called whenever the user clicks a shell command menu in the main menu.
\param[in] u cast tp long to get the index of the shell command
*/
void menu_shell_cmd_cb(Fl_Widget*, void *u) {
long index = (long)(fl_intptr_t)u;
g_shell_config->list[index]->run();
}
/**
This is called when the user selects the menu to edit the shell commands.
It pops up the setting panel at the shell settings tab.
*/
void menu_shell_customize_cb(Fl_Widget*, void*) {
settings_window->show();
w_settings_tabs->value(w_settings_shell_tab);
}
/**
Rebuild the entire shell submenu from scratch and replace the old menu.
*/
void Fd_Shell_Command_List::rebuild_shell_menu() {
static Fl_Menu_Item *shell_submenu = NULL;
if (!shell_submenu)
shell_submenu = (Fl_Menu_Item*)main_menubar->find_item(menu_marker);
int i, j, num_active_items = 0;
// count the active commands
for (i=0; i<list_size; i++) {
if (list[i]->is_active()) num_active_items++;
}
// allocate a menu item array
Fl_Menu_Item *mi = (Fl_Menu_Item*)::calloc(num_active_items+2, sizeof(Fl_Menu_Item));
// set the menu item pointer for all active commands
for (i=j=0; i<list_size; i++) {
Fd_Shell_Command *cmd = list[i];
if (cmd->is_active()) {
cmd->shell_menu_item_ = mi + j;
mi[j].callback(menu_shell_cmd_cb);
mi[j].argument(i);
cmd->update_shell_menu();
j++;
}
}
if (j>0) mi[j-1].flags |= FL_MENU_DIVIDER;
mi[j].label(fl_strdup("Customize..."));
mi[j].shortcut(FL_ALT+'x');
mi[j].callback(menu_shell_customize_cb);
// replace the old menu array with the new one
Fl_Menu_Item *mi_old = shell_menu_;
shell_menu_ = mi;
shell_submenu->user_data(shell_menu_);
// free all resources from the old menu
if (mi_old && (mi_old != default_menu)) {
for (i=0; ; i++) {
const char *label = mi_old[i].label();
if (!label) break;
::free((void*)label);
}
::free(mi_old);
}
}
/**
Tell the settings dialog to query this list and update its GUI elements.
*/
void Fd_Shell_Command_List::update_settings_dialog() {
if (w_settings_shell_tab)
w_settings_shell_tab->do_callback(w_settings_shell_tab, LOAD);
}
/**
The default shell submenu in batch mode.
*/
Fl_Menu_Item Fd_Shell_Command_List::default_menu[] = {
{ "Customize...", FL_ALT+'x', menu_shell_customize_cb },
{ NULL }
};
/**
Used to find the shell submenu within the main menu tree.
*/
void Fd_Shell_Command_List::menu_marker(Fl_Widget*, void*) {
// intentionally left empty
}
/**
Export all selected shell commands to an external file.
Verify that g_shell_config and w_settings_shell_list are not NULL. Open a
file chooser and export all items that are selected in w_settings_shell_list
into an external file.
*/
void Fd_Shell_Command_List::export_selected() {
if (!g_shell_config || (g_shell_config->list_size == 0)) return;
if (!w_settings_shell_list) return;
Fl_Native_File_Chooser dialog;
dialog.title("Export selected shell commands:");
dialog.type(Fl_Native_File_Chooser::BROWSE_SAVE_FILE);
dialog.filter("FLUID Files\t*.flcmd\n");
dialog.directory(g_project.projectfile_path().c_str());
dialog.preset_file((g_project.basename() + ".flcmd").c_str());
if (dialog.show() != 0) return;
Fl_Preferences file(dialog.filename(), "flcmd.fluid.fltk.org", NULL, (Fl_Preferences::Root)(Fl_Preferences::C_LOCALE|Fl_Preferences::CLEAR));
Fl_Preferences shell_commands(file, "shell_commands");
int i, index = 0, n = w_settings_shell_list->size();
for (i = 0; i < n; i++) {
if (w_settings_shell_list->selected(i+1)) {
Fl_Preferences cmd(shell_commands, Fl_Preferences::Name(index++));
g_shell_config->list[i]->write(cmd, true);
}
}
}
/**
Import shell commands from an external file and add them to the list.
Verify that g_shell_config and w_settings_shell_list are not NULL. Open a
file chooser and import all items.
*/
void Fd_Shell_Command_List::import_from_file() {
if (!g_shell_config || (g_shell_config->list_size == 0)) return;
if (!w_settings_shell_list) return;
Fl_Native_File_Chooser dialog;
dialog.title("Import shell commands:");
dialog.type(Fl_Native_File_Chooser::BROWSE_FILE);
dialog.filter("FLUID Files\t*.flcmd\n");
dialog.directory(g_project.projectfile_path().c_str());
dialog.preset_file((g_project.basename() + ".flcmd").c_str());
if (dialog.show() != 0) return;
Fl_Preferences file(dialog.filename(), "flcmd.fluid.fltk.org", NULL, Fl_Preferences::C_LOCALE);
Fl_Preferences shell_commands(file, "shell_commands");
int i, n = shell_commands.groups();
for (i = 0; i < n; i++) {
Fl_Preferences cmd_prefs(shell_commands, Fl_Preferences::Name(i));
Fd_Shell_Command *cmd = new Fd_Shell_Command();
cmd->storage = FD_STORE_USER;
cmd->read(cmd_prefs);
g_shell_config->add(cmd);
}
w_settings_shell_list->do_callback(w_settings_shell_list, LOAD);
w_settings_shell_cmd->do_callback(w_settings_shell_cmd, LOAD);
w_settings_shell_toolbox->do_callback(w_settings_shell_toolbox, LOAD);
g_shell_config->rebuild_shell_menu();
}
/**
A pointer to the list of shell commands if we are not in batch mode.
*/
Fd_Shell_Command_List *g_shell_config = NULL;

View File

@ -1,7 +1,7 @@
//
// FLUID main entry for the Fast Light Tool Kit (FLTK).
//
// Copyright 1998-2021 by Bill Spitzak and others.
// Copyright 1998-2023 by Bill Spitzak and others.
//
// This library is free software. Distribution and use rights are outlined in
// the file "COPYING" which should have been included with this file. If this
@ -17,11 +17,13 @@
#ifndef _FLUID_SHELL_COMMAND_H
#define _FLUID_SHELL_COMMAND_H
#include <stdio.h>
#include <stdlib.h>
#include "fluid.h"
#include <FL/Fl_String.H>
#include <FL/Enumerations.H>
#include <stdio.h>
#include <stdlib.h>
#if defined(_WIN32) && !defined(__CYGWIN__)
# include <direct.h>
# include <windows.h>
@ -33,29 +35,10 @@
# include <unistd.h>
#endif
void show_shell_window();
void do_shell_command(class Fl_Return_Button*, void*);
typedef struct {
char *command;
int flags;
} Shell_Settings;
extern Shell_Settings shell_settings_windows;
extern Shell_Settings shell_settings_linux;
extern Shell_Settings shell_settings_macos;
extern Fl_String g_shell_command;
extern int g_shell_save_fl;
extern int g_shell_save_code;
extern int g_shell_save_strings;
extern int g_shell_use_fl_settings;
void shell_prefs_get();
void shell_prefs_set();
void shell_settings_read();
void shell_settings_write();
struct Fl_Menu_Item;
class Fl_Widget;
void run_shell_command(const Fl_String &cmd, int flags);
class Fl_Process {
public:
@ -88,4 +71,71 @@ protected:
FILE * _fpt;
};
class Fd_Shell_Command {
public:
enum { ALWAYS, NEVER, MAC_ONLY, UX_ONLY, WIN_ONLY, MAC_AND_UX_ONLY, USER_ONLY, HOST_ONLY, ENV_ONLY }; // conditions
enum { SAVE_PROJECT = 1, SAVE_SOURCECODE = 2, SAVE_STRINGS = 4, SAVE_ALL = 7 }; // flags
Fd_Shell_Command();
Fd_Shell_Command(const Fd_Shell_Command *rhs);
Fd_Shell_Command(const Fl_String &in_name);
Fd_Shell_Command(const Fl_String &in_name,
const Fl_String &in_label,
Fl_Shortcut in_shortcut,
Fd_Tool_Store in_storage,
int in_condition,
const Fl_String &in_condition_data,
const Fl_String &in_command,
int in_flags);
Fl_String name;
Fl_String label;
Fl_Shortcut shortcut;
Fd_Tool_Store storage;
int condition; // always, hide, windows only, linux only, mac only, user, machine
Fl_String condition_data; // user name, machine name
Fl_String command;
int flags; // save_project, save_code, save_string, ...
Fl_Menu_Item *shell_menu_item_;
void run();
void read(Fl_Preferences &prefs);
void write(Fl_Preferences &prefs, bool save_location = false);
void read(class Fd_Project_Reader*);
void write(class Fd_Project_Writer*);
void update_shell_menu();
bool is_active();
};
class Fd_Shell_Command_List {
public:
Fd_Shell_Command **list;
int list_size;
int list_capacity;
Fl_Menu_Item *shell_menu_;
public:
Fd_Shell_Command_List();
~Fd_Shell_Command_List();
Fd_Shell_Command *at(int index) const;
void add(Fd_Shell_Command *cmd);
void insert(int index, Fd_Shell_Command *cmd);
void remove(int index);
void clear();
void clear(Fd_Tool_Store store);
// void move_up();
// void move_down();
// int load(const Fl_String &filename);
// int save(const Fl_String &filename);
void read(Fl_Preferences &prefs, Fd_Tool_Store storage);
void write(Fl_Preferences &prefs, Fd_Tool_Store storage);
void read(class Fd_Project_Reader*);
void write(class Fd_Project_Writer*);
void rebuild_shell_menu();
void update_settings_dialog();
static Fl_Menu_Item default_menu[];
static void menu_marker(Fl_Widget*, void*);
static void export_selected();
static void import_from_file();
};
extern Fd_Shell_Command_List *g_shell_config;
#endif // _FLUID_SHELL_COMMAND_H

View File

@ -87,17 +87,17 @@ void update_sourceview_cb(class Fl_Button*, void*) {
if (!sv_source_filename) {
sv_source_filename = (char*)malloc(FL_PATH_MAX);
fluid_prefs.getUserdataPath(sv_source_filename, FL_PATH_MAX);
fl_strlcpy(sv_source_filename, get_tmpdir().c_str(), FL_PATH_MAX);
fl_strlcat(sv_source_filename, "source_view_tmp.cxx", FL_PATH_MAX);
}
if (!sv_header_filename) {
sv_header_filename = (char*)malloc(FL_PATH_MAX);
fluid_prefs.getUserdataPath(sv_header_filename, FL_PATH_MAX);
fl_strlcpy(sv_header_filename, get_tmpdir().c_str(), FL_PATH_MAX);
fl_strlcat(sv_header_filename, "source_view_tmp.h", FL_PATH_MAX);
}
if (!sv_design_filename) {
sv_design_filename = (char*)malloc(FL_PATH_MAX);
fluid_prefs.getUserdataPath(sv_design_filename, FL_PATH_MAX);
fl_strlcpy(sv_design_filename, get_tmpdir().c_str(), FL_PATH_MAX);
fl_strlcat(sv_design_filename, "source_view_tmp.fl", FL_PATH_MAX);
}
@ -108,8 +108,9 @@ void update_sourceview_cb(class Fl_Button*, void*) {
sv_project->scroll(top, 0);
} else if (sv_strings->visible_r()) {
static const char *exts[] = { ".txt", ".po", ".msg" };
char fn[FL_PATH_MAX];
fluid_prefs.getUserdataPath(fn, FL_PATH_MAX);
char fn[FL_PATH_MAX+1];
fl_strlcpy(fn, get_tmpdir().c_str(), FL_PATH_MAX);
fl_strlcat(fn, "strings", FL_PATH_MAX);
fl_filename_setext(fn, FL_PATH_MAX, exts[g_project.i18n_type]);
write_strings(fn);
int top = sv_strings->top_line();

View File

@ -102,17 +102,17 @@ and load those into the Code Viewer widgets.} open return_type void
if (!sv_source_filename) {
sv_source_filename = (char*)malloc(FL_PATH_MAX);
fluid_prefs.getUserdataPath(sv_source_filename, FL_PATH_MAX);
fl_strlcpy(sv_source_filename, get_tmpdir().c_str(), FL_PATH_MAX);
fl_strlcat(sv_source_filename, "source_view_tmp.cxx", FL_PATH_MAX);
}
if (!sv_header_filename) {
sv_header_filename = (char*)malloc(FL_PATH_MAX);
fluid_prefs.getUserdataPath(sv_header_filename, FL_PATH_MAX);
fl_strlcpy(sv_header_filename, get_tmpdir().c_str(), FL_PATH_MAX);
fl_strlcat(sv_header_filename, "source_view_tmp.h", FL_PATH_MAX);
}
if (!sv_design_filename) {
sv_design_filename = (char*)malloc(FL_PATH_MAX);
fluid_prefs.getUserdataPath(sv_design_filename, FL_PATH_MAX);
fl_strlcpy(sv_design_filename, get_tmpdir().c_str(), FL_PATH_MAX);
fl_strlcat(sv_design_filename, "source_view_tmp.fl", FL_PATH_MAX);
}
@ -123,8 +123,9 @@ and load those into the Code Viewer widgets.} open return_type void
sv_project->scroll(top, 0);
} else if (sv_strings->visible_r()) {
static const char *exts[] = { ".txt", ".po", ".msg" };
char fn[FL_PATH_MAX];
fluid_prefs.getUserdataPath(fn, FL_PATH_MAX);
char fn[FL_PATH_MAX+1];
fl_strlcpy(fn, get_tmpdir().c_str(), FL_PATH_MAX);
fl_strlcat(fn, "strings", FL_PATH_MAX);
fl_filename_setext(fn, FL_PATH_MAX, exts[g_project.i18n_type]);
write_strings(fn);
int top = sv_strings->top_line();

View File

@ -233,7 +233,9 @@ Fl_Preferences::Root Fl_Preferences::filename( char *buffer, size_t buffer_size,
<tt>\<null\>/Library/Preferences/\$(vendor)/\$(application).prefs</tt>,
which would silently fail to create a preference file.
\param[in] root can be \c USER_L or \c SYSTEM_L for user specific or system wide preferences
\param[in] root can be \c USER_L or \c SYSTEM_L for user specific or system
wide preferences, add the CLEAR flag to start with a clean set of
preferences instead of reading them from the database
\param[in] vendor unique text describing the company or author of this file, must be a valid filepath segment
\param[in] application unique text describing the application, must be a valid filepath segment
@ -246,19 +248,41 @@ Fl_Preferences::Fl_Preferences( Root root, const char *vendor, const char *appli
}
/**
\brief Use this constructor to create or read a preference file at an
arbitrary position in the file system.
\brief Deprecated: Use this constructor to create or read a preference file at an
arbitrary position in the file system.
The file name is generated in the form <tt>\$(path)/\$(application).prefs</tt>.
If \p application is \c NULL, \p path is taken literally as the file path and name.
This constructor should no longer be used because the generated database uses
the current locale, making it impossible to exchange floating point settings
between machines with different language settings.
\param[in] path path to the directory that contains the preference file
\param[in] vendor unique text describing the company or author of this file, must be a valid filepath segment
\param[in] application unique text describing the application, must be a valid filepath segment
Use `Fl_Preferences(path, vendor, application, C_LOCALE)` in new projects and
`Fl_Preferences(path, vendor, application, 0)` if you must keep backward
compatibility.
\see Fl_Preferences( const char *path, const char *vendor, const char *application, Root flags )
*/
Fl_Preferences::Fl_Preferences( const char *path, const char *vendor, const char *application ) {
node = new Node( "." );
rootNode = new RootNode( this, path, vendor, application );
rootNode = new RootNode( this, path, vendor, application, (Root)0 );
node->setRoot(rootNode);
}
/**
\brief Use this constructor to create or read a preference file at an
arbitrary position in the file system.
The file name is generated in the form <tt>\$(path)/\$(application).prefs</tt>.
If \p application is \c NULL, \p path is taken literally as the file path and name.
\param[in] path path to the directory that contains the preference file
\param[in] vendor unique text describing the company or author of this file, must be a valid filepath segment
\param[in] application unique text describing the application, must be a valid filename or NULL
\param[in] set C_LOCALE to make the preferences file independent of the current locale,
add the CLEAR flag to start with a clean set of preferences instead of reading from the database
*/
Fl_Preferences::Fl_Preferences( const char *path, const char *vendor, const char *application, Root flags ) {
node = new Node( "." );
rootNode = new RootNode( this, path, vendor, application, flags );
node->setRoot(rootNode);
}
@ -1168,23 +1192,24 @@ Fl_Preferences::RootNode::RootNode( Fl_Preferences *prefs, Root root, const char
filename_(0L),
vendor_(0L),
application_(0L),
root_type_(root)
root_type_((Root)(root & ~CLEAR))
{
char *filename = Fl::system_driver()->preference_rootnode(prefs, root, vendor, application);
filename_ = filename ? fl_strdup(filename) : 0L;
vendor_ = fl_strdup(vendor);
application_ = fl_strdup(application);
read();
if ( (root & CLEAR) == 0 )
read();
}
// create the root node
// - construct the name of the file that will hold our preferences
Fl_Preferences::RootNode::RootNode( Fl_Preferences *prefs, const char *path, const char *vendor, const char *application )
Fl_Preferences::RootNode::RootNode( Fl_Preferences *prefs, const char *path, const char *vendor, const char *application, Root flags )
: prefs_(prefs),
filename_(0L),
vendor_(0L),
application_(0L),
root_type_(Fl_Preferences::USER)
root_type_( (Root)(USER | (flags & C_LOCALE) ))
{
if (!vendor)
@ -1199,7 +1224,8 @@ Fl_Preferences::RootNode::RootNode( Fl_Preferences *prefs, const char *path, con
}
vendor_ = fl_strdup(vendor);
application_ = fl_strdup(application);
read();
if ( (flags & CLEAR) == 0 )
read();
}
// create a root node that exists only on RAM and can not be read or written to

View File

@ -367,3 +367,4 @@ Fl_String fl_getcwd() {
fl_getcwd(buffer, FL_PATH_MAX);
return Fl_String(buffer);
}

View File

@ -392,7 +392,7 @@ tree->clear_changed();} open
tooltip {Test tree} xywh {15 22 320 539} box DOWN_BOX color 55 selection_color 15
class Fl_Tree
} {}
Fl_Group {} {open selected
Fl_Group {} {open
xywh {350 5 681 556}
code0 {o->resizable(0);}
} {
@ -780,7 +780,7 @@ Fl::visible_focus(onoff);}
}
}
Fl_Group {} {
label {Test Operations}
label {Test Operations} open selected
tooltip {These controls only affect the defaults for new items that are created. These test the Fl_Tree_Prefs methods.} xywh {350 435 330 125} box GTK_DOWN_BOX color 47 labelsize 12
} {
Fl_Group showitem_box {
@ -847,7 +847,7 @@ tree->redraw();}
callback {const char *filename = fl_file_chooser("Select a Preferences style Database", "Preferences(*.prefs)", 0L);
if (filename) {
tree->clear();
Fl_Preferences prefs(filename, 0L, 0L);
Fl_Preferences prefs(filename, 0L, 0L, Fl_Preferences::C_LOCALE);
tree->load(prefs);
tree->redraw();
}}

View File

@ -278,7 +278,7 @@ TEST(fl_filename, relative) {
r = fl_filename_relative("../foo.txt", base);
EXPECT_STREQ(r.c_str(), "../foo.txt");
return true;
}
}
TEST(fl_filename, absolute) {
Fl_String base = "/var/tmp/somedir";
@ -293,6 +293,7 @@ TEST(fl_filename, absolute) {
return true;
}
bool cb1a_ok = false, cb1b_ok = false, cb1c_ok = false;
int cb1_alloc = 0;
class MyString : public Fl_String {