Gimp/app/tools/gimptexttool.c
Michael Natterer c110220793 Bug 693978 - GIMP crashes after changing text preset
Applying a preset overwrites all the tool option's properties, also
temporarily its name. This name change gets auto-synced with the
option's text proxy object which also inherits from GimpObject and has
a name. Make sure we don't queue that name change for being applied to
the text layer's text object, because that code only handles
properties of GimpText itself.
(cherry picked from commit 931ea102f8)
2013-02-16 20:43:49 +01:00

1981 lines
64 KiB
C

/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* GimpTextTool
* Copyright (C) 2002-2010 Sven Neumann <sven@gimp.org>
* Daniel Eddeland <danedde@svn.gnome.org>
* Michael Natterer <mitch@gimp.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <gegl.h>
#include <gtk/gtk.h>
#include "libgimpbase/gimpbase.h"
#include "libgimpconfig/gimpconfig.h"
#include "libgimpwidgets/gimpwidgets.h"
#include "tools-types.h"
#include "core/gimp.h"
#include "core/gimpcontext.h"
#include "core/gimpimage.h"
#include "core/gimpimage-pick-layer.h"
#include "core/gimpimage-undo.h"
#include "core/gimpimage-undo-push.h"
#include "core/gimplayer-floating-sel.h"
#include "core/gimpmarshal.h"
#include "core/gimptoolinfo.h"
#include "core/gimpundostack.h"
#include "text/gimptext.h"
#include "text/gimptext-vectors.h"
#include "text/gimptextlayer.h"
#include "text/gimptextlayout.h"
#include "text/gimptextundo.h"
#include "vectors/gimpvectors-warp.h"
#include "widgets/gimpdialogfactory.h"
#include "widgets/gimphelp-ids.h"
#include "widgets/gimpmenufactory.h"
#include "widgets/gimptextbuffer.h"
#include "widgets/gimpuimanager.h"
#include "widgets/gimpviewabledialog.h"
#include "display/gimpcanvasgroup.h"
#include "display/gimpdisplay.h"
#include "display/gimpdisplayshell.h"
#include "gimprectangletool.h"
#include "gimptextoptions.h"
#include "gimptexttool.h"
#include "gimptexttool-editor.h"
#include "gimptoolcontrol.h"
#include "gimp-intl.h"
#define TEXT_UNDO_TIMEOUT 3
/* local function prototypes */
static void gimp_text_tool_rectangle_tool_iface_init (GimpRectangleToolInterface *iface);
static void gimp_text_tool_constructed (GObject *object);
static void gimp_text_tool_finalize (GObject *object);
static void gimp_text_tool_control (GimpTool *tool,
GimpToolAction action,
GimpDisplay *display);
static void gimp_text_tool_button_press (GimpTool *tool,
const GimpCoords *coords,
guint32 time,
GdkModifierType state,
GimpButtonPressType press_type,
GimpDisplay *display);
static void gimp_text_tool_button_release (GimpTool *tool,
const GimpCoords *coords,
guint32 time,
GdkModifierType state,
GimpButtonReleaseType release_type,
GimpDisplay *display);
static void gimp_text_tool_motion (GimpTool *tool,
const GimpCoords *coords,
guint32 time,
GdkModifierType state,
GimpDisplay *display);
static gboolean gimp_text_tool_key_press (GimpTool *tool,
GdkEventKey *kevent,
GimpDisplay *display);
static gboolean gimp_text_tool_key_release (GimpTool *tool,
GdkEventKey *kevent,
GimpDisplay *display);
static void gimp_text_tool_oper_update (GimpTool *tool,
const GimpCoords *coords,
GdkModifierType state,
gboolean proximity,
GimpDisplay *display);
static void gimp_text_tool_cursor_update (GimpTool *tool,
const GimpCoords *coords,
GdkModifierType state,
GimpDisplay *display);
static GimpUIManager * gimp_text_tool_get_popup (GimpTool *tool,
const GimpCoords *coords,
GdkModifierType state,
GimpDisplay *display,
const gchar **ui_path);
static void gimp_text_tool_draw (GimpDrawTool *draw_tool);
static void gimp_text_tool_draw_selection (GimpDrawTool *draw_tool);
static void gimp_text_tool_frame_item (GimpTextTool *text_tool);
static gboolean gimp_text_tool_rectangle_change_complete
(GimpRectangleTool *rect_tool);
static void gimp_text_tool_connect (GimpTextTool *text_tool,
GimpTextLayer *layer,
GimpText *text);
static void gimp_text_tool_layer_notify (GimpTextLayer *layer,
const GParamSpec *pspec,
GimpTextTool *text_tool);
static void gimp_text_tool_proxy_notify (GimpText *text,
const GParamSpec *pspec,
GimpTextTool *text_tool);
static void gimp_text_tool_text_notify (GimpText *text,
const GParamSpec *pspec,
GimpTextTool *text_tool);
static void gimp_text_tool_text_changed (GimpText *text,
GimpTextTool *text_tool);
static gboolean gimp_text_tool_apply (GimpTextTool *text_tool,
gboolean push_undo);
static void gimp_text_tool_create_layer (GimpTextTool *text_tool,
GimpText *text);
static void gimp_text_tool_layer_changed (GimpImage *image,
GimpTextTool *text_tool);
static void gimp_text_tool_set_image (GimpTextTool *text_tool,
GimpImage *image);
static gboolean gimp_text_tool_set_drawable (GimpTextTool *text_tool,
GimpDrawable *drawable,
gboolean confirm);
static void gimp_text_tool_block_drawing (GimpTextTool *text_tool);
static void gimp_text_tool_unblock_drawing (GimpTextTool *text_tool);
static void gimp_text_tool_buffer_begin_edit (GimpTextBuffer *buffer,
GimpTextTool *text_tool);
static void gimp_text_tool_buffer_end_edit (GimpTextBuffer *buffer,
GimpTextTool *text_tool);
G_DEFINE_TYPE_WITH_CODE (GimpTextTool, gimp_text_tool,
GIMP_TYPE_DRAW_TOOL,
G_IMPLEMENT_INTERFACE (GIMP_TYPE_RECTANGLE_TOOL,
gimp_text_tool_rectangle_tool_iface_init))
#define parent_class gimp_text_tool_parent_class
void
gimp_text_tool_register (GimpToolRegisterCallback callback,
gpointer data)
{
(* callback) (GIMP_TYPE_TEXT_TOOL,
GIMP_TYPE_TEXT_OPTIONS,
gimp_text_options_gui,
GIMP_CONTEXT_FOREGROUND_MASK |
GIMP_CONTEXT_FONT_MASK |
GIMP_CONTEXT_PALETTE_MASK /* for the color popup's palette tab */,
"gimp-text-tool",
_("Text"),
_("Text Tool: Create or edit text layers"),
N_("Te_xt"), "T",
NULL, GIMP_HELP_TOOL_TEXT,
GIMP_STOCK_TOOL_TEXT,
data);
}
static void
gimp_text_tool_class_init (GimpTextToolClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
GimpDrawToolClass *draw_tool_class = GIMP_DRAW_TOOL_CLASS (klass);
object_class->constructed = gimp_text_tool_constructed;
object_class->finalize = gimp_text_tool_finalize;
object_class->set_property = gimp_rectangle_tool_set_property;
object_class->get_property = gimp_rectangle_tool_get_property;
tool_class->control = gimp_text_tool_control;
tool_class->button_press = gimp_text_tool_button_press;
tool_class->motion = gimp_text_tool_motion;
tool_class->button_release = gimp_text_tool_button_release;
tool_class->key_press = gimp_text_tool_key_press;
tool_class->key_release = gimp_text_tool_key_release;
tool_class->oper_update = gimp_text_tool_oper_update;
tool_class->cursor_update = gimp_text_tool_cursor_update;
tool_class->get_popup = gimp_text_tool_get_popup;
draw_tool_class->draw = gimp_text_tool_draw;
gimp_rectangle_tool_install_properties (object_class);
}
static void
gimp_text_tool_rectangle_tool_iface_init (GimpRectangleToolInterface *iface)
{
iface->execute = NULL;
iface->cancel = NULL;
iface->rectangle_change_complete = gimp_text_tool_rectangle_change_complete;
}
static void
gimp_text_tool_init (GimpTextTool *text_tool)
{
GimpTool *tool = GIMP_TOOL (text_tool);
text_tool->proxy = NULL;
text_tool->pending = NULL;
text_tool->idle_id = 0;
text_tool->text = NULL;
text_tool->layer = NULL;
text_tool->image = NULL;
text_tool->layout = NULL;
text_tool->buffer = gimp_text_buffer_new ();
g_signal_connect (text_tool->buffer, "begin-user-action",
G_CALLBACK (gimp_text_tool_buffer_begin_edit),
text_tool);
g_signal_connect (text_tool->buffer, "end-user-action",
G_CALLBACK (gimp_text_tool_buffer_end_edit),
text_tool);
text_tool->handle_rectangle_change_complete = TRUE;
gimp_text_tool_editor_init (text_tool);
gimp_tool_control_set_scroll_lock (tool->control, TRUE);
gimp_tool_control_set_handle_empty_image (tool->control, TRUE);
gimp_tool_control_set_wants_click (tool->control, TRUE);
gimp_tool_control_set_wants_double_click (tool->control, TRUE);
gimp_tool_control_set_wants_triple_click (tool->control, TRUE);
gimp_tool_control_set_wants_all_key_events (tool->control, TRUE);
gimp_tool_control_set_precision (tool->control,
GIMP_CURSOR_PRECISION_PIXEL_BORDER);
gimp_tool_control_set_tool_cursor (tool->control,
GIMP_TOOL_CURSOR_TEXT);
gimp_tool_control_set_action_object_1 (tool->control,
"context/context-font-select-set");
}
static void
gimp_text_tool_constructed (GObject *object)
{
GimpTextTool *text_tool = GIMP_TEXT_TOOL (object);
GimpTextOptions *options = GIMP_TEXT_TOOL_GET_OPTIONS (text_tool);
if (G_OBJECT_CLASS (parent_class)->constructed)
G_OBJECT_CLASS (parent_class)->constructed (object);
gimp_rectangle_tool_constructor (object);
text_tool->proxy = g_object_new (GIMP_TYPE_TEXT, NULL);
gimp_text_options_connect_text (options, text_tool->proxy);
g_signal_connect_object (text_tool->proxy, "notify",
G_CALLBACK (gimp_text_tool_proxy_notify),
text_tool, 0);
g_object_set (options,
"highlight", FALSE,
NULL);
}
static void
gimp_text_tool_finalize (GObject *object)
{
GimpTextTool *text_tool = GIMP_TEXT_TOOL (object);
if (text_tool->proxy)
{
g_object_unref (text_tool->proxy);
text_tool->proxy = NULL;
}
if (text_tool->buffer)
{
g_object_unref (text_tool->buffer);
text_tool->buffer = NULL;
}
gimp_text_tool_editor_finalize (text_tool);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
gimp_text_tool_control (GimpTool *tool,
GimpToolAction action,
GimpDisplay *display)
{
GimpTextTool *text_tool = GIMP_TEXT_TOOL (tool);
switch (action)
{
case GIMP_TOOL_ACTION_PAUSE:
case GIMP_TOOL_ACTION_RESUME:
break;
case GIMP_TOOL_ACTION_HALT:
gimp_text_tool_editor_halt (text_tool);
gimp_text_tool_set_drawable (text_tool, NULL, FALSE);
break;
}
gimp_rectangle_tool_control (tool, action, display);
GIMP_TOOL_CLASS (parent_class)->control (tool, action, display);
}
static void
gimp_text_tool_button_press (GimpTool *tool,
const GimpCoords *coords,
guint32 time,
GdkModifierType state,
GimpButtonPressType press_type,
GimpDisplay *display)
{
GimpTextTool *text_tool = GIMP_TEXT_TOOL (tool);
GimpRectangleTool *rect_tool = GIMP_RECTANGLE_TOOL (tool);
GimpImage *image = gimp_display_get_image (display);
GimpText *text = text_tool->text;
gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
if (tool->display && tool->display != display)
gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, display);
if (press_type == GIMP_BUTTON_PRESS_NORMAL)
{
gimp_tool_control_activate (tool->control);
gimp_text_tool_reset_im_context (text_tool);
text_tool->selecting = FALSE;
if (gimp_rectangle_tool_point_in_rectangle (rect_tool,
coords->x,
coords->y) &&
! text_tool->moving)
{
gimp_rectangle_tool_set_function (rect_tool, GIMP_RECTANGLE_TOOL_DEAD);
}
else
{
gimp_rectangle_tool_button_press (tool, coords, time, state, display);
}
/* bail out now if the user user clicked on a handle of an
* existing rectangle, but not inside an existing framed layer
*/
if (gimp_rectangle_tool_get_function (rect_tool) !=
GIMP_RECTANGLE_TOOL_CREATING)
{
if (text_tool->layer)
{
GimpItem *item = GIMP_ITEM (text_tool->layer);
gdouble x = coords->x - gimp_item_get_offset_x (item);
gdouble y = coords->y - gimp_item_get_offset_y (item);
if (x < 0 || x >= gimp_item_get_width (item) ||
y < 0 || y >= gimp_item_get_height (item))
{
gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
return;
}
}
else
{
gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
return;
}
}
/* if the the click is not related to the currently edited text
* layer in any way, try to pick a text layer
*/
if (! text_tool->moving &&
gimp_rectangle_tool_get_function (rect_tool) ==
GIMP_RECTANGLE_TOOL_CREATING)
{
GimpTextLayer *text_layer;
text_layer = gimp_image_pick_text_layer (image, coords->x, coords->y);
if (text_layer && text_layer != text_tool->layer)
{
if (text_tool->image == image)
g_signal_handlers_block_by_func (image,
gimp_text_tool_layer_changed,
text_tool);
gimp_image_set_active_layer (image, GIMP_LAYER (text_layer));
if (text_tool->image == image)
g_signal_handlers_unblock_by_func (image,
gimp_text_tool_layer_changed,
text_tool);
}
}
}
if (gimp_image_coords_in_active_pickable (image, coords, FALSE, FALSE))
{
GimpDrawable *drawable = gimp_image_get_active_drawable (image);
GimpItem *item = GIMP_ITEM (drawable);
gdouble x = coords->x - gimp_item_get_offset_x (item);
gdouble y = coords->y - gimp_item_get_offset_y (item);
/* did the user click on a text layer? */
if (gimp_text_tool_set_drawable (text_tool, drawable, TRUE))
{
if (press_type == GIMP_BUTTON_PRESS_NORMAL)
{
/* if we clicked on a text layer while the tool was idle
* (didn't show a rectangle), frame the layer and switch to
* selecting instead of drawing a new rectangle
*/
if (gimp_rectangle_tool_get_function (rect_tool) ==
GIMP_RECTANGLE_TOOL_CREATING)
{
gimp_rectangle_tool_set_function (rect_tool,
GIMP_RECTANGLE_TOOL_DEAD);
gimp_text_tool_frame_item (text_tool);
}
if (text_tool->text && text_tool->text != text)
{
gimp_text_tool_editor_start (text_tool);
}
}
if (text_tool->text && ! text_tool->moving)
{
text_tool->selecting = TRUE;
gimp_text_tool_editor_button_press (text_tool, x, y, press_type);
}
else
{
text_tool->selecting = FALSE;
}
gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
return;
}
}
if (press_type == GIMP_BUTTON_PRESS_NORMAL)
{
/* create a new text layer */
text_tool->text_box_fixed = FALSE;
gimp_text_tool_connect (text_tool, NULL, NULL);
gimp_text_tool_editor_start (text_tool);
}
gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
}
static void
gimp_text_tool_button_release (GimpTool *tool,
const GimpCoords *coords,
guint32 time,
GdkModifierType state,
GimpButtonReleaseType release_type,
GimpDisplay *display)
{
GimpRectangleTool *rect_tool = GIMP_RECTANGLE_TOOL (tool);
GimpTextTool *text_tool = GIMP_TEXT_TOOL (tool);
gimp_tool_control_halt (tool->control);
if (text_tool->selecting)
{
/* we are in a selection process (user has initially clicked on
* an existing text layer), so finish the selection process and
* ignore rectangle-change-complete.
*/
/* need to block "end-user-action" on the text buffer, because
* GtkTextBuffer considers copying text to the clipboard an
* undo-relevant user action, which is clearly a bug, but what
* can we do...
*/
g_signal_handlers_block_by_func (text_tool->buffer,
gimp_text_tool_buffer_begin_edit,
text_tool);
g_signal_handlers_block_by_func (text_tool->buffer,
gimp_text_tool_buffer_end_edit,
text_tool);
gimp_text_tool_editor_button_release (text_tool);
g_signal_handlers_unblock_by_func (text_tool->buffer,
gimp_text_tool_buffer_end_edit,
text_tool);
g_signal_handlers_unblock_by_func (text_tool->buffer,
gimp_text_tool_buffer_begin_edit,
text_tool);
text_tool->selecting = FALSE;
text_tool->handle_rectangle_change_complete = FALSE;
/* there is no cancelling of selections yet */
if (release_type == GIMP_BUTTON_RELEASE_CANCEL)
release_type = GIMP_BUTTON_RELEASE_NORMAL;
}
else if (gimp_rectangle_tool_get_function (rect_tool) ==
GIMP_RECTANGLE_TOOL_DEAD)
{
/* the user clicked in dead space (like between the corner and
* edge handles, so completely ignore that.
*/
text_tool->handle_rectangle_change_complete = FALSE;
}
else if (release_type == GIMP_BUTTON_RELEASE_CANCEL)
{
/* user has clicked outside of any text layer in order to
* create a new text, but cancelled the operation.
*/
gimp_text_tool_editor_halt (text_tool);
text_tool->handle_rectangle_change_complete = FALSE;
}
else
{
gint x1, y1;
gint x2, y2;
/* otherwise the user has clicked outside of any text layer in
* order to create a new text, fall through and let
* rectangle-change-complete do its job of setting the new text
* layer's size.
*/
g_object_get (text_tool,
"x1", &x1,
"y1", &y1,
"x2", &x2,
"y2", &y2,
NULL);
if (release_type == GIMP_BUTTON_RELEASE_CLICK ||
(x2 - x1) < 3 ||
(y2 - y1) < 3)
{
/* unless the rectangle is unreasonably small to hold any
* real text (the user has eitherjust clicked or just made
* a rectangle of a few pixels), so set the text box to
* dynamic and ignore rectangle-change-complete.
*/
g_object_set (text_tool->proxy,
"box-mode", GIMP_TEXT_BOX_DYNAMIC,
NULL);
text_tool->handle_rectangle_change_complete = FALSE;
}
}
gimp_rectangle_tool_button_release (tool, coords, time, state,
release_type, display);
text_tool->handle_rectangle_change_complete = TRUE;
}
static void
gimp_text_tool_motion (GimpTool *tool,
const GimpCoords *coords,
guint32 time,
GdkModifierType state,
GimpDisplay *display)
{
GimpTextTool *text_tool = GIMP_TEXT_TOOL (tool);
if (! text_tool->selecting)
{
gimp_rectangle_tool_motion (tool, coords, time, state, display);
}
else
{
GimpItem *item = GIMP_ITEM (text_tool->layer);
gdouble x = coords->x - gimp_item_get_offset_x (item);
gdouble y = coords->y - gimp_item_get_offset_y (item);
gimp_text_tool_editor_motion (text_tool, x, y);
}
}
static gboolean
gimp_text_tool_key_press (GimpTool *tool,
GdkEventKey *kevent,
GimpDisplay *display)
{
GimpTextTool *text_tool = GIMP_TEXT_TOOL (tool);
if (display == tool->display)
return gimp_text_tool_editor_key_press (text_tool, kevent);
return FALSE;
}
static gboolean
gimp_text_tool_key_release (GimpTool *tool,
GdkEventKey *kevent,
GimpDisplay *display)
{
GimpTextTool *text_tool = GIMP_TEXT_TOOL (tool);
if (display == tool->display)
return gimp_text_tool_editor_key_release (text_tool, kevent);
return FALSE;
}
static void
gimp_text_tool_oper_update (GimpTool *tool,
const GimpCoords *coords,
GdkModifierType state,
gboolean proximity,
GimpDisplay *display)
{
GimpTextTool *text_tool = GIMP_TEXT_TOOL (tool);
GimpRectangleTool *rect_tool = GIMP_RECTANGLE_TOOL (tool);
gimp_rectangle_tool_oper_update (tool, coords, state, proximity, display);
text_tool->moving = (gimp_rectangle_tool_get_function (rect_tool) ==
GIMP_RECTANGLE_TOOL_MOVING &&
(state & GDK_MOD1_MASK));
}
static void
gimp_text_tool_cursor_update (GimpTool *tool,
const GimpCoords *coords,
GdkModifierType state,
GimpDisplay *display)
{
if (tool->display == display)
{
GimpTextTool *text_tool = GIMP_TEXT_TOOL (tool);
if (gimp_rectangle_tool_point_in_rectangle (GIMP_RECTANGLE_TOOL (tool),
coords->x,
coords->y) &&
! text_tool->moving)
{
gimp_tool_control_set_cursor (tool->control, GDK_XTERM);
gimp_tool_control_set_cursor_modifier (tool->control,
GIMP_CURSOR_MODIFIER_NONE);
}
else
{
gimp_rectangle_tool_cursor_update (tool, coords, state, display);
}
}
else
{
gimp_tool_control_set_cursor (tool->control, GDK_XTERM);
gimp_tool_control_set_cursor_modifier (tool->control,
GIMP_CURSOR_MODIFIER_NONE);
}
GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, display);
}
static GimpUIManager *
gimp_text_tool_get_popup (GimpTool *tool,
const GimpCoords *coords,
GdkModifierType state,
GimpDisplay *display,
const gchar **ui_path)
{
GimpTextTool *text_tool = GIMP_TEXT_TOOL (tool);
if (gimp_rectangle_tool_point_in_rectangle (GIMP_RECTANGLE_TOOL (text_tool),
coords->x,
coords->y))
{
if (! text_tool->ui_manager)
{
GimpDialogFactory *dialog_factory;
GtkWidget *im_menu;
GList *children;
dialog_factory = gimp_dialog_factory_get_singleton ();
text_tool->ui_manager =
gimp_menu_factory_manager_new (gimp_dialog_factory_get_menu_factory (dialog_factory),
"<TextTool>",
text_tool, FALSE);
im_menu = gtk_ui_manager_get_widget (GTK_UI_MANAGER (text_tool->ui_manager),
"/text-tool-popup/text-tool-input-methods-menu");
if (GTK_IS_MENU_ITEM (im_menu))
im_menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (im_menu));
/* hide the generated "empty" item */
children = gtk_container_get_children (GTK_CONTAINER (im_menu));
while (children)
{
gtk_widget_hide (children->data);
children = g_list_remove (children, children->data);
}
gtk_im_multicontext_append_menuitems (GTK_IM_MULTICONTEXT (text_tool->im_context),
GTK_MENU_SHELL (im_menu));
}
gimp_ui_manager_update (text_tool->ui_manager, text_tool);
*ui_path = "/text-tool-popup";
return text_tool->ui_manager;
}
return NULL;
}
static void
gimp_text_tool_draw (GimpDrawTool *draw_tool)
{
GimpTextTool *text_tool = GIMP_TEXT_TOOL (draw_tool);
g_object_set (text_tool,
"narrow-mode", TRUE,
NULL);
gimp_rectangle_tool_draw (draw_tool, NULL);
if (! text_tool->text ||
! text_tool->layer ||
! text_tool->layer->text)
return;
gimp_text_tool_ensure_layout (text_tool);
if (gtk_text_buffer_get_has_selection (GTK_TEXT_BUFFER (text_tool->buffer)))
{
/* If the text buffer has a selection, highlight the selected letters */
gimp_text_tool_draw_selection (draw_tool);
}
else
{
/* If the text buffer has no selection, draw the text cursor */
GimpCanvasItem *item;
PangoRectangle cursor_rect;
gint off_x, off_y;
gboolean overwrite;
gimp_text_tool_editor_get_cursor_rect (text_tool,
text_tool->overwrite_mode,
&cursor_rect);
gimp_item_get_offset (GIMP_ITEM (text_tool->layer), &off_x, &off_y);
cursor_rect.x += off_x;
cursor_rect.y += off_y;
overwrite = text_tool->overwrite_mode && cursor_rect.width != 0;
item = gimp_draw_tool_add_text_cursor (draw_tool, &cursor_rect,
overwrite);
gimp_canvas_item_set_highlight (item, TRUE);
}
}
static void
gimp_text_tool_draw_selection (GimpDrawTool *draw_tool)
{
GimpTextTool *text_tool = GIMP_TEXT_TOOL (draw_tool);
GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
GimpCanvasGroup *fill_group;
PangoLayout *layout;
gint offset_x;
gint offset_y;
gint off_x, off_y;
PangoLayoutIter *iter;
GtkTextIter sel_start, sel_end;
gint min, max;
gint i;
fill_group = gimp_draw_tool_add_fill_group (draw_tool);
gimp_canvas_item_set_highlight (GIMP_CANVAS_ITEM (fill_group), TRUE);
gtk_text_buffer_get_selection_bounds (buffer, &sel_start, &sel_end);
min = gimp_text_buffer_get_iter_index (text_tool->buffer, &sel_start, TRUE);
max = gimp_text_buffer_get_iter_index (text_tool->buffer, &sel_end, TRUE);
layout = gimp_text_layout_get_pango_layout (text_tool->layout);
gimp_text_layout_get_offsets (text_tool->layout, &offset_x, &offset_y);
gimp_item_get_offset (GIMP_ITEM (text_tool->layer), &off_x, &off_y);
offset_x += off_x;
offset_y += off_y;
iter = pango_layout_get_iter (layout);
gimp_draw_tool_push_group (draw_tool, fill_group);
do
{
if (! pango_layout_iter_get_run (iter))
continue;
i = pango_layout_iter_get_index (iter);
if (i >= min && i < max)
{
PangoRectangle rect;
gint ytop, ybottom;
pango_layout_iter_get_char_extents (iter, &rect);
pango_layout_iter_get_line_yrange (iter, &ytop, &ybottom);
rect.y = ytop;
rect.height = ybottom - ytop;
pango_extents_to_pixels (&rect, NULL);
gimp_text_layout_transform_rect (text_tool->layout, &rect);
rect.x += offset_x;
rect.y += offset_y;
gimp_draw_tool_add_rectangle (draw_tool, TRUE,
rect.x, rect.y,
rect.width, rect.height);
}
}
while (pango_layout_iter_next_char (iter));
gimp_draw_tool_pop_group (draw_tool);
pango_layout_iter_free (iter);
}
static void
gimp_text_tool_frame_item (GimpTextTool *text_tool)
{
g_return_if_fail (GIMP_IS_LAYER (text_tool->layer));
text_tool->handle_rectangle_change_complete = FALSE;
gimp_rectangle_tool_frame_item (GIMP_RECTANGLE_TOOL (text_tool),
GIMP_ITEM (text_tool->layer));
text_tool->handle_rectangle_change_complete = TRUE;
}
static gboolean
gimp_text_tool_rectangle_change_complete (GimpRectangleTool *rect_tool)
{
GimpTextTool *text_tool = GIMP_TEXT_TOOL (rect_tool);
gimp_text_tool_editor_position (text_tool);
if (text_tool->handle_rectangle_change_complete)
{
GimpItem *item = GIMP_ITEM (text_tool->layer);
gint x1, y1;
gint x2, y2;
if (! item)
{
/* we can't set properties for the text layer, because it
* isn't created until some text has been inserted, so we
* need to make a special note that will remind us what to
* do when we actually create the layer
*/
text_tool->text_box_fixed = TRUE;
return TRUE;
}
g_object_get (rect_tool,
"x1", &x1,
"y1", &y1,
"x2", &x2,
"y2", &y2,
NULL);
if (x1 != gimp_item_get_offset_x (item) ||
y1 != gimp_item_get_offset_y (item) ||
(x2 - x1) != gimp_item_get_width (item) ||
(y2 - y1) != gimp_item_get_height (item))
{
GimpUnit box_unit = text_tool->proxy->box_unit;
gdouble xres, yres;
gboolean push_undo = TRUE;
GimpUndo *undo;
gimp_image_get_resolution (text_tool->image, &xres, &yres);
g_object_set (text_tool->proxy,
"box-mode", GIMP_TEXT_BOX_FIXED,
"box-width", gimp_pixels_to_units (x2 - x1,
box_unit, xres),
"box-height", gimp_pixels_to_units (y2 - y1,
box_unit, yres),
NULL);
undo = gimp_image_undo_can_compress (text_tool->image,
GIMP_TYPE_UNDO_STACK,
GIMP_UNDO_GROUP_TEXT);
if (undo &&
gimp_undo_get_age (undo) <= TEXT_UNDO_TIMEOUT &&
g_object_get_data (G_OBJECT (undo), "reshape-text-layer") == (gpointer) item)
push_undo = FALSE;
if (push_undo)
{
gimp_image_undo_group_start (text_tool->image, GIMP_UNDO_GROUP_TEXT,
_("Reshape Text Layer"));
undo = gimp_image_undo_can_compress (text_tool->image, GIMP_TYPE_UNDO_STACK,
GIMP_UNDO_GROUP_TEXT);
if (undo)
g_object_set_data (G_OBJECT (undo), "reshape-text-layer",
(gpointer) item);
}
gimp_item_translate (item,
x1 - gimp_item_get_offset_x (item),
y1 - gimp_item_get_offset_y (item),
push_undo);
gimp_text_tool_apply (text_tool, push_undo);
if (push_undo)
gimp_image_undo_group_end (text_tool->image);
}
}
return TRUE;
}
static void
gimp_text_tool_connect (GimpTextTool *text_tool,
GimpTextLayer *layer,
GimpText *text)
{
GimpTool *tool = GIMP_TOOL (text_tool);
g_return_if_fail (text == NULL || (layer != NULL && layer->text == text));
if (text_tool->text != text)
{
GimpTextOptions *options = GIMP_TEXT_TOOL_GET_OPTIONS (tool);
g_signal_handlers_block_by_func (text_tool->buffer,
gimp_text_tool_buffer_begin_edit,
text_tool);
g_signal_handlers_block_by_func (text_tool->buffer,
gimp_text_tool_buffer_end_edit,
text_tool);
if (text_tool->text)
{
g_signal_handlers_disconnect_by_func (text_tool->text,
gimp_text_tool_text_notify,
text_tool);
g_signal_handlers_disconnect_by_func (text_tool->text,
gimp_text_tool_text_changed,
text_tool);
if (text_tool->pending)
gimp_text_tool_apply (text_tool, TRUE);
g_object_unref (text_tool->text);
text_tool->text = NULL;
g_object_set (text_tool->proxy,
"text", NULL,
"markup", NULL,
NULL);
gimp_text_buffer_set_text (text_tool->buffer, NULL);
gimp_text_tool_clear_layout (text_tool);
}
gimp_context_define_property (GIMP_CONTEXT (options),
GIMP_CONTEXT_PROP_FOREGROUND,
text != NULL);
if (text)
{
if (text->unit != text_tool->proxy->unit)
gimp_size_entry_set_unit (GIMP_SIZE_ENTRY (options->size_entry),
text->unit);
gimp_config_sync (G_OBJECT (text), G_OBJECT (text_tool->proxy), 0);
if (text->markup)
gimp_text_buffer_set_markup (text_tool->buffer, text->markup);
else
gimp_text_buffer_set_text (text_tool->buffer, text->text);
gimp_text_tool_clear_layout (text_tool);
text_tool->text = g_object_ref (text);
g_signal_connect (text, "notify",
G_CALLBACK (gimp_text_tool_text_notify),
text_tool);
g_signal_connect (text, "changed",
G_CALLBACK (gimp_text_tool_text_changed),
text_tool);
}
g_signal_handlers_unblock_by_func (text_tool->buffer,
gimp_text_tool_buffer_end_edit,
text_tool);
g_signal_handlers_unblock_by_func (text_tool->buffer,
gimp_text_tool_buffer_begin_edit,
text_tool);
}
if (text_tool->layer != layer)
{
if (text_tool->layer)
g_signal_handlers_disconnect_by_func (text_tool->layer,
gimp_text_tool_layer_notify,
text_tool);
text_tool->layer = layer;
if (layer)
g_signal_connect_object (text_tool->layer, "notify",
G_CALLBACK (gimp_text_tool_layer_notify),
text_tool, 0);
}
}
static void
gimp_text_tool_layer_notify (GimpTextLayer *layer,
const GParamSpec *pspec,
GimpTextTool *text_tool)
{
GimpTool *tool = GIMP_TOOL (text_tool);
if (! strcmp (pspec->name, "modified"))
{
if (layer->modified)
gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, tool->display);
}
else if (! strcmp (pspec->name, "text"))
{
if (! layer->text)
gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, tool->display);
}
}
static gboolean
gimp_text_tool_apply_idle (gpointer text_tool)
{
return gimp_text_tool_apply (text_tool, TRUE);
}
static void
gimp_text_tool_proxy_notify (GimpText *text,
const GParamSpec *pspec,
GimpTextTool *text_tool)
{
if (! text_tool->text)
return;
if ((pspec->flags & G_PARAM_READWRITE) == G_PARAM_READWRITE &&
pspec->owner_type == GIMP_TYPE_TEXT)
{
gimp_text_tool_block_drawing (text_tool);
text_tool->pending = g_list_append (text_tool->pending, (gpointer) pspec);
if (text_tool->idle_id)
g_source_remove (text_tool->idle_id);
text_tool->idle_id =
g_idle_add_full (G_PRIORITY_LOW,
gimp_text_tool_apply_idle, text_tool,
NULL);
}
}
static void
gimp_text_tool_text_notify (GimpText *text,
const GParamSpec *pspec,
GimpTextTool *text_tool)
{
g_return_if_fail (text == text_tool->text);
gimp_text_tool_block_drawing (text_tool);
if ((pspec->flags & G_PARAM_READWRITE) == G_PARAM_READWRITE)
{
GValue value = { 0, };
g_value_init (&value, pspec->value_type);
g_object_get_property (G_OBJECT (text), pspec->name, &value);
g_signal_handlers_block_by_func (text_tool->proxy,
gimp_text_tool_proxy_notify,
text_tool);
g_object_set_property (G_OBJECT (text_tool->proxy), pspec->name, &value);
g_signal_handlers_unblock_by_func (text_tool->proxy,
gimp_text_tool_proxy_notify,
text_tool);
g_value_unset (&value);
}
/* if the text has changed, (probably because of an undo), we put
* the new text into the text buffer
*/
if (strcmp (pspec->name, "text") == 0 ||
strcmp (pspec->name, "markup") == 0)
{
g_signal_handlers_block_by_func (text_tool->buffer,
gimp_text_tool_buffer_begin_edit,
text_tool);
g_signal_handlers_block_by_func (text_tool->buffer,
gimp_text_tool_buffer_end_edit,
text_tool);
if (text->markup)
gimp_text_buffer_set_markup (text_tool->buffer, text->markup);
else
gimp_text_buffer_set_text (text_tool->buffer, text->text);
g_signal_handlers_unblock_by_func (text_tool->buffer,
gimp_text_tool_buffer_end_edit,
text_tool);
g_signal_handlers_unblock_by_func (text_tool->buffer,
gimp_text_tool_buffer_begin_edit,
text_tool);
}
}
static void
gimp_text_tool_text_changed (GimpText *text,
GimpTextTool *text_tool)
{
/* we need to redraw the rectangle in any case because whatever
* changes to the text can change its size
*/
gimp_text_tool_frame_item (text_tool);
gimp_text_tool_unblock_drawing (text_tool);
}
static gboolean
gimp_text_tool_apply (GimpTextTool *text_tool,
gboolean push_undo)
{
const GParamSpec *pspec = NULL;
GimpImage *image;
GimpTextLayer *layer;
GObject *src;
GObject *dest;
GList *list;
gboolean undo_group = FALSE;
if (text_tool->idle_id)
{
g_source_remove (text_tool->idle_id);
text_tool->idle_id = 0;
}
g_return_val_if_fail (text_tool->text != NULL, FALSE);
g_return_val_if_fail (text_tool->layer != NULL, FALSE);
layer = text_tool->layer;
image = gimp_item_get_image (GIMP_ITEM (layer));
g_return_val_if_fail (layer->text == text_tool->text, FALSE);
/* Walk over the list of changes and figure out if we are changing
* a single property or need to push a full text undo.
*/
for (list = text_tool->pending;
list && list->next && list->next->data == list->data;
list = list->next)
/* do nothing */;
if (g_list_length (list) == 1)
pspec = list->data;
/* If we are changing a single property, we don't need to push
* an undo if all of the following is true:
* - the redo stack is empty
* - the last item on the undo stack is a text undo
* - the last undo changed the same text property on the same layer
* - the last undo happened less than TEXT_UNDO_TIMEOUT seconds ago
*/
if (pspec)
{
GimpUndo *undo = gimp_image_undo_can_compress (image, GIMP_TYPE_TEXT_UNDO,
GIMP_UNDO_TEXT_LAYER);
if (undo && GIMP_ITEM_UNDO (undo)->item == GIMP_ITEM (layer))
{
GimpTextUndo *text_undo = GIMP_TEXT_UNDO (undo);
if (text_undo->pspec == pspec)
{
if (gimp_undo_get_age (undo) < TEXT_UNDO_TIMEOUT)
{
GimpTool *tool = GIMP_TOOL (text_tool);
GimpContext *context;
context = GIMP_CONTEXT (gimp_tool_get_options (tool));
push_undo = FALSE;
gimp_undo_reset_age (undo);
gimp_undo_refresh_preview (undo, context);
}
}
}
}
if (push_undo)
{
if (layer->modified)
{
undo_group = TRUE;
gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_TEXT, NULL);
gimp_image_undo_push_text_layer_modified (image, NULL, layer);
/* see comment in gimp_text_layer_set() */
gimp_image_undo_push_drawable_mod (image, NULL,
GIMP_DRAWABLE (layer), TRUE);
}
gimp_image_undo_push_text_layer (image, NULL, layer, pspec);
}
src = G_OBJECT (text_tool->proxy);
dest = G_OBJECT (text_tool->text);
g_signal_handlers_block_by_func (dest,
gimp_text_tool_text_notify,
text_tool);
g_signal_handlers_block_by_func (dest,
gimp_text_tool_text_changed,
text_tool);
g_object_freeze_notify (dest);
for (; list; list = g_list_next (list))
{
GValue value = { 0, };
/* look ahead and compress changes */
if (list->next && list->next->data == list->data)
continue;
pspec = list->data;
g_value_init (&value, pspec->value_type);
g_object_get_property (src, pspec->name, &value);
g_object_set_property (dest, pspec->name, &value);
g_value_unset (&value);
}
g_list_free (text_tool->pending);
text_tool->pending = NULL;
g_object_thaw_notify (dest);
g_signal_handlers_unblock_by_func (dest,
gimp_text_tool_text_notify,
text_tool);
g_signal_handlers_unblock_by_func (dest,
gimp_text_tool_text_changed,
text_tool);
if (push_undo)
{
g_object_set (layer, "modified", FALSE, NULL);
if (undo_group)
gimp_image_undo_group_end (image);
}
gimp_text_tool_frame_item (text_tool);
gimp_image_flush (image);
gimp_text_tool_unblock_drawing (text_tool);
return FALSE;
}
static void
gimp_text_tool_create_layer (GimpTextTool *text_tool,
GimpText *text)
{
GimpRectangleTool *rect_tool = GIMP_RECTANGLE_TOOL (text_tool);
GimpTool *tool = GIMP_TOOL (text_tool);
GimpImage *image = gimp_display_get_image (tool->display);
GimpLayer *layer;
gint x1, y1;
gint x2, y2;
gimp_text_tool_block_drawing (text_tool);
if (text)
{
text = gimp_config_duplicate (GIMP_CONFIG (text));
}
else
{
gchar *string;
if (gimp_text_buffer_has_markup (text_tool->buffer))
{
string = gimp_text_buffer_get_markup (text_tool->buffer);
g_object_set (text_tool->proxy,
"markup", string,
"box-mode", GIMP_TEXT_BOX_DYNAMIC,
NULL);
}
else
{
string = gimp_text_buffer_get_text (text_tool->buffer);
g_object_set (text_tool->proxy,
"text", string,
"box-mode", GIMP_TEXT_BOX_DYNAMIC,
NULL);
}
g_free (string);
text = gimp_config_duplicate (GIMP_CONFIG (text_tool->proxy));
}
layer = gimp_text_layer_new (image, text);
g_object_unref (text);
if (! layer)
{
gimp_text_tool_unblock_drawing (text_tool);
return;
}
gimp_text_tool_connect (text_tool, GIMP_TEXT_LAYER (layer), text);
gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_TEXT,
_("Add Text Layer"));
if (gimp_image_get_floating_selection (image))
{
g_signal_handlers_block_by_func (image,
gimp_text_tool_layer_changed,
text_tool);
floating_sel_anchor (gimp_image_get_floating_selection (image));
g_signal_handlers_unblock_by_func (image,
gimp_text_tool_layer_changed,
text_tool);
}
g_object_get (rect_tool,
"x1", &x1,
"y1", &y1,
"x2", &x2,
"y2", &y2,
NULL);
gimp_item_set_offset (GIMP_ITEM (layer), x1, y1);
gimp_image_add_layer (image, layer,
GIMP_IMAGE_ACTIVE_PARENT, -1, TRUE);
if (text_tool->text_box_fixed)
{
GimpUnit box_unit = text_tool->proxy->box_unit;
gdouble xres, yres;
gimp_image_get_resolution (image, &xres, &yres);
g_object_set (text_tool->proxy,
"box-mode", GIMP_TEXT_BOX_FIXED,
"box-width", gimp_pixels_to_units (x2 - x1,
box_unit, xres),
"box-height", gimp_pixels_to_units (y2 - y1,
box_unit, yres),
NULL);
gimp_text_tool_apply (text_tool, TRUE); /* unblocks drawing */
}
else
{
gimp_text_tool_frame_item (text_tool);
gimp_text_tool_unblock_drawing (text_tool);
}
gimp_image_undo_group_end (image);
gimp_image_flush (image);
gimp_text_tool_set_drawable (text_tool, GIMP_DRAWABLE (layer), FALSE);
}
#define RESPONSE_NEW 1
static void
gimp_text_tool_confirm_response (GtkWidget *widget,
gint response_id,
GimpTextTool *text_tool)
{
GimpTextLayer *layer = text_tool->layer;
gtk_widget_destroy (widget);
if (layer && layer->text)
{
switch (response_id)
{
case RESPONSE_NEW:
gimp_text_tool_create_layer (text_tool, layer->text);
break;
case GTK_RESPONSE_ACCEPT:
gimp_text_tool_connect (text_tool, layer, layer->text);
/* cause the text layer to be rerendered */
g_object_notify (G_OBJECT (text_tool->proxy), "markup");
gimp_text_tool_editor_start (text_tool);
break;
default:
break;
}
}
}
static void
gimp_text_tool_confirm_dialog (GimpTextTool *text_tool)
{
GimpTool *tool = GIMP_TOOL (text_tool);
GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
GtkWidget *dialog;
GtkWidget *vbox;
GtkWidget *label;
g_return_if_fail (text_tool->layer != NULL);
if (text_tool->confirm_dialog)
{
gtk_window_present (GTK_WINDOW (text_tool->confirm_dialog));
return;
}
dialog = gimp_viewable_dialog_new (GIMP_VIEWABLE (text_tool->layer),
GIMP_CONTEXT (gimp_tool_get_options (tool)),
_("Confirm Text Editing"),
"gimp-text-tool-confirm",
GIMP_STOCK_TEXT_LAYER,
_("Confirm Text Editing"),
GTK_WIDGET (shell),
gimp_standard_help_func, NULL,
_("Create _New Layer"), RESPONSE_NEW,
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
GTK_STOCK_EDIT, GTK_RESPONSE_ACCEPT,
NULL);
gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
RESPONSE_NEW,
GTK_RESPONSE_ACCEPT,
GTK_RESPONSE_CANCEL,
-1);
gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
g_signal_connect (dialog, "response",
G_CALLBACK (gimp_text_tool_confirm_response),
text_tool);
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
gtk_container_set_border_width (GTK_CONTAINER (vbox), 12);
gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
vbox, FALSE, FALSE, 0);
gtk_widget_show (vbox);
label = gtk_label_new (_("The layer you selected is a text layer but "
"it has been modified using other tools. "
"Editing the layer with the text tool will "
"discard these modifications."
"\n\n"
"You can edit the layer or create a new "
"text layer from its text attributes."));
gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
gtk_widget_show (label);
gtk_widget_show (dialog);
text_tool->confirm_dialog = dialog;
g_signal_connect_swapped (dialog, "destroy",
G_CALLBACK (g_nullify_pointer),
&text_tool->confirm_dialog);
}
static void
gimp_text_tool_layer_changed (GimpImage *image,
GimpTextTool *text_tool)
{
GimpLayer *layer = gimp_image_get_active_layer (image);
if (layer == GIMP_LAYER (text_tool->layer))
return;
/* all this stuff doesn't work quite yet, but it's better than before
*/
gimp_draw_tool_pause (GIMP_DRAW_TOOL (text_tool));
gimp_text_tool_editor_halt (text_tool);
gimp_text_tool_clear_layout (text_tool);
if (gimp_draw_tool_is_active (GIMP_DRAW_TOOL (text_tool)))
gimp_draw_tool_stop (GIMP_DRAW_TOOL (text_tool));
if (gimp_text_tool_set_drawable (text_tool, GIMP_DRAWABLE (layer), FALSE) &&
GIMP_LAYER (text_tool->layer) == layer)
{
gimp_draw_tool_start (GIMP_DRAW_TOOL (text_tool),
GIMP_TOOL (text_tool)->display);
gimp_text_tool_frame_item (text_tool);
gimp_text_tool_editor_start (text_tool);
gimp_text_tool_editor_position (text_tool);
}
else
{
gimp_tool_control (GIMP_TOOL (text_tool), GIMP_TOOL_ACTION_HALT,
GIMP_TOOL (text_tool)->display);
}
gimp_draw_tool_resume (GIMP_DRAW_TOOL (text_tool));
}
static void
gimp_text_tool_set_image (GimpTextTool *text_tool,
GimpImage *image)
{
if (text_tool->image == image)
return;
if (text_tool->image)
{
g_signal_handlers_disconnect_by_func (text_tool->image,
gimp_text_tool_layer_changed,
text_tool);
g_object_remove_weak_pointer (G_OBJECT (text_tool->image),
(gpointer) &text_tool->image);
}
text_tool->image = image;
if (image)
{
GimpTextOptions *options = GIMP_TEXT_TOOL_GET_OPTIONS (text_tool);
gdouble xres;
gdouble yres;
g_object_add_weak_pointer (G_OBJECT (text_tool->image),
(gpointer) &text_tool->image);
g_signal_connect_object (text_tool->image, "active-layer-changed",
G_CALLBACK (gimp_text_tool_layer_changed),
text_tool, 0);
gimp_image_get_resolution (image, &xres, &yres);
gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (options->size_entry), 0,
yres, FALSE);
}
}
static gboolean
gimp_text_tool_set_drawable (GimpTextTool *text_tool,
GimpDrawable *drawable,
gboolean confirm)
{
GimpImage *image = NULL;
if (text_tool->confirm_dialog)
gtk_widget_destroy (text_tool->confirm_dialog);
if (drawable)
image = gimp_item_get_image (GIMP_ITEM (drawable));
gimp_text_tool_set_image (text_tool, image);
if (GIMP_IS_TEXT_LAYER (drawable) && GIMP_TEXT_LAYER (drawable)->text)
{
GimpTextLayer *layer = GIMP_TEXT_LAYER (drawable);
if (layer == text_tool->layer && layer->text == text_tool->text)
return TRUE;
if (layer->modified)
{
if (confirm)
{
gimp_text_tool_connect (text_tool, layer, NULL);
gimp_text_tool_confirm_dialog (text_tool);
return TRUE;
}
}
else
{
gimp_text_tool_connect (text_tool, layer, layer->text);
return TRUE;
}
}
gimp_text_tool_connect (text_tool, NULL, NULL);
return FALSE;
}
static void
gimp_text_tool_block_drawing (GimpTextTool *text_tool)
{
if (! text_tool->drawing_blocked)
{
gimp_draw_tool_pause (GIMP_DRAW_TOOL (text_tool));
gimp_text_tool_clear_layout (text_tool);
text_tool->drawing_blocked = TRUE;
}
}
static void
gimp_text_tool_unblock_drawing (GimpTextTool *text_tool)
{
g_return_if_fail (text_tool->drawing_blocked == TRUE);
text_tool->drawing_blocked = FALSE;
gimp_draw_tool_resume (GIMP_DRAW_TOOL (text_tool));
}
static void
gimp_text_tool_buffer_begin_edit (GimpTextBuffer *buffer,
GimpTextTool *text_tool)
{
gimp_text_tool_block_drawing (text_tool);
}
static void
gimp_text_tool_buffer_end_edit (GimpTextBuffer *buffer,
GimpTextTool *text_tool)
{
if (text_tool->text)
{
gchar *string;
if (gimp_text_buffer_has_markup (buffer))
{
string = gimp_text_buffer_get_markup (buffer);
g_object_set (text_tool->proxy,
"markup", string,
NULL);
}
else
{
string = gimp_text_buffer_get_text (buffer);
g_object_set (text_tool->proxy,
"text", string,
NULL);
}
g_free (string);
}
else
{
gimp_text_tool_create_layer (text_tool, NULL);
}
}
/* public functions */
void
gimp_text_tool_clear_layout (GimpTextTool *text_tool)
{
if (text_tool->layout)
{
g_object_unref (text_tool->layout);
text_tool->layout = NULL;
}
}
gboolean
gimp_text_tool_ensure_layout (GimpTextTool *text_tool)
{
if (! text_tool->layout && text_tool->text)
{
GimpImage *image = gimp_item_get_image (GIMP_ITEM (text_tool->layer));
gdouble xres;
gdouble yres;
gimp_image_get_resolution (image, &xres, &yres);
text_tool->layout = gimp_text_layout_new (text_tool->layer->text,
xres, yres);
}
return text_tool->layout != NULL;
}
void
gimp_text_tool_set_layer (GimpTextTool *text_tool,
GimpLayer *layer)
{
g_return_if_fail (GIMP_IS_TEXT_TOOL (text_tool));
g_return_if_fail (layer == NULL || GIMP_IS_LAYER (layer));
if (gimp_text_tool_set_drawable (text_tool, GIMP_DRAWABLE (layer), TRUE))
{
GimpTool *tool = GIMP_TOOL (text_tool);
GimpItem *item = GIMP_ITEM (layer);
GimpContext *context;
GimpDisplay *display;
context = gimp_get_user_context (tool->tool_info->gimp);
display = gimp_context_get_display (context);
if (! display ||
gimp_display_get_image (display) != gimp_item_get_image (item))
{
GList *list;
display = NULL;
for (list = gimp_get_display_iter (tool->tool_info->gimp);
list;
list = g_list_next (list))
{
display = list->data;
if (gimp_display_get_image (display) == gimp_item_get_image (item))
{
gimp_context_set_display (context, display);
break;
}
display = NULL;
}
}
tool->display = display;
if (tool->display)
{
GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);
tool->drawable = GIMP_DRAWABLE (layer);
if (gimp_draw_tool_is_active (draw_tool))
gimp_draw_tool_stop (draw_tool);
gimp_draw_tool_start (draw_tool, display);
gimp_text_tool_frame_item (text_tool);
gimp_text_tool_editor_start (text_tool);
}
}
}
gboolean
gimp_text_tool_get_has_text_selection (GimpTextTool *text_tool)
{
GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
return gtk_text_buffer_get_has_selection (buffer);
}
void
gimp_text_tool_delete_selection (GimpTextTool *text_tool)
{
GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
if (gtk_text_buffer_get_has_selection (buffer))
{
gtk_text_buffer_delete_selection (buffer, TRUE, TRUE);
}
}
void
gimp_text_tool_cut_clipboard (GimpTextTool *text_tool)
{
GimpDisplayShell *shell;
GtkClipboard *clipboard;
g_return_if_fail (GIMP_IS_TEXT_TOOL (text_tool));
shell = gimp_display_get_shell (GIMP_TOOL (text_tool)->display);
clipboard = gtk_widget_get_clipboard (GTK_WIDGET (shell),
GDK_SELECTION_CLIPBOARD);
gtk_text_buffer_cut_clipboard (GTK_TEXT_BUFFER (text_tool->buffer),
clipboard, TRUE);
}
void
gimp_text_tool_copy_clipboard (GimpTextTool *text_tool)
{
GimpDisplayShell *shell;
GtkClipboard *clipboard;
g_return_if_fail (GIMP_IS_TEXT_TOOL (text_tool));
shell = gimp_display_get_shell (GIMP_TOOL (text_tool)->display);
clipboard = gtk_widget_get_clipboard (GTK_WIDGET (shell),
GDK_SELECTION_CLIPBOARD);
/* need to block "end-user-action" on the text buffer, because
* GtkTextBuffer considers copying text to the clipboard an
* undo-relevant user action, which is clearly a bug, but what
* can we do...
*/
g_signal_handlers_block_by_func (text_tool->buffer,
gimp_text_tool_buffer_begin_edit,
text_tool);
g_signal_handlers_block_by_func (text_tool->buffer,
gimp_text_tool_buffer_end_edit,
text_tool);
gtk_text_buffer_copy_clipboard (GTK_TEXT_BUFFER (text_tool->buffer),
clipboard);
g_signal_handlers_unblock_by_func (text_tool->buffer,
gimp_text_tool_buffer_end_edit,
text_tool);
g_signal_handlers_unblock_by_func (text_tool->buffer,
gimp_text_tool_buffer_begin_edit,
text_tool);
}
void
gimp_text_tool_paste_clipboard (GimpTextTool *text_tool)
{
GimpDisplayShell *shell;
GtkClipboard *clipboard;
g_return_if_fail (GIMP_IS_TEXT_TOOL (text_tool));
shell = gimp_display_get_shell (GIMP_TOOL (text_tool)->display);
clipboard = gtk_widget_get_clipboard (GTK_WIDGET (shell),
GDK_SELECTION_CLIPBOARD);
gtk_text_buffer_paste_clipboard (GTK_TEXT_BUFFER (text_tool->buffer),
clipboard, NULL, TRUE);
}
void
gimp_text_tool_create_vectors (GimpTextTool *text_tool)
{
GimpVectors *vectors;
g_return_if_fail (GIMP_IS_TEXT_TOOL (text_tool));
if (! text_tool->text || ! text_tool->image)
return;
vectors = gimp_text_vectors_new (text_tool->image, text_tool->text);
if (text_tool->layer)
{
gint x, y;
gimp_item_get_offset (GIMP_ITEM (text_tool->layer), &x, &y);
gimp_item_translate (GIMP_ITEM (vectors), x, y, FALSE);
}
gimp_image_add_vectors (text_tool->image, vectors,
GIMP_IMAGE_ACTIVE_PARENT, -1, TRUE);
gimp_image_flush (text_tool->image);
}
void
gimp_text_tool_create_vectors_warped (GimpTextTool *text_tool)
{
GimpVectors *vectors0;
GimpVectors *vectors;
gdouble box_height;
g_return_if_fail (GIMP_IS_TEXT_TOOL (text_tool));
if (! text_tool->text || ! text_tool->image || ! text_tool->layer)
return;
box_height = gimp_item_get_height (GIMP_ITEM (text_tool->layer));
vectors0 = gimp_image_get_active_vectors (text_tool->image);
if (! vectors0)
return;
vectors = gimp_text_vectors_new (text_tool->image, text_tool->text);
gimp_vectors_warp_vectors (vectors0, vectors, 0.5 * box_height);
gimp_item_set_visible (GIMP_ITEM (vectors), TRUE, FALSE);
gimp_image_add_vectors (text_tool->image, vectors,
GIMP_IMAGE_ACTIVE_PARENT, -1, TRUE);
gimp_image_flush (text_tool->image);
}