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

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