package dtRdr::HTMLWidget;
$VERSION = eval{require version}?version::qv($_):$_ for(0.10.1);

use warnings;
use strict;

use Carp;


use dtRdr::Traits::Class qw(
  NOT_IMPLEMENTED
  WARN_NOT_IMPLEMENTED
  );

my $delegate_class;

use Wx qw(
  wxHW_NO_SELECTION
  );

use dtRdr::Logger;

use Class::Accessor::Classy;
rw qw(
  load_in_progress
  zoom
);
ro qw(
  parent
  url_handler
);
rs html_source => \ (my $set_html_source);
no  Class::Accessor::Classy;


=head1 NAME

dtRdr::HTMLWidget - Generic HTML widget interface

=head1 SYNOPSIS

  use dtRdr::HTMLWidget;

=head1 DESCRIPTION

Provides a browser-independent widget.  Uses a platform-specific
adapter (shim), or the basic WxHTML widget if that fails.

=cut

our @ISA;
BEGIN {
  $delegate_class = 0;
  my %dispatch = (
    darwin   => 'dtRdr::HTMLShim::WebKit',
    MSWin32  => 'dtRdr::HTMLShim::ActiveXIE',
    linux    => 'dtRdr::HTMLShim::WxMozilla',
    # linux    => 'dtRdr::HTMLShim::WxHTML',
    fallback => 'dtRdr::HTMLShim::WxHTML',
    );

  # override
  my $force = $ENV{THOUT_WIDGET} ?
    'dtRdr::HTMLShim::' . $ENV{THOUT_WIDGET} :
    undef;
  # pick one
  if(my $backend = $force || $dispatch{$^O}) {
    eval "use $backend";
    if($@) {
      die "$force not working ($@)" if($force);
      warn "could not use $backend -- try fallback ($@)";
    }
    else {
      $delegate_class = $backend;
    }
  }
  else {
    warn "'$^O' backend not specified";
  }

  # If something went wrong, go to the fallback
  unless($delegate_class) {
    eval "use $dispatch{fallback}";
    $@ and die "even the fallback failed! $@";
    $delegate_class = $dispatch{fallback};
  }

  # If we can't use the fallback, then punt
  our @ISA;
  # we're basically inserting ourselves between the platform-specific
  # widget and the class that it wants to inherit from
  # e.g. we inherit from the widget's chosen base class on its behalf
  push(@ISA, $delegate_class->base);
} # end BEGIN

=head1 CONSTRUCTOR

=head2 new

Constructor / Factory method.

Create and return a new HTML rendering widget.

Base classes may inherit this.

Calling classes should call this (do not call the base class constructor
directly.)

  my @wx_args = (
    # from http://www.wxwindows.org/manuals/2.6.3/wx_wxhtmlwindow.html
    $parent, # wxWindow *parent,
    $id,     # wxWindowID id = -1,
    $pos,    # const wxPoint& pos = wxDefaultPosition,
    $size,   # const wxSize& size = wxDefaultSize,
    $style,  # long style = wxHW_DEFAULT_STYLE,
    $name,   # const wxString& name = "htmlWindow"
    );
  $widget = dtRdr::HTMLWidget->new(\@wx_args, \%opts);

ASIDE: this is the old interface:

  $widget = dtRdr::HTMLWidget->new($parent, $id, $position, $size, $style)

=cut

sub new {
  my $package = shift;
  my $caller = caller;
  my ($b_args, $w_args) = @_;
  # allow duality of API
  if((ref($b_args) || '') ne 'ARRAY') {
    eval {$b_args->isa('Wx::Window')} or croak("bad arguments @_");
    $b_args = [@_];
  }
  $w_args ||= {};

  if(defined($caller) and $caller->isa(__PACKAGE__)) {
    # being inherited => be a base class
    0 and warn "base class for $caller";
    my $class = ref($package) || $package;
    0 and warn "b_args: ", join("|", @$b_args);
    my $self = $class->base->new(@$b_args);
    bless($self, $class);
    return($self);
  }
  else {
    # being called => be a factory
    0 and warn "factory" . ($caller ? " (for $caller)" : '');
    return($delegate_class->new($b_args, $w_args));
  }
} # end subroutine new definition
########################################################################

=head1 Class Methods

=head2 base

Returns the base class of the widget.  Subclass must override this.

  $widget->base;

=cut

sub base { my $self = shift; $self->NOT_IMPLEMENTED; }
########################################################################

=head1 Callback Methods

=head2 init

Initialization callback.

  $widget->init($parent, url_handler => $handler);

=cut

sub init {
  my $self = shift;
  my $parent = shift;
  (@_ % 2) and croak('odd number of elements in argument list');
  my %args = @_;
  $self->{parent} = $parent;
  my @attribs = qw(
    url_handler
  );
  foreach my $arg (@attribs) {
    $self->{$arg} = $args{$arg} if(exists($args{$arg}));
  }
} # end subroutine init definition
########################################################################

=head1 ...Methods

=head2 render_HTML

Render the HTML in the widget.

  $bool = $widget->render_HTML($html, $data_handle);

$data_handle must respond to requests for related URIs.  Note that the
URIs I<may> have a dotReader specific prefix, such as book:.

  $data_handle->get_member_string($relative_uri)

=cut

sub render_HTML {
  my $self = shift;
  my ($html, $dh) = @_;

  $self->{load_in_progress} = 1;
  # XXX shouldn't need this filtering anymore
  # $html = $self->filter_HTML($html, $dh);
  DBG_DUMP('FILTERED', 'filtered.html', sub {$html});
  my $status = $self->SetPage($html);
  $self->$set_html_source($html);
  $self->{load_in_progress} = 0;
  return $status;
} # end subroutine render_HTML definition
########################################################################

=head2 reset_wrap

Grabs the widget's internal state variables, runs $subref, and resets
the state variables.

  $hw->reset_wrap($subref);

=cut

sub reset_wrap {
  my $self = shift;
  my ($subref) = @_;
  my $yscroll = $self->get_scroll_pos();
  #L->debug("scroll pos $yscroll");
  my $zoom = $self->get_zoom;
  #L->debug("zoom $zoom");
  $subref->();
  my $new_pos = $self->set_scroll_pos($yscroll);
  #L->debug("set scroll pos to $new_pos");
  $self->set_zoom($zoom);
} # end subroutine reset_wrap definition
########################################################################

=head2 filter_HTML

  $html = $hw->filter_HTML($html, $datahandle);

=cut

sub filter_HTML {
  my $self = shift;
  my ($html, $dh) = @_;
  return($html);
} # end subroutine filter_HTML definition
########################################################################

=head2 allow_copy

  $widget->allow_copy($bool)

Enable or disable OS copy-to-clipboard functionality

=cut

sub allow_copy {
  my ($self, $allow) = @_;
  my $style = $self->GetWindowStyleFlag();
  if (!$allow) {
    $style = $style | wxHW_NO_SELECTION;
    $self->SetWindowStyleFlag($style);
  } else {
    $style = $style & (~wxHW_NO_SELECTION);
    $self->SetWindowStyleFlag($style);
  }
} # end subroutine allow_copy definition
########################################################################

=head1 Handlers

These are the callbacks that the widget shims must implement. In all
cases, registering a callback for a widget returns the previously
registered callback, on the off-chance that your code's doing a
temporary override of the callback.

=head2 register_get_file

  $old_callback = $widget->register_get_file(&callback)

  This is the function called when the widget needs to fetch a file
  that's embedded within a webpage. (This is for E<lt>imgE<gt> tag
  fetches and suchlike things)

=cut

sub register_get_file { my $self = shift; $self->NOT_IMPLEMENTED; }
########################################################################

=head2 load_in_progress

  $bool = $widget->load_in_progress;

Returns true if the widget is currently fetching information or working
on rendering the HTML contents, false if it is not.

=cut

# accessor
########################################################################

=head2 get_scroll_pos

  callback implement in html shim
  $position = $widget->get_cursor_pos();
  Returns the current vertical scroll bar position for the HTML widget

=cut

sub get_scroll_pos { my $self = shift; $self->NOT_IMPLEMENTED; }
########################################################################

=head2 set_scroll_pos

  callback implement in html shim
  $widget->set_scroll_pos(200);

  Sets the current vertical scroll bar position for the HTML widget

=cut

sub set_scroll_pos { my $self = shift; $self->NOT_IMPLEMENTED; }
########################################################################

=head2 scroll_page_down

  callback implement in html shim
  scroll_page_down();
  scrolls the browser widget down 1 page.
  equivilent to [Page Down]
  Returns 1 if successfull or 0 if it can't scroll

=cut

sub scroll_page_down { my $self = shift; $self->NOT_IMPLEMENTED; }
########################################################################


=head2 scroll_page_up

  callback implement in html shim
  scroll_page_up();
  scrolls the browser widget down 1 page.
  equivilent to [Page Up]
  Returns 1 if successfull or 0 if it can't scroll

=cut

sub scroll_page_up { my $self = shift; $self->NOT_IMPLEMENTED; }
########################################################################


=head2 scroll_page_bottom

  callback implement in html shim
  scroll_page_bottom
  scrolls the browser to the bottom of the page

=cut

sub scroll_page_bottom { my $self = shift; $self->NOT_IMPLEMENTED; }
########################################################################


=head2 increase_font

callback implement in html shim
Decreases the font size
  $widget->increase_font

=cut

sub increase_font {  my $self = shift; $self->NOT_IMPLEMENTED; }
########################################################################

=head2 decrease_font

callback implement in html shim
Decreases the font size
  $widget->decrease_font

=cut

sub decrease_font {  my $self = shift; $self->NOT_IMPLEMENTED; }
########################################################################

=head2 register_url_changed        XXX   NOT USED   XXX

  $old_callback = $widget->register_url_changed(&callback)

  This is the callback that's called when the browser pane is about to
  change to a new page because someone clicked on a link.

=cut

sub register_url_changed { my $self = shift; $self->NOT_IMPLEMENTED; }
########################################################################

=head2 register_form_post

  $old_callback = $widget->register_form_post(&callback)

  This is the callback that's called when the browser pane does a form
  POST request.

=cut

sub register_form_post { my $self = shift; $self->NOT_IMPLEMENTED; }
########################################################################

=head2 register_form_get

  $old_callback = $widget->register_form_get(&callback)

  This is the callback that's called when the browser pane does a form
  GET request.

=cut

sub register_form_get { my $self = shift; $self->NOT_IMPLEMENTED; }
########################################################################

=head1 history

=head2 history_next

  Navigates to the next state in history

=cut

sub history_next { my $self = shift; $self->NOT_IMPLEMENTED; }
########################################################################

=head2 history_back

  Navigates to the previous state in history

=cut

sub history_back { my $self = shift; $self->NOT_IMPLEMENTED; }
########################################################################

=head1 Print

=head2 print_page

  Print the current page

=cut

sub print_page { my $self = shift; $self->NOT_IMPLEMENTED; }
########################################################################


=head1 Selection

=head2 get_selection

  Returns selected text

  my $string = $hw->get_selection;

=cut

sub get_selection { my $self = shift; $self->NOT_IMPLEMENTED; }
########################################################################

=head2 get_selection_as_html

  my $html = $hw->get_selection_as_html;

=cut

sub get_selection_as_html { my $self = shift; $self->NOT_IMPLEMENTED; }
########################################################################

=head2 get_selection_context

  Returns ($pre, $str, $post)
  where pre and post text are $context_length or less if the selected
  text is near the begining or end of the document

  my ($pre, $str, $post) = $hw->get_selection_context($context_length);

=cut

sub get_selection_context { my $self = shift; $self->NOT_IMPLEMENTED; }
########################################################################

=head1 AUTHOR

Dan Sugalski, E<lt>dan@sidhe.orgE<gt>

Eric Wilhelm

Gary Varnell

=head1 COPYRIGHT

Copyright (C) 2006 by Dan Sugalski, Eric Wilhelm, and OSoft, All Rights
Reserved.

=head1 NO WARRANTY

Absolutely, positively NO WARRANTY, neither express or implied, is
offered with this software.  You use this software at your own risk.  In
case of loss, no person or entity owes you anything whatsoever.  You
have been warned.

=head1 LICENSE

The dotReader(TM) is OSI Certified Open Source Software licensed under
the GNU General Public License (GPL) Version 2, June 1991. Non-encrypted
and encrypted packages are usable in connection with the dotReader(TM).
The ability to create, edit, or otherwise modify content of such
encrypted packages is self-contained within the packages, and NOT
provided by the dotReader(TM), and is addressed in a separate commercial
license.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

=cut

1;
# vim:ts=2:sw=2:et:sta