') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); Timed messages in StatusBar by jonathanrocher · Pull Request #522 · enthought/pyface · GitHub
Skip to content

Timed messages in StatusBar - #522

Open
jonathanrocher wants to merge 7 commits into
mainfrom
feature/qt_status_bar_timer
Open

Timed messages in StatusBar#522
jonathanrocher wants to merge 7 commits into
mainfrom
feature/qt_status_bar_timer

Conversation

@jonathanrocher

Copy link
Copy Markdown
Collaborator

This PR exposes the ability to control the duration of a StatusBar message (qt only for now). I am creating this PR to start the discussion:

  • Would others agree that this is desirable?
  • How to handle Wx since its wx.StatusBar doesn't expose the same ability? What's pyface's policy for exposing toolkit specific capabilities? If that's not acceptable, a similar behavior could be implemented using a wx.Timer: https://stackoverflow.com/questions/23188042/wxpython-statusbar-temporary-text . That feels like a lot of complexity brought into pyface though.

@jonathanrocherjonathanrocher changed the title Timed messages in StatusBarTimed messages in StatusBar: feedback requestedMay 28, 2020

@corranwebstercorranwebster left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This generally looks fine as an addition. I would prefer to measure time in seconds and then convert to integer milliseconds at the toolkit level.

For the wx case, we have timers exposed in a toolkit-independent fashion in pyface.timer, so it should be fairly easy to add the capability to remove after a fixed amount of time with a toolkit-independent timer object.

# widget - depends on what wx is capable of.
self.status_bar.showMessage(" ".join(self.messages))
self.status_bar.showMessage(" ".join(self.messages),
msecs=self.message_duration)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think duration in seconds would be better. That is what pyface.timer uses.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed.

@jonathanrocherjonathanrocher changed the title Timed messages in StatusBar: feedback requestedTimed messages in StatusBarJul 11, 2020
@codecov-commenter

codecov-commenter commented Jul 14, 2020

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 45.71429% with 19 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (main@c98d1d3). Learn more about missing BASE report.

Files with missing linesPatch %Lines
pyface/ui/wx/action/status_bar_manager.py26.31%14 Missing ⚠️
pyface/ui/qt4/action/status_bar_manager.py68.75%3 Missing and 2 partials ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files

Impacted file tree graph

@@ Coverage Diff @@## main #522 +/- ##
=======================================
Coverage ? 38.89% =======================================
Files ? 487 Lines ? 26818 Branches ? 4067 =======================================
Hits ? 10431 Misses ? 15918 Partials ? 469 
Files with missing linesCoverage Δ
pyface/ui/qt4/action/status_bar_manager.py71.18% <68.75%> (ø)
pyface/ui/wx/action/status_bar_manager.py30.00% <26.31%> (ø)

Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update c98d1d3...feed701. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@jonathanrocher

Copy link
Copy Markdown
CollaboratorAuthor

This is ready for a second look. WX support added, implementation redone for Qt, unit test added.

@jonathanrocher

Copy link
Copy Markdown
CollaboratorAuthor

Test failure only happens with pyqt, not pyqt5 and is triggered by the following exception in the listener:

Exception occurred in traits notification handler.
Please check the log file for details.
Exception occurred in traits notification handler for object: <pyface.ui.qt4.action.status_bar_manager.StatusBarManager object at 0x127802888>, trait: messages, old value: ['goodbye world'], new value: []
Traceback (most recent call last):
File "/Users/jrocher/.edm/envs/bootstrap3/lib/python3.6/site-packages/traits/trait_notifiers.py", line 381, in __call__
self.handler(*args)
File "/Users/jrocher/Projects/ETS_source/pyface/pyface/ui/qt4/action/status_bar_manager.py", line 100, in _messages_changed
self._show_messages()
File "/Users/jrocher/Projects/ETS_source/pyface/pyface/ui/qt4/action/status_bar_manager.py", line 142, in _show_messages
self.status_bar.showMessage(" ".join(self.messages))
RuntimeError: wrapped C/C++ object of type QStatusBar has been deleted

Can't make sense of it. Any suggestions?

@kitchoi

Copy link
Copy Markdown
Contributor

If the UI is closed before the timer gets to run its action, then when the timer's action is run, it would be accessing deleted objects. I think in the remove_status_bar method, we should stop and remove the timer if there is one. See if that resolves the error you saw?

@jonathanrocher

Copy link
Copy Markdown
CollaboratorAuthor

Thanks for the suggestion @kitchoi . Didn't help with the test failure, but good suggestion anyway, so added.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@jonathanrocher@codecov-commenter@kitchoi@corranwebster