diff --git a/build.sbt b/build.sbt index 22f7e97..0c2d9b1 100644 --- a/build.sbt +++ b/build.sbt @@ -7,3 +7,5 @@ organization := "io.atal" scalaVersion := "2.11.4" libraryDependencies += "org.scalatest" % "scalatest_2.11" % "2.2.1" % "test" + +libraryDependencies += "org.scala-lang" % "scala-swing" % "2.11+" diff --git a/src/main/scala/io/atal/butterfly/Buffer.scala b/src/main/scala/io/atal/butterfly/Buffer.scala index c50f7c2..90d1d34 100644 --- a/src/main/scala/io/atal/butterfly/Buffer.scala +++ b/src/main/scala/io/atal/butterfly/Buffer.scala @@ -10,7 +10,7 @@ class Buffer(var content: String) { /** Return the content as an array */ - def lines: Array[String] = content.split("\n") + def lines: Array[String] = content.split("\n", -1) /** Return a selected substring in the buffer * diff --git a/src/main/scala/io/atal/butterfly/Editor.scala b/src/main/scala/io/atal/butterfly/Editor.scala index 344efcc..c513f82 100644 --- a/src/main/scala/io/atal/butterfly/Editor.scala +++ b/src/main/scala/io/atal/butterfly/Editor.scala @@ -25,7 +25,7 @@ class Editor(var buffer: Buffer = new Buffer("")) { def write(text: String): Unit = { for (cursor <- cursors) { buffer.insert(text, cursor.position) - cursor.moveRight(text.length) + moveCursorRight(cursor, text.length) } } @@ -109,6 +109,18 @@ class Editor(var buffer: Buffer = new Buffer("")) { /** Clear all selections */ def clearSelection: Unit = selections = List() + + + /** Return the position of the cursor in the whole string + * + * @param cursor The cursor that we want + * @retutrn The position in the buffer + */ + def getIndexPosition(cursor: Cursor): Int = cursor.position match { + case (0, y) => y + case (x, 0) => buffer.lines(x-1).length + 1 + getIndexPosition(new Cursor(x-1, 0)) + case (x, y) => y + getIndexPosition(new Cursor(x, 0)) + } /** Move up all cursors * diff --git a/src/main/scala/io/atal/butterfly/EditorManager.scala b/src/main/scala/io/atal/butterfly/EditorManager.scala new file mode 100644 index 0000000..755612f --- /dev/null +++ b/src/main/scala/io/atal/butterfly/EditorManager.scala @@ -0,0 +1,79 @@ +package io.atal.butterfly + +/** A manager that allow to use multiple editor + * Call function of Editor, and manage the focus upon editors + * Manage the clipboard + */ +class EditorManager { + var _editors: List[Editor] = List() + var _clipboard: Clipboard = new Clipboard() + var _currentEditor: Option[Editor] = None + + def editors: List[Editor] = _editors + + def clipboard: Clipboard = _clipboard + + def currentEditor: Option[Editor] = _currentEditor + + def currentEditor_=(editor: Editor) = _currentEditor = Some(editor) + + /** Open a new Editor + * + * @TODO Add a param files to open a file text + */ + def openEditor: Unit = { + _editors = new Editor() :: _editors + _currentEditor = Some(_editors.head) + } + + /** Close an Editor + * + * @param editor The editor to close + * @TODO Save the editor + */ + def closeEditor(editor: Editor): Unit = { + _editors = _editors.diff(List(editor)) + if(_currentEditor == Some(editor)) { + _currentEditor = Some(_editors.head) + } + } + + /** Write text into the current Editor + * + * Call the write method of Editor + * @param text The text to insert + */ + def write(text: String): Unit = _currentEditor match { + case Some(editor) => editor.write(text) + case None => Unit + } + + /** Erase text from the current Editor + * + * Call the erase method of Editor + */ + def erase: Unit = _currentEditor match { + case Some(editor) => editor.erase + case None => Unit + } + + /** Erase all the selections from the current Editor + * + * Call the eraseSelection method from Editor + */ + def eraseSelection: Unit = _currentEditor match { + case Some(editor) => editor.eraseSelection + case None => Unit + } + + /** Get contents from the selections of the current Editor + * + * Call the getSelectionContent method from Editor + * @return String The text from the selection (or an empty string if there is no current editor) + */ + def getSelectionContent: String = _currentEditor match { + case Some(editor) => editor.getSelectionContent + case None => "" + } + +} diff --git a/src/main/scala/io/atal/butterfly/Interface.scala b/src/main/scala/io/atal/butterfly/Interface.scala new file mode 100644 index 0000000..12f3471 --- /dev/null +++ b/src/main/scala/io/atal/butterfly/Interface.scala @@ -0,0 +1,89 @@ +package io.atal.butterfly + +import scala.swing._ +import scala.swing.event._ + +/** + * A simple swing demo. + */ +object HelloWorld extends SimpleSwingApplication { + def top = new MainFrame { + title = "Hello, World!" + + var editorManager = new EditorManager + editorManager.openEditor + + var current: Editor = editorManager.currentEditor.get + var isSpec = false + + object editor extends EditorPane { + text = current.buffer.content + preferredSize = new Dimension(1000,500) + + editable = false + caret.visible = true + + focusable = true + requestFocus + + listenTo(keys) + reactions += { + /** + * KeyPressed and KeyTyped are too different event, both sent everytime. + * KeyPressed allow to do any action for a key (those which are non buguy (all accents)), for exemple erase for backspace + * KeyTyped allow to capture the character generate with a key (taking account shift, alt, ...) => no buguy accent, but buguy char for BackSpace + * It doesn't recognize a key, so no filter :( + */ + + case KeyPressed(_, Key.BackSpace, _, _) => { + isSpec = true + current.erase + updateLabel + } + + case KeyPressed(_, Key.Left, _, _) => { + isSpec = true + current.moveCursorsLeft() + } + + case KeyPressed(_, Key.Right, _, _) => { + isSpec = true + current.moveCursorsRight() + } + + case KeyPressed(_, Key.Up, _, _) => { + isSpec = true + current.moveCursorsUp() + } + + case KeyPressed(_, Key.Down, _, _) => { + isSpec = true + current.moveCursorsDown() + } + + case KeyPressed(_, x, _, _) => isSpec = false //Allow for non-specified KeyPressed (like Key.BackSpace) to be match with KeyTyped. It's ugly. Better way ? + + case KeyTyped(_, y, _, _) => { + if(!isSpec) { + current.write(y.toString) + updateLabel + } + } + } + } + + contents = new FlowPanel { + contents.append(editor) + border = Swing.EmptyBorder(10, 10, 10, 10) + + + } + + def updateLabel: Unit = { + editor.text = current.buffer.content + editor.caret.position = current.getIndexPosition(current.cursors.head) + } + } + +} + diff --git a/src/test/scala/io/atal/butterfly/EditorManagerTest.scala b/src/test/scala/io/atal/butterfly/EditorManagerTest.scala new file mode 100644 index 0000000..04d3f85 --- /dev/null +++ b/src/test/scala/io/atal/butterfly/EditorManagerTest.scala @@ -0,0 +1,55 @@ +package io.atal.butterfly + +import org.scalatest._ +import Matchers._ + +/** EditorManager unit tests + */ +class EditorManagerTest extends FlatSpec { + + "The EditorManager accessor and mutator" should "be as expected" in { + val editorManager = new EditorManager + + assert(editorManager.editors == List()) + assert(editorManager.currentEditor == None) + } + + "The EditorManager openEditor method" should "add a new editor and move the current editor to it" in { + val editorManager = new EditorManager + + editorManager.editors should have length 0 + + editorManager.openEditor + + editorManager.editors should have length 1 + assert(editorManager.currentEditor == Some(editorManager.editors.head)) + } + + "The EditorManager closeEditor method" should "remove the editor and potentially move the current editor" in { + val editorManager = new EditorManager + + editorManager.openEditor + editorManager.openEditor + + editorManager.editors should have length 2 + assert(editorManager.currentEditor == Some(editorManager.editors.head)) + + var old_editors = editorManager.editors + + editorManager.closeEditor(editorManager.editors.head) + assert(editorManager.editors == old_editors.tail) + assert(editorManager.currentEditor == Some(editorManager.editors.head)) + + editorManager.openEditor + + editorManager.editors should have length 2 + assert(editorManager.currentEditor == Some(editorManager.editors.head)) + + old_editors = editorManager.editors + + editorManager.closeEditor(editorManager.editors.tail.head) + assert(editorManager.editors == old_editors.head :: old_editors.tail.tail) + assert(editorManager.currentEditor == Some(editorManager.editors.head)) + } + +} diff --git a/src/test/scala/io/atal/butterfly/EditorTest.scala b/src/test/scala/io/atal/butterfly/EditorTest.scala index e0e4ca0..0bb612b 100644 --- a/src/test/scala/io/atal/butterfly/EditorTest.scala +++ b/src/test/scala/io/atal/butterfly/EditorTest.scala @@ -105,6 +105,16 @@ class EditorTest extends FlatSpec { editor.selections should have length 0 } + + "The Editor getIndexPosition method" should "actually return the index position of the cursor" in { + val editor = new Editor + val cursor = new Cursor((1,3)) + + editor.buffer.content = "Hello\nthe world" + + assert(editor.getIndexPosition(cursor) == 9) + + } "The Editor write method" should "write the given text at all cursors position" in { val editor = new Editor