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 (stefano.ceccherini@gmail.com)
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
75bool
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 false;
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 false;
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	return true;
111}
112
113
114void
115TextGapBuffer::RemoveRange(int32 start, int32 end)
116{
117	int32 inAtIndex = start;
118	int32 inNumItems = end - start;
119
120	if (inNumItems < 1)
121		return;
122
123	inAtIndex = (inAtIndex > fItemCount - 1) ? (fItemCount - 1) : inAtIndex;
124	inAtIndex = (inAtIndex < 0) ? 0 : inAtIndex;
125
126	_MoveGapTo(inAtIndex);
127
128	fGapCount += inNumItems;
129	fItemCount -= inNumItems;
130
131	if (fGapCount > kTextGapBufferBlockSize)
132		_ShrinkGapTo(kTextGapBufferBlockSize / 2);
133}
134
135
136const char*
137TextGapBuffer::GetString(int32 fromOffset, int32* _numBytes)
138{
139	const char* result = "";
140	if (_numBytes == NULL)
141		return result;
142
143	int32 numBytes = *_numBytes;
144	if (numBytes < 1)
145		return result;
146
147	bool isStartBeforeGap = fromOffset < fGapIndex;
148	bool isEndBeforeGap = (fromOffset + numBytes - 1) < fGapIndex;
149
150	if (isStartBeforeGap == isEndBeforeGap) {
151		result = fBuffer + fromOffset;
152		if (!isStartBeforeGap)
153			result += fGapCount;
154	} else {
155		if (fScratchSize < numBytes) {
156			fScratchBuffer = (char*)realloc(fScratchBuffer, numBytes);
157			fScratchSize = numBytes;
158		}
159
160		for (int32 i = 0; i < numBytes; i++)
161			fScratchBuffer[i] = RealCharAt(fromOffset + i);
162
163		result = fScratchBuffer;
164	}
165
166	// TODO: this could be improved. We are overwriting what we did some lines
167	// ago, we could just avoid to do that.
168	if (fPasswordMode) {
169		uint32 numChars = UTF8CountChars(result, numBytes);
170		uint32 charLen = UTF8CountBytes(B_UTF8_BULLET, 1);
171		uint32 newSize = numChars * charLen;
172
173		if ((uint32)fScratchSize < newSize) {
174			fScratchBuffer = (char*)realloc(fScratchBuffer, newSize);
175			fScratchSize = newSize;
176		}
177		result = fScratchBuffer;
178
179		char* scratchPtr = fScratchBuffer;
180		for (uint32 i = 0; i < numChars; i++) {
181			memcpy(scratchPtr, B_UTF8_BULLET, charLen);
182			scratchPtr += charLen;
183		}
184
185		*_numBytes = newSize;
186	}
187
188	return result;
189}
190
191
192bool
193TextGapBuffer::FindChar(char inChar, int32 fromIndex, int32* ioDelta)
194{
195	int32 numChars = *ioDelta;
196	for (int32 i = 0; i < numChars; i++) {
197		char realChar = RealCharAt(fromIndex + i);
198		if ((realChar & 0xc0) == 0x80)
199			continue;
200		if (realChar == inChar) {
201			*ioDelta = i;
202			return true;
203		}
204	}
205
206	return false;
207}
208
209
210const char*
211TextGapBuffer::Text()
212{
213	const char* realText = RealText();
214
215	if (fPasswordMode) {
216		const uint32 numChars = UTF8CountChars(realText, Length());
217		const uint32 bulletCharLen = UTF8CountBytes(B_UTF8_BULLET, 1);
218		uint32 newSize = numChars * bulletCharLen + 1;
219
220		if ((uint32)fScratchSize < newSize) {
221			fScratchBuffer = (char*)realloc(fScratchBuffer, newSize);
222			fScratchSize = newSize;
223		}
224
225		char* scratchPtr = fScratchBuffer;
226		for (uint32 i = 0; i < numChars; i++) {
227			memcpy(scratchPtr, B_UTF8_BULLET, bulletCharLen);
228			scratchPtr += bulletCharLen;
229		}
230		*scratchPtr = '\0';
231
232		return fScratchBuffer;
233	}
234
235	return realText;
236}
237
238
239const char*
240TextGapBuffer::RealText()
241{
242	_MoveGapTo(fItemCount);
243
244	if (fGapCount == 0)
245		_EnlargeGapTo(kTextGapBufferBlockSize);
246
247	fBuffer[fItemCount] = '\0';
248	return fBuffer;
249}
250
251
252void
253TextGapBuffer::GetString(int32 offset, int32 length, char* buffer)
254{
255	if (buffer == NULL)
256		return;
257
258	int32 textLen = Length();
259
260	if (offset < 0 || offset > (textLen - 1) || length < 1) {
261		buffer[0] = '\0';
262		return;
263	}
264
265	length = ((offset + length) > textLen) ? textLen - offset : length;
266
267	bool isStartBeforeGap = (offset < fGapIndex);
268	bool isEndBeforeGap = ((offset + length - 1) < fGapIndex);
269
270	if (isStartBeforeGap == isEndBeforeGap) {
271		char* source = fBuffer + offset;
272		if (!isStartBeforeGap)
273			source += fGapCount;
274
275		memcpy(buffer, source, length);
276
277	} else {
278		// if we are here, it can only be that start is before gap,
279		// and the end is after gap.
280
281		int32 beforeLen = fGapIndex - offset;
282		int32 afterLen = length - beforeLen;
283
284		memcpy(buffer, fBuffer + offset, beforeLen);
285		memcpy(buffer + beforeLen, fBuffer + fGapIndex + fGapCount, afterLen);
286
287	}
288
289	buffer[length] = '\0';
290}
291
292
293bool
294TextGapBuffer::PasswordMode() const
295{
296	return fPasswordMode;
297}
298
299
300void
301TextGapBuffer::SetPasswordMode(bool state)
302{
303	fPasswordMode = state;
304}
305
306
307void
308TextGapBuffer::_MoveGapTo(int32 toIndex)
309{
310	if (toIndex == fGapIndex)
311		return;
312	if (toIndex > fItemCount) {
313		debugger("MoveGapTo: invalid toIndex supplied");
314		return;
315	}
316
317	int32 srcIndex = 0;
318	int32 dstIndex = 0;
319	int32 count = 0;
320	if (toIndex > fGapIndex) {
321		srcIndex = fGapIndex + fGapCount;
322		dstIndex = fGapIndex;
323		count = toIndex - fGapIndex;
324	} else {
325		srcIndex = toIndex;
326		dstIndex = toIndex + fGapCount;
327		count = fGapIndex- toIndex;
328	}
329
330	if (count > 0)
331		memmove(fBuffer + dstIndex, fBuffer + srcIndex, count);
332
333	fGapIndex = toIndex;
334}
335
336
337void
338TextGapBuffer::_EnlargeGapTo(int32 inCount)
339{
340	if (inCount == fGapCount)
341		return;
342
343	fBuffer = (char*)realloc(fBuffer, fItemCount + inCount);
344	memmove(fBuffer + fGapIndex + inCount, fBuffer + fGapIndex + fGapCount,
345		fBufferCount - (fGapIndex + fGapCount));
346
347	fGapCount = inCount;
348	fBufferCount = fItemCount + fGapCount;
349}
350
351
352void
353TextGapBuffer::_ShrinkGapTo(int32 inCount)
354{
355	if (inCount == fGapCount)
356		return;
357
358	memmove(fBuffer + fGapIndex + inCount, fBuffer + fGapIndex + fGapCount,
359		fBufferCount - (fGapIndex + fGapCount));
360	fBuffer = (char*)realloc(fBuffer, fItemCount + inCount);
361
362	fGapCount = inCount;
363	fBufferCount = fItemCount + fGapCount;
364}
365
366
367} // namespace BPrivate
368