/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace dbaui { using namespace ::com::sun::star::uno; using namespace ::com::sun::star::sdb; using namespace ::com::sun::star::lang; using namespace ::com::sun::star::sdbc; using namespace ::com::sun::star::sdbcx; using namespace ::com::sun::star::container; using namespace ::com::sun::star::sdb::application; using namespace ::dbtools; using namespace ::comphelper; namespace DatabaseObject = ::com::sun::star::sdb::application::DatabaseObject; namespace DatabaseObjectContainer = ::com::sun::star::sdb::application::DatabaseObjectContainer; // OTableTreeListBox OTableTreeListBox::OTableTreeListBox(std::unique_ptr xTreeView, bool bShowToggles) : TreeListBox(std::move(xTreeView), true) , m_xImageProvider(new ImageProvider) , m_bVirtualRoot(false) , m_bNoEmptyFolders(false) , m_bShowToggles(bShowToggles) { if (m_bShowToggles) m_xTreeView->enable_toggle_buttons(weld::ColumnToggleType::Check); } bool OTableTreeListBox::isFolderEntry(const weld::TreeIter& rEntry) const { sal_Int32 nEntryType = m_xTreeView->get_id(rEntry).toInt32(); return ( nEntryType == DatabaseObjectContainer::TABLES ) || ( nEntryType == DatabaseObjectContainer::CATALOG ) || ( nEntryType == DatabaseObjectContainer::SCHEMA ); } void OTableTreeListBox::implOnNewConnection( const Reference< XConnection >& _rxConnection ) { m_xConnection = _rxConnection; m_xImageProvider.reset( new ImageProvider( m_xConnection ) ); } void OTableTreeListBox::UpdateTableList( const Reference< XConnection >& _rxConnection ) { Sequence< OUString > sTables, sViews; OUString sCurrentActionError; try { Reference< XTablesSupplier > xTableSupp( _rxConnection, UNO_QUERY_THROW ); sCurrentActionError = DBA_RES(STR_NOTABLEINFO); Reference< XNameAccess > xTables,xViews; Reference< XViewsSupplier > xViewSupp( _rxConnection, UNO_QUERY ); if ( xViewSupp.is() ) { xViews = xViewSupp->getViews(); if (xViews.is()) sViews = xViews->getElementNames(); } xTables = xTableSupp->getTables(); if (xTables.is()) sTables = xTables->getElementNames(); } catch(RuntimeException&) { TOOLS_WARN_EXCEPTION( "dbaccess", "OTableTreeListBox::UpdateTableList"); } catch ( const SQLException& ) { throw; } catch(Exception&) { css::uno::Any anyEx = cppu::getCaughtException(); // a non-SQLException exception occurred ... simply throw an SQLException throw SQLException(sCurrentActionError, nullptr, "", 0, anyEx); } UpdateTableList( _rxConnection, sTables, sViews ); } namespace { struct OViewSetter { const Sequence< OUString> m_aViews; ::comphelper::UStringMixEqual m_aEqualFunctor; OViewSetter(const Sequence< OUString>& _rViews,bool _bCase) : m_aViews(_rViews),m_aEqualFunctor(_bCase){} OTableTreeListBox::TNames::value_type operator() (const OUString& name) { OTableTreeListBox::TNames::value_type aRet; aRet.first = name; aRet.second = std::any_of(m_aViews.begin(), m_aViews.end(), [this, &name](const OUString& lhs) { return m_aEqualFunctor(lhs, name); } ); return aRet; } }; } void OTableTreeListBox::UpdateTableList( const Reference< XConnection >& _rxConnection, const Sequence< OUString>& _rTables, const Sequence< OUString>& _rViews ) { TNames aTables; aTables.resize(_rTables.getLength()); try { Reference< XDatabaseMetaData > xMeta( _rxConnection->getMetaData(), UNO_SET_THROW ); std::transform( _rTables.begin(), _rTables.end(), aTables.begin(), OViewSetter( _rViews, xMeta->supportsMixedCaseQuotedIdentifiers() ) ); } catch(Exception&) { DBG_UNHANDLED_EXCEPTION("dbaccess"); } UpdateTableList( _rxConnection, aTables ); } namespace { std::vector< OUString > lcl_getMetaDataStrings_throw( const Reference< XResultSet >& _rxMetaDataResult, sal_Int32 _nColumnIndex ) { std::vector< OUString > aStrings; Reference< XRow > xRow( _rxMetaDataResult, UNO_QUERY_THROW ); while ( _rxMetaDataResult->next() ) aStrings.push_back( xRow->getString( _nColumnIndex ) ); return aStrings; } bool lcl_shouldDisplayEmptySchemasAndCatalogs( const Reference< XConnection >& _rxConnection ) { ::dbtools::DatabaseMetaData aMetaData( _rxConnection ); return aMetaData.displayEmptyTableFolders(); } } void OTableTreeListBox::UpdateTableList( const Reference< XConnection >& _rxConnection, const TNames& _rTables ) { implOnNewConnection( _rxConnection ); // throw away all the old stuff m_xTreeView->clear(); m_xTreeView->make_unsorted(); try { if (haveVirtualRoot()) { OUString sRootEntryText; if ( std::none_of(_rTables.begin(),_rTables.end(), [] (const TNames::value_type& name) { return !name.second; }) ) sRootEntryText = DBA_RES(STR_ALL_TABLES); else if ( std::none_of(_rTables.begin(),_rTables.end(), [] (const TNames::value_type& name) { return name.second; }) ) sRootEntryText = DBA_RES(STR_ALL_VIEWS); else sRootEntryText = DBA_RES(STR_ALL_TABLES_AND_VIEWS); OUString sId(OUString::number(DatabaseObjectContainer::TABLES)); OUString sImageId = ImageProvider::getFolderImageId(DatabaseObject::TABLE); std::unique_ptr xRet(m_xTreeView->make_iterator()); m_xTreeView->insert(nullptr, -1, nullptr, &sId, nullptr, nullptr, false, xRet.get()); m_xTreeView->set_image(*xRet, sImageId, -1); if (m_bShowToggles) m_xTreeView->set_toggle(*xRet, TRISTATE_FALSE); m_xTreeView->set_text(*xRet, sRootEntryText, 0); m_xTreeView->set_text_emphasis(*xRet, false, 0); } if ( _rTables.empty() ) // nothing to do (besides inserting the root entry) return; // get the table/view names Reference< XDatabaseMetaData > xMeta( _rxConnection->getMetaData(), UNO_SET_THROW ); for (auto const& table : _rTables) { // add the entry implAddEntry(xMeta, table.first, false); } if ( !m_bNoEmptyFolders && lcl_shouldDisplayEmptySchemasAndCatalogs( _rxConnection ) ) { bool bSupportsCatalogs = xMeta->supportsCatalogsInDataManipulation(); bool bSupportsSchemas = xMeta->supportsSchemasInDataManipulation(); if ( bSupportsCatalogs || bSupportsSchemas ) { // we display empty catalogs if the DB supports catalogs, and they're noted at the beginning of a // composed name. Otherwise, we display empty schematas. (also see the tree structure explained in // implAddEntry) bool bCatalogs = bSupportsCatalogs && xMeta->isCatalogAtStart(); std::vector< OUString > aFolderNames( lcl_getMetaDataStrings_throw( bCatalogs ? xMeta->getCatalogs() : xMeta->getSchemas(), 1 ) ); sal_Int32 nFolderType = bCatalogs ? DatabaseObjectContainer::CATALOG : DatabaseObjectContainer::SCHEMA; OUString sImageId = ImageProvider::getFolderImageId(DatabaseObject::TABLE); std::unique_ptr xRootEntry(getAllObjectsEntry()); std::unique_ptr xRet(m_xTreeView->make_iterator()); for (auto const& folderName : aFolderNames) { std::unique_ptr xFolder(GetEntryPosByName(folderName, xRootEntry.get())); if (!xFolder) { OUString sId(OUString::number(nFolderType)); m_xTreeView->insert(xRootEntry.get(), -1, nullptr, &sId, nullptr, nullptr, false, xRet.get()); m_xTreeView->set_image(*xRet, sImageId, -1); if (m_bShowToggles) m_xTreeView->set_toggle(*xRet, TRISTATE_FALSE); m_xTreeView->set_text(*xRet, folderName, 0); m_xTreeView->set_text_emphasis(*xRet, false, 0); } } } } } catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("dbaccess"); } m_xTreeView->make_sorted(); } bool OTableTreeListBox::isWildcardChecked(const weld::TreeIter& rEntry) { return m_xTreeView->get_text_emphasis(rEntry, 0); } void OTableTreeListBox::checkWildcard(const weld::TreeIter& rEntry) { if (!m_bShowToggles) return; m_xTreeView->set_toggle(rEntry, TRISTATE_TRUE); checkedButton_noBroadcast(rEntry); } std::unique_ptr OTableTreeListBox::getAllObjectsEntry() const { if (!haveVirtualRoot()) return nullptr; auto xRet = m_xTreeView->make_iterator(); if (!m_xTreeView->get_iter_first(*xRet)) return nullptr; return xRet; } void OTableTreeListBox::checkedButton_noBroadcast(const weld::TreeIter& rEntry) { if (!m_bShowToggles) return; TriState eState = m_xTreeView->get_toggle(rEntry); OSL_ENSURE(TRISTATE_INDET != eState, "OTableTreeListBox::CheckButtonHdl: user action which lead to TRISTATE?"); if (m_xTreeView->iter_has_child(rEntry)) // if it has children, check those too { std::unique_ptr xChildEntry(m_xTreeView->make_iterator(&rEntry)); std::unique_ptr xSiblingEntry(m_xTreeView->make_iterator(&rEntry)); bool bChildEntry = m_xTreeView->iter_next(*xChildEntry); bool bSiblingEntry = m_xTreeView->iter_next_sibling(*xSiblingEntry); while (bChildEntry && (!bSiblingEntry || !xChildEntry->equal(*xSiblingEntry))) { m_xTreeView->set_toggle(*xChildEntry, eState); bChildEntry = m_xTreeView->iter_next(*xChildEntry); } } if (m_xTreeView->is_selected(rEntry)) { m_xTreeView->selected_foreach([this, eState](weld::TreeIter& rSelected){ m_xTreeView->set_toggle(rSelected, eState); if (m_xTreeView->iter_has_child(rSelected)) // if it has children, check those too { std::unique_ptr xChildEntry(m_xTreeView->make_iterator(&rSelected)); std::unique_ptr xSiblingEntry(m_xTreeView->make_iterator(&rSelected)); bool bChildEntry = m_xTreeView->iter_next(*xChildEntry); bool bSiblingEntry = m_xTreeView->iter_next_sibling(*xSiblingEntry); while (bChildEntry && (!bSiblingEntry || !xChildEntry->equal(*xSiblingEntry))) { m_xTreeView->set_toggle(*xChildEntry, eState); bChildEntry = m_xTreeView->iter_next(*xChildEntry); } } return false; }); } CheckButtons(); // if an entry has children, it makes a difference if the entry is checked // because all children are checked or if the user checked it explicitly. // So we track explicit (un)checking implEmphasize(rEntry, eState == TRISTATE_TRUE); } void OTableTreeListBox::implEmphasize(const weld::TreeIter& rEntry, bool _bChecked, bool _bUpdateDescendants, bool _bUpdateAncestors) { // special emphasizing handling for the "all objects" entry bool bAllObjectsEntryAffected = haveVirtualRoot() && (getAllObjectsEntry()->equal(rEntry)); if ( m_xTreeView->iter_has_child(rEntry) // the entry has children || bAllObjectsEntryAffected // or it is the "all objects" entry ) { m_xTreeView->set_text_emphasis(rEntry, _bChecked, 0); } if (_bUpdateDescendants) { std::unique_ptr xChild(m_xTreeView->make_iterator(&rEntry)); // remove the mark for all children of the checked entry bool bChildLoop = m_xTreeView->iter_children(*xChild); while (bChildLoop) { if (m_xTreeView->iter_has_child(*xChild)) implEmphasize(*xChild, false, true, false); bChildLoop = m_xTreeView->iter_next_sibling(*xChild); } } if (_bUpdateAncestors) { std::unique_ptr xParent(m_xTreeView->make_iterator(&rEntry)); // remove the mark for all ancestors of the entry if (m_xTreeView->iter_parent(*xParent)) implEmphasize(*xParent, false, false); } } std::unique_ptr OTableTreeListBox::implAddEntry( const Reference< XDatabaseMetaData >& _rxMeta, const OUString& _rTableName, bool _bCheckName ) { OSL_PRECOND( _rxMeta.is(), "OTableTreeListBox::implAddEntry: invalid meta data!" ); if ( !_rxMeta.is() ) return nullptr; // split the complete name into its components OUString sCatalog, sSchema, sName; qualifiedNameComponents( _rxMeta, _rTableName, sCatalog, sSchema, sName, ::dbtools::EComposeRule::InDataManipulation ); std::unique_ptr xParentEntry(getAllObjectsEntry()); // if the DB uses catalog at the start of identifiers, then our hierarchy is // catalog // +- schema // +- table // else it is // schema // +- catalog // +- table bool bCatalogAtStart = _rxMeta->isCatalogAtStart(); const OUString& rFirstName = bCatalogAtStart ? sCatalog : sSchema; const sal_Int32 nFirstFolderType = bCatalogAtStart ? DatabaseObjectContainer::CATALOG : DatabaseObjectContainer::SCHEMA; const OUString& rSecondName = bCatalogAtStart ? sSchema : sCatalog; const sal_Int32 nSecondFolderType = bCatalogAtStart ? DatabaseObjectContainer::SCHEMA : DatabaseObjectContainer::CATALOG; if ( !rFirstName.isEmpty() ) { std::unique_ptr xFolder(GetEntryPosByName(rFirstName, xParentEntry.get())); if (!xFolder) { xFolder = m_xTreeView->make_iterator(); OUString sId(OUString::number(nFirstFolderType)); OUString sImageId = ImageProvider::getFolderImageId(DatabaseObject::TABLE); m_xTreeView->insert(xParentEntry.get(), -1, nullptr, &sId, nullptr, nullptr, false, xFolder.get()); m_xTreeView->set_image(*xFolder, sImageId, -1); if (m_bShowToggles) m_xTreeView->set_toggle(*xFolder, TRISTATE_FALSE); m_xTreeView->set_text(*xFolder, rFirstName, 0); m_xTreeView->set_text_emphasis(*xFolder, false, 0); } xParentEntry = std::move(xFolder); } if ( !rSecondName.isEmpty() ) { std::unique_ptr xFolder(GetEntryPosByName(rSecondName, xParentEntry.get())); if (!xFolder) { xFolder = m_xTreeView->make_iterator(); OUString sId(OUString::number(nSecondFolderType)); OUString sImageId = ImageProvider::getFolderImageId(DatabaseObject::TABLE); m_xTreeView->insert(xParentEntry.get(), -1, nullptr, &sId, nullptr, nullptr, false, xFolder.get()); m_xTreeView->set_image(*xFolder, sImageId, -1); if (m_bShowToggles) m_xTreeView->set_toggle(*xFolder, TRISTATE_FALSE); m_xTreeView->set_text(*xFolder, rSecondName, 0); m_xTreeView->set_text_emphasis(*xFolder, false, 0); } xParentEntry = std::move(xFolder); } if (!_bCheckName || !GetEntryPosByName(sName, xParentEntry.get())) { std::unique_ptr xEntry = m_xTreeView->make_iterator(); m_xTreeView->insert(xParentEntry.get(), -1, nullptr, nullptr, nullptr, nullptr, false, xEntry.get()); auto xGraphic = m_xImageProvider->getXGraphic(_rTableName, DatabaseObject::TABLE); if (xGraphic.is()) m_xTreeView->set_image(*xEntry, xGraphic, -1); else { OUString sImageId(m_xImageProvider->getImageId(_rTableName, DatabaseObject::TABLE)); m_xTreeView->set_image(*xEntry, sImageId, -1); } if (m_bShowToggles) m_xTreeView->set_toggle(*xEntry, TRISTATE_FALSE); m_xTreeView->set_text(*xEntry, sName, 0); m_xTreeView->set_text_emphasis(*xEntry, false, 0); return xEntry; } return nullptr; } NamedDatabaseObject OTableTreeListBox::describeObject(const weld::TreeIter& rEntry) { NamedDatabaseObject aObject; sal_Int32 nEntryType = m_xTreeView->get_id(rEntry).toInt32(); if ( nEntryType == DatabaseObjectContainer::TABLES ) { aObject.Type = DatabaseObjectContainer::TABLES; } else if ( ( nEntryType == DatabaseObjectContainer::CATALOG ) || ( nEntryType == DatabaseObjectContainer::SCHEMA ) ) { // nothing useful to be done } else { aObject.Type = DatabaseObject::TABLE; aObject.Name = getQualifiedTableName(rEntry); } return aObject; } std::unique_ptr OTableTreeListBox::addedTable(const OUString& rName) { try { Reference< XDatabaseMetaData > xMeta; if ( impl_getAndAssertMetaData( xMeta ) ) return implAddEntry( xMeta, rName ); } catch( const Exception& ) { DBG_UNHANDLED_EXCEPTION("dbaccess"); } return nullptr; } bool OTableTreeListBox::impl_getAndAssertMetaData( Reference< XDatabaseMetaData >& _out_rMetaData ) const { if ( m_xConnection.is() ) _out_rMetaData = m_xConnection->getMetaData(); OSL_PRECOND( _out_rMetaData.is(), "OTableTreeListBox::impl_getAndAssertMetaData: invalid current connection!" ); return _out_rMetaData.is(); } OUString OTableTreeListBox::getQualifiedTableName(const weld::TreeIter& rEntry) const { OSL_PRECOND( !isFolderEntry(rEntry), "OTableTreeListBox::getQualifiedTableName: folder entries not allowed here!" ); try { Reference< XDatabaseMetaData > xMeta; if ( !impl_getAndAssertMetaData( xMeta ) ) return OUString(); OUString sCatalog; OUString sSchema; OUString sTable; std::unique_ptr xSchema(m_xTreeView->make_iterator(&rEntry)); bool bSchema = m_xTreeView->iter_parent(*xSchema); if (bSchema) { std::unique_ptr xCatalog(m_xTreeView->make_iterator(xSchema.get())); bool bCatalog = m_xTreeView->iter_parent(*xCatalog); if ( bCatalog || ( xMeta->supportsCatalogsInDataManipulation() && !xMeta->supportsSchemasInDataManipulation() ) // here we support catalog but no schema ) { if (!bCatalog) { xCatalog = std::move(xSchema); bSchema = false; } sCatalog = m_xTreeView->get_text(*xCatalog); } if (bSchema) sSchema = m_xTreeView->get_text(*xSchema); } sTable = m_xTreeView->get_text(rEntry); return ::dbtools::composeTableName( xMeta, sCatalog, sSchema, sTable, false, ::dbtools::EComposeRule::InDataManipulation ); } catch( const Exception& ) { DBG_UNHANDLED_EXCEPTION("dbaccess"); } return OUString(); } std::unique_ptr OTableTreeListBox::getEntryByQualifiedName(const OUString& rName) { try { Reference< XDatabaseMetaData > xMeta; if ( !impl_getAndAssertMetaData( xMeta ) ) return nullptr; // split the complete name into its components OUString sCatalog, sSchema, sName; qualifiedNameComponents(xMeta, rName, sCatalog, sSchema, sName,::dbtools::EComposeRule::InDataManipulation); std::unique_ptr xParent(getAllObjectsEntry()); std::unique_ptr xCat; std::unique_ptr xSchema; if (!sCatalog.isEmpty()) { xCat = GetEntryPosByName(sCatalog); if (xCat) xParent = std::move(xCat); } if (!sSchema.isEmpty()) { xSchema = GetEntryPosByName(sSchema, xParent.get()); if (xSchema) xParent = std::move(xSchema); } return GetEntryPosByName(sName, xParent.get()); } catch( const Exception& ) { DBG_UNHANDLED_EXCEPTION("dbaccess"); } return nullptr; } void OTableTreeListBox::removedTable(const OUString& rName) { try { std::unique_ptr xEntry = getEntryByQualifiedName(rName); if (xEntry) m_xTreeView->remove(*xEntry); } catch( const Exception& ) { DBG_UNHANDLED_EXCEPTION("dbaccess"); } } void OTableTreeListBox::CheckButtons() { if (!m_bShowToggles) return; auto xEntry(m_xTreeView->make_iterator()); if (!m_xTreeView->get_iter_first(*xEntry)) return; do { implDetermineState(*xEntry); } while (m_xTreeView->iter_next_sibling(*xEntry)); } TriState OTableTreeListBox::implDetermineState(const weld::TreeIter& rEntry) { if (!m_bShowToggles) return TRISTATE_FALSE; TriState eState = m_xTreeView->get_toggle(rEntry); if (!m_xTreeView->iter_has_child(rEntry)) // nothing to do in this bottom-up routine if there are no children ... return eState; // loop through the children and check their states sal_uInt16 nCheckedChildren = 0; sal_uInt16 nChildrenOverall = 0; std::unique_ptr xChild(m_xTreeView->make_iterator(&rEntry)); bool bChildLoop = m_xTreeView->iter_children(*xChild); while (bChildLoop) { TriState eChildState = implDetermineState(*xChild); if (eChildState == TRISTATE_INDET) break; if (eChildState == TRISTATE_TRUE) ++nCheckedChildren; ++nChildrenOverall; bChildLoop = m_xTreeView->iter_next_sibling(*xChild); } if (bChildLoop) { // we did not finish the loop because at least one of the children is in tristate eState = TRISTATE_INDET; // but this means that we did not finish all the siblings of pChildLoop, // so their checking may be incorrect at the moment // -> correct this while (bChildLoop) { implDetermineState(*xChild); bChildLoop = m_xTreeView->iter_next_sibling(*xChild); } } else { // none if the children are in tristate if (nCheckedChildren) { // we have at least one child checked if (nCheckedChildren != nChildrenOverall) { // not all children are checked eState = TRISTATE_INDET; } else { // all children are checked eState = TRISTATE_TRUE; } } else { // no children are checked eState = TRISTATE_FALSE; } } // finally set the entry to the state we just determined m_xTreeView->set_toggle(rEntry, eState); return eState; } DBTableTreeView::DBTableTreeView(weld::Container* pContainer) : DBTreeViewBase(pContainer) { m_xTreeListBox.reset(new OTableTreeListBox(m_xBuilder->weld_tree_view("treeview"), /*bShowToggles*/false)); } } // namespace dbaui /* vim:set shiftwidth=4 softtabstop=4 expandtab: */