2012-05-29 13:30:46 -04:00
|
|
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
2019-01-31 09:07:06 -05:00
|
|
|
/* exported CandidatePopup */
|
2012-05-29 13:30:46 -04:00
|
|
|
|
2023-06-08 00:52:46 -04:00
|
|
|
const Clutter = imports.gi.Clutter;
|
|
|
|
const GObject = imports.gi.GObject;
|
|
|
|
const IBus = imports.gi.IBus;
|
|
|
|
const St = imports.gi.St;
|
2012-05-29 13:30:46 -04:00
|
|
|
|
|
|
|
const BoxPointer = imports.ui.boxpointer;
|
|
|
|
const Main = imports.ui.main;
|
|
|
|
|
2017-07-18 13:47:27 -04:00
|
|
|
var MAX_CANDIDATES_PER_PAGE = 16;
|
2012-05-29 13:30:46 -04:00
|
|
|
|
2020-03-27 09:18:34 -04:00
|
|
|
var DEFAULT_INDEX_LABELS = [
|
|
|
|
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
|
|
|
|
'a', 'b', 'c', 'd', 'e', 'f',
|
|
|
|
];
|
2014-07-07 08:30:40 -04:00
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
var CandidateArea = GObject.registerClass({
|
|
|
|
Signals: {
|
2020-03-27 09:18:34 -04:00
|
|
|
'candidate-clicked': {
|
|
|
|
param_types: [
|
|
|
|
GObject.TYPE_UINT, GObject.TYPE_UINT, Clutter.ModifierType.$gtype,
|
|
|
|
],
|
|
|
|
},
|
2019-07-16 05:24:13 -04:00
|
|
|
'cursor-down': {},
|
|
|
|
'cursor-up': {},
|
|
|
|
'next-page': {},
|
|
|
|
'previous-page': {},
|
2019-08-20 17:43:54 -04:00
|
|
|
},
|
2019-07-16 05:24:13 -04:00
|
|
|
}, class CandidateArea extends St.BoxLayout {
|
|
|
|
_init() {
|
|
|
|
super._init({
|
|
|
|
vertical: true,
|
|
|
|
reactive: true,
|
2019-08-20 17:43:54 -04:00
|
|
|
visible: false,
|
2019-07-16 05:24:13 -04:00
|
|
|
});
|
2013-01-02 08:24:37 -05:00
|
|
|
this._candidateBoxes = [];
|
2012-05-29 13:30:46 -04:00
|
|
|
for (let i = 0; i < MAX_CANDIDATES_PER_PAGE; ++i) {
|
2020-03-29 17:51:13 -04:00
|
|
|
const box = new St.BoxLayout({
|
|
|
|
style_class: 'candidate-box',
|
|
|
|
reactive: true,
|
|
|
|
track_hover: true,
|
|
|
|
});
|
2013-01-02 08:24:37 -05:00
|
|
|
box._indexLabel = new St.Label({ style_class: 'candidate-index' });
|
|
|
|
box._candidateLabel = new St.Label({ style_class: 'candidate-label' });
|
2019-10-21 14:44:00 -04:00
|
|
|
box.add_child(box._indexLabel);
|
|
|
|
box.add_child(box._candidateLabel);
|
2013-01-02 08:24:37 -05:00
|
|
|
this._candidateBoxes.push(box);
|
2019-07-16 05:24:13 -04:00
|
|
|
this.add(box);
|
2013-01-16 18:21:14 -05:00
|
|
|
|
|
|
|
let j = i;
|
2017-10-30 20:38:18 -04:00
|
|
|
box.connect('button-release-event', (actor, event) => {
|
2013-01-16 18:21:14 -05:00
|
|
|
this.emit('candidate-clicked', j, event.get_button(), event.get_state());
|
2013-11-29 13:17:34 -05:00
|
|
|
return Clutter.EVENT_PROPAGATE;
|
2017-10-30 20:38:18 -04:00
|
|
|
});
|
2012-05-29 13:30:46 -04:00
|
|
|
}
|
|
|
|
|
2013-02-14 17:36:32 -05:00
|
|
|
this._buttonBox = new St.BoxLayout({ style_class: 'candidate-page-button-box' });
|
|
|
|
|
2019-10-21 14:44:00 -04:00
|
|
|
this._previousButton = new St.Button({
|
|
|
|
style_class: 'candidate-page-button candidate-page-button-previous button',
|
|
|
|
x_expand: true,
|
|
|
|
});
|
|
|
|
this._buttonBox.add_child(this._previousButton);
|
2013-02-14 17:36:32 -05:00
|
|
|
|
2019-10-21 14:44:00 -04:00
|
|
|
this._nextButton = new St.Button({
|
|
|
|
style_class: 'candidate-page-button candidate-page-button-next button',
|
|
|
|
x_expand: true,
|
|
|
|
});
|
|
|
|
this._buttonBox.add_child(this._nextButton);
|
2013-02-14 17:36:32 -05:00
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
this.add(this._buttonBox);
|
2013-02-14 17:36:32 -05:00
|
|
|
|
2017-10-30 20:38:18 -04:00
|
|
|
this._previousButton.connect('clicked', () => {
|
2013-02-14 17:36:32 -05:00
|
|
|
this.emit('previous-page');
|
2017-10-30 20:38:18 -04:00
|
|
|
});
|
|
|
|
this._nextButton.connect('clicked', () => {
|
2013-02-14 17:36:32 -05:00
|
|
|
this.emit('next-page');
|
2017-10-30 20:38:18 -04:00
|
|
|
});
|
2013-02-14 17:36:32 -05:00
|
|
|
|
2012-05-29 13:30:46 -04:00
|
|
|
this._orientation = -1;
|
|
|
|
this._cursorPosition = 0;
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2012-05-29 13:30:46 -04:00
|
|
|
|
2019-09-10 01:42:48 -04:00
|
|
|
vfunc_scroll_event(scrollEvent) {
|
|
|
|
switch (scrollEvent.direction) {
|
|
|
|
case Clutter.ScrollDirection.UP:
|
|
|
|
this.emit('cursor-up');
|
|
|
|
break;
|
|
|
|
case Clutter.ScrollDirection.DOWN:
|
|
|
|
this.emit('cursor-down');
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return Clutter.EVENT_PROPAGATE;
|
|
|
|
}
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
setOrientation(orientation) {
|
2012-05-29 13:30:46 -04:00
|
|
|
if (this._orientation == orientation)
|
|
|
|
return;
|
|
|
|
|
|
|
|
this._orientation = orientation;
|
|
|
|
|
2013-02-14 17:36:32 -05:00
|
|
|
if (this._orientation == IBus.Orientation.HORIZONTAL) {
|
2019-07-16 05:24:13 -04:00
|
|
|
this.vertical = false;
|
|
|
|
this.remove_style_class_name('vertical');
|
|
|
|
this.add_style_class_name('horizontal');
|
2022-03-21 11:44:24 -04:00
|
|
|
this._previousButton.icon_name = 'go-previous-symbolic';
|
|
|
|
this._nextButton.icon_name = 'go-next-symbolic';
|
2013-02-14 17:36:32 -05:00
|
|
|
} else { // VERTICAL || SYSTEM
|
2019-07-16 05:24:13 -04:00
|
|
|
this.vertical = true;
|
|
|
|
this.add_style_class_name('vertical');
|
|
|
|
this.remove_style_class_name('horizontal');
|
2022-03-21 11:44:24 -04:00
|
|
|
this._previousButton.icon_name = 'go-up-symbolic';
|
|
|
|
this._nextButton.icon_name = 'go-down-symbolic';
|
2013-02-14 17:36:32 -05:00
|
|
|
}
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2012-05-29 13:30:46 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
setCandidates(indexes, candidates, cursorPosition, cursorVisible) {
|
2012-05-29 13:30:46 -04:00
|
|
|
for (let i = 0; i < MAX_CANDIDATES_PER_PAGE; ++i) {
|
|
|
|
let visible = i < candidates.length;
|
2013-01-02 08:24:37 -05:00
|
|
|
let box = this._candidateBoxes[i];
|
|
|
|
box.visible = visible;
|
2012-05-29 13:30:46 -04:00
|
|
|
|
|
|
|
if (!visible)
|
|
|
|
continue;
|
|
|
|
|
2019-08-19 15:38:51 -04:00
|
|
|
box._indexLabel.text = indexes && indexes[i] ? indexes[i] : DEFAULT_INDEX_LABELS[i];
|
2013-01-02 08:24:37 -05:00
|
|
|
box._candidateLabel.text = candidates[i];
|
2012-05-29 13:30:46 -04:00
|
|
|
}
|
|
|
|
|
2013-01-02 08:24:37 -05:00
|
|
|
this._candidateBoxes[this._cursorPosition].remove_style_pseudo_class('selected');
|
2012-05-29 13:30:46 -04:00
|
|
|
this._cursorPosition = cursorPosition;
|
|
|
|
if (cursorVisible)
|
2013-01-02 08:24:37 -05:00
|
|
|
this._candidateBoxes[cursorPosition].add_style_pseudo_class('selected');
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2013-02-14 17:36:32 -05:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
updateButtons(wrapsAround, page, nPages) {
|
2013-02-14 17:36:32 -05:00
|
|
|
if (nPages < 2) {
|
|
|
|
this._buttonBox.hide();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this._buttonBox.show();
|
|
|
|
this._previousButton.reactive = wrapsAround || page > 0;
|
|
|
|
this._nextButton.reactive = wrapsAround || page < nPages - 1;
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2019-07-16 05:24:13 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
var CandidatePopup = GObject.registerClass(
|
|
|
|
class IbusCandidatePopup extends BoxPointer.BoxPointer {
|
|
|
|
_init() {
|
|
|
|
super._init(St.Side.TOP);
|
|
|
|
this.visible = false;
|
|
|
|
this.style_class = 'candidate-popup-boxpointer';
|
2012-05-29 13:30:46 -04:00
|
|
|
|
2021-06-29 15:17:27 -04:00
|
|
|
this._dummyCursor = new Clutter.Actor({ opacity: 0 });
|
2019-10-08 06:22:34 -04:00
|
|
|
Main.layoutManager.uiGroup.add_actor(this._dummyCursor);
|
|
|
|
|
2022-07-03 23:57:36 -04:00
|
|
|
Main.layoutManager.addTopChrome(this);
|
2012-05-29 13:30:46 -04:00
|
|
|
|
2020-03-29 17:51:13 -04:00
|
|
|
const box = new St.BoxLayout({
|
|
|
|
style_class: 'candidate-popup-content',
|
|
|
|
vertical: true,
|
|
|
|
});
|
2019-07-16 05:24:13 -04:00
|
|
|
this.bin.set_child(box);
|
2012-05-29 13:30:46 -04:00
|
|
|
|
2020-03-29 17:51:13 -04:00
|
|
|
this._preeditText = new St.Label({
|
|
|
|
style_class: 'candidate-popup-text',
|
|
|
|
visible: false,
|
|
|
|
});
|
2013-01-02 08:24:37 -05:00
|
|
|
box.add(this._preeditText);
|
2012-05-29 13:30:46 -04:00
|
|
|
|
2020-03-29 17:51:13 -04:00
|
|
|
this._auxText = new St.Label({
|
|
|
|
style_class: 'candidate-popup-text',
|
|
|
|
visible: false,
|
|
|
|
});
|
2013-01-02 08:24:37 -05:00
|
|
|
box.add(this._auxText);
|
2012-05-29 13:30:46 -04:00
|
|
|
|
2013-01-02 08:24:37 -05:00
|
|
|
this._candidateArea = new CandidateArea();
|
2019-07-16 05:24:13 -04:00
|
|
|
box.add(this._candidateArea);
|
2012-05-29 13:30:46 -04:00
|
|
|
|
2017-10-30 20:38:18 -04:00
|
|
|
this._candidateArea.connect('previous-page', () => {
|
2013-02-14 17:36:32 -05:00
|
|
|
this._panelService.page_up();
|
2017-10-30 20:38:18 -04:00
|
|
|
});
|
|
|
|
this._candidateArea.connect('next-page', () => {
|
2013-02-14 17:36:32 -05:00
|
|
|
this._panelService.page_down();
|
2017-10-30 20:38:18 -04:00
|
|
|
});
|
2016-12-12 03:02:32 -05:00
|
|
|
|
2017-10-30 20:38:18 -04:00
|
|
|
this._candidateArea.connect('cursor-up', () => {
|
2016-12-12 03:02:32 -05:00
|
|
|
this._panelService.cursor_up();
|
2017-10-30 20:38:18 -04:00
|
|
|
});
|
|
|
|
this._candidateArea.connect('cursor-down', () => {
|
2016-12-12 03:02:32 -05:00
|
|
|
this._panelService.cursor_down();
|
2017-10-30 20:38:18 -04:00
|
|
|
});
|
2016-12-12 03:02:32 -05:00
|
|
|
|
2018-04-18 00:21:55 -04:00
|
|
|
this._candidateArea.connect('candidate-clicked', (area, index, button, state) => {
|
2013-01-16 18:21:14 -05:00
|
|
|
this._panelService.candidate_clicked(index, button, state);
|
2017-10-30 20:38:18 -04:00
|
|
|
});
|
2013-02-14 17:36:32 -05:00
|
|
|
|
2012-05-29 13:30:46 -04:00
|
|
|
this._panelService = null;
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2012-05-29 13:30:46 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
setPanelService(panelService) {
|
2012-05-29 13:30:46 -04:00
|
|
|
this._panelService = panelService;
|
|
|
|
if (!panelService)
|
|
|
|
return;
|
|
|
|
|
2017-10-30 20:38:18 -04:00
|
|
|
panelService.connect('set-cursor-location', (ps, x, y, w, h) => {
|
|
|
|
this._setDummyCursorGeometry(x, y, w, h);
|
|
|
|
});
|
2016-02-17 11:54:52 -05:00
|
|
|
try {
|
2017-10-30 20:38:18 -04:00
|
|
|
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);
|
|
|
|
});
|
2019-01-28 20:26:39 -05:00
|
|
|
} catch (e) {
|
2016-02-17 11:54:52 -05:00
|
|
|
// 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.
|
|
|
|
}
|
2017-10-30 20:38:18 -04:00
|
|
|
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();
|
2019-08-19 20:51:42 -04:00
|
|
|
if (attrs) {
|
2017-10-30 20:38:18 -04:00
|
|
|
this._setTextAttributes(this._preeditText.clutter_text,
|
|
|
|
attrs);
|
2019-08-19 20:51:42 -04:00
|
|
|
}
|
2017-10-30 20:38:18 -04:00
|
|
|
});
|
2019-02-04 06:30:53 -05:00
|
|
|
panelService.connect('show-preedit-text', () => {
|
2017-10-30 20:38:18 -04:00
|
|
|
this._preeditText.show();
|
|
|
|
this._updateVisibility();
|
|
|
|
});
|
2019-02-04 06:30:53 -05:00
|
|
|
panelService.connect('hide-preedit-text', () => {
|
2017-10-30 20:38:18 -04:00
|
|
|
this._preeditText.hide();
|
|
|
|
this._updateVisibility();
|
|
|
|
});
|
2019-01-31 09:08:10 -05:00
|
|
|
panelService.connect('update-auxiliary-text', (_ps, text, visible) => {
|
2017-10-30 20:38:18 -04:00
|
|
|
this._auxText.visible = visible;
|
|
|
|
this._updateVisibility();
|
|
|
|
|
|
|
|
this._auxText.text = text.get_text();
|
|
|
|
});
|
2019-02-04 06:30:53 -05:00
|
|
|
panelService.connect('show-auxiliary-text', () => {
|
2017-10-30 20:38:18 -04:00
|
|
|
this._auxText.show();
|
|
|
|
this._updateVisibility();
|
|
|
|
});
|
2019-02-04 06:30:53 -05:00
|
|
|
panelService.connect('hide-auxiliary-text', () => {
|
2017-10-30 20:38:18 -04:00
|
|
|
this._auxText.hide();
|
|
|
|
this._updateVisibility();
|
|
|
|
});
|
2019-01-31 09:08:10 -05:00
|
|
|
panelService.connect('update-lookup-table', (_ps, lookupTable, visible) => {
|
2019-07-16 05:24:13 -04:00
|
|
|
this._candidateArea.visible = visible;
|
2017-10-30 20:38:18 -04:00
|
|
|
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);
|
2019-08-19 15:38:51 -04:00
|
|
|
let page = cursorPos == 0 ? 0 : Math.floor(cursorPos / pageSize);
|
2017-10-30 20:38:18 -04:00
|
|
|
let startIndex = page * pageSize;
|
|
|
|
let endIndex = Math.min((page + 1) * pageSize, nCandidates);
|
|
|
|
|
|
|
|
let indexes = [];
|
|
|
|
let indexLabel;
|
2019-01-29 16:56:03 -05:00
|
|
|
for (let i = 0; (indexLabel = lookupTable.get_label(i)); ++i)
|
2019-01-29 14:36:54 -05:00
|
|
|
indexes.push(indexLabel.get_text());
|
2017-10-30 20:38:18 -04:00
|
|
|
|
|
|
|
Main.keyboard.resetSuggestions();
|
2022-06-29 07:06:07 -04:00
|
|
|
Main.keyboard.setSuggestionsVisible(visible);
|
2017-10-30 20:38:18 -04:00
|
|
|
|
|
|
|
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);
|
|
|
|
});
|
2019-02-04 06:30:53 -05:00
|
|
|
panelService.connect('show-lookup-table', () => {
|
2022-06-29 07:06:07 -04:00
|
|
|
Main.keyboard.setSuggestionsVisible(true);
|
2019-07-16 05:24:13 -04:00
|
|
|
this._candidateArea.show();
|
2017-10-30 20:38:18 -04:00
|
|
|
this._updateVisibility();
|
|
|
|
});
|
2019-02-04 06:30:53 -05:00
|
|
|
panelService.connect('hide-lookup-table', () => {
|
2022-06-29 07:06:07 -04:00
|
|
|
Main.keyboard.setSuggestionsVisible(false);
|
2019-07-16 05:24:13 -04:00
|
|
|
this._candidateArea.hide();
|
2017-10-30 20:38:18 -04:00
|
|
|
this._updateVisibility();
|
|
|
|
});
|
2019-02-04 06:30:53 -05:00
|
|
|
panelService.connect('focus-out', () => {
|
2019-07-16 05:24:13 -04:00
|
|
|
this.close(BoxPointer.PopupAnimation.NONE);
|
2017-10-30 20:38:18 -04:00
|
|
|
Main.keyboard.resetSuggestions();
|
|
|
|
});
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2012-05-29 13:30:46 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_setDummyCursorGeometry(x, y, w, h) {
|
2019-10-08 06:22:34 -04:00
|
|
|
this._dummyCursor.set_position(Math.round(x), Math.round(y));
|
|
|
|
this._dummyCursor.set_size(Math.round(w), Math.round(h));
|
|
|
|
|
2019-07-16 05:24:13 -04:00
|
|
|
if (this.visible)
|
|
|
|
this.setPosition(this._dummyCursor, 0);
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2014-07-30 08:40:39 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_updateVisibility() {
|
2019-08-19 15:38:51 -04:00
|
|
|
let isVisible = !Main.keyboard.visible &&
|
2017-12-06 08:44:16 -05:00
|
|
|
(this._preeditText.visible ||
|
|
|
|
this._auxText.visible ||
|
2019-08-19 15:38:51 -04:00
|
|
|
this._candidateArea.visible);
|
2013-01-02 08:24:37 -05:00
|
|
|
|
|
|
|
if (isVisible) {
|
2019-07-16 05:24:13 -04:00
|
|
|
this.setPosition(this._dummyCursor, 0);
|
|
|
|
this.open(BoxPointer.PopupAnimation.NONE);
|
2022-07-03 23:59:43 -04:00
|
|
|
// We shouldn't be above some components like the screenshot UI,
|
|
|
|
// so don't raise to the top.
|
|
|
|
// The on-screen keyboard is expected to be above any entries,
|
|
|
|
// so just above the keyboard gets us to the right layer.
|
|
|
|
const { keyboardBox } = Main.layoutManager;
|
|
|
|
this.get_parent().set_child_above_sibling(this, keyboardBox);
|
2013-01-02 08:24:37 -05:00
|
|
|
} else {
|
2019-07-16 05:24:13 -04:00
|
|
|
this.close(BoxPointer.PopupAnimation.NONE);
|
2013-01-02 08:24:37 -05:00
|
|
|
}
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2012-05-29 13:30:46 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_setTextAttributes(clutterText, ibusAttrList) {
|
2012-05-29 13:30:46 -04:00
|
|
|
let attr;
|
2019-08-19 20:51:42 -04:00
|
|
|
for (let i = 0; (attr = ibusAttrList.get(i)); ++i) {
|
2012-05-29 13:30:46 -04:00
|
|
|
if (attr.get_attr_type() == IBus.AttrType.BACKGROUND)
|
|
|
|
clutterText.set_selection(attr.get_start_index(), attr.get_end_index());
|
2019-08-19 20:51:42 -04:00
|
|
|
}
|
2012-05-29 13:30:46 -04:00
|
|
|
}
|
2019-07-16 05:24:13 -04:00
|
|
|
});
|