diff --git a/efiler/EDE_Browser.cpp b/efiler/EDE_Browser.cpp index a7d42a2..20b4b7f 100644 --- a/efiler/EDE_Browser.cpp +++ b/efiler/EDE_Browser.cpp @@ -118,6 +118,10 @@ void EDE_Browser::sort(int column, SortType type, bool reverse) { 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; @@ -125,7 +129,7 @@ void EDE_Browser::sort(int column, SortType type, bool reverse) { bool found=false; while (delim=strchr(h, colchar)) { if (col==sort_column) { - strncpy(delim-SYMLEN+1,delim,strlen(delim)+1); + for (int i=0; i<=strlen(delim); i++) delim[i-SYMLEN+1]=delim[i]; found=true; break; } @@ -211,8 +215,6 @@ EDE_Browser::EDE_Browser(int X,int Y,int W,int H,const char *L) : Fl_Icon_Browse totalwidth_(0), column_header_(0), sort_column(0), sort_type(NO_SORT), sort_direction(false) { -fprintf (stderr, "ctor(%d,%d,%d,%d)\n",X,Y,W,H); - heading = new Heading(X,Y,W,buttonheight); heading->end(); heading->hide(); @@ -236,10 +238,20 @@ fprintf (stderr, "ctor(%d,%d,%d,%d)\n",X,Y,W,H); } +// Deallocate all memory used by header labels +void EDE_Browser::cleanup_header() { + // Deallocate old button labels + for (int i=0; ichildren(); 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); - heading->clear(); + cleanup_header(); heading->hide(); } @@ -248,7 +260,7 @@ void EDE_Browser::show_header() { int button_x=0; char *hdr = column_header_; const int* l = Fl_Icon_Browser::column_widths(); - heading->clear(); + cleanup_header(); for (int i=0; i==0||l[i-1]; i++) { // If the button is last, calculate size int button_w = l[i]; @@ -278,7 +290,6 @@ void EDE_Browser::show_header() { hdr=delim+1; // next field } } -fprintf (stderr, "showheader calls resize(%d,%d,%d,%d)\n",x(),y(),w(),h()); if (!heading->visible()) resize(x(),y()+buttonheight,w(),h()-buttonheight); heading->resizable(0); // We will resize the heading and individual buttons manually heading->show(); @@ -293,17 +304,19 @@ void EDE_Browser::column_widths(const int* l) { // if (total>=scroll->w()) { // Fl_Icon_Browser::size(total,h()); // } -fprintf(stderr, "Total width is: %d\n", totalwidth_); // If there was heading before, regenerate if (heading->visible()) heading->size(totalwidth_,buttonheight); // show_header(); // Second array for the Fl_Browser - int *tmp = new int[i]; // FIXME: Someone should cleanup this memory sometimes... + static int *tmp = 0; + if (tmp) delete[] tmp; + tmp=new int[i]; // FIXME: Dtor should cleanup this memory for (int j=0; jcolumn_widths(columns); b->redraw(); // OPTIMIZE (some smart damage in column_widths() ?) - free(columns); + delete[] columns; // There will be many RELEASE events, so we update sx (used when calculating dx) sx=Fl::event_x(); diff --git a/efiler/EDE_Browser.h b/efiler/EDE_Browser.h index 325eba5..dcd9db7 100644 --- a/efiler/EDE_Browser.h +++ b/efiler/EDE_Browser.h @@ -81,6 +81,7 @@ private: SortType sort_type; bool sort_direction; + void cleanup_header(); void hide_header(); void show_header(); @@ -92,6 +93,7 @@ public: ~EDE_Browser() { delete[] column_sort_types_; + cleanup_header(); delete heading; // delete scroll; delete hscrollbar; diff --git a/efiler/EDE_FileView.h b/efiler/EDE_FileView.h index 78a9082..7500f8d 100644 --- a/efiler/EDE_FileView.h +++ b/efiler/EDE_FileView.h @@ -40,86 +40,10 @@ struct FileItem { -////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////////////////// - - - -/*// Event handler for EditBox -int EditBox::handle(int event) { - if (!this->visible()) return parent()->handle(event); - bool above=false; - //fprintf(stderr,"Editbox event: %d (%s)\n",event,event_name(event)); - - // Change filename - if (event==KEY && (event_key()==ReturnKey || event_key()==KeypadEnter)) { - // split old filename to path and file - char path[PATH_MAX], file[PATH_MAX]; - strcpy(path, (char*)editing_->user_data()); - if (path[strlen(path)-1] == '/') path[strlen(path)-1]='\0'; - char *p = strrchr(path,'/'); - if (p==0 || *p=='\0') { - strcpy(file,path); - path[0]='\0'; - } else { // usual case - p++; - strcpy(file,p); - *p='\0'; - } - - if (strlen(file)!=strlen(text()) || strcmp(file,text())!=0) { - // Create new filename - strncat(path, text(), PATH_MAX-strlen(path)); - char oldname[PATH_MAX]; - strcpy(oldname, (char*)editing_->user_data()); - if (rename(oldname, path) == -1) { - alert(tsprintf(_("Could not rename file! Error was:\n\t%s"), strerror(errno))); - } else { - // Update browser - free(editing_->user_data()); - editing_->user_data(strdup(path)); - const char* l = editing_->label(); - editing_->label(tasprintf("%s%s",text(),strchr(l, '\t'))); - } - } - - above=true; - } - - // Hide editbox - if (above || ( event==KEY && event_key()==EscapeKey ) ) { - this->hide(); - return 1; - } - Input::handle(event); -} - - -// We override hide method to ensure certain things done -void EditBox::hide() { - Input::hide(); - // Remove box so it doesn't get in the way - this->x(0); - this->y(0); - this->w(0); - this->h(0); - // Return the browser item into "visible" state - if (editing_) { - editing_->textcolor(textcolor()); - editing_->redraw(); - parent()->take_focus(); - } -} - -*/ -////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////////////////////// - // Type for rename_callback // I don't know how to do this without creating a new type :( -typedef void (my_callback)(const char*); +typedef void (rename_callback_type)(const char*); +typedef void (paste_callback_type)(const char*,const char*); @@ -164,17 +88,20 @@ private: 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; - my_callback* rename_callback_; + rename_callback_type* rename_callback_; + paste_callback_type* dnd_callback_; // 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 (strcmp(text(row), "..")==0) return; // can't rename "go up" button + 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); @@ -229,7 +156,37 @@ private: //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; iparent(this); editbox_->textsize(12); // FIXME: hack for font size editbox_->hide(); + + rename_callback_ = 0; + dnd_callback_ = 0; } // ~FileDetailsView() { delete browser; } @@ -262,7 +222,9 @@ public: // Construct browser line edelib::String value; value = item->name+"\t"+item->description+"\t"+item->size+"\t"+item->date+"\t"+item->permissions; - EDE_Browser::insert(row, value.c_str(), strdup(item->realpath.c_str())); // put realpath into data + 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 @@ -278,24 +240,42 @@ fprintf (stderr, "value: %s\n", value.c_str()); 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); - // FIXME: this will lose focus, making it impossible to click on something while + + //EDE_Browser::remove(row); + //insert(row, item); + // this was reimplemented because a) it's unoptimized, b) adds stuff 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())); } // 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))+4); // add 4 places for format chars + char *ntext = (char*)malloc(sizeof(char)*strlen(text(row))+5); // add 4 places for format chars strncpy(ntext+4, text(row), strlen(text(row))); - ntext[0]='@'; ntext[1]='C'; ntext[2]='2'; ntext[3]='5'; + ntext[0]='@'; ntext[1]='C'; ntext[2]='2'; ntext[3]='5'; // @C25 - nice shade of gray + ntext[strlen(text(row))+4]='\0'; // in case text(row) was broken text(row,ntext); - free(ntext); + bucket.add(ntext); // grey icon - but how to ungray? Fl_Image* im = get_icon(row)->copy(); @@ -310,7 +290,7 @@ fprintf (stderr, "value: %s\n", value.c_str()); char *ntext = (char*)malloc(sizeof(char)*strlen(text(row))-4); // 4 places for format chars strncpy(ntext, text(row)+4, strlen(text(row))-4); text(row,ntext); - free(ntext); + bucket.add(ntext); // don't work //Fl_Image* im = get_icon(row); @@ -319,8 +299,8 @@ fprintf (stderr, "value: %s\n", value.c_str()); //redraw(); // OPTIMIZE } - // Renaming support int handle(int e) { + // Rename support if (e==FL_KEYBOARD) { if (Fl::event_key()==FL_F+2) { if (editbox_->visible()) @@ -330,12 +310,101 @@ fprintf (stderr, "value: %s\n", value.c_str()); } } if (e==FL_PUSH && editbox_->visible() && !Fl::event_inside(editbox_)) - hide_editbox(); // hide editbox when click outside it + 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 + + 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); // Click outside selected item + if (selected(get_focus())!=1) + return Fl_Icon_Browser::handle(e); // allow to select item if it's just focused + + renaming=true; + } + if (e==FL_RELEASE && renaming && Fl::event_clicks()==0) { + show_editbox(get_focus()); + renaming=false; + return 1; // don't pass mouse event, otherwise item will become selected again + } + + // Drag&drop support + static int paste_event_y; + + /*--- This is to get dnd events from non-fltk apps --- + static bool dragging=false; + if (e==FL_PUSH) dragging=false; + if (e==FL_DND_ENTER) dragging=true; + if (e==FL_RELEASE && dragging) { + paste_event_y=Fl::event_y(); + Fl::paste(*this,0); + dragging=false; + } + /*--- End ugly hack ---*/ + + // Don't unselect on FL_PUSH cause that could be dragging + if (e==FL_PUSH && Fl::event_clicks()!=1) return 1; + + if (e==FL_DRAG) { + edelib::String selected_items; + for (int i=1; i<=size(); i++) + if (selected(i)==1) { + if (selected_items != "") selected_items += ","; + selected_items += (char*)data(i); + } + Fl::copy(selected_items.c_str(),selected_items.length(),0); + Fl::dnd(); + return 1; // don't do the multiple selection thing from Fl_Browser + } + + if (e==FL_DND_RELEASE) { + paste_event_y=Fl::event_y(); + Fl::paste(*this,0); + } + if (e==FL_PASTE) { + if (!Fl::event_text() || !Fl::event_length()) return 1; + + // Where is the user dropping? + void* item = item_first(); + int itemy=y()-position(); + int i; + for (i=1; i<=size(); i++) { + itemy+=item_height(item); + if (itemy>paste_event_y) break; + } + dnd_callback_(Fl::event_text(),(const char*)data(i)); + } +// if (e==FL_DND_ENTER) { take_focus(); Fl::focus(this); Fl::belowmouse(this); Fl_Icon_Browser::handle(FL_FOCUS); } +// if (e==FL_DND_LEAVE) { take_focus(); Fl::focus(this); Fl::belowmouse(this); Fl_Icon_Browser::handle(FL_FOCUS); } + //fprintf (stderr, "Event: %d\n", e); + return Fl_Icon_Browser::handle(e); } - // Setup callback that will be used when renaming - void rename_callback(my_callback* cb) { rename_callback_ = cb; } + // Setup callback that will be used when renaming and dnd + void rename_callback(rename_callback_type* cb) { rename_callback_ = cb; } + void dnd_callback(paste_callback_type* cb) { dnd_callback_ = cb; } + + // Avoid memory leak + void clear() { +fprintf(stderr, "Call FileView::clear()\n"); + bucket.empty(); + EDE_Browser::clear(); + } }; diff --git a/efiler/efiler.cpp b/efiler/efiler.cpp index 6180881..7b01afb 100644 --- a/efiler/efiler.cpp +++ b/efiler/efiler.cpp @@ -157,6 +157,7 @@ char *simpleopener(const char* mimetype) { if (buf[0]=='\0' || buf[1]=='\0' || buf[0]=='#') continue; buf[strlen(buf)-1]='\0'; char *tmp = strstr(buf, "||"); + if (!tmp) continue; // malformatted opener *tmp = '\0'; sopeners* q = new sopeners; q->type=strdup(buf); @@ -215,7 +216,7 @@ void loaddir(const char *path) { if (path[0] == '~') // Expand tilde snprintf(current_dir,PATH_MAX,"%s/%s",getenv("HOME"),path+1); else - strcpy(current_dir,path); + if (path!=current_dir) strcpy(current_dir,path); } else strcpy(current_dir,getenv("HOME")); @@ -255,7 +256,9 @@ fprintf (stderr, "loaddir(%s) = (%s)\n",path,current_dir); } // set window label - win->label(tasprintf(_("%s - File manager"), current_dir)); + // unlike fltk2, labels can be pointers to static char + win->label(tsprintf(_("%s - File manager"), current_dir)); + statusbar->label(tsprintf(_("Scanning directory %s..."), current_dir)); view->clear(); @@ -264,6 +267,7 @@ fprintf (stderr, "loaddir(%s) = (%s)\n",path,current_dir); for (int i=0; id_name; //shortcut + if (i>0) free(files[i-1]); // see scandir(3) // don't show . (current directory) if (strcmp(n,".")==0) continue; @@ -301,6 +305,7 @@ fprintf (stderr, "loaddir(%s) = (%s)\n",path,current_dir); item_list[fsize++] = item; } + free(files[size-1]); free(files); // see scandir(3) // Populate view @@ -355,7 +360,7 @@ fprintf (stderr, "ICON: %s !!!!!\n", icon.c_str()); // f_bfree is size available to root double percent = double(statfs_buffer.f_blocks-statfs_buffer.f_bavail)/statfs_buffer.f_blocks*100; char *tmp = strdup(nice_size(totalsize)); // nice_size() operates on a static char buffer, we can't use two calls at the same time - statusbar->label(tasprintf(_("Filesystem %s, Size %s, Free %s (%4.1f%% used)"), find_fs_for(current_dir), tmp, nice_size(freesize), percent)); + statusbar->label(tsprintf(_("Filesystem %s, Size %s, Free %s (%4.1f%% used)"), find_fs_for(current_dir), tmp, nice_size(freesize), percent)); free(tmp); } else statusbar->label(_("Error reading filesystem information!")); @@ -363,11 +368,6 @@ fprintf (stderr, "ICON: %s !!!!!\n", icon.c_str()); -/*----------------------------------------------------------------- - File moving and copying operations --------------------------------------------------------------------*/ - - /*----------------------------------------------------------------- Main menu callbacks -------------------------------------------------------------------*/ @@ -386,9 +386,6 @@ fprintf (stderr,"enter\n"); tm=newtm; if (view->value()==0) return; // This can happen while efiler is loading - char* filename = strdup(view->text(view->value())); - if (char*k = strchr(filename, view->column_char())) *k='\0'; - char* path = (char*)view->data(view->value()); fprintf(stderr, "Path: %s (ev %d)\n",path,Fl::event()); @@ -411,15 +408,21 @@ fprintf (stderr,"enter\n"); const char *o2 = tsprintf(opener,path); fprintf (stderr, "run_program: %s\n", o2); + + // construct filename for the message + char* filename = strdup(view->text(view->value())); + if (char*k = strchr(filename, view->column_char())) *k='\0'; + if (opener) { int k=edelib::run_program(o2,false); fprintf(stderr, "retval: %d\n", k); } else - statusbar->label(tasprintf(_("No program to open %s!"), filename)); + statusbar->label(tsprintf(_("No program to open %s!"), filename)); free(filename); rlim->rlim_cur = old_rlimit; setrlimit (RLIMIT_CORE, rlim); + free(rlim); } } // open_cb @@ -525,6 +528,7 @@ edelib::IconTheme::init("crystalsvg"); view->callback(open_cb); // callback for renaming view->rename_callback(do_rename); + view->dnd_callback(dnd_cb); Fl_Group *sbgroup = new Fl_Group(0, default_window_height-statusbar_height, default_window_width, statusbar_height); statusbar = new Fl_Box(2, default_window_height-statusbar_height+2, statusbar_width, statusbar_height-4); diff --git a/efiler/fileops.cpp b/efiler/fileops.cpp index abac435..0dd5cdb 100644 --- a/efiler/fileops.cpp +++ b/efiler/fileops.cpp @@ -88,9 +88,9 @@ void do_cut_copy(bool m_copy) { // Update statusbar if (m_copy) - statusbar->label(tasprintf(_("Selected %d items for copying"), nselected)); + statusbar->label(tsprintf(_("Selected %d items for copying"), nselected)); else - statusbar->label(tasprintf(_("Selected %d items for moving"), nselected)); + statusbar->label(tsprintf(_("Selected %d items for moving"), nselected)); } @@ -149,19 +149,20 @@ bool my_copy(const char* src, const char* dest) { if (q == 0) return true; else return false; } - if ( !edelib::file_writeable(dest) ) +// edelib::file_writeable() returns false if dest doesn't exist +/* if ( !edelib::file_writeable(dest) ) { // here was choice_alert int q = fl_choice(tsprintf(_("Cannot create file %s"),dest), _("&Stop"), _("&Continue"), 0); if (q == 0) return true; else return false; - } + }*/ // we will try to preserve permissions etc. cause that's usually what people want if (!edelib::file_copy(src,dest,true)) fl_alert(tsprintf(_("Error copying %s to %s"),src,dest)); - fclose(fold); - fclose(fnew); +// fclose(fold); +// fclose(fnew); return true; } @@ -239,6 +240,7 @@ void do_paste() { } rename(cut_copy_buffer[i],newname); free(cut_copy_buffer[i]); + free(newname); } free(cut_copy_buffer); cut_copy_buffer=0; @@ -263,6 +265,7 @@ void do_paste() { if (strcmp(srcdir,current_dir)==0) { fl_alert(_("You cannot copy a file onto itself!")); + free(srcdir); return; } @@ -333,13 +336,22 @@ void do_paste() { } progress_window->hide(); - // Deallocate files_list[][] - for (int i=0; isize(); j++) + if (strncmp(tmp, view->text(j), strlen(tmp))==0) + view->select(j,1); + free(files_list[i]); + } + + free(files_list); + free(srcdir); } } @@ -350,11 +362,9 @@ void do_delete() { int list_size = 0, list_capacity = 1000; char** files_list = (char**)malloc(sizeof(char**)*list_capacity); - for (int i=1; i<=view->size(); i++) { - if (view->selected(i)==1) { + for (int i=1; i<=view->size(); i++) + if (view->selected(i)==1) expand_dirs((char*)view->data(i), files_list, list_size, list_capacity); - } - } if (list_size==0) { //nothing selected, use the focused item int i=view->get_focus(); @@ -374,7 +384,10 @@ void do_delete() { // delete for (int i=0; isize(); i++) + if (view->selected(i)==1) + view->remove(i); } @@ -405,3 +418,52 @@ void do_rename(const char* c) { } } + +// Drag & drop callback - mostly copied from do_cut_copy() +void dnd_cb(const char* from,const char* to) { + fprintf (stderr, "PASTE from '%s', to '%s'\n",from,to); + return; + + char *t = (char*)to; + if (!fl_filename_isdir(to)) + t=current_dir; + + +/* + // Clear cut/copy buffer and optionally ungray the previously cutted icons + if (cut_copy_buffer) { + for (int i=0; cut_copy_buffer[i]; i++) + free(cut_copy_buffer[i]); + free(cut_copy_buffer); + if (!operation_is_copy) { + for (int i=1; i<=num; i++) + view->ungray(i); + } + } + + // Allocate buffer + cut_copy_buffer = (char**)malloc(sizeof(char*) * (nselected+2)); + + // Add selected files to buffer + int buf=0; + for (int i=1; i<=num; i++) + if (view->selected(i)==1) + cut_copy_buffer[buf++] = strdup((char*)view->data(i)); + // We don't know yet if this is cut or copy, so no need to gray anything + + // + + // Clear cut/copy buffer and optionally ungray the previously cutted icons + if (cut_copy_buffer) { + for (int i=0; cut_copy_buffer[i]; i++) + free(cut_copy_buffer[i]); + free(cut_copy_buffer); + if (!operation_is_copy) { + for (int i=1; i<=num; i++) + view->ungray(i); + } + } + + + int c = fl_choice(tsprintf(_("Do you want to copy or move file %s to directory %s?"), k+1), _("Do&n't delete"), _("&Delete"), 0);*/ +} diff --git a/efiler/fileops.h b/efiler/fileops.h index be8b8a8..06f8ed4 100644 --- a/efiler/fileops.h +++ b/efiler/fileops.h @@ -20,6 +20,9 @@ void do_delete(); // Rename the file with focus to given name void do_rename(const char*); +// Callback for drag&drop operations +void dnd_cb(const char*from,const char*to); + extern FileDetailsView* view; extern Fl_Box* statusbar;