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

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