ruler.cpp
上传用户:yhdzpy8989
上传日期:2007-06-13
资源大小:13604k
文件大小:23k
- /*
- * ===========================================================================
- * PRODUCTION $Log: ruler.cpp,v $
- * PRODUCTION Revision 1000.4 2004/06/01 21:10:43 gouriano
- * PRODUCTION PRODUCTION: UPGRADED [GCC34_MSVC7] Dev-tree R1.15
- * PRODUCTION
- * ===========================================================================
- */
- /* $Id: ruler.cpp,v 1000.4 2004/06/01 21:10:43 gouriano Exp $
- * ===========================================================================
- *
- * PUBLIC DOMAIN NOTICE
- * National Center for Biotechnology Information
- *
- * This software/database is a "United States Government Work" under the
- * terms of the United States Copyright Act. It was written as part of
- * the author's official duties as a United States Government employee and
- * thus cannot be copyrighted. This software/database is freely available
- * to the public for use. The National Library of Medicine and the U.S.
- * Government have not placed any restriction on its use or reproduction.
- *
- * Although all reasonable efforts have been taken to ensure the accuracy
- * and reliability of the software and data, the NLM and the U.S.
- * Government do not and cannot warrant the performance or results that
- * may be obtained by using this software or data. The NLM and the U.S.
- * Government disclaim all warranties, express or implied, including
- * warranties of performance, merchantability or fitness for any particular
- * purpose.
- *
- * Please cite the author in any work or product based on this material.
- *
- * ===========================================================================
- *
- * Authors: Andrey Yazhuk
- *
- * File Description:
- *
- */
- #include <ncbi_pch.hpp>
- #include <corelib/ncbistd.hpp>
- #include <gui/opengl/glhelpers.hpp>
- #include <gui/widgets/gl/ruler.hpp>
- #include <math.h>
- BEGIN_NCBI_SCOPE
- CRuler::CRuler(bool b_horz)
- : m_bHorz(b_horz),
- m_bAutoRange(true),
- m_Start(0),
- m_End(0),
- m_Offset(0),
- m_bReverseDisplay(false),
- m_DisplayOptions(0),
- m_Font(CGlBitmapFont::eHelvetica12),
- m_TextColor(0.2f, 0.4f, 0.2f),
- m_RullerColor(0.2f, 0.2f, 0.2f),
- m_BackColor(0.95f, 0.95f, 0.95f),
- m_MajorTickSize(6),
- m_RegTickSize(3),
- m_bDirty(true),
- m_PosLabelsStep(-1)
- {
- m_LabelPlace = m_bHorz ? eBottom : eLeft;
- }
- CRuler::~CRuler()
- {
- }
- void CRuler::SetHorizontal(bool b_horz, ELabelPlacement place)
- {
- m_bHorz = b_horz;
- if(m_bHorz) {
- switch(place) {
- case eLeft:
- case eRight: _ASSERT(false);
- case eDefault:
- case eBottom: m_LabelPlace = eBottom; break;
- case eTop: m_LabelPlace = eTop; break;
- }
- } else {
- switch(place) {
- case eBottom:
- case eTop: _ASSERT(false);
- case eDefault:
- case eLeft: m_LabelPlace = eLeft; break;
- case eRight: m_LabelPlace = eRight; break;
- }
- }
- m_bDirty = true;
- }
- void CRuler::SetColor(EColorType type, const CGlColor& color)
- {
- switch(type) {
- case eRuller: m_RullerColor = color; break;
- case eText: m_TextColor = color; break;
- case eBackground: m_BackColor = color; break;
- default: _ASSERT(false);
- }
- m_bDirty = true;
- }
- void CRuler::SetFont(CGlBitmapFont::EFont font_type)
- {
- m_Font.SetFont(font_type);
- m_bDirty = true;
- }
- void CRuler::SetDisplayOptions(int options)
- {
- m_DisplayOptions = options;
- }
- void CRuler::SetAutoRange()
- {
- m_bAutoRange = true;
- m_Offset = 0;
- m_bReverseDisplay = false;
- }
- void CRuler::SetRange(int Start, int End, int SeqStart, bool b_reverse)
- {
- m_bAutoRange = false;
- m_Start = Start;
- m_End = End;
- m_Offset = SeqStart - (b_reverse ? 0 : m_Start);
- m_bReverseDisplay = b_reverse;
- }
- void CRuler::SetGeometryParam(EGeometryParam geom, int value)
- {
- switch(geom) {
- case eRegularTickHeight: m_RegTickSize = value; break;
- case eMajorTickHeight: m_MajorTickSize = value; break;
- default: _ASSERT(false);
- }
- m_bDirty = true;
- }
- /// vertical spacing between text and borders or between text and other graphics
- static int kTextSpaceY = 3;
- TVPPoint CRuler::GetPreferredSize() const
- {
- int w = 0, h = 0;
- if(m_bHorz) {
- h = x_GetBaseHeight();
- if(m_DisplayOptions & (fShowOrigin | fShowMetric)) {
- double t_h = m_Font.GetMetric(CGlBitmapFont::eMetric_FullCharHeight);
- int text_h = (int) ceil(t_h);
- h += max(text_h, m_MajorTickSize) + kTextSpaceY;
- }
- } else {
- }
- return TVPPoint(w, h);
- }
- TVPRect CRuler::GetVPRect()
- {
- TVPPoint pt = GetPreferredSize();
- return TVPRect(0, 0, pt.X(), pt.Y());
- }
- TModelRect CRuler::GetModelRect()
- {
- return TModelRect();
- }
- const static int kLabelSepX = 8;
- const static int kLabelSepY = 4;
- const static int kMinTickStepPixels = 5;
- const static int kLabelSepPix = 24; // min distance between labels
- // calculates distance in pixels between position labels
- void CRuler::x_CalculatePosLabelsStep(CGlPane& pane)
- {
- m_MaxLabelH = m_Font.TextHeight();
- double char_w = m_Font.GetMetric(CGlBitmapFont::eMetric_AvgCharWidth);
- double comma_w = m_Font.TextWidth(",");
-
- // determining maximal number of characters in a label
- double max_num = max(x_ToDisplay(m_Start), x_ToDisplay(m_End));
- int digits_count = (int) ceil(log10(max_num));
- int commas_count = (digits_count - 1) / 3;
-
- // length of the longest possible label in pixels (add 2 for separation)
- m_MaxLabelW = kLabelSepPix + digits_count * char_w + commas_count * comma_w;
- // calculate size of the longest label in model coords
- double scale = m_bHorz ? pane.GetScaleX() : pane.GetScaleY();
- double max_label_sym = 0; // in symbols
- if(m_bHorz) {
- max_label_sym = scale * m_MaxLabelW;
-
- } else {
- max_label_sym = 2 * scale * m_MaxLabelH;
- }
-
- // choosing step in model coords
- double log = (max_label_sym >= 1.0) ? log10(max_label_sym) : 0;
- log = ceil(log);
- double step = pow((double)10, log);
- double base_step = step;
- if(step > 10.001) {
- // try to mimimize step without intersecting labels
- if(m_bHorz) {
- // using compact notation (1 K vs 1,000) saves space, check if we can
- // benefit from this
- // adjusting order
- int groups_n = 0;
- step = step * 10; // to compensate effect of fisrt iteration
- double max_label_w = m_MaxLabelW;
- do
- {
- _ASSERT(step > 0);
- step = step / 10;
- log = ceil(log10(step));
- groups_n = (int) (log / 3);
- if(groups_n) {
- int d_digits = 3 * groups_n - 2;
- max_label_w = m_MaxLabelW - d_digits * char_w + groups_n * comma_w;
- max_label_sym = scale * max_label_w;
- }
- } while(groups_n && step > max_label_sym * 10);
- m_MaxLabelW = max_label_w;
- base_step = step;
- }
- // currently step has a form 10^X, lets check if we can choose
- // a smaller step in a form K * 10^(X-1), where K = 2, 5
- // this adjusment does not affect labels size
- if(step > max_label_sym * 5) {
- base_step = step / 10; // 10^(X-1)
- step = step / 5; // 2 * 10^(X-1)
- }
- else if(step > max_label_sym * 2) {
- base_step = step / 10; // 10^(X-1)
- step = step / 2; // 5 * 10^(X-1)
- }
- }
-
- m_BaseStep = (int) base_step;
- m_BaseStep = max(m_BaseStep, 1);
- m_PosLabelsStep = (int) step;
- m_PosLabelsStep = max(m_PosLabelsStep , 1);
- // choosing optimal distance between ticks
- m_TickSpace = m_BaseStep;
- int ar_K[] = { 10, 5 ,2 };
- for( int i = 0; i < 3; i++ ) {
- int space = m_TickSpace / ar_K[i];
- if(space >= 1 && space / scale > kMinTickStepPixels) {
- m_TickSpace = space;
- break;
- }
- }
- m_bDirty = false;
- }
- void CRuler::Render(CGlPane& pane)
- {
- CGlAttrGuard AttrGuard(GL_POLYGON_BIT | GL_LINE_BIT);
- glLineWidth(1.0f);
- const TModelRect& rc_lim = pane.GetModelLimitsRect();
-
- if(m_bAutoRange) {
- m_Start = (int) (m_bHorz ? rc_lim.Left() : rc_lim.Bottom());
- m_End = (int) (m_bHorz ? rc_lim.Right() : rc_lim.Top()) - 1;
- }
- bool b_update = m_bDirty || m_PosLabelsStep <= 0;
- if(! b_update) {
- bool b_scale_changed = m_bHorz ? (m_ScaleX != pane.GetScaleX())
- : (m_ScaleY != pane.GetScaleY());
- b_update = b_scale_changed || ! (m_rcLimits == rc_lim);
-
- m_rcLimits = rc_lim;
- m_ScaleX = pane.GetScaleX();
- m_ScaleY = pane.GetScaleY();
- }
- if(b_update)
- x_CalculatePosLabelsStep(pane);
-
- if(m_PosLabelsStep > 0) { // step is valid
- // calculating range to draw
- TModelRect rcV = pane.GetVisibleRect();
-
- // [first_elem, last_elem] - is a range being rendered in model coords
- int first_elem = (int) floor(m_bHorz ? rcV.Left() : rcV.Bottom());
- int last_elem = (int) ceil(m_bHorz ? rcV.Right() : rcV.Top()) -1;
- first_elem = max(m_Start, first_elem);
- last_elem = min(m_End, last_elem);
- if(last_elem >= first_elem) {
- pane.OpenOrtho();
-
- // fill background
- glColorC(m_BackColor);
- glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
- TModelUnit offset_x = pane.GetOffsetX();
- glRectd(rcV.Left() - offset_x, rcV.Bottom(),
- rcV.Right() - offset_x, rcV.Top());
-
- // render ticks and labels
- x_RenderScale(pane, first_elem, last_elem);
-
- if((m_DisplayOptions & fHideLabels) == 0) {
- x_RenderAllPosLabels(pane, first_elem, last_elem);
- }
- pane.Close();
- pane.OpenPixels();
- x_RenderOriginAndMetric(pane);
- pane.Close();
- }
- }
- }
- int CRuler::x_ToDisplay(int model) const
- {
- return m_Offset + 1 + (m_bReverseDisplay ? (m_End - model) : model);
- }
- int CRuler::x_ToModel(int display) const
- {
- int off = display - (m_Offset + 1);
- return (m_bReverseDisplay ? (m_End - off) : off);
- }
- void CRuler::x_RenderScale(CGlPane& pane, int first_elem, int last_elem)
- {
- TModelUnit offset_x = pane.GetOffsetX();
- TModelUnit offset_y = pane.GetOffsetY();
- int Length = last_elem - first_elem + 1;
- if(Length > 0) {
- // draw horizonatl line
- double MinU = m_Start + 0.5;
- double MaxU = m_End + 0.5;
- double u1 = max(MinU, (double) first_elem);
- double u2 = min(MaxU, (double) last_elem + 1);
-
- TModelUnit v1 = 0, v2 = 0, v3 = 0;
- switch(m_LabelPlace) {
- case eTop:
- case eRight: {
- v1 = m_MajorTickSize;
- v2 = v1 - m_RegTickSize;
- v3 = v1 - m_MajorTickSize;
- }; break;
- case eLeft: {
- v1 = pane.GetVisibleRect().Width() - m_MajorTickSize - 1;
- v2 = v1 + m_RegTickSize;
- v3 = v1 + m_MajorTickSize;
- }; break;
- case eBottom: {
- v1 = pane.GetVisibleRect().Height() - m_MajorTickSize - 1;
- v2 = v1 + m_RegTickSize;
- v3 = v1 + m_MajorTickSize;
- }; break;
- default: _ASSERT(false);
- }
-
- glColorC(m_RullerColor);
- glBegin(GL_LINES);
-
- if(m_bHorz) {
- glVertex2d(u1 - offset_x, v1 - offset_y);
- glVertex2d(u2 - offset_x, v1 - offset_y);
- } else {
- glVertex2d(v1 - offset_x, u1 - offset_y);
- glVertex2d(v1 - offset_x, u2 - offset_y);
- }
- // draw regular ticks
- if(m_TickSpace) {
- int orig_disp = (x_ToDisplay(m_Start) / m_BaseStep) * m_BaseStep;
- int orig = x_ToModel(orig_disp);
- int i_first_disp = (x_ToDisplay(first_elem) / m_TickSpace ) * m_TickSpace;
- int i_last_disp = (x_ToDisplay(last_elem) / m_TickSpace) * m_TickSpace;
-
- int i_start = x_ToModel(i_first_disp);
- int i_end = x_ToModel(i_last_disp) + m_TickSpace;
- if(i_start < first_elem)
- i_start += m_TickSpace;
- while(i_end > last_elem)
- i_end -= m_TickSpace;
- for( int i = i_start; i <= i_end; i += m_TickSpace ) {
- double v = ((i - orig) % m_BaseStep) ? v2 : v3;
- double u = 0.5 + i;
- if(m_bHorz) {
- glVertex2d(u - offset_x, v - offset_y);
- glVertex2d(u - offset_x, v1 - offset_y);
- } else {
- glVertex2d(v - offset_x, u - offset_y);
- glVertex2d(v1 - offset_x, u - offset_y);
- }
- }
- }
- glEnd();
- }
- }
- // This function fills given vector with indeces of the Alignment elements
- // for which position labels should be shown
- void CRuler::x_GenerateLabelPositions(int first_elem, int last_elem,
- vector<int>& vElemPos)
- {
- vElemPos.clear();
- const int& step = m_PosLabelsStep;
-
- // i_start and i_end are positions in model space
- int i_first_disp = (x_ToDisplay(first_elem) / step ) * step;
- int i_last_disp = (x_ToDisplay(last_elem) / step) * step;
-
- int i_start = x_ToModel(i_first_disp);
- int i_end = x_ToModel(i_last_disp) + step;
-
- while(i_start < first_elem)
- i_start += step;
- while(i_end > last_elem)
- i_end -= step;
- for( int i = i_start; i <= i_end; i += step )
- vElemPos.push_back(i);
- }
- /// Renders all labels on the ruller.
- void CRuler::x_RenderAllPosLabels(CGlPane& pane, int first_elem, int last_elem)
- {
- vector<int> vLabelsPos;
- x_GenerateLabelPositions(first_elem, last_elem, vLabelsPos);
- // setup text output params
- double scale = m_bHorz ? pane.GetScaleX() : pane.GetScaleY();
-
- // draw the First Label
- int pos = m_Start;
- string S = x_GetPositionLabel(m_Start);
-
- double label_size = 0;
- if(m_bHorz) {
- label_size = (m_Font.TextWidth(S.c_str()) + kLabelSepPix) * scale;
- } else {
- label_size = (m_Font.TextHeight() + kLabelSepPix) * scale;
- }
- double label_u = 0.5 + m_Start;
- x_RenderPosLabel(pane, label_u, 0, S);
- double low_limit = label_u + label_size;
- // draw the Last label
- label_u = 0.5 + m_End;
- S = x_GetPositionLabel(m_End);
-
- if(m_bHorz) {
- label_size = m_Font.TextWidth(S.c_str()) * scale;
- }
- if(label_u) // there is enough space at least for the last label
- {
- x_RenderPosLabel(pane, label_u, -label_size, S.c_str());
- double high_limit = label_u - (label_size + kLabelSepPix * scale);
-
- // draw regular labels
- int labels_n = vLabelsPos.size();
- for( int i_label = labels_n - 1; i_label >= 0; i_label-- ) {
- pos = vLabelsPos[i_label];
- string S = x_GetPositionLabel(pos);
- label_u = pos + 0.5;
- if(m_bHorz) {
- label_size = m_Font.TextWidth(S.c_str()) * scale;
- }
- double half_size = label_size / 2.0;
- bool b_draw_text = (label_u + half_size < high_limit)
- && (label_u - half_size > low_limit);
- if(! b_draw_text) {
- S = "";
- } else {
- high_limit = label_u - half_size;
- }
- x_RenderPosLabel(pane, label_u, -half_size, S);
- }
- }
- }
- /// distnce in pixels between left side of ruler and origin labels
- const static int kOriginOffsetX = 6;
- /// minimal size of the metric in pixels
- const static int kMinMetricPix = 20;
- /// returns height necessary for drawing ticks and tick labels (if not hidden)
- int CRuler::x_GetBaseHeight() const
- {
- int h = m_MajorTickSize + kTextSpaceY;
- if((m_DisplayOptions & fHideLabels) == 0) {
- h += m_MajorTickSize + (int)m_Font.TextHeight() + kTextSpaceY;
- }
- return h;
- }
- void CRuler::x_RenderOriginAndMetric(CGlPane& pane)
- {
- if(m_bHorz) {
- TVPRect rc_vp = pane.GetViewport();
- double t_h = m_Font.GetMetric(CGlBitmapFont::eMetric_FullCharHeight);
- int text_h = (int) ceil(t_h);
- TModelUnit y = rc_vp.Bottom() + x_GetBaseHeight(); // bottoom
- int origin_right = rc_vp.Left(); // rightmost point of the origin label
- if(m_DisplayOptions & fShowOrigin) { // render Origin label
-
- int origin = x_ToModel(1) + 1;
- string s = "Origin : " + CTextUtils::FormatSeparatedNumber(origin, true);
- int or_text_w = (int) ceil(m_Font.TextWidth(s.c_str()));
-
- or_text_w = min(or_text_w, rc_vp.Width() - kOriginOffsetX);
- int x = rc_vp.Left() + kOriginOffsetX;
- m_Font.TextOut(x, y, or_text_w, text_h, s.c_str()); // render label
- origin_right += kOriginOffsetX + or_text_w;
- }
- if(m_DisplayOptions & fShowMetric) {
- // choose metric size
- TModelUnit scale_x = pane.GetScaleX();
- TModelUnit step = m_BaseStep;
- while(step / scale_x < kMinMetricPix) {
- step *= 10;
- }
- int pix_l = (int) ceil(step / scale_x); //length of metric in pixels
- string s = CTextUtils::FormatSeparatedNumber((int) step, true);
- s += " ";
- int text_w = (int) ceil(m_Font.TextWidth(s.c_str()));
-
- int metric_w = max(pix_l, text_w);
- if(origin_right + metric_w + kOriginOffsetX < rc_vp.Right()) {
- // there is enough space for rendering metric
- TModelUnit x = rc_vp.Right() - kOriginOffsetX - metric_w;
- TModelUnit yc = y + max(text_h, m_MajorTickSize) / 2;
-
- TModelUnit y1 = yc + m_MajorTickSize / 2;
- TModelUnit y2 = yc - m_MajorTickSize / 2;
- glBegin(GL_LINES);
- glVertex2d(x, y1);
- glVertex2d(x, y2);
-
- glVertex2d(x, yc);
- glVertex2d(x + pix_l, yc);
-
- glVertex2d(x + pix_l, y1);
- glVertex2d(x + pix_l, y2);
- glEnd();
- m_Font.TextOut(x - text_w, y, text_w, text_h, s.c_str(), FL_ALIGN_RIGHT);
- }
- }
- }
- }
- // pos_u is in model coords
- void CRuler::x_RenderPosLabel(CGlPane& pane, double pos_u,
- double u_label_offset, const string& s_text )
- {
- TModelUnit offset_x = pane.GetOffsetX();
- TModelUnit offset_y = pane.GetOffsetY();
-
- if(m_bHorz) { // horizontal orientation
- TModelUnit y1, y2, text_y;
- TModelUnit tick_h = 2 * m_MajorTickSize;
-
- if(m_LabelPlace == eTop) {
- y1 = 0;
- y2 = tick_h;
- text_y = y2 + kTextSpaceY;
- } else {
- y2 = pane.GetVisibleRect().Top();
- y1 = y2 - tick_h;
- text_y = y1 - kTextSpaceY - m_Font.TextHeight();
- }
- glColorC(m_TextColor);
- m_Font.TextOut(pos_u + u_label_offset - offset_x, text_y - offset_y, s_text.c_str());
-
- // draw label's tick
- glColorC(m_RullerColor);
- glBegin(GL_LINES);
- glVertex2d(pos_u - offset_x, y1 - offset_y);
- glVertex2d(pos_u - offset_x, y2 - offset_y);
- glEnd();
- } else { // vertical orientation
- TModelUnit tick_w = 2 * m_MajorTickSize;
- TModelUnit text_w = pane.GetVisibleRect().Width() - tick_w;
-
- TModelUnit y = pos_u + u_label_offset;
- TModelUnit h = m_Font.TextHeight();
-
- TModelUnit x0 = pane.GetVisibleRect().Left();
- TModelUnit tick_x = text_w;
- if (m_LabelPlace == eRight) {
- x0 += tick_w + kLabelSepX;
- tick_x = 0;
- }
- int text_align = FL_ALIGN_BOTTOM | ((m_LabelPlace == eRight)
- ? FL_ALIGN_LEFT : FL_ALIGN_RIGHT);
-
- glColorC(m_TextColor);
- m_Font.TextOut(x0 - offset_x, y - offset_y, text_w - kLabelSepX, h, s_text.c_str(), text_align);
- // draw label's tick
- glColorC(m_RullerColor);
- glBegin(GL_LINES);
- glVertex2d(tick_x - offset_x, pos_u - offset_y);
- glVertex2d(tick_x + tick_w - offset_x, pos_u - offset_y);
- glEnd();
- }
- }
- // i_elem is in model coords
- string CRuler::x_GetPositionLabel(int i_elem)
- {
- int pos = x_ToDisplay(i_elem);
- string S = CTextUtils::FormatSeparatedNumber(pos, true);
- return S;
- }
- END_NCBI_SCOPE
- /*
- * ===========================================================================
- * $Log: ruler.cpp,v $
- * Revision 1000.4 2004/06/01 21:10:43 gouriano
- * PRODUCTION: UPGRADED [GCC34_MSVC7] Dev-tree R1.15
- *
- * Revision 1.15 2004/05/21 22:27:54 gorelenk
- * Added PCH ncbi_pch.hpp
- *
- * Revision 1.14 2004/03/23 14:07:37 dicuccio
- * Fixed compiler warnings
- *
- * Revision 1.13 2004/03/04 19:31:48 yazhuk
- * Improved layout, labels spacing
- *
- * Revision 1.12 2004/03/02 21:54:12 yazhuk
- * Added rendering of origin and metric
- *
- * Revision 1.11 2004/02/17 15:23:10 yazhuk
- * Refactoring - replaced CGlParam with CGlAttrGuard
- *
- * Revision 1.10 2004/02/11 15:24:57 yazhuk
- * Fixed background rendering
- *
- * Revision 1.9 2003/12/23 17:39:19 yazhuk
- * Fixed bug with labels generation in "reverse" mode.
- *
- * Revision 1.8 2003/12/12 17:37:24 ivanov
- * Explicit using pow(double,double) to avoid compilation error on MSVC 7
- *
- * Revision 1.7 2003/12/10 16:55:34 yazhuk
- * Implemented control over displayed range, offset and direction.
- *
- * Revision 1.6 2003/12/08 15:12:02 yazhuk
- * Fixed "first label is always 1" problem. Workaround for OpenGL
- * rounding/precision problems.
- *
- * Revision 1.5 2003/12/01 16:36:06 yazhuk
- * Improved labels layout algorithm and ruller ticks rendering .
- *
- * Revision 1.4 2003/11/18 17:57:08 yazhuk
- * Fixed GCC warnings
- *
- * Revision 1.3 2003/11/18 00:59:03 yazhuk
- * Restored IRenderable implementation
- *
- * Revision 1.2 2003/11/17 23:43:12 yazhuk
- * Fixed GCC compilation problem.
- *
- * Revision 1.1 2003/11/17 20:24:19 yazhuk
- * Renamed from ruller.cpp
- *
- * Revision 1.2 2003/10/31 14:04:18 dicuccio
- * Corrected spelling error: CRuller -> CRuler
- *
- * Revision 1.1 2003/10/29 23:18:28 yazhuk
- * Initial revision
- *
- * ===========================================================================
- */