/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public
 * License Version 1.1 (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.mozilla.org/NPL/
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation. All
 * Rights Reserved.
 *
 * Contributor(s): 
 */

/*
* 'View' for interactive viewing of source code
*/

// when     who     what
// 06/27/97 jband   added this header to my code
//

package com.netscape.jsdebugging.ifcui;

import java.util.Observable;
import java.util.Observer;
import netscape.application.*;
import netscape.util.*;
import com.netscape.jsdebugging.ifcui.palomar.util.*;
import java.io.StringBufferInputStream;
import java.io.DataInputStream;
import com.netscape.jsdebugging.ifcui.palomar.widget.layout.*;
import com.netscape.jsdebugging.api.*;

// XXX deal with window closing -- remove as Observer

public class SourceView extends InternalWindow
    implements Observer
{

    public SourceView( Rect rect,
                       Emperor emperor,
                       SourceTextItem item )
    {
        super(rect);

        _item = item;

        _emperor = emperor;
        _controlTyrant  = emperor.getControlTyrant();
        _sourceTyrant = emperor.getSourceTyrant();
        _breakpointTyrant = emperor.getBreakpointTyrant();
        _sourceViewManager = emperor.getSourceViewManager();
        _stackTyrant    = emperor.getStackTyrant();

        if(AS.S)ER.T(null!=_controlTyrant,"emperor init order problem", this);
        if(AS.S)ER.T(null!=_sourceTyrant,"emperor init order problem", this);
        if(AS.S)ER.T(null!=_breakpointTyrant,"emperor init order problem", this);
        if(AS.S)ER.T(null!=_sourceViewManager,"emperor init order problem", this);
        if(AS.S)ER.T(null!=_stackTyrant,"emperor init order problem", this);

        _sourceLineVectorModel =
            new SourceLineVectorModel(this, _controlTyrant, _stackTyrant,
                                      _sourceTyrant, item);
        _listview = new SourceTextListView(this);

        Rect rectSG = new Rect(rect);
        rectSG.moveBy(-rect.x, -rect.y);
        ScrollGroup sg = new ScrollGroup(rectSG);
        sg.setHorizScrollBarDisplay( ScrollGroup.ALWAYS_DISPLAY );
        sg.setVertScrollBarDisplay(  ScrollGroup.ALWAYS_DISPLAY );
        sg.setContentView( _listview );
        sg.setAutoResizeSubviews(true);
        sg.contentView().setLayoutManager( new MarginLayout() );
        sg.setBackgroundColor(_emperor.getBackgroundColor());

        setCloseable( true );
        setResizable( true );
//        setTitle( "Source for: " + item.getURL() );
        setTitle( item.getURL() );
        addSubview(sg);
        setAutoResizeSubviews(true);
        contentView().setLayoutManager( new MarginLayout() );

        _listview.setAllowsEmptySelection(true);
        _listview.setAllowsMultipleSelection(false);

        _listview.setBackgroundColor(_emperor.getBackgroundColor());

        _sourceTyrant.addObserver(this);
        _controlTyrant.addObserver(this);
        _breakpointTyrant.addObserver(this);

        _refresh(true);

        layoutView(1,1);
        layoutView(-1,-1);

        show();
    }

    public void ensureSourceLineVisible( int line )
    {
        int userline = _sourceTyrant.systemLine2UserLine(_item,line);
        if(userline >= 1 && userline <= _listview.count())
            _listview.scrollItemAtToVisible(userline-1);
    }

    // implement observer interface
    public void update(Observable o, Object arg)
    {
        if( o == _sourceTyrant )
        {
            SourceTyrantUpdate update = (SourceTyrantUpdate) arg;
            switch( update.type )
            {
                case SourceTyrantUpdate.VECTOR_CHANGED:
                    _refresh(false);
                    updateMarks();
                    break;
                case SourceTyrantUpdate.ITEM_CHANGED:
                    if( update.item == _item )
                    {
                        _refresh(false);
                        updateMarks();
                    }
                    break;
                case SourceTyrantUpdate.ADJUSTMENTS_CHANGED:
                    updateMarks();
                    break;
                default:
                    break;  // ignore any other types
            }
        }
        else if ( o == _breakpointTyrant )
        {
            BreakpointTyrantUpdate update = (BreakpointTyrantUpdate) arg;
            switch( update.type )
            {
                case BreakpointTyrantUpdate.ADD_ONE:
                case BreakpointTyrantUpdate.REMOVE_ONE:
                case BreakpointTyrantUpdate.MODIFIED_ONE:
                    if( update.bp.getURL().equals(_item.getURL()) )
                        updateMarks();
//                        _refreshMarksForSingleLine(update.bp.getLine());
                    break;
                case BreakpointTyrantUpdate.REMOVE_ALL_FOR_URL:
                    if( ! (update.bp.getURL().equals(_item.getURL())) )
                        break;
                    // else... FALL THROUGH
                case BreakpointTyrantUpdate.REMOVE_ALL:
                case BreakpointTyrantUpdate.REFRESH_ALL:
                    updateMarks();
                    break;
                default:
                    break;  // ignore any other types
            }
        }
        else if ( o == _controlTyrant )
        {
            // update all dirty views on stop
            if( ControlTyrant.STOPPED == _controlTyrant.getState() )
                _refresh(false);
            updateMarks();
        }

    }

    public void forceRefresh()
    {
        _refresh(true);
    }

    public void updateMarks()
    {
        _sourceLineVectorModel.updateLineItemVector();
        _listview.drawMarks();
    }

/*
    private synchronized void _refreshSingleLine( int line, Breakpoint bp )
    {
        if( null == _sourceLineVectorModel || null == _listview )
            return;
        _sourceLineVectorModel.updateSingleLineItem(line);
        // XXX This will change with 'selective text'
        _listview.drawItemAt(line-1);
    }
    private synchronized void _refreshMarksForSingleLine(int line)
    {
        if( null == _sourceLineVectorModel || null == _listview )
            return;
        _sourceLineVectorModel.updateSingleLineItem(line);
        // XXX This will change with 'selective text'
        _listview.drawMarksOfItemAt(line-1);
    }
*/

    private synchronized void _refresh(boolean force)
    {
        if( _item.getDirty() || force )
        {
            _emperor.setWaitCursor(true);

//            System.out.println(" _refreshing: " + _item.getURL() +
//                               " _item.getDirty() returned: " +
//                               (_item.getDirty() ? "true" : "false") +
//                               "_item.getAlterCount() returned: " +
//                               _item.getAlterCount() +
//                               " force == " +
//                               (force ? "true" : "false") );
            // refill all
            _listview.removeAllItems();
            _sourceLineVectorModel.rebuildLineItemVector();
            setSelection(null,0);

            Vector vec = _sourceLineVectorModel.getLineItemVector();
//            System.out.println( "vec size = " + vec.size() );

            Font linefont = _emperor.getFixedFont();

            boolean showNumbers = _sourceViewManager.getShowLineNumbers();
            SourceTextItemDrawer drawer = new SourceTextItemDrawer();

            _listview.setItemDrawer(drawer);

            int maxwidth = 0;
            int itemHeight = 0;
            for (int i = 0; i < vec.size(); i++)
            {
                SourceLineItemModel lineitem = (SourceLineItemModel) vec.elementAt(i);
                String text = lineitem.text;

                SourceTextListItem newlistitem = new SourceTextListItem(drawer,text.length());
                newlistitem.setTitle( text );
                newlistitem.setData( lineitem );
                newlistitem.setFont( linefont );
                newlistitem.setSelectedColor(_emperor.getSelectionColor());

                _listview.addItem( newlistitem );

                if( 0 == i )
                {
                    itemHeight = _listview.rectForItem(newlistitem).height;
                    drawer.init( linefont, showNumbers, vec.size(), itemHeight );
                }
                maxwidth = Math.max( maxwidth, newlistitem.minWidth() );
            }

            _listview.setBounds(0,0,maxwidth,0);
            _listview.sizeToMinSize();

            layoutView(0,0);
            _listview.draw();

            _item.setDirty(false);

            JSSourceLocation loc = _stackTyrant.getCurrentLocation();
            if( null != loc && loc.getURL().equals(getURL()) )
                ensureSourceLineVisible( loc.getLine() );

            _emperor.setWaitCursor(false);
        }
    }

    public String   getSelectedText() {return _selectedText;}
    public int      getSelectedLineNumber() {return _selectedLineNumber;}
    public void     setSelection(String s, int lineNumber)
    {
        if( _selectedLineNumber == lineNumber && s != null && s.equals(_selectedText) )
            return;
        _selectedText = s;
        _selectedLineNumber = lineNumber;
        _sourceViewManager.selectionInSourceViewChanged(this,s,lineNumber);
    }

    public SourceViewManager getSourceViewManager() {return _sourceViewManager;}
    public BreakpointTyrant  getBreakpointTyrant() {return _breakpointTyrant;}
    public Emperor           getEmperor() {return _emperor;}
    public SourceTyrant      getSourceTyrant() {return _sourceTyrant;}

    public SourceTextItem   getSourceItem() {return _item;}
    public String           getURL() {return _item.getURL();}

    private Emperor                 _emperor;
    private ControlTyrant           _controlTyrant;
    private SourceTyrant            _sourceTyrant;
    private StackTyrant             _stackTyrant;
    private BreakpointTyrant        _breakpointTyrant;
    private SourceViewManager       _sourceViewManager;
    private SourceLineVectorModel   _sourceLineVectorModel;
    private SourceTextItem          _item;
    private SourceTextListView      _listview;
    private String                  _selectedText = null;
    private int                     _selectedLineNumber = 0;


}

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

final class SourceTextListView extends SmartItemListView
{
    public SourceTextListView( SourceView sourceView )
    {
        super();
        _sourceView = sourceView;
    }


    public boolean mouseDown(MouseEvent me)
    {
        _dragOrigin = -1;

        // middle click
        if( me.isAltKeyDown() && _isInMarksZone(me) )
        {
            SourceTyrant st = getSourceView().getSourceTyrant();
            SourceTextItem sti =  _sourceView.getSourceItem();
            st.clearAllAdjustmentsForItem(sti);
            return false;
        }

        // right click
        if( me.isMetaKeyDown() && _isInMarksZone(me) )
        {
            SourceTyrant sourceTyrant = getSourceView().getSourceTyrant();

            _dragLast = _dragOrigin = _itemNumberAtPoint(me);
            SourceTextItem sti =  _sourceView.getSourceItem();
            int sysline = sourceTyrant.userLine2SystemLine(sti,_dragOrigin);
            _dragAdjLineOrigin = sysline;
//            System.out.println( "original line = " + _dragOrigin );
//            System.out.println( "sys line = " + sysline );
//            System.out.println( "\n" );
            return true;
        }
        return super.mouseDown(me);
    }


    public void mouseDragged(MouseEvent me)
    {
        if( -1 != _dragOrigin )
        {
            int curIndex = _itemNumberAtPoint(me);
            if( _dragLast == curIndex )
                return;
            _extendAdjLine(me);
        }
        else
            super.mouseDragged(me);
    }
    public void mouseUp(MouseEvent me)
    {
        if( -1 != _dragOrigin )
        {
            _extendAdjLine(me);
            _dragAdjLineOrigin = -1;
        }
        else
            super.mouseUp(me);
    }

    private final boolean _isInMarksZone(MouseEvent me)
    {
        return me.x < _drawer.marksWidth();
    }
    private final int _itemNumberAtPoint(MouseEvent me)
    {
        return 1 + indexOfItem(itemForPoint(me.x,me.y));
    }

    private void _extendAdjLine(MouseEvent me)
    {
        int curIndex = _itemNumberAtPoint(me);
        if( _dragLast == curIndex )
            return;
        int offset = curIndex - _dragLast;
        _dragLast = curIndex;

        SourceTyrant st = getSourceView().getSourceTyrant();
        SourceTextItem sti =  _sourceView.getSourceItem();
        int old_offset = st.getAdjustment(sti,_dragAdjLineOrigin);
        st.makeAdjustment(sti, _dragAdjLineOrigin, old_offset + offset);
    }

    protected void ancestorWillRemoveFromViewHierarchy(View removedView)
    {
        if( removedView == _sourceView )
        {
            _sourceView.getSourceViewManager().viewBeingRemoved(_sourceView);
        }

        super.ancestorWillRemoveFromViewHierarchy(removedView);
    }

    public void setItemDrawer(SourceTextItemDrawer drawer) {_drawer = drawer;}

    public void drawMarks()
    {
        if(AS.S)ER.T(null!=_drawer,"SourceTextListView has null drawer",this);
        if( null == _drawer || null == _drawer.marksRect())
        {
            draw();
            return;
        }
        // calc marks rect and call draw()
        Rect r = new Rect( 0,0, _drawer.marksRect().width, height() );
//        System.out.println( "draw marks rect: " + r );
        draw(r);
    }

    public void drawMarksOfItemAt(int i)
    {
        if(AS.S)ER.T(null!=_drawer,"SourceTextListView has null drawer",this);
        if( null == _drawer )
        {
            drawItemAt(i);
            return;
        }
        // calc marks rect for item and call draw()
        Rect rItem = rectForItemAt(i);
        Rect r = new Rect( rItem.x,
                           rItem.y,
                           _drawer.marksRect().width,
                           rItem.height );
//        System.out.println( "draw marks rect for single item: " + r );
        draw(r);
    }

    public SourceTextListItem getItemWithSel() {return _itemWithSel;}
    public int getSelStart()                   {return _selStart;}
    public int getSelEnd()                     {return _selEnd;}
    public void setItemWithSel(SourceTextListItem i) {_itemWithSel=i;}
    public void setSelRange(int start, int end) {_selStart=start;_selEnd=end;}

    public void selChangeCompleted()
    {
        int lineNumber = 0;
        _selString = null;

        if( null != _itemWithSel )
        {
            _selString = _itemWithSel.stringInRange(_selStart,_selEnd);
            lineNumber = indexOfItem(_itemWithSel) + 1;
        }
        _sourceView.setSelection(_selString, lineNumber );
    }

    public SourceView getSourceView() {return _sourceView;}

    private SourceView           _sourceView;
    private SourceTextItemDrawer _drawer;
    private SourceTextListItem   _itemWithSel;
    private int                  _selStart;
    private int                  _selEnd;
    private String               _selString;
    private int                  _dragOrigin = -1;
    private int                  _dragLast;
    private int                  _dragAdjLineOrigin;

}

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

final class SourceTextItemDrawer
{
    public SourceTextItemDrawer() {}

    public void init( Font font, boolean showLineNumbers,
                      int lineCount, int itemHeight )
    {
        _font = font;
        _showLineNumbers = showLineNumbers;

        _lineNumberColumnCount = String.valueOf(lineCount).length();
//        _height = font.fontMetrics().stringHeight();
        _height = itemHeight;
        _charWidth = font.fontMetrics().charWidth('0');

        _rectBP       = new Rect( 0, 0,
                                  _height, _height);
        Rect rectExec = new Rect( _rectBP.x + _rectBP.width, 0,
                                  _height/2, _height);
        Rect rectNum = new Rect( rectExec.x  + rectExec.width, 0,
                                 _showLineNumbers ? _charWidth*(_lineNumberColumnCount+1) : 0, _height);

        _width = rectNum.x + rectNum.width;

        _ptLineNumbers = new Point(rectNum.x, rectNum.y);

        int margin = _height / 4;

        // size before shrink
        _rectExecPointBackground = new Rect( rectExec );
        _rectBPBackground        = new Rect( _rectBP );
        _rectMarks               = Rect.rectFromUnion(rectExec, _rectBP);
        if( null == _rectMarks )
            _rectMarks = new Rect();

        _rectBP.growBy( -margin, - margin );
        rectExec.growBy( -(margin/2), -margin );

//        _rectBP.moveBy( 0, -margin/2 );
//        rectExec.moveBy( 0, -margin/4 );

        _polyExecPoint  = new Polygon();
        _polyExecPoint.addPoint( rectExec.x, rectExec.y );
        _polyExecPoint.addPoint( rectExec.x, rectExec.y + rectExec.height);
        _polyExecPoint.addPoint( rectExec.x+rectExec.width, rectExec.midY());
        _polyExecPoint.addPoint( rectExec.x, rectExec.y );

        _ptText = new Point( _width, 0 );
        _stringClipper = new DrawStringClipper( _charWidth );
    }

    public void draw( Graphics g,
                      Rect boundsRect,
                      SourceTextListItem item,
                      int lineNumber,
                      boolean hasBreakpoint,
                      boolean hasConditionalBreakpoint,
                      int scriptType,
                      boolean isExecPoint,
                      String adjustmentChar  )
    {
        SourceLineItemModel itemModel = (SourceLineItemModel) item.data();

        Rect r = new Rect();
        Rect clipRect = g.clipRect();

        // draw the main text

        String text = item.title();
        if( null != text && 0 != text.length() )
        {
            r.setBounds( boundsRect.x + _ptText.x,
                         boundsRect.y + _ptText.y,
                         boundsRect.width - _ptText.x,
                         boundsRect.height - _ptText.y );
            
//            System.out.println("r is "+r+"  clip is "+clipRect);            
            if( r.intersects( clipRect ) )
            {
                /* no sync - we trust that we are drawn on only one thread */
                if( _stringClipper.doClip(text, r, clipRect) > 0 )
                    item.drawStringInRect(g, _stringClipper.getString(), _font,
                                          _stringClipper.getRect(),
                                          Graphics.LEFT_JUSTIFIED);
            }
        }

        if( hasBreakpoint )
        {
            r.setBounds( boundsRect.x + _rectBP.x,
                         boundsRect.y + _rectBP.y,
                         _rectBP.width,
                         _rectBP.height );

            if( r.intersects( clipRect ) )
            {
                if( hasConditionalBreakpoint )
                    g.setColor( Color.orange );
                else
                    g.setColor( Color.red );
                g.fillOval( r );
            }
        }

        if( null != adjustmentChar )
        {
            r.setBounds( boundsRect.x + _rectBPBackground.x,
                         boundsRect.y + _rectBPBackground.y,
                         _rectBPBackground.width,
                         _rectBPBackground.height );

            if( r.intersects( clipRect ) )
            {
                g.setColor( Color.black );
                g.setFont(_font);
                g.drawStringInRect(adjustmentChar,r,Graphics.LEFT_JUSTIFIED);
            }
        }

        if( scriptType != SourceLineItemModel.NO_SCRIPT )
        {
            r.setBounds( boundsRect.x + _rectExecPointBackground.x,
                         boundsRect.y + _rectExecPointBackground.y,
                         _rectExecPointBackground.width,
                         _rectExecPointBackground.height );

            if( r.intersects( clipRect ) )
            {
                if( scriptType == SourceLineItemModel.FUNCTION_BODY )
                    g.setColor( Color.orange );
                else
                    g.setColor( Color.yellow );

                g.fillRect( r );
            }
        }

        if( isExecPoint )
        {
            int count = 4;
            int[] x = new int[count];
            int[] y = new int[count];

            for( int i = 0; i < count; i++ )
            {
                x[i] = _polyExecPoint.xPoints[i] + boundsRect.x;
                y[i] = _polyExecPoint.yPoints[i] + boundsRect.y;
            }

            g.setColor( Color.blue );
            g.fillPolygon( x,y,count);

        }
        if( _showLineNumbers )
        {
            r.setBounds( boundsRect.x + _ptLineNumbers.x,
                         boundsRect.y + _ptLineNumbers.y,
                         boundsRect.width - _ptLineNumbers.x,
                         boundsRect.height - _ptLineNumbers.y );

            if( r.intersects( clipRect ) )
            {
                g.setColor( Color.darkGray );
                g.setFont(_font);
                g.drawStringInRect(lineNumberString(lineNumber),r,Graphics.LEFT_JUSTIFIED);
            }
        }
    }

    public int width() {return _width;}
    public int height() {return _height;}
    public int charWidth() {return _charWidth;}
    public Rect breakpointRect() {return _rectBPBackground;}
    public Rect marksRect()      {return _rectMarks;}
    public Point textOrigin() {return _ptText;}
    public int marksWidth() {return _rectMarks.width;}

    private String lineNumberString( int number )
    {
        StringBuffer buf = new StringBuffer();
        String s = String.valueOf(number);
        int len = s.length();
        if(AS.S)ER.T(len<=_lineNumberColumnCount,"lineNumberColumnCount screwed up",this);

        for( int i = 0; i < _lineNumberColumnCount-len; i++ )
            buf.append('0');
        buf.append(s);
        buf.append(':');

        return buf.toString();
    }

    private int _width;
    private int _height;

    private int _lineNumberColumnCount;
    private Font _font;

    private Rect    _rectBP;
    private Rect    _rectBPBackground;
    private Polygon _polyExecPoint;
    private Rect    _rectExecPointBackground;
    private Rect    _rectMarks;
    private Point   _ptLineNumbers;
    private Point   _ptText;
    private boolean _showLineNumbers;
    private int     _charWidth;
    private DrawStringClipper _stringClipper;


}

final class SourceTextListItem extends SmartListItem
{
    public SourceTextListItem(SourceTextItemDrawer drawer, int charCount)
    {
        super();
        _drawer = drawer;
        _charCount = charCount;
    }

    public boolean mouseDown(MouseEvent me)
    {
/*
        System.out.println(".......................");
        System.out.println("mousedown x=" + me.x + " y=" + me.y);
        System.out.println(myRect());
        System.out.println(".......................");
*/

        _dragOrigin = -1;

        // middle click
        if( me.isAltKeyDown() )
            return false;

        // right click
        if( me.isMetaKeyDown() )
        {
            // evaluate selected text
            SourceTextListView v = (SourceTextListView) listView();
            if( v.getItemWithSel() != this )
                return false;

            Rect rectText = textRect();
            if( ! rectText.contains(me.x,me.y) )
                return false;

            Emperor emperor = v.getSourceView().getEmperor();
            CommandTyrant ct = emperor.getCommandTyrant();
            Menu menu = new Menu(true);

            if( ControlTyrant.STOPPED == emperor.getControlTyrant().getState() )
            {
                menu.addItem("Eval", ct.cmdString(CommandTyrant.EVAL_SEL_STRING), ct);
                menu.addItem("Inspect...", ct.cmdString(CommandTyrant.INSPECT_SEL_STRING), ct);
                menu.addItem("Copy to Watch", ct.cmdString(CommandTyrant.COPY_TO_WATCH), ct);
                menu.addSeparator();
            }

            menu.addItem("Copy", ct.cmdString(CommandTyrant.COPY), ct);
            menu.addSeparator();

            CmdState cs = ct.findCmdState(CommandTyrant.TOGGLE_BREAKPOINT);
            if( null != cs && cs.enabled )
            {
                if( cs.checked )
                {
                    menu.addItem("Clear Breakpoint", ct.cmdString(CommandTyrant.TOGGLE_BREAKPOINT), ct);
                    menu.addItem("Edit Breakpoint...", ct.cmdString(CommandTyrant.EDIT_BREAKPOINT), ct);
                }
                else
                    menu.addItem("Set Breakpoint", ct.cmdString(CommandTyrant.TOGGLE_BREAKPOINT), ct);
            }

//            String cmd = ct.cmdString( CommandTyrant.EVAL_STRING );
//            String str = stringInRange(v.getSelStart(), v.getSelEnd());
//            Application.application().performCommandLater( ct, cmd, str );

            ListView lv = listView();
            RootView rv = lv.rootView();
            MouseEvent tme = lv.convertEventToView(rv, me);
            MouseMenuView mmv = new MouseMenuView(menu);
            mmv.show(rv, tme);

            return false;
        }

        Rect rectBP = breakpointRect();
        if( rectBP.contains(me.x,me.y) )
        {
            // toggle breakpoint
            SourceLineItemModel model = (SourceLineItemModel) data();
            SourceView sv = ((SourceTextListView)listView()).getSourceView();
            BreakpointTyrant bpt = sv.getBreakpointTyrant();
            SourceTyrant sourceTyrant = sv.getSourceTyrant();
            SourceTextItem sti =  sv.getSourceItem();

            int sysline = sourceTyrant.userLine2SystemLine(sti,model.lineNumber);

            Location loc = new Location( sv.getURL(), sysline );

            Breakpoint bp = bpt.findBreakpoint(loc);
            if( null == bp )
                bpt.addBreakpoint(loc);
            else
                bpt.removeBreakpoint(bp);

            // redraw done on notification...
        }
        else
        {
            // start select (or select word)
            Rect rectText = textRect();
            if( rectText.contains(me.x,me.y) )
            {
                int start, end;
                SourceTextListView v = (SourceTextListView) listView();
                start = end = charIndexAtPointInRect( me.x, rectText );

                SourceTextListItem oldItem = v.getItemWithSel();
                v.setItemWithSel(this);

                if( me.clickCount() > 1 )
                {
                    Range r = wordAtIndex(start);
                    if( null != r )
                    {
                        start = r.index;
                        end = start + r.length-1;
                    }
                    _dragOrigin = -1;
                }
                else
                    _dragOrigin = start;

                v.setSelRange(start, end);

                if( null != oldItem )
                    v.drawItemAt( v.indexOfItem(oldItem) );
                v.draw( rectOfRangeInRect(start, end, rectText) );

                if( _dragOrigin == -1 ) // i.e. was double click
                    v.selChangeCompleted();
            }
        }
        return true;
    }

    public void mouseDragged(MouseEvent me)
    {
        SourceTextListView v = (SourceTextListView) listView();
        if( -1 != _dragOrigin && v.getItemWithSel() == this )
            _extendSelection(me, v);
    }

    public void mouseUp(MouseEvent me)
    {
        SourceTextListView v = (SourceTextListView) listView();
        if( -1 != _dragOrigin && v.getItemWithSel() == this )
        {
            _extendSelection(me, v);
            v.selChangeCompleted();
            _dragOrigin = -1;
        }
    }

    private void _extendSelection(MouseEvent me, SourceTextListView v)
    {
        Rect rectText = textRect();
        if( rectText.contains(me.x,me.y) )
        {
            int cur = charIndexAtPointInRect( me.x, rectText );
            int oldStart = v.getSelStart();
            int oldEnd   = v.getSelEnd();

            int start;
            int end;

            if( cur > _dragOrigin )
            {
                start = _dragOrigin;
                end   = cur;
            }
            else
            {
                start = cur;
                end   = _dragOrigin;
            }
            if( start == oldStart && end == oldEnd )
                return;

            v.setSelRange(start, end);

            // redraw only changed parts (not worried about overlap)...
            int x1, x2;
            x1 = Math.min(oldStart, start);
            x2 = Math.max(oldStart, start);
            if( x1 != x2 )
                v.draw( rectOfRangeInRect(x1, x2, rectText) );
            x1 = Math.min(oldEnd, end);
            x2 = Math.max(oldEnd, end);
            if( x1 != x2 )
                v.draw( rectOfRangeInRect(x1, x2, rectText) );
        }
    }

    public int minWidth() {return super.minWidth() + _drawer.width();}


    public Range wordAtIndex(int index)
    {
        String text = ((SourceLineItemModel)data()).text;
        int len = text.length();
        int start = -1;
        int end;
        int i;

        // search for first char...
        for( i = 0; i <= index; i++ )
        {
            char c = text.charAt(i);
            if( -1 == start )
            {
                if( Character.isJavaIdentifierStart(c) )
                    start = i;
            }
            else
            {
                if( ! Character.isJavaIdentifierPart(c) )
                    start = -1;
            }
        }
        if( -1 == start )
            return null;

        for(; i < len; i++ )
        {
            char c = text.charAt(i);
            if( ! Character.isJavaIdentifierPart(c) )
                break;
        }

        return new Range(start, i-start);
    }

    public String stringInRange(int start, int end)
    {
        return ((SourceLineItemModel)data()).text.substring(start,end+1);
    }



    protected void drawBackground(Graphics g, Rect boundsRect)
    {
        SourceTextListView v = (SourceTextListView) listView();

        if( v.getItemWithSel() == this )
        {
            Rect r = rectOfRangeInRect(v.getSelStart(),v.getSelEnd(),textRect());
            r.intersectWith(boundsRect);
            if( ! r.isEmpty() )
            {
                g.setColor(selectedColor());
                g.fillRect(r);
            }
        }
    }

    public void drawInRect(Graphics g, Rect boundsRect)
    {
        drawBackground(g, boundsRect);

        SourceLineItemModel model = (SourceLineItemModel) data();
        _drawer.draw( g, boundsRect, this,
                      model.lineNumber,
                      model.bp != null,
                      model.bp != null?model.bp.getBreakCondition()!=null:false,
                      model.type,
                      model.executing,
                      model.adjustmentChar );
    }

    // make this public to be called by our drawer helper
    public void drawStringInRect( Graphics g, String title,
                                  Font titleFont, Rect textBounds,
                                  int justification)
    {
        // XXX temp test...
//        if(AS.DEBUG) {
//            int len = title.length();
//            String sub;
//            if(0 == len)
//                sub = "BLANK";
//            else
//                sub = title.substring(0, Math.min(60,len));
//            System.out.println("drawing text: \""+sub+"\" at "+textBounds);
//        }
        super.drawStringInRect(g,title,titleFont,textBounds,justification);
    }

    public Rect breakpointRect()
    {
//        Rect r = new Rect( _drawer.breakpointRect() );
        Rect r = new Rect( _drawer.marksRect() );
        Rect myRect = myRect();
        r.moveBy(myRect.x, myRect.y);
        return r;
    }

    public Rect textRect()
    {
        Rect r = new Rect( _drawer.textOrigin().x,
                           _drawer.textOrigin().y,
                           _drawer.charWidth() * _charCount,
                           _drawer.height() );
        Rect myRect = myRect();
        r.moveBy(myRect.x, myRect.y);
        return r;
    }

    // ASSUMES that point is in rect -- caller beware!
    protected int charIndexAtPointInRect( int x, Rect r )
    {
        return (x - r.x) / _drawer.charWidth();
    }

    protected Rect rectOfRangeInRect( int start, int end, Rect r )
    {
        int cw = _drawer.charWidth();
        return new Rect((start*cw)+r.x, r.y, (1+end-start)*cw, r.height);
    }

    private int                  _dragOrigin = -1;
    private int                  _charCount;
    private SourceTextItemDrawer _drawer;
}

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

final class SourceLineVectorModel
{
    public SourceLineVectorModel( SourceView sourceView,
                                  ControlTyrant controlTyrant,
                                  StackTyrant   stackTyrant,
                                  SourceTyrant  sourceTyrant,
                                  SourceTextItem sourceTextItem )
    {
        _sourceView     = sourceView;
        _controlTyrant  = controlTyrant;
        _stackTyrant    = stackTyrant;
        _sourceTextItem = sourceTextItem;
        _sourceTyrant   = sourceTyrant;
        _sourceLineItemVector = new Vector();
    }

    public synchronized void rebuildLineItemVector()
    {
        Vector  vec = new Vector();
        try
        {
            DataInputStream s = new DataInputStream(
                                        new StringBufferInputStream(
                                                getSourceTextItem().getText() ));
            if( null != s )
            {
                String linetext;
                int lineNumber = 1;
                while( null != (linetext = s.readLine()) )
                {
                    SourceLineItemModel item = new SourceLineItemModel();

                    item.lineNumber       = lineNumber;
                    item.text             = Util.expandTabs(linetext,8);
                    item.type             = SourceLineItemModel.NO_SCRIPT;
                    item.bp               = null;   // XXX
                    item.executing        = false;
                    item.adjustmentChar   = null;

                    vec.addElement( item );
                    lineNumber++ ;
                }
                s.close();
            }
        }
        catch( Exception e ) {}

        _sourceLineItemVector = vec;
        updateLineItemVector();
    }

    public synchronized void updateLineItemVector()
    {
/*
        // XXX script stuff must change...
        ScriptProvider sp = JSDScriptProvider.getScriptProvider();
        if( null == sp )
            return;
*/

        int i;
        int count;

        // clear all line

        int maxLineItemIndex = _sourceLineItemVector.size() - 1;

        count = _sourceLineItemVector.size();
        if( 0 == count )
            return;
        for( i = 0; i < count; i++ )
        {
            SourceLineItemModel item =
                        (SourceLineItemModel) _sourceLineItemVector.elementAt(i);
            item.type       = SourceLineItemModel.NO_SCRIPT;
            item.bp         = null;
            item.executing  = false;
            item.adjustmentChar = null;
        }

        // walk through the scripts and mark lines with scripts

        String url = _sourceTextItem.getURL();
        BreakpointTyrant bpt = _sourceView.getBreakpointTyrant();

        Vector vecScripts = bpt.getScriptsVectorForURL(url);
        count = vecScripts.size();
        for( i = 0; i < count; i++ )
        {
            Script script = (Script) vecScripts.elementAt(i);

            if( ! script.isValid() )
                continue;

            boolean fun = null != script.getFunction();

            ScriptSection[] sections = script.getSections();
            for( int n = 0; n < sections.length; n++ )
            {
                int base   = sections[n].getBaseLineNumber();
                int extent = sections[n].getLineExtent();

                for( int k = base; k < (base+extent); k++ )
                {
                    if( k > maxLineItemIndex+1 )
                        continue;
                    int j = _sourceTyrant.systemLine2UserLine(_sourceTextItem,k);
                    if( j > maxLineItemIndex+1 )
                        continue;
                    SourceLineItemModel item =
                            (SourceLineItemModel) _sourceLineItemVector.elementAt(j-1);

                    // don't overwrite function with top level script

                    if( fun )
                        item.type = SourceLineItemModel.FUNCTION_BODY;
                    else if( item.type != SourceLineItemModel.FUNCTION_BODY )
                        item.type = SourceLineItemModel.TOP_LEVEL_SCRIPT;
                }
            }

        }

        // set breakpoints
        Vector vecBP = bpt.getBreakpointsForURL( _sourceTextItem.getURL() );
        count = vecBP.size();
        for( i = 0; i < count; i++ )
        {
            Breakpoint bp = (Breakpoint)vecBP.elementAt(i);
            int line = bp.getLine();
            line = _sourceTyrant.systemLine2UserLine(_sourceTextItem,line);

            if( line > maxLineItemIndex+1 )
                continue;

            SourceLineItemModel item = (SourceLineItemModel)
                            _sourceLineItemVector.elementAt(line-1);
            item.bp = bp;
        }

        // set executing lines

        if( ControlTyrant.STOPPED ==  _controlTyrant.getState() )
        {
            JSSourceLocation loc = _stackTyrant.getCurrentLocation();
            if( null != loc && loc.getURL().equals(url) )
            {
                int index = loc.getLine();
                index = _sourceTyrant.systemLine2UserLine(_sourceTextItem,index);

                if( index > maxLineItemIndex+1 )
                    index = maxLineItemIndex+1;
                if( index < 1 )
                    index = 1;

                SourceLineItemModel item = (SourceLineItemModel)
                               _sourceLineItemVector.elementAt(index-1);
                item.executing  = true;
            }
        }

        // set adjustment chars

        int[] adjArray = _sourceTyrant.getUserAdjustedLineArray(_sourceTextItem);
        if( null != adjArray )
        {
            String adjChar;
            for( int k = 0; k < adjArray.length; k++ )
            {
                int line = adjArray[k];
                if( line < 0 )
                {
                    line *= -1;
                    adjChar = "-";
                }
                else
                    adjChar = "+";
                SourceLineItemModel item = (SourceLineItemModel)
                               _sourceLineItemVector.elementAt(line-1);
                item.adjustmentChar = adjChar;
            }
        }
    }


    // This is out of date. If anyone wants to reserect it then they
    // had better update this to do the right things!
/*
    public synchronized void updateSingleLineItem( int lineNumber )
    {
        BreakpointTyrant bpt = _sourceView.getBreakpointTyrant();
        Breakpoint bp = bpt.findBreakpoint(
                            new Location(_sourceTextItem.getURL(),lineNumber));

        int maxLineItemIndex = _sourceLineItemVector.size() - 1;
        if( lineNumber > maxLineItemIndex+1 )
            lineNumber = maxLineItemIndex+1;

        SourceLineItemModel item = (SourceLineItemModel)
                        _sourceLineItemVector.elementAt(lineNumber-1);
        item.bp = bp;
    }
*/

    public Vector            getLineItemVector()    {return _sourceLineItemVector;}
    public SourceTextItem    getSourceTextItem()    {return _sourceTextItem;}

    private SourceView      _sourceView;
    private SourceTyrant    _sourceTyrant;
    private ControlTyrant   _controlTyrant;
    private StackTyrant     _stackTyrant;
    private SourceTextItem  _sourceTextItem;    // has url, text, and fullness status
    private Vector          _sourceLineItemVector;
}



