TScroller does not update the scroll bars reliably when the scroll range is changed.
For example, TCoolEdit will not display the vertical scroller when the number of text lines exceeds the number of the visible lines. Only after resizing the window the scrollbar will appear. When opening a file that doesn't fit into the window, the scroller is displayed.
To reproduce open a new edit window and type Enter until the cursor reaches the end of the window.
Bugs: #615
Feature Requests: #266
Wiki: OWLNext_7_Series_Revision_History
Although AdjustScroller() is called when a line is added, the scrollbar will not appear even if Scroller->SetRange applies an yRange well beyond the number of sreen lines.
The problem is not related to TCoolScroller, it also occurs with TScroller.
Last edit: Erwin Lotter 2025-08-04
This issue has been fixed in branches/elotter-cooledit by [r8524].
In fact, the fix is more of a workaround than a solution and could indicate that there is an issue with the underlying scroller handling.
Related
Commit: [r8524]
Diff:
Thanks for creating the ticket and looking into this. I ran into the same problem on the trunk. At some point I had some elaborate code trying to circumvent the issue, but I decided to remove it, since it was quite brittle, and I concluded the issue originates in TScroller and should be fixed at source.
I've updated the ticket title to reflect the source of the problem.
On introducing the horizontal scrollbar adjustment new issues appeared.
I could solve most of them by skipping the Scroller->ShowWindow calls and hiding unnecessary bars by setting the scroll range to zero. The remaining issues are:
If a bar gets activated while the other bar is already visible, its range will be ignored and the maximum range will be applied. Only after resizing the window the bars will be ok.
After the horizontal bar is activated, the scroll range will be too small by approx. two characters. Only after resizing the window the scroller will be ok.
Claude Code analysis:
I analyzed bug #623 ("TScroller scroll bars behave erratically", open, milestone 8) against trunk and
branches/elotter-cooledit(both at r8968). The cause is howTScrollerandTWindowupdate the scroll bars, notTCoolEdit, which matches Vidar's view on the ticket. This comes from reading the code and the Win32 scroll-bar rules; I haven't run a repro.What the ticket says
TCoolEdit, press Enter until the text passes the bottom of the window. No vertical scroll bar appears until you resize. A file that's already too long when opened is fine.AdjustScroller. Showing it only works after toggling it off and on again.Root causes
1. A scroll bar's page size and range are set in separate calls.
SetRangeβSetSBarRangesends onlySIF_RANGE(scroller.cpp:135).SIF_PAGE) is set only inSetPageSize, which runs onWM_SIZEorSetUnits(scroller.cpp:62).SetScrollInfocall, using whatever page and range it has at that moment. Because the two are set separately, each call can toggle the bar using half-updated state.TCoolEdit::AdjustScrollercallsSetUnitsand thenSetRange, so it toggles twice per update.2. Nested
WM_SIZEmessages are thrown away.WM_SIZEimmediately, while still insideSetScrollInfo. That is exactly when the page size needs recalculating.staticguards discard it instead:TWindow::EvSizehasstatic bool inScroller(window.cpp:1641).TCoolTextWnd::AdjustScrollerhasstatic bool in_work, whose comment says it exists to block theEvSizetriggered by its ownSetRange.XPage/YPage, the editor's cached screen lines and characters, and the clamping inScrollToall keep the client size from before the bar changed.+2; // kompensate bug in TScrollerhack makes up for exactly this.WM_SIZEthat isn't nested.static, so they are shared by every window and editor instance, not just one. A child window's legitimateWM_SIZEcan be dropped too.3. Smaller issues
SetSBarRangeskips the update when only the range is unchanged. It never checks the page.EvSizeupdates page and range but never callsScrollToto re-clamp the scroll position against the new page.EvSizecallsInherited::EvSizebefore resettingScreenLines/ScreenChars, so the base class works with stale values.HasHScrollBar/HasVScrollBarare read once from the window style when the scroller is constructed, so later style changes are missed.ShowScrollBarruns beforeSetRange. The followingSetScrollInfo(with the old page) can hide the bar again, which is probably why the "off/on twice" trick was needed.Suggested fix (in
TScroller, trunk)UpdateScrollBars()that works out the page from the current client size and sets range, page and position in a singleSetScrollInfo(SIF_RANGE|SIF_PAGE|SIF_POS)call per bar. HaveSetRange,SetUnits,SetPageSize,SetSBarRangeandEvSizeall go through it.staticguards. After updating, check whether the client size changed; if it did, run the update again until it stops changing (at most 2β3 rounds, since the two bars affect each other). Use a per-instance flag if re-entry still needs guarding.ScrollTo(XPos, YPos)after the page changes.TCoolEditworkarounds: the manualShowScrollBar,in_work, the+2, and the zero-range hiding. Windows already hides a bar when its page covers the whole range.Fixed in [r8969].
Related
Commit: [r8969]
Can [r8969] be merged to 7?
Related
Commit: [r8969]
@elotter wrote:
Have you tested it? I did some simple testing with OWLMaker after rebuilding it with this fix, and the scrollbars still behave kind of wonky.
Related
Commit: [r8969]
No I didn't because I need it in branch-7 for doing so.
Why? Just check out the trunk and you can build the library and all the examples in a few clicks in OWLMaker (or just open the new VS solution and build from there). Then you can explore latest changes easily.
I'd like to see what happens when I remove the workarounds in my version of TCoolEdit that will not compile with the trunk I fear.
Ah. That makes sense. Still, having a look at the developments on the trunk will do you good.
(Of course, I'm only trying to get you involved on the trunk, now that I've retired from trunk duty myself. We need more human resources to keep the AI agents in check. :-))
In collaboration with ChatGPT:
The vital issues to observe:
The key is to separate the visibility decision from the actual scrollbar updates. Don't let changes in scrollbar visibility trigger a chain of calculations based on successively changing client dimensions. Determine the correct layout first, then apply it.
Note: GetClientRect reports the current client dimensions, dependent on currently visible scrollbars, not the potential dimensions with both scrollbars absent.
Illustrative C++ logic
Assuming baseWidth and baseHeight represent the client dimensions with both scrollbars absent, and contentWidth, contentHeight, vBarWidth and hBarHeight are measured in the same units:
This illustrates the layout calculation, not a drop-in OWLNext fix. The reference dimensions and scrollbar metrics must be obtained correctly for the actual window, DPI and scrollbar configuration. The selected visibility state must then be applied, and the actual client dimensions verified before setting page sizes and ranges.
Last edit: Vidar Hasfjord 2026-10-15
Wow, Copilot says:
See documentation for SetScrollInfo.
There is even the SIF_DISABLENOSCROLL flag:
So indeed, it appears the algorithm can entirely be left to Windows, if the parameters of the scrollbars are simply set correctly and completely.
That wouldn't be particularly surprising, considering that scrolling is such a fundamental aspect of windows.
True. The question is whether Windows makes it easy or not to set the parameters to get the right behaviour. I suspect it may not. If the correct page size depends on the available client area after subtracting space for scrollbars, then you have a circular dependency that needs to be broken. The algorithm ChatGPT provided does just that. If however, you could set the page size based on the potential area available sans scrollbars, then it would be pretty easy, since you wouldn't have to correct for the space taken by the scrollbars themselves.
It would probably be easier to start from first principles than try to repair horrible code with reentrancy guards etc. and no clear underlying logic.
Claude Code:
I've now tested r8969 in OWLMaker, comparing it with the code before the fix (r8968).
Setup
scroller.cpp/scroller.hreverted to r8968, the only commit that touched them, so the fix is the only difference.TOWLMakerEdit):Results
What goes wrong without the fix:
With r8969, every step settles correctly straight away.
What this does not cover
I also found an unrelated TCoolEdit issue: after you delete a long line, the horizontal bar stays visible until the line count changes. The range only drops to 87 against a page of 85. That's TCoolEdit's range calculation, not TScroller.
Vidar, could you describe what looked wonky in your test? Was it flicker, the horizontal bar that doesn't go away, or something else, and with which steps? Then I can check it specifically.
On the design: r8969 already decides visibility from the client area without any of our bars, in the same way as the
ChooseLayoutapproach. It works out the bars needed step by step rather than evaluating all four combinations, which gives the same result. The re-entry guard and the repeat loop are only there as a safety net for nestedWM_SIZE.SIF_DISABLENOSCROLLwould remove the circular dependency, but bars would then always show (disabled when not needed), which changes how windows look. Without that flag, Windows already shows and hides the bars itself, but the page size still depends on whether the other bar is visible, so TScroller has to work out the layout anyway. I'm happy to look at a cleaner rewrite if we can name a case r8969 gets wrong.Erwin, I can merge r8969 to branch 7 so you can test it with your workarounds removed. Let me know if you'd like that.
(Testing done with the help of Claude Code.)
By the way, I have also not been able to reproduce the original reported issue: "To reproduce open a new edit window and type Enter until the cursor reaches the end of the window."
I tried it in OWLMaker 2026.2.22.8642 - the latest published version - and opening a new editor window and pressing the Enter key till it reaches the end of the window and beyond, results in the vertical scroll bar being displayed.
I can still reproduce this issue in 7.0.21 when I replace my version of TCoolTextWnd::AdjustScroller() by the one from the trunk.
But it is gone with [r8969], which I merged manually into my branches-7 working copy.
A few quick tests have shown me that [r8969] generally works quite well.
So far I noticed two minor issues:
1.) Entering new lines in an empty window causes the invisible vertical bar to show up briefly. Once it becomes permanently active, however, it will not flicker anymore.
I think that this can be avoided by testing the need of the vertical bar only if its close to getting visible.
2.) When the horizontal bar is activated (by entering characters) the window content is not scrolled up even if the new horizontal bar hides the cursor line.
I'm however not sure whether this should be handled by TScroller or by TCoolTextWnd.
Related
Commit: [r8969]
Claude Code:
Erwin, thanks for testing r8969 on branch 7. I've reproduced the flash and found the cause.
The flash
Windows clamps a scroll bar's stored page to the size of its range (
nPage <= nMax - nMin + 1). While the vertical bar was hidden with its real range, the page Windows stored was only just big enough to keep it hidden. Branch 7'sTCoolTextWnd::AdjustScrollercallsSetRangeon every edit, so each Enter grows the range by one line. That made the stored page small enough for Windows to show the bar, until r8969 re-applied the real page and hid it again. Setting range and page in either order flashes; only setting both in a single call avoids it.TScrollercan't do that through its virtualSetScrollRange/SetScrollPagesetters (whichTFlatScrolleroverrides) without an interface change.The fix, as you did in your branch, hides a bar that isn't needed with an empty range (
0..0). Whether a bar is needed is decided from the scroller'sXRange/YRange, so the range stored in the bar no longer matters. Trunk's TCoolEdit never showed the flash because it only touches the scroller once the text is taller than the window.Results (test programs on a hidden desktop that log every scroll bar call)
In the CoolEdit test, the bar is shown and hidden at exactly the same line counts as with r8969, just without the flashes in between.
TCoolGrid
TCoolGrid::TGridScrollerhad its ownSetSBarRangeoverride: an old copy of the base function that set only the range, capped atSHRT_MAX. It conflicts with the empty-range approach, and grids were already showing unneeded bars, flashing and going stale before r8969. Because the grid scrolls in pixels, the cap also stopped the thumb from reaching the end of grids taller or wider than 32,767 px (about 2,000 rows). I've removed the override. Branch 7 has the same one (TCoolGridScroller::SetSBarRange), so it must be removed in the same merge.Your second point, the content not scrolling to keep the cursor visible when the horizontal bar appears: TScroller doesn't know where the caret is, so I think this belongs in the editor. It should scroll to the caret when a scroll bar changes the client size, for example in
EvSize. I'll look at that separately.The fix is committed as [r9088] in trunk. Erwin, if you merge it together with r8969 (and remove the grid override), please check whether the flash is gone in your editor too.
(Investigation and testing done with the help of Claude Code.)
Related
Commit: [r9088]
Claude Code:
The fix is now in branch 7 as [r9089], merged manually from [r8969] and [r9088] in trunk.
It is binary compatible: no headers have changed, and no members or virtual functions have been added or removed. To keep it that way, the implementation differs from trunk:
TScroller::SetSBarRange, not in a new private member function.thread_local.TCoolGridScroller::SetSBarRangeis kept, but now just calls the base implementation (in trunk, it was removed).Tests (test programs on a hidden desktop that log every scroll bar call)
Both test programs derive from DLL classes (
TCoolEditFile,TCoolGrid), so this checks class layout and vtables as well. The exported symbols of the owl and coolprj DLLs are unchanged.Erwin, since you merged r8969 into your branch 7 working copy by hand, please revert that and update to [r9089] instead. The r8969 header change isn't needed in branch 7. You can then remove your scroll bar workarounds, as in your earlier test. Please let me know whether the flash is gone in your editor too.
Your other point, keeping the cursor visible when the horizontal bar appears, is still open. I think it belongs in the editor (
EvSize), not in TScroller.(Investigation and testing done with the help of Claude Code.)
Related
Commit: [r8969]
Commit: [r9088]
Commit: [r9089]
On behalf of @jogybl, Claude wrote:
I've retested the wonkiness now.
"Sticky" bars test
This wonky behaviour may be more of an issue to do with content extent calculation and update notifications, rather than faulty logic in the scroll bars.
Bar visibility test
This seems to be beautifully handled now, except that the vertical bar triggers a little early, i.e. with the content having a line to spare from the bottom edge.
However:
Again, this may have more to do with content extent calculation and update.
Last edit: Vidar Hasfjord 2026-10-18