@@ -5213,6 +5213,89 @@ void cmdJumpToTime(Command* command) {
5213
5213
}, 50 );
5214
5214
}
5215
5215
5216
+ void cmdVirtualMidiKeyboard (Command* command) {
5217
+ static accelerator_register_t accelReg = {0 };
5218
+ if (GetToggleCommandState (command->gaccel .accel .cmd )) {
5219
+ // The virtual keyboard is showing. Run the command to hide it.
5220
+ Main_OnCommand (command->gaccel .accel .cmd , 0 );
5221
+ return ;
5222
+ }
5223
+ const bool isRegistered = !!accelReg.translateAccel ;
5224
+ accelReg.translateAccel = [](MSG* msg, accelerator_register_t * accelReg) -> int {
5225
+ HWND dialog = (HWND)accelReg->user ;
5226
+ if (dialog && !IsWindow (dialog)) {
5227
+ // The dialog is dead. Ideally, we would unregister. However, if we do,
5228
+ // subsequent registrations of our hook never intercept key presses in the
5229
+ // virtual keyboard. So, we just have to leave this registered.
5230
+ accelReg->user = nullptr ;
5231
+ return 0 ; // Normal handling.
5232
+ }
5233
+ if (msg->message != WM_KEYDOWN ||
5234
+ (msg->hwnd != dialog && GetParent (msg->hwnd ) != dialog)) {
5235
+ // This key isn't for us.
5236
+ return 0 ;
5237
+ }
5238
+ if (isClassName (msg->hwnd , " Edit" ) || isClassName (msg->hwnd , " ComboBox" )) {
5239
+ // Don't trap arrow keys in these controls.
5240
+ return 0 ;
5241
+ }
5242
+ if (isShortcutHelpEnabled) {
5243
+ switch (msg->wParam ) {
5244
+ case VK_RIGHT:
5245
+ outputMessage (translate (" Octave up" ));
5246
+ return 1 ; // Eat the key.
5247
+ case VK_LEFT:
5248
+ outputMessage (translate (" Octave down" ));
5249
+ return 1 ;
5250
+ case VK_UP:
5251
+ outputMessage (translate (" Increase MIDI channel" ));
5252
+ return 1 ;
5253
+ case VK_DOWN:
5254
+ outputMessage (translate (" Decrease MIDI channel" ));
5255
+ return 1 ;
5256
+ default :
5257
+ break ;
5258
+ }
5259
+ return 0 ;
5260
+ }
5261
+ switch (msg->wParam ) {
5262
+ case VK_RIGHT:
5263
+ case VK_LEFT:
5264
+ // We need to wait until this executes before we can report the new value.
5265
+ CallLater ([dialog] {
5266
+ constexpr long WCID_CENTER_NOTE = 1239 ;
5267
+ char note[10 ];
5268
+ if (GetDlgItemText (dialog, WCID_CENTER_NOTE, note, sizeof (note)) != 0 ) {
5269
+ outputMessage (note);
5270
+ }
5271
+ }, 0 );
5272
+ return 0 ;
5273
+ case VK_UP:
5274
+ case VK_DOWN:
5275
+ CallLater ([dialog] {
5276
+ constexpr long WCID_CHANNEL = 1377 ;
5277
+ char channel[10 ];
5278
+ if (GetDlgItemText (dialog, WCID_CHANNEL, channel, sizeof (channel)) != 0 ) {
5279
+ outputMessage (channel);
5280
+ }
5281
+ }, 0 );
5282
+ return 0 ;
5283
+ default :
5284
+ break ;
5285
+ }
5286
+ return 0 ;
5287
+ };
5288
+ accelReg.isLocal = true ;
5289
+ accelReg.user = nullptr ;
5290
+ if (!isRegistered) {
5291
+ // We must register the hook before the window appears or it won't work.
5292
+ plugin_register (" accelerator" , &accelReg);
5293
+ }
5294
+ // Show the window.
5295
+ Main_OnCommand (command->gaccel .accel .cmd , 0 );
5296
+ accelReg.user = GetForegroundWindow (); // The dialog.
5297
+ }
5298
+
5216
5299
#define DEFACCEL {0 , 0 , 0 }
5217
5300
5218
5301
Command COMMANDS[] = {
@@ -5316,6 +5399,7 @@ Command COMMANDS[] = {
5316
5399
{MAIN_SECTION, {{0 , 0 , 42207 }, nullptr }, nullptr , cmdAddAutoItems}, // Envelope: Convert all project automation to automation items
5317
5400
{MAIN_SECTION, {{0 , 0 , 42089 }, nullptr }, nullptr , cmdGlueAutoItems}, // Envelope: Glue automation items
5318
5401
{MAIN_SECTION, {{0 , 0 , 40069 }, nullptr }, nullptr , cmdJumpToTime}, // View: Jump (go) to time window
5402
+ {MAIN_SECTION, {{0 , 0 , 40377 }, nullptr }, nullptr , cmdVirtualMidiKeyboard}, // View: Show virtual MIDI keyboard
5319
5403
{MIDI_EDITOR_SECTION, {{0 , 0 , 40036 }, nullptr }, nullptr , cmdMidiMoveCursor}, // View: Go to start of file
5320
5404
{MIDI_EVENT_LIST_SECTION, {{0 , 0 , 40036 }, nullptr }, nullptr , cmdMidiMoveCursor}, // View: Go to start of file
5321
5405
{MIDI_EDITOR_SECTION, {{0 , 0 , 40037 }, nullptr }, nullptr , cmdMidiMoveCursor}, // View: Go to end of file
0 commit comments