From bb3e5403d2473a4b5c188782edfcf108cf4a18a6 Mon Sep 17 00:00:00 2001 From: Tanner Date: Thu, 1 Jun 2023 10:19:53 -0700 Subject: [PATCH 01/12] REVERT ME BEFORE MERGE - retarget for VS 2022 --- ColumnMode/ColumnMode.vcxproj | 10 +- SamplePlugin/SamplePlugin.vcxproj | 354 +++++++++++++++--------------- 2 files changed, 182 insertions(+), 182 deletions(-) diff --git a/ColumnMode/ColumnMode.vcxproj b/ColumnMode/ColumnMode.vcxproj index 4f3131e..8f2286b 100644 --- a/ColumnMode/ColumnMode.vcxproj +++ b/ColumnMode/ColumnMode.vcxproj @@ -23,33 +23,33 @@ {6546B3A0-08BA-43DD-A450-B2DE1262820A} Win32Proj ColumnMode - 10.0.17134.0 + 10.0 ColumnMode Application true - v141 + v143 Unicode Application false - v141 + v143 true Unicode Application true - v141 + v143 Unicode Application false - v141 + v143 true Unicode diff --git a/SamplePlugin/SamplePlugin.vcxproj b/SamplePlugin/SamplePlugin.vcxproj index 7c332dd..65fb020 100644 --- a/SamplePlugin/SamplePlugin.vcxproj +++ b/SamplePlugin/SamplePlugin.vcxproj @@ -1,178 +1,178 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 16.0 - Win32Proj - {c544b2fb-d327-4011-87b4-f184470bc716} - SamplePlugin - 10.0 - - - - DynamicLibrary - true - v142 - Unicode - - - DynamicLibrary - false - v142 - true - Unicode - - - DynamicLibrary - true - v142 - Unicode - - - DynamicLibrary - false - v142 - true - Unicode - - - - - - - - - - - - - - - - - - - - - true - - - false - - - true - - - false - - - - Level3 - true - WIN32;_DEBUG;SAMPLEPLUGIN_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - true - Use - pch.h - - - Windows - true - false - SamplePlugin.def - - - - - Level3 - true - true - true - WIN32;NDEBUG;SAMPLEPLUGIN_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - true - Use - pch.h - - - Windows - true - true - true - false - SamplePlugin.def - - - - - Level3 - true - _DEBUG;SAMPLEPLUGIN_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - true - Use - pch.h - - - Windows - true - false - SamplePlugin.def - - - - - Level3 - true - true - true - NDEBUG;SAMPLEPLUGIN_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - true - Use - pch.h - - - Windows - true - true - true - false - SamplePlugin.def - - - - - - - - - - - - Create - Create - Create - Create - - - - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {c544b2fb-d327-4011-87b4-f184470bc716} + SamplePlugin + 10.0 + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + false + + + true + + + false + + + + Level3 + true + WIN32;_DEBUG;SAMPLEPLUGIN_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + false + SamplePlugin.def + + + + + Level3 + true + true + true + WIN32;NDEBUG;SAMPLEPLUGIN_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + true + true + false + SamplePlugin.def + + + + + Level3 + true + _DEBUG;SAMPLEPLUGIN_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + false + SamplePlugin.def + + + + + Level3 + true + true + true + NDEBUG;SAMPLEPLUGIN_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + true + true + false + SamplePlugin.def + + + + + + + + + + + + Create + Create + Create + Create + + + + + + + + \ No newline at end of file From 1c995e35ccc7c24c2f1242a36d547ed632dead2d Mon Sep 17 00:00:00 2001 From: Tanner Date: Thu, 1 Jun 2023 10:27:35 -0700 Subject: [PATCH 02/12] Language standard was different between Debug and Release. Set to C++ 17 --- ColumnMode/ColumnMode.vcxproj | 1 + 1 file changed, 1 insertion(+) diff --git a/ColumnMode/ColumnMode.vcxproj b/ColumnMode/ColumnMode.vcxproj index 8f2286b..cb15e28 100644 --- a/ColumnMode/ColumnMode.vcxproj +++ b/ColumnMode/ColumnMode.vcxproj @@ -143,6 +143,7 @@ true NDEBUG;_WINDOWS;%(PreprocessorDefinitions) true + stdcpp17 Windows From 87df7ba67d21430b49f1876bc7919dcebac4dd4f Mon Sep 17 00:00:00 2001 From: Tanner Date: Thu, 1 Jun 2023 10:30:15 -0700 Subject: [PATCH 03/12] fix misc build warnings --- ColumnMode/Main.cpp | 2 +- ColumnMode/PluginManager.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ColumnMode/Main.cpp b/ColumnMode/Main.cpp index f1db5c4..198fd40 100644 --- a/ColumnMode/Main.cpp +++ b/ColumnMode/Main.cpp @@ -114,7 +114,7 @@ void MyRegisterClass(HINSTANCE hInstance) } { // Document window - WNDCLASSEXW wcex; + WNDCLASSEXW wcex {}; wcex.cbSize = sizeof(WNDCLASSEX); diff --git a/ColumnMode/PluginManager.cpp b/ColumnMode/PluginManager.cpp index 61daaff..6d46b75 100644 --- a/ColumnMode/PluginManager.cpp +++ b/ColumnMode/PluginManager.cpp @@ -77,7 +77,7 @@ HRESULT ColumnMode::PluginManager::LoadPlugin(LPCWSTR pluginName) reinterpret_cast(GetProcAddress(pluginModule, "OpenColumnModePlugin")); PluginFunctions pluginFuncs = { 0 }; - OpenPluginArgs args; + OpenPluginArgs args{}; args.apiVersion = c_ColumnModePluginApiVersion; args.hPlugin = NULL; args.pPluginFuncs = &pluginFuncs; From 191e0297055a17df788629551f90eec2522fa304 Mon Sep 17 00:00:00 2001 From: Tanner Date: Mon, 5 Jun 2023 11:15:56 -0700 Subject: [PATCH 04/12] Add separator between TextModes and Theme selection --- ColumnMode/ColumnMode.rc | 533 ++++++++++++++++++++------------------- 1 file changed, 267 insertions(+), 266 deletions(-) diff --git a/ColumnMode/ColumnMode.rc b/ColumnMode/ColumnMode.rc index 1e1674e..1ad6447 100644 --- a/ColumnMode/ColumnMode.rc +++ b/ColumnMode/ColumnMode.rc @@ -1,266 +1,267 @@ -// Microsoft Visual C++ generated resource script. -// -#include "resource.h" - -#define APSTUDIO_READONLY_SYMBOLS -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 2 resource. -// -#ifndef APSTUDIO_INVOKED -#include "targetver.h" -#endif -#define APSTUDIO_HIDDEN_SYMBOLS -#include "windows.h" -#undef APSTUDIO_HIDDEN_SYMBOLS - -///////////////////////////////////////////////////////////////////////////// -#undef APSTUDIO_READONLY_SYMBOLS - -///////////////////////////////////////////////////////////////////////////// -// English (United States) resources - -#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) -LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US -#pragma code_page(1252) - -///////////////////////////////////////////////////////////////////////////// -// -// Icon -// - -// Icon with lowest ID value placed first to ensure application icon -// remains consistent on all systems. -IDI_COLUMNMODE ICON "ColumnMode.ico" - -IDI_SMALL ICON "small.ico" - - -///////////////////////////////////////////////////////////////////////////// -// -// Menu -// - -IDC_COLUMNMODE MENU -BEGIN - POPUP "&File" - BEGIN - MENUITEM "New\tCtrl+N", ID_FILE_NEW - MENUITEM "Open\tCtrl+O", ID_FILE_OPEN - MENUITEM "Save\tCtrl+S", ID_FILE_SAVE - MENUITEM "Save As\tCtrl+Shift+S", ID_FILE_SAVEAS - MENUITEM "Refresh\tF5", ID_FILE_REFRESH - MENUITEM "Properties...", ID_FILE_PROPERTIES - MENUITEM SEPARATOR - MENUITEM "Print...\tCtrl+P", ID_FILE_PRINT - MENUITEM SEPARATOR - MENUITEM "E&xit", IDM_EXIT - END - POPUP "Edit" - BEGIN - MENUITEM "Undo\tCtrl+Z", ID_EDIT_UNDO, INACTIVE - MENUITEM SEPARATOR - MENUITEM "Cut\tCtrl+X", ID_EDIT_CUT - MENUITEM "Copy\tCtrl+C", ID_EDIT_COPY - MENUITEM "Paste\tCtrl+V", ID_EDIT_PASTE - MENUITEM "Delete\tDel", ID_EDIT_DELETE - MENUITEM "Find\tCtrl+F", ID_EDIT_FIND - END - POPUP "Options" - BEGIN - MENUITEM "Diagram Mode", ID_OPTIONS_DIAGRAMMODE, CHECKED - MENUITEM "Text Mode", ID_OPTIONS_TEXTMODE - POPUP "Themes" - BEGIN - MENUITEM "Create New Theme", ID_THEMES_CREATENEWTHEME - MENUITEM "Rescan", ID_THEMES_RESCAN - MENUITEM SEPARATOR - END - END - POPUP "Plugins" - BEGIN - MENUITEM "Rescan", ID_PLUGINS_RESCAN - MENUITEM SEPARATOR - END - POPUP "&Help" - BEGIN - MENUITEM "&About ...", IDM_ABOUT - END -END - - -///////////////////////////////////////////////////////////////////////////// -// -// Accelerator -// - -IDC_COLUMNMODE ACCELERATORS -BEGIN - "C", ID_EDIT_COPY, VIRTKEY, CONTROL, NOINVERT - "X", ID_EDIT_CUT, VIRTKEY, CONTROL, NOINVERT - VK_DELETE, ID_EDIT_DELETE, VIRTKEY, CONTROL, NOINVERT - "F", ID_EDIT_FIND, VIRTKEY, CONTROL, NOINVERT - "V", ID_EDIT_PASTE, VIRTKEY, CONTROL, NOINVERT - "Z", ID_EDIT_UNDO, VIRTKEY, CONTROL, NOINVERT - "N", ID_FILE_NEW, VIRTKEY, CONTROL, NOINVERT - "O", ID_FILE_OPEN, VIRTKEY, CONTROL, NOINVERT - "P", ID_FILE_PRINT, VIRTKEY, CONTROL, NOINVERT - VK_F5, ID_FILE_REFRESH, VIRTKEY, NOINVERT - "S", ID_FILE_SAVE, VIRTKEY, CONTROL, NOINVERT - "S", ID_FILE_SAVEAS, VIRTKEY, SHIFT, CONTROL, NOINVERT - "/", IDM_ABOUT, ASCII, ALT, NOINVERT -END - - -///////////////////////////////////////////////////////////////////////////// -// -// Dialog -// - -IDD_ABOUTBOX DIALOGEX 0, 0, 170, 62 -STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "About ColumnMode" -FONT 8, "MS Shell Dlg", 0, 0, 0x1 -BEGIN - ICON IDR_MAINFRAME,IDC_STATIC,14,14,21,20 - LTEXT "ColumnMode, Version 1.0",IDC_STATIC,42,14,114,8,SS_NOPREFIX - LTEXT "Copyright (C) 2019",IDC_STATIC,42,26,114,8 - DEFPUSHBUTTON "OK",IDOK,113,41,50,14,WS_GROUP -END - -IDD_DOCUMENTPROPERTIES DIALOGEX 0, 0, 179, 78 -STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "Document Properties" -FONT 8, "MS Shell Dlg", 400, 0, 0x1 -BEGIN - DEFPUSHBUTTON "OK",IDOK,64,56,50,14 - PUSHBUTTON "Cancel",IDCANCEL,122,56,50,14 - LTEXT "Line Count:",IDC_STATIC,7,9,38,8 - EDITTEXT IDC_LINECOUNT_EDITBOX,66,7,55,14,ES_AUTOHSCROLL - LTEXT "Column Count:",IDC_STATIC,7,30,48,8 - EDITTEXT IDC_COLUMNCOUNT_EDITBOX,66,27,55,14,ES_AUTOHSCROLL -END - -IDD_FIND_DIALOG DIALOGEX 0, 0, 179, 53 -STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "Find" -FONT 8, "MS Shell Dlg", 400, 0, 0x1 -BEGIN - PUSHBUTTON "Next",ID_NEXT,64,28,50,14 - PUSHBUTTON "Previous",ID_PREVIOUS,122,28,50,14 - LTEXT "Search Text:",-1,7,9,45,8 - EDITTEXT IDC_FIND_EDITBOX,66,7,106,14,ES_AUTOHSCROLL -END - -IDD_THEMENAMEQUERY DIALOGEX 0, 0, 179, 53 -STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "Create New Theme" -FONT 8, "MS Shell Dlg", 400, 0, 0x1 -BEGIN - PUSHBUTTON "OK",IDOK,64,28,50,14 - PUSHBUTTON "CANCEL",IDCANCEL,122,28,50,14 - LTEXT "Theme Name:",-1,7,9,45,8 - EDITTEXT IDC_THEMENAME_EDITBOX,66,7,106,14,ES_AUTOHSCROLL -END - - -///////////////////////////////////////////////////////////////////////////// -// -// DESIGNINFO -// - -#ifdef APSTUDIO_INVOKED -GUIDELINES DESIGNINFO -BEGIN - IDD_ABOUTBOX, DIALOG - BEGIN - LEFTMARGIN, 7 - RIGHTMARGIN, 163 - TOPMARGIN, 7 - BOTTOMMARGIN, 55 - END - - IDD_DOCUMENTPROPERTIES, DIALOG - BEGIN - LEFTMARGIN, 7 - RIGHTMARGIN, 172 - TOPMARGIN, 7 - BOTTOMMARGIN, 71 - END - - IDD_FIND_DIALOG, DIALOG - BEGIN - LEFTMARGIN, 7 - RIGHTMARGIN, 172 - TOPMARGIN, 7 - BOTTOMMARGIN, 46 - END - - IDD_THEMENAMEQUERY, DIALOG - BEGIN - LEFTMARGIN, 7 - RIGHTMARGIN, 172 - TOPMARGIN, 7 - BOTTOMMARGIN, 46 - END -END -#endif // APSTUDIO_INVOKED - - -#ifdef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// TEXTINCLUDE -// - -1 TEXTINCLUDE -BEGIN - "resource.h\0" -END - -2 TEXTINCLUDE -BEGIN - "#ifndef APSTUDIO_INVOKED\r\n" - "#include ""targetver.h""\r\n" - "#endif\r\n" - "#define APSTUDIO_HIDDEN_SYMBOLS\r\n" - "#include ""windows.h""\r\n" - "#undef APSTUDIO_HIDDEN_SYMBOLS\r\n" - "\0" -END - -3 TEXTINCLUDE -BEGIN - "\r\n" - "\0" -END - -#endif // APSTUDIO_INVOKED - - -///////////////////////////////////////////////////////////////////////////// -// -// String Table -// - -STRINGTABLE -BEGIN - IDS_APP_TITLE "ColumnMode" - IDC_COLUMNMODE "COLUMNMODE" -END - -#endif // English (United States) resources -///////////////////////////////////////////////////////////////////////////// - - - -#ifndef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 3 resource. -// - - -///////////////////////////////////////////////////////////////////////////// -#endif // not APSTUDIO_INVOKED - +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#ifndef APSTUDIO_INVOKED +#include "targetver.h" +#endif +#define APSTUDIO_HIDDEN_SYMBOLS +#include "windows.h" +#undef APSTUDIO_HIDDEN_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_COLUMNMODE ICON "ColumnMode.ico" + +IDI_SMALL ICON "small.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Menu +// + +IDC_COLUMNMODE MENU +BEGIN + POPUP "&File" + BEGIN + MENUITEM "New\tCtrl+N", ID_FILE_NEW + MENUITEM "Open\tCtrl+O", ID_FILE_OPEN + MENUITEM "Save\tCtrl+S", ID_FILE_SAVE + MENUITEM "Save As\tCtrl+Shift+S", ID_FILE_SAVEAS + MENUITEM "Refresh\tF5", ID_FILE_REFRESH + MENUITEM "Properties...", ID_FILE_PROPERTIES + MENUITEM SEPARATOR + MENUITEM "Print...\tCtrl+P", ID_FILE_PRINT + MENUITEM SEPARATOR + MENUITEM "E&xit", IDM_EXIT + END + POPUP "Edit" + BEGIN + MENUITEM "Undo\tCtrl+Z", ID_EDIT_UNDO, INACTIVE + MENUITEM SEPARATOR + MENUITEM "Cut\tCtrl+X", ID_EDIT_CUT + MENUITEM "Copy\tCtrl+C", ID_EDIT_COPY + MENUITEM "Paste\tCtrl+V", ID_EDIT_PASTE + MENUITEM "Delete\tDel", ID_EDIT_DELETE + MENUITEM "Find\tCtrl+F", ID_EDIT_FIND + END + POPUP "Options" + BEGIN + MENUITEM "Diagram Mode", ID_OPTIONS_DIAGRAMMODE, CHECKED + MENUITEM "Text Mode", ID_OPTIONS_TEXTMODE + MENUITEM SEPARATOR + POPUP "Themes" + BEGIN + MENUITEM "Create New Theme", ID_THEMES_CREATENEWTHEME + MENUITEM "Rescan", ID_THEMES_RESCAN + MENUITEM SEPARATOR + END + END + POPUP "Plugins" + BEGIN + MENUITEM "Rescan", ID_PLUGINS_RESCAN + MENUITEM SEPARATOR + END + POPUP "&Help" + BEGIN + MENUITEM "&About ...", IDM_ABOUT + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Accelerator +// + +IDC_COLUMNMODE ACCELERATORS +BEGIN + "C", ID_EDIT_COPY, VIRTKEY, CONTROL, NOINVERT + "X", ID_EDIT_CUT, VIRTKEY, CONTROL, NOINVERT + VK_DELETE, ID_EDIT_DELETE, VIRTKEY, CONTROL, NOINVERT + "F", ID_EDIT_FIND, VIRTKEY, CONTROL, NOINVERT + "V", ID_EDIT_PASTE, VIRTKEY, CONTROL, NOINVERT + "Z", ID_EDIT_UNDO, VIRTKEY, CONTROL, NOINVERT + "N", ID_FILE_NEW, VIRTKEY, CONTROL, NOINVERT + "O", ID_FILE_OPEN, VIRTKEY, CONTROL, NOINVERT + "P", ID_FILE_PRINT, VIRTKEY, CONTROL, NOINVERT + VK_F5, ID_FILE_REFRESH, VIRTKEY, NOINVERT + "S", ID_FILE_SAVE, VIRTKEY, CONTROL, NOINVERT + "S", ID_FILE_SAVEAS, VIRTKEY, SHIFT, CONTROL, NOINVERT + "/", IDM_ABOUT, ASCII, ALT, NOINVERT +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_ABOUTBOX DIALOGEX 0, 0, 170, 62 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "About ColumnMode" +FONT 8, "MS Shell Dlg", 0, 0, 0x1 +BEGIN + ICON IDR_MAINFRAME,IDC_STATIC,14,14,21,20 + LTEXT "ColumnMode, Version 1.0",IDC_STATIC,42,14,114,8,SS_NOPREFIX + LTEXT "Copyright (C) 2019",IDC_STATIC,42,26,114,8 + DEFPUSHBUTTON "OK",IDOK,113,41,50,14,WS_GROUP +END + +IDD_DOCUMENTPROPERTIES DIALOGEX 0, 0, 179, 78 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Document Properties" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + DEFPUSHBUTTON "OK",IDOK,64,56,50,14 + PUSHBUTTON "Cancel",IDCANCEL,122,56,50,14 + LTEXT "Line Count:",IDC_STATIC,7,9,38,8 + EDITTEXT IDC_LINECOUNT_EDITBOX,66,7,55,14,ES_AUTOHSCROLL + LTEXT "Column Count:",IDC_STATIC,7,30,48,8 + EDITTEXT IDC_COLUMNCOUNT_EDITBOX,66,27,55,14,ES_AUTOHSCROLL +END + +IDD_FIND_DIALOG DIALOGEX 0, 0, 179, 53 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Find" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + PUSHBUTTON "Next",ID_NEXT,64,28,50,14 + PUSHBUTTON "Previous",ID_PREVIOUS,122,28,50,14 + LTEXT "Search Text:",-1,7,9,45,8 + EDITTEXT IDC_FIND_EDITBOX,66,7,106,14,ES_AUTOHSCROLL +END + +IDD_THEMENAMEQUERY DIALOGEX 0, 0, 179, 53 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Create New Theme" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + PUSHBUTTON "OK",IDOK,64,28,50,14 + PUSHBUTTON "CANCEL",IDCANCEL,122,28,50,14 + LTEXT "Theme Name:",-1,7,9,45,8 + EDITTEXT IDC_THEMENAME_EDITBOX,66,7,106,14,ES_AUTOHSCROLL +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_ABOUTBOX, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 163 + TOPMARGIN, 7 + BOTTOMMARGIN, 55 + END + + IDD_DOCUMENTPROPERTIES, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 172 + TOPMARGIN, 7 + BOTTOMMARGIN, 71 + END + + IDD_FIND_DIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 172 + TOPMARGIN, 7 + BOTTOMMARGIN, 46 + END + + IDD_THEMENAMEQUERY, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 172 + TOPMARGIN, 7 + BOTTOMMARGIN, 46 + END +END +#endif // APSTUDIO_INVOKED + + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#ifndef APSTUDIO_INVOKED\r\n" + "#include ""targetver.h""\r\n" + "#endif\r\n" + "#define APSTUDIO_HIDDEN_SYMBOLS\r\n" + "#include ""windows.h""\r\n" + "#undef APSTUDIO_HIDDEN_SYMBOLS\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_APP_TITLE "ColumnMode" + IDC_COLUMNMODE "COLUMNMODE" +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + From 6445bdcc2685eb7d25c10350b1c3c177ab78fe31 Mon Sep 17 00:00:00 2001 From: Tanner Date: Mon, 5 Jun 2023 12:21:08 -0700 Subject: [PATCH 05/12] Add a manual page for Themes --- Images/themes_UI_annotation.png | Bin 0 -> 36631 bytes Manual/Themes.md | 29 +++++++++++++++++++++++++++++ README.md | 2 ++ 3 files changed, 31 insertions(+) create mode 100644 Images/themes_UI_annotation.png create mode 100644 Manual/Themes.md diff --git a/Images/themes_UI_annotation.png b/Images/themes_UI_annotation.png new file mode 100644 index 0000000000000000000000000000000000000000..f496aacc4e1b1eac4f910aa607d560fecb24ac31 GIT binary patch literal 36631 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 literal 0 HcmV?d00001 diff --git a/Manual/Themes.md b/Manual/Themes.md new file mode 100644 index 0000000..7f5b0a8 --- /dev/null +++ b/Manual/Themes.md @@ -0,0 +1,29 @@ +# Themes +Themes allow you to style ColumnMode's color pallet to suit your taste / usage. +You can find the Themes installed on your system at `%APPDATA%/ColumnMode/Themes` (extension is `.cmt`). +`.cmt` files are a JSON format pairing colors to theme Ids. +Opening a `.cmt` file in ColumnMode will allow you to preview your changes in realtime (though some fields are not used by the editor when viewing `.cmt` files. Eventually this may be addressed by a more full-featured theme editor). + +You can create a new Theme by going to Options > Themes > Create New Theme. +Your new theme will be based on the default theme: ColumnModeClassic. + +## Theme Ids +You can think of a theme as a pallet of colors that can be picked from by the Column Mode editor for different uses. + +### `UI_`* Theme Ids +![Annotated UI elements](../Images/themes_UI_annotation.png) + +1. `UI_BACKGROUND`: The empty region of the window that can't be edited. +1. `UI_PAPER`: The region available for editing. +1. `UI_PAPER_BORDER` +1. `UI_MARGIN` +1. `UI_CURRENT_LINE_HIGHLIGHT`: A color that should be different from `UI_PAPER` to indicate the line bing edited. +1. `UI_CARET`: The flashing indicator for the character you are about to edit. +1. `UI_DRAG_RECT`: The solid line that shows the exact selection rectangle you are dragging out. +1. `UI_SELECTION`: The shaded region of your selection. +1. `UI_SELECTION_BORDER`: The "marching ants" on the ourside of the effective selection rectangle. + +### `TEXT_`* Theme Ids +Most of these aren't used yet. +For now just edit `TEXT_DEFAULT` to set the color of the text. +Other `TEXT_`* colors will be used in the future when plugins are able to handle text colorization. \ No newline at end of file diff --git a/README.md b/README.md index ee2fd2a..7dd36ea 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ ColumnMode is a lightweight, column-based text editor. * Supports copying and pasting * Supports undo * Supports print +* Supports extension via plugins +* Supports [Themes](Manual/Themes.md) to change the look and feel ![Example image](https://raw.githubusercontent.com/clandrew/ColumnMode/master/Images/CutPaste.gif "Example image") ![Example image](https://raw.githubusercontent.com/clandrew/ColumnMode/master/Images/Undo.gif "Example image") From cc77914e46db4177f776f6d791ce8cfabc949e05 Mon Sep 17 00:00:00 2001 From: Tanner Date: Mon, 5 Jun 2023 14:09:37 -0700 Subject: [PATCH 06/12] Add ability to set a persistent warning message on the status bar. --- ColumnMode/Program.cpp | 33 +++++++++++++++++++++++++++++++++ ColumnMode/Program.h | 4 +++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/ColumnMode/Program.cpp b/ColumnMode/Program.cpp index ff17fa4..e539016 100644 --- a/ColumnMode/Program.cpp +++ b/ColumnMode/Program.cpp @@ -1,4 +1,6 @@ #include "stdafx.h" +#include + #include "Program.h" #include "Resource.h" #include "Verify.h" @@ -110,6 +112,7 @@ class Status int m_caretRow; int m_caretColumn; Mode m_mode; + std::optional m_warning; void RefreshStatusBar(HWND statusBarLabelHwnd) { @@ -118,6 +121,11 @@ class Status label << L"Row: " << (m_caretRow + 1) << " Col: " << (m_caretColumn + 1); label << L" Mode: " << (m_mode == Mode::TextMode ? L"Text" : L"Diagram"); + if (m_warning.has_value()) + { + label << L" WARNING: " << m_warning.value(); + } + Static_SetText(statusBarLabelHwnd, label.str().c_str()); } @@ -147,6 +155,21 @@ class Status return m_mode; } + void SetWarning(std::wstring str, HWND statusBarLabelHwnd) + { + m_warning = str; + RefreshStatusBar(statusBarLabelHwnd); + } + + void ClearWarning(std::wstring str, HWND statusBarLabelHwnd) + { + if (m_warning.has_value() && m_warning.value() == str) + { + m_warning.reset(); + } + RefreshStatusBar(statusBarLabelHwnd); + } + } g_status; bool g_isDragging; @@ -3306,4 +3329,14 @@ std::wstring& GetAllText() ColumnMode::FindTool& GetFindTool() { return g_findTool; +} + +void SetWarningMessage(std::wstring str) +{ + g_status.SetWarning(str, g_windowManager.GetWindowHandles().StatusBarLabel); +} + +void ClearWarningMssage(std::wstring str) +{ + g_status.ClearWarning(str, g_windowManager.GetWindowHandles().StatusBarLabel); } \ No newline at end of file diff --git a/ColumnMode/Program.h b/ColumnMode/Program.h index 3c6ab50..09f60a0 100644 --- a/ColumnMode/Program.h +++ b/ColumnMode/Program.h @@ -83,4 +83,6 @@ enum class ScrollToStyle }; void ScrollTo(UINT index, ScrollToStyle scrollStyle = ScrollToStyle::CENTER); -ColumnMode::FindTool& GetFindTool(); \ No newline at end of file +ColumnMode::FindTool& GetFindTool(); +void SetWarningMessage(std::wstring str); +void ClearWarningMssage(std::wstring str); \ No newline at end of file From c42cbb38d62587dafadde28b1cd56f9220dd15ff Mon Sep 17 00:00:00 2001 From: Tanner Date: Mon, 5 Jun 2023 14:12:07 -0700 Subject: [PATCH 07/12] Add menu item to edit a theme This is basically a shortcut to open a *.cmt file in the %APPDATA%/ColumnMode/Themes folder. --- ColumnMode/ColumnMode.rc | 1 + ColumnMode/Main.cpp | 3 +++ ColumnMode/Program.cpp | 31 +++++++++++++++++++++++++++++++ ColumnMode/Program.h | 1 + ColumnMode/Resource.h | Bin 5054 -> 5336 bytes 5 files changed, 36 insertions(+) diff --git a/ColumnMode/ColumnMode.rc b/ColumnMode/ColumnMode.rc index 1ad6447..cd66676 100644 --- a/ColumnMode/ColumnMode.rc +++ b/ColumnMode/ColumnMode.rc @@ -74,6 +74,7 @@ BEGIN POPUP "Themes" BEGIN MENUITEM "Create New Theme", ID_THEMES_CREATENEWTHEME + MENUITEM "Edit a Theme", ID_THEMES_EDIT MENUITEM "Rescan", ID_THEMES_RESCAN MENUITEM SEPARATOR END diff --git a/ColumnMode/Main.cpp b/ColumnMode/Main.cpp index 198fd40..f4be5a1 100644 --- a/ColumnMode/Main.cpp +++ b/ColumnMode/Main.cpp @@ -316,6 +316,9 @@ LRESULT CALLBACK TopLevelWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM case ID_THEMES_CREATENEWTHEME: OnCreateTheme(hWnd, hInst); break; + case ID_THEMES_EDIT: + OnEditTheme(hWnd, hInst); + break; case ID_FILE_REFRESH: OnRefresh(g_windowHandles); break; diff --git a/ColumnMode/Program.cpp b/ColumnMode/Program.cpp index e539016..da83027 100644 --- a/ColumnMode/Program.cpp +++ b/ColumnMode/Program.cpp @@ -2963,6 +2963,37 @@ void OnCreateTheme(HWND hwnd, HINSTANCE hInst) DialogBox(hInst, MAKEINTRESOURCE(IDD_THEMENAMEQUERY), hwnd, ThemeNameQueryCallback); } +void OnEditTheme(HWND hwnd, HINSTANCE hInst) +{ + std::filesystem::path currentThemePath = g_themeManager.GetThemeFilepath(g_theme); + + OPENFILENAME ofn; // common dialog box structure + wchar_t szFile[MAX_PATH]; + + ZeroMemory(&ofn, sizeof(ofn)); + ofn.lStructSize = sizeof(ofn); + ofn.hwndOwner = g_windowManager.GetWindowHandles().TopLevel; + ofn.lpstrFile = szFile; + + // Set lpstrFile[0] to '\0' so that GetOpenFileName does not + // use the contents of szFile to initialize itself. + ofn.lpstrFile[0] = L'\0'; + ofn.nMaxFile = sizeof(szFile); + ofn.lpstrFilter = L"*.cmt\0"; + ofn.nFilterIndex = 1; + ofn.lpstrFileTitle = NULL; + ofn.nMaxFileTitle = 0; + ofn.lpstrInitialDir = currentThemePath.parent_path().c_str(); + ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; + + // Display the Open dialog box. + + if (!!GetOpenFileName(&ofn)) + { + OpenImpl(g_windowManager.GetWindowHandles(), ofn.lpstrFile); + } +} + bool OnMaybeDynamicMenuItemSelected(WindowHandles windowHandles, int id) { static_assert(ColumnMode::ThemeManager::THEME_MENU_ITEM_START_INDEX > ColumnMode::PluginManager::PLUGIN_MENU_ITEM_START_INDEX); diff --git a/ColumnMode/Program.h b/ColumnMode/Program.h index 09f60a0..20576ef 100644 --- a/ColumnMode/Program.h +++ b/ColumnMode/Program.h @@ -43,6 +43,7 @@ void OnPrint(WindowHandles windowHandles); void OnPluginRescan(WindowHandles windowHandles, bool skipRescan=false); void OnThemesRescan(WindowHandles windowHandles, bool skipRescan = false); void OnCreateTheme(HWND hwnd, HINSTANCE hInst); +void OnEditTheme(HWND hwnd, HINSTANCE hInst); bool OnMaybeDynamicMenuItemSelected(WindowHandles windowHandles, int id); void OnDiagramMode(WindowHandles windowHandles); void OnTextMode(WindowHandles windowHandles); diff --git a/ColumnMode/Resource.h b/ColumnMode/Resource.h index 2c1874381b4006034df828e15b9a4899786198ba..72c29ce964e8be61498b68bbdb1a7afc992715e2 100644 GIT binary patch delta 98 zcmdm|enWG^0Rhf<2499$hCGJS$%bsklN|&&CQo6OVvlDCVaQ-e1&e*)VPm#nFqpiM zMSAiwegRfj1{Vg;$$?DjK-FxFhLbn4%5Od*Aj8CL$Y416A(QB22O*KkJi?m*G7cIT delta 28 kcmcbixlet=0fET{tSp=F3Cb`{p2RFMIZs4nvYN;y0HRU~8vp Date: Mon, 5 Jun 2023 14:13:05 -0700 Subject: [PATCH 08/12] Add better warning messages when working with an invalid theme --- ColumnMode/Program.cpp | 29 ++++++++++++++++++++++------- ColumnMode/Theme.cpp | 10 ++++++++++ ColumnMode/Theme.h | 1 + 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/ColumnMode/Program.cpp b/ColumnMode/Program.cpp index da83027..5ea2de8 100644 --- a/ColumnMode/Program.cpp +++ b/ColumnMode/Program.cpp @@ -2016,6 +2016,17 @@ void OpenImpl(WindowHandles windowHandles, LPCWSTR fileName) EnableMenuItem(windowHandles, ID_FILE_REFRESH); EnableMenuItem(windowHandles, ID_FILE_SAVE); g_pluginManager.PF_OnOpen_ALL(fileName); + + std::filesystem::path path = fileName; + if (path.extension() == L".cmt") + { + if (!g_themeManager.LoadTheme(path.filename(), g_theme, false)) + { + WCHAR buff[256]; + std::swprintf(buff, 256, L"There was an error loading the theme.\nWhen you have fixed the issue, CTRL+S and then select it as the active theme from Options > Themes > %s.", path.filename().replace_extension(L"").c_str()); + MessageBox(NULL, buff, fileName, MB_OK); + } + } } void OnOpen(WindowHandles windowHandles) @@ -2054,12 +2065,6 @@ void OnOpen(WindowHandles windowHandles) if (!!GetOpenFileName(&ofn)) { OpenImpl(windowHandles, ofn.lpstrFile); - std::filesystem::path path = ofn.lpstrFile; - if (path.extension() == L".cmt") { - if (!g_themeManager.LoadTheme(path.filename(), g_theme, false)) { - MessageBox(NULL, L"There was an error loading the theme.", ofn.lpstrFile, MB_OK); - } - } } } @@ -3058,7 +3063,17 @@ bool OnMaybeThemeSelected(WindowHandles windowHandles, int id) int size = GetMenuString(themesMenu, id, buff, 64, MF_BYCOMMAND); if (size > 0 && g_theme.GetName().compare(buff) != 0) { - g_themeManager.LoadTheme(buff, g_theme); + if (!g_themeManager.LoadTheme(buff, g_theme)) + { + int dialogResult = MessageBox(NULL, L"There was an error loading the theme.\nOpen it for editing and try to fix the issue?", buff, MB_ICONERROR | MB_YESNO); + if (dialogResult == IDYES) + { + ColumnMode::Theme temp{}; + temp.SetName(buff); + std::wstring path = g_themeManager.GetThemeFilepath(temp); + OpenImpl(g_windowManager.GetWindowHandles(), path.c_str()); + } + } OnThemesRescan(windowHandles, true); // Handle the check marking-ing in probably the worst way } return true; diff --git a/ColumnMode/Theme.cpp b/ColumnMode/Theme.cpp index 7cc1c02..d8756cd 100644 --- a/ColumnMode/Theme.cpp +++ b/ColumnMode/Theme.cpp @@ -1,5 +1,7 @@ #include "stdafx.h" #include "../External/json.hpp" + +#include "Program.h" #include "Theme.h" #include "Verify.h" #include "utf8Conversion.h" @@ -185,10 +187,18 @@ bool ColumnMode::ThemeManager::LoadThemeFromText(std::wstring jsonString, Theme& { json data = json::parse(jsonString); out = data; + themeTextInvalidOnLastLoadFromText = false; + ClearWarningMssage(L"Theme JSON invalid!"); return true; } catch (...) { + if (!themeTextInvalidOnLastLoadFromText) + { + MessageBox(NULL, L"Your latest change broke the theme.\nEnsure that your JSON is well formatted and color values are valid floats.", L"Error parsing Theme", MB_ICONERROR | MB_OK); + } + themeTextInvalidOnLastLoadFromText = true; + SetWarningMessage(L"Theme JSON invalid!"); return false; } return false; diff --git a/ColumnMode/Theme.h b/ColumnMode/Theme.h index 9ed6505..e9bc2a7 100644 --- a/ColumnMode/Theme.h +++ b/ColumnMode/Theme.h @@ -78,6 +78,7 @@ namespace ColumnMode private: std::filesystem::path m_themesRootPath; std::vector m_availableThemes; + bool themeTextInvalidOnLastLoadFromText = false; public: static const int THEME_MENU_ITEM_START_INDEX = 5000;//chosen arbitrarily. hopefully the resource generator doesn't conflict From ee99a72ea9cf55a09b385d88fd808dc286508f1b Mon Sep 17 00:00:00 2001 From: Tanner Date: Thu, 1 Jun 2023 12:57:45 -0700 Subject: [PATCH 09/12] Loading a plugin should consider the folder it is in when searching for dependencies --- ColumnMode/PluginManager.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/ColumnMode/PluginManager.cpp b/ColumnMode/PluginManager.cpp index 6d46b75..6a2c661 100644 --- a/ColumnMode/PluginManager.cpp +++ b/ColumnMode/PluginManager.cpp @@ -62,14 +62,19 @@ HRESULT ColumnMode::PluginManager::ScanForPlugins() HRESULT ColumnMode::PluginManager::LoadPlugin(LPCWSTR pluginName) { std::filesystem::path path(m_modulesRootPath); - path.append(pluginName) //Plugins should be in a folder of the plugin name - .append(pluginName) //Plugin is a DLL file of the plugin name + path.append(pluginName) //Plugins should be in a folder of the plugin name + .append(pluginName) //Plugin is a DLL file of the plugin name .replace_extension(L".dll"); - HMODULE pluginModule = LoadLibrary(path.c_str()); + DWORD flags = LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR; + HMODULE pluginModule = LoadLibraryEx(path.c_str(), NULL, flags); if (pluginModule == NULL) { - MessageBox(NULL, path.c_str(), L"Plugin Not Found", MB_OK | MB_ICONERROR); + DWORD err = GetLastError(); + WCHAR buff[1024]; + std::swprintf(buff, 1024, _T("Plugin not found or DLL failed to load.\nError code: %d\nPath: %s"), err, path.c_str()); + MessageBox(NULL, buff, L"Error loading plugin DLL", MB_OK | MB_ICONERROR); + return E_INVALIDARG; } From e8b15d446834c7daee43ab212b1f1bcb29734888 Mon Sep 17 00:00:00 2001 From: Tanner Date: Mon, 5 Jun 2023 10:56:16 -0700 Subject: [PATCH 10/12] Plugin dependency loading Some weirdness required due to dxcompiler.dll being a load time dependency of a plugin, but dxcompiler.dll does a check in dllmain to see if dxil.dll is loadable. It isn't in the default search path, so it silently decides to not load dxil.dll at runtime. FML.jpg Hacky solution is to load the plugin, get the dependency list, Free the plugin (which also frees dxcompiler.dll), THEN load the runtime dependency list, then RELOAD the plugin (which in turn loads its Load time dependencies, namely dxcompiler,dll). Now everyone is happy :) Hopefully no future plugin requires opposite behavior... --- ColumnMode/ColumnModePluginApi.h | 20 ++++++ ColumnMode/PluginManager.cpp | 111 ++++++++++++++++++++++++++++--- ColumnMode/Verify.h | 2 + ColumnMode/stdafx.h | Bin 2206 -> 2846 bytes 4 files changed, 124 insertions(+), 9 deletions(-) diff --git a/ColumnMode/ColumnModePluginApi.h b/ColumnMode/ColumnModePluginApi.h index fa0c838..7775461 100644 --- a/ColumnMode/ColumnModePluginApi.h +++ b/ColumnMode/ColumnModePluginApi.h @@ -49,6 +49,12 @@ namespace ColumnMode #pragma endregion + struct PluginDependency + { + UINT length; // number of WCHARs in pName + WCHAR* pName; + }; + struct OpenPluginArgs { _In_ UINT apiVersion; @@ -58,6 +64,20 @@ namespace ColumnMode }; } +/* +Safe to not export if your plugin doesn't have run-time dll dependencies. +Called in the following pattern: +1. pCount is a valid pointer to any UINT and pDependencies is nullptr. - Plugin should set pCount to the number of dependencies. +2. pCount is a valid pointer, pDependencies is a valid pointer, each dependency's pName is nullptr. - Plugin should validate pCount is the right size, then populate the length fields of each dependency struct. +3. pCount is a valid pointer, pDependencies is a valid pointer, each dependency's pName is a valid pointer - Plugin should validate pCount, then foreach dependency: validate the length and then write the dependency name to the buffer (including extension). + +Return value from each step should be S_OK if everything is valid and fields are being written as expected. If pCount or a length value is wrong, return E_INVALIDARG. Else, return E_FAIL. + +Note that depenency dlls should be in the same directory as your plugin: %APPDATA%/ColumnMode/Plugins/your_plugin_name/ +*/ +extern "C" HRESULT WINAPI QueryColumnModePluginDependencies(_Inout_ UINT* pCount, _Inout_opt_count_(*pCount) ColumnMode::PluginDependency* pDependencies); +typedef HRESULT(WINAPI* PFN_QUERYCOLUMNMODEPLUGINDEPENDENCIES)(_Inout_ UINT* pCount, _Inout_opt_count_(*pCount) ColumnMode::PluginDependency* pDependencies); +// Required export extern "C" HRESULT WINAPI OpenColumnModePlugin(_Inout_ ColumnMode::OpenPluginArgs* args); typedef HRESULT(WINAPI* PFN_OPENCOLUMNMODEPLUGIN)(_Inout_ ColumnMode::OpenPluginArgs* args); \ No newline at end of file diff --git a/ColumnMode/PluginManager.cpp b/ColumnMode/PluginManager.cpp index 6a2c661..714f1ac 100644 --- a/ColumnMode/PluginManager.cpp +++ b/ColumnMode/PluginManager.cpp @@ -59,6 +59,91 @@ HRESULT ColumnMode::PluginManager::ScanForPlugins() return S_OK; } +HRESULT LoadLibraryHelper(std::filesystem::path path, HMODULE& out_pluginModule) +{ + DWORD flags = LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR; + out_pluginModule = LoadLibraryEx(path.c_str(), NULL, flags); + if (out_pluginModule == NULL) + { + DWORD err = GetLastError(); + WCHAR buff[1024]; + std::swprintf(buff, 1024, _T("Plugin not found or DLL failed to load.\nError code: %d\nPath: %s"), err, path.c_str()); + MessageBox(NULL, buff, L"Error loading plugin DLL", MB_OK | MB_ICONERROR); + + return E_INVALIDARG; + } + return S_OK; +} + +HRESULT LoadPluginDependenciesHelper(PFN_QUERYCOLUMNMODEPLUGINDEPENDENCIES pfnQueryDeps, std::filesystem::path pluginDir, HMODULE pluginModule) +{ + UINT numDeps = 0; + std::wstring pluginName = pluginDir.filename(); //plugin name is the last part of the pluginDir + + //query num dependencies and create dependency list + HRESULT hr = pfnQueryDeps(&numDeps, nullptr); + if(FAILED(hr)) + { + MessageBoxHelper_FormattedBody(MB_ICONERROR | MB_OK, L"Failed to get dependencies", L"%s returned failure code %d when calling QueryColumnModePluginDependencies (1st call).", pluginName, hr); + return hr; + } + ColumnMode::PluginDependency defaultInit{ 0, nullptr }; + std::vector deps = std::vector(numDeps, defaultInit); + + // query sizes of dependency names and allocate space + hr = pfnQueryDeps(&numDeps, deps.data()); + if (FAILED(hr)) + { + MessageBoxHelper_FormattedBody(MB_ICONERROR | MB_OK, L"Failed to get dependencies", L"%s returned failure code %d when calling QueryColumnModePluginDependencies (2nd call).", pluginName, hr); + return hr; + } + for (auto depIt = deps.begin(); depIt != deps.end(); depIt++) + { + depIt->pName = (WCHAR*)malloc((depIt->length +1) * sizeof(WCHAR)); //add one extra char of padding so that we can ensure null-terminated + if (depIt->pName == nullptr) + { + hr = E_OUTOFMEMORY; + goto cleanup_and_exit; + } + } + + // query dependency names + hr = pfnQueryDeps(&numDeps, deps.data()); + if (FAILED(hr)) + { + MessageBoxHelper_FormattedBody(MB_ICONERROR | MB_OK, L"Failed to get dependencies", L"%s returned failure code %d when calling QueryColumnModePluginDependencies (3rd call).", pluginName, hr); + goto cleanup_and_exit; + } + + //Now we need to free the plugin library in case it has a load time library that does weird checks for runtime dependencies in dllmain (looking at you dxcompiler.dll) + FreeLibrary(pluginModule); + //Finally load the requested libraries + for (auto depIt = deps.begin(); depIt != deps.end(); depIt++) + { + depIt->pName[depIt->length] = L'\0'; //ensure null-terminated + std::filesystem::path depFullPath = pluginDir / depIt->pName; + HMODULE hm; + if (FAILED(LoadLibraryHelper(depFullPath, hm))) + { + hr = E_FAIL; + goto cleanup_and_exit; + //maybe unload dlls? + } + } + + cleanup_and_exit: + // free allocated strings + for (auto depIt = deps.begin(); depIt != deps.end(); depIt++) + { + if (depIt->pName != nullptr) + { + free(depIt->pName); + depIt->pName = nullptr; + } + } + return hr; +} + HRESULT ColumnMode::PluginManager::LoadPlugin(LPCWSTR pluginName) { std::filesystem::path path(m_modulesRootPath); @@ -66,21 +151,29 @@ HRESULT ColumnMode::PluginManager::LoadPlugin(LPCWSTR pluginName) .append(pluginName) //Plugin is a DLL file of the plugin name .replace_extension(L".dll"); - DWORD flags = LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR; - HMODULE pluginModule = LoadLibraryEx(path.c_str(), NULL, flags); - if (pluginModule == NULL) + + + HMODULE pluginModule; + BAIL_ON_FAIL_HR(LoadLibraryHelper(path, pluginModule)); + + PFN_QUERYCOLUMNMODEPLUGINDEPENDENCIES pfnQueryDeps = + reinterpret_cast(GetProcAddress(pluginModule, "QueryColumnModePluginDependencies")); + + if (pfnQueryDeps != nullptr) { - DWORD err = GetLastError(); - WCHAR buff[1024]; - std::swprintf(buff, 1024, _T("Plugin not found or DLL failed to load.\nError code: %d\nPath: %s"), err, path.c_str()); - MessageBox(NULL, buff, L"Error loading plugin DLL", MB_OK | MB_ICONERROR); - - return E_INVALIDARG; + BAIL_ON_FAIL_HR(LoadPluginDependenciesHelper(pfnQueryDeps, m_modulesRootPath / pluginName, pluginModule)); } + BAIL_ON_FAIL_HR(LoadLibraryHelper(path, pluginModule)); PFN_OPENCOLUMNMODEPLUGIN pfnOpenPlugin = reinterpret_cast(GetProcAddress(pluginModule, "OpenColumnModePlugin")); + if (pfnOpenPlugin == nullptr) + { + MessageBoxHelper_FormattedBody(MB_ICONERROR | MB_OK, L"Failed to open plugin", L"%s doesn't export the OpenColumnModePlugin function.", pluginName); + return E_FAIL; + } + PluginFunctions pluginFuncs = { 0 }; OpenPluginArgs args{}; args.apiVersion = c_ColumnModePluginApiVersion; diff --git a/ColumnMode/Verify.h b/ColumnMode/Verify.h index 1c896d8..474776c 100644 --- a/ColumnMode/Verify.h +++ b/ColumnMode/Verify.h @@ -6,6 +6,8 @@ void inline VerifyHR(HRESULT hr) __debugbreak(); } +#define BAIL_ON_FAIL_HR(hrFunc) {HRESULT hr = hrFunc; if(FAILED(hr)) { return hr;}} + void inline VerifyBool(BOOL b) { if (!b) diff --git a/ColumnMode/stdafx.h b/ColumnMode/stdafx.h index 344b9ac5eafc0ba3952507f268b042f3030cf967..2f18b10d27f60f5fc1eb983063da0c7207f05f23 100644 GIT binary patch delta 653 zcmaKq%SyvQ6o$Wzpn?lGuCq)*TnM`G25q4zRFPhAAtEVBOCVL5jCe!xCVd7U(SN2% zjatef$uQ^gUruJ*vz@Qmda=bOmV_x!WJLaa!jKED7!WYxnXw|l&?n_xr+`yE`y6n{ zu1=OWbtgQif2!}EkAS9nCghyqP*UZssxMWMD8=+(VfvKN5_$i zeN`-&utv>6Ee4fyI$neWb;P<{)8 Date: Tue, 6 Jun 2023 13:05:21 -0700 Subject: [PATCH 11/12] Add support for notifying plugins when the user stops typing --- ColumnMode/ColumnModePluginApi.h | 6 +++++- ColumnMode/PluginManager.cpp | 3 ++- ColumnMode/PluginManagerFunctions.inl | 2 ++ ColumnMode/Program.cpp | 1 + 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/ColumnMode/ColumnModePluginApi.h b/ColumnMode/ColumnModePluginApi.h index 7775461..635eb26 100644 --- a/ColumnMode/ColumnModePluginApi.h +++ b/ColumnMode/ColumnModePluginApi.h @@ -4,7 +4,7 @@ namespace ColumnMode { - constexpr UINT c_ColumnModePluginApiVersion = 1; + constexpr UINT c_ColumnModePluginApiVersion = 2; #pragma region ColumnModeCallbacks @@ -32,6 +32,7 @@ namespace ColumnMode typedef HRESULT(APIENTRY* PFN_PF_ONOPEN)(HANDLE, LPCWSTR); typedef HRESULT(APIENTRY* PFN_PF_ONSAVE)(HANDLE, LPCWSTR); typedef HRESULT(APIENTRY* PFN_PF_ONSAVEAS)(HANDLE, LPCWSTR); + typedef HRESULT(APIENTRY* PFN_PF_ONTYPINGCOMPLETE)(HANDLE, const size_t numChars, const WCHAR* pAllText); //Plugin Life cycle typedef HRESULT(APIENTRY* PFN_PF_ONLOADCOMPLETED)(HANDLE); //Called after OpenColumnModePlugin @@ -45,6 +46,9 @@ namespace ColumnMode PFN_PF_ONLOADCOMPLETED pfnOnLoadCompleted; PFN_PF_ONSHUTDOWN pfnOnShutdown; + + // API version >= 2 + PFN_PF_ONTYPINGCOMPLETE pfnOnTypingComplete; }; #pragma endregion diff --git a/ColumnMode/PluginManager.cpp b/ColumnMode/PluginManager.cpp index 714f1ac..0c39e12 100644 --- a/ColumnMode/PluginManager.cpp +++ b/ColumnMode/PluginManager.cpp @@ -174,7 +174,8 @@ HRESULT ColumnMode::PluginManager::LoadPlugin(LPCWSTR pluginName) return E_FAIL; } - PluginFunctions pluginFuncs = { 0 }; + PluginFunctions pluginFuncs{}; + ZeroMemory(&pluginFuncs, sizeof(pluginFuncs)); OpenPluginArgs args{}; args.apiVersion = c_ColumnModePluginApiVersion; args.hPlugin = NULL; diff --git a/ColumnMode/PluginManagerFunctions.inl b/ColumnMode/PluginManagerFunctions.inl index e593507..e86bf2c 100644 --- a/ColumnMode/PluginManagerFunctions.inl +++ b/ColumnMode/PluginManagerFunctions.inl @@ -21,6 +21,7 @@ DECLARE_PLUGINMANAGER_FUNCTION_CALL_ALL(OnOpen, (LPCWSTR)) DECLARE_PLUGINMANAGER_FUNCTION_CALL_ALL(OnSave, (LPCWSTR)) DECLARE_PLUGINMANAGER_FUNCTION_CALL_ALL(OnSaveAs, (LPCWSTR)) +DECLARE_PLUGINMANAGER_FUNCTION_CALL_ALL(OnTypingComplete, (const size_t, const WCHAR*)) #undef DECLARE_PLUGINMANAGER_FUNCTION_CALL_ALL @@ -39,6 +40,7 @@ DECLARE_PLUGINMANAGER_FUNCTION_CALL_ALL(OnSaveAs, (LPCWSTR)) DEFINE_PLUGINMANAGER_FUNCTION_CALL_ALL(OnOpen, (LPCWSTR fileName), (p.m_hPlugin, fileName)) DEFINE_PLUGINMANAGER_FUNCTION_CALL_ALL(OnSave, (LPCWSTR fileName), (p.m_hPlugin, fileName)) DEFINE_PLUGINMANAGER_FUNCTION_CALL_ALL(OnSaveAs, (LPCWSTR fileName), (p.m_hPlugin, fileName)) +DEFINE_PLUGINMANAGER_FUNCTION_CALL_ALL(OnTypingComplete, (const size_t numChars, const WCHAR* pAllText), (p.m_hPlugin, numChars, pAllText)) #undef DEFINE_PLUGINMANAGER_FUNCTION_CALL_ALL diff --git a/ColumnMode/Program.cpp b/ColumnMode/Program.cpp index 5ea2de8..ea4a4ec 100644 --- a/ColumnMode/Program.cpp +++ b/ColumnMode/Program.cpp @@ -1915,6 +1915,7 @@ void ConfirmEdits() { g_themeManager.LoadThemeFromText(g_allText, g_theme); } + g_pluginManager.PF_OnTypingComplete_ALL(g_allText.length(), g_allText.c_str()); } void Update() From f7fbf3cf0cd7fb625cfe5d95efd9668d2693e427 Mon Sep 17 00:00:00 2001 From: Tanner Date: Tue, 6 Jun 2023 14:44:25 -0700 Subject: [PATCH 12/12] Only trigger ConfirmEdits() when typing characters in g_keyOutput --- ColumnMode/Program.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ColumnMode/Program.cpp b/ColumnMode/Program.cpp index ea4a4ec..0c487c8 100644 --- a/ColumnMode/Program.cpp +++ b/ColumnMode/Program.cpp @@ -192,6 +192,7 @@ DWRITE_HIT_TEST_METRICS g_caretMetrics; int g_caretBlinkState; int g_updatesSinceLastEdit; +bool g_editMadeSinceLastConfirmationOfEdits = false; const int g_numUpdatesWithoutEditBeforeConfirmingEdits = 50; std::wstring g_allText; @@ -756,7 +757,7 @@ void InitGraphics(WindowHandles windowHandles) g_isTrackingLeaveClientArea = false; g_hasTextSelectionRectangle = false; g_caretBlinkState = 0; - g_updatesSinceLastEdit = 0; + g_updatesSinceLastEdit = g_numUpdatesWithoutEditBeforeConfirmingEdits+1; //init to more than the confirmation value so we don't prematurely confirm g_isShiftDown = false; g_hasUnsavedChanges = false; g_needsDeviceRecreation = false; @@ -1613,12 +1614,11 @@ void OnKeyDown(WindowHandles windowHandles, WPARAM wParam) return; g_caretBlinkState = 0; - g_updatesSinceLastEdit = 0; CheckModifierKeys(); // modifiers could be pressed when a message went to a different handler if (g_keyOutput[wParam].Valid) { DisableTextSelectionRectangle(windowHandles); - + g_updatesSinceLastEdit = 0; // only register newly typed characters for calling ConfirmEdits wchar_t chr = g_isShiftDown ? g_keyOutput[wParam].Uppercase : g_keyOutput[wParam].Lowercase; if (g_status.GetMode() == Mode::DiagramMode)