Usermode/AxWin3 - Implemented Focus/Raising support
[tpg/acess2.git] / Usermode / Applications / axwin3_src / WM / renderer_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, 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         _SysDebug("Renderer_Menu_Create: ->MaxItems = %i", info->MaxItems);
97         
98         return ret;
99 }
100
101 void Renderer_Menu_Redraw(tWindow *Window)
102 {
103         tMenuWindowInfo *info = Window->RendererInfo;
104          int    w, h, y, i;
105
106         w = info->CachedW;
107         h = ciMenu_TopPadding + ciMenu_BottomPadding;
108         for( i = 0; i < info->nItems; i ++ )
109         {
110                 if( !info->Items[i] )   continue;
111                 
112                 if(info->Items[i]->Label)
113                         h += ciMenu_ItemHeight;
114                 else
115                         h += ciMenu_SpacerHeight;
116         }
117
118 //      _SysDebug("w = %i, h = %i", w, h);
119
120         // - Resize window to contain all items
121         WM_ResizeWindow(Window, w, h);
122
123         // - Move the window such that it is on screen
124         //  > Make sure to catch if the menu can't fit fully onscreen
125
126         // - Clear
127         WM_Render_FillRect(Window, 0, 0, w, h, cMenu_BackgroundColour);
128         WM_Render_DrawRect(Window, 0, 0, w, h, cMenu_BorderColour);
129
130         // - Render each item
131         y = ciMenu_TopPadding;
132         for( i = 0; i < info->nItems; i ++ )
133         {
134                 tMenuItem       *item = info->Items[i];
135                 
136                 // Unused slot
137                 if(!item)       continue;
138                 
139                 // Spacer
140                 if(!item->Label)
141                 {
142                         WM_Render_FillRect(Window,
143                                 1, y + ciMenu_SpacerHeight/2,
144                                 w-2, 1,
145                                 cMenu_SpacerColour
146                                 );
147                         y += ciMenu_SpacerHeight;
148                         continue ;
149                 }
150         
151                 // Hilight
152                 if( info->HilightedItem == i )
153                 {
154                         WM_Render_FillRect(Window,
155                                 1, y,
156                                 w-2, ciMenu_ItemHeight,
157                                 cMenu_HilightColour
158                                 );
159                 }
160         
161                 // Text
162                 WM_Render_DrawText(Window,
163                         ciMenu_LeftPadding, y,
164                         w, ciMenu_ItemHeight,
165                         gMenu_Font,
166                         cMenu_LabelColour,
167                         item->Label
168                         );
169                 // Underline
170                 if(item->UnderlineW)
171                 {
172                         WM_Render_FillRect(Window,
173                                 ciMenu_LeftPadding + item->UnderlineX, y + ciMenu_FontHeight + 1,
174                                 item->UnderlineW, 1,
175                                 cMenu_LabelColour
176                                 );
177                 }
178                 
179                 // Shortcut key
180                 if(item->Shortcut)
181                 {
182                         WM_Render_DrawText(Window,
183                                 w - item->ShortcutWidth - ciMenu_RightPadding, y,
184                                 w, ciMenu_ItemHeight,
185                                 gMenu_Font,
186                                 cMenu_ShortcutColour,
187                                 item->Shortcut
188                                 );
189                 }
190                 
191                 y += ciMenu_ItemHeight;
192         }
193 }
194
195 int Renderer_Menu_int_AddItem(tWindow *Window, int Length, void *Data)
196 {
197         tMenuWindowInfo *info = Window->RendererInfo;
198         tMenuMsg_AddItem        *req = Data;
199         tMenuItem       *item;
200         
201         // Sanity checking
202         // - Message length
203         if(Length < sizeof(*req) + 1 || req->Label[Length-sizeof(*req)-1] != '\0') {
204                 _SysDebug("Renderer_Menu_int_AddItem: Size checks failed");
205                 return -1;
206         }
207         // - ID Number
208         if(req->ID >= info->MaxItems) {
209                 _SysDebug("Renderer_Menu_int_AddItem: ID (%i) >= MaxItems (%i)",
210                         req->ID, info->MaxItems);
211                 return -1;
212         }
213         
214         // Don't overwrite
215         if(info->Items[req->ID])        return 0;
216         // Bookkeeping
217         if(req->ID >= info->nItems)     info->nItems = req->ID + 1;
218         // Allocate
219         item = malloc(sizeof(tMenuItem)+strlen(req->Label));
220         info->Items[req->ID] = item;
221         
222         if(req->Label[0] == '\0')
223         {
224                 // Spacer
225                 item->Label = NULL;
226                 
227                 return 0;
228         }
229         
230         // Actual item
231         char    *dest = item->Data;
232         char    *src = req->Label;
233          int    ofs = 0;
234
235         // - Main label
236         item->KeyOffset = -1;
237         item->Label = dest;
238         for(ofs = 0; *src && *src != '\t'; ofs ++)
239         {
240                 if(*src == '&') {
241                         *dest = '\0';
242                         item->KeyOffset = ofs;
243                         src ++;
244                 }
245                 else {
246                         *dest++ = *src++;
247                 }
248         }
249         *dest++ = '\0';
250         // - Key combo / Shortcut
251         if(*src)
252         {
253                 src ++;
254                 item->Shortcut = dest;
255                 strcpy(item->Shortcut, src);
256         }
257         else
258         {
259                 item->Shortcut = NULL;
260         }
261         
262         // Get dimensions
263         // - Underline (hotkey)
264         if(item->KeyOffset == -1)
265         {
266                 item->UnderlineX = 0;
267                 item->UnderlineW = 0;
268         }
269         else
270         {
271                 char    tmp = item->Label[item->KeyOffset];
272                 // Get width of preceding substring
273                 item->Label[item->KeyOffset] = '\0';
274                 WM_Render_GetTextDims(NULL, item->Label, &item->UnderlineX, NULL);
275                 // Get the width of the underlined character
276                 // TODO: Fix for high UTF-8 characters
277                 item->Label[item->KeyOffset] = tmp;
278                 tmp = item->Label[item->KeyOffset+1];
279                 item->Label[item->KeyOffset+1] = '\0';
280                 WM_Render_GetTextDims(
281                         NULL, item->Label+item->KeyOffset,
282                         &item->UnderlineW, NULL
283                         );
284                 item->Label[item->KeyOffset+1] = tmp;
285         }
286         // - Labels
287         WM_Render_GetTextDims(NULL, item->Label, &item->LabelWidth, NULL);
288         if(item->Shortcut)
289                 WM_Render_GetTextDims(NULL, item->Shortcut, &item->ShortcutWidth, NULL);
290         else
291                 item->ShortcutWidth = 0;
292         
293         if( item->LabelWidth > info->MaxLabelWidth )
294                 info->MaxLabelWidth = item->LabelWidth;
295         if( item->ShortcutWidth > info->MaxShortcutWidth )
296                 info->MaxShortcutWidth = item->ShortcutWidth;
297         
298         if( info->MaxLabelWidth + info->MaxShortcutWidth + ciMenu_Gap > info->CachedW )
299         {
300                 info->CachedW = ciMenu_LeftPadding + info->MaxLabelWidth
301                         + ciMenu_Gap + info->MaxShortcutWidth
302                         + ciMenu_RightPadding;
303                 // TODO: Smarter height?
304                 WM_ResizeWindow(Window, info->CachedW, info->nItems*ciMenu_ItemHeight);
305         }
306         
307         return 0;
308 }
309
310 int Renderer_Menu_int_GetItemByPos(tWindow *Window, tMenuWindowInfo *Info, int X, int Y)
311 {
312          int    i;
313
314         if( X < 0 || X >= Window->W )
315                 return -1;
316         
317         for( i = 0; i < Info->nItems; i ++ )
318         {
319                 if( !Info->Items[i] )   continue;
320                         
321                 if( !Info->Items[i]->Label )
322                 {
323                         // Spacer - doesn't hilight
324                         if(Y < ciMenu_SpacerHeight) {
325                                 return -1;
326                         }
327                         Y -= ciMenu_SpacerHeight;
328                 }
329                 else
330                 {
331                         // Normal item, set the hilight
332                         if(Y < ciMenu_ItemHeight) {
333                                 return i;
334                         }
335                         Y -= ciMenu_ItemHeight;
336                 }
337         }
338         return -1;
339 }
340
341 int Renderer_Menu_HandleMessage(tWindow *Window, int Msg, int Length, void *Data)
342 {
343         tMenuWindowInfo *info = Window->RendererInfo;
344         switch(Msg)
345         {
346         case WNDMSG_SHOW: {
347                 struct sWndMsg_Bool     *msg = Data;
348                 if(Length < sizeof(*msg))       return -1;
349                 if(msg->Val) {
350                         // Take focus?
351                         _SysDebug(" - Shown, take focus");
352                         WM_GiveFocus(Window);
353                 }
354                 else
355                 {
356                         // Hide Children
357                         _SysDebug("- Hidden, hide the children!");
358                 }
359                 return 0; }
360         case WNDMSG_FOCUS: {
361                 struct sWndMsg_Bool     *msg = Data;
362                 if(Length < sizeof(*msg))       return -1;
363                 if(!msg->Val) {
364                         // TODO: Catch if focus was given away to a child
365                         _SysDebug("- Lost focus");
366                         WM_ShowWindow(Window, 0);       // Hide!
367                 }
368                 else {
369                         _SysDebug("- Focus gained, TODO: Show accel keys");
370                 }
371                 return 0; }
372
373         case WNDMSG_MOUSEBTN: {
374                 struct sWndMsg_MouseButton      *msg = Data;
375                  int    item;
376                 
377                 if(Length < sizeof(*msg))       return -1;
378
379                 if(msg->Button == 0 && msg->bPressed == 0)
380                 {
381                         item = Renderer_Menu_int_GetItemByPos(Window, info, msg->X, msg->Y);
382                         if(item != -1)
383                         {
384                                 tMenuMsg_Select _msg;
385                                 // TODO: Ignore sub-menus too
386                                 _msg.ID = item;
387                                 WM_SendMessage(Window, Window, MSG_MENU_SELECT, sizeof(_msg), &_msg);
388                                 WM_ShowWindow(Window, 0);
389                         }
390                 }
391                                 
392
393                 return 0; }     
394
395         case WNDMSG_MOUSEMOVE: {
396                 struct sWndMsg_MouseMove        *msg = Data;
397                  int    new_hilight;
398
399                 if(Length < sizeof(*msg))       return -1;
400
401                 new_hilight = Renderer_Menu_int_GetItemByPos(Window, info, msg->X, msg->Y);
402
403                 if( new_hilight != info->HilightedItem )
404                 {
405                         info->HilightedItem = new_hilight;
406                         // TODO: Change sub-menu
407                         WM_Invalidate(Window);
408                 }
409
410                 return 0; }
411
412         // Manipulation messages
413         case MSG_MENU_ADDITEM:
414                 _SysDebug("MSG_MENU_ADDITEM");
415                 return Renderer_Menu_int_AddItem(Window, Length, Data);
416         
417         // Only message to pass to client
418         case MSG_MENU_SELECT:
419                 return 1;
420         }
421         return 0;
422 }
423

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