forked from Leonard-The-Wise/NWScript-Npp
-
Notifications
You must be signed in to change notification settings - Fork 0
/
PluginMain.cpp
3419 lines (2868 loc) · 143 KB
/
PluginMain.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/** @file PluginMain.cpp
* Controls the Plugin functions, processes all Plugin messages
*
* Although this is the main object, the actual DLL Entry point is defined in PluginInterface.cpp
*
**/
// Copyright (C) 2022 - Leonardo Silva
// The License.txt file describes the conditions under which this software may be distributed.
#include "pch.h"
//#include <Windows.h>
//#include <string>
//#include <sstream>
//#include <stdexcept>
//#include <ShlObj.h>
//#include <codecvt>
//#include <locale>
//#include <cwchar>
//#include <Shlwapi.h>
#include "menuCmdID.h"
#include "Common.h"
#include "LexerCatalogue.h"
#include "PluginMain.h"
#include "PluginControlsRC.h"
#include "NWScriptParser.h"
#include "BatchProcessingDialog.h"
#include "CompilerSettingsDialog.h"
#include "FileParseSummaryDialog.h"
#include "PathAccessDialog.h"
#include "ProcessFilesDialog.h"
#include "UsersPreferencesDialog.h"
#include "WarningDialog.h"
#include "XMLGenStrings.h"
#include "VersionInfoEx.h"
#include "ColorConvert.h"
#include "PluginDarkMode.h"
#pragma warning (disable : 6387)
//#define DEBUG_AUTO_INDENT_833 // Uncomment to test auto-indent with message
#define USE_THREADS // Process compilations and batchs in multi-threaded operations
#define NAGIVATECALLBACKTIMER 0x800 // Temporary timer to schedule navigations
using namespace NWScriptPlugin;
using namespace LexerInterface;
// Static members definition
generic_string Plugin::pluginName = TEXT("NWScript Tools");
// Menu functions order.
// Needs to be Sync'ed with pluginFunctions[]
#define PLUGINMENU_SWITCHAUTOINDENT 0
#define PLUGINMENU_DASH1 1
#define PLUGINMENU_COMPILESCRIPT 2
#define PLUGINMENU_DISASSEMBLESCRIPT 3
#define PLUGINMENU_BATCHPROCESSING 4
#define PLUGINMENU_RUNLASTBATCH 5
#define PLUGINMENU_DASH2 6
#define PLUGINMENU_FETCHPREPROCESSORTEXT 7
#define PLUGINMENU_VIEWSCRIPTDEPENDENCIES 8
#define PLUGINMENU_DASH3 9
#define PLUGINMENU_SHOWCONSOLE 10
#define PLUGINMENU_DASH4 11
#define PLUGINMENU_SETTINGS 12
#define PLUGINMENU_USERPREFERENCES 13
#define PLUGINMENU_DASH5 14
#define PLUGINMENU_INSTALLDARKTHEME 15
#define PLUGINMENU_IMPORTDEFINITIONS 16
#define PLUGINMENU_IMPORTUSERTOKENS 17
#define PLUGINMENU_RESETUSERTOKENS 18
#define PLUGINMENU_RESETEDITORCOLORS 19
#define PLUGINMENU_REPAIRXMLASSOCIATION 20
#define PLUGINMENU_DASH6 21
#define PLUGINMENU_INSTALLCOMPLEMENTFILES 22
#define PLUGINMENU_DASH7 23
#define PLUGINMENU_ABOUTME 24
#define PLUGIN_HOMEPATH TEXT("https://github.com/Leonard-The-Wise/NWScript-Npp")
#define PLUGIN_ONLINEHELP TEXT("https://github.com/Leonard-The-Wise/NWScript-Npp/blob/master/OnlineHelp.md")
ShortcutKey compileScriptKey = { false, false, false, VK_F9 };
ShortcutKey disassembleScriptKey = { true, false, false, VK_F9 };
ShortcutKey batchScriptKey = { true, false, true, VK_F9 };
ShortcutKey runLastBatchKey = { false, false, true, VK_F9 };
ShortcutKey toggleConsoleKey = { true, false, false, VK_OEM_COMMA };
FuncItem Plugin::pluginFunctions[] = {
{TEXT("Use auto-identation"), Plugin::SwitchAutoIndent, 0, false },
{TEXT("---")},
{TEXT("Compile NWScript"), Plugin::CompileScript, 0, false, &compileScriptKey },
{TEXT("Disassemble NWScript file..."), Plugin::DisassembleFile, 0, false, &disassembleScriptKey },
{TEXT("Batch Process NWScript Files..."), Plugin::BatchProcessFiles, 0, false, &batchScriptKey },
{TEXT("Run last batch"), Plugin::RunLastBatch, 0, false, &runLastBatchKey},
{TEXT("---")},
{TEXT("Fetch preprocessed output"), Plugin::FetchPreprocessorText},
{TEXT("View NWScript dependencies"), Plugin::ViewScriptDependencies},
{TEXT("---")},
{TEXT("Toggle NWScript Compiler Console"), Plugin::ToggleLogger, 0, false, &toggleConsoleKey},
{TEXT("---")},
{TEXT("Compiler settings..."), Plugin::CompilerSettings},
{TEXT("User's preferences..."), Plugin::UserPreferences},
{TEXT("---")},
{TEXT("Install Dark Theme"), Plugin::InstallDarkTheme},
{TEXT("Import NWScript definitions"), Plugin::ImportDefinitions},
{TEXT("Import user-defined tokens"), Plugin::ImportUserTokens},
{TEXT("Reset user-defined tokens"), Plugin::ResetUserTokens},
{TEXT("Reset editor colors"), Plugin::ResetEditorColors},
{TEXT("Repair Function List"), Plugin::RepairFunctionList},
{TEXT("---")},
{TEXT("Install Plugin's XML Config Files"), Plugin::InstallAdditionalFiles},
{TEXT("---")},
{TEXT("About me"), Plugin::AboutMe},
};
Plugin* Plugin::_instance(nullptr);
// Static file and directory names
// Notepad Plugin Configuration installation root directory. We made it const, since we're not retrieving this outside a plugin build
constexpr const TCHAR NotepadPluginRootDir[] = TEXT("plugins\\");
// Notepad Plugin Configuration installation root directory. We made it const, since we're not retrieving this outside a plugin build
constexpr const TCHAR NotepadPluginConfigRootDir[] = TEXT("plugins\\Config\\");
// Notepad Default Themes install directory. We made it const, since we're not retrieving this outside a plugin build
constexpr const TCHAR NotepadDefaultThemesRootDir[] = TEXT("themes\\");
// Notepad Default Auto Completion directory.
constexpr const TCHAR NotepadAutoCompleteRootDir[] = TEXT("autoCompletion\\");
// Notepad Default Function List directory.
constexpr const TCHAR NotepadFunctionListRootDir[] = TEXT("functionList\\");
// Notepad config file
constexpr const TCHAR NotepadConfigFile[] = TEXT("config.xml");
// OverrideMap XML file
constexpr const TCHAR OverrideMapFile[] = TEXT("overrideMap.xml");
// Notepad Default Dark Theme file.
constexpr const TCHAR NotepadDefaultDarkThemeFile[] = TEXT("DarkModeDefault.xml");
// Default pseudo-batch file to create in case we need to restart notepad++
constexpr const TCHAR PseudoBatchRestartFile[] = TEXT("~doNWScriptNotepadRestart.bat");
// NWScript known engine objects file
constexpr const TCHAR NWScriptEngineObjectsFile[] = TEXT("NWScript-Npp-EngineObjects.bin");
// NWScript known user objects file
constexpr const TCHAR NWScriptUserObjectsFile[] = TEXT("NWScript-Npp-UserObjects.bin");
// Bellow is a list of FIXED keywords NWScript engine uses and since it is part of the base syntax, hardly
// this will ever change, so we made them constants here.
constexpr const char fixedPreProcInstructionSet[] = "#define #include";
constexpr const char fixedInstructionSet[] = "break case continue default do else FALSE for if return switch TRUE while";
constexpr const char fixedKeywordSet[] = "action command const float int string struct vector void";
constexpr const char fixedObjKeywordSet[] = "object OBJECT_INVALID OBJECT_SELF";
#pragma region
// Initializes the Plugin (called by Main DLL entry point - ATTACH)
void Plugin::PluginInit(HANDLE hModule)
{
// Can't be called twice
if (_instance)
{
throw std::runtime_error("Double initialization of a singleton class.");
return;
}
// Instantiate the Plugin
_instance = new Plugin(static_cast<HMODULE>(hModule));
// Get metainformation about this plugin
TCHAR fTemp[MAX_PATH] = {};
DWORD fSize = GetModuleFileName(static_cast<HMODULE>(hModule), fTemp, MAX_PATH);
Instance()._pluginPaths.insert({ "PluginPath", fs::path(fTemp) });
Instance()._pluginFileName = Instance()._pluginPaths["PluginPath"].stem();
// The rest of metainformation is get when Notepad Messenger is set...
// Check on Plugin::SetNotepadData
// Load latest Richedit library and only then create the about dialog
LoadLibrary(TEXT("Msftedit.dll"));
Instance()._aboutDialog = std::make_unique<AboutDialog>();
Instance()._loggerWindow = std::make_unique<LoggerDialog>();
Instance()._processingFilesDialog = std::make_unique<ProcessFilesDialog>();
}
// Cleanup Plugin memory upon deletion (called by Main DLL entry point - DETACH)
void Plugin::PluginRelease()
{
if (!_instance)
{
throw std::runtime_error("Trying to release an uninitalized class.");
return;
}
delete _instance;
_instance = nullptr;
}
// Return the number of Menu Functions Count. Can't be inline because pluginFunctions is defined outside class
int Plugin::GetFunctionCount() const
{
return static_cast<int>(std::size(pluginFunctions));
}
// Create plugin's file paths variables
void Plugin::MakePluginFilePaths()
{
// Finish initializing the Plugin Metainformation
TCHAR fBuffer[MAX_PATH + 1] = {0};
generic_string sPath;
// Get main paths for program
Messenger().SendNppMessage(NPPM_GETNPPFULLFILEPATH, static_cast<WPARAM>(MAX_PATH), reinterpret_cast<LPARAM>(fBuffer));
sPath = fBuffer;
_pluginPaths.insert({ "NotepadInstallExecutablePath", fs::path(fBuffer) });
_pluginPaths.insert({ "NotepadExecutableDir", fs::path(fBuffer).parent_path() });
Messenger().SendNppMessage(NPPM_GETPLUGINSCONFIGDIR, static_cast<WPARAM>(MAX_PATH), reinterpret_cast<LPARAM>(fBuffer));
sPath = fBuffer;
_pluginPaths.insert({ "PluginConfigDir", fs::path(sPath) });
_pluginPaths.insert({ "NotepadUserConfigDir", fs::path(sPath).parent_path().parent_path() });
// Step 1:
// First we get all files avaliable from Plugin Config Dir if they are present there, because
// that's the Notepad++ priority list for loading config files
sPath = str2wstr(_pluginPaths["NotepadUserConfigDir"].string());
sPath.append(TEXT("\\")).append(NotepadConfigFile);
_pluginPaths.insert({ "NotepadConfigFile", fs::path(sPath) });
generic_string pluginLexerConfigFile = _pluginFileName;
sPath = str2wstr(_pluginPaths["PluginConfigDir"].string());
sPath.append(TEXT("\\")).append(pluginLexerConfigFile).append(TEXT(".xml"));
_pluginPaths.insert({ "PluginLexerConfigFilePath", fs::path(sPath) });
sPath = str2wstr(_pluginPaths["PluginConfigDir"].string());
sPath.append(TEXT("\\")).append(_pluginFileName).append(TEXT(".ini"));
_pluginPaths.insert({ "PluginSettingsFile", fs::path(sPath) });
// These files here are the one that can be in multiple places at one time
sPath = str2wstr(_pluginPaths["NotepadUserConfigDir"].string());
sPath.append(TEXT("\\")).append(NotepadDefaultThemesRootDir).append(NotepadDefaultDarkThemeFile);
_pluginPaths.insert({ "NotepadDarkThemeFilePath", fs::path(sPath) });
sPath = str2wstr(_pluginPaths["NotepadUserConfigDir"].string());
sPath.append(TEXT("\\")).append(NotepadAutoCompleteRootDir).append(str2wstr(LexerCatalogue::GetLexerName(0, true))).append(TEXT(".xml"));
_pluginPaths.insert({ "PluginAutoCompleteFilePath", fs::path(sPath) });
sPath = str2wstr(_pluginPaths["NotepadUserConfigDir"].string());
sPath.append(TEXT("\\")).append(NotepadFunctionListRootDir).append(OverrideMapFile);
_pluginPaths.insert({ "NotepadOverrideMapFile", fs::path(sPath) });
sPath = str2wstr(_pluginPaths["NotepadUserConfigDir"].string());
sPath.append(TEXT("\\")).append(NotepadFunctionListRootDir).append(str2wstr(LexerCatalogue::GetLexerName(0, true))).append(TEXT(".xml"));
_pluginPaths.insert({ "PluginFunctionListFile", fs::path(sPath) });
// Bellow are files that will always be on Plugin's config sPath
// Temporary batch we write in case we need to restart Notepad++ by ourselves. Root is Plugin config sPath
sPath = _pluginPaths["PluginConfigDir"];
sPath.append(TEXT("\\")).append(PseudoBatchRestartFile);
_pluginPaths.insert({ "PseudoBatchRestartFile", fs::path(sPath) });
// Known NWScript structures
sPath = _pluginPaths["PluginConfigDir"];
sPath.append(TEXT("\\")).append(NWScriptEngineObjectsFile);
_pluginPaths.insert({ "NWScriptEngineObjectsFile", fs::path(sPath) });
sPath = _pluginPaths["PluginConfigDir"];
sPath.append(TEXT("\\")).append(NWScriptUserObjectsFile);
_pluginPaths.insert({ "NWScriptUserObjectsFile", fs::path(sPath) });
// Step 2:
// For any file not present on Plugins Config Dir, we then check on the Notepad++ executable sPath.
// Force Install paths tells the compiler to not test to files existences. This is used
// while Notepad++ do not support custom paths for the mentioned files and always use the ones inside the installation dir
#define FORCE_INSTALL_PATHS
if (!PathFileExists(str2wstr(_pluginPaths["NotepadDarkThemeFilePath"].string()).c_str()))
{
sPath = _pluginPaths["NotepadExecutableDir"];
sPath.append(TEXT("\\")).append(NotepadDefaultThemesRootDir).append(NotepadDefaultDarkThemeFile);
#ifndef FORCE_INSTALL_PATHS
if (PathFileExists(sPath.c_str()))
#endif
_pluginPaths["NotepadDarkThemeFilePath"] = sPath;
}
if (!PathFileExists(str2wstr(_pluginPaths["PluginAutoCompleteFilePath"].string()).c_str()))
{
sPath = _pluginPaths["NotepadExecutableDir"];
sPath.append(TEXT("\\")).append(NotepadAutoCompleteRootDir).append(str2wstr(LexerCatalogue::GetLexerName(0, true))).append(TEXT(".xml"));
#ifndef FORCE_INSTALL_PATHS
if (PathFileExists(sPath.c_str()))
#endif
_pluginPaths["PluginAutoCompleteFilePath"] = sPath;
}
if (!PathFileExists(str2wstr(_pluginPaths["NotepadOverrideMapFile"].string()).c_str()))
{
sPath = _pluginPaths["NotepadExecutableDir"];
sPath.append(TEXT("\\")).append(NotepadFunctionListRootDir).append(OverrideMapFile);
#ifndef FORCE_INSTALL_PATHS
if (PathFileExists(sPath.c_str()))
#endif
_pluginPaths["NotepadOverrideMapFile"] = sPath;
}
if (!PathFileExists(str2wstr(_pluginPaths["PluginFunctionListFile"].string()).c_str()))
{
sPath = _pluginPaths["NotepadExecutableDir"];
sPath.append(TEXT("\\")).append(NotepadFunctionListRootDir).append(str2wstr(LexerCatalogue::GetLexerName(0, true))).append(TEXT(".xml"));
#ifndef FORCE_INSTALL_PATHS
if (PathFileExists(sPath.c_str()))
#endif
_pluginPaths["PluginFunctionListFile"] = sPath;
}
}
// Setup Notepad++ and Scintilla handles and finish initializing the
// plugin's objects that need a Windows Handle to work
void Plugin::SetNotepadData(NppData& data)
{
_messageInstance.SetData(data);
_indentor.SetMessenger(_messageInstance);
//Keep a copy of Notepad HWND to easy access
_notepadHwnd = Messenger().GetNotepadHwnd();
// Create the configuration files paths
MakePluginFilePaths();
// Create settings instance and load all values
_settings.InitSettings(str2wstr(_pluginPaths["PluginSettingsFile"].string()));
Settings().Load();
// Pick current Notepad++ version and compares with Settings, to se whether user gets a new
// Dark Mode install attempt or not...
VersionInfoEx currentNotepad = VersionInfoEx(_pluginPaths["NotepadInstallExecutablePath"]);
if (Settings().notepadVersion.empty())
Settings().notepadVersion = currentNotepad;
if (Settings().notepadVersion < currentNotepad)
{
Settings().darkThemeInstallAttempt = false;
Settings().notepadVersion = currentNotepad;
}
if (currentNotepad > "8.4")
_NppSupportDarkModeMessages = true;
// Adjust menu "Use Auto-Indentation" checked or not before creation
pluginFunctions[PLUGINMENU_SWITCHAUTOINDENT]._init2Check = Settings().enableAutoIndentation;
// Points the compiler to our global settings
_compiler.appendSettings(&_settings);
// Initializes the compiler log window
InitCompilerLogWindow();
// Check engine objects file
CheckupPluginObjectFiles();
// The rest of initialization processes on Notepad++ SCI message NPPN_READY.
}
// Initializes the compiler log window
void Plugin::InitCompilerLogWindow()
{
_dockingData = {};
// Register settings class to the window
_loggerWindow->appendSettings(&_settings);
// additional info
_loggerWindow->init(DllHModule(), NotepadHwnd());
_loggerWindow->create(&_dockingData);
_dockingData.uMask = DWS_DF_CONT_BOTTOM | DWS_ICONTAB | DWS_ADDINFO;
_dockingIcon = loadSVGFromResourceIcon(DllHModule(), IDI_NEVERWINTERAPP, false, 32, 32);
_dockingData.hIconTab = _dockingIcon;
_dockingData.pszModuleName = _pluginFileName.c_str();
_dockingData.dlgID = 0;
// Register the dialog box with Notepad++
Messenger().SendNppMessage<void>(NPPM_DMMREGASDCKDLG, 0, (LPARAM)&_dockingData);
if (_settings.compilerWindowVisible == false)
DisplayCompilerLogWindow(false);
// Allow modifications on compilerWindowVisible status (because Notepad++ internally calls show
// window on creation and that would change the status of compilerWindowVisible unintentionally)
_settings.compilerWindowVisibleAllowChange = true;
// Set the compiler log callback
_compiler.setLoggerMessageCallback(WriteToCompilerLog);
// Set the compiler log navigate callback (from errors list to main window)
_loggerWindow->SetNavigateFunctionCallback(NavigateToCode);
}
// Display / Hide the compiler log window
void Plugin::DisplayCompilerLogWindow(bool toShow)
{
Instance()._loggerWindow->display(toShow);
SetFocus(Instance().Messenger().GetCurentScintillaHwnd());
}
// Check the files for known engine objects
void Plugin::CheckupPluginObjectFiles()
{
if (!PathFileExists(_pluginPaths["NWScriptEngineObjectsFile"].c_str()))
{
// Rant message to the user if we already installed the file and then it's gone.
if (Settings().installedEngineKnownObjects)
MessageBox(NotepadHwnd(), (TEXT("Could not find file:\r\n\r\n") + str2wstr(_pluginPaths["NWScriptEngineObjectsFile"].string()) + TEXT(".\r\n\r\n")
+ TEXT("This file is necessary for the NWScript Tools Plugin to know which objects are already imported.\r\n\r\n")
+ TEXT("Please DO NOT delete it or you may lose some of your imported languages keywords.\r\n\r\n")
+ TEXT("The file is going to be rebuilt with the default predefinitions now.")).c_str(),
TEXT("NWScript Tools - Warning"), MB_OK | MB_ICONEXCLAMATION);
// Load file from resources
auto hResource = FindResourceW(DllHModule(), MAKEINTRESOURCE(IDR_KNOWNOBJECTS), L"BIN");
size_t _size = SizeofResource(DllHModule(), hResource);
auto hMemory = LoadResource(DllHModule(), hResource);
LPVOID ptr = LockResource(hMemory);
std::string fileContents;
if (hMemory)
{
fileContents.assign((char*)hMemory, _size);
if (bufferToFile(_pluginPaths["NWScriptEngineObjectsFile"].c_str(), fileContents))
Settings().installedEngineKnownObjects = true;
else
MessageBox(NotepadHwnd(), (TEXT("Could not create file: ") + str2wstr(_pluginPaths["NWScriptEngineObjectsFile"].string()) + TEXT(".\r\n")
+ TEXT("The import definitions may not work properly. Please check for write permission on the folder and if the disk has enough space.")).c_str(),
TEXT("NWScript Tools - Error creating file"), MB_OK | MB_ICONERROR);
}
else
MessageBox(NotepadHwnd(), TEXT("Could not allocate memory for resource buffer. Please report this error to the \
plugin creator:\r\n File: PluginMain.cpp, function 'CheckupPluginObjectFiles()'"), TEXT("NWScript Plugin - Critical Error"), MB_OK | MB_ICONERROR);
FreeResource(hMemory);
// Retrieve statistics
_NWScriptParseResults = std::make_unique<NWScriptParser::ScriptParseResults>();
_NWScriptParseResults->SerializeFromFile(_pluginPaths["NWScriptEngineObjectsFile"].c_str());
Settings().engineStructs = _NWScriptParseResults->EngineStructuresCount;
Settings().engineFunctionCount = _NWScriptParseResults->FunctionsCount;
Settings().engineConstants = _NWScriptParseResults->ConstantsCount;
_NWScriptParseResults = nullptr;
}
// Create or restore the plugin XML configuration file
if (!PathFileExists(_pluginPaths["PluginLexerConfigFilePath"].c_str()))
{
// Create a dummy skeleton of the file from our resource string, so we don't crash...
// (the REAL file only installs if the user wants to)
// Set some Timestamp headers
char timestamp[128]; time_t currTime; struct tm currTimeP;
time(&currTime);
errno_t error = localtime_s(&currTimeP, &currTime);
strftime(timestamp, 64, "Creation timestamp is: %B %d, %Y - %R", &currTimeP);
std::string xmlHeaderComment = XMLDOCHEADER;
xmlHeaderComment.append(timestamp).append(".\r\n");
xmlHeaderComment = "<!--" + xmlHeaderComment + "-->\r\n";
std::string xmlConfigFile = xmlHeaderComment + XMLPLUGINLEXERCONFIG;
if (!bufferToFile(str2wstr(_pluginPaths["PluginLexerConfigFilePath"].string().c_str()), xmlConfigFile))
return; // End of the line, plugin will crash...
// Set an offer to install the plugin's definitions for the user later, when initialization finishes
// So we can get a pretty screen up. This is a one-time offer, since next time the user will have NWScript-Npp.xml already created
_OneTimeOffer = true;
}
}
// Properly detects if dark mode is enabled (Notepad++ 8.3.4 and above)
void Plugin::RefreshDarkMode(bool ForceUseDark, bool UseDark)
{
// Initialization
if (!PluginDarkMode::isInitialized())
PluginDarkMode::initDarkMode();
bool isDarkModeEnabled = false;
// Legacy support
if (ForceUseDark)
isDarkModeEnabled = UseDark;
// Non-legacy support
if (_NppSupportDarkModeMessages && !ForceUseDark)
{
isDarkModeEnabled = Messenger().SendNppMessage<bool>(NPPM_ISDARKMODEENABLED);
PluginDarkMode::Colors newColors;
bool bSuccess = Messenger().SendNppMessage<bool>(NPPM_GETDARKMODECOLORS, sizeof(newColors), reinterpret_cast<LPARAM>(&newColors));
if (bSuccess)
{
PluginDarkMode::setDarkTone(PluginDarkMode::ColorTone::customizedTone);
PluginDarkMode::changeCustomTheme(newColors);
// We override link colors
PluginDarkMode::setLinkTextColor(HEXRGB(0xFFC000));
}
else
CheckDarkModeLegacy();
}
// Set Dark Mode for window/application
PluginDarkMode::setDarkMode(isDarkModeEnabled, true);
// Rebuild menu
SetupPluginMenuItems();
// Refresh persistent dialogs dark mode
_loggerWindow->refreshDarkMode();
_aboutDialog->refreshDarkMode();
}
// Check Dark Mode for Legacy Notepad++ versions
void Plugin::CheckDarkModeLegacy()
{
tinyxml2::XMLDocument nppConfig;
fs::path notepadPath = _pluginPaths["NotepadConfigFile"];
int success = nppConfig.LoadFile(notepadPath.string().c_str());
if (success != 0)
return;
tinyxml2::XMLElement* GUIConfig = searchElement(nppConfig.RootElement(), "GUIConfig", "name", "DarkMode");
if (!GUIConfig)
return;
if (strcmp(GUIConfig->FindAttribute("enable")->Value(), "yes") == 0)
{
PluginDarkMode::initDarkMode();
PluginDarkMode::Colors colors;
colors.background = stoi(GUIConfig->FindAttribute("customColorTop")->Value());
colors.darkerText = stoi(GUIConfig->FindAttribute("customColorDarkText")->Value());
colors.disabledText = stoi(GUIConfig->FindAttribute("customColorDisabledText")->Value());
colors.edge = stoi(GUIConfig->FindAttribute("customColorEdge")->Value());
colors.errorBackground = stoi(GUIConfig->FindAttribute("customColorError")->Value());
colors.hotBackground = stoi(GUIConfig->FindAttribute("customColorMenuHotTrack")->Value());
colors.linkText = stoi(GUIConfig->FindAttribute("customColorLinkText")->Value());
colors.pureBackground = stoi(GUIConfig->FindAttribute("customColorMain")->Value());
colors.softerBackground = stoi(GUIConfig->FindAttribute("customColorActive")->Value());
colors.text = stoi(GUIConfig->FindAttribute("customColorText")->Value());
PluginDarkMode::ColorTone C = static_cast<PluginDarkMode::ColorTone>(stoi(GUIConfig->FindAttribute("colorTone")->Value()));
PluginDarkMode::changeCustomTheme(colors);
PluginDarkMode::setDarkTone(C);
// We override link colors
PluginDarkMode::setLinkTextColor(HEXRGB(0xFFC000));
Instance().RefreshDarkMode(true, true);
}
else
Instance().RefreshDarkMode(true, false);
}
#pragma endregion Plugin DLL Initialization
#pragma region
// Processes Raw messages from a Notepad++ window (the ones not handled by editor).
LRESULT Plugin::ProcessMessagesNpp(UINT Message, WPARAM wParam, LPARAM lParam)
{
return TRUE;
}
// Processes all Notepad++ and Scintilla messages. Newer versions of Notepad++ will
// send messages through this function.
void Plugin::ProcessMessagesSci(SCNotification* notifyCode)
{
switch (notifyCode->nmhdr.code)
{
case NPPN_READY:
{
// Do Initialization procedures.
// Note: The ORDER that many operations are performed here is important, so avoid changing it.
// We do this as first step, beause we are removing dash items that cannot be made by ID, so we need
// the proper position to be present. If we remove any other menus later, the positions might change a lot.
RemoveUnusedMenuItems();
// Starting from second step, these can be done on any order
SetAutoIndentSupport();
LoadNotepadLexer();
// Detects Dark Theme installation and possibly setup a hook to auto-install it.
// This step must be performed before SetupPluginMenuItems because that function depends on the detection
// of Dark Theme to properly setup the icons for menu. If an auto-update is to happen, this function
// causes the initialization process to cancel, so we preserve the restart hooks set in it.
if (DetectDarkThemeInstall() == RestartMode::Admin)
return;
// Check OverrideMap.xml and (possibly) auto-patch it - if other plugins had overwritten it.
if (CheckAndPatchOverrideMapXMLFile())
RemovePluginMenuItem(PLUGINMENU_REPAIRXMLASSOCIATION);
// Initially disable run last batch (until the user runs a batch in session)
EnablePluginMenuItem(PLUGINMENU_RUNLASTBATCH, false);
// Detects Dark Mode support. Can be done by messaging Notepad++ for newer versions
// Or if the messages aren't supported (on a previous version), checks the installation on Notepad++ config.xml.
// Note: Dark Mode is different from Dark Theme - 1st is for the plugin GUI, the second is for NWScript file lexing.
if (_NppSupportDarkModeMessages)
RefreshDarkMode();
else
CheckDarkModeLegacy();
// Setup the rest of menu items. RefreshDarkMode checkups can build the menu icons for us by
// calling SetupPluginMenuItems internally when DarkMode is present for legacy version (CheckDarkModeLegacy)
// and always for the other (RefreshDarkMode), so we check if no icons were set before to avoid double loading
if (_menuBitmaps.size() == 0)
SetupPluginMenuItems();
// Checks the one-time offer to install the additional XML files when the plugin first initializes
// (or when NWScript-Npp.xml file was not present at initialization)
if (_OneTimeOffer)
InstallAdditionalFiles();
if (_OneTimeOfferAccepted)
return;
// Auto call a function that required restart during the previous session (because of privilege elevation)
// Up to now...
// 1 = ImportDefinitions
// 2 = Fix Editor's Colors
// 3 = Repair OverrideMap file
// 4 = Install Additional
// Since all functions that required restart must have returned in Admin Mode, we check this
// to see if the user didn't cancel the UAC request.
if (IsUserAnAdmin())
{
switch (Settings().notepadRestartFunction)
{
case RestartFunctionHook::ResetUserTokensPhase1:
if (DoResetUserTokens(Settings().notepadRestartFunction) != RestartMode::None)
return;
break;
case RestartFunctionHook::ResetEditorColorsPhase1:
if (DoResetEditorColors(Settings().notepadRestartFunction) != RestartMode::None)
return;
break;
case RestartFunctionHook::InstallDarkModePhase1:
if (DoInstallDarkTheme(Settings().notepadRestartFunction) != RestartMode::None)
return;
break;
case RestartFunctionHook::RepairOverrideMapPhase1:
if (DoRepairOverrideMap(Settings().notepadRestartFunction) != RestartMode::None)
return;
break;
case RestartFunctionHook::InstallAdditionalFilesPhase1:
if (DoInstallAdditionalFiles(Settings().notepadRestartFunction) != RestartMode::None)
return;
break;
}
}
// If it makes it here, make sure to clear the hooks, temp files, etc.
// This must always execute after processing the hooks.
SetRestartHook(RestartMode::None, RestartFunctionHook::None);
// Mark plugin ready to use. Last step on the initialization chain
_isReady = true;
break;
}
case NPPN_CANCELSHUTDOWN:
{
// We're clearing any attempt to hook restarts here, have they been setup or not
SetRestartHook(RestartMode::None);
break;
}
case NPPN_SHUTDOWN:
{
_isReady = false;
Settings().Save();
// If we have a restart hook setup, call out shell to execute it.
if (Settings().notepadRestartMode != RestartMode::None)
{
runProcess(Settings().notepadRestartMode == RestartMode::Admin ? true : false,
Instance()._pluginPaths["PseudoBatchRestartFile"].c_str());
}
break;
}
case NPPN_LANGCHANGED:
{
LoadNotepadLexer();
break;
}
case NPPN_BUFFERACTIVATED:
{
if (_isReady)
LoadNotepadLexer();
break;
}
case SCN_CHARADDED:
{
// Conditions to perform the Auto-Indent:
// - Notepad is in Ready state;
// - Current Language is set to one of the plugin supported langs
// - Notepad version doesn't yet support Extended AutoIndent functionality
if (_isReady && IsPluginLanguage() && _needPluginAutoIndent
&& Settings().enableAutoIndentation)
Indentor().IndentLine(static_cast<TCHAR>(notifyCode->ch));
break;
}
case NPPN_DARKMODECHANGED:
{
RefreshDarkMode();
break;
}
case NPPN_TBMODIFICATION:
{
_tbIcons[0].hToolbarBmp = loadSVGFromResource(DllHModule(), IDI_COMPILEFILE, false, _dpiManager.scaleX(16), _dpiManager.scaleY(16));
_tbIcons[0].hToolbarIcon = loadSVGFromResourceIcon(DllHModule(), IDI_COMPILEFILE, false, _dpiManager.scaleX(16), _dpiManager.scaleY(16));
_tbIcons[0].hToolbarIconDarkMode = loadSVGFromResourceIcon(DllHModule(), IDI_COMPILEFILE, true, _dpiManager.scaleX(16), _dpiManager.scaleY(16));
_tbIcons[1].hToolbarBmp = loadSVGFromResource(DllHModule(), IDI_COMPILEBATCH, false, _dpiManager.scaleX(16), _dpiManager.scaleY(16));
_tbIcons[1].hToolbarIcon = loadSVGFromResourceIcon(DllHModule(), IDI_COMPILEBATCH, false, _dpiManager.scaleX(16), _dpiManager.scaleY(16));
_tbIcons[1].hToolbarIconDarkMode = loadSVGFromResourceIcon(DllHModule(), IDI_COMPILEBATCH, true, _dpiManager.scaleX(16), _dpiManager.scaleY(16));
_tbIcons[2].hToolbarBmp = loadSVGFromResource(DllHModule(), IDI_NEVERWINTERAPP, false, _dpiManager.scaleX(16), _dpiManager.scaleY(16));
_tbIcons[2].hToolbarIcon = loadSVGFromResourceIcon(DllHModule(), IDI_NEVERWINTERAPP, false, _dpiManager.scaleX(16), _dpiManager.scaleY(16));
_tbIcons[2].hToolbarIconDarkMode = loadSVGFromResourceIcon(DllHModule(), IDI_NEVERWINTERAPP, true, _dpiManager.scaleX(16), _dpiManager.scaleY(16));
Messenger().SendNppMessage<void>(NPPM_ADDTOOLBARICON_FORDARKMODE,
pluginFunctions[PLUGINMENU_COMPILESCRIPT]._cmdID, reinterpret_cast<LPARAM>(&_tbIcons[0]));
Messenger().SendNppMessage<void>(NPPM_ADDTOOLBARICON_FORDARKMODE,
pluginFunctions[PLUGINMENU_BATCHPROCESSING]._cmdID, reinterpret_cast<LPARAM>(&_tbIcons[1]));
Messenger().SendNppMessage<void>(NPPM_ADDTOOLBARICON_FORDARKMODE,
pluginFunctions[PLUGINMENU_SHOWCONSOLE]._cmdID, reinterpret_cast<LPARAM>(&_tbIcons[2]));
}
}
}
// Setup a restart Hook. Normal or Admin mode. This function saves settings immediately.
void Plugin::SetRestartHook(RestartMode type, RestartFunctionHook function)
{
Settings().notepadRestartMode = type; Settings().notepadRestartFunction = function;
if (type == RestartMode::None)
{
if (PathFileExists(_pluginPaths["PseudoBatchRestartFile"].c_str()))
DeleteFile(_pluginPaths["PseudoBatchRestartFile"].c_str());
}
else
writePseudoBatchExecute(_pluginPaths["PseudoBatchRestartFile"].c_str(), _pluginPaths["NotepadInstallExecutablePath"].c_str());
Settings().Save();
}
#pragma endregion Plugin DLL Message Processing
#pragma region
// Set the Auto-Indent type for all of this Plugin's installed languages
void Plugin::SetAutoIndentSupport()
{
PluginMessenger& msg = Messenger();
bool lexerSearch = false;
ExternalLexerAutoIndentMode langIndent = ExternalLexerAutoIndentMode::Standard;
// Try to set Plugin's Lexers Auto-Indentation. Older versions of NPP will return langSearch=FALSE (cause this message won't exist).
generic_stringstream lexerNameW;
for (int i = 0; i < LexerCatalogue::GetLexerCount(); i++)
{
lexerNameW = {};
lexerNameW << str2wstr(LexerCatalogue::GetLexerName(i));
lexerSearch = msg.SendNppMessage<int>(NPPM_GETEXTERNALLEXERAUTOINDENTMODE,
reinterpret_cast<WPARAM>(lexerNameW.str().c_str()), reinterpret_cast<LPARAM>(&langIndent));
if (lexerSearch)
{
if (langIndent != ExternalLexerAutoIndentMode::C_Like)
{
bool success = msg.SendNppMessage<bool>(NPPM_SETEXTERNALLEXERAUTOINDENTMODE,
reinterpret_cast<WPARAM>(lexerNameW.str().c_str()), static_cast<LPARAM>(ExternalLexerAutoIndentMode::C_Like));
// We got a problem here. Our procedure SHOULD succeed in setting this.
if (!success)
{
generic_stringstream sWarning = {};
sWarning << TEXT("Warning: failed to set Auto-Indentation for language [") << lexerNameW.str().c_str() << "].";
MessageBox(_notepadHwnd, reinterpret_cast<LPCWSTR>(sWarning.str().c_str()),
pluginName.c_str(), MB_OK | MB_ICONWARNING | MB_APPLMODAL);
return;
}
}
}
}
// lexerSearch will be TRUE if Notepad++ support NPPM_GETEXTERNALExternalLexerAutoIndentMode message
if (lexerSearch)
{
Instance()._needPluginAutoIndent = false;
#ifndef DEBUG_AUTO_INDENT_833
// Auto-adjust the settings
Instance().Settings().enableAutoIndentation = false;
#endif
}
else
Instance()._needPluginAutoIndent = true;
}
// Get Current notepad lexer language;
void Plugin::LoadNotepadLexer()
{
PluginMessenger& msg = Messenger();
bool lexerSearch = FALSE;
bool isPluginLanguage = FALSE;
int currLang = 0;
ExternalLexerAutoIndentMode langIndent = ExternalLexerAutoIndentMode::Standard;
msg.SendNppMessage<>(NPPM_GETCURRENTLANGTYPE, 0, (LPARAM)&currLang);
// First call: retrieve buffer size. Second call, fill up name (from Manual).
int buffSize = msg.SendNppMessage<int>(NPPM_GETLANGUAGENAME, currLang, reinterpret_cast<LPARAM>(nullptr));
std::unique_ptr<TCHAR[]> lexerName = make_unique<TCHAR[]>(buffSize + 1);
buffSize = msg.SendNppMessage<int>(NPPM_GETLANGUAGENAME, currLang, reinterpret_cast<LPARAM>(lexerName.get()));
// Try to get Language Auto-Indentation if it's one of the plugin installed languages
generic_string lexerNameW;
for (int i = 0; i < LexerCatalogue::GetLexerCount() && lexerSearch == false; i++)
{
lexerNameW = str2wstr(LexerCatalogue::GetLexerName(i));
isPluginLanguage = (_tcscmp(lexerName.get(), lexerNameW.c_str()) == 0);
if (isPluginLanguage)
{
lexerSearch = msg.SendNppMessage<int>(NPPM_GETEXTERNALLEXERAUTOINDENTMODE,
reinterpret_cast<WPARAM>(lexerNameW.c_str()), reinterpret_cast<LPARAM>(&langIndent));
}
}
//Update Lexer
_notepadCurrentLexer.SetLexer(currLang, lexerName.get(), isPluginLanguage, langIndent);
}
// Detects if Dark Theme is already installed
RestartMode Plugin::DetectDarkThemeInstall()
{
// Here we are parsing the file silently
tinyxml2::XMLDocument darkThemeDoc;
errno_t error = darkThemeDoc.LoadFile(wstr2str(Instance()._pluginPaths["NotepadDarkThemeFilePath"].c_str()).c_str());
if (error)
{
RemovePluginMenuItem(PLUGINMENU_INSTALLDARKTHEME);
_pluginDarkThemeIs = DarkThemeStatus::Unsupported;
return RestartMode::None;
}
// Try to navigate to the XML node corresponding to the name of the installed language (nwscript). If not found, means uninstalled.
if (!searchElement(darkThemeDoc.RootElement(), "LexerType", "name", LexerCatalogue::GetLexerName(0)))
_pluginDarkThemeIs = DarkThemeStatus::Uninstalled;
else
{
//Dark theme installed, mark it here.
RemovePluginMenuItem(PLUGINMENU_INSTALLDARKTHEME);
_pluginDarkThemeIs = DarkThemeStatus::Installed;
}
// Auto-reinstall if a previous installation existed and support for it is enabled...
if (_pluginDarkThemeIs == DarkThemeStatus::Uninstalled && _settings.darkThemePreviouslyInstalled && _settings.autoInstallDarkTheme)
{
// Check to see if we already attempted a reinstall before, so we won't loop...
if (_settings.darkThemeInstallAttempt)
return RestartMode::None;
// Set to true... this will only be reset upon a successfull installation.
_settings.darkThemeInstallAttempt = true;
// Check files permissions (redirect result to ignore since we already know file exists)...
PathWritePermission fPerm = PathWritePermission::UndeterminedError;
std::ignore = checkWritePermission(_pluginPaths["NotepadDarkThemeFilePath"], fPerm);
// Setup a restart hook...
if (fPerm == PathWritePermission::RequiresAdminPrivileges && !IsUserAnAdmin())
{
SetRestartHook(RestartMode::Admin, RestartFunctionHook::InstallDarkModePhase1);
Messenger().SendNppMessage(WM_CLOSE, 0, 0);
return RestartMode::Admin;
}
else
// Since we've got perms... call the install function right on...
DoInstallDarkTheme(RestartFunctionHook::InstallDarkModePhase1);
return RestartMode::None;
}
// Mark it was previously installed
if (_pluginDarkThemeIs == DarkThemeStatus::Installed && !_settings.darkThemePreviouslyInstalled)
_settings.darkThemePreviouslyInstalled = true;
return RestartMode::None;
}
int Plugin::FindPluginLangID()
{
TCHAR lexerName[64] = { 0 };
for (int i = 85; i < 115; i++) // Language names start at 85 and supports up to 30 custom langs
{
Messenger().SendNppMessage<void>(NPPM_GETLANGUAGENAME, i, reinterpret_cast<LPARAM>(lexerName));
for (int j = 0; j < LexerCatalogue::GetLexerCount(); j++)
{
if (_tcscmp(lexerName, str2wstr(LexerCatalogue::GetLexerName(j)).c_str()) == 0)
return i;
}
}
return 0;
}
// Look for our language menu item among installed external languages and call it
void Plugin::SetNotepadToPluginLexer()
{
MENUITEMINFO menuInfo = {};
HMENU currentMenu = GetNppMainMenu();
generic_string menuItemName;
generic_string lexerName = str2wstr(LexerCatalogue::GetLexerName(0));
TCHAR szString[256] = {};
int commandID = -1;
// Search for name inside given external language names
for (int i = IDM_LANG_EXTERNAL; i < IDM_LANG_EXTERNAL_LIMIT; i++)
{
ZeroMemory(szString, sizeof(szString));
menuInfo.cch = 256;
menuInfo.fMask = MIIM_TYPE;
menuInfo.fType = MFT_STRING;
menuInfo.cbSize = sizeof(MENUITEMINFO);
menuInfo.dwTypeData = szString;
bool bSuccess = GetMenuItemInfo(currentMenu, i, MF_BYCOMMAND, &menuInfo);
menuItemName = szString;
if (menuItemName == lexerName)
{
commandID = i;
break;
}
}
// Dispatch command.
if (commandID > -1)
Messenger().SendNppMessage<void>(NPPM_MENUCOMMAND, 0, commandID);
}
// Handling functions for plugin menu
#pragma region
#define NOTEPADPLUS_USER_INTERNAL (WM_USER + 0000)
#define NPPM_INTERNAL_GETMENU (NOTEPADPLUS_USER_INTERNAL + 14)
HMENU Plugin::GetNppMainMenu()
{
PluginMessenger& pMsg = Messenger();
HMENU hMenu;
// Notepad++ ver > 6.3
hMenu = reinterpret_cast<HMENU>(pMsg.SendNppMessage<LRESULT>(NPPM_GETMENUHANDLE, 1, 0));
if (hMenu && IsMenu(hMenu))
return hMenu;
// Notepad++ ver <= 6.3
hMenu = reinterpret_cast<HMENU>(pMsg.SendNppMessage<LRESULT>(NPPM_INTERNAL_GETMENU, 0, 0));
if (hMenu && IsMenu(hMenu))
return hMenu;
return ::GetMenu(Instance().NotepadHwnd());
}
bool Plugin::IsPluginMenuItemEnabled(int ID)
{
HMENU hMenu = GetNppMainMenu();
if (hMenu)
{
UINT state = GetMenuState(hMenu, GetFunctions()[ID]._cmdID, MF_BYCOMMAND);
return state == MF_DISABLED || state == MF_GRAYED ? false : true;
}