From 525041e2e796c21600e1ec5ec59ec65249d0f5c1 Mon Sep 17 00:00:00 2001
From: Rye Mutt <rye@alchemyviewer.org>
Date: Sun, 24 Dec 2023 20:08:48 -0500
Subject: [PATCH] Add nearby toast following chatbar width based on code from
 catznip. Also add setting to directly control nearby toast width

---
 indra/newview/app_settings/settings.xml       |  33 ++++
 indra/newview/llchatbar.cpp                   |  26 +++
 indra/newview/llchatbar.h                     |  11 ++
 indra/newview/llchatitemscontainerctrl.cpp    |   5 +-
 indra/newview/llfloaterimnearbychat.cpp       |  37 +++-
 indra/newview/llfloaterimnearbychat.h         |  18 +-
 .../newview/llfloaterimnearbychathandler.cpp  | 159 +++++++++++++++++-
 .../skins/default/xui/en/floater_chatbar.xml  |   4 +-
 8 files changed, 285 insertions(+), 8 deletions(-)

diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index 52314fb4eef..78a8265d64d 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -7836,6 +7836,39 @@
       <key>Value</key>
       <integer>23</integer>
     </map>
+	<key>NearbyToastHeightRatio</key>
+	<map>
+		<key>Comment</key>
+		<string>Percentage of the window height that nearby chat toasts can appear in</string>
+		<key>Persist</key>
+		<integer>1</integer>
+		<key>Type</key>
+		<string>S32</string>
+		<key>Value</key>
+		<integer>85</integer>
+	</map>
+	<key>NearbyToastOffset</key>
+	<map>
+		<key>Comment</key>
+		<string>Nearby chat toast offset from the bottom of the screen</string>
+		<key>Persist</key>
+		<integer>1</integer>
+		<key>Type</key>
+		<string>S32</string>
+		<key>Value</key>
+		<integer>70</integer>
+	</map>
+	<key>NearbyToastWidth</key>
+	<map>
+		<key>Comment</key>
+		<string>Width of the nearby chat toasts (or 0 to have the toasts follow the width of the chat bar)</string>
+		<key>Persist</key>
+		<integer>1</integer>
+		<key>Type</key>
+		<string>S32</string>
+		<key>Value</key>
+		<integer>0</integer>
+	</map>
     <key>StartUpToastLifeTime</key>
     <map>
       <key>Comment</key>
diff --git a/indra/newview/llchatbar.cpp b/indra/newview/llchatbar.cpp
index 3491ef3b2d1..8833e857726 100644
--- a/indra/newview/llchatbar.cpp
+++ b/indra/newview/llchatbar.cpp
@@ -108,6 +108,10 @@ LLChatBar::~LLChatBar()
 	LLGestureMgr::instance().removeObserver(mObserver);
 	delete mObserver;
 	mObserver = nullptr;
+
+	delete mReshapeSignal;
+	mReshapeSignal = nullptr;
+
 	if (mChatFontSizeConnection.connected())
 		mChatFontSizeConnection.disconnect();
 	// LLView destructor cleans up children
@@ -477,3 +481,25 @@ void LLChatBar::onCommitGesture(LLUICtrl* ctrl)
 		mGestureCombo->setFocus(FALSE);
 	}
 }
+
+// [SL:KB] - Patch: Chat-NearbyToastWidth | Checked: 2010-11-10 (Catznip-2.4)
+// virtual
+void LLChatBar::reshape(S32 width, S32 height, BOOL called_from_parent)
+{
+	LLFloater::reshape(width, height, called_from_parent);
+
+	if (mReshapeSignal)
+	{
+		(*mReshapeSignal)(this, width, height);
+	}
+}
+
+boost::signals2::connection LLChatBar::setReshapeCallback(const reshape_signal_t::slot_type& cb)
+{
+	if (!mReshapeSignal)
+	{
+		mReshapeSignal = new reshape_signal_t();
+	}
+	return mReshapeSignal->connect(cb);
+}
+// [/SL:KB]
\ No newline at end of file
diff --git a/indra/newview/llchatbar.h b/indra/newview/llchatbar.h
index 4114b75614d..f33f1aff8d9 100644
--- a/indra/newview/llchatbar.h
+++ b/indra/newview/llchatbar.h
@@ -52,6 +52,13 @@ class LLChatBar final
 	BOOL		handleKeyHere(KEY key, MASK mask) override;
 	void		onFocusLost() override;
 
+// [SL:KB] - Patch: Chat-NearbyToastWidth | Checked: 2010-11-10 (Catznip-2.4)
+	/*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE) override;
+
+	typedef boost::signals2::signal<void (LLUICtrl* ctrl, S32 width, S32 height)> reshape_signal_t;
+	boost::signals2::connection setReshapeCallback(const reshape_signal_t::slot_type& cb);
+// [/SL:KB]
+
 	void		refresh() override;
 	void		refreshGestures();
 
@@ -94,6 +101,10 @@ class LLChatBar final
 
 	LLChatBarGestureObserver* mObserver;
 
+// [SL:KB] - Patch: Chat-NearbyToastWidth | Checked: 2010-11-10 (Catznip-2.4)
+	reshape_signal_t*		mReshapeSignal = nullptr;
+// [/SL:KB]
+
 	boost::signals2::connection mChatFontSizeConnection;
 };
 
diff --git a/indra/newview/llchatitemscontainerctrl.cpp b/indra/newview/llchatitemscontainerctrl.cpp
index 8b241411311..8d815113217 100644
--- a/indra/newview/llchatitemscontainerctrl.cpp
+++ b/indra/newview/llchatitemscontainerctrl.cpp
@@ -379,7 +379,10 @@ void	LLFloaterIMNearbyChatToastPanel::snapToMessageHeight	()
 
 	panel_rect.setLeftTopAndSize( panel_rect.mLeft, panel_rect.mTop, panel_rect.getWidth(), new_height);
 	
-	reshape( getRect().getWidth(), getRect().getHeight(), 1);
+// [SL:KB] - Patch: Chat-NearbyToastWidth | Checked: 2010-08-27 (Catznip-2.1)
+	reshape( panel_rect.getWidth(), panel_rect.getHeight(), 1);
+// [/SL:KB]
+//	reshape( getRect().getWidth(), getRect().getHeight(), 1);
 	
 	setRect(panel_rect);
 
diff --git a/indra/newview/llfloaterimnearbychat.cpp b/indra/newview/llfloaterimnearbychat.cpp
index 71e62e5694d..edba749f777 100644
--- a/indra/newview/llfloaterimnearbychat.cpp
+++ b/indra/newview/llfloaterimnearbychat.cpp
@@ -106,6 +106,9 @@ LLFloaterIMNearbyChat::LLFloaterIMNearbyChat(const LLSD& llsd)
 :	LLFloaterIMSessionTab(LLSD(LLUUID::null)),
 	//mOutputMonitor(NULL),
 	mSpeakerMgr(NULL),
+// [SL:KB] - Patch: Chat-NearbyToastWidth | Checked: 2010-11-10 (Catznip-2.4)
+	mReshapeSignal(NULL),
+// [/SL:KB]
 	mExpandedHeight(COLLAPSED_HEIGHT + EXPANDED_HEIGHT)
 {
     mIsP2PChat = false;
@@ -123,6 +126,14 @@ LLFloaterIMNearbyChat::LLFloaterIMNearbyChat(const LLSD& llsd)
 	mChatChannelConnection = gSavedSettings.getControl("AlchemyNearbyChatChannel")->getCommitSignal()->connect([this](LLControlVariable*, const LLSD& newval, const LLSD&) { changeChannelLabel(newval.asInteger()); });
 }
 
+// [SL:KB] - Patch: Chat-NearbyToastWidth | Checked: 2010-11-10 (Catznip-2.4)
+LLFloaterIMNearbyChat::~LLFloaterIMNearbyChat()
+{
+	delete mReshapeSignal;
+	mChatChannelConnection.disconnect();
+}
+// [/SL:KB]
+
 //static
 LLFloaterIMNearbyChat* LLFloaterIMNearbyChat::buildFloater(const LLSD& key)
 {
@@ -295,7 +306,7 @@ void LLFloaterIMNearbyChat::setVisible(BOOL visible)
 {
 	LLFloaterIMSessionTab::setVisible(visible);
 
-	if(visible && isMessagePaneExpanded())
+	if(visible && isMessagePaneExpanded()) // <alchemy/>
 	{
 		removeScreenChat();
 	}
@@ -306,7 +317,7 @@ void LLFloaterIMNearbyChat::setVisibleAndFrontmost(BOOL take_focus, const LLSD&
 {
 	LLFloaterIMSessionTab::setVisibleAndFrontmost(take_focus, key);
 
-	if(!isTornOff() && matchesKey(key))
+	if(!isTornOff() && matchesKey(key)) // <alchemy/>
 	{
 		LLFloaterIMContainer::getInstance()->selectConversationPair(mSessionID, true, take_focus);
 	}
@@ -407,6 +418,28 @@ bool LLFloaterIMNearbyChat::isChatVisible() const
 	return isVisible;
 }
 
+// [SL:KB] - Patch: Chat-NearbyToastWidth | Checked: 2010-11-10 (Catznip-2.4)
+// virtual
+void LLFloaterIMNearbyChat::reshape(S32 width, S32 height, BOOL called_from_parent)
+{
+	LLFloater::reshape(width, height, called_from_parent);
+
+	if (mReshapeSignal)
+	{
+		(*mReshapeSignal)(this, width, height);
+	}
+}
+
+boost::signals2::connection LLFloaterIMNearbyChat::setReshapeCallback(const reshape_signal_t::slot_type& cb)
+{
+	if (!mReshapeSignal)
+	{
+		mReshapeSignal = new reshape_signal_t();
+	}
+	return mReshapeSignal->connect(cb); 
+}
+// [/SL:KB]
+
 void LLFloaterIMNearbyChat::showHistory()
 {
 	openFloater();
diff --git a/indra/newview/llfloaterimnearbychat.h b/indra/newview/llfloaterimnearbychat.h
index 969c4ec0b88..1efaf6fafa1 100644
--- a/indra/newview/llfloaterimnearbychat.h
+++ b/indra/newview/llfloaterimnearbychat.h
@@ -5,6 +5,7 @@
  * $LicenseInfo:firstyear=2002&license=viewerlgpl$
  * Second Life Viewer Source Code
  * Copyright (C) 2010, Linden Research, Inc.
+ * Copyright (C) 2010-2016, Kitty Barnett
  * 
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -48,7 +49,10 @@ class LLFloaterIMNearbyChat final
 public:
 	// constructor for inline chat-bars (e.g. hosted in chat history window)
 	LLFloaterIMNearbyChat(const LLSD& key = LLSD(LLUUID()));
-	~LLFloaterIMNearbyChat() {}
+// [SL:KB] - Patch: Chat-NearbyToastWidth | Checked: 2010-11-10 (Catznip-2.4)
+	~LLFloaterIMNearbyChat();
+// [/SL:KB]
+//	~LLFloaterIMNearbyChat() {}
 
 	static LLFloaterIMNearbyChat* buildFloater(const LLSD& key);
 
@@ -88,6 +92,13 @@ class LLFloaterIMNearbyChat final
 
 	static bool isWordsName(const std::string& name);
 
+// [SL:KB] - Patch: Chat-NearbyToastWidth | Checked: 2010-11-10 (Catznip-2.4)
+	/*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE) override;
+
+	typedef boost::signals2::signal<void (LLUICtrl* ctrl, S32 width, S32 height)> reshape_signal_t;
+	boost::signals2::connection setReshapeCallback(const reshape_signal_t::slot_type& cb);
+// [/SL:KB]
+
 	void showHistory();
 	void changeChannelLabel(S32 channel);
 
@@ -117,6 +128,9 @@ class LLFloaterIMNearbyChat final
 	LLOutputMonitorCtrl*	mOutputMonitor;
 	LLLocalSpeakerMgr*		mSpeakerMgr;
 
+// [SL:KB] - Patch: Chat-NearbyToastWidth | Checked: 2010-11-10 (Catznip-2.4)
+	reshape_signal_t*		mReshapeSignal;
+// [/SL:KB]
 	S32 mExpandedHeight;
 
 private:
@@ -124,7 +138,7 @@ class LLFloaterIMNearbyChat final
 
 	std::vector<LLChat> mMessageArchive;
 
-	boost::signals2::scoped_connection mChatChannelConnection;
+	boost::signals2::connection mChatChannelConnection;
 };
 
 template <class T>
diff --git a/indra/newview/llfloaterimnearbychathandler.cpp b/indra/newview/llfloaterimnearbychathandler.cpp
index c27d80c90a0..9da301cf57f 100644
--- a/indra/newview/llfloaterimnearbychathandler.cpp
+++ b/indra/newview/llfloaterimnearbychathandler.cpp
@@ -29,6 +29,10 @@
 #include "llagentdata.h" // for gAgentID
 #include "llfloaterimnearbychathandler.h"
 
+// [SL:KB] - Patch: Chat-NearbyToastWidth | Checked: 2010-11-10 (Catznip-2.4)
+#include "llchatentry.h"
+// [/SL:KB]
+#include "llchatbar.h"
 #include "llchatitemscontainerctrl.h"
 #include "llfirstuse.h"
 #include "llfloaterscriptdebug.h"
@@ -88,7 +92,29 @@ class LLFloaterIMNearbyChatScreenChannel: public LLScreenChannelBase
 		{
 			ctrl->getSignal()->connect(boost::bind(&LLFloaterIMNearbyChatScreenChannel::updateToastFadingTime, this));
 		}
+
+// [SL:KB] - Patch: Chat-NearbyToastWidth | Checked: 2010-08-27 (Catznip-2.1)
+		ctrl = gSavedSettings.getControl("NearbyToastWidth").get();
+		if (ctrl)
+		{
+			// updateToastWidth() will call getToastWidth() which will set up/break down mChatBarReshapeConnection as needed
+			ctrl->getSignal()->connect(boost::bind(&LLFloaterIMNearbyChatScreenChannel::updateToastWidth, this));
+		}
+// [/SL:KB]
+
+		ctrl = gSavedSettings.getControl("AlchemyNearbyChatInput").get();
+		if (ctrl)
+		{
+			ctrl->getSignal()->connect([this](LLControlVariable* control, const LLSD& new_val, const LLSD& old_val) { mChatBarReshapeConnection.disconnect(); });
+		}
+	}
+
+// [SL:KB] - Patch: Chat-NearbyToastWidth | Checked: 2010-11-10 (Catznip-2.4)
+	~LLFloaterIMNearbyChatScreenChannel()
+	{
+		mChatBarReshapeConnection.disconnect();
 	}
+// [/SL:KB]
 
 	void addChat	(LLSD& chat);
 	void arrangeToasts		();
@@ -99,6 +125,11 @@ class LLFloaterIMNearbyChatScreenChannel: public LLScreenChannelBase
 	void onToastDestroyed	(LLToast* toast, bool app_quitting);
 	void onToastFade		(LLToast* toast);
 
+// [SL:KB] - Patch: Chat-NearbyToastWidth | Checked: 2010-08-27 (Catznip-2.1)
+	S32  getToastWidth();
+	void updateToastWidth();
+// [/SL:KB]
+
 	void redrawToasts()
 	{
 		arrangeToasts();
@@ -159,6 +190,10 @@ class LLFloaterIMNearbyChatScreenChannel: public LLScreenChannelBase
 
 	bool	mStopProcessing;
 	bool	mChannelRect;
+
+// [SL:KB] - Patch: Chat-NearbyToastWidth | Checked: 2010-11-10 (Catznip-2.4)
+	boost::signals2::connection mChatBarReshapeConnection;
+// [/SL:KB]
 };
 
 
@@ -267,6 +302,12 @@ bool	LLFloaterIMNearbyChatScreenChannel::createPoolToast()
 	if(!panel)
 		return false;
 	
+// [SL:KB] - Patch: Chat-NearbyToastWidth | Checked: 2010-08-27 (Catznip-2.1)
+	LLRect rctPanel = panel->getRect();
+	rctPanel.setLeftTopAndSize(rctPanel.mLeft, rctPanel.mTop, getToastWidth(), rctPanel.getHeight());
+	panel->setRect(rctPanel);
+// [/SL:KB]
+
 	LLToast::Params p;
 	p.panel = panel;
 	p.lifetime_secs = gSavedSettings.getS32("NearbyToastLifeTime");
@@ -379,6 +420,10 @@ static bool sort_toasts_predicate(LLHandle<LLToast> first, LLHandle<LLToast> sec
 
 void LLFloaterIMNearbyChatScreenChannel::arrangeToasts()
 {
+// [SL:KB] - Patch: Chat-NearbyToastHeightRatio | Checked: Catznip-5.3
+	const float nToastHeightRatio = llclamp(gSavedSettings.getS32("NearbyToastHeightRatio"), 30, 100) / 100.f;
+// [/SL:KB]
+
 	if(mStopProcessing || isHovering())
 		return;
 
@@ -401,10 +446,16 @@ void LLFloaterIMNearbyChatScreenChannel::arrangeToasts()
 	mFloaterSnapRegion->localRectToOtherView(mFloaterSnapRegion->getLocalRect(), &channel_rect, gFloaterView);
 	channel_rect.mLeft += 10;
 	channel_rect.mRight = channel_rect.mLeft + 300;
+// [SL:KB] - Patch: Chat-NearbyToastHeightRatio | Checked: Catznip-5.3
+	channel_rect.mTop = channel_rect.mBottom + channel_rect.getHeight() * nToastHeightRatio;
+// [/SL:KB]
 
 	S32 channel_bottom = channel_rect.mBottom;
 
-	S32		bottom = channel_bottom + 80;
+//	S32		bottom = channel_bottom + 80;
+// [SL:KB] - Patch: Chat-NearbyChatBar | Checked: 2012-01-17 (Catznip-3.2)
+	S32		bottom = channel_bottom + gSavedSettings.getS32("NearbyToastOffset");
+// [/SL:KB]
 	S32		margin = ALControlCache::ToastGap;
 
 	//sort active toasts
@@ -423,7 +474,14 @@ void LLFloaterIMNearbyChatScreenChannel::arrangeToasts()
 
 		S32 toast_top = bottom + toast->getRect().getHeight() + margin;
 
-		if(toast_top > channel_rect.getHeight())
+//		if(toast_top > channel_rect.getHeight())
+// [SL:KB] - Patch: Chat-NearbyToastHeightRatio | Checked: Catznip-5.3
+		// Make some allowances:
+		//  * if a large toast appears (that currently won't fit the reserved height) then don't kill it and all other toasts
+		//  * if only 2 toasts are visible only kill them if we're covering at least half the screen or if they're really too tall
+		if ( (toast_top > channel_rect.mTop) && (it != m_active_toasts.begin()) &&
+		     ((m_active_toasts.size() > 3) || (nToastHeightRatio >= 0.5) || (toast_top > channel_rect.mBottom + channel_rect.getHeight() * 1.5)) )
+// [/SL:KB]
 		{
 			while(it!=m_active_toasts.end())
 			{
@@ -454,7 +512,104 @@ void LLFloaterIMNearbyChatScreenChannel::arrangeToasts()
 
 }
 
+// [SL:KB] - Patch: Chat-NearbyToastWidth | Checked: 2010-11-10 (Catznip-2.4)
+S32 LLFloaterIMNearbyChatScreenChannel::getToastWidth()
+{
+	static LLCachedControl<S32> s_nToastWidth(gSavedSettings, "NearbyToastWidth", 0);
+	if (0 == s_nToastWidth)					// Follow the width of the nearby chat bar
+	{
+		static S32 s_nLastToastWidth = 400;
+		static LLCachedControl<bool> sUseChatbar(gSavedSettings, "AlchemyNearbyChatInput", true);
+		if (!sUseChatbar)
+		{
+			LLFloaterIMNearbyChat* pNearbyChat = LLFloaterReg::findTypedInstance<LLFloaterIMNearbyChat>("nearby_chat");
+			if (pNearbyChat)
+			{
+				if (!mChatBarReshapeConnection.connected())
+				{
+					mChatBarReshapeConnection = pNearbyChat->setReshapeCallback(boost::bind(&LLFloaterIMNearbyChatScreenChannel::updateToastWidth, this));
+				}
+
+				// We're using the width of the nearby chat floater since it'll be nearly the width of the nearby chat bar anyway
+				if ((!pNearbyChat->isMinimized()) && ((pNearbyChat->isTornOff()) || (!pNearbyChat->getHost()) || (!pNearbyChat->getHost()->isMinimized())))
+				{
+					s_nLastToastWidth = llmax(pNearbyChat->getRect().getWidth(), 350);
+				}
+			}
+		}
+		else
+		{
+			LLChatBar* pNearbyChat = LLFloaterReg::getTypedInstance<LLChatBar>("chatbar");
+			if (pNearbyChat)
+			{
+				if (!mChatBarReshapeConnection.connected())
+				{
+					mChatBarReshapeConnection = pNearbyChat->setReshapeCallback(boost::bind(&LLFloaterIMNearbyChatScreenChannel::updateToastWidth, this));
+				}
+
+				// We're using the width of the chatbar floater
+				//if ((!pNearbyChat->isMinimized()) && ((pNearbyChat->isTornOff()) || (!pNearbyChat->getHost()) || (!pNearbyChat->getHost()->isMinimized())))
+				{
+					s_nLastToastWidth = llmax(pNearbyChat->getRect().getWidth(), 350);
+				}
+			}
+		}
+		return s_nLastToastWidth;
+	}
+
+	if (mChatBarReshapeConnection.connected())
+	{
+		mChatBarReshapeConnection.disconnect();
+	}
+
+	return llmax((S32)s_nToastWidth, 400);	// Provide a sane lower threshold for toast width
+}
+
+void LLFloaterIMNearbyChatScreenChannel::updateToastWidth()
+{
+	static S32 s_nToastWidthPrev = 0;
+
+	// Do nothing if the toast width hasn't actually changed
+	S32 nToastWidth = getToastWidth();
+	if (s_nToastWidthPrev == nToastWidth)
+		return;
+	s_nToastWidthPrev = nToastWidth;
 
+	//
+	// Resize the active toasts
+	//
+	for(toast_vec_t::iterator itActive = m_active_toasts.begin(); itActive != m_active_toasts.end(); ++itActive)
+	{
+		LLToast* pToast = (*itActive).get();
+		LLFloaterIMNearbyChatToastPanel* pToastPanel = (pToast) ? dynamic_cast<LLFloaterIMNearbyChatToastPanel*>(pToast->getPanel()) : NULL;
+		if (!pToastPanel)
+			continue;
+
+		LLRect rctToastPanel = pToastPanel->getRect();
+		rctToastPanel.setLeftTopAndSize(rctToastPanel.mLeft, rctToastPanel.mTop, nToastWidth, rctToastPanel.getHeight());
+		pToastPanel->reshape(rctToastPanel.getWidth(), rctToastPanel.getHeight(), 1);
+		pToastPanel->setRect(rctToastPanel);
+		pToastPanel->snapToMessageHeight();
+		pToast->reshapeToPanel();
+	}
+	arrangeToasts();
+
+	//
+	// Resize toasts in the toast pool
+	//
+	for(toast_list_t::iterator itPool = m_toast_pool.begin(); itPool != m_toast_pool.end(); ++itPool)
+	{
+		LLToast* pToast = (*itPool).get();
+		LLFloaterIMNearbyChatToastPanel* pToastPanel = (pToast) ? dynamic_cast<LLFloaterIMNearbyChatToastPanel*>(pToast->getPanel()) : NULL;
+		if (!pToastPanel)
+			continue;
+
+		LLRect rctToastPanel = pToastPanel->getRect();
+		rctToastPanel.setLeftTopAndSize(rctToastPanel.mLeft, rctToastPanel.mTop, nToastWidth, rctToastPanel.getHeight());
+		pToastPanel->setRect(rctToastPanel);
+	}
+}
+// [/SL:KB]
 
 //-----------------------------------------------------------------------------------------------
 //LLFloaterIMNearbyChatHandler
diff --git a/indra/newview/skins/default/xui/en/floater_chatbar.xml b/indra/newview/skins/default/xui/en/floater_chatbar.xml
index 4f287e0c271..2bd9c669be4 100644
--- a/indra/newview/skins/default/xui/en/floater_chatbar.xml
+++ b/indra/newview/skins/default/xui/en/floater_chatbar.xml
@@ -17,7 +17,9 @@
  name="chatbar"
  chrome="true"
  title=""
- width="450">
+ width="450"
+ single_instance="true"
+ save_rect="true">
    <line_editor
     border_style="line"
     border_thickness="1"
-- 
GitLab