# # $Id: scrollbar.icn,v 1.4 2004/11/11 19:53:42 rparlett Exp $ # # This file is in the public domain. # # Author: Robert Parlett (parlett@dial.pipex.com) # package gui link graphics $include "guih.icn" $define MIN_BAR_SIZE 8 # # Component representing the bar area # class BarArea : Component( bar_x, # bar_y, # bar_w, # bar_h # ) method display(buffer_flag) EraseRectangle(self.cbwin, self.x, self.y, self.w, self.h) DrawRaisedRectangle(self.cbwin, self.bar_x, self.bar_y, self.bar_w, self.bar_h) self.do_shading(self.cbwin) if /buffer_flag then CopyArea(self.cbwin, self.cwin, self.x, self.y, self.w, self.h, self.x, self.y) end end # # This class provides horizontal and vertical scroll bars. # # There are two ways to use a scroll bar. The first way is to # set a total_size (represented by the whole bar), a page_size # (represented by the draggable button) and an increment_size # (being the amount added/subtracted when the top/bottom # button is pressed). The value will then range from zero to # (total_size - page_size) inclusive. An initial value must # be set with the {set_value()} method. # @example # @ vb := ScrollBar() # @ vb.set_pos("85%", "25%") # @ vb.set_size(20, "40%") # @ vb.set_total_size(130) # @ vb.set_page_size(30) # @ vb.set_increment_size(1) # @ vb.set_value(0) # @ self.add(vb) # # Alternatively, a scroll bar can be used as a slider which # ranges over a given range of values. In this case, the # range is set with {set_range()}. It is still necessary to set # the increment size and the initial value, as above, but # page_size and total_size should not be set. # # Real numbers as opposed to integers can be used for the # range settings if desired. # @example # @ vb := ScrollBar() # @ vb.set_pos("85%", "25%") # @ vb.set_size(20, "40%") # @ vb.set_range(2, 25) # @ vb.set_value(10) # @ vb.set_increment_size(1) # @ self.add(vb) # # A SCROLLBAR_PRESSED_EVENT occurs whenever the buttons are pressed, # or after a drag, and a SCROLLBAR_DRAGGED_EVENT occurs during the drag. The # value can then be retrieved by get_value(). The fact that # there are two different event types can be used to reduce the # number of events which are processed by the user's program - just # listen for SCROLLBAR_PRESSED_EVENT. # class ScrollBar : Component( value, # page_size, # increment_size, # total_size, # hi, # lo, # bar_down, # is_paging, # repeat_delay, # bar_down_offset, # bar_area, # b1, # b2, # is_horizontal_flag, # bar_pos, # Orientation independent bar pos bar_size, # bar_area_pos, # Orientation independent bararea pos bar_area_size, # is_range_flag # ) # # Make the scroll bar horizontal (default is vertical). # method set_is_horizontal() return self.is_horizontal_flag := 1 end # # Make the scroll bar vertical (the default). # method clear_is_horizontal() return self.is_horizontal_flag := &null end # # Set the total size which the scroll bar area represents. # @param x The total size # method set_total_size(x) self.total_size := x self.reconfigure() end # # Return the total size. # method get_total_size() return self.total_size end # # Set the size which the bar in the scroll bar area represents. # @param x The size. # method set_page_size(x) self.page_size := x self.reconfigure() end # # Get the page size. # method get_page_size() return self.page_size end # # Get the value. # @return The value # method get_value() return self.value end # # Set the value representing the top of the bar in the scroll # bar. The value is forced into range if it is not in range already. # @param x The value. # method set_value(x) if /self.x then self.value := x else { self.move_value(x) self.set_pos_from_value() } end # # Set the amount to increase the value by when one of the # buttons is pressed. # @param x The increment size. # method set_increment_size(x) return self.increment_size := x end # # Set the range of the scroll bar. The values may # be integer or real. # @param lo The lower bound # @param hi The upper bound # method set_range(lo, hi) self.is_range_flag := 1 self.lo := lo self.hi := hi self.reconfigure() end method move_bar_pos(x) self.bar_pos := x self.bar_pos <:= self.bar_area_pos self.bar_pos >:= self.bar_area_pos + self.bar_area_size - self.bar_size if /self.is_horizontal_flag then self.bar_area.bar_y := self.bar_pos else self.bar_area.bar_x := self.bar_pos self.bar_area.invalidate() end method move_value(x) self.value := x self.value <:= self.lo self.value >:= self.hi end method set_pos_from_value() if self.hi ~= self.lo then self.move_bar_pos(self.bar_area_pos + integer(((self.get_value() - self.lo) * (self.bar_area_size - self.bar_size)) / (self.hi - self.lo))) else self.move_bar_pos(self.bar_area_pos) end method set_value_from_pos() if self.bar_area_size ~= self.bar_size then self.move_value(self.lo + ((self.hi - self.lo) * (self.bar_pos - self.bar_area_pos)) / (self.bar_area_size - self.bar_size)) else self.move_value(self.lo) end method handle_press(e) if (self.bar_area.bar_x <= &x < self.bar_area.bar_x + self.bar_area.bar_w) & (self.bar_area.bar_y <= &y < self.bar_area.bar_y + self.bar_area.bar_h) then { # # Click on bar; set flag and save offset between top of bar and pointer position # self.bar_down := 1 if /self.is_horizontal_flag then self.bar_down_offset := &y - self.bar_area.bar_y else self.bar_down_offset := &x - self.bar_area.bar_x fire(SCROLLBAR_PRESSED_EVENT, e) } else if (/self.is_horizontal_flag & (self.bar_area.x <= &x < self.bar_area.x + self.bar_area.w) & (self.bar_area.y <= &y < self.bar_area.bar_y)) | ((self.bar_area.y <= &y < self.bar_area.y + self.bar_area.h) & (self.bar_area.x <= &x < self.bar_area.bar_x)) then { self.move_value(self.get_value() - self.page_size) self.set_pos_from_value() start_paging(1) fire(SCROLLBAR_PRESSED_EVENT, e) } else if (/self.is_horizontal_flag & (self.bar_area.x <= &x < self.bar_area.x + self.bar_area.w) & ( self.bar_area.bar_y + self.bar_area.bar_h <= &y < self.bar_area.y + self.bar_area.h)) | ((self.bar_area.y <= &y < self.bar_area.y + self.bar_area.h) & ( self.bar_area.bar_x + self.bar_area.bar_w <= &x < self.bar_area.x + self.bar_area.w)) then { self.move_value(self.get_value() + self.page_size) self.set_pos_from_value() start_paging(2) fire(SCROLLBAR_PRESSED_EVENT, e) } end method handle_release(e) if \self.bar_down then { # # Released; clear flag # self.bar_down := &null fire(SCROLLBAR_PRESSED_EVENT, e) } else if \self.is_paging then stop_paging() end method tick() if dispatcher.curr_time_of_day() > self.repeat_delay then { if self.is_paging === 1 then self.move_value(self.get_value() - self.page_size) else self.move_value(self.get_value() + self.page_size) self.set_pos_from_value() fire(SCROLLBAR_PRESSED_EVENT) } end method start_paging(n) self.is_paging := n self.repeat_delay := dispatcher.curr_time_of_day() + self.parent_dialog.repeat_delay set_ticker(self.parent_dialog.repeat_rate) end method stop_paging() self.is_paging := &null stop_ticker() end method handle_button_up(ev) # # Button up clicked # self.move_value(self.get_value() - self.increment_size) self.set_pos_from_value() fire(SCROLLBAR_PRESSED_EVENT, ev) end method handle_button_down(ev) # # Button down clicked # self.move_value(self.get_value() + self.increment_size) self.set_pos_from_value() fire(SCROLLBAR_PRESSED_EVENT, ev) end method handle_drag(e) # # Bar dragged; compute new position # if /self.is_horizontal_flag then self.move_bar_pos(&y - self.bar_down_offset) else self.move_bar_pos(&x - self.bar_down_offset) self.set_value_from_pos() fire(SCROLLBAR_DRAGGED_EVENT, e) end method handle_event(e) b1.handle_event(e) b2.handle_event(e) if e === (&lpress | &rpress | &mpress) then handle_press(e) else if e === (&lrelease | &rrelease | &mrelease) then handle_release(e) else if \self.bar_down & (e === (&ldrag | &rdrag | &mdrag)) then handle_drag(e) end method display(buffer_flag) W := if /buffer_flag then self.cwin else self.cbwin EraseRectangle(W, self.x, self.y, self.w, self.h) # # Draw rectangle around area within which bar moves # if /self.is_horizontal_flag then DrawRaisedRectangle(W, self.x, self.y + self.w, self.w, self.h - 2 * self.w) else DrawRaisedRectangle(W, self.x + self.h, self.y, self.w - 2 * self.h, self.h) # # Display two buttons # b1.display(buffer_flag) b2.display(buffer_flag) bar_area.display(buffer_flag) self.do_shading(W) end method reconfigure() # Don't do anything if we haven't called resized yet. if /self.x then return if /self.is_range_flag then { # # Check total size and page size # if /self.total_size then fatal("total size not set") if /self.page_size then fatal("page size not set") if self.page_size <= 0 then fatal("invalid page size") } else { if \self.lo >= \self.hi then fatal("invalid range") } # # Check increment size, value # if /self.increment_size then fatal("increment size not set") if /self.value then fatal("value not set") if /self.is_range_flag then { # # Not a range; compute lo, hi and bar_size. # self.lo := 0 if self.total_size > self.page_size then { self.hi := self.total_size - self.page_size self.bar_size := integer((self.bar_area_size * self.page_size) / self.total_size) } else { # # Total <= page; these settings produce an immovable full size bar. # self.hi := 0 self.bar_size := self.bar_area_size } } else { # # Range; set bar size proportional to button size, but leave room if bar_area_size is small. # self.bar_size := (b1.w_spec * 3) / 2 self.bar_size >:= self.bar_area_size - 8 } # # Ensure bar size in range not less than MIN_BAR_SIZE, but must be within # bar_area_size. # self.bar_size <:= MIN_BAR_SIZE self.bar_size >:= self.bar_area_size if \self.is_range_flag then { # # For a slider, we still need the page size for clicks in the bar. # self.page_size := ((self.hi - self.lo) * self.bar_size) / (0 ~= self.bar_area_size) | 0 } # # Set bar height/width according to orientation # if /self.is_horizontal_flag then self.bar_area.bar_h := self.bar_size else self.bar_area.bar_w := self.bar_size self.move_value(self.value) self.set_pos_from_value() end method resize() compute_absolutes() if /self.is_horizontal_flag then { # # Compute bar area dimensions # bar_area.set_pos(BORDER_WIDTH + 2, self.w + BORDER_WIDTH + 2) bar_area.set_size(self.w - 2 * (BORDER_WIDTH + 2), self.h - 2 * self.w - 2 * (BORDER_WIDTH + 2)) bar_area.resize() self.bar_area.bar_x := self.bar_area.x self.bar_area_pos := self.bar_area.y self.bar_area.bar_w := self.bar_area.w self.bar_area_size := self.bar_area.h # # Set button positions # b1.set_pos(0, 0) b1.set_size(self.w, self.w) b2.set_pos(0, self.h - self.w) b2.set_size(self.w, self.w) b1.set_img(img_style("arrow_up")) b2.set_img(img_style("arrow_down")) } else { bar_area.set_pos(self.h + BORDER_WIDTH + 2, BORDER_WIDTH + 2) bar_area.set_size(self.w - 2 * self. h - 2 * (BORDER_WIDTH + 2), self.h - 2 * (BORDER_WIDTH + 2)) bar_area.resize() self.bar_area_pos := self.bar_area.x self.bar_area.bar_y := self.bar_area.y self.bar_area_size := self.bar_area.w self.bar_area.bar_h := self.bar_area.h b1.set_pos(0, 0) b1.set_size(self.h, self.h) b2.set_pos(self.w - self.h, 0) b2.set_size(self.h, self.h) b1.set_img(img_style("arrow_left")) b2.set_img(img_style("arrow_right")) } b1.resize() b2.resize() reconfigure() end method set_one(attr, val) case attr of { "is_horizontal" : if test_flag(attr, val) then set_is_horizontal() else clear_is_horizontal() "total_size" : set_total_size(int_val(attr, val)) "page_size" : set_page_size(int_val(attr, val)) "increment_size" : set_increment_size(int_val(attr, val)) "value" : set_value(numeric_val(attr, val)) "range" : set_range!numeric_vals(attr, val, 2) default: self.Component.set_one(attr, val) } end initially(a[]) self.Component.initially() self.b1 := IconButton() self.b1.connect(self, "handle_button_up", BUTTON_PRESS_EVENT) self.b1.connect(self, "handle_button_up", BUTTON_HELD_EVENT) self.b1.toggle_draw_border() self.b1.clear_accepts_focus() add(self.b1) self.b2 := IconButton() self.b2.connect(self, "handle_button_down", BUTTON_PRESS_EVENT) self.b2.connect(self, "handle_button_down", BUTTON_HELD_EVENT) self.b2.toggle_draw_border() self.b2.clear_accepts_focus() add(self.b2) self.bar_area := BarArea() add(self.bar_area) set_fields(a) end