text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

//==============================================================================
/**
    A class to take care of the logic involved with the loading/saving of some kind
    of document.

    There's quite a lot of tedious logic involved in writing all the load/save/save-as
    functions you need for documents that get saved to a file, so this class attempts
    to abstract most of the boring stuff.

    Your subclass should just implement all the pure virtual methods, and you can
    then use the higher-level public methods to do the load/save dialogs, to warn the user
    about overwriting files, etc.

    The document object keeps track of whether it has changed since it was last saved or
    loaded, so when you change something, call its changed() method. This will set a
    flag so it knows it needs saving, and will also broadcast a change message using the
    ChangeBroadcaster base class.

    @see ChangeBroadcaster
*/
class JUCE_API FileBasedDocument  : public ChangeBroadcaster
{
public:
    /** Creates a FileBasedDocument.

        @param fileExtension            the extension to use when loading/saving files, e.g. ".doc"
        @param fileWildCard             the wildcard to use in file dialogs, e.g. "*.doc"
        @param openFileDialogTitle      the title to show on an open-file dialog, e.g. "Choose a file to open.."
        @param saveFileDialogTitle      the title to show on an save-file dialog, e.g. "Choose a file to save as.."
    */
    FileBasedDocument (const String& fileExtension,
                       const String& fileWildCard,
                       const String& openFileDialogTitle,
                       const String& saveFileDialogTitle);

    /** Destructor. */
    virtual ~FileBasedDocument();

    //==============================================================================
    /** Returns true if the changed() method has been called since the file was
        last saved or loaded.

        @see setChangedFlag, changed
    */
    bool hasChangedSinceSaved() const                           { return changedSinceSave; }

    /** Called to indicate that the document has changed and needs saving.

        This method will also trigger a change message to be sent out using the
        ChangeBroadcaster base class.

        After calling the method, the hasChangedSinceSaved() method will return true, until
        it is reset either by saving to a file or using the setChangedFlag() method.

        @see hasChangedSinceSaved, setChangedFlag
    */
    virtual void changed();

    /** Sets the state of the 'changed' flag.

        The 'changed' flag is set to true when the changed() method is called - use this method
        to reset it or to set it without also broadcasting a change message.

        @see changed, hasChangedSinceSaved
    */
    void setChangedFlag (bool hasChanged);

    //==============================================================================
    /** Tries to open a file.

        If the file opens correctly, the document's file (see the getFile() method) is set
        to this new one; if it fails, the document's file is left unchanged, and optionally
        a message box is shown telling the user there was an error.

        @returns A result indicating whether the new file loaded successfully, or the error
                 message if it failed.
        @see loadDocument, loadFromUserSpecifiedFile
    */
    Result loadFrom (const File& fileToLoadFrom,
                     bool showMessageOnFailure);

    /** Asks the user for a file and tries to load it.

        This will pop up a dialog box using the title, file extension and
        wildcard specified in the document's constructor, and asks the user
        for a file. If they pick one, the loadFrom() method is used to
        try to load it, optionally showing a message if it fails.

        @returns    a result indicating success; This will be a failure message if the user
                    cancelled or if they picked a file which failed to load correctly
        @see loadFrom
    */
    Result loadFromUserSpecifiedFile (bool showMessageOnFailure);

    //==============================================================================
    /** A set of possible outcomes of one of the save() methods
    */
    enum SaveResult
    {
        savedOk = 0,            /**< indicates that a file was saved successfully. */
        userCancelledSave,      /**< indicates that the user aborted the save operation. */
        failedToWriteToFile     /**< indicates that it tried to write to a file but this failed. */
    };

    /** Tries to save the document to the last file it was saved or loaded from.

        This will always try to write to the file, even if the document isn't flagged as
        having changed.

        @param askUserForFileIfNotSpecified     if there's no file currently specified and this is
                                                true, it will prompt the user to pick a file, as if
                                                saveAsInteractive() was called.
        @param showMessageOnFailure             if true it will show a warning message when if the
                                                save operation fails
        @see saveIfNeededAndUserAgrees, saveAs, saveAsInteractive
    */
    SaveResult save (bool askUserForFileIfNotSpecified,
                     bool showMessageOnFailure);

    /** If the file needs saving, it'll ask the user if that's what they want to do, and save
        it if they say yes.

        If you've got a document open and want to close it (e.g. to quit the app), this is the
        method to call.

        If the document doesn't need saving it'll return the value savedOk so
        you can go ahead and delete the document.

        If it does need saving it'll prompt the user, and if they say "discard changes" it'll
        return savedOk, so again, you can safely delete the document.

        If the user clicks "cancel", it'll return userCancelledSave, so if you can abort the
        close-document operation.

        And if they click "save changes", it'll try to save and either return savedOk, or
        failedToWriteToFile if there was a problem.

        @see save, saveAs, saveAsInteractive
    */
    SaveResult saveIfNeededAndUserAgrees();

    /** Tries to save the document to a specified file.

        If this succeeds, it'll also change the document's internal file (as returned by
        the getFile() method). If it fails, the file will be left unchanged.

        @param newFile                      the file to try to write to
        @param warnAboutOverwritingExistingFiles    if true and the file exists, it'll ask
                                            the user first if they want to overwrite it
        @param askUserForFileIfNotSpecified if the file is non-existent and this is true, it'll
                                            use the saveAsInteractive() method to ask the user for a
                                            filename
        @param showMessageOnFailure         if true and the write operation fails, it'll show
                                            a message box to warn the user
        @see saveIfNeededAndUserAgrees, save, saveAsInteractive
    */
    SaveResult saveAs (const File& newFile,
                       bool warnAboutOverwritingExistingFiles,
                       bool askUserForFileIfNotSpecified,
                       bool showMessageOnFailure);

    /** Prompts the user for a filename and tries to save to it.

        This will pop up a dialog box using the title, file extension and
        wildcard specified in the document's constructor, and asks the user
        for a file. If they pick one, the saveAs() method is used to try to save
        to this file.

        @param warnAboutOverwritingExistingFiles    if true and the file exists, it'll ask
                                            the user first if they want to overwrite it
        @see saveIfNeededAndUserAgrees, save, saveAs
    */
    SaveResult saveAsInteractive (bool warnAboutOverwritingExistingFiles);

    //==============================================================================
    /** Returns the file that this document was last successfully saved or loaded from.

        When the document object is created, this will be set to File().

        It is changed when one of the load or save methods is used, or when setFile()
        is used to explicitly set it.
    */
    const File& getFile() const                             { return documentFile; }

    /** Sets the file that this document thinks it was loaded from.

        This won't actually load anything - it just changes the file stored internally.

        @see getFile
    */
    void setFile (const File& newFile);

protected:
    //==============================================================================
    /** Overload this to return the title of the document.

        This is used in message boxes, filenames and file choosers, so it should be
        something sensible.
    */
    virtual String getDocumentTitle() = 0;

    /** This method should try to load your document from the given file.
        @returns a Result object to indicate the whether there was an error.
    */
    virtual Result loadDocument (const File& file) = 0;

    /** This method should try to write your document to the given file.
        @returns a Result object to indicate the whether there was an error.
    */
    virtual Result saveDocument (const File& file) = 0;

    /** This is used for dialog boxes to make them open at the last folder you
        were using.

        getLastDocumentOpened() and setLastDocumentOpened() are used to store
        the last document that was used - you might want to store this value
        in a static variable, or even in your application's properties. It should
        be a global setting rather than a property of this object.

        This method works very well in conjunction with a RecentlyOpenedFilesList
        object to manage your recent-files list.

        As a default value, it's ok to return File(), and the document object will
        use a sensible one instead.

        @see RecentlyOpenedFilesList
    */
    virtual File getLastDocumentOpened() = 0;

    /** This is used for dialog boxes to make them open at the last folder you
        were using.

        getLastDocumentOpened() and setLastDocumentOpened() are used to store
        the last document that was used - you might want to store this value
        in a static variable, or even in your application's properties. It should
        be a global setting rather than a property of this object.

        This method works very well in conjunction with a RecentlyOpenedFilesList
        object to manage your recent-files list.

        @see RecentlyOpenedFilesList
    */
    virtual void setLastDocumentOpened (const File& file) = 0;

   #if JUCE_MODAL_LOOPS_PERMITTED
    /** This is called by saveAsInteractive() to allow you to optionally customise the
        filename that the user is presented with in the save dialog.
        The defaultFile parameter is an initial suggestion based on what the class knows
        about the current document - you can return a variation on this file with a different
        extension, etc, or just return something completely different.
    */
    virtual File getSuggestedSaveAsFile (const File& defaultFile);
   #endif

private:
    //==============================================================================
    File documentFile;
    bool changedSinceSave;
    String fileExtension, fileWildcard, openFileDialogTitle, saveFileDialogTitle;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileBasedDocument)
};

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

/** @internal */
bool juce_handleXEmbedEvent (ComponentPeer*, void*);
/** @internal */
unsigned long juce_getCurrentFocusWindow (ComponentPeer*);

#if JUCE_LINUX || DOXYGEN

//==============================================================================
/**
    A Linux-specific class that can embed a foreign X11 widget.

    Use this class to embed a foreign X11 widget from other toolkits such as
    GTK+ or QT.

    There are two ways to initiate the Xembed protocol. Either the client creates
    a window and passes this to the host (client initiated) or the host
    creates a window in which the client can reparent it's client widget
    (host initiated). XEmbedComponent supports both protocol types.

    This is how you embed a GTK+ widget: if you are using the client
    initiated version of the protocol, then create a new gtk widget with
    gtk_plug_new (0). Then query the window id of the plug via gtk_plug_get_id().
    Pass this id to the constructor of this class.

    If you are using the host initiated version of the protocol, then first create
    the XEmbedComponent using the default constructor. Use getHostWindowID to get
    the window id of the host, use this to construct your gtk plug via gtk_plug_new.

    A similar approach can be used to embed QT widgets via QT's QX11EmbedWidget
    class.

    Other toolkits or raw X11 widgets should follow the X11 embed protocol:
    https://specifications.freedesktop.org/xembed-spec/xembed-spec-latest.html
*/
class XEmbedComponent : public Component
{
public:
    //==============================================================================

    /** Creates a JUCE component wrapping a foreign widget

        Use this constructor if you are using the host initiated version
        of the XEmbedProtocol. When using this version of the protocol
        you must call getHostWindowID() and pass this id to the foreign toolkit.
    */
    XEmbedComponent (bool wantsKeyboardFocus = true,
                     bool allowForeignWidgetToResizeComponent = false);

    /** Create a JUCE component wrapping the foreign widget with id wID

        Use this constructor if you are using the client initiated version
        of the XEmbedProtocol.
    */
    XEmbedComponent (unsigned long wID, bool wantsKeyboardFocus = true,
                     bool allowForeignWidgetToResizeComponent = false);

    /** Destructor. */
    ~XEmbedComponent();

    /** Use this method to retrieve the host's window id when using the
        host initiated version of the XEmbedProtocol
    */
    unsigned long getHostWindowID();

protected:
    //==============================================================================
    /** @internal */
    void paint (Graphics&) override;
    void focusGained (FocusChangeType) override;
    void focusLost (FocusChangeType) override;
    void broughtToFront() override;

private:
    friend bool juce::juce_handleXEmbedEvent (ComponentPeer*, void*);
    friend unsigned long juce_getCurrentFocusWindow (ComponentPeer*);

    class Pimpl;
    friend struct ContainerDeletePolicy<Pimpl>;
    ScopedPointer<Pimpl> pimpl;
};

#endif

} // namespace juce

repo_name: fedden/RenderMan

mime_type: text/x-c++


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

#if JUCE_MAC || DOXYGEN

//==============================================================================
/**
    A Mac-specific class that can create and embed an NSView inside itself.

    To use it, create one of these, put it in place and make sure it's visible in a
    window, then use setView() to assign an NSView to it. The view will then be
    moved and resized to follow the movements of this component.

    Of course, since the view is a native object, it'll obliterate any
    juce components that may overlap this component, but that's life.
*/
class JUCE_API  NSViewComponent   : public Component
{
public:
    //==============================================================================
    /** Create an initially-empty container. */
    NSViewComponent();

    /** Destructor. */
    ~NSViewComponent();

    /** Assigns an NSView to this peer.

        The view will be retained and released by this component for as long as
        it is needed. To remove the current view, just call setView (nullptr).

        Note: a void* is used here to avoid including the cocoa headers as
        part of the juce.h, but the method expects an NSView*.
    */
    void setView (void* nsView);

    /** Returns the current NSView.

        Note: a void* is returned here to avoid the needing to include the cocoa
        headers, so you should just cast the return value to an NSView*.
    */
    void* getView() const;

    /** Resizes this component to fit the view that it contains. */
    void resizeToFitView();

    //==============================================================================
    /** @internal */
    void paint (Graphics&) override;
    /** @internal */
    void alphaChanged() override;
    /** @internal */
    static ReferenceCountedObject* attachViewToComponent (Component&, void*);

private:
    ReferenceCountedObjectPtr<ReferenceCountedObject> attachment;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NSViewComponent)
};

#endif

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

#if JUCE_WINDOWS || DOXYGEN

//==============================================================================
/**
    A Windows-specific class that can create and embed an ActiveX control inside
    itself.

    To use it, create one of these, put it in place and make sure it's visible in a
    window, then use createControl() to instantiate an ActiveX control. The control
    will then be moved and resized to follow the movements of this component.

    Of course, since the control is a heavyweight window, it'll obliterate any
    juce components that may overlap this component, but that's life.
*/
class JUCE_API  ActiveXControlComponent   : public Component
{
public:
    //==============================================================================
    /** Create an initially-empty container. */
    ActiveXControlComponent();

    /** Destructor. */
    ~ActiveXControlComponent();

    /** Tries to create an ActiveX control and embed it in this peer.

        The peer controlIID is a pointer to an IID structure - it's treated
        as a void* because when including the Juce headers, you might not always
        have included windows.h first, in which case IID wouldn't be defined.

        e.g. @code
        const IID myIID = __uuidof (QTControl);
        myControlComp->createControl (&myIID);
        @endcode
    */
    bool createControl (const void* controlIID);

    /** Deletes the ActiveX control, if one has been created.
    */
    void deleteControl();

    /** Returns true if a control is currently in use. */
    bool isControlOpen() const noexcept                 { return control != nullptr; }

    /** Does a QueryInterface call on the embedded control object.

        This allows you to cast the control to whatever type of COM object you need.

        The iid parameter is a pointer to an IID structure - it's treated
        as a void* because when including the Juce headers, you might not always
        have included windows.h first, in which case IID wouldn't be defined, but
        you should just pass a pointer to an IID.

        e.g. @code
        const IID iid = __uuidof (IOleWindow);

        IOleWindow* oleWindow = (IOleWindow*) myControlComp->queryInterface (&iid);

        if (oleWindow != nullptr)
        {
            HWND hwnd;
            oleWindow->GetWindow (&hwnd);

            ...

            oleWindow->Release();
        }
        @endcode
    */
    void* queryInterface (const void* iid) const;

    /** Set this to false to stop mouse events being allowed through to the control.
    */
    void setMouseEventsAllowed (bool eventsCanReachControl);

    /** Returns true if mouse events are allowed to get through to the control.
    */
    bool areMouseEventsAllowed() const noexcept                 { return mouseEventsAllowed; }

    //==============================================================================
    /** @internal */
    void paint (Graphics&) override;

    /** @internal */
    intptr_t offerEventToActiveXControl (void*);
    static intptr_t offerEventToActiveXControlStatic (void*);

private:
    class Pimpl;
    friend struct ContainerDeletePolicy<Pimpl>;
    ScopedPointer<Pimpl> control;
    bool mouseEventsAllowed = true;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ActiveXControlComponent)
};

#endif

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

#if JUCE_IOS || DOXYGEN

//==============================================================================
/**
    An iOS-specific class that can create and embed an UIView inside itself.

    To use it, create one of these, put it in place and make sure it's visible in a
    window, then use setView() to assign a UIView to it. The view will then be
    moved and resized to follow the movements of this component.

    Of course, since the view is a native object, it'll obliterate any
    juce components that may overlap this component, but that's life.
*/
class JUCE_API  UIViewComponent   : public Component
{
public:
    //==============================================================================
    /** Create an initially-empty container. */
    UIViewComponent();

    /** Destructor. */
    ~UIViewComponent();

    /** Assigns an UIView to this peer.

        The view will be retained and released by this component for as long as
        it is needed. To remove the current view, just call setView (nullptr).

        Note: a void* is used here to avoid including the cocoa headers as
        part of the juce.h, but the method expects an UIView*.
    */
    void setView (void* uiView);

    /** Returns the current UIView.

        Note: a void* is returned here to avoid the needing to include the cocoa
        headers, so you should just cast the return value to an UIView*.
    */
    void* getView() const;

    /** Resizes this component to fit the view that it contains. */
    void resizeToFitView();

    //==============================================================================
    /** @internal */
    void paint (Graphics&) override;

private:
    class Pimpl;
    friend class Pimpl;
    ScopedPointer<Pimpl> pimpl;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIViewComponent)
};

#endif

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

class CodeDocumentLine
{
public:
    CodeDocumentLine (const String::CharPointerType startOfLine,
                      const String::CharPointerType endOfLine,
                      const int lineLen,
                      const int numNewLineChars,
                      const int startInFile)
        : line (startOfLine, endOfLine),
          lineStartInFile (startInFile),
          lineLength (lineLen),
          lineLengthWithoutNewLines (lineLen - numNewLineChars)
    {
    }

    static void createLines (Array<CodeDocumentLine*>& newLines, StringRef text)
    {
        auto t = text.text;
        int charNumInFile = 0;
        bool finished = false;

        while (! (finished || t.isEmpty()))
        {
            auto startOfLine = t;
            auto startOfLineInFile = charNumInFile;
            int lineLength = 0;
            int numNewLineChars = 0;

            for (;;)
            {
                auto c = t.getAndAdvance();

                if (c == 0)
                {
                    finished = true;
                    break;
                }

                ++charNumInFile;
                ++lineLength;

                if (c == '\r')
                {
                    ++numNewLineChars;

                    if (*t == '\n')
                    {
                        ++t;
                        ++charNumInFile;
                        ++lineLength;
                        ++numNewLineChars;
                    }

                    break;
                }

                if (c == '\n')
                {
                    ++numNewLineChars;
                    break;
                }
            }

            newLines.add (new CodeDocumentLine (startOfLine, t, lineLength,
                                                numNewLineChars, startOfLineInFile));
        }

        jassert (charNumInFile == text.length());
    }

    bool endsWithLineBreak() const noexcept
    {
        return lineLengthWithoutNewLines != lineLength;
    }

    void updateLength() noexcept
    {
        lineLength = 0;
        lineLengthWithoutNewLines = 0;

        for (auto t = line.getCharPointer();;)
        {
            auto c = t.getAndAdvance();

            if (c == 0)
                break;

            ++lineLength;

            if (c != '\n' && c != '\r')
                lineLengthWithoutNewLines = lineLength;
        }
    }

    String line;
    int lineStartInFile, lineLength, lineLengthWithoutNewLines;
};

//==============================================================================
CodeDocument::Iterator::Iterator (const CodeDocument& doc) noexcept : document (&doc) {}

CodeDocument::Iterator::~Iterator() noexcept {}

juce_wchar CodeDocument::Iterator::nextChar() noexcept
{
    for (;;)
    {
        if (charPointer.getAddress() == nullptr)
        {
            if (auto* l = document->lines[line])
                charPointer = l->line.getCharPointer();
            else
                return 0;
        }

        if (auto result = charPointer.getAndAdvance())
        {
            if (charPointer.isEmpty())
            {
                ++line;
                charPointer = nullptr;
            }

            ++position;
            return result;
        }

        ++line;
        charPointer = nullptr;
    }
}

void CodeDocument::Iterator::skip() noexcept
{
    nextChar();
}

void CodeDocument::Iterator::skipToEndOfLine() noexcept
{
    if (charPointer.getAddress() == nullptr)
    {
        if (auto* l = document->lines[line])
            charPointer = l->line.getCharPointer();
        else
            return;
    }

    position += (int) charPointer.length();
    ++line;
    charPointer = nullptr;
}

juce_wchar CodeDocument::Iterator::peekNextChar() const noexcept
{
    if (charPointer.getAddress() == nullptr)
    {
        if (auto* l = document->lines[line])
            charPointer = l->line.getCharPointer();
        else
            return 0;
    }

    if (auto c = *charPointer)
        return c;

    if (auto* l = document->lines [line + 1])
        return l->line[0];

    return 0;
}

void CodeDocument::Iterator::skipWhitespace() noexcept
{
    while (CharacterFunctions::isWhitespace (peekNextChar()))
        skip();
}

bool CodeDocument::Iterator::isEOF() const noexcept
{
    return charPointer.getAddress() == nullptr && line >= document->lines.size();
}

//==============================================================================
CodeDocument::Position::Position() noexcept
{
}

CodeDocument::Position::Position (const CodeDocument& ownerDocument,
                                  const int lineNum, const int index) noexcept
    : owner (const_cast<CodeDocument*> (&ownerDocument)),
      line (lineNum), indexInLine (index)
{
    setLineAndIndex (lineNum, index);
}

CodeDocument::Position::Position (const CodeDocument& ownerDocument, int pos) noexcept
    : owner (const_cast<CodeDocument*> (&ownerDocument))
{
    setPosition (pos);
}

CodeDocument::Position::Position (const Position& other) noexcept
    : owner (other.owner), characterPos (other.characterPos), line (other.line),
      indexInLine (other.indexInLine)
{
    jassert (*this == other);
}

CodeDocument::Position::~Position()
{
    setPositionMaintained (false);
}

CodeDocument::Position& CodeDocument::Position::operator= (const Position& other)
{
    if (this != &other)
    {
        const bool wasPositionMaintained = positionMaintained;
        if (owner != other.owner)
            setPositionMaintained (false);

        owner = other.owner;
        line = other.line;
        indexInLine = other.indexInLine;
        characterPos = other.characterPos;
        setPositionMaintained (wasPositionMaintained);

        jassert (*this == other);
    }

    return *this;
}

bool CodeDocument::Position::operator== (const Position& other) const noexcept
{
    jassert ((characterPos == other.characterPos)
               == (line == other.line && indexInLine == other.indexInLine));

    return characterPos == other.characterPos
            && line == other.line
            && indexInLine == other.indexInLine
            && owner == other.owner;
}

bool CodeDocument::Position::operator!= (const Position& other) const noexcept
{
    return ! operator== (other);
}

void CodeDocument::Position::setLineAndIndex (const int newLineNum, const int newIndexInLine)
{
    jassert (owner != nullptr);

    if (owner->lines.size() == 0)
    {
        line = 0;
        indexInLine = 0;
        characterPos = 0;
    }
    else
    {
        if (newLineNum >= owner->lines.size())
        {
            line = owner->lines.size() - 1;

            auto& l = *owner->lines.getUnchecked (line);
            indexInLine = l.lineLengthWithoutNewLines;
            characterPos = l.lineStartInFile + indexInLine;
        }
        else
        {
            line = jmax (0, newLineNum);

            auto& l = *owner->lines.getUnchecked (line);

            if (l.lineLengthWithoutNewLines > 0)
                indexInLine = jlimit (0, l.lineLengthWithoutNewLines, newIndexInLine);
            else
                indexInLine = 0;

            characterPos = l.lineStartInFile + indexInLine;
        }
    }
}

void CodeDocument::Position::setPosition (const int newPosition)
{
    jassert (owner != nullptr);

    line = 0;
    indexInLine = 0;
    characterPos = 0;

    if (newPosition > 0)
    {
        int lineStart = 0;
        auto lineEnd = owner->lines.size();

        for (;;)
        {
            if (lineEnd - lineStart < 4)
            {
                for (int i = lineStart; i < lineEnd; ++i)
                {
                    auto& l = *owner->lines.getUnchecked (i);
                    auto index = newPosition - l.lineStartInFile;

                    if (index >= 0 && (index < l.lineLength || i == lineEnd - 1))
                    {
                        line = i;
                        indexInLine = jmin (l.lineLengthWithoutNewLines, index);
                        characterPos = l.lineStartInFile + indexInLine;
                    }
                }

                break;
            }
            else
            {
                auto midIndex = (lineStart + lineEnd + 1) / 2;

                if (newPosition >= owner->lines.getUnchecked (midIndex)->lineStartInFile)
                    lineStart = midIndex;
                else
                    lineEnd = midIndex;
            }
        }
    }
}

void CodeDocument::Position::moveBy (int characterDelta)
{
    jassert (owner != nullptr);

    if (characterDelta == 1)
    {
        setPosition (getPosition());

        // If moving right, make sure we don't get stuck between the \r and \n characters..
        if (line < owner->lines.size())
        {
            auto& l = *owner->lines.getUnchecked (line);

            if (indexInLine + characterDelta < l.lineLength
                 && indexInLine + characterDelta >= l.lineLengthWithoutNewLines + 1)
                ++characterDelta;
        }
    }

    setPosition (characterPos + characterDelta);
}

CodeDocument::Position CodeDocument::Position::movedBy (const int characterDelta) const
{
    CodeDocument::Position p (*this);
    p.moveBy (characterDelta);
    return p;
}

CodeDocument::Position CodeDocument::Position::movedByLines (const int deltaLines) const
{
    CodeDocument::Position p (*this);
    p.setLineAndIndex (getLineNumber() + deltaLines, getIndexInLine());
    return p;
}

juce_wchar CodeDocument::Position::getCharacter() const
{
    if (auto* l = owner->lines [line])
        return l->line [getIndexInLine()];

    return 0;
}

String CodeDocument::Position::getLineText() const
{
    if (auto* l = owner->lines [line])
        return l->line;

    return {};
}

void CodeDocument::Position::setPositionMaintained (const bool isMaintained)
{
    if (isMaintained != positionMaintained)
    {
        positionMaintained = isMaintained;

        if (owner != nullptr)
        {
            if (isMaintained)
            {
                jassert (! owner->positionsToMaintain.contains (this));
                owner->positionsToMaintain.add (this);
            }
            else
            {
                // If this happens, you may have deleted the document while there are Position objects that are still using it...
                jassert (owner->positionsToMaintain.contains (this));
                owner->positionsToMaintain.removeFirstMatchingValue (this);
            }
        }
    }
}

//==============================================================================
CodeDocument::CodeDocument() : undoManager (std::numeric_limits<int>::max(), 10000)
{
}

CodeDocument::~CodeDocument()
{
}

String CodeDocument::getAllContent() const
{
    return getTextBetween (Position (*this, 0),
                           Position (*this, lines.size(), 0));
}

String CodeDocument::getTextBetween (const Position& start, const Position& end) const
{
    if (end.getPosition() <= start.getPosition())
        return {};

    auto startLine = start.getLineNumber();
    auto endLine = end.getLineNumber();

    if (startLine == endLine)
    {
        if (auto* line = lines [startLine])
            return line->line.substring (start.getIndexInLine(), end.getIndexInLine());

        return {};
    }

    MemoryOutputStream mo;
    mo.preallocate ((size_t) (end.getPosition() - start.getPosition() + 4));

    auto maxLine = jmin (lines.size() - 1, endLine);

    for (int i = jmax (0, startLine); i <= maxLine; ++i)
    {
        auto& line = *lines.getUnchecked(i);
        auto len = line.lineLength;

        if (i == startLine)
        {
            auto index = start.getIndexInLine();
            mo << line.line.substring (index, len);
        }
        else if (i == endLine)
        {
            len = end.getIndexInLine();
            mo << line.line.substring (0, len);
        }
        else
        {
            mo << line.line;
        }
    }

    return mo.toUTF8();
}

int CodeDocument::getNumCharacters() const noexcept
{
    if (auto* lastLine = lines.getLast())
        return lastLine->lineStartInFile + lastLine->lineLength;

    return 0;
}

String CodeDocument::getLine (const int lineIndex) const noexcept
{
    if (auto* line = lines[lineIndex])
        return line->line;

    return {};
}

int CodeDocument::getMaximumLineLength() noexcept
{
    if (maximumLineLength < 0)
    {
        maximumLineLength = 0;

        for (auto* l : lines)
            maximumLineLength = jmax (maximumLineLength, l->lineLength);
    }

    return maximumLineLength;
}

void CodeDocument::deleteSection (const Position& startPosition, const Position& endPosition)
{
    deleteSection (startPosition.getPosition(), endPosition.getPosition());
}

void CodeDocument::deleteSection (const int start, const int end)
{
    remove (start, end, true);
}

void CodeDocument::insertText (const Position& position, const String& text)
{
    insertText (position.getPosition(), text);
}

void CodeDocument::insertText (const int insertIndex, const String& text)
{
    insert (text, insertIndex, true);
}

void CodeDocument::replaceSection (const int start, const int end, const String& newText)
{
    insertText (end, newText);
    deleteSection (start, end);
}

void CodeDocument::applyChanges (const String& newContent)
{
    const String corrected (StringArray::fromLines (newContent)
                                .joinIntoString (newLineChars));

    TextDiff diff (getAllContent(), corrected);

    for (auto& c : diff.changes)
    {
        if (c.isDeletion())
            remove (c.start, c.start + c.length, true);
        else
            insert (c.insertedText, c.start, true);
    }
}

void CodeDocument::replaceAllContent (const String& newContent)
{
    remove (0, getNumCharacters(), true);
    insert (newContent, 0, true);
}

bool CodeDocument::loadFromStream (InputStream& stream)
{
    remove (0, getNumCharacters(), false);
    insert (stream.readEntireStreamAsString(), 0, false);
    setSavePoint();
    clearUndoHistory();
    return true;
}

bool CodeDocument::writeToStream (OutputStream& stream)
{
    for (auto* l : lines)
    {
        auto temp = l->line; // use a copy to avoid bloating the memory footprint of the stored string.
        const char* utf8 = temp.toUTF8();

        if (! stream.write (utf8, strlen (utf8)))
            return false;
    }

    return true;
}

void CodeDocument::setNewLineCharacters (const String& newChars) noexcept
{
    jassert (newChars == "\r\n" || newChars == "\n" || newChars == "\r");
    newLineChars = newChars;
}

void CodeDocument::newTransaction()
{
    undoManager.beginNewTransaction (String());
}

void CodeDocument::undo()
{
    newTransaction();
    undoManager.undo();
}

void CodeDocument::redo()
{
    undoManager.redo();
}

void CodeDocument::clearUndoHistory()
{
    undoManager.clearUndoHistory();
}

void CodeDocument::setSavePoint() noexcept
{
    indexOfSavedState = currentActionIndex;
}

bool CodeDocument::hasChangedSinceSavePoint() const noexcept
{
    return currentActionIndex != indexOfSavedState;
}

//==============================================================================
static inline int getCharacterType (juce_wchar character) noexcept
{
    return (CharacterFunctions::isLetterOrDigit (character) || character == '_')
                ? 2 : (CharacterFunctions::isWhitespace (character) ? 0 : 1);
}

CodeDocument::Position CodeDocument::findWordBreakAfter (const Position& position) const noexcept
{
    auto p = position;
    const int maxDistance = 256;
    int i = 0;

    while (i < maxDistance
            && CharacterFunctions::isWhitespace (p.getCharacter())
            && (i == 0 || (p.getCharacter() != '\n'
                            && p.getCharacter() != '\r')))
    {
        ++i;
        p.moveBy (1);
    }

    if (i == 0)
    {
        auto type = getCharacterType (p.getCharacter());

        while (i < maxDistance && type == getCharacterType (p.getCharacter()))
        {
            ++i;
            p.moveBy (1);
        }

        while (i < maxDistance
                && CharacterFunctions::isWhitespace (p.getCharacter())
                && (i == 0 || (p.getCharacter() != '\n'
                                && p.getCharacter() != '\r')))
        {
            ++i;
            p.moveBy (1);
        }
    }

    return p;
}

CodeDocument::Position CodeDocument::findWordBreakBefore (const Position& position) const noexcept
{
    auto p = position;
    const int maxDistance = 256;
    int i = 0;
    bool stoppedAtLineStart = false;

    while (i < maxDistance)
    {
        auto c = p.movedBy (-1).getCharacter();

        if (c == '\r' || c == '\n')
        {
            stoppedAtLineStart = true;

            if (i > 0)
                break;
        }

        if (! CharacterFunctions::isWhitespace (c))
            break;

        p.moveBy (-1);
        ++i;
    }

    if (i < maxDistance && ! stoppedAtLineStart)
    {
        auto type = getCharacterType (p.movedBy (-1).getCharacter());

        while (i < maxDistance && type == getCharacterType (p.movedBy (-1).getCharacter()))
        {
            p.moveBy (-1);
            ++i;
        }
    }

    return p;
}

void CodeDocument::findTokenContaining (const Position& pos, Position& start, Position& end) const noexcept
{
    auto isTokenCharacter = [] (juce_wchar c)  { return CharacterFunctions::isLetterOrDigit (c) || c == '.' || c == '_'; };

    end = pos;
    while (isTokenCharacter (end.getCharacter()))
        end.moveBy (1);

    start = end;
    while (start.getIndexInLine() > 0
            && isTokenCharacter (start.movedBy (-1).getCharacter()))
        start.moveBy (-1);
}

void CodeDocument::findLineContaining  (const Position& pos, Position& s, Position& e) const noexcept
{
    s.setLineAndIndex (pos.getLineNumber(), 0);
    e.setLineAndIndex (pos.getLineNumber() + 1, 0);
}

void CodeDocument::checkLastLineStatus()
{
    while (lines.size() > 0
            && lines.getLast()->lineLength == 0
            && (lines.size() == 1 || ! lines.getUnchecked (lines.size() - 2)->endsWithLineBreak()))
    {
        // remove any empty lines at the end if the preceding line doesn't end in a newline.
        lines.removeLast();
    }

    const CodeDocumentLine* const lastLine = lines.getLast();

    if (lastLine != nullptr && lastLine->endsWithLineBreak())
    {
        // check that there's an empty line at the end if the preceding one ends in a newline..
        lines.add (new CodeDocumentLine (StringRef(), StringRef(), 0, 0,
                                         lastLine->lineStartInFile + lastLine->lineLength));
    }
}

//==============================================================================
void CodeDocument::addListener    (CodeDocument::Listener* l) noexcept   { listeners.add (l); }
void CodeDocument::removeListener (CodeDocument::Listener* l) noexcept   { listeners.remove (l); }

//==============================================================================
struct CodeDocument::InsertAction   : public UndoableAction
{
    InsertAction (CodeDocument& doc, const String& t, const int pos) noexcept
        : owner (doc), text (t), insertPos (pos)
    {
    }

    bool perform() override
    {
        owner.currentActionIndex++;
        owner.insert (text, insertPos, false);
        return true;
    }

    bool undo() override
    {
        owner.currentActionIndex--;
        owner.remove (insertPos, insertPos + text.length(), false);
        return true;
    }

    int getSizeInUnits() override        { return text.length() + 32; }

    CodeDocument& owner;
    const String text;
    const int insertPos;

    JUCE_DECLARE_NON_COPYABLE (InsertAction)
};

void CodeDocument::insert (const String& text, const int insertPos, const bool undoable)
{
    if (text.isNotEmpty())
    {
        if (undoable)
        {
            undoManager.perform (new InsertAction (*this, text, insertPos));
        }
        else
        {
            Position pos (*this, insertPos);
            auto firstAffectedLine = pos.getLineNumber();

            auto* firstLine = lines[firstAffectedLine];
            auto textInsideOriginalLine = text;

            if (firstLine != nullptr)
            {
                auto index = pos.getIndexInLine();
                textInsideOriginalLine = firstLine->line.substring (0, index)
                                         + textInsideOriginalLine
                                         + firstLine->line.substring (index);
            }

            maximumLineLength = -1;
            Array<CodeDocumentLine*> newLines;
            CodeDocumentLine::createLines (newLines, textInsideOriginalLine);
            jassert (newLines.size() > 0);

            auto* newFirstLine = newLines.getUnchecked (0);
            newFirstLine->lineStartInFile = firstLine != nullptr ? firstLine->lineStartInFile : 0;
            lines.set (firstAffectedLine, newFirstLine);

            if (newLines.size() > 1)
                lines.insertArray (firstAffectedLine + 1, newLines.getRawDataPointer() + 1, newLines.size() - 1);

            int lineStart = newFirstLine->lineStartInFile;

            for (int i = firstAffectedLine; i < lines.size(); ++i)
            {
                auto& l = *lines.getUnchecked (i);
                l.lineStartInFile = lineStart;
                lineStart += l.lineLength;
            }

            checkLastLineStatus();
            auto newTextLength = text.length();

            for (auto* p : positionsToMaintain)
                if (p->getPosition() >= insertPos)
                    p->setPosition (p->getPosition() + newTextLength);

            listeners.call (&CodeDocument::Listener::codeDocumentTextInserted, text, insertPos);
        }
    }
}

//==============================================================================
struct CodeDocument::DeleteAction  : public UndoableAction
{
    DeleteAction (CodeDocument& doc, int start, int end) noexcept
        : owner (doc), startPos (start), endPos (end),
          removedText (doc.getTextBetween (CodeDocument::Position (doc, start),
                                           CodeDocument::Position (doc, end)))
    {
    }

    bool perform() override
    {
        owner.currentActionIndex++;
        owner.remove (startPos, endPos, false);
        return true;
    }

    bool undo() override
    {
        owner.currentActionIndex--;
        owner.insert (removedText, startPos, false);
        return true;
    }

    int getSizeInUnits() override    { return (endPos - startPos) + 32; }

    CodeDocument& owner;
    const int startPos, endPos;
    const String removedText;

    JUCE_DECLARE_NON_COPYABLE (DeleteAction)
};

void CodeDocument::remove (const int startPos, const int endPos, const bool undoable)
{
    if (endPos <= startPos)
        return;

    if (undoable)
    {
        undoManager.perform (new DeleteAction (*this, startPos, endPos));
    }
    else
    {
        Position startPosition (*this, startPos);
        Position endPosition (*this, endPos);

        maximumLineLength = -1;
        auto firstAffectedLine = startPosition.getLineNumber();
        auto endLine = endPosition.getLineNumber();
        auto& firstLine = *lines.getUnchecked (firstAffectedLine);

        if (firstAffectedLine == endLine)
        {
            firstLine.line = firstLine.line.substring (0, startPosition.getIndexInLine())
                           + firstLine.line.substring (endPosition.getIndexInLine());
            firstLine.updateLength();
        }
        else
        {
            auto& lastLine = *lines.getUnchecked (endLine);

            firstLine.line = firstLine.line.substring (0, startPosition.getIndexInLine())
                            + lastLine.line.substring (endPosition.getIndexInLine());
            firstLine.updateLength();

            int numLinesToRemove = endLine - firstAffectedLine;
            lines.removeRange (firstAffectedLine + 1, numLinesToRemove);
        }

        for (int i = firstAffectedLine + 1; i < lines.size(); ++i)
        {
            auto& l = *lines.getUnchecked (i);
            auto& previousLine = *lines.getUnchecked (i - 1);
            l.lineStartInFile = previousLine.lineStartInFile + previousLine.lineLength;
        }

        checkLastLineStatus();
        auto totalChars = getNumCharacters();

        for (auto* p : positionsToMaintain)
        {
            if (p->getPosition() > startPosition.getPosition())
                p->setPosition (jmax (startPos, p->getPosition() + startPos - endPos));

            if (p->getPosition() > totalChars)
                p->setPosition (totalChars);
        }

        listeners.call (&CodeDocument::Listener::codeDocumentTextDeleted, startPos, endPos);
    }
}

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

XmlTokeniser::XmlTokeniser() {}
XmlTokeniser::~XmlTokeniser() {}

CodeEditorComponent::ColourScheme XmlTokeniser::getDefaultColourScheme()
{
    struct Type
    {
        const char* name;
        uint32 colour;
    };

    const Type types[] =
    {
        { "Error",              0xffcc0000 },
        { "Comment",            0xff00aa00 },
        { "Keyword",            0xff0000cc },
        { "Operator",           0xff225500 },
        { "Identifier",         0xff000000 },
        { "String",             0xff990099 },
        { "Bracket",            0xff000055 },
        { "Punctuation",        0xff004400 },
        { "Preprocessor Text",  0xff660000 }
    };

    CodeEditorComponent::ColourScheme cs;

    for (auto& t : types)
        cs.set (t.name, Colour (t.colour));

    return cs;
}

template <typename Iterator>
static void skipToEndOfXmlDTD (Iterator& source) noexcept
{
    bool lastWasQuestionMark = false;

    for (;;)
    {
        auto c = source.nextChar();

        if (c == 0 || (c == '>' && lastWasQuestionMark))
            break;

        lastWasQuestionMark = (c == '?');
    }
}

template <typename Iterator>
static void skipToEndOfXmlComment (Iterator& source) noexcept
{
    juce_wchar last[2] = {};

    for (;;)
    {
        auto c = source.nextChar();

        if (c == 0 || (c == '>' && last[0] == '-' && last[1] == '-'))
            break;

        last[1] = last[0];
        last[0] = c;
    }
}

int XmlTokeniser::readNextToken (CodeDocument::Iterator& source)
{
    source.skipWhitespace();
    auto firstChar = source.peekNextChar();

    switch (firstChar)
    {
        case 0:  break;

        case '"':
        case '\'':
            CppTokeniserFunctions::skipQuotedString (source);
            return tokenType_string;

        case '<':
        {
            source.skip();
            source.skipWhitespace();
            auto nextChar = source.peekNextChar();

            if (nextChar == '?')
            {
                source.skip();
                skipToEndOfXmlDTD (source);
                return tokenType_preprocessor;
            }

            if (nextChar == '!')
            {
                source.skip();

                if (source.peekNextChar() == '-')
                {
                    source.skip();

                    if (source.peekNextChar() == '-')
                    {
                        skipToEndOfXmlComment (source);
                        return tokenType_comment;
                    }
                }
            }

            CppTokeniserFunctions::skipIfNextCharMatches (source, '/');
            CppTokeniserFunctions::parseIdentifier (source);
            source.skipWhitespace();
            CppTokeniserFunctions::skipIfNextCharMatches (source, '/');
            source.skipWhitespace();
            CppTokeniserFunctions::skipIfNextCharMatches (source, '>');
            return tokenType_keyword;
        }

        case '>':
            source.skip();
            return tokenType_keyword;

        case '/':
            source.skip();
            source.skipWhitespace();
            CppTokeniserFunctions::skipIfNextCharMatches (source, '>');
            return tokenType_keyword;

        case '=':
        case ':':
            source.skip();
            return tokenType_operator;

        default:
            if (CppTokeniserFunctions::isIdentifierStart (firstChar))
                CppTokeniserFunctions::parseIdentifier (source);

            source.skip();
            break;
    };

    return tokenType_identifier;
}

} // namespace juce