OZ++ Sample: ObjectDetector
/******************************************************************************
 *
 * Copyright (c) 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.
 *
 *
 *  ObjectDetector.cpp
 *
 *****************************************************************************/

//2017/5/20 Modified to use OpenCVScrolledImage instead of OpenCVImageView
//         to reduce excessive screen flickering on a window resizing.

#include <oz++/motif/Label.h>
#include <oz++/motif/TextField.h>
#include <oz++/motif/PushButton.h>
#include <oz++/motif/RowColumn.h>
#include <oz++/motif/FileDropdownComboBox.h>
#include <oz++/motif/FileOpenDialog.h>
#include <oz++/motif/FolderOpenDialog.h>
#include <oz++/opencv/OpenCVMainView.h>
#include <oz++/opencv/OpenCVImageView.h>
#include <oz++/opencv/OpenCVScrolledImageView.h>
#include <oz++/opencv/OpenCVScaleComboBox.h>
#include <opencv2/objdetect/objdetect.hpp>

namespace OZ {

class MainView : public OpenCVMainView {

private:
  //////////////////////////////////////////////
  //Inner class starts.

  class SimpleView  :public OpenCVScrolledImageView {
  private:
    cv::Mat originalImage;
    cv::Mat scaledImage;

    void display()
    {
      show(scaledImage);
    }

  public:
    SimpleView(View* parent, const char* name, Args& args)
    :OpenCVScrolledImageView(parent, name, args)
    {
      const char* filename = "";
      int   loadingFlag    = 1;
      int   scalingRatio   = 100;

      if (args.has(XmNimageFileName)) {
        filename = (const char*)args.get(XmNimageFileName);
      }

      if (args.has(XmNimageLoadingFlag)) {
        loadingFlag = (int)args.get(XmNimageLoadingFlag); 
      }
      if (args.has(XmNimageScalingRatio)) {
        scalingRatio = (int)args.get(XmNimageScalingRatio); 
      }

      loadImage(filename, loadingFlag, scalingRatio);
    }

    void loadImage(const char* filename, int loadingFlag, int scalingRatio)
    {
      originalImage = readImage(filename, loadingFlag);
      scaleImage(originalImage, scaledImage, scalingRatio); 
    }

    void rescale(int scalingRatio)
    {
      scaledImage.release();
      scaleImage(originalImage, scaledImage, scalingRatio); 
    }
 
    void clear(int scalingRatio)
    {
      rescale(scalingRatio); 
    }

    void detect(const char* path, int scalingRatio)
    {
      try {
        CascadeClassifier classifier;
        classifier.load(path);
      
        cv::vector<Rect> faces;
       classifier.detectMultiScale(originalImage, faces, 1.1, 3, 0, cv::Size(20,20));
        if (faces.size() == 0) {
         // MessageBox(NULL, "Not detected by detectMultiScale.", 
         //     "DetectOperation", MB_OK|MB_ICONINFORMATION); 
        }
    for (int i = 0; i < faces.size(); i++){
          rectangle(originalImage, Point(faces[i].x, faces[i].y), 
           Point(faces[i].x + faces[i].width, faces[i].y + faces[i].height),
              Scalar(0, 200, 0), 3, CV_AA);
        }
        scaleImage(originalImage, scaledImage, scalingRatio); 
      } catch (Exception& ex) {
        caught(ex);
      }
    }
  };
  //Inner class ends.
  //////////////////////////////////////////////

  //SmartPtr<BulletinBoard>   bboard;
    SmartPtr<TextField>       filePath;
    SmartPtr<Label>            classifierLabel;
    SmartPtr<FileDropdownComboBox> classifierComboBox;
    SmartPtr<PushButton>      folderButton;
    StringT<char>            classifierFolder;

    SmartPtr<SimpleView>     view;

  SmartPtr<RowColumn>      controlPane;
    SmartPtr<OpenCVScaleComboBox>  scaleComboBox;
    SmartPtr<PushButton>   reloadButton;
    SmartPtr<PushButton>   detectButton;

  SmartPtr<FileOpenDialog> fileDialog;
  SmartPtr<FolderOpenDialog>  folderDialog;

  StringT<char>            imageFileName;
  int                      imageLoadingFlag;
  int                      imageScalingRatio;

  void fileOpen(Action& action)
  {
    fileDialog->popup();
  }

  void updateLabel(const char* filename)
  {
     CompoundString cs(filename);
     filePath->set(XmNlabelString, cs);    
     filePath->setString(filename);//, cs);    
  }

  void scaleChanged(Action& action)
  {
    int val = scaleComboBox->getScale();

    if (val > 0 && imageScalingRatio != val) {
      imageScalingRatio = val;
      view -> rescale(imageScalingRatio); 
    }
  }  

  void showFolderBrowser(Action& action)
  {
    folderDialog->popup();
  }

  void ok(Action& action)
  {
    try {  
      const char* filename = fileDialog->getFileName();
      imageFileName = filename;
      printf("filename: %s\n", filename);
      fileDialog->popdown();
      view->invalidate();
      view->loadImage(filename, 
        imageLoadingFlag, imageScalingRatio);

      updateLabel(filename);

      resize(width(), height());
      //flush();
    } catch (OZ::Exception& ex) {
       caught(ex);
    } 
  }

  void selectFolder(Action& action)
  {
    classifierFolder = folderDialog->getFolderName();
    printf("selectFolder %s\n", (const char*)classifierFolder);
   
    folderDialog->popdown();
    classifierComboBox->listupFiles((const char*)classifierFolder, "*.xml");

  }

  void reload(Action& action)
  {
     view -> loadImage(imageFileName,
                       imageLoadingFlag,
                       imageScalingRatio);
  }

  void detect(Action& action)
  {
    XmString xms;
    classifierComboBox->get(XmNselectedItem, (XtArgVal)&xms);
    CompoundString cs(xms);
    char* item = NULL;
    cs.get(&item);
    if (item != NULL) {
      char classifierxml[PATH_MAX];
      sprintf(classifierxml, "%s/%s", (const char*)classifierFolder,
           item); 
      printf("detect xml=%s\n", classifierxml);
      view -> detect(classifierxml, imageScalingRatio); 
    }
    XtFree(item);
  }

  void resize(Dimension w, Dimension h)
  {
    int CP_WIDTH = 160;
    int ww =  w-CP_WIDTH;
    static  int LH = 0;
    if (LH == 0) {
      LH = filePath->height();
    }

    static  int CH = 0;
    if (CH == 0) {
      CH = classifierComboBox->height();
    }
    static const int CL_WIDTH = 100;
    static const int FB_WIDTH = 60;
    if (filePath && classifierComboBox && view  && controlPane ) {
      BulletinBoard* bboard = getBulletinBoard();
      //bboard->unmap();
      //bboard->unmanage();

      int lh = LH;
      int ch = CH;
      int m = 2; 
      filePath              -> reshape(m, m, w, lh+m);
      classifierLabel    -> reshape(m, lh+2*m, CL_WIDTH, CH); 
      classifierComboBox -> reshape(2*m+CL_WIDTH, lh+2*m, w-CL_WIDTH-FB_WIDTH-4*m, CH);
      folderButton       -> reshape(w - FB_WIDTH, lh+2*m+m, FB_WIDTH-m, LH);

      view               -> reshape(m, lh+2*m + ch+2*m, ww-2*m, 
                    h - lh - ch -4*m-ch);
      Window cw = controlPane->getWindow();
      controlPane        -> reshape(ww-1, lh+2*m + ch+2*m, CP_WIDTH+1, 
                    h - lh - ch-4*m);

      //controlPane -> unmap();
      //controlPane -> map();

      //bboard->manage();
      //bboard->map();
    }
  }

public:
  MainView(OpenCVApplication& applet, const char* name, Args& args)
  :OpenCVMainView(applet, name, args)
  {
    BulletinBoard* bboard = getBulletinBoard();

    try {
      Args ar;
      ar.reset();

      imageFileName     = "../images/WafukuMannequin.png";

      imageLoadingFlag  = CV_LOAD_IMAGE_COLOR;
      imageScalingRatio = 100;

      //1 Create a filePath to display a filename.
      ar.set(XmNalignment,  XmALIGNMENT_BEGINNING);
      filePath = new TextField(bboard, "", ar);
      filePath -> setEditable(False);
      updateLabel(imageFileName);

      CompoundString classifiercs("Classifier");
      ar.reset();
      ar.set(XmNalignment,  XmALIGNMENT_BEGINNING);
      ar.set(XmNlabelString, classifiercs);
      classifierLabel = new Label(bboard, "", ar);

      CompoundString foldercs("...");
      ar.reset();
      ar.set(XmNlabelString, foldercs);
      folderButton = new PushButton(bboard, "", ar);
      folderButton -> addCallback(XmNactivateCallback, this,
        (Callback)&MainView::showFolderBrowser, NULL);

      ar.reset();
      classifierComboBox = new FileDropdownComboBox(bboard, "", ar);

      //2 Create a simpleview.
      ar.reset();
      ar.set(XmNimageFileName,    imageFileName);
      ar.set(XmNimageLoadingFlag, imageLoadingFlag);
      ar.set(XmNimageScalingRatio, imageScalingRatio);
      view = new SimpleView(bboard, "", ar);

      //3 Create a controlPane.
      ar.reset();
      controlPane = new RowColumn(bboard, "", ar);

      //4 Create a scaleComboBox.
      const char* defaultScale = "100%";
      ar.reset();
      CompoundString scaler("Scale");
      ar.set(XmNlabelString, scaler);
      ar.set(XmNdefaultScale, defaultScale);
      scaleComboBox = new OpenCVScaleComboBox(controlPane, "", ar);
      scaleComboBox->addCallback(XmNselectionCallback, this,
        (Callback)&MainView::scaleChanged, NULL);

      CompoundString reloadcs("Reload");
      ar.reset();
      ar.set(XmNlabelString, reloadcs);
      reloadButton = new PushButton(controlPane, "", ar);
      reloadButton -> addCallback(XmNactivateCallback, this,
        (Callback)&MainView::reload, NULL);

      CompoundString detectcs("Detect");
      ar.reset();
      ar.set(XmNlabelString, detectcs);
      detectButton = new PushButton(controlPane, "", ar);
      detectButton -> addCallback(XmNactivateCallback, this,
        (Callback)&MainView::detect, NULL);

      const char* home = getenv("HOME");

      //5 Create a fileOpendialog
      ar.reset();
      ar.set(XmNrootFolderName, home);
      fileDialog = new FileOpenDialog(this, "FileOpenDialog", ar);
      fileDialog  -> getOkButton()
                  -> addCallback(XmNactivateCallback, this,
                          (Callback)&MainView::ok, NULL);  

      //5 Create a folderOpenDialog
      ar.reset();
      ar.set(XmNrootFolderName, home);
      folderDialog = new FolderOpenDialog(this, "FolderOpenDialog", ar);
      folderDialog  -> getOkButton()
                  -> addCallback(XmNactivateCallback, this,
                          (Callback)&MainView::selectFolder, NULL);  
    } catch (OZ::Exception& ex) {
      caught(ex);
    }
  }

  ~MainView()
  {
  }
};

}

//
int main(int argc, char** argv)
{
  try {
    const char*  appclass = argv[0];

    OpenCVApplication applet(appclass, argc, argv);

    Args args;
    args.set(XmNwidth,  800);
    args.set(XmNheight, 460);
    MainView view(applet, argv[0], args);
    view.realize();

    applet.run();
  } catch (OZ::Exception& ex) {
    caught(ex);
  }
  return 0;
}