1 /*
2  * This program is free software; you can redistribute it and/or modify it
3  * under the terms of the GNU General Public License as published by the Free
4  * Software Foundation; either version 2 of the License, or (at your option)
5  * any later version.
6  *
7  * This program is distributed in the hope that it will be useful, but WITHOUT
8  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
9  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
10  * more details.
11  *
12  * You should have received a copy of the GNU General Public License along with
13  * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
14  * Place, Suite 330, Boston, MA  02111-1307  USA
15  */
16 
17 #include <mousepad/mousepad-private.h>
18 #include <mousepad/mousepad-settings.h>
19 #include <mousepad/mousepad-util.h>
20 #include <mousepad/mousepad-application.h>
21 #include <mousepad/mousepad-view.h>
22 #include <mousepad/mousepad-window.h>
23 
24 
25 
26 #define mousepad_view_get_buffer(view) (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)))
27 
28 
29 
30 /* GObject virtual functions */
31 static void      mousepad_view_finalize                      (GObject            *object);
32 static void      mousepad_view_set_property                  (GObject            *object,
33                                                               guint               prop_id,
34                                                               const GValue       *value,
35                                                               GParamSpec         *pspec);
36 
37 /* GtkWidget virtual functions */
38 static gboolean  mousepad_view_drag_motion                   (GtkWidget          *widget,
39                                                               GdkDragContext     *context,
40                                                               gint                x,
41                                                               gint                y,
42                                                               guint               timestamp);
43 
44 /* GtkTextView virtual functions */
45 static void      mousepad_view_cut_clipboard                 (GtkTextView        *text_view);
46 static void      mousepad_view_delete_from_cursor            (GtkTextView        *text_view,
47                                                               GtkDeleteType       type,
48                                                               int                 count);
49 static void      mousepad_view_paste_clipboard               (GtkTextView        *text_view);
50 
51 /* GtkSourceView virtual functions */
52 #if GTK_SOURCE_MAJOR_VERSION >= 4
53 static void      mousepad_view_move_lines                    (GtkSourceView      *source_view,
54                                                               gboolean            down);
55 #else
56 static void      mousepad_view_move_lines                    (GtkSourceView      *source_view,
57                                                               gboolean            copy,
58                                                               gint                count);
59 #endif
60 static void      mousepad_view_move_words                    (GtkSourceView      *source_view,
61                                                               gint                count);
62 static void      mousepad_view_redo                          (GtkSourceView      *source_view);
63 static void      mousepad_view_undo                          (GtkSourceView      *source_view);
64 
65 /* MousepadView own functions */
66 static void      mousepad_view_transpose_range               (GtkTextBuffer       *buffer,
67                                                               GtkTextIter         *start_iter,
68                                                               GtkTextIter         *end_iter);
69 static void      mousepad_view_transpose_lines               (GtkTextBuffer       *buffer,
70                                                               GtkTextIter         *start_iter,
71                                                               GtkTextIter         *end_iter);
72 static void      mousepad_view_transpose_words               (GtkTextBuffer       *buffer,
73                                                               GtkTextIter         *iter);
74 static void      mousepad_view_set_font                      (MousepadView        *view,
75                                                               const gchar         *font);
76 static void      mousepad_view_set_show_whitespace           (MousepadView        *view,
77                                                               gboolean             show);
78 static void      mousepad_view_set_space_location_flags      (MousepadView        *view,
79                                                               gulong               flags);
80 static void      mousepad_view_set_show_line_endings         (MousepadView        *view,
81                                                               gboolean             show);
82 static void      mousepad_view_set_color_scheme              (MousepadView        *view,
83                                                               const gchar         *color_scheme);
84 static void      mousepad_view_set_word_wrap                 (MousepadView        *view,
85                                                               gboolean             enabled);
86 static void      mousepad_view_set_match_braces              (MousepadView        *view,
87                                                               gboolean             enabled);
88 
89 
90 
91 struct _MousepadViewClass
92 {
93   GtkSourceViewClass __parent__;
94 };
95 
96 struct _MousepadView
97 {
98   GtkSourceView         __parent__;
99 
100   /* property related */
101   GBinding                    *font_binding;
102   gboolean                     show_whitespace;
103   GtkSourceSpaceLocationFlags  space_location_flags;
104   gboolean                     show_line_endings;
105   gchar                       *color_scheme;
106   gboolean                     match_braces;
107 };
108 
109 
110 
111 enum
112 {
113   PROP_0,
114   PROP_FONT,
115   PROP_SHOW_WHITESPACE,
116   PROP_SPACE_LOCATION,
117   PROP_SHOW_LINE_ENDINGS,
118   PROP_COLOR_SCHEME,
119   PROP_WORD_WRAP,
120   PROP_MATCH_BRACES,
121   NUM_PROPERTIES
122 };
123 
124 
125 
G_DEFINE_TYPE(MousepadView,mousepad_view,GTK_SOURCE_TYPE_VIEW)126 G_DEFINE_TYPE (MousepadView, mousepad_view, GTK_SOURCE_TYPE_VIEW)
127 
128 
129 
130 static void
131 mousepad_view_class_init (MousepadViewClass *klass)
132 {
133   GObjectClass       *gobject_class = G_OBJECT_CLASS (klass);
134   GtkWidgetClass     *widget_class = GTK_WIDGET_CLASS (klass);
135   GtkTextViewClass   *textview_class = GTK_TEXT_VIEW_CLASS (klass);
136   GtkSourceViewClass *sourceview_class = GTK_SOURCE_VIEW_CLASS (klass);
137 
138   gobject_class->finalize = mousepad_view_finalize;
139   gobject_class->set_property = mousepad_view_set_property;
140 
141   widget_class->drag_motion = mousepad_view_drag_motion;
142 
143   textview_class->cut_clipboard = mousepad_view_cut_clipboard;
144   textview_class->delete_from_cursor = mousepad_view_delete_from_cursor;
145   textview_class->paste_clipboard = mousepad_view_paste_clipboard;
146 
147   sourceview_class->move_lines = mousepad_view_move_lines;
148   sourceview_class->move_words = mousepad_view_move_words;
149   sourceview_class->redo = mousepad_view_redo;
150   sourceview_class->undo = mousepad_view_undo;
151 
152   g_object_class_install_property (gobject_class, PROP_FONT,
153     g_param_spec_string ("font", "Font", "The font to use in the view",
154                          NULL, G_PARAM_WRITABLE));
155 
156   g_object_class_install_property (gobject_class, PROP_SHOW_WHITESPACE,
157     g_param_spec_boolean ("show-whitespace", "ShowWhitespace",
158                           "Whether whitespace is visualized in the view",
159                           FALSE, G_PARAM_WRITABLE));
160 
161   g_object_class_install_property (gobject_class, PROP_SPACE_LOCATION,
162     g_param_spec_flags ("space-location", "SpaceLocation",
163                         "The space locations to show in the view",
164                         GTK_SOURCE_TYPE_SPACE_LOCATION_FLAGS, GTK_SOURCE_SPACE_LOCATION_ALL,
165                         G_PARAM_WRITABLE));
166 
167   g_object_class_install_property (gobject_class, PROP_SHOW_LINE_ENDINGS,
168     g_param_spec_boolean ("show-line-endings", "ShowLineEndings",
169                           "Whether line-endings are visualized in the view",
170                           FALSE, G_PARAM_WRITABLE));
171 
172   g_object_class_install_property (gobject_class, PROP_COLOR_SCHEME,
173     g_param_spec_string ("color-scheme", "ColorScheme",
174                          "The id of the syntax highlighting color scheme to use",
175                          NULL, G_PARAM_WRITABLE));
176 
177   g_object_class_install_property (gobject_class, PROP_WORD_WRAP,
178     g_param_spec_boolean ("word-wrap", "WordWrap",
179                           "Whether to virtually wrap long lines in the view",
180                           FALSE, G_PARAM_WRITABLE));
181 
182   g_object_class_install_property (gobject_class, PROP_MATCH_BRACES,
183     g_param_spec_boolean ("match-braces", "MatchBraces",
184                           "Whether to highlight matching braces, parens, brackets, etc.",
185                           FALSE, G_PARAM_WRITABLE));
186 }
187 
188 
189 
190 static void
mousepad_view_buffer_changed(MousepadView * view,GParamSpec * pspec,gpointer user_data)191 mousepad_view_buffer_changed (MousepadView *view,
192                               GParamSpec   *pspec,
193                               gpointer      user_data)
194 {
195   GtkSourceBuffer *buffer;
196   buffer = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)));
197 
198   if (buffer != NULL)
199     {
200       GtkSourceStyleSchemeManager *manager;
201       GtkSourceStyleScheme        *scheme;
202       gboolean enable_highlight = TRUE;
203 
204       manager = gtk_source_style_scheme_manager_get_default ();
205       scheme = gtk_source_style_scheme_manager_get_scheme (manager,
206                  view->color_scheme ? view->color_scheme : "");
207 
208       if (scheme == NULL)
209         {
210           scheme = gtk_source_style_scheme_manager_get_scheme (manager, "classic");
211           enable_highlight = FALSE;
212         }
213 
214       gtk_source_buffer_set_style_scheme (buffer, scheme);
215       gtk_source_buffer_set_highlight_syntax (buffer, enable_highlight);
216       gtk_source_buffer_set_highlight_matching_brackets (buffer, view->match_braces);
217     }
218 }
219 
220 
221 
222 static void
mousepad_view_use_default_font(MousepadView * view)223 mousepad_view_use_default_font (MousepadView *view)
224 {
225   /* default font is used: unbind from GSettings, bind to application property */
226   if (MOUSEPAD_SETTING_GET_BOOLEAN (USE_DEFAULT_FONT))
227     {
228       g_settings_unbind (view, "font");
229       view->font_binding = g_object_bind_property (g_application_get_default (), "default-font",
230                                                    view, "font", G_BINDING_SYNC_CREATE);
231     }
232   /* default font is not used: unbind from application property, bind to GSettings */
233   else
234     {
235       if (view->font_binding != NULL)
236         {
237           g_binding_unbind (view->font_binding);
238           view->font_binding = NULL;
239         }
240 
241       MOUSEPAD_SETTING_BIND (FONT, view, "font", G_SETTINGS_BIND_GET);
242     }
243 }
244 
245 
246 
247 static void
mousepad_view_init(MousepadView * view)248 mousepad_view_init (MousepadView *view)
249 {
250   GApplication *application;
251 
252   /* initialize properties variables */
253   view->font_binding = NULL;
254   view->show_whitespace = FALSE;
255   view->space_location_flags = GTK_SOURCE_SPACE_LOCATION_ALL;
256   view->show_line_endings = FALSE;
257   view->color_scheme = g_strdup ("none");
258   view->match_braces = FALSE;
259 
260   /* make sure any buffers set on the view get the color scheme applied to them */
261   g_signal_connect (view, "notify::buffer",
262                     G_CALLBACK (mousepad_view_buffer_changed), NULL);
263 
264   /* bind Gsettings */
265 #define BIND_(setting, prop) \
266   MOUSEPAD_SETTING_BIND (setting, view, prop, G_SETTINGS_BIND_GET)
267 
268   BIND_ (AUTO_INDENT,            "auto-indent");
269   BIND_ (INDENT_ON_TAB,          "indent-on-tab");
270   BIND_ (INDENT_WIDTH,           "indent-width");
271   BIND_ (TAB_WIDTH,              "tab-width");
272   BIND_ (INSERT_SPACES,          "insert-spaces-instead-of-tabs");
273   BIND_ (SMART_BACKSPACE,        "smart-backspace");
274   BIND_ (SMART_HOME_END,         "smart-home-end");
275   BIND_ (SHOW_WHITESPACE,        "show-whitespace");
276   BIND_ (SHOW_LINE_ENDINGS,      "show-line-endings");
277   BIND_ (SHOW_LINE_MARKS,        "show-line-marks");
278   BIND_ (SHOW_LINE_NUMBERS,      "show-line-numbers");
279   BIND_ (SHOW_RIGHT_MARGIN,      "show-right-margin");
280   BIND_ (RIGHT_MARGIN_POSITION,  "right-margin-position");
281   BIND_ (HIGHLIGHT_CURRENT_LINE, "highlight-current-line");
282   BIND_ (COLOR_SCHEME,           "color-scheme");
283   BIND_ (WORD_WRAP,              "word-wrap");
284   BIND_ (MATCH_BRACES,           "match-braces");
285 
286 #undef BIND_
287 
288   /* bind the "font" property conditionally */
289   mousepad_view_use_default_font (view);
290   MOUSEPAD_SETTING_CONNECT_OBJECT (USE_DEFAULT_FONT, mousepad_view_use_default_font,
291                                    view, G_CONNECT_SWAPPED);
292 
293   /* bind the whitespace display property to that of the application */
294   application = g_application_get_default ();
295   g_object_bind_property (application, "space-location", view, "space-location", G_BINDING_SYNC_CREATE);
296 }
297 
298 
299 
300 static void
mousepad_view_finalize(GObject * object)301 mousepad_view_finalize (GObject *object)
302 {
303   MousepadView *view = MOUSEPAD_VIEW (object);
304 
305   /* cleanup color scheme name */
306   g_free (view->color_scheme);
307 
308   (*G_OBJECT_CLASS (mousepad_view_parent_class)->finalize) (object);
309 }
310 
311 
312 
313 static void
mousepad_view_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)314 mousepad_view_set_property (GObject      *object,
315                             guint         prop_id,
316                             const GValue *value,
317                             GParamSpec   *pspec)
318 {
319   MousepadView *view = MOUSEPAD_VIEW (object);
320 
321   switch (prop_id)
322     {
323     case PROP_FONT:
324       mousepad_view_set_font (view, g_value_get_string (value));
325       break;
326     case PROP_SHOW_WHITESPACE:
327       mousepad_view_set_show_whitespace (view, g_value_get_boolean (value));
328       break;
329     case PROP_SPACE_LOCATION:
330       mousepad_view_set_space_location_flags (view, g_value_get_flags (value));
331       break;
332     case PROP_SHOW_LINE_ENDINGS:
333       mousepad_view_set_show_line_endings (view, g_value_get_boolean (value));
334       break;
335     case PROP_COLOR_SCHEME:
336       mousepad_view_set_color_scheme (view, g_value_get_string (value));
337       break;
338     case PROP_WORD_WRAP:
339       mousepad_view_set_word_wrap (view, g_value_get_boolean (value));
340       break;
341     case PROP_MATCH_BRACES:
342       mousepad_view_set_match_braces (view, g_value_get_boolean (value));
343       break;
344     default:
345       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
346       break;
347     }
348 }
349 
350 
351 
352 static gboolean
mousepad_view_drag_motion(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint timestamp)353 mousepad_view_drag_motion (GtkWidget      *widget,
354                            GdkDragContext *context,
355                            gint            x,
356                            gint            y,
357                            guint           timestamp)
358 {
359   GtkTargetList *target_list;
360   gboolean       drop_zone;
361 
362   /* chain up to parent */
363   drop_zone = GTK_WIDGET_CLASS (mousepad_view_parent_class)->drag_motion (widget, context,
364                                                                           x, y, timestamp);
365 
366   /* enforce acceptance of our targets, especially when hovering over selections */
367   target_list = gtk_target_list_new (drop_targets, G_N_ELEMENTS (drop_targets));
368   if (gtk_drag_dest_find_target (widget, context, target_list) != GDK_NONE)
369   {
370     gdk_drag_status (context, gdk_drag_context_get_suggested_action (context), timestamp);
371     drop_zone = TRUE;
372   }
373 
374   gtk_target_list_unref (target_list);
375 
376   return drop_zone;
377 }
378 
379 
380 
381 static void
mousepad_view_cut_clipboard(GtkTextView * text_view)382 mousepad_view_cut_clipboard (GtkTextView *text_view)
383 {
384   /* let GTK do the main job */
385   GTK_TEXT_VIEW_CLASS (mousepad_view_parent_class)->cut_clipboard (text_view);
386 
387   /* scroll to cursor in our way */
388   mousepad_view_scroll_to_cursor (MOUSEPAD_VIEW (text_view));
389 }
390 
391 
392 
393 static void
mousepad_view_delete_from_cursor(GtkTextView * text_view,GtkDeleteType type,int count)394 mousepad_view_delete_from_cursor (GtkTextView   *text_view,
395                                   GtkDeleteType  type,
396                                   int            count)
397 {
398   GtkTextBuffer *buffer;
399   GtkTextIter    iter, start, end;
400   GtkTextMark   *start_mark, *end_mark;
401   gchar         *text = NULL, *eol;
402   gint           line, column, n_lines;
403 
404   /* override only GTK_DELETE_PARAGRAPHS to make "win.edit.delete-line" work as expected */
405   if (type == GTK_DELETE_PARAGRAPHS)
406     {
407       /* get iter at cursor */
408       buffer = mousepad_view_get_buffer (MOUSEPAD_VIEW (text_view));
409       gtk_text_buffer_get_iter_at_mark (buffer, &iter, gtk_text_buffer_get_insert (buffer));
410 
411       /* store current line and column numbers, treating end iter as a special case, to have
412        * consistent cursor positions at undo/redo (the undo manager enforces this cursor
413        * position at redo, so the best we can do is to enforce it also at undo) */
414       line = gtk_text_iter_get_line (&iter);
415       if (gtk_text_iter_is_end (&iter))
416         column = -1;
417       else
418         column = mousepad_util_get_real_line_offset (&iter);
419 
420       /* begin a user action, playing with the undo manager to preserve cursor position */
421       g_object_freeze_notify (G_OBJECT (buffer));
422       gtk_text_buffer_begin_user_action (buffer);
423 
424       /* insert fake text, so that cursor position is restored when undoing the action */
425       gtk_text_buffer_insert (buffer, &iter, "c", 1);
426 
427       /* delimit the line to delete, paragraph delimiter excluded */
428       start = iter;
429       gtk_text_iter_set_line_offset (&start, 0);
430       end = start;
431       gtk_text_iter_forward_to_line_end (&end);
432 
433       /* handle paragraph delimiter and final cursor position */
434       if (G_LIKELY ((n_lines = gtk_text_buffer_get_line_count (buffer)) > 1))
435         {
436           /* reduce the last line case to the general case by reversing the last two lines */
437           if (line == n_lines - 1)
438             {
439               /* mark deletion positions */
440               start_mark = gtk_text_buffer_create_mark (buffer, NULL, &start, FALSE);
441               end_mark = gtk_text_buffer_create_mark (buffer, NULL, &end, TRUE);
442 
443               /* copy the penultimate line, paragraph delimiter aside */
444               gtk_text_buffer_get_iter_at_line (buffer, &start, line - 1);
445               end = start;
446               if (! gtk_text_iter_ends_line (&end))
447                 gtk_text_iter_forward_to_line_end (&end);
448 
449               text = gtk_text_buffer_get_text (buffer, &start, &end, TRUE);
450               iter = end;
451               gtk_text_iter_forward_char (&end);
452               eol = gtk_text_buffer_get_text (buffer, &iter, &end, TRUE);
453 
454               /* delete the penultimate line, paragraph delimiter included */
455               gtk_text_buffer_delete (buffer, &start, &end);
456 
457               /* paste the penultimate line as last line (paragraph delimiter switched) */
458               gtk_text_buffer_get_end_iter (buffer, &iter);
459               gtk_text_buffer_insert (buffer, &iter, eol, -1);
460               gtk_text_buffer_insert (buffer, &iter, text, -1);
461               g_free (text);
462               g_free (eol);
463 
464               /* restore deletion positions at their marks */
465               gtk_text_buffer_get_iter_at_mark (buffer, &start, start_mark);
466               gtk_text_buffer_get_iter_at_mark (buffer, &end, end_mark);
467             }
468 
469           /* add the paragraph delimiter to the line to delete */
470           gtk_text_iter_forward_char (&end);
471 
472           /* place cursor at its position after line deletion, preserving old position
473            * as far as possible */
474           mousepad_util_place_cursor (buffer, line + 1, column);
475 
476           /* finally, add the text portion up to the future cursor position to the text
477            * to delete, so that restoring it afterwards will restore cursor position when
478            * redoing the action */
479           iter = end;
480           gtk_text_buffer_get_iter_at_mark (buffer, &end, gtk_text_buffer_get_insert (buffer));
481           text = gtk_text_buffer_get_text (buffer, &iter, &end, TRUE);
482         }
483 
484       /* delete delimited text */
485       gtk_text_buffer_delete (buffer, &start, &end);
486 
487       /* restore text to the left of the cursor */
488       if (text != NULL)
489         {
490           gtk_text_buffer_insert_at_cursor (buffer, text, -1);
491           g_free (text);
492         }
493 
494       /* end user action */
495       gtk_text_buffer_end_user_action (buffer);
496       g_object_thaw_notify (G_OBJECT (buffer));
497 
498       return;
499     }
500 
501   /* let GTK handle other cases */
502   GTK_TEXT_VIEW_CLASS (mousepad_view_parent_class)->delete_from_cursor (text_view, type, count);
503 }
504 
505 
506 
507 static void
mousepad_view_paste_clipboard(GtkTextView * text_view)508 mousepad_view_paste_clipboard (GtkTextView *text_view)
509 {
510   /* let GTK do the main job */
511   GTK_TEXT_VIEW_CLASS (mousepad_view_parent_class)->paste_clipboard (text_view);
512 
513   /* scroll to cursor in our way */
514   mousepad_view_scroll_to_cursor (MOUSEPAD_VIEW (text_view));
515 }
516 
517 
518 
519 static void
_mousepad_view_move_lines(GtkSourceView * source_view,gboolean down)520 _mousepad_view_move_lines (GtkSourceView *source_view,
521                            gboolean       down)
522 {
523   GtkTextBuffer *buffer;
524   GtkTextIter    start, end, iter;
525   gint           n_lines, start_line, end_line, start_char, end_char;
526   gboolean       cursor_start = FALSE, inserted = FALSE;
527 
528   /* get selection lines and character offsets */
529   buffer = mousepad_view_get_buffer (MOUSEPAD_VIEW (source_view));
530   n_lines = gtk_text_buffer_get_line_count (buffer);
531   gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
532   start_line = gtk_text_iter_get_line (&start);
533   end_line = gtk_text_iter_get_line (&end);
534   start_char = gtk_text_iter_get_line_offset (&start);
535   end_char = gtk_text_iter_get_line_offset (&end);
536 
537   /* determine cursor position */
538   gtk_text_buffer_get_iter_at_mark (buffer, &iter, gtk_text_buffer_get_insert (buffer));
539   if (gtk_text_iter_equal (&iter, &start))
540     cursor_start = TRUE;
541 
542   /* begin a user action */
543   g_object_freeze_notify (G_OBJECT (buffer));
544   gtk_text_buffer_begin_user_action (buffer);
545 
546   /* insert fake text on last line if needed, to not make the last empty line a special case */
547   if ((down && end_line == n_lines - 2 && (end_char != 0 || start_line == end_line))
548       || (! down && start_line == n_lines - 1))
549     {
550       gtk_text_buffer_get_end_iter (buffer, &end);
551       if (gtk_text_iter_get_chars_in_line (&end) == 0)
552         {
553           gtk_text_buffer_insert (buffer, &end, "c", 1);
554           inserted = TRUE;
555         }
556     }
557 
558   /* compute new start/end lines */
559   if (! down && start_line != 0)
560     {
561       start_line--;
562       end_line--;
563     }
564   else if (down && end_line != n_lines - 1)
565     {
566       start_line++;
567       end_line++;
568     }
569 
570   /* let GSV move lines */
571 #if GTK_SOURCE_MAJOR_VERSION >= 4
572   GTK_SOURCE_VIEW_CLASS (mousepad_view_parent_class)->move_lines (source_view, down);
573 #else
574   GTK_SOURCE_VIEW_CLASS (mousepad_view_parent_class)->move_lines (source_view, FALSE, down ? 1 : -1);
575 #endif
576 
577   /* delete fake text */
578   if (inserted)
579     {
580       down = !! down;
581       gtk_text_buffer_get_iter_at_line_offset (buffer, &start, start_line - down, 0);
582       gtk_text_buffer_get_iter_at_line_offset (buffer, &end, start_line - down, 1);
583       gtk_text_buffer_delete (buffer, &start, &end);
584     }
585 
586   /* restore selection */
587   gtk_text_buffer_get_iter_at_line_offset (buffer, &start, start_line, start_char);
588   gtk_text_buffer_get_iter_at_line_offset (buffer, &end, end_line, end_char);
589   if (cursor_start)
590     gtk_text_buffer_select_range (buffer, &start, &end);
591   else
592     gtk_text_buffer_select_range (buffer, &end, &start);
593 
594   /* end user action */
595   gtk_text_buffer_end_user_action (buffer);
596   g_object_thaw_notify (G_OBJECT (buffer));
597 }
598 
599 
600 
601 #if GTK_SOURCE_MAJOR_VERSION >= 4
602 
603 static void
mousepad_view_move_lines(GtkSourceView * source_view,gboolean down)604 mousepad_view_move_lines (GtkSourceView *source_view,
605                           gboolean       down)
606 {
607   _mousepad_view_move_lines (source_view, down);
608 }
609 
610 #else
611 
612 static void
mousepad_view_move_lines(GtkSourceView * source_view,gboolean copy,gint count)613 mousepad_view_move_lines (GtkSourceView *source_view,
614                           gboolean       copy,
615                           gint           count)
616 {
617   _mousepad_view_move_lines (source_view, copy);
618 }
619 
620 #endif
621 
622 
623 
624 static void
mousepad_view_move_words(GtkSourceView * source_view,gint count)625 mousepad_view_move_words (GtkSourceView *source_view,
626                           gint           count)
627 {
628   GtkTextBuffer *buffer, *test_buffer;
629   GtkWidget     *test_view;
630   GtkTextIter    start, end;
631   gchar         *text;
632   gint           n_chars, start_offset, end_offset;
633   gboolean       succeed;
634 
635   /*
636    * GSV sometimes fails to move words correctly, and removes content in these cases:
637    * see https://gitlab.gnome.org/GNOME/gtksourceview/-/issues/190.
638    * Below, we first test in a virtual view if the operation succeeds, before letting
639    * GSV operate on the real view.
640    */
641 
642   /* get data to build the test view */
643   buffer = mousepad_view_get_buffer (MOUSEPAD_VIEW (source_view));
644   n_chars = gtk_text_buffer_get_char_count (buffer);
645   gtk_text_buffer_get_bounds (buffer, &start, &end);
646   text = gtk_text_buffer_get_text (buffer, &start, &end, TRUE);
647 
648   gtk_text_buffer_get_iter_at_mark (buffer, &start, gtk_text_buffer_get_insert (buffer));
649   gtk_text_buffer_get_iter_at_mark (buffer, &end, gtk_text_buffer_get_selection_bound (buffer));
650   start_offset = gtk_text_iter_get_offset (&start);
651   end_offset = gtk_text_iter_get_offset (&end);
652 
653   /* build the test view */
654   test_view = g_object_ref_sink (gtk_source_view_new ());
655   test_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (test_view));
656   gtk_text_buffer_set_text (test_buffer, text, -1);
657   gtk_text_buffer_get_iter_at_offset (test_buffer, &start, start_offset);
658   gtk_text_buffer_get_iter_at_offset (test_buffer, &end, end_offset);
659   gtk_text_buffer_select_range (test_buffer, &start, &end);
660 
661   /* exit if GSV fails to move words correctly */
662   g_signal_emit_by_name (test_view, "move-words", count);
663   succeed = (gtk_text_buffer_get_char_count (test_buffer) == n_chars);
664   g_object_unref (test_view);
665   g_free (text);
666   if (! succeed)
667     return;
668 
669   /* let GSV move words */
670   GTK_SOURCE_VIEW_CLASS (mousepad_view_parent_class)->move_words (source_view, count);
671 }
672 
673 
674 
675 static void
mousepad_view_redo(GtkSourceView * source_view)676 mousepad_view_redo (GtkSourceView *source_view)
677 {
678   /* let GSV do the main job */
679   GTK_SOURCE_VIEW_CLASS (mousepad_view_parent_class)->redo (source_view);
680 
681   /* scroll to cursor in our way */
682   mousepad_view_scroll_to_cursor (MOUSEPAD_VIEW (source_view));
683 }
684 
685 
686 
687 static void
mousepad_view_undo(GtkSourceView * source_view)688 mousepad_view_undo (GtkSourceView *source_view)
689 {
690   /* let GSV do the main job */
691   GTK_SOURCE_VIEW_CLASS (mousepad_view_parent_class)->undo (source_view);
692 
693   /* scroll to cursor in our way */
694   mousepad_view_scroll_to_cursor (MOUSEPAD_VIEW (source_view));
695 }
696 
697 
698 
699 gboolean
mousepad_view_scroll_to_cursor(gpointer data)700 mousepad_view_scroll_to_cursor (gpointer data)
701 {
702   MousepadView  *view = data;
703   GtkTextBuffer *buffer;
704 
705   /* get the buffer */
706   buffer = mousepad_view_get_buffer (view);
707 
708   /* scroll to visible area */
709   gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
710                                 gtk_text_buffer_get_insert (buffer),
711                                 0.02, FALSE, 0.0, 0.0);
712 
713   return FALSE;
714 }
715 
716 
717 
718 static void
mousepad_view_transpose_range(GtkTextBuffer * buffer,GtkTextIter * start_iter,GtkTextIter * end_iter)719 mousepad_view_transpose_range (GtkTextBuffer *buffer,
720                                GtkTextIter   *start_iter,
721                                GtkTextIter   *end_iter)
722 {
723   gchar *string, *reversed;
724   gint   offset;
725 
726   /* store start iter line offset */
727   offset = gtk_text_iter_get_offset (start_iter);
728 
729   /* get selected text */
730   string = gtk_text_buffer_get_slice (buffer, start_iter, end_iter, FALSE);
731   if (G_LIKELY (string))
732     {
733       /* reverse the string */
734       reversed = g_utf8_strreverse (string, -1);
735 
736       /* only change the buffer then the string changed */
737       if (G_LIKELY (reversed && strcmp (reversed, string) != 0))
738         {
739           /* delete the text between the iters */
740           gtk_text_buffer_delete (buffer, start_iter, end_iter);
741 
742           /* insert the reversed string */
743           gtk_text_buffer_insert (buffer, end_iter, reversed, -1);
744 
745           /* restore start iter */
746           gtk_text_buffer_get_iter_at_offset (buffer, start_iter, offset);
747         }
748 
749       /* cleanup */
750       g_free (string);
751       g_free (reversed);
752     }
753 }
754 
755 
756 
757 static void
mousepad_view_transpose_lines(GtkTextBuffer * buffer,GtkTextIter * start_iter,GtkTextIter * end_iter)758 mousepad_view_transpose_lines (GtkTextBuffer *buffer,
759                                GtkTextIter   *start_iter,
760                                GtkTextIter   *end_iter)
761 {
762   GString *string;
763   gint     start_line, end_line;
764   gint     i;
765   gchar   *slice;
766 
767   /* make sure the order is ok */
768   gtk_text_iter_order (start_iter, end_iter);
769 
770   /* get the line numbers */
771   start_line = gtk_text_iter_get_line (start_iter);
772   end_line = gtk_text_iter_get_line (end_iter);
773 
774   /* new string */
775   string = g_string_new (NULL);
776 
777   /* add the lines in reversed order to the string */
778   for (i = start_line; i <= end_line && i < G_MAXINT; i++)
779     {
780       /* get start iter */
781       gtk_text_buffer_get_iter_at_line (buffer, start_iter, i);
782 
783       /* set end iter */
784       *end_iter = *start_iter;
785 
786       /* only prepend when the iters won't be equal */
787       if (!gtk_text_iter_ends_line (end_iter))
788         {
789           /* move the iter to the end of this line */
790           gtk_text_iter_forward_to_line_end (end_iter);
791 
792           /* prepend line */
793           slice = gtk_text_buffer_get_slice (buffer, start_iter, end_iter, FALSE);
794           string = g_string_prepend (string, slice);
795           g_free (slice);
796         }
797 
798       /* prepend new line */
799       if (i < end_line)
800         string = g_string_prepend_c (string, '\n');
801     }
802 
803   /* get start iter again */
804   gtk_text_buffer_get_iter_at_line (buffer, start_iter, start_line);
805 
806   /* delete selection */
807   gtk_text_buffer_delete (buffer, start_iter, end_iter);
808 
809   /* insert reversed lines */
810   gtk_text_buffer_insert (buffer, end_iter, string->str, string->len);
811 
812   /* cleanup */
813   g_string_free (string, TRUE);
814 
815   /* restore start iter */
816   gtk_text_buffer_get_iter_at_line (buffer, start_iter, start_line);
817 }
818 
819 
820 
821 static void
mousepad_view_transpose_words(GtkTextBuffer * buffer,GtkTextIter * iter)822 mousepad_view_transpose_words (GtkTextBuffer *buffer,
823                                GtkTextIter   *iter)
824 {
825   GtkTextMark *left_mark, *right_mark;
826   GtkTextIter  start_left, end_left, start_right, end_right;
827   gchar       *word_left, *word_right;
828   gboolean     restore_cursor;
829 
830   /* left word end */
831   end_left = *iter;
832   if (!mousepad_util_iter_backward_text_start (&end_left))
833     return;
834 
835   /* left word start */
836   start_left = end_left;
837   if (!mousepad_util_iter_backward_word_start (&start_left))
838     return;
839 
840   /* right word start */
841   start_right = *iter;
842   if (!mousepad_util_iter_forward_text_start (&start_right))
843     return;
844 
845   /* right word end */
846   end_right = start_right;
847   if (!mousepad_util_iter_forward_word_end (&end_right))
848     return;
849 
850   /* check if we selected something usefull */
851   if (gtk_text_iter_get_line (&start_left) == gtk_text_iter_get_line (&end_right)
852       && !gtk_text_iter_equal (&start_left, &end_left)
853       && !gtk_text_iter_equal (&start_right, &end_right)
854       && !gtk_text_iter_equal (&end_left, &start_right))
855     {
856       /* get the words */
857       word_left = gtk_text_buffer_get_slice (buffer, &start_left, &end_left, FALSE);
858       word_right = gtk_text_buffer_get_slice (buffer, &start_right, &end_right, FALSE);
859 
860       /* check if we need to restore the cursor afterwards */
861       restore_cursor = gtk_text_iter_equal (iter, &start_right);
862 
863       /* insert marks at the start and end of the right word */
864       left_mark = gtk_text_buffer_create_mark (buffer, NULL, &start_right, TRUE);
865       right_mark = gtk_text_buffer_create_mark (buffer, NULL, &end_right, FALSE);
866 
867       /* delete the left word and insert the right word there */
868       gtk_text_buffer_delete (buffer, &start_left, &end_left);
869       gtk_text_buffer_insert (buffer, &start_left, word_right, -1);
870       g_free (word_right);
871 
872       /* restore the right word iters */
873       gtk_text_buffer_get_iter_at_mark (buffer, &start_right, left_mark);
874       gtk_text_buffer_get_iter_at_mark (buffer, &end_right, right_mark);
875 
876       /* delete the right words and insert the left word there */
877       gtk_text_buffer_delete (buffer, &start_right, &end_right);
878       gtk_text_buffer_insert (buffer, &end_right, word_left, -1);
879       g_free (word_left);
880 
881       /* restore the cursor if needed */
882       if (restore_cursor)
883         {
884           /* restore the iter from the left mark (left gravity) */
885           gtk_text_buffer_get_iter_at_mark (buffer, &start_right, left_mark);
886           gtk_text_buffer_place_cursor (buffer, &start_right);
887         }
888 
889       /* cleanup the marks */
890       gtk_text_buffer_delete_mark (buffer, left_mark);
891       gtk_text_buffer_delete_mark (buffer, right_mark);
892     }
893 }
894 
895 
896 
897 void
mousepad_view_transpose(MousepadView * view)898 mousepad_view_transpose (MousepadView *view)
899 {
900   GtkTextBuffer *buffer;
901   GtkTextIter    sel_start, sel_end;
902 
903   g_return_if_fail (MOUSEPAD_IS_VIEW (view));
904 
905   /* get the buffer */
906   buffer = mousepad_view_get_buffer (view);
907 
908   /* begin user action */
909   gtk_text_buffer_begin_user_action (buffer);
910 
911   if (gtk_text_buffer_get_selection_bounds (buffer, &sel_start, &sel_end))
912     {
913       /* if the selection is not on the same line, include the whole lines */
914       if (gtk_text_iter_get_line (&sel_start) == gtk_text_iter_get_line (&sel_end))
915         {
916           /* reverse selection */
917           mousepad_view_transpose_range (buffer, &sel_start, &sel_end);
918         }
919       else
920         {
921           /* reverse lines */
922           mousepad_view_transpose_lines (buffer, &sel_start, &sel_end);
923         }
924 
925       /* restore selection */
926       gtk_text_buffer_select_range (buffer, &sel_end, &sel_start);
927     }
928   else
929     {
930       /* get cursor iter */
931       gtk_text_buffer_get_iter_at_mark (buffer, &sel_start, gtk_text_buffer_get_insert (buffer));
932 
933       /* set end iter */
934       sel_end = sel_start;
935 
936       if (gtk_text_iter_starts_line (&sel_start))
937         {
938           /* swap this line with the line above */
939           if (gtk_text_iter_backward_line (&sel_end))
940             {
941               mousepad_view_transpose_lines (buffer, &sel_start, &sel_end);
942 
943               /* place cursor in its original position */
944               gtk_text_iter_set_line_offset (&sel_end, 0);
945               gtk_text_buffer_place_cursor (buffer, &sel_end);
946             }
947         }
948       else if (gtk_text_iter_ends_line (&sel_start))
949         {
950           /* swap this line with the line below */
951           if (gtk_text_iter_forward_line (&sel_end) || gtk_text_iter_starts_line (&sel_end))
952             {
953               mousepad_view_transpose_lines (buffer, &sel_start, &sel_end);
954 
955               /* for empty line place cursor at the start of just swapped line */
956               if (gtk_text_iter_ends_line (&sel_start))
957                 gtk_text_iter_forward_line (&sel_start);
958               /* for non-empty line place cursor in its original position */
959               else
960                 gtk_text_iter_forward_to_line_end (&sel_start);
961 
962               gtk_text_buffer_place_cursor (buffer, &sel_start);
963             }
964         }
965       else if (mousepad_util_iter_inside_word (&sel_start))
966         {
967           /* reverse the characters before and after the cursor */
968           if (gtk_text_iter_backward_char (&sel_start) && gtk_text_iter_forward_char (&sel_end))
969             {
970               mousepad_view_transpose_range (buffer, &sel_start, &sel_end);
971 
972               /* place cursor in its original position */
973               gtk_text_iter_backward_char (&sel_end);
974               gtk_text_buffer_place_cursor (buffer, &sel_end);
975             }
976         }
977       else
978         {
979           /* swap the words left and right of the cursor */
980           mousepad_view_transpose_words (buffer, &sel_start);
981         }
982     }
983 
984   /* end user action */
985   gtk_text_buffer_end_user_action (buffer);
986 }
987 
988 
989 
990 void
mousepad_view_custom_paste(MousepadView * view,const gchar * string)991 mousepad_view_custom_paste (MousepadView *view,
992                             const gchar  *string)
993 {
994   GtkClipboard   *clipboard;
995   GtkTextBuffer  *buffer;
996   GtkTextMark    *mark;
997   GtkTextIter     iter, start_iter, end_iter;
998   gchar         **pieces;
999   gchar          *text = NULL;
1000   gint            i, column;
1001 
1002   /* leave when the view is not editable */
1003   if (! gtk_text_view_get_editable (GTK_TEXT_VIEW (view)))
1004     return;
1005 
1006   /* get the buffer */
1007   buffer = mousepad_view_get_buffer (view);
1008 
1009   /* begin user action */
1010   gtk_text_buffer_begin_user_action (buffer);
1011 
1012   if (string == NULL)
1013     {
1014       /* get the clipboard */
1015       clipboard = gtk_widget_get_clipboard (GTK_WIDGET (view), GDK_SELECTION_CLIPBOARD);
1016 
1017       /* get the clipboard text */
1018       text = gtk_clipboard_wait_for_text (clipboard);
1019 
1020       /* leave when the text is null */
1021       if (G_UNLIKELY (text == NULL))
1022         return;
1023 
1024       /* chop the string into pieces */
1025       pieces = g_strsplit (text, "\n", -1);
1026 
1027       /* get iter at cursor position */
1028       mark = gtk_text_buffer_get_insert (buffer);
1029       gtk_text_buffer_get_iter_at_mark (buffer, &iter, mark);
1030 
1031       /* get the iter column */
1032       column = mousepad_util_get_real_line_offset (&iter);
1033 
1034       /* insert the pieces in the buffer */
1035       for (i = 0; pieces[i] != NULL; i++)
1036         {
1037           /* insert the text in the buffer */
1038           gtk_text_buffer_insert (buffer, &iter, pieces[i], -1);
1039 
1040           /* break if the next piece is null */
1041           if (G_UNLIKELY (pieces[i+1] == NULL))
1042             break;
1043 
1044           /* move the iter to the next line */
1045           if (!gtk_text_iter_forward_line (&iter))
1046             {
1047               /* no new line, insert a new line */
1048               gtk_text_buffer_insert (buffer, &iter, "\n", 1);
1049             }
1050           else
1051             {
1052               /* set the iter at the column offset */
1053               mousepad_util_set_real_line_offset (&iter, column, FALSE);
1054             }
1055         }
1056 
1057       /* cleanup */
1058       g_free (text);
1059       g_strfreev (pieces);
1060 
1061       /* set the cursor to the last iter position */
1062       gtk_text_buffer_place_cursor (buffer, &iter);
1063     }
1064   else
1065     {
1066       /* get selection bounds */
1067       gtk_text_buffer_get_selection_bounds (buffer, &start_iter, &end_iter);
1068 
1069       /* remove the existing selection if the iters are not equal */
1070       if (!gtk_text_iter_equal (&start_iter, &end_iter))
1071         gtk_text_buffer_delete (buffer, &start_iter, &end_iter);
1072 
1073       /* insert string */
1074       gtk_text_buffer_insert (buffer, &start_iter, string, -1);
1075     }
1076 
1077   /* end user action */
1078   gtk_text_buffer_end_user_action (buffer);
1079 
1080   /* put cursor on screen */
1081   mousepad_view_scroll_to_cursor (view);
1082 }
1083 
1084 
1085 
1086 void
mousepad_view_convert_spaces_and_tabs(MousepadView * view,gint type)1087 mousepad_view_convert_spaces_and_tabs (MousepadView *view,
1088                                        gint          type)
1089 {
1090   GtkTextBuffer *buffer;
1091   GtkTextMark   *mark;
1092   GtkTextIter    start_iter, end_iter;
1093   GtkTextIter    iter;
1094   gint           tab_size;
1095   gboolean       in_range = FALSE;
1096   gboolean       no_forward;
1097   gunichar       c;
1098   gint           offset;
1099   gint           n_spaces = 0;
1100   gint           start_offset = -1;
1101   gchar         *string;
1102 
1103   g_return_if_fail (MOUSEPAD_IS_VIEW (view));
1104 
1105   /* get the buffer */
1106   buffer = mousepad_view_get_buffer (view);
1107 
1108   /* get the tab size */
1109   tab_size = gtk_source_view_get_tab_width (GTK_SOURCE_VIEW (view));
1110 
1111   /* get the start and end iter */
1112   if (gtk_text_buffer_get_selection_bounds (buffer, &start_iter, &end_iter))
1113     {
1114       /* move to the start of the line when replacing spaces */
1115       if (type == SPACES_TO_TABS && !gtk_text_iter_starts_line (&start_iter))
1116         gtk_text_iter_set_line_offset (&start_iter, 0);
1117 
1118       /* offset for restoring the selection afterwards */
1119       start_offset = gtk_text_iter_get_offset (&start_iter);
1120     }
1121   else
1122     {
1123       /* get the document bounds */
1124       gtk_text_buffer_get_bounds (buffer, &start_iter, &end_iter);
1125     }
1126 
1127   /* leave when the iters are equal (empty docs) */
1128   if (gtk_text_iter_equal (&start_iter, &end_iter))
1129     return;
1130 
1131   /* begin a user action and free notifications */
1132   g_object_freeze_notify (G_OBJECT (buffer));
1133   gtk_text_buffer_begin_user_action (buffer);
1134 
1135   /* create a mark to restore the end iter after modifieing the buffer */
1136   mark = gtk_text_buffer_create_mark (buffer, NULL, &end_iter, FALSE);
1137 
1138   /* walk the text between the iters */
1139   for (;;)
1140     {
1141       /* get the character */
1142       c = gtk_text_iter_get_char (&start_iter);
1143 
1144       /* reset */
1145       no_forward = FALSE;
1146 
1147       if (type == SPACES_TO_TABS)
1148         {
1149           if (c == ' ' || in_range)
1150             {
1151               if (! in_range)
1152                 {
1153                   /* set the start iter */
1154                   iter = start_iter;
1155 
1156                   /* get the real offset of the start iter */
1157                   offset = mousepad_util_get_real_line_offset (&iter);
1158 
1159                   /* the number of spaces to inline with the tabs */
1160                   n_spaces = tab_size - offset % tab_size;
1161                 }
1162 
1163               /* check if we can already insert a tab */
1164               if (n_spaces == 0)
1165                 {
1166                   /* delete the selected spaces */
1167                   gtk_text_buffer_delete (buffer, &iter, &start_iter);
1168                   gtk_text_buffer_insert (buffer, &start_iter, "\t", 1);
1169 
1170                   /* restore the end iter */
1171                   gtk_text_buffer_get_iter_at_mark (buffer, &end_iter, mark);
1172 
1173                   /* stop being inside a range */
1174                   in_range = FALSE;
1175 
1176                   /* no need to forward (iter moved after the insert */
1177                   no_forward = TRUE;
1178                 }
1179               else
1180                 {
1181                   /* check whether we're still in a range */
1182                   in_range = (c == ' ');
1183                 }
1184 
1185               /* decrease counter */
1186               n_spaces--;
1187             }
1188 
1189           /* go to next line if we reached text */
1190           if (!g_unichar_isspace (c))
1191             {
1192               /* continue to the next line if we hit non spaces */
1193               gtk_text_iter_forward_line (&start_iter);
1194 
1195               /* make sure there is no valid range anymore */
1196               in_range = FALSE;
1197 
1198               /* no need to forward */
1199               no_forward = TRUE;
1200             }
1201         }
1202       else if (type == TABS_TO_SPACES && c == '\t')
1203         {
1204           /* get the real offset of the iter */
1205           offset = mousepad_util_get_real_line_offset (&start_iter);
1206 
1207           /* the number of spaces to inline with the tabs */
1208           n_spaces = tab_size - offset % tab_size;
1209 
1210           /* move one character forwards */
1211           iter = start_iter;
1212           gtk_text_iter_forward_char (&start_iter);
1213 
1214           /* delete the tab */
1215           gtk_text_buffer_delete (buffer, &iter, &start_iter);
1216 
1217           /* create a string with the number of spaces */
1218           string = g_strnfill (n_spaces, ' ');
1219 
1220           /* insert the spaces */
1221           gtk_text_buffer_insert (buffer, &start_iter, string, n_spaces);
1222 
1223           /* cleanup */
1224           g_free (string);
1225 
1226           /* restore the end iter */
1227           gtk_text_buffer_get_iter_at_mark (buffer, &end_iter, mark);
1228 
1229           /* iter already moved by the insert */
1230           no_forward = TRUE;
1231         }
1232 
1233       /* break when the iters are equal (or start is bigger) */
1234       if (gtk_text_iter_compare (&start_iter, &end_iter) >= 0)
1235         break;
1236 
1237       /* forward the iter */
1238       if (G_LIKELY (! no_forward))
1239         gtk_text_iter_forward_char (&start_iter);
1240     }
1241 
1242   /* delete our mark */
1243   gtk_text_buffer_delete_mark (buffer, mark);
1244 
1245   /* restore the selection if needed */
1246   if (start_offset > -1)
1247     {
1248       /* restore iter and select range */
1249       gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start_offset);
1250       gtk_text_buffer_select_range (buffer, &end_iter, &start_iter);
1251     }
1252 
1253   /* end the user action */
1254   gtk_text_buffer_end_user_action (buffer);
1255   g_object_thaw_notify (G_OBJECT (buffer));
1256 }
1257 
1258 
1259 
1260 void
mousepad_view_strip_trailing_spaces(MousepadView * view)1261 mousepad_view_strip_trailing_spaces (MousepadView *view)
1262 {
1263   GtkTextBuffer *buffer;
1264   GtkTextIter    start_iter, end_iter, needle;
1265   gint           start, end, i;
1266   gunichar       c;
1267 
1268   g_return_if_fail (MOUSEPAD_IS_VIEW (view));
1269 
1270   /* get the buffer */
1271   buffer = mousepad_view_get_buffer (view);
1272 
1273   /* get range in line numbers */
1274   if (gtk_text_buffer_get_selection_bounds (buffer, &start_iter, &end_iter))
1275     {
1276       start = gtk_text_iter_get_line (&start_iter);
1277       end = gtk_text_iter_get_line (&end_iter) + 1;
1278     }
1279   else
1280     {
1281       start = 0;
1282       end = gtk_text_buffer_get_line_count (buffer);
1283     }
1284 
1285   /* begin a user action and free notifications */
1286   g_object_freeze_notify (G_OBJECT (buffer));
1287   gtk_text_buffer_begin_user_action (buffer);
1288 
1289   /* walk all the selected lines */
1290   for (i = start; i < end; i++)
1291     {
1292       /* get the iter at the line */
1293       gtk_text_buffer_get_iter_at_line (buffer, &end_iter, i);
1294 
1295       /* continue if the line is empty */
1296       if (gtk_text_iter_ends_line (&end_iter))
1297         continue;
1298 
1299       /* move the iter to the end of the line */
1300       gtk_text_iter_forward_to_line_end (&end_iter);
1301 
1302       /* initialize the iters */
1303       needle = start_iter = end_iter;
1304 
1305       /* walk backwards until we hit something else then a space or tab */
1306       while (gtk_text_iter_backward_char (&needle))
1307         {
1308           /* get the character */
1309           c = gtk_text_iter_get_char (&needle);
1310 
1311           /* set the start iter of the spaces range or stop searching */
1312           if (c == ' ' || c == '\t')
1313             start_iter = needle;
1314           else
1315             break;
1316         }
1317 
1318       /* remove the spaces/tabs if the iters are not equal */
1319       if (!gtk_text_iter_equal (&start_iter, &end_iter))
1320         gtk_text_buffer_delete (buffer, &start_iter, &end_iter);
1321     }
1322 
1323   /* end the user action */
1324   gtk_text_buffer_end_user_action (buffer);
1325   g_object_thaw_notify (G_OBJECT (buffer));
1326 }
1327 
1328 
1329 
1330 void
mousepad_view_duplicate(MousepadView * view)1331 mousepad_view_duplicate (MousepadView *view)
1332 {
1333   GtkTextBuffer *buffer;
1334   GtkTextIter    start_iter, end_iter;
1335   gboolean       has_selection;
1336   gboolean       insert_eol = FALSE;
1337 
1338   g_return_if_fail (MOUSEPAD_IS_VIEW (view));
1339 
1340   /* get the buffer */
1341   buffer = mousepad_view_get_buffer (view);
1342 
1343   /* begin a user action */
1344   gtk_text_buffer_begin_user_action (buffer);
1345 
1346   /* get iters */
1347   has_selection = gtk_text_buffer_get_selection_bounds (buffer, &start_iter, &end_iter);
1348 
1349   /* select entire line */
1350   if (! has_selection)
1351     {
1352       /* set to the start of the line */
1353       if (!gtk_text_iter_starts_line (&start_iter))
1354         gtk_text_iter_set_line_offset (&start_iter, 0);
1355 
1356       /* move the other iter to the start of the next line */
1357       insert_eol = !gtk_text_iter_forward_line (&end_iter);
1358     }
1359 
1360   /* insert a dupplicate of the text before the iter */
1361   gtk_text_buffer_insert_range (buffer, &start_iter, &start_iter, &end_iter);
1362 
1363   /* insert a new line if needed */
1364   if (insert_eol)
1365     gtk_text_buffer_insert (buffer, &start_iter, "\n", 1);
1366 
1367   /* end the action */
1368   gtk_text_buffer_end_user_action (buffer);
1369 }
1370 
1371 
1372 
1373 gint
mousepad_view_get_selection_length(MousepadView * view)1374 mousepad_view_get_selection_length (MousepadView *view)
1375 {
1376   GtkTextBuffer *buffer;
1377   GtkTextIter    start, end;
1378   gint           length = 0;
1379 
1380   g_return_val_if_fail (MOUSEPAD_IS_VIEW (view), FALSE);
1381 
1382   /* get the text buffer */
1383   buffer = mousepad_view_get_buffer (view);
1384 
1385   /* calculate the selection length from the iter offset (absolute) */
1386   if (gtk_text_buffer_get_selection_bounds (buffer, &start, &end))
1387     length = ABS (gtk_text_iter_get_offset (&end) - gtk_text_iter_get_offset (&start));
1388 
1389   return length;
1390 }
1391 
1392 
1393 
1394 static void
mousepad_view_set_font(MousepadView * view,const gchar * font)1395 mousepad_view_set_font (MousepadView *view,
1396                         const gchar  *font)
1397 {
1398   PangoFontDescription *font_desc;
1399   GtkCssProvider       *provider;
1400   gchar                *css_font_desc, *css_string;
1401 
1402   g_return_if_fail (MOUSEPAD_IS_VIEW (view));
1403 
1404   /* from font string to css string through pango description */
1405   font_desc = pango_font_description_from_string (font);
1406   css_font_desc = mousepad_util_pango_font_description_to_css (font_desc);
1407   css_string = g_strdup_printf ("textview { %s }", css_font_desc);
1408 
1409   /* set font */
1410   provider = gtk_css_provider_new ();
1411   gtk_css_provider_load_from_data (provider, css_string, -1, NULL);
1412   gtk_style_context_add_provider (gtk_widget_get_style_context (GTK_WIDGET (view)),
1413                                   GTK_STYLE_PROVIDER (provider),
1414                                   GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
1415 
1416   /* cleanup */
1417   g_object_unref (provider);
1418   pango_font_description_free (font_desc);
1419   g_free (css_font_desc);
1420   g_free (css_string);
1421 }
1422 
1423 
1424 
1425 static void
mousepad_view_update_draw_spaces(MousepadView * view)1426 mousepad_view_update_draw_spaces (MousepadView *view)
1427 {
1428   GtkSourceSpaceTypeFlags      type_flags = GTK_SOURCE_SPACE_TYPE_NONE;
1429   GtkSourceSpaceLocationFlags  flag;
1430   GtkSourceSpaceDrawer        *drawer;
1431   gboolean                     enable_matrix = FALSE;
1432 
1433   drawer = gtk_source_view_get_space_drawer (GTK_SOURCE_VIEW (view));
1434 
1435   if (view->show_whitespace)
1436     {
1437       type_flags |= GTK_SOURCE_SPACE_TYPE_SPACE | GTK_SOURCE_SPACE_TYPE_TAB
1438                     | GTK_SOURCE_SPACE_TYPE_NBSP;
1439       enable_matrix = TRUE;
1440 
1441       for (flag = 1; flag < GTK_SOURCE_SPACE_LOCATION_ALL; flag <<= 1)
1442         if (view->space_location_flags & flag)
1443           gtk_source_space_drawer_set_types_for_locations (drawer, flag, type_flags);
1444         else
1445           gtk_source_space_drawer_set_types_for_locations (drawer, flag, GTK_SOURCE_SPACE_TYPE_NONE);
1446     }
1447   else
1448     gtk_source_space_drawer_set_types_for_locations (drawer, GTK_SOURCE_SPACE_LOCATION_ALL,
1449                                                      GTK_SOURCE_SPACE_TYPE_NONE);
1450 
1451   if (view->show_line_endings)
1452     {
1453       enable_matrix = TRUE;
1454       if (view->space_location_flags & GTK_SOURCE_SPACE_LOCATION_TRAILING)
1455         type_flags |= GTK_SOURCE_SPACE_TYPE_NEWLINE;
1456       else
1457         type_flags = GTK_SOURCE_SPACE_TYPE_NEWLINE;
1458 
1459       gtk_source_space_drawer_set_types_for_locations (drawer, GTK_SOURCE_SPACE_LOCATION_TRAILING,
1460                                                        type_flags);
1461     }
1462 
1463   gtk_source_space_drawer_set_enable_matrix (drawer, enable_matrix);
1464 }
1465 
1466 
1467 
1468 static void
mousepad_view_set_show_whitespace(MousepadView * view,gboolean show)1469 mousepad_view_set_show_whitespace (MousepadView *view,
1470                                    gboolean      show)
1471 {
1472   g_return_if_fail (MOUSEPAD_IS_VIEW (view));
1473 
1474   view->show_whitespace = show;
1475   mousepad_view_update_draw_spaces (view);
1476 }
1477 
1478 
1479 
1480 static void
mousepad_view_set_space_location_flags(MousepadView * view,gulong flags)1481 mousepad_view_set_space_location_flags (MousepadView *view,
1482                                         gulong        flags)
1483 {
1484   g_return_if_fail (MOUSEPAD_IS_VIEW (view));
1485 
1486   view->space_location_flags = flags;
1487   mousepad_view_update_draw_spaces (view);
1488 }
1489 
1490 
1491 
1492 static void
mousepad_view_set_show_line_endings(MousepadView * view,gboolean show)1493 mousepad_view_set_show_line_endings (MousepadView *view,
1494                                      gboolean      show)
1495 {
1496   g_return_if_fail (MOUSEPAD_IS_VIEW (view));
1497 
1498   view->show_line_endings = show;
1499   mousepad_view_update_draw_spaces (view);
1500 }
1501 
1502 
1503 
1504 static void
mousepad_view_set_color_scheme(MousepadView * view,const gchar * color_scheme)1505 mousepad_view_set_color_scheme (MousepadView *view,
1506                                 const gchar  *color_scheme)
1507 {
1508   g_return_if_fail (MOUSEPAD_IS_VIEW (view));
1509 
1510   if (g_strcmp0 (color_scheme, view->color_scheme) != 0)
1511     {
1512       g_free (view->color_scheme);
1513       view->color_scheme = g_strdup (color_scheme);
1514 
1515       /* update the buffer if there is one */
1516       mousepad_view_buffer_changed (view, NULL, NULL);
1517     }
1518 }
1519 
1520 
1521 
1522 static void
mousepad_view_set_word_wrap(MousepadView * view,gboolean enabled)1523 mousepad_view_set_word_wrap (MousepadView *view,
1524                              gboolean      enabled)
1525 {
1526   g_return_if_fail (MOUSEPAD_IS_VIEW (view));
1527 
1528   gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (view),
1529                                enabled ? GTK_WRAP_WORD_CHAR : GTK_WRAP_NONE);
1530 }
1531 
1532 
1533 
1534 static void
mousepad_view_set_match_braces(MousepadView * view,gboolean enabled)1535 mousepad_view_set_match_braces (MousepadView *view,
1536                                 gboolean      enabled)
1537 {
1538   g_return_if_fail (MOUSEPAD_IS_VIEW (view));
1539 
1540   view->match_braces = enabled;
1541 
1542   mousepad_view_buffer_changed (view, NULL, NULL);
1543 }
1544