1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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>
31 #include <strings.hrc>
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
)
56 else if ((sClassId
== u
"GtkMenu" || sClassId
== u
"GtkGrid") && sUIItemId
!= sActiveCategory
)
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
);
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
;
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
);
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
);
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
);
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
);
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
&)
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);
233 auto xImage
= GetSaveInData()->GetImage(sUIItemCommand
);
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
;
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
);
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
);
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
;
278 if (pNodePtr
->type
== XML_ELEMENT_NODE
)
280 const char* cNodeName
= reinterpret_cast<const char*>(pNodePtr
->name
);
281 if (strcmp(cNodeName
, "object") == 0)
285 xmlChar
* UriValue
= xmlGetProp(pNodePtr
, reinterpret_cast<const xmlChar
*>("id"));
286 const char* cUIItemID
= reinterpret_cast<const char*>(UriValue
);
287 OUString sUIItemId
= charToString(cUIItemID
);
290 UriValue
= xmlGetProp(pNodePtr
, reinterpret_cast<const xmlChar
*>("class"));
291 const char* cClassId
= reinterpret_cast<const char*>(UriValue
);
292 OUString sClassId
= charToString(cClassId
);
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
)
314 aCategoryEntry
.sDisplayName
316 o3tl::getToken(aCurItemEntry
.sDisplayName
, rPos
, ' ', rPos
))
319 aCategoryList
.push_back(aCategoryEntry
);
320 aCurItemEntry
= std::move(aCategoryEntry
);
322 else if (sClassId
== "svtlo-ManagedMenuButton")
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
)
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
,
391 void SvxNotebookbarConfigPage::SelectElement()
393 OString sUIFileUIPath
= CustomNotebookbarGenerator::getSystemPath(
394 CustomNotebookbarGenerator::getCustomizedUIPath());
395 xmlDocPtr pDoc
= xmlParseFile(sUIFileUIPath
.getStr());
398 sUIFileUIPath
= CustomNotebookbarGenerator::getSystemPath(
399 CustomNotebookbarGenerator::getOriginalUIPath());
400 pDoc
= xmlParseFile(sUIFileUIPath
.getStr());
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")
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
]);
429 sActiveCategory
= aEntries
[nIdx
].sUIItemId
.getToken(rPos
, ':', rPos
);
430 FillFunctionsList(pNodePtr
, aTempEntries
, aCategoryList
, sActiveCategory
);
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
);
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
;
469 SvxNotebookbarEntriesListBox::SvxNotebookbarEntriesListBox(std::unique_ptr
<weld::TreeView
> xParent
,
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
)
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
++)
494 std::u16string_view sFirstValue
= o3tl::getToken(aOldEntries
[nIdx
], rPos
, ',', rPos
);
495 if (sFirstValue
== sUIItemId
)
497 aOldEntries
.getArray()[nIdx
] = sSetEntry
;
501 pNewEntries
[nIdx
] = aOldEntries
[nIdx
];
506 pNewEntries
[aOldEntries
.getLength()] = sSetEntry
;
507 CustomNotebookbarGenerator::setCustomizedUIItem(aNewEntries
, sNotebookbarInterface
);
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());
521 if (m_xControl
->get_toggle(nRow
) == TRISTATE_TRUE
)
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
,
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
546 ChangedVisibility(nRow
);
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())
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: */