要求:
1. 改正typer程序中的不足
2. 增加读、写文件功能
3. 使用鼠标指定插入点
4. 在窗口标题中显示该程序运行的总时间(每秒更新一次)
5. 增加垂直滚动条,处理相应的操作。
实现:
1.水平滚动条与垂直滚动条都有,可使用鼠标滚轮,文件宽高都可多页(编辑过程中Caret移出窗口时,窗口会自动滚动);
2.可以新建,打开,编辑,保存,另存为文件(未考虑实际的文件编码,而是使用自定义的文件格式);
3.使用鼠标指定插入点;
4.文件修改未保存时新建或关闭会有询问;
5.窗口标题栏显示正在编辑的文件名,及程序运行总时间;
6.支持中英文混合编辑;
源码:
resource.h
1//{{NO_DEPENDENCIES}}
2// Microsoft Visual C++ generated include file.
3// Used by TyperZJ.rc
4//
5#define IDR_MENU 101
6#define IDR_ACCE 102
7
8// Next default values for new objects
9//
10#ifdef APSTUDIO_INVOKED
11#ifndef APSTUDIO_READONLY_SYMBOLS
12#define _APS_NEXT_RESOURCE_VALUE 103
13#define _APS_NEXT_COMMAND_VALUE 40003
14#define _APS_NEXT_CONTROL_VALUE 1001
15#define _APS_NEXT_SYMED_VALUE 101
16#endif
17#endif
18
TyperZJ.rc
1// Microsoft Visual C++ generated resource script.
2//
3#include "resource.h"
4
5#define APSTUDIO_READONLY_SYMBOLS
6/**////////////////////////////////////////////////////////////////////////////// 7//
8// Generated from the TEXTINCLUDE 2 resource.
9//
10#include "afxres.h"
11
12/**//////////////////////////////////////////////////////////////////////////////13#undef APSTUDIO_READONLY_SYMBOLS
14
15/**//////////////////////////////////////////////////////////////////////////////16// Chinese (Simplified, PRC) resources
17
18#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
19LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
20
21#ifdef APSTUDIO_INVOKED
22/**//////////////////////////////////////////////////////////////////////////////23//
24// TEXTINCLUDE
25//
26
271 TEXTINCLUDE
28BEGIN
29 "resource.h\0"
30END
31
322 TEXTINCLUDE
33BEGIN
34 "#include ""afxres.h""\r\n"
35 "\0"
36END
37
383 TEXTINCLUDE
39BEGIN
40 "\r\n"
41 "\0"
42END
43
44#endif // APSTUDIO_INVOKED
45
46
47/**//////////////////////////////////////////////////////////////////////////////48//
49// Menu
50//
51
52IDR_MENU MENU
53BEGIN
54 POPUP "文件(&F)"
55 BEGIN
56 MENUITEM "新建(&N)\tCtrl+N", ID_FILE_NEW
57 MENUITEM "打开(&O)\tCtrl+O", ID_FILE_OPEN
58 MENUITEM "保存(&S)\tCtrl+S", ID_FILE_SAVE
59 MENUITEM "另存为(&A)", ID_FILE_SAVE_AS
60 MENUITEM SEPARATOR
61 MENUITEM "退出(&X)\tCtrl+X", ID_FILE_CLOSE
62 END
63END
64
65
66/**//////////////////////////////////////////////////////////////////////////////67//
68// Accelerator
69//
70
71IDR_ACCE ACCELERATORS
72BEGIN
73 "N", ID_FILE_NEW, VIRTKEY, CONTROL, NOINVERT
74 "O", ID_FILE_OPEN, VIRTKEY, CONTROL, NOINVERT
75 "S", ID_FILE_SAVE, VIRTKEY, CONTROL, NOINVERT
76 "X", ID_FILE_CLOSE, VIRTKEY, CONTROL, NOINVERT
77END
78
79#endif // Chinese (Simplified, PRC) resources
80/**//////////////////////////////////////////////////////////////////////////////81
82
83
84#ifndef APSTUDIO_INVOKED
85/**//////////////////////////////////////////////////////////////////////////////86//
87// Generated from the TEXTINCLUDE 3 resource.
88//
89
90
91/**//////////////////////////////////////////////////////////////////////////////92#endif // not APSTUDIO_INVOKED
93
94
TyperZJ.cpp
1#ifndef UNICODE
2#define UNICODE
3#endif
4#ifndef _UNICODE
5#define _UNICODE
6#endif
7
8
9#include "resource.h"
10#include "afxres.h"
11#include <windows.h>
12
13#include <list>
14using std::list;
15
16
17// 半角,全角字符宽高硬编码
18#define CX_HALF 8
19#define CY_HALF 16
20#define CX_FULL (CX_HALF+CX_HALF)
21#define CY_FULL CY_HALF
22
23
24/**///////// 文本 25// 内存中不保存回车换行,内存中的文本至少一行,会有空行
26
27typedef list< TCHAR > TLINE;
28typedef list< TLINE > TTEXT;
29typedef TTEXT::iterator TPOSLINE;
30typedef TLINE::iterator TPOSCHAR;
31
32 // 当前行中当前字符宽度
33inline INT GetDeltaX( const TLINE &line, const TPOSCHAR &pc ) {
34 return ( ((pc==line.end())||(((UINT)(*pc))<0x80)) ? CX_HALF : CX_FULL );
35}
36 // 当前行中当前字符左侧 X 坐标
37INT GetX( TLINE &line, const TPOSCHAR &pc ) {
38 TPOSCHAR p;
39 INT x = 0;
40 for ( p = line.begin(); (p!=line.end())&&(p!=pc); ++p ) {
41 x += ::GetDeltaX( line, p );
42 }
43 return x;
44}
45 // 当前行中当前 X 坐标右侧字符
46TPOSCHAR GetPoschar( TLINE &line, INT x ) {
47 TPOSCHAR p;
48 for ( p = line.begin(); (p!=line.end())&&(x>0); ++p ) {
49 x -= ::GetDeltaX( line, p );
50 }
51 return p;
52}
53 // 当前行中当前字符高度
54inline INT GetDeltaY( const TLINE &line, const TPOSCHAR &pc ) {
55 return ( ((pc==line.end())||(((UINT)(*pc))<0x80)) ? CY_HALF : CY_FULL );
56}
57 // 当前文本中当前行高度
58inline INT GetDeltaY( const TTEXT &text, const TPOSLINE &pl ) {
59 return ( (pl==text.end()) ? (CY_HALF) : (::GetDeltaY(*pl,pl->begin())) );
60}
61 // 当前文本中当前文本行上方 Y 坐标
62INT GetY( TTEXT &text, const TPOSLINE &pl ) {
63 TPOSLINE p;
64 INT y = 0;
65 for ( p = text.begin(); (p!=text.end())&&(p!=pl); ++p ) {
66 y += ::GetDeltaY( text, p );
67 }
68 return y;
69}
70 // 当前文本中当前 Y 坐标下方文本行
71TPOSLINE GetPosline( TTEXT &text, INT y ) {
72 TPOSLINE p;
73 for ( p = text.begin(); (p!=text.end())&&(y>0); ++p ) {
74 y -= ::GetDeltaY( text, p );
75 }
76 if ( p == text.end() ) {
77 --p;
78 }
79 return p;
80}
81
82 // 当前文本中最长的行的长度,单位 字符数
83INT GetLineMaxLength( TTEXT &text ) {
84 INT iMax = 0, tmp;
85 TPOSLINE p;
86 for ( p = text.begin(); p != text.end(); ++p ) {
87 tmp = p->size();
88 if ( tmp > iMax ) {
89 iMax = tmp;
90 }
91 }
92 return iMax;
93}
94 // 文本宽度,单位 字符
95inline INT GetTextWidth( TTEXT &text ) {
96 return ::GetLineMaxLength( text );
97}
98 // 文本高度,单位 字符
99inline INT GetTextHeight( TTEXT &text ) {
100 return text.size();
101}
102 // 文本区宽度,单位 像素
103INT GetTextAreaWidth( TTEXT &text ) {
104 INT res = 0, tmp;
105 TPOSLINE pl;
106 for ( pl = text.begin(); pl != text.end(); ++pl ) {
107 tmp = ::GetX( *pl, pl->end() );
108 if ( tmp > res ) {
109 res = tmp;
110 }
111 }
112 return res;
113}
114 // 文本区高度,单位 像素
115inline INT GetTextAreaHeight( TTEXT &text ) {
116 return ::GetTextHeight(text) * CY_FULL; // 偷懒,因高度硬编码
117}
118
119
120/**///////// 文件121// 只能读入自己保存的文件,因为未考虑实际的文件编码
122// 内存中的文本与磁盘上的文件内容有些差别,但保证读写过程不会对文件造成修改,如:
123// 读入空文件会得到非空的有一个空行的文本,写文件时会将其过滤
124// szFileDir "F:\\ZJ\\"
125// szFileName "a.txt"
126
127 // 文件对话框,bOpen TRUE OPEN, FALSE SAVE;目录与文件名既输入也输出;放弃返回FALSE
128 // iMaxLen 为 szFileDir, szFileName 字符数组的大小,包括结尾的 NULL
129BOOL GetFileName( HWND hWnd, LPTSTR szFileDir, LPTSTR szFileName, INT iMaxLen, BOOL bOpen ) {
130 ::OPENFILENAME ofn = {0};
131 ofn.lStructSize = sizeof(ofn);
132 ofn.hwndOwner = hWnd;
133 ofn.hInstance = NULL;
134 TCHAR szFilter[100] = TEXT("文本文件\0*.txt\0所有文件\0*.*\0\0");
135 ofn.lpstrFilter = szFilter;
136 ofn.nFilterIndex = 1;
137 const int L = 1024;
138 TCHAR szFile[ L ];
139 ::lstrcpy( szFile, szFileName );
140 ofn.lpstrFile = szFile;
141 ofn.nMaxFile = L;
142 ofn.lpstrFileTitle = NULL;
143 ofn.lpstrInitialDir = szFileDir;
144 ofn.lpstrTitle = ( bOpen ? TEXT("打开文件") : TEXT("保存文件") );
145 ofn.Flags = OFN_EXPLORER;
146 BOOL res = ( bOpen ? ::GetOpenFileName( &ofn ) : ::GetSaveFileName( &ofn ) );
147 if ( res ) {
148 ::lstrcpy( szFileName, szFile + ofn.nFileOffset );
149 szFile[ ofn.nFileOffset ] = 0;
150 ::lstrcpy( szFileDir, szFile );
151 // test begin
152 // ::MessageBox( hWnd, szFileDir, szFileName, MB_OK );
153 // test end
154 return TRUE;
155 }
156 return FALSE;
157}
158 // 文件若存在,就覆盖;不存在,就新建
159BOOL SaveFile( TTEXT &text, LPCTSTR szFileDir, LPCTSTR szFileName ) {
160 TCHAR szFile[ 1024 ];
161 ::lstrcpy( szFile, szFileDir );
162 ::lstrcat( szFile, szFileName );
163 HANDLE hFile = ::CreateFile( szFile, GENERIC_WRITE, 0, NULL,
164 CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
165 if ( hFile == INVALID_HANDLE_VALUE ) {
166 return FALSE;
167 }
168
169 INT iMaxLen = ::GetLineMaxLength( text );
170 HGLOBAL hMem = ::GlobalAlloc( GMEM_FIXED, sizeof(TCHAR)*(iMaxLen+20) ); // +20 防空行
171 LPVOID pMem = ::GlobalLock( hMem );
172
173 TPOSLINE pl;
174 for ( pl = text.begin(); pl != text.end(); /**//*++pl*/ ) {
175 TCHAR *buf = (TCHAR*)pMem;
176 TPOSCHAR pc;
177 DWORD n = 0, nw = 0;
178 for ( pc = pl->begin(); pc != pl->end(); ++pc ) {
179 buf[ n++ ] = *pc;
180 }
181 if ( (++pl) != text.end() ) {
182 buf[ n++ ] = TEXT('\n'); // 最后一行尾不要加换行符
183 }
184 n *= sizeof(TCHAR);
185 BYTE *pByte = (BYTE*)pMem;
186 while ( n > 0 ) {
187 ::WriteFile( hFile, pByte, n, &nw, NULL );
188 pByte += nw;
189 n -= nw;
190 }
191 }
192
193 ::GlobalUnlock( hMem );
194 ::GlobalFree( hMem );
195 ::CloseHandle( hFile );
196 return TRUE;
197}
198 // 文件若不存在,就返回FALSE表示失败
199BOOL LoadFile( TTEXT &text, LPCTSTR szFileDir, LPCTSTR szFileName ) {
200 TCHAR szFile[ 1024 ];
201 ::lstrcpy( szFile, szFileDir );
202 ::lstrcat( szFile, szFileName );
203 HANDLE hFile = ::CreateFile( szFile, GENERIC_READ, 0, NULL,
204 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
205 if ( hFile == INVALID_HANDLE_VALUE ) {
206 return FALSE;
207 }
208
209 INT iMaxLen = 1024 * 4;
210 HGLOBAL hMem = ::GlobalAlloc( GMEM_FIXED, iMaxLen+20 );
211 LPVOID pMem = ::GlobalLock( hMem );
212
213 DWORD nr = 0, nm = 0;
214 text.clear();
215 text.push_back( TLINE() );
216 TPOSLINE pl = text.begin();
217 BYTE *pByte = (BYTE*)pMem;
218 // 考虑一次成功读入的字节数不是 sizeof(TCHAR) 的整数倍
219 while ( ::ReadFile( hFile, pByte+nm, iMaxLen-nm, &nr, NULL ) && (nr>0) ) {
220 DWORD i;
221 TCHAR *buf = (TCHAR*)pMem;
222 nr += nm;
223 nm = nr % (sizeof(TCHAR));
224 nr /= sizeof(TCHAR);
225 for ( i = 0; i < nr; ++i ) {
226 if ( buf[ i ] == TEXT('\n') ) {
227 text.push_back( TLINE() );
228 pl = text.end();
229 --pl;
230 }
231 else {
232 pl->push_back( buf[ i ] );
233 }
234 }
235 nr *= sizeof(TCHAR);
236 for ( i = 0; i < nm; ++i ) {
237 pByte[ i ] = pByte[ nr + i ];
238 }
239 }
240
241 ::GlobalUnlock( hMem );
242 ::GlobalFree( hMem );
243 ::CloseHandle( hFile );
244 return TRUE;
245}
246
247
248/**///////// 滚动条249// 滚动范围 0..文本宽度-窗口客户区宽度 0..文本高度-窗口客户区高度 单位 CX_HALE, CY_HALF
250// 位置 窗口客户区左上角半字符在整个文本中的位置
251 // nPage, nMin, nMax
252void CalcScrollInfo( HWND hWnd, TTEXT &text, INT nBar, ::SCROLLINFO &si ) {
253 RECT rt;
254 ::GetClientRect( hWnd, &rt );
255 if ( nBar == SB_HORZ ) {
256 si.nPage = ( rt.right - rt.left ) / CX_FULL * CX_FULL;
257 si.nMax = ::GetTextAreaWidth( text );
258 }
259 if ( nBar == SB_VERT ) {
260 si.nPage = ( rt.bottom - rt.top ) / CY_FULL * CY_FULL;
261 si.nMax = ::GetTextAreaHeight( text );
262 }
263 si.nMin = 0;
264}
265 // 获取两个滚动条的所有信息
266void GetBothScrollInfo( HWND hWnd, ::SCROLLINFO &sih, ::SCROLLINFO &siv ) {
267 sih.cbSize = sizeof(sih);
268 sih.fMask = SIF_ALL;
269 ::GetScrollInfo( hWnd, SB_HORZ, &sih );
270 siv.cbSize = sizeof(siv);
271 siv.fMask = SIF_ALL;
272 ::GetScrollInfo( hWnd, SB_VERT, &siv );
273}
274 // nMin, nMax, nPage, if ( bInit ) nPos=0
275void UpdateScrollBar( HWND hWnd, TTEXT &text, BOOL bInit = FALSE ) {
276 INT i, sb[] = { SB_HORZ, SB_VERT };
277 ::SCROLLINFO si, sio;
278 for ( i = 0; i < sizeof(sb)/sizeof(sb[0]); ++i ) {
279 sio.cbSize = sizeof(sio);
280 sio.fMask = SIF_ALL;
281 ::GetScrollInfo( hWnd, sb[i], &sio );
282
283 ::CalcScrollInfo( hWnd, text, sb[i], si );
284 si.cbSize = sizeof(si);
285 si.fMask = SIF_DISABLENOSCROLL | SIF_ALL;
286 if ( bInit ) {
287 si.nPos = 0;
288 }
289 else {
290 si.nPos = sio.nPos;
291 }
292 if ( si.nPos < si.nMin ) {
293 si.nPos = si.nMin;
294 }
295 if ( si.nPos > si.nMax ) {
296 si.nPos = si.nMax;
297 }
298 si.nTrackPos = sio.nTrackPos;
299 ::SetScrollInfo( hWnd, sb[i], &si, TRUE );
300 }
301}
302 // 窗口客户区左上角在文本区中的位置
303inline void GetLeftTopDelta( HWND hWnd, INT &leftX, INT &topY ) {
304 ::SCROLLINFO sih, siv;
305 ::GetBothScrollInfo( hWnd, sih, siv );
306 leftX = sih.nPos;
307 topY = siv.nPos;
308}
309
310/**///////// Caret311void UpdateCaretPos( HWND hWnd, TTEXT &text, TPOSLINE &pl, TPOSCHAR &pc ) {
312 INT leftX, topY;
313 ::GetLeftTopDelta( hWnd, leftX, topY );
314 ::SetCaretPos( ::GetX( *pl, pc ) - leftX, ::GetY( text, pl ) - topY );
315}
316
317
318/**///////// 窗口319TCHAR szClassName[] = TEXT("TyperZJ");
320TCHAR szWndNameMid[] = TEXT(" - TyperZJ - ");
321
322 // 利用常量相等 SB_LINEUP==SB_LINELEFT,
323 // SB_LINEDOWN==SB_LINERIGHT, SB_PAGEUP==SB_PAGELEFT, SB_PAGEDOWN==SB_PAGERIGHT
324 // SB_TOP==SB_LEFT, SB_BOTTOM==SB_RIGHT
325void OnScroll( HWND hWnd, INT nBar, WPARAM wParam, TTEXT &text, TPOSLINE &pl, TPOSCHAR &pc ) {
326 ::SCROLLINFO si;
327 si.cbSize = sizeof(si);
328 si.fMask = SIF_ALL;
329 ::GetScrollInfo( hWnd, nBar, &si );
330
331 switch ( LOWORD(wParam) ) {
332 case SB_LINELEFT :
333 si.nPos -= CX_FULL;
334 break;
335 case SB_LINERIGHT :
336 si.nPos += CY_FULL;
337 break;
338 case SB_PAGELEFT :
339 si.nPos -= si.nPage;
340 break;
341 case SB_PAGERIGHT :
342 si.nPos += si.nPage;
343 break;
344 case SB_THUMBTRACK :
345 si.nPos = si.nTrackPos;
346 break;
347 }
348 if ( si.nPos < si.nMin ) {
349 si.nPos = si.nMin;
350 }
351 if ( si.nPos > si.nMax ) {
352 si.nPos = si.nMax;
353 }
354
355 si.fMask = SIF_POS;
356 ::SetScrollInfo( hWnd, nBar, &si, TRUE );
357 ::UpdateCaretPos( hWnd, text, pl, pc );
358 ::InvalidateRect( hWnd, NULL, TRUE );
359}
360 // 文本修改并更新滚动条nMin,nMax,nPage后,修改滚动条nPos,Caret
361void OnTextChange( HWND hWnd, TTEXT &text, TPOSLINE &pl, TPOSCHAR &pc ) {
362 ::SCROLLINFO sih, siv;
363 ::GetBothScrollInfo( hWnd, sih, siv );
364
365 INT x = ::GetX( *pl, pc );
366 INT xP = sih.nPos;
367 if ( x < sih.nPos ) {
368 sih.nPos = x;
369 }
370 else if ( x >= sih.nPos + (INT)sih.nPage ) {
371 sih.nPos = x - sih.nPage;
372 }
373 if ( sih.nPos < sih.nMin ) {
374 sih.nPos = sih.nMin;
375 }
376 if ( sih.nPos > sih.nMax ) {
377 sih.nPos = sih.nMax;
378 }
379 if ( xP != sih.nPos ) {
380 sih.cbSize = sizeof(sih);
381 sih.fMask = SIF_POS;
382 ::SetScrollInfo( hWnd, SB_HORZ, &sih, TRUE );
383 }
384
385 INT y = ::GetY( text, pl );
386 INT yP = siv.nPos;
387 if ( y < siv.nPos ) {
388 siv.nPos = y;
389 }
390 else if ( y >= siv.nPos + (INT)siv.nPage ) {
391 siv.nPos = y - siv.nPage;
392 }
393 if ( siv.nPos < siv.nMin ) {
394 siv.nPos = siv.nMin;
395 }
396 if ( siv.nPos > siv.nMax ) {
397 siv.nPos = siv.nMax;
398 }
399 if ( yP != siv.nPos ) {
400 siv.cbSize = sizeof(siv);
401 siv.fMask = SIF_POS;
402 ::SetScrollInfo( hWnd, SB_VERT, &siv, TRUE );
403 }
404
405 ::UpdateCaretPos( hWnd, text, pl, pc );
406
407 ::InvalidateRect( hWnd, NULL, TRUE );
408}
409 // 更新窗口标题 文件名 + 程序名 + 程序运行时间
410void UpdateWindowText( HWND hWnd, LONG time, LPCTSTR szFileName ) {
411 static TCHAR text[ 1024 ];
412 TCHAR szTime[ 1024 ];
413 ::swprintf( szTime, TEXT(" <已运行 %ld 秒> "), time );
414 ::lstrcpy( text, szFileName );
415 ::lstrcat( text, szWndNameMid );
416 ::lstrcat( text, szTime );
417 ::SetWindowText( hWnd, text );
418}
419
420 // 窗口过程函数
421LRESULT CALLBACK WndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) {
422 // 文本
423 static TTEXT text;
424 static TPOSLINE pl;
425 static TPOSCHAR pc;
426
427 // 文件
428 static BOOL bModified = FALSE;
429 static BOOL bNamed = FALSE;
430 #define FILE_LEN 1024
431 static TCHAR szFileDir[ FILE_LEN ] = {0};
432 static TCHAR szFileName[ FILE_LEN ] = {0};
433
434 // 时间
435 // 误差在200毫秒以内,系统运行 49.7 天以内正确计时
436 static LONG totTime = 0; // 程序运行总时间,单位 秒
437 static DWORD preTick; // 单位 毫秒
438
439 switch ( uMsg ) {
440 case WM_CREATE :
441 ::SendMessage( hWnd, WM_COMMAND, ID_FILE_NEW, 0 );
442 ::SetTimer( hWnd, 0, 200, NULL );
443 preTick = ::GetTickCount();
444 ::UpdateWindowText( hWnd, totTime, szFileName );
445 return 0;
446
447 case WM_TIMER :
448 {
449 DWORD cnt = ::GetTickCount();
450 if ( cnt - preTick >= 1000 ) {
451 totTime += ( cnt - preTick ) / 1000;
452 ::UpdateWindowText( hWnd, totTime, szFileName );
453 preTick = cnt;
454 }
455 }
456 return 0;
457
458 case WM_PAINT :
459 {
460 ::PAINTSTRUCT ps;
461 ::HDC hdc;
462
463 hdc = ::BeginPaint( hWnd, &ps );
464
465 TPOSLINE il;
466 INT leftX, topY, y;
467 ::GetLeftTopDelta( hWnd, leftX, topY );
468 y = -topY;
469 HFONT hf = ::CreateFont( 0, 0, 0, 0, 0, 0, 0, 0,
470 DEFAULT_CHARSET, 0, 0, 0, FIXED_PITCH, NULL );
471 HFONT hfOld = (HFONT)::SelectObject( hdc, hf );
472 for ( il = text.begin(); il != text.end(); ++il ) {
473 TPOSCHAR ic;
474 TCHAR sz[ 3 ];
475 INT x = -leftX;
476 for ( ic = il->begin(); ic != il->end(); ++ic ) {
477 sz[ 0 ] = (*ic);
478 ::TextOut( hdc, x, y, sz, 1 );
479 x += ::GetDeltaX( *il, ic );
480 }
481 y += ::GetDeltaY( text, il );
482 }
483 ::SelectObject( hdc, hfOld );
484 ::DeleteObject( hf );
485
486 ::EndPaint( hWnd, &ps );
487 }
488 return 0;
489
490 // 鼠标选取,点击插入点
491 case WM_LBUTTONDOWN :
492 {
493 INT leftX, topY;
494 ::GetLeftTopDelta( hWnd, leftX, topY );
495 INT x = leftX + LOWORD(lParam) - ( CX_HALF / 2 ); // 微调,找最近
496 INT y = topY + HIWORD(lParam) - ( CY_HALF / 3 * 2 ); // .
497 pl = ::GetPosline( text, y );
498 pc = ::GetPoschar( *pl, x );
499 ::UpdateCaretPos( hWnd, text, pl, pc );
500 ::InvalidateRect( hWnd, NULL, TRUE );
501 }
502 return 0;
503
504 // 输入
505 case WM_KEYDOWN :
506 switch ( wParam ) {
507 case VK_HOME :
508 pc = pl->begin();
509 break;
510 case VK_END :
511 pc = pl->end();
512 break;
513 case VK_LEFT :
514 if ( pc != pl->begin() ) {
515 --pc;
516 }
517 else if ( pl != text.begin() ) {
518 --pl;
519 pc = pl->end();
520 }
521 break;
522 case VK_RIGHT :
523 if ( pc != pl->end() ) {
524 ++pc;
525 }
526 else if ( ++pl != text.end() ) {
527 pc = pl->begin();
528 }
529 else {
530 --pl;
531 }
532 break;
533 case VK_UP :
534 if ( pl != text.begin() ) {
535 INT x = ::GetX( *pl, pc );
536 --pl;
537 pc = ::GetPoschar( *pl, x );
538 }
539 break;
540 case VK_DOWN :
541 if ( ++pl != text.end() ) {
542 --pl;
543 INT x = ::GetX( *pl, pc );
544 ++pl;
545 pc = ::GetPoschar( *pl, x );
546 }
547 else {
548 --pl;
549 }
550 break;
551 case VK_DELETE :
552 {
553 TPOSLINE pln = pl;
554 ++pln;
555 if ( pc != pl->end() ) {
556 pc = pl->erase( pc );
557 }
558 else if ( pln != text.end() ) {
559 INT x = ::GetX( *pl, pc );
560 INT y = ::GetY( text, pl );
561 TPOSCHAR pcn;
562 for ( pcn = pln->begin(); pcn != pln->end(); ++pcn ) {
563 pl->push_back( *pcn );
564 }
565 text.erase( pln );
566 pl = ::GetPosline( text, y );
567 pc = ::GetPoschar( *pl, x );
568 }
569 bModified = TRUE;
570 ::UpdateScrollBar( hWnd, text );
571 }
572 break;
573 case VK_RETURN :
574 {
575 TLINE line( pc, pl->end() );
576 pl->erase( pc, pl->end() );
577 ++pl;
578 if ( pl == text.end() ) {
579 text.push_back( line );
580 pl = text.end();
581 --pl;
582 pc = pl->begin();
583 }
584 else {
585 pl = text.insert( pl, line );
586 pc = pl->begin();
587 }
588 bModified = TRUE;
589 ::UpdateScrollBar( hWnd, text );
590 }
591 break;
592 }
593 ::OnTextChange( hWnd, text, pl, pc );
594 return 0;
595
596 case WM_CHAR :
597 switch ( wParam ) {
598 case TEXT('\b') :
599 if ( pc != pl->begin() ) {
600 ::SendMessage( hWnd, WM_KEYDOWN, VK_LEFT, 1 );
601 ::SendMessage( hWnd, WM_KEYDOWN, VK_DELETE, 1 );
602 }
603 break;
604 case TEXT('\t') :
605 {
606 TPOSCHAR p;
607 INT ip = 0;
608 for ( p = pl->begin(); (p!=pl->end())&&(p!=pc); ++p ) {
609 ++ip;
610 }
611 --ip;
612 do {
613 ++ip;
614 ::SendMessage( hWnd, WM_CHAR, TEXT(' '), 1 );
615 } while ( ip % 8 != 7 );
616 bModified = TRUE;
617 ::UpdateScrollBar( hWnd, text );
618 }
619 break;
620 case TEXT('\r') :
621 break;
622 case TEXT('\n') :
623 break;
624 case TEXT('\x1B') :
625 break;
626 default :
627 {
628 TCHAR ch = wParam;
629 if ( pc == pl->end() ) {
630 pl->push_back( ch );
631 pc = pl->end();
632 }
633 else {
634 pc = pl->insert( pc, ch );
635 ++pc;
636 }
637 bModified = TRUE;
638 ::UpdateScrollBar( hWnd, text );
639 }
640 break;
641 }
642 ::OnTextChange( hWnd, text, pl, pc );
643 return 0;
644
645 // 滚动
646 case WM_HSCROLL :
647 ::OnScroll( hWnd, SB_HORZ, wParam, text, pl, pc );
648 return 0;
649 case WM_VSCROLL :
650 ::OnScroll( hWnd, SB_VERT, wParam, text, pl, pc );
651 return 0;
652 case WM_MOUSEWHEEL :
653 {
654 SHORT delta = HIWORD(wParam);
655 if ( delta > 0 ) {
656 ::SendMessage( hWnd, WM_VSCROLL, SB_LINEUP, 0 );
657 }
658 else if ( delta < 0 ) {
659 ::SendMessage( hWnd, WM_VSCROLL, SB_LINEDOWN, 0 );
660 }
661 }
662 return 0;
663
664 case WM_SIZE :
665 ::UpdateScrollBar( hWnd, text );
666 ::UpdateCaretPos( hWnd, text, pl, pc );
667 ::InvalidateRect( hWnd, NULL, TRUE );
668 return 0;
669
670 // 输入焦点
671 case WM_SETFOCUS :
672 ::CreateCaret( hWnd, NULL, 2, ::GetDeltaY( *pl, pc ) );
673 ::UpdateCaretPos( hWnd, text, pl, pc );
674 ::ShowCaret( hWnd );
675 return 0;
676 case WM_KILLFOCUS :
677 ::HideCaret( hWnd );
678 ::DestroyCaret();
679 return 0;
680
681 // 菜单命令
682 case WM_COMMAND :
683 switch ( LOWORD(wParam) ) {
684 // 文件
685 case ID_FILE_NEW :
686 if ( bModified ) {
687 // 已修改
688 INT res = ::MessageBox( hWnd, TEXT("文件已修改,是否保存?"), TEXT("文件已修改"), MB_YESNOCANCEL | MB_ICONQUESTION );
689 if ( IDYES == res ) {
690 if ( ! ::SendMessage( hWnd, WM_COMMAND, ID_FILE_SAVE, 0 ) ) {
691 // 保存文件时放弃
692 return 0;
693 }
694 }
695 else if ( IDCANCEL == res ) {
696 // 放弃
697 return 0;
698 }
699 }
700 text.clear();
701 text.push_back( TLINE() );
702 pl = text.begin();
703 pc = pl->begin();
704 ::lstrcpy( szFileName, TEXT("未命名.txt") );
705 bNamed = FALSE;
706 bModified = FALSE;
707 ::UpdateScrollBar( hWnd, text, TRUE );
708 ::UpdateCaretPos( hWnd, text, pl, pc );
709 ::InvalidateRect( hWnd, NULL, TRUE );
710 // 成功
711 return 1;
712
713 case ID_FILE_OPEN :
714 if ( bModified ) {
715 // 已修改
716 INT res = ::MessageBox( hWnd, TEXT("文件已修改,是否保存?"), TEXT("文件已修改"), MB_YESNOCANCEL | MB_ICONQUESTION );
717 if ( IDYES == res ) {
718 if ( ! ::SendMessage( hWnd, WM_COMMAND, ID_FILE_SAVE, 0 ) ) {
719 // 保存文件时放弃
720 return 0;
721 }
722 }
723 else if ( IDCANCEL == res ) {
724 // 放弃
725 return 0;
726 }
727 }
728 if ( ::GetFileName( hWnd, szFileDir, szFileName, FILE_LEN, TRUE ) ) {
729 if ( ::LoadFile( text, szFileDir, szFileName ) ) {
730 bModified = FALSE;
731 bNamed = TRUE;
732 pl = text.begin();
733 pc = pl->begin();
734 ::UpdateScrollBar( hWnd, text, TRUE );
735 ::UpdateCaretPos( hWnd, text, pl, pc );
736 ::InvalidateRect( hWnd, NULL, TRUE );
737 // 成功
738 return 1;
739 }
740 // 读入文件失败
741 return 0;
742 }
743 // 打开时放弃
744 return 0;
745
746 case ID_FILE_SAVE :
747 if ( bNamed ) {
748 // 已命名
749 if ( bModified ) {
750 // 已修改
751 if ( ::SaveFile( text, szFileDir, szFileName ) ) {
752 bModified = FALSE;
753 // 成功
754 return 1;
755 }
756 // 保存失败
757 return 0;
758 }
759 // 未修改
760 return 1;
761 }
762 // 尚未命名
763 return ::SendMessage( hWnd, WM_COMMAND, ID_FILE_SAVE_AS, 0 );
764
765 case ID_FILE_SAVE_AS :
766 if ( ::GetFileName( hWnd, szFileDir, szFileName, FILE_LEN, FALSE ) ) {
767 if ( ::SaveFile( text, szFileDir, szFileName ) ) {
768 bModified = FALSE;
769 bNamed = TRUE;
770 // 成功
771 return 1;
772 }
773 // 保存失败
774 return 0;
775 }
776 // 放弃
777 return 0;
778
779 case ID_FILE_CLOSE :
780 if ( bModified ) {
781 INT res = ::MessageBox( hWnd, TEXT("文件已修改,是否保存?"), TEXT("文件已修改"), MB_YESNOCANCEL | MB_ICONQUESTION );
782 if ( IDYES == res ) {
783 if ( ! ::SendMessage( hWnd, WM_COMMAND, ID_FILE_SAVE, 0 ) ) {
784 // 保存时放弃
785 return 0;
786 }
787 }
788 else if ( IDCANCEL == res ) {
789 // 放弃
790 return 0;
791 }
792 }
793 ::DestroyWindow( hWnd );
794 return 0;
795 }
796 break;
797
798 case WM_CLOSE :
799 ::SendMessage( hWnd, WM_COMMAND, ID_FILE_CLOSE, 0 );
800 return 0;
801
802 case WM_DESTROY :
803 ::KillTimer( hWnd, 0 );
804 ::PostQuitMessage( 0 );
805 return 0;
806 }
807 return ::DefWindowProc( hWnd, uMsg, wParam, lParam );
808}
809
810
811INT APIENTRY WinMain( HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR szCmd, INT iShowCmd ) {
812 WNDCLASSEX wc;
813 wc.cbClsExtra = 0;
814 wc.cbSize = sizeof(wc);
815 wc.cbWndExtra = 0;
816 wc.hbrBackground = (HBRUSH)::GetStockObject( WHITE_BRUSH );
817 wc.hCursor = ::LoadCursor( NULL, IDC_IBEAM );
818 wc.hIcon = ::LoadIcon( NULL, IDI_APPLICATION );
819 wc.hIconSm = ::LoadIcon( NULL, IDI_APPLICATION );
820 wc.hInstance = hInst;
821 wc.lpfnWndProc = WndProc;
822 wc.lpszClassName = szClassName;
823 wc.lpszMenuName = MAKEINTRESOURCE( IDR_MENU );
824 wc.style = CS_HREDRAW | CS_VREDRAW;
825
826 if ( ! ::RegisterClassEx( &wc ) ) {
827 ::MessageBox( NULL, TEXT("RegisterClassEx Failed"), TEXT("ERROR"), MB_OK | MB_ICONERROR );
828 return 0;
829 }
830
831 HWND hWnd = ::CreateWindowEx( 0, szClassName, szWndNameMid,
832 WS_OVERLAPPEDWINDOW | WS_HSCROLL | WS_VSCROLL,
833 100, 30, 800, 600,
834 NULL, NULL, wc.hInstance, NULL );
835 if ( ! hWnd ) {
836 ::MessageBox( NULL, TEXT("CreateWindowEx Failed"), TEXT("ERROR"), MB_OK | MB_ICONERROR );
837 return 0;
838 }
839 ::ShowWindow( hWnd, iShowCmd );
840 ::UpdateWindow( hWnd );
841
842 MSG msg;
843 HACCEL hAcce = ::LoadAccelerators( hInst, MAKEINTRESOURCE(IDR_ACCE) );
844 while ( ::GetMessage( &msg, NULL, 0, 0 ) ) {
845 if ( ! ::TranslateAccelerator( hWnd, hAcce, &msg ) ) {
846 ::TranslateMessage( &msg );
847 ::DispatchMessage( &msg );
848 }
849 }
850 return msg.wParam;
851}
852