Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[cocoa] Keyboard shortcuts: copy, paste, etc #796

Closed
samschott opened this issue Jan 23, 2020 · 9 comments · Fixed by #1291
Closed

[cocoa] Keyboard shortcuts: copy, paste, etc #796

samschott opened this issue Jan 23, 2020 · 9 comments · Fixed by #1291
Labels
enhancement New features, or improvements to existing features. not quite right The idea or PR has been reviewed, but more work is needed.

Comments

@samschott
Copy link
Member

Expected Behavior

One would typically expect from an app on macOS that common keyboard shortcuts such as CMD+C, CMD+V, etc "just work". However, they don't on widgets such as TextInput.

Current Behavior

Keyboard events handled by a widget itself do work (e.g., arrow keys in a TreeView, backspace in a TextInput). However, globally defined shortcuts such as copy and paste do not work.

Possible fixes

  1. This is typically handled in a NSApplication by adding the respective menu bar items with selectors (e.g., SEL('copy:') for CMD+C). This is what users on macOS expect, but it has two disadvantages:

    • It requires always having those menu entries. But some use cases work better without a menu bar, such as a system tray app which occasionally shows a window.
    • When we add those items to the menu by default, they will be always enabled, unless the menu has set autoenablesItems = True. In the latter case, they will be automatically enabled/disabled when the action becomes available, e.g., 'Minimise' (CMD+M) will only be enabled when the window supports being minimised. However, setting autoenablesItems = True means that all user defined items will always be enabled (in the current app implementation).
  2. One could intercepting the key events in the NSApplication delegate. This has the disadvantage that the default menu bar items will not be visible unless explicitly added by the user. However, the commands will always work, regardless of the chosen menu bar configuration.

@freakboy3742
Copy link
Member

I agreed that cut/copy/paste/paste-special should be "available by default" commands in the same way "About", "Preferences", "Quit" and "Visit homepage" are on "normal" apps; and that these commands should be hooked into text widgets (and anywhere else that makes sense).

I also agree these options shouldn't exist on system tray apps. Supporting system tray apps is a long standing feature request (#97). They have completely different UI/UX requirements to a "standard" app - and I'd argue that the way we should accomodate this is a different base class for systray apps. If you're building a systray app, it's not a toga.App with a couple of different flags passed to the constructor - it's a toga.TrayApp (or something similar). We've already got a DocumentApp specifically for this sort of purpose - recognizing that an app that is focused on handling documents is different to an app that provides a control interface.

There may even be a need for several different base classes, differentiating between a system tray app that is little more than a menu (e.g., the battery status), and an app that provides a custom window (e.g., Dropbox). This would also allow for the existence of the copy/paste commands, without necessarily adding the command to the menu.

@samschott
Copy link
Member Author

Yes, I think having a separate base class for system tray apps makes a lot of sense. I have already outlined a rough API and Cocoa implementation for a system tray (icon + menu) but one issue I stumbled upon is the current API for menus and menu items. This also ties in to supporting Copy / Paste commands:

Currently, there is toga.Command which is used both for menubar items and toolbar buttons. But the use cases are sufficiently different that the may deserve separate API's:

  • It is common to associate keyboard commands with menu(bar) items but not with toolbar buttons.
  • Menu bar items are often enabled depending on the context (e.g., undo command, select all), toolbar buttons are almost always enabled.
  • An API for menu items can be shared between context menus, system tray menus and menu bars. This generalisation does not hold for Commands.

On the Implementation layer, toga.Command currently does not have a native layer in Cocoa. Instead, the native instances are created and retained by the App during initialisation. This makes it difficult to implement enabling / disabling individual menu bar items while keeping other items in the same menu 'automatic'.

@samschott
Copy link
Member Author

@freakboy3742, I have created a sample implementation for catching keyboard commands here.

For now, this just subclasses NSApplication and does not add any menubar items. This is the much easier fix since it does not require changing the code to auto enable menubar items. And it will be useful for a future system tray application.

If you are happy with this as a temporary solution, I can create a PR. Otherwise, keyboard shortcuts will need to wait until I (or someone else) finds some more time...

@freakboy3742
Copy link
Member

@samschott It sounds like the purpose of Command isn't clear (and that's entirely my fault - we need a lot more documentation, and the existing code has some less-than-ideal implementation details).

The idea of Command is to wrap a "thing" that you can do in a GUI. That "thing" can surface in many ways in a GUI - as a menu item, as a toolbar item, or as some other action in a GUI (pressing a button in the normal layout of the app).

The key abstraction is that command can be active or inactive - but the active status of the Command should drive the enabled/disabled status of the widgets that expose it. If the command is disabled, then any control activating that action should also be disabled. You shouldn't need to remember that you have to disable both the menu item and the toolbar item.

In the current implementation, I've assumed that all commands manifest as a menu item. The intention is to aid discoverability; menus showing that something can be done are a key way for end-users to discover the capabilities of an app.

Looking at the Cocoa implementation of Command - there's no default native object; but it will accumulate native objects as the command is deployed (e.g., as a menu, as a toolbar etc).

Within this framework, I'd argue that Cut, Copy and Paste are excellent examples of behaviors that should be wrapped as commands, and installed as part of the construction of a toga.App (I'll also note that the current backends all independently define the core application commands like About, Help and Quit; these should be defined at the interface level, since they're common to all apps - only the command grouping and shortcuts are platform specific). They'll acquire their app shortcuts by virtue of being installed as menu items in a standard app; once we start looking at system tray apps, we can investigate the best way to attach those keyboard bindings.

Does that make any sense?

@samschott
Copy link
Member Author

That does clarify the concept of a Command. At the moment they can only be used in the menu bar and toolbar, right? Looking through the docs, I have found no way of attaching a Command to a regular button...

In the current implementation, I've assumed that all commands manifest as a menu item.

You have also assumed, as far as I can see, that every menu item is a command and therefore omitted a separate API for menu items. This is valid in most cases, but there are some exceptions to this (submenus, 'widgets' such as the help search field on macOS, etc). But this is digressing form the original issue...

Within this framework, I'd argue that Cut, Copy and Paste are excellent examples of behaviors that should be wrapped as commands, and installed as part of the construction of a toga.App

Agreed!

@freakboy3742
Copy link
Member

At the moment they can only be used in the menu bar and toolbar, right? Looking through the docs, I have found no way of attaching a Command to a regular button...

Correct. Chalk this up as one more thing on the "want to add" list :-)

More broadly, there's a design pattern that I've used with Tree and Table that I want to use more broadly - the concept of separating the "data" from the underlying widget.

In the simple case, you can give a List widget a list of tuples, and it will display them. However, in a more realistic example, the list will be backed by some actual data source, and if that data changes, any visualization of the data should also change. The natural way to store the underlying data might not be a Python list, either - it might be an API call or something similar. Toga handles this with the ListSource API - you can implement the data source however you want, as long as it adheres to the base API; and if the data in the source changes, the widget updates itself.

Although I've only implemented this for List, Tree and DetailedList, the same relationship exists on much simpler inputs, too. For example, a text input will usually display some simple name/value; if the underlying name/value changes, then the contents of the text input should change, too. Even a simple Label will sometimes be a reflection of some data state. And in the case of widgets that have "on activation" actions, the enabled/disabled status of the button can be dependent on whether the underlying action is actually possible at any given point in time.

In the current implementation, I've assumed that all commands manifest as a menu item.

You have also assumed, as far as I can see, that every menu item is a command and therefore omitted a separate API for menu items. This is valid in most cases, but there are some exceptions to this (submenus, 'widgets' such as the help search field on macOS, etc). But this is digressing form the original issue...

This is technically true, but misses the significance of UI discoverability. My underlying argument is that anything that can be done should be visible in the menu.

It should be possible to support submenus by defining commands that have subcommands, or with a sub-grouping

That said - I'm willing to entertain the idea that maybe we need to change the Command API to allow for explicit menu creation (and ordering/grouping).

However, in any API change, there are 3 key features I want to preserve:

  1. Avoiding the need for end-users to define menu items tied to key application capabilities.Every application needs a Quit, About, Cut, Copy, Paste, and Visit Website option; most will need a Preferences or Help item; and document-based apps will need Open/Save, etc. End users shouldn't need to define these commands - they should "just exist".

  2. Avoiding the need for end-users to specifically position key application menu items. For example - where does the Quit menu item appear? On macOS, it's under the app menu; on Windows, it's at the bottom of the File menu. But every application needs a Quit item. The end user shouldn't need to write code defining where this item appears - it should always be in the platform-appropriate place.

  3. It should be easy (and obvious) that defining a single command is possible, and that a command-based approach is preferable to defining a menu item, a toolbar item, and a button handler, and cross-coordinating between them all to ensure that enabled/disable state is accurately represented.

Both of these points are digressions, but hopefully they're digressions that illustrate the underlying idea I'm aiming at, and might provide some guidance about how we can add cut/copy/paste in a way that will fit the overall picture (and possibly even move other pieces of the bigger picture in the right direction).

@freakboy3742 freakboy3742 added enhancement New features, or improvements to existing features. not quite right The idea or PR has been reviewed, but more work is needed. up-for-grabs labels Apr 25, 2020
@whateverforever
Copy link

Is there any update on this? My app needs copy paste to work, but I'm not sure how to use SEL('copy:'). I can see it is a toga_cocoa import, but if I use that, doesn't that break compatibility with windows and linux?

@freakboy3742
Copy link
Member

@whateverforever This is a volunteer-driven project; we can't really offer any estimates for when a any given bug will be fixed, or feature implemented, because it's entirely dependent on contributions provided by the community.

You're correct in your understanding about using SEL('copy:') though - if you import and use that call, the code will become macOS specific, and won't run on Windows and Linux.

@trankov
Copy link

trankov commented Jun 27, 2021

Well, but finally, please, where can I find information about how to implement Copy/Paste to TextInput? All I want just when user press Cmd-V in focused text field the clipboard text has been pasted to it. But I cannot find the description. Does it support 'on key press' event or something like this? I don't know where to read this. Please, can somebody help me and answer? Thanks in advance!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New features, or improvements to existing features. not quite right The idea or PR has been reviewed, but more work is needed.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants