|
Собственные типы колонок на панели плагина.
Введение.
В этой статье я попробую рассказать, как создавать полноценные нестандартные
типы колонок на панели вашего плагина, и как обойти ограничения Far Manager по
созданию таких колонок.
1. Создание собственных типов колонок и помещение в них ваших данных.
Давайте для начала выполним тривиальную задачу и добавим к панели плагина
колонку с собственными строковыми данными (например номерами контактов в ICQ).
Far Manager поддерживает до 10 пользовательских колонок на панели плагина.
Пусть на панели нашего плагина будет только две колонки: имя контакта в ICQ и его
номер. Пусть колонка с номером контакта будет пользовательской. В качестве хранилища
имени контакта будем использовать имена файлов на панели. Код функции GetFindData
плагина будет следующим:
int __stdcall GetFindData( HANDLE hPlugin, 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 - число требуемых нестандартных типов колонок. В нашем // cлучае - 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 __stdcall FreeFindData ( HANDLE hPlugin, 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 параметра
OpenPluginInfo *Item, передаваемого в функцию GetOpenPluginInfo плагина:
void __stdcall GetOpenPluginInfo ( HANDLE hPlugin, 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 колонки, вторая из которых будет содержать наши данные.
2. Чего-то не хватает...
Действительно, для нашей колонки на панели не хватает заголовка. Для того,
чтобы добавить его туда, надо изменить поле ColumnTitles элемента массива
PanelModesArray, отвечающего за наш режим панели (в нашем случае - нулевой).
Добавим в функцию GetOpenPluginInfo следующие строки:
static const char *CustomColumnTitles[2] = {NULL, "Contact ID"};
Info->PanelModesArray[0].PanelTitles = (char**)&CustomColumnTitles;
// для первой колонки в качестве заголовка используется значение // NULL (стандартный заголовок), т.к. в первой колонке хранятся // в нашем случае имена файлов, и заголовок для такого типа // колонки Far Manager известен ("Имя").
Теперь, казалось бы все в порядке, но...
3. Проблема.
Все работает как надо и у нашей колонки есть даже корректный заголовок.
Но что будет, если плагин предоставит пользователю возможность самостоятельно
задавать типы и ширину колонок на панели, как это делают многие плагины?
На первый взгляд это не должно вызывать проблем, но представим себе следующую
ситуацию: в качестве строки, определяющей типы колонок на "нулевом" режиме панели
пользователь ввел "C0, N". Заметим, что эта строка отличается, от той строки, что
мы использовали выше. Колонки поменялись местами. Но ведь массив, в котором хранятся
заголовки панелей остался неизменным. Значит теперь заголовки колонок стали неверны.
4. Решение.
Следовательно, если плагин предоставляет пользователю возможность самому
определять типы колонок на панели, он должен знать, какие колонки на данный
момент отображаются и автоматически создавать соответствующий массив заголовков
для этих колонок. Для этого плагин должен самостоятельно разбирать строку,
определяющую типы колонок. Пусть в нашем случае пользователь может самостоятельно
задать только типы колонок для "нулевого" режима панели. Создадим еще одну
функцию, задачей которой будет разбор строки, определяющей типы колонок на панели
и автоматическое создание массива заголовков этих колонок:
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);
memset (pColumnTitles, 0, nTokens*4);
strcpy (lpTokens, lpUserColumnTypes);
lpToken = strtok (lpTokens, ",");
nTokens = 0;
// найдем все колонки, тип которых задан как C0 // и поместим в соответствующий элемент массива // ссылку на нужный заголовок. Для всех остальных // типов колонок используем значение NULL, // полагая, что остальные типы колонок известны // Far Manager.
while ( lpToken ) { while ( *lpToken && (*lpToken == ' ') ) 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.
Переменная обязательно должна быть инициализирована. Тогда в функция ClosePlugin
будет выглядеть так:
void __stdcall ClosePlugin ( HANDLE hPlugin ) { free (pColumnTitles); }
Теперь модифицируем функцию GetOpenPluginInfo, чтобы в нашем режиме панели
правильно отображались заголовки.
void __stdcall GetOpenPluginInfo ( HANDLE hPlugin, OpenPluginInfo *Info ) { ...
// если произошел повторный вызов функции GetOpenPluginInfo до вызова // фунции ClosePlugin (что в Far Manager совсем не редкость), память // занятая под массив pColumnTitles должна быть освобождена.
if ( pColumnTitles ) free (pColumnTitles);
pColumnTitles = ConvertColumnTypes (lpUserColumnTypes);
// lpUserColumnTypes - глобальная переменная, содержащая // пользовательские типы колонок для нашего режима панели.
CustomPanelModes[0].ColumnTitles = pColumnTitles;
... }
Теперь плагин правильно отображает заголовки нестандартной колонки "Contact ID".
5. Красивости.
Согласитесь, использование идентификаторов C0-C9 для типов колонок собственного
режима панели не очень красиво. Тем более, что каждый плагин использует именно эти
безликие идентификаторы. И пользователь вынужден каждый раз читать файлы помощи,
чтобы вспомнить, что ему нужно написать в строке типов колонок, чтобы на панели
плагина появилась нужная ему колонка. Значит нам нужно преобразовывать заданные
пользователем осмысленные идентификаторы типов колонок в идентификаторы, понятные
Far Manager. Для этого слегка модифицируем функцию ConvertColumnTypes:
char **ConvertColumnTypes ( const char *lpUserColumnTypes, // строка, содержащая типы колонок, заданные пользователем char *lpRealColumnTypes // строка, в которую будут помещены типы колонок, понятные Far Manager. // память под эту строку должна быть выделена заранее. ) { 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);
memset (pColumnTitles, 0, nTokens*4);
strcpy (lpTokens, lpUserColumnTypes);
lpToken = strtok (lpTokens, ",");
nTokens = 0;
while ( lpToken ) { while ( *lpToken && (*lpToken == ' ') ) 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 __stdcall GetOpenPluginInfo ( HANDLE hPlugin, OpenPluginInfo *Info ) { ...
CustomPanelModes[0].ColumnTitles = pColumnTitles; CustomPanelModes[0].ColumnTypes = &RealColumnTypes;
... }
6. Заключение.
Что можно сказать в заключение? Если написанное выше было вам понятно и ново,
то теперь вы можете использовать собственные типы колонок на панелях ваших плагинов
и делать их еще более настраиваемыми и удобными для пользователя. Как пользователь,
я буду рад, если вы к этому стремитесь.
ПРИМЕЧАНИЕ.
Приведенные выше фрагменты кода являются исключительно демонстрационными и не могут
считаться частью полноценного плагина, хотя вы можете использовать их, как пожелаете.
|