1/*
2 * Copyright 2001-2006, Haiku, Inc. All Rights Reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Marc Flerackers (mflerackers@androme.be)
7 *		Stefano Ceccherini (burton666@libero.it)
8 */
9
10
11#include <cstdio>
12#include <cstdlib>
13#include <cstring>
14
15#include <utf8_functions.h>
16
17#include <File.h>
18#include <InterfaceDefs.h> // for B_UTF8_BULLET
19
20#include "TextGapBuffer.h"
21
22
23namespace BPrivate {
24
25
26static const int32 kTextGapBufferBlockSize = 2048;
27
28
29TextGapBuffer::TextGapBuffer()
30	:
31	fItemCount(0),
32	fBuffer(NULL),
33	fBufferCount(kTextGapBufferBlockSize + fItemCount),
34	fGapIndex(fItemCount),
35	fGapCount(fBufferCount - fGapIndex),
36	fScratchBuffer(NULL),
37	fScratchSize(0),
38	fPasswordMode(false)
39{
40	fBuffer = (char*)malloc(kTextGapBufferBlockSize + fItemCount);
41	fScratchBuffer = NULL;
42}
43
44
45TextGapBuffer::~TextGapBuffer()
46{
47	free(fBuffer);
48	free(fScratchBuffer);
49}
50
51
52void
53TextGapBuffer::InsertText(const char* inText, int32 inNumItems, int32 inAtIndex)
54{
55	if (inNumItems < 1)
56		return;
57
58	inAtIndex = (inAtIndex > fItemCount) ? fItemCount : inAtIndex;
59	inAtIndex = (inAtIndex < 0) ? 0 : inAtIndex;
60
61	if (inAtIndex != fGapIndex)
62		_MoveGapTo(inAtIndex);
63
64	if (fGapCount < inNumItems)
65		_EnlargeGapTo(inNumItems + kTextGapBufferBlockSize);
66
67	memcpy(fBuffer + fGapIndex, inText, inNumItems);
68
69	fGapCount -= inNumItems;
70	fGapIndex += inNumItems;
71	fItemCount += inNumItems;
72}
73
74
75void
76TextGapBuffer::InsertText(BFile* file, int32 fileOffset, int32 inNumItems,
77	int32 inAtIndex)
78{
79	off_t fileSize;
80
81	if (file->GetSize(&fileSize) != B_OK
82		|| !file->IsReadable())
83		return;
84
85	// Clamp the text length to the file size
86	fileSize -= fileOffset;
87
88	if (fileSize < inNumItems)
89		inNumItems = fileSize;
90
91	if (inNumItems < 1)
92		return;
93
94	inAtIndex = (inAtIndex > fItemCount) ? fItemCount : inAtIndex;
95	inAtIndex = (inAtIndex < 0) ? 0 : inAtIndex;
96
97	if (inAtIndex != fGapIndex)
98		_MoveGapTo(inAtIndex);
99
100	if (fGapCount < inNumItems)
101		_EnlargeGapTo(inNumItems + kTextGapBufferBlockSize);
102
103	// Finally, read the data and put it into the buffer
104	if (file->ReadAt(fileOffset, fBuffer + fGapIndex, inNumItems) > 0) {
105		fGapCount -= inNumItems;
106		fGapIndex += inNumItems;
107		fItemCount += inNumItems;
108	}
109}
110
111
112void
113TextGapBuffer::RemoveRange(int32 start, int32 end)
114{
115	long inAtIndex = start;
116	long inNumItems = end - start;
117
118	if (inNumItems < 1)
119		return;
120
121	inAtIndex = (inAtIndex > fItemCount - 1) ? (fItemCount - 1) : inAtIndex;
122	inAtIndex = (inAtIndex < 0) ? 0 : inAtIndex;
123
124	_MoveGapTo(inAtIndex);
125
126	fGapCount += inNumItems;
127	fItemCount -= inNumItems;
128
129	if (fGapCount > kTextGapBufferBlockSize)
130		_ShrinkGapTo(kTextGapBufferBlockSize / 2);
131}
132
133
134const char*
135TextGapBuffer::GetString(int32 fromOffset, int32* _numBytes)
136{
137	const char* result = "";
138	if (_numBytes == NULL)
139		return result;
140
141	int32 numBytes = *_numBytes;
142	if (numBytes < 1)
143		return result;
144
145	bool isStartBeforeGap = (fromOffset < fGapIndex);
146	bool isEndBeforeGap = ((fromOffset + numBytes - 1) < fGapIndex);
147
148	if (isStartBeforeGap == isEndBeforeGap) {
149		result = fBuffer + fromOffset;
150		if (!isStartBeforeGap)
151			result += fGapCount;
152	} else {
153		if (fScratchSize < numBytes) {
154			fScratchBuffer = (char*)realloc(fScratchBuffer, numBytes);
155			fScratchSize = numBytes;
156		}
157
158		for (long i = 0; i < numBytes; i++)
159			fScratchBuffer[i] = RealCharAt(fromOffset + i);
160
161		result = fScratchBuffer;
162	}
163
164	// TODO: this could be improved. We are overwriting what we did some lines
165	// ago, we could just avoid to do that.
166	if (fPasswordMode) {
167		uint32 numChars = UTF8CountChars(result, numBytes);
168		uint32 charLen = UTF8CountBytes(B_UTF8_BULLET, 1);
169		uint32 newSize = numChars * charLen;
170
171		if ((uint32)fScratchSize < newSize) {
172			fScratchBuffer = (char*)realloc(fScratchBuffer, newSize);
173			fScratchSize = newSize;
174		}
175		result = fScratchBuffer;
176
177		char* scratchPtr = fScratchBuffer;
178		for (uint32 i = 0; i < numChars; i++) {
179			memcpy(scratchPtr, B_UTF8_BULLET, charLen);
180			scratchPtr += charLen;
181		}
182
183		*_numBytes = newSize;
184	}
185
186	return result;
187}
188
189
190bool
191TextGapBuffer::FindChar(char inChar, int32 fromIndex, int32* ioDelta)
192{
193	long numChars = *ioDelta;
194	for (long i = 0; i < numChars; i++) {
195		char realChar = RealCharAt(fromIndex + i);
196		if ((realChar & 0xc0) == 0x80)
197			continue;
198		if (realChar == inChar) {
199			*ioDelta = i;
200			return true;
201		}
202	}
203
204	return false;
205}
206
207
208const char*
209TextGapBuffer::Text()
210{
211	const char* realText = RealText();
212
213	if (fPasswordMode) {
214		const uint32 numChars = UTF8CountChars(realText, Length());
215		const uint32 bulletCharLen = UTF8CountBytes(B_UTF8_BULLET, 1);
216		uint32 newSize = numChars * bulletCharLen + 1;
217
218		if ((uint32)fScratchSize < newSize) {
219			fScratchBuffer = (char*)realloc(fScratchBuffer, newSize);
220			fScratchSize = newSize;
221		}
222
223		char* scratchPtr = fScratchBuffer;
224		for (uint32 i = 0; i < numChars; i++) {
225			memcpy(scratchPtr, B_UTF8_BULLET, bulletCharLen);
226			scratchPtr += bulletCharLen;
227		}
228		scratchPtr = '\0';
229
230		return fScratchBuffer;
231	}
232
233	return realText;
234}
235
236
237const char*
238TextGapBuffer::RealText()
239{
240	_MoveGapTo(fItemCount);
241
242	if (fGapCount == 0)
243		_EnlargeGapTo(kTextGapBufferBlockSize);
244
245	fBuffer[fItemCount] = '\0';
246	return fBuffer;
247}
248
249
250void
251TextGapBuffer::GetString(int32 offset, int32 length, char* buffer)
252{
253	if (buffer == NULL)
254		return;
255
256	int32 textLen = Length();
257
258	if (offset < 0 || offset > (textLen - 1) || length < 1) {
259		buffer[0] = '\0';
260		return;
261	}
262
263	length = ((offset + length) > textLen) ? textLen - offset : length;
264
265	bool isStartBeforeGap = (offset < fGapIndex);
266	bool isEndBeforeGap = ((offset + length - 1) < fGapIndex);
267
268	if (isStartBeforeGap == isEndBeforeGap) {
269		char* source = fBuffer + offset;
270		if (!isStartBeforeGap)
271			source += fGapCount;
272
273		memcpy(buffer, source, length);
274
275	} else {
276		// if we are here, it can only be that start is before gap,
277		// and the end is after gap.
278
279		int32 beforeLen = fGapIndex - offset;
280		int32 afterLen = length - beforeLen;
281
282		memcpy(buffer, fBuffer + offset, beforeLen);
283		memcpy(buffer + beforeLen, fBuffer + fGapIndex, afterLen);
284
285	}
286
287	buffer[length] = '\0';
288}
289
290
291bool
292TextGapBuffer::PasswordMode() const
293{
294	return fPasswordMode;
295}
296
297
298void
299TextGapBuffer::SetPasswordMode(bool state)
300{
301	fPasswordMode = state;
302}
303
304
305void
306TextGapBuffer::_MoveGapTo(int32 toIndex)
307{
308	if (toIndex == fGapIndex)
309		return;
310	if (toIndex > fItemCount) {
311		debugger("MoveGapTo: invalid toIndex supplied");
312		return;
313	}
314
315	int32 srcIndex = 0;
316	int32 dstIndex = 0;
317	int32 count = 0;
318	if (toIndex > fGapIndex) {
319		srcIndex = fGapIndex + fGapCount;
320		dstIndex = fGapIndex;
321		count = toIndex - fGapIndex;
322	} else {
323		srcIndex = toIndex;
324		dstIndex = toIndex + fGapCount;
325		count = fGapIndex- toIndex;
326	}
327
328	if (count > 0)
329		memmove(fBuffer + dstIndex, fBuffer + srcIndex, count);
330
331	fGapIndex = toIndex;
332}
333
334
335void
336TextGapBuffer::_EnlargeGapTo(int32 inCount)
337{
338	if (inCount == fGapCount)
339		return;
340
341	fBuffer = (char*)realloc(fBuffer, fItemCount + inCount);
342	memmove(fBuffer + fGapIndex + inCount, fBuffer + fGapIndex + fGapCount,
343		fBufferCount - (fGapIndex + fGapCount));
344
345	fGapCount = inCount;
346	fBufferCount = fItemCount + fGapCount;
347}
348
349
350void
351TextGapBuffer::_ShrinkGapTo(int32 inCount)
352{
353	if (inCount == fGapCount)
354		return;
355
356	memmove(fBuffer + fGapIndex + inCount, fBuffer + fGapIndex + fGapCount,
357		fBufferCount - (fGapIndex + fGapCount));
358	fBuffer = (char*)realloc(fBuffer, fItemCount + inCount);
359
360	fGapCount = inCount;
361	fBufferCount = fItemCount + fGapCount;
362}
363
364
365} // namespace BPrivate
366