This is probably massively overengineered, but there's a lot of boilerplate in here. Heavy use is made of the Template Method design pattern, hopefully meaning there's only a few bits you'll need to implement yourself. Those bits you do need to implement are actually virtual private methods. Most of them have default empty methods, so you don't have to implement a lot of empty functions.
You will require a compiler that supports C++ 20 or later to use the library.
In order to build the demo project, you will need the latest Windows SDK (10.0.22621.0 at the time of writing), as prior to that, the windows headers use non-standard extensions (or you can set Project => Properties => Configuration Properties => C/C++ => Language => Disable Language Extensions to no)
This includes the notepad++ template (from https://github.com/npp-plugins/plugintemplate) as a submodule so the include path is set up for that. Projects based off of this therefore don't need to include it, but should you have require it, there is a notepad++ directory which contains forwarding includes for all the headers.
You will need to add the path to the project in your includes setting.
It's worth using the "Forced Include File" setting in the C/C++ advanced settings pane of your project configuration, or you'll have to mark all the C++ source files in this module as 'not using precompiled header files'.
Take a copy of dllmain.cpp from the Template directory and modify it to use your header and class name (at around line 18).
Create your plugin by making a class which inherits from the Plugin class.
You will need to implement one static method, get_plugin_name and one pure virtual function, on_get_menu_entries (a pure virtual function required by the Plugin base class).
You may wish to implement a DllMain function, but that is not necessary for a notepad++ plugin, and is therefore outside the scope of this document.
The overall life story of your plugin is:
- Notepad++ calls
getName. This causes the main code to call yourget_plugin_namestatic function. This is used to display the name of your plugin in the plugins menu. It is unclear whether or not notepad++ copies this data but it's probably safest to return a static string. - Notepad++ calls the
setInfofunction, and the main code will construct an instance of your class, passing a reference to the suppliedNppDataobject. - Notepad++ calls the
getFuncsArrayfunction. The main code will then call theon_get_menu_entriesmethod of your class (see below) - Messages and notifications are sent to your plugin via the
on_messageandon_notificationmethods.- You should probably ignore the
NPPM_SHUTDOWNmessage. Among other things it is not safe to delete the class object until Notepad++ exits because it still accesses the memory allocated for the module name inTbDataafter it sends the message, and it makes more sense to clean up resources in your destructor.
- You should probably ignore the
- Notepad++ exits.
- The runtime library deletes the instance of your class, and so your destructor is called.
Notepad++ queries your plugin for its name by calling getName. dllmain.cpp transfers control to this function in your class:
static wchar_t const *get_plugin_name() noexcept
It's arguably unnecessary boilerplate, but it keeps all your plugin related code in one class and cuts down the amount of stuff you need to copy and modify.
Notepad++ queries your DLL for a list of function pointers. Using these to call into your class is moderately complicated, as notepad++ doesn't allow you to pass a parameter to call your supplied function pointer with. However, there is a way of doing this. It relies on some boiler plate code.
(If you're interested in how this works, see http://p-nand-q.com/programming/cplusplus/using_member_functions_with_c_function_pointers.html )
To aid in implementing this, 3 macros have been provided, DEFINE_PLUGIN_MENU_CALLBACKS, PLUGIN_MENU_MAKE_CALLBACK and PLUGIN_MENU_MAKE_SEPARATOR.
The first thing you need to do is to set up the context map.
DEFINE_PLUGIN_MENU_CALLBACKS(My_Plugin);You also need to define an enumeration in your class for the menu entries, and then you can implement the on_get_menu_entries method in your class like this:
std::vector<FuncItem> &My_Plugin::on_get_menu_entries()
{
static std::vector<FuncItem> res = {
PLUGIN_MENU_MAKE_CALLBACK(My_Plugin, Entry_0, L"Entry 0", callback_0),
PLUGIN_MENU_MAKE_CALLBACK(My_Plugin, Entry_1, L"Entry 1", callback_1),
//...
//If you want to add separators, do so like this...
PLUGIN_MENU_MAKE_SEPARATOR(My_Plugin, Entry_2),
//
PLUGIN_MENU_MAKE_CALLBACK(My_Plugin, Entry_Last, L"Entry the last", callback_last)
};
return res;
};You can also set the init2Check flag (which causes the menu item to get a check mark against it), and shortcut key as arguments to the macro. For most situations, you'll probably want to add your own macro because typing all those My_Plugin, s can get tedious.
Notes:
- Notepad++ expects at least one menu entry, and it will crash if you provide none.
- The returned vector must not be destroyed till notepad++ exits. hence the use of
static. You could achieve this in other ways of course.
This class provides a lot of boilerplate code and 3 virtual functions, the first of which you must implement. Default null implementations are supplied for the other two.
Note that these are public mainly so that dialogue classes can get hold of useful functionality.
-
Plugin(NppData const &, std::wstring_view name)This takes the pointer to the notepad++ data which your constructor is passed, and the name of your plugin. This should be the same as your
get_name()method returns. -
HINSTANCE module() const noexceptGets your module handle.
-
std::wstring get_name() constThis returns the name with which you constructed the plugin.
-
std::filesystem::path get_module_path() constThis returns the path to the module .dll.
-
HWND get_notepad_window() const noexceptGet hold of the notepad++ window handle. You probably won't need to use this.
-
LRESULT send_to_notepad(UINT message, WPARAM = 0, LPARAM = 0) const noexceptSend a message to notepad++
LRESULT send_to_notepad(UINT message, WPARAM wParam, void const *buff) const noexceptSame, but avoids messy reinterpret_casts round the windows API
-
std::filesystem::path get_config_dir() constGet the path to the notepad++ config directory.
-
std::filesystem::path get_plugin_config_dir() constGet a path to a directory with the name of your plugin in the notepad++ config_directory. The directory will be created if necessary.
-
std::filesystem::path get_document_path() constGet the current document path.
-
std::filesystem::path get_document_path(uptr_t buffer_id) constGet the path to the document in the specified buffer.
-
HWND get_scintilla_window() const noexceptGet the current scintilla window. You probably won't need to use this.
-
LRESULT send_to_editor(UINT message, WPARAM = 0, LPARAM = 0) const noexceptSend a message to the current editor window
LRESULT send_to_editor(UINT message, WPARAM wParam, void const *buff) const noexceptSame, but avoids messy reinterpret_casts round the windows API
-
std::string get_document_text() constGet the contents of the current document
-
std::string get_line_text(int line) constGet the contents of the specified line in the current document
-
virtual std::vector<FuncItem> &on_get_menu_entries() = 0See above
-
virtual LRESULT on_process_message(UINT message, WPARAM, LPARAM)Implement this to handle messages from notepad++ if required. Note I cannot find any examples of how to use these messages in notepad++.
-
virtual void on_process_notification(SCNotification const *)Implement this to handle notifications from scintilla if required.
-
int message_box(std::wstring const &message, UINT type) const noexceptThis is a wrapper round
::MessageBox, and throws up a message box using your plugin name as the title. -
template <typename Callbacks, typename Context, typename Class, typename Callback> FuncItem make_callback( int entry, wchar_t const *message, Callbacks &contexts, Context context, Class self, Callback callback, bool check = false, ShortcutKey const *key = nullptr )This is a utility function to aid setting up notepad++ menu definition. You are strongly advised to use the
PLUGIN_MENU_MAKE_CALLBACKmacro for this. -
template <typename Callbacks, typename Context, typename Class, typename Callback> FuncItem make_callback( int entry, std::wstring const &message, Callbacks &contexts, Context context, Class self, Callback callback, bool check = false, ShortcutKey const *key = nullptr )An overload of the above. Note that although it would be possible to use std::wstring_view to make these 2 back into one, wstring_views are not guaranteed null terminated, and I do not want to have to code round that.
-
template <typename Callbacks, typename Context, typename Class> FuncItem make_separator(int entry, Callbacks &contexts, Context context, Class self)Simplified wrapper around
make_callbackfor menu separators. You are strongly advised to use thePLUGIN_MENU_MAKE_SEPARATORmacro for this.
To be written
To be written
This repo contains base classes for docking dialogues (which can be docked to any side of the notepad++ window or be free floating), non-modal (or modeless) dialogues, and modal dialogues (the ones where you have to press OK or cancel before you can resume editing), which all share a certain amount of common code.
In order to implement your class, you subclass the appropriate _xxx_Dialogue_Interface_ class and implement the on_dialogue_message virtual method in order to process messages.
Important Note
Your on_dialogue_message method will get called with the WM_INITDIALOG message. However, be aware that your constructor may not have had all the members initialised at the point, so be very careful what you do in response.
This is a base class to all of the dialogue classes, and so all the protected methods can be used.
-
Dialogue_Interface(Plugin const *)Stashes a pointer to the plugin class and gets hold of the current module name.
-
virtual Message_Return on_dialogue_message(UINT message, WPARAM wParam, LPARAM lParam)Message_Returnis a typdef forstd::optional<INT_PTR>but it's astonishingly easy to get that wrong if you look at stackoverflow, hence the typedef.Return
std::nullopt(to returnFALSEto windows dialog processing), or a value to be set with::SetWindowLongPtr(in which caseTRUEwill be returned to windows). Note that some messages require you to returnFALSE(std::nullopt) even if you do handle them.message,wParamandlParamare the values passed to aDLGPROCfunction by windows,
Most of these are wrappers round windows functions (or macros) of the same or similar name.
-
Plugin const *plugin() const noexceptGet hold of plugin object for useful boilerplate
-
HWND window() const noexceptGet hold of the current dialogue window handle
-
void InvalidateRect(RECT const *rect = nullptr) const noexceptRequests a redraw by invalidating the specified rectangle or the whole dialogue area.
-
RECT getClientRect() const noexceptUtility to get the current client rectangle
-
RECT getWindowRect() const noexceptUtility to get the current window rectangle
-
HWND GetDlgItem(int, HWND = nullptr) const noexceptUtility to get a dialogue item. Normally the item would be from your own window, in which case there's no need to pass a window handle, but if you need to get hold of something from another window, you can pass the window handle in.
-
std::wstring get_window_text(int, HWND window = nullptr) constA wrapper round
GetWindowTextwhich gets the text of the specified item in the window. Normally the item would be from your own window, in which case there's no need to pass a window handle, but if you need to get hold of something from another window, you can pass the window handle in. -
void SetFocus(int) constSets the focus to the given item.
-
int message_box(std::wstring const &message, UINT type) const noexcept;This is a wrapper round
::MessageBox, and throws up a message box using your dialogue name as the title. -
void add_item_callback(int item, Item_Callback_Function callback_func)This allows you to "subclass" (the phrase windows uses) a window element, to intercept events on it.
Item_Callback_Functionis defined as:typedef std::optional<LRESULT> Item_Callback_Return;typedef std::function<Item_Callback_Return(HWND, UINT, WPARAM, LPARAM)> Item_Callback_FunctionThis behaves in much the same way as
on_dialogue_message, in that you returnstd::nulloptif you've not handled the callback. Note that becauseItem_Callback_Functionis a std::function, if you're using a class instance method, you will need to wrap it with std::bind and 4 placeholders...
When you want to create a non-modal dialogue, you should create a subclass of the Non_Modal_Dialogue_Interface class. This contains the necessary calls to notepad++ to ensure it knows to send your dialogue keystrokes and such.
In your constructor, you must
-
Call the base class constructor with the ID of the dialogue (i.e. the ID from
resource.h), and a pointer to your main plugin instance. Your dialogue will be created as part of this, and the base class will retain the window handle. -
Do any window setup required (see the important note above about
WM_INITDIALOGand do the setup in the constructor, not in theon_dialog_messagecallback) -
Your class will start receiving messages to process via
on_dialogue_message(if you have implemented it).
-
Non_Modal_Dialogue_Interface(int dialogue_id, Plugin const *plugin, HWND parent = nullptr)Constructor for the class. The dialogue ID is the appropriate identifier from
resource.h. You can optionally pass the handle to a parent window.
When you want to create a docking dialogue, you should create a subclass of the Docking_Dialogue_Interface class. A docking dialogue is pretty much a modeless dialogue, except it is able to be docked in the main notepad++ window (in the same way as e.g. the results of the find dialogue). Note that the parent window is always the notepad++ window so there's no parameter for that.
In your constructor, you must
-
Call the base class constructor with the ID of the dialogue (i.e. the ID from resource.h), and a pointer to your main plugin instance. Your dialogue will be created as part of this, and the
Docking_Dialogue_Interfaceclass will retain the window handle. -
Do any window setup required (see the important note above about
WM_INITDIALOGand do the setup in the constructor, not in theon_dialog_messagecallback) -
Call
register_dialoguein order to tell notepad++ that your new dialogue is ready to be used. -
Your class will start receiving messages to process via
on_dialogue_message(if you have implemented it).
Important Note:
- The actual dialogue you create needs to have a caption bar with a title if you want Notepad++ to save the state between sessions.
-
Docking_Dialogue_Interface(int dialogue_id, Plugin const *plugin)Constructor for the class. The dialogue ID is the appropriate identifier from
resource.h. -
void display() noexceptCall this to display the dialogue. If you have special actions to be take on display, implement
void on_display() noexcept override. -
void hide() noexceptCall this to hide the dialogue. If you have special actions to be take on hiding, implement
void on_hide() noexcept override. -
bool is_hidden() const noexceptReturns
trueif the dialogue is currently hidden.
-
virtual void on_display() noexceptThis is called whenever the dialogue is about to be displayed.
-
virtual void on_hide() noexceptThis is called whenever the dialogue is about to be hidden.
-
void register_dialogue(int menu_index, Position position, HICON icon = nullptr, wchar_t const *extra = nullptr) noexceptmenu_indexis the menu entry number which causes this dialogue to be displayed. I strongly recommend you use anenumhere so you can tie it up with the entries in theFuncItemtable. Note that Notepad++ appears to use this value when saving window states and you can get some quite unexpected results if the numbers don't match (or change)!positiondefines where the dialogue will be placed the first time it is displayed by Notepad++. On subsequent runs, this value will be ignored. It may be one ofDock_Left, Dock_Right, Dock_Top, Dock_Bottom, Floatingicon, if supplied, must be an icon which you have loaded. It will be displayed on the tab bar in the docking dialogue window.extrais extra text to display in the title bar if required.
A modal dialogue doesn't return until you have clicked the OK or cancel button (or the close button at the top right). This means that when you create the dialogue, any subsequent code will not be executed, so your constructor needs to be implemented a little differently to that of a docking dialogue.
You should do all the work you can do before calling the create_dialogue_window method. Any further work needs to be done in the on_dialogue_message callback function.
The class provides default handlers for 'OK', 'Cancel' and 'Close' buttons. These return Clicked_OK, Clicked_Cancel and Clicked_Close to the API (which are NOT 0 or -1 - see below). Of course, you do not have to use the default handler, and may handle the events yourself in the on_dialogue_message function.
-
Modal_Dialogue_Interface(Plugin const *)The constructor. This just takes a pointer to the plugin class. It doesn't take a dialogue id, you need to pass that into
create_modal_dialogue. -
INT_PTR get_result() const noexcept;This returns the result passed to the
EndDialogmethod. Note Avoid returning 0 or -1 viaEndDialogas they are indistinguishable from the values returned when the windowsDialogBoxfunction gets an error.
-
void create_modal_dialogue(int dialogID) noexcept;Create the dialogue - this function will not return until the user has closed the dialogue, at which point the result may be checked by calling
get_result() -
BOOL EndDialog(INT_PTR retval) const noexcept;
This is a wrapper round ::EndDialog. Try not to call it with 0 or -1 - see above. Be warned that calling this with 0 will give you a compiler error, because C++ can't tell the difference between 0 and nullptr. Use 0LL if you must return 0 (but see above on that approach).
-
BOOL EndDialog(void *retval) const noexcept;Wrapper round
::EndDialogthat avoids reinterpret_cast. However, it is probably better to returnClicked_OKand provide another method in your class to return complex data. -
BOOL centre_dialogue() const noexcept;Centre the dialogue on the Notepad++ window