/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace com::sun::star; // order of the categories in navigator ------------------------------------- const ScContentId pTypeList[int(ScContentId::LAST) + 1] = { ScContentId::ROOT, // ROOT (0) has to be at the front ScContentId::TABLE, ScContentId::RANGENAME, ScContentId::DBAREA, ScContentId::AREALINK, ScContentId::GRAPHIC, ScContentId::OLEOBJECT, ScContentId::NOTE, ScContentId::DRAWING }; constexpr OUString aContentBmps[]= { RID_BMP_CONTENT_TABLE, RID_BMP_CONTENT_RANGENAME, RID_BMP_CONTENT_DBAREA, RID_BMP_CONTENT_GRAPHIC, RID_BMP_CONTENT_OLEOBJECT, RID_BMP_CONTENT_NOTE, RID_BMP_CONTENT_AREALINK, RID_BMP_CONTENT_DRAWING }; ScDocShell* ScContentTree::GetManualOrCurrent() { ScDocShell* pSh = nullptr; if ( !aManualDoc.isEmpty() ) { SfxObjectShell* pObjSh = SfxObjectShell::GetFirst( checkSfxObjectShell ); while ( pObjSh && !pSh ) { if ( pObjSh->GetTitle() == aManualDoc ) pSh = dynamic_cast( pObjSh ); pObjSh = SfxObjectShell::GetNext( *pObjSh, checkSfxObjectShell ); } } else { // only current when manual isn't set // (so it's detected when the documents don't exists any longer) SfxViewShell* pViewSh = SfxViewShell::Current(); if ( pViewSh ) { SfxObjectShell* pObjSh = pViewSh->GetViewFrame().GetObjectShell(); pSh = dynamic_cast( pObjSh ); } } return pSh; } // ScContentTree ScContentTree::ScContentTree(std::unique_ptr xTreeView, ScNavigatorDlg* pNavigatorDlg) : m_xTreeView(std::move(xTreeView)) , m_xScratchIter(m_xTreeView->make_iterator()) , m_xTransferObj(new ScLinkTransferObj) , pParentWindow(pNavigatorDlg) , nRootType(ScContentId::ROOT) , bIsInNavigatorDlg(false) , m_bFreeze(false) , m_nAsyncMouseReleaseId(nullptr) { for (sal_uInt16 i = 0; i <= int(ScContentId::LAST); ++i) pPosList[pTypeList[i]] = i; // inverse for searching m_aRootNodes[ScContentId::ROOT] = nullptr; for (sal_uInt16 i = 1; i < int(ScContentId::LAST); ++i) InitRoot(static_cast(i)); m_xTreeView->connect_row_activated(LINK(this, ScContentTree, ContentDoubleClickHdl)); m_xTreeView->connect_mouse_release(LINK(this, ScContentTree, MouseReleaseHdl)); m_xTreeView->connect_key_press(LINK(this, ScContentTree, KeyInputHdl)); m_xTreeView->connect_popup_menu(LINK(this, ScContentTree, CommandHdl)); m_xTreeView->connect_query_tooltip(LINK(this, ScContentTree, QueryTooltipHdl)); rtl::Reference xHelper(m_xTransferObj); m_xTreeView->enable_drag_source(xHelper, DND_ACTION_COPY | DND_ACTION_LINK); m_xTreeView->connect_drag_begin(LINK(this, ScContentTree, DragBeginHdl)); m_xTreeView->set_selection_mode( SelectionMode::Single ); m_xTreeView->set_size_request(m_xTreeView->get_approximate_digit_width() * 30, m_xTreeView->get_text_height() * 13); } ScContentTree::~ScContentTree() { if (m_nAsyncMouseReleaseId) { Application::RemoveUserEvent(m_nAsyncMouseReleaseId); m_nAsyncMouseReleaseId = nullptr; } } const TranslateId SCSTR_CONTENT_ARY[] = { SCSTR_CONTENT_ROOT, SCSTR_CONTENT_TABLE, SCSTR_CONTENT_RANGENAME, SCSTR_CONTENT_DBAREA, SCSTR_CONTENT_GRAPHIC, SCSTR_CONTENT_OLEOBJECT, SCSTR_CONTENT_NOTE, SCSTR_CONTENT_AREALINK, SCSTR_CONTENT_DRAWING }; void ScContentTree::InitRoot( ScContentId nType ) { if ( nType == ScContentId::ROOT ) return; if ( nRootType != ScContentId::ROOT && nRootType != nType ) // hidden ? { m_aRootNodes[nType] = nullptr; return; } auto const & aImage = aContentBmps[static_cast(nType) - 1]; OUString aName; if(comphelper::LibreOfficeKit::isActive()) { //In case of LOK we may have many different ScContentTrees in different languages. //At creation time, we store what language we use, and then use it later too. //It does not work in the constructor, that is why it is here. if (!m_pResLocaleForLOK) { m_pResLocaleForLOK = std::make_unique(SC_MOD()->GetResLocale()); } aName = Translate::get(SCSTR_CONTENT_ARY[static_cast(nType)], *m_pResLocaleForLOK); } else { aName = ScResId(SCSTR_CONTENT_ARY[static_cast(nType)]); } // back to the correct position: sal_uInt16 nPos = nRootType != ScContentId::ROOT ? 0 : pPosList[nType]-1; m_aRootNodes[nType] = m_xTreeView->make_iterator(); m_xTreeView->insert(nullptr, nPos, &aName, nullptr, nullptr, nullptr, false, m_aRootNodes[nType].get()); m_xTreeView->set_image(*m_aRootNodes[nType], aImage); } void ScContentTree::ClearAll() { //There are one method in Control::SetUpdateMode(), and one override method SvTreeListBox::SetUpdateMode(). Here although //SvTreeListBox::SetUpdateMode() is called in refresh method, it only call SvTreeListBox::SetUpdateMode(), not Control::SetUpdateMode(). //In m_xTreeView->clear(), Broadcast( LISTACTION_CLEARED ) will be called and finally, it will be trapped into the event yield() loop. And //the InitRoot() method won't be called. Then if a user click or press key to update the navigator tree, crash happens. //So the solution is to disable the UpdateMode of Control, then call Clear(), then recover the update mode bool bWasFrozen = m_bFreeze; if (!bWasFrozen) freeze(); m_xTreeView->clear(); if (!bWasFrozen) thaw(); for (sal_uInt16 i=1; i<=int(ScContentId::LAST); i++) InitRoot(static_cast(i)); } void ScContentTree::ClearType(ScContentId nType) { if (nType == ScContentId::ROOT) ClearAll(); else { weld::TreeIter* pParent = m_aRootNodes[nType].get(); if (!pParent || m_xTreeView->iter_has_child(*pParent)) // not if no children existing { if (pParent) m_xTreeView->remove(*pParent); // with all children InitRoot( nType ); // if needed insert anew } } } void ScContentTree::InsertContent( ScContentId nType, const OUString& rValue ) { weld::TreeIter* pParent = m_aRootNodes[nType].get(); if (pParent) { m_xTreeView->insert(pParent, -1, &rValue, nullptr, nullptr, nullptr, false, m_xScratchIter.get()); m_xTreeView->set_sensitive(*m_xScratchIter, true); } else { OSL_FAIL("InsertContent without parent"); } } void ScContentTree::GetEntryIndexes(ScContentId& rnRootIndex, sal_uLong& rnChildIndex, const weld::TreeIter* pEntry) const { rnRootIndex = ScContentId::ROOT; rnChildIndex = SC_CONTENT_NOCHILD; if( !pEntry ) return; std::unique_ptr xParent(m_xTreeView->make_iterator(pEntry)); if (!m_xTreeView->iter_parent(*xParent)) xParent.reset(); bool bFound = false; for( int i = 1; !bFound && (i <= int(ScContentId::LAST)); ++i ) { ScContentId nRoot = static_cast(i); if (!m_aRootNodes[nRoot]) continue; if (m_xTreeView->iter_compare(*pEntry, *m_aRootNodes[nRoot]) == 0) { rnRootIndex = nRoot; rnChildIndex = ~0UL; bFound = true; } else if (xParent && m_xTreeView->iter_compare(*xParent, *m_aRootNodes[nRoot]) == 0) { rnRootIndex = nRoot; // search the entry in all child entries of the parent sal_uLong nEntry = 0; std::unique_ptr xIterEntry(m_xTreeView->make_iterator(xParent.get())); bool bIterEntry = m_xTreeView->iter_children(*xIterEntry); while (!bFound && bIterEntry) { if (m_xTreeView->iter_compare(*pEntry, *xIterEntry) == 0) { rnChildIndex = nEntry; bFound = true; // exit the while loop } bIterEntry = m_xTreeView->iter_next_sibling(*xIterEntry); ++nEntry; } bFound = true; // exit the for loop } } } sal_uLong ScContentTree::GetChildIndex(const weld::TreeIter* pEntry) const { ScContentId nRoot; sal_uLong nChild; GetEntryIndexes(nRoot, nChild, pEntry); return nChild; } static OUString lcl_GetDBAreaRange( const ScDocument* pDoc, const OUString& rDBName ) { OUString aRet; if (pDoc) { ScDBCollection* pDbNames = pDoc->GetDBCollection(); const ScDBData* pData = pDbNames->getNamedDBs().findByUpperName(ScGlobal::getCharClass().uppercase(rDBName)); if (pData) { ScRange aRange; pData->GetArea(aRange); aRet = aRange.Format(*pDoc, ScRefFlags::RANGE_ABS_3D); } } return aRet; } IMPL_LINK_NOARG(ScContentTree, ContentDoubleClickHdl, weld::TreeView&, bool) { ScContentId nType; sal_uLong nChild; std::unique_ptr xEntry(m_xTreeView->make_iterator()); if (!m_xTreeView->get_cursor(xEntry.get())) xEntry.reset(); GetEntryIndexes(nType, nChild, xEntry.get()); if (xEntry && (nType != ScContentId::ROOT) && (nChild != SC_CONTENT_NOCHILD)) { OUString aText(m_xTreeView->get_text(*xEntry)); if ( !aManualDoc.isEmpty() ) pParentWindow->SetCurrentDoc( aManualDoc ); switch( nType ) { case ScContentId::TABLE: { // tdf#133159 store current config before changing sheet // plausible that this should be done for all cases, but this // is the known case that needs it StoreNavigatorSettings(); pParentWindow->SetCurrentTableStr( aText ); } break; case ScContentId::RANGENAME: pParentWindow->SetCurrentCellStr( aText ); break; case ScContentId::DBAREA: { // If the same names of area and DB exists, then // SID_CURRENTCELL takes the area name. // Therefore for DB areas access them directly via address. OUString aRangeStr = lcl_GetDBAreaRange( GetSourceDocument(), aText ); if (!aRangeStr.isEmpty()) pParentWindow->SetCurrentCellStr( aRangeStr ); } break; case ScContentId::OLEOBJECT: case ScContentId::GRAPHIC: case ScContentId::DRAWING: pParentWindow->SetCurrentObject( aText ); break; case ScContentId::NOTE: { ScAddress aPos = GetNotePos( nChild ); pParentWindow->SetCurrentTable( aPos.Tab() ); pParentWindow->SetCurrentCell( aPos.Col(), aPos.Row() ); // Check whether the comment is currently visible and toggle its visibility ScDocument* pSrcDoc = GetSourceDocument(); if (ScPostIt* pNote = pSrcDoc ? pSrcDoc->GetNote(aPos.Col(), aPos.Row(), aPos.Tab()) : nullptr) { bool bVisible = pNote->IsCaptionShown(); // Effectivelly set the visibility of the comment GetManualOrCurrent()->GetDocFunc().ShowNote(aPos, !bVisible); // Put the note in edit mode ScTabViewShell* pScTabViewShell = ScNavigatorDlg::GetTabViewShell(); pScTabViewShell->EditNote(); } } break; case ScContentId::AREALINK: { const ScAreaLink* pLink = GetLink(nChild); ScDocument* pSrcDoc = GetSourceDocument(); if (pLink && pSrcDoc) { const ScRange& aRange = pLink->GetDestArea(); OUString aRangeStr(aRange.Format(*pSrcDoc, ScRefFlags::RANGE_ABS_3D, pSrcDoc->GetAddressConvention())); pParentWindow->SetCurrentCellStr( aRangeStr ); } } break; default: break; } ScNavigatorDlg::ReleaseFocus(); // set focus into document } return false; } void ScContentTree::LaunchAsyncStoreNavigatorSettings() { if (!m_nAsyncMouseReleaseId) m_nAsyncMouseReleaseId = Application::PostUserEvent(LINK(this, ScContentTree, AsyncStoreNavigatorSettings)); } IMPL_LINK_NOARG(ScContentTree, MouseReleaseHdl, const MouseEvent&, bool) { LaunchAsyncStoreNavigatorSettings(); return false; } IMPL_LINK_NOARG(ScContentTree, AsyncStoreNavigatorSettings, void*, void) { m_nAsyncMouseReleaseId = nullptr; StoreNavigatorSettings(); } IMPL_LINK(ScContentTree, KeyInputHdl, const KeyEvent&, rKEvt, bool) { bool bUsed = false; const vcl::KeyCode aCode = rKEvt.GetKeyCode(); if (aCode.GetCode() == KEY_RETURN) { switch (aCode.GetModifier()) { case KEY_MOD1: ToggleRoot(); // toggle root mode (as in Writer) bUsed = true; break; case 0: { std::unique_ptr xEntry(m_xTreeView->make_iterator()); if (!m_xTreeView->get_cursor(xEntry.get())) xEntry.reset(); if (xEntry) { ScContentId nType; sal_uLong nChild; GetEntryIndexes(nType, nChild, xEntry.get()); if (nType != ScContentId::ROOT && nChild == SC_CONTENT_NOCHILD) { if (m_xTreeView->get_row_expanded(*xEntry)) m_xTreeView->collapse_row(*xEntry); else m_xTreeView->expand_row(*xEntry); } else ContentDoubleClickHdl(*m_xTreeView); // select content as if double clicked } bUsed = true; } break; } } //Make KEY_SPACE has same function as DoubleClick, and realize //multi-selection. if ( bIsInNavigatorDlg ) { if(aCode.GetCode() == KEY_SPACE ) { bUsed = true; ScContentId nType; sal_uLong nChild; std::unique_ptr xEntry(m_xTreeView->make_iterator()); if (!m_xTreeView->get_cursor(xEntry.get())) xEntry.reset(); GetEntryIndexes(nType, nChild, xEntry.get()); if (xEntry && (nType != ScContentId::ROOT) && (nChild != SC_CONTENT_NOCHILD)) { OUString aText(m_xTreeView->get_text(*xEntry)); if (!aManualDoc.isEmpty()) pParentWindow->SetCurrentDoc( aManualDoc ); switch (nType) { case ScContentId::OLEOBJECT: case ScContentId::GRAPHIC: case ScContentId::DRAWING: { ScDrawView* pScDrawView = nullptr; ScTabViewShell* pScTabViewShell = ScNavigatorDlg::GetTabViewShell(); if (pScTabViewShell) pScDrawView = pScTabViewShell->GetViewData().GetScDrawView(); if (pScDrawView) { pScDrawView->SelectCurrentViewObject(aText); bool bHasMakredObject = false; weld::TreeIter* pParent = m_aRootNodes[nType].get(); std::unique_ptr xBeginEntry(m_xTreeView->make_iterator(pParent)); bool bBeginEntry = false; if (pParent) bBeginEntry = m_xTreeView->iter_children(*xBeginEntry); while (bBeginEntry) { OUString aTempText(m_xTreeView->get_text(*xBeginEntry)); if( pScDrawView->GetObjectIsMarked( pScDrawView->GetObjectByName( aTempText ) ) ) { bHasMakredObject = true; break; } bBeginEntry = m_xTreeView->iter_next(*xBeginEntry); } if (!bHasMakredObject && pScTabViewShell) pScTabViewShell->SetDrawShell(false); } break; } default: break; } } } } if (!bUsed) { if (aCode.GetCode() == KEY_F5) StoreNavigatorSettings(); else LaunchAsyncStoreNavigatorSettings(); } return bUsed; } IMPL_LINK(ScContentTree, CommandHdl, const CommandEvent&, rCEvt, bool) { bool bDone = false; ScContentId nType; sal_uLong nChild; std::unique_ptr xEntry(m_xTreeView->make_iterator()); if (!m_xTreeView->get_cursor(xEntry.get())) xEntry.reset(); GetEntryIndexes(nType, nChild, xEntry.get()); switch ( rCEvt.GetCommand() ) { case CommandEventId::ContextMenu: { // drag-and-drop mode std::unique_ptr xBuilder(Application::CreateBuilder(m_xTreeView.get(), u"modules/scalc/ui/dropmenu.ui"_ustr)); std::unique_ptr xPop(xBuilder->weld_menu(u"contextmenu"_ustr)); std::unique_ptr xDropMenu(xBuilder->weld_menu(u"dragmodesubmenu"_ustr)); switch (pParentWindow->GetDropMode()) { case 0: xDropMenu->set_active(u"hyperlink"_ustr, true); break; case 1: xDropMenu->set_active(u"link"_ustr, true); break; case 2: xDropMenu->set_active(u"copy"_ustr, true); break; } // displayed document std::unique_ptr xDocMenu(xBuilder->weld_menu(u"displaymenu"_ustr)); sal_uInt16 i=0; OUString sActive; OUString sId; // loaded documents ScDocShell* pCurrentSh = dynamic_cast( SfxObjectShell::Current() ); SfxObjectShell* pSh = SfxObjectShell::GetFirst(); while ( pSh ) { if ( dynamic_cast( pSh) != nullptr ) { OUString aName = pSh->GetTitle(); OUString aEntry = aName; if ( pSh == pCurrentSh ) aEntry += pParentWindow->aStrActive; else aEntry += pParentWindow->aStrNotActive; ++i; sId = "document" + OUString::number(i); xDocMenu->append_radio(sId, aEntry); if (aName == aManualDoc) sActive = sId; } pSh = SfxObjectShell::GetNext( *pSh ); } // "active window" ++i; sId = "document" + OUString::number(i); xDocMenu->append_radio(sId, pParentWindow->aStrActiveWin); if (aManualDoc.isEmpty()) sActive = sId; xDocMenu->set_active(sActive, true); // Edit/Delete Comments are only visible for comments if (nType != ScContentId::NOTE) { xPop->set_visible(u"edit"_ustr, false); xPop->set_visible(u"delete"_ustr, false); } OUString sIdent = xPop->popup_at_rect(m_xTreeView.get(), tools::Rectangle(rCEvt.GetMousePosPixel(), Size(1, 1))); if (sIdent == "hyperlink") pParentWindow->SetDropMode(0); else if (sIdent == "link") pParentWindow->SetDropMode(1); else if (sIdent == "copy") pParentWindow->SetDropMode(2); else if (sIdent.startsWith("document")) { OUString aName = xDocMenu->get_label(sIdent); SelectDoc(aName); } else if (sIdent == "edit") { ScAddress aPos = GetNotePos( nChild ); pParentWindow->SetCurrentTable( aPos.Tab() ); pParentWindow->SetCurrentCell( aPos.Col(), aPos.Row() ); ScDocument* pSrcDoc = GetSourceDocument(); if (pSrcDoc->GetNote(aPos.Col(), aPos.Row(), aPos.Tab())) { // Make the note visible and put it in edit mode GetManualOrCurrent()->GetDocFunc().ShowNote(aPos, true); ScTabViewShell* pScTabViewShell = ScNavigatorDlg::GetTabViewShell(); pScTabViewShell->EditNote(); bDone = true; } } else if (sIdent == "delete") { ScAddress aPos = GetNotePos(nChild); pParentWindow->SetCurrentTable(aPos.Tab()); pParentWindow->SetCurrentCell(aPos.Col(), aPos.Row()); ScTabViewShell* pScTabViewShell = ScNavigatorDlg::GetTabViewShell(); pScTabViewShell->DeleteContents(InsertDeleteFlags::NOTE); } } break; default: break; } return bDone; } IMPL_LINK(ScContentTree, QueryTooltipHdl, const weld::TreeIter&, rEntry, OUString) { OUString aHelpText; std::unique_ptr xParent(m_xTreeView->make_iterator(&rEntry)); if (!m_xTreeView->iter_parent(*xParent)) xParent.reset(); if (!xParent) // Top-Level ? { aHelpText = OUString::number(m_xTreeView->iter_n_children(rEntry)) + " " + m_xTreeView->get_text(rEntry); } else if (m_aRootNodes[ScContentId::NOTE] && m_xTreeView->iter_compare(*xParent, *m_aRootNodes[ScContentId::NOTE]) == 0) { aHelpText = m_xTreeView->get_text(rEntry); // notes as help text } else if (m_aRootNodes[ScContentId::AREALINK] && m_xTreeView->iter_compare(*xParent, *m_aRootNodes[ScContentId::AREALINK]) == 0) { auto nIndex = GetChildIndex(&rEntry); if (nIndex != SC_CONTENT_NOCHILD) { const ScAreaLink* pLink = GetLink(nIndex); if (pLink) { aHelpText = pLink->GetFile(); // source file as help text } } } return aHelpText; } ScDocument* ScContentTree::GetSourceDocument() { ScDocShell* pSh = GetManualOrCurrent(); if (pSh) return &pSh->GetDocument(); return nullptr; } void ScContentTree::Refresh( ScContentId nType ) { // if nothing has changed the cancel right away (against flicker) if ( nType == ScContentId::NOTE ) if (!NoteStringsChanged()) return; if ( nType == ScContentId::GRAPHIC ) if (!DrawNamesChanged(ScContentId::GRAPHIC)) return; if ( nType == ScContentId::OLEOBJECT ) if (!DrawNamesChanged(ScContentId::OLEOBJECT)) return; if ( nType == ScContentId::DRAWING ) if (!DrawNamesChanged(ScContentId::DRAWING)) return; freeze(); ClearType( nType ); if ( nType == ScContentId::ROOT || nType == ScContentId::TABLE ) GetTableNames(); if ( nType == ScContentId::ROOT || nType == ScContentId::RANGENAME ) GetAreaNames(); if ( nType == ScContentId::ROOT || nType == ScContentId::DBAREA ) GetDbNames(); if ( nType == ScContentId::ROOT || nType == ScContentId::GRAPHIC ) GetGraphicNames(); if ( nType == ScContentId::ROOT || nType == ScContentId::OLEOBJECT ) GetOleNames(); if ( nType == ScContentId::ROOT || nType == ScContentId::DRAWING ) GetDrawingNames(); if ( nType == ScContentId::ROOT || nType == ScContentId::NOTE ) GetNoteStrings(); if ( nType == ScContentId::ROOT || nType == ScContentId::AREALINK ) GetLinkNames(); thaw(); ApplyNavigatorSettings(); } void ScContentTree::GetTableNames() { if ( nRootType != ScContentId::ROOT && nRootType != ScContentId::TABLE ) // hidden ? return; ScDocument* pDoc = GetSourceDocument(); if (!pDoc) return; OUString aName; SCTAB nCount = pDoc->GetTableCount(); for ( SCTAB i=0; iGetName( i, aName ); InsertContent( ScContentId::TABLE, aName ); } } namespace { OUString createLocalRangeName(std::u16string_view rName, std::u16string_view rTableName) { return OUString::Concat(rName) + " (" + rTableName + ")"; } } void ScContentTree::GetAreaNames() { if ( nRootType != ScContentId::ROOT && nRootType != ScContentId::RANGENAME ) // hidden ? return; ScDocument* pDoc = GetSourceDocument(); if (!pDoc) return; ScRange aDummy; std::set aSet; ScRangeName* pRangeNames = pDoc->GetRangeName(); for (const auto& rEntry : *pRangeNames) { if (rEntry.second->IsValidReference(aDummy)) aSet.insert(rEntry.second->GetName()); } for (SCTAB i = 0; i < pDoc->GetTableCount(); ++i) { ScRangeName* pLocalRangeName = pDoc->GetRangeName(i); if (pLocalRangeName && !pLocalRangeName->empty()) { OUString aTableName; pDoc->GetName(i, aTableName); for (const auto& rEntry : *pLocalRangeName) { if (rEntry.second->IsValidReference(aDummy)) aSet.insert(createLocalRangeName(rEntry.second->GetName(), aTableName)); } } } for (const auto& rItem : aSet) { InsertContent(ScContentId::RANGENAME, rItem); } } void ScContentTree::GetDbNames() { if ( nRootType != ScContentId::ROOT && nRootType != ScContentId::DBAREA ) // hidden ? return; ScDocument* pDoc = GetSourceDocument(); if (!pDoc) return; ScDBCollection* pDbNames = pDoc->GetDBCollection(); const ScDBCollection::NamedDBs& rDBs = pDbNames->getNamedDBs(); for (const auto& rxDB : rDBs) { const OUString& aStrName = rxDB->GetName(); InsertContent(ScContentId::DBAREA, aStrName); } } bool ScContentTree::IsPartOfType( ScContentId nContentType, SdrObjKind nObjIdentifier ) { bool bRet = false; switch ( nContentType ) { case ScContentId::GRAPHIC: bRet = ( nObjIdentifier == SdrObjKind::Graphic ); break; case ScContentId::OLEOBJECT: bRet = ( nObjIdentifier == SdrObjKind::OLE2 ); break; case ScContentId::DRAWING: bRet = ( nObjIdentifier != SdrObjKind::Graphic && nObjIdentifier != SdrObjKind::OLE2 ); // everything else break; default: OSL_FAIL("unknown content type"); } return bRet; } constexpr int MAX_TREE_NODES = 1000; void ScContentTree::GetDrawNames( ScContentId nType ) { if (!bIsInNavigatorDlg) return; if ( nRootType != ScContentId::ROOT && nRootType != nType ) // hidden ? return; ScDocument* pDoc = GetSourceDocument(); if (!pDoc) return; ScDrawLayer* pDrawLayer = pDoc->GetDrawLayer(); if (!pDrawLayer) return; ScDocShell* pShell = pDoc->GetDocumentShell(); if (!pShell) return; // iterate in flat mode for groups SdrIterMode eIter = ( nType == ScContentId::DRAWING ) ? SdrIterMode::Flat : SdrIterMode::DeepNoGroups; std::vector aNames; SCTAB nTabCount = pDoc->GetTableCount(); for (SCTAB nTab=0; nTabGetPage(static_cast(nTab)); OSL_ENSURE(pPage,"Page ?"); if (!pPage) continue; SdrObjListIter aIter(pPage, eIter); SdrObject* pObject = aIter.Next(); while (pObject) { if (IsPartOfType(nType, pObject->GetObjIdentifier())) { OUString aName = ScDrawLayer::GetVisibleName(pObject); if (!aName.isEmpty()) aNames.push_back(aName); if (aNames.size() > MAX_TREE_NODES) { SAL_WARN("sc", "too many tree nodes, ignoring the rest"); break; } } pObject = aIter.Next(); } } weld::TreeIter* pParent = m_aRootNodes[nType].get(); assert(pParent && "InsertContent without parent"); // insert all of these in one go under pParent m_xTreeView->bulk_insert_for_each(aNames.size(), [this, &aNames](weld::TreeIter& rIter, int nIndex) { m_xTreeView->set_text(rIter, aNames[nIndex], 0); m_xTreeView->set_sensitive(rIter, true); }, pParent); } void ScContentTree::GetGraphicNames() { GetDrawNames( ScContentId::GRAPHIC ); } void ScContentTree::GetOleNames() { GetDrawNames( ScContentId::OLEOBJECT ); } void ScContentTree::GetDrawingNames() { GetDrawNames( ScContentId::DRAWING ); } void ScContentTree::GetLinkNames() { if ( nRootType != ScContentId::ROOT && nRootType != ScContentId::AREALINK ) // hidden ? return; ScDocument* pDoc = GetSourceDocument(); if (!pDoc) return; sfx2::LinkManager* pLinkManager = pDoc->GetLinkManager(); assert(pLinkManager && "no LinkManager on document?"); const ::sfx2::SvBaseLinks& rLinks = pLinkManager->GetLinks(); sal_uInt16 nCount = rLinks.size(); for (sal_uInt16 i=0; i( pBase)) InsertContent( ScContentId::AREALINK, pScAreaLink->GetSource() ); // insert in list the names of source areas } } const ScAreaLink* ScContentTree::GetLink( sal_uLong nIndex ) { ScDocument* pDoc = GetSourceDocument(); if (!pDoc) return nullptr; sal_uLong nFound = 0; sfx2::LinkManager* pLinkManager = pDoc->GetLinkManager(); assert(pLinkManager && "no LinkManager on document?"); const ::sfx2::SvBaseLinks& rLinks = pLinkManager->GetLinks(); sal_uInt16 nCount = rLinks.size(); for (sal_uInt16 i=0; i( pBase)) { if (nFound == nIndex) return pAreaLink; ++nFound; } } OSL_FAIL("link not found"); return nullptr; } static OUString lcl_NoteString( const ScPostIt& rNote ) { return rNote.GetText().replace('\n', ' '); } void ScContentTree::GetNoteStrings() { if ( nRootType != ScContentId::ROOT && nRootType != ScContentId::NOTE ) // hidden ? return; ScDocument* pDoc = GetSourceDocument(); if (!pDoc) return; // loop over cell notes std::vector aEntries; pDoc->GetAllNoteEntries(aEntries); weld::TreeIter* pParent = m_aRootNodes[ScContentId::NOTE].get(); for (const auto& rEntry : aEntries) { OUString aValue = lcl_NoteString(*rEntry.mpNote); m_xTreeView->insert(pParent, -1, &aValue, nullptr, nullptr, nullptr, false, m_xScratchIter.get()); m_xTreeView->set_sensitive(*m_xScratchIter, true); } } ScAddress ScContentTree::GetNotePos( sal_uLong nIndex ) { ScDocument* pDoc = GetSourceDocument(); if (!pDoc) return ScAddress(); return pDoc->GetNotePosition(nIndex); } bool ScContentTree::NoteStringsChanged() { ScDocument* pDoc = GetSourceDocument(); if (!pDoc) return false; weld::TreeIter* pParent = m_aRootNodes[ScContentId::NOTE].get(); if (!pParent) return false; std::unique_ptr xEntry(m_xTreeView->make_iterator(pParent)); bool bEntry = m_xTreeView->iter_children(*xEntry); std::vector aEntries; pDoc->GetAllNoteEntries(aEntries); for (const auto& rEntry : aEntries) { const ScPostIt* pNote = rEntry.mpNote; if (!bEntry) return true; if (lcl_NoteString(*pNote) != m_xTreeView->get_text(*xEntry)) return true; bEntry = m_xTreeView->iter_next_sibling(*xEntry); } return bEntry; } bool ScContentTree::DrawNamesChanged( ScContentId nType ) { ScDocument* pDoc = GetSourceDocument(); if (!pDoc) return false; weld::TreeIter* pParent = m_aRootNodes[nType].get(); if (!pParent) return false; std::unique_ptr xEntry(m_xTreeView->make_iterator(pParent)); bool bEntry = m_xTreeView->iter_children(*xEntry); // iterate in flat mode for groups SdrIterMode eIter = ( nType == ScContentId::DRAWING ) ? SdrIterMode::Flat : SdrIterMode::DeepNoGroups; bool bEqual = true; ScDrawLayer* pDrawLayer = pDoc->GetDrawLayer(); ScDocShell* pShell = pDoc->GetDocumentShell(); if (pDrawLayer && pShell) { SCTAB nTabCount = pDoc->GetTableCount(); for (SCTAB nTab=0; nTabGetPage(static_cast(nTab)); OSL_ENSURE(pPage,"Page ?"); if (pPage) { SdrObjListIter aIter( pPage, eIter ); SdrObject* pObject = aIter.Next(); while (pObject && bEqual) { if ( IsPartOfType( nType, pObject->GetObjIdentifier() ) ) { if ( !bEntry ) bEqual = false; else { if (ScDrawLayer::GetVisibleName(pObject) != m_xTreeView->get_text(*xEntry)) bEqual = false; bEntry = m_xTreeView->iter_next_sibling(*xEntry); } } pObject = aIter.Next(); } } } } if ( bEntry ) bEqual = false; // anything else return !bEqual; } static bool lcl_GetRange( const ScDocument& rDoc, ScContentId nType, const OUString& rName, ScRange& rRange ) { bool bFound = false; if ( nType == ScContentId::RANGENAME ) { ScRangeName* pList = rDoc.GetRangeName(); if (pList) { const ScRangeData* p = pList->findByUpperName(ScGlobal::getCharClass().uppercase(rName)); if (p && p->IsValidReference(rRange)) bFound = true; } } else if ( nType == ScContentId::DBAREA ) { ScDBCollection* pList = rDoc.GetDBCollection(); if (pList) { const ScDBData* p = pList->getNamedDBs().findByUpperName(ScGlobal::getCharClass().uppercase(rName)); if (p) { SCTAB nTab; SCCOL nCol1, nCol2; SCROW nRow1, nRow2; p->GetArea(nTab, nCol1, nRow1, nCol2, nRow2); rRange = ScRange(nCol1, nRow1, nTab, nCol2, nRow2, nTab); bFound = true; } } } return bFound; } static bool lcl_DoDragObject( ScDocShell* pSrcShell, std::u16string_view rName, ScContentId nType, weld::TreeView& rTreeView ) { bool bDisallow = true; ScDocument& rSrcDoc = pSrcShell->GetDocument(); ScDrawLayer* pModel = rSrcDoc.GetDrawLayer(); if (pModel) { bool bOle = ( nType == ScContentId::OLEOBJECT ); bool bGraf = ( nType == ScContentId::GRAPHIC ); SdrObjKind nDrawId = bOle ? SdrObjKind::OLE2 : ( bGraf ? SdrObjKind::Graphic : SdrObjKind::Group ); SCTAB nTab = 0; SdrObject* pObject = pModel->GetNamedObject( rName, nDrawId, nTab ); if (pObject) { SdrView aEditView(*pModel); aEditView.ShowSdrPage(aEditView.GetModel().GetPage(nTab)); SdrPageView* pPV = aEditView.GetSdrPageView(); aEditView.MarkObj(pObject, pPV); // tdf125520 this is a D&D-start potentially with an OLE object. If // so, we need to do similar as e.g. in ScDrawView::BeginDrag so that // the temporary SdrModel for transfer does have a GetPersist() so // that the EmbeddedObjectContainer gets copied. We need no CheckOle // here, test is simpler. ScDocShellRef aDragShellRef; if(SdrObjKind::OLE2 == pObject->GetObjIdentifier()) { aDragShellRef = new ScDocShell; // DocShell needs a Ref immediately aDragShellRef->DoInitNew(); } ScDrawLayer::SetGlobalDrawPersist(aDragShellRef.get()); std::unique_ptr pDragModel(aEditView.CreateMarkedObjModel()); ScDrawLayer::SetGlobalDrawPersist(nullptr); TransferableObjectDescriptor aObjDesc; pSrcShell->FillTransferableObjectDescriptor( aObjDesc ); aObjDesc.maDisplayName = pSrcShell->GetMedium()->GetURLObject().GetURLNoPass(); // maSize is set in ScDrawTransferObj ctor rtl::Reference pTransferObj = new ScDrawTransferObj( std::move(pDragModel), pSrcShell, std::move(aObjDesc) ); pTransferObj->SetDragSourceObj( *pObject, nTab ); pTransferObj->SetDragSourceFlags(ScDragSrc::Navigator); SC_MOD()->SetDragObject( nullptr, pTransferObj.get() ); rtl::Reference xHelper(pTransferObj); rTreeView.enable_drag_source(xHelper, DND_ACTION_COPY | DND_ACTION_LINK); bDisallow = false; } } return bDisallow; } static bool lcl_DoDragCells( ScDocShell* pSrcShell, const ScRange& rRange, ScDragSrc nFlags, weld::TreeView& rTreeView ) { bool bDisallow = true; ScDocument& rSrcDoc = pSrcShell->GetDocument(); ScMarkData aMark(rSrcDoc.GetSheetLimits()); aMark.SelectTable( rRange.aStart.Tab(), true ); aMark.SetMarkArea( rRange ); if ( !rSrcDoc.HasSelectedBlockMatrixFragment( rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row(), aMark ) ) { ScDocumentUniquePtr pClipDoc(new ScDocument( SCDOCMODE_CLIP )); ScClipParam aClipParam(rRange, false); rSrcDoc.CopyToClip(aClipParam, pClipDoc.get(), &aMark, false, false); // pClipDoc->ExtendMerge( rRange, sal_True ); TransferableObjectDescriptor aObjDesc; pSrcShell->FillTransferableObjectDescriptor( aObjDesc ); aObjDesc.maDisplayName = pSrcShell->GetMedium()->GetURLObject().GetURLNoPass(); // maSize is set in ScTransferObj ctor rtl::Reference pTransferObj = new ScTransferObj( std::move(pClipDoc), std::move(aObjDesc) ); pTransferObj->SetDragSource( pSrcShell, aMark ); pTransferObj->SetDragSourceFlags( nFlags ); SC_MOD()->SetDragObject( pTransferObj.get(), nullptr ); // for internal D&D rtl::Reference xHelper(pTransferObj); rTreeView.enable_drag_source(xHelper, DND_ACTION_COPY | DND_ACTION_LINK); bDisallow = false; } return bDisallow; } IMPL_LINK(ScContentTree, DragBeginHdl, bool&, rUnsetDragIcon, bool) { rUnsetDragIcon = true; StoreNavigatorSettings(); bool bDisallow = true; ScModule* pScMod = SC_MOD(); ScContentId nType; sal_uLong nChild; std::unique_ptr xEntry(m_xTreeView->make_iterator()); if (!m_xTreeView->get_cursor(xEntry.get())) xEntry.reset(); GetEntryIndexes(nType, nChild, xEntry.get()); if( xEntry && (nChild != SC_CONTENT_NOCHILD) && (nType != ScContentId::ROOT) && (nType != ScContentId::NOTE) && (nType != ScContentId::AREALINK) ) { OUString aText(m_xTreeView->get_text(*xEntry)); ScDocument* pLocalDoc = nullptr; // for URL drop OUString aDocName; ScDocShell* pDocSh = GetManualOrCurrent(); if (pDocSh) { if (pDocSh->HasName()) aDocName = pDocSh->GetMedium()->GetName(); else pLocalDoc = &pDocSh->GetDocument(); // drop only in this document } bool bDoLinkTrans = false; // use ScLinkTransferObj OUString aLinkURL; // for ScLinkTransferObj OUString aLinkText; sal_uInt16 nDropMode = pParentWindow->GetDropMode(); switch ( nDropMode ) { case SC_DROPMODE_URL: { OUString aUrl = aDocName + "#" + aText; pScMod->SetDragJump( pLocalDoc, aUrl, aText ); if (!aDocName.isEmpty()) { // provide URL to outside only if the document has a name // (without name, only internal D&D via SetDragJump) aLinkURL = aUrl; aLinkText = aText; } bDoLinkTrans = true; } break; case SC_DROPMODE_LINK: { if ( !aDocName.isEmpty() ) // link only to named documents { // for internal D&D, set flag to insert a link switch ( nType ) { case ScContentId::TABLE: pScMod->SetDragLink( aDocName, aText, OUString() ); bDoLinkTrans = true; break; case ScContentId::RANGENAME: case ScContentId::DBAREA: pScMod->SetDragLink( aDocName, OUString(), aText ); bDoLinkTrans = true; break; // other types cannot be linked default: break; } } } break; case SC_DROPMODE_COPY: { ScDocShell* pSrcShell = GetManualOrCurrent(); if ( pSrcShell ) { ScDocument& rSrcDoc = pSrcShell->GetDocument(); if ( nType == ScContentId::RANGENAME || nType == ScContentId::DBAREA ) { ScRange aRange; if ( lcl_GetRange( rSrcDoc, nType, aText, aRange ) ) { bDisallow = lcl_DoDragCells( pSrcShell, aRange, ScDragSrc::Navigator, *m_xTreeView ); } } else if ( nType == ScContentId::TABLE ) { SCTAB nTab; if ( rSrcDoc.GetTable( aText, nTab ) ) { ScRange aRange(0, 0, nTab, rSrcDoc.MaxCol(), rSrcDoc.MaxRow(), nTab); bDisallow = lcl_DoDragCells( pSrcShell, aRange, (ScDragSrc::Navigator | ScDragSrc::Table), *m_xTreeView ); } } else if ( nType == ScContentId::GRAPHIC || nType == ScContentId::OLEOBJECT || nType == ScContentId::DRAWING ) { bDisallow = lcl_DoDragObject( pSrcShell, aText, nType, *m_xTreeView ); // during ExecuteDrag the navigator can be deleted // -> don't access member anymore !!! } } } break; } if (bDoLinkTrans) { if (!aLinkURL.isEmpty()) m_xTransferObj->SetLinkURL(aLinkURL, aLinkText); rtl::Reference xHelper(m_xTransferObj); m_xTreeView->enable_drag_source(xHelper, DND_ACTION_COPY | DND_ACTION_LINK); bDisallow = false; } } return bDisallow; } void ScContentTree::SetRootType( ScContentId nNew ) { if ( nNew != nRootType ) { nRootType = nNew; Refresh(); ScNavipiCfg& rCfg = SC_MOD()->GetNavipiCfg(); rCfg.SetRootType( nRootType ); } } void ScContentTree::ToggleRoot() // after selection { ScContentId nNew = ScContentId::ROOT; if ( nRootType == ScContentId::ROOT ) { std::unique_ptr xEntry(m_xTreeView->make_iterator()); if (m_xTreeView->get_cursor(xEntry.get())) { std::unique_ptr xParent(m_xTreeView->make_iterator(xEntry.get())); if (!m_xTreeView->iter_parent(*xParent)) xParent.reset(); for (sal_uInt16 i=1; i<=int(ScContentId::LAST); i++) { if (!m_aRootNodes[static_cast(i)]) continue; if ((m_xTreeView->iter_compare(*xEntry, *m_aRootNodes[static_cast(i)]) == 0) || (xParent && m_xTreeView->iter_compare(*xParent, *m_aRootNodes[static_cast(i)]) == 0)) { nNew = static_cast(i); } } } } SetRootType( nNew ); } void ScContentTree::ResetManualDoc() { aManualDoc.clear(); ActiveDocChanged(); } bool ScContentTree::ActiveDocChanged() { bool bRefreshed = false; if (aManualDoc.isEmpty()) { Refresh(); // content only if automatic bRefreshed = true; } // if flag active Listbox must be updated OUString aCurrent; ScDocShell* pSh = GetManualOrCurrent(); if (pSh) aCurrent = pSh->GetTitle(); else { // document is no longer available aManualDoc.clear(); // again automatically Refresh(); bRefreshed = true; pSh = GetManualOrCurrent(); // should be active now if (pSh) aCurrent = pSh->GetTitle(); } pParentWindow->GetDocNames( &aCurrent ); // select return bRefreshed; } void ScContentTree::SetManualDoc(const OUString& rName) { aManualDoc = rName; Refresh(); pParentWindow->GetDocNames( &aManualDoc ); // select } void ScContentTree::SelectDoc(const OUString& rName) // rName like shown in Menu/Listbox { if ( rName == pParentWindow->aStrActiveWin ) { ResetManualDoc(); return; } // omit "active" or "inactive" OUString aRealName = rName; sal_Int32 nLen = rName.getLength(); sal_Int32 nActiveStart = nLen - pParentWindow->aStrActive.getLength(); if ( rName.subView( nActiveStart ) == pParentWindow->aStrActive ) aRealName = rName.copy( 0, nActiveStart ); sal_Int32 nNotActiveStart = nLen - pParentWindow->aStrNotActive.getLength(); if ( rName.subView( nNotActiveStart ) == pParentWindow->aStrNotActive ) aRealName = rName.copy( 0, nNotActiveStart ); bool bLoaded = false; // Is it a normally loaded document? SfxObjectShell* pSh = SfxObjectShell::GetFirst(); while ( pSh && !bLoaded ) { if ( dynamic_cast( pSh) != nullptr ) if ( pSh->GetTitle() == aRealName ) bLoaded = true; pSh = SfxObjectShell::GetNext( *pSh ); } if (bLoaded) { SetManualDoc(aRealName); } else { OSL_FAIL("SelectDoc: not found"); } } void ScContentTree::SelectEntryByName(const ScContentId nRoot, std::u16string_view rName) { weld::TreeIter* pParent = m_aRootNodes[nRoot].get(); if (!pParent || !m_xTreeView->iter_has_child(*pParent)) return; std::unique_ptr xEntry(m_xTreeView->make_iterator(pParent)); bool bEntry = m_xTreeView->iter_children(*xEntry); while (bEntry) { if (m_xTreeView->get_text(*xEntry) == rName) { m_xTreeView->select(*xEntry); m_xTreeView->set_cursor(*xEntry); // Scroll to the selected item m_xTreeView->scroll_to_row(*xEntry); StoreNavigatorSettings(); return; } bEntry = m_xTreeView->iter_next(*xEntry); } } void ScContentTree::ApplyNavigatorSettings() { const ScNavigatorSettings* pSettings = ScNavigatorDlg::GetNavigatorSettings(); if( !pSettings ) return; ScContentId nRootSel = pSettings->GetRootSelected(); auto nChildSel = pSettings->GetChildSelected(); // tdf#133079 ensure Sheet root is selected if nothing // else would be if (nRootSel == ScContentId::ROOT) { nRootSel = ScContentId::TABLE; nChildSel = SC_CONTENT_NOCHILD; } for( int i = 1; i <= int(ScContentId::LAST); ++i ) { ScContentId nEntry = static_cast(i); if( m_aRootNodes[ nEntry ] ) { // gray or ungray if (!m_xTreeView->iter_has_child(*m_aRootNodes[nEntry])) m_xTreeView->set_sensitive(*m_aRootNodes[nEntry], false); else m_xTreeView->set_sensitive(*m_aRootNodes[nEntry], true); // expand bool bExp = pSettings->IsExpanded( nEntry ); if (bExp != m_xTreeView->get_row_expanded(*m_aRootNodes[nEntry])) { if( bExp ) m_xTreeView->expand_row(*m_aRootNodes[nEntry]); else m_xTreeView->collapse_row(*m_aRootNodes[nEntry]); } // select if( nRootSel == nEntry ) { std::unique_ptr xEntry; if (bExp && (nChildSel != SC_CONTENT_NOCHILD)) { xEntry = m_xTreeView->make_iterator(m_aRootNodes[nEntry].get()); if (!m_xTreeView->iter_children(*xEntry) || !m_xTreeView->iter_nth_sibling(*xEntry, nChildSel)) xEntry.reset(); } m_xTreeView->select(xEntry ? *xEntry : *m_aRootNodes[nEntry]); m_xTreeView->set_cursor(xEntry ? *xEntry : *m_aRootNodes[nEntry]); } } } } void ScContentTree::StoreNavigatorSettings() { if (m_nAsyncMouseReleaseId) { Application::RemoveUserEvent(m_nAsyncMouseReleaseId); m_nAsyncMouseReleaseId = nullptr; } ScNavigatorSettings* pSettings = ScNavigatorDlg::GetNavigatorSettings(); if( !pSettings ) return; for( int i = 1; i <= int(ScContentId::LAST); ++i ) { ScContentId nEntry = static_cast(i); bool bExp = m_aRootNodes[nEntry] && m_xTreeView->get_row_expanded(*m_aRootNodes[nEntry]); pSettings->SetExpanded( nEntry, bExp ); } std::unique_ptr xCurEntry(m_xTreeView->make_iterator()); if (!m_xTreeView->get_cursor(xCurEntry.get())) xCurEntry.reset(); ScContentId nRoot; sal_uLong nChild; GetEntryIndexes(nRoot, nChild, xCurEntry.get()); pSettings->SetRootSelected( nRoot ); pSettings->SetChildSelected( nChild ); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */