From 2d19b297a98d8be87c487f6b93e098dce9ca908c Mon Sep 17 00:00:00 2001 From: Paul ProductEngine Date: Fri, 13 May 2011 18:23:11 +0300 Subject: STORM-1202 FIXED Time spin control Implemented time spin control which is like spin control, but shows and allows to edit time string in "hh:mm PM/AM" format. Implemented according to the WLRS spec. --- indra/llui/lltimectrl.cpp | 390 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 390 insertions(+) create mode 100644 indra/llui/lltimectrl.cpp (limited to 'indra/llui/lltimectrl.cpp') 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 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 spinctrl_spacing ("UISpinctrlSpacing", 0); + static LLUICachedControl spinctrl_btn_width ("UISpinctrlBtnWidth", 0); + static LLUICachedControl 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 (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 (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(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(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; +} -- cgit v1.3 From 5daeefd35e1e105f403f99184ad866e8823767d4 Mon Sep 17 00:00:00 2001 From: Paul ProductEngine Date: Mon, 16 May 2011 17:59:05 +0300 Subject: STORM-1202 Code cleanup for LLLineEditor - Removed code duplication - Renamed prevalidateInputText to prevalidateInput --- indra/llui/lllineeditor.cpp | 65 +++++++++++++++++++++++++++------------------ indra/llui/lllineeditor.h | 8 +++++- indra/llui/lltimectrl.cpp | 2 +- 3 files changed, 47 insertions(+), 28 deletions(-) (limited to 'indra/llui/lltimectrl.cpp') diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp index 66c607e988..2527a608bf 100644 --- a/indra/llui/lllineeditor.cpp +++ b/indra/llui/lllineeditor.cpp @@ -174,7 +174,7 @@ LLLineEditor::LLLineEditor(const LLLineEditor::Params& p) updateTextPadding(); setCursor(mText.length()); - setPrevalidate(p.prevalidate_input_callback()); + setPrevalidateInput(p.prevalidate_input_callback()); setPrevalidate(p.prevalidate_callback()); LLContextMenu* menu = LLUICtrlFactory::instance().createFromFile @@ -406,11 +406,10 @@ void LLLineEditor::setCursorAtLocalPos( S32 local_mouse_x ) 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); + S32 length = llabs( mSelectionStart - cursor_pos ); + const LLWString& substr = mText.getWString().substr(left_pos, length); - if (mPrevalidateInputFunc && mIsSelecting && !mPrevalidateInputFunc(substr)) + if (mIsSelecting && !prevalidateInput(substr)) return; setCursor(cursor_pos); @@ -496,8 +495,10 @@ BOOL LLLineEditor::canSelectAll() const void LLLineEditor::selectAll() { - if (mPrevalidateInputFunc && !mPrevalidateInputFunc(mText.getWString())) + if (!prevalidateInput(mText.getWString())) + { return; + } mSelectionStart = mText.length(); mSelectionEnd = 0; @@ -791,7 +792,7 @@ void LLLineEditor::removeChar() { if( getCursor() > 0 ) { - if (mPrevalidateInputFunc && !mPrevalidateInputFunc(mText.getWString().substr(getCursor()-1, 1))) + if (!prevalidateInput(mText.getWString().substr(getCursor()-1, 1))) return; mText.erase(getCursor() - 1, 1); @@ -814,7 +815,7 @@ void LLLineEditor::addChar(const llwchar uni_char) } else if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode()) { - if (mPrevalidateInputFunc && !mPrevalidateInputFunc(mText.getWString().substr(getCursor(), 1))) + if (!prevalidateInput(mText.getWString().substr(getCursor(), 1))) return; mText.erase(getCursor(), 1); @@ -867,9 +868,9 @@ void LLLineEditor::extendSelection( S32 new_cursor_pos ) S32 left_pos = llmin( mSelectionStart, new_cursor_pos ); S32 selection_length = llabs( mSelectionStart - new_cursor_pos ); - const LLWString& selection = mText.getWString(); + const LLWString& selection = mText.getWString().substr(left_pos, selection_length); - if ( mPrevalidateInputFunc && !mPrevalidateInputFunc(selection.substr(left_pos, selection_length))) + if (!prevalidateInput(selection)) return; setCursor(new_cursor_pos); @@ -1002,11 +1003,11 @@ void LLLineEditor::deleteSelection() { if( !mReadOnly && hasSelection() ) { - S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); - S32 selection_length = llabs( mSelectionStart - mSelectionEnd ); - const LLWString& selection = mText.getWString(); + S32 left_pos, selection_length; + getSelectionRange(&left_pos, &selection_length); + const LLWString& selection = mText.getWString().substr(left_pos, selection_length); - if ( mPrevalidateInputFunc && !mPrevalidateInputFunc(selection.substr(left_pos, selection_length))) + if (!prevalidateInput(selection)) return; mText.erase(left_pos, selection_length); @@ -1025,11 +1026,11 @@ void LLLineEditor::cut() { if( canCut() ) { - S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); - S32 length = llabs( mSelectionStart - mSelectionEnd ); - const LLWString& selection = mText.getWString(); + S32 left_pos, length; + getSelectionRange(&left_pos, &length); + const LLWString& selection = mText.getWString().substr(left_pos, length); - if ( mPrevalidateInputFunc && !mPrevalidateInputFunc(selection.substr(left_pos, length))) + if (!prevalidateInput(selection)) return; // Prepare for possible rollback @@ -1114,7 +1115,7 @@ void LLLineEditor::pasteHelper(bool is_primary) if (!paste.empty()) { - if ( mPrevalidateInputFunc && !mPrevalidateInputFunc(paste) ) + if (!prevalidateInput(paste)) return; // Prepare for possible rollback @@ -1464,10 +1465,12 @@ 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; + { + LLWString u_char; + u_char.assign(1, uni_char); + if (!prevalidateInput(u_char)) + return handled; + } addChar(uni_char); @@ -1520,9 +1523,9 @@ void LLLineEditor::doDelete() } else if ( getCursor() < mText.length()) { - const LLWString& selection = mText.getWString(); + const LLWString& text_to_delete = mText.getWString().substr(getCursor(), 1); - if ( mPrevalidateInputFunc && !mPrevalidateInputFunc(selection.substr(getCursor(), 1))) + if (!prevalidateInput(text_to_delete)) { if( mKeystrokeCallback ) mKeystrokeCallback( this ); @@ -1990,12 +1993,22 @@ void LLLineEditor::setPrevalidate(LLTextValidate::validate_func_t func) updateAllowingLanguageInput(); } -void LLLineEditor::setPrevalidateInputText(LLTextValidate::validate_func_t func) +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 1e29fd0dbf..1588f59b46 100644 --- a/indra/llui/lllineeditor.h +++ b/indra/llui/lllineeditor.h @@ -232,9 +232,15 @@ public: // Prevalidation controls which keystrokes can affect the editor void setPrevalidate( LLTextValidate::validate_func_t func ); - void setPrevalidateInputText( 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 diff --git a/indra/llui/lltimectrl.cpp b/indra/llui/lltimectrl.cpp index 33e8db432f..08d24a29a8 100644 --- a/indra/llui/lltimectrl.cpp +++ b/indra/llui/lltimectrl.cpp @@ -106,7 +106,7 @@ LLTimeCtrl::LLTimeCtrl(const LLTimeCtrl::Params& p) params.max_length.chars(8); params.keystroke_callback(boost::bind(&LLTimeCtrl::onTextEntry, this, _1)); mEditor = LLUICtrlFactory::create (params); - mEditor->setPrevalidateInputText(LLTextValidate::validateNonNegativeS32NoSpace); + mEditor->setPrevalidateInput(LLTextValidate::validateNonNegativeS32NoSpace); mEditor->setPrevalidate(boost::bind(&LLTimeCtrl::isTimeStringValid, this, _1)); mEditor->setText(LLStringExplicit("0:00 AM")); addChild(mEditor); -- cgit v1.3 From a9d7ee879216857fd61b36c70ffea257021c869a Mon Sep 17 00:00:00 2001 From: Vadim ProductEngine Date: Wed, 18 May 2011 16:07:36 +0300 Subject: STORM-1202 ADDITIONAL_FIX Fixing Windows build. --- indra/llui/lltimectrl.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'indra/llui/lltimectrl.cpp') diff --git a/indra/llui/lltimectrl.cpp b/indra/llui/lltimectrl.cpp index 08d24a29a8..4b49c45006 100644 --- a/indra/llui/lltimectrl.cpp +++ b/indra/llui/lltimectrl.cpp @@ -371,17 +371,17 @@ LLTimeCtrl::EEditingPart LLTimeCtrl::getEditingPart() S32 cur_pos = mEditor->getCursor(); LLWString time_str = mEditor->getWText(); - size_t colon_index = time_str.find_first_of(':'); + S32 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)) + else if (cur_pos > colon_index && cur_pos <= (S32)(time_str.length() - AMPM_LEN)) { return MINUTES; } - else if (cur_pos > (time_str.length() - AMPM_LEN)) + else if (cur_pos > (S32)(time_str.length() - AMPM_LEN)) { return DAYPART; } -- cgit v1.3 From f02d6e7095d526bc7abc75cc796dc4f07c9b18c7 Mon Sep 17 00:00:00 2001 From: Paul ProductEngine Date: Wed, 18 May 2011 21:07:06 +0300 Subject: STORM-1202 ADDITIONAL_FIX Workaround for another Windows build issue. --- indra/llui/lltimectrl.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'indra/llui/lltimectrl.cpp') diff --git a/indra/llui/lltimectrl.cpp b/indra/llui/lltimectrl.cpp index 4b49c45006..a77842a392 100644 --- a/indra/llui/lltimectrl.cpp +++ b/indra/llui/lltimectrl.cpp @@ -225,7 +225,8 @@ bool LLTimeCtrl::isTimeStringValid(const LLWString &wstr) bool LLTimeCtrl::isHoursStringValid(const LLWString& wstr) { U32 hours; - if ((!LLWStringUtil::convertToU32(wstr, hours) || (hours <= HOURS_MAX)) && wstr.length() < 3) + std::string utf8time = wstring_to_utf8str(wstr); + if ((!LLStringUtil::convertToU32(utf8time, hours) || (hours <= HOURS_MAX)) && wstr.length() < 3) return true; return false; @@ -234,7 +235,8 @@ bool LLTimeCtrl::isHoursStringValid(const LLWString& wstr) bool LLTimeCtrl::isMinutesStringValid(const LLWString& wstr) { U32 minutes; - if (!LLWStringUtil::convertToU32(wstr, minutes) || (minutes <= MINUTES_MAX) && wstr.length() < 3) + std::string utf8time = wstring_to_utf8str(wstr); + if (!LLStringUtil::convertToU32(utf8time, minutes) || (minutes <= MINUTES_MAX) && wstr.length() < 3) return true; return false; @@ -243,7 +245,8 @@ bool LLTimeCtrl::isMinutesStringValid(const LLWString& wstr) void LLTimeCtrl::validateHours(const LLWString& wstr) { U32 hours; - if (LLWStringUtil::convertToU32(wstr, hours) && (hours >= HOURS_MIN) && (hours <= HOURS_MAX)) + std::string utf8time = wstring_to_utf8str(wstr); + if (LLStringUtil::convertToU32(utf8time, hours) && (hours >= HOURS_MIN) && (hours <= HOURS_MAX)) { mHours = hours; } @@ -256,7 +259,8 @@ void LLTimeCtrl::validateHours(const LLWString& wstr) void LLTimeCtrl::validateMinutes(const LLWString& wstr) { U32 minutes; - if (LLWStringUtil::convertToU32(wstr, minutes) && (minutes >= MINUTES_MIN) && (minutes <= MINUTES_MAX)) + std::string utf8time = wstring_to_utf8str(wstr); + if (LLStringUtil::convertToU32(utf8time, minutes) && (minutes >= MINUTES_MIN) && (minutes <= MINUTES_MAX)) { mMinutes = minutes; } -- cgit v1.3 From 657e434fd59139436e8b97e5ecd01ca686e82269 Mon Sep 17 00:00:00 2001 From: Vadim ProductEngine Date: Mon, 30 May 2011 22:34:56 +0300 Subject: STORM-1253 WIP New day cycle editor. Done: * Creating new local day cycles. * Editing existing local day cycles. * Deleting day cycles. To do: * Editing region day cycle, dealing with skies in region scope. * Handle teleport while editing a day cycle. * Update UI when a day cycle or sky preset gets deleted. * Make the time ctrl increase/decrease consistently. --- indra/llui/lltimectrl.cpp | 86 ++- indra/llui/lltimectrl.h | 21 +- indra/newview/lldaycyclemanager.cpp | 62 ++- indra/newview/lldaycyclemanager.h | 9 +- indra/newview/llenvmanager.cpp | 8 +- indra/newview/llfloaterdeleteenvpreset.cpp | 68 ++- indra/newview/llfloaterdeleteenvpreset.h | 2 + indra/newview/llfloatereditdaycycle.cpp | 599 ++++++++++++++++++++- indra/newview/llfloatereditdaycycle.h | 80 +++ indra/newview/llfloaterwindlight.cpp | 8 +- indra/newview/llwldaycycle.cpp | 11 +- indra/newview/llwldaycycle.h | 5 +- indra/newview/llwlparammanager.h | 8 +- .../default/xui/en/floater_delete_env_preset.xml | 2 + .../default/xui/en/floater_edit_day_cycle.xml | 30 +- 15 files changed, 941 insertions(+), 58 deletions(-) (limited to 'indra/llui/lltimectrl.cpp') diff --git a/indra/llui/lltimectrl.cpp b/indra/llui/lltimectrl.cpp index a77842a392..ce376f001d 100644 --- a/indra/llui/lltimectrl.cpp +++ b/indra/llui/lltimectrl.cpp @@ -108,7 +108,7 @@ LLTimeCtrl::LLTimeCtrl(const LLTimeCtrl::Params& p) mEditor = LLUICtrlFactory::create (params); mEditor->setPrevalidateInput(LLTextValidate::validateNonNegativeS32NoSpace); mEditor->setPrevalidate(boost::bind(&LLTimeCtrl::isTimeStringValid, this, _1)); - mEditor->setText(LLStringExplicit("0:00 AM")); + mEditor->setText(LLStringExplicit("12:00 AM")); addChild(mEditor); //================= Spin Buttons ==========// @@ -130,6 +130,66 @@ LLTimeCtrl::LLTimeCtrl(const LLTimeCtrl::Params& p) setUseBoundingRect( TRUE ); } +F32 LLTimeCtrl::getTime24() const +{ + return getHours24() + getMinutes() / 60.0f; +} + +U32 LLTimeCtrl::getHours24() const +{ + U32 h = mHours; + + if (h == 12) + { + h = 0; + } + + if (mCurrentDayPeriod == PM) + { + h += 12; + } + + return h; +} + +U32 LLTimeCtrl::getMinutes() const +{ + return mMinutes; +} + +void LLTimeCtrl::setTime24(F32 time) +{ + time = llclamp(time, 0.0f, 23.99f); // fix out of range values + + U32 h = time; + U32 m = llround((time - h) * 60); // fixes values like 4.99999 + + // fix rounding error + if (m == 60) + { + m = 0; + ++h; + } + + mCurrentDayPeriod = (h >= 12 ? PM : AM); + + if (h >= 12) + { + h -= 12; + } + + if (h == 0) + { + h = 12; + } + + + mHours = h; + mMinutes = m; + + updateText(); +} + BOOL LLTimeCtrl::handleKeyHere(KEY key, MASK mask) { if (mEditor->hasFocus()) @@ -144,6 +204,11 @@ BOOL LLTimeCtrl::handleKeyHere(KEY key, MASK mask) onDownBtn(); return TRUE; } + if (key == KEY_RETURN) + { + onCommit(); + return TRUE; + } } return FALSE; } @@ -165,8 +230,8 @@ void LLTimeCtrl::onUpBtn() break; } - buildTimeString(); - mEditor->setText(mTimeString); + updateText(); + onCommit(); } void LLTimeCtrl::onDownBtn() @@ -186,15 +251,14 @@ void LLTimeCtrl::onDownBtn() break; } - buildTimeString(); - mEditor->setText(mTimeString); + updateText(); + onCommit(); } void LLTimeCtrl::onFocusLost() { - buildTimeString(); - mEditor->setText(mTimeString); - + updateText(); + onCommit(); LLUICtrl::onFocusLost(); } @@ -300,6 +364,7 @@ LLWString LLTimeCtrl::getMinutesWString(const LLWString& wstr) void LLTimeCtrl::increaseMinutes() { + // *TODO: snap to 5 min if (++mMinutes > MINUTES_MAX) { mMinutes = MINUTES_MIN; @@ -316,6 +381,7 @@ void LLTimeCtrl::increaseHours() void LLTimeCtrl::decreaseMinutes() { + // *TODO: snap to 5 min if (mMinutes-- == MINUTES_MIN) { mMinutes = MINUTES_MAX; @@ -344,7 +410,7 @@ void LLTimeCtrl::switchDayPeriod() } } -void LLTimeCtrl::buildTimeString() +void LLTimeCtrl::updateText() { std::stringstream time_buf; time_buf << mHours << ":"; @@ -367,7 +433,7 @@ void LLTimeCtrl::buildTimeString() break; } - mTimeString = time_buf.str(); + mEditor->setText(time_buf.str()); } LLTimeCtrl::EEditingPart LLTimeCtrl::getEditingPart() diff --git a/indra/llui/lltimectrl.h b/indra/llui/lltimectrl.h index 81d4477da4..aebc5b6eab 100644 --- a/indra/llui/lltimectrl.h +++ b/indra/llui/lltimectrl.h @@ -37,6 +37,7 @@ class LLLineEditor; class LLTimeCtrl : public LLUICtrl { + LOG_CLASS(LLTimeCtrl); public: struct Params : public LLInitParam::Block { @@ -51,13 +52,17 @@ public: 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; - U32 getHours() const { return mHours; } - U32 getMinutes() const { return mMinutes; } - private: enum EDayPeriod @@ -101,7 +106,7 @@ private: void switchDayPeriod(); - void buildTimeString(); + void updateText(); EEditingPart getEditingPart(); @@ -114,11 +119,9 @@ private: class LLButton* mUpBtn; class LLButton* mDownBtn; - U32 mHours; - U32 mMinutes; - EDayPeriod mCurrentDayPeriod; - - std::string mTimeString; + U32 mHours; // 1 - 12 + U32 mMinutes; // 0 - 59 + EDayPeriod mCurrentDayPeriod; // AM/PM BOOL mAllowEdit; }; diff --git a/indra/newview/lldaycyclemanager.cpp b/indra/newview/lldaycyclemanager.cpp index c0eae8cd3c..e80da5ba11 100644 --- a/indra/newview/lldaycyclemanager.cpp +++ b/indra/newview/lldaycyclemanager.cpp @@ -38,7 +38,7 @@ const LLDayCycleManager::dc_map_t& LLDayCycleManager::getPresets() return mDayCycleMap; } -bool LLDayCycleManager::getPreset(const std::string name, LLWLDayCycle& day_cycle) +bool LLDayCycleManager::getPreset(const std::string name, LLWLDayCycle& day_cycle) const { dc_map_t::const_iterator it = mDayCycleMap.find(name); if (it == mDayCycleMap.end()) @@ -50,7 +50,7 @@ bool LLDayCycleManager::getPreset(const std::string name, LLWLDayCycle& day_cycl return true; } -bool LLDayCycleManager::getPreset(const std::string name, LLSD& day_cycle) +bool LLDayCycleManager::getPreset(const std::string name, LLSD& day_cycle) const { LLWLDayCycle dc; if (!getPreset(name, dc)) @@ -62,6 +62,46 @@ bool LLDayCycleManager::getPreset(const std::string name, LLSD& day_cycle) return true; } +bool LLDayCycleManager::presetExists(const std::string name) const +{ + LLWLDayCycle dummy; + return getPreset(name, dummy); +} + +bool LLDayCycleManager::isSystemPreset(const std::string& name) const +{ + return gDirUtilp->fileExists(getSysDir() + LLURI::escape(name) + ".xml"); +} + +bool LLDayCycleManager::savePreset(const std::string& name, const LLSD& data) +{ + // Save given preset. + LLWLDayCycle day; + day.loadDayCycle(data, LLEnvKey::SCOPE_LOCAL); + day.save(getUserDir() + LLURI::escape(name) + ".xml"); + + // Add it to our map. + addPreset(name, data); + return true; +} + +bool LLDayCycleManager::deletePreset(const std::string& name) +{ + std::string path; + std::string filename = LLURI::escape(name) + ".xml"; + + // Try removing specified user preset. + path = getUserDir() + filename; + if (gDirUtilp->fileExists(path)) + { + gDirUtilp->deleteFilesInDir(getUserDir(), filename); + return true; + } + + // Invalid or system preset. + return false; +} + // virtual void LLDayCycleManager::initSingleton() { @@ -102,13 +142,25 @@ bool LLDayCycleManager::loadPreset(const std::string& path) } std::string name(gDirUtilp->getBaseFileName(LLURI::unescape(path), /*strip_exten = */ true)); - LLWLDayCycle day_cycle; - day_cycle.loadDayCycle(data, LLEnvKey::SCOPE_LOCAL); - mDayCycleMap[name] = day_cycle; + addPreset(name, data); return true; } +bool LLDayCycleManager::addPreset(const std::string& name, const LLSD& data) +{ + if (name.empty()) + { + llassert(name.empty()); + return false; + } + + LLWLDayCycle day; + day.loadDayCycle(data, LLEnvKey::SCOPE_LOCAL); + mDayCycleMap[name] = day; + return false; +} + // static std::string LLDayCycleManager::getSysDir() { diff --git a/indra/newview/lldaycyclemanager.h b/indra/newview/lldaycyclemanager.h index e49e4986fe..b33c0a0d39 100644 --- a/indra/newview/lldaycyclemanager.h +++ b/indra/newview/lldaycyclemanager.h @@ -46,8 +46,12 @@ public: typedef std::map dc_map_t; const dc_map_t& getPresets(); - bool getPreset(const std::string name, LLWLDayCycle& day_cycle); - bool getPreset(const std::string name, LLSD& day_cycle); + bool getPreset(const std::string name, LLWLDayCycle& day_cycle) const; + bool getPreset(const std::string name, LLSD& day_cycle) const; + bool presetExists(const std::string name) const; + bool isSystemPreset(const std::string& name) const; + bool savePreset(const std::string& name, const LLSD& data); + bool deletePreset(const std::string& name); private: friend class LLSingleton; @@ -56,6 +60,7 @@ private: void loadAllPresets(); void loadPresets(const std::string& dir); bool loadPreset(const std::string& path); + bool addPreset(const std::string& name, const LLSD& data); static std::string getSysDir(); static std::string getUserDir(); diff --git a/indra/newview/llenvmanager.cpp b/indra/newview/llenvmanager.cpp index d5d03ff1e5..48d84fcffc 100644 --- a/indra/newview/llenvmanager.cpp +++ b/indra/newview/llenvmanager.cpp @@ -746,6 +746,7 @@ void LLEnvManagerNew::setUseRegionSettings(bool val) void LLEnvManagerNew::setUseWaterPreset(const std::string& name) { + // *TODO: make sure the preset exists. if (name.empty()) { llwarns << "Empty water preset name passed" << llendl; @@ -759,6 +760,7 @@ void LLEnvManagerNew::setUseWaterPreset(const std::string& name) void LLEnvManagerNew::setUseSkyPreset(const std::string& name) { + // *TODO: make sure the preset exists. if (name.empty()) { llwarns << "Empty sky preset name passed" << llendl; @@ -772,9 +774,9 @@ void LLEnvManagerNew::setUseSkyPreset(const std::string& name) void LLEnvManagerNew::setUseDayCycle(const std::string& name) { - if (name.empty()) + if (!LLDayCycleManager::instance().presetExists(name)) { - llwarns << "Empty day cycle name passed" << llendl; + llwarns << "Invalid day cycle name passed" << llendl; return; } @@ -939,6 +941,8 @@ void LLEnvManagerNew::onRegionChange(bool interpolate) return; } + // *TODO: clear environment settings of the previous region? + // Request environment settings of the new region. LL_DEBUGS("Windlight") << "New viewer region: " << region_uuid << LL_ENDL; mCurRegionUUID = region_uuid; diff --git a/indra/newview/llfloaterdeleteenvpreset.cpp b/indra/newview/llfloaterdeleteenvpreset.cpp index e014eedeb1..d2f0f6e520 100644 --- a/indra/newview/llfloaterdeleteenvpreset.cpp +++ b/indra/newview/llfloaterdeleteenvpreset.cpp @@ -31,11 +31,23 @@ // libs #include "llbutton.h" #include "llcombobox.h" +#include "llnotificationsutil.h" // newview #include "lldaycyclemanager.h" #include "llwaterparammanager.h" +static bool confirmation_callback(const LLSD& notification, const LLSD& response, boost::function cb) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) + { + cb(); + } + return false; + +} + LLFloaterDeleteEnvPreset::LLFloaterDeleteEnvPreset(const LLSD &key) : LLFloater(key) , mPresetCombo(NULL) @@ -50,9 +62,6 @@ BOOL LLFloaterDeleteEnvPreset::postBuild() getChild("delete")->setCommitCallback(boost::bind(&LLFloaterDeleteEnvPreset::onBtnDelete, this)); getChild("cancel")->setCommitCallback(boost::bind(&LLFloaterDeleteEnvPreset::onBtnCancel, this)); - // Deletion is not implemented yet, so disable the button for now. - getChild("delete")->setEnabled(FALSE); - return TRUE; } @@ -70,14 +79,15 @@ void LLFloaterDeleteEnvPreset::onOpen(const LLSD& key) getChild("label")->setValue(combo_label); // Populate the combobox. - mPresetCombo->removeall(); if (param == "water") { populateWaterPresetsList(); + getChild("delete")->setEnabled(FALSE); // not implemented yet } else if (param == "sky") { populateSkyPresetsList(); + getChild("delete")->setEnabled(FALSE); // not implemented yet } else if (param == "day_cycle") { @@ -91,7 +101,42 @@ void LLFloaterDeleteEnvPreset::onOpen(const LLSD& key) void LLFloaterDeleteEnvPreset::onBtnDelete() { - closeFloater(); + std::string param = mKey.asString(); + boost::function confirm_cb; + + if (param == "water") + { + llwarns << "Deleting water presets not implemented" << llendl; + return; + } + else if (param == "sky") + { + llwarns << "Deleting sky presets not implemented" << llendl; + return; + } + else if (param == "day_cycle") + { + LLDayCycleManager& day_mgr = LLDayCycleManager::instance(); + std::string preset_name = mPresetCombo->getValue().asString(); + + // Don't allow deleting system presets. + if (day_mgr.isSystemPreset(preset_name)) + { + LLNotificationsUtil::add("WLNoEditDefault"); + return; + } + + confirm_cb = boost::bind(&LLFloaterDeleteEnvPreset::onDeleteDayCycleConfirmation, this); + } + else + { + llwarns << "Unrecognized key" << llendl; + } + + LLSD args; + args["MESSAGE"] = getString("msg_confirm_deletion"); + LLNotificationsUtil::add("GenericAlertYesCancel", args, LLSD(), + boost::bind(&confirmation_callback, _1, _2, confirm_cb)); } void LLFloaterDeleteEnvPreset::onBtnCancel() @@ -101,6 +146,8 @@ void LLFloaterDeleteEnvPreset::onBtnCancel() void LLFloaterDeleteEnvPreset::populateWaterPresetsList() { + mPresetCombo->removeall(); + // *TODO: Reload the list when user preferences change. LLWaterParamManager& water_mgr = LLWaterParamManager::instance(); LL_DEBUGS("Windlight") << "Current water preset: " << water_mgr.mCurParams.mName << LL_ENDL; @@ -116,6 +163,9 @@ void LLFloaterDeleteEnvPreset::populateWaterPresetsList() void LLFloaterDeleteEnvPreset::populateSkyPresetsList() { + mPresetCombo->removeall(); + + // *TODO: Reload the list when user preferences change. LLWLParamManager& sky_mgr = LLWLParamManager::instance(); LL_DEBUGS("Windlight") << "Current sky preset: " << sky_mgr.mCurParams.mName << LL_ENDL; @@ -130,6 +180,8 @@ void LLFloaterDeleteEnvPreset::populateSkyPresetsList() void LLFloaterDeleteEnvPreset::populateDayCyclesList() { + mPresetCombo->removeall(); + // *TODO: Disable current day cycle. const LLDayCycleManager::dc_map_t& map = LLDayCycleManager::instance().getPresets(); for (LLDayCycleManager::dc_map_t::const_iterator it = map.begin(); it != map.end(); ++it) @@ -137,3 +189,9 @@ void LLFloaterDeleteEnvPreset::populateDayCyclesList() mPresetCombo->add(it->first); } } + +void LLFloaterDeleteEnvPreset::onDeleteDayCycleConfirmation() +{ + LLDayCycleManager::instance().deletePreset(mPresetCombo->getValue().asString()); + populateDayCyclesList(); +} diff --git a/indra/newview/llfloaterdeleteenvpreset.h b/indra/newview/llfloaterdeleteenvpreset.h index 08fd11a16d..76b23feae8 100644 --- a/indra/newview/llfloaterdeleteenvpreset.h +++ b/indra/newview/llfloaterdeleteenvpreset.h @@ -49,6 +49,8 @@ private: void populateSkyPresetsList(); void populateDayCyclesList(); + void onDeleteDayCycleConfirmation(); + LLComboBox* mPresetCombo; }; diff --git a/indra/newview/llfloatereditdaycycle.cpp b/indra/newview/llfloatereditdaycycle.cpp index 6275b48203..91809e0707 100644 --- a/indra/newview/llfloatereditdaycycle.cpp +++ b/indra/newview/llfloatereditdaycycle.cpp @@ -30,22 +30,53 @@ // libs #include "llbutton.h" +#include "llcheckboxctrl.h" +#include "llcombobox.h" +#include "llmultisliderctrl.h" +#include "llnotifications.h" +#include "llnotificationsutil.h" +#include "llspinctrl.h" +#include "lltimectrl.h" // newview +#include "llagent.h" +#include "lldaycyclemanager.h" +#include "llenvmanager.h" +#include "llviewerregion.h" +#include "llwlparammanager.h" + +const F32 LLFloaterEditDayCycle::sHoursPerDay = 24.0f; LLFloaterEditDayCycle::LLFloaterEditDayCycle(const LLSD &key) : LLFloater(key) +, mDayCycleNameEditor(NULL) +, mDayCyclesCombo(NULL) +, mTimeSlider(NULL) +, mKeysSlider(NULL) +, mSkyPresetsCombo(NULL) +, mTimeCtrl(NULL) +, mMakeDefaultCheckBox(NULL) +, mSaveButton(NULL) { } // virtual BOOL LLFloaterEditDayCycle::postBuild() { - getChild("cancel")->setCommitCallback(boost::bind(&LLFloaterEditDayCycle::onBtnCancel, this)); + mDayCycleNameEditor = getChild("day_cycle_name"); + mDayCyclesCombo = getChild("day_cycle_combo"); + + mTimeSlider = getChild("WLTimeSlider"); + mKeysSlider = getChild("WLDayCycleKeys"); + mSkyPresetsCombo = getChild("WLSkyPresets"); + mTimeCtrl = getChild("time"); + mSaveButton = getChild("save"); + mMakeDefaultCheckBox = getChild("make_default_cb"); - // Disable some non-functional controls. - getChildView("day_cycle_combo")->setEnabled(FALSE); - getChildView("save")->setEnabled(FALSE); + initCallbacks(); + + // add the time slider + mTimeSlider->addSlider(); return TRUE; } @@ -53,6 +84,7 @@ BOOL LLFloaterEditDayCycle::postBuild() // virtual void LLFloaterEditDayCycle::onOpen(const LLSD& key) { + bool new_day = isNewDay(); std::string param = key.asString(); std::string floater_title = getString(std::string("title_") + param); std::string hint = getString(std::string("hint_" + param)); @@ -64,10 +96,567 @@ void LLFloaterEditDayCycle::onOpen(const LLSD& key) getChild("hint")->setValue(hint); // Hide the hint to the right of the combo if we're invoked to create a new preset. - getChildView("note")->setVisible(param == "edit"); + getChildView("note")->setVisible(!new_day); + + // Switch between the day cycle presets combobox and day cycle name input field. + mDayCyclesCombo->setVisible(!new_day); + mDayCycleNameEditor->setVisible(new_day); + + // TODO: Make sure only one instance of the floater exists? + + reset(); +} + +// virtual +void LLFloaterEditDayCycle::onClose(bool app_quitting) +{ + if (!app_quitting) // there's no point to change environment if we're quitting + { + LLEnvManagerNew::instance().usePrefs(); // revert changes made to current day cycle + } +} + +// virtual +void LLFloaterEditDayCycle::draw() +{ + syncTimeSlider(); + LLFloater::draw(); +} + +void LLFloaterEditDayCycle::initCallbacks(void) +{ + mDayCycleNameEditor->setKeystrokeCallback(boost::bind(&LLFloaterEditDayCycle::onDayCycleNameEdited, this), NULL); + mDayCyclesCombo->setCommitCallback(boost::bind(&LLFloaterEditDayCycle::onDayCycleSelected, this)); + mTimeSlider->setCommitCallback(boost::bind(&LLFloaterEditDayCycle::onTimeSliderMoved, this)); + mKeysSlider->setCommitCallback(boost::bind(&LLFloaterEditDayCycle::onKeyTimeMoved, this)); + mTimeCtrl->setCommitCallback(boost::bind(&LLFloaterEditDayCycle::onKeyTimeChanged, this)); + mSkyPresetsCombo->setCommitCallback(boost::bind(&LLFloaterEditDayCycle::onKeyPresetChanged, this)); + + getChild("WLAddKey")->setClickedCallback(boost::bind(&LLFloaterEditDayCycle::onAddKey, this)); + getChild("WLDeleteKey")->setClickedCallback(boost::bind(&LLFloaterEditDayCycle::onDeleteKey, this)); + + mSaveButton->setCommitCallback(boost::bind(&LLFloaterEditDayCycle::onBtnSave, this)); + mSaveButton->setRightMouseDownCallback(boost::bind(&LLFloaterEditDayCycle::dumpTrack, this)); + getChild("cancel")->setCommitCallback(boost::bind(&LLFloaterEditDayCycle::onBtnCancel, this)); +} + +void LLFloaterEditDayCycle::syncTimeSlider() +{ + // set time + mTimeSlider->setCurSliderValue((F32)LLWLParamManager::getInstance()->mAnimator.getDayTime() * sHoursPerDay); +} + +void LLFloaterEditDayCycle::loadTrack() +{ + // clear the slider + mKeysSlider->clear(); + mSliderToKey.clear(); + + // add sliders + + lldebugs << "Adding " << LLWLParamManager::getInstance()->mDay.mTimeMap.size() << " keys to slider" << llendl; + + LLWLDayCycle& cur_dayp = LLWLParamManager::instance().mDay; + for (std::map::iterator it = cur_dayp.mTimeMap.begin(); it != cur_dayp.mTimeMap.end(); ++it) + { + addSliderKey(it->first * sHoursPerDay, it->second); + } + + // set drop-down menu to match preset of currently-selected keyframe (one is automatically selected initially) + const std::string& cur_sldr = mKeysSlider->getCurSlider(); + if (strlen(cur_sldr.c_str()) > 0) // only do this if there is a curSldr, otherwise we put an invalid entry into the map + { + mSkyPresetsCombo->selectByValue(mSliderToKey[cur_sldr].keyframe.toStringVal()); + } + + syncTimeSlider(); +} + +void LLFloaterEditDayCycle::applyTrack() +{ + lldebugs << "Applying track (" << mSliderToKey.size() << ")" << llendl; + + // if no keys, do nothing + if (mSliderToKey.size() == 0) + { + lldebugs << "No keys, not syncing" << llendl; + return; + } + + llassert_always(mSliderToKey.size() == mKeysSlider->getValue().size()); + + // create a new animation track + LLWLParamManager::getInstance()->mDay.clearKeyframes(); + + // add the keys one by one + for (std::map::iterator it = mSliderToKey.begin(); + it != mSliderToKey.end(); ++it) + { + LLWLParamManager::getInstance()->mDay.addKeyframe(it->second.time / sHoursPerDay, + it->second.keyframe); + } + + // set the param manager's track to the new one + LL_DEBUGS("Windlight") << "Time slider val: " << mTimeSlider->getCurSliderValue() / sHoursPerDay << " (" << mTimeSlider->getCurSliderValue() << ")" << LL_ENDL; + LLWLParamManager::getInstance()->resetAnimator( + mTimeSlider->getCurSliderValue() / sHoursPerDay, false); + + LLWLParamManager::getInstance()->mAnimator.update( + LLWLParamManager::getInstance()->mCurParams); +} + +void LLFloaterEditDayCycle::refreshSkyPresetsList() +{ + LLWLParamManager& sky_mgr = LLWLParamManager::instance(); + for (std::map::iterator it = sky_mgr.mParamList.begin(); + it != sky_mgr.mParamList.end(); ++it) + { + const LLWLParamKey& key = it->first; + + std::string item_title = key.name; + if (key.scope == LLEnvKey::SCOPE_REGION) + { + item_title += " (" + getRegionName() + ")"; + } + + mSkyPresetsCombo->add(item_title, LLSD(key.toStringVal())); + } + + // set defaults on combo boxes + mSkyPresetsCombo->selectFirstItem(); +} + +void LLFloaterEditDayCycle::refreshDayCyclesList() +{ + llassert(isNewDay() == false); + + mDayCyclesCombo->removeall(); + + const LLSD& region_day = LLEnvManagerNew::instance().getRegionSettings().getWLDayCycle(); + if (region_day.size() > 0) + { + LLWLParamKey key(getRegionName(), LLEnvKey::SCOPE_REGION); + mDayCyclesCombo->add(key.name, key.toLLSD()); + mDayCyclesCombo->addSeparator(); + } + + const LLDayCycleManager::dc_map_t& map = LLDayCycleManager::instance().getPresets(); + for (LLDayCycleManager::dc_map_t::const_iterator it = map.begin(); it != map.end(); ++it) + { + LLWLParamKey key(it->first, LLEnvKey::SCOPE_LOCAL); + mDayCyclesCombo->add(key.name, key.toLLSD()); + } + + mDayCyclesCombo->setLabel(getString("combo_label")); +} + +void LLFloaterEditDayCycle::onTimeSliderMoved() +{ + /// get the slider value + F32 val = mTimeSlider->getCurSliderValue() / sHoursPerDay; + LL_DEBUGS("Windlight") << "Time slider val: " << val << " (" << mTimeSlider->getCurSliderValue() << ")" << LL_ENDL; + + // set the value, turn off animation + LLWLParamManager::getInstance()->mAnimator.setDayTime((F64)val); + LLWLParamManager::getInstance()->mAnimator.deactivate(); + + // then call update once + LLWLParamManager::getInstance()->mAnimator.update( + LLWLParamManager::getInstance()->mCurParams); +} + +void LLFloaterEditDayCycle::onKeyTimeMoved() +{ + if (mKeysSlider->getValue().size() == 0) + { + return; + } + + // make sure we have a slider + const std::string& cur_sldr = mKeysSlider->getCurSlider(); + if (cur_sldr == "") + { + return; + } + + F32 time24 = mKeysSlider->getCurSliderValue(); + + // check to see if a key exists + LLWLParamKey key = mSliderToKey[cur_sldr].keyframe; + lldebugs << "Setting key time: " << time24 << LL_ENDL; + mSliderToKey[cur_sldr].time = time24; + + // if it exists, turn on check box + mSkyPresetsCombo->selectByValue(key.toStringVal()); + + mTimeCtrl->setTime24(time24); + + applyTrack(); +} + +void LLFloaterEditDayCycle::onKeyTimeChanged() +{ + // if no keys, skipped + if (mSliderToKey.size() == 0) + { + return; + } + + F32 time24 = mTimeCtrl->getTime24(); + + const std::string& cur_sldr = mKeysSlider->getCurSlider(); + mKeysSlider->setCurSliderValue(time24, TRUE); + F32 time = mKeysSlider->getCurSliderValue() / sHoursPerDay; + + // now set the key's time in the sliderToKey map + lldebugs << "Setting key time: " << time << LL_ENDL; + mSliderToKey[cur_sldr].time = time; + + applyTrack(); +} + +void LLFloaterEditDayCycle::onKeyPresetChanged() +{ + // do nothing if no sliders + if (mKeysSlider->getValue().size() == 0) + { + return; + } + + // change the map + + std::string stringVal = mSkyPresetsCombo->getSelectedValue().asString(); + LLWLParamKey new_key(stringVal); + const std::string& cur_sldr = mKeysSlider->getCurSlider(); + + // if null, don't use + if (cur_sldr == "") + { + return; + } + + mSliderToKey[cur_sldr].keyframe = new_key; + + // Apply changes to current day cycle. + applyTrack(); +} + +void LLFloaterEditDayCycle::onAddKey() +{ + llassert_always(mSliderToKey.size() == mKeysSlider->getValue().size()); + + S32 max_sliders; + LLEnvKey::EScope scope = LLEnvKey::SCOPE_LOCAL; // *TODO: editing region day cycle + switch (scope) + { + case LLEnvKey::SCOPE_LOCAL: + max_sliders = 20; // *HACK this should be LLWLPacketScrubber::MAX_LOCAL_KEY_FRAMES; + break; + case LLEnvKey::SCOPE_REGION: + max_sliders = 12; // *HACK this should be LLWLPacketScrubber::MAX_REGION_KEY_FRAMES; + break; + default: + max_sliders = (S32) mKeysSlider->getMaxValue(); + break; + } + + if ((S32)mSliderToKey.size() >= max_sliders) + { + LLSD args; + args["SCOPE"] = LLEnvManager::getScopeString(scope); + args["MAX"] = max_sliders; + LLNotificationsUtil::add("DayCycleTooManyKeyframes", args, LLSD(), LLNotificationFunctorRegistry::instance().DONOTHING); + return; + } + + // add the slider key + LLWLParamKey sky_params(mSkyPresetsCombo->getSelectedValue()); + F32 time = mTimeSlider->getCurSliderValue(); + addSliderKey(time, sky_params); + + // apply the change to current day cycles + applyTrack(); +} + +void LLFloaterEditDayCycle::addSliderKey(F32 time, LLWLParamKey keyframe) +{ + // make a slider + const std::string& sldr_name = mKeysSlider->addSlider(time); + if (sldr_name.empty()) + { + return; + } + + // set the key + SliderKey newKey(keyframe, mKeysSlider->getCurSliderValue()); + + llassert_always(sldr_name != LLStringUtil::null); + + // add to map + mSliderToKey.insert(std::pair(sldr_name, newKey)); + + llassert_always(mSliderToKey.size() == mKeysSlider->getValue().size()); +} + +LLWLParamKey LLFloaterEditDayCycle::getSelectedDayCycle() +{ + LLWLParamKey dc_key; + + if (mDayCycleNameEditor->getVisible()) + { + dc_key.name = mDayCycleNameEditor->getText(); + dc_key.scope = LLEnvKey::SCOPE_LOCAL; + } + else + { + LLSD combo_val = mDayCyclesCombo->getValue(); + + if (!combo_val.isArray()) // manually typed text + { + dc_key.name = combo_val.asString(); + dc_key.scope = LLEnvKey::SCOPE_LOCAL; + } + else + { + dc_key.fromLLSD(combo_val); + } + } + + return dc_key; +} + +void LLFloaterEditDayCycle::deletePreset(LLWLParamKey keyframe) +{ + // *TODO: This should be called when a sky preset gets deleted. + + /// delete any reference + std::map::iterator curr_preset, next_preset; + for (curr_preset = mSliderToKey.begin(); curr_preset != mSliderToKey.end(); curr_preset = next_preset) + { + next_preset = curr_preset; + ++next_preset; + if (curr_preset->second.keyframe == keyframe) + { + mKeysSlider->deleteSlider(curr_preset->first); + mSliderToKey.erase(curr_preset); + } + } +} + +bool LLFloaterEditDayCycle::isNewDay() const +{ + return mKey.asString() == "new"; +} + +void LLFloaterEditDayCycle::dumpTrack() +{ + LL_DEBUGS("Windlight") << "Dumping day cycle" << LL_ENDL; + + LLWLDayCycle& cur_dayp = LLWLParamManager::instance().mDay; + for (std::map::iterator it = cur_dayp.mTimeMap.begin(); it != cur_dayp.mTimeMap.end(); ++it) + { + F32 time = it->first * 24.0f; + S32 h = time; + S32 m = (time - h) * 60.0f; + LL_DEBUGS("Windlight") << llformat("(%.3f) %02d:%02d", time, h, m) << " => " << it->second.name << LL_ENDL; + } +} + +void LLFloaterEditDayCycle::enableEditing(bool enable) +{ + mSkyPresetsCombo->setEnabled(enable); + mTimeCtrl->setEnabled(enable); + getChild("day_cycle_slider_panel")->setCtrlsEnabled(enable); + mSaveButton->setEnabled(enable); + mMakeDefaultCheckBox->setEnabled(enable); +} + +void LLFloaterEditDayCycle::reset() +{ + // clear the slider + mKeysSlider->clear(); + mSliderToKey.clear(); + + refreshSkyPresetsList(); + + if (isNewDay()) + { + mDayCycleNameEditor->setValue(LLSD()); + F32 time = 0.5f * sHoursPerDay; + mSaveButton->setEnabled(FALSE); // will be enabled as soon as users enters a name + mTimeSlider->setCurSliderValue(time); + + addSliderKey(time, LLWLParamKey("Default", LLEnvKey::SCOPE_LOCAL)); + onKeyTimeMoved(); // update the time control and sky sky combo + + applyTrack(); + } + else + { + refreshDayCyclesList(); + + // Disable controls until a day cycle to edit is selected. + enableEditing(false); + } +} + +void LLFloaterEditDayCycle::onDeleteKey() +{ + if (mSliderToKey.size() == 0) + { + return; + } + else if (mSliderToKey.size() == 1) + { + LLNotifications::instance().add("EnvCannotDeleteLastDayCycleKey", LLSD(), LLSD()); + return; + } + + // delete from map + const std::string& sldr_name = mKeysSlider->getCurSlider(); + std::map::iterator mIt = mSliderToKey.find(sldr_name); + mSliderToKey.erase(mIt); + + mKeysSlider->deleteCurSlider(); + + if (mSliderToKey.size() == 0) + { + return; + } + + const std::string& name = mKeysSlider->getCurSlider(); + mSkyPresetsCombo->selectByValue(mSliderToKey[name].keyframe.toStringVal()); + F32 time24 = mSliderToKey[name].time; + + mTimeCtrl->setTime24(time24); + + applyTrack(); +} + +void LLFloaterEditDayCycle::onDayCycleNameEdited() +{ + // Disable saving a day cycle having empty name. + LLWLParamKey key = getSelectedDayCycle(); + mSaveButton->setEnabled(!key.name.empty()); +} + +void LLFloaterEditDayCycle::onDayCycleSelected() +{ + LLSD day_data; + LLWLParamKey dc_key = getSelectedDayCycle(); + + if (dc_key.scope == LLEnvKey::SCOPE_LOCAL) + { + if (!LLDayCycleManager::instance().getPreset(dc_key.name, day_data)) + { + llwarns << "No day cycle named " << dc_key.name << llendl; + return; + } + } + else + { + day_data = LLEnvManagerNew::instance().getRegionSettings().getWLDayCycle(); + if (day_data.size() == 0) + { + llwarns << "Empty region day cycle" << llendl; + llassert(day_data.size() > 0); + return; + } + } + + F32 slider_time = mTimeSlider->getCurSliderValue() / sHoursPerDay; + LL_DEBUGS("Windlight") << "Time slider val: " << slider_time << " (" << mTimeSlider->getCurSliderValue() << ")" << LL_ENDL; + LLWLParamManager::instance().applyDayCycleParams(day_data, dc_key.scope, slider_time); + loadTrack(); + + enableEditing(true); +} + +void LLFloaterEditDayCycle::onBtnSave() +{ + LLDayCycleManager& day_mgr = LLDayCycleManager::instance(); + LLWLParamKey selected_day = getSelectedDayCycle(); + + if (selected_day.scope == LLEnvKey::SCOPE_REGION) + { + llwarns << "Saving to a local day cycle" << llendl; + } + + std::string name = selected_day.name; + if (name.empty()) + { + // *TODO: show an alert + llwarns << "Empty day cycle name" << llendl; + return; + } + + // Don't allow overwriting system presets. + if (day_mgr.isSystemPreset(name)) + { + LLNotificationsUtil::add("WLNoEditDefault"); + return; + } + + // Save, ask for confirmation for overwriting an existing preset. + if (day_mgr.presetExists(name)) + { + LLNotificationsUtil::add("WLSavePresetAlert", LLSD(), LLSD(), boost::bind(&LLFloaterEditDayCycle::onSaveAnswer, this, _1, _2)); + } + else + { + // new preset, hence no confirmation needed + onSaveConfirmed(); + } } void LLFloaterEditDayCycle::onBtnCancel() { +#if 0 // temporary + onClose(false); + reset(); +#else + closeFloater(); +#endif +} + +bool LLFloaterEditDayCycle::onSaveAnswer(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + + // If they choose save, do it. Otherwise, don't do anything + if (option == 0) + { + onSaveConfirmed(); + } + + return false; +} + +void LLFloaterEditDayCycle::onSaveConfirmed() +{ + std::string name = getSelectedDayCycle().name; + + // Save preset. + LLSD data = LLWLParamManager::instance().mDay.asLLSD(); + LL_DEBUGS("Windlight") << "Saving day cycle " << name << ": " << data << LL_ENDL; + LLDayCycleManager::instance().savePreset(name, data); + + // Change preference if requested. + if (mMakeDefaultCheckBox->getValue()) + { + LL_DEBUGS("Windlight") << name << " is now the new preferred day cycle" << llendl; + LLEnvManagerNew::instance().setUseDayCycle(name); + } + +#if 0 // temporary + reset(); +#else closeFloater(); +#endif +} + +// static +std::string LLFloaterEditDayCycle::getRegionName() +{ + return gAgent.getRegion() ? gAgent.getRegion()->getName() : LLTrans::getString("Unknown"); } diff --git a/indra/newview/llfloatereditdaycycle.h b/indra/newview/llfloatereditdaycycle.h index d1caa05888..742608712b 100644 --- a/indra/newview/llfloatereditdaycycle.h +++ b/indra/newview/llfloatereditdaycycle.h @@ -29,6 +29,16 @@ #include "llfloater.h" +#include "llwlparammanager.h" // for LLWLParamKey + +class LLCheckBoxCtrl; +class LLComboBox; +class LLLineEditor; +class LLMultiSliderCtrl; +class LLTimeCtrl; + +/// Menu for all of windlight's functionality. +/// Menuing system for adjusting the atmospheric settings of the world. class LLFloaterEditDayCycle : public LLFloater { LOG_CLASS(LLFloaterEditDayCycle); @@ -38,8 +48,78 @@ public: /*virtual*/ BOOL postBuild(); /*virtual*/ void onOpen(const LLSD& key); + /*virtual*/ void onClose(bool app_quitting); + /*virtual*/ void draw(); + +private: + + /// sync the time slider with day cycle structure + void syncTimeSlider(); + + // makes sure key slider has what's in day cycle + void loadTrack(); + + /// makes sure day cycle data structure has what's in menu + void applyTrack(); + + /// refresh the sky presets combobox + void refreshSkyPresetsList(); + + /// refresh the day cycle combobox + void refreshDayCyclesList(); + + /// add a slider to the track + void addSliderKey(F32 time, LLWLParamKey keyframe); + void initCallbacks(); + LLWLParamKey getSelectedDayCycle(); + void deletePreset(LLWLParamKey keyframe); + bool isNewDay() const; + void dumpTrack(); + void enableEditing(bool enable); + void reset(); + + void onTimeSliderMoved(); /// time slider moved + void onKeyTimeMoved(); /// a key frame moved + void onKeyTimeChanged(); /// a key frame's time changed + void onKeyPresetChanged(); /// sky preset selected + void onAddKey(); /// new key added on slider + void onDeleteKey(); /// a key frame deleted + + void onDayCycleNameEdited(); + void onDayCycleSelected(); + void onBtnSave(); void onBtnCancel(); + + bool onSaveAnswer(const LLSD& notification, const LLSD& response); + void onSaveConfirmed(); + + static std::string getRegionName(); + + /// convenience class for holding keyframes mapped to sliders + struct SliderKey + { + public: + SliderKey(LLWLParamKey kf, F32 t) : keyframe(kf), time(t) {} + SliderKey() : keyframe(), time(0.f) {} // Don't use this default constructor + + LLWLParamKey keyframe; + F32 time; + }; + + static const F32 sHoursPerDay; + + LLLineEditor* mDayCycleNameEditor; + LLComboBox* mDayCyclesCombo; + LLMultiSliderCtrl* mTimeSlider; + LLMultiSliderCtrl* mKeysSlider; + LLComboBox* mSkyPresetsCombo; + LLTimeCtrl* mTimeCtrl; + LLCheckBoxCtrl* mMakeDefaultCheckBox; + LLButton* mSaveButton; + + // map of sliders to parameters + std::map mSliderToKey; }; #endif // LL_LLFLOATEREDITDAYCYCLE_H diff --git a/indra/newview/llfloaterwindlight.cpp b/indra/newview/llfloaterwindlight.cpp index fb69e6f594..16cb0f5e57 100644 --- a/indra/newview/llfloaterwindlight.cpp +++ b/indra/newview/llfloaterwindlight.cpp @@ -82,7 +82,13 @@ BOOL LLFloaterWindLight::postBuild() LLWLParamManager::getInstance()->mParamList.begin(); for(; mIt != LLWLParamManager::getInstance()->mParamList.end(); mIt++) { - comboBox->add(mIt->first.toString(), mIt->first.toLLSD()); + const LLWLParamKey& key = mIt->first; + std::string item_title = key.name; + if (key.scope == LLEnvKey::SCOPE_REGION) + { + item_title += std::string(" (") + LLTrans::getString("Region") + std::string(")"); + } + comboBox->add(item_title, key.toLLSD()); } // entry for when we're in estate time diff --git a/indra/newview/llwldaycycle.cpp b/indra/newview/llwldaycycle.cpp index 6ac63fb99e..91d2173d6d 100644 --- a/indra/newview/llwldaycycle.cpp +++ b/indra/newview/llwldaycycle.cpp @@ -127,12 +127,17 @@ LLSD LLWLDayCycle::loadDayCycleFromPath(const std::string& file_path) void LLWLDayCycle::saveDayCycle(const std::string & fileName) { - LLSD day_data = asLLSD(); - std::string pathName(gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "windlight/days", fileName)); //llinfos << "Saving WindLight settings to " << pathName << llendl; - llofstream day_cycle_xml(pathName); + save(pathName); +} + +void LLWLDayCycle::save(const std::string& file_path) +{ + LLSD day_data = asLLSD(); + + llofstream day_cycle_xml(file_path); LLPointer formatter = new LLSDXMLFormatter(); formatter->format(day_data, day_cycle_xml, LLSDFormatter::OPTIONS_PRETTY); day_cycle_xml.close(); diff --git a/indra/newview/llwldaycycle.h b/indra/newview/llwldaycycle.h index 56bd66f114..e5bc82bb6c 100644 --- a/indra/newview/llwldaycycle.h +++ b/indra/newview/llwldaycycle.h @@ -62,9 +62,12 @@ public: /// load a day cycle void loadDayCycleFromFile(const std::string & fileName); - /// load a day cycle + /// save a day cycle void saveDayCycle(const std::string & fileName); + /// save a day cycle + void save(const std::string& file_path); + /// load the LLSD data from a file (returns the undefined LLSD if not found) static LLSD loadCycleDataFromFile(const std::string & fileName); diff --git a/indra/newview/llwlparammanager.h b/indra/newview/llwlparammanager.h index 7903661a7e..30fc8cd68f 100644 --- a/indra/newview/llwlparammanager.h +++ b/indra/newview/llwlparammanager.h @@ -139,7 +139,7 @@ public: } inline LLWLParamKey() // NOT really valid, just so std::maps can return a default of some sort - : name(NULL), scope(SCOPE_LOCAL) + : name(""), scope(SCOPE_LOCAL) { } @@ -168,6 +168,12 @@ public: return llsd; } + inline void fromLLSD(const LLSD& llsd) + { + name = llsd[NAME_IDX].asString(); + scope = EScope(llsd[SCOPE_IDX].asInteger()); + } + inline bool operator <(const LLWLParamKey other) const { if (name < other.name) diff --git a/indra/newview/skins/default/xui/en/floater_delete_env_preset.xml b/indra/newview/skins/default/xui/en/floater_delete_env_preset.xml index 1539c6448e..82a541d40d 100644 --- a/indra/newview/skins/default/xui/en/floater_delete_env_preset.xml +++ b/indra/newview/skins/default/xui/en/floater_delete_env_preset.xml @@ -16,6 +16,8 @@ Preset: Preset: Day cycle: + + Are you sure you want to delete the selected preset? Edit Day Cycle Name your day cycle, adjust the controls to create it, and click "Save". To edit your day cycle, adjust the controls below and click "Save". + -Select a preset- - - + width="200" /> + Time: - + width="75"/>