!7 cmodule ConvoPopup { L posts = dm_synchroList(this); int windowWidth = 400, windowHeight = 400; S userInput; S text; bool right; // for form bool popupVisible; Rect popupPosition; transient JChatConvo view; srecord Post(S html, bool right) {} start { componentFieldsToKeep = litset('view); // we clean it up ourselves if (popupVisible) openPopup(); } visual withCenteredButtons(withMargin(jvstack( centerAndEastWithMargin(dm_textField text(), jline(dm_checkBox right(), jbutton("Add post", rEnter { addPost(text, right) }))))), "Show popup", rEnter openPopup, "Clear posts", rEnter clearPosts); void cleanMeUp { JComponent v = view; view = null; disposeFrame(v); } void sendInput { S s = trim(userInput); if (nempty(s)) { addPost(htmlEncode2(s), false); vmBus_send chatUserInput(module(), s); setField(userInput := ""); } } // API void openPopup swing { if (view == null) { setField(popupVisible := true); view = new JChatConvo; for (Post p : cloneList(posts)) view.addPost(p.html, p.right); JComponent content = centerAndSouth( jscroll_trackWidth(view), withMargin(dm_textFieldAndSubmit userInput('sendInput, buttonText := "Send"))); JFrame frame = showFrameInBottomRightCorner(content, windowWidth, windowHeight); setBounds(frame, popupPosition); moveWindowIntoScreen(frame); alwaysOnTop(frame); onFrameClosing(content, r { if (view != null) setField(popupVisible := false) }); onBoundsChange(frame, r { setField(popupPosition := boundsRect(frame)) }); } else activateFrame(view); } void closePopup swing { disposeFrame(view); } void addPost(S html, bool right) swing { posts.add(new Post(html, right)); view?.addPost(html, right); } void clearPosts swing { posts.clear(); view?.clearPosts(); } void reply(S s) { if (empty(s = trim(s))) ret; addPost(htmlEncode2(s), true); } }