1/////////////////////////////////////////////////////////////////////////////
2// Name:        src/palmos/menu.cpp
3// Purpose:     wxMenu, wxMenuBar, wxMenuItem
4// Author:      William Osborne - minimal working wxPalmOS port
5// Modified by:
6// Created:     10/12/04
7// RCS-ID:      $Id: menu.cpp 48053 2007-08-13 17:07:01Z JS $
8// Copyright:   (c) William Osborne
9// Licence:     wxWindows licence
10/////////////////////////////////////////////////////////////////////////////
11
12// ===========================================================================
13// declarations
14// ===========================================================================
15
16// ---------------------------------------------------------------------------
17// headers
18// ---------------------------------------------------------------------------
19
20// For compilers that support precompilation, includes "wx.h".
21#include "wx/wxprec.h"
22
23#ifdef __BORLANDC__
24    #pragma hdrstop
25#endif
26
27#if wxUSE_MENUS
28
29#include "wx/menu.h"
30
31#ifndef WX_PRECOMP
32    #include "wx/frame.h"
33    #include "wx/utils.h"
34    #include "wx/intl.h"
35    #include "wx/log.h"
36#endif
37
38#if wxUSE_OWNER_DRAWN
39    #include "wx/ownerdrw.h"
40#endif
41
42#include <Loader.h>
43#include <Form.h>
44#include <Menu.h>
45
46// ----------------------------------------------------------------------------
47// global variables
48// ----------------------------------------------------------------------------
49
50extern wxMenu *wxCurrentPopupMenu;
51
52// ----------------------------------------------------------------------------
53// constants
54// ----------------------------------------------------------------------------
55
56// the (popup) menu title has this special id
57static const int idMenuTitle = -3;
58
59// ----------------------------------------------------------------------------
60// private functions
61// ----------------------------------------------------------------------------
62
63// ============================================================================
64// implementation
65// ============================================================================
66
67#include "wx/listimpl.cpp"
68
69WX_DEFINE_LIST( wxMenuInfoList )
70
71#if wxUSE_EXTENDED_RTTI
72
73WX_DEFINE_FLAGS( wxMenuStyle )
74
75wxBEGIN_FLAGS( wxMenuStyle )
76    wxFLAGS_MEMBER(wxMENU_TEAROFF)
77wxEND_FLAGS( wxMenuStyle )
78
79IMPLEMENT_DYNAMIC_CLASS_XTI(wxMenu, wxEvtHandler,"wx/menu.h")
80
81wxCOLLECTION_TYPE_INFO( wxMenuItem * , wxMenuItemList ) ;
82
83template<> void wxCollectionToVariantArray( wxMenuItemList const &theList, wxxVariantArray &value)
84{
85    wxListCollectionToVariantArray<wxMenuItemList::compatibility_iterator>( theList , value ) ;
86}
87
88wxBEGIN_PROPERTIES_TABLE(wxMenu)
89    wxEVENT_PROPERTY( Select , wxEVT_COMMAND_MENU_SELECTED , wxCommandEvent)
90    wxPROPERTY( Title, wxString , SetTitle, GetTitle, wxString(), 0 /*flags*/ , wxT("Helpstring") , wxT("group") )
91    wxREADONLY_PROPERTY_FLAGS( MenuStyle , wxMenuStyle , long , GetStyle , EMPTY_MACROVALUE , 0 /*flags*/ , wxT("Helpstring") , wxT("group")) // style
92    wxPROPERTY_COLLECTION( MenuItems , wxMenuItemList , wxMenuItem* , Append , GetMenuItems , 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
93wxEND_PROPERTIES_TABLE()
94
95wxBEGIN_HANDLERS_TABLE(wxMenu)
96wxEND_HANDLERS_TABLE()
97
98wxDIRECT_CONSTRUCTOR_2( wxMenu , wxString , Title , long , MenuStyle  )
99
100WX_DEFINE_FLAGS( wxMenuBarStyle )
101
102wxBEGIN_FLAGS( wxMenuBarStyle )
103    wxFLAGS_MEMBER(wxMB_DOCKABLE)
104wxEND_FLAGS( wxMenuBarStyle )
105
106// the negative id would lead the window (its superclass !) to vetoe streaming out otherwise
107bool wxMenuBarStreamingCallback( const wxObject *WXUNUSED(object), wxWriter * , wxPersister * , wxxVariantArray & )
108{
109    return true ;
110}
111
112IMPLEMENT_DYNAMIC_CLASS_XTI_CALLBACK(wxMenuBar, wxWindow ,"wx/menu.h",wxMenuBarStreamingCallback)
113
114IMPLEMENT_DYNAMIC_CLASS_XTI(wxMenuInfo, wxObject , "wx/menu.h" )
115
116wxBEGIN_PROPERTIES_TABLE(wxMenuInfo)
117    wxREADONLY_PROPERTY( Menu , wxMenu* , GetMenu , EMPTY_MACROVALUE , 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
118    wxREADONLY_PROPERTY( Title , wxString , GetTitle , wxString() , 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
119wxEND_PROPERTIES_TABLE()
120
121wxBEGIN_HANDLERS_TABLE(wxMenuInfo)
122wxEND_HANDLERS_TABLE()
123
124wxCONSTRUCTOR_2( wxMenuInfo , wxMenu* , Menu , wxString , Title )
125
126wxCOLLECTION_TYPE_INFO( wxMenuInfo * , wxMenuInfoList ) ;
127
128template<> void wxCollectionToVariantArray( wxMenuInfoList const &theList, wxxVariantArray &value)
129{
130    wxListCollectionToVariantArray<wxMenuInfoList::compatibility_iterator>( theList , value ) ;
131}
132
133wxBEGIN_PROPERTIES_TABLE(wxMenuBar)
134    wxPROPERTY_COLLECTION( MenuInfos , wxMenuInfoList , wxMenuInfo* , Append , GetMenuInfos , 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
135wxEND_PROPERTIES_TABLE()
136
137wxBEGIN_HANDLERS_TABLE(wxMenuBar)
138wxEND_HANDLERS_TABLE()
139
140wxCONSTRUCTOR_DUMMY( wxMenuBar )
141
142#else
143IMPLEMENT_DYNAMIC_CLASS(wxMenu, wxEvtHandler)
144IMPLEMENT_DYNAMIC_CLASS(wxMenuBar, wxWindow)
145IMPLEMENT_DYNAMIC_CLASS(wxMenuInfo, wxObject)
146#endif
147
148const wxMenuInfoList& wxMenuBar::GetMenuInfos() const
149{
150    wxMenuInfoList* list = const_cast< wxMenuInfoList* >( &m_menuInfos ) ;
151    WX_CLEAR_LIST( wxMenuInfoList , *list ) ;
152    for( size_t i = 0 ; i < GetMenuCount() ; ++i )
153    {
154        wxMenuInfo* info = new wxMenuInfo() ;
155        info->Create( const_cast<wxMenuBar*>(this)->GetMenu(i) , GetLabelTop(i) ) ;
156        list->Append( info ) ;
157    }
158    return m_menuInfos ;
159}
160
161// ---------------------------------------------------------------------------
162// wxMenu construction, adding and removing menu items
163// ---------------------------------------------------------------------------
164
165// Construct a menu with optional title (then use append)
166void wxMenu::Init()
167{
168}
169
170// The wxWindow destructor will take care of deleting the submenus.
171wxMenu::~wxMenu()
172{
173}
174
175void wxMenu::Break()
176{
177}
178
179void wxMenu::Attach(wxMenuBarBase *menubar)
180{
181    wxMenuBase::Attach(menubar);
182}
183
184#if wxUSE_ACCEL
185
186int wxMenu::FindAccel(int id) const
187{
188    return wxNOT_FOUND;
189}
190
191void wxMenu::UpdateAccel(wxMenuItem *item)
192{
193}
194
195#endif // wxUSE_ACCEL
196
197// append a new item or submenu to the menu
198bool wxMenu::DoInsertOrAppend(wxMenuItem *pItem, size_t pos)
199{
200    if ( IsAttached() && GetMenuBar()->IsAttached() )
201    {
202        // Regenerate the menu resource
203        GetMenuBar()->Refresh();
204    }
205
206    return true;
207}
208
209void wxMenu::EndRadioGroup()
210{
211}
212
213wxMenuItem* wxMenu::DoAppend(wxMenuItem *item)
214{
215    wxCHECK_MSG( item, NULL, _T("NULL item in wxMenu::DoAppend") );
216
217    if(!wxMenuBase::DoAppend(item) || !DoInsertOrAppend(item))
218    {
219        return NULL;
220    }
221    else if(IsAttached() && GetMenuBar()->IsAttached())
222    {
223        // Regenerate the menu resource
224        GetMenuBar()->Refresh();
225    }
226
227    return item;
228}
229
230wxMenuItem* wxMenu::DoInsert(size_t pos, wxMenuItem *item)
231{
232    if (wxMenuBase::DoInsert(pos, item) && DoInsertOrAppend(item, pos))
233        return item;
234    else
235        return NULL;
236}
237
238wxMenuItem *wxMenu::DoRemove(wxMenuItem *item)
239{
240    // we need to find the items position in the child list
241    size_t pos;
242    wxMenuItemList::compatibility_iterator node = GetMenuItems().GetFirst();
243    for ( pos = 0; node; pos++ )
244    {
245        if ( node->GetData() == item )
246            break;
247
248        node = node->GetNext();
249    }
250
251    // DoRemove() (unlike Remove) can only be called for existing item!
252    wxCHECK_MSG( node, NULL, wxT("bug in wxMenu::Remove logic") );
253
254    // remove the item from the menu
255    wxMenuItem *ret=wxMenuBase::DoRemove(item);
256
257    if ( IsAttached() && GetMenuBar()->IsAttached() )
258    {
259        // Regenerate the menu resource
260        GetMenuBar()->Refresh();
261    }
262
263    return ret;
264}
265
266// ---------------------------------------------------------------------------
267// accelerator helpers
268// ---------------------------------------------------------------------------
269
270#if wxUSE_ACCEL
271
272// create the wxAcceleratorEntries for our accels and put them into provided
273// array - return the number of accels we have
274size_t wxMenu::CopyAccels(wxAcceleratorEntry *accels) const
275{
276    size_t count = GetAccelCount();
277    for ( size_t n = 0; n < count; n++ )
278    {
279        *accels++ = *m_accels[n];
280    }
281
282    return count;
283}
284
285#endif // wxUSE_ACCEL
286
287// ---------------------------------------------------------------------------
288// set wxMenu title
289// ---------------------------------------------------------------------------
290
291void wxMenu::SetTitle(const wxString& label)
292{
293    m_title = label;
294
295    if ( IsAttached() && GetMenuBar()->IsAttached() )
296    {
297        // Regenerate the menu resource
298        GetMenuBar()->Refresh();
299    }
300}
301
302// ---------------------------------------------------------------------------
303// event processing
304// ---------------------------------------------------------------------------
305
306bool wxMenu::PalmCommand(WXUINT WXUNUSED(param), WXWORD id)
307{
308    return false;
309}
310
311// ---------------------------------------------------------------------------
312// other
313// ---------------------------------------------------------------------------
314
315wxWindow *wxMenu::GetWindow() const
316{
317    return NULL;
318}
319
320// ---------------------------------------------------------------------------
321// Menu Bar
322// ---------------------------------------------------------------------------
323
324void wxMenuBar::Init()
325{
326}
327
328wxMenuBar::wxMenuBar()
329{
330}
331
332wxMenuBar::wxMenuBar( long WXUNUSED(style) )
333{
334}
335
336wxMenuBar::wxMenuBar(size_t count, wxMenu *menus[], const wxString titles[], long WXUNUSED(style))
337{
338}
339
340wxMenuBar::~wxMenuBar()
341{
342}
343
344// ---------------------------------------------------------------------------
345// wxMenuBar helpers
346// ---------------------------------------------------------------------------
347
348void wxMenuBar::Refresh()
349{
350    wxCHECK_RET( IsAttached(), wxT("can't refresh unattached menubar") );
351
352       // Regenerate the menu resource
353    LoadMenu();
354}
355
356WXHMENU wxMenuBar::Create()
357{
358    return NULL;
359}
360
361int wxMenuBar::PalmPositionForWxMenu(wxMenu *menu, int wxpos)
362{
363    return -1;
364}
365
366// ---------------------------------------------------------------------------
367// wxMenuBar functions to work with the top level submenus
368// ---------------------------------------------------------------------------
369
370void wxMenuBar::EnableTop(size_t pos, bool enable)
371{
372    // Palm OS does not have support for grayed or disabled items
373}
374
375void wxMenuBar::SetLabelTop(size_t pos, const wxString& label)
376{
377    wxCHECK_RET( pos < GetMenuCount(), wxT("invalid menu index") );
378
379    m_titles[pos]=wxStripMenuCodes(label);
380
381    if ( !IsAttached() )
382    {
383        return;
384    }
385
386       // Regenerate the menu resource
387    Refresh();
388}
389
390wxString wxMenuBar::GetLabelTop(size_t pos) const
391{
392    wxCHECK_MSG( pos < GetMenuCount(), wxEmptyString,
393                 wxT("invalid menu index in wxMenuBar::GetLabelTop") );
394
395    return wxMenuItem::GetLabelFromText(m_titles[pos]);
396}
397
398// Gets the original label at the top-level of the menubar
399wxString wxMenuBar::GetMenuLabel(size_t pos) const
400{
401    wxCHECK_MSG( pos < GetMenuCount(), wxEmptyString,
402                 wxT("invalid menu index in wxMenuBar::GetMenuLabel") );
403
404    return m_titles[pos];
405}
406
407// ---------------------------------------------------------------------------
408// wxMenuBar construction
409// ---------------------------------------------------------------------------
410
411wxMenu *wxMenuBar::Replace(size_t pos, wxMenu *menu, const wxString& title)
412{
413   wxMenu *menuOld = wxMenuBarBase::Replace(pos, menu, title);
414    if ( !menuOld )
415        return NULL;
416
417    m_titles[pos]=wxStripMenuCodes(title);
418
419    if ( IsAttached() )
420    {
421        // Regenerate the menu resource
422        Refresh();
423    }
424
425    return menuOld;
426}
427
428bool wxMenuBar::Insert(size_t pos, wxMenu *menu, const wxString& title)
429{
430    if ( !wxMenuBarBase::Insert(pos, menu, title) )
431        return false;
432
433    m_titles.Insert(wxStripMenuCodes(title), pos);
434
435    if ( IsAttached() )
436    {
437        // Regenerate the menu resource
438        Refresh();
439    }
440
441    return true;
442}
443
444bool wxMenuBar::Append(wxMenu *menu, const wxString& title)
445{
446    if ( !wxMenuBarBase::Append(menu, title) )
447        return false;
448
449    m_titles.Add(wxStripMenuCodes(title));
450
451    if(IsAttached())
452    {
453        // Regenerate the menu resource
454        Refresh();
455    }
456
457    return true;
458}
459
460wxMenu *wxMenuBar::Remove(size_t pos)
461{
462    wxMenu *menu = wxMenuBarBase::Remove(pos);
463    if ( !menu )
464        return NULL;
465
466    m_titles.RemoveAt(pos);
467
468    if (IsAttached())
469    {
470        // Regenerate the menu resource
471        Refresh();
472    }
473
474    return menu;
475}
476
477#if wxUSE_ACCEL
478
479void wxMenuBar::RebuildAccelTable()
480{
481}
482
483#endif // wxUSE_ACCEL
484
485int wxMenuBar::ProcessCommand(int ItemID)
486{
487    if(!IsAttached())
488        return -1;
489
490    int MenuNum=(ItemID/1000)-1;
491    int ItemNum=(ItemID-(1000*(MenuNum+1)));
492
493    // Should never happen, but it doesn't hurt to check anyway.
494    if(MenuNum>GetMenuCount())
495        return -1;
496
497    // Get the menu
498    wxMenu *ActiveMenu=GetMenu(MenuNum);
499
500    // Make sure this is a valid item.
501    if(ItemNum>ActiveMenu->GetMenuItemCount())
502        return -1;
503
504    // Get the item
505    wxMenuItem *ActiveItem=ActiveMenu->FindItemByPosition(ItemNum);
506    int ActiveID=ActiveItem->GetId();
507
508    return ActiveID;
509}
510
511/* Palm OS does not have good dynamic menu support.  About all you can do with
512 * the standard API calls is to add new items to an existing drop-down menu and
513 * hide/show items in a drop-down menu.  It is impossible to add, hide, or
514 * change the label on a drop-down menu.
515 *
516 * The easiest and simplest way around this limitation is to modify the Palm OS
517 * MenuBarType structure directly.  This gives limited ability to change the
518 * label on a drop-down menu.  I have not been able to find a safe way to add,
519 * delete, or resize drop-down menus in OS 6.
520 *
521 * The following routine attempt to work around these limitations present in the
522 * Palm OS API to provide limited dynamic menu support.  This solution is far
523 * from perfect, but the only other option is to wait for PalmSource to add full
524 * dynamic menu support, or to recreate the Palm OS menu system from scratch.
525 *
526 * This system is limited in that no more than 4 drop-down menus are allowed per
527 * menu bar, and the label for each drop-down menu is limited to 8 characters of
528 * text.  However, this menu system should work for most applications.
529 *
530 * Basically the menu routines select one of four menu bars, depending on
531 * whether or not the requested menu bar has one, two, three, or four drop-down
532 * menus.
533 *
534 * These four "template" menu bars contain one, two, three, or four drop-down
535 * menus.  Each menu has a dummy menu item attached to it to allow the Palm OS
536 * MenuAddItem function to add the real items.
537 *
538 * The labels on the drop-down menus are then replaced with the labels of the
539 * real menus.
540 *
541 * The menu is then attached to the active window and the MenuAddItem API
542 * function is called to add the items to each drop-down menu.  Finally,
543 * MenuHideItem is called to remove the dummy items from each drop-down menu.
544 */
545void wxMenuBar::LoadMenu()
546{
547    int i=0;
548    int j=0;
549
550    // Handle to the currently running application database
551    DmOpenRef    AppDB;
552
553    // Get app database reference - needed for some Palm OS Menu API calls.
554    SysGetModuleDatabase(SysGetRefNum(), NULL, &AppDB);
555
556    // Get the number of menus
557    int NumMenus=GetMenuCount();
558
559    // Set up the pointers and handles
560    char *PalmOSMenuBarPtr;
561    MemHandle PalmOSMenuBar;
562
563    // Load the menu template and set up the menu pointers
564    if(NumMenus==1)
565    {
566        PalmOSMenuBar=DmGetResource(AppDB,'MBAR',1000);
567        PalmOSMenuBarPtr=(char *)MemHandleLock(PalmOSMenuBar);
568
569        PalmOSMenuBarPtr+=74;
570    }
571    else if(NumMenus==2)
572    {
573        PalmOSMenuBar=DmGetResource(AppDB,'MBAR',2000);
574        PalmOSMenuBarPtr=(char *)MemHandleLock(PalmOSMenuBar);
575
576        PalmOSMenuBarPtr+=116;
577    }
578    else if(NumMenus==3)
579    {
580        PalmOSMenuBar=DmGetResource(AppDB,'MBAR',3000);
581        PalmOSMenuBarPtr=(char *)MemHandleLock(PalmOSMenuBar);
582
583        PalmOSMenuBarPtr+=158;
584    }
585    else
586    {
587        // We support a maximum of 4 menus, so make sure that do not create
588        // more than we can handle.
589        NumMenus=4;
590
591        PalmOSMenuBar=DmGetResource(AppDB,'MBAR',4000);
592        PalmOSMenuBarPtr=(char *)MemHandleLock(PalmOSMenuBar);
593
594        PalmOSMenuBarPtr+=200;
595    }
596
597    // Set the proper names for the drop-down triggers.
598    for(i=0;i<NumMenus;i++)
599    {
600        // Clear out the old label
601        char buffer[8]={' ',' ',' ',' ',' ',' ',' ',' '};
602        MemMove(PalmOSMenuBarPtr,buffer,8);
603
604        wxString MenuTitle=m_titles.Item(i);
605
606        // Make sure we don't copy more than 8 bytes for the label
607        int LengthToCopy=MenuTitle.length();
608        if(LengthToCopy>8)
609            LengthToCopy=8;
610
611        MemMove(PalmOSMenuBarPtr,MenuTitle,LengthToCopy);
612        PalmOSMenuBarPtr+=11;
613    }
614
615    // We are done with the menu pointer.
616    MemHandleUnlock(PalmOSMenuBar);
617    DmReleaseResource(PalmOSMenuBar);
618
619    // We must make the menu active before we can add items to the drop-down
620    // triggers.
621    FrmSetMenu(FrmGetActiveForm(),AppDB,NumMenus*1000);
622
623    /* Add the menu items to the drop-down triggers.  This must be done after
624     * setting the triggers, because setting the names of drop-down triggers
625     * that have a variable number of items requires carefull calculation of
626     * the offsets in the MenuBarType structure.  Setting the triggers first
627     * avoids this.
628     */
629    for(i=0;i<NumMenus;i++)
630    {
631        wxMenu *CurrentMenu=GetMenu(i);
632
633        for(j=0;j<CurrentMenu->GetMenuItemCount();j++)
634        {
635            wxMenuItem *CurrentItem=CurrentMenu->FindItemByPosition(j);
636            wxString ItemLabel=CurrentItem->GetLabel();
637
638            if(CurrentItem->IsSeparator()==true)
639            {
640                char Separator=MenuSeparatorChar;
641                if(j==0)
642                    MenuAddItem(9000+i,((i*1000)+1000)+j,0x00,&Separator);
643                else
644                    MenuAddItem(((i*1000)+1000)+j-1,((i*1000)+1000)+j,0x00,&Separator);
645            }
646            else
647            {
648                if(j==0)
649                    MenuAddItem(9000+i,((i*1000)+1000)+j,0x00,ItemLabel);
650                else
651                    MenuAddItem(((i*1000)+1000)+j-1,((i*1000)+1000)+j,0x00,ItemLabel);
652            }
653        }
654
655        // Hide the dummy menu item, since we don't need it anymore.
656        MenuHideItem(9000+i);
657    }
658}
659
660void wxMenuBar::Attach(wxFrame *frame)
661{
662    // before attaching preprocess menus to not include wxID_EXIT item
663    // as PalmOS guidelines suggest
664
665    wxMenuItem *item;
666    wxMenu *menu;
667    int i;
668
669    while( item = FindItem(wxID_EXIT) )
670    {
671        menu = item->GetMenu();
672        if( !menu ) break; // something broken ?
673
674        size_t count = menu->GetMenuItemCount();
675        if( count == 0 ) break; // something broken ?
676
677        // if EXIT is last item in menu
678        if( menu->FindItemByPosition( count - 1 ) == item )
679        {
680            menu->Destroy( item );
681
682            // was more than one item?
683            // was previous separator ?
684            if( count > 2 )
685            {
686                item = menu->FindItemByPosition( count - 2 );
687                if(item && item->IsSeparator())
688                    menu->Destroy( item );
689            }
690        }
691
692        // if EXIT is first item in menu
693        else if( menu->FindItemByPosition( 0 ) == item )
694        {
695            menu->Destroy( item );
696
697            // was more than one item?
698            // was previous separator ?
699            if( count > 2 )
700            {
701                item = menu->FindItemByPosition( 0 );
702                if(item && item->IsSeparator())
703                    menu->Destroy( item );
704            }
705        }
706
707        // if EXIT is in the middle but before and after are selectors
708        else
709        {
710            i = 1; // 0 case already done
711            while ( (i < count) && (menu->FindItemByPosition( 0 ) != item) )
712            {
713                i++;
714            }
715
716            if (i >= count) break;
717            if (menu->FindItemByPosition( i ) != item) break;
718            menu->Destroy( item );
719            item = menu->FindItemByPosition( i );
720            if ( item &&
721                 item->IsSeparator() &&
722                 menu->FindItemByPosition( i-1 )->IsSeparator() )
723            {
724                // noe need for two neighbouring separators
725                menu->Destroy( item );
726            }
727        }
728    }
729
730    // check if we received any empty menu!
731    i = 0;
732    while(i < GetMenuCount())
733    {
734        menu = GetMenu(i);
735
736        if( menu && (menu->GetMenuItemCount()==0) )
737        {
738            menu = Remove( i );
739            delete menu;
740        }
741        else
742            i++;
743    }
744
745    wxMenuBarBase::Attach(frame);
746
747    LoadMenu();
748}
749
750void wxMenuBar::Detach()
751{
752    wxMenuBarBase::Detach();
753}
754
755#endif // wxUSE_MENUS
756