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