ede/efiler/EDE_Browser.cpp
2007-08-17 11:15:19 +00:00

582 lines
15 KiB
C++

/*
* $Id$
*
* EDE Browser class
* Part of edelib.
* Copyright (c) 2005-2007 EDE Authors.
*
* This program is licenced under terms of the
* GNU General Public Licence version 2 or newer.
* See COPYING for details.
*/
#include "EDE_Browser.h"
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <Fl/Fl.H>
#include <Fl/Fl_Window.H>
// ---------------- SORT ----------------
// convert size string to float - used for sorting files by size
double sizetof(char *s) {
double d=atof(s);
if (strchr(s,'g') || strchr(s,'G'))
d*=1024*1024*1024;
else if (strchr(s,'m') || strchr(s,'M'))
d*=1024*1024;
else if (strchr(s,'k') || strchr(s,'K'))
d*=1024;
return d;
}
// Sort function - return true if first is larger then second, otherwise false
bool EDE_Browser::sortfn(char*s1,char*s2,SortType type) {
switch(type) {
case ALPHA_SORT:
return (strcmp(s1,s2)>0);
case ALPHA_CASE_SORT:
return (strcasecmp(s1,s2)>0);
case NUMERIC_SORT:
return (atof(s1)>atof(s2));
case DATE_SORT:
// unimplemented
// TODO: use edelib::Date
return false;
case FILE_SIZE_SORT:
return (sizetof(s1)>sizetof(s2));
default:
return false; // NO_SORT
}
}
// Optimized quick sort algorithm
void EDE_Browser::mqsort(char* arr[], int beg, int end, SortType type) {
bool k = sort_direction ? false : true;
if (end > beg + 1)
{
char* piv = arr[beg]; int l = beg + 1, r = end;
while (l < r)
{
if (k^sortfn(arr[l],piv,type)) // ^ is XOR
l++;
else if (l==r-1) // avoid costly swap if they're the same
r--;
else {
swap(--r,l); // Fl_Browser::swap()
char *tmp=arr[l]; // update array
arr[l]=arr[r];
arr[r]=tmp;
}
}
// avoid costly swap if they're the same
if (beg==l-1)
l--;
else {
swap(--l,beg);
char*tmp=arr[beg]; // update array
arr[beg]=arr[l];
arr[l]=tmp;
}
// recursion
mqsort(arr, beg, l, type);
mqsort(arr, r, end, type);
}
}
// callback for header buttons
void header_callback(Fl_Widget*w, void*) {
Fl_Group* heading = (Fl_Group*)w->parent();
EDE_Browser* browser = (EDE_Browser*)w->parent()->parent();
for (int i=heading->children(); i--; )
if (w == heading->child(i)) { browser->sort(i); break; }
}
// Toggle-type method
void EDE_Browser::sort(int column) {
SortType t = column_sort_types_[column];
if (t==NO_SORT) t=ALPHA_SORT;
if (column!=sort_column || sort_type==NO_SORT)
sort(column, t, false);
else
sort(column, sort_type, !sort_direction);
}
// Real sorting method with three parameters
#define SYMLEN 6
void EDE_Browser::sort(int column, SortType type, bool reverse) {
char *h=column_header_;
int hlen=strlen(h);
char colchar = Fl_Icon_Browser::column_char();
// FIXME sort() shouldn't call column_header() because that calls show_header() and that
// deletes all buttons from header (so they could be recreated). This cause valgrind errors
// since sort is called in button callback - you can't delete a widget in its own callback
// Remove old sort direction symbol (if any) from header
char *delim = 0;
int col=0;
if (sort_type != NO_SORT) {
bool found=false;
while ((delim=strchr(h, colchar))) {
if (col==sort_column) {
for (uint i=0; i<=strlen(delim); i++) delim[i-SYMLEN+1]=delim[i];
found=true;
break;
}
h=delim+1;
col++;
}
if (!found && col==sort_column)
column_header_[hlen-SYMLEN+1]='\0';
h=column_header_;
delim = 0;
col=0;
}
// Add new symbol
char *newheader = new char[hlen+6];
if (type != NO_SORT) {
// Construct symbol
char sym[SYMLEN];
if (reverse) snprintf(sym,SYMLEN,"@-22<");
else snprintf(sym,SYMLEN,"@-22>");
// Find column
bool found=false;
while ((delim=strchr(h, colchar))) {
if (col==column) {
*delim='\0';
snprintf(newheader,hlen+SYMLEN,"%s%s\t%s",column_header_,sym,delim+1);
found=true;
break;
}
h=delim+1;
col++;
}
if (!found && col==column) // Just append symbol to string
snprintf(newheader, hlen+SYMLEN,"%s%s",column_header_,sym);
} else {
strncpy (newheader, column_header_, hlen);
newheader[hlen]='\0';
}
column_header(newheader);
delete[] newheader;
sort_column=column; sort_type=type; sort_direction=reverse;
// Start actually sorting
if (type != NO_SORT) {
// First create an array of strings in a given column
char** sorttext = (char**)malloc(sizeof(char*)*(size()+1)); // index starts from 1 for simplicity in mqsort
for (int i=1;i<=size();i++) {
char *tmp = strdup(text(i));
char *l = tmp;
int col=0;
while ((delim=strchr(l, Fl_Icon_Browser::column_char()))) {
*delim = '\0';
if (col==column) break;
l=delim+1;
col++;
}
sorttext[i] = strdup(l);
free(tmp);
}
mqsort(sorttext, 1, size()+1, type);
redraw();
// Free the allocated memory
for (int i=1; i<=size(); i++) free (sorttext[i]);
free(sorttext);
}
}
// --------------------------------------
static void scroll_cb(Fl_Widget* w, void*) {
Fl_Scrollbar *s = (Fl_Scrollbar*)w;
EDE_Browser *b = (EDE_Browser*)w->parent();
b->hposition(s->value());
}
// ctor
EDE_Browser::EDE_Browser(int X,int Y,int W,int H,const char *L) : Fl_Icon_Browser(X,Y,W,H),
totalwidth_(0), column_header_(0), sort_column(0), sort_type(NO_SORT), sort_direction(false) {
thegroup = new Fl_Group(X,Y,W,H);
thegroup->begin();
heading = new Heading(0,0,W,buttonheight);
heading->box(FL_FLAT_BOX); // draw heading background
heading->align(FL_ALIGN_CLIP);
heading->end();
heading->hide();
heading->parent(this); // for callback
hscrollbar = new Fl_Scrollbar(1, H-Fl::scrollbar_size(), W-Fl::scrollbar_size()-3, Fl::scrollbar_size()); // take account for edges
hscrollbar->type(FL_HORIZONTAL);
hscrollbar->hide();
hscrollbar->parent(this); // for callback
hscrollbar->callback(scroll_cb);
thegroup->end();
thegroup->add(this);
has_scrollbar(VERTICAL);
resizable(0);
thegroup->resizable(this);
thegroup->align(FL_ALIGN_CLIP);
// EDE_Browser is always a multiple-selection browser
type(FL_MULTI_BROWSER);
column_sort_types_ = new SortType[256]; // 256 columns should be enough for anyone (tm)
for (int i=0; i<256; i++) column_sort_types_[i]=NO_SORT;
}
// Deallocate all memory used by header labels
void EDE_Browser::cleanup_header() {
// Deallocate old button labels
for (int i=0; i<heading->children(); i++) {
char *l = (char*)heading->child(i)->label();
if (l && l[0]!='\0') free(l);
}
heading->clear();
}
//make buttons invisible
void EDE_Browser::hide_header() {
if (heading->visible()) resize(x(),y()-buttonheight,w(),h()+buttonheight);
cleanup_header();
heading->hide();
}
// Regenerate and display header
void EDE_Browser::show_header() {
int button_x=0;
char *hdr = column_header_;
const int* l = Fl_Icon_Browser::column_widths();
cleanup_header();
for (int i=0; i==0||l[i-1]; i++) {
// If the button is last, calculate size
int button_w = l[i];
if (button_w == 0) button_w = totalwidth_-button_x;
// Get part of header until delimiter char
char *delim = 0;
Fl_Button *b;
if (hdr) {
delim = strchr(hdr, Fl_Icon_Browser::column_char());
if (delim) *delim='\0'; // temporarily
b=new Fl_Button(button_x,heading->y(),button_w,buttonheight,strdup(hdr));
} else {
b=new Fl_Button(button_x,heading->y(),button_w,buttonheight,"");
}
b->align(FL_ALIGN_INSIDE|FL_ALIGN_LEFT|FL_ALIGN_CLIP);
b->callback(header_callback);
b->labelsize(12); // FIXME: hack for label size
//b->labelcolor(FL_DARK3);
heading->add(b);
button_x += l[i];
if (delim) {
*delim=Fl_Icon_Browser::column_char(); // put back delimiter
hdr=delim+1; // next field
}
}
if (!heading->visible()) resize(x(),y()+buttonheight,w(),h()-buttonheight);
heading->resizable(0); // We will resize the heading and individual buttons manually
heading->show();
heading->redraw(); //in case it was already visible
}
// Subclassed to resize heading and browser if neccessary
void EDE_Browser::column_widths(const int* l) {
totalwidth_=0;
int i;
for (i=0; l[i]; i++) totalwidth_+=l[i];
// if (total>=scroll->w()) {
// Fl_Icon_Browser::size(total,h());
// }
// If there was heading before, regenerate
if (heading->visible())
heading->size(totalwidth_,buttonheight);
// show_header();
// Second array for the Fl_Browser
static int *tmp = 0;
if (tmp) delete[] tmp;
tmp=new int[i]; // FIXME: Dtor should cleanup this memory
for (int j=0; j<i-1; j++) tmp[j]=l[j];
tmp[i-1]=0;
Fl_Icon_Browser::column_widths(tmp);
// delete[] tmp; -- can't do this, browser goes berserk
// Redraw parent so we don't get ugly artifacts after shrinking last button
// Doesn't work anymore!
// parent()->redraw();
}
const int* EDE_Browser::column_widths() const {
int i,total=0;
const int *l=Fl_Icon_Browser::column_widths();
for (i=0; l[i]; i++) total+=l[i];
static int *tmp = 0;
if (tmp) delete[] tmp;
tmp=new int[i+2]; // FIXME: Someone should cleanup this memory sometimes...
for (int j=0; l[j]; j++) tmp[j]=l[j];
tmp[i]=(totalwidth_-total);
tmp[i+1]=0;
return tmp;
}
// Subclassed handle() for keyboard searching
int EDE_Browser::handle(int e) {
if (e==FL_FOCUS) { fprintf(stderr, "EB::focus\n"); }
if (e==FL_KEYBOARD && Fl::event_state()==0) {
// when user presses a key, jump to row starting with that character
int k=Fl::event_key();
if ((k>='a'&&k<='z') || (k>='A'&&k<='Z') || (k>='0'&&k<='9')) {
if (k>='A'&&k<='Z') k+=('a'-'A');
int ku = k - ('a'-'A'); //upper case
int p=lineno(selection());
for (int i=1; i<=size(); i++) {
int mi = (i+p-1)%size() + 1; // search from currently selected one
if (text(mi)[0]==k || text(mi)[0]==ku) {
// select(line,0) just moves focus to line without selecting
// if line was already selected, it won't be anymore
select(mi,selected(mi));
middleline(mi);
//break;
return 1; // menu will get triggered on key press :(
}
}
}
// Attempt to fix erratic behavior on enter key
// Fl_Browser seems to do the following on enter key:
// - when item is both selected and focused, callback isn't called at all (even FL_WHEN_ENTER_KEY_ALWAYS)
// - when no item is selected, callback is called 2 times on focused item
// - when one item is selected and other is focused, callback is first called on selected then on
// focused item, then the focused becomes selected
// This partial fix at least ensures that callback is always called. Callback function should
// deal with being called many times repeatedly.
if ((when() & FL_WHEN_ENTER_KEY_ALWAYS) && k == FL_Enter) {
// if (changed()!=0) {
//fprintf(stderr,"do_callback()\n");
do_callback();
// }
}
if (k == FL_Tab) {
fprintf (stderr, "TAB\n");
// Fl_Icon_Browser::handle(FL_UNFOCUS); return 1;
}
}
return Fl_Icon_Browser::handle(e);
}
// Overload resize for show/hide horizontal scrollbar
void EDE_Browser::resize(int X, int Y, int W, int H) {
int fsbs = Fl::scrollbar_size();
if (W >= totalwidth_) {
// hide scrollbar
if (hscrollbar->visible())
hscrollbar->hide();
H += fsbs;
} else {
// show scrollbar
hscrollbar->value(hscrollbar->value(), W, 0, totalwidth_);
if (!hscrollbar->visible()) {
hscrollbar->resize(X+1, Y+H-fsbs, W-fsbs-3, fsbs);
hscrollbar->show();
H -= fsbs;
} else {
hscrollbar->resize(X+1, Y+H, W-fsbs-3, fsbs);
hscrollbar->redraw();
}
}
heading->resize(X, Y-buttonheight, W, buttonheight);
// else
// heading->position(X, Y);
Fl_Icon_Browser::resize(X,Y,W,H);
}
// *****************
// class Heading - implementation
// *****************
// The following code is modified from Fl_Tile.cxx and extensively commented, as I was trying
// to figure it out ;)
static void set_cursor(Fl_Group*t, Fl_Cursor c) {
static Fl_Cursor cursor;
if (cursor == c || !t->window()) return;
cursor = c;
#ifdef __sgi
t->window()->cursor(c,FL_RED,FL_WHITE);
#else
t->window()->cursor(c);
#endif
}
static Fl_Cursor cursors[4] = {
FL_CURSOR_DEFAULT,
FL_CURSOR_WE,
FL_CURSOR_NS,
FL_CURSOR_MOVE
};
int EDE_Browser::Heading::handle(int event) {
static int sdrag; // Type of drag
static int sdx; // Event distance from the widget boundary
static int sx; // Original event x
#define DRAGH 1
#define DRAGV 2
// width of grabbing area in pixels:
#define GRABAREA 4
// Event coordinates
int evx = Fl::event_x();
int evy = Fl::event_y();
if (event==FL_FOCUS) return 0; // this is a focusless widget!
// if (event==FL_FOCUS && Fl::event_key()==FL_Tab) {
// parent()->take_focus(); // heading shouldn't take focus
// return 1;
// }
switch (event) {
case FL_MOVE:
case FL_ENTER:
case FL_PUSH: {
int mindx = 100;
// int mindy = 100;
// int oldx = 0;
// int oldy = 0;
Fl_Widget*const* a = array();
// Is there a button boundary within GRABAREA ?
for (int i=0; i<children(); i++) {
Fl_Widget* o = *a++;
if (o == resizable()) continue; // resizable has a special meaning in Fl_Tile, but we don't use it in Heading
if (o->y()<=evy+GRABAREA && o->y()+o->h()>=evy-GRABAREA) {
int t = evx - (o->x()+o->w());
if (abs(t) < mindx) {
sdx = t;
mindx = abs(t);
}
}
}
sdrag = 0; sx = 0;
if (mindx <= GRABAREA) {sdrag = DRAGH; sx = evx;}
set_cursor(this, cursors[sdrag]);
if (sdrag) return 1;
return Fl_Group::handle(event);
}
case FL_LEAVE:
set_cursor(this, FL_CURSOR_DEFAULT);
break;
case FL_DRAG:
// This is necessary if CONSOLIDATE_MOTION in Fl_x.cxx is turned off:
// if (damage()) return 1; // don't fall behind
case FL_RELEASE: {
if (!sdrag) return 0; // should not happen
Fl_Widget* r = resizable(); if (!r) r = this;
// Calculate where the new boundary (newx) should be
int newx;
if (sdrag&DRAGH) {
newx = Fl::event_x()-sdx;
if (newx < r->x()) newx = r->x();
else if (newx > r->x()+r->w()) newx = r->x()+r->w();
}
// Mouse movement distance (dx)
int dx = Fl::event_x()-sx;
Fl_Widget*const* a = array();
// Here we check if any widget will get size 0
// because column size 0 is illegal in Browser
for (int i=children(); i--; ) {
Fl_Widget* o = a[i];
int end = o->x()+o->w(); // End coord. of widget
if ((end == newx-dx || newx<1) && o->w()+dx < 1) {
set_changed();
do_callback();
set_cursor(this, FL_CURSOR_DEFAULT);
return 1;
}
}
if (newx>=w()) newx+=dx; // last button is resized by dragging the edge
// Go again through list of widgets and resize everything
int *columns = new int[children()+1];
int j=0;
for (int i=children(); i--; ) {
Fl_Widget* o = *a++;
int end = o->x()+o->w(); // End coord. of widget
if (end == newx-dx) {
// Resize left widget
o->damage_resize( o->x(), o->y(), o->w()+dx, o->h());
} else if (end > newx-dx) {
// Resize all remaining widgets to the right
o->damage_resize(o->x()+dx, o->y(), o->w(), o->h());
}
// Push new width into the columns array
columns[j++]=o->w();
}
columns[j]=0;
// This is the EDE_Browser method. It will also resize the heading and
// the browser if neccessary
EDE_Browser*b = (EDE_Browser*)parent();
b->column_widths(columns);
b->redraw(); // OPTIMIZE (some smart damage in column_widths() ?)
delete[] columns;
// There will be many RELEASE events, so we update sx (used when calculating dx)
sx=Fl::event_x();
if (event == FL_DRAG) set_changed();
do_callback();
return 1;
}
}
return Fl_Group::handle(event);
}