/* Copyright 1997 Acorn Computers Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/***************************************************/
/* File   : OpenURL.c                              */
/*                                                 */
/* Purpose: Functions relating to the Open URL     */
/*          dialogue box.                          */
/*                                                 */
/* Author : A.D.Hodgkinson                         */
/*                                                 */
/* History: 17-Apr-97: Created.                    */
/***************************************************/

#include <stdlib.h>
#include <string.h>

#include "swis.h"

#include "HTMLLib.h" /* HTML library API, Which will include html2_ext.h, tags.h and struct.h */

#include "wimp.h"
#include "wimplib.h"
#include "event.h"

#include "toolbox.h"
#include "window.h"

#include "svcprint.h"
#include "Global.h"
#include "FromROSLib.h"
#include "MiscDefs.h"
#include "Utils.h"

#include "Browser.h"
#include "Fetch.h" /* (For ISLINK macro) */
#include "FetchPage.h"
#include "History.h"
#include "Hotlist.h"
#include "Menus.h"
#include "URLutils.h"
#include "Windows.h"

#include "OpenURL.h"

/* Local structures */

typedef struct openurl_contents
{
  char         url[Limits_URLBarWrit];

  /* Where to open the URL to */

  unsigned int in_this   :1;
  unsigned int save_link :1;
  unsigned int in_new    :1;
  unsigned int in_parent :1;

} openurl_contents;

/* Local statics */

// We *really* want to be mallocing this...
static openurl_contents   contents;        /* Remember the old dialogue contents so the Cancel button can work */

static ObjectId           window_id   = 0; /* Remember the ID in case it needs closing 'out of the blue'. */
static ObjectId           ancestor_id = 0; /* Remember the ancestor ID in case the ancestor closes. */

static HStream          * open_hst = NULL; /* See openurl_to_show_from_menu */

/* Static function prototypes */

static _kernel_oserror * openurl_read_contents   (ObjectId dialogue, openurl_contents * contents);
static _kernel_oserror * openurl_set_contents    (ObjectId dialogue, openurl_contents * contents);

static int               openurl_ok              (int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle);
static int               openurl_cancel          (int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle);
static int               openurl_radio_group_one (int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle);
static int               openurl_click           (int eventcode, WimpPollBlock * b, IdBlock * idb, void * handle);
static int               openurl_key_handler     (int eventcode, WimpPollBlock * b, IdBlock * idb, void * handle);

/*************************************************/
/* openurl_read_contents()                       */
/*                                               */
/* Reads the contents of the Open URL dialogue   */
/* into an openurl_contents structure.           */
/*                                               */
/* Parameters: Object ID of the dialogue;        */
/*                                               */
/*             Pointer to the structure to write */
/*             to.                               */
/*************************************************/

static _kernel_oserror * openurl_read_contents(ObjectId dialogue, openurl_contents * contents)
{
  _kernel_oserror * e;
  int               state;

  /* Read the URL string */

  *contents->url = 0;

  e = writablefield_get_value(0,
                              dialogue,
                              OpenWrit,
                              contents->url,
                              sizeof(contents->url),
                              NULL);

  contents->url[sizeof(contents->url) - 1] = 0; /* (Ensure termination) */

  if (e) return e;

  /* Read radio group 1 - where to open the URL */

  RetError(radiobutton_get_state(0, dialogue, OpenInThis,   &state, NULL)); contents->in_this   = !!state;
  RetError(radiobutton_get_state(0, dialogue, OpenSaveLink, &state, NULL)); contents->save_link = !!state;
  RetError(radiobutton_get_state(0, dialogue, OpenInNew,    &state, NULL)); contents->in_new    = !!state;
  RetError(radiobutton_get_state(0, dialogue, OpenInParent, &state, NULL)); contents->in_parent = !!state;

  /* Finished */

  return NULL;
}

/*************************************************/
/* openurl_set_contents()                        */
/*                                               */
/* Sets the contents of the Open URL dialogue    */
/* from an openurl_contents structure.           */
/*                                               */
/* Parameters: Object ID of the dialogue;        */
/*                                               */
/*             Pointer to the structure to read  */
/*             from.                             */
/*************************************************/

static _kernel_oserror * openurl_set_contents(ObjectId dialogue, openurl_contents * contents)
{
  /* The URL entry field */

  RetError(writablefield_set_value(0,
                                   dialogue,
                                   OpenWrit,
                                   contents->url));

  /* Radio group 1 - where to open the URL */

  RetError(radiobutton_set_state(0, dialogue, OpenInThis,   contents->in_this));
  RetError(radiobutton_set_state(0, dialogue, OpenSaveLink, contents->save_link));
  RetError(radiobutton_set_state(0, dialogue, OpenInNew,    contents->in_new));
  return   radiobutton_set_state(0, dialogue, OpenInParent, contents->in_parent);
}

/*************************************************/
/* openurl_fill_in_url()                         */
/*                                               */
/* If an external function wants to set the URL  */
/* in the writable field of the Open URL         */
/* dialogue, this is the function to use.        */
/*                                               */
/* Parameters: Pointer to the null terminated    */
/*             URL string.                       */
/*************************************************/

_kernel_oserror * openurl_fill_in_url(char * url)
{
  if (!url || !window_id) return NULL;

  return writablefield_set_value(0,
                                 window_id,
                                 OpenWrit,
                                 url);
}

/*************************************************/
/* openurl_to_be_shown()                         */
/*                                               */
/* Called when the EOpenToBeShownMisc event is   */
/* generated, typically when the Open URL window */
/* is about to be shown. Handles any icon        */
/* processing commands in the writable,          */
/* registers event handlers, etc.                */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

int openurl_to_be_shown(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  char text[Limits_URLBarWrit];

  /* In multiuser builds, must be logged in... */

  #ifndef SINGLE_USER

    if (!logged_in)
    {
      toolbox_hide_object(0, idb->self_id);
      return 1;
    }

  #endif

  /* If the stored dialogue ID is non-zero on entry, the dialogue */
  /* was reopened without closing - so get rid of the various     */
  /* event handlers before we reregister them.                    */

  if (window_id) openurl_close(0, 1);

  /* Read the window ID and ancestor ID from the ID block */

  window_id   = idb->self_id;
  ancestor_id = idb->ancestor_id;

  /* Process the icon text */

  ChkError(windows_process_component_text(idb->self_id, OpenWrit, text, sizeof(text), 0, 1));

  /* Attach handlers for the various actions the window can perform */

  ChkError(event_register_toolbox_handler(idb->self_id, EOpenOK,     openurl_ok,              NULL));
  ChkError(event_register_toolbox_handler(idb->self_id, EOpenCancel, openurl_cancel,          NULL));
  ChkError(event_register_toolbox_handler(idb->self_id, EOpenRG1,    openurl_radio_group_one, NULL));

  ChkError(event_register_wimp_handler(idb->self_id,
                                       Wimp_EKeyPressed,
                                       openurl_key_handler,
                                       NULL));

  ChkError(event_register_wimp_handler(idb->self_id,
                                       Wimp_EMouseClick,
                                       openurl_click,
                                       NULL));

  /* Make sure the radios are up to date */

  openurl_radio_group_one(eventcode, event, idb, handle);

  /* Read the existing contents into the static openurl_contents block */

  ChkError(openurl_read_contents(idb->self_id, &contents));

  /* Do we have a History? */

  if (history_empty(NULL)) set_gadget_state(idb->self_id, OpenHistory, 1);
  else                     set_gadget_state(idb->self_id, OpenHistory, 0);

  /* Was the menu that generated this dialogue - if any - opened */
  /* over a link? If so, write the URL to the dialogue.          */
  /*                                                             */
  /* See openurl_to_show_from_menu for more details.             */

  if (open_hst)
  {
    if (ISLINK(open_hst))
    {
      /* Don't put internal URLs in there! */

      if (!urlutils_internal_extra(open_hst->anchor))
      {
        StrNCpy0(contents.url, open_hst->anchor);
        ChkError(openurl_set_contents(idb->self_id, &contents));
      }
    }
    else if (
              open_hst->style & IMG ||
              (
                open_hst->tagno         == TAG_INPUT &&
                HtmlINPUTtype(open_hst) == inputtype_IMAGE
              )
            )
    {
      /* If it is an image, write the image source URL to the dialogue */

      StrNCpy0(contents.url, open_hst->src);
      ChkError(openurl_set_contents(idb->self_id, &contents));
    }
    else if (ISOBJECT(open_hst))
    {
      const char   * data;
      const char   * current;
      browser_data * b = NULL;

      if (ancestor_id) toolbox_get_client_handle(0, idb->self_id, (void *) &b);
      if (b && !is_known_browser(b)) b = NULL;

      /* If it is an Object, write an appropriate URL in there, */
      /* remembering to relativise it where possible.           */

      data            = HtmlOBJECTdata(open_hst);
      if (!data) data = HtmlOBJECTcodebase(open_hst);

      if (b)
      {
        current = browser_fetch_url(b);
        if (!current) current = browser_current_url(b);

        if (current)
        {
          const char * newdata;

          newdata = HtmlRelativiseURL(current, data, open_hst);

          if (newdata) data = newdata;
        }
      }

      StrNCpy0(contents.url, data);
      ChkError(openurl_set_contents(idb->self_id, &contents));
    }

    /* Clear the value */

    open_hst = NULL;
  }

  return 1;
}

/*************************************************/
/* openurl_to_show_from_menu()                   */
/*                                               */
/* Called when the EOpenToBeShownMenu event is   */
/* generated, typically when the Open URL window */
/* is about to be shown from a menu item.        */
/*                                               */
/* To be able to show the URL of a link in the   */
/* window, menu functions have to read the       */
/* token the pointer is over and remember it.    */
/* But to ensure this doesn't get out of date,   */
/* the value is cleared when the menu is hidden. */
/*                                               */
/* Unhelpfully, the Toolbox sends events out in  */
/* an order that means the value is cleared      */
/* before the EOpenToBeShownMisc event is        */
/* raised.                                       */
/*                                               */
/* To get round that, the menu entry itself      */
/* raises EOpenToBeShownMenu, so this function   */
/* is called. It reads the value and stores it   */
/* locally - great... Two copies hanging around. */
/* Anyway, when the EOpenToBeShownMisc event     */
/* finally arrives, openurl_to_be_shown is       */
/* called, reads the value set here, acts on it, */
/* and clears the value to ensure correct        */
/* behaviour on future calls.                    */
/*                                               */
/* All this because the events arrive in a silly */
/* order. Sigh.                                  */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

int openurl_to_show_from_menu(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  open_hst = menus_document_opened_over();

  return 1;
}

/*************************************************/
/* openurl_update_popup()                        */
/*                                               */
/* Ensures that the greyed/ungreyed state of the */
/* History menu popup in the Open URL dialogue   */
/* is up to date.                                */
/*************************************************/

void openurl_update_popup(void)
{
  if (!window_id) return;

  if (history_empty(NULL)) set_gadget_state(window_id, OpenHistory, 1);
  else                     set_gadget_state(window_id, OpenHistory, 0);
}

/*************************************************/
/* openurl_ok()                                  */
/*                                               */
/* Handles clicks on the 'OK' button in the      */
/* Open URL dialogue.                            */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

static int openurl_ok(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  openurl_contents          localcontents;
  WimpGetPointerInfoBlock   info;
  browser_data            * b;
  browser_data            * ancestor;

  /* Work out where the dialogue came from */

  ChkError(wimp_get_pointer_info(&info));

  if (!idb->ancestor_id) b = ancestor = NULL;
  else
  {
    ChkError(toolbox_get_client_handle(0, idb->ancestor_id, (void *) &b));

    if (is_known_browser(b)) ancestor = b->ancestor;
    else                     ancestor = b = NULL;
  }

  /* Read the dialogue contents */

  ChkError(openurl_read_contents(idb->self_id, &localcontents));

  /* If Select was pressed, the dialogue will close, so */
  /* remember the current contents for future reference */
  /* (no button => Return was pressed)                  */

  if ((info.button_state & Wimp_MouseButtonSelect) || !info.button_state) contents = localcontents;

  /* Fetch the indicated URL */

  if (*localcontents.url)
  {
    /* If asked to open in a new window or saving the object, */
    /* open a new window (this will be a small fetch window   */
    /* in the latter case).                                   */

    if (localcontents.in_new || localcontents.save_link || !b)
    {
      ChkError(windows_create_browser(localcontents.url,
                                      NULL,
                                      NULL,
                                      NULL,
                                      localcontents.save_link ? Windows_CreateBrowser_SaveToFile : Windows_CreateBrowser_Normal));
    }

    /* Otherwise, open the URL in the browser window from which */
    /* the dialogue was obtained in the first place.            */

    else
    {
      browser_data * fetch;

      /* Work out which existing window to fetch to */

      if (localcontents.in_this || localcontents.save_link) fetch = b;
      else                                                  fetch = ancestor;

      /* Set that window's save_link and allow_cancel */
      /* flags according to the dialogue contents     */

      fetch->save_link = localcontents.save_link;
      if (fetch->save_link) b->allow_cancel = 0;

      /* Initiate the fetch */

      ChkError(fetchpage_new(fetch, localcontents.url, 1, 1));
    }
  }

  if ((info.button_state & Wimp_MouseButtonSelect) || !info.button_state)
  {
    ChkError(openurl_close(0, 0));
  }

  return 1;
}

/*************************************************/
/* openurl_cancel()                              */
/*                                               */
/* Handles clicks on the 'Cancel' button in the  */
/* Open URL dialogue.                            */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

static int openurl_cancel(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  WimpGetPointerInfoBlock info;

  /* Restore the old contents */

  ChkError(openurl_set_contents(window_id, &contents));

  /* If Select was pressed, the dialogue should close. */
  /* (No button => Escape was pressed).                */

  ChkError(wimp_get_pointer_info(&info));

  if ((info.button_state & Wimp_MouseButtonSelect) || !info.button_state)
  {
    ChkError(openurl_close(0, 0));
  }

  return 1;
}

/*************************************************/
/* openurl_close()                               */
/*                                               */
/* If the Open URL dialogue is opened, this will */
/* close it, deregistering any associated event  */
/* handlers.                                     */
/*                                               */
/* Parameters: An object ID, or 0. If not zero,  */
/*             the ID must match the ancestor    */
/*             recorded when the dialogue was    */
/*             opened or no action is taken.     */
/*                                               */
/*             0 to close the dialogue, 1 to do  */
/*             everything except that.           */
/*************************************************/

_kernel_oserror * openurl_close(ObjectId ancestor, int do_not_close)
{
  _kernel_oserror * e = NULL;

  if (ancestor && ancestor != ancestor_id) return NULL;

  if (window_id)
  {
    /* Deregister associated event handlers */

    e = event_deregister_toolbox_handlers_for_object(window_id);
    if (e) goto openurl_close_exit;

    e = event_deregister_wimp_handlers_for_object(window_id);
    if (e) goto openurl_close_exit;

    /* Restore the old contents */

    e = openurl_set_contents(window_id, &contents);
    if (e) goto openurl_close_exit;

    /* Close the dialogue */

    if (!do_not_close) e = toolbox_hide_object(0, window_id);
  }

openurl_close_exit:

  ancestor_id = window_id = 0;
  return e;
}

/*************************************************/
/* openurl_radio_group_one()                     */
/*                                               */
/* Looks at radio group one and greys or ungreys */
/* items as required. Usually used as a handler  */
/* for when the selection therein changes, but   */
/* can be used in a more general purpose way.    */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

static int openurl_radio_group_one(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  _kernel_oserror * e;
  browser_data    * b;
  browser_data    * ancestor;
  int               state;

  /* Note that few errors are reported here - this allows items to be */
  /* removed from the window without causing errors to appear.        */

  if (!idb->ancestor_id) b = ancestor = NULL;
  else
  {
    ChkError(toolbox_get_client_handle(0, idb->ancestor_id, (void *) &b));

    if (is_known_browser(b)) ancestor = b->ancestor;
    else                     ancestor = b = NULL;
  }

  /* If b is NULL, this was not opened from a browser window */

  if (!b)
  {
    /* Grey out the gadget. Try to read its state, and, if this */
    /* succeds and the gadget was selected, then select the     */
    /* 'Open in new window' gadget instead - this will never be */
    /* greyed out.                                              */

    gadget_set_flags(0,
                     idb->self_id,
                     OpenInThis,
                     Gadget_Faded);

    e = radiobutton_get_state(0,
                              idb->self_id,
                              OpenInThis,
                              &state,
                              NULL);

    if (!e && state) radiobutton_set_state(0,
                                           idb->self_id,
                                           OpenInNew,
                                           1);
  }
  else gadget_set_flags(0,
                        idb->self_id,
                        OpenInThis,
                        0);

  /* If ancestor is NULL, this was not opened from a frame */

  if (!ancestor)
  {
    gadget_set_flags(0,
                     idb->self_id,
                     OpenInParent,
                     Gadget_Faded);

    e = radiobutton_get_state(0,
                              idb->self_id,
                              OpenInParent,
                              &state,
                              NULL);

    if (!e && state) radiobutton_set_state(0,
                                           idb->self_id,
                                           OpenInNew,
                                           1);
  }
  else gadget_set_flags(0,
                        idb->self_id,
                        OpenInParent,
                        0);
  return 1;
}

/*************************************************/
/* openurl_click()                               */
/*                                               */
/* Handles Wimp mouse click events in the Open   */
/* URL dialogue.                                 */
/*                                               */
/* Parameters are as standard for a Wimp event   */
/* handler.                                      */
/*************************************************/

static int openurl_click(int eventcode, WimpPollBlock * b, IdBlock * idb, void * handle)
{
  int used = 0;

  switch (idb->self_component)
  {
    case OpenHistory:
    {
      ChkError(history_menu_popup(NULL,
                                  idb->self_id,
                                  idb->self_component,
                                  1,
                                  b->mouse_click.buttons & Wimp_MouseButtonAdjust ? !choices.show_urls : choices.show_urls));
      used = 1;
    }
    break;
  }

  return used;
}

/*************************************************/
/* openurl_key_handler()                         */
/*                                               */
/* Handles a few key pressed events in the Open  */
/* URL dialogue.                                 */
/*                                               */
/* Parameters are as standard for a Wimp event   */
/* handler.                                      */
/*************************************************/

static int openurl_key_handler(int eventcode, WimpPollBlock * b, IdBlock * idb, void * handle)
{
  _kernel_oserror * e;
  int               key;

  key = ((WimpKeyPressedEvent *) b)->key_code;

  switch (key)
  {
    case akbd_TabK + akbd_Ctl:
    {
      char url[Limits_URLBarWrit];
      int  changed = 0;

      /* Read whatever is in the URL bar writable */

      *url = 0;

      e = writablefield_get_value(0,
                                  idb->self_id,
                                  OpenWrit,
                                  url,
                                  sizeof(url),
                                  NULL);

      if (!e)
      {
        url[sizeof(url) - 1] = 0; /* (Ensure termination) */

        /* Try and find something appropriate in the hotlist, */
        /* then the history.                                  */

        #ifndef REMOTE_HOTLIST
          changed = hotlist_find_match(url, sizeof(url));
        #endif

        if (!changed) changed = history_find_match(url, sizeof(url));

        if (changed)
        {
          /* Update the URL writable */

          writablefield_set_value(0,
                                  idb->self_id,
                                  OpenWrit,
                                  url);
        }
      }

      key = 0;
    }
    break;

    case akbd_TabK + akbd_Sh:
    {
      char url[Limits_URLBarWrit];
      int  changed;

      /* Read whatever is in the URL bar writable */

      *url = 0;

      e = writablefield_get_value(0,
                                  idb->self_id,
                                  OpenWrit,
                                  url,
                                  sizeof(url),
                                  NULL);

      if (!e)
      {
        url[sizeof(url) - 1] = 0; /* (Ensure termination) */

        /* Cycle the protocol specifier (adding one in if not already present) */

        changed = urlutils_cycle_protocol(url, sizeof(url));

        if (changed)
        {
          /* Update the URL writable */

          writablefield_set_value(0,
                                  idb->self_id,
                                  OpenWrit,
                                  url);
        }
      }

      key = 0;
    }
    break;
  }

  if (key) wimp_process_key(key);

  return 1;
}