레이블이 Notepad2인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Notepad2인 게시물을 표시합니다. 모든 게시물 표시

2010년 7월 11일 일요일

Notepad2 4.1.24 정식버전 관련 패치 모음

by BLUEnLIVE | 2010/06/26 16:13

notepad2 4.1.24 패치버전 공개 (한글판) by BLUEnLIVE | 2010/03/08 21:10 notepad2 4.1.24 패치버전 공개 (영문판) by BLUEnLIVE | 2010/0...



notepad2 4.1.24 한글 패치에 적용한 패치들을 정리했다.


0. 들어가기 전에

다음과 같은 내용은 모두 준비가 되어있다고 가정하고 설명함.

a. Visual C++ v6.0 및 Service Pack 6 설치
b. Platform SDK 설치 (Windows® Server 2003 R2 Platform SDK ISO Download)
c. Scintilla 2.03 다운로드. 현재 Scintilla는 2.12까지 공개되었으나, 2.03을 사용해야 함


1. Kai Liu님의 패치 적용

np2-4.0.22-allpatches.7z 패치를 적용한다.
이 패치는 총 13개의 패치로 이루어져있으며, 코드 폴딩이나 각종 문법을 포함한 다양한 패치로 구성되어있다.

문제는 이 패치는 Notepad2 4.0.22, 그것도 beta5에 적용 가능한 패치라는 거.
대부분은 그대로 적용가능하고, 일부는 눈치껏(?) 적용할 수 있으나, 2개는 완전히 바뀐 구조에 따라 일일이 적용해야 한다.
특히 문법 패치의 경우, 바뀐 포맷을 유심히 본 후, 문자열 중에 기존 문법에 있는 부분은 그대로 사용해야 되고, 없는 부분은 일일이 notepad2.rc에 문자열을 추가해야 한다.

사용자 삽입 이미지

더 이상의 자세한 설명은 생략한다.


패치 대상 세부 내역 보기..



2. 한글 IME 패치

간단하게 단 하나로 끝난다. (아싸!)

a. notepad2 컴파일 삽질기 8 : 한글 IME 패치


3. 기능 추가

okto 님께서 필요하다고 닥달한 기능들이며, 귀차니 님이 큰 축을 담당하시고, 나도 중간중간 밥숟갈을 들었음.

a. notepad2 컴파일 삽질기 부록#5.2 : context-menu 기능 수정

b. notepad2 컴파일 삽질기 부록#6.1 : 메모장 대신 사용 (수정)

c. notepad2 컴파일 삽질기 부록#4 : AutoHotkey 스킴 추가
   이것 역시 앞의 1번에 따라 열심히 패치한다. 역시 설명은 생략한다. ㅋㅋ

d. notepad2 컴파일 삽질기 부록#11 : 포트란 스킴 추가

e. notepad2 컴파일 삽질기 부록#7 : Shift+마우스 휠에 Page Up/Down 기능 부여

f. notepad2 컴파일 삽질기 부록#9 : 다음 바꾸기(Replace Next) 메뉴에 추가

g. notepad2 컴파일 삽질기 부록#10 : 시간/날짜 삽입 순서 변경

h. notepad2 컴파일 삽질기 부록#12 : NBSP 관련 수정  (2010. 7. 11 추가)

그런데... 패치를 정리한 것 맞나? 뭔가 좀...


notepad2 4.1.24 패치버전 재공개 (한글판)

by BLUEnLIVE | 2010/03/08 21:10

notepad2 4.1.24 패치버전 공개 (영문판) by BLUEnLIVE | 2010/03/07 10:48 지난 3월 2일 드디어 notepad2가 4.1.24로 업데이트되었다....


notepad2 4.1.24의 한글 패치를 공개한지 넉달이 되어간다.
그동안 notepad2 자체는 패치되지 않았지만, Scintilla도 2.12로 업데이트 되고, 기타 수정사항도 있어 손을 좀 댔다.

그런데… Scintilla 2.12를 적용하고 나니 찾기 기능이 잘 동작하지 않았다.
급하게 다시 Scintilla 홈페이지를 뒤져봤더니…

DBCS 환경에서는 무조건 case-sensitive 검색만 지원


DBCS 환경에서는 case-sensitive 검색만 지원하도록 수정되었단다. 헐~

그래서 다시 2.03으로 회귀…
하지만, 문법 분석기(lexer)는 여러모로 업데이트 된 것이 있어 그건 2.12의 것을 사용하기로 했다.


1. Scintilla 2.12중 lexer 적용

얼마 전(정확히는 지난 6월 1일) Scintilla 라이브러리가 2.12로 업데이트 되었다.
속도가 일부 향상되고, 약간의 버그가 수정되었다.

하지만, 전술했듯이 검색 기능이 정상적으로 동작하지 않아 다시 2.03으로 회귀했다.
하지만, lexer는 여러모로 업데이트 되었기 때문에 lexer(Lex*.cxx)들과 SciLexer.h만 2.12의 것을 사용했다.


2. 포트란 스킴 추가

notepad2 컴파일 삽질기 부록#11 : 포트란 스킴 추가에서 설명한 포트란 스킴을 추가했다.


3. 파일명이 notepad.exe인 경우 "메모장 대신 사용" 기능 미동작

(구운고구마 님께서 지적해주신 문제임)
동작 원리상, 파일명을 notepad.exe로 바꾼 경우 메모장 대신 사용을 적용할 수 없다.
파일명이 notepad.exe인 경우 메모장 대신 사용 기능을 선택하면 경고벨소리를 들려주도록 수정했다.
※ 관련 포스트: notepad2 컴파일 삽질기 부록#6.1 : 메모장 대신 사용(수정)


4. NBSP를 ASCII에서 붙이면 공백으로 자동 변환

NBSP(Non-break space)를 ASCII 모드에서 붙일 때 공백으로 변환하도로 수정했다.
※ 관련 포스트: notepad2 컴파일 삽질기 부록#12 : NBSP 관련 수정  (2010. 7. 11 추가)


이렇게 수정된 notepad2는 아래 링크에서 다운받을 수 있으며…


동작화면은 아래와 같다.


notepad2 컴파일 삽질기 부록#12 : NBSP 관련 수정

SMI 포맷의 자막을 만들거나, HTML을 직접 작성해본 경험이 있는 사람은  라는 표현을 자주 봤을 것이다.
그런데, NBSP가 뭔지 정확하게 아는 사람은 그리 많지 않은 것 같다.

NBSP는 Non-break Space의 약자로 공백의 일종이지만, 사실 일반적인 공백(0x20)과는 다르다.
줄바꿈을 하지 않는 공백을 의미하며, 유니코드에서는 0xa0이라는 값에 할당되어 있고, ASCII에는 이 개념이 없다.

유니코드를 ASCII 코드로 변환할 때 ASCII 코드에 없는 문자를 변환하는 방식은 정의되어있지 않다.
따라서 표준에 따르면 NBSP를 ASCII로 변환하면 공백으로 만들던, 물음표로 만들던 문제가 되지 않는다.

하지만, 실세계에서는 NBSP는 알아서 공백(0x20)으로 변환해주는 것이 예의다.

notepad2는 WideCharToMultiByte() 함수를 이용해서 유니코드를 ASCII 등으로 변환한다.
그런데, 이 함수에선 NBSP의 변환을 별도로 처리하지 않는다.

따라서, NBSP를 ASCII 모드에서 붙여넣으면 아래와 같은 결과를 보여준다.

구글 크롬에서 Syntax Highlighter 3.0으로 처리한 소스를 복사-붙이기 한 화면


웹 브라우저 중에서 IE나 FireFox에서 복사(copy)를 하면 NBSP를 공백으로 변환한 뒤 클립보드로 보내지만, 구글 크롬은 NBSP를 그대로 내보낸다.
Syntax Highlighter에서 코드를 복사한 뒤에 메모장2에 붙여넣으면 이 현상을 경험할 수 있다.

전술했듯이, 이 현상은 결코 버그가 아니며, 단지 현상일 뿐이다.
하지만, 사용자 입장에서 불편하긴 마찬가지다.

그래서 메모장2를 패치하기로 했다.


1. ScintillaWin.cxx 수정


이 현상을 수정하려면 일단 붙여넣기 부분을 수정해야 된다.
ScintillaWin.cxx 파일에서 void ScintillaWin::Paste() 함수를 찾은 뒤 아래 부분의 위치를 확인한다.

  UINT cpDest = CodePageFromCharSet(
      vs.styles[STYLE_DEFAULT].characterSet, pdoc->dbcsCodePage);

  len = ::WideCharToMultiByte(cpDest, 0, uptr, -1,
      NULL, 0, NULL, NULL) - 1; // subtract 0 terminator

이 부분을 아래와 같이 수정한다. 몇 줄의 코드를 삽입하는 것이며, 기존 코드의 수정은 없다.

  UINT cpDest = CodePageFromCharSet(
      vs.styles[STYLE_DEFAULT].characterSet, pdoc->dbcsCodePage);

  // 여기부터 삽입
  int wlen = wcslen(uptr);
  for (int i=0; i<wlen; i++)
  {
      if (uptr[i] == 0xa0) uptr[i] = 0x20;
  }
  // 여기까지 삽입

  len = ::WideCharToMultiByte(cpDest, 0, uptr, -1,
      NULL, 0, NULL, NULL) - 1; // subtract 0 terminator

이것으로 붙여넣기에서 문제는 해결되었다.


2. Edit.c

사실, 이 현상을 경험할 수 있는 것은 붙이기 명령 외에 하나가 더 있다.

유니코드에서 ASCII로 코드 페이지를 바꾸면 이 현상이 발생할 수 있다.
그래서, 코드 페이지를 바꿀 때도 이 문제가 발생하지 않도록 수정한다.

BOOL EditConvertText(...) 함수를 찾아 아래 내용의 위치를 확인한다.

  cbwText  = MultiByteToWideChar(cpSource,0,pchText,length,pwchText,length*3+2);
  cbText   = WideCharToMultiByte(cpDest,0,pwchText,cbwText,pchText,length*5+2,NULL,NULL);


이 내용을 아래처럼 수정한다. 역시 몇 줄의 코드를 삽입하는 것이며, 기존 코드의 수정은 없다.

  cbwText  = MultiByteToWideChar(cpSource,0,pchText,length,pwchText,length*3+2);
  // 여기부터 삽입
  {
    int i;
    for (i=0; i<length; i++)
        if (pwchText[i] == 0xa0) pwchText[i] = 0x20;
  }
  // 여기까지 삽입
  cbText   = WideCharToMultiByte(cpDest,0,pwchText,cbwText,pchText,length*5+2,NULL,NULL);

이렇게 수정한 뒤 구글 크롬 메모장2로 복사-붙이기를 실행하면 아래와 같이 동작하는 모습을 볼 수 있다.

구글 크롬 따위는 겁나지 않는다능!


2010년 6월 26일 토요일

notepad2 컴파일 삽질기 부록#6.1 : 메모장 대신 사용(수정)



okto님 배포본 notepad2는 설치 및 윈도우 메모장 대체를 위해 notepad.inf 파일을 이용했다.
이 방식은 설치가 쉽다는 장점은 있지만, 설치 제거에 손이 많이 간다는 단점이 있다.

장점을 살리고 단점을 제거하기 위해 구차니님께서 메모장을 대체하는 기능을 구현해주셨다.

그런데, 이를 위해 제작/공개한 notepad2 컴파일 삽질기 부록#6 : 메모장 대신 사용에는 사소한 버그가 하나 있었다.
바로 파일명을 notepad.exe로 바꾼 상태에선 동작하지 않는 다는 것이다.

이 부분은 사실 당연한 것이다.
이 기능은 레지스트리의 Image File Execution Options 키를 이용해서 notepad.exe라는 프로그램을 실행하면 다른 프로그램(notepad2.exe)을 실행하도록 지정하는 것인데, 그 프로그램이 또 notepad.exe라서 발생하는 문제이다.

이 기능을 사용하려면 소스 코드를 아래와 같이 수정하면 된다.

반드시 삽질기 부록#5.1 : context-menu 통합 및 수정을 적용한 후에 이 수정을 해야 함

수정 대상 파일은 역시 resource.h, Notepad2.rc, Notepad2.c 세 개이다.


1. resource.h

다음 줄을 추가한다.
#define IDM_REPLACE_NOTEPAD             40692



2. Notepad2.rc

아래 내용을 찾는다.
MENUITEM "Add context-menu to all file type", IDM_REGISTRY_ALL
, CHECKED
바로 앞에 다음 줄을 추가한다.
MENUITEM "Replace Notepad",             IDM_REPLACE_NOTEPAD



3. Notepad2.c

앞부분에 다음 변수를 하나 선언한다.
BOOL      bReplaceNotepad;


다음, void MsgInitMenu(HWND hwnd,WPARAM wParam,LPARAM lParam)에서 아래 내용을 찾는다.
CheckCmd(hmenu,IDM_REGISTRY_UNKNOWN,bRegistryUnknownType);
또는
CheckCmd(hmenu,IDM_REGISTRY_ALL,bRegistryAllType || bRegistryUnknownType);
바로 앞에 다음 줄을 추가한다.
CheckCmd(hmenu,IDM_REPLACE_NOTEPAD,bReplaceNotepad);


다음, LRESULT MsgCommand(HWND hwnd,WPARAM wParam,LPARAM lParam) 함수에 다음 case 문을 추가한다.
이 함수 전체가 하나의 switch-case 문으로 되어있는데, case를 하나 추가하는 것이다.
case IDM_REPLACE_NOTEPAD:
if (bReplaceNotepad)
{
//WinNT requires the key to have no subkeys
RegDeleteKey(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\notepad.exe");
bReplaceNotepad = FALSE;
}
else
{
WCHAR path[MAX_PATH], *wTemp;
GetModuleFileName(GetModuleHandle(NULL), path, MAX_PATH);
bReplaceNotepad = TRUE;

wTemp = path;
while (*wTemp) wTemp++;
if (_wcsicmp(wTemp-12, L"\\notepad.exe"))
{
HKEY key1;
LONG res = RegCreateKey(HKEY_LOCAL_MACHINE,L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\notepad.exe",&key1);
if (res == ERROR_SUCCESS)
{
WCHAR cmd[MAX_PATH + 4];
int len = wsprintf(cmd, L"\"%s\" /z", path);
RegSetValueExW(key1, L"Debugger", 0, REG_SZ, (const unsigned char*)cmd, len * 2);
}
else
{
bReplaceNotepad = FALSE;
MessageBeep(MB_ICONEXCLAMATION);
}
RegCloseKey(key1);
}
else
{
bReplaceNotepad = FALSE;
MessageBeep(MB_ICONEXCLAMATION);
}
}
break;


마지막으로 void CheckRegistry() 함수의 마지막 부분에 다음 내용을 추가하면 된다.
res = RegOpenKey(HKEY_LOCAL_MACHINE,L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\notepad.exe",&key);
if(res == ERROR_SUCCESS)
        bReplaceNotepad = TRUE;
else    bReplaceNotepad = FALSE;
RegCloseKey(key);


수정된 결과는 아래와 같다. 짜잔~

사용자 삽입 이미지

수정에 수고해주신 구차니님께 다시 한 번 감사드립니다.

notepad2 컴파일 삽질기 부록#5.2 : context-menu 기능 수정

삽질기 부록#5.1 : context-menu 통합 및 수정을 수정한 포스팅. 부록 #5/#5.1과 본 포스트는 동시 적용불가.

구차니님께서 수고해주신 덕분에 context 메뉴(오른쪽 버튼 클릭 메뉴)를 통해 Notepad2를 사용할 수 있었다.
(삽질기 부록#1 : context-menu 추가삽질기 부록#2 : context-menu 추가 업그레이드 참조)

그런데, Notepad2 3.1.21 정식버전에서는 이 수정을 미묘하게 바꿔야 했으며, 이후 okto님구차니님과 context 메뉴에 대한 수정을 논의한 결과 메뉴를 조금 간결하게 변형하기로 했다.
(File 메뉴에 있는 context-menu 관련 메뉴를 하나만 남기도록 수정)

그리고, 레지스트리에 등록이 실패했을 때의 코드가 빠져있었는데, 실패했을 경우 간단한 경보음을 들려주도록 했다.

삽질기 1~3을 모두 적용했다고 가정하고 시작한다.
그리고, notepad2 컴파일 삽질기 부록#5.1 : context-menu 통합 및 수정은 적용하지 않은 상태에서 시작한다.


수정 대상파일은 resource.h, Notepad2.rc, Notepad2.c의 3개이며, 모두 src 폴더에 저장되어 있다.


1. resource.h

다음 한 줄을 추가.
#define IDM_REGISTRY_ALL            40690


2. Notepad2.rc

IDR_MAINWND MENU DISCARDABLE에서 아래 내용을 찾는다.
MENUITEM "Propert&ies...",              IDM_FILE_PROPERTIES
MENUITEM "Create &Desktop Link",        IDM_FILE_CREATELINK
MENUITEM SEPARATOR
POPUP "&Favorites"
BEGIN
    MENUITEM "&Open Favorites...\tAlt+I",   IDM_FILE_OPENFAV
    MENUITEM "&Add Current File...\tAlt+K", IDM_FILE_ADDTOFAV
    MENUITEM "&Manage...\tF9",              IDM_FILE_MANAGEFAV
END
이 부분을 아래와 같이 수정한다.
MENUITEM "Propert&ies...",              IDM_FILE_PROPERTIES
MENUITEM SEPARATOR
MENUITEM "Create &Desktop Link",        IDM_FILE_CREATELINK
MENUITEM "Add context-menu to all file type", IDM_REGISTRY_ALL, CHECKED
MENUITEM SEPARATOR
POPUP "&Favorites"
BEGIN
    MENUITEM "&Open Favorites...\tAlt+I",   IDM_FILE_OPENFAV
    MENUITEM "&Add Current File...\tAlt+K", IDM_FILE_ADDTOFAV
    MENUITEM "&Manage...\tF9",              IDM_FILE_MANAGEFAV
END

구분자 하나와 context-menu를 추가하는 내용임.


3. Notepad2.h

새로운 함수 하나를 추가.
void CheckRegistry();


4. Notepad2.c

파일 앞부분의 변수 선언부에 다음 내용을 추가.
BOOL      bRegistryUnknownType;
BOOL      bRegistryAllType;


다음, LRESULT CALLBACK MainWndProc(HWND hwnd,UINT umsg,WPARAM wParam,LPARAM lParam)에서 아래 내용을 찾는다.
case WM_COMMAND:
  return MsgCommand(hwnd,wParam,lParam);
여기에 아래와 같이 한 줄을 추가한다.
case WM_COMMAND:
  CheckRegistry();
  return MsgCommand(hwnd,wParam,lParam);


다음, void MsgInitMenu(HWND hwnd,WPARAM wParam,LPARAM lParam)에서 아래 내용을 찾는다.
EnableCmd(hmenu,IDM_EDIT_SELTONEXT,i);
EnableCmd(hmenu,IDM_EDIT_SELTOPREV,i && lstrlenA(efrData.szFind));
EnableCmd(hmenu,IDM_EDIT_REPLACE,i /*&& !bReadOnly*/);

CheckCmd(hmenu,IDM_VIEW_USE2NDDEFAULT,Style_GetUse2ndDefault(hwndEdit));
이 부분을 아래와 같이 수정한다. 1줄을 추가하는 것임.
EnableCmd(hmenu,IDM_EDIT_SELTONEXT,i);
EnableCmd(hmenu,IDM_EDIT_SELTOPREV,i && lstrlenA(efrData.szFind));
EnableCmd(hmenu,IDM_EDIT_REPLACE,i /*&& !bReadOnly*/);

CheckCmd(hmenu,IDM_REGISTRY_ALL,bRegistryAllType || bRegistryUnknownType);

CheckCmd(hmenu,IDM_VIEW_USE2NDDEFAULT,Style_GetUse2ndDefault(hwndEdit));


다음은 LRESULT MsgCommand(HWND hwnd,WPARAM wParam,LPARAM lParam)의 수정임.
이 함수는 전체가 하나의 switch-case 문으로 되어있는데, 이 case 중에 IDM_REGISTRY_ALL을 추가함.
case IDM_REGISTRY_ALL:
// Register registry - type all
if (bRegistryAllType || bRegistryUnknownType)
{
if (bRegistryAllType)
{
  //WinNT requires the key to have no subkeys
RegDeleteKey(HKEY_CLASSES_ROOT, L"*\\shell\\Open with notepad2\\command");
RegDeleteKey(HKEY_CLASSES_ROOT, L"*\\shell\\Open with notepad2");
bRegistryAllType = FALSE;
}
if (bRegistryUnknownType)
 {
//WinNT requires the key to have no subkeys
RegDeleteKey(HKEY_CLASSES_ROOT, L"Unknown\\shell\\Open with notepad2\\command");
RegDeleteKey(HKEY_CLASSES_ROOT, L"Unknown\\shell\\Open with notepad2");
bRegistryUnknownType = FALSE;
}
}
else
{
HKEY key1;
LONG res = RegCreateKey(HKEY_CLASSES_ROOT,L"*\\shell\\Open with notepad2",&key1);
bRegistryAllType = TRUE;
if (res == ERROR_SUCCESS)
{
WCHAR cmd[] = L"Open with &notepad2";
RegSetValue(key1, NULL, REG_SZ, cmd, wcslen(cmd));
}
else bRegistryAllType = FALSE;
RegCloseKey(key1);

  res = RegCreateKey(HKEY_CLASSES_ROOT,L"*\\shell\\Open with notepad2\\command",&key1);
if (res == ERROR_SUCCESS)
{
WCHAR cmd[MAX_PATH + 4];
WCHAR path[MAX_PATH];
int len;
GetModuleFileName(GetModuleHandle(NULL), path, MAX_PATH);
len = wsprintf(cmd, L"%s %%1", path);
RegSetValue(key1, NULL, REG_SZ, cmd, len);
}
else bRegistryAllType = FALSE;
RegCloseKey(key1);

if (!bRegistryAllType)
{
MessageBeep(MB_ICONEXCLAMATION);
}
}
break;


마지막으로 함수를 하나 추가한다.
void CheckRegistry()
{
    HKEY key;
    LONG res;

    res = RegOpenKey(HKEY_CLASSES_ROOT,L"*\\shell\\Open with notepad2",&key);
    if(res == ERROR_SUCCESS)
            bRegistryAllType = TRUE;
    else    bRegistryAllType = FALSE;
    RegCloseKey(key);

    res = RegOpenKey(HKEY_CLASSES_ROOT,L"unknown\\shell\\Open with notepad2",&key);
    if(res == ERROR_SUCCESS)
            bRegistryUnknownType = TRUE;
    else    bRegistryUnknownType = FALSE;
    RegCloseKey(key);
}

이렇게 수정하면 아래처럼 File 메뉴에서 context-menu 메뉴 하나가 표시된다.

사용자 삽입 이미지

context-menu가 하나만 나오도록 수정. ^^;


notepad2 컴파일 삽질기 부록#11 : 포트란 스킴 추가

회사에서 일을 하면서 포트란 소스를 C++로 변환할 일이 생겼다.
notepad2에서 포트란 스킴을 지원하는 줄 알았는데, 지금 보니 지원하지 않았다. 헐~

그래서 포트란 스킴을 추가하기로 했다.
notepad2에서 사용하는 Scintilla에서는 포트란 스킴은 지원하지만, 이를 notepad2에서 사용하지 않는 것이다.
따라서, 이를 사용하기 위해서는 조금만 수정하면 된다.

포트란 스킴을 사용하려면 아래와 같이 수정하면 된다.


1. Notepad2.rc 수정

아래와 같은 부분을 찾는다.
    63016                   "Batch Files"
    63017                   "Diff Files"
    63018                   "SQL Query"
    63019                   "Python Script"
    63020                   "Apache Config Files"
    63021                   "PowerShell Script"
END

여기에 아래와 같이 한 줄을 추가한다. 위치는 물론 END 바로 앞이며, 언어를 이미 추가한 경우 적절히 알아서 한다.
    63022                   "Fortran Source Code"
   63023                   "Fortran77 Source Code"


2. Styles.h 수정

아래와 같은 부분을 찾는다.
// Number of Lexers in pLexArray
#define NUMLEXERS 30

NUMLEXERS의 값을 둘 증가시킨다. 아래처럼.


#define NUMLEXERS 32


3. Styles.c 수정 #1

아래와 같은 부분을 찾는다.
// This array holds all the lexers...
PEDITLEXER pLexArray[NUMLEXERS] =
{
  &lexDefault,

바로 앞에 아래와 같은 코드를 추가한다.



// FORTRAN added by BLUEnLIVE - 25th June 2010
KEYWORDLIST KeyWords_FORTRAN = {
"abs achar acos acosd adjustl adjustr aimag aimax0 aimin0 aint ajmax0 "
"ajmin0 akmax0 akmin0 all allocated alog alog10 amax0 amax1 amin0 amin1 "
"amod anint any asin asind associated atan atan2 atan2d atand bitest bitl "
"bitlr bitrl bjtest bit_size bktest break btest cabs ccos cdabs cdcos cdexp "
"cdlog cdsin cdsqrt ceiling cexp char clog cmplx conjg cos cosd cosh count "
"cpu_time cshift csin csqrt dabs dacos dacosd dasin dasind datan datan2 "
"datan2d datand date date_and_time dble dcmplx dconjg dcos dcosd dcosh "
"dcotan ddim dexp dfloat dflotk dfloti dflotj digits dim dimag dint dlog "
"dlog10 dmax1 dmin1 dmod dnint dot_product dprod dreal dsign dsin dsind "
"dsinh dsqrt dtan dtand dtanh eoshift epsilon errsns exp exponent float "
"floati floatj floatk floor fraction free huge iabs iachar iand ibclr ibits "
"ibset ichar idate idim idint idnint ieor ifix iiabs iiand iibclr iibits "
"iibset iidim iidint iidnnt iieor iifix iint iior iiqint iiqnnt iishft "
"iishftc iisign ilen imax0 imax1 imin0 imin1 imod index inint inot int "
"int1 int2 int4 int8 iqint iqnint ior ishft ishftc isign isnan izext jiand "
"jibclr jibits jibset jidim jidint jidnnt jieor jifix jint jior jiqint jiqnnt "
"jishft jishftc jisign jmax0 jmax1 jmin0 jmin1 jmod jnint jnot jzext kiabs "
"kiand kibclr kibits kibset kidim kidint kidnnt kieor kifix kind kint kior "
"kishft kishftc kisign kmax0 kmax1 kmin0 kmin1 kmod knint knot kzext lbound "
"leadz len len_trim lenlge lge lgt lle llt log log10 logical lshift malloc "
"matmul max max0 max1 maxexponent maxloc maxval merge min min0 min1 "
"minexponent minloc minval mod modulo mvbits nearest nint not nworkers "
"number_of_processors pack popcnt poppar precision present product radix "
"random random_number random_seed range real repeat reshape rrspacing rshift "
"scale scan secnds selected_int_kind selected_real_kind set_exponent shape "
"sign sin sind sinh size sizeof sngl snglq spacing spread sqrt sum "
"system_clock tan tand tanh tiny transfer transpose trim ubound unpack verify",
"access action advance allocatable allocate apostrophe assign assignment "
"associate asynchronous backspace bind blank blockdata call case character "
"class close common complex contains continue cycle data deallocate decimal "
"delim default dimension direct do dowhile double doubleprecision else elseif "
"elsewhere encoding end endassociate endblockdata enddo endfile endforall "
"endfunction endif endinterface endmodule endprogram endselect endsubroutine "
"endtype endwhere entry eor equivalence err errmsg exist exit external file "
"flush fmt forall form format formatted function go goto id if implicit in "
"include inout integer inquire intent interface intrinsic iomsg iolength "
"iostat kind len logical module name named namelist nextrec nml none nullify "
"number only open opened operator optional out pad parameter pass pause "
"pending pointer pos position precision print private program protected "
"public quote read readwrite real rec recl recursive result return rewind "
"save select selectcase selecttype sequential sign size stat status stop "
"stream subroutine target then to type unformatted unit use value volatile "
"wait where while write",
"", "", "", "", "", "", "" };

EDITLEXER lexFORTRAN = { SCLEX_FORTRAN, 63022, L"Fortran Source Code", L"f; for; f90; f95", L"", &KeyWords_FORTRAN, {
    { STYLE_DEFAULT, 63126, L"Default", L"", L"" },
    { SCE_F_COMMENT, 63127, L"Comment", L"italics; fore:#008000", L"" },
    { SCE_F_NUMBER, 63130, L"Number", L"bold; fore:#0000FF", L"" },
    { MULTI_STYLE(SCE_F_STRING1,SCE_F_STRING2,SCE_F_STRINGEOL,0), 63131, L"String", L"fore:#008000", L"" },
    { MULTI_STYLE(SCE_F_OPERATOR,SCE_F_OPERATOR,0,0), 63132, L"Operator", L"bold", L"" },
    { SCE_F_IDENTIFIER, 63281, L"Identifier", L"bold; fore:#C80000", L"" },
    { SCE_F_WORD, 63236, L"Word", L"fore:#0000A0; bold", L"" },
    { SCE_F_WORD2, 63260, L"Word2", L"fore:#0000FF; bold", L"" },
    { SCE_F_WORD3, 63203, L"Word3", L"fore:#008000; bold", L"" },
    { SCE_F_PREPROCESSOR, 63133, L"Preprocessor", L"fore:#FF8000", L"" },
    { SCE_F_LABEL, 63235, L"Label", L"fore:#000000; back:#FFFFD1; bold", L"" },
    { SCE_F_CONTINUATION, 63257, L"Continuation", L"back:#FFC0C0", L"" },
    { -1, 00000, L"", L"", L"" } } };

EDITLEXER lexFORTRAN77 = { SCLEX_F77, 63023, L"Fortran77 Source Code", L"f77", L"", &KeyWords_FORTRAN, {
    { STYLE_DEFAULT, 63126, L"Default", L"", L"" },
    { SCE_F_COMMENT, 63127, L"Comment", L"italics; fore:#008000", L"" },
    { SCE_F_NUMBER, 63130, L"Number", L"bold; fore:#0000FF", L"" },
    { MULTI_STYLE(SCE_F_STRING1,SCE_F_STRING2,SCE_F_STRINGEOL,0), 63131, L"String", L"fore:#008000", L"" },
    { MULTI_STYLE(SCE_F_OPERATOR,SCE_F_OPERATOR,0,0), 63132, L"Operator", L"bold", L"" },
    { SCE_F_IDENTIFIER, 63281, L"Identifier", L"bold; fore:#C80000", L"" },
    { SCE_F_WORD, 63236, L"Word", L"fore:#0000A0; bold", L"" },
    { SCE_F_WORD2, 63260, L"Word2", L"fore:#0000FF; bold", L"" },
    { SCE_F_WORD3, 63203, L"Word3", L"fore:#008000; bold", L"" },
    { SCE_F_PREPROCESSOR, 63133, L"Preprocessor", L"fore:#FF8000", L"" },
    { SCE_F_LABEL, 63235, L"Label", L"fore:#000000; back:#FFFFD1; bold", L"" },
    { SCE_F_CONTINUATION, 63257, L"Continuation", L"back:#FFC0C0", L"" },
    { -1, 00000, L"", L"", L"" } } };

여기서 48, 63행의 63022, 63023은 위의 1번에서 추가한 값과 똑같아야 한다.


4. Styles.c 수정 #2


위의 3번에서 찾은 내용은 아래와 같이 생겼다.

PEDITLEXER pLexArray[NUMLEXERS] =
{
  &lexDefault,
  &lexHTML,
  &lexXML,
  &lexCSS,
  &lexJS,
  &lexVBS,

이 배열의 맨 마지막에 아래와 같은 내용을 추가한다.


  &lexFORTRAN,
  &lexFORTRAN77


이러한 수정이 적용된 화면은 아래와 같다.

포트란이 지원되는 메모장2!


2010년 3월 8일 월요일

notepad2 4.1.24 패치버전 공개 (한글판)

by BLUEnLIVE | 2010/03/07 10:48

지난 3월 2일 드디어 notepad2가 4.1.24로 업데이트되었다. 4.1.24에서 수정된 내용은 아래와 같으며, - /z command line switch (support re...


okto 님께서 노력해주신 덕분에 notepad2 4.1.24의 한글화작업을 무사히 마쳤다.
(이 버전에서 수정된 내역은 이전 포스트 참고)


수정된 버전은 아래 링크에서 다운받을 수 있으며, 같은 내용은 okto님의 포스트에서도 확인할 수 있다.


2010년 3월 7일 일요일

notepad2 4.1.24 패치버전 공개 (영문판)

지난 3월 2일 드디어 notepad2가 4.1.24로 업데이트되었다.

4.1.24에서 수정된 내용은 아래와 같으며,

수정된 내용 펼치기..


기존 패치에 적용했던 Kai Liu님의 패치도 대부분 수작업으로 적용했다.

적용된 패치 펼치기..


또한, 한글 IME 문제 해결 등의 패치도 모두 적용했다.


실행 화면은 아래와 같으며,


아래 파일을 다운받아 압축을 풀면 사용할 수 있다.


덧. 번역 작업은 현재 99% 완료되었으며, 완성되면 공개할 예정임.

2010년 3월 4일 목요일

notepad2 컴파일 삽질기 8 : 한글 IME 패치

by BLUEnLIVE | 2010/03/04 21:32

notepad2가 4.1.24로 업데이트 되었다. 이전 버전인 4.0.23으로 충분히 완벽해졌다고 생각했는데, Florian Balmer 님께선 아직 업뎃이 고픈가...


이전 포스트에서 얘기한 notepad2 4.1.24의 한글 IME 패치방법을 설명하기에 앞서, 몇 가지 작업을 해야한다.


0. 들어가기 전에

다음과 같은 내용은 모두 준비가 되어있다고 가정하고 설명함.

a. Visual C++ v6.0 및 Service Pack 6 설치
b. Platform SDK 설치 (Windows® Server 2003 R2 Platform SDK ISO Download)
c. Scintilla 2.03


1. Visual C++ 6.0을 위한 dsp 파일 생성

notepad2의 소스에는 VC6을 위한 dsp 파일이 없다. VC7을 기준으로 만들어졌기 때문이다.

얼마전까지(정확히는 4.0.22-beta5 까지) notepad2의 기능추가 패치를 제작하시던 Kai Liu님이 notepad2 패치 작업을 중단했는데, Kai Liu 님의 패치 중에 VC6을 위한 dsp 파일 생성이 포함되어 있었다.
따라서, VC6에서 notepad2를 컴파일하려면 dsp 파일을 다시 만들어야 한다.

아래 파일을 다운받아 압축을 푼 뒤에 notepad2 소스의 루트 폴더에 저장한다.



2. Edit.c 버그 수정

EditTitleCase() 함수에서 아래와 같은 부분을 찾는다.
if (!IsCharAlphaNumericW(pszTextW[i]) && !StrChr(L"'?,pszTextW[i])) {

이 부분을 아래와 같이 수정한다.
if (!IsCharAlphaNumericW(pszTextW[i]) && !StrChr(L"'",pszTextW[i])) {


이건 그냥 notepad2의 버그다. 제작자 Balmer 님께 문의를 했는데, 패치할 의지가 없다는 답장을 받았다. 헐~


3. ScintillaWin.cxx 수정 #1

짜잔~ 이제야 한글 IME 패치를 할 차례가 되었다.
수정할 곳은 이전 패치과 같이 두 군데다. 그 중 첫번째는 WndProc().

WM_IME_STARTCOMPOSITION: 을 찾아 아래와 같이 수정한다.
이 부분은 이전 패치 그대로이다.

case WM_IME_STARTCOMPOSITION:     // dbcs
    ImeStartComposition();
    // added from here-------------------------------------------------
    if (LOWORD(GetKeyboardLayout(0))==MAKELANGID(LANG_KOREAN, SUBLANG_KOREAN)) {
        // if the current IME is the Korean IME, do not show the default IME window
        return 0;
    }
    // added to here-------------------------------------------------
    return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam);


4. ScintillaWin.cxx 수정 #2

HandleComposition()을 찾아 아래 내용으로 대체한다.
이 부분은 이전 패치보다 좀 길어지고, 복잡해졌다.

sptr_t ScintillaWin::HandleComposition(uptr_t wParam, sptr_t lParam) {
#ifdef __DMC__
    // Digital Mars compiler does not include Imm library
    return 0;
#else
    static int cs    = -1;
    static int undo  = -1;
    static bool comp = false;
    static bool bEndOfLine, bOverstrike;
    static bool wasSelection = false;
    bool bKoreanIME = LOWORD(GetKeyboardLayout(0))==MAKELANGID(LANG_KOREAN, SUBLANG_KOREAN);
 
    if (bKoreanIME && (lParam & GCS_COMPSTR)) {
        HIMC hIMC = ::ImmGetContext(MainHWND());
        if (hIMC) {
            const int maxLenInputIME = 200;
            wchar_t wcs[maxLenInputIME];
            LONG bytes = ::ImmGetCompositionStringW(hIMC, GCS_COMPSTR, wcs, (maxLenInputIME-1)*2);
            int wides = bytes / 2;

            int lastitem = sel.Count()-1;
            int selBegin = sel.Range(lastitem).End().Position();
            int selEnd = selBegin;

            if (bytes) {
                //comp==false 이면 최초 진입
                //undo를 마비시키기 전에 삭제할 글자/블럭 삭제
                if (!comp)
                {
                    FilterSelections();

                    {
                        UndoGroup ug(pdoc, (sel.Count() > 1) || !sel.Empty() || inOverstrike);
                        for (size_t r=0; r<sel.Count(); r++) {
                            if (!RangeContainsProtected(sel.Range(r).Start().Position(),
                                sel.Range(r).End().Position())) {
                                selBegin = sel.Range(r).Start().Position();
                                if (!sel.Range(r).Empty()) {
                                    if (sel.Range(r).Length()) {
                                        pdoc->DeleteChars(selBegin, sel.Range(r).Length());
                                        sel.Range(r).ClearVirtualSpace();
                                    } else {
                                        // Range is all virtual so collapse to start of virtual space
                                        sel.Range(r).MinimizeVirtualSpace();
                                    }
                                } else if (inOverstrike) {
                                    if (selBegin < pdoc->Length()) {
                                        if (!IsEOLChar(pdoc->CharAt(selBegin))) {
                                            pdoc->DelChar(selBegin);
                                            sel.Range(r).ClearVirtualSpace();
                                        }
                                    }
                                }
                                selBegin = InsertSpace(selBegin, sel.Range(r).caret.VirtualSpace());

                                sel.Range(r).ClearVirtualSpace();
                                // If in wrap mode rewrap current line so EnsureCaretVisible has accurate information
                                if (wrapState != eWrapNone) {
                                    AutoSurface surface(this);
                                    if (surface) {
                                        WrapOneLine(surface, pdoc->LineFromPosition(selBegin));
                                    }
                                }
                            }
                        }

                    }

                    bOverstrike = inOverstrike;
                }
               
                if (cs < 0 && !bOverstrike) {
                    cs = vs.caretStyle;
                    vs.caretStyle = CARETSTYLE_BLOCK;
                }
               
                if (undo < 0) {
                    undo = pdoc->IsCollectingUndo()?1:0;
                    pdoc->SetUndoCollection(false);
                }
               
                if (!comp) {
                    comp = true;
                } else {
                    DelChar();
                }
            } else {
                //조합 중 조합중인 글자를 다 지운 경우
                if (cs >= 0) {
                    vs.caretStyle = cs;
                    cs = -1;
                }
 
                if (comp) {
                    comp = false;
                   
                    DelChar();
                }

                if (undo >= 0) {
                    pdoc->SetUndoCollection(undo==1);
                    undo = -1;
                }
            }
           
            MovePositionTo(selBegin);
           
            inOverstrike = false;
            if (IsUnicodeMode()) {
                char utfval[maxLenInputIME * 3];
                unsigned int len = UTF8Length(wcs, wides);
                UTF8FromUTF16(wcs, wides, utfval, len);
                utfval[len] = '\0';
                AddCharUTF(utfval, len);
            } else {
                char dbcsval[maxLenInputIME * 2];
                int size = ::WideCharToMultiByte(InputCodePage(),
                    0, wcs, wides, dbcsval, sizeof(dbcsval) - 1, 0, 0);
                for (int i=0; i<size; i++)
                    AddChar(dbcsval[i]);
            }
            inOverstrike = bOverstrike;
   
            MovePositionTo(selBegin);
        }
    }
   
    if (lParam & GCS_RESULTSTR) {
        //앞의 if문 6개는 모두 한글 입력기에서만 동작
        //다른 언어 IME에서는 패스
        if (comp) {
            comp = false;
            DelChar();
        }
       
        if (cs >= 0) {
            vs.caretStyle = cs;
            cs = -1;
        }
        if (undo >= 0){
            pdoc->SetUndoCollection(undo==1);
            undo = -1;
        }

        //덮어쓰기에서 한글 조합 중 마지막에 숫자나 기호를 붙인 경우 한 글자 더 삭제(가9)
        bool bKoreanPlusOneMore = false;
        if (bKoreanIME && bOverstrike)
        {
            HIMC hIMC = ::ImmGetContext(MainHWND());
            if (hIMC) {
                const int maxLenInputIME = 200;
                wchar_t wcs[maxLenInputIME];
                LONG bytes = ::ImmGetCompositionStringW(hIMC,
                    GCS_RESULTSTR, wcs, (maxLenInputIME-1)*2);
                int wides = bytes / 2;
               
                char dbcsval[maxLenInputIME * 2];
                int size = ::WideCharToMultiByte(InputCodePage(),
                    0, wcs, wides, dbcsval, sizeof(dbcsval) - 1, 0, 0);
   
                bKoreanPlusOneMore = (size==3);
                ::ImmReleaseContext(MainHWND(), hIMC);
            }
        }
        if (bKoreanPlusOneMore) DelChar();

        if (bKoreanIME) inOverstrike = false;

        HIMC hIMC = ::ImmGetContext(MainHWND());
        if (hIMC) {
            const int maxLenInputIME = 200;
            wchar_t wcs[maxLenInputIME];
            LONG bytes = ::ImmGetCompositionStringW(hIMC,
                GCS_RESULTSTR, wcs, (maxLenInputIME-1)*2);
            int wides = bytes / 2;
           
            if (IsUnicodeMode()) {
                char utfval[maxLenInputIME * 3];
                unsigned int len = UTF8Length(wcs, wides);
                UTF8FromUTF16(wcs, wides, utfval, len);
                utfval[len] = '\0';
                AddCharUTF(utfval, len);
            } else {
                char dbcsval[maxLenInputIME * 2];
                int size = ::WideCharToMultiByte(InputCodePage(),
                    0, wcs, wides, dbcsval, sizeof(dbcsval) - 1, 0, 0);
                for (int i=0; i<size; i++)
                    AddChar(dbcsval[i]);
            }
           
            // Set new position after converted
            Point pos = LocationFromPosition(sel.Range(0).End().Position());
            COMPOSITIONFORM CompForm;
            CompForm.dwStyle = CFS_POINT;
            CompForm.ptCurrentPos.x = pos.x;
            CompForm.ptCurrentPos.y = pos.y;
            ::ImmSetCompositionWindow(hIMC, &CompForm);
           
            ::ImmReleaseContext(MainHWND(), hIMC);
        }
        if (bKoreanIME) inOverstrike = bOverstrike;
       
        return 0;
    }
   
    return ::DefWindowProc(MainHWND(), WM_IME_COMPOSITION, wParam, lParam);
#endif
}

이렇게 수정하면 아래와 같이 언제나 정상적으로 한글 입력이 가능한 notepad2를 만날 수 있다.

쿠헬헬~


notepad2 4.1.24 업데이트! 그런데!!!

notepad2가 4.1.24로 업데이트 되었다.
이전 버전인 4.0.23으로 충분히 완벽해졌다고 생각했는데, Florian Balmer 님께선 아직 업뎃이 고픈가보다.

이번 버전에서도 (이전과 같이) 커다란 변화가 있었다.

4.1.24에서 바뀐 내용 열기..



1. 업데이트된 Scintilla의 기능을 충실히 활용하지 못하는 notepad2

이 변화 중에 가장 주목해야 될 부분이 바로 Scintilla2.03으로 업데이트 되었다는 것이다.
Scintilla는 1.x에서 2.x로 업데이트되면서 커다란 변화가 생겼는데, 바로 다중입력 기능이 추가되었다는 것이다.
즉, 컬럼블럭(alt+마우스 선택)을 잡은 상태에서 글을 입력하면 여러 줄에 같은 내용이 동시에 입력된다.

하지만, notepad2는 Scintilla 2.03을 기반으로 하면서도 이 기능을 지원하지 않는다.

아마도 notepad2의 undo-redo 구조와 맞지 않은 것이 원인인 듯 하다.


2. 새롭게 발생한 오류


notepad2 4.1.24에서 alt+마우스 선택을 이용해서 컬럼블럭을 설정한 뒤 한글을 입력하면 아래와 같이 깨져나온다.
새롭게 추가된 기능을 제대로 활용하지도 않으면서 오류만 발생하는 슬픈(?) 현상이 발생하는 것이다. OTL

'쿠헬헬'을 입력했을 뿐인데...


※ 이 화면은 IME2002에서 동작하는 화면이며, IME2003에서는 조금 달라보이긴 하지만, 발생하는 오류는 동일함.


3. 결국 HandleComposition()을 다시 패치


외형적으로는 notepad2 4.0.23와 4.1.24는 비슷하게 동작하지만, 한글 IME 패치는 완전히 다시 작성해야 한다.
내부적으로 변경된 기능을 충실하게 반영하도록 수정해야 하는 것이다. OTL

결국... 결국... 며칠 간의 삽질 끝에 HandleComposition()을 패치하고야 말았다!

이 모습 보기가 결코 쉽지 않았다



패치하는 법은 다음 포스트에서 설명하도록 하겠다.

2009년 11월 14일 토요일

notepad2 컴파일 삽질기 부록#10 : 시간/날짜 삽입 순서 변경

okto님의 블로그를 통해 삼각형 님께서 시간/날짜의 삽입 순서를 바꿔달란 요청을 해오셨다.

사용자 삽입 이미지

결국 직역의 한계는 그대로... ㄷㄷㄷ


처음엔 옵션 기능을 추가해서 선택적으로 바꿀 수 있도록 할 생각도 했지만, 결국 IME 패치를 사용하는 사람들은 모두 한국사람인지라 걍 순서를 바꾸는 것으로 수정. (전문용어로 게으르니즘이라고 한다)

수정 대상은 Notepad2.c 달랑 하나. (당연한 얘기다)

아래와 같은 줄을 찾는다.

wsprintf(tchDateTime,L"%s %s",tchTime,tchDate);

이걸 아래와 같이 고친다.

wsprintf(tchDateTime,L"%s %s",tchDate,tchTime);

참 쉽죠잉?

2009년 10월 13일 화요일

Notepad2 4.0.23 정식버전 관련 패치 모음

사용자 삽입 이미지

지난 10월 6일 Notepad2 4.0.23이 공개되었다.
이에 따라 Notepad2의 각종 패치를 정리했다.


0. 들어가기 전에

다음과 같은 내용은 모두 준비가 되어있다고 가정하고 설명함.

a. Visual C++ v6.0 및 Service Pack 6 설치
b. Platform SDK 설치 (Windows® Server 2003 R2 Platform SDK ISO Download)
c. Scintilla 1.78 다운로드. 현재 Scintilla는 2.01까지 공개되었으나, Notepad2는 1.78을 사용함


1. Kai Liu님의 패치 적용

np2-4.0.22-allpatches.7z 패치를 적용한다.
이 패치는 총 13개의 패치로 이루어져있으며, 코드 폴딩이나 각종 문법을 포함한 다양한 패치로 구성되어있다.

문제는 이 패치는 Notepad2 4.0.22, 그것도 beta5에 적용 가능한 패치라는 거.
대부분은 그대로 적용가능하고, 일부는 눈치껏(?) 적용할 수 있으나, 2개는 완전히 바뀐 구조에 따라 일일이 적용해야 한다.
특히 문법 패치의 경우, 바뀐 포맷을 유심히 본 후, 문자열 중에 기존 문법에 있는 부분은 그대로 사용해야 되고, 없는 부분은 일일이 notepad2.rc에 문자열을 추가해야 한다.

사용자 삽입 이미지

더 이상의 자세한 설명은 생략한다.


패치 대상 세부 내역 보기..



2. 한글 IME 패치

간단하게 단 하나로 끝난다. (아싸!)

a. notepad2 컴파일 삽질기 7 : 한글 IME 패치 재작성


3. 기능 추가


그런데... 패치를 정리한 것 맞나? 뭔가 좀...


2009년 10월 9일 금요일

notepad2 컴파일 삽질기 7 : 한글 IME 패치 재작성

Notepad2에서 한글을 정상적으로 입력할 수 있도록 하기 위해 다음과 같은 패치를 공개했다.


이 패치의 가장 큰 문제는 너무 복잡하다는 점이다.
그런데, 또 하나의 문제가 있다. 바로 한글 IME 외의 IME에서 완벽하게 동작하지 않는다는 것...

사실, 모든 IME에서 완벽하게 동작하게 만들기 위해 이리저리 손을 많이 대었는데, 알고 보니 한글 IME만 엉망인 거다. OTL

한글 외의 일본어, 중국어 IME는 원래부터 정상적으로 동작했음


이 문제를 한꺼번에 해결하고, 좀 더 간결한 패치를 유지하기 위해 다시 패치를 해봤다.
참고로, 이 패치는 기본적으로 행복한고니 님의 패치[footnote]이 패치는 간결하긴 하지만, 덮어쓰기 모드에서 치명적인 버그가 둘이나 있다.
(뒤에 다시 언급됨)[/footnote]를 기본으로 작성했다.
이 패치를 하려면, 위의 세 패치는 안한 셈 치고 처음부터 다시 시작해야 한다.

패치할 대상은 ScintillaWin.cxx 하나다. (얏호! 드디어 정상적인 패치를 만든 거다!)


1. WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam) 수정

WM_IME_STARTCOMPOSITION: 을 찾아 아래와 같이 수정한다.
이 부분은 정확히는 행복한고니 님이 참고한 sixman님의 패치(헥헥 길다)에서 그대로 가져온 것이다.

case WM_IME_STARTCOMPOSITION:     // dbcs
    ImeStartComposition();
    // added from here-------------------------------------------------
    if (LOWORD(GetKeyboardLayout(0))==MAKELANGID(LANG_KOREAN, SUBLANG_KOREAN)) {
        // if the current IME is the Korean IME, do not show the default IME window
        return 0;
    }
    // added to here-------------------------------------------------
    return ::DefWindowProc(MainHWND(), iMessage, wParam, lParam);


2. ScintillaWin::HandleComposition(uptr_t wParam, sptr_t lParam) 대체

HandleComposition()을 찾아 아래 내용으로 대체한다.
삽입/덮어쓰기/행의 끝에서 덮어쓰기에서 각각 다르게 동작하도록 작성했다.
따라서, 행복한고니 님 패치의 버그[footnote]1. undo/redo가 정상동작하지 않음
2. 행의 끝에서 글을 입력하면 이상하게 동작함[/footnote]는 발생하지 않는다.

sptr_t ScintillaWin::HandleComposition(uptr_t wParam, sptr_t lParam) {
#ifdef __DMC__
    // Digital Mars compiler does not include Imm library
    return 0;
#else
    static int cs    = -1;
    static int undo  = -1;
    static bool comp = false;
    static bool bEndOfLine, bOverstrike;
    static bool wasSelection = false;
    bool bKoreanIME = LOWORD(GetKeyboardLayout(0))==MAKELANGID(LANG_KOREAN, SUBLANG_KOREAN);
 
    if (bKoreanIME && (lParam & GCS_COMPSTR)) {
        HIMC hIMC = ::ImmGetContext(MainHWND());
        if (hIMC) {
            const int maxLenInputIME = 200;
            wchar_t wcs[maxLenInputIME];
            LONG bytes = ::ImmGetCompositionStringW(hIMC, GCS_COMPSTR, wcs, (maxLenInputIME-1)*2);
            int wides = bytes / 2;
            int selBegin = currentPos;

            if (bytes) {
                //comp==false 이면 최초 진입
                //undo를 마비시키기 전에 삭제할 글자/블럭 삭제
                if (!comp)
                {
                    wasSelection = currentPos != anchor;
                    if (wasSelection && anchor<currentPos) selBegin = anchor;
                    ClearSelection();
                    bEndOfLine = (selBegin == pdoc->Length()) || IsEOLChar(pdoc->CharAt(selBegin));
                    bOverstrike = inOverstrike;
                    if (bOverstrike && !wasSelection && !bEndOfLine && !RangeContainsProtected(selBegin, selBegin + 1)) DelChar();
                }
               
                if (cs < 0 && !bOverstrike) {
                    cs = vs.caretStyle;
                    vs.caretStyle = CARETSTYLE_BLOCK;
                }
               
                if (undo < 0) {
                    undo = pdoc->IsCollectingUndo()?1:0;
                    pdoc->SetUndoCollection(false);
                }
               
                if (!comp) {
                    comp = true;
                } else {
                    DelChar();
                }
            } else {
                //조합 중 조합중인 글자를 다 지운 경우
                if (cs >= 0) {
                    vs.caretStyle = cs;
                    cs = -1;
                }
 
                if (comp) {
                    comp = false;
                    DelChar();
                }

                if (undo >= 0) {
                    pdoc->SetUndoCollection(undo==1);
                    undo = -1;
                }
            }
           
            inOverstrike = false;
            if (IsUnicodeMode()) {
                char utfval[maxLenInputIME * 3];
                unsigned int len = UTF8Length(wcs, wides);
                UTF8FromUTF16(wcs, wides, utfval, len);
                utfval[len] = '\0';
                AddCharUTF(utfval, len);
            } else {
                char dbcsval[maxLenInputIME * 2];
                int size = ::WideCharToMultiByte(InputCodePage(),
                    0, wcs, wides, dbcsval, sizeof(dbcsval) - 1, 0, 0);
                for (int i=0; i<size; i++)
                    AddChar(dbcsval[i]);
            }
            inOverstrike = bOverstrike;
   
            MovePositionTo(selBegin);
        }
    }
   
    if (lParam & GCS_RESULTSTR) {
        //앞의 if문 6개는 모두 한글 입력기에서만 동작
        //다른 언어 IME에서는 패스
        if (comp) {
            comp = false;
            DelChar();
        }
       
        if (cs >= 0) {
            vs.caretStyle = cs;
            cs = -1;
        }
        if (undo >= 0){
            pdoc->SetUndoCollection(undo==1);
            undo = -1;
        }

        //덮어쓰기에서 한글 조합 중 마지막에 숫자나 기호를 붙인 경우 한 글자 더 삭제(가9)
        bool bKoreanPlusOneMore = false;
        if (bKoreanIME && bOverstrike)
        {
            HIMC hIMC = ::ImmGetContext(MainHWND());
            if (hIMC) {
                const int maxLenInputIME = 200;
                wchar_t wcs[maxLenInputIME];
                LONG bytes = ::ImmGetCompositionStringW(hIMC,
                    GCS_RESULTSTR, wcs, (maxLenInputIME-1)*2);
                int wides = bytes / 2;
               
                char dbcsval[maxLenInputIME * 2];
                int size = ::WideCharToMultiByte(InputCodePage(),
                    0, wcs, wides, dbcsval, sizeof(dbcsval) - 1, 0, 0);
   
                bKoreanPlusOneMore = (size==3);
                ::ImmReleaseContext(MainHWND(), hIMC);
            }
        }
        if (bKoreanPlusOneMore) DelChar();

        if (bKoreanIME) inOverstrike = false;

        HIMC hIMC = ::ImmGetContext(MainHWND());
        if (hIMC) {
            const int maxLenInputIME = 200;
            wchar_t wcs[maxLenInputIME];
            LONG bytes = ::ImmGetCompositionStringW(hIMC,
                GCS_RESULTSTR, wcs, (maxLenInputIME-1)*2);
            int wides = bytes / 2;
           
            if (IsUnicodeMode()) {
                char utfval[maxLenInputIME * 3];
                unsigned int len = UTF8Length(wcs, wides);
                UTF8FromUTF16(wcs, wides, utfval, len);
                utfval[len] = '\0';
                AddCharUTF(utfval, len);
            } else {
                char dbcsval[maxLenInputIME * 2];
                int size = ::WideCharToMultiByte(InputCodePage(),
                    0, wcs, wides, dbcsval, sizeof(dbcsval) - 1, 0, 0);
                for (int i=0; i<size; i++)
                    AddChar(dbcsval[i]);
            }
           
            // Set new position after converted
            Point pos = LocationFromPosition(currentPos);
            COMPOSITIONFORM CompForm;
            CompForm.dwStyle = CFS_POINT;
            CompForm.ptCurrentPos.x = pos.x;
            CompForm.ptCurrentPos.y = pos.y;
            ::ImmSetCompositionWindow(hIMC, &CompForm);
           
            ::ImmReleaseContext(MainHWND(), hIMC);
        }
        if (bKoreanIME) inOverstrike = bOverstrike;
       
        return 0;
    }
   
    return ::DefWindowProc(MainHWND(), WM_IME_COMPOSITION, wParam, lParam);
#endif
}


덧1. 드디어 완전체를 만들었다는 생각에 뿌듯함도 잠시... Scintilla 라이브러리가 그 사이에 2.x로 업데이트 되었다.
바뀐 부분의 코드를 읽어보니... 다중선택시 입력을 하면 전체 선택영역에 똑같은 내용을 출력하는 기능이 있더라.
이건 대체 어케 패치해야 되는 거냐... OTL

덧2. 한글날이 지나가기 전에 패치를 공개하기 위해 고군분투했다.
패치의 버그를 찾기 위해 열심히 뛰어주신 okto 님께 감사드린다.

2009년 8월 8일 토요일

휴가 기간에 3대 어플 업데이트 완료!

일 주일의 휴가동안 꽤 바쁜 시간을 보냈다.
처가, 본가에 갔다 왔고, 거제도에서 해수욕 및 피서도 즐겼으며, 풀장 있는 식당에서 가족들과 더불어 물장구와 함께 식사도 즐겼고, 에버랜드에 가서 밤 11시 30분까지 대탐사도 하고, 마눌님과 [UP]을 관람한 등등...

그 와중에 짬을 내서 본 블로그의 3대 프로그램들도 업데이트를 했다.


사용자 삽입 이미지

낮에는 놀러다니고 밤에는 코딩하는 주놀야코를 통해 이룬 성과임. ㅠ.ㅠ


노는 짬짬이 시간을 내고, 밤 며서 수정한 프로그램들이라 더욱 뿌듯함. ^^;

2009년 8월 7일 금요일

notepad2 컴파일 삽질기 6 : 일본어 IME도 정상적으로 사용이 가능하도록 수정

삽질기2: IME 메시지 처리삽질기3: undo/redo 정상동작토록 수정에서 발견된 IME 관련 버그를 수정하는 포스트임


step6. 일본어 IME 메시지도 잘 처리하도록 수정

모 포럼에서 notepad2를 일본어 윈도우에서 돌려보니 IME 쪽에서 문제가 발생했다는 버그 리포트가 들어왔다.
(notepad2를 일본어 윈도우에서 돌려볼 생각도 해본 적이 없었음)

한글의 경우 한 순간에 조합되고 있는 글자의 수가 무조건 하나인데, 수정된 방식은 이 상황에 최적화되어있어 발생한 버그이다.
하지만, 일본어의 경우 아주 많은 글자가 동시에 입력되는 상황이 발생할 수 있다는 것이다.

일본어 윈도우에서도 notepad2를 무난하게 사용하려면 아래와 같이 수정하면 된다.
(일본어 윈도우를 쓸 일이 없다면 이 수정은 당연히 무의미하다)

수정 대상 파일은 ScintillaWin.cxx 하나이다.


1. ScintillaWin 클래스 선언부에서 메쏘드 및 변수 추가

ScintillaWin 클래스의 선언부에 아래와 같이 메쏘드 및 변수를 추가한다.
선언부 맨 끝부분에 추가하면 된다.

private:
void DelCharBacks(int iCount);
int GetCompositioningStrLen(DWORD index);
long lCompositioningLenOld, lCompositioningLen;



2. HandleComposition() 메쏘드 수정

IME에서 글자를 입력중일 때 메시지를 처리하는 메쏘드가 바로 HandleComposition() 이다.
따라서, 뭘 바꾸든 이 메쏘드가 바뀔 가능성은 백푸로다.

이번에는(에효... 또냐... ㅡ.ㅡ;) 아래와 같은 내용으로 통째로 바꿔준다.

sptr_t ScintillaWin::HandleComposition(uptr_t wParam, sptr_t lParam) {
#ifdef __DMC__
// Digital Mars compiler does not include Imm library
return 0;
#else
ClearSelection();
if (!inOverstrike) pdoc->cb.uh.EnableUndoAction(false);

if (lParam & GCS_COMPSTR)
{
if (inComposition) DelCharBacks(lCompositioningLenOld);

if (inOverstrike) bCompositioning = true;

inComposition = !(AddImeCompositionString(GCS_COMPSTR) == 0);
}
else if (lParam & GCS_RESULTSTR)
{
if (inComposition) DelCharBacks(lCompositioningLenOld);

if (inOverstrike) bCompositioning = false;
else pdoc->cb.uh.EnableUndoAction(true);

AddImeCompositionString(GCS_RESULTSTR);
inComposition = FALSE;
lCompositioningLenOld = 0;
}

UpdateSystemCaret();
return 0;
#endif
}



3. ImeStartComposition() 메쏘드 수정

ImeStartComposition() 메쏘드를 찾는다.
이 메쏘드의 앞부분은 아래와 같은 모양이다.

void ScintillaWin::ImeStartComposition() {
#ifndef __DMC__
// Digital Mars compiler does not include Imm library
if (caret.active) {
// Move IME Window to current caret position
HIMC hIMC = ::ImmGetContext(MainHWND());

여기에 한 줄을 추가해서 아래와 같이 바꿔준다.

void ScintillaWin::ImeStartComposition() {
#ifndef __DMC__
// Digital Mars compiler does not include Imm library
lCompositioningLenOld = 0;
if (caret.active) {
// Move IME Window to current caret position
HIMC hIMC = ::ImmGetContext(MainHWND());

중간에 한 줄(4행)이 추가된 것 외엔 바뀐 부분은 없다.



4. AddImeCompositionString() 메쏘드 수정

ImeStartComposition() 메쏘드를 찾는다.
이 메쏘드를 아래의 내용으로 통째로 바꿔준다.

내부적으로만 사용되던 문자의 개수(wides)를 멤버변수(lCompositionLenOld)로 바꿔주는 것이다.

LONG ScintillaWin::AddImeCompositionString(DWORD index)
{
const int maxImeBuf = 200;
wchar_t wcs[maxImeBuf];

LONG bytes = GetCompositionString(index, wcs, (maxImeBuf-1)*2);
lCompositioningLenOld = bytes/2;

if(bytes == 0)
return bytes;

if (IsUnicodeMode()) {
char utfval[maxImeBuf * 3];
unsigned int len = UTF8Length(wcs, lCompositioningLenOld);
UTF8FromUTF16(wcs, lCompositioningLenOld, utfval, len);
utfval[len] = '\0';
AddCharUTF(utfval, len);
} else {
char dbcsval[maxImeBuf * 2];
int size = ::WideCharToMultiByte(InputCodePage()
, 0
, wcs
, lCompositioningLenOld
 , dbcsval
, sizeof(dbcsval) - 1
, 0
, 0);
AddCharUTF(dbcsval, size);
}

return bytes;
}



5. GetCaretSize() 메쏘드 수정

GetCaretSize() 메쏘드에서 아래와 같은 부분을 찾는다.

    if(inComposition)
{
Point end = LocationFromPosition(currentPos);
Point start = LocationFromPosition(currentPos-2);
cs.x = end.x - start.x;
cs.y = vs.lineHeight;
}

위에 하일라이트된 4행을 아래와 같이 수정한다.

    if(inComposition)
{
Point end = LocationFromPosition(currentPos);
Point start = LocationFromPosition(currentPos - lCompositioningLen);
cs.x = end.x - start.x;
cs.y = vs.lineHeight;
}

한글이란 가정 하에 길이를 무조건 2로 설정하지 않고, 조합중인 글자 수로 설정하는 수정이다.



6. 메쏘드 2개 추가

수정중인 ScintillaWin.cxx의 맨 끝부분에 아래와 같이 새로운 메쏘드 2개를 추가한다.

int ScintillaWin::GetCompositioningStrLen(DWORD index)
{
HIMC hIMC = ::ImmGetContext(MainHWND());
if (hIMC) {
const int maxLenInputIME = 200;
wchar_t wcs[maxLenInputIME];
LONG bytes = ::ImmGetCompositionStringW(hIMC,
index, wcs, (maxLenInputIME-1)*2);
int wides = bytes / 2;
return wides;
}
return 0;
}

void ScintillaWin::DelCharBacks(int iCount)
{
DestroySystemCaret();
while (iCount--) DelCharBack(false);
CreateSystemCaret();
}


이렇게 수정하면 일본어 IME에서도 정상적인 문자의 입력이 가능하다.

사용자 삽입 이미지

XPMode + 일본어 Win XP에서 입력중인 모습