#include <../../nrnconf.h>
#if HAVE_IV  // to end of file

/*
 * Copyright (c) 1991 Stanford University
 * Copyright (c) 1991 Silicon Graphics, Inc.
 *
 * Permission to use, copy, modify, distribute, and sell this software and
 * its documentation for any purpose is hereby granted without fee, provided
 * that (i) the above copyright notices and this permission notice appear in
 * all copies of the software and related documentation, and (ii) the names of
 * Stanford and Silicon Graphics may not be used in any advertising or
 * publicity relating to the software without the specific, prior written
 * permission of Stanford and Silicon Graphics.
 *
 * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
 * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
 *
 * IN NO EVENT SHALL STANFORD OR SILICON GRAPHICS BE LIABLE FOR
 * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF
 * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
 * OF THIS SOFTWARE.
 */

/*
 * FieldSEditor -- simple editor for text fields
 */

#include <Dispatch/dispatcher.h>
#include <Dispatch/iocallback.h>
//#include <IV-look/field.h>
#include "field.h"
#include <IV-look/kit.h>
#include <InterViews/background.h>
#include <InterViews/canvas.h>
#include <InterViews/display.h>
#include <InterViews/font.h>
#include <InterViews/event.h>
#include <InterViews/hit.h>
#include <InterViews/layout.h>
#include <InterViews/printer.h>
#include <InterViews/selection.h>
#include <InterViews/style.h>
#include <InterViews/window.h>
#include <IV-2_6/InterViews/button.h>
#include <IV-2_6/InterViews/painter.h>
#include <IV-2_6/InterViews/sensor.h>
#include <IV-2_6/InterViews/streditor.h>
#include <IV-2_6/InterViews/textdisplay.h>
#include <OS/math.h>
#include <OS/string.h>
#include <stdio.h>

class FieldStringSEditor: public StringEditor {
  public:
    FieldStringSEditor(ButtonState* bs, const char* sample, WidgetKit*, Style*);
    virtual ~FieldStringSEditor();

    virtual void print(Printer*, const Allocation&) const;
    virtual void pick(Canvas*, const Allocation&, int depth, Hit&);

    void press(const Event&);
    void drag(const Event&);
    void release(const Event&);
    bool keystroke(const Event&);
    void cursor_on();
    void cursor_off();
    void focus_in();
    void focus_out();
    void cut(SelectionManager*);
    void paste(SelectionManager*);

    void Select(int pos);
    void Select(int left, int right);
    void selection(int& start, int& index);

  protected:
    virtual void Reconfig();

  private:
    WidgetKit* kit_;
    Style* style_;
    int start_;
    int index_;
    int origin_;
    int width_;

    void do_select(Event&);
    void do_grab_scroll(Event&);
    void do_rate_scroll(Event&);
};

declareSelectionCallback(FieldStringSEditor) implementSelectionCallback(FieldStringSEditor)

    FieldStringSEditor::FieldStringSEditor(ButtonState* bs,
                                           const char* sample,
                                           WidgetKit* kit,
                                           Style* style)
    : StringEditor(bs, sample) {
    kit_ = kit;
    style_ = style;
    Resource::ref(style);
    delete input;
    input = NULL;
    start_ = index_ = -1;
}

FieldStringSEditor::~FieldStringSEditor() {
    Resource::unref(style_);
}

void FieldStringSEditor::Select(int pos) {
    start_ = index_ = pos;
    StringEditor::Select(pos);
}
void FieldStringSEditor::Select(int left, int right) {
    start_ = left;
    index_ = right;
    StringEditor::Select(left, right);
}
void FieldStringSEditor::selection(int& start, int& index) {
    start = start_;
    index = index_;
}

void FieldStringSEditor::print(Printer* p, const Allocation& a) const {
    const Font* f = output->GetFont();
    const Color* fg = output->GetFgColor();
    FontBoundingBox b;
    f->font_bbox(b);
    Coord x = a.left(), y = a.bottom() + b.font_descent();
    FieldStringSEditor* e = (FieldStringSEditor*) this;
    for (const char* s = e->Text(); *s != '\0'; s++) {
        Coord w = f->width(*s);
        p->character(f, *s, w, fg, x, y);
        x += w;
    }
}

void FieldStringSEditor::pick(Canvas*, const Allocation& a, int depth, Hit& h) {
    const Event* ep = h.event();
    if (ep != NULL && h.left() < a.right() && h.right() >= a.left() && h.bottom() < a.top() &&
        h.top() >= a.bottom()) {
        h.target(depth, this, 0);
    }
}

void FieldStringSEditor::press(const Event& event) {
    Event e;
    display->Draw(output, canvas);
    switch (event.pointer_button()) {
    case Event::left:
    case Event::middle:
    case Event::right:
        origin_ = display->Left(0, 0);
        width_ = display->Width();
        Poll(e);
        start_ = display->LineIndex(0, e.x);
        do_select(e);
        break;
#if 0
    case Event::middle:
	do_grab_scroll(e);
	break;
    case Event::right:
	do_rate_scroll(e);
	break;
#endif
    }
}

void FieldStringSEditor::drag(const Event&) {
    Event e;
    // I have no idea why the event.pointer_button() is 0.
    Poll(e);
    //    if (e.leftmouse)
    { do_select(e); }
}

void FieldStringSEditor::release(const Event& event) {
    Event e;
    switch (event.pointer_button()) {
    case Event::left:
    case Event::middle:
    case Event::right: {
        Poll(e);
        do_select(e);
        SelectionManager* s = e.display()->primary_selection();
        s->own(new SelectionCallback(FieldStringSEditor)(this, &FieldStringSEditor::cut));
    } break;
    }
}

void FieldStringSEditor::do_select(Event& e) {
    if (e.x < 0) {
        origin_ = Math::min(0, origin_ - e.x);
    } else if (e.x > xmax) {
        origin_ = Math::max(xmax - width_, origin_ - (e.x - xmax));
    }
    display->Scroll(0, origin_, ymax);
    index_ = display->LineIndex(0, e.x);
    DoSelect(start_, index_);
}

void FieldStringSEditor::do_grab_scroll(Event& e) {
    Window* w = canvas->window();
    Cursor* c = w->cursor();
    w->cursor(kit_->hand_cursor());
    int origin = display->Left(0, 0);
    int width = display->Width();
    Poll(e);
    int x = e.x;
    do {
        origin += e.x - x;
        origin = Math::min(0, Math::max(Math::min(0, xmax - width), origin));
        display->Scroll(0, origin, ymax);
        x = e.x;
        Poll(e);
    } while (e.middlemouse);
    w->cursor(c);
}

void FieldStringSEditor::do_rate_scroll(Event& e) {
    Window* w = canvas->window();
    Cursor* c = w->cursor();
    WidgetKit& kit = *kit_;
    Cursor* left = kit.lfast_cursor();
    Cursor* right = kit.rfast_cursor();
    int origin = display->Left(0, 0);
    int width = display->Width();
    Poll(e);
    int x = e.x;
    do {
        origin += x - e.x;
        origin = Math::min(0, Math::max(Math::min(0, xmax - width), origin));
        display->Scroll(0, origin, ymax);
        if (e.x - x < 0) {
            w->cursor(left);
        } else {
            w->cursor(right);
        }
        Poll(e);
    } while (e.rightmouse);
    w->cursor(c);
}

bool FieldStringSEditor::keystroke(const Event& e) {
    char c;
    return e.mapkey(&c, 1) != 0 && HandleChar(c) && c == '\t';
}

void FieldStringSEditor::cursor_on() {
    if (canvas != NULL) {
        display->CaretStyle(BarCaret);
    }
}

void FieldStringSEditor::cursor_off() {
    if (canvas != NULL) {
        display->CaretStyle(NoCaret);
    }
}

void FieldStringSEditor::focus_in() {}
void FieldStringSEditor::focus_out() {}

void FieldStringSEditor::cut(SelectionManager* s) {
    //    s->put_value(Text() + start_, index_ - start_);
    int st = Math::min(start_, index_);
    int i = Math::max(index_, start_);
    s->put_value(Text() + st, i - st);
}

void FieldStringSEditor::paste(SelectionManager*) {
    /* unimplemented */
}

void FieldStringSEditor::Reconfig() {
    kit_->push_style();
    kit_->style(style_);
    Painter* p = new Painter(output);
    p->SetColors(kit_->foreground(), kit_->background());
    p->SetFont(kit_->font());
    Resource::unref(output);
    output = p;
    StringEditor::Reconfig();
    kit_->pop_style();
}

class FieldSButton: public ButtonState {
  public:
    FieldSButton(FieldSEditor*, FieldSEditorAction*);
    virtual ~FieldSButton();

    virtual void Notify();

  private:
    FieldSEditor* editor_;
    FieldSEditorAction* action_;
};

class FieldSEditorImpl {
  private:
    friend class FieldSEditor;

    WidgetKit* kit_;
    FieldStringSEditor* editor_;
    FieldSButton* bs_;
    String text_;
    bool cursor_is_on_;
    IOHandler* blink_handler_;
    long flash_rate_;

    void build(FieldSEditor*, const char*, FieldSEditorAction*);
    void blink_cursor(long, long);
    void stop_blinking();
};

declareIOCallback(FieldSEditorImpl) implementIOCallback(FieldSEditorImpl)

    FieldSEditor::FieldSEditor(const String& sample,
                               WidgetKit* kit,
                               Style* s,
                               FieldSEditorAction* action)
    : InputHandler(NULL, s) {
    impl_ = new FieldSEditorImpl;
    impl_->kit_ = kit;
    NullTerminatedString ns(sample);
    impl_->build(this, ns.string(), action);
}

FieldSEditor::~FieldSEditor() {
    FieldSEditorImpl* i = impl_;
    i->stop_blinking();
    Resource::unref(i->editor_);
    Resource::unref(i->bs_);
    delete i->blink_handler_;
    delete i;
}

void FieldSEditor::undraw() {
    FieldSEditorImpl& f = *impl_;
    f.stop_blinking();
    InputHandler::undraw();
}

void FieldSEditor::press(const Event& e) {
    impl_->editor_->press(e);
}

void FieldSEditor::drag(const Event& e) {
    impl_->editor_->drag(e);
}
void FieldSEditor::release(const Event& e) {
    impl_->editor_->release(e);
}

void FieldSEditor::keystroke(const Event& e) {
    FieldSEditorImpl& f = *impl_;
    if (f.editor_->keystroke(e)) {
        select(text()->length());
        next_focus();
    }
}

InputHandler* FieldSEditor::focus_in() {
    FieldSEditorImpl& f = *impl_;
    f.blink_cursor(0, 0);
    f.editor_->focus_in();
    return InputHandler::focus_in();
}

void FieldSEditor::focus_out() {
    FieldSEditorImpl& f = *impl_;
    f.stop_blinking();
    f.editor_->cursor_off();
    f.editor_->focus_out();
    InputHandler::focus_out();
}

void FieldSEditor::field(const char* str) {
    impl_->editor_->Message(str);
}

void FieldSEditor::field(const String& s) {
    NullTerminatedString ns(s);
    impl_->editor_->Message(ns.string());
}

void FieldSEditor::select(int pos) {
    impl_->editor_->Select(pos);
}

void FieldSEditor::select(int l, int r) {
    impl_->editor_->Select(l, r);
}

void FieldSEditor::selection(int& start, int& index) const {
    impl_->editor_->selection(start, index);
}

void FieldSEditor::edit() {
    impl_->editor_->Edit();
}

void FieldSEditor::edit(const char* str, int left, int right) {
    impl_->editor_->Edit(str, left, right);
}

void FieldSEditor::edit(const String& str, int left, int right) {
    NullTerminatedString ns(str);
    impl_->editor_->Edit(ns.string(), left, right);
}

const String* FieldSEditor::text() const {
    impl_->text_ = String(impl_->editor_->Text());
    return &impl_->text_;
}

/** class FieldSEditorImpl **/

void FieldSEditorImpl::build(FieldSEditor* e, const char* str, FieldSEditorAction* a) {
    WidgetKit& kit = *kit_;
    kit.begin_style("FieldEditor");
    Style* s = kit.style();
    bs_ = new FieldSButton(e, a);
    editor_ = new FieldStringSEditor(bs_, str, kit_, s);
    Glyph* g = editor_;
    if (s->value_is_on("beveled")) {
        g = kit.inset_frame(
            new Background(LayoutKit::instance()->h_margin(editor_, 2.0), kit.background()));
    }
    e->body(g);
    cursor_is_on_ = false;
    blink_handler_ = new IOCallback(FieldSEditorImpl)(this, &FieldSEditorImpl::blink_cursor);
    float sec = 0.5;
    s->find_attribute("cursorFlashRate", sec);
    flash_rate_ = long(sec * 1000000);
    kit.end_style();
}

void FieldSEditorImpl::blink_cursor(long, long) {
    if (cursor_is_on_) {
        editor_->cursor_off();
        cursor_is_on_ = false;
    } else {
        editor_->cursor_on();
        cursor_is_on_ = true;
    }
    if (flash_rate_ > 10) {
        Dispatcher::instance().startTimer(0, flash_rate_, blink_handler_);
    }
}

void FieldSEditorImpl::stop_blinking() {
    Dispatcher::instance().stopTimer(blink_handler_);
    editor_->cursor_off();
    cursor_is_on_ = false;
}

/** class FieldSButton **/

FieldSButton::FieldSButton(FieldSEditor* editor, FieldSEditorAction* action) {
    editor_ = editor;
    action_ = action;
    Resource::ref(action_);
}

FieldSButton::~FieldSButton() {
    Resource::unref(action_);
}

/*
 * We need to reset the button state's value so that we'll get
 * notified again.  If we call SetValue, then it will call
 * Notify again!  Alas, we must access the protected value member (sigh).
 */

void FieldSButton::Notify() {
    int v;
    GetValue(v);
    value = 0;
    if (action_ != NULL) {
        switch (v) {
        case '\r':
            action_->accept(editor_);
            break;
        case /*  ^G */ '\007':
        case /* Esc */ '\033':
            action_->cancel(editor_);
            break;
        default:
            break;
        }
    }
}

/** class FieldSEditorAction **/

FieldSEditorAction::FieldSEditorAction() {}
FieldSEditorAction::~FieldSEditorAction() {}
void FieldSEditorAction::accept(FieldSEditor*) {}
void FieldSEditorAction::cancel(FieldSEditor*) {}
#endif
