summaryrefslogtreecommitdiff
path: root/indra/llui
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llui')
-rw-r--r--indra/llui/CMakeLists.txt2
-rw-r--r--indra/llui/lllineeditor.cpp123
-rw-r--r--indra/llui/lllineeditor.h10
-rw-r--r--indra/llui/lltextvalidate.cpp33
-rw-r--r--indra/llui/lltextvalidate.h1
-rw-r--r--indra/llui/lltimectrl.cpp432
-rw-r--r--indra/llui/lltimectrl.h131
7 files changed, 709 insertions, 23 deletions
diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt
index 684e393cba..5914c3551a 100644
--- a/indra/llui/CMakeLists.txt
+++ b/indra/llui/CMakeLists.txt
@@ -93,6 +93,7 @@ set(llui_SOURCE_FILES
lltextparser.cpp
lltextutil.cpp
lltextvalidate.cpp
+ lltimectrl.cpp
lltransutil.cpp
lltoggleablemenu.cpp
lltooltip.cpp
@@ -191,6 +192,7 @@ set(llui_HEADER_FILES
lltextparser.h
lltextutil.h
lltextvalidate.h
+ lltimectrl.h
lltoggleablemenu.h
lltooltip.h
lltransutil.h
diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp
index 0196080d90..123997e5e9 100644
--- a/indra/llui/lllineeditor.cpp
+++ b/indra/llui/lllineeditor.cpp
@@ -81,6 +81,7 @@ LLLineEditor::Params::Params()
: max_length(""),
keystroke_callback("keystroke_callback"),
prevalidate_callback("prevalidate_callback"),
+ prevalidate_input_callback("prevalidate_input_callback"),
background_image("background_image"),
background_image_disabled("background_image_disabled"),
background_image_focused("background_image_focused"),
@@ -173,6 +174,7 @@ LLLineEditor::LLLineEditor(const LLLineEditor::Params& p)
updateTextPadding();
setCursor(mText.length());
+ setPrevalidateInput(p.prevalidate_input_callback());
setPrevalidate(p.prevalidate_callback());
LLContextMenu* menu = LLUICtrlFactory::instance().createFromFile<LLContextMenu>
@@ -405,23 +407,15 @@ void LLLineEditor::setText(const LLStringExplicit &new_text)
// Picks a new cursor position based on the actual screen size of text being drawn.
void LLLineEditor::setCursorAtLocalPos( S32 local_mouse_x )
{
- const llwchar* wtext = mText.getWString().c_str();
- LLWString asterix_text;
- if (mDrawAsterixes)
- {
- for (S32 i = 0; i < mText.length(); i++)
- {
- asterix_text += utf8str_to_wstring(PASSWORD_ASTERISK);
- }
- wtext = asterix_text.c_str();
- }
+ S32 cursor_pos = calcCursorPos(local_mouse_x);
+
+ S32 left_pos = llmin( mSelectionStart, cursor_pos );
+ S32 length = llabs( mSelectionStart - cursor_pos );
+ const LLWString& substr = mText.getWString().substr(left_pos, length);
+
+ if (mIsSelecting && !prevalidateInput(substr))
+ return;
- S32 cursor_pos =
- mScrollHPos +
- mGLFont->charFromPixelOffset(
- wtext, mScrollHPos,
- (F32)(local_mouse_x - mTextLeftEdge),
- (F32)(mTextRightEdge - mTextLeftEdge + 1)); // min-max range is inclusive
setCursor(cursor_pos);
}
@@ -505,6 +499,11 @@ BOOL LLLineEditor::canSelectAll() const
void LLLineEditor::selectAll()
{
+ if (!prevalidateInput(mText.getWString()))
+ {
+ return;
+ }
+
mSelectionStart = mText.length();
mSelectionEnd = 0;
setCursor(mSelectionEnd);
@@ -590,6 +589,9 @@ BOOL LLLineEditor::handleMouseDown(S32 x, S32 y, MASK mask)
if (mask & MASK_SHIFT)
{
+ // assume we're starting a drag select
+ mIsSelecting = TRUE;
+
// Handle selection extension
S32 old_cursor_pos = getCursor();
setCursorAtLocalPos(x);
@@ -624,8 +626,6 @@ BOOL LLLineEditor::handleMouseDown(S32 x, S32 y, MASK mask)
mSelectionStart = old_cursor_pos;
mSelectionEnd = getCursor();
}
- // assume we're starting a drag select
- mIsSelecting = TRUE;
}
else
{
@@ -796,6 +796,9 @@ void LLLineEditor::removeChar()
{
if( getCursor() > 0 )
{
+ if (!prevalidateInput(mText.getWString().substr(getCursor()-1, 1)))
+ return;
+
mText.erase(getCursor() - 1, 1);
setCursor(getCursor() - 1);
@@ -816,6 +819,9 @@ void LLLineEditor::addChar(const llwchar uni_char)
}
else if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode())
{
+ if (!prevalidateInput(mText.getWString().substr(getCursor(), 1)))
+ return;
+
mText.erase(getCursor(), 1);
}
@@ -864,6 +870,13 @@ void LLLineEditor::extendSelection( S32 new_cursor_pos )
startSelection();
}
+ S32 left_pos = llmin( mSelectionStart, new_cursor_pos );
+ S32 selection_length = llabs( mSelectionStart - new_cursor_pos );
+ const LLWString& selection = mText.getWString().substr(left_pos, selection_length);
+
+ if (!prevalidateInput(selection))
+ return;
+
setCursor(new_cursor_pos);
mSelectionEnd = getCursor();
}
@@ -994,8 +1007,12 @@ void LLLineEditor::deleteSelection()
{
if( !mReadOnly && hasSelection() )
{
- S32 left_pos = llmin( mSelectionStart, mSelectionEnd );
- S32 selection_length = llabs( mSelectionStart - mSelectionEnd );
+ S32 left_pos, selection_length;
+ getSelectionRange(&left_pos, &selection_length);
+ const LLWString& selection = mText.getWString().substr(left_pos, selection_length);
+
+ if (!prevalidateInput(selection))
+ return;
mText.erase(left_pos, selection_length);
deselect();
@@ -1013,12 +1030,16 @@ void LLLineEditor::cut()
{
if( canCut() )
{
+ S32 left_pos, length;
+ getSelectionRange(&left_pos, &length);
+ const LLWString& selection = mText.getWString().substr(left_pos, length);
+
+ if (!prevalidateInput(selection))
+ return;
+
// Prepare for possible rollback
LLLineEditorRollback rollback( this );
-
- S32 left_pos = llmin( mSelectionStart, mSelectionEnd );
- S32 length = llabs( mSelectionStart - mSelectionEnd );
gClipboard.copyFromSubstring( mText.getWString(), left_pos, length );
deleteSelection();
@@ -1098,6 +1119,9 @@ void LLLineEditor::pasteHelper(bool is_primary)
if (!paste.empty())
{
+ if (!prevalidateInput(paste))
+ return;
+
// Prepare for possible rollback
LLLineEditorRollback rollback(this);
@@ -1445,6 +1469,13 @@ BOOL LLLineEditor::handleUnicodeCharHere(llwchar uni_char)
LLLineEditorRollback rollback( this );
+ {
+ LLWString u_char;
+ u_char.assign(1, uni_char);
+ if (!prevalidateInput(u_char))
+ return handled;
+ }
+
addChar(uni_char);
mKeystrokeTimer.reset();
@@ -1496,6 +1527,15 @@ void LLLineEditor::doDelete()
}
else if ( getCursor() < mText.length())
{
+ const LLWString& text_to_delete = mText.getWString().substr(getCursor(), 1);
+
+ if (!prevalidateInput(text_to_delete))
+ {
+ if( mKeystrokeCallback )
+ mKeystrokeCallback( this );
+
+ return;
+ }
setCursor(getCursor() + 1);
removeChar();
}
@@ -1843,6 +1883,27 @@ S32 LLLineEditor::findPixelNearestPos(const S32 cursor_offset) const
return result;
}
+S32 LLLineEditor::calcCursorPos(S32 mouse_x)
+{
+ const llwchar* wtext = mText.getWString().c_str();
+ LLWString asterix_text;
+ if (mDrawAsterixes)
+ {
+ for (S32 i = 0; i < mText.length(); i++)
+ {
+ asterix_text += utf8str_to_wstring(PASSWORD_ASTERISK);
+ }
+ wtext = asterix_text.c_str();
+ }
+
+ S32 cur_pos = mScrollHPos +
+ mGLFont->charFromPixelOffset(
+ wtext, mScrollHPos,
+ (F32)(mouse_x - mTextLeftEdge),
+ (F32)(mTextRightEdge - mTextLeftEdge + 1)); // min-max range is inclusive
+
+ return cur_pos;
+}
//virtual
void LLLineEditor::clear()
{
@@ -1936,6 +1997,22 @@ void LLLineEditor::setPrevalidate(LLTextValidate::validate_func_t func)
updateAllowingLanguageInput();
}
+void LLLineEditor::setPrevalidateInput(LLTextValidate::validate_func_t func)
+{
+ mPrevalidateInputFunc = func;
+ updateAllowingLanguageInput();
+}
+
+bool LLLineEditor::prevalidateInput(const LLWString& wstr)
+{
+ if (mPrevalidateInputFunc && !mPrevalidateInputFunc(wstr))
+ {
+ return false;
+ }
+
+ return true;
+}
+
// static
BOOL LLLineEditor::postvalidateFloat(const std::string &str)
{
diff --git a/indra/llui/lllineeditor.h b/indra/llui/lllineeditor.h
index fe191e5971..4b77708590 100644
--- a/indra/llui/lllineeditor.h
+++ b/indra/llui/lllineeditor.h
@@ -76,6 +76,7 @@ public:
Optional<keystroke_callback_t> keystroke_callback;
Optional<LLTextValidate::validate_func_t, LLTextValidate::ValidateTextNamedFuncs> prevalidate_callback;
+ Optional<LLTextValidate::validate_func_t, LLTextValidate::ValidateTextNamedFuncs> prevalidate_input_callback;
Optional<LLViewBorder::Params> border;
@@ -232,8 +233,15 @@ public:
// Prevalidation controls which keystrokes can affect the editor
void setPrevalidate( LLTextValidate::validate_func_t func );
+ // This method sets callback that prevents from:
+ // - deleting, selecting, typing, cutting, pasting characters that are not valid.
+ // Also callback that this method sets differs from setPrevalidate in a way that it validates just inputed
+ // symbols, before existing text is modified, but setPrevalidate validates line after it was modified.
+ void setPrevalidateInput(LLTextValidate::validate_func_t func);
static BOOL postvalidateFloat(const std::string &str);
+ bool prevalidateInput(const LLWString& wstr);
+
// line history support:
void setEnableLineHistory( BOOL enabled ) { mHaveHistory = enabled; } // switches line history on or off
void updateHistory(); // stores current line in history
@@ -251,6 +259,7 @@ private:
void addChar(const llwchar c);
void setCursorAtLocalPos(S32 local_mouse_x);
S32 findPixelNearestPos(S32 cursor_offset = 0) const;
+ S32 calcCursorPos(S32 mouse_x);
BOOL handleSpecialKey(KEY key, MASK mask);
BOOL handleSelectionKey(KEY key, MASK mask);
BOOL handleControlKey(KEY key, MASK mask);
@@ -312,6 +321,7 @@ protected:
S32 mLastSelectionEnd;
LLTextValidate::validate_func_t mPrevalidateFunc;
+ LLTextValidate::validate_func_t mPrevalidateInputFunc;
LLFrameTimer mKeystrokeTimer;
LLTimer mTripleClickTimer;
diff --git a/indra/llui/lltextvalidate.cpp b/indra/llui/lltextvalidate.cpp
index 4b9faa0560..234e600ccd 100644
--- a/indra/llui/lltextvalidate.cpp
+++ b/indra/llui/lltextvalidate.cpp
@@ -188,6 +188,39 @@ namespace LLTextValidate
return success;
}
+ bool validateNonNegativeS32NoSpace(const LLWString &str)
+ {
+ LLLocale locale(LLLocale::USER_LOCALE);
+
+ LLWString test_str = str;
+ S32 len = test_str.length();
+ bool success = TRUE;
+ if(0 < len)
+ {
+ if('-' == test_str[0])
+ {
+ success = FALSE;
+ }
+ S32 i = 0;
+ while(success && (i < len))
+ {
+ if(!LLStringOps::isDigit(test_str[i]) || LLStringOps::isSpace(test_str[i++]))
+ {
+ success = FALSE;
+ }
+ }
+ }
+ if (success)
+ {
+ S32 val = strtol(wstring_to_utf8str(test_str).c_str(), NULL, 10);
+ if (val < 0)
+ {
+ success = FALSE;
+ }
+ }
+ return success;
+ }
+
bool validateAlphaNum(const LLWString &str)
{
LLLocale locale(LLLocale::USER_LOCALE);
diff --git a/indra/llui/lltextvalidate.h b/indra/llui/lltextvalidate.h
index 84644be30c..5c830d7db3 100644
--- a/indra/llui/lltextvalidate.h
+++ b/indra/llui/lltextvalidate.h
@@ -46,6 +46,7 @@ namespace LLTextValidate
bool validateInt(const LLWString &str );
bool validatePositiveS32(const LLWString &str);
bool validateNonNegativeS32(const LLWString &str);
+ bool validateNonNegativeS32NoSpace(const LLWString &str);
bool validateAlphaNum(const LLWString &str );
bool validateAlphaNumSpace(const LLWString &str );
bool validateASCIIPrintableNoPipe(const LLWString &str);
diff --git a/indra/llui/lltimectrl.cpp b/indra/llui/lltimectrl.cpp
new file mode 100644
index 0000000000..9ea1e8815e
--- /dev/null
+++ b/indra/llui/lltimectrl.cpp
@@ -0,0 +1,432 @@
+/**
+ * @file lltimectrl.cpp
+ * @brief LLTimeCtrl base class
+ *
+ * $LicenseInfo:firstyear=2001&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2011, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#include "linden_common.h"
+
+#include "lltimectrl.h"
+
+#include "llui.h"
+#include "lluiconstants.h"
+
+#include "llbutton.h"
+#include "llfontgl.h"
+#include "lllineeditor.h"
+#include "llkeyboard.h"
+#include "llstring.h"
+#include "lltextbox.h"
+#include "lluictrlfactory.h"
+
+static LLDefaultChildRegistry::Register<LLTimeCtrl> time_r("time");
+
+const U32 AMPM_LEN = 3;
+const U32 MINUTES_MIN = 0;
+const U32 MINUTES_MAX = 59;
+const U32 HOURS_MIN = 1;
+const U32 HOURS_MAX = 12;
+const U32 MINUTES_PER_HOUR = 60;
+const U32 MINUTES_PER_DAY = 24 * MINUTES_PER_HOUR;
+
+
+LLTimeCtrl::Params::Params()
+: label_width("label_width"),
+ snap_to("snap_to"),
+ allow_text_entry("allow_text_entry", true),
+ text_enabled_color("text_enabled_color"),
+ text_disabled_color("text_disabled_color"),
+ up_button("up_button"),
+ down_button("down_button")
+{}
+
+LLTimeCtrl::LLTimeCtrl(const LLTimeCtrl::Params& p)
+: LLUICtrl(p),
+ mLabelBox(NULL),
+ mTextEnabledColor(p.text_enabled_color()),
+ mTextDisabledColor(p.text_disabled_color()),
+ mTime(0),
+ mSnapToMin(5)
+{
+ static LLUICachedControl<S32> spinctrl_spacing ("UISpinctrlSpacing", 0);
+ static LLUICachedControl<S32> spinctrl_btn_width ("UISpinctrlBtnWidth", 0);
+ static LLUICachedControl<S32> spinctrl_btn_height ("UISpinctrlBtnHeight", 0);
+ S32 centered_top = getRect().getHeight();
+ S32 centered_bottom = getRect().getHeight() - 2 * spinctrl_btn_height;
+ S32 label_width = llclamp(p.label_width(), 0, llmax(0, getRect().getWidth() - 40));
+ S32 editor_left = label_width + spinctrl_spacing;
+
+ //================= Label =================//
+ if( !p.label().empty() )
+ {
+ LLRect label_rect( 0, centered_top, label_width, centered_bottom );
+ LLTextBox::Params params;
+ params.name("TimeCtrl Label");
+ params.rect(label_rect);
+ params.initial_value(p.label());
+ if (p.font.isProvided())
+ {
+ params.font(p.font);
+ }
+ mLabelBox = LLUICtrlFactory::create<LLTextBox> (params);
+ addChild(mLabelBox);
+
+ editor_left = label_rect.mRight + spinctrl_spacing;
+ }
+
+ S32 editor_right = getRect().getWidth() - spinctrl_btn_width - spinctrl_spacing;
+
+ //================= Editor ================//
+ LLRect editor_rect( editor_left, centered_top, editor_right, centered_bottom );
+ LLLineEditor::Params params;
+ params.name("SpinCtrl Editor");
+ params.rect(editor_rect);
+ if (p.font.isProvided())
+ {
+ params.font(p.font);
+ }
+
+ params.follows.flags(FOLLOWS_LEFT | FOLLOWS_BOTTOM);
+ params.max_length.chars(8);
+ params.keystroke_callback(boost::bind(&LLTimeCtrl::onTextEntry, this, _1));
+ mEditor = LLUICtrlFactory::create<LLLineEditor> (params);
+ mEditor->setPrevalidateInput(LLTextValidate::validateNonNegativeS32NoSpace);
+ mEditor->setPrevalidate(boost::bind(&LLTimeCtrl::isTimeStringValid, this, _1));
+ mEditor->setText(LLStringExplicit("12:00 AM"));
+ addChild(mEditor);
+
+ //================= Spin Buttons ==========//
+ LLButton::Params up_button_params(p.up_button);
+ up_button_params.rect = LLRect(editor_right + 1, getRect().getHeight(), editor_right + spinctrl_btn_width, getRect().getHeight() - spinctrl_btn_height);
+
+ up_button_params.click_callback.function(boost::bind(&LLTimeCtrl::onUpBtn, this));
+ up_button_params.mouse_held_callback.function(boost::bind(&LLTimeCtrl::onUpBtn, this));
+ mUpBtn = LLUICtrlFactory::create<LLButton>(up_button_params);
+ addChild(mUpBtn);
+
+ LLButton::Params down_button_params(p.down_button);
+ down_button_params.rect = LLRect(editor_right + 1, getRect().getHeight() - spinctrl_btn_height, editor_right + spinctrl_btn_width, getRect().getHeight() - 2 * spinctrl_btn_height);
+ down_button_params.click_callback.function(boost::bind(&LLTimeCtrl::onDownBtn, this));
+ down_button_params.mouse_held_callback.function(boost::bind(&LLTimeCtrl::onDownBtn, this));
+ mDownBtn = LLUICtrlFactory::create<LLButton>(down_button_params);
+ addChild(mDownBtn);
+
+ setUseBoundingRect( TRUE );
+}
+
+F32 LLTimeCtrl::getTime24() const
+{
+ // 0.0 - 23.99;
+ return mTime / 60.0f;
+}
+
+U32 LLTimeCtrl::getHours24() const
+{
+ return (U32) getTime24();
+}
+
+U32 LLTimeCtrl::getMinutes() const
+{
+ return mTime % MINUTES_PER_HOUR;
+}
+
+void LLTimeCtrl::setTime24(F32 time)
+{
+ time = llclamp(time, 0.0f, 23.99f); // fix out of range values
+ mTime = llround(time * MINUTES_PER_HOUR); // fixes values like 4.99999
+
+ updateText();
+}
+
+BOOL LLTimeCtrl::handleKeyHere(KEY key, MASK mask)
+{
+ if (mEditor->hasFocus())
+ {
+ if(key == KEY_UP)
+ {
+ onUpBtn();
+ return TRUE;
+ }
+ if(key == KEY_DOWN)
+ {
+ onDownBtn();
+ return TRUE;
+ }
+ if (key == KEY_RETURN)
+ {
+ onCommit();
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+void LLTimeCtrl::onUpBtn()
+{
+ switch(getEditingPart())
+ {
+ case HOURS:
+ increaseHours();
+ break;
+ case MINUTES:
+ increaseMinutes();
+ break;
+ case DAYPART:
+ switchDayPeriod();
+ break;
+ default:
+ break;
+ }
+
+ updateText();
+ onCommit();
+}
+
+void LLTimeCtrl::onDownBtn()
+{
+ switch(getEditingPart())
+ {
+ case HOURS:
+ decreaseHours();
+ break;
+ case MINUTES:
+ decreaseMinutes();
+ break;
+ case DAYPART:
+ switchDayPeriod();
+ break;
+ default:
+ break;
+ }
+
+ updateText();
+ onCommit();
+}
+
+void LLTimeCtrl::onFocusLost()
+{
+ updateText();
+ onCommit();
+ LLUICtrl::onFocusLost();
+}
+
+void LLTimeCtrl::onTextEntry(LLLineEditor* line_editor)
+{
+ std::string time_str = line_editor->getText();
+ U32 h12 = parseHours(getHoursString(time_str));
+ U32 m = parseMinutes(getMinutesString(time_str));
+ bool pm = parseAMPM(getAMPMString(time_str));
+
+ if (h12 == 12)
+ {
+ h12 = 0;
+ }
+
+ U32 h24 = pm ? h12 + 12 : h12;
+
+ mTime = h24 * MINUTES_PER_HOUR + m;
+}
+
+bool LLTimeCtrl::isTimeStringValid(const LLWString &wstr)
+{
+ std::string str = wstring_to_utf8str(wstr);
+
+ return isHoursStringValid(getHoursString(str)) &&
+ isMinutesStringValid(getMinutesString(str)) &&
+ isPMAMStringValid(getAMPMString(str));
+}
+
+void LLTimeCtrl::increaseMinutes()
+{
+ mTime = (mTime + mSnapToMin) % MINUTES_PER_DAY - (mTime % mSnapToMin);
+}
+
+void LLTimeCtrl::increaseHours()
+{
+ mTime = (mTime + MINUTES_PER_HOUR) % MINUTES_PER_DAY;
+}
+
+void LLTimeCtrl::decreaseMinutes()
+{
+ if (mTime < mSnapToMin)
+ {
+ mTime = MINUTES_PER_DAY - mTime;
+ }
+
+ mTime -= (mTime % mSnapToMin) ? mTime % mSnapToMin : mSnapToMin;
+}
+
+void LLTimeCtrl::decreaseHours()
+{
+ if (mTime < MINUTES_PER_HOUR)
+ {
+ mTime = 23 * MINUTES_PER_HOUR + mTime;
+ }
+ else
+ {
+ mTime -= MINUTES_PER_HOUR;
+ }
+}
+
+bool LLTimeCtrl::isPM() const
+{
+ return mTime >= (MINUTES_PER_DAY / 2);
+}
+
+void LLTimeCtrl::switchDayPeriod()
+{
+ if (isPM())
+ {
+ mTime -= MINUTES_PER_DAY / 2;
+ }
+ else
+ {
+ mTime += MINUTES_PER_DAY / 2;
+ }
+}
+
+void LLTimeCtrl::updateText()
+{
+ U32 h24 = getHours24();
+ U32 m = getMinutes();
+ U32 h12 = h24 > 12 ? h24 - 12 : h24;
+
+ if (h12 == 0)
+ h12 = 12;
+
+ mEditor->setText(llformat("%d:%02d %s", h12, m, isPM() ? "PM":"AM"));
+}
+
+LLTimeCtrl::EEditingPart LLTimeCtrl::getEditingPart()
+{
+ S32 cur_pos = mEditor->getCursor();
+ std::string time_str = mEditor->getText();
+
+ S32 colon_index = time_str.find_first_of(':');
+
+ if (cur_pos <= colon_index)
+ {
+ return HOURS;
+ }
+ else if (cur_pos > colon_index && cur_pos <= (S32)(time_str.length() - AMPM_LEN))
+ {
+ return MINUTES;
+ }
+ else if (cur_pos > (S32)(time_str.length() - AMPM_LEN))
+ {
+ return DAYPART;
+ }
+
+ return NONE;
+}
+
+// static
+std::string LLTimeCtrl::getHoursString(const std::string& str)
+{
+ size_t colon_index = str.find_first_of(':');
+ std::string hours_str = str.substr(0, colon_index);
+
+ return hours_str;
+}
+
+// static
+std::string LLTimeCtrl::getMinutesString(const std::string& str)
+{
+ size_t colon_index = str.find_first_of(':');
+ ++colon_index;
+
+ int minutes_len = str.length() - colon_index - AMPM_LEN;
+ std::string minutes_str = str.substr(colon_index, minutes_len);
+
+ return minutes_str;
+}
+
+// static
+std::string LLTimeCtrl::getAMPMString(const std::string& str)
+{
+ return str.substr(str.size() - 2, 2); // returns last two characters
+}
+
+// static
+bool LLTimeCtrl::isHoursStringValid(const std::string& str)
+{
+ U32 hours;
+ if ((!LLStringUtil::convertToU32(str, hours) || (hours <= HOURS_MAX)) && str.length() < 3)
+ return true;
+
+ return false;
+}
+
+// static
+bool LLTimeCtrl::isMinutesStringValid(const std::string& str)
+{
+ U32 minutes;
+ if (!LLStringUtil::convertToU32(str, minutes) || (minutes <= MINUTES_MAX) && str.length() < 3)
+ return true;
+
+ return false;
+}
+
+// static
+bool LLTimeCtrl::isPMAMStringValid(const std::string& str)
+{
+ S32 len = str.length();
+
+ bool valid = (str[--len] == 'M') && (str[--len] == 'P' || str[len] == 'A');
+
+ return valid;
+}
+
+// static
+U32 LLTimeCtrl::parseHours(const std::string& str)
+{
+ U32 hours;
+ if (LLStringUtil::convertToU32(str, hours) && (hours >= HOURS_MIN) && (hours <= HOURS_MAX))
+ {
+ return hours;
+ }
+ else
+ {
+ return HOURS_MIN;
+ }
+}
+
+// static
+U32 LLTimeCtrl::parseMinutes(const std::string& str)
+{
+ U32 minutes;
+ if (LLStringUtil::convertToU32(str, minutes) && (minutes >= MINUTES_MIN) && (minutes <= MINUTES_MAX))
+ {
+ return minutes;
+ }
+ else
+ {
+ return MINUTES_MIN;
+ }
+}
+
+// static
+bool LLTimeCtrl::parseAMPM(const std::string& str)
+{
+ return str == "PM";
+}
diff --git a/indra/llui/lltimectrl.h b/indra/llui/lltimectrl.h
new file mode 100644
index 0000000000..b5f268c76a
--- /dev/null
+++ b/indra/llui/lltimectrl.h
@@ -0,0 +1,131 @@
+/**
+ * @file lltimectrl.h
+ * @brief Time control
+ *
+ * $LicenseInfo:firstyear=2002&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2011, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#ifndef LLTIMECTRL_H_
+#define LLTIMECTRL_H_
+
+#include "stdtypes.h"
+#include "llbutton.h"
+#include "v4color.h"
+#include "llrect.h"
+
+class LLLineEditor;
+
+class LLTimeCtrl
+: public LLUICtrl
+{
+ LOG_CLASS(LLTimeCtrl);
+public:
+ struct Params : public LLInitParam::Block<Params, LLUICtrl::Params>
+ {
+ Optional<S32> label_width;
+ Optional<S32> snap_to;
+ Optional<bool> allow_text_entry;
+
+ Optional<LLUIColor> text_enabled_color;
+ Optional<LLUIColor> text_disabled_color;
+
+ Optional<LLButton::Params> up_button;
+ Optional<LLButton::Params> down_button;
+
+ Params();
+ };
+
+ F32 getTime24() const; // 0.0 - 24.0
+ U32 getHours24() const; // 0 - 23
+ U32 getMinutes() const; // 0 - 59
+
+ void setTime24(F32 time); // 0.0 - 23.98(3)
+
+protected:
+ LLTimeCtrl(const Params&);
+ friend class LLUICtrlFactory;
+
+private:
+
+ enum EDayPeriod
+ {
+ AM,
+ PM
+ };
+
+ enum EEditingPart
+ {
+ HOURS,
+ MINUTES,
+ DAYPART,
+ NONE
+ };
+
+ virtual void onFocusLost();
+ virtual BOOL handleKeyHere(KEY key, MASK mask);
+
+ void onUpBtn();
+ void onDownBtn();
+ void onTextEntry(LLLineEditor* line_editor);
+
+ bool isTimeStringValid(const LLWString& wstr);
+
+ void increaseMinutes();
+ void increaseHours();
+
+ void decreaseMinutes();
+ void decreaseHours();
+
+ bool isPM() const;
+ void switchDayPeriod();
+
+ void updateText();
+
+ EEditingPart getEditingPart();
+
+ static std::string getHoursString(const std::string& str);
+ static std::string getMinutesString(const std::string& str);
+ static std::string getAMPMString(const std::string& str);
+
+ static bool isHoursStringValid(const std::string& str);
+ static bool isMinutesStringValid(const std::string& str);
+ static bool isPMAMStringValid(const std::string& str);
+
+ static U32 parseHours(const std::string& str);
+ static U32 parseMinutes(const std::string& str);
+ static bool parseAMPM(const std::string& str);
+
+ class LLTextBox* mLabelBox;
+
+ class LLLineEditor* mEditor;
+ LLUIColor mTextEnabledColor;
+ LLUIColor mTextDisabledColor;
+
+ class LLButton* mUpBtn;
+ class LLButton* mDownBtn;
+
+ U32 mTime; // minutes since midnight: 0 - 1439
+ U32 mSnapToMin; // interval in minutes to snap to
+
+ BOOL mAllowEdit;
+};
+#endif /* LLTIMECTRL_H_ */