wip on profile viewer and fetching full profiles

dbus-notify
Zlatin Balevsky 2022-05-31 20:01:16 +01:00
parent fec578efa4
commit 388cad0b5d
No known key found for this signature in database
GPG Key ID: A72832072D525E41
15 changed files with 445 additions and 16 deletions

View File

@ -10,6 +10,9 @@ import com.muwire.core.messenger.UIFolderCreateEvent
import com.muwire.core.messenger.UIFolderDeleteEvent
import com.muwire.core.messenger.UIMessageMovedEvent
import com.muwire.core.profile.MWProfile
import com.muwire.core.profile.MWProfileFetcher
import com.muwire.core.profile.MWProfileHeader
import com.muwire.core.profile.UIProfileFetchEvent
import com.muwire.core.update.AutoUpdater
import java.nio.charset.StandardCharsets
@ -290,6 +293,9 @@ public class Core {
} else
log.info("no profile exists for ${me.getHumanReadableName()}")
Supplier<MWProfile> profileSupplier = this::getMyProfile
Supplier<MWProfileHeader> profileHeaderSupplier = {getMyProfile()?.getHeader()} as Supplier
eventBus = new EventBus()
log.info("initializing i2p connector")
@ -430,7 +436,7 @@ public class Core {
eventBus.register(UIFeedUpdateEvent.class, feedClient)
log.info "initializing results sender"
ResultsSender resultsSender = new ResultsSender(eventBus, i2pConnector, me, { getMyProfile()?.getHeader() } as Supplier,
ResultsSender resultsSender = new ResultsSender(eventBus, i2pConnector, me, profileHeaderSupplier,
props, certificateManager, chatServer, collectionManager)
log.info "initializing search manager"
@ -480,7 +486,7 @@ public class Core {
I2PAcceptor i2pAcceptor = new I2PAcceptor(i2pConnector::getSocketManager)
eventBus.register(RouterConnectedEvent.class, i2pAcceptor)
eventBus.register(RouterDisconnectedEvent.class, i2pAcceptor)
connectionAcceptor = new ConnectionAcceptor(eventBus, me, connectionManager, props,
connectionAcceptor = new ConnectionAcceptor(eventBus, me, profileSupplier, connectionManager, props,
i2pAcceptor, hostCache, trustService, searchManager, uploadManager, fileManager, connectionEstablisher,
certificateManager, chatServer, collectionManager, isVisible)
@ -493,6 +499,10 @@ public class Core {
BrowseManager browseManager = new BrowseManager(i2pConnector, eventBus, me)
eventBus.register(UIBrowseEvent.class, browseManager)
log.info("initializing profile fetcher")
MWProfileFetcher profileFetcher = new MWProfileFetcher(i2pConnector, eventBus, me, profileHeaderSupplier)
eventBus.register(UIProfileFetchEvent.class, profileFetcher)
log.info("initializing watched directory converter")
watchedDirectoryConverter = new WatchedDirectoryConverter(this)

View File

@ -1,13 +1,15 @@
package com.muwire.core.connection
import com.muwire.core.profile.MWProfile
import com.muwire.core.profile.MWProfileHeader
import net.i2p.I2PException
import net.i2p.data.ByteArray
import java.nio.charset.StandardCharsets
import java.nio.file.attribute.DosFileAttributes
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.function.BiPredicate
import java.util.function.Supplier
import java.util.logging.Level
import java.util.zip.DeflaterOutputStream
import java.util.zip.GZIPInputStream
@ -54,6 +56,7 @@ class ConnectionAcceptor {
final EventBus eventBus
final Persona me
final Supplier<MWProfile> myProfile
final UltrapeerConnectionManager manager
final MuWireSettings settings
final I2PAcceptor acceptor
@ -75,13 +78,14 @@ class ConnectionAcceptor {
volatile int browsed
ConnectionAcceptor(EventBus eventBus, Persona me, UltrapeerConnectionManager manager,
ConnectionAcceptor(EventBus eventBus, Persona me, Supplier<MWProfile> myProfile, UltrapeerConnectionManager manager,
MuWireSettings settings, I2PAcceptor acceptor, HostCache hostCache,
TrustService trustService, SearchManager searchManager, UploadManager uploadManager,
FileManager fileManager, ConnectionEstablisher establisher, CertificateManager certificateManager,
ChatServer chatServer, CollectionManager collectionManager, BiPredicate<File,Persona> isVisible) {
this.eventBus = eventBus
this.me = me
this.myProfile = myProfile
this.manager = manager
this.settings = settings
this.acceptor = acceptor
@ -188,6 +192,8 @@ class ConnectionAcceptor {
case (byte)'L':
processETTER(e)
break
case (byte)'A':
procesVATAR(e)
default:
throw new Exception("Invalid read $read")
}
@ -775,5 +781,41 @@ class ConnectionAcceptor {
e.close()
}
}
private void processVATAR(Endpoint e) {
byte [] VATAR = "VATAR\r\n".getBytes(StandardCharsets.US_ASCII)
byte [] read = new byte[VATAR.length]
DataInputStream dis = new DataInputStream(e.getInputStream())
try {
dis.readFully(read)
if (VATAR != read)
throw new Exception("Invalid VATAR")
Map<String, String> headers = DataUtil.readAllHeaders(dis)
if (headers['Version'] != "1")
throw new IOException("unrecognized version")
OutputStream os = e.getOutputStream()
MWProfile profile = myProfile.get()
if (profile == null) {
os.write("404 Not Found\r\n".getBytes(StandardCharsets.US_ASCII))
return
}
os.write("200 OK\r\n".getBytes(StandardCharsets.US_ASCII))
ByteArrayOutputStream baos = new ByteArrayInputStream()
profile.write(baos)
byte [] payload = baos.toByteArray()
os.write("Length:${payload.length}\r\n".getBytes(StandardCharsets.US_ASCII))
os.write("\r\n".getBytes(StandardCharsets.US_ASCII))
os.write(payload)
} catch (Exception bad) {
log.log(Level.WARNING, "failed to process AVATAR", bad)
} finally {
e.close()
}
}
}

View File

@ -0,0 +1,11 @@
package com.muwire.core.profile
import com.muwire.core.Event
import com.muwire.core.Persona
class MWProfileFetchEvent extends Event {
MWProfileFetchStatus status
Persona host
UUID uuid
MWProfile profile
}

View File

@ -0,0 +1,83 @@
package com.muwire.core.profile
import com.muwire.core.Constants
import com.muwire.core.EventBus
import com.muwire.core.Persona
import com.muwire.core.connection.Endpoint
import com.muwire.core.connection.I2PConnector
import com.muwire.core.util.DataUtil
import groovy.util.logging.Log
import java.nio.charset.StandardCharsets
import java.util.concurrent.Executor
import java.util.concurrent.Executors
import java.util.function.Supplier
import java.util.logging.Level
@Log
class MWProfileFetcher {
private final I2PConnector connector
private final EventBus eventBus
private final Persona me
private final Supplier<MWProfileHeader> myProfileHeader
private final Executor fetcherThread = Executors.newCachedThreadPool()
MWProfileFetcher(I2PConnector connector, EventBus eventBus,
Persona me, Supplier<MWProfileHeader> myProfileHeader) {
this.connector = connector
this.eventBus = eventBus
this.me = me
this.myProfileHeader = myProfileHeader
}
void onUIProfileFetchEvent(UIProfileFetchEvent e) {
fetcherThread.execute({
Endpoint endpoint = null
try {
eventBus.publish(new MWProfileFetchEvent(host: e.host, status: MWProfileFetchStatus.CONNECTING, uuid: e.uuid))
endpoint = connector.connect(e.host.destination)
OutputStream os = endpoint.getOutputStream()
os.write("AVATAR\r\n".getBytes(StandardCharsets.US_ASCII))
os.write("Version:1\r\n".getBytes(StandardCharsets.US_ASCII))
os.write("Persona:${me.toBase64()}\r\n".getBytes(StandardCharsets.US_ASCII))
MWProfileHeader header = myProfileHeader.get()
if (header != null)
os.write("ProfileHeader:${header.toBase64()}\r\n".getBytes(StandardCharsets.US_ASCII))
os.write("\r\n".getBytes(StandardCharsets.US_ASCII))
InputStream is = endpoint.getInputStream()
String code = DataUtil.readTillRN(is)
if (!code.startsWith("200"))
throw new IOException("invalid code $code")
Map<String,String> headers = DataUtil.readAllHeaders(is)
if (!headers.containsKey("Length"))
throw new IOException("No length header")
int length = Integer.parseInt(headers['Length'])
if (length > Constants.MAX_PROFILE_LENGTH)
throw new IOException("profile too large $length")
eventBus.publish(new MWProfileFetchEvent(host: e.host, status: MWProfileFetchStatus.FETCHING, uuid: e.uuid))
byte[] payload = new byte[length]
DataInputStream dis = new DataInputStream(is)
dis.readFully(payload)
MWProfile profile = new MWProfile(new ByteArrayInputStream(payload))
if (profile.getHeader().getPersona() != e.host)
throw new Exception("profile and host mismatch")
eventBus.publish(new MWProfileFetchEvent(host: e.host, status: MWProfileFetchStatus.FINISHED,
uuid: e.uuid, profile: profile))
} catch (Exception bad) {
log.log(Level.WARNING, "profile fetch failed", bad)
eventBus.publish(new MWProfileFetchEvent(host: e.host, status: MWProfileFetchStatus.FAILED, uuid: e.uuid))
} finally {
endpoint?.close()
}
} as Runnable)
}
}

View File

@ -0,0 +1,9 @@
package com.muwire.core.profile
import com.muwire.core.Event
import com.muwire.core.Persona
class UIProfileFetchEvent extends Event {
UUID uuid
Persona host
}

View File

@ -0,0 +1,5 @@
package com.muwire.core.profile;
public enum MWProfileFetchStatus {
CONNECTING, FETCHING, FAILED, FINISHED
}

View File

@ -99,7 +99,7 @@ class ConnectionAcceptorTest {
uploadManager = uploadManagerMock.proxyInstance()
connectionEstablisher = connectionEstablisherMock.proxyInstance()
acceptor = new ConnectionAcceptor(eventBus, null, connectionManager, settings, i2pAcceptor,
acceptor = new ConnectionAcceptor(eventBus, null, null, connectionManager, settings, i2pAcceptor,
hostCache, trustService, searchManager, uploadManager, null, connectionEstablisher, null, null, null,
{f, p -> true} as BiPredicate)
acceptor.start()

View File

@ -243,4 +243,9 @@ mvcGroups {
view = 'com.muwire.gui.profile.EditProfileView'
controller = 'com.muwire.gui.profile.EditProfileController'
}
'view-profile' {
model = 'com.muwire.gui.profile.ViewProfileModel'
view = 'com.muwire.gui.profile.ViewProfileView'
controller = 'com.muwire.gui.profile.ViewProfileController'
}
}

View File

@ -90,6 +90,7 @@ class SearchTabController {
def sender = view.selectedSender()
if (sender == null)
return
sender = sender.getPersona()
String reason = JOptionPane.showInputDialog("Enter reason (optional)")
core.eventBus.publish( new TrustEvent(persona : sender, level : TrustLevel.TRUSTED, reason : reason))
}
@ -99,16 +100,25 @@ class SearchTabController {
def sender = view.selectedSender()
if (sender == null)
return
sender = sender.getPersona()
String reason = JOptionPane.showInputDialog("Enter reason (optional)")
core.eventBus.publish( new TrustEvent(persona : sender, level : TrustLevel.DISTRUSTED, reason : reason))
}
@ControllerAction
void neutral() {
void viewProfile() {
def sender = view.selectedSender()
if (sender == null)
return
core.eventBus.publish( new TrustEvent(persona : sender, level : TrustLevel.NEUTRAL))
UUID uuid = UUID.randomUUID()
def params = [:]
params.core = model.core
params.persona = sender.getPersona()
params.uuid = uuid
params.profileTitle = sender.getTitle()
mvcGroup.createMVCGroup("view-profile", uuid.toString(), params)
}
@ControllerAction
@ -117,6 +127,7 @@ class SearchTabController {
if (sender == null)
return
sender = sender.getPersona()
String groupId = UUID.randomUUID().toString()
Map<String,Object> params = new HashMap<>()
params['host'] = sender
@ -131,6 +142,7 @@ class SearchTabController {
if (sender == null)
return
sender = sender.getPersona()
UUID uuid = UUID.randomUUID()
def params = [:]
params['fileName'] = sender.getHumanReadableName()
@ -147,6 +159,7 @@ class SearchTabController {
if (sender == null)
return
sender = sender.getPersona()
Feed feed = new Feed(sender)
feed.setAutoDownload(core.muOptions.defaultFeedAutoDownload)
feed.setSequential(core.muOptions.defaultFeedSequential)
@ -163,6 +176,7 @@ class SearchTabController {
if (sender == null)
return
sender = sender.getPersona()
def parent = mvcGroup.parentGroup
parent.controller.startChat(sender)
parent.view.showChatWindow.call()
@ -170,7 +184,8 @@ class SearchTabController {
@ControllerAction
void message() {
Persona recipient = view.selectedSender()
Persona recipient = view.selectedSender()?.getPersona()
if (recipient == null)
return
@ -182,7 +197,7 @@ class SearchTabController {
@ControllerAction
void copyFullID() {
Persona sender = view.selectedSender()
Persona sender = view.selectedSender()?.getPersona()
if (sender == null)
return
CopyPasteSupport.copyToClipboard(sender.toBase64())

View File

@ -0,0 +1,45 @@
package com.muwire.gui.profile
import com.muwire.core.trust.TrustEvent
import com.muwire.core.trust.TrustLevel
import griffon.core.artifact.GriffonController
import griffon.core.controller.ControllerAction
import griffon.inject.MVCMember
import griffon.metadata.ArtifactProviderFor
import static com.muwire.gui.Translator.trans
import javax.annotation.Nonnull
import javax.swing.JOptionPane
@ArtifactProviderFor(GriffonController)
class ViewProfileController {
@MVCMember @Nonnull
ViewProfileModel model
@MVCMember @Nonnull
ViewProfileView view
@ControllerAction
void fetch() {
model.register()
}
@ControllerAction
void addContact() {
String reason = JOptionPane.showInputDialog(trans("ENTER_REASON_OPTIONAL"))
model.core.eventBus.publish(new TrustEvent(persona: model.persona, level: TrustLevel.TRUSTED, reason: reason))
}
@ControllerAction
void block() {
String reason = JOptionPane.showInputDialog(trans("ENTER_REASON_OPTIONAL"))
model.core.eventBus.publish(new TrustEvent(persona: model.persona, level: TrustLevel.DISTRUSTED, reason: reason))
}
@ControllerAction
void close() {
view.window.setVisible(false)
mvcGroup.destroy()
}
}

View File

@ -271,7 +271,7 @@ NEUTRAL=Neutral
DISTRUST=Distrust
COPY_FULL_ID=Copy Full ID
NO_PROFILE=This user does not have a profile
VIEW_PROFILE=View Profile
# results table (group by sender)
DIRECT_SOURCES=Direct Sources
@ -712,6 +712,15 @@ PROFILE_EDITOR_ERROR_NO_IMAGE=Please select an avatar or generate one
PROFILE_EDITOR_ERROR_LARGE_TITLE=Profile title is too long
PROFILE_EDITOR_ERROR_LARGE_BODY=Profile is too long
## View Profile Frame
PROFILE_VIEWER_TITLE=Profile of {0}
PROFILE_VIEWER_HEADER=Profile Title
PROFILE_VIEWER_HEADER_MISSING=No profile title available. Fetch profile to see the title.
PROFILE_VIEWER_FETCH=Fetch Profile
PROFILE_VIEWER_AVATAR=User Avatar
PROFILE_VIEWER_PROFILE=Profile
PROFILE_VIEWER_BLOCK=Block
## Tooltips
TOOLTIP_FILE_FEED_DISABLED=Your file feed is disabled
@ -727,6 +736,7 @@ TOOLTIP_BROWSE_COLLECTIONS_SENDER=Browse all the sender's collections
TOOLTIP_SENDER_COPY_FULL=Copy the full ID of the sender
TOOLTIP_ADD_CONTACT_SENDER=Add the sender as a trusted contact
TOOLTIP_DISTRUST_SENDER=Block the sender
TOOLTIP_VIEW_PROFILE=View the profile of the sender
TOOLTIP_DOWNLOAD_FILE=Download the selected results
TOOLTIP_DOWNLOAD_SEQUENTIALLY=Download in order (enables preview)
TOOLTIP_VIEW_DETAILS_RESULT=View details about the result
@ -886,5 +896,10 @@ TOOLTIP_CHAT_SERVERS_CONNECT=Connect to server now
TOOLTIP_PROFILE_EDITOR=Edit Profile
TOOLTIP_PROFILE_EDITOR_GENERATE=Generate an avatar from your MuWire ID
### Tooltips for the profile viewer
TOOLTIP_PROFILE_VIEWER_FETCH=Fetch the full profile of this user
TOOLTIP_PROFILE_VIEWER_ADD_CONTACT=Add this user as a trusted contact
TOOLTIP_PROFILE_VIEWER_BLOCK=Block this user
## Test string
TEST_PLURALIZABLE_STRING={count, plural, one {You have {count} item.} other {You have {count} items.}}

View File

@ -39,6 +39,7 @@ class SearchTabModel {
@Observable boolean chatActionEnabled
@Observable boolean subscribeActionEnabled
@Observable boolean messageActionEnabled
@Observable boolean viewProfileActionEnabled
@Observable boolean viewDetailsActionEnabled
@Observable boolean groupedByFile
@ -62,7 +63,6 @@ class SearchTabModel {
def results2 = []
def allResults2 = new LinkedHashSet()
def senders2 = []
volatile String[] filter
volatile Filterer filterer
@Observable boolean clearFilterActionEnabled

View File

@ -0,0 +1,55 @@
package com.muwire.gui.profile
import com.muwire.core.Core
import com.muwire.core.Persona
import com.muwire.core.profile.MWProfileFetchEvent
import com.muwire.core.profile.MWProfileFetchStatus
import com.muwire.core.profile.MWProfileHeader
import com.muwire.core.profile.UIProfileFetchEvent
import griffon.core.artifact.GriffonModel
import griffon.inject.MVCMember
import griffon.metadata.ArtifactProviderFor
import griffon.transform.Observable
import javax.annotation.Nonnull
@ArtifactProviderFor(GriffonModel)
class ViewProfileModel {
@MVCMember @Nonnull
ViewProfileView view
Core core
Persona persona
UUID uuid
String profileTitle
@Observable MWProfileFetchStatus status
private boolean registered
void mvcGroupInit(Map<String, String> args) {
}
void register() {
if (registered)
return
registered = true
core.getEventBus().register(MWProfileFetchEvent.class, this)
core.getEventBus().publish(new UIProfileFetchEvent(uuid: uuid, host: persona))
}
void mvcGroupDestroy() {
if (registered)
core.getEventBus().unregister(MWProfileFetchEvent.class, this)
}
void onMWProfileFetchEvent(MWProfileFetchEvent event) {
if (uuid != event.uuid)
return
runInsideUIAsync {
status = event.status
if (status == MWProfileFetchStatus.FINISHED)
view.profileFetched(event.profile)
}
}
}

View File

@ -126,6 +126,7 @@ class SearchTabView {
panel (border : etchedBorder()){
button(text : trans("ADD_CONTACT"), toolTipText: trans("TOOLTIP_ADD_CONTACT_SENDER"), enabled: bind {model.trustButtonsEnabled }, trustAction)
button(text : trans("DISTRUST"), toolTipText: trans("TOOLTIP_DISTRUST_SENDER"), enabled : bind {model.trustButtonsEnabled}, distrustAction)
button(text : trans("VIEW_PROFILE"), toolTipText: trans("TOOLTIP_VIEW_PROFILE"), enabled: bind {model.viewProfileActionEnabled}, viewProfileAction)
}
}
}
@ -412,6 +413,7 @@ class SearchTabView {
int row = selectedSenderRow()
if (row < 0) {
model.trustButtonsEnabled = false
model.viewProfileActionEnabled = false
model.browseActionEnabled = false
model.subscribeActionEnabled = false
model.browseCollectionsActionEnabled = false
@ -428,6 +430,7 @@ class SearchTabView {
model.subscribeActionEnabled = bucket.results[0].feed &&
model.core.feedManager.getFeed(sender) == null
model.trustButtonsEnabled = true
model.viewProfileActionEnabled = true
model.results.clear()
model.results.addAll(bucket.results)
@ -469,6 +472,7 @@ class SearchTabView {
model.browseCollectionsActionEnabled = false
model.chatActionEnabled = false
model.messageActionEnabled = false
model.viewProfileActionEnabled = false
return
}
@ -525,6 +529,7 @@ class SearchTabView {
model.tab = parent.indexOfComponent(pane)
parent.removeTabAt(model.tab)
model.trustButtonsEnabled = false
model.viewProfileActionEnabled = false
model.downloadActionEnabled = false
resultDetails.values().each {it.destroy()}
mvcGroup.destroy()
@ -680,14 +685,11 @@ class SearchTabView {
}
}
Persona selectedSender() {
PersonaOrProfile selectedSender() {
int row = selectedSenderRow()
if (row < 0)
return null
if (model.groupedByFile)
return model.senders2[row]?.sender
else
return model.senders[row]?.sender
return model.senders[row]
}
def showSenderGrouping = {

View File

@ -0,0 +1,132 @@
package com.muwire.gui.profile
import com.muwire.core.profile.MWProfile
import com.muwire.gui.HTMLSanitizer
import javax.imageio.ImageIO
import javax.swing.JLabel
import javax.swing.JPanel
import javax.swing.JTextArea
import javax.swing.border.TitledBorder
import java.awt.BorderLayout
import java.awt.Dimension
import java.awt.Window
import java.awt.event.WindowAdapter
import java.awt.event.WindowEvent
import static com.muwire.gui.Translator.trans
import griffon.core.GriffonApplication
import griffon.core.artifact.GriffonView
import griffon.inject.MVCMember
import griffon.metadata.ArtifactProviderFor
import javax.annotation.Nonnull
import javax.inject.Inject
import javax.swing.JFrame
@ArtifactProviderFor(GriffonView)
class ViewProfileView {
@MVCMember @Nonnull
ViewProfileModel model
@MVCMember @Nonnull
ViewProfileController controller
@MVCMember @Nonnull
FactoryBuilderSupport builder
@Inject
GriffonApplication application
JFrame window
JFrame mainFrame
JPanel mainPanel
JLabel titleLabel
JPanel imagePanel
JTextArea bodyArea
void initUI() {
mainFrame = application.windowManager.findWindow("main-frame")
def mainDim = mainFrame.getSize()
int dimX = Math.max(1100, (int)(mainDim.getWidth() / 2))
int dimY = Math.max(700, (int)(mainDim.getHeight() / 2))
window = builder.frame(visible: false, defaultCloseOperation: JFrame.DISPOSE_ON_CLOSE,
iconImage: builder.imageIcon("/MuWire-48x48.png").image,
title: trans("PROFILE_VIEWER_TITLE", model.persona.getHumanReadableName())) {
borderLayout()
panel(border: titledBorder(title : trans("PROFILE_VIEWER_HEADER"), border: etchedBorder(),
titlePosition: TitledBorder.TOP), constraints: BorderLayout.NORTH) {
if (model.profileTitle == null)
titleLabel = label(text: trans("PROFILE_VIEWER_HEADER_MISSING"))
else
titleLabel = label(text: model.profileTitle)
}
mainPanel = panel(constraints: BorderLayout.CENTER) {
cardLayout()
panel(constraints: "fetch-profile") {
button(text: trans("PROFILE_VIEWER_FETCH"), toolTipText: trans("TOOLTIP_PROFILE_VIEWER_FETCH"),
fetchAction)
}
panel(constraints: "full-profile") {
gridLayout(rows: 1, cols: 2)
panel(border: titledBorder(title: trans("PROFILE_VIEWER_AVATAR"), border: etchedBorder(),
titlePosition: TitledBorder.TOP)) {
imagePanel = panel()
}
panel(border: titledBorder(title: trans("PROFILE_VIEWER_PROFILE"), border: etchedBorder(),
titlePosition: TitledBorder.TOP)) {
scrollPane {
bodyArea = textArea(editable: false, lineWrap: true, wrapStyleWord: true)
}
}
}
}
panel(constraints: BorderLayout.SOUTH) {
borderLayout()
panel(constraints: BorderLayout.WEST) {
label(text : bind { model.status == null ? "" : trans(model.status.name())})
}
panel(constraints: BorderLayout.CENTER) {
button(text: trans("ADD_CONTACT"), toolTipText: trans("TOOLTIP_PROFILE_VIEWER_ADD_CONTACT"),
addContactAction)
button(text: trans("PROFILE_VIEWER_BLOCK"), toolTipText: trans("TOOLTIP_PROFILE_VIEWER_BLOCK"),
blockAction)
}
panel(constraints: BorderLayout.EAST) {
button(text : trans("CLOSE"), closeAction)
}
}
}
window.setPreferredSize([dimX, dimY] as Dimension)
}
void mvcGroupInit(Map<String, String> params) {
window.addWindowListener(new WindowAdapter() {
@Override
void windowClosed(WindowEvent e) {
mvcGroup.destroy()
}
})
window.pack()
window.setLocationRelativeTo(mainFrame)
window.setVisible(true)
}
void profileFetched(MWProfile profile) {
mainPanel.getLayout().show(mainPanel, "full-profile")
titleLabel.setText(HTMLSanitizer.sanitize(profile.getHeader().getTitle()))
bodyArea.setText(profile.getBody())
def rawImage = ImageIO.read(new ByteArrayInputStream(profile.getImage()))
def mainImage = ImageScaler.scaleToMax(rawImage)
def imgDim = imagePanel.getSize()
imagePanel.getGraphics().drawImage(mainImage,
(int)(imgDim.getWidth() / 2) - (int)(mainImage.getWidth() / 2),
(int)(imgDim.getHeight() / 2) - (int)(mainImage.getHeight() / 2),
null)
}
}