ms-obj.c 12 KB
Newer Older
1 2 3 4
/**
 * ms-obj.c: MS Excel Object support for Gnumeric
 *
 * Author:
5
 *    Jody Goldberg (jgoldberg@home.com)
6
 *    Michael Meeks (mmeeks@gnu.org)
7
 *
Jody Goldberg's avatar
Jody Goldberg committed
8
 * (C) 1998, 1999, 2000 Jody Goldberg, Michael Meeks
9
 **/
10

11
#include <config.h>
12

13
#include "boot.h"
14
#include "ms-obj.h"
Michael Meeks's avatar
Michael Meeks committed
15
#include "ms-chart.h"
16
#include "ms-escher.h"
17
#include "parse-util.h"
18 19
#include "sheet-object-widget.h"
#include "sheet-object-graphic.h"
20 21 22

#define GR_END                0x00
#define GR_MACRO              0x04
23 24
#define GR_COMMAND_BUTTON     0x05
#define GR_GROUP_BUTTON       0x06
25 26 27
#define GR_CLIPBOARD_FORMAT   0x07
#define GR_PICTURE_OPTIONS    0x08
#define GR_PICTURE_FORMULA    0x09
28 29 30 31 32 33 34 35 36 37 38
#define GR_CHECKBOX_LINK      0x0A
#define GR_RADIO_BUTTON       0x0B
#define GR_SCROLLBAR          0x0C
#define GR_NOTE_STRUCTURE     0x0D
#define GR_SCROLLBAR_FORMULA  0x0E
#define GR_GROUP_BOX_DATA     0x0F
#define GR_EDIT_CONTROL_DATA  0x10
#define GR_RADIO_BUTTON_DATA  0x11
#define GR_CHECKBOX_DATA      0x12
#define GR_LISTBOX_DATA       0x13
#define GR_CHECKBOX_FORMULA   0x14
39 40
#define GR_COMMON_OBJ_DATA    0x15

41
void
42
ms_destroy_OBJ (MSObj *obj)
43
{
Morten Welinder's avatar
Morten Welinder committed
44 45 46 47 48
	if (obj) {
		/* TODO : Fill in the blank */
		if (obj->gnum_obj) {
			gtk_object_unref (obj->gnum_obj);
		}
49
		g_free (obj);
Morten Welinder's avatar
Morten Welinder committed
50
	}
51
}
52

53 54 55
/*
 * See: S59EOE.HTM
 */
56
char *
57
ms_read_TXO (BiffQuery *q)
58
{
59
	static char const * const orientations [] = {
60 61 62 63
		"Left to right",
		"Top to Bottom",
		"Bottom to Top on Side",
		"Top to Bottom on Side"
64
	};
65
	static char const * const haligns [] = {
66 67
		"At left", "Horizontaly centered",
		"At right", "Horizontaly justified"
68
	};
69
	static char const * const valigns [] = {
70 71
		"At top", "Verticaly centered",
		"At bottom", "Verticaly justified"
72
	};
73

74 75 76 77
	guint16 const options     = MS_OLE_GET_GUINT16 (q->data);
	guint16 const orient      = MS_OLE_GET_GUINT16 (q->data + 2);
	guint16 const text_len    = MS_OLE_GET_GUINT16 (q->data + 10);
/*	guint16 const num_formats = MS_OLE_GET_GUINT16 (q->data + 12);*/
78 79
	int const halign = (options >> 1) & 0x7;
	int const valign = (options >> 4) & 0x7;
80 81
	char         *text = g_new (char, text_len + 1);
	guint16       peek_op;
82

83 84 85
	g_return_val_if_fail (orient <= 3, NULL);
	g_return_val_if_fail (1 <= halign && halign <= 4, NULL);
	g_return_val_if_fail (1 <= valign && valign <= 4, NULL);
86

87 88 89 90 91 92 93
	text [0] = '\0';
	if (ms_biff_query_peek_next (q, &peek_op) &&
	    peek_op == BIFF_CONTINUE) {
		guint8 *data;
		int i, increment = 1;

		ms_biff_query_next (q);
94 95
		increment = (MS_OLE_GET_GUINT8 (q->data)) ? 2 : 1;
		data = q->data + 1;
96 97 98 99 100 101 102

		/*
		 * FIXME: Use biff_get_text or something ?
		 */
		if (q->length < increment * text_len) {
			g_free (text);
			text = g_strdup ("Broken continue");
103
		} else {
104 105 106
			for (i = 0; i < text_len ; ++i)
				text [i] = data [i * increment];
			text [text_len] = '\0';
107
		}
108 109 110 111 112 113 114 115

		if (ms_biff_query_peek_next (q, &peek_op) &&
		    peek_op == BIFF_CONTINUE)
			ms_biff_query_next (q);
		else
			g_warning ("Unusual, TXO text with no formatting");
	} else if (text_len > 0)
		g_warning ("TXO len of %d but no continue", text_len);
116

117
#ifndef NO_DEBUG_EXCEL
Jody Goldberg's avatar
Jody Goldberg committed
118
	if (ms_excel_object_debug > 0) {
Jody Goldberg's avatar
Jody Goldberg committed
119 120 121 122 123 124
		printf ("{ TextObject\n");
		printf ("Text '%s'\n", text);
		printf ("is %s, %s & %s;\n",
			orientations[orient], haligns[halign], valigns[valign]);
		printf ("}; /* TextObject */\n");
	}
125
#endif
126
	return text;
127 128 129
}

static void
130
ms_obj_dump (guint8 const *data, int len, int data_left, char const *name)
131
{
132
#ifndef NO_DEBUG_EXCEL
Jody Goldberg's avatar
Jody Goldberg committed
133
	if (ms_excel_object_debug < 2)
134 135 136
		return;

	printf ("{ %s \n", name);
137 138 139 140 141 142
	if (len+4 > data_left) {
		printf ("/* invalid length %d (0x%x) > %d(0x%x)*/\n",
			len+4, len+4, data_left, data_left);
		len = data_left - 4;
	}
	ms_ole_dump (data, len+4);
143
	printf ("}; /* %s */\n", name);
144
#endif
145 146
}

147 148 149 150

/*
 * See: S59DAD.HTM
 */
151
static gboolean
152
ms_obj_read_pre_biff8_obj (BiffQuery *q, MSContainer *container, MSObj *obj)
153
{
154 155
	/* TODO : Lots of docs for these things.  Write the parser. */

156
#if 0
157 158
	guint32 const numObjects = MS_OLE_GET_GUINT16(q->data);
	guint16 const flags = MS_OLE_GET_GUINT16(q->data+8);
159
#endif
160 161
	obj->excel_type = MS_OLE_GET_GUINT16(q->data + 4);
	obj->id         = MS_OLE_GET_GUINT32(q->data + 6);
162

163 164 165 166
	memcpy (obj->raw_anchor, q->data+8, MS_ANCHOR_SIZE);
	obj->anchor_set = TRUE;

	return TRUE;
167 168
}

169 170 171
/*
 * See: S59DAD.HTM
 */
172
static gboolean
173
ms_obj_read_biff8_obj (BiffQuery *q, MSContainer *container, MSObj *obj)
174 175 176
{
	guint8 *data;
	gint32 data_len_left;
177
	gboolean hit_end = FALSE;
Jody Goldberg's avatar
Jody Goldberg committed
178
	gboolean next_biff_record_maybe_imdata = FALSE;
179

180 181
	g_return_val_if_fail (q, TRUE);
	g_return_val_if_fail (q->ls_op == BIFF_OBJ, TRUE);
182 183 184 185

	data = q->data;
	data_len_left = q->length;

186 187 188 189
#if 0
	dump_biff (q);
#endif

190
	/* Scan through the pseudo BIFF substream */
191
	while (data_len_left > 0 && !hit_end) {
192
		guint16 const record_type = MS_OLE_GET_GUINT16(data);
193

194 195 196 197 198 199
		/* All the sub-records seem to have this layout
		 * 2001/Mar/29 JEG : liars.  Ok not all records have this
		 * layout.  Create a list box.  It seems to do something
		 * unique.  It acts like an end, and has no length specified.
		 */
		guint16 len = MS_OLE_GET_GUINT16(data+2);
200 201

		/* 1st record must be COMMON_OBJ*/
202 203 204
		g_return_val_if_fail (obj->excel_type >= 0 ||
				      record_type == GR_COMMON_OBJ_DATA,
				      TRUE);
205 206

		switch (record_type) {
207
		case GR_END:
208
			g_return_val_if_fail (len == 0, TRUE);
209
			ms_obj_dump (data, len, data_len_left, "ObjEnd");
210
			hit_end = TRUE;
211
			break;
212 213

		case GR_MACRO :
214
			ms_obj_dump (data, len, data_len_left, "MacroObject");
215 216 217
			break;

		case GR_COMMAND_BUTTON :
218
			ms_obj_dump (data, len, data_len_left, "CommandButton");
219
			break;
220

221
		case GR_GROUP_BUTTON :
222
			ms_obj_dump (data, len, data_len_left, "GroupButton");
223 224
			break;

225
		case GR_CLIPBOARD_FORMAT:
226
			ms_obj_dump (data, len, data_len_left, "ClipboardFmt");
227
			break;
228

229
		case GR_PICTURE_OPTIONS:
230 231 232
		{
			guint16 pict_opt;
			g_return_val_if_fail (len == 2, TRUE);
233

234 235 236
			pict_opt = MS_OLE_GET_GUINT16(data+4);

#ifndef NO_DEBUG_EXCEL
Jody Goldberg's avatar
Jody Goldberg committed
237
			if (ms_excel_object_debug >= 1) {
238 239 240 241 242 243
				printf ("{ /* PictOpt */\n");
				printf ("value = %d;\n", pict_opt);
				printf ("}; /* PictOpt */\n");
			}
#endif

Jody Goldberg's avatar
Jody Goldberg committed
244
			next_biff_record_maybe_imdata = TRUE;
245
			break;
246
		}
247

248
		case GR_PICTURE_FORMULA:
249
			ms_obj_dump (data, len, data_len_left, "PictFormula");
250
			break;
251

252
		case GR_CHECKBOX_LINK :
253
			ms_obj_dump (data, len, data_len_left, "CheckboxLink");
254
			break;
255

256
		case GR_RADIO_BUTTON :
257
			ms_obj_dump (data, len, data_len_left, "RadioButton");
258
			break;
259

260
		case GR_SCROLLBAR :
261
			ms_obj_dump (data, len, data_len_left, "ScrollBar");
262
			break;
263

264
		case GR_NOTE_STRUCTURE :
265
			ms_obj_dump (data, len, data_len_left, "Note");
266
			break;
267

268
		case GR_SCROLLBAR_FORMULA :
269 270 271 272 273
			/* A touch of spelunking suggests that
			 * 0x06 uint16 == first visible element (0 based)
			 * 0x12 uint16 == number of elements
			 * 0x14 uint16 == current element (1 based)
			 */
274
			ms_obj_dump (data, len, data_len_left, "ScrollbarFmla");
275
			break;
276

277
		case GR_GROUP_BOX_DATA :
278
			ms_obj_dump (data, len, data_len_left, "GroupBoxData");
279
			break;
280

281
		case GR_EDIT_CONTROL_DATA :
282
			ms_obj_dump (data, len, data_len_left, "EditCtrlData");
283
			break;
284

285
		case GR_RADIO_BUTTON_DATA :
286
			ms_obj_dump (data, len, data_len_left, "RadioData");
287
			break;
288

289
		case GR_CHECKBOX_DATA :
290
			ms_obj_dump (data, len, data_len_left, "CheckBoxData");
291
			break;
292

293
		case GR_LISTBOX_DATA :
294
		{
295 296 297 298 299 300 301 302
			/* FIXME : find some docs for this
			 * It seems as if list box data does not conform to
			 * the docs.  It acts like an end and has no size.
			 */
			hit_end = TRUE;
			len = data_len_left - 4;

			ms_obj_dump (data, len, data_len_left, "ListBoxData");
303
			break;
304
		}
305

306
		case GR_CHECKBOX_FORMULA :
307 308 309
		{
			guint16 const row = MS_OLE_GET_GUINT16(data+11);
			guint16 const col = MS_OLE_GET_GUINT16(data+13) &0x3fff;
310
#ifndef NO_DEBUG_EXCEL
Jody Goldberg's avatar
Jody Goldberg committed
311
			if (ms_excel_object_debug > 0)
312
				printf ("Checkbox linked to : %s%d\n", col_name(col), row+1);
313
			ms_obj_dump (data, len, data_len_left, "CheckBoxFmla");
314
#endif
315
			break;
316
		}
317

318 319
		case GR_COMMON_OBJ_DATA:
		{
320
			guint16 const options =MS_OLE_GET_GUINT16 (data+8);
321 322

			/* Multiple objects in 1 record ?? */
323
			g_return_val_if_fail (obj->excel_type == -1, -1);
324

325 326
			obj->excel_type = MS_OLE_GET_GUINT16(data+4);
			obj->id = MS_OLE_GET_GUINT16(data+6);
327

328
#ifndef NO_DEBUG_EXCEL
329
			/* only print when debug is enabled */
Jody Goldberg's avatar
Jody Goldberg committed
330
			if (ms_excel_object_debug == 0)
331 332
				break;

333
			if (options&0x0001)
334
				printf ("Locked;\n");
335
			if (options&0x0010)
336
				printf ("Printable;\n");
337
			if (options&0x2000)
338
				printf ("AutoFilled;\n");
339
			if (options&0x4000)
340
				printf ("AutoLines;\n");
341 342 343 344

			if ((options & 0x9fee) != 0)
				printf ("WARNING : Why is option not 0 (%x)\n",
					options & 0x9fee);
345
#endif
346
		}
347 348
		break;

349
		default:
350
			printf ("ERROR : Unknown Obj record 0x%x len 0x%x dll %d;\n",
351
				record_type, len, data_len_left);
352
		}
353 354 355

		if (data_len_left < len+4)
			printf ("record len %d (0x%x) > %d\n", len+4, len+4, data_len_left);
356 357

		/* FIXME : We need a structure akin to the escher code to do this properly */
358
		for (data_len_left -= len+4; data_len_left < 0; ) {
359 360
			guint16 peek_op;

361 362
			printf ("deficit of %d\n", data_len_left);

363 364 365 366
			/* FIXME : what do we expect here ??
			 * I've seen what seem to be embedded drawings
			 * but I am not sure what is embedding what.
			 */
367 368 369 370 371 372 373 374 375
			if (!ms_biff_query_peek_next (q, &peek_op) ||
			    (peek_op != BIFF_CONTINUE &&
			     peek_op != BIFF_MS_O_DRAWING &&
			     peek_op != BIFF_TXO &&
			     peek_op != BIFF_OBJ)) {
				printf ("0x%x vs 0x%x\n", q->opcode, peek_op);
				return TRUE;
			}

376 377
			ms_biff_query_next (q);
			data_len_left += q->length;
378
			printf ("merged in 0x%x with len %d\n", q->opcode, q->length);
379
		}
380
		data = q->data + q->length - data_len_left;
381 382 383
	}

	/* The ftEnd record should have been the last */
Jody Goldberg's avatar
Jody Goldberg committed
384
	if (data_len_left > 0) {
Jody Goldberg's avatar
Jody Goldberg committed
385 386 387
		printf("OBJ : unexpected extra data after Object End record;\n");
		ms_ole_dump (data, data_len_left);
		return TRUE;
388
	}
Jody Goldberg's avatar
Jody Goldberg committed
389 390

	/* Catch underflow too */
391
	g_return_val_if_fail (data_len_left == 0, TRUE);
392

Jody Goldberg's avatar
Jody Goldberg committed
393 394 395 396 397
	/* FIXME : Throw away the IMDATA that may follow.
	 * I am not sure when the IMDATA does follow, or how to display it,
	 * but very careful in case it is not there.
	 */
	if (next_biff_record_maybe_imdata) {
398
		guint16 op;
399

400 401 402 403 404
		if (ms_biff_query_peek_next (q, &op) && op == BIFF_IMDATA) {
			ms_biff_query_next (q);
			while (ms_biff_query_peek_next (q, &op) &&
			       op == BIFF_CONTINUE)
				ms_biff_query_next (q);
Jody Goldberg's avatar
Jody Goldberg committed
405
		}
406 407
	}

408
	return FALSE;
409 410
}

411
MSObj *
412
ms_read_OBJ (BiffQuery *q, MSContainer *container)
413
{
414
	static char const * const object_type_names[] =
415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443
	{
		"Group", 	/* 0x00 */
		"Line",		/* 0x01 */
		"Rectangle",	/* 0x02 */
		"Oval",		/* 0x03 */
		"Arc",		/* 0x04 */
		"Chart",	/* 0x05 */
		"TextBox",	/* 0x06 */
		"Button",	/* 0x07 */
		"Picture",	/* 0x08 */
		"Polygon",	/* 0x09 */
		NULL,		/* 0x0A */
		"CheckBox",	/* 0x0B */
		"Option",	/* 0x0C */
		"Edit",		/* 0x0D */
		"Label",	/* 0x0E */
		"Dialog",	/* 0x0F */
		"Spinner",	/* 0x10 */
		"Scroll",	/* 0x11 */
		"List",		/* 0x12 */
		"Group",	/* 0x13 */
		"Combo",	/* 0x14 */
		NULL, NULL, NULL, NULL, /* 0x15 - 0x18 */
		"Comment",	/* 0x19 */
		NULL, NULL, NULL, NULL,	/* 0x1A - 0x1D */
		"MS Drawing"	/* 0x1E */
	};

	gboolean errors;
444 445
	MSObj *obj = g_new(MSObj, 1);

446 447 448 449
	obj->excel_type = (unsigned)-1; /* Set to undefined */
	obj->id = -1;
	obj->anchor_set = FALSE;

Jody Goldberg's avatar
Jody Goldberg committed
450 451 452 453
#ifndef NO_DEBUG_EXCEL
	if (ms_excel_object_debug > 0)
		printf ("{ /* OBJ start */\n");
#endif
454 455 456
	errors = (container->ver >= MS_BIFF_V8)
		? ms_obj_read_biff8_obj (q, container, obj)
		: ms_obj_read_pre_biff8_obj (q, container, obj);
457 458 459

	if (errors) {
		g_free (obj);
Jody Goldberg's avatar
Jody Goldberg committed
460 461 462 463
#ifndef NO_DEBUG_EXCEL
		if (ms_excel_object_debug > 0)
			printf ("}; /* OBJ error 1 */\n");
#endif
464 465 466
		return NULL;
	}

467
	obj->excel_type_name = NULL;
468
	if (obj->excel_type < sizeof(object_type_names)/sizeof(char*))
469
		obj->excel_type_name = object_type_names [obj->excel_type];
470 471
	if (obj->excel_type_name == NULL)
		obj->excel_type_name = "Unknown";
472

473
	obj->gnum_obj = (*container->vtbl->create_obj) (container, obj);
474 475 476 477 478
	if (obj->gnum_obj == NULL) {
		g_free (obj);
		return NULL;
	}

479 480 481
	/* Chart, There should be a BOF next */
	if (obj->excel_type == 0x5)
		ms_excel_read_chart (q, container);
482 483

#ifndef NO_DEBUG_EXCEL
Jody Goldberg's avatar
Jody Goldberg committed
484
	if (ms_excel_object_debug > 0) {
485
		printf ("Object (%d) is a '%s'\n", obj->id, obj->excel_type_name);
Jody Goldberg's avatar
Jody Goldberg committed
486 487
		printf ("}; /* OBJ end */\n");
	}
488 489
#endif

490 491
	ms_container_add_obj (container, obj);

492
	return obj;
493
}