<commit_before>/*
 * item grouping implementation
 */

#include <cstring>

#include "node.h"
#include "convert.h"
#include "object.h"

#include "collection.h"

__MPT_NAMESPACE_BEGIN

/*!
 * \ingroup mptCollection
 * \brief get collection interface traits
 *
 * Get named traits for collection pointer data.
 *
 * \return named traits for collection pointer
 */
const named_traits *collection::pointer_traits()
{
	return mpt_interface_traits(TypeCollectionPtr);
}

/*!
 * \ingroup mptCollection
 * \brief get group interface traits
 *
 * Get named traits for group pointer data.
 *
 * \return named traits for group pointer
 */
const named_traits *group::pointer_traits()
{
	static const named_traits *traits = 0;
	if (!traits && !(traits = mpt_type_interface_add("mpt.group"))) {
		traits = mpt_type_interface_add(0);
	}
	return traits;
}

template <> int type_properties<group *>::id(bool obtain) {
	static const named_traits *traits = 0;
	if (traits) {
		return traits->type;
	}
	if (obtain && !(traits = group::pointer_traits())) {
		return BadOperation;
	}
	return traits ? traits->type : static_cast<int>(BadType);
}

template <> const struct type_traits *type_properties<group *>::traits()
{
	static const struct type_traits *traits = 0;
	if (!traits) {
		const named_traits *nt = group::pointer_traits();
		if (nt) {
			traits = &nt->traits;
		}
		else {
			static const struct type_traits fallback(sizeof(group *));
			traits = &fallback;
		}
	}
	return traits;
}

bool add_items(metatype &to, const node *head, const relation *relation, logger *out)
{
	const char _func[] = "mpt::add_items";
	group *grp = to;
	object *obj = to;

	// no assignable target
	if (!grp && !obj) {
		return true;
	}

	for (; head; head = head->next) {
		metatype *from = head->_meta;

		if (grp && from && from->addref()) {
			reference<metatype> m;
			m.set_instance(from);
			if (grp->append(&head->ident, from) < 0) {
				m.detach();
			}
			else if (out) {
				out->message(_func, out->Warning, "%s %p: %s",
				             MPT_tr("group"), grp, MPT_tr("failed to add object"));
			}
			continue;
		}
		// name is property
		const char *name;

		if (!(name = mpt_node_ident(head))) {
			if (out) {
				out->message(_func, out->Error, "%s %p: %s",
				             MPT_tr("node"), head, MPT_tr("bad element name"));
			}
			return false;
		}
		// set property
		if (obj) {
			if (!grp) {
				if (obj->set_property(name, from) >= 0) {
					continue;
				}
			}
			if (from) {
				value val;

				if (from->get(val)) {
					if (obj->set(name, val, out)) {
						continue;
					}
				}
				obj->set(name, from->string(), out);
				continue;
			}
		}
		if (!grp) {
			continue;
		}
		// get item type
		const char *start, *pos;
		size_t len;
		start = pos = name;
		name = mpt_convert_key(&pos, 0, &len);
		if (!name || name != start || !*name) {
			if (out) {
				out->message(_func, out->Warning, "%s: %s",
				             MPT_tr("bad element name"), start);
			}
			continue;
		}
		// create item
		if (!(from = grp->create(name, len))) {
			if (out) {
				out->message(_func, out->Warning, "%s: %s",
				             MPT_tr("invalid item type"), std::string(name, len).c_str());
			}
			continue;
		}
		// get item name
		name = mpt_convert_key(&pos, ":", &len);

		if (!name || !len) {
			if (out) {
				out->message(_func, out->Warning, "%s",
				             MPT_tr("empty item name"));
			}
			from->unref();
			continue;
		}
		// name conflict on same level
		if (collection::relation(*grp).find(from->type(), name, len)) {
			if (out) {
				out->message(_func, out->Warning, "%s: %s",
				             MPT_tr("conflicting item name"), std::string(name, len).c_str());
			}
			from->unref();
			continue;
		}
		const char *ident = name;
		int ilen = len;

		// find dependant items
		while ((name = mpt_convert_key(&pos, 0, &len))) {
			convertable *curr;
			if (relation) {
				curr = relation->find(from->type(), name, len);
			} else {
				curr = collection::relation(*grp).find(from->type(), name, len);
			}
			if (!curr) {
				if (out) {
					out->message(_func, out->Error, "%s: '%s': %s",
					             MPT_tr("unable to get parent"), head->ident.name(), std::string(name, len).c_str());
				}
				from->unref();
				return false;
			}
			object *obj, *src;
			if ((src = *curr) && (obj = *from)) {
				obj->set(*src, out);
				continue;
			}
			from->unref();
			if (out) {
				out->message(_func, out->Error, "%s: '%s': %s",
				             MPT_tr("unable to get inheritance"), head->ident.name(), std::string(name, len).c_str());
			}
			return false;
		}
		// add item to group
		identifier id;
		if (!id.set_name(ident, ilen)) {
			if (out) {
				out->message(_func, out->Error, "%s: %s",
				             MPT_tr("unable add item"), std::string(ident, ilen).c_str());
			}
			continue;
		}
		if (grp->append(&id, from) < 0) {
			from->unref();
			if (out) {
				out->message(_func, out->Error, "%s: %s",
				             MPT_tr("unable add item"), std::string(ident, ilen).c_str());
			}
			continue;
		}
		// set properties and subitems
		if (!head->children) {
			continue;
		}
		// process child items
		object *curr;
		if (!(curr = *from)) {
			if (out) {
				out->message(_func, out->Warning, "%s (%p): %s",
				             name, from, MPT_tr("element not an object"));
			}
		}
		group *ig;
		if (!relation || !(ig = *from)) {
			if (!(add_items(*from, head->children, 0, out))) {
				return false;
			}
			continue;
		}
		collection::relation rel(*ig, relation);
		if (!(add_items(*from, head->children, &rel, out))) {
			return false;
		}
	}
	return true;
}

// Relation search operations
struct item_match
{
	convertable *conv;
	const char *ident;
	size_t curr;
	size_t left;
	int type;
	char sep;
};
static int find_item(void *ptr, const identifier *id, convertable *conv, const collection *sub)
{
	struct item_match *ctx = static_cast<item_match *>(ptr);
	if (!id) {
		return 0;
	}
	if (!id->equal(ctx->ident, ctx->curr)) {
		return 0;
	}
	if (!ctx->left) {
		if (!conv) {
			return TraverseLeafs;
		}
		if (ctx->type) {
			int val = conv->type();
			if (val != ctx->type && (val = conv->convert(ctx->type, 0)) < 0) {
				return TraverseLeafs;
			}
		}
		ctx->conv = conv;
		return TraverseStop | TraverseLeafs;
	}
	if (!sub || !ctx->sep) {
		return 0;
	}
	struct item_match tmp = *ctx;

	const char *next = ctx->ident + ctx->left;
	const char *sep = (char *) memchr(next, ctx->sep, ctx->left);

	if (!sep) {
		return BadValue;
	}
	tmp.curr = sep - next;
	tmp.left -= (tmp.curr + 1);
	tmp.ident = next;

	int ret = sub->each(find_item, &tmp);
	if (ret < 0) {
		return ret;
	}
	ctx->conv = tmp.conv;
	ret |= TraverseNonLeafs;
	return ret;
}

convertable *collection::relation::find(int type, const char *name, int nlen) const
{
	struct item_match m;
	const char *sep;

	if (nlen < 0) nlen = name ? strlen(name) : 0;

	m.conv = 0;
	m.ident = name;
	m.curr = nlen;
	m.left = 0;
	if (_sep && name && (sep = (char *) memchr(name, _sep, nlen))) {
		size_t plen = sep - name;
		m.curr = plen;
		m.left = nlen - plen - 1;
	}
	m.type = type;
	m.sep = _sep;

	if (_curr.each(find_item, &m) >= 0 && m.conv) {
		return m.conv;
	}
	return _parent ? _parent->find(type, name, nlen) : 0;
}

convertable *node_relation::find(int type, const char *name, int nlen) const
{
	if (!_curr) {
		return 0;
	}
	if (nlen < 0) nlen = name ? strlen(name) : 0;

	for (const node *c = _curr->children; c; c = c->next) {
		metatype *m;
		if (!(m = c->_meta)) {
			continue;
		}
		if (name && !c->ident.equal(name, nlen)) {
			continue;
		}
		if (type < 0) {
			return m;
		}
		int val;
		if ((val = m->type()) < 0) {
			continue;
		}
		if (type) {
			if (type != val && (val = m->convert(type, 0)) < 0) {
				continue;
			}
		}
		else if (!val) {
			continue;
		}
		return m;
	}
	return _parent ? _parent->find(type, name, nlen) : 0;
}

__MPT_NAMESPACE_END
<commit_msg>fix nested collection element lookup<commit_after>/*
 * item grouping implementation
 */

#include <cstring>

#include "node.h"
#include "convert.h"
#include "object.h"

#include "collection.h"

__MPT_NAMESPACE_BEGIN

/*!
 * \ingroup mptCollection
 * \brief get collection interface traits
 *
 * Get named traits for collection pointer data.
 *
 * \return named traits for collection pointer
 */
const named_traits *collection::pointer_traits()
{
	return mpt_interface_traits(TypeCollectionPtr);
}

/*!
 * \ingroup mptCollection
 * \brief get group interface traits
 *
 * Get named traits for group pointer data.
 *
 * \return named traits for group pointer
 */
const named_traits *group::pointer_traits()
{
	static const named_traits *traits = 0;
	if (!traits && !(traits = mpt_type_interface_add("mpt.group"))) {
		traits = mpt_type_interface_add(0);
	}
	return traits;
}

template <> int type_properties<group *>::id(bool obtain) {
	static const named_traits *traits = 0;
	if (traits) {
		return traits->type;
	}
	if (obtain && !(traits = group::pointer_traits())) {
		return BadOperation;
	}
	return traits ? traits->type : static_cast<int>(BadType);
}

template <> const struct type_traits *type_properties<group *>::traits()
{
	static const struct type_traits *traits = 0;
	if (!traits) {
		const named_traits *nt = group::pointer_traits();
		if (nt) {
			traits = &nt->traits;
		}
		else {
			static const struct type_traits fallback(sizeof(group *));
			traits = &fallback;
		}
	}
	return traits;
}

bool add_items(metatype &to, const node *head, const relation *relation, logger *out)
{
	const char _func[] = "mpt::add_items";
	group *grp = to;
	object *obj = to;

	// no assignable target
	if (!grp && !obj) {
		return true;
	}

	for (; head; head = head->next) {
		metatype *from = head->_meta;

		if (grp && from && from->addref()) {
			reference<metatype> m;
			m.set_instance(from);
			if (grp->append(&head->ident, from) < 0) {
				m.detach();
			}
			else if (out) {
				out->message(_func, out->Warning, "%s %p: %s",
				             MPT_tr("group"), grp, MPT_tr("failed to add object"));
			}
			continue;
		}
		// name is property
		const char *name;

		if (!(name = mpt_node_ident(head))) {
			if (out) {
				out->message(_func, out->Error, "%s %p: %s",
				             MPT_tr("node"), head, MPT_tr("bad element name"));
			}
			return false;
		}
		// set property
		if (obj) {
			if (!grp) {
				if (obj->set_property(name, from) >= 0) {
					continue;
				}
			}
			if (from) {
				value val;

				if (from->get(val)) {
					if (obj->set(name, val, out)) {
						continue;
					}
				}
				obj->set(name, from->string(), out);
				continue;
			}
		}
		if (!grp) {
			continue;
		}
		// get item type
		const char *start, *pos;
		size_t len;
		start = pos = name;
		name = mpt_convert_key(&pos, 0, &len);
		if (!name || name != start || !*name) {
			if (out) {
				out->message(_func, out->Warning, "%s: %s",
				             MPT_tr("bad element name"), start);
			}
			continue;
		}
		// create item
		if (!(from = grp->create(name, len))) {
			if (out) {
				out->message(_func, out->Warning, "%s: %s",
				             MPT_tr("invalid item type"), std::string(name, len).c_str());
			}
			continue;
		}
		// get item name
		name = mpt_convert_key(&pos, ":", &len);

		if (!name || !len) {
			if (out) {
				out->message(_func, out->Warning, "%s",
				             MPT_tr("empty item name"));
			}
			from->unref();
			continue;
		}
		// name conflict on same level
		if (collection::relation(*grp).find(from->type(), name, len)) {
			if (out) {
				out->message(_func, out->Warning, "%s: %s",
				             MPT_tr("conflicting item name"), std::string(name, len).c_str());
			}
			from->unref();
			continue;
		}
		const char *ident = name;
		int ilen = len;

		// find dependant items
		while ((name = mpt_convert_key(&pos, 0, &len))) {
			convertable *curr;
			if (relation) {
				curr = relation->find(from->type(), name, len);
			} else {
				curr = collection::relation(*grp).find(from->type(), name, len);
			}
			if (!curr) {
				if (out) {
					out->message(_func, out->Error, "%s: '%s': %s",
					             MPT_tr("unable to get parent"), head->ident.name(), std::string(name, len).c_str());
				}
				from->unref();
				return false;
			}
			object *obj, *src;
			if ((src = *curr) && (obj = *from)) {
				obj->set(*src, out);
				continue;
			}
			from->unref();
			if (out) {
				out->message(_func, out->Error, "%s: '%s': %s",
				             MPT_tr("unable to get inheritance"), head->ident.name(), std::string(name, len).c_str());
			}
			return false;
		}
		// add item to group
		identifier id;
		if (!id.set_name(ident, ilen)) {
			if (out) {
				out->message(_func, out->Error, "%s: %s",
				             MPT_tr("unable add item"), std::string(ident, ilen).c_str());
			}
			continue;
		}
		if (grp->append(&id, from) < 0) {
			from->unref();
			if (out) {
				out->message(_func, out->Error, "%s: %s",
				             MPT_tr("unable add item"), std::string(ident, ilen).c_str());
			}
			continue;
		}
		// set properties and subitems
		if (!head->children) {
			continue;
		}
		// process child items
		object *curr;
		if (!(curr = *from)) {
			if (out) {
				out->message(_func, out->Warning, "%s (%p): %s",
				             name, from, MPT_tr("element not an object"));
			}
		}
		group *ig;
		if (!relation || !(ig = *from)) {
			if (!(add_items(*from, head->children, 0, out))) {
				return false;
			}
			continue;
		}
		collection::relation rel(*ig, relation);
		if (!(add_items(*from, head->children, &rel, out))) {
			return false;
		}
	}
	return true;
}

// Relation search operations
struct item_match
{
	convertable *conv;
	const char *ident;
	size_t curr;
	size_t left;
	int type;
	char sep;
};
static int find_item(void *ptr, const identifier *id, convertable *conv, const collection *sub)
{
	struct item_match *ctx = static_cast<item_match *>(ptr);
	if (!id) {
		return 0;
	}
	// identifier must match current element
	if (!id->equal(ctx->ident, ctx->curr)) {
		return 0;
	}
	// top level entry
	if (!ctx->left) {
		if (!conv) {
			return TraverseLeafs;
		}
		// check for type compatibility
		if (ctx->type) {
			int val = conv->type();
			if (val != ctx->type && (val = conv->convert(ctx->type, 0)) < 0) {
				return TraverseLeafs;
			}
		}
		ctx->conv = conv;
		return TraverseStop | TraverseLeafs;
	}
	// no nested data
	if (!sub) {
		return 0;
	}
	// advance to nested element name
	const char *next = ctx->ident + ctx->curr + 1;
	const char *sep = (char *) memchr(next, ctx->sep, ctx->left);
	struct item_match tmp = *ctx;
	if (sep) {
		tmp.curr = sep - next;
		tmp.left -= (tmp.curr + 1);
	} else {
		tmp.curr = tmp.left;
		tmp.left = 0;
	}
	tmp.ident = next;

	// find nested element
	int ret = sub->each(find_item, &tmp);
	if (ret < 0) {
		return ret;
	}
	ctx->conv = tmp.conv;
	ret |= TraverseNonLeafs;
	return ret;
}

convertable *collection::relation::find(int type, const char *name, int nlen) const
{
	struct item_match m;
	const char *sep;

	if (nlen < 0) nlen = name ? strlen(name) : 0;

	m.conv = 0;
	m.ident = name;
	m.curr = nlen;
	m.left = 0;
	if (_sep && name && (sep = (char *) memchr(name, _sep, nlen))) {
		size_t plen = sep - name;
		m.curr = plen;
		m.left = nlen - plen - 1;
	}
	m.type = type;
	m.sep = _sep;

	if (_curr.each(find_item, &m) >= 0 && m.conv) {
		return m.conv;
	}
	return _parent ? _parent->find(type, name, nlen) : 0;
}

convertable *node_relation::find(int type, const char *name, int nlen) const
{
	if (!_curr) {
		return 0;
	}
	if (nlen < 0) nlen = name ? strlen(name) : 0;

	for (const node *c = _curr->children; c; c = c->next) {
		metatype *m;
		if (!(m = c->_meta)) {
			continue;
		}
		if (name && !c->ident.equal(name, nlen)) {
			continue;
		}
		if (type < 0) {
			return m;
		}
		int val;
		if ((val = m->type()) < 0) {
			continue;
		}
		if (type) {
			if (type != val && (val = m->convert(type, 0)) < 0) {
				continue;
			}
		}
		else if (!val) {
			continue;
		}
		return m;
	}
	return _parent ? _parent->find(type, name, nlen) : 0;
}

__MPT_NAMESPACE_END
<|endoftext|>


<commit_before>#include "max.hpp"
#include "../RayTracer.hpp"
#include "../Mesh.hpp"
#include "../Vec3D.hpp"
#include <cmath>

unsigned int raysTraced = 0;
bool debug = true;

Ray performRayTracingMax(Ray ray){
	raysTraced++;

	std::vector<Triangle> triangles = MyMesh.triangles;
	float hit = 10e6f;
	for (unsigned int i = 0; i < triangles.size(); i++){
		hit = fmin(hit, intersect(triangles[i], ray));
	}
	//hit = 1 / ((hit * 2) + 1); // Arithmetic function for getting a usable color.
	ray.setColor(Vec3Df(hit, hit/5, hit*5));
	return ray;
}

float intersect(Triangle t, Ray ray){
	Vec3Df v0 = t.vertices[0].p, // Variables saved as locals for less external function calls,
		v1 = t.vertices[1].p,    // less characters in the code and better readability.
		v2 = t.vertices[2].p,
		rayOrig = ray.getOrig(),

		rayDir = normal(ray.getDest() - rayOrig); // The normalized direction of the ray.
	float angle = dot(t.normal, rayDir); // The cosine of the angle of the vectors (dotproduct of the vectors).

	if (angle == 0) // If the ray and the plane are parallel (so their angle is 0), they won't intersect.
		return 10e6f;

	float rayD = -(dot(t.normal, rayOrig) + dot(t.normal, v0)) / angle; // The distance of the ray's origin
		// to the plane in which lies the triangle.

	Vec3Df rayHit = rayOrig + rayD * rayDir; // The intersection point of the ray and the plane in which lies the triangle.

	if (dot(t.normal, cross(v1 - v0, rayHit - v0)) < 0)
		return 10e6f;
	if (dot(t.normal, cross(v2 - v1, rayHit - v1)) < 0)
		return 10e6f;
	if (dot(t.normal, cross(v0 - v2, rayHit - v2)) < 0)
		return 10e6f;

	return rayD;
}

Vec3Df cross(Vec3Df in1, Vec3Df in2){
	return in1.crossProduct(in1, in2);
}

float dot(Vec3Df in1, Vec3Df in2){
	return in1.dotProduct(in1, in2);
}

Vec3Df normal(Vec3Df in){
	Vec3Df res = Vec3Df(in);
	res.normalize();
	return res;
}
<commit_msg>Performance improvements on raytracer.<commit_after>#include "max.hpp"
#include "../RayTracer.hpp"
#include "../Mesh.hpp"
#include "../Vec3D.hpp"
#include <cmath>

unsigned int raysTraced = 0;
// bool debug = true;

Ray performRayTracingMax(Ray ray){
	raysTraced++;

	float hit = 10e6f;
	unsigned int amountOfTriangles = MyMesh.triangles.size();
	for (unsigned int i = 0; i < amountOfTriangles; i++){
		hit = fmin(hit, intersect(MyMesh.triangles[i], ray));
	}
	//hit = 1 / ((hit * 2) + 1); // Arithmetic function for getting a usable color.
	ray.setColor(Vec3Df(hit, hit/5, hit*5));
	return ray;
}

float intersect(Triangle t, Ray ray){
	Vec3Df v0 = t.vertices[0].p, // Variables saved as locals for less external function calls,
		v1 = t.vertices[1].p,    // less characters in the code and better readability.
		v2 = t.vertices[2].p,
		rayOrig = ray.getOrig(),

		rayDir = normal(ray.getDest() - rayOrig); // The normalized direction of the ray.
	float angle = dot(t.normal, rayDir); // The cosine of the angle of the vectors (dotproduct of the vectors).

	if (angle == 0) // If the ray and the plane are parallel (so their angle is 0), they won't intersect.
		return 10e6f;

	float rayD = -(dot(t.normal, rayOrig) + dot(t.normal, v0)) / angle; // The distance of the ray's origin
		// to the plane in which lies the triangle.

	Vec3Df rayHit = rayOrig + rayD * rayDir; // The intersection point of the ray and the plane in which lies the triangle.

	if (dot(t.normal, cross(v1 - v0, rayHit - v0)) < 0)
		return 10e6f;
	if (dot(t.normal, cross(v2 - v1, rayHit - v1)) < 0)
		return 10e6f;
	if (dot(t.normal, cross(v0 - v2, rayHit - v2)) < 0)
		return 10e6f;

	return rayD;
}

Vec3Df cross(Vec3Df in1, Vec3Df in2){
	return in1.crossProduct(in1, in2);
}

float dot(Vec3Df in1, Vec3Df in2){
	return in1.dotProduct(in1, in2);
}

Vec3Df normal(Vec3Df in){
	Vec3Df res = Vec3Df(in);
	res.normalize();
	return res;
}
<|endoftext|>


<commit_before>/*=====================================================================

QGroundControl Open Source Ground Control Station

(c) 2009, 2010 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>

This file is part of the QGROUNDCONTROL project

    QGROUNDCONTROL is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    QGROUNDCONTROL 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with QGROUNDCONTROL. If not, see <http://www.gnu.org/licenses/>.

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

/**
 * @file
 *   @brief Implementation of CommConfigurationWindow
 *
 *   @author Lorenz Meier <mavteam@student.ethz.ch>
 *   @author Bill Bonney <billbonney@communistech.com>
 *
 */

#include "QsLog.h"

#include "CommConfigurationWindow.h"
#include "SerialConfigurationWindow.h"
#include "SerialLink.h"
#include "UDPLink.h"
#include "TCPLink.h"
#include "MAVLinkSimulationLink.h"
#ifdef XBEELINK
#include "XbeeLink.h"
#include "XbeeConfigurationWindow.h"
#endif // XBEELINK
#ifdef OPAL_RT
#include "OpalLink.h"
#include "OpalLinkConfigurationWindow.h"
#endif
#include "MAVLinkProtocol.h"
#include "MAVLinkSettingsWidget.h"
#include "QGCUDPLinkConfiguration.h"
#include "QGCTCPLinkConfiguration.h"
#include "LinkManager.h"
#include "MainWindow.h"

#include <QDir>
#include <QFileInfoList>
#include <QBoxLayout>
#include <QWidget>

CommConfigurationWindow::CommConfigurationWindow(int linkid, ProtocolInterface* protocol, QWidget *parent) : QDialog(parent)
{
    setWindowFlags(Qt::WindowStaysOnTopHint);
    this->m_linkid = linkid;

    // Setup the user interface according to link type
    ui.setupUi(this);

    // Initialize basic ui state

    // Do not allow changes here unless advanced is checked
    ui.connectionType->setEnabled(false);
    ui.linkType->setEnabled(false);
    ui.protocolGroupBox->setVisible(false);
    ui.protocolTypeGroupBox->setVisible(false);

    // Connect UI element visibility to checkbox
    ui.advancedOptionsCheckBox->setVisible(false);
    connect(ui.advCheckBox, SIGNAL(clicked(bool)), ui.connectionType, SLOT(setEnabled(bool)));
    connect(ui.advCheckBox, SIGNAL(clicked(bool)), ui.linkType, SLOT(setEnabled(bool)));
    connect(ui.advCheckBox, SIGNAL(clicked(bool)), ui.protocolGroupBox, SLOT(setVisible(bool)));
    ui.advCheckBox->setVisible(false);

    // add link types
    ui.linkType->addItem(tr("Serial"), QGC_LINK_SERIAL);
    ui.linkType->addItem(tr("UDP"), QGC_LINK_UDP);
    ui.linkType->addItem(tr("TCP"), QGC_LINK_TCP);
    //    ui.linkType->addItem(tr("Simulation"), QGC_LINK_SIMULATION); // [TODO] left as key where to add simulation mode

#ifdef OPAL_RT
    ui.linkType->addItem(tr("Opal-RT Link"), QGC_LINK_OPAL);
#endif
#ifdef XBEELINK
	ui.linkType->addItem(tr("Xbee API"),QGC_LINK_XBEE);
#endif // XBEELINK
    ui.linkType->setEditable(false);

    // Create action to open this menu
    // Create configuration action for this link
    // Connect the current UAS
    action = new QAction(QIcon(":/files/images/devices/network-wireless.svg"), "", this);
    action->setData(linkid);
    action->setEnabled(true);
    action->setVisible(true);

    setLinkName(LinkManager::instance()->getLinkName(linkid));
    connect(action, SIGNAL(triggered()), this, SLOT(show()));

    // Setup user actions and link notifications
    connect(ui.connectButton, SIGNAL(clicked()), this, SLOT(setConnection()));
    connect(ui.closeButton, SIGNAL(clicked()), this->window(), SLOT(close()));
    connect(ui.deleteButton, SIGNAL(clicked()), this, SLOT(remove()));

    connect(LinkManager::instance(),SIGNAL(linkChanged(int)),this,SLOT(linkUpdate(int)));

    // Fill in the current data
    if(LinkManager::instance()->getLinkConnected(m_linkid)) ui.connectButton->setChecked(true);

    connectButtonStatus(linkid);

    // Open details pane for serial link if necessary

    if (LinkManager::instance()->getLinkType(linkid) == LinkInterface::SERIAL_LINK)
    {
        QWidget* conf = new SerialConfigurationWindow(linkid, this);
        ui.linkScrollArea->setWidget(conf);
        ui.linkGroupBox->setTitle(tr("Serial Link"));
        ui.linkType->setCurrentIndex(ui.linkType->findData(QGC_LINK_SERIAL));

    }
    else if (LinkManager::instance()->getLinkType(linkid) == LinkInterface::UDP_LINK)
    {
        QWidget* conf = new QGCUDPLinkConfiguration(linkid, this);
        ui.linkScrollArea->setWidget(conf);
        ui.linkGroupBox->setTitle(tr("UDP Link"));
        ui.linkType->setCurrentIndex(ui.linkType->findData(QGC_LINK_UDP));
    }
    else if (LinkManager::instance()->getLinkType(linkid) == LinkInterface::TCP_LINK)
    {
        QWidget *conf = new QGCTCPLinkConfiguration(linkid,this);
        ui.linkScrollArea->setWidget(conf);
        ui.linkGroupBox->setTitle(tr("TCP Link"));
        ui.linkType->setCurrentIndex(ui.linkType->findData(QGC_LINK_TCP));
    }

    // Display the widget
    this->window()->setWindowTitle(tr("Settings for ") + LinkManager::instance()->getLinkName(linkid));
    this->hide();
}

void CommConfigurationWindow::connectButtonStatus(int linkid)
{
    if(LinkManager::instance()->getLinkConnected(linkid)) {
        ui.deleteButton->setEnabled(false);
        ui.connectionStatusLabel->setText(tr("Connected"));
        ui.connectButton->setText("Disconnect");
    } else {
        ui.deleteButton->setEnabled(true);
        ui.connectionStatusLabel->setText(tr("Disconnected"));
        ui.connectButton->setText("Connect");
    }
}

void CommConfigurationWindow::linkUpdate(int linkid)
{
    if (linkid != this->m_linkid)
    {
        return;
    }

    if (LinkManager::instance()->getLinkType(linkid) == LinkInterface::TCP_LINK
        && LinkManager::instance()->isTcpServer(linkid))
    {
        this->window()->close();
    }

    setLinkName(LinkManager::instance()->getLinkName(linkid));
    connectButtonStatus(linkid);
}

CommConfigurationWindow::~CommConfigurationWindow()
{

}

QAction* CommConfigurationWindow::getAction()
{
    return action;
}

void CommConfigurationWindow::setProtocol(int protocol)
{
    QLOG_DEBUG() << "Changing to protocol" << protocol;
}

void CommConfigurationWindow::setConnection()
{
    if (!LinkManager::instance()->getLinkConnected(m_linkid))
    {
        if (LinkManager::instance()->connectLink(m_linkid))
            this->window()->close();
    }
    else
    {
        LinkManager::instance()->disconnectLink(m_linkid);
    }
}

void CommConfigurationWindow::setLinkName(QString name)
{
    action->setText(tr("%1 Settings").arg(name));
    action->setStatusTip(tr("Adjust setting for link %1").arg(name));
    this->window()->setWindowTitle(tr("Settings for %1").arg(name));
}

void CommConfigurationWindow::remove()
{
    if(action) delete action; //delete action first since it has a pointer to link
    action=NULL;

    LinkManager::instance()->removeLink(m_linkid); //close & remove link from LinkManager list

    this->window()->close();
    this->deleteLater();
}

void CommConfigurationWindow::connectionState(bool connect)
{
    ui.connectButton->setChecked(connect);
    if(connect) {
        ui.connectionStatusLabel->setText(tr("Connected"));
        ui.connectButton->setText(tr("Disconnect"));
    } else {
        ui.connectionStatusLabel->setText(tr("Disconnected"));
        ui.connectButton->setText(tr("Connect"));
    }
}
<commit_msg>TCP Link: avoid NULL exception when the Link fires a disconnected event when the link is deleted.<commit_after>/*=====================================================================

QGroundControl Open Source Ground Control Station

(c) 2009, 2010 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>

This file is part of the QGROUNDCONTROL project

    QGROUNDCONTROL is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    QGROUNDCONTROL 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with QGROUNDCONTROL. If not, see <http://www.gnu.org/licenses/>.

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

/**
 * @file
 *   @brief Implementation of CommConfigurationWindow
 *
 *   @author Lorenz Meier <mavteam@student.ethz.ch>
 *   @author Bill Bonney <billbonney@communistech.com>
 *
 */

#include "QsLog.h"

#include "CommConfigurationWindow.h"
#include "SerialConfigurationWindow.h"
#include "SerialLink.h"
#include "UDPLink.h"
#include "TCPLink.h"
#include "MAVLinkSimulationLink.h"
#ifdef XBEELINK
#include "XbeeLink.h"
#include "XbeeConfigurationWindow.h"
#endif // XBEELINK
#ifdef OPAL_RT
#include "OpalLink.h"
#include "OpalLinkConfigurationWindow.h"
#endif
#include "MAVLinkProtocol.h"
#include "MAVLinkSettingsWidget.h"
#include "QGCUDPLinkConfiguration.h"
#include "QGCTCPLinkConfiguration.h"
#include "LinkManager.h"
#include "MainWindow.h"

#include <QDir>
#include <QFileInfoList>
#include <QBoxLayout>
#include <QWidget>

CommConfigurationWindow::CommConfigurationWindow(int linkid, ProtocolInterface* protocol, QWidget *parent) : QDialog(parent)
{
    setWindowFlags(Qt::WindowStaysOnTopHint);
    this->m_linkid = linkid;

    // Setup the user interface according to link type
    ui.setupUi(this);

    // Initialize basic ui state

    // Do not allow changes here unless advanced is checked
    ui.connectionType->setEnabled(false);
    ui.linkType->setEnabled(false);
    ui.protocolGroupBox->setVisible(false);
    ui.protocolTypeGroupBox->setVisible(false);

    // Connect UI element visibility to checkbox
    ui.advancedOptionsCheckBox->setVisible(false);
    connect(ui.advCheckBox, SIGNAL(clicked(bool)), ui.connectionType, SLOT(setEnabled(bool)));
    connect(ui.advCheckBox, SIGNAL(clicked(bool)), ui.linkType, SLOT(setEnabled(bool)));
    connect(ui.advCheckBox, SIGNAL(clicked(bool)), ui.protocolGroupBox, SLOT(setVisible(bool)));
    ui.advCheckBox->setVisible(false);

    // add link types
    ui.linkType->addItem(tr("Serial"), QGC_LINK_SERIAL);
    ui.linkType->addItem(tr("UDP"), QGC_LINK_UDP);
    ui.linkType->addItem(tr("TCP"), QGC_LINK_TCP);
    //    ui.linkType->addItem(tr("Simulation"), QGC_LINK_SIMULATION); // [TODO] left as key where to add simulation mode

#ifdef OPAL_RT
    ui.linkType->addItem(tr("Opal-RT Link"), QGC_LINK_OPAL);
#endif
#ifdef XBEELINK
	ui.linkType->addItem(tr("Xbee API"),QGC_LINK_XBEE);
#endif // XBEELINK
    ui.linkType->setEditable(false);

    // Create action to open this menu
    // Create configuration action for this link
    // Connect the current UAS
    action = new QAction(QIcon(":/files/images/devices/network-wireless.svg"), "", this);
    action->setData(linkid);
    action->setEnabled(true);
    action->setVisible(true);

    setLinkName(LinkManager::instance()->getLinkName(linkid));
    connect(action, SIGNAL(triggered()), this, SLOT(show()));

    // Setup user actions and link notifications
    connect(ui.connectButton, SIGNAL(clicked()), this, SLOT(setConnection()));
    connect(ui.closeButton, SIGNAL(clicked()), this->window(), SLOT(close()));
    connect(ui.deleteButton, SIGNAL(clicked()), this, SLOT(remove()));

    connect(LinkManager::instance(),SIGNAL(linkChanged(int)),this,SLOT(linkUpdate(int)));

    // Fill in the current data
    if(LinkManager::instance()->getLinkConnected(m_linkid)) ui.connectButton->setChecked(true);

    connectButtonStatus(linkid);

    // Open details pane for serial link if necessary

    if (LinkManager::instance()->getLinkType(linkid) == LinkInterface::SERIAL_LINK)
    {
        QWidget* conf = new SerialConfigurationWindow(linkid, this);
        ui.linkScrollArea->setWidget(conf);
        ui.linkGroupBox->setTitle(tr("Serial Link"));
        ui.linkType->setCurrentIndex(ui.linkType->findData(QGC_LINK_SERIAL));

    }
    else if (LinkManager::instance()->getLinkType(linkid) == LinkInterface::UDP_LINK)
    {
        QWidget* conf = new QGCUDPLinkConfiguration(linkid, this);
        ui.linkScrollArea->setWidget(conf);
        ui.linkGroupBox->setTitle(tr("UDP Link"));
        ui.linkType->setCurrentIndex(ui.linkType->findData(QGC_LINK_UDP));
    }
    else if (LinkManager::instance()->getLinkType(linkid) == LinkInterface::TCP_LINK)
    {
        QWidget *conf = new QGCTCPLinkConfiguration(linkid,this);
        ui.linkScrollArea->setWidget(conf);
        ui.linkGroupBox->setTitle(tr("TCP Link"));
        ui.linkType->setCurrentIndex(ui.linkType->findData(QGC_LINK_TCP));
    }

    // Display the widget
    this->window()->setWindowTitle(tr("Settings for ") + LinkManager::instance()->getLinkName(linkid));
    this->hide();
}

void CommConfigurationWindow::connectButtonStatus(int linkid)
{
    if(LinkManager::instance()->getLinkConnected(linkid)) {
        ui.deleteButton->setEnabled(false);
        ui.connectionStatusLabel->setText(tr("Connected"));
        ui.connectButton->setText("Disconnect");
    } else {
        ui.deleteButton->setEnabled(true);
        ui.connectionStatusLabel->setText(tr("Disconnected"));
        ui.connectButton->setText("Connect");
    }
}

void CommConfigurationWindow::linkUpdate(int linkid)
{
    if (linkid != this->m_linkid)
    {
        return;
    }

    if (LinkManager::instance()->getLinkType(linkid) == LinkInterface::TCP_LINK
        && LinkManager::instance()->isTcpServer(linkid))
    {
        this->window()->close();
    }

    setLinkName(LinkManager::instance()->getLinkName(linkid));
    connectButtonStatus(linkid);
}

CommConfigurationWindow::~CommConfigurationWindow()
{

}

QAction* CommConfigurationWindow::getAction()
{
    return action;
}

void CommConfigurationWindow::setProtocol(int protocol)
{
    QLOG_DEBUG() << "Changing to protocol" << protocol;
}

void CommConfigurationWindow::setConnection()
{
    if (!LinkManager::instance()->getLinkConnected(m_linkid))
    {
        if (LinkManager::instance()->connectLink(m_linkid))
            this->window()->close();
    }
    else
    {
        LinkManager::instance()->disconnectLink(m_linkid);
    }
}

void CommConfigurationWindow::setLinkName(QString name)
{
    if (action)
    {
        action->setText(tr("%1 Settings").arg(name));
        action->setStatusTip(tr("Adjust setting for link %1").arg(name));
        this->window()->setWindowTitle(tr("Settings for %1").arg(name));
    }
}

void CommConfigurationWindow::remove()
{
    if(action) delete action; //delete action first since it has a pointer to link
    action=NULL;

    LinkManager::instance()->removeLink(m_linkid); //close & remove link from LinkManager list

    this->window()->close();
    this->deleteLater();
}

void CommConfigurationWindow::connectionState(bool connect)
{
    ui.connectButton->setChecked(connect);
    if(connect) {
        ui.connectionStatusLabel->setText(tr("Connected"));
        ui.connectButton->setText(tr("Disconnect"));
    } else {
        ui.connectionStatusLabel->setText(tr("Disconnected"));
        ui.connectButton->setText(tr("Connect"));
    }
}
<|endoftext|>


<commit_before>/*
 * This istream filter reads a 32 bit header size from the stream,
 * reads it into a buffer and invokes a callback with the tail of the
 * stream.
 *
 * author: Max Kellermann <mk@cm4all.com>
 */

#include "sink_header.hxx"
#include "pool.hxx"
#include "istream_internal.hxx"
#include "async.hxx"
#include "util/Cast.hxx"
#include "util/ByteOrder.hxx"

#include <glib.h>

#include <assert.h>
#include <string.h>
#include <stdint.h>

struct sink_header {
    struct istream output;

    enum {
        SIZE, HEADER, CALLBACK, DATA
    } state;

    struct istream *input;

    unsigned char size_buffer[4];

    unsigned char *buffer;
    size_t size, position;

    /**
     * How much data of the input is pending to be consumed?  Only
     * valid while state==CALLBACK.
     */
    size_t pending;

    const struct sink_header_handler *handler;
    void *handler_ctx;

    struct async_operation async_operation;
};

static GQuark
sink_header_quark(void)
{
    return g_quark_from_static_string("sink_header");
}

static size_t
header_invoke_callback(struct sink_header *header, size_t consumed)
{
    assert(header->state == sink_header::SIZE ||
           header->state == sink_header::HEADER);

    header->async_operation.Finished();

    const ScopePoolRef ref(*header->output.pool TRACE_ARGS);

    /* the base value has been set by sink_header_input_data() */
    header->pending += consumed;

    header->state = sink_header::CALLBACK;
    header->handler->done(header->buffer, header->size,
                          &header->output,
                          header->handler_ctx);

    if (header->input != NULL) {
        header->state = sink_header::DATA;
        istream_handler_set_direct(header->input,
                                   header->output.handler_direct);
    } else
        /* we have been closed meanwhile; bail out */
        consumed = 0;

    return consumed;
}

static size_t
header_consume_size(struct sink_header *header,
                    const void *data, size_t length)
{
    assert(header->position < sizeof(header->size_buffer));

    if (length > sizeof(header->size_buffer) - header->position)
        length = sizeof(header->size_buffer) - header->position;

    memcpy(header->size_buffer + header->position, data, length);
    header->position += length;

    if (header->position < sizeof(header->size_buffer))
        return length;

    const void *size_buffer = header->size_buffer;
        const uint32_t *size_p = (const uint32_t *)size_buffer;
    header->size = FromBE32(*size_p);
    if (header->size > 0x100000) {
        /* header too large */
        header->async_operation.Finished();
        istream_close_handler(header->input);

        GError *error =
            g_error_new_literal(sink_header_quark(), 0,
                                "header is too large");
        header->handler->error(error, header->handler_ctx);
        istream_deinit(&header->output);
        return 0;
    }

    if (header->size > 0) {
        header->buffer = (unsigned char *)
            p_malloc(header->output.pool, header->size);
        header->state = sink_header::HEADER;
        header->position = 0;
    } else {
        /* header empty: don't allocate, invoke callback now */

        header->buffer = NULL;

        length = header_invoke_callback(header, length);
    }

    return length;
}

static size_t
header_consume_header(struct sink_header *header,
                      const void *data, size_t length)
{
    size_t nbytes = header->size - header->position;

    assert(header->position < header->size);

    if (nbytes > length)
        nbytes = length;

    memcpy(header->buffer + header->position, data, nbytes);
    header->position += nbytes;

    if (header->position < header->size)
        return nbytes;

    return header_invoke_callback(header, nbytes);
}

/*
 * istream handler
 *
 */

static size_t
sink_header_input_data(const void *data0, size_t length, void *ctx)
{
    sink_header *header = (sink_header *)ctx;
    const unsigned char *data = (const unsigned char *)data0;
    size_t consumed = 0, nbytes;

    if (header->state == sink_header::DATA)
        return istream_invoke_data(&header->output, data, length);

    if (header->state == sink_header::SIZE) {
        header->pending = 0; /* just in case the callback is invoked */

        consumed = header_consume_size(header, data, length);
        if (consumed == 0)
            return 0;

        if (consumed == length)
            return length;

        data += consumed;
        length -= consumed;
    }

    if (header->state == sink_header::HEADER) {
        header->pending = consumed; /* just in case the callback is invoked */

        nbytes = header_consume_header(header, data, length);
        if (nbytes == 0)
            return 0;

        consumed += nbytes;
        if (consumed == length)
            return length;

        data += nbytes;
        length -= nbytes;
    }

    assert(consumed > 0);

    if (header->state == sink_header::DATA && length > 0) {
        const ScopePoolRef ref(*header->output.pool TRACE_ARGS);

        nbytes = istream_invoke_data(&header->output, data, length);
        if (nbytes == 0 && header->input == NULL)
            consumed = 0;
        else
            consumed += nbytes;
    }

    return consumed;
}

static ssize_t
sink_header_input_direct(FdType type, int fd, size_t max_length, void *ctx)
{
    sink_header *header = (sink_header *)ctx;

    assert(header->state == sink_header::DATA);

    return istream_invoke_direct(&header->output, type, fd, max_length);
}

static void
sink_header_input_eof(void *ctx)
{
    sink_header *header = (sink_header *)ctx;

    switch (header->state) {
        GError *error;

    case sink_header::SIZE:
    case sink_header::HEADER:
        header->async_operation.Finished();

        error = g_error_new_literal(sink_header_quark(), 0,
                                    "premature end of file");
        header->handler->error(error, header->handler_ctx);
        istream_deinit(&header->output);
        break;

    case sink_header::CALLBACK:
        assert(false);
        gcc_unreachable();

    case sink_header::DATA:
        istream_deinit_eof(&header->output);
        break;
    }
}

static void
sink_header_input_abort(GError *error, void *ctx)
{
    sink_header *header = (sink_header *)ctx;

    switch (header->state) {
    case sink_header::SIZE:
    case sink_header::HEADER:
        header->async_operation.Finished();
        header->handler->error(error, header->handler_ctx);
        istream_deinit(&header->output);
        break;

    case sink_header::CALLBACK:
        assert(false);
        gcc_unreachable();

    case sink_header::DATA:
        istream_deinit_abort(&header->output, error);
        break;
    }
}

static const struct istream_handler sink_header_input_handler = {
    .data = sink_header_input_data,
    .direct = sink_header_input_direct,
    .eof = sink_header_input_eof,
    .abort = sink_header_input_abort,
};

/*
 * istream implementation
 *
 */

static inline struct sink_header *
istream_to_header(struct istream *istream)
{
    return &ContainerCast2(*istream, &sink_header::output);
}

static off_t
sink_header_available(struct istream *istream, bool partial)
{
    struct sink_header *header = istream_to_header(istream);
    off_t available = istream_available(header->input, partial);

    if (available >= 0 && header->state == sink_header::CALLBACK) {
        if (available < (off_t)header->pending) {
            assert(partial);

            return -1;
        }

        available -= header->pending;
    }

    return available;
}

static void
sink_header_read(struct istream *istream)
{
    struct sink_header *header = istream_to_header(istream);

    if (header->state == sink_header::CALLBACK)
        /* workaround: when invoking the callback from the data()
           handler, it would be illegal to call header->input again */
        return;

    istream_handler_set_direct(header->input, header->output.handler_direct);
    istream_read(header->input);
}

static void
sink_header_close(struct istream *istream)
{
    struct sink_header *header = istream_to_header(istream);

    istream_free_handler(&header->input);
    istream_deinit(&header->output);
}

static const struct istream_class istream_sink = {
    .available = sink_header_available,
    .read = sink_header_read,
    .close = sink_header_close,
};

/*
 * async operation
 *
 */

static struct sink_header *
async_to_sink_header(struct async_operation *ao)
{
    return &ContainerCast2(*ao, &sink_header::async_operation);
}

static void
sink_header_abort(struct async_operation *ao)
{
    struct sink_header *header = async_to_sink_header(ao);

    istream_close_handler(header->input);
    istream_deinit(&header->output);
}

static const struct async_operation_class sink_header_operation = {
    .abort = sink_header_abort,
};

/*
 * constructor
 *
 */

void
sink_header_new(struct pool *pool, struct istream *input,
                const struct sink_header_handler *handler, void *ctx,
                struct async_operation_ref *async_ref)
{
    struct sink_header *header = (struct sink_header *)
        istream_new(pool, &istream_sink, sizeof(*header));

    assert(input != NULL);
    assert(!istream_has_handler(input));
    assert(handler != NULL);
    assert(handler->done != NULL);
    assert(handler->error != NULL);

    istream_assign_handler(&header->input, input,
                           &sink_header_input_handler, header,
                           0);

    header->state = sink_header::SIZE;
    header->position = 0;
    header->handler = handler;
    header->handler_ctx = ctx;

    header->async_operation.Init(sink_header_operation);
    async_ref->Set(header->async_operation);
}
<commit_msg>sink_header: use NewFromPool() instead of istream_new()<commit_after>/*
 * This istream filter reads a 32 bit header size from the stream,
 * reads it into a buffer and invokes a callback with the tail of the
 * stream.
 *
 * author: Max Kellermann <mk@cm4all.com>
 */

#include "sink_header.hxx"
#include "pool.hxx"
#include "istream_internal.hxx"
#include "async.hxx"
#include "util/Cast.hxx"
#include "util/ByteOrder.hxx"

#include <glib.h>

#include <assert.h>
#include <string.h>
#include <stdint.h>

struct sink_header {
    struct istream output;

    enum {
        SIZE, HEADER, CALLBACK, DATA
    } state;

    struct istream *input;

    unsigned char size_buffer[4];

    unsigned char *buffer;
    size_t size, position;

    /**
     * How much data of the input is pending to be consumed?  Only
     * valid while state==CALLBACK.
     */
    size_t pending;

    const struct sink_header_handler *handler;
    void *handler_ctx;

    struct async_operation async_operation;
};

static GQuark
sink_header_quark(void)
{
    return g_quark_from_static_string("sink_header");
}

static size_t
header_invoke_callback(struct sink_header *header, size_t consumed)
{
    assert(header->state == sink_header::SIZE ||
           header->state == sink_header::HEADER);

    header->async_operation.Finished();

    const ScopePoolRef ref(*header->output.pool TRACE_ARGS);

    /* the base value has been set by sink_header_input_data() */
    header->pending += consumed;

    header->state = sink_header::CALLBACK;
    header->handler->done(header->buffer, header->size,
                          &header->output,
                          header->handler_ctx);

    if (header->input != NULL) {
        header->state = sink_header::DATA;
        istream_handler_set_direct(header->input,
                                   header->output.handler_direct);
    } else
        /* we have been closed meanwhile; bail out */
        consumed = 0;

    return consumed;
}

static size_t
header_consume_size(struct sink_header *header,
                    const void *data, size_t length)
{
    assert(header->position < sizeof(header->size_buffer));

    if (length > sizeof(header->size_buffer) - header->position)
        length = sizeof(header->size_buffer) - header->position;

    memcpy(header->size_buffer + header->position, data, length);
    header->position += length;

    if (header->position < sizeof(header->size_buffer))
        return length;

    const void *size_buffer = header->size_buffer;
        const uint32_t *size_p = (const uint32_t *)size_buffer;
    header->size = FromBE32(*size_p);
    if (header->size > 0x100000) {
        /* header too large */
        header->async_operation.Finished();
        istream_close_handler(header->input);

        GError *error =
            g_error_new_literal(sink_header_quark(), 0,
                                "header is too large");
        header->handler->error(error, header->handler_ctx);
        istream_deinit(&header->output);
        return 0;
    }

    if (header->size > 0) {
        header->buffer = (unsigned char *)
            p_malloc(header->output.pool, header->size);
        header->state = sink_header::HEADER;
        header->position = 0;
    } else {
        /* header empty: don't allocate, invoke callback now */

        header->buffer = NULL;

        length = header_invoke_callback(header, length);
    }

    return length;
}

static size_t
header_consume_header(struct sink_header *header,
                      const void *data, size_t length)
{
    size_t nbytes = header->size - header->position;

    assert(header->position < header->size);

    if (nbytes > length)
        nbytes = length;

    memcpy(header->buffer + header->position, data, nbytes);
    header->position += nbytes;

    if (header->position < header->size)
        return nbytes;

    return header_invoke_callback(header, nbytes);
}

/*
 * istream handler
 *
 */

static size_t
sink_header_input_data(const void *data0, size_t length, void *ctx)
{
    sink_header *header = (sink_header *)ctx;
    const unsigned char *data = (const unsigned char *)data0;
    size_t consumed = 0, nbytes;

    if (header->state == sink_header::DATA)
        return istream_invoke_data(&header->output, data, length);

    if (header->state == sink_header::SIZE) {
        header->pending = 0; /* just in case the callback is invoked */

        consumed = header_consume_size(header, data, length);
        if (consumed == 0)
            return 0;

        if (consumed == length)
            return length;

        data += consumed;
        length -= consumed;
    }

    if (header->state == sink_header::HEADER) {
        header->pending = consumed; /* just in case the callback is invoked */

        nbytes = header_consume_header(header, data, length);
        if (nbytes == 0)
            return 0;

        consumed += nbytes;
        if (consumed == length)
            return length;

        data += nbytes;
        length -= nbytes;
    }

    assert(consumed > 0);

    if (header->state == sink_header::DATA && length > 0) {
        const ScopePoolRef ref(*header->output.pool TRACE_ARGS);

        nbytes = istream_invoke_data(&header->output, data, length);
        if (nbytes == 0 && header->input == NULL)
            consumed = 0;
        else
            consumed += nbytes;
    }

    return consumed;
}

static ssize_t
sink_header_input_direct(FdType type, int fd, size_t max_length, void *ctx)
{
    sink_header *header = (sink_header *)ctx;

    assert(header->state == sink_header::DATA);

    return istream_invoke_direct(&header->output, type, fd, max_length);
}

static void
sink_header_input_eof(void *ctx)
{
    sink_header *header = (sink_header *)ctx;

    switch (header->state) {
        GError *error;

    case sink_header::SIZE:
    case sink_header::HEADER:
        header->async_operation.Finished();

        error = g_error_new_literal(sink_header_quark(), 0,
                                    "premature end of file");
        header->handler->error(error, header->handler_ctx);
        istream_deinit(&header->output);
        break;

    case sink_header::CALLBACK:
        assert(false);
        gcc_unreachable();

    case sink_header::DATA:
        istream_deinit_eof(&header->output);
        break;
    }
}

static void
sink_header_input_abort(GError *error, void *ctx)
{
    sink_header *header = (sink_header *)ctx;

    switch (header->state) {
    case sink_header::SIZE:
    case sink_header::HEADER:
        header->async_operation.Finished();
        header->handler->error(error, header->handler_ctx);
        istream_deinit(&header->output);
        break;

    case sink_header::CALLBACK:
        assert(false);
        gcc_unreachable();

    case sink_header::DATA:
        istream_deinit_abort(&header->output, error);
        break;
    }
}

static const struct istream_handler sink_header_input_handler = {
    .data = sink_header_input_data,
    .direct = sink_header_input_direct,
    .eof = sink_header_input_eof,
    .abort = sink_header_input_abort,
};

/*
 * istream implementation
 *
 */

static inline struct sink_header *
istream_to_header(struct istream *istream)
{
    return &ContainerCast2(*istream, &sink_header::output);
}

static off_t
sink_header_available(struct istream *istream, bool partial)
{
    struct sink_header *header = istream_to_header(istream);
    off_t available = istream_available(header->input, partial);

    if (available >= 0 && header->state == sink_header::CALLBACK) {
        if (available < (off_t)header->pending) {
            assert(partial);

            return -1;
        }

        available -= header->pending;
    }

    return available;
}

static void
sink_header_read(struct istream *istream)
{
    struct sink_header *header = istream_to_header(istream);

    if (header->state == sink_header::CALLBACK)
        /* workaround: when invoking the callback from the data()
           handler, it would be illegal to call header->input again */
        return;

    istream_handler_set_direct(header->input, header->output.handler_direct);
    istream_read(header->input);
}

static void
sink_header_close(struct istream *istream)
{
    struct sink_header *header = istream_to_header(istream);

    istream_free_handler(&header->input);
    istream_deinit(&header->output);
}

static const struct istream_class istream_sink = {
    .available = sink_header_available,
    .read = sink_header_read,
    .close = sink_header_close,
};

/*
 * async operation
 *
 */

static struct sink_header *
async_to_sink_header(struct async_operation *ao)
{
    return &ContainerCast2(*ao, &sink_header::async_operation);
}

static void
sink_header_abort(struct async_operation *ao)
{
    struct sink_header *header = async_to_sink_header(ao);

    istream_close_handler(header->input);
    istream_deinit(&header->output);
}

static const struct async_operation_class sink_header_operation = {
    .abort = sink_header_abort,
};

/*
 * constructor
 *
 */

void
sink_header_new(struct pool *pool, struct istream *input,
                const struct sink_header_handler *handler, void *ctx,
                struct async_operation_ref *async_ref)
{
    assert(input != NULL);
    assert(!istream_has_handler(input));
    assert(handler != NULL);
    assert(handler->done != NULL);
    assert(handler->error != NULL);

    auto header = NewFromPool<struct sink_header>(*pool);
    istream_init(&header->output, &istream_sink, pool);

    istream_assign_handler(&header->input, input,
                           &sink_header_input_handler, header,
                           0);

    header->state = sink_header::SIZE;
    header->position = 0;
    header->handler = handler;
    header->handler_ctx = ctx;

    header->async_operation.Init(sink_header_operation);
    async_ref->Set(header->async_operation);
}
<|endoftext|>


<commit_before>#include <itemlist_formaction.h>
#include <view.h>
#include <config.h>
#include <logger.h>

#include <sstream>

namespace newsbeuter {

itemlist_formaction::itemlist_formaction(view * vv, std::string formstr)
	: formaction(vv,formstr), feed(0), apply_filter(false), update_visible_items(true) {
}

itemlist_formaction::~itemlist_formaction() { }

void itemlist_formaction::process_operation(operation op) {
	bool quit = false;
	switch (op) {
		case OP_INT_END_SETFILTER: {
				std::string filtertext = f->get("filtertext");
				f->modify("lastline","replace","{hbox[lastline] .expand:0 {label[msglabel] .expand:h text[msg]:\"\"}}");
				if (filtertext.length() > 0) {
					if (!m.parse(filtertext)) {
						v->show_error(_("Error: couldn't parse filter command!"));
					} else {
						apply_filter = true;
						update_visible_items = true;
						do_redraw = true;
					}
				}
			}
			break;
		case OP_OPEN: {
				std::string itemposname = f->get("itempos");
				GetLogger().log(LOG_INFO, "itemlist_formaction: opening item at pos `%s'", itemposname.c_str());
				if (itemposname.length() > 0) {
					std::istringstream posname(itemposname);
					unsigned int pos = 0;
					posname >> pos;
					v->push_itemview(feed, visible_items[pos].first->guid());
					do_redraw = true;
				} else {
					v->show_error(_("No item selected!")); // should not happen
				}
			}
			break;
		case OP_OPENINBROWSER: {
				std::string itemposname = f->get("itempos");
				GetLogger().log(LOG_INFO, "itemlist_formaction: opening item at pos `%s'", itemposname.c_str());
				if (itemposname.length() > 0) {
					std::istringstream posname(itemposname);
					unsigned int pos = 0;
					posname >> pos;
					if (pos < visible_items.size()) {
						v->open_in_browser(visible_items[pos].first->link());
						do_redraw = true;
					}
				} else {
					v->show_error(_("No item selected!")); // should not happen
				}
			}
			break;
		case OP_SAVE:
			{
				char buf[1024];
				std::string itemposname = f->get("itempos");
				GetLogger().log(LOG_INFO, "itemlist_formaction: saving item at pos `%s'", itemposname.c_str());
				if (itemposname.length() > 0) {
					std::istringstream posname(itemposname);
					unsigned int pos = 0;
					posname >> pos;

					std::string filename = v->run_filebrowser(FBT_SAVE,v->get_filename_suggestion(visible_items[pos].first->title()));
					if (filename == "") {
						v->show_error(_("Aborted saving."));
					} else {
						try {
							v->write_item(*visible_items[pos].first, filename);
							snprintf(buf, sizeof(buf), _("Saved article to %s"), filename.c_str());
							v->show_error(buf);

						} catch (...) {
							snprintf(buf, sizeof(buf), _("Error: couldn't save article to %s"), filename.c_str());
							v->show_error(buf);
						}
					}
				} else {
					v->show_error(_("Error: no item selected!"));
				}
			}
			break;
		case OP_HELP:
			v->push_help();
			break;
		case OP_RELOAD:
			GetLogger().log(LOG_INFO, "itemlist_formaction: reloading current feed");
			v->get_ctrl()->reload(pos);
			// feed = v->get_ctrl()->get_feed(pos);
			update_visible_items = true;
			do_redraw = true;
			break;
		case OP_QUIT:
			GetLogger().log(LOG_INFO, "itemlist_formaction: quitting");
			quit = true;
			break;
		case OP_NEXTUNREAD:
			GetLogger().log(LOG_INFO, "itemlist_formaction: jumping to next unread item");
			if (!jump_to_next_unread_item(false)) {
				v->show_error(_("No unread items."));
			}
			break;
		case OP_MARKFEEDREAD:
			GetLogger().log(LOG_INFO, "itemlist_formaction: marking feed read");
			v->set_status(_("Marking feed read..."));
			v->get_ctrl()->mark_all_read(pos);
			v->set_status("");
			do_redraw = true;
			break;
		case OP_SEARCH:
			v->run_search(feed->rssurl());
			break;
		case OP_TOGGLEITEMREAD: {
				std::string itemposname = f->get("itempos");
				GetLogger().log(LOG_INFO, "itemlist_formaction: toggling item read at pos `%s'", itemposname.c_str());
				if (itemposname.length() > 0) {
					std::istringstream posname(itemposname);
					unsigned int pos = 0;
					posname >> pos;
					v->set_status(_("Toggling read flag for article..."));
					visible_items[pos].first->set_unread(!visible_items[pos].first->unread());
					v->set_status("");
					do_redraw = true;
				}
			}
			break;
		case OP_SETFILTER: {
				char buf[256];
				snprintf(buf,sizeof(buf), "{hbox[lastline] .expand:0 {label .expand:0 text:\"%s\"}{input[filter] modal:1 .expand:h text[filtertext]:\"\"}}", _("Filter: "));
				f->modify("lastline", "replace", buf);
				f->set_focus("filter");
			}
			break;
		case OP_CLEARFILTER:
			apply_filter = false;
			do_redraw = true;
			break;
		default:
			break;
	}
	if (quit) {
		v->pop_current_formaction();
	}
}

void itemlist_formaction::do_update_visible_items() {
	std::vector<rss_item>& items = feed->items();

	if (visible_items.size() > 0)
		visible_items.erase(visible_items.begin(), visible_items.end());

	unsigned int i=0;
	for (std::vector<rss_item>::iterator it = items.begin(); it != items.end(); ++it, ++i) {
		if (!apply_filter || m.matches(&(*it))) {
			visible_items.push_back(std::pair<rss_item *, unsigned int>(&(*it), i));
		}
	}
}

void itemlist_formaction::prepare() {
	if (update_visible_items) {
		do_update_visible_items();
		update_visible_items = false;
	}

	if (do_redraw) {
		std::string code = "{list";

		for (std::vector<std::pair<rss_item *, unsigned int> >::iterator it = visible_items.begin(); it != visible_items.end(); ++it) {
			std::string line = "{listitem[";
			std::ostringstream x;
			x << it->second;
			line.append(x.str());
			line.append("] text:");
			std::string title;
			char buf[20];
			snprintf(buf,sizeof(buf),"%4u ",it->second + 1);
			title.append(buf);
			if (it->first->unread()) {
				title.append("N ");
			} else {
				title.append("  ");
			}
			char datebuf[64];
			time_t t = it->first->pubDate_timestamp();
			struct tm * stm = localtime(&t);
			strftime(datebuf,sizeof(datebuf), "%b %d   ", stm);
			title.append(datebuf);
			title.append(it->first->title());
			GetLogger().log(LOG_DEBUG, "itemlist_formaction: XXXTITLE it->first->title = `%s' title = `%s' quoted title = `%s'",
				it->first->title().c_str(), title.c_str(), stfl::quote(title).c_str());
			line.append(stfl::quote(title));
			line.append("}");
			code.append(line);
		}

		code.append("}");

		f->modify("items","replace_inner",code);

		set_head(feed->title(),feed->unread_item_count(),feed->items().size(), feed->rssurl());

		do_redraw = false;
	}
}

void itemlist_formaction::init() {
	f->set("itempos","0");
	f->set("msg","");
	do_redraw = true;
	set_keymap_hints();
}

void itemlist_formaction::set_head(const std::string& s, unsigned int unread, unsigned int total, const std::string &url) {
	char buf[1024];
	snprintf(buf, sizeof(buf), _("%s %s - Articles in feed '%s' (%u unread, %u total) - %s"), PROGRAM_NAME, PROGRAM_VERSION, s.c_str(), unread, total, url.c_str());
	f->set("head", buf);
}

bool itemlist_formaction::jump_to_next_unread_item(bool start_with_first) {
	unsigned int pos;
	std::istringstream is(f->get("itempos"));
	is >> pos;
	for (unsigned int i=(start_with_first?pos:(pos+1));i<visible_items.size();++i) {
		GetLogger().log(LOG_DEBUG, "itemlist_formaction::jump_to_next_unread_item: visible_items[%u] unread = %s", i, visible_items[i].first->unread() ? "true" : "false");
		if (visible_items[i].first->unread()) {
			std::ostringstream os;
			os << i;
			f->set("itempos", os.str());
			return true;
		}
	}
	for (unsigned int i=0;i<=pos;++i) {
		if (visible_items[i].first->unread()) {
			std::ostringstream os;
			os << i;
			f->set("itempos", os.str());
			return true;
		}
	}
	return false;
}

std::string itemlist_formaction::get_guid() {
	unsigned int pos;
	std::istringstream is(f->get("itempos"));
	is >> pos;
	return visible_items[pos].first->guid();
}

keymap_hint_entry * itemlist_formaction::get_keymap_hint() {
	static keymap_hint_entry hints[] = {
		{ OP_QUIT, _("Quit") },
		{ OP_OPEN, _("Open") },
		{ OP_SAVE, _("Save") },
		{ OP_RELOAD, _("Reload") },
		{ OP_NEXTUNREAD, _("Next Unread") },
		{ OP_MARKFEEDREAD, _("Mark All Read") },
		{ OP_SEARCH, _("Search") },
		{ OP_HELP, _("Help") },
		{ OP_NIL, NULL }
	};
	return hints;
}

void itemlist_formaction::handle_cmdline(const std::string& cmd) {
	unsigned int idx = 0;
	if (1==sscanf(cmd.c_str(),"%u",&idx)) {
		if (idx > 0 && idx <= visible_items[visible_items.size()-1].second + 1) {
			int i = get_pos(idx - 1);
			if (i == -1) {
				v->show_error(_("Position not visible!"));
			} else {
				std::ostringstream idxstr;
				idxstr << i;
				f->set("itempos", idxstr.str());
			}
		} else {
			v->show_error(_("Invalid position!"));
		}
	} else {
		// hand over all other commands to formaction
		formaction::handle_cmdline(cmd);
	}
}

int itemlist_formaction::get_pos(unsigned int realidx) {
	for (unsigned int i=0;i<visible_items.size();++i) {
		if (visible_items[i].second == realidx)
			return i;
	}
	return -1;
}

}
<commit_msg>Andreas Krennmair: 	pos -> itempos in some places, for clarification<commit_after>#include <itemlist_formaction.h>
#include <view.h>
#include <config.h>
#include <logger.h>

#include <sstream>

namespace newsbeuter {

itemlist_formaction::itemlist_formaction(view * vv, std::string formstr)
	: formaction(vv,formstr), feed(0), apply_filter(false), update_visible_items(true) {
}

itemlist_formaction::~itemlist_formaction() { }

void itemlist_formaction::process_operation(operation op) {
	bool quit = false;
	switch (op) {
		case OP_INT_END_SETFILTER: {
				std::string filtertext = f->get("filtertext");
				f->modify("lastline","replace","{hbox[lastline] .expand:0 {label[msglabel] .expand:h text[msg]:\"\"}}");
				if (filtertext.length() > 0) {
					if (!m.parse(filtertext)) {
						v->show_error(_("Error: couldn't parse filter command!"));
					} else {
						apply_filter = true;
						update_visible_items = true;
						do_redraw = true;
					}
				}
			}
			break;
		case OP_OPEN: {
				std::string itemposname = f->get("itempos");
				GetLogger().log(LOG_INFO, "itemlist_formaction: opening item at pos `%s'", itemposname.c_str());
				if (itemposname.length() > 0) {
					std::istringstream posname(itemposname);
					unsigned int itempos = 0;
					posname >> itempos;
					v->push_itemview(feed, visible_items[itempos].first->guid());
					do_redraw = true;
				} else {
					v->show_error(_("No item selected!")); // should not happen
				}
			}
			break;
		case OP_OPENINBROWSER: {
				std::string itemposname = f->get("itempos");
				GetLogger().log(LOG_INFO, "itemlist_formaction: opening item at pos `%s'", itemposname.c_str());
				if (itemposname.length() > 0) {
					std::istringstream posname(itemposname);
					unsigned int itempos = 0;
					posname >> itempos;
					if (itempos < visible_items.size()) {
						v->open_in_browser(visible_items[itempos].first->link());
						do_redraw = true;
					}
				} else {
					v->show_error(_("No item selected!")); // should not happen
				}
			}
			break;
		case OP_SAVE:
			{
				char buf[1024];
				std::string itemposname = f->get("itempos");
				GetLogger().log(LOG_INFO, "itemlist_formaction: saving item at pos `%s'", itemposname.c_str());
				if (itemposname.length() > 0) {
					std::istringstream posname(itemposname);
					unsigned int itempos = 0;
					posname >> itempos;

					std::string filename = v->run_filebrowser(FBT_SAVE,v->get_filename_suggestion(visible_items[itempos].first->title()));
					if (filename == "") {
						v->show_error(_("Aborted saving."));
					} else {
						try {
							v->write_item(*visible_items[itempos].first, filename);
							snprintf(buf, sizeof(buf), _("Saved article to %s"), filename.c_str());
							v->show_error(buf);

						} catch (...) {
							snprintf(buf, sizeof(buf), _("Error: couldn't save article to %s"), filename.c_str());
							v->show_error(buf);
						}
					}
				} else {
					v->show_error(_("Error: no item selected!"));
				}
			}
			break;
		case OP_HELP:
			v->push_help();
			break;
		case OP_RELOAD:
			GetLogger().log(LOG_INFO, "itemlist_formaction: reloading current feed");
			v->get_ctrl()->reload(pos);
			// feed = v->get_ctrl()->get_feed(pos);
			update_visible_items = true;
			do_redraw = true;
			break;
		case OP_QUIT:
			GetLogger().log(LOG_INFO, "itemlist_formaction: quitting");
			quit = true;
			break;
		case OP_NEXTUNREAD:
			GetLogger().log(LOG_INFO, "itemlist_formaction: jumping to next unread item");
			if (!jump_to_next_unread_item(false)) {
				v->show_error(_("No unread items."));
			}
			break;
		case OP_MARKFEEDREAD:
			GetLogger().log(LOG_INFO, "itemlist_formaction: marking feed read");
			v->set_status(_("Marking feed read..."));
			v->get_ctrl()->mark_all_read(pos);
			v->set_status("");
			do_redraw = true;
			break;
		case OP_SEARCH:
			v->run_search(feed->rssurl());
			break;
		case OP_TOGGLEITEMREAD: {
				std::string itemposname = f->get("itempos");
				GetLogger().log(LOG_INFO, "itemlist_formaction: toggling item read at pos `%s'", itemposname.c_str());
				if (itemposname.length() > 0) {
					std::istringstream posname(itemposname);
					unsigned int itempos = 0;
					posname >> itempos;
					v->set_status(_("Toggling read flag for article..."));
					visible_items[itempos].first->set_unread(!visible_items[itempos].first->unread());
					v->set_status("");
					do_redraw = true;
				}
			}
			break;
		case OP_SETFILTER: {
				char buf[256];
				snprintf(buf,sizeof(buf), "{hbox[lastline] .expand:0 {label .expand:0 text:\"%s\"}{input[filter] modal:1 .expand:h text[filtertext]:\"\"}}", _("Filter: "));
				f->modify("lastline", "replace", buf);
				f->set_focus("filter");
			}
			break;
		case OP_CLEARFILTER:
			apply_filter = false;
			do_redraw = true;
			break;
		default:
			break;
	}
	if (quit) {
		v->pop_current_formaction();
	}
}

void itemlist_formaction::do_update_visible_items() {
	std::vector<rss_item>& items = feed->items();

	if (visible_items.size() > 0)
		visible_items.erase(visible_items.begin(), visible_items.end());

	unsigned int i=0;
	for (std::vector<rss_item>::iterator it = items.begin(); it != items.end(); ++it, ++i) {
		if (!apply_filter || m.matches(&(*it))) {
			visible_items.push_back(std::pair<rss_item *, unsigned int>(&(*it), i));
		}
	}
}

void itemlist_formaction::prepare() {
	if (update_visible_items) {
		do_update_visible_items();
		update_visible_items = false;
	}

	if (do_redraw) {
		std::string code = "{list";

		for (std::vector<std::pair<rss_item *, unsigned int> >::iterator it = visible_items.begin(); it != visible_items.end(); ++it) {
			std::string line = "{listitem[";
			std::ostringstream x;
			x << it->second;
			line.append(x.str());
			line.append("] text:");
			std::string title;
			char buf[20];
			snprintf(buf,sizeof(buf),"%4u ",it->second + 1);
			title.append(buf);
			if (it->first->unread()) {
				title.append("N ");
			} else {
				title.append("  ");
			}
			char datebuf[64];
			time_t t = it->first->pubDate_timestamp();
			struct tm * stm = localtime(&t);
			strftime(datebuf,sizeof(datebuf), "%b %d   ", stm);
			title.append(datebuf);
			title.append(it->first->title());
			GetLogger().log(LOG_DEBUG, "itemlist_formaction: XXXTITLE it->first->title = `%s' title = `%s' quoted title = `%s'",
				it->first->title().c_str(), title.c_str(), stfl::quote(title).c_str());
			line.append(stfl::quote(title));
			line.append("}");
			code.append(line);
		}

		code.append("}");

		f->modify("items","replace_inner",code);

		set_head(feed->title(),feed->unread_item_count(),feed->items().size(), feed->rssurl());

		do_redraw = false;
	}
}

void itemlist_formaction::init() {
	f->set("itempos","0");
	f->set("msg","");
	do_redraw = true;
	set_keymap_hints();
}

void itemlist_formaction::set_head(const std::string& s, unsigned int unread, unsigned int total, const std::string &url) {
	char buf[1024];
	snprintf(buf, sizeof(buf), _("%s %s - Articles in feed '%s' (%u unread, %u total) - %s"), PROGRAM_NAME, PROGRAM_VERSION, s.c_str(), unread, total, url.c_str());
	f->set("head", buf);
}

bool itemlist_formaction::jump_to_next_unread_item(bool start_with_first) {
	unsigned int itempos;
	std::istringstream is(f->get("itempos"));
	is >> itempos;
	for (unsigned int i=(start_with_first?itempos:(itempos+1));i<visible_items.size();++i) {
		GetLogger().log(LOG_DEBUG, "itemlist_formaction::jump_to_next_unread_item: visible_items[%u] unread = %s", i, visible_items[i].first->unread() ? "true" : "false");
		if (visible_items[i].first->unread()) {
			std::ostringstream os;
			os << i;
			f->set("itempos", os.str());
			return true;
		}
	}
	for (unsigned int i=0;i<=itempos;++i) {
		if (visible_items[i].first->unread()) {
			std::ostringstream os;
			os << i;
			f->set("itempos", os.str());
			return true;
		}
	}
	return false;
}

std::string itemlist_formaction::get_guid() {
	unsigned int itempos;
	std::istringstream is(f->get("itempos"));
	is >> itempos;
	return visible_items[itempos].first->guid();
}

keymap_hint_entry * itemlist_formaction::get_keymap_hint() {
	static keymap_hint_entry hints[] = {
		{ OP_QUIT, _("Quit") },
		{ OP_OPEN, _("Open") },
		{ OP_SAVE, _("Save") },
		{ OP_RELOAD, _("Reload") },
		{ OP_NEXTUNREAD, _("Next Unread") },
		{ OP_MARKFEEDREAD, _("Mark All Read") },
		{ OP_SEARCH, _("Search") },
		{ OP_HELP, _("Help") },
		{ OP_NIL, NULL }
	};
	return hints;
}

void itemlist_formaction::handle_cmdline(const std::string& cmd) {
	unsigned int idx = 0;
	if (1==sscanf(cmd.c_str(),"%u",&idx)) {
		if (idx > 0 && idx <= visible_items[visible_items.size()-1].second + 1) {
			int i = get_pos(idx - 1);
			if (i == -1) {
				v->show_error(_("Position not visible!"));
			} else {
				std::ostringstream idxstr;
				idxstr << i;
				f->set("itempos", idxstr.str());
			}
		} else {
			v->show_error(_("Invalid position!"));
		}
	} else {
		// hand over all other commands to formaction
		formaction::handle_cmdline(cmd);
	}
}

int itemlist_formaction::get_pos(unsigned int realidx) {
	for (unsigned int i=0;i<visible_items.size();++i) {
		if (visible_items[i].second == realidx)
			return i;
	}
	return -1;
}

}
<|endoftext|>


<commit_before>#include "widgets/splits/SplitInput.hpp"

#include "Application.hpp"
#include "controllers/commands/CommandController.hpp"
#include "messages/Link.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchServer.hpp"
#include "singletons/Settings.hpp"
#include "singletons/Theme.hpp"
#include "util/LayoutCreator.hpp"
#include "widgets/Notebook.hpp"
#include "widgets/dialogs/EmotePopup.hpp"
#include "widgets/helper/ChannelView.hpp"
#include "widgets/helper/EffectLabel.hpp"
#include "widgets/helper/ResizingTextEdit.hpp"
#include "widgets/splits/Split.hpp"
#include "widgets/splits/SplitContainer.hpp"
#include "widgets/splits/SplitInput.hpp"

#include <QCompleter>
#include <QPainter>

namespace chatterino {

SplitInput::SplitInput(Split *_chatWidget)
    : BaseWidget(_chatWidget)
    , split_(_chatWidget)
{
    this->initLayout();

    auto completer =
        new QCompleter(&this->split_->getChannel().get()->completionModel);
    this->ui_.textEdit->setCompleter(completer);

    this->split_->channelChanged.connect([this] {
        auto completer =
            new QCompleter(&this->split_->getChannel()->completionModel);
        this->ui_.textEdit->setCompleter(completer);
    });

    // misc
    this->installKeyPressedEvent();
    this->scaleChangedEvent(this->getScale());
}

void SplitInput::initLayout()
{
    auto app = getApp();
    LayoutCreator<SplitInput> layoutCreator(this);

    auto layout =
        layoutCreator.setLayoutType<QHBoxLayout>().withoutMargin().assign(
            &this->ui_.hbox);

    // input
    auto textEdit =
        layout.emplace<ResizingTextEdit>().assign(&this->ui_.textEdit);
    connect(textEdit.getElement(), &ResizingTextEdit::textChanged, this,
            &SplitInput::editTextChanged);

    // right box
    auto box = layout.emplace<QVBoxLayout>().withoutMargin();
    box->setSpacing(0);
    {
        auto textEditLength =
            box.emplace<QLabel>().assign(&this->ui_.textEditLength);
        textEditLength->setAlignment(Qt::AlignRight);

        box->addStretch(1);
        box.emplace<EffectLabel>().assign(&this->ui_.emoteButton);
    }

    this->ui_.emoteButton->getLabel().setTextFormat(Qt::RichText);

    // ---- misc

    // set edit font
    this->ui_.textEdit->setFont(
        app->fonts->getFont(FontStyle::ChatMedium, this->getScale()));

    this->managedConnections_.push_back(app->fonts->fontChanged.connect([=]() {
        this->ui_.textEdit->setFont(
            app->fonts->getFont(FontStyle::ChatMedium, this->getScale()));
    }));

    // open emote popup
    QObject::connect(this->ui_.emoteButton, &EffectLabel::clicked, [this] {
        if (!this->emotePopup_) {
            this->emotePopup_ = std::make_unique<EmotePopup>();
            this->emotePopup_->linkClicked.connect([this](const Link &link) {
                if (link.type == Link::InsertText) {
                    this->insertText(link.value + " ");
                }
            });
        }

        this->emotePopup_->resize(int(300 * this->emotePopup_->getScale()),
                                  int(500 * this->emotePopup_->getScale()));
        this->emotePopup_->loadChannel(this->split_->getChannel());
        this->emotePopup_->show();
    });

    // clear channelview selection when selecting in the input
    QObject::connect(this->ui_.textEdit, &QTextEdit::copyAvailable,
                     [this](bool available) {
                         if (available) {
                             this->split_->view_->clearSelection();
                         }
                     });

    // textEditLength visibility
    getSettings()->showMessageLength.connect(
        [this](const bool &value, auto) {
            this->ui_.textEditLength->setHidden(!value);
        },
        this->managedConnections_);
}

void SplitInput::scaleChangedEvent(float scale)
{
    // update the icon size of the emote button
    this->updateEmoteButton();

    // set maximum height
    this->setMaximumHeight(int(150 * this->getScale()));
    this->ui_.textEdit->setFont(
        getApp()->fonts->getFont(FontStyle::ChatMedium, this->getScale()));
}

void SplitInput::themeChangedEvent()
{
    QPalette palette;

    palette.setColor(QPalette::Foreground, this->theme->splits.input.text);

    this->updateEmoteButton();
    this->ui_.textEditLength->setPalette(palette);

    this->ui_.textEdit->setStyleSheet(this->theme->splits.input.styleSheet);

    this->ui_.hbox->setMargin(
        int((this->theme->isLightTheme() ? 4 : 2) * this->getScale()));

    this->ui_.emoteButton->getLabel().setStyleSheet("color: #000");
}

void SplitInput::updateEmoteButton()
{
    float scale = this->getScale();

    QString text = "";
    text.replace("xD", QString::number(int(12 * scale)));

    if (this->theme->isLightTheme()) {
        text.replace("emote", "emote_dark");
    }

    this->ui_.emoteButton->getLabel().setText(text);
    this->ui_.emoteButton->setFixedHeight(int(18 * scale));
}

void SplitInput::installKeyPressedEvent()
{
    auto app = getApp();

    this->ui_.textEdit->keyPressed.connect([this, app](QKeyEvent *event) {
        if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) {
            auto c = this->split_->getChannel();
            if (c == nullptr) {
                return;
            }

            QString message = ui_.textEdit->toPlainText();

            QString sendMessage = app->commands->execCommand(message, c, false);
            sendMessage = sendMessage.replace('\n', ' ');

            c->sendMessage(sendMessage);
            // don't add duplicate messages to message history
            if (this->prevMsg_.isEmpty() || !this->prevMsg_.endsWith(message))
                this->prevMsg_.append(message);

            event->accept();
            if (!(event->modifiers() == Qt::ControlModifier)) {
                this->currMsg_ = QString();
                this->ui_.textEdit->setText(QString());
                this->prevIndex_ = 0;
            } else if (this->ui_.textEdit->toPlainText() ==
                       this->prevMsg_.at(this->prevMsg_.size() - 1)) {
                this->prevMsg_.removeLast();
            }
            this->prevIndex_ = this->prevMsg_.size();
        } else if (event->key() == Qt::Key_Up) {
            if ((event->modifiers() & Qt::ShiftModifier) != 0) {
                return;
            }
            if (event->modifiers() == Qt::AltModifier) {
                SplitContainer *page = this->split_->getContainer();

                if (page != nullptr) {
                    page->selectNextSplit(SplitContainer::Above);
                }
            } else {
                if (this->prevMsg_.size() && this->prevIndex_) {
                    if (this->prevIndex_ == (this->prevMsg_.size())) {
                        this->currMsg_ = ui_.textEdit->toPlainText();
                    }

                    this->prevIndex_--;
                    this->ui_.textEdit->setText(
                        this->prevMsg_.at(this->prevIndex_));

                    QTextCursor cursor = this->ui_.textEdit->textCursor();
                    cursor.movePosition(QTextCursor::End);
                    this->ui_.textEdit->setTextCursor(cursor);
                }
            }
        } else if (event->key() == Qt::Key_Down) {
            if ((event->modifiers() & Qt::ShiftModifier) != 0) {
                return;
            }
            if (event->modifiers() == Qt::AltModifier) {
                SplitContainer *page = this->split_->getContainer();

                if (page != nullptr) {
                    page->selectNextSplit(SplitContainer::Below);
                }
            } else {
                if (this->prevIndex_ != (this->prevMsg_.size() - 1) &&
                    this->prevIndex_ != this->prevMsg_.size()) {
                    this->prevIndex_++;
                    this->ui_.textEdit->setText(
                        this->prevMsg_.at(this->prevIndex_));
                } else {
                    this->prevIndex_ = this->prevMsg_.size();
                    this->ui_.textEdit->setText(this->currMsg_);
                }

                QTextCursor cursor = this->ui_.textEdit->textCursor();
                cursor.movePosition(QTextCursor::End);
                this->ui_.textEdit->setTextCursor(cursor);
            }
        } else if (event->key() == Qt::Key_Left) {
            if (event->modifiers() == Qt::AltModifier) {
                SplitContainer *page = this->split_->getContainer();

                if (page != nullptr) {
                    page->selectNextSplit(SplitContainer::Left);
                }
            }
        } else if (event->key() == Qt::Key_Right) {
            if (event->modifiers() == Qt::AltModifier) {
                SplitContainer *page = this->split_->getContainer();

                if (page != nullptr) {
                    page->selectNextSplit(SplitContainer::Right);
                }
            }
        } else if (event->key() == Qt::Key_Tab) {
            if (event->modifiers() == Qt::ControlModifier) {
                SplitContainer *page =
                    static_cast<SplitContainer *>(this->split_->parentWidget());

                Notebook *notebook =
                    static_cast<Notebook *>(page->parentWidget());

                notebook->selectNextTab();
            }
        } else if (event->key() == Qt::Key_Backtab) {
            if (event->modifiers() ==
                (Qt::ControlModifier | Qt::ShiftModifier)) {
                SplitContainer *page =
                    static_cast<SplitContainer *>(this->split_->parentWidget());

                Notebook *notebook =
                    static_cast<Notebook *>(page->parentWidget());

                notebook->selectPreviousTab();
            }
        } else if (event->key() == Qt::Key_C &&
                   event->modifiers() == Qt::ControlModifier) {
            if (this->split_->view_->hasSelection()) {
                this->split_->copyToClipboard();
                event->accept();
            }
        }
    });
}

void SplitInput::clearSelection()
{
    QTextCursor c = this->ui_.textEdit->textCursor();

    c.setPosition(c.position());
    c.setPosition(c.position(), QTextCursor::KeepAnchor);

    this->ui_.textEdit->setTextCursor(c);
}

QString SplitInput::getInputText() const
{
    return this->ui_.textEdit->toPlainText();
}

void SplitInput::insertText(const QString &text)
{
    this->ui_.textEdit->insertPlainText(text);
}

void SplitInput::editTextChanged()
{
    auto app = getApp();

    // set textLengthLabel value
    QString text = this->ui_.textEdit->toPlainText();

    if (text.startsWith("/r ", Qt::CaseInsensitive) &&
        this->split_->getChannel()->isTwitchChannel())  //
    {
        QString lastUser = app->twitch.server->lastUserThatWhisperedMe.get();
        if (!lastUser.isEmpty()) {
            this->ui_.textEdit->setPlainText("/w " + lastUser + text.mid(2));
            this->ui_.textEdit->moveCursor(QTextCursor::EndOfBlock);
        }
    } else {
        this->textChanged.invoke(text);

        text = text.trimmed();
        static QRegularExpression spaceRegex("\\s\\s+");
        text = text.replace(spaceRegex, " ");

        text =
            app->commands->execCommand(text, this->split_->getChannel(), true);
    }

    QString labelText;

    if (text.length() == 0) {
        labelText = "";
    } else {
        labelText = QString::number(text.length());
    }

    this->ui_.textEditLength->setText(labelText);
}

void SplitInput::paintEvent(QPaintEvent *)
{
    QPainter painter(this);

    if (this->theme->isLightTheme()) {
        int s = int(3 * this->getScale());
        QRect rect = this->rect().marginsRemoved(QMargins(s - 1, s - 1, s, s));

        painter.fillRect(rect, this->theme->splits.input.background);

        painter.setPen(QColor("#ccc"));
        painter.drawRect(rect);
    } else {
        int s = int(1 * this->getScale());
        QRect rect = this->rect().marginsRemoved(QMargins(s - 1, s - 1, s, s));

        painter.fillRect(rect, this->theme->splits.input.background);

        painter.setPen(QColor("#333"));
        painter.drawRect(rect);
    }

    //    int offset = 2;
    //    painter.fillRect(offset, this->height() - offset, this->width() - 2 *
    //    offset, 1,
    //                     getApp()->themes->splits.input.focusedLine);
}

void SplitInput::resizeEvent(QResizeEvent *)
{
    if (this->height() == this->maximumHeight()) {
        this->ui_.textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
    } else {
        this->ui_.textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    }
}

void SplitInput::mousePressEvent(QMouseEvent *)
{
    this->split_->giveFocus(Qt::MouseFocusReason);
}

}  // namespace chatterino
<commit_msg>Don't add empty messages to message history. (#674)<commit_after>#include "widgets/splits/SplitInput.hpp"

#include "Application.hpp"
#include "controllers/commands/CommandController.hpp"
#include "messages/Link.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchServer.hpp"
#include "singletons/Settings.hpp"
#include "singletons/Theme.hpp"
#include "util/LayoutCreator.hpp"
#include "widgets/Notebook.hpp"
#include "widgets/dialogs/EmotePopup.hpp"
#include "widgets/helper/ChannelView.hpp"
#include "widgets/helper/EffectLabel.hpp"
#include "widgets/helper/ResizingTextEdit.hpp"
#include "widgets/splits/Split.hpp"
#include "widgets/splits/SplitContainer.hpp"
#include "widgets/splits/SplitInput.hpp"

#include <QCompleter>
#include <QPainter>

namespace chatterino {

SplitInput::SplitInput(Split *_chatWidget)
    : BaseWidget(_chatWidget)
    , split_(_chatWidget)
{
    this->initLayout();

    auto completer =
        new QCompleter(&this->split_->getChannel().get()->completionModel);
    this->ui_.textEdit->setCompleter(completer);

    this->split_->channelChanged.connect([this] {
        auto completer =
            new QCompleter(&this->split_->getChannel()->completionModel);
        this->ui_.textEdit->setCompleter(completer);
    });

    // misc
    this->installKeyPressedEvent();
    this->scaleChangedEvent(this->getScale());
}

void SplitInput::initLayout()
{
    auto app = getApp();
    LayoutCreator<SplitInput> layoutCreator(this);

    auto layout =
        layoutCreator.setLayoutType<QHBoxLayout>().withoutMargin().assign(
            &this->ui_.hbox);

    // input
    auto textEdit =
        layout.emplace<ResizingTextEdit>().assign(&this->ui_.textEdit);
    connect(textEdit.getElement(), &ResizingTextEdit::textChanged, this,
            &SplitInput::editTextChanged);

    // right box
    auto box = layout.emplace<QVBoxLayout>().withoutMargin();
    box->setSpacing(0);
    {
        auto textEditLength =
            box.emplace<QLabel>().assign(&this->ui_.textEditLength);
        textEditLength->setAlignment(Qt::AlignRight);

        box->addStretch(1);
        box.emplace<EffectLabel>().assign(&this->ui_.emoteButton);
    }

    this->ui_.emoteButton->getLabel().setTextFormat(Qt::RichText);

    // ---- misc

    // set edit font
    this->ui_.textEdit->setFont(
        app->fonts->getFont(FontStyle::ChatMedium, this->getScale()));

    this->managedConnections_.push_back(app->fonts->fontChanged.connect([=]() {
        this->ui_.textEdit->setFont(
            app->fonts->getFont(FontStyle::ChatMedium, this->getScale()));
    }));

    // open emote popup
    QObject::connect(this->ui_.emoteButton, &EffectLabel::clicked, [this] {
        if (!this->emotePopup_) {
            this->emotePopup_ = std::make_unique<EmotePopup>();
            this->emotePopup_->linkClicked.connect([this](const Link &link) {
                if (link.type == Link::InsertText) {
                    this->insertText(link.value + " ");
                }
            });
        }

        this->emotePopup_->resize(int(300 * this->emotePopup_->getScale()),
                                  int(500 * this->emotePopup_->getScale()));
        this->emotePopup_->loadChannel(this->split_->getChannel());
        this->emotePopup_->show();
    });

    // clear channelview selection when selecting in the input
    QObject::connect(this->ui_.textEdit, &QTextEdit::copyAvailable,
                     [this](bool available) {
                         if (available) {
                             this->split_->view_->clearSelection();
                         }
                     });

    // textEditLength visibility
    getSettings()->showMessageLength.connect(
        [this](const bool &value, auto) {
            this->ui_.textEditLength->setHidden(!value);
        },
        this->managedConnections_);
}

void SplitInput::scaleChangedEvent(float scale)
{
    // update the icon size of the emote button
    this->updateEmoteButton();

    // set maximum height
    this->setMaximumHeight(int(150 * this->getScale()));
    this->ui_.textEdit->setFont(
        getApp()->fonts->getFont(FontStyle::ChatMedium, this->getScale()));
}

void SplitInput::themeChangedEvent()
{
    QPalette palette;

    palette.setColor(QPalette::Foreground, this->theme->splits.input.text);

    this->updateEmoteButton();
    this->ui_.textEditLength->setPalette(palette);

    this->ui_.textEdit->setStyleSheet(this->theme->splits.input.styleSheet);

    this->ui_.hbox->setMargin(
        int((this->theme->isLightTheme() ? 4 : 2) * this->getScale()));

    this->ui_.emoteButton->getLabel().setStyleSheet("color: #000");
}

void SplitInput::updateEmoteButton()
{
    float scale = this->getScale();

    QString text = "";
    text.replace("xD", QString::number(int(12 * scale)));

    if (this->theme->isLightTheme()) {
        text.replace("emote", "emote_dark");
    }

    this->ui_.emoteButton->getLabel().setText(text);
    this->ui_.emoteButton->setFixedHeight(int(18 * scale));
}

void SplitInput::installKeyPressedEvent()
{
    auto app = getApp();

    this->ui_.textEdit->keyPressed.connect([this, app](QKeyEvent *event) {
        if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) {
            auto c = this->split_->getChannel();
            if (c == nullptr) {
                return;
            }

            QString message = ui_.textEdit->toPlainText();

            QString sendMessage = app->commands->execCommand(message, c, false);
            sendMessage = sendMessage.replace('\n', ' ');

            c->sendMessage(sendMessage);
            // don't add duplicate messages and empty message to message history
            if ((this->prevMsg_.isEmpty() || !this->prevMsg_.endsWith(message)) &&
                !message.trimmed().isEmpty())
                this->prevMsg_.append(message);

            event->accept();
            if (!(event->modifiers() == Qt::ControlModifier)) {
                this->currMsg_ = QString();
                this->ui_.textEdit->setText(QString());
                this->prevIndex_ = 0;
            } else if (this->ui_.textEdit->toPlainText() ==
                       this->prevMsg_.at(this->prevMsg_.size() - 1)) {
                this->prevMsg_.removeLast();
            }
            this->prevIndex_ = this->prevMsg_.size();
        } else if (event->key() == Qt::Key_Up) {
            if ((event->modifiers() & Qt::ShiftModifier) != 0) {
                return;
            }
            if (event->modifiers() == Qt::AltModifier) {
                SplitContainer *page = this->split_->getContainer();

                if (page != nullptr) {
                    page->selectNextSplit(SplitContainer::Above);
                }
            } else {
                if (this->prevMsg_.size() && this->prevIndex_) {
                    if (this->prevIndex_ == (this->prevMsg_.size())) {
                        this->currMsg_ = ui_.textEdit->toPlainText();
                    }

                    this->prevIndex_--;
                    this->ui_.textEdit->setText(
                        this->prevMsg_.at(this->prevIndex_));

                    QTextCursor cursor = this->ui_.textEdit->textCursor();
                    cursor.movePosition(QTextCursor::End);
                    this->ui_.textEdit->setTextCursor(cursor);
                }
            }
        } else if (event->key() == Qt::Key_Down) {
            if ((event->modifiers() & Qt::ShiftModifier) != 0) {
                return;
            }
            if (event->modifiers() == Qt::AltModifier) {
                SplitContainer *page = this->split_->getContainer();

                if (page != nullptr) {
                    page->selectNextSplit(SplitContainer::Below);
                }
            } else {
                if (this->prevIndex_ != (this->prevMsg_.size() - 1) &&
                    this->prevIndex_ != this->prevMsg_.size()) {
                    this->prevIndex_++;
                    this->ui_.textEdit->setText(
                        this->prevMsg_.at(this->prevIndex_));
                } else {
                    this->prevIndex_ = this->prevMsg_.size();
                    this->ui_.textEdit->setText(this->currMsg_);
                }

                QTextCursor cursor = this->ui_.textEdit->textCursor();
                cursor.movePosition(QTextCursor::End);
                this->ui_.textEdit->setTextCursor(cursor);
            }
        } else if (event->key() == Qt::Key_Left) {
            if (event->modifiers() == Qt::AltModifier) {
                SplitContainer *page = this->split_->getContainer();

                if (page != nullptr) {
                    page->selectNextSplit(SplitContainer::Left);
                }
            }
        } else if (event->key() == Qt::Key_Right) {
            if (event->modifiers() == Qt::AltModifier) {
                SplitContainer *page = this->split_->getContainer();

                if (page != nullptr) {
                    page->selectNextSplit(SplitContainer::Right);
                }
            }
        } else if (event->key() == Qt::Key_Tab) {
            if (event->modifiers() == Qt::ControlModifier) {
                SplitContainer *page =
                    static_cast<SplitContainer *>(this->split_->parentWidget());

                Notebook *notebook =
                    static_cast<Notebook *>(page->parentWidget());

                notebook->selectNextTab();
            }
        } else if (event->key() == Qt::Key_Backtab) {
            if (event->modifiers() ==
                (Qt::ControlModifier | Qt::ShiftModifier)) {
                SplitContainer *page =
                    static_cast<SplitContainer *>(this->split_->parentWidget());

                Notebook *notebook =
                    static_cast<Notebook *>(page->parentWidget());

                notebook->selectPreviousTab();
            }
        } else if (event->key() == Qt::Key_C &&
                   event->modifiers() == Qt::ControlModifier) {
            if (this->split_->view_->hasSelection()) {
                this->split_->copyToClipboard();
                event->accept();
            }
        }
    });
}

void SplitInput::clearSelection()
{
    QTextCursor c = this->ui_.textEdit->textCursor();

    c.setPosition(c.position());
    c.setPosition(c.position(), QTextCursor::KeepAnchor);

    this->ui_.textEdit->setTextCursor(c);
}

QString SplitInput::getInputText() const
{
    return this->ui_.textEdit->toPlainText();
}

void SplitInput::insertText(const QString &text)
{
    this->ui_.textEdit->insertPlainText(text);
}

void SplitInput::editTextChanged()
{
    auto app = getApp();

    // set textLengthLabel value
    QString text = this->ui_.textEdit->toPlainText();

    if (text.startsWith("/r ", Qt::CaseInsensitive) &&
        this->split_->getChannel()->isTwitchChannel())  //
    {
        QString lastUser = app->twitch.server->lastUserThatWhisperedMe.get();
        if (!lastUser.isEmpty()) {
            this->ui_.textEdit->setPlainText("/w " + lastUser + text.mid(2));
            this->ui_.textEdit->moveCursor(QTextCursor::EndOfBlock);
        }
    } else {
        this->textChanged.invoke(text);

        text = text.trimmed();
        static QRegularExpression spaceRegex("\\s\\s+");
        text = text.replace(spaceRegex, " ");

        text =
            app->commands->execCommand(text, this->split_->getChannel(), true);
    }

    QString labelText;

    if (text.length() == 0) {
        labelText = "";
    } else {
        labelText = QString::number(text.length());
    }

    this->ui_.textEditLength->setText(labelText);
}

void SplitInput::paintEvent(QPaintEvent *)
{
    QPainter painter(this);

    if (this->theme->isLightTheme()) {
        int s = int(3 * this->getScale());
        QRect rect = this->rect().marginsRemoved(QMargins(s - 1, s - 1, s, s));

        painter.fillRect(rect, this->theme->splits.input.background);

        painter.setPen(QColor("#ccc"));
        painter.drawRect(rect);
    } else {
        int s = int(1 * this->getScale());
        QRect rect = this->rect().marginsRemoved(QMargins(s - 1, s - 1, s, s));

        painter.fillRect(rect, this->theme->splits.input.background);

        painter.setPen(QColor("#333"));
        painter.drawRect(rect);
    }

    //    int offset = 2;
    //    painter.fillRect(offset, this->height() - offset, this->width() - 2 *
    //    offset, 1,
    //                     getApp()->themes->splits.input.focusedLine);
}

void SplitInput::resizeEvent(QResizeEvent *)
{
    if (this->height() == this->maximumHeight()) {
        this->ui_.textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
    } else {
        this->ui_.textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    }
}

void SplitInput::mousePressEvent(QMouseEvent *)
{
    this->split_->giveFocus(Qt::MouseFocusReason);
}

}  // namespace chatterino
<|endoftext|>