<commit_before>/**
 * The Seeks proxy and plugin framework are part of the SEEKS project.
 * Copyright (C) 2010 Emmanuel Benazera, juban@free.fr
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "img_websearch.h"
#include "img_sort_rank.h"
#include "websearch.h" // websearch plugin.
#include "errlog.h"
#include "cgi.h"
#include "cgisimple.h"
#include "miscutil.h"
#include "json_renderer.h"
#include "static_renderer.h"
#include "seeks_proxy.h"

#include <unistd.h>
#include <sys/stat.h>
#include <sys/times.h>

using namespace sp;

namespace seeks_plugins
{
   img_websearch_configuration* img_websearch::_iwconfig = NULL;
   hash_map<uint32_t,query_context*,id_hash_uint> img_websearch::_active_img_qcontexts
     = hash_map<uint32_t,query_context*,id_hash_uint>();

   img_websearch::img_websearch()
     : plugin()
       {
	  _name = "img_websearch";
	  _version_major = "0";
	  _version_minor = "1";

	  // config filename.
	  if (seeks_proxy::_datadir.empty())
	    _config_filename = plugin_manager::_plugin_repository + "img_websearch/img-websearch-config";
	  else _config_filename = seeks_proxy::_datadir + "/plugins/img_websearch/img-websearch-config";

#ifdef SEEKS_CONFIGDIR
	  struct stat stFileInfo;
	  if (!stat(_config_filename.c_str(), &stFileInfo)  == 0)
	    {
	       _config_filename = SEEKS_CONFIGDIR "/img-websearch-config";
	    }
#endif

	  if (img_websearch::_iwconfig == NULL)
	    img_websearch::_iwconfig = new img_websearch_configuration(_config_filename);

	  // cgi dispatchers.
	  _cgi_dispatchers.reserve(2);

	  cgi_dispatcher *cgid_wb_seeks_img_search_css
	    = new cgi_dispatcher("seeks_img_search.css", &img_websearch::cgi_img_websearch_search_css, NULL, TRUE);
	  _cgi_dispatchers.push_back(cgid_wb_seeks_img_search_css);

	  cgi_dispatcher *cgid_img_wb_search
	    = new cgi_dispatcher("search_img", &img_websearch::cgi_img_websearch_search, NULL, TRUE);
	  _cgi_dispatchers.push_back(cgid_img_wb_search);
       }

   img_websearch::~img_websearch()
     {
     }

   // CGI calls.
   sp_err img_websearch::cgi_img_websearch_search_css(client_state *csp,
						      http_response *rsp,
						      const hash_map<const char*, const char*, hash<const char*>, eqstr> *parameters)
     {
	assert(csp);
	assert(rsp);
	assert(parameters);

	std::string seeks_search_css_str = "img_websearch/templates/css/seeks_img_search.css";
	hash_map<const char*,const char*,hash<const char*>,eqstr> *exports
	  = static_renderer::websearch_exports(csp);
	csp->_content_type = CT_CSS;
	sp_err err = cgi::template_fill_for_cgi_str(csp,seeks_search_css_str.c_str(),
						    (seeks_proxy::_datadir.empty() ? plugin_manager::_plugin_repository.c_str()
						     : std::string(seeks_proxy::_datadir + "plugins/").c_str()),
						    exports,rsp);
	if (err != SP_ERR_OK)
	  {
	     errlog::log_error(LOG_LEVEL_ERROR, "Could not load seeks_img_search.css");
	  }
	rsp->_is_static = 1;

	return SP_ERR_OK;
     }

   sp_err img_websearch::cgi_img_websearch_search(client_state *csp,
						  http_response *rsp,
						  const hash_map<const char*,const char*,hash<const char*>,eqstr> *parameters)
     {
	if (!parameters->empty())
	  {
	     const char *query = miscutil::lookup(parameters,"q"); // grab the query.
	     if (!query || strlen(query) == 0)
	       {
		  // return websearch homepage instead.
		  return websearch::cgi_websearch_hp(csp,rsp,parameters);
	       }
	     else se_handler::preprocess_parameters(parameters); // preprocess the query...

	     // perform websearch or other requested action.
	     const char *action = miscutil::lookup(parameters,"action");
	     if (!action)
	       return websearch::cgi_websearch_hp(csp,rsp,parameters);

	     sp_err err = SP_ERR_OK;
	     if (strcmp(action,"expand") == 0 || strcmp(action,"page") == 0)
	       err = img_websearch::perform_img_websearch(csp,rsp,parameters);
#ifdef FEATURE_OPENCV2
	     else if (strcmp(action,"similarity") == 0)
	       err = img_websearch::cgi_img_websearch_similarity(csp,rsp,parameters);
#endif
	     return err;
	  }
	else return SP_ERR_OK;
     }

#ifdef FEATURE_OPENCV2
   sp_err img_websearch::cgi_img_websearch_similarity(client_state *csp, http_response *rsp,
						      const hash_map<const char*, const char*, hash<const char*>, eqstr> *parameters)
     {
	static std::string tmpl_name = "templates/seeks_result_template.html";

	if (!parameters->empty())
	  {
	     img_query_context *qc = dynamic_cast<img_query_context*>(websearch::lookup_qc(parameters,csp,_active_img_qcontexts));

	     if (!qc)
	       {
		  // no cache, (re)do the websearch first.
		  sp_err err = img_websearch::perform_img_websearch(csp,rsp,parameters,false);
		  if (err != SP_ERR_OK)
		    return err;
		  qc = dynamic_cast<img_query_context*>(websearch::lookup_qc(parameters,csp,_active_img_qcontexts));
		  if (!qc)
		    return SP_ERR_MEMORY;
	       }
	     const char *id = miscutil::lookup(parameters,"id");

	     seeks_proxy::mutex_lock(&qc->_qc_mutex);
	     qc->_lock = true;
	     img_search_snippet *ref_sp = NULL;

	     img_sort_rank::score_and_sort_by_similarity(qc,id,ref_sp,qc->_cached_snippets);

	     if (!ref_sp)
	       {
		  qc->_lock = false;
		  seeks_proxy::mutex_unlock(&qc->_qc_mutex);
		  return SP_ERR_OK;
	       }

	     const char *output = miscutil::lookup(parameters,"output");
	     sp_err err = SP_ERR_OK;
	     if (!output || strcmp(output,"html")==0)
	       {
		  // sets the number of images per page, if not already set.
		  const char *rpp = miscutil::lookup(parameters,"rpp");
		  if (!rpp)
		    {
		       miscutil::add_map_entry(const_cast<hash_map<const char*,const char*,hash<const char*>,eqstr>*>(parameters),"rpp",1,
					       miscutil::to_string(img_websearch_configuration::_img_wconfig->_N).c_str(),1);
		    }
		  err = static_renderer::render_result_page_static(qc->_cached_snippets,
								   csp,rsp,parameters,qc,
								   (seeks_proxy::_datadir.empty() ? plugin_manager::_plugin_repository + tmpl_name
								    : std::string(seeks_proxy::_datadir) + "plugins/img_websearch/" + tmpl_name),
								   "/search_img?");
	       }
	     else
	       {
		  csp->_content_type = CT_JSON;
		  err = json_renderer::render_json_results(qc->_cached_snippets,
							   csp,rsp,parameters,qc,0.0);
		  qc->_lock = false;
		  seeks_proxy::mutex_unlock(&qc->_qc_mutex);
		  return err;
	       }

	     // reset scores.
	     std::vector<search_snippet*>::iterator vit = qc->_cached_snippets.begin();
	     while(vit!=qc->_cached_snippets.end())
	       {
		  (*vit)->_seeks_ir = 0;
		  ++vit;
	       }
	     ref_sp->set_similarity_link(); // reset sim_link.
	     qc->_lock = false;
	     seeks_proxy::mutex_unlock(&qc->_qc_mutex);
	     return err;
	  }
	else return SP_ERR_OK;
     }
#endif

   // internal functions.
   sp_err img_websearch::perform_img_websearch(client_state *csp,
					       http_response *rsp,
					       const hash_map<const char*,const char*,hash<const char*>,eqstr> *parameters,
					       bool render)
     {
	static std::string tmpl_name = "templates/seeks_result_template.html";

	// time measure, returned.
	// XXX: not sure whether it is functional on all *nix platforms.
	struct tms st_cpu;
	struct tms en_cpu;
	clock_t start_time = times(&st_cpu);

	// lookup a cached context for the incoming query.
	query_context *vqc = websearch::lookup_qc(parameters,csp,_active_img_qcontexts);
	img_query_context *qc = NULL;
	if (vqc)
	  qc = dynamic_cast<img_query_context*>(vqc);

	// check whether search is expanding or the user is leafing through pages.
	const char *action = miscutil::lookup(parameters,"action");

	// expansion: we fetch more pages from every search engine.
	bool expanded = false;
	if (qc) // we already had a context for this query.
	  {
	     if (strcmp(action,"expand") == 0)
	       {
		  expanded = true;

		  seeks_proxy::mutex_lock(&qc->_qc_mutex);
		  qc->_lock = true;
		  qc->generate(csp,rsp,parameters,expanded);
		  qc->_lock = false;
		  seeks_proxy::mutex_unlock(&qc->_qc_mutex);
	       }
	     else if (strcmp(action,"page") == 0)
	       {
		  // XXX: update other parameters, as needed, qc vs parameters.
		  qc->update_parameters(const_cast<hash_map<const char*,const char*,hash<const char*>,eqstr>*>(parameters));
	       }
	  }
	else
	  {
	     // new context, whether we're expanding or not doesn't matter, we need
	     // to generate snippets first.
	     expanded = true;
	     qc = new img_query_context(parameters,csp->_headers);
	     qc->register_qc();
	     seeks_proxy::mutex_lock(&qc->_qc_mutex);
	     qc->_lock = true;
	     qc->generate(csp,rsp,parameters,expanded);
	     qc->_lock = false;
	     seeks_proxy::mutex_unlock(&qc->_qc_mutex);
	  }

	// sort and rank search snippets.
	if (expanded)
	  {
	     seeks_proxy::mutex_lock(&qc->_qc_mutex);
	     qc->_lock = true;
	     img_sort_rank::sort_rank_and_merge_snippets(qc,qc->_cached_snippets);
	     qc->_compute_tfidf_features = true;
	     qc->_lock = false;
	     seeks_proxy::mutex_unlock(&qc->_qc_mutex);
	  }

	// rendering.
	seeks_proxy::mutex_lock(&qc->_qc_mutex);
	qc->_lock = true;

	// time measured before rendering, since we need to write it down.
	clock_t end_time = times(&en_cpu);
	double qtime = (end_time-start_time)/websearch::_cl_sec;
	if (qtime<0)
	  qtime = -1.0; // unavailable.

	if (!render)
	  {
	     qc->_lock = false;
	     seeks_proxy::mutex_unlock(&qc->_qc_mutex);
	     return SP_ERR_OK;
	  }
	const char *output = miscutil::lookup(parameters,"output");
	sp_err err = SP_ERR_OK;
	if (!output || strcmp(output,"html")==0)
	  {
	     // sets the number of images per page, if not already set.
	     const char *rpp = miscutil::lookup(parameters,"rpp");
	     if (!rpp)
	       {
		  miscutil::add_map_entry(const_cast<hash_map<const char*,const char*,hash<const char*>,eqstr>*>(parameters),"rpp",1,
					  miscutil::to_string(img_websearch_configuration::_img_wconfig->_N).c_str(),1);
	       }
	     err = static_renderer::render_result_page_static(qc->_cached_snippets,
							      csp,rsp,parameters,qc,
							      (seeks_proxy::_datadir.empty() ? plugin_manager::_plugin_repository + tmpl_name
							       : std::string(seeks_proxy::_datadir) + "plugins/img_websearch/" + tmpl_name),
							      "/search_img?");
	  }
	else
	  {
	     csp->_content_type = CT_JSON;
	     err = json_renderer::render_json_results(qc->_cached_snippets,
						      csp,rsp,parameters,qc,
						      qtime);
	  }

	// unlock or destroy the query context.
	qc->_lock = false;
	seeks_proxy::mutex_unlock(&qc->_qc_mutex);
	/* if (qc->empty())
	  delete qc; */

	return err;
     }

   /* auto-registration. */
   extern "C"
     {
	plugin* makeiw()
	  {
	     return new img_websearch;
	  }
     }

   class proxy_autoiw
     {
      public:
	proxy_autoiw()
	  {
	     plugin_manager::_factory["image-websearch"] = makeiw;
	  }
     };

   proxy_autoiw _p; // one instance, instanciated when dl-opening.

} /* end of namespace. */
<commit_msg>immediate deletion of image query context when no results<commit_after>/**
 * The Seeks proxy and plugin framework are part of the SEEKS project.
 * Copyright (C) 2010 Emmanuel Benazera, juban@free.fr
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "img_websearch.h"
#include "img_sort_rank.h"
#include "websearch.h" // websearch plugin.
#include "errlog.h"
#include "cgi.h"
#include "cgisimple.h"
#include "miscutil.h"
#include "json_renderer.h"
#include "static_renderer.h"
#include "seeks_proxy.h"

#include <unistd.h>
#include <sys/stat.h>
#include <sys/times.h>

using namespace sp;

namespace seeks_plugins
{
   img_websearch_configuration* img_websearch::_iwconfig = NULL;
   hash_map<uint32_t,query_context*,id_hash_uint> img_websearch::_active_img_qcontexts
     = hash_map<uint32_t,query_context*,id_hash_uint>();

   img_websearch::img_websearch()
     : plugin()
       {
	  _name = "img_websearch";
	  _version_major = "0";
	  _version_minor = "1";

	  // config filename.
	  if (seeks_proxy::_datadir.empty())
	    _config_filename = plugin_manager::_plugin_repository + "img_websearch/img-websearch-config";
	  else _config_filename = seeks_proxy::_datadir + "/plugins/img_websearch/img-websearch-config";

#ifdef SEEKS_CONFIGDIR
	  struct stat stFileInfo;
	  if (!stat(_config_filename.c_str(), &stFileInfo)  == 0)
	    {
	       _config_filename = SEEKS_CONFIGDIR "/img-websearch-config";
	    }
#endif

	  if (img_websearch::_iwconfig == NULL)
	    img_websearch::_iwconfig = new img_websearch_configuration(_config_filename);

	  // cgi dispatchers.
	  _cgi_dispatchers.reserve(2);

	  cgi_dispatcher *cgid_wb_seeks_img_search_css
	    = new cgi_dispatcher("seeks_img_search.css", &img_websearch::cgi_img_websearch_search_css, NULL, TRUE);
	  _cgi_dispatchers.push_back(cgid_wb_seeks_img_search_css);

	  cgi_dispatcher *cgid_img_wb_search
	    = new cgi_dispatcher("search_img", &img_websearch::cgi_img_websearch_search, NULL, TRUE);
	  _cgi_dispatchers.push_back(cgid_img_wb_search);
       }

   img_websearch::~img_websearch()
     {
     }

   // CGI calls.
   sp_err img_websearch::cgi_img_websearch_search_css(client_state *csp,
						      http_response *rsp,
						      const hash_map<const char*, const char*, hash<const char*>, eqstr> *parameters)
     {
	assert(csp);
	assert(rsp);
	assert(parameters);

	std::string seeks_search_css_str = "img_websearch/templates/css/seeks_img_search.css";
	hash_map<const char*,const char*,hash<const char*>,eqstr> *exports
	  = static_renderer::websearch_exports(csp);
	csp->_content_type = CT_CSS;
	sp_err err = cgi::template_fill_for_cgi_str(csp,seeks_search_css_str.c_str(),
						    (seeks_proxy::_datadir.empty() ? plugin_manager::_plugin_repository.c_str()
						     : std::string(seeks_proxy::_datadir + "plugins/").c_str()),
						    exports,rsp);
	if (err != SP_ERR_OK)
	  {
	     errlog::log_error(LOG_LEVEL_ERROR, "Could not load seeks_img_search.css");
	  }
	rsp->_is_static = 1;

	return SP_ERR_OK;
     }

   sp_err img_websearch::cgi_img_websearch_search(client_state *csp,
						  http_response *rsp,
						  const hash_map<const char*,const char*,hash<const char*>,eqstr> *parameters)
     {
	if (!parameters->empty())
	  {
	     const char *query = miscutil::lookup(parameters,"q"); // grab the query.
	     if (!query || strlen(query) == 0)
	       {
		  // return websearch homepage instead.
		  return websearch::cgi_websearch_hp(csp,rsp,parameters);
	       }
	     else se_handler::preprocess_parameters(parameters); // preprocess the query...

	     // perform websearch or other requested action.
	     const char *action = miscutil::lookup(parameters,"action");
	     if (!action)
	       return websearch::cgi_websearch_hp(csp,rsp,parameters);

	     sp_err err = SP_ERR_OK;
	     if (strcmp(action,"expand") == 0 || strcmp(action,"page") == 0)
	       err = img_websearch::perform_img_websearch(csp,rsp,parameters);
#ifdef FEATURE_OPENCV2
	     else if (strcmp(action,"similarity") == 0)
	       err = img_websearch::cgi_img_websearch_similarity(csp,rsp,parameters);
#endif
	     return err;
	  }
	else return SP_ERR_OK;
     }

#ifdef FEATURE_OPENCV2
   sp_err img_websearch::cgi_img_websearch_similarity(client_state *csp, http_response *rsp,
						      const hash_map<const char*, const char*, hash<const char*>, eqstr> *parameters)
     {
	static std::string tmpl_name = "templates/seeks_result_template.html";

	if (!parameters->empty())
	  {
	     img_query_context *qc = dynamic_cast<img_query_context*>(websearch::lookup_qc(parameters,csp,_active_img_qcontexts));

	     if (!qc)
	       {
		  // no cache, (re)do the websearch first.
		  sp_err err = img_websearch::perform_img_websearch(csp,rsp,parameters,false);
		  if (err != SP_ERR_OK)
		    return err;
		  qc = dynamic_cast<img_query_context*>(websearch::lookup_qc(parameters,csp,_active_img_qcontexts));
		  if (!qc)
		    return SP_ERR_MEMORY;
	       }
	     const char *id = miscutil::lookup(parameters,"id");

	     seeks_proxy::mutex_lock(&qc->_qc_mutex);
	     qc->_lock = true;
	     img_search_snippet *ref_sp = NULL;

	     img_sort_rank::score_and_sort_by_similarity(qc,id,ref_sp,qc->_cached_snippets);

	     if (!ref_sp)
	       {
		  qc->_lock = false;
		  seeks_proxy::mutex_unlock(&qc->_qc_mutex);
		  return SP_ERR_OK;
	       }

	     const char *output = miscutil::lookup(parameters,"output");
	     sp_err err = SP_ERR_OK;
	     if (!output || strcmp(output,"html")==0)
	       {
		  // sets the number of images per page, if not already set.
		  const char *rpp = miscutil::lookup(parameters,"rpp");
		  if (!rpp)
		    {
		       miscutil::add_map_entry(const_cast<hash_map<const char*,const char*,hash<const char*>,eqstr>*>(parameters),"rpp",1,
					       miscutil::to_string(img_websearch_configuration::_img_wconfig->_N).c_str(),1);
		    }
		  err = static_renderer::render_result_page_static(qc->_cached_snippets,
								   csp,rsp,parameters,qc,
								   (seeks_proxy::_datadir.empty() ? plugin_manager::_plugin_repository + tmpl_name
								    : std::string(seeks_proxy::_datadir) + "plugins/img_websearch/" + tmpl_name),
								   "/search_img?");
	       }
	     else
	       {
		  csp->_content_type = CT_JSON;
		  err = json_renderer::render_json_results(qc->_cached_snippets,
							   csp,rsp,parameters,qc,0.0);
		  qc->_lock = false;
		  seeks_proxy::mutex_unlock(&qc->_qc_mutex);
		  return err;
	       }

	     // reset scores.
	     std::vector<search_snippet*>::iterator vit = qc->_cached_snippets.begin();
	     while(vit!=qc->_cached_snippets.end())
	       {
		  (*vit)->_seeks_ir = 0;
		  ++vit;
	       }
	     ref_sp->set_similarity_link(); // reset sim_link.
	     qc->_lock = false;
	     seeks_proxy::mutex_unlock(&qc->_qc_mutex);
	     return err;
	  }
	else return SP_ERR_OK;
     }
#endif

   // internal functions.
   sp_err img_websearch::perform_img_websearch(client_state *csp,
					       http_response *rsp,
					       const hash_map<const char*,const char*,hash<const char*>,eqstr> *parameters,
					       bool render)
     {
	static std::string tmpl_name = "templates/seeks_result_template.html";

	// time measure, returned.
	// XXX: not sure whether it is functional on all *nix platforms.
	struct tms st_cpu;
	struct tms en_cpu;
	clock_t start_time = times(&st_cpu);

	// lookup a cached context for the incoming query.
	query_context *vqc = websearch::lookup_qc(parameters,csp,_active_img_qcontexts);
	img_query_context *qc = NULL;
	if (vqc)
	  qc = dynamic_cast<img_query_context*>(vqc);

	// check whether search is expanding or the user is leafing through pages.
	const char *action = miscutil::lookup(parameters,"action");

	// expansion: we fetch more pages from every search engine.
	bool expanded = false;
	if (qc) // we already had a context for this query.
	  {
	     if (strcmp(action,"expand") == 0)
	       {
		  expanded = true;

		  seeks_proxy::mutex_lock(&qc->_qc_mutex);
		  qc->_lock = true;
		  qc->generate(csp,rsp,parameters,expanded);
		  qc->_lock = false;
		  seeks_proxy::mutex_unlock(&qc->_qc_mutex);
	       }
	     else if (strcmp(action,"page") == 0)
	       {
		  // XXX: update other parameters, as needed, qc vs parameters.
		  qc->update_parameters(const_cast<hash_map<const char*,const char*,hash<const char*>,eqstr>*>(parameters));
	       }
	  }
	else
	  {
	     // new context, whether we're expanding or not doesn't matter, we need
	     // to generate snippets first.
	     expanded = true;
	     qc = new img_query_context(parameters,csp->_headers);
	     qc->register_qc();
	     seeks_proxy::mutex_lock(&qc->_qc_mutex);
	     qc->_lock = true;
	     qc->generate(csp,rsp,parameters,expanded);
	     qc->_lock = false;
	     seeks_proxy::mutex_unlock(&qc->_qc_mutex);
	  }

	// sort and rank search snippets.
	if (expanded)
	  {
	     seeks_proxy::mutex_lock(&qc->_qc_mutex);
	     qc->_lock = true;
	     img_sort_rank::sort_rank_and_merge_snippets(qc,qc->_cached_snippets);
	     qc->_compute_tfidf_features = true;
	     qc->_lock = false;
	     seeks_proxy::mutex_unlock(&qc->_qc_mutex);
	  }

	// rendering.
	seeks_proxy::mutex_lock(&qc->_qc_mutex);
	qc->_lock = true;

	// time measured before rendering, since we need to write it down.
	clock_t end_time = times(&en_cpu);
	double qtime = (end_time-start_time)/websearch::_cl_sec;
	if (qtime<0)
	  qtime = -1.0; // unavailable.

	if (!render)
	  {
	     qc->_lock = false;
	     seeks_proxy::mutex_unlock(&qc->_qc_mutex);
	     return SP_ERR_OK;
	  }
	const char *output = miscutil::lookup(parameters,"output");
	sp_err err = SP_ERR_OK;
	if (!output || strcmp(output,"html")==0)
	  {
	     // sets the number of images per page, if not already set.
	     const char *rpp = miscutil::lookup(parameters,"rpp");
	     if (!rpp)
	       {
		  miscutil::add_map_entry(const_cast<hash_map<const char*,const char*,hash<const char*>,eqstr>*>(parameters),"rpp",1,
					  miscutil::to_string(img_websearch_configuration::_img_wconfig->_N).c_str(),1);
	       }
	     err = static_renderer::render_result_page_static(qc->_cached_snippets,
							      csp,rsp,parameters,qc,
							      (seeks_proxy::_datadir.empty() ? plugin_manager::_plugin_repository + tmpl_name
							       : std::string(seeks_proxy::_datadir) + "plugins/img_websearch/" + tmpl_name),
							      "/search_img?");
	  }
	else
	  {
	     csp->_content_type = CT_JSON;
	     err = json_renderer::render_json_results(qc->_cached_snippets,
						      csp,rsp,parameters,qc,
						      qtime);
	  }

	// unlock or destroy the query context.
	qc->_lock = false;
	seeks_proxy::mutex_unlock(&qc->_qc_mutex);
	sweeper::unregister_sweepable(qc);
	if (qc->empty())
	  delete qc;

	return err;
     }

   /* auto-registration. */
   extern "C"
     {
	plugin* makeiw()
	  {
	     return new img_websearch;
	  }
     }

   class proxy_autoiw
     {
      public:
	proxy_autoiw()
	  {
	     plugin_manager::_factory["image-websearch"] = makeiw;
	  }
     };

   proxy_autoiw _p; // one instance, instanciated when dl-opening.

} /* end of namespace. */
<|endoftext|>


<commit_before>/*
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 1999-2000 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. 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.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Xerces" and "Apache Software Foundation" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact apache\@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    nor may "Apache" appear in their name, without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 APACHE SOFTWARE FOUNDATION OR
 * ITS 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.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation, and was
 * originally based on software copyright (c) 1999, International
 * Business Machines, Inc., http://www.ibm.com .  For more information
 * on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */

/*
 * $Id$
 */

// ---------------------------------------------------------------------------
//  Includes
// ---------------------------------------------------------------------------
#include <util/XMLUniDefs.hpp>
#include <util/XMLUni.hpp>
#include "IconvTransService.hpp"
#include <wchar.h>
#if defined (XML_GCC) || defined (XML_PTX) || defined (XML_IBMVAOS2)
#include <wctype.h>
#endif
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

// ---------------------------------------------------------------------------
//  Local, const data
// ---------------------------------------------------------------------------
static const int    gTempBuffArraySize = 1024;
static const XMLCh  gMyServiceId[] =
{
    chLatin_I, chLatin_C, chLatin_o, chLatin_n, chLatin_v, chNull
};

// ---------------------------------------------------------------------------
//  Local methods
// ---------------------------------------------------------------------------
static unsigned int getWideCharLength(const XMLCh* const src)
{
    if (!src)
        return 0;

    unsigned int len = 0;
    const XMLCh* pTmp = src;
    while (*pTmp++)
        len++;
    return len;
}

// ---------------------------------------------------------------------------
//  IconvTransService: Constructors and Destructor
// ---------------------------------------------------------------------------
IconvTransService::IconvTransService()
{
}

IconvTransService::~IconvTransService()
{
}

// ---------------------------------------------------------------------------
//  IconvTransService: The virtual transcoding service API
// ---------------------------------------------------------------------------
int IconvTransService::compareIString(  const   XMLCh* const    comp1
                                        , const XMLCh* const    comp2)
{
    const XMLCh* cptr1 = comp1;
    const XMLCh* cptr2 = comp2;

    while ( (*cptr1 != 0) && (*cptr2 != 0) )
    {
        wint_t wch1 = towupper(*cptr1);
        wint_t wch2 = towupper(*cptr2);
        if (wch1 != wch2)
            break;

        cptr1++;
        cptr2++;
    }
    return (int) ( towupper(*cptr1) - towupper(*cptr2) );
}

int IconvTransService::compareNIString( const   XMLCh* const    comp1
                                        , const XMLCh* const    comp2
                                        , const unsigned int    maxChars)
{
    unsigned int  n = 0;
    const XMLCh* cptr1 = comp1;
    const XMLCh* cptr2 = comp2;

    while ( (*cptr1 != 0) && (*cptr2 != 0) && (n < maxChars) )
    {
        wint_t wch1 = towupper(*cptr1);
        wint_t wch2 = towupper(*cptr2);
        if (wch1 != wch2)
            break;

        cptr1++;
        cptr2++;
        n++;
    }
    return (int) ( towupper(*cptr1) - towupper(*cptr2) );
}

const XMLCh* IconvTransService::getId() const
{
    return gMyServiceId;
}

bool IconvTransService::isSpace(const XMLCh toCheck) const
{
    return (iswspace(toCheck) != 0);
}

XMLLCPTranscoder* IconvTransService::makeNewLCPTranscoder()
{
    // Just allocate a new transcoder of our type
    return new IconvLCPTranscoder;
}

bool IconvTransService::supportsSrcOfs() const
{
    return true;
}

// ---------------------------------------------------------------------------
//  IconvTransService: The protected virtual transcoding service API
// ---------------------------------------------------------------------------
XMLTranscoder*
IconvTransService::makeNewXMLTranscoder(const   XMLCh* const            encodingName
                                        ,       XMLTransService::Codes& resValue
                                        , const unsigned int            )
{
    //
    //  NOTE: We don't use the block size here
    //
    //  This is a minimalist transcoding service, that only supports a local
    //  default transcoder. All named encodings return zero as a failure,
    //  which means that only the intrinsic encodings supported by the parser
    //  itself will work for XML data.
    //
    resValue = XMLTransService::UnsupportedEncoding;
    return 0;
}

void IconvTransService::upperCase(XMLCh* const toUpperCase) const
{
    XMLCh* outPtr = toUpperCase;
    while (*outPtr)
    {
        *outPtr = towupper(*outPtr);
        outPtr++;
    }
}

// ---------------------------------------------------------------------------
//  IconvLCPTranscoder: The virtual transcoder API
// ---------------------------------------------------------------------------
unsigned int IconvLCPTranscoder::calcRequiredSize(const char* const srcText)
{
    if (!srcText)
        return 0;

    const unsigned int retVal = ::mbstowcs(NULL, srcText, 0);

    if (retVal == ~0)
        return 0;
    return retVal;
}

unsigned int IconvLCPTranscoder::calcRequiredSize(const XMLCh* const srcText)
{
    if (!srcText)
        return 0;

    unsigned int  wLent = getWideCharLength(srcText);
    wchar_t       tmpWideCharArr[gTempBuffArraySize];
    wchar_t*      allocatedArray = 0;
    wchar_t*      wideCharBuf = 0;

    if (wLent >= gTempBuffArraySize)
        wideCharBuf = allocatedArray = new wchar_t[wLent + 1];
    else
        wideCharBuf = tmpWideCharArr;

    for (unsigned int i = 0; i < wLent; i++)
    {
        wideCharBuf[i] = srcText[i];
    }
    wideCharBuf[wLent] = 0x00;

    const unsigned int retVal = ::wcstombs(NULL, wideCharBuf, 0);
    delete [] allocatedArray;

    if (retVal == ~0)
        return 0;
    return retVal;
}

char* IconvLCPTranscoder::transcode(const XMLCh* const toTranscode)
{
    if (!toTranscode)
        return 0;

    char* retVal = 0;
    if (toTranscode)
    {
        unsigned int  wLent = getWideCharLength(toTranscode);

        wchar_t       tmpWideCharArr[gTempBuffArraySize];
        wchar_t*      allocatedArray = 0;
        wchar_t*      wideCharBuf = 0;

        if (wLent >= gTempBuffArraySize)
            wideCharBuf = allocatedArray = new wchar_t[wLent + 1];
        else
            wideCharBuf = tmpWideCharArr;

        for (unsigned int i = 0; i < wLent; i++)
        {
            wideCharBuf[i] = toTranscode[i];
        }
        wideCharBuf[wLent] = 0x00;

        // Calc the needed size.
        const size_t neededLen = ::wcstombs(NULL, wideCharBuf, 0);
        if (neededLen == -1)
        {
            delete [] allocatedArray;
            return 0;
        }

        retVal = new char[neededLen + 1];
        ::wcstombs(retVal, wideCharBuf, neededLen);
        retVal[neededLen] = 0;
        delete [] allocatedArray;
    }
    else
    {
        retVal = new char[1];
        retVal[0] = 0;
    }
    return retVal;
}

bool IconvLCPTranscoder::transcode( const   XMLCh* const    toTranscode
                                    ,       char* const     toFill
                                    , const unsigned int    maxBytes)
{
    // Watch for a couple of pyscho corner cases
    if (!toTranscode || !maxBytes)
    {
        toFill[0] = 0;
        return true;
    }

    if (!*toTranscode)
    {
        toFill[0] = 0;
        return true;
    }

    wchar_t       tmpWideCharArr[gTempBuffArraySize];
    wchar_t*      allocatedArray = 0;
    wchar_t*      wideCharBuf = 0;

    if (maxBytes >= gTempBuffArraySize)
        wideCharBuf = allocatedArray = new wchar_t[maxBytes + 1];
    else
        wideCharBuf = tmpWideCharArr;

    for (unsigned int i = 0; i < maxBytes; i++)
    {
        wideCharBuf[i] = toTranscode[i];
    }
    wideCharBuf[maxBytes] = 0x00;

    // Ok, go ahead and try the transcoding. If it fails, then ...
    if (::wcstombs(toFill, wideCharBuf, maxBytes) == -1)
    {
        delete [] allocatedArray;
        return false;
    }

    // Cap it off just in case
    toFill[maxBytes] = 0;
    delete [] allocatedArray;
    return true;
}

XMLCh* IconvLCPTranscoder::transcode(const char* const toTranscode)
{
    XMLCh* retVal = 0;
    if (toTranscode)
    {
        const unsigned int len = calcRequiredSize(toTranscode);
        if (len == 0)
        {
            retVal = new XMLCh[1];
            retVal[0] = 0;
            return retVal;
        }

        wchar_t       tmpWideCharArr[gTempBuffArraySize];
        wchar_t*      allocatedArray = 0;
        wchar_t*      wideCharBuf = 0;

        if (len >= gTempBuffArraySize)
            wideCharBuf = allocatedArray = new wchar_t[len + 1];
        else
            wideCharBuf = tmpWideCharArr;

        ::mbstowcs(wideCharBuf, toTranscode, len);
        retVal = new XMLCh[len + 1];
        for (unsigned int i = 0; i < len; i++)
        {
            retVal[i] = (XMLCh) wideCharBuf[i];
        }
        retVal[len] = 0x00;
        delete [] allocatedArray;
    }
    else
    {
        retVal = new XMLCh[1];
        retVal[0] = 0;
    }
    return retVal;
}

bool IconvLCPTranscoder::transcode( const   char* const     toTranscode
                                    ,       XMLCh* const    toFill
                                    , const unsigned int    maxChars)
{
    // Check for a couple of psycho corner cases
    if (!toTranscode || !maxChars)
    {
        toFill[0] = 0;
        return true;
    }

    if (!*toTranscode)
    {
        toFill[0] = 0;
        return true;
    }

    wchar_t       tmpWideCharArr[gTempBuffArraySize];
    wchar_t*      allocatedArray = 0;
    wchar_t*      wideCharBuf = 0;

    if (maxChars >= gTempBuffArraySize)
        wideCharBuf = allocatedArray = new wchar_t[maxChars + 1];
    else
        wideCharBuf = tmpWideCharArr;

    if (::mbstowcs(wideCharBuf, toTranscode, maxChars) == -1)
    {
        delete [] allocatedArray;
        return false;
    }

    for (unsigned int i = 0; i < maxChars; i++)
    {
        toFill[i] = (XMLCh) wideCharBuf[i];
    }
    toFill[maxChars] = 0x00;
    delete [] allocatedArray;
    return true;
}

// ---------------------------------------------------------------------------
//  IconvLCPTranscoder: Constructors and Destructor
// ---------------------------------------------------------------------------
IconvLCPTranscoder::IconvLCPTranscoder()
{
}

IconvLCPTranscoder::~IconvLCPTranscoder()
{
}

// ---------------------------------------------------------------------------
//  IconvTranscoder: Constructors and Destructor
// ---------------------------------------------------------------------------
IconvTranscoder::IconvTranscoder(const  XMLCh* const    encodingName
                                , const unsigned int    blockSize) :

    XMLTranscoder(encodingName, blockSize)
{
}

IconvTranscoder::~IconvTranscoder()
{
}

// ---------------------------------------------------------------------------
//  IconvTranscoder: Implementation of the virtual transcoder API
// ---------------------------------------------------------------------------
XMLCh IconvTranscoder::transcodeOne(const   XMLByte* const  srcData
                                    , const unsigned int    srcBytes
                                    ,       unsigned int&   bytesEaten)
{
    wchar_t  toFill;
    int eaten = ::mbtowc(&toFill, (const char*)srcData, srcBytes);
    if (eaten == -1)
    {
        bytesEaten = 0;
        return 0;
    }

    // Return the bytes we ate and the resulting char.
    bytesEaten = eaten;
    return toFill;
}

unsigned int
IconvTranscoder::transcodeXML(  const   XMLByte* const          srcData
                                , const unsigned int            srcCount
                                ,       XMLCh* const            toFill
                                , const unsigned int            maxChars
                                ,       unsigned int&           bytesEaten
                                ,       unsigned char* const    charSizes)
{
    //
    //  For this one, because we have to maintain the offset table, we have
    //  to do them one char at a time until we run out of source data.
    //
    unsigned int countIn = 0;
    unsigned int countOut = 0;
    while (countOut < maxChars)
    {
        wchar_t   oneWideChar;
        const int bytesEaten =
            ::mbtowc(&oneWideChar, (const char*)&srcData[countIn], srcCount - countIn);

        // We are done, so break out
        if (bytesEaten == -1)
            break;
        toFill[countOut] = (XMLCh) oneWideChar;
        countIn += (unsigned int) bytesEaten;
        countOut++;
    }

    // Give back the counts of eaten and transcoded
    bytesEaten = countIn;
    return countOut;
}
<commit_msg>Bug fix -  handle maxBytes > length(toTranscode)<commit_after>/*
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 1999-2000 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. 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.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Xerces" and "Apache Software Foundation" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact apache\@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    nor may "Apache" appear in their name, without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 APACHE SOFTWARE FOUNDATION OR
 * ITS 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.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation, and was
 * originally based on software copyright (c) 1999, International
 * Business Machines, Inc., http://www.ibm.com .  For more information
 * on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */

/*
 * $Id$
 */

// ---------------------------------------------------------------------------
//  Includes
// ---------------------------------------------------------------------------
#include <util/XMLUniDefs.hpp>
#include <util/XMLUni.hpp>
#include "IconvTransService.hpp"
#include <wchar.h>
#if defined (XML_GCC) || defined (XML_PTX) || defined (XML_IBMVAOS2)
#include <wctype.h>
#endif
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

// ---------------------------------------------------------------------------
//  Local, const data
// ---------------------------------------------------------------------------
static const int    gTempBuffArraySize = 1024;
static const XMLCh  gMyServiceId[] =
{
    chLatin_I, chLatin_C, chLatin_o, chLatin_n, chLatin_v, chNull
};

// ---------------------------------------------------------------------------
//  Local methods
// ---------------------------------------------------------------------------
static unsigned int getWideCharLength(const XMLCh* const src)
{
    if (!src)
        return 0;

    unsigned int len = 0;
    const XMLCh* pTmp = src;
    while (*pTmp++)
        len++;
    return len;
}

// ---------------------------------------------------------------------------
//  IconvTransService: Constructors and Destructor
// ---------------------------------------------------------------------------
IconvTransService::IconvTransService()
{
}

IconvTransService::~IconvTransService()
{
}

// ---------------------------------------------------------------------------
//  IconvTransService: The virtual transcoding service API
// ---------------------------------------------------------------------------
int IconvTransService::compareIString(  const   XMLCh* const    comp1
                                        , const XMLCh* const    comp2)
{
    const XMLCh* cptr1 = comp1;
    const XMLCh* cptr2 = comp2;

    while ( (*cptr1 != 0) && (*cptr2 != 0) )
    {
        wint_t wch1 = towupper(*cptr1);
        wint_t wch2 = towupper(*cptr2);
        if (wch1 != wch2)
            break;

        cptr1++;
        cptr2++;
    }
    return (int) ( towupper(*cptr1) - towupper(*cptr2) );
}

int IconvTransService::compareNIString( const   XMLCh* const    comp1
                                        , const XMLCh* const    comp2
                                        , const unsigned int    maxChars)
{
    unsigned int  n = 0;
    const XMLCh* cptr1 = comp1;
    const XMLCh* cptr2 = comp2;

    while ( (*cptr1 != 0) && (*cptr2 != 0) && (n < maxChars) )
    {
        wint_t wch1 = towupper(*cptr1);
        wint_t wch2 = towupper(*cptr2);
        if (wch1 != wch2)
            break;

        cptr1++;
        cptr2++;
        n++;
    }
    return (int) ( towupper(*cptr1) - towupper(*cptr2) );
}

const XMLCh* IconvTransService::getId() const
{
    return gMyServiceId;
}

bool IconvTransService::isSpace(const XMLCh toCheck) const
{
    return (iswspace(toCheck) != 0);
}

XMLLCPTranscoder* IconvTransService::makeNewLCPTranscoder()
{
    // Just allocate a new transcoder of our type
    return new IconvLCPTranscoder;
}

bool IconvTransService::supportsSrcOfs() const
{
    return true;
}

// ---------------------------------------------------------------------------
//  IconvTransService: The protected virtual transcoding service API
// ---------------------------------------------------------------------------
XMLTranscoder*
IconvTransService::makeNewXMLTranscoder(const   XMLCh* const            encodingName
                                        ,       XMLTransService::Codes& resValue
                                        , const unsigned int            )
{
    //
    //  NOTE: We don't use the block size here
    //
    //  This is a minimalist transcoding service, that only supports a local
    //  default transcoder. All named encodings return zero as a failure,
    //  which means that only the intrinsic encodings supported by the parser
    //  itself will work for XML data.
    //
    resValue = XMLTransService::UnsupportedEncoding;
    return 0;
}

void IconvTransService::upperCase(XMLCh* const toUpperCase) const
{
    XMLCh* outPtr = toUpperCase;
    while (*outPtr)
    {
        *outPtr = towupper(*outPtr);
        outPtr++;
    }
}

// ---------------------------------------------------------------------------
//  IconvLCPTranscoder: The virtual transcoder API
// ---------------------------------------------------------------------------
unsigned int IconvLCPTranscoder::calcRequiredSize(const char* const srcText)
{
    if (!srcText)
        return 0;

    const unsigned int retVal = ::mbstowcs(NULL, srcText, 0);

    if (retVal == ~0)
        return 0;
    return retVal;
}

unsigned int IconvLCPTranscoder::calcRequiredSize(const XMLCh* const srcText)
{
    if (!srcText)
        return 0;

    unsigned int  wLent = getWideCharLength(srcText);
    wchar_t       tmpWideCharArr[gTempBuffArraySize];
    wchar_t*      allocatedArray = 0;
    wchar_t*      wideCharBuf = 0;

    if (wLent >= gTempBuffArraySize)
        wideCharBuf = allocatedArray = new wchar_t[wLent + 1];
    else
        wideCharBuf = tmpWideCharArr;

    for (unsigned int i = 0; i < wLent; i++)
    {
        wideCharBuf[i] = srcText[i];
    }
    wideCharBuf[wLent] = 0x00;

    const unsigned int retVal = ::wcstombs(NULL, wideCharBuf, 0);
    delete [] allocatedArray;

    if (retVal == ~0)
        return 0;
    return retVal;
}

char* IconvLCPTranscoder::transcode(const XMLCh* const toTranscode)
{
    if (!toTranscode)
        return 0;

    char* retVal = 0;
    if (toTranscode)
    {
        unsigned int  wLent = getWideCharLength(toTranscode);

        wchar_t       tmpWideCharArr[gTempBuffArraySize];
        wchar_t*      allocatedArray = 0;
        wchar_t*      wideCharBuf = 0;

        if (wLent >= gTempBuffArraySize)
            wideCharBuf = allocatedArray = new wchar_t[wLent + 1];
        else
            wideCharBuf = tmpWideCharArr;

        for (unsigned int i = 0; i < wLent; i++)
        {
            wideCharBuf[i] = toTranscode[i];
        }
        wideCharBuf[wLent] = 0x00;

        // Calc the needed size.
        const size_t neededLen = ::wcstombs(NULL, wideCharBuf, 0);
        if (neededLen == -1)
        {
            delete [] allocatedArray;
            return 0;
        }

        retVal = new char[neededLen + 1];
        ::wcstombs(retVal, wideCharBuf, neededLen);
        retVal[neededLen] = 0;
        delete [] allocatedArray;
    }
    else
    {
        retVal = new char[1];
        retVal[0] = 0;
    }
    return retVal;
}

bool IconvLCPTranscoder::transcode( const   XMLCh* const    toTranscode
                                    ,       char* const     toFill
                                    , const unsigned int    maxBytes)
{
    // Watch for a couple of pyscho corner cases
    if (!toTranscode || !maxBytes)
    {
        toFill[0] = 0;
        return true;
    }

    if (!*toTranscode)
    {
        toFill[0] = 0;
        return true;
    }

    unsigned int  wLent = getWideCharLength(toTranscode);
    wchar_t       tmpWideCharArr[gTempBuffArraySize];
    wchar_t*      allocatedArray = 0;
    wchar_t*      wideCharBuf = 0;

    if (wLent > maxBytes) {
        wLent = maxBytes;
    }

    if (maxBytes >= gTempBuffArraySize)
        wideCharBuf = allocatedArray = new wchar_t[maxBytes + 1];
    else
        wideCharBuf = tmpWideCharArr;

    for (unsigned int i = 0; i < wLent; i++)
    {
        wideCharBuf[i] = toTranscode[i];
    }
    wideCharBuf[wLent] = 0x00;

    // Ok, go ahead and try the transcoding. If it fails, then ...
    if (::wcstombs(toFill, wideCharBuf, maxBytes) == -1)
    {
        delete [] allocatedArray;
        return false;
    }

    // Cap it off just in case
    toFill[wLent] = 0;
    delete [] allocatedArray;
    return true;
}

XMLCh* IconvLCPTranscoder::transcode(const char* const toTranscode)
{
    XMLCh* retVal = 0;
    if (toTranscode)
    {
        const unsigned int len = calcRequiredSize(toTranscode);
        if (len == 0)
        {
            retVal = new XMLCh[1];
            retVal[0] = 0;
            return retVal;
        }

        wchar_t       tmpWideCharArr[gTempBuffArraySize];
        wchar_t*      allocatedArray = 0;
        wchar_t*      wideCharBuf = 0;

        if (len >= gTempBuffArraySize)
            wideCharBuf = allocatedArray = new wchar_t[len + 1];
        else
            wideCharBuf = tmpWideCharArr;

        ::mbstowcs(wideCharBuf, toTranscode, len);
        retVal = new XMLCh[len + 1];
        for (unsigned int i = 0; i < len; i++)
        {
            retVal[i] = (XMLCh) wideCharBuf[i];
        }
        retVal[len] = 0x00;
        delete [] allocatedArray;
    }
    else
    {
        retVal = new XMLCh[1];
        retVal[0] = 0;
    }
    return retVal;
}

bool IconvLCPTranscoder::transcode( const   char* const     toTranscode
                                    ,       XMLCh* const    toFill
                                    , const unsigned int    maxChars)
{
    // Check for a couple of psycho corner cases
    if (!toTranscode || !maxChars)
    {
        toFill[0] = 0;
        return true;
    }

    if (!*toTranscode)
    {
        toFill[0] = 0;
        return true;
    }

    unsigned int len = calcRequiredSize(toTranscode);
    wchar_t       tmpWideCharArr[gTempBuffArraySize];
    wchar_t*      allocatedArray = 0;
    wchar_t*      wideCharBuf = 0;

    if (len > maxChars) {
        len = maxChars;
    }

    if (maxChars >= gTempBuffArraySize)
        wideCharBuf = allocatedArray = new wchar_t[maxChars + 1];
    else
        wideCharBuf = tmpWideCharArr;

    if (::mbstowcs(wideCharBuf, toTranscode, maxChars) == -1)
    {
        delete [] allocatedArray;
        return false;
    }

    for (unsigned int i = 0; i < len; i++)
    {
        toFill[i] = (XMLCh) wideCharBuf[i];
    }
    toFill[len] = 0x00;
    delete [] allocatedArray;
    return true;
}

// ---------------------------------------------------------------------------
//  IconvLCPTranscoder: Constructors and Destructor
// ---------------------------------------------------------------------------
IconvLCPTranscoder::IconvLCPTranscoder()
{
}

IconvLCPTranscoder::~IconvLCPTranscoder()
{
}

// ---------------------------------------------------------------------------
//  IconvTranscoder: Constructors and Destructor
// ---------------------------------------------------------------------------
IconvTranscoder::IconvTranscoder(const  XMLCh* const    encodingName
                                , const unsigned int    blockSize) :

    XMLTranscoder(encodingName, blockSize)
{
}

IconvTranscoder::~IconvTranscoder()
{
}

// ---------------------------------------------------------------------------
//  IconvTranscoder: Implementation of the virtual transcoder API
// ---------------------------------------------------------------------------
XMLCh IconvTranscoder::transcodeOne(const   XMLByte* const  srcData
                                    , const unsigned int    srcBytes
                                    ,       unsigned int&   bytesEaten)
{
    wchar_t  toFill;
    int eaten = ::mbtowc(&toFill, (const char*)srcData, srcBytes);
    if (eaten == -1)
    {
        bytesEaten = 0;
        return 0;
    }

    // Return the bytes we ate and the resulting char.
    bytesEaten = eaten;
    return toFill;
}

unsigned int
IconvTranscoder::transcodeXML(  const   XMLByte* const          srcData
                                , const unsigned int            srcCount
                                ,       XMLCh* const            toFill
                                , const unsigned int            maxChars
                                ,       unsigned int&           bytesEaten
                                ,       unsigned char* const    charSizes)
{
    //
    //  For this one, because we have to maintain the offset table, we have
    //  to do them one char at a time until we run out of source data.
    //
    unsigned int countIn = 0;
    unsigned int countOut = 0;
    while (countOut < maxChars)
    {
        wchar_t   oneWideChar;
        const int bytesEaten =
            ::mbtowc(&oneWideChar, (const char*)&srcData[countIn], srcCount - countIn);

        // We are done, so break out
        if (bytesEaten == -1)
            break;
        toFill[countOut] = (XMLCh) oneWideChar;
        countIn += (unsigned int) bytesEaten;
        countOut++;
    }

    // Give back the counts of eaten and transcoded
    bytesEaten = countIn;
    return countOut;
}
<|endoftext|>


<commit_before>/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/

#include "completionwidget.h"
#include "completionsupport.h"
#include "icompletioncollector.h"

#include <texteditor/itexteditable.h>

#include <utils/faketooltip.h>
#include <utils/qtcassert.h>

#include <QtCore/QEvent>
#include <QtGui/QApplication>
#include <QtGui/QDesktopWidget>
#include <QtGui/QKeyEvent>
#include <QtGui/QVBoxLayout>
#include <QtGui/QScrollBar>
#include <QtGui/QLabel>
#include <QtGui/QStylePainter>
#include <QtGui/QToolTip>

#include <limits.h>

using namespace TextEditor;
using namespace TextEditor::Internal;

#define NUMBER_OF_VISIBLE_ITEMS 10

namespace TextEditor {
namespace Internal {

class AutoCompletionModel : public QAbstractListModel
{
public:
    AutoCompletionModel(QObject *parent);

    inline const CompletionItem &itemAt(const QModelIndex &index) const
    { return m_items.at(index.row()); }

    void setItems(const QList<CompletionItem> &items);

    int rowCount(const QModelIndex &parent = QModelIndex()) const;
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;

private:
    QList<CompletionItem> m_items;
};

class CompletionInfoFrame : public Utils::FakeToolTip
{
public:
    CompletionInfoFrame(QWidget *parent = 0) :
        Utils::FakeToolTip(parent),
        m_label(new QLabel(this))
    {
        QVBoxLayout *layout = new QVBoxLayout(this);
        layout->setMargin(0);
        layout->setSpacing(0);
        layout->addWidget(m_label);

        m_label->setForegroundRole(QPalette::ToolTipText);
        m_label->setBackgroundRole(QPalette::ToolTipBase);
    }

    void setText(const QString &text)
    {
        m_label->setText(text);
    }

private:
    QLabel *m_label;
};

} // namespace Internal
} // namespace TextEditor

AutoCompletionModel::AutoCompletionModel(QObject *parent)
    : QAbstractListModel(parent)
{
}

void AutoCompletionModel::setItems(const QList<CompletionItem> &items)
{
    m_items = items;
    reset();
}

int AutoCompletionModel::rowCount(const QModelIndex &) const
{
    return m_items.count();
}

QVariant AutoCompletionModel::data(const QModelIndex &index, int role) const
{
    if (index.row() >= m_items.count())
        return QVariant();

    if (role == Qt::DisplayRole) {
        return itemAt(index).text;
    } else if (role == Qt::DecorationRole) {
        return itemAt(index).icon;
    } else if (role == Qt::WhatsThisRole) {
        return itemAt(index).details;
    }

    return QVariant();
}

CompletionWidget::CompletionWidget(CompletionSupport *support, ITextEditable *editor)
    : QFrame(0, Qt::Popup),
      m_support(support),
      m_editor(editor),
      m_completionListView(new CompletionListView(support, editor, this))
{
    // We disable the frame on this list view and use a QFrame around it instead.
    // This improves the look with QGTKStyle.
#ifndef Q_WS_MAC
    setFrameStyle(m_completionListView->frameStyle());
#endif
    m_completionListView->setFrameStyle(QFrame::NoFrame);

    setObjectName(QLatin1String("m_popupFrame"));
    setAttribute(Qt::WA_DeleteOnClose);
    setMinimumSize(1, 1);

    QVBoxLayout *layout = new QVBoxLayout(this);
    layout->setMargin(0);

    layout->addWidget(m_completionListView);
    setFocusProxy(m_completionListView);

    connect(m_completionListView, SIGNAL(itemSelected(TextEditor::CompletionItem)),
            this, SIGNAL(itemSelected(TextEditor::CompletionItem)));
    connect(m_completionListView, SIGNAL(completionListClosed()),
            this, SIGNAL(completionListClosed()));
    connect(m_completionListView, SIGNAL(activated(QModelIndex)),
            SLOT(closeList(QModelIndex)));
    connect(editor, SIGNAL(contentsChangedBecauseOfUndo()),
            this, SLOT(closeList()));
}

CompletionWidget::~CompletionWidget()
{
}

void CompletionWidget::setQuickFix(bool quickFix)
{
    m_completionListView->setQuickFix(quickFix);
}

void CompletionWidget::setCompletionItems(const QList<TextEditor::CompletionItem> &completionitems)
{
    m_completionListView->setCompletionItems(completionitems);
}

void CompletionWidget::closeList(const QModelIndex &index)
{
    m_completionListView->closeList(index);
    close();
}

void CompletionWidget::showCompletions(int startPos)
{
    ensurePolished();
    updatePositionAndSize(startPos);
    show();
    setFocus();
}

void CompletionWidget::updatePositionAndSize(int startPos)
{
    // Determine size by calculating the space of the visible items
    QAbstractItemModel *model = m_completionListView->model();
    int visibleItems = model->rowCount();
    if (visibleItems > NUMBER_OF_VISIBLE_ITEMS)
        visibleItems = NUMBER_OF_VISIBLE_ITEMS;

    const QStyleOptionViewItem &option = m_completionListView->viewOptions();

    QSize shint;
    for (int i = 0; i < visibleItems; ++i) {
        QSize tmp = m_completionListView->itemDelegate()->sizeHint(option, model->index(i, 0));
        if (shint.width() < tmp.width())
            shint = tmp;
    }

    const int fw = frameWidth();
    const int width = shint.width() + fw * 2 + 30;
    const int height = shint.height() * visibleItems + fw * 2;

    // Determine the position, keeping the popup on the screen
    const QRect cursorRect = m_editor->cursorRect(startPos);
    const QDesktopWidget *desktop = QApplication::desktop();

    QWidget *editorWidget = m_editor->widget();

#ifdef Q_WS_MAC
    const QRect screen = desktop->availableGeometry(desktop->screenNumber(editorWidget));
#else
    const QRect screen = desktop->screenGeometry(desktop->screenNumber(editorWidget));
#endif

    QPoint pos = cursorRect.bottomLeft();
    pos.rx() -= 16 + fw;    // Space for the icons

    if (pos.y() + height > screen.bottom())
        pos.setY(cursorRect.top() - height);

    if (pos.x() + width > screen.right())
        pos.setX(screen.right() - width);

    setGeometry(pos.x(), pos.y(), width, height);
}

CompletionListView::CompletionListView(CompletionSupport *support, ITextEditable *editor, CompletionWidget *completionWidget)
    : QListView(completionWidget),
      m_blockFocusOut(false),
      m_quickFix(false),
      m_editor(editor),
      m_editorWidget(editor->widget()),
      m_completionWidget(completionWidget),
      m_model(new AutoCompletionModel(this)),
      m_support(support)
{
    QTC_ASSERT(m_editorWidget, return);

    m_infoTimer.setInterval(1000);
    m_infoTimer.setSingleShot(true);
    connect(&m_infoTimer, SIGNAL(timeout()), SLOT(maybeShowInfoTip()));

    setAttribute(Qt::WA_MacShowFocusRect, false);
    setUniformItemSizes(true);
    setSelectionBehavior(QAbstractItemView::SelectItems);
    setSelectionMode(QAbstractItemView::SingleSelection);
    setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    setMinimumSize(1, 1);
    setModel(m_model);
#ifdef Q_WS_MAC
    if (horizontalScrollBar())
        horizontalScrollBar()->setAttribute(Qt::WA_MacMiniSize);
    if (verticalScrollBar())
        verticalScrollBar()->setAttribute(Qt::WA_MacMiniSize);
#endif
}

CompletionListView::~CompletionListView()
{
}

void CompletionListView::maybeShowInfoTip()
{
    QModelIndex current = currentIndex();
    if (!current.isValid())
        return;
    QString infoTip = current.data(Qt::WhatsThisRole).toString();

    if (infoTip.isEmpty()) {
        delete m_infoFrame.data();
        m_infoTimer.setInterval(200);
        return;
    }

    if (m_infoFrame.isNull())
        m_infoFrame = new CompletionInfoFrame(this);

    QRect r = rectForIndex(current);
    m_infoFrame->move(
            (parentWidget()->mapToGlobal(
                    parentWidget()->rect().topRight())).x() + 3,
            mapToGlobal(r.topRight()).y() - verticalOffset()
            );
    m_infoFrame->setText(infoTip);
    m_infoFrame->adjustSize();
    m_infoFrame->show();
    m_infoFrame->raise();

    m_infoTimer.setInterval(0);
}

void CompletionListView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
{
    QListView::currentChanged(current, previous);
    m_infoTimer.start();
}

bool CompletionListView::event(QEvent *e)
{
    if (m_blockFocusOut)
        return QListView::event(e);

    bool forwardKeys = true;
    if (e->type() == QEvent::FocusOut) {
        QModelIndex index;
#if defined(Q_OS_DARWIN) && ! defined(QT_MAC_USE_COCOA)
        QFocusEvent *fe = static_cast<QFocusEvent *>(e);
        if (fe->reason() == Qt::OtherFocusReason) {
            // Qt/carbon workaround
            // focus out is received before the key press event.
            index = currentIndex();
        }
#endif
        m_completionWidget->closeList(index);
        return true;
    } else if (e->type() == QEvent::ShortcutOverride) {
        QKeyEvent *ke = static_cast<QKeyEvent *>(e);
        switch (ke->key()) {
        case Qt::Key_N:
        case Qt::Key_P:
            // select next/previous completion
            if (ke->modifiers() == Qt::ControlModifier)
            {
                e->accept();
                int change = (ke->key() == Qt::Key_N) ? 1 : -1;
                int nrows = model()->rowCount();
                int row = currentIndex().row();
                int newRow = (row + change + nrows) % nrows;
                if (newRow == row + change || !ke->isAutoRepeat())
                    setCurrentIndex(m_model->index(newRow));
                return true;
            }
        }
    } else if (e->type() == QEvent::KeyPress) {
        QKeyEvent *ke = static_cast<QKeyEvent *>(e);
        switch (ke->key()) {
        case Qt::Key_N:
        case Qt::Key_P:
            // select next/previous completion - so don't pass on to editor
            if (ke->modifiers() == Qt::ControlModifier)
                forwardKeys = false;
            break;

        case Qt::Key_Escape:
            m_completionWidget->closeList();
            return true;

        case Qt::Key_Right:
        case Qt::Key_Left:
            break;

        case Qt::Key_Tab:
        case Qt::Key_Return:
            //independently from style, accept current entry if return is pressed
            if (qApp->focusWidget() == this)
                m_completionWidget->closeList(currentIndex());
            return true;

        case Qt::Key_Up:
            if (!ke->isAutoRepeat()
                && currentIndex().row() == 0) {
                setCurrentIndex(model()->index(model()->rowCount()-1, 0));
                return true;
            }
            forwardKeys = false;
            break;

        case Qt::Key_Down:
            if (!ke->isAutoRepeat()
                && currentIndex().row() == model()->rowCount()-1) {
                setCurrentIndex(model()->index(0, 0));
                return true;
            }
            forwardKeys = false;
            break;

        case Qt::Key_Enter:
        case Qt::Key_PageDown:
        case Qt::Key_PageUp:
            forwardKeys = false;
            break;

        default:
            // if a key is forwarded, completion widget is re-opened and selected item is reset to first,
            // so only forward keys that insert text and refine the completed item
            forwardKeys = !ke->text().isEmpty();
            break;
        }

        if (forwardKeys && ! m_quickFix) {
            m_blockFocusOut = true;
            QApplication::sendEvent(m_editorWidget, e);
            m_blockFocusOut = false;

            // Have the completion support update the list of items
            m_support->autoComplete(m_editor, false);

            return true;
        }
    }
    return QListView::event(e);
}

void CompletionListView::keyboardSearch(const QString &search)
{
    Q_UNUSED(search)
}

void CompletionListView::setQuickFix(bool quickFix)
{
    m_quickFix = quickFix;
}

void CompletionListView::setCompletionItems(const QList<TextEditor::CompletionItem> &completionItems)
{
    m_model->setItems(completionItems);

    // Select the first of the most relevant completion items
    int relevance = INT_MIN;
    int mostRelevantIndex = 0;
    for (int i = 0; i < completionItems.size(); ++i) {
        const CompletionItem &item = completionItems.at(i);
        if (item.relevance > relevance) {
            relevance = item.relevance;
            mostRelevantIndex = i;
        }
    }

    setCurrentIndex(m_model->index(mostRelevantIndex));
}

void CompletionListView::closeList(const QModelIndex &index)
{
    m_blockFocusOut = true;

    if (index.isValid())
        emit itemSelected(m_model->itemAt(index));

    emit completionListClosed();

    m_blockFocusOut = false;
}
<commit_msg>Forward Home and End keys to the editor when completing<commit_after>/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/

#include "completionwidget.h"
#include "completionsupport.h"
#include "icompletioncollector.h"

#include <texteditor/itexteditable.h>

#include <utils/faketooltip.h>
#include <utils/qtcassert.h>

#include <QtCore/QEvent>
#include <QtGui/QApplication>
#include <QtGui/QDesktopWidget>
#include <QtGui/QKeyEvent>
#include <QtGui/QVBoxLayout>
#include <QtGui/QScrollBar>
#include <QtGui/QLabel>
#include <QtGui/QStylePainter>
#include <QtGui/QToolTip>

#include <limits.h>

using namespace TextEditor;
using namespace TextEditor::Internal;

#define NUMBER_OF_VISIBLE_ITEMS 10

namespace TextEditor {
namespace Internal {

class AutoCompletionModel : public QAbstractListModel
{
public:
    AutoCompletionModel(QObject *parent);

    inline const CompletionItem &itemAt(const QModelIndex &index) const
    { return m_items.at(index.row()); }

    void setItems(const QList<CompletionItem> &items);

    int rowCount(const QModelIndex &parent = QModelIndex()) const;
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;

private:
    QList<CompletionItem> m_items;
};

class CompletionInfoFrame : public Utils::FakeToolTip
{
public:
    CompletionInfoFrame(QWidget *parent = 0) :
        Utils::FakeToolTip(parent),
        m_label(new QLabel(this))
    {
        QVBoxLayout *layout = new QVBoxLayout(this);
        layout->setMargin(0);
        layout->setSpacing(0);
        layout->addWidget(m_label);

        m_label->setForegroundRole(QPalette::ToolTipText);
        m_label->setBackgroundRole(QPalette::ToolTipBase);
    }

    void setText(const QString &text)
    {
        m_label->setText(text);
    }

private:
    QLabel *m_label;
};

} // namespace Internal
} // namespace TextEditor

AutoCompletionModel::AutoCompletionModel(QObject *parent)
    : QAbstractListModel(parent)
{
}

void AutoCompletionModel::setItems(const QList<CompletionItem> &items)
{
    m_items = items;
    reset();
}

int AutoCompletionModel::rowCount(const QModelIndex &) const
{
    return m_items.count();
}

QVariant AutoCompletionModel::data(const QModelIndex &index, int role) const
{
    if (index.row() >= m_items.count())
        return QVariant();

    if (role == Qt::DisplayRole) {
        return itemAt(index).text;
    } else if (role == Qt::DecorationRole) {
        return itemAt(index).icon;
    } else if (role == Qt::WhatsThisRole) {
        return itemAt(index).details;
    }

    return QVariant();
}

CompletionWidget::CompletionWidget(CompletionSupport *support, ITextEditable *editor)
    : QFrame(0, Qt::Popup),
      m_support(support),
      m_editor(editor),
      m_completionListView(new CompletionListView(support, editor, this))
{
    // We disable the frame on this list view and use a QFrame around it instead.
    // This improves the look with QGTKStyle.
#ifndef Q_WS_MAC
    setFrameStyle(m_completionListView->frameStyle());
#endif
    m_completionListView->setFrameStyle(QFrame::NoFrame);

    setObjectName(QLatin1String("m_popupFrame"));
    setAttribute(Qt::WA_DeleteOnClose);
    setMinimumSize(1, 1);

    QVBoxLayout *layout = new QVBoxLayout(this);
    layout->setMargin(0);

    layout->addWidget(m_completionListView);
    setFocusProxy(m_completionListView);

    connect(m_completionListView, SIGNAL(itemSelected(TextEditor::CompletionItem)),
            this, SIGNAL(itemSelected(TextEditor::CompletionItem)));
    connect(m_completionListView, SIGNAL(completionListClosed()),
            this, SIGNAL(completionListClosed()));
    connect(m_completionListView, SIGNAL(activated(QModelIndex)),
            SLOT(closeList(QModelIndex)));
    connect(editor, SIGNAL(contentsChangedBecauseOfUndo()),
            this, SLOT(closeList()));
}

CompletionWidget::~CompletionWidget()
{
}

void CompletionWidget::setQuickFix(bool quickFix)
{
    m_completionListView->setQuickFix(quickFix);
}

void CompletionWidget::setCompletionItems(const QList<TextEditor::CompletionItem> &completionitems)
{
    m_completionListView->setCompletionItems(completionitems);
}

void CompletionWidget::closeList(const QModelIndex &index)
{
    m_completionListView->closeList(index);
    close();
}

void CompletionWidget::showCompletions(int startPos)
{
    ensurePolished();
    updatePositionAndSize(startPos);
    show();
    setFocus();
}

void CompletionWidget::updatePositionAndSize(int startPos)
{
    // Determine size by calculating the space of the visible items
    QAbstractItemModel *model = m_completionListView->model();
    int visibleItems = model->rowCount();
    if (visibleItems > NUMBER_OF_VISIBLE_ITEMS)
        visibleItems = NUMBER_OF_VISIBLE_ITEMS;

    const QStyleOptionViewItem &option = m_completionListView->viewOptions();

    QSize shint;
    for (int i = 0; i < visibleItems; ++i) {
        QSize tmp = m_completionListView->itemDelegate()->sizeHint(option, model->index(i, 0));
        if (shint.width() < tmp.width())
            shint = tmp;
    }

    const int fw = frameWidth();
    const int width = shint.width() + fw * 2 + 30;
    const int height = shint.height() * visibleItems + fw * 2;

    // Determine the position, keeping the popup on the screen
    const QRect cursorRect = m_editor->cursorRect(startPos);
    const QDesktopWidget *desktop = QApplication::desktop();

    QWidget *editorWidget = m_editor->widget();

#ifdef Q_WS_MAC
    const QRect screen = desktop->availableGeometry(desktop->screenNumber(editorWidget));
#else
    const QRect screen = desktop->screenGeometry(desktop->screenNumber(editorWidget));
#endif

    QPoint pos = cursorRect.bottomLeft();
    pos.rx() -= 16 + fw;    // Space for the icons

    if (pos.y() + height > screen.bottom())
        pos.setY(cursorRect.top() - height);

    if (pos.x() + width > screen.right())
        pos.setX(screen.right() - width);

    setGeometry(pos.x(), pos.y(), width, height);
}

CompletionListView::CompletionListView(CompletionSupport *support, ITextEditable *editor, CompletionWidget *completionWidget)
    : QListView(completionWidget),
      m_blockFocusOut(false),
      m_quickFix(false),
      m_editor(editor),
      m_editorWidget(editor->widget()),
      m_completionWidget(completionWidget),
      m_model(new AutoCompletionModel(this)),
      m_support(support)
{
    QTC_ASSERT(m_editorWidget, return);

    m_infoTimer.setInterval(1000);
    m_infoTimer.setSingleShot(true);
    connect(&m_infoTimer, SIGNAL(timeout()), SLOT(maybeShowInfoTip()));

    setAttribute(Qt::WA_MacShowFocusRect, false);
    setUniformItemSizes(true);
    setSelectionBehavior(QAbstractItemView::SelectItems);
    setSelectionMode(QAbstractItemView::SingleSelection);
    setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    setMinimumSize(1, 1);
    setModel(m_model);
#ifdef Q_WS_MAC
    if (horizontalScrollBar())
        horizontalScrollBar()->setAttribute(Qt::WA_MacMiniSize);
    if (verticalScrollBar())
        verticalScrollBar()->setAttribute(Qt::WA_MacMiniSize);
#endif
}

CompletionListView::~CompletionListView()
{
}

void CompletionListView::maybeShowInfoTip()
{
    QModelIndex current = currentIndex();
    if (!current.isValid())
        return;
    QString infoTip = current.data(Qt::WhatsThisRole).toString();

    if (infoTip.isEmpty()) {
        delete m_infoFrame.data();
        m_infoTimer.setInterval(200);
        return;
    }

    if (m_infoFrame.isNull())
        m_infoFrame = new CompletionInfoFrame(this);

    QRect r = rectForIndex(current);
    m_infoFrame->move(
            (parentWidget()->mapToGlobal(
                    parentWidget()->rect().topRight())).x() + 3,
            mapToGlobal(r.topRight()).y() - verticalOffset()
            );
    m_infoFrame->setText(infoTip);
    m_infoFrame->adjustSize();
    m_infoFrame->show();
    m_infoFrame->raise();

    m_infoTimer.setInterval(0);
}

void CompletionListView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
{
    QListView::currentChanged(current, previous);
    m_infoTimer.start();
}

bool CompletionListView::event(QEvent *e)
{
    if (m_blockFocusOut)
        return QListView::event(e);

    bool forwardKeys = true;
    if (e->type() == QEvent::FocusOut) {
        QModelIndex index;
#if defined(Q_OS_DARWIN) && ! defined(QT_MAC_USE_COCOA)
        QFocusEvent *fe = static_cast<QFocusEvent *>(e);
        if (fe->reason() == Qt::OtherFocusReason) {
            // Qt/carbon workaround
            // focus out is received before the key press event.
            index = currentIndex();
        }
#endif
        m_completionWidget->closeList(index);
        return true;
    } else if (e->type() == QEvent::ShortcutOverride) {
        QKeyEvent *ke = static_cast<QKeyEvent *>(e);
        switch (ke->key()) {
        case Qt::Key_N:
        case Qt::Key_P:
            // select next/previous completion
            if (ke->modifiers() == Qt::ControlModifier)
            {
                e->accept();
                int change = (ke->key() == Qt::Key_N) ? 1 : -1;
                int nrows = model()->rowCount();
                int row = currentIndex().row();
                int newRow = (row + change + nrows) % nrows;
                if (newRow == row + change || !ke->isAutoRepeat())
                    setCurrentIndex(m_model->index(newRow));
                return true;
            }
        }
    } else if (e->type() == QEvent::KeyPress) {
        QKeyEvent *ke = static_cast<QKeyEvent *>(e);
        switch (ke->key()) {
        case Qt::Key_N:
        case Qt::Key_P:
            // select next/previous completion - so don't pass on to editor
            if (ke->modifiers() == Qt::ControlModifier)
                forwardKeys = false;
            break;

        case Qt::Key_Escape:
            m_completionWidget->closeList();
            return true;

        case Qt::Key_Right:
        case Qt::Key_Left:
        case Qt::Key_Home:
        case Qt::Key_End:
            // We want these navigation keys to work in the editor, so forward them
            break;

        case Qt::Key_Tab:
        case Qt::Key_Return:
            //independently from style, accept current entry if return is pressed
            if (qApp->focusWidget() == this)
                m_completionWidget->closeList(currentIndex());
            return true;

        case Qt::Key_Up:
            if (!ke->isAutoRepeat()
                && currentIndex().row() == 0) {
                setCurrentIndex(model()->index(model()->rowCount()-1, 0));
                return true;
            }
            forwardKeys = false;
            break;

        case Qt::Key_Down:
            if (!ke->isAutoRepeat()
                && currentIndex().row() == model()->rowCount()-1) {
                setCurrentIndex(model()->index(0, 0));
                return true;
            }
            forwardKeys = false;
            break;

        case Qt::Key_Enter:
        case Qt::Key_PageDown:
        case Qt::Key_PageUp:
            forwardKeys = false;
            break;

        default:
            // if a key is forwarded, completion widget is re-opened and selected item is reset to first,
            // so only forward keys that insert text and refine the completed item
            forwardKeys = !ke->text().isEmpty();
            break;
        }

        if (forwardKeys && ! m_quickFix) {
            m_blockFocusOut = true;
            QApplication::sendEvent(m_editorWidget, e);
            m_blockFocusOut = false;

            // Have the completion support update the list of items
            m_support->autoComplete(m_editor, false);

            return true;
        }
    }
    return QListView::event(e);
}

void CompletionListView::keyboardSearch(const QString &search)
{
    Q_UNUSED(search)
}

void CompletionListView::setQuickFix(bool quickFix)
{
    m_quickFix = quickFix;
}

void CompletionListView::setCompletionItems(const QList<TextEditor::CompletionItem> &completionItems)
{
    m_model->setItems(completionItems);

    // Select the first of the most relevant completion items
    int relevance = INT_MIN;
    int mostRelevantIndex = 0;
    for (int i = 0; i < completionItems.size(); ++i) {
        const CompletionItem &item = completionItems.at(i);
        if (item.relevance > relevance) {
            relevance = item.relevance;
            mostRelevantIndex = i;
        }
    }

    setCurrentIndex(m_model->index(mostRelevantIndex));
}

void CompletionListView::closeList(const QModelIndex &index)
{
    m_blockFocusOut = true;

    if (index.isValid())
        emit itemSelected(m_model->itemAt(index));

    emit completionListClosed();

    m_blockFocusOut = false;
}
<|endoftext|>