All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jvmMain.io.nacular.doodle.theme.native.NativeFileSelectorBehavior.kt Maven / Gradle / Ivy

There is a newer version: 0.10.4
Show newest version
package io.nacular.doodle.theme.native

import io.nacular.doodle.controls.files.FileSelector
import io.nacular.doodle.controls.files.FileSelector.Companion.AnyFile
import io.nacular.doodle.controls.files.FileSelectorBehavior
import io.nacular.doodle.core.Behavior
import io.nacular.doodle.core.View
import io.nacular.doodle.datatransport.LocalFile
import io.nacular.doodle.datatransport.MimeType
import io.nacular.doodle.datatransport.SimpleFile
import io.nacular.doodle.drawing.Canvas
import io.nacular.doodle.drawing.impl.CanvasImpl
import io.nacular.doodle.event.PointerEvent
import io.nacular.doodle.event.PointerListener.Companion.clicked
import io.nacular.doodle.focus.FocusManager
import io.nacular.doodle.geometry.Rectangle
import io.nacular.doodle.geometry.Size
import io.nacular.doodle.system.Cursor
import io.nacular.doodle.system.Cursor.Companion.Default
import io.nacular.doodle.theme.native.NativeTheme.WindowDiscovery
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import java.awt.Dimension
import java.awt.datatransfer.DataFlavor
import java.awt.event.FocusEvent
import java.awt.event.FocusListener
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.awt.event.MouseEvent.MOUSE_DRAGGED
import java.awt.event.MouseEvent.MOUSE_MOVED
import java.io.File
import java.nio.file.Files
import javax.swing.JButton
import javax.swing.JFileChooser
import javax.swing.JFileChooser.APPROVE_OPTION
import javax.swing.filechooser.FileFilter
import kotlin.coroutines.CoroutineContext

internal class NativeFileSelectorStylerImpl(window: WindowDiscovery): NativeFileSelectorStyler {
    private val fileHandler = FileSelectionHandler(window)

    override fun invoke(fileSelector: FileSelector, behavior: Behavior): Behavior = NativeFileSelectorBehaviorWrapper(
        fileHandler,
        fileSelector,
        behavior
    )
}

private class FileSelectionHandler(private val window: WindowDiscovery) {
    private val fileChooser: JFileChooser by lazy {
        JFileChooser().apply { isAcceptAllFileFilterUsed = false }
    }

    fun run(fileSelector: FileSelector): List {
        try {
            with(fileChooser) {
                currentDirectory        = File(System.getProperty("user.home"))
                isMultiSelectionEnabled = fileSelector.allowMultiple

                when {
                    fileSelector.acceptedTypes === AnyFile -> {}
                    else                                   -> {
                        fileFilter = object : FileFilter() {
                            override fun accept(pathname: File?): Boolean {
                                return pathname?.let { Files.probeContentType(it.toPath()) }?.let {
                                    val type = DataFlavor(it).run {
                                        MimeType(primaryType, subType, emptyMap())
                                    }

                                    type in fileSelector.acceptedTypes

                                } ?: false
                            }

                            override fun getDescription() = fileSelector.acceptedTypes.joinToString(", ")
                        }
                    }
                }

                window.frameFor(fileSelector)?.let {
                    showOpenDialog(it).let { result ->
                        if (result == APPROVE_OPTION) {
                            return fileChooser.selectedFiles.map { SimpleFile(it) }
                        }
                    }
                }
            }
        } catch (ignored: Exception) {
        }

        return emptyList()
    }
}

private class NativeFileSelectorBehaviorWrapper(private val fileHandler: FileSelectionHandler, fileSelector: FileSelector, private val delegate: Behavior): FileSelectorBehavior, Behavior by delegate {
    init {
        fileSelector.pointerChanged += clicked {
            fileSelector.files = fileHandler.run(fileSelector)
        }
    }
}

internal class NativeFileSelectorBehavior(
        private val appScope                 : CoroutineScope,
        private val uiDispatcher             : CoroutineContext,
        private val window                   : WindowDiscovery,
        private val swingGraphicsFactory     : SwingGraphicsFactory,
        private val focusManager             : FocusManager?,
        private val nativePointerPreprocessor: NativePointerPreprocessor?,
        private val prompt               : String,
): FileSelectorBehavior {
    private val fileHandler = FileSelectionHandler(window)

    private inner class FileSelectorPeer(fileSelector: FileSelector): JButton(prompt) {
        // This needs to remain since JButton will render on construct before the local value is initialized
        @Suppress("RedundantNullableReturnType")
        private val fileSelector: FileSelector? = fileSelector

        init {
            focusTraversalKeysEnabled = false

            addFocusListener(object : FocusListener {
                override fun focusGained(e: FocusEvent?) {
                    focusManager?.requestFocus(fileSelector)
                }

                override fun focusLost(e: FocusEvent?) {
                    focusManager?.clearFocus()
                }
            })

            nativePointerPreprocessor?.set(fileSelector, object: NativePointerHandler {
                override fun invoke(event: PointerEvent) {
                    if (event.source == fileSelector) {
                        [email protected](event.toAwt(nativePeer))
                    }
                }
            })

            addMouseListener(object: MouseAdapter() {
                override fun mouseClicked(e: MouseEvent?) {
                    fileSelector.files = fileHandler.run(fileSelector)
                }
            })
        }

        override fun repaint(tm: Long, x: Int, y: Int, width: Int, height: Int) {
            fileSelector?.rerender()
        }

        public override fun processMouseEvent(e: MouseEvent?)  = when (e?.id) {
            MOUSE_MOVED, MOUSE_DRAGGED -> super.processMouseMotionEvent(e)
            else                       -> super.processMouseEvent      (e)
        }
    }

    private lateinit var nativePeer   : FileSelectorPeer
    private          var oldCursor    : Cursor? = null
    private          var oldIdealSize : Size?   = null

    private val focusChanged: (View, Boolean, Boolean) -> Unit = { _,_,new ->
        when (new) {
            true -> nativePeer.requestFocusInWindow()
            else -> nativePeer.transferFocus       ()
        }
    }

    private val enabledChanged: (View, Boolean, Boolean) -> Unit = { _,_,new ->
        nativePeer.isEnabled = new
    }

    private val focusableChanged: (View, Boolean, Boolean) -> Unit = { _,_,new ->
        nativePeer.isFocusable = new
    }

    private val boundsChanged: (View, Rectangle, Rectangle) -> Unit = { _,_,new ->
        nativePeer.size = new.size.run { Dimension(width.toInt(), height.toInt()) }
    }

    override fun render(view: FileSelector, canvas: Canvas) {
        nativePeer.paint(swingGraphicsFactory((canvas as CanvasImpl).skiaCanvas))
    }

    override fun install(view: FileSelector) {
        super.install(view)

        nativePeer = FileSelectorPeer(view)

        view.apply {
            focusChanged        += [email protected]
            boundsChanged       += [email protected]
            enabledChanged      += [email protected]
            focusabilityChanged += [email protected]
        }

        appScope.launch(uiDispatcher) {
            nativePeer.size = view.size.run { Dimension(view.width.toInt(), view.height.toInt()) }

            view.apply {
                cursor    = Default
                idealSize = nativePeer.preferredSize.run { Size(width, height) }
            }

            window.frameFor(view)?.add(nativePeer)

            if (view.hasFocus) {
                nativePeer.requestFocusInWindow()
            }
        }
    }

    override fun uninstall(view: FileSelector) {
        super.uninstall(view)

        view.apply {
            cursor    = oldCursor
            idealSize = oldIdealSize

            focusChanged        -= [email protected]
            boundsChanged       -= [email protected]
            enabledChanged      -= [email protected]
            focusabilityChanged -= [email protected]
        }

        appScope.launch(uiDispatcher) {
            window.frameFor(view)?.remove(nativePeer)
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy