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