summaryrefslogtreecommitdiff
path: root/fluid/shell_command.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'fluid/shell_command.cxx')
-rw-r--r--fluid/shell_command.cxx832
1 files changed, 709 insertions, 123 deletions
diff --git a/fluid/shell_command.cxx b/fluid/shell_command.cxx
index cd764ca24..c557c6e54 100644
--- a/fluid/shell_command.cxx
+++ b/fluid/shell_command.cxx
@@ -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.
+/** \class Fl_Process
+ Launch an external shell command.
*/
-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.
+ Create a process manager
*/
-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);
+Fl_Process::Fl_Process() {
+ _fpt= NULL;
}
/**
- Copy shell settings from the .fl buffer if use_fl_settings is set.
+ Destroy the project manager.
*/
-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);
+Fl_Process::~Fl_Process() {
+ // TODO: check what we need to do if a task is still running
+ if (_fpt) close();
}
/**
- 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;
-}
+ Open a process.
-/** \class Fl_Process
- \todo Explain.
+ \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
*/
-
-Fl_Process::Fl_Process() {
- _fpt= NULL;
-}
-
-Fl_Process::~Fl_Process() {
- if (_fpt) close();
-}
-
-// FIXME: popen needs the UTF-8 equivalent fl_popen
-// portable open process:
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.
+ Create an empty shell command structure.
+ */
+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);
+ }
+ }
+}
- Copies the current settings into the dialog box.
+/**
+ 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);
+ }
+}
- 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.
+/**
+ 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);
+ }
+ }
+}
- 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.
+/**
+ 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
+ }
+ }
+}
- Fluid will save different shell settings for different operating system as
- it is common that a different OS requires a different shell command.
+/**
+ 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}");
+ }
+}
- 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.
+/**
+ Add a previously created shell command to the end of the list.
- Fluid app settings are saved per user and per machine.
+ \param[in] cmd a pointer to the command that we want to add
*/
-void show_shell_window() {
+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;
+