
This helps take cruft out of the uiGroup, and ensures that components remain stacked properly on top of each other. In the future, we'll use this group to ensure that grabs are ordered properly, as well.
177 lines
5.7 KiB
JavaScript
177 lines
5.7 KiB
JavaScript
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
|
|
|
const Clutter = imports.gi.Clutter;
|
|
const Gtk = imports.gi.Gtk;
|
|
const Lang = imports.lang;
|
|
const St = imports.gi.St;
|
|
|
|
const BoxPointer = imports.ui.boxpointer;
|
|
const Main = imports.ui.main;
|
|
const Params = imports.misc.params;
|
|
const PopupMenu = imports.ui.popupMenu;
|
|
|
|
const EntryMenu = new Lang.Class({
|
|
Name: 'ShellEntryMenu',
|
|
Extends: PopupMenu.PopupMenu,
|
|
|
|
_init: function(entry) {
|
|
this.parent(entry, 0, St.Side.TOP);
|
|
|
|
this.actor.add_style_class_name('entry-context-menu');
|
|
|
|
this._entry = entry;
|
|
this._clipboard = St.Clipboard.get_default();
|
|
|
|
// Populate menu
|
|
let item;
|
|
item = new PopupMenu.PopupMenuItem(_("Copy"));
|
|
item.connect('activate', Lang.bind(this, this._onCopyActivated));
|
|
this.addMenuItem(item);
|
|
this._copyItem = item;
|
|
|
|
item = new PopupMenu.PopupMenuItem(_("Paste"));
|
|
item.connect('activate', Lang.bind(this, this._onPasteActivated));
|
|
this.addMenuItem(item);
|
|
this._pasteItem = item;
|
|
|
|
this._passwordItem = null;
|
|
|
|
Main.layoutManager.menuGroup.add_actor(this.actor);
|
|
this.actor.hide();
|
|
},
|
|
|
|
_makePasswordItem: function() {
|
|
let item = new PopupMenu.PopupMenuItem('');
|
|
item.connect('activate', Lang.bind(this,
|
|
this._onPasswordActivated));
|
|
this.addMenuItem(item);
|
|
this._passwordItem = item;
|
|
},
|
|
|
|
get isPassword() {
|
|
return this._passwordItem != null;
|
|
},
|
|
|
|
set isPassword(v) {
|
|
if (v == this.isPassword)
|
|
return;
|
|
|
|
if (v) {
|
|
this._makePasswordItem();
|
|
this._entry.input_purpose = Gtk.InputPurpose.PASSWORD;
|
|
} else {
|
|
this._passwordItem.destroy();
|
|
this._passwordItem = null;
|
|
this._entry.input_purpose = Gtk.InputPurpose.FREE_FORM;
|
|
}
|
|
},
|
|
|
|
open: function(animate) {
|
|
this._updatePasteItem();
|
|
this._updateCopyItem();
|
|
if (this._passwordItem)
|
|
this._updatePasswordItem();
|
|
|
|
this.parent(animate);
|
|
this._entry.add_style_pseudo_class('focus');
|
|
|
|
let direction = Gtk.DirectionType.TAB_FORWARD;
|
|
if (!this.actor.navigate_focus(null, direction, false))
|
|
this.actor.grab_key_focus();
|
|
},
|
|
|
|
_updateCopyItem: function() {
|
|
let selection = this._entry.clutter_text.get_selection();
|
|
this._copyItem.setSensitive(!this._entry.clutter_text.password_char &&
|
|
selection && selection != '');
|
|
},
|
|
|
|
_updatePasteItem: function() {
|
|
this._clipboard.get_text(St.ClipboardType.CLIPBOARD, Lang.bind(this,
|
|
function(clipboard, text) {
|
|
this._pasteItem.setSensitive(text && text != '');
|
|
}));
|
|
},
|
|
|
|
_updatePasswordItem: function() {
|
|
let textHidden = (this._entry.clutter_text.password_char);
|
|
if (textHidden)
|
|
this._passwordItem.label.set_text(_("Show Text"));
|
|
else
|
|
this._passwordItem.label.set_text(_("Hide Text"));
|
|
},
|
|
|
|
_onCopyActivated: function() {
|
|
let selection = this._entry.clutter_text.get_selection();
|
|
this._clipboard.set_text(St.ClipboardType.CLIPBOARD, selection);
|
|
},
|
|
|
|
_onPasteActivated: function() {
|
|
this._clipboard.get_text(St.ClipboardType.CLIPBOARD, Lang.bind(this,
|
|
function(clipboard, text) {
|
|
if (!text)
|
|
return;
|
|
this._entry.clutter_text.delete_selection();
|
|
let pos = this._entry.clutter_text.get_cursor_position();
|
|
this._entry.clutter_text.insert_text(text, pos);
|
|
}));
|
|
},
|
|
|
|
_onPasswordActivated: function() {
|
|
let visible = !!(this._entry.clutter_text.password_char);
|
|
this._entry.clutter_text.set_password_char(visible ? '' : '\u25cf');
|
|
}
|
|
});
|
|
|
|
function _setMenuAlignment(entry, stageX) {
|
|
let [success, entryX, entryY] = entry.transform_stage_point(stageX, 0);
|
|
if (success)
|
|
entry.menu.setSourceAlignment(entryX / entry.width);
|
|
};
|
|
|
|
function _onButtonPressEvent(actor, event, entry) {
|
|
if (entry.menu.isOpen) {
|
|
entry.menu.close(BoxPointer.PopupAnimation.FULL);
|
|
return Clutter.EVENT_STOP;
|
|
} else if (event.get_button() == 3) {
|
|
let [stageX, stageY] = event.get_coords();
|
|
_setMenuAlignment(entry, stageX);
|
|
entry.menu.open(BoxPointer.PopupAnimation.FULL);
|
|
return Clutter.EVENT_STOP;
|
|
}
|
|
return Clutter.EVENT_PROPAGATE;
|
|
};
|
|
|
|
function _onPopup(actor, entry) {
|
|
let [success, textX, textY, lineHeight] = entry.clutter_text.position_to_coords(-1);
|
|
if (success)
|
|
entry.menu.setSourceAlignment(textX / entry.width);
|
|
entry.menu.open(BoxPointer.PopupAnimation.FULL);
|
|
};
|
|
|
|
function addContextMenu(entry, params) {
|
|
if (entry.menu)
|
|
return;
|
|
|
|
params = Params.parse (params, { isPassword: false });
|
|
|
|
entry.menu = new EntryMenu(entry);
|
|
entry.menu.isPassword = params.isPassword;
|
|
entry._menuManager = new PopupMenu.PopupMenuManager({ actor: entry });
|
|
entry._menuManager.addMenu(entry.menu);
|
|
|
|
// Add an event handler to both the entry and its clutter_text; the former
|
|
// so padding is included in the clickable area, the latter because the
|
|
// event processing of ClutterText prevents event-bubbling.
|
|
entry.clutter_text.connect('button-press-event', Lang.bind(null, _onButtonPressEvent, entry));
|
|
entry.connect('button-press-event', Lang.bind(null, _onButtonPressEvent, entry));
|
|
|
|
entry.connect('popup-menu', Lang.bind(null, _onPopup, entry));
|
|
|
|
entry.connect('destroy', function() {
|
|
entry.menu.destroy();
|
|
entry.menu = null;
|
|
entry._menuManager = null;
|
|
});
|
|
}
|