root/src/kits/interface/textview_support/TextGapBuffer.cpp
/*
 * Copyright 2001-2006, Haiku, Inc. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Marc Flerackers (mflerackers@androme.be)
 *              Stefano Ceccherini (stefano.ceccherini@gmail.com)
 */


#include <cstdio>
#include <cstdlib>
#include <cstring>

#include <utf8_functions.h>

#include <File.h>
#include <InterfaceDefs.h> // for B_UTF8_BULLET

#include "TextGapBuffer.h"


namespace BPrivate {


static const int32 kTextGapBufferBlockSize = 2048;


TextGapBuffer::TextGapBuffer()
        :
        fItemCount(0),
        fBuffer(NULL),
        fBufferCount(kTextGapBufferBlockSize + fItemCount),
        fGapIndex(fItemCount),
        fGapCount(fBufferCount - fGapIndex),
        fScratchBuffer(NULL),
        fScratchSize(0),
        fPasswordMode(false)
{
        fBuffer = (char*)malloc(kTextGapBufferBlockSize + fItemCount);
        fScratchBuffer = NULL;
}


TextGapBuffer::~TextGapBuffer()
{
        free(fBuffer);
        free(fScratchBuffer);
}


void
TextGapBuffer::InsertText(const char* inText, int32 inNumItems, int32 inAtIndex)
{
        if (inNumItems < 1)
                return;

        inAtIndex = (inAtIndex > fItemCount) ? fItemCount : inAtIndex;
        inAtIndex = (inAtIndex < 0) ? 0 : inAtIndex;

        if (inAtIndex != fGapIndex)
                _MoveGapTo(inAtIndex);

        if (fGapCount < inNumItems)
                _EnlargeGapTo(inNumItems + kTextGapBufferBlockSize);

        memcpy(fBuffer + fGapIndex, inText, inNumItems);

        fGapCount -= inNumItems;
        fGapIndex += inNumItems;
        fItemCount += inNumItems;
}


bool
TextGapBuffer::InsertText(BFile* file, int32 fileOffset, int32 inNumItems,
        int32 inAtIndex)
{
        off_t fileSize;

        if (file->GetSize(&fileSize) != B_OK
                || !file->IsReadable())
                return false;

        // Clamp the text length to the file size
        fileSize -= fileOffset;

        if (fileSize < inNumItems)
                inNumItems = fileSize;

        if (inNumItems < 1)
                return false;

        inAtIndex = (inAtIndex > fItemCount) ? fItemCount : inAtIndex;
        inAtIndex = (inAtIndex < 0) ? 0 : inAtIndex;

        if (inAtIndex != fGapIndex)
                _MoveGapTo(inAtIndex);

        if (fGapCount < inNumItems)
                _EnlargeGapTo(inNumItems + kTextGapBufferBlockSize);

        // Finally, read the data and put it into the buffer
        if (file->ReadAt(fileOffset, fBuffer + fGapIndex, inNumItems) > 0) {
                fGapCount -= inNumItems;
                fGapIndex += inNumItems;
                fItemCount += inNumItems;
        }

        return true;
}


void
TextGapBuffer::RemoveRange(int32 start, int32 end)
{
        int32 inAtIndex = start;
        int32 inNumItems = end - start;

        if (inNumItems < 1)
                return;

        inAtIndex = (inAtIndex > fItemCount - 1) ? (fItemCount - 1) : inAtIndex;
        inAtIndex = (inAtIndex < 0) ? 0 : inAtIndex;

        _MoveGapTo(inAtIndex);

        fGapCount += inNumItems;
        fItemCount -= inNumItems;

        if (fGapCount > kTextGapBufferBlockSize)
                _ShrinkGapTo(kTextGapBufferBlockSize / 2);
}


const char*
TextGapBuffer::GetString(int32 fromOffset, int32* _numBytes)
{
        const char* result = "";
        if (_numBytes == NULL)
                return result;

        int32 numBytes = *_numBytes;
        if (numBytes < 1)
                return result;

        bool isStartBeforeGap = fromOffset < fGapIndex;
        bool isEndBeforeGap = (fromOffset + numBytes - 1) < fGapIndex;

        if (isStartBeforeGap == isEndBeforeGap) {
                result = fBuffer + fromOffset;
                if (!isStartBeforeGap)
                        result += fGapCount;
        } else {
                if (fScratchSize < numBytes) {
                        fScratchBuffer = (char*)realloc(fScratchBuffer, numBytes);
                        fScratchSize = numBytes;
                }

                for (int32 i = 0; i < numBytes; i++)
                        fScratchBuffer[i] = RealCharAt(fromOffset + i);

                result = fScratchBuffer;
        }

        // TODO: this could be improved. We are overwriting what we did some lines
        // ago, we could just avoid to do that.
        if (fPasswordMode) {
                uint32 numChars = UTF8CountChars(result, numBytes);
                uint32 charLen = UTF8CountBytes(B_UTF8_BULLET, 1);
                uint32 newSize = numChars * charLen;

                if ((uint32)fScratchSize < newSize) {
                        fScratchBuffer = (char*)realloc(fScratchBuffer, newSize);
                        fScratchSize = newSize;
                }
                result = fScratchBuffer;

                char* scratchPtr = fScratchBuffer;
                for (uint32 i = 0; i < numChars; i++) {
                        memcpy(scratchPtr, B_UTF8_BULLET, charLen);
                        scratchPtr += charLen;
                }

                *_numBytes = newSize;
        }

        return result;
}


bool
TextGapBuffer::FindChar(char inChar, int32 fromIndex, int32* ioDelta)
{
        int32 numChars = *ioDelta;
        for (int32 i = 0; i < numChars; i++) {
                char realChar = RealCharAt(fromIndex + i);
                if ((realChar & 0xc0) == 0x80)
                        continue;
                if (realChar == inChar) {
                        *ioDelta = i;
                        return true;
                }
        }

        return false;
}


const char*
TextGapBuffer::Text()
{
        const char* realText = RealText();

        if (fPasswordMode) {
                const uint32 numChars = UTF8CountChars(realText, Length());
                const uint32 bulletCharLen = UTF8CountBytes(B_UTF8_BULLET, 1);
                uint32 newSize = numChars * bulletCharLen + 1;

                if ((uint32)fScratchSize < newSize) {
                        fScratchBuffer = (char*)realloc(fScratchBuffer, newSize);
                        fScratchSize = newSize;
                }

                char* scratchPtr = fScratchBuffer;
                for (uint32 i = 0; i < numChars; i++) {
                        memcpy(scratchPtr, B_UTF8_BULLET, bulletCharLen);
                        scratchPtr += bulletCharLen;
                }
                *scratchPtr = '\0';

                return fScratchBuffer;
        }

        return realText;
}


const char*
TextGapBuffer::RealText()
{
        _MoveGapTo(fItemCount);

        if (fGapCount == 0)
                _EnlargeGapTo(kTextGapBufferBlockSize);

        fBuffer[fItemCount] = '\0';
        return fBuffer;
}


void
TextGapBuffer::GetString(int32 offset, int32 length, char* buffer)
{
        if (buffer == NULL)
                return;

        int32 textLen = Length();

        if (offset < 0 || offset > (textLen - 1) || length < 1) {
                buffer[0] = '\0';
                return;
        }

        length = ((offset + length) > textLen) ? textLen - offset : length;

        bool isStartBeforeGap = (offset < fGapIndex);
        bool isEndBeforeGap = ((offset + length - 1) < fGapIndex);

        if (isStartBeforeGap == isEndBeforeGap) {
                char* source = fBuffer + offset;
                if (!isStartBeforeGap)
                        source += fGapCount;

                memcpy(buffer, source, length);

        } else {
                // if we are here, it can only be that start is before gap,
                // and the end is after gap.

                int32 beforeLen = fGapIndex - offset;
                int32 afterLen = length - beforeLen;

                memcpy(buffer, fBuffer + offset, beforeLen);
                memcpy(buffer + beforeLen, fBuffer + fGapIndex + fGapCount, afterLen);

        }

        buffer[length] = '\0';
}


bool
TextGapBuffer::PasswordMode() const
{
        return fPasswordMode;
}


void
TextGapBuffer::SetPasswordMode(bool state)
{
        fPasswordMode = state;
}


void
TextGapBuffer::_MoveGapTo(int32 toIndex)
{
        if (toIndex == fGapIndex)
                return;
        if (toIndex > fItemCount) {
                debugger("MoveGapTo: invalid toIndex supplied");
                return;
        }

        int32 srcIndex = 0;
        int32 dstIndex = 0;
        int32 count = 0;
        if (toIndex > fGapIndex) {
                srcIndex = fGapIndex + fGapCount;
                dstIndex = fGapIndex;
                count = toIndex - fGapIndex;
        } else {
                srcIndex = toIndex;
                dstIndex = toIndex + fGapCount;
                count = fGapIndex- toIndex;
        }

        if (count > 0)
                memmove(fBuffer + dstIndex, fBuffer + srcIndex, count);

        fGapIndex = toIndex;
}


void
TextGapBuffer::_EnlargeGapTo(int32 inCount)
{
        if (inCount == fGapCount)
                return;

        fBuffer = (char*)realloc(fBuffer, fItemCount + inCount);
        memmove(fBuffer + fGapIndex + inCount, fBuffer + fGapIndex + fGapCount,
                fBufferCount - (fGapIndex + fGapCount));

        fGapCount = inCount;
        fBufferCount = fItemCount + fGapCount;
}


void
TextGapBuffer::_ShrinkGapTo(int32 inCount)
{
        if (inCount == fGapCount)
                return;

        memmove(fBuffer + fGapIndex + inCount, fBuffer + fGapIndex + fGapCount,
                fBufferCount - (fGapIndex + fGapCount));
        fBuffer = (char*)realloc(fBuffer, fItemCount + inCount);

        fGapCount = inCount;
        fBufferCount = fItemCount + fGapCount;
}


} // namespace BPrivate