Notepad++源码编译及其分析

  Notepad++是一个小巧精悍的编辑器,其使用方法我就不多说了,由于notepad++是使用c++封装的windows句柄以及api来实现的,因此对于其源码的研究有助于学习如何封装自己简单的库(当然不是MTL、MFC或者QT那样大型的库)。Notepad++源码:https://github.com/notepad-plus-plus/notepad-plus-plus/releases/tag/v6.7.9.2

  下面是Notepad++源码的目录:

       

  其主目录如第一张图所示,包含了两个开源工程,第一个PowerEditor就是notepad++;第二scintilla是一个代码编辑器的开源库,十分强大,许多编辑器都是基于这个库封装起来的,对于scintilla来说可以实现代码折叠、标记、缩进等等很多功能,具体情况可以去scintilla的官网以及一些博客来了解如何使用这个开源库:

http://www.scintilla.org/

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行代码,加上注释也比较少,实在花了我好几天才摸清楚大概情况。

  从界面开始说起,整个工程中的窗口都是继承自Window这个类的,这个类封装最重要的一个成员就是HWND _hSelf,这个就是用来存放CreateWindow函数返回的窗口句柄,其次就是父窗口句柄HWND _hParent,以及实例HINSTANCE _hInst。还提供了许多窗口都能够用到的方法,都是以虚函数的方法来提供的,比如display函数用来显示窗口,reSizeTo用来调整窗口,redraw用来重绘窗口等等许多函数。有了Window类,后面的就容易理解一些了,其中整个NotePad++的主窗口类Notepad_plus_Window继承自Window,notepad++中所有的对话框都是继承自StaticDialog的,而StaticDialog也是继承自Window这个父类的。下面是Window类的源码:

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源码:

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对象中,这里有些属性马上就用到,有些属性都是很久之后才用到的。

  因为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了,之后就构建了消息循环,开始处理之前在消息队列中存放的消息,之后程序就跑起来了:

 

 

 

 

 

 

 

 

 

 

 

 

 

  

posted @ 2015-07-05 23:04 银杏花下 阅读(...) 评论(...) 编辑 收藏