1/*
2 * This file is part of the popup menu implementation for <select> elements in WebCore.
3 *
4 * Copyright (C) 2010 Stephan Aßmus <superstippi@gmx.de>
5 * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 * Library General Public License for more details.
16 *
17 * You should have received a copy of the GNU Library General Public License
18 * along with this library; see the file COPYING.LIB.  If not, write to
19 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 * Boston, MA 02111-1307, USA.
21 *
22 */
23
24#include "config.h"
25#include "PopupMenuHaiku.h"
26
27#include "FrameView.h"
28
29#include "NotImplemented.h"
30#include <Application.h>
31#include <Handler.h>
32#include <MenuItem.h>
33#include <Message.h>
34#include <PopUpMenu.h>
35#include <String.h>
36#include <Window.h>
37#include <support/Autolock.h>
38#include <support/Locker.h>
39
40namespace WebCore {
41
42static const uint32 kPopupResult = 'pmrs';
43static const uint32 kPopupHidden = 'pmhd';
44
45// This BHandler is added to the application (main) thread, so that we
46// invoke methods on the PopupMenuClient within the main thread.
47class PopupMenuHandler : public BHandler {
48public:
49    PopupMenuHandler(PopupMenuClient* popupClient)
50        : m_popupClient(popupClient)
51    {
52    }
53
54    virtual void MessageReceived(BMessage* message)
55    {
56        switch (message->what) {
57        case kPopupResult: {
58            int32 index = 0;
59            message->FindInt32("index", &index);
60            m_popupClient->valueChanged(index);
61            break;
62        }
63        case kPopupHidden:
64            m_popupClient->popupDidHide();
65            break;
66
67        default:
68            BHandler::MessageReceived(message);
69        }
70    }
71
72private:
73    PopupMenuClient* m_popupClient;
74};
75
76class HaikuPopup : public BPopUpMenu {
77public:
78    HaikuPopup(PopupMenuClient* popupClient)
79        : BPopUpMenu("WebCore Popup", true, false)
80        , m_popupClient(popupClient)
81        , m_Handler(popupClient)
82    {
83        if (be_app->Lock()) {
84            be_app->AddHandler(&m_Handler);
85            be_app->Unlock();
86        }
87        SetAsyncAutoDestruct(false);
88    }
89
90    virtual ~HaikuPopup()
91    {
92        if (be_app->Lock()) {
93            be_app->RemoveHandler(&m_Handler);
94            be_app->Unlock();
95        }
96    }
97
98    void show(const IntRect& rect, FrameView* view, int index)
99    {
100        // Clean out the menu first
101        for (int32 i = CountItems() - 1; i >= 0; i--)
102            delete RemoveItem(i);
103
104        // Popuplate the menu from the client
105        int itemCount = m_popupClient->listSize();
106        for (int i = 0; i < itemCount; i++) {
107            if (m_popupClient->itemIsSeparator(i))
108                AddSeparatorItem();
109            else {
110                // NOTE: WebCore distinguishes between "Group" and "Label"
111                // here, but both types of item (radio or check mark) currently
112                // look the same on Haiku.
113                BString label(m_popupClient->itemText(i));
114                BMessage* message = new BMessage(kPopupResult);
115                message->AddInt32("index", i);
116                BMenuItem* item = new BMenuItem(label.String(), message);
117                AddItem(item);
118                item->SetTarget(BMessenger(&m_Handler));
119                item->SetEnabled(m_popupClient->itemIsEnabled(i));
120                item->SetMarked(i == index);
121            }
122        }
123
124        // We need to force a layout now, or the item frames will not be
125        // computed yet, so we cannot move the current item under the mouse.
126        DoLayout();
127
128        // Account for frame of menu field
129        BRect screenRect(view->contentsToScreen(rect));
130        screenRect.OffsetBy(2, 2);
131        // Move currently selected item under the mouse.
132        if (BMenuItem* item = ItemAt(index))
133            screenRect.OffsetBy(0, -item->Frame().top);
134
135        BRect openRect = Bounds().OffsetToSelf(screenRect.LeftTop());
136
137        Go(screenRect.LeftTop(), true, true, openRect, true);
138    }
139
140    void hide()
141    {
142        if (!IsHidden())
143            Hide();
144    }
145
146private:
147    virtual void Hide()
148    {
149        BPopUpMenu::Hide();
150        be_app->PostMessage(kPopupHidden, &m_Handler);
151    }
152
153    PopupMenuClient* m_popupClient;
154    PopupMenuHandler m_Handler;
155};
156
157PopupMenuHaiku::PopupMenuHaiku(PopupMenuClient* client)
158    : m_popupClient(client)
159    , m_menu(new HaikuPopup(client))
160{
161    // We don't need additional references to the client, since we completely
162    // control any sub-objects we create that need it as well.
163}
164
165PopupMenuHaiku::~PopupMenuHaiku()
166{
167    delete m_menu;
168}
169
170void PopupMenuHaiku::disconnectClient()
171{
172    m_popupClient = 0;
173}
174
175void PopupMenuHaiku::show(const IntRect& rect, FrameView* view, int index)
176{
177    // The menu will update itself from the PopupMenuClient before showing.
178    m_menu->show(rect, view, index);
179}
180
181void PopupMenuHaiku::hide()
182{
183    m_menu->hide();
184}
185
186void PopupMenuHaiku::updateFromElement()
187{
188    client()->setTextFromItem(m_popupClient->selectedIndex());
189}
190
191} // namespace WebCore
192
193