1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 2 /* 3 * This file is part of the LibreOffice project. 4 * 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/. 8 * 9 * This file incorporates work covered by the following license notice: 10 * 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 . 18 */ 19 20 #include <sal/config.h> 21 22 #include <memory> 23 24 #include "advancedsettings.hxx" 25 #include <advancedsettingsdlg.hxx> 26 #include <dsitems.hxx> 27 #include "DbAdminImpl.hxx" 28 #include "DriverSettings.hxx" 29 #include "optionalboolitem.hxx" 30 #include <dbu_dlg.hxx> 31 32 #include <svl/eitem.hxx> 33 #include <svl/intitem.hxx> 34 #include <svl/stritem.hxx> 35 36 namespace dbaui 37 { 38 39 using ::com::sun::star::uno::Reference; 40 using ::com::sun::star::uno::Any; 41 using ::com::sun::star::uno::XComponentContext; 42 using ::com::sun::star::beans::XPropertySet; 43 using ::com::sun::star::sdbc::XConnection; 44 using ::com::sun::star::sdbc::XDriver; 45 46 // SpecialSettingsPage 47 struct BooleanSettingDesc 48 { 49 std::unique_ptr<weld::CheckButton>& xControl; // the dialog's control which displays this setting 50 OString sControlId; // the widget name of the control in the .ui 51 sal_uInt16 nItemId; // the ID of the item (in an SfxItemSet) which corresponds to this setting 52 bool bInvertedDisplay; // true if and only if the checkbox is checked when the item is sal_False, and vice versa 53 bool bOptionalBool; // type is OptionalBool 54 }; 55 56 // SpecialSettingsPage SpecialSettingsPage(weld::Container * pPage,weld::DialogController * pController,const SfxItemSet & _rCoreAttrs,const DataSourceMetaData & _rDSMeta)57 SpecialSettingsPage::SpecialSettingsPage(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& _rCoreAttrs, const DataSourceMetaData& _rDSMeta) 58 : OGenericAdministrationPage(pPage, pController, "dbaccess/ui/specialsettingspage.ui", "SpecialSettingsPage", _rCoreAttrs) 59 , m_aBooleanSettings { 60 { m_xIsSQL92Check, "usesql92", DSID_SQL92CHECK, false, false }, 61 { m_xAppendTableAlias, "append", DSID_APPEND_TABLE_ALIAS, false, false }, 62 { m_xAsBeforeCorrelationName, "useas", DSID_AS_BEFORE_CORRNAME, false, false }, 63 { m_xEnableOuterJoin, "useoj", DSID_ENABLEOUTERJOIN, false, false }, 64 { m_xIgnoreDriverPrivileges, "ignoreprivs", DSID_IGNOREDRIVER_PRIV, false, false }, 65 { m_xParameterSubstitution, "replaceparams", DSID_PARAMETERNAMESUBST, false, false }, 66 { m_xSuppressVersionColumn, "displayver", DSID_SUPPRESSVERSIONCL, true, false }, 67 { m_xCatalog, "usecatalogname", DSID_CATALOG, false, false }, 68 { m_xSchema, "useschemaname", DSID_SCHEMA, false, false }, 69 { m_xIndexAppendix, "createindex", DSID_INDEXAPPENDIX, false, false }, 70 { m_xDosLineEnds, "eol", DSID_DOSLINEENDS, false, false }, 71 { m_xCheckRequiredFields, "inputchecks", DSID_CHECK_REQUIRED_FIELDS, false, false }, 72 { m_xIgnoreCurrency, "ignorecurrency", DSID_IGNORECURRENCY, false, false }, 73 { m_xEscapeDateTime, "useodbcliterals", DSID_ESCAPE_DATETIME, false, false }, 74 { m_xPrimaryKeySupport, "primarykeys", DSID_PRIMARY_KEY_SUPPORT, false, false }, 75 { m_xRespectDriverResultSetType, "resulttype", DSID_RESPECTRESULTSETTYPE, false, false } } 76 , m_bHasBooleanComparisonMode( _rDSMeta.getFeatureSet().has( DSID_BOOLEANCOMPARISON ) ) 77 , m_bHasMaxRowScan( _rDSMeta.getFeatureSet().has( DSID_MAX_ROW_SCAN ) ) 78 { 79 const FeatureSet& rFeatures( _rDSMeta.getFeatureSet() ); 80 // create all the check boxes for the boolean settings 81 for (auto & booleanSetting : m_aBooleanSettings) 82 { 83 sal_uInt16 nItemId = booleanSetting.nItemId; 84 if ( rFeatures.has( nItemId ) ) 85 { 86 // check whether this must be a tristate check box 87 const SfxPoolItem& rItem = _rCoreAttrs.Get(nItemId); 88 booleanSetting.bOptionalBool = dynamic_cast<const OptionalBoolItem*>(&rItem) != nullptr; 89 booleanSetting.xControl = m_xBuilder->weld_check_button(booleanSetting.sControlId); 90 if (booleanSetting.bOptionalBool) 91 booleanSetting.xControl->connect_toggled(LINK(this, SpecialSettingsPage, OnTriStateToggleHdl)); 92 else 93 booleanSetting.xControl->connect_toggled(LINK(this, SpecialSettingsPage, OnToggleHdl)); 94 booleanSetting.xControl->show(); 95 } 96 } 97 98 // create the controls for the boolean comparison mode 99 if ( m_bHasBooleanComparisonMode ) 100 { 101 m_xBooleanComparisonModeLabel = m_xBuilder->weld_label("comparisonft"); 102 m_xBooleanComparisonMode = m_xBuilder->weld_combo_box("comparison"); 103 m_xBooleanComparisonMode->connect_changed(LINK(this, SpecialSettingsPage, BooleanComparisonSelectHdl)); 104 m_xBooleanComparisonModeLabel->show(); 105 m_xBooleanComparisonMode->show(); 106 } 107 // create the controls for the max row scan 108 if ( m_bHasMaxRowScan ) 109 { 110 m_xMaxRowScanLabel = m_xBuilder->weld_label("rowsft"); 111 m_xMaxRowScan = m_xBuilder->weld_spin_button("rows"); 112 m_xMaxRowScan->connect_value_changed(LINK(this, OGenericAdministrationPage, OnControlSpinButtonModifyHdl)); 113 m_xMaxRowScanLabel->show(); 114 m_xMaxRowScan->show(); 115 } 116 } 117 IMPL_LINK(SpecialSettingsPage,OnTriStateToggleHdl,weld::ToggleButton &,rToggle,void)118 IMPL_LINK(SpecialSettingsPage, OnTriStateToggleHdl, weld::ToggleButton&, rToggle, void) 119 { 120 auto eOldState = m_aTriStates[&rToggle]; 121 switch (eOldState) 122 { 123 case TRISTATE_INDET: 124 rToggle.set_state(TRISTATE_FALSE); 125 break; 126 case TRISTATE_TRUE: 127 rToggle.set_state(TRISTATE_INDET); 128 break; 129 case TRISTATE_FALSE: 130 rToggle.set_state(TRISTATE_TRUE); 131 break; 132 } 133 m_aTriStates[&rToggle] = rToggle.get_state(); 134 OnToggleHdl(rToggle); 135 } 136 IMPL_LINK(SpecialSettingsPage,OnToggleHdl,weld::ToggleButton &,rBtn,void)137 IMPL_LINK(SpecialSettingsPage, OnToggleHdl, weld::ToggleButton&, rBtn, void) 138 { 139 if (&rBtn == m_xAppendTableAlias.get() && m_xAsBeforeCorrelationName) 140 { 141 // make m_xAsBeforeCorrelationName depend on m_xAppendTableAlias 142 m_xAsBeforeCorrelationName->set_sensitive(m_xAppendTableAlias->get_active()); 143 } 144 OnControlModifiedButtonClick(rBtn); 145 } 146 IMPL_LINK(SpecialSettingsPage,BooleanComparisonSelectHdl,weld::ComboBox &,rControl,void)147 IMPL_LINK(SpecialSettingsPage, BooleanComparisonSelectHdl, weld::ComboBox&, rControl, void) 148 { 149 callModifiedHdl(&rControl); 150 } 151 ~SpecialSettingsPage()152 SpecialSettingsPage::~SpecialSettingsPage() 153 { 154 } 155 fillWindows(std::vector<std::unique_ptr<ISaveValueWrapper>> & _rControlList)156 void SpecialSettingsPage::fillWindows( std::vector< std::unique_ptr<ISaveValueWrapper> >& _rControlList ) 157 { 158 if ( m_bHasBooleanComparisonMode ) 159 { 160 _rControlList.emplace_back(new ODisableWidgetWrapper<weld::Label>(m_xBooleanComparisonModeLabel.get())); 161 } 162 if ( m_bHasMaxRowScan ) 163 { 164 _rControlList.emplace_back(new ODisableWidgetWrapper<weld::Label>(m_xMaxRowScanLabel.get())); 165 } 166 } 167 fillControls(std::vector<std::unique_ptr<ISaveValueWrapper>> & _rControlList)168 void SpecialSettingsPage::fillControls(std::vector< std::unique_ptr<ISaveValueWrapper> >& _rControlList) 169 { 170 for (auto const& booleanSetting : m_aBooleanSettings) 171 { 172 if (booleanSetting.xControl) 173 { 174 _rControlList.emplace_back(new OSaveValueWidgetWrapper<weld::ToggleButton>(booleanSetting.xControl.get())); 175 } 176 } 177 178 if ( m_bHasBooleanComparisonMode ) 179 _rControlList.emplace_back(new OSaveValueWidgetWrapper<weld::ComboBox>(m_xBooleanComparisonMode.get())); 180 if ( m_bHasMaxRowScan ) 181 _rControlList.emplace_back(new OSaveValueWidgetWrapper<weld::SpinButton>(m_xMaxRowScan.get())); 182 } 183 implInitControls(const SfxItemSet & _rSet,bool _bSaveValue)184 void SpecialSettingsPage::implInitControls(const SfxItemSet& _rSet, bool _bSaveValue) 185 { 186 // check whether or not the selection is invalid or readonly (invalid implies readonly, but not vice versa) 187 bool bValid, bReadonly; 188 getFlags( _rSet, bValid, bReadonly ); 189 190 if ( !bValid ) 191 { 192 OGenericAdministrationPage::implInitControls(_rSet, _bSaveValue); 193 return; 194 } 195 196 m_aTriStates.clear(); 197 198 // the boolean items 199 for (auto const& booleanSetting : m_aBooleanSettings) 200 { 201 if (!booleanSetting.xControl) 202 continue; 203 204 bool bTriState = false; 205 206 boost::optional<bool> aValue; 207 208 const SfxPoolItem* pItem = _rSet.GetItem<SfxPoolItem>(booleanSetting.nItemId); 209 if (const SfxBoolItem *pBoolItem = dynamic_cast<const SfxBoolItem*>( pItem) ) 210 { 211 aValue = pBoolItem->GetValue(); 212 } 213 else if (const OptionalBoolItem *pOptionalItem = dynamic_cast<const OptionalBoolItem*>( pItem) ) 214 { 215 aValue = pOptionalItem->GetFullValue(); 216 bTriState = true; 217 } 218 else 219 OSL_FAIL( "SpecialSettingsPage::implInitControls: unknown boolean item type!" ); 220 221 if ( !aValue ) 222 { 223 booleanSetting.xControl->set_state(TRISTATE_INDET); 224 } 225 else 226 { 227 bool bValue = *aValue; 228 if ( booleanSetting.bInvertedDisplay ) 229 bValue = !bValue; 230 booleanSetting.xControl->set_active(bValue); 231 } 232 if (bTriState) 233 m_aTriStates[booleanSetting.xControl.get()] = booleanSetting.xControl->get_state(); 234 } 235 236 if (m_xAppendTableAlias && m_xAsBeforeCorrelationName) 237 { 238 // make m_xAsBeforeCorrelationName depend on m_xAppendTableAlias 239 m_xAsBeforeCorrelationName->set_sensitive(m_xAppendTableAlias->get_active()); 240 } 241 242 // the non-boolean items 243 if ( m_bHasBooleanComparisonMode ) 244 { 245 const SfxInt32Item* pBooleanComparison = _rSet.GetItem<SfxInt32Item>(DSID_BOOLEANCOMPARISON); 246 m_xBooleanComparisonMode->set_active(static_cast<sal_uInt16>(pBooleanComparison->GetValue())); 247 } 248 249 if ( m_bHasMaxRowScan ) 250 { 251 const SfxInt32Item* pMaxRowScan = _rSet.GetItem<SfxInt32Item>(DSID_MAX_ROW_SCAN); 252 m_xMaxRowScan->set_value(pMaxRowScan->GetValue()); 253 } 254 255 OGenericAdministrationPage::implInitControls(_rSet, _bSaveValue); 256 } 257 FillItemSet(SfxItemSet * _rSet)258 bool SpecialSettingsPage::FillItemSet( SfxItemSet* _rSet ) 259 { 260 bool bChangedSomething = false; 261 262 // the boolean items 263 for (auto const& booleanSetting : m_aBooleanSettings) 264 { 265 if (!booleanSetting.xControl) 266 continue; 267 fillBool(*_rSet, booleanSetting.xControl.get(), booleanSetting.nItemId, booleanSetting.bOptionalBool, bChangedSomething, booleanSetting.bInvertedDisplay); 268 } 269 270 // the non-boolean items 271 if ( m_bHasBooleanComparisonMode ) 272 { 273 if (m_xBooleanComparisonMode->get_value_changed_from_saved()) 274 { 275 _rSet->Put(SfxInt32Item(DSID_BOOLEANCOMPARISON, m_xBooleanComparisonMode->get_active())); 276 bChangedSomething = true; 277 } 278 } 279 if ( m_bHasMaxRowScan ) 280 { 281 fillInt32(*_rSet,m_xMaxRowScan.get(),DSID_MAX_ROW_SCAN,bChangedSomething); 282 } 283 return bChangedSomething; 284 } 285 286 // GeneratedValuesPage GeneratedValuesPage(weld::Container * pPage,weld::DialogController * pController,const SfxItemSet & _rCoreAttrs)287 GeneratedValuesPage::GeneratedValuesPage(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& _rCoreAttrs) 288 : OGenericAdministrationPage(pPage, pController, "dbaccess/ui/generatedvaluespage.ui", "GeneratedValuesPage", _rCoreAttrs) 289 , m_xAutoRetrievingEnabled(m_xBuilder->weld_check_button("autoretrieve")) 290 , m_xGrid(m_xBuilder->weld_widget("grid")) 291 , m_xAutoIncrementLabel(m_xBuilder->weld_label("statementft")) 292 , m_xAutoIncrement(m_xBuilder->weld_entry("statement")) 293 , m_xAutoRetrievingLabel(m_xBuilder->weld_label("queryft")) 294 , m_xAutoRetrieving(m_xBuilder->weld_entry("query")) 295 { 296 m_xAutoRetrievingEnabled->connect_toggled(LINK(this, GeneratedValuesPage, OnAutoToggleHdl)); 297 m_xAutoIncrement->connect_changed(LINK(this, OGenericAdministrationPage, OnControlEntryModifyHdl)); 298 m_xAutoRetrieving->connect_changed(LINK(this, OGenericAdministrationPage, OnControlEntryModifyHdl)); 299 } 300 IMPL_LINK(GeneratedValuesPage,OnAutoToggleHdl,weld::ToggleButton &,rBtn,void)301 IMPL_LINK(GeneratedValuesPage, OnAutoToggleHdl, weld::ToggleButton&, rBtn, void) 302 { 303 m_xGrid->set_sensitive(rBtn.get_active()); 304 OnControlModifiedButtonClick(rBtn); 305 } 306 ~GeneratedValuesPage()307 GeneratedValuesPage::~GeneratedValuesPage() 308 { 309 } 310 fillWindows(std::vector<std::unique_ptr<ISaveValueWrapper>> & _rControlList)311 void GeneratedValuesPage::fillWindows( std::vector< std::unique_ptr<ISaveValueWrapper> >& _rControlList ) 312 { 313 _rControlList.emplace_back(new ODisableWidgetWrapper<weld::Widget>(m_xContainer.get())); 314 } 315 fillControls(std::vector<std::unique_ptr<ISaveValueWrapper>> & _rControlList)316 void GeneratedValuesPage::fillControls( std::vector< std::unique_ptr<ISaveValueWrapper> >& _rControlList ) 317 { 318 _rControlList.emplace_back( new OSaveValueWidgetWrapper<weld::ToggleButton>( m_xAutoRetrievingEnabled.get() ) ); 319 _rControlList.emplace_back( new OSaveValueWidgetWrapper<weld::Entry>( m_xAutoIncrement.get() ) ); 320 _rControlList.emplace_back( new OSaveValueWidgetWrapper<weld::Entry>( m_xAutoRetrieving.get() ) ); 321 } 322 implInitControls(const SfxItemSet & _rSet,bool _bSaveValue)323 void GeneratedValuesPage::implInitControls( const SfxItemSet& _rSet, bool _bSaveValue ) 324 { 325 // check whether or not the selection is invalid or readonly (invalid implies readonly, but not vice versa) 326 bool bValid, bReadonly; 327 getFlags(_rSet, bValid, bReadonly); 328 329 // collect the items 330 const SfxStringItem* pAutoIncrementItem = _rSet.GetItem<SfxStringItem>(DSID_AUTOINCREMENTVALUE); 331 const SfxStringItem* pAutoRetrieveValueItem = _rSet.GetItem<SfxStringItem>(DSID_AUTORETRIEVEVALUE); 332 const SfxBoolItem* pAutoRetrieveEnabledItem = _rSet.GetItem<SfxBoolItem>(DSID_AUTORETRIEVEENABLED); 333 334 // forward the values to the controls 335 if (bValid) 336 { 337 bool bEnabled = pAutoRetrieveEnabledItem->GetValue(); 338 m_xAutoRetrievingEnabled->set_active(bEnabled); 339 340 m_xAutoIncrement->set_text(pAutoIncrementItem->GetValue()); 341 m_xAutoIncrement->save_value(); 342 m_xAutoRetrieving->set_text(pAutoRetrieveValueItem->GetValue()); 343 m_xAutoRetrieving->save_value(); 344 } 345 OGenericAdministrationPage::implInitControls( _rSet, _bSaveValue ); 346 } 347 FillItemSet(SfxItemSet * _rSet)348 bool GeneratedValuesPage::FillItemSet(SfxItemSet* _rSet) 349 { 350 bool bChangedSomething = false; 351 352 fillString( *_rSet, m_xAutoIncrement.get(), DSID_AUTOINCREMENTVALUE, bChangedSomething ); 353 fillBool( *_rSet, m_xAutoRetrievingEnabled.get(), DSID_AUTORETRIEVEENABLED, false, bChangedSomething ); 354 fillString( *_rSet, m_xAutoRetrieving.get(), DSID_AUTORETRIEVEVALUE, bChangedSomething ); 355 356 return bChangedSomething; 357 } 358 359 // AdvancedSettingsDialog AdvancedSettingsDialog(weld::Window * pParent,SfxItemSet * _pItems,const Reference<XComponentContext> & _rxContext,const Any & _aDataSourceName)360 AdvancedSettingsDialog::AdvancedSettingsDialog(weld::Window* pParent, SfxItemSet* _pItems, 361 const Reference< XComponentContext >& _rxContext, const Any& _aDataSourceName ) 362 : SfxTabDialogController(pParent, "dbaccess/ui/advancedsettingsdialog.ui", "AdvancedSettingsDialog", _pItems) 363 { 364 m_pImpl.reset(new ODbDataSourceAdministrationHelper(_rxContext, m_xDialog.get(), pParent, this)); 365 m_pImpl->setDataSourceOrName(_aDataSourceName); 366 Reference< XPropertySet > xDatasource = m_pImpl->getCurrentDataSource(); 367 m_pImpl->translateProperties(xDatasource, *_pItems); 368 SetInputSet(_pItems); 369 // propagate this set as our new input set and reset the example set 370 m_xExampleSet.reset(new SfxItemSet(*GetInputSetImpl())); 371 372 const OUString eType = dbaui::ODbDataSourceAdministrationHelper::getDatasourceType(*_pItems); 373 374 DataSourceMetaData aMeta( eType ); 375 const FeatureSet& rFeatures( aMeta.getFeatureSet() ); 376 377 // auto-generated values? 378 if (rFeatures.supportsGeneratedValues()) 379 AddTabPage("generated", ODriversSettings::CreateGeneratedValuesPage, nullptr); 380 else 381 RemoveTabPage("generated"); 382 383 // any "special settings"? 384 if (rFeatures.supportsAnySpecialSetting()) 385 AddTabPage("special", ODriversSettings::CreateSpecialSettingsPage, nullptr); 386 else 387 RemoveTabPage("special"); 388 389 // remove the reset button - it's meaning is much too ambiguous in this dialog 390 RemoveResetButton(); 391 } 392 ~AdvancedSettingsDialog()393 AdvancedSettingsDialog::~AdvancedSettingsDialog() 394 { 395 SetInputSet(nullptr); 396 } 397 doesHaveAnyAdvancedSettings(const OUString & _sURL)398 bool AdvancedSettingsDialog::doesHaveAnyAdvancedSettings( const OUString& _sURL ) 399 { 400 DataSourceMetaData aMeta( _sURL ); 401 const FeatureSet& rFeatures( aMeta.getFeatureSet() ); 402 return rFeatures.supportsGeneratedValues() || rFeatures.supportsAnySpecialSetting(); 403 } 404 Ok()405 short AdvancedSettingsDialog::Ok() 406 { 407 short nRet = SfxTabDialogController::Ok(); 408 if ( nRet == RET_OK ) 409 { 410 m_xExampleSet->Put(*GetOutputItemSet()); 411 m_pImpl->saveChanges(*m_xExampleSet); 412 } 413 return nRet; 414 } 415 PageCreated(const OString & rId,SfxTabPage & _rPage)416 void AdvancedSettingsDialog::PageCreated(const OString& rId, SfxTabPage& _rPage) 417 { 418 // register ourself as modified listener 419 static_cast<OGenericAdministrationPage&>(_rPage).SetServiceFactory( getORB() ); 420 static_cast<OGenericAdministrationPage&>(_rPage).SetAdminDialog(this,this); 421 SfxTabDialogController::PageCreated(rId, _rPage); 422 } 423 getOutputSet() const424 const SfxItemSet* AdvancedSettingsDialog::getOutputSet() const 425 { 426 return m_xExampleSet.get(); 427 } 428 getWriteOutputSet()429 SfxItemSet* AdvancedSettingsDialog::getWriteOutputSet() 430 { 431 return m_xExampleSet.get(); 432 } 433 createConnection()434 std::pair< Reference< XConnection >, bool > AdvancedSettingsDialog::createConnection() 435 { 436 return m_pImpl->createConnection(); 437 } 438 getORB() const439 Reference< XComponentContext > AdvancedSettingsDialog::getORB() const 440 { 441 return m_pImpl->getORB(); 442 } 443 getDriver()444 Reference< XDriver > AdvancedSettingsDialog::getDriver() 445 { 446 return m_pImpl->getDriver(); 447 } 448 getDatasourceType(const SfxItemSet & _rSet) const449 OUString AdvancedSettingsDialog::getDatasourceType(const SfxItemSet& _rSet) const 450 { 451 return dbaui::ODbDataSourceAdministrationHelper::getDatasourceType(_rSet); 452 } 453 clearPassword()454 void AdvancedSettingsDialog::clearPassword() 455 { 456 m_pImpl->clearPassword(); 457 } 458 setTitle(const OUString & _sTitle)459 void AdvancedSettingsDialog::setTitle(const OUString& _sTitle) 460 { 461 m_xDialog->set_title(_sTitle); 462 } 463 enableConfirmSettings(bool)464 void AdvancedSettingsDialog::enableConfirmSettings( bool ) {} 465 saveDatasource()466 void AdvancedSettingsDialog::saveDatasource() 467 { 468 PrepareLeaveCurrentPage(); 469 } 470 471 } // namespace dbaui 472 473 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ 474