403Webshell
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 :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /usr/share/org.gnome.Characters/org.gnome.Characters.src.gresource
GVariant�(
	
Ե�����L���Xp��v�3!KFv�3!L8!X!{W�yX!v`!�1KP��1L�1�1��$0�1L�12����2L22��.G2v 2$��g�$�	v0���+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.jsz// -*- 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.jsu9// -*- 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)

Youez - 2016 - github.com/yon3zu
LinuXploit