среда, 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);
}

среда, 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)