2010-11-20 10:37:14 +00:00
|
|
|
/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
|
|
|
|
/* If you are missing that file, acquire a complete release at teeworlds.com. */
|
2010-05-29 07:25:38 +00:00
|
|
|
#ifndef GAME_CLIENT_UI_H
|
|
|
|
#define GAME_CLIENT_UI_H
|
2007-08-22 07:52:33 +00:00
|
|
|
|
2022-06-06 20:03:24 +00:00
|
|
|
#include <engine/input.h>
|
2020-10-12 10:29:47 +00:00
|
|
|
#include <engine/textrender.h>
|
2022-05-18 16:00:05 +00:00
|
|
|
|
|
|
|
#include <chrono>
|
2020-10-12 10:29:47 +00:00
|
|
|
#include <string>
|
|
|
|
#include <vector>
|
|
|
|
|
2009-10-27 14:38:53 +00:00
|
|
|
class CUIRect
|
2008-01-12 12:27:55 +00:00
|
|
|
{
|
2022-05-14 11:31:07 +00:00
|
|
|
public:
|
2011-04-13 18:37:12 +00:00
|
|
|
float x, y, w, h;
|
|
|
|
|
2020-08-18 10:50:25 +00:00
|
|
|
/**
|
|
|
|
* Splits 2 CUIRect inside *this* CUIRect horizontally. You can pass null pointers.
|
2021-09-13 21:48:10 +00:00
|
|
|
*
|
2022-01-26 19:19:25 +00:00
|
|
|
* @param pTop This rect will end up taking the top half of this CUIRect.
|
|
|
|
* @param pBottom This rect will end up taking the bottom half of this CUIRect.
|
|
|
|
* @param Spacing Total size of margin between split rects.
|
2020-08-18 10:50:25 +00:00
|
|
|
*/
|
2022-01-26 19:19:25 +00:00
|
|
|
void HSplitMid(CUIRect *pTop, CUIRect *pBottom, float Spacing = 0.0f) const;
|
2020-08-18 10:50:25 +00:00
|
|
|
/**
|
|
|
|
* Splits 2 CUIRect inside *this* CUIRect.
|
|
|
|
*
|
|
|
|
* The cut parameter determines the height of the top rect, so it allows more customization than HSplitMid.
|
|
|
|
*
|
|
|
|
* This method doesn't check if Cut is bigger than *this* rect height.
|
|
|
|
*
|
|
|
|
* @param Cut The height of the pTop rect.
|
|
|
|
* @param pTop The rect that ends up at the top with a height equal to Cut.
|
|
|
|
* @param pBottom The rect that ends up at the bottom with a height equal to *this* rect minus the Cut.
|
|
|
|
*/
|
2009-10-27 14:38:53 +00:00
|
|
|
void HSplitTop(float Cut, CUIRect *pTop, CUIRect *pBottom) const;
|
2020-08-18 10:50:25 +00:00
|
|
|
/**
|
|
|
|
* Splits 2 CUIRect inside *this* CUIRect.
|
|
|
|
*
|
|
|
|
* The cut parameter determines the height of the bottom rect, so it allows more customization than HSplitMid.
|
|
|
|
*
|
|
|
|
* This method doesn't check if Cut is bigger than *this* rect height.
|
|
|
|
*
|
|
|
|
* @param Cut The height of the pBottom rect.
|
|
|
|
* @param pTop The rect that ends up at the top with a height equal to *this* CUIRect height minus Cut.
|
|
|
|
* @param pBottom The rect that ends up at the bottom with a height equal to Cut.
|
|
|
|
*/
|
2009-10-27 14:38:53 +00:00
|
|
|
void HSplitBottom(float Cut, CUIRect *pTop, CUIRect *pBottom) const;
|
2020-08-18 10:50:25 +00:00
|
|
|
/**
|
|
|
|
* Splits 2 CUIRect inside *this* CUIRect vertically. You can pass null pointers.
|
|
|
|
*
|
|
|
|
* @param pLeft This rect will take up the left half of *this* CUIRect.
|
|
|
|
* @param pRight This rect will take up the right half of *this* CUIRect.
|
2022-01-26 19:19:25 +00:00
|
|
|
* @param Spacing Total size of margin between split rects.
|
2020-08-18 10:50:25 +00:00
|
|
|
*/
|
2022-01-26 19:19:25 +00:00
|
|
|
void VSplitMid(CUIRect *pLeft, CUIRect *pRight, float Spacing = 0.0f) const;
|
2020-08-18 10:50:25 +00:00
|
|
|
/**
|
|
|
|
* Splits 2 CUIRect inside *this* CUIRect.
|
|
|
|
*
|
|
|
|
* The cut parameter determines the width of the left rect, so it allows more customization than VSplitMid.
|
|
|
|
*
|
|
|
|
* This method doesn't check if Cut is bigger than *this* rect width.
|
|
|
|
*
|
|
|
|
* @param Cut The width of the pLeft rect.
|
|
|
|
* @param pLeft The rect that ends up at the left with a width equal to Cut.
|
|
|
|
* @param pRight The rect that ends up at the right with a width equal to *this* rect minus the Cut.
|
|
|
|
*/
|
2009-10-27 14:38:53 +00:00
|
|
|
void VSplitLeft(float Cut, CUIRect *pLeft, CUIRect *pRight) const;
|
2020-08-18 10:50:25 +00:00
|
|
|
/**
|
|
|
|
* Splits 2 CUIRect inside *this* CUIRect.
|
|
|
|
*
|
|
|
|
* The cut parameter determines the width of the right rect, so it allows more customization than VSplitMid.
|
|
|
|
*
|
|
|
|
* This method doesn't check if Cut is bigger than *this* rect width.
|
|
|
|
*
|
|
|
|
* @param Cut The width of the pRight rect.
|
|
|
|
* @param pLeft The rect that ends up at the left with a width equal to *this* CUIRect width minus Cut.
|
|
|
|
* @param pRight The rect that ends up at the right with a width equal to Cut.
|
|
|
|
*/
|
2009-10-27 14:38:53 +00:00
|
|
|
void VSplitRight(float Cut, CUIRect *pLeft, CUIRect *pRight) const;
|
|
|
|
|
2020-08-18 10:50:25 +00:00
|
|
|
/**
|
|
|
|
* Places pOtherRect inside *this* CUIRect with Cut as the margin.
|
|
|
|
*
|
|
|
|
* @param Cut The margin.
|
|
|
|
* @param pOtherRect The CUIRect to place inside *this* CUIRect.
|
|
|
|
*/
|
2009-10-27 14:38:53 +00:00
|
|
|
void Margin(float Cut, CUIRect *pOtherRect) const;
|
2020-08-18 10:50:25 +00:00
|
|
|
/**
|
|
|
|
* Places pOtherRect inside *this* CUIRect applying Cut as the margin only on the vertical axis.
|
|
|
|
*
|
|
|
|
* @param Cut The margin.
|
|
|
|
* @param pOtherRect The CUIRect to place inside *this* CUIRect
|
|
|
|
*/
|
2009-10-27 14:38:53 +00:00
|
|
|
void VMargin(float Cut, CUIRect *pOtherRect) const;
|
2020-08-18 10:50:25 +00:00
|
|
|
/**
|
|
|
|
* Places pOtherRect inside *this* CUIRect applying Cut as the margin only on the horizontal axis.
|
|
|
|
*
|
|
|
|
* @param Cut The margin.
|
|
|
|
* @param pOtherRect The CUIRect to place inside *this* CUIRect
|
|
|
|
*/
|
2009-10-27 14:38:53 +00:00
|
|
|
void HMargin(float Cut, CUIRect *pOtherRect) const;
|
2021-11-26 20:55:31 +00:00
|
|
|
|
2022-03-24 00:16:05 +00:00
|
|
|
bool Inside(float x_, float y_) const;
|
2009-10-27 14:38:53 +00:00
|
|
|
};
|
2008-01-12 12:27:55 +00:00
|
|
|
|
2020-10-26 01:10:55 +00:00
|
|
|
struct SUIAnimator
|
|
|
|
{
|
2020-10-26 02:48:05 +00:00
|
|
|
bool m_Active;
|
|
|
|
bool m_ScaleLabel;
|
2020-10-26 03:10:58 +00:00
|
|
|
bool m_RepositionLabel;
|
2020-10-26 02:48:05 +00:00
|
|
|
|
2022-05-18 16:00:05 +00:00
|
|
|
std::chrono::nanoseconds m_Time;
|
2020-10-26 01:10:55 +00:00
|
|
|
float m_Value;
|
2020-10-26 02:48:05 +00:00
|
|
|
|
|
|
|
float m_XOffset;
|
|
|
|
float m_YOffset;
|
|
|
|
float m_WOffset;
|
|
|
|
float m_HOffset;
|
2020-10-26 01:10:55 +00:00
|
|
|
};
|
|
|
|
|
2020-10-12 10:29:47 +00:00
|
|
|
class CUI;
|
|
|
|
|
|
|
|
class CUIElement
|
|
|
|
{
|
|
|
|
friend class CUI;
|
|
|
|
|
2021-09-13 21:48:10 +00:00
|
|
|
CUIElement(CUI *pUI, int RequestedRectCount) { Init(pUI, RequestedRectCount); }
|
2020-10-12 10:29:47 +00:00
|
|
|
|
|
|
|
public:
|
|
|
|
struct SUIElementRect
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
int m_UIRectQuadContainer;
|
|
|
|
int m_UITextContainer;
|
|
|
|
|
|
|
|
float m_X;
|
|
|
|
float m_Y;
|
|
|
|
float m_Width;
|
|
|
|
float m_Height;
|
|
|
|
|
|
|
|
std::string m_Text;
|
|
|
|
|
|
|
|
CTextCursor m_Cursor;
|
|
|
|
|
2022-07-01 04:42:36 +00:00
|
|
|
ColorRGBA m_TextColor;
|
|
|
|
ColorRGBA m_TextOutlineColor;
|
2020-11-08 18:41:16 +00:00
|
|
|
|
2020-11-25 12:05:53 +00:00
|
|
|
SUIElementRect();
|
2021-09-13 21:48:10 +00:00
|
|
|
|
|
|
|
ColorRGBA m_QuadColor;
|
|
|
|
|
|
|
|
void Reset();
|
2020-10-12 10:29:47 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
protected:
|
2022-06-11 19:38:18 +00:00
|
|
|
std::vector<SUIElementRect> m_vUIRects;
|
2020-10-12 10:29:47 +00:00
|
|
|
|
|
|
|
// used for marquees or other user implemented things
|
2021-06-23 05:05:49 +00:00
|
|
|
int64_t m_ElementTime;
|
2020-10-12 10:29:47 +00:00
|
|
|
|
|
|
|
public:
|
|
|
|
CUIElement() = default;
|
|
|
|
|
2021-09-13 21:48:10 +00:00
|
|
|
void Init(CUI *pUI, int RequestedRectCount);
|
2020-10-12 10:29:47 +00:00
|
|
|
|
|
|
|
SUIElementRect *Get(size_t Index)
|
|
|
|
{
|
2022-06-11 19:38:18 +00:00
|
|
|
return &m_vUIRects[Index];
|
2020-10-12 10:29:47 +00:00
|
|
|
}
|
|
|
|
|
2021-09-13 21:48:10 +00:00
|
|
|
bool AreRectsInit()
|
2020-10-12 10:29:47 +00:00
|
|
|
{
|
2022-06-11 19:38:18 +00:00
|
|
|
return !m_vUIRects.empty();
|
2020-10-12 10:29:47 +00:00
|
|
|
}
|
|
|
|
|
2021-09-13 21:48:10 +00:00
|
|
|
void InitRects(int RequestedRectCount);
|
2020-10-12 10:29:47 +00:00
|
|
|
};
|
|
|
|
|
2022-03-11 16:34:48 +00:00
|
|
|
struct SLabelProperties
|
|
|
|
{
|
|
|
|
float m_MaxWidth = -1;
|
|
|
|
int m_AlignVertically = 1;
|
|
|
|
bool m_StopAtEnd = false;
|
|
|
|
class CTextCursor *m_pSelCursor = nullptr;
|
|
|
|
bool m_EnableWidthCheck = true;
|
|
|
|
};
|
|
|
|
|
2009-10-27 14:38:53 +00:00
|
|
|
class CUI
|
2008-01-12 12:27:55 +00:00
|
|
|
{
|
2022-05-27 16:14:31 +00:00
|
|
|
bool m_Enabled;
|
|
|
|
|
2009-10-27 14:38:53 +00:00
|
|
|
const void *m_pHotItem;
|
|
|
|
const void *m_pActiveItem;
|
|
|
|
const void *m_pLastActiveItem;
|
2021-11-22 17:26:13 +00:00
|
|
|
const void *m_pBecomingHotItem;
|
2022-04-07 07:46:02 +00:00
|
|
|
const void *m_pActiveTooltipItem;
|
2022-05-27 08:55:32 +00:00
|
|
|
bool m_ActiveItemValid = false;
|
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
float m_MouseX, m_MouseY; // in gui space
|
2020-12-14 00:51:31 +00:00
|
|
|
float m_MouseDeltaX, m_MouseDeltaY; // in gui space
|
2010-05-29 07:25:38 +00:00
|
|
|
float m_MouseWorldX, m_MouseWorldY; // in world space
|
2009-10-27 14:38:53 +00:00
|
|
|
unsigned m_MouseButtons;
|
|
|
|
unsigned m_LastMouseButtons;
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2009-10-27 14:38:53 +00:00
|
|
|
CUIRect m_Screen;
|
2022-05-13 18:20:04 +00:00
|
|
|
|
2022-06-11 19:38:18 +00:00
|
|
|
std::vector<CUIRect> m_vClips;
|
2022-05-13 18:20:04 +00:00
|
|
|
void UpdateClipping();
|
|
|
|
|
2022-05-27 16:14:31 +00:00
|
|
|
class IInput *m_pInput;
|
2009-10-27 14:38:53 +00:00
|
|
|
class IGraphics *m_pGraphics;
|
2010-05-29 07:25:38 +00:00
|
|
|
class ITextRender *m_pTextRender;
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2022-06-11 19:38:18 +00:00
|
|
|
std::vector<CUIElement *> m_vpOwnUIElements; // ui elements maintained by CUI class
|
|
|
|
std::vector<CUIElement *> m_vpUIElements;
|
2020-10-12 10:29:47 +00:00
|
|
|
|
2009-10-27 14:38:53 +00:00
|
|
|
public:
|
2021-12-03 18:49:22 +00:00
|
|
|
static float ms_FontmodHeight;
|
|
|
|
|
2009-10-27 14:38:53 +00:00
|
|
|
// TODO: Refactor: Fill this in
|
2022-05-27 16:14:31 +00:00
|
|
|
void Init(class IInput *pInput, class IGraphics *pGraphics, class ITextRender *pTextRender);
|
|
|
|
class IInput *Input() const { return m_pInput; }
|
2021-02-08 21:26:26 +00:00
|
|
|
class IGraphics *Graphics() const { return m_pGraphics; }
|
|
|
|
class ITextRender *TextRender() const { return m_pTextRender; }
|
2008-01-12 12:27:55 +00:00
|
|
|
|
2009-10-27 14:38:53 +00:00
|
|
|
CUI();
|
2020-10-12 10:29:47 +00:00
|
|
|
~CUI();
|
|
|
|
|
|
|
|
void ResetUIElement(CUIElement &UIElement);
|
|
|
|
|
2021-09-13 21:48:10 +00:00
|
|
|
CUIElement *GetNewUIElement(int RequestedRectCount);
|
2020-10-12 10:29:47 +00:00
|
|
|
|
|
|
|
void AddUIElement(CUIElement *pElement);
|
|
|
|
void OnElementsReset();
|
|
|
|
void OnWindowResize();
|
|
|
|
void OnLanguageChange();
|
2007-05-22 15:03:32 +00:00
|
|
|
|
2009-10-27 14:38:53 +00:00
|
|
|
enum
|
|
|
|
{
|
2020-09-26 19:41:58 +00:00
|
|
|
CORNER_TL = 1,
|
|
|
|
CORNER_TR = 2,
|
|
|
|
CORNER_BL = 4,
|
|
|
|
CORNER_BR = 8,
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2020-09-26 19:41:58 +00:00
|
|
|
CORNER_T = CORNER_TL | CORNER_TR,
|
|
|
|
CORNER_B = CORNER_BL | CORNER_BR,
|
|
|
|
CORNER_R = CORNER_TR | CORNER_BR,
|
|
|
|
CORNER_L = CORNER_TL | CORNER_BL,
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2020-09-26 19:41:58 +00:00
|
|
|
CORNER_ALL = CORNER_T | CORNER_B
|
2009-10-27 14:38:53 +00:00
|
|
|
};
|
2007-05-22 15:03:32 +00:00
|
|
|
|
2022-05-27 16:14:31 +00:00
|
|
|
void SetEnabled(bool Enabled) { m_Enabled = Enabled; }
|
|
|
|
bool Enabled() const { return m_Enabled; }
|
|
|
|
void Update(float MouseX, float MouseY, float MouseWorldX, float MouseWorldY);
|
2007-05-22 15:03:32 +00:00
|
|
|
|
2020-12-14 00:51:31 +00:00
|
|
|
float MouseDeltaX() const { return m_MouseDeltaX; }
|
|
|
|
float MouseDeltaY() const { return m_MouseDeltaY; }
|
2009-10-27 14:38:53 +00:00
|
|
|
float MouseX() const { return m_MouseX; }
|
|
|
|
float MouseY() const { return m_MouseY; }
|
|
|
|
float MouseWorldX() const { return m_MouseWorldX; }
|
|
|
|
float MouseWorldY() const { return m_MouseWorldY; }
|
2020-09-26 19:41:58 +00:00
|
|
|
int MouseButton(int Index) const { return (m_MouseButtons >> Index) & 1; }
|
2021-02-08 21:26:26 +00:00
|
|
|
int MouseButtonClicked(int Index) const { return MouseButton(Index) && !((m_LastMouseButtons >> Index) & 1); }
|
|
|
|
int MouseButtonReleased(int Index) const { return ((m_LastMouseButtons >> Index) & 1) && !MouseButton(Index); }
|
2008-01-12 12:27:55 +00:00
|
|
|
|
2021-11-22 17:26:13 +00:00
|
|
|
void SetHotItem(const void *pID) { m_pBecomingHotItem = pID; }
|
2020-09-26 19:41:58 +00:00
|
|
|
void SetActiveItem(const void *pID)
|
|
|
|
{
|
2022-05-27 08:55:32 +00:00
|
|
|
m_ActiveItemValid = true;
|
2020-09-26 19:41:58 +00:00
|
|
|
m_pActiveItem = pID;
|
|
|
|
if(pID)
|
|
|
|
m_pLastActiveItem = pID;
|
|
|
|
}
|
2022-05-27 08:55:32 +00:00
|
|
|
bool CheckActiveItem(const void *pID)
|
|
|
|
{
|
|
|
|
if(m_pActiveItem == pID)
|
|
|
|
{
|
|
|
|
m_ActiveItemValid = true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2022-04-07 07:46:02 +00:00
|
|
|
void SetActiveTooltipItem(const void *pID) { m_pActiveTooltipItem = pID; }
|
2022-05-27 08:55:32 +00:00
|
|
|
void ClearLastActiveItem() { m_pLastActiveItem = nullptr; }
|
2009-10-27 14:38:53 +00:00
|
|
|
const void *HotItem() const { return m_pHotItem; }
|
2021-11-22 17:26:13 +00:00
|
|
|
const void *NextHotItem() const { return m_pBecomingHotItem; }
|
2009-10-27 14:38:53 +00:00
|
|
|
const void *ActiveItem() const { return m_pActiveItem; }
|
2022-04-07 07:46:02 +00:00
|
|
|
const void *ActiveTooltipItem() const { return m_pActiveTooltipItem; }
|
2009-10-27 14:38:53 +00:00
|
|
|
const void *LastActiveItem() const { return m_pLastActiveItem; }
|
2008-01-12 12:27:55 +00:00
|
|
|
|
2022-05-27 08:55:32 +00:00
|
|
|
void StartCheck() { m_ActiveItemValid = false; }
|
|
|
|
void FinishCheck()
|
|
|
|
{
|
2022-05-27 17:00:03 +00:00
|
|
|
if(!m_ActiveItemValid && m_pActiveItem != nullptr)
|
|
|
|
{
|
2022-05-27 08:55:32 +00:00
|
|
|
SetActiveItem(nullptr);
|
2022-05-27 17:00:03 +00:00
|
|
|
m_pHotItem = nullptr;
|
|
|
|
m_pBecomingHotItem = nullptr;
|
|
|
|
}
|
2022-05-27 08:55:32 +00:00
|
|
|
}
|
|
|
|
|
2021-11-26 20:55:31 +00:00
|
|
|
bool MouseInside(const CUIRect *pRect) const;
|
2022-05-13 18:24:10 +00:00
|
|
|
bool MouseInsideClip() const { return !IsClipped() || MouseInside(ClipArea()); }
|
|
|
|
bool MouseHovered(const CUIRect *pRect) const { return MouseInside(pRect) && MouseInsideClip(); }
|
2022-06-06 20:03:24 +00:00
|
|
|
void ConvertMouseMove(float *pX, float *pY, IInput::ECursorType CursorType) const;
|
2008-01-12 12:27:55 +00:00
|
|
|
|
2021-11-26 20:55:31 +00:00
|
|
|
float ButtonColorMulActive() { return 0.5f; }
|
|
|
|
float ButtonColorMulHot() { return 1.5f; }
|
|
|
|
float ButtonColorMulDefault() { return 1.0f; }
|
|
|
|
float ButtonColorMul(const void *pID);
|
|
|
|
|
2009-10-27 14:38:53 +00:00
|
|
|
CUIRect *Screen();
|
2021-09-22 19:48:55 +00:00
|
|
|
void MapScreen();
|
2012-01-10 22:13:19 +00:00
|
|
|
float PixelSize();
|
2022-05-13 18:20:04 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
void ClipEnable(const CUIRect *pRect);
|
2009-10-27 14:38:53 +00:00
|
|
|
void ClipDisable();
|
2022-05-13 18:20:04 +00:00
|
|
|
const CUIRect *ClipArea() const;
|
2022-06-11 19:38:18 +00:00
|
|
|
inline bool IsClipped() const { return !m_vClips.empty(); }
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2020-10-12 10:29:47 +00:00
|
|
|
int DoButtonLogic(const void *pID, int Checked, const CUIRect *pRect);
|
2015-08-17 12:10:08 +00:00
|
|
|
int DoPickerLogic(const void *pID, const CUIRect *pRect, float *pX, float *pY);
|
2011-04-13 18:37:12 +00:00
|
|
|
|
2022-03-11 16:34:48 +00:00
|
|
|
float DoTextLabel(float x, float y, float w, float h, const char *pText, float Size, int Align, const SLabelProperties &LabelProps = {});
|
|
|
|
void DoLabel(const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps = {});
|
2020-10-12 10:29:47 +00:00
|
|
|
|
2022-06-13 16:28:13 +00:00
|
|
|
void DoLabel(CUIElement::SUIElementRect &RectEl, const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps, int StrLen = -1, class CTextCursor *pReadCursor = nullptr);
|
|
|
|
void DoLabelStreamed(CUIElement::SUIElementRect &RectEl, float x, float y, float w, float h, const char *pText, float Size, int Align, float MaxWidth = -1, int AlignVertically = 1, bool StopAtEnd = false, int StrLen = -1, class CTextCursor *pReadCursor = nullptr);
|
|
|
|
void DoLabelStreamed(CUIElement::SUIElementRect &RectEl, const CUIRect *pRect, const char *pText, float Size, int Align, float MaxWidth = -1, int AlignVertically = 1, bool StopAtEnd = false, int StrLen = -1, class CTextCursor *pReadCursor = nullptr);
|
2009-10-27 14:38:53 +00:00
|
|
|
};
|
2007-08-22 07:52:33 +00:00
|
|
|
|
2007-05-22 15:03:32 +00:00
|
|
|
#endif
|