xref: /haiku/src/kits/translation/TranslationUtils.cpp (revision c237c4ce593ee823d9867fd997e51e4c447f5623)
1 /*
2  * Copyright 2002-2007, Haiku Inc.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Michael Wilber
7  *		Axel Dörfler, axeld@pinc-software.de
8  */
9 
10 /*! Utility functions for the Translation Kit */
11 
12 
13 #include <Application.h>
14 #include <Bitmap.h>
15 #include <BitmapStream.h>
16 #include <CharacterSet.h>
17 #include <CharacterSetRoster.h>
18 #include <Entry.h>
19 #include <File.h>
20 #include <MenuItem.h>
21 #include <NodeInfo.h>
22 #include <ObjectList.h>
23 #include <Path.h>
24 #include <Resources.h>
25 #include <Roster.h>
26 #include <String.h>
27 #include <TextView.h>
28 #include <TranslationUtils.h>
29 #include <TranslatorFormats.h>
30 #include <TranslatorRoster.h>
31 #include <UTF8.h>
32 
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <strings.h>
37 
38 
39 using namespace BPrivate;
40 
41 
42 BTranslationUtils::BTranslationUtils()
43 {
44 }
45 
46 
47 BTranslationUtils::~BTranslationUtils()
48 {
49 }
50 
51 
52 BTranslationUtils::BTranslationUtils(const BTranslationUtils &kUtils)
53 {
54 }
55 
56 
57 BTranslationUtils &
58 BTranslationUtils::operator=(const BTranslationUtils &kUtils)
59 {
60 	return *this;
61 }
62 
63 // ---------------------------------------------------------------
64 // GetBitmap
65 //
66 // Returns a BBitmap object for the bitmap file or resource
67 // kName. The user has to delete this object. It first tries
68 // to open kName as a file, then as a resource.
69 //
70 // Preconditions:
71 //
72 // Parameters: kName, the name of the bitmap file or resource to
73 //                    be returned
74 //             roster, BTranslatorRoster used to do the translation
75 //
76 // Postconditions:
77 //
78 // Returns: NULL, if the file could not be opened and the
79 //                resource couldn't be found or couldn't be
80 //                translated to a BBitmap
81 //          BBitmap * to the bitmap reference by kName
82 // ---------------------------------------------------------------
83 BBitmap *
84 BTranslationUtils::GetBitmap(const char *kName, BTranslatorRoster *roster)
85 {
86 	BBitmap *pBitmap = GetBitmapFile(kName, roster);
87 		// Try loading a bitmap from the file named name
88 
89 	// Try loading the bitmap as an application resource
90 	if (pBitmap == NULL)
91 		pBitmap = GetBitmap(B_TRANSLATOR_BITMAP, kName, roster);
92 
93 	return pBitmap;
94 }
95 
96 // ---------------------------------------------------------------
97 // GetBitmap
98 //
99 // Returns a BBitmap object for the bitmap resource identified by
100 // the type type with the resource id, id.
101 // The user has to delete this object.
102 //
103 // Preconditions:
104 //
105 // Parameters: type, the type of resource to be loaded
106 //             id, the id for the resource to be loaded
107 //             roster, BTranslatorRoster used to do the translation
108 //
109 // Postconditions:
110 //
111 // Returns: NULL, if the resource couldn't be loaded or couldn't
112 //                be translated to a BBitmap
113 //          BBitmap * to the bitmap identified by type and id
114 // ---------------------------------------------------------------
115 BBitmap *
116 BTranslationUtils::GetBitmap(uint32 type, int32 id, BTranslatorRoster *roster)
117 {
118 	BResources *pResources = BApplication::AppResources();
119 		// Remember: pResources must not be freed because
120 		// it belongs to the application
121 	if (pResources == NULL || pResources->HasResource(type, id) == false)
122 		return NULL;
123 
124 	// Load the bitmap resource from the application file
125 	// pRawData should be NULL if the resource is an
126 	// unknown type or not available
127 	size_t bitmapSize = 0;
128 	const void *kpRawData = pResources->LoadResource(type, id, &bitmapSize);
129 	if (kpRawData == NULL || bitmapSize == 0)
130 		return NULL;
131 
132 	BMemoryIO memio(kpRawData, bitmapSize);
133 		// Put the pointer to the raw image data into a BMemoryIO object
134 		// so that it can be used with BTranslatorRoster->Translate() in
135 		// the GetBitmap(BPositionIO *, BTranslatorRoster *) function
136 
137 	return GetBitmap(&memio, roster);
138 		// Translate the data in memio using the BTranslatorRoster roster
139 }
140 
141 // ---------------------------------------------------------------
142 // GetBitmap
143 //
144 // Returns a BBitmap object for the bitmap resource identified by
145 // the type type with the resource name, kName.
146 // The user has to delete this object. Note that a resource type
147 // and name does not uniquely identify a resource in a file.
148 //
149 // Preconditions:
150 //
151 // Parameters: type, the type of resource to be loaded
152 //             kName, the name of the resource to be loaded
153 //             roster, BTranslatorRoster used to do the translation
154 //
155 // Postconditions:
156 //
157 // Returns: NULL, if the resource couldn't be loaded or couldn't
158 //                be translated to a BBitmap
159 //          BBitmap * to the bitmap identified by type and kName
160 // ---------------------------------------------------------------
161 BBitmap *
162 BTranslationUtils::GetBitmap(uint32 type, const char *kName,
163 	BTranslatorRoster *roster)
164 {
165 	BResources *pResources = BApplication::AppResources();
166 		// Remember: pResources must not be freed because
167 		// it belongs to the application
168 	if (pResources == NULL || pResources->HasResource(type, kName) == false)
169 		return NULL;
170 
171 	// Load the bitmap resource from the application file
172 	size_t bitmapSize = 0;
173 	const void *kpRawData = pResources->LoadResource(type, kName, &bitmapSize);
174 	if (kpRawData == NULL || bitmapSize == 0)
175 		return NULL;
176 
177 	BMemoryIO memio(kpRawData, bitmapSize);
178 		// Put the pointer to the raw image data into a BMemoryIO object so
179 		// that it can be used with BTranslatorRoster->Translate()
180 
181 	return GetBitmap(&memio, roster);
182 		// Translate the data in memio using the BTranslatorRoster roster
183 }
184 
185 // ---------------------------------------------------------------
186 // GetBitmapFile
187 //
188 // Returns a BBitmap object for the bitmap file named kName.
189 // The user has to delete this object.
190 //
191 // Preconditions:
192 //
193 // Parameters: kName, the name of the bitmap file
194 //             roster, BTranslatorRoster used to do the translation
195 //
196 // Postconditions:
197 //
198 // Returns: NULL, if the file couldn't be opened or couldn't
199 //                be translated to a BBitmap
200 //          BBitmap * to the bitmap file named kName
201 // ---------------------------------------------------------------
202 BBitmap *
203 BTranslationUtils::GetBitmapFile(const char *kName, BTranslatorRoster *roster)
204 {
205 	if (!be_app || !kName || kName[0] == '\0')
206 		return NULL;
207 
208 	BPath path;
209 	if (kName[0] != '/') {
210 		// If kName is a relative path, use the path of the application's
211 		// executable as the base for the relative path
212 		app_info info;
213 		if (be_app->GetAppInfo(&info) != B_OK)
214 			return NULL;
215 		BEntry appRef(&info.ref);
216 		if (path.SetTo(&appRef) != B_OK)
217 			return NULL;
218 		if (path.GetParent(&path) != B_OK)
219 			return NULL;
220 		if (path.Append(kName) != B_OK)
221 			return NULL;
222 
223 	} else if (path.SetTo(kName) != B_OK)
224 		return NULL;
225 
226 	BFile bitmapFile(path.Path(), B_READ_ONLY);
227 	if (bitmapFile.InitCheck() != B_OK)
228 		return NULL;
229 
230 	return GetBitmap(&bitmapFile, roster);
231 		// Translate the data in memio using the BTranslatorRoster roster
232 }
233 
234 // ---------------------------------------------------------------
235 // GetBitmap
236 //
237 // Returns a BBitmap object for the bitmap file with the entry_ref
238 // kRef. The user has to delete this object.
239 //
240 // Preconditions:
241 //
242 // Parameters: kRef, the entry_ref for the bitmap file
243 //             roster, BTranslatorRoster used to do the translation
244 //
245 // Postconditions:
246 //
247 // Returns: NULL, if the file couldn't be opened or couldn't
248 //                be translated to a BBitmap
249 //          BBitmap * to the bitmap file referenced by kRef
250 // ---------------------------------------------------------------
251 BBitmap *
252 BTranslationUtils::GetBitmap(const entry_ref *kRef, BTranslatorRoster *roster)
253 {
254 	BFile bitmapFile(kRef, B_READ_ONLY);
255 	if (bitmapFile.InitCheck() != B_OK)
256 		return NULL;
257 
258 	return GetBitmap(&bitmapFile, roster);
259 		// Translate the data in bitmapFile using the BTranslatorRoster roster
260 }
261 
262 // ---------------------------------------------------------------
263 // GetBitmap
264 //
265 // Returns a BBitmap object from the BPositionIO *stream. The
266 // user must delete the returned object. This GetBitmap function
267 // is used by the other GetBitmap functions to do all of the
268 // "real" work.
269 //
270 // Preconditions:
271 //
272 // Parameters: stream, the stream with bitmap data in it
273 //             roster, BTranslatorRoster used to do the translation
274 //
275 // Postconditions:
276 //
277 // Returns: NULL, if the stream couldn't be translated to a BBitmap
278 //          BBitmap * for the bitmap data from pio if successful
279 // ---------------------------------------------------------------
280 BBitmap *
281 BTranslationUtils::GetBitmap(BPositionIO *stream, BTranslatorRoster *roster)
282 {
283 	if (stream == NULL)
284 		return NULL;
285 
286 	// Use default Translator if none is specified
287 	if (roster == NULL) {
288 		roster = BTranslatorRoster::Default();
289 		if (roster == NULL)
290 			return NULL;
291 	}
292 
293 	// Translate the file from whatever format it is in the file
294 	// to the type format so that it can be stored in a BBitmap
295 	BBitmapStream bitmapStream;
296 	if (roster->Translate(stream, NULL, NULL, &bitmapStream,
297 		B_TRANSLATOR_BITMAP) < B_OK)
298 		return NULL;
299 
300 	// Detach the BBitmap from the BBitmapStream so the user
301 	// of this function can do what they please with it.
302 	BBitmap *pBitmap = NULL;
303 	if (bitmapStream.DetachBitmap(&pBitmap) == B_NO_ERROR)
304 		return pBitmap;
305 	else
306 		return NULL;
307 }
308 
309 
310 /*!
311 	This function translates the styled text in fromStream and
312 	inserts it at the end of the text in intoView, using the
313 	BTranslatorRoster *roster to do the translation. The structs
314 	that make it possible to work with the translated data are
315 	defined in
316 	/boot/develop/headers/be/translation/TranslatorFormats.h
317 
318 	\param source the stream with the styled text
319 	\param intoView the view where the test will be inserted
320 		roster, BTranslatorRoster used to do the translation
321 	\param the encoding to use, defaults to UTF-8
322 
323 	\return B_BAD_VALUE, if fromStream or intoView is NULL
324 	\return B_ERROR, if any other error occurred
325 	\return B_OK, if successful
326 */
327 status_t
328 BTranslationUtils::GetStyledText(BPositionIO* source, BTextView* intoView,
329 	const char* encoding, BTranslatorRoster* roster)
330 {
331 	if (source == NULL || intoView == NULL)
332 		return B_BAD_VALUE;
333 
334 	// Use default Translator if none is specified
335 	if (roster == NULL) {
336 		roster = BTranslatorRoster::Default();
337 		if (roster == NULL)
338 			return B_ERROR;
339 	}
340 
341 	BMessage config;
342 	if (encoding != NULL && encoding[0])
343 		config.AddString("be:encoding", encoding);
344 
345 	// Translate the file from whatever format it is to B_STYLED_TEXT_FORMAT
346 	// we understand
347 	BMallocIO mallocIO;
348 	if (roster->Translate(source, NULL, &config, &mallocIO,
349 			B_STYLED_TEXT_FORMAT) < B_OK)
350 		return B_BAD_TYPE;
351 
352 	const uint8* buffer = (const uint8*)mallocIO.Buffer();
353 
354 	// make sure there is enough data to fill the stream header
355 	const size_t kStreamHeaderSize = sizeof(TranslatorStyledTextStreamHeader);
356 	if (mallocIO.BufferLength() < kStreamHeaderSize)
357 		return B_BAD_DATA;
358 
359 	// copy the stream header from the mallio buffer
360 	TranslatorStyledTextStreamHeader header =
361 		*(reinterpret_cast<const TranslatorStyledTextStreamHeader *>(buffer));
362 
363 	// convert the stm_header.header struct to the host format
364 	const size_t kRecordHeaderSize = sizeof(TranslatorStyledTextRecordHeader);
365 	swap_data(B_UINT32_TYPE, &header.header, kRecordHeaderSize, B_SWAP_BENDIAN_TO_HOST);
366 	swap_data(B_INT32_TYPE, &header.version, sizeof(int32), B_SWAP_BENDIAN_TO_HOST);
367 
368 	if (header.header.magic != 'STXT')
369 		return B_BAD_TYPE;
370 
371 	// copy the text header from the mallocIO buffer
372 
373 	uint32 offset = header.header.header_size + header.header.data_size;
374 	const size_t kTextHeaderSize = sizeof(TranslatorStyledTextTextHeader);
375 	if (mallocIO.BufferLength() < offset + kTextHeaderSize)
376 		return B_BAD_DATA;
377 
378 	TranslatorStyledTextTextHeader textHeader =
379 		*(const TranslatorStyledTextTextHeader *)(buffer + offset);
380 
381 	// convert the stm_header.header struct to the host format
382 	swap_data(B_UINT32_TYPE, &textHeader.header, kRecordHeaderSize, B_SWAP_BENDIAN_TO_HOST);
383 	swap_data(B_INT32_TYPE, &textHeader.charset, sizeof(int32), B_SWAP_BENDIAN_TO_HOST);
384 
385 	if (textHeader.header.magic != 'TEXT' || textHeader.charset != B_UNICODE_UTF8)
386 		return B_BAD_TYPE;
387 
388 	offset += textHeader.header.header_size;
389 	if (mallocIO.BufferLength() < offset + textHeader.header.data_size) {
390 		// text buffer misses its end; handle this gracefully
391 		textHeader.header.data_size = mallocIO.BufferLength() - offset;
392 	}
393 
394 	const char* text = (const char*)buffer + offset;
395 		// point text pointer at the actual character data
396 	bool hasStyles = false;
397 
398 	if (mallocIO.BufferLength() > offset + textHeader.header.data_size) {
399 		// If the stream contains information beyond the text data
400 		// (which means that this data is probably styled text data)
401 
402 		offset += textHeader.header.data_size;
403 		const size_t kStyleHeaderSize =
404 			sizeof(TranslatorStyledTextStyleHeader);
405 		if (mallocIO.BufferLength() >= offset + kStyleHeaderSize) {
406 			TranslatorStyledTextStyleHeader styleHeader =
407 				*(reinterpret_cast<const TranslatorStyledTextStyleHeader *>(buffer + offset));
408 			swap_data(B_UINT32_TYPE, &styleHeader.header, kRecordHeaderSize, B_SWAP_BENDIAN_TO_HOST);
409 			swap_data(B_UINT32_TYPE, &styleHeader.apply_offset, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST);
410 			swap_data(B_UINT32_TYPE, &styleHeader.apply_length, sizeof(uint32), B_SWAP_BENDIAN_TO_HOST);
411 			if (styleHeader.header.magic == 'STYL') {
412 				offset += styleHeader.header.header_size;
413 				if (mallocIO.BufferLength() >= offset + styleHeader.header.data_size)
414 					hasStyles = true;
415 			}
416 		}
417 	}
418 
419 	text_run_array *runArray = NULL;
420 	if (hasStyles)
421 		runArray = BTextView::UnflattenRunArray(buffer + offset);
422 
423 	if (runArray != NULL) {
424 		intoView->Insert(intoView->TextLength(),
425 			text, textHeader.header.data_size, runArray);
426 #ifdef HAIKU_TARGET_PLATFORM_HAIKU
427 		BTextView::FreeRunArray(runArray);
428 #else
429 		free(runArray);
430 #endif
431 	} else {
432 		intoView->Insert(intoView->TextLength(), text,
433 			textHeader.header.data_size);
434 	}
435 
436 	return B_OK;
437 }
438 
439 
440 status_t
441 BTranslationUtils::GetStyledText(BPositionIO* source, BTextView* intoView,
442 	BTranslatorRoster* roster)
443 {
444 	return GetStyledText(source, intoView, NULL, roster);
445 }
446 
447 
448 /*!
449 	This function takes styled text data from fromView and writes it to
450 	intoStream.  The plain text data and styled text data are combined
451 	when they are written to intoStream.  This is different than how
452 	a save operation in StyledEdit works.  With StyledEdit, it writes
453 	plain text data to the file, but puts the styled text data in
454 	the "styles" attribute.  In other words, this function writes
455 	styled text data to files in a manner that isn't human readable.
456 
457 	So, if you want to write styled text
458 	data to a file, and you want it to behave the way StyledEdit does,
459 	you want to use the BTranslationUtils::WriteStyledEditFile() function.
460 
461 	\param fromView, the view with the styled text in it
462 	\param intoStream, the stream where the styled text is put
463 		roster, not used
464 
465 	\return B_BAD_VALUE, if fromView or intoStream is NULL
466 	\return B_ERROR, if anything else went wrong
467 	\return B_NO_ERROR, if successful
468 */
469 status_t
470 BTranslationUtils::PutStyledText(BTextView *fromView, BPositionIO *intoStream,
471 	BTranslatorRoster *roster)
472 {
473 	if (fromView == NULL || intoStream == NULL)
474 		return B_BAD_VALUE;
475 
476 	int32 textLength = fromView->TextLength();
477 	if (textLength < 0)
478 		return B_ERROR;
479 
480 	const char *pTextData = fromView->Text();
481 		// its OK if the result of fromView->Text() is NULL
482 
483 	int32 runArrayLength = 0;
484 	text_run_array *runArray = fromView->RunArray(0, textLength,
485 		&runArrayLength);
486 	if (runArray == NULL)
487 		return B_ERROR;
488 
489 	int32 flatRunArrayLength = 0;
490 	void *pflatRunArray =
491 		BTextView::FlattenRunArray(runArray, &flatRunArrayLength);
492 	if (pflatRunArray == NULL) {
493 #ifdef HAIKU_TARGET_PLATFORM_HAIKU
494 		BTextView::FreeRunArray(runArray);
495 #else
496 		free(runArray);
497 #endif
498 		return B_ERROR;
499 	}
500 
501 	// Rather than use a goto, I put a whole bunch of code that
502 	// could error out inside of a loop, and break out of the loop
503 	// if there is an error.
504 
505 	// This block of code is where I do all of the writing of the
506 	// data to the stream. I've gathered all of the data that I
507 	// need at this point.
508 	bool ok = false;
509 	while (!ok) {
510 		const size_t kStreamHeaderSize =
511 			sizeof(TranslatorStyledTextStreamHeader);
512 		TranslatorStyledTextStreamHeader stm_header;
513 		stm_header.header.magic = 'STXT';
514 		stm_header.header.header_size = kStreamHeaderSize;
515 		stm_header.header.data_size = 0;
516 		stm_header.version = 100;
517 
518 		// convert the stm_header.header struct to the host format
519 		const size_t kRecordHeaderSize =
520 			sizeof(TranslatorStyledTextRecordHeader);
521 		if (swap_data(B_UINT32_TYPE, &stm_header.header, kRecordHeaderSize,
522 			B_SWAP_HOST_TO_BENDIAN) != B_OK)
523 			break;
524 		if (swap_data(B_INT32_TYPE, &stm_header.version, sizeof(int32),
525 			B_SWAP_HOST_TO_BENDIAN) != B_OK)
526 			break;
527 
528 		const size_t kTextHeaderSize = sizeof(TranslatorStyledTextTextHeader);
529 		TranslatorStyledTextTextHeader txt_header;
530 		txt_header.header.magic = 'TEXT';
531 		txt_header.header.header_size = kTextHeaderSize;
532 		txt_header.header.data_size = textLength;
533 		txt_header.charset = B_UNICODE_UTF8;
534 
535 		// convert the stm_header.header struct to the host format
536 		if (swap_data(B_UINT32_TYPE, &txt_header.header, kRecordHeaderSize,
537 			B_SWAP_HOST_TO_BENDIAN) != B_OK)
538 			break;
539 		if (swap_data(B_INT32_TYPE, &txt_header.charset, sizeof(int32),
540 			B_SWAP_HOST_TO_BENDIAN) != B_OK)
541 			break;
542 
543 		const size_t kStyleHeaderSize =
544 			sizeof(TranslatorStyledTextStyleHeader);
545 		TranslatorStyledTextStyleHeader stl_header;
546 		stl_header.header.magic = 'STYL';
547 		stl_header.header.header_size = kStyleHeaderSize;
548 		stl_header.header.data_size = flatRunArrayLength;
549 		stl_header.apply_offset = 0;
550 		stl_header.apply_length = textLength;
551 
552 		// convert the stl_header.header struct to the host format
553 		if (swap_data(B_UINT32_TYPE, &stl_header.header, kRecordHeaderSize,
554 			B_SWAP_HOST_TO_BENDIAN) != B_OK)
555 			break;
556 		if (swap_data(B_UINT32_TYPE, &stl_header.apply_offset, sizeof(uint32),
557 			B_SWAP_HOST_TO_BENDIAN) != B_OK)
558 			break;
559 		if (swap_data(B_UINT32_TYPE, &stl_header.apply_length, sizeof(uint32),
560 			B_SWAP_HOST_TO_BENDIAN) != B_OK)
561 			break;
562 
563 		// Here, you can see the structure of the styled text data by
564 		// observing the order that the various structs and data are
565 		// written to the stream
566 		ssize_t amountWritten = 0;
567 		amountWritten = intoStream->Write(&stm_header, kStreamHeaderSize);
568 		if ((size_t) amountWritten != kStreamHeaderSize)
569 			break;
570 		amountWritten = intoStream->Write(&txt_header, kTextHeaderSize);
571 		if ((size_t) amountWritten != kTextHeaderSize)
572 			break;
573 		amountWritten = intoStream->Write(pTextData, textLength);
574 		if (amountWritten != textLength)
575 			break;
576 		amountWritten = intoStream->Write(&stl_header, kStyleHeaderSize);
577 		if ((size_t) amountWritten != kStyleHeaderSize)
578 			break;
579 		amountWritten = intoStream->Write(pflatRunArray, flatRunArrayLength);
580 		if (amountWritten != flatRunArrayLength)
581 			break;
582 
583 		ok = true;
584 			// gracefully break out of the loop
585 	}
586 
587 	free(pflatRunArray);
588 #ifdef HAIKU_TARGET_PLATFORM_HAIKU
589 	BTextView::FreeRunArray(runArray);
590 #else
591 	free(runArray);
592 #endif
593 
594 	return ok ? B_OK : B_ERROR;
595 }
596 
597 
598 /*!
599 	\brief Writes the styled text data from \a view to the specified \a file.
600 
601 	This function is similar to PutStyledText() except that it
602 	only writes styled text data to files and it puts the
603 	plain text data in the file and stores the styled data as
604 	the attribute "styles".
605 
606 	You can use PutStyledText() to write styled text data
607 	to files, but it writes the data in a format that isn't
608 	human readable.
609 
610 	\param view the view with the styled text
611 	\param file the file where the styled text is written to
612 	\param the encoding to use, defaults to UTF-8
613 
614 	\return B_BAD_VALUE, if either parameter is NULL
615 		B_OK, if successful, and any possible file error
616 		if writing failed.
617 */
618 status_t
619 BTranslationUtils::WriteStyledEditFile(BTextView* view, BFile* file, const char *encoding)
620 {
621 	if (view == NULL || file == NULL)
622 		return B_BAD_VALUE;
623 
624 	int32 textLength = view->TextLength();
625 	if (textLength < 0)
626 		return B_ERROR;
627 
628 	const char *text = view->Text();
629 	if (text == NULL && textLength != 0)
630 		return B_ERROR;
631 
632 	// move to the start of the file if not already there
633 	status_t status = file->Seek(0, SEEK_SET);
634 	if (status != B_OK)
635 		return status;
636 
637 	const BCharacterSet* characterSet = NULL;
638 	if (encoding != NULL && strcmp(encoding, ""))
639 		characterSet = BCharacterSetRoster::FindCharacterSetByName(encoding);
640 	if (characterSet == NULL) {
641 		// default encoding - UTF-8
642 		// Write plain text data to file
643 		ssize_t bytesWritten = file->Write(text, textLength);
644 		if (bytesWritten != textLength) {
645 			if (bytesWritten < B_OK)
646 				return bytesWritten;
647 
648 			return B_ERROR;
649 		}
650 
651 		// be:encoding, defaults to UTF-8 (65535)
652 		// Note that the B_UNICODE_UTF8 constant is 0 and for some reason
653 		// not appropriate for use here.
654 		int32 value = 65535;
655 		file->WriteAttr("be:encoding", B_INT32_TYPE, 0, &value, sizeof(value));
656 	} else {
657 		// we need to convert the text
658 		uint32 id = characterSet->GetConversionID();
659 		const char* outText = view->Text();
660 		int32 sourceLength = textLength;
661 		int32 state = 0;
662 
663 		textLength = 0;
664 
665 		do {
666 			char buffer[32768];
667 			int32 length = sourceLength;
668 			int32 bufferSize = sizeof(buffer);
669 			status = convert_from_utf8(id, outText, &length, buffer, &bufferSize, &state);
670 			if (status != B_OK)
671 				return status;
672 
673 			ssize_t bytesWritten = file->Write(buffer, bufferSize);
674 			if (bytesWritten < B_OK)
675 				return bytesWritten;
676 
677 			sourceLength -= length;
678 			textLength += bytesWritten;
679 			outText += length;
680 		} while (sourceLength > 0);
681 
682 		BString encodingStr(encoding);
683 		file->WriteAttrString("be:encoding", &encodingStr);
684 	}
685 
686 	// truncate any extra text
687 	status = file->SetSize(textLength);
688 	if (status != B_OK)
689 		return status;
690 
691 	// Write attributes. We don't report an error anymore after this point,
692 	// as attributes aren't that crucial - not all volumes support attributes.
693 	// However, if writing one attribute fails, no further attributes are
694 	// tried to be written.
695 
696 	BNodeInfo info(file);
697 	char type[B_MIME_TYPE_LENGTH];
698 	if (info.GetType(type) != B_OK) {
699 		// This file doesn't have a file type yet, so let's set it
700 		if (info.SetType("text/plain") < B_OK)
701 			return B_OK;
702 	}
703 
704 	// word wrap setting, turned on by default
705 	int32 wordWrap = view->DoesWordWrap() ? 1 : 0;
706 	ssize_t bytesWritten = file->WriteAttr("wrap", B_INT32_TYPE, 0,
707 		&wordWrap, sizeof(int32));
708 	if (bytesWritten != sizeof(int32))
709 		return B_OK;
710 
711 	// alignment, default is B_ALIGN_LEFT
712 	int32 alignment = view->Alignment();
713 	bytesWritten = file->WriteAttr("alignment", B_INT32_TYPE, 0,
714 		&alignment, sizeof(int32));
715 	if (bytesWritten != sizeof(int32))
716 		return B_OK;
717 
718 	// Write text_run_array, ie. the styles of the text
719 
720 	text_run_array *runArray = view->RunArray(0, view->TextLength());
721 	if (runArray != NULL) {
722 		int32 runArraySize = 0;
723 		void *flattenedRunArray = BTextView::FlattenRunArray(runArray, &runArraySize);
724 		if (flattenedRunArray != NULL) {
725 			file->WriteAttr("styles", B_RAW_TYPE, 0, flattenedRunArray,
726 				runArraySize);
727 		}
728 
729 		free(flattenedRunArray);
730 #ifdef HAIKU_TARGET_PLATFORM_HAIKU
731 		BTextView::FreeRunArray(runArray);
732 #else
733 		free(runArray);
734 #endif
735 	}
736 
737 	return B_OK;
738 }
739 
740 
741 status_t
742 BTranslationUtils::WriteStyledEditFile(BTextView* view, BFile* file)
743 {
744 	return WriteStyledEditFile(view, file, NULL);
745 }
746 
747 
748 /*!
749 	Each translator can have default settings, set by the
750 	"translations" control panel. You can read these settings to
751 	pass on to a translator using one of these functions.
752 
753 	\param forTranslator, the translator the settings are for
754 		roster, the roster used to get the settings
755 
756 	\return BMessage of configuration data for forTranslator - you own
757 		this message and have to free it when you're done with it.
758 	\return NULL, if anything went wrong
759 */
760 BMessage *
761 BTranslationUtils::GetDefaultSettings(translator_id forTranslator,
762 	BTranslatorRoster *roster)
763 {
764 	// Use default Translator if none is specified
765 	if (roster == NULL) {
766 		roster = BTranslatorRoster::Default();
767 		if (roster == NULL)
768 			return NULL;
769 	}
770 
771 	BMessage *message = new BMessage();
772 	if (message == NULL)
773 		return NULL;
774 
775 	status_t result = roster->GetConfigurationMessage(forTranslator, message);
776 	if (result != B_OK && result != B_NO_TRANSLATOR) {
777 		// Be's version seems to just pass an empty BMessage
778 		// in case of B_NO_TRANSLATOR, well, in some cases anyway
779 		delete message;
780 		return NULL;
781 	}
782 
783 	return message;
784 }
785 
786 
787 // ---------------------------------------------------------------
788 // GetDefaultSettings
789 //
790 // Attempts to find the translator settings for
791 // the translator named kTranslatorName with a version of
792 // translatorVersion.
793 //
794 // Preconditions:
795 //
796 // Parameters: kTranslatorName, the name of the translator
797 //                              the settings are for
798 //             translatorVersion, the version of the translator
799 //                                to retrieve
800 //
801 // Postconditions:
802 //
803 // Returns: NULL, if anything went wrong
804 //          BMessage * of configuration data for kTranslatorName
805 // ---------------------------------------------------------------
806 BMessage *
807 BTranslationUtils::GetDefaultSettings(const char *kTranslatorName,
808 	int32 translatorVersion)
809 {
810 	BTranslatorRoster *roster = BTranslatorRoster::Default();
811 	translator_id *translators = NULL;
812 	int32 numTranslators = 0;
813 	if (roster == NULL
814 		|| roster->GetAllTranslators(&translators, &numTranslators) != B_OK)
815 		return NULL;
816 
817 	// Cycle through all of the default translators
818 	// looking for a translator that matches the name and version
819 	// that I was given
820 	BMessage *pMessage = NULL;
821 	const char *currentTranName = NULL, *currentTranInfo = NULL;
822 	int32 currentTranVersion = 0;
823 	for (int i = 0; i < numTranslators; i++) {
824 
825 		if (roster->GetTranslatorInfo(translators[i], &currentTranName,
826 			&currentTranInfo, &currentTranVersion) == B_OK) {
827 
828 			if (currentTranVersion == translatorVersion
829 				&& strcmp(currentTranName, kTranslatorName) == 0) {
830 				pMessage = GetDefaultSettings(translators[i], roster);
831 				break;
832 			}
833 		}
834 	}
835 
836 	delete[] translators;
837 	return pMessage;
838 }
839 
840 
841 // ---------------------------------------------------------------
842 // AddTranslationItems
843 //
844 // Envious of that "Save As" menu in ShowImage? Well, you can have your own!
845 // AddTranslationItems will add menu items for all translations from the
846 // basic format you specify (B_TRANSLATOR_BITMAP, B_TRANSLATOR_TEXT etc).
847 // The translator ID and format constant chosen will be added to the message
848 // that is sent to you when the menu item is selected.
849 //
850 // The following code is a modified version of code
851 // written by Jon Watte from
852 // http://www.b500.com/bepage/TranslationKit2.html
853 //
854 // Preconditions:
855 //
856 // Parameters: intoMenu, the menu where the entries are created
857 //             fromType, the type of translators to put on
858 //                       intoMenu
859 //             kModel, the BMessage model for creating the menu
860 //                     if NULL, B_TRANSLATION_MENU is used
861 //             kTranslationIdName, the name used for
862 //                                 translator_id in the menuitem,
863 //                                 if NULL, be:translator is used
864 //             kTranslatorTypeName, the name used for
865 //                                  output format id in the menuitem
866 //             roster, BTranslatorRoster used to find translators
867 //                     if NULL, the default translators are used
868 //
869 //
870 // Postconditions:
871 //
872 // Returns: B_BAD_VALUE, if intoMenu is NULL
873 //          B_OK, if successful
874 //          error value if not successful
875 // ---------------------------------------------------------------
876 status_t
877 BTranslationUtils::AddTranslationItems(BMenu *intoMenu, uint32 fromType,
878 	const BMessage *kModel, const char *kTranslatorIdName,
879 	const char *kTranslatorTypeName, BTranslatorRoster *roster)
880 {
881 	if (!intoMenu)
882 		return B_BAD_VALUE;
883 
884 	if (!roster)
885 		roster = BTranslatorRoster::Default();
886 
887 	if (!kTranslatorIdName)
888 		kTranslatorIdName = "be:translator";
889 
890 	if (!kTranslatorTypeName)
891 		kTranslatorTypeName = "be:type";
892 
893 	translator_id * ids = NULL;
894 	int32 count = 0;
895 	status_t err = roster->GetAllTranslators(&ids, &count);
896 	if (err < B_OK)
897 		return err;
898 
899 	BObjectList<translator_info> infoList;
900 
901 	for (int tix = 0; tix < count; tix++) {
902 		const translation_format *formats = NULL;
903 		int32 numFormats = 0;
904 		bool ok = false;
905 		err = roster->GetInputFormats(ids[tix], &formats, &numFormats);
906 		if (err == B_OK) {
907 			for (int iix = 0; iix < numFormats; iix++) {
908 				if (formats[iix].type == fromType) {
909 					ok = true;
910 					break;
911 				}
912 			}
913 		}
914 		if (!ok)
915 			continue;
916 
917 		// Get supported output formats
918 		err = roster->GetOutputFormats(ids[tix], &formats, &numFormats);
919 		if (err == B_OK) {
920 			for (int oix = 0; oix < numFormats; oix++) {
921 				if (formats[oix].type != fromType) {
922 					infoList.AddItem(_BuildTranslatorInfo(ids[tix],
923 						const_cast<translation_format*>(&formats[oix])));
924 				}
925 			}
926 		}
927 	}
928 
929 	// Sort alphabetically by name
930 	infoList.SortItems(&_CompareTranslatorInfoByName);
931 
932 	// Now add the menu items
933 	for (int i = 0; i < infoList.CountItems(); i++) {
934 		translator_info* info = infoList.ItemAt(i);
935 
936 		BMessage *itemmsg;
937 		if (kModel)
938 			itemmsg = new BMessage(*kModel);
939 		else
940 			itemmsg = new BMessage(B_TRANSLATION_MENU);
941 		itemmsg->AddInt32(kTranslatorIdName, info->translator);
942 		itemmsg->AddInt32(kTranslatorTypeName, info->type);
943 		intoMenu->AddItem(new BMenuItem(info->name, itemmsg));
944 
945 		// Delete object created in _BuildTranslatorInfo
946 		delete info;
947 	}
948 
949 	delete[] ids;
950 	return B_OK;
951 }
952 
953 
954 translator_info*
955 BTranslationUtils::_BuildTranslatorInfo(const translator_id id, const translation_format* format)
956 {
957 	// Caller must delete
958 	translator_info* info = new translator_info;
959 
960 	info->translator = id;
961 	info->type = format->type;
962 	info->group = format->group;
963 	info->quality = format->quality;
964 	info->capability = format->capability;
965 	strlcpy(info->name, format->name, sizeof(info->name));
966 	strlcpy(info->MIME, format->MIME, sizeof(info->MIME));
967 
968 	return info;
969 }
970 
971 
972 int
973 BTranslationUtils::_CompareTranslatorInfoByName(const translator_info* info1, const translator_info* info2)
974 {
975 	return strcasecmp(info1->name, info2->name);
976 }
977