// Copyright 2008, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//    * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//    * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//    * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#ifndef CHROME_FRAME_IN_PLACE_MENU_H_
#define CHROME_FRAME_IN_PLACE_MENU_H_

// in_place_menu.h : menu merging implementation
//
// This file is a modified version of the menu.h file, which is
// part of the ActiveDoc MSDN sample. The modifications are largely
// conversions to Google coding guidelines. Below is the original header
// from the file.

// This is a part of the Active Template Library.
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
// This source code is only intended as a supplement to the
// Active Template Library Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// Active Template Library product.

#include "base/logging.h"
#include "base/scoped_comptr_win.h"

template <class T>
class InPlaceMenu {
 public:
  InPlaceMenu() : shared_menu_(NULL), ole_menu_(NULL), our_menu_(NULL) {
  }

  ~InPlaceMenu() {
    InPlaceMenuDestroy();
  }

  HRESULT InPlaceMenuCreate(LPCWSTR menu_name) {
    // We might already have an in-place menu set, because we set menus
    // IOleDocumentView::UIActivate as well as in
    // IOleInPlaceActiveObject::OnDocWindowActivate. If we have already
    // done our work, just return silently
    if (ole_menu_ || shared_menu_)
      return S_OK;

    ScopedComPtr<IOleInPlaceFrame> in_place_frame;
    GetInPlaceFrame(in_place_frame.Receive());
    // We have no IOleInPlaceFrame, no menu merging possible
    if (!in_place_frame) {
      NOTREACHED();
      return E_FAIL;
    }
    // Create a blank menu and ask the container to add
    // its menus into the OLEMENUGROUPWIDTHS structure
    shared_menu_ = ::CreateMenu();
    OLEMENUGROUPWIDTHS mgw = {0};
    HRESULT hr = in_place_frame->InsertMenus(shared_menu_, &mgw);
    if (FAILED(hr)) {
      ::DestroyMenu(shared_menu_);
      shared_menu_ = NULL;
      return hr;
    }
    // Insert our menus
    our_menu_ = LoadMenu(_AtlBaseModule.GetResourceInstance(),menu_name);
    MergeMenus(shared_menu_, our_menu_, &mgw.width[0], 1);
    // Send the menu to the client
    ole_menu_ = (HMENU)OleCreateMenuDescriptor(shared_menu_, &mgw);
    T* t = static_cast<T*>(this);
    in_place_frame->SetMenu(shared_menu_, ole_menu_, t->m_hWnd);
    return S_OK;
  }

  HRESULT InPlaceMenuDestroy() {
    ScopedComPtr<IOleInPlaceFrame> in_place_frame;
    GetInPlaceFrame(in_place_frame.Receive());
    if (in_place_frame) {
      in_place_frame->RemoveMenus(shared_menu_);
      in_place_frame->SetMenu(NULL, NULL, NULL);
    }
    if (ole_menu_) {
      OleDestroyMenuDescriptor(ole_menu_);
      ole_menu_ = NULL;
    }
    if (shared_menu_) {
      UnmergeMenus(shared_menu_, our_menu_);
      DestroyMenu(shared_menu_);
      shared_menu_ = NULL;
    }
    if (our_menu_) {
      DestroyMenu(our_menu_);
      our_menu_ = NULL;
    }
    return S_OK;
  }

  void MergeMenus(HMENU shared_menu, HMENU source_menu, LONG* menu_widths,
                  unsigned int width_index) {
    // Copy the popups from the source menu
    // Insert at appropriate spot depending on width_index
    DCHECK(width_index == 0 || width_index == 1);
    int position = 0;
    if (width_index == 1)
      position = (int)menu_widths[0];
    int group_width = 0;
    int menu_items = GetMenuItemCount(source_menu);
    for (int index = 0; index < menu_items; index++) {
      // Get the HMENU of the popup
      HMENU popup_menu = ::GetSubMenu(source_menu, index);
      // Separators move us to next group
      UINT state = GetMenuState(source_menu, index, MF_BYPOSITION);
      if (!popup_menu && (state & MF_SEPARATOR)) {
         // Servers should not touch past 5
        DCHECK(width_index <= 5);
        menu_widths[width_index] = group_width;
        group_width = 0;
        if (width_index < 5)
          position += static_cast<int>(menu_widths[width_index+1]);
        width_index += 2;
      } else {
        // Get the menu item text
        TCHAR item_text[256] = {0};
        int text_length = GetMenuString(source_menu, index, item_text,
                                        ARRAYSIZE(item_text), MF_BYPOSITION);
        // Popups are handled differently than normal menu items
        if (popup_menu) {
          if (::GetMenuItemCount(popup_menu) != 0) {
            // Strip the HIBYTE because it contains a count of items
            state = LOBYTE(state) | MF_POPUP;   // Must be popup
            // Non-empty popup -- add it to the shared menu bar
            InsertMenu(shared_menu, position, state|MF_BYPOSITION,
                       reinterpret_cast<UINT_PTR>(popup_menu), item_text);
            ++position;
            ++group_width;
          }
        } else if (text_length > 0) {
          // only non-empty items are added
          DCHECK(item_text[0] != 0);
          // here the state does not contain a count in the HIBYTE
          InsertMenu(shared_menu, position, state|MF_BYPOSITION,
                     GetMenuItemID(source_menu, index), item_text);
          ++position;
          ++group_width;
        }
      }
    }
  }

  void UnmergeMenus(HMENU shared_menu, HMENU source_menu) {
    int our_item_count = GetMenuItemCount(source_menu);
    int shared_item_count = GetMenuItemCount(shared_menu);

    for (int index = shared_item_count - 1; index >= 0; index--) {
      // Check the popup menus
      HMENU popup_menu = ::GetSubMenu(shared_menu, index);
      if (popup_menu) {
        // If it is one of ours, remove it from the shared menu
        for (int sub_index = 0; sub_index < our_item_count; sub_index++) {
          if (::GetSubMenu(source_menu, sub_index) == popup_menu) {
            // Remove the menu from hMenuShared
            RemoveMenu(shared_menu, index, MF_BYPOSITION);
            break;
          }
        }
      }
    }
  }

 protected:
  HRESULT GetInPlaceFrame(IOleInPlaceFrame** in_place_frame) {
    if (!in_place_frame) {
      NOTREACHED();
      return E_POINTER;
    }
    T* t = static_cast<T*>(this);
    HRESULT hr = E_FAIL;
    if (!t->in_place_frame_) {
      // We weren't given an IOleInPlaceFrame pointer, so
      // we'll have to get it ourselves.
      if (t->m_spInPlaceSite) {
        t->frame_info_.cb = sizeof(OLEINPLACEFRAMEINFO);
        ScopedComPtr<IOleInPlaceUIWindow> in_place_ui_window;
        RECT position_rect = {0};
        RECT clip_rect = {0};
        hr = t->m_spInPlaceSite->GetWindowContext(in_place_frame,
                                                  in_place_ui_window.Receive(),
                                                  &position_rect, &clip_rect,
                                                  &t->frame_info_);
      }
    } else {
      *in_place_frame = t->in_place_frame_;
      (*in_place_frame)->AddRef();
      hr = S_OK;
    }
    return hr;
  }

 protected:
  // The OLE menu descriptor created by the OleCreateMenuDescriptor
  HMENU ole_menu_;
  // The shared menu that we pass to IOleInPlaceFrame::SetMenu
  HMENU shared_menu_;
  // Our menu resource that we want to insert
  HMENU our_menu_;
};

#endif  // CHROME_FRAME_IN_PLACE_MENU_H_