mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-15 18:02:58 +08:00
feat: without jbr
This commit is contained in:
@@ -118,7 +118,6 @@ dependencies {
|
|||||||
|
|
||||||
application {
|
application {
|
||||||
val args = mutableListOf(
|
val args = mutableListOf(
|
||||||
"--add-exports java.base/sun.nio.ch=ALL-UNNAMED",
|
|
||||||
"-Xmx2g",
|
"-Xmx2g",
|
||||||
"-XX:+UseZGC",
|
"-XX:+UseZGC",
|
||||||
"-XX:+ZUncommit",
|
"-XX:+ZUncommit",
|
||||||
@@ -127,6 +126,10 @@ application {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (os.isMacOsX) {
|
if (os.isMacOsX) {
|
||||||
|
// macOS NSWindow
|
||||||
|
args.add("--add-opens java.desktop/java.awt=ALL-UNNAMED")
|
||||||
|
args.add("--add-opens java.desktop/sun.lwawt=ALL-UNNAMED")
|
||||||
|
args.add("--add-opens java.desktop/sun.lwawt.macosx=ALL-UNNAMED")
|
||||||
args.add("-Dsun.java2d.metal=true")
|
args.add("-Dsun.java2d.metal=true")
|
||||||
args.add("-Dapple.awt.application.appearance=system")
|
args.add("-Dapple.awt.application.appearance=system")
|
||||||
}
|
}
|
||||||
@@ -344,6 +347,10 @@ tasks.register<Exec>("jpackage") {
|
|||||||
options.add("-Dsun.java2d.metal=true")
|
options.add("-Dsun.java2d.metal=true")
|
||||||
|
|
||||||
if (os.isMacOsX) {
|
if (os.isMacOsX) {
|
||||||
|
// NSWindow
|
||||||
|
options.add("--add-opens java.desktop/java.awt=ALL-UNNAMED")
|
||||||
|
options.add("--add-opens java.desktop/sun.lwawt=ALL-UNNAMED")
|
||||||
|
options.add("--add-opens java.desktop/sun.lwawt.macosx=ALL-UNNAMED")
|
||||||
options.add("-Dapple.awt.application.appearance=system")
|
options.add("-Dapple.awt.application.appearance=system")
|
||||||
options.add("--add-opens java.desktop/sun.lwawt.macosx.concurrent=ALL-UNNAMED")
|
options.add("--add-opens java.desktop/sun.lwawt.macosx.concurrent=ALL-UNNAMED")
|
||||||
}
|
}
|
||||||
@@ -420,15 +427,9 @@ tasks.register<Exec>("jpackage") {
|
|||||||
|
|
||||||
tasks.register("dist") {
|
tasks.register("dist") {
|
||||||
doLast {
|
doLast {
|
||||||
val vendor = Jvm.current().vendor ?: StringUtils.EMPTY
|
|
||||||
@Suppress("UnstableApiUsage")
|
|
||||||
if (!JvmVendorSpec.JETBRAINS.matches(vendor)) {
|
|
||||||
throw GradleException("JVM: $vendor is not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
val gradlew = File(projectDir, if (os.isWindows) "gradlew.bat" else "gradlew").absolutePath
|
val gradlew = File(projectDir, if (os.isWindows) "gradlew.bat" else "gradlew").absolutePath
|
||||||
|
|
||||||
|
|
||||||
// 清空目录
|
// 清空目录
|
||||||
exec { commandLine(gradlew, "clean") }
|
exec { commandLine(gradlew, "clean") }
|
||||||
|
|
||||||
@@ -735,8 +736,6 @@ fun stapleMacOSLocalFile(file: File) {
|
|||||||
kotlin {
|
kotlin {
|
||||||
jvmToolchain {
|
jvmToolchain {
|
||||||
languageVersion = JavaLanguageVersion.of(21)
|
languageVersion = JavaLanguageVersion.of(21)
|
||||||
@Suppress("UnstableApiUsage")
|
|
||||||
vendor = JvmVendorSpec.JETBRAINS
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ class ApplicationRunner {
|
|||||||
|
|
||||||
private fun setupLaf() {
|
private fun setupLaf() {
|
||||||
|
|
||||||
System.setProperty(FlatSystemProperties.USE_WINDOW_DECORATIONS, "${SystemInfo.isLinux}")
|
System.setProperty(FlatSystemProperties.USE_WINDOW_DECORATIONS, "${SystemInfo.isLinux || SystemInfo.isWindows}")
|
||||||
System.setProperty(FlatSystemProperties.USE_ROUNDED_POPUP_BORDER, "false")
|
System.setProperty(FlatSystemProperties.USE_ROUNDED_POPUP_BORDER, "false")
|
||||||
|
|
||||||
if (SystemInfo.isLinux) {
|
if (SystemInfo.isLinux) {
|
||||||
@@ -152,15 +152,6 @@ class ApplicationRunner {
|
|||||||
JDialog.setDefaultLookAndFeelDecorated(true)
|
JDialog.setDefaultLookAndFeelDecorated(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
UIManager.put(
|
|
||||||
"FileChooser.${if (SystemInfo.isWindows) "win32" else "other"}.newFolder",
|
|
||||||
I18n.getString("termora.welcome.contextmenu.new.folder.name")
|
|
||||||
)
|
|
||||||
UIManager.put(
|
|
||||||
"FileChooser.${if (SystemInfo.isWindows) "win32" else "other"}.newFolder.subsequent",
|
|
||||||
"${I18n.getString("termora.welcome.contextmenu.new.folder.name")}.{0}"
|
|
||||||
)
|
|
||||||
|
|
||||||
val themeManager = ThemeManager.getInstance()
|
val themeManager = ThemeManager.getInstance()
|
||||||
val appearance = Database.getDatabase().appearance
|
val appearance = Database.getDatabase().appearance
|
||||||
var theme = appearance.theme
|
var theme = appearance.theme
|
||||||
@@ -176,8 +167,8 @@ class ApplicationRunner {
|
|||||||
themeManager.change(theme, true)
|
themeManager.change(theme, true)
|
||||||
|
|
||||||
|
|
||||||
if (Application.isUnknownVersion())
|
// if (Application.isUnknownVersion())
|
||||||
FlatInspector.install("ctrl shift alt X")
|
FlatInspector.install("ctrl X")
|
||||||
|
|
||||||
UIManager.put(FlatClientProperties.FULL_WINDOW_CONTENT, true)
|
UIManager.put(FlatClientProperties.FULL_WINDOW_CONTENT, true)
|
||||||
UIManager.put(FlatClientProperties.USE_WINDOW_DECORATIONS, false)
|
UIManager.put(FlatClientProperties.USE_WINDOW_DECORATIONS, false)
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ package app.termora
|
|||||||
|
|
||||||
import app.termora.actions.AnAction
|
import app.termora.actions.AnAction
|
||||||
import app.termora.actions.AnActionEvent
|
import app.termora.actions.AnActionEvent
|
||||||
|
import app.termora.native.osx.NativeMacLibrary
|
||||||
import com.formdev.flatlaf.FlatClientProperties
|
import com.formdev.flatlaf.FlatClientProperties
|
||||||
import com.formdev.flatlaf.FlatLaf
|
|
||||||
import com.formdev.flatlaf.util.SystemInfo
|
import com.formdev.flatlaf.util.SystemInfo
|
||||||
import com.jetbrains.JBR
|
import com.jetbrains.JBR
|
||||||
import java.awt.*
|
import java.awt.*
|
||||||
@@ -12,29 +12,60 @@ import java.awt.event.WindowAdapter
|
|||||||
import java.awt.event.WindowEvent
|
import java.awt.event.WindowEvent
|
||||||
import javax.swing.*
|
import javax.swing.*
|
||||||
|
|
||||||
|
|
||||||
abstract class DialogWrapper(owner: Window?) : JDialog(owner) {
|
abstract class DialogWrapper(owner: Window?) : JDialog(owner) {
|
||||||
private val rootPanel = JPanel(BorderLayout())
|
|
||||||
private val titleLabel = JLabel()
|
private val titleLabel = JLabel()
|
||||||
private val titleBar by lazy { LogicCustomTitleBar.createCustomTitleBar(this) }
|
|
||||||
val disposable = Disposer.newDisposable()
|
val disposable = Disposer.newDisposable()
|
||||||
|
private val customTitleBar = if (SystemInfo.isMacOS && JBR.isWindowDecorationsSupported())
|
||||||
|
JBR.getWindowDecorations().createCustomTitleBar() else null
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val DEFAULT_ACTION = "DEFAULT_ACTION"
|
const val DEFAULT_ACTION = "DEFAULT_ACTION"
|
||||||
private const val PROCESS_GLOBAL_KEYMAP = "PROCESS_GLOBAL_KEYMAP"
|
private const val PROCESS_GLOBAL_KEYMAP = "PROCESS_GLOBAL_KEYMAP"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected var controlsVisible = true
|
protected var controlsVisible = true
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
titleBar.putProperty("controls.visible", value)
|
if (SystemInfo.isMacOS) {
|
||||||
|
if (customTitleBar != null) {
|
||||||
|
customTitleBar.putProperty("controls.visible", value)
|
||||||
|
} else {
|
||||||
|
NativeMacLibrary.setControlsVisible(this, value)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rootPane.putClientProperty(FlatClientProperties.TITLE_BAR_SHOW_ICONIFFY, value)
|
||||||
|
rootPane.putClientProperty(FlatClientProperties.TITLE_BAR_SHOW_MAXIMIZE, value)
|
||||||
|
rootPane.putClientProperty(FlatClientProperties.TITLE_BAR_SHOW_CLOSE, value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected var titleBarHeight = UIManager.getInt("TabbedPane.tabHeight").toFloat()
|
protected var fullWindowContent = false
|
||||||
set(value) {
|
set(value) {
|
||||||
titleBar.height = value
|
|
||||||
field = value
|
field = value
|
||||||
|
rootPane.putClientProperty(FlatClientProperties.FULL_WINDOW_CONTENT, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected var titleVisible = true
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
rootPane.putClientProperty(FlatClientProperties.TITLE_BAR_SHOW_TITLE, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected var titleIconVisible = false
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
rootPane.putClientProperty(FlatClientProperties.TITLE_BAR_SHOW_ICON, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected var titleBarHeight = UIManager.getInt("TabbedPane.tabHeight")
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
if (SystemInfo.isMacOS) {
|
||||||
|
customTitleBar?.height = height.toFloat()
|
||||||
|
} else {
|
||||||
|
rootPane.putClientProperty(FlatClientProperties.TITLE_BAR_HEIGHT, value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected var lostFocusDispose = false
|
protected var lostFocusDispose = false
|
||||||
@@ -51,25 +82,43 @@ abstract class DialogWrapper(owner: Window?) : JDialog(owner) {
|
|||||||
super.rootPane.putClientProperty(PROCESS_GLOBAL_KEYMAP, value)
|
super.rootPane.putClientProperty(PROCESS_GLOBAL_KEYMAP, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
super.setDefaultCloseOperation(DISPOSE_ON_CLOSE)
|
||||||
|
|
||||||
|
// 使用 FlatLaf 的 TitlePane
|
||||||
|
if (SystemInfo.isWindows || SystemInfo.isLinux) {
|
||||||
|
rootPane.windowDecorationStyle = JRootPane.PLAIN_DIALOG
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected fun init() {
|
protected fun init() {
|
||||||
|
|
||||||
|
|
||||||
defaultCloseOperation = DISPOSE_ON_CLOSE
|
|
||||||
|
|
||||||
initTitleBar()
|
|
||||||
initEvents()
|
initEvents()
|
||||||
|
|
||||||
if (JBR.isWindowDecorationsSupported()) {
|
val rootPanel = JPanel(BorderLayout())
|
||||||
if (rootPane.getClientProperty(FlatClientProperties.TITLE_BAR_SHOW_TITLE) != false) {
|
rootPanel.add(createCenterPanel(), BorderLayout.CENTER)
|
||||||
val titlePanel = createTitlePanel()
|
|
||||||
if (titlePanel != null) {
|
if (SystemInfo.isMacOS) {
|
||||||
rootPanel.add(titlePanel, BorderLayout.NORTH)
|
rootPane.putClientProperty("apple.awt.windowTitleVisible", false)
|
||||||
}
|
rootPane.putClientProperty("apple.awt.fullWindowContent", true)
|
||||||
|
rootPane.putClientProperty("apple.awt.transparentTitleBar", true)
|
||||||
|
rootPane.putClientProperty(
|
||||||
|
FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING,
|
||||||
|
FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING_MEDIUM
|
||||||
|
)
|
||||||
|
|
||||||
|
val titlePanel = createTitlePanel()
|
||||||
|
if (titlePanel != null) {
|
||||||
|
rootPanel.add(titlePanel, BorderLayout.NORTH)
|
||||||
|
}
|
||||||
|
|
||||||
|
val customTitleBar = this.customTitleBar
|
||||||
|
if (customTitleBar != null) {
|
||||||
|
customTitleBar.putProperty("controls.visible", controlsVisible)
|
||||||
|
customTitleBar.height = titleBarHeight.toFloat()
|
||||||
|
JBR.getWindowDecorations().setCustomTitleBar(this, customTitleBar)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rootPanel.add(createCenterPanel(), BorderLayout.CENTER)
|
|
||||||
|
|
||||||
val southPanel = createSouthPanel()
|
val southPanel = createSouthPanel()
|
||||||
if (southPanel != null) {
|
if (southPanel != null) {
|
||||||
rootPanel.add(southPanel, BorderLayout.SOUTH)
|
rootPanel.add(southPanel, BorderLayout.SOUTH)
|
||||||
@@ -122,7 +171,7 @@ abstract class DialogWrapper(owner: Window?) : JDialog(owner) {
|
|||||||
|
|
||||||
val panel = JPanel(BorderLayout())
|
val panel = JPanel(BorderLayout())
|
||||||
panel.add(titleLabel, BorderLayout.CENTER)
|
panel.add(titleLabel, BorderLayout.CENTER)
|
||||||
panel.preferredSize = Dimension(-1, titleBar.height.toInt())
|
panel.preferredSize = Dimension(-1, titleBarHeight)
|
||||||
|
|
||||||
|
|
||||||
return panel
|
return panel
|
||||||
@@ -191,30 +240,20 @@ abstract class DialogWrapper(owner: Window?) : JDialog(owner) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (SystemInfo.isWindows) {
|
|
||||||
addWindowListener(object : WindowAdapter(), ThemeChangeListener {
|
|
||||||
override fun windowClosed(e: WindowEvent) {
|
|
||||||
ThemeManager.getInstance().removeThemeChangeListener(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun windowOpened(e: WindowEvent) {
|
|
||||||
onChanged()
|
|
||||||
ThemeManager.getInstance().addThemeChangeListener(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onChanged() {
|
|
||||||
titleBar.putProperty("controls.dark", FlatLaf.isLafDark())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initTitleBar() {
|
override fun addNotify() {
|
||||||
titleBar.height = titleBarHeight
|
super.addNotify()
|
||||||
titleBar.putProperty("controls.visible", controlsVisible)
|
|
||||||
if (JBR.isWindowDecorationsSupported()) {
|
// 显示后触发一次重绘制
|
||||||
JBR.getWindowDecorations().setCustomTitleBar(this, titleBar)
|
if (SystemInfo.isWindows || SystemInfo.isLinux) {
|
||||||
|
this.controlsVisible = controlsVisible
|
||||||
|
this.titleBarHeight = titleBarHeight
|
||||||
|
this.titleIconVisible = titleIconVisible
|
||||||
|
this.titleVisible = titleVisible
|
||||||
|
this.fullWindowContent = fullWindowContent
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun doOKAction() {
|
protected open fun doOKAction() {
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
package app.termora
|
|
||||||
|
|
||||||
import com.formdev.flatlaf.extras.components.FlatTextField
|
|
||||||
import org.apache.commons.lang3.StringUtils
|
|
||||||
import java.awt.Window
|
|
||||||
import java.awt.event.KeyAdapter
|
|
||||||
import java.awt.event.KeyEvent
|
|
||||||
import javax.swing.BorderFactory
|
|
||||||
import javax.swing.JComponent
|
|
||||||
import javax.swing.UIManager
|
|
||||||
|
|
||||||
class InputDialog(
|
|
||||||
owner: Window,
|
|
||||||
title: String,
|
|
||||||
text: String = StringUtils.EMPTY,
|
|
||||||
placeholderText: String = StringUtils.EMPTY
|
|
||||||
) : DialogWrapper(owner) {
|
|
||||||
private val textField = FlatTextField()
|
|
||||||
private var text: String? = null
|
|
||||||
|
|
||||||
init {
|
|
||||||
setSize(340, 60)
|
|
||||||
setLocationRelativeTo(owner)
|
|
||||||
|
|
||||||
super.setTitle(title)
|
|
||||||
|
|
||||||
isResizable = false
|
|
||||||
isModal = true
|
|
||||||
controlsVisible = false
|
|
||||||
titleBarHeight = UIManager.getInt("TabbedPane.tabHeight") * 0.8f
|
|
||||||
|
|
||||||
|
|
||||||
textField.placeholderText = placeholderText
|
|
||||||
textField.text = text
|
|
||||||
textField.addKeyListener(object : KeyAdapter() {
|
|
||||||
override fun keyPressed(e: KeyEvent) {
|
|
||||||
if (e.keyCode == KeyEvent.VK_ENTER) {
|
|
||||||
if (textField.text.isBlank()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
doOKAction()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
init()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun createCenterPanel(): JComponent {
|
|
||||||
textField.background = UIManager.getColor("window")
|
|
||||||
textField.border = BorderFactory.createEmptyBorder(0, 13, 0, 13)
|
|
||||||
|
|
||||||
return textField
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getText(): String? {
|
|
||||||
isVisible = true
|
|
||||||
return text
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun doCancelAction() {
|
|
||||||
text = null
|
|
||||||
super.doCancelAction()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun doOKAction() {
|
|
||||||
text = textField.text
|
|
||||||
super.doOKAction()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun createSouthPanel(): JComponent? {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
package app.termora
|
|
||||||
|
|
||||||
import com.formdev.flatlaf.FlatClientProperties
|
|
||||||
import com.jetbrains.JBR
|
|
||||||
import com.jetbrains.WindowDecorations.CustomTitleBar
|
|
||||||
import java.awt.Rectangle
|
|
||||||
import java.awt.Window
|
|
||||||
import javax.swing.RootPaneContainer
|
|
||||||
|
|
||||||
class LogicCustomTitleBar(private val titleBar: CustomTitleBar) : CustomTitleBar {
|
|
||||||
companion object {
|
|
||||||
fun createCustomTitleBar(rootPaneContainer: RootPaneContainer): CustomTitleBar {
|
|
||||||
if (!JBR.isWindowDecorationsSupported()) {
|
|
||||||
return LogicCustomTitleBar(object : CustomTitleBar {
|
|
||||||
override fun getHeight(): Float {
|
|
||||||
val bounds = rootPaneContainer.rootPane
|
|
||||||
.getClientProperty(FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_BOUNDS)
|
|
||||||
if (bounds is Rectangle) {
|
|
||||||
return bounds.height.toFloat()
|
|
||||||
}
|
|
||||||
return 0f
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setHeight(height: Float) {
|
|
||||||
rootPaneContainer.rootPane.putClientProperty(
|
|
||||||
FlatClientProperties.TITLE_BAR_HEIGHT,
|
|
||||||
height.toInt()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getProperties(): MutableMap<String, Any> {
|
|
||||||
return mutableMapOf()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun putProperties(m: MutableMap<String, *>?) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun putProperty(key: String?, value: Any?) {
|
|
||||||
if (key == "controls.visible" && value is Boolean) {
|
|
||||||
rootPaneContainer.rootPane.putClientProperty(
|
|
||||||
FlatClientProperties.TITLE_BAR_SHOW_CLOSE,
|
|
||||||
value
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getLeftInset(): Float {
|
|
||||||
return 0f
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getRightInset(): Float {
|
|
||||||
val bounds = rootPaneContainer.rootPane
|
|
||||||
.getClientProperty(FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_BOUNDS)
|
|
||||||
if (bounds is Rectangle) {
|
|
||||||
return bounds.width.toFloat()
|
|
||||||
}
|
|
||||||
return 0f
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun forceHitTest(client: Boolean) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getContainingWindow(): Window {
|
|
||||||
return rootPaneContainer as Window
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return JBR.getWindowDecorations().createCustomTitleBar()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getHeight(): Float {
|
|
||||||
return titleBar.height
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setHeight(height: Float) {
|
|
||||||
titleBar.height = height
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getProperties(): MutableMap<String, Any> {
|
|
||||||
return titleBar.properties
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun putProperties(m: MutableMap<String, *>?) {
|
|
||||||
titleBar.putProperties(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun putProperty(key: String?, value: Any?) {
|
|
||||||
titleBar.putProperty(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getLeftInset(): Float {
|
|
||||||
return titleBar.leftInset
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getRightInset(): Float {
|
|
||||||
return titleBar.rightInset
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun forceHitTest(client: Boolean) {
|
|
||||||
titleBar.forceHitTest(client)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getContainingWindow(): Window {
|
|
||||||
return titleBar.containingWindow
|
|
||||||
}
|
|
||||||
}
|
|
||||||
11
src/main/kotlin/app/termora/MyFlatRootPaneUI.kt
Normal file
11
src/main/kotlin/app/termora/MyFlatRootPaneUI.kt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package app.termora
|
||||||
|
|
||||||
|
import com.formdev.flatlaf.ui.FlatRootPaneUI
|
||||||
|
import com.formdev.flatlaf.ui.FlatTitlePane
|
||||||
|
|
||||||
|
class MyFlatRootPaneUI : FlatRootPaneUI() {
|
||||||
|
|
||||||
|
fun getTitlePane(): FlatTitlePane? {
|
||||||
|
return super.titlePane
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
package app.termora
|
package app.termora
|
||||||
|
|
||||||
|
import app.termora.native.osx.NativeMacLibrary
|
||||||
import com.formdev.flatlaf.FlatClientProperties
|
import com.formdev.flatlaf.FlatClientProperties
|
||||||
import com.formdev.flatlaf.extras.components.FlatTextPane
|
import com.formdev.flatlaf.extras.components.FlatTextPane
|
||||||
import com.formdev.flatlaf.util.SystemInfo
|
import com.formdev.flatlaf.util.SystemInfo
|
||||||
import com.jetbrains.JBR
|
import com.jetbrains.JBR
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.swing.Swing
|
import kotlinx.coroutines.swing.Swing
|
||||||
|
import org.apache.commons.lang3.StringUtils
|
||||||
import java.awt.BorderLayout
|
import java.awt.BorderLayout
|
||||||
import java.awt.Component
|
import java.awt.Component
|
||||||
import java.awt.Desktop
|
import java.awt.Desktop
|
||||||
@@ -113,6 +115,36 @@ object OptionPane {
|
|||||||
dialog.dispose()
|
dialog.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun showInputDialog(
|
||||||
|
parentComponent: Component?,
|
||||||
|
title: String = UIManager.getString("OptionPane.messageDialogTitle"),
|
||||||
|
value: String = StringUtils.EMPTY,
|
||||||
|
placeholder: String = StringUtils.EMPTY,
|
||||||
|
): String? {
|
||||||
|
val pane = JOptionPane(StringUtils.EMPTY, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION)
|
||||||
|
val dialog = initDialog(pane.createDialog(parentComponent, title))
|
||||||
|
pane.wantsInput = true
|
||||||
|
pane.initialSelectionValue = value
|
||||||
|
|
||||||
|
val textField = SwingUtils.getDescendantsOfType(JTextField::class.java, pane, true).firstOrNull()
|
||||||
|
if (textField?.name == "OptionPane.textField") {
|
||||||
|
textField.border = BorderFactory.createCompoundBorder(
|
||||||
|
BorderFactory.createMatteBorder(0, 0, 1, 0, DynamicColor.BorderColor),
|
||||||
|
BorderFactory.createEmptyBorder(0, 0, 2, 0)
|
||||||
|
)
|
||||||
|
textField.background = UIManager.getColor("window")
|
||||||
|
textField.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, placeholder)
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.isVisible = true
|
||||||
|
dialog.dispose()
|
||||||
|
|
||||||
|
val inputValue = pane.inputValue
|
||||||
|
if (inputValue == JOptionPane.UNINITIALIZED_VALUE) return null
|
||||||
|
|
||||||
|
return inputValue as? String
|
||||||
|
}
|
||||||
|
|
||||||
fun openFileInFolder(
|
fun openFileInFolder(
|
||||||
parentComponent: Component,
|
parentComponent: Component,
|
||||||
file: File,
|
file: File,
|
||||||
@@ -140,14 +172,31 @@ object OptionPane {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun initDialog(dialog: JDialog): JDialog {
|
private fun initDialog(dialog: JDialog): JDialog {
|
||||||
|
if (SystemInfo.isWindows || SystemInfo.isLinux) {
|
||||||
|
dialog.rootPane.putClientProperty(FlatClientProperties.TITLE_BAR_SHOW_CLOSE, false)
|
||||||
|
dialog.rootPane.putClientProperty(
|
||||||
|
FlatClientProperties.TITLE_BAR_HEIGHT,
|
||||||
|
UIManager.getInt("TabbedPane.tabHeight")
|
||||||
|
)
|
||||||
|
} else if (SystemInfo.isMacOS) {
|
||||||
|
dialog.rootPane.putClientProperty("apple.awt.windowTitleVisible", false)
|
||||||
|
dialog.rootPane.putClientProperty("apple.awt.fullWindowContent", true)
|
||||||
|
dialog.rootPane.putClientProperty("apple.awt.transparentTitleBar", true)
|
||||||
|
dialog.rootPane.putClientProperty(
|
||||||
|
FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING,
|
||||||
|
FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING_MEDIUM
|
||||||
|
)
|
||||||
|
|
||||||
if (JBR.isWindowDecorationsSupported()) {
|
|
||||||
|
|
||||||
val windowDecorations = JBR.getWindowDecorations()
|
val height = UIManager.getInt("TabbedPane.tabHeight") - 10
|
||||||
val titleBar = windowDecorations.createCustomTitleBar()
|
if (JBR.isWindowDecorationsSupported()) {
|
||||||
titleBar.putProperty("controls.visible", false)
|
val customTitleBar = JBR.getWindowDecorations().createCustomTitleBar()
|
||||||
titleBar.height = UIManager.getInt("TabbedPane.tabHeight") - if (SystemInfo.isMacOS) 10f else 6f
|
customTitleBar.putProperty("controls.visible", false)
|
||||||
windowDecorations.setCustomTitleBar(dialog, titleBar)
|
customTitleBar.height = height.toFloat()
|
||||||
|
JBR.getWindowDecorations().setCustomTitleBar(dialog, customTitleBar)
|
||||||
|
} else {
|
||||||
|
NativeMacLibrary.setControlsVisible(dialog, false)
|
||||||
|
}
|
||||||
|
|
||||||
val label = JLabel(dialog.title)
|
val label = JLabel(dialog.title)
|
||||||
label.putClientProperty(FlatClientProperties.STYLE, "font: bold")
|
label.putClientProperty(FlatClientProperties.STYLE, "font: bold")
|
||||||
@@ -155,11 +204,9 @@ object OptionPane {
|
|||||||
box.add(Box.createHorizontalGlue())
|
box.add(Box.createHorizontalGlue())
|
||||||
box.add(label)
|
box.add(label)
|
||||||
box.add(Box.createHorizontalGlue())
|
box.add(Box.createHorizontalGlue())
|
||||||
box.preferredSize = Dimension(-1, titleBar.height.toInt())
|
box.preferredSize = Dimension(-1, height)
|
||||||
|
|
||||||
dialog.contentPane.add(box, BorderLayout.NORTH)
|
dialog.contentPane.add(box, BorderLayout.NORTH)
|
||||||
}
|
}
|
||||||
|
|
||||||
return dialog
|
return dialog
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -190,12 +190,11 @@ class TerminalTabbed(
|
|||||||
val rename = popupMenu.add(I18n.getString("termora.tabbed.contextmenu.rename"))
|
val rename = popupMenu.add(I18n.getString("termora.tabbed.contextmenu.rename"))
|
||||||
rename.addActionListener {
|
rename.addActionListener {
|
||||||
if (tabIndex > 0) {
|
if (tabIndex > 0) {
|
||||||
val dialog = InputDialog(
|
val text = OptionPane.showInputDialog(
|
||||||
SwingUtilities.getWindowAncestor(this),
|
SwingUtilities.getWindowAncestor(this),
|
||||||
title = rename.text,
|
title = rename.text,
|
||||||
text = tabbedPane.getTitleAt(tabIndex),
|
value = tabbedPane.getTitleAt(tabIndex)
|
||||||
)
|
)
|
||||||
val text = dialog.getText()
|
|
||||||
if (!text.isNullOrBlank()) {
|
if (!text.isNullOrBlank()) {
|
||||||
tabbedPane.setTitleAt(tabIndex, text)
|
tabbedPane.setTitleAt(tabIndex, text)
|
||||||
c.putClientProperty(titleProperty, text)
|
c.putClientProperty(titleProperty, text)
|
||||||
|
|||||||
@@ -7,21 +7,19 @@ import app.termora.actions.DataProviders
|
|||||||
import app.termora.sftp.SFTPTab
|
import app.termora.sftp.SFTPTab
|
||||||
import app.termora.terminal.DataKey
|
import app.termora.terminal.DataKey
|
||||||
import com.formdev.flatlaf.FlatClientProperties
|
import com.formdev.flatlaf.FlatClientProperties
|
||||||
import com.formdev.flatlaf.FlatLaf
|
|
||||||
import com.formdev.flatlaf.util.SystemInfo
|
import com.formdev.flatlaf.util.SystemInfo
|
||||||
import com.jetbrains.JBR
|
import com.jetbrains.JBR
|
||||||
|
import java.awt.BorderLayout
|
||||||
import java.awt.Dimension
|
import java.awt.Dimension
|
||||||
import java.awt.Insets
|
import java.awt.Insets
|
||||||
import java.awt.event.MouseAdapter
|
import java.awt.event.MouseAdapter
|
||||||
import java.awt.event.MouseEvent
|
import java.awt.event.MouseEvent
|
||||||
|
import java.awt.event.MouseListener
|
||||||
|
import java.awt.event.MouseMotionListener
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.imageio.ImageIO
|
import javax.imageio.ImageIO
|
||||||
import javax.swing.Box
|
import javax.swing.*
|
||||||
import javax.swing.JFrame
|
|
||||||
import javax.swing.SwingUtilities
|
|
||||||
import javax.swing.SwingUtilities.isEventDispatchThread
|
import javax.swing.SwingUtilities.isEventDispatchThread
|
||||||
import javax.swing.UIManager
|
|
||||||
import kotlin.math.max
|
|
||||||
|
|
||||||
fun assertEventDispatchThread() {
|
fun assertEventDispatchThread() {
|
||||||
if (!isEventDispatchThread()) throw WrongThreadException("AWT EventQueue")
|
if (!isEventDispatchThread()) throw WrongThreadException("AWT EventQueue")
|
||||||
@@ -33,14 +31,13 @@ class TermoraFrame : JFrame(), DataProvider {
|
|||||||
|
|
||||||
private val id = UUID.randomUUID().toString()
|
private val id = UUID.randomUUID().toString()
|
||||||
private val windowScope = ApplicationScope.forWindowScope(this)
|
private val windowScope = ApplicationScope.forWindowScope(this)
|
||||||
private val titleBar = LogicCustomTitleBar.createCustomTitleBar(this)
|
|
||||||
private val tabbedPane = MyTabbedPane()
|
private val tabbedPane = MyTabbedPane()
|
||||||
private val toolbar = TermoraToolBar(windowScope, titleBar, tabbedPane)
|
private val toolbar = TermoraToolBar(windowScope, this, tabbedPane)
|
||||||
private val terminalTabbed = TerminalTabbed(windowScope, toolbar, tabbedPane)
|
private val terminalTabbed = TerminalTabbed(windowScope, toolbar, tabbedPane)
|
||||||
private val isWindowDecorationsSupported by lazy { JBR.isWindowDecorationsSupported() }
|
|
||||||
private val dataProviderSupport = DataProviderSupport()
|
private val dataProviderSupport = DataProviderSupport()
|
||||||
private val welcomePanel = WelcomePanel(windowScope)
|
private val welcomePanel = WelcomePanel(windowScope)
|
||||||
private val sftp get() = Database.getDatabase().sftp
|
private val sftp get() = Database.getDatabase().sftp
|
||||||
|
private val myUI = MyFlatRootPaneUI()
|
||||||
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -49,44 +46,146 @@ class TermoraFrame : JFrame(), DataProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun initEvents() {
|
private fun initEvents() {
|
||||||
|
if (SystemInfo.isLinux) {
|
||||||
|
val mouseAdapter = object : MouseAdapter() {
|
||||||
|
override fun mouseClicked(e: MouseEvent) {
|
||||||
|
getMouseHandler()?.mouseClicked(e)
|
||||||
|
}
|
||||||
|
|
||||||
forceHitTest()
|
override fun mousePressed(e: MouseEvent) {
|
||||||
|
getMouseHandler()?.mousePressed(e)
|
||||||
|
}
|
||||||
|
|
||||||
// macos 需要判断是否全部删除
|
override fun mouseDragged(e: MouseEvent) {
|
||||||
// 当 Tab 为 0 的时候,需要加一个边距,避开控制栏
|
val mouseLayer = getMouseLayer() ?: return
|
||||||
if (SystemInfo.isMacOS && isWindowDecorationsSupported) {
|
getMouseMotionListener()?.mouseDragged(
|
||||||
tabbedPane.addChangeListener {
|
MouseEvent(
|
||||||
tabbedPane.leadingComponent = if (tabbedPane.tabCount == 0) {
|
mouseLayer,
|
||||||
Box.createHorizontalStrut(titleBar.leftInset.toInt())
|
e.id,
|
||||||
} else {
|
e.`when`,
|
||||||
null
|
e.modifiersEx,
|
||||||
|
e.x,
|
||||||
|
e.y,
|
||||||
|
e.clickCount,
|
||||||
|
e.isPopupTrigger,
|
||||||
|
e.button
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getMouseHandler(): MouseListener? {
|
||||||
|
return getHandler() as? MouseListener
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getMouseMotionListener(): MouseMotionListener? {
|
||||||
|
return getHandler() as? MouseMotionListener
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getMouseLayer(): JComponent? {
|
||||||
|
val titlePane = myUI.getTitlePane() ?: return null
|
||||||
|
val handlerField = titlePane.javaClass.getDeclaredField("mouseLayer") ?: return null
|
||||||
|
handlerField.isAccessible = true
|
||||||
|
return handlerField.get(titlePane) as? JComponent
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getHandler(): Any? {
|
||||||
|
val titlePane = myUI.getTitlePane() ?: return null
|
||||||
|
val handlerField = titlePane.javaClass.getDeclaredField("handler") ?: return null
|
||||||
|
handlerField.isAccessible = true
|
||||||
|
return handlerField.get(titlePane)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
toolbar.getJToolBar().addMouseListener(mouseAdapter)
|
||||||
|
toolbar.getJToolBar().addMouseMotionListener(mouseAdapter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// force hit
|
||||||
|
if (SystemInfo.isMacOS) {
|
||||||
|
if (JBR.isWindowDecorationsSupported()) {
|
||||||
|
val height = UIManager.getInt("TabbedPane.tabHeight") + tabbedPane.tabAreaInsets.top
|
||||||
|
val customTitleBar = JBR.getWindowDecorations().createCustomTitleBar()
|
||||||
|
customTitleBar.height = height.toFloat()
|
||||||
|
|
||||||
// 监听主题变化 需要动态修改控制栏颜色
|
val mouseAdapter = object : MouseAdapter() {
|
||||||
if (SystemInfo.isWindows && isWindowDecorationsSupported) {
|
|
||||||
ThemeManager.getInstance().addThemeChangeListener(object : ThemeChangeListener {
|
private fun hit(e: MouseEvent) {
|
||||||
override fun onChanged() {
|
if (e.source == tabbedPane) {
|
||||||
titleBar.putProperty("controls.dark", FlatLaf.isLafDark())
|
val index = tabbedPane.indexAtLocation(e.x, e.y)
|
||||||
|
if (index >= 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
customTitleBar.forceHitTest(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun mouseClicked(e: MouseEvent) {
|
||||||
|
hit(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun mousePressed(e: MouseEvent) {
|
||||||
|
hit(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun mouseReleased(e: MouseEvent) {
|
||||||
|
hit(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun mouseEntered(e: MouseEvent) {
|
||||||
|
hit(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun mouseDragged(e: MouseEvent) {
|
||||||
|
hit(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun mouseMoved(e: MouseEvent) {
|
||||||
|
hit(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
terminalTabbed.addMouseListener(mouseAdapter)
|
||||||
|
terminalTabbed.addMouseMotionListener(mouseAdapter)
|
||||||
|
|
||||||
|
tabbedPane.addMouseListener(mouseAdapter)
|
||||||
|
tabbedPane.addMouseMotionListener(mouseAdapter)
|
||||||
|
|
||||||
|
toolbar.getJToolBar().addMouseListener(mouseAdapter)
|
||||||
|
toolbar.getJToolBar().addMouseMotionListener(mouseAdapter)
|
||||||
|
|
||||||
|
JBR.getWindowDecorations().setCustomTitleBar(this, customTitleBar)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun initView() {
|
private fun initView() {
|
||||||
if (isWindowDecorationsSupported) {
|
|
||||||
titleBar.height = UIManager.getInt("TabbedPane.tabHeight").toFloat()
|
// macOS 要避开左边的控制栏
|
||||||
titleBar.putProperty("controls.dark", FlatLaf.isLafDark())
|
if (SystemInfo.isMacOS) {
|
||||||
JBR.getWindowDecorations().setCustomTitleBar(this, titleBar)
|
tabbedPane.tabAreaInsets = Insets(0, 76, 0, 0)
|
||||||
|
} else if (SystemInfo.isWindows) {
|
||||||
|
// Windows 10 会有1像素误差
|
||||||
|
tabbedPane.tabAreaInsets = Insets(if (SystemInfo.isWindows_11_orLater) 1 else 2, 2, 0, 0)
|
||||||
|
} else if (SystemInfo.isLinux) {
|
||||||
|
rootPane.setUI(myUI)
|
||||||
|
tabbedPane.tabAreaInsets = Insets(1, 2, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SystemInfo.isLinux) {
|
val height = UIManager.getInt("TabbedPane.tabHeight") + tabbedPane.tabAreaInsets.top
|
||||||
|
|
||||||
|
if (SystemInfo.isWindows || SystemInfo.isLinux) {
|
||||||
rootPane.putClientProperty(FlatClientProperties.FULL_WINDOW_CONTENT, true)
|
rootPane.putClientProperty(FlatClientProperties.FULL_WINDOW_CONTENT, true)
|
||||||
rootPane.putClientProperty(FlatClientProperties.TITLE_BAR_HEIGHT, UIManager.getInt("TabbedPane.tabHeight"))
|
rootPane.putClientProperty(FlatClientProperties.TITLE_BAR_SHOW_ICON, false)
|
||||||
|
rootPane.putClientProperty(FlatClientProperties.TITLE_BAR_SHOW_TITLE, false)
|
||||||
|
rootPane.putClientProperty(FlatClientProperties.TITLE_BAR_HEIGHT, height)
|
||||||
|
} else if (SystemInfo.isMacOS) {
|
||||||
|
rootPane.putClientProperty("apple.awt.windowTitleVisible", false)
|
||||||
|
rootPane.putClientProperty("apple.awt.fullWindowContent", true)
|
||||||
|
rootPane.putClientProperty("apple.awt.transparentTitleBar", true)
|
||||||
|
rootPane.putClientProperty(
|
||||||
|
FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING,
|
||||||
|
FlatClientProperties.MACOS_WINDOW_BUTTONS_SPACING_MEDIUM
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SystemInfo.isWindows || SystemInfo.isLinux) {
|
if (SystemInfo.isWindows || SystemInfo.isLinux) {
|
||||||
@@ -102,88 +201,21 @@ class TermoraFrame : JFrame(), DataProvider {
|
|||||||
terminalTabbed.addTerminalTab(welcomePanel)
|
terminalTabbed.addTerminalTab(welcomePanel)
|
||||||
|
|
||||||
// 下一次事件循环检测是否固定 SFTP
|
// 下一次事件循环检测是否固定 SFTP
|
||||||
SwingUtilities.invokeLater {
|
if (sftp.pinTab) {
|
||||||
if (sftp.pinTab) {
|
SwingUtilities.invokeLater {
|
||||||
terminalTabbed.addTerminalTab(SFTPTab(), false)
|
terminalTabbed.addTerminalTab(SFTPTab(), false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// macOS 要避开左边的控制栏
|
|
||||||
if (SystemInfo.isMacOS) {
|
|
||||||
val left = max(titleBar.leftInset.toInt(), 76)
|
|
||||||
if (tabbedPane.tabCount == 0) {
|
|
||||||
tabbedPane.leadingComponent = Box.createHorizontalStrut(left)
|
|
||||||
} else {
|
|
||||||
tabbedPane.tabAreaInsets = Insets(0, left, 0, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Disposer.register(windowScope, terminalTabbed)
|
Disposer.register(windowScope, terminalTabbed)
|
||||||
add(terminalTabbed)
|
add(terminalTabbed, BorderLayout.CENTER)
|
||||||
|
|
||||||
dataProviderSupport.addData(DataProviders.TabbedPane, tabbedPane)
|
dataProviderSupport.addData(DataProviders.TabbedPane, tabbedPane)
|
||||||
dataProviderSupport.addData(DataProviders.TermoraFrame, this)
|
dataProviderSupport.addData(DataProviders.TermoraFrame, this)
|
||||||
dataProviderSupport.addData(DataProviders.WindowScope, windowScope)
|
dataProviderSupport.addData(DataProviders.WindowScope, windowScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun forceHitTest() {
|
|
||||||
val mouseAdapter = object : MouseAdapter() {
|
|
||||||
|
|
||||||
private fun hit(e: MouseEvent) {
|
|
||||||
if (e.source == tabbedPane) {
|
|
||||||
val index = tabbedPane.indexAtLocation(e.x, e.y)
|
|
||||||
if (index >= 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
titleBar.forceHitTest(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun mouseClicked(e: MouseEvent) {
|
|
||||||
hit(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun mousePressed(e: MouseEvent) {
|
|
||||||
if (e.source == toolbar.getJToolBar()) {
|
|
||||||
if (!isWindowDecorationsSupported && SwingUtilities.isLeftMouseButton(e)) {
|
|
||||||
if (JBR.isWindowMoveSupported()) {
|
|
||||||
JBR.getWindowMove().startMovingTogetherWithMouse(this@TermoraFrame, e.button)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
hit(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun mouseReleased(e: MouseEvent) {
|
|
||||||
hit(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun mouseEntered(e: MouseEvent) {
|
|
||||||
hit(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun mouseDragged(e: MouseEvent) {
|
|
||||||
|
|
||||||
hit(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun mouseMoved(e: MouseEvent) {
|
|
||||||
hit(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
terminalTabbed.addMouseListener(mouseAdapter)
|
|
||||||
terminalTabbed.addMouseMotionListener(mouseAdapter)
|
|
||||||
|
|
||||||
tabbedPane.addMouseListener(mouseAdapter)
|
|
||||||
tabbedPane.addMouseMotionListener(mouseAdapter)
|
|
||||||
|
|
||||||
toolbar.getJToolBar().addMouseListener(mouseAdapter)
|
|
||||||
toolbar.getJToolBar().addMouseMotionListener(mouseAdapter)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun <T : Any> getData(dataKey: DataKey<T>): T? {
|
override fun <T : Any> getData(dataKey: DataKey<T>): T? {
|
||||||
return dataProviderSupport.getData(dataKey)
|
return dataProviderSupport.getData(dataKey)
|
||||||
?: terminalTabbed.getData(dataKey)
|
?: terminalTabbed.getData(dataKey)
|
||||||
@@ -204,5 +236,22 @@ class TermoraFrame : JFrame(), DataProvider {
|
|||||||
return id.hashCode()
|
return id.hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun addNotify() {
|
||||||
|
super.addNotify()
|
||||||
|
|
||||||
|
val dialog = object : DialogWrapper(this@TermoraFrame) {
|
||||||
|
init {
|
||||||
|
init()
|
||||||
|
controlsVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createCenterPanel(): JComponent {
|
||||||
|
return JPanel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dialog.title = "Hello"
|
||||||
|
dialog.size = Dimension(800, 600)
|
||||||
|
dialog.setLocationRelativeTo(this)
|
||||||
|
// dialog.isVisible = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,13 +4,13 @@ import app.termora.Application.ohMyJson
|
|||||||
import app.termora.actions.*
|
import app.termora.actions.*
|
||||||
import app.termora.findeverywhere.FindEverywhereAction
|
import app.termora.findeverywhere.FindEverywhereAction
|
||||||
import app.termora.snippet.SnippetAction
|
import app.termora.snippet.SnippetAction
|
||||||
|
import com.formdev.flatlaf.FlatClientProperties
|
||||||
import com.formdev.flatlaf.extras.components.FlatTabbedPane
|
import com.formdev.flatlaf.extras.components.FlatTabbedPane
|
||||||
import com.formdev.flatlaf.util.SystemInfo
|
import com.formdev.flatlaf.util.SystemInfo
|
||||||
import com.jetbrains.WindowDecorations
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import org.apache.commons.lang3.StringUtils
|
import org.apache.commons.lang3.StringUtils
|
||||||
import org.jdesktop.swingx.action.ActionContainerFactory
|
import org.jdesktop.swingx.action.ActionContainerFactory
|
||||||
import java.awt.Insets
|
import java.awt.Rectangle
|
||||||
import java.awt.event.ComponentAdapter
|
import java.awt.event.ComponentAdapter
|
||||||
import java.awt.event.ComponentEvent
|
import java.awt.event.ComponentEvent
|
||||||
import javax.swing.Box
|
import javax.swing.Box
|
||||||
@@ -25,7 +25,7 @@ data class ToolBarAction(
|
|||||||
|
|
||||||
class TermoraToolBar(
|
class TermoraToolBar(
|
||||||
private val windowScope: WindowScope,
|
private val windowScope: WindowScope,
|
||||||
private val titleBar: WindowDecorations.CustomTitleBar,
|
private val frame: TermoraFrame,
|
||||||
private val tabbedPane: FlatTabbedPane
|
private val tabbedPane: FlatTabbedPane
|
||||||
) {
|
) {
|
||||||
private val properties by lazy { Database.getDatabase().properties }
|
private val properties by lazy { Database.getDatabase().properties }
|
||||||
@@ -155,14 +155,11 @@ class TermoraToolBar(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun adjust() {
|
fun adjust() {
|
||||||
if (SystemInfo.isMacOS) {
|
if (SystemInfo.isWindows || SystemInfo.isLinux) {
|
||||||
val left = titleBar.leftInset.toInt()
|
val rectangle =
|
||||||
if (tabbedPane.tabAreaInsets.left != left) {
|
frame.rootPane.getClientProperty(FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_BOUNDS)
|
||||||
tabbedPane.tabAreaInsets = Insets(0, left, 0, 0)
|
as? Rectangle ?: return
|
||||||
}
|
val right = rectangle.width
|
||||||
} else if (SystemInfo.isWindows || SystemInfo.isLinux) {
|
|
||||||
|
|
||||||
val right = titleBar.rightInset.toInt()
|
|
||||||
val toolbar = this@MyToolBar
|
val toolbar = this@MyToolBar
|
||||||
for (i in 0 until toolbar.componentCount) {
|
for (i in 0 until toolbar.componentCount) {
|
||||||
val c = toolbar.getComponent(i)
|
val c = toolbar.getComponent(i)
|
||||||
|
|||||||
@@ -7,9 +7,7 @@ import app.termora.WindowScope
|
|||||||
import app.termora.actions.AnAction
|
import app.termora.actions.AnAction
|
||||||
import app.termora.actions.AnActionEvent
|
import app.termora.actions.AnActionEvent
|
||||||
import app.termora.macro.MacroFindEverywhereProvider
|
import app.termora.macro.MacroFindEverywhereProvider
|
||||||
import com.formdev.flatlaf.FlatClientProperties
|
|
||||||
import com.formdev.flatlaf.extras.components.FlatTextField
|
import com.formdev.flatlaf.extras.components.FlatTextField
|
||||||
import com.jetbrains.JBR
|
|
||||||
import java.awt.BorderLayout
|
import java.awt.BorderLayout
|
||||||
import java.awt.Dimension
|
import java.awt.Dimension
|
||||||
import java.awt.Insets
|
import java.awt.Insets
|
||||||
@@ -45,17 +43,10 @@ class FindEverywhere(owner: Window, windowScope: WindowScope) : DialogWrapper(ow
|
|||||||
minimumSize = Dimension(size.width / 2, size.height / 2)
|
minimumSize = Dimension(size.width / 2, size.height / 2)
|
||||||
isModal = false
|
isModal = false
|
||||||
lostFocusDispose = true
|
lostFocusDispose = true
|
||||||
controlsVisible = false
|
|
||||||
defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
|
|
||||||
setLocationRelativeTo(null)
|
setLocationRelativeTo(null)
|
||||||
|
|
||||||
// 不支持装饰,铺满
|
|
||||||
if (!JBR.isWindowDecorationsSupported()) {
|
|
||||||
rootPane.putClientProperty(FlatClientProperties.FULL_WINDOW_CONTENT, true)
|
|
||||||
rootPane.putClientProperty(FlatClientProperties.TITLE_BAR_SHOW_CLOSE, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
rootPane.background = DynamicColor("desktop")
|
val desktopBackground = DynamicColor("desktop")
|
||||||
centerPanel.background = DynamicColor("desktop")
|
centerPanel.background = DynamicColor("desktop")
|
||||||
centerPanel.border = BorderFactory.createEmptyBorder(12, 12, 12, 12)
|
centerPanel.border = BorderFactory.createEmptyBorder(12, 12, 12, 12)
|
||||||
|
|
||||||
@@ -70,7 +61,7 @@ class FindEverywhere(owner: Window, windowScope: WindowScope) : DialogWrapper(ow
|
|||||||
resultList.isRolloverEnabled = false
|
resultList.isRolloverEnabled = false
|
||||||
resultList.selectionMode = ListSelectionModel.SINGLE_SELECTION
|
resultList.selectionMode = ListSelectionModel.SINGLE_SELECTION
|
||||||
resultList.border = BorderFactory.createEmptyBorder(5, 0, 0, 0)
|
resultList.border = BorderFactory.createEmptyBorder(5, 0, 0, 0)
|
||||||
resultList.background = rootPane.background
|
resultList.background = desktopBackground
|
||||||
|
|
||||||
|
|
||||||
val scrollPane = JScrollPane(resultList)
|
val scrollPane = JScrollPane(resultList)
|
||||||
@@ -226,5 +217,11 @@ class FindEverywhere(owner: Window, windowScope: WindowScope) : DialogWrapper(ow
|
|||||||
super.setVisible(visible)
|
super.setVisible(visible)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun addNotify() {
|
||||||
|
super.addNotify()
|
||||||
|
|
||||||
|
controlsVisible = false
|
||||||
|
fullWindowContent = true
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,10 +119,11 @@ class KeymapPanel : JPanel(BorderLayout()) {
|
|||||||
val keymap = getCurrentKeymap()
|
val keymap = getCurrentKeymap()
|
||||||
val index = keymapComboBox.selectedIndex
|
val index = keymapComboBox.selectedIndex
|
||||||
if (keymap != null && !keymap.isReadonly && index >= 0) {
|
if (keymap != null && !keymap.isReadonly && index >= 0) {
|
||||||
val text = InputDialog(
|
val text = OptionPane.showInputDialog(
|
||||||
SwingUtilities.getWindowAncestor(this@KeymapPanel),
|
SwingUtilities.getWindowAncestor(this@KeymapPanel),
|
||||||
title = renameBtn.toolTipText, text = keymap.name
|
title = renameBtn.toolTipText,
|
||||||
).getText()
|
value = keymap.name
|
||||||
|
)
|
||||||
if (!text.isNullOrBlank()) {
|
if (!text.isNullOrBlank()) {
|
||||||
if (text != keymap.name) {
|
if (text != keymap.name) {
|
||||||
keymapManager.removeKeymap(keymap.name)
|
keymapManager.removeKeymap(keymap.name)
|
||||||
|
|||||||
@@ -103,7 +103,9 @@ class KeyManagerPanel : JPanel(BorderLayout()) {
|
|||||||
|
|
||||||
private fun initEvents() {
|
private fun initEvents() {
|
||||||
generateBtn.addActionListener {
|
generateBtn.addActionListener {
|
||||||
val dialog = GenerateKeyDialog(SwingUtilities.getWindowAncestor(this))
|
val owner = SwingUtilities.getWindowAncestor(this)
|
||||||
|
val dialog = GenerateKeyDialog(owner)
|
||||||
|
dialog.setLocationRelativeTo(owner)
|
||||||
dialog.isVisible = true
|
dialog.isVisible = true
|
||||||
if (dialog.ohKeyPair != OhKeyPair.empty) {
|
if (dialog.ohKeyPair != OhKeyPair.empty) {
|
||||||
val keyPair = dialog.ohKeyPair
|
val keyPair = dialog.ohKeyPair
|
||||||
@@ -142,12 +144,14 @@ class KeyManagerPanel : JPanel(BorderLayout()) {
|
|||||||
editBtn.addActionListener {
|
editBtn.addActionListener {
|
||||||
val row = keyPairTable.selectedRow
|
val row = keyPairTable.selectedRow
|
||||||
if (row >= 0) {
|
if (row >= 0) {
|
||||||
|
val owner = SwingUtilities.getWindowAncestor(this)
|
||||||
var ohKeyPair = keyPairTableModel.getOhKeyPair(row)
|
var ohKeyPair = keyPairTableModel.getOhKeyPair(row)
|
||||||
val dialog = GenerateKeyDialog(
|
val dialog = GenerateKeyDialog(
|
||||||
SwingUtilities.getWindowAncestor(this),
|
owner,
|
||||||
ohKeyPair,
|
ohKeyPair,
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
|
dialog.setLocationRelativeTo(owner)
|
||||||
dialog.title = ohKeyPair.name
|
dialog.title = ohKeyPair.name
|
||||||
dialog.isVisible = true
|
dialog.isVisible = true
|
||||||
ohKeyPair = dialog.ohKeyPair
|
ohKeyPair = dialog.ohKeyPair
|
||||||
@@ -342,7 +346,6 @@ class KeyManagerPanel : JPanel(BorderLayout()) {
|
|||||||
|
|
||||||
pack()
|
pack()
|
||||||
size = Dimension(UIManager.getInt("Dialog.width") - 300, size.height)
|
size = Dimension(UIManager.getInt("Dialog.width") - 300, size.height)
|
||||||
setLocationRelativeTo(null)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,7 +357,7 @@ class KeyManagerPanel : JPanel(BorderLayout()) {
|
|||||||
|
|
||||||
var rows = 1
|
var rows = 1
|
||||||
val step = 2
|
val step = 2
|
||||||
return FormBuilder.create().layout(layout).padding("0dlu, $formMargin, $formMargin, $formMargin")
|
return FormBuilder.create().layout(layout).padding("2dlu, $formMargin, $formMargin, $formMargin")
|
||||||
.add("${I18n.getString("termora.keymgr.table.type")}:").xy(1, rows)
|
.add("${I18n.getString("termora.keymgr.table.type")}:").xy(1, rows)
|
||||||
.add(typeComboBox).xy(3, rows).apply { rows += step }
|
.add(typeComboBox).xy(3, rows).apply { rows += step }
|
||||||
.add("${I18n.getString("termora.keymgr.table.length")}:").xy(1, rows)
|
.add("${I18n.getString("termora.keymgr.table.length")}:").xy(1, rows)
|
||||||
@@ -512,7 +515,7 @@ class KeyManagerPanel : JPanel(BorderLayout()) {
|
|||||||
|
|
||||||
var rows = 1
|
var rows = 1
|
||||||
val step = 2
|
val step = 2
|
||||||
return FormBuilder.create().layout(layout).padding("0dlu, $formMargin, $formMargin, $formMargin")
|
return FormBuilder.create().layout(layout).padding("2dlu, $formMargin, $formMargin, $formMargin")
|
||||||
.add("File:").xy(1, rows)
|
.add("File:").xy(1, rows)
|
||||||
.add(fileTextField).xy(3, rows).apply { rows += step }
|
.add(fileTextField).xy(3, rows).apply { rows += step }
|
||||||
.add("${I18n.getString("termora.keymgr.table.type")}:").xy(1, rows)
|
.add("${I18n.getString("termora.keymgr.table.type")}:").xy(1, rows)
|
||||||
@@ -587,8 +590,10 @@ class KeyManagerPanel : JPanel(BorderLayout()) {
|
|||||||
try {
|
try {
|
||||||
val provider = FileKeyPairProvider(file.toPath())
|
val provider = FileKeyPairProvider(file.toPath())
|
||||||
provider.passwordFinder = FilePasswordProvider { _, _, _ ->
|
provider.passwordFinder = FilePasswordProvider { _, _, _ ->
|
||||||
val dialog = InputDialog(owner = this@ImportKeyDialog, title = "Password")
|
OptionPane.showInputDialog(
|
||||||
dialog.getText() ?: String()
|
SwingUtilities.getWindowAncestor(this),
|
||||||
|
title = I18n.getString("termora.new-host.general.password"),
|
||||||
|
) ?: String()
|
||||||
}
|
}
|
||||||
val keyPair = provider.loadKeys(null).firstOrNull()
|
val keyPair = provider.loadKeys(null).firstOrNull()
|
||||||
?: throw IllegalStateException("Failed to load the key file")
|
?: throw IllegalStateException("Failed to load the key file")
|
||||||
|
|||||||
@@ -83,8 +83,11 @@ class MacroDialog(owner: Window) : DialogWrapper(owner) {
|
|||||||
val index = list.selectedIndex
|
val index = list.selectedIndex
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
val macro = model.getElementAt(index)
|
val macro = model.getElementAt(index)
|
||||||
val dialog = InputDialog(owner = this, title = macro.name, text = macro.name)
|
val text = OptionPane.showInputDialog(
|
||||||
val text = dialog.getText() ?: String()
|
this,
|
||||||
|
title = macro.name,
|
||||||
|
value = macro.name
|
||||||
|
) ?: String()
|
||||||
if (text.isNotBlank()) {
|
if (text.isNotBlank()) {
|
||||||
val newMacro = macro.copy(name = text)
|
val newMacro = macro.copy(name = text)
|
||||||
macroManager.addMacro(newMacro)
|
macroManager.addMacro(newMacro)
|
||||||
|
|||||||
51
src/main/kotlin/app/termora/native/osx/NativeMacLibrary.kt
Normal file
51
src/main/kotlin/app/termora/native/osx/NativeMacLibrary.kt
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package app.termora.native.osx
|
||||||
|
|
||||||
|
import de.jangassen.jfa.foundation.Foundation
|
||||||
|
import de.jangassen.jfa.foundation.ID
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.awt.Component
|
||||||
|
import java.awt.Window
|
||||||
|
|
||||||
|
object NativeMacLibrary {
|
||||||
|
private val log = LoggerFactory.getLogger(NativeMacLibrary::class.java)
|
||||||
|
|
||||||
|
fun getNSWindow(window: Window): Long? {
|
||||||
|
try {
|
||||||
|
val peerField = Component::class.java.getDeclaredField("peer") ?: return null
|
||||||
|
peerField.isAccessible = true
|
||||||
|
val peer = peerField.get(window) ?: return null
|
||||||
|
|
||||||
|
val platformWindowField = peer.javaClass.getDeclaredField("platformWindow") ?: return null
|
||||||
|
platformWindowField.isAccessible = true
|
||||||
|
val platformWindow = platformWindowField.get(peer)
|
||||||
|
|
||||||
|
val ptrField = Class.forName("sun.lwawt.macosx.CFRetainedResource")
|
||||||
|
.getDeclaredField("ptr") ?: return null
|
||||||
|
ptrField.isAccessible = true
|
||||||
|
return ptrField.get(platformWindow) as Long
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (log.isErrorEnabled) {
|
||||||
|
log.error(e.message, e)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setControlsVisible(window: Window, visible: Boolean) {
|
||||||
|
val nsWindow = ID(getNSWindow(window) ?: return)
|
||||||
|
try {
|
||||||
|
Foundation.executeOnMainThread(true, true) {
|
||||||
|
for (i in 0..2) {
|
||||||
|
val button = Foundation.invoke(nsWindow, "standardWindowButton:", i)
|
||||||
|
Foundation.invoke(button, "setHidden:", !visible)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (log.isErrorEnabled) {
|
||||||
|
log.error(e.message, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -414,12 +414,11 @@ class FileSystemViewTable(
|
|||||||
val index = selectedRow
|
val index = selectedRow
|
||||||
if (index < 0) return
|
if (index < 0) return
|
||||||
val attr = model.getAttr(index)
|
val attr = model.getAttr(index)
|
||||||
val dialog = InputDialog(
|
val text = OptionPane.showInputDialog(
|
||||||
owner,
|
owner,
|
||||||
title = attr.name,
|
value = attr.name,
|
||||||
text = attr.name,
|
title = I18n.getString("termora.transport.table.contextmenu.rename")
|
||||||
)
|
) ?: return
|
||||||
val text = dialog.getText() ?: return
|
|
||||||
if (text.isBlank() || text == attr.name) return
|
if (text.isBlank() || text == attr.name) return
|
||||||
if (model.getPathNames().contains(text)) {
|
if (model.getPathNames().contains(text)) {
|
||||||
OptionPane.showMessageDialog(
|
OptionPane.showMessageDialog(
|
||||||
@@ -544,12 +543,7 @@ class FileSystemViewTable(
|
|||||||
private fun newFolderOrFile(isFile: Boolean) {
|
private fun newFolderOrFile(isFile: Boolean) {
|
||||||
val name = if (isFile) I18n.getString("termora.transport.table.contextmenu.new.file")
|
val name = if (isFile) I18n.getString("termora.transport.table.contextmenu.new.file")
|
||||||
else I18n.getString("termora.welcome.contextmenu.new.folder.name")
|
else I18n.getString("termora.welcome.contextmenu.new.folder.name")
|
||||||
val dialog = InputDialog(
|
val text = OptionPane.showInputDialog(owner, title = name, value = name) ?: return
|
||||||
owner,
|
|
||||||
title = name,
|
|
||||||
text = name,
|
|
||||||
)
|
|
||||||
val text = dialog.getText() ?: return
|
|
||||||
if (text.isBlank()) return
|
if (text.isBlank()) return
|
||||||
if (model.getPathNames().contains(text)) {
|
if (model.getPathNames().contains(text)) {
|
||||||
OptionPane.showMessageDialog(
|
OptionPane.showMessageDialog(
|
||||||
|
|||||||
Reference in New Issue
Block a user