cui/qa/uitest/dialogs/accelerators.py |  184 ++++++++++++++++++
 cui/source/customize/acccfg.cxx       |  237 ++++++++++++++++++++++-
 cui/source/inc/acccfg.hxx             |   15 +
 cui/uiconfig/ui/accelconfigpage.ui    |  345 +++++++++++++++++++---------------
 4 files changed, 630 insertions(+), 151 deletions(-)

New commits:
commit e4e601b1eb8ec8d5b5783743606e4bc79ba859de
Author:     Neil Roberts <[email protected]>
AuthorDate: Tue Jan 20 11:47:41 2026 +0100
Commit:     Neil Roberts <[email protected]>
CommitDate: Sat Feb 28 10:49:02 2026 +0100

    tdf#94522: accelconfigpage: Allow defining shortcuts in document
    
    Previously, when assigning a shortcut key, there was only a choice
    between assigning it in the current module or globally for all of
    LibreOffice. This patch adds a third radio button with a dropdown box to
    be able to pick a document to define the key in instead. This
    functionality is already available in the toolbar and menu pages.
    
    Change-Id: I465dbb84428268f621174d8e52662175a72f44e5
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197709
    Reviewed-by: Heiko Tietze <[email protected]>
    Tested-by: Jenkins

diff --git a/cui/qa/uitest/dialogs/accelerators.py 
b/cui/qa/uitest/dialogs/accelerators.py
new file mode 100644
index 000000000000..34722ffc02a5
--- /dev/null
+++ b/cui/qa/uitest/dialogs/accelerators.py
@@ -0,0 +1,184 @@
+# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*-
+#
+# 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/.
+#
+
+from uitest.framework import UITestCase
+from uitest.uihelper.common import select_pos, select_by_text
+from libreoffice.uno.propertyvalue import mkPropertyValues
+from uitest.uihelper.common import get_state_as_dict
+from com.sun.star.awt import KeyEvent
+from com.sun.star.awt import Key
+from com.sun.star.container import NoSuchElementException
+
+def select_key(xShortcuts, key_name):
+    for child in xShortcuts.getChildren():
+        entry = xShortcuts.getChild(child)
+        name = get_state_as_dict(entry)["Text"]
+
+        if name.startswith(key_name):
+            entry.executeAction("SELECT", tuple())
+            break
+    else:
+        raise Exception(f"Couldn’t find key {key_name}")
+
+def select_keyboard_tab(xDialog):
+    xTabs = xDialog.getChild("tabcontrol")
+
+    n_tabs = int(get_state_as_dict(xTabs)["PageCount"])
+
+    for i in range(n_tabs):
+        select_pos(xTabs, str(i))
+
+        if get_state_as_dict(xTabs)["CurrPageTitle"] == "Keyboard":
+            break
+    else:
+        raise Exception("Couldn’t find keyboard tab")
+
+class Test(UITestCase):
+    def assign_key(self, xDialog, scope, key_name, action):
+        xAcceleratorPage = xDialog.getChild("AccelConfigPage")
+
+        select_keyboard_tab(xDialog)
+
+        if scope == "office":
+            xAcceleratorPage.getChild("office").executeAction("CLICK", tuple())
+        elif scope == "module":
+            xAcceleratorPage.getChild("module").executeAction("CLICK", tuple())
+        else:
+            xAcceleratorPage.getChild("document").executeAction("CLICK", 
tuple())
+            xScope = xAcceleratorPage.getChild("savein")
+            select_by_text(xScope, scope)
+
+        xShortcuts = xAcceleratorPage.getChild("shortcuts")
+        select_key(xShortcuts, key_name)
+
+        xSearchEntry = xAcceleratorPage.getChild("searchEntry")
+        xSearchEntry.executeAction("FOCUS", tuple())
+        xSearchEntry.executeAction("SET", mkPropertyValues({"TEXT": action}))
+
+        # Focus something else so that the search will be applied
+        # immediately without having to wait for the timer
+        xShortcuts.executeAction("FOCUS", tuple())
+
+        xChange = xAcceleratorPage.getChild("change")
+        xChange.executeAction("CLICK", tuple())
+
+    def test_scope(self):
+        with self.ui_test.create_doc_in_start_center("writer") as xComponent:
+            # Set a shortcut on the global scope
+            with 
self.ui_test.execute_dialog_through_command(".uno:ConfigureDialog") as xDialog:
+                self.assign_key(xDialog, "office", "F7", ".uno:Credits")
+
+            # Check that the key made it into the global config
+            xGlobalAccelCfg = self.xContext.ServiceManager.createInstance(
+                'com.sun.star.ui.GlobalAcceleratorConfiguration')
+            xKeyEvent = KeyEvent()
+            xKeyEvent.KeyCode = Key.F7
+            self.assertEqual(xGlobalAccelCfg.getCommandByKeyEvent(xKeyEvent), 
".uno:Credits")
+
+            # Set a shortcut on the module scope
+            with 
self.ui_test.execute_dialog_through_command(".uno:ConfigureDialog") as xDialog:
+                self.assign_key(xDialog, "module", "F8", ".uno:EditBookmark")
+
+            # Check that the key made it into the module config
+            xModuleAccelCfg = 
self.xContext.ServiceManager.createInstanceWithArguments(
+                'com.sun.star.ui.ModuleAcceleratorConfiguration',
+                ('com.sun.star.text.TextDocument',))
+            xKeyEvent = KeyEvent()
+            xKeyEvent.KeyCode = Key.F8
+            self.assertEqual(xModuleAccelCfg.getCommandByKeyEvent(xKeyEvent), 
".uno:EditBookmark")
+
+            # Set a shortcut on the document scope
+            with 
self.ui_test.execute_dialog_through_command(".uno:ConfigureDialog") as xDialog:
+                self.assign_key(xDialog, "Untitled 1", "F9", 
".uno:OptionsSecurityDialog")
+
+            # Check that the key made it into the document config
+            xDocAccelCfg = 
xComponent.getUIConfigurationManager().getShortCutManager()
+            xKeyEvent = KeyEvent()
+            xKeyEvent.KeyCode = Key.F9
+            self.assertEqual(xDocAccelCfg.getCommandByKeyEvent(xKeyEvent),
+                             ".uno:OptionsSecurityDialog")
+
+    def test_scope_multi_doc(self):
+        # Create two writer documents and a calc document. Only the
+        # writer documents should appear in the scope combobox
+        with self.ui_test.create_doc_in_start_center("writer"), \
+             self.ui_test.load_empty_file("calc"), \
+             self.ui_test.load_empty_file("writer"), \
+             
self.ui_test.execute_dialog_through_command(".uno:ConfigureDialog") as xDialog:
+            xAcceleratorPage = xDialog.getChild("AccelConfigPage")
+
+            select_keyboard_tab(xDialog)
+            xAcceleratorPage.getChild("document").executeAction("CLICK", 
tuple())
+
+            xScope = xAcceleratorPage.getChild("savein")
+
+            self.assertEqual(get_state_as_dict(xScope)["EntryCount"], "2")
+
+            # The current document should be listed first
+            select_pos(xScope, "0")
+            self.assertEqual(get_state_as_dict(xScope)["SelectEntryText"], 
"Untitled 3")
+
+            select_pos(xScope, "1")
+            self.assertEqual(get_state_as_dict(xScope)["SelectEntryText"], 
"Untitled 1")
+
+    def test_deleted_doc(self):
+        # Create two writer docs then try to assign a key in the first
+        # one but close the document before pressing ok.
+        with self.ui_test.create_doc_in_start_center("writer") as xDoc1, \
+             self.ui_test.load_empty_file("writer"), \
+             
self.ui_test.execute_dialog_through_command(".uno:ConfigureDialog") as xDialog:
+            self.assign_key(xDialog, "Untitled 1", "F7", ".uno:Credits")
+            xFrame1 = xDoc1.getCurrentController().getFrame()
+            self.xUITest.executeCommandForProvider(".uno:CloseDoc", xFrame1)
+
+            xAcceleratorPage = xDialog.getChild("AccelConfigPage")
+            xScope = xAcceleratorPage.getChild("savein")
+            self.assertEqual(get_state_as_dict(xScope)["SelectEntryText"], 
"Untitled 2")
+
+    def test_reset_doc(self):
+        with self.ui_test.create_doc_in_start_center("writer") as xComponent:
+            with 
self.ui_test.execute_dialog_through_command(".uno:ConfigureDialog") as xDialog:
+                # Assign a key
+                self.assign_key(xDialog, "Untitled 1", "F7", ".uno:Credits")
+                # … but then press reset before pressing OK
+                xReset = xDialog.getChild("AccelConfigPage").getChild("reset")
+                xReset.executeAction("CLICK", tuple())
+
+            # Make sure the key isn’t assigned
+            xDocAccelCfg = 
xComponent.getUIConfigurationManager().getShortCutManager()
+            xKeyEvent = KeyEvent()
+            xKeyEvent.KeyCode = Key.F7
+            with self.assertRaises(NoSuchElementException):
+                xDocAccelCfg.getCommandByKeyEvent(xKeyEvent)
+
+    def test_savein_enabled(self):
+        with self.ui_test.create_doc_in_start_center("writer") as xComponent:
+            with 
self.ui_test.execute_dialog_through_command(".uno:ConfigureDialog") as xDialog:
+                select_keyboard_tab(xDialog)
+
+                xAcceleratorPage = xDialog.getChild("AccelConfigPage")
+                xScope = xAcceleratorPage.getChild("savein")
+
+                # The combobox should be initially disabled
+                self.assertEqual(get_state_as_dict(xScope)["Enabled"], "false")
+
+                # Check that selecting any of the other radio buttons makes 
the save-in combobox be
+                # disabled
+                xAcceleratorPage.getChild("office").executeAction("CLICK", 
tuple())
+                self.assertEqual(get_state_as_dict(xScope)["Enabled"], "false")
+                xAcceleratorPage.getChild("module").executeAction("CLICK", 
tuple())
+                self.assertEqual(get_state_as_dict(xScope)["Enabled"], "false")
+
+                # It should be enabled when the document radio button is 
selected
+                xAcceleratorPage.getChild("document").executeAction("CLICK", 
tuple())
+                self.assertEqual(get_state_as_dict(xScope)["Enabled"], "true")
+
+                # … and disabled again if something else is selected
+                xAcceleratorPage.getChild("office").executeAction("CLICK", 
tuple())
+                self.assertEqual(get_state_as_dict(xScope)["Enabled"], "false")
+
+# vim: set shiftwidth=4 softtabstop=4 expandtab:
diff --git a/cui/source/customize/acccfg.cxx b/cui/source/customize/acccfg.cxx
index 8d758b09a1cb..e29b6e9fb253 100644
--- a/cui/source/customize/acccfg.cxx
+++ b/cui/source/customize/acccfg.cxx
@@ -22,6 +22,7 @@
 #include <acccfg.hxx>
 #include <cfgutil.hxx>
 #include <dialmgr.hxx>
+#include <cfg.hxx>
 
 #include <sfx2/filedlghelper.hxx>
 #include <sfx2/minfitem.hxx>
@@ -44,10 +45,13 @@
 #include <com/sun/star/frame/XModel.hpp>
 #include <com/sun/star/frame/ModuleManager.hpp>
 #include <com/sun/star/frame/theUICommandDescription.hpp>
+#include <com/sun/star/frame/FrameSearchFlag.hpp>
+#include <com/sun/star/frame/UnknownModuleException.hpp>
 #include <com/sun/star/ui/GlobalAcceleratorConfiguration.hpp>
 #include <com/sun/star/ui/theModuleUIConfigurationManagerSupplier.hpp>
 #include <com/sun/star/ui/UIConfigurationManager.hpp>
 #include <com/sun/star/ui/XUIConfigurationManager.hpp>
+#include <com/sun/star/ui/XUIConfigurationManagerSupplier.hpp>
 #include <com/sun/star/ui/dialogs/TemplateDescription.hpp>
 
 // include search util
@@ -56,12 +60,15 @@
 #include <unotools/textsearch.hxx>
 
 // include other projects
+#include <comphelper/documentinfo.hxx>
 #include <comphelper/processfactory.hxx>
 #include <svtools/acceleratorexecute.hxx>
 #include <vcl/svapp.hxx>
 #include <vcl/weld/Builder.hxx>
 #include <comphelper/sequenceashashmap.hxx>
 #include <config_features.h>
+#include <unotools/configmgr.hxx>
+#include <cppuhelper/implbase.hxx>
 
 #include <com/sun/star/frame/XLayoutManager.hpp>
 
@@ -820,6 +827,81 @@ const sal_uInt16 KEYCODE_ARRAY[] = { KEY_F1,
 
 const sal_uInt16 KEYCODE_ARRAY_SIZE = std::size(KEYCODE_ARRAY);
 
+namespace
+{
+struct AcceleratorSaveInData
+{
+    AcceleratorSaveInData(const uno::Reference<frame::XModel>& xModel,
+                          const uno::Reference<ui::XAcceleratorConfiguration>& 
xAccMgr)
+        : m_xModel(xModel)
+        , m_xAccMgr(xAccMgr)
+    {
+    }
+
+    uno::Reference<frame::XModel> m_xModel;
+    uno::Reference<ui::XAcceleratorConfiguration> m_xAccMgr;
+};
+}
+
+// Helper class to listen for components being disposed so we can
+// remove them from the SaveIn combobox
+class ComponentDisposedListener : public 
::cppu::WeakImplHelper<lang::XEventListener>
+{
+public:
+    ComponentDisposedListener(SfxAcceleratorConfigPage* pAccelCfgPage)
+        : m_pAccelCfgPage(pAccelCfgPage)
+    {
+    }
+
+    void SAL_CALL disposing(const lang::EventObject& rEvent) override;
+
+private:
+    SfxAcceleratorConfigPage* m_pAccelCfgPage;
+    friend class SfxAcceleratorConfigPage;
+};
+
+void ComponentDisposedListener::disposing(const lang::EventObject& rEvent)
+{
+    SolarMutexGuard aGuard;
+
+    if (!m_pAccelCfgPage)
+        return;
+
+    int cnt = m_pAccelCfgPage->m_xSaveInListBox->get_count();
+
+    for (int i = 0; i < cnt; ++i)
+    {
+        AcceleratorSaveInData* pData
+            = 
weld::fromId<AcceleratorSaveInData*>(m_pAccelCfgPage->m_xSaveInListBox->get_id(i));
+
+        if (pData->m_xModel == rEvent.Source)
+        {
+            int nOldActive = m_pAccelCfgPage->m_xSaveInListBox->get_active();
+
+            m_pAccelCfgPage->m_xSaveInListBox->remove(i);
+
+            if (m_pAccelCfgPage->m_xDocumentButton->get_active() && nOldActive 
== i)
+            {
+                m_pAccelCfgPage->m_xModuleButton->set_active(true);
+                m_pAccelCfgPage->m_xSaveInListBox->set_active(0);
+                m_pAccelCfgPage->HandleScopeChanged();
+            }
+
+            if (m_pAccelCfgPage->m_xSaveInListBox->get_count() <= 0)
+            {
+                m_pAccelCfgPage->m_xSaveInListBox->hide();
+                m_pAccelCfgPage->m_xDocumentButton->hide();
+            }
+
+            pData->m_xModel->removeEventListener(this);
+
+            delete pData;
+
+            break;
+        }
+    }
+}
+
 /** select the entry, which match the current key input ... excepting
     keys, which are used for the dialog itself.
   */
@@ -873,6 +955,7 @@ 
SfxAcceleratorConfigPage::SfxAcceleratorConfigPage(weld::Container* pPage,
     , m_xEntriesBox(m_xBuilder->weld_tree_view(u"shortcuts"_ustr))
     , m_xOfficeButton(m_xBuilder->weld_radio_button(u"office"_ustr))
     , m_xModuleButton(m_xBuilder->weld_radio_button(u"module"_ustr))
+    , m_xDocumentButton(m_xBuilder->weld_radio_button(u"document"_ustr))
     , m_xChangeButton(m_xBuilder->weld_button(u"change"_ustr))
     , m_xRemoveButton(m_xBuilder->weld_button(u"delete"_ustr))
     , m_xGroupLBox(new 
CuiConfigGroupListBox(m_xBuilder->weld_tree_view(u"category"_ustr)))
@@ -882,6 +965,8 @@ 
SfxAcceleratorConfigPage::SfxAcceleratorConfigPage(weld::Container* pPage,
     , m_xLoadButton(m_xBuilder->weld_button(u"load"_ustr))
     , m_xSaveButton(m_xBuilder->weld_button(u"save"_ustr))
     , m_xResetButton(m_xBuilder->weld_button(u"reset"_ustr))
+    , m_xSaveInListBox(m_xBuilder->weld_combo_box(u"savein"_ustr))
+    , m_xComponentDisposedListener(new ComponentDisposedListener(this))
 {
     Size aSize(m_xEntriesBox->get_approximate_digit_width() * 40,
                m_xEntriesBox->get_height_rows(10));
@@ -908,6 +993,8 @@ 
SfxAcceleratorConfigPage::SfxAcceleratorConfigPage(weld::Container* pPage,
     m_xSaveButton->connect_clicked(LINK(this, SfxAcceleratorConfigPage, Save));
     m_xResetButton->connect_clicked(LINK(this, SfxAcceleratorConfigPage, 
Default));
     m_xOfficeButton->connect_toggled(LINK(this, SfxAcceleratorConfigPage, 
RadioHdl));
+    m_xDocumentButton->connect_toggled(LINK(this, SfxAcceleratorConfigPage, 
DocumentRadioHdl));
+    m_xSaveInListBox->connect_changed(LINK(this, SfxAcceleratorConfigPage, 
SelectSaveInLocation));
     m_xSearchEdit->connect_changed(LINK(this, SfxAcceleratorConfigPage, 
SearchUpdateHdl));
     m_xSearchEdit->connect_focus_out(LINK(this, SfxAcceleratorConfigPage, 
FocusOut_Impl));
 
@@ -949,6 +1036,11 @@ SfxAcceleratorConfigPage::~SfxAcceleratorConfigPage()
         TAccInfo* pUserData = 
weld::fromId<TAccInfo*>(m_xEntriesBox->get_id(i));
         delete pUserData;
     }
+
+    // Free the dynamic user data for the SaveIn combobox
+    ClearSaveInComboBox();
+
+    m_xComponentDisposedListener->m_pAccelCfgPage = nullptr;
 }
 
 void SfxAcceleratorConfigPage::InitAccCfg()
@@ -1151,9 +1243,13 @@ IMPL_LINK_NOARG(SfxAcceleratorConfigPage, Save, 
weld::Button&, void)
 
 IMPL_LINK_NOARG(SfxAcceleratorConfigPage, Default, weld::Button&, void)
 {
-    uno::Reference<form::XReset> xReset(m_xAct, uno::UNO_QUERY);
-    if (xReset.is())
-        xReset->reset();
+    // XReset doesn’t work on document-based configs
+    if (!m_xDocumentButton->get_active())
+    {
+        uno::Reference<form::XReset> xReset(m_xAct, uno::UNO_QUERY);
+        if (xReset.is())
+            xReset->reset();
+    }
 
     m_xEntriesBox->freeze();
     ResetConfig();
@@ -1288,6 +1384,23 @@ IMPL_LINK(SfxAcceleratorConfigPage, SelectHdl, 
weld::TreeView&, rListBox, void)
 }
 
 IMPL_LINK_NOARG(SfxAcceleratorConfigPage, RadioHdl, weld::Toggleable&, void)
+{
+    HandleScopeChanged();
+}
+
+IMPL_LINK_NOARG(SfxAcceleratorConfigPage, DocumentRadioHdl, weld::Toggleable&, 
void)
+{
+    m_xSaveInListBox->set_sensitive(m_xDocumentButton->get_active());
+    HandleScopeChanged();
+}
+
+IMPL_LINK_NOARG(SfxAcceleratorConfigPage, SelectSaveInLocation, 
weld::ComboBox&, void)
+{
+    m_xDocumentButton->set_active(true);
+    HandleScopeChanged();
+}
+
+void SfxAcceleratorConfigPage::HandleScopeChanged()
 {
     uno::Reference<ui::XAcceleratorConfiguration> xOld = m_xAct;
 
@@ -1295,6 +1408,11 @@ IMPL_LINK_NOARG(SfxAcceleratorConfigPage, RadioHdl, 
weld::Toggleable&, void)
         m_xAct = m_xGlobal;
     else if (m_xModuleButton->get_active())
         m_xAct = m_xModule;
+    else if (m_xDocumentButton->get_active())
+    {
+        OUString sId = m_xSaveInListBox->get_active_id();
+        m_xAct = weld::fromId<AcceleratorSaveInData*>(sId)->m_xAccMgr;
+    }
 
     // nothing changed? => do nothing!
     if (m_xAct.is() && (xOld == m_xAct))
@@ -1514,6 +1632,107 @@ bool SfxAcceleratorConfigPage::FillItemSet(SfxItemSet*)
     return true;
 }
 
+void SfxAcceleratorConfigPage::ClearSaveInComboBox()
+{
+    int cnt = m_xSaveInListBox->get_count();
+
+    for (int i = 0; i < cnt; ++i)
+    {
+        AcceleratorSaveInData* pData
+            = 
weld::fromId<AcceleratorSaveInData*>(m_xSaveInListBox->get_id(i));
+
+        pData->m_xModel->removeEventListener(m_xComponentDisposedListener);
+
+        delete pData;
+    }
+
+    m_xSaveInListBox->clear();
+}
+
+void SfxAcceleratorConfigPage::AddFrameToSaveInComboBox(const 
uno::Reference<frame::XFrame>& xFrame)
+{
+    uno::Reference<frame::XController> xController = xFrame->getController();
+
+    if (!xController.is())
+        return;
+
+    uno::Reference<frame::XModel> xModel = xController->getModel();
+
+    if (!xModel.is())
+        return;
+
+    uno::Reference<css::ui::XUIConfigurationManagerSupplier> 
xCfgSupplier(xModel, uno::UNO_QUERY);
+
+    if (!xCfgSupplier.is())
+        return;
+
+    uno::Reference<css::ui::XUIConfigurationManager> xDocCfgMgr
+        = xCfgSupplier->getUIConfigurationManager();
+
+    if (!xCfgSupplier)
+        return;
+
+    uno::Reference<css::ui::XAcceleratorConfiguration> xAccMgr = 
xDocCfgMgr->getShortCutManager();
+
+    if (!xAccMgr)
+        return;
+
+    AcceleratorSaveInData* pDocData = new AcceleratorSaveInData(xModel, 
xAccMgr);
+    OUString aTitle = ::comphelper::DocumentInfo::getDocumentTitle(xModel);
+    m_xSaveInListBox->append(weld::toId(pDocData), aTitle);
+
+    xModel->addEventListener(m_xComponentDisposedListener);
+}
+
+void SfxAcceleratorConfigPage::FillSaveInComboBox()
+{
+    ClearSaveInComboBox();
+
+    if (!m_xModule.is() || !SvxConfigPage::CanConfig(m_sModuleLongName))
+        return;
+
+    // Add the current frame first
+    AddFrameToSaveInComboBox(m_xFrame);
+
+    // Add any other frames with the same module
+    uno::Reference<frame::XModuleManager2> xModuleManager
+        = frame::ModuleManager::create(m_xContext);
+
+    uno::Reference<frame::XDesktop2> xFramesSupplier = 
frame::Desktop::create(m_xContext);
+
+    uno::Reference<frame::XFrames> xFrames = xFramesSupplier->getFrames();
+
+    uno::Sequence<uno::Reference<frame::XFrame>> aFrameList
+        = xFrames->queryFrames(frame::FrameSearchFlag::ALL);
+
+    for (uno::Reference<frame::XFrame> const& xFrame : aFrameList)
+    {
+        if (!xFrame.is() || xFrame == m_xFrame)
+            continue;
+
+        OUString sFrameModule;
+
+        try
+        {
+            sFrameModule = xModuleManager->identify(xFrame);
+        }
+        catch (css::lang::IllegalArgumentException&)
+        {
+            continue;
+        }
+        catch (css::frame::UnknownModuleException&)
+        {
+            continue;
+        }
+
+        if (m_sModuleLongName == sFrameModule)
+            AddFrameToSaveInComboBox(xFrame);
+    }
+
+    if (m_xSaveInListBox->get_count() >= 1)
+        m_xSaveInListBox->set_active(0);
+}
+
 void SfxAcceleratorConfigPage::Reset(const SfxItemSet* rSet)
 {
     // open accelerator configs
@@ -1536,7 +1755,17 @@ void SfxAcceleratorConfigPage::Reset(const SfxItemSet* 
rSet)
         m_xOfficeButton->set_active(true);
     }
 
-    RadioHdl(*m_xOfficeButton);
+    FillSaveInComboBox();
+
+    if (m_xSaveInListBox->get_count() <= 0)
+    {
+        m_xDocumentButton->hide();
+        m_xSaveInListBox->hide();
+    }
+    else
+        m_xSaveInListBox->set_sensitive(false);
+
+    HandleScopeChanged();
 
 #if HAVE_FEATURE_SCRIPTING
     if (const SfxMacroInfoItem* pMacroItem = rSet->GetItemIfSet(SID_MACROINFO))
diff --git a/cui/source/inc/acccfg.hxx b/cui/source/inc/acccfg.hxx
index ed44d8c16d5c..9e444c2615e0 100644
--- a/cui/source/inc/acccfg.hxx
+++ b/cui/source/inc/acccfg.hxx
@@ -27,6 +27,7 @@
 #include <sfx2/tabdlg.hxx>
 #include <vcl/timer.hxx>
 #include <vcl/keycod.hxx>
+#include <vcl/weld/ComboBox.hxx>
 #include <vcl/weld/Entry.hxx>
 #include <i18nutil/searchopt.hxx>
 #include <config_features.h>
@@ -74,6 +75,8 @@ enum class StartFileDialogType
     SaveAs
 };
 
+class ComponentDisposedListener;
+
 class SfxAcceleratorConfigPage : public SfxTabPage
 {
 private:
@@ -92,6 +95,7 @@ private:
     css::uno::Reference<css::uno::XComponentContext> m_xContext;
     css::uno::Reference<css::ui::XAcceleratorConfiguration> m_xGlobal;
     css::uno::Reference<css::ui::XAcceleratorConfiguration> m_xModule;
+    css::uno::Reference<css::ui::XAcceleratorConfiguration> m_xDocument;
     css::uno::Reference<css::ui::XAcceleratorConfiguration> m_xAct;
     css::uno::Reference<css::container::XNameAccess> m_xUICmdDescription;
     css::uno::Reference<css::frame::XFrame> m_xFrame;
@@ -106,6 +110,7 @@ private:
     std::unique_ptr<weld::TreeView> m_xEntriesBox;
     std::unique_ptr<weld::RadioButton> m_xOfficeButton;
     std::unique_ptr<weld::RadioButton> m_xModuleButton;
+    std::unique_ptr<weld::RadioButton> m_xDocumentButton;
     std::unique_ptr<weld::Button> m_xChangeButton;
     std::unique_ptr<weld::Button> m_xRemoveButton;
     std::unique_ptr<CuiConfigGroupListBox> m_xGroupLBox;
@@ -115,6 +120,10 @@ private:
     std::unique_ptr<weld::Button> m_xLoadButton;
     std::unique_ptr<weld::Button> m_xSaveButton;
     std::unique_ptr<weld::Button> m_xResetButton;
+    std::unique_ptr<weld::ComboBox> m_xSaveInListBox;
+
+    rtl::Reference<ComponentDisposedListener> m_xComponentDisposedListener;
+    friend class ComponentDisposedListener;
 
     DECL_LINK(ChangeHdl, weld::Button&, void);
     DECL_LINK(RemoveHdl, weld::Button&, void);
@@ -124,6 +133,8 @@ private:
     DECL_LINK(Load, weld::Button&, void);
     DECL_LINK(Default, weld::Button&, void);
     DECL_LINK(RadioHdl, weld::Toggleable&, void);
+    DECL_LINK(DocumentRadioHdl, weld::Toggleable&, void);
+    DECL_LINK(SelectSaveInLocation, weld::ComboBox&, void);
     DECL_LINK(ImplUpdateDataHdl, Timer*, void);
     DECL_LINK(FocusOut_Impl, weld::Widget&, void);
 
@@ -140,6 +151,10 @@ private:
 
     void Init(const css::uno::Reference<css::ui::XAcceleratorConfiguration>& 
pAccMgr);
     void ResetConfig();
+    void ClearSaveInComboBox();
+    void AddFrameToSaveInComboBox(const 
css::uno::Reference<css::frame::XFrame>& xFrame);
+    void FillSaveInComboBox();
+    void HandleScopeChanged();
 
 public:
     SfxAcceleratorConfigPage(weld::Container* pPage, weld::DialogController* 
pController,
diff --git a/cui/uiconfig/ui/accelconfigpage.ui 
b/cui/uiconfig/ui/accelconfigpage.ui
index 4e4945a0012d..98acf73a9696 100644
--- a/cui/uiconfig/ui/accelconfigpage.ui
+++ b/cui/uiconfig/ui/accelconfigpage.ui
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.38.2 -->
+<!-- Generated with glade 3.40.0 -->
 <interface domain="cui">
   <requires lib="gtk+" version="3.24"/>
   <object class="GtkTreeStore" id="liststore1">
@@ -44,7 +44,7 @@
       <column type="gchararray"/>
     </columns>
   </object>
-  <!-- n-columns=1 n-rows=2 -->
+  <!-- n-columns=1 n-rows=3 -->
   <object class="GtkGrid" id="AccelConfigPage">
     <property name="visible">True</property>
     <property name="can-focus">False</property>
@@ -142,59 +142,24 @@ To quickly find a shortcut in this list, simply press the 
key combination.</prop
           </packing>
         </child>
         <child>
-          <object class="GtkBox" id="box17">
+          <object class="GtkButtonBox" id="buttonbox1">
             <property name="visible">True</property>
             <property name="can-focus">False</property>
             <property name="vexpand">True</property>
             <property name="orientation">vertical</property>
             <property name="spacing">6</property>
+            <property name="layout-style">start</property>
             <child>
-              <object class="GtkBox" id="box18">
+              <object class="GtkButton" id="change">
+                <property name="label" translatable="yes" 
context="accelconfigpage|change">_Assign</property>
                 <property name="visible">True</property>
-                <property name="can-focus">False</property>
-                <property name="orientation">vertical</property>
-                <property name="spacing">6</property>
-                <child>
-                  <object class="GtkRadioButton" id="office">
-                    <property name="label" translatable="yes" 
context="accelconfigpage|office">%PRODUCTNAME</property>
-                    <property name="visible">True</property>
-                    <property name="can-focus">True</property>
-                    <property name="receives-default">False</property>
-                    <property name="use-underline">True</property>
-                    <property name="active">True</property>
-                    <property name="draw-indicator">True</property>
-                    <child internal-child="accessible">
-                      <object class="AtkObject" id="office-atkobject">
-                        <property name="AtkObject::accessible-description" 
translatable="yes" context="accelconfigpage|extended_tip|office">Displays 
shortcut keys that are common to all the office suite applications.</property>
-                      </object>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">True</property>
-                    <property name="position">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkRadioButton" id="module">
-                    <property name="label" translatable="yes" 
context="accelconfigpage|module">$(MODULE)</property>
-                    <property name="visible">True</property>
-                    <property name="can-focus">True</property>
-                    <property name="receives-default">False</property>
-                    <property name="use-underline">True</property>
-                    <property name="draw-indicator">True</property>
-                    <property name="group">office</property>
-                    <child internal-child="accessible">
-                      <object class="AtkObject" id="module-atkobject">
-                        <property name="AtkObject::accessible-description" 
translatable="yes" context="accelconfigpage|extended_tip|module">Displays 
shortcut keys for the current office suite application.</property>
-                      </object>
-                    </child>
+                <property name="can-focus">True</property>
+                <property name="receives-default">True</property>
+                <property name="use-underline">True</property>
+                <child internal-child="accessible">
+                  <object class="AtkObject" id="change-atkobject">
+                    <property name="AtkObject::accessible-description" 
translatable="yes" context="accelconfigpage|extended_tip|change">Assigns the 
key combination selected in the Shortcut keys list to the command selected in 
the Function list.</property>
                   </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">True</property>
-                    <property name="position">1</property>
-                  </packing>
                 </child>
               </object>
               <packing>
@@ -204,117 +169,83 @@ To quickly find a shortcut in this list, simply press 
the key combination.</prop
               </packing>
             </child>
             <child>
-              <object class="GtkButtonBox" id="buttonbox1">
+              <object class="GtkButton" id="delete">
+                <property name="label" translatable="yes" 
context="stock">_Delete</property>
                 <property name="visible">True</property>
-                <property name="can-focus">False</property>
-                <property name="vexpand">True</property>
-                <property name="orientation">vertical</property>
-                <property name="spacing">6</property>
-                <property name="layout-style">start</property>
-                <child>
-                  <object class="GtkButton" id="change">
-                    <property name="label" translatable="yes" 
context="accelconfigpage|change">_Assign</property>
-                    <property name="visible">True</property>
-                    <property name="can-focus">True</property>
-                    <property name="receives-default">True</property>
-                    <property name="use-underline">True</property>
-                    <child internal-child="accessible">
-                      <object class="AtkObject" id="change-atkobject">
-                        <property name="AtkObject::accessible-description" 
translatable="yes" context="accelconfigpage|extended_tip|change">Assigns the 
key combination selected in the Shortcut keys list to the command selected in 
the Function list.</property>
-                      </object>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">True</property>
-                    <property name="position">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkButton" id="delete">
-                    <property name="label" translatable="yes" 
context="stock">_Delete</property>
-                    <property name="visible">True</property>
-                    <property name="can-focus">True</property>
-                    <property name="receives-default">True</property>
-                    <property name="use-underline">True</property>
-                    <child internal-child="accessible">
-                      <object class="AtkObject" id="delete-atkobject">
-                        <property name="AtkObject::accessible-description" 
translatable="yes" context="accelconfigpage|extended_tip|delete">Deletes the 
selected element or elements without requiring confirmation.</property>
-                      </object>
-                    </child>
+                <property name="can-focus">True</property>
+                <property name="receives-default">True</property>
+                <property name="use-underline">True</property>
+                <child internal-child="accessible">
+                  <object class="AtkObject" id="delete-atkobject">
+                    <property name="AtkObject::accessible-description" 
translatable="yes" context="accelconfigpage|extended_tip|delete">Deletes the 
selected element or elements without requiring confirmation.</property>
                   </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">True</property>
-                    <property name="position">1</property>
-                  </packing>
                 </child>
-                <child>
-                  <object class="GtkButton" id="load">
-                    <property name="label" translatable="yes" 
context="accelconfigpage|load">_Load...</property>
-                    <property name="visible">True</property>
-                    <property name="can-focus">True</property>
-                    <property name="receives-default">True</property>
-                    <property name="use-underline">True</property>
-                    <child internal-child="accessible">
-                      <object class="AtkObject" id="load-atkobject">
-                        <property name="AtkObject::accessible-description" 
translatable="yes" context="accelconfigpage|extended_tip|load">Replaces the 
shortcut key configuration with one that was previously saved.</property>
-                      </object>
-                    </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="load">
+                <property name="label" translatable="yes" 
context="accelconfigpage|load">_Load...</property>
+                <property name="visible">True</property>
+                <property name="can-focus">True</property>
+                <property name="receives-default">True</property>
+                <property name="use-underline">True</property>
+                <child internal-child="accessible">
+                  <object class="AtkObject" id="load-atkobject">
+                    <property name="AtkObject::accessible-description" 
translatable="yes" context="accelconfigpage|extended_tip|load">Replaces the 
shortcut key configuration with one that was previously saved.</property>
                   </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">True</property>
-                    <property name="position">2</property>
-                    <property name="secondary">True</property>
-                  </packing>
                 </child>
-                <child>
-                  <object class="GtkButton" id="save">
-                    <property name="label" translatable="yes" 
context="accelconfigpage|save">_Save...</property>
-                    <property name="visible">True</property>
-                    <property name="can-focus">True</property>
-                    <property name="receives-default">True</property>
-                    <property name="use-underline">True</property>
-                    <child internal-child="accessible">
-                      <object class="AtkObject" id="save-atkobject">
-                        <property name="AtkObject::accessible-description" 
translatable="yes" context="accelconfigpage|extended_tip|save">Saves the 
current shortcut key configuration, so that you can load it later.</property>
-                      </object>
-                    </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">2</property>
+                <property name="secondary">True</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="save">
+                <property name="label" translatable="yes" 
context="accelconfigpage|save">_Save...</property>
+                <property name="visible">True</property>
+                <property name="can-focus">True</property>
+                <property name="receives-default">True</property>
+                <property name="use-underline">True</property>
+                <child internal-child="accessible">
+                  <object class="AtkObject" id="save-atkobject">
+                    <property name="AtkObject::accessible-description" 
translatable="yes" context="accelconfigpage|extended_tip|save">Saves the 
current shortcut key configuration, so that you can load it later.</property>
                   </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">True</property>
-                    <property name="position">3</property>
-                    <property name="secondary">True</property>
-                  </packing>
                 </child>
-                <child>
-                  <object class="GtkButton" id="reset">
-                    <property name="label" translatable="yes" 
context="stock">_Reset</property>
-                    <property name="visible">True</property>
-                    <property name="can-focus">True</property>
-                    <property name="receives-default">True</property>
-                    <property name="tooltip-text" translatable="yes" 
context="accelconfigpage|tooltip|reset">Unsaved modifications to shortcut keys 
are reverted.</property>
-                    <property name="use-underline">True</property>
-                    <child internal-child="accessible">
-                      <object class="AtkObject" id="reset-atkobject">
-                        <property name="AtkObject::accessible-description" 
translatable="yes" context="accelconfigpage|extended_tip|reset">Revert any 
changes made to keyboard shortcuts to the assignments that were present when 
this dialog was opened.</property>
-                      </object>
-                    </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">3</property>
+                <property name="secondary">True</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="reset">
+                <property name="label" translatable="yes" 
context="stock">_Reset</property>
+                <property name="visible">True</property>
+                <property name="can-focus">True</property>
+                <property name="receives-default">True</property>
+                <property name="tooltip-text" translatable="yes" 
context="accelconfigpage|tooltip|reset">Unsaved modifications to shortcut keys 
are reverted.</property>
+                <property name="use-underline">True</property>
+                <child internal-child="accessible">
+                  <object class="AtkObject" id="reset-atkobject">
+                    <property name="AtkObject::accessible-description" 
translatable="yes" context="accelconfigpage|extended_tip|reset">Revert any 
changes made to keyboard shortcuts to the assignments that were present when 
this dialog was opened.</property>
                   </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">True</property>
-                    <property name="position">4</property>
-                    <property name="secondary">True</property>
-                  </packing>
                 </child>
               </object>
               <packing>
                 <property name="expand">False</property>
                 <property name="fill">True</property>
-                <property name="position">1</property>
+                <property name="position">4</property>
+                <property name="secondary">True</property>
               </packing>
             </child>
           </object>
@@ -326,7 +257,7 @@ To quickly find a shortcut in this list, simply press the 
key combination.</prop
       </object>
       <packing>
         <property name="left-attach">0</property>
-        <property name="top-attach">0</property>
+        <property name="top-attach">1</property>
       </packing>
     </child>
     <child>
@@ -574,7 +505,127 @@ To quickly find a shortcut in this list, simply press the 
key combination.</prop
       </object>
       <packing>
         <property name="left-attach">0</property>
-        <property name="top-attach">1</property>
+        <property name="top-attach">2</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkFrame">
+        <property name="visible">True</property>
+        <property name="can-focus">False</property>
+        <property name="label-xalign">0</property>
+        <property name="shadow-type">none</property>
+        <child>
+          <object class="GtkBox" id="box18">
+            <property name="visible">True</property>
+            <property name="can-focus">False</property>
+            <property name="halign">start</property>
+            <property name="hexpand">False</property>
+            <property name="spacing">6</property>
+            <child>
+              <object class="GtkRadioButton" id="office">
+                <property name="label" translatable="yes" 
context="accelconfigpage|office">%PRODUCTNAME</property>
+                <property name="visible">True</property>
+                <property name="can-focus">True</property>
+                <property name="receives-default">False</property>
+                <property name="use-underline">True</property>
+                <property name="active">True</property>
+                <property name="draw-indicator">True</property>
+                <child internal-child="accessible">
+                  <object class="AtkObject" id="office-atkobject">
+                    <property name="AtkObject::accessible-description" 
translatable="yes" context="accelconfigpage|extended_tip|office">Displays 
shortcut keys that are common to all the office suite applications.</property>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkRadioButton" id="module">
+                <property name="label" translatable="yes" 
context="accelconfigpage|module">$(MODULE)</property>
+                <property name="visible">True</property>
+                <property name="can-focus">True</property>
+                <property name="receives-default">False</property>
+                <property name="use-underline">True</property>
+                <property name="draw-indicator">True</property>
+                <property name="group">office</property>
+                <child internal-child="accessible">
+                  <object class="AtkObject" id="module-atkobject">
+                    <property name="AtkObject::accessible-description" 
translatable="yes" context="accelconfigpage|extended_tip|module">Displays 
shortcut keys for the current office suite application.</property>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkRadioButton" id="document">
+                <property name="label" translatable="yes" 
context="accelconfigpage|document">_Document</property>
+                <property name="visible">True</property>
+                <property name="can-focus">True</property>
+                <property name="receives-default">False</property>
+                <property name="use-underline">True</property>
+                <property name="draw-indicator">True</property>
+                <property name="group">office</property>
+                <child internal-child="accessible">
+                  <object class="AtkObject" id="document-atkobject">
+                    <property name="AtkObject::accessible-description" 
translatable="yes" context="accelconfigpage|extended_tip|document">Displays 
shortcut keys for the chosen document.</property>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox">
+                <property name="visible">True</property>
+                <property name="can-focus">False</property>
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="GtkComboBoxText" id="savein">
+                    <property name="visible">True</property>
+                    <property name="can-focus">False</property>
+                    <child internal-child="accessible">
+                      <object class="AtkObject" id="savein-atkobject">
+                        <property name="AtkObject::accessible-description" 
translatable="yes" context="accelconfigpage|extended_tip|savein">Select the 
document to make the shortcut be available only in that specific 
document.</property>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">3</property>
+              </packing>
+            </child>
+          </object>
+        </child>
+        <child type="label">
+          <object class="GtkLabel">
+            <property name="visible">True</property>
+            <property name="can-focus">False</property>
+            <property name="label" translatable="yes" 
context="accelconfigpage|scopelabel">S_cope</property>
+            <property name="use-underline">True</property>
+          </object>
+        </child>
+      </object>
+      <packing>
+        <property name="left-attach">0</property>
+        <property name="top-attach">0</property>
       </packing>
     </child>
     <child internal-child="accessible">

Reply via email to