This document attempts to summarize some of the issues surrounding scrolling and GTK+. There are a wide variety of current widgets that can be scrolled. GtkXmHTML GtkText GtkCList GtkViewport GtkCanvas GtkCaanvas GtkLayout There are a number of different ways that these widgets treat their scrollbars - GtkText and GtkLayout are controlled by external scrollbars. - GtkViewport interacts with the ScrolledWindow widget to provide scrollbars - GtkCList manages its own scrollbars. Proposed heirarchy: GtkViewport GtkLayout GtkCList GtkCanvas GtkXmHTML [ GtkSimpleScroller ] GtkText GtkCaanvas The idea is that all of these classes should be modified so that in every case, adding scrollbars consists of simply putting the widget into a GtkScrolledWindow (perhaps that would be better called something else like GtkScroller) The common interface for something that could be put into a GtkScrolledWindow cannot be implemented as a common base interface, because the GtkText must inherit from GtkEditable. Instead, it is implemented as a couple of signals, which every scrollble object implements. void set_adjustments:: (GtkAdjustment *hadj, GtkAdjustment *vadj); void hint_request:: (GtkScrollHints *xhints, GtkScrollHints *yhints) The set_adjustments gives the scrollable widget the adjustments that control scrolling, the hint_request() operation asks the scrollable widget to tell the ScrolledWindow something about itself: typedef enum { GTK_SCROLL_HINT_FIXED = 1<<0, GTK_SCROLL_HINT_MONOTONIC = 1<<1, GTK_SCROLL_HINT_GUESS = 1<<2 } GtkScrollHints; FIXED means the given dimension will not change when the allocation of the scrollable widget changes; MONOTONIC means that if both dimensions of the allocation increase, this dimension of the scrolled area will not increases, and if both dimensions decrease, this dimension of the scrolled area will not decrease. GUESS means that the scrolled window should try to use some (as yet not detailed) heuristics to figure out if the scrollbars are going to change when the allocation changes. Automatic scrollbars ==================== Thie reason for the hints is to handle automatic scrollbars properly.Some of the widgets (GtkCList, GtkC[a]anvas) are relatively simple in this regard - the size of the scrolled area does not depend on the scrollbars. But a Text or HTML widget is more complicated. The most general algorithm is as follows: (this goes into the size_allocate call for GtkScrolledWindow) - this is what the scrolled window will do when no hints are set. - Ask the Scrollable for its full size, when put into the window without scrollbars. - If this is bigger than the horizontal and/or vertical dimensions of the window, add the scrollbars, and ask the scrollable for the size with now smaller window. - If the new size results in needing more scrollbars, add them (But don't take any away). This is the 100% correct solution, but has some problems. The main one is that when a window with scrollbars is resized, it gets allocated at least twice. If the size allocation involves laying out a complex document, this can be a big performance hit. The ScrolledWindow currently uses a different algorithm: Start with the current set of scrollbars: - Ask the scrollable for its size, with those scrollbars. - From that information, add or remove scrollbars as necessary. - Repeat until no scrollbars are added or removed - _but_ if after the first step, we add one scrollbar and remove the other, add them both back, and quit. This algorithm shows hysteresis in sizing. At some sizes, a window will fit without scrollbars, but with scrollbars will need scrollbars. Since we start with the current set of scrollbars, which alternative is picked could depend on the history. The new algorithm for the ScrolledWindow will be something like: - ScrolledWindow gets size allocated: - Set up scrollbars as best we can: - If either dimension has HINT_FIXED, and we already know that dimension, calculate it's scrollbar. - If both dimensions gets smaller, and either dimension has HINT_MONOTONIC, and already has a scrollbar, use a scrollbar for that dimension. - HINT_GUESS: Use current sizes to guess if we will need scrollbars at the new size. This is important to avoid double rewraps for very long text widgets, for example. - Remove all other scrollbars. - Sends size_allocate() to child with new area. - Check resulting page sizes. If needs scrollbars, add them. If we used HINT_GUESS, and a dimension no longer needs a scrollbar, then remove it. - If scrollbars changed: - Sends size_allocate() to child with new area. - Check resulting page sizes. If needs scrollbars, add them. (We never remove scrollbars at this stage) Communication: ============= The ScrolledWindow communicates with its child by: a) Calling size_allocate() b) (Via the ScrollBars) setting the value of the adjustments and emitting value_changed c) Emitting set_adjustments:: d) Emitting request_hints:: The child communicates with the ScrolledWindow by: - When the child changes the amount of size it wants (perhaps because one dimension is fixed to the size of the parent) It calls gtk_widget_queue_resize() on itself, as always. - When the child changes the size of its scrolled area, it changes the upper/lower of the adjustments and emits "changed". (Implementation problem: We want to queue the recheck of the scrollbars here, but we do _not_ want to cause the scrollbars to be redrawn when they stay the same, if we can avoid it. Do we use a separate idle? Also, when we do show/hide the scrollbars, we want to avoid having a resize queued on us, since that will cause a resize (harmless) and a double flash (not harmless) - this may require hacking hide/show manually) Mechanics of scrolling ====================== Scrolling a Viewport is straightforward - you just move the X window around. There is, however a major limitation - coordinates for X windows are signed shorts, so the "Viewport" model falls short. Widgets that want to allow arbitrary-sized scrolling need to translate coordinates themselves. When the window is scrolled, the contents and/or subwindows of the widget need to be moved relative to the windows coordinate system. If the widget has no embedded subwindows, then one can just use XCopyArea to move the data on the screen around. (GtkText does this). However, if you do this, and then manually move subwindows, there will be a lag, and the subwindows may leave a trail of background color behind. One way to fix this up is to essentially disable the exposes, then do them yourself. (This is done by XmHTML). This is, however, quite complicated in the presence of embedded subwindows. A better method (taken from the Netscape sources) is to use the X window window and bit gravity options for windows to move the gravity and subwindows together as a consequence of moving the window. See: http://www.gtk.org/~otaylor/whitepapers/guffaw-scrolling.txt The idea is to have all this handled automatically by GtkLayout or GtkSimpleScroller widgets. (The GtkSimpleScroller would use XCopyArea - it could be implemented as just an option for GtkLayout). Relation to window updating =========================== Any sort of scrolling produces large numbers of expose events. Also, importantly, these expose events need to be kept in sync with the scrolling. It does not make sense to continue scrolling by one pixel increments if the expose events cannot be processed that quickly. Backwards compatibility: ======================= ScrolledWindow: gtk_scrolled_window_new() creates a ScrolledWindow with a special backwards-compatibilty flag set. This has two effects: - A viewport is automatically added to it. - The scrolled_window does the current gtk_widget_new (GTK_TYPE_VIEWPORT, ...) and gtk_scrolled_window_new_empty() create an empty viewport. CList/CTree/GnomeIconList: Real backwards compatibility is not possible, since gtk_clist_new() needs to return both a CList and something that can be added to its parent. However, if we really want to hack backwards compatibility, we could keep the current scrollbar code in CList, and use it only if the parent is not a ScrolledWindow.