Notepad++源碼編譯及其分析

  Notepad++是一個小巧精悍的編輯器,其使用方法我就很少說了,因爲notepad++是使用c++封裝的windows句柄以及api來實現的,所以對於其源碼的研究有助於學習如何封裝本身簡單的庫(固然不是MTL、MFC或者QT那樣大型的庫)。Notepad++源碼:https://github.com/notepad-plus-plus/notepad-plus-plus/releases/tag/v6.7.9.2html

  下面是Notepad++源碼的目錄:c++

       

  其主目錄如第一張圖所示,包含了兩個開源工程,第一個PowerEditor就是notepad++;第二scintilla是一個代碼編輯器的開源庫,十分強大,許多編輯器都是基於這個庫封裝起來的,對於scintilla來講能夠實現代碼摺疊、標記、縮進等等不少功能,具體狀況能夠去scintilla的官網以及一些博客來了解如何使用這個開源庫:git

http://www.scintilla.org/github

http://www.cnblogs.com/superanyi/archive/2011/04/07/2008636.html算法

  將第一個工程PowerEditor打開以後將如上右圖所示,這裏最重要的是src源碼文件以及installer中的配置文件。當我用vs2012打開visual.net打開工程文件notepadPlus.vs2005.vcproj後出現了幾個問題,第一個問題,就是一大堆找不到預編譯頭文件,解決方法是不使用預編譯頭文件便可。第二個問題出如今Sorters.h頭文件中,vs2012雖然實現了c++11的部分特性,可是卻沒有實現std::unique_ptr,由此引起出不少語法錯誤,解決辦法能夠有本身寫一個相似於unique_ptr這樣的智能指針或者有辦法替換vs2012內置編譯器也能夠。我比較懶,剛好筆記本中安裝有vs2013,是支持unique_ptr的,我就直接換環境了。編程

  而後程序跑起來仍是有一些問題,在WinMain中做者對一些左值進行了賦值,語法直接報錯了,對於這個直接註釋掉這一行便可,其次再刪除一個預編譯源文件和實現一個函數聲明以後程序就跑了起來,以後的小問題就很少說了,由於本文主要是講notePad++的運行機制是啥。我稍微統計了下,整個工程大概是21W行代碼,加上註釋也比較少,實在花了我好幾天才摸清楚大概狀況。windows

  從界面開始提及,整個工程中的窗口都是繼承自Window這個類的,這個類封裝最重要的一個成員就是HWND _hSelf,這個就是用來存放CreateWindow函數返回的窗口句柄,其次就是父窗口句柄HWND _hParent,以及實例HINSTANCE _hInst。還提供了許多窗口都可以用到的方法,都是以虛函數的方法來提供的,好比display函數用來顯示窗口,reSizeTo用來調整窗口,redraw用來重繪窗口等等許多函數。有了Window類,後面的就容易理解一些了,其中整個NotePad++的主窗口類Notepad_plus_Window繼承自Window,notepad++中全部的對話框都是繼承自StaticDialog的,而StaticDialog也是繼承自Window這個父類的。下面是Window類的源碼:api

class Window
{
public:
    Window(): _hInst(NULL), _hParent(NULL), _hSelf(NULL){};
    virtual ~Window() {};

    virtual void init(HINSTANCE hInst, HWND parent)
    {
        _hInst = hInst;
        _hParent = parent;
    }

    virtual void destroy() = 0;

    virtual void display(bool toShow = true) const {
        ::ShowWindow(_hSelf, toShow?SW_SHOW:SW_HIDE);
    };
    
    virtual void reSizeTo(RECT & rc) // should NEVER be const !!!
    { 
        ::MoveWindow(_hSelf, rc.left, rc.top, rc.right, rc.bottom, TRUE);
        redraw();
    };

    virtual void reSizeToWH(RECT & rc) // should NEVER be const 
    { 
        ::MoveWindow(_hSelf, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, TRUE);
        redraw();
    };

    virtual void redraw(bool forceUpdate = false) const {
        ::InvalidateRect(_hSelf, NULL, TRUE);
        if (forceUpdate)
            ::UpdateWindow(_hSelf);
    };
    
    virtual void getClientRect(RECT & rc) const {
        ::GetClientRect(_hSelf, &rc);
    };

    virtual void getWindowRect(RECT & rc) const {
        ::GetWindowRect(_hSelf, &rc);
    };

    virtual int getWidth() const {
        RECT rc;
        ::GetClientRect(_hSelf, &rc);
        return (rc.right - rc.left);
    };

    virtual int getHeight() const {
        RECT rc;
        ::GetClientRect(_hSelf, &rc);
        if (::IsWindowVisible(_hSelf) == TRUE)
            return (rc.bottom - rc.top);
        return 0;
    };

    virtual bool isVisible() const {
        return (::IsWindowVisible(_hSelf)?true:false);
    };

    HWND getHSelf() const {
        //assert(_hSelf != 0);
        return _hSelf;
    };

    HWND getHParent() const {
        return _hParent;
    };

    void getFocus() const {
        ::SetFocus(_hSelf);
    };

    HINSTANCE getHinst() const {
        //assert(_hInst != 0);
        return _hInst;
    };
protected:
    HINSTANCE _hInst;
    HWND _hParent;
    HWND _hSelf;
};

   從直觀上來講,由於像菜單欄、工具欄、編輯框等等這些窗口應該屬於主窗口,不過做者在主窗口Notepad_plus_Window和這些子窗口中間添加了一層,將全部的子窗口對象都封裝在了Notepad_plus這個類中,再由Notepad_plus_Window來封裝Notepad_plus對象_notepad_plus_plus_core。這樣一來讓主窗口的代碼和子窗口的一些實現分離了,讓Notepad_plus_Window的功能變得很清晰,不過Notepad_plus這個類由於封裝可大量的子窗口對象變得十分複雜,另外一個問題就是這些子窗口的父窗口須要指定,可是這個父窗口句柄被封裝在Notepad_plus_Window中,因而Notepad_plus類中又封裝了Notepad_plus_Window對象指針,機智的經過編譯又可以拿到父窗口句柄了。下面是Notepad_plus_Window源碼:session

class Notepad_plus_Window : public Window {
public:
    Notepad_plus_Window() : _isPrelaunch(false), _disablePluginsManager(false) {};


    void init(HINSTANCE, HWND, const TCHAR *cmdLine, CmdLineParams *cmdLineParams);

    bool isDlgsMsg(MSG *msg) const;
    
    HACCEL getAccTable() const {
        return _notepad_plus_plus_core.getAccTable();
    };
    
    bool emergency(generic_string emergencySavedDir) {
        return _notepad_plus_plus_core.emergency(emergencySavedDir);
    };

    bool isPrelaunch() const {
        return _isPrelaunch;
    };

    void setIsPrelaunch(bool val) {
        _isPrelaunch = val;
    };

    virtual void destroy(){
        ::DestroyWindow(_hSelf);
    };

    static const TCHAR * getClassName() {
        return _className;
    };
    static HWND gNppHWND;    //static handle to Notepad++ window, NULL if non-existant
    
private:
    Notepad_plus _notepad_plus_plus_core;
    //窗口過程函數
    static LRESULT CALLBACK Notepad_plus_Proc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam);
    LRESULT runProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam);

    static const TCHAR _className[32];
    bool _isPrelaunch;
    bool _disablePluginsManager;
    std::string _userQuote; // keep the availability of this string for thread using 
};
View Code

  接下來就從WinMain這個函數(源碼在下面)入口來說解程序是怎麼跑起來的,首先NotePad++是可以接受命令行的,從最開始就使用GetCommandLine()來接受命令行,做者爲了更好的支持命令行,寫了兩個類ParamVector和CmdLineParams來分別保存命令行以及根據這些命令決定了程序運行的一些屬性(如是否容許插件、可讀性等等)。做者將GetCommandLine()返回的LPTSTR類型變量使用算法parseCommandLine()保存到了ParamVector對象中,在利用CmdLineParams方法isInList()來判斷是否命令行帶有某些程序運行屬性,並將其保存在CmdLineParams對象中,這裏有些屬性立刻就用到,有些屬性都是好久以後纔用到的。app

  由於NotePad++將許多配置信息都保存在了本地文件中,好比哪國語言、總體風格、用戶快捷鍵等等都是如此,所以在命令行以後就應該處理好這些參數,以讓窗口和子窗口顯示出來時候都是按照之前設置的配置來的。這裏做者建立NppParameters類來控制配置信息,這個類的頭文件和源文件也是多的夠嗆,分別是1700行和6500行。總的來講就是把本地的配置信息讀取到內存中來,NppGUI用來保存界面配置,ScintillaViewParams用來保存ScintillaView的配置,LexerStylerArray和StyleArray用來保存顏色以及字體,還有許多vector類來保存各類快捷鍵。這些配置的載入時從NppParameters的load()函數運行開始的,這些配置文件都應該跟程序在同一個文件夾下,由於代碼中默認在程序運行的同一路徑之下去查找這些配置文件的,在通過讀取config.xml、stylers.xml、userDefineLang.xml、nativeLang.xml、toolbarIcons.xml、shortcuts.xml、contextMenu.xml、session.xml、blacklist.xml這些配置文件讀入以後,load()函數就返回了,有讀固然有寫,寫函數也是定義在NppParameters類中。其實只要找到一個配置文件debug一趟就明白前因後果了。

  以後回到WinMain中判斷程序是否容許多實例,若是不容許多實例而且還不是第一個啓動的實例的話,就直接用::FindWindow()找到已經存在在內存中的窗口就好,以後顯示這個主窗口,若是有參數的話就把參數利用::SendMessage()以WM_COPYDATA的消息傳遞過去,以後返回。若是是容許多實例(用戶能夠在首選項設定)或者是第一次啓動NotePad++的話直接跳過這段日後執行。

  若是是一個新的實例的話,先建立主界面封裝類Notepad_plus_Window對象notepad_plus_plus留着後面用。緊接着程序看看當前目錄下的updater目錄下有沒有GUP.exe這個程序,這個程序是用來升級NotePad++的,若是當前的日期比較陳舊而且存在這個程序而且操做系統比XP新再而且是第一個NotePad++實例的話就運行這個NotePad++的程序,若是有一個不符合就過嘍。

  以後當我第一次看到了MSG msg;這句代碼,我很高興,說明消息循環要開始了,後面要透明瞭,可是在此以前首先執行了notepad_plus_plus.init(hInstance, NULL, quotFileName.c_str(), &cmdLineParams); notepad_plus_plus我以前說過是主界面對象,這個對象在初始化的時候基本沒幹什麼事情,反而在這裏要露肌肉了,由於init()函數太過龐大,容我先傳上WinMain函數的代碼再來解釋這個函數:

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int)
{
    LPTSTR cmdLine = ::GetCommandLine();
    ParamVector params;
    parseCommandLine(cmdLine, params);

    MiniDumper mdump;    //for debugging purposes.

    bool TheFirstOne = true;
    ::SetLastError(NO_ERROR);
    ::CreateMutex(NULL, false, TEXT("nppInstance"));
    if (::GetLastError() == ERROR_ALREADY_EXISTS)
        TheFirstOne = false;

    bool isParamePresent;
    bool showHelp = isInList(FLAG_HELP, params);
    bool isMultiInst = isInList(FLAG_MULTI_INSTANCE, params);

    CmdLineParams cmdLineParams;
    cmdLineParams._isNoTab = isInList(FLAG_NOTABBAR, params);
    cmdLineParams._isNoPlugin = isInList(FLAG_NO_PLUGIN, params);
    cmdLineParams._isReadOnly = isInList(FLAG_READONLY, params);
    cmdLineParams._isNoSession = isInList(FLAG_NOSESSION, params);
    cmdLineParams._isPreLaunch = isInList(FLAG_SYSTRAY, params);
    cmdLineParams._alwaysOnTop = isInList(FLAG_ALWAYS_ON_TOP, params);
    cmdLineParams._showLoadingTime = isInList(FLAG_LOADINGTIME, params);
    cmdLineParams._isSessionFile = isInList(FLAG_OPENSESSIONFILE, params);
    cmdLineParams._isRecursive = isInList(FLAG_RECURSIVE, params);

    cmdLineParams._langType = getLangTypeFromParam(params);
    cmdLineParams._localizationPath = getLocalizationPathFromParam(params);
    cmdLineParams._line2go = getNumberFromParam('n', params, isParamePresent);
    cmdLineParams._column2go = getNumberFromParam('c', params, isParamePresent);
    cmdLineParams._point.x = getNumberFromParam('x', params, cmdLineParams._isPointXValid);
    cmdLineParams._point.y = getNumberFromParam('y', params, cmdLineParams._isPointYValid);
    cmdLineParams._easterEggName = getEasterEggNameFromParam(params, cmdLineParams._quoteType);
    
    
    if (showHelp)
    {
        ::MessageBox(NULL, COMMAND_ARG_HELP, TEXT("Notepad++ Command Argument Help"), MB_OK);
    }

    NppParameters *pNppParameters = NppParameters::getInstance();
    
    if (cmdLineParams._localizationPath != TEXT(""))
    {
        pNppParameters->setStartWithLocFileName(cmdLineParams._localizationPath);
    }
    pNppParameters->load();

    // override the settings if notepad style is present
    if (pNppParameters->asNotepadStyle())
    {
        isMultiInst = true;
        cmdLineParams._isNoTab = true;
        cmdLineParams._isNoSession = true;
    }

    // override the settings if multiInst is choosen by user in the preference dialog
    const NppGUI & nppGUI = pNppParameters->getNppGUI();
    if (nppGUI._multiInstSetting == multiInst)
    {
        isMultiInst = true;
        // Only the first launch remembers the session
        if (!TheFirstOne)
            cmdLineParams._isNoSession = true;
    }

    generic_string quotFileName = TEXT("");
    // tell the running instance the FULL path to the new files to load
    size_t nrFilesToOpen = params.size();

    for(size_t i = 0; i < nrFilesToOpen; ++i)
    {
        const TCHAR * currentFile = params.at(i);
        if (currentFile[0])
        {
            //check if relative or full path. Relative paths dont have a colon for driveletter
            
            quotFileName += TEXT("\"");
            quotFileName += relativeFilePathToFullFilePath(currentFile);
            quotFileName += TEXT("\" ");
        }
    }

    //Only after loading all the file paths set the working directory
    ::SetCurrentDirectory(NppParameters::getInstance()->getNppPath().c_str());    //force working directory to path of module, preventing lock

    if ((!isMultiInst) && (!TheFirstOne))
    {
        HWND hNotepad_plus = ::FindWindow(Notepad_plus_Window::getClassName(), NULL);
        for (int i = 0 ;!hNotepad_plus && i < 5 ; ++i)
        {
            Sleep(100);
            hNotepad_plus = ::FindWindow(Notepad_plus_Window::getClassName(), NULL);
        }

        if (hNotepad_plus)
        {
        // First of all, destroy static object NppParameters
        pNppParameters->destroyInstance();
        MainFileManager->destroyInstance();

        int sw = 0;

        if (::IsZoomed(hNotepad_plus))
            sw = SW_MAXIMIZE;
        else if (::IsIconic(hNotepad_plus))
            sw = SW_RESTORE;

/* REMOVED
        else
            sw = SW_SHOW;

        // IMPORTANT !!!
        ::ShowWindow(hNotepad_plus, sw);
DEVOMER*/
/* ADDED */
        if (sw != 0)
            ::ShowWindow(hNotepad_plus, sw);
/* DEDDA */
        ::SetForegroundWindow(hNotepad_plus);

        if (params.size() > 0)    //if there are files to open, use the WM_COPYDATA system
        {
            COPYDATASTRUCT paramData;
            paramData.dwData = COPYDATA_PARAMS;
            paramData.lpData = &cmdLineParams;
            paramData.cbData = sizeof(cmdLineParams);

            COPYDATASTRUCT fileNamesData;
            fileNamesData.dwData = COPYDATA_FILENAMES;
            fileNamesData.lpData = (void *)quotFileName.c_str();
            fileNamesData.cbData = long(quotFileName.length() + 1)*(sizeof(TCHAR));

            ::SendMessage(hNotepad_plus, WM_COPYDATA, (WPARAM)hInstance, (LPARAM)&paramData);
            ::SendMessage(hNotepad_plus, WM_COPYDATA, (WPARAM)hInstance, (LPARAM)&fileNamesData);
        }
        return 0;
        }
    }
    //最重要的主界面對象在此處建立 具體事物封裝在類中
    Notepad_plus_Window notepad_plus_plus;
    
    NppGUI & nppGui = (NppGUI &)pNppParameters->getNppGUI();

    generic_string updaterDir = pNppParameters->getNppPath();
    updaterDir += TEXT("\\updater\\");

    generic_string updaterFullPath = updaterDir + TEXT("GUP.exe");
 
    generic_string version = TEXT("-v");
    version += VERSION_VALUE;

    bool isUpExist = nppGui._doesExistUpdater = (::PathFileExists(updaterFullPath.c_str()) == TRUE);

    bool doUpdate = nppGui._autoUpdateOpt._doAutoUpdate;

    if (doUpdate) // check more detail 
    {
        Date today(0);
        
        if (today < nppGui._autoUpdateOpt._nextUpdateDate)
            doUpdate = false;
    }

    // wingup doesn't work with the obsolet security layer (API) under xp since downloadings are secured with SSL on notepad_plus_plus.org
    winVer ver = pNppParameters->getWinVersion();
    bool isGtXP = ver > WV_XP;
    //若是是第一個實例 具備升級版本在 確認升級 windows版本大於xp則升級 調用的是ShellExcute程序
    if (TheFirstOne && isUpExist && doUpdate && isGtXP)
    {
        Process updater(updaterFullPath.c_str(), version.c_str(), updaterDir.c_str());
        updater.run();
        
        // Update next update date
        if (nppGui._autoUpdateOpt._intervalDays < 0) // Make sure interval days value is positive
            nppGui._autoUpdateOpt._intervalDays = 0 - nppGui._autoUpdateOpt._intervalDays;
        nppGui._autoUpdateOpt._nextUpdateDate = Date(nppGui._autoUpdateOpt._intervalDays);
    }
    MSG msg;
    msg.wParam = 0;
    Win32Exception::installHandler();
    try {
        notepad_plus_plus.init(hInstance, NULL, quotFileName.c_str(), &cmdLineParams);

        // Tell UAC that lower integrity processes are allowed to send WM_COPYDATA messages to this process (or window)
        // This allows opening new files to already opened elevated Notepad++ process via explorer context menu.
        if (ver >= WV_VISTA || ver == WV_UNKNOWN)
        {
            HMODULE hDll = GetModuleHandle(TEXT("user32.dll"));
            if (hDll)
            {
                // According to MSDN ChangeWindowMessageFilter may not be supported in future versions of Windows, 
                // that is why we use ChangeWindowMessageFilterEx if it is available (windows version >= Win7).
                if(pNppParameters->getWinVersion() == WV_VISTA)
                {
                    typedef BOOL (WINAPI *MESSAGEFILTERFUNC)(UINT message,DWORD dwFlag);
                    //const DWORD MSGFLT_ADD = 1;

                    MESSAGEFILTERFUNC func = (MESSAGEFILTERFUNC)::GetProcAddress( hDll, "ChangeWindowMessageFilter" );

                    if (func)
                    {
                        func(WM_COPYDATA, MSGFLT_ADD);
                    }
                }
                else
                {
                    typedef BOOL (WINAPI *MESSAGEFILTERFUNCEX)(HWND hWnd,UINT message,DWORD action,VOID* pChangeFilterStruct);
                    //const DWORD MSGFLT_ALLOW = 1;

                    MESSAGEFILTERFUNCEX func = (MESSAGEFILTERFUNCEX)::GetProcAddress( hDll, "ChangeWindowMessageFilterEx" );

                    if (func)
                    {
                        func(notepad_plus_plus.getHSelf(), WM_COPYDATA, MSGFLT_ALLOW, NULL );
                    }
                }
            }
        }

        bool going = true;
        while (going)
        {
            going = ::GetMessageW(&msg, NULL, 0, 0) != 0;
            if (going)
            {
                // if the message doesn't belong to the notepad_plus_plus's dialog
                if (!notepad_plus_plus.isDlgsMsg(&msg))
                {
                    //翻譯鍵盤加速鍵
                    if (::TranslateAccelerator(notepad_plus_plus.getHSelf(), notepad_plus_plus.getAccTable(), &msg) == 0)
                    {
                        ::TranslateMessage(&msg);
                        ::DispatchMessageW(&msg);
                    }
                }
            }
        }
    } catch(int i) {
        TCHAR str[50] = TEXT("God Damned Exception : ");
        TCHAR code[10];
        wsprintf(code, TEXT("%d"), i);
        ::MessageBox(Notepad_plus_Window::gNppHWND, lstrcat(str, code), TEXT("Int Exception"), MB_OK);
        doException(notepad_plus_plus);
    } catch(std::runtime_error & ex) {
        ::MessageBoxA(Notepad_plus_Window::gNppHWND, ex.what(), "Runtime Exception", MB_OK);
        doException(notepad_plus_plus);
    } catch (const Win32Exception & ex) {
        TCHAR message[1024];    //TODO: sane number
        wsprintf(message, TEXT("An exception occured. Notepad++ cannot recover and must be shut down.\r\nThe exception details are as follows:\r\n")
        TEXT("Code:\t0x%08X\r\nType:\t%S\r\nException address: 0x%08X"), ex.code(), ex.what(), (long)ex.where());
        ::MessageBox(Notepad_plus_Window::gNppHWND, message, TEXT("Win32Exception"), MB_OK | MB_ICONERROR);
        mdump.writeDump(ex.info());
        doException(notepad_plus_plus);
    } catch(std::exception & ex) {
        ::MessageBoxA(Notepad_plus_Window::gNppHWND, ex.what(), "General Exception", MB_OK);
        doException(notepad_plus_plus);
    } catch(...) {    //this shouldnt ever have to happen
        ::MessageBoxA(Notepad_plus_Window::gNppHWND, "An exception that we did not yet found its name is just caught", "Unknown Exception", MB_OK);
        doException(notepad_plus_plus);
    }

    return (UINT)msg.wParam;
}

  如今進入主窗口對象notepad_plus_plus的init()函數進行探究,init()函數的第一件事情就是建立窗口類,這裏指定了主窗口的總體風格以及其菜單名稱,這裏的菜單就是做者事先準備好的菜單資源,這個資源定義在Notepad_plus.rc資源腳本中。以後就是你們熟知的註冊窗口類,用CreateWindowEx建立窗口,返回的句柄保存在從Window繼承下來的_hSelf成員變量中。由於有CreateWindowEx這個函數,系統會發送WM_CREATE消息到消息隊列中,由於這個消息比較特殊,在消息循環未創建好以前也會被回調函數捕捉處理。所以在CreateWindowEx函數以後就應該轉到窗口句柄對應的回調函數去,這個函數的實現位於NppBigSwitch.cpp中,下面是這個函數,只有WM_CREATE是現場處理的,其餘的消息都被轉到了_notepad_plus_plus_core的process函數中去了:

LRESULT Notepad_plus_Window::runProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
    LRESULT result = FALSE;
    switch (Message)
    {
        case WM_CREATE:
        {
            try{
                _notepad_plus_plus_core._pPublicInterface = this;
                result = _notepad_plus_plus_core.init(hwnd);
            } catch (std::exception ex) {
                ::MessageBoxA(_notepad_plus_plus_core._pPublicInterface->getHSelf(), ex.what(), "Exception On WM_CREATE", MB_OK);
                exit(-1);
            }
        }
        break;
        default:    
            if (this)
                return _notepad_plus_plus_core.process(hwnd, Message, wParam, lParam);
    }
    return result;
}

  在處理WM_CREATE消息中調用了_notepad_plus_plus_core的init()函數,按照程序執行的步驟來說解,這裏也轉到init()函數內部來進行講解,先理一下順序:WinMain->notepad_plus_plus.init()->CreateWindwEx()->WM_CREATE->_note_plus_plus_core.init();固然只有WM_CREATE會被首先捕捉處理,其餘的消息仍然是在構建好消息循環以後接受處理的。

  程序首先斷定語言菜單項是否是緊湊型的,若是是緊湊型的話就將現有的語言菜單欄給移走,由於這裏須要改變語言欄的形狀,由於_notepad_plus_plus_core中已經定義了兩個ScitillaEditView(開源項目scintilla的封裝)對象_mainEditView和_subEditView,這兩個很容易理解,由於Notepad++是可以將編輯框分割成左邊通常右邊通常的,所以這兩個一個是左邊的編輯框,一個是右邊的編輯框。然而還定義了兩個DocTabView對象,_mainDocTab和_subDocTab,這個十分有必要說一下,DocTabView類是繼承自TabBarPlus的,而TabBarPlus是集成自TabBar的,而TabBar是繼承自Window的,說明DocTabView是一個窗口,若是看到程序後面的DocTabView對象的init()函數就知道,這個DocTabView對象不只繼承自TabBarPlus並且還將ScitillaEditView封裝在本身的對象中,從字面意思可以看懂TabBarPlus是一個標籤欄,一個標籤欄和編輯框組合在一塊兒,明顯確定是想要利用標籤欄來控制編輯框的關閉、移動窗口等等工做。事實上,DocTabView的init()函數還接受了IconList對象,其實這個對象_docTabIconList中只是用來管理標籤欄的圖標的,一共只有三種,也就是未保存狀態的圖標,保存狀態的圖標和文件只可讀的圖標:

    int tabBarStatus = nppGUI._tabStatus;
    _toReduceTabBar = ((tabBarStatus & TAB_REDUCE) != 0);
    int iconDpiDynamicalSize = NppParameters::getInstance()->_dpiManager.scaleY(_toReduceTabBar?13:20);
    _docTabIconList.create(iconDpiDynamicalSize, _pPublicInterface->getHinst(), docTabIconIDs, sizeof(docTabIconIDs)/sizeof(int));

    _mainDocTab.init(_pPublicInterface->getHinst(), hwnd, &_mainEditView, &_docTabIconList);
    _subDocTab.init(_pPublicInterface->getHinst(), hwnd, &_subEditView, &_docTabIconList);

  上面有個tabBarStatus用來保存標籤欄的狀態,決定了是縮小仍是不縮小,這個功能在實際的軟件的設置->首選項->經常使用->標籤欄中能夠勾選或者不選縮小。以後程序又爲一個不可見的ScitillaEditView對象_invisibleEditView進行了初始化,這個_invisibleEditView是爲了用戶搜索以後將搜索結果放在這個_invisibleEditView中顯示的,平時當讓不可見了。再以後就是對三個做者封裝的編輯框進行了初始化,由於scintilla自己十分之強大,而做者的ScitillaEditView主要是對這個功能進行了本身的封裝,以後都是經過execute()函數來進行調用scintilla的功能的,這些針對於ScitillaEditView的設置暫時放在一邊。以後初始化了兩個對話框:

    _configStyleDlg.init(_pPublicInterface->getHinst(), hwnd);
    _preference.init(_pPublicInterface->getHinst(), hwnd);

  一個是語言格式設置,一個就是首選項了。在這以後就是就是調整標籤欄的顯示情況、標籤欄上的關閉按鈕、繪製頂層標籤欄、決定標籤欄是否可以拖動等等一系列事情,再加載了標籤欄的風格:

    TabBarPlus::doDragNDrop(true);

    if (_toReduceTabBar)
    {
        HFONT hf = (HFONT)::GetStockObject(DEFAULT_GUI_FONT);

        if (hf)
        {
            ::SendMessage(_mainDocTab.getHSelf(), WM_SETFONT, (WPARAM)hf, MAKELPARAM(TRUE, 0));
            ::SendMessage(_subDocTab.getHSelf(), WM_SETFONT, (WPARAM)hf, MAKELPARAM(TRUE, 0));
        }
        int tabDpiDynamicalHeight = NppParameters::getInstance()->_dpiManager.scaleY(20);
        int tabDpiDynamicalWidth = NppParameters::getInstance()->_dpiManager.scaleX(45);
        TabCtrl_SetItemSize(_mainDocTab.getHSelf(), tabDpiDynamicalWidth, tabDpiDynamicalHeight);
        TabCtrl_SetItemSize(_subDocTab.getHSelf(), tabDpiDynamicalWidth, tabDpiDynamicalHeight);
    }
    _mainDocTab.display();
    TabBarPlus::doDragNDrop((tabBarStatus & TAB_DRAGNDROP) != 0);
    TabBarPlus::setDrawTopBar((tabBarStatus & TAB_DRAWTOPBAR) != 0);
    TabBarPlus::setDrawInactiveTab((tabBarStatus & TAB_DRAWINACTIVETAB) != 0);
    TabBarPlus::setDrawTabCloseButton((tabBarStatus & TAB_CLOSEBUTTON) != 0);
    TabBarPlus::setDbClk2Close((tabBarStatus & TAB_DBCLK2CLOSE) != 0);
    TabBarPlus::setVertical((tabBarStatus & TAB_VERTICAL) != 0);
    drawTabbarColoursFromStylerArray();

  以後又初始化了分割欄SplitterContainer對象_subSplitter,這個從字面上來理解爲分割容器的類其實十分之強大和費解,我在讀取有關這個類的代碼的時候一度十分困擾,跟蹤了許多消息才恍然大悟是如此使用。其實除了這個_subSplitter以外,還有一個_pMainSplitter,讓咱們先搞懂_subSplitter這個對象,這個類SplitterContainer也是繼承自Window的,_subSplitter在建立開始的時候吸納了兩個帶有標籤欄的編輯框,並在對象內維護了一個真正的Splitter,也就是說事實上咱們看到的NotePad++的運行界面上的兩個編輯框事實上還有一個SplitterContainer在顯示,只不過其Background是NULL,也就是說不可見的。至於這樣作的好處,可謂十分機制,在後面會有講述。以後又初始化了狀態欄,設定了狀態欄的大體狀況:

    bool isVertical = (nppGUI._splitterPos == POS_VERTICAL);

    _subSplitter.init(_pPublicInterface->getHinst(), hwnd);
    _subSplitter.create(&_mainDocTab, &_subDocTab, 8, DYNAMIC, 50, isVertical);

    //--Status Bar Section--//
    bool willBeShown = nppGUI._statusBarShow;
    _statusBar.init(_pPublicInterface->getHinst(), hwnd, 6);
    _statusBar.setPartWidth(STATUSBAR_DOC_SIZE, 200);
    _statusBar.setPartWidth(STATUSBAR_CUR_POS, 260);
    _statusBar.setPartWidth(STATUSBAR_EOF_FORMAT, 110);
    _statusBar.setPartWidth(STATUSBAR_UNICODE_TYPE, 120);
    _statusBar.setPartWidth(STATUSBAR_TYPING_MODE, 30);
    _statusBar.display(willBeShown);

  以後判斷主界面是否須要被最小化,若是須要最小化的話則先保存。以後又讓插件管理器初始化了,由於後面須要加載插件信息。接着後面就比較簡單了,就是爲主菜單欄的菜單項添加子菜單,有宏菜單、運行菜單、語言、文件、插件、窗口菜單,一個比較特殊的就是「升級」這個菜單項是否應該出如今菜單中,若是不該該就刪除嘛。另外語言菜單項還須要將用戶排除的那些編程語言去除語言菜單項中,這些代碼雖然長,可是比較簡單:

    std::vector<MacroShortcut> & macros  = pNppParam->getMacroList();
    HMENU hMacroMenu = ::GetSubMenu(_mainMenuHandle, MENUINDEX_MACRO);
    size_t const posBase = 6;
    size_t nbMacro = macros.size();
    if (nbMacro >= 1)
        ::InsertMenu(hMacroMenu, posBase - 1, MF_BYPOSITION, (unsigned int)-1, 0);

    for (size_t i = 0 ; i < nbMacro ; ++i)
    {
        ::InsertMenu(hMacroMenu, posBase + i, MF_BYPOSITION, ID_MACRO + i, macros[i].toMenuItemString().c_str());
    }

    if (nbMacro >= 1)
    {
        ::InsertMenu(hMacroMenu, posBase + nbMacro + 1, MF_BYPOSITION, (unsigned int)-1, 0);
        ::InsertMenu(hMacroMenu, posBase + nbMacro + 2, MF_BYCOMMAND, IDM_SETTING_SHORTCUT_MAPPER_MACRO, TEXT("Modify Shortcut/Delete Macro..."));
    }
    // Run Menu
    std::vector<UserCommand> & userCommands = pNppParam->getUserCommandList();
    HMENU hRunMenu = ::GetSubMenu(_mainMenuHandle, MENUINDEX_RUN);
    int const runPosBase = 2;
    size_t nbUserCommand = userCommands.size();
    if (nbUserCommand >= 1)
        ::InsertMenu(hRunMenu, runPosBase - 1, MF_BYPOSITION, (unsigned int)-1, 0);
    for (size_t i = 0 ; i < nbUserCommand ; ++i)
    {
        ::InsertMenu(hRunMenu, runPosBase + i, MF_BYPOSITION, ID_USER_CMD + i, userCommands[i].toMenuItemString().c_str());
    }

    if (nbUserCommand >= 1)
    {
        ::InsertMenu(hRunMenu, runPosBase + nbUserCommand + 1, MF_BYPOSITION, (unsigned int)-1, 0);
        ::InsertMenu(hRunMenu, runPosBase + nbUserCommand + 2, MF_BYCOMMAND, IDM_SETTING_SHORTCUT_MAPPER_RUN, TEXT("Modify Shortcut/Delete Command..."));
    }

    // Updater menu item
    if (!nppGUI._doesExistUpdater)
    {
        ::DeleteMenu(_mainMenuHandle, IDM_UPDATE_NPP, MF_BYCOMMAND);
        ::DeleteMenu(_mainMenuHandle, IDM_CONFUPDATERPROXY, MF_BYCOMMAND);
        HMENU hHelpMenu = ::GetSubMenu(_mainMenuHandle, MENUINDEX_PLUGINS + 1);
        if (!hHelpMenu)
            hHelpMenu = ::GetSubMenu(_mainMenuHandle, MENUINDEX_PLUGINS);
        if (hHelpMenu)
            ::DeleteMenu(hHelpMenu, 7, MF_BYPOSITION); // SEPARATOR
        ::DrawMenuBar(hwnd);
    }
    //Languages Menu
    HMENU hLangMenu = ::GetSubMenu(_mainMenuHandle, MENUINDEX_LANGUAGE);

    // Add external languages to menu
    for (int i = 0 ; i < pNppParam->getNbExternalLang() ; ++i)
    {
        ExternalLangContainer & externalLangContainer = pNppParam->getELCFromIndex(i);

        int numLangs = ::GetMenuItemCount(hLangMenu);
        const int bufferSize = 100;
        TCHAR buffer[bufferSize];

        int x;
        for(x = 0; (x == 0 || lstrcmp(externalLangContainer._name, buffer) > 0) && x < numLangs; ++x)
        {
            ::GetMenuString(hLangMenu, x, buffer, bufferSize, MF_BYPOSITION);
        }

        ::InsertMenu(hLangMenu, x-1, MF_BYPOSITION, IDM_LANG_EXTERNAL + i, externalLangContainer._name);
    }

    if (nppGUI._excludedLangList.size() > 0)
    {
        for (size_t i = 0, len = nppGUI._excludedLangList.size(); i < len ; ++i)
        {
            int cmdID = pNppParam->langTypeToCommandID(nppGUI._excludedLangList[i]._langType);
            const int itemSize = 256;
            TCHAR itemName[itemSize];
            ::GetMenuString(hLangMenu, cmdID, itemName, itemSize, MF_BYCOMMAND);
            nppGUI._excludedLangList[i]._cmdID = cmdID;
            nppGUI._excludedLangList[i]._langName = itemName;
            ::DeleteMenu(hLangMenu, cmdID, MF_BYCOMMAND);
            DrawMenuBar(hwnd);
        }
    }
    //File Menu
    HMENU hFileMenu = ::GetSubMenu(_mainMenuHandle, MENUINDEX_FILE);
    int nbLRFile = pNppParam->getNbLRFile();
    //int pos = IDM_FILEMENU_LASTONE - IDM_FILE + 1 /* +1 : because of  IDM_FILE_PRINTNOW */;

    _lastRecentFileList.initMenu(hFileMenu, IDM_FILEMENU_LASTONE + 1, IDM_FILEMENU_EXISTCMDPOSITION, &_accelerator, pNppParam->putRecentFileInSubMenu());
    _lastRecentFileList.setLangEncoding(_nativeLangSpeaker.getLangEncoding());
    for (int i = 0 ; i < nbLRFile ; ++i)
    {
        generic_string * stdStr = pNppParam->getLRFile(i);
        if (!nppGUI._checkHistoryFiles || PathFileExists(stdStr->c_str()))
        {
            _lastRecentFileList.add(stdStr->c_str());
        }
    }

    //Plugin menu
    _pluginsManager.setMenu(_mainMenuHandle, NULL);

    //Main menu is loaded, now load context menu items
    //主菜單和右鍵菜單的初始化
    pNppParam->getContextMenuFromXmlTree(_mainMenuHandle, _pluginsManager.getMenuHandle());

    if (pNppParam->hasCustomContextMenu())
    {
        _mainEditView.execute(SCI_USEPOPUP, FALSE);
        _subEditView.execute(SCI_USEPOPUP, FALSE);
    }

    //尋找nativeLang.xml文件 若是有這個文件 就可以經過這個文件來進行改變notepad++更改語言
    generic_string pluginsTrans, windowTrans;
    _nativeLangSpeaker.changeMenuLang(_mainMenuHandle, pluginsTrans, windowTrans);
    ::DrawMenuBar(hwnd);


    if (_pluginsManager.hasPlugins() && pluginsTrans != TEXT(""))
    {
        ::ModifyMenu(_mainMenuHandle, MENUINDEX_PLUGINS, MF_BYPOSITION, 0, pluginsTrans.c_str());
    }
    //Windows menu
    _windowsMenu.init(_pPublicInterface->getHinst(), _mainMenuHandle, windowTrans.c_str());

    // Update context menu strings (translated)
    vector<MenuItemUnit> & tmp = pNppParam->getContextMenuItems();
    size_t len = tmp.size();
    TCHAR menuName[64];
    for (size_t i = 0 ; i < len ; ++i)
    {
        if (tmp[i]._itemName == TEXT(""))
        {
            ::GetMenuString(_mainMenuHandle, tmp[i]._cmdID, menuName, 64, MF_BYCOMMAND);
            tmp[i]._itemName = purgeMenuItemString(menuName);
        }
    }

  後面就是程序另外一大功能了,添加程序快捷鍵,做者將這些快捷鍵分開管理了,其實添加快捷鍵的代碼仍是比較麻煩的,主要的仍是先將普通菜單欄的一些菜單項的快捷鍵設置好,其次是插件的快捷鍵,宏的快捷鍵,用戶自定義的快捷鍵,右鍵菜單。分別是都是用vector來保存的:vector<CommandShortcut> vector<MacroShortcut> vector<UserCommand> vector<PluginCmdShortcut>。在這期間將英語設置成了用戶以前自定義的語言:

    vector<CommandShortcut> & shortcuts = pNppParam->getUserShortcuts();
    len = shortcuts.size();

    for(size_t i = 0; i < len; ++i)
    {
        CommandShortcut & csc = shortcuts[i];
        if (!csc.getName()[0])
        {    //no predefined name, get name from menu and use that
            ::GetMenuString(_mainMenuHandle, csc.getID(), menuName, 64, MF_BYCOMMAND);
            //獲得現有的菜單項 若是須要改變的則改變
            csc.setName(purgeMenuItemString(menuName, true).c_str());
        }
    }
    //notepad++將快捷鍵分紅了幾個部分 不管如何 最終都放在了快捷鍵表中了
    //Translate non-menu shortcuts
    //請注意 此爲非菜單的快捷鍵 此處包括scint的快捷鍵
    _nativeLangSpeaker.changeShortcutLang();

    //Update plugin shortcuts, all plugin commands should be available now
    //插件的快捷鍵映射
    pNppParam->reloadPluginCmds();

  以後就到了映射快捷鍵的時候的,通過層層調用,最終使用系統函數CreateAcceleratorTable來進行映射:

void Accelerator::updateShortcuts() 
{
    vector<int> IFAccIds;
    IFAccIds.push_back(IDM_SEARCH_FINDNEXT);
    IFAccIds.push_back(IDM_SEARCH_FINDPREV);
    IFAccIds.push_back(IDM_SEARCH_FINDINCREMENT);

    NppParameters *pNppParam = NppParameters::getInstance();

    vector<CommandShortcut> & shortcuts = pNppParam->getUserShortcuts();
    vector<MacroShortcut> & macros  = pNppParam->getMacroList();
    vector<UserCommand> & userCommands = pNppParam->getUserCommandList();
    vector<PluginCmdShortcut> & pluginCommands = pNppParam->getPluginCommandList();

    size_t nbMenu = shortcuts.size();
    size_t nbMacro = macros.size();
    size_t nbUserCmd = userCommands.size();
    size_t nbPluginCmd = pluginCommands.size();

    if (_pAccelArray)
        delete [] _pAccelArray;
    _pAccelArray = new ACCEL[nbMenu+nbMacro+nbUserCmd+nbPluginCmd];
    vector<ACCEL> IFAcc;

    int offset = 0;
    size_t i = 0;
    //no validation performed, it might be that invalid shortcuts are being used by default. Allows user to 'hack', might be a good thing
    for(i = 0; i < nbMenu; ++i)
    {
        if (shortcuts[i].isEnabled())
        {
            _pAccelArray[offset].cmd = (WORD)(shortcuts[i].getID());
            _pAccelArray[offset].fVirt = shortcuts[i].getAcceleratorModifiers();
            _pAccelArray[offset].key = shortcuts[i].getKeyCombo()._key;

            // Special extra handling for shortcuts shared by Incremental Find dialog
            if (std::find(IFAccIds.begin(), IFAccIds.end(), shortcuts[i].getID()) != IFAccIds.end())
                IFAcc.push_back(_pAccelArray[offset]);

            ++offset;
        }
    }

    for(i = 0; i < nbMacro; ++i)
    {
        if (macros[i].isEnabled()) 
        {
            _pAccelArray[offset].cmd = (WORD)(macros[i].getID());
            _pAccelArray[offset].fVirt = macros[i].getAcceleratorModifiers();
            _pAccelArray[offset].key = macros[i].getKeyCombo()._key;
            ++offset;
        }
    }

    for(i = 0; i < nbUserCmd; ++i)
    {
        if (userCommands[i].isEnabled())
        {
            _pAccelArray[offset].cmd = (WORD)(userCommands[i].getID());
            _pAccelArray[offset].fVirt = userCommands[i].getAcceleratorModifiers();
            _pAccelArray[offset].key = userCommands[i].getKeyCombo()._key;
            ++offset;
        }
    }

    for(i = 0; i < nbPluginCmd; ++i)
    {
        if (pluginCommands[i].isEnabled())
        {
            _pAccelArray[offset].cmd = (WORD)(pluginCommands[i].getID());
            _pAccelArray[offset].fVirt = pluginCommands[i].getAcceleratorModifiers();
            _pAccelArray[offset].key = pluginCommands[i].getKeyCombo()._key;
            ++offset;
        }
    }

    _nbAccelItems = offset;

    updateFullMenu();
    
    //update the table
    if (_hAccTable)
        ::DestroyAcceleratorTable(_hAccTable);
    //將pAccelArray轉換成爲加速鍵表
    _hAccTable = ::CreateAcceleratorTable(_pAccelArray, _nbAccelItems);

    if (_hIncFindAccTab)
        ::DestroyAcceleratorTable(_hIncFindAccTab);

    size_t nb = IFAcc.size();
    ACCEL *tmpAccelArray = new ACCEL[nb];
    for (i = 0; i < nb; ++i)
    {
        tmpAccelArray[i] = IFAcc[i];
    }
    // Incremental Find的減速鍵 是共享的
    _hIncFindAccTab = ::CreateAcceleratorTable(tmpAccelArray, nb);
    delete [] tmpAccelArray;

    return;
}

  還有編輯框的快捷鍵的映射,由於走的不一樣的通道,因此這裏原本就應該分開的:

    vector<HWND> scints;
    scints.push_back(_mainEditView.getHSelf());
    scints.push_back(_subEditView.getHSelf());
    _scintaccelerator.init(&scints, _mainMenuHandle, hwnd);

    pNppParam->setScintillaAccelerator(&_scintaccelerator);
    _scintaccelerator.updateKeys();

  以後就是工具欄了嘛,工具欄在win32中有比較方便的實現方法,這裏做者也是直接用的,首先須要TBBUTTON這個結構體,這個東西能夠直接和ImageList掛鉤起來,設置好圖片以後直接給toolbar發送一條消息就能夠:TB_SETIMAGELIST。由於這個是現有的就很少作解釋了,網上資料很少可是還可以找到點:

https://msdn.microsoft.com/en-us/library/bb787433(v=vs.85).aspx

http://www.gamedev.net/topic/451684-win32-non-mfc-rebar-and-toolbar-problems/

  再下面就是初始化其餘的對話框,好比查找代替啊,運行對話框啊等等...:

    _findReplaceDlg.init(_pPublicInterface->getHinst(), hwnd, &_pEditView);
    _incrementFindDlg.init(_pPublicInterface->getHinst(), hwnd, &_findReplaceDlg, _nativeLangSpeaker.isRTL());
    _incrementFindDlg.addToRebar(&_rebarBottom);
    _goToLineDlg.init(_pPublicInterface->getHinst(), hwnd, &_pEditView);
    _findCharsInRangeDlg.init(_pPublicInterface->getHinst(), hwnd, &_pEditView);
    _colEditorDlg.init(_pPublicInterface->getHinst(), hwnd, &_pEditView);
    _aboutDlg.init(_pPublicInterface->getHinst(), hwnd);
    _runDlg.init(_pPublicInterface->getHinst(), hwnd);
    _runMacroDlg.init(_pPublicInterface->getHinst(), hwnd);

  下面就是最麻煩的一個問題了,用戶自定義語言格式是一個對話框,可是這個對話框的特殊之處在於帶有了一個dock按鈕,也就是浮動功能,可是這個浮動和其餘窗口的浮動徹底不同,若是這個對話框在顯示的狀態下被按下了dock按鈕,會幹許多的事情:

    int uddStatus = nppGUI._userDefineDlgStatus;
    UserDefineDialog *udd = _pEditView->getUserDefineDlg();

    bool uddShow = false;
    switch (uddStatus)
    {
        case UDD_SHOW :                 // show & undocked
            udd->doDialog(true, _nativeLangSpeaker.isRTL());
            _nativeLangSpeaker.changeUserDefineLang(udd);
            uddShow = true;
            break;
        case UDD_DOCKED : {              // hide & docked
            _isUDDocked = true;
            break;}
        case (UDD_SHOW | UDD_DOCKED) :    // show & docked
            udd->doDialog(true, _nativeLangSpeaker.isRTL());
            _nativeLangSpeaker.changeUserDefineLang(udd);
            ::SendMessage(udd->getHSelf(), WM_COMMAND, IDC_DOCK_BUTTON, 0);
            uddShow = true;
            break;

        default :                        // hide & undocked
            break;
    }

  注意最後一個case狀況,這裏的udd->doDialog()可以將對話框顯示出來,由於沒有浮動的狀況下,這個對話框是沒有父類的,因此可以看到,讀者能夠自行調試一下。可是若是是浮動的狀態會經過消息的形式來模擬dock按鈕被按下,那按下以後作了什麼事情呢:

                    case IDC_DOCK_BUTTON :
                    {
                        int msg = WM_UNDOCK_USERDEFINE_DLG;

                        if (_status == UNDOCK)
                        {
                            if (pNppParam->isTransparentAvailable())
                            {
                                pNppParam->removeTransparent(_hSelf);
                                ::ShowWindow(::GetDlgItem(_hSelf, IDC_UD_TRANSPARENT_CHECK), SW_HIDE);
                                ::ShowWindow(::GetDlgItem(_hSelf, IDC_UD_PERCENTAGE_SLIDER), SW_HIDE);
                                ::UpdateWindow(_hSelf);
                            }
                            msg = WM_DOCK_USERDEFINE_DLG;
                        }

                        changeStyle();

                        if (_status == UNDOCK)
                        {
                            if (pNppParam->isTransparentAvailable())
                            {
                                bool isChecked = (BST_CHECKED == ::SendDlgItemMessage(_hSelf, IDC_UD_TRANSPARENT_CHECK, BM_GETCHECK, 0, 0));
                                if (isChecked)
                                {
                                    int percent = ::SendDlgItemMessage(_hSelf, IDC_UD_PERCENTAGE_SLIDER, TBM_GETPOS, 0, 0);
                                    pNppParam->SetTransparent(_hSelf, percent);
                                }
                                ::ShowWindow(::GetDlgItem(_hSelf, IDC_UD_TRANSPARENT_CHECK), SW_SHOW);
                                ::ShowWindow(::GetDlgItem(_hSelf, IDC_UD_PERCENTAGE_SLIDER), SW_SHOW);
                            }
                        }
                        ::SendMessage(_hParent, msg, 0, 0);
                        return TRUE;

  上面像顯示透明度條和按鈕啊都將被隱藏,由於在主窗口中透明確定很差實現,以後向父窗口也就是整個主窗口發送了一條消息

WM_DOCK_USERDEFINE_DLG,由於主窗口多了一個用戶自定義語言對話框,毫無疑問確定須要從新設計整個界面的大小和排版了,因而以下:
        case WM_DOCK_USERDEFINE_DLG:
        {
            dockUserDlg();
            return TRUE;
        }

  再進入到這個函數:

void Notepad_plus::dockUserDlg()
{
    if (!_pMainSplitter)
    {
        _pMainSplitter = new SplitterContainer;
        _pMainSplitter->init(_pPublicInterface->getHinst(), _pPublicInterface->getHSelf());

        Window *pWindow;
        //只要主窗口或者子窗口有一個是活動的 就將pwindow設置爲第二分離器
        //int i = _mainWindowStatus&(WindowMainActive | WindowSubActive);
        if (_mainWindowStatus & (WindowMainActive | WindowSubActive))
            pWindow = &_subSplitter;
        else
            pWindow = _pDocTab;
        //mainwindow 是一個splittercontainer 包含了兩個窗口和一個分離器
        _pMainSplitter->create(pWindow, ScintillaEditView::getUserDefineDlg(), 8, RIGHT_FIX, 45);
    }

    if (bothActive())
        _pMainSplitter->setWin0(&_subSplitter);
    else
        _pMainSplitter->setWin0(_pDocTab);

    _pMainSplitter->display();
    //主窗口的狀態加上用戶自定義窗口活動
    _mainWindowStatus |= WindowUserActive;
    _pMainWindow = _pMainSplitter;

    ::SendMessage(_pPublicInterface->getHSelf(), WM_SIZE, 0, 0);
}

  這裏終於從新見到了_pMainSplitter,這個意思就是若是用戶自定義語言窗口浮動了,就將_pMainSplitter設定爲_subSplitter和用戶自定義對話框,而_subSplitter在上面已經講過了是兩個文本框放在一塊兒的!這裏的_pMainSplitter完美的反映出目前的情況,這個機制的實現比較複雜,使用了指針指針對象以及部分多態,重點是跨越的點實在太遠了,很難聯想到一塊兒。在設置了這些窗口以後,用一個WM_SIZE消息調整一下大小,這個調整也很機智:

        case WM_SIZE:
        {
            RECT rc;
            _pPublicInterface->getClientRect(rc);
            if (lParam == 0) {
                lParam = MAKELPARAM(rc.right - rc.left, rc.bottom - rc.top);
            }

            ::MoveWindow(_rebarTop.getHSelf(), 0, 0, rc.right, _rebarTop.getHeight(), TRUE);
            _statusBar.adjustParts(rc.right);
            ::SendMessage(_statusBar.getHSelf(), WM_SIZE, wParam, lParam);

            int rebarBottomHeight = _rebarBottom.getHeight();
            int statusBarHeight = _statusBar.getHeight();
            ::MoveWindow(_rebarBottom.getHSelf(), 0, rc.bottom - rebarBottomHeight - statusBarHeight, rc.right, rebarBottomHeight, TRUE);
            
            getMainClientRect(rc);
            //每次改變都要改變dockingManager的size
            _dockingManager.reSizeTo(rc);

            if (_pDocMap)
            {
                _pDocMap->doMove();
                _pDocMap->reloadMap();
            }

            result = TRUE;
        }
        break;

  乍一看,感受一點關係都沒有,只不過就是改變了狀態欄的高度神馬的。這裏引出了一個新的對象_dockingManager,浮動管理器,這個東西感受就是用來管理用戶自定義語言對話框的,可是感受十分不靠譜,我在這裏被坑了好久。其實這裏的浮動管理器管理的額浮動根本就不是針對於兩個編輯框以及用戶自定義語言窗口的!這個是爲了像插件窗口這樣的能夠浮動的窗口準備的,整個_dockingManager管理者四塊區域,也就是上下左右,若是一點有哪一個窗口dock在了上下左右中的一個就會引發整個主界面客戶去的調整,首先調整的是dock窗口自己,其次!是ppMainWindow!也就是說只有dock窗口先調整,以後再輪到原來的兩個編輯框和用戶對話框的調整:

void DockingManager::reSizeTo(RECT & rc)
{
    // store current size of client area
    _rect = rc;

    // prepare size of work area
    _rcWork    = rc;

    if (_isInitialized == FALSE)
        return;

    // set top container
    _dockData.rcRegion[CONT_TOP].left      = rc.left;
    _dockData.rcRegion[CONT_TOP].top       = rc.top;
    _dockData.rcRegion[CONT_TOP].right     = rc.right-rc.left;
    
    _vSplitter[CONT_TOP]->display(false);

    if (_vContainer[CONT_TOP]->isVisible())
    {
        _rcWork.top        += _dockData.rcRegion[CONT_TOP].bottom + SPLITTER_WIDTH;
        _rcWork.bottom    -= _dockData.rcRegion[CONT_TOP].bottom + SPLITTER_WIDTH;

        // set size of splitter
        RECT    rc = {_dockData.rcRegion[CONT_TOP].left  ,
                      _dockData.rcRegion[CONT_TOP].top + _dockData.rcRegion[CONT_TOP].bottom,
                      _dockData.rcRegion[CONT_TOP].right ,
                      SPLITTER_WIDTH};
        _vSplitter[CONT_TOP]->reSizeTo(rc);
    }

    // set bottom container
    _dockData.rcRegion[CONT_BOTTOM].left   = rc.left;
    _dockData.rcRegion[CONT_BOTTOM].top    = rc.top + rc.bottom - _dockData.rcRegion[CONT_BOTTOM].bottom;
    _dockData.rcRegion[CONT_BOTTOM].right  = rc.right-rc.left;

    // create temporary rect for bottom container
    RECT        rcBottom    = _dockData.rcRegion[CONT_BOTTOM];

    _vSplitter[CONT_BOTTOM]->display(false);

    if (_vContainer[CONT_BOTTOM]->isVisible())
    {
        _rcWork.bottom    -= _dockData.rcRegion[CONT_BOTTOM].bottom + SPLITTER_WIDTH;

        // correct the visibility of bottom container when height is NULL
        if (_rcWork.bottom < rc.top)
        {
            rcBottom.top     = _rcWork.top + rc.top + SPLITTER_WIDTH;
            rcBottom.bottom += _rcWork.bottom - rc.top;
            _rcWork.bottom = rc.top;
        }
        if ((rcBottom.bottom + SPLITTER_WIDTH) < 0)
        {
            _rcWork.bottom = rc.bottom - _dockData.rcRegion[CONT_TOP].bottom;
        }

        // set size of splitter
        RECT    rc = {rcBottom.left,
                      rcBottom.top - SPLITTER_WIDTH,
                      rcBottom.right,
                      SPLITTER_WIDTH};
        _vSplitter[CONT_BOTTOM]->reSizeTo(rc);
    }

    // set left container
    _dockData.rcRegion[CONT_LEFT].left     = rc.left;
    _dockData.rcRegion[CONT_LEFT].top      = _rcWork.top;
    _dockData.rcRegion[CONT_LEFT].bottom   = _rcWork.bottom;

    _vSplitter[CONT_LEFT]->display(false);

    if (_vContainer[CONT_LEFT]->isVisible())
    {
        _rcWork.left        += _dockData.rcRegion[CONT_LEFT].right + SPLITTER_WIDTH;
        _rcWork.right    -= _dockData.rcRegion[CONT_LEFT].right + SPLITTER_WIDTH;

        // set size of splitter
        RECT    rc = {_dockData.rcRegion[CONT_LEFT].right,
                      _dockData.rcRegion[CONT_LEFT].top,
                      SPLITTER_WIDTH,
                      _dockData.rcRegion[CONT_LEFT].bottom};
        _vSplitter[CONT_LEFT]->reSizeTo(rc);
    }

    // set right container
    _dockData.rcRegion[CONT_RIGHT].left    = rc.right - _dockData.rcRegion[CONT_RIGHT].right;
    _dockData.rcRegion[CONT_RIGHT].top     = _rcWork.top;
    _dockData.rcRegion[CONT_RIGHT].bottom  = _rcWork.bottom;

    // create temporary rect for right container
    RECT        rcRight        = _dockData.rcRegion[CONT_RIGHT];

    _vSplitter[CONT_RIGHT]->display(false);
    if (_vContainer[CONT_RIGHT]->isVisible())
    {
        _rcWork.right    -= _dockData.rcRegion[CONT_RIGHT].right + SPLITTER_WIDTH;

        // correct the visibility of right container when width is NULL
        if (_rcWork.right < 15)
        {
            rcRight.left    = _rcWork.left + 15 + SPLITTER_WIDTH;
            rcRight.right  += _rcWork.right - 15;
            _rcWork.right    = 15;
        }

        // set size of splitter
        RECT    rc = {rcRight.left - SPLITTER_WIDTH,
                      rcRight.top,
                      SPLITTER_WIDTH,
                      rcRight.bottom};
        _vSplitter[CONT_RIGHT]->reSizeTo(rc);
    }

    // set window positions of container
    if (_vContainer[CONT_BOTTOM]->isVisible())
    {
        ::SetWindowPos(_vContainer[CONT_BOTTOM]->getHSelf(), NULL, 
                       rcBottom.left  ,
                       rcBottom.top   ,
                       rcBottom.right ,
                       rcBottom.bottom,
                       SWP_NOZORDER);
        _vSplitter[CONT_BOTTOM]->display();
    }

    if (_vContainer[CONT_TOP]->isVisible())
    {
        ::SetWindowPos(_vContainer[CONT_TOP]->getHSelf(), NULL, 
                       _dockData.rcRegion[CONT_TOP].left  ,
                       _dockData.rcRegion[CONT_TOP].top   ,
                       _dockData.rcRegion[CONT_TOP].right ,
                       _dockData.rcRegion[CONT_TOP].bottom,
                       SWP_NOZORDER);
        _vSplitter[CONT_TOP]->display();
    }

    if (_vContainer[CONT_RIGHT]->isVisible())
    {
        ::SetWindowPos(_vContainer[CONT_RIGHT]->getHSelf(), NULL, 
                       rcRight.left  ,
                       rcRight.top   ,
                       rcRight.right ,
                       rcRight.bottom,
                       SWP_NOZORDER);
        _vSplitter[CONT_RIGHT]->display();
    }

    if (_vContainer[CONT_LEFT]->isVisible())
    {
        ::SetWindowPos(_vContainer[CONT_LEFT]->getHSelf(), NULL, 
                       _dockData.rcRegion[CONT_LEFT].left  ,
                       _dockData.rcRegion[CONT_LEFT].top   ,
                       _dockData.rcRegion[CONT_LEFT].right ,
                       _dockData.rcRegion[CONT_LEFT].bottom,
                       SWP_NOZORDER);
        _vSplitter[CONT_LEFT]->display();
    }
    //此處的mainwindow是containtersplitter
    (*_ppMainWindow)->reSizeTo(_rcWork);
}

  這最後一句讓我醍醐灌頂,注意這裏是多態,因此調用的不是父類的resizeto(),而是:

    //將整個mainwindow移動到除了dockdialog所處的地方 此處爲總體移動 其內部的移動在splittercontainer的resize消息中
    void reSizeTo(RECT & rc) {
        _x = rc.left;
        _y = rc.top;
        ::MoveWindow(_hSelf, _x, _y, rc.right, rc.bottom, FALSE);
        _splitter.resizeSpliter();
    };
void Splitter::resizeSpliter(RECT *pRect)
{
    RECT rect;

    if (pRect)
        rect = *pRect;
    else
        ::GetClientRect(_hParent,&rect);
    
    if (_dwFlags & SV_HORIZONTAL)
    {
        // for a Horizontal spliter the width remains the same 
        // as the width of the parent window, so get the new width of the parent.
        _rect.right = rect.right;
        
        //if resizeing should be done proportionately.
        if (_dwFlags & SV_RESIZEWTHPERCNT)
            _rect.top  = ((rect.bottom * _splitPercent)/100);
        else // soit la fenetre en haut soit la fenetre en bas qui est fixee
            //神奇的出現了一句法語
            _rect.top = getSplitterFixPosY();
    }
    else
    {
        // for a (default) Vertical spliter the height remains the same 
        // as the height of the parent window, so get the new height of the parent.
        _rect.bottom = rect.bottom;
        
        //if resizeing should be done proportionately.
        if (_dwFlags & SV_RESIZEWTHPERCNT) 
        {
            _rect.left = ((rect.right * _splitPercent)/100);
        }
        else // soit la fenetre gauche soit la fenetre droit qui est fixee
            _rect.left = getSplitterFixPosX();
        
    }
    ::MoveWindow(_hSelf, _rect.left, _rect.top, _rect.right, _rect.bottom, TRUE);
    //傳遞splitter的左上角的點的左邊wparam爲left座標  lparam爲top座標
    ::SendMessage(_hParent, WM_RESIZE_CONTAINER, _rect.left, _rect.top);
    
    RECT rc;
    getClientRect(rc);    
    _clickZone2BR.right = getClickZone(WIDTH);
    _clickZone2BR.bottom = getClickZone(HEIGHT);
    _clickZone2BR.left = rc.right - _clickZone2BR.right;
    _clickZone2BR.top = rc.bottom - _clickZone2BR.bottom;


    //force the window to repaint, to make sure the splitter is visible
    // in the new position.
    redraw();
}

  有時候傳遞的消息反而是咱們容易忽略的,可是偏偏最重要,這裏的WM_RESIZE_CONTAINER消息以後,全部的主界面都將被調整好。通過這些深刻的探究也該回到_notepad_plus_plus_core的init()了,其實後面作的事情就是爲編輯框加載文件了,以後正常返回:

    //
    // Initialize the default foreground & background color
    //
    //不管有多少種風格 只要這種風格的id是style_default就OK
    StyleArray & globalStyles = (NppParameters::getInstance())->getGlobalStylers();
    int i = globalStyles.getStylerIndexByID(STYLE_DEFAULT);
    if (i != -1)
    {
        Style & style = globalStyles.getStyler(i);
        (NppParameters::getInstance())->setCurrentDefaultFgColor(style._fgColor);
        (NppParameters::getInstance())->setCurrentDefaultBgColor(style._bgColor);
    }

    //
    // launch the plugin dlg memorized at the last session
    //
    DockingManagerData &dmd = nppGUI._dockingData;

    _dockingManager.setDockedContSize(CONT_LEFT  , nppGUI._dockingData._leftWidth);
    _dockingManager.setDockedContSize(CONT_RIGHT , nppGUI._dockingData._rightWidth);
    _dockingManager.setDockedContSize(CONT_TOP     , nppGUI._dockingData._topHeight);
    _dockingManager.setDockedContSize(CONT_BOTTOM, nppGUI._dockingData._bottomHight);

    for (size_t i = 0, len = dmd._pluginDockInfo.size(); i < len ; ++i)
    {
        PluginDlgDockingInfo & pdi = dmd._pluginDockInfo[i];
        if (pdi._isVisible)
        {
            if (pdi._name == NPP_INTERNAL_FUCTION_STR)
            {
                _internalFuncIDs.push_back(pdi._internalID);
            }
            else
            {
                _pluginsManager.runPluginCommand(pdi._name.c_str(), pdi._internalID);
            }
        }
    }

    for (size_t i = 0, len = dmd._containerTabInfo.size(); i < len; ++i)
    {
        ContainerTabInfo & cti = dmd._containerTabInfo[i];
        _dockingManager.setActiveTab(cti._cont, cti._activeTab);
    }
    //Load initial docs into doctab
    loadBufferIntoView(_mainEditView.getCurrentBufferID(), MAIN_VIEW);
    loadBufferIntoView(_subEditView.getCurrentBufferID(), SUB_VIEW);
    activateBuffer(_mainEditView.getCurrentBufferID(), MAIN_VIEW);
    activateBuffer(_subEditView.getCurrentBufferID(), SUB_VIEW);
    //::SetFocus(_mainEditView.getHSelf());
    _mainEditView.getFocus();
    return TRUE;

  返回以後就到了最開始notepad_plus_plus.init()這裏,在CreateWindowEx代碼以後繼續執行,以後嘛該最小化就最小化,添加主題選擇器和語言選擇器等等,再以後一些細節加載完成就返回到了WinMain了,以後就構建了消息循環,開始處理以前在消息隊列中存放的消息,以後程序就跑起來了:

相關文章
相關標籤/搜索