/* * $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_FileView_H #define EDE_FileView_H #include #include #include #include #include "EDE_Browser.h" // This class provides the generic FileView class and two // derived classes called FileIconView and FileDetailsView. // These classes are interchangeable, so that application // can just declare FileView* and use. // FileView is populated with FileItem's. struct FileItem { edelib::String name; // just the name edelib::String icon; edelib::String size; edelib::String realpath; // whatever the caller uses to access the file edelib::String description; edelib::String date; edelib::String permissions; }; // Type for rename_callback // I don't know how to do this without creating a new type :( typedef void (rename_callback_type)(const char*); typedef void (paste_callback_type)(const char*); class FileDetailsView_ : public EDE_Browser { private: // EDE_Browser* browser; - yada // internal list of items /* struct myfileitem { struct FileItem *item; myfileitem* previous; myfileitem* next; } *firstitem;*/ int findrow(edelib::String realpath) { for (int i=1; i<=size(); i++) { char *tmp = (char*)data(i); if (realpath==tmp) return i; } return 0; } rename_callback_type* rename_callback_; paste_callback_type* paste_callback_; Fl_Callback* context_callback_; // Subclass Fl_Input so we can handle keyboard class EditBox : public Fl_Input { friend class FileDetailsView; 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) { 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->do_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); } }* editbox_; int editbox_row; // show editbox at specified row and make the row "invisible" (bgcolor same as fgcolor) void 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; // reasonable int H=get_icon(row)->h()+2; int Y=y()+2 - position(); void* item = item_first(); for (int i=1; iresize(X,Y,W,H); editbox_->show(); editbox_->take_focus(); editbox_->redraw(); } void hide_editbox() { fprintf(stderr, "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); } // Do the rename void do_rename() { fprintf(stderr, "editbox_cb()\n"); rename_callback_(editbox_->value()); //hide_editbox(); } // Bucket 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 :) class Bucket { void** items; int size, capacity; public: Bucket() : size(0), capacity(1000), items((void**)malloc(sizeof(void*)*1000)) { for (int i=0; i=capacity) { capacity+=1000; items = (void**)realloc(items,sizeof(void*)*capacity); for (int i=capacity-1000; iend(); // end(); // resizable(browser->the_scroll()); // resizable(browser->the_group()); // resizable(browser); const int cw[]={250,200,70,130,100,0}; column_widths(cw); column_char('\t'); column_header("Name\tType\tSize\tDate\tPermissions"); textsize(12); // FIXME: hack for font size 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_->textsize(12); // FIXME: hack for font size editbox_->hide(); rename_callback_ = 0; paste_callback_ = 0; context_callback_ = 0; } // ~FileDetailsView() { delete browser; } void 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); fprintf (stderr, "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 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); } // why??? void update(FileItem *item) { int row=findrow(item->realpath); if (row==0) return; //EDE_Browser::remove(row); //insert(row, item); // this 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); fprintf (stderr, "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 is needed because update() uses path to find item in list void 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); } // Change color of row to gray void 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 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); //im->uncache(); // doesn't work //redraw(); // OPTIMIZE } // Overloaded handle for file renaming and dnd support int handle(int e) { // Right click //fprintf(stderr, "Event: %d\n", e); 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; } // if (e==FL_RELEASE && Fl::event_button()==3) { return 1; } /* ------------------------------ Rename support --------------------------------- */ // fprintf (stderr, "Event: %d\n", e); /* if (e==FL_KEYBOARD) { if (Fl::event_key()==FL_F+2) { if (editbox_->visible()) hide_editbox(); else show_editbox(get_focus()); } }*/ if (e==FL_PUSH && editbox_->visible() && !Fl::event_inside(editbox_)) hide_editbox(); // hide editbox when user clicks outside of it if (e==FL_MOUSEWHEEL && editbox_->visible()) hide_editbox(); // hide editbox when scrolling mouse // Click once on item that is already selected AND focused to rename it static bool renaming=false; if (e==FL_PUSH) renaming=false; if (e==FL_PUSH && !editbox_->visible() && Fl::event_clicks()==0 && Fl::event_button()==1) { const int* l = column_widths(); if (Fl::event_x()x()+l[0]) return Fl_Icon_Browser::handle(e); // we're only interested in first column // Is clicked item also focused? void* item = item_first(); int focusy=y()-position(); for (int i=1; iFl::event_y()) break; item=item_next(item); } if (Fl::event_y()focusy+item_height(item)) return Fl_Icon_Browser::handle(e); // It isn't if (selected(get_focus())!=1) return Fl_Icon_Browser::handle(e); // If it isn't selected, then this action is select 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; return 1; // don't pass mouse event, otherwise item will become reselected which is a bit ugly } /* ------------------------------ Drag&drop support --------------------------------- */ // If paste_callback_ isn't set, that means we don't support dnd if (paste_callback_==0) return EDE_Browser::handle(e); // 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 (only works on heading... :( ) if (e==FL_DND_LEAVE) { 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; if (e==FL_DRAG) { edelib::String selected_items; for (int i=1; i<=size(); i++) if (selected(i)==1) { if (selected_items != "") selected_items += "\n"; selected_items += (char*)data(i); } 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; if (e==FL_DND_RELEASE) { if (Fl::event_y()y()+h()) return 1; // ^^ this can be a source of crashes in Fl::dnd() fprintf(stderr, "FL_DND_RELEASE\n"); // Sometimes drag is accidental if (abs(Fl::event_x()-dragx)>10 || abs(Fl::event_y()-dragy)>10) { dndrelease=true; Fl::paste(*this,0); } } if (e==FL_PASTE) { fprintf(stderr, "FL_PASTE\n"); if (!Fl::event_text() || !Fl::event_length()) return 1; fprintf(stderr, "1 '%s' (%d)\n",Fl::event_text(),Fl::event_length()); // Paste comes from menu/keyboard if (!dndrelease) { paste_callback_(0); return 1; } dndrelease=false; // Where is the user dropping? // If it's not the first column, we assume the current directory const int* l = column_widths(); if (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; // Fl_Browser doesn't know about paste so don't bother it } return EDE_Browser::handle(e); } // Setup callback that will be used when renaming and dnd void rename_callback(rename_callback_type* cb) { rename_callback_ = cb; } void paste_callback(paste_callback_type* cb) { paste_callback_ = cb; } void context_callback(Fl_Callback* cb) { context_callback_ = cb; } // Avoid memory leak void clear() { fprintf(stderr, "Call FileView::clear()\n"); bucket.empty(); EDE_Browser::clear(); } }; class FileView : public Fl_Group { public: FileView(int X, int Y, int W, int H, char*label=0) : Fl_Group(X,Y,W,H,label) {} void insert(int row, FileItem *item); void add(FileItem *item); void remove(FileItem *item); void update(FileItem *item); void update_path(const char* oldpath,const char* newpath); void gray(int row); void ungray(int row); void rename_callback(rename_callback_type* cb); void paste_callback(paste_callback_type* cb); }; class FileDetailsView : public FileView { private: FileDetailsView_ *browser; public: FileDetailsView(int X, int Y, int W, int H, char*label=0) : FileView(X,Y,W,H,label) { browser = new FileDetailsView_(X,Y,W,H,label); browser->end(); end(); } // ~FileDetailsView() { delete browser; } void insert(int row, FileItem *item) { browser->insert(row,item); } void add(FileItem *item) { browser->add(item); } void remove(FileItem *item) { browser->remove(item); } void update(FileItem *item) { browser->update(item); } void update_path(const char* oldpath,const char* newpath) { browser->update_path(oldpath,newpath); } void gray(int row) { browser->gray(row); } void ungray(int row) { browser->ungray(row); } void rename_callback(rename_callback_type* cb) { browser->rename_callback(cb); } void paste_callback(paste_callback_type* cb) { browser->paste_callback(cb); } void context_callback(Fl_Callback* cb) { browser->context_callback(cb); } // Browser methods const char* path(int i) { return (const char*)browser->data(i); } int size() { return browser->size(); } int selected(int i) { return browser->selected(i); } void select(int i, int k) { browser->select(i,k); browser->middleline(i); } int get_focus() { return browser->get_focus(); } void set_focus(int i) { browser->set_focus(i); } void remove(int i) { browser->remove(i); } void clear() { browser->clear(); } void callback(Fl_Callback*cb) { browser->callback(cb); } // These methods are used by do_rename const char* text(int i) { return browser->text(i); } void text(int i, const char* c) { return browser->text(i,c); } uchar column_char() { return browser->column_char(); } }; #endif /* $Id */