include/vcl/weld.hxx | 3 svx/source/tbxctrls/tbunosearchcontrollers.cxx | 29 ++++--- vcl/inc/salvtables.hxx | 7 + vcl/source/app/salvtables.cxx | 81 ++++++++++++++++++++++ vcl/unx/gtk3/gtkinst.cxx | 92 +++++++++++++++++++++++++ 5 files changed, 201 insertions(+), 11 deletions(-)
New commits: commit d1da1c59d196b7f6037b7e0820b81fc527d56a4c Author: Caolán McNamara <caol...@redhat.com> AuthorDate: Thu Apr 14 20:13:17 2022 +0100 Commit: Caolán McNamara <caol...@redhat.com> CommitDate: Fri Apr 15 20:44:49 2022 +0200 tdf#148349 add a way to call the user's attention to a widget Change-Id: I2846155a44f3e51ddd8cc1acd81e84a38b4d3934 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/133030 Tested-by: Jenkins Reviewed-by: Caolán McNamara <caol...@redhat.com> diff --git a/include/vcl/weld.hxx b/include/vcl/weld.hxx index bdef5b22b9ca..226e2cb6c21a 100644 --- a/include/vcl/weld.hxx +++ b/include/vcl/weld.hxx @@ -320,6 +320,9 @@ public: virtual VclPtr<VirtualDevice> create_virtual_device() const = 0; + //do something transient to attract the attention of the user to the widget + virtual void call_attention_to() = 0; + //make this widget look like a page in a notebook virtual void set_stack_background() = 0; //make this widget look like it has a highlighted background diff --git a/svx/source/tbxctrls/tbunosearchcontrollers.cxx b/svx/source/tbxctrls/tbunosearchcontrollers.cxx index 6deb930c18f2..b168462baf2d 100644 --- a/svx/source/tbxctrls/tbunosearchcontrollers.cxx +++ b/svx/source/tbxctrls/tbunosearchcontrollers.cxx @@ -295,22 +295,29 @@ IMPL_LINK(FindTextFieldControl, KeyInputHdl, const KeyEvent&, rKeyEvent, bool) } } } - // Select text in the search box when Ctrl-F pressed - else if ( bMod1 && nCode == KEY_F ) - m_xWidget->select_entry_region(0, -1); - - // Execute the search when Ctrl-G, F3 and Shift-RETURN pressed (in addition to ActivateHdl condition which handles bare RETURN) - else if ( (bMod1 && KEY_G == nCode) || (bShift && KEY_RETURN == nCode) || (KEY_F3 == nCode) ) - { - ActivateFind(bShift); - bRet = true; - } else { auto awtKey = svt::AcceleratorExecute::st_VCLKey2AWTKey(rKeyEvent.GetKeyCode()); const OUString aCommand(m_pAcc->findCommand(awtKey)); - if (aCommand == ".uno:SearchDialog") + + // Select text in the search box when Ctrl-F pressed + if ( bMod1 && nCode == KEY_F ) + m_xWidget->select_entry_region(0, -1); + // Execute the search when Ctrl-G, F3 and Shift-RETURN pressed (in addition to ActivateHdl condition which handles bare RETURN) + else if ( (bMod1 && KEY_G == nCode) || (bShift && KEY_RETURN == nCode) || (KEY_F3 == nCode) ) + { + ActivateFind(bShift); + bRet = true; + } + else if (aCommand == ".uno:SearchDialog") bRet = m_pAcc->execute(awtKey); + + // find-shortcut called with focus already in find + if (aCommand == "vnd.sun.star.findbar:FocusToFindbar") + { + m_xWidget->call_attention_to(); + bRet = true; + } } return bRet || ChildKeyInput(rKeyEvent); diff --git a/vcl/inc/salvtables.hxx b/vcl/inc/salvtables.hxx index 5f8ceb6b9ae3..3f574237444b 100644 --- a/vcl/inc/salvtables.hxx +++ b/vcl/inc/salvtables.hxx @@ -167,10 +167,13 @@ public: virtual ~SalInstanceMenu() override; }; +class SalFlashAttention; + class SalInstanceWidget : public virtual weld::Widget { protected: VclPtr<vcl::Window> m_xWidget; + std::unique_ptr<SalFlashAttention> m_xFlashAttention; SalInstanceBuilder* m_pBuilder; private: @@ -367,6 +370,8 @@ public: virtual void get_property_tree(tools::JsonWriter& rJsonWriter) override; + virtual void call_attention_to() override; + virtual void set_stack_background() override; virtual void set_title_background() override; @@ -1000,6 +1005,8 @@ public: virtual void HandleEventListener(VclWindowEvent& rEvent) override; + virtual void call_attention_to() override; + virtual ~SalInstanceComboBoxWithEdit() override; }; diff --git a/vcl/source/app/salvtables.cxx b/vcl/source/app/salvtables.cxx index 263c29ff5eea..a8fdaf2a7978 100644 --- a/vcl/source/app/salvtables.cxx +++ b/vcl/source/app/salvtables.cxx @@ -559,6 +559,79 @@ VclPtr<VirtualDevice> SalInstanceWidget::create_virtual_device() const DeviceFormat::DEFAULT); } +class SalFlashAttention +{ +private: + VclPtr<vcl::Window> m_xWidget; + Timer m_aFlashTimer; + Color m_aOrigControlBackground; + Wallpaper m_aOrigBackground; + bool m_bOrigControlBackground; + int m_nFlashCount; + + void SetFlash() + { + Color aColor(Application::GetSettings().GetStyleSettings().GetHighlightColor()); + m_xWidget->SetControlBackground(aColor); + } + + void ClearFlash() + { + if (m_bOrigControlBackground) + m_xWidget->SetControlBackground(m_aOrigControlBackground); + else + m_xWidget->SetControlBackground(); + } + + void Flash() + { + constexpr int FlashesWanted = 1; + + if (m_nFlashCount % 2 == 0) + ClearFlash(); + else + SetFlash(); + + if (m_nFlashCount == FlashesWanted * 2) + return; + + ++m_nFlashCount; + + m_aFlashTimer.Start(); + } + + DECL_LINK(FlashTimeout, Timer*, void); + +public: + SalFlashAttention(VclPtr<vcl::Window> xWidget) + : m_xWidget(xWidget) + , m_aFlashTimer("SalFlashAttention") + , m_bOrigControlBackground(false) + , m_nFlashCount(1) + { + m_aFlashTimer.SetTimeout(150); + m_aFlashTimer.SetInvokeHandler(LINK(this, SalFlashAttention, FlashTimeout)); + } + + void Start() + { + m_bOrigControlBackground = m_xWidget->IsControlBackground(); + if (m_bOrigControlBackground) + m_aOrigControlBackground = m_xWidget->GetControlBackground(); + m_aFlashTimer.Start(); + } + + ~SalFlashAttention() { ClearFlash(); } +}; + +IMPL_LINK_NOARG(SalFlashAttention, FlashTimeout, Timer*, void) { Flash(); } + +void SalInstanceWidget::call_attention_to() +{ + m_xFlashAttention.reset(new SalFlashAttention(m_xWidget)); + m_xFlashAttention->Start(); +} + css::uno::Reference<css::datatransfer::dnd::XDropTarget> SalInstanceWidget::get_drop_target() { return m_xWidget->GetDropTarget(); @@ -6356,6 +6429,14 @@ SalInstanceComboBoxWithEdit::SalInstanceComboBoxWithEdit(::ComboBox* pComboBox, bool SalInstanceComboBoxWithEdit::has_entry() const { return true; } +void SalInstanceComboBoxWithEdit::call_attention_to() +{ + Edit* pEdit = m_xComboBox->GetSubEdit(); + assert(pEdit); + m_xFlashAttention.reset(new SalFlashAttention(pEdit)); + m_xFlashAttention->Start(); +} + bool SalInstanceComboBoxWithEdit::changed_by_direct_pick() const { return m_bInSelect && !m_xComboBox->IsModifyByKeyboard() && !m_xComboBox->IsTravelSelect(); diff --git a/vcl/unx/gtk3/gtkinst.cxx b/vcl/unx/gtk3/gtkinst.cxx index 50e3b7949877..65c618eeae13 100644 --- a/vcl/unx/gtk3/gtkinst.cxx +++ b/vcl/unx/gtk3/gtkinst.cxx @@ -2494,6 +2494,92 @@ void set_buildable_id(GtkBuildable* pWidget, const OString& rId) namespace { +class FlashAttention +{ +private: + GtkWidget* m_pWidget; + int m_nFlashCount; + gint m_nFlashTimeout; + + static gboolean signalDraw(GtkWidget* pWidget, cairo_t* cr, gpointer self) + { + FlashAttention* pThis = static_cast<FlashAttention*>(self); + if (pThis->m_nFlashCount % 2 == 0) + return false; + + GtkAllocation alloc {0, 0, + gtk_widget_get_allocated_width(pWidget), + gtk_widget_get_allocated_height(pWidget)}; + + Color aColor(Application::GetSettings().GetStyleSettings().GetHighlightColor()); + cairo_set_source_rgba(cr, aColor.GetRed() / 255.0, aColor.GetGreen() / 255.0, aColor.GetBlue() / 255.0, 0.5); + cairo_rectangle(cr, alloc.x + 0.5, alloc.y + 0.5, alloc.width - 1, alloc.height - 1); + cairo_fill(cr); + + return false; + } + + static void signalUnmap(gpointer self) + { + FlashAttention* pThis = static_cast<FlashAttention*>(self); + pThis->ClearFlash(); + } + + void ClearFlash() + { + if (m_nFlashTimeout != 0) + { + g_source_remove(m_nFlashTimeout); + m_nFlashTimeout = 0; + } + if (m_pWidget) + { + gtk_widget_queue_draw(m_pWidget); + g_signal_handlers_disconnect_by_func(m_pWidget, reinterpret_cast<void*>(signalDraw), this); + g_signal_handlers_disconnect_by_func(m_pWidget, reinterpret_cast<void*>(signalUnmap), this); + m_pWidget = nullptr; + } + } + + bool QueueFlash() + { + constexpr int FlashesWanted = 1; + + gtk_widget_queue_draw(m_pWidget); + m_nFlashCount++; + + if (m_nFlashCount == FlashesWanted * 2) + { + ClearFlash(); + return false; + } + + return true; + } + + static gboolean FlashTimeout(FlashAttention* pThis) + { + return pThis->QueueFlash(); + } + +public: + FlashAttention(GtkWidget* pWidget) + : m_pWidget(pWidget) + , m_nFlashCount(1) + { + g_signal_connect_after(m_pWidget, "draw", G_CALLBACK(signalDraw), this); + g_signal_connect_swapped(m_pWidget, "unmap", G_CALLBACK(signalUnmap), this); + gtk_widget_queue_draw(m_pWidget); + + m_nFlashTimeout = g_timeout_add(250, reinterpret_cast<GSourceFunc>(FlashTimeout), this); + } + + ~FlashAttention() + { + ClearFlash(); + } +}; + class GtkInstanceWidget : public virtual weld::Widget { protected: @@ -2789,6 +2875,7 @@ private: #if !GTK_CHECK_VERSION(4, 0, 0) GdkDragAction m_eDragAction; #endif + std::unique_ptr<FlashAttention> m_xFlashAttention; gulong m_nFocusInSignalId; gulong m_nMnemonicActivateSignalId; gulong m_nFocusOutSignalId; @@ -4153,6 +4240,11 @@ public: //not implemented for the gtk variant } + virtual void call_attention_to() override + { + m_xFlashAttention.reset(new FlashAttention(m_pWidget)); + } + virtual void set_stack_background() override { do_set_background(Application::GetSettings().GetStyleSettings().GetWindowColor());