Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Программирование для начинающих и не только


Информационный Канал Subscribe.Ru

По материалам сайта www.gigabyte.iatp.org.ua

©Gigabyte 2004
©Gigabyte 2004

Усовершенствование FAR-a

Большинство пользователей ПК не представляют себе эффективной и оперативной работы на ПК без использования файловых менеджеров. И одним из популярнейших представителей этого класса программ является разработка Евгения Рошала под названием FAR. В этой статье пойдет речь о том, как расширить штатные возможности этой программы путем написания плагинов.

Постановка задачи

Для начала откроем директорию в которой установлен FAR и поищем то, что могло бы нам пригодится при разработке плагинов. Здесь мы обнаружим архив PlugDoc.rar и распакуем его в отдельный каталог. Теперь можно более детально разобраться с содержимым этого архива:

  • Папка Examples - здесь находятся примеры уже имеющихся расширений (Временная панель, Архиватор, Просмотр сетевого окружения и т.п.)
  • В папке Headers.c, как видно из названия хранятся заголовки и объявления функций необходимых для написания плагинов на С.
  • Папка Headers.pas включает модули с теми же объявлениями для языка Pascal.

Из других полезных вещей, на которые Вам следует обратить внимание можно назвать файл справки plugins.hlp в котором достаточно подробно описаны все возможности по использованию функций FAR-а, к которым имеет доступ плагин.

И хотя для беглого ознакомления с техникой разработки плагинов для FAR-a нам будет достаточно заглянуть в исходник HelloWorld.pas расположенный в Examples\HelloWorld. Но его точно будет недостаточно для написания более менее полезных модулей.

И поэтому после HelloWorld мы разберемся с задачей написания просмотрщика ID3 тегов в MP3 файлах.

Здравствуй, Мир!

Как уже говорилось в каталоге Expamples\HelloWorld есть исходник, простенького плагина с которого мы и начнем изучение расширений для FAR-a.

Итак скомпилировав файл HelloWorld.pas (а это можно сделать написав в командной сроке "dcc32 helloworld.pas") мы получим файл библиотеки HelloWorld.dll. который вместе с дополнительными файлами (*.lng и *.hlf) поместить в отдельную папку каталога Plugins который размещается в директории FAR-a (к примеру C:\Program Files\Far\Plugins\Simple\). Далее, следуя рекомендациям из справки и дополнительных файлов из архива PlugDoc.rar, мы закрываем FAR и открываем его снова (дабы он обнаружил наш вновь созданный плагин). Хотя лично я просто открываю еще одну его копию. Теперь для просмотра результатов наших титанических усилий мы в заново открытом FAR-e жмем F11 и внимательно изучаем список подключенных модулей (рис.1). Наш плагин в этом списке выведен под названием "Здравствуй, Мир!". При нажатии на него появляется следующее предупреждение (рис.2).



Реализация этого модуля довольно проста. Если взглянуть на исходник HelloWorld.pas то там мы увидим:

  • В самом конце - секцию экспортируемых функций SetStartupInfo, GetPluginInfo, OpenPlugin;
  • Первая из этих функций вызывается один раз при загрузке плагина. Здесь необходимо как минимум сохранить передаваемую ей ссылку на внутренние функции FAR-a при помощу которых и будет создаваться и вызываться сообщение.
  • При вызове второй функции мы возвращаем ей информацию о нашем модуле (название плагина в списке, флаги его присутствия в списке при разных экранных ражимах - Viewer, Editor, FilePanel:).
  • Последняя функция отвечает за собственно отображения сообщения при запуске модуля из списка.
  • Остановимся на каждом пункте более подробно.

    Процедура SetStartupInfo.

    
    procedure SetStartupInfo(var psi: TPluginStartupInfo); stdcall;
    begin
      Move(psi, FARAPI, SizeOf(FARAPI));
    end;
    

    Как уже говорилось она вызывается 1 раз при загрузке плагина и передает ему всю необходимую информацию в параметре psi. Параметр psi в свою очередь содержит список всех жизненно важных функций:

    
      TPluginStartupInfo = packed record
        StructSize: integer;
        ModuleName: packed array[0..Pred(NM)] of char;
        ModuleNumber: integer;
        RootKey: PChar;
        Menu: TFarApiMenu;
        Dialog: TFarApiDialog;
        Message: TFarApiMessage;
        GetMsg: TFarApiGetMsg;
        Control: TFarApiControl;
        SaveScreen: TFarApiSaveScreen;
        RestoreScreen: TFarApiRestoreScreen;
        GetDirList: TFarApiGetDirList;
        GetPluginDirList: TFarApiGetPluginDirList;
        FreeDirList: TFarApiFreeDirList;
        Viewer: TFarApiViewer;
        Editor: TFarApiEditor;
        CmpName: TFarApiCmpName;
        CharTable: TFarApiCharTable;
        Text: TFarApiText;
        EditorControl: TFarApiEditorControl;
        FSF: PFarStandardFunctions;
        ShowHelp: TFarApiShowHelp;
        AdvControl: TFarApiAdvControl;
        InputBox: TFarApiInputBox;
        DialogEx: TFarApiDialogEx;
        SendDlgMessage: TFarApiSendDlgMessage;
        DefDlgProc: TFarApiDefDlgProc;
        Reserved: array[0..1] of DWORD;
      end;
    

    Большинство из которых отвечают за вызов тех или иных сервисных функций самого FAR-a. Так функции Menu, Message, Dialog, Control отвечают за вызов соответственно указателей на меню, вывод информационных сообщений, создание диалогов, получение информации об имеющихся в наличии панелях (Файловая, Информационная, Временная:).Другие же функции позволяют выводить текст, справку, открывать редактор и просмотрщик файлов. Кроме этих функций используется также и другой набор из т.н. Стандартных функций (FSF:PFarStandardFunctions) в который включены функции по работе со строками (сравнение, преобразование:), с буфером обмена, с таблицами перекодировок символов и т.п.

    Процедура GetPluginInfo. В модуле HelloWorld реализована следующим образом:

    
    procedure GetPluginInfo(var pi: TPluginInfo); stdcall;
    begin
      pi.StructSize:= SizeOf(pi);
      pi.Flags:= PF_EDITOR;
    
      PluginMenuStrings[0]:= GetMsg(MTitle);
      pi.PluginMenuStrings:= @PluginMenuStrings;
      pi.PluginMenuStringsNumber:= 1;
    end;
    

    Здесь происходит "заполнение анкеты" нашего плагина (т.е. структуры TPluginInfo) путем указания флага и названия нашего модуля. Кроме этих параметров можно также заполнить параметры:

    • DiskMenuStrings: PPCharArr - указатель на список с названиями пунктов, которые будут добавлены в меню дисков (Alt+F1,Alt+F2).
    • DiskMenuNumbers: PintArr - указатель на список с клавишами быстрого вызова пункта из меню дисков. Если этот параметр равен NULL, то FAR автоматически назначит горячие клавиши для каждого пункта.
    • DiskMenuStringsNumber: integer - количество строк, которые будут добавляться в меню дисков.
    • PluginConfigStrings: PPCharArr - список пунктов, которые будут добавлены в меню конфигурации подключаемых модулей.
    • PluginConfigStringsNumber: integer - количество этих самых пунктов.
    • CommandPrefix: Pchar - префикс команд, используется для перехвата стандартной реакции FAR на командную строку с различными префиксами типа http://www.site.com, ftp://ftp.site.com и т.д.

    Функция OpenPlugin вызывается каждый раз при запуске плагина тем или иным образом. Здесь размещается код, который собственно и отвечает за отображение и деятельность нашего модуля:

    
    function OpenPlugin(OpenFrom: integer; Item: integer): THandle; stdcall;
    var
      Msg: array[0..6] of PChar;
    begin
      Msg[0]:= GetMsg(MTitle);
      Msg[1]:= GetMsg(MMessage1);
      Msg[2]:= GetMsg(MMessage2);
      Msg[3]:= GetMsg(MMessage3);
      Msg[4]:= GetMsg(MMessage4);
      Msg[5]:= #01#00;                   // separator line
      Msg[6]:= GetMsg(MButton);
    
      FARAPI.Message(FARAPI.ModuleNumber,             // PluginNumber
                     FMSG_WARNING or FMSG_LEFTALIGN,  // Flags
                    'Contents',                       // HelpTopic
                     @Msg,                            // Items
                     7,                               // ItemsNumber
                     1);                              // ButtonsNumber
    
      result:= INVALID_HANDLE_VALUE;
    end;
    

    Здесь в первых строках идет заполнение массива строк из которых будет состоять наше сообщение. Значения этих строк берутся из стандартного языкового файла (т.е. файла с расширением lng расположенного в той же директории, что и модуль и соответствующего используемому в FAR языку). Далее происходить вызов стандартной функции Message которая и отображает окно сообщения. Присвоение результату функции значения INVALID_HANDLE_VALUE необходимо для того, чтоб потом не возится с освобождение ресурсов, которые выделяет FAR под создание новой панели для нашего плагина. В других же случаях необходимо будет реализовать еще и процедуру ClosePlugin, которая теоретически принимает дескриптор созданный в функции OpenPlugin и освобождать соответствующие ему ресурсы.

    ID3 Вьювер

    Теперь уже можно перейти от баловства к созданию более интересного и практичного плагина.

    Для приготовления этого блюда нам понадобится:

    • IDE Delphi 3,4,5,6,7,8 - одна штука
    • Компонент TvID3v1 из набор компонентов JCL/JVCL для чтения id3 тегов, или любой другой компонент с теми же функциями (можно и не компонент, а просто функцию или процедуру).
    • Набор заголовочных файлов из директории Headers.pas
    • Другие приправы по вкусу.

    После сбора всех ингредиентов нам следует запустить Delphi и создать в нем новый проект-библиотеку. В эту библиотеку мы будет поочередно носить все функции рассмотренные в предыдущей главе.

    Сперва мы сохраним указатель на сервисные функции FAR-a:

    
    library Simple;
    uses
      windows,
      plugin,
      DialogDef in 'DialogDef.pas';
    
    procedure SetStartupInfo(var psi: TPluginStartupInfo); stdcall;
    begin
      Move(psi, FARAPI, SizeOf(FARAPI));
    end;
    

    Далее добавляем название нашего модуля в список модулей вызываемых клавишей F11:

    
    var
      PluginMenuStrings: array[0..0] of PChar;
    
    procedure GetPluginInfo(var pi: TPluginInfo); stdcall;
    begin
      pi.StructSize:= SizeOf(pi);
      pi.Flags:= PF_EDITOR;
      PluginMenuStrings[0]:= 'Simple Plugin';
      pi.PluginMenuStrings:= @PluginMenuStrings;
      pi.PluginMenuStringsNumber:= 1;
    end;
    
    Теперь реализуем собственно отображение информации на экране:
    
    function OpenPlugin(OpenFrom: integer; Item: integer): THandle; stdcall;
    begin
      CreateDialog;
      FARAPI.Dialog(FARAPI.ModuleNumber, -1, -1, 40, 20, nil, @DialogItems, DialogItemsCount);
      result:= INVALID_HANDLE_VALUE;
    end;
    

    Здесь главную роль играет объявленная в модуле DialogDef функция, которая создает элементы диалога, который отображается посредством функции Dialog.

    Сам вид уже готового диалога показан на рисунке 3. Как видим наш диалог состоит из 7-ми элементов:

    • Панели с названием "Информация MP3"
    • Статического текста с названием <Файл> (Аналог Tlabel в стандартных оконных приложениях)
    • Редактируемого названия файла
    • Трех строк текста, которые отображают соответственно название, исполнителя и альбом
    • Кнопки закрыть

    Все эти элементы создаются путем заполнения массива структур TfarDialogItem, который имеет довольно много различных параметров не востребованных при реализации данного плагина. И поэтому мы заполним только самые нужные из полей т.е. координаты, тип поля, название каждого элемента:

    
      with DialogItems[0] do
       begin
        ItemType:=DI_DOUBLEBOX;
        X1:=0;Y1:=0;
        X2:=39;Y2:=19;
        Focus:=0;
        Flags:=DIF_BOXCOLOR;
        S:=FARAPI.GetMsg(FARAPI.ModuleNumber,0);
        with Data do
     ZeroMemory(@Data,SizeOf(Data));
        with Data do
         Move(Pointer(S)^,Data,Length(S));
       end;
    

    Как видите, название панели (как и других элементов) мы берем из языкового файла, что существенно облегчит нам трансляцию нашего плагина на другие языки.

    Создание других элементов также не является более трудной задачей, только при назначении их координат следует помнить о том, что они задаются в клиентской системе координат нашей панели.

    Теперь возьмемся за заполнение названий наших компонентов. Так для заполнения элемента, который отображает название текущего файла мы используем функцию FARAPI.Control в таком контексте:
    FARAPI.Control(INVALID_HANDLE_VALUE,FCTL_GETPANELINFO,@P);

    Такой код позволяет нам в переменной P получить указатель на текущую панель, которой в нашем случае является файловая панель. В чем мы еще раз убеждаемся сделав в коде проверку:

    
         if @P<>nil then
           if (P.PanelType=PTYPE_FILEPANEL) and (P.ItemsNumber>0) then
            begin
             S:=P.PanelItems^[P.CurrentItem].FindData.cFilename;
             with Data do
              ZeroMemory(@Data,SizeOf(Data));
             with Data do
              Move(Pointer(S)^,Data,Min(512,Length(S)));
            end;
    

    Далее, как видно из кода, мы запрашиваем информацию о текущей позиции курсора и считываем информацию о текущем файле. Эти данные (точнее имя текущего файла) мы и передаем в параметр Data нашего элемента, а также используем его для извлечения ID3 тега посредством компонента TvID3v1:

    
    id3:=TJvId3v1.Create(nil);
     try
      id3.FileName:=S;
      with DialogItems[3] do
       begin
        ItemType:=DI_TEXT;
        X1:=2;Y1:=5;X2:=35;
        if id3.TagPresent then S:=FARAPI.GetMsg(FARAPI.ModuleNumber,2)+': '+id3.SongName
        else S:='No mp3 tag found';
        with Data do
         ZeroMemory(@Data,SizeOf(Data));
        Move(Pointer(S)^,Data,35);
        S:='';
       end;
    

    Если же данный файл не содержит тегов или это вообще не тот файл, то мы просто записываем на место данных строку "No mp3 tag found".

    В конце модуля мы на всякий случай очищаем массив DialogItems:

    
    begin
     ZeroMemory(@DialogItems,SizeOf(DialogItems));
    end.
    

    Замечания и предложения

    Инсталляция этого плагина реализуется тем же образом, что HelloWorld. Но перед этим следует дополнительно создать языковый файл с указанием названий всех элементов нашего диалога. К счастью программистов и простых пользователей языковый файл FAR это простой текстовый файл, в котором в отдельных строках размещаются названия отдельных элементов. Первая строка заполняется служебным идентификатором <.Language>, в который записывается название используемого языка. В нашем случае мы создадим языковый файл следующего содержания:

    
     .Langugage = Russian,Russian (Русский)
    "Информация MP3"
    "Файл"
    "Название"
    "Исполнитель"
    "Альбом"
    "Закрыть"
    

    Кроме того тут есть возможность использования комментариев, а также символов табуляции, конца строки и т.п. Которые записываются в стиле C/C++.

    Естественно нет предела совершенству и эта маленькая библиотека еще очень далека от него. Ее возможностей хватит пожалуй только то чтоб быстро упорядочить ваши mp3 файлы не выходя из любимого файлового менеджера. К возможным последующим усовершенствованиям этого плагина можно отнести добавление дополнительных ID3 полей и возможность их редактирования, а также более быстрый вызов этой надстройки посредством скажем горячих клавиш, или вывод информации на информационную панель (Ctrl+L); но это уже темя для отдельной статьи.

    Листинг - Simple.dpr

    
    {$APPTYPE CONSOLE}
    {$MINENUMSIZE 4}
    library Simple;
    uses
      windows,
      plugin,
      DialogDEf in 'DialogDEf.pas';
    
    procedure SetStartupInfo(var psi: TPluginStartupInfo); stdcall;
    begin
      Move(psi, FARAPI, SizeOf(FARAPI));
    end;
    
    var
      PluginMenuStrings: array[0..0] of PChar;
    
    procedure GetPluginInfo(var pi: TPluginInfo); stdcall;
    begin
      pi.StructSize:= SizeOf(pi);
      pi.Flags:= PF_EDITOR;
      PluginMenuStrings[0]:= 'Simple Plugin';
      pi.PluginMenuStrings:= @PluginMenuStrings;
      pi.PluginMenuStringsNumber:= 1;
    end;
    
    function OpenPlugin(OpenFrom: integer; Item: integer): THandle; stdcall;
    begin
      CreateDialog;
      FARAPI.Dialog(FARAPI.ModuleNumber,-1,-1,40,20,nil,@DialogItems,DialogItemsCount);
      result:= INVALID_HANDLE_VALUE;
    end;
    
    exports
      SetStartupInfo,GetPluginInfo,OpenPlugin;
    end.
    
    

    Листинг - DialogDef.pas

    
    unit DialogDef;
    
    interface
    uses plugin,windows;
    Const DialogItemsCount = 7;
    
    var FARAPI: TPluginStartupInfo;
    var DialogItems: array[0..DialogItemsCount-1] of TFarDialogItem;
    
    procedure CreateDialog;
    implementation
    uses jvid3v1,DebugUnit;
    
    function Min(A,B:Integer):Integer;
    begin
    Result:=A;
    if A>B then Result:=B;
    end;
    
    procedure CreateDialog;
    var S:String;Mem:Pointer;
        P:TPanelInfo;
        id3:TJvId3v1;
    begin
      with DialogItems[0] do
       begin
        ItemType:=DI_DOUBLEBOX;
        X1:=0;Y1:=0;
        X2:=39;Y2:=19;
        Focus:=0;
        Flags:=DIF_BOXCOLOR;
        S:=FARAPI.GetMsg(FARAPI.ModuleNumber,0);
        with Data do
         ZeroMemory(@Data,SizeOf(Data));
        with Data do
         Move(Pointer(S)^,Data,Length(S));
       end;
      with DialogItems[1] do
       begin
        ItemType:=DI_TEXT;
        X1:=2;Y1:=2;
        S:=FARAPI.GetMsg(FARAPI.ModuleNumber,1);
        X2:=Length(S);
        with Data do
         ZeroMemory(@Data,SizeOf(Data));
        with Data do
         Move(Pointer(S)^,Data,Length(S));
       end;
      with DialogItems[2] do
       begin
        ItemType:=DI_EDIT;
        X1:=2;Y1:=3;
        X2:=37;
        Focus:=1;
        DefaultButton:=True;
        GetMem(Mem,2048);
        try
        FARAPI.Control(INVALID_HANDLE_VALUE,FCTL_GETPANELINFO,@P);
        finally
         if @P<>nil then
           if (P.PanelType=PTYPE_FILEPANEL) and (P.ItemsNumber>0) then
            begin
             S:=P.PanelItems^[P.CurrentItem].FindData.cFilename;
             with Data do
              ZeroMemory(@Data,SizeOf(Data));
             with Data do
              Move(Pointer(S)^,Data,Min(512,Length(S)));
            end;
         FreeMem(Mem,2048);
        end;
       end;
    id3:=TJvId3v1.Create(nil);
     try
      id3.FileName:=S;
      with DialogItems[3] do
       begin
        ItemType:=DI_TEXT;
        X1:=2;Y1:=5;X2:=35;
        if id3.TagPresent then S:=FARAPI.GetMsg(FARAPI.ModuleNumber,2)+': '+id3.SongName
        else S:='No mp3 tag found';
        with Data do
         ZeroMemory(@Data,SizeOf(Data));
        Move(Pointer(S)^,Data,35);
        S:='';
       end;
      with DialogItems[4] do
       begin
        ItemType:=DI_TEXT;
        X1:=2;Y1:=6;X2:=35;
        if id3.TagPresent then S:=FARAPI.GetMsg(FARAPI.ModuleNumber,3)+': '+id3.Artist
        else S:='No mp3 tag found';
        with Data do
         ZeroMemory(@Data,SizeOf(Data));
        Move(Pointer(S)^,Data,35);
        S:='';
       end;
      with DialogItems[5] do
       begin
        ItemType:=DI_TEXT;
        X1:=2;Y1:=7;X2:=35;
        if id3.TagPresent then S:=FARAPI.GetMsg(FARAPI.ModuleNumber,4)+': '+id3.Album
        else S:='No mp3 tag found';
        with Data do
         ZeroMemory(@Data,SizeOf(Data));
        Move(Pointer(S)^,Data,35);
        S:='';
       end;
     finally
      id3.free;
     end;
      with DialogItems[6] do
       begin
        ItemType:=DI_BUTTON;
        S:=FARAPI.GetMsg(FARAPI.ModuleNumber,5);
        X1:=15;
        X2:=Length(S);
        Y1:=18;
        Y2:=1;
        Focus:=1;
        DefaultButton:=True;
        with Data do
         ZeroMemory(@Data,SizeOf(Data));
        with Data do
         Move(Pointer(S)^,Data,Length(S));
        S:='';
       end;
    end;
    
    begin
    ZeroMemory(@DialogItems,SizeOf(DialogItems));
    end.
    
    

    Листинг - Simple.lng

    
     .Langugage = Russian,Russian (Русский)
    "Информация MP3"
    "Файл"
    "Название"
    "Исполнитель"
    "Альбом"
    "Закрыть"
    

    Subscribe.Ru
    Поддержка подписчиков
    Другие рассылки этой тематики
    Другие рассылки этого автора
    Подписан адрес:
    Код этой рассылки: comp.soft.prog.programmershelp
    Отписаться
    Вспомнить пароль

    В избранное