//----------------------------------------------------------------------------
// ObjectWindows
// (C) Copyright 1994 by Borland International, All Rights Reserved
//
//   Implementation of class TOleFrame.
//----------------------------------------------------------------------------
#define INC_OLE2
#include <owl/owlpch.h>
#include <owl/decmdifr.h>
#include <owl/statusba.h>
#include <owl/ocfevent.h>
#include <owl/uihandle.h>
#include <owl/oleframe.h>
#include <owl/olewindo.h>

DIAG_DEFINE_GROUP_INIT(OWL_INI, OwlOleMenu, 1, 0);

//#define DEBUG_PADS          // define to make pads paint red for debugging
const int DllIdleTime = 200;  // time in MS for idle action polling

// Four edge locations for inplace-server space negotiation
//
const TDecoratedFrame::TLocation SpaceLoc[] = {
  TDecoratedFrame::Left,
  TDecoratedFrame::Top,
  TDecoratedFrame::Right,
  TDecoratedFrame::Bottom
};

//----------------------------------------------------------------------------
// An empty no-erase window that inserts itself into its decorated frame
// parent
//

class TPadWindow : public TWindow {
  public:
    TPadWindow(TWindow* parent, int edge, TModule* module = 0)
      : TWindow(parent, 0, module)
    {
      Attr.Id = IDW_PADDECORATION+edge;
      Attr.Style = WS_CHILD | WS_VISIBLE;
      Attr.W = Attr.H = 1;             // takeup no space until needed
                                       // need 1 since layout overlapps borders
      #if defined(DEBUG_PADS)
        SetBkgndColor(TColor(255,0,0));  // to help Debug toolbar negotiations
        strstream s;
        s << "Pad" << ('0' + edge);
        Title = strnewdup(s.str());
      #else
        SetBkgndColor(NoErase);          // Don't erase--to avoid flicker
      #endif
    }
};

//----------------------------------------------------------------------------
// A small window class to act as a holder for the unattached remote server
// view windows
//

class TRemViewBucket : public TFrameWindow {
  public:
    TRemViewBucket(TWindow* parent)
      : TFrameWindow(parent, "RemViewBucket")
      {
        Attr.Style = WS_CHILD | WS_DISABLED;
        Attr.Id = IDW_REMVIEWBUCKET;
      }

    bool SetDocTitle(const char far*, int) {return true;}

  protected:
  private:
};


DEFINE_RESPONSE_TABLE1(TOleFrame, TDecoratedFrame)
  EV_WM_SIZE,
  EV_WM_TIMER,
  EV_WM_ACTIVATEAPP,

  EV_MESSAGE(WM_OCEVENT, EvOcEvent),
  EV_OC_APPINSMENUS,
  EV_OC_APPMENUS,
  EV_OC_APPPROCESSMSG,
  EV_OC_APPFRAMERECT,
  EV_OC_APPBORDERSPACEREQ,
  EV_OC_APPBORDERSPACESET,
  EV_OC_APPSTATUSTEXT,
  EV_OC_APPRESTOREUI,
  EV_OC_APPSHUTDOWN,
END_RESPONSE_TABLE;

TOleFrame::TOleFrame(const char far* title,
                     TWindow*        clientWnd,
                     bool            trackMenuSelection,
                     TModule*        module)
:
  TDecoratedFrame(0, title, clientWnd, trackMenuSelection, module),
  TFrameWindow(0, title, clientWnd, false, module),
  TWindow(0, title, module),
  HOldMenu(0),
  OcApp(0),
  StashCount(0),
  OcShutDown(DontCare)
{
  new TRemViewBucket(this);  // Construct bucket to hold hidden server windows

  // Retrieve the OcApp ptr from our owning application if it is a TOcModule
  //
  TOcModule* ocm = TYPESAFE_DOWNCAST(GetApplication(), TOcModule);
  if (ocm)
    SetOcApp(ocm->OcApp);
}

//
// Let the OC app go. It will delete itself when it can
//
TOleFrame::~TOleFrame()
{
}

//
// Initial set of OcApp being passed to us to use.
//
void
TOleFrame::SetOcApp(TOcApp* ocApp)
{
  PRECONDITION(ocApp);
  OcApp = ocApp;

  // Initialize OLE 2 clipboard format names
  //
  char f[] = "%s";
  AddUserFormatName(f, f, ocrEmbedSource);
  AddUserFormatName(f, f, ocrEmbeddedObject);
  AddUserFormatName(f, f, ocrLinkSource);
}

//
// Let the OcApp know our HWND so that it can talk to us
//
void
TOleFrame::SetupWindow()
{
  PRECONDITION(OcApp);
  TDecoratedFrame::SetupWindow();
  OcApp->SetupWindow(*this);

  // Insert the four pad windows for in-place server toolbars. Inserting last
  // will place them as the inner-most decorations, which is needed with the
  // status bar
  //
  for (int edge = 0; edge < 4; edge++)
    Insert(*new TPadWindow(this, edge), ::SpaceLoc[edge]);

  // Create a timer to allow us to poll for idle time when we are a dll server
  //
  if (!OcApp->IsOptionSet(amExeMode))
    SetTimer(IDT_DLLIDLE, DllIdleTime);
}

//
//
//
void
TOleFrame::CleanupWindow()
{
  if (!OcApp->IsOptionSet(amExeMode))
    KillTimer(IDT_DLLIDLE);
}

//
// Add user defined format and result names to the list
//
void
TOleFrame::AddUserFormatName(const char far* name, const char far* resultName,
                             const char far* id)
{
  PRECONDITION(OcApp);
  OcApp->AddUserFormatName(name, resultName, id);
}

//
//
//
void
TOleFrame::OleViewClosing(bool close)
{
  if (close && OcShutDown == DontCare) {
    OcShutDown = ViewInitiated;
  }
  else if (!close && OcShutDown == ViewInitiated) {
    OcShutDown = DontCare;
  }
}

// Disconnect document servers with their clients.
// Document servers can be documents with objects copied on the clipboard or
// documents brought up though linking.
//
void
sDisconnectDocServer(TWindow* win, void* /*retVal*/)
{
  if (win) {
    win->ForEach(sDisconnectDocServer);

    TOleWindow* oleWin = TYPESAFE_DOWNCAST(win, TOleWindow);
    if (oleWin)
      oleWin->OleShutDown();
  }
}

//
// Make sure that any embeded servers also get a chance to OK the close
// sequence. If the user closes the app, but there are still embeddings, hide
// the frame instead of destroying it
//
void
TOleFrame::Destroy(int retVal)
{
  if (!HWindow || OcShutDown == UserInitiated)
    return;

  // Disconnect document servers with their clients if user shuts down the app
  //
  if (OcShutDown == DontCare) {
    OcShutDown = UserInitiated;
    ForEach(sDisconnectDocServer, 0);
  }

  if (!OcApp->CanClose()) {
    OcApp->SetOption(amEmbedding, true);
    OcShutDown = DontCare; // reset the shutdown flag
    ShowWindow(SW_HIDE);
  }
  else {
    bool dllServer = !OcApp->IsOptionSet(amExeMode);

    TDecoratedFrame::Destroy(retVal);

    // If user shuts down the DLL server (as in the case of open-editing,
    // we need to set the mainwindow flag to 0 so that only the application
    // will be destroyed.  All windows are destroyed in previous calls.
    //
    if (dllServer && OcShutDown != ServerInitiated) {
//      GetApplication()->SetMainWindow(0);
      GetApplication()->MainWindow = 0;
      delete GetApplication();
    }
  }
}

//
// Forward size message to OcApp to allow it to notify any embedded servers
// about size changes
//
void
TOleFrame::EvSize(uint sizeType, TSize& size)
{
  TDecoratedFrame::EvSize(sizeType, size);
  if (OcApp)
    OcApp->EvResize();
}

//
// Forward Activate messages to OcApp to allow it to notify any embedded servers
// about being activated
//
void
TOleFrame::EvActivateApp(bool active, HTASK hTask)
{
  OcApp->EvActivate(active);
  TDecoratedFrame::EvActivateApp(active, hTask);
}

//
//
//
void
TOleFrame::EvTimer(uint timerId)
{
  if (timerId == IDT_DLLIDLE)
    GetApplication()->PostDispatchAction();
  TWindow::EvTimer(timerId);
}

//
// Handle & sub-dispatch the OC event message.
//
LRESULT
TOleFrame::EvOcEvent(WPARAM wParam, LPARAM lParam)
{
  TEventHandler::TEventInfo eventInfo(WM_OCEVENT, wParam);

  // Give the client window the first chance at it
  //
  TWindow* receiver = GetClientWindow();
  if (receiver->Find(eventInfo))
    return receiver->Dispatch(eventInfo, wParam, lParam);

  // Then try this frame
  //
  if (Find(eventInfo))
    return Dispatch(eventInfo, wParam, lParam);

  // Last, try the application in case it wants to override events
  //
  if (GetApplication()->Find(eventInfo))
    return GetApplication()->Dispatch(eventInfo, wParam, lParam);
  return 0;
}

//
// Insert our menus into a provided menu bar, possibly merging them with a
// servers.
//
bool
TOleFrame::EvOcAppInsMenus(TOcMenuDescr far& sharedMenu)
{
  if (HOldMenu) {
    TRACEX(OwlOleMenu, 0, "EvOcAppInsMenus called while HOldMenu is " << hex <<
          (uint)HOldMenu);
    return true;
  }

  // Recreate a temporary composite menu for frame
  //
  TMenuDescr compMenuDesc; // empty menudescr
  if (GetMenuDescr()) {
    compMenuDesc.Merge(*GetMenuDescr());

    // Mask off the server menus
    //
    compMenuDesc.Merge(TMenuDescr(0,  0, -1, 0, -1, 0, -1));
  }

  // Merge into the OLE shared menubar
  //
  TMenuDescr shMenuDescr(sharedMenu.HMenu,
                         sharedMenu.Width[0],
                         sharedMenu.Width[1],
                         sharedMenu.Width[2],
                         sharedMenu.Width[3],
                         sharedMenu.Width[4],
                         sharedMenu.Width[5]);
  shMenuDescr.Merge(compMenuDesc);

  // Copy the shared menu widths back to the OC struct
  //
  for (int i = 0; i < 6; i++)
    sharedMenu.Width[i] = shMenuDescr.GetGroupCount(i);

  // Save the container popups so they can be destroyed later
  //
  StashContainerPopups(shMenuDescr);

  TRACEX(OwlOleMenu, 0, "Merged menu " << hex << (uint)sharedMenu.HMenu);
  return true;
}

//
// Now, actually set the merged menubar into our frame
//
bool
TOleFrame::EvOcAppMenus(TOcMenuDescr far& appMenus)
{
  if (!appMenus.HMenu) {
    if (HOldMenu) {
      TRACEX(OwlOleMenu, 0, "EvOcAppMenus(0) resetting Old " << hex <<
             (uint)HOldMenu);
      TMenu oleMenu(*this);
      SetMenu(HOldMenu);     // assumes we are just restoring the old owl menu
      HOldMenu = 0;
    }
    DestroyStashedPopups();  // destroy the popup copies we made
    return true;
  }

  // Don't set the menu again if we are holding a merged one already
  //
  if (HOldMenu) {
    TRACEX(OwlOleMenu, 0, "EvOcAppMenus called while HOldMenu is " << hex <<
          (uint)HOldMenu);
    return true;
  }

  HOldMenu = GetMenu();
  TRACEX(OwlOleMenu, 0, "Saved Old " << hex << (uint)HOldMenu);

  SetMenu(appMenus.HMenu);
  TRACEX(OwlOleMenu, 0, "Set merged " << hex << (uint)appMenus.HMenu);

  return true;
}

//
// Process accelerators and other queued messages from the server's queue in
// our app
//
bool
TOleFrame::EvOcAppProcessMsg(MSG far* msg)
{
#if defined(BI_DATA_NEAR)
  MSG nearMsg = *msg;
  bool ret = GetApplication()->ProcessAppMsg(nearMsg);
  *msg = nearMsg;
  return ret;
#else
  return GetApplication()->ProcessAppMsg(*msg);
#endif
}

//
// Let the server know our frame's client rect, minus the status bar height,
// if there is one
//
bool
TOleFrame::EvOcAppFrameRect(TRect far* rect)
{
  PRECONDITION(rect);
  *rect = GetClientRect();
  TWindow*  sb = ChildWithId(IDW_STATUSBAR);
  if (sb) {
    TRect sbr = sb->GetClientRect();
    rect->bottom -= sbr.bottom+1;
  }
  return true;
}

//
// Let the server know that we can handle border adornments on all four edges
//
bool
TOleFrame::EvOcAppBorderSpaceReq(TRect far* /*space*/)
{
  return true;
}

//
// Actually create space on the requested edges for the server's adornments
//
bool
TOleFrame::EvOcAppBorderSpaceSet(TRect far* space)
{
  // Resize pad decorations based on edges requested
  //
  int* edges = (int*)space;   // treat space as array of 4 int edges
  bool needLayout = false;
  for (int i = 0; i < 4; i++) {
    TWindow*  pad = ChildWithId(IDW_PADDECORATION+i);
    if (pad) {
      int edge = edges && edges[i] ? edges[i]+1 : 1;
      if (i%1 == 0 && pad->Attr.W != edge ||   // left & right edge
          i%1 == 1 && pad->Attr.H != edge) {   // top & bottom edge
        TLayoutMetrics m;
        GetChildLayoutMetrics(*pad, m);
        pad->Attr.H = pad->Attr.W = edge;  // set both axis, one will be stretched
        SetChildLayoutMetrics(*pad, m);
        needLayout = true;
      }
    }
  }

  // Turn on/off control bar as needed
  //
  TWindow*  tb = ChildWithId(IDW_TOOLBAR);
  if (tb)
    if (space && tb->IsWindowVisible() || !space && !tb->IsWindowVisible()) {
      SendMessage(WM_COMMAND, IDW_TOOLBAR);  // toggle tool bar on/off
      needLayout = false;  // layout already done now by decorated frame.
    }

  // Now do layout once at the end to reduce repaint
  //
  if (needLayout)
    Layout();

  return true;
}

//
// Display the servers status messages on our status bar
//
void
TOleFrame::EvOcAppStatusText(const char far* text)
{
  TMessageBar* mb = TYPESAFE_DOWNCAST(ChildWithId(IDW_STATUSBAR), TMessageBar);
  if (mb) {
    //mb->SetHintText(text);   // is the text a hint, or general status??
#if defined(BI_DATA_NEAR)
    mb->SetText(string(text).c_str());
#else
    mb->SetText(text);
#endif
  }
}

//
// Save off the popup menu handles that are copies so that we can destroy
// them later. Keep track of active object menus, to allow cleanup when
// the last one goes away.
//
void TOleFrame::StashContainerPopups(const TMenuDescr& shMenuDescr)
{
  StashCount++;

  int m = 0;
  for (int i = 0; i < 6; i++) {
    if (i%2 == 0)
      for (int j = 0; j < shMenuDescr.GetGroupCount(i); j++) {
        uint  state = shMenuDescr.GetMenuState(m+j, MF_BYPOSITION);
        if (state == uint(-1))
          continue;
        TRACEX(OwlOleMenu, 1, "Stashing " << hex << (uint)shMenuDescr.GetSubMenu(m+j));
        StashedContainerPopups.AppendMenu(state, TMenu(shMenuDescr.GetSubMenu(m+j)), "");
      }
    m += shMenuDescr.GetGroupCount(i);
  }
}

//
// Throw away our popup copies that are no longer needed, when the last active
// object is deactivated.
//
void TOleFrame::DestroyStashedPopups()
{
  if (--StashCount)
    return;

  while (StashedContainerPopups.GetMenuItemCount()) {
    TRACEX(OwlOleMenu, 1, "Destroying " << hex << (uint)StashedContainerPopups.GetSubMenu(0));
    StashedContainerPopups.DeleteMenu(0, MF_BYPOSITION);
  }
}

//
// Restore the UI back to pre-inplace server state. Remove the border space pads
// and restore the menu.
//
void
TOleFrame::EvOcAppRestoreUI()
{
  // Only restore the old menu if we are holding a merged one
  //
  if (HOldMenu) {
    TRACEX(OwlOleMenu, 0, "EvOcAppRestoreUI resetting Old " << hex <<
           (uint)HOldMenu);
    TMenu oleMenu(*this);
    SetMenu(HOldMenu);    // assumes we are just restoring the old owl menu
    HOldMenu = 0;
  }

  // Remove pad decorations & restore our toobar if we have one
  //
  EvOcAppBorderSpaceSet(0);
}

//
// Called by TOcRemView::Close when last embedding is closed
// If that's the only reason the app is up we need to shut ourselves down
//
bool
TOleFrame::EvOcAppShutdown()
{
  // If TOleFrame was created purely for embedded server, then
  // we want to shut down the app when nobody is using the server.
  // The amEmbedding flag will be set to false if user created normal
  // document in this frame.
  //
  if (OcShutDown == DontCare && OcApp->IsOptionSet(amEmbedding)) {
    // The shut down is initiated by OCF
    //
    OcShutDown = ServerInitiated;

    // Post frame close to kill the app later
    //
    PostMessage(WM_CLOSE);

    return true;
  }
  else {

    // If the last view closing caused the ocapp to shutdown, then close this
    // frame.
    //
    if (OcApp->IsOptionSet(amEmbedding) && OcShutDown == ViewInitiated)
      PostMessage(WM_CLOSE);

    return false; // Shut down initiated by user
  }
}
