[gnome-builder/wip/slaf/spellcheck: 6/8] spellcheck: spell widget
- From: Sébastien Lafargue <slafargue src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder/wip/slaf/spellcheck: 6/8] spellcheck: spell widget
- Date: Sat, 17 Dec 2016 12:10:28 +0000 (UTC)
commit cd9ed4b9c8577e11fa2f3b052c98c9423f14ba46
Author: Sebastien Lafargue <slafargue gnome org>
Date: Thu Dec 8 19:28:14 2016 +0100
spellcheck: spell widget
data/keybindings/default.css | 1 +
data/keybindings/emacs.css | 1 +
data/keybindings/vim.css | 2 +
data/theme/shared.css | 33 +-
libide/Makefile.am | 2 +
libide/editor/ide-editor-frame-actions.c | 63 +++
libide/editor/ide-editor-frame-private.h | 7 +
libide/editor/ide-editor-frame.c | 19 +
libide/editor/ide-editor-frame.ui | 441 ++++++++++----------
libide/editor/ide-editor-spell-widget.c | 675 ++++++++++++++++++++++++++++++
libide/editor/ide-editor-spell-widget.h | 38 ++
libide/editor/ide-editor-spell-widget.ui | 310 ++++++++++++++
libide/ide-types.h | 2 +
libide/resources/libide.gresource.xml | 1 +
po/POTFILES.in | 1 +
15 files changed, 1374 insertions(+), 222 deletions(-)
---
diff --git a/data/keybindings/default.css b/data/keybindings/default.css
index 89e2966..d50ad7a 100644
--- a/data/keybindings/default.css
+++ b/data/keybindings/default.css
@@ -8,6 +8,7 @@
"clear-count" ()
"clear-snippets" ()
"hide-completion" () };
+ bind "<shift>F7" { "action" ("frame", "spellcheck", "1") };
bind "<ctrl>f" { "action" ("frame", "find", "3") };
bind "<ctrl>o" { "action" ("win", "open-with-dialog", "") };
bind "<ctrl>s" { "action" ("view", "save", "") };
diff --git a/data/keybindings/emacs.css b/data/keybindings/emacs.css
index 6da5923..7c5cd6f 100644
--- a/data/keybindings/emacs.css
+++ b/data/keybindings/emacs.css
@@ -59,6 +59,7 @@
bind "<alt>x" { "action" ("win", "show-command-bar", "") };
bind "<ctrl>r" { "action" ("frame", "find", "2") };
bind "<ctrl>s" { "action" ("frame", "find", "3") };
+ bind "<alt>dollar" { "action" ("frame", "spellcheck", "1") };
bind "<alt>period" { "goto-definition" () };
bind "<alt>n" { "move-error" (down) };
bind "<alt>p" { "move-error" (up) };
diff --git a/data/keybindings/vim.css b/data/keybindings/vim.css
index 4da8e08..ba433d0 100644
--- a/data/keybindings/vim.css
+++ b/data/keybindings/vim.css
@@ -178,6 +178,8 @@
/* start search backward */
bind "question" { "action" ("frame", "find", "2") };
+ /* start spell checking */
+ bind "<shift>F7" { "action" ("frame", "spellcheck", "1") };
/* start search */
bind "slash" { "action" ("frame", "find", "3") };
bind "KP_Divide" { "action" ("frame", "find", "3") };
diff --git a/data/theme/shared.css b/data/theme/shared.css
index cbb6d34..64ec952 100644
--- a/data/theme/shared.css
+++ b/data/theme/shared.css
@@ -3,8 +3,8 @@ filechooser actionbar button.combo {
padding: 0;
}
-/* styling for editor search */
-frame.gb-search-frame {
+/* styling for editor search and spellchecker */
+frame.gb-search-frame, frame.gb-spell-frame {
background-image: linear-gradient(shade(@theme_bg_color,1.05), @theme_bg_color);
padding: 6px;
border-style: solid;
@@ -15,36 +15,51 @@ frame.gb-search-frame {
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
}
-frame.gb-search-frame border {
+frame.gb-search-frame border,
+frame.gb-spell-frame border {
border: none;
}
-/* styling for the search frame close button */
+frame.gb-spell-frame list {
+ border: 1px solid @borders;
+}
+
+frame.gb-spell-option-frame border {
+ border: 1px solid @borders;
+}
+
+/* styling for the search and spellchecker frame close button */
frame.gb-search-frame > box > grid:first-child > button.close:disabled,
-frame.gb-search-frame > box > grid:first-child > button.close {
+frame.gb-spell-frame > box > grid:first-child > button.close:disabled,
+frame.gb-search-frame > box > grid:first-child > button.close,
+frame.gb-spell-frame > box > grid:first-child > button.close {
background: none;
border: none;
box-shadow: none;
padding-left: 4px;
padding-right: 4px;
}
-frame.gb-search-frame > box > grid:first-child > button.close image {
+frame.gb-search-frame > box > grid:first-child > button.close image,
+frame.gb-spell-frame > box > grid:first-child > button.close image {
color: @theme_fg_color;
opacity: 0.3;
margin: 2px;
border: 1px solid transparent;
border-radius: 3px;
}
-frame.gb-search-frame > box > grid:first-child > button.close:hover image {
+frame.gb-search-frame > box > grid:first-child > button.close:hover image,
+frame.gb-spell-frame > box > grid:first-child > button.close:hover image {
opacity: .75;
transition-duration: 250ms;
border: 1px solid @borders;
}
-frame.gb-search-frame > box > grid:first-child > button.close:active image {
+frame.gb-search-frame > box > grid:first-child > button.close:active image,
+frame.gb-spell-frame > box > grid:first-child > button.close:active image {
opacity: .8;
background-image: linear-gradient(shade(@theme_bg_color, 0.9), @theme_bg_color);
}
-frame.gb-search-frame > box > grid:first-child > button.close:backdrop image {
+frame.gb-search-frame > box > grid:first-child > button.close:backdrop image,
+frame.gb-spell-frame > box > grid:first-child > button.close:backdrop image {
opacity: .1;
}
diff --git a/libide/Makefile.am b/libide/Makefile.am
index 987d890..f841fa7 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -388,6 +388,8 @@ libide_1_0_la_SOURCES = \
editor/ide-editor-layout-stack-controls.h \
editor/ide-editor-print-operation.c \
editor/ide-editor-print-operation.h \
+ editor/ide-editor-spell-widget.c \
+ editor/ide-editor-spell-widget.h \
editor/ide-editor-tweak-widget.c \
editor/ide-editor-tweak-widget.h \
editor/ide-editor-view-actions.c \
diff --git a/libide/editor/ide-editor-frame-actions.c b/libide/editor/ide-editor-frame-actions.c
index ed0fda8..f4c4838 100644
--- a/libide/editor/ide-editor-frame-actions.c
+++ b/libide/editor/ide-editor-frame-actions.c
@@ -20,9 +20,60 @@
#include "ide-editor-frame-actions.h"
#include "ide-editor-frame-private.h"
+#include "ide-editor-spell-widget.h"
#include "util/ide-gtk.h"
static void
+ide_editor_frame_actions_spellcheck (GSimpleAction *action,
+ GVariant *variant,
+ gpointer user_data)
+{
+ IdeEditorFrame *self = user_data;
+ GtkWidget *spell_widget;
+ GtkWidget *entry;
+
+ g_assert (IDE_IS_EDITOR_FRAME (self));
+
+ if (IDE_IS_SOURCE_VIEW (self->source_view) &&
+ !self->spellchecker_opened)
+ {
+ g_assert (gtk_bin_get_child (GTK_BIN (self->spell_revealer)) == NULL);
+
+ self->spellchecker_opened = TRUE;
+
+ spell_widget = ide_editor_spell_widget_new (self->source_view);
+ gtk_widget_show (spell_widget);
+ gtk_container_add (GTK_CONTAINER (self->spell_revealer), spell_widget);
+
+ gtk_revealer_set_reveal_child (self->spell_revealer, TRUE);
+ entry = ide_editor_spell_widget_get_entry (IDE_EDITOR_SPELL_WIDGET (spell_widget));
+
+ /* We need the widget to be realized before the grab to avoid:
+ * gtk_widget_event: assertion 'WIDGET_REALIZED_FOR_EVENT (widget, event)' failed
+ */
+ gtk_widget_realize (entry);
+ gtk_widget_grab_focus (entry);
+ g_signal_connect_object (spell_widget,
+ "unmap",
+ G_CALLBACK (ide_editor_frame_spell_widget_unmapped_cb),
+ self,
+ G_CONNECT_SWAPPED | G_CONNECT_AFTER);
+ }
+}
+
+static void
+ide_editor_frame_actions_exit_spell (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ IdeEditorFrame *self = user_data;
+
+ g_assert (IDE_IS_EDITOR_FRAME (self));
+
+ gtk_widget_grab_focus (GTK_WIDGET (self->source_view));
+}
+
+static void
ide_editor_frame_actions_find (GSimpleAction *action,
GVariant *variant,
gpointer user_data)
@@ -389,6 +440,7 @@ static const GActionEntry IdeEditorFrameActions[] = {
{ "next-search-result", ide_editor_frame_actions_next_search_result },
{ "previous-search-result", ide_editor_frame_actions_previous_search_result },
{ "replace-confirm", ide_editor_frame_actions_replace_confirm, "as" },
+ { "spellcheck", ide_editor_frame_actions_spellcheck, "i" },
};
static const GActionEntry IdeEditorFrameSearchActions[] = {
@@ -404,6 +456,10 @@ static const GActionEntry IdeEditorFrameSearchActions[] = {
{ "replace-all", ide_editor_frame_actions_replace_all },
};
+static const GActionEntry IdeEditorFrameSpellActions[] = {
+ { "exit-spell", ide_editor_frame_actions_exit_spell },
+};
+
void
ide_editor_frame_actions_init (IdeEditorFrame *self)
{
@@ -433,4 +489,11 @@ ide_editor_frame_actions_init (IdeEditorFrame *self)
gtk_widget_insert_action_group (GTK_WIDGET (self->search_frame), "search-entry", G_ACTION_GROUP (group));
g_object_unref (group);
+
+ group = g_simple_action_group_new ();
+ g_action_map_add_action_entries (G_ACTION_MAP (group), IdeEditorFrameSpellActions,
+ G_N_ELEMENTS (IdeEditorFrameSpellActions), self);
+
+ gtk_widget_insert_action_group (GTK_WIDGET (self->spell_revealer), "spell-entry", G_ACTION_GROUP (group));
+ g_object_unref (group);
}
diff --git a/libide/editor/ide-editor-frame-private.h b/libide/editor/ide-editor-frame-private.h
index 134eb2f..2b07937 100644
--- a/libide/editor/ide-editor-frame-private.h
+++ b/libide/editor/ide-editor-frame-private.h
@@ -23,7 +23,9 @@
#include <gtk/gtk.h>
#include <nautilus-floating-bar.h>
+#include "ide-types.h"
#include "editor/ide-editor-map-bin.h"
+#include "ide-editor-spell-widget.h"
#include "sourceview/ide-source-map.h"
#include "sourceview/ide-source-view.h"
@@ -41,6 +43,7 @@ struct _IdeEditorFrame
GtkLabel *overwrite_label;
GtkScrolledWindow *scrolled_window;
GtkRevealer *search_revealer;
+ GtkRevealer *spell_revealer;
GtkFrame *search_frame;
GdTaggedEntry *search_entry;
GtkSearchEntry *replace_entry;
@@ -58,8 +61,12 @@ struct _IdeEditorFrame
guint pending_replace_confirm;
guint auto_hide_map : 1;
guint show_ruler : 1;
+ guint spellchecker_opened : 1;
};
+void ide_editor_frame_spell_widget_unmapped_cb (IdeEditorFrame *self,
+ IdeEditorSpellWidget *spell_widget);
+
G_END_DECLS
#endif /* IDE_EDITOR_FRAME_PRIVATE_H */
diff --git a/libide/editor/ide-editor-frame.c b/libide/editor/ide-editor-frame.c
index b76a7ad..75b288c 100644
--- a/libide/editor/ide-editor-frame.c
+++ b/libide/editor/ide-editor-frame.c
@@ -29,6 +29,7 @@
#include "editor/ide-editor-frame.h"
#include "editor/ide-editor-map-bin.h"
#include "editor/ide-editor-perspective.h"
+#include "editor/ide-editor-spell-widget.h"
#include "history/ide-back-forward-list.h"
#include "util/ide-dnd.h"
#include "util/ide-gtk.h"
@@ -56,6 +57,22 @@ enum {
static GParamSpec *properties [LAST_PROP];
+void
+ide_editor_frame_spell_widget_unmapped_cb (IdeEditorFrame *self,
+ IdeEditorSpellWidget *spell_widget)
+{
+ GtkWidget *child;
+
+ g_assert (IDE_IS_EDITOR_FRAME (self));
+ g_assert (IDE_IS_EDITOR_SPELL_WIDGET (spell_widget));
+
+ if (NULL != (child = gtk_bin_get_child (GTK_BIN (self->spell_revealer))))
+ {
+ gtk_container_remove (GTK_CONTAINER (self->spell_revealer), child);
+ self->spellchecker_opened = FALSE;
+ }
+}
+
static void
update_replace_actions_sensitivity (IdeEditorFrame *self)
{
@@ -845,6 +862,7 @@ ide_editor_frame__source_view_focus_in_event (IdeEditorFrame *self,
g_assert (IDE_IS_SOURCE_VIEW (source_view));
gtk_revealer_set_reveal_child (self->search_revealer, FALSE);
+ gtk_revealer_set_reveal_child (self->spell_revealer, FALSE);
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (source_view));
@@ -1201,6 +1219,7 @@ ide_editor_frame_class_init (IdeEditorFrameClass *klass)
gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, replace_all_button);
gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, search_options);
gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, search_revealer);
+ gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, spell_revealer);
gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, source_map_container);
gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, source_overlay);
gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, source_view);
diff --git a/libide/editor/ide-editor-frame.ui b/libide/editor/ide-editor-frame.ui
index 0d14976..07609b8 100644
--- a/libide/editor/ide-editor-frame.ui
+++ b/libide/editor/ide-editor-frame.ui
@@ -45,290 +45,305 @@
<property name="expand">true</property>
<property name="visible">true</property>
<child type="overlay">
- <object class="GtkRevealer" id="search_revealer">
+ <object class="GtkBox" id="revealer_box">
+ <property name="orientation">vertical</property>
<property name="halign">end</property>
<property name="valign">start</property>
<property name="visible">true</property>
<child>
- <object class="GtkFrame" id="search_frame">
+ <object class="GtkRevealer" id="search_revealer">
+ <property name="halign">end</property>
+ <property name="valign">start</property>
<property name="visible">true</property>
- <property name="margin-end">12</property>
- <style>
- <class name="gb-search-frame"/>
- </style>
<child>
- <object class="GtkBox">
+ <object class="GtkFrame" id="search_frame">
<property name="visible">true</property>
- <property name="orientation">vertical</property>
- <property name="spacing">7</property>
+ <property name="margin-end">12</property>
+ <style>
+ <class name="gb-search-frame"/>
+ </style>
<child>
- <object class="GtkGrid">
+ <object class="GtkBox">
<property name="visible">true</property>
- <property name="can_focus">false</property>
- <property name="row_spacing">8</property>
- <property name="column_spacing">8</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">7</property>
<child>
- <object class="GdTaggedEntry" id="search_entry">
- <property name="visible">true</property>
- <property name="tag-close-visible">false</property>
- <property name="can_focus">true</property>
- <property name="width-chars">20</property>
- <property name="max-width-chars">30</property>
- <property name="primary_icon_name">edit-find-symbolic</property>
- <property name="primary_icon_activatable">false</property>
- <property name="primary_icon_sensitive">false</property>
- </object>
- <packing>
- <property name="left_attach">0</property>
- <property name="top_attach">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkSearchEntry" id="replace_entry">
- <property name="visible">false</property>
- <property name="can_focus">true</property>
- <property name="width-chars">20</property>
- <property name="max-width-chars">30</property>
- <property name="primary_icon_name">edit-find-replace-symbolic</property>
- <property name="primary_icon_activatable">false</property>
- <property name="primary_icon_sensitive">false</property>
- </object>
- <packing>
- <property name="left_attach">0</property>
- <property name="top_attach">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkBox">
- <property name="homogeneous">true</property>
+ <object class="GtkGrid">
<property name="visible">true</property>
<property name="can_focus">false</property>
- <property name="valign">center</property>
- <style>
- <class name="linked"/>
- </style>
+ <property name="row_spacing">8</property>
+ <property name="column_spacing">8</property>
<child>
- <object class="GtkButton">
- <property name="action-name">frame.previous-search-result</property>
+ <object class="GdTaggedEntry" id="search_entry">
<property name="visible">true</property>
- <property name="can_focus">false</property>
- <property name="receives_default">true</property>
- <child>
- <object class="GtkImage">
- <property name="visible">true</property>
- <property name="can_focus">false</property>
- <property name="icon_name">go-up-symbolic</property>
- <property name="icon_size">1</property>
- </object>
- </child>
+ <property name="tag-close-visible">false</property>
+ <property name="can_focus">true</property>
+ <property name="width-chars">20</property>
+ <property name="max-width-chars">30</property>
+ <property name="primary_icon_name">edit-find-symbolic</property>
+ <property name="primary_icon_activatable">false</property>
+ <property name="primary_icon_sensitive">false</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSearchEntry" id="replace_entry">
+ <property name="visible">false</property>
+ <property name="can_focus">true</property>
+ <property name="width-chars">20</property>
+ <property name="max-width-chars">30</property>
+ <property
name="primary_icon_name">edit-find-replace-symbolic</property>
+ <property name="primary_icon_activatable">false</property>
+ <property name="primary_icon_sensitive">false</property>
</object>
<packing>
- <property name="expand">false</property>
- <property name="fill">true</property>
- <property name="position">0</property>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
</packing>
</child>
<child>
- <object class="GtkButton">
- <property name="action-name">frame.next-search-result</property>
+ <object class="GtkBox">
+ <property name="homogeneous">true</property>
<property name="visible">true</property>
<property name="can_focus">false</property>
- <property name="receives_default">true</property>
+ <property name="valign">center</property>
+ <style>
+ <class name="linked"/>
+ </style>
<child>
- <object class="GtkImage">
+ <object class="GtkButton">
+ <property
name="action-name">frame.previous-search-result</property>
<property name="visible">true</property>
<property name="can_focus">false</property>
- <property name="icon_name">go-down-symbolic</property>
- <property name="icon_size">1</property>
+ <property name="receives_default">true</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">true</property>
+ <property name="can_focus">false</property>
+ <property name="icon_name">go-up-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ </child>
</object>
+ <packing>
+ <property name="expand">false</property>
+ <property name="fill">true</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="action-name">frame.next-search-result</property>
+ <property name="visible">true</property>
+ <property name="can_focus">false</property>
+ <property name="receives_default">true</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">true</property>
+ <property name="can_focus">false</property>
+ <property name="icon_name">go-down-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">false</property>
+ <property name="fill">true</property>
+ <property name="position">1</property>
+ </packing>
</child>
</object>
<packing>
- <property name="expand">false</property>
- <property name="fill">true</property>
- <property name="position">1</property>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
</packing>
</child>
- </object>
- <packing>
- <property name="left_attach">1</property>
- <property name="top_attach">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkButton" id="replace_button">
- <property name="label" translatable="yes">Replace</property>
- <property name="action-name">search-entry.replace</property>
- <property name="visible">false</property>
- <property name="can_focus">true</property>
- <property name="receives_default">true</property>
- </object>
- <packing>
- <property name="left_attach">1</property>
- <property name="top_attach">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkButton" id="replace_all_button">
- <property name="label" translatable="yes">Replace All</property>
- <property name="action-name">search-entry.replace-all</property>
- <property name="visible">false</property>
- <property name="can_focus">true</property>
- <property name="receives_default">true</property>
- </object>
- <packing>
- <property name="left_attach">2</property>
- <property name="top_attach">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkBox">
- <property name="homogeneous">true</property>
- <property name="visible">true</property>
- <property name="can_focus">false</property>
- <property name="valign">center</property>
- <property name="spacing">8</property>
<child>
- <object class="GtkToggleButton">
- <property
name="action-name">search-entry.toggle-search-replace</property>
- <property name="action-target">true</property>
- <property name="tooltip-text" translatable="yes">Switch between
Search and Search-and-Replace</property>
- <property name="visible">true</property>
+ <object class="GtkButton" id="replace_button">
+ <property name="label" translatable="yes">Replace</property>
+ <property name="action-name">search-entry.replace</property>
+ <property name="visible">false</property>
<property name="can_focus">true</property>
<property name="receives_default">true</property>
- <property name="image_position">right</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="replace_all_button">
+ <property name="label" translatable="yes">Replace All</property>
+ <property name="action-name">search-entry.replace-all</property>
+ <property name="visible">false</property>
+ <property name="can_focus">true</property>
+ <property name="receives_default">true</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="homogeneous">true</property>
+ <property name="visible">true</property>
+ <property name="can_focus">false</property>
+ <property name="valign">center</property>
+ <property name="spacing">8</property>
<child>
- <object class="GtkImage">
+ <object class="GtkToggleButton">
+ <property
name="action-name">search-entry.toggle-search-replace</property>
+ <property name="action-target">true</property>
+ <property name="tooltip-text" translatable="yes">Switch between
Search and Search-and-Replace</property>
<property name="visible">true</property>
- <property name="can_focus">false</property>
- <property name="icon_name">edit-find-replace-symbolic</property>
+ <property name="can_focus">true</property>
+ <property name="receives_default">true</property>
+ <property name="image_position">right</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">true</property>
+ <property name="can_focus">false</property>
+ <property
name="icon_name">edit-find-replace-symbolic</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">false</property>
+ <property name="fill">true</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToggleButton">
+ <property
name="action-name">search-entry.toggle-search-options</property>
+ <property name="action-target">true</property>
+ <property name="tooltip-text" translatable="yes">Show or hide
search options such as case sensitivity</property>
+ <property name="visible">true</property>
+ <property name="can_focus">true</property>
+ <property name="receives_default">true</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">true</property>
+ <property name="can_focus">false</property>
+ <property name="icon_name">emblem-system-symbolic</property>
+ </object>
+ </child>
</object>
+ <packing>
+ <property name="expand">false</property>
+ <property name="fill">true</property>
+ <property name="position">1</property>
+ </packing>
</child>
</object>
<packing>
- <property name="expand">false</property>
- <property name="fill">true</property>
- <property name="position">0</property>
+ <property name="left_attach">2</property>
+ <property name="top_attach">0</property>
</packing>
</child>
<child>
- <object class="GtkToggleButton">
- <property
name="action-name">search-entry.toggle-search-options</property>
- <property name="action-target">true</property>
- <property name="tooltip-text" translatable="yes">Show or hide search
options such as case sensitivity</property>
+ <object class="GtkButton" id="close_button">
<property name="visible">true</property>
- <property name="can_focus">true</property>
- <property name="receives_default">true</property>
+ <property name="action-name">search-entry.exit-search</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="focus_on_click">false</property>
+ <style>
+ <class name="close"/>
+ </style>
<child>
<object class="GtkImage">
<property name="visible">true</property>
- <property name="can_focus">false</property>
- <property name="icon_name">emblem-system-symbolic</property>
+ <property name="icon_name">window-close-symbolic</property>
</object>
</child>
</object>
<packing>
- <property name="expand">false</property>
- <property name="fill">true</property>
- <property name="position">1</property>
+ <property name="left_attach">4</property>
+ <property name="top_attach">0</property>
</packing>
</child>
</object>
<packing>
- <property name="left_attach">2</property>
- <property name="top_attach">0</property>
+ <property name="expand">false</property>
+ <property name="fill">true</property>
+ <property name="position">0</property>
</packing>
</child>
<child>
- <object class="GtkButton" id="close_button">
- <property name="visible">true</property>
- <property name="action-name">search-entry.exit-search</property>
- <property name="halign">center</property>
- <property name="valign">center</property>
- <property name="focus_on_click">false</property>
- <style>
- <class name="close"/>
- </style>
+ <object class="GtkGrid" id="search_options">
+ <property name="visible">false</property>
+ <property name="can_focus">false</property>
+ <property name="column_spacing">8</property>
<child>
- <object class="GtkImage">
+ <object class="GtkCheckButton" id="use_regex">
+ <property
name="action-name">search-entry.change-regex-enabled</property>
+ <property name="label" translatable="yes">Regular
expressions</property>
<property name="visible">true</property>
- <property name="icon_name">window-close-symbolic</property>
+ <property name="can_focus">false</property>
+ <property name="receives_default">false</property>
+ <property name="xalign">0</property>
+ <property name="draw_indicator">true</property>
</object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="case_sensitive">
+ <property
name="action-name">search-entry.change-case-sensitive</property>
+ <property name="label" translatable="yes">Case sensitive</property>
+ <property name="visible">true</property>
+ <property name="can_focus">false</property>
+ <property name="receives_default">false</property>
+ <property name="xalign">0</property>
+ <property name="draw_indicator">true</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="whole_word">
+ <property
name="action-name">search-entry.change-word-boundaries</property>
+ <property name="label" translatable="yes">Match whole word
only</property>
+ <property name="visible">true</property>
+ <property name="can_focus">false</property>
+ <property name="receives_default">false</property>
+ <property name="xalign">0</property>
+ <property name="draw_indicator">true</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">0</property>
+ </packing>
</child>
</object>
<packing>
- <property name="left_attach">4</property>
- <property name="top_attach">0</property>
- </packing>
- </child>
- </object>
- <packing>
- <property name="expand">false</property>
- <property name="fill">true</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkGrid" id="search_options">
- <property name="visible">false</property>
- <property name="can_focus">false</property>
- <property name="column_spacing">8</property>
- <child>
- <object class="GtkCheckButton" id="use_regex">
- <property name="action-name">search-entry.change-regex-enabled</property>
- <property name="label" translatable="yes">Regular expressions</property>
- <property name="visible">true</property>
- <property name="can_focus">false</property>
- <property name="receives_default">false</property>
- <property name="xalign">0</property>
- <property name="draw_indicator">true</property>
- </object>
- <packing>
- <property name="left_attach">0</property>
- <property name="top_attach">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkCheckButton" id="case_sensitive">
- <property
name="action-name">search-entry.change-case-sensitive</property>
- <property name="label" translatable="yes">Case sensitive</property>
- <property name="visible">true</property>
- <property name="can_focus">false</property>
- <property name="receives_default">false</property>
- <property name="xalign">0</property>
- <property name="draw_indicator">true</property>
- </object>
- <packing>
- <property name="left_attach">1</property>
- <property name="top_attach">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkCheckButton" id="whole_word">
- <property
name="action-name">search-entry.change-word-boundaries</property>
- <property name="label" translatable="yes">Match whole word
only</property>
- <property name="visible">true</property>
- <property name="can_focus">false</property>
- <property name="receives_default">false</property>
- <property name="xalign">0</property>
- <property name="draw_indicator">true</property>
- </object>
- <packing>
- <property name="left_attach">2</property>
- <property name="top_attach">0</property>
+ <property name="expand">false</property>
+ <property name="fill">true</property>
+ <property name="position">1</property>
</packing>
</child>
</object>
- <packing>
- <property name="expand">false</property>
- <property name="fill">true</property>
- <property name="position">1</property>
- </packing>
</child>
</object>
</child>
</object>
</child>
+ <child>
+ <object class="GtkRevealer" id="spell_revealer">
+ <property name="halign">end</property>
+ <property name="valign">start</property>
+ <property name="visible">true</property>
+ </object>
+ </child>
</object>
</child>
<child>
diff --git a/libide/editor/ide-editor-spell-widget.c b/libide/editor/ide-editor-spell-widget.c
new file mode 100644
index 0000000..2f02f39
--- /dev/null
+++ b/libide/editor/ide-editor-spell-widget.c
@@ -0,0 +1,675 @@
+/* ide-editor-spell-widget.c
+ *
+ * Copyright (C) 2016 Sebastien Lafargue <slafargue 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+#include "ide.h"
+
+#include <glib/gi18n.h>
+#include <gspell/gspell.h>
+
+#include "ide-editor-spell-widget.h"
+
+struct _IdeEditorSpellWidget
+{
+ GtkBin parent_instance;
+
+ GspellNavigator *navigator;
+ IdeSourceView *view;
+ IdeBuffer *buffer;
+ GspellChecker *checker;
+ const GspellLanguage *spellchecker_language;
+
+ GtkLabel *word_label;
+ GtkEntry *word_entry;
+ GtkButton *check_button;
+ GtkButton *add_dict_button;
+ GtkButton *ignore_button;
+ GtkButton *ignore_all_button;
+ GtkButton *change_button;
+ GtkButton *change_all_button;
+ GtkButton *close_button;
+ GtkListBox *suggestions_box;
+
+ GtkButton *highlight_checkbutton;
+ GtkButton *language_chooser_button;
+
+ GtkWidget *placeholder;
+ GAction *view_spellchecking_action;
+
+ guint view_spellchecker_set : 1;
+};
+
+G_DEFINE_TYPE (IdeEditorSpellWidget, ide_editor_spell_widget, GTK_TYPE_BIN)
+
+enum {
+ PROP_0,
+ PROP_VIEW,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+clear_suggestions_box (IdeEditorSpellWidget *self)
+{
+ GList *children;
+
+ children = gtk_container_get_children (GTK_CONTAINER (self->suggestions_box));
+
+ for (GList *l = children; l != NULL; l = g_list_next (l))
+ gtk_widget_destroy (GTK_WIDGET (l->data));
+}
+
+static void
+set_sensiblility (IdeEditorSpellWidget *self,
+ gboolean sensibility)
+{
+ g_assert (IDE_IS_EDITOR_SPELL_WIDGET (self));
+
+ gtk_entry_set_text (self->word_entry, "");
+ clear_suggestions_box (self);
+
+ gtk_widget_set_sensitive (GTK_WIDGET (self->word_entry), sensibility);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->check_button), sensibility);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->ignore_button), sensibility);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->ignore_all_button), sensibility);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->change_button), sensibility);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->change_all_button), sensibility);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->add_dict_button), sensibility);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->suggestions_box), sensibility);
+}
+
+GtkWidget *
+ide_editor_spell_widget_get_entry (IdeEditorSpellWidget *self)
+{
+ return GTK_WIDGET (self->word_entry);
+}
+
+static GtkWidget *
+create_suggestion_row (IdeEditorSpellWidget *self,
+ const gchar *word)
+{
+ GtkWidget *row;
+
+ g_assert (IDE_IS_EDITOR_SPELL_WIDGET (self));
+
+ row = g_object_new (GTK_TYPE_LABEL,
+ "label", word,
+ "visible", TRUE,
+ "halign", GTK_ALIGN_START,
+ NULL);
+
+ return row;
+}
+
+static void
+fill_suggestions_box (IdeEditorSpellWidget *self,
+ const gchar *word,
+ gchar **first_result)
+{
+ GSList *suggestions = NULL;
+ GtkWidget *item;
+
+ g_assert (IDE_IS_EDITOR_SPELL_WIDGET (self));
+
+ clear_suggestions_box (self);
+ if (ide_str_empty0 (word))
+ {
+ gtk_widget_set_sensitive (GTK_WIDGET (self->suggestions_box), FALSE);
+ return;
+ }
+
+ if (NULL == (suggestions = gspell_checker_get_suggestions (self->checker, word, -1)))
+ {
+ *first_result = NULL;
+ gtk_widget_set_sensitive (GTK_WIDGET (self->suggestions_box), FALSE);
+ }
+ else
+ {
+ *first_result = g_strdup (suggestions->data);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->suggestions_box), TRUE);
+ for (GSList *l = (GSList *)suggestions; l != NULL; l = l->next)
+ {
+ item = create_suggestion_row (self, l->data);
+ gtk_list_box_insert (self->suggestions_box, item, -1);
+ }
+
+ g_slist_free_full (suggestions, g_free);
+ }
+}
+
+static gboolean
+jump_to_next_misspelled_word (IdeEditorSpellWidget *self)
+{
+ GspellChecker *checker = NULL;
+ g_autofree gchar *word = NULL;
+ g_autofree gchar *first_result = NULL;
+ GtkListBoxRow *row;
+ GError *error = NULL;
+ gboolean ret = FALSE;
+
+ g_assert (IDE_IS_EDITOR_SPELL_WIDGET (self));
+
+ gtk_widget_grab_focus (GTK_WIDGET (self->word_entry));
+ if ((ret = gspell_navigator_goto_next (self->navigator, &word, &checker, &error)))
+ {
+ gtk_label_set_text (self->word_label, word);
+ fill_suggestions_box (self, word, &first_result);
+ if (!ide_str_empty0 (first_result))
+ {
+ row = gtk_list_box_get_row_at_index (self->suggestions_box, 0);
+ gtk_list_box_select_row (self->suggestions_box, row);
+ }
+ }
+ else
+ {
+ if (error != NULL)
+ gtk_label_set_text (GTK_LABEL (self->placeholder), error->message);
+ else
+ {
+ gtk_label_set_text (GTK_LABEL (self->placeholder), _("Completed spell checking"));
+ set_sensiblility (self, FALSE);
+ }
+ }
+
+ return ret;
+}
+
+GtkWidget *
+ide_editor_spell_widget_new (IdeSourceView *source_view)
+{
+ return g_object_new (IDE_TYPE_EDITOR_SPELL_WIDGET,
+ "view", source_view,
+ NULL);
+}
+
+static IdeSourceView *
+ide_editor_spell_widget_get_view (IdeEditorSpellWidget *self)
+{
+ g_return_val_if_fail (IDE_IS_EDITOR_SPELL_WIDGET (self), NULL);
+
+ return self->view;
+}
+
+static void
+ide_editor_spell_widget_set_view (IdeEditorSpellWidget *self,
+ IdeSourceView *view)
+{
+ g_return_if_fail (IDE_IS_EDITOR_SPELL_WIDGET (self));
+ g_return_if_fail (IDE_IS_SOURCE_VIEW (view));
+
+ self->view = view;
+ if (GSPELL_IS_NAVIGATOR (self->navigator))
+ g_clear_object (&self->navigator);
+
+ self->navigator = gspell_navigator_text_view_new (GTK_TEXT_VIEW (view));
+}
+
+static void
+ide_editor_spell_widget__word_entry_changed_cb (IdeEditorSpellWidget *self,
+ GtkEntry *entry)
+{
+ gboolean sensitive;
+
+ g_assert (IDE_IS_EDITOR_SPELL_WIDGET (self));
+ g_assert (GTK_IS_ENTRY (entry));
+
+ sensitive = (gtk_entry_get_text_length (entry) > 0);
+
+ gtk_widget_set_sensitive (GTK_WIDGET (self->check_button), sensitive);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->change_button), sensitive);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->change_all_button), sensitive);
+}
+
+static void
+ide_editor_spell_widget__check_button_clicked_cb (IdeEditorSpellWidget *self,
+ GtkButton *button)
+{
+ const gchar *word;
+ g_autofree gchar *first_result = NULL;
+ GError *error = NULL;
+ gboolean ret = FALSE;
+
+ g_assert (IDE_IS_EDITOR_SPELL_WIDGET (self));
+ g_assert (GTK_IS_BUTTON (button));
+
+ word = gtk_entry_get_text (self->word_entry);
+ g_assert (!ide_str_empty0 (word));
+
+ ret = gspell_checker_check_word (self->checker, word, -1, &error);
+ if (error != NULL)
+ {
+ printf ("check error:%s\n", error->message);
+ }
+
+ if (ret)
+ {
+ gtk_label_set_text (GTK_LABEL (self->placeholder), _("Correct spelling"));
+ fill_suggestions_box (self, NULL, NULL);
+ }
+ else
+ {
+ fill_suggestions_box (self, word, &first_result);
+ if (!ide_str_empty0 (first_result))
+ gtk_entry_set_text (self->word_entry, first_result);
+ else
+ gtk_label_set_text (GTK_LABEL (self->placeholder), _("No suggestioons"));
+ }
+}
+
+static void
+ide_editor_spell_widget__add_dict_button_clicked_cb (IdeEditorSpellWidget *self,
+ GtkButton *button)
+{
+ const gchar *word;
+
+ g_assert (IDE_IS_EDITOR_SPELL_WIDGET (self));
+ g_assert (GTK_IS_BUTTON (button));
+
+ word = gtk_label_get_text (self->word_label);
+ g_assert (!ide_str_empty0 (word));
+
+ gspell_checker_add_word_to_personal (self->checker, word, -1);
+ jump_to_next_misspelled_word (self);
+}
+
+static void
+ide_editor_spell_widget__ignore_button_clicked_cb (IdeEditorSpellWidget *self,
+ GtkButton *button)
+{
+ g_assert (IDE_IS_EDITOR_SPELL_WIDGET (self));
+ g_assert (GTK_IS_BUTTON (button));
+
+ jump_to_next_misspelled_word (self);
+}
+
+static void
+ide_editor_spell_widget__ignore_all_button_clicked_cb (IdeEditorSpellWidget *self,
+ GtkButton *button)
+{
+ const gchar *word;
+
+ g_assert (IDE_IS_EDITOR_SPELL_WIDGET (self));
+ g_assert (GTK_IS_BUTTON (button));
+
+ word = gtk_label_get_text (self->word_label);
+ g_assert (!ide_str_empty0 (word));
+
+ gspell_checker_add_word_to_session (self->checker, word, -1);
+ jump_to_next_misspelled_word (self);
+}
+
+static void
+change_misspelled_word (IdeEditorSpellWidget *self,
+ gboolean change_all)
+{
+ const gchar *word;
+ g_autofree gchar *change_to = NULL;
+
+ g_assert (IDE_IS_EDITOR_SPELL_WIDGET (self));
+
+ word = gtk_label_get_text (self->word_label);
+ g_assert (!ide_str_empty0 (word));
+
+ change_to = g_strdup (gtk_entry_get_text (self->word_entry));
+ g_assert (!ide_str_empty0 (change_to));
+
+ gspell_checker_set_correction (self->checker, word, -1, change_to, -1);
+
+ if (change_all)
+ gspell_navigator_change_all (self->navigator, word, change_to);
+ else
+ gspell_navigator_change (self->navigator, word, change_to);
+
+ jump_to_next_misspelled_word (self);
+}
+
+static void
+ide_editor_spell_widget__change_button_clicked_cb (IdeEditorSpellWidget *self,
+ GtkButton *button)
+{
+ change_misspelled_word (self, FALSE);
+}
+
+static void
+ide_editor_spell_widget__change_all_button_clicked_cb (IdeEditorSpellWidget *self,
+ GtkButton *button)
+{
+ change_misspelled_word (self, TRUE);
+}
+
+static void
+ide_editor_spell_widget__row_selected_cb (IdeEditorSpellWidget *self,
+ GtkListBoxRow *row,
+ GtkListBox *listbox)
+{
+ const gchar *word;
+ GtkLabel *label;
+
+ if (row != NULL)
+ {
+ label = GTK_LABEL (gtk_bin_get_child (GTK_BIN (row)));
+ word = gtk_label_get_text (label);
+ gtk_entry_set_text (self->word_entry, word);
+ gtk_editable_set_position (GTK_EDITABLE (self->word_entry), -1);
+ }
+}
+
+static void
+ide_editor_spell_widget__row_activated_cb (IdeEditorSpellWidget *self,
+ GtkListBoxRow *row,
+ GtkListBox *listbox)
+{
+ if (row != NULL)
+ change_misspelled_word (self, FALSE);
+}
+
+static gboolean
+ide_editor_spell_widget__key_press_event_cb (IdeEditorSpellWidget *self,
+ GdkEventKey *event)
+{
+ g_assert (IDE_IS_SOURCE_VIEW (self->view));
+
+ switch (event->keyval)
+ {
+ case GDK_KEY_Escape:
+ ide_widget_action (GTK_WIDGET (self), "spell-entry", "exit-spell", NULL);
+ return GDK_EVENT_STOP;
+
+ default:
+ break;
+ }
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static void
+ide_editor_frame_spell_widget_mapped_cb (IdeEditorSpellWidget *self)
+{
+ GActionGroup *group = NULL;
+ GtkWidget *widget = GTK_WIDGET (self);
+ g_autoptr (GVariant) value = NULL;
+
+ g_assert (IDE_IS_EDITOR_SPELL_WIDGET (self));
+
+ while ((group == NULL) && (widget != NULL))
+ {
+ group = gtk_widget_get_action_group (widget, "view");
+ widget = gtk_widget_get_parent (widget);
+ }
+
+ if (group != NULL &&
+ NULL != (self->view_spellchecking_action = g_action_map_lookup_action (G_ACTION_MAP (group),
+ "spellchecking")))
+ {
+ value = g_action_get_state (self->view_spellchecking_action);
+ self->view_spellchecker_set = g_variant_get_boolean (value);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->highlight_checkbutton),
+ self->view_spellchecker_set);
+ }
+
+ jump_to_next_misspelled_word (self);
+}
+
+static void
+ide_editor_spell_widget__highlight_checkbutton_toggled_cb (IdeEditorSpellWidget *self,
+ GtkCheckButton *button)
+{
+ GspellTextView *spell_text_view;
+ gboolean state;
+
+ g_assert (IDE_IS_EDITOR_SPELL_WIDGET (self));
+
+ state = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button));
+ spell_text_view = gspell_text_view_get_from_gtk_text_view (GTK_TEXT_VIEW (self->view));
+ gspell_text_view_set_inline_spell_checking (spell_text_view, state);
+}
+
+static void
+ide_editor_spell_widget__language_notify_cb (IdeEditorSpellWidget *self,
+ GParamSpec *pspec,
+ GtkButton *language_chooser_button)
+{
+ const GspellLanguage *current_language;
+ const GspellLanguage *spell_language;
+ g_autofree gchar *word = NULL;
+ g_autofree gchar *first_result = NULL;
+ GtkListBoxRow *row;
+
+ g_assert (IDE_IS_EDITOR_SPELL_WIDGET (self));
+
+ current_language = gspell_checker_get_language (self->checker);
+ spell_language = gspell_language_chooser_get_language (GSPELL_LANGUAGE_CHOOSER (language_chooser_button));
+ if (gspell_language_compare (current_language, spell_language) != 0)
+ {
+ gspell_checker_set_language (self->checker, spell_language);
+ fill_suggestions_box (self, word, &first_result);
+ if (!ide_str_empty0 (first_result))
+ {
+ row = gtk_list_box_get_row_at_index (self->suggestions_box, 0);
+ gtk_list_box_select_row (self->suggestions_box, row);
+ }
+ }
+}
+
+static void
+ide_editor_spell_widget_constructed (GObject *object)
+{
+ IdeEditorSpellWidget *self = (IdeEditorSpellWidget *)object;
+ GspellTextBuffer *spell_buffer;
+
+ g_assert (IDE_IS_SOURCE_VIEW (self->view));
+
+ self->buffer = IDE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (self->view)));
+ ide_buffer_set_spell_checking (self->buffer, TRUE);
+
+ spell_buffer = gspell_text_buffer_get_from_gtk_text_buffer (GTK_TEXT_BUFFER (self->buffer));
+ self->checker = gspell_text_buffer_get_spell_checker (spell_buffer);
+
+ self->spellchecker_language = gspell_checker_get_language (self->checker);
+ gspell_language_chooser_set_language (GSPELL_LANGUAGE_CHOOSER (self->language_chooser_button),
+ self->spellchecker_language);
+
+ self->navigator = gspell_navigator_text_view_new (GTK_TEXT_VIEW (self->view));
+
+ g_signal_connect_swapped (self->word_entry,
+ "changed",
+ G_CALLBACK (ide_editor_spell_widget__word_entry_changed_cb),
+ self);
+
+ g_signal_connect_swapped (self->check_button,
+ "clicked",
+ G_CALLBACK (ide_editor_spell_widget__check_button_clicked_cb),
+ self);
+
+ g_signal_connect_swapped (self->add_dict_button,
+ "clicked",
+ G_CALLBACK (ide_editor_spell_widget__add_dict_button_clicked_cb),
+ self);
+
+ g_signal_connect_swapped (self->ignore_button,
+ "clicked",
+ G_CALLBACK (ide_editor_spell_widget__ignore_button_clicked_cb),
+ self);
+
+ g_signal_connect_swapped (self->ignore_all_button,
+ "clicked",
+ G_CALLBACK (ide_editor_spell_widget__ignore_all_button_clicked_cb),
+ self);
+
+ g_signal_connect_swapped (self->change_button,
+ "clicked",
+ G_CALLBACK (ide_editor_spell_widget__change_button_clicked_cb),
+ self);
+
+ g_signal_connect_swapped (self->change_all_button,
+ "clicked",
+ G_CALLBACK (ide_editor_spell_widget__change_all_button_clicked_cb),
+ self);
+
+ g_signal_connect_swapped (self->suggestions_box,
+ "row-selected",
+ G_CALLBACK (ide_editor_spell_widget__row_selected_cb),
+ self);
+
+ g_signal_connect_swapped (self->suggestions_box,
+ "row-activated",
+ G_CALLBACK (ide_editor_spell_widget__row_activated_cb),
+ self);
+
+ g_signal_connect_swapped (self,
+ "key-press-event",
+ G_CALLBACK (ide_editor_spell_widget__key_press_event_cb),
+ self);
+
+ g_signal_connect_swapped (self->highlight_checkbutton,
+ "toggled",
+ G_CALLBACK (ide_editor_spell_widget__highlight_checkbutton_toggled_cb),
+ self);
+
+ g_signal_connect_object (self->language_chooser_button,
+ "notify::language",
+ G_CALLBACK (ide_editor_spell_widget__language_notify_cb),
+ self,
+ G_CONNECT_SWAPPED);
+
+ self->placeholder = gtk_label_new (NULL);
+ gtk_widget_set_visible (self->placeholder, TRUE);
+ gtk_list_box_set_placeholder (self->suggestions_box, self->placeholder);
+
+ /* Due to the change of focus between the view and the spellchecker widget,
+ * we need to start checking only when the widget is mapped,
+ * so the view can keep the selection on the first word.
+ */
+ g_signal_connect_object (self,
+ "map",
+ G_CALLBACK (ide_editor_frame_spell_widget_mapped_cb),
+ NULL,
+ G_CONNECT_AFTER);
+}
+
+static void
+ide_editor_spell_widget_finalize (GObject *object)
+{
+ IdeEditorSpellWidget *self = (IdeEditorSpellWidget *)object;
+ GspellTextView *spell_text_view;
+ const GspellLanguage *spell_language;
+ GtkTextBuffer *buffer;
+
+ printf ("ide_editor_spell_widget_finalize\n");
+ g_clear_object (&self->navigator);
+
+ /* Set back the view spellchecking previous state */
+ spell_text_view = gspell_text_view_get_from_gtk_text_view (GTK_TEXT_VIEW (self->view));
+ if (self->view_spellchecker_set)
+ {
+ gspell_text_view_set_inline_spell_checking (spell_text_view, TRUE);
+ spell_language = gspell_checker_get_language (self->checker);
+ if (gspell_language_compare (self->spellchecker_language, spell_language) != 0)
+ gspell_checker_set_language (self->checker, self->spellchecker_language);
+ }
+ else
+ {
+ gspell_text_view_set_inline_spell_checking (spell_text_view, FALSE);
+ gspell_text_view_set_enable_language_menu (spell_text_view, FALSE);
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self->view));
+ ide_buffer_set_spell_checking (IDE_BUFFER (buffer), FALSE);
+ }
+
+ G_OBJECT_CLASS (ide_editor_spell_widget_parent_class)->finalize (object);
+}
+
+static void
+ide_editor_spell_widget_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeEditorSpellWidget *self = IDE_EDITOR_SPELL_WIDGET (object);
+
+ switch (prop_id)
+ {
+ case PROP_VIEW:
+ g_value_set_object (value, ide_editor_spell_widget_get_view (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_editor_spell_widget_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeEditorSpellWidget *self = IDE_EDITOR_SPELL_WIDGET (object);
+
+ switch (prop_id)
+ {
+ case PROP_VIEW:
+ ide_editor_spell_widget_set_view (self, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_editor_spell_widget_class_init (IdeEditorSpellWidgetClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->constructed = ide_editor_spell_widget_constructed;
+ object_class->finalize = ide_editor_spell_widget_finalize;
+ object_class->get_property = ide_editor_spell_widget_get_property;
+ object_class->set_property = ide_editor_spell_widget_set_property;
+
+ properties [PROP_VIEW] =
+ g_param_spec_object ("view",
+ "View",
+ "The source view.",
+ IDE_TYPE_SOURCE_VIEW,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+
+ gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/builder/ui/ide-editor-spell-widget.ui");
+ gtk_widget_class_bind_template_child (widget_class, IdeEditorSpellWidget, word_label);
+ gtk_widget_class_bind_template_child (widget_class, IdeEditorSpellWidget, word_entry);
+ gtk_widget_class_bind_template_child (widget_class, IdeEditorSpellWidget, check_button);
+ gtk_widget_class_bind_template_child (widget_class, IdeEditorSpellWidget, add_dict_button);
+ gtk_widget_class_bind_template_child (widget_class, IdeEditorSpellWidget, ignore_button);
+ gtk_widget_class_bind_template_child (widget_class, IdeEditorSpellWidget, ignore_all_button);
+ gtk_widget_class_bind_template_child (widget_class, IdeEditorSpellWidget, change_button);
+ gtk_widget_class_bind_template_child (widget_class, IdeEditorSpellWidget, change_all_button);
+ gtk_widget_class_bind_template_child (widget_class, IdeEditorSpellWidget, close_button);
+ gtk_widget_class_bind_template_child (widget_class, IdeEditorSpellWidget, highlight_checkbutton);
+ gtk_widget_class_bind_template_child (widget_class, IdeEditorSpellWidget, language_chooser_button);
+ gtk_widget_class_bind_template_child (widget_class, IdeEditorSpellWidget, suggestions_box);
+}
+
+static void
+ide_editor_spell_widget_init (IdeEditorSpellWidget *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ self->view_spellchecker_set = FALSE;
+}
diff --git a/libide/editor/ide-editor-spell-widget.h b/libide/editor/ide-editor-spell-widget.h
new file mode 100644
index 0000000..c08bb08
--- /dev/null
+++ b/libide/editor/ide-editor-spell-widget.h
@@ -0,0 +1,38 @@
+/* ide-editor-spell-widget.h
+ *
+ * Copyright (C) 2016 Sebastien Lafargue <slafargue 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_EDITOR_SPELL_WIDGET_H
+#define IDE_EDITOR_SPELL_WIDGET_H
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#include "sourceview/ide-source-view.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_EDITOR_SPELL_WIDGET (ide_editor_spell_widget_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeEditorSpellWidget, ide_editor_spell_widget, IDE, EDITOR_SPELL_WIDGET, GtkBin)
+
+GtkWidget *ide_editor_spell_widget_new (IdeSourceView *source_view);
+GtkWidget *ide_editor_spell_widget_get_entry (IdeEditorSpellWidget *self);
+
+G_END_DECLS
+
+#endif /* IDE_EDITOR_SPELL_WIDGET_H */
diff --git a/libide/editor/ide-editor-spell-widget.ui b/libide/editor/ide-editor-spell-widget.ui
new file mode 100644
index 0000000..7af36ca
--- /dev/null
+++ b/libide/editor/ide-editor-spell-widget.ui
@@ -0,0 +1,310 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.15 -->
+ <template class="IdeEditorSpellWidget" parent="GtkBin">
+ <child>
+ <object class="GtkFrame" id="spell_frame">
+ <property name="visible">true</property>
+ <property name="margin-end">12</property>
+ <style>
+ <class name="gb-spell-frame"/>
+ </style>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">true</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">7</property>
+ <child>
+ <object class="GtkGrid">
+ <property name="visible">true</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">true</property>
+ <property name="label" translatable="yes">Misspelled</property>
+ <property name="halign">end</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">true</property>
+ <property name="label" translatable="yes">Change _to</property>
+ <property name="halign">end</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">word_entry</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">true</property>
+ <property name="label" translatable="yes">_Suggestions</property>
+ <property name="halign">end</property>
+ <property name="valign">start</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">suggestions_box</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">true</property>
+ <property name="orientation">horizontal</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="word_label">
+ <property name="visible">true</property>
+ <property name="halign">start</property>
+ <property name="margin_left">10</property>
+ <property name="selectable">True</property>
+ <property name="use_markup">True</property>
+ <property name="wrap">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="count_label">
+ <property name="visible">true</property>
+ <property name="halign">end</property>
+ <property name="expand">true</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="word_entry">
+ <property name="visible">true</property>
+ <property name="can_focus">true</property>
+ <property name="width-chars">20</property>
+ <property name="max-width-chars">30</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="visible">true</property>
+ <property name="expand">true</property>
+ <property name="shadow_type">in</property>
+ <property name="min-content-height">110</property>
+ <property name="max-content-height">110</property>
+ <property name="propagate-natural-height">true</property>
+ <child>
+ <object class="GtkListBox" id="suggestions_box">
+ <property name="visible">true</property>
+ <property name="can_focus">true</property>
+ <property name="activate-on-single-click">false</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="add_dict_button">
+ <property name="label" translatable="yes">Add w_ord</property>
+ <property name="visible">true</property>
+ <property name="can_focus">true</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="check_button">
+ <property name="label" translatable="yes">Check _word</property>
+ <property name="visible">true</property>
+ <property name="can_focus">true</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="ignore_button">
+ <property name="label" translatable="yes">_Ignore</property>
+ <property name="visible">true</property>
+ <property name="can_focus">true</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">3</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="change_button">
+ <property name="label" translatable="yes">Cha_nge</property>
+ <property name="visible">true</property>
+ <property name="can_focus">true</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">3</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="ignore_all_button">
+ <property name="label" translatable="yes">Ignore _All</property>
+ <property name="visible">true</property>
+ <property name="can_focus">true</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">4</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="change_all_button">
+ <property name="label" translatable="yes">Change A_ll</property>
+ <property name="visible">true</property>
+ <property name="can_focus">true</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">4</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="close_button">
+ <property name="visible">true</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="focus_on_click">false</property>
+ <property name="action-name">spell-entry.exit-spell</property>
+ <style>
+ <class name="close"/>
+ </style>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">true</property>
+ <property name="icon_name">window-close-symbolic</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">5</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="option_frame">
+ <property name="visible">true</property>
+ <property name="label" translatable="yes">Options</property>
+ <property name="shadow_type">in</property>
+ <property name="halign">fill</property>
+ <property name="valign">fill</property>
+ <child>
+ <object class="GtkGrid">
+ <property name="visible">true</property>
+ <property name="can_focus">false</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">6</property>
+ <property name="margin">6</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">true</property>
+ <property name="label" translatable="yes">_Language</property>
+ <property name="halign">end</property>
+ <property name="expand">true</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">language_chooser_button</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="highlight_checkbutton">
+ <property name="visible">true</property>
+ <property name="can_focus">true</property>
+ <property name="halign">end</property>
+ <property name="expand">true</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GspellLanguageChooserButton" id="language_chooser_button">
+ <property name="visible">true</property>
+ <property name="can_focus">true</property>
+ <property name="expand">true</property>
+ <property name="valign">center</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">true</property>
+ <property name="label" translatable="yes">_Highlight in the view</property>
+ <property name="expand">true</property>
+ <property name="halign">start</property>
+ <property name="margin-start">6</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">highlight_checkbutton</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <style>
+ <class name="gb-spell-option-frame"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="width">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/libide/ide-types.h b/libide/ide-types.h
index 230ebaa..80e9a62 100644
--- a/libide/ide-types.h
+++ b/libide/ide-types.h
@@ -57,6 +57,8 @@ typedef struct _IdeDiagnosticProvider IdeDiagnosticProvider;
typedef struct _IdeDiagnostics IdeDiagnostics;
typedef struct _IdeDiagnosticsManager IdeDiagnosticsManager;
+typedef struct _IdeEditorFrame IdeEditorFrame;
+
typedef struct _IdeEnvironment IdeEnvironment;
typedef struct _IdeEnvironmentVariable IdeEnvironmentVariable;
diff --git a/libide/resources/libide.gresource.xml b/libide/resources/libide.gresource.xml
index 5f49c8f..1f69cf1 100644
--- a/libide/resources/libide.gresource.xml
+++ b/libide/resources/libide.gresource.xml
@@ -54,6 +54,7 @@
<file compressed="true" alias="ide-editor-frame.ui">../editor/ide-editor-frame.ui</file>
<file compressed="true"
alias="ide-editor-layout-stack-controls.ui">../editor/ide-editor-layout-stack-controls.ui</file>
<file compressed="true" alias="ide-editor-perspective.ui">../editor/ide-editor-perspective.ui</file>
+ <file compressed="true" alias="ide-editor-spell-widget.ui">../editor/ide-editor-spell-widget.ui</file>
<file compressed="true" alias="ide-editor-tweak-widget.ui">../editor/ide-editor-tweak-widget.ui</file>
<file compressed="true" alias="ide-editor-view.ui">../editor/ide-editor-view.ui</file>
<file compressed="true" alias="ide-greeter-perspective.ui">../greeter/ide-greeter-perspective.ui</file>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 4ce0441..c99feba 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -55,6 +55,7 @@ libide/editor/ide-editor-layout-stack-controls.c
libide/editor/ide-editor-layout-stack-controls.ui
libide/editor/ide-editor-perspective.c
libide/editor/ide-editor-perspective.ui
+libide/editor/ide-editor-spell-widget.ui
libide/editor/ide-editor-tweak-widget.ui
libide/editor/ide-editor-view-actions.c
libide/editor/ide-editor-view.c
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]