/* * $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 #include // for FL_PATH_MAX #include // for fl_measure and drawing selection box #include #include #include #include #include // 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; iname.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; ilabel()); // 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; jchildren(); 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; ib->x()+SELECTION_EDGE && exx()+b->w()-SELECTION_EDGE && ey>b->y()+SELECTION_EDGE && eyy()+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;icolor(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;icolor(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; ix()+w->w(); int wy2 = w->y()+w->h(); if (select_x2>w->x() && select_x1w->y() && select_y1label(), 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; itake_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()+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; ib->x()+SELECTION_EDGE && exx()+b->w()-SELECTION_EDGE && ey>b->y()+SELECTION_EDGE && eyy()+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 */