feat: without jbr

This commit is contained in:
hstyi
2025-03-15 13:15:55 +08:00
committed by hstyi
parent 9884ed19fa
commit 4bb1a411e8
16 changed files with 405 additions and 405 deletions

View File

@@ -118,7 +118,6 @@ dependencies {
application {
val args = mutableListOf(
"--add-exports java.base/sun.nio.ch=ALL-UNNAMED",
"-Xmx2g",
"-XX:+UseZGC",
"-XX:+ZUncommit",
@@ -127,6 +126,10 @@ application {
)
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("-Dapple.awt.application.appearance=system")
}
@@ -344,6 +347,10 @@ tasks.register<Exec>("jpackage") {
options.add("-Dsun.java2d.metal=true")
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("--add-opens java.desktop/sun.lwawt.macosx.concurrent=ALL-UNNAMED")
}
@@ -420,15 +427,9 @@ tasks.register<Exec>("jpackage") {
tasks.register("dist") {
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
// 清空目录
exec { commandLine(gradlew, "clean") }
@@ -735,8 +736,6 @@ fun stapleMacOSLocalFile(file: File) {
kotlin {
jvmToolchain {
languageVersion = JavaLanguageVersion.of(21)
@Suppress("UnstableApiUsage")
vendor = JvmVendorSpec.JETBRAINS
}
}

View File

@@ -144,7 +144,7 @@ class ApplicationRunner {
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")
if (SystemInfo.isLinux) {
@@ -152,15 +152,6 @@ class ApplicationRunner {
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 appearance = Database.getDatabase().appearance
var theme = appearance.theme
@@ -176,8 +167,8 @@ class ApplicationRunner {
themeManager.change(theme, true)
if (Application.isUnknownVersion())
FlatInspector.install("ctrl shift alt X")
// if (Application.isUnknownVersion())
FlatInspector.install("ctrl X")
UIManager.put(FlatClientProperties.FULL_WINDOW_CONTENT, true)
UIManager.put(FlatClientProperties.USE_WINDOW_DECORATIONS, false)

View File

@@ -2,8 +2,8 @@ package app.termora
import app.termora.actions.AnAction
import app.termora.actions.AnActionEvent
import app.termora.native.osx.NativeMacLibrary
import com.formdev.flatlaf.FlatClientProperties
import com.formdev.flatlaf.FlatLaf
import com.formdev.flatlaf.util.SystemInfo
import com.jetbrains.JBR
import java.awt.*
@@ -12,29 +12,60 @@ import java.awt.event.WindowAdapter
import java.awt.event.WindowEvent
import javax.swing.*
abstract class DialogWrapper(owner: Window?) : JDialog(owner) {
private val rootPanel = JPanel(BorderLayout())
private val titleLabel = JLabel()
private val titleBar by lazy { LogicCustomTitleBar.createCustomTitleBar(this) }
val disposable = Disposer.newDisposable()
private val customTitleBar = if (SystemInfo.isMacOS && JBR.isWindowDecorationsSupported())
JBR.getWindowDecorations().createCustomTitleBar() else null
companion object {
const val DEFAULT_ACTION = "DEFAULT_ACTION"
private const val PROCESS_GLOBAL_KEYMAP = "PROCESS_GLOBAL_KEYMAP"
}
protected var controlsVisible = true
set(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) {
titleBar.height = 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
@@ -51,25 +82,43 @@ abstract class DialogWrapper(owner: Window?) : JDialog(owner) {
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() {
defaultCloseOperation = DISPOSE_ON_CLOSE
initTitleBar()
initEvents()
if (JBR.isWindowDecorationsSupported()) {
if (rootPane.getClientProperty(FlatClientProperties.TITLE_BAR_SHOW_TITLE) != false) {
val titlePanel = createTitlePanel()
if (titlePanel != null) {
rootPanel.add(titlePanel, BorderLayout.NORTH)
}
val rootPanel = JPanel(BorderLayout())
rootPanel.add(createCenterPanel(), BorderLayout.CENTER)
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
)
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()
if (southPanel != null) {
rootPanel.add(southPanel, BorderLayout.SOUTH)
@@ -122,7 +171,7 @@ abstract class DialogWrapper(owner: Window?) : JDialog(owner) {
val panel = JPanel(BorderLayout())
panel.add(titleLabel, BorderLayout.CENTER)
panel.preferredSize = Dimension(-1, titleBar.height.toInt())
panel.preferredSize = Dimension(-1, titleBarHeight)
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() {
titleBar.height = titleBarHeight
titleBar.putProperty("controls.visible", controlsVisible)
if (JBR.isWindowDecorationsSupported()) {
JBR.getWindowDecorations().setCustomTitleBar(this, titleBar)
override fun addNotify() {
super.addNotify()
// 显示后触发一次重绘制
if (SystemInfo.isWindows || SystemInfo.isLinux) {
this.controlsVisible = controlsVisible
this.titleBarHeight = titleBarHeight
this.titleIconVisible = titleIconVisible
this.titleVisible = titleVisible
this.fullWindowContent = fullWindowContent
}
}
protected open fun doOKAction() {

View File

@@ -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
}
}

View File

@@ -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
}
}

View 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
}
}

View File

@@ -1,11 +1,13 @@
package app.termora
import app.termora.native.osx.NativeMacLibrary
import com.formdev.flatlaf.FlatClientProperties
import com.formdev.flatlaf.extras.components.FlatTextPane
import com.formdev.flatlaf.util.SystemInfo
import com.jetbrains.JBR
import kotlinx.coroutines.*
import kotlinx.coroutines.swing.Swing
import org.apache.commons.lang3.StringUtils
import java.awt.BorderLayout
import java.awt.Component
import java.awt.Desktop
@@ -113,6 +115,36 @@ object OptionPane {
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(
parentComponent: Component,
file: File,
@@ -140,14 +172,31 @@ object OptionPane {
}
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 titleBar = windowDecorations.createCustomTitleBar()
titleBar.putProperty("controls.visible", false)
titleBar.height = UIManager.getInt("TabbedPane.tabHeight") - if (SystemInfo.isMacOS) 10f else 6f
windowDecorations.setCustomTitleBar(dialog, titleBar)
val height = UIManager.getInt("TabbedPane.tabHeight") - 10
if (JBR.isWindowDecorationsSupported()) {
val customTitleBar = JBR.getWindowDecorations().createCustomTitleBar()
customTitleBar.putProperty("controls.visible", false)
customTitleBar.height = height.toFloat()
JBR.getWindowDecorations().setCustomTitleBar(dialog, customTitleBar)
} else {
NativeMacLibrary.setControlsVisible(dialog, false)
}
val label = JLabel(dialog.title)
label.putClientProperty(FlatClientProperties.STYLE, "font: bold")
@@ -155,11 +204,9 @@ object OptionPane {
box.add(Box.createHorizontalGlue())
box.add(label)
box.add(Box.createHorizontalGlue())
box.preferredSize = Dimension(-1, titleBar.height.toInt())
box.preferredSize = Dimension(-1, height)
dialog.contentPane.add(box, BorderLayout.NORTH)
}
return dialog
}
}

View File

@@ -190,12 +190,11 @@ class TerminalTabbed(
val rename = popupMenu.add(I18n.getString("termora.tabbed.contextmenu.rename"))
rename.addActionListener {
if (tabIndex > 0) {
val dialog = InputDialog(
val text = OptionPane.showInputDialog(
SwingUtilities.getWindowAncestor(this),
title = rename.text,
text = tabbedPane.getTitleAt(tabIndex),
value = tabbedPane.getTitleAt(tabIndex)
)
val text = dialog.getText()
if (!text.isNullOrBlank()) {
tabbedPane.setTitleAt(tabIndex, text)
c.putClientProperty(titleProperty, text)

View File

@@ -7,21 +7,19 @@ import app.termora.actions.DataProviders
import app.termora.sftp.SFTPTab
import app.termora.terminal.DataKey
import com.formdev.flatlaf.FlatClientProperties
import com.formdev.flatlaf.FlatLaf
import com.formdev.flatlaf.util.SystemInfo
import com.jetbrains.JBR
import java.awt.BorderLayout
import java.awt.Dimension
import java.awt.Insets
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.awt.event.MouseListener
import java.awt.event.MouseMotionListener
import java.util.*
import javax.imageio.ImageIO
import javax.swing.Box
import javax.swing.JFrame
import javax.swing.SwingUtilities
import javax.swing.*
import javax.swing.SwingUtilities.isEventDispatchThread
import javax.swing.UIManager
import kotlin.math.max
fun assertEventDispatchThread() {
if (!isEventDispatchThread()) throw WrongThreadException("AWT EventQueue")
@@ -33,14 +31,13 @@ class TermoraFrame : JFrame(), DataProvider {
private val id = UUID.randomUUID().toString()
private val windowScope = ApplicationScope.forWindowScope(this)
private val titleBar = LogicCustomTitleBar.createCustomTitleBar(this)
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 isWindowDecorationsSupported by lazy { JBR.isWindowDecorationsSupported() }
private val dataProviderSupport = DataProviderSupport()
private val welcomePanel = WelcomePanel(windowScope)
private val sftp get() = Database.getDatabase().sftp
private val myUI = MyFlatRootPaneUI()
init {
@@ -49,44 +46,146 @@ class TermoraFrame : JFrame(), DataProvider {
}
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 需要判断是否全部删除
// 当 Tab 为 0 的时候,需要加一个边距,避开控制栏
if (SystemInfo.isMacOS && isWindowDecorationsSupported) {
tabbedPane.addChangeListener {
tabbedPane.leadingComponent = if (tabbedPane.tabCount == 0) {
Box.createHorizontalStrut(titleBar.leftInset.toInt())
} else {
null
override fun mouseDragged(e: MouseEvent) {
val mouseLayer = getMouseLayer() ?: return
getMouseMotionListener()?.mouseDragged(
MouseEvent(
mouseLayer,
e.id,
e.`when`,
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()
// 监听主题变化 需要动态修改控制栏颜色
if (SystemInfo.isWindows && isWindowDecorationsSupported) {
ThemeManager.getInstance().addThemeChangeListener(object : ThemeChangeListener {
override fun onChanged() {
titleBar.putProperty("controls.dark", FlatLaf.isLafDark())
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
}
}
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() {
if (isWindowDecorationsSupported) {
titleBar.height = UIManager.getInt("TabbedPane.tabHeight").toFloat()
titleBar.putProperty("controls.dark", FlatLaf.isLafDark())
JBR.getWindowDecorations().setCustomTitleBar(this, titleBar)
// macOS 要避开左边的控制栏
if (SystemInfo.isMacOS) {
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.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) {
@@ -102,88 +201,21 @@ class TermoraFrame : JFrame(), DataProvider {
terminalTabbed.addTerminalTab(welcomePanel)
// 下一次事件循环检测是否固定 SFTP
SwingUtilities.invokeLater {
if (sftp.pinTab) {
if (sftp.pinTab) {
SwingUtilities.invokeLater {
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)
add(terminalTabbed)
add(terminalTabbed, BorderLayout.CENTER)
dataProviderSupport.addData(DataProviders.TabbedPane, tabbedPane)
dataProviderSupport.addData(DataProviders.TermoraFrame, this)
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? {
return dataProviderSupport.getData(dataKey)
?: terminalTabbed.getData(dataKey)
@@ -204,5 +236,22 @@ class TermoraFrame : JFrame(), DataProvider {
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
}
}

View File

@@ -4,13 +4,13 @@ import app.termora.Application.ohMyJson
import app.termora.actions.*
import app.termora.findeverywhere.FindEverywhereAction
import app.termora.snippet.SnippetAction
import com.formdev.flatlaf.FlatClientProperties
import com.formdev.flatlaf.extras.components.FlatTabbedPane
import com.formdev.flatlaf.util.SystemInfo
import com.jetbrains.WindowDecorations
import kotlinx.serialization.Serializable
import org.apache.commons.lang3.StringUtils
import org.jdesktop.swingx.action.ActionContainerFactory
import java.awt.Insets
import java.awt.Rectangle
import java.awt.event.ComponentAdapter
import java.awt.event.ComponentEvent
import javax.swing.Box
@@ -25,7 +25,7 @@ data class ToolBarAction(
class TermoraToolBar(
private val windowScope: WindowScope,
private val titleBar: WindowDecorations.CustomTitleBar,
private val frame: TermoraFrame,
private val tabbedPane: FlatTabbedPane
) {
private val properties by lazy { Database.getDatabase().properties }
@@ -155,14 +155,11 @@ class TermoraToolBar(
}
fun adjust() {
if (SystemInfo.isMacOS) {
val left = titleBar.leftInset.toInt()
if (tabbedPane.tabAreaInsets.left != left) {
tabbedPane.tabAreaInsets = Insets(0, left, 0, 0)
}
} else if (SystemInfo.isWindows || SystemInfo.isLinux) {
val right = titleBar.rightInset.toInt()
if (SystemInfo.isWindows || SystemInfo.isLinux) {
val rectangle =
frame.rootPane.getClientProperty(FlatClientProperties.FULL_WINDOW_CONTENT_BUTTONS_BOUNDS)
as? Rectangle ?: return
val right = rectangle.width
val toolbar = this@MyToolBar
for (i in 0 until toolbar.componentCount) {
val c = toolbar.getComponent(i)

View File

@@ -7,9 +7,7 @@ import app.termora.WindowScope
import app.termora.actions.AnAction
import app.termora.actions.AnActionEvent
import app.termora.macro.MacroFindEverywhereProvider
import com.formdev.flatlaf.FlatClientProperties
import com.formdev.flatlaf.extras.components.FlatTextField
import com.jetbrains.JBR
import java.awt.BorderLayout
import java.awt.Dimension
import java.awt.Insets
@@ -45,17 +43,10 @@ class FindEverywhere(owner: Window, windowScope: WindowScope) : DialogWrapper(ow
minimumSize = Dimension(size.width / 2, size.height / 2)
isModal = false
lostFocusDispose = true
controlsVisible = false
defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
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.border = BorderFactory.createEmptyBorder(12, 12, 12, 12)
@@ -70,7 +61,7 @@ class FindEverywhere(owner: Window, windowScope: WindowScope) : DialogWrapper(ow
resultList.isRolloverEnabled = false
resultList.selectionMode = ListSelectionModel.SINGLE_SELECTION
resultList.border = BorderFactory.createEmptyBorder(5, 0, 0, 0)
resultList.background = rootPane.background
resultList.background = desktopBackground
val scrollPane = JScrollPane(resultList)
@@ -226,5 +217,11 @@ class FindEverywhere(owner: Window, windowScope: WindowScope) : DialogWrapper(ow
super.setVisible(visible)
}
override fun addNotify() {
super.addNotify()
controlsVisible = false
fullWindowContent = true
}
}

View File

@@ -119,10 +119,11 @@ class KeymapPanel : JPanel(BorderLayout()) {
val keymap = getCurrentKeymap()
val index = keymapComboBox.selectedIndex
if (keymap != null && !keymap.isReadonly && index >= 0) {
val text = InputDialog(
val text = OptionPane.showInputDialog(
SwingUtilities.getWindowAncestor(this@KeymapPanel),
title = renameBtn.toolTipText, text = keymap.name
).getText()
title = renameBtn.toolTipText,
value = keymap.name
)
if (!text.isNullOrBlank()) {
if (text != keymap.name) {
keymapManager.removeKeymap(keymap.name)

View File

@@ -103,7 +103,9 @@ class KeyManagerPanel : JPanel(BorderLayout()) {
private fun initEvents() {
generateBtn.addActionListener {
val dialog = GenerateKeyDialog(SwingUtilities.getWindowAncestor(this))
val owner = SwingUtilities.getWindowAncestor(this)
val dialog = GenerateKeyDialog(owner)
dialog.setLocationRelativeTo(owner)
dialog.isVisible = true
if (dialog.ohKeyPair != OhKeyPair.empty) {
val keyPair = dialog.ohKeyPair
@@ -142,12 +144,14 @@ class KeyManagerPanel : JPanel(BorderLayout()) {
editBtn.addActionListener {
val row = keyPairTable.selectedRow
if (row >= 0) {
val owner = SwingUtilities.getWindowAncestor(this)
var ohKeyPair = keyPairTableModel.getOhKeyPair(row)
val dialog = GenerateKeyDialog(
SwingUtilities.getWindowAncestor(this),
owner,
ohKeyPair,
true
)
dialog.setLocationRelativeTo(owner)
dialog.title = ohKeyPair.name
dialog.isVisible = true
ohKeyPair = dialog.ohKeyPair
@@ -342,7 +346,6 @@ class KeyManagerPanel : JPanel(BorderLayout()) {
pack()
size = Dimension(UIManager.getInt("Dialog.width") - 300, size.height)
setLocationRelativeTo(null)
}
@@ -354,7 +357,7 @@ class KeyManagerPanel : JPanel(BorderLayout()) {
var rows = 1
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(typeComboBox).xy(3, rows).apply { rows += step }
.add("${I18n.getString("termora.keymgr.table.length")}:").xy(1, rows)
@@ -512,7 +515,7 @@ class KeyManagerPanel : JPanel(BorderLayout()) {
var rows = 1
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(fileTextField).xy(3, rows).apply { rows += step }
.add("${I18n.getString("termora.keymgr.table.type")}:").xy(1, rows)
@@ -587,8 +590,10 @@ class KeyManagerPanel : JPanel(BorderLayout()) {
try {
val provider = FileKeyPairProvider(file.toPath())
provider.passwordFinder = FilePasswordProvider { _, _, _ ->
val dialog = InputDialog(owner = this@ImportKeyDialog, title = "Password")
dialog.getText() ?: String()
OptionPane.showInputDialog(
SwingUtilities.getWindowAncestor(this),
title = I18n.getString("termora.new-host.general.password"),
) ?: String()
}
val keyPair = provider.loadKeys(null).firstOrNull()
?: throw IllegalStateException("Failed to load the key file")

View File

@@ -83,8 +83,11 @@ class MacroDialog(owner: Window) : DialogWrapper(owner) {
val index = list.selectedIndex
if (index >= 0) {
val macro = model.getElementAt(index)
val dialog = InputDialog(owner = this, title = macro.name, text = macro.name)
val text = dialog.getText() ?: String()
val text = OptionPane.showInputDialog(
this,
title = macro.name,
value = macro.name
) ?: String()
if (text.isNotBlank()) {
val newMacro = macro.copy(name = text)
macroManager.addMacro(newMacro)

View 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)
}
}
}
}

View File

@@ -414,12 +414,11 @@ class FileSystemViewTable(
val index = selectedRow
if (index < 0) return
val attr = model.getAttr(index)
val dialog = InputDialog(
val text = OptionPane.showInputDialog(
owner,
title = attr.name,
text = attr.name,
)
val text = dialog.getText() ?: return
value = attr.name,
title = I18n.getString("termora.transport.table.contextmenu.rename")
) ?: return
if (text.isBlank() || text == attr.name) return
if (model.getPathNames().contains(text)) {
OptionPane.showMessageDialog(
@@ -544,12 +543,7 @@ class FileSystemViewTable(
private fun newFolderOrFile(isFile: Boolean) {
val name = if (isFile) I18n.getString("termora.transport.table.contextmenu.new.file")
else I18n.getString("termora.welcome.contextmenu.new.folder.name")
val dialog = InputDialog(
owner,
title = name,
text = name,
)
val text = dialog.getText() ?: return
val text = OptionPane.showInputDialog(owner, title = name, value = name) ?: return
if (text.isBlank()) return
if (model.getPathNames().contains(text)) {
OptionPane.showMessageDialog(