Version 5.2.6.1, tag libreoffice-5.2.6.1
[LibreOffice.git] / sc / source / ui / xmlsource / xmlsourcedlg.cxx
blob433e31a4831f490cc4838b49c2464bbddecc6d9c
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
10 #include "xmlsourcedlg.hxx"
11 #include "sc.hrc"
12 #include "scresid.hxx"
13 #include "document.hxx"
14 #include "orcusfilters.hxx"
15 #include "filter.hxx"
16 #include "reffact.hxx"
17 #include "tabvwsh.hxx"
19 #include <unotools/pathoptions.hxx>
20 #include <tools/urlobj.hxx>
21 #include <svtools/svlbitm.hxx>
22 #include <svtools/treelistentry.hxx>
23 #include <svtools/viewdataentry.hxx>
24 #include <sfx2/objsh.hxx>
26 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
27 #include <com/sun/star/ui/dialogs/FilePicker.hpp>
28 #include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp>
29 #include <com/sun/star/ui/dialogs/TemplateDescription.hpp>
31 using namespace com::sun::star;
33 namespace {
35 bool isAttribute(const SvTreeListEntry& rEntry)
37 const ScOrcusXMLTreeParam::EntryData* pUserData = ScOrcusXMLTreeParam::getUserData(rEntry);
38 if (!pUserData)
39 return false;
41 return pUserData->meType == ScOrcusXMLTreeParam::Attribute;
44 OUString getXPath(
45 const SvTreeListBox& rTree, const SvTreeListEntry& rEntry, std::vector<size_t>& rNamespaces)
47 OUStringBuffer aBuf;
48 for (const SvTreeListEntry* p = &rEntry; p; p = rTree.GetParent(p))
50 const SvLBoxItem* pItem = p->GetFirstItem(SV_ITEM_ID_LBOXSTRING);
51 if (!pItem)
52 continue;
54 // Collect used namespace.
55 const ScOrcusXMLTreeParam::EntryData* pData = ScOrcusXMLTreeParam::getUserData(*p);
56 if (pData)
57 rNamespaces.push_back(pData->mnNamespaceID);
59 const SvLBoxString* pStr = static_cast<const SvLBoxString*>(pItem);
60 aBuf.insert(0, pStr->GetText());
61 aBuf.insert(0, isAttribute(*p) ? '@' : '/');
64 return aBuf.makeStringAndClear();
69 ScXMLSourceDlg::ScXMLSourceDlg(
70 SfxBindings* pB, SfxChildWindow* pCW, vcl::Window* pParent, ScDocument* pDoc)
71 : ScAnyRefDlg(pB, pCW, pParent, "XMLSourceDialog",
72 "modules/scalc/ui/xmlsourcedialog.ui")
73 , mpCurRefEntry(nullptr)
74 , mpDoc(pDoc)
75 , mbDlgLostFocus(false)
77 get(mpBtnSelectSource, "selectsource");
78 get(mpFtSourceFile, "sourcefile");
79 get(mpMapGrid, "mapgrid");
80 get(mpLbTree, "tree");
81 Size aTreeSize(mpLbTree->LogicToPixel(Size(130, 120), MAP_APPFONT));
82 mpLbTree->set_width_request(aTreeSize.Width());
83 mpLbTree->set_height_request(aTreeSize.Height());
84 get(mpRefEdit, "edit");
85 mpRefEdit->SetReferences(this, nullptr);
86 get(mpRefBtn, "ref");
87 mpRefBtn->SetReferences(this, mpRefEdit);
88 get(mpBtnCancel, "cancel");
89 get(mpBtnOk, "ok");
91 mpActiveEdit = mpRefEdit;
93 maXMLParam.maImgElementDefault = Image(ScResId(IMG_ELEMENT_DEFAULT));
94 maXMLParam.maImgElementRepeat = Image(ScResId(IMG_ELEMENT_REPEAT));
95 maXMLParam.maImgAttribute = Image(ScResId(IMG_ELEMENT_ATTRIBUTE));
97 Link<Button*,void> aBtnHdl = LINK(this, ScXMLSourceDlg, BtnPressedHdl);
98 mpBtnSelectSource->SetClickHdl(aBtnHdl);
99 mpBtnOk->SetClickHdl(aBtnHdl);
100 mpBtnCancel->SetClickHdl(aBtnHdl);
102 Link<Control&,void> aLink2 = LINK(this, ScXMLSourceDlg, GetFocusHdl);
103 mpRefEdit->SetGetFocusHdl(aLink2);
104 mpRefBtn->SetGetFocusHdl(aLink2);
106 mpLbTree->SetSelectHdl(LINK(this, ScXMLSourceDlg, TreeItemSelectHdl));
108 Link<Edit&,void> aLink = LINK(this, ScXMLSourceDlg, RefModifiedHdl);
109 mpRefEdit->SetModifyHdl(aLink);
111 mpBtnOk->Disable();
113 SetNonLinkable();
114 mpBtnSelectSource->GrabFocus(); // Initial focus is on the select source button.
117 ScXMLSourceDlg::~ScXMLSourceDlg()
119 disposeOnce();
122 void ScXMLSourceDlg::dispose()
124 mpBtnSelectSource.clear();
125 mpFtSourceFile.clear();
126 mpMapGrid.clear();
127 mpLbTree.clear();
128 mpRefEdit.clear();
129 mpRefBtn.clear();
130 mpBtnOk.clear();
131 mpBtnCancel.clear();
132 mpActiveEdit.clear();
133 ScAnyRefDlg::dispose();
136 bool ScXMLSourceDlg::IsRefInputMode() const
138 return mpActiveEdit != nullptr && mpActiveEdit->IsEnabled();
141 void ScXMLSourceDlg::SetReference(const ScRange& rRange, ScDocument* pDoc)
143 if (!mpActiveEdit)
144 return;
146 if (rRange.aStart != rRange.aEnd)
147 RefInputStart(mpActiveEdit);
149 OUString aStr(rRange.aStart.Format(ScRefFlags::ADDR_ABS_3D, pDoc, pDoc->GetAddressConvention()));
150 mpActiveEdit->SetRefString(aStr);
152 RefEditModified();
155 void ScXMLSourceDlg::Deactivate()
157 mbDlgLostFocus = true;
160 void ScXMLSourceDlg::SetActive()
162 if (mbDlgLostFocus)
164 mbDlgLostFocus = false;
165 if (mpActiveEdit)
167 mpActiveEdit->GrabFocus();
170 else
172 GrabFocus();
175 RefInputDone();
178 bool ScXMLSourceDlg::Close()
180 return DoClose(ScXMLSourceDlgWrapper::GetChildWindowId());
183 void ScXMLSourceDlg::SelectSourceFile()
185 uno::Reference<ui::dialogs::XFilePicker3> xFilePicker = ui::dialogs::FilePicker::createWithMode( comphelper::getProcessComponentContext(), ui::dialogs::TemplateDescription::FILEOPEN_SIMPLE );
187 if (maSrcPath.isEmpty())
188 // Use default path.
189 xFilePicker->setDisplayDirectory(SvtPathOptions().GetWorkPath());
190 else
192 // Use the directory of current source file.
193 INetURLObject aURL(maSrcPath);
194 aURL.removeSegment();
195 aURL.removeFinalSlash();
196 OUString aPath = aURL.GetMainURL(INetURLObject::NO_DECODE);
197 xFilePicker->setDisplayDirectory(aPath);
200 if (xFilePicker->execute() != ui::dialogs::ExecutableDialogResults::OK)
201 // File picker dialog cancelled.
202 return;
204 uno::Sequence<OUString> aFiles = xFilePicker->getSelectedFiles();
205 if (!aFiles.getLength())
206 return;
208 // There should only be one file returned from the file picker.
209 maSrcPath = aFiles[0];
210 mpFtSourceFile->SetText(maSrcPath);
211 maHighlightedEntries.clear();
212 LoadSourceFileStructure(maSrcPath);
215 void ScXMLSourceDlg::LoadSourceFileStructure(const OUString& rPath)
217 ScOrcusFilters* pOrcus = ScFormatFilter::Get().GetOrcusFilters();
218 if (!pOrcus)
219 return;
221 mpXMLContext.reset(pOrcus->createXMLContext(*mpDoc, rPath));
222 if (!mpXMLContext)
223 return;
225 mpXMLContext->loadXMLStructure(*mpLbTree, maXMLParam);
228 void ScXMLSourceDlg::HandleGetFocus(Control* pCtrl)
230 mpActiveEdit = nullptr;
231 if (pCtrl == mpRefEdit || pCtrl == mpRefBtn)
232 mpActiveEdit = mpRefEdit;
234 if (mpActiveEdit)
235 mpActiveEdit->SetSelection(Selection(0, SELECTION_MAX));
238 namespace {
240 class UnhighlightEntry : public std::unary_function<SvTreeListEntry*, void>
242 SvTreeListBox& mrTree;
243 public:
244 explicit UnhighlightEntry(SvTreeListBox& rTree) : mrTree(rTree) {}
246 void operator() (SvTreeListEntry* p)
248 SvViewDataEntry* pView = mrTree.GetViewDataEntry(p);
249 if (!pView)
250 return;
252 pView->SetHighlighted(false);
253 mrTree.Invalidate();
258 * When the current entry is a direct or indirect child of a mappable
259 * repeat element entry, that entry becomes the reference entry.
260 * Otherwise the reference entry equals the current entry. A reference
261 * entry is the entry that stores mapped cell position.
263 SvTreeListEntry* getReferenceEntry(SvTreeListBox& rTree, SvTreeListEntry* pCurEntry)
265 SvTreeListEntry* pParent = rTree.GetParent(pCurEntry);
266 SvTreeListEntry* pRefEntry = nullptr;
267 while (pParent)
269 ScOrcusXMLTreeParam::EntryData* pUserData = ScOrcusXMLTreeParam::getUserData(*pParent);
270 OSL_ASSERT(pUserData);
271 if (pUserData->meType == ScOrcusXMLTreeParam::ElementRepeat)
273 // This is a repeat element.
274 if (pRefEntry)
276 // Second repeat element encountered. Not good.
277 return pCurEntry;
280 pRefEntry = pParent;
282 pParent = rTree.GetParent(pParent);
285 return pRefEntry ? pRefEntry : pCurEntry;
290 void ScXMLSourceDlg::TreeItemSelected()
292 SvTreeListEntry* pEntry = mpLbTree->GetCurEntry();
293 if (!pEntry)
294 return;
296 if (!maHighlightedEntries.empty())
298 // Remove highlights from all previously highlighted entries (if any).
299 std::for_each(maHighlightedEntries.begin(), maHighlightedEntries.end(), UnhighlightEntry(*mpLbTree));
300 maHighlightedEntries.clear();
303 mpCurRefEntry = getReferenceEntry(*mpLbTree, pEntry);
305 ScOrcusXMLTreeParam::EntryData* pUserData = ScOrcusXMLTreeParam::getUserData(*mpCurRefEntry);
306 OSL_ASSERT(pUserData);
308 const ScAddress& rPos = pUserData->maLinkedPos;
309 if (rPos.IsValid())
311 OUString aStr(rPos.Format(ScRefFlags::ADDR_ABS_3D, mpDoc, mpDoc->GetAddressConvention()));
312 mpRefEdit->SetRefString(aStr);
314 else
315 mpRefEdit->SetRefString(OUString());
317 switch (pUserData->meType)
319 case ScOrcusXMLTreeParam::Attribute:
320 AttributeSelected(*mpCurRefEntry);
321 break;
322 case ScOrcusXMLTreeParam::ElementDefault:
323 DefaultElementSelected(*mpCurRefEntry);
324 break;
325 case ScOrcusXMLTreeParam::ElementRepeat:
326 RepeatElementSelected(*mpCurRefEntry);
327 break;
328 default:
333 void ScXMLSourceDlg::DefaultElementSelected(SvTreeListEntry& rEntry)
336 if (mpLbTree->GetChildCount(&rEntry) > 0)
338 // Only an element with no child elements (leaf element) can be linked.
339 bool bHasChild = false;
340 for (SvTreeListEntry* pChild = mpLbTree->FirstChild(&rEntry); pChild; pChild = SvTreeListBox::NextSibling(pChild))
342 ScOrcusXMLTreeParam::EntryData* pUserData = ScOrcusXMLTreeParam::getUserData(*pChild);
343 OSL_ASSERT(pUserData);
344 if (pUserData->meType != ScOrcusXMLTreeParam::Attribute)
346 // This child is not an attribute. Bail out.
347 bHasChild = true;
348 break;
352 if (bHasChild)
354 SetNonLinkable();
355 return;
359 // Check all its parents and make sure non of them are range-linked nor
360 // repeat elements.
361 if (IsParentDirty(&rEntry))
363 SetNonLinkable();
364 return;
367 SetSingleLinkable();
370 void ScXMLSourceDlg::RepeatElementSelected(SvTreeListEntry& rEntry)
372 // Check all its parents first.
374 if (IsParentDirty(&rEntry))
376 SetNonLinkable();
377 return;
380 // Check all its child elements / attributes and make sure non of them are
381 // linked or repeat elements. In the future we will support range linking
382 // of repeat element who has another repeat elements. But first I need to
383 // support that scenario in orcus.
385 if (IsChildrenDirty(&rEntry))
387 SetNonLinkable();
388 return;
391 SvViewDataEntry* p = mpLbTree->GetViewDataEntry(&rEntry);
392 if (!p->IsHighlighted())
394 // Highlight the entry if not highlighted already. This can happen
395 // when the current entry is a child entry of a repeat element entry.
396 p->SetHighlighted(true);
397 mpLbTree->Invalidate();
398 maHighlightedEntries.push_back(&rEntry);
401 SelectAllChildEntries(rEntry);
402 SetRangeLinkable();
405 void ScXMLSourceDlg::AttributeSelected(SvTreeListEntry& rEntry)
407 // Check all its parent elements and make sure non of them are linked nor
408 // repeat elements. In attribute's case, it's okay to have the immediate
409 // parent element linked (but not range-linked).
411 SvTreeListEntry* pParent = mpLbTree->GetParent(&rEntry);
412 OSL_ASSERT(pParent); // attribute should have a parent element.
414 ScOrcusXMLTreeParam::EntryData* pUserData = ScOrcusXMLTreeParam::getUserData(*pParent);
415 OSL_ASSERT(pUserData);
416 if (pUserData->maLinkedPos.IsValid() && pUserData->mbRangeParent)
418 // Parent element is range-linked. Bail out.
419 SetNonLinkable();
420 return;
423 if (IsParentDirty(&rEntry))
425 SetNonLinkable();
426 return;
429 SetSingleLinkable();
432 void ScXMLSourceDlg::SetNonLinkable()
434 mpMapGrid->Disable();
437 void ScXMLSourceDlg::SetSingleLinkable()
439 mpMapGrid->Enable();
442 void ScXMLSourceDlg::SetRangeLinkable()
444 mpMapGrid->Enable();
447 void ScXMLSourceDlg::SelectAllChildEntries(SvTreeListEntry& rEntry)
449 SvTreeListEntries& rChildren = rEntry.GetChildEntries();
450 for (auto const& it : rChildren)
452 SvTreeListEntry& r = *it;
453 SelectAllChildEntries(r); // select recursively.
454 SvViewDataEntry* p = mpLbTree->GetViewDataEntry(&r);
455 p->SetHighlighted(true);
456 mpLbTree->Invalidate();
457 maHighlightedEntries.push_back(&r);
461 bool ScXMLSourceDlg::IsParentDirty(SvTreeListEntry* pEntry) const
463 SvTreeListEntry* pParent = mpLbTree->GetParent(pEntry);
464 while (pParent)
466 ScOrcusXMLTreeParam::EntryData* pUserData = ScOrcusXMLTreeParam::getUserData(*pParent);
467 assert(pUserData);
468 if (pUserData->maLinkedPos.IsValid())
470 // This parent is already linked.
471 return true;
473 if (pUserData->meType == ScOrcusXMLTreeParam::ElementRepeat)
475 // This is a repeat element.
476 return true;
478 pParent = mpLbTree->GetParent(pParent);
480 return false;
483 bool ScXMLSourceDlg::IsChildrenDirty(SvTreeListEntry* pEntry) const
485 for (SvTreeListEntry* pChild = mpLbTree->FirstChild(pEntry); pChild; pChild = SvTreeListBox::NextSibling(pChild))
487 ScOrcusXMLTreeParam::EntryData* pUserData = ScOrcusXMLTreeParam::getUserData(*pChild);
488 OSL_ASSERT(pUserData);
489 if (pUserData->maLinkedPos.IsValid())
490 // Already linked.
491 return true;
493 if (pUserData->meType == ScOrcusXMLTreeParam::ElementRepeat)
494 // We don't support linking of nested repeat elements (yet).
495 return true;
497 if (pUserData->meType == ScOrcusXMLTreeParam::ElementDefault)
499 // Check recursively.
500 if (IsChildrenDirty(pChild))
501 return true;
505 return false;
508 namespace {
511 * Pick only the leaf elements.
513 void getFieldLinks(
514 ScOrcusImportXMLParam::RangeLink& rRangeLink, std::vector<size_t>& rNamespaces,
515 const SvTreeListBox& rTree, const SvTreeListEntry& rEntry)
517 const SvTreeListEntries& rChildren = rEntry.GetChildEntries();
518 if (rChildren.empty())
519 // No more children. We're done.
520 return;
522 for (auto const& it : rChildren)
524 const SvTreeListEntry& rChild = *it;
525 OUString aPath = getXPath(rTree, rChild, rNamespaces);
526 const ScOrcusXMLTreeParam::EntryData* pUserData = ScOrcusXMLTreeParam::getUserData(rChild);
528 if (pUserData && pUserData->mbLeafNode)
530 if (!aPath.isEmpty())
531 // XPath should never be empty anyway, but it won't hurt to check...
532 rRangeLink.maFieldPaths.push_back(OUStringToOString(aPath, RTL_TEXTENCODING_UTF8));
535 // Walk recursively.
536 getFieldLinks(rRangeLink, rNamespaces, rTree, rChild);
540 void removeDuplicates(std::vector<size_t>& rArray)
542 std::sort(rArray.begin(), rArray.end());
543 std::vector<size_t>::iterator it = std::unique(rArray.begin(), rArray.end());
544 rArray.erase(it, rArray.end());
549 void ScXMLSourceDlg::OkPressed()
551 if (!mpXMLContext)
552 return;
554 // Begin import.
556 ScOrcusImportXMLParam aParam;
558 // Convert single cell links.
560 std::set<const SvTreeListEntry*>::const_iterator it = maCellLinks.begin(), itEnd = maCellLinks.end();
561 for (; it != itEnd; ++it)
563 const SvTreeListEntry& rEntry = **it;
564 OUString aPath = getXPath(*mpLbTree, rEntry, aParam.maNamespaces);
565 const ScOrcusXMLTreeParam::EntryData* pUserData = ScOrcusXMLTreeParam::getUserData(rEntry);
567 aParam.maCellLinks.push_back(
568 ScOrcusImportXMLParam::CellLink(
569 pUserData->maLinkedPos, OUStringToOString(aPath, RTL_TEXTENCODING_UTF8)));
573 // Convert range links. For now, an element with range link takes all its
574 // child elements as its fields.
576 std::set<const SvTreeListEntry*>::const_iterator it = maRangeLinks.begin(), itEnd = maRangeLinks.end();
577 for (; it != itEnd; ++it)
579 const SvTreeListEntry& rEntry = **it;
580 const ScOrcusXMLTreeParam::EntryData* pUserData = ScOrcusXMLTreeParam::getUserData(rEntry);
582 ScOrcusImportXMLParam::RangeLink aRangeLink;
583 aRangeLink.maPos = pUserData->maLinkedPos;
585 // Go through all its child elements.
586 getFieldLinks(aRangeLink, aParam.maNamespaces, *mpLbTree, rEntry);
588 aParam.maRangeLinks.push_back(aRangeLink);
592 // Remove duplicate namespace IDs.
593 removeDuplicates(aParam.maNamespaces);
595 // Now do the import.
596 mpXMLContext->importXML(aParam);
598 // Don't forget to broadcast the change.
599 SfxObjectShell* pShell = mpDoc->GetDocumentShell();
600 pShell->Broadcast(SfxSimpleHint(FID_DATACHANGED));
602 // Repaint the grid to force repaint the cell values.
603 ScTabViewShell* pViewShell = ScTabViewShell::GetActiveViewShell();
604 if (pViewShell)
605 pViewShell->PaintGrid();
607 Close();
610 void ScXMLSourceDlg::CancelPressed()
612 Close();
615 void ScXMLSourceDlg::RefEditModified()
617 OUString aRefStr = mpRefEdit->GetText();
619 // Check if the address is valid.
620 ScAddress aLinkedPos;
621 ScRefFlags nRes = aLinkedPos.Parse(aRefStr, mpDoc, mpDoc->GetAddressConvention());
622 bool bValid = ( (nRes & ScRefFlags::VALID) == ScRefFlags::VALID );
624 // TODO: For some unknown reason, setting the ref invalid will hide the text altogether.
625 // Find out how to make this work.
626 // mpRefEdit->SetRefValid(bValid);
628 if (!bValid)
629 aLinkedPos.SetInvalid();
631 // Set this address to the current reference entry.
632 if (!mpCurRefEntry)
633 // This should never happen.
634 return;
636 ScOrcusXMLTreeParam::EntryData* pUserData = ScOrcusXMLTreeParam::getUserData(*mpCurRefEntry);
637 if (!pUserData)
638 // This should never happen either.
639 return;
641 bool bRepeatElem = pUserData->meType == ScOrcusXMLTreeParam::ElementRepeat;
642 pUserData->maLinkedPos = aLinkedPos;
643 pUserData->mbRangeParent = aLinkedPos.IsValid() && bRepeatElem;
645 if (bRepeatElem)
647 if (bValid)
648 maRangeLinks.insert(mpCurRefEntry);
649 else
650 maRangeLinks.erase(mpCurRefEntry);
652 else
654 if (bValid)
655 maCellLinks.insert(mpCurRefEntry);
656 else
657 maCellLinks.erase(mpCurRefEntry);
660 // Enable the import button only when at least one link exists.
661 bool bHasLink = !maCellLinks.empty() || !maRangeLinks.empty();
662 mpBtnOk->Enable(bHasLink);
665 IMPL_LINK_TYPED(ScXMLSourceDlg, GetFocusHdl, Control&, rCtrl, void)
667 HandleGetFocus(&rCtrl);
670 IMPL_LINK_TYPED(ScXMLSourceDlg, BtnPressedHdl, Button*, pBtn, void)
672 if (pBtn == mpBtnSelectSource)
673 SelectSourceFile();
674 else if (pBtn == mpBtnOk)
675 OkPressed();
676 else if (pBtn == mpBtnCancel)
677 CancelPressed();
680 IMPL_LINK_NOARG_TYPED(ScXMLSourceDlg, TreeItemSelectHdl, SvTreeListBox*, void)
682 TreeItemSelected();
685 IMPL_LINK_NOARG_TYPED(ScXMLSourceDlg, RefModifiedHdl, Edit&, void)
687 RefEditModified();
690 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */