private void saveColumnVisibility() {
    final boolean[] columnVisibility = new boolean[treeTableView.getColumns().size()];

    for (int i = 0; i < columnVisibility.length; i++) {
      columnVisibility[i] = treeTableView.getColumns().get(i).isVisible();
    }

    preferences.put(COLUMN_VISIBILITY, EncodeDecode.encodeBooleanArray(columnVisibility));
  }
  private void restoreColumnVisibility() {
    final String result = preferences.get(COLUMN_VISIBILITY, null);

    if (result != null) {
      boolean[] columnVisibility = EncodeDecode.decodeBooleanArray(result);

      if (columnVisibility.length == treeTableView.getColumns().size()) {
        for (int i = 0; i < columnVisibility.length; i++) {
          treeTableView.getColumns().get(i).setVisible(columnVisibility[i]);
        }
      }
    }
  }
  private void installListeners() {
    treeTableView
        .getSelectionModel()
        .selectedItemProperty()
        .addListener(
            (observable, oldValue, newValue) -> {
              if (newValue != null) {
                selectedAccountProperty.setValue(newValue.getValue());
              } else {
                selectedAccountProperty.setValue(null);
              }
            });

    for (final TreeTableColumn<?, ?> treeTableColumn : treeTableView.getColumns()) {
      treeTableColumn
          .visibleProperty()
          .addListener((observable, oldValue, newValue) -> saveColumnVisibility());
    }
  }
  private TreeTableView<ITreeNode> createTreeTableView(ProfileContainer container) {

    final List<Profile> profiles = container.getProfiles();
    final MyTreeNode rootNode = new MyTreeNode(null);
    for (Profile p : profiles) {
      final MyTreeNode profileNode = new MyTreeNode(p);
      rootNode.addChild(profileNode);

      profileNode.addChild(createTreeNodes(p.getTopLevelMethod()));
    }

    final MyTreeModel model = new MyTreeModel(rootNode, new MethodStatsHelper(container));

    final TreeItem<ITreeNode> root = model.toTreeItems();
    root.setExpanded(true);

    final TreeTableView<ITreeNode> treeTableView = new TreeTableView<>(root);

    for (int i = 0; i < model.getColumnCount(); i++) {
      final int columnNo = i;
      final TreeTableColumn<ITreeNode, String> newColumn =
          new TreeTableColumn<>(model.getColumnName(i));

      newColumn.setCellValueFactory(
          new Callback<
              TreeTableColumn.CellDataFeatures<ITreeNode, String>, ObservableValue<String>>() {

            @Override
            public ObservableValue<String> call(CellDataFeatures<ITreeNode, String> param) {
              return new ReadOnlyStringWrapper(
                  model.getValue(param.getValue().getValue(), columnNo));
            }
          });
      treeTableView.getColumns().add(newColumn);
    }

    treeTableView.setShowRoot(false);
    return treeTableView;
  }
  @SuppressWarnings("unchecked")
  private void initializeTreeTableView() {
    treeTableView.setShowRoot(false); // don't show the root
    treeTableView.setEditable(true); // required for editable columns
    treeTableView.setTableMenuButtonVisible(true);

    // force resize policy for better default appearance
    treeTableView.setColumnResizePolicy(TreeTableView.CONSTRAINED_RESIZE_POLICY);

    final TreeTableColumn<Account, String> nameColumn =
        new TreeTableColumn<>(resources.getString("Column.Account"));
    nameColumn.setCellValueFactory(
        param -> new ReadOnlyStringWrapper(param.getValue().getValue().getName()));

    final TreeTableColumn<Account, Integer> entriesColumn =
        new TreeTableColumn<>(resources.getString("Column.Entries"));
    entriesColumn.setCellValueFactory(
        param ->
            new SimpleIntegerProperty(param.getValue().getValue().getTransactionCount())
                .asObject());

    final TreeTableColumn<Account, BigDecimal> balanceColumn =
        new TreeTableColumn<>(resources.getString("Column.Balance"));
    balanceColumn.setCellValueFactory(
        param ->
            new ReadOnlyObjectWrapper<>(
                AccountBalanceDisplayManager.convertToSelectedBalanceMode(
                    param.getValue().getValue().getAccountType(),
                    param.getValue().getValue().getTreeBalance())));
    balanceColumn.setCellFactory(cell -> new AccountCommodityFormatTreeTableCell());

    final TreeTableColumn<Account, BigDecimal> reconciledBalanceColumn =
        new TreeTableColumn<>(resources.getString("Column.ReconciledBalance"));
    reconciledBalanceColumn.setCellValueFactory(
        param ->
            new ReadOnlyObjectWrapper<>(
                AccountBalanceDisplayManager.convertToSelectedBalanceMode(
                    param.getValue().getValue().getAccountType(),
                    param.getValue().getValue().getReconciledTreeBalance())));
    reconciledBalanceColumn.setCellFactory(cell -> new AccountCommodityFormatTreeTableCell());

    final TreeTableColumn<Account, String> currencyColumn =
        new TreeTableColumn<>(resources.getString("Column.Currency"));
    currencyColumn.setCellValueFactory(
        param ->
            new ReadOnlyStringWrapper(param.getValue().getValue().getCurrencyNode().getSymbol()));

    final TreeTableColumn<Account, String> typeColumn =
        new TreeTableColumn<>(resources.getString("Column.Type"));
    typeColumn.setCellValueFactory(
        param ->
            new ReadOnlyStringWrapper(param.getValue().getValue().getAccountType().toString()));

    final TreeTableColumn<Account, Integer> codeColumn =
        new TreeTableColumn<>(resources.getString("Column.Code"));
    codeColumn.setEditable(true);
    codeColumn.setCellValueFactory(
        param ->
            new SimpleIntegerProperty(param.getValue().getValue().getAccountCode()).asObject());
    codeColumn.setCellFactory(param -> new IntegerEditingTreeTableCell());
    codeColumn.setOnEditCommit(
        event -> updateAccountCode(event.getRowValue().getValue(), event.getNewValue()));

    treeTableView
        .getColumns()
        .addAll(
            nameColumn,
            codeColumn,
            entriesColumn,
            balanceColumn,
            reconciledBalanceColumn,
            currencyColumn,
            typeColumn);

    restoreColumnVisibility();

    installListeners();
  }
  protected void refreshWorklogTableViewAndResults() {

    // return early if no data present or still the same data
    // as the last time this tab was active
    Optional<FetchTimereportContext> reportContextOptional = this.fetchTimereportContext;
    if (!reportContextOptional.isPresent()
        || !reportContextOptional.get().getResult().isPresent()
        || !resultToDisplayChangedSinceLastRender) {
      LOGGER.debug(
          "[{}] No results to display or data not changed. Not refreshing TableView and data",
          getText());
      return;
    }

    SettingsUtil.Settings settings = SettingsUtil.loadSettings();
    if (settings.isShowStatistics() && statisticsView == null
        || !settings.isShowStatistics() && statisticsView != null) {
      // statistics are disabled and were previously rendered
      // or statistics are enabled and weren't rendered before
      // update content view
      LOGGER.debug("Updating contentView since settings for statistics seemed to have changed");
      setContent(getContentNode());
    }

    FetchTimereportContext timereportContext = reportContextOptional.get();
    TimerangeProvider timerangeProvider = timereportContext.getTimerangeProvider();

    // render the table columns if the timerange changed from last result
    if (!lastUsedTimerangeProvider.isPresent()
        || !lastUsedTimerangeProvider.get().equals(timerangeProvider)
        || (lastCollapseState.isPresent() && lastCollapseState.get() != settings.getCollapseState())
        || (lastHighlightState.isPresent()
            && lastHighlightState.get() != settings.getHighlightState())) {

      LOGGER.debug(
          "[{}] Regenerating columns for timerange {}",
          getText(),
          timerangeProvider.getReportTimerange().name());
      taskTableView.getColumns().clear();
      taskTableView.getColumns().add(taskStatusTreeTableColumn);
      taskTableView.getColumns().add(taskDescriptionTreeTableColumn);

      // render tables for all days in the selected timerange
      // e.g. timerange current month renders a column for
      // each day of the current month
      long amountOfDaysToDisplay =
          ChronoUnit.DAYS.between(timerangeProvider.getStartDate(), timerangeProvider.getEndDate());

      for (int days = 0; days <= amountOfDaysToDisplay; days++) {
        LocalDate currentColumnDate = timerangeProvider.getStartDate().plus(days, ChronoUnit.DAYS);
        String displayDate = FormattingUtil.formatDate(currentColumnDate);

        // worklog column
        taskTableView.getColumns().add(new WorklogTreeTableColumn(displayDate, currentColumnDate));
      }

      // also add another summary per task column
      taskTableView.getColumns().add(new TaskWorklogSummaryTreeTableColumn());

      lastUsedTimerangeProvider = Optional.of(timerangeProvider);
      lastCollapseState = Optional.of(settings.getCollapseState());
      lastHighlightState = Optional.of(settings.getHighlightState());
    }

    // refresh data
    LOGGER.debug("[{}] Refreshing items in TableView", getText());

    TreeItem<DisplayRow> root = taskTableView.getRoot();
    root.getChildren().clear();

    DisplayData displayData =
        getDisplayData(timereportContext, resultToDisplayChangedSinceLastRender);
    root.getChildren().addAll(displayData.getTreeRows());

    resultToDisplayChangedSinceLastRender = false;
  }