#
#
#            Nim's Runtime Library
#        (c) Copyright 2012 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#


## This module implements portable dialogs for Nim; the implementation
## builds on the GTK interface. On Windows, native dialogs are shown instead.

import
  glib2, gtk2

when defined(Windows):
  import windows, ShellAPI, os

proc info*(window: PWindow, msg: string) =
  ## Shows an information message to the user. The process waits until the
  ## user presses the OK button.
  when defined(Windows):
    discard MessageBoxA(0, msg, "Information", MB_OK or MB_ICONINFORMATION)
  else:
    var dialog = message_dialog_new(window,
                DIALOG_MODAL or DIALOG_DESTROY_WITH_PARENT,
                MESSAGE_INFO, BUTTONS_OK, "%s", cstring(msg))
    setTitle(dialog, "Information")
    discard run(dialog)
    destroy(PWidget(dialog))

proc warning*(window: PWindow, msg: string) =
  ## Shows a warning message to the user. The process waits until the user
  ## presses the OK button.
  when defined(Windows):
    discard MessageBoxA(0, msg, "Warning", MB_OK or MB_ICONWARNING)
  else:
    var dialog = DIALOG(message_dialog_new(window,
                DIALOG_MODAL or DIALOG_DESTROY_WITH_PARENT,
                MESSAGE_WARNING, BUTTONS_OK, "%s", cstring(msg)))
    setTitle(dialog, "Warning")
    discard run(dialog)
    destroy(PWidget(dialog))

proc error*(window: PWindow, msg: string) =
  ## Shows an error message to the user. The process waits until the user
  ## presses the OK button.
  when defined(Windows):
    discard MessageBoxA(0, msg, "Error", MB_OK or MB_ICONERROR)
  else:
    var dialog = DIALOG(message_dialog_new(window,
                DIALOG_MODAL or DIALOG_DESTROY_WITH_PARENT,
                MESSAGE_ERROR, BUTTONS_OK, "%s", cstring(msg)))
    setTitle(dialog, "Error")
    discard run(dialog)
    destroy(PWidget(dialog))


proc chooseFileToOpen*(window: PWindow, root: string = ""): string =
  ## Opens a dialog that requests a filename from the user. Returns ""
  ## if the user closed the dialog without selecting a file. On Windows,
  ## the native dialog is used, else the GTK dialog is used.
  when defined(Windows):
    var
      opf: TOPENFILENAME
      buf: array [0..2047, char]
    opf.lStructSize = sizeof(opf).int32
    if root.len > 0:
      opf.lpstrInitialDir = root
    opf.lpstrFilter = "All Files\0*.*\0\0"
    opf.flags = OFN_FILEMUSTEXIST
    opf.lpstrFile = buf
    opf.nMaxFile = sizeof(buf).int32
    var res = GetOpenFileName(addr(opf))
    if res != 0:
      result = $buf
    else:
      result = ""
  else:
    var chooser = file_chooser_dialog_new("Open File", window,
                FILE_CHOOSER_ACTION_OPEN, 
                STOCK_CANCEL, RESPONSE_CANCEL,
                STOCK_OPEN, RESPONSE_OK, nil)
    if root.len > 0:
      discard set_current_folder(chooser, root)
    if run(chooser) == cint(RESPONSE_OK):
      var x = get_filename(chooser)
      result = $x
      g_free(x)
    else:
      result = ""
    destroy(PWidget(chooser))

proc chooseFilesToOpen*(window: PWindow, root: string = ""): seq[string] =
  ## Opens a dialog that requests filenames from the user. Returns ``@[]``
  ## if the user closed the dialog without selecting a file. On Windows,
  ## the native dialog is used, else the GTK dialog is used.
  when defined(Windows):
    var
      opf: TOPENFILENAME
      buf: array [0..2047*4, char]
    opf.lStructSize = sizeof(opf).int32
    if root.len > 0:
      opf.lpstrInitialDir = root
    opf.lpstrFilter = "All Files\0*.*\0\0"
    opf.flags = OFN_FILEMUSTEXIST or OFN_ALLOWMULTISELECT or OFN_EXPLORER
    opf.lpstrFile = buf
    opf.nMaxFile = sizeof(buf).int32
    var res = GetOpenFileName(addr(opf))
    result = @[]
    if res != 0:
      # parsing the result is horrible:
      var
        i = 0
        s: string
        path = ""
      while buf[i] != '\0':
        add(path, buf[i])
        inc(i)
      inc(i)
      if buf[i] != '\0':
        while true:
          s = ""
          while buf[i] != '\0':
            add(s, buf[i])
            inc(i)
          add(result, s)
          inc(i)
          if buf[i] == '\0': break
        for i in 0..result.len-1: result[i] = os.joinPath(path, result[i])
      else:
        # only one file selected --> gosh, what an ungly thing 
        # the windows API is
        add(result, path) 
  else:
    var chooser = file_chooser_dialog_new("Open Files", window,
                FILE_CHOOSER_ACTION_OPEN,
                STOCK_CANCEL, RESPONSE_CANCEL,
                STOCK_OPEN, RESPONSE_OK, nil)
    if root.len > 0:
      discard set_current_folder(chooser, root)
    set_select_multiple(chooser, true)
    result = @[]
    if run(chooser) == cint(RESPONSE_OK):
      var L = get_filenames(chooser)
      var it = L
      while it != nil:
        add(result, $cast[cstring](it.data))
        g_free(it.data)
        it = it.next
      free(L)
    destroy(PWidget(chooser))


proc chooseFileToSave*(window: PWindow, root: string = ""): string =
  ## Opens a dialog that requests a filename to save to from the user.
  ## Returns "" if the user closed the dialog without selecting a file.
  ## On Windows, the native dialog is used, else the GTK dialog is used.
  when defined(Windows):
    var
      opf: TOPENFILENAME
      buf: array [0..2047, char]
    opf.lStructSize = sizeof(opf).int32
    if root.len > 0:
      opf.lpstrInitialDir = root
    opf.lpstrFilter = "All Files\0*.*\0\0"
    opf.flags = OFN_OVERWRITEPROMPT
    opf.lpstrFile = buf
    opf.nMaxFile = sizeof(buf).int32
    var res = GetSaveFileName(addr(opf))
    if res != 0:
      result = $buf
    else:
      result = ""
  else:
    var chooser = file_chooser_dialog_new("Save File", window,
                FILE_CHOOSER_ACTION_SAVE,
                STOCK_CANCEL, RESPONSE_CANCEL,
                STOCK_SAVE, RESPONSE_OK, nil)
    if root.len > 0:
      discard set_current_folder(chooser, root)
    set_do_overwrite_confirmation(chooser, true)
    if run(chooser) == cint(RESPONSE_OK):
      var x = get_filename(chooser)
      result = $x
      g_free(x)
    else:
      result = ""
    destroy(PWidget(chooser))


proc chooseDir*(window: PWindow, root: string = ""): string =
  ## Opens a dialog that requests a directory from the user.
  ## Returns "" if the user closed the dialog without selecting a directory.
  ## On Windows, the native dialog is used, else the GTK dialog is used.
  when defined(Windows):
    var
      lpItemID: PItemIDList
      BrowseInfo: TBrowseInfo
      DisplayName: array [0..MAX_PATH, char]
      TempPath: array [0..MAX_PATH, char]
    result = ""
    #BrowseInfo.hwndOwner = Application.Handle
    BrowseInfo.pszDisplayName = DisplayName
    BrowseInfo.ulFlags = 1 #BIF_RETURNONLYFSDIRS
    lpItemID = SHBrowseForFolder(cast[LPBrowseInfo](addr(BrowseInfo)))
    if lpItemId != nil:
      discard SHGetPathFromIDList(lpItemID, TempPath)
      result = $TempPath
      discard GlobalFreePtr(lpItemID)
  else:
    var chooser = file_chooser_dialog_new("Select Directory", window,
                FILE_CHOOSER_ACTION_SELECT_FOLDER,
                STOCK_CANCEL, RESPONSE_CANCEL,
                STOCK_OPEN, RESPONSE_OK, nil)
    if root.len > 0:
      discard set_current_folder(chooser, root)
    if run(chooser) == cint(RESPONSE_OK):
      var x = get_filename(chooser)
      result = $x
      g_free(x)
    else:
      result = ""
    destroy(PWidget(chooser))

