1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*  This file is part of the GtkHTML library.
3  *
4  *  Copyright (C) 2000 Helix Code, Inc.
5  *
6  *  This library is free software; you can redistribute it and/or
7  *  modify it under the terms of the GNU Library General Public
8  *  License as published by the Free Software Foundation; either
9  *  version 2 of the License, or (at your option) any later version.
10  *
11  *  This library is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  *  Library General Public License for more details.
15  *
16  *  You should have received a copy of the GNU Library General Public License
17  *  along with this library; see the file COPYING.LIB.  If not, write to
18  *  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  *  Boston, MA 02110-1301, USA.
20 */
21 
22 #include <config.h>
23 #include "htmlengine-edit-clueflowstyle.h"
24 #include "htmlundo.h"
25 #include "htmltype.h"
26 #include "htmlcursor.h"
27 #include "htmlselection.h"
28 
29 
30 /* Properties of a paragraph.  */
31 struct _ClueFlowProps {
32 	HTMLClueFlowStyle style;
33 	HTMLListType item_type;
34 	HTMLHAlignType alignment;
35 	GByteArray *levels;
36 };
37 typedef struct _ClueFlowProps ClueFlowProps;
38 
39 /* Data for the redo/undo operation.  */
40 struct _ClueFlowStyleOperation {
41 	HTMLUndoData data;
42 
43 	/* Whether we should go backward or forward when re-setting
44 	 * the style.  */
45 	gboolean forward;
46 
47 	/* List of properties for the paragraphs (ClueFlowProps).  */
48 	GList *prop_list;
49 };
50 typedef struct _ClueFlowStyleOperation ClueFlowStyleOperation;
51 
52 static void
free_prop(ClueFlowProps * props)53 free_prop (ClueFlowProps *props)
54 {
55 	g_byte_array_free (props->levels, TRUE);
56 	g_free (props);
57 }
58 
59 static void
free_prop_list(GList * list)60 free_prop_list (GList *list)
61 {
62 	GList *p;
63 
64 	for (p = list; p != NULL; p = p->next) {
65 		ClueFlowProps *props;
66 
67 		props = (ClueFlowProps *) p->data;
68 		free_prop (props);
69 	}
70 
71 	g_list_free (list);
72 }
73 
74 static void
style_operation_destroy(HTMLUndoData * data)75 style_operation_destroy (HTMLUndoData *data)
76 {
77 	free_prop_list (((ClueFlowStyleOperation *) data)->prop_list);
78 }
79 
80 static ClueFlowStyleOperation *
style_operation_new(GList * prop_list,gboolean forward)81 style_operation_new (GList *prop_list,
82                      gboolean forward)
83 {
84 	ClueFlowStyleOperation *op;
85 
86 	op = g_new (ClueFlowStyleOperation, 1);
87 
88 	html_undo_data_init (HTML_UNDO_DATA (op));
89 
90 	op->data.destroy = style_operation_destroy;
91 	op->prop_list    = prop_list;
92 	op->forward      = forward;
93 
94 	return op;
95 }
96 
97 static ClueFlowProps *
get_props(HTMLClueFlow * clueflow)98 get_props (HTMLClueFlow *clueflow)
99 {
100 	ClueFlowProps *props;
101 
102 	props = g_new (ClueFlowProps, 1);
103 
104 	props->levels      = html_clueflow_dup_levels (clueflow);
105 	props->alignment   = html_clueflow_get_halignment (clueflow);
106 	props->style       = html_clueflow_get_style (clueflow);
107 	props->item_type   = html_clueflow_get_item_type (clueflow);
108 
109 	return props;
110 }
111 
112 static void
set_props(HTMLEngine * engine,HTMLClueFlow * clueflow,HTMLClueFlowStyle style,HTMLListType item_type,HTMLHAlignType alignment,gint indentation_delta,guint8 * indentation_levels,HTMLEngineSetClueFlowStyleMask mask)113 set_props (HTMLEngine *engine,
114            HTMLClueFlow *clueflow,
115            HTMLClueFlowStyle style,
116            HTMLListType item_type,
117            HTMLHAlignType alignment,
118            gint indentation_delta,
119            guint8 *indentation_levels,
120            HTMLEngineSetClueFlowStyleMask mask)
121 {
122 	if (mask & HTML_ENGINE_SET_CLUEFLOW_INDENTATION)
123 		html_clueflow_set_indentation (clueflow, engine, indentation_delta, indentation_levels);
124 
125 	if (mask & HTML_ENGINE_SET_CLUEFLOW_INDENTATION_DELTA)
126 		html_clueflow_modify_indentation_by_delta (clueflow, engine, indentation_delta, indentation_levels);
127 
128 	/* FIXME levels mostly work now */
129 	if (mask & HTML_ENGINE_SET_CLUEFLOW_STYLE) {
130 		if (style == HTML_CLUEFLOW_STYLE_LIST_ITEM && clueflow->style != HTML_CLUEFLOW_STYLE_LIST_ITEM
131 		    && clueflow->levels->len == 0
132 		    && !(mask & (HTML_ENGINE_SET_CLUEFLOW_INDENTATION | HTML_ENGINE_SET_CLUEFLOW_INDENTATION_DELTA))) {
133 			guint8 tmp = item_type;
134 			html_clueflow_set_indentation (clueflow, engine, 1, &tmp);
135 		} else if (clueflow->style == HTML_CLUEFLOW_STYLE_LIST_ITEM && style != HTML_CLUEFLOW_STYLE_LIST_ITEM
136 			   && clueflow->levels->len == 1
137 			   && !(mask & (HTML_ENGINE_SET_CLUEFLOW_INDENTATION | HTML_ENGINE_SET_CLUEFLOW_INDENTATION_DELTA))) {
138 			html_clueflow_set_indentation (clueflow, engine, 0, NULL);
139 		}
140 		html_clueflow_set_style (clueflow, engine, style);
141 		html_clueflow_set_item_type (clueflow, engine, item_type);
142 		html_object_change_set_down (HTML_OBJECT (clueflow), HTML_CHANGE_ALL);
143 	}
144 	if (mask & HTML_ENGINE_SET_CLUEFLOW_ALIGNMENT)
145 		html_clueflow_set_halignment (clueflow, engine, alignment);
146 
147 }
148 
149 
150 /* Undo/redo operations.  */
151 
152 static void add_undo (HTMLEngine *engine, ClueFlowStyleOperation *op, HTMLUndoDirection dir);
153 
154 static void
undo_or_redo(HTMLEngine * engine,HTMLUndoData * data,HTMLUndoDirection dir,guint position_after)155 undo_or_redo (HTMLEngine *engine,
156               HTMLUndoData *data,
157               HTMLUndoDirection dir,
158               guint position_after)
159 {
160 	ClueFlowStyleOperation *op, *new_op;
161 	ClueFlowProps *props, *orig_props;
162 	HTMLObject *obj;
163 	HTMLClueFlow *clueflow;
164 	GList *prop_list;
165 	GList *p;
166 
167 	op = (ClueFlowStyleOperation *) data;
168 	g_assert (op != NULL);
169 	g_assert (op->prop_list != NULL);
170 
171 	obj = engine->cursor->object;
172 	g_assert (obj != NULL);
173 
174 	prop_list = NULL;
175 
176 	p = op->prop_list;
177 
178 	while (p != NULL && obj != NULL) {
179 		if (HTML_OBJECT_TYPE (obj->parent) != HTML_TYPE_CLUEFLOW) {
180 			g_warning ("(%s:%s)  Eeeek!  Unknown parent type `%s'.",
181 				   __FILE__, G_STRFUNC,
182 				   html_type_name (HTML_OBJECT_TYPE (obj->parent)));
183 			break;
184 		}
185 
186 		clueflow = HTML_CLUEFLOW (obj->parent);
187 
188 		orig_props = get_props (clueflow);
189 		prop_list = g_list_prepend (prop_list, orig_props);
190 
191 		props = (ClueFlowProps *) p->data;
192 
193 		html_clueflow_set_levels (clueflow, engine, props->levels);
194 		html_clueflow_set_style (clueflow, engine, props->style);
195 		html_clueflow_set_item_type (clueflow, engine, props->item_type);
196 		html_clueflow_set_levels (clueflow, engine, props->levels);
197 		html_clueflow_set_halignment (clueflow, engine, props->alignment);
198 
199 		p = p->next;
200 		if (p == NULL)
201 			break;
202 
203 		/* Go forward object by object, until we find one
204 		 * whose parent (i.e. paragraph) is different.  */
205 		do {
206 			if (op->forward)
207 				obj = html_object_next_leaf (obj);
208 			else
209 				obj = html_object_prev_leaf (obj);
210 
211 			if (obj == NULL) {
212 				/* This should not happen.  */
213 				g_warning ("(%s:%s)  There were not enough paragraphs for "
214 					   "setting the paragraph style.",
215 					   __FILE__, G_STRFUNC);
216 				break;
217 			}
218 		} while (obj != NULL && HTML_CLUEFLOW (obj->parent) == clueflow);
219 	}
220 
221 	if (prop_list == NULL) {
222 		/* This should not happen.  */
223 		g_warning ("%s:%s Eeek!  Nothing done?", __FILE__, G_STRFUNC);
224 		return;
225 	}
226 
227 	prop_list = g_list_reverse (prop_list);
228 
229 	new_op = style_operation_new (prop_list, op->forward);
230 
231 	add_undo (engine, new_op, html_undo_direction_reverse (dir));
232 }
233 
234 static HTMLUndoAction *
undo_action_from_op(HTMLEngine * engine,ClueFlowStyleOperation * op)235 undo_action_from_op (HTMLEngine *engine,
236                      ClueFlowStyleOperation *op)
237 {
238 	return html_undo_action_new ("Paragraph style change",
239 				     undo_or_redo, HTML_UNDO_DATA (op),
240 				     html_cursor_get_position (engine->cursor),
241 				     html_cursor_get_position (engine->cursor));
242 }
243 
244 static void
add_undo(HTMLEngine * engine,ClueFlowStyleOperation * op,HTMLUndoDirection dir)245 add_undo (HTMLEngine *engine,
246           ClueFlowStyleOperation *op,
247           HTMLUndoDirection dir)
248 {
249 	html_undo_add_action (engine->undo, engine, undo_action_from_op (engine, op), dir);
250 }
251 
252 
253 /* "Do" operations.  */
254 
255 static void
set_clueflow_style_in_region(HTMLEngine * engine,HTMLClueFlowStyle style,HTMLListType item_type,HTMLHAlignType alignment,gint indentation_delta,guint8 * indentation_levels,HTMLEngineSetClueFlowStyleMask mask,HTMLUndoDirection dir,gboolean do_undo)256 set_clueflow_style_in_region (HTMLEngine *engine,
257                               HTMLClueFlowStyle style,
258                               HTMLListType item_type,
259                               HTMLHAlignType alignment,
260                               gint indentation_delta,
261                               guint8 *indentation_levels,
262                               HTMLEngineSetClueFlowStyleMask mask,
263                               HTMLUndoDirection dir,
264                               gboolean do_undo)
265 {
266 	HTMLClueFlow *clueflow;
267 	HTMLObject *start, *end, *p;
268 	GList *prop_list;
269 	gboolean undo_forward;
270 
271 	if (html_cursor_precedes (engine->cursor, engine->mark)) {
272 		start = engine->cursor->object;
273 		end = engine->mark->object;
274 		undo_forward = TRUE;
275 	} else {
276 		start = engine->mark->object;
277 		end = engine->cursor->object;
278 		undo_forward = FALSE;
279 	}
280 
281 	prop_list = NULL;
282 
283 	p = start;
284 	while (p != NULL) {
285 		if (HTML_OBJECT_TYPE (p->parent) != HTML_TYPE_CLUEFLOW) {
286 			g_warning ("(%s:%s)  Eeeek!  Unknown parent type `%s'.",
287 				   __FILE__, G_STRFUNC,
288 				   html_type_name (HTML_OBJECT_TYPE (p->parent)));
289 			break;
290 		}
291 
292 		clueflow = HTML_CLUEFLOW (p->parent);
293 
294 		if (do_undo)
295 			prop_list = g_list_prepend (prop_list, get_props (clueflow));
296 
297 		set_props (engine, clueflow,
298 			   style, item_type, alignment, indentation_delta, indentation_levels,
299 			   mask);
300 
301 		if (p == end)
302 			break;
303 
304 		do {
305 			p = html_object_next_leaf (p);
306 		} while (p != NULL && p != end && HTML_CLUEFLOW (p->parent) == clueflow);
307 
308 		if (p == end && HTML_CLUEFLOW (p->parent) == clueflow)
309 			break;
310 	}
311 
312 	if (!do_undo)
313 		return;
314 
315 	add_undo (engine, style_operation_new (undo_forward ? g_list_reverse (prop_list) : prop_list, undo_forward), dir);
316 }
317 
318 static void
set_clueflow_style_at_cursor(HTMLEngine * engine,HTMLClueFlowStyle style,HTMLListType item_type,HTMLHAlignType alignment,gint indentation_delta,guint8 * indentation_levels,HTMLEngineSetClueFlowStyleMask mask,HTMLUndoDirection dir,gboolean do_undo)319 set_clueflow_style_at_cursor (HTMLEngine *engine,
320                               HTMLClueFlowStyle style,
321                               HTMLListType item_type,
322                               HTMLHAlignType alignment,
323                               gint indentation_delta,
324                               guint8 *indentation_levels,
325                               HTMLEngineSetClueFlowStyleMask mask,
326                               HTMLUndoDirection dir,
327                               gboolean do_undo)
328 {
329 	HTMLClueFlow *clueflow;
330 	HTMLObject *curr;
331 
332 	curr = engine->cursor->object;
333 
334 	g_return_if_fail (curr != NULL);
335 	g_return_if_fail (curr->parent != NULL);
336 	g_return_if_fail (HTML_OBJECT_TYPE (curr->parent) == HTML_TYPE_CLUEFLOW);
337 
338 	clueflow = HTML_CLUEFLOW (curr->parent);
339 
340 	if (do_undo)
341 		add_undo (engine, style_operation_new (g_list_append (NULL, get_props (clueflow)), TRUE), dir);
342 
343 	set_props (engine, clueflow,
344 		   style, item_type, alignment,
345 		   indentation_delta,
346 		   indentation_levels,
347 		   mask);
348 
349 }
350 
351 
352 gboolean
html_engine_set_clueflow_style(HTMLEngine * engine,HTMLClueFlowStyle style,HTMLListType item_type,HTMLHAlignType alignment,gint indentation_delta,guint8 * indentation_levels,HTMLEngineSetClueFlowStyleMask mask,HTMLUndoDirection dir,gboolean do_undo)353 html_engine_set_clueflow_style (HTMLEngine *engine,
354                                 HTMLClueFlowStyle style,
355                                 HTMLListType item_type,
356                                 HTMLHAlignType alignment,
357                                 gint indentation_delta,
358                                 guint8 *indentation_levels,
359                                 HTMLEngineSetClueFlowStyleMask mask,
360                                 HTMLUndoDirection dir,
361                                 gboolean do_undo)
362 {
363 	g_return_val_if_fail (engine != NULL, FALSE);
364 	g_return_val_if_fail (HTML_IS_ENGINE (engine), FALSE);
365 
366 	html_engine_freeze (engine);
367 	if (html_engine_is_selection_active (engine))
368 		set_clueflow_style_in_region (engine,
369 					      style, item_type, alignment,
370 					      indentation_delta, indentation_levels,
371 					      mask,
372 					      dir, do_undo);
373 	else
374 		set_clueflow_style_at_cursor (engine,
375 					      style, item_type, alignment,
376 					      indentation_delta, indentation_levels,
377 					      mask,
378 					      dir, do_undo);
379 	html_engine_thaw (engine);
380 
381 	/* This operation can never fail.  */
382 	return TRUE;
383 }
384 
385 
386 /* The following functions are used to report the current indentation
387  * as it should be shown e.g in a toolbar.  */
388 
389 static HTMLClueFlow *
get_current_para(HTMLEngine * engine)390 get_current_para (HTMLEngine *engine)
391 {
392 	HTMLObject *current;
393 	HTMLObject *parent;
394 
395 	current = engine->cursor->object;
396 	if (current == NULL)
397 		return NULL;
398 
399 	parent = current->parent;
400 	if (parent == NULL)
401 		return NULL;
402 
403 	if (HTML_OBJECT_TYPE (parent) != HTML_TYPE_CLUEFLOW)
404 		return NULL;
405 
406 	return HTML_CLUEFLOW (parent);
407 }
408 
409 void
html_engine_get_current_clueflow_style(HTMLEngine * engine,HTMLClueFlowStyle * style,HTMLListType * item_type)410 html_engine_get_current_clueflow_style (HTMLEngine *engine,
411                                         HTMLClueFlowStyle *style,
412                                         HTMLListType *item_type)
413 {
414 	HTMLClueFlow *para;
415 
416 	/* FIXME TODO region */
417 
418 	*style = HTML_CLUEFLOW_STYLE_NORMAL;
419 	*item_type = HTML_LIST_TYPE_UNORDERED;
420 
421 	g_return_if_fail (engine != NULL);
422 	g_return_if_fail (HTML_IS_ENGINE (engine));
423 
424 	para = get_current_para (engine);
425 	if (para) {
426 		*style = para->style;
427 		*item_type = para->item_type;
428 	}
429 }
430 
431 guint
html_engine_get_current_clueflow_indentation(HTMLEngine * engine)432 html_engine_get_current_clueflow_indentation (HTMLEngine *engine)
433 {
434 	HTMLClueFlow *para;
435 
436 	/* FIXME TODO region */
437 
438 	g_return_val_if_fail (engine != NULL, 0);
439 	g_return_val_if_fail (HTML_IS_ENGINE (engine), 0);
440 
441 	para = get_current_para (engine);
442 	if (para == NULL)
443 		return 0;
444 
445 	/* FIXME levels TODO levels */
446 	return para->levels->len;
447 }
448 
449 HTMLHAlignType
html_engine_get_current_clueflow_alignment(HTMLEngine * engine)450 html_engine_get_current_clueflow_alignment (HTMLEngine *engine)
451 {
452 	HTMLClueFlow *para;
453 
454 	/* FIXME TODO region */
455 
456 	g_return_val_if_fail (engine != NULL, HTML_HALIGN_LEFT);
457 	g_return_val_if_fail (HTML_IS_ENGINE (engine), HTML_HALIGN_LEFT);
458 
459 	para = get_current_para (engine);
460 	if (para == NULL)
461 		return 0;
462 
463 	return html_clueflow_get_halignment (para);
464 }
465