Revised SkOSMenu
http://codereview.appspot.com/4827042/


git-svn-id: http://skia.googlecode.com/svn/trunk@2013 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/include/views/SkOSMenu.h b/include/views/SkOSMenu.h
index c599bd8..2dbc1a5 100644
--- a/include/views/SkOSMenu.h
+++ b/include/views/SkOSMenu.h
@@ -15,30 +15,123 @@
 
 class SkOSMenu {
 public:
-    explicit SkOSMenu(const char title[]);
+    explicit SkOSMenu(const char title[] = "");
     ~SkOSMenu();
+    
+    void reset();
+    /**
+     * Each of these (except action) has an associated value, which is stored in 
+     * the event payload for the item.
+     * Each type has a specific type for its value...
+     *     Action : none
+     *     List : int (selected index)
+     *     Segmented : int (selected index)
+     *     Slider : float
+     *     Switch : bool
+     *     TextField : string
+     *     TriState : TriState
+     *     Custom : custom object/value
+     */
+    enum TriState {
+        kMixedState = -1,
+        kOffState = 0,
+        kOnState = 1
+    };
+    
+    enum Type {
+        kAction_Type,
+        kList_Type,
+        kSlider_Type,
+        kSwitch_Type,
+        kTriState_Type,
+        kTextField_Type,
+        kCustom_Type
+    };
+    
+    class Item {
+    public:
+        //Auto increments a global to generate an unique ID for each new item
+        //Thread safe
+        Item(const char label[], SkOSMenu::Type type, const char slotName[], 
+             SkEvent* evt, SkEventSinkID target);
+        ~Item() { delete fEvent; }
+        
+        SkEvent* getEvent() const { return fEvent; }
+        int getID() { return fID; }
+        const char* getLabel() const { return fLabel.c_str(); }
+        const char* getSlotName() const { return fSlotName.c_str(); }
+        Type getType() const { return fType; }
+        
+        //Post event associated with the menu item to target, any changes to the
+        //associated event must be made prior to calling this method.
+        void postEvent() const { (new SkEvent(*(fEvent)))->post(fTarget); }
+        
+        //Helper functions for predefined types
+        void postEventWithBool(bool value) const; //For Switch
+        void postEventWithScalar(SkScalar value) const; //For Slider
+        void postEventWithInt(int value) const; //For List, TriState
+        void postEventWithString(const char value[]) const; //For TextField
 
-    const char* getTitle() const { return fTitle; }
-
-    void    appendItem(const char title[], const char eventType[], int32_t eventData);
-
+        
+    private:
+        int             fID;
+        SkEvent*        fEvent;
+        SkString        fLabel;
+        SkString        fSlotName;
+        SkEventSinkID   fTarget;
+        Type            fType;
+    };
+    
+    //The following functions append new items to the menu and returns their 
+    //associated unique id, which can be used to by the client to refer to 
+    //the menu item created and change its state. slotName specifies the string
+    //identifier of any state/value to be returned in the item's SkEvent object
+    //NOTE: evt must be dynamically allocated
+    int appendItem(const char label[], Type type, const char slotName[], 
+                   SkEvent* evt, SkEventSinkID target); 
+    
+    //Predefined items and helper functions:
+    //Identifiers
+    static const char* EventType;
+    static const char* Delimiter;
+    static const char* List_Items_Str;
+    static const char* Slider_Min_Scalar;
+    static const char* Slider_Max_Scalar;
+    
+    //Create predefined items with the given parameters. To be used with the 
+    int appendAction(const char label[], SkEventSinkID target);
+    int appendList(const char label[], const char slotName[], 
+                   SkEventSinkID target, int defaultIndex, const char[] ...);
+    int appendSlider(const char label[], const char slotName[], 
+                     SkEventSinkID target, SkScalar min, SkScalar max, 
+                     SkScalar defaultValue);
+    int appendSwitch(const char label[], const char slotName[], 
+                     SkEventSinkID target, bool defaultState = false);
+    int appendTriState(const char label[], const char slotName[],
+                       SkEventSinkID target, SkOSMenu::TriState defaultState = kOffState);
+    int appendTextField(const char label[], const char slotName[],
+                        SkEventSinkID target, const char placeholder[] = "");
+    
+    //Returns true if the event is of type SkOSMenu::EventType and retrieves 
+    //value stored in the evt that corresponds to the slotName. Otherwise, 
+    //returns false and leaves value unchanged
+    static bool FindAction(const SkEvent* evt, const char label[]);
+    static bool FindListIndex(const SkEvent* evt, const char slotName[], int* selected);
+    static bool FindSliderValue(const SkEvent* evt, const char slotName[], SkScalar* value);
+    static bool FindSwitchState(const SkEvent* evt, const char slotName[], bool* value);
+    static bool FindTriState(const SkEvent* evt, const char slotName[], TriState* state);
+    static bool FindText(const SkEvent* evt, const char slotName[], SkString* value);
+    
+    const char* getTitle() const { return fTitle.c_str(); }
+    void setTitle (const char title[]) { fTitle.set(title); }
     // called by SkOSWindow when it receives an OS menu event
     int         countItems() const;
-    const char* getItem(int index, uint32_t* cmdID) const;
-
-    SkEvent* createEvent(uint32_t os_cmd);
+    const Item* getItem(int index) const;
 
 private:
-    const char* fTitle;
-
-    struct Item {
-        const char* fTitle;
-        const char* fEventType;
-        uint32_t    fEventData;
-        uint32_t    fOSCmd; // internal
-    };
-    SkTDArray<Item> fItems;
-
+    SkString fTitle;
+    SkTDArray<Item*> fItems;
+    
     // illegal
     SkOSMenu(const SkOSMenu&);
     SkOSMenu& operator=(const SkOSMenu&);
diff --git a/include/views/SkWindow.h b/include/views/SkWindow.h
index 64c5bea..eda928c 100644
--- a/include/views/SkWindow.h
+++ b/include/views/SkWindow.h
@@ -51,9 +51,9 @@
     bool    handleChar(SkUnichar);
     bool    handleKey(SkKey);
     bool    handleKeyUp(SkKey);
-    bool    handleMenu(uint32_t os_cmd);
 
     void    addMenu(SkOSMenu*);
+    const SkTDArray<SkOSMenu*>* getMenus() { return &fMenus; }
     
     const char* getTitle() const { return fTitle.c_str(); }
     void    setTitle(const char title[]);
@@ -73,7 +73,8 @@
     virtual bool onHandleChar(SkUnichar);
     virtual bool onHandleKey(SkKey);
     virtual bool onHandleKeyUp(SkKey);
-    virtual void onAddMenu(const SkOSMenu*) {}
+    virtual void onAddMenu(const SkOSMenu*) {};
+    virtual void onUpdateMenu(const SkOSMenu*) {};
     virtual void onSetTitle(const char title[]) {}
 
     // overrides from SkView
diff --git a/src/views/SkOSMenu.cpp b/src/views/SkOSMenu.cpp
index 5ce36dc..a8856cc 100644
--- a/src/views/SkOSMenu.cpp
+++ b/src/views/SkOSMenu.cpp
@@ -1,60 +1,185 @@
-
 /*
  * Copyright 2011 Google Inc.
  *
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
+#include <stdarg.h>
 #include "SkOSMenu.h"
+#include "SkThread.h"
 
 static int gOSMenuCmd = 7000;
 
-SkOSMenu::SkOSMenu(const char title[])
-{
-	fTitle = title;
+SkOSMenu::SkOSMenu(const char title[]) {
+	fTitle.set(title);
 }
 
-SkOSMenu::~SkOSMenu()
-{
+SkOSMenu::~SkOSMenu() {
+    this->reset();
 }
 
-int SkOSMenu::countItems() const
-{
+void SkOSMenu::reset() {
+    fItems.deleteAll();
+    fTitle.reset();
+}
+
+int SkOSMenu::countItems() const {
 	return fItems.count();
 }
 
-void SkOSMenu::appendItem(const char title[], const char eventType[], int32_t eventData)
-{
-	Item* item = fItems.append();
-
-	item->fTitle	 = title;
-	item->fEventType = eventType;
-	item->fEventData = eventData;
-	item->fOSCmd	 = ++gOSMenuCmd;
+const SkOSMenu::Item* SkOSMenu::getItem(int index) const{
+	return fItems[index];
 }
 
-SkEvent* SkOSMenu::createEvent(uint32_t os_cmd)
-{
-	const Item* iter = fItems.begin();
-	const Item*	stop = fItems.end();
+////////////////////////////////////////////////////////////////////////////////
 
-	while (iter < stop)
-	{
-		if (iter->fOSCmd == os_cmd)
-		{
-			SkEvent* evt = new SkEvent(iter->fEventType);
-			evt->setFast32(iter->fEventData);
-			return evt;
-		}
-		iter++;
-	}
-	return NULL;
+SkOSMenu::Item::Item(const char label[], SkOSMenu::Type type, 
+                     const char slotName[], SkEvent* evt, SkEventSinkID target) {
+    fLabel.set(label);
+    fSlotName.set(slotName);
+    fType = type;
+    fTarget = target;
+    fEvent = evt;
+    fID = sk_atomic_inc(&gOSMenuCmd);
 }
 
-const char* SkOSMenu::getItem(int index, uint32_t* cmdID) const
-{
-	if (cmdID)
-		*cmdID = fItems[index].fOSCmd;
-	return fItems[index].fTitle;
+void SkOSMenu::Item::postEventWithBool(bool value) const {
+    SkASSERT(SkOSMenu::kSwitch_Type == fType);
+    fEvent->setBool(fSlotName.c_str(), value);
+    this->postEvent();
 }
 
+void SkOSMenu::Item::postEventWithScalar(SkScalar value) const {
+    SkASSERT(SkOSMenu::kSlider_Type == fType);
+    fEvent->setScalar(fSlotName.c_str(), value);
+    this->postEvent();
+}
+
+void SkOSMenu::Item::postEventWithInt(int value) const {
+    SkASSERT(SkOSMenu::kList_Type == fType || SkOSMenu::kTriState_Type == fType);
+    fEvent->setS32(fSlotName.c_str(), value);
+    this->postEvent();
+}
+
+void SkOSMenu::Item::postEventWithString(const char value[]) const {
+    SkASSERT(SkOSMenu::kTextField_Type == fType);
+    fEvent->setString(fSlotName.c_str(), value);
+    this->postEvent();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+const char* SkOSMenu::EventType = "SkOSMenuEventType";
+const char* SkOSMenu::Delimiter = "|";
+const char* SkOSMenu::Slider_Min_Scalar = "SkOSMenuSlider_Min";
+const char* SkOSMenu::Slider_Max_Scalar = "SkOSMenuSlider_Max";
+const char* SkOSMenu::List_Items_Str = "SkOSMenuList_Items";
+
+int SkOSMenu::appendItem(const char label[], Type type, const char slotName[], 
+                         SkEvent* evt, SkEventSinkID target) {
+    SkOSMenu::Item* item = new Item(label, type, slotName, evt, target);
+    fItems.append(1, &item);
+    return item->getID();
+}
+
+int SkOSMenu::appendAction(const char label[], SkEventSinkID target) {
+    SkEvent* evt = new SkEvent(SkOSMenu::EventType);
+    SkOSMenu::Item* item = new Item(label, SkOSMenu::kAction_Type, "", evt, target);
+    //Store label in event so it can be used to identify the action later
+    evt->setString(label, "");
+    fItems.append(1, &item);
+    return item->getID();
+}
+
+int SkOSMenu::appendList(const char label[], const char slotName[], 
+                         SkEventSinkID target, int index, const char option[], ...) {
+    SkEvent* evt = new SkEvent(SkOSMenu::EventType);
+    va_list args;
+    if (option) {
+        SkString str(option);
+        va_start(args, option);
+        for (const char* arg = va_arg(args, const char*); arg != NULL; arg = va_arg(args, const char*)) {
+            str += SkOSMenu::Delimiter;
+            str += arg;
+        }
+        va_end(args);
+        evt->setString(SkOSMenu::List_Items_Str, str);
+        evt->setS32(slotName, index);
+    }
+    SkOSMenu::Item* item = new Item(label, SkOSMenu::kList_Type, slotName, evt, target);
+    fItems.append(1, &item);
+    return item->getID();
+}
+
+int SkOSMenu::appendSlider(const char label[], const char slotName[], 
+                           SkEventSinkID target, SkScalar min, SkScalar max, 
+                           SkScalar defaultValue) {
+    SkEvent* evt = new SkEvent(SkOSMenu::EventType);
+    evt->setScalar(SkOSMenu::Slider_Min_Scalar, min);
+    evt->setScalar(SkOSMenu::Slider_Max_Scalar, max);
+    evt->setScalar(slotName, defaultValue);
+    SkOSMenu::Item* item = new Item(label, SkOSMenu::kSlider_Type, slotName, evt, target);
+    fItems.append(1, &item);
+    return item->getID();
+}
+
+int SkOSMenu::appendSwitch(const char label[], const char slotName[], 
+                           SkEventSinkID target, bool defaultState) {
+    SkEvent* evt = new SkEvent(SkOSMenu::EventType);
+    evt->setBool(slotName, defaultState);
+    SkOSMenu::Item* item = new Item(label, SkOSMenu::kSwitch_Type, slotName, evt, target);
+    fItems.append(1, &item);
+    return item->getID();
+}
+
+int SkOSMenu::appendTriState(const char label[], const char slotName[],
+                             SkEventSinkID target, SkOSMenu::TriState defaultState) {
+    SkEvent* evt = new SkEvent(SkOSMenu::EventType);
+    evt->setS32(slotName, defaultState);
+    SkOSMenu::Item* item = new Item(label, SkOSMenu::kTriState_Type, slotName, evt, target);
+    fItems.append(1, &item);
+    return item->getID();
+}
+
+int SkOSMenu::appendTextField(const char label[], const char slotName[], 
+                              SkEventSinkID target, const char placeholder[]) {
+    SkEvent* evt = new SkEvent(SkOSMenu::EventType);
+    evt->setString(slotName, placeholder);
+    SkOSMenu::Item* item = new Item(label, SkOSMenu::kTextField_Type, slotName, evt, target);
+    fItems.append(1, &item);
+    return item->getID();
+}
+
+
+bool SkOSMenu::FindAction(const SkEvent* evt, const char label[]) {
+    return evt->isType(SkOSMenu::EventType) && evt->findString(label);
+}
+
+bool SkOSMenu::FindListIndex(const SkEvent* evt, const char slotName[], int* selected) {
+    return evt->isType(SkOSMenu::EventType) && evt->findS32(slotName, selected); 
+}
+
+bool SkOSMenu::FindSliderValue(const SkEvent* evt, const char slotName[], SkScalar* value) {
+    return evt->isType(SkOSMenu::EventType) && evt->findScalar(slotName, value);
+}
+
+bool SkOSMenu::FindSwitchState(const SkEvent* evt, const char slotName[], bool* value) {
+    return evt->isType(SkOSMenu::EventType) && evt->findBool(slotName, value);
+}
+
+bool SkOSMenu::FindTriState(const SkEvent* evt, const char slotName[], SkOSMenu::TriState* state) {
+    return evt->isType(SkOSMenu::EventType) && evt->findS32(slotName, (int*)state);
+}
+
+bool SkOSMenu::FindText(const SkEvent* evt, const char slotName[], SkString* value) {
+    if (evt->isType(SkOSMenu::EventType)) {
+        const char* text = evt->findString(slotName);
+        if (!text || !*text)
+            return false;
+        else {
+            value->set(text);
+            return true;
+        }
+    }
+    return false;
+}
diff --git a/src/views/SkWindow.cpp b/src/views/SkWindow.cpp
index ef3264f..f6054df 100644
--- a/src/views/SkWindow.cpp
+++ b/src/views/SkWindow.cpp
@@ -290,8 +290,7 @@
     return false;
 }
 
-void SkWindow::addMenu(SkOSMenu* menu)
-{
+void SkWindow::addMenu(SkOSMenu* menu) {
 	*fMenus.append() = menu;
 	this->onAddMenu(menu);
 }
@@ -304,20 +303,6 @@
     this->onSetTitle(title);
 }
 
-bool SkWindow::handleMenu(uint32_t cmd)
-{
-	for (int i = 0; i < fMenus.count(); i++)
-	{
-		SkEvent* evt = fMenus[i]->createEvent(cmd);
-		if (evt)
-		{
-			evt->post(this->getSinkID());
-			return true;
-		}
-	}
-	return false;
-}
-
 //////////////////////////////////////////////////////////////////////
 
 bool SkWindow::onEvent(const SkEvent& evt)