A spreadsheet demo:
Code: Select all
'
' "$Id$"
'
' Simple example of an interactive spreadsheet using Fl_Table.
' Uses Mr. Satan's technique of instancing an Fl_Input around.
' Modified to test Jean-Marc's mods for keyboard nav and mouse selection.
'
' Fl_Table[1.00/LGPL] 04/18/03 Mister Satan -- Initial implementation, submitted to erco for Fl_Table
' Fl_Table[1.10/LGPL] 05/17/03 Greg Ercolano -- Small mods to follow changes to Fl_Table
' Fl_Table[1.20/LGPL] 02/22/04 Jean-Marc Lienher -- Keyboard nav and mouse selection
' Fl_Table[1.21/LGPL] 02/22/04 Greg Ercolano -- Small reformatting mods, comments
' FLTK[1.3.0/LGPL] 10/26/10 Greg Ercolano -- Moved from Fl_Table to FLTK 1.3.x, CMP compliance
'
' Copyright 1998-2010 by Bill Spitzak and others.
'
' This library is free software. Distribution and use rights are outlined in
' the file "COPYING" which should have been included with this file. If this
' file is missing or damaged, see the license at:
'
' http:'www.fltk.org/COPYING.php
'
' Please report all bugs and problems on the following page:
'
' http:'www.fltk.org/str.php
'
'#include <stdio.h>
'#include <stdlib.h>
#include once "FLTK/Fl.bi"
#include once "FLTK/Fl_Double_Window.bi"
#include once "FLTK/Fl_Table.bi"
#include once "FLTK/Fl_Int_Input.bi"
#include once "FLTK/Fl_Value_Slider.bi"
#include once "FLTK/fl_draw.bi"
#include once "FLTK/Fl_Menu_Bar.bi"
const MAX_COLS = 26
const MAX_ROWS = 500
type Spreadsheet extends Fl_Table
private:
declare constructor (byref w as const Spreadsheet)
declare operator let (byref w as const Spreadsheet)
input_ as Fl_Int_Input ptr ' single instance of Fl_Int_Input widget
values(MAX_ROWS-1,MAX_COLS-1) as long ' array of data for cells
as long row_edit, col_edit ' row/col being modified
as long s_left, s_top, s_right, s_bottom ' kb nav + mouse selection
protected:
declare sub draw_cell(context as TableContext, a as long=0, b as long=0, c as long=0, d as long=0, e as long=0, f as long=0)
declare sub event_callback2() ' table's event callback (instance)
declare static sub event_callback(a as Fl_Widget ptr, v as any ptr)
declare static sub input_cb(a as Fl_Widget ptr, v as any ptr)
public:
declare constructor(X as long, Y as long, W as long, H as long, L as const zstring ptr=0)
declare destructor
' Apply value from input widget to values[row][col] array and hide (done editing)
declare sub set_value_hide()
' Change number of rows
declare sub rows(val_ as long)
' Change number of columns
declare sub cols(val_ as long)
' Get number of rows
declare function rows() as long
' Get number of columns
declare function cols() as long
' Start editing a new cell: move the Fl_Int_Input widget to specified row/column
' Preload the widget with the cell's current value,
' and make the widget 'appear' at the cell's location.
'
declare sub start_editing(R as long, C as long)
' Tell the input widget it's done editing, and to 'hide'
declare sub done_editing()
' Return the sum of all rows in this column
declare function sum_rows(C as long) as long
' Return the sum of all cols in this row
declare function sum_cols(R as long) as long
' Return the sum of all cells in table
declare function sum_all() as long
end type
private sub Spreadsheet.event_callback(a as Fl_Widget ptr, v as any ptr) ' table's event callback (static)
cast(Spreadsheet ptr,v)->event_callback2()
end sub
private sub Spreadsheet.input_cb(a as Fl_Widget ptr, v as any ptr) ' input widget's callback
cast(Spreadsheet ptr,v)->set_value_hide()
end sub
private constructor Spreadsheet(X as long, Y as long, W as long, H as long, L as const zstring ptr)
base(X,Y,W,H,L)
callback(@event_callback, cast(any ptr,@this))
when(FL_WHEN_NOT_CHANGED or when())
' Create input widget that we'll use whenever user clicks on a cell
input_ = new Fl_Int_Input(W/2,H/2,0,0)
input_->hide()
input_->callback(@input_cb, cast(any ptr,@this))
input_->when(FL_WHEN_ENTER_KEY_ALWAYS) ' callback triggered when user hits Enter
input_->maximum_size(5)
row_edit = 0: col_edit = 0
s_left = 0: s_top = 0: s_right = 0: s_bottom = 0
for c as integer = 0 to MAX_COLS - 1
for r as integer = 0 to MAX_ROWS - 1
values(r,c) = (r + 2) * (c + 3) ' initialize cells
next
next
end_()
end constructor
private destructor Spreadsheet
end destructor
private sub Spreadsheet.set_value_hide()
values(row_edit,col_edit) = val(*input_->value())
input_->hide()
window()->cursor(FL_CURSOR_DEFAULT) ' XXX: if we don't do this, cursor can disappear!
end sub
private sub Spreadsheet.rows(val_ as long)
base.rows(val_)
end sub
private sub Spreadsheet.cols(val_ as long)
base.cols(val_)
end sub
private function Spreadsheet.rows() as long
return base.rows()
end function
private function Spreadsheet.cols() as long
return base.cols()
end function
private sub Spreadsheet.start_editing(R as long, C as long)
row_edit = R ' Now editing this row/col
col_edit = C
dim as long X,Y,W,H
find_cell(CONTEXT_CELL, R,C, X,Y,W,H) ' Find X/Y/W/H of cell
input_->resize(X,Y,W,H) ' Move Fl_Input widget there
dim s as zstring *30: s=str (values(R,C)) ' Load input widget with cell's current value
input_->value(s)
input_->position(0,len(s)) ' Select entire input field
input_->show() ' Show the input widget, now that we've positioned it
input_->take_focus()
end sub
private sub Spreadsheet.done_editing()
if input_->visible() then ' input widget visible, ie. edit in progress?
set_value_hide() ' Transfer its current contents to cell and hide
end if
end sub
private function Spreadsheet.sum_rows(C as long) as long
dim sum as long = 0
for r as integer=0 to rows()-2 ' -1: don't include cell data in 'totals' column
sum += values(r,C)
next
return(sum)
end function
private function Spreadsheet.sum_cols(R as long) as long
dim sum as long = 0
for c as integer=0 to cols()-2 ' -1: don't include cell data in 'totals' column
sum += values(R,c)
next
return(sum)
end function
private function Spreadsheet.sum_all() as long
dim sum as long = 0
for c as integer=0 to cols()-2 ' -1: don't include cell data in 'totals' column
for r as integer=0 to rows()-2 ' -1: ''
sum += values(r,c)
next
next
return(sum)
end function
' Handle drawing all cells in table
sub Spreadsheet.draw_cell(context as TableContext, R as long, C as long, X as long, Y as long, W as long, H as long)
static s as zstring * 30
select case context
case CONTEXT_STARTPAGE: ' table about to redraw
' Get kb nav + mouse 'selection region' for use below
get_selection(s_top, s_left, s_bottom, s_right)
case CONTEXT_COL_HEADER: ' table wants us to draw a column heading (C is column)
fl_font(FL_HELVETICA or FL_BOLD, 14) ' set font for heading to bold
fl_push_clip(X,Y,W,H) ' clip region for text
fl_draw_box(FL_THIN_UP_BOX, X,Y,W,H, col_header_color())
fl_color(FL_BLACK)
if C = cols()-1 then
fl_draw("TOTAL", X,Y,W,H, FL_ALIGN_CENTER)
else
' Not last column? show column letter
s=chr(asc("A")+C) 'sprintf(s, "%c", 'A' + C)
fl_draw(s, X,Y,W,H, FL_ALIGN_CENTER)
end if
fl_pop_clip()
return
case CONTEXT_ROW_HEADER: ' table wants us to draw a row heading (R is row)
fl_font(FL_HELVETICA or FL_BOLD, 14) ' set font for row heading to bold
fl_push_clip(X,Y,W,H)
fl_draw_box(FL_THIN_UP_BOX, X,Y,W,H, row_header_color())
fl_color(FL_BLACK)
if R = rows()-1 then
fl_draw("TOTAL", X,Y,W,H, FL_ALIGN_CENTER)
else
s=str(R+1) 'sprintf(s, "%d", R+1);
fl_draw(s, X,Y,W,H, FL_ALIGN_CENTER)
end if
fl_pop_clip()
return
case CONTEXT_CELL: ' table wants us to draw a cell
if R = row_edit andalso C = col_edit andalso input_->visible() then
return ' dont draw for cell with input widget over it
end if
' Background
' Keyboard nav and mouse selection highlighting
if R >= s_top andalso R <= s_bottom andalso C >= s_left andalso C <= s_right then
fl_draw_box(FL_THIN_UP_BOX, X,Y,W,H, FL_YELLOW)
elseif C < cols()-1 andalso R < rows()-1 then
fl_draw_box(FL_THIN_UP_BOX, X,Y,W,H, FL_WHITE)
else
fl_draw_box(FL_THIN_UP_BOX, X,Y,W,H, &Hbbddbb00) ' money green
end if
' Text
fl_push_clip(X+3, Y+3, W-6, H-6)
fl_color(FL_BLACK)
if C = cols()-1 orelse R = rows()-1 then
fl_font(FL_HELVETICA or FL_BOLD, 14) ' ..in bold font
if C = cols()-1 andalso R = rows()-1 then
s=str(sum_all())' sprintf(s, "%d", sum_all());
elseif C = cols()-1 then
s=str(sum_cols(R))' sprintf(s, "%d", sum_cols(R));
elseif R = rows()-1 then
s=str(sum_rows(C))' sprintf(s, "%d", sum_rows(C));
end if
fl_draw(s, X+3,Y+3,W-6,H-6, FL_ALIGN_RIGHT)
else
fl_font(FL_HELVETICA, 14) ' ..in regular font
s=str(values(R,C))' sprintf(s, "%d", values[R][C])
fl_draw(s, X+3,Y+3,W-6,H-6, FL_ALIGN_RIGHT)
end if
fl_pop_clip()
return
case CONTEXT_RC_RESIZE: ' table resizing rows or columns
if not input_->visible() then return
find_cell(CONTEXT_TABLE, row_edit, col_edit, X, Y, W, H)
if X=input_->x() andalso Y=input_->y() andalso W=input_->w() andalso H=input_->h() then return
input_->resize(X,Y,W,H)
return
case else:
return
end select
end sub
' Callback whenever someone clicks on different parts of the table
sub Spreadsheet.event_callback2()
dim R as long = callback_row()
dim C as long = callback_col()
dim context as TableContext = callback_context()
select case context
case CONTEXT_CELL: ' A table event occurred on a cell
select case Fl.event() ' see what FLTK event caused it
case FL_PUSH ' mouse click?
done_editing() ' finish editing previous
if R<> rows()-1 andalso C <> cols()-1 then start_editing(R,C) ' start new edit
return
case FL_KEYBOARD: ' key press in table?
if Fl.event_key() = _FL_Escape then end ' ESC closes app
if C = cols()-1 orelse R = rows()-1 then return ' no editing of totals column
done_editing() ' finish any previous editing
set_selection(R, C, R, C) ' select the current cell
start_editing(R,C) ' start new edit
if Fl.event() = FL_KEYBOARD andalso Fl.e_text[0] <> !"\r" then
input_->handle(Fl.event()) ' pass keypress to input widget
end if
return
end select
return
case CONTEXT_TABLE, CONTEXT_ROW_HEADER, CONTEXT_COL_HEADER ' A table event occurred on dead zone in table
done_editing() ' done editing, hide
return
case else
return
end select
end sub
' Change number of columns
sub setcols_cb cdecl(w as Fl_Widget ptr, v as any ptr)
dim table as Spreadsheet ptr = cast(Spreadsheet ptr,v)
dim in as Fl_Valuator ptr = cast(Fl_Valuator ptr, w)
dim cols as long = int(in->value()) + 1
table->cols(cols)
table->redraw()
end sub
' Change number of rows
sub setrows_cb cdecl(w as Fl_Widget ptr, v as any ptr)
dim table as Spreadsheet ptr = cast(Spreadsheet ptr,v)
dim in as Fl_Valuator ptr = cast(Fl_Valuator ptr, w)
dim rows as long = int(in->value()) + 1
table->rows(rows)
table->redraw()
end sub
Fl.option(Fl.OPTION_ARROW_FOCUS, 1) ' we want arrow keys to navigate table's widgets
dim win as Fl_Double_Window ptr = new Fl_Double_Window(922, 382, "Fl_Table Spreadsheet with Keyboard Navigation")
dim table as Spreadsheet ptr = new Spreadsheet(20, 20, win->w()-80, win->h()-80)
' Table rows
table->row_header(1)
table->row_header_width(70)
table->row_resize(1)
table->rows(11)
table->row_height_all(25)
' Table cols
table->col_header(1)
table->col_header_height(25)
table->col_resize(1)
table->cols(11)
table->col_width_all(70)
table->set_selection(0,0,0,0) ' select top/left cell
' Add children to window
win->begin()
' Row slider
dim setrows as Fl_Value_Slider=Fl_Value_Slider(win->w()-40,20,20,win->h()-80, 0)
setrows.type_(FL_VERT_NICE_SLIDER_)
setrows.bounds(2,MAX_ROWS)
setrows.step_(1)
setrows.value(table->rows()-1)
setrows.callback(@setrows_cb, cast(any ptr, table))
setrows.when(FL_WHEN_CHANGED)
setrows.clear_visible_focus()
' Column slider
dim setcols as Fl_Value_Slider=Fl_Value_Slider(20,win->h()-40,win->w()-80,20, 0)
setcols.type_(FL_HOR_NICE_SLIDER_)
setcols.bounds(2,MAX_COLS)
setcols.step_(1)
setcols.value(table->cols()-1)
setcols.callback(@setcols_cb, cast(any ptr, table))
setcols.when(FL_WHEN_CHANGED)
setcols.clear_visible_focus()
win->end_()
win->resizable(table)
win->show()
Fl.run_()
'
' End of "$Id$".
'