
/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples. 
*       Copyright (C) 1993 Microsoft Corporation.
*       All rights reserved. 
*       This source code is only intended as a supplement to 
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the 
*       Microsoft samples programs.
\******************************************************************************/

/****************************** Module Header *******************************
* Module Name: BAR.C
*
* This module contains functions for bar window 
* graphically showing two lists of sections and showing 
* colored vertical bars for the sections of text,
* with linking lines for the sections that are the same.
*
* Functions:
*
* BarWndProc()
* BarPaint()
* DrawSection()
* DrawLink()
* BarClick() 
* InitBarClass()
* BarDrawPosition()
* 
* Comments:
*
****************************************************************************/

#include <windows.h>
#include <commdlg.h>

#include "gutils.h"
#include "table.h"
#include "state.h"
#include "wdiffrc.h"
#include "windiff.h"
#include "list.h"
#include "line.h"
#include "scandir.h"
#include "file.h"
#include "section.h"
#include "compitem.h"
#include "complist.h"
#include "view.h"


long APIENTRY BarWndProc(HWND hWnd, UINT message, UINT wParam, LONG lParam);
void BarPaint(HWND hwnd);
void DrawSection(HDC hdc, int cx, int cy, int lines, SECTION sec, int sidecode);
void DrawLink(HDC hdc, int cx, int cy, int lines, SECTION sec);
void BarClick(HWND hwnd, int x, int y);


HPEN hpenSame, hpenLeft, hpenRight;
HBRUSH hbrSame, hbrLeft, hbrRight;
HBRUSH hbrSideBar;


/***************************************************************************
 * Function: InitBarClass
 *
 * Purpose:
 *
 * Create bar window class
 */
BOOL
InitBarClass(HINSTANCE hInstance)
{
        WNDCLASS    wc;
        BOOL resp;



        wc.style = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc = BarWndProc;
        wc.cbClsExtra = 0;
        wc.cbWndExtra = 0;
        wc.hInstance = hInstance;
        wc.hIcon = NULL;
        wc.hCursor = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = GetStockObject(WHITE_BRUSH);
        wc.lpszClassName = "BarClass";
        wc.lpszMenuName = NULL;

        resp = RegisterClass(&wc);

        return(resp);
}



/***************************************************************************
 * Function: BarWndProc
 *
 * Purpose:
 *
 * Window procedure supporting bar window
 *
 */

long APIENTRY
BarWndProc(HWND hWnd, UINT message, UINT wParam, LONG lParam)
{

        switch(message) {


        case WM_CREATE:

                hpenSame = CreatePen(PS_SOLID, 1, RGB(0,0,0));
                hbrSame = CreateSolidBrush(RGB(255,255,255));

                hpenLeft = CreatePen(PS_SOLID, 1, rgb_barleft);
                hbrLeft = CreateSolidBrush(rgb_barleft);

                hpenRight = CreatePen(PS_SOLID, 1, rgb_barright);
                hbrRight = CreateSolidBrush(rgb_barright);

                hbrSideBar = CreateSolidBrush(rgb_barcurrent);
                break;

        case WM_DESTROY:
                DeleteObject(hpenSame);
                DeleteObject(hpenLeft);
                DeleteObject(hpenRight);
                DeleteObject(hbrSame);
                DeleteObject(hbrLeft);
                DeleteObject(hbrRight);
                DeleteObject(hbrSideBar);
                break;

        case WM_PAINT:
                BarPaint(hWnd);
                break;

        case WM_LBUTTONDOWN:
                BarClick(hWnd, LOWORD(lParam), HIWORD(lParam));
                break;

        default:
                return(DefWindowProc(hWnd, message, wParam, lParam));
        }
        return 0;
}

/***************************************************************************
 * Function: BarDrawPosition
 *
 * Purpose:
 *
 * Draw the current position as side-bars down the bar window,
 * showing which lines from each file are currently in view. HDC can be
 * NULL (we get one ourselves if so). If bErase is true, we clear
 * the previous side-bars first.
 *
 * This is called from BarPaint when we paint the whole window, and
 * from TableServer() whenever it receives a TQ_SCROLL notification that
 * the table window has been scrolled.
 */
void
BarDrawPosition(HWND hwndBar, HDC hdcIn, BOOL bErase)
{
        HDC hdc;
        int total_lines, cy, cx;
        RECT rc, rcLeft, rcRight;
        VIEW view;
        COMPITEM item;
        LIST listleft, listright;
        long toprow, endrow, i;
        int left_first, left_last, right_first, right_last, linenr;


        /* get a hdc if we weren't given one */
        if (hdcIn == NULL) {
                hdc = GetDC(hwndBar);
        } else {
                hdc = hdcIn;
        }

        /* set horz position of bars */
        GetClientRect(hwndBar, &rc);
        cx = (int)(rc.right - rc.left);
        cy = (int)(rc.bottom - rc.top);

        /* layout constants are defined as percentages of window width */
        rcLeft.left = cx * L_POS_START / 100;
        rcRight.left = cx * R_POS_START / 100;
        rcLeft.right = rcLeft.left +  (cx * L_POS_WIDTH / 100);
        rcRight.right = rcRight.left +  (cx * R_POS_WIDTH / 100);

        /* erase the whole marker section if requested */
        if (bErase) {
                rcLeft.top = rc.top;
                rcLeft.bottom = rc.bottom;
                rcRight.top = rc.top;
                rcRight.bottom = rc.bottom;

                FillRect(hdc, &rcLeft, GetStockObject(WHITE_BRUSH));

                FillRect(hdc, &rcRight, GetStockObject(WHITE_BRUSH));
        }


        /*
         * calculate the vertical scaling - depends on the
         * total number of lines shown
         */

        /* get the handles to the two lists of sections */
        view = (VIEW) SendMessage(hwndClient, TM_CURRENTVIEW, 0, 0);
        /* make sure we are in expand mode */
        if (view_isexpanded(view) == FALSE) {
                /* get rid of the dc if we made it ourselves */
                if (hdcIn == NULL) {
                        ReleaseDC(hwndBar, hdc);
                }
                return;
        }

        item = view_getitem(view, 0);

        listleft = compitem_getleftsections(item);
        listright = compitem_getrightsections(item);

        /* if there is only one list of sections, draw nothing. The
         * picture for a single file is not very exciting.
         */

        if ((listleft == NULL) || (listright == NULL)) {
                /* get rid of the dc if we made it ourselves */
                if (hdcIn == NULL) {
                        ReleaseDC(hwndBar, hdc);
                }
                return;
        }

        /* take the longest of the two files and use this
         * for vertical scaling. the scale is such that the longest file
         * *just fits*.
         */
        total_lines = line_getlinenr(section_getlastline(List_Last(listleft)));
        total_lines = max(total_lines,
                       (int) line_getlinenr(section_getlastline(List_Last(listright))));


        /* get the current top row and nr of rows visible */
        toprow = SendMessage(hwndRCD, TM_TOPROW, FALSE, 0);
        endrow = SendMessage(hwndRCD, TM_ENDROW, FALSE, 0);
        endrow = min(endrow, view_getrowcount(view)-1);



        /*
         * find the first and last line nrs from each file currently visible.
         *
         */
        left_first = left_last = right_first = right_last = 0;

        for (i = toprow; i <= endrow; i++) {
                linenr = view_getlinenr_left(view, i);

                if (linenr > 0) {

                        if (left_first == 0) {
                                left_first = linenr;
                        }
                        left_first = min(left_first, linenr);
                        left_last = max(left_last, linenr);
                }

                linenr = view_getlinenr_right(view, i);
                if (linenr > 0) {
                        if (right_first == 0) {
                                right_first = linenr;
                        }
                        right_first = min(right_first, linenr);
                        right_last = max(right_last, linenr);
                }

        }

        /* draw the two markers as thick bars -> elongated rectangles */
        rcLeft.top = MulDiv(left_first-1, cy, total_lines);
        rcLeft.bottom = MulDiv(left_last, cy, total_lines);
        FillRect(hdc, &rcLeft, hbrSideBar);

        rcRight.top = MulDiv(right_first-1, cy, total_lines);
        rcRight.bottom = MulDiv(right_last, cy, total_lines);
        FillRect(hdc, &rcRight, hbrSideBar);

        /* get rid of the dc if we made it ourselves */
        if (hdcIn == NULL) {
                ReleaseDC(hwndBar, hdc);
        }

}


/***************************************************************************
 * Function: BarPaint
 *
 * Purpose:
 *
 * Paint the bar window 
 */
void
BarPaint(HWND hwnd)
{
        PAINTSTRUCT ps;
        HDC hdc;
        VIEW view;
        COMPITEM item;
        LIST listleft, listright;
        SECTION sec;
        int total_lines, cx, cy;
        RECT rc;

        hdc = BeginPaint(hwnd, &ps);

        /* draw a separator line at the very edge of the window */
        GetClientRect(hwnd, &rc);
        MoveToEx(hdc, (int)(rc.right-1), rc.top, NULL);
        LineTo(hdc, (int)(rc.right-1), rc.bottom);


        /* first gather information about what is to be displayed */

        /* find the total lines (for horz. scaling) */

        /* get the handles to the two lists of sections */
        view = (VIEW) SendMessage(hwndClient, TM_CURRENTVIEW, 0, 0);

        /* make sure we are in expand mode */
        if (view_isexpanded(view) == FALSE) {
                return;
        }

        item = view_getitem(view, 0);

        listleft = compitem_getleftsections(item);
        listright = compitem_getrightsections(item);

        /*
         * don't bother if there is only one list - not very interesting
         */
        if ((listleft == NULL) || (listright == NULL)) {
                EndPaint(hwnd, &ps);
                return;
        }

        /* take the longest of the two files and use this
         * for vertical scaling. the scale is such that the longest file
         * *just fits*.
         */
        total_lines = (int) line_getlinenr(section_getlastline(List_Last(listleft)));
        total_lines = max(total_lines,
                       (int) line_getlinenr(section_getlastline(List_Last(listright))));



        /* horizontal spacing:
         *
         * there are two columns, for the left and right files, and a gap
         * between them criss-crossed by lines marking the links.
         *
         * Each of the columns then has three sections, for the
         * position marker, the different sections
         * and the linked sections. The width and positions of these items
         * are defined (in windiff.h) as percentages of the window width.
         */

        cx = (int)(rc.right - rc.left);
        cy = (int)(rc.bottom - rc.top);


        /* draw all the left sections and links */
        List_TRAVERSE(listleft, sec) {
                DrawSection(hdc, cx, cy, total_lines, sec, STATE_LEFTONLY);

                if (section_getlink(sec) != NULL) {
                        DrawLink(hdc, cx, cy, total_lines, sec);
                }
        }

        /* draw all the right sections */
        List_TRAVERSE(listright, sec) {
                DrawSection(hdc, cx, cy, total_lines, sec, STATE_RIGHTONLY);
        }



        /* now draw current position markers */
        BarDrawPosition(hwnd, hdc, FALSE);

        EndPaint(hwnd, &ps);
}

/***************************************************************************
 * Function: DrawSection
 *
 * Purpose:
 *
 * Paint a single section 
 */
void
DrawSection(HDC hdc, int cx, int cy, int lines, SECTION sec, int sidecode)
{
        int x1, y1, x2, y2;
        HPEN hpenOld;
        HBRUSH hbrOld;

        /* calculate the vertical position from the scaling. the scaling
         * is such that the longest file just fits
         */
        y1 = MulDiv(line_getlinenr(section_getfirstline(sec))- 1, cy, lines);
        y2 = MulDiv(line_getlinenr(section_getlastline(sec)), cy, lines);


        /* left or right  - set bar position and width*/
        if (sidecode == STATE_LEFTONLY) {
                if (section_getlink(sec) != NULL) {
                        x1 = L_MATCH_START;
                        x2 = L_MATCH_WIDTH;
                } else {
                        x1 = L_UNMATCH_START;
                        x2 = L_UNMATCH_WIDTH;
                }
        } else {
                if (section_getlink(sec) != NULL) {
                        x1 = R_MATCH_START;
                        x2 = R_MATCH_WIDTH;
                } else {
                        x1 = R_UNMATCH_START;
                        x2 = R_UNMATCH_WIDTH;
                }
        }
        /* bar position defines are in percentages of the win width (cx) */
        x1 = cx * x1 / 100;
        x2 = (cx * x2 / 100) + x1;


        /* select pens and brushes */
        if (section_getlink(sec) != NULL) {
                hpenOld = SelectObject(hdc, hpenSame);
                hbrOld = SelectObject(hdc, hbrSame);
        } else if (sidecode == STATE_LEFTONLY) {
                hpenOld = SelectObject(hdc, hpenLeft);
                hbrOld = SelectObject(hdc, hbrLeft);
        } else {
                hpenOld = SelectObject(hdc, hpenRight);
                hbrOld = SelectObject(hdc, hbrRight);
        }

        /* draw the section as a coloured elongated rectangle */
        Rectangle(hdc, x1, y1, x2, y2);

        /* de-select the pen and brush in favour of the default */
        SelectObject(hdc, hpenOld);
        SelectObject(hdc, hbrOld);

}

/***************************************************************************
 * Function: DrawLink
 *
 * Purpose:
 *
 * Draw a line linking two sections. Indicates a section from each
 * file that match each other. psec points to the section in the
 * left file.
 */
void
DrawLink(HDC hdc, int cx, int cy, int lines, SECTION sec)
{
        int x1, y1, x2, y2;
        int ybase, yrange;
        SECTION other;

        other = section_getlink(sec);

        /* position the link line halfway down the section
         * - allow for the case where
         * the section is one line (ie halve the co-ords, not the line nr)
         */
        ybase = MulDiv(line_getlinenr(section_getfirstline(sec)) - 1, cy, lines);
        yrange = MulDiv(line_getlinenr(section_getlastline(sec)), cy, lines);
        y1 = ((yrange - ybase) / 2) + ybase;

        ybase = MulDiv(line_getlinenr(section_getfirstline(other)) - 1, cy, lines);
        yrange = MulDiv(line_getlinenr(section_getlastline(other)), cy, lines);
        y2 = ((yrange - ybase) / 2) + ybase;

        /* horizontal layout constants are defined as percentages of the
         * window width
         */
        x1 = cx * (L_MATCH_START + L_MATCH_WIDTH) / 100;
        x2 = cx * R_UNMATCH_START / 100;

        MoveToEx(hdc, x1, y1, NULL);
        LineTo(hdc, x2, y2);
}


/***************************************************************************
 * Function: BarClick
 *
 * Purpose:
 *
 * The user has clicked on the bar window. Translate the clicked position into
 * a line in one of the files if possible, and scroll the table window to
 * show that line.
 */
void
BarClick(HWND hwnd, int x, int y)
{
        RECT rc;
        int xleft, xright;
        int linenr, i, this;
        BOOL bIsLeft;
        int tot_left, tot_right, total_lines;
        LIST listleft, listright;
        VIEW view;
        COMPITEM item;
        TableSelection select;


        /* find size of the window to get horz scaling, and see
         * where click was
         */
        GetClientRect(hwnd, &rc);

        /* was it near either of the bars ? */

        /* horz positioning is in percentages of window width */
        xleft = max(L_UNMATCH_START + L_UNMATCH_WIDTH,
                     L_MATCH_START + L_MATCH_WIDTH);
        xright = min(R_UNMATCH_START, R_MATCH_START);
        xleft = xleft * (rc.right - rc.left) / 100;
        xright = xright * (rc.right - rc.left) / 100;


        if (x < xleft) {
                bIsLeft = TRUE;
        } else if (x > xright) {
                bIsLeft = FALSE;
        } else {
                /* click was between the two bars - ignore it */
                return;
        }


        /* calculate the vertical scaling (based on total lines displayed)
         * so that we can convert the y position into a line nr
         */

        /* get the handles to the two lists of sections */
        view = (VIEW) SendMessage(hwndClient, TM_CURRENTVIEW, 0, 0);

        /* make sure we are in expand mode */
        if (view_isexpanded(view) == FALSE) {
                return;
        }

        item = view_getitem(view, 0);

        listleft = compitem_getleftsections(item);
        listright = compitem_getrightsections(item);

        /* ignore the click if only one list of sections, since in
         * this case there is nothing drawn for him to click on.
         */
        if ((listleft == NULL) || (listright == NULL)) {
                return;
        }

        /* take the longest of the two files and use this
         * for vertical scaling. the scale is such that the longest file
         * *just fits*.
         */
        tot_left = line_getlinenr(section_getlastline(List_Last(listleft)));
        tot_right = line_getlinenr(section_getlastline(List_Last(listright)));

        total_lines = max(tot_left, tot_right);


        /* convert vertical position into a line nr. The vertical scaling
         * can be calculated from knowing that the longest list of
         * lines just fits in the window.
         */
        linenr = (int) (((long) total_lines * y) / (rc.bottom - rc.top)) + 1;

        /* check that the line is valid */
        if (bIsLeft) {
                if (linenr > tot_left) {
                        return;
                }
        } else {
                if (linenr > tot_right) {
                        return;
                }
        }

        /* search the current view, looking for a row with this
         * line nr on the correct side
         */
        for (i = 0; i < view_getrowcount(view); i++) {
                if (bIsLeft) {
                        this = view_getlinenr_left(view,i);
                } else {
                        this = view_getlinenr_right(view,i);
                }

                if (linenr == this) {
                        /* found the matching line- select it in the
                         * table window
                         */
                        select.startrow = i;
                        select.startcell = 0;
                        select.nrows = 1;
                        select.ncells = 1;
                        SendMessage(hwndRCD, TM_SELECT, 0, (long) (LPSTR)&select);
                        return;
                }
        }

        windiff_UI(TRUE);
        MessageBox(hwndClient, "Line not visible in this view",
                "WinDiff", MB_ICONSTOP|MB_OK);
        windiff_UI(FALSE);
}


