text: /*++
  
  Copyright (c) 1999-2002  Microsoft Corporation
  
  Module Name:
  
      cmnwin.cpp
  
  Abstract:
  
      This module contains the code for the common window architecture.
  
  --*/
  
  #include "precomp.hxx"
  #pragma hdrstop
  
  ULONG g_WinOptions = WOPT_AUTO_ARRANGE | WOPT_AUTO_DISASM;
  
  LIST_ENTRY g_ActiveWin;
  
  PCOMMONWIN_DATA g_IndexedWin[MAXVAL_WINDOW];
  HWND g_IndexedHwnd[MAXVAL_WINDOW];
  
  INDEXED_FONT g_Fonts[FONT_COUNT];
  
  BOOL g_LineMarkers = FALSE;
  
  #define CW_WSP_SIG3 '3WCW'
  
  //
  //
  //
  COMMONWIN_DATA::COMMONWIN_DATA(ULONG ChangeBy)
      : StateBuffer(ChangeBy)
  {
      m_Size.cx = 0;
      m_Size.cy = 0;
      m_CausedArrange = FALSE;
      // Creation is an automatic operation so
      // InAutoOp is initialized to a non-zero value.
      // After CreateWindow returns it is decremented.
      m_InAutoOp = 1;
      m_enumType = MINVAL_WINDOW;
      m_Font = &g_Fonts[FONT_FIXED];
      m_FontHeight = 0;
      m_LineHeight = 0;
      m_Toolbar = NULL;
      m_ShowToolbar = FALSE;
      m_ToolbarHeight = 0;
      m_MinToolbarWidth = 0;
      m_ToolbarEdit = NULL;
  }
  
  void
  COMMONWIN_DATA::Validate()
  {
      Assert(MINVAL_WINDOW < m_enumType);
      Assert(m_enumType < MAXVAL_WINDOW);
  }
  
  void 
  COMMONWIN_DATA::SetFont(ULONG FontIndex)
  {
      m_Font = &g_Fonts[FontIndex];
      m_FontHeight = m_Font->Metrics.tmHeight;
      m_LineHeight = m_Size.cy / m_FontHeight;
  }
  
  BOOL
  COMMONWIN_DATA::CanCopy()
  {
      if (GetFocus() == m_ToolbarEdit)
      {
          DWORD Start, End;
          SendMessage(m_ToolbarEdit, EM_GETSEL,
                      (WPARAM)&Start, (WPARAM)&End);
          return Start != End;
      }
      else
      {
          return FALSE;
      }
  }
  
  BOOL
  COMMONWIN_DATA::CanCut()
  {
      if (GetFocus() == m_ToolbarEdit)
      {
          DWORD Start, End;
          SendMessage(m_ToolbarEdit, EM_GETSEL,
                      (WPARAM)&Start, (WPARAM)&End);
          return Start != End;
      }
      else
      {
          return FALSE;
      }
  }
  
  BOOL
  COMMONWIN_DATA::CanPaste()
  {
      if (GetFocus() == m_ToolbarEdit)
      {
          return TRUE;
      }
      else
      {
          return FALSE;
      }
  }
  
  void
  COMMONWIN_DATA::Copy()
  {
      if (GetFocus() == m_ToolbarEdit)
      {
          SendMessage(m_ToolbarEdit, WM_COPY, 0, 0);
      }
  }
  
  void
  COMMONWIN_DATA::Cut()
  {
      if (GetFocus() == m_ToolbarEdit)
      {
          SendMessage(m_ToolbarEdit, WM_CUT, 0, 0);
      }
  }
  
  void
  COMMONWIN_DATA::Paste()
  {
      if (GetFocus() == m_ToolbarEdit)
      {
          SendMessage(m_ToolbarEdit, WM_PASTE, 0, 0);
      }
  }
  
  BOOL
  COMMONWIN_DATA::CanSelectAll()
  {
      return FALSE;
  }
  
  void
  COMMONWIN_DATA::SelectAll()
  {
  }
  
  BOOL
  COMMONWIN_DATA::SelectedText(PTSTR Buffer, ULONG BufferChars)
  {
      return FALSE;
  }
  
  BOOL 
  COMMONWIN_DATA::CanWriteTextToFile()
  {
      return FALSE;
  }
  
  HRESULT
  COMMONWIN_DATA::WriteTextToFile(HANDLE File)
  {
      return E_NOTIMPL;
  }
  
  BOOL 
  COMMONWIN_DATA::HasEditableProperties()
  {
      return FALSE;
  }
  
  BOOL 
  COMMONWIN_DATA::EditProperties()
  /*++
  Returns
      TRUE - If properties were edited
      FALSE - If nothing was changed
  --*/
  {
      return FALSE;
  }
  
  HMENU
  COMMONWIN_DATA::GetContextMenu(void)
  {
      return NULL;
  }
  
  void
  COMMONWIN_DATA::OnContextMenuSelection(UINT Item)
  {
      // Nothing to do.
  }
  
  BOOL
  COMMONWIN_DATA::CanGotoLine(void)
  {
      return FALSE;
  }
  
  void
  COMMONWIN_DATA::GotoLine(ULONG Line)
  {
      // Do nothing.
  }
  
  void
  COMMONWIN_DATA::Find(PTSTR Text, ULONG Flags, BOOL FromDlg)
  {
      // Do nothing.
  }
  
  HRESULT
  COMMONWIN_DATA::CodeExprAtCaret(PSTR Expr, ULONG ExprSize, PULONG64 Offset)
  {
      return E_NOINTERFACE;
  }
  
  void
  COMMONWIN_DATA::ToggleBpAtCaret(void)
  {
      char CodeExpr[MAX_OFFSET_EXPR];
      ULONG64 Offset;
      
      if (CodeExprAtCaret(CodeExpr, DIMA(CodeExpr), &Offset) != S_OK)
      {
          MessageBeep(0);
          ErrorBox(NULL, 0, ERR_No_Code_For_File_Line);
          return;
      }
  
      ULONG CurBpId = DEBUG_ANY_ID;
  
      // This doesn't work too well with duplicate
      // breakpoints, but that should be a minor problem.
      if (IsBpAtOffset(NULL, Offset, &CurBpId) != BP_NONE)
      {
          PrintStringCommand(UIC_SILENT_EXECUTE, "bc %d", CurBpId);
      }
      else
      {
          PrintStringCommand(UIC_SILENT_EXECUTE, "bp %s", CodeExpr);
      }
  }
  
  BOOL
  COMMONWIN_DATA::OnCreate(void)
  {
      return TRUE;
  }
  
  LRESULT
  COMMONWIN_DATA::OnCommand(WPARAM wParam, LPARAM lParam)
  {
      return 1;
  }
  
  void
  COMMONWIN_DATA::OnSetFocus(void)
  {
  }
  
  void
  COMMONWIN_DATA::OnSize(void)
  {
      RECT Rect;
      
      // Resize the toolbar.
      if (m_Toolbar != NULL && m_ShowToolbar)
      {
          // If the toolbar gets too small sometimes it's better
          // to just let it get clipped rather than have it
          // try to fit into a narrow column.
          if (m_Size.cx >= m_MinToolbarWidth)
          {
              MoveWindow(m_Toolbar, 0, 0, m_Size.cx, m_ToolbarHeight, TRUE);
          }
  
          // Record what size it ended up.
          GetClientRect(m_Toolbar, &Rect);
          m_ToolbarHeight = Rect.bottom - Rect.top;
  
          if (m_FontHeight != 0)
          {
              if (m_ToolbarHeight >= m_Size.cy)
              {
                  m_LineHeight = 0;
              }
              else
              {
                  m_LineHeight = (m_Size.cy - m_ToolbarHeight) / m_FontHeight;
              }
          }
      }
      else
      {
          Assert(m_ToolbarHeight == 0);
      }
  }
  
  void
  COMMONWIN_DATA::OnButtonDown(ULONG Button)
  {
  }
  
  void
  COMMONWIN_DATA::OnButtonUp(ULONG Button)
  {
  }
  
  void
  COMMONWIN_DATA::OnMouseMove(ULONG Modifiers, ULONG X, ULONG Y)
  {
  }
  
  void
  COMMONWIN_DATA::OnTimer(WPARAM TimerId)
  {
  }
  
  LRESULT
  COMMONWIN_DATA::OnGetMinMaxInfo(LPMINMAXINFO Info)
  {
      return 1;
  }
  
  LRESULT
  COMMONWIN_DATA::OnVKeyToItem(WPARAM wParam, LPARAM lParam)
  {
      return -1;
  }
  
  LRESULT
  COMMONWIN_DATA::OnNotify(WPARAM wParam, LPARAM lParam)
  {
      return 0;
  }
  
  void
  COMMONWIN_DATA::OnUpdate(UpdateType Type)
  {
  }
  
  void
  COMMONWIN_DATA::OnDestroy(void)
  {
  }
  
  LRESULT
  COMMONWIN_DATA::OnOwnerDraw(UINT uMsg, WPARAM wParam, LPARAM lParam)
  {
      return 0;
  }
  
  ULONG
  COMMONWIN_DATA::GetWorkspaceSize(void)
  {
      return 3 * sizeof(ULONG) + sizeof(WINDOWPLACEMENT);
  }
  
  PUCHAR
  COMMONWIN_DATA::SetWorkspace(PUCHAR Data)
  {
      // First store the special signature that marks
      // this version of the workspace data.
      *(PULONG)Data = CW_WSP_SIG3;
      Data += sizeof(ULONG);
  
      // Store the size saved by this layer.
      *(PULONG)Data = COMMONWIN_DATA::GetWorkspaceSize();
      Data += sizeof(ULONG);
  
      //
      // Store the actual data.
      //
  
      *(PULONG)Data = m_ShowToolbar;
      Data += sizeof(ULONG);
      
      LPWINDOWPLACEMENT Place = (LPWINDOWPLACEMENT)Data;
      Place->length = sizeof(WINDOWPLACEMENT);
      GetWindowPlacement(m_Win, Place);
      Data += sizeof(WINDOWPLACEMENT);
  
      return Data;
  }
  
  PUCHAR
  COMMONWIN_DATA::ApplyWorkspace1(PUCHAR Data, PUCHAR End)
  {
      ULONG_PTR Size = End - Data;
      
      // There are three versions of the base COMMONWIN data.
      // 1. RECT.
      // 2. WINDOWPLACEMENT.
      // 3. CW_WSP_SIG3 sized block.
      // All three cases can be easily distinguished.
  
      if (Size > 2 * sizeof(ULONG) &&
          *(PULONG)Data == CW_WSP_SIG3 &&
          Size >= *(PULONG)(Data + sizeof(ULONG)))
      {
          Size = *(PULONG)(Data + sizeof(ULONG)) - 2 * sizeof(ULONG);
          Data += 2 * sizeof(ULONG);
          
          if (Size >= sizeof(ULONG))
          {
              SetShowToolbar(*(PULONG)Data);
              Size -= sizeof(ULONG);
              Data += sizeof(ULONG);
          }
      }
  
      if (Size >= sizeof(WINDOWPLACEMENT) &&
          ((LPWINDOWPLACEMENT)Data)->length == sizeof(WINDOWPLACEMENT))
      {
          LPWINDOWPLACEMENT Place = (LPWINDOWPLACEMENT)Data;
  
          if (!IsAutoArranged(m_enumType))
          {
              SetWindowPlacement(m_Win, Place);
          }
          
          return (PUCHAR)(Place + 1);
      }
      else
      {
          LPRECT Rect = (LPRECT)Data;
          Assert((PUCHAR)(Rect + 1) <= End);
      
          if (!IsAutoArranged(m_enumType))
          {
              MoveWindow(m_Win, Rect->left, Rect->top,
                         (Rect->right - Rect->left), (Rect->bottom - Rect->top),
                         TRUE);
          }
      
          return (PUCHAR)(Rect + 1);
      }
  }
  
  void
  COMMONWIN_DATA::UpdateColors(void)
  {
      // Nothing to do.
  }
  
  void
  COMMONWIN_DATA::UpdateSize(ULONG Width, ULONG Height)
  {
      m_Size.cx = Width;
      m_Size.cy = Height;
      if (m_FontHeight != 0)
      {
          m_LineHeight = m_Size.cy / m_FontHeight;
      }
  }
  
  void
  COMMONWIN_DATA::SetShowToolbar(BOOL Show)
  {
      if (!m_Toolbar)
      {
          return;
      }
      
      m_ShowToolbar = Show;
      if (m_ShowToolbar)
      {
          ShowWindow(m_Toolbar, SW_SHOW);
      }
      else
      {
          ShowWindow(m_Toolbar, SW_HIDE);
          m_ToolbarHeight = 0;
      }
  
      OnSize();
      if (g_Workspace != NULL)
      {
          g_Workspace->AddDirty(WSPF_DIRTY_WINDOWS);
      }
  }
  
  PCOMMONWIN_DATA
  NewWinData(WIN_TYPES Type)
  {
      switch(Type)
      {
      case DOC_WINDOW:
          return new DOCWIN_DATA;
      case WATCH_WINDOW:
          return new WATCHWIN_DATA;
      case LOCALS_WINDOW:
          return new LOCALSWIN_DATA;
      case CPU_WINDOW:
          return new CPUWIN_DATA;
      case DISASM_WINDOW:
          return new DISASMWIN_DATA;
      case CMD_WINDOW:
          return new CMDWIN_DATA;
      case SCRATCH_PAD_WINDOW:
          return new SCRATCH_PAD_DATA;
      case MEM_WINDOW:
          return new MEMWIN_DATA;
  #if 0
      case QUICKW_WINDOW:
          // XXX drewb - Unimplemented.
          return new QUICKWWIN_DATA;
  #endif
      case CALLS_WINDOW:
          return new CALLSWIN_DATA;
      case PROCESS_THREAD_WINDOW:
          return new PROCESS_THREAD_DATA;
      default:
          Assert(FALSE);
          return NULL;
      }
  }
  
  LRESULT
  CALLBACK
  COMMONWIN_DATA::WindowProc(
      HWND hwnd,
      UINT uMsg,
      WPARAM wParam,
      LPARAM lParam
      )
  {
      PCOMMONWIN_DATA pWinData = GetCommonWinData(hwnd);
  
  #if 0
      {
          DebugPrint("CommonWin msg %X for %p, args %X %X\n",
                     uMsg, pWinData, wParam, lParam);
      }
  #endif
  
      if (uMsg != WM_CREATE && pWinData == NULL)
      {
          return DefMDIChildProc(hwnd, uMsg, wParam, lParam);
      }
      
      switch (uMsg)
      {
      case WM_CREATE:
          RECT rc;
          COMMONWIN_CREATE_DATA* Data;
  
          Assert(NULL == pWinData);
  
          Data = (COMMONWIN_CREATE_DATA*)
              ((LPMDICREATESTRUCT)
               (((CREATESTRUCT *)lParam)->lpCreateParams))->lParam;
  
          pWinData = NewWinData(Data->Type);
          if (!pWinData)
          {
              return -1; // Fail window creation
          }
          Assert(pWinData->m_enumType == Data->Type);
  
          pWinData->m_Win = hwnd;
          
          GetClientRect(hwnd, &rc);
          pWinData->m_Size.cx = rc.right;
          pWinData->m_Size.cy = rc.bottom;
              
          if ( !pWinData->OnCreate() )
          {
              delete pWinData;
              return -1; // Fail window creation
          }
  
          // store this in the window
          SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pWinData);
  
  #if DBG
          pWinData->Validate();
  #endif
              
          g_IndexedWin[Data->Type] = pWinData;
          g_IndexedHwnd[Data->Type] = hwnd;
          InsertHeadList(&g_ActiveWin, &pWinData->m_ActiveWin);
  
          if (g_Workspace != NULL)
          {
              g_Workspace->AddDirty(WSPF_DIRTY_WINDOWS);
          }
  
          SendMessage(hwnd, WM_SETICON, 0, (LPARAM)
                      LoadIcon(g_hInst,
                               MAKEINTRESOURCE(pWinData->m_enumType +
                                               MINVAL_WINDOW_ICON)));
  
          // A new buffer has been created so put it in the list
          // then wake up the engine to fill it.
          Dbg_EnterCriticalSection(&g_QuickLock);
          InsertHeadList(&g_StateList, pWinData);
          Dbg_LeaveCriticalSection(&g_QuickLock);
          UpdateEngine();
  
          // Force initial updates so that the window starts
          // out with a state which matches the current debug
          // session's state.
          PostMessage(hwnd, WU_UPDATE, UPDATE_BUFFER, 0);
          PostMessage(hwnd, WU_UPDATE, UPDATE_EXEC, 0);
  
          if (g_WinOptions & WOPT_AUTO_ARRANGE)
          {
              Arrange();
          }
          return 0;
  
      case WM_COMMAND:
          if (pWinData->OnCommand(wParam, lParam) == 0)
          {
              return 0;
          }
          break;
          
      case WM_SETFOCUS:
          pWinData->OnSetFocus();
          break;
  
      case WM_MOVE:
          // When the frame window is minimized or restored
          // a move to 0,0 comes through.  Ignore this so
          // as to not trigger the warning.
          if (!IsIconic(g_hwndFrame) && lParam != 0 &&
              !IsIconic(hwnd) && !pWinData->m_CausedArrange)
          {
              DisplayAutoArrangeWarning(pWinData);
          }
          if (g_Workspace != NULL)
          {
              g_Workspace->AddDirty(WSPF_DIRTY_WINDOWS);
          }
          return DefWindowProc(hwnd, uMsg, wParam, lParam);
          
      case WM_SIZE:
          if (wParam == SIZE_MAXHIDE || wParam == SIZE_MAXSHOW)
          {
              // We don't care about cover/uncover events.
              break;
          }
          // We don't care about size events while the frame is
          // minimized as the children can't be seen.  When
          // the frame is restored a new size event will come through
          // and things will get updated when they're actually visible.
          if (IsIconic(g_hwndFrame))
          {
              break;
          }
  
          if (wParam == SIZE_RESTORED && !pWinData->m_CausedArrange)
          {
              DisplayAutoArrangeWarning(pWinData);
          }
          if (g_Workspace != NULL)
          {
              g_Workspace->AddDirty(WSPF_DIRTY_WINDOWS);
          }
  
          pWinData->UpdateSize(LOWORD(lParam), HIWORD(lParam));
  
          // No need to run sizing code for minimize.
          if (wParam == SIZE_MINIMIZED)
          {
              // The minimized window will leave a hole so
              // arrange to fill it and leave space for the
              // minimized window.
              if (g_WinOptions & WOPT_AUTO_ARRANGE)
              {
                  pWinData->m_CausedArrange = TRUE;
                  Arrange();
              }
              break;
          }
  
          if (wParam == SIZE_RESTORED && pWinData->m_CausedArrange)
          {
              // If we're restoring a window that caused
              // a rearrange when it was minimized we
              // need to update things to account for it.
              pWinData->m_CausedArrange = FALSE;
              
              if (g_WinOptions & WOPT_AUTO_ARRANGE)
              {
                  Arrange();
              }
          }
          else if (wParam == SIZE_MAXIMIZED)
          {
              // Ask for a rearrange on restore just
              // for consistency with minimize.
              pWinData->m_CausedArrange = TRUE;
          }
  
          pWinData->OnSize();
          break;
  
      case WM_LBUTTONDOWN:
          pWinData->OnButtonDown(MK_LBUTTON);
          return 0;
      case WM_LBUTTONUP:
          pWinData->OnButtonUp(MK_LBUTTON);
          return 0;
      case WM_MBUTTONDOWN:
          pWinData->OnButtonDown(MK_MBUTTON);
          return 0;
      case WM_MBUTTONUP:
          pWinData->OnButtonUp(MK_MBUTTON);
          return 0;
      case WM_RBUTTONDOWN:
          pWinData->OnButtonDown(MK_RBUTTON);
          return 0;
      case WM_RBUTTONUP:
          pWinData->OnButtonUp(MK_RBUTTON);
          return 0;
  
      case WM_MOUSEMOVE:
          pWinData->OnMouseMove((ULONG)wParam, LOWORD(lParam), HIWORD(lParam));
          return 0;
  
      case WM_TIMER:
          pWinData->OnTimer(wParam);
          return 0;
  
      case WM_GETMINMAXINFO:
          if (pWinData->OnGetMinMaxInfo((LPMINMAXINFO)lParam) == 0)
          {
              return 0;
          }
          break;
          
      case WM_VKEYTOITEM:
          return pWinData->OnVKeyToItem(wParam, lParam);
          
      case WM_NOTIFY:
          return pWinData->OnNotify(wParam, lParam);
          
      case WU_UPDATE:
          pWinData->OnUpdate((UpdateType)wParam);
          return 0;
  
      case WU_RECONFIGURE:
          pWinData->OnSize();
          break;
  
      case WM_DESTROY:
          pWinData->OnDestroy();
          
          SetWindowLongPtr(hwnd, GWLP_USERDATA, NULL);
          g_IndexedWin[pWinData->m_enumType] = NULL;
          g_IndexedHwnd[pWinData->m_enumType] = NULL;
          RemoveEntryList(&pWinData->m_ActiveWin);
          
          if (g_Workspace != NULL)
          {
              g_Workspace->AddDirty(WSPF_DIRTY_WINDOWS);
          }
          
          // Mark this buffer as ready for cleanup by the
          // engine when it gets around to it.
          pWinData->m_Win = NULL;
          if (pWinData == g_FindLast)
          {
              g_FindLast = NULL;
          }
          UpdateEngine();
          
          if (g_WinOptions & WOPT_AUTO_ARRANGE)
          {
              Arrange();
          }
          break;
          
      case WM_MEASUREITEM:
      case WM_DRAWITEM:
          // 
          // Both these messages must be handled by owner drawn windows
          // 
          return pWinData->OnOwnerDraw(uMsg, wParam, lParam);
  
      case WM_CTLCOLORLISTBOX:
          // Substitute windbg's default window colors.
          SetTextColor((HDC)wParam, g_Colors[COL_PLAIN_TEXT].Color);
          SetBkColor((HDC)wParam, g_Colors[COL_PLAIN].Color);
          return (LRESULT)g_Colors[COL_PLAIN].Brush;
      }
      
      return DefMDIChildProc(hwnd, uMsg, wParam, lParam);
  }
  
  
  //
  //
  //
  SINGLE_CHILDWIN_DATA::SINGLE_CHILDWIN_DATA(ULONG ChangeBy)
      : COMMONWIN_DATA(ChangeBy)
  {
      m_hwndChild = NULL;
  }
  
  void 
  SINGLE_CHILDWIN_DATA::Validate()
  {
      COMMONWIN_DATA::Validate();
  
      Assert(m_hwndChild);
  }
  
  void 
  SINGLE_CHILDWIN_DATA::SetFont(ULONG FontIndex)
  {
      COMMONWIN_DATA::SetFont(FontIndex);
  
      SendMessage(m_hwndChild, 
                  WM_SETFONT, 
                  (WPARAM) m_Font->Font,
                  (LPARAM) TRUE
                  );
  }
  
  BOOL
  SINGLE_CHILDWIN_DATA::CanCopy()
  {
      if (GetFocus() != m_hwndChild)
      {
          return COMMONWIN_DATA::CanCopy();
      }
      
      switch (m_enumType)
      {
      default:
          Assert(!"Unknown type");
          return FALSE;
  
      case CMD_WINDOW:
          Assert(!"Should not be handled here since this is only for windows"
              " with only one child window.");
          return FALSE;
  
      case WATCH_WINDOW:
      case LOCALS_WINDOW:
      case CPU_WINDOW:
      case QUICKW_WINDOW:
          return -1 != ListView_GetNextItem(m_hwndChild,
                                            -1, // Find the first match
                                            LVNI_FOCUSED
                                            );
  
      case CALLS_WINDOW:
          return LB_ERR != SendMessage(m_hwndChild, LB_GETCURSEL, 0, 0);
  
      case DOC_WINDOW:
      case DISASM_WINDOW:
      case MEM_WINDOW:
      case SCRATCH_PAD_WINDOW:
          CHARRANGE chrg;
          SendMessage(m_hwndChild, EM_EXGETSEL, 0, (LPARAM)&chrg);
          return chrg.cpMin != chrg.cpMax;
  
      case PROCESS_THREAD_WINDOW:
          return NULL != TreeView_GetSelection(m_hwndChild);
      }
  }
  
  BOOL
  SINGLE_CHILDWIN_DATA::CanCut()
  {
      if (GetFocus() != m_hwndChild)
      {
          return COMMONWIN_DATA::CanCut();
      }
      
      switch (m_enumType)
      {
      default:
          Assert(!"Unknown type");
          return FALSE;
  
      case CMD_WINDOW:
          Assert(!"Should not be handled here since this is only for windows"
              " with only one child window.");
          return FALSE;
  
      case WATCH_WINDOW:
      case LOCALS_WINDOW:
      case CPU_WINDOW:
      case QUICKW_WINDOW:
      case CALLS_WINDOW:
      case DOC_WINDOW:
      case DISASM_WINDOW:
      case MEM_WINDOW:
      case PROCESS_THREAD_WINDOW:
          return FALSE;
          
      case SCRATCH_PAD_WINDOW:
          CHARRANGE chrg;
          SendMessage(m_hwndChild, EM_EXGETSEL, 0, (LPARAM)&chrg);
          return chrg.cpMin != chrg.cpMax;
      }
  }
  
  BOOL
  SINGLE_CHILDWIN_DATA::CanPaste()
  {
      if (GetFocus() != m_hwndChild)
      {
          return COMMONWIN_DATA::CanPaste();
      }
      
      switch (m_enumType)
      {
      default:
          Assert(!"Unknown type");
          return FALSE;
  
      case CMD_WINDOW:
          Assert(!"Should not be handled here since this is only for windows"
              " with only one child window.");
          return FALSE;
  
      case WATCH_WINDOW:
      case LOCALS_WINDOW:
      case CPU_WINDOW:
      case QUICKW_WINDOW:
      case CALLS_WINDOW:
      case DOC_WINDOW:
      case DISASM_WINDOW:
      case MEM_WINDOW:
      case PROCESS_THREAD_WINDOW:
          return FALSE;
          
      case SCRATCH_PAD_WINDOW:
          return TRUE;
      }
  }
  
  void
  SINGLE_CHILDWIN_DATA::Copy()
  {
      if (GetFocus() != m_hwndChild)
      {
          COMMONWIN_DATA::Copy();
      }
      else
      {
          SendMessage(m_hwndChild, WM_COPY, 0, 0);
      }
  }
  
  void
  SINGLE_CHILDWIN_DATA::Cut()
  {
      if (GetFocus() != m_hwndChild)
      {
          COMMONWIN_DATA::Paste();
      }
  }
  
  void
  SINGLE_CHILDWIN_DATA::Paste()
  {
      if (GetFocus() != m_hwndChild)
      {
          COMMONWIN_DATA::Paste();
      }
  }
  
  void
  SINGLE_CHILDWIN_DATA::OnSetFocus()
  {
      ::SetFocus(m_hwndChild);
  }
  
  void
  SINGLE_CHILDWIN_DATA::OnSize(void)
  {
      COMMONWIN_DATA::OnSize();
      MoveWindow(m_hwndChild, 0, m_ToolbarHeight,
                 m_Size.cx, m_Size.cy - m_ToolbarHeight, TRUE);
  }
  
  void
  SINGLE_CHILDWIN_DATA::UpdateColors(void)
  {
      // Force a repaint of the child.
      InvalidateRect(m_hwndChild, NULL, TRUE);
  }
  
  //
  //
  //
  PROCESS_THREAD_DATA::PROCESS_THREAD_DATA()
      : SINGLE_CHILDWIN_DATA(512)
  {
      m_enumType = PROCESS_THREAD_WINDOW;
      m_TotalSystems = 0;
      m_NamesOffset = 0;
  }
  
  void
  PROCESS_THREAD_DATA::Validate()
  {
      SINGLE_CHILDWIN_DATA::Validate();
  
      Assert(PROCESS_THREAD_WINDOW == m_enumType);
  }
  
  HRESULT
  PROCESS_THREAD_DATA::ReadProcess(ULONG ProcId, PULONG Offset)
  {
      HRESULT Status;
      PULONG ThreadIds, ThreadSysIds;
      ULONG NumThread;
      char Name[MAX_PATH];
      ULONG NameLen;
      PULONG Data;
  
      if ((Status = g_pDbgSystem->
           SetCurrentProcessId(ProcId)) != S_OK ||
          (Status = g_pDbgSystem->GetNumberThreads(&NumThread)) != S_OK)
      {
          return Status;
      }
      if (FAILED(Status = g_pDbgSystem->
                 GetCurrentProcessExecutableName(Name, sizeof(Name),
                                                 NULL)))
      {
          PrintString(Name, DIMA(Name), "<%s>", FormatStatusCode(Status));
      }
      
      NameLen = strlen(Name) + 1;
      if (NameLen > 1)
      {
          PSTR NameStore = (PSTR)AddData(NameLen);
          if (NameStore == NULL)
          {
              return E_OUTOFMEMORY;
          }
  
          strcpy(NameStore, Name);
      }
      
      // Refresh pointers in case a resize
      // caused buffer movement.
      Data = (PULONG)GetDataBuffer() + *Offset;
      *Data++ = NumThread;
      *Data++ = NameLen;
      *Offset += 2;
      
      ThreadIds = Data;
      ThreadSysIds = ThreadIds + NumThread;
          
      if ((Status = g_pDbgSystem->
           GetThreadIdsByIndex(0, NumThread,
                               ThreadIds, ThreadSysIds)) != S_OK)
      {
          return Status;
      }
  
      *Offset += 2 * NumThread;
  
      return S_OK;
  }
  
  HRESULT
  PROCESS_THREAD_DATA::ReadSystem(ULONG SysId,
                                  PULONG Offset)
  {
      HRESULT Status;
      ULONG ProcIdsOffset;
      PULONG ProcIds, ProcSysIds;
      ULONG NumProc;
      ULONG i;
      char Name[MAX_PATH + 32];
      ULONG NameLen;
      PULONG Data;
  
      if (g_pDbgSystem3)
      {
          if ((Status = g_pDbgSystem3->
               SetCurrentSystemId(SysId)) != S_OK ||
              FAILED(Status = g_pDbgSystem3->
                     GetCurrentSystemServerName(Name, sizeof(Name), NULL)))
          {
              return Status;
          }
      }
      else
      {
          Name[0] = 0;
      }
  
      NameLen = strlen(Name) + 1;
      if (NameLen > 1)
      {
          PSTR NameStore = (PSTR)AddData(NameLen);
          if (NameStore == NULL)
          {
              return E_OUTOFMEMORY;
          }
  
          strcpy(NameStore, Name);
      }
      
      if ((Status = g_pDbgSystem->
           GetNumberProcesses(&NumProc)) != S_OK)
      {
          return Status;
      }
      
      // Refresh pointers in case a resize
      // caused buffer movement.
      Data = (PULONG)GetDataBuffer() + *Offset;
      *Data++ = NumProc;
      *Data++ = NameLen;
      *Offset += 2;
  
      if (NumProc == 0)
      {
          return S_OK;
      }
      
      ProcIds = Data;
      ProcIdsOffset = *Offset;
      ProcSysIds = ProcIds + NumProc;
      
      if ((Status = g_pDbgSystem->
           GetProcessIdsByIndex(0, NumProc, ProcIds, ProcSysIds)) != S_OK)
      {
          return Status;
      }
  
      *Offset += 2 * NumProc;
      for (i = 0; i < NumProc; i++)
      {
          ProcIds = (PULONG)GetDataBuffer() + ProcIdsOffset;
          
          if ((Status = ReadProcess(ProcIds[i], Offset)) != S_OK)
          {
              return Status;
          }
      }
  
      return S_OK;
  }
  
  HRESULT
  PROCESS_THREAD_DATA::ReadState(void)
  {
      HRESULT Status;
      ULONG CurProc;
      ULONG TotalSys, TotalThread, TotalProc;
      ULONG MaxProcThread, MaxSysThread, MaxSysProc;
      PULONG SysIds;
      ULONG i;
      ULONG Offset;
      ULONG NamesOffset;
  
      if ((Status = g_pDbgSystem->GetCurrentProcessId(&CurProc)) != S_OK)
      {
          return Status;
      }
      if (g_pDbgSystem3)
      {
          if ((Status = g_pDbgSystem3->GetNumberSystems(&TotalSys)) != S_OK ||
              (Status = g_pDbgSystem3->
               GetTotalNumberThreadsAndProcesses(&TotalThread, &TotalProc,
                                                 &MaxProcThread, &MaxSysThread,
                                                 &MaxSysProc)) != S_OK)
          {
              return Status;
          }
      }
      else
      {
          if ((Status = g_pDbgSystem->GetNumberProcesses(&TotalProc)) != S_OK ||
              (Status = g_pDbgSystem->
               GetTotalNumberThreads(&TotalThread, &MaxProcThread)) != S_OK)
          {
              return Status;
          }
          
          TotalSys = 1;
          MaxSysThread = MaxProcThread;
          MaxSysProc = TotalProc;
      }
  
      Empty();
  
      NamesOffset = (TotalSys * 3 + TotalProc * 4 + TotalThread * 2) *
          sizeof(ULONG);
      SysIds = (PULONG)AddData(NamesOffset);
      if (SysIds == NULL)
      {
          return E_OUTOFMEMORY;
      }
  
      if (g_pDbgSystem3)
      {
          if ((Status = g_pDbgSystem3->
               GetSystemIdsByIndex(0, TotalSys, SysIds)) != S_OK)
          {
              return Status;
          }
      }
      else
      {
          *SysIds = 0;
      }
  
      ULONG OutMask, LogMask;
      
      // Ignore thread notifications as we're changing the thread.
      g_IgnoreThreadChange = TRUE;
      // Switching threads causes output which we don't want so
      // ignore all output.
      g_pDbgClient->GetOutputMask(&OutMask);
      g_pDbgControl->GetLogMask(&LogMask);
      g_pDbgClient->SetOutputMask(0);
      g_pDbgControl->SetLogMask(0);
      
      Offset = TotalSys;
      for (i = 0; i < TotalSys; i++)
      {
          SysIds = (PULONG)GetDataBuffer();
          
          if ((Status = ReadSystem(SysIds[i], &Offset)) != S_OK)
          {
              break;
          }
      }
  
      // This will also set the current system and thread
      // from the process information.
      g_pDbgSystem->SetCurrentProcessId(CurProc);
  
      g_IgnoreThreadChange = FALSE;
      
      g_pDbgClient->SetOutputMask(OutMask);
      g_pDbgControl->SetLogMask(LogMask);
  
      if (Status == S_OK)
      {
          m_TotalSystems = TotalSys;
          m_NamesOffset = NamesOffset;
      }
      
      return Status;
  }
  
  BOOL
  PROCESS_THREAD_DATA::OnCreate(void)
  {
      if (!SINGLE_CHILDWIN_DATA::OnCreate())
      {
          return FALSE;
      }
  
      m_hwndChild = CreateWindow(
          WC_TREEVIEW,                                // class name
          NULL,                                       // title
          WS_CLIPSIBLINGS |
          WS_CHILD | WS_VISIBLE |
          WS_HSCROLL | WS_VSCROLL |
          TVS_HASBUTTONS | TVS_LINESATROOT |
          TVS_HASLINES,                               // style
          0,                                          // x
          0,                                          // y
          m_Size.cx,                                  // width
          m_Size.cy,                                  // height
          m_Win,                                      // parent
          (HMENU) IDC_PROCESS_TREE,                   // control id
          g_hInst,                                    // hInstance
          NULL);                                      // user defined data
      if (!m_hwndChild)
      {
          return FALSE;
      }
      
      SetFont(FONT_FIXED);
      SendMessage(m_hwndChild, TVM_SETTEXTCOLOR,
                  0, g_Colors[COL_PLAIN_TEXT].Color);
      SendMessage(m_hwndChild, TVM_SETBKCOLOR,
                  0, g_Colors[COL_PLAIN].Color);
      
      return TRUE;
  }
  
  LRESULT
  PROCESS_THREAD_DATA::OnNotify(WPARAM Wpm, LPARAM Lpm)
  {
      LPNMTREEVIEW Tvn;
      HTREEITEM Sel;
  
      Tvn = (LPNMTREEVIEW)Lpm;
      if (Tvn->hdr.idFrom != IDC_PROCESS_TREE)
      {
          return FALSE;
      }
      
      switch(Tvn->hdr.code)
      {
      case TVN_SELCHANGED:
          if (Tvn->action == TVC_BYMOUSE)
          {
              SetCurThreadFromProcessTreeItem(m_hwndChild, Tvn->itemNew.hItem);
          }
          break;
  
      case NM_DBLCLK:
      case NM_RETURN:
          Sel = TreeView_GetSelection(m_hwndChild);
          if (Sel)
          {
              SetCurThreadFromProcessTreeItem(m_hwndChild, Sel);
          }
          return TRUE;
      }
  
      return FALSE;
  }
  
  void
  PROCESS_THREAD_DATA::OnUpdate(UpdateType Type)
  {
      if (Type != UPDATE_BUFFER &&
          Type != UPDATE_EXEC)
      {
          return;
      }
      
      HRESULT Status;
      
      Status = UiLockForRead();
      if (Status != S_OK)
      {
          return;
      }
      
      ULONG Sys;
      ULONG NameLen;
      PULONG SysIds, Data;
      char Text[MAX_PATH + 64];
      PSTR Names;
      TVINSERTSTRUCT Insert;
      HTREEITEM CurThreadItem = NULL;
  
      SysIds = (PULONG)GetDataBuffer();
      Data = SysIds + m_TotalSystems;
      Names = (PSTR)GetDataBuffer() + m_NamesOffset;
      
      TreeView_DeleteAllItems(m_hwndChild);
  
      for (Sys = 0; Sys < m_TotalSystems; Sys++)
      {
          HTREEITEM SysItem;
          ULONG NumProc, Proc;
          PULONG ProcIds, ProcSysIds;
  
          NumProc = *Data++;
          NameLen = *Data++;
          ProcIds = Data;
          ProcSysIds = ProcIds + NumProc;
          Data = ProcSysIds + NumProc;
  
          sprintf(Text, "%d ", SysIds[Sys]);
          if (NameLen > 1)
          {
              CatString(Text, Names, DIMA(Text));
              Names += strlen(Names) + 1;
          }
          
          if (m_TotalSystems > 1)
          {
              Insert.hParent = TVI_ROOT;
              Insert.hInsertAfter = TVI_LAST;
              Insert.item.mask = TVIF_TEXT | TVIF_STATE | TVIF_PARAM;
              Insert.item.pszText = Text;
              Insert.item.state =
                  SysIds[Sys] == g_CurSystemId ? TVIS_EXPANDED | TVIS_BOLD: 0;
              Insert.item.stateMask = TVIS_EXPANDED | TVIS_BOLD;
              // Parameter is the thread ID to set to select the given system.
              Insert.item.lParam = NumProc > 0 ? (LPARAM)Data[2] : (LPARAM)-1;
              SysItem = TreeView_InsertItem(m_hwndChild, &Insert);
          }
          else
          {
              SysItem = TVI_ROOT;
          }
  
          for (Proc = 0; Proc < NumProc; Proc++)
          {
              HTREEITEM ProcItem;
              ULONG NumThread, Thread;
              PULONG ThreadIds, ThreadSysIds;
  
              NumThread = *Data++;
              NameLen = *Data++;
              ThreadIds = Data;
              ThreadSysIds = Data + NumThread;
              Data = ThreadSysIds + NumThread;
              
              sprintf(Text, "%03d:%x ", ProcIds[Proc], ProcSysIds[Proc]);
              if (NameLen > 1)
              {
                  CatString(Text, Names, DIMA(Text));
                  Names += strlen(Names) + 1;
              }
          
              Insert.hParent = SysItem;
              Insert.hInsertAfter = TVI_LAST;
              Insert.item.mask = TVIF_TEXT | TVIF_STATE | TVIF_PARAM;
              Insert.item.pszText = Text;
              Insert.item.state =
                  SysIds[Sys] == g_CurSystemId &&
                  ProcIds[Proc] == g_CurProcessId ?
                  TVIS_EXPANDED | TVIS_BOLD: 0;
              Insert.item.stateMask = TVIS_EXPANDED | TVIS_BOLD;
              // Parameter is the thread ID to set to select the given thread.
              Insert.item.lParam = (LPARAM)ThreadIds[0];
              ProcItem = TreeView_InsertItem(m_hwndChild, &Insert);
  
              for (Thread = 0; Thread < NumThread; Thread++)
              {
                  HTREEITEM ThreadItem;
              
                  sprintf(Text, "%03d:%x",
                          ThreadIds[Thread], ThreadSysIds[Thread]);
                  Insert.hParent = ProcItem;
                  Insert.hInsertAfter = TVI_LAST;
                  Insert.item.mask = TVIF_TEXT | TVIF_STATE | TVIF_PARAM;
                  Insert.item.pszText = Text;
                  Insert.item.state =
                      SysIds[Sys] == g_CurSystemId &&
                      ProcIds[Proc] == g_CurProcessId &&
                      ThreadIds[Thread] == g_CurThreadId ?
                      TVIS_BOLD : 0;
                  Insert.item.stateMask = TVIS_BOLD;
                  Insert.item.lParam = (LPARAM)ThreadIds[Thread];
                  ThreadItem = TreeView_InsertItem(m_hwndChild, &Insert);
                  if (Insert.item.state & TVIS_BOLD)
                  {
                      CurThreadItem = ThreadItem;
                  }
              }
          }
      }
  
      if (CurThreadItem)
      {
          TreeView_Select(m_hwndChild, CurThreadItem, TVGN_CARET);
      }
      
      UnlockStateBuffer(this);
  }
  
  void
  PROCESS_THREAD_DATA::UpdateColors(void)
  {
      SendMessage(m_hwndChild, TVM_SETTEXTCOLOR,
                  0, g_Colors[COL_PLAIN_TEXT].Color);
      SendMessage(m_hwndChild, TVM_SETBKCOLOR,
                  0, g_Colors[COL_PLAIN].Color);
      InvalidateRect(m_hwndChild, NULL, TRUE);
  }
  
  void
  PROCESS_THREAD_DATA::SetCurThreadFromProcessTreeItem(HWND Tree, HTREEITEM Sel)
  {
      TVITEM Item;
                  
      Item.hItem = Sel;
      Item.mask = TVIF_CHILDREN | TVIF_PARAM;
      TreeView_GetItem(Tree, &Item);
      if (Item.lParam != (LPARAM)-1)
      {
          g_pUiSystem->SetCurrentThreadId((ULONG)Item.lParam);
      }
  }
  
  
  //
  //
  //
  EDITWIN_DATA::EDITWIN_DATA(ULONG ChangeBy)
      : SINGLE_CHILDWIN_DATA(ChangeBy)
  {
      m_TextLines = 0;
      m_Highlights = NULL;
  }
  
  void
  EDITWIN_DATA::Validate()
  {
      SINGLE_CHILDWIN_DATA::Validate();
  }
  
  void 
  EDITWIN_DATA::SetFont(ULONG FontIndex)
  {
      SINGLE_CHILDWIN_DATA::SetFont(FontIndex);
  
      // Force the tabstop size to be recomputed
      // with the new font.
      SendMessage(m_hwndChild, EM_SETTABSTOPS, 1, (LPARAM)&g_TabWidth);
  }
  
  BOOL
  EDITWIN_DATA::CanSelectAll()
  {
      return TRUE;
  }
  
  void
  EDITWIN_DATA::SelectAll()
  {
      CHARRANGE Sel;
  
      Sel.cpMin = 0;
      Sel.cpMax = INT_MAX;
      SendMessage(m_hwndChild, EM_EXSETSEL, 0, (LPARAM)&Sel);
  }
  
  BOOL
  EDITWIN_DATA::OnCreate(void)
  {
      m_hwndChild = CreateWindowEx(
          WS_EX_CLIENTEDGE,                           // Extended style
          RICHEDIT_CLASS,                             // class name
          NULL,                                       // title
          WS_CLIPSIBLINGS
          | WS_CHILD | WS_VISIBLE
          | WS_VSCROLL | ES_AUTOVSCROLL
          | WS_HSCROLL | ES_AUTOHSCROLL
          | ES_READONLY
          | ES_MULTILINE,                             // style
          0,                                          // x
          m_ToolbarHeight,                            // y
          m_Size.cx,                                  // width
          m_Size.cy - m_ToolbarHeight,                // height
          m_Win,                                      // parent
          (HMENU) 0,                                  // control id
          g_hInst,                                    // hInstance
          NULL);                                      // user defined data
  
      if (m_hwndChild)
      {
          CHARFORMAT2 Fmt;
  
          SetFont(FONT_FIXED);
  
          SendMessage(m_hwndChild, EM_SETBKGNDCOLOR, FALSE,
                      g_Colors[COL_PLAIN].Color);
  
          ZeroMemory(&Fmt, sizeof(Fmt));
          Fmt.cbSize = sizeof(Fmt);
          Fmt.dwMask = CFM_COLOR;
          Fmt.crTextColor = g_Colors[COL_PLAIN_TEXT].Color;
          SendMessage(m_hwndChild, EM_SETCHARFORMAT,
                      SCF_SELECTION, (LPARAM)&Fmt);
      }
  
      return m_hwndChild != NULL;
  }
  
  LRESULT
  EDITWIN_DATA::OnNotify(WPARAM Wpm, LPARAM Lpm)
  {
      NMHDR* Hdr = (NMHDR*)Lpm;
      if (Hdr->code == EN_SAVECLIPBOARD)
      {
          // Indicate that the clipboard contents should
          // be kept alive.
          return 0;
      }
      else if (Hdr->code == EN_MSGFILTER)
      {
          MSGFILTER* Filter = (MSGFILTER*)Lpm;
          
          if (WM_SYSKEYDOWN == Filter->msg ||
              WM_SYSKEYUP == Filter->msg ||
              WM_SYSCHAR == Filter->msg)
          {
              // Force default processing for menu operations
              // so that the Alt-minus menu comes up.
              return 1;
          }
      }
  
      return 0;
  }
  
  void
  EDITWIN_DATA::OnDestroy(void)
  {
      EDIT_HIGHLIGHT* Next;
      
      while (m_Highlights != NULL)
      {
          Next = m_Highlights->Next;
          delete m_Highlights;
          m_Highlights = Next;
      }
  
      SINGLE_CHILDWIN_DATA::OnDestroy();
  }
  
  void
  EDITWIN_DATA::UpdateColors(void)
  {
      RicheditUpdateColors(m_hwndChild,
                           g_Colors[COL_PLAIN_TEXT].Color, TRUE,
                           g_Colors[COL_PLAIN].Color, TRUE);
      UpdateCurrentLineHighlight();
      UpdateBpMarks();
  }
  
  void
  EDITWIN_DATA::SetCurrentLineHighlight(ULONG Line)
  {
      //
      // Clear any other current line highlight in this window.
      // Also, only one doc window can have a current IP highlight so if
      // this is a doc window getting a current IP highlight make
      // sure no other doc windows have a current IP highlight.
      //
      if (m_enumType == DOC_WINDOW && ULONG_MAX != Line)
      {
          RemoveActiveWinHighlights(1 << DOC_WINDOW, EHL_CURRENT_LINE);
      }
      else
      {
          RemoveAllHighlights(EHL_CURRENT_LINE);
      }
      
      if (ULONG_MAX != Line)
      {
          AddHighlight(Line, EHL_CURRENT_LINE);
          RicheditScrollToLine(m_hwndChild, Line, m_LineHeight);
      }
  }
      
  void
  EDITWIN_DATA::UpdateCurrentLineHighlight(void)
  {
      EDIT_HIGHLIGHT* Hl;
  
      for (Hl = m_Highlights; Hl != NULL; Hl = Hl->Next)
      {
          if (Hl->Flags & EHL_CURRENT_LINE)
          {
              break;
          }
      }
  
      if (Hl)
      {
          ApplyHighlight(Hl);
      }
  }
  
  EDIT_HIGHLIGHT*
  EDITWIN_DATA::GetLineHighlighting(ULONG Line)
  {
      EDIT_HIGHLIGHT* Hl;
      
      for (Hl = m_Highlights; Hl != NULL; Hl = Hl->Next)
      {
          if (Hl->Line == Line)
          {
              return Hl;
          }
      }
  
      return NULL;
  }
  
  void
  EDITWIN_DATA::ApplyHighlight(EDIT_HIGHLIGHT* Hl)
  {
      CHARRANGE OldSel;
      BOOL HasFocus = ::GetFocus() == m_hwndChild;
  
      // Get the old selection and scroll position.
      SendMessage(m_hwndChild, EM_EXGETSEL, 0, (LPARAM)&OldSel);
  
      // Disable the window to prevent auto-scrolling
      // when the selection is set.
      EnableWindow(m_hwndChild, FALSE);
      
      //
      // Compute the highlight information.
      //
  
      char Markers[LINE_MARKERS + 1];
      CHARFORMAT2 Fmt;
      ULONG TextCol, BgCol;
  
      Markers[2] = 0;
      ZeroMemory(&Fmt, sizeof(Fmt));
      Fmt.cbSize = sizeof(Fmt);
      Fmt.dwMask = CFM_COLOR | CFM_BACKCOLOR;
      
      if (Hl->Flags & EHL_CURRENT_LINE)
      {
          Markers[1] = '>';
          switch(Hl->Flags & EHL_ANY_BP)
          {
          case EHL_ENABLED_BP:
              Markers[0] = 'B';
              TextCol = COL_BP_CURRENT_LINE_TEXT;
              BgCol = COL_BP_CURRENT_LINE;
              break;
          case EHL_DISABLED_BP:
              Markers[0] = 'D';
              TextCol = COL_BP_CURRENT_LINE_TEXT;
              BgCol = COL_BP_CURRENT_LINE;
              break;
          default:
              Markers[0] = ' ';
              TextCol = COL_CURRENT_LINE_TEXT;
              BgCol = COL_CURRENT_LINE;
              break;
          }
      }
      else
      {
          Markers[1] = ' ';
          switch(Hl->Flags & EHL_ANY_BP)
          {
          case EHL_ENABLED_BP:
              Markers[0] = 'B';
              TextCol = COL_ENABLED_BP_TEXT;
              BgCol = COL_ENABLED_BP;
              break;
          case EHL_DISABLED_BP:
              Markers[0] = 'D';
              TextCol = COL_DISABLED_BP_TEXT;
              BgCol = COL_DISABLED_BP;
              break;
          default:
              Markers[0] = ' ';
              TextCol = COL_PLAIN_TEXT;
              BgCol = COL_PLAIN;
              break;
          }
      }
  
      Fmt.crTextColor = g_Colors[TextCol].Color;
      Fmt.crBackColor = g_Colors[BgCol].Color;
      
      //
      // Select the line to be highlighted
      //
      
      CHARRANGE FmtSel;
      
      FmtSel.cpMin = (LONG)SendMessage(m_hwndChild, EM_LINEINDEX, Hl->Line, 0);
  
      if (g_LineMarkers)
      {
          // Replace the markers at the beginning of the line.
          FmtSel.cpMax = FmtSel.cpMin + 2;
          SendMessage(m_hwndChild, EM_EXSETSEL, 0, (LPARAM)&FmtSel);
          SendMessage(m_hwndChild, EM_REPLACESEL, FALSE, (LPARAM)Markers);
      }
  
      // Color the line.
      FmtSel.cpMax = FmtSel.cpMin + (LONG)
          SendMessage(m_hwndChild, EM_LINELENGTH, FmtSel.cpMin, 0) + 1;
      if (g_LineMarkers)
      {
          FmtSel.cpMin += 2;
      }
      SendMessage(m_hwndChild, EM_EXSETSEL, 0, (LPARAM)&FmtSel);
      SendMessage(m_hwndChild, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&Fmt);
  
      // Restore the old selection
      SendMessage(m_hwndChild, EM_EXSETSEL, 0, (LPARAM)&OldSel);
      
      EnableWindow(m_hwndChild, TRUE);
  
      // The disabling of the window caused the richedit
      // to forget its focus status so force the focus
      // back if it had it.
      if (HasFocus)
      {
          ::SetFocus(m_hwndChild);
      }
  }
  
  EDIT_HIGHLIGHT*
  EDITWIN_DATA::AddHighlight(ULONG Line, ULONG Flags)
  {
      EDIT_HIGHLIGHT* Hl;
  
      // Search for an existing highlight record for the line.
      Hl = GetLineHighlighting(Line);
  
      if (Hl == NULL)
      {
          Hl = new EDIT_HIGHLIGHT;
          if (Hl == NULL)
          {
              return NULL;
          }
  
          Hl->Data = 0;
          Hl->Line = Line;
          Hl->Flags = 0;
          Hl->Next = m_Highlights;
          m_Highlights = Hl;
      }
  
      Hl->Flags |= Flags;
      ApplyHighlight(Hl);
  
      return Hl;
  }
  
  void
  EDITWIN_DATA::RemoveHighlight(ULONG Line, ULONG Flags)
  {
      EDIT_HIGHLIGHT* Hl;
      EDIT_HIGHLIGHT* Prev;
      
      // Search for an existing highlight record for the line.
      Prev = NULL;
      for (Hl = m_Highlights; Hl != NULL; Hl = Hl->Next)
      {
          if (Hl->Line == Line)
          {
              break;
          }
  
          Prev = Hl;
      }
  
      if (Hl == NULL)
      {
          return;
      }
  
      Hl->Flags &= ~Flags;
      ApplyHighlight(Hl);
  
      if (Hl->Flags == 0)
      {
          if (Prev == NULL)
          {
              m_Highlights = Hl->Next;
          }
          else
          {
              Prev->Next = Hl->Next;
          }
  
          delete Hl;
      }
  }
  
  void
  EDITWIN_DATA::RemoveAllHighlights(ULONG Flags)
  {
      EDIT_HIGHLIGHT* Hl;
      EDIT_HIGHLIGHT* Next;
      EDIT_HIGHLIGHT* Prev;
  
      Prev = NULL;
      for (Hl = m_Highlights; Hl != NULL; Hl = Next)
      {
          Next = Hl->Next;
  
          if (Hl->Flags & Flags)
          {
              Hl->Flags &= ~Flags;
              ApplyHighlight(Hl);
  
              if (Hl->Flags == 0)
              {
                  if (Prev == NULL)
                  {
                      m_Highlights = Hl->Next;
                  }
                  else
                  {
                      Prev->Next = Hl->Next;
                  }
  
                  delete Hl;
              }
              else
              {
                  Prev = Hl;
              }
          }
          else
          {
              Prev = Hl;
          }
      }
  }
  
  void
  EDITWIN_DATA::RemoveActiveWinHighlights(ULONG Types, ULONG Flags)
  {
      PLIST_ENTRY Entry = g_ActiveWin.Flink;
  
      while (Entry != &g_ActiveWin)
      {
          PEDITWIN_DATA WinData = (PEDITWIN_DATA)
              ACTIVE_WIN_ENTRY(Entry);
              
          if (Types & (1 << WinData->m_enumType))
          {
              WinData->RemoveAllHighlights(Flags);
          }
  
          Entry = Entry->Flink;
      }
  }
  
  void
  EDITWIN_DATA::UpdateBpMarks(void)
  {
      // Empty implementation for derived classes
      // that do not show BP marks.
  }
  
  int
  EDITWIN_DATA::CheckForFileChanges(PCSTR File, FILETIME* LastWrite)
  {
      HANDLE Handle;
      
      Handle = CreateFile(File, GENERIC_READ, FILE_SHARE_READ,
                          NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 
                          NULL);
      if (Handle == INVALID_HANDLE_VALUE)
      {
          goto Changed;
      }
  
      FILETIME NewWrite;
      
      if (!GetFileTime(Handle, NULL, NULL, &NewWrite))
      {
          if (!GetFileTime(Handle, &NewWrite, NULL, NULL))
          {
              ZeroMemory(&NewWrite, sizeof(NewWrite));
          }
      }
  
      CloseHandle(Handle);
  
      if (CompareFileTime(LastWrite, &NewWrite) == 0)
      {
          // No change.
          return IDCANCEL;
      }
  
   Changed:
      return
          g_QuietSourceMode == QMODE_ALWAYS_YES ? IDYES :
          (g_QuietSourceMode == QMODE_ALWAYS_NO ? IDCANCEL :
           QuestionBox(ERR_File_Has_Changed, MB_YESNO, File));
  }
  
  //
  //
  //
  
  SCRATCH_PAD_DATA::SCRATCH_PAD_DATA()
      : EDITWIN_DATA(16)
  {
      m_enumType = SCRATCH_PAD_WINDOW;
  }
  
  void
  SCRATCH_PAD_DATA::Validate()
  {
      EDITWIN_DATA::Validate();
  
      Assert(SCRATCH_PAD_WINDOW == m_enumType);
  }
  
  void
  SCRATCH_PAD_DATA::Cut()
  {
      SendMessage(m_hwndChild, WM_CUT, 0, 0);
  }
  
  void
  SCRATCH_PAD_DATA::Paste()
  {
      SendMessage(m_hwndChild, EM_PASTESPECIAL, CF_TEXT, 0);
  }
  
  BOOL 
  SCRATCH_PAD_DATA::CanWriteTextToFile()
  {
      return TRUE;
  }
  
  HRESULT
  SCRATCH_PAD_DATA::WriteTextToFile(HANDLE File)
  {
      return RicheditWriteToFile(m_hwndChild, File);
  }
  
  BOOL
  SCRATCH_PAD_DATA::OnCreate(void)
  {
      if (!EDITWIN_DATA::OnCreate())
      {
          return FALSE;
      }
  
      SendMessage(m_hwndChild, EM_SETOPTIONS, ECOOP_AND, ~ECO_READONLY);
      SendMessage(m_hwndChild, EM_SETEVENTMASK, 0, ENM_MOUSEEVENTS);
  
      return TRUE;
  }
  
  LRESULT
  SCRATCH_PAD_DATA::OnNotify(WPARAM Wpm, LPARAM Lpm)
  {
      MSGFILTER* Filter = (MSGFILTER *)Lpm;
      
      if (EN_MSGFILTER != Filter->nmhdr.code)
      {
          return 0;
      }
  
      if (WM_RBUTTONDOWN == Filter->msg ||
          WM_RBUTTONDBLCLK == Filter->msg)
      {
          // If there's a selection copy it to the clipboard
          // and clear it.  Otherwise try to paste.
          if (CanCopy())
          {
              Copy();
              
              CHARRANGE Sel;
              SendMessage(m_hwndChild, EM_EXGETSEL, 0, (LPARAM)&Sel);
              Sel.cpMax = Sel.cpMin;
              SendMessage(m_hwndChild, EM_EXSETSEL, 0, (LPARAM)&Sel);
          }
          else if (SendMessage(m_hwndChild, EM_CANPASTE, CF_TEXT, 0))
          {
              Paste();
          }
          
          // Ignore right-button events.
          return 1;
      }
  
      return 0;
  }
  
  //
  //
  //
  DISASMWIN_DATA::DISASMWIN_DATA()
      : EDITWIN_DATA(2048)
  {
      m_enumType = DISASM_WINDOW;
      sprintf(m_OffsetExpr, "0x%I64x", g_EventIp);
      m_UpdateExpr = FALSE;
      m_FirstInstr = 0;
      m_LastInstr = 0;
  }
  
  void
  DISASMWIN_DATA::Validate()
  {
      EDITWIN_DATA::Validate();
  
      Assert(DISASM_WINDOW == m_enumType);
  }
  
  HRESULT
  DISASMWIN_DATA::ReadState(void)
  {
      HRESULT Status;
      // Sample these values right away in case the UI changes them.
      ULONG LinesTotal = m_LineHeight;
      ULONG LinesBefore = LinesTotal / 2;
      DEBUG_VALUE Value;
  
      if ((Status = g_pDbgControl->Evaluate(m_OffsetExpr, DEBUG_VALUE_INT64,
                                            &Value, NULL)) != S_OK)
      {
          return Status;
      }
  
      m_PrimaryInstr = Value.I64;
      
      // Reserve space at the beginning of the buffer to
      // store the line to offset mapping table.
      PULONG64 LineMap;
      
      Empty();
      LineMap = (PULONG64)AddData(sizeof(ULONG64) * LinesTotal);
      if (LineMap == NULL)
      {
          return E_OUTOFMEMORY;
      }
  
      // We also need to allocate a temporary line map to
      // pass to the engine for filling.  This can't be
      // the state buffer data since that may move as
      // output is generated.
      LineMap = new ULONG64[LinesTotal];
      if (LineMap == NULL)
      {
          return E_OUTOFMEMORY;
      }
      
      g_OutStateBuf.SetBuffer(this);
      if ((Status = g_OutStateBuf.Start(FALSE)) != S_OK)
      {
          delete [] LineMap;
          return Status;
      }
  
      Status = g_pOutCapControl->
          OutputDisassemblyLines(DEBUG_OUTCTL_THIS_CLIENT |
                                 DEBUG_OUTCTL_OVERRIDE_MASK |
                                 DEBUG_OUTCTL_NOT_LOGGED,
                                 LinesBefore, LinesTotal, m_PrimaryInstr,
                                 DEBUG_DISASM_EFFECTIVE_ADDRESS |
                                 DEBUG_DISASM_MATCHING_SYMBOLS,
                                 &m_PrimaryLine, &m_FirstInstr, &m_LastInstr,
                                 LineMap);
  
      memcpy(m_Data, LineMap, sizeof(ULONG64) * LinesTotal);
      delete [] LineMap;
  
      if (Status != S_OK)
      {
          g_OutStateBuf.End(FALSE);
          return Status;
      }
  
      m_TextLines = LinesTotal;
      m_TextOffset = LinesTotal * sizeof(ULONG64);
      
      // The line map is generated with offsets followed by
      // invalid offsets for continuation lines.  We want
      // the offsets to be on the last line of the disassembly
      // for a continuation set so move them down.
      // We don't want to move the offsets down to blank lines,
      // though, such as the blank lines that separate bundles
      // in IA64 disassembly.
      LineMap = (PULONG64)m_Data;
      PULONG64 LineMapEnd = LineMap + m_TextLines;
      PULONG64 SetStart;
      PSTR Text = (PSTR)m_Data + m_TextOffset;
      PSTR PrevText;
          
      while (LineMap < LineMapEnd)
      {
          if (*LineMap != DEBUG_INVALID_OFFSET)
          {
              SetStart = LineMap;
              for (;;)
              {
                  PrevText = Text;
                  Text = strchr(Text, '\n') + 1;
                  LineMap++;
                  if (LineMap >= LineMapEnd ||
                      *LineMap != DEBUG_INVALID_OFFSET ||
                      *Text == '\n')
                  {
                      break;
                  }
              }
              LineMap--;
              Text = PrevText;
              
              if (LineMap > SetStart)
              {
                  *LineMap = *SetStart;
                  *SetStart = DEBUG_INVALID_OFFSET;
              }
          }
              
          LineMap++;
          Text = strchr(Text, '\n') + 1;
      }
      
  #ifdef DEBUG_DISASM
      LineMap = (PULONG64)m_Data;
      for (Line = 0; Line < m_TextLines; Line++)
      {
          DebugPrint("%d: %I64x\n", Line, LineMap[Line]);
      }
  #endif
  
      return g_OutStateBuf.End(TRUE);
  }
  
  HRESULT
  DISASMWIN_DATA::CodeExprAtCaret(PSTR Expr, ULONG ExprSize, PULONG64 Offset)
  {
      HRESULT Status;
      LRESULT LineChar;
      LONG Line;
      PULONG64 LineMap;
      
      if ((Status = UiLockForRead()) != S_OK)
      {
          // Don't want to return any success codes here.
          return FAILED(Status) ? Status : E_FAIL;
      }
  
      LineChar = SendMessage(m_hwndChild, EM_LINEINDEX, -1, 0);
      Line = (LONG)SendMessage(m_hwndChild, EM_EXLINEFROMCHAR, 0, LineChar);
      if (Line < 0 || (ULONG)Line >= m_TextLines)
      {
          Status = E_INVALIDARG;
          goto Unlock;
      }
  
      ULONG64 LineOff;
      
      // Look up the offset in the line map.  If it's part of
      // a multiline group move forward to the offset.
      LineMap = (PULONG64)m_Data;
      LineOff = LineMap[Line];
      while ((ULONG)(Line + 1) < m_TextLines && LineOff == DEBUG_INVALID_OFFSET)
      {
          Line++;
          LineOff = LineMap[Line];
      }
  
      if (Expr != NULL)
      {
          if (!PrintString(Expr, ExprSize, "0x%I64x", LineOff))
          {
              Status = E_INVALIDARG;
              goto Unlock;
          }
      }
      if (Offset != NULL)
      {
          *Offset = LineOff;
      }
      Status = S_OK;
      
   Unlock:
      UnlockStateBuffer(this);
      return Status;
  }
  
  BOOL
  DISASMWIN_DATA::OnCreate(void)
  {
      RECT Rect;
      ULONG Height;
  
      Height = GetSystemMetrics(SM_CYVSCROLL) + 4 * GetSystemMetrics(SM_CYEDGE);
      
      m_Toolbar = CreateWindowEx(0, REBARCLASSNAME, NULL,
                                 WS_VISIBLE | WS_CHILD |
                                 WS_CLIPCHILDREN | WS_CLIPSIBLINGS |
                                 CCS_NODIVIDER | CCS_NOPARENTALIGN |
                                 RBS_VARHEIGHT | RBS_BANDBORDERS,
                                 0, 0, m_Size.cx, Height, m_Win,
                                 (HMENU)ID_TOOLBAR,
                                 g_hInst, NULL);
      if (m_Toolbar == NULL)
      {
          return FALSE;
      }
  
      REBARINFO BarInfo;
      BarInfo.cbSize = sizeof(BarInfo);
      BarInfo.fMask = 0;
      BarInfo.himl = NULL;
      SendMessage(m_Toolbar, RB_SETBARINFO, 0, (LPARAM)&BarInfo);
  
      m_ToolbarEdit = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", NULL,
                                     WS_VISIBLE | WS_CHILD | ES_AUTOHSCROLL,
                                     0, 0, 18 * m_Font->Metrics.tmAveCharWidth,
                                     Height, m_Toolbar, (HMENU)IDC_EDIT_OFFSET,
                                     g_hInst, NULL);
      if (m_ToolbarEdit == NULL)
      {
          return FALSE;
      }
  
      SendMessage(m_ToolbarEdit, WM_SETFONT, (WPARAM)m_Font->Font, 0);
      SendMessage(m_ToolbarEdit, EM_LIMITTEXT, sizeof(m_OffsetExpr) - 1, 0);
      
      GetClientRect(m_ToolbarEdit, &Rect);
  
      REBARBANDINFO BandInfo;
      BandInfo.cbSize = sizeof(BandInfo);
      BandInfo.fMask = RBBIM_STYLE | RBBIM_TEXT | RBBIM_CHILD | RBBIM_CHILDSIZE;
      BandInfo.fStyle = RBBS_FIXEDSIZE;
      BandInfo.lpText = "Offset:";
      BandInfo.hwndChild = m_ToolbarEdit;
      BandInfo.cxMinChild = Rect.right - Rect.left;
      BandInfo.cyMinChild = Rect.bottom - Rect.top;
      SendMessage(m_Toolbar, RB_INSERTBAND, -1, (LPARAM)&BandInfo);
  
      // If the toolbar is allowed to shrink too small it hangs
      // while resizing.  Just let it clip off below a certain width.
      m_MinToolbarWidth = BandInfo.cxMinChild * 2;
      
      PSTR PrevText = "Previous";
      m_PreviousButton =
          AddButtonBand(m_Toolbar, PrevText, PrevText, IDC_DISASM_PREVIOUS);
      m_NextButton =
          AddButtonBand(m_Toolbar, "Next", PrevText, IDC_DISASM_NEXT);
      if (m_PreviousButton == NULL || m_NextButton == NULL)
      {
          return FALSE;
      }
  
      // Maximize the space for the offset expression.
      SendMessage(m_Toolbar, RB_MAXIMIZEBAND, 0, FALSE);
      
      GetClientRect(m_Toolbar, &Rect);
      m_ToolbarHeight = Rect.bottom - Rect.top;
      m_ShowToolbar = TRUE;
      
      if (!EDITWIN_DATA::OnCreate())
      {
          return FALSE;
      }
  
      // Suppress the scroll bar as the text is always
      // fitted to the window size.
      SendMessage(m_hwndChild, EM_SHOWSCROLLBAR, SB_VERT, FALSE);
  
      SendMessage(m_hwndChild, EM_SETEVENTMASK, 0, ENM_KEYEVENTS);
  
      return TRUE;
  }
  
  LRESULT
  DISASMWIN_DATA::OnCommand(WPARAM Wpm, LPARAM Lpm)
  {
      switch(LOWORD(Wpm))
      {
      case IDC_EDIT_OFFSET:
          if (HIWORD(Wpm) == EN_CHANGE)
          {
              // This message is sent on every keystroke
              // which causes a bit too much updating.
              // Set up a timer to trigger the actual
              // update in half a second.
              SetTimer(m_Win, IDC_EDIT_OFFSET, EDIT_DELAY, NULL);
              m_UpdateExpr = TRUE;
          }
          break;
      case IDC_DISASM_PREVIOUS:
          ScrollLower();
          break;
      case IDC_DISASM_NEXT:
          ScrollHigher();
          break;
      }
      
      return 0;
  }
  
  void
  DISASMWIN_DATA::OnSize(void)
  {
      EDITWIN_DATA::OnSize();
  
      // Force buffer to refill for new line count.
      UiRequestRead();
  }
  
  void
  DISASMWIN_DATA::OnTimer(WPARAM TimerId)
  {
      if (TimerId == IDC_EDIT_OFFSET && m_UpdateExpr)
      {
          m_UpdateExpr = FALSE;
          GetWindowText(m_ToolbarEdit, m_OffsetExpr, sizeof(m_OffsetExpr));
          UiRequestRead();
      }
  }
  
  LRESULT
  DISASMWIN_DATA::OnNotify(WPARAM Wpm, LPARAM Lpm)
  {
      MSGFILTER* Filter = (MSGFILTER*)Lpm;
  
      if (Filter->nmhdr.code != EN_MSGFILTER)
      {
          return EDITWIN_DATA::OnNotify(Wpm, Lpm);
      }
      
      if (Filter->msg == WM_KEYDOWN)
      {
          switch(Filter->wParam)
          {
          case VK_UP:
          {
              CHARRANGE range;
  
              SendMessage(m_hwndChild, EM_EXGETSEL, 0, (LPARAM) &range);
              if (!SendMessage(m_hwndChild, EM_LINEFROMCHAR, range.cpMin, 0)) 
              {
                  // up arrow on top line, scroll
                  ScrollLower();
                  return 1;
              }
              break;
          }
          case VK_DOWN:
          {
              CHARRANGE range;
              int MaxLine;
  
              SendMessage(m_hwndChild, EM_EXGETSEL, 0, (LPARAM) &range);
              MaxLine = (int) SendMessage(m_hwndChild, EM_GETLINECOUNT, 0, 0);
  
              if (MaxLine == (1+SendMessage(m_hwndChild, EM_LINEFROMCHAR, range.cpMin, 0)))
              {
                  // down arrow on bottom line, scroll
                  ScrollHigher();
                  return 1;
              }
              break;
          }
          
          case VK_PRIOR:
              ScrollLower();
              return 1;
          case VK_NEXT:
              ScrollHigher();
              return 1;
          }
      }
      else if (WM_SYSKEYDOWN == Filter->msg ||
               WM_SYSKEYUP == Filter->msg ||
               WM_SYSCHAR == Filter->msg)
      {
          // Force default processing for menu operations
          // so that the Alt-minus menu comes up.
          return 1;
      }
  
      return 0;
  }
  
  void
  DISASMWIN_DATA::OnUpdate(UpdateType Type)
  {
      if (Type == UPDATE_BP ||
          Type == UPDATE_END_SESSION)
      {
          UpdateBpMarks();
          return;
      }
      else if (Type != UPDATE_BUFFER)
      {
          return;
      }
      
      HRESULT Status;
      
      Status = UiLockForRead();
      if (Status == S_OK)
      {
          PULONG64 LineMap;
          ULONG Line;
  
          if (!g_LineMarkers)
          {
              SendMessage(m_hwndChild, WM_SETTEXT,
                          0, (LPARAM)m_Data + m_TextOffset);
          }
          else
          {
              SendMessage(m_hwndChild, WM_SETTEXT, 0, (LPARAM)"");
              PSTR Text = (PSTR)m_Data + m_TextOffset;
              for (;;)
              {
                  SendMessage(m_hwndChild, EM_REPLACESEL, FALSE, (LPARAM)"  ");
                  PSTR NewLine = strchr(Text, '\n');
                  if (NewLine != NULL)
                  {
                      *NewLine = 0;
                  }
                  SendMessage(m_hwndChild, EM_REPLACESEL, FALSE, (LPARAM)Text);
                  if (NewLine == NULL)
                  {
                      break;
                  }
                  SendMessage(m_hwndChild, EM_REPLACESEL, FALSE, (LPARAM)"\n");
                  *NewLine = '\n';
                  Text = NewLine + 1;
              }
          }
  
          // Highlight the last line of multiline disassembly.
          LineMap = (PULONG64)m_Data;
          Line = m_PrimaryLine;
          while (Line + 1 < m_TextLines &&
                 LineMap[Line] == DEBUG_INVALID_OFFSET)
          {
              Line++;
          }
          
          SetCurrentLineHighlight(Line);
  
          UnlockStateBuffer(this);
  
          RicheditUpdateColors(m_hwndChild,
                               g_Colors[COL_PLAIN_TEXT].Color, TRUE,
                               0, FALSE);
          UpdateCurrentLineHighlight();
          UpdateBpMarks();
          
          EnableWindow(m_PreviousButton, m_FirstInstr != m_PrimaryInstr);
          EnableWindow(m_NextButton, m_LastInstr != m_PrimaryInstr);
      }
      else
      {
          SendLockStatusMessage(m_hwndChild, WM_SETTEXT, Status);
          RemoveCurrentLineHighlight();
      }
  }
  
  void
  DISASMWIN_DATA::UpdateBpMarks(void)
  {
      if (m_TextLines == 0 ||
          UiLockForRead() != S_OK)
      {
          return;
      }
  
      if (g_BpBuffer->UiLockForRead() != S_OK)
      {
          UnlockStateBuffer(this);
          return;
      }
  
      SendMessage(m_hwndChild, WM_SETREDRAW, FALSE, 0);
  
      // Remove existing BP highlights.
      RemoveAllHighlights(EHL_ANY_BP);
      
      //
      // Highlight every line that matches a breakpoint.
      //
      
      PULONG64 LineMap = (PULONG64)m_Data;
      BpBufferData* BpData = (BpBufferData*)g_BpBuffer->GetDataBuffer();
      ULONG Line;
      BpStateType State;
  
      for (Line = 0; Line < m_TextLines; Line++)
      {
          if (*LineMap != DEBUG_INVALID_OFFSET)
          {
              State = IsBpAtOffset(BpData, *LineMap, NULL);
              if (State != BP_NONE)
              {
                  AddHighlight(Line, State == BP_ENABLED ?
                               EHL_ENABLED_BP : EHL_DISABLED_BP);
              }
          }
  
          LineMap++;
      }
  
      SendMessage(m_hwndChild, WM_SETREDRAW, TRUE, 0);
      InvalidateRect(m_hwndChild, NULL, TRUE);
      
      UnlockStateBuffer(g_BpBuffer);
      UnlockStateBuffer(this);
  }
  
  void
  DISASMWIN_DATA::SetCurInstr(ULONG64 Offset)
  {
      // Any pending user update is now irrelevant.
      m_UpdateExpr = FALSE;
      sprintf(m_OffsetExpr, "0x%I64x", Offset);
      // Force engine to update buffer.
      UiRequestRead();
  }
  
  
  void
  RicheditFind(HWND Edit,
               PTSTR Text, ULONG Flags,
               CHARRANGE* SaveSel, PULONG SaveFlags,
               BOOL HideSel)
  {
      if (Text == NULL)
      {
          // Clear last find.
          if (SaveSel->cpMax >= SaveSel->cpMin)
          {
              if (*SaveFlags & FR_DOWN)
              {
                  SaveSel->cpMin = SaveSel->cpMax;
              }
              else
              {
                  SaveSel->cpMax = SaveSel->cpMin;
              }
              if (HideSel)
              {
                  SendMessage(Edit, EM_SETOPTIONS, ECOOP_AND, ~ECO_NOHIDESEL);
              }
              SendMessage(Edit, EM_EXSETSEL, 0, (LPARAM)SaveSel);
              SendMessage(Edit, EM_SCROLLCARET, 0, 0);
              SaveSel->cpMin = 1;
              SaveSel->cpMax = 0;
          }
      }
      else
      {
          LRESULT Match;
          FINDTEXTEX Find;
  
          SendMessage(Edit, EM_EXGETSEL, 0, (LPARAM)&Find.chrg);
          if (Flags & FR_DOWN)
          {
              if (Find.chrg.cpMax > Find.chrg.cpMin)
              {
                  Find.chrg.cpMin++;
              }
              Find.chrg.cpMax = LONG_MAX;
          }
          else
          {
              Find.chrg.cpMax = 0;
          }
          Find.lpstrText = Text;
          Match = SendMessage(Edit, EM_FINDTEXTEX, Flags, (LPARAM)&Find);
          if (Match != -1)
          {
              *SaveSel = Find.chrgText;
              *SaveFlags = Flags;
              if (HideSel)
              {
                  SendMessage(Edit, EM_SETOPTIONS, ECOOP_OR, ECO_NOHIDESEL);
              }
              SendMessage(Edit, EM_EXSETSEL, 0, (LPARAM)SaveSel);
              SendMessage(Edit, EM_SCROLLCARET, 0, 0);
          }
          else
          {
              if (g_FindDialog)
              {
                  EnableWindow(g_FindDialog, FALSE);
              }
              
              InformationBox(ERR_No_More_Matches, Text);
              
              if (g_FindDialog)
              {
                  EnableWindow(g_FindDialog, TRUE);
                  SetFocus(g_FindDialog);
              }
          }
      }
  }
  
  DWORD CALLBACK 
  StreamOutCb(DWORD_PTR File, LPBYTE Buffer, LONG Request, PLONG Done)
  {
      return WriteFile((HANDLE)File, Buffer, Request, (LPDWORD)Done, NULL) ?
          0 : GetLastError();
  }
  
  HRESULT
  RicheditWriteToFile(HWND Edit, HANDLE File)
  {
      EDITSTREAM Stream;
  
      Stream.dwCookie = (DWORD_PTR)File;
      Stream.dwError = 0;
      Stream.pfnCallback = StreamOutCb;
      SendMessage(Edit, EM_STREAMOUT, SF_TEXT, (LPARAM)&Stream);
      if (Stream.dwError)
      {
          return HRESULT_FROM_WIN32(Stream.dwError);
      }
      return S_OK;
  }
  
  void
  RicheditUpdateColors(HWND Edit,
                       COLORREF Fg, BOOL UpdateFg,
                       COLORREF Bg, BOOL UpdateBg)
  {
      if (UpdateBg)
      {
          if (UpdateFg)
          {
              SendMessage(Edit, WM_SETREDRAW, FALSE, 0);
          }
  
          SendMessage(Edit, EM_SETBKGNDCOLOR, FALSE, Bg);
  
          if (UpdateFg)
          {
              SendMessage(Edit, WM_SETREDRAW, TRUE, 0);
          }
      }
  
      if (UpdateFg)
      {
          CHARFORMAT2 Fmt;
  
          ZeroMemory(&Fmt, sizeof(Fmt));
          Fmt.cbSize = sizeof(Fmt);
          Fmt.dwMask = CFM_COLOR;
          Fmt.crTextColor = Fg;
          SendMessage(Edit, EM_SETCHARFORMAT, SCF_ALL, (LPARAM)&Fmt);
      }
  }
  
  #define EXTRA_VIS 3
  
  void
  RicheditScrollToLine(HWND Edit, ULONG Line, ULONG VisLines)
  {
      CHARRANGE Sel;
      ULONG CurLine;
      ULONG VisAround;
      ULONG TotalLines;
      LONG Scroll;
  
      //
      // Scroll the given line into view.  Try to keep
      // the line from being the first or last line
      // in view.
      //
      // Disable the window during this to prevent
      // the default richedit scrolling from occurring.
      //
  
      VisAround = VisLines / 2;
      if (VisAround > EXTRA_VIS)
      {
          VisAround = EXTRA_VIS;
      }
      
      TotalLines = (ULONG)SendMessage(Edit, EM_GETLINECOUNT, 0, 0);
      CurLine = (ULONG)SendMessage(Edit, EM_GETFIRSTVISIBLELINE, 0, 0);
  
      if (Line < CurLine + VisAround)
      {
          Scroll = (LONG)Line - (LONG)(CurLine + VisAround);
          if ((ULONG)-Scroll > CurLine)
          {
              Scroll = -(LONG)CurLine;
          }
      }
      else if (Line >= CurLine + VisLines - VisAround &&
               CurLine + VisLines < TotalLines)
      {
          Scroll = (LONG)Line - (LONG)(CurLine + VisLines - VisAround) + 1;
      }
      else
      {
          Scroll = 0;
      }
  
      if (Scroll)
      {
          SendMessage(Edit, EM_LINESCROLL, 0, Scroll);
      }
  
      Sel.cpMax = Sel.cpMin = (LONG)
          SendMessage(Edit, EM_LINEINDEX, Line, 0);
      SendMessage(Edit, EM_EXSETSEL, 0, (LPARAM)&Sel);
  }
  
  ULONG
  RicheditGetSelectionText(HWND Edit, PTSTR Buffer, ULONG BufferChars)
  {
      CHARRANGE Sel;
  
      SendMessage(Edit, EM_EXGETSEL, 0, (LPARAM)&Sel);
      if (Sel.cpMin >= Sel.cpMax)
      {
          return 0;
      }
  
      Sel.cpMax -= Sel.cpMin;
      if ((ULONG)Sel.cpMax + 1 > BufferChars)
      {
          return 0;
      }
  
      SendMessage(Edit, EM_GETSELTEXT, 0, (LPARAM)Buffer);
      return Sel.cpMax;
  }
  
  ULONG
  RicheditGetSourceToken(HWND Edit, PTSTR Buffer, ULONG BufferChars,
                         CHARRANGE* Range)
  {
      LRESULT Idx;
      TEXTRANGE GetRange;
      CHARRANGE Sel;
  
      //
      // Get the text for the line containing the selection.
      //
      
      SendMessage(Edit, EM_EXGETSEL, 0, (LPARAM)&Sel);
      if (Sel.cpMin > Sel.cpMax)
      {
          return 0;
      }
  
      if ((Idx = SendMessage(Edit, EM_LINEINDEX, -1, 0)) < 0)
      {
          return 0;
      }
      GetRange.chrg.cpMin = (LONG)Idx;
  
      if (!(Idx = SendMessage(Edit, EM_LINELENGTH, GetRange.chrg.cpMin, 0)))
      {
          return 0;
      }
      if (BufferChars <= (ULONG)Idx)
      {
          Idx = (LONG)BufferChars - 1;
      }
      GetRange.chrg.cpMax = GetRange.chrg.cpMin + (LONG)Idx;
      GetRange.lpstrText = Buffer;
      if (!SendMessage(Edit, EM_GETTEXTRANGE, 0, (LPARAM)&GetRange))
      {
          return 0;
      }
  
      //
      // Check and see if the selection is within a source token.
      //
      
      PTSTR Scan = Buffer + (Sel.cpMin - GetRange.chrg.cpMin);
      if (!iscsym(*Scan))
      {
          return 0;
      }
  
      //
      // Find the start of the token and validate it.
      //
      
      PTSTR Start = Scan;
      if (Start > Buffer)
      {
          while (--Start >= Buffer && iscsym(*Start))
          {
              // Back up.
          }
          Start++;
      }
      if (!iscsymf(*Start))
      {
          return 0;
      }
  
      //
      // Find the end of the token.
      //
  
      Scan++;
      while (iscsym(*Scan))
      {
          Scan++;
      }
  
      ULONG Len;
      
      // Chop the buffer down to just the token and return.
      Len = (ULONG)(Scan - Start);
      memmove(Buffer, Start, Len);
      Buffer[Len] = 0;
      Range->cpMin = GetRange.chrg.cpMin + (LONG)(Start - Buffer);
      Range->cpMax = Range->cpMin + Len;
      return Len;
  }
  
  #undef DEFINE_GET_WINDATA
  #undef ASSERT_CLASS_TYPE
  
  
  #ifndef DBG
  
  #define ASSERT_CLASS_TYPE(p, ct)        ((VOID)0)
  
  #else
  
  #define ASSERT_CLASS_TYPE(p, ct)        if (p) { AssertType(*p, ct); }
  
  #endif
  
  
  
  #define DEFINE_GET_WINDATA(ClassType, FuncName)         \
  ClassType *                                             \
  Get##FuncName##WinData(                                 \
      HWND hwnd                                           \
      )                                                   \
  {                                                       \
      ClassType *p = (ClassType *)                        \
          GetWindowLongPtr(hwnd, GWLP_USERDATA);          \
                                                          \
      ASSERT_CLASS_TYPE(p, ClassType);                    \
                                                          \
      return p;                                           \
  }
  
  
  #include "fncdefs.h"
  
  
  #undef DEFINE_GET_WINDATA
  #undef ASSERT_CLASS_TYPE
  