Usermode/AxWin3 - Added delete support to text input
[tpg/acess2.git] / Usermode / Applications / axwin3_src / WM / renderers / menu.c
1 /*
2  * Acess2 Window Manager v3
3  * - By John Hodge (thePowersGang)
4  *
5  * render_menu.c
6  * - Pop-up menu window class/renderer
7  */
8 #include <common.h>
9 #include <wm_renderer.h>
10 #include <menu_messages.h>
11 #include <wm_messages.h>
12 #include <stdlib.h>
13 #include <string.h>
14
15 // === STRUCTURES ===
16 typedef struct sMenuItem
17 {
18         // Settings
19         char    *Label;
20         char    *Shortcut;
21          int    KeyOffset;
22          int    Flags;
23         
24         // Cached values
25          int    LabelWidth;     
26          int    ShortcutWidth;
27          int    UnderlineX;
28          int    UnderlineW;
29
30         char    Data[];
31 } tMenuItem;
32
33 typedef struct sMenuWindowInfo
34 {
35          int    MaxLabelWidth;
36          int    MaxShortcutWidth;
37          int    CachedW;
38
39          int    HilightedItem;  
40
41          int    MaxItems;
42          int    nItems;
43         tMenuItem       *Items[];
44 } tMenuWindowInfo;
45
46 // === PROTOTYPES ===
47 void    Renderer_Menu_Init(void);
48 tWindow *Renderer_Menu_Create(int Argument);
49 void    Renderer_Menu_Redraw(tWindow *Window);
50  int    Renderer_Menu_HandleMessage(tWindow *Window, int Msg, int Length, const void *Data);
51
52 // === CONSTANTS ===
53 const int       ciMenu_Gap = 10;        // Gap between label and shortcut
54 const int       ciMenu_TopPadding = 2;
55 const int       ciMenu_BottomPadding = 2;
56 const int       ciMenu_LeftPadding = 2;
57 const int       ciMenu_RightPadding = 2;
58 const int       ciMenu_FontHeight = 16;
59 const int       ciMenu_ItemHeight = 20;
60 const int       ciMenu_SpacerHeight = 5;
61 const tColour   cMenu_BackgroundColour = 0xCCCCCC;
62 const tColour   cMenu_BorderColour   = 0x000000;
63 const tColour   cMenu_SpacerColour   = 0x404040;
64 const tColour   cMenu_LabelColour    = 0x000000;
65 const tColour   cMenu_ShortcutColour = 0x404040;
66 const tColour   cMenu_HilightColour  = 0xE0E0E0;
67
68 // === GLOBALS ===
69 tWMRenderer     gRenderer_Menu = {
70         .Name = "Menu",
71         .CreateWindow = Renderer_Menu_Create,
72         .Redraw = Renderer_Menu_Redraw,
73         .HandleMessage = Renderer_Menu_HandleMessage
74 };
75 tFont   *gMenu_Font = NULL;     // System monospace
76
77 // === CODE ===
78 void Renderer_Menu_Init(void)
79 {
80         WM_RegisterRenderer(&gRenderer_Menu);
81 }
82
83 tWindow *Renderer_Menu_Create(int Argument)
84 {
85         tWindow *ret;
86         tMenuWindowInfo *info;
87
88         if(Argument < 5)        Argument = 5;
89         if(Argument > 200)      Argument = 200; 
90
91         ret = WM_CreateWindowStruct(sizeof(*info) + Argument*sizeof(info->Items[0]));
92         info = ret->RendererInfo;
93         info->MaxItems = Argument;
94         info->HilightedItem = -1;
95
96         ret->Flags |= WINFLAG_NODECORATE;
97         ret->H = ciMenu_TopPadding + ciMenu_BottomPadding;
98
99 //      _SysDebug("Renderer_Menu_Create: ->MaxItems = %i", info->MaxItems);
100         
101         return ret;
102 }
103
104 void Renderer_Menu_Redraw(tWindow *Window)
105 {
106         tMenuWindowInfo *info = Window->RendererInfo;
107          int    w, h, y, i;
108
109         w = info->CachedW;
110         #if 0
111         h = ciMenu_TopPadding + ciMenu_BottomPadding;
112         for( i = 0; i < info->nItems; i ++ )
113         {
114                 if( !info->Items[i] )   continue;
115                 
116                 if(info->Items[i]->Label)
117                         h += ciMenu_ItemHeight;
118                 else
119                         h += ciMenu_SpacerHeight;
120         }
121         #else
122         h = Window->H;
123         #endif
124
125 //      _SysDebug("w = %i, h = %i", w, h);
126
127         // - Move the window such that it is on screen
128         //  > Make sure to catch if the menu can't fit fully onscreen
129
130         // - Clear
131         WM_Render_FillRect(Window, 0, 0, w, h, cMenu_BackgroundColour);
132         WM_Render_DrawRect(Window, 0, 0, w, h, cMenu_BorderColour);
133
134         // - Render each item
135         y = ciMenu_TopPadding;
136         for( i = 0; i < info->nItems; i ++ )
137         {
138                 tMenuItem       *item = info->Items[i];
139                 
140                 // Unused slot
141                 if(!item)       continue;
142                 
143                 // Spacer
144                 if(!item->Label)
145                 {
146                         WM_Render_FillRect(Window,
147                                 1, y + ciMenu_SpacerHeight/2,
148                                 w-2, 1,
149                                 cMenu_SpacerColour
150                                 );
151                         y += ciMenu_SpacerHeight;
152                         continue ;
153                 }
154         
155                 // Hilight
156                 if( info->HilightedItem == i )
157                 {
158                         WM_Render_FillRect(Window,
159                                 1, y,
160                                 w-2, ciMenu_ItemHeight,
161                                 cMenu_HilightColour
162                                 );
163                 }
164         
165                 // Text
166                 WM_Render_DrawText(Window,
167                         ciMenu_LeftPadding, y+1,
168                         w, ciMenu_ItemHeight,
169                         gMenu_Font,
170                         cMenu_LabelColour,
171                         item->Label, -1
172                         );
173                 // Underline
174                 if(item->UnderlineW)
175                 {
176                         WM_Render_FillRect(Window,
177                                 ciMenu_LeftPadding + item->UnderlineX, y + 1 + ciMenu_FontHeight,
178                                 item->UnderlineW, 1,
179                                 cMenu_LabelColour
180                                 );
181                 }
182                 
183                 // Shortcut key
184                 if(item->Shortcut)
185                 {
186                         WM_Render_DrawText(Window,
187                                 w - item->ShortcutWidth - ciMenu_RightPadding, y,
188                                 w, ciMenu_ItemHeight,
189                                 gMenu_Font,
190                                 cMenu_ShortcutColour,
191                                 item->Shortcut, -1
192                                 );
193                 }
194                 
195                 y += ciMenu_ItemHeight;
196         }
197 }
198
199 int Renderer_Menu_int_AddItem(tWindow *Window, int Length, const tMenuMsg_AddItem *Msg)
200 {
201         tMenuWindowInfo *info = Window->RendererInfo;
202         tMenuItem       *item;
203         
204         // Sanity checking
205         // - Message length
206         if(Length < sizeof(*Msg) + 1 || Msg->Label[Length-sizeof(*Msg)-1] != '\0') {
207                 _SysDebug("Renderer_Menu_int_AddItem: Size checks failed");
208                 return -1;
209         }
210         // - ID Number
211         if(Msg->ID >= info->MaxItems) {
212                 _SysDebug("Renderer_Menu_int_AddItem: ID (%i) >= MaxItems (%i)",
213                         Msg->ID, info->MaxItems);
214                 return -1;
215         }
216         
217         // Don't overwrite
218         if(info->Items[Msg->ID]) {
219                 _SysDebug("- Caught overwrite of %i", Msg->ID);
220                 return 0;
221         }
222         // Bookkeeping
223         if(Msg->ID >= info->nItems)     info->nItems = Msg->ID + 1;
224         // Allocate
225         item = malloc(sizeof(tMenuItem)+strlen(Msg->Label)+1);
226         info->Items[Msg->ID] = item;
227         
228         if(Msg->Label[0] == '\0')
229         {
230                 // Spacer
231                 item->Label = NULL;
232                 WM_ResizeWindow(Window, info->CachedW, Window->H+ciMenu_SpacerHeight);
233                 
234                 return 0;
235         }
236         
237         // Actual item
238         char    *dest = item->Data;
239         const char      *src = Msg->Label;
240          int    ofs = 0;
241
242         // - Main label
243         item->KeyOffset = -1;
244         item->Label = dest;
245         for(ofs = 0; *src && *src != '\t'; ofs ++)
246         {
247                 if(*src == '&') {
248                         *dest = '\0';
249                         item->KeyOffset = ofs;
250                         src ++;
251                 }
252                 else {
253                         *dest++ = *src++;
254                 }
255         }
256         *dest++ = '\0';
257         // - Key combo / Shortcut
258         if(*src)
259         {
260                 src ++;
261                 item->Shortcut = dest;
262                 strcpy(item->Shortcut, src);
263         }
264         else
265         {
266                 item->Shortcut = NULL;
267         }
268         
269         // Get dimensions
270         // - Underline (hotkey)
271         if(item->KeyOffset == -1)
272         {
273                 item->UnderlineX = 0;
274                 item->UnderlineW = 0;
275         }
276         else
277         {
278                 char    tmp = item->Label[item->KeyOffset];
279                 // Get width of preceding substring
280                 WM_Render_GetTextDims(NULL, item->Label, item->KeyOffset, &item->UnderlineX, NULL);
281                 // Get the width of the underlined character
282                 item->Label[item->KeyOffset] = tmp;
283                 // NOTE: 1 makes only one character be parsed, even if it is >1 byte long
284                 WM_Render_GetTextDims(
285                         NULL, item->Label+item->KeyOffset, 1,
286                         &item->UnderlineW, NULL
287                         );
288                 item->Label[item->KeyOffset+1] = tmp;
289         }
290         // - Labels
291         WM_Render_GetTextDims(NULL, item->Label, -1, &item->LabelWidth, NULL);
292         if(item->Shortcut)
293                 WM_Render_GetTextDims(NULL, item->Shortcut, -1, &item->ShortcutWidth, NULL);
294         else
295                 item->ShortcutWidth = 0;
296         
297         if( item->LabelWidth > info->MaxLabelWidth )
298                 info->MaxLabelWidth = item->LabelWidth;
299         if( item->ShortcutWidth > info->MaxShortcutWidth )
300                 info->MaxShortcutWidth = item->ShortcutWidth;
301         
302         if( info->MaxLabelWidth + info->MaxShortcutWidth + ciMenu_Gap > info->CachedW )
303         {
304                 info->CachedW = ciMenu_LeftPadding + info->MaxLabelWidth
305                         + ciMenu_Gap + info->MaxShortcutWidth
306                         + ciMenu_RightPadding;
307         }
308         WM_ResizeWindow(Window, info->CachedW, Window->H+ciMenu_ItemHeight);
309         
310         return 0;
311 }
312
313 int Renderer_Menu_int_GetItemByPos(tWindow *Window, tMenuWindowInfo *Info, int X, int Y)
314 {
315          int    i;
316
317         if( X < 0 || X >= Window->W )
318                 return -1;
319         
320         for( i = 0; i < Info->nItems; i ++ )
321         {
322                 if( !Info->Items[i] )   continue;
323                         
324                 if( !Info->Items[i]->Label )
325                 {
326                         // Spacer - not selectable
327                         if(Y < ciMenu_SpacerHeight) {
328                                 return -1;
329                         }
330                         Y -= ciMenu_SpacerHeight;
331                 }
332                 else
333                 {
334                         // Normal item, can be selected/hilighted
335                         if(Y < ciMenu_ItemHeight) {
336                                 return i;
337                         }
338                         Y -= ciMenu_ItemHeight;
339                 }
340         }
341         return -1;
342 }
343
344 int Renderer_Menu_HandleMessage(tWindow *Window, int Msg, int Length, const void *Data)
345 {
346         tMenuWindowInfo *info = Window->RendererInfo;
347         switch(Msg)
348         {
349         case WNDMSG_SHOW: {
350                 const struct sWndMsg_Bool       *msg = Data;
351                 if(Length < sizeof(*msg))       return -1;
352                 if(msg->Val)
353                 {
354 //                      _SysDebug(" - Shown, take focus");
355                         // TODO: This shouldn't really be done, instead focus should be given
356                         //       when the menu is shown.
357 //                      WM_FocusWindow(Window);
358                 }
359                 else
360                 {
361                         // Hide Children
362                         _SysDebug("- Hidden, hide the children!");
363                 }
364                 return 0; }
365         case WNDMSG_FOCUS: {
366                 const struct sWndMsg_Bool       *msg = Data;
367                 if(Length < sizeof(*msg))       return -1;
368                 if(!msg->Val) {
369                         // TODO: Catch if focus was given away to a child
370                         _SysDebug("- Lost focus");
371                         WM_ShowWindow(Window, 0);       // Hide!
372                 }
373                 else {
374                         _SysDebug("- Focus gained, TODO: Show accel keys");
375                 }
376                 return 0; }
377
378         case WNDMSG_MOUSEBTN: {
379                 const struct sWndMsg_MouseButton        *msg = Data;
380                  int    item;
381                 
382                 if(Length < sizeof(*msg))       return -1;
383
384                 if(msg->Button == 0 && msg->bPressed == 0)
385                 {
386                         item = Renderer_Menu_int_GetItemByPos(Window, info, msg->X, msg->Y);
387                         if(item != -1)
388                         {
389                                 tMenuMsg_Select _msg;
390                                 // TODO: Ignore sub-menus too
391                                 _msg.ID = item;
392                                 WM_SendMessage(Window, Window, MSG_MENU_SELECT, sizeof(_msg), &_msg);
393                                 WM_ShowWindow(Window, 0);
394                         }
395                 }
396                                 
397
398                 return 0; }     
399
400         case WNDMSG_MOUSEMOVE: {
401                 const struct sWndMsg_MouseMove  *msg = Data;
402                  int    new_hilight;
403
404                 if(Length < sizeof(*msg))       return -1;
405
406                 new_hilight = Renderer_Menu_int_GetItemByPos(Window, info, msg->X, msg->Y);
407
408                 if( new_hilight != info->HilightedItem )
409                 {
410                         info->HilightedItem = new_hilight;
411                         // TODO: Change sub-menu
412                         WM_Invalidate(Window);
413                 }
414
415                 return 0; }
416
417         // Manipulation messages
418         case MSG_MENU_ADDITEM:
419 //              _SysDebug("MSG_MENU_ADDITEM");
420                 return Renderer_Menu_int_AddItem(Window, Length, Data);
421         
422         // Only message to pass to client
423         case MSG_MENU_SELECT:
424                 return 1;
425         }
426         return 0;
427 }
428

UCC git Repository :: git.ucc.asn.au