mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 02:12:58 +08:00
chore!: migrate to version 2.x
This commit is contained in:
16
plugins/bg/build.gradle.kts
Normal file
16
plugins/bg/build.gradle.kts
Normal file
@@ -0,0 +1,16 @@
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.jvm)
|
||||
}
|
||||
|
||||
|
||||
project.version = "0.0.2"
|
||||
|
||||
|
||||
|
||||
dependencies {
|
||||
testImplementation(kotlin("test"))
|
||||
compileOnly(project(":"))
|
||||
}
|
||||
|
||||
apply(from = "$rootDir/plugins/common.gradle.kts")
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package app.termora.plugins.bg
|
||||
|
||||
import app.termora.EnableManager
|
||||
import app.termora.database.DatabaseManager
|
||||
|
||||
object Appearance {
|
||||
private val enableManager get() = EnableManager.getInstance()
|
||||
private val appearance get() = DatabaseManager.getInstance().appearance
|
||||
|
||||
var backgroundImage: String
|
||||
get() = enableManager.getFlag("Plugins.bg.backgroundImage", appearance.backgroundImage)
|
||||
set(value) {
|
||||
enableManager.setFlag("Plugins.bg.backgroundImage", value)
|
||||
}
|
||||
|
||||
var interval: Int
|
||||
get() = enableManager.getFlag("Plugins.bg.interval", 360)
|
||||
set(value) {
|
||||
enableManager.setFlag("Plugins.bg.interval", value)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package app.termora.plugins.bg
|
||||
|
||||
import app.termora.GlassPaneExtension
|
||||
import com.formdev.flatlaf.FlatLaf
|
||||
import java.awt.AlphaComposite
|
||||
import java.awt.Graphics2D
|
||||
import javax.swing.JComponent
|
||||
|
||||
class BGGlassPaneExtension private constructor() : GlassPaneExtension {
|
||||
companion object {
|
||||
val instance = BGGlassPaneExtension()
|
||||
}
|
||||
|
||||
override fun paint(
|
||||
c: JComponent,
|
||||
g2d: Graphics2D
|
||||
): Boolean {
|
||||
|
||||
val img = BackgroundManager.getInstance().getBackgroundImage() ?: return false
|
||||
g2d.composite = AlphaComposite.getInstance(
|
||||
AlphaComposite.SRC_OVER,
|
||||
if (FlatLaf.isLafDark()) 0.2f else 0.1f
|
||||
)
|
||||
g2d.drawImage(img, 0, 0, c.width, c.height, null)
|
||||
g2d.composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
26
plugins/bg/src/main/kotlin/app/termora/plugins/bg/BGI18n.kt
Normal file
26
plugins/bg/src/main/kotlin/app/termora/plugins/bg/BGI18n.kt
Normal file
@@ -0,0 +1,26 @@
|
||||
package app.termora.plugins.bg
|
||||
|
||||
import app.termora.AbstractI18n
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.util.*
|
||||
|
||||
object BGI18n : AbstractI18n() {
|
||||
private val log = LoggerFactory.getLogger(BGI18n::class.java)
|
||||
private val myBundle by lazy {
|
||||
val bundle = ResourceBundle.getBundle("i18n/messages", Locale.getDefault(), BGI18n::class.java.classLoader)
|
||||
if (log.isInfoEnabled) {
|
||||
log.info("I18n: {}", bundle.baseBundleName ?: "null")
|
||||
}
|
||||
return@lazy bundle
|
||||
}
|
||||
|
||||
|
||||
override fun getBundle(): ResourceBundle {
|
||||
return myBundle
|
||||
}
|
||||
|
||||
override fun getLogger(): Logger {
|
||||
return log
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package app.termora.plugins.bg
|
||||
|
||||
import app.termora.ApplicationRunnerExtension
|
||||
import app.termora.GlassPaneAwareExtension
|
||||
import app.termora.GlassPaneExtension
|
||||
import app.termora.SettingsOptionExtension
|
||||
import app.termora.plugin.Extension
|
||||
import app.termora.plugin.ExtensionSupport
|
||||
import app.termora.plugin.Plugin
|
||||
|
||||
class BGPlugin : Plugin {
|
||||
private val support = ExtensionSupport()
|
||||
|
||||
init {
|
||||
support.addExtension(GlassPaneExtension::class.java) { BGGlassPaneExtension.instance }
|
||||
support.addExtension(SettingsOptionExtension::class.java) { BackgroundSettingsOptionExtension.instance }
|
||||
support.addExtension(ApplicationRunnerExtension::class.java) { BackgroundManager.getInstance() }
|
||||
support.addExtension(GlassPaneAwareExtension::class.java) { BackgroundManager.getInstance() }
|
||||
}
|
||||
|
||||
override fun getAuthor(): String {
|
||||
return "TermoraDev"
|
||||
}
|
||||
|
||||
|
||||
override fun getName(): String {
|
||||
return "Customize Background"
|
||||
}
|
||||
|
||||
|
||||
override fun <T : Extension> getExtensions(clazz: Class<T>): List<T> {
|
||||
return support.getExtensions(clazz)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package app.termora.plugins.bg
|
||||
|
||||
import app.termora.*
|
||||
import app.termora.database.DatabaseManager
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.Request
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.awt.Window
|
||||
import java.awt.image.BufferedImage
|
||||
import java.io.File
|
||||
import java.lang.ref.WeakReference
|
||||
import javax.imageio.ImageIO
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JPopupMenu
|
||||
import javax.swing.SwingUtilities
|
||||
import kotlin.math.max
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
internal class BackgroundManager private constructor() : Disposable, GlassPaneAwareExtension,
|
||||
ApplicationRunnerExtension {
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(BackgroundManager::class.java)
|
||||
fun getInstance(): BackgroundManager {
|
||||
return ApplicationScope.Companion.forApplicationScope()
|
||||
.getOrCreate(BackgroundManager::class) { BackgroundManager() }
|
||||
}
|
||||
}
|
||||
|
||||
private var bufferedImage: BufferedImage? = null
|
||||
private var imageFilepath = StringUtils.EMPTY
|
||||
private val glassPanes = mutableListOf<WeakReference<JComponent>>()
|
||||
|
||||
|
||||
fun setBackgroundImage(url: String) {
|
||||
clearBackgroundImage()
|
||||
Appearance.backgroundImage = url
|
||||
refreshBackgroundImage()
|
||||
}
|
||||
|
||||
fun getBackgroundImage(): BufferedImage? {
|
||||
val bg = doGetBackgroundImage()
|
||||
if (bg == null) {
|
||||
if (JPopupMenu.getDefaultLightWeightPopupEnabled()) {
|
||||
return null
|
||||
} else {
|
||||
JPopupMenu.setDefaultLightWeightPopupEnabled(true)
|
||||
}
|
||||
} else {
|
||||
if (JPopupMenu.getDefaultLightWeightPopupEnabled()) {
|
||||
JPopupMenu.setDefaultLightWeightPopupEnabled(false)
|
||||
}
|
||||
}
|
||||
return bg
|
||||
}
|
||||
|
||||
private fun doGetBackgroundImage(): BufferedImage? {
|
||||
synchronized(this) {
|
||||
return bufferedImage
|
||||
}
|
||||
}
|
||||
|
||||
fun clearBackgroundImage() {
|
||||
synchronized(this) {
|
||||
bufferedImage = null
|
||||
imageFilepath = StringUtils.EMPTY
|
||||
Appearance.backgroundImage = StringUtils.EMPTY
|
||||
}
|
||||
refreshGlassPanes()
|
||||
}
|
||||
|
||||
private fun refreshBackgroundImage() {
|
||||
val backgroundImage = Appearance.backgroundImage
|
||||
if (backgroundImage.isBlank()) {
|
||||
return
|
||||
}
|
||||
|
||||
var file: File? = null
|
||||
|
||||
// 从网络下载
|
||||
if (backgroundImage.startsWith("http://") || backgroundImage.startsWith("https://")) {
|
||||
file = Application.httpClient.newCall(
|
||||
Request.Builder().get()
|
||||
.url(backgroundImage).build()
|
||||
).execute().use { response ->
|
||||
val tempFile = File(Application.getTemporaryDir(), randomUUID())
|
||||
if (response.isSuccessful.not()) {
|
||||
if (log.isErrorEnabled) {
|
||||
log.error("Request {} failed with code {}", backgroundImage, response.code)
|
||||
}
|
||||
return
|
||||
}
|
||||
val body = response.body
|
||||
if (body != null) {
|
||||
tempFile.outputStream().use { IOUtils.copy(body.byteStream(), it) }
|
||||
}
|
||||
IOUtils.closeQuietly(body)
|
||||
return@use tempFile
|
||||
}
|
||||
}
|
||||
|
||||
val backgroundImageFile = File(backgroundImage)
|
||||
if (backgroundImageFile.isDirectory) {
|
||||
val files = FileUtils.listFiles(backgroundImageFile, arrayOf("png", "jpg", "jpeg"), false)
|
||||
if (files.isNotEmpty()) {
|
||||
for (i in 0 until files.size) {
|
||||
file = files.randomOrNull()
|
||||
if (file == null) break
|
||||
if (file.absolutePath == imageFilepath) continue
|
||||
}
|
||||
} else {
|
||||
synchronized(this) {
|
||||
imageFilepath = StringUtils.EMPTY
|
||||
bufferedImage = null
|
||||
refreshGlassPanes()
|
||||
}
|
||||
}
|
||||
} else if (backgroundImageFile.isFile) {
|
||||
file = backgroundImageFile
|
||||
}
|
||||
|
||||
if (file == null || imageFilepath == file.absolutePath) {
|
||||
return
|
||||
}
|
||||
|
||||
bufferedImage = file.inputStream().use { ImageIO.read(it) }
|
||||
imageFilepath = file.absolutePath
|
||||
|
||||
refreshGlassPanes()
|
||||
}
|
||||
|
||||
private fun refreshGlassPanes() {
|
||||
SwingUtilities.invokeLater {
|
||||
glassPanes.removeIf {
|
||||
val glassPane = it.get()
|
||||
glassPane?.repaint()
|
||||
glassPane == null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
|
||||
}
|
||||
|
||||
override fun setGlassPane(window: Window, glassPane: JComponent) {
|
||||
glassPanes.add(WeakReference(glassPane))
|
||||
}
|
||||
|
||||
override fun ready() {
|
||||
swingCoroutineScope.launch(Dispatchers.IO) {
|
||||
while (isActive) {
|
||||
runCatching { refreshBackgroundImage() }.onFailure {
|
||||
if (log.isErrorEnabled) {
|
||||
log.error("Refresh failed", it)
|
||||
}
|
||||
}
|
||||
delay(max(Appearance.interval, 30).seconds)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
package app.termora.plugins.bg
|
||||
|
||||
import app.termora.*
|
||||
import app.termora.OptionsPane.Companion.FORM_MARGIN
|
||||
import app.termora.database.DatabaseManager
|
||||
import app.termora.nv.FileChooser
|
||||
import com.formdev.flatlaf.extras.components.FlatButton
|
||||
import com.formdev.flatlaf.extras.components.FlatTextPane
|
||||
import com.jgoodies.forms.builder.FormBuilder
|
||||
import com.jgoodies.forms.layout.FormLayout
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.awt.BorderLayout
|
||||
import java.io.File
|
||||
import java.nio.file.StandardCopyOption
|
||||
import javax.swing.*
|
||||
import javax.swing.event.DocumentEvent
|
||||
|
||||
class BackgroundOption : JPanel(BorderLayout()), OptionsPane.PluginOption {
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(BackgroundOption::class.java)
|
||||
}
|
||||
|
||||
private val owner get() = SwingUtilities.getWindowAncestor(this)
|
||||
|
||||
val backgroundImageTextField = OutlineTextField()
|
||||
val intervalSpinner = NumberSpinner(360, minimum = 30, maximum = 86400)
|
||||
|
||||
private val backgroundButton = JButton(Icons.folder)
|
||||
private val backgroundClearButton = FlatButton()
|
||||
|
||||
|
||||
init {
|
||||
initView()
|
||||
initEvents()
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
|
||||
backgroundImageTextField.isEditable = false
|
||||
backgroundImageTextField.trailingComponent = backgroundButton
|
||||
backgroundImageTextField.text = Appearance.backgroundImage
|
||||
backgroundImageTextField.document.addDocumentListener(object : DocumentAdaptor() {
|
||||
override fun changedUpdate(e: DocumentEvent) {
|
||||
backgroundClearButton.isEnabled = backgroundImageTextField.text.isNotBlank()
|
||||
}
|
||||
})
|
||||
|
||||
backgroundClearButton.isFocusable = false
|
||||
backgroundClearButton.isEnabled = backgroundImageTextField.text.isNotBlank()
|
||||
backgroundClearButton.icon = Icons.delete
|
||||
backgroundClearButton.buttonType = FlatButton.ButtonType.toolBarButton
|
||||
|
||||
intervalSpinner.value = Appearance.interval
|
||||
|
||||
add(getFormPanel(), BorderLayout.CENTER)
|
||||
}
|
||||
|
||||
private fun initEvents() {
|
||||
backgroundButton.addActionListener {
|
||||
val chooser = FileChooser()
|
||||
chooser.osxAllowedFileTypes = listOf("png", "jpg", "jpeg")
|
||||
chooser.allowsMultiSelection = false
|
||||
chooser.win32Filters.add(Pair("Image files", listOf("png", "jpg", "jpeg")))
|
||||
chooser.fileSelectionMode = JFileChooser.FILES_AND_DIRECTORIES
|
||||
chooser.showOpenDialog(owner).thenAccept {
|
||||
if (it.isNotEmpty()) {
|
||||
onSelectedBackgroundImage(it.first())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
backgroundClearButton.addActionListener {
|
||||
BackgroundManager.getInstance().clearBackgroundImage()
|
||||
backgroundImageTextField.text = StringUtils.EMPTY
|
||||
}
|
||||
|
||||
intervalSpinner.addChangeListener {
|
||||
val value = intervalSpinner.value
|
||||
if (value is Int) {
|
||||
Appearance.interval = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSelectedBackgroundImage(file: File) {
|
||||
try {
|
||||
if (file.isFile) {
|
||||
val destFile = FileUtils.getFile(Application.getBaseDataDir(), "background", file.name)
|
||||
FileUtils.forceMkdirParent(destFile)
|
||||
FileUtils.deleteQuietly(destFile)
|
||||
FileUtils.copyFile(file, destFile, StandardCopyOption.REPLACE_EXISTING)
|
||||
BackgroundManager.getInstance().setBackgroundImage(destFile.absolutePath)
|
||||
} else if (file.isDirectory) {
|
||||
BackgroundManager.getInstance().setBackgroundImage(file.absolutePath)
|
||||
}
|
||||
backgroundImageTextField.text = file.absolutePath
|
||||
} catch (e: Exception) {
|
||||
if (log.isErrorEnabled) {
|
||||
log.error(e.message, e)
|
||||
}
|
||||
SwingUtilities.invokeLater {
|
||||
OptionPane.showMessageDialog(
|
||||
owner,
|
||||
ExceptionUtils.getRootCauseMessage(e),
|
||||
messageType = JOptionPane.ERROR_MESSAGE
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getIcon(isSelected: Boolean): Icon {
|
||||
return Icons.imageGray
|
||||
}
|
||||
|
||||
override fun getTitle(): String {
|
||||
return BGI18n.getString("termora.plugins.bg.background-image")
|
||||
}
|
||||
|
||||
override fun getJComponent(): JComponent {
|
||||
return this
|
||||
}
|
||||
|
||||
|
||||
private fun getFormPanel(): JPanel {
|
||||
val layout = FormLayout(
|
||||
"left:pref, $FORM_MARGIN, default:grow, $FORM_MARGIN, default",
|
||||
"pref, $FORM_MARGIN, pref"
|
||||
)
|
||||
|
||||
var rows = 1
|
||||
val step = 2
|
||||
val builder = FormBuilder.create().layout(layout)
|
||||
val bgClearBox = Box.createHorizontalBox()
|
||||
bgClearBox.add(backgroundClearButton)
|
||||
|
||||
builder.add("${BGI18n.getString("termora.plugins.bg.background-image")}:").xy(1, rows)
|
||||
.add(backgroundImageTextField).xy(3, rows)
|
||||
.add(bgClearBox).xy(5, rows)
|
||||
.apply { rows += step }
|
||||
|
||||
builder.add("${BGI18n.getString("termora.plugins.bg.interval")}:").xy(1, rows)
|
||||
.add(intervalSpinner).xy(3, rows)
|
||||
.apply { rows += step }
|
||||
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package app.termora.plugins.bg
|
||||
|
||||
import app.termora.OptionsPane
|
||||
import app.termora.SettingsOptionExtension
|
||||
|
||||
class BackgroundSettingsOptionExtension private constructor(): SettingsOptionExtension {
|
||||
companion object {
|
||||
val instance by lazy { BackgroundSettingsOptionExtension() }
|
||||
}
|
||||
|
||||
override fun createSettingsOption(): OptionsPane.Option {
|
||||
return BackgroundOption()
|
||||
}
|
||||
}
|
||||
23
plugins/bg/src/main/resources/META-INF/plugin.xml
Normal file
23
plugins/bg/src/main/resources/META-INF/plugin.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<termora-plugin>
|
||||
|
||||
<id>bg</id>
|
||||
|
||||
<name>Customize Background</name>
|
||||
|
||||
<version>${projectVersion}</version>
|
||||
|
||||
<entry>app.termora.plugins.bg.BGPlugin</entry>
|
||||
|
||||
<termora-version since=">=${rootProjectVersion}" until=""/>
|
||||
|
||||
|
||||
<descriptions>
|
||||
<description>Customize application background</description>
|
||||
<description language="zh_CN">自定义应用程序背景</description>
|
||||
<description language="zh_TW">自訂應用程式背景</description>
|
||||
</descriptions>
|
||||
|
||||
<vendor url="https://github.com/TermoraDev">TermoraDev</vendor>
|
||||
|
||||
|
||||
</termora-plugin>
|
||||
6
plugins/bg/src/main/resources/META-INF/pluginIcon.svg
Normal file
6
plugins/bg/src/main/resources/META-INF/pluginIcon.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<!-- Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="2.5" y="2.5" width="11" height="11" rx="1.5" stroke="#3574F0"/>
|
||||
<path d="M2.5 9.33566L4.1822 7.66899C4.56052 7.29415 5.16625 7.28159 5.55979 7.64043L11.9861 13.5" stroke="#3574F0"/>
|
||||
<circle cx="10" cy="6" r="1.5" stroke="#3574F0"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 472 B |
@@ -0,0 +1,6 @@
|
||||
<!-- Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="2.5" y="2.5" width="11" height="11" rx="1.5" stroke="#548AF7"/>
|
||||
<path d="M2.5 9.33566L4.1822 7.66899C4.56052 7.29415 5.16625 7.28159 5.55979 7.64043L11.9861 13.5" stroke="#548AF7"/>
|
||||
<circle cx="10" cy="6" r="1.5" stroke="#548AF7"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 472 B |
2
plugins/bg/src/main/resources/i18n/messages.properties
Normal file
2
plugins/bg/src/main/resources/i18n/messages.properties
Normal file
@@ -0,0 +1,2 @@
|
||||
termora.plugins.bg.interval=Interval
|
||||
termora.plugins.bg.background-image=Background Image
|
||||
@@ -0,0 +1,2 @@
|
||||
termora.plugins.bg.background-image=背景图
|
||||
termora.plugins.bg.interval=切换间隔
|
||||
@@ -0,0 +1,2 @@
|
||||
termora.plugins.bg.background-image=背景圖
|
||||
termora.plugins.bg.interval=切換間隔
|
||||
Reference in New Issue
Block a user