{
  (c) Пляко Андрей Дмитриевич, Санкт-Петербург, 2001
      E-Mail:  Dmitry.Plyako@paloma.spbu.ru
      NetMail: 2:5030/922.20
}
unit VCLRoutine;
// Модуль содержит набор рутинных методов для работы
// с различными VCL классами
interface

uses StdCtrls, ComCtrls, Grids, DBGrids;

type
  SetOfByte = set of byte;

{
  *** Растягивание (выравнивание) заданной колонки CustomGrid'а ***
  Если SizeColumn не указан, то будет растягиваться последняя колонка.
  Если колонка не видна на экране, то она не будет изменять свою ширину.
  Рекомендуется, при использовании этой функции в качестве обработчика
  события OnResize (формы или панели, на которой расположена сетка),
  устанавливать свойство aGrid.ScrollBars в ssVertical (а не ssBoth);
  иначе, неизбежна излишняя перерисовка из-за на мгновение возникающей
  горизонтальной полосы прокрутки.
}
procedure ResizeCustomGrid(aGrid: TCustomGrid; SizeColumn: Integer=-1);


{
  *** Растягивание (выравнивание) заданной колонки DBGrid'а ***
  Если SizeColumn не указан, то будет растягиваться последняя колонка.
  Если колонка не видна на экране, то она не будет изменять свою ширину.
}
procedure ResizeCustomDBGrid(aGrid: TCustomDBGrid; SizeColumn: Integer=-1);


{
  *** Делает панели StatusBar'а одинаковыми по ширине ***
  В параметре DontResize можно указать номера тех панелей, ширину
  которых следует оставить неизменной
}
procedure ResizeStatusBar(aBar: TStatusBar; const DontResize: SetOfByte=[]);


{
  *** Индикатор, может ли пользователь изменить ширину колонки ***
  Функция по указанным координатам курсора мыши определяет, ширину
  какой колонки собирается менять пользователь.
  Если указанные координаты не соответствуют ни одной из колонок сетки,
  то функция возвращает -1.
}
function CanResizeCustomGridColumn(aGrid: TCustomGrid;
                                   const x,y: Integer): Integer;

{
  *** Индикатор, может ли пользователь изменить ширину колонки ***
  Аналог функции CanResizeCustomGridColumn для CustomDBGrid
}
function CanResizeCustomDBGridColumn(aGrid: TCustomDBGrid;
                                     const x,y: Integer): Integer;


{
  *** Замена местами двух строчек в таблице StringGrid'а ***
  Значение элементов строк Row1 и Row2 заменяются местами
}
procedure SwapRows(aGrid: TStringGrid; const Row1, Row2: Integer);


implementation
uses SysUtils, Windows;

type
  // Вспомогательные типы, позволяющие получить доступ к скрытым полям
  TPublicDBGrid = class(TCustomDBGrid);

  TPublicGrid = class(TCustomGrid);


///////////////////////////////////////////////////////////////////
//                   Вспомогательные функции                     //
///////////////////////////////////////////////////////////////////

// Возвращает ширину вертикальной разделительной линии сетки
function CustomGridLineWidth(aGrid: TCustomGrid): Integer;
begin
  with TPublicGrid(aGrid) do
    if [goFixedVertLine, goVertLine]*Options = [] then
         Result := 0
    else Result := GridLineWidth;
end;

// Возвращает высоту горизонтальной разделительной линии сетки
function CustomGridLineHeight(aGrid: TCustomGrid): Integer;
begin
  with TPublicGrid(aGrid) do
    if [goFixedHorzLine, goHorzLine]*Options = [] then
         Result := 0
    else Result := GridLineWidth;
end;


{
  Ключевая функция ResizeCustom[Db]Grid. Вычисляет ширину,
  которая должна иметь колонка SizeColumn при Resizing'е.
  Если колонка SizeColumn не видна (находится за пределами
  отображаемой части сетки), то функция возвращает
  отрицательное число.
}
function CalcResizeCustomGrid(aGrid: TCustomGrid; SizeColumn: Integer): Integer;
var i: Integer;            // Счетчик
    NewWidth: Integer;     // Размер, который следует установить
    ColIsVisible: Boolean; // Индикатор видимости указанной колонки
    LineWidth: Integer;    // Ширина разделяющей линии
    ColumnHigh: Integer;   // Номер последней обрабатываемой колонки
    ColumnCount: Integer;  // Общее число столбцов в сетке
begin
  with TPublicGrid(aGrid) do
    begin
  // Инициализация переменных:
     ColumnCount := ColCount; // Запоминаем число колонок в сетке
     if SizeColumn<0 then     // Будем изменять ширину последней колонки
        SizeColumn := Pred(ColumnCount);
     NewWidth := ClientWidth; // Всего сетка может иметь размер ClientWidth
     LineWidth := CustomGridLineWidth(aGrid); // Ширина линий между столбцами
  // Вычтем из NewWidth ширину фиксированных колонок
     for i:=0 to Pred(FixedCols) do
        dec(NewWidth, ColWidths[i]);
  // Обрабатываем прочие колонки
     // Следует обработать все (хотя бы частично) видимые колонки
     ColumnHigh := LeftCol+VisibleColCount;
     if ColumnHigh>=ColumnCount then // _ВСЕ_ колонки видны
          dec(ColumnHigh)
     else // Одна колонка видна частично, надо будет оставить место под
          dec(NewWidth, LineWidth); //   самую правую разделяющую линию
  // Проверяем, видна ли (полностью или частично) колонка SizeColumn:
     ColIsVisible := (LeftCol<=SizeColumn) and (SizeColumn<=ColumnHigh);
  // Теперь вычтем из NewWidth ширину всех обрабатываемых колонок:
     for i:=LeftCol to ColumnHigh do
       if i<>SizeColumn then dec(NewWidth, ColWidths[i]);
  // Вычтем из NewWidth ширину всех видимых разделяющих линий
     dec(NewWidth, CustomGridLineWidth(aGrid)*(VisibleColCount+FixedCols));
     // Контрольная проверка...
     if NewWidth<0 then NewWidth := -LineWidth;
  // Все, теперь NewWidth -- та ширина, которую должна иметь SizeColumn,
     if ColIsVisible then // но устанавливать ее стоит только в этом случае
        Result := NewWidth
     else Result := -NewWidth
    end;
end;


{
  Ключевая функция CanResizeGridColumn. Определяет, сможет ли
  пользователь при указанных параметрах изменить ширину колонок:
    DelimiterX     -- самая _правая_ точка линии-разделителя
    DelimiterWidth -- ширина линии-разделителя
    MouseX         -- Иксовая координата мышки
}
function UserCanResize(const DelimiterX, DelimiterWidth, MouseX: Integer): Boolean;
const DefWidth = 8;
var RightWidth: Integer;
begin
{    Нехитрое изучение CalcAxisState в Grids.pas подсказывает такой
  алгоритм определения когда включается механизм resizing'а колонок
     А именно:
     - Ширина той области, в которой нажатие клавиши мышки считается
       за начало resizing'а равна DefWidth пикселей, если ширина
       разделяющей линии меньше DefWidth; в противном случае ширина
       этой области равна ширине разделяющей линии
     - Указанная область располагается относительно разделяющей линии
       так, что разделяющая линия находится по центру этой области.
     Вполне естественный алгоритм, не правда ли?
}
  if DefWidth<= DelimiterWidth then
     // Интервал изменения равен DelimiterWidth
     Result := (DelimiterX-DelimiterWidth <= MouseX) and
               (DelimiterX >= MouseX)
  else
    begin
     // Интервал изменения равен DefWidth
     RightWidth := DelimiterX + ((DefWidth - DelimiterWidth) shr 1);
     Result := (RightWidth-DefWidth+1 <=MouseX) and
               (RightWidth>=MouseX);
    end;
end;




///////////////////////////////////////////////////////////////////
//          Методы, объявленные в interface'ной части            //
///////////////////////////////////////////////////////////////////

procedure ResizeCustomGrid(aGrid: TCustomGrid; SizeColumn: Integer);
var NewWidth: Integer;
begin
  with TPublicGrid(aGrid) do
   begin
     if SizeColumn<0 then // Будем изменять ширину последней колонки
        SizeColumn := Pred(ColCount);
     NewWidth := CalcResizeCustomGrid(aGrid,SizeColumn);
     if NewWidth >=0 then
        ColWidths[SizeColumn] := NewWidth;
   end;
end;


///////////////////////////////////////////////////////////////////


procedure ResizeCustomDBGrid(aGrid: TCustomDBGrid; SizeColumn: Integer);
var RealColumn, NewWidth: Integer;
begin
  with TPublicDBGrid(aGrid) do
   begin
     if SizeColumn<0 then // Будем изменять ширину последней колонки
       begin
        SizeColumn := Pred(ColCount);
        if (dgIndicator in Options) then dec(SizeColumn)
       end;
     // Учтем возможное наличие индикатора в сетке
     if (dgIndicator in Options) then
          RealColumn := SizeColumn+1
     else RealColumn := SizeColumn;
     NewWidth := CalcResizeCustomGrid(aGrid,RealColumn);
     if NewWidth >=0 then
        Columns[SizeColumn].Width:=NewWidth;
   end;
end;


///////////////////////////////////////////////////////////////////


procedure ResizeStatusBar(aBar: TStatusBar; const DontResize: SetOfByte);
var NewCount, NewWidth, ModWidth: Integer;
    i: Byte;
begin
  with aBar do
   begin
     // NewCount -- столько панелей надо "подровнять"
     // NewWidth -- их суммарная ширина
     NewCount:=Panels.Count; NewWidth:=Width;
     for i:=0 to Pred(NewCount) do
       if (i in DontResize) then
         begin
          dec(NewCount);
          dec(NewWidth,Panels[i].Width);
         end;
     if NewCount>0 then
       begin
       // Подравниваем колонки
        ModWidth:=NewWidth mod NewCount; // Остаток от деления
        NewWidth:=NewWidth div NewCount; // Новая ширина колонок
        for i:=0 to Pred(Panels.Count) do
          if not (i in DontResize) then
            if ModWidth>0 then // "разбрасываем" остаток по колонкам
              begin Panels[i].Width := NewWidth+1; dec(ModWidth) end
            else Panels[i].Width := NewWidth;
       end;
   end;
end;


///////////////////////////////////////////////////////////////////


function CanResizeCustomGridColumn(aGrid: TCustomGrid;
                                   const x,y: Integer): Integer;
var i: Integer;               // Счетчик
    // Координата правой разделяющей линии колонки, содержащей (x,y)
    RightDelimiter:  Integer;
    // Координата левой  разделяющей линии колонки, содержащей (x,y)
    LeftDelimiter:   Integer;
    DelimiterWidth:  Integer; // Толщина вертикальной разделяющей линии
    DelimiterHeight: Integer; // Высота горизонтальной разделяющей линии
begin
  Result := -1;
  With TPublicGrid(aGrid) do
  // Начинаем проверку, можно ли менять ширину колонки
    if (goColSizing in Options) then
      begin
       // Инициализируем DelimiterWidth и DelimiterHeight:
       DelimiterWidth  := CustomGridLineWidth(aGrid);
       DelimiterHeight := CustomGridLineHeight(aGrid);
       // Проверяем позволяет ли y-я координата resize'ать колонку; это легко:
       RightDelimiter := -1;
       for i:=0 to Pred(FixedRows) do
         RightDelimiter := RightDelimiter + RowHeights[i] + DelimiterHeight;
       if (y>=0) and (y<=RightDelimiter) then // y-к правильный
         begin
       // Проверяем позволяет ли x-я координата resize'ать колонку; это сложнее:
          RightDelimiter := 0;
          // Первые FixedCols линий не могут менять свой размер
          for i:=0 to Pred(FixedCols) do
            RightDelimiter := RightDelimiter + ColWidths[i] + DelimiterWidth;
          // Первый видимый столбец не может изменять свою левую границу:
          RightDelimiter := RightDelimiter + ColWidths[LeftCol] + DelimiterWidth;
          LeftDelimiter := RightDelimiter;
          // Ищем две разделяющие линии, содержащих точку (x,y)
          i := LeftCol+1;
          while (i<ColCount) and (x>RightDelimiter) do
            begin
             LeftDelimiter := RightDelimiter;
             // Вычисляем координату следующей разделяющей линии
             RightDelimiter := LeftDelimiter + ColWidths[i] + DelimiterWidth;
             inc(i);
            end; {end of WHILE statement}
          if RightDelimiter>GridWidth then RightDelimiter := GridWidth+DelimiterWidth;
          // Проверка, находится ли x вблизи одной из разделяющих линий:
          if UserCanResize(RightDelimiter, DelimiterWidth, x) then
             Result := i-1
          else if UserCanResize(LeftDelimiter, DelimiterWidth, x) then
             Result := i-2;
         end; {end of "if `у y-ка правильные координаты`"}
       // Ура! Проверка окончена.
      end {end of "if (goColSizing in Options)"}
end;


///////////////////////////////////////////////////////////////////


function CanResizeCustomDBGridColumn(aGrid: TCustomDBGrid;
                                     const x,y: Integer): Integer;
begin
  Result := CanResizeCustomGridColumn(aGrid,x,y);
  // Просто учтем индикатор:
  if dgIndicator in TPublicDBGrid(aGrid).Options then
     dec(Result);
end;


///////////////////////////////////////////////////////////////////


procedure SwapRows(aGrid: TStringGrid; const Row1, Row2: Integer);
var i: Integer;
    CurString: String;
    LockResult: Boolean;
begin
  with aGrid do
    begin
      // Предотвращаем многократную перерисовку сетки
      LockResult := LockWindowUpdate(Handle);
      try
        for i:=0 to Pred(ColCount) do
          begin
           CurString     := Cells[i,Row1];
           Cells[i,Row1] := Cells[i,Row2];
           Cells[i,Row2] := CurString;
          end
      finally
        // Отображаем все сделанные изменения
        if LockResult then LockWindowUpdate(0);
      end;
    end;
end;
end.

