appDisplay: Allow editing folder names

Add a new popover with a regular entry + button to rename
folders. The layout is similar to other GNOME applications.

The popup is implemented as a PopupMenu subclass, leaving
the grab management to PopupMenuManager.

https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/675
This commit is contained in:
Georges Basile Stavracas Neto 2019-09-01 14:12:42 -03:00 committed by Florian Müllner
parent 1e68e78d8e
commit dfc0ef56f6
2 changed files with 172 additions and 0 deletions

View File

@ -611,6 +611,14 @@ StScrollBar {
} }
// Rename popup
.rename-folder-popup-box {
spacing: 6px;
margin-left: 12px;
margin-right: 12px;
}
// Background menu // Background menu
.background-menu { -boxpointer-gap: 4px; -arrow-rise: 0px; } .background-menu { -boxpointer-gap: 4px; -arrow-rise: 0px; }

View File

@ -1438,6 +1438,13 @@ var FolderIcon = class FolderIcon {
this._itemDragEndId = Main.overview.connect( this._itemDragEndId = Main.overview.connect(
'item-drag-end', this._onDragEnd.bind(this)); 'item-drag-end', this._onDragEnd.bind(this));
this._popupTimeoutId = 0;
this.actor.connect('leave-event', this._onLeaveEvent.bind(this));
this.actor.connect('button-press-event', this._onButtonPress.bind(this));
this.actor.connect('touch-event', this._onTouchEvent.bind(this));
this.actor.connect('popup-menu', this._popupRenamePopup.bind(this));
this.actor.connect('clicked', this.open.bind(this)); this.actor.connect('clicked', this.open.bind(this));
this.actor.connect('destroy', this.onDestroy.bind(this)); this.actor.connect('destroy', this.onDestroy.bind(this));
this.actor.connect('notify::mapped', () => { this.actor.connect('notify::mapped', () => {
@ -1462,9 +1469,12 @@ var FolderIcon = class FolderIcon {
if (this._popup) if (this._popup)
this._popup.actor.destroy(); this._popup.actor.destroy();
this._removeMenuTimeout();
} }
open() { open() {
this._removeMenuTimeout();
this._ensurePopup(); this._ensurePopup();
this.view.actor.vscroll.adjustment.value = 0; this.view.actor.vscroll.adjustment.value = 0;
this._openSpaceForPopup(); this._openSpaceForPopup();
@ -1627,6 +1637,76 @@ var FolderIcon = class FolderIcon {
this._popupInvalidated = false; this._popupInvalidated = false;
} }
_removeMenuTimeout() {
if (this._popupTimeoutId > 0) {
Mainloop.source_remove(this._popupTimeoutId);
this._popupTimeoutId = 0;
}
}
_setPopupTimeout() {
this._removeMenuTimeout();
this._popupTimeoutId = Mainloop.timeout_add(MENU_POPUP_TIMEOUT, () => {
this._popupTimeoutId = 0;
this._popupRenamePopup();
return GLib.SOURCE_REMOVE;
});
GLib.Source.set_name_by_id(this._popupTimeoutId,
'[gnome-shell] this._popupRenamePopup');
}
_onLeaveEvent(_actor, _event) {
this.actor.fake_release();
this._removeMenuTimeout();
}
_onButtonPress(_actor, event) {
let button = event.get_button();
if (button == 1) {
this._setPopupTimeout();
} else if (button == 3) {
this._popupRenamePopup();
return Clutter.EVENT_STOP;
}
return Clutter.EVENT_PROPAGATE;
}
_onTouchEvent(actor, event) {
if (event.type() == Clutter.EventType.TOUCH_BEGIN)
this._setPopupTimeout();
return Clutter.EVENT_PROPAGATE;
}
_popupRenamePopup() {
this._removeMenuTimeout();
this.actor.fake_release();
if (!this._menu) {
this._menuManager = new PopupMenu.PopupMenuManager(this.actor);
this._menu = new RenameFolderMenu(this, this._folder);
this._menuManager.addMenu(this._menu);
this._menu.connect('open-state-changed', (menu, isPoppedUp) => {
if (!isPoppedUp)
this.actor.sync_hover();
});
let id = Main.overview.connect('hiding', () => {
this._menu.close();
});
this.actor.connect('destroy', () => {
Main.overview.disconnect(id);
});
this._menuManager.addMenu(this._menu);
}
this.actor.set_hover(true);
this._menu.popup();
this._menuManager.ignoreRelease();
}
adaptToSize(width, height) { adaptToSize(width, height) {
this._parentAvailableWidth = width; this._parentAvailableWidth = width;
this._parentAvailableHeight = height; this._parentAvailableHeight = height;
@ -1637,6 +1717,90 @@ var FolderIcon = class FolderIcon {
}; };
Signals.addSignalMethods(FolderIcon.prototype); Signals.addSignalMethods(FolderIcon.prototype);
var RenameFolderMenu = class RenameFolderMenu extends PopupMenu.PopupMenu {
constructor(source, folder) {
super(source.actor, 0.5, St.Side.BOTTOM);
this._source = source;
this._folder = folder;
// We want to keep the item hovered while the menu is up
this.blockSourceEvents = true;
let box = new St.BoxLayout({ style_class: 'rename-folder-popup-box' });
this.box.add_child(box);
// Entry
this._entry = new St.Entry({
x_expand: true,
width: 200,
});
box.add_child(this._entry);
this._entry.clutter_text.connect('notify::text',
this._validate.bind(this));
this._entry.clutter_text.connect('activate',
this._updateFolderName.bind(this));
// Rename button
this._button = new St.Button({
style_class: 'button',
reactive: true,
button_mask: St.ButtonMask.ONE | St.ButtonMask.TWO,
can_focus: true,
label: _("Rename"),
});
box.add_child(this._button);
this._button.connect('clicked', this._updateFolderName.bind(this));
// Chain our visibility and lifecycle to that of the source
this._sourceMappedId = source.actor.connect('notify::mapped', () => {
if (!source.actor.mapped)
this.close();
});
source.actor.connect('destroy', () => {
source.actor.disconnect(this._sourceMappedId);
this.destroy();
});
Main.uiGroup.add_actor(this.actor);
}
popup() {
let folderName = _getFolderName(this._folder);
this._entry.text = folderName;
this._entry.clutter_text.set_selection(0, folderName.length);
this.open();
}
_isValidFolderName() {
let folderName = _getFolderName(this._folder);
let newFolderName = this._entry.text.trim();
return newFolderName.length > 0 && newFolderName != folderName;
}
_validate() {
let isValid = this._isValidFolderName();
this._button.reactive = isValid;
}
_updateFolderName() {
if (!this._isValidFolderName())
return;
let newFolderName = this._entry.text.trim();
this._folder.set_string('name', newFolderName);
this._folder.set_boolean('translate', false);
this.close();
}
};
Signals.addSignalMethods(RenameFolderMenu.prototype);
var AppFolderPopup = class AppFolderPopup { var AppFolderPopup = class AppFolderPopup {
constructor(source, side) { constructor(source, side) {
this._source = source; this._source = source;