There is a bug regarding the initial scrollbar position when switching from one book (or double page?) to another.
I already tracked down the issue to these lines:
main.py:_draw_image → main.py:scroll_to_predefined → main.py:update_viewport_position.MainWindow.update_viewport_position, there is a call self._hadjust.set_value(viewport_position[0]).viewport_position[0] is correct and less than self._hadjust.get_upper() so there should be no problem.self._hadjust.get_value() is different from viewport_position[0]. According to my (very few) tests, the difference matches exactly the sum of the widths of all widgets to the left and the right of the main area. Thus, for example, if the thumbnailer has a width of 100 and the vertical scrollbar of the main area has a width of 12, self._hadjust.get_value() will report a value that is 112 less than desired. The position of what is displayed in the main area to the user seems to be off, indicating that this bad value has been applied as such.self._hadjust.get_value() will return the correct (desired) value, despite none of the other values changing.This behavior seems to masquerade as normal in western mode, probably because the desired initial value for the horizontal scrollbar in this case is 0 so scrolling is trivial.
This bug might be related to GTK 3. I tried to fix this by moving the line self._scroll[0].queue_resize_no_redraw() just before self._hadjust.set_value(viewport_position[0]) but to no avail.
Any ideas?
Feature Requests: #123
Feature Requests: #124
Feature Requests: #134
Diff:
I managed to produce a simple test case. You can use the attached file and the following (sketchy) shell script:
Then,
doublepage2). Note how the first (white) page is not centered. (You may pressCursorRightto confirm.)PageDownor similar). Note how the third (white) page is centered.stderroutput. When you do step 5, the wrong value is applied, but in step 6, the correct value is applied.Apparently, all pages can be of the same size, and the issue is observable when switching directories but not when switching pages within a directory.
Is there anyone else who can reproduce this issue?
I am not exactly knowledgable when it comes to GTK 3, so I am not sure whether any of the following applies at all:
Having read that, I wonder whether we need a
ScrolledWindow:It reads: Widgets with native scroll support include Gtk.TreeView, GtkTextView, and Gtk.Layout. In some way, we do use a
Layoutbut I am not sure about how to connect everything. Guessing around will probably take forever, so I probably need a helping hand here.Hmm. OK, first principles: I think I need more information about your exact setup, because there are a lot of moving parts here and they're all interrelated. I wasn't able to reproduce your exact issue, at least not so far. (Working from the original description; I haven't looked at your followup repro yet.)
But I did encounter enough weirdness that makes me doubt this is a GTK bug, and feel it's very likely a bug in the MComix scroll/layout implementation. (Which is basically all custom — the event handling for the scroll, drag, and keypress events is custom, the scroll positioning is custom — hence the
_hadjustand_vadjust— the layout of the images in the viewport is custom.) So, given that it's all a custom job, sort of by definition there can't be a Gtk bug... MComix is using Gtk in not-strictly-supported ways, so it gets to own all the bugs in how it does so.Only if we could prove that the behavior we're seeing is also exhibited by Gtk even when it's used in the standard/recommended fashion, could it then be bumped up to the Gtk devs as a bug on their end. (Which they'd be unlikely to fix anyway, because Gtk3 is a development cul-de-sac and has no resources devoted to maintaining it anymore. Unless the bug's also present in Gtk4, the response would be, "Upgrade and it'll go away." Which is... not unfair, from the perspective of their need to prioritize finite resources.)
Anyway, here are my attempts and what I experienced. But first, all the settings that might be relevant here. For my initial attempt, my Preferences were set to:
Now, I don't have any manga, don't expect to read any manga, and have probably never enabled manga mode before today. So I just loaded a traditional western comic archive from a directory full of them and switched on Manga mode for it, because it's not like MComix can tell the difference. Two-Page mode was already enabled so I left it enabled. Zoom I set to Fit-to-size, as that left me with page images big enough to exceed the available viewport even when MComix is maximized. The thumbnails are visible, as they always are for me — I keep them open and just let MComix auto-hide them while fullscreened, if I want undistracted reading. I didn't enter fullscreen mode for any of this testing, only maximized the window (which kept the thumbnails visible).
Attempt 1
First thing I realized, very quickly, was that I was going to have to turn off "Show only one page..." mode to even attempt repro'ing your issue. So I set that to Never.
Attempt 2
Second thing I realized was that "Smart scroll" would also have to go. With Smart Scroll enabled, the scroll positioning always appeared to be... well, not "correct" but "intentional", except HOLY WOW this cannot be how it's supposed to work!!??
So, just to scroll through a SINGLE page, with "smart" scroll enabled, I would have to make at least SIXTEEN right-to-left passes, while bumping incrementally downwards by a tiny bit on each pass. After I'd completed my 16–20 horizontal sweeps, I'd finally reach the bottom left edge of the right page, and be whisked off to the top-right edge of the left page. Lather, rinse, repeat. This is the least-smart "smart" scrolling I have ever encountered. Maybe tweaking some of those distance numbers in Preferences->Behavior->Scroll would make it suck less, but I didn't feel like wasting time experimenting. So, I turned that off too.
Attempt 3
So, now we're cooking. Scrolling vertically scrolls the page vertically, PgDn / PgUp move to the top-right or bottom-left of the next/previous dual-page spread, respectively, as does overscrolling while at the top or bottom edge of a page-spread. When viewing the last two-page spread of a book, overscrolling past the bottom advances to the next archive in the directory. All is right with the MComix world.
In fact, a little too right. Because I mostly seem to be getting positioned correctly with each page switch. Mostly.
The first little bit of weirdness I notice is that, whenever I scroll across file boundaries, as I start scrolling down the first spread of the newly-loaded file the horizontal scrollbar jump just sliiiightly to the left. Not even remotely to the same degree you reported, but there's unmistakably this tiny jump away from the right edge of the page. Happens every time, between every pair of files, and only on the first page(s) of a new file. Seems to be slightly more exaggerated when I have the window maximized, less so when it's smaller. So the distance seems to have something to do with the viewport dimensions relative to the image dimensions.
It could be that you're experiencing a much more exaggerated version of that same phenomenon. I can think of plenty of possible reasons why it might be worse for you, though they're all only guesses:
1988×3056-pixel page images in the files I'm testing with. (I don't have disparate-page-sized archives handy, so I haven't introduced that variable yet.)That last one bears some further scrutiny, too: How are you navigating, in MComix? Are you using a touchscreen, a trackpad, a mouse? If you're using a trackpad, does it use two-finger drags to scroll, and does it have a kinetic scrolling feature? A trackpad sending high-resolution scroll gestures with kinetic acceleration would produce exponentially more scroll events than my mouse wheel is even capable of generating, so it's possible that the MComix scroll event handler is being overwhelmed and mishandling scroll inputs far more severely on your device than on mine.
One other observation I made in my testing, that has me leaning towards that theory: I've noticed that, when I scroll back across archives (scroll upwards from the first page of one file, to switch to the previous file in the directory), very often I don't end up at the bottom edge of the last page (spread) of the previous archive. Quite frequently, but not entirely consistently/predictably, I'll warp off of the first page(s) of one archive, and end up getting delivered four, six, eight pages before the last pair, in the previous archive.
What appears to be happening is that, while the new (previous) archive is being loaded and there's no content in the MComix window, any leftover scroll inputs queued up — or any I actively make during the transition — are processed while the previous archive is in the process of being loaded. Because there are no pages displayed in the window, the scroll distance to switch pages is effectively 0 pixels, so my scroll-up events quickly jump backwards through the file until they land on a page spread that can be loaded and displayed, which then absorbs the scroll events. That, too, could be related to the issue you're seeing. But I need to bang on the scroll event handler code some more, before I can confirm/understand what's going on there.
Last edit: FeRD 2023-05-06
The answer to that is probably, "Maybe but...".
Yes, the GNOME HIG-conforming way to do this stuff would be to build the interface in Gtk4, with a
GtkScrolledWindowas its primary display surface and event controllers and gestures handling the input events, in place of the current custom input event processing. (Direct access to input events is deprecated in Gtk3 and no longer available in Gtk4, so the current custom event handler code is unusable for Gtk4.)But building things that way would likely require sacrificing a fair number of MComix features that
GtkScrolledWindowdoesn't support. Even theGtkScrolledWindowdocs themselves are quick to say:(And the strong subtext there is, "Don't come to us with feature requests to support your weird stuff natively in
GtkScrolledWindow. We're not gonna add it, just build your own.)In order to have an interface that supports a sufficient number of the navigation features its custom event-handling code currently provides, MComix would almost certainly find itself in that exact boat pretty quickly. The only difference is, it would need custom Event Controller and
GtkScrollableinterface implementations, to replace the current custom event handler and image-painting code.Last edit: FeRD 2023-05-06
Thanks for butting in. This is the correct thread. :-)
When I wrote that "This bug might be related to GTK 3", I merely wanted to express the idea that, in some ways, the bug might have something to do with GTK 3 or how MComix interacts with GTK 3, and that the bug is probably not due to faulty layout calculations on our side alone.
That is probably correct.
Which is good because this is how you read a manga. The way you have to interact with a touch device to accomplish this, however, is certainly an issue. MComix should become more touch device friendly. This is not related to the bug we are discussing here, though.
We certainly need different units for this feature, since absolute pixel distances are difficult to use here. However, this is an unrelated issue.
I think this is what I wanted you to observe. See below for more on it.
I use the space bar most of the time. Combined with "Stretch small images" enabled, I mostly only need "Fit to size" (
Skey) for most pages and "Fit to both" (Bkey) for the rest. (You can tweak some details in the preferences.) This is three keys on a keyboard doing wonders (with the occasionalshift+space barin case I need to go back).Your explanation for this behavior seems to match mine here.
Maybe, but I can trigger this behavior using a single key press (
PgUporPgDown). If fixing one of the issues happens to implicitly also fix the other, I would not be too surprised, though.That being said, I just found an easier way to demonstrate what the issue at hand looks like. Read on in the next post!
First of all, what would the ideal way look like, given an imaginary Ideal Toolkit™?
Let's say no book is opened, so no widgets except menu bar and status bar. Now, we open a book. First, we add the thumbnailer and make it visible. It might start adding and displaying thumbnails by itself asynchronously, but it must start with no selected thumbnail because what we see in the main area, which is empty right now, does not match any of the thumbnails. Concurrently, the first image is loaded sufficiently enough by now to know its dimensions (including rotation etc.), so we can do layout calculations. First, we freeze the viewport so it will not change its content while we are busy calculating. We start with no scroll bars, so we turn them off (but this is not visible because the entire viewport is frozen) so we know how big the viewport is at most. Then, we might need to zoom into or out of the image, depending on settings and viewport space available. We might conclude that we need to add a scroll bar, so we ask Ideal Toolkit™ to do just that (invisibly to the user, of course). As a result, we might have less actual viewport space available, so we need to redo our calculations. Again, we might conclude that we need to add yet another scroll bar, and so on, so we re-iterate until we have added all the scroll bars that are both available and needed. Now, all calculations have converged to their respective final values, so we can put the results to good use. We inform Ideal Toolkit™ about the dimensions of the scrollable area and where images should appear (and how big they are). Then, we scroll to the correct corner (e.g. top right when in manga mode), mark the appropriate thumbnail in the thumbnailer as selected, and thaw up everything. Later, we repaint the content of the viewport as soon as a new image is fully loaded and prepared for display.
Kind of like this. Now, back to reality.
I am not sure what is going on here, but my attempt to understand this mess is that the most interesting function is
mcomix.MainWindow._draw_imagewhich is queued to be called somehow from outside using someGObject.idle_addmagic implemented indraw_image(note the absence of a leading_). If this really happens as asynchronously as I think it does,_waiting_for_redrawshould be turned into something that guarantees atomic operations, I guess. Or maybe we do not redraw often enough? This is so confusing! Anyway, back to_draw_image. Somewhere in there, where all the layout computations are performed,_show_scrollbarsis called in the hopes of forcing GTK 3 to recalculate the dimensions of the viewport synchronously so the calculations can go on with the new values immediately.And this is where, I guess, we run into this issue. Not quite sure, but my mediocre understanding of all this now tells me that it does not happen synchronously so we need more of this
GObject.idle_addmagic despite it looking wasteful, and it would kind of counteract our attempt to not queue up endless redrawing requests by using atomic(?) operations. If it does not end up in a deadlock or something, that is.As I said earlier, there is, apparently, a much simpler way to demonstrate what the issue looks like. One important observation I made: Adding or removing widgets affects the main viewport differently, depending on where the widgets are located. Adding widgets close to the origin (i.e. top-left corner), like menu bar, thumbnailer and toolbar, forces the main viewport to be moved away from the origin, while adding widgets far away from the origin (i.e. lower-right corner), like scroll bars and status bar, appear to be drawn "on top" of the main viewport.
With this in mind, let's go! Add as many widgets to the main window as possible. Open a single image and make sure that the image is way bigger than the entire window, and that both scroll bars are needed and visible. Scroll to the lower-right corner of the image. Observe that the scroll bars do reflect the current position correctly. Now, press
Ito hide all widgets. The lower-right corner of the image should still match the lower-right corner of the window. Now, pressIagain. Observe that the scroll bars indicate that we should still be at the lower-right corner, however, the image in the viewport appears to have moved! Now, try to drag the viewport content around by just a tiny bit. Note how the scroll bars immediately readjust. Redo everything for the next part of the experiment, but instead of dragging the viewport content, now try to drag one of the scroll bars a little bit. Note how now it is the viewport content that jumps around to match the scroll bar. Also, the scroll bar behaves strangely until you drag the viewport content around.If you measure by how far everything appears to be off or jumps around, you will probably end up with the same observation as described above.
By the way, this demonstration works independent of manga mode being enabled or disabled.
The issue we see in this little experiment seems to have a strong connection with the bug this ticket is about.
With all that, now, my explanation for what the user can observe looks like this: When switching from one book to another, the GUI is actually redrawn in between when neither the old nor the new book is opened, and since this is just empty space, there is no scroll bars or anything. So, the calculations for the first page of the new book opened are all done with scroll bars disabled. GTK 3 later happily draws the mismatched-looking scroll bars on top of what should be image content, as described above. However, when switching to the next page (using
PgDown) which just happens to have similar dimensions like the one we were looking at until a moment ago, the scroll bars are already there. Thus, the calculations will end up with the conclusion that scroll bars are needed (of course, because the image is of the same size as the one earlier), and since they are already there, at least as far as GTK 3 is concerned, they do not need to be redrawn "on top" but can instead be made to reflect the actual value. Also, our layout calculations happen to see the "correct" viewport dimensions on the first iteration. "Correct" as in "actually final, but we could not actually conclude on our own that they will be the correct ones". This might explain why we get to see the values we see in the debug output (see the patches attached in an earlier post).Sorry, I hope my previous post is not too convoluted. Now that I wrote about it, I can succinctly express what I think is the underlying issue here:
Whenever we come to the conclusion that the current layout might be incorrect, we need to redo layout calculations, with GTK 3 immediately telling us the things we need and adjusting scrollbars as we need, and all of this needs to happen atomically.
However, since it seems to not yet be the case, we run into this kind of bug.
Now that I think about it, maybe we can work around this issue by adding a new feature. In this case, it would be a new preference so the user can set the width of the viewport scrollbars. With that in place, we do not need to wait for the toolkit to lay out the widgets so we can re-read the viewport's dimensions, but instead, we can calculate everything at once.
… Which might not actually solve the main issue here. I am not sure. GTK 3 is confusing to me. Help?
I just glued some snippets together to reduce the code needed to observe the issue. See attached file. The working directory needs to be the directory where the script is located.
@ferd617 Please have a look at the attached example. It would be great if you (or anyone else) could fix that bug. I already tried some things, as noted in the code, but to no avail.
Good news! By (almost) pure chance, I stumbled across https://docs.gtk.org/glib/const.PRIORITY_HIGH_IDLE.html where the description just happens to hint at the solution: By changing line 87 to use
priority=GLib.PRIORITY_LOW, some revalidation seems to take place before the actual drawing function is being called again. It still needs some fine-tuning, though, since the content is visibly being moved into place now, but at least it looks like I can finally solve this issue!