NetSurf
menu.c
Go to the documentation of this file.
1/*
2 * Copyright 2008-9, 2013, 2017 Chris Young <chris@unsatisfactorysoftware.co.uk>
3 *
4 * This file is part of NetSurf, http://www.netsurf-browser.org/
5 *
6 * NetSurf is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; version 2 of the License.
9 *
10 * NetSurf is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19#include "amiga/os3support.h"
20
21#include <string.h>
22#include <stdlib.h>
23
24#include <proto/gadtools.h>
25#include <proto/graphics.h>
26#include <proto/intuition.h>
27
28#include <libraries/gadtools.h>
29#ifdef __amigaos4__
30#include <intuition/menuclass.h>
31#endif
32
33#include <classes/window.h>
34#include <proto/label.h>
35#include <images/label.h>
36#include <proto/bitmap.h>
37#include <images/bitmap.h>
38
39#include <reaction/reaction_macros.h>
40
41#include "utils/log.h"
42#include "utils/messages.h"
43
44#include "amiga/gui.h"
45#include "amiga/libs.h"
46#include "amiga/menu.h"
47#include "amiga/utf8.h"
48
49enum {
55};
56
57#define NSA_MAX_HOTLIST_MENU_LEN 100
58
59static Object *restrict menu_glyph[NSA_GLYPH_MAX];
61static bool menu_glyphs_loaded = false;
62
63bool ami_menu_get_selected(struct Menu *menu, struct IntuiMessage *msg)
64{
65 bool checked = false;
66
67 if(LIB_IS_AT_LEAST((struct Library *)IntuitionBase, 54, 6)) {
68#ifdef __amigaos4__
69 ULONG state;
70 struct ExtIntuiMessage *emsg = (struct ExtIntuiMessage *)msg;
71
72 state = IDoMethod((Object *)menu, MM_GETSTATE, 0, emsg->eim_LongCode, MS_CHECKED);
73 if(state & MS_CHECKED) checked = true;
74#endif
75 } else {
76 if(ItemAddress(menu, msg->Code)->Flags & CHECKED) checked = true;
77 }
78
79 return checked;
80}
81
82/* menu creation code */
83void ami_menu_free_lab_item(struct ami_menu_data **md, int i)
84{
85 if(md[i] == NULL) return;
86 if(md[i]->menulab &&
87 (md[i]->menulab != NM_BARLABEL) &&
88 (md[i]->menulab != ML_SEPARATOR)) {
89 if(md[i]->menutype & MENU_IMAGE) {
90 if(md[i]->menuobj) DisposeObject(md[i]->menuobj);
91 }
92
93 ami_utf8_free(md[i]->menulab);
94 }
95
96 if(md[i]->menukey != NULL) free(md[i]->menukey);
97
98 md[i]->menulab = NULL;
99 md[i]->menuobj = NULL;
100 md[i]->menukey = NULL;
101 md[i]->menutype = 0;
102 free(md[i]);
103 md[i] = NULL;
104}
105
106static void ami_menu_free_labs(struct ami_menu_data **md, int max)
107{
108 int i;
109
110 for(i = 0; i <= max; i++) {
112 }
113}
114
115void ami_menu_alloc_item(struct ami_menu_data **md, int num, UBYTE type,
116 const char *restrict label, const char *restrict key, const char *restrict icon,
117 void *restrict func, void *restrict hookdata, UWORD flags)
118{
119 md[num] = calloc(1, sizeof(struct ami_menu_data));
120 md[num]->menutype = type;
121 md[num]->flags = flags;
122
123 if(type == NM_END) return;
124
125 if((label == NM_BARLABEL) || (strcmp(label, "--") == 0)) {
126 md[num]->menulab = NM_BARLABEL;
127 icon = NULL;
128 } else { /* horrid non-generic stuff */
129 if((num >= AMI_MENU_AREXX) && (num < AMI_MENU_AREXX_MAX)) {
130 md[num]->menulab = strdup(label);
131 } else {
132 md[num]->menulab = ami_utf8_easy(messages_get(label));
133 }
134 }
135
136 md[num]->menuicon = NULL;
137 if(key) md[num]->menukey = strdup(key);
138 if(func) md[num]->menu_hook.h_Entry = (HOOKFUNC)func;
139 if(hookdata) md[num]->menu_hook.h_Data = hookdata;
140
141#ifdef __amigaos4__
142 char menu_icon[1024];
143
144 if(LIB_IS_AT_LEAST((struct Library *)GadToolsBase, 53, 7)) {
145 if(icon) {
146 if(ami_locate_resource(menu_icon, icon) == true) {
147 md[num]->menuicon = (char *)strdup(menu_icon);
148 } else {
149 /* If the requested icon can't be found, put blank space in instead */
150 md[num]->menuicon = (char *)strdup(NSA_SPACE);
151 }
152 }
153 }
154#endif
155}
156
157static void ami_menu_load_glyphs(struct DrawInfo *dri)
158{
159#ifdef __amigaos4__
160 if(LIB_IS_AT_LEAST((struct Library *)GadToolsBase, 53, 7)) {
161 for(int i = 0; i < NSA_GLYPH_MAX; i++)
162 menu_glyph[i] = NULL;
163
164 menu_glyph[NSA_GLYPH_SUBMENU] = NewObject(NULL, "sysiclass",
165 SYSIA_Which, MENUSUB,
166 SYSIA_DrawInfo, dri,
167 TAG_DONE);
168 menu_glyph[NSA_GLYPH_AMIGAKEY] = NewObject(NULL, "sysiclass",
169 SYSIA_Which, AMIGAKEY,
170 SYSIA_DrawInfo, dri,
171 TAG_DONE);
172 GetAttr(IA_Width, menu_glyph[NSA_GLYPH_SUBMENU],
174 GetAttr(IA_Width, menu_glyph[NSA_GLYPH_AMIGAKEY],
176
177 menu_glyphs_loaded = true;
178 }
179#endif
180}
181
183{
184#ifdef __amigaos4__
185 if(LIB_IS_AT_LEAST((struct Library *)GadToolsBase, 53, 7)) {
186 int i;
187 if(menu_glyphs_loaded == false) return;
188
189 for(i = 0; i < NSA_GLYPH_MAX; i++) {
190 if(menu_glyph[i]) DisposeObject(menu_glyph[i]);
191 menu_glyph[i] = NULL;
192 };
193
194 menu_glyphs_loaded = false;
195 }
196#endif
197}
198
199static int ami_menu_calc_item_width(struct ami_menu_data **md, int j, struct RastPort *rp)
200{
201 int space_width = TextLength(rp, " ", 1);
202 int item_size;
203
204 item_size = TextLength(rp, md[j]->menulab, strlen(md[j]->menulab));
205 item_size += space_width;
206
207 if(md[j]->menukey) {
208 item_size += TextLength(rp, md[j]->menukey, 1);
210 /**TODO: take account of the size of other imagery too
211 */
212 } else {
213 /* assume worst case - it doesn't really matter if we make menus wider */
214 item_size += TextLength(rp, "M", 1);
216 }
217
218 if(md[j]->menuicon) {
219 item_size += 16;
220 }
221
222 return item_size;
223}
224
225#ifdef __amigaos4__
226static int ami_menu_layout_mc_recursive(Object *menu_parent, struct ami_menu_data **md, int level, int i, int max)
227{
228 int j;
229 Object *menu_item = menu_parent;
230
231 for(j = i; j < max; j++) {
232 /* skip empty entries */
233 if(md[j] == NULL) continue;
234 if(md[j]->menutype == NM_IGNORE) continue;
235
236 if(md[j]->menutype == level) {
237 if(md[j]->menulab == NM_BARLABEL)
238 md[j]->menulab = ML_SEPARATOR;
239
240 if(level == NM_TITLE) {
241 menu_item = NewObject(NULL, "menuclass",
242 MA_Type, T_MENU,
243 MA_ID, j,
244 MA_Label, md[j]->menulab,
245 TAG_DONE);
246 } else {
247 menu_item = NewObject(NULL, "menuclass",
248 MA_Type, T_ITEM,
249 MA_ID, j,
250 MA_Label, md[j]->menulab,
251 MA_Image,
252 BitMapObj,
253 IA_Scalable, TRUE,
254 BITMAP_Screen, ami_gui_get_screen(),
255 BITMAP_SourceFile, md[j]->menuicon,
256 BITMAP_Masking, TRUE,
257 BitMapEnd,
258 MA_Key, md[j]->menukey,
259 MA_UserData, &md[j]->menu_hook, /* NB: Intentionally UserData */
260 MA_Disabled, (md[j]->flags & NM_ITEMDISABLED),
261 MA_Selected, (md[j]->flags & CHECKED),
262 MA_Toggle, (md[j]->flags & MENUTOGGLE),
263 TAG_DONE);
264 }
265
266 NSLOG(netsurf, DEEPDEBUG,
267 "Adding item %p ID %d (%s) to parent %p",
268 menu_item, j, md[j]->menulab, menu_parent);
269 IDoMethod(menu_parent, OM_ADDMEMBER, menu_item);
270 continue;
271 } else if (md[j]->menutype > level) {
272 j = ami_menu_layout_mc_recursive(menu_item, md, md[j]->menutype, j, max);
273 } else {
274 break;
275 }
276 }
277 return (j - 1);
278}
279
280static struct Menu *ami_menu_layout_mc(struct ami_menu_data **md, int max)
281{
282 Object *menu_root = NewObject(NULL, "menuclass",
283 MA_Type, T_ROOT,
284 MA_EmbeddedKey, FALSE,
285 TAG_DONE);
286
287 ami_menu_layout_mc_recursive(menu_root, md, NM_TITLE, 0, max);
288
289 return (struct Menu *)menu_root;
290}
291#endif
292
293static struct Menu *ami_menu_layout_gt(struct ami_menu_data **md, int max)
294{
295 int i, j;
296 int txtlen = 0;
297 int left_posn = 0;
298 struct NewMenu *nm;
299 struct Menu *imenu = NULL;
300 struct VisualInfo *vi;
301 struct Screen *scrn = ami_gui_get_screen();
302 struct RastPort *rp = &scrn->RastPort;
303 struct DrawInfo *dri = GetScreenDrawInfo(scrn);
304 int space_width = TextLength(rp, " ", 1);
305
306 if(menu_glyphs_loaded == false)
308
309 nm = calloc(1, sizeof(struct NewMenu) * (max + 1));
310 if(nm == NULL) return NULL;
311
312 for(i = 0; i < max; i++) {
313 if(md[i] == NULL) {
314 nm[i].nm_Type = NM_IGNORE;
315 continue;
316 }
317
318 if(md[i]->menutype == NM_TITLE) {
319 j = i + 1;
320 txtlen = 0;
321 do {
322 if(md[j]->menulab != NM_BARLABEL) {
323 if(md[j]->menutype == NM_ITEM) {
324 int item_size = ami_menu_calc_item_width(md, j, rp);
325 if(item_size > txtlen) {
326 txtlen = item_size;
327 }
328 }
329 }
330 j++;
331 } while((j <= max) && (md[j] != NULL) && (md[j]->menutype != NM_TITLE) && (md[j]->menutype != 0));
332 }
333#ifdef __amigaos4__
334 if(LIB_IS_AT_LEAST((struct Library *)GadToolsBase, 53, 7)) {
335 /* GadTools 53.7+ only. For now we will only create the menu
336 using label.image if there's a bitmap associated with the item. */
337 if((md[i]->menuicon != NULL) && (md[i]->menulab != NM_BARLABEL)) {
338 int icon_width = 0;
339 Object *restrict submenuarrow = NULL;
340 Object *restrict icon = BitMapObj,
341 IA_Scalable, TRUE,
342 BITMAP_Screen, scrn,
343 BITMAP_SourceFile, md[i]->menuicon,
344 BITMAP_Masking, TRUE,
345 BitMapEnd;
346
347 /* \todo make this scale the bitmap to these dimensions */
348 SetAttrs(icon,
349 BITMAP_Width, 16,
350 BITMAP_Height, 16,
351 TAG_DONE);
352
353 GetAttr(IA_Width, icon, (ULONG *)&icon_width);
354
355 if(md[i]->menutype != NM_SUB) {
356 left_posn = txtlen;
357 }
358
359 left_posn = left_posn -
360 TextLength(rp, md[i]->menulab, strlen(md[i]->menulab)) -
361 icon_width - space_width;
362
363 if((md[i]->menutype == NM_ITEM) && md[i+1] && (md[i+1]->menutype == NM_SUB)) {
365
366 submenuarrow = NewObject(NULL, "sysiclass",
367 SYSIA_Which, MENUSUB,
368 SYSIA_DrawInfo, dri,
369 IA_Left, left_posn,
370 TAG_DONE);
371 }
372
373 md[i]->menuobj = LabelObj,
374 LABEL_MenuMode, TRUE,
375 LABEL_DrawInfo, dri,
376 LABEL_DisposeImage, TRUE,
377 LABEL_Image, icon,
378 LABEL_Text, " ",
379 LABEL_Text, md[i]->menulab,
380 LABEL_DisposeImage, TRUE,
381 LABEL_Image, submenuarrow,
382 LabelEnd;
383
384 if(md[i]->menuobj) md[i]->menutype |= MENU_IMAGE;
385 }
386 }
387#endif
388 nm[i].nm_Type = md[i]->menutype;
389
390 if(md[i]->menuobj)
391 nm[i].nm_Label = (void *)md[i]->menuobj;
392 else
393 nm[i].nm_Label = md[i]->menulab;
394
395 if((md[i]->menukey) && (strlen(md[i]->menukey) == 1)) {
396 nm[i].nm_CommKey = md[i]->menukey;
397 }
398 nm[i].nm_Flags = md[i]->flags;
399 if(md[i]->menu_hook.h_Entry) nm[i].nm_UserData = &md[i]->menu_hook;
400
401 if(md[i]->menuicon) {
402 free(md[i]->menuicon);
403 md[i]->menuicon = NULL;
404 }
405 }
406
407 FreeScreenDrawInfo(scrn, dri);
408
409 vi = GetVisualInfo(scrn, TAG_DONE);
410 imenu = CreateMenus(nm, TAG_DONE);
411 LayoutMenus(imenu, vi,
412 GTMN_NewLookMenus, TRUE, TAG_DONE);
413 free(nm);
414 FreeVisualInfo(vi); /* Not using GadTools after layout so shouldn't need this */
415
416 return imenu;
417}
418
419struct Menu *ami_menu_layout(struct ami_menu_data **md, int max)
420{
421 if(LIB_IS_AT_LEAST((struct Library *)IntuitionBase, 54, 6)) {
422#ifdef __amigaos4__
423 return ami_menu_layout_mc(md, max);
424#endif
425 } else {
426 return ami_menu_layout_gt(md, max);
427 }
428}
429
430void ami_menu_free_menu(struct ami_menu_data **md, int max, struct Menu *imenu)
431{
433 if(LIB_IS_AT_LEAST((struct Library *)IntuitionBase, 54, 6)) {
434 DisposeObject((Object *)imenu); // if we detach our menu from the window we need to do this manually
435 } else {
436 FreeMenus(imenu);
437 }
438}
439
440void ami_menu_refresh(struct Menu *menu, struct ami_menu_data **md, int menu_item, int max,
441 nserror (*cb)(struct ami_menu_data **md))
442{
443#ifdef __amigaos4__
444 Object *restrict obj;
445 Object *restrict menu_item_obj;
446 int i;
447
448 if(menu == NULL) return;
449
450 if(LIB_IS_AT_LEAST((struct Library *)IntuitionBase, 54, 6)) {
451 /* find the address of the menu */
452 menu_item_obj = (Object *)IDoMethod((Object *)menu, MM_FINDID, 0, menu_item);
453
454 /* remove all children */
455 while((obj = (Object *)IDoMethod(menu_item_obj, MM_NEXTCHILD, 0, NULL)) != NULL) {
456 IDoMethod(menu_item_obj, OM_REMMEMBER, obj);
457 DisposeObject(obj);
458 }
459
460 /* free associated data */
461 for(i = (menu_item + 1); i <= max; i++) {
462 if(md[i] == NULL) continue;
464 }
465
466 /* get current data */
467 cb(md);
468
469 /* re-add items to menu */
470 ami_menu_layout_mc_recursive(menu_item_obj, md, NM_ITEM, (menu_item + 1), max);
471 }
472#endif
473}
474
struct Screen * ami_gui_get_screen(void)
Get a pointer to the screen NetSurf is running on.
Definition: gui.c:405
static struct Screen * scrn
Definition: gui.c:328
bool ami_locate_resource(char *fullpath, const char *file)
Definition: gui.c:816
void ami_menu_refresh(struct Menu *menu, struct ami_menu_data **md, int menu_item, int max, nserror(*cb)(struct ami_menu_data **md))
Definition: menu.c:440
struct Menu * ami_menu_layout(struct ami_menu_data **md, int max)
Definition: menu.c:419
static int ami_menu_calc_item_width(struct ami_menu_data **md, int j, struct RastPort *rp)
Definition: menu.c:199
void ami_menu_free_menu(struct ami_menu_data **md, int max, struct Menu *imenu)
Definition: menu.c:430
void ami_menu_alloc_item(struct ami_menu_data **md, int num, UBYTE type, const char *restrict label, const char *restrict key, const char *restrict icon, void *restrict func, void *restrict hookdata, UWORD flags)
Definition: menu.c:115
void ami_menu_free_glyphs(void)
Definition: menu.c:182
static Object *restrict menu_glyph[NSA_GLYPH_MAX]
Definition: menu.c:59
static bool menu_glyphs_loaded
Definition: menu.c:61
static int menu_glyph_width[NSA_GLYPH_MAX]
Definition: menu.c:60
void ami_menu_free_lab_item(struct ami_menu_data **md, int i)
Definition: menu.c:83
@ NSA_GLYPH_MX
Definition: menu.c:53
@ NSA_GLYPH_CHECKMARK
Definition: menu.c:52
@ NSA_GLYPH_AMIGAKEY
Definition: menu.c:51
@ NSA_GLYPH_SUBMENU
Definition: menu.c:50
@ NSA_GLYPH_MAX
Definition: menu.c:54
static void ami_menu_load_glyphs(struct DrawInfo *dri)
Definition: menu.c:157
static void ami_menu_free_labs(struct ami_menu_data **md, int max)
Definition: menu.c:106
static struct Menu * ami_menu_layout_gt(struct ami_menu_data **md, int max)
Definition: menu.c:293
bool ami_menu_get_selected(struct Menu *menu, struct IntuiMessage *msg)
Get the selected state of a menu item.
Definition: menu.c:63
#define NSA_SPACE
empty space
Definition: menu.h:37
nserror
Enumeration of error codes.
Definition: errors.h:29
const char * type
Definition: filetype.cpp:44
void ami_utf8_free(char *ptr)
Definition: utf8.c:104
char * ami_utf8_easy(const char *string)
Definition: utf8.c:109
@ AMI_MENU_AREXX
Definition: gui_menu.h:101
@ AMI_MENU_AREXX_MAX
Definition: gui_menu.h:102
#define LabelObj
Definition: libs.h:63
#define BitMapObj
Definition: libs.h:53
#define NSLOG(catname, level, logmsg, args...)
Definition: log.h:116
const char * messages_get(const char *key)
Fast lookup of a message by key from the standard Messages hash.
Definition: messages.c:241
Localised message support (interface).
APTR NewObject(struct IClass *classPtr, CONST_STRPTR classID, ULONG tagList,...)
Definition: os3support.c:434
Minimal compatibility header for AmigaOS 3.
#define LIB_IS_AT_LEAST(B, V, R)
Definition: os3support.h:55
#define IDoMethod
Definition: os3support.h:169
#define ML_SEPARATOR
Definition: os3support.h:127
Interface to utility string handling.
Object *restrict menuobj
Definition: menu.h:28
char *restrict menulab
Definition: menu.h:27
char *restrict menuicon
Definition: menu.h:30
UBYTE menutype
Definition: menu.h:32
char *restrict menukey
Definition: menu.h:29
UWORD flags
Definition: menu.h:33
struct Hook menu_hook
Definition: menu.h:31
#define max(x, y)
Definition: utils.h:50