Files
libreoffice/sc/source/ui/xmlsource/xmlsourcedlg.cxx
Tomaž Vajngerl 25a0bd7b90 refactor TreeListBox to use RenderContext
Change-Id: I901a1f1f9732fb66718dca34c698a851e5b0d87f
2015-05-11 10:03:29 +09:00

698 lines
19 KiB
C++

/* -*- 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/.
*/
#include "xmlsourcedlg.hxx"
#include "sc.hrc"
#include "scresid.hxx"
#include "document.hxx"
#include "orcusfilters.hxx"
#include "filter.hxx"
#include "reffact.hxx"
#include "tabvwsh.hxx"
#include <unotools/pathoptions.hxx>
#include <tools/urlobj.hxx>
#include <svtools/svlbitm.hxx>
#include <svtools/treelistentry.hxx>
#include <svtools/viewdataentry.hxx>
#include <sfx2/objsh.hxx>
#include <com/sun/star/lang/XMultiServiceFactory.hpp>
#include <com/sun/star/ui/dialogs/FilePicker.hpp>
#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp>
#include <com/sun/star/ui/dialogs/TemplateDescription.hpp>
using namespace com::sun::star;
namespace {
bool isAttribute(const SvTreeListEntry& rEntry)
{
const ScOrcusXMLTreeParam::EntryData* pUserData = ScOrcusXMLTreeParam::getUserData(rEntry);
if (!pUserData)
return false;
return pUserData->meType == ScOrcusXMLTreeParam::Attribute;
}
OUString getXPath(
const SvTreeListBox& rTree, const SvTreeListEntry& rEntry, std::vector<size_t>& rNamespaces)
{
OUStringBuffer aBuf;
for (const SvTreeListEntry* p = &rEntry; p; p = rTree.GetParent(p))
{
const SvLBoxItem* pItem = p->GetFirstItem(SV_ITEM_ID_LBOXSTRING);
if (!pItem)
continue;
// Collect used namespace.
const ScOrcusXMLTreeParam::EntryData* pData = ScOrcusXMLTreeParam::getUserData(*p);
if (pData)
rNamespaces.push_back(pData->mnNamespaceID);
const SvLBoxString* pStr = static_cast<const SvLBoxString*>(pItem);
aBuf.insert(0, pStr->GetText());
aBuf.insert(0, isAttribute(*p) ? '@' : '/');
}
return aBuf.makeStringAndClear();
}
}
ScXMLSourceDlg::ScXMLSourceDlg(
SfxBindings* pB, SfxChildWindow* pCW, vcl::Window* pParent, ScDocument* pDoc)
: ScAnyRefDlg(pB, pCW, pParent, "XMLSourceDialog",
"modules/scalc/ui/xmlsourcedialog.ui")
, mpCurRefEntry(NULL)
, mpDoc(pDoc)
, mbDlgLostFocus(false)
{
get(mpBtnSelectSource, "selectsource");
get(mpFtSourceFile, "sourcefile");
get(mpMapGrid, "mapgrid");
get(mpLbTree, "tree");
Size aTreeSize(mpLbTree->LogicToPixel(Size(130, 120), MAP_APPFONT));
mpLbTree->set_width_request(aTreeSize.Width());
mpLbTree->set_height_request(aTreeSize.Height());
get(mpRefEdit, "edit");
mpRefEdit->SetReferences(this, NULL);
get(mpRefBtn, "ref");
mpRefBtn->SetReferences(this, mpRefEdit);
get(mpBtnCancel, "cancel");
get(mpBtnOk, "ok");
mpActiveEdit = mpRefEdit;
maXMLParam.maImgElementDefault = Image(ScResId(IMG_ELEMENT_DEFAULT));
maXMLParam.maImgElementRepeat = Image(ScResId(IMG_ELEMENT_REPEAT));
maXMLParam.maImgAttribute = Image(ScResId(IMG_ELEMENT_ATTRIBUTE));
Link<> aBtnHdl = LINK(this, ScXMLSourceDlg, BtnPressedHdl);
mpBtnSelectSource->SetClickHdl(aBtnHdl);
mpBtnOk->SetClickHdl(aBtnHdl);
mpBtnCancel->SetClickHdl(aBtnHdl);
Link<> aLink = LINK(this, ScXMLSourceDlg, GetFocusHdl);
mpRefEdit->SetGetFocusHdl(aLink);
mpRefBtn->SetGetFocusHdl(aLink);
aLink = LINK(this, ScXMLSourceDlg, TreeItemSelectHdl);
mpLbTree->SetSelectHdl(aLink);
aLink = LINK(this, ScXMLSourceDlg, RefModifiedHdl);
mpRefEdit->SetModifyHdl(aLink);
mpBtnOk->Disable();
SetNonLinkable();
mpBtnSelectSource->GrabFocus(); // Initial focus is on the select source button.
}
ScXMLSourceDlg::~ScXMLSourceDlg()
{
disposeOnce();
}
void ScXMLSourceDlg::dispose()
{
mpBtnSelectSource.clear();
mpFtSourceFile.clear();
mpMapGrid.clear();
mpLbTree.clear();
mpRefEdit.clear();
mpRefBtn.clear();
mpBtnOk.clear();
mpBtnCancel.clear();
mpActiveEdit.clear();
ScAnyRefDlg::dispose();
}
bool ScXMLSourceDlg::IsRefInputMode() const
{
return mpActiveEdit != nullptr && mpActiveEdit->IsEnabled();
}
void ScXMLSourceDlg::SetReference(const ScRange& rRange, ScDocument* pDoc)
{
if (!mpActiveEdit)
return;
if (rRange.aStart != rRange.aEnd)
RefInputStart(mpActiveEdit);
OUString aStr(rRange.aStart.Format(SCA_ABS_3D, pDoc, pDoc->GetAddressConvention()));
mpActiveEdit->SetRefString(aStr);
RefEditModified();
}
void ScXMLSourceDlg::Deactivate()
{
mbDlgLostFocus = true;
}
void ScXMLSourceDlg::SetActive()
{
if (mbDlgLostFocus)
{
mbDlgLostFocus = false;
if (mpActiveEdit)
{
mpActiveEdit->GrabFocus();
}
}
else
{
GrabFocus();
}
RefInputDone();
}
bool ScXMLSourceDlg::Close()
{
return DoClose(ScXMLSourceDlgWrapper::GetChildWindowId());
}
void ScXMLSourceDlg::SelectSourceFile()
{
uno::Reference<ui::dialogs::XFilePicker3> xFilePicker = ui::dialogs::FilePicker::createWithMode( comphelper::getProcessComponentContext(), ui::dialogs::TemplateDescription::FILEOPEN_SIMPLE );
if (maSrcPath.isEmpty())
// Use default path.
xFilePicker->setDisplayDirectory(SvtPathOptions().GetWorkPath());
else
{
// Use the directory of current source file.
INetURLObject aURL(maSrcPath);
aURL.removeSegment();
aURL.removeFinalSlash();
OUString aPath = aURL.GetMainURL(INetURLObject::NO_DECODE);
xFilePicker->setDisplayDirectory(aPath);
}
if (xFilePicker->execute() != ui::dialogs::ExecutableDialogResults::OK)
// File picker dialog cancelled.
return;
uno::Sequence<OUString> aFiles = xFilePicker->getFiles();
if (!aFiles.getLength())
return;
// There should only be one file returned from the file picker.
maSrcPath = aFiles[0];
mpFtSourceFile->SetText(maSrcPath);
maHighlightedEntries.clear();
LoadSourceFileStructure(maSrcPath);
}
void ScXMLSourceDlg::LoadSourceFileStructure(const OUString& rPath)
{
ScOrcusFilters* pOrcus = ScFormatFilter::Get().GetOrcusFilters();
if (!pOrcus)
return;
mpXMLContext.reset(pOrcus->createXMLContext(*mpDoc, rPath));
if (!mpXMLContext)
return;
mpXMLContext->loadXMLStructure(*mpLbTree, maXMLParam);
}
void ScXMLSourceDlg::HandleGetFocus(Control* pCtrl)
{
mpActiveEdit = NULL;
if (pCtrl == mpRefEdit || pCtrl == mpRefBtn)
mpActiveEdit = mpRefEdit;
if (mpActiveEdit)
mpActiveEdit->SetSelection(Selection(0, SELECTION_MAX));
}
namespace {
class UnhighlightEntry : std::unary_function<SvTreeListEntry*, void>
{
SvTreeListBox& mrTree;
public:
UnhighlightEntry(SvTreeListBox& rTree) : mrTree(rTree) {}
void operator() (SvTreeListEntry* p)
{
SvViewDataEntry* pView = mrTree.GetViewDataEntry(p);
if (!pView)
return;
pView->SetHighlighted(false);
mrTree.Invalidate();
}
};
/**
* When the current entry is a direct or indirect child of a mappable
* repeat element entry, that entry becomes the reference entry.
* Otherwise the reference entry equals the current entry. A reference
* entry is the entry that stores mapped cell position.
*/
SvTreeListEntry* getReferenceEntry(SvTreeListBox& rTree, SvTreeListEntry* pCurEntry)
{
SvTreeListEntry* pParent = rTree.GetParent(pCurEntry);
SvTreeListEntry* pRefEntry = NULL;
while (pParent)
{
ScOrcusXMLTreeParam::EntryData* pUserData = ScOrcusXMLTreeParam::getUserData(*pParent);
OSL_ASSERT(pUserData);
if (pUserData->meType == ScOrcusXMLTreeParam::ElementRepeat)
{
// This is a repeat element.
if (pRefEntry)
{
// Second repeat element encountered. Not good.
return pCurEntry;
}
pRefEntry = pParent;
}
pParent = rTree.GetParent(pParent);
}
return pRefEntry ? pRefEntry : pCurEntry;
}
}
void ScXMLSourceDlg::TreeItemSelected()
{
SvTreeListEntry* pEntry = mpLbTree->GetCurEntry();
if (!pEntry)
return;
if (!maHighlightedEntries.empty())
{
// Remove highlights from all previously highlighted entries (if any).
std::for_each(maHighlightedEntries.begin(), maHighlightedEntries.end(), UnhighlightEntry(*mpLbTree));
maHighlightedEntries.clear();
}
mpCurRefEntry = getReferenceEntry(*mpLbTree, pEntry);
ScOrcusXMLTreeParam::EntryData* pUserData = ScOrcusXMLTreeParam::getUserData(*mpCurRefEntry);
OSL_ASSERT(pUserData);
const ScAddress& rPos = pUserData->maLinkedPos;
if (rPos.IsValid())
{
OUString aStr(rPos.Format(SCA_ABS_3D, mpDoc, mpDoc->GetAddressConvention()));
mpRefEdit->SetRefString(aStr);
}
else
mpRefEdit->SetRefString(OUString());
switch (pUserData->meType)
{
case ScOrcusXMLTreeParam::Attribute:
AttributeSelected(*mpCurRefEntry);
break;
case ScOrcusXMLTreeParam::ElementDefault:
DefaultElementSelected(*mpCurRefEntry);
break;
case ScOrcusXMLTreeParam::ElementRepeat:
RepeatElementSelected(*mpCurRefEntry);
break;
default:
;
}
}
void ScXMLSourceDlg::DefaultElementSelected(SvTreeListEntry& rEntry)
{
if (mpLbTree->GetChildCount(&rEntry) > 0)
{
// Only an element with no child elements (leaf element) can be linked.
bool bHasChild = false;
for (SvTreeListEntry* pChild = mpLbTree->FirstChild(&rEntry); pChild; pChild = SvTreeListBox::NextSibling(pChild))
{
ScOrcusXMLTreeParam::EntryData* pUserData = ScOrcusXMLTreeParam::getUserData(*pChild);
OSL_ASSERT(pUserData);
if (pUserData->meType != ScOrcusXMLTreeParam::Attribute)
{
// This child is not an attribute. Bail out.
bHasChild = true;
break;
}
}
if (bHasChild)
{
SetNonLinkable();
return;
}
}
// Check all its parents and make sure non of them are range-linked nor
// repeat elements.
if (IsParentDirty(&rEntry))
{
SetNonLinkable();
return;
}
SetSingleLinkable();
}
void ScXMLSourceDlg::RepeatElementSelected(SvTreeListEntry& rEntry)
{
// Check all its parents first.
if (IsParentDirty(&rEntry))
{
SetNonLinkable();
return;
}
// Check all its child elements / attributes and make sure non of them are
// linked or repeat elements. In the future we will support range linking
// of repeat element who has another repeat elements. But first I need to
// support that scenario in orcus.
if (IsChildrenDirty(&rEntry))
{
SetNonLinkable();
return;
}
SvViewDataEntry* p = mpLbTree->GetViewDataEntry(&rEntry);
if (!p->IsHighlighted())
{
// Highlight the entry if not highlighted already. This can happen
// when the current entry is a child entry of a repeat element entry.
p->SetHighlighted(true);
mpLbTree->Invalidate();
maHighlightedEntries.push_back(&rEntry);
}
SelectAllChildEntries(rEntry);
SetRangeLinkable();
}
void ScXMLSourceDlg::AttributeSelected(SvTreeListEntry& rEntry)
{
// Check all its parent elements and make sure non of them are linked nor
// repeat elements. In attribute's case, it's okay to have the immediate
// parent element linked (but not range-linked).
SvTreeListEntry* pParent = mpLbTree->GetParent(&rEntry);
OSL_ASSERT(pParent); // attribute should have a parent element.
ScOrcusXMLTreeParam::EntryData* pUserData = ScOrcusXMLTreeParam::getUserData(*pParent);
OSL_ASSERT(pUserData);
if (pUserData->maLinkedPos.IsValid() && pUserData->mbRangeParent)
{
// Parent element is range-linked. Bail out.
SetNonLinkable();
return;
}
if (IsParentDirty(&rEntry))
{
SetNonLinkable();
return;
}
SetSingleLinkable();
}
void ScXMLSourceDlg::SetNonLinkable()
{
mpMapGrid->Disable();
}
void ScXMLSourceDlg::SetSingleLinkable()
{
mpMapGrid->Enable();
}
void ScXMLSourceDlg::SetRangeLinkable()
{
mpMapGrid->Enable();
}
void ScXMLSourceDlg::SelectAllChildEntries(SvTreeListEntry& rEntry)
{
SvTreeListEntries& rChildren = rEntry.GetChildEntries();
SvTreeListEntries::iterator it = rChildren.begin(), itEnd = rChildren.end();
for (; it != itEnd; ++it)
{
SvTreeListEntry& r = *it;
SelectAllChildEntries(r); // select recursively.
SvViewDataEntry* p = mpLbTree->GetViewDataEntry(&r);
p->SetHighlighted(true);
mpLbTree->Invalidate();
maHighlightedEntries.push_back(&r);
}
}
bool ScXMLSourceDlg::IsParentDirty(SvTreeListEntry* pEntry) const
{
SvTreeListEntry* pParent = mpLbTree->GetParent(pEntry);
while (pParent)
{
ScOrcusXMLTreeParam::EntryData* pUserData = ScOrcusXMLTreeParam::getUserData(*pParent);
assert(pUserData);
if (pUserData->maLinkedPos.IsValid())
{
// This parent is already linked.
return true;
}
if (pUserData->meType == ScOrcusXMLTreeParam::ElementRepeat)
{
// This is a repeat element.
return true;
}
pParent = mpLbTree->GetParent(pParent);
}
return false;
}
bool ScXMLSourceDlg::IsChildrenDirty(SvTreeListEntry* pEntry) const
{
for (SvTreeListEntry* pChild = mpLbTree->FirstChild(pEntry); pChild; pChild = SvTreeListBox::NextSibling(pChild))
{
ScOrcusXMLTreeParam::EntryData* pUserData = ScOrcusXMLTreeParam::getUserData(*pChild);
OSL_ASSERT(pUserData);
if (pUserData->maLinkedPos.IsValid())
// Already linked.
return true;
if (pUserData->meType == ScOrcusXMLTreeParam::ElementRepeat)
// We don't support linking of nested repeat elements (yet).
return true;
if (pUserData->meType == ScOrcusXMLTreeParam::ElementDefault)
{
// Check recursively.
if (IsChildrenDirty(pChild))
return true;
}
}
return false;
}
namespace {
/**
* Pick only the leaf elements.
*/
void getFieldLinks(
ScOrcusImportXMLParam::RangeLink& rRangeLink, std::vector<size_t>& rNamespaces,
const SvTreeListBox& rTree, const SvTreeListEntry& rEntry)
{
const SvTreeListEntries& rChildren = rEntry.GetChildEntries();
if (rChildren.empty())
// No more children. We're done.
return;
SvTreeListEntries::const_iterator it = rChildren.begin(), itEnd = rChildren.end();
for (; it != itEnd; ++it)
{
const SvTreeListEntry& rChild = *it;
OUString aPath = getXPath(rTree, rChild, rNamespaces);
const ScOrcusXMLTreeParam::EntryData* pUserData = ScOrcusXMLTreeParam::getUserData(rChild);
if (pUserData && pUserData->mbLeafNode)
{
if (!aPath.isEmpty())
// XPath should never be empty anyway, but it won't hurt to check...
rRangeLink.maFieldPaths.push_back(OUStringToOString(aPath, RTL_TEXTENCODING_UTF8));
}
// Walk recursively.
getFieldLinks(rRangeLink, rNamespaces, rTree, rChild);
}
}
void removeDuplicates(std::vector<size_t>& rArray)
{
std::sort(rArray.begin(), rArray.end());
std::vector<size_t>::iterator it = std::unique(rArray.begin(), rArray.end());
rArray.erase(it, rArray.end());
}
}
void ScXMLSourceDlg::OkPressed()
{
if (!mpXMLContext)
return;
// Begin import.
ScOrcusImportXMLParam aParam;
// Convert single cell links.
{
std::set<const SvTreeListEntry*>::const_iterator it = maCellLinks.begin(), itEnd = maCellLinks.end();
for (; it != itEnd; ++it)
{
const SvTreeListEntry& rEntry = **it;
OUString aPath = getXPath(*mpLbTree, rEntry, aParam.maNamespaces);
const ScOrcusXMLTreeParam::EntryData* pUserData = ScOrcusXMLTreeParam::getUserData(rEntry);
aParam.maCellLinks.push_back(
ScOrcusImportXMLParam::CellLink(
pUserData->maLinkedPos, OUStringToOString(aPath, RTL_TEXTENCODING_UTF8)));
}
}
// Convert range links. For now, an element with range link takes all its
// child elements as its fields.
{
std::set<const SvTreeListEntry*>::const_iterator it = maRangeLinks.begin(), itEnd = maRangeLinks.end();
for (; it != itEnd; ++it)
{
const SvTreeListEntry& rEntry = **it;
const ScOrcusXMLTreeParam::EntryData* pUserData = ScOrcusXMLTreeParam::getUserData(rEntry);
ScOrcusImportXMLParam::RangeLink aRangeLink;
aRangeLink.maPos = pUserData->maLinkedPos;
// Go through all its child elements.
getFieldLinks(aRangeLink, aParam.maNamespaces, *mpLbTree, rEntry);
aParam.maRangeLinks.push_back(aRangeLink);
}
}
// Remove duplicate namespace IDs.
removeDuplicates(aParam.maNamespaces);
// Now do the import.
mpXMLContext->importXML(aParam);
// Don't forget to broadcast the change.
SfxObjectShell* pShell = mpDoc->GetDocumentShell();
pShell->Broadcast(SfxSimpleHint(FID_DATACHANGED));
// Repaint the grid to force repaint the cell values.
ScTabViewShell* pViewShell = ScTabViewShell::GetActiveViewShell();
if (pViewShell)
pViewShell->PaintGrid();
Close();
}
void ScXMLSourceDlg::CancelPressed()
{
Close();
}
void ScXMLSourceDlg::RefEditModified()
{
OUString aRefStr = mpRefEdit->GetText();
// Check if the address is valid.
ScAddress aLinkedPos;
sal_uInt16 nRes = aLinkedPos.Parse(aRefStr, mpDoc, mpDoc->GetAddressConvention());
bool bValid = (nRes & SCA_VALID) == SCA_VALID;
// TODO: For some unknown reason, setting the ref invalid will hide the text altogether.
// Find out how to make this work.
// mpRefEdit->SetRefValid(bValid);
if (!bValid)
aLinkedPos.SetInvalid();
// Set this address to the current reference entry.
if (!mpCurRefEntry)
// This should never happen.
return;
ScOrcusXMLTreeParam::EntryData* pUserData = ScOrcusXMLTreeParam::getUserData(*mpCurRefEntry);
if (!pUserData)
// This should never happen either.
return;
bool bRepeatElem = pUserData->meType == ScOrcusXMLTreeParam::ElementRepeat;
pUserData->maLinkedPos = aLinkedPos;
pUserData->mbRangeParent = aLinkedPos.IsValid() && bRepeatElem;
if (bRepeatElem)
{
if (bValid)
maRangeLinks.insert(mpCurRefEntry);
else
maRangeLinks.erase(mpCurRefEntry);
}
else
{
if (bValid)
maCellLinks.insert(mpCurRefEntry);
else
maCellLinks.erase(mpCurRefEntry);
}
// Enable the import button only when at least one link exists.
bool bHasLink = !maCellLinks.empty() || !maRangeLinks.empty();
mpBtnOk->Enable(bHasLink);
}
IMPL_LINK(ScXMLSourceDlg, GetFocusHdl, Control*, pCtrl)
{
HandleGetFocus(pCtrl);
return 0;
}
IMPL_LINK(ScXMLSourceDlg, BtnPressedHdl, Button*, pBtn)
{
if (pBtn == mpBtnSelectSource)
SelectSourceFile();
else if (pBtn == mpBtnOk)
OkPressed();
else if (pBtn == mpBtnCancel)
CancelPressed();
return 0;
}
IMPL_LINK_NOARG(ScXMLSourceDlg, TreeItemSelectHdl)
{
TreeItemSelected();
return 0;
}
IMPL_LINK_NOARG(ScXMLSourceDlg, RefModifiedHdl)
{
RefEditModified();
return 0;
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */