summaryrefslogtreecommitdiff
path: root/indra/newview
diff options
context:
space:
mode:
authorMerov Linden <merov@lindenlab.com>2012-09-12 23:21:42 -0700
committerMerov Linden <merov@lindenlab.com>2012-09-12 23:21:42 -0700
commitaa191cb98a80cd637b623f1dfb765374bf98dfa6 (patch)
tree689e5282dd9fddfa5acce80ce0da1111ba467ecb /indra/newview
parent5cad4b5768cccc8301da5e15dd09a0a4e68d4112 (diff)
parent51725b3898df96aa5819d86d1e8e7c71b47304f2 (diff)
Pull merge from richard/viewer-chui
Diffstat (limited to 'indra/newview')
-rw-r--r--indra/newview/llconversationlog.cpp141
-rw-r--r--indra/newview/llconversationlog.h44
-rw-r--r--indra/newview/llconversationloglist.cpp57
-rw-r--r--indra/newview/llconversationloglist.h9
-rw-r--r--indra/newview/llconversationloglistitem.cpp10
-rw-r--r--indra/newview/llconversationloglistitem.h8
-rw-r--r--indra/newview/llfloaterconversationlog.cpp12
-rw-r--r--indra/newview/llfloaterconversationlog.h5
-rw-r--r--indra/newview/llfloaterconversationpreview.cpp2
-rw-r--r--indra/newview/skins/default/xui/en/floater_conversation_preview.xml29
10 files changed, 190 insertions, 127 deletions
diff --git a/indra/newview/llconversationlog.cpp b/indra/newview/llconversationlog.cpp
index cc02e18698..239a89015f 100644
--- a/indra/newview/llconversationlog.cpp
+++ b/indra/newview/llconversationlog.cpp
@@ -37,7 +37,7 @@ struct Conversation_params
Conversation_params(time_t time)
: mTime(time),
mTimestamp(LLConversation::createTimestamp(time)),
- mIsConversationPast(true)
+ mIsVoice(false)
{}
time_t mTime;
@@ -48,7 +48,6 @@ struct Conversation_params
LLUUID mSessionID;
LLUUID mParticipantID;
bool mIsVoice;
- bool mIsConversationPast;
bool mHasOfflineIMs;
};
@@ -65,7 +64,6 @@ LLConversation::LLConversation(const Conversation_params& params)
mSessionID(params.mSessionID),
mParticipantID(params.mParticipantID),
mIsVoice(params.mIsVoice),
- mIsConversationPast(params.mIsConversationPast),
mHasOfflineIMs(params.mHasOfflineIMs)
{
setListenIMFloaterOpened();
@@ -80,7 +78,6 @@ LLConversation::LLConversation(const LLIMModel::LLIMSession& session)
mSessionID(session.mSessionID),
mParticipantID(session.mOtherParticipantID),
mIsVoice(session.mStartedAsIMCall),
- mIsConversationPast(false),
mHasOfflineIMs(session.mHasOfflineMessage)
{
setListenIMFloaterOpened();
@@ -96,7 +93,6 @@ LLConversation::LLConversation(const LLConversation& conversation)
mSessionID = conversation.getSessionID();
mParticipantID = conversation.getParticipantID();
mIsVoice = conversation.isVoice();
- mIsConversationPast = conversation.isConversationPast();
mHasOfflineIMs = conversation.hasOfflineMessages();
setListenIMFloaterOpened();
@@ -107,12 +103,10 @@ LLConversation::~LLConversation()
mIMFloaterShowedConnection.disconnect();
}
-void LLConversation::setIsVoice(bool is_voice)
+void LLConversation::updateTimestamp()
{
- if (mIsConversationPast)
- return;
-
- mIsVoice = is_voice;
+ mTime = time_corrected();
+ mTimestamp = createTimestamp(mTime);
}
void LLConversation::onIMFloaterShown(const LLUUID& session_id)
@@ -154,14 +148,18 @@ void LLConversation::setListenIMFloaterOpened()
LLIMFloater* floater = LLIMFloater::findInstance(mSessionID);
bool has_offline_ims = !mIsVoice && mHasOfflineIMs;
- bool ims_are_read = LLIMFloater::isVisible(floater) && floater->hasFocus();
+ bool offline_ims_visible = LLIMFloater::isVisible(floater) && floater->hasFocus();
// we don't need to listen for im floater with this conversation is opened
// if floater is already opened or this conversation doesn't have unread offline messages
- if (has_offline_ims && !ims_are_read)
+ if (has_offline_ims && !offline_ims_visible)
{
mIMFloaterShowedConnection = LLIMFloater::setIMFloaterShowedCallback(boost::bind(&LLConversation::onIMFloaterShown, this, _1));
}
+ else
+ {
+ mHasOfflineIMs = false;
+ }
}
/************************************************************************/
@@ -205,6 +203,7 @@ LLConversationLog::LLConversationLog()
if (ctrl->getValue().asBoolean())
{
LLIMMgr::instance().addSessionObserver(this);
+ newMessageSignalConnection = LLIMModel::instance().addNewMsgCallback(boost::bind(&LLConversationLog::onNewMessageReceived, this, _1));
}
}
@@ -218,17 +217,80 @@ void LLConversationLog::observeIMSession()
if (gSavedPerAccountSettings.getBOOL("LogInstantMessages"))
{
LLIMMgr::instance().addSessionObserver(this);
+ LLIMModel::instance().addNewMsgCallback(boost::bind(&LLConversationLog::onNewMessageReceived, this, _1));
}
else
{
LLIMMgr::instance().removeSessionObserver(this);
+ newMessageSignalConnection.disconnect();
}
}
-void LLConversationLog::logConversation(const LLConversation& conversation)
+void LLConversationLog::logConversation(const LLUUID& session_id)
{
- mConversations.push_back(conversation);
- notifyObservers();
+ LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession(session_id);
+ LLConversation* conversation = findConversation(session_id);
+
+ if (session && conversation)
+ {
+ updateConversationTimestamp(conversation);
+ }
+ else if (session && !conversation)
+ {
+ createConversation(session_id);
+ }
+}
+
+void LLConversationLog::createConversation(const LLUUID& session_id)
+{
+ LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession(session_id);
+
+ if (session)
+ {
+ LLConversation conversation(*session);
+ mConversations.push_back(conversation);
+
+ if (LLIMModel::LLIMSession::P2P_SESSION == session->mSessionType)
+ {
+ LLAvatarNameCache::get(session->mOtherParticipantID, boost::bind(&LLConversationLog::onAvatarNameCache, this, _1, _2, session));
+ }
+
+ notifyObservers();
+ }
+}
+
+void LLConversationLog::updateConversationName(const LLUUID& session_id, const std::string& name)
+{
+ LLConversation* conversation = findConversation(session_id);
+
+ if (conversation)
+ {
+ conversation->setConverstionName(name);
+ notifyPrticularConversationObservers(session_id, LLConversationLogObserver::CHANGED_NAME);
+ }
+}
+
+void LLConversationLog::updateConversationTimestamp(LLConversation* conversation)
+{
+ if (conversation)
+ {
+ conversation->updateTimestamp();
+ notifyPrticularConversationObservers(conversation->getSessionID(), LLConversationLogObserver::CHANGED_TIME);
+ }
+}
+
+LLConversation* LLConversationLog::findConversation(const LLUUID& session_id)
+{
+ conversations_vec_t::iterator conv_it = mConversations.begin();
+ for(; conv_it != mConversations.end(); ++conv_it)
+ {
+ if (conv_it->getSessionID() == session_id)
+ {
+ return &*conv_it;
+ }
+ }
+
+ return NULL;
}
void LLConversationLog::removeConversation(const LLConversation& conversation)
@@ -271,34 +333,7 @@ void LLConversationLog::removeObserver(LLConversationLogObserver* observer)
void LLConversationLog::sessionAdded(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id)
{
- LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession(session_id);
- if (session)
- {
- if (LLIMModel::LLIMSession::P2P_SESSION == session->mSessionType)
- {
- LLAvatarNameCache::get(session->mOtherParticipantID, boost::bind(&LLConversationLog::onAvatarNameCache, this, _1, _2, session));
- }
- else
- {
- LLConversation conversation(*session);
- LLConversationLog::instance().logConversation(conversation);
- session->mVoiceChannel->setStateChangedCallback(boost::bind(&LLConversationLog::onVoiceChannelConnected, this, _5, _2));
- }
- }
-}
-
-void LLConversationLog::sessionRemoved(const LLUUID& session_id)
-{
- conversations_vec_t::reverse_iterator rev_iter = mConversations.rbegin();
-
- for (; rev_iter != mConversations.rend(); ++rev_iter)
- {
- if (rev_iter->getSessionID() == session_id && !rev_iter->isConversationPast())
- {
- rev_iter->setIsPast(true);
- return; // return here because only one session with session_id may be active
- }
- }
+ logConversation(session_id);
}
void LLConversationLog::cache()
@@ -442,25 +477,13 @@ void LLConversationLog::notifyPrticularConversationObservers(const LLUUID& sessi
}
}
-void LLConversationLog::onVoiceChannelConnected(const LLUUID& session_id, const LLVoiceChannel::EState& state)
+void LLConversationLog::onNewMessageReceived(const LLSD& data)
{
- conversations_vec_t::reverse_iterator rev_iter = mConversations.rbegin();
-
- for (; rev_iter != mConversations.rend(); ++rev_iter)
- {
- if (rev_iter->getSessionID() == session_id && !rev_iter->isConversationPast() && LLVoiceChannel::STATE_CALL_STARTED == state)
- {
- rev_iter->setIsVoice(true);
- notifyPrticularConversationObservers(session_id, LLConversationLogObserver::VOICE_STATE);
- return; // return here because only one session with session_id may be active
- }
- }
+ const LLUUID session_id = data["session_id"].asUUID();
+ logConversation(session_id);
}
void LLConversationLog::onAvatarNameCache(const LLUUID& participant_id, const LLAvatarName& av_name, LLIMModel::LLIMSession* session)
{
- LLConversation conversation(*session);
- conversation.setConverstionName(av_name.getCompleteName());
- LLConversationLog::instance().logConversation(conversation);
- session->mVoiceChannel->setStateChangedCallback(boost::bind(&LLConversationLog::onVoiceChannelConnected, this, _5, _2));
+ updateConversationName(session->mSessionID, av_name.getCompleteName());
}
diff --git a/indra/newview/llconversationlog.h b/indra/newview/llconversationlog.h
index 16be37d67a..ffd27f7e20 100644
--- a/indra/newview/llconversationlog.h
+++ b/indra/newview/llconversationlog.h
@@ -57,16 +57,19 @@ public:
const std::string& getTimestamp() const { return mTimestamp; }
const time_t& getTime() const { return mTime; }
bool isVoice() const { return mIsVoice; }
- bool isConversationPast() const { return mIsConversationPast; }
bool hasOfflineMessages() const { return mHasOfflineIMs; }
void setIsVoice(bool is_voice);
- void setIsPast (bool is_past) { mIsConversationPast = is_past; }
void setConverstionName(std::string conv_name) { mConversationName = conv_name; }
bool isOlderThan(U32 days) const;
/*
+ * updates last interaction time
+ */
+ void updateTimestamp();
+
+ /*
* Resets flag of unread offline message to false when im floater with this conversation is opened.
*/
void onIMFloaterShown(const LLUUID& session_id);
@@ -86,7 +89,7 @@ private:
boost::signals2::connection mIMFloaterShowedConnection;
- time_t mTime; // start time of conversation
+ time_t mTime; // last interaction time
SessionType mConversationType;
std::string mConversationName;
std::string mHistoryFileName;
@@ -94,8 +97,7 @@ private:
LLUUID mParticipantID;
bool mIsVoice;
bool mHasOfflineIMs;
- bool mIsConversationPast; // once session is finished conversation became past forever
- std::string mTimestamp; // conversation start time in form of: mm/dd/yyyy hh:mm
+ std::string mTimestamp; // last interaction time in form of: mm/dd/yyyy hh:mm
};
/**
@@ -112,32 +114,26 @@ class LLConversationLog : public LLSingleton<LLConversationLog>, LLIMSessionObse
friend class LLSingleton<LLConversationLog>;
public:
- /**
- * adds conversation to the conversation list and notifies observers
- */
- void logConversation(const LLConversation& conversation);
void removeConversation(const LLConversation& conversation);
/**
* Returns first conversation with matched session_id
*/
- const LLConversation* getConversation(const LLUUID& session_id);
+ const LLConversation* getConversation(const LLUUID& session_id);
+ const std::vector<LLConversation>& getConversations() { return mConversations; }
void addObserver(LLConversationLogObserver* observer);
void removeObserver(LLConversationLogObserver* observer);
- const std::vector<LLConversation>& getConversations() { return mConversations; }
-
// LLIMSessionObserver triggers
virtual void sessionAdded(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id);
- virtual void sessionRemoved(const LLUUID& session_id);
+ virtual void sessionRemoved(const LLUUID& session_id){} // Stub
virtual void sessionVoiceOrIMStarted(const LLUUID& session_id){}; // Stub
virtual void sessionIDUpdated(const LLUUID& old_session_id, const LLUUID& new_session_id){}; // Stub
void notifyObservers();
- void notifyPrticularConversationObservers(const LLUUID& session_id, U32 mask);
- void onVoiceChannelConnected(const LLUUID& session_id, const LLVoiceChannel::EState& state);
+ void onNewMessageReceived(const LLSD& data);
/**
* public method which is called on viewer exit to save conversation log
@@ -148,6 +144,13 @@ private:
LLConversationLog();
+ /**
+ * adds conversation to the conversation list and notifies observers
+ */
+ void logConversation(const LLUUID& session_id);
+
+ void notifyPrticularConversationObservers(const LLUUID& session_id, U32 mask);
+
void observeIMSession();
/**
@@ -161,11 +164,19 @@ private:
void onAvatarNameCache(const LLUUID& participant_id, const LLAvatarName& av_name, LLIMModel::LLIMSession* session);
+ void createConversation(const LLUUID& session_id);
+ void updateConversationTimestamp(LLConversation* conversation);
+ void updateConversationName(const LLUUID& session_id, const std::string& name);
+
+ LLConversation* findConversation(const LLUUID& session_id);
+
typedef std::vector<LLConversation> conversations_vec_t;
std::vector<LLConversation> mConversations;
std::set<LLConversationLogObserver*> mObservers;
LLFriendObserver* mFriendObserver; // Observer of the LLAvatarTracker instance
+
+ boost::signals2::connection newMessageSignalConnection;
};
class LLConversationLogObserver
@@ -174,7 +185,8 @@ public:
enum EConversationChange
{
- VOICE_STATE = 1
+ CHANGED_TIME = 1, // last interaction time changed
+ CHANGED_NAME = 2 // conversation name changed
};
virtual ~LLConversationLogObserver(){}
diff --git a/indra/newview/llconversationloglist.cpp b/indra/newview/llconversationloglist.cpp
index d39e090c22..429e99f7ad 100644
--- a/indra/newview/llconversationloglist.cpp
+++ b/indra/newview/llconversationloglist.cpp
@@ -143,22 +143,32 @@ void LLConversationLogList::changed()
void LLConversationLogList::changed(const LLUUID& session_id, U32 mask)
{
- if (mask & LLConversationLogObserver::VOICE_STATE)
+ LLConversationLogListItem* item = getConversationLogListItem(session_id);
+
+ if (!item)
{
- std::vector<LLPanel*> panels;
- LLFlatListViewEx::getItems(panels);
+ return;
+ }
- std::vector<LLPanel*>::iterator iter = panels.begin();
+ if (mask & LLConversationLogObserver::CHANGED_TIME)
+ {
+ item->updateTimestamp();
- for (; iter != panels.end(); ++iter)
+ // if list is sorted by date and a date of some item has changed,
+ // than the whole list should be rebuilt
+ if (E_SORT_BY_DATE == getSortOrder())
{
- LLConversationLogListItem* item = dynamic_cast<LLConversationLogListItem*>(*iter);
-
- if (item && session_id == item->getConversation()->getSessionID() && !item->getConversation()->isConversationPast())
- {
- item->initIcons();
- return;
- }
+ mIsDirty = true;
+ }
+ }
+ else if (mask & LLConversationLogObserver::CHANGED_NAME)
+ {
+ item->updateName();
+ // if list is sorted by name and a name of some item has changed,
+ // than the whole list should be rebuilt
+ if (E_SORT_BY_DATE == getSortOrder())
+ {
+ mIsDirty = true;
}
}
}
@@ -401,6 +411,29 @@ const LLConversation* LLConversationLogList::getSelectedConversation()
return NULL;
}
+LLConversationLogListItem* LLConversationLogList::getConversationLogListItem(const LLUUID& session_id)
+{
+ std::vector<LLPanel*> panels;
+ LLFlatListViewEx::getItems(panels);
+ std::vector<LLPanel*>::iterator iter = panels.begin();
+
+ for (; iter != panels.end(); ++iter)
+ {
+ LLConversationLogListItem* item = dynamic_cast<LLConversationLogListItem*>(*iter);
+ if (item && session_id == item->getConversation()->getSessionID())
+ {
+ return item;
+ }
+ }
+
+ return NULL;
+}
+
+LLConversationLogList::ESortOrder LLConversationLogList::getSortOrder()
+{
+ return static_cast<ESortOrder>(gSavedSettings.getU32("CallLogSortOrder"));
+}
+
bool LLConversationLogListItemComparator::compare(const LLPanel* item1, const LLPanel* item2) const
{
const LLConversationLogListItem* conversation_item1 = dynamic_cast<const LLConversationLogListItem*>(item1);
diff --git a/indra/newview/llconversationloglist.h b/indra/newview/llconversationloglist.h
index 5e7fc0a9fb..62ec57e09e 100644
--- a/indra/newview/llconversationloglist.h
+++ b/indra/newview/llconversationloglist.h
@@ -43,6 +43,12 @@ class LLConversationLogList: public LLFlatListViewEx, public LLConversationLogOb
{
LOG_CLASS(LLConversationLogList);
public:
+
+ typedef enum e_sort_oder{
+ E_SORT_BY_NAME = 0,
+ E_SORT_BY_DATE = 1,
+ } ESortOrder;
+
struct Params : public LLInitParam::Block<Params, LLFlatListViewEx::Params>
{
Params(){};
@@ -90,6 +96,9 @@ private:
LLIMModel::LLIMSession::SType getSelectedSessionType();
const LLConversationLogListItem* getSelectedConversationPanel();
const LLConversation* getSelectedConversation();
+ LLConversationLogListItem* getConversationLogListItem(const LLUUID& session_id);
+
+ ESortOrder getSortOrder();
LLHandle<LLToggleableMenu> mContextMenu;
bool mIsDirty;
diff --git a/indra/newview/llconversationloglistitem.cpp b/indra/newview/llconversationloglistitem.cpp
index dddf216592..fac6130371 100644
--- a/indra/newview/llconversationloglistitem.cpp
+++ b/indra/newview/llconversationloglistitem.cpp
@@ -117,6 +117,16 @@ void LLConversationLogListItem::initIcons()
}
}
+void LLConversationLogListItem::updateTimestamp()
+{
+ mConversationDate->setValue(mConversation->getTimestamp());
+}
+
+void LLConversationLogListItem::updateName()
+{
+ mConversationName->setValue(mConversation->getConversationName());
+}
+
void LLConversationLogListItem::onMouseEnter(S32 x, S32 y, MASK mask)
{
getChildView("hovered_icon")->setVisible(true);
diff --git a/indra/newview/llconversationloglistitem.h b/indra/newview/llconversationloglistitem.h
index 2aaafa0fba..1bf7a0ed93 100644
--- a/indra/newview/llconversationloglistitem.h
+++ b/indra/newview/llconversationloglistitem.h
@@ -64,10 +64,16 @@ public:
void onDoubleClick();
- void initIcons();
+ /**
+ * updates string value of last interaction time from conversation
+ */
+ void updateTimestamp();
+ void updateName();
private:
+ void initIcons();
+
const LLConversation* mConversation;
LLTextBox* mConversationName;
diff --git a/indra/newview/llfloaterconversationlog.cpp b/indra/newview/llfloaterconversationlog.cpp
index 4375ce5726..7b4c999e52 100644
--- a/indra/newview/llfloaterconversationlog.cpp
+++ b/indra/newview/llfloaterconversationlog.cpp
@@ -45,11 +45,11 @@ BOOL LLFloaterConversationLog::postBuild()
switch (gSavedSettings.getU32("CallLogSortOrder"))
{
- case E_SORT_BY_NAME:
+ case LLConversationLogList::E_SORT_BY_NAME:
mConversationLogList->sortByName();
break;
- case E_SORT_BY_DATE:
+ case LLConversationLogList::E_SORT_BY_DATE:
mConversationLogList->sortByDate();
break;
}
@@ -87,12 +87,12 @@ void LLFloaterConversationLog::onCustomAction (const LLSD& userdata)
if ("sort_by_name" == command_name)
{
mConversationLogList->sortByName();
- gSavedSettings.setU32("CallLogSortOrder", E_SORT_BY_NAME);
+ gSavedSettings.setU32("CallLogSortOrder", LLConversationLogList::E_SORT_BY_NAME);
}
else if ("sort_by_date" == command_name)
{
mConversationLogList->sortByDate();
- gSavedSettings.setU32("CallLogSortOrder", E_SORT_BY_DATE);
+ gSavedSettings.setU32("CallLogSortOrder", LLConversationLogList::E_SORT_BY_DATE);
}
else if ("sort_friends_on_top" == command_name)
{
@@ -117,11 +117,11 @@ bool LLFloaterConversationLog::isActionChecked(const LLSD& userdata)
if ("sort_by_name" == command_name)
{
- return sort_order == E_SORT_BY_NAME;
+ return sort_order == LLConversationLogList::E_SORT_BY_NAME;
}
else if ("sort_by_date" == command_name)
{
- return sort_order == E_SORT_BY_DATE;
+ return sort_order == LLConversationLogList::E_SORT_BY_DATE;
}
else if ("sort_friends_on_top" == command_name)
{
diff --git a/indra/newview/llfloaterconversationlog.h b/indra/newview/llfloaterconversationlog.h
index 7d788c0290..e971330f3d 100644
--- a/indra/newview/llfloaterconversationlog.h
+++ b/indra/newview/llfloaterconversationlog.h
@@ -34,11 +34,6 @@ class LLFloaterConversationLog : public LLFloater
{
public:
- typedef enum e_sort_oder{
- E_SORT_BY_NAME = 0,
- E_SORT_BY_DATE = 1,
- } ESortOrder;
-
LLFloaterConversationLog(const LLSD& key);
virtual ~LLFloaterConversationLog(){};
diff --git a/indra/newview/llfloaterconversationpreview.cpp b/indra/newview/llfloaterconversationpreview.cpp
index 9ea35fcbad..dbcf154ef2 100644
--- a/indra/newview/llfloaterconversationpreview.cpp
+++ b/indra/newview/llfloaterconversationpreview.cpp
@@ -65,8 +65,6 @@ BOOL LLFloaterConversationPreview::postBuild()
std::string title = getString("Title", args);
setTitle(title);
- getChild<LLLineEditor>("description")->setValue(name);
-
LLLogChat::loadChatHistory(file, mMessages, true);
mCurrentPage = mMessages.size() / mPageSize;
diff --git a/indra/newview/skins/default/xui/en/floater_conversation_preview.xml b/indra/newview/skins/default/xui/en/floater_conversation_preview.xml
index d74c2c252d..0e5af67f68 100644
--- a/indra/newview/skins/default/xui/en/floater_conversation_preview.xml
+++ b/indra/newview/skins/default/xui/en/floater_conversation_preview.xml
@@ -14,41 +14,17 @@
name="Title">
CONVERSATION: [NAME]
</floater.string>
- <text
- type="string"
- length="1"
- follows="left|top"
- font="SansSerif"
- height="19"
- layout="topleft"
- left="10"
- name="desc txt"
- top="22"
- width="90">
- Description:
- </text>
- <line_editor
- border_style="line"
- border_thickness="1"
- enabled="false"
- follows="left|top|right"
- font="SansSerif"
- height="22"
- layout="topleft"
- left_pad="0"
- max_length_bytes="127"
- name="description"
- width="296" />
<chat_history
font="SansSerifSmall"
follows="all"
visible="true"
- height="310"
+ height="330"
name="chat_history"
notify_unread_msg="false"
parse_highlights="true"
parse_urls="true"
left="5"
+ top_pad="25"
width="390">
</chat_history>
<text
@@ -63,6 +39,7 @@
width="35">
</text>
<spinner
+ allow_digits_only="true"
decimal_digits="0"
follows="bottom|right"
height="23"