c4c5c4fd5c
Remove the `this.actor = ...` and `this.actor._delegate = this` patterns in most of classes, by inheriting all the actor container classes. Uses interfaces when needed for making sure that multiple classes will implement some required methods or to avoid redefining the same code multiple times. https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/559
319 lines
12 KiB
JavaScript
319 lines
12 KiB
JavaScript
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
|
/* exported CandidatePopup */
|
|
|
|
const { Clutter, GObject, IBus, St } = imports.gi;
|
|
|
|
const BoxPointer = imports.ui.boxpointer;
|
|
const Main = imports.ui.main;
|
|
|
|
var MAX_CANDIDATES_PER_PAGE = 16;
|
|
|
|
var DEFAULT_INDEX_LABELS = ['1', '2', '3', '4', '5', '6', '7', '8',
|
|
'9', '0', 'a', 'b', 'c', 'd', 'e', 'f'];
|
|
|
|
var CandidateArea = GObject.registerClass({
|
|
Signals: {
|
|
'candidate-clicked': { param_types: [GObject.TYPE_UINT,
|
|
GObject.TYPE_UINT,
|
|
Clutter.ModifierType.$gtype] },
|
|
'cursor-down': {},
|
|
'cursor-up': {},
|
|
'next-page': {},
|
|
'previous-page': {},
|
|
}
|
|
}, class CandidateArea extends St.BoxLayout {
|
|
_init() {
|
|
super._init({
|
|
vertical: true,
|
|
reactive: true,
|
|
visible: false
|
|
});
|
|
this._candidateBoxes = [];
|
|
for (let i = 0; i < MAX_CANDIDATES_PER_PAGE; ++i) {
|
|
let box = new St.BoxLayout({ style_class: 'candidate-box',
|
|
reactive: true,
|
|
track_hover: true });
|
|
box._indexLabel = new St.Label({ style_class: 'candidate-index' });
|
|
box._candidateLabel = new St.Label({ style_class: 'candidate-label' });
|
|
box.add(box._indexLabel, { y_fill: false });
|
|
box.add(box._candidateLabel, { y_fill: false });
|
|
this._candidateBoxes.push(box);
|
|
this.add(box);
|
|
|
|
let j = i;
|
|
box.connect('button-release-event', (actor, event) => {
|
|
this.emit('candidate-clicked', j, event.get_button(), event.get_state());
|
|
return Clutter.EVENT_PROPAGATE;
|
|
});
|
|
}
|
|
|
|
this.connect('scroll-event', (actor, event) => {
|
|
let direction = event.get_scroll_direction();
|
|
switch (direction) {
|
|
case Clutter.ScrollDirection.UP:
|
|
this.emit('cursor-up');
|
|
break;
|
|
case Clutter.ScrollDirection.DOWN:
|
|
this.emit('cursor-down');
|
|
break;
|
|
}
|
|
return Clutter.EVENT_PROPAGATE;
|
|
});
|
|
|
|
this._buttonBox = new St.BoxLayout({ style_class: 'candidate-page-button-box' });
|
|
|
|
this._previousButton = new St.Button({ style_class: 'candidate-page-button candidate-page-button-previous button' });
|
|
this._previousButton.child = new St.Icon({ style_class: 'candidate-page-button-icon' });
|
|
this._buttonBox.add(this._previousButton, { expand: true });
|
|
|
|
this._nextButton = new St.Button({ style_class: 'candidate-page-button candidate-page-button-next button' });
|
|
this._nextButton.child = new St.Icon({ style_class: 'candidate-page-button-icon' });
|
|
this._buttonBox.add(this._nextButton, { expand: true });
|
|
|
|
this.add(this._buttonBox);
|
|
|
|
this._previousButton.connect('clicked', () => {
|
|
this.emit('previous-page');
|
|
});
|
|
this._nextButton.connect('clicked', () => {
|
|
this.emit('next-page');
|
|
});
|
|
|
|
this._orientation = -1;
|
|
this._cursorPosition = 0;
|
|
}
|
|
|
|
setOrientation(orientation) {
|
|
if (this._orientation == orientation)
|
|
return;
|
|
|
|
this._orientation = orientation;
|
|
|
|
if (this._orientation == IBus.Orientation.HORIZONTAL) {
|
|
this.vertical = false;
|
|
this.remove_style_class_name('vertical');
|
|
this.add_style_class_name('horizontal');
|
|
this._previousButton.child.icon_name = 'go-previous-symbolic';
|
|
this._nextButton.child.icon_name = 'go-next-symbolic';
|
|
} else { // VERTICAL || SYSTEM
|
|
this.vertical = true;
|
|
this.add_style_class_name('vertical');
|
|
this.remove_style_class_name('horizontal');
|
|
this._previousButton.child.icon_name = 'go-up-symbolic';
|
|
this._nextButton.child.icon_name = 'go-down-symbolic';
|
|
}
|
|
}
|
|
|
|
setCandidates(indexes, candidates, cursorPosition, cursorVisible) {
|
|
for (let i = 0; i < MAX_CANDIDATES_PER_PAGE; ++i) {
|
|
let visible = i < candidates.length;
|
|
let box = this._candidateBoxes[i];
|
|
box.visible = visible;
|
|
|
|
if (!visible)
|
|
continue;
|
|
|
|
box._indexLabel.text = ((indexes && indexes[i]) ? indexes[i] : DEFAULT_INDEX_LABELS[i]);
|
|
box._candidateLabel.text = candidates[i];
|
|
}
|
|
|
|
this._candidateBoxes[this._cursorPosition].remove_style_pseudo_class('selected');
|
|
this._cursorPosition = cursorPosition;
|
|
if (cursorVisible)
|
|
this._candidateBoxes[cursorPosition].add_style_pseudo_class('selected');
|
|
}
|
|
|
|
updateButtons(wrapsAround, page, nPages) {
|
|
if (nPages < 2) {
|
|
this._buttonBox.hide();
|
|
return;
|
|
}
|
|
this._buttonBox.show();
|
|
this._previousButton.reactive = wrapsAround || page > 0;
|
|
this._nextButton.reactive = wrapsAround || page < nPages - 1;
|
|
}
|
|
});
|
|
|
|
var CandidatePopup = GObject.registerClass(
|
|
class IbusCandidatePopup extends BoxPointer.BoxPointer {
|
|
_init() {
|
|
super._init(St.Side.TOP);
|
|
this.visible = false;
|
|
this.style_class = 'candidate-popup-boxpointer';
|
|
|
|
this._dummyCursor = new St.Widget({ opacity: 0 });
|
|
Main.layoutManager.uiGroup.add_actor(this._dummyCursor);
|
|
|
|
Main.layoutManager.addChrome(this);
|
|
|
|
let box = new St.BoxLayout({ style_class: 'candidate-popup-content',
|
|
vertical: true });
|
|
this.bin.set_child(box);
|
|
|
|
this._preeditText = new St.Label({ style_class: 'candidate-popup-text',
|
|
visible: false });
|
|
box.add(this._preeditText);
|
|
|
|
this._auxText = new St.Label({ style_class: 'candidate-popup-text',
|
|
visible: false });
|
|
box.add(this._auxText);
|
|
|
|
this._candidateArea = new CandidateArea();
|
|
box.add(this._candidateArea);
|
|
|
|
this._candidateArea.connect('previous-page', () => {
|
|
this._panelService.page_up();
|
|
});
|
|
this._candidateArea.connect('next-page', () => {
|
|
this._panelService.page_down();
|
|
});
|
|
|
|
this._candidateArea.connect('cursor-up', () => {
|
|
this._panelService.cursor_up();
|
|
});
|
|
this._candidateArea.connect('cursor-down', () => {
|
|
this._panelService.cursor_down();
|
|
});
|
|
|
|
this._candidateArea.connect('candidate-clicked', (area, index, button, state) => {
|
|
this._panelService.candidate_clicked(index, button, state);
|
|
});
|
|
|
|
this._panelService = null;
|
|
}
|
|
|
|
setPanelService(panelService) {
|
|
this._panelService = panelService;
|
|
if (!panelService)
|
|
return;
|
|
|
|
panelService.connect('set-cursor-location', (ps, x, y, w, h) => {
|
|
this._setDummyCursorGeometry(x, y, w, h);
|
|
});
|
|
try {
|
|
panelService.connect('set-cursor-location-relative', (ps, x, y, w, h) => {
|
|
if (!global.display.focus_window)
|
|
return;
|
|
let window = global.display.focus_window.get_compositor_private();
|
|
this._setDummyCursorGeometry(window.x + x, window.y + y, w, h);
|
|
});
|
|
} catch (e) {
|
|
// Only recent IBus versions have support for this signal
|
|
// which is used for wayland clients. In order to work
|
|
// with older IBus versions we can silently ignore the
|
|
// signal's absence.
|
|
}
|
|
panelService.connect('update-preedit-text', (ps, text, cursorPosition, visible) => {
|
|
this._preeditText.visible = visible;
|
|
this._updateVisibility();
|
|
|
|
this._preeditText.text = text.get_text();
|
|
|
|
let attrs = text.get_attributes();
|
|
if (attrs)
|
|
this._setTextAttributes(this._preeditText.clutter_text,
|
|
attrs);
|
|
});
|
|
panelService.connect('show-preedit-text', () => {
|
|
this._preeditText.show();
|
|
this._updateVisibility();
|
|
});
|
|
panelService.connect('hide-preedit-text', () => {
|
|
this._preeditText.hide();
|
|
this._updateVisibility();
|
|
});
|
|
panelService.connect('update-auxiliary-text', (_ps, text, visible) => {
|
|
this._auxText.visible = visible;
|
|
this._updateVisibility();
|
|
|
|
this._auxText.text = text.get_text();
|
|
});
|
|
panelService.connect('show-auxiliary-text', () => {
|
|
this._auxText.show();
|
|
this._updateVisibility();
|
|
});
|
|
panelService.connect('hide-auxiliary-text', () => {
|
|
this._auxText.hide();
|
|
this._updateVisibility();
|
|
});
|
|
panelService.connect('update-lookup-table', (_ps, lookupTable, visible) => {
|
|
this._candidateArea.visible = visible;
|
|
this._updateVisibility();
|
|
|
|
let nCandidates = lookupTable.get_number_of_candidates();
|
|
let cursorPos = lookupTable.get_cursor_pos();
|
|
let pageSize = lookupTable.get_page_size();
|
|
let nPages = Math.ceil(nCandidates / pageSize);
|
|
let page = ((cursorPos == 0) ? 0 : Math.floor(cursorPos / pageSize));
|
|
let startIndex = page * pageSize;
|
|
let endIndex = Math.min((page + 1) * pageSize, nCandidates);
|
|
|
|
let indexes = [];
|
|
let indexLabel;
|
|
for (let i = 0; (indexLabel = lookupTable.get_label(i)); ++i)
|
|
indexes.push(indexLabel.get_text());
|
|
|
|
Main.keyboard.resetSuggestions();
|
|
|
|
let candidates = [];
|
|
for (let i = startIndex; i < endIndex; ++i) {
|
|
candidates.push(lookupTable.get_candidate(i).get_text());
|
|
|
|
Main.keyboard.addSuggestion(lookupTable.get_candidate(i).get_text(), () => {
|
|
let index = i;
|
|
this._panelService.candidate_clicked(index, 1, 0);
|
|
});
|
|
}
|
|
|
|
this._candidateArea.setCandidates(indexes,
|
|
candidates,
|
|
cursorPos % pageSize,
|
|
lookupTable.is_cursor_visible());
|
|
this._candidateArea.setOrientation(lookupTable.get_orientation());
|
|
this._candidateArea.updateButtons(lookupTable.is_round(), page, nPages);
|
|
});
|
|
panelService.connect('show-lookup-table', () => {
|
|
this._candidateArea.show();
|
|
this._updateVisibility();
|
|
});
|
|
panelService.connect('hide-lookup-table', () => {
|
|
this._candidateArea.hide();
|
|
this._updateVisibility();
|
|
});
|
|
panelService.connect('focus-out', () => {
|
|
this.close(BoxPointer.PopupAnimation.NONE);
|
|
Main.keyboard.resetSuggestions();
|
|
});
|
|
}
|
|
|
|
_setDummyCursorGeometry(x, y, w, h) {
|
|
this._dummyCursor.set_position(Math.round(x), Math.round(y));
|
|
this._dummyCursor.set_size(Math.round(w), Math.round(h));
|
|
|
|
if (this.visible)
|
|
this.setPosition(this._dummyCursor, 0);
|
|
}
|
|
|
|
_updateVisibility() {
|
|
let isVisible = (!Main.keyboard.visible &&
|
|
(this._preeditText.visible ||
|
|
this._auxText.visible ||
|
|
this._candidateArea.visible));
|
|
|
|
if (isVisible) {
|
|
this.setPosition(this._dummyCursor, 0);
|
|
this.open(BoxPointer.PopupAnimation.NONE);
|
|
this.raise_top();
|
|
} else {
|
|
this.close(BoxPointer.PopupAnimation.NONE);
|
|
}
|
|
}
|
|
|
|
_setTextAttributes(clutterText, ibusAttrList) {
|
|
let attr;
|
|
for (let i = 0; (attr = ibusAttrList.get(i)); ++i)
|
|
if (attr.get_attr_type() == IBus.AttrType.BACKGROUND)
|
|
clutterText.set_selection(attr.get_start_index(), attr.get_end_index());
|
|
}
|
|
});
|