001package conexp.fx.gui.context;
002
003/*
004 * #%L
005 * Concept Explorer FX
006 * %%
007 * Copyright (C) 2010 - 2023 Francesco Kriegel
008 * %%
009 * This program is free software: you can redistribute it and/or modify
010 * it under the terms of the GNU General Public License as
011 * published by the Free Software Foundation, either version 3 of the
012 * License, or (at your option) any later version.
013 * 
014 * This program is distributed in the hope that it will be useful,
015 * but WITHOUT ANY WARRANTY; without even the implied warranty of
016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
017 * GNU General Public License for more details.
018 * 
019 * You should have received a copy of the GNU General Public
020 * License along with this program.  If not, see
021 * <http://www.gnu.org/licenses/gpl-3.0.html>.
022 * #L%
023 */
024import java.util.Collection;
025import java.util.Map;
026import java.util.Map.Entry;
027import java.util.Timer;
028import java.util.TimerTask;
029import java.util.concurrent.ConcurrentHashMap;
030
031//import org.semanticweb.owlapi.model.OWLClassExpression;
032
033import com.google.common.base.Function;
034import com.google.common.collect.Collections2;
035
036import conexp.fx.core.collections.Pair;
037import conexp.fx.core.collections.relation.RelationEvent;
038import conexp.fx.core.collections.relation.RelationEventHandler;
039import conexp.fx.core.context.Concept;
040import conexp.fx.core.context.MatrixContext;
041import conexp.fx.core.context.MatrixContext.Incidence;
042import conexp.fx.core.util.Constants;
043import conexp.fx.core.util.IdGenerator;
044//import conexp.fx.core.util.OWLUtil;
045import conexp.fx.gui.ConExpFX;
046import conexp.fx.gui.cellpane.Cell;
047import conexp.fx.gui.cellpane.CellPane;
048import conexp.fx.gui.cellpane.InteractionMode;
049import conexp.fx.gui.dataset.FCADataset;
050import conexp.fx.gui.graph.ConceptGraph;
051import conexp.fx.gui.util.ColorScheme;
052import conexp.fx.gui.util.LaTeX;
053import conexp.fx.gui.util.Platform2;
054import de.tudresden.inf.tcs.fcalib.Implication;
055import javafx.animation.Interpolator;
056import javafx.animation.KeyFrame;
057import javafx.animation.KeyValue;
058import javafx.animation.Timeline;
059import javafx.application.Platform;
060import javafx.beans.binding.DoubleBinding;
061import javafx.beans.binding.IntegerBinding;
062import javafx.beans.binding.StringBinding;
063import javafx.beans.property.BooleanProperty;
064import javafx.beans.property.DoubleProperty;
065import javafx.beans.property.IntegerProperty;
066import javafx.beans.property.SimpleBooleanProperty;
067import javafx.beans.property.SimpleDoubleProperty;
068import javafx.beans.property.SimpleIntegerProperty;
069import javafx.beans.value.ChangeListener;
070import javafx.beans.value.ObservableValue;
071import javafx.collections.MapChangeListener;
072import javafx.event.ActionEvent;
073import javafx.event.EventHandler;
074import javafx.geometry.Bounds;
075import javafx.geometry.Insets;
076import javafx.geometry.Pos;
077import javafx.scene.Node;
078import javafx.scene.control.Button;
079import javafx.scene.control.ButtonBuilder;
080import javafx.scene.control.ContextMenu;
081import javafx.scene.control.MenuItem;
082import javafx.scene.control.ScrollBar;
083import javafx.scene.control.Slider;
084import javafx.scene.control.SliderBuilder;
085import javafx.scene.control.TextField;
086import javafx.scene.control.TextFieldBuilder;
087import javafx.scene.control.ToggleButton;
088import javafx.scene.control.ToolBar;
089import javafx.scene.image.Image;
090import javafx.scene.image.ImageView;
091import javafx.scene.image.ImageViewBuilder;
092import javafx.scene.input.KeyEvent;
093import javafx.scene.input.MouseEvent;
094import javafx.scene.layout.BorderPane;
095import javafx.scene.layout.ColumnConstraints;
096import javafx.scene.layout.GridPane;
097import javafx.scene.layout.HBox;
098import javafx.scene.layout.RowConstraints;
099import javafx.scene.paint.Color;
100import javafx.scene.text.TextAlignment;
101import javafx.util.Duration;
102
103public class MatrixContextWidget<G, M> extends BorderPane {
104
105  public final class RowHeaderPane extends CellPane<RowHeaderPane, RowHeaderCell> {
106
107    private RowHeaderPane(final boolean interactive) {
108      super("DomainPane", InteractionMode.ROWS, interactive);
109      this.rowHeightDefault.bind(MatrixContextWidget.this.cellSizeDefault);
110      this.columnWidthDefault.bind(MatrixContextWidget.this.rowHeaderSizeDefault);
111      this.zoomFactor.bind(MatrixContextWidget.this.zoomFactor);
112      this.textSizeDefault.bind(MatrixContextWidget.this.textSizeDefault);
113      this.animate.bind(MatrixContextWidget.this.animate);
114      this.autoSizeRows.set(false);
115      this.autoSizeColumns.set(true);
116      this.maxColumns.set(1);
117      this.maxRows.set(context.rowHeads().size());
118//      final RelationEventHandler<G, M> eventHandler = new RelationEventHandler<G, M>() {
119//
120//        public final void handle(final RelationEvent<G, M> event) {
121//          Platform2.runOnFXThread(new Runnable() {
122//
123//            public void run() {
124////              System.out.println("updating rows");
125//              maxRows.set(context.rowHeads().size());
126////              updateContent();
127//            }
128//          });
129//        }
130//      };
131//      context.addEventHandler(eventHandler, RelationEvent.ROWS);
132//      context.addEventHandler(eventHandler, RelationEvent.ROWS_ADDED);
133//      context.addEventHandler(eventHandler, RelationEvent.ROWS_REMOVED);
134      context.addEventHandler(
135          __ -> Platform2.runOnFXThread(() -> maxRows.set(context.rowHeads().size())),
136          RelationEvent.ROWS);
137      if (dataset != null)
138        this.interactionPane.addEventHandler(MouseEvent.MOUSE_EXITED, new EventHandler<MouseEvent>() {
139
140          public final void handle(final MouseEvent event) {
141            dataset.conceptGraph.highlight(false, dataset.conceptGraph.highlightRequests.dehighlight());
142          }
143        });
144      zoomFactor.addListener((__, ___, ____) -> updateContent());
145    }
146
147    protected final RowHeaderCell createCell(final int row, final int column) {
148      return new RowHeaderCell(row);
149    }
150
151    public final void highlightConcept(final Collection<Integer> domainIndices) {
152      highlight(domainIndices, null);
153    }
154
155    protected final Map<G, Node> decorations = new ConcurrentHashMap<>();
156
157    public final void addDecoration(final G row, final Node decoration) {
158      decorations.put(row, decoration);
159    }
160  }
161
162  public final class ColHeaderPane extends CellPane<ColHeaderPane, ColHeaderCell> {
163
164    private ColHeaderPane(final boolean interactive) {
165      super("CodomainPane", InteractionMode.COLUMNS, interactive);
166      this.rowHeightDefault.bind(MatrixContextWidget.this.colHeaderSizeDefault);
167      this.columnWidthDefault.bind(MatrixContextWidget.this.cellSizeDefault);
168      this.zoomFactor.bind(MatrixContextWidget.this.zoomFactor);
169      this.textSizeDefault.bind(MatrixContextWidget.this.textSizeDefault);
170      this.animate.bind(MatrixContextWidget.this.animate);
171      this.autoSizeRows.set(true);
172      this.autoSizeColumns.set(false);
173      this.maxRows.set(1);
174      this.maxColumns.set(context.colHeads().size());
175      final RelationEventHandler<G, M> eventHandler = new RelationEventHandler<G, M>() {
176
177        public final void handle(final RelationEvent<G, M> event) {
178          Platform2.runOnFXThread(new Runnable() {
179
180            public void run() {
181//              System.out.println("updating columns");
182              maxColumns.set(context.colHeads().size());
183//              updateContent();
184            }
185          });
186        }
187      };
188      context.addEventHandler(eventHandler, RelationEvent.COLUMNS);
189//      context.addEventHandler(eventHandler, RelationEvent.COLUMNS_ADDED);
190//      context.addEventHandler(eventHandler, RelationEvent.COLUMNS_REMOVED);
191      if (dataset != null)
192        this.interactionPane.addEventHandler(MouseEvent.MOUSE_EXITED, new EventHandler<MouseEvent>() {
193
194          public final void handle(final MouseEvent event) {
195            dataset.conceptGraph.highlight(false, dataset.conceptGraph.highlightRequests.dehighlight());
196          }
197        });
198      zoomFactor.addListener((__, ___, ____) -> updateContent());
199    }
200
201    protected final ColHeaderCell createCell(final int row, final int column) {
202      return new ColHeaderCell(column);
203    }
204
205    public final void highlightConcept(final Collection<Integer> codomainIndices) {
206      highlight(null, codomainIndices);
207    }
208  }
209
210  public final class ContextPane extends CellPane<ContextPane, ContextCell> {
211
212    private ContextPane(final boolean interactive) {
213      super("FormalContextPane", InteractionMode.ROWS_AND_COLUMNS, interactive);
214      this.textSizeDefault.bind(MatrixContextWidget.this.incidenceSizeDefault);
215      this.zoomFactor.bind(MatrixContextWidget.this.zoomFactor);
216      this.bind(rowHeaderPane, InteractionMode.ROWS);
217      this.bind(colHeaderPane, InteractionMode.COLUMNS);
218      if (dataset != null)
219        this.rowMap.addListener(new MapChangeListener<Integer, Integer>() {
220
221          public final void onChanged(
222              final javafx.collections.MapChangeListener.Change<? extends Integer, ? extends Integer> change) {
223            dataset.unsavedChanges.set(true);
224          }
225        });
226      if (dataset != null)
227        this.columnMap.addListener(new MapChangeListener<Integer, Integer>() {
228
229          public final void onChanged(
230              final javafx.collections.MapChangeListener.Change<? extends Integer, ? extends Integer> change) {
231            dataset.unsavedChanges.set(true);
232          }
233        });
234      if (dataset != null)
235        this.interactionPane.addEventHandler(MouseEvent.MOUSE_EXITED, new EventHandler<MouseEvent>() {
236
237          public final void handle(final MouseEvent event) {
238            dataset.conceptGraph.highlight(false, dataset.conceptGraph.highlightRequests.dehighlight());
239          }
240        });
241      zoomFactor.addListener((__, ___, ____) -> updateContent());
242    }
243
244    protected final ContextCell createCell(final int row, final int column) {
245      return new ContextCell(row, column);
246    }
247  }
248
249  public final class RowHeaderCell extends Cell<RowHeaderCell, RowHeaderPane> {
250
251    private ImageView view = ImageViewBuilder.create().build();
252
253    private RowHeaderCell(final int row) {
254      super(rowHeaderPane, row, 0, Pos.CENTER_RIGHT, TextAlignment.RIGHT, false, null, false);
255      this.contentPane.get().getChildren().add(view);
256      this.contentPane.get().text.setOpacity(0);
257      if (cellPane.interactive) {
258        final ContextMenu contextMenu = new ContextMenu();
259        final MenuItem editItem = new MenuItem("Edit");
260        final MenuItem removeItem = new MenuItem("Remove");
261        final MenuItem selectItem = new MenuItem("Select");
262        final MenuItem insertItem = new MenuItem("Insert");
263        if (dataset != null && dataset.editable)
264          contextMenu.getItems().addAll(editItem, removeItem, selectItem, insertItem);
265        else
266          contextMenu.getItems().addAll(removeItem, selectItem);
267        insertItem.setOnAction(__ -> {
268          ((FCADataset<String, String>) dataset).addObject(
269              (dataset.context.isHomogen() ? "Element " : "Object ") + IdGenerator.getNextId(dataset),
270              contentCoordinates.get().x());
271          rowHeaderPane.rowOpacityMap.keySet().stream().sorted().filter(i -> i >= contentCoordinates.get().x()).forEach(
272              i -> rowHeaderPane.rowOpacityMap.put(i + 1, rowHeaderPane.rowOpacityMap.remove(i)));
273        });
274        editItem.setOnAction(__ -> {
275          if (MatrixContextWidget.this.dataset.editable) {
276            final G object = context.rowHeads().get(contentCoordinates.get().x());
277            final TextField textField = TextFieldBuilder.create().text((String) object).build();
278            textField.addEventHandler(KeyEvent.KEY_RELEASED, event -> {
279              switch (event.getCode()) {
280              case ENTER:
281                dataset.renameObject(object, (G) textField.getText().trim());
282              case ESCAPE:
283//                interactionPane.get().getChildren().remove(textField);
284                contentPane.get().getChildren().remove(textField);
285                contentPane.get().getChildren().add(view);
286              }
287            });
288//            interactionPane.get().getChildren().add(textField);
289            contentPane.get().getChildren().remove(view);
290            contentPane.get().getChildren().add(textField);
291            textField.focusedProperty().addListener(
292                (observable, oldValue, newValue) -> new Timer().schedule(new TimerTask() {
293
294              public final void run() {
295                Platform.runLater(() -> textField.selectAll());
296              }
297            }, 20));
298            textField.requestFocus();
299          } else {
300            System.out.println("no instance of MatrixContextWidget");
301          }
302        });
303        removeItem.setOnAction(__ -> {
304          dataset.removeObject(context.rowHeads().get(contentCoordinates.get().x()));
305          rowHeaderPane.rowOpacityMap.keySet().stream().sorted().filter(i -> i > contentCoordinates.get().x()).forEach(
306              i -> rowHeaderPane.rowOpacityMap.put(i - 1, rowHeaderPane.rowOpacityMap.remove(i)));
307        });
308        selectItem.setOnAction(__ -> select());
309        this.interactionPane.get().addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
310          switch (event.getButton()) {
311          case PRIMARY:
312            select();
313            break;
314          case SECONDARY:
315            contextMenu.show(interactionPane.getValue(), event.getScreenX(), event.getScreenY());
316          }
317        });
318      }
319      if (dataset != null)
320        this.interactionPane.get().addEventHandler(MouseEvent.MOUSE_ENTERED, event -> {
321          if (contextPane.highlight.get())
322            dataset.conceptGraph.highlight(
323                true,
324                dataset.conceptGraph.highlightRequests.object(context.rowHeads().get(contentCoordinates.get().x())));
325        });
326      if (cellPane.autoSizeRows.get() || cellPane.autoSizeColumns.get())
327        view.layoutBoundsProperty().addListener(new ChangeListener<Bounds>() {
328
329          @Override
330          public void changed(ObservableValue<? extends Bounds> observable, Bounds oldValue, Bounds newValue) {
331            final double decorationWidth =
332                cellPane.decorations.containsKey(context.rowHeads().get(contentCoordinates.get().x()))
333                    ? cellPane.decorations
334                        .get(context.rowHeads().get(contentCoordinates.get().x()))
335                        .layoutBoundsProperty()
336                        .get()
337                        .getWidth()
338                    : 0d;
339            final double width = newValue.getWidth() + decorationWidth;
340            if (width > cellPane.maximalTextWidth.get())
341              cellPane.maximalTextWidth.set(width);
342          }
343        });
344      context.addEventHandler(event -> Platform2.runOnFXThread(RowHeaderCell.this::updateContent), RelationEvent.ROWS);
345      updateContent();
346    }
347
348    private final void select() {
349      synchronized (rowHeaderPane.rowOpacityMap) {
350        if (rowHeaderPane.rowOpacityMap.containsKey(contentCoordinates.get().x())) {
351          if (dataset != null)
352            dataset.conceptGraph.highlight(false, dataset.conceptGraph.highlightRequests.dehighlight());
353          rowHeaderPane.rowOpacityMap.remove(contentCoordinates.get().x());
354          if (dataset != null)
355            dataset.selectObject(context.rowHeads().get(contentCoordinates.get().x()));
356        } else {
357          if (dataset != null)
358            dataset.conceptGraph.highlight(false, dataset.conceptGraph.highlightRequests.dehighlight());
359          rowHeaderPane.rowOpacityMap.put(contentCoordinates.get().x(), Constants.HIDE_OPACITY);
360          if (dataset != null)
361            dataset.ignoreObject(context.rowHeads().get(contentCoordinates.get().x()));
362        }
363      }
364//    if (rowHeaderPane.rowOpacityMap.containsKey(contentCoordinates.get().x())) {
365//    rowHeaderPane.rowOpacityMap.remove(contentCoordinates.get().x());
366////  final G object = context.getDomain().get(contentCoordinates.get().x());
367////  context.selectObject(object);
368//  } else {
369//    rowHeaderPane.rowOpacityMap.put(contentCoordinates.get().x(), Constants.HIDE_OPACITY);
370////  final G object = context.getDomain().get(contentCoordinates.get().x());
371////  context.deselectObject(object);
372//  }
373    }
374
375    public final void updateContent() {
376      try {
377        final String string = context.rowHeads().get(contentCoordinates.get().x()).toString();
378        textContent.set(string);
379        view.setImage(LaTeX.toFXImage(string, (float) (16d * zoomFactor.get())));
380        if (cellPane.decorations.containsKey(context.rowHeads().get(contentCoordinates.get().x()))) {
381          interactionPane.get().getChildren().removeAll(cellPane.decorations.values());
382          interactionPane
383              .get()
384              .setRight(cellPane.decorations.get(context.rowHeads().get(contentCoordinates.get().x())));
385        }
386      } catch (IndexOutOfBoundsException __) {}
387    }
388  }
389
390  public final class ColHeaderCell extends Cell<ColHeaderCell, ColHeaderPane> {
391
392    private ImageView view = ImageViewBuilder.create().build();
393
394    private ColHeaderCell(final int column) {
395      super(colHeaderPane, 0, column, Pos.CENTER_LEFT, TextAlignment.LEFT, true, null, false);
396      this.contentPane.get().getChildren().add(view);
397      this.contentPane.get().text.setOpacity(0);
398      if (cellPane.interactive) {
399        final ContextMenu contextMenu = new ContextMenu();
400        final MenuItem editItem = new MenuItem("Edit");
401        final MenuItem removeItem = new MenuItem("Remove");
402        final MenuItem selectItem = new MenuItem("Select");
403        final MenuItem insertItem = new MenuItem("Insert");
404        if (dataset != null && dataset.editable)
405          contextMenu.getItems().addAll(editItem, removeItem, selectItem, insertItem);
406        else
407          contextMenu.getItems().addAll(removeItem, selectItem);
408        insertItem.setOnAction(__ -> {
409          ((FCADataset<String, String>) dataset).addAttribute(
410              (dataset.context.isHomogen() ? "Element " : "Attribute ") + IdGenerator.getNextId(dataset),
411              contentCoordinates.get().y());
412          colHeaderPane.columnOpacityMap
413              .keySet()
414              .stream()
415              .sorted()
416              .filter(i -> i >= contentCoordinates.get().y())
417              .forEach(i -> colHeaderPane.columnOpacityMap.put(i + 1, colHeaderPane.columnOpacityMap.remove(i)));
418        });
419        editItem.setOnAction(event -> {
420          if (MatrixContextWidget.this.dataset.editable) {
421            final M attribute = context.colHeads().get(contentCoordinates.get().y());
422            final TextField textField = TextFieldBuilder.create().text((String) attribute).build();
423            textField.addEventHandler(KeyEvent.KEY_RELEASED, keyEvent -> {
424              switch (keyEvent.getCode()) {
425              case ENTER:
426                dataset.renameAttribute(attribute, (M) textField.getText().trim());
427              case ESCAPE:
428//                interactionPane.get().getChildren().remove(textField);
429                contentPane.get().getChildren().remove(textField);
430                contentPane.get().getChildren().add(view);
431              }
432            });
433//            textField.rotateProperty().set(-90);
434            textField.setMinSize(colHeaderPane.rowHeight.get(), cellSize.get());
435            textField.setMaxSize(colHeaderPane.rowHeight.get(), cellSize.get());
436//            interactionPane.get().getChildren().add(textField);
437            contentPane.get().getChildren().remove(view);
438            contentPane.get().getChildren().add(textField);
439            textField.focusedProperty().addListener(
440                (observable, oldValue, newValue) -> new Timer().schedule(new TimerTask() {
441
442              public final void run() {
443                Platform.runLater(() -> textField.selectAll());
444              }
445            }, 20));
446            textField.requestFocus();
447          }
448        });
449        removeItem.setOnAction(event -> {
450          dataset.removeAttribute(context.colHeads().get(contentCoordinates.get().y()));
451          colHeaderPane.columnOpacityMap.remove(contentCoordinates.get().y());
452          colHeaderPane.columnOpacityMap
453              .keySet()
454              .stream()
455              .sorted()
456              .filter(i -> i > contentCoordinates.get().y())
457              .forEach(i -> colHeaderPane.columnOpacityMap.put(i - 1, colHeaderPane.columnOpacityMap.remove(i)));
458        });
459        selectItem.setOnAction(event -> select());
460//      if (!tab.fca.context.selectedAttributes().contains(tab.fca.context.colHeads().get(contentCoordinates.get().y())))
461//        colHeaderPane.columnOpacityMap.put(contentCoordinates.get().y(), Constants.HIDE_OPACITY);
462        this.interactionPane.get().addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
463          switch (event.getButton()) {
464          case PRIMARY:
465            select();
466            break;
467          case SECONDARY:
468            contextMenu.show(interactionPane.getValue(), event.getScreenX(), event.getScreenY());
469          }
470        });
471      }
472      if (dataset != null)
473        this.interactionPane.get().addEventHandler(MouseEvent.MOUSE_ENTERED, event -> {
474          if (contextPane.highlight.get()) {
475            final M m = context.colHeads().get(contentCoordinates.get().y());
476            if (context.selectedAttributes().contains(m))
477              dataset.conceptGraph.highlight(true, dataset.conceptGraph.highlightRequests.attribute(m));
478          }
479        });
480      if (cellPane.autoSizeRows.get() || cellPane.autoSizeColumns.get())
481        view.layoutBoundsProperty().addListener(new ChangeListener<Bounds>() {
482
483          @Override
484          public void changed(ObservableValue<? extends Bounds> observable, Bounds oldValue, Bounds newValue) {
485            final double width = newValue.getWidth();
486            if (width > cellPane.maximalTextWidth.get())
487              cellPane.maximalTextWidth.set(width);
488          }
489        });
490      context
491          .addEventHandler(event -> Platform2.runOnFXThread(ColHeaderCell.this::updateContent), RelationEvent.COLUMNS);
492      updateContent();
493    }
494
495    private void select() {
496      synchronized (colHeaderPane.columnOpacityMap) {
497        if (colHeaderPane.columnOpacityMap.containsKey(contentCoordinates.get().y())) {
498          if (dataset != null)
499            dataset.conceptGraph.highlight(false, dataset.conceptGraph.highlightRequests.dehighlight());
500          colHeaderPane.columnOpacityMap.remove(contentCoordinates.get().y());
501          if (dataset != null)
502            dataset.selectAttribute(context.colHeads().get(contentCoordinates.get().y()));
503        } else {
504          if (dataset != null)
505            dataset.conceptGraph.highlight(false, dataset.conceptGraph.highlightRequests.dehighlight());
506          colHeaderPane.columnOpacityMap.put(contentCoordinates.get().y(), Constants.HIDE_OPACITY);
507          if (dataset != null)
508            dataset.ignoreAttribute(context.colHeads().get(contentCoordinates.get().y()));
509        }
510      }
511    }
512
513    public final void updateContent() {
514      try {
515        final M m = context.colHeads().get(contentCoordinates.get().y());
516        final String string =
517//            m instanceof OWLClassExpression ? OWLUtil.toString((OWLClassExpression) m) : 
518              m.toString();
519        textContent.set(string);
520        view.setImage(LaTeX.toFXImage(string, (float) (16d * zoomFactor.get())));
521      } catch (IndexOutOfBoundsException __) {}
522    }
523  }
524
525  public final class ContextCell extends Cell<ContextCell, ContextPane> {
526
527    private ContextCell(final int row, final int column) {
528      super(contextPane, row, column, Pos.CENTER, TextAlignment.CENTER, false, null, false);
529      if (dataset != null)
530        this.interactionPane.get().addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
531
532          public final void handle(final MouseEvent event) {
533            final G g = context.rowHeads().get(contentCoordinates.get().x());
534            final M m = context.colHeads().get(contentCoordinates.get().y());
535            dataset.conceptGraph.highlight(false, dataset.conceptGraph.highlightRequests.dehighlight());
536            dataset.flip(g, m);
537            dataset.unsavedChanges.set(true);
538          }
539        });
540      else
541        this.interactionPane.get().addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
542          final G g = context.rowHeads().get(contentCoordinates.get().x());
543          final M m = context.colHeads().get(contentCoordinates.get().y());
544          if (context.contains(g, m))
545            context.remove(g, m);
546          else
547            context.add(g, m);
548        });
549      if (dataset != null)
550        this.interactionPane.get().addEventHandler(MouseEvent.MOUSE_ENTERED, new EventHandler<MouseEvent>() {
551
552          public final void handle(final MouseEvent event) {
553            if (contextPane.highlight.get()) {
554              final G g = context.rowHeads().get(contentCoordinates.get().x());
555              final M m = context.colHeads().get(contentCoordinates.get().y());
556              if (context.selectedAttributes().contains(m))
557                if (textContent.get().equals(Constants.CROSS_CHARACTER))
558                  dataset.conceptGraph.highlight(true, dataset.conceptGraph.highlightRequests.incidence(g, m));
559                else if (textContent.get().equals(Constants.DOWN_ARROW_CHARACTER))
560                  dataset.conceptGraph.highlight(true, dataset.conceptGraph.highlightRequests.downArrow(g, m));
561                else if (textContent.get().equals(Constants.UP_ARROW_CHARACTER))
562                  dataset.conceptGraph.highlight(true, dataset.conceptGraph.highlightRequests.upArrow(g, m));
563                else if (textContent.get().equals(Constants.BOTH_ARROW_CHARACTER))
564                  dataset.conceptGraph.highlight(true, dataset.conceptGraph.highlightRequests.bothArrow(g, m));
565                else if (textContent.get().equals(Constants.NO_CROSS_CHARACTER))
566                  dataset.conceptGraph.highlight(true, dataset.conceptGraph.highlightRequests.nonIncidence(g, m));
567            }
568          }
569        });
570      context.addEventHandler(event -> updateContent(), RelationEvent.ANY);
571//          RelationEvent.ENTRIES_ADDED,
572//          RelationEvent.ENTRIES_REMOVED,
573//          RelationEvent.ALL_CHANGED,
574//          RelationEvent.SELECTION_CHANGED);
575      updateContent();
576    }
577
578    @SuppressWarnings("incomplete-switch")
579    public final void updateContent() {
580      try {
581        final G g = context.rowHeads().get(contentCoordinates.get().x());
582        final M m = context.colHeads().get(contentCoordinates.get().y());
583        if (context.selectedAttributes().contains(m) && context.selectedObjects().contains(g)) {
584          final Pair<Incidence, Incidence> p = context.selection.getValue(g, m, showArrows.get(), showPaths.get());
585          final Incidence first = p.first();
586          final Incidence second = p.second();
587          ContextCell.this.textContent.set(
588              second != null && first == Incidence.NO_CROSS ? Constants.NO_CROSS_CHARACTER_BOLD : first.toString());
589          switch (first) {
590          case BOTH_ARROW:
591            ContextCell.this.contentPane.get().text.setRotate(-45d);
592            break;
593          case DOWN_ARROW:
594          case UP_ARROW:
595          case CROSS:
596          case NO_CROSS:
597            ContextCell.this.contentPane.get().text.setRotate(0d);
598            break;
599          }
600          if (second == null)
601            ContextCell.this.contentPane.get().text.setFill(Color.BLACK);
602          else
603            switch (second) {
604            case BOTH_PATH:
605              ContextCell.this.contentPane.get().text.setFill(ConceptGraph.COLOR_INTERVAL);
606              break;
607            case DOWN_PATH:
608              ContextCell.this.contentPane.get().text.setFill(ConceptGraph.COLOR_LOWER);
609              break;
610            case UP_PATH:
611              ContextCell.this.contentPane.get().text.setFill(ConceptGraph.COLOR_UPPER);
612              break;
613            }
614        } else {
615          ContextCell.this.textContent.set(context.getValue(g, m, false).first().toString());
616          ContextCell.this.contentPane.get().text.setRotate(0d);
617        }
618      } catch (IndexOutOfBoundsException __) {}
619    }
620  }
621
622  protected final FCADataset<G, M>    dataset;
623  protected final MatrixContext<G, M> context;
624  protected final GridPane            centerPane            = new GridPane();
625  public final RowHeaderPane          rowHeaderPane;
626  public final ColHeaderPane          colHeaderPane;
627  public final ContextPane            contextPane;
628  protected final ScrollBar           rowScrollBar;
629  protected final ScrollBar           colScrollBar;
630//  public final ListSpinner<Integer> zoomSpinner =new ListSpinner<Integer>(-4, 4, 1);
631  public final Slider                 zoomSlider            = SliderBuilder
632                                                                .create()
633                                                                .min(-4d)
634                                                                .max(4d)
635                                                                .value(0d)
636                                                                // .showTickMarks(true)
637                                                                // .minorTickCount(17)
638                                                                // .majorTickUnit(1d)
639                                                                // .snapToTicks(true)
640                                                                .blockIncrement(0.25d)
641                                                                .build();
642  public final DoubleProperty         zoomFactor            = new SimpleDoubleProperty(0.01d);
643  public final IntegerProperty        rowHeaderSizeDefault  = new SimpleIntegerProperty(40);
644  public final IntegerProperty        colHeaderSizeDefault  = new SimpleIntegerProperty(40);
645  public final IntegerProperty        cellSizeDefault       = new SimpleIntegerProperty(20);
646  public final IntegerProperty        textSizeDefault       = new SimpleIntegerProperty(16);
647  public final IntegerProperty        incidenceSizeDefault  = new SimpleIntegerProperty(20);
648  public final IntegerBinding         cellSize              = new IntegerBinding() {
649
650                                                              {
651                                                                super.bind(zoomFactor, cellSizeDefault);
652                                                              }
653
654                                                              protected int computeValue() {
655                                                                return (int) (zoomFactor.get()
656                                                                    * cellSizeDefault.doubleValue());
657                                                              };
658                                                            };
659  public final IntegerBinding         textSize              = new IntegerBinding() {
660
661                                                              {
662                                                                super.bind(zoomFactor, textSizeDefault);
663                                                              }
664
665                                                              protected int computeValue() {
666                                                                return (int) (zoomFactor.get()
667                                                                    * textSizeDefault.doubleValue());
668                                                              };
669                                                            };
670  public final IntegerBinding         incidenceSize         = new IntegerBinding() {
671
672                                                              {
673                                                                super.bind(zoomFactor, textSizeDefault);
674                                                              }
675
676                                                              protected int computeValue() {
677                                                                return (int) (zoomFactor.get()
678                                                                    * incidenceSizeDefault.doubleValue());
679                                                              };
680                                                            };
681  public final BooleanProperty        animate               = new SimpleBooleanProperty(false);
682  public final BooleanProperty        showArrows            = new SimpleBooleanProperty();
683  public final BooleanProperty        showPaths             = new SimpleBooleanProperty();
684  public final ToggleButton           highlightToggleButton = new ToggleButton();
685  public final DoubleBinding          height;
686
687  public MatrixContextWidget(final FCADataset<G, M> fcaInstance) {
688    this(fcaInstance, true);
689  }
690
691  /**
692   * @param dataset
693   * @param withToolbar
694   * @param orContext
695   *          may only be set if fcaInstance is null, otherwise unexpected behaviour may occur.
696   */
697  @SafeVarargs
698  public MatrixContextWidget(
699      final FCADataset<G, M> dataset,
700      final boolean withToolbar,
701      final MatrixContext<G, M>... orContext) {
702    this(dataset, withToolbar, true, orContext);
703  }
704
705  @SafeVarargs
706  public MatrixContextWidget(
707      final FCADataset<G, M> dataset,
708      final boolean withToolbar,
709      final boolean interactive,
710      final MatrixContext<G, M>... orContext) {
711    super();
712    this.dataset = dataset;
713    if (dataset == null && orContext[0] != null)
714      this.context = orContext[0];
715    else
716      this.context = dataset.context;
717    this.rowHeaderPane = new RowHeaderPane(interactive);
718    this.colHeaderPane = new ColHeaderPane(interactive);
719    this.contextPane = new ContextPane(interactive);
720    this.rowScrollBar = contextPane.getRowScrollBar();
721    this.colScrollBar = contextPane.getColumnScrollBar();
722    centerPane.setHgap(4);
723    centerPane.setVgap(4);
724    rowHeaderPane.colorScheme.setValue(ColorScheme.JAVA_FX);
725    colHeaderPane.colorScheme.setValue(ColorScheme.JAVA_FX);
726    contextPane.colorScheme.setValue(ColorScheme.JAVA_FX);
727    final RowConstraints firstRowConstraints = new RowConstraints();
728    firstRowConstraints.minHeightProperty().bind(colHeaderPane.rowHeight);
729    firstRowConstraints.maxHeightProperty().bind(colHeaderPane.rowHeight);
730    final RowConstraints secondRowConstraints = new RowConstraints();
731    secondRowConstraints.minHeightProperty().bind(cellSize);
732    secondRowConstraints.prefHeightProperty().bind(new IntegerBinding() {
733
734      {
735        super.bind(rowHeaderPane.maxRows, cellSize);
736      }
737
738      protected final int computeValue() {
739        return context.rowHeads().size() * cellSize.get();
740      }
741    });
742    final RowConstraints thirdRowConstraints = new RowConstraints();
743    thirdRowConstraints.minHeightProperty().bind(cellSize);
744    thirdRowConstraints.maxHeightProperty().bind(cellSize);
745    centerPane.getRowConstraints().addAll(firstRowConstraints, secondRowConstraints, thirdRowConstraints);
746    final ColumnConstraints firstColumnConstraints = new ColumnConstraints();
747    firstColumnConstraints.minWidthProperty().bind(rowHeaderPane.columnWidth);
748    firstColumnConstraints.maxWidthProperty().bind(rowHeaderPane.columnWidth);
749    final ColumnConstraints secondColumnConstraints = new ColumnConstraints();
750    secondColumnConstraints.minWidthProperty().bind(cellSize);
751    secondColumnConstraints.maxWidthProperty().bind(new IntegerBinding() {
752
753      {
754        super.bind(colHeaderPane.maxColumns, cellSize);
755      }
756
757      protected final int computeValue() {
758        return context.colHeads().size() * cellSize.get();
759      }
760    });
761    final ColumnConstraints thirdColumnConstraints = new ColumnConstraints();
762    thirdColumnConstraints.minWidthProperty().bind(cellSize);
763    thirdColumnConstraints.maxWidthProperty().bind(cellSize);
764    centerPane.getColumnConstraints().addAll(firstColumnConstraints, secondColumnConstraints, thirdColumnConstraints);
765    centerPane.add(contextPane.getContentAndInteractionStackPane(), 1, 1);
766    centerPane.add(rowHeaderPane.getContentAndInteractionStackPane(), 0, 1);
767    centerPane.add(colHeaderPane.getContentAndInteractionStackPane(), 1, 0);
768    centerPane.add(rowScrollBar, 2, 1);
769    centerPane.add(colScrollBar, 1, 2);
770    this.setCenter(centerPane);
771    if (withToolbar)
772      createToolBar();
773    else
774      zoomFactor.set(Math.pow(2d, 0));
775    final ChangeListener<Boolean> updateContentListener = new ChangeListener<Boolean>() {
776
777      public final void
778          changed(final ObservableValue<? extends Boolean> observable, final Boolean oldValue, final Boolean newValue) {
779        contextPane.updateContent();
780      }
781    };
782    showArrows.addListener(updateContentListener);
783    showPaths.addListener(updateContentListener);
784    height = new DoubleBinding() {
785
786      {
787        bind(
788            colHeaderPane.heightProperty(),
789            contextPane.heightProperty(),
790            colHeaderPane.rowHeight,
791            colHeaderPane.visibleRows,
792            contextPane.rowHeight,
793            contextPane.visibleRows);
794      }
795
796      @Override
797      protected double computeValue() {
798//        System.out.println(colHeaderPane.rowHeight.get());
799//        System.out.println(colHeaderPane.visibleRows.get());
800//        System.out.println(contextPane.rowHeight.get());
801//        System.out.println(contextPane.visibleRows.get());
802        final int h = colHeaderPane.rowHeight.get() * colHeaderPane.visibleRows.get()
803            + contextPane.rowHeight.get() * contextPane.visibleRows.get();
804//        System.out.println(h);
805        return h;
806      }
807    };
808    rowHeaderPane.toFront();
809    colHeaderPane.toFront();
810    final Timeline t = new Timeline();
811    t.getKeyFrames().add(new KeyFrame(
812        Duration.millis(1000),
813        // new EventHandler<ActionEvent>() {
814        //
815        // public final void handle(final ActionEvent event) {
816        // final Timeline s = new Timeline();
817        // s.getKeyFrames().add(
818        // new KeyFrame(Duration.millis(1000), new KeyValue(zoomSlider.valueProperty(), 0.9d, Interpolator.EASE_OUT)));
819        // Platform.runLater(new Runnable() {
820        //
821        //
822        // public final void run() {
823        // s.play();
824        // }
825        // });
826        // }
827        // },
828        new KeyValue(zoomSlider.valueProperty(), 0d, Interpolator.EASE_IN)));
829    Platform.runLater(new Runnable() {
830
831      public void run() {
832        t.play();
833      }
834    });
835    if (dataset != null && dataset.editable) {
836
837      final Button domainButton = ButtonBuilder
838          .create()
839          // .text(conExpTab.fca.context.isHomogen() ? "New Element" : "New Object")
840          .onAction(new EventHandler<ActionEvent>() {
841
842            public void handle(ActionEvent event) {
843              ((FCADataset<String, String>) dataset).addObject(
844                  (dataset.context.isHomogen() ? "Element " : "Object ") + IdGenerator.getNextId(dataset),
845                  -1);
846            }
847          })
848          .build();
849      final ImageView view =
850          ImageViewBuilder.create().image(new Image(ConExpFX.class.getResourceAsStream("image/16x16/add.png"))).build();
851      view.scaleXProperty().bind(zoomFactor);
852      view.scaleYProperty().bind(zoomFactor);
853      domainButton.setGraphic(view);
854      domainButton.minWidthProperty().bind(rowHeaderPane.columnWidth);
855      domainButton.maxWidthProperty().bind(rowHeaderPane.columnWidth);
856      domainButton.minHeightProperty().bind(cellSize);
857      domainButton.maxHeightProperty().bind(cellSize);
858      domainButton.styleProperty().bind(new StringBinding() {
859
860        {
861          super.bind(textSize);
862        }
863
864        @Override
865        protected String computeValue() {
866          return "-fx-padding: 0; -fx-font-size: " + textSize.get() + ";";
867        }
868      });
869      centerPane.add(domainButton, 0, 2);
870      final Button codomainButton = ButtonBuilder
871          .create()
872          // .text(conExpTab.fca.context.isHomogen() ? "New Element" : "New Attribute")
873          .onAction(new EventHandler<ActionEvent>() {
874
875            public void handle(ActionEvent event) {
876              ((FCADataset<String, String>) dataset).addAttribute(
877                  (dataset.context.isHomogen() ? "Element " : "Attribute ") + IdGenerator.getNextId(dataset),
878                  -1);
879            }
880          })
881          .build();
882      final ImageView view2 =
883          ImageViewBuilder.create().image(new Image(ConExpFX.class.getResourceAsStream("image/16x16/add.png"))).build();
884      view2.scaleXProperty().bind(zoomFactor);
885      view2.scaleYProperty().bind(zoomFactor);
886      codomainButton.setGraphic(view2);
887      codomainButton.rotateProperty().set(-90);
888      codomainButton.minWidthProperty().bind(colHeaderPane.rowHeight);
889      codomainButton.maxWidthProperty().bind(colHeaderPane.rowHeight);
890      codomainButton.minHeightProperty().bind(cellSize);
891      codomainButton.maxHeightProperty().bind(cellSize);
892      codomainButton.translateXProperty().bind(new DoubleBinding() {
893
894        {
895          super.bind(colHeaderPane.rowHeight, cellSize);
896        }
897
898        @Override
899        protected double computeValue() {
900          return -(colHeaderPane.rowHeight.get() - cellSize.get()) / 2d;
901        }
902      });
903      codomainButton.styleProperty().bind(new StringBinding() {
904
905        {
906          super.bind(textSize);
907        }
908
909        @Override
910        protected String computeValue() {
911          return "-fx-padding: 0; -fx-font-size: " + textSize.get() + ";";
912        }
913      });
914      centerPane.add(codomainButton, 2, 0);
915    }
916  }
917
918  private final void createToolBar() {
919    zoomFactor.bind(new DoubleBinding() {
920
921      {
922        bind(zoomSlider.valueProperty());
923      }
924
925      protected double computeValue() {
926        return Math.pow(2d, zoomSlider.valueProperty().get());
927      }
928    });
929    final ToggleButton arrowsToggleButton =
930        new ToggleButton(Constants.DOWN_ARROW_CHARACTER + Constants.UP_ARROW_CHARACTER);
931    arrowsToggleButton.setSelected(false);
932    arrowsToggleButton.setMinHeight(24);
933    showArrows.bind(arrowsToggleButton.selectedProperty());
934    final ToggleButton pathsToggleButton =
935        new ToggleButton(Constants.DOWN_ARROW_CHARACTER + Constants.DOWN_ARROW_CHARACTER);
936    pathsToggleButton.setDisable(true);
937    pathsToggleButton.setSelected(false);
938    pathsToggleButton.setMinHeight(24);
939    showPaths.bind(pathsToggleButton.selectedProperty());
940    arrowsToggleButton.addEventHandler(ActionEvent.ACTION, new EventHandler<ActionEvent>() {
941
942      @Override
943      public final void handle(final ActionEvent event) {
944        if (showArrows.get()) {
945          pathsToggleButton.setDisable(false);
946        } else {
947          pathsToggleButton.setSelected(false);
948          pathsToggleButton.setDisable(true);
949        }
950      }
951    });
952    highlightToggleButton.setSelected(false);
953    highlightToggleButton.setMinHeight(24);
954    highlightToggleButton.setGraphic(
955        ImageViewBuilder.create().image(new Image(ConExpFX.class.getResourceAsStream("image/16x16/flag.png"))).build());
956    rowHeaderPane.highlight.bind(highlightToggleButton.selectedProperty());
957    colHeaderPane.highlight.bind(highlightToggleButton.selectedProperty());
958    contextPane.highlight.bind(highlightToggleButton.selectedProperty());
959    arrowsToggleButton.setStyle("-fx-background-radius: 5 0 0 5, 5 0 0 5, 4 0 0 4, 3 0 0 3;");
960    pathsToggleButton.setStyle("-fx-background-radius: 0, 0, 0, 0");
961    highlightToggleButton.setStyle("-fx-background-radius: 0 5 5 0, 0 5 5 0, 0 4 4 0, 0 3 3 0;");
962    HBox showBox = new HBox();
963    showBox.setPadding(new Insets(0d));
964    showBox.getChildren().addAll(arrowsToggleButton, pathsToggleButton, highlightToggleButton);
965    final ToolBar toolBar = new ToolBar();
966    toolBar.getItems().addAll(zoomSlider, showBox);
967    this.setTop(toolBar);
968    toolBar.toFront();
969  }
970
971  protected final void update() {
972    rowHeaderPane.updateContent();
973    colHeaderPane.updateContent();
974    contextPane.updateContent();
975  }
976
977  public final void dehighlight() {
978    rowHeaderPane.dehighlight();
979    colHeaderPane.dehighlight();
980    contextPane.dehighlight();
981    contextPane.highlightConcept.set(false);
982  }
983
984  public final void highlight(final Concept<G, M> concept) {
985    final Collection<Integer> domainIndices = context.rowHeads().indicesOf(concept.extent(), true);
986    final Collection<Integer> codomainIndices = context.colHeads().indicesOf(concept.intent(), true);
987    contextPane.highlightConcept.set(true);
988    rowHeaderPane.highlightConcept(Collections2.transform(domainIndices, new Function<Integer, Integer>() {
989
990      public final Integer apply(final Integer index) {
991        for (Entry<Integer, Integer> entry : contextPane.rowMap.get().entrySet())
992          if (entry.getValue().equals(index))
993            return entry.getKey();
994        return index;
995      }
996    }));
997    colHeaderPane.highlightConcept(Collections2.transform(codomainIndices, new Function<Integer, Integer>() {
998
999      public final Integer apply(final Integer index) {
1000        for (Entry<Integer, Integer> entry : contextPane.columnMap.get().entrySet())
1001          if (entry.getValue().equals(index))
1002            return entry.getKey();
1003        return index;
1004      }
1005    }));
1006  }
1007
1008  public final void highlightImplication(final Implication<M> implication) {
1009
1010  }
1011
1012  public final void addRowDecoration(final G row, final Node decoration) {
1013    rowHeaderPane.addDecoration(row, decoration);
1014  }
1015}