2404 lines
78 KiB
C
2404 lines
78 KiB
C
/* PIKA - Photo and Image Kooker Application
|
|
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
|
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
|
*
|
|
* Original copyright, applying to most contents (license remains unchanged):
|
|
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
|
*
|
|
* PikaTextTool
|
|
* 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 <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <gegl.h>
|
|
#include <gtk/gtk.h>
|
|
|
|
#include "libpikabase/pikabase.h"
|
|
#include "libpikaconfig/pikaconfig.h"
|
|
#include "libpikawidgets/pikawidgets.h"
|
|
|
|
#include "tools-types.h"
|
|
|
|
#include "core/pika.h"
|
|
#include "core/pikaasyncset.h"
|
|
#include "core/pikacontext.h"
|
|
#include "core/pikadatafactory.h"
|
|
#include "core/pikaerror.h"
|
|
#include "core/pikaimage.h"
|
|
#include "core/pika-palettes.h"
|
|
#include "core/pikaimage-pick-item.h"
|
|
#include "core/pikaimage-undo.h"
|
|
#include "core/pikaimage-undo-push.h"
|
|
#include "core/pikalayer-floating-selection.h"
|
|
#include "core/pikatoolinfo.h"
|
|
#include "core/pikaundostack.h"
|
|
|
|
#include "menus/menus.h"
|
|
|
|
#include "text/pikatext.h"
|
|
#include "text/pikatext-vectors.h"
|
|
#include "text/pikatextlayer.h"
|
|
#include "text/pikatextlayout.h"
|
|
#include "text/pikatextundo.h"
|
|
|
|
#include "vectors/pikastroke.h"
|
|
#include "vectors/pikavectors.h"
|
|
#include "vectors/pikavectors-warp.h"
|
|
|
|
#include "widgets/pikadialogfactory.h"
|
|
#include "widgets/pikadockcontainer.h"
|
|
#include "widgets/pikahelp-ids.h"
|
|
#include "widgets/pikamenufactory.h"
|
|
#include "widgets/pikatextbuffer.h"
|
|
#include "widgets/pikauimanager.h"
|
|
#include "widgets/pikaviewabledialog.h"
|
|
|
|
#include "display/pikacanvasgroup.h"
|
|
#include "display/pikadisplay.h"
|
|
#include "display/pikadisplayshell.h"
|
|
#include "display/pikatoolrectangle.h"
|
|
|
|
#include "pikatextoptions.h"
|
|
#include "pikatexttool.h"
|
|
#include "pikatexttool-editor.h"
|
|
#include "pikatoolcontrol.h"
|
|
|
|
#include "pika-intl.h"
|
|
|
|
|
|
#define TEXT_UNDO_TIMEOUT 3
|
|
|
|
|
|
/* local function prototypes */
|
|
|
|
static void pika_text_tool_constructed (GObject *object);
|
|
static void pika_text_tool_finalize (GObject *object);
|
|
|
|
static void pika_text_tool_control (PikaTool *tool,
|
|
PikaToolAction action,
|
|
PikaDisplay *display);
|
|
static void pika_text_tool_button_press (PikaTool *tool,
|
|
const PikaCoords *coords,
|
|
guint32 time,
|
|
GdkModifierType state,
|
|
PikaButtonPressType press_type,
|
|
PikaDisplay *display);
|
|
static void pika_text_tool_button_release (PikaTool *tool,
|
|
const PikaCoords *coords,
|
|
guint32 time,
|
|
GdkModifierType state,
|
|
PikaButtonReleaseType release_type,
|
|
PikaDisplay *display);
|
|
static void pika_text_tool_motion (PikaTool *tool,
|
|
const PikaCoords *coords,
|
|
guint32 time,
|
|
GdkModifierType state,
|
|
PikaDisplay *display);
|
|
static gboolean pika_text_tool_key_press (PikaTool *tool,
|
|
GdkEventKey *kevent,
|
|
PikaDisplay *display);
|
|
static gboolean pika_text_tool_key_release (PikaTool *tool,
|
|
GdkEventKey *kevent,
|
|
PikaDisplay *display);
|
|
static void pika_text_tool_oper_update (PikaTool *tool,
|
|
const PikaCoords *coords,
|
|
GdkModifierType state,
|
|
gboolean proximity,
|
|
PikaDisplay *display);
|
|
static void pika_text_tool_cursor_update (PikaTool *tool,
|
|
const PikaCoords *coords,
|
|
GdkModifierType state,
|
|
PikaDisplay *display);
|
|
static PikaUIManager * pika_text_tool_get_popup (PikaTool *tool,
|
|
const PikaCoords *coords,
|
|
GdkModifierType state,
|
|
PikaDisplay *display,
|
|
const gchar **ui_path);
|
|
|
|
static void pika_text_tool_draw (PikaDrawTool *draw_tool);
|
|
static void pika_text_tool_draw_selection (PikaDrawTool *draw_tool);
|
|
|
|
static gboolean pika_text_tool_start (PikaTextTool *text_tool,
|
|
PikaDisplay *display,
|
|
PikaLayer *layer,
|
|
GError **error);
|
|
static void pika_text_tool_halt (PikaTextTool *text_tool);
|
|
|
|
static void pika_text_tool_frame_item (PikaTextTool *text_tool);
|
|
|
|
static void pika_text_tool_rectangle_response
|
|
(PikaToolRectangle *rectangle,
|
|
gint response_id,
|
|
PikaTextTool *text_tool);
|
|
static void pika_text_tool_rectangle_change_complete
|
|
(PikaToolRectangle *rectangle,
|
|
PikaTextTool *text_tool);
|
|
|
|
static void pika_text_tool_connect (PikaTextTool *text_tool,
|
|
PikaTextLayer *layer,
|
|
PikaText *text);
|
|
|
|
static void pika_text_tool_layer_notify (PikaTextLayer *layer,
|
|
const GParamSpec *pspec,
|
|
PikaTextTool *text_tool);
|
|
static void pika_text_tool_proxy_notify (PikaText *text,
|
|
const GParamSpec *pspec,
|
|
PikaTextTool *text_tool);
|
|
|
|
static void pika_text_tool_text_notify (PikaText *text,
|
|
const GParamSpec *pspec,
|
|
PikaTextTool *text_tool);
|
|
static void pika_text_tool_text_changed (PikaText *text,
|
|
PikaTextTool *text_tool);
|
|
|
|
static void
|
|
pika_text_tool_fonts_async_set_empty_notify (PikaAsyncSet *async_set,
|
|
GParamSpec *pspec,
|
|
PikaTextTool *text_tool);
|
|
|
|
static void pika_text_tool_apply_list (PikaTextTool *text_tool,
|
|
GList *pspecs);
|
|
|
|
static void pika_text_tool_create_layer (PikaTextTool *text_tool,
|
|
PikaText *text);
|
|
|
|
static void pika_text_tool_layer_changed (PikaImage *image,
|
|
PikaTextTool *text_tool);
|
|
static void pika_text_tool_set_image (PikaTextTool *text_tool,
|
|
PikaImage *image);
|
|
static gboolean pika_text_tool_set_drawable (PikaTextTool *text_tool,
|
|
PikaDrawable *drawable,
|
|
gboolean confirm);
|
|
|
|
static void pika_text_tool_block_drawing (PikaTextTool *text_tool);
|
|
static void pika_text_tool_unblock_drawing (PikaTextTool *text_tool);
|
|
|
|
static void pika_text_tool_buffer_begin_edit (PikaTextBuffer *buffer,
|
|
PikaTextTool *text_tool);
|
|
static void pika_text_tool_buffer_end_edit (PikaTextBuffer *buffer,
|
|
PikaTextTool *text_tool);
|
|
|
|
static void pika_text_tool_buffer_color_applied
|
|
(PikaTextBuffer *buffer,
|
|
const PikaRGB *color,
|
|
PikaTextTool *text_tool);
|
|
|
|
|
|
G_DEFINE_TYPE (PikaTextTool, pika_text_tool, PIKA_TYPE_DRAW_TOOL)
|
|
|
|
#define parent_class pika_text_tool_parent_class
|
|
|
|
|
|
void
|
|
pika_text_tool_register (PikaToolRegisterCallback callback,
|
|
gpointer data)
|
|
{
|
|
(* callback) (PIKA_TYPE_TEXT_TOOL,
|
|
PIKA_TYPE_TEXT_OPTIONS,
|
|
pika_text_options_gui,
|
|
PIKA_CONTEXT_PROP_MASK_FOREGROUND |
|
|
PIKA_CONTEXT_PROP_MASK_FONT |
|
|
PIKA_CONTEXT_PROP_MASK_PALETTE /* for the color popup's palette tab */,
|
|
"pika-text-tool",
|
|
_("Text"),
|
|
_("Text Tool: Create or edit text layers"),
|
|
N_("Te_xt"), "T",
|
|
NULL, PIKA_HELP_TOOL_TEXT,
|
|
PIKA_ICON_TOOL_TEXT,
|
|
data);
|
|
}
|
|
|
|
static void
|
|
pika_text_tool_class_init (PikaTextToolClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
PikaToolClass *tool_class = PIKA_TOOL_CLASS (klass);
|
|
PikaDrawToolClass *draw_tool_class = PIKA_DRAW_TOOL_CLASS (klass);
|
|
|
|
object_class->constructed = pika_text_tool_constructed;
|
|
object_class->finalize = pika_text_tool_finalize;
|
|
|
|
tool_class->control = pika_text_tool_control;
|
|
tool_class->button_press = pika_text_tool_button_press;
|
|
tool_class->motion = pika_text_tool_motion;
|
|
tool_class->button_release = pika_text_tool_button_release;
|
|
tool_class->key_press = pika_text_tool_key_press;
|
|
tool_class->key_release = pika_text_tool_key_release;
|
|
tool_class->oper_update = pika_text_tool_oper_update;
|
|
tool_class->cursor_update = pika_text_tool_cursor_update;
|
|
tool_class->get_popup = pika_text_tool_get_popup;
|
|
|
|
draw_tool_class->draw = pika_text_tool_draw;
|
|
}
|
|
|
|
static void
|
|
pika_text_tool_init (PikaTextTool *text_tool)
|
|
{
|
|
PikaTool *tool = PIKA_TOOL (text_tool);
|
|
|
|
text_tool->buffer = pika_text_buffer_new ();
|
|
|
|
g_signal_connect (text_tool->buffer, "begin-user-action",
|
|
G_CALLBACK (pika_text_tool_buffer_begin_edit),
|
|
text_tool);
|
|
g_signal_connect (text_tool->buffer, "end-user-action",
|
|
G_CALLBACK (pika_text_tool_buffer_end_edit),
|
|
text_tool);
|
|
g_signal_connect (text_tool->buffer, "color-applied",
|
|
G_CALLBACK (pika_text_tool_buffer_color_applied),
|
|
text_tool);
|
|
|
|
text_tool->handle_rectangle_change_complete = TRUE;
|
|
|
|
pika_text_tool_editor_init (text_tool);
|
|
|
|
pika_tool_control_set_scroll_lock (tool->control, TRUE);
|
|
pika_tool_control_set_handle_empty_image (tool->control, TRUE);
|
|
pika_tool_control_set_wants_click (tool->control, TRUE);
|
|
pika_tool_control_set_wants_double_click (tool->control, TRUE);
|
|
pika_tool_control_set_wants_triple_click (tool->control, TRUE);
|
|
pika_tool_control_set_wants_all_key_events (tool->control, TRUE);
|
|
pika_tool_control_set_active_modifiers (tool->control,
|
|
PIKA_TOOL_ACTIVE_MODIFIERS_SEPARATE);
|
|
pika_tool_control_set_precision (tool->control,
|
|
PIKA_CURSOR_PRECISION_PIXEL_BORDER);
|
|
pika_tool_control_set_tool_cursor (tool->control,
|
|
PIKA_TOOL_CURSOR_TEXT);
|
|
pika_tool_control_set_action_object_1 (tool->control,
|
|
"context-font-select-set");
|
|
}
|
|
|
|
static void
|
|
pika_text_tool_constructed (GObject *object)
|
|
{
|
|
PikaTextTool *text_tool = PIKA_TEXT_TOOL (object);
|
|
PikaTextOptions *options = PIKA_TEXT_TOOL_GET_OPTIONS (text_tool);
|
|
PikaTool *tool = PIKA_TOOL (text_tool);
|
|
PikaAsyncSet *async_set;
|
|
|
|
G_OBJECT_CLASS (parent_class)->constructed (object);
|
|
|
|
text_tool->proxy = g_object_new (PIKA_TYPE_TEXT, "pika", tool->tool_info->pika, NULL);
|
|
|
|
pika_text_options_connect_text (options, text_tool->proxy);
|
|
|
|
g_signal_connect_object (text_tool->proxy, "notify",
|
|
G_CALLBACK (pika_text_tool_proxy_notify),
|
|
text_tool, 0);
|
|
|
|
async_set =
|
|
pika_data_factory_get_async_set (tool->tool_info->pika->font_factory);
|
|
|
|
g_signal_connect_object (async_set,
|
|
"notify::empty",
|
|
G_CALLBACK (pika_text_tool_fonts_async_set_empty_notify),
|
|
text_tool, 0);
|
|
}
|
|
|
|
static void
|
|
pika_text_tool_finalize (GObject *object)
|
|
{
|
|
PikaTextTool *text_tool = PIKA_TEXT_TOOL (object);
|
|
|
|
g_clear_object (&text_tool->proxy);
|
|
g_clear_object (&text_tool->buffer);
|
|
|
|
pika_text_tool_editor_finalize (text_tool);
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
pika_text_tool_remove_empty_text_layer (PikaTextTool *text_tool)
|
|
{
|
|
PikaTextLayer *text_layer = text_tool->layer;
|
|
|
|
if (text_layer && text_layer->auto_rename)
|
|
{
|
|
PikaText *text = pika_text_layer_get_text (text_layer);
|
|
|
|
if (text && text->box_mode == PIKA_TEXT_BOX_DYNAMIC &&
|
|
(! text->text || text->text[0] == '\0') &&
|
|
(! text->markup || text->markup[0] == '\0'))
|
|
{
|
|
PikaImage *image = pika_item_get_image (PIKA_ITEM (text_layer));
|
|
|
|
if (text_tool->image == image)
|
|
g_signal_handlers_block_by_func (image,
|
|
pika_text_tool_layer_changed,
|
|
text_tool);
|
|
|
|
pika_image_remove_layer (image, PIKA_LAYER (text_layer), TRUE, NULL);
|
|
pika_image_flush (image);
|
|
|
|
if (text_tool->image == image)
|
|
g_signal_handlers_unblock_by_func (image,
|
|
pika_text_tool_layer_changed,
|
|
text_tool);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
pika_text_tool_control (PikaTool *tool,
|
|
PikaToolAction action,
|
|
PikaDisplay *display)
|
|
{
|
|
PikaTextTool *text_tool = PIKA_TEXT_TOOL (tool);
|
|
|
|
switch (action)
|
|
{
|
|
case PIKA_TOOL_ACTION_PAUSE:
|
|
case PIKA_TOOL_ACTION_RESUME:
|
|
break;
|
|
|
|
case PIKA_TOOL_ACTION_HALT:
|
|
pika_text_tool_halt (text_tool);
|
|
break;
|
|
|
|
case PIKA_TOOL_ACTION_COMMIT:
|
|
break;
|
|
}
|
|
|
|
PIKA_TOOL_CLASS (parent_class)->control (tool, action, display);
|
|
}
|
|
|
|
static void
|
|
pika_text_tool_button_press (PikaTool *tool,
|
|
const PikaCoords *coords,
|
|
guint32 time,
|
|
GdkModifierType state,
|
|
PikaButtonPressType press_type,
|
|
PikaDisplay *display)
|
|
{
|
|
PikaTextTool *text_tool = PIKA_TEXT_TOOL (tool);
|
|
PikaImage *image = pika_display_get_image (display);
|
|
PikaText *text = text_tool->text;
|
|
PikaToolRectangle *rectangle;
|
|
|
|
pika_draw_tool_pause (PIKA_DRAW_TOOL (tool));
|
|
|
|
if (tool->display && tool->display != display)
|
|
pika_tool_control (tool, PIKA_TOOL_ACTION_HALT, display);
|
|
|
|
if (! text_tool->widget)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (! pika_text_tool_start (text_tool, display, NULL, &error))
|
|
{
|
|
pika_draw_tool_resume (PIKA_DRAW_TOOL (tool));
|
|
|
|
pika_tool_message_literal (tool, display, error->message);
|
|
|
|
g_clear_error (&error);
|
|
|
|
return;
|
|
}
|
|
|
|
pika_tool_widget_hover (text_tool->widget, coords, state, TRUE);
|
|
|
|
/* HACK: force CREATING on a newly created rectangle; otherwise,
|
|
* the above binding of properties would cause the rectangle to
|
|
* start with the size from tool options.
|
|
*/
|
|
pika_tool_rectangle_set_function (PIKA_TOOL_RECTANGLE (text_tool->widget),
|
|
PIKA_TOOL_RECTANGLE_CREATING);
|
|
}
|
|
|
|
rectangle = PIKA_TOOL_RECTANGLE (text_tool->widget);
|
|
|
|
if (press_type == PIKA_BUTTON_PRESS_NORMAL)
|
|
{
|
|
pika_tool_control_activate (tool->control);
|
|
|
|
/* clicking anywhere while a preedit is going on aborts the
|
|
* preedit, this is ugly but at least leaves everything in
|
|
* a consistent state
|
|
*/
|
|
if (text_tool->preedit_active)
|
|
pika_text_tool_abort_im_context (text_tool);
|
|
else
|
|
pika_text_tool_reset_im_context (text_tool);
|
|
|
|
text_tool->selecting = FALSE;
|
|
|
|
if (pika_tool_rectangle_point_in_rectangle (rectangle,
|
|
coords->x,
|
|
coords->y) &&
|
|
! text_tool->moving)
|
|
{
|
|
pika_tool_rectangle_set_function (rectangle,
|
|
PIKA_TOOL_RECTANGLE_DEAD);
|
|
}
|
|
else if (pika_tool_widget_button_press (text_tool->widget, coords,
|
|
time, state, press_type))
|
|
{
|
|
text_tool->grab_widget = text_tool->widget;
|
|
}
|
|
|
|
/* bail out now if the user user clicked on a handle of an
|
|
* existing rectangle, but not inside an existing framed layer
|
|
*/
|
|
if (pika_tool_rectangle_get_function (rectangle) !=
|
|
PIKA_TOOL_RECTANGLE_CREATING)
|
|
{
|
|
if (text_tool->layer)
|
|
{
|
|
PikaItem *item = PIKA_ITEM (text_tool->layer);
|
|
gdouble x = coords->x - pika_item_get_offset_x (item);
|
|
gdouble y = coords->y - pika_item_get_offset_y (item);
|
|
|
|
if (x < 0 || x >= pika_item_get_width (item) ||
|
|
y < 0 || y >= pika_item_get_height (item))
|
|
{
|
|
pika_draw_tool_resume (PIKA_DRAW_TOOL (tool));
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pika_draw_tool_resume (PIKA_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 &&
|
|
pika_tool_rectangle_get_function (rectangle) ==
|
|
PIKA_TOOL_RECTANGLE_CREATING)
|
|
{
|
|
PikaTextLayer *text_layer;
|
|
|
|
text_layer = pika_image_pick_text_layer (image, coords->x, coords->y);
|
|
|
|
if (text_layer && text_layer != text_tool->layer)
|
|
{
|
|
GList *selection = g_list_prepend (NULL, text_layer);
|
|
|
|
if (text_tool->image == image)
|
|
g_signal_handlers_block_by_func (image,
|
|
pika_text_tool_layer_changed,
|
|
text_tool);
|
|
|
|
pika_image_set_selected_layers (image, selection);
|
|
g_list_free (selection);
|
|
|
|
if (text_tool->image == image)
|
|
g_signal_handlers_unblock_by_func (image,
|
|
pika_text_tool_layer_changed,
|
|
text_tool);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pika_image_coords_in_active_pickable (image, coords, FALSE, FALSE, FALSE))
|
|
{
|
|
GList *drawables = pika_image_get_selected_drawables (image);
|
|
PikaDrawable *drawable = NULL;
|
|
gdouble x = coords->x;
|
|
gdouble y = coords->y;
|
|
|
|
if (g_list_length (drawables) == 1)
|
|
{
|
|
PikaItem *item = PIKA_ITEM (drawables->data);
|
|
|
|
x = coords->x - pika_item_get_offset_x (item);
|
|
y = coords->y - pika_item_get_offset_y (item);
|
|
|
|
drawable = drawables->data;
|
|
}
|
|
g_list_free (drawables);
|
|
|
|
/* did the user click on a text layer? */
|
|
if (drawable &&
|
|
pika_text_tool_set_drawable (text_tool, drawable, TRUE))
|
|
{
|
|
if (press_type == PIKA_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 (pika_tool_rectangle_get_function (rectangle) ==
|
|
PIKA_TOOL_RECTANGLE_CREATING)
|
|
{
|
|
pika_tool_rectangle_set_function (rectangle,
|
|
PIKA_TOOL_RECTANGLE_DEAD);
|
|
|
|
pika_text_tool_frame_item (text_tool);
|
|
}
|
|
|
|
if (text_tool->text && text_tool->text != text)
|
|
{
|
|
pika_text_tool_editor_start (text_tool);
|
|
}
|
|
}
|
|
|
|
if (text_tool->text && ! text_tool->moving)
|
|
{
|
|
text_tool->selecting = TRUE;
|
|
|
|
pika_text_tool_editor_button_press (text_tool, x, y, press_type);
|
|
}
|
|
else
|
|
{
|
|
text_tool->selecting = FALSE;
|
|
}
|
|
|
|
pika_draw_tool_resume (PIKA_DRAW_TOOL (tool));
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (press_type == PIKA_BUTTON_PRESS_NORMAL)
|
|
{
|
|
/* create a new text layer */
|
|
text_tool->text_box_fixed = FALSE;
|
|
|
|
/* make sure the text tool has an image, even if the user didn't click
|
|
* inside the active drawable, in particular, so that the text style
|
|
* editor picks the correct resolution.
|
|
*/
|
|
pika_text_tool_set_image (text_tool, image);
|
|
|
|
pika_text_tool_connect (text_tool, NULL, NULL);
|
|
pika_text_tool_editor_start (text_tool);
|
|
}
|
|
|
|
pika_draw_tool_resume (PIKA_DRAW_TOOL (tool));
|
|
}
|
|
|
|
static void
|
|
pika_text_tool_button_release (PikaTool *tool,
|
|
const PikaCoords *coords,
|
|
guint32 time,
|
|
GdkModifierType state,
|
|
PikaButtonReleaseType release_type,
|
|
PikaDisplay *display)
|
|
{
|
|
PikaTextTool *text_tool = PIKA_TEXT_TOOL (tool);
|
|
PikaToolRectangle *rectangle = PIKA_TOOL_RECTANGLE (text_tool->widget);
|
|
|
|
pika_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,
|
|
pika_text_tool_buffer_begin_edit,
|
|
text_tool);
|
|
g_signal_handlers_block_by_func (text_tool->buffer,
|
|
pika_text_tool_buffer_end_edit,
|
|
text_tool);
|
|
|
|
pika_text_tool_editor_button_release (text_tool);
|
|
|
|
g_signal_handlers_unblock_by_func (text_tool->buffer,
|
|
pika_text_tool_buffer_end_edit,
|
|
text_tool);
|
|
g_signal_handlers_unblock_by_func (text_tool->buffer,
|
|
pika_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 == PIKA_BUTTON_RELEASE_CANCEL)
|
|
release_type = PIKA_BUTTON_RELEASE_NORMAL;
|
|
}
|
|
else if (text_tool->moving)
|
|
{
|
|
/* the user has moved the text layer with Alt-drag, fall
|
|
* through and let rectangle-change-complete do its job of
|
|
* setting text layer's new position.
|
|
*/
|
|
}
|
|
else if (pika_tool_rectangle_get_function (rectangle) ==
|
|
PIKA_TOOL_RECTANGLE_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 == PIKA_BUTTON_RELEASE_CANCEL)
|
|
{
|
|
/* user has canceled the rectangle resizing, fall through
|
|
* and let the rectangle handle restoring the previous size
|
|
*/
|
|
}
|
|
else
|
|
{
|
|
gdouble x1, y1;
|
|
gdouble 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 (rectangle,
|
|
"x1", &x1,
|
|
"y1", &y1,
|
|
"x2", &x2,
|
|
"y2", &y2,
|
|
NULL);
|
|
|
|
if (release_type == PIKA_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", PIKA_TEXT_BOX_DYNAMIC,
|
|
NULL);
|
|
|
|
text_tool->handle_rectangle_change_complete = FALSE;
|
|
}
|
|
}
|
|
|
|
if (text_tool->grab_widget)
|
|
{
|
|
pika_tool_widget_button_release (text_tool->grab_widget,
|
|
coords, time, state, release_type);
|
|
text_tool->grab_widget = NULL;
|
|
}
|
|
|
|
text_tool->handle_rectangle_change_complete = TRUE;
|
|
}
|
|
|
|
static void
|
|
pika_text_tool_motion (PikaTool *tool,
|
|
const PikaCoords *coords,
|
|
guint32 time,
|
|
GdkModifierType state,
|
|
PikaDisplay *display)
|
|
{
|
|
PikaTextTool *text_tool = PIKA_TEXT_TOOL (tool);
|
|
|
|
if (! text_tool->selecting)
|
|
{
|
|
if (text_tool->grab_widget)
|
|
{
|
|
pika_tool_widget_motion (text_tool->grab_widget,
|
|
coords, time, state);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PikaItem *item = PIKA_ITEM (text_tool->layer);
|
|
gdouble x = coords->x - pika_item_get_offset_x (item);
|
|
gdouble y = coords->y - pika_item_get_offset_y (item);
|
|
|
|
pika_text_tool_editor_motion (text_tool, x, y);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
pika_text_tool_key_press (PikaTool *tool,
|
|
GdkEventKey *kevent,
|
|
PikaDisplay *display)
|
|
{
|
|
PikaTextTool *text_tool = PIKA_TEXT_TOOL (tool);
|
|
|
|
if (display == tool->display)
|
|
return pika_text_tool_editor_key_press (text_tool, kevent);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
pika_text_tool_key_release (PikaTool *tool,
|
|
GdkEventKey *kevent,
|
|
PikaDisplay *display)
|
|
{
|
|
PikaTextTool *text_tool = PIKA_TEXT_TOOL (tool);
|
|
|
|
if (display == tool->display)
|
|
return pika_text_tool_editor_key_release (text_tool, kevent);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
pika_text_tool_oper_update (PikaTool *tool,
|
|
const PikaCoords *coords,
|
|
GdkModifierType state,
|
|
gboolean proximity,
|
|
PikaDisplay *display)
|
|
{
|
|
PikaTextTool *text_tool = PIKA_TEXT_TOOL (tool);
|
|
PikaToolRectangle *rectangle = PIKA_TOOL_RECTANGLE (text_tool->widget);
|
|
|
|
PIKA_TOOL_CLASS (parent_class)->oper_update (tool, coords, state,
|
|
proximity, display);
|
|
|
|
text_tool->moving = (text_tool->widget &&
|
|
pika_tool_rectangle_get_function (rectangle) ==
|
|
PIKA_TOOL_RECTANGLE_MOVING &&
|
|
(state & GDK_MOD1_MASK));
|
|
}
|
|
|
|
static void
|
|
pika_text_tool_cursor_update (PikaTool *tool,
|
|
const PikaCoords *coords,
|
|
GdkModifierType state,
|
|
PikaDisplay *display)
|
|
{
|
|
PikaTextTool *text_tool = PIKA_TEXT_TOOL (tool);
|
|
PikaToolRectangle *rectangle = PIKA_TOOL_RECTANGLE (text_tool->widget);
|
|
|
|
if (rectangle && tool->display == display)
|
|
{
|
|
if (pika_tool_rectangle_point_in_rectangle (rectangle,
|
|
coords->x,
|
|
coords->y) &&
|
|
! text_tool->moving)
|
|
{
|
|
pika_tool_set_cursor (tool, display,
|
|
(PikaCursorType) GDK_XTERM,
|
|
pika_tool_control_get_tool_cursor (tool->control),
|
|
PIKA_CURSOR_MODIFIER_NONE);
|
|
}
|
|
else
|
|
{
|
|
PIKA_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state,
|
|
display);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PikaAsyncSet *async_set;
|
|
|
|
async_set =
|
|
pika_data_factory_get_async_set (tool->tool_info->pika->font_factory);
|
|
|
|
if (pika_async_set_is_empty (async_set))
|
|
{
|
|
PIKA_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state,
|
|
display);
|
|
}
|
|
else
|
|
{
|
|
pika_tool_set_cursor (tool, display,
|
|
pika_tool_control_get_cursor (tool->control),
|
|
pika_tool_control_get_tool_cursor (tool->control),
|
|
PIKA_CURSOR_MODIFIER_BAD);
|
|
}
|
|
}
|
|
}
|
|
|
|
static PikaUIManager *
|
|
pika_text_tool_get_popup (PikaTool *tool,
|
|
const PikaCoords *coords,
|
|
GdkModifierType state,
|
|
PikaDisplay *display,
|
|
const gchar **ui_path)
|
|
{
|
|
PikaTextTool *text_tool = PIKA_TEXT_TOOL (tool);
|
|
PikaToolRectangle *rectangle = PIKA_TOOL_RECTANGLE (text_tool->widget);
|
|
|
|
if (rectangle &&
|
|
pika_tool_rectangle_point_in_rectangle (rectangle,
|
|
coords->x,
|
|
coords->y))
|
|
{
|
|
PikaMenuFactory *menu_factory;
|
|
PikaUIManager *ui_manager;
|
|
|
|
menu_factory = menus_get_global_menu_factory (tool->tool_info->pika);
|
|
ui_manager = pika_menu_factory_get_manager (menu_factory, "<TextTool>", text_tool);
|
|
|
|
pika_ui_manager_update (ui_manager, text_tool);
|
|
|
|
*ui_path = "/text-tool-popup";
|
|
|
|
return ui_manager;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
pika_text_tool_draw (PikaDrawTool *draw_tool)
|
|
{
|
|
PikaTextTool *text_tool = PIKA_TEXT_TOOL (draw_tool);
|
|
|
|
PIKA_DRAW_TOOL_CLASS (parent_class)->draw (draw_tool);
|
|
|
|
if (! text_tool->text ||
|
|
! text_tool->layer ||
|
|
! text_tool->layer->text)
|
|
{
|
|
pika_text_tool_editor_update_im_cursor (text_tool);
|
|
|
|
return;
|
|
}
|
|
|
|
pika_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 */
|
|
|
|
pika_text_tool_draw_selection (draw_tool);
|
|
}
|
|
else
|
|
{
|
|
/* If the text buffer has no selection, draw the text cursor */
|
|
|
|
PikaCanvasItem *item;
|
|
PangoRectangle cursor_rect;
|
|
gint off_x, off_y;
|
|
gboolean overwrite;
|
|
PikaTextDirection direction;
|
|
|
|
pika_text_tool_editor_get_cursor_rect (text_tool,
|
|
text_tool->overwrite_mode,
|
|
&cursor_rect);
|
|
|
|
pika_item_get_offset (PIKA_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;
|
|
|
|
direction = pika_text_tool_get_direction (text_tool);
|
|
|
|
item = pika_draw_tool_add_text_cursor (draw_tool, &cursor_rect,
|
|
overwrite, direction);
|
|
pika_canvas_item_set_highlight (item, TRUE);
|
|
}
|
|
|
|
pika_text_tool_editor_update_im_cursor (text_tool);
|
|
}
|
|
|
|
static void
|
|
pika_text_tool_draw_selection (PikaDrawTool *draw_tool)
|
|
{
|
|
PikaTextTool *text_tool = PIKA_TEXT_TOOL (draw_tool);
|
|
GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
|
|
PikaCanvasGroup *group;
|
|
PangoLayout *layout;
|
|
gint offset_x;
|
|
gint offset_y;
|
|
gint width;
|
|
gint height;
|
|
gint off_x, off_y;
|
|
PangoLayoutIter *iter;
|
|
GtkTextIter sel_start, sel_end;
|
|
gint min, max;
|
|
gint i;
|
|
PikaTextDirection direction;
|
|
|
|
group = pika_draw_tool_add_stroke_group (draw_tool);
|
|
pika_canvas_item_set_highlight (PIKA_CANVAS_ITEM (group), TRUE);
|
|
|
|
gtk_text_buffer_get_selection_bounds (buffer, &sel_start, &sel_end);
|
|
|
|
min = pika_text_buffer_get_iter_index (text_tool->buffer, &sel_start, TRUE);
|
|
max = pika_text_buffer_get_iter_index (text_tool->buffer, &sel_end, TRUE);
|
|
|
|
layout = pika_text_layout_get_pango_layout (text_tool->layout);
|
|
|
|
pika_text_layout_get_offsets (text_tool->layout, &offset_x, &offset_y);
|
|
|
|
pika_text_layout_get_size (text_tool->layout, &width, &height);
|
|
|
|
pika_item_get_offset (PIKA_ITEM (text_tool->layer), &off_x, &off_y);
|
|
offset_x += off_x;
|
|
offset_y += off_y;
|
|
|
|
direction = pika_text_tool_get_direction (text_tool);
|
|
|
|
iter = pango_layout_get_iter (layout);
|
|
|
|
pika_draw_tool_push_group (draw_tool, 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);
|
|
|
|
pika_text_layout_transform_rect (text_tool->layout, &rect);
|
|
|
|
switch (direction)
|
|
{
|
|
case PIKA_TEXT_DIRECTION_LTR:
|
|
case PIKA_TEXT_DIRECTION_RTL:
|
|
rect.x += offset_x;
|
|
rect.y += offset_y;
|
|
pika_draw_tool_add_rectangle (draw_tool, FALSE,
|
|
rect.x, rect.y,
|
|
rect.width, rect.height);
|
|
break;
|
|
case PIKA_TEXT_DIRECTION_TTB_RTL:
|
|
case PIKA_TEXT_DIRECTION_TTB_RTL_UPRIGHT:
|
|
rect.y = offset_x - rect.y + width;
|
|
rect.x = offset_y + rect.x;
|
|
pika_draw_tool_add_rectangle (draw_tool, FALSE,
|
|
rect.y, rect.x,
|
|
-rect.height, rect.width);
|
|
break;
|
|
case PIKA_TEXT_DIRECTION_TTB_LTR:
|
|
case PIKA_TEXT_DIRECTION_TTB_LTR_UPRIGHT:
|
|
rect.y = offset_x + rect.y;
|
|
rect.x = offset_y - rect.x + height;
|
|
pika_draw_tool_add_rectangle (draw_tool, FALSE,
|
|
rect.y, rect.x,
|
|
rect.height, -rect.width);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
while (pango_layout_iter_next_char (iter));
|
|
|
|
pika_draw_tool_pop_group (draw_tool);
|
|
|
|
pango_layout_iter_free (iter);
|
|
}
|
|
|
|
static gboolean
|
|
pika_text_tool_start (PikaTextTool *text_tool,
|
|
PikaDisplay *display,
|
|
PikaLayer *layer,
|
|
GError **error)
|
|
{
|
|
PikaTool *tool = PIKA_TOOL (text_tool);
|
|
PikaDisplayShell *shell = pika_display_get_shell (display);
|
|
PikaToolWidget *widget;
|
|
PikaAsyncSet *async_set;
|
|
|
|
async_set =
|
|
pika_data_factory_get_async_set (tool->tool_info->pika->font_factory);
|
|
|
|
if (! pika_async_set_is_empty (async_set))
|
|
{
|
|
g_set_error_literal (error, PIKA_ERROR, PIKA_FAILED,
|
|
_("Fonts are still loading"));
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
tool->display = display;
|
|
|
|
text_tool->widget = widget = pika_tool_rectangle_new (shell);
|
|
|
|
g_object_set (widget,
|
|
"force-narrow-mode", TRUE,
|
|
"status-title", _("Text box: "),
|
|
NULL);
|
|
|
|
pika_draw_tool_set_widget (PIKA_DRAW_TOOL (tool), widget);
|
|
|
|
g_signal_connect (widget, "response",
|
|
G_CALLBACK (pika_text_tool_rectangle_response),
|
|
text_tool);
|
|
g_signal_connect (widget, "change-complete",
|
|
G_CALLBACK (pika_text_tool_rectangle_change_complete),
|
|
text_tool);
|
|
|
|
pika_draw_tool_start (PIKA_DRAW_TOOL (tool), display);
|
|
|
|
if (layer)
|
|
{
|
|
pika_text_tool_frame_item (text_tool);
|
|
pika_text_tool_editor_start (text_tool);
|
|
pika_text_tool_editor_position (text_tool);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
pika_text_tool_halt (PikaTextTool *text_tool)
|
|
{
|
|
PikaTool *tool = PIKA_TOOL (text_tool);
|
|
|
|
pika_text_tool_editor_halt (text_tool);
|
|
pika_text_tool_clear_layout (text_tool);
|
|
pika_text_tool_set_drawable (text_tool, NULL, FALSE);
|
|
|
|
if (pika_draw_tool_is_active (PIKA_DRAW_TOOL (tool)))
|
|
pika_draw_tool_stop (PIKA_DRAW_TOOL (tool));
|
|
|
|
pika_draw_tool_set_widget (PIKA_DRAW_TOOL (tool), NULL);
|
|
g_clear_object (&text_tool->widget);
|
|
|
|
tool->display = NULL;
|
|
g_list_free (tool->drawables);
|
|
tool->drawables = NULL;
|
|
}
|
|
|
|
static void
|
|
pika_text_tool_frame_item (PikaTextTool *text_tool)
|
|
{
|
|
g_return_if_fail (PIKA_IS_LAYER (text_tool->layer));
|
|
|
|
text_tool->handle_rectangle_change_complete = FALSE;
|
|
|
|
pika_tool_rectangle_frame_item (PIKA_TOOL_RECTANGLE (text_tool->widget),
|
|
PIKA_ITEM (text_tool->layer));
|
|
|
|
text_tool->handle_rectangle_change_complete = TRUE;
|
|
}
|
|
|
|
static void
|
|
pika_text_tool_rectangle_response (PikaToolRectangle *rectangle,
|
|
gint response_id,
|
|
PikaTextTool *text_tool)
|
|
{
|
|
PikaTool *tool = PIKA_TOOL (text_tool);
|
|
|
|
/* this happens when a newly created rectangle gets canceled,
|
|
* we have to shut down the tool
|
|
*/
|
|
if (response_id == PIKA_TOOL_WIDGET_RESPONSE_CANCEL)
|
|
pika_tool_control (tool, PIKA_TOOL_ACTION_HALT, tool->display);
|
|
}
|
|
|
|
static void
|
|
pika_text_tool_rectangle_change_complete (PikaToolRectangle *rectangle,
|
|
PikaTextTool *text_tool)
|
|
{
|
|
pika_text_tool_editor_position (text_tool);
|
|
|
|
if (text_tool->handle_rectangle_change_complete)
|
|
{
|
|
PikaItem *item = PIKA_ITEM (text_tool->layer);
|
|
gdouble x1, y1;
|
|
gdouble 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;
|
|
}
|
|
|
|
g_object_get (rectangle,
|
|
"x1", &x1,
|
|
"y1", &y1,
|
|
"x2", &x2,
|
|
"y2", &y2,
|
|
NULL);
|
|
|
|
if ((x2 - x1) != pika_item_get_width (item) ||
|
|
(y2 - y1) != pika_item_get_height (item))
|
|
{
|
|
PikaUnit box_unit = text_tool->proxy->box_unit;
|
|
gdouble xres, yres;
|
|
gboolean push_undo = TRUE;
|
|
PikaUndo *undo;
|
|
|
|
pika_image_get_resolution (text_tool->image, &xres, &yres);
|
|
|
|
g_object_set (text_tool->proxy,
|
|
"box-mode", PIKA_TEXT_BOX_FIXED,
|
|
"box-width", pika_pixels_to_units (x2 - x1,
|
|
box_unit, xres),
|
|
"box-height", pika_pixels_to_units (y2 - y1,
|
|
box_unit, yres),
|
|
NULL);
|
|
|
|
undo = pika_image_undo_can_compress (text_tool->image,
|
|
PIKA_TYPE_UNDO_STACK,
|
|
PIKA_UNDO_GROUP_TEXT);
|
|
|
|
if (undo &&
|
|
pika_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)
|
|
{
|
|
pika_image_undo_group_start (text_tool->image, PIKA_UNDO_GROUP_TEXT,
|
|
_("Reshape Text Layer"));
|
|
|
|
undo = pika_image_undo_can_compress (text_tool->image, PIKA_TYPE_UNDO_STACK,
|
|
PIKA_UNDO_GROUP_TEXT);
|
|
|
|
if (undo)
|
|
g_object_set_data (G_OBJECT (undo), "reshape-text-layer",
|
|
(gpointer) item);
|
|
}
|
|
|
|
pika_text_tool_block_drawing (text_tool);
|
|
|
|
pika_item_translate (item,
|
|
x1 - pika_item_get_offset_x (item),
|
|
y1 - pika_item_get_offset_y (item),
|
|
push_undo);
|
|
pika_text_tool_apply (text_tool, push_undo);
|
|
|
|
pika_text_tool_unblock_drawing (text_tool);
|
|
|
|
if (push_undo)
|
|
pika_image_undo_group_end (text_tool->image);
|
|
}
|
|
else if (x1 != pika_item_get_offset_x (item) ||
|
|
y1 != pika_item_get_offset_y (item))
|
|
{
|
|
pika_text_tool_block_drawing (text_tool);
|
|
|
|
pika_text_tool_apply (text_tool, TRUE);
|
|
|
|
pika_item_translate (item,
|
|
x1 - pika_item_get_offset_x (item),
|
|
y1 - pika_item_get_offset_y (item),
|
|
TRUE);
|
|
|
|
pika_text_tool_unblock_drawing (text_tool);
|
|
|
|
pika_image_flush (text_tool->image);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
pika_text_tool_connect (PikaTextTool *text_tool,
|
|
PikaTextLayer *layer,
|
|
PikaText *text)
|
|
{
|
|
PikaTool *tool = PIKA_TOOL (text_tool);
|
|
|
|
g_return_if_fail (text == NULL || (layer != NULL && layer->text == text));
|
|
|
|
if (text_tool->text != text)
|
|
{
|
|
PikaTextOptions *options = PIKA_TEXT_TOOL_GET_OPTIONS (tool);
|
|
|
|
g_signal_handlers_block_by_func (text_tool->buffer,
|
|
pika_text_tool_buffer_begin_edit,
|
|
text_tool);
|
|
g_signal_handlers_block_by_func (text_tool->buffer,
|
|
pika_text_tool_buffer_end_edit,
|
|
text_tool);
|
|
|
|
if (text_tool->text)
|
|
{
|
|
g_signal_handlers_disconnect_by_func (text_tool->text,
|
|
pika_text_tool_text_notify,
|
|
text_tool);
|
|
g_signal_handlers_disconnect_by_func (text_tool->text,
|
|
pika_text_tool_text_changed,
|
|
text_tool);
|
|
|
|
if (text_tool->pending)
|
|
pika_text_tool_apply (text_tool, TRUE);
|
|
|
|
g_clear_object (&text_tool->text);
|
|
|
|
g_object_set (text_tool->proxy,
|
|
"text", NULL,
|
|
"markup", NULL,
|
|
NULL);
|
|
pika_text_buffer_set_text (text_tool->buffer, NULL);
|
|
|
|
pika_text_tool_clear_layout (text_tool);
|
|
}
|
|
|
|
pika_context_define_property (PIKA_CONTEXT (options),
|
|
PIKA_CONTEXT_PROP_FOREGROUND,
|
|
text != NULL);
|
|
|
|
if (text)
|
|
{
|
|
if (text->unit != text_tool->proxy->unit)
|
|
pika_size_entry_set_unit (PIKA_SIZE_ENTRY (options->size_entry),
|
|
text->unit);
|
|
|
|
pika_config_sync (G_OBJECT (text), G_OBJECT (text_tool->proxy), 0);
|
|
|
|
if (text->markup)
|
|
pika_text_buffer_set_markup (text_tool->buffer, text->markup);
|
|
else
|
|
pika_text_buffer_set_text (text_tool->buffer, text->text);
|
|
|
|
pika_text_tool_clear_layout (text_tool);
|
|
|
|
text_tool->text = g_object_ref (text);
|
|
|
|
g_signal_connect (text, "notify",
|
|
G_CALLBACK (pika_text_tool_text_notify),
|
|
text_tool);
|
|
g_signal_connect (text, "changed",
|
|
G_CALLBACK (pika_text_tool_text_changed),
|
|
text_tool);
|
|
}
|
|
|
|
g_signal_handlers_unblock_by_func (text_tool->buffer,
|
|
pika_text_tool_buffer_end_edit,
|
|
text_tool);
|
|
g_signal_handlers_unblock_by_func (text_tool->buffer,
|
|
pika_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,
|
|
pika_text_tool_layer_notify,
|
|
text_tool);
|
|
|
|
/* don't try to remove the layer if it is not attached,
|
|
* which can happen if we got here because the layer was
|
|
* somehow deleted from the image (like by the user in the
|
|
* layers dialog).
|
|
*/
|
|
if (pika_item_is_attached (PIKA_ITEM (text_tool->layer)))
|
|
pika_text_tool_remove_empty_text_layer (text_tool);
|
|
}
|
|
|
|
text_tool->layer = layer;
|
|
|
|
if (layer)
|
|
{
|
|
g_signal_connect_object (text_tool->layer, "notify",
|
|
G_CALLBACK (pika_text_tool_layer_notify),
|
|
text_tool, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
pika_text_tool_layer_notify (PikaTextLayer *layer,
|
|
const GParamSpec *pspec,
|
|
PikaTextTool *text_tool)
|
|
{
|
|
PikaTool *tool = PIKA_TOOL (text_tool);
|
|
|
|
if (! strcmp (pspec->name, "modified"))
|
|
{
|
|
if (layer->modified)
|
|
pika_tool_control (tool, PIKA_TOOL_ACTION_HALT, tool->display);
|
|
}
|
|
else if (! strcmp (pspec->name, "text"))
|
|
{
|
|
if (! layer->text)
|
|
pika_tool_control (tool, PIKA_TOOL_ACTION_HALT, tool->display);
|
|
}
|
|
else if (! strcmp (pspec->name, "offset-x") ||
|
|
! strcmp (pspec->name, "offset-y"))
|
|
{
|
|
if (pika_item_is_attached (PIKA_ITEM (layer)))
|
|
{
|
|
pika_text_tool_block_drawing (text_tool);
|
|
|
|
pika_text_tool_frame_item (text_tool);
|
|
|
|
pika_text_tool_unblock_drawing (text_tool);
|
|
}
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
pika_text_tool_apply_idle (PikaTextTool *text_tool)
|
|
{
|
|
text_tool->idle_id = 0;
|
|
|
|
pika_text_tool_apply (text_tool, TRUE);
|
|
|
|
pika_text_tool_unblock_drawing (text_tool);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
pika_text_tool_proxy_notify (PikaText *text,
|
|
const GParamSpec *pspec,
|
|
PikaTextTool *text_tool)
|
|
{
|
|
if (! text_tool->text)
|
|
return;
|
|
|
|
if ((pspec->flags & G_PARAM_READWRITE) == G_PARAM_READWRITE &&
|
|
pspec->owner_type == PIKA_TYPE_TEXT)
|
|
{
|
|
if (text_tool->preedit_active)
|
|
{
|
|
/* if there is a preedit going on, don't queue pending
|
|
* changes to be idle-applied with undo; instead, flush the
|
|
* pending queue (happens only when preedit starts), and
|
|
* apply the changes to text_tool->text directly. Preedit
|
|
* will *always* end by removing the preedit string, and if
|
|
* the preedit was committed, it will insert the resulting
|
|
* text, which will not trigger this if() any more.
|
|
*/
|
|
|
|
GList *list = NULL;
|
|
|
|
/* if there are pending changes, apply them before applying
|
|
* preedit stuff directly (bypassing undo)
|
|
*/
|
|
if (text_tool->pending)
|
|
{
|
|
pika_text_tool_block_drawing (text_tool);
|
|
pika_text_tool_apply (text_tool, TRUE);
|
|
pika_text_tool_unblock_drawing (text_tool);
|
|
}
|
|
|
|
pika_text_tool_block_drawing (text_tool);
|
|
|
|
list = g_list_append (list, (gpointer) pspec);
|
|
pika_text_tool_apply_list (text_tool, list);
|
|
g_list_free (list);
|
|
|
|
pika_text_tool_frame_item (text_tool);
|
|
|
|
pika_image_flush (pika_item_get_image (PIKA_ITEM (text_tool->layer)));
|
|
|
|
pika_text_tool_unblock_drawing (text_tool);
|
|
}
|
|
else
|
|
{
|
|
/* else queue the property change for normal processing,
|
|
* including undo
|
|
*/
|
|
|
|
text_tool->pending = g_list_append (text_tool->pending,
|
|
(gpointer) pspec);
|
|
|
|
if (! text_tool->idle_id)
|
|
{
|
|
pika_text_tool_block_drawing (text_tool);
|
|
|
|
text_tool->idle_id =
|
|
g_idle_add_full (G_PRIORITY_LOW,
|
|
(GSourceFunc) pika_text_tool_apply_idle,
|
|
text_tool,
|
|
NULL);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
pika_text_tool_text_notify (PikaText *text,
|
|
const GParamSpec *pspec,
|
|
PikaTextTool *text_tool)
|
|
{
|
|
g_return_if_fail (text == text_tool->text);
|
|
|
|
/* an undo cancels all preedit operations */
|
|
if (text_tool->preedit_active)
|
|
pika_text_tool_abort_im_context (text_tool);
|
|
|
|
pika_text_tool_block_drawing (text_tool);
|
|
|
|
if ((pspec->flags & G_PARAM_READWRITE) == G_PARAM_READWRITE)
|
|
{
|
|
GValue value = G_VALUE_INIT;
|
|
|
|
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,
|
|
pika_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,
|
|
pika_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,
|
|
pika_text_tool_buffer_begin_edit,
|
|
text_tool);
|
|
g_signal_handlers_block_by_func (text_tool->buffer,
|
|
pika_text_tool_buffer_end_edit,
|
|
text_tool);
|
|
|
|
if (text->markup)
|
|
pika_text_buffer_set_markup (text_tool->buffer, text->markup);
|
|
else
|
|
pika_text_buffer_set_text (text_tool->buffer, text->text);
|
|
|
|
g_signal_handlers_unblock_by_func (text_tool->buffer,
|
|
pika_text_tool_buffer_end_edit,
|
|
text_tool);
|
|
g_signal_handlers_unblock_by_func (text_tool->buffer,
|
|
pika_text_tool_buffer_begin_edit,
|
|
text_tool);
|
|
}
|
|
|
|
pika_text_tool_unblock_drawing (text_tool);
|
|
}
|
|
|
|
static void
|
|
pika_text_tool_text_changed (PikaText *text,
|
|
PikaTextTool *text_tool)
|
|
{
|
|
pika_text_tool_block_drawing (text_tool);
|
|
|
|
/* we need to redraw the rectangle in any case because whatever
|
|
* changes to the text can change its size
|
|
*/
|
|
pika_text_tool_frame_item (text_tool);
|
|
|
|
pika_text_tool_unblock_drawing (text_tool);
|
|
}
|
|
|
|
static void
|
|
pika_text_tool_fonts_async_set_empty_notify (PikaAsyncSet *async_set,
|
|
GParamSpec *pspec,
|
|
PikaTextTool *text_tool)
|
|
{
|
|
PikaTool *tool = PIKA_TOOL (text_tool);
|
|
|
|
if (! pika_async_set_is_empty (async_set) && tool->display)
|
|
pika_tool_control (tool, PIKA_TOOL_ACTION_HALT, tool->display);
|
|
}
|
|
|
|
static void
|
|
pika_text_tool_apply_list (PikaTextTool *text_tool,
|
|
GList *pspecs)
|
|
{
|
|
GObject *src = G_OBJECT (text_tool->proxy);
|
|
GObject *dest = G_OBJECT (text_tool->text);
|
|
GList *list;
|
|
|
|
g_signal_handlers_block_by_func (dest,
|
|
pika_text_tool_text_notify,
|
|
text_tool);
|
|
g_signal_handlers_block_by_func (dest,
|
|
pika_text_tool_text_changed,
|
|
text_tool);
|
|
|
|
g_object_freeze_notify (dest);
|
|
|
|
for (list = pspecs; list; list = g_list_next (list))
|
|
{
|
|
const GParamSpec *pspec;
|
|
GValue value = G_VALUE_INIT;
|
|
|
|
/* 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_object_thaw_notify (dest);
|
|
|
|
g_signal_handlers_unblock_by_func (dest,
|
|
pika_text_tool_text_notify,
|
|
text_tool);
|
|
g_signal_handlers_unblock_by_func (dest,
|
|
pika_text_tool_text_changed,
|
|
text_tool);
|
|
}
|
|
|
|
static void
|
|
pika_text_tool_create_layer (PikaTextTool *text_tool,
|
|
PikaText *text)
|
|
{
|
|
PikaTool *tool = PIKA_TOOL (text_tool);
|
|
PikaImage *image = pika_display_get_image (tool->display);
|
|
PikaLayer *layer;
|
|
gdouble x1, y1;
|
|
gdouble x2, y2;
|
|
|
|
pika_text_tool_block_drawing (text_tool);
|
|
|
|
if (text)
|
|
{
|
|
text = pika_config_duplicate (PIKA_CONFIG (text));
|
|
}
|
|
else
|
|
{
|
|
gchar *string;
|
|
|
|
if (pika_text_buffer_has_markup (text_tool->buffer))
|
|
{
|
|
string = pika_text_buffer_get_markup (text_tool->buffer);
|
|
|
|
g_object_set (text_tool->proxy,
|
|
"markup", string,
|
|
"box-mode", PIKA_TEXT_BOX_DYNAMIC,
|
|
NULL);
|
|
}
|
|
else
|
|
{
|
|
string = pika_text_buffer_get_text (text_tool->buffer);
|
|
|
|
g_object_set (text_tool->proxy,
|
|
"text", string,
|
|
"box-mode", PIKA_TEXT_BOX_DYNAMIC,
|
|
NULL);
|
|
}
|
|
|
|
g_free (string);
|
|
|
|
text = pika_config_duplicate (PIKA_CONFIG (text_tool->proxy));
|
|
}
|
|
|
|
layer = pika_text_layer_new (image, text);
|
|
|
|
g_object_unref (text);
|
|
|
|
if (! layer)
|
|
{
|
|
pika_text_tool_unblock_drawing (text_tool);
|
|
return;
|
|
}
|
|
|
|
pika_text_tool_connect (text_tool, PIKA_TEXT_LAYER (layer), text);
|
|
|
|
pika_image_undo_group_start (image, PIKA_UNDO_GROUP_TEXT,
|
|
_("Add Text Layer"));
|
|
|
|
if (pika_image_get_floating_selection (image))
|
|
{
|
|
g_signal_handlers_block_by_func (image,
|
|
pika_text_tool_layer_changed,
|
|
text_tool);
|
|
|
|
floating_sel_anchor (pika_image_get_floating_selection (image));
|
|
|
|
g_signal_handlers_unblock_by_func (image,
|
|
pika_text_tool_layer_changed,
|
|
text_tool);
|
|
}
|
|
|
|
g_object_get (text_tool->widget,
|
|
"x1", &x1,
|
|
"y1", &y1,
|
|
"x2", &x2,
|
|
"y2", &y2,
|
|
NULL);
|
|
|
|
if (text_tool->text_box_fixed == FALSE)
|
|
{
|
|
if (text_tool->text &&
|
|
(text_tool->text->base_dir == PIKA_TEXT_DIRECTION_TTB_RTL ||
|
|
text_tool->text->base_dir == PIKA_TEXT_DIRECTION_TTB_RTL_UPRIGHT))
|
|
{
|
|
x1 -= pika_item_get_width (PIKA_ITEM (layer));
|
|
}
|
|
}
|
|
pika_item_set_offset (PIKA_ITEM (layer), x1, y1);
|
|
|
|
pika_image_add_layer (image, layer,
|
|
PIKA_IMAGE_ACTIVE_PARENT, -1, TRUE);
|
|
|
|
if (text_tool->text_box_fixed)
|
|
{
|
|
PikaUnit box_unit = text_tool->proxy->box_unit;
|
|
gdouble xres, yres;
|
|
|
|
pika_image_get_resolution (image, &xres, &yres);
|
|
|
|
g_object_set (text_tool->proxy,
|
|
"box-mode", PIKA_TEXT_BOX_FIXED,
|
|
"box-width", pika_pixels_to_units (x2 - x1,
|
|
box_unit, xres),
|
|
"box-height", pika_pixels_to_units (y2 - y1,
|
|
box_unit, yres),
|
|
NULL);
|
|
|
|
pika_text_tool_apply (text_tool, TRUE); /* unblocks drawing */
|
|
}
|
|
else
|
|
{
|
|
pika_text_tool_frame_item (text_tool);
|
|
}
|
|
|
|
pika_image_undo_group_end (image);
|
|
|
|
pika_image_flush (image);
|
|
|
|
pika_text_tool_set_drawable (text_tool, PIKA_DRAWABLE (layer), FALSE);
|
|
|
|
pika_text_tool_unblock_drawing (text_tool);
|
|
}
|
|
|
|
#define RESPONSE_NEW 1
|
|
|
|
static void
|
|
pika_text_tool_confirm_response (GtkWidget *widget,
|
|
gint response_id,
|
|
PikaTextTool *text_tool)
|
|
{
|
|
PikaTextLayer *layer = text_tool->layer;
|
|
|
|
gtk_widget_destroy (widget);
|
|
|
|
if (layer && layer->text)
|
|
{
|
|
switch (response_id)
|
|
{
|
|
case RESPONSE_NEW:
|
|
pika_text_tool_create_layer (text_tool, layer->text);
|
|
break;
|
|
|
|
case GTK_RESPONSE_ACCEPT:
|
|
pika_text_tool_connect (text_tool, layer, layer->text);
|
|
|
|
/* cause the text layer to be rerendered */
|
|
g_object_notify (G_OBJECT (text_tool->proxy), "markup");
|
|
|
|
pika_text_tool_editor_start (text_tool);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
pika_text_tool_confirm_dialog (PikaTextTool *text_tool)
|
|
{
|
|
PikaTool *tool = PIKA_TOOL (text_tool);
|
|
PikaDisplayShell *shell = pika_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 = pika_viewable_dialog_new (g_list_prepend (NULL, text_tool->layer),
|
|
PIKA_CONTEXT (pika_tool_get_options (tool)),
|
|
_("Confirm Text Editing"),
|
|
"pika-text-tool-confirm",
|
|
PIKA_ICON_LAYER_TEXT_LAYER,
|
|
_("Confirm Text Editing"),
|
|
GTK_WIDGET (shell),
|
|
pika_standard_help_func, NULL,
|
|
|
|
_("Create _New Layer"), RESPONSE_NEW,
|
|
_("_Cancel"), GTK_RESPONSE_CANCEL,
|
|
_("_Edit"), GTK_RESPONSE_ACCEPT,
|
|
|
|
NULL);
|
|
|
|
pika_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 (pika_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_label_set_xalign (GTK_LABEL (label), 0.0);
|
|
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
|
|
pika_text_tool_layer_changed (PikaImage *image,
|
|
PikaTextTool *text_tool)
|
|
{
|
|
GList *layers = pika_image_get_selected_layers (image);
|
|
|
|
if (g_list_length (layers) != 1 || layers->data != text_tool->layer)
|
|
{
|
|
PikaTool *tool = PIKA_TOOL (text_tool);
|
|
PikaDisplay *display = tool->display;
|
|
|
|
if (display)
|
|
{
|
|
PikaLayer *layer = NULL;
|
|
|
|
pika_tool_control (tool, PIKA_TOOL_ACTION_HALT, display);
|
|
|
|
if (g_list_length (layers) == 1)
|
|
layer = layers->data;
|
|
|
|
/* The tool can only be started when a single layer is
|
|
* selected and this is a text layer.
|
|
*/
|
|
if (layer &&
|
|
pika_text_tool_set_drawable (text_tool, PIKA_DRAWABLE (layer),
|
|
FALSE) &&
|
|
PIKA_LAYER (text_tool->layer) == layer)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (! pika_text_tool_start (text_tool, display, layer, &error))
|
|
{
|
|
pika_text_tool_set_drawable (text_tool, NULL, FALSE);
|
|
|
|
pika_tool_message_literal (tool, display, error->message);
|
|
|
|
g_clear_error (&error);
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
pika_text_tool_set_image (PikaTextTool *text_tool,
|
|
PikaImage *image)
|
|
{
|
|
if (text_tool->image == image)
|
|
return;
|
|
|
|
if (text_tool->image)
|
|
{
|
|
g_signal_handlers_disconnect_by_func (text_tool->image,
|
|
pika_text_tool_layer_changed,
|
|
text_tool);
|
|
}
|
|
|
|
g_set_weak_pointer (&text_tool->image, image);
|
|
|
|
if (image)
|
|
{
|
|
PikaTextOptions *options = PIKA_TEXT_TOOL_GET_OPTIONS (text_tool);
|
|
gdouble xres;
|
|
gdouble yres;
|
|
|
|
g_signal_connect_object (text_tool->image, "selected-layers-changed",
|
|
G_CALLBACK (pika_text_tool_layer_changed),
|
|
text_tool, 0);
|
|
|
|
pika_image_get_resolution (image, &xres, &yres);
|
|
pika_size_entry_set_resolution (PIKA_SIZE_ENTRY (options->size_entry), 0,
|
|
yres, FALSE);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
pika_text_tool_set_drawable (PikaTextTool *text_tool,
|
|
PikaDrawable *drawable,
|
|
gboolean confirm)
|
|
{
|
|
PikaImage *image = NULL;
|
|
|
|
if (text_tool->confirm_dialog)
|
|
gtk_widget_destroy (text_tool->confirm_dialog);
|
|
|
|
if (drawable)
|
|
image = pika_item_get_image (PIKA_ITEM (drawable));
|
|
|
|
pika_text_tool_set_image (text_tool, image);
|
|
|
|
if (PIKA_IS_TEXT_LAYER (drawable) && PIKA_TEXT_LAYER (drawable)->text)
|
|
{
|
|
PikaTextLayer *layer = PIKA_TEXT_LAYER (drawable);
|
|
|
|
if (layer == text_tool->layer && layer->text == text_tool->text)
|
|
return TRUE;
|
|
|
|
if (layer->modified)
|
|
{
|
|
if (confirm)
|
|
{
|
|
pika_text_tool_connect (text_tool, layer, NULL);
|
|
pika_text_tool_confirm_dialog (text_tool);
|
|
return TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pika_text_tool_connect (text_tool, layer, layer->text);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
pika_text_tool_connect (text_tool, NULL, NULL);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
pika_text_tool_block_drawing (PikaTextTool *text_tool)
|
|
{
|
|
if (text_tool->drawing_blocked == 0)
|
|
{
|
|
pika_draw_tool_pause (PIKA_DRAW_TOOL (text_tool));
|
|
|
|
pika_text_tool_clear_layout (text_tool);
|
|
}
|
|
|
|
text_tool->drawing_blocked++;
|
|
}
|
|
|
|
static void
|
|
pika_text_tool_unblock_drawing (PikaTextTool *text_tool)
|
|
{
|
|
g_return_if_fail (text_tool->drawing_blocked > 0);
|
|
|
|
text_tool->drawing_blocked--;
|
|
|
|
if (text_tool->drawing_blocked == 0)
|
|
pika_draw_tool_resume (PIKA_DRAW_TOOL (text_tool));
|
|
}
|
|
|
|
static void
|
|
pika_text_tool_buffer_begin_edit (PikaTextBuffer *buffer,
|
|
PikaTextTool *text_tool)
|
|
{
|
|
pika_text_tool_block_drawing (text_tool);
|
|
}
|
|
|
|
static void
|
|
pika_text_tool_buffer_end_edit (PikaTextBuffer *buffer,
|
|
PikaTextTool *text_tool)
|
|
{
|
|
if (text_tool->text)
|
|
{
|
|
gchar *string;
|
|
|
|
if (pika_text_buffer_has_markup (buffer))
|
|
{
|
|
string = pika_text_buffer_get_markup (buffer);
|
|
|
|
g_object_set (text_tool->proxy,
|
|
"markup", string,
|
|
NULL);
|
|
}
|
|
else
|
|
{
|
|
string = pika_text_buffer_get_text (buffer);
|
|
|
|
g_object_set (text_tool->proxy,
|
|
"text", string,
|
|
NULL);
|
|
}
|
|
|
|
g_free (string);
|
|
}
|
|
else
|
|
{
|
|
pika_text_tool_create_layer (text_tool, NULL);
|
|
}
|
|
|
|
pika_text_tool_unblock_drawing (text_tool);
|
|
}
|
|
|
|
static void
|
|
pika_text_tool_buffer_color_applied (PikaTextBuffer *buffer,
|
|
const PikaRGB *color,
|
|
PikaTextTool *text_tool)
|
|
{
|
|
pika_palettes_add_color_history (PIKA_TOOL (text_tool)->tool_info->pika,
|
|
color);
|
|
}
|
|
|
|
|
|
/* public functions */
|
|
|
|
void
|
|
pika_text_tool_clear_layout (PikaTextTool *text_tool)
|
|
{
|
|
g_clear_object (&text_tool->layout);
|
|
}
|
|
|
|
gboolean
|
|
pika_text_tool_ensure_layout (PikaTextTool *text_tool)
|
|
{
|
|
if (! text_tool->layout && text_tool->text)
|
|
{
|
|
PikaImage *image = pika_item_get_image (PIKA_ITEM (text_tool->layer));
|
|
gdouble xres;
|
|
gdouble yres;
|
|
GError *error = NULL;
|
|
|
|
pika_image_get_resolution (image, &xres, &yres);
|
|
|
|
text_tool->layout = pika_text_layout_new (text_tool->layer->text,
|
|
xres, yres, &error);
|
|
if (error)
|
|
{
|
|
pika_message_literal (image->pika, NULL, PIKA_MESSAGE_ERROR, error->message);
|
|
g_error_free (error);
|
|
}
|
|
}
|
|
|
|
return text_tool->layout != NULL;
|
|
}
|
|
|
|
void
|
|
pika_text_tool_apply (PikaTextTool *text_tool,
|
|
gboolean push_undo)
|
|
{
|
|
const GParamSpec *pspec = NULL;
|
|
PikaImage *image;
|
|
PikaTextLayer *layer;
|
|
GList *list;
|
|
gboolean undo_group = FALSE;
|
|
|
|
if (text_tool->idle_id)
|
|
{
|
|
g_source_remove (text_tool->idle_id);
|
|
text_tool->idle_id = 0;
|
|
|
|
pika_text_tool_unblock_drawing (text_tool);
|
|
}
|
|
|
|
g_return_if_fail (text_tool->text != NULL);
|
|
g_return_if_fail (text_tool->layer != NULL);
|
|
|
|
layer = text_tool->layer;
|
|
image = pika_item_get_image (PIKA_ITEM (layer));
|
|
|
|
g_return_if_fail (layer->text == text_tool->text);
|
|
|
|
/* 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)
|
|
{
|
|
PikaUndo *undo = pika_image_undo_can_compress (image, PIKA_TYPE_TEXT_UNDO,
|
|
PIKA_UNDO_TEXT_LAYER);
|
|
|
|
if (undo && PIKA_ITEM_UNDO (undo)->item == PIKA_ITEM (layer))
|
|
{
|
|
PikaTextUndo *text_undo = PIKA_TEXT_UNDO (undo);
|
|
|
|
if (text_undo->pspec == pspec)
|
|
{
|
|
if (pika_undo_get_age (undo) < TEXT_UNDO_TIMEOUT)
|
|
{
|
|
PikaTool *tool = PIKA_TOOL (text_tool);
|
|
PikaContext *context;
|
|
|
|
context = PIKA_CONTEXT (pika_tool_get_options (tool));
|
|
|
|
push_undo = FALSE;
|
|
pika_undo_reset_age (undo);
|
|
pika_undo_refresh_preview (undo, context);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (push_undo)
|
|
{
|
|
if (layer->modified)
|
|
{
|
|
undo_group = TRUE;
|
|
pika_image_undo_group_start (image, PIKA_UNDO_GROUP_TEXT, NULL);
|
|
|
|
pika_image_undo_push_text_layer_modified (image, NULL, layer);
|
|
|
|
/* see comment in pika_text_layer_set() */
|
|
pika_image_undo_push_drawable_mod (image, NULL,
|
|
PIKA_DRAWABLE (layer), TRUE);
|
|
}
|
|
|
|
if (pspec)
|
|
pika_image_undo_push_text_layer (image, NULL, layer, pspec);
|
|
}
|
|
|
|
pika_text_tool_apply_list (text_tool, list);
|
|
|
|
g_list_free (text_tool->pending);
|
|
text_tool->pending = NULL;
|
|
|
|
if (push_undo)
|
|
{
|
|
g_object_set (layer, "modified", FALSE, NULL);
|
|
|
|
if (undo_group)
|
|
pika_image_undo_group_end (image);
|
|
}
|
|
|
|
pika_text_tool_frame_item (text_tool);
|
|
|
|
pika_image_flush (image);
|
|
}
|
|
|
|
gboolean
|
|
pika_text_tool_set_layer (PikaTextTool *text_tool,
|
|
PikaLayer *layer)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_TEXT_TOOL (text_tool), FALSE);
|
|
g_return_val_if_fail (layer == NULL || PIKA_IS_LAYER (layer), FALSE);
|
|
|
|
if (layer == PIKA_LAYER (text_tool->layer))
|
|
return TRUE;
|
|
|
|
/* FIXME this function works, and I have no clue why: first we set
|
|
* the drawable, then we HALT the tool and start() it without
|
|
* re-setting the drawable. Why this works perfectly anyway when
|
|
* double clicking a text layer in the layers dialog... no idea.
|
|
*/
|
|
if (pika_text_tool_set_drawable (text_tool, PIKA_DRAWABLE (layer), TRUE))
|
|
{
|
|
PikaTool *tool = PIKA_TOOL (text_tool);
|
|
PikaItem *item = PIKA_ITEM (layer);
|
|
PikaContext *context;
|
|
PikaDisplay *display;
|
|
|
|
context = pika_get_user_context (tool->tool_info->pika);
|
|
display = pika_context_get_display (context);
|
|
|
|
if (! display ||
|
|
pika_display_get_image (display) != pika_item_get_image (item))
|
|
{
|
|
GList *list;
|
|
|
|
display = NULL;
|
|
|
|
for (list = pika_get_display_iter (tool->tool_info->pika);
|
|
list;
|
|
list = g_list_next (list))
|
|
{
|
|
display = list->data;
|
|
|
|
if (pika_display_get_image (display) == pika_item_get_image (item))
|
|
{
|
|
pika_context_set_display (context, display);
|
|
break;
|
|
}
|
|
|
|
display = NULL;
|
|
}
|
|
}
|
|
|
|
if (tool->display)
|
|
pika_tool_control (tool, PIKA_TOOL_ACTION_HALT, tool->display);
|
|
|
|
if (display)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (! pika_text_tool_start (text_tool, display, layer, &error))
|
|
{
|
|
pika_text_tool_set_drawable (text_tool, NULL, FALSE);
|
|
|
|
pika_tool_message_literal (tool, display, error->message);
|
|
|
|
g_clear_error (&error);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
g_list_free (tool->drawables);
|
|
tool->drawables = g_list_prepend (NULL, PIKA_DRAWABLE (layer));
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
pika_text_tool_get_has_text_selection (PikaTextTool *text_tool)
|
|
{
|
|
GtkTextBuffer *buffer = GTK_TEXT_BUFFER (text_tool->buffer);
|
|
|
|
return gtk_text_buffer_get_has_selection (buffer);
|
|
}
|
|
|
|
void
|
|
pika_text_tool_delete_selection (PikaTextTool *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
|
|
pika_text_tool_cut_clipboard (PikaTextTool *text_tool)
|
|
{
|
|
PikaDisplayShell *shell;
|
|
GtkClipboard *clipboard;
|
|
|
|
g_return_if_fail (PIKA_IS_TEXT_TOOL (text_tool));
|
|
|
|
shell = pika_display_get_shell (PIKA_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
|
|
pika_text_tool_copy_clipboard (PikaTextTool *text_tool)
|
|
{
|
|
PikaDisplayShell *shell;
|
|
GtkClipboard *clipboard;
|
|
|
|
g_return_if_fail (PIKA_IS_TEXT_TOOL (text_tool));
|
|
|
|
shell = pika_display_get_shell (PIKA_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,
|
|
pika_text_tool_buffer_begin_edit,
|
|
text_tool);
|
|
g_signal_handlers_block_by_func (text_tool->buffer,
|
|
pika_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,
|
|
pika_text_tool_buffer_end_edit,
|
|
text_tool);
|
|
g_signal_handlers_unblock_by_func (text_tool->buffer,
|
|
pika_text_tool_buffer_begin_edit,
|
|
text_tool);
|
|
}
|
|
|
|
void
|
|
pika_text_tool_paste_clipboard (PikaTextTool *text_tool)
|
|
{
|
|
PikaDisplayShell *shell;
|
|
GtkClipboard *clipboard;
|
|
|
|
g_return_if_fail (PIKA_IS_TEXT_TOOL (text_tool));
|
|
|
|
shell = pika_display_get_shell (PIKA_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
|
|
pika_text_tool_create_vectors (PikaTextTool *text_tool)
|
|
{
|
|
PikaVectors *vectors;
|
|
|
|
g_return_if_fail (PIKA_IS_TEXT_TOOL (text_tool));
|
|
|
|
if (! text_tool->text || ! text_tool->image)
|
|
return;
|
|
|
|
vectors = pika_text_vectors_new (text_tool->image, text_tool->text);
|
|
|
|
if (text_tool->layer)
|
|
{
|
|
gint x, y;
|
|
|
|
pika_item_get_offset (PIKA_ITEM (text_tool->layer), &x, &y);
|
|
pika_item_translate (PIKA_ITEM (vectors), x, y, FALSE);
|
|
}
|
|
|
|
pika_image_add_vectors (text_tool->image, vectors,
|
|
PIKA_IMAGE_ACTIVE_PARENT, -1, TRUE);
|
|
|
|
pika_image_flush (text_tool->image);
|
|
}
|
|
|
|
gboolean
|
|
pika_text_tool_create_vectors_warped (PikaTextTool *text_tool,
|
|
GError **error)
|
|
{
|
|
GList *vectors0;
|
|
PikaVectors *vectors;
|
|
gdouble box_width;
|
|
gdouble box_height;
|
|
PikaTextDirection dir;
|
|
gdouble offset = 0.0;
|
|
|
|
g_return_val_if_fail (PIKA_IS_TEXT_TOOL (text_tool), FALSE);
|
|
|
|
if (! text_tool->text || ! text_tool->image || ! text_tool->layer)
|
|
{
|
|
if (! text_tool->text)
|
|
g_set_error_literal (error, PIKA_ERROR, PIKA_FAILED,
|
|
_("Text is required."));
|
|
if (! text_tool->image)
|
|
g_set_error_literal (error, PIKA_ERROR, PIKA_FAILED,
|
|
_("No image."));
|
|
if (! text_tool->layer)
|
|
g_set_error_literal (error, PIKA_ERROR, PIKA_FAILED,
|
|
_("No layer."));
|
|
return FALSE;
|
|
}
|
|
|
|
box_width = pika_item_get_width (PIKA_ITEM (text_tool->layer));
|
|
box_height = pika_item_get_height (PIKA_ITEM (text_tool->layer));
|
|
|
|
vectors0 = pika_image_get_selected_vectors (text_tool->image);
|
|
if (g_list_length (vectors0) != 1)
|
|
{
|
|
g_set_error_literal (error, PIKA_ERROR, PIKA_FAILED,
|
|
_("Exactly one path must be selected."));
|
|
return FALSE;
|
|
}
|
|
|
|
vectors = pika_text_vectors_new (text_tool->image, text_tool->text);
|
|
|
|
offset = 0;
|
|
dir = pika_text_tool_get_direction (text_tool);
|
|
switch (dir)
|
|
{
|
|
case PIKA_TEXT_DIRECTION_LTR:
|
|
case PIKA_TEXT_DIRECTION_RTL:
|
|
offset = 0.5 * box_height;
|
|
break;
|
|
case PIKA_TEXT_DIRECTION_TTB_RTL:
|
|
case PIKA_TEXT_DIRECTION_TTB_RTL_UPRIGHT:
|
|
case PIKA_TEXT_DIRECTION_TTB_LTR:
|
|
case PIKA_TEXT_DIRECTION_TTB_LTR_UPRIGHT:
|
|
{
|
|
PikaStroke *stroke = NULL;
|
|
|
|
while ((stroke = pika_vectors_stroke_get_next (vectors, stroke)))
|
|
{
|
|
pika_stroke_rotate (stroke, 0, 0, 270);
|
|
pika_stroke_translate (stroke, 0, box_width);
|
|
}
|
|
}
|
|
offset = 0.5 * box_width;
|
|
break;
|
|
}
|
|
|
|
pika_vectors_warp_vectors (vectors0->data, vectors, offset);
|
|
|
|
pika_item_set_visible (PIKA_ITEM (vectors), TRUE, FALSE);
|
|
|
|
pika_image_add_vectors (text_tool->image, vectors,
|
|
PIKA_IMAGE_ACTIVE_PARENT, -1, TRUE);
|
|
|
|
pika_image_flush (text_tool->image);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
PikaTextDirection
|
|
pika_text_tool_get_direction (PikaTextTool *text_tool)
|
|
{
|
|
PikaTextOptions *options = PIKA_TEXT_TOOL_GET_OPTIONS (text_tool);
|
|
return options->base_dir;
|
|
}
|