Resolves tdf#163685 - Save user-defined formula

Change-Id: I88a1c40d3e97d77c289c8b670b52dca50dea126f
Co-authored-by: Rafael Lima <rafael.palma.lima@gmail.com>
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/176788
Reviewed-by: Rafael Lima <rafael.palma.lima@gmail.com>
Tested-by: Jenkins
This commit is contained in:
Heiko Tietze 2024-11-19 18:04:04 +01:00 committed by Heiko Tietze
parent f81af8f7a5
commit f395e6599f
17 changed files with 253 additions and 27 deletions

View file

@ -156,6 +156,7 @@ enum class SfxHintId {
// STARMATH
MathFormatChanged,
SmNewUserFormula,
// Sw
SwDrawViewsCreated,

View file

@ -161,6 +161,14 @@
<value xml:lang="en-US">Gap</value>
</prop>
</node>
<node oor:name=".uno:SaveFormula" oor:op="replace">
<prop oor:name="Label" oor:type="xs:string">
<value xml:lang="en-US">Save as User-defined Formula</value>
</prop>
<prop oor:name="Properties" oor:type="xs:int">
<value>1</value>
</prop>
</node>
</node>
<node oor:name="Popups">
<node oor:name=".uno:UnaryBinaryMenu" oor:op="replace">

View file

@ -216,6 +216,16 @@
</info>
</prop>
</group>
<group oor:name="Formula">
<info>
<desc>Contains user-defined formulas.</desc>
</info>
<prop oor:name="FormulaText" oor:type="xs:string">
<info>
<desc>Specifies the formula.</desc>
</info>
</prop>
</group>
</templates>
<component>
<group oor:name="Print">
@ -1036,5 +1046,10 @@
<desc>Lists the defined symbols.</desc>
</info>
</set>
<set oor:name="User-Defined" oor:node-type="Formula">
<info>
<desc>List of user-defined formulas.</desc>
</info>
</set>
</component>
</oor:component-schema>

View file

@ -42,9 +42,12 @@ class SmElementsControl
SmFormat maFormat;
int mnCurrentSetIndex;
sal_Int16 m_nSmSyntaxVersion;
bool m_bAllowDelete;
OUString m_sHoveredItem;
std::vector<std::unique_ptr<ElementData>> maItemDatas;
std::unique_ptr<weld::IconView> mpIconView;
std::unique_ptr<weld::Menu> mxPopup;
Link<const OUString&, void> maSelectHdlLink;
@ -55,22 +58,27 @@ class SmElementsControl
DECL_LINK(QueryTooltipHandler, const weld::TreeIter&, OUString);
DECL_LINK(ElementActivatedHandler, weld::IconView&, bool);
DECL_LINK(MousePressHdl, const MouseEvent&, bool);
static OUString GetElementSource(const OUString& itemId);
static OUString GetElementHelpText(const OUString& itemId);
static int GetElementPos(const OUString& itemId);
public:
explicit SmElementsControl(std::unique_ptr<weld::IconView> pIconView);
explicit SmElementsControl(std::unique_ptr<weld::IconView> pIconView,
std::unique_ptr<weld::Menu> pMenu);
~SmElementsControl();
static const std::vector<TranslateId>& categories();
void setElementSetIndex(int nSetIndex);
void setElementSetIndex(int nSetIndex, bool bForceBuild = false);
void setSmSyntaxVersion(sal_Int16 nSmSyntaxVersion);
void SetSelectHdl(const Link<const OUString&, void>& rLink) { maSelectHdlLink = rLink; }
void SetAllowDelete(bool bAllow) { m_bAllowDelete = bAllow; }
static Color GetTextColor();
static Color GetControlBackground();
};

View file

@ -100,6 +100,7 @@ class SmMathConfig final : public utl::ConfigItem, public SfxBroadcaster
std::unique_ptr<SmCfgOther> pOther;
std::unique_ptr<SmFontFormatList> pFontFormatList;
std::unique_ptr<SmSymbolManager> pSymbolMgr;
css::uno::Sequence<OUString> m_sUserDefinedNames;
bool bIsOtherModified;
bool bIsFormatModified;
SmFontPickList vFontPickList[8];
@ -169,6 +170,12 @@ public:
const SmFormat& GetStandardFormat() const;
void SetStandardFormat(const SmFormat& rFormat, bool bSaveFontFormatList = false);
css::uno::Sequence<OUString> LoadUserDefinedNames();
void GetUserDefinedFormula(std::u16string_view sName, OUString& sFormula);
bool HasUserDefinedFormula(std::u16string_view sName);
void SaveUserDefinedFormula(std::u16string_view sName, const OUString& sElement);
void DeleteUserDefinedFormula(std::u16string_view sName);
bool IsPrintTitle() const;
void SetPrintTitle(bool bVal);
bool IsPrintFormulaText() const;

View file

@ -74,5 +74,6 @@ class SfxUInt16Item;
#define SID_SMEDITWINDOWZOOM TypedWhichId<SfxUInt16Item>(SID_SMA_START + 129)
#define SID_DEFAULT_SM_SYNTAX_VERSION TypedWhichId<SfxUInt16Item>(SID_SMA_START + 130)
#define SID_INLINE_EDIT_ENABLE TypedWhichId<SfxBoolItem>(SID_SMA_START + 131)
#define SID_SAVE_FORMULA (SID_SMA_START + 132)
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

View file

@ -324,6 +324,7 @@
#define RID_CATEGORY_FORMATS NC_("RID_CATEGORY_FORMATS", "Formats" )
#define RID_CATEGORY_OTHERS NC_("RID_CATEGORY_OTHERS", "Others" )
#define RID_CATEGORY_EXAMPLES NC_("RID_CATEGORY_EXAMPLES", "Examples" )
#define RID_CATEGORY_USERDEFINED NC_("RID_CATEGORY_USERDEFINED", "User-defined" )
#define RID_EXAMPLE_CIRCUMFERENCE_HELP NC_("RID_EXAMPLE_CIRCUMFERENCE_HELP", "Circumference" )
#define RID_EXAMPLE_MASS_ENERGY_EQUIV_HELP NC_("RID_EXAMPLE_MASS_ENERGY_EQUIV_HELP", "Massenergy equivalence" )
@ -406,6 +407,8 @@
#define RID_PRINTUIOPT_ORIGSIZE NC_("RID_PRINTUIOPT_ORIGSIZE", "O~riginal size" )
#define RID_PRINTUIOPT_FITTOPAGE NC_("RID_PRINTUIOPT_FITTOPAGE", "Fit to ~page" )
#define RID_PRINTUIOPT_SCALING NC_("RID_PRINTUIOPT_SCALING", "~Scaling" )
#define STR_USER_DEFINED_FORMULA NC_("STR_USER_DEFINED_FORMULA", "Save formula as:" )
#define STR_USER_DEFINED_FORMULA_EXISTS NC_("STR_USER_DEFINED_FORMULA_EXISTS", "The Formula %1 exists.\nDo you want to overwrite?" )
// clang-format on
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

View file

@ -562,3 +562,20 @@ SfxVoidItem ZoomOut SID_ZOOMOUT
ToolBoxConfig = TRUE,
GroupId = SfxGroupId::View;
]
SfxVoidItem SaveFormula SID_SAVE_FORMULA
()
[
AutoUpdate = FALSE,
FastCall = FALSE,
ReadOnlyDoc = TRUE,
Toggle = FALSE,
Container = FALSE,
RecordAbsolute = FALSE,
RecordPerSet;
AccelConfig = TRUE,
MenuConfig = TRUE,
ToolBoxConfig = TRUE,
GroupId = SfxGroupId::View;
]

View file

@ -297,6 +297,11 @@ interface FormulaView
ExecMethod = Execute ;
StateMethod = GetState ;
]
SID_SAVE_FORMULA
[
ExecMethod = Execute ;
StateMethod = GetState ;
]
}
shell SmViewShell

View file

@ -460,6 +460,7 @@ const std::vector<TranslateId> s_a5Categories{
RID_CATEGORY_FORMATS,
RID_CATEGORY_OTHERS,
RID_CATEGORY_EXAMPLES,
RID_CATEGORY_USERDEFINED,
};
template <size_t N>
@ -493,24 +494,30 @@ struct ElementData
{
OUString maElementSource;
OUString maHelpText;
ElementData(const OUString& aElementSource, const OUString& aHelpText)
int maPos;
ElementData(const OUString& aElementSource, const OUString& aHelpText, const int& aPos)
: maElementSource(aElementSource)
, maHelpText(aHelpText)
, maPos(aPos)
{
}
};
SmElementsControl::SmElementsControl(std::unique_ptr<weld::IconView> pIconView)
SmElementsControl::SmElementsControl(std::unique_ptr<weld::IconView> pIconView,
std::unique_ptr<weld::Menu> pMenu)
: mpDocShell(new SmDocShell(SfxModelFlags::EMBEDDED_OBJECT))
, mnCurrentSetIndex(-1)
, m_nSmSyntaxVersion(SmModule::get()->GetConfig()->GetDefaultSmSyntaxVersion())
, m_bAllowDelete(false)
, mpIconView(std::move(pIconView))
, mxPopup(std::move(pMenu))
{
maParser.reset(starmathdatabase::GetVersionSmParser(m_nSmSyntaxVersion));
maParser->SetImportSymbolNames(true);
mpIconView->connect_query_tooltip(LINK(this, SmElementsControl, QueryTooltipHandler));
mpIconView->connect_item_activated(LINK(this, SmElementsControl, ElementActivatedHandler));
mpIconView->connect_mouse_press(LINK(this, SmElementsControl, MousePressHdl));
}
SmElementsControl::~SmElementsControl()
@ -585,7 +592,7 @@ void SmElementsControl::addElement(const OUString& aElementVisual, const OUStrin
pDevice->SetOutputSizePixel(aSize);
SmDrawingVisitor(*pDevice, pDevice->PixelToLogic(Point(5, 0)), pNode.get(), maFormat);
maItemDatas.push_back(std::make_unique<ElementData>(aElementSource, aHelpText));
maItemDatas.push_back(std::make_unique<ElementData>(aElementSource, aHelpText, maItemDatas.size()));
const OUString aId(weld::toId(maItemDatas.back().get()));
mpIconView->insert(-1, nullptr, &aId, pDevice, nullptr);
if (mpIconView->get_item_width() < aSize.Width())
@ -602,9 +609,14 @@ OUString SmElementsControl::GetElementHelpText(const OUString& itemId)
return weld::fromId<ElementData*>(itemId)->maHelpText;
}
void SmElementsControl::setElementSetIndex(int nSetIndex)
int SmElementsControl::GetElementPos(const OUString& itemId)
{
if (mnCurrentSetIndex == nSetIndex)
return weld::fromId<ElementData*>(itemId)->maPos;
}
void SmElementsControl::setElementSetIndex(int nSetIndex, bool bForceBuild)
{
if (!bForceBuild && mnCurrentSetIndex == nSetIndex)
return;
mnCurrentSetIndex = nSetIndex;
build();
@ -617,25 +629,36 @@ void SmElementsControl::addElements(int nCategory)
mpIconView->set_item_width(0);
maItemDatas.clear();
assert(nCategory >= 0 && o3tl::make_unsigned(nCategory) < s_a5CategoryDescriptions.size());
const auto& [aElementsArray, aElementsArraySize] = s_a5CategoryDescriptions[nCategory];
for (size_t i = 0; i < aElementsArraySize; i++)
if (o3tl::make_unsigned(nCategory) < s_a5CategoryDescriptions.size())
{
const auto& [element, elementHelp, elementVisual, visualTranslatable] = aElementsArray[i];
if (element.empty())
const auto& [aElementsArray, aElementsArraySize] = s_a5CategoryDescriptions[nCategory];
for (size_t i = 0; i < aElementsArraySize; i++)
{
mpIconView->append_separator({});
const auto& [element, elementHelp, elementVisual, visualTranslatable] = aElementsArray[i];
if (element.empty())
{
mpIconView->append_separator({});
}
else
{
OUString aElement(element);
OUString aVisual(elementVisual.empty() ? aElement : OUString(elementVisual));
if (visualTranslatable)
aVisual = aVisual.replaceFirst("$1", SmResId(visualTranslatable));
OUString aHelp(elementHelp ? SmResId(elementHelp) : OUString());
addElement(aVisual, aElement, aHelp);
}
}
else
}
else
{
css::uno::Sequence<OUString> sNames = SmModule::get()->GetConfig()->LoadUserDefinedNames();
OUString sFormula;
for (int i = 0; i < sNames.getLength(); i++)
{
OUString aElement(element);
OUString aVisual(elementVisual.empty() ? aElement : OUString(elementVisual));
if (visualTranslatable)
aVisual = aVisual.replaceFirst("$1", SmResId(visualTranslatable));
OUString aHelp(elementHelp ? SmResId(elementHelp) : OUString());
addElement(aVisual, aElement, aHelp);
SmModule::get()->GetConfig()->GetUserDefinedFormula(sNames[i], sFormula);
addElement(sFormula, sFormula, sNames[i]);
}
}
@ -649,6 +672,7 @@ void SmElementsControl::build()
{
case 5:
addElements(mnCurrentSetIndex);
m_sHoveredItem = "nil"; // if list is empty we must not use the previously hovered item
break;
case 6:
default:
@ -671,7 +695,10 @@ void SmElementsControl::setSmSyntaxVersion(sal_Int16 nSmSyntaxVersion)
IMPL_LINK(SmElementsControl, QueryTooltipHandler, const weld::TreeIter&, iter, OUString)
{
if (const OUString id = mpIconView->get_id(iter); !id.isEmpty())
{
m_sHoveredItem = id;
return GetElementHelpText(id);
}
return {};
}
@ -684,4 +711,24 @@ IMPL_LINK_NOARG(SmElementsControl, ElementActivatedHandler, weld::IconView&, boo
return true;
}
IMPL_LINK(SmElementsControl, MousePressHdl, const MouseEvent&, rEvt, bool)
{
if (rEvt.IsRight() && m_bAllowDelete && (m_sHoveredItem != "nil"))
{
mpIconView->select( GetElementPos(m_sHoveredItem) );
OUString sElementId = mpIconView->get_selected_id();
if (!sElementId.isEmpty())
{
OUString sResponse = mxPopup->popup_at_rect(
mpIconView.get(), tools::Rectangle(rEvt.GetPosPixel(), Size(1, 1)));
if (sResponse == "delete")
{
SmModule::get()->GetConfig()->DeleteUserDefinedFormula( GetElementHelpText(m_sHoveredItem) );
build(); //refresh view
}
mpIconView->unselect_all();
}
}
return true;
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

View file

@ -24,6 +24,7 @@
#include <sfx2/lokcomponenthelpers.hxx>
#include <svl/stritem.hxx>
#include <svl/itemset.hxx>
#include <svl/hint.hxx>
#include "SmElementsPanel.hxx"
#include <starmath.hrc>
@ -45,8 +46,8 @@ SmElementsPanel::SmElementsPanel(weld::Widget& rParent, const SfxBindings& rBind
u"modules/smath/ui/sidebarelements_math.ui"_ustr)
, mrBindings(rBindings)
, mxCategoryList(m_xBuilder->weld_combo_box(u"categorylist"_ustr))
, mxElementsControl(
std::make_unique<SmElementsControl>(m_xBuilder->weld_icon_view(u"elements"_ustr)))
, mxElementsControl(std::make_unique<SmElementsControl>(
m_xBuilder->weld_icon_view(u"elements"_ustr), m_xBuilder->weld_menu("deletemenu")))
{
for (const auto& rCategoryId : SmElementsControl::categories())
mxCategoryList->append_text(SmResId(rCategoryId));
@ -58,6 +59,8 @@ SmElementsPanel::SmElementsPanel(weld::Widget& rParent, const SfxBindings& rBind
mxElementsControl->setElementSetIndex(0);
mxElementsControl->SetSelectHdl(LINK(this, SmElementsPanel, ElementClickHandler));
StartListening(*GetView());
}
SmElementsPanel::~SmElementsPanel()
@ -66,6 +69,15 @@ SmElementsPanel::~SmElementsPanel()
mxCategoryList.reset();
}
void SmElementsPanel::Notify(SfxBroadcaster&, const SfxHint& rHint)
{
if (rHint.GetId() == SfxHintId::SmNewUserFormula)
{
mxCategoryList->set_active_text(SmResId(RID_CATEGORY_USERDEFINED));
mxElementsControl->setElementSetIndex(mxCategoryList->get_active(), true);
}
}
IMPL_LINK(SmElementsPanel, CategorySelectedHandle, weld::ComboBox&, rList, void)
{
const int nActive = rList.get_active();
@ -74,6 +86,12 @@ IMPL_LINK(SmElementsPanel, CategorySelectedHandle, weld::ComboBox&, rList, void)
mxElementsControl->setElementSetIndex(nActive);
if (SmViewShell* pViewSh = GetView())
mxElementsControl->setSmSyntaxVersion(pViewSh->GetDoc()->GetSmSyntaxVersion());
// If the "User-defined" category is selected, allow deletion
if (mxCategoryList->get_active_text() == SmResId(RID_CATEGORY_USERDEFINED))
mxElementsControl->SetAllowDelete(true);
else
mxElementsControl->SetAllowDelete(false);
}
IMPL_LINK(SmElementsPanel, ElementClickHandler, const OUString&, ElementSource, void)

View file

@ -21,6 +21,7 @@
#include <sal/config.h>
#include <svl/lstner.hxx>
#include <sfx2/bindings.hxx>
#include <sfx2/sidebar/PanelLayout.hxx>
#include <vcl/customweld.hxx>
@ -32,10 +33,13 @@
namespace sm::sidebar
{
class SmElementsPanel : public PanelLayout
class SmElementsPanel : public PanelLayout, public SfxListener
{
public:
static std::unique_ptr<PanelLayout> Create(weld::Widget& rParent, const SfxBindings& rBindings);
void Notify(SfxBroadcaster& rBC, const SfxHint& rHint);
SmElementsPanel(weld::Widget& rParent, const SfxBindings& rBindings);
~SmElementsPanel();

View file

@ -44,6 +44,7 @@ using namespace com::sun::star::beans;
constexpr OUString SYMBOL_LIST = u"SymbolList"_ustr;
constexpr OUString FONT_FORMAT_LIST = u"FontFormatList"_ustr;
constexpr OUString USER_DEFINED_LIST = u"User-Defined"_ustr;
static Sequence< OUString > lcl_GetFontPropertyNames()
{
@ -575,7 +576,6 @@ void SmMathConfig::SetSymbols( const std::vector< SmSym > &rNewSymbols )
StripFontFormatList( rNewSymbols );
}
SmFontFormatList & SmMathConfig::GetFontFormatList()
{
if (!pFontFormatList)
@ -585,7 +585,6 @@ SmFontFormatList & SmMathConfig::GetFontFormatList()
return *pFontFormatList;
}
void SmMathConfig::LoadFontFormatList()
{
if (!pFontFormatList)
@ -661,6 +660,47 @@ void SmMathConfig::ReadFontFormat( SmFontFormat &rFontFormat,
OSL_ENSURE( bOK, "read FontFormat failed" );
}
css::uno::Sequence<OUString> SmMathConfig::LoadUserDefinedNames()
{
m_sUserDefinedNames = GetNodeNames(USER_DEFINED_LIST);
return m_sUserDefinedNames;
}
void SmMathConfig::GetUserDefinedFormula(std::u16string_view sName, OUString &sFormula)
{
css::uno::Sequence<OUString> aNames(1);
OUString* pName = aNames.getArray();
pName[0] = USER_DEFINED_LIST + "/" + sName + "/FormulaText";
const Sequence<Any> aValues(GetProperties(aNames));
const Any* pValues = aValues.getConstArray();
const Any* pVal = pValues;
*pVal >>= sFormula;
}
bool SmMathConfig::HasUserDefinedFormula(std::u16string_view sName)
{
for (int i = 0; i < m_sUserDefinedNames.getLength(); i++)
if (m_sUserDefinedNames[i] == sName)
return true;
return false;
}
void SmMathConfig::SaveUserDefinedFormula(std::u16string_view sName, const OUString& sElement)
{
Sequence<PropertyValue> pValues(1);
auto pArgs = pValues.getArray();
pArgs[0].Name = USER_DEFINED_LIST + "/" + sName + "/FormulaText";
pArgs[0].Value <<= sElement;
SetSetProperties( USER_DEFINED_LIST, pValues );
}
void SmMathConfig::DeleteUserDefinedFormula(std::u16string_view sName)
{
Sequence<OUString> aElements { OUString(sName) };
ClearNodeElements(USER_DEFINED_LIST, aElements);
}
void SmMathConfig::SaveFontFormatList()
{

View file

@ -89,6 +89,7 @@
#include <mathmlimport.hxx>
#include <cursor.hxx>
#include "accessibility.hxx"
#include <svl/hint.hxx>
#include <ElementsDockingWindow.hxx>
#include <helpids.h>
@ -1840,6 +1841,39 @@ void SmViewShell::Execute(SfxRequest& rReq)
GetViewFrame().GetBindings().Invalidate(bRTL ? SID_ATTR_PARA_LEFT_TO_RIGHT : SID_ATTR_PARA_RIGHT_TO_LEFT);
}
break;
case SID_SAVE_FORMULA:
{
OUString aName = "My Formula 1";
OUString aDesc(SmResId(STR_USER_DEFINED_FORMULA));
SvxAbstractDialogFactory* pFact = SvxAbstractDialogFactory::Create();
ScopedVclPtr<AbstractSvxNameDialog> pDlg(
pFact->CreateSvxNameDialog(GetFrameWeld(), aName, aDesc));
if (pDlg->Execute() == RET_OK)
{
aName = pDlg->GetName();
if (SmModule::get()->GetConfig()->HasUserDefinedFormula(aName))
{
std::unique_ptr<weld::MessageDialog> xQuery(Application::CreateMessageDialog(
GetFrameWeld(), VclMessageType::Question, VclButtonsType::YesNo,
SmResId(STR_USER_DEFINED_FORMULA_EXISTS).replaceAll("%1", aName)));
if (xQuery->run() == RET_NO)
break;
}
SmEditWindow* pEditWin = GetEditWindow();
SmModule::get()->GetConfig()->SaveUserDefinedFormula(aName, pEditWin->GetText());
// Show the Elements sidebar with the "User-defined" entry selected
GetViewFrame().ShowChildWindow(SID_SIDEBAR);
sfx2::sidebar::Sidebar::ShowPanel(u"MathElementsPanel",
GetViewFrame().GetFrame().GetFrameInterface());
GetViewFrame().GetBindings().Invalidate( SID_ELEMENTSDOCKINGWINDOW );
Broadcast(SfxHint(SfxHintId::SmNewUserFormula));
rReq.Ignore ();
}
pDlg.disposeAndClear();
}
break;
}
rReq.Done();
}
@ -1949,6 +1983,10 @@ void SmViewShell::GetState(SfxItemSet &rSet)
case SID_ATTR_PARA_RIGHT_TO_LEFT:
rSet.Put(SfxBoolItem(nWh, GetDoc()->GetFormat().IsRightToLeft()));
break;
case SID_SAVE_FORMULA:
if (!pEditWin || pEditWin->IsEmpty())
rSet.DisableItem(nWh);
break;
}
}
}

View file

@ -125,6 +125,7 @@
<menu:menuitem menu:id=".uno:InsertSymbol"/>
<menu:menuitem menu:id=".uno:ImportFormula"/>
<menu:menuitem menu:id=".uno:ImportMathMLClipboard"/>
<menu:menuitem menu:id=".uno:SaveFormula"/>
<menu:menuseparator/>
<menu:menu menu:id=".uno:MacrosMenu">
<menu:menupopup>

View file

@ -15,6 +15,7 @@
<menu:menuitem menu:id=".uno:Cut"/>
<menu:menuitem menu:id=".uno:Copy"/>
<menu:menuitem menu:id=".uno:Paste"/>
<menu:menuitem menu:id=".uno:SaveFormula"/>
<menu:menuseparator/>
<menu:menu menu:id=".uno:UnaryBinaryMenu">
<menu:menupopup>

View file

@ -2,6 +2,18 @@
<!-- Generated with glade 3.38.1 -->
<interface domain="sm">
<requires lib="gtk+" version="3.20"/>
<object class="GtkMenu" id="deletemenu">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkMenuItem" id="delete">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes" context="mathelementspanel|popupmenu|delete">Delete Formula</property>
<property name="use-underline">True</property>
</object>
</child>
</object>
<object class="GtkTreeStore" id="liststore2">
<columns>
<!-- column-name expander -->