- Add renaming support to FileIconView. Also some tweaks to renaming in

FileDetailsView (accidental renaming should now be rare).
- Fix keyboard support for edelib::ExpandableGroup in FIV. The remaining bugs 
are in ExpandableGroup class and not in efiler.
This commit is contained in:
Vedran Ljubovic 2007-08-24 07:51:02 +00:00
parent 9b7ea5dca2
commit 7a59ab12a4
6 changed files with 332 additions and 63 deletions

View File

@ -5,5 +5,7 @@ will be assumed.
- Callback for tree view widget is erratic - it usually doesn't work on Enter
key and sometimes it is sufficient to press arrow up or down to enter a
directory.
- Pressing F2 doesn't start renaming the file
- In icon view, holding shift while clicking has the same effect as ctrl (i.e.
adds just the focused widget to selection, not the ones in between)
- Icon view: sometimes files with @ in name aren't properly aligned

View File

@ -16,6 +16,7 @@
#include <FL/Fl_Input.H>
#include <FL/Fl_Shared_Image.H>
#include <FL/Fl.H>
#include <FL/Fl_Window.H> // for window() used in ctor
#include <FL/filename.H> // used in update_path()
#include <edelib/Nls.h>
@ -69,6 +70,7 @@ void FileDetailsView::show_editbox(int row) {
// unselect the row with focus - it's prettier that way
select(row,0);
editbox_row=row;
// Copy filename to editbox
char* filename = strdup(text(row));
@ -78,7 +80,6 @@ void FileDetailsView::show_editbox(int row) {
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);
@ -161,6 +162,7 @@ FileDetailsView::FileDetailsView(int X, int Y, int W, int H, char*label) : EDE_B
//first=0;
editbox_ = new EditBox(0, 0, 0, 0);
window()->add(editbox_); // this is required to make editbox visible
editbox_->box(FL_BORDER_BOX);
editbox_->parent(this);
editbox_->hide();
@ -327,6 +329,8 @@ int FileDetailsView::handle(int e) {
// 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) {
// allow to click for focusing
if (Fl::focus()!=this) return EDE_Browser::handle(e);
// we're only interested in the first column
// columns 2-5 are "safe haven" where user can click without renaming anything
@ -352,8 +356,9 @@ int FileDetailsView::handle(int 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
if (renaming && (e==FL_RELEASE || e==FL_MOVE) && Fl::event_is_click()!=0 && Fl::event_clicks()==0) {
// Fl::event_is_click() will be ==0 on drag
// Fl::event_clicks() 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
@ -402,7 +407,7 @@ EDEBUG(DBG "DnD buffer: '%s'\n", selected_items.c_str());
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());
EDEBUG(DBG "FL_DND_RELEASE (%d,%d)\n", Fl::event_x(), Fl::event_y());
// Sometimes drag is accidental
if (abs(Fl::event_x()-dragx)>MIN_DISTANCE_FOR_DND || abs(Fl::event_y()-dragy)>MIN_DISTANCE_FOR_DND) {

View File

@ -18,6 +18,7 @@
#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 <FL/Fl_Window.H> // for window() used in ctor
#include <edelib/Nls.h>
#include <edelib/String.h>
@ -29,6 +30,118 @@
#define DBG "FileIconView: "
// 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
// size of editbox
#define EDITBOX_WIDTH (ICONW+50)
// --------------------------------------
//
// File renaming stuff
//
// --------------------------------------
// EditBox is displayed when renaming a file
// We subclass Fl_Input so we can handle keyboard
int FileIconView::EditBox::handle (int e) {
EDEBUG(DBG " Editbox handle(%d)\n", e);
FileIconView* view = (FileIconView*)parent();
// in FIV we handle FL_KEYDOWN and FL_KEYUP - they shouldn't be sent!
if ((e==FL_KEYBOARD || e==FL_KEYDOWN || e==FL_KEYUP) && 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 FileIconView::show_editbox(int row) {
if (!rename_callback_) return;
if (row<1 || row>children()) return; // nothing selected
Fl_Widget* w = child(row-1);
const char* label = w->label();
if (label[0]=='.' && label[1]=='.' && label[2]=='\0') return; // can't rename "go up" button
// unselect the icon with focus - it's prettier that way
select(row,0);
editbox_row=row;
// Copy filename to editbox
char* path_ = strdup(path(row));
if (path_[strlen(path_)-1] == '/') path_[strlen(path_)-1]='\0';
const char* filename = fl_filename_name(path_);
editbox_->value(filename);
free(path_);
// make the label "invisible"
w->labelcolor(FL_BACKGROUND2_COLOR);
// calculate location for editbox
// "Magic constants" here are just nice spacing
int X=w->x() + (w->w()/2) - (EDITBOX_WIDTH/2);
if (X<x()) X=x();
int Y=w->y() + edelib::ICON_SIZE_MEDIUM + 5;
int W=EDITBOX_WIDTH;
int H=FL_NORMAL_SIZE + 4;
EDEBUG(DBG"editbox (%d,%d,%d,%d) \n",X,Y,W,H);
editbox_->resize(X,Y,W,H);
editbox_->show();
editbox_->take_focus();
redraw();
w->redraw();
editbox_->redraw();
}
void FileIconView::hide_editbox() {
EDEBUG(DBG "hide_editbox()\n");
editbox_->hide();
// Make the edited row visible again
Fl_Widget* w = child(editbox_row-1);
w->labelcolor(FL_FOREGROUND_COLOR);
w->redraw(); // label will be updated by rename_callback, if necessary
}
// This is called to actually rename file (when user presses Enter in editbox)
void FileIconView::finish_rename() {
EDEBUG(DBG "finish_rename()\n");
rename_callback_(editbox_->value());
}
// ctor
#ifdef USE_FLU_WRAP_GROUP
@ -51,6 +164,12 @@ FileIconView::FileIconView(int X, int Y, int W, int H, char*label) : edelib::Exp
rename_callback_ = 0;
paste_callback_ = 0;
context_callback_ = 0;
editbox_ = new EditBox(0, 0, 0, 0);
window()->add(editbox_); // this is required to make editbox visible
editbox_->box(FL_BORDER_BOX);
editbox_->parent(this);
editbox_->hide();
}
@ -129,15 +248,15 @@ void FileIconView::insert(int row, FileItem *item) {
}
void FileIconView::remove(FileItem *item) {
Fl_Button* b = find_button(item->realpath);
if (b) {
Fl_Widget* w = find_icon(item->realpath);
if (w) {
#ifdef USE_FLU_WRAP_GROUP
Flu_Wrap_Group::remove(*b); // note that FWG requires to dereference the pointer
Flu_Wrap_Group::remove(*w); // note that FWG requires to dereference the pointer
#else
edelib::ExpandableGroup::remove(b);
edelib::ExpandableGroup::remove(w);
#endif
//remove(b);
delete b;
//remove(w);
delete w;
}
}
@ -155,26 +274,63 @@ void FileIconView::remove(int row) {
void FileIconView::update(FileItem *item) {
Fl_Button* b = find_button(item->realpath);
if (!b) return;
Fl_Widget* w = find_icon(item->realpath);
if (!w) 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()));
w->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()));
w->image(Fl_Shared_Image::get(icon.c_str()));
b->redraw();
w->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));
Fl_Widget* w = find_icon(oldpath);
if (!w) return;
w->user_data(strdup(newpath));
// Set the label
char buffer[FL_PATH_MAX];
const char* name = fl_filename_name(newpath);
uint j=0;
for (uint i=0; i<strlen(name); i++,j++) {
buffer[j] = name[i];
buffer[j+1] = '\0';
if (buffer[j] == '@') { // escape @
buffer[++j] = '@';
buffer[j+1] = '\0';
}
w->copy_label(buffer);
int lw =0, lh = 0;
fl_measure(w->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<=strlen(name)+2){
buffer[j++]='\n';
//buffer[j++]='L';
}
buffer[j]='\0';
w->copy_label(buffer);
}
// Select item (if value==1) or unselect (if value==0)
@ -219,30 +375,30 @@ EDEBUG(DBG"show_item(%d)\n", row);
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
Fl_Scrollbar* s = &scrollbar;
#else
Fl_Scrollbar* s = get_scroll();
#endif
// 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);
int scrollto=0;
if (b->y()+b->h() > y()+h())
scrollto = s->value() + (b->y()+b->h()-y()-h())+1;
else if (b->y() < y())
scrollto = s->value() - (y()-b->y());
if (scrollto) {
EDEBUG(DBG"by: %d bh: %d y: %d h: %d sv: %d\n", b->y(), b->h(), y(), h(), s->value());
#ifdef USE_FLU_WRAP_GROUP
((Fl_Valuator*)s)->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 }
scrolly(scrollto); // ExpandableGroup method
#endif
}
}
@ -300,6 +456,54 @@ int FileIconView::handle(int e) {
if (b2==child(i)) set_focus(i+1);
return 1;
}
#else
// edelib::ExpandableGroup - Full keyboard navigation
if (e==FL_KEYDOWN) {
int x = get_focus();
// Wrap around
if ((k==FL_Up || k==FL_Left) && x==1)
set_focus(children());
else if ((k==FL_Down || k==FL_Right) && x==children())
set_focus(1);
// Left/right use item ordering
else if (k==FL_Left)
set_focus(x-1);
else if (k==FL_Right)
set_focus(x+1);
// Use widget x() && y() to find item above/below
// This isn't very elegant, but with ExpandableGroup there is no other way...
else {
Fl_Widget* w = child(x-1);
Fl_Widget* w2 = 0;
int dest=0;
for (int i=0; i<children(); i++) {
Fl_Widget* t=child(i);
if (k==FL_Up && t->x()==w->x() && t->y()<w->y())
if (!w2 || w2->y()<t->y()) {
w2=t;
dest=i+1;
}
if (k==FL_Down && t->x()==w->x() && t->y()>w->y())
if (!w2 || w2->y()>t->y()) {
w2=t;
dest=i+1;
}
}
if (w2)
set_focus(dest);
else { // nothing found
if (k==FL_Up)
set_focus(1);
else
set_focus(children());
}
}
return 1;
}
#endif
// Remember focus for restoring later
@ -318,7 +522,7 @@ int FileIconView::handle(int e) {
}
// Tab key should always navigate outside group
// NOTE: This is code is ugly! I am ashamed...
// NOTE: This 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.
@ -365,6 +569,65 @@ EDEBUG(DBG"[X]\n");
/* ------------------------------
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;
EDEBUG(DBG"FL_PUSH\n");
}
// 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) {
EDEBUG(DBG"start rename\n");
// allow to click for focusing
if (!Fl::focus()->inside(this)) return 1;
// Is click inside a label?
int clicked=0;
for (int i=0; i<children(); i++) {
Fl_Widget* w = child(i);
int X=Fl::event_x(); int Y=Fl::event_y();
if (X>w->x() && X<w->x()+w->w() && Y>w->y()+edelib::ICON_SIZE_MEDIUM && Y<w->y()+w->h())
{ clicked=i+1; break; }
}
// Is clicked item selected and focused?
if (clicked && get_focus()==clicked && selected(clicked)) {
renaming=true; // On next event, we will check if it's doubleclick
} // otherwise, let the event become select/focus event
EDEBUG(DBG"clicked: %d \n",clicked);
if (renaming) EDEBUG(DBG"renaming!\n");
}
if (renaming && (e==FL_RELEASE || e==FL_MOVE) && Fl::event_is_click()!=0 && Fl::event_clicks()==0) {
EDEBUG(DBG"stop rename\n");
// Fl::event_is_click() will be ==0 on drag
// Fl::event_clicks() 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;
}
}
// ------------------------------------------------
// Make Fl_Group behave like proper widget
// ------------------------------------------------

View File

@ -15,14 +15,12 @@
#define EDE_FileIconView_H
#include <FL/Fl_Button.H>
#include <edelib/String.h>
//#include "EDE_FileView.h"
// uncomment this to use edelib::ExpandableGroup
// comment this to use edelib::ExpandableGroup
#define USE_FLU_WRAP_GROUP
#ifdef USE_FLU_WRAP_GROUP
@ -33,22 +31,6 @@
// 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
@ -69,16 +51,33 @@ private:
// 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) {
// Find the widget corresponding to real path (encoded in user_data())
Fl_Widget* find_icon(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;
Fl_Widget* w = child(i);
char *tmp = (char*)w->user_data();
if (realpath==tmp) return w;
}
return 0;
}
// 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();
public:
FileIconView(int X, int Y, int W, int H, char*label=0);

View File

@ -3,3 +3,5 @@
- Add support for fd.o Trash protocol
- Altnernate line color in browser (add to EDE_Browser)
- Vertical lines delineating columns (add to EDE_Browser)
- Nicer edit box for FileIconView (centered, wrapped, possibly multiline - see
Konqueror)

View File

@ -386,7 +386,6 @@ void do_paste(const char* t) {
fprintf (stderr, "PASTE from '%s', to '%s', type=%d\n",(char*)Fl::event_text(),to,operation);
if (!strchr(Fl::event_text(), '/'))
return; // User is pasting something that isn't files
// TODO: create a text file?
@ -468,7 +467,6 @@ fprintf (stderr, "from[%d]='%s'\n", k, from[k]);
else
c = ede_choice_alert(tsprintf(_("Copy or move these %d files to directory\n\t%s ?"), count, to), _("C&ancel"), _("&Copy"), _("&Move"));
fprintf(stderr, "Exited choice_alert\n");
if (c==0) goto FINISH;
if (c==1) operation=COPY; else operation=CUT;
}