/* Copyright 1997 David Coppit. Permission to use and modify this code for research purposes is granted to provided that this copyright statement retained in all derivative software. Many thanks to Dietmar Kuehl for his help. Some of this code is based on his streams examples at: http://www.informatik.uni-konstanz.de/~kuehl/c++/iostream/ This file, along with winbuff.cpp, PromptDialog.h, and PromptDialog.cpp implement a Windows MFC compatible text-based input window for cout and cin. A tutorial and more information can be found at http://www.coppit.org/soft_eng/winbuf/index.html */ #include "winbuff.h" #include "resource.h" #include "PromptDialog.h" #include "StdAfx.h" inWinStream::inWinStream(PromptDialog *mp, int bufferSize): istream(theWinBuf = new winBuf(mp, bufferSize, 0)) { ASSERT(bufferSize > 0); } inWinStream::~inWinStream() { delete theWinBuf; } outWinStream::outWinStream(PromptDialog* md, int bufferSize): ostream(theWinBuf = new winBuf(md, 0, bufferSize)) { ASSERT(bufferSize >= 0); } outWinStream::~outWinStream() { delete theWinBuf; } errWinStream::errWinStream(int bufferSize): ostream(theWinErr = new winErr(bufferSize)) { ASSERT(bufferSize > 0); } errWinStream::~errWinStream() { delete theWinErr; } //############################################################################ /* Construct a winBuf for either input or output. The buffer size for input defaults to 256, and the default for output is 0. Store a pointer to the dialog box so that we can read and write to and read from it. */ winBuf::winBuf(PromptDialog* inDialog, int inputBufferSize, int outputBufferSize): streambuf(), m_dialog(inDialog) { if (outputBufferSize) { // Create some space in the class for the buffer. (It's deleted in the destructor.) char *ptr = new char[outputBufferSize]; // Set the output buffer start and end pointers. setp(ptr, ptr + outputBufferSize); } else { // We won't be using any buffering for input, and the start and end pointers will be 0. setp(0,0); } if (inputBufferSize) { // Create some space in the class for the buffer. (It's deleted in the destructor.) char *ptr = new char[inputBufferSize]; // Set the input buffer start, current, and end pointers. Current == end because we // want to force an input from the Window at the first use of the class. setg(ptr,ptr+inputBufferSize,ptr+inputBufferSize); } else { // We won't be using any buffering for output, and the start and end pointers will be 0. setg(0,0,0); } // This variable says whether a new line of input is necessary. needInput = TRUE; // One can temporarily disable output if needed. outputEnabled = TRUE; } winBuf::~winBuf() { // Clear any output from the output buffer before dying. sync(); // Delete any buffer space. delete[] pbase(); delete[] eback(); } void winBuf::enableOutput() { outputEnabled = TRUE; } void winBuf::disableOutput() { outputEnabled = FALSE; } int winBuf::sync() { // If we're using an output buffer, flush it. if (pbase() != pptr()) put_buffer(); return 0; } /* Underflow is called whenever a character needs to be read. The following things happen: Get a character from the get buffer (which might entail asking for a line of input from the dialog box). If it's a newline, we change it to an EOF. WE DON'T MOVE THE POINTER TO THE CURRENT CHARACTER IN THE GET BUFFER! This is done by the superclass. */ int winBuf::underflow() { int c; // Are we at the end of the buffer, or have we exhausted the input that we read in before? if ((gptr() == egptr()) || needInput) { needInput = FALSE; c = get_buffer(); } else { c = *gptr(); } // If we've finished reading a line of input, reset the pointers and needInput. if (c == '\n') { setg(eback(),gptr() + 1,egptr()); needInput = TRUE; } return c; } /* Here we get a buffer's worth of input from our dialog box. If there's no input in our dialog box, we wait until the user enters some or wishes to quit. */ int winBuf::get_buffer() { int bufferLength = (egptr() - eback()); if (m_dialog->m_strEdit1.GetLength() == 0) { // Clear the input field in the Window. (i.e. Make sure the window // reflects the contents of the associated variable.) m_dialog->UpdateData(TRUE); // Clear the state variables. m_dialog->OKClicked = FALSE; m_dialog->CancelClicked = FALSE; // Wait for input. while (!(m_dialog->OKClicked && (m_dialog->m_strEdit1 != "")) && !m_dialog->CancelClicked) { MSG message; // This allows the events for the other window to occur, namely user // keypresses and mouse clicks. while (::PeekMessage(&message, NULL, 0, 0, PM_REMOVE)) { if (!IsDialogMessage(m_dialog->GetSafeHwnd(), &message)) { ::TranslateMessage(&message); ::DispatchMessage(&message); } } } if (m_dialog->OKClicked) { // Echo the input. m_dialog->m_displaytext += m_dialog->m_strEdit1 + "\r\n"; // Tell the dialog to copy the member variable into the box's output area. m_dialog->UpdateData(FALSE); // Go to the end of the output edit box. m_dialog->m_ViewControl.LineScroll(m_dialog->m_ViewControl.GetLineCount()); // The updated dialog box item is invalid. m_dialog->GetDlgItem(IDC_EDIT1)->Invalidate(TRUE); // Redraw the window. m_dialog->UpdateWindow(); // Tack a newline at the end to help us know when to send an EOF. m_dialog->m_strEdit1 += '\n'; } } if (m_dialog->OKClicked) { // Copy a buffer's worth of input into the get buffer. strncpy(eback(), (LPCSTR)m_dialog->m_strEdit1,bufferLength); // Remove that data from the input box's variable. m_dialog->m_strEdit1 = m_dialog->m_strEdit1.Right(m_dialog->m_strEdit1.GetLength() - bufferLength); setg(eback(), eback(), egptr()); return *(eback()); } else return EOF; } /* This function is called when the output buffer is full. The first task is to write the buffer to the window, along with the current character if we're not doing any buffering. */ int winBuf::overflow(int c) { if (outputEnabled = FALSE) return 0; put_buffer(); if (c != EOF) if (pbase() == epptr()) put_char(c); else sputc(c); return 0; } /* This function ouputs a single character to the window. */ void winBuf::put_char(int chr) { // Convert any \n to \r\n if ((char)chr == '\n') { m_dialog->m_displaytext += "\r\n"; } else { m_dialog->m_displaytext += (char)chr; } // Tell the dialog to copy the member variable into the box's output area. m_dialog->UpdateData(FALSE); // Go to the end of the output edit box. m_dialog->m_ViewControl.LineScroll(m_dialog->m_ViewControl.GetLineCount()); // The updated dialog box item is invalid. m_dialog->GetDlgItem(IDC_EDIT1)->Invalidate(TRUE); // Redraw the window. m_dialog->UpdateWindow(); } /* Here we output a buffer's worth of data to the window. */ void winBuf::put_buffer() { int bufferLength = (pptr() - pbase()); if (bufferLength == 0) return; // Convert the string into a CString. char *putString = new char[bufferLength + 1]; strncpy(putString, pbase(), bufferLength); putString[bufferLength] = 0; CString tempString(putString); // Change all the "\n"s to "\r\n"s. while (tempString.GetLength() != 0) { int subStrLoc(tempString.Find('\n')); if (subStrLoc == -1) { m_dialog->m_displaytext += tempString; tempString = ""; } else { m_dialog->m_displaytext += tempString.Left(subStrLoc) + "\r\n"; tempString = tempString.Right(tempString.GetLength() - subStrLoc - 1); } } // Tell the dialog to copy the member variable into the box's output area. m_dialog->UpdateData(FALSE); // Go to the end of the output edit box. m_dialog->m_ViewControl.LineScroll(m_dialog->m_ViewControl.GetLineCount()); // The updated dialog box item is invalid. m_dialog->GetDlgItem(IDC_EDIT1)->Invalidate(TRUE); // Redraw the window. m_dialog->UpdateWindow(); setp(pbase(), epptr()); delete [] putString; } //############################################################################ winErr::winErr(int bufferSize): streambuf(), m_bufferSize(bufferSize) { if (bufferSize > 0) { // Create some space in the class for the buffer. (It's deleted in the destructor.) errorBuffer = new char[bufferSize]; // Set the output buffer start and end pointers. } else { errorBuffer = new char[256]; } strcpy (errorBuffer, ""); setp (0,0); setg (0,0,0); } winErr::~winErr() { // Clear any output from the output buffer before dying. sync(); // Delete any buffer space. delete [] errorBuffer; } /* We can't read from this class */ int winErr::underflow() { return 0; } /* Here I pop up an error box if needed. Unfortunately, this design requires a box for every line of output, but hey, one can always redirect cerr to cout... */ int winErr::overflow(int c) { if (c != '\n') { put_char(c); } else { if (strcmp (errorBuffer, "")) { MessageBox(AfxGetMainWnd()->GetSafeHwnd(),errorBuffer,"Error",MB_OK | MB_ICONEXCLAMATION); strcpy (errorBuffer,""); } return EOF; } return 0; } /* Dump any output to an error box. */ int winErr::sync() { if (strcmp(errorBuffer, "")) { MessageBox(AfxGetMainWnd()->GetSafeHwnd(),errorBuffer,"Error",MB_OK | MB_ICONEXCLAMATION); strcpy (errorBuffer,""); } return 0; } /* Tack the character on to the end of the errorBuffer. */ void winErr::put_char(int chr) { // Check to see if we've overrun our buffer size. If we have, allocate some more space. if (m_bufferSize == strlen(errorBuffer)+1) { char *tempPtr = new char[m_bufferSize]; m_bufferSize += m_bufferSize; strcpy (tempPtr, errorBuffer); errorBuffer = tempPtr; } strncat (errorBuffer, (char *)&chr,1); }