/********************************************************************************
*                                                                               *
*                      I m a g e   V i e w e r   D e m o                        *
*                                                                               *
*********************************************************************************
* Copyright (C) 2000,2025 by Jeroen van der Zijp.   All Rights Reserved.        *
********************************************************************************/
#include "fx.h"
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <signal.h>
#ifndef WIN32
#include <unistd.h>
#endif


/*******************************************************************************/


// Mini application object
class ImageWindow : public FXMainWindow {
  FXDECLARE(ImageWindow)
protected:
  FXImageView       *imageview;             // Image viewer
  FXRecentFiles      mrufiles;              // Recent files
  FXString           filename;              // File being viewed
  FXMenuBar         *menubar;               // Menu bar
  FXToolBar         *toolbar;               // Tool bar
  FXStatusBar       *statusbar;             // Status bar
  FXSplitter        *splitter;              // Splitter
  FXHorizontalFrame *filebox;               // Box containing directories/files
  FXFileList        *filelist;              // File List
  FXMenuPane        *filemenu;              // File menu
  FXMenuPane        *editmenu;              // Edit menu
  FXMenuPane        *viewmenu;              // View menu
  FXMenuPane        *helpmenu;              // Help menu
  FXMenuPane        *manipmenu;             // Manipulations menu
  FXTextField       *filter;                // Filter for tree list
  FXIcon            *fileopenicon;          // Icons
  FXIcon            *filesaveicon;
  FXIcon            *cuticon;
  FXIcon            *copyicon;
  FXIcon            *pasteicon;
  FXIcon            *uplevelicon;
  FXIcon            *paletteicon;
protected:
  ImageWindow(){}
public:
  long onCmdAbout(FXObject*,FXSelector,void*);
  long onCmdOpen(FXObject*,FXSelector,void*);
  long onCmdSave(FXObject*,FXSelector,void*);
  long onCmdQuit(FXObject*,FXSelector,void*);
  long onUpdTitle(FXObject*,FXSelector,void*);
  long onCmdFileList(FXObject*,FXSelector,void*);
  long onCmdRecentFile(FXObject*,FXSelector,void*);
  long onCmdRotate(FXObject*,FXSelector,void*);
  long onCmdMirror(FXObject*,FXSelector,void*);
  long onCmdScale(FXObject*,FXSelector,void*);
  long onCmdCrop(FXObject*,FXSelector,void*);
  long onUpdImage(FXObject*,FXSelector,void*);
public:
  enum{
    ID_ABOUT=FXMainWindow::ID_LAST,
    ID_OPEN,
    ID_SAVE,
    ID_TITLE,
    ID_QUIT,
    ID_FILELIST,
    ID_RECENTFILE,
    ID_ROTATE_90,
    ID_ROTATE_180,
    ID_ROTATE_270,
    ID_MIRROR_HOR,
    ID_MIRROR_VER,
    ID_MIRROR_BOTH,
    ID_SCALE,
    ID_CROP,
    ID_LAST
    };
public:
  ImageWindow(FXApp* a);
  virtual void create();
  FXbool loadimage(const FXString& file);
  FXbool saveimage(const FXString& file);
  virtual ~ImageWindow();
  };


/*******************************************************************************/


// File Save
const unsigned char filesave[]={
  0x47,0x49,0x46,0x38,0x37,0x61,0x10,0x00,0x10,0x00,0xf2,0x00,0x00,0xb2,0xc0,0xdc,
  0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x80,0x80,0x80,0xff,0xff,0xff,0x00,
  0x00,0x00,0x00,0x00,0x00,0x2c,0x00,0x00,0x00,0x00,0x10,0x00,0x10,0x00,0x00,0x03,
  0x42,0x08,0xaa,0xb1,0xfe,0x4c,0x34,0x06,0xa3,0x90,0x20,0xcc,0x1a,0xae,0xc4,0x95,
  0x75,0x6d,0x8f,0xa6,0x79,0x64,0xa6,0x0d,0x44,0x87,0x9a,0x0c,0xc1,0x9a,0xb4,0x3c,
  0x05,0x33,0xad,0xd7,0x43,0x50,0x0c,0xbf,0x20,0x10,0xb8,0x6a,0x0d,0x85,0x42,0x8d,
  0x11,0x79,0xec,0x65,0x7a,0x4c,0x64,0x23,0xb0,0x6c,0x26,0x55,0xd6,0xe6,0x6d,0xc7,
  0x55,0x24,0x00,0x00,0x3b
  };


// File Open
const unsigned char fileopen[]={
  0x47,0x49,0x46,0x38,0x37,0x61,0x10,0x00,0x10,0x00,0xf2,0x00,0x00,0xb2,0xc0,0xdc,
  0x00,0x00,0x00,0x00,0xff,0x00,0xff,0xff,0x00,0x80,0x80,0x80,0xff,0xff,0xff,0x00,
  0x00,0x00,0x00,0x00,0x00,0x2c,0x00,0x00,0x00,0x00,0x10,0x00,0x10,0x00,0x00,0x03,
  0x45,0x08,0xba,0x1c,0xfe,0x2c,0xae,0x20,0x44,0x90,0x91,0xd6,0x8b,0x95,0xb6,0x1c,
  0x00,0x79,0x95,0x25,0x3e,0x03,0x31,0x3c,0xa5,0x73,0xaa,0xf0,0xda,0xae,0x5e,0xfa,
  0xdc,0xb8,0xa3,0x06,0xc5,0xd0,0xff,0x3e,0x9f,0xc3,0x16,0x04,0x02,0x75,0x81,0xa2,
  0xb2,0x77,0x49,0xf2,0x96,0xc5,0x26,0x21,0x69,0x54,0x36,0xa9,0x50,0xa6,0xe7,0x59,
  0xfd,0x71,0x72,0x60,0x4e,0x02,0x00,0x3b
  };


// Cut
const unsigned char cut[]={
  0x47,0x49,0x46,0x38,0x37,0x61,0x10,0x00,0x10,0x00,0xf1,0x00,0x00,0xb2,0xc0,0xdc,
  0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x2c,0x00,0x00,0x00,0x00,0x10,0x00,
  0x10,0x00,0x00,0x02,0x29,0x84,0x8f,0xa9,0x10,0xd7,0xeb,0x22,0x34,0x8f,0x4d,0xda,
  0x42,0x9d,0xf9,0x3a,0xed,0x49,0xa1,0x10,0x08,0x21,0x60,0x0a,0xea,0xa4,0xa6,0x86,
  0xa9,0xc0,0x2e,0xba,0xc8,0xf4,0x9d,0xd8,0x28,0x5c,0xf3,0xe7,0x54,0x00,0x00,0x3b
  };


// Copy
const unsigned char copy[]={
  0x47,0x49,0x46,0x38,0x37,0x61,0x10,0x00,0x10,0x00,0xf1,0x00,0x00,0xb2,0xc0,0xdc,
  0x00,0x00,0x00,0xff,0xff,0xff,0x00,0x00,0x80,0x2c,0x00,0x00,0x00,0x00,0x10,0x00,
  0x10,0x00,0x00,0x02,0x38,0x84,0x8f,0x69,0xc1,0xad,0x18,0x84,0x14,0xec,0xad,0x49,
  0xa9,0x8d,0x8c,0x8e,0x0f,0x42,0x98,0x30,0x60,0xdf,0xc1,0x35,0xe5,0x54,0x0e,0x97,
  0x49,0x45,0xa0,0x9b,0x32,0xeb,0x48,0xe3,0x71,0x43,0x02,0x8d,0x3a,0x92,0xb8,0x14,
  0xa5,0x9f,0xec,0x71,0x33,0x21,0x67,0x33,0x8b,0xf3,0x99,0x28,0x00,0x00,0x3b
  };


// Paste
const unsigned char paste[]={
  0x47,0x49,0x46,0x38,0x37,0x61,0x10,0x00,0x10,0x00,0xf2,0x00,0x00,0xb2,0xc0,0xdc,
  0x00,0x00,0x00,0xff,0xff,0x00,0xc0,0xc0,0xc0,0x80,0x80,0x00,0x80,0x80,0x80,0x00,
  0x00,0x80,0xff,0xff,0xff,0x2c,0x00,0x00,0x00,0x00,0x10,0x00,0x10,0x00,0x00,0x03,
  0x4b,0x08,0xba,0xdc,0x1e,0x30,0x38,0x15,0xa1,0x10,0x55,0x52,0x52,0x48,0xc0,0x5f,
  0xd0,0x15,0x9a,0x48,0x0e,0x68,0x2a,0x7a,0x1b,0x99,0x41,0x05,0x49,0x8d,0x5c,0x4d,
  0x97,0x36,0x61,0xec,0xbc,0x51,0xd2,0x05,0xc3,0x61,0x38,0xdc,0xb5,0x6a,0x42,0xe2,
  0x41,0x68,0x00,0xac,0x46,0xc2,0xc8,0x92,0x17,0xc8,0x25,0x95,0xc5,0x27,0x27,0x9a,
  0x59,0x3a,0x2b,0x57,0x6c,0xb3,0xd1,0x2b,0x8f,0x27,0x93,0x04,0x00,0x3b
  };


// Palette of colors
const unsigned char colorpal[]={
  0x47,0x49,0x46,0x38,0x37,0x61,0x10,0x00,0x10,0x00,0xf2,0x00,0x00,0x80,0x80,0x80,
  0x00,0x00,0x00,0xff,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0xff,0x00,0x00,0x00,
  0x00,0xff,0x00,0x00,0x00,0x2c,0x00,0x00,0x00,0x00,0x10,0x00,0x10,0x00,0x00,0x03,
  0x47,0x08,0x0a,0xd1,0xfb,0x30,0x88,0xe9,0xe0,0x92,0x33,0x07,0xcb,0xf2,0xf8,0xd4,
  0xd3,0x64,0xc2,0x37,0x48,0x55,0x83,0x11,0x04,0x83,0x09,0x9b,0x23,0xb1,0x2d,0x49,
  0x6d,0xdd,0x42,0xc6,0x68,0x67,0xdf,0x23,0x58,0xa7,0x40,0x7c,0x05,0x85,0x12,0x62,
  0xe1,0x65,0xc3,0xa1,0x24,0x86,0x68,0x54,0x73,0xc9,0x48,0x0d,0x54,0xd1,0xf1,0x66,
  0x91,0xa9,0x70,0x11,0x30,0x07,0x90,0x00,0x00,0x3b
  };

const unsigned char tbuplevel[]={
  0x47,0x49,0x46,0x38,0x37,0x61,0x10,0x00,0x10,0x00,0xf1,0x00,0x00,0xb2,0xc0,0xdc,
  0x00,0x00,0x00,0xff,0xff,0x00,0xff,0xff,0xff,0x2c,0x00,0x00,0x00,0x00,0x10,0x00,
  0x10,0x00,0x00,0x02,0x32,0x84,0x8f,0x79,0xc1,0xac,0x18,0xc4,0x90,0x22,0x3c,0xd0,
  0xb2,0x76,0x78,0x7a,0xfa,0x0d,0x56,0xe4,0x05,0x21,0x35,0x96,0xcc,0x29,0x62,0x92,
  0x76,0xa6,0x28,0x08,0x8e,0x35,0x5b,0x75,0x28,0xfc,0xba,0xf8,0x27,0xfb,0xf5,0x36,
  0x44,0xce,0xe5,0x88,0x44,0x14,0x00,0x00,0x3b
  };

// Patterns
const FXchar patterns[]=
  "All Files (*)"
  "\nGIF Image (*.gif)"
  "\nBMP Image (*.bmp)"
  "\nXPM Image (*.xpm)"
  "\nPCX Image (*.pcx)"
  "\nICO Image (*.ico)"
  "\nRGB Image  (*.rgb)"
  "\nXBM Image  (*.xbm)"
  "\nTARGA Image  (*.tga)"
  "\nPPM Image  (*.ppm)"
  "\nPNG Image  (*.png)"
  "\nJPEG Image (*.jpg)"
  "\nTIFF Image (*.tif)"
  "\nRAS Image (*.ras)"
  "\nDDS Image (*.dds)"
  "\nIFF Image (*.iff)"
  "\nJP2 Image (*.jp2)"
  "\nEXE Image (*.exe)"
  "\nQOIF Image (*.qoi)"
  ;

/*******************************************************************************/

// Map
FXDEFMAP(ImageWindow) ImageWindowMap[]={
  FXMAPFUNC(SEL_COMMAND,       ImageWindow::ID_ABOUT,      ImageWindow::onCmdAbout),
  FXMAPFUNC(SEL_COMMAND,       ImageWindow::ID_OPEN,       ImageWindow::onCmdOpen),
  FXMAPFUNC(SEL_COMMAND,       ImageWindow::ID_SAVE,       ImageWindow::onCmdSave),
  FXMAPFUNC(SEL_UPDATE,        ImageWindow::ID_TITLE,      ImageWindow::onUpdTitle),
  FXMAPFUNC(SEL_COMMAND,       ImageWindow::ID_QUIT,       ImageWindow::onCmdQuit),
  FXMAPFUNC(SEL_SIGNAL,        ImageWindow::ID_QUIT,       ImageWindow::onCmdQuit),
  FXMAPFUNC(SEL_CLOSE,         ImageWindow::ID_TITLE,      ImageWindow::onCmdQuit),
  FXMAPFUNC(SEL_DOUBLECLICKED, ImageWindow::ID_FILELIST,   ImageWindow::onCmdFileList),
  FXMAPFUNC(SEL_COMMAND,       ImageWindow::ID_RECENTFILE, ImageWindow::onCmdRecentFile),
  FXMAPFUNC(SEL_COMMAND,       ImageWindow::ID_ROTATE_90,  ImageWindow::onCmdRotate),
  FXMAPFUNC(SEL_COMMAND,       ImageWindow::ID_ROTATE_180, ImageWindow::onCmdRotate),
  FXMAPFUNC(SEL_COMMAND,       ImageWindow::ID_ROTATE_270, ImageWindow::onCmdRotate),
  FXMAPFUNC(SEL_COMMAND,       ImageWindow::ID_MIRROR_HOR, ImageWindow::onCmdMirror),
  FXMAPFUNC(SEL_COMMAND,       ImageWindow::ID_MIRROR_VER, ImageWindow::onCmdMirror),
  FXMAPFUNC(SEL_COMMAND,       ImageWindow::ID_MIRROR_BOTH,ImageWindow::onCmdMirror),
  FXMAPFUNC(SEL_COMMAND,       ImageWindow::ID_SCALE,      ImageWindow::onCmdScale),
  FXMAPFUNC(SEL_COMMAND,       ImageWindow::ID_CROP,       ImageWindow::onCmdCrop),
  FXMAPFUNC(SEL_UPDATE,        ImageWindow::ID_ROTATE_90,  ImageWindow::onUpdImage),
  FXMAPFUNC(SEL_UPDATE,        ImageWindow::ID_ROTATE_180, ImageWindow::onUpdImage),
  FXMAPFUNC(SEL_UPDATE,        ImageWindow::ID_ROTATE_270, ImageWindow::onUpdImage),
  FXMAPFUNC(SEL_UPDATE,        ImageWindow::ID_MIRROR_HOR, ImageWindow::onUpdImage),
  FXMAPFUNC(SEL_UPDATE,        ImageWindow::ID_MIRROR_VER, ImageWindow::onUpdImage),
  FXMAPFUNC(SEL_UPDATE,        ImageWindow::ID_MIRROR_BOTH,ImageWindow::onUpdImage),
  FXMAPFUNC(SEL_UPDATE,        ImageWindow::ID_SCALE,      ImageWindow::onUpdImage),
  FXMAPFUNC(SEL_UPDATE,        ImageWindow::ID_CROP,       ImageWindow::onUpdImage),
  };


// Object implementation
FXIMPLEMENT(ImageWindow,FXMainWindow,ImageWindowMap,ARRAYNUMBER(ImageWindowMap))


// Make some windows
ImageWindow::ImageWindow(FXApp* a):FXMainWindow(a,"FOX Image Viewer: - untitled",nullptr,nullptr,DECOR_ALL,0,0,850,600,0,0),mrufiles(a){
  setTarget(this);
  setSelector(ID_TITLE);

  // Make some icons
  fileopenicon=new FXGIFIcon(getApp(),fileopen);
  filesaveicon=new FXGIFIcon(getApp(),filesave);
  cuticon=new FXGIFIcon(getApp(),cut);
  copyicon=new FXGIFIcon(getApp(),copy);
  pasteicon=new FXGIFIcon(getApp(),paste);
  uplevelicon=new FXGIFIcon(getApp(),tbuplevel);
  paletteicon=new FXGIFIcon(getApp(),colorpal);

  // Make color dialog
  FXColorDialog *colordlg=new FXColorDialog(this,"Color Dialog");

  // Make menu bar
  menubar=new FXMenuBar(this,LAYOUT_SIDE_TOP|LAYOUT_FILL_X|FRAME_RAISED);

  // Tool bar
  toolbar=new FXToolBar(this,LAYOUT_SIDE_TOP|PACK_UNIFORM_WIDTH|PACK_UNIFORM_HEIGHT|FRAME_RAISED|LAYOUT_FILL_X);

  // Tool bar
  //FXHorizontalFrame* toolbarcontainer=new FXHorizontalFrame(this,LAYOUT_SIDE_TOP|LAYOUT_FILL_X,0,0,0,0, 0,0,0,0, 0,0);
  //new FXToolBarTab(toolbarcontainer,nullptr,0,FRAME_RAISED);
  //toolbar=new FXToolBar(toolbarcontainer,FRAME_RAISED|PACK_UNIFORM_WIDTH|LAYOUT_SIDE_TOP|LAYOUT_FILL_X,0,0,0,0, 4,4,4,4, 0,0);

  // Status bar
  statusbar=new FXStatusBar(this,LAYOUT_SIDE_BOTTOM|LAYOUT_FILL_X|STATUSBAR_WITH_DRAGCORNER);

  // File menu
  filemenu=new FXMenuPane(this);
  new FXMenuTitle(menubar,"&File",nullptr,filemenu);

  // Edit Menu
  editmenu=new FXMenuPane(this);
  new FXMenuTitle(menubar,"&Edit",nullptr,editmenu);

  // Edit Menu
  manipmenu=new FXMenuPane(this);
  new FXMenuTitle(menubar,"&Manipulation",nullptr,manipmenu);

  // View menu
  viewmenu=new FXMenuPane(this);
  new FXMenuTitle(menubar,"&View",nullptr,viewmenu);

  // Help menu
  helpmenu=new FXMenuPane(this);
  new FXMenuTitle(menubar,"&Help",nullptr,helpmenu,LAYOUT_RIGHT);

  // Splitter
  splitter=new FXSplitter(this,LAYOUT_SIDE_TOP|LAYOUT_FILL_X|LAYOUT_FILL_Y|SPLITTER_TRACKING|SPLITTER_VERTICAL|SPLITTER_REVERSED);

  // Sunken border for image widget
  FXHorizontalFrame *imagebox=new FXHorizontalFrame(splitter,FRAME_SUNKEN|FRAME_THICK|LAYOUT_FILL_X|LAYOUT_FILL_Y,0,0,0,0, 0,0,0,0);

  // Make image widget
  imageview=new FXImageView(imagebox,nullptr,nullptr,0,LAYOUT_FILL_X|LAYOUT_FILL_Y);

  // Sunken border for file list
  filebox=new FXHorizontalFrame(splitter,LAYOUT_FILL_X|LAYOUT_FILL_Y,0,0,0,0, 0,0,0,0);

  // Make file list
  FXHorizontalFrame* fileframe=new FXHorizontalFrame(filebox,FRAME_SUNKEN|FRAME_THICK|LAYOUT_FILL_X|LAYOUT_FILL_Y,0,0,0,0, 0,0,0,0, 0,0);
  filelist=new FXFileList(fileframe,this,ID_FILELIST,LAYOUT_FILL_X|LAYOUT_FILL_Y|ICONLIST_MINI_ICONS|ICONLIST_AUTOSIZE);
  new FXButton(filebox,"\tUp one level\tGo up to higher directory.",uplevelicon,filelist,FXFileList::ID_DIRECTORY_UP,BUTTON_TOOLBAR|FRAME_RAISED|LAYOUT_FILL_Y);

  // Toobar buttons: File manipulation
  new FXButton(toolbar,"&Open\tOpen Image\tOpen image file.",fileopenicon,this,ID_OPEN,ICON_ABOVE_TEXT|BUTTON_TOOLBAR|FRAME_RAISED);
  new FXButton(toolbar,"&Save\tSave Image\tSave image file.",filesaveicon,this,ID_SAVE,ICON_ABOVE_TEXT|BUTTON_TOOLBAR|FRAME_RAISED);

  // Toobar buttons: Editing
  new FXButton(toolbar,"Cut\tCut",cuticon,nullptr,0,ICON_ABOVE_TEXT|BUTTON_TOOLBAR|FRAME_RAISED);
  new FXButton(toolbar,"Copy\tCopy",copyicon,nullptr,0,ICON_ABOVE_TEXT|BUTTON_TOOLBAR|FRAME_RAISED);
  new FXButton(toolbar,"Paste\tPaste",pasteicon,nullptr,0,ICON_ABOVE_TEXT|BUTTON_TOOLBAR|FRAME_RAISED);

  // Color
  new FXButton(toolbar,"&Colors\tColors\tDisplay color dialog.",paletteicon,colordlg,FXWindow::ID_SHOW,ICON_ABOVE_TEXT|BUTTON_TOOLBAR|FRAME_RAISED|LAYOUT_RIGHT);

  // File Menu entries
  new FXMenuCommand(filemenu,"&Open...\tCtl-O\tOpen image file.",fileopenicon,this,ID_OPEN);
  new FXMenuCommand(filemenu,"&Save...\tCtl-S\tSave image file.",filesaveicon,this,ID_SAVE);
  new FXMenuCommand(filemenu,"Dump",nullptr,getApp(),FXApp::ID_DUMP);

  // Recent file menu; this automatically hides if there are no files
  new FXMenuSeparator(filemenu,&mrufiles,FXRecentFiles::ID_ANYFILES);
  new FXMenuCommand(filemenu,"&1",nullptr,&mrufiles,FXRecentFiles::ID_FILE_1);
  new FXMenuCommand(filemenu,"&2",nullptr,&mrufiles,FXRecentFiles::ID_FILE_2);
  new FXMenuCommand(filemenu,"&3",nullptr,&mrufiles,FXRecentFiles::ID_FILE_3);
  new FXMenuCommand(filemenu,"&4",nullptr,&mrufiles,FXRecentFiles::ID_FILE_4);
  new FXMenuCommand(filemenu,"&5",nullptr,&mrufiles,FXRecentFiles::ID_FILE_5);
  new FXMenuCommand(filemenu,"&6",nullptr,&mrufiles,FXRecentFiles::ID_FILE_6);
  new FXMenuCommand(filemenu,"&7",nullptr,&mrufiles,FXRecentFiles::ID_FILE_7);
  new FXMenuCommand(filemenu,"&8",nullptr,&mrufiles,FXRecentFiles::ID_FILE_8);
  new FXMenuCommand(filemenu,"&9",nullptr,&mrufiles,FXRecentFiles::ID_FILE_9);
  new FXMenuCommand(filemenu,"1&0",nullptr,&mrufiles,FXRecentFiles::ID_FILE_10);
  new FXMenuCommand(filemenu,"&Clear Recent Files",nullptr,&mrufiles,FXRecentFiles::ID_CLEAR);
  new FXMenuSeparator(filemenu,&mrufiles,FXRecentFiles::ID_ANYFILES);
  new FXMenuCommand(filemenu,"&Quit\tCtl-Q",nullptr,this,ID_QUIT);

  // Edit Menu entries
  new FXMenuCommand(editmenu,"&Undo\tCtl-Z\tUndo last change.");
  new FXMenuCommand(editmenu,"&Redo\tCtl-R\tRedo last undo.");
  new FXMenuCommand(editmenu,"&Copy\tCtl-C\tCopy selection to clipboard.",copyicon,nullptr,0);
  new FXMenuCommand(editmenu,"C&ut\tCtl-X\tCut selection to clipboard.",cuticon,nullptr,0);
  new FXMenuCommand(editmenu,"&Paste\tCtl-V\tPaste from clipboard.",pasteicon,nullptr,0);
  new FXMenuCommand(editmenu,"&Delete\t\tDelete selection.",nullptr,nullptr,0);

  // Manipulation Menu entries
  new FXMenuCommand(manipmenu,"Rotate 90\t\tRotate 90 degrees.",nullptr,this,ID_ROTATE_90);
  new FXMenuCommand(manipmenu,"Rotate 180\t\tRotate 180 degrees.",nullptr,this,ID_ROTATE_180);
  new FXMenuCommand(manipmenu,"Rotate -90\t\tRotate -90 degrees.",nullptr,this,ID_ROTATE_270);
  new FXMenuCommand(manipmenu,"Mirror Horizontally\t\tMirror Horizontally.",nullptr,this,ID_MIRROR_HOR);
  new FXMenuCommand(manipmenu,"Mirror Vertically\t\tMirror Vertically.",nullptr,this,ID_MIRROR_VER);
  new FXMenuCommand(manipmenu,"Mirror Both\t\tMirror Both.",nullptr,this,ID_MIRROR_BOTH);
  new FXMenuCommand(manipmenu,"Scale...\t\tScale image.",nullptr,this,ID_SCALE);
  new FXMenuCommand(manipmenu,"Crop...\t\tCrop image.",nullptr,this,ID_CROP);

  // View Menu entries
  new FXMenuCheck(viewmenu,"File list\t\tDisplay file list.",filebox,FXWindow::ID_TOGGLESHOWN);
  new FXMenuCommand(viewmenu,"Show hidden files\t\tShow hidden files and directories.",nullptr,filelist,FXFileList::ID_TOGGLE_HIDDEN);
  new FXMenuCommand(viewmenu,"Show small icons\t\tDisplay directory with small icons.",nullptr,filelist,FXFileList::ID_SHOW_MINI_ICONS);
  new FXMenuCommand(viewmenu,"Show big icons\t\tDisplay directory with big icons.",nullptr,filelist,FXFileList::ID_SHOW_BIG_ICONS);
  new FXMenuCommand(viewmenu,"Show details view\t\tDisplay detailed directory listing.",nullptr,filelist,FXFileList::ID_SHOW_DETAILS);
  new FXMenuCommand(viewmenu,"Rows of icons\t\tView row-wise.",nullptr,filelist,FXFileList::ID_ARRANGE_BY_ROWS);
  new FXMenuCommand(viewmenu,"Columns of icons\t\tView column-wise.",nullptr,filelist,FXFileList::ID_ARRANGE_BY_COLUMNS);
  new FXMenuCheck(viewmenu,"Toolbar\t\tDisplay toolbar.",toolbar,FXWindow::ID_TOGGLESHOWN);
  new FXMenuCommand(viewmenu,"Float toolbar\t\tUndock the toolbar.",nullptr,toolbar,FXToolBar::ID_DOCK_FLOAT);
  new FXMenuCommand(viewmenu,"Dock toolbar top\t\tDock the toolbar on the top.",nullptr,toolbar,FXToolBar::ID_DOCK_TOP);
  new FXMenuCommand(viewmenu,"Dock toolbar left\t\tDock the toolbar on the left.",nullptr,toolbar,FXToolBar::ID_DOCK_LEFT);
  new FXMenuCommand(viewmenu,"Dock toolbar right\t\tDock the toolbar on the right.",nullptr,toolbar,FXToolBar::ID_DOCK_RIGHT);
  new FXMenuCommand(viewmenu,"Dock toolbar bottom\t\tDock the toolbar on the bottom.",nullptr,toolbar,FXToolBar::ID_DOCK_BOTTOM);
  new FXMenuCheck(viewmenu,"Status line\t\tDisplay status line.",statusbar,FXWindow::ID_TOGGLESHOWN);

  // Help Menu entries
  new FXMenuCommand(helpmenu,"&About FOX...",nullptr,this,ID_ABOUT,0);

  // Make a tool tip
  new FXToolTip(getApp(),TOOLTIP_NORMAL);

  // Recent files
  mrufiles.setTarget(this);
  mrufiles.setSelector(ID_RECENTFILE);

  // Initialize file name
  filename="untitled";
  }


// Clean up
ImageWindow::~ImageWindow(){
  delete filemenu;
  delete editmenu;
  delete manipmenu;
  delete helpmenu;
  delete viewmenu;
  delete fileopenicon;
  delete filesaveicon;
  delete cuticon;
  delete copyicon;
  delete pasteicon;
  delete uplevelicon;
  delete paletteicon;
  }


// About box
long ImageWindow::onCmdAbout(FXObject*,FXSelector,void*){
  FXMessageBox about(this,"About Image Viewer","Image Viewer demonstrates the FOX ImageView widget.\n\nUsing the FOX C++ GUI Library (http://www.fox-toolkit.org)\n\nCopyright (C) 2000,2022 Jeroen van der Zijp (jeroen@fox-toolkit.net)",nullptr,MBOX_OK|DECOR_TITLE|DECOR_BORDER);
  about.execute();
  return 1;
  }


// Load file
FXbool ImageWindow::loadimage(const FXString& file){
  FXString ext=FXPath::extension(file);
  FXImage *img=nullptr;
  FXImage *old;
  if(FXString::comparecase(ext,"gif")==0){
    img=new FXGIFImage(getApp(),nullptr,IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP);
    }
  else if(FXString::comparecase(ext,"bmp")==0){
    img=new FXBMPImage(getApp(),nullptr,IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP);
    }
  else if(FXString::comparecase(ext,"xpm")==0){
    img=new FXXPMImage(getApp(),nullptr,IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP);
    }
  else if(FXString::comparecase(ext,"pcx")==0){
    img=new FXPCXImage(getApp(),nullptr,IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP);
    }
  else if(FXString::comparecase(ext,"ico")==0 || FXString::comparecase(ext,"cur")==0){
    img=new FXICOImage(getApp(),nullptr,IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP);
    }
  else if(FXString::comparecase(ext,"tga")==0){
    img=new FXTGAImage(getApp(),nullptr,IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP);
    }
  else if(FXString::comparecase(ext,"rgb")==0){
    img=new FXRGBImage(getApp(),nullptr,IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP);
    }
  else if(FXString::comparecase(ext,"xbm")==0){
    img=new FXXBMImage(getApp(),nullptr,nullptr,IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP);
    }
  else if(FXString::comparecase(ext,"ppm")==0 || FXString::comparecase(ext,"pbm")==0 || FXString::comparecase(ext,"pgm")==0 || FXString::comparecase(ext,"pnm")==0){
    img=new FXPPMImage(getApp(),nullptr,IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP);
    }
  else if(FXString::comparecase(ext,"iff")==0 || FXString::comparecase(ext,"lbm")==0){
    img=new FXIFFImage(getApp(),nullptr,IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP);
    }
  else if(FXString::comparecase(ext,"ras")==0){
    img=new FXRASImage(getApp(),nullptr,IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP);
    }
  else if(FXString::comparecase(ext,"png")==0){
    img=new FXPNGImage(getApp(),nullptr,IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP,0,0,PNG_FILTER_BEST|PNG_IMAGE_ANALYZE|PNG_INDEX_COLOR);
//    img=new FXPNGImage(getApp(),nullptr,IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP,0,0,PNG_FILTER_BEST|PNG_IMAGE_ANALYZE);
//    img=new FXPNGImage(getApp(),nullptr,IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP,0,0,PNG_FILTER_BEST);
    }
  else if(FXString::comparecase(ext,"jpg")==0 || FXString::comparecase(ext,"jpeg")==0){
    img=new FXJPGImage(getApp(),nullptr,IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP);
    }
  else if(FXString::comparecase(ext,"jp2")==0){
    img=new FXJP2Image(getApp(),nullptr,IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP);
    }
  else if(FXString::comparecase(ext,"tif")==0 || FXString::comparecase(ext,"tiff")==0){
    img=new FXTIFImage(getApp(),nullptr,IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP);
    }
  else if(FXString::comparecase(ext,"dds")==0){
    img=new FXDDSImage(getApp(),nullptr,IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP);
    }
  else if(FXString::comparecase(ext,"webp")==0){
    img=new FXWEBPImage(getApp(),nullptr,IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP);
    }
  else if(FXString::comparecase(ext,"exe")==0 || FXString::comparecase(ext,"dll")==0){
    img=new FXEXEImage(getApp(),nullptr,IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP);
    }
  else if(FXString::comparecase(ext,"qoi")==0 || FXString::comparecase(ext,"qoif")==0){
    img=new FXQOIFImage(getApp(),nullptr,IMAGE_KEEP|IMAGE_SHMI|IMAGE_SHMP);
    }

  // Perhaps failed
  if(img==nullptr){
    FXMessageBox::error(this,MBOX_OK,"Error Loading Image","Unsupported type: %s",ext.text());
    return false;
    }

  // Load it
  FXFileStream stream;
  if(stream.open(file,FXStreamLoad)){
    getApp()->beginWaitCursor();
    img->loadPixels(stream);
    stream.close();

//img->gradient(FXRGB(255,0,0),FXRGB(0,255,0),FXRGB(0,0,255),FXRGB(255,255,255));
//img->gradient(FXRGB(255,0,0),FXRGB(255,255,0),FXRGB(0,0,255),FXRGB(255,0,255));
//img->hgradient(FXRGB(255,0,0),FXRGB(0,0,255));
//img->vgradient(FXRGB(255,0,0),FXRGB(0,0,255));
//img->fill(FXRGB(255,0,0));
//img->fade(FXRGB(255,255,255),128);
//img->blend(getApp()->getBaseColor());
//img->blend(FXRGB(255,128,255));
//img->xshear(-30*256,FXRGB(0,255,128));
//img->yshear(-50*256,FXRGB(0,255,128));

    img->create();
    old=imageview->getImage();
    imageview->setImage(img);
    delete old;
    getApp()->endWaitCursor();
    }
  return true;
  }


// Save file
FXbool ImageWindow::saveimage(const FXString& file){
  FXImage *img;
  FXFileStream stream;
  if(stream.open(file,FXStreamSave)){
    getApp()->beginWaitCursor();
    img=imageview->getImage();///// We may want to save in another format...
    img->savePixels(stream);
    stream.close();
    getApp()->endWaitCursor();
    }
  return true;
  }


// Open
long ImageWindow::onCmdOpen(FXObject*,FXSelector,void*){
  FXFileDialog open(this,"Open Image");
  open.setFilename(filename);
  open.setPatternList(patterns);
  if(open.execute()){
    filename=open.getFilename();
    filelist->setCurrentFile(filename);
    mrufiles.appendFile(filename);
    loadimage(filename);
    }
  return 1;
  }


// Save
long ImageWindow::onCmdSave(FXObject*,FXSelector,void*){
  FXFileDialog savedialog(this,"Save Image");
  savedialog.setFilename(filename);
  if(savedialog.execute()){
    if(FXStat::exists(savedialog.getFilename())){
      if(MBOX_CLICKED_NO==FXMessageBox::question(this,MBOX_YES_NO,"Overwrite Image","Overwrite existing image?")) return 1;
      }
    filename=savedialog.getFilename();
    filelist->setCurrentFile(filename);
    mrufiles.appendFile(filename);
    saveimage(filename);
    }
  return 1;
  }


// Quit
long ImageWindow::onCmdQuit(FXObject*,FXSelector,void*){
  FXTRACE((100,"Quit\n"));

  // Write new window size back to registry
  getApp()->reg().writeIntEntry("SETTINGS","x",getX());
  getApp()->reg().writeIntEntry("SETTINGS","y",getY());
  getApp()->reg().writeIntEntry("SETTINGS","width",getWidth());
  getApp()->reg().writeIntEntry("SETTINGS","height",getHeight());

  // Width of tree
  getApp()->reg().writeIntEntry("SETTINGS","fileheight",filebox->getHeight());

  // Was file box shown
  getApp()->reg().writeIntEntry("SETTINGS","filesshown",filebox->shown());

  FXString dir=filelist->getDirectory();
  getApp()->reg().writeStringEntry("SETTINGS","directory",dir.text());

  // Quit
  getApp()->exit(0);
  return 1;
  }


// Update title
long ImageWindow::onUpdTitle(FXObject* sender,FXSelector,void*){
  FXString caption("FOX Image Viewer:- " + filename);
  FXImage* image=imageview->getImage();
  if(image){
    caption.format("FOX Image Viewer:- %s (%d x %d)",filename.text(),image->getWidth(),image->getHeight());
    }
  sender->handle(this,FXSEL(SEL_COMMAND,FXWindow::ID_SETSTRINGVALUE),(void*)&caption);
  return 1;
  }


// Open recent file
long ImageWindow::onCmdRecentFile(FXObject*,FXSelector,void* ptr){
  filename=(FXchar*)ptr;
  filelist->setCurrentFile(filename);
  loadimage(filename);
  return 1;
  }


// Command from the tree list
long ImageWindow::onCmdFileList(FXObject*,FXSelector,void* ptr){
  FXint index=(FXint)(FXival)ptr;
  if(0<=index){
    if(filelist->isItemDirectory(index)){
      filelist->setDirectory(filelist->getItemPathname(index));
      }
    else if(filelist->isItemFile(index)){
      filename=filelist->getItemPathname(index);
      mrufiles.appendFile(filename);
      loadimage(filename);
      }
    }
  return 1;
  }


// Rotate image
long ImageWindow::onCmdRotate(FXObject*,FXSelector sel,void*){
  FXImage* image=imageview->getImage();
  FXASSERT(image);
  switch(FXSELID(sel)){
    case ID_ROTATE_90: image->rotate(90); break;
    case ID_ROTATE_180: image->rotate(180); break;
    case ID_ROTATE_270: image->rotate(270); break;
    }
  imageview->setImage(image);
  return 1;
  }


// Update image
long ImageWindow::onUpdImage(FXObject* sender,FXSelector,void*){
  if(imageview->getImage())
    sender->handle(this,FXSEL(SEL_COMMAND,FXWindow::ID_ENABLE),nullptr);
  else
    sender->handle(this,FXSEL(SEL_COMMAND,FXWindow::ID_DISABLE),nullptr);
  return 1;
  }


// Mirror
long ImageWindow::onCmdMirror(FXObject*,FXSelector sel,void*){
  FXImage* image=imageview->getImage();
  FXASSERT(image);
  switch(FXSELID(sel)){
    case ID_MIRROR_HOR: image->mirror(true,false); break;
    case ID_MIRROR_VER: image->mirror(false,true); break;
    case ID_MIRROR_BOTH: image->mirror(true,true); break;
    }
  imageview->setImage(image);
  return 1;
  }


// Scale
long ImageWindow::onCmdScale(FXObject*,FXSelector,void*){
  FXImage* image=imageview->getImage();
  FXASSERT(image);
  FXint sx=image->getWidth();
  FXint sy=image->getHeight();
  FXint sq=1;                   //: ADDED by Amanda Ross To reflect new feature since V1.1.32
  FXDataTarget sxtarget(sx);
  FXDataTarget sytarget(sy);
  FXDataTarget sqtarget(sq);    //: ADDED by Amanda Ross To reflect new feature since V1.1.32
  FXDialogBox  scalepanel(this,"Scale Image To Size");
  FXHorizontalFrame* frame=new FXHorizontalFrame(&scalepanel,LAYOUT_SIDE_TOP|LAYOUT_FILL_X|LAYOUT_FILL_Y);
  new FXLabel(frame,"W:",nullptr,LAYOUT_CENTER_Y);
  new FXTextField(frame,5,&sxtarget,FXDataTarget::ID_VALUE,LAYOUT_CENTER_Y|FRAME_SUNKEN|FRAME_THICK|JUSTIFY_RIGHT);
  new FXLabel(frame,"H:",nullptr,LAYOUT_CENTER_Y);
  new FXTextField(frame,5,&sytarget,FXDataTarget::ID_VALUE,LAYOUT_CENTER_Y|FRAME_SUNKEN|FRAME_THICK|JUSTIFY_RIGHT);
  new FXLabel(frame,"Q:",nullptr,LAYOUT_CENTER_Y); //: ADDED by Amanda Ross To reflect new feature since V1.1.32
  FXSpinner *spinner=new FXSpinner(frame,2,&sqtarget,FXDataTarget::ID_VALUE,SPIN_NORMAL|FRAME_SUNKEN|FRAME_THICK|LAYOUT_SIDE_TOP);
  spinner->setRange(0,1);
  spinner->setValue(1);
  new FXButton(frame,"Cancel",nullptr,&scalepanel,FXDialogBox::ID_CANCEL,LAYOUT_CENTER_Y|FRAME_RAISED|FRAME_THICK,0,0,0,0, 20,20,4,4);
  new FXButton(frame,"OK",nullptr,&scalepanel,FXDialogBox::ID_ACCEPT,LAYOUT_CENTER_Y|FRAME_RAISED|FRAME_THICK,0,0,0,0, 30,30,4,4);
  if(!scalepanel.execute()) return 1;
  if(sx<1 || sy<1) return 1;
  image->scale(sx,sy,FXCLAMP(0,sq,1));
  imageview->setImage(image);
  return 1;
  }


// Crop
long ImageWindow::onCmdCrop(FXObject*,FXSelector,void*){
  FXImage* image=imageview->getImage();
  FXASSERT(image);
  FXint cx=0;
  FXint cy=0;
  FXint cw=image->getWidth();
  FXint ch=image->getHeight();
  FXDataTarget cxtarget(cx);
  FXDataTarget cytarget(cy);
  FXDataTarget cwtarget(cw);
  FXDataTarget chtarget(ch);
  FXDialogBox  croppanel(this,"Crop image");
  FXHorizontalFrame* frame=new FXHorizontalFrame(&croppanel,LAYOUT_SIDE_TOP|LAYOUT_FILL_X|LAYOUT_FILL_Y);
  new FXLabel(frame,"X:",nullptr,LAYOUT_CENTER_Y);
  new FXTextField(frame,5,&cxtarget,FXDataTarget::ID_VALUE,LAYOUT_CENTER_Y|FRAME_SUNKEN|FRAME_THICK|JUSTIFY_RIGHT);
  new FXLabel(frame,"Y:",nullptr,LAYOUT_CENTER_Y);
  new FXTextField(frame,5,&cytarget,FXDataTarget::ID_VALUE,LAYOUT_CENTER_Y|FRAME_SUNKEN|FRAME_THICK|JUSTIFY_RIGHT);
  new FXLabel(frame,"W:",nullptr,LAYOUT_CENTER_Y);
  new FXTextField(frame,5,&cwtarget,FXDataTarget::ID_VALUE,LAYOUT_CENTER_Y|FRAME_SUNKEN|FRAME_THICK|JUSTIFY_RIGHT);
  new FXLabel(frame,"H:",nullptr,LAYOUT_CENTER_Y);
  new FXTextField(frame,5,&chtarget,FXDataTarget::ID_VALUE,LAYOUT_CENTER_Y|FRAME_SUNKEN|FRAME_THICK|JUSTIFY_RIGHT);
  new FXButton(frame,"Cancel",nullptr,&croppanel,FXDialogBox::ID_CANCEL,LAYOUT_CENTER_Y|FRAME_RAISED|FRAME_THICK,0,0,0,0, 20,20,4,4);
  new FXButton(frame,"OK",nullptr,&croppanel,FXDialogBox::ID_ACCEPT,LAYOUT_CENTER_Y|FRAME_RAISED|FRAME_THICK,0,0,0,0, 30,30,4,4);
  if(!croppanel.execute()) return 1;
  if(cx+cw<=0 || cy+ch<=0 || cx>=image->getWidth() || cy>=image->getHeight()) return 1;
  image->crop(cx,cy,cw,ch);
  imageview->setImage(image);
  return 1;
  }


// Create and show window
void ImageWindow::create(){
  FXint ww,hh,xx,yy,fh,fs;
  FXString dir;

  // Get size
  xx=getApp()->reg().readIntEntry("SETTINGS","x",600);
  yy=getApp()->reg().readIntEntry("SETTINGS","y",400);
  ww=getApp()->reg().readIntEntry("SETTINGS","width",600);
  hh=getApp()->reg().readIntEntry("SETTINGS","height",400);

  fh=getApp()->reg().readIntEntry("SETTINGS","fileheight",100);
  fs=getApp()->reg().readIntEntry("SETTINGS","filesshown",true);

  dir=getApp()->reg().readStringEntry("SETTINGS","directory","~");
  filelist->setDirectory(dir);

  filebox->setHeight(fh);

  // Hide tree if asked for
  if(!fs) filebox->hide();

  // Reposition window
  position(xx,yy,ww,hh);

  FXMainWindow::create();
  show();
  }


/*******************************************************************************/


// Start the whole thing
int main(int argc,char *argv[]){

  // Make application
  FXApp application("ImageViewer","FoxTest");

  // Open display
  application.init(argc,argv);

  // Make window
  ImageWindow* window=new ImageWindow(&application);

  // Create it
  application.create();

  // Passed image file?
  if(argc>1) window->loadimage(argv[1]);

  // Run
  return application.run();
  }


