// dirwin_core.cxx  - core non-gui types and functions for DirWin

#define APP_VER "0.7.4" // Last update 2011-07-04

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <time.h>
#include <sys/time.h>
#include <sys/inotify.h>
#include <sys/ioctl.h>
#include <FL/Fl_Help_Dialog.H>
#include <Fl/Fl_Multi_Browser.h>
#include <FL/fl_draw.H>
#include "dirwin.h"
#include "dirwin_core.h"

#ifndef MAX
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#endif

#define RESELECT 1
#define NORMTEXT_COLOR  (Fl_Color)16
#define HIDETEXT_COLOR  (Fl_Color)17
#define LISTBG_COLOR    (Fl_Color)18
#define LISTBG2_COLOR   (Fl_Color)19
char TextColor_str[8]  = "@C16";    
char HideColor_str[8]  = "@C17";
char BgColor_str[8]    = "@B18";    
char Bg2Color_str[8]   = "@B19";
char BtnColor_str[8]   = "@B49";   
#define DEFAULT_DIR_PERMS    (S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)

#define MAXASSOC 100        // application associations
#define MAXPATLEN 16           // file ident. matching pattern
#define MAXTLBLLEN 32          // Assoc. app. "Type" label 
#define MAXALBLLEN 32          // Assoc. app. "Action" label 
#define MAXCMDSPECLEN 128      // Assoc. app. command specification

#define MAXFNAMELEN 256     // filename
#define MAXFPATHLEN 768     // total path
#define MAXFFULLPATHLEN 1024    // total path plus name

#define MAXNINAME 80         // name of user or user group 
//#define MAXONAME 80         // user ("owner") name

const char About_text[] = 
"DirWin File Manager version %s\n"
"copyright 2011 by Michael A. Losh\n"
"\n"
"DirWin is free software: you can redistribute it and/or\n"
"modify it under the terms of the GNU General Public License\n"
"as published by the Free Software Foundation, version 3.\n"
"\n"
"DirWin is distributed in the hope that it will be useful,\n"
"but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
"See http://www.gnu.org/licenses/ for more details.";


struct association {
    char pat[MAXPATLEN];
    char typelabel[MAXTLBLLEN];
    char cmdlabel[MAXALBLLEN];
    char cmdspec[MAXCMDSPECLEN];
};
// File type codes

// Executable specification type codes

// Menu items
enum {  MI_NEW_FILE = 0, MI_NEW_FOLDER, MI_NEW_SYMLINK, 
        MI_RUN, MI_OPEN, MI_OPENWITH, MI_SELECTALL,
        MI_RENAME, MI_COPY, MI_COPYTO, MI_MOVE,
        MI_TRASH, MI_RESTORE, MI_DELETE, MI_PROPERTIES,
        MI_SELECT_ALL, MI_PASTE, MI_OPENTERM, 
        MI_OPTIONS, MI_CANCEL, MI_FILETYPES,
        MI_ABOUT, MI_HELP,
        MI_QUIT
};

// file copy or move operations
typedef enum {COPY_OPER, MOVE_OPER} fileop_enum;

enum {  FT_CHARDEV     = -5, FT_BLOCKDEV = -4, FT_FIFO = -3, FT_SOCKET = -2,
        FT_SHELLSCRIPT = -1, FT_EXEC     =  0, FT_DIR  =  1, FT_TEXT   =  2};

struct association Association[MAXASSOC] = {
    //ext pattern,   label        //cmd label //run cmd                
    {"!",           "executable", "Run",      "./%s &"           },
    {"/",           "dir",        "DirWin",   "dirwin %s &"      },  
    {"*",           "file",       "View Text","editor -R %s &"   },
    {"*",           "file",       "Edit Text","editor %s &"      },
    {".sh",			"script",     "Run",	  "aterm -e ./%s &"  },
    {".tcz",        "TCE",        "Run",      "tce-run %s &"     },
    {".lst",        "list",       "Edit",     "editor %s &"      },

};
int Associations = 7;
int FirstConfigrableAssociation = 4;


char* SelectedFiles               = NULL;
int   SelectedFilesBufsiz         = 128;
char* SelectedFullPathFiles       = NULL;
int   SelectedFullPathFilesBufsiz = 128;
int   NumSelected = 0;

int NumMarked = 0;
char* MarkedFileItems_sz_p = NULL;
int NeedToRedefineMenu = 0;

//const int Cols[] = {150,  42,   64,   84,  74,  0};
int ColWidths[] = {200,  80,   64,   100,  100,  0};

int DirKBytes = 0;
int DirFiles = 0;

int FileDragging = 0;
//int BatchReplaceMode = BATCH_ASK_EACH;  // how to handle batch copy/move ops

int ConfirmDelete = 0;
int ShowAllFiles = 0;
int AllowUseSudo = 1;
int ShowingDiskUsage = 0;

// Sorting order specifications
enum {  SORTSPEC_NAME_ASC = 0, SORTSPEC_NAME_DESC, 
        SORTSPEC_TYPE_ASC,     SORTSPEC_TYPE_DESC,
        SORTSPEC_SIZE_ASC,     SORTSPEC_SIZE_DESC,
        SORTSPEC_DATE_ASC,     SORTSPEC_DATE_DESC
     };

int SortSpec = SORTSPEC_NAME_ASC;

int ConfigSaved = 0;
// Initial size and location hard-coded, 
// but later size and loc recorded in .fluff.conf for recall
//int MainXPos =  15;
//int MainYPos =  40;
int MainWide = 640;
int MainHigh = 420;

int DirWatch_fd;            // file descriptor for inotify
int DirWatch_wd;            // watch descriptors
#define WATCHBUFLEN 1024
char WatchEventBuf[1024];

int LastSel = -1;
int LastAssoc = -99;
char SelFilename[MAXFNAMELEN];
char SelDir[MAXFFULLPATHLEN];

char HomeDir[MAXFFULLPATHLEN];
char StartDir[MAXFFULLPATHLEN];
char TceDir[MAXFFULLPATHLEN];

double TimeToResync_rsec = -1.0;

char* strclr(char* buf) { buf[0] = (char)0; return buf;}

char* strnzcpy(char* dstr, const char* sstr, size_t buflen) {
    int n = buflen - 1;
    if (n < 0) n = 0;
    char* ret = strncpy(dstr, sstr, n);
    dstr[n] = '\0';
    return ret;
} 

// ---- FUNCTION PROTOTYPES ---------------
void populate_file_list(int reselect = 0);
void redefine_menu(void);
void mark_file_items(void);
void do_cmd(void);
void change_dir(const char* path);
double systemtime_as_real(void);
void reselect_items(void);

// ---- CODE ---------------------
void Fl_DND_Box::callback_deferred(void *v)
{
    Fl_DND_Box *w = (Fl_DND_Box*)v;

    w->do_callback();
}

Fl_DND_Box::Fl_DND_Box(int X, int Y, int W, int H, const char *L)
        : Fl_Box(X,Y,W,H,L), evt(FL_NO_EVENT), evt_txt(0), evt_len(0)
{
    labeltype(FL_NO_LABEL);
    box(FL_NO_BOX);
    clear_visible_focus();
}

Fl_DND_Box::~Fl_DND_Box()
{
    delete [] evt_txt;
}

int Fl_DND_Box::event()
{
    return evt;
}

const char* Fl_DND_Box::event_text()
{
    return evt_txt;
}

int Fl_DND_Box::event_length()
{
    return evt_len;
}


int Fl_DND_Box::handle(int e)
{
    //printf("Fl_DND_Box event %d\n", e); fflush(0);
    //printf("dnd: %s at t=%1.3f, e=%d\n", __func__, systemtime_as_real(), e);			
    switch(e)
    {

        /* Receiving Drag and Drop */
        case FL_DND_ENTER:
        case FL_DND_RELEASE:
        case FL_DND_LEAVE:
            evt = e;
            return 1;
            
        case FL_DND_DRAG:
            evt = e;
            //printf("DND Drag event... mouse y =%d\n", Fl::event_y());
            do_callback();
            return 1;

        case FL_PASTE:
            evt = e;

            // make a copy of the DND payload
            evt_len = Fl::event_length() + 8;

            delete [] evt_txt;

            evt_txt = new char[evt_len];
            strnzcpy(evt_txt, Fl::event_text(), evt_len);

            // If there is a callback registered, call it.
            // The callback must access Fl::event_text() to
            // get the string or file path(s) that was dropped.
            // Note that do_callback() is not called directly.
            // Instead it will be executed by the FLTK main-loop
            // once we have finished handling the DND event.
            // This allows caller to popup a window or change widget focus.
            if(callback() && ((when() & FL_WHEN_RELEASE) || (when() & FL_WHEN_CHANGED)))
                Fl::add_timeout(0.0, Fl_DND_Box::callback_deferred, (void*)this);
            return 1;
    }

    return Fl_Box::handle(e);
}

File_Detail_List_Browser::File_Detail_List_Browser(int x, int y, int w, int h, const char* label_text) :
    Fl_Multi_Browser(x, y, w, h, label_text), dragging(0), partitioning(0), list_item_height(16) 
{
}

void File_Detail_List_Browser::draw() 
{
	// Adjust column widths
	// int ColWidths[] = {200,  80,   64,   100,  100,  0};
	
    //printf("%s at t=%1.3f\n", __func__, systemtime_as_real());			
	int extraspace = w() - 64 - 64 - 100 - 100 - 100;
	ColWidths[0] = (extraspace * 8) / 10;		// 80%
	if (ColWidths[0] < 100)   ColWidths[0] = 100;
	ColWidths[1] = 64 + (extraspace * 2) / 20;	// 15% + 40
	ColWidths[2] = 64 + (extraspace / 20);		// 5%  + 40
	column_widths(ColWidths);
	
    // Determine where to put sort direction symbol
    int sortcol  = SortSpec >> 1;
    int sortdown = SortSpec & 1; // down=descending order (down arrow)
    
    // Draw generic stuff
    Fl_Multi_Browser::draw();
    int X,Y,W,H,colx;
    Fl_Browser::bbox(X,Y,W,H);
    fl_color(42);
    colx = this->x() - hposition();

    // DRAW COLUMN SEPARATORS
    colx = this->x() - hposition();
    Fl_Browser::bbox(X,Y,W,H);
    int line_h = partitioning ?  (H - 1) : (textsize());
    for ( int t=0; ColWidths[t]; t++ ) {
        colx += ColWidths[t];
        if ( colx > X && colx < (X+W) && line_h > position() ) {
            fl_color(42);
            Y -= position();
            fl_line(colx, Y, colx, Y+line_h);
            if (t == sortcol) {
                fl_color(40);
                int mx = colx - ColWidths[t] + textsize();
                int my = Y - position();
                if (sortdown) {
                    my += textsize();
                    fl_polygon(mx - 12, my - 8, mx - 7, my - 3, mx - 2, my - 8);
                }
                else {
                    fl_polygon(mx - 12, my + 8, mx - 7, my + 3, mx - 2, my + 8);
                } 
            } 
        }
    }
}

int column_in_filelist(int x) 
{
    int col = -1;
    int c = 0;
    int start_x = 0;
    int end_x = 0;
    while (ColWidths[c] && col < 0 ) {
        end_x += ColWidths[c];
        if (x >= start_x   && x < end_x) {
            col = c;
        }
        c++;
        start_x += ColWidths[c];
    } 
    if (col < 0 && x > start_x) {
        col = c;
    }
    
    return col;
}

void File_Detail_List_Browser::record_item_height(void) {
	void* bi_p = item_first(); // browser item ptr.
	if (bi_p)  list_item_height = item_height(bi_p);
}

int File_Detail_List_Browser::handle(int e) {
    int ret = 0;
    File_Detail_List_Browser* p = UI_p->file_list_p;
    int ex = Fl::event_x();
    int ey = Fl::event_y();
    int wgt_x = ex - 2 - File_Detail_List_Browser::x() 
                + File_Detail_List_Browser::hposition();   // x within scrolling widget
    int wgt_y = ey - 2 - File_Detail_List_Browser::y() 
                + File_Detail_List_Browser::position();    // y within scrolling widget
    int event_row = 1 + (wgt_y) / list_item_height;
    int event_column = column_in_filelist(wgt_x);
	//printf("item height %d, sbar width %d; ex %d, ey %d; wgt_x %d, wgt_y %d; erow %d, ecol %d\n", 
	//       list_item_height, p->scrollbar_width(), ex, ey, wgt_x, wgt_y, event_row, event_column); fflush(0);
    
    //printf("File_Detail_List_Browser event %d, column %d, last click %d\n", 
    //        e, event_column, MainWnd_p->last_click_browser); fflush(0);
    //printf("%s at t=%1.3f, e=%d\n", __func__, systemtime_as_real(), e);			
    switch ( e ) {
        case FL_KEYDOWN:
            if (Fl::event_key() == FL_Escape) {
                populate_file_list();
                return 1;
            }
            break;
            
        case FL_FOCUS:
        case FL_UNFOCUS:
            redraw();
            return 1;

        case FL_PUSH:
        {
            //printf("PUSH event in file detail list, row %d\n", event_row); fflush(0); 
            dragx = ex;
            dragy = ey;
            
            //printf("dragx %d, lb_y %d, scroll pos %d, height %d\n", 
            //        dragy, p->y(), p->position(), height); fflush(0);
            int vscrollx = p->x() + p->w() - p->scrollbar_width();
            int hscrolly = p->y() + p->h() - p->scrollbar_width();
			if ((event_row != 1) && ((dragy > hscrolly) || (dragx > vscrollx))) {
                //return Fl_Multi_Browser::handle(e); // on a scroll bar
                goto do_default;
            }
            
            if (Fl::event_button() == FL_RIGHT_MOUSE ) {
                // Note: right-click not handled?  Pop open the main menu?
                UI_p->menu_p->popup();
                return 1;
            }
             
            if (Fl::event_button() == FL_LEFT_MOUSE) {
//                if (p->value() == 1) {
//                    return 1;
//                }
                if (Fl::event_state() & (FL_SHIFT | FL_CTRL)) {
                    //return Fl_Multi_Browser::handle(e);
                    goto do_default;
                }
                if (Fl::event_clicks() == 0) {
                    if (event_column == 0) {
                        return 1;
                    }
                    else {
                        //return Fl_Multi_Browser::handle(e);
                        goto do_default;
                    }
                }                
                else if (Fl::event_button() == FL_LEFT_MOUSE && Fl::event_clicks() == 1) {
					//do_cmd();   // double-click... just what was loaded on the first click
//					do_callback();
				/*	
                    // Double-click... do run command or change dirs
                    if (p) {
                        MainWnd_p->sel_fi_p = (file_item*)(p->data(p->value()));
                        btnbar_action1_cb();
                    } */
                    return 1;          // (tells caller we handled this event)
                } 
            }

            else {
                //ret = Fl_Multi_Browser::handle(e);
                //return(ret);
                goto do_default;          
            }
        }
            break;
            
        case FL_MOVE:       // FL_MOVE event means no buttons are held right now,
            dragging = 0;   // so abort any unfinished drag-and-drop
            break;
            
        case FL_DRAG:
            dragging = 1;

            if (event_column > 0) {
                // Not dragging from name, handle as sweep-selection
                //return Fl_Multi_Browser::handle(e);
                goto do_default;
            }
            
            if (abs(ex - dragx) < 8 && abs(ey - dragy) < 4 ) {
                // Didn't move far, ignore?
                return 1;
            }
                        
            if (!p->selected(event_row)) {
                return 1; // can't drag unselected item... maybe this row will be selected later
            }
            
            mark_file_items();
            //printf( "Initializing drag and drop with the following item(s):\n%s\n", 
            //       MarkedFileItems_sz_p);
            
            // Copy the file path(s) to the selection buffer
            Fl::copy(MarkedFileItems_sz_p, strlen(MarkedFileItems_sz_p)+1, 0);

            // Deactivate the path and cmd input fields so the auto-focus
            // behavior does not reset the cursor
            UI_p->cmd_inp_p->deactivate();
            UI_p->path_inp_p->deactivate();

            // initiate the DnD magic.
            // At this point FLTK's system wide DnD kicks in and we lose control
            Fl::dnd();

            // Reactivate the path and cmd input fields
            UI_p->cmd_inp_p->activate();
            UI_p->path_inp_p->activate();
            return 1;
            //break;

        case FL_RELEASE:
            if (Fl::event_button() == FL_RIGHT_MOUSE ) {
                do_callback();
                return 1;
            }
            if (Fl::event_button() == FL_LEFT_MOUSE ) {
                //printf("left click release ...\n"); fflush(0);
                if (dragging) {
                    //printf("end drag-n-dropping...\n"); fflush(0);
                    dragging = 0;
                }

                if (event_row == 1) {
                    //printf("change sort order\n"); fflush(0);
                    int cur_sort_col = SortSpec >> 1;
                    int descending = SortSpec & 1;
                    if (event_column >= 0 && event_column <= 3) {
						if (event_column == 1)   event_column = 0;	// sorting by type not working lin busybox's ls
                        if (event_column == cur_sort_col) {
                            SortSpec ^= 1; // toggle direction
                        }
                        else {
                            SortSpec = (event_column << 1) | descending; // change sort param, keep current direction
                        }
                        populate_file_list(RESELECT);                        
                    }
                    else {
                        p->select(1, 0);
                    }
                    return 1;
                }

                if (Fl::event_state() & (FL_SHIFT | FL_CTRL)) {
                    //ret = Fl_Multi_Browser::handle(e);
                    goto do_default;
                }                    
/*                else if (event_column > 0) {
                    ret = Fl_Multi_Browser::handle(e);
                    MainWnd_p->sel_fi_p = (file_item*)p->data(p->value());
                } */
                else if (event_row > 1 && event_row <= p->size()) {
                    //printf("single-select row %d\n", event_row);
                    p->deselect();
                    p->select(event_row);
                    p->redraw();
					do_callback();
                    ret = 1;
                }
                return ret;          
                goto do_default;
            }
            break;

        case FL_MOUSEWHEEL:
			//return Fl_Multi_Browser::handle(e);
            goto do_default;
            break;

        case FL_SHOW:
            return 1;
            
        default:
do_default:
            ret = Fl_Multi_Browser::handle(e);
            break;

    }
    return(ret);
}

void init_dir_watching(void) {
    DirWatch_fd = inotify_init();
    if (-1 == DirWatch_fd) {
        //IgnoreNextKey = 1;
        fl_alert("Directory monitoring capability missing!");
    }
}

void close_dir_watching(void) {
    close(DirWatch_fd);
}

const unsigned int INOTIFY_WATCH_FLAGS = IN_ATTRIB | IN_MODIFY | \
IN_CREATE | IN_DELETE | IN_DELETE_SELF | IN_MOVE_SELF | IN_MOVE;

const unsigned int INOTIFY_EVENT_FLAGS  = INOTIFY_WATCH_FLAGS | IN_UNMOUNT;

void setup_dir_watch(const char* dir_path) {
    if (!dir_path || !*dir_path) {
        //printf("NULL directory item does not require a watch!\n");
        return;
    }
    
    // Don't watch /dev... can cause problems
    if (!strncmp(dir_path, "/dev", 4)) {
        return;
    }
    
    DirWatch_wd = inotify_add_watch(DirWatch_fd, dir_path, 
                                    INOTIFY_WATCH_FLAGS);
    if (-1 == DirWatch_wd) {
        perror("inotify_add_watch");
        printf("Failed to add watch for '%s'\n", dir_path);
        return;
    }
    //printf("Now watching dir '%s'\n", dir_path);
}

void remove_dir_watch(void) {
    int w, ret;
    ret = inotify_rm_watch(DirWatch_fd, DirWatch_wd);
    if (ret) {
        perror("inotify_rm_watch");
    }
    else {
        DirWatch_wd = -1;
    }
    fflush(0);
}


int dir_watch_events_pending_len(void) {
    int ret;
    unsigned int len;
    ret = ioctl(DirWatch_fd, FIONREAD, &len);
    if (ret >= 0 && len > 0) return len;
    else return 0;
}

void react_to_dir_watch_events(void) {
    ssize_t len = -1; 
    ssize_t i = 0;
    int need_to_sync = 0;
    double resync_delay = 1.0;
    
    //printf("%s at t=%1.3f\n", __func__, systemtime_as_real());			
    //printf("%s dir watched. %d bytes of event info pending\n", 
    //        SelDir, dir_watch_events_pending_len());
    while (dir_watch_events_pending_len()) {
        len = read(DirWatch_fd, WatchEventBuf, WATCHBUFLEN);
        //printf("Pending watch event data length is %d\n", len); fflush(0);
        while (i < len) {
            struct inotify_event* event_p =
                (struct inotify_event*) &WatchEventBuf[i];
            // is this an event type we care about?
            if (INOTIFY_EVENT_FLAGS & event_p->mask) {
/*                printf("Filesystem event: wd=%d, mask=0x%08X, cookie=%d, len=%d, isdir=%s",
                        event_p->wd, event_p->mask, event_p->cookie,
                        event_p->len, (event_p->mask & IN_ISDIR) ? "yes":"no"); fflush(0);
                if (event_p->len) printf(", name='%s'\n", event_p->name);
                else              printf("\n"); */
				need_to_sync = 1;
            }
            i += sizeof(struct inotify_event) + event_p->len;
        }
    }
    //printf("Events handled. %d bytes of event info pending.  Need to sync? %d\n", 
    //        dir_watch_events_pending_len(), need_to_sync); fflush(0);
    double now = systemtime_as_real();
    if (need_to_sync)  {
        if (now > TimeToResync_rsec) {
            TimeToResync_rsec = now + resync_delay;
        }
    }
	//printf("t now is %1.3lf, rsync at %1.3lf (or in %1.3lf seconds)\n", 
	//       now, TimeToResync_rsec, (TimeToResync_rsec - now));
}


int marked_file_items(const char* urls) 
{
    if (!urls) return 0;
    int cnt = 0;
    for(const char *c = urls; *c != '\0'; ++c)
    {
        if(*c == '\n')
            cnt++;
    }
    return cnt;
}

void wait_cursor(void)
{
    fl_cursor(FL_CURSOR_WAIT, FL_BLACK, FL_WHITE);
    Fl::check();
}

void normal_cursor(void)
{
    fl_cursor(FL_CURSOR_DEFAULT, FL_BLACK, FL_WHITE);
    Fl::wait(0.01);
}


void perform_file_copy_or_move(fileop_enum oper, const char* dirstr)
{
    //printf("%s at t=%1.3f\n", __func__, systemtime_as_real());			
    int i, buflen; //, fplen;        
    int t = 0;
    buflen = 0;
    int err = 0;
//    BatchReplaceMode = BATCH_ASK_EACH;
    //wait_cursor();
/*    const char* oper_str;
    switch(oper) {
        case MOVE_OPER:   
            oper_str = "perhaps moved";  
            break;
        case COPY_OPER: 
            oper_str = "perhaps copied"; 
            break;
    } */
	int cnt = marked_file_items(MarkedFileItems_sz_p);
	char fullpath[MAXFFULLPATHLEN];
	strclr(fullpath);
	
	char* c = MarkedFileItems_sz_p;
	while(*c && t < cnt) {
		char* f = fullpath;
		while(*c && strncmp(c, "file://", 7)) c++;
		c += 7;
		while(*c && *c != '\n') {
			*f++ = *c++;
		}
		*f = '\0';
		c++;
		char cmdstr[1024] = {0};
		sprintf(cmdstr, "%s \"%s\" \"%s\"",
				(oper == COPY_OPER) ? "cp -fpR" : "mv -f", 
				fullpath, dirstr);
		//printf("Trying cmd '%s'\n", cmdstr); fflush(0);
		err = system(cmdstr);
		if (err) {
			fl_alert("Command '%s' not successful", cmdstr);
			return;
		}
		t++;
	}
            
    //normal_cursor();
    if (err) {
        fl_alert("There were errors during the %s command", 
                 (oper == MOVE_OPER) ? "move" : "paste");
    }
}

void dnd_receive(const char *urls)
{
    //printf("%s at t=%1.3f\n", __func__, systemtime_as_real());			
    char dirstr[MAXFPATHLEN];
    getcwd(dirstr, MAXFPATHLEN);
	
    int cnt = marked_file_items(urls);
    
    //BatchReplaceMode = BATCH_ASK_EACH;
    //IgnoreNextKey = 1; // fl_choice() doesn't seem to eat these, so ignore the key event that comes through
    int answer = fl_choice( "You dropped %d file%s on %s.\nWould you like to...", 
                            "cancel", "move to here", "copy to here", cnt, 
                            (cnt == 1) ? "" : "s", dirstr);
    if (answer == 0) {
        return;
    }
    else {
        free(MarkedFileItems_sz_p);
        MarkedFileItems_sz_p = (char*)urls;
        perform_file_copy_or_move((answer == 2) ? COPY_OPER : MOVE_OPER, dirstr); 
        MarkedFileItems_sz_p = 0;
    }
    
    FileDragging = 0;
    populate_file_list(RESELECT);
}


void dnd_cb(Fl_Widget *o, void *v)
{
    Fl_DND_Box *dnd = (Fl_DND_Box*)o;

    //printf("%s at t=%1.3f\n", __func__, systemtime_as_real());			
    switch(dnd->event())
    {
        case FL_DND_DRAG:           // User is dragging widget
            {
                //MainWnd_p->list_p->deselect();
            }
            break;

        case FL_PASTE:          // DND Drop
        {
            //if (MainWnd_p->seldir_fi_p) {
                //printf("directory '%s' will try to receive:\n%s\n------\n",
                //        MainWnd_p->seldir_fi_p->fullpath, dnd->event_text()); fflush(0);
                dnd_receive(dnd->event_text());
            //}
            //break;
        }
    }
}


double systemtime_as_real(void)
{
    static struct timeval now;
    static double now_rsec = 0.0;
    if (gettimeofday(&now, NULL)) {
        perror("gettimeofday");
        return 0.0l;
    }
    now_rsec = ((double)now.tv_sec) + (((double)now.tv_usec) / 1000000.0);
    return now_rsec;
}

void strip_link_info(char* filename)
{
    char* p = strstr(filename, " -> "); 
    if (p)  *p = '\0';
}

int select_association(char type, char* fname) 
{
	switch(type) {
		case 'd':	return FT_DIR;		break;
		case 's':	return FT_SOCKET;	break;
		case 'p':	return FT_FIFO;		break;
		case 'b':	return FT_BLOCKDEV;	break;
		case 'c':	return FT_CHARDEV;	break;
        case 'l':   {
                if (fname[strlen(fname) - 1] == '/') {
                    return FT_DIR;
                }
            }
            break;
		default:
			break;
	}
    char filename[MAXFNAMELEN];
    strcpy(filename, fname);
    strip_link_info(filename);
    
	int imfd = open(filename, O_RDONLY);
	if (-1 == imfd) {
		return FT_TEXT;
	}
	
	// Peek inside file for a few important types
	char imbyte[32];
	if (-1 == read(imfd, imbyte, 32)) {
		close(imfd);
		return FT_TEXT;
	}
	close(imfd);
	if (!strncmp(imbyte + 1, "ELF", 3)) {
		return FT_EXEC;
	}
	if (!strncmp(imbyte + 0, "#!", 2)) {
		// Check for a "script" association
		for (int a = FirstConfigrableAssociation; a < Associations; a++) {
			if (!strcmp(Association[a].typelabel, "script")) {
				return a;  // found one... return it
			}
		}
  		return FT_SHELLSCRIPT;  // otherwise, return special code
	}
	
	// Check filename extension for assocciated application
	for (int a = 0; a < Associations; a++) {
		struct association* assoc_p = &Association[a];
		int patlen = strlen(assoc_p->pat);
		//printf("checking extension '%s' for pattern '%s'...", name + strlen(name) + 1 - patlen, fth_p->pat);
		if (!strncmp(filename + strlen(filename) - patlen, assoc_p->pat, patlen)) 
		{
            return a;
		}
	}
    return FT_TEXT; // should not get here
}

Fl_Color faded(Fl_Color& clr, float amount)
{
    uchar r, g, b;
    Fl::get_color(clr, r, g, b);
    //float avg = (float)(r + g + b) / 3.0;
    
    r += (int)(amount*(150 - r));
    g += (int)(amount*(150 - g));
    b += (int)(amount*(150 - b));
    return fl_rgb_color(r, g, b);
}

void configure_colors(void) 
{
    uchar r, g, b;

    Fl_Color fg = FL_FOREGROUND_COLOR;  // text
    Fl::get_color(fg, r, g, b);
    //printf("Forground color r %d, g %d, b%d\n", r, g, b);
    //Fl::get_color(NORMTEXT_COLOR, r, g, b);
    //printf("Forground color r %d, g %d, b%d\n", r, g, b);
    Fl::set_color(NORMTEXT_COLOR, r, g, b);
//    Fl::set_color(NORMTEXT_COLOR, 512-(g+b), 512-(r+b), 512-(r+g));
    
    Fl_Color fg2 = faded(fg, 0.8);
    Fl::get_color(fg2, r, g, b);
    //printf("Fg2 color components are %d, %d, %d\n", r, g, b);
    Fl::set_color(HIDETEXT_COLOR, r, g, b);

    Fl_Color bg = FL_BACKGROUND2_COLOR;  // list background
    Fl::get_color(bg, r, g, b);
    //printf("Bg color components are %d, %d, %d\n", r, g, b);
    Fl::set_color(LISTBG_COLOR, r, g, b);


    
    Fl_Color bg2 = faded(bg, 0.20);
    Fl::get_color(bg2, r, g, b);
    //printf("Bg2 color components are %d, %d, %d\n", r, g, b);
    Fl::set_color(LISTBG2_COLOR, r, g, b);
//    Fl::set_color(NORMTEXT_COLOR, 512-(g+b), 512-(r+b), 512-(r+g));
}


void save_configuration(void)
{
    if (ConfigSaved) {
        return;
    }
    char configfilename[MAXFFULLPATHLEN];
    sprintf(configfilename, "%s/.dirwin.conf", HomeDir);
    FILE* cfgf = NULL;
    
    cfgf = fopen(configfilename, "w");
    if (cfgf) {
        fprintf(cfgf, "# DirWin file utility configuration file\n");
        time_t now = time(NULL);
        fprintf(cfgf, "# Auto-written %s\n", ctime(&now));
        fprintf(cfgf, "# NOTE: if you hand-edit, please take care to spell\n");
        fprintf(cfgf, "#       and structure configuration statements carefully!\n");
        fprintf(cfgf, "\n");
        fflush(0);
        
        fprintf(cfgf, "# Main Window placement and size\n");
        fprintf(cfgf, "MainWide = %d\n", UI_p->MainWnd_p->w());
        fprintf(cfgf, "MainHigh = %d\n", UI_p->MainWnd_p->h());
        fprintf(cfgf, "\n");
        fflush(0);

        fprintf(cfgf, "# Options\n");
        fprintf(cfgf, "ConfirmDelete = %d\n", ConfirmDelete);
        fprintf(cfgf, "ShowAllFiles = %d\n", 1); //UI_p->MainWnd_p->showingall);

        fprintf(cfgf, "SortOrder = %d\n", SortSpec);
        fprintf(cfgf, "# Sorting: 0=name+asc, 1=name+desc, 2=size+asc, 3=size+desc\n");
        fprintf(cfgf, "#          4=date+asc, 5=date+desc\n");
        fprintf(cfgf, "\n");
        fflush(0);
                
        fprintf(cfgf, "# Filetype Application Associations\n");
        fprintf(cfgf, "Associations:\n");
        fprintf(cfgf, "# (extension or *), (type label), (action label), (cmd spec)\n");
        fprintf(cfgf, "# example..\n");
        fprintf(cfgf, "# .jpg,image,view,flpicsee %%s &\n");
        fprintf(cfgf, "# Make sure there is at least one 'script' association below!\n");
        int assoc = 0;

        for(assoc = FirstConfigrableAssociation ; assoc < Associations; assoc++) {
            fprintf(cfgf, "%s,%s,%s,%s\n", 
                    Association[assoc].pat, 
                    Association[assoc].typelabel,                     
                    Association[assoc].cmdlabel, 
                    Association[assoc].cmdspec);
        }
        
    }
    fclose(cfgf);
    ConfigSaved = 1;
}


// copies at most (lim - 1) characters, but quits if it hits delimiter
// returns a pointer to the first character after the delimiter
char* copy_field_limited_and_delimited(char* dest, const char* src, int lim, char delimiter)
{
    int n = 0;
    const char* p = src;
    while (*p && (*p != delimiter) && (n <  lim - 1)) {
        dest[n] = *p++;
        n++;
    }
    dest[n] = '\0';
    while (*p && (*p != ','))   p++;    // skip any extra chars
    if (*p == ',')   p++;               // skip the delimiter comma
    return (char*)p;
}

void consider_association(char* confline) {
    char buf[256];
    char typelabel[256];
    
    char* p = confline;
    char* b = typelabel;
    
    if (confline[0] == '#') {
        //printf("Skipping conf. line %s", confline); fflush(0);
        return;
    }
    if (confline[strlen(confline) - 1] == '\n') {
        confline[strlen(confline) - 1] = '\0';  // trim off newline
    }
    p = copy_field_limited_and_delimited(Association[Associations].pat, 
                                         p, MAXPATLEN, ',');
    p = copy_field_limited_and_delimited(Association[Associations].typelabel, 
                                         p, MAXTLBLLEN, ',');
    p = copy_field_limited_and_delimited(Association[Associations].cmdlabel, 
                                         p, MAXALBLLEN, ',');
    p = copy_field_limited_and_delimited(Association[Associations].cmdspec, 
                                         p, MAXCMDSPECLEN, '\0');
    if (    strlen(Association[Associations].pat) 
        &&  strlen(Association[Associations].typelabel)
    ) {
        Associations++;
    }    
}

void load_configuration(void)
{
    char configfilename[MAXFNAMELEN];
    char confline[256];
    char strbuf[MAXFPATHLEN];
    char* sp = strbuf;
    char* env_home_p = getenv("HOME");
    sprintf(configfilename, "%s/.dirwin.conf", env_home_p);
    FILE* cfgf = NULL;
    int n;
    
    cfgf = fopen(configfilename, "r");
    if (cfgf) {
        while (fgets(confline, 255, cfgf)) {
            if (confline[0] == '#' || confline[0] == '\n') {
                continue;   // skip lines marked with # as comments
            }
            else if (1 == sscanf(confline, "MainWide = %d\n", &n)) {
                MainWide = n;  continue;
            }
            else if (1 == sscanf(confline, "MainHigh = %d\n", &n)) {
                MainHigh = n;  continue;
            }
            else if (1 == sscanf(confline, "ConfirmDelete = %d\n", &n)) {
                ConfirmDelete = n;  continue;
            }
            else if (1 == sscanf(confline, "ShowAllFiles = %d\n", &n)) {
                ShowAllFiles = n;  continue;
            }
            else if (1 == sscanf(confline, "SortOrder = %d\n", &n)) {
                SortSpec = n;  continue;
            }
            else if (!strncmp(confline, "Associations:", strlen("Associations:"))) {
                //printf("Configuring file associations now...\n"); fflush(0);
                Associations = FirstConfigrableAssociation; 
            
                while (fgets(confline, 255, cfgf)) {
                    consider_association(confline);
                }
            }
            else {
                printf("Don't know how to process '%s'...\n", confline); fflush(0);
            }
        } // while there is more conf. file            
    } // end if (cfgf)
}


int num_selected(void) {
	File_Detail_List_Browser* list_p = UI_p->file_list_p;
    int num = 0;
    int i;
    for (i = 1; i <= list_p->size(); i++) {
        if (list_p->selected(i)) {
            num++;
        }
    }
    return num;
}

int WaitingForDU = 0;
char stats_str[128];
void update_stats(void)
{
    //printf("%s at t=%1.3f\n", __func__, systemtime_as_real());			
	char num[16];
	sprintf(stats_str, "%d files  %d KB", DirFiles, DirKBytes);
	int selcount = num_selected();
	if (selcount > 0) {
		strcat(stats_str, "  |  ");
		sprintf(num, "%d", selcount);
		strcat(stats_str, num);
		strcat(stats_str, " selected  ");
	}
	UI_p->file_list_p->label(stats_str);
    int wide = 0, high = 0;
    fl_measure(stats_str, wide, high);
    int y = UI_p->file_list_p->y() - high - 4;
    int x = UI_p->file_list_p->x() + wide + 8;
    UI_p->selsize_btn_p->position(x, y);
}

void populate_file_list(int reselect)
{
    //printf("%s at t=%1.3f\n", __func__, systemtime_as_real());			
	File_Detail_List_Browser* list_p = UI_p->file_list_p;
	list_p->column_widths(ColWidths);
	char cmd[64];
	char buf[512];
	int n;
    int kbytes = -1;
    
    
	
	list_p->clear();

    sprintf(buf,    "%s%s@.  File\t"
                    "%s%s@.Type\t"
                    "%s%s@.  Size\t"
                    "%s%s@.  Date\t"
                    "%s%s@.Permissions\t"
                    "%s%s@.Owner:Group\t",
                    BtnColor_str, TextColor_str, 
                    BtnColor_str, TextColor_str, 
                    BtnColor_str, TextColor_str, 
                    BtnColor_str, TextColor_str, 
                    BtnColor_str, TextColor_str, 
                    BtnColor_str, TextColor_str 
                    );
	list_p->add(buf);
	list_p->record_item_height();

/*  ls command options...
            -1      List in a single column
            -A      Don't list . and ..
            -a      Don't hide entries starting with .
            -C      List by columns
            -c      With -l: sort by ctime
            --color[={always,never,auto}]   Control coloring
            -d      List directory entries instead of contents
            -e      List full date and time
            -F      Append indicator (one of * / = @ | ) to entries
            -i      List inode numbers
            -l      Long listing format
            -n      List numeric UIDs and GIDs instead of names
            -p      Append indicator (one of /=@|) to entries
            -L      List entries pointed to by symlinks
            -R      List subdirectories recursively
            -r      Sort in reverse order
            -S      Sort by file size
            -s      List the size of each file, in blocks
            -T NUM  Assume tabstop every NUM columns
            -t      With -l: sort by modification time
            -u      With -l: sort by access time
            -v      Sort by version
            -w NUM  Assume the terminal is NUM columns wide
            -x      List by lines
            -X      Sort by extension
            -h      List sizes in human readable format (1K 243M 2G)
 */    
    int reverse = SortSpec & 0x01;
    if ((SortSpec & 0x06) != SORTSPEC_NAME_ASC) {
		reverse = !reverse;
	}
	
    sprintf(cmd, "ls -p%s%s%s%sl", 
			ShowAllFiles?"a":"", 
			((SortSpec & 0x06) == SORTSPEC_SIZE_ASC) ? "S":"h",
			((SortSpec & 0x06) == SORTSPEC_DATE_ASC) ? "t":"",
			reverse ?"r":"");
	//printf("files list for sort spec %d: %s\n", SortSpec, cmd);
    
    FILE* ls_fs = popen(cmd, "r");
    if (!ls_fs) {
        fl_alert("Cannot browse the directory\n");
        return;
    }

	DirFiles = 0;
    int links = 0;
    
    char permissions[12];
    char owner[MAXNINAME];
    char group[MAXNINAME];
    char size[32];
    char month[16];
    int  day;
    char year_or_time[16];
    char fname[MAXFNAMELEN];
    
    while (fgets(buf, 512, ls_fs)) {
        if (1 == sscanf(buf, "total %d", &n)) {
            DirKBytes = n;
        }
        //duuugggooo   (hard link count) owner group    size  modification_date  name

        else if (9 == sscanf(buf, "%s %d %s %s %s %s %d %s %[^\n]",
                                   permissions, &links, 
                                   owner, group, 
                                   size, month, &day, year_or_time,
                                   fname )) 
        {
                int assoc = select_association(permissions[0], fname);
                const char* typelabel = "";
                if (assoc >= 0) {
					typelabel = Association[assoc].typelabel;
				}
				sprintf(buf, "@C16@.%s\t@C16@.%s\t@C16@.%s\t@C16@.%s %d %s\t@C16@.%s\t@C16@.%s:%s\n", 
						fname, typelabel, size, month, day, year_or_time, 
                        permissions, owner, group);
				list_p->add(buf);
				list_p->data(list_p->size(), (void*)assoc);
				DirFiles++;
        }
    }
    fclose(ls_fs);
    update_stats();
    if (reselect)   reselect_items();
}

void present_selected_file_size(void)
{
    char* cmd = (char*)malloc(strlen(SelectedFiles) + 128);
    //sprintf(cmd, "rm -f /tmp/selsize.txt /tmp/du.txt; du -ck %s > /tmp/du.txt; tail -1 /tmp/du.txt > /tmp/size.txt &", SelectedFiles);
    sprintf(cmd, "du -ck $sel_items | tail -1");
    //printf("cmd (0x%08X) is '%s'\n", cmd, cmd);
    //system(cmd);
    int selsize;
    char selsizestr[64] = {'\0'};
    strcpy(selsizestr, "(Size not available)");
    char name[MAXFNAMELEN];
    char line[MAXFNAMELEN];
    FILE* du_fp = popen(cmd, "r");
    int selcount = num_selected();
    while (fgets(line, MAXFNAMELEN, du_fp)) {
        //printf("du line: %s\n", line);
        int scanned = sscanf(line, "%d %s", &selsize, name);
        //printf("Scanned: %d items, number %d and string %s\n", scanned, selsize, name);
        if ((2 == scanned) &&  !strcmp(name, "total")) {
            sprintf(selsizestr, "%d KB\n", selsize);
        }
    } 
    free(cmd);
    if (selcount > 0) {
        sprintf(line, "%d item%s occupying %s", selcount, (selcount==1)?"":"s", selsizestr);
    }
    else {
        sprintf(line, "This directory occupies %s", selsizestr);
    }
    fl_message(line);
}

void reselect_items(void) 
{
    //printf("%s at t=%1.3f\n", __func__, systemtime_as_real());			
	File_Detail_List_Browser* list_p = UI_p->file_list_p;
	char filename[MAXFNAMELEN];
	//printf("Previously selected items: %s\n", SelectedFiles);
	for(int i = 2; i <= list_p->size(); i++) {
		copy_field_limited_and_delimited(filename, list_p->text(i)+6, MAXFNAMELEN, '\t');
		strip_link_info(filename);
		//printf("Reselect '%s'?  ", filename);
		char* p = SelectedFiles;
		while (p && *p) {
            //printf("Is '%s' in selected files: %s?\n", filename, p); fflush(0);
			p = strstr(p, filename);
			if (p && (*(p - 1) == '\'') && (*(p + strlen(filename)) == '\'')) {
				list_p->select(i);
				//printf("Yes.");
				break;
			}
            else if (p) {
                p += strlen(filename);
            }
		}
		//printf("\n");
	}
}

int LastPathlen = 0;

void show_path(void)
{
    //printf("%s at t=%1.3f\n", __func__, systemtime_as_real());			
    char dirstr[MAXFPATHLEN + 10];
    getcwd(dirstr, MAXFPATHLEN);
    //printf("Current working directory is '%s'\n", dirstr);
    UI_p->path_inp_p->when(0);
    LastPathlen = strlen(dirstr);

    UI_p->path_inp_p->replace(0, 9999, dirstr, LastPathlen);
    UI_p->path_inp_p->when(FL_WHEN_CHANGED);
    strcat(dirstr, " - DirWin");
    UI_p->MainWnd_p->label(dirstr);
}

void do_cmd(void)
{
    int ret = system(UI_p->cmd_inp_p->value());
    if (ret) {
        fl_alert("Command returned %d, errno %d\n", ret, errno);
    }
}
int exists(const char* fullpath)
{
    //printf("%s at t=%1.3f\n", __func__, systemtime_as_real());			
    struct stat64 status;
    int ret = stat64(fullpath, &status);
    if (ret) {
        //printf("Error attempting stat64 of '%s'\n", fullpath);
        perror("stat64");
        
        return 0;
    }
    return 1;
}

void change_dir(const char* path)
{
    //printf("%s at t=%1.3f\n", __func__, systemtime_as_real());			
    remove_dir_watch();
    chdir(path);
    populate_file_list();
    show_path();
    getcwd(SelDir, MAXFPATHLEN);
    setup_dir_watch(SelDir);
}

void mark_file_items(void) 
{
    int i, len;
    len = 0;
    // Write the list of selected file items to a buffer 
    // First, free previous string
    if (MarkedFileItems_sz_p)  free(MarkedFileItems_sz_p);
    
    // first get size of needed buffer
    len = NumSelected * (sizeof("file://") + MAXFFULLPATHLEN);
    // allocate it
    MarkedFileItems_sz_p = (char *)malloc(len);
    if (!MarkedFileItems_sz_p) {
        perror("Could not allocate memory for copy/paste or drag-and-drop\n");
    }
    strclr(MarkedFileItems_sz_p);
    
    char* s = SelectedFiles;
    char* d = MarkedFileItems_sz_p;
    char dirstr[MAXFPATHLEN + 10];
    getcwd(dirstr, MAXFPATHLEN);
    if (strcmp(dirstr, "/")) {	// if it is not the root dir
		strcat(dirstr, "/");    //    append a trailing slash
	}
	int dirlen = strlen(dirstr);
    
    for(i = 0; i < NumSelected; i++) {
		strcat(d, "file://");  d += strlen("file://");
		strcat(d, dirstr);    d += dirlen;
		if (*s == '\'')   s++;
		while(*s != '\'') {
			*d++ = *s++;		// copy filename
		}
		*d = '\0';
		if (*s == '\'')   s++;		
		while (*s == ' ' )   s++;
		strcat(d, "\n");   d += strlen("\n");
	}
	*d = '\0';
    //printf("Marked item string:\n%s\n", MarkedFileItems_sz_p); fflush(0);
    NumMarked = NumSelected;
    NeedToRedefineMenu = 1;
}

void get_selected_files_str(void) 
{
    //printf("%s at t=%1.3f\n", __func__, systemtime_as_real());			
	File_Detail_List_Browser* list_p = UI_p->file_list_p;
	char filename[MAXFNAMELEN];
	NumSelected = 0;
	strclr(SelectedFiles);
	for(int i = 2; i <= list_p->size(); i++) {
		if (list_p->selected(i)) {
			copy_field_limited_and_delimited(filename, list_p->text(i)+6, MAXFNAMELEN, '\t');
			strip_link_info(filename);
			if (strlen(SelectedFiles) + strlen(filename) + 4 > SelectedFilesBufsiz) {
				SelectedFilesBufsiz *= 2;
				char* newbuf = (char*)malloc(SelectedFilesBufsiz);
				strcpy(newbuf, SelectedFiles);
				free(SelectedFiles);
				SelectedFiles = newbuf;
			}
			strcat(SelectedFiles, "'");
			strcat(SelectedFiles, filename);
			strcat(SelectedFiles, "' ");
			//strcat(SelectedFiles, " ");
			NumSelected++;
		}
	}
	//printf("Selected file (set): %s\n", SelectedFiles);
    int selstrlen = strlen(SelectedFiles);
    //printf("String len of selected file set: %d\n", selstrlen);
    char* envstr = (char*)malloc(selstrlen);
    char* s = SelectedFiles;
    char* d = envstr;
    while(*s) {
        if (*s != '\'')   *d++ = *s;
        s++;
    }
    *d = '\0';
    setenv("sel_items", envstr, 1);
    free(envstr);
}

void handle_gui_event(Fl_Widget* w, void* data) 
{
    //printf("%s at t=%1.3f, event 0x%08X %s\n", __func__, systemtime_as_real(), 
            //ata, ((int)data > MAXASSOC + 5)?(char*)data:"");			
	char* cmd = (char*)malloc(strlen(SelectedFiles) + MAXFNAMELEN + 64);
	//wait_cursor();
    static double last_list_event_time = 0.0;
    if ((int)data < Associations) {
        get_selected_files_str();
        if (NumSelected >= 1) {
            //printf("Event assoc=%d >>>>>>>>>>>>>\n", (int)data);
            sprintf(cmd, Association[(int)data].cmdspec, 
//                    (NumSelected > 1) ? SelectedFiles : SelFilename);
                    (NumSelected > 1) ? "$sel_items" : SelFilename);
            UI_p->cmd_inp_p->replace(0, 9999, cmd, strlen(cmd));
            do_cmd();
            populate_file_list(RESELECT);
        }
        goto return_point;
    }
    else if ((int)data >= MAXASSOC && (int)data <= (MAXASSOC + 5)) {
		const char* dirstr = ".";
		switch((int)data - MAXASSOC) {
			case 0:	dirstr = HomeDir;  break;
			case 1:	dirstr = StartDir; break;
			case 2:	dirstr = "/mnt";   break;
			case 3:	dirstr = "/opt";   break;
			case 4:	dirstr = "/tmp";   break;
			case 5:	dirstr = TceDir;   break;
		}
		change_dir(dirstr);
        goto return_point;
	}
    //printf("Event %s >>>>>>>>>>>>>\n", (char*)data);
        
	if (!strcmp((char*)data, "about")) {
	    fl_message(About_text, APP_VER);
    }
	else if (!strcmp((char*)data, "help")) {
        Fl_Help_Dialog hd;
        hd.load("/usr/local/share/doc/dirwin/dirwin_help.htm");
        hd.textsize(14);
        hd.show();
        while (hd.visible()) {
            Fl::wait(1);
        } 
    }
	else if (!strcmp((char*)data, "term")) {
        system("aterm &");    
    }
	else if (!strcmp((char*)data, "quit")) {
	    UI_p->MainWnd_p->hide();
    }
	else if (!strcmp((char*)data, "createdir")) {
	    char newname[MAXFNAMELEN];
        strclr(newname);
	    //char cmd[MAXFNAMELEN + 10];
        const char* userinput = fl_input("Enter name for new directory");
        if (userinput && strlen(userinput)) {
            strncpy(newname, userinput, MAXFNAMELEN - 1);
            sprintf(cmd, "mkdir %s", newname);
            int ret = system(cmd);
            if (ret) {
                fl_alert("Create directory issue: %s", strerror(errno));
            }
            populate_file_list(RESELECT);
        }
    }
	else if (!strcmp((char*)data, "refresh")) {
	    populate_file_list(RESELECT);
    }
	else if (!strcmp((char*)data, "showhide")) {
//      fl_message("Show Hidden button clicked... is is now %s", 
//                  UI_p->showhiden_chk_p->value() ? "on" : "off");
        ShowAllFiles = UI_p->showhiden_chk_p->value();
        populate_file_list(RESELECT);
        show_path();
	}
	else if (!strcmp((char*)data, "path")) {
		const char* pathstr = UI_p->path_inp_p->value();
		int pathlen = strlen(pathstr);
        char endchar = pathstr[pathlen -1];
		//printf("Path entry field holds '%s', last len was %d, len now %d\n", pathstr, LastPathlen, pathlen);
        if (	((pathlen < LastPathlen) || (endchar != '/'))
			&& 	exists(pathstr)) {
            change_dir(pathstr);
        }
        else {
			LastPathlen = strlen(pathstr);
		}
    }
	else if (!strcmp((char*)data, "listbox")) {
		File_Detail_List_Browser* list_p = UI_p->file_list_p;
		double now = systemtime_as_real();
		int sel = list_p->value();
		if (sel > 1) {
			//printf(" - - - - - - - \n");
			//printf("last click time: %1.3f   last sel: %d\n", last_list_event_time, LastSel);
			//printf("current time:    %1.3f        sel: %d\n", now, sel);
			if (    (sel == LastSel) 
				&& ((now - last_list_event_time) < 0.3)
			) {
				if (LastAssoc == FT_DIR) {
					//printf("Calling change_dir() for '%s'\n", SelFilename);
					change_dir(SelFilename);
				}
				else {
					do_cmd();   // double-click... just what was loaded on the first click
                    reselect_items();
				}
			}
			else {
				//first, collect names of all the selected files (maybe later, check if they are are associate to same app)
				get_selected_files_str();
				// now get association for the most recent-selected item
				int assoc = (int)list_p->data(sel);
				if (assoc >=0) {
					//Fl_Input* inp_p = UI_p->cmd_inp_p;
					//printf("Selected file line text: '%s'\n", list_p->text(sel));
					
                    copy_field_limited_and_delimited(SelFilename, list_p->text(sel)+6, MAXFNAMELEN, '\t');
					strip_link_info(SelFilename);
					sprintf(cmd, Association[assoc].cmdspec,
							(NumSelected > 1) ? "$sel_items" : SelFilename);
					UI_p->cmd_inp_p->replace(0, 9999, cmd, strlen(cmd));
					
                    //UI_p->cmd_inp_p->take_focus();
					LastSel = sel;
					last_list_event_time = now;
					LastAssoc = assoc;
				}
			}
		}
		update_stats();
		NeedToRedefineMenu = 1;
	}
	else if (!strcmp((char*)data, "execute")) {
        do_cmd();
        populate_file_list(RESELECT);
	}
	else if (!strcmp((char*)data, "delete")) {
		get_selected_files_str();
		if (ConfirmDelete) {
			//normal_cursor();
			char prompt_str[80];
			if (strlen(SelectedFiles) < 40) {
				strnzcpy(prompt_str, SelectedFiles, 80);
			}
			else {
				int n = NumSelected;
				if (n > 2) {
					sprintf(prompt_str, "%d file%s", n, (n>1)?"s":"");
				}
				else {
					strnzcpy(prompt_str, SelectedFiles, 80);
				}
			}
//			IgnoreNextKey = 1; // fl_choice() doesn't seem to eat these, so ignore the key event that comes through
			int ans = fl_choice("Delete %s?", "No, cancel", "Yes, delete", NULL, prompt_str);
			if (ans == 0) {
                goto return_point;
			}
		}
		//wait_cursor();
//		sprintf(cmd, "rm -rf %s", SelectedFiles);
		sprintf(cmd, "rm -rf $sel_items");
		int ret = system(cmd);
		if (ret) {
			fl_alert("There may be an issue with the delete\n");
		}	
        populate_file_list();
	}
	else if (!strcmp((char*)data, "copy")) {
		get_selected_files_str();
		mark_file_items();
		// Copy the file path(s) to the selection buffer
		Fl::copy(MarkedFileItems_sz_p, strlen(MarkedFileItems_sz_p)+1, 0);
//        populate_file_list();
		NeedToRedefineMenu = 1;
	}
	else if (!strcmp((char*)data, "paste")) {
		Fl::paste(*UI_p->list_group_p, 0);
	}
	else if (!strcmp((char*)data, "rename")) {
		sprintf(cmd, "mv %s ", SelFilename);
		UI_p->cmd_inp_p->replace(0, 9999, cmd, strlen(cmd));
		UI_p->cmd_inp_p->take_focus();
	}
    else if (!strcmp((char*)data, "usage")) {
        //ShowingDiskUsage = !ShowingDiskUsage;
        //update_stats();
        present_selected_file_size();
    }
	//normal_cursor();
return_point:
    free(cmd);
}

int LowContrastColor  = 8;
int HighContrastColor = FL_BLACK;
int LowSigColor		  = FL_RED;
int MediumSigColor	  = FL_YELLOW;
int HighSigColor	  = FL_GREEN;

void pre_gui_setup(int argc, char** argv)
{
    //varlog_init();
    load_configuration();
    // parse command-line options
    
    int a = 1;
    while (a < argc) {
//		if (!strncmp(argv[a], "--child", 7)) {
//			IsChild = 1;
//		}
//		else {
			chdir(argv[a]);
//		}
        a++;
    }
    SelectedFiles = (char*)malloc(SelectedFilesBufsiz);
    strclr(SelectedFiles);
    SelectedFullPathFiles = (char*)malloc(SelectedFullPathFilesBufsiz);
    strclr(SelectedFullPathFiles);
    strclr(SelFilename);
    strclr(SelDir);
    init_dir_watching();
    getcwd(SelDir, MAXFPATHLEN);
    strcpy(StartDir, SelDir);
    setup_dir_watch(SelDir);
	strcpy(HomeDir, getenv("HOME"));
	strclr(TceDir);
	FILE* tce_fp = fopen("/opt/.tce_dir", "r");
	if (tce_fp) {
		fgets(TceDir, MAXFFULLPATHLEN, tce_fp);
		TceDir[strlen(TceDir) - 1] = '\0';	// trim off \n
		//printf("TCE directory is '%s'\n", TceDir);
	}
    Fl::args(argc, argv);
}

void redefine_menu(void) {
    //printf("%s at t=%1.3f\n", __func__, systemtime_as_real());			

    UI_p->menu_p->clear();
    UI_p->menu_p->add("Jump to/Home dir", FL_CTRL + FL_SHIFT + FL_Home, handle_gui_event, (void*)MAXASSOC);
    UI_p->menu_p->add("Jump to/Start dir", FL_CTRL + FL_SHIFT + 's', handle_gui_event, (void*)(MAXASSOC + 1));
    UI_p->menu_p->add("Jump to//mnt",  FL_CTRL + FL_SHIFT + 'm', handle_gui_event, (void*)(MAXASSOC + 2));
    UI_p->menu_p->add("Jump to//opt",  FL_CTRL + FL_SHIFT + 'o', handle_gui_event, (void*)(MAXASSOC + 3));
    UI_p->menu_p->add("Jump to//tmp",  FL_CTRL + FL_SHIFT + 't', handle_gui_event, (void*)(MAXASSOC + 4));
    if (*TceDir) {
		UI_p->menu_p->add("Jump to/TCE dir", FL_CTRL + FL_SHIFT + 'e', handle_gui_event, (void*)(MAXASSOC + 5));
	}
    UI_p->menu_p->add("Open Terminal", FL_ALT + 't', handle_gui_event, (void*)"term");
    UI_p->menu_p->add("Create Directory", FL_CTRL + 'd', handle_gui_event, (void*)"createdir");
    UI_p->menu_p->add("Refresh", FL_F + 5, handle_gui_event, (void*)"refresh");
    UI_p->menu_p->add("About", FL_F + 10, handle_gui_event, (void*)"about");
    UI_p->menu_p->add("Help", FL_F + 1, handle_gui_event, (void*)"help");
    UI_p->menu_p->add("_Quit", FL_CTRL + 'q', handle_gui_event, (void*)"quit");
    
    if (num_selected() > 0) {
        UI_p->menu_p->add("Rename", FL_F + 2, handle_gui_event, (void*)"rename");
        UI_p->menu_p->add("Delete", FL_Delete, handle_gui_event, (void*)"delete");
        UI_p->menu_p->add("Copy", FL_CTRL + 'c', handle_gui_event, (void*)"copy");   
    }

//    if (NumMarked > 0) {
//	if (Fl::event_length() > 0) {
        UI_p->menu_p->add("_Paste", FL_CTRL + 'v', handle_gui_event, (void*)"paste");        
//    }

/*    if (ShowingDiskUsage) {
        UI_p->menu_p->add("_Don't Show Disk Usage", FL_CTRL + 'u', handle_gui_event, (void*)"usage");        
    }
    else {
        UI_p->menu_p->add("_Show Disk Usage", FL_CTRL + 'u', handle_gui_event, (void*)"usage");        
    }     
*/
    UI_p->menu_p->add("_Disk usage of selection", FL_CTRL + 'u', handle_gui_event, (void*)"usage");
    
    //Now add commands associated with the selected file's type
    if (LastAssoc >= 0) {
        int added = 0;
        for (int a = 0; a < Associations; a++) {
            if (!strcmp(Association[a].typelabel, "file")) 
            {
                char shortcut[8];
                added++;
                sprintf(shortcut, "^%d", added);
                UI_p->menu_p->add(Association[a].cmdlabel,
                                  (added < 9) ? shortcut : "", 
                                  handle_gui_event, 
                                  (void*)a);
                //printf("    Added menu item %s\n", shortcut);
            } 
        }
        added = 0;

        // Check filename extension for assocciated application
        for (int a = 0; a < Associations; a++) {
            struct association* assoc_p = &Association[a];
            int patlen = strlen(assoc_p->pat);
            //printf("checking extension '%s' for pattern '%s'...", name + strlen(name) + 1 - patlen, fth_p->pat);
            if (!strncmp(SelFilename + strlen(SelFilename) - patlen, assoc_p->pat, patlen)) 
            {
                char shortcut[8];
                unsigned int keycombo = FL_ALT + '1' + added;
                added++;
//                sprintf(shortcut, "#%d", 30 + added);
                UI_p->menu_p->add(Association[a].cmdlabel,
//                                  (added < 9) ? shortcut : "", 
                                  keycombo,
                                  handle_gui_event, 
                                  (void*)a);
                //printf("    Added menu item %s\n", shortcut);
            }
        }        
    }
    NeedToRedefineMenu = 0;
}

void gui_setup(void)
{
    configure_colors();
    redefine_menu();
    
    UI_p->showhiden_chk_p->value(ShowAllFiles);
    UI_p->MainWnd_p->resizable(UI_p->list_group_p);
    UI_p->list_group_p->when(FL_WHEN_CHANGED | FL_WHEN_RELEASE);
	UI_p->MainWnd_p->size(MainWide, MainHigh);
    UI_p->cmd_inp_p->when(0); 
    UI_p->path_inp_p->when(FL_WHEN_CHANGED);
    show_path();
    Fl::wait(0.1);
}

void gui_init(void)
{	
	populate_file_list();
    
    Fl::wait(0.1);
    while (UI_p->MainWnd_p->visible() && (0 <= Fl::wait(0.5))) {
        time_t now;
        time(&now);
        if (NeedToRedefineMenu) {
            redefine_menu();
        }
        if (dir_watch_events_pending_len()) {
            react_to_dir_watch_events();
        }
        else if (   (systemtime_as_real() > TimeToResync_rsec) 
                 && (TimeToResync_rsec >= 0.0)
                ) 
        {
            populate_file_list(RESELECT);
            TimeToResync_rsec = -1.0;	// flag for "no resync needed
        }

    }
		//varlog(ESSENTIAL, "Exiting main loop!\n");
//	}
}

