From 125fcb51b3a0aeb4f8a6d5b469d77f24b847d64e Mon Sep 17 00:00:00 2001 From: Rohan Sircar Date: Tue, 26 May 2020 12:59:04 +0530 Subject: [PATCH] Added custom cell factory --- .../wow/doge/chatto/control/MessageBox.scala | 42 +++- .../chatto/controller/ChatController.scala | 205 +++++++----------- .../chatto/controller/LoginController.scala | 22 +- 3 files changed, 119 insertions(+), 150 deletions(-) diff --git a/src/main/scala/wow/doge/chatto/control/MessageBox.scala b/src/main/scala/wow/doge/chatto/control/MessageBox.scala index 02579e4..3ceda6f 100644 --- a/src/main/scala/wow/doge/chatto/control/MessageBox.scala +++ b/src/main/scala/wow/doge/chatto/control/MessageBox.scala @@ -5,6 +5,44 @@ import javafx.scene.control.Label import scalafx.Includes._ import wow.doge.chatto.controller.ChatData import com.sfxcode.sapphire.core.value.FXBean +import wow.doge.chatto.controller.Message +import com.sandec.mdfx.MDFXNode +import javafx.geometry.Pos +import javafx.scene.layout.Priority -class MessageBox(val sender: String, val receiver: String, val message: String) - extends HBox() {} +class MessageBox() extends HBox() { + import MessageBox._ + private var mdfxNode = new MDFXNode(""); + mdfxNode + .getStylesheets() + .add(markdownStyleSheet) + + setAlignment(Pos.CENTER_RIGHT) + maxWidth(500) + this.hgrow = Priority.ALWAYS + this.vgrow = Priority.ALWAYS + this.children ++= Seq(mdfxNode) + this.fillHeight = true + + def setItem(message: Message): Unit = { + // val index = this.children.indexOf(mdfxNode) + val mdfxNode2 = new MDFXNode(message.message) + mdfxNode2 + .getStylesheets() + .add(markdownStyleSheet) + mdfxNode2.setMaxWidth(500) + mdfxNode2.vgrow = Priority.ALWAYS + mdfxNode2.setAlignment(Pos.CENTER) + mdfxNode2.styleClass = Seq("chat-message-box") + + this.children -= mdfxNode + this.children += mdfxNode2 + + mdfxNode = mdfxNode2 + // this.children.updated(index, mdfxNode2) + } +} +object MessageBox { + lazy val markdownStyleSheet = + getClass().getResource("/styles/markdown.css").toExternalForm() +} diff --git a/src/main/scala/wow/doge/chatto/controller/ChatController.scala b/src/main/scala/wow/doge/chatto/controller/ChatController.scala index 06a5db2..5deffa2 100644 --- a/src/main/scala/wow/doge/chatto/controller/ChatController.scala +++ b/src/main/scala/wow/doge/chatto/controller/ChatController.scala @@ -61,6 +61,8 @@ import javafx.scene.input.DataFormat import wow.doge.chatto.control.MessageBox import javafx.scene.control.SelectionMode import scalafx.beans.property.BooleanProperty +import javafx.scene.control.ListCell +import java.time.Instant class ChatController @Inject() ( userService: UserService, @@ -76,14 +78,14 @@ class ChatController @Inject() ( // @FXML private var chatTextArea: TextArea = _ @FXML private var chatInput: TextArea = _ @FXML private var usersVBox: VBox = _ - @FXML var usersListView: JFXListView[UserBox2] = _ - @FXML var chatListView: JFXListView[MessageBox] = _ + @FXML var usersListView: JFXListView[ActiveUser] = _ + @FXML var chatListView: JFXListView[Message] = _ @FXML private var curUsr: Label = _ @FXML private var lastActiveLabel: Label = _ @FXML private var isOnlineLabel: Label = _ @FXML private var selectedUserBox: HBox = _ - private val usersBuffer = ObservableBuffer.empty[UserBox2] + private val usersBuffer = ObservableBuffer.empty[ActiveUser] private val usersListProperty = BufferProperty(usersBuffer) /** @@ -91,7 +93,7 @@ class ChatController @Inject() ( * Synchronized with the internal users list property. * Attemping to modify the internal list will throw an exception */ - val usersListROProp: ReadOnlyListProperty[UserBox2] = BufferProperty( + val usersListROProp: ReadOnlyListProperty[ActiveUser] = BufferProperty( FXCollections.unmodifiableObservableList(usersListProperty()) ) @@ -103,7 +105,9 @@ class ChatController @Inject() ( super.didGainVisibilityFirstTime() this.stage.resizable = true chatMainPane.hgrow = Priority.ALWAYS + chatListView.selectionModel().selectionMode = SelectionMode.MULTIPLE + chatListView.setCellFactory(_ => new ChatListCell()) chatDataAdapter.set(FXBean(ChatData.empty)) @@ -114,6 +118,8 @@ class ChatController @Inject() ( .isNull() }) + // usersListView2.setCellFactory(lv => {}) + // curUserKeys.add("content", "${_self.data().content()}") val chatDataAdapterKeys = KeyBindings() chatDataAdapterKeys.add("currentUser", "${_self.userName()}") @@ -134,7 +140,7 @@ class ChatController @Inject() ( .addListener((_, _, newValue) => { Option(newValue).foreach(nv => { val maybeCDP = chatDataStore - .get(nv.username) + .get(nv.userName) val chatDataBean = maybeCDP .map(_.bean) .getOrElse { @@ -143,9 +149,9 @@ class ChatController @Inject() ( } chatDataAdapter.set(chatDataBean) maybeCDP.foreach(cdp => { - // lastActiveLabel.text <== cdp.lastActive + lastActiveLabel.text <== cdp.lastActive isOnlineLabel.text <== cdp.isActive.asString() - chatListView.items <== cdp.messageBubbles + chatListView.items <== cdp.messages }) }) }) @@ -155,32 +161,12 @@ class ChatController @Inject() ( new KeyCodeCombination(KeyCode.C, KeyCombination.ControlDown) copyMessageMenuItem.onAction = _ => { val content = new ClipboardContent() - val x = chatListView.getSelectionModel().getSelectedItems().map(_.message) - // chatListView.selectionModel().selectedItem().message - Option(x).foreach(message => { - content.putString(message.mkString("\n")) - Clipboard.getSystemClipboard().setContent(content) - }) - // val message = cdp.messageList(selectedIndex) - - // val maybeCDP = chatDataStore.get(chatDataAdapter.get.bean.userName) - // maybeCDP.foreach(cdp => { - // // val message = Option(cdp.messageList().get(selectedIndex)).toRight { - // // "Unexpected error - message not found" - // // } - // // message.map(msg => { - // // // content.putString(msg) - // // // clipboard.setContent(content) - // // // val content = clipboard.content - // // // content.putString(msg) - // // // clipboard.content = content - // // clipboard.putString(msg) - // // }) - // // message.left.map(err => logger.error(err)) - // }) + val messages = chatListView.selectionModel().selectedItems.map(_.message) + content.putString(messages.mkString("\n")) + Clipboard.getSystemClipboard().setContent(content) } - val chatListMenu = new ContextMenu() + val chatListMenu = new ContextMenu() chatListMenu.items += copyMessageMenuItem chatListView.contextMenu = chatListMenu @@ -191,19 +177,18 @@ class ChatController @Inject() ( submitButton.disable <== validator.containsErrorsProperty() submitButton.onAction = (e) => { - // val msgBox = ChatDataProperty.createMdMessageBox2(chatInput.text()) - // chatListView.items() += msgBox if (!chatInput.text().equals("") && !chatInput.text().equals(" ") && !chatInput.text().equals("\n")) { val maybeCDP = chatDataStore.get(chatDataAdapter.get.bean.userName) maybeCDP.foreach(cdp => { - cdp.messageBubbles += ChatDataProperty.createMdMessageBox3( - appDataHandler.appData.credentials.username, - cdp.username(), - chatInput.text() - ) + // cdp.messageBubbles += ChatDataProperty.createMdMessageBox3( + // appDataHandler.appData.credentials.username, + // cdp.username(), + // chatInput.text() + // ) + cdp.messages += Message.empty.copy(message = chatInput.text()) }) } } @@ -236,49 +221,44 @@ class ChatController @Inject() ( val maybeUserBoxes = maybeActiveUsers.map(users => { users.map(user => { val chatData = - ChatData(user.userName, user, ObservableBuffer.empty[String]) + ChatData(user.userName, user, ObservableBuffer.empty[Message]) chatDataStore.put(user.userName, new ChatDataProperty(chatData)) new UserBox2(user.userName, chatData) { this.styleClass ++= Seq("text-white") } }) }) - val messageBox = ChatDataProperty.createMdMessageBox2( - """**Hello world qefwew yeeehay bwergqwevqcqe** - |**Hello world qefwew yeeehay bwergqwevqcqe** - | - | Hello World - """.stripMargin - ) + // val messageBox = ChatDataProperty.createMdMessageBox2( + // """**Hello world qefwew yeeehay bwergqwevqcqe** + // |**Hello world qefwew yeeehay bwergqwevqcqe** + // | + // | Hello World + // """.stripMargin + // ) onFX { - maybeUserBoxes.foreach(userBoxes => { - usersBuffer ++= userBoxes - }) + // maybeUserBoxes.foreach(userBoxes => { + // usersBuffer ++= userBoxes + // }) + maybeActiveUsers.foreach(users => usersBuffer ++= users) chatListView.items() ++= Seq( - messageBox, - ChatDataProperty.createMdMessageBox2("hello"), - ChatDataProperty.createMdMessageBox2( - """ 1. Hello world qefwew yeeehay bwergqwevqcqe - |1. Hello world qefwew yeeehay bwergqwevqcqe - |1. Hello world qefwew yeeehay bwergqwevqcqe - |1. Hello world qefwew yeeehay bwergqwevqcqe""".stripMargin - ) + // messageBox, + // ChatDataProperty.createMdMessageBox2("hello"), + // ChatDataProperty.createMdMessageBox2( + // """ 1. Hello world qefwew yeeehay bwergqwevqcqe + // |1. Hello world qefwew yeeehay bwergqwevqcqe + // |1. Hello world qefwew yeeehay bwergqwevqcqe + // |1. Hello world qefwew yeeehay bwergqwevqcqe""".stripMargin + // ) ) - // .map(node => { - // node.prefWidthProperty <== (chatListView.prefWidthProperty - 200) - // node - // }) - // JFXSmoothScroll.smoothScrollingListView(chatListView, 0.1) } chatDataStore .map { case (key, value) => value } .foreach(cdp => { - cdp.messageBubbles ++= Seq( - ChatDataProperty.createMdMessageBox2("hi"), - ChatDataProperty.createMdMessageBox2("hello"), - ChatDataProperty.createMdMessageBox2("bye") + cdp.messages ++= Seq( + Message.empty.copy(message = "hi"), + Message.empty.copy(message = "hello"), + Message.empty.copy(message = "bye") ) - // .map(ChatDataProperty.createMdMessageBox) }) } } @@ -304,21 +284,12 @@ class ChatController @Inject() ( this.stage.maximized = false applicationController.logout() } - - implicit class MyClipboardExtension(clipboard: Clipboard) { - def putString(string: String) = { - // val content = Option(clipboard.getContent(DataFormat.PLAIN_TEXT)) - // .getOrElse(new ClipboardContent()) - // content.putString(string) - // clipboard.setContent(content) - } - } } final case class ChatData( userName: String, activeUser: ActiveUser, - messages: ObservableBuffer[String] + messages: ObservableBuffer[Message] ) { lazy val lastActiveString = activeUser.lastActive @@ -328,69 +299,33 @@ final case class ChatData( } object ChatData { def empty = { - ChatData("empty", ActiveUser.empty, ObservableBuffer.empty[String]) + ChatData("empty", ActiveUser.empty, ObservableBuffer.empty[Message]) } } class ChatDataProperty(chatData: ChatData) { - import ChatDataProperty._ val bean = FXBean(chatData) val username = bean.getStringProperty("userName") val isActive = bean.getBooleanProperty("activeUser.online") val lastActive = bean.getStringProperty("lastActiveString") - lazy val messageBubbles = BufferProperty( - chatData.messages.map(ChatDataProperty.createMdMessageBox2) - ) - def messageList = messageBubbles().map(_.message) - // lazy val messages = messagesBubbleProperty - // .get() - // .map(_.messageText) + val messages = BufferProperty(chatData.messages) } -object ChatDataProperty { + +final case class Message( + fromUser: String, + toUser: String, + message: String, + messageTime: Instant +) +object Message { lazy val markdownStyleSheet = getClass().getResource("/styles/markdown.css").toExternalForm() - def createMdMessageBox(mdfxText: String) = { - val mdfxNode = new BubbledMDFXNode(mdfxText); - mdfxNode - .getStylesheets() - .add(markdownStyleSheet); - mdfxNode.setBubbleSpec(BubbleSpec.FACE_RIGHT_CENTER); - mdfxNode.setBackground( - new Background(new BackgroundFill(Color.LIGHTSTEELBLUE, null, null)) - ); - val box = new HBox(); - mdfxNode.setMinWidth(100.0); - box.setAlignment(Pos.TOP_RIGHT); - box.children += mdfxNode; - box - } + def empty = Message("", "", "", Instant.MIN) - def createMdMessageBox2(mdfxText: String) = { - val mdfxNode = new MDFXNode(mdfxText); - mdfxNode - .getStylesheets() - .add(markdownStyleSheet) - mdfxNode.setMaxWidth(500) - mdfxNode.vgrow = Priority.ALWAYS - mdfxNode.setAlignment(Pos.CENTER) - mdfxNode.styleClass = Seq("chat-message-box") - - val box = new MessageBox("", "", mdfxText) - box.setAlignment(Pos.CENTER_RIGHT) - // box.maxWidth(500) - box.hgrow = Priority.ALWAYS - box.vgrow = Priority.ALWAYS - box.children ++= Seq(mdfxNode) - box.fillHeight = true - box - } - - def createMdMessageBox3( - sender: String, - receiver: String, - mdfxText: String + def createMdMessageBox( + message: Message ) = { - val mdfxNode = new MDFXNode(mdfxText); + val mdfxNode = new MDFXNode(message.message); mdfxNode .getStylesheets() .add(markdownStyleSheet) @@ -399,7 +334,7 @@ object ChatDataProperty { mdfxNode.setAlignment(Pos.CENTER) mdfxNode.styleClass = Seq("chat-message-box") - val box = new MessageBox(sender, receiver, mdfxText) + val box = new HBox() box.setAlignment(Pos.CENTER_RIGHT) // box.maxWidth(500) box.hgrow = Priority.ALWAYS @@ -408,5 +343,19 @@ object ChatDataProperty { box.fillHeight = true box } +} +final class ChatListCell extends ListCell[Message] { + private val messageBox = new MessageBox() + override def updateItem(item: Message, empty: Boolean): Unit = { + super.updateItem(item, empty) + if (empty || item == null) { + setText(null) + setGraphic(null) + } else { + // messageBox.setItem(item) + // setGraphic(messageBox) + setGraphic(Message.createMdMessageBox(item)) + } + } } diff --git a/src/main/scala/wow/doge/chatto/controller/LoginController.scala b/src/main/scala/wow/doge/chatto/controller/LoginController.scala index b06cc39..264ada8 100644 --- a/src/main/scala/wow/doge/chatto/controller/LoginController.scala +++ b/src/main/scala/wow/doge/chatto/controller/LoginController.scala @@ -7,7 +7,6 @@ import com.jfoenix.controls.JFXButton import com.jfoenix.controls.JFXTextField import com.jfoenix.controls.JFXPasswordField import scalafx.Includes._ -// import scalafx.application.Platform import scalafx.event.ActionEvent import com.sfxcode.sapphire.core.value.KeyBindings import scalafx.scene.layout.VBox @@ -54,25 +53,6 @@ class LoginController @Inject() ( override def didGainVisibilityFirstTime(): Unit = { super.didGainVisibilityFirstTime() this.stage.resizable = false - // usernameTextField.requestFocus() - - // submitButton.setOnAction(actionLogin) - - val bindings = KeyBindings("username", "password") - // Expression Binding Example - // bindings.add( - // "usernameTextField", - // "${sf:i18n('personText', _self.usernameTextField(), _self.passwordTextField())})" - // ) - - val box = new VBox() - val adapter = FXBeanAdapter[Person](this) - val bean = FXBean[Person](Person("0", "username", "password")) - adapter.addBindings(bindings) - adapter.set(bean) - // adapter.addIntConverter("age") - // adapter.hasBeanProperty - // adapter.revert() Array(usernameTextField, passwordTextField, submitButton) .foreach(_.onKeyPressed = (keyEvent) => { @@ -83,6 +63,8 @@ class LoginController @Inject() ( override def didGainVisibility(): Unit = { usernameTextField.requestFocus() + usernameTextField.text() = "username" + passwordTextField.text() = "password" } def actionLogin() = {