Usermode/AxWin3 - Splitting widget types out into separate files
[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,
168                         w, ciMenu_ItemHeight,
169                         gMenu_Font,
170                         cMenu_LabelColour,
171                         item->Label
172                         );
173                 // Underline
174                 if(item->UnderlineW)
175                 {
176                         WM_Render_FillRect(Window,
177                                 ciMenu_LeftPadding + item->UnderlineX, y + 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
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                 item->Label[item->KeyOffset] = '\0';
281                 WM_Render_GetTextDims(NULL, item->Label, &item->UnderlineX, NULL);
282                 // Get the width of the underlined character
283                 // TODO: Fix for high UTF-8 characters
284                 item->Label[item->KeyOffset] = tmp;
285                 tmp = item->Label[item->KeyOffset+1];
286                 item->Label[item->KeyOffset+1] = '\0';
287                 WM_Render_GetTextDims(
288                         NULL, item->Label+item->KeyOffset,
289                         &item->UnderlineW, NULL
290                         );
291                 item->Label[item->KeyOffset+1] = tmp;
292         }
293         // - Labels
294         WM_Render_GetTextDims(NULL, item->Label, &item->LabelWidth, NULL);
295         if(item->Shortcut)
296                 WM_Render_GetTextDims(NULL, item->Shortcut, &item->ShortcutWidth, NULL);
297         else
298                 item->ShortcutWidth = 0;
299         
300         if( item->LabelWidth > info->MaxLabelWidth )
301                 info->MaxLabelWidth = item->LabelWidth;
302         if( item->ShortcutWidth > info->MaxShortcutWidth )
303                 info->MaxShortcutWidth = item->ShortcutWidth;
304         
305         if( info->MaxLabelWidth + info->MaxShortcutWidth + ciMenu_Gap > info->CachedW )
306         {
307                 info->CachedW = ciMenu_LeftPadding + info->MaxLabelWidth
308                         + ciMenu_Gap + info->MaxShortcutWidth
309                         + ciMenu_RightPadding;
310         }
311         WM_ResizeWindow(Window, info->CachedW, Window->H+ciMenu_ItemHeight);
312         
313         return 0;
314 }
315
316 int Renderer_Menu_int_GetItemByPos(tWindow *Window, tMenuWindowInfo *Info, int X, int Y)
317 {
318          int    i;
319
320         if( X < 0 || X >= Window->W )
321                 return -1;
322         
323         for( i = 0; i < Info->nItems; i ++ )
324         {
325                 if( !Info->Items[i] )   continue;
326                         
327                 if( !Info->Items[i]->Label )
328                 {
329                         // Spacer - not selectable
330                         if(Y < ciMenu_SpacerHeight) {
331                                 return -1;
332                         }
333                         Y -= ciMenu_SpacerHeight;
334                 }
335                 else
336                 {
337                         // Normal item, can be selected/hilighted
338                         if(Y < ciMenu_ItemHeight) {
339                                 return i;
340                         }
341                         Y -= ciMenu_ItemHeight;
342                 }
343         }
344         return -1;
345 }
346
347 int Renderer_Menu_HandleMessage(tWindow *Window, int Msg, int Length, const void *Data)
348 {
349         tMenuWindowInfo *info = Window->RendererInfo;
350         switch(Msg)
351         {
352         case WNDMSG_SHOW: {
353                 const struct sWndMsg_Bool       *msg = Data;
354                 if(Length < sizeof(*msg))       return -1;
355                 if(msg->Val)
356                 {
357 //                      _SysDebug(" - Shown, take focus");
358                         // TODO: This shouldn't really be done, instead focus should be given
359                         //       when the menu is shown.
360 //                      WM_FocusWindow(Window);
361                 }
362                 else
363                 {
364                         // Hide Children
365                         _SysDebug("- Hidden, hide the children!");
366                 }
367                 return 0; }
368         case WNDMSG_FOCUS: {
369                 const struct sWndMsg_Bool       *msg = Data;
370                 if(Length < sizeof(*msg))       return -1;
371                 if(!msg->Val) {
372                         // TODO: Catch if focus was given away to a child
373                         _SysDebug("- Lost focus");
374                         WM_ShowWindow(Window, 0);       // Hide!
375                 }
376                 else {
377                         _SysDebug("- Focus gained, TODO: Show accel keys");
378                 }
379                 return 0; }
380
381         case WNDMSG_MOUSEBTN: {
382                 const struct sWndMsg_MouseButton        *msg = Data;
383                  int    item;
384                 
385                 if(Length < sizeof(*msg))       return -1;
386
387                 if(msg->Button == 0 && msg->bPressed == 0)
388                 {
389                         item = Renderer_Menu_int_GetItemByPos(Window, info, msg->X, msg->Y);
390                         if(item != -1)
391                         {
392                                 tMenuMsg_Select _msg;
393                                 // TODO: Ignore sub-menus too
394                                 _msg.ID = item;
395                                 WM_SendMessage(Window, Window, MSG_MENU_SELECT, sizeof(_msg), &_msg);
396                                 WM_ShowWindow(Window, 0);
397                         }
398                 }
399                                 
400
401                 return 0; }     
402
403         case WNDMSG_MOUSEMOVE: {
404                 const struct sWndMsg_MouseMove  *msg = Data;
405                  int    new_hilight;
406
407                 if(Length < sizeof(*msg))       return -1;
408
409                 new_hilight = Renderer_Menu_int_GetItemByPos(Window, info, msg->X, msg->Y);
410
411                 if( new_hilight != info->HilightedItem )
412                 {
413                         info->HilightedItem = new_hilight;
414                         // TODO: Change sub-menu
415                         WM_Invalidate(Window);
416                 }
417
418                 return 0; }
419
420         // Manipulation messages
421         case MSG_MENU_ADDITEM:
422 //              _SysDebug("MSG_MENU_ADDITEM");
423                 return Renderer_Menu_int_AddItem(Window, Length, Data);
424         
425         // Only message to pass to client
426         case MSG_MENU_SELECT:
427                 return 1;
428         }
429         return 0;
430 }
431

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