WxWidgets:Example:GraphPanel
wxWidgets를 사용한 그래프를 그려주는 패널 구현.
Header file
/**
* @file MugcupGraph.hpp
* @brief MugcupGraph class prototype.
* @author your
* @date 2018-06-15
*/
#ifndef __INCLUDE_LIBMS__LIBMUGCUP_SRC_UI_COMPONENTS_MUGCUPGRAPH_HPP__
#define __INCLUDE_LIBMS__LIBMUGCUP_SRC_UI_COMPONENTS_MUGCUPGRAPH_HPP__
// MS compatible compilers support #pragma once
#if defined(_MSC_VER) && (_MSC_VER >= 1020)
#pragma once
#endif
#include <libtbag/config.h>
#include <libtbag/predef.hpp>
#include <libmugcup/src/config.h>
#include <map>
#include <vector>
#include <wx/wx.h>
// ---------------------
NAMESPACE_LIBMUGCUP_OPEN
// ---------------------
/**
* MugcupGraph class prototype.
*
* @author your
* @date 2018-06-15
*/
class MugcupGraph : public wxPanel
{
public:
wxDECLARE_EVENT_TABLE();
public:
TBAG_CONSTEXPR static unsigned long const BACKGROUND_COLOR = 0x403e3b;
TBAG_CONSTEXPR static unsigned long const REMARKS_TEXT_COLOR = 0xd8d9da;
TBAG_CONSTEXPR static unsigned long const GUIDE_LINE_COLOR = 0x828282;
TBAG_CONSTEXPR static unsigned long const GUIDE_TEXT_COLOR = 0xd8d9da;
TBAG_CONSTEXPR static unsigned long const DEBUGGING_COLOR = 0x0024ff;
TBAG_CONSTEXPR static unsigned long const DATA_LINE_COLOR = 0x0024ff;
TBAG_CONSTEXPR static unsigned long const DATA_TEXT_COLOR = 0xd8d9da;
TBAG_CONSTEXPR static unsigned long const DISABLE_RECT_COLOR = 0x242424;
TBAG_CONSTEXPR static unsigned long const DISABLE_TEXT_COLOR = 0x999999;
TBAG_CONSTEXPR static int const PADDING_SIZE = 8;
TBAG_CONSTEXPR static int const STEP_PADDING_SIZE = 4;
TBAG_CONSTEXPR static int const TITLE_TEXT_SIZE = 14;
TBAG_CONSTEXPR static int const REMARKS_TEXT_SIZE = 10;
TBAG_CONSTEXPR static int const GUIDE_LINE_WIDTH = 1;
TBAG_CONSTEXPR static int const GUIDE_TEXT_SIZE = 8;
TBAG_CONSTEXPR static int const DATA_LINE_WIDTH = 2;
TBAG_CONSTEXPR static int const DATA_TEXT_SIZE = 8;
public:
using Points = std::vector<wxRealPoint>;
public:
struct Remarks
{
bool show = true;
int size = REMARKS_TEXT_SIZE;
wxColour color = REMARKS_TEXT_COLOR;
wxString text;
};
struct Guide
{
bool show = true;
int line_width = GUIDE_LINE_WIDTH;
int text_size = GUIDE_TEXT_SIZE;
wxColour line_color = GUIDE_LINE_COLOR;
wxColour text_color = GUIDE_TEXT_COLOR;
// Dynamic graph properties.
double start_data = 0.0; ///< The starting value of the data.
double print_begin = 0.0; ///< The beginning value of the print data.
double print_end = 1.0; ///< The end value of the print data.
double print_step = 0.1; ///< Guidelines for print data.
bool show_begin = true;
bool show_end = true;
bool show_text = true;
bool integer_text = false;
};
struct Data
{
bool show = true;
int line_width = DATA_LINE_WIDTH;
int text_size = DATA_TEXT_SIZE;
wxColour line_color = DATA_LINE_COLOR;
wxColour text_color = DATA_TEXT_COLOR;
wxString text;
Points points;
/** Internal variable. */
struct {
wxRect rect;
} internal;
};
public:
using Datas = std::vector<Data>;
private:
Remarks _title;
private:
wxString _integer_format;
wxString _floating_format;
private:
Remarks _top;
Remarks _left;
Remarks _right;
Remarks _bottom;
private:
Guide _guide_x;
Guide _guide_y;
private:
// @formatter:off
wxCoord _padding = PADDING_SIZE;
wxCoord _step_padding = STEP_PADDING_SIZE;
// @formatter:on
private:
Datas _datas;
public:
MugcupGraph(wxWindow * parent,
wxWindowID id = wxID_ANY,
wxPoint const & pos = wxDefaultPosition,
wxSize const & size = wxDefaultSize);
virtual ~MugcupGraph();
public:
inline Remarks & atTitle() TBAG_NOEXCEPT { return _title; }
inline Remarks const & atTitle() const TBAG_NOEXCEPT { return _title; }
public:
inline wxString getIntegerFormat() const TBAG_NOEXCEPT { return _integer_format; }
inline wxString getFloatingFormat() const TBAG_NOEXCEPT { return _floating_format; }
public:
inline Remarks & atTop () TBAG_NOEXCEPT { return _top; }
inline Remarks const & atTop () const TBAG_NOEXCEPT { return _top; }
inline Remarks & atLeft () TBAG_NOEXCEPT { return _left; }
inline Remarks const & atLeft () const TBAG_NOEXCEPT { return _left; }
inline Remarks & atRight () TBAG_NOEXCEPT { return _right; }
inline Remarks const & atRight () const TBAG_NOEXCEPT { return _right; }
inline Remarks & atBottom() TBAG_NOEXCEPT { return _bottom; }
inline Remarks const & atBottom() const TBAG_NOEXCEPT { return _bottom; }
public:
inline Guide & atGuideX() TBAG_NOEXCEPT { return _guide_x; }
inline Guide const & atGuideX() const TBAG_NOEXCEPT { return _guide_x; }
inline Guide & atGuideY() TBAG_NOEXCEPT { return _guide_y; }
inline Guide const & atGuideY() const TBAG_NOEXCEPT { return _guide_y; }
public:
inline wxCoord getPadding() const TBAG_NOEXCEPT { return _padding; }
inline wxCoord getStepPadding() const TBAG_NOEXCEPT { return _step_padding; }
inline void setPadding(wxCoord value) TBAG_NOEXCEPT { _padding = value; }
inline void setStepPadding(wxCoord value) TBAG_NOEXCEPT { _step_padding = value; }
public:
inline Datas & atDatas() TBAG_NOEXCEPT { return _datas; }
inline Datas const & atDatas() const TBAG_NOEXCEPT { return _datas; }
public:
wxFont getDefaultFont(int size);
protected:
void onPaint(wxPaintEvent & event);
void onLeftDown(wxMouseEvent & event);
void onLeftUp(wxMouseEvent & event);
void onMotion(wxMouseEvent & event);
private:
void drawTitle(wxDC & dc, int & top);
void drawTop(wxDC & dc, int & top);
void drawBottom(wxDC & dc, int & bottom);
void drawLeft(wxDC & dc, int & left);
void drawRight(wxDC & dc, int & right);
private:
void drawDataRemarks(wxDC & dc, int top, int & right, int rect_width = 24);
void findMaxTextSize(wxDC & dc, wxSize & size);
private:
void updateGuideXOffset(wxDC & dc, int & right, int & bottom);
void updateGuideYOffset(wxDC & dc, int & left, int & top);
private:
enum class GuideDirection
{
GD_HORIZONTAL,
GD_VERTICAL,
};
private:
void drawGuide(wxDC & dc, Guide const & guide,
int graph_begin, int graph_end, int line_begin, int line_end,
GuideDirection direction);
void drawGuideLine(wxDC & dc, bool show_text, bool integer_text, GuideDirection direction,
double text_value, int graph_value, int line_begin, int line_end,
wxRect & prev_drawing_label);
private:
void drawPoints(wxDC & dc, wxPoint const & lt, wxPoint const & rb);
private:
void drawDebuggingRect(wxDC & dc, wxRect const & rect);
};
// ----------------------
NAMESPACE_LIBMUGCUP_CLOSE
// ----------------------
#endif // __INCLUDE_LIBMS__LIBMUGCUP_SRC_UI_COMPONENTS_MUGCUPGRAPH_HPP__
Source file
/**
* @file MugcupGraph.cpp
* @brief MugcupGraph class implementation.
* @author your
* @date 2018-06-15
*/
#include <libmugcup/src/ui/components/MugcupGraph.hpp>
#include <libtbag/random/Random.hpp>
#include <wx/dcbuffer.h>
#define ENABLE_DUMMY_DATA
//#define ENABLE_ON_PAINT_DEBUGGING
// ---------------------
NAMESPACE_LIBMUGCUP_OPEN
// ---------------------
wxBEGIN_EVENT_TABLE(MugcupGraph, wxPanel)
EVT_PAINT(MugcupGraph::onPaint)
EVT_LEFT_DOWN(MugcupGraph::onLeftDown)
EVT_LEFT_UP(MugcupGraph::onLeftUp)
EVT_MOTION(MugcupGraph::onMotion)
wxEND_EVENT_TABLE()
MugcupGraph::MugcupGraph(wxWindow * parent,
wxWindowID id,
wxPoint const & pos,
wxSize const & size)
: wxPanel(parent, id, pos, size),
_integer_format(wxT("%d")),
_floating_format(wxT("%.2f"))
{
SetBackgroundColour(BACKGROUND_COLOR);
#if defined(ENABLE_DUMMY_DATA)
// @formatter:off
_title .text = wxT("[TITLE]");
_title .size = TITLE_TEXT_SIZE;
_top .text = wxT("Top content");
_top .show = true;
_left .text = wxT("Left content");
_left .show = true;
_right .text = wxT("Right content");
_right .show = true;
_bottom.text = wxT("Bottom content");
_bottom.show = true;
// @formatter:on
// @formatter:off
_guide_x.start_data = 0.05;
_guide_x.print_begin = 0.5;
_guide_x.print_end = 1.0;
_guide_x.print_step = 0.1;
// ------------------------
_guide_y.start_data = 0.0;
_guide_y.print_begin = 0.0;
_guide_y.print_end = 1.0;
_guide_y.print_step = 0.5;
// @formatter:on
_datas.resize(2U);
_datas[0].line_color.Set(wxT("#fa8e00"));
_datas[0].text = wxT("First");
_datas[1].line_color.Set(wxT("#619e1b"));
_datas[1].text = wxT("Second");
int const DATA_COUNT = 10;
double const STEP = (_guide_x.print_end - _guide_x.print_begin) / DATA_COUNT;
for (int i = 0; i <= DATA_COUNT; ++i) {
auto y1 = libtbag::random::gen<int>(0, 99);
_datas[0].points.emplace_back(_guide_x.print_begin + (STEP * i), y1*0.01);
auto y2 = libtbag::random::gen<int>(0, 99);
_datas[1].points.emplace_back(_guide_x.print_begin + (STEP * i), y2*0.01);
}
#endif
}
MugcupGraph::~MugcupGraph()
{
// EMPTY.
}
wxFont MugcupGraph::getDefaultFont(int size)
{
return wxFont(size, wxFontFamily::wxFONTFAMILY_DEFAULT,
wxFontStyle::wxFONTSTYLE_NORMAL,
wxFontWeight::wxFONTWEIGHT_NORMAL);
}
void MugcupGraph::onPaint(wxPaintEvent & event)
{
wxPaintDC dc(this);
dc.SetBackground(wxBrush(GetBackgroundColour()));
dc.Clear();
wxSize const WINDOW_SIZE = GetSize();
wxPoint lt(_padding, _padding);
wxPoint rb(WINDOW_SIZE.x - _padding, WINDOW_SIZE.y - _padding);
// @formatter:off
if (_title .show) { drawTitle (dc, lt.y); }
if (_top .show) { drawTop (dc, lt.y); }
if (_bottom.show) { drawBottom(dc, rb.y); }
if (_left .show) { drawLeft (dc, lt.x); }
drawDataRemarks(dc, lt.y, rb.x);
if (_right .show) { drawRight (dc, rb.x); }
// @formatter:on
#if defined(ENABLE_ON_PAINT_DEBUGGING)
drawDebuggingRect(dc, wxRect(lt, rb));
#endif
// @formatter:off
if (_guide_x.show && _guide_x.show_text) { updateGuideXOffset(dc, rb.x, rb.y); }
if (_guide_y.show && _guide_y.show_text) { updateGuideYOffset(dc, lt.x, lt.y); }
// @formatter:on
// @formatter:off
if (_guide_x.show) { drawGuide(dc, _guide_x, lt.x, rb.x, lt.y, rb.y, GuideDirection::GD_HORIZONTAL); }
if (_guide_y.show) { drawGuide(dc, _guide_y, lt.y, rb.y, lt.x, rb.x, GuideDirection::GD_VERTICAL); }
// @formatter:on
drawPoints(dc, lt, rb);
}
void MugcupGraph::onLeftDown(wxMouseEvent & event)
{
// EMPTY.
}
void MugcupGraph::onLeftUp(wxMouseEvent & event)
{
int change_count = 0;
for (auto & data : _datas) {
if (data.internal.rect.Contains(event.m_x, event.m_y)) {
data.show = !(data.show);
++change_count;
}
}
if (change_count >= 1) {
Update();
Refresh();
}
}
void MugcupGraph::onMotion(wxMouseEvent & event)
{
// EMPTY.
}
void MugcupGraph::drawTitle(wxDC & dc, int & top)
{
wxCoord width, height;
dc.SetFont(getDefaultFont(_title.size));
dc.SetTextForeground(_title.color);
dc.GetTextExtent(_title.text, &width, &height);
dc.DrawText(_title.text, (GetSize().x - width) / 2, top);
top += (_step_padding + height);
}
void MugcupGraph::drawTop(wxDC & dc, int & top)
{
wxCoord width, height;
dc.SetFont(getDefaultFont(_top.size));
dc.SetTextForeground(_top.color);
dc.GetTextExtent(_top.text, &width, &height);
dc.DrawText(_top.text, (GetSize().x - width) / 2, top);
top += (_step_padding + height);
}
void MugcupGraph::drawBottom(wxDC & dc, int & bottom)
{
wxCoord width, height;
dc.SetFont(getDefaultFont(_bottom.size));
dc.SetTextForeground(_bottom.color);
dc.GetTextExtent(_bottom.text, &width, &height);
bottom -= height;
dc.DrawText(_bottom.text, (GetSize().x - width) / 2, bottom);
bottom -= _step_padding;
}
void MugcupGraph::drawLeft(wxDC & dc, int & left)
{
wxCoord width, height;
dc.SetFont(getDefaultFont(_left.size));
dc.SetTextForeground(_left.color);
dc.GetTextExtent(_left.text, &width, &height);
dc.DrawRotatedText(_left.text, left, (GetSize().y + width) / 2, 90.0);
left += (_step_padding + height);
}
void MugcupGraph::drawRight(wxDC & dc, int & right)
{
wxCoord width, height;
dc.SetFont(getDefaultFont(_right.size));
dc.SetTextForeground(_right.color);
dc.GetTextExtent(_right.text, &width, &height);
dc.DrawRotatedText(_right.text, right, (GetSize().y - width) / 2, -90.0);
right -= (_step_padding + height);
}
void MugcupGraph::drawDataRemarks(wxDC & dc, int top, int & right, int rect_width)
{
auto const HALF_PADDING = static_cast<int>(_step_padding / 2);
auto const ROUND_RADIUS = 3.14f / 2.0f;
auto const DATA_COUNT = _datas.size();
wxSize max;
findMaxTextSize(dc, max);
for (std::size_t i = 0; i < DATA_COUNT; ++i) {
auto & data = _datas[i];
wxRect color_rect;
color_rect.x = right - _step_padding - max.x - rect_width;
color_rect.y = top + (int)((max.y + _step_padding) * i);
color_rect.width = rect_width;
color_rect.height = max.y;
data.internal.rect.x = color_rect.x - HALF_PADDING;
data.internal.rect.y = color_rect.y - HALF_PADDING;
data.internal.rect.width = rect_width + max.x + _step_padding + (HALF_PADDING * 2);
data.internal.rect.height = color_rect.height + (HALF_PADDING * 2);
if (data.show == false) {
dc.SetPen(wxPen(wxColour(DISABLE_RECT_COLOR)));
dc.SetBrush(wxBrush(DISABLE_RECT_COLOR));
dc.DrawRoundedRectangle(data.internal.rect, ROUND_RADIUS);
}
dc.SetFont(getDefaultFont(data.text_size));
if (data.show) {
dc.SetTextForeground(data.text_color);
} else {
dc.SetTextForeground(DISABLE_TEXT_COLOR);
}
dc.DrawText(data.text, right - max.x, color_rect.y);
if (data.show) {
dc.SetPen(wxPen(data.line_color));
dc.SetBrush(wxBrush(data.line_color));
} else {
dc.SetPen(wxPen(DISABLE_TEXT_COLOR));
dc.SetBrush(wxBrush(DISABLE_TEXT_COLOR));
}
dc.DrawRoundedRectangle(color_rect, ROUND_RADIUS);
}
right -= (_step_padding + max.x + _step_padding + rect_width);
}
void MugcupGraph::findMaxTextSize(wxDC & dc, wxSize & size)
{
size.x = 0;
size.y = 0;
for (auto & data : _datas) {
wxCoord width, height;
dc.SetFont(getDefaultFont(data.text_size));
dc.SetTextForeground(data.text_color);
dc.GetTextExtent(data.text, &width, &height);
size.x = (width > size.x ? width : size.x);
size.y = (height > size.y ? height : size.y);
}
}
void MugcupGraph::updateGuideXOffset(wxDC & dc, int & right, int & bottom)
{
dc.SetFont(getDefaultFont(_guide_x.text_size));
dc.SetTextForeground(_guide_x.text_color);
wxString str1, str2;
if (_guide_x.integer_text) {
str1 = wxString::Format(_integer_format, (int)_guide_x.print_end);
str2 = wxString::Format(_integer_format, (int)_guide_x.print_begin);
} else {
str1 = wxString::Format(_floating_format, _guide_x.print_end);
str2 = wxString::Format(_floating_format, _guide_x.print_begin);
}
wxCoord width1, width2, height1, height2;
dc.GetTextExtent(str1, &width1, &height1);
dc.GetTextExtent(str2, &width2, &height2);
right -= ((width1 > width2 ? width1 : width2) / 2);
bottom -= (height1 > height2 ? height1 : height2) + (_step_padding * 2);
}
void MugcupGraph::updateGuideYOffset(wxDC & dc, int & left, int & top)
{
dc.SetFont(getDefaultFont(_guide_y.text_size));
dc.SetTextForeground(_guide_y.text_color);
wxString str1, str2;
if (_guide_y.integer_text) {
str1 = wxString::Format(_integer_format, (int)_guide_y.print_end);
str2 = wxString::Format(_integer_format, (int)_guide_y.print_begin);
} else {
str1 = wxString::Format(_floating_format, _guide_y.print_end);
str2 = wxString::Format(_floating_format, _guide_y.print_begin);
}
wxCoord width1, width2, height1, height2;
dc.GetTextExtent(str1, &width1, &height1);
dc.GetTextExtent(str2, &width2, &height2);
left += (width1 > width2 ? width1 : width2) + (_step_padding * 2);
top += ((height1 > height2 ? height1 : height2) / 2);
}
void MugcupGraph::drawGuide(wxDC & dc, Guide const & guide,
int graph_begin, int graph_end, int line_begin, int line_end,
GuideDirection direction)
{
assert(guide.print_begin < guide.print_end);
assert(guide.print_step > 0);
dc.SetPen(wxPen(guide.line_color, guide.line_width));
dc.SetFont(getDefaultFont(guide.text_size));
dc.SetTextForeground(guide.text_color);
double const PRINT_WIDTH = guide.print_end - guide.print_begin;
double const PRINT_STEP = guide.print_step;
if (PRINT_WIDTH != 0) {
double const GRAPH_WIDTH = graph_end - graph_begin;
double const RATE_OF_PRINT_TO_GRAPH = GRAPH_WIDTH / PRINT_WIDTH;
double const GRAPH_STEP = PRINT_STEP * RATE_OF_PRINT_TO_GRAPH;
wxRect label_area;
if (guide.show_begin) {
drawGuideLine(dc, guide.show_text, guide.integer_text, direction,
guide.print_begin, graph_begin, line_begin, line_end, label_area);
}
double const DATA_OFFSET = guide.print_begin - guide.start_data;
double const FIRST_PRINT_STEP = guide.start_data + (static_cast<int>(DATA_OFFSET / PRINT_STEP) * PRINT_STEP);
double value_cursor = FIRST_PRINT_STEP;
while (value_cursor <= guide.print_end) {
if (value_cursor >= guide.print_begin) {
auto graph_x = graph_begin + static_cast<int>((value_cursor - guide.print_begin) * RATE_OF_PRINT_TO_GRAPH);
drawGuideLine(dc, guide.show_text, guide.integer_text, direction,
value_cursor, graph_x, line_begin, line_end, label_area);
}
value_cursor += PRINT_STEP;
}
if (guide.show_end) {
drawGuideLine(dc, guide.show_text, guide.integer_text, direction,
guide.print_end, graph_end, line_begin, line_end, label_area);
}
}
}
void MugcupGraph::drawGuideLine(wxDC & dc, bool show_text, bool integer_text, GuideDirection direction,
double text_value, int graph_value, int line_begin, int line_end,
wxRect & prev_drawing_label)
{
if (show_text) {
wxString str;
if (integer_text) {
str = wxString::Format(_integer_format, (int)text_value);
} else {
str = wxString::Format(_floating_format, text_value);
}
wxCoord width, height;
dc.GetTextExtent(str, &width, &height);
wxRect temp_area;
if (direction == GuideDirection::GD_HORIZONTAL) {
temp_area.x = graph_value - (width / 2);
temp_area.y = line_end + _step_padding;
} else {
assert(direction == GuideDirection::GD_VERTICAL);
temp_area.x = line_begin - _step_padding - width;
temp_area.y = graph_value - (height / 2);
}
temp_area.width = width;
temp_area.height = height;
if (prev_drawing_label.Intersects(temp_area) == false) {
dc.DrawText(str, temp_area.x, temp_area.y);
prev_drawing_label = temp_area;
}
}
if (direction == GuideDirection::GD_HORIZONTAL) {
dc.DrawLine(graph_value, line_begin, graph_value, line_end);
} else {
assert(direction == GuideDirection::GD_VERTICAL);
dc.DrawLine(line_begin, graph_value, line_end, graph_value);
}
}
void MugcupGraph::drawPoints(wxDC & dc, wxPoint const & lt, wxPoint const & rb)
{
double const PRINT_WIDTH = _guide_x.print_end - _guide_x.print_begin;
double const PRINT_HEIGHT = _guide_y.print_end - _guide_y.print_begin;
if (PRINT_WIDTH != 0 && PRINT_HEIGHT != 0) {
int const GRAPH_X_BEGIN = lt.x;
int const GRAPH_Y_BEGIN = lt.y;
double const GRAPH_WIDTH = rb.x - lt.x;
double const GRAPH_HEIGHT = rb.y - lt.y;
double const WIDTH_RATE_OF_PRINT_TO_GRAPH = GRAPH_WIDTH / PRINT_WIDTH;
double const HEIGHT_RATE_OF_PRINT_TO_GRAPH = GRAPH_HEIGHT / PRINT_HEIGHT;
double const WIDTH_DATA_OFFSET = _guide_x.print_begin - _guide_x.start_data;
double const HEIGHT_DATA_OFFSET = _guide_y.print_begin - _guide_y.start_data;
std::vector<wxPoint> points;
for (auto & data : _datas) {
if (!data.show) {
continue;
}
points.clear();
for (auto & point : data.points) {
auto x = point.x;
auto y = point.y;
if (_guide_x.print_begin <= COMPARE_AND(x) <= _guide_x.print_end &&
_guide_y.print_begin <= COMPARE_AND(y) <= _guide_y.print_end) {
auto graph_x = GRAPH_X_BEGIN + static_cast<int>((x - _guide_x.print_begin) * WIDTH_RATE_OF_PRINT_TO_GRAPH);
auto graph_y = GRAPH_Y_BEGIN + static_cast<int>((y - _guide_y.print_begin) * HEIGHT_RATE_OF_PRINT_TO_GRAPH);
points.emplace_back(graph_x, graph_y);
}
}
dc.SetPen(wxPen(data.line_color, data.line_width));
dc.DrawLines(static_cast<int>(points.size()), points.data());
}
}
}
void MugcupGraph::drawDebuggingRect(wxDC & dc, wxRect const & rect)
{
dc.SetPen(wxPen(wxColour(DEBUGGING_COLOR)));
dc.SetBrush(*wxTRANSPARENT_BRUSH);
dc.DrawRectangle(rect);
}
// ----------------------
NAMESPACE_LIBMUGCUP_CLOSE
// ----------------------