> = rows.drop(1).map { tr ->
+ tr.select("td, th").map { td -> td.handleHtmlFormatting() as Any? }.toVector()
+ }.toVector()
+
+ // Find the type of column automatically.
+ val contentRows = rows.drop(1)
+ val columnTypes = (0 until width).map { col ->
+ // Check if all contents of the column (except the header) can be converted to a number.
+ // When that's the case => it's a number column. All other cases, text. Ignoring the Math option
+ // as the table information is most probably something outside of a latex context.
+ if (contentRows.all { it.select("td, th").getOrNull(col)?.text()?.toDoubleOrNull() != null }) {
+ ColumnType.NUMBERS_COLUMN
+ }
+ else ColumnType.TEXT_COLUMN
+ }
+
+ return TableCreationDialogWrapper(
+ columnTypes,
+ TableCreationTableModel(content, header)
+ )
+ }
+
+ /**
+ * Converts // tags to latex formatting commands.
+ * `this` is a HTML Element.
+ */
+ private fun Element.handleHtmlFormatting(): String {
+ val prefix = StringBuilder()
+ val suffix = StringBuilder()
+
+ if (select("b, strong").isNotEmpty()) {
+ prefix.append("\\textbf{")
+ suffix.append("}")
+ }
+ if (select("i, em").isNotEmpty()) {
+ prefix.append("\\textit{")
+ suffix.append("}")
+ }
+ if (select("u").isNotEmpty()) {
+ prefix.append("\\underline{")
+ suffix.append("}")
+ }
+
+ return prefix
+ .append(text())
+ .append(suffix.toString())
+ .toString()
+ }
+}
\ No newline at end of file
diff --git a/src/nl/hannahsten/texifyidea/ui/tablecreationdialog/TableCreationDialogWrapper.kt b/src/nl/hannahsten/texifyidea/ui/tablecreationdialog/TableCreationDialogWrapper.kt
deleted file mode 100644
index 070c12c0d..000000000
--- a/src/nl/hannahsten/texifyidea/ui/tablecreationdialog/TableCreationDialogWrapper.kt
+++ /dev/null
@@ -1,296 +0,0 @@
-package nl.hannahsten.texifyidea.ui.tablecreationdialog
-
-import com.intellij.openapi.actionSystem.AnActionEvent
-import com.intellij.openapi.actionSystem.KeyboardShortcut
-import com.intellij.openapi.actionSystem.ShortcutSet
-import com.intellij.openapi.ui.DialogWrapper
-import com.intellij.openapi.ui.ValidationInfo
-import com.intellij.ui.AnActionButton
-import com.intellij.ui.LayeredIcon
-import com.intellij.ui.ToolbarDecorator
-import com.intellij.ui.components.JBLabel
-import com.intellij.ui.components.JBTextField
-import com.intellij.ui.scale.JBUIScale.scale
-import com.intellij.ui.table.JBTable
-import com.intellij.util.IconUtil
-import nl.hannahsten.texifyidea.action.wizard.table.TableInformation
-import java.awt.*
-import java.awt.event.ActionEvent
-import java.awt.event.KeyEvent
-import javax.swing.*
-import javax.swing.border.EmptyBorder
-
-/**
- * Wrapper that contains the table creation dialog. It validates the form when clicking the OK button.
- *
- * @param columnTypes The types of the columns of the table, see [ColumnType], always start with an empty table.
- * @param tableModel The model of the table, always start with an empty table.
- * @param tableInformation Information about the table that is needed to convert it to latex.
- *
- * UI components that have to be validated when clicking the OK button, i.e., checking if the user entered something.
- * @param table The JTable component that shows the table.
- * @param caption The text field that contains the caption for the table.
- * @param reference The text field that contains the label for the table. It has a default value "tab:" to encourage usage
- * of label conventions.
- *
- * @author Abby Berkers
- */
-class TableCreationDialogWrapper(
- private val columnTypes: MutableList = emptyList().toMutableList(),
- private val tableModel: TableCreationTableModel = TableCreationTableModel(),
- var tableInformation: TableInformation = TableInformation(tableModel, columnTypes, "", ""),
- // Components that have to be validated when clicking the OK button.
- private val table: JTable = JBTable(tableModel),
- private val caption: JBTextField = JBTextField(),
- private val reference: JBTextField = JBTextField("tab:")
-) :
- DialogWrapper(true) {
-
- init {
- // Initialise the dialog, otherwise it shows as a line (i.e., infinitely small) and without any of the elements.
- init()
- title = "Insert table"
- }
-
- /**
- * Add a table column.
- *
- * @param title of the column.
- * @param columnType is the column type of the column.
- */
- @Suppress("KDocUnresolvedReference")
- private val addColumnFun = fun(title: String, columnType: ColumnType, _: Int) {
- // Add the column to the table, with an empty cell for each row (instead of the default null).
- tableModel.addColumn(title, (0 until tableModel.rowCount).map { "" }.toTypedArray())
- // Add the column type to the list of column types.
- columnTypes.add(columnType)
- // If table is currently empty, add one row to this new column.
- if (tableModel.columnCount == 1) tableModel.addRow(arrayOf(""))
- }
-
- /**
- * Edit the table column, i.e., udpate the header title and the column type.
- *
- * @param title is the new title of the header.
- * @param columnType is the index of the column type.
- * @param columnIndex is the index of the edited column in the table, starting at 0.
- */
- @Suppress("KDocUnresolvedReference", "KDocUnresolvedReference")
- private val editColumnFun = fun(title: String, columnType: ColumnType, columnIndex: Int) {
- tableModel.setHeaderName(title, columnIndex)
- // Edit the column type of the edited column.
- columnTypes[columnIndex] = columnType
- tableModel.fireTableStructureChanged()
- }
-
- private fun getEditColumnActionButton(): AnActionButton = object : AnActionButton("Edit column header", addText(IconUtil.getEditIcon(), "C")) {
-
- override fun isEnabled() = table.columnCount > 0
-
- override fun actionPerformed(e: AnActionEvent) {
- if (table.selectedColumn >= 0) {
- TableCreationEditColumnDialog(
- editColumnFun,
- table.selectedColumn,
- table.getColumnName(table.selectedColumn),
- columnTypes[table.selectedColumn]
- )
- }
- }
- }
-
- private fun getAddRowActionButton() = object : AnActionButton("Add Row", addText(IconUtil.getAddIcon(), "R")) {
-
- override fun isEnabled() = table.columnCount > 0
-
- override fun actionPerformed(e: AnActionEvent) {
- tableModel.addEmptyRow()
- }
- }
-
- private fun getRemoveRowActionButton() = object : AnActionButton("Remove Row", addText(IconUtil.getRemoveIcon(), "R")) {
-
- override fun isEnabled() = table.selectedRow > -1
-
- override fun actionPerformed(e: AnActionEvent) {
- tableModel.removeRow(table.selectedRow)
- }
- }
-
- private fun getRemoveColumnActionButton() = object : AnActionButton("Remove Column", addText(IconUtil.getRemoveIcon(), "C")) {
-
- override fun isEnabled() = table.selectedColumn > -1
-
- override fun actionPerformed(e: AnActionEvent) {
- tableModel.removeColumn(table.selectedColumn)
- }
- }
-
- override fun createCenterPanel(): JPanel {
-
- // Decorator that contains the add/remove/edit buttons.
- val decorator = ToolbarDecorator.createDecorator(table)
- .setAddAction {
- TableCreationEditColumnDialog(addColumnFun, tableModel.columnCount)
- }
- .setAddActionName("Add Column")
- .setAddIcon(addText(IconUtil.getAddIcon(), "C"))
- .addExtraAction(getRemoveColumnActionButton())
- .addExtraAction(getEditColumnActionButton())
- .addExtraAction(getAddRowActionButton())
- .addExtraAction(getRemoveRowActionButton().apply { shortcut = ShortcutSet { arrayOf(KeyboardShortcut(KeyStroke.getKeyStroke("DELETE"), null)) } })
- .createPanel()
-
- table.addTabCreatesNewRowAction()
- table.addEnterCreatesNewRowAction()
-
- val captionLabel = JBLabel("Caption:")
- captionLabel.labelFor = caption
-
- val referenceLabel = JBLabel("Label:")
- referenceLabel.labelFor = reference
-
- // Add all elements to the panel view.
- val panel = JPanel()
- panel.apply {
- // Add some air around the elements.
- border = EmptyBorder(8, 8, 8, 8)
- layout = BoxLayout(this, BoxLayout.Y_AXIS)
-
- // Create a panel for the table and its decorator.
- val tablePanel = JPanel()
- tablePanel.apply {
- layout = BorderLayout()
- add(JScrollPane(table), BorderLayout.WEST)
- add(decorator, BorderLayout.EAST)
- }
-
- // Help text
- val helpText = JBLabel("Press tab to go to the next cell or row, press enter to go to the next row.")
- helpText.foreground = Color.GRAY
-
- // Put help text below table
- val tablePanelContainer = JPanel(GridBagLayout())
- val constraints = GridBagConstraints()
- constraints.gridx = 0
- constraints.gridy = GridBagConstraints.RELATIVE
- tablePanelContainer.apply {
- add(tablePanel, constraints)
- add(helpText, constraints)
- }
- add(tablePanelContainer)
-
- // Create a panel for the caption box and its label.
- val captionPanel = JPanel()
- captionPanel.apply {
- layout = BoxLayout(this, BoxLayout.X_AXIS)
- captionLabel.preferredSize = Dimension(80, captionLabel.height)
- add(captionLabel)
- add(caption)
- }
-
- // Create a panel for the label/reference box and its label.
- val referencePanel = JPanel()
- referencePanel.apply {
- layout = BoxLayout(this, BoxLayout.X_AXIS)
- referenceLabel.preferredSize = Dimension(80, referenceLabel.height)
- add(referenceLabel)
- add(reference)
- }
-
- // Actually add all the panels to the main panel.
- // Add some air between components.
- add(Box.createRigidArea(Dimension(0, 8)))
- add(captionPanel)
- add(Box.createRigidArea(Dimension(0, 8)))
- add(referencePanel)
- }
-
- return panel
- }
-
- /**
- * See [IconUtil.addText].
- */
- fun addText(base: Icon, text: String, scale: Float = 7f): Icon? {
- val icon = LayeredIcon(2)
- icon.setIcon(base, 0, SwingConstants.NORTH_WEST)
- icon.setIcon(IconUtil.textToIcon(text, JLabel(), scale(scale)), 1, SwingConstants.SOUTH_EAST)
- return icon
- }
-
- /**
- * When clicking OK, the wrapper will validate the form. This means that the table should at least have a header,
- * there is some text in the caption text field, and the label text field contains more than just "tab:" (or no
- * "tab:" at all, but then it should not be empty).
- */
- override fun doValidate(): ValidationInfo? {
- return if (tableModel.getColumnNames().size == 0) ValidationInfo("Table cannot be empty.", table)
- else if (caption.text.isEmpty()) ValidationInfo("Caption cannot be empty.", caption)
- else if (reference.text.isEmpty() || reference.text == "tab:") ValidationInfo("Label cannot be empty", reference)
- else {
- // 'Save' the current values in the form.
- tableInformation = TableInformation(tableModel, columnTypes, caption.text, reference.text)
- return null
- }
- }
-
- /**
- * Sets the action when pressing TAB on the last cell in the last row to create a new (empty) row and set the
- * selection on the first cell of the new row.
- */
- private fun JTable.addTabCreatesNewRowAction() {
- // Get the key stroke for pressing TAB.
- val keyStroke = KeyStroke.getKeyStroke("TAB")
- val actionKey = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).get(keyStroke)
- // Get the action that currently is under the TAB key.
- val action = actionMap[actionKey]
-
- val actionWrapper = object : AbstractAction() {
- override fun actionPerformed(e: ActionEvent?) {
- val table = this@addTabCreatesNewRowAction
- // When we're in the last column of the last row, add a new row before calling the usual action.
- if (table.selectionModel.leadSelectionIndex == table.rowCount - 1 &&
- table.columnModel.selectionModel.leadSelectionIndex == table.columnCount - 1
- ) {
- tableModel.addEmptyRow()
- }
- // Perform the usual action.
- action.actionPerformed(e)
- }
- }
-
- // Map the new action to the TAB key.
- actionMap.put(actionKey, actionWrapper)
- }
-
- /**
- * Sets the action when pressing ENTER to create a new (empty) row and set the
- * selection on the first cell of the new row.
- *
- * See [addTabCreatesNewRowAction]
- */
- private fun JTable.addEnterCreatesNewRowAction() {
- val keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0)
- getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(keyStroke, "enter")
-
- val actionWrapper = object : AbstractAction() {
- override fun actionPerformed(e: ActionEvent?) {
- tableModel.addEmptyRow()
-
- val keyStrokeTab = KeyStroke.getKeyStroke("TAB")
- val actionKey = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).get(keyStrokeTab)
- // Get the action to go to the next cell
- val nextCellAction = actionMap[actionKey]
-
- // Skip the rest of the cells in the row
- val table = this@addEnterCreatesNewRowAction
- for (i in table.columnModel.selectionModel.leadSelectionIndex until table.columnCount) {
- nextCellAction.actionPerformed(e)
- }
- }
- }
-
- actionMap.put("enter", actionWrapper)
- }
-}
\ No newline at end of file
diff --git a/src/nl/hannahsten/texifyidea/ui/tablecreationdialog/TableCreationEditColumnDialog.kt b/src/nl/hannahsten/texifyidea/ui/tablecreationdialog/TableCreationEditColumnDialog.kt
deleted file mode 100644
index a818fa509..000000000
--- a/src/nl/hannahsten/texifyidea/ui/tablecreationdialog/TableCreationEditColumnDialog.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-package nl.hannahsten.texifyidea.ui.tablecreationdialog
-
-import com.intellij.openapi.ui.DialogBuilder
-import com.intellij.openapi.ui.DialogWrapper
-import com.intellij.ui.components.JBLabel
-import com.intellij.ui.components.JBTextField
-import javax.swing.JComboBox
-import javax.swing.JPanel
-
-/**
- * Dialog to add a new column to the table.
- *
- * @param onOkFunction The function to execute when clicking the OK button.
- * @param editingColumn The index of the column being edited.
- * @param columnName The name of the column that is being edited. Default is the empty string, the title of a column that does
- * not yet exist.
- * @param columnType The [ColumnType] of the column that is being edited. Default is a text column.
- *
- * @author Abby Berkers
- */
-class TableCreationEditColumnDialog(
- private val onOkFunction: (String, ColumnType, Int) -> Unit,
- private val editingColumn: Int,
- private val columnName: String = "",
- private val columnType: ColumnType = ColumnType.TEXT_COLUMN
-) {
-
- init {
- DialogBuilder().apply {
- // Text field for the name of the column, with the old name of the editing column filled in.
- val columnNameField = JBTextField(columnName)
- val columnNameLabel = JBLabel("Column name")
- columnNameLabel.labelFor = columnNameField
-
- // A combobox to select the column type.
- val columnTypeComboBox = JComboBox(ColumnType.values().map { it.displayName }.toTypedArray())
- // Select the old type of the editing column.
- columnTypeComboBox.selectedIndex = ColumnType.values().indexOf(columnType)
- val columnTypeLabel = JBLabel("Column type")
- columnTypeLabel.labelFor = columnTypeComboBox
-
- // Add UI elements.
- val panel = JPanel().apply {
- add(columnNameLabel)
- add(columnNameField)
- add(columnTypeLabel)
- add(columnTypeComboBox)
- }
- setCenterPanel(panel)
- setPreferredFocusComponent(columnNameField)
-
- addOkAction()
- setOkOperation {
- dialogWrapper.close(0)
- }
-
- if (columnName.isBlank()) {
- title("Add column")
- }
- else {
- title("Edit column")
- }
-
- if (show() == DialogWrapper.OK_EXIT_CODE) {
- onOkFunction(columnNameField.text, ColumnType.values()[columnTypeComboBox.selectedIndex], editingColumn)
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/nl/hannahsten/texifyidea/util/Clipboard.kt b/src/nl/hannahsten/texifyidea/util/Clipboard.kt
new file mode 100644
index 000000000..05c8cc1cc
--- /dev/null
+++ b/src/nl/hannahsten/texifyidea/util/Clipboard.kt
@@ -0,0 +1,20 @@
+package nl.hannahsten.texifyidea.util
+
+/**
+ * @author Hannah Schellekens
+ */
+object Clipboard {
+
+ /**
+ * Takes the complete clipboard contents (must have an html data flavor) and extracts the html (thus dropping
+ * the header).
+ *
+ * @return null when it could not find html
+ */
+ @JvmStatic
+ fun extractHtmlFromClipboard(clipboardContents: String): String? {
+ return clipboardContents.indexOf("= 0 }
+ ?.let { clipboardContents.substring(it) }
+ }
+}
\ No newline at end of file
diff --git a/src/nl/hannahsten/texifyidea/util/Collections.kt b/src/nl/hannahsten/texifyidea/util/Collections.kt
index 3f080ce27..60d7ecc58 100644
--- a/src/nl/hannahsten/texifyidea/util/Collections.kt
+++ b/src/nl/hannahsten/texifyidea/util/Collections.kt
@@ -147,4 +147,9 @@ fun Stream.set(): Set = this.mutableSet()
/**
* Collects stream to [MutableSet]
*/
-fun Stream.mutableSet(): MutableSet = this.collect(Collectors.toSet())
\ No newline at end of file
+fun Stream.mutableSet(): MutableSet = this.collect(Collectors.toSet())
+
+/**
+ * Converts the collection to a vector.
+ */
+fun Collection.toVector() = Vector(this)
\ No newline at end of file
diff --git a/src/nl/hannahsten/texifyidea/util/UserInterface.kt b/src/nl/hannahsten/texifyidea/util/UserInterface.kt
index 54bfb1764..1db531e70 100644
--- a/src/nl/hannahsten/texifyidea/util/UserInterface.kt
+++ b/src/nl/hannahsten/texifyidea/util/UserInterface.kt
@@ -6,9 +6,15 @@ import com.intellij.openapi.ui.popup.Balloon
import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.openapi.wm.WindowManager
import com.intellij.ui.awt.RelativePoint
+import com.intellij.ui.components.JBLabel
+import java.awt.BorderLayout
+import java.awt.Dimension
import java.awt.event.KeyAdapter
import java.awt.event.KeyEvent
import java.awt.event.KeyListener
+import javax.swing.JComponent
+import javax.swing.JPanel
+import javax.swing.border.EmptyBorder
import javax.swing.event.DocumentEvent
import javax.swing.event.DocumentListener
import javax.swing.text.JTextComponent
@@ -130,4 +136,42 @@ fun JTextComponent.setInputFilter(allowedCharacters: Set) = addKeyTypedLis
if (it.keyChar !in allowedCharacters) {
it.consume()
}
+}
+
+/**
+ * Adds a component to the panel with a label before it.
+ *
+ * @param component
+ * The component to add to the panel.
+ * @param description
+ * The label to put before the component.
+ * @param labelWidth
+ * The fixed label width, or `null` to use the label's inherent size.
+ */
+fun JPanel.addLabeledComponent(
+ component: JComponent,
+ description: String,
+ labelWidth: Int? = null,
+ leftPadding: Int = 16
+): JPanel {
+ // Uses a border layout with West for the label and Center for the control itself.
+ // East is reserved for suffix elements.
+ val pane = JPanel(BorderLayout()).apply {
+ val label = JBLabel(description).apply {
+ // Left padding.
+ border = EmptyBorder(0, leftPadding, 0, 0)
+
+ // Custom width if specified.
+ labelWidth?.let {
+ preferredSize = Dimension(it, height)
+ }
+
+ // Align top.
+ alignmentY = 0.0f
+ }
+ add(label, BorderLayout.WEST)
+ add(component, BorderLayout.CENTER)
+ }
+ add(pane)
+ return pane
}
\ No newline at end of file
|