Skip to content

wxGUI: Delete shortcut for the Layers pane and the Data Catalog - #8051

Draft
polucifier wants to merge 2 commits into
OSGeo:mainfrom
polucifier:gui-delete-shortcuts
Draft

polucifier wants to merge 2 commits into
OSGeo:mainfrom
polucifier:gui-delete-shortcuts

Conversation

@polucifier

Copy link
Copy Markdown
Contributor

Adds the Delete shortcut for removing items in the Data Catalog, and fixes the
Layers pane one, which has been in the code for the entire history of the
project but never worked.

This covers the delete shortcuts from #5425 that were suggested in #7423. The
Data Catalog and the Layers pane are in the same PR because both were asked for
and the changes are small and independent. They are kept in separate commits so
they are trivial to split if you prefer.

Data Catalog

keys selection action
Delete map (raster, vector, 3D raster) delete the map, after confirmation
Delete mapset, or several mapsets delete, after one confirmation
Delete project, or several projects delete, after confirmation
Delete GRASS database node remove it from the catalog only, the data stays on disk
Ctrl+Shift+Delete GRASS database node delete the database from disk

The conditions mirror the context menu items, including the ones that disable
them, so the shortcuts never remove more than the menus offer:

  • the database currently in use cannot be removed from the catalog
  • a map inside a space-time dataset is not touched, the menu only offers to
    unregister it
  • a mixed selection, or several database nodes, does nothing
  • a node being renamed is not deleted, the keys go to the label editor
  • nothing happens when no item is selected

Layers pane

LayerTree.OnKeyUp binds EVT_KEY_UP and dispatches WXK_DELETE to the layer
manager, and both are already present in the parent of
c730ae7e6,
the 2011-11-24 reorganisation that moved layertree.py to its current path,
gui/wxpython/lmgr/layertree.py. So nothing was ever missing from the handler,
it simply never ran.

Each layer item carries an options button, which is a child of the tree, and the
Delete key event is delivered to that button rather than to the tree, so a
binding on the tree never sees it. This does not depend on clicking the button:
selecting a layer row with the mouse and pressing Delete behaves exactly the
same, and in both cases the key never reaches LayerTree.OnKeyUp, so the
shortcut has never been usable from the Layers pane.

The shortcut is now bound on the button as well, through a
_createLayerButton() helper shared by AddLayer and RecreateItem so the two
call sites cannot drift apart. Removing a layer destroys the button which is
dispatching the event, so the removal is deferred with CallAfter, the same way
the button click handler already avoids destroying itself on some platforms.
event.Skip() is called on every path, so that the button keeps handling space
and enter itself.

Testing

Both panes were tested manually in the wxGUI, going through every selection
type listed above, including the cases that must be refused, several items
selected at once, a node being renamed, and Delete with nothing selected.

Not included

Space-time datasets are left out on purpose. The context menu offers both
delete dataset only and delete dataset and maps as separate items, and a
single key cannot choose between them. It would be good to get an opinion on how
this should behave before implementing it.

Related findings, deliberately left out of this PR

I didn't find an existing issue for any of these; they all came out of testing this change.

  • Arrow key navigation does not work in the Layers pane.
    CustomTreeCtrl.__init__ binds EVT_KEY_DOWN to self.OnKeyDown, which
    GRASS's LayerTree overrides with a bare event.Skip(), so the widget's own
    key handling never runs.
  • After a layer is removed, the options buttons of the remaining rows move next
    to the layer text instead of staying at the right edge of the row. This
    reproduces on the current release with two or more layers, both through the
    button's context menu and through the Delete shortcut.
  • _isCurrent() returns (True, True, True) unconditionally unless
    _restricted is set, and SetRestriction is never called, so
    item.Enable(currentMapset) is currently a no-op in the popup builders. The
    shortcut reproduces the same condition so that it matches the menu, but the
    underlying issue looks worth its own report.

Addresses the delete shortcuts proposed in #5425 for the Data Catalog and the
Layers pane.

@github-actions github-actions Bot added GUI wxGUI related Python Related code is in Python HTML Related code is in HTML docs markdown Related to markdown, markdown files labels Oct 11, 2026
Comment thread gui/wxpython/lmgr/layertree.py Outdated
rhea added 2 commits October 11, 2026 14:44
Each layer item carries an options button which is a child of the tree, and
it can take the keyboard focus when clicked. Key events do not propagate
from a child to its parent, so the Delete shortcut bound on the tree was
never invoked while such a button was focused, and the key was simply
discarded. Bind the same shortcut on the button.

Removing a layer destroys the button which is dispatching the event, so the
removal is deferred with CallAfter, the same way the button click handler
already avoids destroying itself on some platforms.
Bind the Delete key to the removal actions offered by the context menus
of the data catalog, and Ctrl+Shift+Delete to deleting a GRASS database
from disk. The conditions are the same as for the context menu items, so
that the shortcuts never remove more than the menus offer.

Removal is handled on key up rather than key down, so that holding the
key does not remove several items in a row. This matches the layer tree
and the graphical modeler.

Addresses the delete shortcuts proposed in
OSGeo#5425 for the Data Catalog.
@polucifier
polucifier force-pushed the gui-delete-shortcuts branch from ae3c04a to 09c77d4 Compare October 11, 2026 12:47
@petrasovaa

Copy link
Copy Markdown
Contributor

@saket0187 would you be able to review this?

@saket0187 saket0187 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks good in general. Here are a few suggestions:

  • Delete on a database node removes it from the catalog tree without any confirmation; if possible, please add one as for maps and mapsets.
  • Not for this PR: The Yes/No buttons are in different positions in the delete map and in other dialogs, and I deleted a mapset by mistake because of it :( , this is worth a follow-up PR.
  • About space-time datasets: maybe Delete = "Delete dataset only" (maps are kept, the safer choice) and Ctrl+Shift+Delete = "Delete dataset and maps", like the database case? Both already ask for confirmation.

Comment on lines +2217 to +2232

Nodes below the database in the tree populate the selected database as
well, so the node type has to be checked explicitly. Only a database
node itself can be removed.
"""
if (
self.mixed
or self.selected_layer[0]
or self.selected_stds[0]
or self.selected_mapset[0]
or self.selected_location[0]
or len(self.selected_grassdb) != 1
):
return None

return self.selected_grassdb[0]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional: since this only needs "exactly one item, and it's a database", it could check that directly.

Suggested change
Nodes below the database in the tree populate the selected database as
well, so the node type has to be checked explicitly. Only a database
node itself can be removed.
"""
if (
self.mixed
or self.selected_layer[0]
or self.selected_stds[0]
or self.selected_mapset[0]
or self.selected_location[0]
or len(self.selected_grassdb) != 1
):
return None
return self.selected_grassdb[0]
"""
selected = self.GetSelected()
if len(selected) == 1 and selected[0].data["type"] == "grassdb":
return selected[0]
return None


if self.selected_layer[0]:
_, _, currentMapset = self._isCurrent(gisenv())
return self.OnDeleteMap if currentMapset else None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not related to this PR, but this check has no effect: SetRestriction() is never called, so _restricted is always False and _isCurrent() always returns True. @petrasovaa should the restriction code be removed in a follow-up, or is it meant to come back?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove in follow up, and it doesn't need to be added here.

Comment on lines +2299 to +2309
if event.ControlDown() and event.ShiftDown():
# Deleting a database from disk cannot be undone by adding it
# back to the catalog, so it needs an extra modifier
if self._selectedGrassDb() and not self._restricted:
self.OnDeleteGrassDb(None)
return
else:
handler = self._getRemoveHandler()
if handler:
handler(None)
return

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if event.ControlDown() and event.ShiftDown():
# Deleting a database from disk cannot be undone by adding it
# back to the catalog, so it needs an extra modifier
if self._selectedGrassDb() and not self._restricted:
self.OnDeleteGrassDb(None)
return
else:
handler = self._getRemoveHandler()
if handler:
handler(None)
return
modifiers = event.GetModifiers()
if modifiers == wx.MOD_CONTROL | wx.MOD_SHIFT:
# Deleting a database from disk cannot be undone by adding it
# back to the catalog, so it needs an extra modifier
if self._selectedGrassDb() and not self._restricted:
self.OnDeleteGrassDb(None)
return
elif modifiers == wx.MOD_NONE:
handler = self._getRemoveHandler()
if handler:
handler(None)
return

Any Delete with other modifiers works (e.g. Shift+Delete) here. Maybe check the modifiers exactly with event.GetModifiers()?

Close current map display

Delete:
Delete selected layer

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Delete selected layer
Remove selected map layer(s)

the catalog, so it can be added back later. Nothing is removed when
several databases are selected or when items of different types are
selected together.</dd>
<dt>Ctrl+Shift+Delete</dt>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest removing this, it's rather rare and not expected.

@petrasovaa

petrasovaa commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor
  • About space-time datasets: maybe Delete = "Delete dataset only" (maps are kept, the safer choice) and Ctrl+Shift+Delete = "Delete dataset and maps", like the database case? Both already ask for confirmation.

I am not sure whether Ctrl+Shift+Delete is all that useful, user would have to know about it, while simple delete is obvious. It seems the action would be rare enough to just select it from context menu. But feel free to disagree, I know many people use shortcuts more than I.

But I agree with the Delete = "Delete dataset only" (maps are kept, the safer choice)

@polucifier
polucifier marked this pull request as draft October 24, 2026 12:05
the key does not remove several items in a row. This matches the
layer tree and the graphical modeler.
"""
if event.GetKeyCode() != wx.WXK_DELETE:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if event.GetKeyCode() != wx.WXK_DELETE:
if event.GetKeyCode() not in {wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE}:

Consider handling wx.WXK_NUMPAD_DELETE as well, so the Delete key on a numeric keypad is supported.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs GUI wxGUI related HTML Related code is in HTML markdown Related to markdown, markdown files Python Related code is in Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants