All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.sciss.mellite.gui.impl.MapViewImpl.scala Maven / Gradle / Ivy

/*
 *  MapViewLike.scala
 *  (Mellite)
 *
 *  Copyright (c) 2012-2016 Hanns Holger Rutz. All rights reserved.
 *
 *  This software is published under the GNU General Public License v3+
 *
 *
 *  For further information, please contact Hanns Holger Rutz at
 *  [email protected]
 */

package de.sciss.mellite
package gui
package impl

import java.awt.datatransfer.Transferable
import java.awt.event.MouseEvent
import java.util.EventObject
import javax.swing.TransferHandler.TransferSupport
import javax.swing.table.{AbstractTableModel, DefaultTableCellRenderer, TableCellEditor}
import javax.swing.undo.UndoableEdit
import javax.swing.{AbstractCellEditor, JComponent, JTable, TransferHandler}

import de.sciss.desktop.{OptionPane, UndoManager, Window}
import de.sciss.lucre.stm
import de.sciss.lucre.stm.{Disposable, Obj, TxnLike}
import de.sciss.lucre.swing._
import de.sciss.lucre.swing.impl.ComponentHolder
import de.sciss.lucre.synth.Sys
import de.sciss.model.impl.ModelImpl
import de.sciss.swingplus.DropMode

import scala.annotation.switch
import scala.collection.breakOut
import scala.collection.immutable.{IndexedSeq => Vec}
import scala.concurrent.stm.TMap
import scala.swing.Swing._
import scala.swing.event.TableRowsSelected
import scala.swing.{Component, Label, ScrollPane, Table, TextField}

abstract class MapViewImpl[S <: Sys[S], Repr](list0: Vec[(String, ListObjView[S])])
                              (implicit val cursor: stm.Cursor[S],
                                        val workspace: Workspace[S], val undoManager: UndoManager)
  extends MapView[S, Repr] with ComponentHolder[Component] with ModelImpl[MapView.Update[S, Repr]] {
  impl: Repr =>

  // ---- abstract ----

  protected def observer: Disposable[S#Tx]

  protected def editImport(key: String, value: Obj[S], isInsert: Boolean)(implicit tx: S#Tx): Option[UndoableEdit]

  protected def editRenameKey(before: String, now: String, value: Obj[S])(implicit tx: S#Tx): Option[UndoableEdit]

  protected def guiInit1(scroll: ScrollPane): Unit

  // ---- impl ----

  protected def showKeyOnly: Boolean = false
  protected def keyEditable: Boolean = true

  private[this] var modelEDT = list0

  private[this] var _table: Table = _
  private[this] var _scroll: ScrollPane = _

  private[this] val viewMap = TMap(list0: _*)

  final protected def table : Table      = _table
  final protected def scroll: ScrollPane = _scroll

  final protected def model: Vec[(String, ListObjView[S])] = {
    requireEDT()
    modelEDT
  }

  def dispose()(implicit tx: S#Tx): Unit = {
    import TxnLike.peer
    observer.dispose()
    viewMap.foreach(_._2.dispose())
    viewMap.clear()
  }

  // helper method that executes model updates on the EDT
  private[this] def withRow(key: String)(fun: Int => Unit)(implicit tx: S#Tx): Unit = deferTx {
    import de.sciss.equal.Implicits._
    val row = modelEDT.indexWhere(_._1 === key)
    if (row < 0) {
      warnNoView(key)
    } else {
      fun(row)
    }
  }

  private[this] def mkValueView(key: String, value: Obj[S])(implicit tx: S#Tx): ListObjView[S] = {
    val view = ListObjView(value)
    viewMap.put(key, view)(tx.peer).foreach(_.dispose())
    view.react { implicit tx => {
      case ObjView.Repaint(_) =>
        withRow(key) { row =>
          tableModel.fireTableRowsUpdated(row, row)
        }
    }}
    view
  }

  final protected def attrAdded(key: String, value: Obj[S])(implicit tx: S#Tx): Unit = {
    val view = mkValueView(key, value)
    deferTx {
      val row = modelEDT.size
      modelEDT :+= key -> view
      tableModel.fireTableRowsInserted(row, row)
    }
  }

  final protected def attrRemoved(key: String)(implicit tx: S#Tx): Unit = {
    viewMap.remove(key)(tx.peer).foreach(_.dispose())
    withRow(key) { row =>
      modelEDT = modelEDT.patch(row, Nil, 1)
      tableModel.fireTableRowsDeleted(row, row)
    }
  }

  final protected def attrReplaced(key: String, before: Obj[S], now: Obj[S])(implicit tx: S#Tx): Unit = {
    val view = mkValueView(key, now)
    withRow(key) { row =>
      modelEDT = modelEDT.patch(row, (key -> view) :: Nil, 1)
      tableModel.fireTableRowsUpdated(row, row)
    }
  }

  private[this] def warnNoView(key: String): Unit = println(s"Warning: AttrMapView - no view found for $key")

  private[this] object tableModel extends AbstractTableModel {
    def getRowCount   : Int = modelEDT.size
    def getColumnCount: Int = if (showKeyOnly) 1 else 3

    override def getColumnName(col: Int): String = (col: @switch) match {
      case 0 => "Key"
      case 1 => "Name"
      case 2 => "Value"
    }

    def getValueAt(row /* rowView */: Int, col: Int): AnyRef = {
      // val row = tab.peer.convertRowIndexToModel(rowView)
      (col: @switch) match {
        case 0 => modelEDT(row)._1
        case 1 => modelEDT(row)._2 // .name
        case 2 => modelEDT(row)._2 // .value.asInstanceOf[AnyRef]
      }
    }

    override def getColumnClass(col: Int): Class[_] = (col: @switch) match {
      case 0 => classOf[String]
      case 1 => classOf[ObjView[S]]
      case 2 => classOf[ObjView[S]]
    }

    override def isCellEditable(row: Int, col: Int): Boolean = {
      val res = if (col == 0) keyEditable else modelEDT(row)._2.isEditable
      // println(s"isCellEditable(row = $row, col = $col) -> $res")
      res
    }

    // println(s"setValueAt(value = $value, row = $row, col = $col")
    override def setValueAt(editValue: Any, row: Int, col: Int): Unit = (col: @switch) match {
      case 0 =>
        val (oldKey, view) = modelEDT(row)
        val newKey  = editValue.toString
        if (oldKey != newKey) {
          val editOpt = cursor.step { implicit tx =>
            val value = view.obj
            editRenameKey(before = oldKey, now = newKey, value = value)
          }
          editOpt.foreach(undoManager.add)
        }

      case 2 =>
        val view    = modelEDT(row)._2
        val editOpt = cursor.step { implicit tx => view.tryEdit(editValue) }
        editOpt.foreach(undoManager.add)

      case _ =>
    }
  }

  final protected def guiInit(): Unit = {
    _table = new Table {
      // Table default has idiotic renderer/editor handling
      override lazy val peer: JTable = new JTable /* with Table.JTableMixin */ with SuperMixin
    }
    _table.model     = tableModel
    val jt        = _table.peer
    jt.setAutoCreateRowSorter(true)
    val tcm       = jt.getColumnModel
    val colName   = tcm.getColumn(0)
    colName .setPreferredWidth( 96)

    if (!showKeyOnly) {
      val colTpe    = tcm.getColumn(1)
      val colValue  = tcm.getColumn(2)
      colTpe  .setPreferredWidth( 96)
      colValue.setPreferredWidth(208)

      colTpe.setCellRenderer(new DefaultTableCellRenderer {
        outer =>
        private val wrap = new Label { override lazy val peer = outer }

        override def setValue(value: Any): Unit = value match {
          case view: ObjView[_] =>
            import de.sciss.equal.Implicits._
            wrap.text = if (view.name === "") "" else view.name
            wrap.icon = view.icon
          case _ =>
        }
      })
      colValue.setCellRenderer(new DefaultTableCellRenderer {
        outer =>
        private val wrap = new Label { override lazy val peer = outer }
        override def getTableCellRendererComponent(table: JTable, value: Any, isSelected: Boolean,
                                                   hasFocus: Boolean, row: Int, column: Int): java.awt.Component = {
          super.getTableCellRendererComponent(table, null, isSelected, hasFocus, row, column)
          if (getIcon != null) setIcon(null)
          value match {
            case view: ListObjView[_] => view.configureRenderer(wrap).peer
            case _ => outer
          }
        }
      })
      colValue.setCellEditor(new AbstractCellEditor with TableCellEditor {
        // private var currentValue: Any = null
        private val editor = new TextField(10)

        override def isCellEditable(e: EventObject): Boolean = e match {
          case m: MouseEvent => m.getClickCount >= 2
          case _ => true
        }

        def getCellEditorValue: AnyRef = editor.text // currentValue.asInstanceOf[AnyRef]

        def getTableCellEditorComponent(table: JTable, value: Any, isSelected: Boolean, rowV: Int,
                                        col: Int): java.awt.Component = {
          val row = table.convertRowIndexToModel(rowV)
          val view      = modelEDT(row)._2
          // currentValue  = view.value
          editor.text   = view.value.toString
          editor.peer
        }
      })
    }

    jt.setPreferredScrollableViewportSize((if (showKeyOnly) 130 else 390) -> 160)

    import de.sciss.swingplus.Implicits._
    _table.sort(0)

    jt.setDragEnabled(true)
    jt.setDropMode(DropMode.OnOrInsertRows)
    jt.setTransferHandler(new TransferHandler {
      override def getSourceActions(c: JComponent): Int = TransferHandler.LINK

      override def createTransferable(c: JComponent): Transferable = {
        val sel     = selection
        val trans1 = if (sel.size == 1) {
          val _res = DragAndDrop.Transferable(ListObjView.Flavor) {
            ListObjView.Drag(workspace, sel.head._2)
          }
          _res
        } else null

        trans1
      }

      override def canImport(support: TransferSupport): Boolean = {
        val res = support.isDrop && {
          val dl = support.getDropLocation.asInstanceOf[JTable.DropLocation]
          val locOk = dl.isInsertRow || {
            val viewCol   = dl.getColumn
            val modelCol  = jt.convertColumnIndexToModel(viewCol)
            modelCol >= 1   // should drop on the 'type' or 'value' column
          }
          // println(s"locOk? $locOk")
          val allOk = locOk && support.isDataFlavorSupported(ListObjView.Flavor)
          if (allOk) support.setDropAction(TransferHandler.LINK)
          allOk
        }
        res
      }

      override def importData(support: TransferSupport): Boolean = {
        val res = support.isDrop && {
          val dl        = support.getDropLocation.asInstanceOf[JTable.DropLocation]
          val isInsert  = dl.isInsertRow
          val view      = support.getTransferable.getTransferData(ListObjView.Flavor).asInstanceOf[ListObjView.Drag[S]].view
          val keyOpt = if (isInsert) { // ---- create new entry with key via dialog ----
            queryKey("key")
          } else {          // ---- update value of existing entrywith key via dialog ----
          val rowV  = dl.getRow
            val row   = jt.convertRowIndexToModel(rowV)
            Some(modelEDT(row)._1)
          }
          // println(s"TODO: ${if (isInsert) "insert" else "replace"} $view")

          keyOpt.exists { key =>
            val editOpt = cursor.step { implicit tx =>
              editImport(isInsert = isInsert, key = key, value = view.obj)
            }
            editOpt.foreach(undoManager.add)
            editOpt.isDefined
          }
        }
        res
      }
    })

    _scroll = new ScrollPane(_table)
    _scroll.peer.putClientProperty("styleId", "undecorated")
    _scroll.border = null
    // component     = scroll
    _table.listenTo(_table.selection)
    _table.reactions += {
      case TableRowsSelected(_, _, false) => // note: range is range of _changes_ rows, not current selection
        val sel = selection
        dispatch(MapView.SelectionChanged(impl, sel))
    }
    guiInit1(_scroll)
  }

  final def queryKey(initial: String): Option[String] = {
    val opt   = OptionPane.textInput(message = "Key Name", initial = initial)
    opt.title = "Create Attribute"
    opt.show(Window.find(component))
  }

  final def selection: List[(String, ObjView[S])] = {
    val ind0: List[Int] = _table.selection.rows.map(_table.peer.convertRowIndexToModel)(breakOut)
    val indices = ind0.sorted
    indices.map(modelEDT.apply)
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy