среда, 29 июля 2015 г.

RegExp в Presto

Opera в своё время прославилась тем, что не поддерживала compile() у регулярных выражений. Дело было в движке Presto (которые почему-то не желает их копилировать).

И вот случилось - Opera переехала на Blink. Теперь compile() поддерживается.... но не в Opera Mini, которая как была на Presto, там так и остаётся.

понедельник, 1 июня 2015 г.

Показать в JavaScript alert без остановки таймера

Вообще, использовать стандартный JavaScript alert даже для отладки - плохая идея. Для сообщений есть отличные окошки из Bootstrap JS или jQueryUI, для отладочной информации - console.log.

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

В браузерах, основанных на WebKit (Chrome, Safari, JavaFX WebEngine) открытый alert считается чрезвычайным событием и останавливает все таймеры своей нити. Поэтому показывать его надо в отдельной нити, вот так:

function showMessage(text){
  setTimeout(function() { alert(text); }, 1);
}

суббота, 30 мая 2015 г.

Личность в контексте истории

Раз уж меняю формат блога - напишу малость не о работе.

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

Мало написать о событиях - пусть мне покажут портреты тех, кто принимал в них участие. В конце концов, древнейший дошедший до нас текст - это биография Гильгамеша, история его взросления, приключений и подвигов. Биография влияет сильнее истории - Густав Адольф и Карл XII вдохновлялись не Геродотом, а историей Александра Македонского.

Каждый крупный историк рисует такие портреты по-своему, а кое-кто и вовсе их рисовать не умел. К примеру, заслуженно ужасными можно назвать портреты из Льва Гумилёва. Когда он описывает географию или процессы, которыми явно движет любимая им сила вещей, то с ним трудно не согласиться. Но стоит ему попытаться набросать портрет, как получается примерно вот это:

[...] Люций Корнелий Сулла, римский патриций, имел и нобиль, имел дом в Риме, виллы в его окрестностях и много рабов и клиентов. Подобно Александру, он не испытывал недостатка ни в яствах, ни в развлечениях. Что же толкнуло его в войско Мария, которого он презирал и ненавидел? И ведь он не ограничился службой штабного офицера, он участвовал в боях и, рискуя жизнью, схватил Югурту, чтобы привезти его в Рим и обречь на голодную смерть в Мамертинской тюрьме. За все эти подвига он получил только одну награду: шатаясь по форуму и болтая с приятелями, он мог называть Мария бездарным болваном, а себя героем. Этому верили многие, но не все; тогда Сулла снова полез в драку, выдержал поединок с вождем варваров, вторгшихся в Италию, убил его и... стал хвастаться еще больше. Но и этого ему показалось мало. Мария он, допустим, превзошел, но оставалась память об Александре. Сулла решил покорить Восток и прославить себя больше македонского царя. Тут ему сказали: "Хватит! Дай поработать и другим!" Казалось бы, Сулла должен был быть доволен: его заслуги перед Римской республикой признаны, дом - полная чаша, все кругом уважают и восхищаются - живи да радуйся! Но Сулла поступил иначе: возмутил легионы, взял приступом родной город, причем шел на баррикады без шлема, чтобы вдохновите своих соратников, и добился, чтобы его послали на очередную нелегкую войну. Что его толкало? Очевидно, стремления к выгоде не было. Но, с нашей точки зрения, внутренний нажим пассионарности был сильнее инстинкта самосохранения, и уважения к законам, воспитанного в нем культурой и обычаем. Дальнейшее - просто развитие логики событий, то, что во времена А. С. Пушкина называлось "силою вещей" (хороший забытый термин). Это уже относится полиостью к исторической науке, которая подкрепляет этнологию. Марий в 87 г. до н.э. выступил против Суллы с войском из ветеранов и рабов, которым была обещана свобода. Его поддержал консул Цинна, привлекший на сторону популяров-италиков, т.е. угнетенные этносы. Взяв Рим, Марий приказал самому гуманному из своих полководцев перебить воинов из рабов, ибо опора на них его компрометировала. И 4 тыс. человек были зарезаны во время сна своими боевыми товарищами. Расправа сия показала, что популяры, при всей их демократической декламации, мало отличались от своих противников - оптиматов.
Но все же отличие было: Сулла тоже мобилизовал в свое войско 10 тыс. рабов, но после победы наградил их земельными участками и римским гражданством. Различие между Марием и Суллой больше определяется личными качествами, нежели программами партий. При этом, в отличие от Александра, Сулла не был честолюбив и горд, ибо сам отказался от власти, как только почувствовал себя удовлетворенным. [...]

Сложно поверить, что эта карикатура написана всерьёз. И, пожалуй, только случайностью можно объяснить,почему въедливый Гумилёв повторяет легенду о том, что Сулла отказался от власти, потому что ему "надоело". Все биографы диктатора отмечают, что в конце жизни он страдал мучительным заболеванием кожи, которое и свело его в могилу, и даже по данным Плутарха легко вычислить, что между отставкой и смертью Суллы прошло не больше года (он подал в отставку в 79 г. до н.э., а умер в 78). Не проще ли предположить, что болезнь зашла так далеко, что Сулла чисто физически не мог править?

К тому же, диктатор был очень суеверен. Плутарх пишет о его приверженности к азиатским культам Афродиты (Астарты?), и о том, что сразу после отставки Сулла начал задавать пиры и заниматься благотворительностью. Почему бы не предположть, что он воспринял свою болезнь как кару богов и пытался искупить вину и получить прощение?

Парадоксально, но видимо Гумилёв просто не понимал людей, которых вела страсть или долг. Зато раздолбаи и самодуры вроде Ян-ди или хладнокровные властолюбцы вроде Цао Цао у него как живые.

Чаще всего мне встречались два подхода к анализу личности.

Первый - историко-лингвистический, который тянется из филолоческих штудий антиковедов XIX столетия. Он сходится к тому, что историк читает источники в оригинале а потом пытается максимально точно изложить, какие события там описаны.

Второй - психологический, который вырос из практики психологов и психотерапевтов, в свою очередь выросших из психиатрии (т.е., науки об уходе за душевнобольными). Соответственно, действия исторических деятелей (или современных) объясняют как если бы они находились в пограничных состояниях сознания.

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

ВНЕШНИЙ ОТДЕЛ

Тетра истории
1. Большая история. Какие обстоятельства кипели вокруг события, в каком состоянии была Большая история (по Хобсбауму) в том регионе? Фалес, Пифагор и Демокрит жили не в абстрактном "давно", а как раз в эпоху расцвета Персидской Империи, крепко связавшей разнородные страны Благодатного Полумесяца, - так что обеспеченный человек, жаждавший мудрости, вполне мог за вполне разумное время обучиться египтской геометрии, вавилонской астрономии и финикийской арифметике и учёту.

2. История в пространстве (география).

3. История во времени (эпоха)

4. Суперлокальная история (время рождения и деятельности).

Тетра закона

5. Адат. Обычаи того народа, к которым принадлежал человек.

6. Шариат. Религиозные и моральные нормы, сакральное.

7. Закон (УК, сословные, поколенческие установления).

8. Семья (родители, этническая принадлежность и т.п.). Быть из Назореи - не то же самое, что быть из Иерусалима.

ВНУТРЕННИЙ ОТДЕЛ

Тетра формирования

9. Врождённые качества (болезни, черты характера)

10. Первая инициация

11. Вторая инициация

12. Жизненный путь (как человек дошёл до жизни такой).

Тетра жизни

13. Социальный статус

14. Внешние обстоятельства вокруг события

15. Личные обстоятельства вокруг события (финансовые, семейные, друзья)

16. Физиология (например, смертельно больному человеку намного легче пойти на самоубийство)

среда, 27 мая 2015 г.

Перефразируя классика

Если верить глазам, то русский Интернет населяет несколько миллионов суровых программистов, знающих, почем килобайт оперативки, стреляных из крупнокалиберного дебаггера, непроводимых на мякине, неподъемных на понт, съевших весь запас собачатины обеих Корей на большинстве математических дисциплин, при ходьбе гулко звенящих тестикулами -- и человек пять мирных жителей, считая с моей Скромной Персоной.

Суровые мужчины русского Интернета, знающие толк в Разработке и Сопровождении, объясняют друг другу, как именно написать на Erlang и так, чтобы всё многопоточно. Они смеются над неправильными IDE друг друга, уличают друг друга в незнании Closure, объясняют, почему в 2020-х в продакшне будет непротолнуться от LISP-а и в чем преимущество чисто функциональных языков перед смешаными.

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

Я не исключаю также, что это они, в совсем уж свободное от Разработки и Сопровождения  время, обмениваются Бытовой Мудростью. То есть публично просят у Интернета помощи в борьбе с Суровой, Мужской Скукой, из-за которой им в последнее время, после шестой настройки Gentoo, даже не захотелось больше ничего делать; советуют выбирать Суровый, Мужской vi для более успешного написания Hello World на Haskell; весьма лично ругают Страуструпа, Джаву, ООП, Фабрики и другие абстрактные величины.

Проблема лишь в одном.

Если вы посмотрите на код и прочие результаты работы любого из этих Суровых, Циничных Программистов --

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

Космос гарантирует.

понедельник, 18 мая 2015 г.

SHA256 в Python

SHA256 в Python бывает и стандартный (из hashlib), и сторонний (из PyCrypto). Интересно, что при неосторожном использовании они дают разные результаты.

Во-первых, SHA256.new() из PyCrypto сразу после создания имеет digest. Это SHA от пустой строки - широко известное 0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.

Во-вторых, каждый update накладывается в нём на предыдущий, поэтому если digest используется на следующей итерации, то SHA256.new() надо пересоздавать. То есть аналог

h = hashlib.sha256(data + h).digest()

Это

hsh_exec = SHA256.new()
hsh_exec.update(data + hsh)
h = hsh_exec.digest()

среда, 6 мая 2015 г.

Тайна неуязвимости Человека с чемоданчиком

Раскрыт секрет неуязвимости Человека с чемоданчиком (он же G-Man) из первых версий Half-Life. Если заглянуть в исходники, становится ясно, почему в него высаживали целую обойму, а он всё равно живой:

SetImpactEnergyScale( 0.0f ); // no physics damage on the gman

понедельник, 4 мая 2015 г.

Про offset в методу get у ByteBuffer

Что делает offset в методе get() у ByteBuffer?

Даже help не вновит ясности. Вроде как смещение - но при попытке сместить и прочитать начинают сыпаться ошибки переполнения буфера.

Справка написана не особо понятно, но, к счастью, есть исходник реализации. И, заглянув в него, мы узнаём, что это.... индекс, с которого надо начить запись в массив!!


...
int end = offset + buffer;
for(int i = offset; i < end; i++)
  dst[i] = get();
...

Конечно, с точки зрения проектирования это настоящий кошмар. Потому что:

  1. Даже со справкой не очень понятно, как использовать.
  2. Реализована совершенно редкая фича вместо фичи нужной ("прочитать начиная со смещения" смотрелось бы тут куда уместней).

среда, 29 апреля 2015 г.

receiving GET params of url - 4

Оказывается, все параметры, передаваемые в URL get-запросом. лежат в свойстве location.search. Как-то так: "?foo=boo".

А значит, старый код можно переписать ещё раз:

$.extend({
    getUrlStr : function() {
        //used for unit test
        return location.search.substr(1);
    },
    getUrlVars : function(){
        var vars = {},
            hashes = this.getUrlStr();
        if(!hashes)
            return {};
        hashes = hashes.split('&');
 for(var i = 0, len = hashes.length; i < len; i++) {
  var hash = hashes[i].split('=');
  vars[hash[0]] = hash[1];
 }
 return vars;
     },
     urlVars : null,
     getUrlVar : function(key) {
 if(!key) return null;
 if(!this.urlVars) this.urlVars = this.getUrlVars();
 return (this.urlVars[key] !== undefined) ? this.urlVars[key] : null;
     },
});

четверг, 23 апреля 2015 г.

Д. Босуэлл, Т. Фаучер - Читаемый код или программирование как искусство

Небольшая книжка, в которой много полезных мелочей.

Например, более точные варианты названий для типичных функций:

send - deliver; dispatch; announce; distribute; route
find - search; extract; locate;recover
start - launch; create; begin; open
make - create; setup; build; generate; compose; new

Именованные интераторы для циклов (ui лучше, чем просто i).

Дополнительные постфиксы - size_mb, html_utf луче, чем size и html.

first/last - это включающие, а begin/end - исключающие границы.

Пометы в комментах:

TODO - задумано, но не сделано.
FIXME - известно, что есть проблема
HACK - неэлегантное решение проблемы
XXX - серьёзная проблема

Уже второе недоумение насчёт цикла do-while (которые непонятно, зачем нужен).

Разоблачён миф о том, что из функции должен быть только один выход. Он тянется из чистого C, где нередко забывали вычистить память перед выходом. Но c тех пор появились исключения, деструкторы и очистка мусора. А в C простительно писать goto cleanup;

Вообще, если удаётся уменьшить количество отступов, - это хорошо.

Запутывают код: многопоточность, обработчики сигналов/прерываний, исключения, указатели на функции и анонимные функции, виртуальные методы.

В JavaScript и Python переменная, объявленная внутри цикла, будет видна и снаружи. Вообще, область видимости переменных надо сужать. А самые лучшие переменные - константы, поэтому лучше всего менять их один раз.

Переменные объявлять где используются, а не в начале блока. Объявлять всё в начале блока - дурная традиция из Pascal и раннего C (хотя в C99 уже разрешили объявлять где угодно).

Также есть приятные куски кода:

Макрос для C++, чтобы убрать warning для неопределённого конструктора копирования или оператора присваивания.

#define DISALLOW_COPY_AND_ASSIGN(ClassName) \
  ClassName(const ClassName&); \
  void operator=(const ClassName&);

И потом писать:
class ClassName {
  private:
     DISALLOW_COPY_AND_ASSIGN(ClassName);
     ...
  public:
     ...
};

вторник, 21 апреля 2015 г.

java: javacTask: source release 8 requires target release 1.8 в IntelliJ IDEA

Эта ошибка появляется внезапно и сразу же доводит до бешенства. Запускаешь компиляцию. а Idea в ответ:

 java: javacTask: source release 8 requires target release 1.8 в IntelliJ IDEA\

Чтобы поправить, отправляемся в .idea/compiler.xml, и выставляем в разделе bytecodeTargetLevel для этого модуля target=1.8

понедельник, 13 апреля 2015 г.

Параметры по умолчанию в JavaScript

Загружаем необязательные опции функции из указанных в default_options.

Для методов вида doSomething(main_data, options), где options не обязательны.


var JsOptionsHelper = (function(){
  function doLoadOptions(default_options, options){
    var result_options = {};
    if(!options) {
      for(var opt_key in default_options)
        result_options[opt_key] = default_options[opt_key];
    } else {
      for(var opt_key in default_options)
        result_options[opt_key] = (options[opt_key] !== undefined) ? options[opt_key] : default_options[opt_key];
    }
    return result_options;
  }
  return {
    loadOptions : doLoadOptions
  };
})();


среда, 8 апреля 2015 г.

Javadoc - наследование комментариев

В C# классы наследуют комментарии из интерфейса.

В JavaDoc всё сложнее - нужно писать над унаследованным свойством:

/**
 * {@inheritDoc}
*/

понедельник, 30 марта 2015 г.

JavaScript charset в браузере

Согласно стандарту HTML5, стандартная кодировка страницы - UTF-8. А вот с JavaScript всё сложнее.

Пусть у нас есть какой-то JSON. Напишем функцию, которая его возвращает:

function getOutlineJson() {
  return {
      "title" : "Элемент1"
  };
}

Сохраняем в отдельный файл, привязываем через <script>. Пытаемся вывести в консоль:


document.addEventListener('DOMContentLoaded', function() {
  console.log(getOutlineJson());
});

В консоли будет JSON с полями на неведомом языке. Хотя, как подтверждает view.encoding() в консоли Sublime, все файлы - в UTF-8.

А если добавить вывод чего-то кириллического прямо на экран?

document.addEventListener('DOMContentLoaded', function() {
  console.log("проверка консоли");
  console.log(getOutlineJson());
});

Внезапно, кодировка починилась. Видимо, кодировка выставляется по первому вызову.

Лечится мета-тегом:

<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />

Проверено в Firefox и Chrome.

"case folding collision between" в Mercurial под Windows

У Mercurial под Windows есть неприятная особенность - если переименовать файл проекта, изменив в нём только регистр одного из символов, то рухнет всё. Mercurial, как дитя Unix-а, считает, что разные регистры - это разные файлы, а вот для Windows это параллельно. В результате - ошибка "case folding collision between" и полный обвал репозитория: даже если удалить зловредный файл, чтобы сделать push, надо сначала сделать pull, а pull не проходит из-за конфликта имён.

Можно посмотреть, что советуют на официальном сайте. Но эти советы не всегда помогают (мне не помогли).

В конце концов, нашёлся хакерский вариант. Только не забудьте сделать резервную копию.

hg pull
# ревизия из репозитория, можно посмотреть в Tortoise
hg debugsetparents <bad revision>
hg debugrebuildstate
hg rm -A -f WeatherTimeMachine-Info.plist
hg ci -m "fixed collision-folding issue"
hg debugsetparents tip
hg debugrebuildstate
hg ci -m "fixed head"
hg push

среда, 25 марта 2015 г.

eval в JavaScript

eval в JavaScript с отложенным выполнением:

var JsEvaluator = (function(){
  var errors = {
    WRONG_SYNTAX : "Синтаксическая ошибка в выражении \"%SOURCE%\"",
    NON_SYNTAX : "Ошибка %ERROR_TITLE% при разборе выражения \"%SOURCE%\""
  };

  function null_func() { return null; }

  return {
    checkedEval : function(str_to_eval){
      if(!str_to_eval)
        return null_func;
      var trimmed_str = str_to_eval.trim();
      if(!trimmed_str)
        return null_func;
      try {
        var result = eval(trimmed_str);
        if(result === undefined)
          return null_func;
      } catch(e) {
        var error_text = (!(e instanceof SyntaxError)) ?
                          errors.WRONG_SYNTAX.replace(/%SOURCE%/, trimmed_str) :
                          errors.NON_SYNTAX.replace(/%ERROR_TITLE%/, e.name).replace(/%SOURCE%/, trimmed_str);
        console.error(error_text);
        return null_func;
      }
      return function() { return eval(trimmed_str); };
    }
  };
})();

Возвращать именно функцию может быть полезно, если выражение в eval завязано на какую-то внешнюю переменную. Например, в str_to_eval у нас условие: "Param1 > 10". Понятно, что его надо выполнить в момент проверки, а не инициализации. Поэтому выполнение в примере отложено.

Для eval выполняется правило последней строки, пришедшее в Ruby и R из Fortran - eval возвращает значение, которое вернула последняя выполненная строка переданного ему выражения. Это может быть в т.ч. строка с числом:
eval("1;2;4")
> 4

или даже со строковой константой:

eval("'boo'")
> "boo"

Как вариант, можно отказаться от ошибок и понимать "неправильный" JavaScript как строковую константу:

var JsEvaluator = (function(){
  function null_func() { return null; }
  return {
    checkedEval : function(str_to_eval){
      if(!str_to_eval)
        return null_func;
      var trimmed_str = str_to_eval.trim();
      if(!trimmed_str)
        return null_func;
      try {
        var result = eval(trimmed_str);
        if(result === undefined)
          return null_func;
      } catch(e) {
        return function() { return str_to_eval; };
      }
      return function() { return eval(trimmed_str); };
    }
  };
})();

Подключаем JSON к Java-проекту

Очень долго JSON в Java читали и генерировали сторонними библиотеками вроде Jackson. Но прогресс не стоит на месте и начиная с Java 7 вроде как появилась стандартная сборка javax.json.

Но, к сожалению, если вставить банальное import javax.json.*, то работать ничего не будет. Java скажет, что не знает этого пространства имён.

Пользователи maven (вроде меня), идёт в Гугл и читают там, что надо подключить зависимость в pom.xml. Вот такую:

<dependency>
   <groupId>org.glassfish</groupId>
   <artifactId>javax.json</artifactId>
   <version>1.0.4</version>
</dependency>

С ней проект действительно компилируется, запускается... и торжественно вылетает с ошибкой Provider org.glassfish.json.JsonProviderImpl not found.

Правильное Dependency выглядит так:

<dependency>
   <groupId>org.glassfish</groupId>
   <artifactId>javax.json</artifactId>
   <version>1.0.4</version>
</dependency>

И теперь всё заработает.

И т.к. namespace - часть Glassfish, то чтобы он заработал в проекте без Maven, надо скачать и добавить к проекту JSON Processing RI jar.

вторник, 24 марта 2015 г.

Разница между isInstance() и instanceof в Java

В чём разница между instanceof из class.isInstance(item) в Java? На самом деле разницы почти нет, просто instanceof требует, чтобы класс, с которым сравнивают, был известен ещё на этапе компиляции.

А вот isInstance можно смело вызывать и от экземпляра: item1.getClass().isInstance(item2)

воскресенье, 22 марта 2015 г.

JavaFX Color в CSS

Внешний вид компонент JavaFX настраивается в CSS. Соответственно, должен быть какой-то конвертер стандартного javafx.scene.paint.Color в CSS-friendly формат.

Возможно, он и правда есть - но я его не нашёл. К тому же, внутри класс Color устроен немного по-другому: насыщенность цвета в свойствах getRed, getBlue и getGreen задаётся double-числом от 0.0 до 1.0.

И вот что получилось:

public static String colorToJson(Color color){
   return String.format("#%02X%02X%02X",
                 (int)(color.getRed() * 0xFF),
                 (int)(color.getBlue() * 0xFF),
                 (int)(color.getGreen() * 0xFF));
}

На выходе из COLOR.Red получается правильный #FF0000.

Выглядит симпатично и немного напоминает чистый C :).Правда, прозрачность потерялась.

А если заглянуть в стандарт CSS, то выясняется: уже давно можно задавать цвет как rgba(0, 0, 255, 1.0).

А значит, наш код можно переписать вот так (formatter нужен, чтобы вместо точки в десятичной дроби не завелась запятая):

public static String colorToJson(Color color){
  StringBuilder sb = new StringBuilder();
  Formatter formatter = new Formatter(sb, Locale.US);
  formatter.format("rgba(%.0f, %.0f, %.0f, %.1f)", color.getRed() * 0xFF, color.getBlue() * 0xFF, color.getGreen() * 0xFF, color.getOpacity());
  return sb.toString();
}

Callable в Java, который возвращает void

К сожалению, в Java пока не появилась аналога шаблонных Func<> и Action<> из C#. Приходится обходиться Callable, в котором указывать тип возвращаемого значения - обязательно.

А если нужно просто выполнить функцию, которая возвращает void, то пишут Callable<Void> и (для асинхронных) Future<Void>. В учебниках обычно не упоминают, что для void есть класс-обёртка.

четверг, 19 марта 2015 г.

Annotation type expected для @Entity или @Test

Иногда в ответ на @Entity у Hibernate или @Test у JUnit компилятор Java заявляет: Annotation type expected.

Это означает, что выставили не тот Import

У @Entity - вместо org.hibernate.metamodel.domain.Entry надо  java.persistance.*
У @Test - вместо junit.framework.Test надо org.junit.Test

Всплывающие подсказки в JavaFX

Накопилась куча материала (кое-что годится на полноценную статью, а что-то просто может забиться).

Буду понемногу выкладывать, чтобы не хранить на бумаге.

Итак, всплывающие подсказки в JavaFx.

В давние времена, когда приложения оставались десктопными, окошечки под Windows было удобней всего рисовать с помощью библиотеки VCL, той самой, которая жила внутри Delphi. Конечно, кто-то писал на MFC в тогда ещё доисторической Visual Studio, кто-то был вынужден поддерживать проекты на OWL, а отдельные маньяки - даже на голом WinAPI, но их мы трогать не будем. И ещё не будем трогать тех, кто писал на VCL для Unix-ов (помните Kylix? А он был!)

Итак, внутри Delphi жил VCL. Если вы писали на C++Builder, он там тоже был. И дополнял C++ своей уникальной String, а также списками, которые начинались с 1.

И почти у всех визуальных компонент были свойства ShowHint и Hint. В Hint писался текст подсказки, а ShowHint мог её отключить. А более прокачанные даже знали, что можно сделать расширенный вариант подсказки. Если написать "Нажми меня|Кнопка просит, чтобы вы её нажали", то левая часть всплывёт, а правая будет передана сообщением, которое можно перехватить и вывести, например, в Status Bar.

Но пришла новая эра, VCL ушёл в историю, а у нас теперь, к примеру, кроссплатформенный JavaFX. И никакого свойства Hint у его компонентов нет. И status bar-а среди компонентов тоже нет. Такие дела.

Что же делать?

Для подсказок всплывающих есть невидимый для SceneBuilder компонент Tooltip, который отвечает за всплывающие подсказки. Если его создать, а потом привязать через setTooltip, то при наведении курсора мы и правда увидим подсказку (на чёрном фоне, но так надо).

Но при этом свойство Tooltip (и соответствующие методы) есть только у наследников класса javafx.scene.control.Control. А все панели и прочие области наследуются от javafx.scene.layout.Region. И никаких подсказок на них всплывать не может.

К тому же, разумеется, мы по прежнему не приблизились к StatusBar-у. Придётся писать всё своими руками.

Сначала класс привязки, который хранит ссылку на элемент управления и текст, который к нему относится:

ATooltipHintItem.java
import javafx.scene.Node;

/**
 * Базовый класс для элемента подсказки. Связывает вместе компонент и
 * текст подсказки.
 *
 * Created by a.teut on 18.03.15.
 */
public abstract class ATooltipHintItem<N> {
    private N attachedNode;
    protected void setAttachedNode(N node) {
        attachedNode = node;
    }
    public N getAttachedNode() {
        return attachedNode;
    }

    private String statusBarHint;
    protected void setStatusBarHint(String hint){
        statusBarHint = hint;
    }
    public String getStatusBarHint(){
        return statusBarHint;
    }

    private ITooltipHintController tooltipHintController;
    public ITooltipHintController getTooltipHintController() {
        return tooltipHintController;
    }

    public void showStatusBarHint(){
        tooltipHintController.setStatusBarText(statusBarHint);
    }

    public ATooltipHintItem(N attachedNode, ITooltipHintController tooltipHintController, String statusBarHint) {
        this.attachedNode = attachedNode;
        this.tooltipHintController = tooltipHintController;

        if(statusBarHint != null && statusBarHint != ""){
            initStatusBar();
            this.setStatusBarHint(statusBarHint);
        }
    }

    private void initStatusBar() {
        getAttachedNode().setOnMouseEntered(observableValue -> {
            this.showStatusBarHint();
        });

        getAttachedNode().setOnMouseExited(observableValue -> {
            getTooltipHintController().setDefaultStatusBarText();
        });
    }
}

Теперь реализация для Region:

TooltipHintRegionItem.java
import javafx.scene.layout.Region;

/**
 * Реализация для компонент, унаследованных от Region. Всплывающие подсказки
 * прицепить нельзя.
 *
 * Created by a.teut on 18.03.15.
 */
public final class TooltipHintRegionItem extends ATooltipHintItem<Region>{
    public TooltipHintRegionItem(Region attachedNode, ITooltipHintController tooltipHintController, String statusBarHint) {
        super(attachedNode, tooltipHintController, statusBarHint);
    }
}

А у Control могут быть Tooltip-ы:


TooltipHintRegionItem.java

import javafx.scene.control.Control;
import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;

/**
 * Реализация для компонент, унаследованных от Control. Можно делать и всплывающие подсказки.
 *
 * Created by a.teut on 18.03.15.
 */

/**
 * Реализация для компонент, унаследованных от Control. Можно делать и всплывающие подсказки.
 *
 * Created by a.teut on 18.03.15.
 */
public final class TooltipHintControlItem extends ATooltipHintItem<Control> {
    private Tooltip tooltip;
    public Tooltip getTooltip() {
        return tooltip;
    }

    private String tooltipHint;
    public TooltipHintControlItem setTooltipHint(String hint){
        tooltipHint = hint;
        if(tooltip == null) {
            initTooltip();
        }
        tooltip.setText(hint);
        return this;
    }
    public String getTooltipHint(){
        return tooltipHint;
    }

    private Image tooltipImage;
    public TooltipHintControlItem setTooltipImage(Image image){
        tooltipImage = image;
        tooltip.setGraphic((image != null) ? new ImageView(image) : null);
        return this;
    }
    public Image getTooltipImage(){
        return tooltipImage;
    }

    public TooltipHintControlItem(Control attachedNode, ITooltipHintController tooltipHintController, String statusBarHint, String tooltipHint, Image imageHint) {
        super(attachedNode, tooltipHintController, statusBarHint);
        if(tooltipHint != null && tooltipHint != ""){
            initTooltip();
        }
        setTooltipHint(tooltipHint);

        if(imageHint == null) {
            setTooltipImage(imageHint);
        }
    }

    public TooltipHintControlItem(Control attachedNode, ITooltipHintController tooltipHintController, String statusBarHint, String tooltipHint) {
        this(attachedNode, tooltipHintController, statusBarHint, tooltipHint, null);
    }

    public TooltipHintControlItem(Control attachedNode, ITooltipHintController tooltipHintController, String statusBarHint) {
        this(attachedNode, tooltipHintController, statusBarHint, null, null);
    }

    private void initTooltip() {
        tooltip = new Tooltip();
        getAttachedNode().setTooltip(tooltip);
    }
    public Image getTooltipImage(){
        return tooltipImage;
    }

    public TooltipHintControlItem(Control attachedNode, ITooltipHintController tooltipHintController, String statusBarHint, String tooltipHint) {
        super(attachedNode, tooltipHintController, statusBarHint);
        if(tooltipHint != null && tooltipHint != ""){
            initTooltip();
        }
        setTooltipHint(tooltipHint);
    }

    public TooltipHintControlItem(Control attachedNode, ITooltipHintController tooltipHintController, String statusBarHint) {
        this(attachedNode, tooltipHintController, statusBarHint, null);
    }

    private void initTooltip() {
        tooltip = new Tooltip();
        getAttachedNode().setTooltip(tooltip);
    }
}

Теперь распишем интерфейс, который их вызывает:

ITooltipHintController.java

/**
 * Функции контроллера, которые вызывают сами элементы привязок.
 *
 * Created by a.teut on 18.03.15.
 */
public interface ITooltipHintController {
    void setStatusBarText(String text);
    String getStatusBarText();
    void setDefaultStatusBarText();
}

И, наконец, контроллер:


TooltipHintController.java

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.control.Labeled;
import javafx.scene.image.Image;
import javafx.scene.layout.Region;

import java.util.ArrayList;
import java.util.Iterator;

/**
 * Контроллер подсказок. Хранит привязки подсказок к компонентом и, если нужно,
 * всплывающие подсказки.
 *
 * Как правило, один на всё приложение.
 *
 * Created by a.teut on 18.03.15.
 */
public final class TooltipHintController implements ITooltipHintController {
    private final String DefaultStatusBarText = "";
    private final Labeled statusBarControl;
    private final ObservableList<ATooltipHintItem> tooltipHintItems;

    private boolean isStatusBarLocked = false;
    public boolean getIsStatusBarLocked() {
        return isStatusBarLocked;
    }
    public void setIsStatusBarLocked(boolean isStatusBarLocked) {
        this.isStatusBarLocked = isStatusBarLocked;
    }

    public Labeled getStatusBarControl() {
        return this.statusBarControl;
    }

    public void setStatusBarTextForce(String text) {
        statusBarControl.setText(text);
    }
    @Override
    public void setStatusBarText(String text) {
        if(!isStatusBarLocked){
            setStatusBarTextForce(text);
        }
    }
    @Override
    public String getStatusBarText() {
        return statusBarControl.getText();
    }
    @Override
    public void setDefaultStatusBarText(){
        setStatusBarTextForce(DefaultStatusBarText);
    }

    //тут есть дублирование кода, но пока ничего серьёзного
    public void addTooltipHint(Region region, String statusBarHint){
        // Tooltip нас не интересует - у регионов в JavaFX не бывает всплывающих подсказок
        ATooltipHintItem tooltipHintItem = findTooltipHint(region);
        if(tooltipHintItem == null) {
            tooltipHintItem = new TooltipHintRegionItem(region, this, statusBarHint);
            tooltipHintItems.add(tooltipHintItem);
        } else {
            TooltipHintControlItem tooltipHintControlItem = (TooltipHintControlItem)tooltipHintItem;
            if(statusBarHint != null && tooltipHintControlItem.getStatusBarHint() == null)
                tooltipHintControlItem.setStatusBarHint(statusBarHint);
        }
    }

    public void addTooltipHint(Control control, String statusBarHint){
        addTooltipHint(control, statusBarHint, null, null);
    }


    public void addTooltipHint(Control control, String statusBarHint, String tooltipHint){
        addTooltipHint(control, statusBarHint, tooltipHint, null);
    }

    public void addTooltipHint(Control control, String statusBarHint, String tooltipHint, Image image){
        ATooltipHintItem tooltipHintItem = findTooltipHint(control);
        if(tooltipHintItem == null) {
            tooltipHintItem = new TooltipHintControlItem(control, this, statusBarHint, tooltipHint, image);
            tooltipHintItems.add(tooltipHintItem);
        } else {
            TooltipHintControlItem tooltipHintControlItem = (TooltipHintControlItem)tooltipHintItem;
            if(statusBarHint != null && tooltipHintControlItem.getStatusBarHint() == null)
                tooltipHintControlItem.setStatusBarHint(statusBarHint);

            if(tooltipHint != null && tooltipHintControlItem.getTooltipHint() == null)
                tooltipHintControlItem.setTooltipHint(tooltipHint);

            if(image != null && tooltipHintControlItem.getTooltipImage() == null)
                tooltipHintControlItem.setTooltipImage(image);
        }
    }

    public void removeTooltipHint(Node control){
        ATooltipHintItem tooltipHintItem = null;
        Iterator<ATooltipHintItem> iteratorTooltipHintItems = tooltipHintItems.iterator();
        while(iteratorTooltipHintItems.hasNext()){
            tooltipHintItem = iteratorTooltipHintItems.next();
            if(tooltipHintItem.getAttachedNode() == control){
                tooltipHintItems.remove(tooltipHintItem);
                break;
            }
        }
    }

    public ATooltipHintItem findTooltipHint(Node control){
        for(ATooltipHintItem tooltipHintItem : tooltipHintItems)
            if(tooltipHintItem.getAttachedNode() == control)
                return tooltipHintItem;
        return null;
    }

    /**
     * При создании нужно привязать контроллер к компоненту, который будет
     * показывать подсказки.
     *
     * @param statusBarControl Компонент для подсказок
     */

    public TooltipHintController(Labeled statusBarControl){
        if(statusBarControl == null) {
            throw new NullPointerException("Unable to create TooltipHintController. statusBarControl can't be null.");
        }

        this.statusBarControl = statusBarControl;
        tooltipHintItems = FXCollections.observableList(new ArrayList<>());
    }

    private static TooltipHintController mainInstance;
    public static TooltipHintController getMainInstance() {
        if(mainInstance == null){
            throw new NullPointerException("Main instance of TooltipHintController isn't initialised.");
        }

        return mainInstance;
    }
    public static void setMainInstance(TooltipHintController tooltipHintController) {
        mainInstance = tooltipHintController;
    }
}

Чтобы заработало, надо инициализировать контроллер тем самым элементом, в который надо отображать подсказки:

TooltipHintController.setMainInstance(new TooltipHintController(labelStatusBar));

И привязываем к компонентам:

TooltipHintController.getMainInstance().addTooltipHint(buttonStart, "Нажми меня", "Нажми эту кнопку");
TooltipHintController.getMainInstance().addTooltipHint(paneButtons, "Здесь нажимают");


вторник, 17 марта 2015 г.

undefined в JavaScript и receiving GET params of url - 3

В чём разница между typeof boo == "undefined" и boo === undefined в JavaScript?

Они делают почти одно и то же. И у boo === undefined есть плюсы и минусы:

Плюсы

+ И в "undefined", и в undefined можно допустить опечатку. Но во втором случае браузер немедленно заругается.
+ Такое сравнение подходит и для "пустых" параметров функции, и для несуществующих свойств объектов.

Минусы

- Если переменная boo просто нигде не объявлена, то будет ошибка. А вот typeof boo == "undefined" сработает.

Аналогично boo === undefined срабатывает и || для неопределённого свойства или аргумента функции.

А это значит, что старый код можно улучшить ещё:

$.extend({
 getUrlVars : function(){
  var vars = {};
  var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
  var i = hashes.length;
  while(i--) {
   var hash = hashes[i].split('=');
   vars[hash[0]] = hash[1];
    }
  return vars;
 },
 urlVars : null,
 getUrlVar : function(key) {
  if(!key) return null;
  if(!this.urlVars) this.urlVars = this.urlVars();
  return (this.urlVars[key] !== undefined) ? this.urlVars[key] : null;
 },
 toUrlVar : function(url, params) {
  var url_val = url || window.location.href.slice(0, window.location.href.indexOf('?'));
  var dict_params = params || this.urlVars;
  if(dict_params === undefined || !dict_params)
   return url_val;
  var params_str = "";
  for(var param_key in dict_params) {
   var param = param_key + "=" + dict_params[param_key];
   params_str += (!params_str) ? param : ("&" + param);
  }
  return params_str ? (url_val + "?" + params_str) : url_val;
 }
});

вторник, 10 марта 2015 г.

FontFamily и FontName

Как получить список шрифтов в JavaFX? Ведь функций там две - Font.getFamilies() и Font.getFontNames()

И в Windows, и в Unix-ах, и на Mac-ах рисуют отдельные шрифты для основного, полужирного и курсивного начертания. У некоторых шрифтов есть только основное. Начертания для одного шрифта группируются в FontFamily, а отдельные шрифты - FontName.

четверг, 19 февраля 2015 г.

Mercurial

Очень хорошее о практике работы с системами контроля версий. И, пока не забыл, как делать и сливать ветки в mercurial из консоли.

Создаём ветку:

hg branch branch1

Список веток:

hg branches

Заливаем с hg addremove:

hg ci -Am "comment"

Переключаемся на ветку:

hg up branch1

И сливаем с основной:

# Последний фикс с addremove
hg ci -Am "* my fix"

# и теперь сливаем
hg up default
hg merge branch1
hg ci -Am "+ merge branch1"
hg up branch1
hg ci -m "+ close branch" --close-branch
hg push

Устоявшейся маркировки в комментариях нет. Традиционно применяют:

+ фиша добавлена
- фича удалена
* изменение
# изменение (вариант *)
! багфикс

четверг, 29 января 2015 г.

Округление на языке без всего

Бывший сослуживец сообщил, что для настройки чего-то в одном из продуктов JetBrains есть свой уникальный встроенный язык (это традиция такая у программистов - писать свои языки), который похож на JavaScript, но при этом никакого объекта Math в нём нет.

Пришлось написать самостоятельно код для округления val:

val = (val % 1 < 0.5) ? (val - (val % 1)) : (val - (val % 1) + 1)