Code

add accessor for the waiting_cursor flag (sorry for the recompile)
[inkscape.git] / src / text-chemistry.cpp
1 #define __SP_TEXT_CHEMISTRY_C__
3 /*
4  * Text commands
5  *
6  * Authors:
7  *   bulia byak
8  *
9  * Copyright (C) 2004 authors
10  *
11  * Released under GNU GPL, read the file 'COPYING' for more information
12  */
14 #ifdef HAVE_CONFIG_H
15 # include <config.h>
16 #endif
18 #include <cstring>
19 #include <string>
20 #include <glibmm/i18n.h>
22 #include "libnr/nr-matrix-fns.h"
23 #include "xml/repr.h"
24 #include "sp-rect.h"
25 #include "sp-textpath.h"
26 #include "inkscape.h"
27 #include "desktop.h"
28 #include "document.h"
29 #include "message-stack.h"
30 #include "selection.h"
31 #include "style.h"
32 #include "desktop-handles.h"
33 #include "text-editing.h"
34 #include "text-chemistry.h"
35 #include "sp-flowtext.h"
36 #include "sp-flowregion.h"
37 #include "sp-flowdiv.h"
38 #include "sp-tspan.h"
41 SPItem *
42 text_in_selection(Inkscape::Selection *selection)
43 {
44     for (GSList *items = (GSList *) selection->itemList();
45          items != NULL;
46          items = items->next) {
47         if (SP_IS_TEXT(items->data))
48             return ((SPItem *) items->data);
49     }
50     return NULL;
51 }
53 SPItem *
54 flowtext_in_selection(Inkscape::Selection *selection)
55 {
56     for (GSList *items = (GSList *) selection->itemList();
57          items != NULL;
58          items = items->next) {
59         if (SP_IS_FLOWTEXT(items->data))
60             return ((SPItem *) items->data);
61     }
62     return NULL;
63 }
65 SPItem *
66 text_or_flowtext_in_selection(Inkscape::Selection *selection)
67 {
68     for (GSList *items = (GSList *) selection->itemList();
69          items != NULL;
70          items = items->next) {
71         if (SP_IS_TEXT(items->data) || SP_IS_FLOWTEXT(items->data))
72             return ((SPItem *) items->data);
73     }
74     return NULL;
75 }
77 SPItem *
78 shape_in_selection(Inkscape::Selection *selection)
79 {
80     for (GSList *items = (GSList *) selection->itemList();
81          items != NULL;
82          items = items->next) {
83         if (SP_IS_SHAPE(items->data))
84             return ((SPItem *) items->data);
85     }
86     return NULL;
87 }
89 void
90 text_put_on_path()
91 {
92     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
93     if (!desktop)
94         return;
96     Inkscape::Selection *selection = sp_desktop_selection(desktop);
98     SPItem *text = text_or_flowtext_in_selection(selection);
99     SPItem *shape = shape_in_selection(selection);
101     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(desktop->doc());
103     if (!text || !shape || g_slist_length((GSList *) selection->itemList()) != 2) {
104         sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>a text and a path</b> to put text on path."));
105         return;
106     }
108     if (SP_IS_TEXT_TEXTPATH(text)) {
109         sp_desktop_message_stack(desktop)->flash(Inkscape::ERROR_MESSAGE, _("This text object is <b>already put on a path</b>. Remove it from the path first. Use <b>Shift+D</b> to look up its path."));
110         return;
111     }
113     if (SP_IS_RECT(shape)) {
114         // rect is the only SPShape which is not <path> yet, and thus SVG forbids us from putting text on it
115         sp_desktop_message_stack(desktop)->flash(Inkscape::ERROR_MESSAGE, _("You cannot put text on a rectangle in this version. Convert rectangle to path first."));
116         return;
117     }
119     // if a flowed text is selected, convert it to a regular text object
120     if (SP_IS_FLOWTEXT(text)) {
122         if (!SP_FLOWTEXT(text)->layout.outputExists()) {
123             sp_desktop_message_stack(desktop)->
124                 flash(Inkscape::WARNING_MESSAGE, 
125                       _("The flowed text(s) must be <b>visible</b> in order to be put on a path."));
126         }
128         Inkscape::XML::Node *repr = SP_FLOWTEXT(text)->getAsText();
130         if (!repr) return;
132         Inkscape::XML::Node *parent = SP_OBJECT_REPR(text)->parent();
133         parent->appendChild(repr);
135         SPItem *new_item = (SPItem *) sp_desktop_document(desktop)->getObjectByRepr(repr);
136         sp_item_write_transform(new_item, repr, text->transform);
137         SP_OBJECT(new_item)->updateRepr();
139         Inkscape::GC::release(repr);
140         text->deleteObject(); // delete the orignal flowtext
142         sp_document_ensure_up_to_date(sp_desktop_document(desktop));
144         selection->clear();
146         text = new_item; // point to the new text
147     }
149     Inkscape::Text::Layout const *layout = te_get_layout(text);
150     Inkscape::Text::Layout::Alignment text_alignment = layout->paragraphAlignment(layout->begin());
152     // remove transform from text, but recursively scale text's fontsize by the expansion
153     SP_TEXT(text)->_adjustFontsizeRecursive (text, NR::expansion(SP_ITEM(text)->transform));
154     SP_OBJECT_REPR(text)->setAttribute("transform", NULL);
156     // make a list of text children
157     GSList *text_reprs = NULL;
158     for (SPObject *o = SP_OBJECT(text)->children; o != NULL; o = o->next) {
159         text_reprs = g_slist_prepend(text_reprs, SP_OBJECT_REPR(o));
160     }
162     // create textPath and put it into the text
163     Inkscape::XML::Node *textpath = xml_doc->createElement("svg:textPath");
164     // reference the shape
165     textpath->setAttribute("xlink:href", g_strdup_printf("#%s", SP_OBJECT_REPR(shape)->attribute("id")));
166     if (text_alignment == Inkscape::Text::Layout::RIGHT)
167         textpath->setAttribute("startOffset", "100%");
168     else if (text_alignment == Inkscape::Text::Layout::CENTER)
169         textpath->setAttribute("startOffset", "50%");
170     SP_OBJECT_REPR(text)->addChild(textpath, NULL);
172     for ( GSList *i = text_reprs ; i ; i = i->next ) {
173         // Make a copy of each text child
174         Inkscape::XML::Node *copy = ((Inkscape::XML::Node *) i->data)->duplicate(xml_doc);
175         // We cannot have multiline in textpath, so remove line attrs from tspans
176         if (!strcmp(copy->name(), "svg:tspan")) {
177             copy->setAttribute("sodipodi:role", NULL);
178             copy->setAttribute("x", NULL);
179             copy->setAttribute("y", NULL);
180         }
181         // remove the old repr from under text
182         SP_OBJECT_REPR(text)->removeChild((Inkscape::XML::Node *) i->data);
183         // put its copy into under textPath
184         textpath->addChild(copy, NULL); // fixme: copy id
185     }
187     // x/y are useless with textpath, and confuse Batik 1.5
188     SP_OBJECT_REPR(text)->setAttribute("x", NULL);
189     SP_OBJECT_REPR(text)->setAttribute("y", NULL);
191     sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, 
192                      _("Put text on path"));
193     g_slist_free(text_reprs);
196 void
197 text_remove_from_path()
199     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
201     Inkscape::Selection *selection = sp_desktop_selection(desktop);
203     if (selection->isEmpty()) {
204         sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>a text on path</b> to remove it from path."));
205         return;
206     }
208     bool did = false;
210     for (GSList *items = g_slist_copy((GSList *) selection->itemList());
211          items != NULL;
212          items = items->next) {
214         if (!SP_IS_TEXT_TEXTPATH(SP_OBJECT(items->data))) {
215             continue;
216         }
218         SPObject *tp = sp_object_first_child(SP_OBJECT(items->data));
220         did = true;
222         sp_textpath_to_text(tp);
223     }
225     if (!did) {
226         sp_desktop_message_stack(desktop)->flash(Inkscape::ERROR_MESSAGE, _("<b>No texts-on-paths</b> in the selection."));
227     } else {
228         sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, 
229                          _("Remove text from path"));
230         selection->setList(g_slist_copy((GSList *) selection->itemList())); // reselect to update statusbar description
231     }
234 void
235 text_remove_all_kerns_recursively(SPObject *o)
237     SP_OBJECT_REPR(o)->setAttribute("dx", NULL);
238     SP_OBJECT_REPR(o)->setAttribute("dy", NULL);
239     SP_OBJECT_REPR(o)->setAttribute("rotate", NULL);
241     // if x contains a list, leave only the first value
242     gchar *x = (gchar *) SP_OBJECT_REPR(o)->attribute("x");
243     if (x) {
244         gchar **xa_space = g_strsplit(x, " ", 0);
245         gchar **xa_comma = g_strsplit(x, ",", 0);
246         if (xa_space && *xa_space && *(xa_space + 1)) {
247             SP_OBJECT_REPR(o)->setAttribute("x", g_strdup(*xa_space));
248         } else if (xa_comma && *xa_comma && *(xa_comma + 1)) {
249             SP_OBJECT_REPR(o)->setAttribute("x", g_strdup(*xa_comma));
250         }
251         g_strfreev(xa_space);
252         g_strfreev(xa_comma);
253     }
255     for (SPObject *i = sp_object_first_child(o); i != NULL; i = SP_OBJECT_NEXT(i)) {
256         text_remove_all_kerns_recursively(i);
257     }
260 //FIXME: must work with text selection
261 void
262 text_remove_all_kerns()
264     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
266     Inkscape::Selection *selection = sp_desktop_selection(desktop);
268     if (selection->isEmpty()) {
269         sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>text(s)</b> to remove kerns from."));
270         return;
271     }
273     bool did = false;
275     for (GSList *items = g_slist_copy((GSList *) selection->itemList());
276          items != NULL;
277          items = items->next) {
278         SPObject *obj = SP_OBJECT(items->data);
280         if (!SP_IS_TEXT(obj) && !SP_IS_TSPAN(obj) && !SP_IS_FLOWTEXT(obj)) {
281             continue;
282         }
284         text_remove_all_kerns_recursively(obj);
285         obj->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_TEXT_LAYOUT_MODIFIED_FLAG);
286         did = true;
287     }
289     if (!did) {
290         sp_desktop_message_stack(desktop)->flash(Inkscape::ERROR_MESSAGE, _("Select <b>text(s)</b> to remove kerns from."));
291     } else {
292         sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, 
293                          _("Remove manual kerns"));
294     }
297 void
298 text_flow_into_shape()
300     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
301     if (!desktop)
302         return;
304     SPDocument *doc = sp_desktop_document (desktop);
305     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
307     Inkscape::Selection *selection = sp_desktop_selection(desktop);
309     SPItem *text = text_or_flowtext_in_selection(selection);
310     SPItem *shape = shape_in_selection(selection);
312     if (!text || !shape || g_slist_length((GSList *) selection->itemList()) < 2) {
313         sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>a text</b> and one or more <b>paths or shapes</b> to flow text into frame."));
314         return;
315     }
317     if (SP_IS_TEXT(text)) {
318       // remove transform from text, but recursively scale text's fontsize by the expansion
319       SP_TEXT(text)->_adjustFontsizeRecursive(text, NR::expansion(SP_ITEM(text)->transform));
320       SP_OBJECT_REPR(text)->setAttribute("transform", NULL);
321     }
323     Inkscape::XML::Node *root_repr = xml_doc->createElement("svg:flowRoot");
324     root_repr->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
325     root_repr->setAttribute("style", SP_OBJECT_REPR(text)->attribute("style")); // fixme: transfer style attrs too
326     SP_OBJECT_REPR(SP_OBJECT_PARENT(shape))->appendChild(root_repr);
327     SPObject *root_object = doc->getObjectByRepr(root_repr);
328     g_return_if_fail(SP_IS_FLOWTEXT(root_object));
330     Inkscape::XML::Node *region_repr = xml_doc->createElement("svg:flowRegion");
331     root_repr->appendChild(region_repr);
332     SPObject *object = doc->getObjectByRepr(region_repr);
333     g_return_if_fail(SP_IS_FLOWREGION(object));
335     /* Add clones */
336     for (GSList *items = (GSList *) selection->itemList();
337          items != NULL;
338          items = items->next) {
339         SPItem *item = SP_ITEM(items->data);
340         if (SP_IS_SHAPE(item)){
341             Inkscape::XML::Node *clone = xml_doc->createElement("svg:use");
342             clone->setAttribute("x", "0");
343             clone->setAttribute("y", "0");
344             clone->setAttribute("xlink:href", g_strdup_printf("#%s", SP_OBJECT_REPR(item)->attribute("id")));
346             // add the new clone to the region
347             region_repr->appendChild(clone);
348         }
349     }
351     if (SP_IS_TEXT(text)) { // flow from text, as string
352         Inkscape::XML::Node *para_repr = xml_doc->createElement("svg:flowPara");
353         root_repr->appendChild(para_repr);
354         object = doc->getObjectByRepr(para_repr);
355         g_return_if_fail(SP_IS_FLOWPARA(object));
357         Inkscape::Text::Layout const *layout = te_get_layout(text);
358         Glib::ustring text_ustring = sp_te_get_string_multiline(text, layout->begin(), layout->end());
360         Inkscape::XML::Node *text_repr = xml_doc->createTextNode(text_ustring.c_str()); // FIXME: transfer all formatting! and convert newlines into flowParas!
361         para_repr->appendChild(text_repr);
363         Inkscape::GC::release(para_repr);
364         Inkscape::GC::release(text_repr);
366     } else { // reflow an already flowed text, preserving paras
367         for (SPObject *o = SP_OBJECT(text)->children; o != NULL; o = o->next) {
368             if (SP_IS_FLOWPARA(o)) {
369                 Inkscape::XML::Node *para_repr = SP_OBJECT_REPR(o)->duplicate(xml_doc);
370                 root_repr->appendChild(para_repr);
371                 object = doc->getObjectByRepr(para_repr);
372                 g_return_if_fail(SP_IS_FLOWPARA(object));
373                 Inkscape::GC::release(para_repr);
374             }
375         }
376     }
378     SP_OBJECT(text)->deleteObject (true);
380     sp_document_done(doc, SP_VERB_CONTEXT_TEXT,
381                      _("Flow text into shape"));
383     sp_desktop_selection(desktop)->set(SP_ITEM(root_object));
385     Inkscape::GC::release(root_repr);
386     Inkscape::GC::release(region_repr);
389 void
390 text_unflow ()
392     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
393     if (!desktop)
394         return;
396     SPDocument *doc = sp_desktop_document (desktop);
397     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
399     Inkscape::Selection *selection = sp_desktop_selection(desktop);
402     if (!flowtext_in_selection(selection) || g_slist_length((GSList *) selection->itemList()) < 1) {
403         sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>a flowed text</b> to unflow it."));
404         return;
405     }
407     GSList *new_objs = NULL;
408     GSList *old_objs = NULL;
410     for (GSList *items = g_slist_copy((GSList *) selection->itemList());
411          items != NULL;
412          items = items->next) {
414         if (!SP_IS_FLOWTEXT(SP_OBJECT(items->data))) {
415             continue;
416         }
418         SPItem *flowtext = SP_ITEM(items->data);
420         if (sp_te_get_string_multiline(flowtext) == NULL) { // flowtext is empty
421             continue;
422         }
424         /* Create <text> */
425         Inkscape::XML::Node *rtext = xml_doc->createElement("svg:text");
426         rtext->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create
428         /* Set style */
429         rtext->setAttribute("style", SP_OBJECT_REPR(flowtext)->attribute("style")); // fixme: transfer style attrs too; and from descendants
431         NRRect bbox;
432         sp_item_invoke_bbox(SP_ITEM(flowtext), &bbox, sp_item_i2doc_affine(SP_ITEM(flowtext)), TRUE);
433         Geom::Point xy(bbox.x0, bbox.y0);
434         if (xy[Geom::X] != 1e18 && xy[Geom::Y] != 1e18) {
435             sp_repr_set_svg_double(rtext, "x", xy[Geom::X]);
436             sp_repr_set_svg_double(rtext, "y", xy[Geom::Y]);
437         }
439         /* Create <tspan> */
440         Inkscape::XML::Node *rtspan = xml_doc->createElement("svg:tspan");
441         rtspan->setAttribute("sodipodi:role", "line"); // otherwise, why bother creating the tspan?
442         rtext->addChild(rtspan, NULL);
444         gchar *text_string = sp_te_get_string_multiline(flowtext);
445         Inkscape::XML::Node *text_repr = xml_doc->createTextNode(text_string); // FIXME: transfer all formatting!!!
446         free(text_string);
447         rtspan->appendChild(text_repr);
449         SP_OBJECT_REPR(SP_OBJECT_PARENT(flowtext))->appendChild(rtext);
450         SPObject *text_object = doc->getObjectByRepr(rtext);
452         new_objs = g_slist_prepend (new_objs, text_object);
453         old_objs = g_slist_prepend (old_objs, flowtext);
455         Inkscape::GC::release(rtext);
456         Inkscape::GC::release(rtspan);
457         Inkscape::GC::release(text_repr);
458     }
460     selection->clear();
461     selection->setList(new_objs);
462     for (GSList *i = old_objs; i; i = i->next) {
463         SP_OBJECT(i->data)->deleteObject (true);
464     }
466     g_slist_free (old_objs);
467     g_slist_free (new_objs);
469     sp_document_done(doc, SP_VERB_CONTEXT_TEXT, 
470                      _("Unflow flowed text"));
473 void
474 flowtext_to_text()
476     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
478     Inkscape::Selection *selection = sp_desktop_selection(desktop);
480     if (selection->isEmpty()) {
481         sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, 
482                                                  _("Select <b>flowed text(s)</b> to convert."));
483         return;
484     }
486     bool did = false;
488     GSList *reprs = NULL;
489     GSList *items = g_slist_copy((GSList *) selection->itemList());
490     for (; items != NULL; items = items->next) {
491         
492         SPItem *item = (SPItem *) items->data;
494         if (!SP_IS_FLOWTEXT(item))
495             continue;
497         if (!SP_FLOWTEXT(item)->layout.outputExists()) {
498             sp_desktop_message_stack(desktop)->
499                 flash(Inkscape::WARNING_MESSAGE, 
500                       _("The flowed text(s) must be <b>visible</b> in order to be converted."));
501             return;
502         }
504         Inkscape::XML::Node *repr = SP_FLOWTEXT(item)->getAsText();
506         if (!repr) break;
508         did = true;
510         Inkscape::XML::Node *parent = SP_OBJECT_REPR(item)->parent();
511         parent->addChild(repr, SP_OBJECT_REPR(item));
513         SPItem *new_item = (SPItem *) sp_desktop_document(desktop)->getObjectByRepr(repr);
514         sp_item_write_transform(new_item, repr, item->transform);
515         SP_OBJECT(new_item)->updateRepr();
516     
517         Inkscape::GC::release(repr);
518         item->deleteObject();
520         reprs = g_slist_prepend(reprs, repr);
521     }
523     g_slist_free(items);
525     if (did) {
526         sp_document_done(sp_desktop_document(desktop), 
527                          SP_VERB_OBJECT_FLOWTEXT_TO_TEXT,
528                          _("Convert flowed text to text"));
529         selection->setReprList(reprs);        
530     } else {
531         sp_desktop_message_stack(desktop)->
532             flash(Inkscape::ERROR_MESSAGE,
533                   _("<b>No flowed text(s)</b> to convert in the selection."));
534     }
536     g_slist_free(reprs);
540 /*
541   Local Variables:
542   mode:c++
543   c-file-style:"stroustrup"
544   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
545   indent-tabs-mode:nil
546   fill-column:99
547   End:
548 */
549 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :