OZ++ Class: FolderSelectionBox
/******************************************************************************
 *
 * Copyright (c) 2015-2017 Antillia.com 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.
 *
 *
 *  FolderSelectionBox.h
 *
 *****************************************************************************/

//2015/08/27
//2017/10/15 Modified to use create method.

#pragma once

#include <oz++/StringT.h>
#include <oz++/StringBufferT.h>
#include <oz++/DirectoryScanner.h>
#include <oz++/motif/Color.h>
#include <oz++/motif/ScrolledWindow.h>
#include <oz++/motif/Form.h>
#include <oz++/motif/Container.h>
#include <oz++/motif/IconGadget.h>
#include <oz++/motif/CompoundString.h>
#include <oz++/motif/Pixelmap.h>
#include <oz++/motif/Label.h>
#include <oz++/motif/TextField.h>
#include <oz++/motif/StrDef.h>

//2017/05/10
#include <oz++/motif/xpm/folder.xpm>
//It contains static const char* folder_xpm[];

namespace OZ {

class FolderSelectionBox :public ScrolledWindow {
public:
  enum {OK_BUTTON =0 , CANCEL_BUTTON, FILTER_BUTTON, HELP_BUTTON};

private:
  SmartPtr<Container>    container;
  SmartPtr<IconGadget>   root;
  SmartPtr<Pixelmap>     folderPixmap;

  StringT<char>          folderName;

  StringT<char>          rootFolder;

  SmartPtr<Action>       selectAction;

  void selected(Action& action)
  {
    XmContainerSelectCallbackStruct* cbs = 
    (XmContainerSelectCallbackStruct*)action.getCallData();
    Container* container = (Container*)action.getClientData();

    int itemCount = cbs -> selected_item_count;
 
    for (int i = 0; i < itemCount; i++) {
      Widget w =  cbs->selected_items[i];
      IconGadget* selected = (IconGadget*)container->lookup(w);
      if (selected) {
        StringBufferT<char> path;
        selected -> getHierarchy(path);
        const char* folder = (const char*)path;
        if (StringT<char>::startsWith(folder, "//")) {
          folder++;
        }
        printf ("selected folder: %s\n", folder);
        folderName = folder;
        if (selected -> hasChildren() == false) {
          buildDirTree(container, selected, folder);
        }
        if (selectAction) {
          printf("itemCount %d\n", i);
          selectAction -> setCallData((XtPointer)selected);
          selectAction -> setClientData((XtPointer)folder);
          selectAction -> call(*selectAction);
        }
      } //selected
    } //for loop
  }

  void  buildDirTree(Container* container, IconGadget* node, const char* dir)
  {
    char path[PATH_MAX];

    if (StringT<char>::startsWith(dir, "/") == false) {
      sprintf(path, "/%s", dir);
    } else {
      sprintf(path, "%s", dir);
    }

    printf("buildDirTree %s\n", path);
    
    DirectoryScanner scanner(path);
    DirEntryList dirList;

    int m = scanner.scanDir("*", dirList);
    printf("DirList dir=%s count=%d\n", dir, m);

    for (int i = 0; i<m; i++) {
      const char* name = dirList.getName(i);
      printf("name %s\n", name);
      Args args;
      args.reset();
      args.set(XmNentryParent,     0);
      args.set(XmNoutlineState,    XmEXPANDED); 
      args.set(XmNsmallIconPixmap, folderPixmap);
      args.set(XmNviewType,        XmSMALL_ICON);
      args.set(XmNshadowThickness, 0);
      args.set(XmNborderWidth,     0);
 
      IconGadget* child = NULL;
      if (node) {
        child = node -> add(name, args);
      } else {
        child = new IconGadget(container, name, args); 
      }
      container->add(child);
    }
  }

public:
  FolderSelectionBox(View* parent, const char* name, Args& args)
  :ScrolledWindow()
  {
    FolderSelectionBox::create(parent, name, args);
  }

  FolderSelectionBox()
  :ScrolledWindow()
  {
  }

  void create(View* parent, const char* name, Args& args)
  {
    ScrolledWindow::create(parent, name, 
        args.set(XmNscrollingPolicy, XmAUTOMATIC)
          .set(XmNadjustLast, True)
          .set(XmNvisualPolicy, XmVARIABLE)
          .set(XmNscrollBarDisplayPolicy, XmAS_NEEDED)
          .set(XmNspacing, 0)
          .set(XmNborderWidth, 0) );

    folderName = "";
  
    Args ar;
    ar.reset();

    Color color(this);
    //Pixel selectColor = color.allocNamedColor("lightblue");
    Pixel selectColor = color.allocNamedColor("lightcyan");

    ar.reset();
    ar.set(XmNlayoutType, XmOUTLINE);
    ar.set(XmNoutlineLineStyle, XmNO_LINE); 
    ar.set(XmNselectionPolicy, XmBROWSE_SELECT);
    ar.set(XmNprimaryOwnership, XmOWN_MULTIPLE);
    //ar.set(XmNprimaryOwnership, XmOWN_NEVER); //XmOWN_MULTIPLE);
    ar.set(XmNselectColor, selectColor);

    container = new Container(this, "folderSelectionContainer", ar);    
/*
    Widget hsb = (Widget)get(XmNhorizontalScrollBar);
    Widget vsb = (Widget)get(XmNverticalScrollBar);
    setArea(hsb, vsb, container->getWidget());  
*/
    try {
      Pixel bg;
      container -> get(XmNbackground, (XtArgVal)&bg);
      //We use folder_xpm variable included by xpm/folder.xpm
      folderPixmap = new Pixelmap(getDisplay(), bg, folder_xpm);
    } catch (Exception& ex) {
      ex.display();
    }

    static const char* rootName = "/";
    //const char* 
    rootFolder = rootName;
    if (args.has(XmNrootFolderName)) {
      rootFolder = (const char*)args.get(XmNrootFolderName); 
      if(StringT<char>::startsWith(rootFolder, rootName) == false) {
        rootFolder = rootName; 
      } else {
        //rootFolder = rootFolder;
      }
    }

    ar.reset(); 
    ar.set(XmNentryParent, 0);
    ar.set(XmNoutlineState, XmEXPANDED);
    if (folderPixmap) {
      ar.set(XmNsmallIconPixmap, folderPixmap);
    }
    ar.set(XmNviewType, XmSMALL_ICON);
    ar.set(XmNshadowThickness, 0);
    ar.set(XmNborderWidth,  0);
    if (strcmp((const char*)rootFolder, "/") ) {
      //If rootFolder is not "/", then we create IconGadget
      root = new IconGadget(container, rootFolder, ar);
      container -> add(root);
    } else {
      //else , we don't create IconGadage to "/",
      //which means root IconGadget is NULL.

      root = NULL;
    }
  
    buildDirTree(container, root, rootFolder);

    container -> addCallback(XmNselectionCallback, this,
        (Callback)&FolderSelectionBox::selected, container);
  }

  ~FolderSelectionBox()
  {
  }

  void getFolderName(StringT<char>& name)
  {
    name = folderName;
  }

  //To retrieve a folder name which starts with "/".
  void getRootedFolderName(StringT<char>& name)
  {
    name = "/";
    name +=  folderName;
  }

  const char* getFolderName()
  {
    return (const char*)folderName;
  }

  //To retrieve a folder name which starts with "/".
  StringT<char> getRootedFolderName()
  {
     return (StringT<char>("/") + folderName);  
  }

  void setSelectCallback(CommonObject* object,
         Callback callback, XtPointer data)
  {
    if (selectAction) {
      selectAction = NULL;
    }
    if (selectAction == NULL) {
      selectAction = new Action(this, object, callback, data, "");
    }
  }
};
}