2025-04-27 07:49:33 -04:00

656 lines
21 KiB
C++

/******************************************************************************
Source File: GPD Viewer.CPP
This file implements the GPD viewing/editing class.
Copyright (c) 1997 by Microsoft Corporation. All Rights Reserved.
A Pretty Penny Enterprises Production.
Change History:
03-24-1997 Bob_Kjelgaard@Prodigy.Net Created it
******************************************************************************/
#include "StdAfx.H"
#include <ProjNode.H>
#include "ModlData\Resource.H"
#include <GPDFile.H>
#include "GPDView.H"
#include "Resource.H"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/******************************************************************************
CGPDViewer class
This class implements the GPD viewer.
******************************************************************************/
/******************************************************************************
CGPDViewer::MarkError
This private member highlights the given line in the error display.
******************************************************************************/
void CGPDViewer::MarkError(unsigned u) {
CWnd *pcwndErrors = m_cdbError.GetDlgItem(IDC_Errors);
unsigned uStart = pcwndErrors -> SendMessage(EM_LINEINDEX, u, 0);
if (uStart == (unsigned) -1)
return; // Index requested is out of range
unsigned uEnd = uStart +
pcwndErrors -> SendMessage(EM_LINELENGTH, uStart, 0);
m_iLine = (int) u;
pcwndErrors -> SendMessage(EM_SETSEL, uStart, uEnd);
CString csError = GetDocument() -> ModelData() -> Error(u);
m_csb.SetPaneText(0, csError);
SetFocus();
// If the string starts with the GPD name, scroll to the line
if (!csError.Find(GetDocument() -> ModelData() -> Name()) &&
0 < csError.Mid(3 +
GetDocument() -> ModelData() -> Name().GetLength()).Find(_T('('))) {
// Extract the line number, and bop on down to it!
csError = csError.Mid(3 + GetDocument() -> ModelData() ->
Name().GetLength());
int iLine = atoi(csError.Mid(1 + csError.Find(_T('('))));
GetRichEditCtrl().SetSel(GetRichEditCtrl().LineIndex(-1 + iLine),
GetRichEditCtrl().LineIndex(-1 + iLine));
GetRichEditCtrl().LineScroll(iLine - (7 +
GetRichEditCtrl().GetFirstVisibleLine()));
}
pcwndErrors -> SendMessage(WM_HSCROLL, SB_TOP, NULL);
}
/******************************************************************************
CGPDViewer::FillErrorBar
This fills the error dialog bar with the current set if errors, if there are
any...
******************************************************************************/
void CGPDViewer::FillErrorBar() {
CModelData& cmd = *GetDocument() -> ModelData();
if (cmd.HasErrors()) {
m_cdbError.Create(GetParentFrame(), IDD_GPDErrors, CBRS_BOTTOM,
IDD_GPDErrors);
GetParentFrame() -> RecalcLayout();
// Make a big string out of all of the messages and insert it into the
// rich edit control.
CString csErrors;
for (unsigned u = 0; u < cmd.Errors(); u++)
csErrors += cmd.Error(u) + _TEXT("\r\n");
m_cdbError.SetDlgItemText(IDC_Errors, csErrors);
MarkError(0);
SetFocus();
}
else {
CString csWork;
csWork.LoadString(IDS_NoSyntaxErrors);
m_csb.SetPaneText(0, csWork);
}
}
/******************************************************************************
CGPDViewer::Color
This private member syntax colors the rich edit controls contents using the
information gleaned from the GPD file's analysis.
******************************************************************************/
void CGPDViewer::Color() {
CHARRANGE crCurrentSel;
CHARFORMAT cf;
CModelData& cmd = *(GetDocument() -> ModelData());
CRichEditCtrl& crec = GetRichEditCtrl();
m_bInColor = TRUE;
// Turn off change and selection notification messages
crec.SetEventMask(GetRichEditCtrl().GetEventMask() &
~(ENM_CHANGE | ENM_SELCHANGE | ENM_SCROLLEVENTS));
crec.GetSel(crCurrentSel);
crec.GetDefaultCharFormat(cf);
cf.dwEffects &= ~CFE_AUTOCOLOR;
cf.dwMask |= CFM_COLOR;
// Color each visible line as it was classsified visibility is
// determined by checking the character bounds against the client
// rectangle for the control.
int iTop = m_iTopLineColored = crec.GetFirstVisibleLine();
int i = iTop;
int iLineHeight = crec.GetCharPos(crec.LineIndex(i+1)).y -
crec.GetCharPos(crec.LineIndex(i)).y;
CRect crEdit;
crec.GetClientRect(crEdit);
crec.LockWindowUpdate(); // Don't let this show until done!
crec.HideSelection(TRUE, TRUE);
do {
cf.crTextColor = LineColor(i);
crec.SetSel(crec.LineIndex(i), crec.LineIndex(i) +
crec.LineLength(crec.LineIndex(i)));
crec.SetSelectionCharFormat(cf);
}
while (++i < cmd.LineCount() &&
crec.GetCharPos(crec.LineIndex(i)).y + iLineHeight <
crEdit.bottom - 1);
// Restore the original position of the cursor, and then the original
// line (in case the cursor is no longer on this page).
crec.SetSel(crCurrentSel);
crec.LineScroll(iTop - crec.GetFirstVisibleLine());
crec.HideSelection(FALSE, TRUE);
crec.UnlockWindowUpdate(); // Let it shine!
// Restore the notification mask
crec.SetEventMask(GetRichEditCtrl().GetEventMask() |
ENM_CHANGE | ENM_SELCHANGE | ENM_SCROLLEVENTS);
if (m_bStart)
FillErrorBar();
m_bInColor = FALSE;
}
/******************************************************************************
CGPDViewer::LineColor
This determines what color to make a line. It is copmplicated a bit by the
fact that the Rich Edit control gives false values for line length on long
files. Probably some 64K thing, but I sure can't fix it.
******************************************************************************/
unsigned CGPDViewer::LineColor(int i) {
CByteArray cba;
CRichEditCtrl& crec = GetRichEditCtrl();
cba.SetSize(max(crec.LineLength(i) + sizeof (unsigned), 100));
CString csLine((LPCTSTR) cba.GetData(),
crec.GetLine(i, (LPSTR) cba.GetData(),
cba.GetSize() - sizeof (unsigned)));
if (csLine.Find(_T("*%")) == -1)
return RGB(0, 0, 0);
// Errors
if (csLine.Find(_T("Error:")) > csLine.Find(_T("*%")))
return RGB(0x80, 0, 0);
// Warnings
if (csLine.Find(_T("Warning:")) > csLine.Find(_T("*%")))
return RGB(0x80, 0x80, 0);
return RGB(0, 0x80, 0);
}
/******************************************************************************
CGPDViewer::UpdateNow
This private member updates the underlying GPD and marks the document as
changed, and the edit control as unmodified. It is called whenever this
needs to be done.
*******************************************************************************/
void CGPDViewer::UpdateNow() {
// Don't do this if nothing's chenged...
if (!GetRichEditCtrl().GetModify())
return;
CWaitCursor cwc; // Just in case
if (m_uTimer)
KillTimer(m_uTimer);
m_uTimer = 0;
GetDocument() -> ModelData() -> UpdateFrom(GetRichEditCtrl());
GetDocument() -> SetModifiedFlag();
GetRichEditCtrl().SetModify(FALSE);
}
IMPLEMENT_DYNCREATE(CGPDViewer, CRichEditView)
CGPDViewer::CGPDViewer() {
m_iLine = m_uTimer = 0;
m_bInColor = FALSE;
m_bStart = TRUE;
m_iTopLineColored = -1;
}
CGPDViewer::~CGPDViewer() {
}
BEGIN_MESSAGE_MAP(CGPDViewer, CRichEditView)
//{{AFX_MSG_MAP(CGPDViewer)
ON_WM_DESTROY()
ON_COMMAND(ID_FILE_PARSE, OnFileParse)
ON_CONTROL_REFLECT(EN_CHANGE, OnChange)
ON_WM_TIMER()
ON_CONTROL_REFLECT(EN_VSCROLL, OnVscroll)
ON_WM_VSCROLL()
ON_COMMAND(ID_FILE_SAVE, OnFileSave)
ON_COMMAND(ID_FILE_SAVE_AS, OnFileSaveAs)
ON_UPDATE_COMMAND_UI(ID_EDIT_PASTE, OnUpdateEditPaste)
ON_UPDATE_COMMAND_UI(ID_EDIT_UNDO, OnUpdateEditUndo)
//}}AFX_MSG_MAP
ON_UPDATE_COMMAND_UI(IDC_Next, OnUpdateNext)
ON_UPDATE_COMMAND_UI(IDC_Previous, OnUpdatePrevious)
ON_COMMAND(IDC_RemoveError, OnRemoveError)
ON_COMMAND(IDC_Next, OnNext)
ON_COMMAND(IDC_Previous, OnPrevious)
ON_NOTIFY_REFLECT(EN_SELCHANGE, OnSelChange)
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CGPDViewer diagnostics
#ifdef _DEBUG
void CGPDViewer::AssertValid() const {
CRichEditView::AssertValid();
}
void CGPDViewer::Dump(CDumpContext& dc) const {
CRichEditView::Dump(dc);
}
#endif //_DEBUG
/******************************************************************************
CGPDViewer::OnDestroy
Handles the required project node notification when the view is destroyed.
A GP Fault is a terrible thing to signal.
******************************************************************************/
void CGPDViewer::OnDestroy() {
CRichEditView::OnDestroy();
if (GetDocument() -> ModelData())
GetDocument() -> ModelData() -> OnEditorDestroyed();
}
/******************************************************************************
CGPDViewer::OnInitialUpdate
This is the wake-up call. We fill the view from the GPD's contents, and
eventually color things to suit us.
******************************************************************************/
void CGPDViewer::OnInitialUpdate() {
GetParentFrame() -> ModifyStyle(0, WS_OVERLAPPEDWINDOW);
CRichEditView::OnInitialUpdate();
if (m_csb.Create(GetParentFrame())) {
static UINT auid[] = {ID_SEPARATOR, ID_LineIndicator};
m_csb.SetIndicators(auid, 2);
m_csb.SetPaneInfo(1, ID_LineIndicator, SBPS_NORMAL, 200);
GetParentFrame() -> RecalcLayout();
}
// We don't want EN_CHANGE messages while we load the control
GetRichEditCtrl().SetEventMask(GetRichEditCtrl().GetEventMask() &
~ENM_CHANGE);
// We also do not want the control to wrap lines for us, as it messes up
// syntax coloring
m_nWordWrap = WrapNone;
WrapChanged();
GetDocument() -> ModelData() -> Fill(GetRichEditCtrl());
SetFocus();
// We want EN_CHANGE messages now, so we can update the cache
GetRichEditCtrl().SetEventMask(GetRichEditCtrl().GetEventMask() |
ENM_CHANGE);
m_uTimer = SetTimer((UINT) this, 500, NULL);
GetRichEditCtrl().SetSel(1, 1); // Have to change the selection!
GetRichEditCtrl().SetSel(0, 0);
}
/******************************************************************************
CGPDViewer::OnUpdateNext
Handles the proper enabling and disabling of the Next Error button
******************************************************************************/
void CGPDViewer::OnUpdateNext(CCmdUI *pccu) {
pccu -> Enable((unsigned) m_iLine < -1 +
GetDocument() -> ModelData() -> Errors());
}
/******************************************************************************
CGPDViewer::OnUpdatePrevious
Handles the proper enabling and disabling of the previous error button
******************************************************************************/
void CGPDViewer::OnUpdatePrevious(CCmdUI *pccu) {
pccu -> Enable(m_iLine);
}
/******************************************************************************
CGPDViewer::OnRemoveError
This handles the "Remove Error" button. The line is removed from the edit
control and the log in the model data.
******************************************************************************/
void CGPDViewer::OnRemoveError() {
CModelData& cmd = *GetDocument() -> ModelData();
cmd.RemoveError((unsigned) m_iLine);
if (!cmd.HasErrors()) {
m_cdbError.DestroyWindow();
GetParentFrame() -> RecalcLayout();
SetFocus();
return;
}
CString csError;
for (unsigned u = 0; u < cmd.Errors(); u++)
csError += cmd.Error(u) + _TEXT("\r\n");
m_cdbError.SetDlgItemText(IDC_Errors, csError);
MarkError(m_iLine - ((unsigned) m_iLine == cmd.Errors()));
}
/******************************************************************************
CGPDViewer::OnNext
This handles the "Next" button by highlighting the next error.
******************************************************************************/
void CGPDViewer::OnNext() {
MarkError((unsigned) m_iLine + 1);
}
/******************************************************************************
CGPDViewer::OnPrevious
This marks the previous error in the list
******************************************************************************/
void CGPDViewer::OnPrevious() {
MarkError((unsigned) m_iLine - 1);
}
/******************************************************************************
CGPDViewer::OnFileParse
Syntax check the GPD file, and show us the results
******************************************************************************/
void CGPDViewer::OnFileParse() {
CWaitCursor cwc;
if (GetDocument() -> ModelData() -> HasErrors()) {
m_cdbError.DestroyWindow();
GetParentFrame() -> RecalcLayout();
}
// Save any changes made to the file.
if (GetRichEditCtrl().GetModify() || GetDocument() -> IsModified()) {
UpdateNow(); // Pick up any new changes
GetDocument() -> ModelData() -> Store();
GetDocument() -> SetModifiedFlag(FALSE);
}
if (!GetDocument() -> ModelData() -> Parse())
AfxMessageBox("Unusual and Fatal Error: Syntax Checker failed!");
FillErrorBar();
MessageBeep(MB_ICONASTERISK);
}
/******************************************************************************
CGPDViewer::OnChange
This gets called whenever a change is made to the contents of the file.
The coloring (now done only on the visible page) is updated, and the
appropriate flags are set. To keep performance smooth, the document is no
longer updated as a result of this message.
******************************************************************************/
void CGPDViewer::OnChange() {
// Since this is a RICHEDIT control, I override the
// CRichEditView::OnInitialUpdate() function to or the ENM_CHANGE flag
// into the control's event mask. Otherwise this message wouldn't be
// sent
// To avoid thrashing the GPD contents unneedfully, we wait for 1 second
// of inactivity before bashing the changes into the GPD.
GetDocument() -> SetModifiedFlag();
if (!m_bInColor)
Color();
}
/******************************************************************************
CGPDViewer::OnTimer
This handles the timeout of the timer used to batch changes into the
underlying document. If this isn't for that timer, we pass it on to the base
class.
******************************************************************************/
void CGPDViewer::OnTimer(UINT uEvent) {
// If this isn't our timer, let the base class do what it will with it.
if (m_uTimer == uEvent)
if (m_bStart) {
if (GetRichEditCtrl().GetLineCount() <
GetDocument() -> ModelData() -> LineCount())
return; // The rich edit control isn't ready, yet...
KillTimer(m_uTimer);
Color();
m_uTimer = 0;
m_bStart = FALSE;
}
else
UpdateNow();
else
CRichEditView::OnTimer(uEvent);
}
/******************************************************************************
CGPDViewer::OnSelChange
This handles the message sent by the control when the selection changes. I'm
hoping this means whenever the caret moves, since the selection, while empty,
has changed.
******************************************************************************/
void CGPDViewer::OnSelChange(LPNMHDR pnmh, LRESULT *plr) {
SELCHANGE* psc = (SELCHANGE *) pnmh;
long lLine = GetRichEditCtrl().LineFromChar(psc -> chrg.cpMax);
CString csWork;
csWork.Format(_T("Line %d, Column %d"), lLine + 1,
1 + psc -> chrg.cpMax - GetRichEditCtrl().LineIndex(lLine));
m_csb.SetPaneText(1, csWork);
}
/******************************************************************************
CGPDViewer::OnUpdate
If the first update hasn't been made, do nothing. Otherwise, redo the error
bar, because someone just syntax checked the workspace.
******************************************************************************/
void CGPDViewer::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) {
if (m_bStart) // Have we already done the first update?
return;
// If there's a dialog bar, can it, and refill as needed
if (m_cdbError.GetSafeHwnd()) {
m_cdbError.DestroyWindow();
GetParentFrame() -> RecalcLayout();
}
FillErrorBar();
}
/******************************************************************************
CGPDViewer::QueryAcceptData
Override the Rich Edit Control default behavior, because we (a) don't have
an associated RichEditDoc, and (b) we don't want to paste anything but text.
Not even rich text, because we control the formatting, and don't want to
paste it.
******************************************************************************/
HRESULT CGPDViewer::QueryAcceptData(LPDATAOBJECT lpdo, CLIPFORMAT* lpcf, DWORD,
BOOL bReally, HGLOBAL hgMetaFile) {
_ASSERTE(lpcf != NULL);
COleDataObject codo;
codo.Attach(lpdo, FALSE);
// if format is 0, then force particular formats if available
if (*lpcf == 0 && (m_nPasteType == 0)&& codo.IsDataAvailable(CF_TEXT)) {
*lpcf = CF_TEXT;
return S_OK;
}
return E_FAIL;
}
/******************************************************************************
CGPDViewer::OnVScroll()
This function is called when EN_VSCROLL messages are refelected from the
edit control. As long as we are not coloring, we color the new page. The
documentation says this message comes BEFORE the scolling occurs, but it
obviously occurs afterwards.
******************************************************************************/
void CGPDViewer::OnVscroll() {
// Even though we turn scroll notifications off in the color routine,
// we still get them, so use a flag to keep from recursive death.
if (m_iTopLineColored != GetRichEditCtrl().GetFirstVisibleLine() &&
!m_bInColor)
Color();
}
/******************************************************************************
CGPDViewer::OnVScroll(UINT uCode, UINT uPosition, CScrollBar *pcsb)
This is called whwnever the scoll bar gets clicked. This may seem
redundant, but EN_VSCROLL messages don't get sent when the thumb itself is
moved with the mouse, and WM_VSCROLL doesn't get sent when the keyboard
interface is used. So you get to lose either way.
This control is buggy as can be, IMHO. Next time I want to do text editing,
I'll use a third party tool. They starve if they don't get it right.
******************************************************************************/
void CGPDViewer::OnVScroll(UINT uCode, UINT uPosition, CScrollBar* pcsb) {
CRichEditView::OnVScroll(uCode, uPosition, pcsb);
OnVscroll();
}
/******************************************************************************
CGPDViewer::OnFileSave
CGPDViewer::OnFileSaveAs
Since we don't update the document as changes are made in the editor, we have
to intercept these, update the document, and then pass these on to the
document.
******************************************************************************/
void CGPDViewer::OnFileSave() {
UpdateNow();
GetDocument() -> OnFileSave();
}
void CGPDViewer::OnFileSaveAs() {
UpdateNow();
GetDocument() -> OnFileSaveAs();
}
/******************************************************************************
CGPDViewer::OnUpdateEditPaste
CGPDViewer::OnUpdateEditUndo
These override the default processing for these menu items. Undo isn't
possible, because of the syntax coloring. Paste is only possible with a text
format.
*******************************************************************************/
void CGPDViewer::OnUpdateEditPaste(CCmdUI* pccui) {
pccui -> Enable(IsClipboardFormatAvailable(CF_TEXT));
}
void CGPDViewer::OnUpdateEditUndo(CCmdUI* pccui) {
pccui -> Enable(FALSE);
}