/* -*- 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 #include #include #include #include namespace dbaui { using namespace ::com::sun::star::uno; using namespace ::com::sun::star::container; using namespace ::com::sun::star::sdbc; using namespace ::com::sun::star::sdb; using namespace ::com::sun::star::lang; using namespace ::dbtools; // helper static bool operator ==(const OIndexField& _rLHS, const OIndexField& _rRHS) { return (_rLHS.sFieldName == _rRHS.sFieldName) && (_rLHS.bSortAscending == _rRHS.bSortAscending); } static bool operator ==(const IndexFields& _rLHS, const IndexFields& _rRHS) { return std::equal(_rLHS.begin(), _rLHS.end(), _rRHS.begin(), _rRHS.end()); } static bool operator !=(const IndexFields& _rLHS, const IndexFields& _rRHS) { return !(_rLHS == _rRHS); } // DbaIndexDialog DbaIndexDialog::DbaIndexDialog(weld::Window* pParent, const Sequence< OUString >& _rFieldNames, const Reference< XNameAccess >& _rxIndexes, const Reference< XConnection >& _rxConnection, const Reference< XComponentContext >& _rxContext) : GenericDialogController(pParent, "dbaccess/ui/indexdesigndialog.ui", "IndexDesignDialog") , m_xConnection(_rxConnection) , m_bEditingActive(false) , m_bEditAgain(false) , m_bNoHandlerCall(false) , m_xContext(_rxContext) , m_xActions(m_xBuilder->weld_toolbar("ACTIONS")) , m_xIndexList(m_xBuilder->weld_tree_view("INDEX_LIST")) , m_xIndexDetails(m_xBuilder->weld_label("INDEX_DETAILS")) , m_xDescriptionLabel(m_xBuilder->weld_label("DESC_LABEL")) , m_xDescription(m_xBuilder->weld_label("DESCRIPTION")) , m_xUnique(m_xBuilder->weld_check_button("UNIQUE")) , m_xFieldsLabel(m_xBuilder->weld_label("FIELDS_LABEL")) , m_xClose(m_xBuilder->weld_button("close")) , m_xTable(m_xBuilder->weld_container("FIELDS")) , m_xTableCtrlParent(m_xTable->CreateChildFrame()) , m_xFields(VclPtr::Create(m_xTableCtrlParent)) { m_xIndexList->set_size_request(m_xIndexList->get_approximate_digit_width() * 17, m_xIndexList->get_height_rows(12)); int nWidth = m_xIndexList->get_approximate_digit_width() * 60; int nHeight = m_xIndexList->get_height_rows(8); m_xTable->set_size_request(nWidth, nHeight); m_xActions->connect_clicked(LINK(this, DbaIndexDialog, OnIndexAction)); m_xIndexList->connect_changed(LINK(this, DbaIndexDialog, OnIndexSelected)); m_xIndexList->connect_editing_started(LINK(this, DbaIndexDialog, OnEntryEditing)); m_xIndexList->connect_editing_done(LINK(this, DbaIndexDialog, OnEntryEdited)); m_xFields->SetSizePixel(Size(nWidth, 100)); m_xFields->Init(_rFieldNames, ::dbtools::getBooleanDataSourceSetting( m_xConnection, "AddIndexAppendix" )); m_xFields->Show(); m_xIndexes.reset(new OIndexCollection()); try { m_xIndexes->attach(_rxIndexes); } catch(SQLException& e) { ::dbtools::showError(SQLExceptionInfo(e), pParent->GetXWindow(), _rxContext); } catch(Exception&) { OSL_FAIL("DbaIndexDialog::DbaIndexDialog: could not retrieve basic information from the UNO collection!"); } fillIndexList(); m_xUnique->connect_clicked(LINK(this, DbaIndexDialog, OnModifiedClick)); m_xFields->SetModifyHdl(LINK(this, DbaIndexDialog, OnModified)); m_xClose->connect_clicked(LINK(this, DbaIndexDialog, OnCloseDialog)); // if all of the indexes have an empty description, we're not interested in displaying it bool bFound = false; for (auto const& check : *m_xIndexes) { if (!check.sDescription.isEmpty()) { bFound = true; break; } } if (!bFound) { // hide the controls which are necessary for the description m_xDescription->hide(); m_xDescriptionLabel->hide(); } } void DbaIndexDialog::updateToolbox() { m_xActions->set_item_sensitive("ID_INDEX_NEW", !m_bEditingActive); int nSelected = m_xIndexList->get_selected_index(); bool bSelectedAnything = nSelected != -1; if (bSelectedAnything) { // is the current entry modified? Indexes::const_iterator aSelectedPos = m_xIndexes->begin() + m_xIndexList->get_id(nSelected).toUInt32(); m_xActions->set_item_sensitive("ID_INDEX_SAVE", aSelectedPos->isModified() || aSelectedPos->isNew()); m_xActions->set_item_sensitive("ID_INDEX_RESET", aSelectedPos->isModified() || aSelectedPos->isNew()); bSelectedAnything = !aSelectedPos->bPrimaryKey; } else { m_xActions->set_item_sensitive("ID_INDEX_SAVE", false); m_xActions->set_item_sensitive("ID_INDEX_RESET", false); } m_xActions->set_item_sensitive("ID_INDEX_DROP", bSelectedAnything); m_xActions->set_item_sensitive("ID_INDEX_RENAME", bSelectedAnything); } void DbaIndexDialog::fillIndexList() { OUString aPKeyIcon(BMP_PKEYICON); // fill the list with the index names m_xIndexList->clear(); sal_uInt32 nPos = 0; for (auto const& indexLoop : *m_xIndexes) { m_xIndexList->append(OUString::number(nPos), indexLoop.sName); if (indexLoop.bPrimaryKey) m_xIndexList->set_image(nPos, aPKeyIcon); ++nPos; } if (nPos) m_xIndexList->select(0); IndexSelected(); } DbaIndexDialog::~DbaIndexDialog( ) { m_xIndexes.reset(); m_xFields.disposeAndClear(); m_xTableCtrlParent->dispose(); m_xTableCtrlParent.clear(); } bool DbaIndexDialog::implCommit(const weld::TreeIter* pEntry) { assert(pEntry && "DbaIndexDialog::implCommit: invalid entry!"); Indexes::iterator aCommitPos = m_xIndexes->begin() + m_xIndexList->get_id(*pEntry).toUInt32(); // if it's not a new index, remove it // (we can't modify indexes, only drop'n'insert) if (!aCommitPos->isNew()) if (!implDropIndex(pEntry, false)) return false; // create the new index SQLExceptionInfo aExceptionInfo; try { m_xIndexes->commitNewIndex(aCommitPos); } catch(SQLContext& e) { aExceptionInfo = SQLExceptionInfo(e); } catch(SQLWarning& e) { aExceptionInfo = SQLExceptionInfo(e); } catch(SQLException& e) { aExceptionInfo = SQLExceptionInfo(e); } // reflect the new selection in the toolbox updateToolbox(); if (aExceptionInfo.isValid()) showError(aExceptionInfo, m_xDialog->GetXWindow(), m_xContext); else { m_xUnique->save_state(); m_xFields->SaveValue(); } return !aExceptionInfo.isValid(); } void DbaIndexDialog::OnNewIndex() { // commit the current entry, if necessary if (!implCommitPreviouslySelected()) return; // get a new unique name for the new index OUString sNewIndexName; const OUString sNewIndexNameBase(DBA_RES(STR_LOGICAL_INDEX_NAME)); sal_Int32 i; for ( i = 1; i < 0x7FFFFFFF; ++i ) { sNewIndexName = sNewIndexNameBase + OUString::number(i); if (m_xIndexes->end() == m_xIndexes->find(sNewIndexName)) break; } if (i == 0x7FFFFFFF) { OSL_FAIL("DbaIndexDialog::OnNewIndex: no free index name found!"); // can't do anything ... of course we try another base, but this could end with the same result ... return; } std::unique_ptr xNewEntry(m_xIndexList->make_iterator()); m_xIndexList->insert(nullptr, -1, &sNewIndexName, nullptr, nullptr, nullptr, nullptr, false, xNewEntry.get()); m_xIndexes->insert(sNewIndexName); // update the user data on the entries in the list box: // they're iterators of the index collection, and thus they have changed when removing the index m_xIndexList->all_foreach([this](weld::TreeIter& rEntry){ Indexes::const_iterator aAfterInsertPos = m_xIndexes->find(m_xIndexList->get_text(rEntry)); OSL_ENSURE(aAfterInsertPos != m_xIndexes->end(), "DbaIndexDialog::OnNewIndex: problems with one of the entries!"); m_xIndexList->set_id(rEntry, OUString::number(aAfterInsertPos - m_xIndexes->begin())); return false; }); // select the entry and start in-place editing m_bNoHandlerCall = true; m_xIndexList->select(*xNewEntry); m_bNoHandlerCall = false; IndexSelected(); m_xIndexList->grab_focus(); m_xIndexList->start_editing(*xNewEntry); updateToolbox(); } void DbaIndexDialog::OnDropIndex(bool _bConfirm) { std::unique_ptr xSelected(m_xIndexList->make_iterator()); // the selected index if (m_xIndexList->get_selected(xSelected.get())) { // let the user confirm the drop if (_bConfirm) { OUString sConfirm(DBA_RES(STR_CONFIRM_DROP_INDEX)); sConfirm = sConfirm.replaceFirst("$name$", m_xIndexList->get_text(*xSelected)); std::unique_ptr xConfirm(Application::CreateMessageDialog(m_xDialog.get(), VclMessageType::Question, VclButtonsType::YesNo, sConfirm)); if (RET_YES != xConfirm->run()) return; } // do the drop implDropIndex(xSelected.get(), true); // reflect the new selection in the toolbox updateToolbox(); } } bool DbaIndexDialog::implDropIndex(const weld::TreeIter* pEntry, bool _bRemoveFromCollection) { // do the drop Indexes::iterator aDropPos = m_xIndexes->begin() + m_xIndexList->get_id(*pEntry).toUInt32(); OSL_ENSURE(aDropPos != m_xIndexes->end(), "DbaIndexDialog::OnDropIndex: did not find the index in my collection!"); SQLExceptionInfo aExceptionInfo; bool bSuccess = false; try { if (_bRemoveFromCollection) bSuccess = m_xIndexes->drop(aDropPos); else bSuccess = m_xIndexes->dropNoRemove(aDropPos); } catch(SQLContext& e) { aExceptionInfo = SQLExceptionInfo(e); } catch(SQLWarning& e) { aExceptionInfo = SQLExceptionInfo(e); } catch(SQLException& e) { aExceptionInfo = SQLExceptionInfo(e); } if (aExceptionInfo.isValid()) showError(aExceptionInfo, m_xDialog->GetXWindow(), m_xContext); else if (bSuccess && _bRemoveFromCollection) { m_bNoHandlerCall = true; // if the entry to remove is the selected on... if (m_xPreviousSelection && m_xPreviousSelection->equal(*pEntry)) m_xPreviousSelection.reset(); m_xIndexList->remove(*pEntry); m_bNoHandlerCall = false; // update the user data on the entries in the list box: // they're iterators of the index collection, and thus they have changed when removing the index m_xIndexList->all_foreach([this](weld::TreeIter& rEntry){ Indexes::const_iterator aAfterDropPos = m_xIndexes->find(m_xIndexList->get_text(rEntry)); OSL_ENSURE(aAfterDropPos != m_xIndexes->end(), "DbaIndexDialog::OnDropIndex: problems with one of the remaining entries!"); m_xIndexList->set_id(rEntry, OUString::number(aAfterDropPos - m_xIndexes->begin())); return false; }); // the Remove automatically selected another entry (if possible), but we disabled the calling of the handler // to prevent that we missed something... call the handler directly IndexSelected(); } return !aExceptionInfo.isValid(); } void DbaIndexDialog::OnRenameIndex() { // the selected index std::unique_ptr xSelected(m_xIndexList->make_iterator()); // the selected index m_xIndexList->get_selected(xSelected.get()); // save the changes made 'til here // Upon leaving the edit mode, the control will be re-initialized with the // settings from the current entry implSaveModified(false); m_xIndexList->grab_focus(); m_xIndexList->start_editing(*xSelected); updateToolbox(); } void DbaIndexDialog::OnSaveIndex() { // the selected index implCommitPreviouslySelected(); updateToolbox(); } void DbaIndexDialog::OnResetIndex() { // the selected index std::unique_ptr xSelected(m_xIndexList->make_iterator()); // the selected index m_xIndexList->get_selected(xSelected.get()); OSL_ENSURE(xSelected, "DbaIndexDialog::OnResetIndex: invalid call!"); Indexes::iterator aResetPos = m_xIndexes->begin() + m_xIndexList->get_id(*xSelected).toUInt32(); if (aResetPos->isNew()) { OnDropIndex(false); return; } SQLExceptionInfo aExceptionInfo; try { m_xIndexes->resetIndex(aResetPos); } catch(SQLContext& e) { aExceptionInfo = SQLExceptionInfo(e); } catch(SQLWarning& e) { aExceptionInfo = SQLExceptionInfo(e); } catch(SQLException& e) { aExceptionInfo = SQLExceptionInfo(e); } if (aExceptionInfo.isValid()) showError(aExceptionInfo, m_xDialog->GetXWindow(), m_xContext); else m_xIndexList->set_text(*xSelected, aResetPos->sName); updateControls(xSelected.get()); updateToolbox(); } IMPL_LINK(DbaIndexDialog, OnIndexAction, const OString&, rClicked, void) { if (rClicked == "ID_INDEX_NEW") OnNewIndex(); else if (rClicked == "ID_INDEX_DROP") OnDropIndex(); else if (rClicked == "ID_INDEX_RENAME") OnRenameIndex(); else if (rClicked == "ID_INDEX_SAVE") OnSaveIndex(); else if (rClicked == "ID_INDEX_RESET") OnResetIndex(); } IMPL_LINK_NOARG(DbaIndexDialog, OnCloseDialog, weld::Button&, void) { if (m_bEditingActive) { OSL_ENSURE(!m_bEditAgain, "DbaIndexDialog::OnCloseDialog: somebody was faster than hell!"); // this means somebody entered a new name, which was invalid, which cause us to posted us an event, // and before the event arrived the user clicked onto "close". VERY fast, this user... m_xIndexList->end_editing(); if (m_bEditAgain) // could not commit the new name (started a new - asynchronous - edit trial) return; } // the currently selected entry std::unique_ptr xSelected(m_xIndexList->make_iterator()); // the selected index if (!m_xIndexList->get_selected(xSelected.get())) xSelected.reset(); OSL_ENSURE(xSelected && m_xPreviousSelection && xSelected->equal(*m_xPreviousSelection), "DbaIndexDialog::OnCloseDialog: inconsistence!"); sal_Int32 nResponse = RET_NO; if (xSelected) { // the descriptor Indexes::const_iterator aSelected = m_xIndexes->begin() + m_xIndexList->get_id(*xSelected).toUInt32(); if (aSelected->isModified() || aSelected->isNew()) { std::unique_ptr xBuilder(Application::CreateBuilder(m_xDialog.get(), "dbaccess/ui/saveindexdialog.ui")); std::unique_ptr xQuery(xBuilder->weld_message_dialog("SaveIndexDialog")); nResponse = xQuery->run(); } } switch (nResponse) { case RET_YES: if (!implCommitPreviouslySelected()) return; break; case RET_NO: break; default: return; } m_xDialog->response(RET_OK); } IMPL_LINK(DbaIndexDialog, OnEditIndexAgain, void*, p, void) { weld::TreeIter* pEntry = static_cast(p); m_bEditAgain = false; m_xIndexList->grab_focus(); m_xIndexList->start_editing(*pEntry); delete pEntry; } IMPL_STATIC_LINK_NOARG(DbaIndexDialog, OnEntryEditing, const weld::TreeIter&, bool) { return true; } IMPL_LINK(DbaIndexDialog, OnEntryEdited, const IterString&, rIterString, bool) { const weld::TreeIter& rEntry = rIterString.first; OUString sNewName = rIterString.second; Indexes::iterator aPosition = m_xIndexes->begin() + m_xIndexList->get_id(rEntry).toUInt32(); OSL_ENSURE(aPosition >= m_xIndexes->begin() && aPosition < m_xIndexes->end(), "DbaIndexDialog::OnEntryEdited: invalid entry!"); Indexes::const_iterator aSameName = m_xIndexes->find(sNewName); if (aSameName != aPosition && m_xIndexes->end() != aSameName) { OUString sError(DBA_RES(STR_INDEX_NAME_ALREADY_USED)); sError = sError.replaceFirst("$name$", sNewName); std::unique_ptr xError(Application::CreateMessageDialog(m_xDialog.get(), VclMessageType::Warning, VclButtonsType::Ok, sError)); xError->run(); updateToolbox(); m_bEditAgain = true; std::unique_ptr xEntry(m_xIndexList->make_iterator(&rEntry)); Application::PostUserEvent(LINK(this, DbaIndexDialog, OnEditIndexAgain), xEntry.release()); return false; } aPosition->sName = sNewName; // rename can be done by a drop/insert combination only if (aPosition->isNew()) { updateToolbox(); // no commitment needed here... return true; } if (aPosition->sName != aPosition->getOriginalName()) { aPosition->setModified(true); updateToolbox(); } return true; } bool DbaIndexDialog::implSaveModified(bool _bPlausibility) { if (m_xPreviousSelection) { // try to commit the previously selected index if (m_xFields->IsModified() && !m_xFields->SaveModified()) return false; Indexes::iterator aPreviouslySelected = m_xIndexes->begin() + m_xIndexList->get_id(*m_xPreviousSelection).toUInt32(); // the unique flag aPreviouslySelected->bUnique = m_xUnique->get_active(); if (m_xUnique->get_state_changed_from_saved()) aPreviouslySelected->setModified(true); // the fields m_xFields->commitTo(aPreviouslySelected->aFields); if (m_xFields->GetSavedValue() != aPreviouslySelected->aFields) aPreviouslySelected->setModified(true); // plausibility checks if (_bPlausibility && !implCheckPlausibility(aPreviouslySelected)) return false; } return true; } bool DbaIndexDialog::implCheckPlausibility(const Indexes::const_iterator& _rPos) { // need at least one field if (_rPos->aFields.empty()) { std::unique_ptr xError(Application::CreateMessageDialog(m_xDialog.get(), VclMessageType::Warning, VclButtonsType::Ok, DBA_RES(STR_NEED_INDEX_FIELDS))); xError->run(); m_xFields->GrabFocus(); return false; } // no double fields std::set< OUString > aExistentFields; for (auto const& fieldCheck : _rPos->aFields) { if (aExistentFields.end() != aExistentFields.find(fieldCheck.sFieldName)) { // a column is specified twice ... won't work anyway, so prevent this here and now OUString sMessage(DBA_RES(STR_INDEXDESIGN_DOUBLE_COLUMN_NAME)); sMessage = sMessage.replaceFirst("$name$", fieldCheck.sFieldName); std::unique_ptr xError(Application::CreateMessageDialog(m_xDialog.get(), VclMessageType::Warning, VclButtonsType::Ok, sMessage)); xError->run(); m_xFields->GrabFocus(); return false; } aExistentFields.insert(fieldCheck.sFieldName); } return true; } bool DbaIndexDialog::implCommitPreviouslySelected() { if (m_xPreviousSelection) { Indexes::const_iterator aPreviouslySelected = m_xIndexes->begin() + m_xIndexList->get_id(*m_xPreviousSelection).toUInt32(); if (!implSaveModified()) return false; // commit the index (if necessary) if (aPreviouslySelected->isModified() && !implCommit(m_xPreviousSelection.get())) return false; } return true; } IMPL_LINK_NOARG(DbaIndexDialog, OnModifiedClick, weld::Button&, void) { OnModified(*m_xFields); } IMPL_LINK_NOARG( DbaIndexDialog, OnModified, IndexFieldsControl&, void ) { assert(m_xPreviousSelection && "DbaIndexDialog, OnModified: invalid call!"); Indexes::iterator aPosition = m_xIndexes->begin() + m_xIndexList->get_id(*m_xPreviousSelection).toUInt32(); aPosition->setModified(true); updateToolbox(); } void DbaIndexDialog::updateControls(const weld::TreeIter* pEntry) { if (pEntry) { // the descriptor of the selected index Indexes::const_iterator aSelectedIndex = m_xIndexes->begin() + m_xIndexList->get_id(*pEntry).toUInt32(); // fill the controls m_xUnique->set_active(aSelectedIndex->bUnique); m_xUnique->set_sensitive(!aSelectedIndex->bPrimaryKey); m_xUnique->save_state(); m_xFields->initializeFrom(aSelectedIndex->aFields); m_xFields->Enable(!aSelectedIndex->bPrimaryKey); m_xFields->SaveValue(); m_xDescription->set_label(aSelectedIndex->sDescription); m_xDescription->set_sensitive(!aSelectedIndex->bPrimaryKey); m_xDescriptionLabel->set_sensitive(!aSelectedIndex->bPrimaryKey); } else { m_xUnique->set_active(false); m_xFields->initializeFrom(IndexFields()); m_xDescription->set_label(OUString()); } } void DbaIndexDialog::IndexSelected() { //TODO m_xIndexList->EndSelection(); if (m_bEditingActive) m_xIndexList->end_editing(); std::unique_ptr xSelected(m_xIndexList->make_iterator()); if (!m_xIndexList->get_selected(xSelected.get())) xSelected.reset(); // commit the old data if (m_xPreviousSelection && (!xSelected || !m_xPreviousSelection->equal(*xSelected))) { // (this call may happen in case somebody ended an in-place edit with 'return', so we need to check this before committing) if (!implCommitPreviouslySelected()) { m_bNoHandlerCall = true; m_xIndexList->select(*m_xPreviousSelection); m_bNoHandlerCall = false; return; } } // disable/enable the detail controls m_xIndexDetails->set_sensitive(xSelected != nullptr); m_xUnique->set_sensitive(xSelected != nullptr); m_xDescriptionLabel->set_sensitive(xSelected != nullptr); m_xFieldsLabel->set_sensitive(xSelected != nullptr); m_xFields->Enable(xSelected != nullptr); updateControls(xSelected.get()); if (xSelected) m_xIndexList->grab_focus(); m_xPreviousSelection = std::move(xSelected); updateToolbox(); } IMPL_LINK_NOARG(DbaIndexDialog, OnIndexSelected, weld::TreeView&, void) { if (m_bNoHandlerCall) return; IndexSelected(); } } // namespace dbaui /* vim:set shiftwidth=4 softtabstop=4 expandtab: */