diff --git a/README.md b/README.md
deleted file mode 100644
index 6c9a5ec..0000000
--- a/README.md
+++ /dev/null
@@ -1,2 +0,0 @@
-# ToolsMenu
-
diff --git a/Readme b/Readme
new file mode 100755
index 0000000..d6283f9
--- /dev/null
+++ b/Readme
@@ -0,0 +1,101 @@
+ToolsMenu - Add tools to the Workbench Tools menu
+
+Copyright (C) 2015, 2018 Kim Fastrup Larsen
+
+This program is free software: you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, either ver-
+sion 3 of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be use-
+ful, but WITHOUT ANY WARRANTY; without even the implied war-
+ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public Li-
+cense along with this program. If not, see
+.
+
+The author can be contacted on
+
+
+New in version 1.6:
+
+- Minor UI adjustments
+- New, "proper" requester for About
+- Modularized the source code
+
+
+ToolsMenu is a commodity that will let you add tools to the Tools menu on
+your Workbench screen. Once a tool has been added, you can run it by
+selecting it from the menu. Any icons selected on the Workbench at that
+moment become arguments for the tool.
+
+For example, if you add MultiView, you can then select the icon of an image
+on the Workbench, then select MultiView from the menu to view that image.
+
+Tools will be run as if started by the Workbench. You will not be able to
+run tools that do not have a proper tool icons.
+
+Adding tools should be pretty self explanatory. Drag tool icons into the
+window to add them, after which you can edit their names if you like. You
+can also click the "New" button to manually enter a tool. You then write the
+name of the tool (as you would like it to appear in the menu) under the
+list, and type in the full path of the tool over on the right where it says
+"Tool". The path must include the name of the tool's executable file.
+
+When you are happy with your selection of tools, you must remember to save
+them from the pull down menu. The definitions will be saved as tool types
+in the commodity's icon.
+
+ToolsMenu requires Kickstart 2 or later.
+
+The source code is included in the hope that it will be useful to those out
+there who still enjoy programming for the Amiga. Elements of interest
+include:
+
+- How to use Catcomp so a project can be localized easily
+- How to implement a commodity sporting a GUI
+- How to implement a dynamic GUI using Gadtools
+- How to read and write tool types
+- How to support dragging icons into a window
+- How to add items to the Workbench Tools menu
+- How to load and run other programs as DOS processes
+- How to pretend to be Workbench
+
+
+History:
+
+1.6 (Nov 26, 2018)
+
+- Minor UI adjustments
+- New, "proper" requester for About
+- Modularized the source code
+
+1.5 (Jul 28, 2015)
+
+- Busy pointer for Kickstart 2
+- Optimized and cleaned up a few things, made code more portable
+
+1.4 (Apr 22, 2015)
+
+- Improved layout for big fonts.
+- Added "About..."
+
+1.3 (Feb 12, 2015)
+
+- Fixed an issue that caused some commodities (and possibly other tools) to
+ crash.
+
+1.2 (Feb 3, 2015)
+
+- Use the user's preferred screen font.
+
+1.1 (Jan 19, 2015)
+
+- Fixed an issue where trying to run a tool with an empty path would cause
+ a Guru Meditation.
+
+1.0 (Jan 12, 2015)
+
+- Initial release
diff --git a/Resources/ToolsMenu.info b/Resources/ToolsMenu.info
new file mode 100755
index 0000000..2abcd72
Binary files /dev/null and b/Resources/ToolsMenu.info differ
diff --git a/SCOPTIONS b/SCOPTIONS
new file mode 100755
index 0000000..6164b1b
--- /dev/null
+++ b/SCOPTIONS
@@ -0,0 +1,12 @@
+PARAMETERS=REGISTERS
+ANSI
+SHORTINTEGERS
+NOSTACKCHECK
+STRINGMERGE
+ERRORREXX
+OPTIMIZE
+OPTIMIZERINLINELOCAL
+NOVERSION
+MEMORYSIZE=HUGE
+DEFINE USE_PRAGMAS
+IGNORE=306
diff --git a/args.c b/args.c
new file mode 100755
index 0000000..5de22e8
--- /dev/null
+++ b/args.c
@@ -0,0 +1,96 @@
+/*
+ToolsMenu - Add tools to the Workbench Tools menu
+
+Copyright (C) 2015, 2018 Kim Fastrup Larsen
+
+This program is free software: you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, either ver-
+sion 3 of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be use-
+ful, but WITHOUT ANY WARRANTY; without even the implied war-
+ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public Li-
+cense along with this program. If not, see
+.
+
+The author can be contacted on
+*/
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#ifdef USE_PRAGMAS
+#include
+#include
+#endif
+
+#include
+#include
+
+#include "args.h"
+
+#define DEF_CX_PRIORITY 0
+#define DEF_CX_POPUP YES
+#define DEF_CX_POPKEY "control alt t"
+
+Args args;
+
+static UBYTE **tool_types;
+static struct RDArgs *rdargs;
+
+static void get_wb_args(char **argv)
+{
+ struct WBArg *wb_arg = ((struct WBStartup *) argv)->sm_ArgList;
+
+ args.exe.dir = wb_arg->wa_Lock;
+ args.exe.filename = wb_arg->wa_Name;
+ if ((tool_types = ArgArrayInit(0, argv)) == NULL)
+ exit(RETURN_FAIL);
+ args.cx_priority = ArgInt(tool_types, "CX_PRIORITY", DEF_CX_PRIORITY);
+ args.cx_popup = strcmp(ArgString(tool_types, "CX_POPUP", DEF_CX_POPUP ?
+ "YES" : "NO"), "NO") != 0;
+ args.cx_popkey = ArgString(tool_types, "CX_POPKEY", DEF_CX_POPKEY);
+}
+
+static void get_shell_args(char **argv)
+{
+ LONG sh_args[3];
+
+ args.exe.dir = ((struct Process *) FindTask(NULL))->pr_CurrentDir;
+ args.exe.filename = argv[0];
+ sh_args[0] = DEF_CX_PRIORITY;
+ sh_args[1] = (LONG) DEF_CX_POPKEY;
+ sh_args[2] = DEF_CX_POPUP ? (LONG) "YES" : (LONG) "NO";
+ if ((rdargs = ReadArgs(TEMPLATE, sh_args, NULL)) == NULL) {
+ PrintFault(IoErr(), argv[0]);
+ exit(RETURN_ERROR);
+ }
+ args.cx_priority = sh_args[0];
+ args.cx_popkey = (STRPTR) sh_args[1];
+ args.cx_popup = strcmp((char *) sh_args[2], "YES") == 0;
+}
+
+void args_set_up(int argc, char **argv)
+{
+ if (argc == 0)
+ get_wb_args(argv);
+ else
+ get_shell_args(argv);
+}
+
+void args_clean_up()
+{
+ if (rdargs != NULL)
+ FreeArgs(rdargs);
+ if (tool_types != NULL)
+ ArgArrayDone();
+}
diff --git a/args.h b/args.h
new file mode 100755
index 0000000..d37431f
--- /dev/null
+++ b/args.h
@@ -0,0 +1,46 @@
+/*
+ToolsMenu - Add tools to the Workbench Tools menu
+
+Copyright (C) 2015, 2018 Kim Fastrup Larsen
+
+This program is free software: you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, either ver-
+sion 3 of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be use-
+ful, but WITHOUT ANY WARRANTY; without even the implied war-
+ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public Li-
+cense along with this program. If not, see
+.
+
+The author can be contacted on
+*/
+
+#ifndef ARGS_H
+#define ARGS_H
+
+#include
+#include
+
+#include "common.h"
+
+typedef struct {
+ struct {
+ BPTR dir;
+ char *filename;
+ } exe;
+ BYTE cx_priority;
+ Bool cx_popup;
+ STRPTR cx_popkey;
+} Args;
+
+extern Args args;
+
+void args_set_up(int argc, char **argv);
+void args_clean_up(void);
+
+#endif
diff --git a/broker.c b/broker.c
new file mode 100755
index 0000000..cafacec
--- /dev/null
+++ b/broker.c
@@ -0,0 +1,135 @@
+/*
+ToolsMenu - Add tools to the Workbench Tools menu
+
+Copyright (C) 2015, 2018 Kim Fastrup Larsen
+
+This program is free software: you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, either ver-
+sion 3 of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be use-
+ful, but WITHOUT ANY WARRANTY; without even the implied war-
+ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public Li-
+cense along with this program. If not, see
+.
+
+The author can be contacted on
+*/
+
+#include
+#include
+
+#include
+#include
+
+#ifdef USE_PRAGMAS
+#include
+#include
+#endif
+
+#include
+#include
+
+#define CATCOMP_NUMBERS
+
+#include "args.h"
+#include "broker.h"
+#include "catalog.h"
+#include "common.h"
+#include "cx.h"
+#include "error.h"
+#include "strings.h"
+
+static struct MsgPort *broker_port;
+static CxObj *broker;
+
+ULONG broker_set_up()
+{
+ struct NewBroker new_broker;
+ LONG error;
+ CxObj *filter;
+
+ if ((broker_port = CreateMsgPort()) == NULL)
+ exit(RETURN_FAIL);
+ memset(&new_broker, 0, sizeof new_broker);
+ new_broker.nb_Version = NB_VERSION;
+ new_broker.nb_Name = APP_NAME;
+ new_broker.nb_Title = TITLE;
+ new_broker.nb_Descr = get_string(MSG_DESCRIPTION);
+ new_broker.nb_Unique = NBU_UNIQUE | NBU_NOTIFY;
+ new_broker.nb_Flags = COF_SHOW_HIDE;
+ new_broker.nb_Pri = args.cx_priority;
+ new_broker.nb_Port = broker_port;
+ if ((broker = CxBroker(&new_broker, &error)) == NULL)
+ exit((error == CBERR_DUP) ? RETURN_OK : RETURN_FAIL);
+ if ((filter = CxFilter(args.cx_popkey)) == NULL)
+ exit(RETURN_FAIL);
+ AttachCxObj(broker, filter);
+ AttachCxObj(filter, CxSender(broker_port, 0));
+ AttachCxObj(filter, CxTranslate(NULL));
+ if ((error = CxObjError(filter)) != 0) {
+ if (error == COERR_BADFILTER) {
+ error_message(get_string(MSG_ERR_HOTKEY), NULL);
+ exit(RETURN_ERROR);
+ } else
+ exit(RETURN_FAIL);
+ }
+ ActivateCxObj(broker, 1);
+ return 1UL << broker_port->mp_SigBit;
+}
+
+static void handle_command(ULONG msg_id)
+{
+ switch (msg_id) {
+ case CXCMD_DISABLE:
+ ActivateCxObj(broker, 0);
+ cx_disable();
+ break;
+ case CXCMD_ENABLE:
+ ActivateCxObj(broker, 1);
+ cx_enable();
+ break;
+ case CXCMD_KILL:
+ cx_quit();
+ break;
+ case CXCMD_UNIQUE:
+ case CXCMD_APPEAR:
+ cx_show();
+ break;
+ case CXCMD_DISAPPEAR:
+ cx_hide();
+ break;
+ }
+}
+
+void broker_handle_signal()
+{
+ CxMsg *msg;
+ ULONG msg_type, msg_id;
+
+ while ((msg = (CxMsg *) GetMsg(broker_port)) != NULL) {
+ msg_type = CxMsgType(msg);
+ msg_id = CxMsgID(msg);
+ ReplyMsg((struct Message *) msg);
+ switch (msg_type) {
+ case CXM_IEVENT:
+ /* Must be Hot Key event */
+ cx_show();
+ break;
+ case CXM_COMMAND:
+ handle_command(msg_id);
+ break;
+ }
+ }
+}
+
+void broker_clean_up()
+{
+ if (broker != NULL)
+ DeleteCxObjAll(broker);
+ delete_port(broker_port);
+}
diff --git a/broker.h b/broker.h
new file mode 100755
index 0000000..8fa848e
--- /dev/null
+++ b/broker.h
@@ -0,0 +1,32 @@
+/*
+ToolsMenu - Add tools to the Workbench Tools menu
+
+Copyright (C) 2015, 2018 Kim Fastrup Larsen
+
+This program is free software: you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, either ver-
+sion 3 of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be use-
+ful, but WITHOUT ANY WARRANTY; without even the implied war-
+ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public Li-
+cense along with this program. If not, see
+.
+
+The author can be contacted on
+*/
+
+#ifndef BROKER_H
+#define BROKER_H
+
+#include
+
+ULONG broker_set_up(void);
+void broker_handle_signal(void);
+void broker_clean_up(void);
+
+#endif
diff --git a/catalog.c b/catalog.c
new file mode 100755
index 0000000..a09e7dc
--- /dev/null
+++ b/catalog.c
@@ -0,0 +1,67 @@
+/*
+ToolsMenu - Add tools to the Workbench Tools menu
+
+Copyright (C) 2015, 2018 Kim Fastrup Larsen
+
+This program is free software: you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, either ver-
+sion 3 of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be use-
+ful, but WITHOUT ANY WARRANTY; without even the implied war-
+ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public Li-
+cense along with this program. If not, see
+.
+
+The author can be contacted on
+*/
+
+#include
+
+#include
+
+#ifdef USE_PRAGMAS
+#include
+#endif
+
+#include "catalog.h"
+
+#define CATCOMP_BLOCK
+#include "strings.h"
+
+extern struct Library *LocaleBase;
+
+static APTR catalog;
+
+void open_catalog(STRPTR name)
+{
+ if (LocaleBase != NULL)
+ catalog = OpenCatalog(NULL, name, TAG_DONE);
+}
+
+STRPTR get_string(ULONG id)
+{
+ LONG *l;
+ UWORD *w;
+ STRPTR built_in;
+
+ l = (LONG *) CatCompBlock;
+ while (*l != id) {
+ w = (UWORD *) ((ULONG) l + 4);
+ l = (LONG *) ((ULONG) l + (ULONG) *w + 6);
+ }
+ built_in = (STRPTR) ((ULONG) l + 6);
+ return (catalog != NULL) ? GetCatalogStr(catalog,id,built_in) : built_in;
+}
+
+void close_catalog()
+{
+ if (LocaleBase != NULL) {
+ CloseCatalog(catalog);
+ catalog = NULL;
+ }
+}
diff --git a/catalog.h b/catalog.h
new file mode 100755
index 0000000..05dfc27
--- /dev/null
+++ b/catalog.h
@@ -0,0 +1,32 @@
+/*
+ToolsMenu - Add tools to the Workbench Tools menu
+
+Copyright (C) 2015, 2018 Kim Fastrup Larsen
+
+This program is free software: you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, either ver-
+sion 3 of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be use-
+ful, but WITHOUT ANY WARRANTY; without even the implied war-
+ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public Li-
+cense along with this program. If not, see
+.
+
+The author can be contacted on
+*/
+
+#ifndef CATALOG_H
+#define CATALOG_H
+
+#include
+
+void open_catalog(STRPTR catalog);
+STRPTR get_string(ULONG id);
+void close_catalog(void);
+
+#endif
diff --git a/common.c b/common.c
new file mode 100755
index 0000000..13f5362
--- /dev/null
+++ b/common.c
@@ -0,0 +1,95 @@
+/*
+ToolsMenu - Add tools to the Workbench Tools menu
+
+Copyright (C) 2015, 2018 Kim Fastrup Larsen
+
+This program is free software: you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, either ver-
+sion 3 of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be use-
+ful, but WITHOUT ANY WARRANTY; without even the implied war-
+ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public Li-
+cense along with this program. If not, see
+.
+
+The author can be contacted on
+*/
+
+#include
+#include
+
+#include
+
+#ifdef USE_PRAGMAS
+#include
+#endif
+
+#include
+
+#include "common.h"
+
+char *copy_of(char *s)
+{
+ char *result;
+
+ if ((result = AllocVec(strlen(s) + 1, MEMF_PUBLIC)) != NULL)
+ strcpy(result, s);
+ return result;
+}
+
+void for_all(struct List *l, void (*f)(void *))
+{
+ struct Node *n, *next;
+
+ /* This form of iteration allows f() to free nodes */
+ for (n = l->lh_Head; (next = n->ln_Succ) != NULL; n = next)
+ (*f)(n);
+}
+
+Bool is_in_list(struct List *l, struct Node *node)
+{
+ struct Node *n;
+
+ for (n = l->lh_Head; n->ln_Succ != NULL; n = n->ln_Succ)
+ if (n == node)
+ return YES;
+ return NO;
+}
+
+struct Node *node_at(struct List *l, int i)
+{
+ struct Node *n;
+
+ for (n = l->lh_Head; n->ln_Succ != NULL; n = n->ln_Succ) {
+ if (i == 0)
+ return n;
+ --i;
+ }
+ return NULL;
+}
+
+int length_of(struct List *l)
+{
+ struct Node *n;
+ int result = 0;
+
+ for (n = l->lh_Head; n->ln_Succ != NULL; n = n->ln_Succ)
+ ++result;
+ return result;
+}
+
+void delete_port(struct MsgPort *port)
+{
+ struct Message *msg;
+
+ if (port != NULL) {
+ while ((msg = GetMsg(port)) != NULL)
+ ReplyMsg(msg);
+ DeleteMsgPort(port);
+ }
+}
diff --git a/common.h b/common.h
new file mode 100755
index 0000000..b23375e
--- /dev/null
+++ b/common.h
@@ -0,0 +1,45 @@
+/*
+ToolsMenu - Add tools to the Workbench Tools menu
+
+Copyright (C) 2015, 2018 Kim Fastrup Larsen
+
+This program is free software: you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, either ver-
+sion 3 of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be use-
+ful, but WITHOUT ANY WARRANTY; without even the implied war-
+ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public Li-
+cense along with this program. If not, see
+.
+
+The author can be contacted on
+*/
+
+#ifndef COMMON_H
+#define COMMON_H
+
+#define APP_NAME "ToolsMenu"
+#define VERSION "1.6"
+#define DATE "26.11.2018"
+#define COPYRIGHT "Copyright © 2015, 2018 Kim Fastrup Larsen"
+#define TEMPLATE "CX_PRIORITY/N/K,CX_POPKEY/K,CX_POPUP/K"
+#define CATALOG "toolsmenu.catalog"
+#define TITLE APP_NAME " " VERSION
+
+#define MAX_PATH_LENGTH 127
+
+typedef enum { NO, YES } Bool;
+
+char *copy_of(char *s);
+void for_all(struct List *, void (*)(void *));
+Bool is_in_list(struct List *, struct Node *);
+struct Node *node_at(struct List *, int index);
+int length_of(struct List *);
+void delete_port(struct MsgPort *);
+
+#endif
diff --git a/compiler.h b/compiler.h
new file mode 100755
index 0000000..5f5dcf5
--- /dev/null
+++ b/compiler.h
@@ -0,0 +1,28 @@
+/*
+ToolsMenu - Add tools to the Workbench Tools menu
+
+Copyright (C) 2015, 2018 Kim Fastrup Larsen
+
+This program is free software: you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, either ver-
+sion 3 of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be use-
+ful, but WITHOUT ANY WARRANTY; without even the implied war-
+ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public Li-
+cense along with this program. If not, see
+.
+
+The author can be contacted on
+*/
+
+#ifndef COMPILER_H
+#define COMPILER_H
+
+#define CHIP __chip
+
+#endif
diff --git a/cx.c b/cx.c
new file mode 100755
index 0000000..95c7c12
--- /dev/null
+++ b/cx.c
@@ -0,0 +1,213 @@
+/*
+ToolsMenu - Add tools to the Workbench Tools menu
+
+Copyright (C) 2015, 2018 Kim Fastrup Larsen
+
+This program is free software: you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, either ver-
+sion 3 of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be use-
+ful, but WITHOUT ANY WARRANTY; without even the implied war-
+ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public Li-
+cense along with this program. If not, see
+.
+
+The author can be contacted on
+*/
+
+#include
+#include
+
+#include
+#include
+
+#ifdef USE_PRAGMAS
+#include
+#endif
+
+#include
+
+#define CATCOMP_NUMBERS
+
+#include "args.h"
+#include "catalog.h"
+#include "common.h"
+#include "cx.h"
+#include "gui.h"
+#include "io.h"
+#include "strings.h"
+#include "wb.h"
+
+struct List *tools;
+
+static Bool enabled = YES;
+
+static void free_tool(Tool *tool)
+{
+ FreeVec(tool->node.ln_Name);
+ FreeVec(tool->path);
+ FreeMem(tool, sizeof *tool);
+}
+
+static Tool *create_tool(char *name, char *path)
+{
+ Tool *t;
+
+ if ((t = AllocMem(sizeof *t, MEMF_PUBLIC | MEMF_CLEAR)) == NULL)
+ return NULL;
+ if ((t->node.ln_Name = copy_of(name)) == NULL)
+ goto create_tool_failed;
+ if ((t->path = copy_of(path)) == NULL)
+ goto create_tool_failed;
+ return t;
+
+create_tool_failed:
+ free_tool(t);
+ return NULL;
+}
+
+static Tool *add_tool(char *name, char *path, struct Node *pred)
+{
+ Tool *tool;
+
+ if ((tool = create_tool(name, path)) != NULL) {
+ Insert(tools, &tool->node, pred);
+ if (enabled)
+ wb_add_tool(tool);
+ }
+ return tool;
+}
+
+static void remove_tool(Tool *tool)
+{
+ if (enabled)
+ wb_remove_tool(tool);
+ Remove(&tool->node);
+ free_tool(tool);
+}
+
+static Tool *do_update(Tool *(*f)(Tool *, char *, char *), Tool *tool,
+ char *name, char *path)
+{
+ Tool *result;
+
+ gui_begin_update();
+ result = (*f)(tool, name, path);
+ gui_end_update();
+ return result;
+}
+
+static Tool *append(Tool *unused, char *name, char *path)
+{
+ return add_tool(name, path, tools->lh_TailPred);
+}
+
+/* Actually creates a new tool and then deletes the old one, to simplify low
+ memory failure scenarios. If name is blank, it will simply delete the
+ tool. */
+static Tool *modify(Tool *tool, char *name, char *path)
+{
+ Tool *new_tool = NULL;
+
+ if (*name != 0 && (new_tool = add_tool(name, path, &tool->node)) == NULL)
+ return tool;
+ remove_tool(tool);
+ return new_tool;
+
+}
+
+static void add_to_menu(void *tool)
+{
+ wb_add_tool((Tool *) tool);
+}
+
+static void remove_from_menu(void *tool)
+{
+ wb_remove_tool((Tool *) tool);
+}
+
+void cx_set_up()
+{
+ if ((tools = AllocMem(sizeof *tools, MEMF_PUBLIC)) == NULL)
+ exit(RETURN_FAIL);
+ NewList(tools);
+ io_read_definitions(args.exe.dir, args.exe.filename);
+}
+
+Tool *cx_add_tool(char *name, char *path)
+{
+ Tool *tool = do_update(append, NULL, name, path);
+
+ gui_edit(tool);
+ return tool;
+}
+
+Tool *cx_modify_tool(Tool *tool, char *name, char *path)
+{
+ return do_update(modify, tool, name, path);
+}
+
+void cx_enable()
+{
+ if (!enabled) {
+ enabled = YES;
+ for_all(tools, add_to_menu);
+ }
+}
+
+void cx_disable()
+{
+ if (enabled) {
+ enabled = NO;
+ for_all(tools, remove_from_menu);
+ }
+}
+
+void cx_show()
+{
+ gui_show();
+}
+
+void cx_hide()
+{
+ gui_hide();
+}
+
+void cx_save_tools()
+{
+ gui_begin_busy();
+ io_write_definitions(args.exe.dir, args.exe.filename);
+ gui_end_busy();
+}
+
+void cx_about()
+{
+ gui_message(get_string(MSG_GAD_CONTINUE),
+ TITLE,
+ COPYRIGHT,
+ "",
+ "This program comes with ABSOLUTELY NO WARRANTY.",
+ "This is free software under the terms of",
+ "version 3 of the GNU General Public License.",
+ NULL);
+}
+
+void cx_quit()
+{
+ cx_hide();
+ cx_disable();
+ exit(RETURN_OK);
+}
+
+void cx_clean_up()
+{
+ if (tools != NULL) {
+ for_all(tools, free_tool);
+ FreeMem(tools, sizeof *tools);
+ }
+}
diff --git a/cx.h b/cx.h
new file mode 100755
index 0000000..8cdce69
--- /dev/null
+++ b/cx.h
@@ -0,0 +1,51 @@
+/*
+ToolsMenu - Add tools to the Workbench Tools menu
+
+Copyright (C) 2015, 2018 Kim Fastrup Larsen
+
+This program is free software: you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, either ver-
+sion 3 of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be use-
+ful, but WITHOUT ANY WARRANTY; without even the implied war-
+ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public Li-
+cense along with this program. If not, see
+.
+
+The author can be contacted on
+*/
+
+#ifndef CX_H
+#define CX_H
+
+#include
+
+/* We will maintain a list of Tools as the main global state of this
+ commodity. The name of each Tool (ie. menu item) will be stored in
+ node.ln_Name. */
+typedef struct {
+ struct Node node;
+ char *path;
+ struct AppMenuItem *menu_item;
+} Tool;
+
+extern struct List *tools;
+
+void cx_set_up(void);
+Tool *cx_add_tool(char *name, char *path);
+Tool *cx_modify_tool(Tool *, char *name, char *path);
+void cx_enable(void);
+void cx_disable(void);
+void cx_show(void);
+void cx_hide(void);
+void cx_save_tools(void);
+void cx_about(void);
+void cx_quit(void);
+void cx_clean_up(void);
+
+#endif
diff --git a/dansk.ct b/dansk.ct
new file mode 100755
index 0000000..e403f2e
--- /dev/null
+++ b/dansk.ct
@@ -0,0 +1,72 @@
+## version $VER: toolsmenu.catalog 1.6 (26.11.2018)
+## codeset 0
+## language dansk
+;
+MSG_ERR_OPEN
+Kunne ikke åbne %s
+; Could not open %s
+;
+MSG_ERR_HOTKEY
+Ugyldig genvejstast
+; Illegal hot key
+;
+MSG_WINDOW_TITLE
+%s: Tryk <%s>
+; %s: Hot Key = <%s>
+;
+MSG_DESCRIPTION
+Tilføj værktøjer til Workbench-menuen
+; Add tools to the Workbench Tools Menu
+;
+MSG_PROJECT_MENU
+Projekt
+; Project
+;
+MSG_PROJECT_SAVE
+Gemme menuposter
+; Save Menu Items
+;
+MSG_PROJECT_HIDE
+Gemme væk
+; Hide
+;
+MSG_PROJECT_ABOUT
+Omkring...
+; About...
+;
+MSG_PROJECT_QUIT
+Afbryde
+; Quit
+;
+MSG_GAD_CONTINUE
+Fortsætte
+; Continue
+;
+MSG_GAD_MENU_ITEMS
+Menuposter
+; Menu Items
+;
+MSG_GAD_NEW
+Ny
+; New
+;
+MSG_GAD_DELETE
+Slette
+; Delete
+;
+MSG_GAD_TOOL
+Værktøj
+; Tool
+;
+MSG_INFO_1
+Værktøjer kan tilføjes
+; You can add tools
+;
+MSG_INFO_2
+ved at trække deres ikoner
+; by dragging their icons
+;
+MSG_INFO_3
+ind i dette vindue.
+; into this window.
+;
diff --git a/error.c b/error.c
new file mode 100755
index 0000000..1109628
--- /dev/null
+++ b/error.c
@@ -0,0 +1,51 @@
+/*
+ToolsMenu - Add tools to the Workbench Tools menu
+
+Copyright (C) 2015, 2018 Kim Fastrup Larsen
+
+This program is free software: you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, either ver-
+sion 3 of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be use-
+ful, but WITHOUT ANY WARRANTY; without even the implied war-
+ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public Li-
+cense along with this program. If not, see
+.
+
+The author can be contacted on
+*/
+
+#include
+
+#include
+
+#ifdef USE_PRAGMAS
+#include
+#endif
+
+#define CATCOMP_NUMBERS
+
+#include "catalog.h"
+#include "common.h"
+#include "error.h"
+#include "strings.h"
+
+extern struct IntuitionBase *IntuitionBase;
+
+void error_message(char *fmt, char *arg)
+{
+ static struct EasyStruct easy_struct = {
+ sizeof(struct EasyStruct), 0, APP_NAME
+ };
+
+ if (IntuitionBase == NULL)
+ return;
+ easy_struct.es_TextFormat = fmt;
+ easy_struct.es_GadgetFormat = get_string(MSG_GAD_CONTINUE);
+ EasyRequestArgs(NULL, &easy_struct, NULL, &arg);
+}
diff --git a/error.h b/error.h
new file mode 100755
index 0000000..5b781bb
--- /dev/null
+++ b/error.h
@@ -0,0 +1,31 @@
+/*
+ToolsMenu - Add tools to the Workbench Tools menu
+
+Copyright (C) 2015, 2018 Kim Fastrup Larsen
+
+This program is free software: you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, either ver-
+sion 3 of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be use-
+ful, but WITHOUT ANY WARRANTY; without even the implied war-
+ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public Li-
+cense along with this program. If not, see
+.
+
+The author can be contacted on
+*/
+
+#ifndef ERROR_H
+#define ERROR_H
+
+/* fmt can contain up to one %s.
+ If fmt contains a %s, arg must be non-NULL.
+*/
+void error_message(char *fmt, char *arg);
+
+#endif
diff --git a/gui.c b/gui.c
new file mode 100755
index 0000000..f0811ac
--- /dev/null
+++ b/gui.c
@@ -0,0 +1,618 @@
+/*
+ToolsMenu - Add tools to the Workbench Tools menu
+
+Copyright (C) 2015, 2018 Kim Fastrup Larsen
+
+This program is free software: you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, either ver-
+sion 3 of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be use-
+ful, but WITHOUT ANY WARRANTY; without even the implied war-
+ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public Li-
+cense along with this program. If not, see
+.
+
+The author can be contacted on
+*/
+
+#define INTUI_V36_NAMES_ONLY
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#ifdef USE_PRAGMAS
+#include
+#include
+#include
+#endif
+
+#include
+#include
+#include
+#include
+
+#define CATCOMP_NUMBERS
+
+#include "args.h"
+#include "catalog.h"
+#include "common.h"
+#include "compiler.h"
+#include "cx.h"
+#include "gui.h"
+#include "message.h"
+#include "strings.h"
+#include "wb.h"
+
+#define NOT_SET 0xffffffffU
+
+#define GAD_ID_LIST 1
+#define GAD_ID_NEW 2
+#define GAD_ID_DELETE 3
+#define GAD_ID_NAME 4
+#define GAD_ID_PATH 5
+
+typedef struct {
+ int line_h;
+ int col1_x;
+ int list_y, list_w, list_h, scro_w;
+ int gadg_h;
+ int name_y, butn_y;
+ int col2_x;
+ int path_y, path_w;
+ int wind_w, wind_h;
+ int text_y;
+} Layout;
+
+extern struct IntuitionBase *IntuitionBase;
+
+static struct IntuiText itexts[3] = {
+ { 0, 0, JAM1, 0, 0, NULL, NULL, itexts + 1 },
+ { 0, 0, JAM1, 0, 0, NULL, NULL, itexts + 2 },
+ { 0, 0, JAM1, 0, 0, NULL, NULL, NULL }
+};
+static WORD zoom[4];
+
+static struct MsgPort *user_port;
+static struct Menu *menus;
+static struct Requester busy_req;
+static Message message;
+static Bool message_constructed;
+static struct Screen *screen;
+static struct DrawInfo *draw_info;
+static APTR visual_info;
+static struct Gadget *gadgets;
+static struct Window *window;
+static struct AppWindow *app_window;
+
+static ULONG left_edge;
+static ULONG top_edge = NOT_SET;
+
+static struct Gadget *list_gad, *name_gad, *del_gad, *path_gad;
+
+static Tool *current;
+
+static UBYTE *strgad_buf(struct Gadget *gadget)
+{
+ return ((struct StringInfo *) gadget->SpecialInfo)->Buffer;
+}
+
+static void strip_messages_for(struct Window *window)
+{
+ struct IntuiMessage *msg, *succ;
+
+ msg = (struct IntuiMessage *) window->UserPort->mp_MsgList.lh_Head;
+ while ((succ = (struct IntuiMessage *) msg->ExecMessage.mn_Node.ln_Succ)
+ != NULL) {
+ if (msg->IDCMPWindow == window) {
+ Remove(&msg->ExecMessage.mn_Node);
+ ReplyMsg(&msg->ExecMessage);
+ }
+ msg = succ;
+ }
+}
+
+static void close_window_safely(struct Window *window)
+{
+ Forbid();
+ strip_messages_for(window);
+ window->UserPort = NULL;
+ ModifyIDCMP(window, 0);
+ Permit();
+ CloseWindow(window);
+}
+
+static Bool set_up_menus(void)
+{
+ static struct NewMenu new_menus[] = {
+ { NM_TITLE, NULL, NULL, 0, 0, (APTR) MSG_PROJECT_MENU },
+ { NM_ITEM, NULL, "S", 0, 0, (APTR) MSG_PROJECT_SAVE },
+ { NM_ITEM, NM_BARLABEL, NULL, 0, 0, NULL },
+ { NM_ITEM, NULL, "H", 0, 0, (APTR) MSG_PROJECT_HIDE },
+ { NM_ITEM, NULL, NULL, 0, 0, (APTR) MSG_PROJECT_ABOUT },
+ { NM_ITEM, NULL, "Q", 0, 0, (APTR) MSG_PROJECT_QUIT },
+ { NM_END, NULL, NULL, 0, 0, NULL }
+ };
+ struct NewMenu *nm;
+
+ for (nm = new_menus; nm->nm_Type != NM_END; ++nm)
+ if (nm->nm_UserData != NULL)
+ nm->nm_Label = get_string((ULONG) nm->nm_UserData);
+ if ((menus = CreateMenus(new_menus, TAG_DONE)) == NULL)
+ return NO;
+ return YES;
+}
+
+static void render(void)
+{
+ PrintIText(window->RPort, itexts, 0, 0);
+}
+
+static void make_layout(Layout *layout)
+{
+ int font_w = draw_info->dri_Font->tf_XSize;
+ int font_h = draw_info->dri_Font->tf_YSize;
+ int pad_x = font_w;
+ int pad_y = font_h / 2;
+ int lab_h = font_h + 4; /* space taken up by label above a gadget */
+ int path_th; /* total height of path gadget and label above */
+ int col2_spc; /* vertical spacing around path gadget and text */
+
+ layout->line_h = font_h + 1;
+ layout->col1_x = pad_x;
+ layout->list_y = pad_y + lab_h;
+ layout->list_w = 24 * font_w + 12; /* should be divisible by 2 */
+ layout->list_h = 6 * layout->line_h + 4;
+ layout->scro_w = font_w + 10;
+ layout->gadg_h = font_h + 6;
+ layout->name_y = layout->list_y + layout->list_h;
+ layout->butn_y = layout->name_y + layout->gadg_h;
+ layout->col2_x = layout->col1_x + layout->list_w + pad_x;
+ layout->path_w = 32 * font_w + 12;
+ layout->wind_w = layout->col2_x + layout->path_w + pad_x;
+ layout->wind_h = layout->butn_y + layout->gadg_h + pad_y;
+ path_th = layout->gadg_h + lab_h;
+ col2_spc = (layout->wind_h - path_th - 3 * layout->line_h) / 3;
+ layout->path_y = col2_spc + lab_h;
+ layout->text_y = 2 * col2_spc + path_th;
+}
+
+static unsigned window_border_top(void)
+{
+ return screen->WBorTop + screen->Font->ta_YSize + 1U;
+}
+
+static void make_newgadget(struct NewGadget *ng, WORD left_edge,
+ WORD top_edge, WORD width, WORD height, UBYTE *gadget_text, UWORD id,
+ ULONG flags)
+{
+ ng->ng_LeftEdge = left_edge + screen->WBorLeft;
+ ng->ng_TopEdge = top_edge + window_border_top();
+ ng->ng_Width = width;
+ ng->ng_Height = height;
+ ng->ng_GadgetText = gadget_text;
+ ng->ng_GadgetID = id;
+ ng->ng_Flags = flags;
+}
+
+static Bool create_gadgets(Layout *l)
+{
+ struct NewGadget ng;
+ struct Gadget *g = CreateContext(&gadgets);
+ int butn_w = l->list_w / 2;
+
+ ng.ng_TextAttr = screen->Font;
+ ng.ng_VisualInfo = visual_info;
+ make_newgadget(&ng, l->col1_x, l->list_y, l->list_w, l->list_h,
+ get_string(MSG_GAD_MENU_ITEMS), GAD_ID_LIST, 0);
+ g = list_gad = CreateGadget(LISTVIEW_KIND, g, &ng,
+ GTLV_Labels, tools,
+ GTLV_ScrollWidth, (LONG) l->scro_w,
+ LAYOUTA_Spacing, 1L,
+ TAG_DONE);
+ make_newgadget(&ng, l->col1_x, l->name_y, l->list_w, l->gadg_h,
+ NULL, GAD_ID_NAME, 0);
+ g = name_gad = CreateGadget(STRING_KIND, g, &ng,
+ GA_Disabled, (LONG) TRUE,
+ TAG_DONE);
+ make_newgadget(&ng, l->col1_x, l->butn_y, butn_w, l->gadg_h,
+ get_string(MSG_GAD_NEW), GAD_ID_NEW, 0);
+ g = CreateGadget(BUTTON_KIND, g, &ng,
+ TAG_DONE);
+ make_newgadget(&ng, l->col1_x + butn_w, l->butn_y, butn_w, l->gadg_h,
+ get_string(MSG_GAD_DELETE), GAD_ID_DELETE, 0);
+ g = del_gad = CreateGadget(BUTTON_KIND, g, &ng,
+ GA_Disabled, (LONG) TRUE,
+ TAG_DONE);
+ make_newgadget(&ng, l->col2_x, l->path_y, l->path_w, l->gadg_h,
+ get_string(MSG_GAD_TOOL), GAD_ID_PATH, PLACETEXT_ABOVE);
+ g = path_gad = CreateGadget(STRING_KIND, g, &ng,
+ GTST_MaxChars, (LONG) MAX_PATH_LENGTH,
+ GA_Disabled, (LONG) TRUE,
+ TAG_DONE);
+ return g != NULL;
+}
+
+static void layout_texts(Layout *layout)
+{
+ int left_edge = layout->col2_x;
+ int top_edge = layout->text_y + window_border_top();
+ struct IntuiText *t;
+
+ for (t = itexts; t != NULL; t = t->NextText) {
+ t->FrontPen = draw_info->dri_Pens[TEXTPEN];
+ t->ITextFont = screen->Font;
+ t->LeftEdge = left_edge + (layout->path_w - IntuiTextLength(t)) / 2;
+ t->TopEdge = top_edge;
+ top_edge += layout->line_h;
+ }
+}
+
+static Bool open_window(Layout *layout)
+{
+ static char window_title_buf[64];
+ char *window_title = get_string(MSG_WINDOW_TITLE);
+
+ if (strlen(window_title)+strlen(APP_NAME)+strlen(args.cx_popkey) > 64)
+ window_title = APP_NAME;
+ else {
+ sprintf(window_title_buf, window_title, APP_NAME, args.cx_popkey);
+ window_title = window_title_buf;
+ }
+ if ((window = OpenWindowTags(NULL,
+ WA_Left, left_edge,
+ WA_Top, top_edge,
+ WA_InnerWidth, (LONG) layout->wind_w,
+ WA_InnerHeight, (LONG) layout->wind_h,
+ WA_Flags, (ULONG) WFLG_DRAGBAR | WFLG_DEPTHGADGET | WFLG_CLOSEGADGET |
+ WFLG_ACTIVATE | WFLG_SIMPLE_REFRESH,
+ WA_PubScreen, screen,
+ WA_Zoom, zoom,
+ WA_Gadgets, gadgets,
+ WA_Title, window_title,
+ WA_AutoAdjust, (LONG) TRUE,
+ WA_NewLookMenus, (LONG) TRUE,
+ TAG_DONE)) == NULL)
+ return NO;
+ window->UserPort = user_port;
+ ModifyIDCMP(window, IDCMP_CLOSEWINDOW | IDCMP_MENUPICK | IDCMP_REFRESHWINDOW
+ | LISTVIEWIDCMP | STRINGIDCMP | BUTTONIDCMP);
+ GT_RefreshWindow(window, NULL);
+ return YES;
+}
+
+static void update_gadgets(void)
+{
+ Bool disable = (current == NULL);
+
+ GT_SetGadgetAttrs(name_gad, window, NULL,
+ GA_Disabled, (LONG) disable,
+ GTST_String, disable ? "" : current->node.ln_Name,
+ TAG_DONE);
+ GT_SetGadgetAttrs(del_gad, window, NULL,
+ GA_Disabled, (LONG) disable,
+ TAG_DONE);
+ GT_SetGadgetAttrs(path_gad, window, NULL,
+ GA_Disabled, (LONG) disable,
+ GTST_String, disable ? "" : current->path,
+ TAG_DONE);
+}
+
+static void update_current(void)
+{
+ if (current == NULL)
+ return;
+ if ((current = cx_modify_tool(current, strgad_buf(name_gad),
+ strgad_buf(path_gad))) == NULL)
+ update_gadgets();
+}
+
+static void select(int index)
+{
+ gui_edit((Tool *) node_at(tools, index));
+}
+
+static void new_tool(void)
+{
+ cx_add_tool("", "");
+}
+
+static void delete_tool(void)
+{
+ GT_SetGadgetAttrs(name_gad, window, NULL,
+ GTST_String, "",
+ TAG_DONE);
+ update_current();
+}
+
+static void handle_gadgetup(struct Gadget *gadget, UWORD code)
+{
+ switch (gadget->GadgetID) {
+ case GAD_ID_LIST:
+ select(code);
+ break;
+ case GAD_ID_NEW:
+ new_tool();
+ break;
+ case GAD_ID_DELETE:
+ delete_tool();
+ break;
+ case GAD_ID_NAME:
+ case GAD_ID_PATH:
+ update_current();
+ break;
+ }
+}
+
+static void handle_project_menu(UWORD code)
+{
+ switch (ITEMNUM(code)) {
+ case 0: /* Save Menu Items */
+ cx_save_tools();
+ break;
+ case 2: /* Hide */
+ cx_hide();
+ break;
+ case 3: /* About */
+ cx_about();
+ break;
+ case 4: /* Quit */
+ cx_quit();
+ break;
+ }
+}
+
+static void handle_menupick(UWORD code)
+{
+ while (code != MENUNULL) {
+ switch (MENUNUM(code)) {
+ case 0:
+ handle_project_menu(code);
+ break;
+ }
+ code = ItemAddress(menus, code)->NextSelect;
+ }
+}
+
+static void handle_closewindow(void)
+{
+ cx_hide();
+}
+
+static void handle_refreshwindow(void)
+{
+ GT_BeginRefresh(window);
+ render();
+ GT_EndRefresh(window, TRUE);
+}
+
+static void bring_to_front(void)
+{
+ ScreenToFront(window->WScreen);
+ WindowToFront(window);
+ ActivateWindow(window);
+ if (window->Height == zoom[3])
+ ZipWindow(window);
+}
+
+static void fill_itexts(struct IntuiText *text, va_list *ap)
+{
+ char *s;
+
+ while ((s = va_arg(*ap, char *)) != NULL) {
+ text->IText = s;
+ text->NextText = text + 1;
+ ++text;
+ }
+ (text - 1)->NextText = NULL;
+}
+
+ULONG gui_set_up()
+{
+ if (IntuitionBase->LibNode.lib_Version >= 39)
+ zoom[0] = zoom[1] = ~0;
+ if (!set_up_menus())
+ exit(RETURN_FAIL);
+ if ((user_port = CreateMsgPort()) == NULL)
+ exit(RETURN_FAIL);
+ itexts[0].IText = get_string(MSG_INFO_1);
+ itexts[1].IText = get_string(MSG_INFO_2);
+ itexts[2].IText = get_string(MSG_INFO_3);
+ InitRequester(&busy_req);
+ if (!message_cons(&message))
+ exit(RETURN_FAIL);
+ message_constructed = YES;
+ return 1UL << user_port->mp_SigBit;
+}
+
+void gui_clean_up()
+{
+ if (message_constructed)
+ message_dest(&message);
+ DeleteMsgPort(user_port);
+ if (menus != NULL) /* Really checking if gadtools.library was opened */
+ FreeMenus(menus);
+}
+
+void gui_handle_signal()
+{
+ struct IntuiMessage *imsg;
+ ULONG clas;
+ UWORD code;
+ APTR iadd;
+
+ while ((imsg = GT_GetIMsg(user_port)) != NULL) {
+ clas = imsg->Class;
+ code = imsg->Code;
+ iadd = imsg->IAddress;
+ GT_ReplyIMsg(imsg);
+ switch (clas) {
+ case IDCMP_MENUPICK:
+ handle_menupick(code);
+ break;
+ case IDCMP_CLOSEWINDOW:
+ handle_closewindow();
+ break;
+ case IDCMP_REFRESHWINDOW:
+ handle_refreshwindow();
+ break;
+ case IDCMP_GADGETUP:
+ handle_gadgetup(iadd, code);
+ break;
+ }
+ }
+}
+
+void gui_show()
+{
+ Layout layout;
+
+ if (window != NULL) {
+ bring_to_front();
+ return;
+ }
+ if ((screen = LockPubScreen(NULL)) == NULL)
+ goto gui_show_failed;
+ ScreenToFront(screen);
+ if ((draw_info = GetScreenDrawInfo(screen)) == NULL)
+ goto gui_show_failed;
+ if ((visual_info = GetVisualInfo(screen, TAG_DONE)) == NULL)
+ goto gui_show_failed;
+ if (!LayoutMenus(menus, visual_info,
+ GTMN_NewLookMenus, (LONG) TRUE,
+ TAG_DONE))
+ goto gui_show_failed;
+ if (top_edge == NOT_SET)
+ top_edge = screen->BarHeight + 1;
+ zoom[2] = 25 * draw_info->dri_Font->tf_XSize;
+ zoom[3] = window_border_top();
+ make_layout(&layout);
+ if (!create_gadgets(&layout))
+ goto gui_show_failed;
+ layout_texts(&layout);
+ if (!open_window(&layout))
+ goto gui_show_failed;
+ render();
+ app_window = wb_add_window(window);
+ SetMenuStrip(window, menus);
+ current = NULL;
+ return;
+
+gui_show_failed:
+ gui_hide();
+}
+
+void gui_hide()
+{
+ if (app_window != NULL) {
+ wb_remove_window(app_window);
+ app_window = NULL;
+ }
+ if (window != NULL) {
+ EndRequest(&message.requester, window);
+ if (window->MenuStrip != NULL)
+ ClearMenuStrip(window);
+ left_edge = window->LeftEdge;
+ top_edge = window->TopEdge;
+ close_window_safely(window);
+ window = NULL;
+ }
+ FreeGadgets(gadgets);
+ gadgets = NULL;
+ FreeVisualInfo(visual_info);
+ visual_info = NULL;
+ if (screen != NULL) {
+ FreeScreenDrawInfo(screen, draw_info);
+ draw_info = NULL;
+ UnlockPubScreen(NULL, screen);
+ screen = NULL;
+ }
+}
+
+void gui_edit(Tool *tool)
+{
+ if (window == NULL)
+ return;
+ current = tool;
+ update_gadgets();
+ if (current != NULL)
+ ActivateGadget(name_gad, window, NULL);
+}
+
+/* Temporarily suspend the listview so we can update the list. */
+void gui_begin_update()
+{
+ if (window == NULL)
+ return;
+ GT_SetGadgetAttrs(list_gad, window, NULL, GTLV_Labels, ~0L, TAG_DONE);
+}
+
+/* Resume normal listview operations. */
+void gui_end_update()
+{
+ if (window == NULL)
+ return;
+ GT_SetGadgetAttrs(list_gad, window, NULL, GTLV_Labels, tools, TAG_DONE);
+}
+
+void gui_begin_busy()
+{
+ static UWORD CHIP busy_pointer[] = {
+ 0x0000, 0x0000, 0x0400, 0x07c0, 0x0000, 0x07c0, 0x0100, 0x0380,
+ 0x0000, 0x07e0, 0x07c0, 0x1ff8, 0x1ff0, 0x3fec, 0x3ff8, 0x7fde,
+ 0x3ff8, 0x7fbe, 0x7ffc, 0xff7f, 0x7efc, 0xffff, 0x7ffc, 0xffff,
+ 0x3ff8, 0x7ffe, 0x3ff8, 0x7ffe, 0x1ff0, 0x3ffc, 0x07c0, 0x1ff8,
+ 0x0000, 0x07e0, 0x0000, 0x0000
+ };
+
+ if (window == NULL)
+ return;
+ Request(&busy_req, window);
+ if (IntuitionBase->LibNode.lib_Version < 39)
+ SetPointer(window, busy_pointer, 16, 16, -6, 0);
+ else
+ SetWindowPointer(window,
+ WA_BusyPointer, (LONG) TRUE,
+ WA_PointerDelay, (LONG) TRUE,
+ TAG_DONE);
+}
+
+void gui_end_busy()
+{
+ if (window == NULL)
+ return;
+ if (IntuitionBase->LibNode.lib_Version < 39)
+ ClearPointer(window);
+ else
+ SetWindowPointer(window, TAG_DONE);
+ EndRequest(&busy_req, window);
+}
+
+void gui_message(char *button_text, ...)
+{
+ static struct IntuiText texts[GUI_MESSAGE_MAX_LINES];
+ va_list ap;
+
+ va_start(ap, button_text);
+ if (window == NULL || message.requester.Flags & REQACTIVE)
+ goto gui_message_exit;
+ fill_itexts(texts, &ap);
+ message_layout(&message, window, draw_info, texts, button_text);
+ Request(&message.requester, window);
+
+gui_message_exit:
+ va_end(ap);
+}
diff --git a/gui.h b/gui.h
new file mode 100755
index 0000000..83a70c0
--- /dev/null
+++ b/gui.h
@@ -0,0 +1,44 @@
+/*
+ToolsMenu - Add tools to the Workbench Tools menu
+
+Copyright (C) 2015, 2018 Kim Fastrup Larsen
+
+This program is free software: you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, either ver-
+sion 3 of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be use-
+ful, but WITHOUT ANY WARRANTY; without even the implied war-
+ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public Li-
+cense along with this program. If not, see
+.
+
+The author can be contacted on
+*/
+
+#ifndef GUI_H
+#define GUI_H
+
+#include
+
+#include "cx.h"
+
+#define GUI_MESSAGE_MAX_LINES 6
+
+ULONG gui_set_up(void);
+void gui_clean_up(void);
+void gui_handle_signal(void);
+void gui_show(void);
+void gui_hide(void);
+void gui_edit(Tool *);
+void gui_begin_update(void);
+void gui_end_update(void);
+void gui_begin_busy(void);
+void gui_end_busy(void);
+void gui_message(char *button_text, ...);
+
+#endif
diff --git a/io.c b/io.c
new file mode 100755
index 0000000..e00d231
--- /dev/null
+++ b/io.c
@@ -0,0 +1,162 @@
+/*
+ToolsMenu - Add tools to the Workbench Tools menu
+
+Copyright (C) 2015, 2018 Kim Fastrup Larsen
+
+This program is free software: you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, either ver-
+sion 3 of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be use-
+ful, but WITHOUT ANY WARRANTY; without even the implied war-
+ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public Li-
+cense along with this program. If not, see
+.
+
+The author can be contacted on
+*/
+
+#include
+
+#include
+#include
+#include
+
+#ifdef USE_PRAGMAS
+#include
+#include
+#include
+#endif
+
+#include
+
+#include "common.h"
+#include "cx.h"
+#include "io.h"
+
+#define DEF_BEGIN '\xab' /* Left double angle quote */
+#define DEF_END '\xbb' /* Right double angle quote */
+
+typedef void Disk_object_f(struct DiskObject *, char *);
+
+static void with_disk_object(Disk_object_f *f, BPTR dir, char *filename)
+{
+ BPTR old_dir = CurrentDir(dir);
+ struct DiskObject *disk_obj = GetDiskObject(filename);
+
+ if (disk_obj != NULL) {
+ (*f)(disk_obj, filename);
+ FreeDiskObject(disk_obj);
+ }
+ CurrentDir(old_dir);
+}
+
+static void read_definitions(struct DiskObject *disk_obj, char *filename)
+{
+ char **tt, *def_end;
+
+ for (tt = disk_obj->do_ToolTypes; *tt != NULL; ++tt)
+ /* Identify the tool types that are menu item definitions and create
+ menu items from them. */
+ if (**tt == DEF_BEGIN && (def_end = strchr(*tt, DEF_END)) != NULL &&
+ *(def_end + 1) == ' ') {
+ /* Temporarily insert terminator to facilitate string copy. */
+ *def_end = '\0';
+ cx_add_tool(*tt + 1, def_end + 2);
+ /* Restore original char to leave string "untouched". */
+ *def_end = DEF_END;
+ }
+}
+
+/* Count the number of tool types that are not menu item definitions. */
+static int non_defs(char **tt)
+{
+ int result = 0;
+
+ for ( ; *tt != NULL; ++tt)
+ if (**tt != DEF_BEGIN)
+ ++result;
+ return result;
+}
+
+/* Create a tool type for a menu item definition. */
+static char *create_def(Tool *t)
+{
+ char *def;
+ size_t name_len = strlen(t->node.ln_Name);
+
+ /* Allocate space for name, path, two delimiters, a space, and
+ string terminator */
+ def = AllocVec(name_len + strlen(t->path) + 4, 0);
+ if (def != NULL) {
+ def[0] = DEF_BEGIN;
+ strcpy(def + 1, t->node.ln_Name);
+ def[name_len + 1] = DEF_END;
+ def[name_len + 2] = ' ';
+ strcpy(def + name_len + 3, t->path);
+ }
+ return def;
+}
+
+/* Create a tool type for every menu item in the tools list. */
+static Bool create_defs(char **tt)
+{
+ struct Node *n;
+
+ for (n = tools->lh_Head; n->ln_Succ != NULL; n = n->ln_Succ)
+ if ((*(tt++) = create_def((Tool *) n)) == NULL)
+ return NO;
+ return YES;
+}
+
+/* Copy over those tool types that are not menu item definitions. */
+static void copy_non_defs(char **new_tt, char **old_tt)
+{
+ for ( ; *old_tt != NULL; ++old_tt)
+ if (**old_tt != DEF_BEGIN)
+ *(new_tt++) = *old_tt;
+}
+
+static void free_tool_types(char **tt)
+{
+ char **i;
+
+ for (i = tt ; *i != NULL; ++i)
+ if (**i == DEF_BEGIN)
+ FreeVec(*i);
+ FreeVec(tt);
+}
+
+static void write_definitions(struct DiskObject *disk_obj, char *filename)
+{
+ int tools_size = length_of(tools);
+ char **old_tool_types = disk_obj->do_ToolTypes;
+ int size = tools_size + non_defs(old_tool_types) + 1;
+ char **new_tool_types = AllocVec(sizeof(char *) * size, MEMF_CLEAR);
+
+ if (new_tool_types == NULL)
+ return;
+ if (!create_defs(new_tool_types))
+ goto write_definitions_exit;
+ copy_non_defs(new_tool_types + tools_size, old_tool_types);
+ disk_obj->do_ToolTypes = new_tool_types;
+ PutDiskObject(filename, disk_obj);
+ disk_obj->do_ToolTypes = old_tool_types;
+
+write_definitions_exit:
+ free_tool_types(new_tool_types);
+}
+
+void io_read_definitions(BPTR dir, char *filename)
+{
+ with_disk_object(read_definitions, dir, filename);
+}
+
+void io_write_definitions(BPTR dir, char *filename)
+{
+ with_disk_object(write_definitions, dir, filename);
+}
diff --git a/io.h b/io.h
new file mode 100755
index 0000000..9698281
--- /dev/null
+++ b/io.h
@@ -0,0 +1,31 @@
+/*
+ToolsMenu - Add tools to the Workbench Tools menu
+
+Copyright (C) 2015, 2018 Kim Fastrup Larsen
+
+This program is free software: you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, either ver-
+sion 3 of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be use-
+ful, but WITHOUT ANY WARRANTY; without even the implied war-
+ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public Li-
+cense along with this program. If not, see
+.
+
+The author can be contacted on
+*/
+
+#ifndef IO_H
+#define IO_H
+
+#include
+
+void io_read_definitions(BPTR dir, char *filename);
+void io_write_definitions(BPTR dir, char *filename);
+
+#endif
diff --git a/main.c b/main.c
new file mode 100755
index 0000000..78219a7
--- /dev/null
+++ b/main.c
@@ -0,0 +1,154 @@
+/*
+ToolsMenu - Add tools to the Workbench Tools menu
+
+Copyright (C) 2015, 2018 Kim Fastrup Larsen
+
+This program is free software: you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, either ver-
+sion 3 of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be use-
+ful, but WITHOUT ANY WARRANTY; without even the implied war-
+ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public Li-
+cense along with this program. If not, see
+.
+
+The author can be contacted on
+*/
+
+#include
+
+#include
+
+#ifdef USE_PRAGMAS
+#include
+#endif
+
+#include
+
+#define CATCOMP_NUMBERS
+
+#include "args.h"
+#include "broker.h"
+#include "catalog.h"
+#include "error.h"
+#include "gui.h"
+#include "strings.h"
+#include "toolrun.h"
+#include "wb.h"
+
+#define OS_REQUIRED 37
+
+extern struct Library *SysBase;
+extern struct Library *DOSBase;
+
+char *version = "\0$VER: " TITLE " (" DATE ")";
+
+struct Library *CxBase;
+struct Library *GadToolsBase;
+struct Library *IconBase;
+struct Library *IntuitionBase;
+struct Library *LocaleBase;
+struct Library *WorkbenchBase;
+
+static ULONG signals = SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_F;
+static ULONG wb_signal, tr_signal, broker_signal, gui_signal;
+
+static void check_system_version(void)
+{
+ if (SysBase->lib_Versionlib_Version.
+
+The author can be contacted on
+*/
+
+#define INTUI_V36_NAMES_ONLY
+
+#include
+#include
+
+#include
+
+#ifdef USE_PRAGMAS
+#include
+#endif
+
+#include "message.h"
+
+#define LINE_SPACING 1
+
+static int max(int a, int b)
+{
+ return (a > b) ? a : b;
+}
+
+static void button_dest(Button *button)
+{
+ DisposeObject(button->gadget.GadgetRender);
+}
+
+static int button_cons(Button *button)
+{
+ if ((button->gadget.GadgetRender = NewObject(NULL, "frameiclass",
+ TAG_DONE)) == NULL)
+ return 0;
+ button->text.DrawMode = JAM1;
+ button->text.NextText = NULL;
+ button->gadget.Flags = GFLG_GADGHIMAGE | GFLG_GADGIMAGE;
+ button->gadget.Activation = GACT_RELVERIFY | GACT_ENDGADGET;
+ button->gadget.GadgetType = GTYP_BOOLGADGET | GTYP_REQGADGET;
+ button->gadget.SelectRender = button->gadget.GadgetRender;
+ button->gadget.GadgetText = &button->text;
+ return 1;
+}
+
+static void button_layout(Button *button, struct TextAttr *font,
+ struct DrawInfo *draw_info, UBYTE *text)
+{
+ button->text.LeftEdge = draw_info->dri_Font->tf_XSize;
+ button->text.TopEdge = draw_info->dri_Font->tf_YSize * 3 / 8;
+ button->text.FrontPen = draw_info->dri_Pens[TEXTPEN];
+ button->text.ITextFont = font;
+ button->text.IText = text;
+ button->gadget.Width = IntuiTextLength(&button->text) + 2 *
+ button->text.LeftEdge;
+ button->gadget.Height = font->ta_YSize + 2 * button->text.TopEdge;
+ SetAttrs(button->gadget.GadgetRender,
+ IA_Width, (LONG) button->gadget.Width,
+ IA_Height, (LONG) button->gadget.Height,
+ TAG_DONE);
+}
+
+static void format_text(struct IntuiText *text, struct TextAttr *font,
+ struct DrawInfo *draw_info, int mar_x, int mar_y, int *width, int *height)
+{
+ int pad_x = draw_info->dri_Font->tf_XSize * 2;
+ int pad_y = draw_info->dri_Font->tf_YSize * 3 / 4;
+ int x = pad_x + mar_x;
+ int y = pad_y + mar_y;
+ int line_h = font->ta_YSize + LINE_SPACING;
+
+ *width = 0, *height = 0;
+ for ( ; text != NULL; text = text->NextText) {
+ text->FrontPen = draw_info->dri_Pens[TEXTPEN];
+ text->DrawMode = JAM1;
+ text->LeftEdge = x;
+ text->TopEdge = y;
+ text->ITextFont = font;
+ y += line_h;
+ *width = max(IntuiTextLength(text), *width);
+ *height += line_h;
+ }
+ *width += 2 * pad_x;
+ *height += 2 * pad_y - LINE_SPACING;
+}
+
+static void dispose_images(struct Image *img)
+{
+ struct Image *next;
+
+ while (img != NULL) {
+ next = img->NextImage;
+ DisposeObject(img);
+ img = next;
+ }
+}
+
+int message_cons(Message *msg)
+{
+ static USHORT dither[] = { 0x5555, 0xAAAA };
+ struct Image *background, *outer_frame, *inner_frame;
+
+ InitRequester(&msg->requester);
+ msg->requester.ReqGadget = &msg->button.gadget;
+ msg->requester.Flags = USEREQIMAGE | SIMPLEREQ;
+ if ((background = NewObject(NULL, "fillrectclass",
+ IA_APattern, dither,
+ IA_APatSize, 1L,
+ TAG_DONE)) == NULL)
+ goto message_cons_failed;
+ msg->requester.ReqImage = background;
+ if ((outer_frame = NewObject(NULL, "frameiclass",
+ IA_EdgesOnly, (ULONG) TRUE,
+ TAG_DONE)) == NULL)
+ goto message_cons_failed;
+ background->NextImage = outer_frame;
+ if ((inner_frame = NewObject(NULL, "frameiclass",
+ IA_Recessed, (ULONG) TRUE,
+ TAG_DONE)) == NULL)
+ goto message_cons_failed;
+ outer_frame->NextImage = inner_frame;
+ if (!button_cons(&msg->button))
+ goto message_cons_failed;
+ msg->button.gadget.NextGadget = NULL;
+ return 1;
+
+message_cons_failed:
+ dispose_images(msg->requester.ReqImage);
+ return 0;
+}
+
+void message_layout(Message *msg, struct Window *window,
+ struct DrawInfo *draw_info, struct IntuiText *text, UBYTE *button_text)
+{
+ struct Image *background = msg->requester.ReqImage;
+ struct Image *outer_frame = background->NextImage;
+ struct Image *inner_frame = outer_frame->NextImage;
+ struct TextAttr *font = window->WScreen->Font;
+ int pad_x = draw_info->dri_Font->tf_XSize;
+ int pad_y = draw_info->dri_Font->tf_YSize / 2;
+ int text_w, text_h;
+
+ msg->requester.ReqText = text;
+ button_layout(&msg->button, font, draw_info, button_text);
+ format_text(text, font, draw_info, pad_x, pad_y, &text_w, &text_h);
+ msg->requester.Width = 2 * pad_x + text_w;
+ msg->requester.Height = 3*pad_y - 1 + text_h + msg->button.gadget.Height;
+ msg->button.gadget.LeftEdge = (msg->requester.Width -
+ msg->button.gadget.Width) / 2;
+ msg->button.gadget.TopEdge = msg->requester.Height -
+ msg->button.gadget.Height - pad_y;
+ msg->requester.LeftEdge = max(0, window->BorderLeft + (window->Width -
+ window->BorderLeft - window->BorderRight - msg->requester.Width) / 2);
+ msg->requester.TopEdge = max(0, window->BorderTop + (window->Height -
+ window->BorderTop - window->BorderBottom - msg->requester.Height) / 2);
+ SetAttrs(inner_frame,
+ IA_Left, (LONG) pad_x,
+ IA_Top, (LONG) pad_y,
+ IA_Width, (LONG) text_w,
+ IA_Height, (LONG) text_h,
+ TAG_DONE);
+ SetAttrs(outer_frame,
+ IA_Width, (LONG) msg->requester.Width,
+ IA_Height, (LONG) msg->requester.Height,
+ TAG_DONE);
+ SetAttrs(background,
+ IA_Width, (LONG) msg->requester.Width,
+ IA_Height, (LONG) msg->requester.Height,
+ IA_FGPen, (ULONG) draw_info->dri_Pens[SHINEPEN],
+ TAG_DONE);
+}
+
+void message_dest(Message *msg)
+{
+ button_dest(&msg->button);
+ dispose_images(msg->requester.ReqImage);
+}
diff --git a/message.h b/message.h
new file mode 100755
index 0000000..6ceabcd
--- /dev/null
+++ b/message.h
@@ -0,0 +1,43 @@
+/*
+ToolsMenu - Add tools to the Workbench Tools menu
+
+Copyright (C) 2015, 2018 Kim Fastrup Larsen
+
+This program is free software: you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, either ver-
+sion 3 of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be use-
+ful, but WITHOUT ANY WARRANTY; without even the implied war-
+ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public Li-
+cense along with this program. If not, see
+.
+
+The author can be contacted on
+*/
+
+#ifndef MESSAGE_H
+#define MESSAGE_H
+
+#include
+
+typedef struct {
+ struct Gadget gadget;
+ struct IntuiText text;
+} Button;
+
+typedef struct {
+ struct Requester requester;
+ Button button;
+} Message;
+
+int message_cons(Message *);
+void message_layout(Message *, struct Window *, struct DrawInfo *,
+ struct IntuiText *text, UBYTE *button_text);
+void message_dest(Message *);
+
+#endif
diff --git a/smakefile b/smakefile
new file mode 100755
index 0000000..65b600c
--- /dev/null
+++ b/smakefile
@@ -0,0 +1,56 @@
+LIB = LIB:scs.lib LIB:amiga.lib
+LINK = slink
+LINKOPT = SMALLCODE SMALLDATA DEFINE @__chkabort=@__dummy DEFINE @_CXBRK=@__dummy QUIET
+
+STARTUP = LIB:c.o
+
+NAME = ToolsMenu
+NAMELC = toolsmenu
+OBJ = args.o broker.o catalog.o common.o cx.o error.o gui.o io.o main.o message.o sprintf.o toolrun.o wb.o
+DANSKCAT = Catalogs/dansk/$(NAMELC).catalog
+
+all: $(NAME) $(NAME).info empty.ct $(DANSKCAT)
+ @Echo "*nBuild succeeded."
+
+clean:
+ -Delete >NIL: \#?.o $(NAME) $(NAME).info empty.ct $(DANSKCAT)
+ -Delete >NIL: Catalogs/dansk
+ -Delete >NIL: Catalogs
+
+$(NAME): $(OBJ)
+ $(LINK) FROM $(STARTUP) $(OBJ) TO $@ LIB $(LIB) $(LINKOPT)
+
+$(NAME).info:
+ @Copy Resources/$(NAME).info ""
+
+.asm.o:
+ GenAm FROM $< TO $@ ALINK QUIET
+
+args.o: args.c args.h common.h
+broker.o: broker.c broker.h args.h catalog.h common.h cx.h error.h strings.h
+catalog.o: catalog.c catalog.h strings.h
+common.o: common.c common.h
+cx.o: cx.c cx.h args.h common.h gui.h io.h wb.h
+error.o: error.c error.h catalog.h common.h strings.h
+gui.o: gui.c gui.h args.h catalog.h common.h compiler.h cx.h message.h strings.h wb.h
+io.o: io.c io.h common.h cx.h
+main.o: main.c args.h broker.h catalog.h common.h cx.h error.h gui.h strings.h toolrun.h wb.h
+message.o: message.c message.h
+sprintf.o: sprintf.asm
+toolrun.o: toolrun.c toolrun.h common.h
+wb.o: wb.c wb.h common.h cx.h toolrun.h
+
+strings.h: $(NAMELC).cd
+ CatComp $(NAMELC).cd CFILE strings.h
+
+empty.ct: $(NAMELC).cd
+ CatComp $(NAMELC).cd CTFILE empty.ct
+
+$(DANSKCAT): $(NAMELC).cd dansk.ct Catalogs/dansk
+ CatComp $(NAMELC).cd dansk.ct CATALOG $(DANSKCAT)
+
+Catalogs/dansk: Catalogs
+ @MakeDir Catalogs/dansk
+
+Catalogs:
+ @MakeDir Catalogs
diff --git a/sprintf.asm b/sprintf.asm
new file mode 100755
index 0000000..ea9d107
--- /dev/null
+++ b/sprintf.asm
@@ -0,0 +1,45 @@
+; ToolsMenu - Add tools to the Workbench Tools menu
+;
+; Copyright (C) 2015, 2018 Kim Fastrup Larsen
+;
+; This program is free software: you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, either ver-
+; sion 3 of the License, or (at your option) any later version.
+;
+; This program is distributed in the hope that it will be use-
+; ful, but WITHOUT ANY WARRANTY; without even the implied war-
+; ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+; See the GNU General Public License for more details.
+;
+; You should have received a copy of the GNU General Public Li-
+; cense along with this program. If not, see
+; .
+;
+; The author can be contacted on
+;
+; Simple version of the C "sprintf" function. Assumes C-style
+; stack-based function conventions.
+
+ XDEF _sprintf
+
+ XREF _AbsExecBase
+ XREF _LVORawDoFmt
+
+_sprintf:
+ movem.l a2/a3/a6,-(sp)
+
+ move.l 4*4(sp),a3 ; Get the output string pointer
+ move.l 5*4(sp),a0 ; Get the FormatString pointer
+ lea.l 6*4(sp),a1 ; Get the pointer to the DataStream
+ lea.l stuffChar(pc),a2
+ move.l _AbsExecBase,a6
+ jsr _LVORawDoFmt(a6)
+
+ movem.l (sp)+,a2/a3/a6
+ rts
+
+; PutChProc used by RawDoFmt
+stuffChar:
+ move.b d0,(a3)+
+ rts
diff --git a/toolrun.c b/toolrun.c
new file mode 100755
index 0000000..251f87f
--- /dev/null
+++ b/toolrun.c
@@ -0,0 +1,183 @@
+/*
+ToolsMenu - Add tools to the Workbench Tools menu
+
+Copyright (C) 2015, 2018 Kim Fastrup Larsen
+
+This program is free software: you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, either ver-
+sion 3 of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be use-
+ful, but WITHOUT ANY WARRANTY; without even the implied war-
+ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public Li-
+cense along with this program. If not, see
+.
+
+The author can be contacted on
+*/
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#ifdef USE_PRAGMAS
+#include
+#include
+#include
+#endif
+
+#include
+#include
+
+#include "common.h"
+#include "toolrun.h"
+
+#define MIN_STACK_SIZE 4000
+
+/* We send this extended WBStartup to any tool that we launch. It will
+ contain the Workbench arguments passed to run_tool(). Once the tool quits,
+ it will reply to this message, and at that point we call the clean_up
+ function (also passed to run_tool()) to let it clean up the arguments. */
+typedef struct {
+ struct WBStartup wb_startup;
+ Tr_clean_up *clean_up;
+ void *user_data;
+} Startmsg;
+
+/* The tools we run will be told to reply to this port when they quit. */
+static struct MsgPort *reply_port;
+/* The number of tools started that haven't quit yet. */
+static int running;
+
+static void free_startmsg(Startmsg *msg)
+{
+ FreeVec(msg->wb_startup.sm_ToolWindow);
+ FreeMem(msg, sizeof *msg);
+}
+
+/* Create the reply port and return its signal bit as a mask. */
+ULONG tr_set_up()
+{
+ if ((reply_port = CreateMsgPort()) == NULL)
+ exit(RETURN_FAIL);
+ return 1UL << reply_port->mp_SigBit;
+}
+
+Bool tr_run_tool(struct WBArg *arg_list, LONG num_args,
+ Tr_clean_up *clean_up, void *user_data)
+{
+ BPTR old_dir;
+ struct DiskObject *disk_obj;
+ Startmsg *startmsg;
+ LONG stack_size;
+ BPTR home_dir;
+ struct Process *process;
+
+ /* Load the tool's icon and check that it really is a Workbench tool. */
+ old_dir = CurrentDir(arg_list[0].wa_Lock);
+ if ((disk_obj = GetDiskObject(arg_list[0].wa_Name)) == NULL)
+ goto run_tool_failed1;
+ if (disk_obj->do_Type != WBTOOL)
+ goto run_tool_failed2;
+ /* Use the stacksize specified in the icon unless it's too small. */
+ if ((stack_size = disk_obj->do_StackSize) < MIN_STACK_SIZE)
+ stack_size = MIN_STACK_SIZE;
+ /* Set up startup message. */
+ if ((startmsg=AllocMem(sizeof *startmsg,MEMF_PUBLIC|MEMF_CLEAR)) == NULL)
+ goto run_tool_failed2;
+ startmsg->wb_startup.sm_ArgList = arg_list;
+ startmsg->wb_startup.sm_NumArgs = num_args;
+ startmsg->clean_up = clean_up;
+ startmsg->user_data = user_data;
+ /* We copy the icon's ToolWindow if it isn't NULL. */
+ if (disk_obj->do_ToolWindow != NULL &&
+ (startmsg->wb_startup.sm_ToolWindow = copy_of(disk_obj->do_ToolWindow))
+ == NULL)
+ goto run_tool_failed3;
+ /* Load the tool. */
+ if ((startmsg->wb_startup.sm_Segment = NewLoadSegTags(
+ arg_list[0].wa_Name, TAG_DONE)) == NULL)
+ goto run_tool_failed3;
+ /* Get a lock for PROGDIR. */
+ if ((home_dir = DupLock(arg_list[0].wa_Lock)) == 0)
+ goto run_tool_failed4;
+ /* Run the tool as a non-CLI process. */
+ if ((process = CreateNewProcTags(
+ NP_Seglist, startmsg->wb_startup.sm_Segment,
+ NP_FreeSeglist, (LONG) TRUE,
+ NP_Input, NULL,
+ NP_CloseInput, (LONG) FALSE,
+ NP_Output, NULL,
+ NP_CloseOutput, (LONG) FALSE,
+ NP_Error, NULL,
+ NP_CloseError, (LONG) FALSE,
+ NP_CurrentDir, NULL,
+ NP_StackSize, stack_size,
+ NP_Name, arg_list[0].wa_Name,
+ NP_Priority, 0L,
+ NP_ConsoleTask, NULL,
+ NP_WindowPtr, NULL,
+ NP_HomeDir, home_dir,
+ TAG_DONE)) == NULL)
+ goto run_tool_failed5;
+ /* We have started the process, so we mustn't do anything after this point
+ that might fail. Send the startup message to the tool. */
+ startmsg->wb_startup.sm_Process = &process->pr_MsgPort;
+ startmsg->wb_startup.sm_Message.mn_ReplyPort = reply_port;
+ PutMsg(startmsg->wb_startup.sm_Process, (struct Message *) startmsg);
+ ++running;
+ FreeDiskObject(disk_obj);
+ CurrentDir(old_dir);
+ return YES;
+
+run_tool_failed5:
+ UnLock(home_dir);
+
+run_tool_failed4:
+ UnLoadSeg(startmsg->wb_startup.sm_Segment);
+
+run_tool_failed3:
+ free_startmsg(startmsg);
+
+run_tool_failed2:
+ FreeDiskObject(disk_obj);
+
+run_tool_failed1:
+ CurrentDir(old_dir);
+ return NO;
+}
+
+/* When a tool started by tr_run_tool() quits, we receive a reply to our
+ startup message. At this point we call the clean up function provided,
+ then clean up the message itself. */
+void tr_handle_signal()
+{
+ Startmsg *msg;
+
+ while ((msg = (Startmsg *) GetMsg(reply_port)) != NULL) {
+ (*msg->clean_up)(msg->wb_startup.sm_ArgList,
+ msg->wb_startup.sm_NumArgs, msg->user_data);
+ free_startmsg(msg);
+ --running;
+ }
+}
+
+/* Wait for all currently running tools to quit before cleaning up the
+ reply port and returning control to the caller. */
+void tr_clean_up()
+{
+ while (running > 0 ) {
+ WaitPort(reply_port);
+ tr_handle_signal();
+ }
+ DeleteMsgPort(reply_port);
+}
diff --git a/toolrun.h b/toolrun.h
new file mode 100755
index 0000000..1f54369
--- /dev/null
+++ b/toolrun.h
@@ -0,0 +1,60 @@
+/*
+ToolsMenu - Add tools to the Workbench Tools menu
+
+Copyright (C) 2015, 2018 Kim Fastrup Larsen
+
+This program is free software: you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, either ver-
+sion 3 of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be use-
+ful, but WITHOUT ANY WARRANTY; without even the implied war-
+ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public Li-
+cense along with this program. If not, see
+.
+
+The author can be contacted on
+*/
+
+#ifndef TOOLRUN_H
+#define TOOLRUN_H
+
+#include "common.h"
+
+/* The signature of the function that will be called when a tool quits (see
+ below). */
+typedef void Tr_clean_up(struct WBArg *, LONG num_args, void *user_data);
+
+/* This function must be called once as part of program initialization. Do
+ not call tr_run_tool() before calling this function. It sets up a message
+ port, for which the signal bit will be returned as a mask. Whenever you
+ receive this signal, call tr_handle_signal() to deal with it. */
+ULONG tr_set_up(void);
+
+/* Run a tool as if started from Workbench.
+ arg_list: An array of WBArg structures. The first element must
+ represent the tool to run, and the rest are its arguments.
+ num_args: The number of elements in arg_list.
+ clean_up: A pointer to a function that will be called when the tool
+ quits - it will be called from within tr_handle_signal() or
+ tr_clean_up(). arg_list, num_args and user_data will be passed
+ to clean_up.
+ user_data: This can be anything. It will be passed to clean_up. */
+Bool tr_run_tool(struct WBArg *arg_list, LONG num_args,
+ Tr_clean_up *clean_up, void *user_data);
+
+/* You must call this function whenever you receive the signal returned by
+ tr_set_up(). */
+void tr_handle_signal(void);
+
+/* You must call this once before quitting. Do not call tr_run_tool() after
+ calling this function.
+ NB: This function will wait around for all tools started by tr_run_tool()
+ to quit before returning. */
+void tr_clean_up(void);
+
+#endif
diff --git a/toolsmenu.cd b/toolsmenu.cd
new file mode 100755
index 0000000..4e93694
--- /dev/null
+++ b/toolsmenu.cd
@@ -0,0 +1,71 @@
+; ToolsMenu - Add tools to the Workbench Tools menu
+;
+; Copyright (C) 2015, 2018 Kim Fastrup Larsen
+;
+; This program is free software: you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, either ver-
+; sion 3 of the License, or (at your option) any later version.
+;
+; This program is distributed in the hope that it will be use-
+; ful, but WITHOUT ANY WARRANTY; without even the implied war-
+; ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+; See the GNU General Public License for more details.
+;
+; You should have received a copy of the GNU General Public Li-
+; cense along with this program. If not, see
+; .
+;
+; The author can be contacted on
+;
+MSG_ERR_OPEN (//)
+Could not open %s
+;
+MSG_ERR_HOTKEY (//)
+Illegal hot key
+;
+MSG_WINDOW_TITLE (//)
+%s: Hot Key = <%s>
+;
+MSG_DESCRIPTION (//)
+Add tools to the Workbench Tools Menu
+; Description should be at most 40 characters
+MSG_PROJECT_MENU (//)
+Project
+;
+MSG_PROJECT_SAVE (//)
+Save Menu Items
+;
+MSG_PROJECT_HIDE (//)
+Hide
+;
+MSG_PROJECT_ABOUT (//)
+About...
+;
+MSG_PROJECT_QUIT (//)
+Quit
+;
+MSG_GAD_CONTINUE (//)
+Continue
+;
+MSG_GAD_MENU_ITEMS (//)
+Menu Items
+;
+MSG_GAD_NEW (//)
+New
+;
+MSG_GAD_DELETE (//)
+Delete
+;
+MSG_GAD_TOOL (//)
+Tool
+;
+MSG_INFO_1 (//)
+You can add tools
+;
+MSG_INFO_2 (//)
+by dragging their icons
+;
+MSG_INFO_3 (//)
+into this window.
+;
diff --git a/wb.c b/wb.c
new file mode 100755
index 0000000..40f34cf
--- /dev/null
+++ b/wb.c
@@ -0,0 +1,209 @@
+/*
+ToolsMenu - Add tools to the Workbench Tools menu
+
+Copyright (C) 2015, 2018 Kim Fastrup Larsen
+
+This program is free software: you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, either ver-
+sion 3 of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be use-
+ful, but WITHOUT ANY WARRANTY; without even the implied war-
+ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public Li-
+cense along with this program. If not, see
+.
+
+The author can be contacted on
+*/
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#ifdef USE_PRAGMAS
+#include
+#include
+#include
+#include
+#endif
+
+#include
+#include
+
+#include "common.h"
+#include "cx.h"
+#include "toolrun.h"
+#include "wb.h"
+
+static struct MsgPort *wb_port;
+
+static void add_tool_from_icon(char *name)
+{
+ char path[MAX_PATH_LENGTH + 1];
+ BPTR lock;
+
+ if ((lock = Lock(name, ACCESS_READ)) == NULL)
+ return;
+ if (NameFromLock(lock, path, MAX_PATH_LENGTH + 1))
+ cx_add_tool(name, path);
+ UnLock(lock);
+}
+
+static void handle_app_window_msg(struct AppMessage *msg)
+{
+ struct WBArg *arg;
+ struct WBArg *end = msg->am_ArgList + msg->am_NumArgs;
+ BPTR old_dir;
+ struct DiskObject *disk_obj;
+
+ cx_show();
+ for (arg = msg->am_ArgList; arg != end; ++arg) {
+ old_dir = CurrentDir(arg->wa_Lock);
+ /* Check that name isn't NULL and isn't empty, and that the icon is
+ a Workbench tool. */
+ if (arg->wa_Name != NULL && *arg->wa_Name != '\0')
+ if ((disk_obj = GetDiskObject(arg->wa_Name)) != NULL) {
+ if (disk_obj->do_Type == WBTOOL)
+ add_tool_from_icon(arg->wa_Name);
+ FreeDiskObject(disk_obj);
+ }
+ CurrentDir(old_dir);
+ }
+ ReplyMsg((struct Message *) msg);
+}
+
+static void free_args(struct WBArg *arg_list, LONG num_args)
+{
+ FreeVec(arg_list[0].wa_Name);
+ if (arg_list[0].wa_Lock != NULL)
+ UnLock(arg_list[0].wa_Lock);
+ FreeMem(arg_list, sizeof(struct WBArg) * num_args);
+}
+
+/* Creates the directory locks and tool names to pass to tr_run_tool(). It
+ actually only creates the first element, ie. the home directory and name
+ of the tool itself, the rest are copied from the AppMessage that
+ workbench.library sent us. */
+static struct WBArg *create_args(STRPTR path, struct WBArg *arg_list,
+ LONG num_args)
+{
+ struct WBArg *result;
+ size_t args_size;
+ UBYTE t;
+ UBYTE *file_part;
+
+ /* Create an array one element bigger than the one received from
+ workbench.library, so we can put the tool itself at the front. */
+ args_size = sizeof(struct WBArg) * num_args;
+ result = AllocMem(args_size+sizeof(struct WBArg),MEMF_PUBLIC|MEMF_CLEAR);
+ if (result == NULL)
+ return NULL;
+ /* Temporarily separate the file part and directory part of the tool's
+ path with a string terminator. */
+ t = *(file_part = FilePart(path));
+ *file_part = '\0';
+ /* Create the tool's directory lock. */
+ result[0].wa_Lock = Lock(path, ACCESS_READ);
+ /* Restore original string. */
+ *file_part = t;
+ if (result[0].wa_Lock == NULL)
+ goto create_args_failed;
+ /* Copy the tool's name. */
+ if ((result[0].wa_Name = copy_of(file_part)) == NULL)
+ goto create_args_failed;
+ /* The rest of the array is just a copy of the AppMessage array. */
+ if (args_size > 0)
+ memcpy(result + 1, arg_list, args_size);
+ return result;
+
+create_args_failed:
+ free_args(result, num_args + 1);
+ return NULL;
+}
+/* This function is called whenever a tool we started quits. At this point,
+ we reply to the workbench.library so it can free the arguments that we
+ borrowed. Called indirectly by tr_clean_up(), so we must not clean up
+ anything needed by reply_app_msg() until after the call to tr_clean_up().
+*/
+static void reply_app_msg(struct WBArg *arg_list, LONG num_args,
+ struct AppMessage *app_message)
+{
+ if (arg_list != NULL)
+ free_args(arg_list, num_args);
+ ReplyMsg((struct Message *) app_message);
+}
+
+static void handle_app_menu_item_msg(struct AppMessage *msg)
+{
+ Tool *t = (Tool *) msg->am_UserData;
+ struct WBArg *args = NULL;
+ LONG num_args = msg->am_NumArgs + 1;
+
+ /* Check that the path isn't empty and that the tool hasn't been removed
+ prior to handling this event, then create the Workbench arguments and
+ run the tool. */
+ if (*t->path == '\0' || !is_in_list(tools, &t->node) ||
+ (args = create_args(t->path,msg->am_ArgList,msg->am_NumArgs)) == NULL ||
+ !tr_run_tool(args, num_args, reply_app_msg, msg))
+ /* Failed to run, reply to the AppMessage right away. */
+ reply_app_msg(args, num_args, msg);
+}
+
+ULONG wb_set_up()
+{
+ if ((wb_port = CreateMsgPort()) == NULL)
+ exit(RETURN_FAIL);
+ return 1UL << wb_port->mp_SigBit;
+}
+
+struct AppWindow *wb_add_window(struct Window *window)
+{
+ return AddAppWindow(0, 0, window, wb_port, TAG_DONE);
+}
+
+void wb_remove_window(struct AppWindow *app_window)
+{
+ RemoveAppWindow(app_window);
+}
+
+void wb_add_tool(Tool *tool)
+{
+ tool->menu_item = AddAppMenuItemA(0, (ULONG) tool, tool->node.ln_Name,
+ wb_port, TAG_DONE);
+}
+
+void wb_remove_tool(Tool *tool)
+{
+ RemoveAppMenuItem(tool->menu_item);
+ tool->menu_item = NULL;
+}
+
+void wb_handle_signal(void)
+{
+ struct AppMessage *msg;
+
+ while ((msg = (struct AppMessage *) GetMsg(wb_port)) != NULL)
+ switch (msg->am_Type) {
+ case AMTYPE_APPWINDOW:
+ handle_app_window_msg(msg);
+ break;
+ case AMTYPE_APPMENUITEM:
+ handle_app_menu_item_msg(msg);
+ break;
+ }
+}
+
+void wb_clean_up()
+{
+ delete_port(wb_port);
+}
diff --git a/wb.h b/wb.h
new file mode 100755
index 0000000..b2adc40
--- /dev/null
+++ b/wb.h
@@ -0,0 +1,38 @@
+/*
+ToolsMenu - Add tools to the Workbench Tools menu
+
+Copyright (C) 2015, 2018 Kim Fastrup Larsen
+
+This program is free software: you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, either ver-
+sion 3 of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be use-
+ful, but WITHOUT ANY WARRANTY; without even the implied war-
+ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public Li-
+cense along with this program. If not, see
+.
+
+The author can be contacted on
+*/
+
+#ifndef WB_H
+#define WB_H
+
+#include
+
+#include "cx.h"
+
+ULONG wb_set_up(void);
+struct AppWindow *wb_add_window(struct Window *);
+void wb_remove_window(struct AppWindow *);
+void wb_add_tool(Tool *);
+void wb_remove_tool(Tool *);
+void wb_handle_signal(void);
+void wb_clean_up(void);
+
+#endif