offapi/com/sun/star/system/windows/XJumpList.idl |  118 +++++++++---
 shell/source/win32/jumplist/JumpList.cxx         |  224 ++++++++++++++++++++---
 2 files changed, 296 insertions(+), 46 deletions(-)

New commits:
commit 675788b208a7c775f8eaa51cd90528b1bb92ed79
Author:     Samuel Mehrbrodt <samuel.mehrbr...@allotropia.de>
AuthorDate: Mon Apr 25 10:53:09 2022 +0200
Commit:     Samuel Mehrbrodt <samuel.mehrbr...@allotropia.de>
CommitDate: Tue Apr 26 11:46:17 2022 +0200

    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
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/133367
    Tested-by: Jenkins
    Reviewed-by: Samuel Mehrbrodt <samuel.mehrbr...@allotropia.de>

diff --git a/offapi/com/sun/star/system/windows/XJumpList.idl 
b/offapi/com/sun/star/system/windows/XJumpList.idl
index 80fef03b60aa..ddf9415243c2 100644
--- 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:
+        <ul>
+            <li>Writer</li>
+            <li>Calc</li>
+            <li>Impress</li>
+            <li>Draw</li>
+            <li>Math</li>
+            <li>Base</li>
+            <li>Startcenter</li>
+        </ul>
 
-        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:
-        <ul>
-            <li>Writer</li>
-            <li>Calc</li>
-            <li>Impress</li>
-            <li>Draw</li>
-            <li>Math</li>
-            <li>Base</li>
-            <li>Startcenter</li>
-        </ul>
-
-        "Startcenter" will map to the generic "LibreOffice" icon.
-
         @throws com::sun::star::lang::IllegalArgumentException
         When one of the following applies:
         <ul>
             <li>`category` is empty</li>
             <li>`jumpListItems` is empty or contains only items which were 
removed by the user</li>
-            <li>`application` is invalid</li>
         </ul>
+
+        @throws com::sun::star::util::InvalidStateException
+        When there is no open list.
     */
     void appendCategory( [in] string category,
-                         [in] 
sequence<com::sun::star::system::windows::JumpListItem> jumpListItems,
-                         [in] string application )
-        raises( ::com::sun::star::lang::IllegalArgumentException );
+                         [in] 
sequence<com::sun::star::system::windows::JumpListItem> 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<com::sun::star::system::windows::JumpListItem> 
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 f5d8ca8532cd..06a62a11038d 100644
--- a/shell/source/win32/jumplist/JumpList.cxx
+++ b/shell/source/win32/jumplist/JumpList.cxx
@@ -30,6 +30,7 @@
 #include <com/sun/star/system/windows/JumpListItem.hpp>
 #include <com/sun/star/system/windows/XJumpList.hpp>
 #include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/util/InvalidStateException.hpp>
 
 #include <prewin.h>
 #include <Shlobj.h>
@@ -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 namespace sal::systools;
 
@@ -51,15 +53,23 @@ namespace
 class JumpListImpl : public BaseMutex, public 
WeakComponentImplHelper<XJumpList, XServiceInfo>
 {
     Reference<XComponentContext> m_xContext;
+    COMReference<ICustomDestinationList> m_aDestinationList;
+    COMReference<IObjectArray> m_aRemoved;
+    bool m_isListOpen;
 
 public:
     explicit JumpListImpl(const Reference<XComponentContext>& xContext);
 
     // XJumpList
+    virtual void SAL_CALL beginList(const OUString& sApplication) override;
     virtual void SAL_CALL appendCategory(const OUString& sCategory,
-                                         const Sequence<JumpListItem>& 
aJumpListItems,
-                                         const OUString& sApplication) 
override;
-    virtual void SAL_CALL deleteCategory(const OUString& sApplication) 
override;
+                                         const Sequence<JumpListItem>& 
aJumpListItems) override;
+    virtual void SAL_CALL addTasks(const Sequence<JumpListItem>& 
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<JumpListItem> SAL_CALL getRemovedItems(const OUString& 
sApplication) override;
 
     // XServiceInfo
@@ -71,6 +81,8 @@ public:
 JumpListImpl::JumpListImpl(const Reference<XComponentContext>& xContext)
     : WeakComponentImplHelper(m_aMutex)
     , m_xContext(xContext)
+    , m_aDestinationList(CLSID_DestinationList, nullptr, CLSCTX_INPROC_SERVER)
+    , m_isListOpen(false)
 {
 }
 
@@ -105,15 +117,11 @@ bool lcl_isItemInArray(COMReference<IShellLinkW> 
pShellLinkItem,
 }
 }
 
-void SAL_CALL JumpListImpl::appendCategory(const OUString& sCategory,
-                                           const Sequence<JumpListItem>& 
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<OWeakObject*>(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")
@@ -127,16 +135,34 @@ void SAL_CALL JumpListImpl::appendCategory(const 
OUString& sCategory,
 
     try
     {
-        COMReference<ICustomDestinationList> 
aDestinationList(CLSID_DestinationList, nullptr,
-                                                              
CLSCTX_INPROC_SERVER);
-
-        aDestinationList->SetAppID(o3tl::toW(sApplicationID.getStr()));
+        m_aDestinationList->SetAppID(o3tl::toW(sApplicationID.getStr()));
 
         UINT min_slots;
-        COMReference<IObjectArray> 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<JumpListItem>& 
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<OWeakObject*>(this), 1);
+    }
+
+    try
+    {
         OUString sofficeURL;
         OUString sofficePath;
         oslProcessError err = osl_getExecutableFile(&sofficeURL.pData);
@@ -191,7 +217,7 @@ void SAL_CALL JumpListImpl::appendCategory(const OUString& 
sCategory,
                               OStringConcatenation("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
@@ -219,10 +245,96 @@ void SAL_CALL JumpListImpl::appendCategory(const 
OUString& sCategory,
                 static_cast<OWeakObject*>(this), 1);
         }
 
-        
ThrowIfFailed(aDestinationList->AppendCategory(o3tl::toW(sCategory.getStr()), 
pObjectArray),
-                      "AppendCategory failed.");
+        ThrowIfFailed(
+            m_aDestinationList->AppendCategory(o3tl::toW(sCategory.getStr()), 
pObjectArray),
+            "AppendCategory failed.");
+    }
+    catch (const ComError& e)
+    {
+        SAL_WARN("shell.jumplist", e.what());
+    }
+}
+
+void SAL_CALL JumpListImpl::addTasks(const Sequence<JumpListItem>& 
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<IObjectCollection> 
aCollection(CLSID_EnumerableObjectCollection, nullptr,
+                                                    CLSCTX_INPROC_SERVER);
 
-        ThrowIfFailed(aDestinationList->CommitList(), "CommitList failed.");
+        for (auto const& item : aJumpListItems)
+        {
+            if (item.name.isEmpty())
+                continue;
+            try
+            {
+                COMReference<IShellLinkW> pShellLinkItem(CLSID_ShellLink, 
nullptr,
+                                                         CLSCTX_INPROC_SERVER);
+
+                {
+                    COMReference<IPropertyStore> 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())),
+                              OStringConcatenation("Setting description '"
+                                                   + item.description.toUtf8() 
+ "' failed."));
+
+                ThrowIfFailed(
+                    pShellLinkItem->SetPath(o3tl::toW(sofficePath.getStr())),
+                    OStringConcatenation("Setting path '" + 
sofficePath.toUtf8() + "' failed."));
+
+                
ThrowIfFailed(pShellLinkItem->SetArguments(o3tl::toW(item.arguments.getStr())),
+                              OStringConcatenation("Setting arguments '" + 
item.arguments.toUtf8()
+                                                   + "' failed."));
+
+                
ThrowIfFailed(pShellLinkItem->SetIconLocation(o3tl::toW(item.iconPath.getStr()),
 0),
+                              OStringConcatenation("Setting icon path '" + 
item.iconPath.toUtf8()
+                                                   + "' failed."));
+
+                aCollection->AddObject(pShellLinkItem);
+            }
+            catch (const ComError& e)
+            {
+                SAL_WARN("shell.jumplist", e.what());
+                continue;
+            }
+        }
+
+        COMReference<IObjectArray> 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<OWeakObject*>(this), 1);
+        }
+
+        ThrowIfFailed(m_aDestinationList->AddUserTasks(pObjectArray), 
"AddUserTasks failed.");
     }
     catch (const ComError& e)
     {
@@ -230,8 +342,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")

Reply via email to