[gnome-characters] Implement filtering by font
- From: Daiki Ueno <dueno src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-characters] Implement filtering by font
- Date: Sat, 14 Feb 2015 23:21:33 +0000 (UTC)
commit 1371a764a26b8158168d0de18e724a678b0c9f82
Author: Daiki Ueno <dueno src gnome org>
Date: Sat Feb 14 20:54:20 2015 +0900
Implement filtering by font
data/mainwindow.ui | 19 +++++
data/menu.ui | 49 +++++++++++
data/org.gnome.Characters.data.gresource.xml | 1 +
src/characterList.js | 113 ++++++++++++++++++--------
src/menu.js | 92 +++++++++++++++++++++
src/org.gnome.Characters.src.gresource.xml | 1 +
src/window.js | 57 +++++++++++---
7 files changed, 287 insertions(+), 45 deletions(-)
---
diff --git a/data/mainwindow.ui b/data/mainwindow.ui
index 4452d46..e935134 100644
--- a/data/mainwindow.ui
+++ b/data/mainwindow.ui
@@ -26,6 +26,25 @@
<property name="pack-type">start</property>
</packing>
</child>
+ <child>
+ <object class="GtkMenuButton" id="menu-button">
+ <property name="can_focus">False</property>
+ <property name="visible">True</property>
+ <property name="use_popover">True</property>
+ <style>
+ <class name="image-button"/>
+ </style>
+ <child>
+ <object class="GtkImage" id="menu-button-image">
+ <property name="visible">True</property>
+ <property name="icon-name">open-menu-symbolic</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="pack-type">end</property>
+ </packing>
+ </child>
</object>
</child>
<child>
diff --git a/data/menu.ui b/data/menu.ui
new file mode 100644
index 0000000..d6f1f21
--- /dev/null
+++ b/data/menu.ui
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="3.12"/>
+ <template class="Gjs_MenuPopover" parent="GtkPopover">
+ <property name="vexpand">True</property>
+ <child>
+ <object class="GtkGrid" id="main-grid">
+ <property name="can_focus">False</property>
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkSearchEntry" id="search-entry">
+ <property name="can_focus">True</property>
+ <property name="visible">True</property>
+ <property name="halign">fill</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="font-scrolled">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">6</property>
+ <property name="margin_end">6</property>
+ <property name="margin_top">6</property>
+ <property name="margin_bottom">6</property>
+ <property name="hscrollbar_policy">never</property>
+ <child>
+ <object class="GtkViewport" id="font-viewport">
+ <property name="can_focus">False</property>
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkListBox" id="font-listbox">
+ <property name="can_focus">False</property>
+ <property name="visible">True</property>
+ <property name="vexpand">True</property>
+ <property name="selection_mode">none</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/data/org.gnome.Characters.data.gresource.xml b/data/org.gnome.Characters.data.gresource.xml
index 5ad4f21..40873b3 100644
--- a/data/org.gnome.Characters.data.gresource.xml
+++ b/data/org.gnome.Characters.data.gresource.xml
@@ -5,6 +5,7 @@
<file preprocess="xml-stripblanks">mainwindow.ui</file>
<file preprocess="xml-stripblanks">character.ui</file>
<file preprocess="xml-stripblanks">characterlist.ui</file>
+ <file preprocess="xml-stripblanks">menu.ui</file>
<file>application.css</file>
</gresource>
</gresources>
diff --git a/src/characterList.js b/src/characterList.js
index 00eb693..4b7fa80 100644
--- a/src/characterList.js
+++ b/src/characterList.js
@@ -86,43 +86,16 @@ const CharacterListWidget = new Lang.Class({
Signals: {
'character-selected': { param_types: [ GObject.TYPE_STRING ] }
},
- Properties: {
- 'font': GObject.ParamSpec.string(
- 'font', '', '',
- GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE,
- 'Cantarell 50')
- },
-
- 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;
- if (this._characters) {
- this.setCharacters(this._characters);
- this.show_all();
- }
- },
_init: function(params) {
+ let filtered = Params.filter(params, { fontDescription: null });
params = Params.fill(params, {});
this.parent(params);
this.get_style_context().add_class('character-list');
this._cellsPerRow = CELLS_PER_ROW;
- this._font = null;
- this._fontDescription = null;
+ this._fontDescription = filtered.fontDescription;
+ this._characters = [];
this._rows = [];
- Main.settings.bind('font', this, 'font', Gio.SettingsBindFlags.DEFAULT);
this.add_events(Gdk.EventMask.BUTTON_PRESS_MASK);
},
@@ -175,6 +148,10 @@ const CharacterListWidget = new Lang.Class({
return row;
},
+ setFontDescription: function(fontDescription) {
+ this._fontDescription = fontDescription;
+ },
+
setCharacters: function(characters) {
this._rows = [];
this._characters = characters;
@@ -231,19 +208,50 @@ const CharacterListView = new Lang.Class({
Extends: Gtk.Stack,
Template: 'resource:///org/gnome/Characters/characterlist.ui',
InternalChildren: ['loading-banner-spinner'],
+ Properties: {
+ 'font': GObject.ParamSpec.string(
+ 'font', '', '',
+ GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE,
+ 'Cantarell 50')
+ },
+
+ 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;
+ if (this.mapped) {
+ this.setCharacters(this._characters);
+ this.show_all();
+ }
+ },
_init: function(params) {
- let filtered = Params.filter(params, { characterList: null });
params = Params.fill(params, { hexpand: true, vexpand: true });
this.parent(params);
- this._characterList = filtered.characterList;
+ Main.settings.bind('font', this, 'font', Gio.SettingsBindFlags.DEFAULT);
+
+ this._characterList = new CharacterListWidget({ hexpand: true,
+ vexpand: true,
+ fontDescription: this._fontDescription });
let scroll = new Gtk.ScrolledWindow({
hscrollbar_policy: Gtk.PolicyType.NEVER
});
scroll.add(this._characterList);
this.add_named(scroll, 'character-list');
+ this._characters = [];
this._spinnerTimeoutId = 0;
this._cancellable = new Gio.Cancellable();
},
@@ -273,10 +281,31 @@ const CharacterListView = new Lang.Class({
characters.push(Gc.search_result_get(result, index));
}
- this.setCharacters(characters)
+ this._characters = characters;
+ this.updateCharacterList()
},
setCharacters: function(characters) {
+ this._characters = characters;
+ },
+
+ updateCharacterList: function() {
+ let characters = this._characters;
+ let fontDescription = this._fontDescription;
+ if (this._filterFontDescription) {
+ let context = this.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;
+ }
+
+ this._characterList.setFontDescription(fontDescription);
this._characterList.setCharacters(characters);
if (characters.length == 0) {
this.visible_child_name = 'search-banner';
@@ -322,5 +351,21 @@ const CharacterListView = new Lang.Class({
cancelSearch: function() {
this._cancellable.cancel();
this._finishSearch([]);
- }
+ },
+
+ setFilterFont: function(family) {
+ if (family == null) {
+ this._filterFontDescription = null;
+ return;
+ }
+
+ let fontDescription = Pango.FontDescription.from_string(family);
+ fontDescription.set_size(this._fontDescription.get_size());
+
+ if (!(this._filterFontDescription &&
+ fontDescription.equal(this._filterFontDescription))) {
+ this._filterFontDescription = fontDescription;
+ this.updateCharacterList();
+ }
+ },
});
diff --git a/src/menu.js b/src/menu.js
new file mode 100644
index 0000000..8d8a440
--- /dev/null
+++ b/src/menu.js
@@ -0,0 +1,92 @@
+// -*- 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 Gio = imports.gi.Gio;
+const GLib = imports.gi.GLib;
+const GObject = imports.gi.GObject;
+const Gtk = imports.gi.Gtk;
+const Lang = imports.lang;
+const Params = imports.params;
+const Pango = imports.gi.Pango;
+
+const MenuPopover = new Lang.Class({
+ Name: 'MenuPopover',
+ Extends: Gtk.Popover,
+ Template: 'resource:///org/gnome/Characters/menu.ui',
+ InternalChildren: ['search-entry', 'font-listbox'],
+
+ _init: function(params) {
+ params = Params.fill(params, {});
+ this.parent(params);
+
+ 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) {
+ let row = new Gtk.ListBoxRow({ visible: true });
+ row._family = families[index];
+ row.add(new Gtk.Label({ label: row._family.get_name(),
+ visible: true,
+ halign: Gtk.Align.START }));
+ this._font_listbox.add(row);
+ }
+
+ this._keywords = [];
+ this._search_entry.connect('search-changed',
+ Lang.bind(this, this._handleSearchChanged));
+ this._font_listbox.connect('row-activated',
+ Lang.bind(this, this._handleRowActivated));
+ this._font_listbox.set_filter_func(Lang.bind(this, this._filterFunc));
+
+ // This silents warning at Characters exit about this widget being
+ // visible but not mapped. Borrowed from Maps.
+ this.connect('unmap', function(popover) { popover.hide(); });
+ },
+
+ _handleSearchChanged: function(entry) {
+ let text = entry.get_text().replace(/^\s+|\s+$/g, '');
+ let keywords = text == '' ? [] : text.split(/\s+/);
+ this._keywords = keywords.map(String.toLowerCase);
+ this._font_listbox.invalidate_filter();
+ return true;
+ },
+
+ _handleRowActivated: function(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.get_name()));
+ }
+ },
+
+ _filterFunc: function(row) {
+ if (this._keywords.length == 0)
+ return true;
+ else {
+ let name = row._family.get_name();
+ let nameWords = name.split(/\s+/).map(String.toLowerCase);
+ return this._keywords.some(function(keyword, index, array) {
+ return nameWords.some(function(nameWord, index, array) {
+ return nameWord.indexOf(keyword) >= 0;
+ });
+ });
+ }
+ }
+});
diff --git a/src/org.gnome.Characters.src.gresource.xml b/src/org.gnome.Characters.src.gresource.xml
index 57f4fcf..eaf5281 100644
--- a/src/org.gnome.Characters.src.gresource.xml
+++ b/src/org.gnome.Characters.src.gresource.xml
@@ -2,6 +2,7 @@
<gresources>
<gresource prefix="/org/gnome/Characters/js">
<file>main.js</file>
+ <file>menu.js</file>
<file>params.js</file>
<file>util.js</file>
<file>window.js</file>
diff --git a/src/window.js b/src/window.js
index d425d29..080c452 100644
--- a/src/window.js
+++ b/src/window.js
@@ -33,8 +33,7 @@ const Params = imports.params;
const CategoryList = imports.categoryList;
const Character = imports.character;
const CharacterList = imports.characterList;
-const Pango = imports.gi.Pango;
-const Gc = imports.gi.Gc;
+const Menu = imports.menu;
const Gettext = imports.gettext;
const Main = imports.main;
@@ -46,6 +45,7 @@ const MainWindow = new Lang.Class({
Template: 'resource:///org/gnome/Characters/mainwindow.ui',
InternalChildren: ['main-headerbar', 'search-active-button',
'search-bar', 'search-entry',
+ 'menu-button',
'main-grid', 'main-hbox', 'sidebar-grid'],
Properties: {
'search-active': GObject.ParamSpec.boolean(
@@ -75,6 +75,9 @@ const MainWindow = new Lang.Class({
state: new GLib.Variant('s', 'punctuation') },
{ name: 'character',
activate: this._character,
+ parameter_type: new GLib.VariantType('s') },
+ { name: 'filter-font',
+ activate: this._filterFont,
parameter_type: new GLib.VariantType('s') }]);
this.bind_property('search-active', this._search_active_button, 'active',
@@ -89,6 +92,9 @@ const MainWindow = new Lang.Class({
this._search_entry.connect('search-changed',
Lang.bind(this, this._handleSearchChanged));
+ this._menu_popover = new Menu.MenuPopover({});
+ this._menu_button.set_popover(this._menu_popover);
+
this._categoryList = new CategoryList.CategoryListWidget();
this._sidebar_grid.add(this._categoryList);
@@ -128,6 +134,8 @@ const MainWindow = new Lang.Class({
},
_handleKeyPress: function(self, event) {
+ if (this._menu_popover.visible)
+ return false;
return this._search_bar.handle_event(event);
},
@@ -153,6 +161,15 @@ const MainWindow = new Lang.Class({
});
},
+ _updateTitle: function(title, filterFont) {
+ if (filterFont) {
+ this._main_headerbar.title =
+ _("%s (%s only)").format(Gettext.gettext(title), filterFont);
+ } else {
+ this._main_headerbar.title = Gettext.gettext(title);
+ }
+ },
+
_category: function(action, v) {
let [name, length] = v.get_string()
@@ -167,13 +184,19 @@ const MainWindow = new Lang.Class({
Util.assertNotEqual(category, null);
this._mainView.setPage(category.name);
- this._main_headerbar.title = Gettext.gettext(category.title);
+ this._updateTitle(category.title, null);
},
_character: function(action, v) {
let [uc, length] = v.get_string()
this._mainView.selectCharacter(uc);
},
+
+ _filterFont: function(action, v) {
+ let [family, length] = v.get_string()
+ this._mainView.setFilterFont(family);
+ this._updateTitle(this._mainView.visible_child.title, family);
+ },
});
const MainView = new Lang.Class({
@@ -201,18 +224,22 @@ const MainView = new Lang.Class({
params = Params.fill(params, { hexpand: true, vexpand: true });
this.parent(params);
- this._characterListWidgets = {};
+ this._characterLists = {};
let characterList;
for (let index in CategoryList.Category) {
let category = CategoryList.Category[index];
characterList = this._createCharacterList(
category.name, _('%s Character List').format(category.title));
+ // FIXME: Can't use GtkContainer.child_get_property.
+ characterList.title = category.title;
this.add_titled(characterList, category.name, category.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
@@ -225,13 +252,16 @@ const MainView = new Lang.Class({
},
_createCharacterList: function(name, accessible_name) {
- let widget = new CharacterList.CharacterListWidget({ hexpand: true,
- vexpand: true });
+ let characterList = new CharacterList.CharacterListView({});
+ characterList.get_accessible().accessible_name = accessible_name;
+
+ let scroll = characterList.get_child_by_name('character-list');
+ let widget = scroll.get_child().get_child();
widget.connect('character-selected',
Lang.bind(this, this._handleCharacterSelected));
- this._characterListWidgets[name] = widget;
- widget.get_accessible().accessible_name = accessible_name;
- return new CharacterList.CharacterListView({ characterList: widget });
+
+ this._characterLists[name] = characterList;
+ return characterList;
},
searchByKeywords: function(keywords) {
@@ -244,14 +274,15 @@ const MainView = new Lang.Class({
},
setPage: function(name) {
- if (!(name in this._characterListWidgets))
+ if (!(name in this._characterLists))
return;
this.visible_child_name = name;
+ this.visible_child.setFilterFont(null);
- let characterList = this._characterListWidgets[name];
if (name == 'recent') {
this.visible_child.setCharacters(this._recentCharacters);
+ this.visible_child.updateCharacterList();
} else {
let category = null;
for (let index in CategoryList.Category) {
@@ -291,5 +322,9 @@ const MainView = new Lang.Class({
if (response_id == Gtk.ResponseType.CLOSE)
dialog.destroy();
});
+ },
+
+ setFilterFont: function(family) {
+ this.visible_child.setFilterFont(family);
}
});
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]