/* The original version of this program can be found at http://damb.dk */
#include <windows.h>
#include <string>
#include <vector>
#include <fstream>
#include <algorithm>
#include <iterator>
std::vector<std::string >FileData; // This is the store for the text
const unsigned int LineHeight = 16;
unsigned int CharWidth;
unsigned int YOffset; // The current scroll offset
unsigned int CurrentLine, CurrentColumn;
char CurrentFileName[256];
enum CommandEnum
{
FileOpenCmd = 1025,
FileSaveCmd,
FileCloseCmd,
AboutCmd,
DeleteCmd,
BackSpaceCmd,
EnterCmd,
JoinLineCmd
};
ACCEL Accelerators[] =
{
{FVIRTKEY, VK_DELETE, DeleteCmd},
{FVIRTKEY, VK_BACK, BackSpaceCmd},
{FVIRTKEY, VK_RETURN, EnterCmd},
{FVIRTKEY|FCONTROL, 'J', JoinLineCmd}
};
HINSTANCE InstanceHandle;
HWND MainWindow;
HFONT Font;
bool ReadFile(const char *aFileName)
{
std::ifstream File(aFileName);
if(!File)
return false;
std::string Line;
while(std::getline(File, Line))
{
#ifdef __BORLANDC__
if(!Line.empty() && Line[Line.size()] == 0)
{
Line.resize(Line.size() - 1);
}
#endif
FileData.push_back(Line);
}
return true;
}
unsigned int GetWindowHeight()
{
RECT R;
GetClientRect(MainWindow, &R);
return (unsigned int )(R.bottom - R.top);
}
unsigned int GetWindowWidth()
{
RECT R;
GetClientRect(MainWindow, &R);
return (unsigned int )(R.right - R.left);
}
void SetScroll()
{
int Height = GetWindowHeight();
SCROLLINFO ScrollInfo;
memset(&ScrollInfo, 0, sizeof(ScrollInfo));
ScrollInfo.cbSize = sizeof(ScrollInfo);
ScrollInfo.fMask = SIF_ALL;
ScrollInfo.nPos = YOffset;
ScrollInfo.nMax = FileData.size()*LineHeight;
ScrollInfo.nPage = (Height/LineHeight)*LineHeight;
SetScrollInfo(MainWindow, SB_VERT, &ScrollInfo, TRUE);
}
void DoPaint(HDC aDc)
{
size_t a;
HGDIOBJ OldFont = SelectObject(aDc, Font);
size_t NumLines = (GetWindowHeight() + LineHeight - 1)/LineHeight;
int FirstLine = YOffset/LineHeight;
for(a = 0; a < NumLines && a + FirstLine < FileData.size(); a++)
TextOut(aDc, 0, a*LineHeight, FileData[a + FirstLine].c_str(), FileData[a + FirstLine].size());
SelectObject(aDc, OldFont);
}
void RedrawCurrentLine()
{
HideCaret(MainWindow);
HDC dc = GetDC(MainWindow);
HGDIOBJ OldFont = SelectObject(dc, Font);
COLORREF BackColor = GetBkColor(dc);
HBRUSH Brush = CreateSolidBrush(BackColor);
RECT Rect = {0, CurrentLine*LineHeight - YOffset, GetWindowWidth(), (CurrentLine + 1)*LineHeight};
FillRect(dc, &Rect, Brush);
TextOut(dc, 0, CurrentLine*LineHeight - YOffset, FileData[CurrentLine].c_str(), FileData[CurrentLine].size());
SelectObject(dc, OldFont);
DeleteObject(Brush);
ReleaseDC(MainWindow, dc);
SetCaretPos(CurrentColumn*CharWidth, CurrentLine*LineHeight - YOffset);
ShowCaret(MainWindow);
}
void OnVScroll(int code, int pos)
{
SCROLLINFO ScrollInfo;
memset(&ScrollInfo, 0, sizeof(ScrollInfo));
ScrollInfo.cbSize = sizeof(ScrollInfo);
ScrollInfo.fMask = SIF_POS | SIF_RANGE | SIF_PAGE;
GetScrollInfo(MainWindow, SB_VERT, &ScrollInfo);
switch(code)
{
case SB_LINEUP:
ScrollInfo.nPos -= LineHeight;
break;
case SB_LINEDOWN:
ScrollInfo.nPos += LineHeight;
break;
case SB_PAGEDOWN:
ScrollInfo.nPos += ScrollInfo.nPage;
break;
case SB_PAGEUP:
ScrollInfo.nPos -= ScrollInfo.nPage;
break;
case SB_THUMBPOSITION:
ScrollInfo.nPos = (pos/LineHeight)*LineHeight;
break;
case SB_ENDSCROLL:
InvalidateRect(MainWindow, 0, TRUE);
default:
return;
}
if(ScrollInfo.nPos < 0)
ScrollInfo.nPos = 0;
else if(ScrollInfo.nPos > ScrollInfo.nMax)
ScrollInfo.nPos = ScrollInfo.nMax;
SetScrollInfo(MainWindow, SB_VERT, &ScrollInfo, TRUE);
YOffset = ScrollInfo.nPos;
// Make sure that the caret (CurrentLine) is within the visible window
unsigned int Height = GetWindowHeight();
if(CurrentLine*LineHeight < YOffset)
{
CurrentLine = (YOffset + LineHeight - 1)/LineHeight;
}
else if(CurrentLine*LineHeight > YOffset + Height)
{
CurrentLine = (ScrollInfo.nPos + Height)/LineHeight;
}
SetCaretPos(CurrentColumn*CharWidth, CurrentLine*LineHeight - YOffset);
}
void MakeVisible()
{ // Make sure that CurrentLine is within visible window, scroll as needed.
unsigned int Height = GetWindowHeight();
if((CurrentLine + 2)*LineHeight > YOffset + Height)
{ // Caret past end window
SCROLLINFO ScrollInfo;
memset(&ScrollInfo, 0, sizeof(ScrollInfo));
ScrollInfo.cbSize = sizeof(ScrollInfo);
ScrollInfo.fMask = SIF_POS;
GetScrollInfo(MainWindow, SB_VERT, &ScrollInfo);
ScrollInfo.nPos = (((CurrentLine + 2)*LineHeight - Height)/LineHeight)*LineHeight;
YOffset = ScrollInfo.nPos;
SetScrollInfo(MainWindow, SB_VERT, &ScrollInfo, TRUE);
InvalidateRect(MainWindow, 0, TRUE);
}
else if(CurrentLine*LineHeight < YOffset)
{ // Caret above top of window
SCROLLINFO ScrollInfo;
memset(&ScrollInfo, 0, sizeof(ScrollInfo));
ScrollInfo.cbSize = sizeof(ScrollInfo);
ScrollInfo.fMask = SIF_POS;
GetScrollInfo(MainWindow, SB_VERT, &ScrollInfo);
ScrollInfo.nPos = CurrentLine*LineHeight;
YOffset = ScrollInfo.nPos;
SetScrollInfo(MainWindow, SB_VERT, &ScrollInfo, TRUE);
InvalidateRect(MainWindow, 0, TRUE);
}
}
void OnKeyDown(int aKey)
{
switch(aKey)
{
case VK_DOWN:
if(CurrentLine < FileData.size() - 1)
{
CurrentLine++;
MakeVisible();
SetCaretPos(CurrentColumn*CharWidth, CurrentLine*LineHeight - YOffset);
}
break;
case VK_UP:
if(CurrentLine)
{
CurrentLine--;
MakeVisible();
SetCaretPos(CurrentColumn*CharWidth, CurrentLine*LineHeight - YOffset);
}
break;
case VK_LEFT:
if(CurrentColumn)
{
CurrentColumn--;
MakeVisible();
SetCaretPos(CurrentColumn*CharWidth, CurrentLine*LineHeight - YOffset);
}
break;
case VK_RIGHT:
unsigned int Width = GetWindowWidth()/CharWidth;
if(CurrentColumn < Width - 1)
{
CurrentColumn++;
MakeVisible();
SetCaretPos(CurrentColumn*CharWidth, CurrentLine*LineHeight - YOffset);
}
break;
}
}
void OnChar(int aChar)
{
if(aChar < 32 || aChar >= 128) // Ignore not normal chars
return;
if(CurrentLine >= FileData.size())
return;
if(CurrentColumn <= FileData[CurrentLine].size())
{
FileData[CurrentLine].insert(FileData[CurrentLine].begin() + CurrentColumn, (char )aChar);
CurrentColumn++;
RedrawCurrentLine();
}
}
void OnDelete()
{
if(CurrentLine < FileData.size() && CurrentColumn < FileData[CurrentLine].size())
{
FileData[CurrentLine].erase(FileData[CurrentLine].begin() + CurrentColumn);
RedrawCurrentLine();
}
}
void OnBackspace()
{
if(CurrentLine < FileData.size() && CurrentColumn && CurrentColumn <= FileData[CurrentLine].size())
{
CurrentColumn--;
FileData[CurrentLine].erase(FileData[CurrentLine].begin() + CurrentColumn);
RedrawCurrentLine();
}
}
void OnEnter()
{
if(CurrentLine >= FileData.size())
return;
std::string NewLine;
if(CurrentColumn < FileData[CurrentLine].size())
{ // Split the line in two
NewLine = FileData[CurrentLine].substr(CurrentColumn);
FileData[CurrentLine] = FileData[CurrentLine].substr(0, CurrentColumn);
}
CurrentLine++;
FileData.insert(FileData.begin() + CurrentLine, NewLine);
CurrentColumn = 0;
SetScroll();
MakeVisible();
SetCaretPos(CurrentColumn*CharWidth, CurrentLine*LineHeight - YOffset);
InvalidateRect(MainWindow, 0, TRUE);
}
void OnJoinLine()
{
if(CurrentLine < FileData.size() - 1)
{
FileData[CurrentLine] += FileData[CurrentLine + 1];
FileData.erase(FileData.begin() + CurrentLine + 1);
InvalidateRect(MainWindow, 0, TRUE);
SetScroll();
}
}
void OnFileOpen()
{
static char Filter[] = "txt (*.txt)\0*.txt\0All (*.*)\0*.*\0";
OPENFILENAME OpenFilename;
memset(&OpenFilename, 0, sizeof(OpenFilename));
OpenFilename.lStructSize = sizeof(OpenFilename);
OpenFilename.hwndOwner = MainWindow;
OpenFilename.hInstance = InstanceHandle;
OpenFilename.lpstrFilter = Filter;
OpenFilename.lpstrFile = CurrentFileName;
OpenFilename.nMaxFile = sizeof(CurrentFileName);
OpenFilename.Flags = OFN_FILEMUSTEXIST;
OpenFilename.lpstrDefExt = "txt";
if(GetOpenFileName(&OpenFilename))
{
FileData.clear();
if(!ReadFile(CurrentFileName))
{
FileData.push_back("");
CurrentFileName[0] = 0;
std::string Msg = "Failed to open:\r\n";
Msg += CurrentFileName;
MessageBox(MainWindow, Msg.c_str(), "TextEditor", MB_OK | MB_ICONWARNING);
}
CurrentLine = 0;
CurrentColumn = 0;
YOffset = 0;
SetCaretPos(CurrentColumn*CharWidth, CurrentLine*LineHeight - YOffset);
SetScroll();
InvalidateRect(MainWindow, 0, TRUE);
}
}
void OnFileSave()
{
if(CurrentFileName[0] == 0)
{ // Need to Get A filename
static char Filter[] = "txt (*.txt)\0*.txt\0All (*.*)\0*.*\0";
OPENFILENAME OpenFilename;
memset(&OpenFilename, 0, sizeof(OpenFilename));
OpenFilename.lStructSize = sizeof(OpenFilename);
OpenFilename.hwndOwner = MainWindow;
OpenFilename.hInstance = InstanceHandle;
OpenFilename.lpstrFilter = Filter;
OpenFilename.lpstrFile = CurrentFileName;
OpenFilename.nMaxFile = sizeof(CurrentFileName);
OpenFilename.Flags = 0;
OpenFilename.lpstrDefExt = "txt";
if(!GetSaveFileName(&OpenFilename))
{
CurrentFileName[0] = 0;
return;
}
}
std::ofstream File(CurrentFileName);
if(!File)
{
std::string Msg = "Failed to open:\r\n";
Msg += CurrentFileName;
MessageBox(MainWindow, Msg.c_str(), "TextEditor", MB_OK | MB_ICONWARNING);
CurrentFileName[0] = 0;
return;
}
std::copy(FileData.begin(), FileData.end(), std::ostream_iterator<std::string>(File, "\n"));
}
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_CREATE:
{
MainWindow = hwnd;
Font = CreateFont(12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "Courier");
HDC Dc = GetDC(MainWindow);
HGDIOBJ OldFont = SelectObject(Dc, Font);
TEXTMETRIC TextMetric;
GetTextMetrics(Dc, &TextMetric);
CharWidth = TextMetric.tmAveCharWidth;
SelectObject(Dc, OldFont);
ReleaseDC(MainWindow, Dc);
break;
}
case WM_CHAR:
OnChar(wParam);
break;
case WM_COMMAND:
switch(LOWORD(wParam))
{
case DeleteCmd:
OnDelete();
break;
case BackSpaceCmd:
OnBackspace();
break;
case EnterCmd:
OnEnter();
break;
case JoinLineCmd:
OnJoinLine();
break;
case FileCloseCmd:
PostMessage(MainWindow, WM_QUIT, 0, 0);
break;
case FileOpenCmd:
OnFileOpen();
break;
case FileSaveCmd:
OnFileSave();
break;
case AboutCmd:
MessageBox(MainWindow, "TextEditor\r\nCopyright Bertel Brander 2005", "TextEditor", MB_OK);
break;
}
break;
case WM_SETFOCUS:
CreateCaret(MainWindow, 0, 2, LineHeight);
SetCaretPos(CurrentColumn*CharWidth, CurrentLine*LineHeight - YOffset);
ShowCaret(hwnd);
break;
case WM_KEYDOWN:
OnKeyDown(wParam);
break;
case WM_KILLFOCUS:
DestroyCaret();
HideCaret(hwnd);
break;
case WM_SIZE:
SetScroll();
break;
case WM_VSCROLL:
OnVScroll(LOWORD(wParam), HIWORD(wParam));
break;
case WM_PAINT:
HDC dc;
PAINTSTRUCT PaintStruct;
dc = BeginPaint(hwnd, &PaintStruct);
DoPaint(dc);
EndPaint(hwnd, &PaintStruct);
break;
default:
return DefWindowProc(hwnd,msg,wParam,lParam);
}
return 0;
}
HWND CreateMainWindow()
{
WNDCLASS wc;
memset(&wc, 0, sizeof(WNDCLASS));
wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS ;
wc.lpfnWndProc = (WNDPROC )MainWndProc;
wc.hInstance = InstanceHandle;
wc.hbrBackground = (HBRUSH )(COLOR_WINDOW + 1);
wc.lpszClassName = "TextEditorWndClass";
wc.lpszMenuName = 0;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
if(!RegisterClass(&wc))
return 0;
HMENU FileMenu = CreateMenu();
AppendMenu(FileMenu, MF_STRING, FileOpenCmd, "Open");
AppendMenu(FileMenu, MF_STRING, FileSaveCmd, "Save");
AppendMenu(FileMenu, MF_SEPARATOR, 0 , 0);
AppendMenu(FileMenu, MF_STRING, FileCloseCmd, "Close");
HMENU HelpMenu = CreateMenu();
AppendMenu(HelpMenu, MF_STRING, AboutCmd, "About");
HMENU MainMenu = CreateMenu();
AppendMenu(MainMenu, MF_POPUP, (UINT )FileMenu, "File");
AppendMenu(MainMenu, MF_POPUP, (UINT )HelpMenu, "Help");
return CreateWindow("TextEditorWndClass",
"Simple-Editor",
WS_MINIMIZEBOX | WS_VISIBLE |
WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_MAXIMIZEBOX |
WS_CAPTION | WS_BORDER | WS_SYSMENU | WS_THICKFRAME | WS_VSCROLL,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
0,
MainMenu,
InstanceHandle,
0);
}
int WINAPI WinMain(HINSTANCE Instance,
HINSTANCE,
LPSTR CmdLine,
INT)
{
InstanceHandle = Instance;
if(CmdLine[0])
{
if(!ReadFile(CmdLine))
{
MessageBox(0, "Failed To read file", "TextEditor", MB_OK);
FileData.push_back(""); // Need at least one empty line
}
else
{
strcpy(CurrentFileName, CmdLine);
}
}
else
{
FileData.push_back(""); // Need at least one empty line
}
if((MainWindow = CreateMainWindow()) == (HWND )0)
{
MessageBox(0, "Failed to create MainWindow!", "TextEditor", MB_OK);
return 0;
}
HACCEL AcceleratorHandle = CreateAcceleratorTable(Accelerators, sizeof(Accelerators)/sizeof(Accelerators[0]));
ShowWindow(MainWindow, SW_SHOW);
MSG Msg;
while(GetMessage(&Msg, 0, 0, 0))
{
if(!TranslateAccelerator(MainWindow, AcceleratorHandle, &Msg))
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
}
return Msg.wParam;
}