| Server IP : 209.205.66.10 / Your IP : 216.73.216.173 Web Server : Apache/2.4.52 (Ubuntu) System : Linux ammon 5.15.0-186-generic #196-Ubuntu SMP Sat Jun 20 16:09:34 UTC 2026 x86_64 User : ( 1006) PHP Version : 8.5.8 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /usr/share/org.gnome.Characters/ |
Upload File : |
GVariant � (
Ե ����� L � � �Xp� � v � 3! KFv� 3! L 8! X! {W�y X! v `! �1 KP� �1 L �1 �1 ��$0 �1 L �1 2 ���� 2 L 2 2 ��.G 2 v 2 $� �g� $� v 0� �� +Ka� �� v � � �(�
� v � x� ���� x� v ��
� ��
� v � �D / characterDialog.js { // -*- Mode: js; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*-
//
// Copyright (C) 2014-2015 Daiki Ueno <dueno@src.gnome.org>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
const {Gc, GLib, Gio,GObject,Gtk, Pango} = imports.gi;
const Params = imports.params;
const Util = imports.util;
var CharacterDialog = GObject.registerClass({
Signals: {
'character-copied': { param_types: [ GObject.TYPE_STRING ] }
},
Template: 'resource:///org/gnome/Characters/character.ui',
InternalChildren: ['main-stack', 'character-stack',
'character-label', 'missing-label', 'detail-label',
'copy-button', 'copy-revealer', 'related-listbox'],
}, class CharacterDialog extends Gtk.Dialog {
_init(params) {
const filtered = Params.filter(params, { character: null,
fontDescription: null });
params = Params.fill(params, { use_header_bar: true,
width_request: 400,
height_request: 400 });
super._init(params);
this._cancellable = new Gio.Cancellable();
this._copy_button.connect('clicked', () => this._copyCharacter());
this._related_listbox.connect('row-selected', (listBox, row) => this._handleRowSelected(listBox, row));
this._relatedButton = new Gtk.ToggleButton({ label: _("See Also") });
this.add_action_widget(this._relatedButton, Gtk.ResponseType.HELP);
this._relatedButton.show();
this._relatedButton.connect('toggled', () => {
if (this._main_stack.visible_child_name == 'character')
this._main_stack.visible_child_name = 'related';
else
this._main_stack.visible_child_name = 'character';
});
this._fontDescription = filtered.fontDescription;
this._setCharacter(filtered.character);
this._copyRevealerTimeoutId = 0;
}
_finishSearch(result) {
let children = this._related_listbox.get_children();
for (let index in children)
this._related_listbox.remove(children[index]);
for (let index = 0; index < result.len; index++) {
let uc = Gc.search_result_get(result, index);
let name = Gc.character_name(uc);
if (name == null)
continue;
let hbox = new Gtk.Box({ orientation: Gtk.Orientation.HORIZONTAL });
let characterLabel = new Gtk.Label({ label: uc,
valign: Gtk.Align.CENTER,
halign: Gtk.Align.CENTER,
width_request: 45 });
characterLabel.get_style_context().add_class('character');
hbox.pack_start(characterLabel, false, false, 2);
let nameLabel = new Gtk.Label({ label: Util.capitalize(name),
halign: Gtk.Align.START,
ellipsize: Pango.EllipsizeMode.END });
hbox.pack_start(nameLabel, true, true, 0);
let row = new Gtk.ListBoxRow();
row._character = uc;
row.add(hbox);
row.show_all();
this._related_listbox.add(row);
}
this._relatedButton.visible =
this._related_listbox.get_children().length > 0;
}
_setCharacter(uc) {
this._character = uc;
let codePoint = Util.toCodePoint(this._character);
let codePointHex = codePoint.toString(16).toUpperCase();
let name = Gc.character_name(this._character);
if (name != null) {
name = Util.capitalize(name);
} else {
name = _("Unicode U+%04s").format(codePointHex);
}
let headerBar = this.get_header_bar();
headerBar.title = name;
this._character_label.override_font(this._fontDescription);
this._character_label.label = this._character;
var pangoContext = this._character_label.get_pango_context();
var pangoLayout = Pango.Layout.new(pangoContext);
pangoLayout.set_text(this._character, -1);
if (pangoLayout.get_unknown_glyphs_count() == 0) {
this._character_stack.visible_child_name = 'character';
} else {
var fontFamily = this._fontDescription.get_family();
this._missing_label.label =
// TRANSLATORS: the first variable is a character, the second is a font
_("%s is not included in %s").format(name, fontFamily);
this._character_stack.visible_child_name = 'missing';
}
this._detail_label.label = _("Unicode U+%04s").format(codePointHex);
this._cancellable.cancel();
this._cancellable.reset();
let criteria = Gc.SearchCriteria.new_related(this._character);
let context = new Gc.SearchContext({ criteria: criteria });
context.search(
-1,
this._cancellable,
(context, res) => {
try {
let result = context.search_finish(res);
this._finishSearch(result);
} catch (e) {
log(`Failed to search related: ${e.message}`);
}
});
this._relatedButton.active = false;
this._main_stack.visible_child_name = 'character';
this._main_stack.show_all();
}
_hideCopyRevealer() {
if (this._copyRevealerTimeoutId > 0) {
GLib.source_remove(this._copyRevealerTimeoutId);
this._copyRevealerTimeoutId = 0;
this._copy_revealer.set_reveal_child(false);
}
}
_clipboardOwnerChanged(clipboard) {
let text = clipboard.wait_for_text();
if (text != this._character)
this._hideCopyRevealer();
}
_copyCharacter() {
if (this._clipboard == null) {
this._clipboard = Gc.gtk_clipboard_get();
let clipboardOwnerChanged =
this._clipboard.connect('owner-change', (clipboard) => this._clipboardOwnerChanged(clipboard));
this.connect('destroy', () => this._clipboard.disconnect(clipboardOwnerChanged));
}
this._clipboard.set_text(this._character, -1);
this.emit('character-copied', this._character);
// Show a feedback message with a revealer. The message is
// hidden after 2 seconds, or when another client set a
// different text to clipboard.
this._hideCopyRevealer();
this._copy_revealer.set_reveal_child(true);
this._copyRevealerTimeoutId =
GLib.timeout_add(GLib.PRIORITY_DEFAULT, 2000, () => this._hideCopyRevealer());
this.connect('destroy', () => {
if (this._copyRevealerTimeoutId > 0)
GLib.source_remove(this._copyRevealerTimeoutId);
});
}
_handleRowSelected(listBox, row) {
if (row != null) {
this._setCharacter(row._character);
let toplevel = this.get_transient_for();
let action = toplevel.lookup_action('character');
action.activate(new GLib.Variant('s', row._character));
}
}
});
(uuay)js/
main.js z // -*- Mode: js; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*-
//
// Copyright (c) 2013 Giovanni Campagna <scampa.giovanni@gmail.com>
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of the GNOME Foundation nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
pkg.initGettext();
pkg.initFormat();
pkg.require({
'Gdk': '3.0',
'Gio': '2.0',
'GLib': '2.0',
'GObject': '2.0',
'Gtk': '3.0',
'Handy': '1',
});
const {GLib, Gio, GObject, Gtk, Handy} = imports.gi;
const Util = imports.util;
const Window = imports.window;
var settings = null;
var application_id = pkg.name;
function initEnvironment() {
window.getApp = function() {
return Gio.Application.get_default();
};
}
var MyApplication = GObject.registerClass({
},class MyApplication extends Gtk.Application {
_init () {
super._init({ application_id: application_id,
flags: Gio.ApplicationFlags.FLAGS_NONE });
GLib.set_application_name(_('Characters'));
}
_onQuit () {
this.quit();
}
_onSearch (action, parameter) {
const window = new Window.MainWindow({ application: this });
window.setSearchKeywords(parameter.get_strv());
window.show();
}
vfunc_startup () {
super.vfunc_startup();
let theme = Gtk.IconTheme.get_default();
theme.add_resource_path('/org/gnome/Characters/icons');
Util.loadStyleSheet('/org/gnome/Characters/application.css');
let styleManager = Handy.StyleManager.get_default();
styleManager.set_color_scheme(Handy.ColorScheme.PREFER_LIGHT);
Util.initActions(this,
[{ name: 'quit',
activate: this._onQuit },
{ name: 'search',
activate: this._onSearch,
parameter_type: new GLib.VariantType('as') }]);
this.set_accels_for_action('app.quit', ['<Primary>q']);
this.set_accels_for_action('win.find', ['<Primary>f']);
this.set_accels_for_action('win.show-primary-menu', ['F10']);
this.set_accels_for_action('win.show-help-overlay', ['<Primary>question']);
settings = Util.getSettings('org.gnome.Characters',
'/org/gnome/Characters/');
log("Characters Application started");
}
vfunc_activate() {
if (!this._appwindow) {
this._appwindow = new Window.MainWindow({ application: this });
}
Handy.init();
this._appwindow.present();
log("Characters Application activated");
}
vfunc_shutdown() {
log("Characters Application exiting");
super.vfunc_shutdown();
}
});
function main(argv) {
initEnvironment();
return (new MyApplication()).run(argv);
}
(uuay)org/ gnome/ Characters/ characterList.js�` // -*- Mode: js; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*-
//
// Copyright (C) 2014-2015 Daiki Ueno <dueno@src.gnome.org>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
const Params = imports.params;
const {Gc, Gdk, GLib, Gio,GObject,Gtk, Pango, PangoCairo, Handy} = imports.gi;
const Cairo = imports.cairo;
const Main = imports.main;
const Util = imports.util;
const BASELINE_OFFSET = 0.85;
const CELLS_PER_ROW = 5;
const NUM_ROWS = 5;
const NUM_COLUMNS = 3;
const CELL_SIZE = 50;
function getCellSize(fontDescription) {
if (fontDescription == null || fontDescription.get_size() == 0)
return CELL_SIZE;
return fontDescription.get_size() * 2 / Pango.SCALE;
}
const CharacterListRow = GObject.registerClass({
}, class CharacterListRow extends GObject.Object {
_init(params) {
const filtered = Params.filter(params, { characters: null,
fontDescription: null,
overlayFontDescription: null });
params = Params.fill(params, {});
super._init(params);
this._characters = filtered.characters;
this._fontDescription = filtered.fontDescription;
this._overlayFontDescription = filtered.overlayFontDescription;
this._styleManager = Handy.StyleManager.get_default();
}
draw(cr, x, y, width, height, styleContext) {
let layout = PangoCairo.create_layout(cr);
layout.set_font_description(this._fontDescription);
this._styleContext = styleContext;
// Draw baseline.
// FIXME: Pick the baseline color from CSS.
let fg_color = this._styleContext.get_color(Gtk.StateFlags.NORMAL);
cr.setSourceRGBA(114.0 / 255.0, 159.0 / 255.0, 207.0 / 255.0, 1.0);
cr.setLineWidth(0.5);
cr.moveTo(x, y + BASELINE_OFFSET * height);
cr.relLineTo(width, 0);
cr.stroke();
cr.setSourceRGBA(fg_color.red, fg_color.green, fg_color.blue, fg_color.alpha);
// Draw characters. Do centering and attach to the baseline.
let cellSize = getCellSize(this._fontDescription);
for (let i in this._characters) {
var cellRect = new Gdk.Rectangle({ x: x + cellSize * i,
y: y,
width: cellSize,
height: cellSize });
if (Gc.character_is_invisible(this._characters[i])) {
this._drawBoundingBox(cr, cellRect, this._characters[i]);
this._drawCharacterName(cr, cellRect, this._characters[i]);
} else {
layout.set_text(this._characters[i], -1);
if (layout.get_unknown_glyphs_count () == 0) {
let layoutBaseline = layout.get_baseline();
let [logicalRect, inkRect] = layout.get_extents();
cr.moveTo(x + cellSize * i - logicalRect.x / Pango.SCALE +
(cellSize - logicalRect.width / Pango.SCALE) / 2,
y + BASELINE_OFFSET * height -
layoutBaseline / Pango.SCALE);
PangoCairo.show_layout(cr, layout);
} else {
this._drawBoundingBox(cr, cellRect, this._characters[i]);
this._drawCharacterName(cr, cellRect, this._characters[i]);
}
}
}
}
_computeBoundingBox(cr, cellRect, uc) {
let layout = PangoCairo.create_layout(cr);
layout.set_font_description(this._fontDescription);
layout.set_text(uc, -1);
let shapeRect;
let layoutBaseline;
if (layout.get_unknown_glyphs_count() == 0) {
let [logicalRect, inkRect] = layout.get_extents();
layoutBaseline = layout.get_baseline();
shapeRect = inkRect;
} else {
// If the character cannot be rendered with the current
// font settings, show a rectangle calculated from the
// base glyphs ('AA').
if (this._baseGlyphRect == null) {
layout.set_text('AA', -1);
let [baseLogicalRect, baseInkRect] = layout.get_extents();
this._baseGlyphLayoutBaseline = layout.get_baseline();
this._baseGlyphRect = baseInkRect;
}
layoutBaseline = this._baseGlyphLayoutBaseline;
shapeRect = new Pango.Rectangle({
x: this._baseGlyphRect.x,
y: this._baseGlyphRect.y,
width: this._baseGlyphRect.width,
height: this._baseGlyphRect.height
});
let characterWidth = Gc.character_width (uc);
if (characterWidth > 1)
shapeRect.width *= characterWidth;
}
shapeRect.x = cellRect.x - shapeRect.x / Pango.SCALE +
(cellRect.width - shapeRect.width / Pango.SCALE) / 2;
shapeRect.y = cellRect.y + BASELINE_OFFSET * cellRect.height -
layoutBaseline / Pango.SCALE;
shapeRect.width = shapeRect.width / Pango.SCALE;
shapeRect.height = shapeRect.height / Pango.SCALE;
return shapeRect;
}
_drawBoundingBox(cr, cellRect, uc) {
cr.save();
cr.rectangle(cellRect.x, cellRect.y, cellRect.width, cellRect.height);
cr.clip();
let layout = PangoCairo.create_layout(cr);
layout.set_font_description(this._fontDescription);
layout.set_text(uc, -1);
let shapeRect = this._computeBoundingBox(cr, cellRect, uc);
let borderWidth = 1;
cr.rectangle(shapeRect.x - borderWidth * 2,
shapeRect.y - borderWidth * 2,
shapeRect.width + borderWidth * 2,
shapeRect.height + borderWidth * 2);
cr.setSourceRGBA(239.0 / 255.0, 239.0 / 255.0, 239.0 / 255.0, 1.0);
cr.fill();
cr.restore();
}
_drawCharacterName(cr, cellRect, uc) {
cr.save();
cr.rectangle(cellRect.x, cellRect.y, cellRect.width, cellRect.height);
cr.clip();
let layout = PangoCairo.create_layout(cr);
layout.set_width(cellRect.width * Pango.SCALE * 0.8);
layout.set_height(cellRect.height * Pango.SCALE * 0.8);
layout.set_wrap(Pango.WrapMode.WORD);
layout.set_ellipsize(Pango.EllipsizeMode.END);
layout.set_alignment(Pango.Alignment.CENTER);
layout.set_font_description(this._overlayFontDescription);
let name = Gc.character_name(uc);
let text = name == null ? _('Unassigned') : Util.capitalize(name);
layout.set_text(text, -1);
let [logicalRect, inkRect] = layout.get_extents();
cr.moveTo(cellRect.x - logicalRect.x / Pango.SCALE +
(cellRect.width - logicalRect.width / Pango.SCALE) / 2,
cellRect.y - logicalRect.y / Pango.SCALE +
(cellRect.height - logicalRect.height / Pango.SCALE) / 2);
let text_color;
if (!this._styleManager.dark) {
text_color = this._styleContext.get_color(Gtk.StateFlags.NORMAL);
} else {
text_color = this._styleContext.get_background_color(Gtk.StateFlags.NORMAL);
}
cr.setSourceRGBA(text_color.red, text_color.green, text_color.blue, text_color.alpha);
PangoCairo.show_layout(cr, layout);
cr.restore();
}
});
const CharacterListWidget = GObject.registerClass({
Signals: {
'character-selected': { param_types: [ GObject.TYPE_STRING ] }
},
}, class CharacterListWidget extends Gtk.DrawingArea {
_init(params) {
const filtered = Params.filter(params, {
fontDescription: null,
numRows: NUM_ROWS
});
params = Params.fill(params, {});
super._init(params);
const context = this.get_style_context();
context.add_class('character-list');
context.save();
this._cellsPerRow = CELLS_PER_ROW;
this._fontDescription = filtered.fontDescription;
this._numRows = filtered.numRows;
this._characters = [];
this._rows = [];
this.add_events(Gdk.EventMask.BUTTON_PRESS_MASK |
Gdk.EventMask.BUTTON_RELEASE_MASK);
this._character = null;
this.drag_source_set(Gdk.ModifierType.BUTTON1_MASK,
null,
Gdk.DragAction.COPY);
this.drag_source_add_text_targets();
}
vfunc_drag_begin(context) {
let cellSize = getCellSize(this._fontDescription);
this._dragSurface = new Cairo.ImageSurface(Cairo.Format.ARGB32,
cellSize,
cellSize);
let cr = new Cairo.Context(this._dragSurface);
cr.setSourceRGBA(1.0, 1.0, 1.0, 1.0);
cr.paint();
cr.setSourceRGBA(0.0, 0.0, 0.0, 1.0);
let row = this._createCharacterListRow([this._character]);
row.draw(cr, 0, 0, cellSize, cellSize, this.get_style_context());
Gtk.drag_set_icon_surface(context, this._dragSurface, 0, 0);
}
vfunc_drag_data_get(context, data, info, time) {
if (this._character != null)
data.set_text(this._character, -1);
}
vfunc_button_press_event(event) {
let allocation = this.get_allocation();
let cellSize = getCellSize(this._fontDescription);
let x = Math.floor(event.x / cellSize);
let y = Math.floor(event.y / cellSize);
let index = y * this._cellsPerRow + x;
if (index < this._characters.length)
this._character = this._characters[index];
else
this._character = null;
return false;
}
vfunc_button_release_event(event) {
if (this._character)
this.emit('character-selected', this._character);
return false;
}
vfunc_get_request_mode() {
return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH;
}
vfunc_get_preferred_height() {
let [minWidth, natWidth] = this.vfunc_get_preferred_width();
return this.vfunc_get_preferred_height_for_width(minWidth);
}
vfunc_get_preferred_height_for_width(width) {
let height = Math.max(this._rows.length, this._numRows) *
getCellSize(this._fontDescription);
return [height, height];
}
vfunc_get_preferred_width() {
return this.vfunc_get_preferred_width_for_height(0);
}
vfunc_get_preferred_width_for_height(height) {
let cellSize = getCellSize(this._fontDescription);
let minWidth = NUM_COLUMNS * cellSize;
let natWidth = Math.max(this._cellsPerRow, NUM_COLUMNS) * cellSize;
return [minWidth, natWidth];
}
vfunc_size_allocate(allocation) {
super.vfunc_size_allocate(allocation);
let cellSize = getCellSize(this._fontDescription);
let cellsPerRow = Math.floor(allocation.width / cellSize);
if (cellsPerRow != this._cellsPerRow) {
// Reflow if the number of cells per row has changed.
this._cellsPerRow = cellsPerRow;
this.setCharacters(this._characters);
}
}
_createCharacterListRow(characters) {
var context = this.get_pango_context();
var fontDescription = context.get_font_description();
fontDescription.set_size(fontDescription.get_size() * 0.8);
let row = new CharacterListRow({
characters: characters,
fontDescription: this._fontDescription,
overlayFontDescription: fontDescription
});
return row;
}
setFontDescription(fontDescription) {
this._fontDescription = fontDescription;
}
setCharacters(characters) {
this._rows = [];
this._characters = characters;
let start = 0, stop = 1;
for (; stop <= characters.length; stop++) {
if (stop % this._cellsPerRow == 0) {
let rowCharacters = characters.slice(start, stop);
let row = this._createCharacterListRow(rowCharacters);
this._rows.push(row);
start = stop;
}
}
if (start != stop - 1) {
let rowCharacters = characters.slice(start, stop);
let row = this._createCharacterListRow(rowCharacters);
this._rows.push(row);
}
this.queue_resize();
this.queue_draw();
}
vfunc_draw(cr) {
// Clear the canvas.
let context = this.get_style_context();
let fg = context.get_color(Gtk.StateFlags.NORMAL);
let bg = context.get_background_color(Gtk.StateFlags.NORMAL);
cr.setSourceRGBA(bg.red, bg.green, bg.blue, bg.alpha);
cr.paint();
cr.setSourceRGBA(fg.red, fg.green, fg.blue, fg.alpha);
// Use device coordinates directly, since PangoCairo doesn't
// work well with scaled matrix:
// https://bugzilla.gnome.org/show_bug.cgi?id=700592
let allocation = this.get_allocation();
// Redraw rows within the clipped region.
let [x1, y1, x2, y2] = cr.clipExtents();
let cellSize = getCellSize(this._fontDescription);
let start = Math.max(0, Math.floor(y1 / cellSize));
let end = Math.min(this._rows.length, Math.ceil(y2 / cellSize));
for (let index = start; index < end; index++) {
this._rows[index].draw(cr, 0, index * cellSize,
allocation.width, cellSize, context);
}
}
});
const MAX_SEARCH_RESULTS = 100;
var FontFilter = GObject.registerClass({
Properties: {
'font': GObject.ParamSpec.string(
'font', '', '',
GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE,
'Cantarell 50')
},
Signals: {
'filter-set': { param_types: [] }
},
}, class FontFilter extends GObject.Object {
get font() {
return this._font;
}
set font(v) {
let fontDescription = Pango.FontDescription.from_string(v);
if (fontDescription.get_size() == 0)
fontDescription.set_size(CELL_SIZE * Pango.SCALE);
if (this._fontDescription &&
fontDescription.equal(this._fontDescription))
return;
this._font = v;
this._fontDescription = fontDescription;
}
get fontDescription() {
if (this._filterFontDescription)
return this._filterFontDescription;
return this._fontDescription;
}
_init(params) {
params = Params.fill(params, {});
super._init(params);
this._fontDescription = null;
this._filterFontDescription = null;
Main.settings.bind('font', this, 'font', Gio.SettingsBindFlags.DEFAULT);
}
setFilterFont(v) {
let fontDescription;
if (v == null) {
fontDescription = null;
} else {
fontDescription = Pango.FontDescription.from_string(v);
fontDescription.set_size(this._fontDescription.get_size());
}
if ((this._filterFontDescription != null && fontDescription == null) ||
(this._filterFontDescription == null && fontDescription != null) ||
(this._filterFontDescription != null && fontDescription != null &&
!fontDescription.equal(this._filterFontDescription))) {
this._filterFontDescription = fontDescription;
this.emit('filter-set');
}
}
apply(widget, characters) {
let fontDescription = this._fontDescription;
if (this._filterFontDescription) {
let context = widget.get_pango_context();
let filterFont = context.load_font(this._filterFontDescription);
let filteredCharacters = [];
for (let index = 0; index < characters.length; index++) {
let uc = characters[index];
if (Gc.pango_context_font_has_glyph(context, filterFont, uc))
filteredCharacters.push(uc);
}
characters = filteredCharacters;
fontDescription = this._filterFontDescription;
}
return [fontDescription, characters];
}
});
var CharacterListView = GObject.registerClass({
Template: 'resource:///org/gnome/Characters/characterlist.ui',
InternalChildren: ['loading-spinner'],
Signals: {
'character-selected': { param_types: [ GObject.TYPE_STRING ] }
},
}, class CharacterListView extends Gtk.Stack {
_init(params) {
const filtered = Params.filter(params, {
fontFilter: null,
});
params = Params.fill(params, {
hexpand: true, vexpand: true,
transition_type: Gtk.StackTransitionType.CROSSFADE
});
super._init(params);
this._fontFilter = filtered.fontFilter;
this._characterList = new CharacterListWidget({
hexpand: true,
vexpand: true,
fontDescription: this._fontFilter.fontDescription
});
this._characterList.connect('character-selected', (w, c) => this.emit('character-selected', c));
let scroll = new Gtk.ScrolledWindow({
hscrollbar_policy: Gtk.PolicyType.NEVER,
visible: true
});
scroll.add(this._characterList);
let context = scroll.get_style_context();
context.add_class('character-list-scroll');
context.save();
this.add_named(scroll, 'character-list');
this.visible_child_name = 'character-list';
this._fontFilter.connect('filter-set', () => this._updateCharacterList());
this._characters = [];
this._spinnerTimeoutId = 0;
this._searchContext = null;
this._cancellable = new Gio.Cancellable();
this._cancellable.connect(() => {
this._stopSpinner();
this._searchContext = null;
this._characters = [];
this._updateCharacterList();
});
scroll.connect('edge-reached', (scrolled, pos) => this._onEdgeReached(scrolled, pos));
scroll.connect('size-allocate', (scrolled, allocation) => this._onSizeAllocate(scrolled, allocation));
}
_startSpinner() {
this._stopSpinner();
this._spinnerTimeoutId =
GLib.timeout_add(GLib.PRIORITY_DEFAULT, 1000, () => {
this._loading_spinner.start();
this.visible_child_name = 'loading';
this.show_all();
});
}
_stopSpinner() {
if (this._spinnerTimeoutId > 0) {
GLib.source_remove(this._spinnerTimeoutId);
this._spinnerTimeoutId = 0;
this._loading_spinner.stop();
}
}
_finishSearch(result) {
this._stopSpinner();
let characters = Util.searchResultToArray(result);
this.setCharacters(characters);
}
setCharacters(characters) {
this._characters = characters;
this._updateCharacterList();
}
_updateCharacterList() {
const [fontDescription, characters] = this._fontFilter.apply(this, this._characters);
this._characterList.setFontDescription(fontDescription);
this._characterList.setCharacters(characters);
if (characters.length == 0) {
this.visible_child_name = 'unavailable';
} else {
this.visible_child_name = 'character-list';
}
this.show_all();
}
_maybeLoadMore() {
if (this._searchContext != null && !this._searchContext.is_finished()) {
this._searchWithContext(this._searchContext, MAX_SEARCH_RESULTS);
}
}
_onEdgeReached(scrolled, pos) {
if (pos == Gtk.PositionType.BOTTOM) {
this._maybeLoadMore();
}
}
get initialSearchCount() {
// Use our parents allocation; we aren't visible before we do the
// initial search, so our allocation is 1x1
let allocation = this.get_parent().get_allocation();
// Sometimes more MAX_SEARCH_RESULTS are visible on screen
// (eg. fullscreen at 1080p). We always present a over-full screen,
// otherwise the lazy loading gets broken
let cellSize = getCellSize(this._fontFilter.fontDescription);
let cellsPerRow = Math.floor(allocation.width / cellSize);
// Ensure the rows cause a scroll
let heightInRows = Math.ceil((allocation.height + 1) / cellSize);
return Math.max(MAX_SEARCH_RESULTS, heightInRows * cellsPerRow);
}
_onSizeAllocate(scrolled, allocation) {
if (this._characters.length < this.initialSearchCount) {
this._maybeLoadMore();
}
}
_addSearchResult(result) {
const characters = Util.searchResultToArray(result);
this.setCharacters(this._characters.concat(characters));
}
_searchWithContext(context, count) {
this._startSpinner();
context.search(count, this._cancellable, (context, res, user_data) => {
this._stopSpinner();
try {
let result = context.search_finish(res);
this._addSearchResult(result);
} catch (e) {
log(`Failed to search: ${e.message}`);
}
});
}
searchByCategory(category) {
this._characters = [];
if ('scripts' in category) {
this.searchByScripts(category.scripts);
return;
}
let criteria = Gc.SearchCriteria.new_category(category.category);
this._searchContext = new Gc.SearchContext({ criteria: criteria });
this._searchWithContext(this._searchContext, this.initialSearchCount);
}
searchByKeywords(keywords) {
const criteria = Gc.SearchCriteria.new_keywords(keywords);
this._searchContext = new Gc.SearchContext({
criteria: criteria,
flags: Gc.SearchFlag.WORD
});
this._searchWithContext(this._searchContext, this.initialSearchCount);
}
searchByScripts(scripts) {
var criteria = Gc.SearchCriteria.new_scripts(scripts);
this._searchContext = new Gc.SearchContext({ criteria: criteria });
this._searchWithContext(this._searchContext, this.initialSearchCount);
}
cancelSearch() {
this._cancellable.cancel();
this._cancellable.reset();
}
});
var RecentCharacterListView = GObject.registerClass({
Signals: {
'character-selected': { param_types: [ GObject.TYPE_STRING ] },
},
}, class RecentCharacterListView extends Gtk.Bin {
_init(params) {
const filtered = Params.filter(params, {
category: null,
fontFilter: null
});
params = Params.fill(params, {
hexpand: true, vexpand: false
});
super._init(params);
this._fontFilter = filtered.fontFilter;
this._characterList = new CharacterListWidget({
hexpand: true,
vexpand: true,
fontDescription: this._fontFilter.fontDescription,
numRows: 0
});
this._characterList.connect('character-selected', (w, c) => this.emit('character-selected', c));
this.add(this._characterList);
this._fontFilter.connect('filter-set', () => this._updateCharacterList());
this._category = filtered.category;
this._characters = [];
}
setCharacters(characters) {
const result = Gc.filter_characters(this._category, characters);
this._characters = Util.searchResultToArray(result);
this._updateCharacterList();
}
_updateCharacterList() {
const [fontDescription, characters] = this._fontFilter.apply(this, this._characters);
this._characterList.setFontDescription(fontDescription);
this._characterList.setCharacters(characters);
this.show_all();
}
});
(uuay)params.js �
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
// Params:
//
// A set of convenience functions for dealing with pseudo-keyword
// arguments.
//
// Examples:
//
// A function with complex arguments
// function myFunction(params) {
// params = Params.parse(params, { myFlags: Flags.NONE,
// anInt: 42,
// aString: 'hello, world!',
// });
// ... params.anInt, params.myFlags, params.aString ...
// }
// myFunction({ anInt: -1 });
//
// Extend a method to allow more params in a subclass
// The superclass can safely use Params.parse(), it won't see
// the extensions.
// const MyClass = new Lang.Class({
// ...
// method: function(params) {
// let mine = Params.filter(params, { anInt: 42 });
// this.parent(params);
// ... mine.anInt ...
// }
// });
// parse:
// @params: caller-provided parameter object, or %null
// @defaults: function-provided defaults object
//
// Examines @params and fills in default values from @defaults for
// any properties in @defaults that don't appear in @params.
// This function will throw a Error if @params contains a property
// that is not recognized. Use fill() or filter() if you don't
// want that.
//
// If @params is %null, this returns the values from @defaults.
//
// Return value: a new object, containing the merged parameters from
// @params and @defaults
function parse(params, defaults) {
let ret = {}, prop;
params = params || {};
for (prop in params) {
if (!(prop in defaults))
throw new Error('Unrecognized parameter "' + prop + '"');
ret[prop] = params[prop];
}
for (prop in defaults) {
if (!(prop in params))
ret[prop] = defaults[prop];
}
return ret;
}
// fill:
// @params: caller-provided parameter object, or %null
// @defaults: function-provided defaults object
//
// Examines @params and fills in default values from @defaults
// for any properties in @defaults that don't appear in @params.
//
// Differently from parse(), this function does not throw for
// unrecognized parameters.
//
// Return value: a new object, containing the merged parameters from
// @params and @defaults
function fill(params, defaults) {
let ret = {}, prop;
params = params || {};
for (prop in params)
ret[prop] = params[prop];
for (prop in defaults) {
if (!(prop in ret))
ret[prop] = defaults[prop];
}
return ret;
}
// filter:
// @params: caller-provided parameter object, or %null
// @defaults: function-provided defaults object
//
// Examines @params and returns an object containing the
// same properties as @defaults, but with values taken from
// @params where available.
// Then it removes from @params all matched properties.
//
// This is similar to parse(), but it accepts unknown properties
// and modifies @params for known ones.
//
// If @params is %null, this returns the values from @defaults.
//
// Return value: a new object, containing the merged parameters from
// @params and @defaults
function filter(params, defaults) {
let ret = {}, prop;
params = params || {};
for (prop in defaults) {
if (!(prop in params))
ret[prop] = defaults[prop];
}
for (prop in params) {
if (prop in defaults) {
ret[prop] = params[prop];
delete params[prop];
}
}
return ret;
}
(uuay)menu.js � // -*- Mode: js; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*-
//
// Copyright (C) 2015 Daiki Ueno <dueno@src.gnome.org>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
const {GLib, GObject, Gtk} = imports.gi;
const Params = imports.params;
var MenuPopover = GObject.registerClass({
Template: 'resource:///org/gnome/Characters/menu.ui',
InternalChildren: ['search-entry', 'font-listbox'],
}, class MenuPopover extends Gtk.Popover {
_createFontListRow(title, family) {
const row = new Gtk.ListBoxRow({ visible: true });
row.get_style_context().add_class('font');
row._family = family;
let label = new Gtk.Label({ label: title,
visible: true,
halign: Gtk.Align.START });
label.get_style_context().add_class('font-label');
row.add(label);
return row;
}
_init(params) {
params = Params.fill(params, {});
super._init(params);
let row = this._createFontListRow(_("None"), 'None');
this._font_listbox.add(row);
let context = this.get_pango_context();
let families = context.list_families();
families = families.sort(function(a, b) {
return a.get_name().localeCompare(b.get_name());
});
for (let index in families) {
row = this._createFontListRow(families[index].get_name(),
families[index].get_name());
this._font_listbox.add(row);
}
this._keywords = [];
this._search_entry.connect('search-changed', (entry) => this._handleSearchChanged(entry));
this._font_listbox.connect('row-activated', (listBox, row) => this._handleRowActivated(listBox, row));
this._font_listbox.set_filter_func((row) => this._filterFunc(row));
// This silents warning at Characters exit about this widget being
// visible but not mapped. Borrowed from Maps.
this.connect('unmap', function(popover) {
popover._font_listbox.unselect_all();
popover.hide();
});
}
_handleSearchChanged(entry) {
let text = entry.get_text().replace(/^\s+|\s+$/g, '');
let keywords = text == '' ? [] : text.split(/\s+/);
this._keywords = keywords.map(x => x.toLowerCase());
this._font_listbox.invalidate_filter();
return true;
}
_handleRowActivated(listBox, row) {
if (row != null) {
let toplevel = this.get_toplevel();
let action = toplevel.lookup_action('filter-font');
action.activate(new GLib.Variant('s', row._family));
}
}
_filterFunc(row) {
if (this._keywords.length == 0)
return true;
if (row._family == 'None')
return true;
let nameWords = row._family.split(/\s+/).map(x => x.toLowerCase());
return this._keywords.every(function(keyword, index, array) {
return nameWords.some(function(nameWord, index, array) {
return nameWord.indexOf(keyword) >= 0;
});
});
}
});
(uuay)util.jsP // -*- Mode: js; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*-
//
// Copyright (c) 2013 Giovanni Campagna <scampa.giovanni@gmail.com>
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of the GNOME Foundation nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
const {Gc, Gdk, Gio, GObject, Gtk} = imports.gi;
const Lang = imports.lang;
const Params = imports.params;
const System = imports.system;
function loadUI(resourcePath, objects) {
let ui = new Gtk.Builder();
if (objects) {
for (let o in objects)
ui.expose_object(o, objects[o]);
}
ui.add_from_resource(resourcePath);
return ui;
}
function loadStyleSheet(resource) {
let provider = new Gtk.CssProvider();
provider.load_from_file(Gio.File.new_for_uri('resource://' + resource));
Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(),
provider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
}
function initActions(actionMap, simpleActionEntries, context) {
simpleActionEntries.forEach(function(entry) {
let filtered = Params.filter(entry, { activate: null,
state_changed: null,
context: null });
let action = new Gio.SimpleAction(entry);
let context = filtered.context || actionMap;
if (filtered.activate)
action.connect('activate', filtered.activate.bind(context));
if (filtered.state_changed)
action.connect('state-changed', filtered.state_changed.bind(context));
actionMap.add_action(action);
});
}
function arrayEqual(one, two) {
if (one.length != two.length)
return false;
for (let i = 0; i < one.length; i++)
if (one[i] != two[i])
return false;
return true;
}
function getSettings(schemaId, path) {
const GioSSS = Gio.SettingsSchemaSource;
let schemaSource;
if (!pkg.moduledir.startsWith('resource://')) {
// Running from the source tree
schemaSource = GioSSS.new_from_directory(pkg.pkgdatadir,
GioSSS.get_default(),
false);
} else {
schemaSource = GioSSS.get_default();
}
let schemaObj = schemaSource.lookup(schemaId, true);
if (!schemaObj) {
log('Missing GSettings schema ' + schemaId);
System.exit(1);
}
if (path === undefined)
return new Gio.Settings({ settings_schema: schemaObj });
else
return new Gio.Settings({ settings_schema: schemaObj,
path: path });
}
function assertEqual(one, two) {
if (one != two)
throw Error('Assertion failed: ' + one + ' != ' + two);
}
function assertNotEqual(one, two) {
if (one == two)
throw Error('Assertion failed: ' + one + ' == ' + two);
}
function capitalizeWord(w) {
if (w.length > 0)
return w[0].toUpperCase() + w.slice(1).toLowerCase()
return w;
}
function capitalize(s) {
return s.split(/\s+/).map(function(w) {
let acronyms = ["CJK"];
if (acronyms.indexOf(w) > -1)
return w;
let prefixes = ["IDEOGRAPH-", "SELECTOR-"];
for (let index in prefixes) {
let prefix = prefixes[index];
if (w.startsWith(prefix))
return capitalizeWord(prefix) + w.slice(prefix.length);
}
return capitalizeWord(w);
}).join(' ');
}
function toCodePoint(s) {
let codePoint = s.charCodeAt(0);
if (codePoint >= 0xD800 && codePoint <= 0xDBFF) {
let high = codePoint;
let low = s.charCodeAt(1);
codePoint = 0x10000 + (high - 0xD800) * 0x400 + (low - 0xDC00);
}
return codePoint;
}
function searchResultToArray(result) {
let characters = [];
for (let index = 0; index < result.len; index++) {
characters.push(Gc.search_result_get(result, index));
}
return characters;
}
(uuay)categoryList.js u9 // -*- Mode: js; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*-
//
// Copyright (C) 2014-2017 Daiki Ueno <dueno@src.gnome.org>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
const {Gc, GLib, GObject, Gtk, GnomeDesktop} = imports.gi;
const Gettext = imports.gettext;
const Params = imports.params;
const Util = imports.util;
const CategoryList = [
{
name: 'emojis',
category: Gc.Category.EMOJI,
title: N_('Emojis'),
icon_name: 'characters-emoji-smileys',
},
{
name: 'letters',
category: Gc.Category.LETTER,
title: N_('Letters & Symbols'),
icon_name: 'characters-latin-symbolic',
}
];
const LetterCategoryList = [
{
name: 'punctuation',
category: Gc.Category.LETTER_PUNCTUATION,
title: N_('Punctuation'),
icon_name: 'characters-punctuation-symbolic',
},
{
name: 'arrow',
category: Gc.Category.LETTER_ARROW,
title: N_('Arrows'),
icon_name: 'characters-arrow-symbolic',
},
{
name: 'bullet',
category: Gc.Category.LETTER_BULLET,
title: N_('Bullets'),
icon_name: 'characters-bullet-symbolic',
},
{
name: 'picture',
category: Gc.Category.LETTER_PICTURE,
title: N_('Pictures'),
icon_name: 'characters-picture-symbolic',
},
{
name: 'currency',
category: Gc.Category.LETTER_CURRENCY,
title: N_('Currencies'),
icon_name: 'characters-currency-symbolic',
},
{
name: 'math',
category: Gc.Category.LETTER_MATH,
title: N_('Math'),
icon_name: 'characters-math-symbolic',
},
{
name: 'letters',
category: Gc.Category.LETTER_LATIN,
title: N_('Letters'),
icon_name: 'characters-latin-symbolic',
}
];
const EmojiCategoryList = [
{
name: 'emoji-smileys',
category: Gc.Category.EMOJI_SMILEYS,
title: N_('Smileys & People'),
icon_name: 'characters-emoji-smileys',
},
{
name: 'emoji-animals',
category: Gc.Category.EMOJI_ANIMALS,
title: N_('Animals & Nature'),
icon_name: 'characters-emoji-animals',
},
{
name: 'emoji-food',
category: Gc.Category.EMOJI_FOOD,
title: N_('Food & Drink'),
icon_name: 'characters-emoji-food',
},
{
name: 'emoji-activities',
category: Gc.Category.EMOJI_ACTIVITIES,
title: N_('Activities'),
icon_name: 'characters-emoji-activities',
},
{
name: 'emoji-travel',
category: Gc.Category.EMOJI_TRAVEL,
title: N_('Travel & Places'),
icon_name: 'characters-emoji-travel',
},
{
name: 'emoji-objects',
category: Gc.Category.EMOJI_OBJECTS,
title: N_('Objects'),
icon_name: 'characters-emoji-objects',
},
{
name: 'emoji-symbols',
category: Gc.Category.EMOJI_SYMBOLS,
title: N_('Symbols'),
icon_name: 'characters-emoji-symbols',
},
{
name: 'emoji-flags',
category: Gc.Category.EMOJI_FLAGS,
title: N_('Flags'),
icon_name: 'characters-emoji-flags',
}
];
const CategoryListRowWidget = GObject.registerClass({
}, class CategoryListRowWidget extends Gtk.ListBoxRow {
_init (params, category) {
params = Params.fill(params, {});
super._init(params);
this.category = category;
this.get_accessible().accessible_name =
_('%s Category List Row').format(category.title);
let hbox = new Gtk.Box({ orientation: Gtk.Orientation.HORIZONTAL });
this.add(hbox);
let image = Gtk.Image.new_from_icon_name(category.icon_name, Gtk.IconSize.LARGE_TOOLBAR);
image.get_style_context().add_class('category-icon');
hbox.pack_start(image, false, false, 2);
let label = new Gtk.Label({ label: Gettext.gettext(category.title),
halign: Gtk.Align.START });
label.get_style_context().add_class('category-label');
hbox.pack_start(label, true, true, 0);
}
});
const CategoryListWidget = GObject.registerClass({
}, class CategoryListWidget extends Gtk.ListBox {
_init(params) {
const filtered = Params.filter(params, { categoryList: null });
params = Params.fill(params, {});
super._init(params);
this._categoryList = filtered.categoryList;
this.populateCategoryList();
this._lastSelectedRow = null;
for (let index in this._categoryList) {
let category = this._categoryList[index];
let rowWidget = new CategoryListRowWidget({}, category);
rowWidget.get_style_context().add_class('category');
this.add(rowWidget);
}
}
vfunc_row_selected(row) {
if (row != null && row.selectable) {
let toplevel = row.get_toplevel();
let action = toplevel.lookup_action('category');
action.activate(new GLib.Variant('s', row.category.name));
this._lastSelectedRow = row;
}
}
populateCategoryList() {
}
getCategoryList() {
return this._categoryList;
}
getCategory(name) {
for (let index in this._categoryList) {
let category = this._categoryList[index];
if (category.name == name)
return category;
}
return null;
}
restorePreviousSelection() {
if (this._lastSelectedRow) {
this.select_row(this._lastSelectedRow)
}
}
unselect() {
let selected = this.get_selected_row()
if (selected)
this.unselect_row(selected)
}
});
const LetterCategoryListWidget = GObject.registerClass({
}, class LetterCategoryListWidget extends CategoryListWidget {
_finishListEngines(sources, bus, res) {
try {
let engines = bus.list_engines_async_finish(res);
if (engines) {
for (let j in engines) {
let engine = engines[j];
let language = engine.get_language();
if (language != null)
this._ibusLanguageList[engine.get_name()] = language;
}
}
} catch (e) {
log(`Failed to list engines: ${e.message}`);
}
this._finishBuildScriptList(sources);
}
_ensureIBusLanguageList(sources) {
if (this._ibusLanguageList != null)
return;
this._ibusLanguageList = {};
// Don't assume IBus is always available.
let ibus;
try {
ibus = imports.gi.IBus;
} catch (e) {
this._finishBuildScriptList(sources);
return;
}
ibus.init();
let bus = new ibus.Bus();
if (bus.is_connected()) {
bus.list_engines_async(-1, null, (sources, bus, res) => this._finishListEngines(sources, bus, res));
} else
this._finishBuildScriptList(sources);
}
_finishBuildScriptList(sources) {
let xkbInfo = new GnomeDesktop.XkbInfo();
let languages = [];
for (let i in sources) {
let [type, id] = sources[i];
switch (type) {
case 'xkb':
// FIXME: Remove this check once gnome-desktop gets the
// support for that.
if (xkbInfo.get_languages_for_layout) {
languages = languages.concat(
xkbInfo.get_languages_for_layout(id));
}
break;
case 'ibus':
if (id in this._ibusLanguageList)
languages.push(this._ibusLanguageList[id]);
break;
}
}
// Add current locale language to languages.
languages.push(Gc.get_current_language());
let allScripts = [];
for (let i in languages) {
let language = GnomeDesktop.normalize_locale(languages[i]);
if (language == null)
continue;
let scripts = Gc.get_scripts_for_language(languages[i]);
for (let j in scripts) {
let script = scripts[j];
// Exclude Latin and Han, since Latin is always added
// at the top and Han contains too many characters.
if (['Latin', 'Han'].indexOf(script) >= 0)
continue;
if (allScripts.indexOf(script) >= 0)
continue;
allScripts.push(script);
}
}
allScripts.unshift('Latin');
let category = this.getCategory('letters');
category.scripts = allScripts;
}
populateCategoryList() {
// Populate the "scripts" element of the "Letter" category
// object, based on the current locale and the input-sources
// settings.
//
// This works asynchronously, in the following call flow:
//
// _buildScriptList()
// if an IBus input-source is configured:
// _ensureIBusLanguageList()
// ibus_bus_list_engines_async()
// _finishListEngines()
// _finishBuildScriptList()
// else:
// _finishBuildScriptList()
//
let settings =
Util.getSettings('org.gnome.desktop.input-sources',
'/org/gnome/desktop/input-sources/');
if (settings) {
let sources = settings.get_value('sources').deep_unpack();
let hasIBus = sources.some(function(current, index, array) {
return current[0] == 'ibus';
});
if (hasIBus)
this._ensureIBusLanguageList(sources);
else
this._finishBuildScriptList(sources);
}
}
});
const EmojiCategoryListWidget = GObject.registerClass({
}, class EmojiCategoryListWidget extends CategoryListWidget {
_init(params) {
params = Params.fill(params, {});
super._init(params);
}
getCategory(name) {
return super.getCategory(name);
}
});
const RecentCategoryListWidget = GObject.registerClass({
}, class RecentCategoryListWidget extends CategoryListWidget {
_init(params) {
super._init(params);
this.recentCategory = {
name: 'recent',
category: Gc.Category.NONE,
title: N_('Recently Used'),
icon_name: 'document-open-recent-symbolic',
};
this.recentRow = new CategoryListRowWidget({}, this.recentCategory);
this.recentRow.get_style_context().add_class('category');
this.recentRow.get_style_context().add_class('recent-category');
this.add(this.recentRow)
}
getCategory(name) {
return this.recentCategory;
}
});
var CategoryListView = GObject.registerClass({
}, class CategoryListView extends Gtk.Box {
_init(params) {
params = Params.fill(params, {
hexpand: true, vexpand: true,
orientation: Gtk.Orientation.VERTICAL,
});
this._lastSelectedList = null;
super._init(params);
this.get_style_context().add_class('categories-list');
this._recentCategoryList = new RecentCategoryListWidget();
this._recentCategoryList.connect('row-selected', (list, row) => {
this._letterCategoryList.unselect();
this._emojiCategoryList.unselect();
this._lastSelectedList = list;
list.select_row(row);
});
this.add(this._recentCategoryList)
this.add(new Gtk.Separator({orientation: Gtk.Orientation.HORIZONTAL}));
let emojis_label = new Gtk.Label ({
label: CategoryList[0].title,
halign: Gtk.Align.START,
margin_top: 12,
margin_start: 12,
margin_bottom: 12,
margin_end: 12,
});
emojis_label.get_style_context().add_class("heading");
this.add(emojis_label);
this._emojiCategoryList = new EmojiCategoryListWidget({
categoryList: EmojiCategoryList
});
this._emojiCategoryList.connect('row-selected', (list, row) => {
this._letterCategoryList.unselect();
this._recentCategoryList.unselect();
this._lastSelectedList = list;
list.select_row(row);
});
this.add(this._emojiCategoryList);
let letters_label = new Gtk.Label ({
label: CategoryList[1].title,
halign: Gtk.Align.START,
margin_top: 12,
margin_start: 12,
margin_bottom: 12,
margin_end: 12,
});
letters_label.get_style_context().add_class("heading");
this.add(letters_label);
this._letterCategoryList = new LetterCategoryListWidget({
categoryList: LetterCategoryList
});
this._letterCategoryList.connect('row-selected', (list, row) => {
this._emojiCategoryList.unselect();
this._recentCategoryList.unselect();
this._lastSelectedList = list;
list.select_row(row);
});
this.add(this._letterCategoryList);
this._categoryList = CategoryList.slice();
}
getCategoryByName(name) {
switch (name) {
case 'emojis':
return this._emojiCategoryList
case 'recent':
return this._recentCategoryList
default:
return this._letterCategoryList
}
}
getCategoryList() {
return this._categoryList;
}
get selectedList() {
return this._lastSelectedList
}
restorePreviousSelection() {
if (this._lastSelectedList) {
this._lastSelectedList.restorePreviousSelection()
}
}
});
(uuay)window.js �E // -*- Mode: js; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*-
//
// Copyright (c) 2013 Giovanni Campagna <scampa.giovanni@gmail.com>
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of the GNOME Foundation nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
const {Gio, GLib, GObject, Gtk, Handy } = imports.gi;
const Params = imports.params;
const CategoryList = imports.categoryList;
const Character = imports.characterDialog;
const CharacterList = imports.characterList;
const Menu = imports.menu;
const Gettext = imports.gettext;
const Main = imports.main;
const Util = imports.util;
var MainWindow = GObject.registerClass({
Template: 'resource:///org/gnome/Characters/mainwindow.ui',
InternalChildren: [
'main-headerbar', 'search-active-button',
'search-bar', 'search-entry', 'back-button',
'menu-button', 'container', 'sidebar',
'leaflet'
],
Properties: {
'search-active': GObject.ParamSpec.boolean(
'search-active', '', '',
GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE, false)
},
}, class MainWindow extends Handy.ApplicationWindow {
_init(params) {
params = Params.fill(params, { title: GLib.get_application_name() });
super._init(params);
this._searchActive = false;
this._searchKeywords = [];
Util.initActions(this,
[{ name: 'about',
activate: this._about },
{ name: 'search-active',
activate: this._toggleSearch,
parameter_type: new GLib.VariantType('b'),
state: new GLib.Variant('b', false) },
{ name: 'find',
activate: this._find },
{ name: 'category',
activate: this._category,
parameter_type: new GLib.VariantType('s'),
state: new GLib.Variant('s', 'emoji-smileys') },
{ name: 'character',
activate: this._character,
parameter_type: new GLib.VariantType('s') },
{ name: 'filter-font',
activate: this._filterFont,
parameter_type: new GLib.VariantType('s') },
{
name: 'show-primary-menu',
activate: this._togglePrimaryMenu,
state: new GLib.Variant('b', false),
}]);
this.bind_property('search-active', this._search_active_button, 'active',
GObject.BindingFlags.SYNC_CREATE |
GObject.BindingFlags.BIDIRECTIONAL);
this.bind_property('search-active',
this._search_bar,
'search-mode-enabled',
GObject.BindingFlags.SYNC_CREATE |
GObject.BindingFlags.BIDIRECTIONAL);
this._search_bar.connect_entry(this._search_entry);
this._search_entry.connect('search-changed', (entry) => this._handleSearchChanged(entry));
this._back_button.connect('clicked', () => {
this._leaflet.navigate(Handy.NavigationDirection.BACK);
});
this._menu_popover = new Menu.MenuPopover({});
this._menu_button.set_popover(this._menu_popover);
this._categoryListView =
new CategoryList.CategoryListView({ vexpand: true });
this._categoryListView.show_all();
let scroll = new Gtk.ScrolledWindow({
hscrollbar_policy: Gtk.PolicyType.NEVER,
hexpand: false,
visible: true,
});
scroll.add(this._categoryListView);
this._sidebar.add(scroll);
this._mainView = new MainView({
categoryListView: this._categoryListView
});
this._container.pack_start(this._mainView, true, true, 0);
let builder = Gtk.Builder.new_from_resource('/org/gnome/Characters/shortcuts.ui');
let helpOverlay = builder.get_object("shortcuts");
this.set_help_overlay(helpOverlay);
// Due to limitations of gobject-introspection wrt GdkEvent
// and GdkEventKey, this needs to be a signal handler
this.connect('key-press-event', (self, event) => this._handleKeyPress(self, event));
}
vfunc_map() {
super.vfunc_map();
this._selectFirstSubcategory();
}
_togglePrimaryMenu(action) {
let state = action.get_state().get_boolean();
action.set_state(GLib.Variant.new_boolean(!state));
}
// Select the first subcategory which contains at least one character.
_selectFirstSubcategory() {
let categoryList;
if (this._mainView.recentCharacters.length !== 0) {
categoryList = this._categoryListView.getCategoryByName('recent');
} else {
categoryList = this._categoryListView.getCategoryByName('emojis');
}
categoryList.select_row(categoryList.get_row_at_index(0));
}
get search_active() {
return this._searchActive;
}
set search_active(v) {
if (this._searchActive == v)
return;
this._searchActive = v;
if (this._searchActive) {
let categoryList = this._categoryListView.selectedList;
categoryList.unselect_all();
this._updateTitle(_("Search Result"));
} else {
this._categoryListView.restorePreviousSelection();
}
this.notify('search-active');
}
_handleSearchChanged(entry) {
const text = entry.get_text().replace(/^\s+|\s+$/g, '');
let keywords = text == '' ? [] : text.split(/\s+/);
keywords = keywords.map(x => x.toUpperCase());
if (keywords != this._searchKeywords) {
this._mainView.cancelSearch();
this._searchKeywords = keywords;
if (this._searchKeywords.length > 0)
this._mainView.searchByKeywords(this._searchKeywords);
}
return true;
}
_handleKeyPress(self, event) {
if (this._menu_popover.visible)
return false;
return this._search_bar.handle_event(event);
}
_about() {
const aboutDialog = new Gtk.AboutDialog(
{ artists: [ 'Allan Day <allanpday@gmail.com>',
'Jakub Steiner <jimmac@gmail.com>' ],
authors: [ 'Daiki Ueno <dueno@src.gnome.org>',
'Giovanni Campagna <scampa.giovanni@gmail.com>' ],
// TRANSLATORS: put your names here, one name per line.
translator_credits: _("translator-credits"),
program_name: _("GNOME Characters"),
comments: _("Character Map"),
copyright: 'Copyright 2014-2018 Daiki Ueno',
license_type: Gtk.License.GPL_2_0,
logo_icon_name: Main.application_id,
version: pkg.version,
website: 'https://wiki.gnome.org/Apps/Characters',
wrap_license: true,
modal: true,
transient_for: this
});
aboutDialog.show();
aboutDialog.connect('response', function() {
aboutDialog.destroy();
});
}
_updateTitle(title) {
if (this._mainView.filterFontFamily) {
this._main_headerbar.title =
_("%s (%s only)").format(Gettext.gettext(title),
this._mainView.filterFontFamily);
} else {
this._main_headerbar.title = Gettext.gettext(title);
}
}
_category(action, v) {
this.search_active = false;
let [name, length] = v.get_string();
let categoryName;
if(name.startsWith("emoji")) {
categoryName = "emojis";
} else if(name === "recent") {
categoryName = "recent";
} else {
categoryName = "letters";
}
let categoryList = this._categoryListView.getCategoryByName(categoryName);
let category = categoryList.getCategory(name);
if (category) {
this._mainView.setPage(category);
this._updateTitle(category.title);
this._leaflet.navigate(Handy.NavigationDirection.FORWARD);
}
}
_character(action, v) {
const [uc, length] = v.get_string();
this._mainView.addToRecent(uc);
}
_filterFont(action, v) {
let [family, length] = v.get_string();
if (family == 'None')
family = null;
this._mainView.filterFontFamily = family;
this._updateTitle(this._mainView.visible_child.title);
this._menu_popover.hide();
}
_find() {
this.search_active = !this.search_active;
}
setSearchKeywords(keywords) {
this.search_active = keywords.length > 0;
this._search_entry.set_text(keywords.join(' '));
}
});
const MainView = GObject.registerClass({
Template: 'resource:///org/gnome/Characters/mainview.ui',
Properties: {
'max-recent-characters': GObject.ParamSpec.uint(
'max-recent-characters', '', '',
GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE,
0, GLib.MAXUINT32, 100)
},
}, class MainView extends Gtk.Stack {
get max_recent_characters() {
return this._maxRecentCharacters;
}
set max_recent_characters(v) {
this._maxRecentCharacters = v;
if (this.recentCharacters.length > this._maxRecentCharacters)
this.recentCharacters = this.recentCharacters.slice(
0, this._maxRecentCharacters);
}
get filterFontFamily() {
return this._filterFontFamily;
}
set filterFontFamily(family) {
this._filterFontFamily = family;
this._fontFilter.setFilterFont(this._filterFontFamily);
}
_init(params) {
const filtered = Params.filter(params, { categoryListView: null });
params = Params.fill(params, {
hexpand: true, vexpand: true,
transition_type: Gtk.StackTransitionType.CROSSFADE
});
super._init(params);
this._fontFilter = new CharacterList.FontFilter({});
this._filterFontFamily = null;
this._characterLists = {};
this._recentCharacterLists = {};
this._categoryListView = filtered.categoryListView;
let characterList;
let categories = this._categoryListView.getCategoryList();
let recentBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL,
hexpand: true, vexpand: false });
for (let i in categories) {
let category = categories[i];
let categoryList = this._categoryListView.getCategoryByName(category.name);
let subcategories = categoryList.getCategoryList();
for (let j in subcategories) {
let subcategory = subcategories[j];
characterList = this._createCharacterList(
subcategory.name,
_('%s Character List').format(subcategory.title));
// FIXME: Can't use GtkContainer.child_get_property.
characterList.title = subcategory.title;
this.add_titled(characterList, subcategory.name, subcategory.title);
}
characterList = this._createRecentCharacterList(
category.name,
// TRANSLATORS: %s will be either 'emojis' or 'letters'
_('Recently Used %s Character List').format(category.title),
category.category);
this._recentCharacterLists[category.name] = characterList;
if (i > 0) {
let separator = new Gtk.Separator({});
recentBox.pack_start(separator, false, false, 0);
}
recentBox.pack_start(characterList, false, false, 0);
}
let scroll = new Gtk.ScrolledWindow({
hscrollbar_policy: Gtk.PolicyType.NEVER,
hexpand: false,
});
scroll.add(recentBox);
recentBox.show_all();
scroll.show_all();
// FIXME: Can't use GtkContainer.child_get_property.
scroll.title = _('Recently Used');
this.add_titled(scroll, 'recent', scroll.title);
characterList = this._createCharacterList(
'search-result', _('Search Result Character List'));
// FIXME: Can't use GtkContainer.child_get_property.
characterList.title = _("Search Result");
this.add_named(characterList, 'search-result');
// FIXME: Can't use GSettings.bind with 'as' from Gjs
let recentCharacters = Main.settings.get_value('recent-characters');
this.recentCharacters = recentCharacters.get_strv();
this._maxRecentCharacters = 100;
Main.settings.bind('max-recent-characters', this,
'max-recent-characters',
Gio.SettingsBindFlags.DEFAULT);
}
_createCharacterList(name, accessible_name) {
const characterList = new CharacterList.CharacterListView({
fontFilter: this._fontFilter,
});
characterList.get_accessible().accessible_name = accessible_name;
characterList.connect('character-selected', (widget, uc) => this._handleCharacterSelected(widget, uc));
this._characterLists[name] = characterList;
return characterList;
}
_createRecentCharacterList(name, accessible_name, category) {
const characterList = new CharacterList.RecentCharacterListView({
fontFilter: this._fontFilter,
category: category
});
characterList.get_accessible().accessible_name = accessible_name;
characterList.connect('character-selected', (widget, uc) => this._handleCharacterSelected(widget, uc));
this._characterLists[name] = characterList;
return characterList;
}
searchByKeywords(keywords) {
this.visible_child_name = 'search-result';
this.visible_child.searchByKeywords(keywords);
}
cancelSearch() {
const characterList = this.get_child_by_name('search-result');
characterList.cancelSearch();
}
setPage(category) {
if (category.name === 'recent') {
if (this.recentCharacters.length === 0)
this.visible_child_name = 'empty-recent';
else {
let categories = this._categoryListView.getCategoryList();
for (let i in categories) {
let category = categories[i];
let characterList = this._recentCharacterLists[category.name];
characterList.setCharacters(this.recentCharacters);
}
this.visible_child_name = 'recent';
}
} else {
let characterList = this.get_child_by_name(category.name);
characterList.searchByCategory(category);
this.visible_child = characterList;
}
}
addToRecent(uc) {
if (this.recentCharacters.indexOf(uc) < 0) {
this.recentCharacters.unshift(uc);
if (this.recentCharacters.length > this._maxRecentCharacters)
this.recentCharacters = this.recentCharacters.slice(
0, this._maxRecentCharacters);
Main.settings.set_value(
'recent-characters',
GLib.Variant.new_strv(this.recentCharacters));
}
}
_addToRecent(widget, uc) {
this.addToRecent(uc);
}
_handleCharacterSelected(widget, uc) {
const dialog = new Character.CharacterDialog({
character: uc,
modal: true,
transient_for: this.get_toplevel(),
fontDescription: this._fontFilter.fontDescription
});
dialog.show();
dialog.connect('character-copied', (widget, uc) => this._addToRecent(widget, uc));
dialog.connect('response', function(self, response_id) {
if (response_id == Gtk.ResponseType.CLOSE)
dialog.destroy();
});
}
});
(uuay)