gedit-view.c 28.2 KB
Newer Older
1 2 3 4 5
/*
 * gedit-view.c
 * This file is part of gedit
 *
 * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
6
 * Copyright (C) 2000, 2002 Chema Celorio, Paolo Maggi 
Paolo Borelli's avatar
Paolo Borelli committed
7
 * Copyright (C) 2003-2005 Paolo Maggi  
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, 
 * Boston, MA 02111-1307, USA. 
 */
 
/*
Paolo Borelli's avatar
Paolo Borelli committed
26
 * Modified by the gedit Team, 1998-2005. See the AUTHORS file for a 
27
 * list of people on the gedit Team.  
Paolo Borelli's avatar
Paolo Borelli committed
28 29 30
 * See the ChangeLog files for a list of changes.
 *
 * $Id$ 
31 32
 */

33 34 35 36
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

Paolo Maggi's avatar
Paolo Maggi committed
37
#include <string.h>
Paolo Borelli's avatar
Paolo Borelli committed
38
#include <stdlib.h>
Paolo Maggi's avatar
Paolo Maggi committed
39

40
#include <gdk/gdkkeysyms.h>
41
#include <libpeas/peas-extension-set.h>
Paolo Borelli's avatar
Paolo Borelli committed
42 43

#include <glib/gi18n.h>
Paolo Maggi's avatar
Paolo Maggi committed
44

45
#include "gedit-view.h"
46 47
#include "gedit-view-activatable.h"
#include "gedit-plugins-engine.h"
48
#include "gedit-debug.h"
Paolo Maggi's avatar
Paolo Maggi committed
49
#include "gedit-marshal.h"
Paolo Borelli's avatar
Paolo Borelli committed
50
#include "gedit-utils.h"
51 52
#include "gedit-settings.h"
#include "gedit-app.h"
53
#include "gedit-notebook.h"
Paolo Maggi's avatar
Paolo Maggi committed
54

Paolo Maggi's avatar
Paolo Maggi committed
55
#define GEDIT_VIEW_SCROLL_MARGIN 0.02
Paolo Borelli's avatar
Paolo Borelli committed
56 57 58 59

#define GEDIT_VIEW_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), GEDIT_TYPE_VIEW, GeditViewPrivate))

typedef enum
60
{
Paolo Borelli's avatar
Paolo Borelli committed
61 62 63
	GOTO_LINE,
	SEARCH
} SearchMode;
64

65 66
enum
{
67 68
	TARGET_URI_LIST = 100,
	TARGET_TAB
69 70
};

Paolo Borelli's avatar
Paolo Borelli committed
71 72
struct _GeditViewPrivate
{
73
	GSettings *editor_settings;
74
	GtkTextBuffer *current_buffer;
75
	PeasExtensionSet *extensions;
Paolo Maggi's avatar
Paolo Maggi committed
76 77
};

78
G_DEFINE_TYPE(GeditView, gedit_view, GTK_SOURCE_TYPE_VIEW)
79

Paolo Borelli's avatar
Paolo Borelli committed
80 81
/* Signals */
enum
Paolo Maggi's avatar
Paolo Maggi committed
82
{
83
	DROP_URIS,
Paolo Borelli's avatar
Paolo Borelli committed
84 85
	LAST_SIGNAL
};
Paolo Maggi's avatar
Paolo Maggi committed
86

Paolo Borelli's avatar
Paolo Borelli committed
87
static guint view_signals [LAST_SIGNAL] = { 0 };
Paolo Maggi's avatar
Paolo Maggi committed
88

Paolo Maggi's avatar
Paolo Maggi committed
89
static void
Paolo Borelli's avatar
Paolo Borelli committed
90 91 92
document_read_only_notify_handler (GeditDocument *document, 
			           GParamSpec    *pspec,
				   GeditView     *view)
Paolo Maggi's avatar
Paolo Maggi committed
93
{
Paolo Borelli's avatar
Paolo Borelli committed
94
	gedit_debug (DEBUG_VIEW);
Paolo Maggi's avatar
Paolo Maggi committed
95

Paolo Borelli's avatar
Paolo Borelli committed
96 97
	gtk_text_view_set_editable (GTK_TEXT_VIEW (view), 
				    !gedit_document_get_readonly (document));
Paolo Maggi's avatar
Paolo Maggi committed
98 99
}

100
static void
101 102 103 104
search_highlight_updated_cb (GeditDocument *doc,
                             GtkTextIter   *start,
                             GtkTextIter   *end,
                             GeditView     *view)
105
{
106 107 108 109 110 111 112 113
	GdkRectangle visible_rect;
	GdkRectangle updated_rect;
	GdkRectangle redraw_rect;
	gint y;
	gint height;
	GtkTextView *text_view;
	
	text_view = GTK_TEXT_VIEW (view);
114

115 116
	g_return_if_fail (gedit_document_get_enable_search_highlighting (
				GEDIT_DOCUMENT (gtk_text_view_get_buffer (text_view))));
Paolo Borelli's avatar
Paolo Borelli committed
117

118 119
	/* get visible area */
	gtk_text_view_get_visible_rect (text_view, &visible_rect);
120

121 122 123 124 125 126 127
	/* get updated rectangle */
	gtk_text_view_get_line_yrange (text_view, start, &y, &height);
	updated_rect.y = y;
	gtk_text_view_get_line_yrange (text_view, end, &y, &height);
	updated_rect.height = y + height - updated_rect.y;
	updated_rect.x = visible_rect.x;
	updated_rect.width = visible_rect.width;
Paolo Borelli's avatar
Paolo Borelli committed
128

129 130 131 132
	/* intersect both rectangles to see whether we need to queue a redraw */
	if (gdk_rectangle_intersect (&updated_rect, &visible_rect, &redraw_rect))
	{
		GdkRectangle widget_rect;
133

134 135 136 137 138 139
		gtk_text_view_buffer_to_window_coords (text_view,
		                                       GTK_TEXT_WINDOW_WIDGET,
		                                       redraw_rect.x,
		                                       redraw_rect.y,
		                                       &widget_rect.x,
		                                       &widget_rect.y);
140

141 142
		widget_rect.width = redraw_rect.width;
		widget_rect.height = redraw_rect.height;
143

144 145 146 147 148 149
		gtk_widget_queue_draw_area (GTK_WIDGET (text_view),
		                            widget_rect.x,
		                            widget_rect.y,
		                            widget_rect.width,
		                            widget_rect.height);
	}
150 151
}

152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
static void
current_buffer_removed (GeditView *view)
{
	if (view->priv->current_buffer)
	{
		g_signal_handlers_disconnect_by_func (view->priv->current_buffer,
						      document_read_only_notify_handler,
						      view);
		g_signal_handlers_disconnect_by_func (view->priv->current_buffer,
						      search_highlight_updated_cb,
						      view);
				     
		g_object_unref (view->priv->current_buffer);
		view->priv->current_buffer = NULL;
	}
}

static void
on_notify_buffer_cb (GeditView  *view,
		     GParamSpec *arg1,
		     gpointer    userdata)
{
	GtkTextBuffer *buffer;
	
	current_buffer_removed (view);
	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
	
	if (buffer == NULL || !GEDIT_IS_DOCUMENT (buffer))
		return;

	view->priv->current_buffer = g_object_ref (buffer);
	g_signal_connect (buffer,
			  "notify::read-only",
			  G_CALLBACK (document_read_only_notify_handler),
			  view);

	gtk_text_view_set_editable (GTK_TEXT_VIEW (view), 
				    !gedit_document_get_readonly (GEDIT_DOCUMENT (buffer)));

	g_signal_connect (buffer,
			  "search_highlight_updated",
			  G_CALLBACK (search_highlight_updated_cb),
			  view);
}

Paolo Borelli's avatar
Paolo Borelli committed
197 198
static void 
gedit_view_init (GeditView *view)
199 200
{
	GtkTargetList *tl;
201

Paolo Borelli's avatar
Paolo Borelli committed
202
	gedit_debug (DEBUG_VIEW);
203

Paolo Borelli's avatar
Paolo Borelli committed
204
	view->priv = GEDIT_VIEW_GET_PRIVATE (view);
205

206 207
	view->priv->editor_settings = g_settings_new ("org.gnome.gedit.preferences.editor");

208
	/* Drag and drop support */
209 210 211
	tl = gtk_drag_dest_get_target_list (GTK_WIDGET (view));

	if (tl != NULL)
212
	{
213
		gtk_target_list_add_uri_targets (tl, TARGET_URI_LIST);
214 215 216 217 218 219
		gtk_target_list_add (tl,
		                     gdk_atom_intern_static_string ("GTK_NOTEBOOK_TAB"),
		                     GTK_TARGET_SAME_APP,
		                     TARGET_TAB);
	}

220 221 222 223 224
	view->priv->extensions =
		peas_extension_set_new (PEAS_ENGINE (gedit_plugins_engine_get_default ()),
		                        GEDIT_TYPE_VIEW_ACTIVATABLE,
		                        "view", view,
		                        NULL);
225

226
	/* Act on buffer change */
227 228
	g_signal_connect (view,
			  "notify::buffer",
229 230
			  G_CALLBACK (on_notify_buffer_cb),
			  NULL);
231
}
232

Paolo Borelli's avatar
Paolo Borelli committed
233
static void
234
gedit_view_destroy (GtkWidget *widget)
235
{
236 237 238 239 240 241 242
	GeditView *view = GEDIT_VIEW (widget);

	/* Disconnect notify buffer because the destroy of the textview will
	   set the buffer to NULL, and we call get_buffer in the notify which
	   would reinstate a GtkTextBuffer which we don't want */
	current_buffer_removed (view);
	g_signal_handlers_disconnect_by_func (view, on_notify_buffer_cb, NULL);
243

244 245 246 247 248 249 250
	GTK_WIDGET_CLASS (gedit_view_parent_class)->destroy (widget);
}

static void
gedit_view_dispose (GObject *object)
{
	GeditView *view = GEDIT_VIEW (object);
251

252 253
	g_clear_object (&view->priv->extensions);
	g_clear_object (&view->priv->editor_settings);
254

255
	G_OBJECT_CLASS (gedit_view_parent_class)->dispose (object);
256 257
}

Paolo Borelli's avatar
Paolo Borelli committed
258 259
static void
gedit_view_finalize (GObject *object)
Paolo Maggi's avatar
Paolo Maggi committed
260
{
261
	GeditView *view = GEDIT_VIEW (object);
Paolo Maggi's avatar
Paolo Maggi committed
262

263 264
	current_buffer_removed (view);

265
	G_OBJECT_CLASS (gedit_view_parent_class)->finalize (object);
Paolo Maggi's avatar
Paolo Maggi committed
266 267
}

268 269
static void
gedit_view_constructed (GObject *object)
270
{
271 272 273 274 275 276 277 278 279 280 281
	GeditView *view;
	gboolean use_default_font;
	gboolean display_line_numbers;
	gboolean auto_indent;
	gboolean insert_spaces;
	gboolean display_right_margin;
	gboolean hl_current_line;
	guint tabs_size;
	guint right_margin_position;
	GtkWrapMode wrap_mode;
	GtkSourceSmartHomeEndType smart_home_end;
282

283
	view = GEDIT_VIEW (object);
284

285 286 287
	/* Get setting values */
	use_default_font = g_settings_get_boolean (view->priv->editor_settings,
	                                           GEDIT_SETTINGS_USE_DEFAULT_FONT);
288

289 290 291 292 293 294 295
	/*
	 *  Set tab, fonts, wrap mode, colors, etc. according
	 *  to preferences 
	 */
	if (!use_default_font)
	{
		gchar *editor_font;
296

297 298
		editor_font = g_settings_get_string (view->priv->editor_settings,
		                                     GEDIT_SETTINGS_EDITOR_FONT);
299

300
		gedit_view_set_font (view, FALSE, editor_font);
Paolo Maggi's avatar
Paolo Maggi committed
301

302 303 304 305 306 307
		g_free (editor_font);
	}
	else
	{
		gedit_view_set_font (view, TRUE, NULL);
	}
308

309 310 311 312 313 314 315 316 317 318 319 320 321 322
	display_line_numbers = g_settings_get_boolean (view->priv->editor_settings,
	                                               GEDIT_SETTINGS_DISPLAY_LINE_NUMBERS);
	auto_indent = g_settings_get_boolean (view->priv->editor_settings,
	                                      GEDIT_SETTINGS_AUTO_INDENT);
	g_settings_get (view->priv->editor_settings, GEDIT_SETTINGS_TABS_SIZE,
	                "u", &tabs_size);
	insert_spaces = g_settings_get_boolean (view->priv->editor_settings,
	                                        GEDIT_SETTINGS_INSERT_SPACES);
	display_right_margin = g_settings_get_boolean (view->priv->editor_settings,
	                                               GEDIT_SETTINGS_DISPLAY_RIGHT_MARGIN);
	g_settings_get (view->priv->editor_settings, GEDIT_SETTINGS_RIGHT_MARGIN_POSITION,
	                "u", &right_margin_position);
	hl_current_line = g_settings_get_boolean (view->priv->editor_settings,
	                                          GEDIT_SETTINGS_HIGHLIGHT_CURRENT_LINE);
323

324 325
	wrap_mode = g_settings_get_enum (view->priv->editor_settings,
	                                 GEDIT_SETTINGS_WRAP_MODE);
Paolo Maggi's avatar
Paolo Maggi committed
326

327 328 329 330 331 332 333 334 335 336 337 338 339 340 341
	smart_home_end = g_settings_get_enum (view->priv->editor_settings,
	                                      GEDIT_SETTINGS_SMART_HOME_END);

	g_object_set (G_OBJECT (view), 
	              "wrap_mode", wrap_mode,
	              "show_line_numbers", display_line_numbers,
	              "auto_indent", auto_indent,
	              "tab_width", tabs_size,
	              "insert_spaces_instead_of_tabs", insert_spaces,
	              "show_right_margin", display_right_margin,
	              "right_margin_position", right_margin_position,
	              "highlight_current_line", hl_current_line,
	              "smart_home_end", smart_home_end,
	              "indent_on_tab", TRUE,
	              NULL);
342

343
	G_OBJECT_CLASS (gedit_view_parent_class)->constructed (object);
344 345
}

346 347
static gint
gedit_view_focus_out (GtkWidget *widget, GdkEventFocus *event)
348
{
349
	gtk_widget_queue_draw (widget);
350

351
	GTK_WIDGET_CLASS (gedit_view_parent_class)->focus_out_event (widget, event);
Paolo Borelli's avatar
Paolo Borelli committed
352

353 354
	return FALSE;
}
355

356 357 358
static gboolean
gedit_view_draw (GtkWidget *widget,
                 cairo_t   *cr)
359
{
360 361 362
	GtkTextView *text_view;
	GeditDocument *doc;
	GdkWindow *window;
363

364
	text_view = GTK_TEXT_VIEW (widget);
365

366
	doc = GEDIT_DOCUMENT (gtk_text_view_get_buffer (text_view));
367
	window = gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_TEXT);
368

369
	if (gtk_cairo_should_draw_window (cr, window) &&
370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387
	    gedit_document_get_enable_search_highlighting (doc))
	{
		GdkRectangle visible_rect;
		GtkTextIter iter1, iter2;
		
		gtk_text_view_get_visible_rect (text_view, &visible_rect);
		gtk_text_view_get_line_at_y (text_view, &iter1,
					     visible_rect.y, NULL);
		gtk_text_view_get_line_at_y (text_view, &iter2,
					     visible_rect.y
					     + visible_rect.height, NULL);
		gtk_text_iter_forward_line (&iter2);
				     
		_gedit_document_search_region (doc,
					       &iter1,
					       &iter2);
	}

388
	return GTK_WIDGET_CLASS (gedit_view_parent_class)->draw (widget, cr);
389 390
}

391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411
static GdkAtom
drag_get_uri_target (GtkWidget      *widget,
		     GdkDragContext *context)
{
	GdkAtom target;
	GtkTargetList *tl;
	
	tl = gtk_target_list_new (NULL, 0);
	gtk_target_list_add_uri_targets (tl, 0);
	
	target = gtk_drag_dest_find_target (widget, context, tl);
	gtk_target_list_unref (tl);
	
	return target;	
}

static gboolean
gedit_view_drag_motion (GtkWidget      *widget,
			GdkDragContext *context,
			gint            x,
			gint            y,
Paolo Borelli's avatar
Paolo Borelli committed
412
			guint           timestamp)
413 414 415
{
	gboolean result;

416 417 418
	/* Chain up to allow textview to scroll and position dnd mark, note 
	 * that this needs to be checked if gtksourceview or gtktextview
	 * changes drag_motion behaviour */
Paolo Borelli's avatar
Paolo Borelli committed
419
	result = GTK_WIDGET_CLASS (gedit_view_parent_class)->drag_motion (widget, context, x, y, timestamp);
420 421

	/* If this is a URL, deal with it here */
422 423
	if (drag_get_uri_target (widget, context) != GDK_NONE) 
	{
424 425 426
		gdk_drag_status (context,
				 gdk_drag_context_get_suggested_action (context),
				 timestamp);
427 428 429 430 431 432
		result = TRUE;
	}

	return result;
}

433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448
static GtkWidget *
get_notebook_from_view (GtkWidget *view)
{
	GtkWidget *widget;

	widget = view;

	do
	{
		widget = gtk_widget_get_parent (widget);
	}
	while (!GEDIT_IS_NOTEBOOK (widget));

	return widget;
}

449 450 451 452 453 454 455
static void
gedit_view_drag_data_received (GtkWidget        *widget,
		       	       GdkDragContext   *context,
			       gint              x,
			       gint              y,
			       GtkSelectionData *selection_data,
			       guint             info,
Paolo Borelli's avatar
Paolo Borelli committed
456
			       guint             timestamp)
457 458 459 460
{
	/* If this is an URL emit DROP_URIS, otherwise chain up the signal */
	if (info == TARGET_URI_LIST)
	{
461 462
		gchar **uri_list;

463 464 465 466 467 468
		uri_list = gedit_utils_drop_get_uris (selection_data);
		
		if (uri_list != NULL)
		{
			g_signal_emit (widget, view_signals[DROP_URIS], 0, uri_list);
			g_strfreev (uri_list);
469
			
Paolo Borelli's avatar
Paolo Borelli committed
470
			gtk_drag_finish (context, TRUE, FALSE, timestamp);
471 472
		}
	}
473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492
	else if (info == TARGET_TAB)
	{
		GtkWidget *notebook;
		GtkWidget *new_notebook;
		GtkWidget *page;

		notebook = gtk_drag_get_source_widget (context);

		if (!GTK_IS_WIDGET (notebook))
		{
			return;
		}

		page = *(GtkWidget **) gtk_selection_data_get_data (selection_data);
		g_return_if_fail (page != NULL);

		/* We need to iterate and get the notebook of the target view
		   because we can have several notebooks per window */
		new_notebook = get_notebook_from_view (widget);

493 494 495 496 497 498 499
		if (notebook != new_notebook)
		{
			gedit_notebook_move_tab (GEDIT_NOTEBOOK (notebook),
				                 GEDIT_NOTEBOOK (new_notebook),
				                 GEDIT_TAB (page),
				                 0);
		}
500 501 502

		gtk_drag_finish (context, TRUE, TRUE, timestamp);
	}
503 504
	else
	{
Garrett Regier's avatar
Garrett Regier committed
505 506 507 508 509 510
		GTK_WIDGET_CLASS (gedit_view_parent_class)->drag_data_received (widget,
										context,
										x, y,
										selection_data,
										info,
										timestamp);
511 512 513 514 515 516 517 518
	}
}

static gboolean
gedit_view_drag_drop (GtkWidget      *widget,
		      GdkDragContext *context,
		      gint            x,
		      gint            y,
Paolo Borelli's avatar
Paolo Borelli committed
519
		      guint           timestamp)
520 521 522 523 524 525 526 527 528
{
	gboolean result;
	GdkAtom target;

	/* If this is a URL, just get the drag data */
	target = drag_get_uri_target (widget, context);

	if (target != GDK_NONE)
	{
Paolo Borelli's avatar
Paolo Borelli committed
529
		gtk_drag_get_data (widget, context, target, timestamp);
530 531 532 533 534
		result = TRUE;
	}
	else
	{
		/* Chain up */
Garrett Regier's avatar
Garrett Regier committed
535 536 537 538
		result = GTK_WIDGET_CLASS (gedit_view_parent_class)->drag_drop (widget,
										context,
										x, y,
										timestamp);
539 540 541 542 543
	}

	return result;
}

544 545 546 547 548 549 550 551 552 553 554
static GtkWidget *
create_line_numbers_menu (GtkWidget *view)
{
	GtkWidget *menu;
	GtkWidget *item;

	menu = gtk_menu_new ();

	item = gtk_check_menu_item_new_with_mnemonic (_("_Display line numbers"));
	gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item),
					gtk_source_view_get_show_line_numbers (GTK_SOURCE_VIEW (view)));
555 556 557

	g_settings_bind (GEDIT_VIEW (view)->priv->editor_settings,
			 GEDIT_SETTINGS_DISPLAY_LINE_NUMBERS,
558 559
			 item,
			 "active",
560 561
			 G_SETTINGS_BIND_SET);
	
562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601
	gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
	
	gtk_widget_show_all (menu);
	
	return menu;
}

static void
show_line_numbers_menu (GtkWidget      *view,
			GdkEventButton *event)
{
	GtkWidget *menu;

	menu = create_line_numbers_menu (view);

	gtk_menu_popup (GTK_MENU (menu), 
			NULL, 
			NULL,
			NULL, 
			NULL,
			event->button, 
			event->time);
}

static gboolean
gedit_view_button_press_event (GtkWidget *widget, GdkEventButton *event)
{
	if ((event->type == GDK_BUTTON_PRESS) && 
	    (event->button == 3) &&
	    (event->window == gtk_text_view_get_window (GTK_TEXT_VIEW (widget),
						        GTK_TEXT_WINDOW_LEFT)))
	{
		show_line_numbers_menu (widget, event);

		return TRUE;
	}

	return GTK_WIDGET_CLASS (gedit_view_parent_class)->button_press_event (widget, event);
}

602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619
static void
extension_added (PeasExtensionSet *extensions,
		 PeasPluginInfo   *info,
		 PeasExtension    *exten,
		 GeditView        *view)
{
	gedit_view_activatable_activate (GEDIT_VIEW_ACTIVATABLE (exten));
}

static void
extension_removed (PeasExtensionSet *extensions,
		   PeasPluginInfo   *info,
		   PeasExtension    *exten,
		   GeditView        *view)
{
	gedit_view_activatable_deactivate (GEDIT_VIEW_ACTIVATABLE (exten));
}

620 621 622 623 624
static void
gedit_view_realize (GtkWidget *widget)
{
	GeditView *view = GEDIT_VIEW (widget);

625
	GTK_WIDGET_CLASS (gedit_view_parent_class)->realize (widget);
626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659

	g_signal_connect (view->priv->extensions,
	                  "extension-added",
	                  G_CALLBACK (extension_added),
	                  view);
	g_signal_connect (view->priv->extensions,
	                  "extension-removed",
	                  G_CALLBACK (extension_removed),
	                  view);

	/* We only activate the extensions when the view is realized,
	 * because most plugins will expect this behaviour, and we won't
	 * change the buffer later anyway. */
	peas_extension_set_foreach (view->priv->extensions,
	                            (PeasExtensionSetForeachFunc) extension_added,
	                            view);
}

static void
gedit_view_unrealize (GtkWidget *widget)
{
	GeditView *view = GEDIT_VIEW (widget);

	g_signal_handlers_disconnect_by_func (view->priv->extensions, extension_added, view);
	g_signal_handlers_disconnect_by_func (view->priv->extensions, extension_removed, view);

	/* We need to deactivate the extension on unrealize because it is not
	   mandatory that a view has been realized when we dispose it, leading
	   to deactivating the plugin without being activated */
	peas_extension_set_foreach (view->priv->extensions,
	                            (PeasExtensionSetForeachFunc) extension_removed,
	                            view);

	GTK_WIDGET_CLASS (gedit_view_parent_class)->unrealize (widget);
660 661
}

662 663 664 665 666 667 668 669 670 671
static void
delete_line (GtkTextView *text_view,
	     gint         count)
{
	GtkTextIter start;
	GtkTextIter end;
	GtkTextBuffer *buffer;

	buffer = gtk_text_view_get_buffer (text_view);

672
	gtk_text_view_reset_im_context (text_view);
673

674 675 676 677 678 679 680 681 682 683 684 685
	/* If there is a selection delete the selected lines and
	 * ignore count */
	if (gtk_text_buffer_get_selection_bounds (buffer, &start, &end))
	{
		gtk_text_iter_order (&start, &end);
		
		if (gtk_text_iter_starts_line (&end))
		{
			/* Do no delete the line with the cursor if the cursor
			 * is at the beginning of the line */
			count = 0;
		}
Garrett Regier's avatar
Garrett Regier committed
686 687
		else
		{
688
			count = 1;
Garrett Regier's avatar
Garrett Regier committed
689
		}
690 691
	}
	
692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707
	gtk_text_iter_set_line_offset (&start, 0);

	if (count > 0)
	{		
		gtk_text_iter_forward_lines (&end, count);

		if (gtk_text_iter_is_end (&end))
		{		
			if (gtk_text_iter_backward_line (&start) && !gtk_text_iter_ends_line (&start))
				gtk_text_iter_forward_to_line_end (&start);
		}
	}
	else if (count < 0) 
	{
		if (!gtk_text_iter_ends_line (&end))
			gtk_text_iter_forward_to_line_end (&end);
708

709 710 711 712 713 714 715
		while (count < 0) 
		{
			if (!gtk_text_iter_backward_line (&start))
				break;
				
			++count;
		}
716

717 718 719 720 721 722
		if (count == 0)
		{
			if (!gtk_text_iter_ends_line (&start))
				gtk_text_iter_forward_to_line_end (&start);
		}
		else
Garrett Regier's avatar
Garrett Regier committed
723
		{
724
			gtk_text_iter_forward_line (&end);
Garrett Regier's avatar
Garrett Regier committed
725
		}
726 727 728 729 730 731 732 733 734 735
	}

	if (!gtk_text_iter_equal (&start, &end))
	{
		GtkTextIter cur = start;
		gtk_text_iter_set_line_offset (&cur, 0);
		
		gtk_text_buffer_begin_user_action (buffer);

		gtk_text_buffer_place_cursor (buffer, &cur);
736

737 738 739 740 741 742
		gtk_text_buffer_delete_interactive (buffer, 
						    &start,
						    &end,
						    gtk_text_view_get_editable (text_view));

		gtk_text_buffer_end_user_action (buffer);
743

744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771
		gtk_text_view_scroll_mark_onscreen (text_view,
						    gtk_text_buffer_get_insert (buffer));
	}
	else
	{
		gtk_widget_error_bell (GTK_WIDGET (text_view));
	}
}

static void
gedit_view_delete_from_cursor (GtkTextView   *text_view,
			       GtkDeleteType  type,
			       gint           count)
{
	/* We override the standard handler for delete_from_cursor since
	   the GTK_DELETE_PARAGRAPHS case is not implemented as we like (i.e. it
	   does not remove the carriage return in the previous line)
	 */
	switch (type)
	{
		case GTK_DELETE_PARAGRAPHS:
			delete_line (text_view, count);
			break;
		default:
			GTK_TEXT_VIEW_CLASS (gedit_view_parent_class)->delete_from_cursor(text_view, type, count);
			break;
	}
}
Garrett Regier's avatar
Garrett Regier committed
772

773 774 775 776 777 778 779 780
static void
gedit_view_class_init (GeditViewClass *klass)
{
	GObjectClass     *object_class = G_OBJECT_CLASS (klass);
	GtkWidgetClass   *widget_class = GTK_WIDGET_CLASS (klass);
	GtkTextViewClass *text_view_class = GTK_TEXT_VIEW_CLASS (klass);
	GtkBindingSet    *binding_set;

781
	object_class->dispose = gedit_view_dispose;
782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807
	object_class->finalize = gedit_view_finalize;
	object_class->constructed = gedit_view_constructed;

	widget_class->destroy = gedit_view_destroy;
	widget_class->focus_out_event = gedit_view_focus_out;
	widget_class->draw = gedit_view_draw;

	/*
	 * Override the gtk_text_view_drag_motion and drag_drop
	 * functions to get URIs
	 *
	 * If the mime type is text/uri-list, then we will accept
	 * the potential drop, or request the data (depending on the
	 * function).
	 *
	 * If the drag context has any other mime type, then pass the
	 * information onto the GtkTextView's standard handlers.
	 * (widget_class->function_name).
	 *
	 * See bug #89881 for details
	 */
	widget_class->drag_motion = gedit_view_drag_motion;
	widget_class->drag_data_received = gedit_view_drag_data_received;
	widget_class->drag_drop = gedit_view_drag_drop;
	widget_class->button_press_event = gedit_view_button_press_event;
	widget_class->realize = gedit_view_realize;
808
	widget_class->unrealize = gedit_view_unrealize;
809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855

	text_view_class->delete_from_cursor = gedit_view_delete_from_cursor;

	/* A new signal DROP_URIS has been added to allow plugins to intercept
	 * the default dnd behaviour of 'text/uri-list'. GeditView now handles
	 * dnd in the default handlers of drag_drop, drag_motion and 
	 * drag_data_received. The view emits drop_uris from drag_data_received
	 * if valid uris have been dropped. Plugins should connect to 
	 * drag_motion, drag_drop and drag_data_received to change this 
	 * default behaviour. They should _NOT_ use this signal because this
	 * will not prevent gedit from loading the uri
	 */
	view_signals[DROP_URIS] =
		g_signal_new ("drop_uris",
		              G_TYPE_FROM_CLASS (object_class),
		              G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
		              G_STRUCT_OFFSET (GeditViewClass, drop_uris),
		              NULL, NULL,
		              g_cclosure_marshal_VOID__BOXED,
		              G_TYPE_NONE, 1, G_TYPE_STRV);

	g_type_class_add_private (klass, sizeof (GeditViewPrivate));

	binding_set = gtk_binding_set_by_class (klass);

	gtk_binding_entry_add_signal (binding_set,
	                              GDK_KEY_d,
	                              GDK_CONTROL_MASK,
	                              "delete_from_cursor", 2,
	                              G_TYPE_ENUM, GTK_DELETE_PARAGRAPHS,
	                              G_TYPE_INT, 1);
}

/**
 * gedit_view_new:
 * @doc: a #GeditDocument
 * 
 * Creates a new #GeditView object displaying the @doc document. 
 * @doc cannot be %NULL.
 *
 * Return value: a new #GeditView
 **/
GtkWidget *
gedit_view_new (GeditDocument *doc)
{
	g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), NULL);

856
	return GTK_WIDGET (g_object_new (GEDIT_TYPE_VIEW, "buffer", doc, NULL));
857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068
}

void
gedit_view_cut_clipboard (GeditView *view)
{
	GtkTextBuffer *buffer;
	GtkClipboard *clipboard;

	gedit_debug (DEBUG_VIEW);

	g_return_if_fail (GEDIT_IS_VIEW (view));

	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
	g_return_if_fail (buffer != NULL);

	clipboard = gtk_widget_get_clipboard (GTK_WIDGET (view),
	                                      GDK_SELECTION_CLIPBOARD);

	/* FIXME: what is default editability of a buffer? */
	gtk_text_buffer_cut_clipboard (buffer,
	                               clipboard,
	                               !gedit_document_get_readonly (
	                               		GEDIT_DOCUMENT (buffer)));

	gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
	                              gtk_text_buffer_get_insert (buffer),
	                              GEDIT_VIEW_SCROLL_MARGIN,
	                              FALSE,
	                              0.0,
	                              0.0);
}

void
gedit_view_copy_clipboard (GeditView *view)
{
	GtkTextBuffer *buffer;
	GtkClipboard *clipboard;

	gedit_debug (DEBUG_VIEW);

	g_return_if_fail (GEDIT_IS_VIEW (view));

	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
	g_return_if_fail (buffer != NULL);

	clipboard = gtk_widget_get_clipboard (GTK_WIDGET (view),
	                                      GDK_SELECTION_CLIPBOARD);

	gtk_text_buffer_copy_clipboard (buffer, clipboard);

	/* on copy do not scroll, we are already on screen */
}

void
gedit_view_paste_clipboard (GeditView *view)
{
	GtkTextBuffer *buffer;
	GtkClipboard *clipboard;

	gedit_debug (DEBUG_VIEW);

	g_return_if_fail (GEDIT_IS_VIEW (view));

	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
	g_return_if_fail (buffer != NULL);

	clipboard = gtk_widget_get_clipboard (GTK_WIDGET (view),
	                                      GDK_SELECTION_CLIPBOARD);

	/* FIXME: what is default editability of a buffer? */
	gtk_text_buffer_paste_clipboard (buffer,
	                                 clipboard,
	                                 NULL,
	                                 !gedit_document_get_readonly (
	                                 	GEDIT_DOCUMENT (buffer)));

	gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
	                              gtk_text_buffer_get_insert (buffer),
	                              GEDIT_VIEW_SCROLL_MARGIN,
	                              FALSE,
	                              0.0,
	                              0.0);
}

/**
 * gedit_view_delete_selection:
 * @view: a #GeditView
 * 
 * Deletes the text currently selected in the #GtkTextBuffer associated
 * to the view and scroll to the cursor position.
 **/
void
gedit_view_delete_selection (GeditView *view)
{
	GtkTextBuffer *buffer;

	gedit_debug (DEBUG_VIEW);

	g_return_if_fail (GEDIT_IS_VIEW (view));

	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
	g_return_if_fail (buffer != NULL);

	/* FIXME: what is default editability of a buffer? */
	gtk_text_buffer_delete_selection (buffer,
	                                  TRUE,
	                                  !gedit_document_get_readonly (
	                                  	GEDIT_DOCUMENT (buffer)));

	gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
	                              gtk_text_buffer_get_insert (buffer),
	                              GEDIT_VIEW_SCROLL_MARGIN,
	                              FALSE,
	                              0.0,
	                              0.0);
}

/**
 * gedit_view_select_all:
 * @view: a #GeditView
 * 
 * Selects all the text displayed in the @view.
 **/
void
gedit_view_select_all (GeditView *view)
{
	GtkTextBuffer *buffer ;
	GtkTextIter start, end;

	gedit_debug (DEBUG_VIEW);

	g_return_if_fail (GEDIT_IS_VIEW (view));

	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
	g_return_if_fail (buffer != NULL);

	gtk_text_buffer_get_bounds (buffer, &start, &end);
	gtk_text_buffer_select_range (buffer, &start, &end);
}

/**
 * gedit_view_scroll_to_cursor:
 * @view: a #GeditView
 * 
 * Scrolls the @view to the cursor position.
 **/
void
gedit_view_scroll_to_cursor (GeditView *view)
{
	GtkTextBuffer* buffer;

	gedit_debug (DEBUG_VIEW);

	g_return_if_fail (GEDIT_IS_VIEW (view));

	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
	g_return_if_fail (buffer != NULL);

	gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
	                              gtk_text_buffer_get_insert (buffer),
	                              0.25,
	                              FALSE,
	                              0.0,
	                              0.0);
}

/* FIXME this is an issue for introspection */
/**
 * gedit_view_set_font:
 * @view: a #GeditView
 * @def: whether to reset the default font
 * @font_name: the name of the font to use
 * 
 * If @def is #TRUE, resets the font of the @view to the default font
 * otherwise sets it to @font_name.
 **/
void
gedit_view_set_font (GeditView   *view, 
		     gboolean     def, 
		     const gchar *font_name)
{
	PangoFontDescription *font_desc;

	gedit_debug (DEBUG_VIEW);

	g_return_if_fail (GEDIT_IS_VIEW (view));

	if (def)
	{
		GObject *settings;
		gchar *font;

		settings = _gedit_app_get_settings (gedit_app_get_default ());
		font = gedit_settings_get_system_font (GEDIT_SETTINGS (settings));

		font_desc = pango_font_description_from_string (font);
		g_free (font);
	}
	else
	{
		g_return_if_fail (font_name != NULL);

		font_desc = pango_font_description_from_string (font_name);
	}

	g_return_if_fail (font_desc != NULL);

	gtk_widget_override_font (GTK_WIDGET (view), font_desc);

	pango_font_description_free (font_desc);
}

1069
/* ex:set ts=8 noet: */