<commit_before>/*
 * Copyright (C) 2016 Muhammad Tayyab Akram
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

extern "C" {
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_BITMAP_H
#include FT_IMAGE_H
#include FT_OUTLINE_H
#include FT_SIZES_H
#include FT_STROKER_H
#include FT_TYPES_H
}

#include <jni.h>

#include "FreeType.h"
#include "JavaBridge.h"
#include "Miscellaneous.h"
#include "GlyphRasterizer.h"

using namespace Tehreer;

GlyphRasterizer::GlyphRasterizer(Typeface &typeface, FT_F26Dot6 pixelWidth, FT_F26Dot6 pixelHeight, FT_Matrix transform)
    : m_typeface(typeface)
    , m_size(nullptr)
    , m_transform(transform)
{
    m_typeface.lock();

    FT_Face baseFace = m_typeface.ftFace();
    FT_New_Size(baseFace, &m_size);
    FT_Activate_Size(m_size);
    FT_Set_Char_Size(baseFace, pixelWidth, pixelHeight, 0, 0);

    m_typeface.unlock();
}

GlyphRasterizer::~GlyphRasterizer()
{
    if (m_size) {
        /*
         * NOTE:
         *      FreeType face must be locked before releasing the size because it changes an
         *      internal list of the face containing all the sizes.
         */

        m_typeface.lock();
        FT_Done_Size(m_size);
        m_typeface.unlock();
    }
}

void GlyphRasterizer::unsafeActivate(FT_Face ftFace)
{
    FT_Activate_Size(m_size);
    FT_Set_Transform(ftFace, &m_transform, nullptr);
}

jobject GlyphRasterizer::unsafeCreateBitmap(const JavaBridge &bridge, const FT_Bitmap *bitmap)
{
    char pixelMode = bitmap->pixel_mode;
    jobject glyphBitmap = nullptr;
    size_t bitmapLength = 0;

    switch (pixelMode) {
    case FT_PIXEL_MODE_GRAY:
        bitmapLength = bitmap->width * bitmap->rows;
        if (bitmapLength > 0) {
            glyphBitmap = bridge.Bitmap_create(bitmap->width, bitmap->rows, JavaBridge::BitmapConfig::Alpha8);
            bridge.Bitmap_setPixels(glyphBitmap, bitmap->buffer, bitmapLength);
        }
    break;

    default:
        LOGW("Unsupported pixel mode of freetype bitmap");
        break;
    }

    return glyphBitmap;
}

void GlyphRasterizer::loadBitmap(const JavaBridge &bridge, jobject glyph)
{
    FT_UInt glyphID = static_cast<FT_UInt>(bridge.Glyph_getGlyphID(glyph));
    jobject glyphBitmap = nullptr;
    jint leftSideBearing = 0;
    jint topSideBearing = 0;

    m_typeface.lock();

    FT_Face baseFace = m_typeface.ftFace();
    unsafeActivate(baseFace);

    FT_Error error = FT_Load_Glyph(baseFace, glyphID, FT_LOAD_RENDER);
    if (error == FT_Err_Ok) {
        FT_GlyphSlot glyphSlot = baseFace->glyph;
        glyphBitmap = unsafeCreateBitmap(bridge, &glyphSlot->bitmap);

        if (glyphBitmap) {
            leftSideBearing = glyphSlot->bitmap_left;
            topSideBearing = glyphSlot->bitmap_top;
        }
    }

    m_typeface.unlock();

    bridge.Glyph_ownBitmap(glyph, glyphBitmap, leftSideBearing, topSideBearing);
}

void GlyphRasterizer::loadOutline(const JavaBridge &bridge, jobject glyph)
{
    FT_UInt glyphID = static_cast<FT_UInt>(bridge.Glyph_getGlyphID(glyph));

    m_typeface.lock();

    FT_Face baseFace = m_typeface.ftFace();
    unsafeActivate(baseFace);

    FT_Glyph outline = nullptr;
    FT_Error error = FT_Load_Glyph(baseFace, glyphID, FT_LOAD_NO_BITMAP);
    if (error == FT_Err_Ok) {
        FT_Get_Glyph(baseFace->glyph, &outline);
    }

    m_typeface.unlock();

    bridge.Glyph_ownOutline(glyph, outline ? reinterpret_cast<jlong>(outline) : 0);
}

void GlyphRasterizer::loadPath(const JavaBridge &bridge, jobject glyph)
{
    FT_UInt glyphID = static_cast<FT_UInt>(bridge.Glyph_getGlyphID(glyph));

    m_typeface.lock();

    FT_Face baseFace = m_typeface.ftFace();
    unsafeActivate(baseFace);

    jobject glyphPath = m_typeface.getGlyphPathNoLock(bridge, glyphID);

    m_typeface.unlock();

    bridge.Glyph_ownPath(glyph, glyphPath);
}

jobject GlyphRasterizer::strokeGlyph(const JavaBridge &bridge, jobject glyph, FT_Fixed lineRadius,
    FT_Stroker_LineCap lineCap, FT_Stroker_LineJoin lineJoin, FT_Fixed miterLimit)
{
    FT_UInt glyphID = static_cast<FT_UInt>(bridge.Glyph_getGlyphID(glyph));
    FT_Glyph baseGlyph = reinterpret_cast<FT_Glyph>(bridge.Glyph_getNativeOutline(glyph));

    if (baseGlyph) {
        m_typeface.lock();

        FT_Stroker stroker = m_typeface.ftStroker();
        FT_Stroker_Set(stroker, lineRadius, lineCap, lineJoin, miterLimit);
        FT_Error error = FT_Glyph_Stroke(&baseGlyph, stroker, 0);

        m_typeface.unlock();

        if (error == FT_Err_Ok) {
            FT_Glyph_To_Bitmap(&baseGlyph, FT_RENDER_MODE_NORMAL, nullptr, 1);

            FT_BitmapGlyph bitmapGlyph = reinterpret_cast<FT_BitmapGlyph>(baseGlyph);
            jobject strokeBitmap = nullptr;
            jint leftSideBearing = 0;
            jint topSideBearing = 0;

            strokeBitmap = unsafeCreateBitmap(bridge, &bitmapGlyph->bitmap);
            if (strokeBitmap) {
                leftSideBearing = bitmapGlyph->left;
                topSideBearing = bitmapGlyph->top;
            }

            jobject result = bridge.Glyph_construct(glyphID);
            bridge.Glyph_ownBitmap(result, strokeBitmap, leftSideBearing, topSideBearing);

            /* Dispose the stroked / bitmap glyph. */
            FT_Done_Glyph(baseGlyph);

            return result;
        }
    }

    return nullptr;
}

static jlong create(JNIEnv *env, jobject obj, jlong typefaceHandle, jint pixelWidth, jint pixelHeight,
    jint transformXX, jint transformXY, jint transformYX, jint transformYY)
{
    Typeface *typeface = reinterpret_cast<Typeface *>(typefaceHandle);
    FT_Matrix transform = {
        transformXX, transformXY,
        transformYX, transformYY
    };

    GlyphRasterizer *glyphRasterizer = new GlyphRasterizer(*typeface, pixelWidth, pixelHeight, transform);
    return reinterpret_cast<jlong>(glyphRasterizer);
}

static void dispose(JNIEnv *env, jobject obj, jlong rasterizerHandle)
{
    GlyphRasterizer *glyphRasterizer = reinterpret_cast<GlyphRasterizer *>(rasterizerHandle);
    delete glyphRasterizer;
}

static void loadBitmap(JNIEnv *env, jobject obj, jlong rasterizerHandle, jobject glyph)
{
    GlyphRasterizer *glyphRasterizer = reinterpret_cast<GlyphRasterizer *>(rasterizerHandle);
    glyphRasterizer->loadBitmap(JavaBridge(env), glyph);
}

static void loadOutline(JNIEnv *env, jobject obj, jlong rasterizerHandle, jobject glyph)
{
    GlyphRasterizer *glyphRasterizer = reinterpret_cast<GlyphRasterizer *>(rasterizerHandle);
    glyphRasterizer->loadOutline(JavaBridge(env), glyph);
}

static void loadPath(JNIEnv *env, jobject obj, jlong rasterizerHandle, jobject glyph)
{
    GlyphRasterizer *glyphRasterizer = reinterpret_cast<GlyphRasterizer *>(rasterizerHandle);
    glyphRasterizer->loadPath(JavaBridge(env), glyph);
}

static jobject strokeGlyph(JNIEnv *env, jobject obj, jlong rasterizerHandle, jobject glyph,
    jint lineRadius, jint lineCap, jint lineJoin, jint miterLimit)
{
    GlyphRasterizer *glyphRasterizer = reinterpret_cast<GlyphRasterizer *>(rasterizerHandle);
    FT_Fixed strokeRadius = static_cast<FT_Fixed >(lineRadius);
    FT_Stroker_LineCap strokeCap = static_cast<FT_Stroker_LineCap>(lineCap);
    FT_Stroker_LineJoin strokeJoin = static_cast<FT_Stroker_LineJoin>(lineJoin);
    FT_Fixed strokeMiter = static_cast<FT_Fixed>(miterLimit);

    return glyphRasterizer->strokeGlyph(JavaBridge(env), glyph, strokeRadius,
                                        strokeCap, strokeJoin, strokeMiter);
}

static JNINativeMethod JNI_METHODS[] = {
    { "nativeCreate", "(JIIIIII)J", (void *)create },
    { "nativeDispose", "(J)V", (void *)dispose },
    { "nativeLoadBitmap", "(JLcom/mta/tehreer/graphics/Glyph;)V", (void *)loadBitmap },
    { "nativeLoadOutline", "(JLcom/mta/tehreer/graphics/Glyph;)V", (void *)loadOutline },
    { "nativeLoadPath", "(JLcom/mta/tehreer/graphics/Glyph;)V", (void *)loadPath },
    { "nativeStrokeGlyph", "(JLcom/mta/tehreer/graphics/Glyph;IIII)Lcom/mta/tehreer/graphics/Glyph;", (void *)strokeGlyph },
};

jint register_com_mta_tehreer_graphics_GlyphRasterizer(JNIEnv *env)
{
    return JavaBridge::registerClass(env, "com/mta/tehreer/graphics/GlyphRasterizer", JNI_METHODS, sizeof(JNI_METHODS) / sizeof(JNI_METHODS[0]));
}
<commit_msg>[jni] Matched native method signatures in glyph rasterizer<commit_after>/*
 * Copyright (C) 2016-2018 Muhammad Tayyab Akram
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

extern "C" {
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_BITMAP_H
#include FT_IMAGE_H
#include FT_OUTLINE_H
#include FT_SIZES_H
#include FT_STROKER_H
#include FT_TYPES_H
}

#include <jni.h>

#include "FreeType.h"
#include "JavaBridge.h"
#include "Miscellaneous.h"
#include "GlyphRasterizer.h"

using namespace Tehreer;

GlyphRasterizer::GlyphRasterizer(Typeface &typeface, FT_F26Dot6 pixelWidth, FT_F26Dot6 pixelHeight, FT_Matrix transform)
    : m_typeface(typeface)
    , m_size(nullptr)
    , m_transform(transform)
{
    m_typeface.lock();

    FT_Face baseFace = m_typeface.ftFace();
    FT_New_Size(baseFace, &m_size);
    FT_Activate_Size(m_size);
    FT_Set_Char_Size(baseFace, pixelWidth, pixelHeight, 0, 0);

    m_typeface.unlock();
}

GlyphRasterizer::~GlyphRasterizer()
{
    if (m_size) {
        /*
         * NOTE:
         *      FreeType face must be locked before releasing the size because it changes an
         *      internal list of the face containing all the sizes.
         */

        m_typeface.lock();
        FT_Done_Size(m_size);
        m_typeface.unlock();
    }
}

void GlyphRasterizer::unsafeActivate(FT_Face ftFace)
{
    FT_Activate_Size(m_size);
    FT_Set_Transform(ftFace, &m_transform, nullptr);
}

jobject GlyphRasterizer::unsafeCreateBitmap(const JavaBridge &bridge, const FT_Bitmap *bitmap)
{
    char pixelMode = bitmap->pixel_mode;
    jobject glyphBitmap = nullptr;
    size_t bitmapLength = 0;

    switch (pixelMode) {
    case FT_PIXEL_MODE_GRAY:
        bitmapLength = bitmap->width * bitmap->rows;
        if (bitmapLength > 0) {
            glyphBitmap = bridge.Bitmap_create(bitmap->width, bitmap->rows, JavaBridge::BitmapConfig::Alpha8);
            bridge.Bitmap_setPixels(glyphBitmap, bitmap->buffer, bitmapLength);
        }
    break;

    default:
        LOGW("Unsupported pixel mode of freetype bitmap");
        break;
    }

    return glyphBitmap;
}

void GlyphRasterizer::loadBitmap(const JavaBridge &bridge, jobject glyph)
{
    FT_UInt glyphID = static_cast<FT_UInt>(bridge.Glyph_getGlyphID(glyph));
    jobject glyphBitmap = nullptr;
    jint leftSideBearing = 0;
    jint topSideBearing = 0;

    m_typeface.lock();

    FT_Face baseFace = m_typeface.ftFace();
    unsafeActivate(baseFace);

    FT_Error error = FT_Load_Glyph(baseFace, glyphID, FT_LOAD_RENDER);
    if (error == FT_Err_Ok) {
        FT_GlyphSlot glyphSlot = baseFace->glyph;
        glyphBitmap = unsafeCreateBitmap(bridge, &glyphSlot->bitmap);

        if (glyphBitmap) {
            leftSideBearing = glyphSlot->bitmap_left;
            topSideBearing = glyphSlot->bitmap_top;
        }
    }

    m_typeface.unlock();

    bridge.Glyph_ownBitmap(glyph, glyphBitmap, leftSideBearing, topSideBearing);
}

void GlyphRasterizer::loadOutline(const JavaBridge &bridge, jobject glyph)
{
    FT_UInt glyphID = static_cast<FT_UInt>(bridge.Glyph_getGlyphID(glyph));

    m_typeface.lock();

    FT_Face baseFace = m_typeface.ftFace();
    unsafeActivate(baseFace);

    FT_Glyph outline = nullptr;
    FT_Error error = FT_Load_Glyph(baseFace, glyphID, FT_LOAD_NO_BITMAP);
    if (error == FT_Err_Ok) {
        FT_Get_Glyph(baseFace->glyph, &outline);
    }

    m_typeface.unlock();

    bridge.Glyph_ownOutline(glyph, outline ? reinterpret_cast<jlong>(outline) : 0);
}

void GlyphRasterizer::loadPath(const JavaBridge &bridge, jobject glyph)
{
    FT_UInt glyphID = static_cast<FT_UInt>(bridge.Glyph_getGlyphID(glyph));

    m_typeface.lock();

    FT_Face baseFace = m_typeface.ftFace();
    unsafeActivate(baseFace);

    jobject glyphPath = m_typeface.getGlyphPathNoLock(bridge, glyphID);

    m_typeface.unlock();

    bridge.Glyph_ownPath(glyph, glyphPath);
}

jobject GlyphRasterizer::strokeGlyph(const JavaBridge &bridge, jobject glyph, FT_Fixed lineRadius,
    FT_Stroker_LineCap lineCap, FT_Stroker_LineJoin lineJoin, FT_Fixed miterLimit)
{
    FT_UInt glyphID = static_cast<FT_UInt>(bridge.Glyph_getGlyphID(glyph));
    FT_Glyph baseGlyph = reinterpret_cast<FT_Glyph>(bridge.Glyph_getNativeOutline(glyph));

    if (baseGlyph) {
        m_typeface.lock();

        FT_Stroker stroker = m_typeface.ftStroker();
        FT_Stroker_Set(stroker, lineRadius, lineCap, lineJoin, miterLimit);
        FT_Error error = FT_Glyph_Stroke(&baseGlyph, stroker, 0);

        m_typeface.unlock();

        if (error == FT_Err_Ok) {
            FT_Glyph_To_Bitmap(&baseGlyph, FT_RENDER_MODE_NORMAL, nullptr, 1);

            FT_BitmapGlyph bitmapGlyph = reinterpret_cast<FT_BitmapGlyph>(baseGlyph);
            jobject strokeBitmap = nullptr;
            jint leftSideBearing = 0;
            jint topSideBearing = 0;

            strokeBitmap = unsafeCreateBitmap(bridge, &bitmapGlyph->bitmap);
            if (strokeBitmap) {
                leftSideBearing = bitmapGlyph->left;
                topSideBearing = bitmapGlyph->top;
            }

            jobject result = bridge.Glyph_construct(glyphID);
            bridge.Glyph_ownBitmap(result, strokeBitmap, leftSideBearing, topSideBearing);

            /* Dispose the stroked / bitmap glyph. */
            FT_Done_Glyph(baseGlyph);

            return result;
        }
    }

    return nullptr;
}

static jlong create(JNIEnv *env, jobject obj, jlong typefaceHandle, jint pixelWidth, jint pixelHeight,
    jint transformXX, jint transformXY, jint transformYX, jint transformYY)
{
    Typeface *typeface = reinterpret_cast<Typeface *>(typefaceHandle);
    FT_Matrix transform = {
        transformXX, transformXY,
        transformYX, transformYY
    };

    GlyphRasterizer *glyphRasterizer = new GlyphRasterizer(*typeface, pixelWidth, pixelHeight, transform);
    return reinterpret_cast<jlong>(glyphRasterizer);
}

static void dispose(JNIEnv *env, jobject obj, jlong rasterizerHandle)
{
    GlyphRasterizer *glyphRasterizer = reinterpret_cast<GlyphRasterizer *>(rasterizerHandle);
    delete glyphRasterizer;
}

static void loadBitmap(JNIEnv *env, jobject obj, jlong rasterizerHandle, jobject glyph)
{
    GlyphRasterizer *glyphRasterizer = reinterpret_cast<GlyphRasterizer *>(rasterizerHandle);
    glyphRasterizer->loadBitmap(JavaBridge(env), glyph);
}

static void loadOutline(JNIEnv *env, jobject obj, jlong rasterizerHandle, jobject glyph)
{
    GlyphRasterizer *glyphRasterizer = reinterpret_cast<GlyphRasterizer *>(rasterizerHandle);
    glyphRasterizer->loadOutline(JavaBridge(env), glyph);
}

static void loadPath(JNIEnv *env, jobject obj, jlong rasterizerHandle, jobject glyph)
{
    GlyphRasterizer *glyphRasterizer = reinterpret_cast<GlyphRasterizer *>(rasterizerHandle);
    glyphRasterizer->loadPath(JavaBridge(env), glyph);
}

static jobject strokeGlyph(JNIEnv *env, jobject obj, jlong rasterizerHandle, jobject glyph,
    jint lineRadius, jint lineCap, jint lineJoin, jint miterLimit)
{
    GlyphRasterizer *glyphRasterizer = reinterpret_cast<GlyphRasterizer *>(rasterizerHandle);
    FT_Fixed strokeRadius = static_cast<FT_Fixed >(lineRadius);
    FT_Stroker_LineCap strokeCap = static_cast<FT_Stroker_LineCap>(lineCap);
    FT_Stroker_LineJoin strokeJoin = static_cast<FT_Stroker_LineJoin>(lineJoin);
    FT_Fixed strokeMiter = static_cast<FT_Fixed>(miterLimit);

    return glyphRasterizer->strokeGlyph(JavaBridge(env), glyph, strokeRadius,
                                        strokeCap, strokeJoin, strokeMiter);
}

static JNINativeMethod JNI_METHODS[] = {
    { "nCreate", "(JIIIIII)J", (void *)create },
    { "nDispose", "(J)V", (void *)dispose },
    { "nLoadBitmap", "(JLcom/mta/tehreer/graphics/Glyph;)V", (void *)loadBitmap },
    { "nLoadOutline", "(JLcom/mta/tehreer/graphics/Glyph;)V", (void *)loadOutline },
    { "nLoadPath", "(JLcom/mta/tehreer/graphics/Glyph;)V", (void *)loadPath },
    { "nStrokeGlyph", "(JLcom/mta/tehreer/graphics/Glyph;IIII)Lcom/mta/tehreer/graphics/Glyph;", (void *)strokeGlyph },
};

jint register_com_mta_tehreer_graphics_GlyphRasterizer(JNIEnv *env)
{
    return JavaBridge::registerClass(env, "com/mta/tehreer/graphics/GlyphRasterizer", JNI_METHODS, sizeof(JNI_METHODS) / sizeof(JNI_METHODS[0]));
}
<|endoftext|>


<commit_before>/*
 * SessionCodeSearch.cpp
 *
 * Copyright (C) 2009-11 by RStudio, Inc.
 *
 * This program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */

#include "SessionCodeSearch.hpp"

#include <iostream>
#include <vector>
#include <set>

#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/foreach.hpp>
#include <boost/algorithm/string/predicate.hpp>

#include <core/Error.hpp>
#include <core/Exec.hpp>
#include <core/FilePath.hpp>
#include <core/FileSerializer.hpp>
#include <core/SafeConvert.hpp>

#include <core/r_util/RSourceIndex.hpp>

#include <core/system/FileChangeEvent.hpp>
#include <core/system/FileMonitor.hpp>

#include <R_ext/rlocale.h>

#include <session/SessionUserSettings.hpp>
#include <session/SessionModuleContext.hpp>

#include <session/projects/SessionProjects.hpp>

#include "SessionSource.hpp"

// TODO: introduce a boolean to control indexing behavior

// TODO: use updateEntry with insert which recovers from duplicate
//       by removing the returned iterator

// TODO: faster search for removed items

using namespace core ;

namespace session {
namespace modules {
namespace code_search {

namespace {

class SourceFileIndex : boost::noncopyable
{
public:
   SourceFileIndex()
   {
   }

   virtual ~SourceFileIndex()
   {
   }

   // COPYING: prohibited

   template <typename ForwardIterator>
   void enqueFiles(ForwardIterator begin, ForwardIterator end)
   {
      // note whether we had anything in the queue before we start
      // (if we don't then we'll schedule indexing after the add)
      bool schedule = indexingQueue_.empty();

      // enque change events (but don't schedule indexing)
      using namespace core::system;
      for ( ; begin != end; ++begin)
         enqueFileChange(FileChangeEvent(FileChangeEvent::FileAdded, *begin), false);

      // schedule indexing if necessary
      if (schedule)
         scheduleIndexing();
   }

   void enqueFileChange(const core::system::FileChangeEvent& event, bool schedule)
   {
      // screen out files which aren't R source files
      FilePath filePath(event.fileInfo().absolutePath());
      if (filePath.isDirectory() || filePath.extensionLowerCase() != ".r")
         return;

      // note whether we need to schedule after we are done
      schedule = schedule && indexingQueue_.empty();

      // add to the queue
      indexingQueue_.push(event);

      // schedule indexing if necessary
      if (schedule)
         scheduleIndexing();
   }

   void searchSource(const std::string& term,
                     std::size_t maxResults,
                     bool prefixOnly,
                     const std::set<std::string>& excludeContexts,
                     std::vector<r_util::RSourceItem>* pItems)
   {
      BOOST_FOREACH(const Entry& entry, entries_)
      {
         // bail if this is an exluded context
         if (excludeContexts.find(entry.pIndex->context()) != excludeContexts.end())
            continue;

         // scan the next index
         entry.pIndex->search(term,
                              prefixOnly,
                              false,
                              std::back_inserter(*pItems));

         // return if we are past maxResults
         if (pItems->size() >= maxResults)
         {
            pItems->resize(maxResults);
            return;
         }
      }
   }

   void searchFiles(const std::string& term,
                    std::size_t maxResults,
                    bool prefixOnly,
                    json::Array* pNames,
                    json::Array* pPaths,
                    bool* pMoreAvailable)
   {
      // default to no more available
      *pMoreAvailable = false;

      // create wildcard pattern if the search has a '*'
      bool hasWildcard = term.find('*') != std::string::npos;
      boost::regex pattern;
      if (hasWildcard)
         pattern = regex_utils::wildcardPatternToRegex(term);

      // iterate over the files
      FilePath projectRoot = projects::projectContext().directory();
      BOOST_FOREACH(const Entry& entry, entries_)
      {
         // get the next file
         FilePath filePath(entry.fileInfo.absolutePath());

         // get name for comparison
         std::string name = filePath.filename();

         // compare for match (wildcard or standard)
         bool matches = false;
         if (hasWildcard)
         {
            matches = regex_utils::textMatches(name,
                                               pattern,
                                               prefixOnly,
                                               false);
         }
         else
         {
            if (prefixOnly)
               matches = boost::algorithm::istarts_with(name, term);
            else
               matches = boost::algorithm::icontains(name, term);
         }

         // add the file if we found a match
         if (matches)
         {
            // name and project relative directory
            pNames->push_back(filePath.filename());
            pPaths->push_back(filePath.relativePath(projectRoot));

            // return if we are past max results
            if (pNames->size() > maxResults)
            {
               *pMoreAvailable = true;
               pNames->resize(maxResults);
               pPaths->resize(maxResults);
               return;
            }
         }

      }
   }

private:

   // index entries we are managing
   struct Entry
   {
      Entry(const FileInfo& fileInfo,
            boost::shared_ptr<core::r_util::RSourceIndex> pIndex)
         : fileInfo(fileInfo), pIndex(pIndex)
      {
      }

      FileInfo fileInfo;

      boost::shared_ptr<core::r_util::RSourceIndex> pIndex;

      bool operator < (const Entry& other) const
      {
         return core::fileInfoPathLessThan(fileInfo, other.fileInfo);
      }
   };

private:

   void scheduleIndexing()
   {
      // schedule indexing -- perform up to 300ms of work immediately and then continue
      // in periodic 100ms chunks until we are completed. note also that we accept the
      // default behavior of only indexing during idle time so as not to interfere
      // with running computations
      module_context::scheduleIncrementalWork(
                        boost::posix_time::milliseconds(300),
                        boost::posix_time::milliseconds(100),
                        boost::bind(&SourceFileIndex::dequeAndIndex, this));
   }

   bool dequeAndIndex()
   {
      using namespace core::system;

      // remove the event from the queue
      FileChangeEvent event = indexingQueue_.front();
      indexingQueue_.pop();

      // process the change
      const FileInfo& fileInfo = event.fileInfo();
      switch(event.type())
      {
         case FileChangeEvent::FileAdded:
         {
            addIndexEntry(fileInfo);
            break;
         }

         case FileChangeEvent::FileModified:
         {
            removeIndexEntry(fileInfo);
            addIndexEntry(fileInfo);
            break;
         }

         case FileChangeEvent::FileRemoved:
         {
            removeIndexEntry(fileInfo);
            break;
         }

         case FileChangeEvent::None:
            break;
      }

      // return status
      return !indexingQueue_.empty();
   }

   void addIndexEntry(const FileInfo& fileInfo)
   {
      // read the file
      FilePath filePath(fileInfo.absolutePath());
      std::string code;
      Error error = module_context::readAndDecodeFile(
                              filePath,
                              projects::projectContext().defaultEncoding(),
                              true,
                              &code);
      if (error)
      {
         error.addProperty("src-file", filePath.absolutePath());
         LOG_ERROR(error);
         return;
      }

      // compute project relative directory (used for context)
      std::string context = filePath.relativePath(projects::projectContext().directory());

      // index the source
      boost::shared_ptr<r_util::RSourceIndex> pIndex(
                                          new r_util::RSourceIndex(context, code));

      // add the entry
      entries_.insert(Entry(fileInfo, pIndex));
   }

   void removeIndexEntry(const FileInfo& fileInfo)
   {
      for (std::set<Entry>::iterator it = entries_.begin(); it != entries_.end(); ++it)
      {
         if (core::fileInfoPathCompare(it->fileInfo, fileInfo) == 0)
         {
            entries_.erase(it);
            break;
         }
      }
   }

private:
   // index entries
   std::set<Entry> entries_;

   // indexing queue
   std::queue<core::system::FileChangeEvent> indexingQueue_;
};

// global source file index
SourceFileIndex s_projectIndex;

void searchSourceDatabase(const std::string& term,
                          std::size_t maxResults,
                          bool prefixOnly,
                          std::vector<r_util::RSourceItem>* pItems,
                          std::set<std::string>* pContextsSearched)
{

   // get all of the source indexes
   std::vector<boost::shared_ptr<r_util::RSourceIndex> > rIndexes =
                                             modules::source::rIndexes();

   BOOST_FOREACH(boost::shared_ptr<r_util::RSourceIndex>& pIndex, rIndexes)
   {
      // get file path
      FilePath docPath = module_context::resolveAliasedPath(pIndex->context());

      // bail if the file isn't in the project
      std::string projRelativePath =
            docPath.relativePath(projects::projectContext().directory());
      if (projRelativePath.empty())
         continue;

      // record that we searched this path
      pContextsSearched->insert(projRelativePath);

      // scan the source index
      pIndex->search(term,
                     projRelativePath,
                     prefixOnly,
                     false,
                     std::back_inserter(*pItems));

      // return if we are past maxResults
      if (pItems->size() >= maxResults)
      {
         pItems->resize(maxResults);
         return;
      }
   }
}

void searchSource(const std::string& term,
                  std::size_t maxResults,
                  bool prefixOnly,
                  std::vector<r_util::RSourceItem>* pItems,
                  bool* pMoreAvailable)
{
   // default to no more available
   *pMoreAvailable = false;

   // first search the source database
   std::set<std::string> srcDBContexts;
   searchSourceDatabase(term, maxResults, prefixOnly, pItems, &srcDBContexts);

   // we are done if we had >= maxResults
   if (pItems->size() > maxResults)
   {
      *pMoreAvailable = true;
      pItems->resize(maxResults);
      return;
   }

   // compute project max results based on existing results
   std::size_t maxProjResults = maxResults - pItems->size();

   // now search the project (excluding contexts already searched in the source db)
   std::vector<r_util::RSourceItem> projItems;
   s_projectIndex.searchSource(term,
                               maxProjResults,
                               prefixOnly,
                               srcDBContexts,
                               &projItems);

   // add project items to the list
   BOOST_FOREACH(const r_util::RSourceItem& sourceItem, projItems)
   {
      // add the item
      pItems->push_back(sourceItem);

      // bail if we've hit the max
      if (pItems->size() > maxResults)
      {
         *pMoreAvailable = true;
         pItems->resize(maxResults);
         break;
      }
   }
}

template <typename TValue, typename TFunc>
json::Array toJsonArray(
      const std::vector<r_util::RSourceItem> &items,
      TFunc memberFunc)
{
   json::Array col;
   std::transform(items.begin(),
                  items.end(),
                  std::back_inserter(col),
                  boost::bind(json::toJsonValue<TValue>,
                                 boost::bind(memberFunc, _1)));
   return col;
}

bool compareItems(const r_util::RSourceItem& i1, const r_util::RSourceItem& i2)
{
   return i1.name() < i2.name();
}

Error searchCode(const json::JsonRpcRequest& request,
                 json::JsonRpcResponse* pResponse)
{
   // get params
   std::string term;
   int maxResultsInt;
   Error error = json::readParams(request.params, &term, &maxResultsInt);
   if (error)
      return error;
   std::size_t maxResults = safe_convert::numberTo<std::size_t>(maxResultsInt,
                                                                20);

   // object to return
   json::Object result;

   // search files
   json::Array names;
   json::Array paths;
   bool moreFilesAvailable = false;
   s_projectIndex.searchFiles(term,
                              maxResults,
                              true,
                              &names,
                              &paths,
                              &moreFilesAvailable);
   json::Object files;
   files["filename"] = names;
   files["path"] = paths;
   result["file_items"] = files;

   // search source (sort results by name)
   std::vector<r_util::RSourceItem> items;
   bool moreSourceItemsAvailable = false;
   searchSource(term, maxResults, true, &items, &moreSourceItemsAvailable);
   std::sort(items.begin(), items.end(), compareItems);

   // see if we need to do src truncation
   bool truncated = false;
   if ( (names.size() + items.size()) > maxResults )
   {
      // truncate source items
      std::size_t srcItems = maxResults - names.size();
      items.resize(srcItems);
      truncated = true;
   }

   // return rpc array list (wire efficiency)
   json::Object src;
   src["type"] = toJsonArray<int>(items, &r_util::RSourceItem::type);
   src["name"] = toJsonArray<std::string>(items, &r_util::RSourceItem::name);
   src["context"] = toJsonArray<std::string>(items, &r_util::RSourceItem::context);
   src["line"] = toJsonArray<int>(items, &r_util::RSourceItem::line);
   src["column"] = toJsonArray<int>(items, &r_util::RSourceItem::column);
   result["source_items"] = src;

   // set more available bit
   result["more_available"] =
         moreFilesAvailable || moreSourceItemsAvailable || truncated;

   pResponse->setResult(result);

   return Success();
}

void onFileMonitorRegistered(const tree<core::FileInfo>& files)
{
   s_projectIndex.enqueFiles(files.begin_leaf(), files.end_leaf());
}

void onFileMonitorRegistrationError(const core::Error& error)
{
   // TODO: disable code searching
}

void onFileMonitorUnregistered()
{
   // TODO: disable code searching
}

void onFilesChanged(const std::vector<core::system::FileChangeEvent>& events)
{
   // index all of the changes
   std::for_each(
         events.begin(),
         events.end(),
         boost::bind(&SourceFileIndex::enqueFileChange, &s_projectIndex, _1, true));
}

} // anonymous namespace

bool enabled()
{
   return projects::projectContext().hasProject();
}

Error initialize()
{
   // subscribe to project context file monitoring state changes
   core::system::file_monitor::Callbacks cb;
   cb.onRegistered = boost::bind(onFileMonitorRegistered, _2);
   cb.onRegistrationError = onFileMonitorRegistrationError;
   cb.onUnregistered = boost::bind(onFileMonitorUnregistered);
   cb.onFilesChanged = onFilesChanged;
   projects::projectContext().registerFileMonitorCallbacks(cb);

   using boost::bind;
   using namespace module_context;
   ExecBlock initBlock ;
   initBlock.addFunctions()
      (bind(registerRpcMethod, "search_code", searchCode));
   ;

   return initBlock.execute();
}

} // namespace agreement
} // namespace modules
} // namespace session
<commit_msg>more code search todos<commit_after>/*
 * SessionCodeSearch.cpp
 *
 * Copyright (C) 2009-11 by RStudio, Inc.
 *
 * This program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */

#include "SessionCodeSearch.hpp"

#include <iostream>
#include <vector>
#include <set>

#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/foreach.hpp>
#include <boost/algorithm/string/predicate.hpp>

#include <core/Error.hpp>
#include <core/Exec.hpp>
#include <core/FilePath.hpp>
#include <core/FileSerializer.hpp>
#include <core/SafeConvert.hpp>

#include <core/r_util/RSourceIndex.hpp>

#include <core/system/FileChangeEvent.hpp>
#include <core/system/FileMonitor.hpp>

#include <R_ext/rlocale.h>

#include <session/SessionUserSettings.hpp>
#include <session/SessionModuleContext.hpp>

#include <session/projects/SessionProjects.hpp>

#include "SessionSource.hpp"

// TODO: introduce a boolean to control indexing behavior

// TODO: use updateEntry with insert which recovers from duplicate
//       by removing the returned iterator

// TODO: faster search for removed items

// TODO: some kind of scanning progress ui

// TODO: enable/disable of code searching / file-mon in project prefs

// TODO: some type of integration with editor (detect change, etc.)

using namespace core ;

namespace session {
namespace modules {
namespace code_search {

namespace {

class SourceFileIndex : boost::noncopyable
{
public:
   SourceFileIndex()
   {
   }

   virtual ~SourceFileIndex()
   {
   }

   // COPYING: prohibited

   template <typename ForwardIterator>
   void enqueFiles(ForwardIterator begin, ForwardIterator end)
   {
      // note whether we had anything in the queue before we start
      // (if we don't then we'll schedule indexing after the add)
      bool schedule = indexingQueue_.empty();

      // enque change events (but don't schedule indexing)
      using namespace core::system;
      for ( ; begin != end; ++begin)
         enqueFileChange(FileChangeEvent(FileChangeEvent::FileAdded, *begin), false);

      // schedule indexing if necessary
      if (schedule)
         scheduleIndexing();
   }

   void enqueFileChange(const core::system::FileChangeEvent& event, bool schedule)
   {
      // screen out files which aren't R source files
      FilePath filePath(event.fileInfo().absolutePath());
      if (filePath.isDirectory() || filePath.extensionLowerCase() != ".r")
         return;

      // note whether we need to schedule after we are done
      schedule = schedule && indexingQueue_.empty();

      // add to the queue
      indexingQueue_.push(event);

      // schedule indexing if necessary
      if (schedule)
         scheduleIndexing();
   }

   void searchSource(const std::string& term,
                     std::size_t maxResults,
                     bool prefixOnly,
                     const std::set<std::string>& excludeContexts,
                     std::vector<r_util::RSourceItem>* pItems)
   {
      BOOST_FOREACH(const Entry& entry, entries_)
      {
         // bail if this is an exluded context
         if (excludeContexts.find(entry.pIndex->context()) != excludeContexts.end())
            continue;

         // scan the next index
         entry.pIndex->search(term,
                              prefixOnly,
                              false,
                              std::back_inserter(*pItems));

         // return if we are past maxResults
         if (pItems->size() >= maxResults)
         {
            pItems->resize(maxResults);
            return;
         }
      }
   }

   void searchFiles(const std::string& term,
                    std::size_t maxResults,
                    bool prefixOnly,
                    json::Array* pNames,
                    json::Array* pPaths,
                    bool* pMoreAvailable)
   {
      // default to no more available
      *pMoreAvailable = false;

      // create wildcard pattern if the search has a '*'
      bool hasWildcard = term.find('*') != std::string::npos;
      boost::regex pattern;
      if (hasWildcard)
         pattern = regex_utils::wildcardPatternToRegex(term);

      // iterate over the files
      FilePath projectRoot = projects::projectContext().directory();
      BOOST_FOREACH(const Entry& entry, entries_)
      {
         // get the next file
         FilePath filePath(entry.fileInfo.absolutePath());

         // get name for comparison
         std::string name = filePath.filename();

         // compare for match (wildcard or standard)
         bool matches = false;
         if (hasWildcard)
         {
            matches = regex_utils::textMatches(name,
                                               pattern,
                                               prefixOnly,
                                               false);
         }
         else
         {
            if (prefixOnly)
               matches = boost::algorithm::istarts_with(name, term);
            else
               matches = boost::algorithm::icontains(name, term);
         }

         // add the file if we found a match
         if (matches)
         {
            // name and project relative directory
            pNames->push_back(filePath.filename());
            pPaths->push_back(filePath.relativePath(projectRoot));

            // return if we are past max results
            if (pNames->size() > maxResults)
            {
               *pMoreAvailable = true;
               pNames->resize(maxResults);
               pPaths->resize(maxResults);
               return;
            }
         }

      }
   }

private:

   // index entries we are managing
   struct Entry
   {
      Entry(const FileInfo& fileInfo,
            boost::shared_ptr<core::r_util::RSourceIndex> pIndex)
         : fileInfo(fileInfo), pIndex(pIndex)
      {
      }

      FileInfo fileInfo;

      boost::shared_ptr<core::r_util::RSourceIndex> pIndex;

      bool operator < (const Entry& other) const
      {
         return core::fileInfoPathLessThan(fileInfo, other.fileInfo);
      }
   };

private:

   void scheduleIndexing()
   {
      // schedule indexing -- perform up to 300ms of work immediately and then continue
      // in periodic 100ms chunks until we are completed. note also that we accept the
      // default behavior of only indexing during idle time so as not to interfere
      // with running computations
      module_context::scheduleIncrementalWork(
                        boost::posix_time::milliseconds(300),
                        boost::posix_time::milliseconds(100),
                        boost::bind(&SourceFileIndex::dequeAndIndex, this));
   }

   bool dequeAndIndex()
   {
      using namespace core::system;

      // remove the event from the queue
      FileChangeEvent event = indexingQueue_.front();
      indexingQueue_.pop();

      // process the change
      const FileInfo& fileInfo = event.fileInfo();
      switch(event.type())
      {
         case FileChangeEvent::FileAdded:
         {
            addIndexEntry(fileInfo);
            break;
         }

         case FileChangeEvent::FileModified:
         {
            removeIndexEntry(fileInfo);
            addIndexEntry(fileInfo);
            break;
         }

         case FileChangeEvent::FileRemoved:
         {
            removeIndexEntry(fileInfo);
            break;
         }

         case FileChangeEvent::None:
            break;
      }

      // return status
      return !indexingQueue_.empty();
   }

   void addIndexEntry(const FileInfo& fileInfo)
   {
      // read the file
      FilePath filePath(fileInfo.absolutePath());
      std::string code;
      Error error = module_context::readAndDecodeFile(
                              filePath,
                              projects::projectContext().defaultEncoding(),
                              true,
                              &code);
      if (error)
      {
         error.addProperty("src-file", filePath.absolutePath());
         LOG_ERROR(error);
         return;
      }

      // compute project relative directory (used for context)
      std::string context = filePath.relativePath(projects::projectContext().directory());

      // index the source
      boost::shared_ptr<r_util::RSourceIndex> pIndex(
                                          new r_util::RSourceIndex(context, code));

      // add the entry
      entries_.insert(Entry(fileInfo, pIndex));
   }

   void removeIndexEntry(const FileInfo& fileInfo)
   {
      for (std::set<Entry>::iterator it = entries_.begin(); it != entries_.end(); ++it)
      {
         if (core::fileInfoPathCompare(it->fileInfo, fileInfo) == 0)
         {
            entries_.erase(it);
            break;
         }
      }
   }

private:
   // index entries
   std::set<Entry> entries_;

   // indexing queue
   std::queue<core::system::FileChangeEvent> indexingQueue_;
};

// global source file index
SourceFileIndex s_projectIndex;

void searchSourceDatabase(const std::string& term,
                          std::size_t maxResults,
                          bool prefixOnly,
                          std::vector<r_util::RSourceItem>* pItems,
                          std::set<std::string>* pContextsSearched)
{

   // get all of the source indexes
   std::vector<boost::shared_ptr<r_util::RSourceIndex> > rIndexes =
                                             modules::source::rIndexes();

   BOOST_FOREACH(boost::shared_ptr<r_util::RSourceIndex>& pIndex, rIndexes)
   {
      // get file path
      FilePath docPath = module_context::resolveAliasedPath(pIndex->context());

      // bail if the file isn't in the project
      std::string projRelativePath =
            docPath.relativePath(projects::projectContext().directory());
      if (projRelativePath.empty())
         continue;

      // record that we searched this path
      pContextsSearched->insert(projRelativePath);

      // scan the source index
      pIndex->search(term,
                     projRelativePath,
                     prefixOnly,
                     false,
                     std::back_inserter(*pItems));

      // return if we are past maxResults
      if (pItems->size() >= maxResults)
      {
         pItems->resize(maxResults);
         return;
      }
   }
}

void searchSource(const std::string& term,
                  std::size_t maxResults,
                  bool prefixOnly,
                  std::vector<r_util::RSourceItem>* pItems,
                  bool* pMoreAvailable)
{
   // default to no more available
   *pMoreAvailable = false;

   // first search the source database
   std::set<std::string> srcDBContexts;
   searchSourceDatabase(term, maxResults, prefixOnly, pItems, &srcDBContexts);

   // we are done if we had >= maxResults
   if (pItems->size() > maxResults)
   {
      *pMoreAvailable = true;
      pItems->resize(maxResults);
      return;
   }

   // compute project max results based on existing results
   std::size_t maxProjResults = maxResults - pItems->size();

   // now search the project (excluding contexts already searched in the source db)
   std::vector<r_util::RSourceItem> projItems;
   s_projectIndex.searchSource(term,
                               maxProjResults,
                               prefixOnly,
                               srcDBContexts,
                               &projItems);

   // add project items to the list
   BOOST_FOREACH(const r_util::RSourceItem& sourceItem, projItems)
   {
      // add the item
      pItems->push_back(sourceItem);

      // bail if we've hit the max
      if (pItems->size() > maxResults)
      {
         *pMoreAvailable = true;
         pItems->resize(maxResults);
         break;
      }
   }
}

template <typename TValue, typename TFunc>
json::Array toJsonArray(
      const std::vector<r_util::RSourceItem> &items,
      TFunc memberFunc)
{
   json::Array col;
   std::transform(items.begin(),
                  items.end(),
                  std::back_inserter(col),
                  boost::bind(json::toJsonValue<TValue>,
                                 boost::bind(memberFunc, _1)));
   return col;
}

bool compareItems(const r_util::RSourceItem& i1, const r_util::RSourceItem& i2)
{
   return i1.name() < i2.name();
}

Error searchCode(const json::JsonRpcRequest& request,
                 json::JsonRpcResponse* pResponse)
{
   // get params
   std::string term;
   int maxResultsInt;
   Error error = json::readParams(request.params, &term, &maxResultsInt);
   if (error)
      return error;
   std::size_t maxResults = safe_convert::numberTo<std::size_t>(maxResultsInt,
                                                                20);

   // object to return
   json::Object result;

   // search files
   json::Array names;
   json::Array paths;
   bool moreFilesAvailable = false;
   s_projectIndex.searchFiles(term,
                              maxResults,
                              true,
                              &names,
                              &paths,
                              &moreFilesAvailable);
   json::Object files;
   files["filename"] = names;
   files["path"] = paths;
   result["file_items"] = files;

   // search source (sort results by name)
   std::vector<r_util::RSourceItem> items;
   bool moreSourceItemsAvailable = false;
   searchSource(term, maxResults, true, &items, &moreSourceItemsAvailable);
   std::sort(items.begin(), items.end(), compareItems);

   // see if we need to do src truncation
   bool truncated = false;
   if ( (names.size() + items.size()) > maxResults )
   {
      // truncate source items
      std::size_t srcItems = maxResults - names.size();
      items.resize(srcItems);
      truncated = true;
   }

   // return rpc array list (wire efficiency)
   json::Object src;
   src["type"] = toJsonArray<int>(items, &r_util::RSourceItem::type);
   src["name"] = toJsonArray<std::string>(items, &r_util::RSourceItem::name);
   src["context"] = toJsonArray<std::string>(items, &r_util::RSourceItem::context);
   src["line"] = toJsonArray<int>(items, &r_util::RSourceItem::line);
   src["column"] = toJsonArray<int>(items, &r_util::RSourceItem::column);
   result["source_items"] = src;

   // set more available bit
   result["more_available"] =
         moreFilesAvailable || moreSourceItemsAvailable || truncated;

   pResponse->setResult(result);

   return Success();
}

void onFileMonitorRegistered(const tree<core::FileInfo>& files)
{
   s_projectIndex.enqueFiles(files.begin_leaf(), files.end_leaf());
}

void onFileMonitorRegistrationError(const core::Error& error)
{
   // TODO: disable code searching
}

void onFileMonitorUnregistered()
{
   // TODO: disable code searching
}

void onFilesChanged(const std::vector<core::system::FileChangeEvent>& events)
{
   // index all of the changes
   std::for_each(
         events.begin(),
         events.end(),
         boost::bind(&SourceFileIndex::enqueFileChange, &s_projectIndex, _1, true));
}

} // anonymous namespace

bool enabled()
{
   return projects::projectContext().hasProject();
}

Error initialize()
{
   // subscribe to project context file monitoring state changes
   core::system::file_monitor::Callbacks cb;
   cb.onRegistered = boost::bind(onFileMonitorRegistered, _2);
   cb.onRegistrationError = onFileMonitorRegistrationError;
   cb.onUnregistered = boost::bind(onFileMonitorUnregistered);
   cb.onFilesChanged = onFilesChanged;
   projects::projectContext().registerFileMonitorCallbacks(cb);

   using boost::bind;
   using namespace module_context;
   ExecBlock initBlock ;
   initBlock.addFunctions()
      (bind(registerRpcMethod, "search_code", searchCode));
   ;

   return initBlock.execute();
}

} // namespace agreement
} // namespace modules
} // namespace session
<|endoftext|>


<commit_before>/*-------------------------------------------------------------------------
 * drawElements Quality Program Tester Core
 * ----------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief X11 utilities.
 *//*--------------------------------------------------------------------*/

#include "tcuX11.hpp"
#include "gluRenderConfig.hpp"
#include "deMemory.h"

#include <X11/Xutil.h>

namespace tcu
{
namespace x11
{

enum
{
	DEFAULT_WINDOW_WIDTH	= 400,
	DEFAULT_WINDOW_HEIGHT	= 300
};

EventState::EventState (void)
	: m_quit(false)
{
}

EventState::~EventState (void)
{
}

void EventState::setQuitFlag (bool quit)
{
	de::ScopedLock lock(m_mutex);
	m_quit = quit;
}

bool EventState::getQuitFlag (void)
{
	de::ScopedLock lock(m_mutex);
	return m_quit;
}

Display::Display (EventState& eventState, const char* name)
	: m_eventState	(eventState)
	, m_display		(DE_NULL)
	, m_deleteAtom	(DE_NULL)
{
	m_display = XOpenDisplay((char*)name); // Won't modify argument string.
	if (!m_display)
		throw ResourceError("Failed to open display", name, __FILE__, __LINE__);

	m_deleteAtom	= XInternAtom(m_display, "WM_DELETE_WINDOW", False);
}

Display::~Display (void)
{
	XCloseDisplay(m_display);
}

void Display::processEvents (void)
{
	XEvent	event;

	while (XPending(m_display))
	{
		XNextEvent(m_display, &event);

		// \todo [2010-10-27 pyry] Handle ConfigureNotify?
		if (event.type == ClientMessage && (unsigned)event.xclient.data.l[0] == m_deleteAtom)
			m_eventState.setQuitFlag(true);
	}
}

bool Display::getVisualInfo (VisualID visualID, XVisualInfo& dst)
{
	XVisualInfo		query;
	query.visualid = visualID;
	int				numVisuals	= 0;
	XVisualInfo*	response	= XGetVisualInfo(m_display, VisualIDMask, &query, &numVisuals);
	bool			succ		= false;

	if (response != DE_NULL)
	{
		if (numVisuals > 0) // should be 1, but you never know...
		{
			dst = response[0];
			succ = true;
		}
		XFree(response);
	}

	return succ;
}

::Visual* Display::getVisual (VisualID visualID)
{
	XVisualInfo		info;

	if (getVisualInfo(visualID, info))
		return info.visual;

	return DE_NULL;
}

Window::Window (Display& display, int width, int height, ::Visual* visual)
	: m_display		(display)
	, m_colormap	(None)
	, m_window		(None)
	, m_visible		(false)
{
	XSetWindowAttributes	swa;
	::Display* const		dpy					= m_display.getXDisplay();
	::Window				root				= DefaultRootWindow(dpy);
	unsigned long			mask				= CWBorderPixel | CWEventMask;

	// If redirect is enabled, window size can't be guaranteed and it is up to
	// the window manager to decide whether to honor sizing requests. However,
	// overriding that causes window to appear as an overlay, which causes
	// other issues, so this is disabled by default.
	const bool				overrideRedirect	= false;

	if (overrideRedirect)
	{
		mask |= CWOverrideRedirect;
		swa.override_redirect = true;
	}

	if (visual == DE_NULL)
		visual = CopyFromParent;
	else
	{
		XVisualInfo	info	= XVisualInfo();
		bool		succ	= display.getVisualInfo(XVisualIDFromVisual(visual), info);

		TCU_CHECK_INTERNAL(succ);

		root				= RootWindow(dpy, info.screen);
		m_colormap			= XCreateColormap(dpy, root, visual, AllocNone);
		swa.colormap		= m_colormap;
		mask |= CWColormap;
	}

	swa.border_pixel	= 0;
	swa.event_mask		= ExposureMask|KeyPressMask|KeyReleaseMask|StructureNotifyMask;

	if (width == glu::RenderConfig::DONT_CARE)
		width = DEFAULT_WINDOW_WIDTH;
	if (height == glu::RenderConfig::DONT_CARE)
		height = DEFAULT_WINDOW_HEIGHT;

	m_window = XCreateWindow(dpy, root, 0, 0, width, height, 0,
							 CopyFromParent, InputOutput, visual, mask, &swa);
	TCU_CHECK(m_window);

	Atom deleteAtom = m_display.getDeleteAtom();
	XSetWMProtocols(dpy, m_window, &deleteAtom, 1);
}

void Window::setVisibility (bool visible)
{
	::Display*	dpy			= m_display.getXDisplay();
	int			eventType	= None;
	XEvent		event;

	if (visible == m_visible)
		return;

	if (visible)
	{
		XMapWindow(dpy, m_window);
		eventType = MapNotify;
	}
	else
	{
		XUnmapWindow(dpy, m_window);
		eventType = UnmapNotify;
	}

	// We are only interested about exposure/structure notify events, not user input
	XSelectInput(dpy, m_window, ExposureMask | StructureNotifyMask);

	do
	{
		XNextEvent(dpy, &event);
	} while (event.type != eventType);

	m_visible = visible;
}

void Window::getDimensions (int* width, int* height) const
{
	int x, y;
	::Window root;
	unsigned width_, height_, borderWidth, depth;

	XGetGeometry(m_display.getXDisplay(), m_window, &root, &x, &y, &width_, &height_, &borderWidth, &depth);
	if (width != DE_NULL)
		*width = static_cast<int>(width_);
	if (height != DE_NULL)
		*height = static_cast<int>(height_);
}

void Window::setDimensions (int width, int height)
{
	const unsigned int	mask = CWWidth | CWHeight;
	XWindowChanges		changes;
	changes.width		= width;
	changes.height		= height;

	XConfigureWindow(m_display.getXDisplay(), m_window, mask, &changes);
}

void Window::processEvents (void)
{
	// A bit of a hack, since we don't really handle all the events.
	m_display.processEvents();
}

Window::~Window (void)
{
	XDestroyWindow(m_display.getXDisplay(), m_window);
	if (m_colormap != None)
		XFreeColormap(m_display.getXDisplay(), m_colormap);
}

} // x11
} // tcu
<commit_msg>x11: Fix deadlock am: 5e863331b2 am: f49e8bfc0e am: 1eb4f43dc4 am: c1c16c73e6<commit_after>/*-------------------------------------------------------------------------
 * drawElements Quality Program Tester Core
 * ----------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief X11 utilities.
 *//*--------------------------------------------------------------------*/

#include "tcuX11.hpp"
#include "gluRenderConfig.hpp"
#include "deMemory.h"

#include <X11/Xutil.h>

namespace tcu
{
namespace x11
{

enum
{
	DEFAULT_WINDOW_WIDTH	= 400,
	DEFAULT_WINDOW_HEIGHT	= 300
};

EventState::EventState (void)
	: m_quit(false)
{
}

EventState::~EventState (void)
{
}

void EventState::setQuitFlag (bool quit)
{
	de::ScopedLock lock(m_mutex);
	m_quit = quit;
}

bool EventState::getQuitFlag (void)
{
	de::ScopedLock lock(m_mutex);
	return m_quit;
}

Display::Display (EventState& eventState, const char* name)
	: m_eventState	(eventState)
	, m_display		(DE_NULL)
	, m_deleteAtom	(DE_NULL)
{
	m_display = XOpenDisplay((char*)name); // Won't modify argument string.
	if (!m_display)
		throw ResourceError("Failed to open display", name, __FILE__, __LINE__);

	m_deleteAtom	= XInternAtom(m_display, "WM_DELETE_WINDOW", False);
}

Display::~Display (void)
{
	XCloseDisplay(m_display);
}

void Display::processEvents (void)
{
	XEvent	event;

	while (XPending(m_display))
	{
		XNextEvent(m_display, &event);

		// \todo [2010-10-27 pyry] Handle ConfigureNotify?
		if (event.type == ClientMessage && (unsigned)event.xclient.data.l[0] == m_deleteAtom)
			m_eventState.setQuitFlag(true);
	}
}

bool Display::getVisualInfo (VisualID visualID, XVisualInfo& dst)
{
	XVisualInfo		query;
	query.visualid = visualID;
	int				numVisuals	= 0;
	XVisualInfo*	response	= XGetVisualInfo(m_display, VisualIDMask, &query, &numVisuals);
	bool			succ		= false;

	if (response != DE_NULL)
	{
		if (numVisuals > 0) // should be 1, but you never know...
		{
			dst = response[0];
			succ = true;
		}
		XFree(response);
	}

	return succ;
}

::Visual* Display::getVisual (VisualID visualID)
{
	XVisualInfo		info;

	if (getVisualInfo(visualID, info))
		return info.visual;

	return DE_NULL;
}

Window::Window (Display& display, int width, int height, ::Visual* visual)
	: m_display		(display)
	, m_colormap	(None)
	, m_window		(None)
	, m_visible		(false)
{
	XSetWindowAttributes	swa;
	::Display* const		dpy					= m_display.getXDisplay();
	::Window				root				= DefaultRootWindow(dpy);
	unsigned long			mask				= CWBorderPixel | CWEventMask;

	// If redirect is enabled, window size can't be guaranteed and it is up to
	// the window manager to decide whether to honor sizing requests. However,
	// overriding that causes window to appear as an overlay, which causes
	// other issues, so this is disabled by default.
	const bool				overrideRedirect	= false;

	if (overrideRedirect)
	{
		mask |= CWOverrideRedirect;
		swa.override_redirect = true;
	}

	if (visual == DE_NULL)
		visual = CopyFromParent;
	else
	{
		XVisualInfo	info	= XVisualInfo();
		bool		succ	= display.getVisualInfo(XVisualIDFromVisual(visual), info);

		TCU_CHECK_INTERNAL(succ);

		root				= RootWindow(dpy, info.screen);
		m_colormap			= XCreateColormap(dpy, root, visual, AllocNone);
		swa.colormap		= m_colormap;
		mask |= CWColormap;
	}

	swa.border_pixel	= 0;
	swa.event_mask		= ExposureMask|KeyPressMask|KeyReleaseMask|StructureNotifyMask;

	if (width == glu::RenderConfig::DONT_CARE)
		width = DEFAULT_WINDOW_WIDTH;
	if (height == glu::RenderConfig::DONT_CARE)
		height = DEFAULT_WINDOW_HEIGHT;

	m_window = XCreateWindow(dpy, root, 0, 0, width, height, 0,
							 CopyFromParent, InputOutput, visual, mask, &swa);
	TCU_CHECK(m_window);

	Atom deleteAtom = m_display.getDeleteAtom();
	XSetWMProtocols(dpy, m_window, &deleteAtom, 1);
}

void Window::setVisibility (bool visible)
{
	::Display*	dpy			= m_display.getXDisplay();
	int			eventType	= None;
	XEvent		event;

	if (visible == m_visible)
		return;

	if (visible)
	{
		XMapWindow(dpy, m_window);
		eventType = MapNotify;
	}
	else
	{
		XUnmapWindow(dpy, m_window);
		eventType = UnmapNotify;
	}

	// We are only interested about exposure/structure notify events, not user input
	XSelectInput(dpy, m_window, ExposureMask | StructureNotifyMask);

	do
	{
		XWindowEvent(dpy, m_window, ExposureMask | StructureNotifyMask, &event);
	} while (event.type != eventType);

	m_visible = visible;
}

void Window::getDimensions (int* width, int* height) const
{
	int x, y;
	::Window root;
	unsigned width_, height_, borderWidth, depth;

	XGetGeometry(m_display.getXDisplay(), m_window, &root, &x, &y, &width_, &height_, &borderWidth, &depth);
	if (width != DE_NULL)
		*width = static_cast<int>(width_);
	if (height != DE_NULL)
		*height = static_cast<int>(height_);
}

void Window::setDimensions (int width, int height)
{
	const unsigned int	mask = CWWidth | CWHeight;
	XWindowChanges		changes;
	changes.width		= width;
	changes.height		= height;

	XConfigureWindow(m_display.getXDisplay(), m_window, mask, &changes);
}

void Window::processEvents (void)
{
	// A bit of a hack, since we don't really handle all the events.
	m_display.processEvents();
}

Window::~Window (void)
{
	XDestroyWindow(m_display.getXDisplay(), m_window);
	if (m_colormap != None)
		XFreeColormap(m_display.getXDisplay(), m_colormap);
}

} // x11
} // tcu
<|endoftext|>


<commit_before>#ifdef _MSC_VER
#pragma warning(disable: 4351)
#endif

#include "FanTable.h"
#include "FanDefinition.h"
#include "../mahjong-algorithm/fan_calculator.h"
#include "../common.h"

USING_NS_CC;

static const char *principle_title[] = { "不重复", "不拆移", "不得相同", "就高不就低", "套算一次" };

static const int fanLevel[] = { 0, 1, 2, 4, 6, 8, 12, 16, 24, 32, 48, 64, 88 };  // 番种
static const size_t eachLevelCounts[] = { 5, 13, 10, 4, 7, 9, 5, 6, 9, 3, 2, 6, 7 };  // 各档次番种的个数
static const size_t eachLevelBeginIndex[] = { 0, 69, 59, 55, 48, 39, 34, 28, 19, 16, 14, 8, 1 };

static inline size_t computeRowsAlign4(size_t cnt) {
    return (cnt >> 2) + !!(cnt & 0x3);
}

bool FanTableScene::init() {
    if (UNLIKELY(!BaseScene::initWithTitle("国标麻将番种表"))) {
        return false;
    }

    Size visibleSize = Director::getInstance()->getVisibleSize();
    Vec2 origin = Director::getInstance()->getVisibleOrigin();

    cw::TableView *tableView = cw::TableView::create();
    tableView->setScrollBarPositionFromCorner(Vec2(2, 2));
    tableView->setScrollBarWidth(4);
    tableView->setScrollBarOpacity(0x99);
    tableView->setContentSize(Size(visibleSize.width - 5, visibleSize.height - 35));
    tableView->setDelegate(this);
    tableView->setDirection(ui::ScrollView::Direction::VERTICAL);
    tableView->setVerticalFillOrder(cw::TableView::VerticalFillOrder::TOP_DOWN);

    tableView->setAnchorPoint(Vec2::ANCHOR_MIDDLE);
    tableView->setPosition(Vec2(origin.x + visibleSize.width * 0.5f, origin.y + visibleSize.height * 0.5f - 15.0f));
    tableView->reloadData();
    this->addChild(tableView);

    return true;
}

ssize_t FanTableScene::numberOfCellsInTableView(cw::TableView *table) {
    return 13;
}

cocos2d::Size FanTableScene::tableCellSizeForIndex(cw::TableView *table, ssize_t idx) {
    size_t cnt = eachLevelCounts[idx];
    float height = computeRowsAlign4(cnt) * 25.0f;
    return Size(0, height + 15.0f);
}

cw::TableViewCell *FanTableScene::tableCellAtIndex(cw::TableView *table, ssize_t idx) {
    typedef cw::TableViewCellEx<Label *, ui::Button *[13]> CustomCell;
    CustomCell *cell = (CustomCell *)table->dequeueCell();

    Size visibleSize = Director::getInstance()->getVisibleSize();
    const float gap = (visibleSize.width - 5.0f) * 0.25f;

    if (cell == nullptr) {
        cell = CustomCell::create();

        CustomCell::ExtDataType &ext = cell->getExtData();
        Label *&label = std::get<0>(ext);
        ui::Button *(&buttons)[13] = std::get<1>(ext);

        label = Label::createWithSystemFont("1番", "Arial", 12);
        label->setAnchorPoint(Vec2::ANCHOR_MIDDLE_LEFT);
        cell->addChild(label);
        label->setColor(Color3B::BLACK);

        for (size_t k = 0; k < 13; ++k) {
            ui::Button *button = ui::Button::create("source_material/btn_square_normal.png", "source_material/btn_square_highlighted.png");
            button->setScale9Enabled(true);
            button->setContentSize(Size(gap - 4.0f, 20.0f));
            button->setTitleColor(Color3B::BLACK);
            button->setTitleFontSize(12);
            button->addClickEventListener(std::bind(&FanTableScene::onPointsNameButton, this, std::placeholders::_1));

            cell->addChild(button);
            buttons[k] = button;
        }
    }

    const size_t currentLevelCount = eachLevelCounts[idx];
    size_t totalRows = computeRowsAlign4(currentLevelCount);

    const CustomCell::ExtDataType ext = cell->getExtData();
    Label *label = std::get<0>(ext);
    ui::Button *const (&buttons)[13] = std::get<1>(ext);

    size_t idx0;
    const char **titleTexts;
    if (fanLevel[idx] == 0) {
        label->setString("基本计分原则");
        idx0 = 100;
        titleTexts = &principle_title[0];
    }
    else {
        label->setString(std::to_string(fanLevel[idx]).append("番"));
        idx0 = eachLevelBeginIndex[idx];
        titleTexts = &mahjong::fan_name[idx0];
    }
    label->setPosition(Vec2(5.0f, totalRows * 25.0f + 7.0f));

    for (size_t k = 0; k < currentLevelCount; ++k) {
        ui::Button *button = buttons[k];
        button->setTitleText(titleTexts[k]);
        button->setUserData(reinterpret_cast<void *>(idx0 + k));
        button->setVisible(true);
        button->setEnabled(true);
        size_t col = k & 0x3;
        size_t row = k >> 2;
        button->setPosition(Vec2(gap * (col + 0.5f), (totalRows - row - 0.5f) * 25.0f));

        Common::scaleLabelToFitWidth(button->getTitleLabel(), gap - 8.0f);
    }

    for (size_t k = currentLevelCount; k < 13; ++k) {
        buttons[k]->setVisible(false);
        buttons[k]->setEnabled(false);
    }

    return cell;
}

void FanTableScene::onPointsNameButton(cocos2d::Ref *sender) {
    ui::Button *button = (ui::Button *)sender;
    size_t idx = reinterpret_cast<size_t>(button->getUserData());
    Director::getInstance()->pushScene(FanDefinitionScene::create(idx));
}
<commit_msg>增加“原则”二字<commit_after>#ifdef _MSC_VER
#pragma warning(disable: 4351)
#endif

#include "FanTable.h"
#include "FanDefinition.h"
#include "../mahjong-algorithm/fan_calculator.h"
#include "../common.h"

USING_NS_CC;

static const char *principle_title[] = { "不重复原则", "不拆移原则", "不得相同原则", "就高不就低", "套算一次原则" };

static const int fanLevel[] = { 0, 1, 2, 4, 6, 8, 12, 16, 24, 32, 48, 64, 88 };  // 番种
static const size_t eachLevelCounts[] = { 5, 13, 10, 4, 7, 9, 5, 6, 9, 3, 2, 6, 7 };  // 各档次番种的个数
static const size_t eachLevelBeginIndex[] = { 0, 69, 59, 55, 48, 39, 34, 28, 19, 16, 14, 8, 1 };

static inline size_t computeRowsAlign4(size_t cnt) {
    return (cnt >> 2) + !!(cnt & 0x3);
}

bool FanTableScene::init() {
    if (UNLIKELY(!BaseScene::initWithTitle("国标麻将番种表"))) {
        return false;
    }

    Size visibleSize = Director::getInstance()->getVisibleSize();
    Vec2 origin = Director::getInstance()->getVisibleOrigin();

    cw::TableView *tableView = cw::TableView::create();
    tableView->setScrollBarPositionFromCorner(Vec2(2, 2));
    tableView->setScrollBarWidth(4);
    tableView->setScrollBarOpacity(0x99);
    tableView->setContentSize(Size(visibleSize.width - 5, visibleSize.height - 35));
    tableView->setDelegate(this);
    tableView->setDirection(ui::ScrollView::Direction::VERTICAL);
    tableView->setVerticalFillOrder(cw::TableView::VerticalFillOrder::TOP_DOWN);

    tableView->setAnchorPoint(Vec2::ANCHOR_MIDDLE);
    tableView->setPosition(Vec2(origin.x + visibleSize.width * 0.5f, origin.y + visibleSize.height * 0.5f - 15.0f));
    tableView->reloadData();
    this->addChild(tableView);

    return true;
}

ssize_t FanTableScene::numberOfCellsInTableView(cw::TableView *table) {
    return 13;
}

cocos2d::Size FanTableScene::tableCellSizeForIndex(cw::TableView *table, ssize_t idx) {
    size_t cnt = eachLevelCounts[idx];
    float height = computeRowsAlign4(cnt) * 25.0f;
    return Size(0, height + 15.0f);
}

cw::TableViewCell *FanTableScene::tableCellAtIndex(cw::TableView *table, ssize_t idx) {
    typedef cw::TableViewCellEx<Label *, ui::Button *[13]> CustomCell;
    CustomCell *cell = (CustomCell *)table->dequeueCell();

    Size visibleSize = Director::getInstance()->getVisibleSize();
    const float gap = (visibleSize.width - 5.0f) * 0.25f;

    if (cell == nullptr) {
        cell = CustomCell::create();

        CustomCell::ExtDataType &ext = cell->getExtData();
        Label *&label = std::get<0>(ext);
        ui::Button *(&buttons)[13] = std::get<1>(ext);

        label = Label::createWithSystemFont("1番", "Arial", 12);
        label->setAnchorPoint(Vec2::ANCHOR_MIDDLE_LEFT);
        cell->addChild(label);
        label->setColor(Color3B::BLACK);

        for (size_t k = 0; k < 13; ++k) {
            ui::Button *button = ui::Button::create("source_material/btn_square_normal.png", "source_material/btn_square_highlighted.png");
            button->setScale9Enabled(true);
            button->setContentSize(Size(gap - 4.0f, 20.0f));
            button->setTitleColor(Color3B::BLACK);
            button->setTitleFontSize(12);
            button->addClickEventListener(std::bind(&FanTableScene::onPointsNameButton, this, std::placeholders::_1));

            cell->addChild(button);
            buttons[k] = button;
        }
    }

    const size_t currentLevelCount = eachLevelCounts[idx];
    size_t totalRows = computeRowsAlign4(currentLevelCount);

    const CustomCell::ExtDataType ext = cell->getExtData();
    Label *label = std::get<0>(ext);
    ui::Button *const (&buttons)[13] = std::get<1>(ext);

    size_t idx0;
    const char **titleTexts;
    if (fanLevel[idx] == 0) {
        label->setString("基本计分原则");
        idx0 = 100;
        titleTexts = &principle_title[0];
    }
    else {
        label->setString(std::to_string(fanLevel[idx]).append("番"));
        idx0 = eachLevelBeginIndex[idx];
        titleTexts = &mahjong::fan_name[idx0];
    }
    label->setPosition(Vec2(5.0f, totalRows * 25.0f + 7.0f));

    for (size_t k = 0; k < currentLevelCount; ++k) {
        ui::Button *button = buttons[k];
        button->setTitleText(titleTexts[k]);
        button->setUserData(reinterpret_cast<void *>(idx0 + k));
        button->setVisible(true);
        button->setEnabled(true);
        size_t col = k & 0x3;
        size_t row = k >> 2;
        button->setPosition(Vec2(gap * (col + 0.5f), (totalRows - row - 0.5f) * 25.0f));

        Common::scaleLabelToFitWidth(button->getTitleLabel(), gap - 8.0f);
    }

    for (size_t k = currentLevelCount; k < 13; ++k) {
        buttons[k]->setVisible(false);
        buttons[k]->setEnabled(false);
    }

    return cell;
}

void FanTableScene::onPointsNameButton(cocos2d::Ref *sender) {
    ui::Button *button = (ui::Button *)sender;
    size_t idx = reinterpret_cast<size_t>(button->getUserData());
    Director::getInstance()->pushScene(FanDefinitionScene::create(idx));
}
<|endoftext|>


<commit_before><commit_msg>Disk cache: Cleanup file_posix so that it uses the latest version of InFlightIO & Co.<commit_after><|endoftext|>


<commit_before><commit_msg>[http] use proper conversion from string to unsigned long<commit_after><|endoftext|>


<commit_before>#include "Log.h"
#include "Platform.h"
#include "MemoryBuffer.h"

#include <gtest/gtest.h>

#include <google/protobuf/io/zero_copy_stream_impl.h>
#include <google/protobuf/io/coded_stream.h>

#include <fcntl.h> // FOR MINGW + O_RDONLY

using namespace std;
using namespace google::protobuf;

TEST(TestFileSystem3, ZeroCopyInputStream)
{
  fs::path path = "Georgia_Regular_64.fnt";

  io::CodedInputStream *codedInput = nullptr;
  io::ZeroCopyInputStream *rawInput = nullptr;
  shared_ptr<chr::MemoryBuffer> memoryBuffer;
  int fd = 0;

  if (chr::hasMemoryResources())
  {
    memoryBuffer = chr::getResourceBuffer(path);

    if (memoryBuffer)
    {
      codedInput = new io::CodedInputStream(reinterpret_cast<const uint8*>(memoryBuffer->data()), memoryBuffer->size());
    }
    else
    {
      ADD_FAILURE() << "chr::getResourceBuffer";
    }
  }
  else if (chr::hasFileResources())
  {
    auto resPath = chr::getResourcePath(path);
    fd = open(resPath.string().data(), O_RDONLY);

    if (fd > 0)
    {
      rawInput = new io::FileInputStream(fd);
      codedInput = new io::CodedInputStream(rawInput);
    }
    else
    {
      ADD_FAILURE() << "open";
    }
  }

  if (codedInput)
  {
    string version;

    if (codedInput->ReadString(&version, 9))
    {
      LOGI << "{" << version << "}" << endl;

      delete codedInput;

      if (rawInput)
      {
        delete rawInput;
        close(fd);
      }
    }
    else
    {
      ADD_FAILURE() << "CodedInputStream::ReadString";
    }
  }
}
<commit_msg>TestingFileSystem3: CHECKING CodedInputStream::ReadLittleEndian32()<commit_after>#include "Log.h"
#include "Platform.h"
#include "MemoryBuffer.h"

#include <gtest/gtest.h>

#include <google/protobuf/io/zero_copy_stream_impl.h>
#include <google/protobuf/io/coded_stream.h>

#include <fcntl.h> // FOR MINGW + O_RDONLY

using namespace std;
using namespace google::protobuf;

TEST(TestFileSystem3, ZeroCopyInputStream)
{
  fs::path path = "Georgia_Regular_64.fnt";

  io::CodedInputStream *codedInput = nullptr;
  io::ZeroCopyInputStream *rawInput = nullptr;
  shared_ptr<chr::MemoryBuffer> memoryBuffer;
  int fd = 0;

  if (chr::hasMemoryResources())
  {
    memoryBuffer = chr::getResourceBuffer(path);

    if (memoryBuffer)
    {
      codedInput = new io::CodedInputStream(reinterpret_cast<const uint8*>(memoryBuffer->data()), memoryBuffer->size());
    }
    else
    {
      ADD_FAILURE() << "chr::getResourceBuffer";
    }
  }
  else if (chr::hasFileResources())
  {
    auto resPath = chr::getResourcePath(path);
    fd = open(resPath.string().data(), O_RDONLY);

    if (fd > 0)
    {
      rawInput = new io::FileInputStream(fd);
      codedInput = new io::CodedInputStream(rawInput);
    }
    else
    {
      ADD_FAILURE() << "open";
    }
  }

  if (codedInput)
  {
    string version;
    EXPECT_TRUE(codedInput->ReadString(&version, 9));
    EXPECT_EQ("XFONT.004", version);
    EXPECT_TRUE(codedInput->Skip(1)); // XXX

    uint32 glyphCount;
    EXPECT_TRUE(codedInput->ReadLittleEndian32(&glyphCount));
    EXPECT_EQ(191, glyphCount);

    float baseSize;
    EXPECT_TRUE(codedInput->ReadLittleEndian32(reinterpret_cast<uint32_t*>(&baseSize)));
    EXPECT_EQ(64, baseSize);

    // ---

    delete codedInput;

    if (rawInput)
    {
      delete rawInput;
      close(fd);
    }
  }
}
<|endoftext|>


<commit_before>/*
  Copyright (C) 2008-2015 The Communi Project

  You may use this file under the terms of BSD license as follows:

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the copyright holder nor the names of its
      contributors may be used to endorse or promote products derived
      from this software without specific prior written permission.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR
  ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "textinput.h"
#include <QStyleOptionFrame>
#include <IrcCommandParser>
#include <IrcBufferModel>
#include <IrcConnection>
#include <QMessageBox>
#include <QSettings>
#include <QCheckBox>
#include <IrcBuffer>
#include <QKeyEvent>
#include <QPainter>

TextInput::TextInput(QWidget* parent) : QLineEdit(parent)
{
    setAttribute(Qt::WA_MacShowFocusRect, false);

    d.hint = "...";
    d.index = 0;
    d.buffer = 0;
    d.parser = 0;

    d.completer = new IrcCompleter(this);
    connect(this, SIGNAL(bufferChanged(IrcBuffer*)), d.completer, SLOT(setBuffer(IrcBuffer*)));
    connect(this, SIGNAL(parserChanged(IrcCommandParser*)), d.completer, SLOT(setParser(IrcCommandParser*)));
    connect(d.completer, SIGNAL(completed(QString,int)), this, SLOT(doComplete(QString,int)));
    connect(this, SIGNAL(textEdited(QString)), d.completer, SLOT(reset()));

    connect(this, SIGNAL(returnPressed()), this, SLOT(sendInput()));
    connect(this, SIGNAL(textChanged(QString)), this, SLOT(updateHint(QString)));
}

IrcBuffer* TextInput::buffer() const
{
    return d.buffer;
}

IrcCommandParser* TextInput::parser() const
{
    return d.parser;
}

static void bind(IrcBuffer* buffer, IrcCommandParser* parser)
{
    if (buffer && parser) {
        IrcBufferModel* model = buffer->model();
        QObject::connect(model, SIGNAL(channelsChanged(QStringList)), parser, SLOT(setChannels(QStringList)));
        QObject::connect(buffer, SIGNAL(titleChanged(QString)), parser, SLOT(setTarget(QString)));

        parser->setTarget(buffer->title());
        parser->setChannels(buffer->model()->channels());
    } else if (parser) {
        parser->reset();
    }
}

static void unbind(IrcBuffer* buffer, IrcCommandParser* parser)
{
    if (buffer && parser) {
        IrcBufferModel* model = buffer->model();
        QObject::disconnect(model, SIGNAL(channelsChanged(QStringList)), parser, SLOT(setChannels(QStringList)));
        QObject::disconnect(buffer, SIGNAL(titleChanged(QString)), parser, SLOT(setTarget(QString)));
    }
}

void TextInput::setBuffer(IrcBuffer* buffer)
{
    if (d.buffer != buffer) {
        unbind(d.buffer, d.parser);
        bind(buffer, d.parser);
        if (d.buffer)
            d.states.insert(d.buffer, saveState());
        d.buffer = buffer;
        if (buffer)
            restoreState(d.states.value(buffer));
        emit bufferChanged(buffer);
    }
}

void TextInput::setParser(IrcCommandParser* parser)
{
    if (d.parser != parser) {
        unbind(d.buffer, d.parser);
        bind(d.buffer, parser);
        d.parser = parser;
        emit parserChanged(parser);
    }
}

bool TextInput::event(QEvent* event)
{
    if (event->type() == QEvent::KeyPress) {
        switch (static_cast<QKeyEvent*>(event)->key()) {
        case Qt::Key_Tab:
            tryComplete(IrcCompleter::Forward);
            return true;
        case Qt::Key_Backtab:
            tryComplete(IrcCompleter::Backward);
            return true;
        case Qt::Key_Up:
            goBackward();
            return true;
        case Qt::Key_Down:
            goForward();
            return true;
        default:
            break;
        }
    }
    return QLineEdit::event(event);
}

// copied from qlineedit.cpp:
#define vMargin 1
#define hMargin 2

void TextInput::paintEvent(QPaintEvent* event)
{
    QLineEdit::paintEvent(event);

    if (!d.hint.isEmpty()) {
        QStyleOptionFrameV2 option;
        initStyleOption(&option);

        QRect r = style()->subElementRect(QStyle::SE_LineEditContents, &option, this);
        int left, top, right, bottom;
        getTextMargins(&left, &top, &right, &bottom);
        left += qMax(0, -fontMetrics().minLeftBearing());
        r.adjust(left, top, -right, -bottom);
        r.adjust(hMargin, vMargin, -hMargin, -vMargin);

        QString txt = text();
        if (!txt.isEmpty()) {
            if (!txt.endsWith(" "))
                txt += " ";
            r.adjust(fontMetrics().width(txt), 0, 0, 0);
        }

        QPainter painter(this);
        QColor color = palette().text().color();
        color.setAlpha(128);
        painter.setPen(color);

        QString hint = fontMetrics().elidedText(d.hint, Qt::ElideRight, r.width());
        painter.drawText(r, alignment(), hint);
    }
}

void TextInput::updateHint(const QString& text)
{
    QString match;
    QStringList params;
    QStringList suggestions;
    if (d.parser) {
        if (text.startsWith('/')) {
            QStringList words = text.mid(1).split(" ");
            QString command = words.value(0);
            params = words.mid(1);
            foreach (const QString& available, d.parser->commands()) {
                if (!command.compare(available, Qt::CaseInsensitive)) {
                    match = available;
                    break;
                } else if (params.isEmpty() && available.startsWith(command, Qt::CaseInsensitive)) {
                    suggestions += available;
                }
            }
        }
    }

    if (!match.isEmpty()) {
        QStringList syntax = d.parser->syntax(match).split(" ", QString::SkipEmptyParts).mid(1);
        if (!params.isEmpty())
            d.hint = QStringList(syntax.mid(params.count() - 1)).join(" ");
        else
            d.hint = syntax.join(" ");
    } else if (suggestions.isEmpty()) {
        d.hint = text.isEmpty() ? "..." : "";
    } else if (suggestions.count() == 1) {
        d.hint = d.parser->syntax(suggestions.first());
    } else {
        d.hint = suggestions.join(" ");
    }
}

void TextInput::goBackward()
{
    if (!text().isEmpty() && !d.history.contains(text()))
        d.current = text();
    if (d.index > 0)
        setText(d.history.value(--d.index));
}

void TextInput::goForward()
{
    if (d.index < d.history.count())
        setText(d.history.value(++d.index));
    if (text().isEmpty())
        setText(d.current);
}

void TextInput::sendInput()
{
    IrcBuffer* b = buffer();
    IrcCommandParser* p = parser();
    IrcConnection* c = b ? b->connection() : 0;
    if (!c || !p)
        return;

    const QStringList lines = text().split(QRegExp("[\\r\\n]"), QString::SkipEmptyParts);
#if QT_VERSION >= 0x050200
    if (lines.count() > 2) {
        QSettings settings;
        bool warn = settings.value("warn", true).toBool();
        if (warn) {
            QMessageBox msgBox;
            msgBox.setText(tr("The input contains more than two lines."));
            msgBox.setInformativeText(tr("IRC is not a suitable medium for pasting multiple lines of text. Consider using a pastebin site instead.\n\n"
                                         "Do you still want to proceed and send %1 lines of text?\n").arg(lines.count()));
            msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
            msgBox.setDefaultButton(QMessageBox::No);
            QCheckBox* checkBox = new QCheckBox(tr("Do not show again"), &msgBox);
            msgBox.setCheckBox(checkBox);
            int res = msgBox.exec();
            settings.setValue("warn", !checkBox->isChecked());
            if (res != QMessageBox::Yes)
                return;
        }
    }
#endif

    if (!text().isEmpty()) {
        d.current.clear();
        d.history.append(text());
        d.index = d.history.count();
    }

    bool error = false;
    foreach (const QString& line, lines) {
        if (!line.trimmed().isEmpty()) {
            IrcCommand* cmd = p->parse(line);
            if (cmd) {
                cmd->setProperty("TextInput", true);
                b->sendCommand(cmd);
                IrcNetwork* network = b->network();
                if (network && network->isCapable("echo-message"))
                    continue;
                if (cmd->type() == IrcCommand::Message || cmd->type() == IrcCommand::Notice || cmd->type() == IrcCommand::CtcpAction) {
                    IrcMessage* msg = cmd->toMessage(c->nickName(), c);
                    if (msg) {
                        b->receiveMessage(msg);
                        msg->deleteLater();
                    }
                }
            } else {
                error = true;
            }
        }
    }
    if (!error)
        clear();
}

void TextInput::tryComplete(IrcCompleter::Direction direction)
{
    d.completer->complete(text(), cursorPosition(), direction);
}

void TextInput::doComplete(const QString& text, int cursor)
{
    setText(text);
    setCursorPosition(cursor);
}

QByteArray TextInput::saveState() const
{
    QByteArray data;
    QDataStream out(&data, QIODevice::WriteOnly);
    out << d.index << d.current << d.history;
    out << text() << cursorPosition() << selectionStart() << selectedText().length();
    return data;
}

void TextInput::restoreState(const QByteArray& state)
{
    QDataStream in(state);
    in >> d.index >> d.current >> d.history;
    QString txt;
    int pos, start, len;
    in >> txt >> pos >> start >> len;
    setText(txt);
    setCursorPosition(pos);
    if (start != -1)
        setSelection(start, len);
}
<commit_msg>TextInput: let sent messages through even when echo-message is active<commit_after>/*
  Copyright (C) 2008-2015 The Communi Project

  You may use this file under the terms of BSD license as follows:

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the copyright holder nor the names of its
      contributors may be used to endorse or promote products derived
      from this software without specific prior written permission.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR
  ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "textinput.h"
#include <QStyleOptionFrame>
#include <IrcCommandParser>
#include <IrcBufferModel>
#include <IrcConnection>
#include <QMessageBox>
#include <QSettings>
#include <QCheckBox>
#include <IrcBuffer>
#include <QKeyEvent>
#include <QPainter>

TextInput::TextInput(QWidget* parent) : QLineEdit(parent)
{
    setAttribute(Qt::WA_MacShowFocusRect, false);

    d.hint = "...";
    d.index = 0;
    d.buffer = 0;
    d.parser = 0;

    d.completer = new IrcCompleter(this);
    connect(this, SIGNAL(bufferChanged(IrcBuffer*)), d.completer, SLOT(setBuffer(IrcBuffer*)));
    connect(this, SIGNAL(parserChanged(IrcCommandParser*)), d.completer, SLOT(setParser(IrcCommandParser*)));
    connect(d.completer, SIGNAL(completed(QString,int)), this, SLOT(doComplete(QString,int)));
    connect(this, SIGNAL(textEdited(QString)), d.completer, SLOT(reset()));

    connect(this, SIGNAL(returnPressed()), this, SLOT(sendInput()));
    connect(this, SIGNAL(textChanged(QString)), this, SLOT(updateHint(QString)));
}

IrcBuffer* TextInput::buffer() const
{
    return d.buffer;
}

IrcCommandParser* TextInput::parser() const
{
    return d.parser;
}

static void bind(IrcBuffer* buffer, IrcCommandParser* parser)
{
    if (buffer && parser) {
        IrcBufferModel* model = buffer->model();
        QObject::connect(model, SIGNAL(channelsChanged(QStringList)), parser, SLOT(setChannels(QStringList)));
        QObject::connect(buffer, SIGNAL(titleChanged(QString)), parser, SLOT(setTarget(QString)));

        parser->setTarget(buffer->title());
        parser->setChannels(buffer->model()->channels());
    } else if (parser) {
        parser->reset();
    }
}

static void unbind(IrcBuffer* buffer, IrcCommandParser* parser)
{
    if (buffer && parser) {
        IrcBufferModel* model = buffer->model();
        QObject::disconnect(model, SIGNAL(channelsChanged(QStringList)), parser, SLOT(setChannels(QStringList)));
        QObject::disconnect(buffer, SIGNAL(titleChanged(QString)), parser, SLOT(setTarget(QString)));
    }
}

void TextInput::setBuffer(IrcBuffer* buffer)
{
    if (d.buffer != buffer) {
        unbind(d.buffer, d.parser);
        bind(buffer, d.parser);
        if (d.buffer)
            d.states.insert(d.buffer, saveState());
        d.buffer = buffer;
        if (buffer)
            restoreState(d.states.value(buffer));
        emit bufferChanged(buffer);
    }
}

void TextInput::setParser(IrcCommandParser* parser)
{
    if (d.parser != parser) {
        unbind(d.buffer, d.parser);
        bind(d.buffer, parser);
        d.parser = parser;
        emit parserChanged(parser);
    }
}

bool TextInput::event(QEvent* event)
{
    if (event->type() == QEvent::KeyPress) {
        switch (static_cast<QKeyEvent*>(event)->key()) {
        case Qt::Key_Tab:
            tryComplete(IrcCompleter::Forward);
            return true;
        case Qt::Key_Backtab:
            tryComplete(IrcCompleter::Backward);
            return true;
        case Qt::Key_Up:
            goBackward();
            return true;
        case Qt::Key_Down:
            goForward();
            return true;
        default:
            break;
        }
    }
    return QLineEdit::event(event);
}

// copied from qlineedit.cpp:
#define vMargin 1
#define hMargin 2

void TextInput::paintEvent(QPaintEvent* event)
{
    QLineEdit::paintEvent(event);

    if (!d.hint.isEmpty()) {
        QStyleOptionFrameV2 option;
        initStyleOption(&option);

        QRect r = style()->subElementRect(QStyle::SE_LineEditContents, &option, this);
        int left, top, right, bottom;
        getTextMargins(&left, &top, &right, &bottom);
        left += qMax(0, -fontMetrics().minLeftBearing());
        r.adjust(left, top, -right, -bottom);
        r.adjust(hMargin, vMargin, -hMargin, -vMargin);

        QString txt = text();
        if (!txt.isEmpty()) {
            if (!txt.endsWith(" "))
                txt += " ";
            r.adjust(fontMetrics().width(txt), 0, 0, 0);
        }

        QPainter painter(this);
        QColor color = palette().text().color();
        color.setAlpha(128);
        painter.setPen(color);

        QString hint = fontMetrics().elidedText(d.hint, Qt::ElideRight, r.width());
        painter.drawText(r, alignment(), hint);
    }
}

void TextInput::updateHint(const QString& text)
{
    QString match;
    QStringList params;
    QStringList suggestions;
    if (d.parser) {
        if (text.startsWith('/')) {
            QStringList words = text.mid(1).split(" ");
            QString command = words.value(0);
            params = words.mid(1);
            foreach (const QString& available, d.parser->commands()) {
                if (!command.compare(available, Qt::CaseInsensitive)) {
                    match = available;
                    break;
                } else if (params.isEmpty() && available.startsWith(command, Qt::CaseInsensitive)) {
                    suggestions += available;
                }
            }
        }
    }

    if (!match.isEmpty()) {
        QStringList syntax = d.parser->syntax(match).split(" ", QString::SkipEmptyParts).mid(1);
        if (!params.isEmpty())
            d.hint = QStringList(syntax.mid(params.count() - 1)).join(" ");
        else
            d.hint = syntax.join(" ");
    } else if (suggestions.isEmpty()) {
        d.hint = text.isEmpty() ? "..." : "";
    } else if (suggestions.count() == 1) {
        d.hint = d.parser->syntax(suggestions.first());
    } else {
        d.hint = suggestions.join(" ");
    }
}

void TextInput::goBackward()
{
    if (!text().isEmpty() && !d.history.contains(text()))
        d.current = text();
    if (d.index > 0)
        setText(d.history.value(--d.index));
}

void TextInput::goForward()
{
    if (d.index < d.history.count())
        setText(d.history.value(++d.index));
    if (text().isEmpty())
        setText(d.current);
}

void TextInput::sendInput()
{
    IrcBuffer* b = buffer();
    IrcCommandParser* p = parser();
    IrcConnection* c = b ? b->connection() : 0;
    if (!c || !p)
        return;

    const QStringList lines = text().split(QRegExp("[\\r\\n]"), QString::SkipEmptyParts);
#if QT_VERSION >= 0x050200
    if (lines.count() > 2) {
        QSettings settings;
        bool warn = settings.value("warn", true).toBool();
        if (warn) {
            QMessageBox msgBox;
            msgBox.setText(tr("The input contains more than two lines."));
            msgBox.setInformativeText(tr("IRC is not a suitable medium for pasting multiple lines of text. Consider using a pastebin site instead.\n\n"
                                         "Do you still want to proceed and send %1 lines of text?\n").arg(lines.count()));
            msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
            msgBox.setDefaultButton(QMessageBox::No);
            QCheckBox* checkBox = new QCheckBox(tr("Do not show again"), &msgBox);
            msgBox.setCheckBox(checkBox);
            int res = msgBox.exec();
            settings.setValue("warn", !checkBox->isChecked());
            if (res != QMessageBox::Yes)
                return;
        }
    }
#endif

    if (!text().isEmpty()) {
        d.current.clear();
        d.history.append(text());
        d.index = d.history.count();
    }

    bool error = false;
    foreach (const QString& line, lines) {
        if (!line.trimmed().isEmpty()) {
            IrcCommand* cmd = p->parse(line);
            if (cmd) {
                cmd->setProperty("TextInput", true);
                b->sendCommand(cmd);
                if (cmd->type() == IrcCommand::Message || cmd->type() == IrcCommand::Notice || cmd->type() == IrcCommand::CtcpAction) {
                    IrcMessage* msg = cmd->toMessage(c->nickName(), c);
                    if (msg) {
                        b->receiveMessage(msg);
                        msg->deleteLater();
                    }
                }
            } else {
                error = true;
            }
        }
    }
    if (!error)
        clear();
}

void TextInput::tryComplete(IrcCompleter::Direction direction)
{
    d.completer->complete(text(), cursorPosition(), direction);
}

void TextInput::doComplete(const QString& text, int cursor)
{
    setText(text);
    setCursorPosition(cursor);
}

QByteArray TextInput::saveState() const
{
    QByteArray data;
    QDataStream out(&data, QIODevice::WriteOnly);
    out << d.index << d.current << d.history;
    out << text() << cursorPosition() << selectionStart() << selectedText().length();
    return data;
}

void TextInput::restoreState(const QByteArray& state)
{
    QDataStream in(state);
    in >> d.index >> d.current >> d.history;
    QString txt;
    int pos, start, len;
    in >> txt >> pos >> start >> len;
    setText(txt);
    setCursorPosition(pos);
    if (start != -1)
        setSelection(start, len);
}
<|endoftext|>