summaryrefslogtreecommitdiff
path: root/indra
diff options
context:
space:
mode:
Diffstat (limited to 'indra')
-rw-r--r--indra/integration_tests/llui_libtest/llwidgetreg.cpp2
-rw-r--r--indra/llui/CMakeLists.txt2
-rw-r--r--indra/llui/lllineeditor.cpp106
-rw-r--r--indra/llui/lllineeditor.h4
-rw-r--r--indra/llui/lltextvalidate.cpp33
-rw-r--r--indra/llui/lltextvalidate.h1
-rw-r--r--indra/llui/lltimectrl.cpp390
-rw-r--r--indra/llui/lltimectrl.h125
-rw-r--r--indra/newview/skins/default/xui/en/widgets/time.xml16
9 files changed, 658 insertions, 21 deletions
diff --git a/indra/integration_tests/llui_libtest/llwidgetreg.cpp b/indra/integration_tests/llui_libtest/llwidgetreg.cpp
index 0d0d9fbff6..cbf6021119 100644
--- a/indra/integration_tests/llui_libtest/llwidgetreg.cpp
+++ b/indra/integration_tests/llui_libtest/llwidgetreg.cpp
@@ -49,6 +49,7 @@
#include "lltabcontainer.h"
#include "lltextbox.h"
#include "lltexteditor.h"
+#include "lltimectrl.h"
#include "llflyoutbutton.h"
#include "llfiltereditor.h"
#include "lllayoutstack.h"
@@ -92,6 +93,7 @@ void LLWidgetReg::initClass(bool register_widgets)
//LLDefaultChildRegistry::Register<LLPlaceHolderPanel> placeholder("placeholder");
LLDefaultChildRegistry::Register<LLTabContainer> tab_container("tab_container");
LLDefaultChildRegistry::Register<LLTextBox> text("text");
+ LLDefaultChildRegistry::Register<LLTimeCtrl> time("time");
LLDefaultChildRegistry::Register<LLTextEditor> simple_text_editor("simple_text_editor");
LLDefaultChildRegistry::Register<LLUICtrl> ui_ctrl("ui_ctrl");
LLDefaultChildRegistry::Register<LLStatView> stat_view("stat_view");
diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt
index 33ab2e93b5..72329c70fe 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 d99ee5a545..66c607e988 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());
+ setPrevalidate(p.prevalidate_input_callback());
setPrevalidate(p.prevalidate_callback());
LLContextMenu* menu = LLUICtrlFactory::instance().createFromFile<LLContextMenu>
@@ -401,23 +403,16 @@ 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 selection_length = llabs( mSelectionStart - cursor_pos );
+ const LLWString& text = mText.getWString();
+ const LLWString& substr = text.substr(left_pos, selection_length);
+
+ if (mPrevalidateInputFunc && mIsSelecting && !mPrevalidateInputFunc(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);
}
@@ -501,6 +496,9 @@ BOOL LLLineEditor::canSelectAll() const
void LLLineEditor::selectAll()
{
+ if (mPrevalidateInputFunc && !mPrevalidateInputFunc(mText.getWString()))
+ return;
+
mSelectionStart = mText.length();
mSelectionEnd = 0;
setCursor(mSelectionEnd);
@@ -586,6 +584,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);
@@ -620,8 +621,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
{
@@ -792,6 +791,9 @@ void LLLineEditor::removeChar()
{
if( getCursor() > 0 )
{
+ if (mPrevalidateInputFunc && !mPrevalidateInputFunc(mText.getWString().substr(getCursor()-1, 1)))
+ return;
+
mText.erase(getCursor() - 1, 1);
setCursor(getCursor() - 1);
@@ -812,6 +814,9 @@ void LLLineEditor::addChar(const llwchar uni_char)
}
else if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode())
{
+ if (mPrevalidateInputFunc && !mPrevalidateInputFunc(mText.getWString().substr(getCursor(), 1)))
+ return;
+
mText.erase(getCursor(), 1);
}
@@ -860,6 +865,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();
+
+ if ( mPrevalidateInputFunc && !mPrevalidateInputFunc(selection.substr(left_pos, selection_length)))
+ return;
+
setCursor(new_cursor_pos);
mSelectionEnd = getCursor();
}
@@ -992,6 +1004,10 @@ void LLLineEditor::deleteSelection()
{
S32 left_pos = llmin( mSelectionStart, mSelectionEnd );
S32 selection_length = llabs( mSelectionStart - mSelectionEnd );
+ const LLWString& selection = mText.getWString();
+
+ if ( mPrevalidateInputFunc && !mPrevalidateInputFunc(selection.substr(left_pos, selection_length)))
+ return;
mText.erase(left_pos, selection_length);
deselect();
@@ -1009,12 +1025,16 @@ void LLLineEditor::cut()
{
if( canCut() )
{
+ S32 left_pos = llmin( mSelectionStart, mSelectionEnd );
+ S32 length = llabs( mSelectionStart - mSelectionEnd );
+ const LLWString& selection = mText.getWString();
+
+ if ( mPrevalidateInputFunc && !mPrevalidateInputFunc(selection.substr(left_pos, length)))
+ 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();
@@ -1094,6 +1114,9 @@ void LLLineEditor::pasteHelper(bool is_primary)
if (!paste.empty())
{
+ if ( mPrevalidateInputFunc && !mPrevalidateInputFunc(paste) )
+ return;
+
// Prepare for possible rollback
LLLineEditorRollback rollback(this);
@@ -1441,6 +1464,11 @@ BOOL LLLineEditor::handleUnicodeCharHere(llwchar uni_char)
LLLineEditorRollback rollback( this );
+ LLWString u_char;
+ u_char.assign(1, uni_char);
+ if (mPrevalidateInputFunc && !mPrevalidateInputFunc(u_char))
+ return handled;
+
addChar(uni_char);
mKeystrokeTimer.reset();
@@ -1492,6 +1520,15 @@ void LLLineEditor::doDelete()
}
else if ( getCursor() < mText.length())
{
+ const LLWString& selection = mText.getWString();
+
+ if ( mPrevalidateInputFunc && !mPrevalidateInputFunc(selection.substr(getCursor(), 1)))
+ {
+ if( mKeystrokeCallback )
+ mKeystrokeCallback( this );
+
+ return;
+ }
setCursor(getCursor() + 1);
removeChar();
}
@@ -1839,6 +1876,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()
{
@@ -1932,6 +1990,12 @@ void LLLineEditor::setPrevalidate(LLTextValidate::validate_func_t func)
updateAllowingLanguageInput();
}
+void LLLineEditor::setPrevalidateInputText(LLTextValidate::validate_func_t func)
+{
+ mPrevalidateInputFunc = func;
+ updateAllowingLanguageInput();
+}
+
// static
BOOL LLLineEditor::postvalidateFloat(const std::string &str)
{
diff --git a/indra/llui/lllineeditor.h b/indra/llui/lllineeditor.h
index 7b5fa218f2..1e29fd0dbf 100644
--- a/indra/llui/lllineeditor.h
+++ b/indra/llui/lllineeditor.h
@@ -75,6 +75,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;
@@ -231,6 +232,7 @@ public:
// Prevalidation controls which keystrokes can affect the editor
void setPrevalidate( LLTextValidate::validate_func_t func );
+ void setPrevalidateInputText( LLTextValidate::validate_func_t func );
static BOOL postvalidateFloat(const std::string &str);
// line history support:
@@ -250,6 +252,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);
@@ -311,6 +314,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..33e8db432f
--- /dev/null
+++ b/indra/llui/lltimectrl.cpp
@@ -0,0 +1,390 @@
+/**
+ * @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;
+
+LLTimeCtrl::Params::Params()
+: label_width("label_width"),
+ 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()),
+ mHours(HOURS_MIN),
+ mMinutes(MINUTES_MIN)
+{
+ 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->setPrevalidateInputText(LLTextValidate::validateNonNegativeS32NoSpace);
+ mEditor->setPrevalidate(boost::bind(&LLTimeCtrl::isTimeStringValid, this, _1));
+ mEditor->setText(LLStringExplicit("0: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 );
+}
+
+BOOL LLTimeCtrl::handleKeyHere(KEY key, MASK mask)
+{
+ if (mEditor->hasFocus())
+ {
+ if(key == KEY_UP)
+ {
+ onUpBtn();
+ return TRUE;
+ }
+ if(key == KEY_DOWN)
+ {
+ onDownBtn();
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+void LLTimeCtrl::onUpBtn()
+{
+ switch(getEditingPart())
+ {
+ case HOURS:
+ increaseHours();
+ break;
+ case MINUTES:
+ increaseMinutes();
+ break;
+ case DAYPART:
+ switchDayPeriod();
+ break;
+ default:
+ break;
+ }
+
+ buildTimeString();
+ mEditor->setText(mTimeString);
+}
+
+void LLTimeCtrl::onDownBtn()
+{
+ switch(getEditingPart())
+ {
+ case HOURS:
+ decreaseHours();
+ break;
+ case MINUTES:
+ decreaseMinutes();
+ break;
+ case DAYPART:
+ switchDayPeriod();
+ break;
+ default:
+ break;
+ }
+
+ buildTimeString();
+ mEditor->setText(mTimeString);
+}
+
+void LLTimeCtrl::onFocusLost()
+{
+ buildTimeString();
+ mEditor->setText(mTimeString);
+
+ LLUICtrl::onFocusLost();
+}
+
+void LLTimeCtrl::onTextEntry(LLLineEditor* line_editor)
+{
+ LLWString time_str = line_editor->getWText();
+ switch(getEditingPart())
+ {
+ case HOURS:
+ validateHours(getHoursWString(time_str));
+ break;
+ case MINUTES:
+ validateMinutes(getMinutesWString(time_str));
+ break;
+ default:
+ break;
+ }
+}
+
+bool LLTimeCtrl::isTimeStringValid(const LLWString &wstr)
+{
+ if (!isHoursStringValid(getHoursWString(wstr)) || !isMinutesStringValid(getMinutesWString(wstr)) || !isPMAMStringValid(wstr))
+ return false;
+
+ return true;
+}
+
+bool LLTimeCtrl::isHoursStringValid(const LLWString& wstr)
+{
+ U32 hours;
+ if ((!LLWStringUtil::convertToU32(wstr, hours) || (hours <= HOURS_MAX)) && wstr.length() < 3)
+ return true;
+
+ return false;
+}
+
+bool LLTimeCtrl::isMinutesStringValid(const LLWString& wstr)
+{
+ U32 minutes;
+ if (!LLWStringUtil::convertToU32(wstr, minutes) || (minutes <= MINUTES_MAX) && wstr.length() < 3)
+ return true;
+
+ return false;
+}
+
+void LLTimeCtrl::validateHours(const LLWString& wstr)
+{
+ U32 hours;
+ if (LLWStringUtil::convertToU32(wstr, hours) && (hours >= HOURS_MIN) && (hours <= HOURS_MAX))
+ {
+ mHours = hours;
+ }
+ else
+ {
+ mHours = HOURS_MIN;
+ }
+}
+
+void LLTimeCtrl::validateMinutes(const LLWString& wstr)
+{
+ U32 minutes;
+ if (LLWStringUtil::convertToU32(wstr, minutes) && (minutes >= MINUTES_MIN) && (minutes <= MINUTES_MAX))
+ {
+ mMinutes = minutes;
+ }
+ else
+ {
+ mMinutes = MINUTES_MIN;
+ }
+}
+
+bool LLTimeCtrl::isPMAMStringValid(const LLWString &wstr)
+{
+ S32 len = wstr.length();
+
+ bool valid = (wstr[--len] == 'M') && (wstr[--len] == 'P' || wstr[len] == 'A');
+
+ return valid;
+}
+
+LLWString LLTimeCtrl::getHoursWString(const LLWString& wstr)
+{
+ size_t colon_index = wstr.find_first_of(':');
+ LLWString hours_str = wstr.substr(0, colon_index);
+
+ return hours_str;
+}
+
+LLWString LLTimeCtrl::getMinutesWString(const LLWString& wstr)
+{
+ size_t colon_index = wstr.find_first_of(':');
+ ++colon_index;
+
+ int minutes_len = wstr.length() - colon_index - AMPM_LEN;
+ LLWString minutes_str = wstr.substr(colon_index, minutes_len);
+
+ return minutes_str;
+}
+
+void LLTimeCtrl::increaseMinutes()
+{
+ if (++mMinutes > MINUTES_MAX)
+ {
+ mMinutes = MINUTES_MIN;
+ }
+}
+
+void LLTimeCtrl::increaseHours()
+{
+ if (++mHours > HOURS_MAX)
+ {
+ mHours = HOURS_MIN;
+ }
+}
+
+void LLTimeCtrl::decreaseMinutes()
+{
+ if (mMinutes-- == MINUTES_MIN)
+ {
+ mMinutes = MINUTES_MAX;
+ }
+}
+
+void LLTimeCtrl::decreaseHours()
+{
+ if (mHours-- == HOURS_MIN)
+ {
+ mHours = HOURS_MAX;
+ switchDayPeriod();
+ }
+}
+
+void LLTimeCtrl::switchDayPeriod()
+{
+ switch (mCurrentDayPeriod)
+ {
+ case AM:
+ mCurrentDayPeriod = PM;
+ break;
+ case PM:
+ mCurrentDayPeriod = AM;
+ break;
+ }
+}
+
+void LLTimeCtrl::buildTimeString()
+{
+ std::stringstream time_buf;
+ time_buf << mHours << ":";
+
+ if (mMinutes < 10)
+ {
+ time_buf << "0";
+ }
+
+ time_buf << mMinutes;
+ time_buf << " ";
+
+ switch (mCurrentDayPeriod)
+ {
+ case AM:
+ time_buf << "AM";
+ break;
+ case PM:
+ time_buf << "PM";
+ break;
+ }
+
+ mTimeString = time_buf.str();
+}
+
+LLTimeCtrl::EEditingPart LLTimeCtrl::getEditingPart()
+{
+ S32 cur_pos = mEditor->getCursor();
+ LLWString time_str = mEditor->getWText();
+
+ size_t colon_index = time_str.find_first_of(':');
+
+ if (cur_pos <= colon_index)
+ {
+ return HOURS;
+ }
+ else if (cur_pos > colon_index && cur_pos <= (time_str.length() - AMPM_LEN))
+ {
+ return MINUTES;
+ }
+ else if (cur_pos > (time_str.length() - AMPM_LEN))
+ {
+ return DAYPART;
+ }
+
+ return NONE;
+}
diff --git a/indra/llui/lltimectrl.h b/indra/llui/lltimectrl.h
new file mode 100644
index 0000000000..81d4477da4
--- /dev/null
+++ b/indra/llui/lltimectrl.h
@@ -0,0 +1,125 @@
+/**
+ * @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
+{
+public:
+ struct Params : public LLInitParam::Block<Params, LLUICtrl::Params>
+ {
+ Optional<S32> label_width;
+ 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();
+ };
+protected:
+ LLTimeCtrl(const Params&);
+ friend class LLUICtrlFactory;
+
+ U32 getHours() const { return mHours; }
+ U32 getMinutes() const { return mMinutes; }
+
+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);
+
+ void validateHours(const LLWString& wstr);
+ void validateMinutes(const LLWString& wstr);
+ bool isTimeStringValid(const LLWString& wstr);
+
+ bool isPMAMStringValid(const LLWString& wstr);
+ bool isHoursStringValid(const LLWString& wstr);
+ bool isMinutesStringValid(const LLWString& wstr);
+
+ LLWString getHoursWString(const LLWString& wstr);
+ LLWString getMinutesWString(const LLWString& wstr);
+
+ void increaseMinutes();
+ void increaseHours();
+
+ void decreaseMinutes();
+ void decreaseHours();
+
+ void switchDayPeriod();
+
+ void buildTimeString();
+
+ EEditingPart getEditingPart();
+
+ class LLTextBox* mLabelBox;
+
+ class LLLineEditor* mEditor;
+ LLUIColor mTextEnabledColor;
+ LLUIColor mTextDisabledColor;
+
+ class LLButton* mUpBtn;
+ class LLButton* mDownBtn;
+
+ U32 mHours;
+ U32 mMinutes;
+ EDayPeriod mCurrentDayPeriod;
+
+ std::string mTimeString;
+
+ BOOL mAllowEdit;
+};
+#endif /* LLTIMECTRL_H_ */
diff --git a/indra/newview/skins/default/xui/en/widgets/time.xml b/indra/newview/skins/default/xui/en/widgets/time.xml
new file mode 100644
index 0000000000..b5bdd564a6
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/widgets/time.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<time text_enabled_color="LabelTextColor"
+ text_disabled_color="LabelDisabledColor"
+ font="SansSerifSmall"
+ label_width="40" >
+ <time.up_button name="SpinCtrl Up"
+ image_unselected="Stepper_Up_Off"
+ image_selected="Stepper_Up_Press"
+ tab_stop="false"
+ follows="left|bottom" />
+ <time.down_button name="SpinCtrl Down"
+ image_unselected="Stepper_Down_Off"
+ image_selected="Stepper_Down_Press"
+ tab_stop="false"
+ follows="left|bottom" />
+</time>