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

相关文章
相关标签/搜索