/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
#include "HeadSpin.h"
#include "max.h"
#include "iparamb2.h"

#include "plComponentDlg.h"
#include "MaxComponent/plComponentBase.h"
#include "MaxComponent/plComponentMgr.h"
#include "MaxComponent/plComponentReg.h"
#include "resource.h"
#include "plMaxNode.h"
#include "plComponentPanel.h"
#include "plMaxAccelerators.h"

#include <algorithm>

extern HINSTANCE hInstance;

plComponentDlg::plComponentDlg() : fhDlg(nil), fCompMenu(nil), fTypeMenu(nil), fCommentNode(nil)
{
    fInterface = GetCOREInterface();

    RegisterNotification(INotify, 0, NOTIFY_FILE_PRE_OPEN);
    RegisterNotification(INotify, 0, NOTIFY_SYSTEM_PRE_NEW);
    RegisterNotification(INotify, 0, NOTIFY_SYSTEM_PRE_RESET);
    RegisterNotification(INotify, 0, NOTIFY_FILE_PRE_MERGE);
    RegisterNotification(INotify, 0, NOTIFY_PRE_IMPORT);
    RegisterNotification(INotify, 0, NOTIFY_FILE_PRE_SAVE);
    RegisterNotification(INotify, 0, NOTIFY_FILE_PRE_SAVE_OLD);

    RegisterNotification(INotify, 0, NOTIFY_FILE_POST_OPEN);
    RegisterNotification(INotify, 0, NOTIFY_SYSTEM_POST_NEW);
    RegisterNotification(INotify, 0, NOTIFY_SYSTEM_POST_RESET);
    RegisterNotification(INotify, 0, NOTIFY_FILE_POST_MERGE);
    RegisterNotification(INotify, 0, NOTIFY_POST_IMPORT);

    RegisterNotification(INotify, 0, NOTIFY_SYSTEM_SHUTDOWN);
}

plComponentDlg::~plComponentDlg()
{
    if (fhDlg)
    {
        fInterface->UnRegisterDlgWnd(fhDlg);
        DestroyWindow(fhDlg);
    }
    if (fCompMenu)
        DestroyMenu(fCompMenu);
    if (fTypeMenu)
        DestroyMenu(fTypeMenu);
}

plComponentDlg& plComponentDlg::Instance()
{
    static plComponentDlg theInstance;
    return theInstance;
}

void plComponentDlg::Open()
{
    if (!fhDlg)
    {
        fhDlg = CreateDialog(hInstance,
                            MAKEINTRESOURCE(IDD_COMP_MAIN),
                            GetCOREInterface()->GetMAXHWnd(),
                            ForwardDlgProc);

        GetWindowRect(fhDlg, &fLastRect);
        fSmallestSize.x = fLastRect.right - fLastRect.left;
        fSmallestSize.y = fLastRect.bottom - fLastRect.top;

        RECT rect;
        memcpy(&rect, &fLastRect, sizeof(RECT));
        rect.right = rect.left + 235;
        rect.bottom = rect.top + 335;
        IPositionControls(&rect, WMSZ_BOTTOM);
        SetWindowPos(fhDlg, NULL, 0, 0, rect.right - rect.left, rect.bottom - rect.top, SWP_NOMOVE | SWP_NOZORDER);
    }
    
    fInterface->RegisterDlgWnd(fhDlg);
    ShowWindow(fhDlg, SW_SHOW);

    if (IsIconic(fhDlg))
        ShowWindow(fhDlg, SW_RESTORE);
}

void plComponentDlg::IPositionControls(RECT *newRect, int edge)
{
    // Get the new width and height
    int newW = newRect->right - newRect->left;
    int newH = newRect->bottom - newRect->top;

    // If an edge we don't support is being dragged, don't allow the resize.
    if (!(edge == WMSZ_BOTTOM ||
        edge == WMSZ_BOTTOMRIGHT ||
        edge == WMSZ_RIGHT))
    {
        memcpy(newRect, &fLastRect, sizeof(RECT));
        return;
    }

    // If the width or height is too small, set it to the minimum
    if (newW < fSmallestSize.x)
        newRect->right = newRect->left + fSmallestSize.x;
    if (newH < fSmallestSize.y)
        newRect->bottom = newRect->top + fSmallestSize.y;

    // Calculate the new width and height
    int hDiff = (newRect->bottom - newRect->top) - (fLastRect.bottom - fLastRect.top);
    int wDiff = (newRect->right - newRect->left) - (fLastRect.right - fLastRect.left);

    // Copy our new rect to the last rect
    memcpy(&fLastRect, newRect, sizeof(RECT));

    // If the size has changed, reposition and resize controls
    if (hDiff != 0 || wDiff != 0)
    {
        IPositionControl(GetDlgItem(fhDlg, IDC_TREE),           hDiff, wDiff, kResizeX | kResizeY);
        IPositionControl(GetDlgItem(fhDlg, IDC_COMMENT_TEXT),   hDiff);
        IPositionControl(GetDlgItem(fhDlg, IDC_COMMENTS),       hDiff, wDiff, kResizeX | kMoveY);
        IPositionControl(GetDlgItem(fhDlg, IDC_ATTACH),         hDiff);
    }

    InvalidateRect(fhDlg, NULL, TRUE);
}

void plComponentDlg::IPositionControl(HWND hControl, int hDiff, int wDiff, int flags)
{
    RECT rect;
    GetWindowRect(hControl, &rect);

    hsAssert(!((flags & kMoveX) & (flags & kResizeX)), "Moving AND resizing in X in IPositionControl");
    hsAssert(!((flags & kMoveY) & (flags & kResizeY)), "Moving AND resizing in Y in IPositionControl");

    if (flags & kMoveX || flags & kMoveY)
    {
        POINT pos = { rect.left, rect.top };
        ScreenToClient(fhDlg, &pos);
        if (flags & kMoveX)
            pos.x += wDiff;
        if (flags & kMoveY)
            pos.y += hDiff;

        SetWindowPos(hControl, NULL, pos.x, pos.y, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOREDRAW);
    }

    if (flags & kResizeX || flags & kResizeY)
    {
        int w = rect.right - rect.left;
        int h = rect.bottom - rect.top;
        if (flags & kResizeX)
            w += wDiff;
        if (flags & kResizeY)
            h += hDiff;

        SetWindowPos(hControl, NULL, 0, 0, w, h, SWP_NOMOVE | SWP_NOZORDER | SWP_NOREDRAW);
    }
}

void plComponentDlg::IGetComment()
{
    if (fCommentNode)
    {
        // Get the text from the edit and store it in the UserPropBuffer
        int len = GetWindowTextLength(GetDlgItem(fhDlg, IDC_COMMENTS))+1;
        if (len != 0)
        {
            char *buf = TRACKED_NEW char[len];
            GetDlgItemText(fhDlg, IDC_COMMENTS, buf, len);
            fCommentNode->SetUserPropBuffer(buf);
            delete [] buf;
        }
        else
            fCommentNode->SetUserPropBuffer("");
    }
}

BOOL plComponentDlg::ForwardDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
    return Instance().DlgProc(hDlg, msg, wParam, lParam);
}

#define MENU_ID_START 41000

BOOL plComponentDlg::DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
    case WM_INITDIALOG:
        fhDlg = hDlg;
        IAddComponentsRecur(GetDlgItem(hDlg, IDC_TREE), (plMaxNode*)GetCOREInterface()->GetRootNode());

        ICreateMenu();
        ICreateRightClickMenu();
        return TRUE;

    case WM_SIZING:
        IPositionControls((RECT*)lParam, wParam);
        return TRUE;

    case WM_ACTIVATE:
        if (LOWORD(wParam) == WA_INACTIVE)
            plMaxAccelerators::Enable();
        else
            plMaxAccelerators::Disable();
        return TRUE;

    case WM_COMMAND:
        if (HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == IDCANCEL)
        {
            ShowWindow(hDlg, SW_HIDE);
            fInterface->UnRegisterDlgWnd(hDlg);
            return TRUE;
        }
        else if (HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == IDC_ATTACH)
        {
            IAttachTreeSelection();
            return TRUE;
        }
        else if (HIWORD(wParam) == EN_KILLFOCUS && LOWORD(wParam) == IDC_COMMENTS)
        {
            IGetComment();
            return TRUE;
        }
        // "Refresh" menu item
        else if (HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == ID_REFRESH)
        {
            IRefreshTree();
            return TRUE;
        }
        // "Remove unused components" menu item
        else if (HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == ID_REMOVE_UNUSED)
        {
            IRemoveUnusedComps();
            return TRUE;
        }
        // Item selected from 'New' menu
        else if (HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) >= MENU_ID_START)
        {
            ClassDesc *desc = plComponentMgr::Inst().Get(LOWORD(wParam)-MENU_ID_START);
            // If this is a component type (not a category)
            if (desc)
            {
                // Create an object of that type and a node to reference it
                Object *obj = (Object*)GetCOREInterface()->CreateInstance(desc->SuperClassID(), desc->ClassID());
                INode *node = GetCOREInterface()->CreateObjectNode(obj);

                plComponentBase *comp = (plComponentBase*)obj;
                node->Hide(!comp->AllowUnhide());
                node->Freeze(TRUE);

                // Add the new component to the tree
                HWND hTree = GetDlgItem(hDlg, IDC_TREE);
                HTREEITEM item = IAddComponent(hTree, (plMaxNode*)node);
                TreeView_SelectItem(hTree, item);
                TreeView_EnsureVisible(hTree, item);
            }
        }
        break;

    case WM_NOTIFY:
        NMHDR *nmhdr = (NMHDR*)lParam;
        if (nmhdr->idFrom == IDC_TREE)
        {
            switch (nmhdr->code)
            {
            case TVN_SELCHANGED:
                {
                    NMTREEVIEW *tv = (NMTREEVIEW*)lParam;

                    IGetComment();

                    bool isComponent = IIsComponent(tv->itemNew.lParam);

                    // If the new selection is a component, enable the attach button and comment field
                    EnableWindow(GetDlgItem(hDlg, IDC_ATTACH), isComponent);
                    SendDlgItemMessage(hDlg, IDC_COMMENTS, EM_SETREADONLY, !isComponent, 0);

                    if (isComponent)
                    {
                        fCommentNode = (plMaxNode*)tv->itemNew.lParam;
                        
                        TSTR buf;
                        fCommentNode->GetUserPropBuffer(buf);
                        SetDlgItemText(hDlg, IDC_COMMENTS, buf);
                    }
                    else
                    {
                        fCommentNode = nil;
                        SetDlgItemText(hDlg, IDC_COMMENTS, "");
                    }

                    return TRUE;
                }
                break;

            case TVN_BEGINLABELEDIT:
                // If this isn't a component, don't allow the edit
                if (!IIsComponent(((NMTVDISPINFO*)lParam)->item.lParam))
                {
                    SetWindowLong(hDlg, DWL_MSGRESULT, TRUE);
                    return TRUE;
                }

                // The edit box this creates kills the focus on our window, causing
                // accelerators to be enabled.  Add an extra disable to counteract that.
                plMaxAccelerators::Disable();

                return TRUE;

            // Finishing changing the name of a component
            case TVN_ENDLABELEDIT:
                {
                    NMTVDISPINFO *di = (NMTVDISPINFO*)lParam;
                    char* text = di->item.pszText;
                    // If the name was changed...
                    if (text && *text != '\0')
                    {
                        // Update the name of the node
                        plMaxNode *node = IGetTreeSelection();
                        node->SetName(text);

                        // Update the name in the panel too
                        if (plComponentUtil::Instance().IsOpen())
                            plComponentUtil::Instance().IUpdateNodeName(node);

                        // Make sure Max knows the file was changed
                        SetSaveRequiredFlag();

                        // Return true to keep the changes
                        SetWindowLong(hDlg, DWL_MSGRESULT, TRUE);
                    }

                    plMaxAccelerators::Enable();
                }
                return TRUE;

            // User double-clicked.  Select the objects the selected component is attached to.
            case NM_DBLCLK:
                ISelectTreeSelection();
                return TRUE;

            case NM_RCLICK:
                IOpenRightClickMenu();
                return TRUE;
                
            case TVN_KEYDOWN:
                // User pressed delete
                if (((NMTVKEYDOWN*)lParam)->wVKey == VK_DELETE)
                {
                    IDeleteComponent(IGetTreeSelection());
                    return TRUE;
                }
                break;
            }
        }
        break;
    }

    return FALSE;
}

HTREEITEM plComponentDlg::IAddLeaf(HWND hTree, HTREEITEM hParent, const char *text, LPARAM lParam)
{
    TVITEM tvi = {0};
    tvi.mask       = TVIF_TEXT | TVIF_PARAM;
    tvi.pszText    = (char*)text;
    tvi.cchTextMax = strlen(text);  
    tvi.lParam     = lParam;

    TVINSERTSTRUCT tvins = {0};
    tvins.item         = tvi;
    tvins.hParent      = hParent;
    tvins.hInsertAfter = TVI_SORT;

    return TreeView_InsertItem(hTree, &tvins);
}

HTREEITEM plComponentDlg::IFindTreeItem(HWND hTree, const char *name, HTREEITEM hParent)
{
    HTREEITEM hChild = TreeView_GetChild(hTree, hParent);

    while (hChild)
    {
        char buf[256];
        TVITEM tvi;
        tvi.mask  = TVIF_TEXT;
        tvi.hItem = hChild;
        tvi.pszText = buf;
        tvi.cchTextMax = sizeof(buf);
        TreeView_GetItem(hTree, &tvi);

        if (!strcmp(name, tvi.pszText))
            return hChild;

        hChild = TreeView_GetNextSibling(hTree, hChild);
    }

    return nil;
}

HTREEITEM plComponentDlg::IAddComponent(HWND hTree, plMaxNode *node)
{
    plComponentBase *comp = node->ConvertToComponent();

    // Try and find the component category in the tree
    const char *category = comp->GetCategory();
    HTREEITEM hCat = IFindTreeItem(hTree, category, TVI_ROOT);
    // If it isn't there yet, add it
    if (!hCat)
        hCat = IAddLeaf(hTree, TVI_ROOT, category, 0);

    // Try and find the component type in the tree
    int idx = plComponentMgr::Inst().FindClassID(comp->ClassID());
    HTREEITEM hType = ISearchTree(hTree, idx+1, hCat);
    if (!hType)
    {
        // If it isn't there yet, add it
        TSTR type;
        comp->GetClassName(type);

        if (IIsHidden(comp->ClassID()))
            type.Append(" (Hidden)");

        hType = IAddLeaf(hTree, hCat, type, idx+1);
    }

    // Add the name of this component to this type
    return IAddLeaf(hTree, hType, node->GetName(), (LPARAM)node);
}

void plComponentDlg::IAddComponentsRecur(HWND hTree, plMaxNode *node)
{
    if (node->IsComponent())
        IAddComponent(hTree, node);

    for (int i = 0; i < node->NumberOfChildren(); i++)
    {
        plMaxNode *child = (plMaxNode*)node->GetChildNode(i);
        IAddComponentsRecur(hTree, child);
    }
}

void plComponentDlg::ICreateMenu()
{
    // Add a refresh option to the system menu, for those rare cases where the manager gets out of sync
    HMENU hMenu = GetMenu(fhDlg);

    HMENU hNew = CreatePopupMenu();
    InsertMenu(hMenu, 0, MF_POPUP | MF_STRING | MF_BYPOSITION, (UINT)hNew, "New");

    const char *lastCat = nil;
    HMENU hCurType = nil;

    UInt32 count = plComponentMgr::Inst().Count();
    for (UInt32 i = 0; i < count; i++)
    {
        plComponentClassDesc *desc = (plComponentClassDesc*)plComponentMgr::Inst().Get(i);

        // Don't put in the create menu if obsolete
        if (desc->IsObsolete())
            continue;

        if (!lastCat || strcmp(lastCat, desc->Category()))
        {
            lastCat = desc->Category();

            hCurType = CreatePopupMenu();
            AppendMenu(hNew, MF_POPUP | MF_STRING, (UINT)hCurType, lastCat);
        }

        AppendMenu(hCurType, MF_STRING, MENU_ID_START+i, desc->ClassName());
    }
}

// Taking advantage of the fact that the node pointers we store in the lParam
// will certainly be higher than the number of component types
bool plComponentDlg::IIsComponent(LPARAM lParam)
{
    return (lParam > plComponentMgr::Inst().Count()+1);
}

bool plComponentDlg::IIsType(LPARAM lParam)
{
    return (lParam > 0 && lParam <= plComponentMgr::Inst().Count()+1);
}

void plComponentDlg::IAttachTreeSelection()
{
    HWND hTree = GetDlgItem(fhDlg, IDC_TREE);

    // Get the current selection from the tree
    HTREEITEM hSelected = TreeView_GetSelection(hTree);
    TVITEM item;
    item.mask = TVIF_PARAM;
    item.hItem = hSelected;
    TreeView_GetItem(hTree, &item);

    // If the item has a lParam it is a component
    if (IIsComponent(item.lParam))
    {
        plMaxNode *node = (plMaxNode*)item.lParam;
        plComponentBase *comp = node->ConvertToComponent();

        // Add each of the selected nodes that is not a component to the targets list
        int count = fInterface->GetSelNodeCount();
        for (int i = 0; i < count; i++)
        {
            plMaxNode *target = (plMaxNode*)fInterface->GetSelNode(i);
            if (!target->IsComponent())
                comp->AddTarget(target);
        }

        // Update the rollups to reflect the new component
        if (plComponentUtil::Instance().IsOpen())
            plComponentUtil::Instance().IUpdateRollups();
    }
}

// Wow, this INodeTab class is very thorough
bool FindNodeInTab(INode *node, INodeTab& nodes)
{
    for (int i = 0; i < nodes.Count(); i++)
    {
        if (node == nodes[i])
            return true;
    }

    return false;
}

void plComponentDlg::SelectComponentTargs(INodeTab& nodes)
{
    // Make an INode tab with all the targets in it
    INodeTab targets;
    for (int i = 0; i < nodes.Count(); i++)
    {
        plComponentBase *comp = ((plMaxNode*)nodes[i])->ConvertToComponent();

        for (int j = 0; j < comp->NumTargets(); j++)
        {
            INode *node = comp->GetTarget(j);
            if (node && !FindNodeInTab(node, targets))
                targets.Append(1, &node);
        }
    }

    // If the user is selecting a single component, make sure it is selected in the rollup too
    if (plComponentUtil::Instance().IsOpen() && nodes.Count() == 1)
        plComponentUtil::Instance().fLastComponent = ((plMaxNode*)nodes[0])->ConvertToComponent();
    
    theHold.Begin();
    fInterface->RedrawViews(fInterface->GetTime(), REDRAW_BEGIN);
    fInterface->ClearNodeSelection(FALSE);              // Deselect current nodes

    // If there is at least one valid target, select it
    if (targets.Count() > 0)
        fInterface->SelectNodeTab(targets, TRUE, FALSE);

    fInterface->RedrawViews(fInterface->GetTime(), REDRAW_END);
    theHold.Accept("Select");
}

void plComponentDlg::ISelectTreeSelection()
{
    INodeTab nodes;

    INode *curComponent = (INode*)IGetTreeSelection();
    if (curComponent)
    {
        nodes.Append(1, &curComponent);
    }
    else
    {
        HWND hTree = GetDlgItem(fhDlg, IDC_TREE);
        HTREEITEM hRoot = TreeView_GetSelection(hTree);

        IGetComponentsRecur(hTree, hRoot, nodes);
    }

    SelectComponentTargs(nodes);
}

void plComponentDlg::IGetComponentsRecur(HWND hTree, HTREEITEM hItem, INodeTab& nodes)
{
    if (hItem)
    {
        INode *node = (INode*)ITreeItemToNode(hTree, hItem);
        if (node)
            nodes.Append(1, &node);
        else
        {
            HTREEITEM hChild = TreeView_GetChild(hTree, hItem);
            IGetComponentsRecur(hTree, hChild, nodes);

            while (hChild = TreeView_GetNextSibling(hTree, hChild))
            {
                IGetComponentsRecur(hTree, hChild, nodes);
            }
        }
    }
}

void plComponentDlg::IDeleteComponent(plMaxNode *component)
{
    if (!component)
        return;

    // Make sure this components interface isn't showing
    if (plComponentUtil::Instance().IsOpen())
        plComponentUtil::Instance().IComponentPreDelete(component->ConvertToComponent());
    
    // Delete the component from the scene
    theHold.Begin();
    fInterface->DeleteNode(component);
    theHold.Accept(_T("Delete Component"));

    // Delete the component from the tree
    HWND hTree = GetDlgItem(fhDlg, IDC_TREE);
    HTREEITEM hItem = TreeView_GetSelection(hTree);
    HTREEITEM hParent = TreeView_GetParent(hTree, hItem);
    TreeView_DeleteItem(hTree, hItem);

    // If that was the only component of this type, delete the type too
    if (!TreeView_GetChild(hTree, hParent))
    {
        HTREEITEM hCategory = TreeView_GetParent(hTree, hParent);
        TreeView_DeleteItem(hTree, hParent);

        // If this is the only type in this category, delete the category too!
        // Sadly, this is the most we can delete.
        if (!TreeView_GetChild(hTree, hCategory))
            TreeView_DeleteItem(hTree, hCategory);
    }

    // Update the rollups in case the selected object had this component attached
    if (plComponentUtil::Instance().IsOpen())
        plComponentUtil::Instance().IUpdateRollups();
}

plMaxNode *plComponentDlg::IGetTreeSelection()
{
    HWND hTree = GetDlgItem(fhDlg, IDC_TREE);

    HTREEITEM hItem = TreeView_GetSelection(hTree);
    return ITreeItemToNode(hTree, hItem);
}

plMaxNode *plComponentDlg::ITreeItemToNode(HWND hTree, HTREEITEM hItem)
{
    if (hItem)
    {
        TVITEM item;
        item.mask = TVIF_PARAM;
        item.hItem = hItem;
        TreeView_GetItem(hTree, &item);

        if (IIsComponent(item.lParam))
            return (plMaxNode*)item.lParam;
    }

    return nil;
}

enum
{
    // Comp menu
    kMenuDelete = 1,
    kMenuRename,
    kMenuCopy,

    // Type menu
    kMenuHide
};

void plComponentDlg::ICreateRightClickMenu()
{
    fCompMenu = CreatePopupMenu();
    AppendMenu(fCompMenu, MF_STRING, kMenuDelete, "Delete");
    AppendMenu(fCompMenu, MF_STRING, kMenuRename, "Rename");
    AppendMenu(fCompMenu, MF_STRING, kMenuCopy, "Copy");

    fTypeMenu = CreatePopupMenu();
    AppendMenu(fTypeMenu, MF_STRING, kMenuHide, "Hide/Show");
}

void plComponentDlg::IOpenRightClickMenu()
{
    HWND hTree = GetDlgItem(fhDlg, IDC_TREE);

    // Get the position of the cursor in screen and tree client coords
    POINT point, localPoint;
    GetCursorPos(&point);
    localPoint = point;
    ScreenToClient(hTree, &localPoint);

    // Check if there is a tree item at that point
    TVHITTESTINFO hitTest;
    hitTest.pt = localPoint;
    TreeView_HitTest(hTree, &hitTest);
    if (!(hitTest.flags & TVHT_ONITEMLABEL))
        return;

    // Check if the tree item has an lParam (is a component)
    TVITEM item;
    item.mask = TVIF_PARAM;
    item.hItem = hitTest.hItem;
    TreeView_GetItem(hTree, &item);

    HMENU menu = nil;
    if (IIsComponent(item.lParam))
        menu = fCompMenu;
    else if (IIsType(item.lParam))
        menu = fTypeMenu;
    else
        return;

    // Select the item we're working with, so the user isn't confused
    TreeView_SelectItem(hTree, item.hItem);

    // Create the popup menu and get the option the user selects
    SetForegroundWindow(fhDlg);
    int sel = TrackPopupMenu(menu, TPM_NONOTIFY | TPM_RETURNCMD, point.x, point.y, 0, fhDlg, NULL);
    switch(sel)
    {
    case kMenuDelete:
        IDeleteComponent((plMaxNode*)item.lParam);
        break;

    case kMenuRename:
        TreeView_EditLabel(hTree, hitTest.hItem);
        break;

    case kMenuCopy:
        {
            // Component to copy
            INode *node = (INode*)item.lParam;
            INodeTab tab;
            tab.Append(1, &node);

            // Copy
            INodeTab copy;

            // Make the copy
            fInterface->CloneNodes(tab, Point3(0,0,0), true, NODE_COPY, NULL, &copy);

            // Delete the targets for the copy and add it to the tree
            plMaxNode *newNode = (plMaxNode*)copy[0];
            newNode->ConvertToComponent()->DeleteAllTargets();
            HTREEITEM hItem = IAddComponent(GetDlgItem(fhDlg, IDC_TREE), newNode);
            TreeView_SelectItem(GetDlgItem(fhDlg, IDC_TREE), hItem);
        }
        break;

    case kMenuHide:
        {
            ClassDesc *desc = plComponentMgr::Inst().Get(item.lParam-1);

            std::vector<Class_ID>::iterator it;
            it = std::find(fHiddenComps.begin(), fHiddenComps.end(), desc->ClassID());

            TSTR name = desc->ClassName();
            if (it == fHiddenComps.end())
            {
                fHiddenComps.push_back(desc->ClassID());
                name.Append(" (Hidden)");
            }
            else
                fHiddenComps.erase(it);

            item.mask = TVIF_TEXT;
            item.pszText = name;
            TreeView_SetItem(GetDlgItem(fhDlg, IDC_TREE), &item);

            plComponentUtil::Instance().IUpdateRollups();
        }
        break;
    }

    PostMessage(fhDlg, WM_USER, 0, 0);
}

HTREEITEM plComponentDlg::ISearchTree(HWND hTree, LPARAM lParam, HTREEITEM hCur)
{
    // Get the param for the current item
    TVITEM tvi;
    tvi.mask  = TVIF_PARAM;
    tvi.hItem = hCur;
    TreeView_GetItem(hTree, &tvi);

    // If the lParam matches the one searching for, return the handle
    if (tvi.lParam == lParam)
        return hCur;

    // Do a recursive search on the items children
    HTREEITEM hChild = TreeView_GetChild(hTree, hCur);
    while (hChild)
    {
        HTREEITEM hResult = ISearchTree(hTree, lParam, hChild);
        if (hResult)
            return hResult;

        hChild = TreeView_GetNextSibling(hTree, hChild);
    }

    return NULL;
}

void plComponentDlg::IRefreshTree()
{
    if (fhDlg)
    {
        fCommentNode = nil;

        HWND hTree = GetDlgItem(fhDlg, IDC_TREE);
        TreeView_DeleteAllItems(hTree);
        IAddComponentsRecur(hTree, (plMaxNode*)GetCOREInterface()->GetRootNode());
    }
}

void plComponentDlg::INotify(void *param, NotifyInfo *info)
{
    if (info->intcode == NOTIFY_SYSTEM_SHUTDOWN)
    {
        UnRegisterNotification(INotify, 0, NOTIFY_FILE_PRE_OPEN);
        UnRegisterNotification(INotify, 0, NOTIFY_SYSTEM_PRE_NEW);
        UnRegisterNotification(INotify, 0, NOTIFY_SYSTEM_PRE_RESET);
        UnRegisterNotification(INotify, 0, NOTIFY_FILE_PRE_MERGE);
        UnRegisterNotification(INotify, 0, NOTIFY_PRE_IMPORT);
        UnRegisterNotification(INotify, 0, NOTIFY_FILE_PRE_SAVE);
        UnRegisterNotification(INotify, 0, NOTIFY_FILE_PRE_SAVE_OLD);

        UnRegisterNotification(INotify, 0, NOTIFY_FILE_POST_OPEN);
        UnRegisterNotification(INotify, 0, NOTIFY_SYSTEM_POST_NEW);
        UnRegisterNotification(INotify, 0, NOTIFY_SYSTEM_POST_RESET);
        UnRegisterNotification(INotify, 0, NOTIFY_FILE_POST_MERGE);
        UnRegisterNotification(INotify, 0, NOTIFY_POST_IMPORT);

        UnRegisterNotification(INotify, 0, NOTIFY_SYSTEM_SHUTDOWN);
    }
    // New nodes are coming in, refresh the scene component list
    else if (info->intcode == NOTIFY_FILE_POST_OPEN ||
             info->intcode == NOTIFY_SYSTEM_POST_NEW ||
             info->intcode == NOTIFY_SYSTEM_POST_RESET ||
             info->intcode == NOTIFY_FILE_POST_MERGE ||
             info->intcode == NOTIFY_POST_IMPORT)
    {
        Instance().IRefreshTree();
    }
    // Nodes may be going away, save the comment now
    else if (info->intcode == NOTIFY_FILE_PRE_OPEN ||
             info->intcode == NOTIFY_SYSTEM_PRE_NEW ||
             info->intcode == NOTIFY_SYSTEM_PRE_RESET ||
             info->intcode == NOTIFY_FILE_PRE_MERGE ||
             info->intcode == NOTIFY_PRE_IMPORT ||
             info->intcode == NOTIFY_FILE_PRE_SAVE ||
             info->intcode == NOTIFY_FILE_PRE_SAVE_OLD)
    {
        // This is causing a crash, so for now if you add a comment and don't
        // pick another component or close the manager before closing the file,
        // you lose the comment -Colin
//      Instance().IGetComment();
    }
}

void plComponentDlg::IUpdateNodeName(plMaxNode *node)
{
    if (!fhDlg)
        return;

    // Update the name in the tree too
    HWND hTree = GetDlgItem(fhDlg, IDC_TREE);
    TVITEM tvi = {0};
    tvi.hItem = ISearchTree(hTree, (LPARAM)node);
    tvi.mask = TVIF_TEXT;
    tvi.pszText = node->GetName();
    TreeView_SetItem(hTree, &tvi);
}

void FindUnusedCompsRecur(plMaxNode *node, std::vector<plMaxNode*>& unused)
{
    plComponentBase *comp = node->ConvertToComponent();
    if (comp)
    {
        bool isAttached = false;

        int num = comp->NumTargets();
        for (int i = 0; i < num; i++)
        {
            if (comp->GetTarget(i))
            {
                isAttached = true;
                break;
            }
        }

        if (!isAttached)
            unused.push_back(node);
    }
    
    for (int i = 0; i < node->NumberOfChildren(); i++)
        FindUnusedCompsRecur((plMaxNode*)node->GetChildNode(i), unused);
}

void plComponentDlg::IRemoveUnusedComps()
{
    std::vector<plMaxNode*> unused;
    FindUnusedCompsRecur((plMaxNode*)GetCOREInterface()->GetRootNode(), unused);

    for (int i = 0; i < unused.size(); i++)
        GetCOREInterface()->DeleteNode(unused[i], FALSE);

    IRefreshTree();
}

bool plComponentDlg::IIsHidden(Class_ID& cid)
{
    return (std::find(fHiddenComps.begin(), fHiddenComps.end(), cid) != fHiddenComps.end());
}

////////////////////////////////////////////////////////////////////////////////

#include "hsUtils.h"

class plCopyCompCallback : public HitByNameDlgCallback
{
protected:
    Tab<plComponentBase*> fSharedComps;
    INodeTab fSelectedNodes;

public:
    bool GetComponents()
    {
        fSelectedNodes.ZeroCount();
        fSharedComps.ZeroCount();

        Interface *ip = GetCOREInterface();

        int nodeCount = ip->GetSelNodeCount();
        if (nodeCount == 0)
            return false;
        
        // Get the components shared among the selected nodes
        int i;
        fSelectedNodes.SetCount(nodeCount);
        for (i = 0; i < nodeCount; i++)
            fSelectedNodes[i] = ip->GetSelNode(i);

        INodeTab sharedComps;
        if (plSharedComponents(fSelectedNodes, sharedComps) == 0)
            return false;

        // Put the shared components in a list
        fSharedComps.SetCount(sharedComps.Count());
        for (i = 0; i < sharedComps.Count(); i++)
            fSharedComps[i] = ((plMaxNode*)sharedComps[i])->ConvertToComponent();

        return true;
    }

    virtual TCHAR *dialogTitle() { return "Select Nodes"; }
    virtual TCHAR *buttonText() { return "Copy"; }

    virtual int filter(INode *node)
    {
        // Make sure this node doesn't already have the components
        for (int i = 0; i < fSelectedNodes.Count(); i++)
        {
            if (fSelectedNodes[i] == node)
                return FALSE;
        }

        return TRUE;
    }

    virtual void proc(INodeTab &nodeTab)
    {
        for (int i = 0; i < nodeTab.Count(); i++)
        {
            for (int j = 0; j < fSharedComps.Count(); j++)
            {
                fSharedComps[j]->AddTarget((plMaxNodeBase*)nodeTab[i]);
            }
        }
    }
};
static plCopyCompCallback copyCompCallback;

void CopyComponents()
{
    if (copyCompCallback.GetComponents())
        GetCOREInterface()->DoHitByNameDialog(&copyCompCallback);
    else
    {
        int count = GetCOREInterface()->GetSelNodeCount();
        if (count == 0)
            hsMessageBox("No object(s) selected", "Component Copy", hsMessageBoxNormal);
        else if (count > 1)
            hsMessageBox("No components are shared among the selected objects", "Component Copy", hsMessageBoxNormal);
        else
            hsMessageBox("No components on the selected object", "Component Copy", hsMessageBoxNormal);
    }
}