diff options
Diffstat (limited to '')
-rw-r--r-- | sfx2/source/control/recentdocsviewitem.cxx | 348 |
1 files changed, 348 insertions, 0 deletions
diff --git a/sfx2/source/control/recentdocsviewitem.cxx b/sfx2/source/control/recentdocsviewitem.cxx new file mode 100644 index 000000000..44f103dfb --- /dev/null +++ b/sfx2/source/control/recentdocsviewitem.cxx @@ -0,0 +1,348 @@ +/* -*- 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 <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/embed/StorageFactory.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/util/URLTransformer.hpp> + +#include <comphelper/base64.hxx> +#include <comphelper/propertyvalue.hxx> +#include <drawinglayer/primitive2d/discretebitmapprimitive2d.hxx> +#include <drawinglayer/processor2d/baseprocessor2d.hxx> +#include <i18nutil/paper.hxx> +#include <officecfg/Office/Common.hxx> +#include <recentdocsview.hxx> +#include <sfx2/templatelocalview.hxx> +#include <tools/diagnose_ex.h> +#include <tools/stream.hxx> +#include <tools/urlobj.hxx> +#include <unotools/historyoptions.hxx> +#include <vcl/event.hxx> +#include <vcl/filter/PngImageReader.hxx> +#include <vcl/ptrstyle.hxx> +#include <vcl/virdev.hxx> + +#include <map> + +#include <bitmaps.hlst> +#include "recentdocsviewitem.hxx" + +using namespace basegfx; +using namespace com::sun::star; +using namespace com::sun::star::uno; +using namespace drawinglayer::primitive2d; +using namespace drawinglayer::processor2d; + +namespace +{ +bool IsDocEncrypted(const OUString& rURL) +{ + bool bIsEncrypted = false; + + try + { + auto xFactory = embed::StorageFactory::create(comphelper::getProcessComponentContext()); + auto xStorage(xFactory->createInstanceWithArguments( + { uno::Any(rURL), uno::Any(embed::ElementModes::READ) })); + if (uno::Reference<beans::XPropertySet> xStorageProps{ xStorage, uno::UNO_QUERY }) + { + try + { + xStorageProps->getPropertyValue("HasEncryptedEntries") >>= bIsEncrypted; + } + catch (uno::Exception&) + { + } + } + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("sfx", "caught exception trying to find out if doc <" + << rURL << "> is encrypted:"); + } + + return bIsEncrypted; +} + +using Ext2IconMap = std::map<sfx2::ApplicationType, OUString>; +BitmapEx Url2Icon(std::u16string_view rURL, const Ext2IconMap& rExtToIcon, const OUString& sDefault) +{ + auto it = std::find_if(rExtToIcon.begin(), rExtToIcon.end(), + [aExt = INetURLObject(rURL).getExtension()](const auto& r) + { return sfx2::RecentDocsView::typeMatchesExtension(r.first, aExt); }); + + return BitmapEx(it != rExtToIcon.end() ? it->second : sDefault); +}; + +BitmapEx getDefaultThumbnail(const OUString& rURL) +{ + static const Ext2IconMap BitmapForExtension + = { { sfx2::ApplicationType::TYPE_WRITER, SFX_FILE_THUMBNAIL_TEXT }, + { sfx2::ApplicationType::TYPE_CALC, SFX_FILE_THUMBNAIL_SHEET }, + { sfx2::ApplicationType::TYPE_IMPRESS, SFX_FILE_THUMBNAIL_PRESENTATION }, + { sfx2::ApplicationType::TYPE_DRAW, SFX_FILE_THUMBNAIL_DRAWING }, + { sfx2::ApplicationType::TYPE_DATABASE, SFX_FILE_THUMBNAIL_DATABASE }, + { sfx2::ApplicationType::TYPE_MATH, SFX_FILE_THUMBNAIL_MATH } }; + + static const Ext2IconMap EncryptedBitmapForExtension + = { { sfx2::ApplicationType::TYPE_WRITER, BMP_128X128_WRITER_DOC }, + { sfx2::ApplicationType::TYPE_CALC, BMP_128X128_CALC_DOC }, + { sfx2::ApplicationType::TYPE_IMPRESS, BMP_128X128_IMPRESS_DOC }, + { sfx2::ApplicationType::TYPE_DRAW, BMP_128X128_DRAW_DOC }, + // You can't save a database file with encryption -> no respective icon + { sfx2::ApplicationType::TYPE_MATH, BMP_128X128_MATH_DOC } }; + + const std::map<sfx2::ApplicationType, OUString>& rWhichMap + = IsDocEncrypted(rURL) ? EncryptedBitmapForExtension : BitmapForExtension; + + return Url2Icon(rURL, rWhichMap, SFX_FILE_THUMBNAIL_DEFAULT); +} + +BitmapEx getModuleOverlay(std::u16string_view rURL) +{ + static const Ext2IconMap OverlayBitmapForExtension + = { { sfx2::ApplicationType::TYPE_WRITER, SFX_FILE_OVERLAY_TEXT }, + { sfx2::ApplicationType::TYPE_CALC, SFX_FILE_OVERLAY_SHEET }, + { sfx2::ApplicationType::TYPE_IMPRESS, SFX_FILE_OVERLAY_PRESENTATION }, + { sfx2::ApplicationType::TYPE_DRAW, SFX_FILE_OVERLAY_DRAWING }, + { sfx2::ApplicationType::TYPE_DATABASE, SFX_FILE_OVERLAY_DATABASE }, + { sfx2::ApplicationType::TYPE_MATH, SFX_FILE_OVERLAY_MATH } }; + + return Url2Icon(rURL, OverlayBitmapForExtension, SFX_FILE_OVERLAY_DEFAULT); +} +}; + +RecentDocsViewItem::RecentDocsViewItem(sfx2::RecentDocsView &rView, const OUString &rURL, + const OUString &rTitle, std::u16string_view const sThumbnailBase64, + sal_uInt16 const nId, tools::Long const nThumbnailSize, + bool const isReadOnly) + : ThumbnailViewItem(rView, nId), + mrParentView(rView), + maURL(rURL), + m_isReadOnly(isReadOnly), + m_bRemoveIconHighlighted(false), + m_aRemoveRecentBitmap(BMP_RECENTDOC_REMOVE), + m_aRemoveRecentBitmapHighlighted(BMP_RECENTDOC_REMOVE_HIGHLIGHTED) +{ + OUString aTitle(rTitle); + INetURLObject aURLObj(rURL); + + if( aURLObj.GetProtocol() == INetProtocol::File ) + m_sHelpText = aURLObj.getFSysPath(FSysStyle::Detect); + if( m_sHelpText.isEmpty() ) + m_sHelpText = aURLObj.GetURLNoPass(); + + if (aTitle.isEmpty()) + aTitle = aURLObj.GetLastName(INetURLObject::DecodeMechanism::WithCharset); + + BitmapEx aThumbnail; + + //fdo#74834: only load thumbnail if the corresponding option is not disabled in the configuration + if (officecfg::Office::Common::History::RecentDocsThumbnail::get()) + { + if (!sThumbnailBase64.empty()) + { + Sequence<sal_Int8> aDecoded; + comphelper::Base64::decode(aDecoded, sThumbnailBase64); + + SvMemoryStream aStream(aDecoded.getArray(), aDecoded.getLength(), StreamMode::READ); + vcl::PngImageReader aReader(aStream); + aThumbnail = aReader.read(); + } + else if (sfx2::RecentDocsView::typeMatchesExtension(sfx2::ApplicationType::TYPE_DATABASE, + aURLObj.getExtension())) + { + aThumbnail + = BitmapEx(nThumbnailSize > 192 ? SFX_THUMBNAIL_BASE_256 : SFX_THUMBNAIL_BASE_192); + } + } + + if (aThumbnail.IsEmpty()) + { + // 1. Thumbnail absent: get the default thumbnail, checking for encryption. + BitmapEx aExt(getDefaultThumbnail(rURL)); + Size aExtSize(aExt.GetSizePixel()); + + // attempt to make it appear as if it is on a piece of paper + tools::Long nPaperHeight; + tools::Long nPaperWidth; + if (sfx2::RecentDocsView::typeMatchesExtension( + sfx2::ApplicationType::TYPE_IMPRESS, aURLObj.getExtension())) + { + // Swap width and height (PAPER_SCREEN_4_3 definition make it needed) + PaperInfo aInfo(PAPER_SCREEN_4_3); + nPaperHeight = aInfo.getWidth(); + nPaperWidth = aInfo.getHeight(); + } + else + { + PaperInfo aInfo(PaperInfo::getSystemDefaultPaper()); + nPaperHeight = aInfo.getHeight(); + nPaperWidth = aInfo.getWidth(); + } + double ratio = double(nThumbnailSize) / double(std::max(nPaperHeight, nPaperWidth)); + Size aThumbnailSize(std::round(nPaperWidth * ratio), std::round(nPaperHeight * ratio)); + + if (aExtSize.Width() > aThumbnailSize.Width() || aExtSize.Height() > aThumbnailSize.Height()) + { + aExt = TemplateLocalView::scaleImg(aExt, aThumbnailSize.Width(), aThumbnailSize.Height()); + aExtSize = aExt.GetSizePixel(); + } + + // create empty, and copy the default thumbnail in + sal_uInt8 nAlpha = 255; + aThumbnail = BitmapEx(Bitmap(aThumbnailSize, vcl::PixelFormat::N24_BPP), AlphaMask(aThumbnailSize, &nAlpha)); + + aThumbnail.CopyPixel( + ::tools::Rectangle(Point((aThumbnailSize.Width() - aExtSize.Width()) / 2, (aThumbnailSize.Height() - aExtSize.Height()) / 2), aExtSize), + ::tools::Rectangle(Point(0, 0), aExtSize), + &aExt); + } + else + { + // 2. Thumbnail present: it's unencrypted document -> add a module overlay. + // Pre-scale the thumbnail to the final size before applying the overlay + aThumbnail = TemplateLocalView::scaleImg(aThumbnail, nThumbnailSize, nThumbnailSize); + + BitmapEx aModule = getModuleOverlay(rURL); + if (!aModule.IsEmpty()) + { + const Size aSize(aThumbnail.GetSizePixel()); + const Size aOverlaySize(aModule.GetSizePixel()); + ScopedVclPtr<VirtualDevice> pVirDev(VclPtr<VirtualDevice>::Create()); + pVirDev->SetOutputSizePixel(aSize); + pVirDev->DrawBitmapEx(Point(), aThumbnail); + pVirDev->DrawBitmapEx(Point(aSize.Width() - aOverlaySize.Width() - 5, + aSize.Height() - aOverlaySize.Height() - 5), + aModule); + aThumbnail = pVirDev->GetBitmapEx(Point(), aSize); + } + } + + maTitle = aTitle; + maPreview1 = aThumbnail; +} + +::tools::Rectangle RecentDocsViewItem::updateHighlight(bool bVisible, const Point& rPoint) +{ + ::tools::Rectangle aRect(ThumbnailViewItem::updateHighlight(bVisible, rPoint)); + + if (bVisible && getRemoveIconArea().Contains(rPoint)) + { + if (!m_bRemoveIconHighlighted) + aRect.Union(getRemoveIconArea()); + + m_bRemoveIconHighlighted = true; + } + else + { + if (m_bRemoveIconHighlighted) + aRect.Union(getRemoveIconArea()); + + m_bRemoveIconHighlighted = false; + } + + return aRect; +} + +::tools::Rectangle RecentDocsViewItem::getRemoveIconArea() const +{ + ::tools::Rectangle aArea(getDrawArea()); + Size aSize(m_aRemoveRecentBitmap.GetSizePixel()); + + return ::tools::Rectangle( + Point(aArea.Right() - aSize.Width() - THUMBNAILVIEW_ITEM_CORNER, aArea.Top() + THUMBNAILVIEW_ITEM_CORNER), + aSize); +} + +OUString RecentDocsViewItem::getHelpText() const +{ + return m_sHelpText; +} + +void RecentDocsViewItem::Paint(drawinglayer::processor2d::BaseProcessor2D *pProcessor, const ThumbnailItemAttributes *pAttrs) +{ + ThumbnailViewItem::Paint(pProcessor, pAttrs); + + // paint the remove icon when highlighted + if (isHighlighted()) + { + drawinglayer::primitive2d::Primitive2DContainer aSeq(1); + + Point aIconPos(getRemoveIconArea().TopLeft()); + + aSeq[0] = drawinglayer::primitive2d::Primitive2DReference(new DiscreteBitmapPrimitive2D( + m_bRemoveIconHighlighted ? m_aRemoveRecentBitmapHighlighted : m_aRemoveRecentBitmap, + B2DPoint(aIconPos.X(), aIconPos.Y()))); + + pProcessor->process(aSeq); + } +} + +void RecentDocsViewItem::MouseButtonUp(const MouseEvent& rMEvt) +{ + if (rMEvt.IsLeft()) + { + if (getRemoveIconArea().Contains(rMEvt.GetPosPixel())) + { + SvtHistoryOptions::DeleteItem(EHistoryType::PickList, maURL); + mrParent.Reload(); + return; + } + + OpenDocument(); + return; + } +} + +void RecentDocsViewItem::OpenDocument() +{ + // show busy mouse pointer + mrParentView.SetPointer(PointerStyle::Wait); + mrParentView.Disable(); + + Reference<frame::XDispatch> xDispatch; + css::util::URL aTargetURL; + Sequence<beans::PropertyValue> aArgsList; + + uno::Reference<frame::XDesktop2> xDesktop = frame::Desktop::create(::comphelper::getProcessComponentContext()); + + aTargetURL.Complete = maURL; + Reference<util::XURLTransformer> xTrans(util::URLTransformer::create(::comphelper::getProcessComponentContext())); + xTrans->parseStrict(aTargetURL); + + aArgsList = { comphelper::makePropertyValue("Referer", OUString("private:user")), + // documents will never be opened as templates + comphelper::makePropertyValue("AsTemplate", false) }; + if (m_isReadOnly) // tdf#149170 only add if true + { + aArgsList.realloc(aArgsList.size()+1); + aArgsList.getArray()[aArgsList.size()-1] = comphelper::makePropertyValue("ReadOnly", true); + } + + xDispatch = xDesktop->queryDispatch(aTargetURL, "_default", 0); + + if (!xDispatch.is()) + return; + + // Call dispatch asynchronously as we can be destroyed while dispatch is + // executed. VCL is not able to survive this as it wants to call listeners + // after select!!! + sfx2::LoadRecentFile *const pLoadRecentFile = new sfx2::LoadRecentFile; + pLoadRecentFile->xDispatch = xDispatch; + pLoadRecentFile->aTargetURL = aTargetURL; + pLoadRecentFile->aArgSeq = aArgsList; + pLoadRecentFile->pView = &mrParentView; + + mrParentView.PostLoadRecentUsedFile(pLoadRecentFile); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |