/* retawq/main.c - main routine, user interaction
This file is part of retawq (), a network
client created by Arne Thomassen; retawq is basically released under certain
versions of the GNU General Public License and WITHOUT ANY WARRANTY.
Read the file COPYING for license details, README for program information.
Copyright (C) 2001-2005 Arne Thomassen
*/
#include "stuff.h"
#include "init.h"
#include "resource.h"
#include "parser.h"
#if HAVE_ARPA_INET_H
#include
#endif
#if CONFIG_TG == TG_X
#include
#elif CONFIG_TG == TG_GTK
#include
#endif
declare_local_i18n_buffer
/** Strings */
static char strbuf[STRBUF_SIZE], strbuf2[STRBUF_SIZE], strbuf3[STRBUF_SIZE];
#if CONFIG_DEBUG
static char debugstrbuf[STRBUF_SIZE];
#endif
static const char strPercsDash[] = "%s - ", strDotSlash[] = "./",
strSlashSlash[] = "//";
#define strDot (strDoubleDot + 1) /* ugly :-) */
static const char strBracedNewWindow[] = N_("(new window)"),
strBack[] = N_("Back"), strForward[] = N_("Forward"),
strReload[] = N_("Reload"), strEnforcedReload[] = N_("Enforced Reload"),
strSearch[] = N_("Search"), strOpenInNewWindow[] = N_("Open in New Window"),
strSource[] = N_("source"), strSourceCode[] = N_("Source Code"),
strSave[] = N_("Save"), strEnableThisElement[] = N_("Enable this Element"),
strContext[] = N_("Context"), strShowElementInfo[] = N_("Show Element Info"),
strOpenLinkInNewWindow[] = N_("Open Link in New Window"),
strSelectionEmpty[] = N_("This selection list is empty!"),
strAddBookmark[] = N_("Add Bookmark"), strSaveAs[] = N_("Save As..."),
strSaveLinkAs[] = N_("Save Link As..."), strUriColonSpace[] = N_("URL: "),
strSubmitThisForm[] = N_("Submit this Form");
#if OPTION_COOKIES
static const char strCookies[] = N_("(cookies) ");
#endif
#if TGC_IS_GRAPHICS || CONFIG_CUSTOM_CONN
static const char strUcDownload[] = N_("Download");
#endif
#if CONFIG_SESSIONS || CONFIG_CONSOLE
static const char strDone[] = N_("Done");
#endif
static const char* const strAek[MAX_AEK + 1] =
{ strUnknown, strLink, N_("text field"), N_("password field"),
N_(strCheckbox), N_("radio button"), N_("submit button"), N_("reset button"),
N_("file field"), N_("text-area field"), N_("selection"), N_(strButton),
N_(strImage), N_("hidden field")
};
static const char strUcClose[] = N_("Close"), strUcQuit[] = N_("Quit");
#if CONFIG_KEYMAPS
static const char strMouseFlip[] = "mouse-flip", strMouseOff[] = "mouse-off",
strMouseOn[] = "mouse-on";
#endif
#if CONFIG_FTP
static const char strAnonymous[] = "anonymous";
#endif
/** Keys'n'curses */
#if TGC_IS_GRAPHICS
#if CONFIG_TG == TG_X
#define KEY_ESCAPE (XK_Escape)
#define KEY_CANCEL (XK_Cancel)
#define KEY_LEFT (XK_Left)
#define KEY_RIGHT (XK_Right)
#define KEY_END (XK_End)
#define KEY_NPAGE (XK_Page_Down)
#define KEY_PPAGE (XK_Page_Up)
#define KEY_DOWN (XK_Down)
#define KEY_UP (XK_Up)
#define KEY_HOME (XK_Home)
#define KEY_ENTER (XK_Return)
#define KEY_BACKSPACE (XK_BackSpace)
#define KEY_DC (XK_Delete)
#define KEY_IC (XK_Insert)
#elif CONFIG_TG == TG_GTK
FAIL!
#define KEY_ESCAPE (GDK_Escape)
#define KEY_CANCEL (GDK_Cancel)
#define KEY_LEFT (GDK_Left)
#define KEY_RIGHT (GDK_Right)
#define KEY_END (GDK_End)
#define KEY_NPAGE (GDK_Page_Down)
#define KEY_PPAGE (GDK_Page_Up)
#define KEY_DOWN (GDK_Down)
#define KEY_UP (GDK_Up)
#define KEY_HOME (GDK_Home)
#define KEY_ENTER (GDK_Return)
#define KEY_BACKSPACE (GDK_BackSpace)
#define KEY_DC (GDK_Delete)
#define KEY_IC (GDK_Insert)
#endif
/* stubs'n'dummies - for now... */
#define COLS (80)
#define LINES (25)
#define A_NORMAL (0)
#define A_BOLD (0)
#define A_UNDERLINE (0)
#define A_REVERSE (0)
#define OK (0)
#define ERR (-1)
typedef unsigned long chtype;
typedef chtype attr_t;
typedef struct { short _curx, _cury; } tTextualWindowContents;
static my_inline int move(int y __cunused, int x __cunused) { return(OK); }
static my_inline int clrtoeol(void) { return(OK); }
static my_inline int addnstr(const char* s __cunused, int n __cunused)
{ return(OK); }
static my_inline int mvaddnstr(int y, int x, const char* s, int n)
{ move(y, x); addnstr(s, n); return(OK); }
static my_inline int addch(const chtype c __cunused) { return(OK); }
static my_inline int mvaddch(int y, int x, const chtype c)
{ move(y, x); addch(c); return(OK); }
static my_inline int addchnstr(const chtype* s __cunused, int n __cunused)
{ return(OK); }
static my_inline int COLOR_PAIR(int x __cunused) { return(0); }
static my_inline int color_set(short x __cunused, void* d __cunused)
{ return(OK); }
/* static my_inline int attron(attr_t a __cunused) { return(OK); }
static my_inline int attroff(attr_t a __cunused) { return(OK); }
static one_caller int refresh(void) { return(OK); } */
static my_inline chtype inch(void) { return(0); }
#elif TGC_IS_CURSES
#if CONFIG_TG != TG_XCURSES
#undef KEY_ESCAPE
#define KEY_ESCAPE '\033'
#endif
#ifdef ACS_HLINE
#define __MY_HLINE (ACS_HLINE)
#else
#define __MY_HLINE ('-')
#endif
#ifdef ACS_VLINE
#define __MY_VLINE (ACS_VLINE)
#else
#define __MY_VLINE ('|')
#endif
#ifdef ACS_ULCORNER
#define __MY_UL (ACS_ULCORNER)
#else
#define __MY_UL ('+')
#endif
#ifdef ACS_URCORNER
#define __MY_UR (ACS_URCORNER)
#else
#define __MY_UR ('+')
#endif
#ifdef ACS_LLCORNER
#define __MY_LL (ACS_LLCORNER)
#else
#define __MY_LL ('+')
#endif
#ifdef ACS_LRCORNER
#define __MY_LR (ACS_LRCORNER)
#else
#define __MY_LR ('+')
#endif
#ifdef ACS_LTEE
#define __MY_LTEE (ACS_LTEE)
#else
#define __MY_LTEE ('+')
#endif
#ifdef ACS_RTEE
#define __MY_RTEE (ACS_RTEE)
#else
#define __MY_RTEE ('+')
#endif
#define VMIDDLE (MAX(((LINES - 2) / 2), 1))
static tBoolean __must_reset_cursor = falsE;
static __my_inline void must_reset_cursor(void) { __must_reset_cursor = truE; }
#endif
#ifndef KEY_TAB
#define KEY_TAB '\t'
#endif
#if OPTION_CED == 0
#define is_bad_uchar(c) ( ((c) < 32) || ((c) >= 127) )
#else
#define is_bad_uchar(c) ( ((c) < 32) || ((c) == 127) )
#endif
/** Keymaps */
/* begin-autogenerated */
my_enum1 enum
{ pccUnknown = 0, pccDocumentBottom = 1, pccDocumentEnforceHtml = 2,
pccDocumentEnforceSource = 3, pccDocumentInfo = 4, pccDocumentReload = 5,
pccDocumentReloadEnforced = 6, pccDocumentSave = 7, pccDocumentSearch = 8,
pccDocumentSearchBackward = 9, pccDocumentSearchNext = 10,
pccDocumentSearchPrevious = 11, pccDocumentTop = 12, pccDownload = 13,
pccDownloadFromElement = 14, pccDump = 15, pccElementEnable = 16,
pccElementInfo = 17, pccElementNext = 18, pccElementOpen = 19,
pccElementOpenSplit = 20, pccElementPrevious = 21, pccExecextShell = 22,
pccExecextShellFlip = 23, pccFormReset = 24, pccFormSubmit = 25,
pccGoBookmarks = 26, pccGoHome = 27, pccGoSearch = 28, pccGoUri = 29,
pccGoUriPreset = 30, pccJump = 31, pccJumpPreset = 32, pccLineDown = 33,
pccLineUp = 34, pccLocalFileDirOpen = 35, pccMenuContextual = 36,
pccMenuUriHistory = 37, pccMenuWindowlist = 38, pccMouseFlip = 39,
pccMouseOff = 40, pccMouseOn = 41, pccPageDown = 42, pccPageUp = 43,
pccQuit = 44, pccScreenSplit = 45, pccScreenSwitch = 46,
pccScreenUnsplit = 47, pccScrollBarsFlip = 48, pccSessionResume = 49,
pccSessionSave = 50, pccStop = 51, pccViewBack = 52, pccViewForward = 53,
pccWindowClose = 54, pccWindowNew = 55, pccWindowNewFromDocument = 56,
pccWindowNewFromElement = 57, pccWindowNext = 58, pccWindowPrevious = 59
} my_enum2(unsigned char) tProgramCommandCode;
typedef struct
{ tKey key;
tProgramCommandCode pcc;
} tKeymapCommandEntry;
static const tKeymapCommandEntry keymap_command_defaultkeys[] =
{ { '!', pccExecextShell },
{ '&', pccExecextShellFlip },
{ '.', pccStop },
{ '/', pccDocumentSearch },
{ '1', pccScreenUnsplit },
{ '2', pccScreenSplit },
{ '?', pccDocumentSearchBackward },
{ 'C', pccWindowClose },
{ 'D', pccDownloadFromElement },
{ 'E', pccElementEnable },
{ 'G', pccGoUriPreset },
{ 'H', pccDocumentEnforceHtml },
{ 'I', pccDocumentInfo },
{ 'J', pccJumpPreset },
{ 'L', pccLineUp },
{ 'M', pccSessionResume },
{ 'N', pccWindowNewFromDocument },
{ 'O', pccWindowNewFromElement },
{ 'Q', pccQuit },
{ 'R', pccDocumentReloadEnforced },
{ 'S', pccSessionSave },
{ 'W', pccWindowPrevious },
{ 'Y', pccMouseFlip },
{ '\\', pccDocumentEnforceSource },
{ 'b', pccGoBookmarks },
{ 4, pccDownload },
{ 15, pccElementOpenSplit },
{ 23, pccMenuWindowlist },
{ KEY_DOWN, pccElementNext },
{ KEY_LEFT, pccViewBack },
{ KEY_RIGHT, pccViewForward },
{ KEY_UP, pccElementPrevious },
{ 'd', pccDump },
{ KEY_DC, pccLineDown },
{ 'e', pccGoSearch },
{ KEY_END, pccDocumentBottom },
{ KEY_ENTER, pccElementOpen },
{ 'g', pccGoUri },
{ 'h', pccGoHome },
{ KEY_HOME, pccDocumentTop },
{ 'i', pccElementInfo },
{ KEY_IC, pccLineUp },
{ 'j', pccJump },
{ 'l', pccLineDown },
{ 'm', pccMenuContextual },
{ 'n', pccWindowNew },
{ 'o', pccElementOpen },
{ KEY_NPAGE, pccPageDown },
{ KEY_PPAGE, pccPageUp },
{ 'r', pccDocumentReload },
{ 's', pccDocumentSave },
{ ' ', pccPageDown },
{ KEY_TAB, pccScreenSwitch },
{ 'u', pccMenuUriHistory },
{ 'w', pccWindowNext }
};
#if CONFIG_KEYMAPS
static const struct
{ const char* str; /* (sorted in strcmp() order) */
tProgramCommandCode pcc; /* REMOVEME? */
} keymap_command_str2pcc[] =
{ { "document-bottom", pccDocumentBottom },
{ "document-enforce-html", pccDocumentEnforceHtml },
{ "document-enforce-source", pccDocumentEnforceSource },
{ "document-info", pccDocumentInfo },
{ "document-reload", pccDocumentReload },
{ "document-reload-enforced", pccDocumentReloadEnforced },
{ "document-save", pccDocumentSave },
{ "document-search", pccDocumentSearch },
{ "document-search-backward", pccDocumentSearchBackward },
{ "document-search-next", pccDocumentSearchNext },
{ "document-search-previous", pccDocumentSearchPrevious },
{ "document-top", pccDocumentTop },
{ "download", pccDownload },
{ "download-from-element", pccDownloadFromElement },
{ "dump", pccDump },
{ "element-enable", pccElementEnable },
{ "element-info", pccElementInfo },
{ "element-next", pccElementNext },
{ "element-open", pccElementOpen },
{ "element-open-split", pccElementOpenSplit },
{ "element-previous", pccElementPrevious },
{ "execext-shell", pccExecextShell },
{ "execext-shell-flip", pccExecextShellFlip },
{ "form-reset", pccFormReset },
{ "form-submit", pccFormSubmit },
{ "go-bookmarks", pccGoBookmarks },
{ "go-home", pccGoHome },
{ "go-search", pccGoSearch },
{ "go-url", pccGoUri },
{ "go-url-preset", pccGoUriPreset },
{ "jump", pccJump },
{ "jump-preset", pccJumpPreset },
{ "line-down", pccLineDown },
{ "line-up", pccLineUp },
{ "local-file-dir-open", pccLocalFileDirOpen },
{ "menu-contextual", pccMenuContextual },
{ "menu-url-history", pccMenuUriHistory },
{ "menu-windowlist", pccMenuWindowlist },
{ strMouseFlip, pccMouseFlip },
{ strMouseOff, pccMouseOff },
{ strMouseOn, pccMouseOn },
{ "page-down", pccPageDown },
{ "page-up", pccPageUp },
{ strQuit, pccQuit },
{ "screen-split", pccScreenSplit },
{ "screen-switch", pccScreenSwitch },
{ "screen-unsplit", pccScreenUnsplit },
{ "scroll-bars-flip", pccScrollBarsFlip },
{ "session-resume", pccSessionResume },
{ "session-save", pccSessionSave },
{ "stop", pccStop },
{ "view-back", pccViewBack },
{ "view-forward", pccViewForward },
{ "window-close", pccWindowClose },
{ "window-new", pccWindowNew },
{ "window-new-from-document", pccWindowNewFromDocument },
{ "window-new-from-element", pccWindowNewFromElement },
{ "window-next", pccWindowNext },
{ "window-previous", pccWindowPrevious }
};
#endif
my_enum1 enum
{ liacUnknown = 0, liacAreaSwitch = 1, liacCancel = 2, liacMouseFlip = 3,
liacMouseOff = 4, liacMouseOn = 5, liacPass2user = 6, liacToEnd = 7,
liacToLeft = 8, liacToRight = 9, liacToStart = 10
} my_enum2(unsigned char) tLineInputActionCode;
typedef struct
{ tKey key;
tLineInputActionCode liac;
} tKeymapLineinputEntry;
static const tKeymapLineinputEntry keymap_lineinput_defaultkeys[] =
{ { 1, liacAreaSwitch },
{ 21, liacPass2user },
{ KEY_DOWN, liacToEnd },
{ KEY_LEFT, liacToLeft },
{ KEY_RIGHT, liacToRight },
{ KEY_UP, liacToStart },
{ KEY_END, liacToEnd },
{ KEY_CANCEL, liacCancel },
{ KEY_HOME, liacToStart },
{ KEY_NPAGE, liacToEnd },
{ KEY_PPAGE, liacToStart }
};
#if CONFIG_KEYMAPS
static const struct
{ const char* str; /* (sorted in strcmp() order) */
tLineInputActionCode liac; /* REMOVEME? */
} keymap_lineinput_str2liac[] =
{ { "area-switch", liacAreaSwitch },
{ "cancel", liacCancel },
{ strMouseFlip, liacMouseFlip },
{ strMouseOff, liacMouseOff },
{ strMouseOn, liacMouseOn },
{ "pass2user", liacPass2user },
{ "to-end", liacToEnd },
{ "to-left", liacToLeft },
{ "to-right", liacToRight },
{ "to-start", liacToStart }
};
#endif
/* end-autogenerated */
static const_after_init tKeymapCommandEntry* keymap_command_keys = NULL;
static const_after_init size_t keymap_command_keys_num = 0,
keymap_command_keys_maxnum = 0;
static int __init keymap_command_sorter(const void* _a, const void* _b)
{ const tKeymapCommandEntry *a = (const tKeymapCommandEntry*) _a,
*b = (const tKeymapCommandEntry*) _b;
tKey ak = a->key, bk = b->key;
return(my_numcmp(ak, bk));
}
static tBoolean __init keymap_command_key_do_register(tKey key,
tProgramCommandCode pcc)
/* returns whether it worked */
{ size_t count;
for (count = 0; count < keymap_command_keys_num; count++) /* IMPROVEME? */
{ if (keymap_command_keys[count].key == key) return(falsE); } /* redefined */
if (keymap_command_keys_num >= keymap_command_keys_maxnum)
{ keymap_command_keys_maxnum += 32;
keymap_command_keys = (tKeymapCommandEntry*)
memory_reallocate(keymap_command_keys, keymap_command_keys_maxnum *
sizeof(tKeymapCommandEntry), mapKeymap);
}
keymap_command_keys[keymap_command_keys_num].key = key;
keymap_command_keys[keymap_command_keys_num].pcc = pcc;
keymap_command_keys_num++;
return(truE);
}
static const_after_init tKeymapLineinputEntry* keymap_lineinput_keys = NULL;
static const_after_init size_t keymap_lineinput_keys_num = 0,
keymap_lineinput_keys_maxnum = 0;
static int __init keymap_lineinput_sorter(const void* _a, const void* _b)
{ const tKeymapLineinputEntry *a = (const tKeymapLineinputEntry*) _a,
*b = (const tKeymapLineinputEntry*) _b;
tKey ak = a->key, bk = b->key;
return(my_numcmp(ak, bk));
}
static tBoolean __init keymap_lineinput_key_do_register(tKey key,
tLineInputActionCode liac)
/* returns whether it worked */
{ size_t count;
for (count = 0; count < keymap_lineinput_keys_num; count++) /* IMPROVEME? */
{ if (keymap_lineinput_keys[count].key == key) return(falsE); } /*redefined*/
if (keymap_lineinput_keys_num >= keymap_lineinput_keys_maxnum)
{ keymap_lineinput_keys_maxnum += 16;
keymap_lineinput_keys = (tKeymapLineinputEntry*)
memory_reallocate(keymap_lineinput_keys, keymap_lineinput_keys_maxnum *
sizeof(tKeymapLineinputEntry), mapKeymap);
}
keymap_lineinput_keys[keymap_lineinput_keys_num].key = key;
keymap_lineinput_keys[keymap_lineinput_keys_num].liac = liac;
keymap_lineinput_keys_num++;
return(truE);
}
#if CONFIG_KEYMAPS
static tKey __init keystr2key(const char* keystr)
{ tKey key = '\0';
unsigned char ch = *keystr;
if ( (ch != '\0') && (keystr[1] == '\0') ) key = ch; /* most likely case? */
else if (!strcmp(keystr, "delete")) key = KEY_DC;
else if (!strcmp(keystr, "end")) key = KEY_END;
/* else if (!strcmp(keystr, "enter")) key = KEY_ENTER; */
else if (!strcmp(keystr, "insert")) key = KEY_IC;
else if (!strcmp(keystr, "home")) key = KEY_HOME;
else if (!strcmp(keystr, "page-down")) key = KEY_NPAGE;
else if (!strcmp(keystr, "page-up")) key = KEY_PPAGE;
else if (!strcmp(keystr, "escape")) key = KEY_CANCEL;
else if (!strcmp(keystr, "space")) key = ' ';
else if (!strcmp(keystr, "tab")) key = '\t';
else if (!strncmp(keystr, "ctrl-", 5))
{ char cc = keystr[5]; /* "control character" */
if ( (my_islower(cc)) && (keystr[6] == '\0') ) key = cc - 'a' + 1;
}
else if (!strncmp(keystr, "cursor-", 7))
{ const char* tmp = keystr + 7;
if (!strcmp(tmp, "down")) key = KEY_DOWN;
else if (!strcmp(tmp, "left")) key = KEY_LEFT;
else if (!strcmp(tmp, "right")) key = KEY_RIGHT;
else if (!strcmp(tmp, "up")) key = KEY_UP;
}
#ifdef KEY_F
else if (!strncmp(keystr, "fn-", 3))
{ const char* tmp = keystr + 3;
if (my_isdigit(*tmp))
{ int num;
my_atoi(tmp, &num, &tmp, 99);
if ( (num >= 0) && (num <= 63) && (*tmp == '\0') ) key = KEY_F(num);
}
}
#endif
return(key);
}
static one_caller tMbsIndex keymap_cmdstr_lookup(const char* str)
{ my_binary_search(0, ARRAY_ELEMNUM(keymap_command_str2pcc) - 1,
strcmp(str, keymap_command_str2pcc[idx].str), return(idx))
}
unsigned char __init keymap_command_key_register(const char* keystr,
const char* cmdstr)
/* return value: 0=fine, 1=bad key identifier; 2=repeatedly defined key; 3=bad
command; IMPROVEME: binary search for ? */
{ tKey key = keystr2key(keystr);
tMbsIndex idx;
tProgramCommandCode pcc;
if (key == '\0') return(1);
/* interpret the command */
idx = keymap_cmdstr_lookup(cmdstr);
if (idx < 0) return(3);
pcc = keymap_command_str2pcc[idx].pcc;
/* register */
if (!keymap_command_key_do_register(key, pcc)) return(2);
return(0);
}
static one_caller tMbsIndex keymap_listr_lookup(const char* str)
{ my_binary_search(0, ARRAY_ELEMNUM(keymap_lineinput_str2liac) - 1,
strcmp(str, keymap_lineinput_str2liac[idx].str), return(idx))
}
unsigned char __init keymap_lineinput_key_register(const char* keystr,
const char* listr)
/* return value: 0=fine, 1=bad key identifier; 2=repeatedly defined key; 3=bad
command; IMPROVEME: binary search for ? */
{ tKey key = keystr2key(keystr);
tMbsIndex idx;
tLineInputActionCode liac;
if (key == '\0') return(1);
/* interpret the action */
idx = keymap_listr_lookup(listr);
if (idx < 0) return(3);
liac = keymap_lineinput_str2liac[idx].liac;
/* register */
if (!keymap_lineinput_key_do_register(key, liac)) return(2);
return(0);
}
#endif /* #if CONFIG_KEYMAPS */
#if TGC_IS_GRAPHICS
#define is_khm_command (truE) /* only one key-handling mode available... */
#else
enum
{ khmCommand = 0, khmLineInput = 1
#if CONFIG_MENUS
, khmMenu = 2
#endif
};
typedef unsigned char tKeyHandlingMode;
static tKeyHandlingMode key_handling_mode = khmCommand;
#define is_khm_command (key_handling_mode == khmCommand)
#define is_bottom_occupied (key_handling_mode == khmLineInput)
#endif
/** Helper functions */
static const char* my_getcwd(void)
{
#if !HAVE_GETCWD
return(strSlash); /* CHECKME! */
#else
static const char* cwd = NULL;
if (cwd == NULL) /* not yet calculated */
{ char buf[2060]; /* 1<<11 + 42*epsilon */
if ( (getcwd(buf, sizeof(buf) - 5) != buf) || (*buf == '\0') )
cwd = strSlash;
else
{ const size_t len = strlen(buf);
if (buf[len - 1] != chDirsep) { buf[len] = chDirsep; buf[len+1] = '\0'; }
cwd = my_strdup(buf);
}
}
return(cwd);
#endif
}
#if TGC_IS_GRAPHICS
static my_inline __sallocator char* __callocator
my_strdup_ucfirst(const char* str)
/* duplicates a string, changing the first character to uppercase */
{ char *retval = my_strdup(str), ch = *retval;
*retval = my_toupper(ch);
return(retval);
}
#endif
#if OPTION_TLS
static tBoolean tls_errtext(const tResource* resource, /*@out@*/ char* dest)
{ tBoolean retval;
tTlsError te = resource->tls_error;
if (!is_tls_error_expressive(te)) retval = falsE;
else
{ sprint_safe(dest, _("TLS error - %s"), _(strTlsError[te])); retval=truE; }
return(retval);
}
#endif
#if 1 /* !CONFIG_JAVASCRIPT */
#define javascript_handle_event(kind, _ae) do { } while (0)
#else
static void javascript_handle_event(tJavascriptEventKind kind,
tActiveElementNumber _ae)
{ tWindowView* view = current_window->current_view;
const tJavascriptEventHandler* eh = view->request->resource->aebase[_ae].eh;
while (eh != NULL)
{ if (eh->kind == kind)
{ const tJavascriptCode* code = eh->code;
if (code != NULL) javascript_execute(view, _ae, code);
break;
}
eh = eh->next;
}
}
#endif
/** Remaining work mechanism */
struct tRemainingWork;
typedef tBoolean (*tRemainingWorkCallback)(const struct tRemainingWork*);
typedef struct tRemainingWork
{ struct tRemainingWork* next;
tRemainingWorkCallback callback;
void *data1, *data2, *data3; /* private data for the callback handler */
} tRemainingWork;
static tRemainingWork *rw_head = NULL, *rw_tail = NULL;
static void remaining_work_store(tRemainingWork* rw)
{ if (rw_head == NULL) rw_head = rw;
if (rw_tail != NULL) rw_tail->next = rw;
rw_tail = rw;
}
static tRemainingWork* remaining_work_create(tRemainingWorkCallback callback)
{ tRemainingWork* retval = memory_allocate(sizeof(tRemainingWork), mapOther);
retval->callback = callback; remaining_work_store(retval); return(retval);
}
static void remaining_work_do(void)
{ tRemainingWork* rw = rw_head;
if (rw == NULL) return; /* the most likely case */
rw_head = rw_tail = NULL; /* detach */
while (rw != NULL)
{ tRemainingWork* next = rw->next;
if ((rw->callback)(rw)) remaining_work_store(rw); /* keep it for retry */
else memory_deallocate(rw);
rw = next;
}
}
/** Generic graphics stuff */
#if TGC_IS_GRAPHICS
static const char strCancel[] = N_("Cancel");
static const_after_init char *istrYesUc, *istrNoUc;
#if CONFIG_TG == TG_X
/* some minor, not-yet-complete abstraction for the future... */
typedef void tGraphicsWidget;
typedef void tGraphicsWindow;
typedef void tGraphicsLowlevelWindow;
typedef GC tGraphicsContext;
typedef XEvent tGraphicsEvent;
typedef XFontStruct tGraphicsFont;
Display* xws_display = NULL;
#else
/* some minor, not-yet-complete abstraction for the future... */
typedef GtkWidget tGraphicsWidget;
typedef GtkWindow tGraphicsWindow;
typedef GdkWindow tGraphicsLowlevelWindow;
typedef GdkGC tGraphicsContext;
typedef GdkEvent tGraphicsEvent;
typedef GdkFont tGraphicsFont;
static const char strGtkDestroy[] = "destroy", strGtkDelete[] = "delete_event",
strGtkConfigure[] = "configure_event", strGtkClicked[] = "clicked",
strGtkButton[] = "button_press_event", strGtkActivate[] = "activate",
strGtkKey[] = "key_press_event";
#endif
static const tGraphicsFont* default_font;
#endif /* #if TGC_IS_GRAPHICS */
/** Line input I */
my_enum1 enum
{ liekFail = 0, liekCancel = 1, liekKey = 2
} my_enum2(unsigned char) tLineInputEventKind;
typedef void (*tLineInputCallback)(void*, tLineInputEventKind, tKey key);
my_enum1 enum
{ liafNone = 0, liafFocusable = 0x01, liafEditable = 0x02,
liafDisguising = 0x04, liafAllowEmptyText = 0x08
} my_enum2(unsigned char) tLineInputAreaFlags;
typedef struct
{ char* text;
short len, maxlen, pos, first; /* CHECKME: size_t? */
short row, colmin, colcurr, colmax, usable;
tLineInputAreaFlags flags;
} tLineInputArea;
typedef unsigned char tLineInputAreaIndex;
my_enum1 enum
{ prrfNone = 0, prrfRedrawAll = 0x01, prrfRedrawOne = 0x02, prrfSource = 0x04,
prrfHtml = 0x08, prrfReload = 0x10, prrfEnforcedReload = 0x20,
prrfIsHttpRedirection = 0x40, prrfUseSfbuf = 0x80, prrfPost = 0x100,
prrfWantUriAnchor = 0x200, prrfUpsfp4 = 0x400
} my_enum2(unsigned short) tPrrFlags; /* for prepare_resource_request() */
#define prrfIsRedirection (prrfIsHttpRedirection)
static tSinkingData sinking_data;
static my_inline void sinking_data_reset(void)
{ my_memclr_var(sinking_data);
}
static void sinking_data_shift(/*@out@*/ tSinkingData** _s)
{ tSinkingData* s = *_s = __memory_allocate(sizeof(tSinkingData),
mapSinkingData);
my_memcpy(s, &sinking_data, sizeof(tSinkingData));
sinking_data_reset();
}
static my_inline void sinking_data_mcleanup(void)
{ sinking_data_cleanup(&sinking_data); sinking_data_reset();
}
static struct
{ tLineInputArea area[2];
#if TGC_IS_GRAPHICS
tWindow* window;
GtkEntry* entry;
#endif
tLineInputCallback callback;
void* callback_data;
tPrrFlags prrf; /* rarely needed */
tLineInputAreaIndex curr, num_areas;
} lid; /* aka "line input data" */
#define lid_area(what) (lid.area[lid.curr].what)
my_enum1 enum
{ eligGoToUri = 0, eligSaveAs = 1
#if CONFIG_EXTRA & EXTRA_DOWNLOAD
, eligDownloadUri = 2, eligDownloadFilename = 3
#endif
#if CONFIG_EXTRA & EXTRA_DUMP
, eligDumpFilename = 4
#endif
, eligDocumentSearch = 5, eligDocumentSearchBackward = 6
#if CONFIG_USER_QUERY
, eligUsername = 7, eligPassword = 8
#endif
#if !TGC_IS_GRAPHICS
, eligFormText = 9, eligFormPassword = 10, eligFormFilename = 11
#endif
#if CONFIG_SESSIONS
, eligSessionSave = 12, eligSessionResume = 13
#endif
#if CONFIG_JUMPS
, eligJump = 14
#endif
#if OPTION_EXECEXT & EXECEXT_SHELL
, eligExecextShell = 15
#endif
} my_enum2(unsigned char) tEditableLineInputGoal;
#if CONFIG_USER_QUERY
#define is_disguising_elig(elig) \
( ((elig) == eligFormPassword) || ((elig) == eligPassword) )
#else
#define is_disguising_elig(elig) ((elig) == eligFormPassword)
#endif
#define is_emptyok_elig(elig) ( ((elig) == eligFormText) || \
((elig) == eligFormPassword) || ((elig) == eligFormFilename) )
my_enum1 enum
{ cgQuit = 0, cgClose = 1, cgOverwriteFile = 2,
#if CONFIG_EXTRA & EXTRA_DOWNLOAD
cgOverwriteDownload = 3,
#endif
#if CONFIG_EXTRA & EXTRA_DUMP
cgOverwriteDump = 4,
#endif
#if CONFIG_SESSIONS
cgOverwriteSession = 5,
#endif
cgSubmit = 6, cgReset = 7, cgRepost = 8, cgHtml = 9, cgEnable = 10
#if CONFIG_FTP && OPTION_TLS
, cgFtpsDataclear = 11
#endif
} my_enum2(unsigned char) tConfirmationGoal;
static const char* khmli_filename = NULL;
#if CONFIG_EXTRA & EXTRA_DOWNLOAD
static const char* khmli_download_uri = NULL;
#endif
#if CONFIG_EXTRA & EXTRA_DUMP
my_enum1 enum
{ dsfNone = 0, dsfMustSource = 0x01, dsfMustHtml = 0x02, dsfBeepWhenDone = 0x04
} my_enum2(unsigned char) tDumpStyleFlags;
my_enum1 enum
{ dscNone = 0, dscDocument = 0x01, dscLinklist = 0x02, dscImagelist = 0x04
} my_enum2(unsigned char) tDumpStyleContents;
typedef struct
{ tCoordinate width;
unsigned char kb_hashmarks;
tDumpStyleFlags flags;
} tDumpStyle;
#if 0
static tDumpStyle khmli_dump_style;
#endif
#endif /* #if CONFIG_EXTRA & EXTRA_DUMP */
#if CONFIG_MENUS & MENUS_UHIST
#define URI_HISTORY_LEN (20)
static const char* uri_history[URI_HISTORY_LEN];
static unsigned short uri_history_index = 0;
#endif
my_enum1 enum
{ wrtRedraw = 0, wrtRedrawRecursive = 1, wrtSearch = 2, wrtSearchBackward = 3,
wrtToEnd = 4
} my_enum2(unsigned char) tWindowRedrawingTask;
/* some prototypes */
static void show_message(const char*, tBoolean);
static void __line_input_estart(tEditableLineInputGoal, const char*,
const char*, tUserQuery*);
#define line_input_estart(a, b, c, d, e, f, g) __line_input_estart(a, b, c, d)
/** URI parsing */
static const struct
{ const char* scheme; /* (sorted in strcmp() order) */
tResourceProtocol rp;
} scheme2rp[] =
{ { strAbout, rpAbout },
#if CONFIG_DEBUG
{ strCvs, rpCvs },
#endif
{ strFile, rpLocal },
{ strFinger, __rpFinger },
{ strFtp, __rpFtp },
{ strFtps, __rpFtps },
{ strGopher, __rpGopher },
{ strHttp, rpHttp },
{ strHttps, __rpHttps },
#if CONFIG_DEBUG
{ strInfo, rpInfo },
#endif
{ strJavascript, __rpJavascript },
{ strLocal, rpLocal },
{ strLocalCgi, __rpLocalCgi },
{ strMailto, __rpMailto },
{ strNews, __rpNntp },
{ strNntp, __rpNntp },
{ strPop, __rpPop },
{ strPops, __rpPops }
};
static one_caller tMbsIndex do_lookup_rp(const char* scheme)
{ my_binary_search(0, ARRAY_ELEMNUM(scheme2rp) - 1,
streqcase3(scheme, scheme2rp[idx].scheme), return(idx))
/* case-insensitivity: RFC3986 (6.2.2.1) and RFC2616 (3.2.3) -- or was that
casein-sensitivity? :-) */
}
static one_caller tResourceProtocol lookup_rp(const char* scheme)
{ const tMbsIndex idx = do_lookup_rp(scheme);
return ( (idx >= 0) ? scheme2rp[idx].rp : rpUnknown );
}
static char* uri_parse_part(const char* uri, unsigned char part)
{ static const char separator[] = ":/?#";
const char* const sep = separator + part;
char uch, sch; /* URI character, separator character */
/* The following code does very much the same as strpbrk(), but that function
isn't portable. */
while ( (uch = *uri) != '\0' )
{ const char* temp = sep;
while ( (sch = *temp++) != '\0' )
{ if (uch == sch) return(unconstify(uri)); }
uri++;
}
return(NULL);
}
static void setup_authority(/*const*/ char** _uri,
/*@out@*/ /*const*/ char** _authority)
{ /*const*/ char *uri = *_uri, *pos = uri_parse_part(uri, 1), *authority;
if (pos != NULL) { authority = my_strndup(uri, pos - uri); uri = pos; }
else { authority = my_strdup(uri); TO_EOS(uri); }
*_uri = uri; *_authority = authority;
}
static __sallocator char* __callocator finalize_path(const char* _path)
/* transforms a path to a standard representation by removing "superfluous"
components */
{ char ch, *path = my_strdup(_path), *temp, *dest, **component;
size_t num, maxnum, count, orig_len = strlen(path);
tBoolean found_tilde = falsE, is_abs = cond2boolean(*path == chDirsep),
is_dir = cond2boolean( (orig_len > 0) && (path[orig_len - 1] == chDirsep)),
is_first;
/* split the path into components */
temp = path; component = NULL; num = maxnum = 0;
splitloop:
while (*temp == chDirsep) temp++;
if (*temp != '\0') /* got a component */
{ if (num >= maxnum)
{ maxnum += 16;
component = memory_reallocate(component, maxnum * sizeof(char*),
mapOther);
}
component[num++] = temp;
while ( (*temp != chDirsep) && (*temp != '\0') ) temp++;
if (*temp != '\0') { *temp++ = '\0'; goto splitloop; } /* look for more */
}
/* remove superfluous components */
#define rc(x) *(component[(x)]) = '\0'
for (count = 0; count < num; count++)
{ const char* str = component[count];
if (!strcmp(str, strDot)) rc(count);
else if (!found_tilde)
{ if (!strcmp(str, strDoubleDot))
{ rc(count);
if (count > 0) /* try to remove "the preceding" component */
{ size_t cnt = count - 1;
while (1)
{ if (*(component[cnt]) != '\0') { rc(cnt); break; } /* done */
if (cnt == 0) break; /* no removable preceding component */
cnt--;
}
}
}
else if (!strcmp(str, "~"))
{ /* e.g. in FTP it should be possible to do "ftp://foo.org/~/../blah",
which can only be interpreted by the server, not by us */
found_tilde = truE;
}
}
}
#undef rc
/* build the new path */
dest = path; is_first = truE;
if (is_abs) *dest++ = chDirsep;
for (count = 0; count < num; count++)
{ const char* src = component[count];
if (*src == '\0') continue; /* removed */
if (is_first) is_first = falsE;
else *dest++ = chDirsep;
while ( (ch = *src++) != '\0' ) *dest++ = ch;
}
if (is_dir)
{ if ( (dest <= path) || (*(dest - 1) != chDirsep) ) *dest++ = chDirsep; }
*dest = '\0';
__dealloc(component);
return(path);
}
#define __cleanup_path \
if (must_dealloc_path) { memory_deallocate(path); must_dealloc_path = falsE;}
static __my_inline void uri_set_error(tUriData* uri, tResourceError re)
{ if (uri->re == reFine) uri->re = re;
}
static __my_inline tUriData* uri_allocate(void)
{ return((tUriData*) memory_allocate(sizeof(tUriData), mapUriData));
}
static tUriData* uri_parse(const char* _uri, const tUriData* const
orig_referrer, /*@out@*/ const char** _fragment, const char* extra_query,
unsigned char special_flags)
/* Please note the number and position of underscore characters in the function
name: it's not "u_rip_....()" although maybe it should be... */
/* RFC3986 says: ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
Simple in theory, but in practice it's much more complicated. Just to
mention a few examples:
- Colons can appear in four places in a URI: right after the scheme string,
as a separator in "username:password", as a separator within a numerical
IPv6 hostname, and as a portnumber indicator.
- There aren't only absolute URIs to be handled, but also relative and
abbreviated URIs.
To distinguish all these correctly, we have to do much more than a trivial
pattern matching. Let's thank all specification developers for such
bogosities. (I've looked at several implementations, and none of them seems
to handle all cases the right way. And the endless comments in this function
might also be an indication of bad URI design concepts, similar to the HTTP
cookie mess...)
*/
{ static const char strWwwDot[] = "www.", strFtpDot[] = "ftp.",
strNewsDot[] = "news.", strGopherDot[] = "gopher.";
tUriData* retval = uri_allocate();
const tUriData* referrer = orig_referrer;
tResourceProtocol rp, wouldbe_rp;
char *uristart, *uri, *pos;
char *scheme, *authority, *hostname, *port, *path, *query, *fragment;
const char *username, *password;
char *complete_query, *spfbuf_uri;
tPortnumber portnumber;
tResourceError uri_error;
tBoolean must_dealloc_path, must_dealloc_complete_query, ua, iph, appfrag;
username = password = scheme = authority = hostname = port = path = query =
fragment = NULL;
portnumber = 0; must_dealloc_path = must_dealloc_complete_query = falsE;
rp = rpUnknown;
/* Prepare the URI */
{ char* dest = uristart = uri = __memory_allocate(strlen(_uri)+1, mapString);
const char* src = _uri;
const unsigned char* test = (const unsigned char*) _uri;
unsigned char testch;
while ( (testch = *test++) != '\0' )
{ const char urich = *src++; /* "My first name is Robert." :-) */
*dest++ = (is_control_char(testch) ? '_' : urich);
}
*dest = '\0';
}
if (orig_referrer != NULL)
{ const char ch = *uri;
if ( (ch == '\0') || (ch == '#') ) /* RFC3986, 4.4 */
{ const char* ref_uri = orig_referrer->uri;
if ( (ref_uri != NULL) && (*ref_uri != '\0') )
{ char* new_uri;
my_spf(NULL, 0, &new_uri, strPercsPercs, ref_uri, uri);
memory_deallocate(uristart); uristart = uri = my_spf_use(new_uri);
}
}
}
/* Look what's explicitly given */
pos = uri_parse_part(uri, 0);
if ( (pos != NULL) && (*pos == ':') && (pos > uri) )
{ /* RFC3986 (3.1) says: scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
Test whether it actually _is_ a scheme name or rather an abbreviated URI
like "foo.org:42" or a numerical IPv6 hostname beginning like
"[42:43...]". To avoid problems, we don't allow any dots in a scheme
name - RFC3986 allows dots, but nobody seems to use them. This way, only
abbreviated URIs like "localhost:42" could still cause problems, and we
check explicitly for "localhost". These are ugly special-case hacks, but
all more elegant algorithms seem to be less correct. */
const char* temp;
char ch;
size_t len;
if ( (pos[1] == '/') && (pos[2] == '/') )
{ /* Found the sequence "://"; accept anything in front of this as a scheme
string, no matter what it actually is. This mechanism allows users to
override the below plausibility tests, so they can use even the
strangest schemes if they want to. (Non-standard URI schemes will be
configurable in a later version, and "all power to the user" is often
a good idea anyway.)
*/
goto do_scheme;
}
temp = uri; ch = *temp++;
if (!my_isalpha(ch)) goto no_scheme;
while (temp < pos)
{ ch = *temp++;
if ( (!my_isalnum(ch)) && (ch != '+') && (ch != '-') ) goto no_scheme;
}
len = pos - uri;
if ( (len == 9) && (strneqcase(uri, strLocalhost, 9)) ) goto no_scheme;
do_scheme: scheme = uri; *pos++ = '\0'; uri = pos; /* scheme given */
no_scheme: {}
}
if ( (uri[0] == '/') && (uri[1] == '/') ) /* authority given */
{ uri += 2; setup_authority(&uri, &authority);
}
path = uri; /* a path is always given (but may be empty, of course) */
pos = uri_parse_part(uri, 2);
if ( (pos != NULL) && (*pos == '?') ) /* query given */
{ *pos++ = '\0'; query = uri = pos; }
pos = uri_parse_part(uri, 3);
if ( (pos != NULL) && (*pos == '#') ) /* fragment given */
{ *pos++ = '\0'; fragment = uri = pos; }
/* Interpret the whole stuff */
/* calculate protocol and possibly authority: */
if (scheme != NULL) rp = lookup_rp(scheme);
else if (orig_referrer != NULL) rp = orig_referrer->rp;
else if (authority == NULL)
{ /* The URI can only be an abbreviated thing like "www.foo.org..." or a
local path. */
if ( (*path == chDirsep) || ( (path[0] == '.') && (path[1] == chDirsep) )
|| ( (path[0] == '~') && (path[1] == chDirsep) ) )
{ /* local scheme, no authority necessary */
set_local: rp = rpLocal;
}
else if (strneqcase(path, strWwwDot, 4))
{ set_http: rp = rpHttp;
handle_abbr_authority: setup_authority(&path, &authority);
}
else if (strneqcase(path, strFtpDot, 4))
{ set_ftp: rp = __rpFtp; goto handle_abbr_authority; }
else if ( (strneqcase(path, strNewsDot, 5)) /* CHECKME! ||
( (strneqcase(path, strNews, 4)) &&
(uri_parse_part(path, 1) == path + 4) ) */ )
{ rp = __rpNntp; goto handle_abbr_authority; }
else if (strneqcase(path, strGopherDot, 7))
{ rp = __rpGopher; goto handle_abbr_authority; }
else /* last resorts */
{ struct stat statbuf;
#if CONFIG_CUSTOM_CONN
if (special_flags & 1) goto set_ftp;
#endif
/* might be a local regular file or directory */
if (my_stat(path, &statbuf) == 0)
{ const mode_t mode = statbuf.st_mode;
if (S_ISREG(mode) || S_ISDIR(mode)) goto set_local;
}
/* nothing else worked, so assume it's an HTTP resource */
goto set_http;
}
}
/* split the authority: */
if (authority != NULL)
{ pos = my_strchr(authority, '@');
if (pos == NULL) hostname = authority;
else
{ *pos++ = '\0'; hostname = pos; username = authority; /* username given */
pos = my_strchr(username, ':');
if (pos != NULL) { *pos++ = '\0'; password = pos; } /* password given */
}
pos = hostname; /* default start position for the below my_strchr() call */
if (*hostname == '[')
{ /*looks like the beginning of a numeric IPv6 address notation (RFC2732)*/
char* temp;
if ( (hostname[1] != '\0') &&
( ( temp = my_strchr(hostname + 2, ']') ) != NULL ) )
{ /* yup, it is */
*temp++ = '\0'; pos = temp; hostname++;
retval->udf |= udfTryIpv6;
}
/* "else": it isn't; we assume that users know what they're doing; we
don't know :-) so we pass the stuff unchanged */
}
pos = my_strchr(pos, ':');
if (pos != NULL) { *pos++ = '\0'; port = pos; } /* port given */
if (*hostname == '\0') hostname = NULL;
else
{ if ( (rp == rpUnknown) && (scheme == NULL) && (orig_referrer == NULL) )
{ /* no explicit scheme, no referrer, but an explicit hostname; seems
that the user entered something like "//www.foo.org" manually; very
rare, but valid and thus should be supported */
if (strneqcase(hostname, strWwwDot, 4)) rp = rpHttp;
else if (strneqcase(hostname, strFtpDot, 4)) rp = __rpFtp;
else if ( (strneqcase(hostname, strNewsDot, 5)) ||
(streqcase(hostname, strNews)) )
rp = __rpNntp;
}
hostname = my_strdup_tolower(hostname);
/* case-insensitivity: RFC3986 (6.2.2.1) and RFC2616 (3.2.3) */
}
}
#if OPTION_EXECEXT & EXECEXT_SHELL
if ( (rp == rpUnknown) && (special_flags & 4) ) rp = rpExecextShell;
#endif
wouldbe_rp = rp; rp = rp_data[wouldbe_rp].final_rp;
ua = cond2boolean(rp_data[wouldbe_rp].flags & rpfUsesAuthority);
iph = cond2boolean(rp_data[wouldbe_rp].flags & rpfIsPathHierarchical);
/* calculate the portnumber: */
if (ua)
{ if ( (port == NULL) || (*port == '\0') )
portnumber = rp2portnumber(wouldbe_rp);
else
{ int _portnumber;
my_atoi(port, &_portnumber, NULL, 99999);
if ( (_portnumber <= 0) || (_portnumber > 65535) )
{ uri_set_error(retval, rePortnumber); portnumber = 0; }
else
{ portnumber = (tPortnumber) htons((unsigned short) _portnumber);
retval->udf |= udfGotExplicitPortnumber;
}
}
#if CONFIG_CUSTOM_CONN
if ( (special_flags & 1) && (!(retval->udf & udfGotExplicitPortnumber)) )
{ if (rp == rpFtp) portnumber = htons(21);
#if OPTION_TLS
else if (rp == rpFtps) portnumber = htons(990);
#endif
}
#endif
}
#if CONFIG_FTP
if (is_ftplike(wouldbe_rp))
{ /* check for ";type=X" ending of FTP path */
const char *l_username, *l_password;
size_t len = strlen(path);
if ( (len >= 7) && (strneqcase(path + len - 7, ";type=", 6)) )
{ char typech = *(path + len - 1);
if (typech == 'a') retval->udf |= udfFtpTypeA;
else if (typech == 'd') retval->udf |= udfFtpTypeD;
else if (typech == 'i') retval->udf |= udfFtpTypeI;
else goto no_ftp_type;
*(path + len - 7) = '\0';
no_ftp_type: {}
}
/* handle username and password */
l_username = strAnonymous; l_password = "guest";
if ( (wouldbe_rp == rpFtp) && ((username == NULL) || (password == NULL)) )
{ const tConfigLogin* l = ( (hostname != NULL) ? config.ftp_login : NULL );
tPortnumber portnumber1 = portnumber;
while (l != NULL)
{ tPortnumber portnumber2 = l->hosts_portnumber;
if ( ( (portnumber2 == 0) || (portnumber2 == portnumber1) ) &&
(my_pattern_matcher(l->hosts_pattern, hostname)) ) /* found */
{ l_username = l->user; l_password = l->password; break; }
l = l->next;
}
}
if (username == NULL) username = l_username;
if ( (password == NULL) && (username != NULL) && (l_username != NULL) &&
(!strcmp(username, l_username)) )
password = l_password;
}
#endif
/* Complete the data */
if ( (query != NULL) && (*query == '\0') ) query = NULL;
if ( (fragment != NULL) && (*fragment == '\0') ) fragment = NULL;
if ( (query != NULL) && (extra_query != NULL) )
{ my_spf(NULL, 0, &complete_query, "%s&%s", query, extra_query);
must_dealloc_complete_query = truE;
}
else if (query != NULL) complete_query = query;
else if (extra_query != NULL) complete_query = unconstify(extra_query);
else complete_query = NULL;
if ( (wouldbe_rp == rpLocal) || (wouldbe_rp == __rpLocalCgi) )
{ if ( (path[0] == '~') && (path[1] == chDirsep) )
{ my_spf(NULL, 0, &path, strPercsPercs, get_homepath(), path + 2);
must_dealloc_path = truE;
}
}
if ( (referrer != NULL) && (rp != referrer->rp) )
referrer = NULL; /* can't use it */
if (referrer != NULL)
{ const char* refpath;
/* duplicate any missing information by taking it from the referrer: */
if (ua)
{ if (hostname == NULL) hostname = my_strdup(referrer->hostname);
else if (strcmp(hostname, referrer->hostname))
{ referrer = NULL; goto done_with_referrer; } /* can't use it */
}
if ( (iph) && (*path != chDirsep) &&
( (refpath = referrer->path) != NULL ) )
{ const char* temp = my_strrchr(refpath, chDirsep);
if (temp == NULL) /* "can't happen" */
{ __cleanup_path path = unconstify(strSlash);
}
else
{ size_t refpathsize = temp - refpath + 1, pathsize = strlen(path) + 1,
size = refpathsize + pathsize;
char* dest = __memory_allocate(size, mapString);
my_memcpy(dest, refpath, refpathsize);
my_memcpy(dest + refpathsize, path, pathsize);
__cleanup_path path = dest; must_dealloc_path = truE;
}
}
}
done_with_referrer: {}
if ( (*path != chDirsep) && (is_locallike(wouldbe_rp)) )
{ char *spfbuf, *pathtemp = path;
const char* prefix = my_getcwd();
while (!strncmp(pathtemp, strDotSlash, 2)) pathtemp += 2; /* REMOVEME! */
my_spf(NULL, 0, &spfbuf, strPercsPercs, prefix, pathtemp);
__cleanup_path path = my_spf_use(spfbuf); must_dealloc_path = truE;
}
if ( (iph) && (*path == '\0') )
{ __cleanup_path path = unconstify(strSlash); }
if (iph)
{ char* finpath = finalize_path(path);
__cleanup_path path = finpath; must_dealloc_path = truE;
}
switch (rp)
{ case rpAbout:
if (*path == '\0') { __cleanup_path path = unconstify(strRetawq); }
break;
#if OPTION_NEWS
case rpNntp:
if (hostname == NULL)
{ const char* n = config.news_server;
if (n != NULL) hostname = my_strdup(n);
}
break;
#endif
}
/* Compose the final URI and store everything */
if (ua) sprint_safe(strbuf, strPercd, ntohs(portnumber));
appfrag = cond2boolean( (special_flags & 2) && (fragment != NULL) );
my_spf(NULL, 0, &spfbuf_uri
,
/*A*/ "%s:" /* scheme */
/*B*/ "%s%s%s%s" /* double slash, hostname, colon, portnumber (if ua) */
/*C*/ "%s%s" /* slash (conditional), path */
/*D*/ "%s%s" /* question mark, query (if any query) */
/*E*/ "%s%s" /* hashmark, fragment (if appfrag) */
,
/*A*/ ( ( (scheme != NULL) && (!is_rp_nice(wouldbe_rp)) ) ? scheme :
/*A*/ rp_data[wouldbe_rp].scheme ),
/*B*/ (ua ? strSlashSlash : strEmpty),
/*B*/ ( (ua && (hostname != NULL)) ? hostname : strEmpty ),
/*B*/ (ua ? ":" : strEmpty), (ua ? strbuf : strEmpty), /* portnumber */
/*C*/ ( (ua && (*path != chDirsep)) ? strDirsep : strEmpty ), path,
/*D*/ ( (complete_query != NULL) ? strQm : strEmpty ),
/*D*/ ( (complete_query != NULL) ? complete_query : strEmpty ),
/*E*/ (appfrag ? strHm : strEmpty), (appfrag ? fragment : strEmpty)
);
retval->rp = rp;
retval->uri = my_spf_use(spfbuf_uri); retval->hostname = hostname;
if (must_dealloc_path) { retval->path = path; must_dealloc_path = falsE; }
else retval->path = my_strdup(path);
if (complete_query != NULL)
{ if (must_dealloc_complete_query)
{ retval->query = complete_query; must_dealloc_complete_query = falsE; }
else retval->query = my_strdup(complete_query);
}
if (username != NULL) retval->username = my_strdup(username);
if (password != NULL) retval->password = my_strdup(password);
if (ua) retval->portnumber = portnumber;
if (_fragment != NULL) *_fragment = (fragment ? my_strdup(fragment) : NULL);
if (rp == rpUnknown) uri_error = reProtocol;
else if (rp == rpDisabled) uri_error = reProtDisabled;
#if CONFIG_JAVASCRIPT
else if (rp == rpJavascript) uri_error = reProtDisabled;
#endif
else if (ua && (hostname == NULL)) uri_error = reHostname;
else goto no_uri_error;
uri_set_error(retval, uri_error);
no_uri_error: {}
memory_deallocate(uristart); __dealloc(authority);
if (must_dealloc_path) memory_deallocate(path);
if (must_dealloc_complete_query) memory_deallocate(complete_query);
return(retval);
}
#undef __cleanup_path
/** Windowing */
my_enum1 enum
{ wkUnknown = 0, wkBrowser = 1
#if DO_WK_INFO
, wkInfo = 2
#endif
#if DO_WK_CUSTOM_CONN
, wkCustomConn = 3
#endif
#if DO_WK_EDITOR
, wkEditor = 4
#endif
#if DO_WK_FILEMGR
, wkFilemgr = 5
#endif
#if DO_WK_MAILMGR
, wkMailmgr = 6
#endif
} my_enum2(unsigned char) tWindowKind;
struct tWindowSpec;
typedef struct tWindow /* a "virtual window" */
{ struct tWindow *prev, *next; /* list of all windows */
const struct tWindowSpec* spec; /* specification (operations etc.) */
void* wksd; /* "window-kind-specific data", "tWkFooData*" */
} tWindow;
my_enum1 enum
{ wsfNone = 0, wsfWantKeyFirst = 0x01
} my_enum2(unsigned char) tWindowSpecFlags;
struct tBrowserDocument;
typedef struct tWindowSpec
{ void (*create)(tWindow*);
void (*remove)(tWindow*);
void (*redraw)(const tWindow*);
tBoolean (*is_precious)(const tWindow*);
tBoolean (*handle_key)(tWindow*, tKey);
tBoolean (*handle_pcc)(tWindow*, tProgramCommandCode);
struct tBrowserDocument* (*find_currdoc)(const tWindow*);
const char* (*get_info)(tWindow*, /*@out@*/ tBoolean* /*is_error*/);
const char* (*get_menu_entry)(tWindow*);
#if CONFIG_MENUS & MENUS_CONTEXT
void (*setup_cm)(tWindow*, short*, short*, tActiveElementNumber);
#define WSPEC_CM(func) (func),
#else
#define WSPEC_CM(func) /* nothing */
#endif
#if CONFIG_SESSIONS
void (*save_session)(tWindow*, int /*fd*/);
#define WSPEC_SESSION(func) (func),
#else
#define WSPEC_SESSION(func) /* nothing */
#endif
#if CONFIG_DO_TEXTMODEMOUSE
void (*handle_mouse)(tWindow*, tCoordinate, tCoordinate, int);
#define WSPEC_MOUSE(func) (func),
#else
#define WSPEC_MOUSE(func) /* nothing */
#endif
char ui_char, session_char; /* for user interface and for session files */
tWindowKind kind;
tWindowSpecFlags flags;
} tWindowSpec; /* "window (kind) specification" */
static /*@null@*/ tWindow *windowlisthead = NULL, *windowlisttail = NULL;
#if TGC_IS_GRAPHICS
static unsigned int window_counter = 0;
#else
static /*@relnull@*/ tWindow* visible_window_x[2] = { NULL, NULL };
typedef unsigned char tVisibleWindowIndex; /* 0..1 */
static tVisibleWindowIndex current_window_index_x = 0;
#define current_window_x (visible_window_x[current_window_index_x])
#endif /* #if TGC_IS_GRAPHICS */
my_enum1 enum
{ wvfNone = 0, wvfScreenFull = 0x01, wvfDontDrawContents = 0x02,
wvfScrollingUp = 0x04, wvfHandledRedirection = 0x08, wvfPost = 0x10
} my_enum2(unsigned char) tWindowViewFlags; /* CHECKME: rename! */
/* wvfScreenFull implies two things: 1. the user may scroll down; 2. we needn't
redraw the document when the resource handler got more content for it. */
my_enum1 enum
{ bddmAutodetect = 0, bddmSource = 1, bddmHtml = 2 /* , bddmHex = 3 */
} my_enum2(unsigned char) tBrowserDocumentDisplayMode;
typedef struct
{ /* tBoolean (*handle_pcc)(struct tBrowserDocument*, tProgramCommandCode); */
tBoolean (*find_coords)(const struct tBrowserDocument*, /*@out@*/ short*,
/*@out@*/ short*, /*@out@*/ short*, /*@out@*/ short*);
void (*display_meta1)(struct tBrowserDocument*);
void (*display_meta2)(struct tBrowserDocument*);
} tBrowserDocumentOps;
typedef signed int tLinenumber; /* ("signed" to simplify calculations) */
#if TGC_IS_CURSES
typedef struct tActiveElementCoordinates
{ struct tActiveElementCoordinates* next;
tLinenumber y;
short x1, x2;
} tActiveElementCoordinates;
/* Such a list usually consists of only one entry, sometimes two (if a
linebreak occurs within an active element). In very small table columns, the
list might become longer - that's the main point with this structure. */
#endif
typedef struct
{ char* current_text; /* for text/password/... fields */
#if TGC_IS_GRAPHICS
tGraphicsWidget* widget;
#else
tActiveElementCoordinates* aec;
#endif
tActiveElementFlags flags; /* only contains aefChangeable flags */
} tActiveElement;
typedef struct tBrowserDocument
{ const tBrowserDocumentOps* ops;
void* container; /* e.g. "tWindowView*" for window kind "browser window" */
tResourceRequest* request;
tCantent* cantent; /* what's displayed for this document */
const char *title, *minor_html_title, *last_info, *anchor;
tActiveElement* active_element;
tActiveElementNumber aenum, aemax;
#if !TGC_IS_GRAPHICS
tActiveElementNumber aecur, former_ae;
#endif
tLinenumber origin_y; /* line of content in top-left corner of e.g. window */
#if MIGHT_USE_SCROLL_BARS
tLinenumber sbdh; /* "document height" */
int sbvi; /* "validity indicator" */
#endif
unsigned char redirections; /* counter to avoid infinite loops */
tBrowserDocumentDisplayMode bddm;
tWindowViewFlags flags;
} tBrowserDocument;
#define window_currdoc(window) (((window)->spec->find_currdoc)(window))
#define current_document_x (window_currdoc(current_window_x))
static char* sfbuf; /* "sf": "submit, form" */
static char sfconv[4];
static size_t sfbuf_size, sfbuf_maxsize;
/* prototype */
static void document_display(tBrowserDocument*, const tWindowRedrawingTask);
static void sfbuf_reset(void)
{ sfbuf = NULL; sfbuf_size = sfbuf_maxsize = 0;
}
static /* __sallocator -- not an "only" reference... */ tWindow* __callocator
window_create(/*@notnull@*/ const tWindowSpec* spec)
{ tWindow* retval = (tWindow*) memory_allocate(sizeof(tWindow), mapWindow);
#if CONFIG_DEBUG
sprint_safe(strbuf, "creating window %p (%d)\n", retval, spec->kind);
debugmsg(strbuf);
#endif
if (windowlisthead == NULL)
{ windowlisthead
#if !TGC_IS_GRAPHICS
= visible_window_x[0]
#endif
= retval;
}
if (windowlisttail != NULL)
{ retval->prev = windowlisttail; windowlisttail->next = retval; }
windowlisttail = retval; retval->spec = spec; retval->spec->create(retval);
#if TGC_IS_GRAPHICS
window_counter++;
#endif
return(retval);
}
static void window_remove(tWindow* window)
{
#if CONFIG_DEBUG
sprint_safe(strbuf, "removing window %p (%d)\n", window, window->spec->kind);
debugmsg(strbuf);
#endif
window->spec->remove(window);
if (windowlisthead == window) windowlisthead = window->next;
if (windowlisttail == window) windowlisttail = window->prev;
if (window->prev != NULL) window->prev->next = window->next;
if (window->next != NULL) window->next->prev = window->prev;
memory_deallocate(window);
#if TGC_IS_GRAPHICS
window_counter--;
if (window_counter == 0) do_quit();
#endif
}
static __my_inline void window_redraw(tWindow* window)
{ window->spec->redraw(window);
}
static void window_redraw_all(void)
/* redraws all visible windows */
{
#if TGC_IS_CURSES
window_redraw(visible_window_x[0]);
if (visible_window_x[1] != NULL) window_redraw(visible_window_x[1]);
#endif
}
/* prototypes */
static tWindow* wk_browser_create(void);
static void wk_browser_prr(tWindow*, const char*, tPrrFlags,
const tBrowserDocument*);
static void wk_browser_reload(tWindow*);
#if DO_WK_INFO
static tWindow* wk_info_create(const char*, tBrowserDocumentDisplayMode,
/*@out@*/ tBrowserDocument** _document);
static void wk_info_collect(tWindow*, const char*);
static void wk_info_finalize(tWindow*);
static void wk_info_set_message(tWindow*, const char*, tBoolean);
#endif
static tBoolean handle_command_code(tProgramCommandCode);
static tBoolean window_is_precious(const tWindow* window)
{ tBoolean (*func)(const tWindow*) = window->spec->is_precious;
return( (func != NULL) ? ((func)(window)) : truE );
}
static void window_hide(tWindow* window, unsigned char removal)
{ /* : 0 = must not; 1 = may; 2 = must */
#if TGC_IS_GRAPHICS
gtk_widget_hide(GTK_WIDGET(window->ww));
if (removal) window_remove(window);
#else
if (window == visible_window_x[0])
{ visible_window_x[0] = visible_window_x[1];
if (visible_window_x[0] == NULL)
{ /* Make sure we always have at least one window on the screen */
tWindow* w = window->next;
if (w == NULL) w = windowlisthead;
if ( (w == NULL) || (w == window) ) w = wk_browser_create();
visible_window_x[0] = w;
}
goto xy;
}
else if (window == visible_window_x[1])
{ xy:
if ( (removal == 2) || ( (removal == 1) && (!window_is_precious(window)) ))
window_remove(window);
visible_window_x[1] = NULL; current_window_index_x = 0;
window_redraw_all();
}
/* "else": nothing to do */
#endif
}
#if TGC_IS_GRAPHICS
#define window_is_visible(window) (truE)
#else
static tBoolean window_is_visible(tWindow* window)
{ tBoolean retval;
if (window == NULL) retval = falsE; /* "should not happen" */
else if ((window == visible_window_x[0]) || (window == visible_window_x[1]))
retval = truE;
else retval = falsE;
return(retval);
}
#endif
#define is_browser_window(window) ((window)->spec->kind == wkBrowser)
static tWindow* require_browser_window(void)
{ tWindow* window = current_window_x;
if (!is_browser_window(window))
window = current_window_x = wk_browser_create();
return(window);
}
static void redraw_message_line(void)
{
#if TGC_IS_CURSES
tWindow* window = current_window_x;
tBoolean is_error;
const char* str;
const char* (*func)(tWindow*, tBoolean*) = window->spec->get_info;
if (func != NULL) str = (func)(window, &is_error);
else { str = NULL; is_error = falsE; }
show_message(null2empty(str), is_error);
#endif
}
static tBoolean window_contents_minmaxrow(const tWindow* window,
/*@out@*/ short* _minrow, /*@out@*/ short* _maxrow)
{ tBoolean retval;
short minrow = 0, maxrow = MAX(LINES - 3, 0);
if (window == visible_window_x[0])
{ if (visible_window_x[1] != NULL) /* only use top-half of screen */
maxrow = VMIDDLE - 1;
it_worked: *_minrow = minrow; *_maxrow = maxrow; retval = truE;
}
else if (window == visible_window_x[1]) /* only use bottom-half of screen */
{ minrow = VMIDDLE + 1; goto it_worked; }
else retval = falsE; /* "should not happen" */
return(retval);
}
/** Message display */
static void show_message(const char* msg, tBoolean is_error)
{
#if TGC_IS_GRAPHICS
tGraphicsWidget* widget = current_window->message;
gtk_label_set_text(GTK_LABEL(widget), msg);
#else
if (!is_bottom_occupied)
{ if (is_error) my_set_color(cpnRed);
(void) mvaddnstr(LINES - 1, 0, msg, COLS - 1);
if (is_error) my_set_color(cpnDefault);
(void) clrtoeol(); must_reset_cursor();
}
#endif
debugmsg("UI message: "); debugmsg(msg); debugmsg(strNewline);
}
static void show_message_osfailure(int errnum, const char* text)
{ if (text == NULL) text = _("Failed!"); /* some stupidistic default text */
if (errnum > 0)
{ const char* e = my_strerror(errnum);
if (strlen(e) > STRBUF_SIZE / 2) e = _(strUnknown); /* can't be serious */
sprint_safe(strbuf, _("%s - error #%d, %s"), text, errnum, e);
text = strbuf; /* CHECKME: use strErrorTrail[]! */
}
show_message(text, truE);
}
#if CONFIG_CONSOLE
static __my_inline void cc_output_str(const char* str)
{ my_write_str(fd_stdout, str); /* FIXME for window! */
}
#define cc_output_errstr(str) cc_output_str(str) /* (_currently_ the same) */
#endif
static void inform_about_error(int errnum, const char* text)
{ if (is_environed) show_message_osfailure(errnum, text);
#if CONFIG_CONSOLE
else if (program_mode == pmConsole)
{ cc_output_errstr(text);
if (errnum > 0)
{ char buf[1000];
const char* errstr = my_strerror(errnum);
if (strlen(errstr) > 200) errstr = _(strUnknown);
sprint_safe(buf, _(strErrorTrail), errnum, errstr);
cc_output_errstr(buf);
}
cc_output_errstr(strNewline);
}
#endif
else fatal_error(errnum, text);
}
#if ((CONFIG_MENUS & MENUS_ALL) != MENUS_ALL) || (!CONFIG_SESSIONS) || (!CONFIG_JUMPS) || (!CONFIG_DO_TEXTMODEMOUSE) || (!OPTION_EXECEXT) || (!MIGHT_USE_SCROLL_BARS) || (!(CONFIG_EXTRA & EXTRA_DOWNLOAD)) || (!(CONFIG_EXTRA & EXTRA_DUMP)) || (!DO_WK_INFO) /* (-: */
static void fwdact(const char* str)
{ char* spfbuf;
if (*str == 'F') str++;
my_spf(strbuf, STRBUF_SIZE, &spfbuf, _(strFwdact), str, strEmpty);
*spfbuf = my_toupper(*spfbuf);
show_message(spfbuf, truE);
my_spf_cleanup(strbuf, spfbuf);
}
#endif
#if (CONFIG_MENUS & MENUS_ALL) != MENUS_ALL
static void menus_were_disabled(void)
{ fwdact(_("Fthis kind of menus"));
}
#endif
#if TGC_IS_CURSES
static my_inline void terminal_too_small(void)
{ show_message(_("Terminal too small"), truE);
}
#endif
/** Resource requests */
static void request_dhm_control(__sunused void* _request __cunused, __sunused
void* data __cunused, __sunused tDhmControlCode dcc __cunused)
/* just a dummy, at least for now */
{ /* tResourceRequest* request = (tResourceRequest*) _request; */
}
static __sallocator tResourceRequest* __callocator request_create(void)
{ tResourceRequest* retval = (tResourceRequest*)
memory_allocate(sizeof(tResourceRequest), mapResourceRequest);
#if CONFIG_DEBUG
sprint_safe(debugstrbuf, "request_create(): %p\n", retval);
debugmsg(debugstrbuf);
#endif
dhm_init(retval, request_dhm_control, "request");
return(retval);
}
static void request_remove(tResourceRequest* request)
{
#if CONFIG_DEBUG
sprint_safe(debugstrbuf, "request_remove(): %p\n", request);
debugmsg(debugstrbuf);
#endif
dhm_notify(request, dhmnfRemoval);
if (request->resource != NULL) dhm_detach(request->resource);
if (request->uri_data != NULL) uri_detach(request->uri_data);
sinking_data_deallocate(&(request->sinking_data));
memory_deallocate(request);
}
static void request_set_error(tResourceRequest* request, tResourceError error)
{ if (request->state != rrsError)
{ request->state = rrsError; request->error = error; }
}
static void request_copy_error(tResourceRequest* request,
const tResource* resource)
/* CHECKME! */
{ if ( (resource->state == rsError) && (request->state != rrsError) )
{ request->state = rrsError; request->error = resource->error; }
}
static void request_queue(tResourceRequest* request,
tResourceRequestAction action)
/* feeds the request to the resource handler for kickoff */
{ request->action = action; request->state = rrsPreparedByMain;
#if CONFIG_DEBUG
{ const tUriData* u = request->uri_data;
char* spfbuf;
my_spf(strbuf, STRBUF_SIZE, &spfbuf,
"Queueing request: %d - *%s* - *%s* - *%s*\n", u->rp, null2empty(u->uri),
null2empty(u->query), null2empty(u->post));
debugmsg(spfbuf); my_spf_cleanup(strbuf, spfbuf);
}
#endif
resource_request_start(request);
}
typedef struct
{ const char* uri; /*I*/
const tBrowserDocument* referrer; /*I*/
tResourceRequest* result; /*O*/
const char* uri_anchor; /*O*/ /* (if prrfWantUriAnchor) */
tPrrFlags prrf; /*I*/
} tPrrData; /* I=input, O=output */
static my_inline void prr_setup(/*@out@*/ tPrrData* data, const char* uri,
tPrrFlags prrf)
{ my_memclr(data, sizeof(tPrrData)); data->uri = uri; data->prrf = prrf;
}
static void prr_setdown(tPrrData* data)
/* removes things which were allocated by prepare_resource_request() but
not "consumed" by its caller */
{ tResourceRequest* request = data->result;
if (request != NULL) request_remove(request);
__dealloc(data->uri_anchor);
}
static void prepare_resource_request(tPrrData* data)
{ tResourceRequest* request = data->result = request_create();
const tBrowserDocument* _referrer = data->referrer;
const tResourceRequest* referrer = (_referrer ? _referrer->request : NULL);
const tUriData* ref_ud = (referrer ? referrer->uri_data : NULL);
const char *sfbuf_final, *extra_query;
const tPrrFlags prrf = data->prrf;
tUriData* uri_data;
if (sfbuf == NULL) sfbuf_final = NULL;
else
{ sfbuf_final = sfbuf;
if (*sfbuf_final == '&') sfbuf_final++;
if (*sfbuf_final == '\0') sfbuf_final = NULL; /* no "real" data */
}
if ( (!(prrf & prrfPost)) && (prrf & prrfUseSfbuf) && (sfbuf_final != NULL) )
{ /* not a post request, but still some submit-form data, thus query */
extra_query = sfbuf_final;
}
else extra_query = NULL;
uri_data = uri_parse(data->uri, ref_ud, ( (prrf & prrfWantUriAnchor) ?
(&(data->uri_anchor)) : NULL ), extra_query, ( (prrf & prrfUpsfp4) ? 4:0));
uri_attach(request->uri_data, uri_data);
if (uri_data->re != reFine) request_set_error(request, uri_data->re);
if (prrf & prrfPost)
{ request->flags |= rrfPost;
if ( (prrf & prrfUseSfbuf) && (sfbuf_final != NULL) )
request->uri_data->post = my_strdup(sfbuf_final);
}
if (sfbuf != NULL) { memory_deallocate(sfbuf); sfbuf_reset(); }
if ( (prrf & prrfIsRedirection) ||
( (_referrer != NULL) && (_referrer->redirections > 0) ) )
{ request->flags |= rrfIsRedirection; }
}
static const char* calculate_reqresmsg(const tResourceRequest* request,
const tResource* resource, unsigned char flags, /*@out@*/tBoolean* _is_error)
{ const char* retval;
*_is_error = falsE; /* default */
if (resource != NULL)
{ const tCantent* cantent = resource->cantent;
const tResourceState rs = resource->state;
const tServerStatusCode ssc = resource->server_status_code;
if (rs == rsError)
{ const tResourceError re = resource->error;
#if OPTION_TLS
if ((re == reTls) && (tls_errtext(resource, strbuf2))) retval = strbuf2;
else
#endif
{ retval = _(strResourceError[re]); }
*_is_error = truE;
}
#if OPTION_TLS
else if (resource_in_tls(resource, truE)) retval = _("TLS handshaking");
#endif
else if (rs == rsReading)
{ const size_t count = resource->bytecount;
if (count < 2) retval = _("Waiting for reply");
else
{ const size_t nominal = resource->nominal_contentlength;
const char* temp;
if ( (nominal != UNKNOWN_CONTENTLENGTH) && (count < nominal) )
{ sprint_safe(strbuf3, _("of %d "), localized_size(nominal));
temp = strbuf3;
}
else temp = strEmpty;
sprint_safe(strbuf2, _("Received %d %sbytes"), localized_size(count),
temp);
retval = strbuf2;
}
}
else if ( (!(flags & 1)) && (rs == rsComplete) && ( (cantent->content ==
NULL) || (cantent->content->used <= 0) ) )
{ retval = _("Document empty"); }
else if (resource_ui_conn_ip(resource, strbuf2, STRBUF_SIZE / 2))
{ sprint_safe(strbuf3, _("Connecting to %s"), strbuf2); retval = strbuf3; }
else retval = _(strResourceState[rs]);
#if CONFIG_DEBUG
sprint_safe(strbuf,
"%s [res=%p,rs=%d,re=%d,rch=%d,rf=%d,ths=%d,ssc=%d,ssi=*%s*]", retval,
resource, rs, resource->error, resource->handshake, resource->flags,
resource->tlheaderstate, ssc, ssc2info(resource->protocol, ssc));
retval = strbuf;
#else
if (ssc != 0)
{ const tResourceProtocol rp = resource->protocol;
const char *ssi = ssc2info(rp, ssc), *ssi_sep = ( (ssi == strEmpty) ?
strEmpty : strSpacedDash ), *whose;
#if OPTION_LOCAL_CGI
if (rp == rpLocalCgi) whose = _("script status: ");
else
#endif
{ whose = _("server status: "); }
sprint_safe(strbuf, "%s (%s%d%s%s)", retval, whose, ssc, ssi_sep, ssi);
retval = strbuf;
}
#endif
}
else if (request != NULL)
{ const tResourceRequestState rrs = request->state;
switch (rrs)
{ case rrsError:
retval = _(strResourceError[request->error]); *_is_error = truE; break;
case rrsUnknown: retval = strEmpty; break;
default: retval = _(strResourceRequestState[rrs]); break;
}
}
else retval = strEmpty;
return(retval);
}
/** Browser documents */
/* Browser documents: screen coordinates */
#if TGC_IS_GRAPHICS
static my_inline tLinenumber document_section_height(const tBrowserDocument*
document)
{ return(LINES); /* FIXME! */
}
static my_inline void calculate_minmaxrow(const tBrowserDocument* document
__cunused, /*@out@*/ short* min, /*@out@*/ short* max)
{ *min = 0; *max = LINES - 1; /* FIXME! */
}
#else
static __my_inline tBoolean document_find_coords(const tBrowserDocument*
document, /*@out@*/ short* _x1, /*@out@*/ short* _y1, /*@out@*/ short* _x2,
/*@out@*/ short* _y2)
{ return((document->ops->find_coords)(document, _x1, _y1, _x2, _y2));
}
static tBoolean document_section_height(const tBrowserDocument* document,
/*@out@*/ tLinenumber* _height)
{ short x1, y1, x2, y2;
const tBoolean retval = document_find_coords(document, &x1, &y1, &x2, &y2);
if (retval) *_height = y2 - y1 + 1;
return(retval);
}
#if (CONFIG_TG == TG_XCURSES) && (MIGHT_USE_SCROLL_BARS)
static tBoolean document_minmaxcol(__sunused const tBrowserDocument* document
__cunused, /*@out@*/ short* _mincol, /*@out@*/ short* _maxcol)
{ *_mincol = 0; *_maxcol = COLS - 1; /* FIXME for framesets etc.! */
return(truE);
}
#endif
static tBoolean document_minmaxrow(const tBrowserDocument* document,
/*@out@*/ short* _minrow, /*@out@*/ short* _maxrow)
{ short x1, y1, x2, y2;
tBoolean retval = document_find_coords(document, &x1, &y1, &x2, &y2);
if (retval) { *_minrow = y1; *_maxrow = y2; }
return(retval);
}
static tBoolean document_line2row(const tBrowserDocument* document,
tLinenumber y, /*@out@*/ short* _row)
{ short x1, y1, x2, y2;
tBoolean retval = document_find_coords(document, &x1, &y1, &x2, &y2);
if (retval) { *_row = y1 + ((short) (y - document->origin_y)); }
return(retval);
}
#endif
/* Browser documents: active elements */
static void deallocate_aec(/*const*/ tActiveElementCoordinates** _aec)
{ const tActiveElementCoordinates* aec = *_aec;
if (aec == NULL) return; /* nothing to do */
while (aec != NULL)
{ const tActiveElementCoordinates* aec_next = aec->next;
memory_deallocate(aec); aec = aec_next;
}
*_aec = NULL;
}
static void __init_ae(const tActiveElementBase* aeb, tActiveElement* ae,
tBoolean is_first_init
#if TGC_IS_GRAPHICS
, tWindowView* view
#endif
)
{ const tActiveElementKind aek = aeb->kind;
const char* text;
if (is_first_init) my_memclr(ae, sizeof(tActiveElement));
else dealloc(ae->current_text);
ae->flags = aeb->flags & aefChangeable;
if ((aek == aekFormText) || (aek == aekFormPassword) || (aek == aekFormFile))
{ if ( (text = aeb->render) != NULL ) ae->current_text = my_strdup(text);
}
else if (aek == aekFormSelect)
{ const tHtmlInputLength count = aeb->maxlength;
if (count > 0) /* non-empty selection list */
{ char* t = ae->current_text = memory_allocate((count + 7) / 8, mapOther);
const tHtmlOption* o = (const tHtmlOption*) aeb->render;
tHtmlInputLength c = 0;
while (o != NULL)
{ if (o->flags & hofSelected) my_bit_set(t, c);
o = o->next; c++;
}
}
}
#if TGC_IS_GRAPHICS
if (is_first_init) /* possibly create a widget */
{ tGraphicsWidget* w;
tHtmlInputLength maxlength;
switch (aek)
{ case aekFormText: case aekFormPassword: case aekFormTextarea:
text = aeb->render;
maxlength = (has_input_length(aek) ? aeb->maxlength : 0);
/* FIXME: don't show passwords! */
/* w = gtk_text_new(NULL, NULL); */
w = ( (maxlength > 0) ? gtk_entry_new_with_max_length(maxlength) :
gtk_entry_new() );
if ( (text != NULL) && (*text != '\0') )
gtk_entry_set_text(GTK_ENTRY(w), text);
break;
case aekFormCheckbox: w = gtk_check_button_new(); break;
case aekFormRadio: w = gtk_radio_button_new(NULL); break;/*IMPLEMENTME!*/
case aekFormFile: w = gtk_file_selection_new(strEmpty); break;
default: w = NULL; break;
}
/* FIXME: check whether disabled, readonly, ...! */
if (w != NULL)
{ pack_box(view->window->contents, w); show_widget(w); ae->widget = w; }
}
#endif
}
#if TGC_IS_GRAPHICS
#define init_ae(a, b, c, d) __init_ae(a, b, c, d)
#else
#define init_ae(a, b, c, d) __init_ae(a, b, c)
#endif
#if !TGC_IS_GRAPHICS
static const tActiveElementCoordinates*
find_visible_aec(const tBrowserDocument* document, tActiveElementNumber _ae)
/* tries to find visible coordinates for the given active element */
{ const tActiveElementCoordinates *retval = NULL,
*aec = document->active_element[_ae].aec;
tLinenumber yvis1, yvis2;
short minrow, maxrow;
if (aec == NULL) goto out; /* no coordinates, nothing visible */
if (!document_minmaxrow(document, &minrow, &maxrow)) goto out;
yvis1 = document->origin_y; yvis2 = yvis1 + (maxrow - minrow);
while (aec != NULL)
{ tLinenumber y = aec->y;
if ( (y >= yvis1) && (y <= yvis2) ) { retval = aec; break; }
aec = aec->next;
}
out:
return(retval);
}
static __my_inline tBoolean is_ae_visible(const tBrowserDocument* document,
tActiveElementNumber _ae)
{ return(cond2boolean(find_visible_aec(document, _ae) != NULL));
}
static tActiveElementNumber next_visible_ae(const tBrowserDocument* document,
tActiveElementNumber _ae)
{ tActiveElementNumber retval = INVALID_AE, l = document->aenum;
while (++_ae < l)
{ if (is_ae_visible(document, _ae)) { retval = _ae; break; } }
return(retval);
}
static tActiveElementNumber previous_visible_ae(const tBrowserDocument*
document, tActiveElementNumber _ae)
{ tActiveElementNumber retval = INVALID_AE;
while (--_ae >= 0)
{ if (is_ae_visible(document, _ae)) { retval = _ae; break; } }
return(retval);
}
static void do_activate_element(tBrowserDocument* document,
tActiveElementNumber _ae, tBoolean active)
{ const tActiveElementCoordinates* aec = document->active_element[_ae].aec;
attr_t attribute;
short minrow, maxrow, row;
tLinenumber y1, y2;
if (aec == NULL) goto finish;
if (!document_section_height(document, &y2)) goto finish;
y1 = document->origin_y; y2 += y1 - 1;
attribute = (active ? A_REVERSE : A_UNDERLINE);
document_minmaxrow(document, &minrow, &maxrow);
while (aec != NULL)
{ tLinenumber y = aec->y;
short x1, x2, count;
if (y < y1) goto do_next;
else if (y > y2) break; /* done */
if (!document_line2row(document, y, &row)) break; /* "should not happen" */
x1 = aec->x1; x2 = aec->x2; count = x2 - x1 + 1; (void) move(row, x1);
while (count-- > 0)
(void) addch((inch() & ~(A_REVERSE | A_UNDERLINE)) | attribute);
do_next:
aec = aec->next;
}
finish:
if (active) document->aecur = document->former_ae = _ae;
else document->aecur = INVALID_AE;
}
static void activate_element(tBrowserDocument* document,
tActiveElementNumber _ae)
{ tActiveElementNumber old = document->aecur;
if (old != INVALID_AE)
do_activate_element(document, old, falsE); /* deactivate old */
do_activate_element(document, _ae, truE); /* activate new */
must_reset_cursor();
}
#endif /* #if TGC_IS_GRAPHICS */
/* Browser documents: general */
static my_inline void document_init(tBrowserDocument* document,
const tBrowserDocumentOps* ops, void* container, tResourceRequest* request)
{ document->ops = ops; document->container = container;
document->request = request;
#if !TGC_IS_GRAPHICS
document->aecur = document->former_ae = INVALID_AE;
#endif
}
static void document_tear(const tBrowserDocument* document)
/* IMPORTANT: the itself must be deallocated by the _caller_ (if
necessary at all)! */
{ tResourceRequest* request = document->request;
tCantent* cantent = document->cantent;
if (request != NULL) request_remove(request);
if (cantent != NULL) cantent_put(cantent);
__dealloc(document->title); __dealloc(document->minor_html_title);
__dealloc(document->last_info); __dealloc(document->anchor);
if (document->aenum > 0)
{ /*const*/ tActiveElement* aes = document->active_element;
tActiveElementNumber _ae;
for (_ae = 0; _ae < document->aenum; _ae++)
{ __dealloc(aes[_ae].current_text);
#if TGC_IS_GRAPHICS
/* FIXME: handle widget! */
#else
deallocate_aec(&(aes[_ae].aec));
#endif
}
memory_deallocate(aes);
}
}
/* Browser documents: HTML forms */
static void __sfbuf_add(const char ch)
/* append character unconverted */
{ if (sfbuf_maxsize <= sfbuf_size)
{ sfbuf_maxsize += 100;
sfbuf = memory_reallocate(sfbuf, sfbuf_maxsize, mapString);
}
sfbuf[sfbuf_size++] = ch;
}
static my_inline void __sfbuf_add_str(const char* str)
/* append string unconverted */
{ char ch;
while ( (ch = *str++) != '\0' ) __sfbuf_add(ch);
}
static void sfbuf_add_str(const char* str)
/* append string converted */
{ char ch;
while ( (ch = *str++) != '\0' )
{ /* append character converted */
{ if (my_isalnum(ch)) __sfbuf_add(ch);
else if (ch == ' ') __sfbuf_add('+');
else if (ch == '\n') __sfbuf_add_str("%0d%0a");
else if (ch != '\r')
{ sfconv[1] = strHexnum[(ch >> 4) & 15]; sfconv[2] = strHexnum[ch & 15];
__sfbuf_add_str(sfconv);
}
}
}
}
static tHtmlFormNumber calc_hfn(const tCantent* cantent,
tActiveElementNumber _ae)
/* searches for the number of the HTML form to which the <_ae> belongs */
{ tHtmlFormNumber retval = INVALID_HTML_FORM_NUMBER, hfn, hfnum;
const tHtmlForm* form;
if ( (_ae == INVALID_AE) || (cantent == NULL) ||
( (hfnum = cantent->hfnum) <= 0 ) )
{ goto out; } /* can't find anything */
form = cantent->form;
for (hfn = 0; hfn < hfnum; hfn++)
{ tActiveElementNumber a1 = form[hfn].first_ae, a2 = form[hfn].last_ae;
if ((a1 != INVALID_AE) && (a2 != INVALID_AE) && (_ae >= a1) && (_ae <= a2))
{ retval = hfn; goto out; } /* found */
}
out:
return(retval);
}
static void cnsniaf(void)
{ show_message(_("Can't submit - not inside a form?"), truE);
}
static void document_form_submit(/*@notnull@*/ const tBrowserDocument*document)
{ const tCantent* cantent = document->cantent;
const tActiveElementBase* aebase;
tActiveElementNumber _ae, a1, a2;
const tActiveElement* aes;
#if TGC_IS_GRAPHICS
const tActiveElementNumber aecur = INVALID_AE; /* FIXME! */
const tHtmlFormNumber hfn = INVALID_HTML_FORM_NUMBER; /* FIXME! */
#else
const tActiveElementNumber aecur = document->aecur;
const tHtmlFormNumber hfn = calc_hfn(cantent, aecur);
#endif
const tHtmlOption* o;
tHtmlOptionNumber onum;
tPrrFlags prrf;
if ( (cantent == NULL) || (hfn == INVALID_HTML_FORM_NUMBER) )
{ cant_submit: cnsniaf(); return; }
a1 = cantent->form[hfn].first_ae; a2 = cantent->form[hfn].last_ae;
if ( (a1 == INVALID_AE) || (a2 == INVALID_AE) ) goto cant_submit;
javascript_handle_event(jekSubmit, aecur); sfbuf_reset();
aebase = cantent->aebase; aes = document->active_element;
for (_ae = a1; _ae <= a2; _ae++)
{ tActiveElementKind kind;
const char *name, *value, *bitfield;
tBoolean is_first, is_multiple;
if (aebase[_ae].flags & aefDisabled) continue;
kind = aebase[_ae].kind;
if ( (!is_form_aek(kind)) || ( (name = aebase[_ae].data) == NULL )
|| (*name == '\0') )
{ continue; } /* non-form element or no element name - unusable here */
switch (kind)
{case aekFormText: case aekFormPassword: case aekFormTextarea:
value = aes[_ae].current_text;
append:
if (value != NULL)
{ __sfbuf_add('&'); sfbuf_add_str(name);
__sfbuf_add('='); sfbuf_add_str(value);
}
break;
case aekFormCheckbox:
if (aes[_ae].flags & aefCheckedSelected) { value = strOn; goto append; }
break;
case aekFormRadio:
if (aes[_ae].flags & aefCheckedSelected)
{ value = aebase[_ae].render; goto append; }
break;
case aekFormSubmit: case aekFormImage:
if (_ae == aecur)
{ if (kind == aekFormImage)
{ __sfbuf_add('&'); sfbuf_add_str(name); __sfbuf_add_str(".x=0&");
sfbuf_add_str(name); __sfbuf_add_str(".y=0"); continue;
}
else if (aebase[_ae].flags & aefButtonTag) value = aebase[_ae].render;
else value = strOn;
goto append;
}
break;
case aekFormHidden:
value = aebase[_ae].render; goto append; /*@notreached@*/ break;
case aekFormSelect:
bitfield = aes[_ae].current_text;
o = (const tHtmlOption*) aebase[_ae].render; onum = 0; is_first = truE;
is_multiple = cond2boolean(aebase[_ae].flags & aefMultiple);
while (o != NULL)
{ if (my_bit_test(bitfield, onum)) /* option is selected */
{ if (!is_first) __sfbuf_add(',');
else
{ __sfbuf_add('&'); sfbuf_add_str(name); __sfbuf_add('=');
is_first = falsE;
}
sfbuf_add_str(o->value);
if (!is_multiple) break; /* done with collecting */
}
o = o->next; onum++;
}
break;
/* case aekFormReset: break; -- nothing to do, element can't succeed */
/* case aekFormFile: break; -- IMPLEMENTME! */
}
}
__sfbuf_add('\0');
prrf = prrfRedrawOne | prrfUseSfbuf;
if (cantent->form[hfn].flags & hffMethodPost) prrf |= prrfPost;
wk_browser_prr(require_browser_window(), cantent->form[hfn].action_uri, prrf,
document);
}
static void current_document_form_submit(void)
{ const tBrowserDocument* document = current_document_x;
if (document != NULL) document_form_submit(document);
}
static void document_form_reset(tBrowserDocument* document)
{ const tCantent* cantent = document->cantent;
const tActiveElementBase* aebase;
tActiveElementNumber _ae, a1, a2;
tActiveElement* aes;
#if TGC_IS_GRAPHICS
tActiveElementNumber aecur = INVALID_AE; /* FIXME! */
tHtmlFormNumber hfn = INVALID_HTML_FORM_NUMBER; /* FIXME! */
#else
tActiveElementNumber aecur = document->aecur;
tHtmlFormNumber hfn = calc_hfn(cantent, aecur);
#endif
if ( (cantent == NULL) || (hfn == INVALID_HTML_FORM_NUMBER) )
{ cant_reset: show_message(_("Can't reset - not inside a form?"), truE);
return;
}
a1 = cantent->form[hfn].first_ae; a2 = cantent->form[hfn].last_ae;
if ( (a1 == INVALID_AE) || (a2 == INVALID_AE) ) goto cant_reset;
javascript_handle_event(jekReset, aecur);
aebase = cantent->aebase; aes = document->active_element;
for (_ae = a1; _ae <= a2; _ae++)
init_ae(&(aebase[_ae]), &(aes[_ae]), falsE, document);
document_display(document, wrtRedraw);
}
static void current_document_form_reset(void)
{ tBrowserDocument* document = current_document_x;
if (document != NULL) document_form_reset(document);
}
/* Browser documents: displaying on screen */
#if (TGC_IS_WINDOWING) || (CONFIG_EXTRA & EXTRA_DUMP) || (DO_PAGER)
#include "renderer.c"
#endif
static void wrc_rd_setup(tRendererData* rd, /*@notnull@*/ tBrowserDocument*
document, short width)
{ const tCantent* cantent = document->cantent;
const tBrowserDocumentDisplayMode bddm = document->bddm;
my_memclr(rd, sizeof(tRendererData));
rd->ra = raLayout; rd->document = document; rd->line_width = width;
rd->flags = rdfAttributes | rdfAlignment | rdfAe;
#if MIGHT_USE_COLORS
rd->flags |= rdfColors;
#endif
#if TGC_IS_GRAPHICS
rd->flags |= rdfGraphical;
#endif
#if TGC_IS_PIXELING
rd->flags |= rdfPixeling;
#endif
#if CONFIG_HTML & HTML_FRAMES
if (!(config.flags & cfHtmlFramesSimple)) rd->flags |= rdfFrames;
#endif
if (bddm != bddmAutodetect) /* the user explicitly wants something */
{ if (bddm == bddmHtml) { set_html: rd->flags |= rdfHtml; }
goto kind_done;
}
if ( (cantent != NULL) && (cantent->kind == rckHtml) )
goto set_html; /* resource kind knowledge, no explicit user preference */
kind_done: {}
}
my_enum1 enum
{ wrfNone = 0, wrfFound = 0x01
} my_enum2(unsigned char) tWindowRedrawingFlags; /* CHECKME: rename! */
typedef struct
{ tLinenumber origin_y, currline, highest_backward_line;
size_t searchlen;
short row, maxrow, mincol;
tWindowRedrawingTask task;
tWindowRedrawingFlags flags;
} tWindowRedrawingData; /* CHECKME: rename! */
static void __document_display_line(tWindowRedrawingData* wrd,
tRendererData* data)
/* actually draws a line on the screen */
{ const tRendererElement* element = data->element;
(void) move(wrd->row, wrd->mincol);
while (element != NULL)
{ const tRendererText* text = element->text;
const tRendererAttr* attr = element->attr;
size_t count = 0, textcount = element->textcount;
if (element->is_spacer) { while (count++ < textcount) addch(' '); }
else
{ while (count < textcount)
{ tRendererAttr a = 0;
if (text != NULL) a |= (tRendererAttr) text[count];
if (attr != NULL) a |= attr[count];
if (a == 0) /* "should not happen" */
a = (tRendererAttr) ((unsigned char) ' ');
addch(a); count++;
}
}
element = element->next;
}
(void) clrtoeol(); wrd->row++;
if (wrd->row > wrd->maxrow) data->flags |= rdfCallerDone;
}
static void document_display_line__standard(tRendererData* data)
{ tWindowRedrawingData* wrd = data->line_callback_data;
if (wrd->currline < wrd->origin_y)
{ wrd->currline++;
if (wrd->currline >= wrd->origin_y) data->flags &= ~rdfVirtual;
}
else { wrd->currline++; __document_display_line(wrd, data); }
}
static /*@null@*/ const char* search_string = NULL;
static tBoolean search_in_line(const tRendererElement* element,
size_t searchlen)
/* returns whether it found something */
{ tBoolean retval = falsE;
while (element != NULL)
{ const char* text;
size_t textcount = element->textcount;
if ( (!(element->is_spacer)) && ( (text = element->text) != NULL ) &&
( (textcount = element->textcount) >= searchlen ) &&
(my_strncasestr(text, search_string, textcount)) )
{ retval = truE; break; }
element = element->next;
}
return(retval);
}
static void document_display_line__search(tRendererData* data)
{ tWindowRedrawingData* wrd = data->line_callback_data;
if (wrd->currline <= wrd->origin_y)
{ wrd->currline++;
if (wrd->currline > wrd->origin_y) data->flags &= ~rdfVirtual;
}
else if (wrd->flags & wrfFound)
{ wrd->currline++; do_redraw: __document_display_line(wrd, data); }
else
{ wrd->currline++;
if (search_in_line(data->element, wrd->searchlen))
{ if (data->document != NULL) data->document->origin_y = wrd->currline - 1;
wrd->flags |= wrfFound; goto do_redraw;
}
}
}
static void document_display_line__search_backward(tRendererData* data)
{ tWindowRedrawingData* wrd = data->line_callback_data;
wrd->currline++;
if (wrd->currline < wrd->origin_y)
{ if (search_in_line(data->element, wrd->searchlen))
{ wrd->highest_backward_line = wrd->currline - 1; wrd->flags |= wrfFound; }
}
else data->flags |= rdfCallerDone;
}
static void document_display_line__count(tRendererData* data)
{ tWindowRedrawingData* wrd = data->line_callback_data;
wrd->currline++;
}
static void document_do_display(/*@notnull@*/ tBrowserDocument* document,
const short mincol, const short minrow, const short maxcol,
const short maxrow, const tWindowRedrawingTask task)
{ tRendererData data;
tWindowRedrawingData wrd;
const tBoolean is_search =
cond2boolean( (task == wrtSearch) || (task == wrtSearchBackward) );
#if !TGC_IS_GRAPHICS
tActiveElementNumber _ae;
#endif
if (task != wrtRedrawRecursive) { (document->ops->display_meta1)(document); }
wrc_rd_setup(&data, document, maxcol - mincol + 1);
my_memclr_var(wrd); data.line_callback_data = &wrd;
data.line_callback = document_display_line__standard; /* default callback */
if (is_search)
{ wrd.searchlen = strlen(search_string);
if (task == wrtSearch) data.line_callback = document_display_line__search;
else if (task == wrtSearchBackward)
data.line_callback = document_display_line__search_backward;
}
else if (task == wrtToEnd) data.line_callback = document_display_line__count;
#if !TGC_IS_GRAPHICS
_ae = document->aecur;
if (_ae != INVALID_AE) do_activate_element(document, _ae, falsE);
#endif
wrd.row = minrow; wrd.maxrow = maxrow; wrd.mincol = mincol; wrd.task = task;
wrd.origin_y = document->origin_y;
switch (task)
{ case wrtSearch: case wrtToEnd: data.flags |= rdfVirtual; break;
case wrtSearchBackward: /* nothing */ break;
default: if (wrd.origin_y > 0) { data.flags |= rdfVirtual; } break;
}
renderer_run(&data);
if (task == wrtRedraw) { /* nothing (test most likely case first) */ }
else if (is_search)
{ const tBoolean did_find = cond2boolean(wrd.flags & wrfFound);
if ( (did_find) && (task == wrtSearchBackward) )
document->origin_y = wrd.highest_backward_line;
/* IMPROVEME: don't redraw always, only if necessary! */
document_do_display(document, mincol, minrow, maxcol, maxrow,
wrtRedrawRecursive); /* risk a recursion :-) */
if (did_find) show_message(_("Found!"), falsE);
else show_message(_("Not found!"), truE);
goto out; /* rest was done in recursively called instance */
}
else if (task == wrtToEnd)
{ const short height = maxrow - minrow;
const tLinenumber num = wrd.currline;
document->origin_y = ( (num > height) ? (num - height) : 0 );
document_do_display(document, mincol, minrow, maxcol, maxrow,
wrtRedrawRecursive);
goto out;
}
if (wrd.row > wrd.maxrow) document->flags |= wvfScreenFull;
else document->flags &= ~wvfScreenFull;
while (wrd.row <= wrd.maxrow)
{ (void) move(wrd.row, mincol); (void) clrtoeol(); wrd.row++; }
#if !TGC_IS_GRAPHICS
if (_ae == INVALID_AE) _ae = document->former_ae;
if (_ae != INVALID_AE)
{ if (!is_ae_visible(document, _ae))
{ if (document->flags & wvfScrollingUp)
_ae = previous_visible_ae(document, _ae);
else _ae = next_visible_ae(document, _ae);
}
}
else
{ _ae = next_visible_ae(document, -1); /* find the first visible ae */
if ( (_ae != INVALID_AE) && (document->flags & wvfScrollingUp) )
{ /* proceed to the _last_ visible active element */
tActiveElementNumber _ae2, l = document->aenum;
for (_ae2 = _ae + 1; _ae2 < l; _ae2++)
{ if (is_ae_visible(document, _ae2)) _ae = _ae2; }
}
}
if (_ae != INVALID_AE) activate_element(document, _ae);
#endif
(document->ops->display_meta2)(document);
document->flags &= ~wvfScrollingUp;
#if 0
/* some texts for the very next versions...
_("looking for fragment"), _("fragment not found")
_("disk cache file"), _("Do not edit manually while retawq is running!"),
_("Upload local file: "), _("Upload to FTP URL: "),
_("Resume upload? [(r)esume, (o)verwrite%s]"), _(", (s)kip"),
_("File size: local %d, remote %d")
*/
#endif
out: {}
}
static void document_display(tBrowserDocument* document,
const tWindowRedrawingTask task)
{ short mincol, minrow, maxcol, maxrow;
if (document_find_coords(document, &mincol, &minrow, &maxcol, &maxrow))
{ document_do_display(document, mincol, minrow, maxcol, maxrow, task);
must_reset_cursor();
}
/* "else": "should not happen" */
}
/** Downloads */
#if CONFIG_EXTRA & (EXTRA_DOWNLOAD | EXTRA_DUMP)
static void print_automated_uri(const char* uri)
{ if (lfdmbs(2))
{ my_write_str(fd_stderr, strUriColonSpace);
/* (no _() here - this output shall be machine-parseable!) */
my_write_str(fd_stderr, uri); my_write_str(fd_stderr, strNewline);
}
}
#endif
#if CONFIG_EXTRA & EXTRA_DOWNLOAD
static const char strCantDownload[] = N_("can't download to file");
static const tBrowserDocument* khmli_download_referrer = NULL;
static tResourceRequest* pm_request; /* for pmDownload */
static tBoolean rwd_cb_reqrem(const tRemainingWork* rw)
{ tResourceRequest* request = (tResourceRequest*) (rw->data1);
request_remove(request); return(falsE);
}
static void schedule_request_removal(tResourceRequest* request)
{ /* Sometimes we can't remove a request directly because then a dhm-related
call chain could try to access deallocated memory on unwinding. Thus: */
tRemainingWork* rw = remaining_work_create(rwd_cb_reqrem);
rw->data1 = request;
}
static void download_request_callback(void* _request,
tDhmNotificationFlags flags)
{ tResourceRequest* request = (tResourceRequest*) _request;
#if CONFIG_DEBUG
sprint_safe(debugstrbuf,"download_request_callback(%p, %d)\n",request,flags);
debugmsg(debugstrbuf);
#endif
if (flags & (dhmnfDataChange | dhmnfMetadataChange))
{ const tBoolean doing_auto = cond2boolean( (program_mode == pmDownload) &&
(request == pm_request) );
tResource* resource = request->resource;
tResourceError re;
if (resource != NULL)
{ re = resource->error;
if (re != reFine)
{ if (doing_auto)
{ const char* text;
#if OPTION_TLS
if ( (re == reTls) && (tls_errtext(resource, strbuf)) ) text=strbuf;
else
#endif
{ handle_re: text = _(strResourceError[re]); }
fatal_error(0, text);
}
req_rem: schedule_request_removal(request);
}
else if (resource->flags & rfFinal)
{ if (doing_auto) do_quit(); /* done */
goto req_rem;
}
}
else if ( (re = request->error) != reFine )
{ if (doing_auto) goto handle_re;
else goto req_rem;
}
}
else if (flags & dhmnfAttachery) /* a resource was attached to the request */
{ dhm_notification_setup(request->resource, download_request_callback,
request, dhmnfDataChange | dhmnfMetadataChange, dhmnSet);
}
}
static void download_queue(tResourceRequest* request, int fd)
/* some common stuff for slightly different kinds of downloads */
{ request->flags |= rrfDownload; make_fd_cloexec(fd);
sinking_data_mcleanup(); sinking_data.download_fd = fd;
sinking_data.flags |= sdfIsDownloadFdValid;
sinking_data_shift(&(request->sinking_data));
request_queue(request, rraEnforcedReload);
}
static void content_download_to_fd(const char* uri,
const tBrowserDocument* referrer, int fd)
{ tPrrData prr_data;
tResourceRequest* request;
prr_setup(&prr_data, uri, prrfNone); prr_data.referrer = referrer;
prepare_resource_request(&prr_data); request = prr_data.result;
if (program_mode == pmDownload)
{ pm_request = request; print_automated_uri(request->uri_data->uri); }
if (request->state == rrsError)
{ inform_about_error(0, _(strResourceError[request->error]));
my_close(fd); goto out;
}
prr_data.result = NULL;
dhm_notification_setup(request, download_request_callback, request,
dhmnfDataChange | dhmnfMetadataChange | dhmnfAttachery, dhmnSet);
download_queue(request, fd);
out:
prr_setdown(&prr_data);
}
static void __content_download(const char* filename, tBoolean may_overwrite)
{ const char* uri = khmli_download_uri;
const tBrowserDocument* referrer = khmli_download_referrer;
int fd, cflags = O_CREAT | O_TRUNC | O_WRONLY;
struct stat statbuf;
khmli_download_referrer = NULL;
if (!may_overwrite) cflags |= O_EXCL;
fd = my_create(filename, cflags, S_IRUSR | S_IWUSR);
if (fd < 0)
{ failed: inform_about_error(errno, _(strCantDownload)); return; }
if (my_fstat(fd, &statbuf) != 0)
{ int e; close_bad: e = errno; my_close(fd); errno = e; goto failed; }
if (!S_ISREG(statbuf.st_mode))
{ errno = ( (S_ISDIR(statbuf.st_mode)) ? EISDIR : 0 );
goto close_bad;
}
content_download_to_fd(uri, referrer, fd);
}
static __my_inline void content_download(tBoolean may_overwrite)
{ __content_download(khmli_filename, may_overwrite);
}
#endif /* #if CONFIG_EXTRA & EXTRA_DOWNLOAD */
/** Dumps */
#if CONFIG_EXTRA & EXTRA_DUMP
typedef struct
{ char* line;
int fd;
} tContentDumpData;
static void content_dump_line(tRendererData* data)
{ const tContentDumpData* cdd = data->line_callback_data;
const tRendererElement* element = data->element;
char* line = cdd->line;
size_t count = 0, cnt;
while (element != NULL)
{ const size_t textcount = element->textcount;
if (textcount > 0)
{ if (element->is_spacer)
{ for (cnt = 0; cnt < textcount; cnt++) line[count++] = ' '; }
else
{ const tRendererText* text = element->text;
for (cnt = 0; cnt < textcount; cnt++) line[count++] = text[cnt];
}
}
element = element->next;
}
line[count++] = '\n';
if (my_write(cdd->fd, line, count) != (ssize_t) count)
{ inform_about_error(errno, _("can't dump line"));
data->flags |= rdfCallerDone;
}
}
static void content_dump_to_fd(tBrowserDocument* document, int fd,
const tDumpStyle* dump_style)
{ tRendererData data;
tContentDumpData cdd;
const size_t line_width = 80;
const tDumpStyleFlags flags = ( (dump_style != NULL) ? (dump_style->flags) :
dsfNone );
my_memclr_var(data);
data.line_callback = content_dump_line; data.line_callback_data = &cdd;
data.ra = raLayout; data.document = document; data.line_width = line_width;
data.flags = rdfAlignment;
if (flags & dsfMustHtml) { do_html: data.flags |= rdfHtml; }
else if (!(flags & dsfMustSource))
{ if (document->bddm == bddmHtml) goto do_html;
if ( (document->cantent != NULL) && (document->cantent->kind == rckHtml) )
goto do_html;
}
cdd.fd = fd; cdd.line = __memory_allocate(line_width + 5, mapString);
renderer_run(&data); memory_deallocate(cdd.line);
}
static void content_dump(tBrowserDocument* document, const char* filename,
tBoolean may_overwrite, const tDumpStyle* dump_style)
{ int fd, cflags = O_CREAT | O_TRUNC | O_WRONLY;
struct stat statbuf;
if (!may_overwrite) cflags |= O_EXCL;
fd = my_create(filename, cflags, S_IRUSR | S_IWUSR);
if (fd < 0)
{ failed: inform_about_error(errno, _("can't dump to file")); return; }
if (my_fstat(fd, &statbuf) != 0)
{ int e; close_bad: e = errno; my_close(fd); errno = e; goto failed; }
if (!S_ISREG(statbuf.st_mode))
{ errno = ( (S_ISDIR(statbuf.st_mode)) ? EISDIR : 0 );
goto close_bad;
}
content_dump_to_fd(document, fd, dump_style); my_close(fd);
}
static void dump_request_callback(void* _request, tDhmNotificationFlags flags)
{ tResourceRequest* request = (tResourceRequest*) _request;
#if CONFIG_DEBUG
sprint_safe(debugstrbuf, "dump_request_callback(%p, %d)\n", request, flags);
debugmsg(debugstrbuf);
#endif
if (flags & (dhmnfDataChange | dhmnfMetadataChange))
{ tResource* resource = request->resource;
tResourceError re;
if (resource != NULL)
{ re = resource->error;
if (re != reFine)
{ const char* text;
#if OPTION_TLS
if ( (re == reTls) && (tls_errtext(resource, strbuf)) ) text = strbuf;
else
#endif
{ handle_re: text = _(strResourceError[re]); }
fatal_error(0, text);
}
else if (resource->flags & rfFinal)
{ tBrowserDocument document;
tDumpStyle ds;
my_memclr_var(document); document.cantent = resource->cantent;
my_memclr_var(ds); content_dump_to_fd(&document, fd_stdout, &ds);
do_quit(); /* done */
}
}
else if ( (re = request->error) != reFine ) goto handle_re;
}
else if (flags & dhmnfAttachery) /* a resource was attached to the request */
{ dhm_notification_setup(request->resource, dump_request_callback, request,
dhmnfDataChange | dhmnfMetadataChange, dhmnSet);
}
}
static one_caller void content_dump_auto(const char* uri)
{ tPrrData prr_data;
tResourceRequest* request;
prr_setup(&prr_data, uri, prrfNone); prepare_resource_request(&prr_data);
request = prr_data.result; prr_data.result = NULL;
print_automated_uri(request->uri_data->uri);
if (request->state == rrsError)
fatal_error(0, _(strResourceError[request->error]));
dhm_notification_setup(request, dump_request_callback, request,
dhmnfDataChange | dhmnfMetadataChange | dhmnfAttachery, dhmnSet);
request_queue(request, rraEnforcedReload); prr_setdown(&prr_data);
}
#endif /* #if CONFIG_EXTRA & EXTRA_DUMP */
/** Sessions */
#if CONFIG_SESSIONS
static const char strSessionMarker[] = "|#|x:";
#define smlp (3) /* session marker letter position */
#define psm(buf, ch) /* prepare session marker */ \
do { strcpy((buf), strSessionMarker); *((buf) + smlp) = (ch); } while (0)
#define bdp(buf) ((buf) + strlen(strSessionMarker)) /* buffer data position */
/* CHECKME: maybe this shouldn't be global... */
static char flagsbuf[50], linenumbuf[50], widthbuf[50], winbuf[50],
htmltitlemarker[10], urimarker[10];
static void session_save(const char* filename, tBoolean may_overwrite)
{ int fd, cflags = O_CREAT | O_TRUNC | O_WRONLY;
struct stat statbuf;
tWindow* window;
/* create the file */
if (!may_overwrite) cflags |= O_EXCL;
fd = my_create(filename, cflags, S_IRUSR | S_IWUSR);
if (fd < 0) { failed: show_message_osfailure(errno, NULL); return; }
if (my_fstat(fd, &statbuf) != 0)
{ int e; close_bad: e = errno; my_close(fd); errno = e; goto failed; }
if (!S_ISREG(statbuf.st_mode))
{ errno = ( (S_ISDIR(statbuf.st_mode)) ? EISDIR : 0 );
goto close_bad;
}
/* save the session */
my_write_str(fd,
_("# Session file for retawq (http://retawq.sourceforge.net/)\n"));
psm(flagsbuf, 'f'); psm(linenumbuf, 'l'); psm(htmltitlemarker, 'h');
psm(urimarker, 'u'); psm(widthbuf, 'w'); *(bdp(widthbuf)) = *strTG;
winbuf[0] = 'W'; winbuf[1] = ':';
window = windowlisthead;
while (window != NULL)
{ void (*func)(tWindow*, int) = window->spec->save_session;
if (func != NULL) (func)(window, fd);
window = window->next;
}
/* finish */
my_close(fd);
}
static tLinenumber session_linenum;
static tWindow* session_window;
#if TGC_IS_CURSES
static tWindow* session_viswin[2];
#endif
#define SESSIONPART_MAXNUM (10)
static const char* session_part[SESSIONPART_MAXNUM];
static unsigned char session_numparts;
static void session_resume_split(char* line)
{ char* temp;
session_numparts = 0;
loop:
temp = my_strstr(line, "|#|");
if (temp != NULL)
{ char ch = temp[3];
if (!my_islower(ch)) { incandloop: line = temp + 1; goto loop; }
if (temp[4] != ':') goto incandloop;
*temp = '\0';
session_part[session_numparts++] = temp + 3;
if (session_numparts < SESSIONPART_MAXNUM)
{ line = temp + 5; goto loop; } /* look for more */
}
}
static __my_inline void session_resume_finish_window(void)
{
#if 0 /* FIXME! */
if ( (session_window != NULL) && (session_currview != NULL) )
session_window->current_view = session_currview;
#endif
}
static void session_resume_window(void)
{ session_resume_finish_window();
session_window = wk_browser_create();
#if 0 /* FIXME! */
session_currview = NULL;
#endif
}
static void session_resume_line(const char* line, const char* limit)
{ size_t len = limit - line;
char buf[STRBUF_SIZE], *temp;
unsigned char count;
session_linenum++;
if (len <= 0) return; /* empty line */
if (len > STRBUF_SIZE - 5) return; /* very long line, can't be serious */
my_memcpy(buf, line, len); buf[len] = '\0'; /* (for simplicity) */
if (buf[len - 1] == '\r') /* (e.g. manually written file?) */
buf[len - 1] = '\0';
debugmsg("session_resume_line(): *"); debugmsg(buf); debugmsg("*\n");
temp = buf;
while ( (*temp == ' ') || (*temp == '\t') ) temp++; /* skip whitespace */
if (*temp == '#') return; /* comment line */
if (!strncmp(temp, "W:", 2))
{ session_resume_window();
#if TGC_IS_CURSES
session_resume_split(temp + 2);
for (count = 0; count < session_numparts; count++)
{ const char *str = session_part[count], *data = str + 2;
int num;
debugmsg("session part W: *"); debugmsg(str); debugmsg("*\n");
if (*str != 'v') continue; /* unknown part */
if (*data == '\0') continue; /* empty part */
my_atoi(data, &num, &data, 9);
if ( (num >= 0) && (num <= 1) && (*data == '\0') )
session_viswin[num] = session_window;
}
#endif
}
else if (!strncmp(temp, "U:", 2))
{ const char *uri, *flagstr, *html_title;
session_resume_split(temp + 2);
uri = flagstr = html_title = NULL;
for (count = 0; count < session_numparts; count++)
{ const char *str = session_part[count], *data = str + 2;
debugmsg("session part U: *"); debugmsg(str); debugmsg("*\n");
if (*data == '\0') continue; /* empty part */
switch (*str)
{ case 'u': uri = data; break;
case 'f': flagstr = data; break;
case 'h': html_title = data; break;
/* "default": unknown part; IMPLEMENTME: print warning/error? */
}
}
if (uri != NULL) /* got useful information */
{ /* tResourceRequest* request; */
tPrrFlags prrf = prrfRedrawOne;
/* tBoolean is_currview = falsE; */
if (flagstr != NULL)
{ const char* f = flagstr;
char ch;
while ( (ch = *f++) != '\0' )
{ if (ch == 's') prrf |= prrfSource;
else if (ch == 'h') prrf |= prrfHtml;
/* else if (ch == 'c') is_currview = truE; */
}
}
if (session_window == NULL) session_resume_window();
wk_browser_prr(session_window, uri, prrf, NULL);
#if 0 /* FIXME! */
if (request != NULL)
{ if (is_currview) session_currview = view;
if (html_title != NULL)
my_strdedup(document->minor_html_title, html_title);
}
#endif
}
}
/* "else": IMPLEMENTME: bad line, print a message, stop resumption! */
}
static void session_resume(const char* filename)
{ size_t size;
void* filebuf;
char *buf, *end, *temp;
if (*filename == '\0') filename = config.session_default;
debugmsg("session_resume(): *"); debugmsg(filename); debugmsg("*\n");
switch (my_mmap_file_readonly(filename, &filebuf, &size))
{ case 0: show_message_osfailure(errno,NULL); return; /*@notreached@*/ break;
case 1: goto out; /*@notreached@*/ break;
}
buf = (char*) filebuf; end = buf + size - 1;
session_window = NULL;
#if TGC_IS_CURSES
session_viswin[0] = session_viswin[1] = NULL;
#endif
#if 0 /* FIXME! */
session_currview = NULL;
#endif
session_linenum = 0;
temp = buf;
while (temp <= end)
{ if (*temp == '\n') { session_resume_line(buf, temp); buf = temp + 1; }
temp++;
}
my_munmap(filebuf, size);
session_resume_finish_window();
#if TGC_IS_CURSES
{ tWindow *viswin0 = session_viswin[0], *viswin1 = session_viswin[1];
if ( (viswin0 == NULL) && (viswin1 != NULL) )
{ viswin0 = viswin1; viswin1 = NULL; }
if (viswin0 != NULL) visible_window_x[0] = viswin0;
if ( (viswin1 != NULL) && (viswin1 != visible_window_x[0]) )
visible_window_x[1] = viswin1;
}
#endif
window_redraw_all();
out:
show_message(_(strDone), falsE);
}
#endif /* #if CONFIG_SESSIONS */
/** Contextual menus (that's a misnomer nowadays; should be something like
"general/basic menu handling", but I don't want to change the nice "cm_"
prefix all over the place) */
#if CONFIG_MENUS
#if TGC_IS_CURSES
#if CONFIG_MENUS & MENUS_BAR
static tBoolean doing_mbar = falsE;
#endif
static void my_hline(short x1, short x2, short y)
/* draws a horizontal line */
{ (void) move(y, x1);
while (x1 <= x2) { (void) addch(__MY_HLINE); x1++; }
}
static void my_vline(short y1, short y2, short x)
/* draws a vertical line */
{ while (y1 <= y2) { (void) mvaddch(y1, x, __MY_VLINE); y1++; }
}
static void draw_box(short x1, short y1, short x2, short y2)
{ my_set_color(cpnBlue); /* attron(A_BOLD); */
my_hline(x1 + 1, x2 - 1, y1); my_hline(x1 + 1, x2 - 1, y2);
my_vline(y1 + 1, y2 - 1, x1); my_vline(y1 + 1, y2 - 1, x2);
(void) mvaddch(y1, x1, __MY_UL); (void) mvaddch(y1, x2, __MY_UR);
(void) mvaddch(y2, x1, __MY_LL); (void) mvaddch(y2, x2, __MY_LR);
/* attroff(A_BOLD); */ my_set_color(cpnDefault);
}
typedef void* tCmCallbackData;
typedef void (*tCmCallbackFunction)(tCmCallbackData);
typedef signed int tCmNumber; /* ("signed" for simplicity only) */
typedef struct
{ const char* render;
tCmCallbackFunction function;
tCmCallbackData data;
} tCmEntry;
static tCmEntry* cm_info;
static tCmNumber cm_num, cm_maxnum, cm_current,
cm_topmost, cm_onscreen_num; /* what's displayed on the screen */
static tCoordinate cm_x1, cm_y1, cm_x2, cm_y2;
static char* cm_select_bitfield;
static tBoolean cm_select_is_multiple;
static tHtmlOptionNumber cm_select_old; /* formerly selected option */
static void cm_init(void)
{ cm_info = NULL;
cm_num = cm_maxnum = cm_current = cm_topmost = 0;
}
static void __cm_add(const char* str, tCmCallbackFunction func,
tCmCallbackData data)
{ if (cm_num >= cm_maxnum)
{ cm_maxnum += 20;
cm_info = memory_reallocate(cm_info, cm_maxnum * sizeof(tCmEntry),
mapOther);
}
cm_info[cm_num].render = my_strdup(str);
cm_info[cm_num].function = func; cm_info[cm_num].data = data;
cm_num++;
}
#define cm_add(str, func, val) \
__cm_add(unconstify_or_(str), func, (tCmCallbackData) MY_INT_TO_POINTER(val))
static my_inline void cm_add_separator(void)
{ if (cm_num > 0) __cm_add(strMinus, NULL, NULL);
}
static void cm_activate(tBoolean active)
/* indicates on screen what's currently selected ("menu cursor") */
{ short x, x0 = cm_x1 + 1, y = (cm_y1 + 1) + (cm_current - cm_topmost);
(void) move(y, x0);
for (x = x0; x < cm_x2; x++)
{ chtype ch = inch();
if (active) ch |= A_REVERSE;
else ch &= ~A_REVERSE;
(void) addch(ch);
}
(void) move(LINES - 1, 0);
}
static void __cm_draw(void)
/* draws the menu texts */
{ tCmNumber num;
short x = cm_x1 + 1, width = cm_x2 - cm_x1 - 1;
for (num = 0; num < cm_onscreen_num; num++)
{ short y = cm_y1 + num + 1;
const char* str = cm_info[cm_topmost + num].render;
if ( (*str == '-') && (str[1] == '\0') ) my_hline(x, x + width - 1, y);
else
{ short count = width - strlen(str);
/*(void)*/ mvaddnstr(y, x, str, width);
while (count-- > 0) (void) addch(' ');
}
}
}
static one_caller tBoolean cm_draw(short x, short y, const char* title)
/* calculates and draws the menu rectangle; returns whether it worked */
{ short titlelen = ( (title != NULL) ? strlen(title) : 0 ),
titlewidth = ( (titlelen > 0) ? (titlelen + 2) : 0 ),
width = titlewidth, cols = COLS, lines = LINES, maxwidth = cols - 10;
tCmNumber num, onscreen_maxnum = lines - 4;
if ( (maxwidth <= 5) || (onscreen_maxnum <= 2) ) return(falsE);
for (num = 0; num < cm_num; num++)
{ short len = strlen(cm_info[num].render);
if (width < len) width = len;
}
if (width > maxwidth) width = maxwidth;
cm_onscreen_num = MIN(cm_num, onscreen_maxnum);
if ( (x < 0) || (x >= cols) ) x = 0;
if ( (y < 0) || (y >= lines) ) y = 0;
if (x + width + 1 >= cols - 1) x = cols - width - 3;
if (y + cm_onscreen_num + 1 >= lines - 2) y = lines - cm_onscreen_num - 4;
cm_x1 = x; cm_y1 = y; cm_x2 = x + width + 1; cm_y2 = y + cm_onscreen_num + 1;
draw_box(cm_x1, cm_y1, cm_x2, cm_y2);
if (titlelen > 0)
{ short maxlen = cm_x2 - cm_x1 - 3, len = MIN(maxlen, titlelen),
offset = (maxlen - len) / 2;
if (offset < 0) offset = 0;
my_set_color(cpnBlue);
/*(void)*/ mvaddnstr(cm_y1, cm_x1 + 2 + offset, title, len);
my_set_color(cpnDefault);
}
__cm_draw(); cm_activate(truE);
return(truE);
}
static void cm_start(short x, short y, const char* title, const char* errstr)
{ if (cm_num > 0)
{ if (cm_draw(x, y, title)) key_handling_mode = khmMenu;
else terminal_too_small();
}
else
{ /* The menu would be empty, so we won't show it. */
if (errstr != NULL) show_message(errstr, truE);
}
}
static void cm_handle_command_code(tCmCallbackData data)
{ (void) handle_command_code((tProgramCommandCode) MY_POINTER_TO_INT(data));
}
#if CONFIG_MENUS & MENUS_CONTEXT
static void cm_setup_contextual(short x, short y, tActiveElementNumber _ae)
/* sets up a contextual menu with ae-related entries (unless <_ae> is invalid)
and some general entries */
{ tWindow* window = current_window_x; /* FIXME! */
void (*func)(tWindow*, short*, short*, tActiveElementNumber) =
window->spec->setup_cm;
cm_init();
if (func != NULL) (func)(window, &x, &y, _ae);
cm_add(strUcClose, cm_handle_command_code, pccWindowClose);
cm_add(strUcQuit, cm_handle_command_code, pccQuit);
cm_start(x, y, _(strContext), NULL);
}
#endif /* #if CONFIG_MENUS & MENUS_CONTEXT */
#if CONFIG_MENUS & MENUS_HTML
static void cm_handle_select_tag(tCmCallbackData data)
/* callback function for menus for the HTML