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