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