// -*- 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 Lang = imports.lang;
const Params = imports.params;
const GnomeDesktop = imports.gi.GnomeDesktop;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const Gtk = imports.gi.Gtk;
const Gettext = imports.gettext;
const Gc = imports.gi.Gc;
const Util = imports.util;
const CategoryList = [
{
name: 'emojis',
category: Gc.Category.EMOJI,
title: N_('Emojis'),
icon_name: 'characters-emoji-smileys',
action_name: 'category'
},
{
name: 'letters',
category: Gc.Category.LETTER,
title: N_('Letters & Symbols'),
icon_name: 'characters-latin-symbolic',
action_name: 'category'
}
];
const LetterCategoryList = [
{
name: 'punctuation',
category: Gc.Category.LETTER_PUNCTUATION,
title: N_('Punctuation'),
icon_name: 'characters-punctuation-symbolic',
action_name: 'subcategory'
},
{
name: 'arrow',
category: Gc.Category.LETTER_ARROW,
title: N_('Arrows'),
icon_name: 'characters-arrow-symbolic',
action_name: 'subcategory'
},
{
name: 'bullet',
category: Gc.Category.LETTER_BULLET,
title: N_('Bullets'),
icon_name: 'characters-bullet-symbolic',
action_name: 'subcategory'
},
{
name: 'picture',
category: Gc.Category.LETTER_PICTURE,
title: N_('Pictures'),
icon_name: 'characters-picture-symbolic',
action_name: 'subcategory'
},
{
name: 'currency',
category: Gc.Category.LETTER_CURRENCY,
title: N_('Currencies'),
icon_name: 'characters-currency-symbolic',
action_name: 'subcategory'
},
{
name: 'math',
category: Gc.Category.LETTER_MATH,
title: N_('Math'),
icon_name: 'characters-math-symbolic',
action_name: 'subcategory'
},
{
name: 'letters',
category: Gc.Category.LETTER_LATIN,
title: N_('Letters'),
icon_name: 'characters-latin-symbolic',
action_name: 'subcategory'
}
];
const EmojiCategoryList = [
{
name: 'emoji-smileys',
category: Gc.Category.EMOJI_SMILEYS,
title: N_('Smileys & People'),
icon_name: 'characters-emoji-smileys',
action_name: 'subcategory'
},
{
name: 'emoji-animals',
category: Gc.Category.EMOJI_ANIMALS,
title: N_('Animals & Nature'),
icon_name: 'characters-emoji-animals',
action_name: 'subcategory'
},
{
name: 'emoji-food',
category: Gc.Category.EMOJI_FOOD,
title: N_('Food & Drink'),
icon_name: 'characters-emoji-food',
action_name: 'subcategory'
},
{
name: 'emoji-activities',
category: Gc.Category.EMOJI_ACTIVITIES,
title: N_('Activities'),
icon_name: 'characters-emoji-activities',
action_name: 'subcategory'
},
{
name: 'emoji-travel',
category: Gc.Category.EMOJI_TRAVEL,
title: N_('Travel & Places'),
icon_name: 'characters-emoji-travel',
action_name: 'subcategory'
},
{
name: 'emoji-objects',
category: Gc.Category.EMOJI_OBJECTS,
title: N_('Objects'),
icon_name: 'characters-emoji-objects',
action_name: 'subcategory'
},
{
name: 'emoji-symbols',
category: Gc.Category.EMOJI_SYMBOLS,
title: N_('Symbols'),
icon_name: 'characters-emoji-symbols',
action_name: 'subcategory'
},
{
name: 'emoji-flags',
category: Gc.Category.EMOJI_FLAGS,
title: N_('Flags'),
icon_name: 'characters-emoji-flags',
action_name: 'subcategory'
}
];
const CategoryListRowWidget = new Lang.Class({
Name: 'CategoryListRowWidget',
Extends: Gtk.ListBoxRow,
_init: function(params, category) {
params = Params.fill(params, {});
this.parent(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 pixbuf = Util.loadIcon(category.icon_name, 24);
let image = Gtk.Image.new_from_pixbuf(pixbuf);
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);
if (category.secondary_icon_name) {
let pixbuf = Util.loadIcon(category.secondary_icon_name, 16);
let image = Gtk.Image.new_from_pixbuf(pixbuf);
image.get_style_context().add_class('category-icon');
hbox.pack_end(image, false, false, 2);
}
}
});
const CategoryListWidget = new Lang.Class({
Name: 'CategoryListWidget',
Extends: Gtk.ListBox,
_init: function(params) {
let filtered = Params.filter(params, { categoryList: null });
params = Params.fill(params, {});
this.parent(params);
this.get_style_context().add_class('categories');
this._categoryList = filtered.categoryList;
this.populateCategoryList();
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: function(row) {
if (row != null && row.selectable) {
let toplevel = row.get_toplevel();
let action = toplevel.lookup_action(row.category.action_name);
action.activate(new GLib.Variant('s', row.category.name));
}
},
populateCategoryList: function() {
},
getCategoryList: function() {
return this._categoryList;
},
getCategory: function(name) {
for (let index in this._categoryList) {
let category = this._categoryList[index];
if (category.name == name)
return category;
}
return null;
}
});
const LetterCategoryListWidget = new Lang.Class({
Name: 'LetterCategoryListWidget',
Extends: CategoryListWidget,
_finishListEngines: function(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: function(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,
Lang.bind(this, function (bus, res) {
this._finishListEngines(sources, bus, res);
}));
} else
this._finishBuildScriptList(sources);
},
_finishBuildScriptList: function(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: function() {
// 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 = new Lang.Class({
Name: 'EmojiCategoryListWidget',
Extends: CategoryListWidget,
_init: function(params) {
params = Params.fill(params, {});
this.parent(params);
let category;
let rowWidget;
category = {
name: 'recent',
category: Gc.Category.NONE,
title: N_('Recently Used'),
icon_name: 'document-open-recent-symbolic',
action_name: 'subcategory'
};
rowWidget = new CategoryListRowWidget({}, category);
rowWidget.get_style_context().add_class('category');
this.prepend(rowWidget);
this._recentCategory = category;
category = {
name: 'letters',
category: Gc.Category.NONE,
title: N_('Letters & Symbols'),
icon_name: 'characters-latin-symbolic',
secondary_icon_name: 'go-next-symbolic',
action_name: 'category',
};
rowWidget = new CategoryListRowWidget({}, category);
rowWidget.get_style_context().add_class('category');
let separator = new Gtk.Separator();
let separatorRowWidget = new Gtk.ListBoxRow({ selectable: false });
separatorRowWidget.add(separator);
this.add(separatorRowWidget);
this.add(rowWidget);
},
getCategory: function(name) {
if (name == 'recent')
return this._recentCategory;
return this.parent(name);
}
});
var CategoryListView = new Lang.Class({
Name: 'CategoryListView',
Extends: Gtk.Stack,
_init: function(params) {
params = Params.fill(params, {
hexpand: true, vexpand: true,
transition_type: Gtk.StackTransitionType.SLIDE_RIGHT
});
this.parent(params);
let emojiCategoryList = new EmojiCategoryListWidget({
categoryList: EmojiCategoryList
});
this.add_named(emojiCategoryList, 'emojis');
let letterCategoryList = new LetterCategoryListWidget({
categoryList: LetterCategoryList
});
this.add_named(letterCategoryList, 'letters');
this.set_visible_child_name('emojis');
this._categoryList = CategoryList.slice();
this.connect('notify::visible-child-name',
Lang.bind(this, this._ensureTransitionType));
},
_ensureTransitionType: function() {
if (this.get_visible_child_name() == 'emojis') {
this.transition_type = Gtk.StackTransitionType.SLIDE_RIGHT;
} else {
this.transition_type = Gtk.StackTransitionType.SLIDE_LEFT;
}
},
getCategoryList: function() {
return this._categoryList;
}
});