From fa9baaece0912561121cd9dd2a0536b3090fcb49 Mon Sep 17 00:00:00 2001 From: Samuel Mehrbrodt Date: Mon, 25 Apr 2022 10:53:09 +0200 Subject: Extend UNO API for custom jump lists * Allow to display the recent/frequent files * Allow adding items to the "Tasks" category * Allow adding multiple categories Follow-up to 7efd22c912262f7bf4e4735dae70db0b31ab3d5b Change-Id: I860d44c1a0d9bc8200529c908b6103741dc37bb5 --- offapi/com/sun/star/system/windows/XJumpList.idl | 118 +++++++++--- shell/source/win32/jumplist/JumpList.cxx | 225 ++++++++++++++++++++--- 2 files changed, 297 insertions(+), 46 deletions(-) diff --git a/offapi/com/sun/star/system/windows/XJumpList.idl b/offapi/com/sun/star/system/windows/XJumpList.idl index 80fef03b60aa..ddf9415243c2 100755 --- a/offapi/com/sun/star/system/windows/XJumpList.idl +++ b/offapi/com/sun/star/system/windows/XJumpList.idl @@ -19,16 +19,48 @@ module com { module sun { module star { module system { module windows { /** Specifies an interface for adding custom jump lists to the task bar (Windows only) + To add a new jump list, call + 1. XJumpList::beginList + 2. XJumpList::appendCategory / XJumpList::addTasks / XJumpList::showRecentFiles / XJumpList::showFrequentFiles + 3. XJumpList::commitList + + Use XJumpList::abortList to cancel a current list building session. + Use XJumpList::getRemovedItems to see which items were removed by the user. + @since LibreOffice 7.4 */ interface XJumpList: com::sun::star::uno::XInterface { - /** Add (or update) a jump list category. + /** + Start a new jump list. + + @param application + Used to map the jump list to the correct application. Use one of the following values: + - Note that it is only possible to have one jump list category per `application`. + "Startcenter" will map to the generic "LibreOffice" icon. + + @throws com::sun::star::lang::IllegalArgumentException + When `application` is invalid - When there is already a jump list for the given `application`, - that jump list will be cleared, and the new `category` and `jumpListItems` will be added. + @throws com::sun::star::util::InvalidStateException + When there is already an open list. + */ + void beginList([in] string application) + raises( ::com::sun::star::lang::IllegalArgumentException, ::com::sun::star::util::InvalidStateException ); + + /** Add a jump list category. + + Users can pin or remove items added via this method. + Use XJumpList::getRemovedItems to see which items were removed by the user. @param category Specifies the category name. It will appear as the title of the custom jump list. @@ -44,34 +76,72 @@ interface XJumpList: com::sun::star::uno::XInterface If you try to add items which the user removed before, they will be silently ignored and not added to the list. - @param application - Used to map the jump list to the correct application. Use one of the following values: - - - "Startcenter" will map to the generic "LibreOffice" icon. - @throws com::sun::star::lang::IllegalArgumentException When one of the following applies: + + @throws com::sun::star::util::InvalidStateException + When there is no open list. */ void appendCategory( [in] string category, - [in] sequence jumpListItems, - [in] string application ) - raises( ::com::sun::star::lang::IllegalArgumentException ); + [in] sequence jumpListItems ) + raises( ::com::sun::star::lang::IllegalArgumentException, ::com::sun::star::util::InvalidStateException ); + + /** Add items to the "Tasks" category. This category is system-defined and the category title cannot be changed. + Also the user cannot remove or pin items from this category (as he can with items added via XJumpList::appendCategory ). + + @param jumpListItems + Specifies a list of com::sun::star::system::JumpListItem. + Must contain at least one item. + These will be added as entries below the "Tasks" system category. + + @throws com::sun::star::lang::IllegalArgumentException + When `jumpListItems` is empty + + @throws com::sun::star::util::InvalidStateException + When there is no open list. + */ + void addTasks([in] sequence jumpListItems) + raises( ::com::sun::star::lang::IllegalArgumentException, ::com::sun::star::util::InvalidStateException ); + + /** Display the recently used files (populated by LibreOffice) + + @throws com::sun::star::util::InvalidStateException + When there is no open list. + */ + void showRecentFiles() + raises (::com::sun::star::util::InvalidStateException); + + /** Display the frequently used files (populated by LibreOffice) + + @throws com::sun::star::util::InvalidStateException + When there is no open list. + */ + void showFrequentFiles() + raises (::com::sun::star::util::InvalidStateException); + + /** + Commits the list. + + @throws com::sun::star::util::InvalidStateException + When there is no open list. + */ + void commitList() + raises( ::com::sun::star::util::InvalidStateException ); + + /** + Aborts a list building session started with beginList. + + @throws com::sun::star::util::InvalidStateException + When there is no open list. + */ + void abortList() + raises( ::com::sun::star::util::InvalidStateException ); - /** Delete a jump list category + /** Deletes the Jump List for a certain application @param application Used to map the jump list to the correct application. Use one of the following values: @@ -90,7 +160,7 @@ interface XJumpList: com::sun::star::uno::XInterface @throws com::sun::star::lang::IllegalArgumentException When `application` is invalid */ - void deleteCategory( [in] string application ) + void deleteList( [in] string application ) raises( ::com::sun::star::lang::IllegalArgumentException ); /** Returns items that were removed from the jump list by the user. diff --git a/shell/source/win32/jumplist/JumpList.cxx b/shell/source/win32/jumplist/JumpList.cxx index cbc72299b41a..3372ec9fc3bb 100755 --- a/shell/source/win32/jumplist/JumpList.cxx +++ b/shell/source/win32/jumplist/JumpList.cxx @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -43,6 +44,7 @@ using namespace css; using namespace css::uno; using namespace css::lang; using namespace css::system::windows; +using namespace css::util; using namespace osl; using sal::systools::COMReference; using sal::systools::COM_QUERY_THROW; @@ -52,16 +54,24 @@ using sal::systools::ThrowIfFailed; class JumpListImpl : public BaseMutex, public WeakComponentImplHelper { Reference m_xContext; + COMReference m_aDestinationList; + COMReference m_aRemoved; + bool m_isListOpen; public: explicit JumpListImpl(const Reference& xContext); ~JumpListImpl(); // XJumpList + virtual void SAL_CALL beginList(const OUString& sApplication) override; virtual void SAL_CALL appendCategory(const OUString& sCategory, - const Sequence& aJumpListItems, - const OUString& sApplication) override; - virtual void SAL_CALL deleteCategory(const OUString& sApplication) override; + const Sequence& aJumpListItems) override; + virtual void SAL_CALL addTasks(const Sequence& aJumpListItems) override; + virtual void SAL_CALL showRecentFiles() override; + virtual void SAL_CALL showFrequentFiles() override; + virtual void SAL_CALL commitList() override; + virtual void SAL_CALL abortList() override; + virtual void SAL_CALL deleteList(const OUString& sApplication) override; virtual Sequence SAL_CALL getRemovedItems(const OUString& sApplication) override; // XServiceInfo @@ -73,7 +83,11 @@ public: JumpListImpl::JumpListImpl(const Reference& xContext) : WeakComponentImplHelper(m_aMutex) , m_xContext(xContext) + , m_aDestinationList() + , m_isListOpen(false) { + CoCreateInstance(CLSID_DestinationList, nullptr, CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&m_aDestinationList)); } JumpListImpl::~JumpListImpl() {} @@ -111,15 +125,11 @@ bool lcl_isItemInArray(COMReference pShellLinkItem, } } -void SAL_CALL JumpListImpl::appendCategory(const OUString& sCategory, - const Sequence& aJumpListItems, - const OUString& sApplication) +void SAL_CALL JumpListImpl::beginList(const OUString& sApplication) { - if (sCategory.isEmpty()) - { - throw IllegalArgumentException("Parameter 'category' must not be empty", - static_cast(this), 1); - } + if (m_isListOpen) + throw InvalidStateException("There is already a list open. Close it with 'commitList'"); + if (sApplication != "Writer" && sApplication != "Calc" && sApplication != "Impress" && sApplication != "Draw" && sApplication != "Math" && sApplication != "Base" && sApplication != "Startcenter") @@ -133,17 +143,34 @@ void SAL_CALL JumpListImpl::appendCategory(const OUString& sCategory, try { - COMReference aDestinationList; - CoCreateInstance(CLSID_DestinationList, nullptr, CLSCTX_INPROC_SERVER, - IID_PPV_ARGS(&aDestinationList)); - - aDestinationList->SetAppID(o3tl::toW(sApplicationID.getStr())); + m_aDestinationList->SetAppID(o3tl::toW(sApplicationID.getStr())); UINT min_slots; - COMReference removed; - ThrowIfFailed(aDestinationList->BeginList(&min_slots, IID_PPV_ARGS(&removed)), + + ThrowIfFailed(m_aDestinationList->BeginList(&min_slots, IID_PPV_ARGS(&m_aRemoved)), "BeginList failed"); + m_isListOpen = true; + } + catch (const ComError& e) + { + SAL_WARN("shell.jumplist", e.what()); + } +} +void SAL_CALL JumpListImpl::appendCategory(const OUString& sCategory, + const Sequence& aJumpListItems) +{ + if (!m_isListOpen) + throw InvalidStateException("No list open. Open it with 'beginList'"); + + if (sCategory.isEmpty()) + { + throw IllegalArgumentException("Parameter 'category' must not be empty", + static_cast(this), 1); + } + + try + { OUString sofficeURL; OUString sofficePath; oslProcessError err = osl_getExecutableFile(&sofficeURL.pData); @@ -199,7 +226,7 @@ void SAL_CALL JumpListImpl::appendCategory(const OUString& sCategory, pShellLinkItem->SetIconLocation(o3tl::toW(item.iconPath.getStr()), 0), OString("Setting icon path '" + item.iconPath.toUtf8() + "' failed.")); - if (lcl_isItemInArray(pShellLinkItem, removed)) + if (lcl_isItemInArray(pShellLinkItem, m_aRemoved)) { SAL_INFO("shell.jumplist", "Ignoring item '" << item.name @@ -228,10 +255,96 @@ void SAL_CALL JumpListImpl::appendCategory(const OUString& sCategory, } ThrowIfFailed( - aDestinationList->AppendCategory(o3tl::toW(sCategory.getStr()), pObjectArray.get()), + m_aDestinationList->AppendCategory(o3tl::toW(sCategory.getStr()), pObjectArray.get()), "AppendCategory failed."); + } + catch (const ComError& e) + { + SAL_WARN("shell.jumplist", e.what()); + } +} + +void SAL_CALL JumpListImpl::addTasks(const Sequence& aJumpListItems) +{ + if (!m_isListOpen) + throw InvalidStateException("No list open. Open it with 'beginList'"); + + try + { + OUString sofficeURL; + OUString sofficePath; + oslProcessError err = osl_getExecutableFile(&sofficeURL.pData); + FileBase::getSystemPathFromFileURL(sofficeURL, sofficePath); + if (err != osl_Process_E_None) + { + SAL_WARN("shell.jumplist", "osl_getExecutableFile failed"); + return; + } + // We need to run soffice.exe, not soffice.bin + sofficePath = sofficePath.replaceFirst("soffice.bin", "soffice.exe"); + + COMReference aCollection; + CoCreateInstance(CLSID_EnumerableObjectCollection, nullptr, CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&aCollection)); - ThrowIfFailed(aDestinationList->CommitList(), "CommitList failed."); + for (auto const& item : aJumpListItems) + { + if (item.name.isEmpty()) + continue; + try + { + COMReference pShellLinkItem; + CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&pShellLinkItem)); + + { + COMReference pps(pShellLinkItem, COM_QUERY_THROW); + + PROPVARIANT propvar; + ThrowIfFailed( + InitPropVariantFromString(o3tl::toW(item.name.getStr()), &propvar), + "InitPropVariantFromString failed."); + + ThrowIfFailed(pps->SetValue(PKEY_Title, propvar), "SetValue failed."); + + ThrowIfFailed(pps->Commit(), "Commit failed."); + + PropVariantClear(&propvar); + } + ThrowIfFailed( + pShellLinkItem->SetDescription(o3tl::toW(item.description.getStr())), + OString("Setting description '" + item.description.toUtf8() + "' failed.")); + + ThrowIfFailed(pShellLinkItem->SetPath(o3tl::toW(sofficePath.getStr())), + OString("Setting path '" + sofficePath.toUtf8() + "' failed.")); + + ThrowIfFailed( + pShellLinkItem->SetArguments(o3tl::toW(item.arguments.getStr())), + OString("Setting arguments '" + item.arguments.toUtf8() + "' failed.")); + + ThrowIfFailed( + pShellLinkItem->SetIconLocation(o3tl::toW(item.iconPath.getStr()), 0), + OString("Setting icon path '" + item.iconPath.toUtf8() + "' failed.")); + + aCollection->AddObject(pShellLinkItem.get()); + } + catch (const ComError& e) + { + SAL_WARN("shell.jumplist", e.what()); + continue; + } + } + + COMReference pObjectArray(aCollection, COM_QUERY_THROW); + UINT nItems; + ThrowIfFailed(pObjectArray->GetCount(&nItems), "GetCount failed."); + if (nItems == 0) + { + throw IllegalArgumentException("No valid items given. `jumpListItems` is empty.", + static_cast(this), 1); + } + + ThrowIfFailed(m_aDestinationList->AddUserTasks(pObjectArray.get()), "AddUserTasks failed."); } catch (const ComError& e) { @@ -239,8 +352,76 @@ void SAL_CALL JumpListImpl::appendCategory(const OUString& sCategory, } } -void SAL_CALL JumpListImpl::deleteCategory(const OUString& sApplication) +void SAL_CALL JumpListImpl::showRecentFiles() { + if (!m_isListOpen) + throw InvalidStateException("No list open. Open it with 'beginList'"); + + try + { + ThrowIfFailed(m_aDestinationList->AppendKnownCategory(KDC_RECENT), + "AppendKnownCategory(KDC_RECENT) failed."); + } + catch (const ComError& e) + { + SAL_WARN("shell.jumplist", e.what()); + } +} + +void SAL_CALL JumpListImpl::showFrequentFiles() +{ + if (!m_isListOpen) + throw InvalidStateException("No list open. Open it with 'beginList'"); + + try + { + ThrowIfFailed(m_aDestinationList->AppendKnownCategory(KDC_FREQUENT), + "AppendKnownCategory(KDC_FREQUENT) failed."); + } + catch (const ComError& e) + { + SAL_WARN("shell.jumplist", e.what()); + } +} + +void SAL_CALL JumpListImpl::commitList() +{ + if (!m_isListOpen) + throw InvalidStateException("No list open. Open it with 'beginList'"); + + try + { + ThrowIfFailed(m_aDestinationList->CommitList(), "CommitList failed."); + m_isListOpen = false; + } + catch (const ComError& e) + { + SAL_WARN("shell.jumplist", e.what()); + } +} + +void SAL_CALL JumpListImpl::abortList() +{ + if (!m_isListOpen) + throw InvalidStateException("No list open."); + + try + { + ThrowIfFailed(m_aDestinationList->AbortList(), "AbortList failed."); + m_isListOpen = false; + } + catch (const ComError& e) + { + SAL_WARN("shell.jumplist", e.what()); + } +} + +void SAL_CALL JumpListImpl::deleteList(const OUString& sApplication) +{ + if (m_isListOpen) + throw InvalidStateException("You are in a list building session. Close it with " + "'commitList', or abort with 'abortList'"); + if (sApplication != "Writer" && sApplication != "Calc" && sApplication != "Impress" && sApplication != "Draw" && sApplication != "Math" && sApplication != "Base" && sApplication != "Startcenter") -- cgit