/*++

Copyright (c) 1992-2001  Microsoft Corporation

Module Name:

    callswin.cpp

Abstract:

    This module contains the main line code for display of calls window.

Environment:

    Win32, User Mode

--*/


#include "precomp.hxx"
#pragma hdrstop

#define MIN_FRAMES 10
#define MORE_LESS 10

#define CALLS_CONTEXT_ID_BASE 0x100

#define TBB_MORE    9
#define TBB_LESS    10

// The IDs of the buttons are the bit shift of the
// corresponding flag.
TBBUTTON g_CallsTbButtons[] =
{
    TEXT_TB_BTN(0, "Args", BTNS_CHECK),
    TEXT_TB_BTN(1, "Func info", BTNS_CHECK),
    TEXT_TB_BTN(2, "Source", BTNS_CHECK),
    TEXT_TB_BTN(3, "Addrs", BTNS_CHECK),
    TEXT_TB_BTN(4, "Headings", BTNS_CHECK),
    TEXT_TB_BTN(5, "Nonvolatile regs", BTNS_CHECK),
    TEXT_TB_BTN(6, "Frame nums", BTNS_CHECK),
    TEXT_TB_BTN(7, "Arg types", BTNS_CHECK),
    SEP_TB_BTN(),
    TEXT_TB_BTN(TBB_MORE, "More", 0),
    TEXT_TB_BTN(TBB_LESS, "Less", 0),
    SEP_TB_BTN(),
    TEXT_TB_BTN(ID_SHOW_TOOLBAR, "Toolbar", 0),
};

#define NUM_CALLS_MENU_BUTTONS \
    (sizeof(g_CallsTbButtons) / sizeof(g_CallsTbButtons[0]))
#define NUM_CALLS_TB_BUTTONS \
    (NUM_CALLS_MENU_BUTTONS - 2)

HMENU CALLSWIN_DATA::s_ContextMenu;

//
//
//
CALLSWIN_DATA::CALLSWIN_DATA()
    : SINGLE_CHILDWIN_DATA(1024)
{
    m_enumType = CALLS_WINDOW;
    m_Flags = 0;
    m_Frames = 20;
    m_FramesFound = 0;
    m_TextOffset = 0;
}

void
CALLSWIN_DATA::Validate()
{
    SINGLE_CHILDWIN_DATA::Validate();

    Assert(CALLS_WINDOW == m_enumType);
}

HRESULT
CALLSWIN_DATA::ReadState(void)
{
    HRESULT Status;

    Empty();
    
    //
    // Record the raw frame data first.
    //

    // Preallocate space to record the raw frames.
    if (AddData(sizeof(DEBUG_STACK_FRAME) * m_Frames) == NULL)
    {
        return E_OUTOFMEMORY;
    }

    // Allocate a separate buffer to hold the frames while
    // calling OutputStackTrace on them.  We can't just pass
    // in the state buffer pointer as resizing of the state
    // buffer may cause the data pointer to change.
    PDEBUG_STACK_FRAME RawFrames = (PDEBUG_STACK_FRAME)malloc(m_DataUsed);
    if (RawFrames == NULL)
    {
        return E_OUTOFMEMORY;
    }

    Status = g_pDbgControl->GetStackTrace(0, 0, 0, RawFrames, m_Frames,
                                          &m_FramesFound);
    if (Status != S_OK)
    {
        free(RawFrames);
        return Status;
    }
    
    m_TextOffset = m_DataUsed;
    
    g_OutStateBuf.SetBuffer(this);
    if ((Status = g_OutStateBuf.Start(FALSE)) != S_OK)
    {
        return Status;
    }

    // If nonvolatile registers were requested we can't use just
    // our saved frames as they require full context information.
    Status = g_pOutCapControl->
        OutputStackTrace(DEBUG_OUTCTL_THIS_CLIENT |
                         DEBUG_OUTCTL_OVERRIDE_MASK |
                         DEBUG_OUTCTL_NOT_LOGGED,
                         (m_Flags & DEBUG_STACK_NONVOLATILE_REGISTERS) ?
                         NULL : RawFrames, m_FramesFound, m_Flags);
    if (Status == S_OK)
    {
        Status = g_OutStateBuf.End(TRUE);
        if (Status == S_OK)
        {
            g_OutStateBuf.ReplaceChar('\n', 0);
        }
    }
    else
    {
        g_OutStateBuf.End(TRUE);
    }

    // Now that the state buffer is stable put the raw frame
    // data in.
    memcpy(m_Data, RawFrames, m_TextOffset);
    free(RawFrames);
    
    return Status;
}

void
CALLSWIN_DATA::Copy()
{
    LRESULT Line = SendMessage(m_hwndChild, LB_GETCURSEL, 0, 0);
    Assert(Line != LB_ERR);

    LRESULT Len = SendMessage(m_hwndChild, LB_GETTEXTLEN, Line, 0);
    if (Len <= 0)
    {
        return;
    }

    Len++;
    
    PSTR Text = (PSTR)malloc(Len);

    if (!Text) 
    {
        return;
    }
    SendMessage(m_hwndChild, LB_GETTEXT, Line, (LPARAM)Text);
    Text[Len - 1] = 0;

    CopyToClipboard(Text);

    free (Text);
    return;

}

HMENU
CALLSWIN_DATA::GetContextMenu(void)
{
    ULONG i;
    
    //
    // We only keep one menu around for all call windows
    // so apply the menu check state for this particular
    // window.
    // In reality there's only one calls window anyway,
    // but this is a good example of how to handle
    // multi-instance windows.
    //

    for (i = 0; i < NUM_CALLS_TB_BUTTONS; i++)
    {
        CheckMenuItem(s_ContextMenu, i + CALLS_CONTEXT_ID_BASE,
                      MF_BYCOMMAND | ((m_Flags & (1 << i)) ? MF_CHECKED : 0));
    }
    CheckMenuItem(s_ContextMenu, ID_SHOW_TOOLBAR + CALLS_CONTEXT_ID_BASE,
                  MF_BYCOMMAND | (m_ShowToolbar ? MF_CHECKED : 0));
    
    return s_ContextMenu;
}

void
CALLSWIN_DATA::OnContextMenuSelection(UINT Item)
{
    Item -= CALLS_CONTEXT_ID_BASE;

    switch(Item)
    {
    case TBB_MORE:
        m_Frames += MORE_LESS;
        break;
    case TBB_LESS:
        if (m_Frames >= MIN_FRAMES + MORE_LESS)
        {
            m_Frames -= MORE_LESS;
        }
        break;
    case ID_SHOW_TOOLBAR:
        SetShowToolbar(!m_ShowToolbar);
        break;
    default:
        m_Flags ^= 1 << Item;
        SyncUiWithFlags(1 << Item);
        break;
    }
    if (g_Workspace != NULL)
    {
        g_Workspace->AddDirty(WSPF_DIRTY_WINDOWS);
    }
    UiRequestRead();
}

BOOL
CALLSWIN_DATA::CodeExprAtCaret(PSTR Expr, PULONG64 Offset)
{
    BOOL Succ = FALSE;
    LRESULT Line = SendMessage(m_hwndChild, LB_GETCURSEL, 0, 0);

    // If the column headers are on ignore that line.
    if (m_Flags & DEBUG_STACK_COLUMN_NAMES)
    {
        if (Line == 0)
        {
            return Succ;
        }
        else
        {
            Line--;
        }
    }
    
    if (Line >= 0 && (ULONG)Line < m_FramesFound)
    {
        if (UiLockForRead() == S_OK)
        {
            PDEBUG_STACK_FRAME RawFrames = (PDEBUG_STACK_FRAME)m_Data;
            if (Expr != NULL)
            {
                sprintf(Expr, "0x%I64x", RawFrames[Line].InstructionOffset);
            }
            if (Offset != NULL)
            {
                *Offset = RawFrames[Line].InstructionOffset;
            }
            Succ = TRUE;
            UnlockStateBuffer(this);
        }
    }
    
    return Succ;
}

HRESULT
CALLSWIN_DATA::
StackFrameAtCaret(PDEBUG_STACK_FRAME pFrame)
{
    LRESULT Line = SendMessage(m_hwndChild, LB_GETCURSEL, 0, 0);

    // If the column headers are on ignore that line.
    if (m_Flags & DEBUG_STACK_COLUMN_NAMES)
    {
        if (Line == 0)
        {
            return E_INVALIDARG;
        }
        else
        {
            Line--;
        }
    }
    
    if (Line >= 0 && (ULONG)Line < m_FramesFound && pFrame)
    {
        if (UiLockForRead() == S_OK)
        {
            PDEBUG_STACK_FRAME RawFrames = (PDEBUG_STACK_FRAME)m_Data;
            *pFrame = RawFrames[Line];
            UnlockStateBuffer(this);
        }
    }
    
    return S_OK;
}

BOOL
CALLSWIN_DATA::OnCreate(void)
{
    if (s_ContextMenu == NULL)
    {
        s_ContextMenu = CreateContextMenuFromToolbarButtons
            (NUM_CALLS_MENU_BUTTONS, g_CallsTbButtons, CALLS_CONTEXT_ID_BASE);
        if (s_ContextMenu == NULL)
        {
            return FALSE;
        }
    }
    
    m_Toolbar = CreateWindowEx(0, TOOLBARCLASSNAME, NULL,
                               WS_CHILD | WS_VISIBLE |
                               TBSTYLE_WRAPABLE | TBSTYLE_LIST | CCS_TOP,
                               0, 0, m_Size.cx, 0, m_Win, (HMENU)ID_TOOLBAR,
                               g_hInst, NULL);
    if (m_Toolbar == NULL)
    {
        return FALSE;
    }
    SendMessage(m_Toolbar, TB_BUTTONSTRUCTSIZE, sizeof(TBBUTTON), 0);
    SendMessage(m_Toolbar, TB_ADDBUTTONS, NUM_CALLS_TB_BUTTONS,
                (LPARAM)&g_CallsTbButtons);
    SendMessage(m_Toolbar, TB_AUTOSIZE, 0, 0);
    
    RECT Rect;
    GetClientRect(m_Toolbar, &Rect);
    m_ToolbarHeight = Rect.bottom - Rect.top;
    m_ShowToolbar = TRUE;

    m_hwndChild = CreateWindowEx(
        WS_EX_CLIENTEDGE,                           // Extended style
        _T("LISTBOX"),                              // class name
        NULL,                                       // title
        WS_CHILD | WS_VISIBLE
        | WS_MAXIMIZE
        | WS_HSCROLL | WS_VSCROLL
        | LBS_NOTIFY | LBS_WANTKEYBOARDINPUT
        | LBS_NOINTEGRALHEIGHT,                     // style
        0,                                          // x
        m_ToolbarHeight,                            // y
        m_Size.cx,                                  // width
        m_Size.cy - m_ToolbarHeight,                // height
        m_Win,                                      // parent
        0,                                          // control id
        g_hInst,                                    // hInstance
        NULL                                        // user defined data
        );

    if (m_hwndChild != NULL)
    {
        SetFont( FONT_FIXED );
        return TRUE;
    }
    else
    {
        return FALSE;
    }
}

LRESULT
CALLSWIN_DATA::OnCommand(
    WPARAM wParam,
    LPARAM lParam
    )
{
    if (HIWORD(wParam) == LBN_DBLCLK)
    {
        ULONG64 Offset;
        if (CodeExprAtCaret(NULL, &Offset) &&
            Offset != DEBUG_INVALID_OFFSET)
        {
            UIC_DISPLAY_CODE_DATA* DispCode =
                StartStructCommand(UIC_DISPLAY_CODE);
            if (DispCode != NULL)
            {
                DispCode->Offset = Offset;
                FinishCommand();
            }
        }

        DEBUG_STACK_FRAME StkFrame;
        if (StackFrameAtCaret(&StkFrame) == S_OK)
        {
            UIC_SET_SCOPE_DATA* SetScope =
                StartStructCommand(UIC_SET_SCOPE);
            if (SetScope != NULL)
            {
                SetScope->StackFrame = StkFrame;
                FinishCommand();
            }
        }
        return 0;
    }

    if ((HWND)lParam == m_Toolbar)
    {
        OnContextMenuSelection(LOWORD(wParam) + CALLS_CONTEXT_ID_BASE);
        return 0;
    }
    
    return 0;
}

LRESULT
CALLSWIN_DATA::OnVKeyToItem(
    WPARAM wParam,
    LPARAM lParam
    )
{
    if (LOWORD(wParam) == VK_RETURN)
    {
        ULONG64 Offset;
        if (CodeExprAtCaret(NULL, &Offset) &&
            Offset != DEBUG_INVALID_OFFSET)
        {
            UIC_DISPLAY_CODE_DATA* DispCode =
                StartStructCommand(UIC_DISPLAY_CODE);
            if (DispCode != NULL)
            {
                DispCode->Offset = Offset;
                FinishCommand();
            }
        }
        DEBUG_STACK_FRAME StkFrame;
        if (StackFrameAtCaret(&StkFrame) == S_OK)
        {
            UIC_SET_SCOPE_DATA* SetScope =
                StartStructCommand(UIC_SET_SCOPE);
            if (SetScope != NULL)
            {
                SetScope->StackFrame = StkFrame;
                FinishCommand();
            }
        }

    }
    else if (_T('G') == LOWORD(wParam))
    {
        ULONG64 Offset;
        if (CodeExprAtCaret(NULL, &Offset) &&
            Offset != DEBUG_INVALID_OFFSET)
        {
            PrintStringCommand(UIC_EXECUTE, "g 0x%I64x", Offset);
        }
    }
    else if (_T('R') == LOWORD(wParam))
    {
        OnUpdate(UPDATE_BUFFER);
    }
    else
    {
        // Default behavior.
        return -1;
    }

    // Keystroke processed.
    return -2;
}

void
CALLSWIN_DATA::OnUpdate(
    UpdateType Type
    )
{
    if (Type != UPDATE_BUFFER)
    {
        return;
    }
    
    LRESULT lbItem;
    int     nFrameCount;
    HRESULT Status;

    lbItem = SendMessage( m_hwndChild, LB_GETCURSEL, 0, 0 );
    
    SendMessage( m_hwndChild, WM_SETREDRAW, FALSE, 0L );
    SendMessage( m_hwndChild, LB_RESETCONTENT, 0, 0 );

    Status = UiLockForRead();
    if (Status == S_OK)
    {
        PSTR Buf = (PSTR)m_Data + m_TextOffset;
        // Ignore final terminator.
        PSTR End = (PSTR)m_Data + m_DataUsed - 1;
        ULONG Width = 0;

        nFrameCount = 0;

        while (Buf < End)
        {
            ULONG Len = strlen(Buf) + 1;
            ULONG StrWidth = (Len - 1) * m_Font->Metrics.tmAveCharWidth;
            SendMessage(m_hwndChild, LB_ADDSTRING, 0, (LPARAM)Buf);
            Buf += Len;
            if (StrWidth > Width)
            {
                Width = StrWidth;
            }
            nFrameCount++;
        }

        SendMessage(m_hwndChild, LB_SETHORIZONTALEXTENT, Width, 0);
        UnlockStateBuffer(this);
    }
    else
    {
        SendLockStatusMessage(m_hwndChild, LB_ADDSTRING, Status);
        nFrameCount = 1;
    }

    SendMessage( m_hwndChild, LB_SETCURSEL,
                 (lbItem > nFrameCount) ? 0 : lbItem, 0 );
    SendMessage( m_hwndChild, WM_SETREDRAW, TRUE, 0L );
}

ULONG
CALLSWIN_DATA::GetWorkspaceSize(void)
{
    return SINGLE_CHILDWIN_DATA::GetWorkspaceSize() + 2 * sizeof(ULONG);
}

PUCHAR
CALLSWIN_DATA::SetWorkspace(PUCHAR Data)
{
    Data = SINGLE_CHILDWIN_DATA::SetWorkspace(Data);
    *(PULONG)Data = m_Flags;
    Data += sizeof(ULONG);
    *(PULONG)Data = m_Frames;
    Data += sizeof(ULONG);
    return Data;
}

PUCHAR
CALLSWIN_DATA::ApplyWorkspace1(PUCHAR Data, PUCHAR End)
{
    Data = SINGLE_CHILDWIN_DATA::ApplyWorkspace1(Data, End);

    if (End - Data >= 2 * sizeof(ULONG))
    {
        m_Flags = *(PULONG)Data;
        Data += sizeof(ULONG);
        m_Frames = *(PULONG)Data;
        Data += sizeof(ULONG);

        SyncUiWithFlags(0xffffffff);
        UiRequestRead();
    }

    return Data;
}

void
CALLSWIN_DATA::SyncUiWithFlags(ULONG Changed)
{
    ULONG i;

    //
    // Set toolbar button state from flags.
    //

    for (i = 0; i < NUM_CALLS_TB_BUTTONS; i++)
    {
        if (Changed & (1 << i))
        {
            SendMessage(m_Toolbar, TB_SETSTATE, g_CallsTbButtons[i].idCommand,
                        TBSTATE_ENABLED |
                        ((m_Flags & (1 << i)) ? TBSTATE_CHECKED : 0));
        }
    }
}