sd: keep a non-owning pointer to the OverridingShell
[LibreOffice.git] / cui / source / customize / SvxNotebookbarConfigPage.cxx
blob7bde0055bcfc39835278a0750527bd4206f418ff
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <sal/config.h>
22 #include <vcl/commandinfoprovider.hxx>
23 #include <vcl/event.hxx>
24 #include <vcl/weld.hxx>
25 #include <vcl/svapp.hxx>
27 #include <algorithm>
28 #include <cstddef>
30 #include <helpids.h>
31 #include <strings.hrc>
33 #include <cfg.hxx>
34 #include <SvxNotebookbarConfigPage.hxx>
35 #include <SvxConfigPageHelper.hxx>
36 #include <dialmgr.hxx>
37 #include <libxml/parser.h>
38 #include <osl/file.hxx>
39 #include <CustomNotebookbarGenerator.hxx>
40 #include <sfx2/notebookbar/SfxNotebookBar.hxx>
41 #include <unotools/configmgr.hxx>
42 #include <comphelper/processfactory.hxx>
43 #include <o3tl/string_view.hxx>
44 #include <com/sun/star/frame/theUICommandDescription.hpp>
46 namespace uno = css::uno;
47 namespace frame = css::frame;
48 namespace container = css::container;
49 namespace beans = css::beans;
51 static bool isCategoryAvailable(std::u16string_view sClassId, std::u16string_view sUIItemId,
52 std::u16string_view sActiveCategory, bool& isCategory)
54 if (sUIItemId == sActiveCategory)
55 return true;
56 else if ((sClassId == u"GtkMenu" || sClassId == u"GtkGrid") && sUIItemId != sActiveCategory)
58 isCategory = false;
59 return false;
61 return false;
64 static OUString charToString(const char* cString)
66 return OUString(cString, strlen(cString), RTL_TEXTENCODING_UTF8);
69 static OUString getFileName(std::u16string_view aFileName)
71 if (aFileName == u"notebookbar.ui")
72 return CuiResId(RID_CUISTR_TABBED);
73 else if (aFileName == u"notebookbar_compact.ui")
74 return CuiResId(RID_CUISTR_TABBED_COMPACT);
75 else if (aFileName == u"notebookbar_groupedbar_full.ui")
76 return CuiResId(RID_CUISTR_GROUPEDBAR);
77 else if (aFileName == u"notebookbar_groupedbar_compact.ui")
78 return CuiResId(RID_CUISTR_GROUPEDBAR_COMPACT);
79 else
80 return u"None"_ustr;
83 static OUString getModuleId(std::u16string_view sModuleName)
85 if (sModuleName == u"Writer")
86 return u"com.sun.star.text.TextDocument"_ustr;
87 else if (sModuleName == u"Draw")
88 return u"com.sun.star.drawing.DrawingDocument"_ustr;
89 else if (sModuleName == u"Impress")
90 return u"com.sun.star.presentation.PresentationDocument"_ustr;
91 else if (sModuleName == u"Calc")
92 return u"com.sun.star.sheet.SpreadsheetDocument"_ustr;
93 else
94 return u"None"_ustr;
97 SvxNotebookbarConfigPage::SvxNotebookbarConfigPage(weld::Container* pPage,
98 weld::DialogController* pController,
99 const SfxItemSet& rSet)
100 : SvxConfigPage(pPage, pController, rSet)
102 m_xCommandCategoryListBox->set_visible(false);
103 m_xDescriptionFieldLb->set_visible(false);
104 m_xSearchEdit->set_visible(false);
105 m_xDescriptionField->set_visible(false);
106 m_xMoveUpButton->set_visible(false);
107 m_xMoveDownButton->set_visible(false);
108 m_xCommandButtons->set_visible(false);
109 m_xLeftFunctionLabel->set_visible(false);
110 m_xSearchLabel->set_visible(false);
111 m_xCategoryLabel->set_visible(false);
112 m_xCustomizeBox->set_visible(false);
113 m_xCustomizeLabel->set_visible(false);
115 weld::TreeView& rCommandCategoryBox = m_xFunctions->get_widget();
116 rCommandCategoryBox.hide();
118 m_xContentsListBox.reset(
119 new SvxNotebookbarEntriesListBox(m_xBuilder->weld_tree_view(u"toolcontents"_ustr), this));
120 m_xDropTargetHelper.reset(
121 new SvxConfigPageFunctionDropTarget(*this, m_xContentsListBox->get_widget()));
122 weld::TreeView& rTreeView = m_xContentsListBox->get_widget();
123 Size aSize(m_xFunctions->get_size_request());
124 rTreeView.set_size_request(aSize.Width(), aSize.Height());
126 rTreeView.set_hexpand(true);
127 rTreeView.set_vexpand(true);
128 rTreeView.set_help_id(HID_SVX_CONFIG_NOTEBOOKBAR_CONTENTS);
129 rTreeView.show();
132 SvxNotebookbarConfigPage::~SvxNotebookbarConfigPage() {}
134 void SvxNotebookbarConfigPage::DeleteSelectedTopLevel() {}
136 void SvxNotebookbarConfigPage::DeleteSelectedContent() {}
138 void SvxNotebookbarConfigPage::Init()
140 m_xTopLevelListBox->clear();
141 m_xContentsListBox->clear();
142 m_xSaveInListBox->clear();
143 OUString sNotebookbarInterface = getFileName(m_sFileName);
145 OUString sScopeName
146 = utl::ConfigManager::getProductName() + " " + m_sAppName + " - " + sNotebookbarInterface;
147 OUString sSaveInListBoxID = notebookbarTabScope;
149 m_xSaveInListBox->append(sSaveInListBoxID, sScopeName);
150 m_xSaveInListBox->set_active_id(sSaveInListBoxID);
152 m_xTopLevelListBox->append(u"NotebookBar"_ustr, CuiResId(RID_CUISTR_ALL_COMMANDS));
153 m_xTopLevelListBox->set_active_id(u"NotebookBar"_ustr);
154 SelectElement();
157 SaveInData* SvxNotebookbarConfigPage::CreateSaveInData(
158 const css::uno::Reference<css::ui::XUIConfigurationManager>& xCfgMgr,
159 const css::uno::Reference<css::ui::XUIConfigurationManager>& xParentCfgMgr,
160 const OUString& aModuleId, bool bDocConfig)
162 return static_cast<SaveInData*>(
163 new ToolbarSaveInData(xCfgMgr, xParentCfgMgr, aModuleId, bDocConfig));
166 void SvxNotebookbarConfigPage::UpdateButtonStates() {}
168 short SvxNotebookbarConfigPage::QueryReset()
170 OUString msg = CuiResId(RID_CUISTR_CONFIRM_TOOLBAR_RESET);
172 OUString saveInName = m_xSaveInListBox->get_active_text();
174 OUString label = SvxConfigPageHelper::replaceSaveInName(msg, saveInName);
176 std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(
177 GetFrameWeld(), VclMessageType::Question, VclButtonsType::YesNo, label));
178 int nValue = xQueryBox->run();
179 if (nValue == RET_YES)
181 osl::File::remove(CustomNotebookbarGenerator::getCustomizedUIPath());
183 OUString sNotebookbarInterface = getFileName(m_sFileName);
184 Sequence<OUString> sSequenceEntries;
185 CustomNotebookbarGenerator::setCustomizedUIItem(sSequenceEntries, sNotebookbarInterface);
186 OUString sUIPath = "modules/s" + m_sAppName.toAsciiLowerCase() + "/ui/";
187 sfx2::SfxNotebookBar::ReloadNotebookBar(sUIPath);
189 return nValue;
192 void SvxConfigPage::InsertEntryIntoNotebookbarTabUI(std::u16string_view sClassId,
193 const OUString& sUIItemId,
194 const OUString& sUIItemCommand,
195 weld::TreeView& rTreeView,
196 const weld::TreeIter& rIter)
198 css::uno::Reference<css::container::XNameAccess> m_xCommandToLabelMap;
199 const uno::Reference<uno::XComponentContext>& xContext
200 = ::comphelper::getProcessComponentContext();
201 uno::Reference<container::XNameAccess> xNameAccess(
202 css::frame::theUICommandDescription::get(xContext));
204 uno::Sequence<beans::PropertyValue> aPropSeq;
206 xNameAccess->getByName(getModuleId(m_sAppName)) >>= m_xCommandToLabelMap;
210 uno::Any aModuleVal = m_xCommandToLabelMap->getByName(sUIItemCommand);
212 aModuleVal >>= aPropSeq;
214 catch (container::NoSuchElementException&)
218 OUString aLabel;
219 for (auto const& prop : aPropSeq)
220 if (prop.Name == "Name")
221 prop.Value >>= aLabel;
223 OUString aName = SvxConfigPageHelper::stripHotKey(aLabel);
225 if (sClassId == u"GtkSeparatorMenuItem" || sClassId == u"GtkSeparator")
227 rTreeView.set_text(rIter, u"--------------------------------------------"_ustr, 0);
229 else
231 if (aName.isEmpty())
232 aName = sUIItemId;
233 auto xImage = GetSaveInData()->GetImage(sUIItemCommand);
234 if (xImage.is())
235 rTreeView.set_image(rIter, xImage, -1);
236 rTreeView.set_text(rIter, aName, 0);
237 rTreeView.set_id(rIter, sUIItemId);
241 void SvxNotebookbarConfigPage::getNodeValue(xmlNode* pNodePtr, NotebookbarEntries& aNodeEntries)
243 pNodePtr = pNodePtr->xmlChildrenNode;
244 while (pNodePtr)
246 if (!(xmlStrcmp(pNodePtr->name, reinterpret_cast<const xmlChar*>("property"))))
248 xmlChar* UriValue = xmlGetProp(pNodePtr, reinterpret_cast<const xmlChar*>("name"));
249 if (!(xmlStrcmp(UriValue, reinterpret_cast<const xmlChar*>("visible"))))
251 xmlChar* aValue = xmlNodeGetContent(pNodePtr);
252 const char* cVisibleValue = reinterpret_cast<const char*>(aValue);
253 aNodeEntries.sVisibleValue = charToString(cVisibleValue);
254 xmlFree(aValue);
256 if (!(xmlStrcmp(UriValue, reinterpret_cast<const xmlChar*>("action_name"))))
258 xmlChar* aValue = xmlNodeGetContent(pNodePtr);
259 const char* cActionName = reinterpret_cast<const char*>(aValue);
260 aNodeEntries.sActionName = charToString(cActionName);
261 xmlFree(aValue);
263 xmlFree(UriValue);
265 pNodePtr = pNodePtr->next;
269 void SvxNotebookbarConfigPage::searchNodeandAttribute(std::vector<NotebookbarEntries>& aEntries,
270 std::vector<CategoriesEntries>& aCategoryList,
271 OUString& sActiveCategory,
272 CategoriesEntries& aCurItemEntry,
273 xmlNode* pNodePtr, bool isCategory)
275 pNodePtr = pNodePtr->xmlChildrenNode;
276 while (pNodePtr)
278 if (pNodePtr->type == XML_ELEMENT_NODE)
280 const char* cNodeName = reinterpret_cast<const char*>(pNodePtr->name);
281 if (strcmp(cNodeName, "object") == 0)
283 OUString sSecondVal;
285 xmlChar* UriValue = xmlGetProp(pNodePtr, reinterpret_cast<const xmlChar*>("id"));
286 const char* cUIItemID = reinterpret_cast<const char*>(UriValue);
287 OUString sUIItemId = charToString(cUIItemID);
288 xmlFree(UriValue);
290 UriValue = xmlGetProp(pNodePtr, reinterpret_cast<const xmlChar*>("class"));
291 const char* cClassId = reinterpret_cast<const char*>(UriValue);
292 OUString sClassId = charToString(cClassId);
293 xmlFree(UriValue);
295 CategoriesEntries aCategoryEntry;
296 if (sClassId == "sfxlo-PriorityHBox")
298 aCategoryEntry.sDisplayName = sUIItemId;
299 aCategoryEntry.sUIItemId = sUIItemId;
300 aCategoryEntry.sClassType = sClassId;
301 aCategoryList.push_back(aCategoryEntry);
303 aCurItemEntry = std::move(aCategoryEntry);
305 else if (sClassId == "sfxlo-PriorityMergedHBox")
307 aCategoryEntry.sDisplayName = aCurItemEntry.sDisplayName + " | " + sUIItemId;
308 aCategoryEntry.sUIItemId = sUIItemId;
309 aCategoryEntry.sClassType = sClassId;
311 if (aCurItemEntry.sClassType == sClassId)
313 sal_Int32 rPos = 0;
314 aCategoryEntry.sDisplayName
315 = OUString::Concat(
316 o3tl::getToken(aCurItemEntry.sDisplayName, rPos, ' ', rPos))
317 + " | " + sUIItemId;
319 aCategoryList.push_back(aCategoryEntry);
320 aCurItemEntry = std::move(aCategoryEntry);
322 else if (sClassId == "svtlo-ManagedMenuButton")
324 sal_Int32 rPos = 1;
325 sSecondVal = sUIItemId.getToken(rPos, ':', rPos);
326 if (!sSecondVal.isEmpty())
328 aCategoryEntry.sDisplayName
329 = aCurItemEntry.sDisplayName + " | " + sSecondVal;
330 aCategoryEntry.sUIItemId = sSecondVal;
331 aCategoryList.push_back(aCategoryEntry);
335 NotebookbarEntries nodeEntries;
336 if (isCategoryAvailable(sClassId, sUIItemId, sActiveCategory, isCategory)
337 || isCategory)
339 isCategory = true;
340 if (sClassId == "GtkMenuItem" || sClassId == "GtkToolButton"
341 || sClassId == "GtkMenuToolButton"
342 || (sClassId == "svtlo-ManagedMenuButton" && sSecondVal.isEmpty()))
344 nodeEntries.sClassId = sClassId;
345 nodeEntries.sUIItemId = sUIItemId;
346 nodeEntries.sDisplayName = sUIItemId;
348 getNodeValue(pNodePtr, nodeEntries);
349 aEntries.push_back(nodeEntries);
351 else if (sClassId == "GtkSeparatorMenuItem" || sClassId == "GtkSeparator")
353 nodeEntries.sClassId = sClassId;
354 nodeEntries.sUIItemId = sUIItemId;
355 nodeEntries.sDisplayName = "Null";
356 nodeEntries.sVisibleValue = "Null";
357 nodeEntries.sActionName = "Null";
358 aEntries.push_back(nodeEntries);
360 else if (sClassId == "sfxlo-PriorityHBox"
361 || sClassId == "sfxlo-PriorityMergedHBox"
362 || sClassId == "svtlo-ManagedMenuButton")
364 nodeEntries.sClassId = sClassId;
365 nodeEntries.sUIItemId = sUIItemId;
366 nodeEntries.sDisplayName
367 = aCategoryList[aCategoryList.size() - 1].sDisplayName;
368 nodeEntries.sVisibleValue = "Null";
369 nodeEntries.sActionName = "Null";
370 aEntries.push_back(nodeEntries);
374 searchNodeandAttribute(aEntries, aCategoryList, sActiveCategory, aCurItemEntry,
375 pNodePtr, isCategory);
377 pNodePtr = pNodePtr->next;
381 void SvxNotebookbarConfigPage::FillFunctionsList(xmlNodePtr pRootNodePtr,
382 std::vector<NotebookbarEntries>& aEntries,
383 std::vector<CategoriesEntries>& aCategoryList,
384 OUString& sActiveCategory)
386 CategoriesEntries aCurItemEntry;
387 searchNodeandAttribute(aEntries, aCategoryList, sActiveCategory, aCurItemEntry, pRootNodePtr,
388 false);
391 void SvxNotebookbarConfigPage::SelectElement()
393 OString sUIFileUIPath = CustomNotebookbarGenerator::getSystemPath(
394 CustomNotebookbarGenerator::getCustomizedUIPath());
395 xmlDocPtr pDoc = xmlParseFile(sUIFileUIPath.getStr());
396 if (!pDoc)
398 sUIFileUIPath = CustomNotebookbarGenerator::getSystemPath(
399 CustomNotebookbarGenerator::getOriginalUIPath());
400 pDoc = xmlParseFile(sUIFileUIPath.getStr());
403 if (!pDoc)
404 return;
405 xmlNodePtr pNodePtr = xmlDocGetRootElement(pDoc);
407 std::vector<NotebookbarEntries> aEntries;
408 std::vector<CategoriesEntries> aCategoryList;
409 OUString sActiveCategory = m_xTopLevelListBox->get_active_id();
410 FillFunctionsList(pNodePtr, aEntries, aCategoryList, sActiveCategory);
412 if (m_xTopLevelListBox->get_count() == 1)
414 for (const auto& rCategory : aCategoryList)
415 m_xTopLevelListBox->append(rCategory.sUIItemId, rCategory.sDisplayName);
417 tools::ULong nStart = 0;
418 if (aEntries[nStart].sClassId == "sfxlo-PriorityHBox"
419 || aEntries[nStart].sClassId == "sfxlo-PriorityMergedHBox")
420 nStart = 1;
422 std::vector<NotebookbarEntries> aTempEntries;
423 for (std::size_t nIdx = nStart; nIdx < aEntries.size(); nIdx++)
425 if (aEntries[nIdx].sClassId == "svtlo-ManagedMenuButton")
427 aTempEntries.push_back(aEntries[nIdx]);
428 sal_Int32 rPos = 1;
429 sActiveCategory = aEntries[nIdx].sUIItemId.getToken(rPos, ':', rPos);
430 FillFunctionsList(pNodePtr, aTempEntries, aCategoryList, sActiveCategory);
432 else
433 aTempEntries.push_back(aEntries[nIdx]);
436 aEntries = std::move(aTempEntries);
438 static_cast<SvxNotebookbarEntriesListBox*>(m_xContentsListBox.get())->GetTooltipMap().clear();
439 weld::TreeView& rTreeView = m_xContentsListBox->get_widget();
440 rTreeView.bulk_insert_for_each(
441 aEntries.size(), [this, &rTreeView, &aEntries](weld::TreeIter& rIter, int nIdx) {
442 if (aEntries[nIdx].sActionName != "Null")
444 if (aEntries[nIdx].sVisibleValue == "True")
446 rTreeView.set_toggle(rIter, TRISTATE_TRUE);
448 else
450 rTreeView.set_toggle(rIter, TRISTATE_FALSE);
453 InsertEntryIntoNotebookbarTabUI(aEntries[nIdx].sClassId, aEntries[nIdx].sDisplayName,
454 aEntries[nIdx].sActionName, rTreeView, rIter);
455 if (aEntries[nIdx].sClassId != u"GtkSeparatorMenuItem"
456 && aEntries[nIdx].sClassId != u"GtkSeparator")
458 static_cast<SvxNotebookbarEntriesListBox*>(m_xContentsListBox.get())
459 ->GetTooltipMap()[aEntries[nIdx].sDisplayName]
460 = aEntries[nIdx].sActionName;
464 aEntries.clear();
466 xmlFreeDoc(pDoc);
469 SvxNotebookbarEntriesListBox::SvxNotebookbarEntriesListBox(std::unique_ptr<weld::TreeView> xParent,
470 SvxConfigPage* pPg)
471 : SvxMenuEntriesListBox(std::move(xParent), pPg)
473 m_xControl->connect_toggled(LINK(this, SvxNotebookbarEntriesListBox, CheckButtonHdl));
474 m_xControl->connect_key_press(Link<const KeyEvent&, bool>());
475 m_xControl->connect_key_press(LINK(this, SvxNotebookbarEntriesListBox, KeyInputHdl));
476 // remove the inherited connect_query_tooltip then add the new one
477 m_xControl->connect_query_tooltip(Link<const weld::TreeIter&, OUString>());
478 m_xControl->connect_query_tooltip(LINK(this, SvxNotebookbarEntriesListBox, QueryTooltip));
481 SvxNotebookbarEntriesListBox::~SvxNotebookbarEntriesListBox() {}
483 static void EditRegistryFile(std::u16string_view sUIItemId, const OUString& sSetEntry,
484 const OUString& sNotebookbarInterface)
486 int nFlag = 0;
487 Sequence<OUString> aOldEntries
488 = CustomNotebookbarGenerator::getCustomizedUIItem(sNotebookbarInterface);
489 Sequence<OUString> aNewEntries(aOldEntries.getLength() + 1);
490 auto pNewEntries = aNewEntries.getArray();
491 for (int nIdx = 0; nIdx < aOldEntries.getLength(); nIdx++)
493 sal_Int32 rPos = 0;
494 std::u16string_view sFirstValue = o3tl::getToken(aOldEntries[nIdx], rPos, ',', rPos);
495 if (sFirstValue == sUIItemId)
497 aOldEntries.getArray()[nIdx] = sSetEntry;
498 nFlag = 1;
499 break;
501 pNewEntries[nIdx] = aOldEntries[nIdx];
504 if (nFlag == 0)
506 pNewEntries[aOldEntries.getLength()] = sSetEntry;
507 CustomNotebookbarGenerator::setCustomizedUIItem(aNewEntries, sNotebookbarInterface);
509 else
511 CustomNotebookbarGenerator::setCustomizedUIItem(aOldEntries, sNotebookbarInterface);
515 void SvxNotebookbarEntriesListBox::ChangedVisibility(int nRow)
517 OUString sUIItemId = m_xControl->get_selected_id();
518 OUString sNotebookbarInterface = getFileName(m_pPage->GetFileName());
520 OUString sVisible;
521 if (m_xControl->get_toggle(nRow) == TRISTATE_TRUE)
522 sVisible = "True";
523 else
524 sVisible = "False";
525 OUString sSetEntries = sUIItemId + ",visible," + sVisible;
526 Sequence<OUString> sSeqOfEntries{ sSetEntries };
527 EditRegistryFile(sUIItemId, sSetEntries, sNotebookbarInterface);
528 CustomNotebookbarGenerator::modifyCustomizedUIFile(sSeqOfEntries);
529 OUString sUIPath = "modules/s" + m_pPage->GetAppName().toAsciiLowerCase() + "/ui/";
530 sfx2::SfxNotebookBar::ReloadNotebookBar(sUIPath);
533 IMPL_LINK(SvxNotebookbarEntriesListBox, CheckButtonHdl, const weld::TreeView::iter_col&, rRowCol,
534 void)
536 ChangedVisibility(m_xControl->get_iter_index_in_parent(rRowCol.first));
539 IMPL_LINK(SvxNotebookbarEntriesListBox, KeyInputHdl, const KeyEvent&, rKeyEvent, bool)
541 if (rKeyEvent.GetKeyCode() == KEY_SPACE)
543 int nRow = m_xControl->get_selected_index();
544 m_xControl->set_toggle(nRow, m_xControl->get_toggle(nRow) == TRISTATE_TRUE ? TRISTATE_FALSE
545 : TRISTATE_TRUE);
546 ChangedVisibility(nRow);
547 return true;
549 return SvxMenuEntriesListBox::KeyInputHdl(rKeyEvent);
552 IMPL_LINK(SvxNotebookbarEntriesListBox, QueryTooltip, const weld::TreeIter&, rIter, OUString)
554 const OUString& rsCommand = m_aTooltipMap[m_xControl->get_id(rIter)];
555 if (rsCommand.isEmpty())
556 return OUString();
557 OUString aModuleName(vcl::CommandInfoProvider::GetModuleIdentifier(m_pPage->GetFrame()));
558 auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(rsCommand, aModuleName);
559 OUString sTooltipLabel = vcl::CommandInfoProvider::GetTooltipForCommand(rsCommand, aProperties,
560 m_pPage->GetFrame());
561 return CuiResId(RID_CUISTR_COMMANDLABEL) + ": "
562 + m_xControl->get_text(rIter).replaceFirst("~", "") + "\n"
563 + CuiResId(RID_CUISTR_COMMANDNAME) + ": " + rsCommand + "\n"
564 + CuiResId(RID_CUISTR_COMMANDTIP) + ": " + sTooltipLabel.replaceFirst("~", "");
567 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */