/* * Copyright (c) 2010-2023 Julien Nadeau Carriere * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Graphical User Interface routines for SK. */ #include #include #include "sk.h" #include "sk_gui.h" #include AG_Object skVfsRoot; /* General-purpose VFS */ AG_Mutex skObjLock; static int nEditorWindows = 0; /* Object classes users can create directly. */ const char *skEditableClasses[] = { "SK:*", NULL }; /* * Display "Save changes?" dialog on exit. */ static void CloseObject(AG_Event *_Nonnull event) { AG_Window *win = AG_PTR(1); AG_Object *obj = AG_PTR(2); int save = AG_INT(3); if (save) { if (AG_ObjectSave(obj) == -1) { AG_TextMsgFromError(); /* TODO suggest "save as" */ return; } } AG_ObjectDetach(win); AG_ObjectDelete(obj); if (--nEditorWindows == 0) AG_Terminate(0); } static AG_Window * SK_GUI_PromptOptions(AG_Button **bOpts, Uint nbOpts, const char *fmt, ...) { char *text; AG_Window *win; AG_Box *bo; va_list ap; Uint i; va_start(ap, fmt); Vasprintf(&text, fmt, ap); va_end(ap); if ((win = AG_WindowNew(AG_WINDOW_MODAL | AG_WINDOW_NOTITLE | AG_WINDOW_NORESIZE)) == NULL) { AG_FatalError(NULL); } win->wmType = AG_WINDOW_WM_DIALOG; AG_WindowSetPosition(win, AG_WINDOW_CENTER, 0); AG_SetSpacing(win, "8"); AG_LabelNewS(win, 0, text); free(text); bo = AG_BoxNew(win, AG_BOX_HORIZ, AG_BOX_HOMOGENOUS | AG_BOX_HFILL); for (i = 0; i < nbOpts; i++) { bOpts[i] = AG_ButtonNewS(bo, 0, "XXXXXXXXXXX"); } AG_WindowShow(win); return (win); } static void WindowClose(AG_Event *_Nonnull event) { AG_Window *win = AG_SELF(); AG_Object *obj = AG_PTR(1); AG_Event ev; AG_Button *bOpts[3]; AG_Window *wDlg; if (!AG_ObjectChanged(obj)) { AG_EventArgs(&ev, "%p,%p,%i", win, obj, 0); CloseObject(&ev); return; } wDlg = SK_GUI_PromptOptions(bOpts, 3, _("Save changes to %s?"), OBJECT(obj)->name); AG_WindowAttach(win, wDlg); AG_ButtonText(bOpts[0], _("Save")); AG_SetEvent(bOpts[0], "button-pushed", CloseObject, "%p,%p,%i", win, obj, 1); AG_WidgetFocus(bOpts[0]); AG_ButtonText(bOpts[1], _("Discard")); AG_SetEvent(bOpts[1], "button-pushed", CloseObject, "%p,%p,%i", win, obj, 0); AG_ButtonText(bOpts[2], _("Cancel")); AG_SetEvent(bOpts[2], "button-pushed", AGWINDETACH(wDlg)); } /* Open a sketch for edition. */ AG_Window * SK_GUI_OpenObject(void *p) { AG_Object *obj = p; AG_Window *win = NULL; AG_Widget *wEdit; /* Invoke edit(), which may return a Window or some other Widget. */ Verbose("Opening %s (%s)\n", obj->name, obj->cls->name); if ((wEdit = obj->cls->edit(obj)) == NULL) { AG_SetError("%s no edit()", obj->cls->name); return (NULL); } if (AG_WINDOW_ISA(wEdit)) { win = (AG_Window *)wEdit; } else if (AG_WIDGET_ISA(wEdit)) { if ((win = AG_WindowNew(AG_WINDOW_MAIN)) == NULL) { return (NULL); } AG_ObjectAttach(win, wEdit); } else { AG_SetError("%s: edit() illegal object", obj->cls->name); return (NULL); } win->flags |= AG_WINDOW_MAIN; AG_WindowSetCaptionS(win, AG_Defined(obj,"archive-path") ? AG_ShortFilename(AG_GetStringP(obj,"archive-path")) : obj->name); AG_SetEvent(win, "window-close", WindowClose, "%p", obj); AG_SetPointer(win, "object", obj); AG_PostEvent(obj, "edit-open", NULL); nEditorWindows++; AG_WindowShow(win); return (win); } /* Create a new sketch instance. */ void SK_GUI_NewObject(AG_Event *event) { AG_ObjectClass *cl = AG_PTR(1); AG_Object *obj; if (cl == &skClass) { obj = (AG_Object *)SK_New(&skVfsRoot, NULL); } else { obj = AG_ObjectNew(&skVfsRoot, NULL, cl); } if (obj == NULL || SK_GUI_OpenObject(obj) == NULL) { goto fail; } return; fail: AG_TextError(_("Failed to create object: %s"), AG_GetError()); if (obj != NULL) { AG_ObjectDelete(obj); } } /* Load an native SK sketch file. */ void SK_GUI_LoadObject(AG_Event *event) { AG_ObjectClass *cl = AG_PTR(1); char *path = AG_STRING(2); AG_Object *obj; if ((obj = AG_ObjectNew(&skVfsRoot, NULL, cl)) == NULL) { AG_TextMsgFromError(); return; } if (AG_ObjectLoadFromFile(obj, path) == -1) { goto fail; } AG_SetString(obj, "archive-path", path); AG_ObjectSetNameS(obj, AG_ShortFilename(path)); if (SK_GUI_OpenObject(obj) == NULL) { goto fail; } return; fail: AG_TextMsgFromError(); AG_ObjectDestroy(obj); } /* "Open..." dialog */ void SK_GUI_OpenDlg(AG_Event *event) { AG_Window *win; AG_FileDlg *fd; if ((win = AG_WindowNew(0)) == NULL) { return; } AG_WindowSetCaptionS(win, _("Open...")); fd = AG_FileDlgNewMRU(win, "sk-objs", AG_FILEDLG_LOAD | AG_FILEDLG_CLOSEWIN | AG_FILEDLG_EXPAND); AG_FileDlgSetOptionContainer(fd, AG_BoxNewVert(win, AG_BOX_HFILL)); AG_FileDlgAddType(fd, _("Agar sketch file"), "*.sk", SK_GUI_LoadObject, "%p", &skClass); AG_WindowShow(win); } /* Save an object file in native .sk format. */ static void SaveNativeObject(AG_Event *_Nonnull event) { AG_Object *obj = AG_PTR(1); char *path = AG_STRING(2); AG_Window *wEdit; if (AG_ObjectSaveToFile(obj, path) == -1) { AG_TextMsgFromError(); return; } AG_SetString(obj, "archive-path", path); AG_ObjectSetNameS(obj, AG_ShortFilename(path)); if ((wEdit = AG_WindowFindFocused()) != NULL) AG_WindowSetCaptionS(wEdit, AG_ShortFilename(path)); } /* "Save as..." dialog. */ void SK_GUI_SaveAsDlg(AG_Event *event) { char defDir[AG_PATHNAME_MAX]; AG_Object *obj = AG_PTR(1); AG_Window *win; AG_FileDlg *fd; if (obj == NULL) { AG_TextError(_("No document is selected for saving.")); return; } if ((win = AG_WindowNew(0)) == NULL) { return; } AG_WindowSetCaption(win, _("Save %s as..."), obj->name); fd = AG_FileDlgNew(win, AG_FILEDLG_SAVE | AG_FILEDLG_CLOSEWIN | AG_FILEDLG_EXPAND); AG_FileDlgSetOptionContainer(fd, AG_BoxNewVert(win, AG_BOX_HFILL)); AG_ConfigGetPath(AG_CONFIG_PATH_DATA, 0, defDir, sizeof(defDir)); AG_FileDlgSetDirectoryMRU(fd, "agar-sk.mru.files", defDir); if (SK_ISA(obj)) { AG_FileDlgAddType(fd, _("Agar sketch file"), "*.sk", SaveNativeObject, "%p", obj); } AG_WindowShow(win); } /* "Save" action */ void SK_GUI_Save(AG_Event *event) { AG_Object *obj = AG_PTR(1); if (obj == NULL) { AG_TextError(_("No document is selected for saving.")); return; } if (!AG_Defined(obj, "archive-path")) { SK_GUI_SaveAsDlg(event); return; } if (AG_ObjectSave(obj) == -1) { AG_TextError(_("Error saving object: %s"), AG_GetError()); } else { AG_TextTmsg(AG_MSG_INFO, 1250, _("Saved %s successfully"), AG_GetStringP(obj, "archive-path")); } } /* Undo last action. */ void SK_GUI_Undo(AG_Event *event) { /* TODO */ } /* Redo last undone action. */ void SK_GUI_Redo(AG_Event *event) { /* TODO */ } /* Standard Edit / Preferences dialog. */ void SK_GUI_EditPreferences(AG_Event *event) { } static void SelectedFont(AG_Event *_Nonnull event) { AG_Window *win = AG_PTR(1); Strlcpy(agConfig->fontFace, OBJECT(agDefaultFont)->name, sizeof(agConfig->fontFace)); agConfig->fontSize = agDefaultFont->spec.size; agConfig->fontFlags = agDefaultFont->flags; (void)AG_ConfigSave(); AG_TextWarning("default-font-changed", _("The default font has been changed.\n" "Please restart application for this change to take effect.")); AG_ObjectDetach(win); } /* "Select font" dialog */ void SK_GUI_SelectFontDlg(AG_Event *event) { AG_Window *win; AG_FontSelector *fs; AG_Box *hBox; win = AG_WindowNew(0); AG_WindowSetCaptionS(win, _("Font selection")); fs = AG_FontSelectorNew(win, AG_FONTSELECTOR_EXPAND); AG_BindPointer(fs, "font", (void *)&agDefaultFont); hBox = AG_BoxNewHoriz(win, AG_BOX_HFILL|AG_BOX_HOMOGENOUS); AG_ButtonNewFn(hBox, 0, _("OK"), SelectedFont, "%p", win); AG_ButtonNewFn(hBox, 0, _("Cancel"), AGWINCLOSE(win)); AG_WindowShow(win); } /* Build a generic "File" menu. */ void SK_FileMenu(AG_MenuItem *m, void *obj) { AG_MenuActionKb(m, _("New sketch..."), agIconDoc.s, AG_KEY_N, AG_KEYMOD_CTRL, SK_GUI_NewObject, "%p", &skClass); AG_MenuSeparator(m); AG_MenuActionKb(m, _("Open..."), agIconLoad.s, AG_KEY_O, AG_KEYMOD_CTRL, SK_GUI_OpenDlg, NULL); AG_MenuActionKb(m, _("Save"), agIconSave.s, AG_KEY_S, AG_KEYMOD_CTRL, SK_GUI_Save, "%p", obj); AG_MenuAction(m, _("Save as..."), agIconSave.s, SK_GUI_SaveAsDlg, "%p", obj); } /* Build a generic "Edit" menu. */ void SK_EditMenu(AG_MenuItem *m, void *obj) { AG_MenuActionKb(m, _("Undo"), agIconUp.s, AG_KEY_Z, AG_KEYMOD_CTRL, SK_GUI_Undo, "%p", obj); AG_MenuActionKb(m, _("Redo"), agIconDown.s, AG_KEY_R, AG_KEYMOD_CTRL, SK_GUI_Redo, "%p", obj); AG_MenuSeparator(m); AG_MenuAction(m, _("Select font..."), agIconMagnifier.s, SK_GUI_SelectFontDlg, NULL); } /* Initialize Agar-SK GUI globals. */ void SK_InitGUI(void) { if (agGUI) { AG_RegisterClass(&skViewClass); } AG_ObjectInit(&skVfsRoot, NULL); skVfsRoot.flags |= AG_OBJECT_STATIC; AG_ObjectSetName(&skVfsRoot, "Agar-SK VFS"); AG_MutexInitRecursive(&skObjLock); } /* Release Agar-SK GUI globals. */ void SK_DestroyGUI(void) { if (agGUI) { AG_UnregisterClass(&skViewClass); } AG_MutexDestroy(&skObjLock); AG_ObjectDestroy(&skVfsRoot); }