Split FileView properly into files. Slowly it is getting its final form.

This commit is contained in:
Vedran Ljubovic
2007-08-23 10:54:12 +00:00
parent 70ff40b29a
commit f56292b6cf
7 changed files with 1524 additions and 1231 deletions

View File

@ -0,0 +1,443 @@
/*
* $Id$
*
* EDE FileView 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_FileView.h"
#include <FL/Fl_Input.H>
#include <FL/Fl_Shared_Image.H>
#include <FL/Fl.H>
#include <edelib/Nls.h>
#include <edelib/IconTheme.h>
#include <edelib/String.h>
#include <edelib/Debug.h>
#include "EDE_Browser.h"
// debugging help
#define DBG "FileDetailsView: "
// --------------------------------------
//
// File renaming stuff
//
// --------------------------------------
// EditBox is displayed when renaming a file
// We subclass Fl_Input so we can handle keyboard
int FileDetailsView::EditBox::handle (int e) {
FileDetailsView* view = (FileDetailsView*)parent();
if (e==FL_KEYBOARD && visible()) {
int k = Fl::event_key();
if (Fl::event_key()==FL_Enter && visible()) {
view->hide_editbox();
view->finish_rename();
} else if (k==FL_Up || k==FL_Down || k==FL_Page_Up || k==FL_Page_Down) {
view->hide_editbox();
return 0; // let the view scroll
} else if (k==FL_F+2 || k==FL_Escape)
view->hide_editbox();
else
Fl_Input::handle(e);
return 1; // don't send keys to view
}
if (e==FL_MOUSEWHEEL && visible()) view->hide_editbox();
return Fl_Input::handle(e);
}
// show editbox at specified row and make the row "invisible" (bgcolor same as fgcolor)
void FileDetailsView::show_editbox(int row) {
if (!rename_callback_) return;
if (row<1 || row>size()) return; // nothing selected
if (text(row)[0]=='.' && text(row)[1]=='.' && text(row)[2]==column_char()) return; // can't rename "go up" button
// unselect the row with focus - it's prettier that way
select(row,0);
// Copy filename to editbox
char* filename = strdup(text(row));
char* tmp = strchr(filename, column_char());
if (tmp) *tmp='\0';
editbox_->value(filename);
bucket.add(filename);
// make the row "invisible"
editbox_row=row;
char* ntext = (char*)malloc(sizeof(char)*(strlen(text(row))+8)); // add 7 places for format chars
strncpy(ntext+7, text(row), strlen(text(row)));
strncpy(ntext, "@C255@.", 7);
ntext[strlen(text(row))+7]='\0';
text(row,ntext);
bucket.add(ntext);
// calculate location for editbox
// "Magic constants" here are just nice spacing
int X=x()+get_icon(row)->w()+8;
int W=150;
int H=get_icon(row)->h()+2;
int Y=y()+2 - position();
void* item = item_first();
for (int i=1; i<row; i++) {
Y+=item_height(item);
item=item_next(item);
}
editbox_->resize(X,Y,W,H);
editbox_->show();
editbox_->take_focus();
redraw();
editbox_->redraw();
}
void FileDetailsView::hide_editbox() {
EDEBUG(DBG "hide_editbox()\n");
editbox_->hide();
// Make the edited row visible again
char* ntext = (char*)malloc(sizeof(char)*(strlen(text(editbox_row))-6)); // remove 7 places for format chars
strncpy(ntext, text(editbox_row)+7, strlen(text(editbox_row))-7);
ntext[strlen(text(editbox_row))-7]='\0';
text(editbox_row,ntext);
bucket.add(ntext);
}
// This is called to actually rename file (when user presses Enter in editbox)
void FileDetailsView::finish_rename() {
EDEBUG(DBG "finish_rename()\n");
rename_callback_(editbox_->value());
}
// --------------------------------------
//
// FileDetailsView
//
// --------------------------------------
// ctor
FileDetailsView::FileDetailsView(int X, int Y, int W, int H, char*label) : EDE_Browser(X,Y,W,H,label) {
// browser = new EDE_Browser(X,Y,W,H,label);
// browser->end();
// end();
// resizable(browser->the_scroll());
// resizable(browser->the_group());
// resizable(browser);
// EDE_Browser properties
const int cw[]={250,200,70,130,100,0};
column_widths(cw);
column_char('\t');
column_header(_("Name\tType\tSize\tDate\tPermissions"));
const SortType st[]={ALPHA_CASE_SORT, ALPHA_CASE_SORT, FILE_SIZE_SORT, DATE_SORT, ALPHA_SORT, NO_SORT};
column_sort_types(st);
when(FL_WHEN_ENTER_KEY_ALWAYS);
//when(FL_WHEN_ENTER_KEY_CHANGED);
//first=0;
editbox_ = new EditBox(0, 0, 0, 0);
editbox_->box(FL_BORDER_BOX);
editbox_->parent(this);
editbox_->hide();
rename_callback_ = 0;
paste_callback_ = 0;
context_callback_ = 0;
}
// Methods for mainpulating items
// They are named similarly to browser methods, except that they take
// struct FileItem (defined in EDE_FileView.h)
void FileDetailsView::insert(int row, FileItem *item) {
// Construct browser line
edelib::String value;
value = item->name+"\t"+item->description+"\t"+item->size+"\t"+item->date+"\t"+item->permissions;
char* realpath = strdup(item->realpath.c_str());
EDE_Browser::insert(row, value.c_str(), realpath); // put realpath into data
bucket.add(realpath);
EDEBUG(DBG "value: %s\n", value.c_str());
// Get icon
edelib::String icon = edelib::IconTheme::get(item->icon.c_str(),edelib::ICON_SIZE_TINY);
if (icon=="") icon = edelib::IconTheme::get("misc",edelib::ICON_SIZE_TINY,edelib::ICON_CONTEXT_MIMETYPE);
set_icon(row, Fl_Shared_Image::get(icon.c_str()));
}
void FileDetailsView::update(FileItem *item) {
int row=findrow(item->realpath);
if (row==0) return;
//EDE_Browser::remove(row);
//insert(row, item);
// the above was reimplemented because a) it's unoptimized, b) adds everything at the end,
// c) causes browser to lose focus, making it impossible to click on something while
// directory is loading
edelib::String value;
value = item->name+"\t"+item->description+"\t"+item->size+"\t"+item->date+"\t"+item->permissions;
char* realpath = strdup(item->realpath.c_str());
text(row, value.c_str());
data(row, realpath);
bucket.add(realpath);
EDEBUG(DBG "value: %s\n", value.c_str());
// Get icon
edelib::String icon = edelib::IconTheme::get(item->icon.c_str(),edelib::ICON_SIZE_TINY);
if (icon=="") icon = edelib::IconTheme::get("misc",edelib::ICON_SIZE_TINY,edelib::ICON_CONTEXT_MIMETYPE);
set_icon(row, Fl_Shared_Image::get(icon.c_str()));
}
// This method is needed because update() uses path to find item in list
void FileDetailsView::update_path(const char* oldpath,const char* newpath) {
int row=findrow(oldpath);
if (row==0) return;
char* c = strdup(newpath);
data(row,c);
bucket.add(c);
}
// Make row look "disabled" (actually we use it for cut operation)
void FileDetailsView::gray(int row) {
if (text(row)[0] == '@' && text(row)[1] == 'C') return; // already greyed
char *ntext = (char*)malloc(sizeof(char)*(strlen(text(row))+7)); // add 6 places for format chars
strncpy(ntext+6, text(row), strlen(text(row)));
strncpy(ntext, "@C25@.", 6);
ntext[strlen(text(row))+6]='\0';
text(row,ntext);
bucket.add(ntext);
// grey icon - but how to ungray?
Fl_Image* im = get_icon(row)->copy();
im->inactive();
set_icon(row,im);
}
void FileDetailsView::ungray(int row) {
if (text(row)[0] != '@' || text(row)[1] != 'C') return; // not greyed
char *ntext = (char*)malloc(sizeof(char)*(strlen(text(row))-5)); // remove 6 places for format chars
strncpy(ntext, text(row)+6, strlen(text(row))-6);
ntext[strlen(text(row))-6]='\0';
text(row,ntext);
bucket.add(ntext);
// doesn't work
Fl_Image* im = get_icon(row)->copy();
//im->refresh();
//im->uncache(); // doesn't work
//im->color_average(FL_BACKGROUND_COLOR, 1.0);
//im->data(im->data(),im->count());
set_icon(row,im);
//redraw(); // OPTIMIZE
}
// Overloaded handle for: context menu, file renaming and dnd support
int FileDetailsView::handle(int e) {
//EDEBUG(DBG"Event: %d\n", e);
/* ------------------------------
Right-click event
--------------------------------- */
if (e==FL_RELEASE && Fl::event_button()==3) {
void* item = item_first();
int itemy=y()-position();
int i;
for (i=1; i<=size(); i++) {
itemy+=item_height(item);
if (itemy>Fl::event_y()) break;
}
set_focus(i);
Fl::event_is_click(0); // prevent doubleclicking with right button
if (context_callback_) context_callback_(this, data(i));
return 1;
}
/* ------------------------------
Rename support
--------------------------------- */
// If rename_callback_ isn't set, we don't support renaming
if (rename_callback_) {
// hide editbox when user clicks outside of it
if (e==FL_PUSH && editbox_->visible() && !Fl::event_inside(editbox_))
hide_editbox();
// hide editbox when scrolling mouse
if (e==FL_MOUSEWHEEL && editbox_->visible())
hide_editbox();
static bool renaming=false;
// this variable is used because we want to select item on FL_PUSH, but
// actually start editing on FL_RELEASE (otherwise it would be impossible
// to doubleclick)
if (e==FL_PUSH) renaming=false;
// Click once on item that is already selected AND focused to rename it
if (e==FL_PUSH && !editbox_->visible() && Fl::event_clicks()==0 && Fl::event_button()==1) {
// we're only interested in the first column
// columns 2-5 are "safe haven" where user can click without renaming anything
const int* l = column_widths();
if (Fl::event_x()<x() || Fl::event_x()>x()+l[0])
return EDE_Browser::handle(e);
// Is clicked item also focused?
void* item = item_first();
int focusy=y()-position();
for (int i=1; i<get_focus(); i++) {
focusy+=item_height(item);
if (focusy>Fl::event_y()) break;
item=item_next(item);
}
if (Fl::event_y()<focusy || Fl::event_y()>focusy+item_height(item))
// It isn't
return EDE_Browser::handle(e);
if (selected(get_focus())!=1)
// If it's focused but not selected, then this action is select
return EDE_Browser::handle(e);
renaming=true; // On next event, we will check if it's doubleclick
}
if (renaming && (e==FL_DRAG || e==FL_RELEASE || e==FL_MOVE) && Fl::event_is_click()==0) {
// Fl::event_is_click() will be >0 on doubleclick
show_editbox(get_focus());
renaming=false;
// don't pass mouse event, otherwise item will become reselected which is a bit ugly
return 1;
}
}
/* ------------------------------
Drag&drop support
--------------------------------- */
// If paste_callback_ isn't set, we can't support dnd
if (paste_callback_) {
// Let others know that we accept dnd
if (e==FL_DND_ENTER||e==FL_DND_DRAG) return 1;
// Scroll the view by dragging to border (only works on heading... :( )
if (e==FL_DND_LEAVE) {
if (Fl::event_y()<y())
position(position()-1);
if (Fl::event_y()>y()+h())
position(position()+1);
return 1;
}
// Don't unselect items on FL_PUSH cause that could be dragging
if (e==FL_PUSH && Fl::event_clicks()!=1) return 1;
static int dragx,dragy; // to check min drag distance
if (e==FL_DRAG) {
edelib::String selected_items;
for (int i=1; i<=size(); i++)
if (selected(i)==1) {
selected_items += "file://";
selected_items += (char*)data(i);
selected_items += "\r\n";
}
EDEBUG(DBG "DnD buffer: '%s'\n", selected_items.c_str());
Fl::copy(selected_items.c_str(),selected_items.length(),0);
Fl::dnd();
dragx = Fl::event_x(); dragy = Fl::event_y();
return 1; // don't do the multiple selection thing from Fl_Browser
}
static bool dndrelease=false; // so we know if paste is from dnd or not
if (e==FL_DND_RELEASE) {
EDEBUG(DBG "FL_DND_RELEASE '%s'\n", Fl::event_text());
// Sometimes drag is accidental
if (abs(Fl::event_x()-dragx)>MIN_DISTANCE_FOR_DND || abs(Fl::event_y()-dragy)>MIN_DISTANCE_FOR_DND) {
dndrelease=true;
Fl::paste(*this,0);
}
return 0;
// return 1 would call Fl::paste(*belowmouse(),0) (see fl_dnd_x.cxx around line 168).
// In our case that could be catastrophic
}
if (e==FL_PASTE) {
EDEBUG(DBG "FL_PASTE\n");
if (!Fl::event_text() || !Fl::event_length()) return 1;
EDEBUG(DBG "-- '%s' (%d)\n",Fl::event_text(),Fl::event_length());
// Paste is coming from menu/keyboard
if (!dndrelease) {
paste_callback_(0); // 0 = current dir
return 1;
}
dndrelease=false;
// Where is user dropping?
// If it's not the first column, we assume current directory
const int* l = column_widths();
if (Fl::event_x()<x() || Fl::event_x()>x()+l[0]) {
paste_callback_(0);
return 1;
}
// Find item where stuff is dropped
void* item = item_first();
int itemy=y()-position();
int i;
for (i=1; i<=size(); i++) {
itemy+=item_height(item);
if (itemy>Fl::event_y()) break;
}
paste_callback_((const char*)data(i));
return 1;
}
} // if (paste_callback_) ...
return EDE_Browser::handle(e);
}
/* $Id */

View File

@ -0,0 +1,166 @@
/*
* $Id$
*
* EDE FileView 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.
*/
#ifndef EDE_FileDetailsView_H
#define EDE_FileDetailsView_H
#include <FL/Fl_Input.H>
#include <edelib/String.h>
#include "EDE_Browser.h"
//#include "EDE_FileView.h"
// Sometimes I accidentaly "drag" an icon - don't want dnd to trigger
#define MIN_DISTANCE_FOR_DND 10
class FileDetailsView : public EDE_Browser {
private:
// EDE_Browser* browser; - yada
// internal list of items
/* struct myfileitem {
struct FileItem *item;
myfileitem* previous;
myfileitem* next;
} *firstitem;*/
// EditBox is displayed when renaming a file
// We subclass Fl_Input so we can handle keyboard events
class EditBox : public Fl_Input {
public:
EditBox(int x, int y, int w, int h, const char* label = 0) : Fl_Input(x,y,w,h,label) {}
int handle (int e);
}* editbox_;
int editbox_row;
// show editbox at specified row and make the row "invisible" (bgcolor same as fgcolor)
void show_editbox(int row);
void hide_editbox();
// This is called to actually rename file (when user presses Enter in editbox)
void finish_rename();
// Find row number corresponding to realpath (encoded in data())
int findrow(edelib::String realpath) {
for (int i=1; i<=size(); i++) {
char *tmp = (char*)data(i);
if (realpath==tmp) return i;
}
return 0;
}
// Callbacks
rename_callback_type* rename_callback_;
paste_callback_type* paste_callback_;
Fl_Callback* context_callback_;
// Deallocator class is used to prevent memleaks
// It stores pointers to allocated memory that will be cleaned up later
// Just remember to call empty() when needed - everything else is automatic :)
// -- The reason we need such class is that there are no insert_copy() or
// data_copy() methods in Fl_Browser (cf. label_copy() in Fl_Widget)
#define MEM_STEP 1000
class Deallocator {
void** items;
int size, capacity;
public:
Deallocator() : size(0), capacity(MEM_STEP) {
items = (void**)malloc(sizeof(void*)*capacity);
for (int i=0; i<capacity; i++) items[i]=0;
}
~Deallocator() { empty(); free(items); }
void add(void* p) {
if (size>=capacity) {
capacity+=MEM_STEP;
items = (void**)realloc(items,sizeof(void*)*capacity);
for (int i=capacity-MEM_STEP; i<capacity; i++)
items[i]=0;
}
items[size++]=p;
}
void empty() {
for (int i=0; i<size; i++) {
if (items[i]) free(items[i]);
items[i]=0;
}
size=0;
}
void debug() {
fprintf(stderr, "Bucket size %d, capacity %d\n", size, capacity);
}
} bucket;
public:
FileDetailsView(int X, int Y, int W, int H, char*label=0);
// ~FileDetailsView() { delete browser; }
// Methods for manipulating items
// They are named similarly to Browser methods, except that they take
// struct FileItem (defined in EDE_FileView.h)
void insert(int row, FileItem *item);
void add(FileItem *item) { insert(size()+1, item); }
void remove(FileItem *item) {
int row = findrow(item->realpath);
if (row) EDE_Browser::remove(row);
}
void remove(int row) { EDE_Browser::remove(row); } // doesnt work without this method :(
void update(FileItem *item); // refresh one row without reloading the whole list
// This method is needed because update() uses path to find item in list
void update_path(const char* oldpath,const char* newpath);
// Make row look "disabled" (actually we use it for cut operation)
void gray(int row);
void ungray(int row);
// Overloaded handle for: context menu, file renaming and dnd support
int handle(int e);
// Callback when user renames a file (takes const char* filename)
void rename_callback(rename_callback_type* cb) { rename_callback_ = cb; }
// Callback that is called by FL_PASTE event (such as drag&drop)
// (takes const char* paste_target /e.g. target directory or 0 for current directory/)
void paste_callback(paste_callback_type* cb) { paste_callback_ = cb; }
// Callback that is called on right mouse click (for e.g. context menu)
void context_callback(Fl_Callback* cb) { context_callback_ = cb; }
// Avoid memory leak
void clear() {
bucket.empty();
EDE_Browser::clear();
}
int visible() { return Fl_Widget::visible(); }
// Aliases for better compatibility with other views
const char*path(int row) { return (const char*)data(row); }
void show_item(int row) { middleline(row); }
};
#endif
/* $Id */

648
efiler/EDE_FileIconView.cpp Normal file
View File

@ -0,0 +1,648 @@
/*
* $Id$
*
* EDE FileView 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_FileView.h"
#include <FL/Fl_Button.H>
#include <FL/filename.H> // for FL_PATH_MAX
#include <FL/fl_draw.H> // for fl_measure and drawing selection box
#include <FL/Fl_Shared_Image.H>
#include <edelib/Nls.h>
#include <edelib/String.h>
#include <edelib/IconTheme.h>
#include <edelib/Debug.h>
// debugging help
#define DBG "FileIconView: "
// ctor
#ifdef USE_FLU_WRAP_GROUP
FileIconView::FileIconView(int X, int Y, int W, int H, char*label) : Flu_Wrap_Group(X,Y,W,H,label)
#else
FileIconView::FileIconView(int X, int Y, int W, int H, char*label) : edelib::ExpandableGroup(X,Y,W,H,label)
#endif
{
end();
box(FL_DOWN_BOX);
color(FL_BACKGROUND2_COLOR);
#ifdef USE_FLU_WRAP_GROUP
spacing(ICON_SPACING,ICON_SPACING);
#endif
select_x1=select_y1=select_x2=select_y2=0;
focused=0;
m_selected = 0;
rename_callback_ = 0;
paste_callback_ = 0;
context_callback_ = 0;
}
// Methods for manipulating items
// They are named similarly to Browser methods, except that they take
// struct FileItem (defined in EDE_FileView.h)
void FileIconView::insert(int row, FileItem *item) {
// update list of selected[] items
if (!m_selected)
m_selected = (int*)malloc(sizeof(int)*(children()+1));
else
m_selected = (int*)realloc(m_selected,sizeof(int)*(children()+1));
m_selected[children()]=0;
Fl_Button* b = new Fl_Button(0,0,ICONW,ICONH);
b->box(FL_RFLAT_BOX);
b->color(FL_BACKGROUND2_COLOR);
b->align(FL_ALIGN_INSIDE|FL_ALIGN_CENTER|FL_ALIGN_CLIP);
// Set the label
char buffer[FL_PATH_MAX];
uint j=0;
for (uint i=0; i<item->name.length(); i++,j++) {
buffer[j] = item->name[i];
buffer[j+1] = '\0';
if (buffer[j] == '@') { // escape @
buffer[++j] = '@';
buffer[j+1] = '\0';
}
b->copy_label(buffer);
int lw =0, lh = 0;
fl_measure(b->label(), lw, lh);
if (lw>ICONW) {
if (j==i+2) { // already added 2 newlines
buffer[j-2]='.';
buffer[j-1]='.';
buffer[j]='.';
buffer[j+1]='\0';
j+=2;
break; // for
} else { // add newline
buffer[j+1]=buffer[j];
buffer[j]='\n';
buffer[j+2]='\0';
j++;
}
}
}
while (j<=item->name.length()+2){
buffer[j++]='\n';
//buffer[j++]='L';
}
buffer[j]='\0';
b->copy_label(buffer);
// Tooltip text
edelib::String tooltip = _("Name: ")+item->name;
if (item->size != "") tooltip += _("\nSize: ")+item->size;
tooltip += _("\nType: ")+item->description+_("\nDate: ")+item->date+_("\nPermissions: ")+item->permissions;
b->tooltip(strdup(tooltip.c_str()));
b->user_data(strdup(item->realpath.c_str()));
// Set icon
edelib::String icon = edelib::IconTheme::get(item->icon.c_str(),edelib::ICON_SIZE_MEDIUM);
if (icon=="") icon = edelib::IconTheme::get("misc",edelib::ICON_SIZE_MEDIUM,edelib::ICON_CONTEXT_MIMETYPE);
b->image(Fl_Shared_Image::get(icon.c_str()));
#ifdef USE_FLU_WRAP_GROUP
Flu_Wrap_Group::insert(*b,row);
#else
edelib::ExpandableGroup::insert(*b,row);
#endif
//insert(*b,row); -- why doesn't this work?
redraw();
}
void FileIconView::remove(FileItem *item) {
Fl_Button* b = find_button(item->realpath);
if (b) {
#ifdef USE_FLU_WRAP_GROUP
Flu_Wrap_Group::remove(*b); // note that FWG requires to dereference the pointer
#else
edelib::ExpandableGroup::remove(b);
#endif
//remove(b);
delete b;
}
}
void FileIconView::remove(int row) {
if (row<1 || row>children()) return;
Fl_Button* b = (Fl_Button*)child(row-1);
#ifdef USE_FLU_WRAP_GROUP
Flu_Wrap_Group::remove(*b); // note that FWG requires to dereference the pointer
#else
edelib::ExpandableGroup::remove(b);
#endif
//remove(b);
delete b;
}
void FileIconView::update(FileItem *item) {
Fl_Button* b = find_button(item->realpath);
if (!b) return;
// Tooltip text
edelib::String tooltip = _("Name: ")+item->name+_("\nSize: ")+item->size+_("\nType: ")+item->description+_("\nDate: ")+item->date+_("\nPermissions: ")+item->permissions;
b->tooltip(strdup(tooltip.c_str()));
// Set icon
edelib::String icon = edelib::IconTheme::get(item->icon.c_str(),edelib::ICON_SIZE_MEDIUM);
if (icon=="") icon = edelib::IconTheme::get("misc",edelib::ICON_SIZE_MEDIUM,edelib::ICON_CONTEXT_MIMETYPE);
b->image(Fl_Shared_Image::get(icon.c_str()));
b->redraw();
}
// This method is needed because update() uses path to find item in list
void FileIconView::update_path(const char* oldpath,const char* newpath) {
Fl_Button* b = find_button(oldpath);
if (!b) return;
b->user_data(strdup(newpath));
}
// Select item (if value==1) or unselect (if value==0)
// We are faking selection by manipulating fg & bg color
// TODO: create a new class for icons that should handle selection internally
void FileIconView::select(int row, int value) {
if (row<1 || row>children()) return;
if (!m_selected) return; // shouldn't happen
set_focus(row);
int i=0;
Fl_Button* b = (Fl_Button*)child(row-1);
if (value) {
while (m_selected[i++]!=0);
m_selected[i-1]=row;
b->color(FL_SELECTION_COLOR);
b->labelcolor(fl_contrast(FL_FOREGROUND_COLOR,FL_SELECTION_COLOR));
b->redraw();
} else {
while (m_selected[i]!=0) {
if (m_selected[i]==row) {
int j=i;
while (m_selected[j]!=0) {
m_selected[j]=m_selected[j+1];
j++;
}
break;
}
i++;
}
b->color(FL_BACKGROUND2_COLOR);
b->labelcolor(FL_FOREGROUND_COLOR);
b->redraw();
}
}
// Scroll the view until item becomes visible
void FileIconView::show_item(int row) {
EDEBUG(DBG"show_item(%d)\n", row);
if (row<1 || row>children()) return;
Fl_Button* b = (Fl_Button*)child(row-1);
#ifdef USE_FLU_WRAP_GROUP
// scroll_to() will scroll until icon is at the top of view
// I prefer that view is scrolled just enough so icon is visible
// FIXME: sometimes b->y() is so large that it wraps around and becomes positive
if (b->y()+b->h() > y()+h()) {
int scrollto = scrollbar.value() + (b->y()+b->h()-y()-h())+1;
EDEBUG(DBG"by: %d bh: %d y: %d h: %d sv: %d\n", b->y(), b->h(), y(), h(), scrollbar.value());
((Fl_Valuator*)&scrollbar)->value(scrollto);
redraw();
draw(); // we need to relayout the group so that b->y() value is updated for next call
}
if (b->y() < y())
scroll_to(b);
#else
// ExpandableGroup scrolling - not tested and probably broken
Fl_Scrollbar* s = get_scroll();
if (b->y() > s->value()+h()) {
// Widget is below current view
scrolly(b->y()+b->h()-h());
} else if (b->y() < s->value()) {
// Widget is above current view
scrolly(b->y());
}
// else { widget is visible, do nothing }
#endif
}
// Overloaded handle() method
// it's currently a bit messy, see implementation comments for details
int FileIconView::handle(int e) {
//EDEBUG(DBG"Event %d\n",e);
// ------------------------------------------------
// Fixes for focus management
// ------------------------------------------------
// fltk provides focus management for members of Fl_Group, but it has minor problems:
// - after switching to another window and back, focused widget is forgotten
// - if no widget is focused, arrow keys will navigate outside group
// - Tab has same effect as right arrow - we want arrows to only be used inside
// group, and (Shift+)Tab to be used for going outside
if (Fl::focus()->inside(this)) { // is focus inside?
int k = Fl::event_key();
//EDEBUG(DBG"event_key: %d\n",k);
if (k==FL_Up || k==FL_Down || k==FL_Left || k==FL_Right) {
#ifdef USE_FLU_WRAP_GROUP
// Wrap around - FWG only (due to methods such as above())
// FL_KEYDOWN happens only if key is otherwise unhandled
if (e==FL_KEYDOWN) {
int x = get_focus()-1;
Fl_Widget* b = child(x);
Fl_Widget* b2;
switch(k) {
case FL_Up:
b2=above(b);
if (x==0) b2=child(children()-1);
break;
case FL_Down:
b2=below(b);
// Bug in Flu_Wrap_Group - inconsistent behavior of below():
if (b2==b) b2=child(children()-1);
if (x==children()-1) b2=child(0);
break;
case FL_Left:
b2=left(b);
if (x==0) b2=child(children()-1);
break;
case FL_Right:
b2=next(b);
if (x==children()-1) b2=child(0);
break;
default: // to silence compiler warnings
break;
}
for (int i=0; i<children(); i++)
if (b2==child(i)) set_focus(i+1);
return 1;
}
#endif
// Remember focus for restoring later
if (e==FL_KEYUP || e==FL_KEYBOARD) { // Sometimes only one of these is triggered
focused = get_focus();
if (!focused) {
// Nothing is focused - this is initial state
if (k==FL_Up || k==FL_Left)
set_focus(children());
else
set_focus(1);
} else
show_item(focused); // scroll follows focus
return 1; // Don't let fltk navigate to another widget
}
}
// Tab key should always navigate outside group
// NOTE: This is code is ugly! I am ashamed...
if (e!=FL_NO_EVENT && e!=FL_KEYUP && k==FL_Tab) { // FL_NO_EVENT happens a lot
EDEBUG(DBG"TAB %d - focused: (%s)\n",e,Fl::focus()->label());
// Try to find another widget to give focus to.
// Because widgets can be *very* nested, we go straight
// to window()
Fl_Group* parent_=(Fl_Group*)window();
Fl_Widget* jumpto=0;
int i=0;
while (jumpto==0 && parent_!=this) {
jumpto=parent_->child(i);
EDEBUG(DBG" -- (%s)\n", jumpto->label());
if (this->inside(jumpto) || !jumpto->visible())
jumpto=0;
i++;
if (i==parent_->children()) {
int j;
for (j=0; j<parent_->children(); j++)
if (parent_->child(j)->visible()) break;
if (j==parent_->children()) {
// nothing is visible!?
EDEBUG(DBG"WTF!\n");
parent_=(Fl_Group*)this;
} else {
parent_=(Fl_Group*)parent_->child(j);
EDEBUG(DBG" -> [%s]\n", parent_->label()); // label everything to debug!!!
}
i=0;
}
if (jumpto!=0 && !jumpto->take_focus())
jumpto=0; // widget refused focus, keep searching
}
EDEBUG(DBG"[X]\n");
// if this is the only widget, do nothing
return 1;
}
} // if (Fl::focus()->inside(this)) ...
// Restore focused widget after losing focus
if (e==FL_FOCUS) {
if (focused) set_focus(focused);
}
// ------------------------------------------------
// Make Fl_Group behave like proper widget
// ------------------------------------------------
// We accept focus (this defaults to 0 with FL_GROUP)
if (e==FL_FOCUS) return 1;
// We accept mouse clicks as long as they're inside
if (e==FL_PUSH && Fl::event_x()>x() && Fl::event_x() < x()+w()-Fl::scrollbar_size()) return 1;
// Do callback on enter key
// (because icons don't have callbacks)
if (e==FL_KEYBOARD && Fl::event_key()==FL_Enter) {
do_callback();
return 1;
}
// Select/unselect toggle using SPACE
if ((e==FL_KEYBOARD || e==FL_KEYUP) && Fl::event_key()==' ') {
if (selected(get_focus()))
select(get_focus(),0);
else
select(get_focus(),1);
return 1;
}
// ------------------------------------------------
// FL_DRAG does two things: laso and dnd
// ------------------------------------------------
// "laso" a.k.a. selection box is common name for box used to select many widgets at once
static bool laso=false; // are we laso-ing?
static int dragx,dragy; // to check for min dnd distance
if (e==FL_DRAG) {
EDEBUG(DBG"FL_DRAG! ");
// No laso is active, either start laso or start dnd operation
if (!laso) {
// Drag inside child is dnd
int ex=Fl::event_x(); int ey=Fl::event_y();
int inside=0;
for (int i=0; i<children(); i++) {
Fl_Button* b = (Fl_Button*)child(i);
if (ex>b->x()+SELECTION_EDGE && ex<b->x()+b->w()-SELECTION_EDGE && ey>b->y()+SELECTION_EDGE && ey<b->y()+b->h()-SELECTION_EDGE) {
inside=i+1; break;
}
}
if (inside) {
// If widget isn't selected, unselect everything else and select this
if (!selected(inside)) {
for (int i=0;i<children();i++) {
// We cannot use select(i+1,0) because that would mess with focus
Fl_Button* b = (Fl_Button*)child(i);
b->color(FL_BACKGROUND2_COLOR);
b->labelcolor(FL_FOREGROUND_COLOR);
}
int i=0;
while (m_selected[i]!=0) m_selected[i++]=0;
select(inside,1);
redraw(); // show changes in selection state
}
// Construct dnd string and start dnd
edelib::String selected_items;
for (int i=1; i<=children(); i++)
if (selected(i)==1) {
selected_items += "file://";
selected_items += path(i);
selected_items += "\r\n";
}
// If paste_callback_ isn't set, that means we don't support dnd
if (paste_callback_) {
Fl::copy(selected_items.c_str(),selected_items.length(),0);
Fl::dnd();
dragx=ex; dragy=ey; // to test if its close
}
return 1;
} else { // if(inside)...
// Drag to select (a.k.a. "laso") operation (outside widgets)
// Draw a dashed line around icons
EDEBUG(DBG"- laso begin.\n");
laso=true;
// Set coordinates for selection box (drawn in draw())
select_x1=select_x2=Fl::event_x();
select_y1=select_y2=Fl::event_y();
}
} else { // if (!laso) ...
// Ongoing laso operation, update selection coordinates
select_x2=Fl::event_x();
select_y2=Fl::event_y();
redraw();
EDEBUG(DBG"- laso box (%d,%d,%d,%d).\n",select_x1,select_y1,select_x2,select_y2);
}
return 1;
} // if (e==FL_DRAG) ...
// ------------------------------------------------
// FL_RELEASE does many things:
// - (un)select items,
// - terminate laso operation,
// - show context menu,
// - doubleclick,
// ------------------------------------------------
// Mouse button released
if (e==FL_RELEASE) {
EDEBUG(DBG"FL_RELEASE! ");
// Unselect everything unless Shift or Ctrl is held
if (!Fl::event_state(FL_SHIFT) && !Fl::event_state(FL_CTRL)) {
for (int i=0;i<children();i++) {
// We cannot use select(i+1,0) because that would mess with focus
Fl_Button* b = (Fl_Button*)child(i);
b->color(FL_BACKGROUND2_COLOR);
b->labelcolor(FL_FOREGROUND_COLOR);
}
redraw();
int i=0;
while (m_selected[i]!=0) m_selected[i++]=0;
}
// Stop laso operation
if (laso) {
EDEBUG(DBG"- stop laso.\n");
laso=false;
// Order coordinates
int tmp;
if (select_x1>select_x2) { tmp=select_x1; select_x1=select_x2; select_x2=tmp; }
if (select_y1>select_y2) { tmp=select_y1; select_y1=select_y2; select_y2=tmp; }
// Exclude edges
select_x1 += SELECTION_EDGE;
select_y1 += SELECTION_EDGE;
select_x2 -= SELECTION_EDGE;
select_y2 -= SELECTION_EDGE;
EDEBUG(DBG"After fixing the box coords: (%d,%d,%d,%d)\n", select_x1, select_y1, select_x2, select_y2);
// Find which buttons were lasoed
int i;
for (i=0; i<children(); i++) {
Fl_Widget* w = child(i); // some shortcuts
int wx2 = w->x()+w->w();
int wy2 = w->y()+w->h();
if (select_x2>w->x() && select_x1<wx2 && select_y2>w->y() && select_y1<wy2) {
select(i+1,1);
EDEBUG(DBG"Select widget: '%20s' (%d,%d,%d,%d)\n", w->label(), w->x(), w->y(), wx2, wy2);
}
}
// Shift key is the same as Ctrl with laso
select_x1=select_x2=select_y1=select_y2=0;
redraw();
// Single click
} else { // if(laso)...
// Find child that was clicked
int i;
for (i=0; i<children(); i++)
if (Fl::event_inside(child(i))) {
EDEBUG(DBG"in child %d\n",i);
// Shift key means "select everything in between"
if (Fl::event_state(FL_SHIFT)) {
int dir = (get_focus()<i) ? 1 : -1;
for (int j=get_focus(); j!=i+1; j+=dir)
select(j,1);
}
select(i+1,1);
}
// Didn't click on icon - remove focus from all icons
if (i==children()) {
this->take_focus();
focused=0;
}
}
// Right button - call context menu
if (Fl::event_button() == 3) {
Fl::event_is_click(0); // prevent doubleclicking with right button
if (context_callback_) context_callback_(this, (void*)path(get_focus()));
}
// Double-click operation
if (Fl::event_clicks()) {
do_callback();
}
}
// ------------------------------------------------
// Drag&drop support (apart from FL_DRAG)
// ------------------------------------------------
// If paste_callback_ isn't set, that means we don't support dnd
if (paste_callback_) {
if (e==FL_DND_ENTER) { EDEBUG(DBG"FL_DND_ENTER\n"); }
if (e==FL_DND_DRAG) { EDEBUG(DBG"FL_DND_DRAG\n"); }
if (e==FL_DND_RELEASE) { EDEBUG(DBG"FL_DND_RELEASE\n"); }
// Let the window manager know that we accept dnd
if (e==FL_DND_ENTER||e==FL_DND_DRAG) return 1;
/* // Scroll the view by dragging to border
if (e==FL_DND_LEAVE) {
if (Fl::event_y()<y())
position(position()-1);
if (Fl::event_y()>y()+h())
position(position()+1);
return 1;
}*/
static bool dndrelease=false;
if (e==FL_DND_RELEASE) {
EDEBUG(DBG"FL_DND_RELEASE '%s'\n", Fl::event_text());
// Sometimes drag is accidental
if (abs(Fl::event_x()-dragx)>MIN_DISTANCE_FOR_DND || abs(Fl::event_y()-dragy)>MIN_DISTANCE_FOR_DND) {
dndrelease=true;
Fl::paste(*this,0);
}
return 0;
// return 1 would call Fl::paste(*belowmouse(),0) (see fl_dnd_x.cxx around line 168).
// In our case that could be catastrophic
}
if (e==FL_PASTE) {
EDEBUG(DBG"FL_PASTE\n");
if (!Fl::event_text() || !Fl::event_length()) return 1;
EDEBUG(DBG"1 '%s' (%d)\n",Fl::event_text(),Fl::event_length());
// Paste comes from menu/keyboard
if (!dndrelease) {
paste_callback_(0); // 0 = current dir
return 1;
}
dndrelease=false;
// Where is the user dropping?
// It it's inside an item, try pasting
int ex=Fl::event_x(); int ey=Fl::event_y();
for (int i=0; i<children(); i++) {
Fl_Button* b = (Fl_Button*)child(i);
if (ex>b->x()+SELECTION_EDGE && ex<b->x()+b->w()-SELECTION_EDGE && ey>b->y()+SELECTION_EDGE && ey<b->y()+b->h()-SELECTION_EDGE) {
paste_callback_(path(i+1));
return 0;
}
}
// Nothing found... assume current directory
paste_callback_(0);
return 0;
}
} // if(paste_callback_)...
// End of handle()
#ifdef USE_FLU_WRAP_GROUP
return Flu_Wrap_Group::handle(e);
#else
return edelib::ExpandableGroup::handle(e);
#endif
}
/* $Id */

204
efiler/EDE_FileIconView.h Normal file
View File

@ -0,0 +1,204 @@
/*
* $Id$
*
* EDE FileView 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.
*/
#ifndef EDE_FileIconView_H
#define EDE_FileIconView_H
#include <FL/Fl_Button.H>
#include <edelib/String.h>
//#include "EDE_FileView.h"
// uncomment this to use edelib::ExpandableGroup
#define USE_FLU_WRAP_GROUP
#ifdef USE_FLU_WRAP_GROUP
#include "Flu_Wrap_Group.h"
#else
#include <edelib/ExpandableGroup.h>
#endif
// Dimensions
#define ICONW 70
#define ICONH 80
// Spacing to use between icons
#define ICON_SPACING 5
// Used to make edges of icons less sensitive to selection box, dnd and such
// (e.g. if selection laso only touches a widget with <5px, it will not be selected)
#define SELECTION_EDGE 5
// Sometimes I accidentaly "drag" an icon - don't want dnd to trigger
#define MIN_DISTANCE_FOR_DND 10
#ifdef USE_FLU_WRAP_GROUP
class FileIconView : public Flu_Wrap_Group {
#else
class FileIconView : public edelib::ExpandableGroup {
#endif
private:
// private vars for handling selection and focus
// because group doesn't do it
int focused;
int* m_selected;
// callbacks
rename_callback_type* rename_callback_;
paste_callback_type* paste_callback_;
Fl_Callback* context_callback_;
// selection box
int select_x1,select_y1,select_x2,select_y2;
// Find the Fl_Button corresponding to real path (encoded in user_data())
Fl_Button* find_button(edelib::String realpath) {
for (int i=0; i<children(); i++) {
Fl_Button* b = (Fl_Button*)child(i);
char *tmp = (char*)b->user_data();
if (realpath==tmp) return b;
}
return 0;
}
public:
FileIconView(int X, int Y, int W, int H, char*label=0);
// Methods for manipulating items
// They are named similarly to Browser methods, except that they take
// struct FileItem (defined in EDE_FileView.h)
// NOTE: For compatibility with Fl_Browser-based views, row is 1..children()
// as opposed to Fl_Group methods that use 0..children()-1
// This is a fltk bug
void insert(int row, FileItem *item);
void add(FileItem *item) { insert(children()+1, item); }
void remove(FileItem *item);
void remove(int row);
void update(FileItem *item);
// This method is needed because update() uses path to find item in list
void update_path(const char* oldpath,const char* newpath);
// Make row look "disabled" (actually we use it for cut operation)
void gray(int row) {
if (row<1 || row>children()) return;
Fl_Button* b = (Fl_Button*)child(row-1);
// FIXME this also means that item can't be selected
b->deactivate();
}
void ungray(int row) {
if (row<1 || row>children()) return;
Fl_Button* b = (Fl_Button*)child(row-1);
b->activate();
}
// Callback when user renames a file (takes const char* filename)
void rename_callback(rename_callback_type* cb) { rename_callback_ = cb; }
// Callback that is called by FL_PASTE event (such as drag&drop)
// (takes const char* paste_target /e.g. target directory or 0 for current directory/)
void paste_callback(paste_callback_type* cb) { paste_callback_ = cb; }
// Callback that is called on right mouse click (for e.g. context menu)
void context_callback(Fl_Callback* cb) { context_callback_ = cb; }
// Item real path (user_data)
const char* path(int row) {
if (row<1 || row>children()) return 0;
Fl_Widget* w = child(row-1);
return (const char*)w->user_data();
}
// Is item selected?
int selected(int row) {
if (row<1 || row>children()) return 0;
int i=0;
while(m_selected[i]!=0)
if (m_selected[i++]==row) return 1;
return 0;
}
// Select item (if value==1) or unselect (if value==0)
void select(int row, int value);
// Return nr. of widget that has keyboard focus
int get_focus() {
Fl_Widget* focus = Fl::focus();
int i = find(focus); // a Fl_Group method
if (i<children()) return i+1;
else return 0;
}
// Set keyboard focus to given item
void set_focus(int row) {
if (row<1 || row>children()) return;
Fl_Widget* w = child(row-1);
w->take_focus();
show_item(row);
focused=row;
}
// Scroll the view until item becomes visible
void show_item(int row);
// Overloaded handle() method
// it's currently a bit messy, see implementation comments for details
int handle(int e);
// Override draw() for selection box
void draw() {
#ifdef USE_FLU_WRAP_GROUP
Flu_Wrap_Group::draw();
#else
edelib::ExpandableGroup::draw();
#endif
if (select_x1>0 && select_y1>0) {
fl_color(33);
fl_line_style(FL_DASH);
fl_line(select_x1,select_y1,select_x1,select_y2);
fl_line(select_x1,select_y2,select_x2,select_y2);
fl_line(select_x2,select_y2,select_x2,select_y1);
fl_line(select_x2,select_y1,select_x1,select_y1);
fl_line_style(0);
}
}
// Override clear so that we can empty selected & focused values
void clear() {
focused=0;
if (m_selected) free(m_selected);
m_selected = 0;
#ifdef USE_FLU_WRAP_GROUP
Flu_Wrap_Group::clear();
scroll_to_beginning(); // move scrollbar to top
#else
edelib::ExpandableGroup::clear();
#endif
}
};
#endif
/* $Id */

File diff suppressed because it is too large Load Diff

View File

@ -30,12 +30,14 @@
#include <Fl/Fl_File_Chooser.H> // for fl_dir_chooser, used in "Open location"
#include <Fl/filename.H>
#include <Fl/Fl_File_Input.H> // location bar
#include <FL/Fl_Shared_Image.H> // for fl_register_images()
#include <edelib/Nls.h>
#include <edelib/MimeType.h>
#include <edelib/String.h>
#include <edelib/StrUtil.h>
#include <edelib/Run.h>
#include <edelib/IconTheme.h> // for setting the icon theme
#include "EDE_FileView.h" // our file view widget
#include "EDE_DirTree.h" // directory tree
@ -48,7 +50,7 @@
Fl_Window* win;
FileDetailsView* view;
FileView* view;
Fl_Menu_Bar* main_menu;
Fl_Box* statusbar;
DirTree* dirtree;
@ -797,7 +799,7 @@ fl_message_font(FL_HELVETICA, 12);
dirtree->when(FL_WHEN_ENTER_KEY_ALWAYS|FL_WHEN_RELEASE_ALWAYS);
dirtree->callback(tree_cb);
view = new FileDetailsView(150, menubar_height+location_bar_height, default_window_width-default_tree_width, default_window_height-menubar_height-location_bar_height-statusbar_height);
view = new FileView(150, menubar_height+location_bar_height, default_window_width-default_tree_width, default_window_height-menubar_height-location_bar_height-statusbar_height);
view->callback(open_cb);
// callbacks for file ops
view->rename_callback(do_rename);

View File

@ -32,7 +32,7 @@ void do_delete();
void do_rename(const char*);
extern FileDetailsView* view;
extern FileView* view;
extern Fl_Window* win;
extern Fl_Box* statusbar;
extern char current_dir[];