WARP ItSelf
WARP_ItSelf@mail.ru
В этой статье я попробую рассказать, как создавать полноценные нестандартные типы колонок на панели вашего плагина, и как обойти ограничения Far Manager по созданию таких колонок.
Давайте для начала выполним тривиальную задачу и добавим к панели плагина колонку с собственными строковыми данными (например номерами контактов в ICQ). Far Manager поддерживает до 10 пользовательских колонок на панели плагина. Пусть на панели нашего плагина будет только две колонки: имя контакта в ICQ и его номер. Пусть колонка с номером контакта будет пользовательской. В качестве хранилища имени контакта будем использовать имена файлов на панели. Код функции GetFindData плагина будет следующим:
int WINAPI GetFindData(HANDLE hPlugin,
struct PluginPanelItem **pPanelItem,
int *pItemsNumber,
int OpMode)
{
PluginPanelItem *pItems = (PluginPanelItem*)malloc (10*sizeof(PluginPanelItem));
memset (pItems, 0, 10*sizeof(PluginPanelItem));
// пусть на панели требуется отобразить 10 элементов.
for (int i = 0; i < 10; i++)
{
strcpy (pItems[i].FindData.cFileName, pNames[i]);
// pNames - массив, в котором хранятся имена контактов.
// А теперь передадим Far Manager строковые данные, которые он
// будет отображать в нашей колонке:
// a) Выделим память для массива, хранящего ссылки на пользовательские
// данные для каждого нестандартного типа колонки:
pItems[i].CustomColumnData = (char**)malloc (nCount*4);
// nCount - число требуемых нестандартных типов колонок. В нашем случае - 1.
// б) Заполним массив нашими данными. У нас всего одна собственная
// колонка на панели, поэтому нам потребуется заполнить только первый элемент
// массива:
pItems[i].CustomColumnData[0] = (char*)malloc (260);
strcpy (pItems[i].CustomColumnData[0], pContactNumbers[i]);
// pContactNumbers - массив строк, содержащих номера контактов ICQ.
// в) Объясним Far Manager, что мы хотим использовать только одну собственную
// колонку на панели нашего плагина. Для этого надо заполнить поле
// CustomColumnNumber.
pItems[i].CustomColumnNumber = 1;
}
*pPluginItem = pItems;
*pItemsNumber = i;
return TRUE;
}
Теперь необходимо позаботиться об освобождении памяти, которую мы выделили для массива CustomColumnData и его элементов. Это нужно сделать в функции FreeFindData плагина, которую Far Manager вызывает для того, чтобы плагин освободил всю занятую под элементы панели память (все данные Far Manager копирует во внутренние структуры):
void WINAPI FreeFindData (HANDLE hPlugin,
struct PluginPanelItem *PanelItem,
int ItemsNumber)
{
for (int i = 0; i < ItemsNumber; i++)
{
for (int j = 0; j < PanelItem[i].CustomColumnNumber; j++)
free (PanelItem[i].CustomColumnData[j]);
free (PanelItem[i].CustomColumnData);
}
free (PanelItem);
}
Можно собирать плагин. После запуска легко будет заметить, что собственная колонка на панели не появилась. Чего же не хватает? Надо объяснить Far Manager, какие типы колонок требуется показывать на нашей панели. Для пользовательских колонок используются идентификаторы C0-C9. Создадим собственный режим панели. Для этого надо изменить поле PanelModesArray параметра *Item, передаваемого в функцию GetOpenPluginInfo плагина:
void WINAPI GetOpenPluginInfo (HANDLE hPlugin,
struct OpenPluginInfo *Info)
{
static PanelMode CustomPanelModes[1] = {
"C0, N", "15, 0", NULL, FALSE, TRUE, TRUE,
TRUE, NULL, NULL, {0, 0}};
memset (Info, 0, sizeof(OpenPluginInfo));
Info->StructSize = sizeof(OpenPluginInfo);
Info->PanelModesArray = &CustomPanelModes;
Info->PanelModesNumber = 1;
// изменим "нулевой" режим панели (LCtrl-0), так, чтобы в нем
// на панели отображались 2 колонки: имя файла (а нашем случае
// имя контакта) и номер контакта (пользовательская колонка "C0").
}
Теперь при выборе "нулевого" режима на панели нашего плагина (LCtrl-0) появятся 2 колонки, вторая из которых будет содержать наши данные.
Действительно, для нашей колонки на панели не хватает заголовка. Для того, чтобы добавить его туда, надо изменить поле ColumnTitles элемента массива PanelModesArray, отвечающего за наш режим панели (в нашем случае - нулевой). Добавим в функцию GetOpenPluginInfo следующие строки:
static const char *CustomColumnTitles[2] = {NULL, "Contact ID"};
Info->PanelModesArray[0].PanelTitles = (char**)&CustomColumnTitles;
// для первой колонки в качестве заголовка используется значение
// NULL (стандартный заголовок), т.к. в первой колонке хранятся
// в нашем случае имена файлов, и заголовок для такого типа
// колонки Far Manager известен ("Имя").
Теперь, казалось бы все в порядке, но...
Все работает как надо и у нашей колонки есть даже корректный заголовок. Но что будет, если плагин предоставит пользователю возможность самостоятельно задавать типы и ширину колонок на панели, как это делают многие плагины? На первый взгляд это не должно вызывать проблем, но представим себе следующую ситуацию: в качестве строки, определяющей типы колонок на "нулевом" режиме панели пользователь ввел "C0, N". Заметим, что эта строка отличается, от той строки, что мы использовали выше. Колонки поменялись местами. Но ведь массив, в котором хранятся заголовки панелей остался неизменным. Значит теперь заголовки колонок стали неверны.
Следовательно, если плагин предоставляет пользователю возможность самому определять типы колонок на панели, он должен знать, какие колонки на данный момент отображаются и автоматически создавать соответствующий массив заголовков для этих колонок. Для этого плагин должен самостоятельно разбирать строку, определяющую типы колонок. Пусть в нашем случае пользователь может самостоятельно задать только типы колонок для "нулевого" режима панели. Создадим еще одну функцию, задачей которой будет разбор строки, определяющей типы колонок на панели и автоматическое создание массива заголовков этих колонок:
// lpUserColumnTypes - строка, содержащая типы колонок,
// заданные пользователем
char** ConvertColumnTypes (const char *lpUserColumnTypes)
{
int nTokens = 0;
char *lpTokens = (char*)malloc (strlen(lpUserColumnTitles)+1);
strcpy (lpTokens, lpUserColumnTypes);
char *lpToken = strtok (lpTokens, ",");
// посчитаем количество заданных пользователем колонок
while ( lpToken )
{
nTokens++;
lpToken = strtok (NULL, ",");
}
char **pColumnTitles = (char**)malloc (nTokens*4);
strcpy (lpTokens, lpUserColumnTypes);
lpToken = strtok (lpTokens, ",");
nTokens = 0;
// найдем все колонки, тип которых задан как C0
// и поместим в соответствующий элемент массива
// ссылку на нужный заголовок. Для всех остальных
// типов колонок используем значение NULL,
// полагая, что остальные типы колонок известны
// Far Manager.
while ( lpToken )
{
if ( !FSF.LStricmp (lpToken, "C0") )
pColumnTitles[nTokens] = "Contact ID";
else
pColumnTitles[nTokens] = NULL;
nTokens++;
lpToken = strtok (NULL, ",");
}
free (lpTokens);
return pColumnTitles;
}
Данная функция возвращает созданный массив заголовков (безусловно, это не самое
красивое, но простое решение). Ввиду того, что количество элементов данного массива
заранее неизвестно, выделить под него память заранее нельзя, но освободить занятую
массивом память все же нужно. Это проще всего сделать в функции ClosePlugin,
предварительно сохранив адрес массива в глобальной переменной (или в члене класса).
Пусть в нашем случае существует глобальная переменная char **pColumnTitles = NULL
void WINAPI ClosePlugin (HANDLE hPlugin)
{
free (pColumnTitles);
}
Теперь модифицируем функцию GetOpenPluginInfo, чтобы в нашем режиме панели правильно отображались заголовки.
void WINAPI GetOpenPluginInfo (HANDLE hPlugin,
struct OpenPluginInfo *Info)
{
...
// если произошел повторный вызов функции GetOpenPluginInfo до вызова
// фунции ClosePlugin (что в Far Manager совсем не редкость), память
// занятая под массив pColumnTitles должна быть освобождена.
if ( pColumnTitles )
free (pColumnTitles);
pColumnTitles = ConvertColumnTypes (lpUserColumnTypes);
// lpUserColumnTypes - глобальная переменная, содержащая пользовательские типы колонок
// для нашего режима панели.
CustomPanelModes[0].ColumnTitles = pColumnTitles;
...
}
Теперь плагин правильно отображает заголовки нестандартной колонки "Contact ID".
Согласитесь, использование идентификаторов C0-C9 для типов колонок собственного режима панели не очень красиво. Тем более, что каждый плагин использует именно эти безликие идентификаторы. И пользователь вынужден каждый раз читать файлы помощи, чтобы вспомнить, что ему нужно написать в строке типов колонок, чтобы на панели плагина появилась нужная ему колонка. Значит нам нужно преобразовывать заданные пользователем осмысленные идентификаторы типов колонок в идентификаторы, понятные Far Manager. Для этого слегка модифицируем функцию ConvertColumnTypes:
// lpUserColumnTypes - строка, содержащая типы колонок,
// заданные пользователем
// lpRealColumnTypes - строка, в которую будут помещены
// типы колонок, понятные Far Manager.
// память под эту строку должна быть
// выделена заранее.
char **ConvertColumnTypes (const char *lpUserColumnTypes,
char *lpRealColumnTypes)
{
int nTokens = 0;
char *lpTokens = (char*)malloc (strlen (lpUserColumnTypes)+1);
strcpy (lpTokens, lpUserColumnTypes);
char *lpToken = strtok (lpTokens, ",");
while ( lpToken )
{
nTokens++;
lpToken = strtok (NULL, ",");
}
char **pColumnTitles = (char**)malloc (nTokens*4);
strcpy (lpTokens, lpUserColumnTypes);
lpToken = strtok (lpTokens, ",");
nTokens = 0;
while ( lpToken )
{
// пусть в качестве пользоваетельского идентификатора
// колонки "Contact ID" выступает строка "ID".
if ( !FSF.LStricmp (lpToken, "ID") ||
!FSF.LStricmp (lpToken, "C0") )
{
strcat (lpRealColumnTypes, "C0");
pColumnTitles[nTokens] = "Contact ID";
}
else
{
pColumnTitles[nTokens] = NULL;
strcat (lpRealColumnTypes, lpToken);
}
strcat (lpRealColumnTypes, ",");
nTokens++;
lpToken = strtok (NULL, ",");
}
free (lpTokens);
return pColumnTitles;
}
Теперь в качестве идентификатора для колонки, содержащей номер контакта ICQ пользователь может использовать строку "ID" наравне с "C0". К глобальным переменным требуется добавить переменную char RealColumnTypes[260] (размер этой переменной может быть иным, а в идеальном варианте его должна высчитывать функция ConvertColumnTypes). Осталось только изменить функцию GetOpenPluginInfo:
void WINAPI GetOpenPluginInfo (HANDLE hPlugin,
struct OpenPluginInfo *Info)
{
...
CustomPanelModes[0].ColumnTitles = pColumnTitles;
CustomPanelModes[0].ColumnTypes = &RealColumnTypes;
...
}
Что можно сказать в заключение? Если написанное выше было вами понятно и ново, то теперь вы можете использовать собственные типы колонок на панелях ваших плагинов и делать их еще более настраиваемыми и удобными для пользователя. Как пользователь, я буду рад, если вы к этому стремитесь.
Приведенные выше фрагменты кода являются исключительно демонстрационными и не могут считаться частью полноценного плагина, хотя вы можете их использовать их, как пожелаете.
02.06.2004