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