Создание надстроек для офисного пакета «МойОфис». Часть 3. Автозаполнение для API и знакомимся с контролами

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Все статьи цикла:

  1. Введение

  2. Расширяем структуру файлов надстройки и удалённая отладка

  3. Автозаполнение для API и знакомимся с контролами (эта статья)

Напомню, что поскольку, худо ли - бедно, я добился удалённой отладки для надстроек (напомню, так почему-то  назвали разработчики «МойОфис» макросы с возможностью использовать пусть и примитивный, но набор контролов и форм). Тем самым, в критических ситуациях можно теперь пошагово отследить, в чём проблема.  Так же напоминаю, что работа сейчас ведётся не в самом пакете «МойОфис», так как там штатно для надстроек вообще пока ничего нет, а в самой последней версии (1.40 на момент написания статьи) IDE для Lua – LuaRT. Который в свою очередь, является заточенной под Windows форк версией, от самой известной IDE - ZeroBrane Studio (иногда в дальнейшем для краткости - ZBS) с разными дополнительными плюшками, которые я надеюсь,  смогу использовать чуть позже (если смогу, то напишу об этом отдельную статью). А сам же ZeroBrane Studio имеет то преимущество, что кроссплатформенная, а значит ею можно пользоваться для аналогичной работы под Linux версией офиса, благодаря тому, что все базовые принципы, которые я здесь излагаю, должны без проблем подойти и в эту программу.

Теперь передо мной встал вопрос об отсутствии банальной в XXI веке «фишке», к которой я привык, как программист о-о-очень избалованный современными IDE, и без наличия которой программирование меня быстро приводит в ярость, а именно – автозаполнение вводимого кода. Как упоминал в первой ещё статье цикла, его нет даже во «встроенном редакторе» «МойОФис» (в кавычках, ибо на редактор это тянет слабо) для того, что там назвали макросами. То есть, хочешь писать макросы, открывай справочник по Lua API для макросов, и ищи там! Бесит, честно говоря. Но поскольку речь не о нём, то что же  мне предлагает  LuaRT (тоже самое есть и в ZeroBrane Studio, поэтому описанное далее подойдёт как родное и туда)?

Создаём автозаполнение кода в IDE LuaRT

Ещё пару хвалебных слов свободному ПО в лице LuarRT и ZBS

ZeroBrane Studio, а следовательно и LuaRT, предлагает много  интересных «фишек» для программирования. Часть из них есть и «из коробки», а часть можно догрузить из набора дополнений к самой программы ZeroBrane Studio (они опять же, почти все без проблем становятся и в LuaRT). В частности, поддерживает довольно обширный набор различных фреймворков, в которых активно используется или которые сами построены на Lua (LÖVE, Corona, Moai, Gideros, Marmalade Quick, Cocos2d-x,  и ряд других). В основном это касается отладки и такой интересной возможности, как «интерпретация на лету», но так же можно добавить и функционал по автодополнению кода при его написании, в соответствии с используемым фреймворком.  Включается такого рода либо загрузкой уже готового бесплатного расширения, либо самостоятельно - путём создания собственного файла описания требуемого API. Итак, приступим!

Перво-наперво, надо в файле настроек (user.lua) добавить следующий пункт:

api = {"baselib","myofficeapi"}
Как попасть в эту настройку, есть в первой статье цикла
Как попасть в эту настройку, есть в первой статье цикла
Ремарка по настройкам

на самом деле можно внести ещё много дополнительных пунктов в настройки (расписанных в документации), чтобы конфигурация IDE была более адекватной для работы с API «под себя», но это сильно отклонит от текущих задачи, поэтому как-нибудь в другой раз. Хотя эта тема и весьма интересная.

Данный пункт «api» -  включит при открытии IDE загрузку специализированных файлов написанных как таблицы lua, в которых и хранится информация, используемая при программировании. Далее перечисляются названия файлов без расширения «.lua», которые в свою очередь должны быть расположены в специальной подпапке ( /api/lua ) внутри папки самого IDE:

Само - собой, что на момент написания я уже более-менее заполнил созданный файл myofficeapi.lua, используя справочник по API надстроек от самих разработчиков, который лежит в свободном доступе на их сайте.

Поскольку у меня нет на момент написания статьи полного описания этого самого API, да и буду ли я его делать – большой вопрос, то я внёс туда только то, что мне нужно было для облегчения моих экспериментов, а именно - описание (и то неполное) двух базовых «классов» для работы с формами: «Forms»  и «ui». Немного подробнее о них я расскажу ниже, а пока распишу, как можно заполнять самостоятельно файлы для автодополнения.

Формат подобных файлов расписан в документации к ZeroBrane Studio:

foo = {
    type = "lib",
    description = "this does something",

    -- function/methods:
    args = "(blah,blubb)",
    returns = "(foo)",

    -- children in the class hierarchy
    childs = { -- recursive
      bar = {
        type = "function",
        description = "this does something",
        valuetype = "mytype",
      }
    }
  },

«foo» - это описываемый «класс», или модуль, или вообще любая первая в написании структура которую мы набираем, по типу foo:function(). А далее, в таблицах lua мы заполняем различные атрибуты – описатели для foo. Если имеются  дочерние структуры (переменные, классы и т.п.), то аналогичным способом внутри таблицы «childs» описываем и их. Это например: foo.bar.f(). Можно (и нужно!) описать так же тип, аргументы и возвращаемый тип, если описывается функция. Вот ниже я привожу перевод из документации:

«type»: одно из следующих значений:

  •        «keyword»: для описания ключевых слов языка, таких как «do» и «end»;

  •        «class» и «lib»: для описания групп функций, методов, значений и других классов и библиотек;

  •        «value»: для описания конкретных значений (например, констант);

  •        «function»: для описания функции, которая принимает параметры («args») и возвращает значения («returns»), возможно, определенного типа («valuetype»);

  •        «method»: описывать вызовы методов; методы описываются аналогично функциям, а также принимают аргументы, возвращаемые значения и типы значений. Единственное отличие состоит в том, что методы предлагаются только после «:» в автозаполнении, а функции предлагаются после «.» и «:».

«description»: текст, описывающий элемент указанного типа. Описание может включать новые строки, которые включаются в отображаемый текст. Длинные описания будут сокращены, чтобы они поместились на экране.

«args»: (необязательно) строка с аргументами; например, (file: file)

«returns»: (необязательная) строка с возвращаемыми значениями; например, (boolean|nil [, string, number])

«childs»: (необязательно) таблица с подэлементами текущего элемента; например, методы в классе или функции/значения в библиотеке.

«valuetype»: (необязательная) строка со значением типа, которая действует как подсказка для обработки типа в автозаполнении. Например, использование valuetype = "f" указывает, что текущий элемент возвращает значение типа f. Значение может быть сложным именем; например, foo.bar.

«inherits»: (необязательная) строка для указания цепочки наследования для текущего класса. Может включать несколько классов, разделенных пробелами.

Теперь запасаемся терпением, открываем в одном окне скачанный pdf файл с описанием API, в другом начинаем мучаться с переносом данных, согласно описанию формата. Приведу здесь кусок с кодом для Forms (соджержит в основном вспомогательные константы и типы данных для создания контролов, описанных классом ui), который у меня получился:

Листинг части файла myofficeapi.lua, описывающей класс Forms API "МойОфис"
Forms = {
    type = "class",
    description = "Класс описания параметров пользовательского интерфейса из 'Мой офис'",
    childs = {
      Size = { 
        type = "value",
        description = "Используется для указания величин ширины и высоты виджета.",
        childs={
            width={
              type = "value",
              description = "Ширина виджета",
            },
            height={
              type = "value",
              description = "Высота виджета",
              },
          },
      },
      ItemID = { 
        type = "value" ,
        description = "Идентификатор элемента виджета.",
      },
      ListItem = { 
        type = "value",
        description = "Элемент списка виджетов.",
        childs={            
            text={
              type = "value",
              valuetype = "string",
              description = "Наименование элемента.",
            },
            id={
              type = "value",
              description = "Идентификатор элемента.",
            },
            checkState={
              type = "value",
              description = "Содержит состояние флажка элемента виджета.",                    
            },
        },        
      },
      Color = { 
        type = "value",
        description = "Предназначена для настройки цвета отображения элементов виджетов.",
        childs={            
            red={
              type = "value",
              valuetype = "string",
              description = "Значение для установки интенсивности красного цвета.",
            },
            green={
              type = "value",
              description = "Значение для установки интенсивности зеленого цвета.",
            },
            blue={
              type = "value",
              description = "Значение для установки интенсивности синего цвета.",                    
            },
        },        
      },
      DialogButton = { 
        type = "value",
        description = "Константы диалоговых кнопок виджетов.",
        childs={            
            DialogButton_OK={
              type = "value",
              valuetype = "string",
              description = "Имя диалоговой кнопки, используемых при подтверждении.",
            },
            DialogButton_Done={
              type = "value",
              valuetype = "string",
              description = "Имя диалоговой кнопки, используемых при подтверждении.",
            },
            DialogButton_Yes={
              type = "value",
              valuetype = "string",
              description = "Имя диалоговой кнопки, используемых при подтверждении.",
            },
            DialogButton_Retry={
              type = "value",
              valuetype = "string",
              description = "Имя диалоговой кнопки, используемых при подтверждении.",
            },
            DialogButton_Ignore={
              type = "value",
              description = "Имя диалоговой кнопки, используемых при отмене.",
            },
            DialogButton_Cancel={
              type = "value",
              description = "Имя диалоговой кнопки, используемых при отмене.",
            },
            DialogButton_No={
              type = "value",
              description = "Имя диалоговой кнопки, используемых при отмене.",
            },
            DialogButton_Close={
              type = "value",
              description = "Имя диалоговой кнопки, используемых при отмене.",
            },
        },        
      },
      DialogButtonRole = { 
        type = "value",
        description = "Содержит константы ролей диалоговых кнопок виджетов.",
        childs={            
            DialogButtonRole_Accept={
              type = "value",
              valuetype = "string",
              description = "Подтверждение при нажатии на кнопку в диалоговом окне, например, кнопка OK."
            },
            DialogButtonRole_Reject={
              type = "value",
              description = "Отмена при нажатии на кнопку в диалоговом окне, например, кнопка Cancel.",
            },            
        },        
      },
      CheckState_Checked={
        type = "value",
        description = "Флажок элемента установлен.",              
      },
      CheckState_Unchecked={
        type = "value",
        description = "Флажок элемента установлен.",              
      },              
      DialogCode_Accepted={
        type = "value",
        description = "Пользователь нажал кнопку 'ОК'",              
      },
      DialogCode_Rejected={
        type = "value",
        description = "Пользователь нажал кнопку 'Отмена'",              
      },      
      Alignment_TopLeft={
        type = "value",
        description = "Выравнивание по верхнему левому углу",              
      },
      Alignment_TopCenter={
        type = "value",
        description = "ВВыравнивание сверху по центру",              
      },
      Alignment_TopRight={
        type = "value",
        description = "Выравнивание по верхнему правому углу",              
      },
      Alignment_MiddleLeft={
        type = "value",
        description = "Выравнивание по левой средней части",              
      },
      Alignment_MiddleCenter={
        type = "value",
        description = "Выравнивание по центру средней части",              
      },
      Alignment_MiddleRight={
        type = "value",
        description = "Выравнивание по правой средней части",              
      },
      Alignment_BottomLeft={
        type = "value",
        description = "Выравнивание по нижнему левому углу",              
      },
      Alignment_BottomCenter={
        type = "value",
        description = "Выравнивание снизу по центру",              
      },
      Alignment_BottomRight={
        type = "value",
        description = "Выравнивание по нижнему правому углу",              
      },        
      ConstListItems = { 
        type = "value",
        description = "Используется для доступа в режиме только для чтения к элементам виджетов",
      },      
      addItem = {
        type = "function",
        description = [[Добавляет элемент списка с заданным названием, идентификатором и состоянием в
конец списка.]],
        args = "(text : string[, id : Forms.ItemID, checkState : Forms.Check_State])",   
        returns = "()", 
      },
      getCount = {
        type = "function",
        args = "()",
        description = "Возвращает количество элементов таблицы.",
        valuetype = "integer",                
      },
      getItem = {
        type = "function",
        description = "Возвращает элемент списка по индексу.",
        args = "(index: integer)",
        valuetype = "Forms.ListIem",                
      },
    },
  }

Если всё прошло пучком (а мне пришлось чтобы убедиться, после каждого нового внесённого объекта проверять, что всё работает, через сохранение файла и перезагрузку всего IDE), то теперь при наборе в LuaRT (или в ZBS) появляются в автозаполнение внесённые данные:

Автозаполнение после внесения описания API класса Forms
Автозаполнение после внесения описания API класса Forms
При автозаполнении доступны и описанные внутренние структуры класса
При автозаполнении доступны и описанные внутренние структуры класса

И больше того, как и положено во "взрослой" IDE, если мы сохраняем вводимое значение как переменную, то «тип» запоминается, и автодополнение корректно работает и с переменной:

Автозаполнение к инициированной переменной (красное подчеркивание - моё, для привлечение внимания!)
Автозаполнение к инициированной переменной (красное подчеркивание - моё, для привлечение внимания!)

В общем: «то что доктор прописал»!
Так можно по идее заполнить всё API «МойОфис», но как говорится, мне за это никто не платит, потому и как-нибудь  «без меня». Главное что возможности для этого есть! Теперь, давайте наконец начнём уже ковыряться в возможностях надстроек, в плане построения на них хоть какого то GUI!

Что нам предлагают в надстройках для создания GUI?

Итак, открываем справочный файл по надстройкам от разработчиков, и смотрим что у нас есть для создания интерактивного взаимодействия между программой «МойОфис»  (и увы и ах, но и это  доступно только для текстового и табличного процессора) и пользователем, через надстройки (раздел 2.9 справочника):

Пользовательский интерфейс в надстройках

Для описания пользовательского интерфейса в надстройках редакторов МойОфис разработчик использует виджеты (widgets) и компоновки (layouts).

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

Компоновки предоставляют инструмент для автоматического размещения виджетов внутри диалогового окна.

Для создания пользовательского интерфейса надстроек доступны следующие виджеты:

Label (Надпись ui:Label) – элемент для отображения неизменяемого текста, например,

пояснительная надпись для поля ввода;

Button (Кнопка ui:Button) – элемент для отображения кнопки, требующей нажатия;

CheckBox (Флажок ui:CheckBox) – элемент для отображения флажка;

RadioButton (Радио кнопка ui:RadioButton) – элемент для отображения переключателя;

GroupBox (Рамка группы ui:GroupBox) – элемент для отображения группы элементов Радиокнопка;

ListBox (Список элементов ui:ListBox) – элемент для отображения окна списка;

ComboBox (Поле с выпадающим списком ui:ComboBox) – элемент для отображения поля с выпадающим списком;

TextBox (Текстовое поле ui:TextBox) – элемент для ввода небольшого объема текста.

 

Для размещения виджетов в пространстве диалогового окна используются

следующие виды компоновок:

Row (ui:Row )– располагает виджеты по горизонтали слева направо по ширине

диалогового окна;

Column (ui:Column) – располагает виджеты по вертикали сверху вниз по высоте диалогового окна;

Spacer(ui:Spacer) – используется для выравнивания позиции виджета относительно ширины или высоты диалогового окна.

Так же имеются ещё и возможности вызова встроенных диалоговых окон для задания параметров печати, самой печати, поиска папок и файлов (но опять, не напрямую как контрол, а через нажатия скажем на кнопку). Кроме того, можно в некоторых контролах менять цвет текста и его расположение внутри (надпись и кнопка). Можно задать размеры контрола, и его доступность. Как выяснилось случайно, есть и недокументированная почему-то возможность сделать контролы невидимыми. Вот навскидку наверное и всё, что нам разрешили на текущий момент разработчкики!

Нет крайне необходимых для визуального разделения фреймов (Frame в терминах VBA). Нет разбиения групп контролов по отельным страницам (MulipageControl и Page). Нельзя поменять шрифт надписей, его размер и начертание. Нельзя визуализировать и изменить стиль границ контролов. Короче, почти ничего нельзя! Почему? Да – хз! Честно говоря, я когда первый раз ознакомился, был мягко говоря удивлён, от скудости возможностей предоставленных разработчиками "МойОФис" программистам. Тем не менее, «на безрыбье и рак – рыба», посему кое-то я вам смогу показать, в плане изготовления из имеющегося г..на и палок набора, чего то более-менее похожего хотя бы по функционалу, на формы в офисе от майкрософта. Но, чудес не ждите! Так как без подключения сторонних модулей для GUI в Lua «МойОфис», ничего не выйдет, пока не расщедрятся разработчкики! А заниматься этим мне как то пока без особого смысла.

Итак, посмотрим как выглядят контролы на форме. Для этого внесём в /cmd/forms.lua (или в /forms/init.lua – почему так см. предыдущую статью) следующие изменения (листинг я скрою под спойлер)

Листинг файла forms.lua (или init.lua)
Frm={}

--Контрол надписи

Frm.labelCntl=ui:Label{
  Text="Label",
  Color=Forms.Color(255,0,0), --установить красный цвет
  Size=Forms.Size(200,30), -- установить размер (для позиционирования на форме)
  Alignment=Forms.Alignment_MiddleCenter, --настройка положения надписи по отношению к границам контрола
}

--Контрол ввоода текста
Frm.textBoxCntrl = ui:TextBox{
  Text="TextBox",
  Size=Forms.Size(200,30),  
}


Frm.btnCntrl = ui:Button{
  Title="Button", 
  Size=Forms.Size(100,20),
}

--Перечисления необходимое для заполнения обычного и выпадающего списка
Frm.itemsFruit = ui:ListItems {
{ text = "Арбуз",
	id = 0,
	checkState = Forms.CheckState_Unchecked
},
{ text = "Груша",
	id = 1,
	checkState = Forms.CheckState_Unchecked
},
{ text = "Слива",
	id = 2,
	checkState = Forms.CheckState_Unchecked
	}
}

--Контрол выпадающего списка
Frm.fruitsComboCntl = ui:ComboBox {
	Name = "ComboFruits",
	Items = Frm.itemsFruit, --используемое перечисление контролов
  Size=Forms.Size(200,50),
	OnCurrentItemChanged = function(id) -- обработка события выбора конкретной позиции
		msg = "Выбран элемент " .. id
		EditorAPI.messageBox(msg)
	end
}

--Контрол списка
Frm.fruitsListCntl=ui:ListBox{
  Name = "ListBox",
	Items = Frm.itemsFruit,
  Size=Forms.Size(200,100),
	OnCurrentItemChanged = function(id)
		msg = "Выбран элемент " .. id
		EditorAPI.messageBox(msg)
	end
}


--Контрол флажка
Frm.cbPrintBothSidesCntl = ui:CheckBox {  
  Title = "Печать на обеих сторонах листа"  
}

--Контрол радиокнопки 1
Frm.printFromPrintDialogRadioButton = ui:RadioButton {
  Title = "Использовать окно Печати" 
}

--Контрол радиокнопки 2
Frm.printFromPrintDocumentRadioButton = ui:RadioButton {
  Title = "Использовать окно Документа"  
}

--Контрол группировки радиокнопок 1 и 2 
Frm.groupBoxCntl= ui:GroupBox {
  ui:Column {
    Frm.printFromPrintDialogRadioButton,
    Frm.printFromPrintDocumentRadioButton
  }
}

--Специальный объект для кнопок внизу формы используемых обычно для принятия или отклонения результатов работы
Frm.dialogButtons = ui:DialogButtons{}
Frm.dialogButtons:addButton("OK", Forms.DialogButtonRole_Accept)
Frm.dialogButtons:addButton("Cancel", Forms.DialogButtonRole_Reject)

--Непосредственно сама форма
Frm.dlgBegin = ui:Dialog {
	Title = "Демонстрация контролов",
	Size = Forms.Size(600,300),
	Buttons = Frm.dialogButtons, --Кнопки Ок и Cancel внизу формы
	--описываем "геометрию" расположения контролов на форме "сверху-вниз" и "слева-направо"
  ui:Column {		--зададим всё в одну колонну 	
    ui:Row {  --первая"строка"    
      ui:Row {Frm.labelCntl},
      ui:Spacer{}, --разедлитель, чтобы контролы не "слипались краями"
      ui:Row {Frm.textBoxCntrl},
      ui:Spacer{},
    },    
    ui:Row {--вторая "строка"
      ui:Spacer{},
      ui:Column{Frm.fruitsComboCntl},      
      ui:Column{Frm.fruitsListCntl},
      ui:Spacer{},
    }, 
    --остальные контролы расположены по-строчно
    ui:Row {Frm.groupBoxCntl},
    ui:Row {Frm.cbPrintBothSidesCntl},
    ui:Row {Frm.btnCntrl},            
  },
}

--Обработчик нажатия на кнопки (добавленные как Frm.Buttons), обычно завершающие работу формы
Frm.dlgBegin:setOnDone(function(ret)
	if ret == 1  then		
		-- Ok pressed
	else
		-- Cancel pressed
	end
end
)

return Frm

Форма надстройки, во всеми доступными контролами
Форма надстройки, во всеми доступными контролами

Я намеренно не стал нагружать этот пример обработчиками событий для каждого контрола, так как они для цели сегодняшнего разбора не особо важны. Части касающиеся расположения я немного прокомментировал, но более подробно «кухню всего этого», и главное, каким образом можно сделать в рамках одной надстройки что-то более-менее сложное, в плане наполнения формы, я покажу в следующей части. Она скорее всего будет заключительной для вводного курса. А на сегодня – всё!

Источник: https://habr.com/ru/articles/739508/


Интересные статьи

Интересные статьи

Привет, Хабр! Меня зовут Станислав Игнатьев, я начальник отдела дизайна продуктов сервисов X5 Group. Мы проектируем интерфейсы для внешних и внутренних сервисов, например: подписка «Пакет», приложение...
 Все персонажи и события являются вымышленными. Любое совпадение с реально живущими или когда-либо жившими людьми случайно.  - Как продать то, что стоит дорого и неп...
Пришло время вернуться к теме, отложенной из-за большого количества работы.Напомню, в первой части был рассказ о дешифровке древнейшей греческой письменности – Линейного письма В, исчезнувшего вскоре ...
Какие есть причины переходить на новые версии Java? Кто-то это сделает из-за новых языковых возможностей вроде выражений switch, блоков текста или записей. Кому-то понадобятся новые интер...
Продолжаем «изобретать» домашний медиацентр с помощью Kubuntu и KODI.Сегодняшняя публикация будет совсем короткая, но надеюсь многим полезная. Особенно тем, кто не имеет ...