OZ++ Class: FileListViewPrev
/******************************************************************************
 *
 * Copyright (c) 2015   TOSHIYUKI ARAI. ALL RIGHTS RESERVED.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions, and the following disclaimer.
 *
 * 2. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *
 *  FileListView.h
 *
 *****************************************************************************/


#include <oz++/motif/ScrolledWindow.h>

//#include <oz++/motif/BulletinBoard.h>
#include <oz++/motif/FlatButton.h>
#include <oz++/motif/PushButton.h>

#include <oz++/motif/FlatButtonGadget.h>
#include <oz++/motif/PixmapFile.h>
#include <oz++/motif/CompoundString.h>
#include <oz++/motif/Color.h>
#include <oz++/DirectoryScanner.h>
#include <oz++/MonetaryFormatter.h>
#include <oz++/FileAttributes.h>
#include <oz++/LocalTime.h>
#include <oz++/SmartArray.h>
#include <oz++/SmartPtrs.h>
#include <oz++/motif/StrDef.h>

namespace OZ {
    
class FileListView :public ScrolledWindow {

private:
  static const int ITEM_HEIGHT = 26;
  static const int MARGIN      = 2;
  static const int NAME_WIDTH  = 380;
  static const int CHANGEDTIME_WIDTH = 170;
  static const int SIZE_WIDTH  = 170;
    
private:
  BulletinBoard*  bboard;
    
  PixmapFile*   folderPixmap;
  PixmapFile*   filePixmap;
  int nameWidth;
  int changedTimeWidth;
  int sizeWidth;
    
  //SmartArray<FlatButtonGadget*> files;
  SmartArray<PushButton*> files;
    
  SmartArray<Gadget*> ctimes;
  SmartArray<Gadget*> sizes;
    
  int x;
  int y;
  int index;
  int height;
    
  bool fileSizeUnitKB;
    
public:
  static const int DIR  = 1;
  static const int FILE = 2;
    
public:
  FileListView(View* parent, const char* name, Args& args)
  :ScrolledWindow(parent, name, args.set(XmNscrollingPolicy, XmAUTOMATIC)
                                      .set(XmNscrollBarDisplayPolicy, XmVARIABLE) ),
  folderPixmap(NULL),
  filePixmap(NULL)
  {
    nameWidth        = NAME_WIDTH;
    changedTimeWidth = CHANGEDTIME_WIDTH;
    sizeWidth        = SIZE_WIDTH;
    
    if (args.has(XmNnameWidth)) {
        int val = (int)args.get(XmNnameWidth);
        if (val > 0) {
          nameWidth = val;  
        }
    }

    if (args.has(XmNchangedTimeWidth)) {
        int val = (int)args.get(XmNchangedTimeWidth);
        if (val > 0) {
          changedTimeWidth = val;   
        }
    }
    
    if (args.has(XmNsizeWidth)) {
        int val = (int)args.get(XmNsizeWidth);
        if (val > 0) {
          sizeWidth = val;  
        }
    }
    
    fileSizeUnitKB = false;
    if (args.has(XmNsizeUnitKB)) {
        fileSizeUnitKB = (bool)args.get(XmNsizeUnitKB); 
    }
    
    Color color(this);
    Pixel white = color.whitePixel();

    x = MARGIN;
    y = MARGIN;
    index = 0;
    Args ar;
    ar.reset();
    ar.set(XmNmarginHeight, 0);
    ar.set(XmNmarginWidth,  0);
    bboard = new BulletinBoard(this, "", ar);
    
    Display* display = parent->getDisplay();
    Pixel fg, bg;
    bboard -> get(XmNforeground, (XtArgVal)&fg);
    bboard -> get(XmNbackground, (XtArgVal)&bg);
    
    const char* folderFile = NULL;
    if (args.has(XmNfolderPixmapFile)) {
      folderFile = (const char*)args.get(XmNfolderPixmapFile);
      folderPixmap = new PixmapFile(display, folderFile, fg, bg);
    }

    const char* fileFile = NULL;
    if (args.has(XmNfilePixmapFile)) {
      fileFile = (const char*)args.get(XmNfilePixmapFile);
      filePixmap = new PixmapFile(display, fileFile, fg, bg);
    }
  }
    
public:
  ~FileListView()
  {
    files.clear();
    ctimes.clear();
    sizes.clear();

    delete folderPixmap;
    delete filePixmap;
    delete bboard;
  }

public:
  void redraw(bool flag)
  {
    if (flag) {
       bboard  -> manage(); 
    } else {
       bboard -> unmanage();
    }
  }
    
public:
  void removeAllItems()
  {
    x = MARGIN;
    y = MARGIN;
    index     = 0;
    files.removeAllItems();     
    ctimes.removeAllItems();
    sizes.removeAllItems();
  }
    
public:
  void addDirectories(const char* dir, char** items, int num)
  {
    if (items && num >0) {
        addItems(dir, DIR, items, num, folderPixmap);
    }
  }
    
public:
  void addFiles(const char* dir, char** items, int num)
  {
    if (items && num > 0) {
        addItems(dir, FILE, items, num, filePixmap);
    }
  }
    
private:
  void addItems(const char* dir, int type, char** items, int num, PixmapFile* pixmapFile)
  {
      char path[PATH_MAX];
      
      for (int i = 0; i<num; i++) {
        sprintf(path, "%s/%s", dir, items[i]);
        try {
        FileAttributes attr(path);
        int fbwidth = nameWidth - MARGIN; 
        CompoundString label(items[i]);
        Args ar;
        ar.reset();
        ar.set(XmNx, x);
        ar.set(XmNy, y);
        ar.set(XmNrecomputeSize, False);
        ar.set(XmNalignment, XmALIGNMENT_BEGINNING);
        ar.set(XmNlabelString, label);
        ar.set(XmNuserData, type);            
        unsigned int pw = 0;

        if (pixmapFile) {
          ar.set(XmNlabelPixmap, pixmapFile);
          ar.set(XmNlabelType, XmPIXMAP_AND_STRING);
          unsigned int ph = 0;
          pixmapFile -> getGeometry(&pw, &ph);
          pw += MARGIN*2;  
        }
          fbwidth -= pw;
        
        ar.set(XmNwidth, fbwidth);
        ar.set(XmNhighlightThickness, 1);
        ar.set(XmNshadowThickness, 0);
        PushButton* fb = new PushButton(bboard, "folder", ar);
        //FlatButtonGadget* fb = new FlatButtonGadget(bboard, "folder", ar);

        files[index++] = fb;
        static Dimension th = 0;
        if (th == 0) {
          th = fb -> height();
        }
     
        LocalTime lt(attr.changedTime());
        CompoundString ctime(lt.toCharString());    
        ar.reset();
        ar.set(XmNx, x + nameWidth);
        ar.set(XmNy, y); 
        ar.set(XmNheight, th);
        
        ar.set(XmNlabelString, ctime);
        ar.set(XmNwidth, changedTimeWidth - MARGIN);
        ctimes[index++] = new LabelGadget(bboard, "", ar);
            
        char csize[128];
        MonetaryFormatter mformatter;    
        if (fileSizeUnitKB) {
          unsigned long fs = attr.getSize();
          unsigned long kb = fs/1024L;
          if ((fs % 1024L)) {
              kb++;
          }
          CharString value = mformatter.toString(kb);
          sprintf(csize, "%s KB", (const char*)value);
        } else {
          CharString value = mformatter.toString((long)attr.getSize());
          sprintf(csize, "%s B", (const char*)value);
        }
            
        CompoundString size(csize);
        ar.reset();
        ar.set(XmNx, x + nameWidth + changedTimeWidth);
        ar.set(XmNy, y);
        ar.set(XmNheight, th);
        ar.set(XmNalignment, XmALIGNMENT_END);
        ar.set(XmNlabelString, size);
        ar.set(XmNwidth, sizeWidth - MARGIN);
        sizes[index++] = new LabelGadget(bboard, "", ar);
        
        y += th; //height;
        } catch (Exception& ex) {
            ex.display();       
        }
     }  
  }

public:
  void listup(const char* dir, const char* pattern = "*")
  {
    redraw(false);
    removeAllItems();
      
    const char* parent[] = {".."};
    addDirectories("",  (char**)parent, 1);
        
    DirectoryScanner dirScanner(dir);
    DirEntryList dirList;
    int m = dirScanner.scanDir(pattern, dirList);
    if (m > 0) { 
      SmartPtrs<char*> dirNames = new char*[m];
      dirList.getNames((char**)dirNames);
      addDirectories(dir, dirNames, m);
    }  
    
    DirectoryScanner fileScanner(dir);
    DirEntryList fileList;
    int n = fileScanner.scanFile(pattern, fileList);
    if (n > 0) {
      SmartPtrs<char*> fileNames = new char*[n];
      fileList.getNames((char**)fileNames);
      addFiles(dir, fileNames, n);
    } 
    redraw(true);
  }
    
public:
  void addCallback(const char* name, CommonObject* object,
        Callback callback, void* data)
  {
    for (int i = 0; i<index; i++) {
      if (files[i]) {
        files[i] -> addCallback(name, object, callback, data);
      }
    }
  }
};

}