├── .github
└── FUNDING.yml
├── .gitignore
├── .idea
├── inspectionProfiles
│ └── Project_Default.xml
└── misc.xml
├── .run
├── FrameSwitcher [buildPlugin].run.xml
└── FrameSwitcher [publishPlugin].run.xml
├── LICENSE.txt
├── README
├── build.gradle.kts
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle.kts
└── src
└── main
├── java
└── krasa
│ └── frameswitcher
│ ├── CloseProjectsAction.java
│ ├── CloseProjectsForm.form
│ ├── CloseProjectsForm.java
│ ├── FocusUtils.java
│ ├── FrameSwitchAction.java
│ ├── FrameSwitcherApplicationService.java
│ ├── FrameSwitcherGui.form
│ ├── FrameSwitcherGui.java
│ ├── FrameSwitcherSettings.java
│ ├── FrameSwitcherUtils.java
│ ├── IconResolver.java
│ ├── MyConfigurable.java
│ ├── MyPreloadingActivity.java
│ ├── ProjectFocusMonitor.java
│ ├── ProjectStartupActivity.java
│ ├── ReopenProjectsAction.java
│ ├── ReopenProjectsForm.form
│ ├── ReopenProjectsForm.java
│ ├── WindowFocusGainedAdapter.java
│ └── networking
│ ├── DiagnosticAction.java
│ ├── DummyRemoteSender.java
│ ├── Receiver.java
│ ├── RemoteIdeInstance.java
│ ├── RemoteInstancesState.java
│ ├── RemoteSender.java
│ ├── RemoteSenderImpl.java
│ ├── StandbyDetector.java
│ └── dto
│ ├── GeneralMessage.java
│ ├── InstanceClosed.java
│ ├── InstanceStarted.java
│ ├── OpenProject.java
│ ├── Ping.java
│ ├── PingResponse.java
│ ├── ProjectClosed.java
│ ├── ProjectOpened.java
│ ├── ProjectsState.java
│ ├── Refresh.java
│ └── RemoteProject.java
└── resources
├── META-INF
└── plugin.xml
└── frameswitcher-fast-local.xml
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [krasa]
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.gradle
2 | **/build
3 | **/out
4 | *.iws
5 | *.ipr
6 | *.zip
7 | .shelf
8 | .idea
9 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.run/FrameSwitcher [buildPlugin].run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
11 |
16 |
17 |
18 | true
19 | true
20 | false
21 | false
22 |
23 |
24 |
--------------------------------------------------------------------------------
/.run/FrameSwitcher [publishPlugin].run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | true
19 | true
20 | false
21 | false
22 |
23 |
24 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | @file:Suppress("HardCodedStringLiteral")
2 |
3 | //import org.jetbrains.changelog.Changelog
4 |
5 | fun properties(key: String) = providers.gradleProperty(key)
6 | fun environment(key: String) = providers.environmentVariable(key)
7 | fun Jar.patchManifest() = manifest { attributes("Version" to project.version) }
8 |
9 | plugins {
10 | id("java") // Java support
11 | alias(libs.plugins.kotlin)
12 | alias(libs.plugins.gradleIntelliJPlugin) // Gradle IntelliJ Plugin
13 | // alias(libs.plugins.changelog) // Gradle Changelog Plugin
14 | }
15 |
16 | group = properties("pluginGroup").get()
17 | version = properties("pluginVersion").get()
18 |
19 | // Configure project's dependencies
20 | repositories {
21 | mavenCentral()
22 | maven {
23 | url = uri("https://cache-redirector.jetbrains.com/intellij-dependencies")
24 | }
25 | }
26 |
27 | dependencies {
28 | implementation("org.apache.commons:commons-lang3:3.14.0")
29 | // https://mvnrepository.com/artifact/com.google.guava/guava
30 | implementation("com.google.guava:guava:31.1-jre")
31 | // https://mvnrepository.com/artifact/org.jgroups/jgroups
32 | implementation("org.jgroups:jgroups:5.1.6.Final")
33 | implementation("uk.com.robust-it:cloning:1.9.12")
34 | }
35 |
36 |
37 | //// Set the JVM language level used to build the project. Use Java 11 for 2020.3+, and Java 17 for 2022.2+.
38 | kotlin {
39 | jvmToolchain(17)
40 | }
41 |
42 |
43 | // Configure Gradle IntelliJ Plugin - read more: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html
44 | intellij {
45 | pluginName = properties("pluginName")
46 | version = properties("platformVersion")
47 | type = properties("platformType")
48 |
49 | // Plugin Dependencies. Uses `platformPlugins` property from the gradle.properties file.
50 | plugins = properties("platformPlugins").map { it.split(',').map(String::trim).filter(String::isNotEmpty) }
51 | }
52 |
53 |
54 | tasks {
55 | wrapper {
56 | gradleVersion = properties("gradleVersion").get()
57 | }
58 |
59 | patchPluginXml {
60 | version = properties("pluginVersion")
61 | sinceBuild = properties("pluginSinceBuild")
62 | untilBuild = properties("pluginUntilBuild")
63 | changeNotes.set(
64 | buildString {
65 | append("- IntelliJ IDEA 2024.1 EAP compatibility").append("
")
66 | }
67 | )
68 | }
69 |
70 | // Set the JVM compatibility versions
71 | withType {
72 | sourceCompatibility = "17"
73 | targetCompatibility = "17"
74 | }
75 |
76 | runIde {
77 | jvmArgs("-Xmx1048m")
78 | }
79 |
80 | signPlugin {
81 | certificateChain.set(System.getenv("CERTIFICATE_CHAIN"))
82 | privateKey.set(System.getenv("PRIVATE_KEY"))
83 | password.set(System.getenv("PRIVATE_KEY_PASSWORD"))
84 | }
85 |
86 | publishPlugin {
87 | token.set(System.getenv("PUBLISH_TOKEN"))
88 | }
89 |
90 | buildSearchableOptions {
91 | enabled = false
92 | }
93 | }
94 |
95 | repositories {
96 | mavenCentral()
97 | }
98 |
99 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # IntelliJ Platform Artifacts Repositories -> https://plugins.jetbrains.com/docs/intellij/intellij-artifacts.html
2 | pluginGroup=FrameSwitcher
3 | pluginName=FrameSwitcher
4 | pluginRepositoryUrl=https://github.com/krasa/FrameSwitcher
5 |
6 | # SemVer format -> https://semver.org
7 | pluginVersion=4.6.0-241.8102
8 |
9 | # Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
10 | pluginSinceBuild=241.8102.112
11 | pluginUntilBuild=
12 |
13 | # IntelliJ Platform Properties -> https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#configuration-intellij-extension
14 | platformType=IC
15 | #platformVersion=241.8102
16 | platformVersion=LATEST-EAP-SNAPSHOT
17 |
18 |
19 | # Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html
20 | # Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22
21 | platformPlugins=
22 | # Gradle Releases -> https://github.com/gradle/gradle/releases
23 | gradleVersion=8.5
24 | # Opt-out flag for bundling Kotlin standard library -> https://jb.gg/intellij-platform-kotlin-stdlib
25 | kotlin.stdlib.default.dependency=false
26 | # Enable Gradle Configuration Cache -> https://docs.gradle.org/current/userguide/configuration_cache.html
27 | org.gradle.configuration-cache=false
28 | # Enable Gradle Build Cache -> https://docs.gradle.org/current/userguide/build_cache.html
29 | org.gradle.caching=true
30 | # Enable Gradle Kotlin DSL Lazy Property Assignment -> https://docs.gradle.org/current/userguide/kotlin_dsl.html#kotdsl:assignment
31 | systemProp.org.gradle.unsafe.kotlin.assignment=true
32 | # Temporary workaround for Kotlin Compiler OutOfMemoryError -> https://jb.gg/intellij-platform-kotlin-oom
33 | kotlin.incremental.useClasspathSnapshot=false
34 |
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | # libraries
3 | annotations = "24.0.1"
4 |
5 | # plugins
6 | kotlin = "1.9.0"
7 | changelog = "2.1.2"
8 | gradleIntelliJPlugin = "1.17.0"
9 | qodana = "0.1.13"
10 | kover = "0.7.2"
11 |
12 | [libraries]
13 | annotations = { group = "org.jetbrains", name = "annotations", version.ref = "annotations" }
14 |
15 | [plugins]
16 | changelog = { id = "org.jetbrains.changelog", version.ref = "changelog" }
17 | gradleIntelliJPlugin = { id = "org.jetbrains.intellij", version.ref = "gradleIntelliJPlugin" }
18 | kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
19 | kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" }
20 | qodana = { id = "org.jetbrains.qodana", version.ref = "qodana" }
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/krasa/FrameSwitcher/5d4cc2ab0c69b99924efe7bcddf5cde23dc5a542/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | #
21 | # Gradle start up script for POSIX generated by Gradle.
22 | #
23 | # Important for running:
24 | #
25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
26 | # noncompliant, but you have some other compliant shell such as ksh or
27 | # bash, then to run this script, type that shell name before the whole
28 | # command line, like:
29 | #
30 | # ksh Gradle
31 | #
32 | # Busybox and similar reduced shells will NOT work, because this script
33 | # requires all of these POSIX shell features:
34 | # * functions;
35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
37 | # * compound commands having a testable exit status, especially «case»;
38 | # * various built-in commands including «command», «set», and «ulimit».
39 | #
40 | # Important for patching:
41 | #
42 | # (2) This script targets any POSIX shell, so it avoids extensions provided
43 | # by Bash, Ksh, etc; in particular arrays are avoided.
44 | #
45 | # The "traditional" practice of packing multiple parameters into a
46 | # space-separated string is a well documented source of bugs and security
47 | # problems, so this is (mostly) avoided, by progressively accumulating
48 | # options in "$@", and eventually passing that to Java.
49 | #
50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
52 | # see the in-line comments for details.
53 | #
54 | # There are tweaks for specific operating systems such as AIX, CygWin,
55 | # Darwin, MinGW, and NonStop.
56 | #
57 | # (3) This script is generated from the Groovy template
58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
59 | # within the Gradle project.
60 | #
61 | # You can find Gradle at https://github.com/gradle/gradle/.
62 | #
63 | ##############################################################################
64 |
65 | # Attempt to set APP_HOME
66 |
67 | # Resolve links: $0 may be a link
68 | app_path=$0
69 |
70 | # Need this for daisy-chained symlinks.
71 | while
72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
73 | [ -h "$app_path" ]
74 | do
75 | ls=$( ls -ld "$app_path" )
76 | link=${ls#*' -> '}
77 | case $link in #(
78 | /*) app_path=$link ;; #(
79 | *) app_path=$APP_HOME$link ;;
80 | esac
81 | done
82 |
83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
84 |
85 | APP_NAME="Gradle"
86 | APP_BASE_NAME=${0##*/}
87 |
88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
90 |
91 | # Use the maximum available, or set MAX_FD != -1 to use that value.
92 | MAX_FD=maximum
93 |
94 | warn () {
95 | echo "$*"
96 | } >&2
97 |
98 | die () {
99 | echo
100 | echo "$*"
101 | echo
102 | exit 1
103 | } >&2
104 |
105 | # OS specific support (must be 'true' or 'false').
106 | cygwin=false
107 | msys=false
108 | darwin=false
109 | nonstop=false
110 | case "$( uname )" in #(
111 | CYGWIN* ) cygwin=true ;; #(
112 | Darwin* ) darwin=true ;; #(
113 | MSYS* | MINGW* ) msys=true ;; #(
114 | NONSTOP* ) nonstop=true ;;
115 | esac
116 |
117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
118 |
119 |
120 | # Determine the Java command to use to start the JVM.
121 | if [ -n "$JAVA_HOME" ] ; then
122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
123 | # IBM's JDK on AIX uses strange locations for the executables
124 | JAVACMD=$JAVA_HOME/jre/sh/java
125 | else
126 | JAVACMD=$JAVA_HOME/bin/java
127 | fi
128 | if [ ! -x "$JAVACMD" ] ; then
129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
130 |
131 | Please set the JAVA_HOME variable in your environment to match the
132 | location of your Java installation."
133 | fi
134 | else
135 | JAVACMD=java
136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
137 |
138 | Please set the JAVA_HOME variable in your environment to match the
139 | location of your Java installation."
140 | fi
141 |
142 | # Increase the maximum file descriptors if we can.
143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
144 | case $MAX_FD in #(
145 | max*)
146 | MAX_FD=$( ulimit -H -n ) ||
147 | warn "Could not query maximum file descriptor limit"
148 | esac
149 | case $MAX_FD in #(
150 | '' | soft) :;; #(
151 | *)
152 | ulimit -n "$MAX_FD" ||
153 | warn "Could not set maximum file descriptor limit to $MAX_FD"
154 | esac
155 | fi
156 |
157 | # Collect all arguments for the java command, stacking in reverse order:
158 | # * args from the command line
159 | # * the main class name
160 | # * -classpath
161 | # * -D...appname settings
162 | # * --module-path (only if needed)
163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
164 |
165 | # For Cygwin or MSYS, switch paths to Windows format before running java
166 | if "$cygwin" || "$msys" ; then
167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
169 |
170 | JAVACMD=$( cygpath --unix "$JAVACMD" )
171 |
172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
173 | for arg do
174 | if
175 | case $arg in #(
176 | -*) false ;; # don't mess with options #(
177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
178 | [ -e "$t" ] ;; #(
179 | *) false ;;
180 | esac
181 | then
182 | arg=$( cygpath --path --ignore --mixed "$arg" )
183 | fi
184 | # Roll the args list around exactly as many times as the number of
185 | # args, so each arg winds up back in the position where it started, but
186 | # possibly modified.
187 | #
188 | # NB: a `for` loop captures its iteration list before it begins, so
189 | # changing the positional parameters here affects neither the number of
190 | # iterations, nor the values presented in `arg`.
191 | shift # remove old arg
192 | set -- "$@" "$arg" # push replacement arg
193 | done
194 | fi
195 |
196 | # Collect all arguments for the java command;
197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
198 | # shell script including quotes and variable substitutions, so put them in
199 | # double quotes to make sure that they get re-expanded; and
200 | # * put everything else in single quotes, so that it's not re-expanded.
201 |
202 | set -- \
203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
204 | -classpath "$CLASSPATH" \
205 | org.gradle.wrapper.GradleWrapperMain \
206 | "$@"
207 |
208 | # Use "xargs" to parse quoted args.
209 | #
210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
211 | #
212 | # In Bash we could simply go:
213 | #
214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
215 | # set -- "${ARGS[@]}" "$@"
216 | #
217 | # but POSIX shell has neither arrays nor command substitution, so instead we
218 | # post-process each arg (as a line of input to sed) to backslash-escape any
219 | # character that might be a shell metacharacter, then use eval to reverse
220 | # that process (while maintaining the separation between arguments), and wrap
221 | # the whole thing up as a single "set" statement.
222 | #
223 | # This will of course break if any of these variables contains a newline or
224 | # an unmatched quote.
225 | #
226 |
227 | eval "set -- $(
228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
229 | xargs -n1 |
230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
231 | tr '\n' ' '
232 | )" '"$@"'
233 |
234 | exec "$JAVACMD" "$@"
235 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | maven("https://oss.sonatype.org/content/repositories/snapshots/")
4 | gradlePluginPortal()
5 | }
6 | }
7 |
8 | rootProject.name = "FrameSwitcher"
9 |
10 | plugins {
11 | id("org.gradle.toolchains.foojay-resolver-convention") version ("0.7.0")
12 | }
--------------------------------------------------------------------------------
/src/main/java/krasa/frameswitcher/CloseProjectsAction.java:
--------------------------------------------------------------------------------
1 | package krasa.frameswitcher;
2 |
3 | import com.intellij.ide.RecentProjectsManagerBase;
4 | import com.intellij.openapi.actionSystem.AnActionEvent;
5 | import com.intellij.openapi.project.DumbAwareAction;
6 | import com.intellij.openapi.project.Project;
7 | import com.intellij.openapi.project.ProjectManager;
8 | import com.intellij.openapi.ui.DialogBuilder;
9 | import com.intellij.openapi.ui.DialogWrapper;
10 | import com.intellij.openapi.wm.impl.welcomeScreen.WelcomeFrame;
11 |
12 | import java.util.List;
13 |
14 | /**
15 | * @author Vojtech Krasa
16 | */
17 | public class CloseProjectsAction extends DumbAwareAction {
18 | @Override
19 | public void actionPerformed(AnActionEvent anActionEvent) {
20 | final CloseProjectsForm form = new CloseProjectsForm(getEventProject(anActionEvent));
21 |
22 | DialogBuilder builder = new DialogBuilder(getEventProject(anActionEvent));
23 | builder.setCenterPanel(form.getRoot());
24 | builder.setDimensionServiceKey("FrameSwitcherCloseProjects");
25 | builder.setTitle("Close Projects");
26 | builder.removeAllActions();
27 | builder.addOkAction();
28 | builder.addCancelAction();
29 |
30 | boolean isOk = builder.show() == DialogWrapper.OK_EXIT_CODE;
31 | if (isOk) {
32 | ProjectManager projectManager = ProjectManager.getInstance();
33 | RecentProjectsManagerBase recentProjectsManagerBase = RecentProjectsManagerBase.getInstanceEx();
34 | List checkProjects = form.getCheckProjects();
35 | for (Project checkProject : checkProjects) {
36 | if (!checkProject.isDisposed()) {
37 | projectManager.closeAndDispose(checkProject);
38 | recentProjectsManagerBase.updateLastProjectPath();
39 | }
40 | }
41 | WelcomeFrame.showIfNoProjectOpened();
42 | }
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/krasa/frameswitcher/CloseProjectsForm.form:
--------------------------------------------------------------------------------
1 |
2 |
22 |
--------------------------------------------------------------------------------
/src/main/java/krasa/frameswitcher/CloseProjectsForm.java:
--------------------------------------------------------------------------------
1 | package krasa.frameswitcher;
2 |
3 | import com.intellij.openapi.project.Project;
4 | import com.intellij.openapi.project.ProjectManager;
5 | import com.intellij.ui.CheckBoxList;
6 | import com.intellij.util.Function;
7 |
8 | import javax.swing.*;
9 | import java.util.ArrayList;
10 | import java.util.Arrays;
11 | import java.util.List;
12 |
13 | /**
14 | * @author Vojtech Krasa
15 | */
16 | public class CloseProjectsForm {
17 | private JPanel root;
18 | private CheckBoxList list;
19 |
20 | public CloseProjectsForm(Project project) {
21 | Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
22 | list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
23 | //IJ 12 compatibility
24 | list.setItems(Arrays.asList(openProjects), new Function() {
25 | @Override
26 | public String fun(Project project) {
27 | return project.getName();
28 | }
29 | });
30 | for (int i = 0; i < openProjects.length; i++) {
31 | Project openProject = openProjects[i];
32 | if (openProject != project) {
33 | list.setItemSelected(openProject, true);
34 | }
35 | }
36 | }
37 |
38 | public JPanel getRoot() {
39 | return root;
40 | }
41 |
42 | public List getCheckProjects() {
43 | DefaultListModel model = (DefaultListModel) list.getModel();
44 | final ArrayList selected = new ArrayList();
45 | Object[] objects = model.toArray();
46 | for (int i = 0; i < objects.length; i++) {
47 | Object object = objects[i];
48 | final JCheckBox cb = (JCheckBox) object;
49 | if (cb.isSelected()) {
50 | //IJ 12 compatibility
51 | selected.add((Project) list.getItemAt(i));
52 | }
53 |
54 | }
55 | return selected;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/java/krasa/frameswitcher/FocusUtils.java:
--------------------------------------------------------------------------------
1 | package krasa.frameswitcher;
2 |
3 | import com.intellij.openapi.actionSystem.AnActionEvent;
4 | import com.intellij.openapi.actionSystem.impl.SimpleDataContext;
5 | import com.intellij.openapi.project.Project;
6 | import com.intellij.openapi.wm.IdeFocusManager;
7 | import com.intellij.openapi.wm.WindowManager;
8 | import com.intellij.openapi.wm.impl.ProjectWindowAction;
9 | import com.intellij.util.BitUtil;
10 |
11 | import javax.swing.*;
12 | import java.awt.*;
13 | import java.awt.event.InputEvent;
14 | import java.awt.event.KeyEvent;
15 |
16 | /**
17 | * @author Vojtech Krasa
18 | */
19 | public class FocusUtils {
20 |
21 |
22 | public static void requestFocus(Project project, final boolean useRobot, boolean custom) {
23 | JFrame frame = WindowManager.getInstance().getFrame(project);
24 | if (frame == null) {
25 | return;
26 | }
27 |
28 | if (custom) {
29 | requestFocusCustom(project, useRobot, frame);
30 | } else {
31 | final ProjectWindowAction windowAction = new ProjectWindowAction(project.getPresentableUrl(), project.getPresentableUrl(), null);
32 | final ProjectWindowAction next = windowAction.getNext();
33 | if (next != null) {
34 | KeyEvent a = new KeyEvent(frame, KeyEvent.KEY_RELEASED, System.currentTimeMillis(), 0, KeyEvent.VK_A, 'a');
35 | AnActionEvent anActionEvent = AnActionEvent.createFromInputEvent(a, "FrameSwitcher", null, SimpleDataContext.getProjectContext(project));
36 | next.setSelected(anActionEvent, true);
37 | }
38 | }
39 | }
40 |
41 | public static void requestFocusCustom(Project project, boolean useRobot, JFrame frame) {
42 | boolean skip = false;
43 |
44 | if (!skip) {
45 | // the only reliable way I found to bring it to the top
46 | boolean aot = frame.isAlwaysOnTop();
47 | frame.setAlwaysOnTop(true);
48 | frame.setAlwaysOnTop(aot);
49 | }
50 |
51 | int frameState = frame.getExtendedState();
52 | if (BitUtil.isSet(frameState, Frame.ICONIFIED)) {
53 | // restore the frame if it is minimized
54 | frame.setExtendedState(BitUtil.set(frameState, Frame.ICONIFIED, false));
55 | }
56 | frame.toFront();
57 |
58 | IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> {
59 | if (project.isDisposed()) {
60 | return;
61 | }
62 | Component mostRecentFocusOwner = frame.getMostRecentFocusOwner();
63 | if (mostRecentFocusOwner != null) {
64 | IdeFocusManager.getGlobalInstance().requestFocus(mostRecentFocusOwner, true);
65 | }
66 | });
67 |
68 | if (useRobot && runningOnWindows7()) {
69 | try {
70 | // remember the last location of mouse
71 | final Point oldMouseLocation = MouseInfo.getPointerInfo().getLocation();
72 |
73 | // simulate a mouse click on title bar of window
74 | Robot robot = new Robot();
75 | robot.mouseMove(frame.getX(), frame.getY());
76 | robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
77 | robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
78 |
79 | // move mouse to old location
80 | robot.mouseMove((int) oldMouseLocation.getX(), (int) oldMouseLocation.getY());
81 | } catch (Exception ex) {
82 | // just ignore exception, or you can handle it as you want
83 | } finally {
84 | frame.setAlwaysOnTop(false);
85 | }
86 | }
87 | }
88 |
89 | public static boolean runningOnWindows7() {
90 | String osName = System.getProperty("os.name");
91 | String osVersion = System.getProperty("os.version");
92 | return "Windows 7".equals(osName) && "6.1".equals(osVersion);
93 | }
94 |
95 | }
96 |
--------------------------------------------------------------------------------
/src/main/java/krasa/frameswitcher/FrameSwitchAction.java:
--------------------------------------------------------------------------------
1 | package krasa.frameswitcher;
2 |
3 | import com.intellij.icons.AllIcons;
4 | import com.intellij.ide.*;
5 | import com.intellij.ide.actions.QuickSwitchSchemeAction;
6 | import com.intellij.openapi.actionSystem.*;
7 | import com.intellij.openapi.diagnostic.Logger;
8 | import com.intellij.openapi.keymap.ex.KeymapManagerEx;
9 | import com.intellij.openapi.project.DumbAware;
10 | import com.intellij.openapi.project.DumbAwareAction;
11 | import com.intellij.openapi.project.Project;
12 | import com.intellij.openapi.project.ProjectManager;
13 | import com.intellij.openapi.ui.popup.JBPopupFactory;
14 | import com.intellij.openapi.ui.popup.ListPopup;
15 | import com.intellij.openapi.util.Condition;
16 | import com.intellij.openapi.util.Conditions;
17 | import com.intellij.openapi.util.IconLoader;
18 | import com.intellij.openapi.util.Ref;
19 | import com.intellij.openapi.util.io.FileUtil;
20 | import com.intellij.openapi.util.text.StringUtil;
21 | import com.intellij.openapi.wm.IdeFocusManager;
22 | import com.intellij.openapi.wm.IdeFrame;
23 | import com.intellij.openapi.wm.WindowManager;
24 | import com.intellij.openapi.wm.impl.welcomeScreen.WelcomeFrame;
25 | import com.intellij.ui.popup.PopupFactoryImpl;
26 | import com.intellij.ui.popup.list.ListPopupImpl;
27 | import com.intellij.ui.popup.list.ListPopupModel;
28 | import com.intellij.util.Alarm;
29 | import com.intellij.util.PathUtil;
30 | import com.intellij.util.SingleAlarm;
31 | import com.intellij.util.ui.EmptyIcon;
32 | import krasa.frameswitcher.networking.RemoteIdeInstance;
33 | import krasa.frameswitcher.networking.RemoteInstancesState;
34 | import krasa.frameswitcher.networking.dto.RemoteProject;
35 | import org.jetbrains.annotations.NotNull;
36 | import org.jetbrains.annotations.SystemIndependent;
37 |
38 | import javax.swing.*;
39 | import java.awt.*;
40 | import java.awt.event.ActionEvent;
41 | import java.awt.event.InputEvent;
42 | import java.awt.event.KeyEvent;
43 | import java.awt.event.MouseEvent;
44 | import java.io.File;
45 | import java.util.List;
46 | import java.util.*;
47 |
48 | public class FrameSwitchAction extends QuickSwitchSchemeAction implements DumbAware {
49 |
50 | private final static Logger LOG = Logger.getInstance(FrameSwitchAction.class);
51 | public static Icon forward;
52 | public static Icon empty;
53 |
54 | static {
55 | forward = AllIcons.Actions.Forward;
56 | empty = IconLoader.createLazy(() -> {
57 | return EmptyIcon.create(AllIcons.Actions.Forward.getIconWidth(), AllIcons.Actions.Forward.getIconHeight());
58 | });
59 | }
60 |
61 | private boolean loadProjectIcon;
62 |
63 | public FrameSwitchAction() {
64 | }
65 |
66 | @Override
67 | protected void fillActions(final Project currentProject, DefaultActionGroup group, DataContext dataContext) {
68 | loadProjectIcon = FrameSwitcherSettings.getInstance().isLoadProjectIcon();
69 | addFrames(currentProject, group);
70 |
71 | fillReopen(group);
72 | }
73 |
74 | public void fillReopen(DefaultActionGroup group) {
75 | FrameSwitcherApplicationService service = FrameSwitcherApplicationService.getInstance();
76 | service.getRemoteInstancesState().sweepRemoteInstance();
77 |
78 | addRemote(group);
79 |
80 | addRecent(group);
81 |
82 | addRemoteRecent(group);
83 |
84 | addIncluded(group);
85 |
86 | service.getRemoteSender().asyncSendRefresh();
87 | service.getRemoteSender().asyncPing();
88 | }
89 |
90 | @Override
91 | public void actionPerformed(@NotNull AnActionEvent e) {
92 | Project project = e.getData(CommonDataKeys.PROJECT);
93 | DefaultActionGroup group = new DefaultActionGroup();
94 | fillActions(project, group, e.getDataContext());
95 | showPopup(e, group);
96 | }
97 |
98 | private void showPopup(AnActionEvent e, DefaultActionGroup group) {
99 | if (group.getChildrenCount() == 0)
100 | return;
101 | JBPopupFactory.ActionSelectionAid aid = getAidMethod();
102 |
103 | Condition preselectActionCondition;
104 | if (FrameSwitcherSettings.getInstance().isDefaultSelectionCurrentProject()) {
105 | preselectActionCondition = Conditions.alwaysFalse();
106 | } else {
107 | preselectActionCondition = (a) -> {
108 | if (a instanceof SwitchFrameAction) {
109 | return !((SwitchFrameAction) a).currentProject;
110 | }
111 | return true;
112 | };
113 |
114 | }
115 |
116 | ListPopup popup = JBPopupFactory.getInstance().createActionGroupPopup(getPopupTitle(e), group,
117 | e.getDataContext(), aid, true, null, -1, preselectActionCondition, myActionPlace);
118 | showPopup(e, popup);
119 | }
120 |
121 | private void addFrames(Project currentProject, DefaultActionGroup group) {
122 | WindowManager windowManager = WindowManager.getInstance();
123 | ArrayList ideFrames = getIdeFrames();
124 |
125 | ProjectFocusMonitor projectFocusMonitor = FrameSwitcherApplicationService.getInstance()
126 | .getProjectFocusMonitor();
127 | Project[] projectsOrderedByFocus = projectFocusMonitor.getProjectsOrderedByFocus();
128 | Set addedProjectsSet = new HashSet<>();
129 |
130 | for (int i = projectsOrderedByFocus.length - 1; i >= 0; i--) {
131 | Project project = projectsOrderedByFocus[i];
132 | if (project == null) {
133 | continue;// TODO
134 | }
135 | if (project.isDisposed()) {
136 | continue;
137 | }
138 | add(currentProject, group, project);
139 |
140 | IdeFrame frame = (IdeFrame) windowManager.getFrame(project);
141 | if (frame != null) {
142 | addedProjectsSet.add(project);
143 | }
144 | }
145 |
146 | for (final IdeFrame frame : ideFrames) {
147 | final Project project = frame.getProject();
148 | if (project != null) {
149 | if (project.isDisposed()) {
150 | continue;
151 | }
152 | if (addedProjectsSet.contains(project)) {
153 | continue;
154 | }
155 | add(currentProject, group, project);
156 | }
157 | }
158 | }
159 |
160 | private void add(Project currentProject, DefaultActionGroup group, final Project project) {
161 | group.addAction(new SwitchFrameAction(project, currentProject == project));
162 | }
163 |
164 | private void addRemote(DefaultActionGroup group) {
165 | final FrameSwitcherApplicationService service = FrameSwitcherApplicationService.getInstance();
166 | RemoteInstancesState remoteInstancesState = service.getRemoteInstancesState();
167 |
168 | for (RemoteIdeInstance remoteIdeInstance : remoteInstancesState.getRemoteIdeInstances()) {
169 | Collection remoteProjects = remoteIdeInstance.remoteProjects;
170 | String ideName = remoteIdeInstance.ideName;
171 |
172 | if (remoteProjects.isEmpty()) {
173 | continue;
174 | }
175 |
176 | if (ideName != null) {
177 | group.addSeparator(ideName);
178 | } else {
179 | group.addSeparator("Remote");
180 | }
181 |
182 | for (final RemoteProject remoteProject : remoteProjects) {
183 | group.add(new SwitchToRemoteProjectAction(remoteProject, remoteIdeInstance.uuid));
184 | }
185 | }
186 | }
187 |
188 | private void addRemoteRecent(DefaultActionGroup group) {
189 | FrameSwitcherSettings settings = FrameSwitcherSettings.getInstance();
190 | List remoteIdeInstances1 = FrameSwitcherApplicationService.getInstance()
191 | .getRemoteInstancesState().getRemoteIdeInstances();
192 | for (RemoteIdeInstance remoteIdeInstance : remoteIdeInstances1) {
193 | Collection remoteRecentProjects = remoteIdeInstance.remoteRecentProjects;
194 | String ideName = remoteIdeInstance.ideName;
195 |
196 | if (remoteRecentProjects.isEmpty()) {
197 | continue;
198 | }
199 |
200 | if (ideName != null) {
201 | group.addSeparator("Recent - " + ideName);
202 | } else {
203 | group.addSeparator("Recent");
204 | }
205 |
206 | for (RemoteProject remoteProject : remoteRecentProjects) {
207 | if (settings.shouldShow(remoteProject)) {
208 | group.add(new SwitchToRemoteProjectAction(remoteProject, remoteIdeInstance.uuid));
209 | }
210 | }
211 | }
212 | }
213 |
214 | private void addRecent(DefaultActionGroup group) {
215 | RecentProjectsManagerBase recentProjectsManagerBase = RecentProjectsManagerBase.getInstanceEx();
216 | AnAction[] recentProjectsActions = recentProjectsManagerBase.getRecentProjectsActions(false);
217 | if (recentProjectsActions != null) {
218 | recentProjectsActions = removeCurrentProjects(recentProjectsActions);
219 | FrameSwitcherSettings settings = FrameSwitcherSettings.getInstance();
220 |
221 | int i = 0;
222 | for (AnAction action : recentProjectsActions) {
223 | ReopenProjectAction recentProjectsAction = (ReopenProjectAction) action;
224 | if (settings.shouldShow(recentProjectsAction)) {
225 | if (i == 0) {
226 | group.addSeparator("Recent");
227 | }
228 | group.add(new ReopenRecentWrapper(recentProjectsAction));
229 | i++;
230 | }
231 | }
232 | }
233 | }
234 |
235 | private void addIncluded(DefaultActionGroup group) {
236 | FrameSwitcherSettings settings = FrameSwitcherSettings.getInstance();
237 | List includeLocations = settings.getIncludeLocations();
238 | if (includeLocations.isEmpty()) {
239 | return;
240 | }
241 |
242 | Set paths = new HashSet<>();
243 | AnAction[] childActionsOrStubs = group.getChildActionsOrStubs();
244 | for (AnAction childActionsOrStub : childActionsOrStubs) {
245 | if (childActionsOrStub instanceof ReopenRecentWrapper) {
246 | paths.add(((ReopenRecentWrapper) childActionsOrStub).getProjectPath());
247 | } else if (childActionsOrStub instanceof SwitchToRemoteProjectAction) {
248 | paths.add(((SwitchToRemoteProjectAction) childActionsOrStub).getProjectPath());
249 | } else if (childActionsOrStub instanceof SwitchFrameAction) {
250 | paths.add(((SwitchFrameAction) childActionsOrStub).getBasePath());
251 | }
252 |
253 | }
254 | group.addSeparator("Included");
255 |
256 | for (String includeLocation : includeLocations) {
257 | File parent = new File(includeLocation);
258 | if (!parent.exists()) {
259 | continue;
260 | }
261 | File[] files = parent.listFiles();
262 | if (files == null) {
263 | continue;
264 | }
265 | for (File file : files) {
266 | if (file.isDirectory() && !file.getName().startsWith(".")) {
267 | String s = FileUtil.toSystemIndependentName(file.getAbsolutePath());
268 | if (paths.contains(s)) {
269 | continue;
270 | }
271 | group.add(new ReopenRecentWrapper(s));
272 | }
273 | }
274 | }
275 | }
276 |
277 | public static AnAction[] removeCurrentProjects(AnAction[] actions) {
278 | ProjectFocusMonitor projectFocusMonitor = FrameSwitcherApplicationService.getInstance()
279 | .getProjectFocusMonitor();
280 | Project[] projectsOrderedByFocus = projectFocusMonitor.getProjectsOrderedByFocus();
281 |
282 | if (projectsOrderedByFocus != null) {
283 | return Arrays.stream(actions).filter(action -> {
284 | return !(action instanceof ReopenProjectAction)
285 | || !isOpen(projectsOrderedByFocus, (ReopenProjectAction) action);
286 | }).toArray(AnAction[]::new);
287 | }
288 | return actions;
289 | }
290 |
291 | private static boolean isOpen(Project[] openedProjects, ReopenProjectAction action) {
292 | String projectPath = PathUtil.toSystemDependentName(action.getProjectPath());
293 | for (Project openedProject : openedProjects) {
294 | if (openedProject == null) {
295 | continue;
296 | }
297 | if (StringUtil.equals(projectPath, PathUtil.toSystemDependentName(openedProject.getBasePath())) ||
298 | Objects.equals(PathUtil.toSystemDependentName(openedProject.getProjectFilePath()), projectPath)) {
299 | return true;
300 | }
301 | }
302 | return false;
303 | }
304 |
305 | public ArrayList getIdeFrames() {
306 | IdeFrame[] allProjectFrames = WindowManager.getInstance().getAllProjectFrames();
307 | ArrayList list = new ArrayList(allProjectFrames.length);
308 | list.addAll(Arrays.asList(allProjectFrames));
309 | Collections.sort(list, new Comparator() {
310 |
311 | @Override
312 | public int compare(IdeFrame o1, IdeFrame o2) {
313 | Project project1 = o1.getProject();
314 | Project project2 = o2.getProject();
315 | if (project1 == null && project2 == null) {
316 | return 0;
317 | }
318 | if (project1 == null) {
319 | return -1;
320 | }
321 | if (project2 == null) {
322 | return 1;
323 | }
324 | return project1.getName().compareToIgnoreCase(project2.getName());
325 | }
326 | });
327 | return list;
328 | }
329 |
330 | @Override
331 | protected void showPopup(AnActionEvent e, ListPopup p) {
332 | final ListPopupImpl popup = (ListPopupImpl) p;
333 | registerActions(popup);
334 | super.showPopup(e, popup);
335 | }
336 |
337 | private void registerActions(final ListPopupImpl popup) {
338 | final Ref invoked = Ref.create(false);
339 | popup.registerAction("ReopenInSameWindow",
340 | KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.SHIFT_DOWN_MASK), new AbstractAction() {
341 | @Override
342 | public void actionPerformed(ActionEvent e) {
343 | JList list = popup.getList();
344 | PopupFactoryImpl.ActionItem selectedValue = (PopupFactoryImpl.ActionItem) list
345 | .getSelectedValue();
346 | if (selectedValue.getAction() instanceof ReopenRecentWrapper) {
347 | popup.closeOk(null);
348 | int confirmOpenNewProject = GeneralSettings.getInstance().getConfirmOpenNewProject();
349 | try {
350 | GeneralSettings.getInstance()
351 | .setConfirmOpenNewProject(GeneralSettings.OPEN_PROJECT_SAME_WINDOW);
352 | ReopenRecentWrapper action = (ReopenRecentWrapper) selectedValue.getAction();
353 | action.actionPerformed(
354 | new AnActionEvent(null, getDataContext(popup), "FrameSwitcher-ExtraPopupAction",
355 | getTemplatePresentation(), ActionManager.getInstance(), 0));
356 | } finally {
357 | restoreOption(confirmOpenNewProject);
358 | }
359 | }
360 | }
361 | });
362 | popup.registerAction("ReopenInNewWindow", KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.CTRL_DOWN_MASK),
363 | new AbstractAction() {
364 | @Override
365 | public void actionPerformed(ActionEvent e) {
366 | JList list = popup.getList();
367 | PopupFactoryImpl.ActionItem selectedValue = (PopupFactoryImpl.ActionItem) list
368 | .getSelectedValue();
369 | if (selectedValue.getAction() instanceof ReopenRecentWrapper) {
370 | popup.closeOk(null);
371 | int confirmOpenNewProject = GeneralSettings.getInstance().getConfirmOpenNewProject();
372 | try {
373 | GeneralSettings.getInstance()
374 | .setConfirmOpenNewProject(GeneralSettings.OPEN_PROJECT_NEW_WINDOW);
375 | ReopenRecentWrapper action = (ReopenRecentWrapper) selectedValue.getAction();
376 | action.actionPerformed(
377 | new AnActionEvent(null, getDataContext(popup), "FrameSwitcher-ExtraPopupAction",
378 | getTemplatePresentation(), ActionManager.getInstance(), InputEvent.CTRL_MASK));
379 | } finally {
380 | restoreOption(confirmOpenNewProject);
381 | }
382 | }
383 | }
384 |
385 | });
386 | popup.registerAction("invokeWithDelete", KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), new AbstractAction() {
387 | @Override
388 | public void actionPerformed(ActionEvent e) {
389 | invoked.set(true);
390 | JList list = popup.getList();
391 | int selectedIndex = list.getSelectedIndex();
392 | ListPopupModel model = (ListPopupModel) list.getModel();
393 | PopupFactoryImpl.ActionItem selectedItem = (PopupFactoryImpl.ActionItem) model.get(selectedIndex);
394 | if (selectedItem != null && selectedItem.getAction() instanceof ReopenRecentWrapper) {
395 | ReopenRecentWrapper action = (ReopenRecentWrapper) selectedItem.getAction();
396 | RecentProjectsManagerBase.getInstanceEx().removePath(action.getProjectPath());
397 | model.deleteItem(selectedItem);
398 | if (selectedIndex == list.getModel().getSize()) { // is last
399 | list.setSelectedIndex(selectedIndex - 1);
400 | } else {
401 | list.setSelectedIndex(selectedIndex);
402 | }
403 | }
404 | if (selectedItem != null && selectedItem.getAction() instanceof SwitchFrameAction) {
405 | SwitchFrameAction action = (SwitchFrameAction) selectedItem.getAction();
406 | Project project = action.getProject();
407 | if (!project.isDisposed()) {
408 | ProjectManager.getInstance().closeAndDispose(project);
409 | RecentProjectsManagerBase.getInstanceEx().updateLastProjectPath();
410 | WelcomeFrame.showIfNoProjectOpened();
411 | model.deleteItem(selectedItem);
412 | if (selectedIndex == list.getModel().getSize()) { // is last
413 | list.setSelectedIndex(selectedIndex - 1);
414 | } else {
415 | list.setSelectedIndex(selectedIndex);
416 | }
417 |
418 | if (action.currentProject) { // is actual
419 | if (selectedIndex == list.getModel().getSize()) { // is last
420 | selectedIndex--;
421 | }
422 | if (selectedIndex >= 0) {
423 | PopupFactoryImpl.ActionItem o = (PopupFactoryImpl.ActionItem) model.get(selectedIndex);
424 | if (o != null && o.getAction() instanceof SwitchFrameAction) {
425 | switchFrame(((SwitchFrameAction) o.getAction()).getProject());
426 | } else {
427 | selectedIndex--;
428 | o = (PopupFactoryImpl.ActionItem) model.get(selectedIndex);
429 | if (o != null && o.getAction() instanceof SwitchFrameAction) {
430 | switchFrame(((SwitchFrameAction) o.getAction()).getProject());
431 | }
432 | }
433 | }
434 | }
435 | }
436 | }
437 | }
438 | });
439 | Shortcut[] shortcuts = KeymapManagerEx.getInstanceEx().getActiveKeymap().getShortcuts(getId());
440 | if (shortcuts == null) {
441 | return;
442 | }
443 |
444 | for (Shortcut shortcut : shortcuts) {
445 | if (shortcut instanceof KeyboardShortcut) {
446 | KeyboardShortcut keyboardShortcut = (KeyboardShortcut) shortcut;
447 | String[] split = keyboardShortcut.getFirstKeyStroke().toString().split(" ");
448 | for (String s : split) {
449 | if (s.equalsIgnoreCase("alt") || s.equalsIgnoreCase("ctrl") || s.equalsIgnoreCase("meta")) {
450 | if (s.equalsIgnoreCase("ctrl")) {
451 | s = "control";
452 | }
453 | register(popup, KeyStroke.getKeyStroke("released " + s.toUpperCase()), new AbstractAction() {
454 | @Override
455 | public void actionPerformed(ActionEvent e) {
456 | if (invoked.get()) {
457 | popup.handleSelect(true);
458 | }
459 | }
460 | });
461 |
462 | }
463 | }
464 | register(popup, keyboardShortcut.getFirstKeyStroke(), new AbstractAction() {
465 | @Override
466 | public void actionPerformed(ActionEvent e) {
467 | invoked.set(true);
468 | JList list = popup.getList();
469 | int selectedIndex = list.getSelectedIndex();
470 | int size = list.getModel().getSize();
471 | if (selectedIndex + 1 < size) {
472 | list.setSelectedIndex(selectedIndex + 1);
473 | } else {
474 | list.setSelectedIndex(0);
475 | }
476 | }
477 | });
478 | register(popup, KeyStroke.getKeyStroke("shift " + keyboardShortcut.getFirstKeyStroke()),
479 | new AbstractAction() {
480 | @Override
481 | public void actionPerformed(ActionEvent e) {
482 | invoked.set(true);
483 | JList list = popup.getList();
484 | int selectedIndex = list.getSelectedIndex();
485 | int size = list.getModel().getSize();
486 | if (selectedIndex - 1 >= 0) {
487 | list.setSelectedIndex(selectedIndex - 1);
488 | } else {
489 | list.setSelectedIndex(size - 1);
490 | }
491 | }
492 | });
493 |
494 | }
495 | }
496 | }
497 |
498 | private void register(ListPopupImpl popup, KeyStroke keyStroke, AbstractAction action) {
499 | LOG.debug("registering ", keyStroke);
500 | if (keyStroke != null) {
501 | popup.registerAction("Custom:" + keyStroke, keyStroke, action);
502 | }
503 | }
504 |
505 | private DataContext getDataContext(ListPopupImpl popup) {
506 | DataContext dataContext = DataManager.getInstance().getDataContext(popup.getOwner());
507 | Project project = dataContext.getData(CommonDataKeys.PROJECT);
508 | if (project == null) {
509 | throw new IllegalStateException("Project is null for " + popup.getOwner());
510 | }
511 | return dataContext;
512 | }
513 |
514 | @Override
515 | protected boolean isEnabled() {
516 | return true;
517 | }
518 |
519 | @Override
520 | protected JBPopupFactory.ActionSelectionAid getAidMethod() {
521 | return FrameSwitcherSettings.getInstance().getPopupSelectionAid();
522 | }
523 |
524 | private class ReopenRecentWrapper extends ReopenProjectAction {
525 |
526 | public ReopenRecentWrapper(ReopenProjectAction recentProjectsAction) {
527 | super(recentProjectsAction.getProjectPath(), recentProjectsAction.getProjectName(),
528 | recentProjectsAction.getProjectDisplayName());
529 | getTemplatePresentation()
530 | .setIcon(IconResolver.resolveIcon(recentProjectsAction.getProjectPath(), loadProjectIcon));
531 | }
532 |
533 | public ReopenRecentWrapper(String s) {
534 | super(s, null, s);
535 | getTemplatePresentation().setIcon(IconResolver.resolveIcon(s, loadProjectIcon));
536 | }
537 |
538 | @Override
539 | public void actionPerformed(AnActionEvent anActionEvent) {
540 | AWTEvent trueCurrentEvent = IdeEventQueue.getInstance().getTrueCurrentEvent();
541 | if (trueCurrentEvent instanceof MouseEvent) {
542 | int modifiersEx = ((MouseEvent) trueCurrentEvent).getModifiersEx();
543 | if (modifiersEx == KeyEvent.SHIFT_DOWN_MASK) {
544 | int confirmOpenNewProject = GeneralSettings.getInstance().getConfirmOpenNewProject();
545 | try {
546 | GeneralSettings.getInstance()
547 | .setConfirmOpenNewProject(GeneralSettings.OPEN_PROJECT_SAME_WINDOW);
548 | super.actionPerformed(new AnActionEvent(null, anActionEvent.getDataContext(),
549 | "FrameSwitcher-OPEN_PROJECT_SAME_WINDOW", getTemplatePresentation(),
550 | ActionManager.getInstance(), 0));
551 | } finally {
552 | restoreOption(confirmOpenNewProject);
553 | }
554 | } else if (modifiersEx == KeyEvent.CTRL_DOWN_MASK) {
555 | int confirmOpenNewProject = GeneralSettings.getInstance().getConfirmOpenNewProject();
556 | try {
557 | GeneralSettings.getInstance().setConfirmOpenNewProject(GeneralSettings.OPEN_PROJECT_NEW_WINDOW);
558 | super.actionPerformed(new AnActionEvent(null, anActionEvent.getDataContext(),
559 | "FrameSwitcher-OPEN_PROJECT_NEW_WINDOW", getTemplatePresentation(),
560 | ActionManager.getInstance(), InputEvent.CTRL_MASK));
561 | } finally {
562 | restoreOption(confirmOpenNewProject);
563 | }
564 | } else {
565 | super.actionPerformed(anActionEvent);
566 | }
567 | } else {
568 | super.actionPerformed(anActionEvent);
569 | }
570 |
571 | SwingUtilities.invokeLater(() -> {
572 | requestFocus(this);
573 | });
574 | }
575 |
576 | private void requestFocus(ReopenRecentWrapper action) {
577 | Project openedProject = null;
578 | Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
579 | for (int i = openProjects.length - 1; i >= 0; i--) {
580 | Project project = openProjects[i];
581 | if (java.util.Objects.equals(project.getBasePath(), action.getProjectPath())
582 | || java.util.Objects.equals(project.getProjectFilePath(), action.getProjectPath())) {
583 | openedProject = project;
584 | break;
585 | }
586 | }
587 | if (openedProject != null) {
588 | requestFocus(openedProject);
589 | } else {
590 | LOG.info("Unable to request focus for reopened project: " + action.getProjectPath());
591 | }
592 | }
593 |
594 | private void requestFocus(Project openProject) {
595 | String requestFocusMs = FrameSwitcherApplicationService.getInstance().getState().getRequestFocusMs();
596 | try {
597 | int ms = Integer.parseInt(requestFocusMs);
598 | if (ms > 0) {
599 | SingleAlarm singleAlarm = new SingleAlarm(() -> {
600 | if (openProject.isDisposed()) {
601 | return;
602 | }
603 | LOG.info("Requesting focus for " + openProject);
604 | switchFrame(openProject);
605 |
606 | }, ms, Alarm.ThreadToUse.SWING_THREAD, openProject);
607 | singleAlarm.request();
608 | }
609 | } catch (NumberFormatException e) {
610 | }
611 | }
612 | }
613 |
614 | private static void restoreOption(int confirmOpenNewProject) {
615 | SwingUtilities.invokeLater(() -> GeneralSettings.getInstance().setConfirmOpenNewProject(confirmOpenNewProject));
616 | }
617 |
618 | private class SwitchFrameAction extends DumbAwareAction {
619 |
620 | private final Project project;
621 | private final boolean currentProject;
622 |
623 | public SwitchFrameAction(Project project, boolean currentProject) {
624 | super(project.getName().replace("_", "__"));
625 | this.project = project;
626 | this.currentProject = currentProject;
627 | if (loadProjectIcon) {
628 | getTemplatePresentation().setIcon(IconResolver.resolveIcon(project.getBasePath(), loadProjectIcon));
629 | } else {
630 | getTemplatePresentation().setIcon(currentProject ? forward : empty);
631 | }
632 | }
633 |
634 | public Project getProject() {
635 | return project;
636 | }
637 |
638 | public @SystemIndependent String getBasePath() {
639 | return project.getBasePath();
640 | }
641 |
642 | @Override
643 | public void actionPerformed(AnActionEvent e) {
644 | switchFrame(project);
645 | }
646 | }
647 |
648 | public void switchFrame(Project project) {
649 | SwingUtilities.invokeLater(() -> {
650 | IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> {
651 | FocusUtils.requestFocus(project, false, isCustom());
652 | });
653 | });
654 | }
655 |
656 | @NotNull
657 | protected String getId() {
658 | return "FrameSwitchAction";
659 | }
660 |
661 | private class SwitchToRemoteProjectAction extends DumbAwareAction {
662 |
663 | private final RemoteProject remoteProject;
664 | private final UUID uuid;
665 |
666 | public SwitchToRemoteProjectAction(RemoteProject remoteProject, UUID uuid) {
667 | super(remoteProject.getName().replace("_", "__"));
668 | this.remoteProject = remoteProject;
669 | this.uuid = uuid;
670 | getTemplatePresentation()
671 | .setIcon(IconResolver.resolveIcon(remoteProject.getProjectPath(), loadProjectIcon));
672 | }
673 |
674 | public String getProjectPath() {
675 | return FileUtil.toSystemIndependentName(remoteProject.getProjectPath());
676 | }
677 |
678 | @Override
679 | public void actionPerformed(AnActionEvent anActionEvent) {
680 | FrameSwitcherApplicationService.getInstance().getRemoteSender().openProject(uuid, remoteProject);
681 | }
682 | }
683 |
684 | protected boolean isCustom() {
685 | return false;
686 | }
687 | }
688 |
--------------------------------------------------------------------------------
/src/main/java/krasa/frameswitcher/FrameSwitcherApplicationService.java:
--------------------------------------------------------------------------------
1 | package krasa.frameswitcher;
2 |
3 | import com.intellij.openapi.Disposable;
4 | import com.intellij.openapi.application.ApplicationManager;
5 | import com.intellij.openapi.components.PersistentStateComponent;
6 | import com.intellij.openapi.components.State;
7 | import com.intellij.openapi.components.Storage;
8 | import com.intellij.openapi.diagnostic.Logger;
9 | import krasa.frameswitcher.networking.*;
10 | import org.jetbrains.annotations.NotNull;
11 |
12 | import java.util.UUID;
13 |
14 | @State(name = "FrameSwitcherSettings", storages = {@Storage("FrameSwitcherSettings.xml")})
15 | public class FrameSwitcherApplicationService implements PersistentStateComponent, Disposable {
16 |
17 | private static final Logger LOG = Logger.getInstance(FrameSwitcherApplicationService.class);
18 |
19 | public static final String IDE_MAX_RECENT_PROJECTS = "ide.max.recent.projects";
20 |
21 | private FrameSwitcherSettings settings = new FrameSwitcherSettings();
22 | private RemoteInstancesState remoteInstancesState = new RemoteInstancesState();
23 | private ProjectFocusMonitor projectFocusMonitor = new ProjectFocusMonitor();
24 | private RemoteSender remoteSender = new DummyRemoteSender();
25 | boolean initialized;
26 |
27 | public static FrameSwitcherApplicationService getInstance() {
28 | FrameSwitcherApplicationService service = ApplicationManager.getApplication().getService(FrameSwitcherApplicationService.class);
29 | if (service != null && !service.initialized) { //DynamicPlugins.unloadPlugin causes null
30 | service.initComponent();
31 | }
32 | return service;
33 | }
34 |
35 | public FrameSwitcherApplicationService() {
36 | }
37 |
38 | public void initComponent() {
39 | long start = System.currentTimeMillis();
40 | initRemoting();
41 | LOG.debug("initComponent done in ", System.currentTimeMillis() - start, "ms");
42 | }
43 |
44 | private synchronized void initRemoting() {
45 | if (!initialized) {
46 | if (this.settings.isRemoting()) {
47 | if (!(remoteSender instanceof RemoteSenderImpl)) {
48 | try {
49 | UUID uuid = getState().getOrInitializeUuid();
50 | remoteSender = new RemoteSenderImpl(this, uuid, new Receiver(uuid, this), getState().getPort());
51 | } catch (Throwable e) {
52 | remoteInstancesState = new RemoteInstancesState();
53 | remoteSender = new DummyRemoteSender();
54 | LOG.error(e);
55 | }
56 | }
57 | } else {
58 | if (remoteSender instanceof RemoteSenderImpl) {
59 | remoteSender.dispose();
60 | }
61 |
62 | if (!(remoteSender instanceof DummyRemoteSender)) {
63 | remoteInstancesState = new RemoteInstancesState();
64 | remoteSender = new DummyRemoteSender();
65 | }
66 | }
67 | initialized = true;
68 | }
69 | }
70 |
71 | public ProjectFocusMonitor getProjectFocusMonitor() {
72 | return projectFocusMonitor;
73 | }
74 |
75 | @NotNull
76 | @Override
77 | public FrameSwitcherSettings getState() {
78 | return settings;
79 | }
80 |
81 | @Override
82 | public void loadState(FrameSwitcherSettings frameSwitcherSettings) {
83 | this.settings = frameSwitcherSettings;
84 | frameSwitcherSettings.applyMaxRecentProjectsToRegistry();
85 | }
86 |
87 | public void updateSettings(FrameSwitcherSettings settings) {
88 | this.settings = settings;
89 | this.settings.applyOrResetMaxRecentProjectsToRegistry();
90 | initRemoting();
91 | }
92 |
93 | public RemoteInstancesState getRemoteInstancesState() {
94 | return remoteInstancesState;
95 | }
96 |
97 | public RemoteSender getRemoteSender() {
98 | return remoteSender;
99 | }
100 |
101 | @Override
102 | public void dispose() {
103 | long start = System.currentTimeMillis();
104 | if (getRemoteSender() != null) {
105 | getRemoteSender().dispose();
106 | }
107 | LOG.debug("disposeComponent done in ", System.currentTimeMillis() - start, "ms");
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/main/java/krasa/frameswitcher/FrameSwitcherGui.form:
--------------------------------------------------------------------------------
1 |
2 |
235 |
--------------------------------------------------------------------------------
/src/main/java/krasa/frameswitcher/FrameSwitcherGui.java:
--------------------------------------------------------------------------------
1 | package krasa.frameswitcher;
2 |
3 | import com.intellij.openapi.fileChooser.FileChooser;
4 | import com.intellij.openapi.fileChooser.FileChooserDescriptor;
5 | import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
6 | import com.intellij.openapi.ui.popup.JBPopupFactory;
7 | import com.intellij.openapi.vfs.VirtualFile;
8 | import org.jdesktop.swingx.combobox.EnumComboBoxModel;
9 |
10 | import javax.swing.*;
11 | import java.awt.event.ActionEvent;
12 | import java.awt.event.ActionListener;
13 | import java.util.ArrayList;
14 | import java.util.Arrays;
15 | import java.util.List;
16 |
17 | public class FrameSwitcherGui {
18 |
19 | private JPanel root;
20 | private JTextField maxRecentProjects;
21 | private JComboBox popupAidComboBox;
22 |
23 | private JList recentProjectFiltersList;
24 | private DefaultListModel filterListModel;
25 | private JButton addButton;
26 | private JButton remove;
27 | private JCheckBox remoting;
28 |
29 | private JCheckBox defaultSelectionCurrentProject;
30 | private JTextField requestFocusMs;
31 | private JCheckBox loadProjectIcon;
32 |
33 | private JButton addInclude;
34 | private JButton removeInclude;
35 | private JList includeProjectList;
36 | private DefaultListModel includeListModel;
37 |
38 | private FrameSwitcherSettings settings;
39 | private EnumComboBoxModel comboBoxModel;
40 |
41 | public FrameSwitcherGui(FrameSwitcherSettings settings) {
42 | this.settings = settings;
43 |
44 | addButton.addActionListener(e -> browseForFile(FrameSwitcherGui.this.filterListModel));
45 | remove.addActionListener(new ActionListener() {
46 | @Override
47 | public void actionPerformed(ActionEvent e) {
48 | int leadSelectionIndex = recentProjectFiltersList.getSelectionModel().getLeadSelectionIndex();
49 | if (!recentProjectFiltersList.getSelectionModel().isSelectionEmpty()) {
50 | filterListModel.remove(leadSelectionIndex);
51 | }
52 | }
53 | });
54 | addInclude.addActionListener(e -> browseForFile(FrameSwitcherGui.this.includeListModel));
55 | removeInclude.addActionListener(e -> {
56 | int leadSelectionIndex = includeProjectList.getSelectionModel().getLeadSelectionIndex();
57 | if (!includeProjectList.getSelectionModel().isSelectionEmpty()) {
58 | includeListModel.remove(leadSelectionIndex);
59 | }
60 | });
61 | initModel(settings);
62 | }
63 |
64 | private void initModel(FrameSwitcherSettings settings) {
65 | comboBoxModel = new EnumComboBoxModel(JBPopupFactory.ActionSelectionAid.class);
66 | comboBoxModel.setSelectedItem(settings.getPopupSelectionAid());
67 | popupAidComboBox.setModel(comboBoxModel);
68 |
69 | filterListModel = new DefaultListModel();
70 | for (String s : settings.getRecentProjectPaths()) {
71 | filterListModel.addElement(s);
72 | }
73 | recentProjectFiltersList.setModel(filterListModel);
74 | recentProjectFiltersList.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
75 |
76 | includeListModel = new DefaultListModel();
77 | for (String s : settings.getIncludeLocations()) {
78 | includeListModel.addElement(s);
79 | }
80 | includeProjectList.setModel(includeListModel);
81 | includeProjectList.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
82 |
83 | }
84 |
85 | private void browseForFile(DefaultListModel listModel) {
86 | final FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createMultipleFoldersDescriptor();
87 |
88 | descriptor.setTitle("Select parent folder");
89 | // 10.5 does not have #chooseFile
90 | VirtualFile[] virtualFile = FileChooser.chooseFiles(descriptor, null, null);
91 | if (virtualFile != null) {
92 | for (int i = 0; i < virtualFile.length; i++) {
93 | VirtualFile file = virtualFile[i];
94 | listModel.addElement(file.getPath());
95 | }
96 | }
97 | }
98 |
99 | public JPanel getRoot() {
100 | return root;
101 | }
102 |
103 | public void importFrom(FrameSwitcherSettings data) {
104 | initModel(data);
105 | setData(data);
106 | comboBoxModel.setSelectedItem(data.getPopupSelectionAid());
107 | }
108 |
109 | public FrameSwitcherSettings exportDisplayedSettings() {
110 | try {
111 | //noinspection ResultOfMethodCallIgnored
112 | Integer.parseInt(maxRecentProjects.getText());
113 | } catch (Exception e) {
114 | maxRecentProjects.setText("");
115 | }
116 | getData(settings);
117 | settings.setPopupSelectionAid(comboBoxModel.getSelectedItem());
118 | settings.setRecentProjectPaths(toListStrings(filterListModel.toArray()));
119 | settings.setIncludeLocations(toListStrings(includeListModel.toArray()));
120 | return settings;
121 | }
122 |
123 | private List toListStrings(final Object[] objects) {
124 | final ArrayList recentProjectPaths = new ArrayList();
125 | for (Object object : objects) {
126 | recentProjectPaths.add((String) object);
127 | }
128 | return recentProjectPaths;
129 | }
130 |
131 |
132 | public boolean isModified_custom(FrameSwitcherSettings data) {
133 | if (!Arrays.equals(filterListModel.toArray(), data.getRecentProjectPaths().toArray())) {
134 | return true;
135 | }
136 | if (!Arrays.equals(includeListModel.toArray(), data.getIncludeLocations().toArray())) {
137 | return true;
138 | }
139 | if (comboBoxModel.getSelectedItem() != data.getPopupSelectionAid()) {
140 | return true;
141 | }
142 | return isModified(data);
143 | }
144 |
145 | public void setData(FrameSwitcherSettings data) {
146 | maxRecentProjects.setText(data.getMaxRecentProjects());
147 | remoting.setSelected(data.isRemoting());
148 | defaultSelectionCurrentProject.setSelected(data.isDefaultSelectionCurrentProject());
149 | requestFocusMs.setText(data.getRequestFocusMs());
150 | loadProjectIcon.setSelected(data.isLoadProjectIcon());
151 | }
152 |
153 | public void getData(FrameSwitcherSettings data) {
154 | data.setMaxRecentProjects(maxRecentProjects.getText());
155 | data.setRemoting(remoting.isSelected());
156 | data.setDefaultSelectionCurrentProject(defaultSelectionCurrentProject.isSelected());
157 | data.setRequestFocusMs(requestFocusMs.getText());
158 | data.setLoadProjectIcon(loadProjectIcon.isSelected());
159 | }
160 |
161 | public boolean isModified(FrameSwitcherSettings data) {
162 | if (maxRecentProjects.getText() != null ? !maxRecentProjects.getText().equals(data.getMaxRecentProjects()) : data.getMaxRecentProjects() != null)
163 | return true;
164 | if (remoting.isSelected() != data.isRemoting()) return true;
165 | if (defaultSelectionCurrentProject.isSelected() != data.isDefaultSelectionCurrentProject()) return true;
166 | if (requestFocusMs.getText() != null ? !requestFocusMs.getText().equals(data.getRequestFocusMs()) : data.getRequestFocusMs() != null)
167 | return true;
168 | if (loadProjectIcon.isSelected() != data.isLoadProjectIcon()) return true;
169 | return false;
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/src/main/java/krasa/frameswitcher/FrameSwitcherSettings.java:
--------------------------------------------------------------------------------
1 | package krasa.frameswitcher;
2 |
3 | import com.intellij.ide.ReopenProjectAction;
4 | import com.intellij.openapi.diagnostic.Logger;
5 | import com.intellij.openapi.ui.popup.JBPopupFactory;
6 | import com.intellij.openapi.util.registry.Registry;
7 | import krasa.frameswitcher.networking.dto.RemoteProject;
8 | import org.apache.commons.lang3.StringUtils;
9 |
10 | import java.beans.Transient;
11 | import java.io.File;
12 | import java.io.IOException;
13 | import java.util.ArrayList;
14 | import java.util.List;
15 | import java.util.UUID;
16 |
17 | public class FrameSwitcherSettings {
18 | private static final Logger LOG = Logger.getInstance(FrameSwitcherSettings.class);
19 |
20 | private JBPopupFactory.ActionSelectionAid popupSelectionAid = JBPopupFactory.ActionSelectionAid.SPEEDSEARCH;
21 | private List recentProjectPaths = new ArrayList();
22 | private List includeLocations = new ArrayList();
23 | private String maxRecentProjects = "";
24 | private String uuid;
25 | private boolean remoting = false;
26 | private boolean defaultSelectionCurrentProject = true;
27 | private String requestFocusMs = "100";
28 | private boolean loadProjectIcon = true;
29 | private int port = 45588;
30 |
31 | public JBPopupFactory.ActionSelectionAid getPopupSelectionAid() {
32 | return popupSelectionAid;
33 | }
34 |
35 | public boolean shouldShow(RemoteProject recentProjectsAction) {
36 | return shouldShow(recentProjectsAction.getProjectPath());
37 | }
38 |
39 | public String getUuid() {
40 | return uuid;
41 | }
42 |
43 | @Transient
44 | public UUID getOrInitializeUuid() {
45 | if (uuid == null) {
46 | uuid = UUID.randomUUID().toString();
47 | }
48 | return UUID.fromString(uuid);
49 | }
50 |
51 | public void setUuid(String uuid) {
52 | this.uuid = uuid;
53 | }
54 |
55 | public boolean shouldShow(ReopenProjectAction action) {
56 | return shouldShow(action.getProjectPath());
57 | }
58 |
59 | private boolean shouldShow(String projectPath) {
60 | if (recentProjectPaths.size() == 0) {
61 | return true;
62 | }
63 | try {
64 | File file = new File(projectPath);
65 | String canonicalPath = file.getCanonicalPath();
66 | for (String recentProjectPath : recentProjectPaths) {
67 | if (canonicalPath.startsWith(new File(recentProjectPath).getCanonicalPath())) {
68 | return true;
69 | }
70 | }
71 | } catch (IOException e) {
72 | LOG.warn(e);
73 | }
74 | return false;
75 | }
76 |
77 | public List getRecentProjectPaths() {
78 | return recentProjectPaths;
79 | }
80 |
81 | public static FrameSwitcherSettings getInstance() {
82 | return FrameSwitcherApplicationService.getInstance().getState();
83 | }
84 |
85 | public String getMaxRecentProjects() {
86 | try {
87 | if (!StringUtils.isBlank(maxRecentProjects)) {
88 | // noinspection ResultOfMethodCallIgnored
89 | Integer.parseInt(maxRecentProjects);
90 | }
91 | } catch (Exception e) {
92 | maxRecentProjects = "";
93 | }
94 | return maxRecentProjects;
95 | }
96 |
97 | public List getIncludeLocations() {
98 | return includeLocations;
99 | }
100 |
101 | public void setIncludeLocations(List includeLocations) {
102 | this.includeLocations = includeLocations;
103 | }
104 |
105 | public void setPopupSelectionAid(JBPopupFactory.ActionSelectionAid popupSelectionAid) {
106 | this.popupSelectionAid = popupSelectionAid;
107 | }
108 |
109 | public void setRecentProjectPaths(List recentProjectPaths) {
110 | this.recentProjectPaths = recentProjectPaths;
111 | }
112 |
113 | public void setMaxRecentProjects(final String maxRecentProjects) {
114 | this.maxRecentProjects = maxRecentProjects;
115 | try {
116 | if (!StringUtils.isBlank(this.maxRecentProjects)) {
117 | // noinspection ResultOfMethodCallIgnored
118 | Integer.parseInt(this.maxRecentProjects);
119 | }
120 | } catch (Exception e) {
121 | this.maxRecentProjects = "";
122 | }
123 | }
124 |
125 | public boolean isRemoting() {
126 | return remoting;
127 | }
128 |
129 | public void setRemoting(final boolean remoting) {
130 | this.remoting = remoting;
131 | }
132 |
133 | public void applyMaxRecentProjectsToRegistry() {
134 | try {
135 | if (!StringUtils.isBlank(maxRecentProjects)) {
136 | LOG.info("Changing Registry " + FrameSwitcherApplicationService.IDE_MAX_RECENT_PROJECTS + " to "
137 | + maxRecentProjects);
138 | Registry.get(FrameSwitcherApplicationService.IDE_MAX_RECENT_PROJECTS)
139 | .setValue(Integer.parseInt(maxRecentProjects));
140 | }
141 | } catch (Throwable e) {// Registry key ide.max.recent.projects is not defined
142 | LOG.debug(e);
143 | }
144 | }
145 |
146 | public void applyOrResetMaxRecentProjectsToRegistry() {
147 | try {
148 | if (!StringUtils.isBlank(maxRecentProjects)) {
149 | applyMaxRecentProjectsToRegistry();
150 | } else {
151 | LOG.info("Changing Registry " + FrameSwitcherApplicationService.IDE_MAX_RECENT_PROJECTS + " to default");
152 | Registry.get(FrameSwitcherApplicationService.IDE_MAX_RECENT_PROJECTS).resetToDefault();
153 | }
154 | } catch (Throwable e) {// Registry key ide.max.recent.projects is not defined
155 | LOG.debug(e);
156 | }
157 | }
158 |
159 | public boolean isDefaultSelectionCurrentProject() {
160 | return defaultSelectionCurrentProject;
161 | }
162 |
163 | public void setDefaultSelectionCurrentProject(final boolean defaultSelectionCurrentProject) {
164 | this.defaultSelectionCurrentProject = defaultSelectionCurrentProject;
165 | }
166 |
167 | public String getRequestFocusMs() {
168 | return requestFocusMs;
169 | }
170 |
171 | public void setRequestFocusMs(final String requestFocusMs) {
172 | this.requestFocusMs = requestFocusMs;
173 | }
174 |
175 | public boolean isLoadProjectIcon() {
176 | return loadProjectIcon;
177 | }
178 |
179 | public void setLoadProjectIcon(final boolean loadProjectIcon) {
180 | this.loadProjectIcon = loadProjectIcon;
181 | }
182 |
183 | public int getPort() {
184 | return port;
185 | }
186 |
187 | public void setPort(int port) {
188 | this.port = port;
189 | }
190 | }
191 |
--------------------------------------------------------------------------------
/src/main/java/krasa/frameswitcher/FrameSwitcherUtils.java:
--------------------------------------------------------------------------------
1 | package krasa.frameswitcher;
2 |
3 | /**
4 | * @author Vojtech Krasa
5 | */
6 | public class FrameSwitcherUtils {
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/krasa/frameswitcher/IconResolver.java:
--------------------------------------------------------------------------------
1 | package krasa.frameswitcher;
2 |
3 | import com.intellij.openapi.diagnostic.Logger;
4 | import com.intellij.ui.JBColor;
5 | import com.intellij.util.ImageLoader;
6 | import com.intellij.util.ui.JBImageIcon;
7 | import org.jetbrains.annotations.Nullable;
8 | import org.jetbrains.annotations.SystemIndependent;
9 |
10 | import javax.swing.*;
11 | import java.awt.*;
12 | import java.io.File;
13 | import java.io.IOException;
14 | import java.util.HashMap;
15 | import java.util.HashSet;
16 | import java.util.Map;
17 | import java.util.Set;
18 |
19 | public class IconResolver {
20 | private final static Logger LOG = Logger.getInstance(FrameSwitchAction.class);
21 | static Set brokenIcons = new HashSet<>();
22 | static Map cache = new HashMap<>();
23 |
24 | public static Icon resolveIcon(@SystemIndependent String basepath, boolean loadProjectIcon) {
25 | if (brokenIcons.contains(basepath)) {
26 | return null;
27 | }
28 | Icon cachedIcon = cache.get(basepath);
29 | if (cachedIcon != null) {
30 | return cachedIcon;
31 | }
32 | long start = System.currentTimeMillis();
33 | Icon icon = resolveIcon2(basepath, loadProjectIcon);
34 | long end = System.currentTimeMillis();
35 | if (end - start > 200) {
36 | brokenIcons.add(basepath);
37 | LOG.warn("Icon resolving took too long: " + (end - start) + "ms - " + basepath);
38 | }
39 | return icon;
40 | }
41 |
42 | @Nullable
43 | private static Icon resolveIcon2(String basepath, boolean loadProjectIcon) {
44 | try {
45 | if (!loadProjectIcon) {
46 | return null;
47 | }
48 | File base = new File(basepath);
49 | if (!base.exists()) {
50 | return null;
51 | }
52 | if (base.isFile()) {
53 | base = base.getParentFile();
54 | }
55 | if (base.getName().startsWith(".")) {
56 | base = base.getParentFile();
57 | }
58 | if (!base.exists()) {
59 | return null;
60 | }
61 |
62 | Icon icon = null;
63 | icon = getIcon(base, ".idea/icon.png");
64 | if (icon != null) {
65 | return icon;
66 | }
67 | // icon = getIcon(base, ".idea/icon.svg");
68 | // if (icon != null) {
69 | // return icon;
70 | // }
71 | icon = getIcon(base, "src/main/resources/META-INF/pluginIcon.svg");
72 | if (icon != null) {
73 | return icon;
74 | }
75 | icon = getIcon(base, "resources/META-INF/pluginIcon.svg");
76 | if (icon != null) {
77 | return icon;
78 | }
79 | icon = getIcon(base, "META-INF/pluginIcon.svg");
80 | if (icon != null) {
81 | return icon;
82 | }
83 | icon = getIcon(base, "icon.png");
84 | if (icon != null) {
85 | return icon;
86 | }
87 | icon = getIcon(base, "icon.svg");
88 | if (icon != null) {
89 | return icon;
90 | }
91 | icon = getIcon(base, ".idea/icon.svg");
92 | if (icon != null) {
93 | return icon;
94 | }
95 | return icon;
96 | } catch (Throwable e) {
97 | LOG.debug(e);
98 | return null;
99 | }
100 | }
101 |
102 | @Nullable
103 | public static Icon getIcon(File base, String subpath) {
104 | File file = null;
105 | try {
106 | Icon icon = null;
107 | if (!JBColor.isBright()) {
108 | File darcula = new File(base, subpath.replace(".svg", "_dark.svg").replace(".png", "_dark.png"));
109 | if (darcula.exists()) {
110 | file = darcula;
111 | }
112 | }
113 | if (file == null) {
114 | file = new File(base, subpath);
115 | }
116 |
117 | if (file.exists()) {
118 | icon = new JBImageIcon(loadImage(file));
119 | // if (icon != null && icon.getIconHeight() > 1
120 | // && icon.getIconHeight() != FrameSwitchAction.empty.getIconHeight()) {
121 | // icon = IconUtil.scale(icon, null,
122 | // (float) FrameSwitchAction.empty.getIconHeight() / icon.getIconHeight());
123 | // }
124 | // // material-theme-jetbrains needs to be scaled 2x
125 | // if (icon != null && icon.getIconHeight() > 1
126 | // && icon.getIconHeight() != FrameSwitchAction.empty.getIconHeight()) {
127 | // icon = IconUtil.scale(icon, null,
128 | // (float) FrameSwitchAction.empty.getIconHeight() / icon.getIconHeight());
129 | // }
130 | if (icon.getIconHeight() > FrameSwitchAction.empty.getIconHeight()
131 | || icon.getIconWidth() > FrameSwitchAction.empty.getIconWidth()) {
132 | brokenIcons.add(base.getAbsolutePath());
133 | LOG.warn("Scaling failed, wrong icon size: " + file.getAbsolutePath() + " " + icon.getIconHeight()
134 | + "x" + icon.getIconWidth());
135 | return null;
136 | }
137 | }
138 | return icon;
139 | } catch (Throwable e) {
140 | LOG.warn(String.valueOf(file), e);
141 | return null;
142 | }
143 | }
144 |
145 | public static Image loadImage(File file) throws IOException {
146 | Image image = ImageLoader.loadFromUrl(file.toURI().toURL());
147 | if (image != null) {
148 | image = ImageLoader.scaleImage(image, FrameSwitchAction.empty.getIconHeight(), FrameSwitchAction.empty.getIconHeight());
149 | }
150 | return image;
151 | // if (file.getName().endsWith(".svg")) {
152 | // return SVGLoader.load(file.toURI().toURL(), 1.0f);
153 | // } else {
154 | // return ImageIO.read(file);
155 | // }
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/src/main/java/krasa/frameswitcher/MyConfigurable.java:
--------------------------------------------------------------------------------
1 | package krasa.frameswitcher;
2 |
3 | import com.intellij.openapi.options.Configurable;
4 | import com.intellij.openapi.options.ConfigurationException;
5 | import org.jetbrains.annotations.Nls;
6 | import org.jetbrains.annotations.NotNull;
7 | import org.jetbrains.annotations.Nullable;
8 |
9 | import javax.swing.*;
10 |
11 | public class MyConfigurable implements Configurable {
12 | private FrameSwitcherGui gui;
13 | @Nls
14 | @Override
15 | public String getDisplayName() {
16 | return "Frame Switcher";
17 | }
18 |
19 | @Nullable
20 | public Icon getIcon() {
21 | return null;
22 | }
23 |
24 | @Nullable
25 | @Override
26 | public String getHelpTopic() {
27 | return null;
28 | }
29 |
30 | @Nullable
31 | @Override
32 | public JComponent createComponent() {
33 | if (gui == null) {
34 | gui = new FrameSwitcherGui(getSettings());
35 | }
36 | return gui.getRoot();
37 | }
38 |
39 | @Override
40 | public boolean isModified() {
41 | return gui.isModified_custom(getSettings());
42 | }
43 |
44 | @Override
45 | public void apply() throws ConfigurationException {
46 | FrameSwitcherSettings settings = gui.exportDisplayedSettings();
47 |
48 | FrameSwitcherApplicationService component = FrameSwitcherApplicationService.getInstance();
49 | component.updateSettings(settings);
50 | }
51 |
52 | @Override
53 | public void reset() {
54 | gui.importFrom(getSettings());
55 | }
56 |
57 | @NotNull
58 | private FrameSwitcherSettings getSettings() {
59 | return FrameSwitcherApplicationService.getInstance().getState();
60 | }
61 |
62 | @Override
63 | public void disposeUIResources() {
64 | gui = null;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/main/java/krasa/frameswitcher/MyPreloadingActivity.java:
--------------------------------------------------------------------------------
1 | package krasa.frameswitcher;
2 |
3 | import com.intellij.openapi.project.Project;
4 | import com.intellij.openapi.startup.StartupActivity;
5 | import org.jetbrains.annotations.NotNull;
6 |
7 | public class MyPreloadingActivity implements StartupActivity.DumbAware {
8 |
9 | @Override
10 | public void runActivity(@NotNull Project project) {
11 | FrameSwitcherApplicationService.getInstance(); //for initComponent()
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/krasa/frameswitcher/ProjectFocusMonitor.java:
--------------------------------------------------------------------------------
1 | package krasa.frameswitcher;
2 |
3 | import com.intellij.openapi.project.Project;
4 |
5 | import java.util.LinkedHashSet;
6 |
7 | /**
8 | * @author Vojtech Krasa
9 | */
10 | public class ProjectFocusMonitor {
11 |
12 | private LinkedHashSet projects = new LinkedHashSet();
13 |
14 | public synchronized void focusGained(Project project) {
15 | projects.remove(project);
16 | projects.add(project);
17 | }
18 |
19 | public synchronized void projectClosed(Project project) {
20 | projects.remove(project);
21 | }
22 |
23 | public synchronized Project[] getProjectsOrderedByFocus() {
24 | return projects.toArray(new Project[0]);
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/krasa/frameswitcher/ProjectStartupActivity.java:
--------------------------------------------------------------------------------
1 | package krasa.frameswitcher;
2 |
3 | import com.intellij.openapi.Disposable;
4 | import com.intellij.openapi.diagnostic.Logger;
5 | import com.intellij.openapi.project.DumbAware;
6 | import com.intellij.openapi.project.Project;
7 | import com.intellij.openapi.startup.ProjectActivity;
8 | import com.intellij.openapi.util.Disposer;
9 | import com.intellij.openapi.wm.WindowManager;
10 | import kotlin.Unit;
11 | import kotlin.coroutines.Continuation;
12 | import org.jetbrains.annotations.NotNull;
13 | import org.jetbrains.annotations.Nullable;
14 |
15 | import javax.swing.*;
16 |
17 | public class ProjectStartupActivity implements ProjectActivity, DumbAware {
18 | private static final Logger LOG = Logger.getInstance(ProjectStartupActivity.class);
19 |
20 | @Nullable
21 | @Override
22 | public Object execute(@NotNull Project project, @NotNull Continuation super Unit> continuation) {
23 | FrameSwitcherApplicationService service = FrameSwitcherApplicationService.getInstance();
24 |
25 | long start = System.currentTimeMillis();
26 | JFrame frame = WindowManager.getInstance().getFrame(project);
27 |
28 | WindowFocusGainedAdapter focusGainedAdapter = new WindowFocusGainedAdapter(project, frame, service.getProjectFocusMonitor());
29 | frame.addWindowFocusListener(focusGainedAdapter);
30 | focusGainedAdapter.windowGainedFocus(null);
31 |
32 | if (service.getRemoteSender() != null) {
33 | service.getRemoteSender().asyncProjectOpened(project);
34 | }
35 | Disposer.register(project, new Disposable() {
36 | @Override
37 | public void dispose() {
38 | long start = System.currentTimeMillis();
39 | FrameSwitcherApplicationService service = FrameSwitcherApplicationService.getInstance();
40 | if (service == null) {
41 | return;
42 | }
43 | if (service.getRemoteSender() != null) {
44 | service.getRemoteSender().sendProjectClosed(project);
45 | }
46 |
47 | ProjectFocusMonitor projectFocusMonitor = service.getProjectFocusMonitor();
48 | projectFocusMonitor.projectClosed(project);
49 |
50 |
51 | JFrame frame = focusGainedAdapter.getFrame();
52 | frame.removeWindowFocusListener(focusGainedAdapter);
53 | LOG.debug("projectClosed done in ", System.currentTimeMillis() - start, "ms");
54 | }
55 | });
56 | LOG.debug("projectOpened done in ", System.currentTimeMillis() - start, "ms");
57 | return null;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/main/java/krasa/frameswitcher/ReopenProjectsAction.java:
--------------------------------------------------------------------------------
1 | package krasa.frameswitcher;
2 |
3 | import com.intellij.openapi.actionSystem.ActionManager;
4 | import com.intellij.openapi.actionSystem.AnAction;
5 | import com.intellij.openapi.actionSystem.AnActionEvent;
6 | import com.intellij.openapi.actionSystem.DataContext;
7 | import com.intellij.openapi.application.ApplicationManager;
8 | import com.intellij.openapi.application.ModalityState;
9 | import com.intellij.openapi.project.DumbAwareAction;
10 | import com.intellij.openapi.ui.DialogBuilder;
11 | import com.intellij.openapi.ui.DialogWrapper;
12 |
13 | import java.util.List;
14 |
15 | /**
16 | * @author Vojtech Krasa
17 | */
18 | public class ReopenProjectsAction extends DumbAwareAction {
19 | @Override
20 | public void actionPerformed(AnActionEvent anActionEvent) {
21 | final ReopenProjectsForm form = new ReopenProjectsForm(getEventProject(anActionEvent));
22 |
23 | DialogBuilder builder = new DialogBuilder(getEventProject(anActionEvent));
24 | builder.setCenterPanel(form.getRoot());
25 | builder.setDimensionServiceKey("FrameSwitcherReopenProjects");
26 | builder.setTitle("Reopen Projects");
27 | builder.removeAllActions();
28 | builder.addOkAction();
29 | builder.addCancelAction();
30 |
31 | boolean isOk = builder.show() == DialogWrapper.OK_EXIT_CODE;
32 | if (isOk) {
33 | List checkProjects = form.getCheckProjects();
34 | for (AnAction checkProject : checkProjects) {
35 | Runnable runnable = () -> {
36 | AnActionEvent frameSwitcherPlugin = new AnActionEvent(anActionEvent.getInputEvent(), DataContext.EMPTY_CONTEXT, "FrameSwitcherPlugin", getTemplatePresentation(), ActionManager.getInstance(), anActionEvent.getModifiers());
37 | checkProject.actionPerformed(frameSwitcherPlugin);
38 | };
39 | ApplicationManager.getApplication().invokeLater(runnable, ModalityState.nonModal());
40 | }
41 | }
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/krasa/frameswitcher/ReopenProjectsForm.form:
--------------------------------------------------------------------------------
1 |
2 |
29 |
--------------------------------------------------------------------------------
/src/main/java/krasa/frameswitcher/ReopenProjectsForm.java:
--------------------------------------------------------------------------------
1 | package krasa.frameswitcher;
2 |
3 | import com.intellij.ide.ReopenProjectAction;
4 | import com.intellij.openapi.actionSystem.AnAction;
5 | import com.intellij.openapi.actionSystem.DefaultActionGroup;
6 | import com.intellij.openapi.actionSystem.Separator;
7 | import com.intellij.openapi.project.Project;
8 | import com.intellij.ui.CheckBoxList;
9 |
10 | import javax.swing.*;
11 | import java.util.ArrayList;
12 | import java.util.List;
13 |
14 | /**
15 | * @author Vojtech Krasa
16 | */
17 | public class ReopenProjectsForm {
18 | private JPanel root;
19 | private CheckBoxList list;
20 |
21 | public ReopenProjectsForm(Project project) {
22 | DefaultActionGroup group = new DefaultActionGroup();
23 | new FrameSwitchAction().fillReopen(group);
24 | for (AnAction child : group.getChildActionsOrStubs()) {
25 | if (child instanceof Separator) {
26 | continue;
27 | }
28 | String text;
29 | if (child instanceof ReopenProjectAction) {
30 | text = ((ReopenProjectAction) child).getProjectDisplayName();
31 | } else {
32 | text = child.getTemplatePresentation().getText();
33 | }
34 |
35 | list.addItem(child, text, false);
36 | }
37 |
38 | list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
39 | }
40 |
41 | public JPanel getRoot() {
42 | return root;
43 | }
44 |
45 | public List getCheckProjects() {
46 | DefaultListModel model = (DefaultListModel) list.getModel();
47 | final ArrayList selected = new ArrayList<>();
48 | Object[] objects = model.toArray();
49 | for (int i = 0; i < objects.length; i++) {
50 | Object object = objects[i];
51 | final JCheckBox cb = (JCheckBox) object;
52 | if (cb.isSelected()) {
53 | //IJ 12 compatibility
54 | selected.add((AnAction) list.getItemAt(i));
55 | }
56 |
57 | }
58 | return selected;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/java/krasa/frameswitcher/WindowFocusGainedAdapter.java:
--------------------------------------------------------------------------------
1 | package krasa.frameswitcher;
2 |
3 | import com.intellij.openapi.project.Project;
4 | import org.jetbrains.annotations.NotNull;
5 |
6 | import javax.swing.*;
7 | import java.awt.event.WindowAdapter;
8 | import java.awt.event.WindowEvent;
9 |
10 | /**
11 | * @author Vojtech Krasa
12 | */
13 | public class WindowFocusGainedAdapter extends WindowAdapter {
14 |
15 | private final Project project;
16 | private final JFrame frame;
17 | private final ProjectFocusMonitor projectFocusMonitor;
18 |
19 | public WindowFocusGainedAdapter(@NotNull Project project, @NotNull JFrame frame, @NotNull ProjectFocusMonitor projectFocusMonitor) {
20 | this.project = project;
21 | this.frame = frame;
22 | this.projectFocusMonitor = projectFocusMonitor;
23 | }
24 |
25 | @Override
26 | public void windowGainedFocus(WindowEvent e) {
27 | if (!project.isDisposed()) {
28 | projectFocusMonitor.focusGained(project);
29 | }
30 | }
31 |
32 | public
33 | @NotNull
34 | JFrame getFrame() {
35 | return frame;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/krasa/frameswitcher/networking/DiagnosticAction.java:
--------------------------------------------------------------------------------
1 | package krasa.frameswitcher.networking;
2 |
3 | import com.intellij.notification.*;
4 | import com.intellij.openapi.actionSystem.AnAction;
5 | import com.intellij.openapi.actionSystem.AnActionEvent;
6 | import krasa.frameswitcher.FrameSwitcherApplicationService;
7 |
8 | /**
9 | * @author Vojtech Krasa
10 | */
11 | public class DiagnosticAction extends AnAction {
12 | @Override
13 | public void actionPerformed(AnActionEvent e) {
14 | final RemoteSender remoteSender1 = FrameSwitcherApplicationService.getInstance().getRemoteSender();
15 | if (remoteSender1 instanceof RemoteSenderImpl) {
16 | final RemoteSenderImpl remoteSender = (RemoteSenderImpl) remoteSender1;
17 | String content = remoteSender.getChannel().getProperties().replace(";", "\n");
18 |
19 | NotificationGroup group = NotificationGroupManager.getInstance().getNotificationGroup("Frame Switcher plugin");
20 | Notification myNotification = group.createNotification("FrameSwitcher", content, NotificationType.INFORMATION);
21 | Notifications.Bus.notify(myNotification, getEventProject(e));
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/krasa/frameswitcher/networking/DummyRemoteSender.java:
--------------------------------------------------------------------------------
1 | package krasa.frameswitcher.networking;
2 |
3 | import com.intellij.openapi.project.Project;
4 | import krasa.frameswitcher.networking.dto.RemoteProject;
5 | import org.jgroups.Message;
6 |
7 | import java.util.UUID;
8 |
9 | /**
10 | * @author Vojtech Krasa
11 | */
12 | public class DummyRemoteSender implements RemoteSender {
13 | @Override
14 | public void sendInstanceStarted() {
15 |
16 | }
17 |
18 | @Override
19 | public void sendProjectsState() {
20 |
21 | }
22 |
23 | @Override
24 | public void dispose() {
25 |
26 | }
27 |
28 | @Override
29 | public void asyncPing() {
30 |
31 | }
32 |
33 | @Override
34 | public void asyncProjectOpened(Project project) {
35 |
36 | }
37 |
38 | @Override
39 | public void sendProjectClosed(Project project) {
40 |
41 | }
42 |
43 | @Override
44 | public void openProject(UUID target, RemoteProject remoteProject) {
45 |
46 | }
47 |
48 | @Override
49 | public void sendPingResponse(Message msg) {
50 |
51 | }
52 |
53 | @Override
54 | public void asyncSendRefresh() {
55 |
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/java/krasa/frameswitcher/networking/Receiver.java:
--------------------------------------------------------------------------------
1 | package krasa.frameswitcher.networking;
2 |
3 | import com.intellij.ide.DataManager;
4 | import com.intellij.ide.ReopenProjectAction;
5 | import com.intellij.openapi.actionSystem.ActionManager;
6 | import com.intellij.openapi.actionSystem.AnActionEvent;
7 | import com.intellij.openapi.actionSystem.Presentation;
8 | import com.intellij.openapi.application.ApplicationManager;
9 | import com.intellij.openapi.application.ModalityState;
10 | import com.intellij.openapi.diagnostic.Logger;
11 | import com.intellij.openapi.project.Project;
12 | import com.intellij.openapi.wm.IdeFocusManager;
13 | import com.intellij.openapi.wm.IdeFrame;
14 | import krasa.frameswitcher.FocusUtils;
15 | import krasa.frameswitcher.FrameSwitchAction;
16 | import krasa.frameswitcher.FrameSwitcherApplicationService;
17 | import krasa.frameswitcher.networking.dto.*;
18 | import org.jgroups.Message;
19 | import org.jgroups.View;
20 |
21 | import javax.swing.*;
22 | import java.util.List;
23 | import java.util.UUID;
24 |
25 | public class Receiver implements org.jgroups.Receiver {
26 |
27 | private static final Logger LOG = Logger.getInstance(Receiver.class);
28 |
29 | private UUID uuid;
30 | private FrameSwitcherApplicationService service;
31 |
32 | public Receiver(UUID uuid, FrameSwitcherApplicationService service) {
33 | this.uuid = uuid;
34 | this.service = service;
35 | }
36 |
37 | @Override
38 | public void receive(Message msg) {
39 | Object object = msg.getObject();
40 | if (object instanceof GeneralMessage) {
41 | if (((GeneralMessage) object).getUuid().equals(uuid)) {
42 | return;
43 | }
44 | }
45 | if (LOG.isDebugEnabled()) {
46 | LOG.debug("Received: " + object);
47 | }
48 |
49 | asyncRespond(msg, object);
50 | }
51 |
52 | @Override
53 | public void viewAccepted(View new_view) {
54 | org.jgroups.Receiver.super.viewAccepted(new_view);
55 | }
56 |
57 | private void asyncRespond(Message msg, Object object) {
58 | ApplicationManager.getApplication().executeOnPooledThread(() -> {
59 | if (object instanceof InstanceStarted) {
60 | service.getRemoteInstancesState().updateRemoteState((InstanceStarted) object);
61 | service.getRemoteSender().sendProjectsState();
62 | } else if (object instanceof ProjectsState) {
63 | service.getRemoteInstancesState().updateRemoteState((ProjectsState) object);
64 | } else if (object instanceof Ping) {
65 | service.getRemoteSender().sendPingResponse(msg);
66 | } else if (object instanceof PingResponse) {
67 | service.getRemoteInstancesState().processPingResponse((PingResponse) object);
68 | } else if (object instanceof ProjectOpened) {
69 | service.getRemoteInstancesState().projectOpened((ProjectOpened) object);
70 | } else if (object instanceof ProjectClosed) {
71 | service.getRemoteInstancesState().projectClosed((ProjectClosed) object);
72 | } else if (object instanceof InstanceClosed) {
73 | service.getRemoteInstancesState().instanceClosed((InstanceClosed) object);
74 | } else if (object instanceof OpenProject) {
75 | openProject((OpenProject) object);
76 | } else if (object instanceof Refresh) {
77 | service.getRemoteSender().sendProjectsState();
78 | }
79 | });
80 | }
81 |
82 | private void openProject(OpenProject openProject) {
83 | if (openProject.getTargetUUID().equals(uuid)) {
84 | RemoteProject openProjectProject = openProject.getProject();
85 |
86 | if (requestFocus(openProjectProject)) return;
87 |
88 | reopenProject(openProjectProject);
89 | }
90 | }
91 |
92 | private boolean requestFocus(RemoteProject openProjectProject) {
93 | List ideFrames = new FrameSwitchAction().getIdeFrames();
94 | for (final IdeFrame ideFrame : ideFrames) {
95 | final Project project = ideFrame.getProject();
96 |
97 | if (project != null) {
98 | if (openProjectProject.getProjectPath().equals(project.getBasePath())) {
99 | SwingUtilities.invokeLater(() -> {
100 | IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> {
101 | FocusUtils.requestFocus(project, false, true);
102 | });
103 | });
104 | return true;
105 | }
106 | }
107 | }
108 | return false;
109 | }
110 |
111 | private void reopenProject(RemoteProject openProjectProject) {
112 | ApplicationManager.getApplication().invokeLater(() -> {
113 | DataManager.getInstance().getDataContextFromFocusAsync().onSuccess(dataContext -> {
114 | ReopenProjectAction reopenProjectAction = new ReopenProjectAction(openProjectProject.getProjectPath(), openProjectProject.getName(), openProjectProject.getName());
115 | reopenProjectAction.actionPerformed(new AnActionEvent(null, dataContext, "FrameSwitcherRemote", new Presentation(), ActionManager.getInstance(), 0));
116 |
117 | SwingUtilities.invokeLater(() -> {
118 | IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> {
119 | requestFocus(openProjectProject);
120 | });
121 | });
122 | });
123 | }, ModalityState.nonModal());
124 | }
125 |
126 | }
127 |
--------------------------------------------------------------------------------
/src/main/java/krasa/frameswitcher/networking/RemoteIdeInstance.java:
--------------------------------------------------------------------------------
1 | package krasa.frameswitcher.networking;
2 |
3 | import krasa.frameswitcher.networking.dto.RemoteProject;
4 |
5 | import java.util.HashSet;
6 | import java.util.Set;
7 | import java.util.UUID;
8 |
9 | public class RemoteIdeInstance {
10 | public Set remoteRecentProjects = new HashSet<>();
11 | public Set remoteProjects = new HashSet<>();
12 | public String ideName = "";
13 | public long lastResponse = System.currentTimeMillis();
14 | public UUID uuid;
15 |
16 | public RemoteIdeInstance(UUID uuid) {
17 | this.uuid = uuid;
18 | }
19 |
20 | @Override
21 | public boolean equals(Object o) {
22 | if (this == o) return true;
23 | if (o == null || getClass() != o.getClass()) return false;
24 |
25 | RemoteIdeInstance that = (RemoteIdeInstance) o;
26 |
27 | return uuid != null ? uuid.equals(that.uuid) : that.uuid == null;
28 | }
29 |
30 | @Override
31 | public int hashCode() {
32 | return uuid != null ? uuid.hashCode() : 0;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/krasa/frameswitcher/networking/RemoteInstancesState.java:
--------------------------------------------------------------------------------
1 | package krasa.frameswitcher.networking;
2 |
3 | import com.intellij.openapi.diagnostic.Logger;
4 | import krasa.frameswitcher.FrameSwitcherApplicationService;
5 | import krasa.frameswitcher.networking.dto.*;
6 |
7 | import java.util.*;
8 |
9 | public class RemoteInstancesState {
10 | private static final Logger LOG = Logger.getInstance(RemoteInstancesState.class);
11 |
12 | public static final int TIMEOUT_millis = 10 * 1000;
13 |
14 | volatile long lastPingSend = 0;
15 | public Map remoteIdeInstances = new HashMap<>();
16 |
17 | public synchronized List getRemoteIdeInstances() {
18 | List list = new ArrayList<>(this.remoteIdeInstances.values());
19 | list.sort(Comparator.comparing(o -> o.ideName));
20 | return list;
21 | }
22 |
23 | public synchronized void sweepRemoteInstance() {
24 | for (Iterator> iterator = remoteIdeInstances.entrySet().iterator(); iterator.hasNext(); ) {
25 | Map.Entry entry = iterator.next();
26 | RemoteIdeInstance value = entry.getValue();
27 | if (lastPingSend - value.lastResponse > TIMEOUT_millis) {
28 | LOG.debug("Removing " + value);
29 | iterator.remove();
30 | }
31 | }
32 | }
33 |
34 | public synchronized void processPingResponse(PingResponse object) {
35 | RemoteIdeInstance remoteIdeInstance = remoteIdeInstances.get(object.getUuid());
36 | if (remoteIdeInstance != null) {
37 | remoteIdeInstance.lastResponse = System.currentTimeMillis();
38 | } else {
39 | LOG.warn("DESYNC " + object);
40 | FrameSwitcherApplicationService.getInstance().getRemoteSender().asyncSendRefresh();
41 | }
42 | }
43 |
44 | public synchronized void updateRemoteState(ProjectsState instanceStarted) {
45 | RemoteIdeInstance remoteIdeInstance = getRemoteIdeInstance(instanceStarted);
46 |
47 | remoteIdeInstance.remoteRecentProjects.clear();
48 | remoteIdeInstance.remoteProjects.clear();
49 |
50 | remoteIdeInstance.remoteRecentProjects.addAll(instanceStarted.getRecentRemoteProjects());
51 | remoteIdeInstance.remoteProjects.addAll(instanceStarted.getRemoteProjects());
52 | remoteIdeInstance.ideName = instanceStarted.getName();
53 | remoteIdeInstance.lastResponse = System.currentTimeMillis();
54 | }
55 |
56 | public synchronized void projectClosed(ProjectClosed projectClosed) {
57 | RemoteIdeInstance remoteIdeInstance = getRemoteIdeInstance(projectClosed);
58 | remoteIdeInstance.remoteProjects.remove(projectClosed.getRemoteProject());
59 | remoteIdeInstance.remoteRecentProjects.add(projectClosed.getRemoteProject());
60 | remoteIdeInstance.lastResponse = System.currentTimeMillis();
61 | }
62 |
63 | public synchronized void projectOpened(ProjectOpened projectOpened) {
64 | RemoteIdeInstance remoteIdeInstance = getRemoteIdeInstance(projectOpened);
65 | remoteIdeInstance.remoteProjects.add(projectOpened.getRemoteProject());
66 | remoteIdeInstance.remoteRecentProjects.remove(projectOpened.getRemoteProject());
67 | remoteIdeInstance.lastResponse = System.currentTimeMillis();
68 | }
69 |
70 | private RemoteIdeInstance getRemoteIdeInstance(GeneralMessage message) {
71 | RemoteIdeInstance remoteIdeInstance = remoteIdeInstances.get(message.getUuid());
72 | if (remoteIdeInstance == null) {
73 | remoteIdeInstance = new RemoteIdeInstance(message.getUuid());
74 | remoteIdeInstances.put(remoteIdeInstance.uuid, remoteIdeInstance);
75 | }
76 | return remoteIdeInstance;
77 | }
78 |
79 | public synchronized void instanceClosed(InstanceClosed object) {
80 | remoteIdeInstances.remove(object.getUuid());
81 | }
82 |
83 | public synchronized void clear() {
84 | remoteIdeInstances.clear();
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/main/java/krasa/frameswitcher/networking/RemoteSender.java:
--------------------------------------------------------------------------------
1 | package krasa.frameswitcher.networking;
2 |
3 | import com.intellij.openapi.project.Project;
4 | import krasa.frameswitcher.networking.dto.RemoteProject;
5 | import org.jgroups.Message;
6 |
7 | import java.util.UUID;
8 |
9 | /**
10 | * @author Vojtech Krasa
11 | */
12 | public interface RemoteSender {
13 | void sendInstanceStarted();
14 |
15 | void sendProjectsState();
16 |
17 | void dispose();
18 |
19 | void asyncPing();
20 |
21 | void asyncProjectOpened(Project project);
22 |
23 | void sendProjectClosed(Project project);
24 |
25 | void openProject(UUID target, RemoteProject remoteProject);
26 |
27 | void sendPingResponse(Message msg);
28 |
29 | void asyncSendRefresh();
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/krasa/frameswitcher/networking/RemoteSenderImpl.java:
--------------------------------------------------------------------------------
1 | package krasa.frameswitcher.networking;
2 |
3 | import com.intellij.ide.RecentProjectsManagerBase;
4 | import com.intellij.openapi.actionSystem.AnAction;
5 | import com.intellij.openapi.application.ApplicationInfo;
6 | import com.intellij.openapi.application.ApplicationManager;
7 | import com.intellij.openapi.diagnostic.Logger;
8 | import com.intellij.openapi.project.Project;
9 | import krasa.frameswitcher.FrameSwitchAction;
10 | import krasa.frameswitcher.FrameSwitcherApplicationService;
11 | import krasa.frameswitcher.networking.dto.*;
12 | import org.jetbrains.annotations.NotNull;
13 | import org.jgroups.ChannelListener;
14 | import org.jgroups.JChannel;
15 | import org.jgroups.Message;
16 | import org.jgroups.ObjectMessage;
17 | import org.jgroups.conf.ConfiguratorFactory;
18 | import org.jgroups.conf.ProtocolConfiguration;
19 | import org.jgroups.conf.ProtocolStackConfigurator;
20 |
21 | import java.util.List;
22 | import java.util.Map;
23 | import java.util.UUID;
24 |
25 | /**
26 | * @author Vojtech Krasa
27 | */
28 | public class RemoteSenderImpl implements RemoteSender {
29 | public static final String FRAME_SWITCHER = "FrameSwitcher";
30 | private final Logger LOG = Logger.getInstance(RemoteSenderImpl.class);
31 |
32 | private UUID uuid;
33 | private JChannel channel;
34 | private StandbyDetector standbyDetector;
35 |
36 | public RemoteSenderImpl(FrameSwitcherApplicationService service, UUID uuid, final Receiver r, int port) throws Exception {
37 | this.uuid = uuid;
38 | ProtocolStackConfigurator stackConfigurator = ConfiguratorFactory.getStackConfigurator("frameswitcher-fast-local.xml");
39 | List protocolStack = stackConfigurator.getProtocolStack();
40 | for (ProtocolConfiguration protocolConfiguration : protocolStack) {
41 | if (protocolConfiguration.getProtocolName().equals("UDP")) {
42 | Map properties = protocolConfiguration.getProperties();
43 | properties.put("mcast_port", String.valueOf(port));
44 | break;
45 | }
46 | }
47 | channel = new JChannel(stackConfigurator);
48 | // channel = new JChannel("tcp.xml");
49 | channel.addChannelListener(new ChannelListener() {
50 | @Override
51 | public void channelConnected(JChannel channel) {
52 | LOG.debug("channelConnected ", channel);
53 | }
54 |
55 | @Override
56 | public void channelDisconnected(JChannel channel) {
57 | LOG.debug("channelDisconnected ", channel);
58 | }
59 |
60 | @Override
61 | public void channelClosed(JChannel channel) {
62 | LOG.debug("channelClosed ", channel);
63 | }
64 | });
65 | channel.setDiscardOwnMessages(true);
66 | channel.setReceiver(r);
67 | channel.connect(FRAME_SWITCHER);
68 | sendInstanceStarted();
69 | standbyDetector = new StandbyDetector(30 * 1000) {
70 | @Override
71 | public void standbyDetected() {
72 | channel.disconnect();
73 | service.getRemoteInstancesState().clear();
74 | }
75 | };
76 | }
77 |
78 | @Override
79 | public void sendInstanceStarted() {
80 | LOG.debug("sending InstanceStarted");
81 | send(new ObjectMessage(null, new InstanceStarted(uuid, getRecentProjectsActions(), new FrameSwitchAction().getIdeFrames(), getName())));
82 | }
83 |
84 | @Override
85 | public void sendProjectsState() {
86 | send(new ObjectMessage(null, new ProjectsState(uuid, getRecentProjectsActions(), new FrameSwitchAction().getIdeFrames(), getName())));
87 | }
88 |
89 | @NotNull
90 | private AnAction[] getRecentProjectsActions() {
91 | return RecentProjectsManagerBase.getInstanceEx().getRecentProjectsActions(false);
92 | }
93 |
94 | private String getName() {
95 | ApplicationInfo instance = ApplicationInfo.getInstance();
96 | return instance.getFullApplicationName() + " - " + instance.getBuild();
97 | }
98 |
99 | @Override
100 | public void dispose() {
101 | LOG.debug("sending InstanceClosed");
102 | send(new ObjectMessage(null, new InstanceClosed(uuid)));
103 | channel.close();
104 | standbyDetector.stop();
105 | }
106 |
107 | @Override
108 | public void asyncPing() {
109 | ApplicationManager.getApplication().executeOnPooledThread(() -> {
110 | LOG.debug("sending Ping");
111 | standbyDetector.check();
112 | FrameSwitcherApplicationService.getInstance().getRemoteInstancesState().lastPingSend = System.currentTimeMillis();
113 | send(new ObjectMessage(null, new Ping(uuid)));
114 | });
115 | }
116 |
117 | @Override
118 | public void asyncProjectOpened(Project project) {
119 | ApplicationManager.getApplication().executeOnPooledThread(() -> {
120 | LOG.debug("sending ProjectOpened");
121 | send(new ObjectMessage(null, new ProjectOpened(project.getName(), project.getBasePath(), uuid)));
122 | });
123 | }
124 |
125 | @Override
126 | public void sendProjectClosed(Project project) {
127 | LOG.debug("sending ProjectClosed");
128 | send(new ObjectMessage(null, new ProjectClosed(project.getName(), project.getBasePath(), uuid)));
129 | }
130 |
131 | @Override
132 | public void openProject(UUID target, RemoteProject remoteProject) {
133 | final OpenProject openProject = new OpenProject(uuid, target, remoteProject);
134 | LOG.debug("sending openProject");
135 | send(new ObjectMessage(null, openProject));
136 | }
137 |
138 | @Override
139 | public void sendPingResponse(Message msg) {
140 | LOG.debug("sending PingResponse");
141 | send(new ObjectMessage(msg.getSrc(), new PingResponse(uuid)));
142 | }
143 |
144 | @Override
145 | public void asyncSendRefresh() {
146 | ApplicationManager.getApplication().executeOnPooledThread(() -> {
147 | LOG.debug("sending Refresh");
148 | standbyDetector.check();
149 | sendInstanceStarted();
150 | });
151 | }
152 |
153 | private void send(Message msg) {
154 | try {
155 | if (!channel.isConnected()) {
156 | LOG.debug("reconnecting");
157 | channel.connect(FRAME_SWITCHER);
158 | sendInstanceStarted();
159 | }
160 | channel.send(msg);
161 | } catch (Throwable e) {
162 | LOG.warn(e);
163 | }
164 | }
165 |
166 | public JChannel getChannel() {
167 | return channel;
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/src/main/java/krasa/frameswitcher/networking/StandbyDetector.java:
--------------------------------------------------------------------------------
1 | package krasa.frameswitcher.networking;
2 |
3 | import com.intellij.openapi.diagnostic.Logger;
4 |
5 | public abstract class StandbyDetector {
6 | private static final Logger LOG = Logger.getInstance(StandbyDetector.class);
7 |
8 | private final long timeoutMillis;
9 | private final long sleepMillis;
10 |
11 | private Thread thread;
12 | private boolean enabled = true;
13 | private long lastTimeStamp= System.currentTimeMillis();
14 |
15 | public StandbyDetector(final long timeoutMillis) {
16 | this.timeoutMillis = timeoutMillis;
17 | this.sleepMillis = timeoutMillis/2;
18 | start();
19 | }
20 |
21 | public final long getTimeoutMs() {
22 | return this.timeoutMillis;
23 | }
24 |
25 | private synchronized void start() {
26 | if (thread == null || !thread.isAlive()) {
27 | thread = new Thread("FrameSwitcher-StandByDetector") {
28 |
29 | @Override
30 | public void run() {
31 | while (enabled) {
32 | try {
33 | Thread.sleep(sleepMillis);
34 | } catch (final InterruptedException e) {
35 | break;
36 | }
37 | check();
38 | }
39 | }
40 | };
41 | thread.setDaemon(true);
42 | thread.start();
43 | }
44 | }
45 |
46 | public synchronized void check() {
47 | long now = System.currentTimeMillis();
48 | final long timeStampGap = now - lastTimeStamp;
49 | if (timeStampGap > this.timeoutMillis) {
50 | try {
51 | LOG.info("Standby detected");
52 | standbyDetected();
53 | } catch (final Throwable e) {
54 | LOG.error(e);
55 | }
56 | }
57 | lastTimeStamp = now;
58 | }
59 |
60 | public abstract void standbyDetected() ;
61 |
62 | public final synchronized boolean isRunning() {
63 | return thread != null && thread.isAlive();
64 | }
65 |
66 | public final synchronized void stop() {
67 | enabled = false;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/main/java/krasa/frameswitcher/networking/dto/GeneralMessage.java:
--------------------------------------------------------------------------------
1 | package krasa.frameswitcher.networking.dto;
2 |
3 | import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
4 | import org.apache.commons.lang3.builder.ToStringStyle;
5 |
6 | import java.io.Serializable;
7 | import java.util.UUID;
8 |
9 | /**
10 | * @author Vojtech Krasa
11 | */
12 | public abstract class GeneralMessage implements Serializable {
13 | protected UUID uuid;
14 | protected int version = 1;
15 |
16 | public GeneralMessage(UUID uuid) {
17 | this.uuid = uuid;
18 | }
19 |
20 | public int getVersion() {
21 | return version;
22 | }
23 |
24 | public UUID getUuid() {
25 | return uuid;
26 | }
27 |
28 | @Override
29 | public String toString() {
30 | return ReflectionToStringBuilder.toString(this, ToStringStyle.SHORT_PREFIX_STYLE);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/krasa/frameswitcher/networking/dto/InstanceClosed.java:
--------------------------------------------------------------------------------
1 | package krasa.frameswitcher.networking.dto;
2 |
3 | import java.util.UUID;
4 |
5 | public class InstanceClosed extends GeneralMessage {
6 |
7 | public InstanceClosed(UUID uuid) {
8 | super(uuid);
9 | }
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/krasa/frameswitcher/networking/dto/InstanceStarted.java:
--------------------------------------------------------------------------------
1 | package krasa.frameswitcher.networking.dto;
2 |
3 | import com.intellij.openapi.actionSystem.AnAction;
4 | import com.intellij.openapi.wm.IdeFrame;
5 |
6 | import java.util.List;
7 | import java.util.UUID;
8 |
9 | public class InstanceStarted extends ProjectsState {
10 | public InstanceStarted(UUID uuid, AnAction[] recentProjectsActions, List ideFrames, String name) {
11 | super(uuid, recentProjectsActions, ideFrames, name);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/krasa/frameswitcher/networking/dto/OpenProject.java:
--------------------------------------------------------------------------------
1 | package krasa.frameswitcher.networking.dto;
2 |
3 | import java.util.UUID;
4 |
5 | public class OpenProject extends GeneralMessage {
6 | private final RemoteProject basePath;
7 | protected UUID targetUUID;
8 |
9 | public RemoteProject getProject() {
10 | return basePath;
11 | }
12 |
13 | public OpenProject(UUID uuid, UUID targetUUID, RemoteProject basePath) {
14 | super(uuid);
15 | this.targetUUID = targetUUID;
16 | this.basePath = basePath;
17 | }
18 |
19 | public UUID getTargetUUID() {
20 | return targetUUID;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/krasa/frameswitcher/networking/dto/Ping.java:
--------------------------------------------------------------------------------
1 | package krasa.frameswitcher.networking.dto;
2 |
3 | import java.util.UUID;
4 |
5 | public class Ping extends GeneralMessage {
6 |
7 | public Ping(UUID uuid) {
8 | super(uuid);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/krasa/frameswitcher/networking/dto/PingResponse.java:
--------------------------------------------------------------------------------
1 | package krasa.frameswitcher.networking.dto;
2 |
3 | import java.util.UUID;
4 |
5 | public class PingResponse extends GeneralMessage {
6 | public PingResponse(UUID uuid) {
7 | super(uuid);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/krasa/frameswitcher/networking/dto/ProjectClosed.java:
--------------------------------------------------------------------------------
1 | package krasa.frameswitcher.networking.dto;
2 |
3 | import java.util.UUID;
4 |
5 | public class ProjectClosed extends GeneralMessage {
6 | private RemoteProject remoteProject;
7 |
8 | public ProjectClosed(String name, String basePath, UUID uuid) {
9 | super(uuid);
10 |
11 | remoteProject = new RemoteProject(name, basePath);
12 | }
13 |
14 | public RemoteProject getRemoteProject() {
15 | return remoteProject;
16 | }
17 |
18 | @Override
19 | public boolean equals(Object o) {
20 | if (this == o) {
21 | return true;
22 | }
23 | if (o == null || getClass() != o.getClass()) {
24 | return false;
25 | }
26 |
27 | ProjectClosed that = (ProjectClosed) o;
28 |
29 | if (remoteProject != null ? !remoteProject.equals(that.remoteProject) : that.remoteProject != null) {
30 | return false;
31 | }
32 | if (uuid != null ? !uuid.equals(that.uuid) : that.uuid != null) {
33 | return false;
34 | }
35 |
36 | return true;
37 | }
38 |
39 | @Override
40 | public int hashCode() {
41 | int result = uuid != null ? uuid.hashCode() : 0;
42 | result = 31 * result + (remoteProject != null ? remoteProject.hashCode() : 0);
43 | return result;
44 | }
45 |
46 | public void setRemoteProject(RemoteProject remoteProject) {
47 | this.remoteProject = remoteProject;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/krasa/frameswitcher/networking/dto/ProjectOpened.java:
--------------------------------------------------------------------------------
1 | package krasa.frameswitcher.networking.dto;
2 |
3 | import java.util.UUID;
4 |
5 | public class ProjectOpened extends GeneralMessage {
6 | private RemoteProject remoteProject;
7 |
8 | public ProjectOpened(String name, String basePath, UUID uuid) {
9 | super(uuid);
10 | remoteProject = new RemoteProject(name, basePath);
11 | }
12 |
13 | public RemoteProject getRemoteProject() {
14 | return remoteProject;
15 | }
16 |
17 | @Override
18 | public boolean equals(Object o) {
19 | if (this == o) {
20 | return true;
21 | }
22 | if (o == null || getClass() != o.getClass()) {
23 | return false;
24 | }
25 |
26 | ProjectOpened that = (ProjectOpened) o;
27 |
28 | if (remoteProject != null ? !remoteProject.equals(that.remoteProject) : that.remoteProject != null) {
29 | return false;
30 | }
31 | if (uuid != null ? !uuid.equals(that.uuid) : that.uuid != null) {
32 | return false;
33 | }
34 |
35 | return true;
36 | }
37 |
38 | @Override
39 | public int hashCode() {
40 | int result = uuid != null ? uuid.hashCode() : 0;
41 | result = 31 * result + (remoteProject != null ? remoteProject.hashCode() : 0);
42 | return result;
43 | }
44 |
45 | public void setRemoteProject(RemoteProject remoteProject) {
46 | this.remoteProject = remoteProject;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/krasa/frameswitcher/networking/dto/ProjectsState.java:
--------------------------------------------------------------------------------
1 | package krasa.frameswitcher.networking.dto;
2 |
3 | import com.intellij.ide.ReopenProjectAction;
4 | import com.intellij.openapi.actionSystem.AnAction;
5 | import com.intellij.openapi.project.Project;
6 | import com.intellij.openapi.wm.IdeFrame;
7 |
8 | import java.util.ArrayList;
9 | import java.util.List;
10 | import java.util.UUID;
11 |
12 | public class ProjectsState extends GeneralMessage {
13 |
14 | private List recentRemoteProjects;
15 | private List remoteProjects;
16 | private String name;
17 |
18 | public ProjectsState(UUID uuid, AnAction[] recentProjectsActions, List ideFrames, String name) {
19 | super(uuid);
20 |
21 | remoteProjects = new ArrayList(ideFrames.size());
22 | for (IdeFrame ideFrame : ideFrames) {
23 | Project project = ideFrame.getProject();
24 | if (project != null) {
25 | remoteProjects.add(new RemoteProject(project));
26 | }
27 | }
28 |
29 |
30 | recentRemoteProjects = new ArrayList(Math.min(recentProjectsActions.length, 20));
31 | this.name = name;
32 | for (int i = 0; i < recentProjectsActions.length && i < 20; i++) {
33 | AnAction action = recentProjectsActions[i];
34 | ReopenProjectAction reopenProjectAction = (ReopenProjectAction) action;
35 | recentRemoteProjects.add(new RemoteProject(reopenProjectAction));
36 | }
37 |
38 | }
39 |
40 | public String getName() {
41 | return name;
42 | }
43 |
44 | public List getRemoteProjects() {
45 | return remoteProjects;
46 | }
47 |
48 | public List getRecentRemoteProjects() {
49 | return recentRemoteProjects;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/krasa/frameswitcher/networking/dto/Refresh.java:
--------------------------------------------------------------------------------
1 | package krasa.frameswitcher.networking.dto;
2 |
3 | import java.util.UUID;
4 |
5 | public class Refresh extends GeneralMessage {
6 |
7 | public Refresh(UUID uuid) {
8 | super(uuid);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/krasa/frameswitcher/networking/dto/RemoteProject.java:
--------------------------------------------------------------------------------
1 | package krasa.frameswitcher.networking.dto;
2 |
3 | import com.intellij.ide.ReopenProjectAction;
4 | import com.intellij.openapi.project.Project;
5 | import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
6 | import org.apache.commons.lang3.builder.ToStringStyle;
7 |
8 | import java.io.Serializable;
9 |
10 | public class RemoteProject implements Serializable {
11 |
12 | private final String name;
13 | private final String projectPath;
14 |
15 | public RemoteProject(String name, String projectPath) {
16 | this.name = name;
17 | this.projectPath = projectPath;
18 | }
19 |
20 | public RemoteProject(Project project) {
21 | this(project.getName(), project.getBasePath());
22 | }
23 |
24 | public RemoteProject(ReopenProjectAction project) {
25 | this(project.getProjectName(), project.getProjectPath());
26 | }
27 |
28 | public String getName() {
29 | return name;
30 | }
31 |
32 | public String getProjectPath() {
33 | return projectPath;
34 | }
35 |
36 | @Override
37 | public boolean equals(Object o) {
38 | if (this == o) {
39 | return true;
40 | }
41 | if (o == null || getClass() != o.getClass()) {
42 | return false;
43 | }
44 |
45 | RemoteProject that = (RemoteProject) o;
46 |
47 | if (projectPath != null ? !projectPath.equals(that.projectPath) : that.projectPath != null) {
48 | return false;
49 | }
50 | if (name != null ? !name.equals(that.name) : that.name != null) {
51 | return false;
52 | }
53 |
54 | return true;
55 | }
56 |
57 | @Override
58 | public int hashCode() {
59 | int result = name != null ? name.hashCode() : 0;
60 | result = 31 * result + (projectPath != null ? projectPath.hashCode() : 0);
61 | return result;
62 | }
63 |
64 | @Override
65 | public String toString() {
66 | return ReflectionToStringBuilder.toString(this, ToStringStyle.SHORT_PREFIX_STYLE);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/plugin.xml:
--------------------------------------------------------------------------------
1 |
2 | Frame Switcher
3 | FrameSwitcher
4 |
5 | Vojtech
6 | Krasa
7 |
8 |
11 |
12 | Switch or reopen a project - ALT + F2 (repeat for selecting a next item)
13 |
14 | - Shift+Enter/LeftMouse - reopen a project in the current window.
15 | - Ctrl+Enter/LeftMouse - reopen a project in a new window.
16 | - Delete - closes project or removes a recent project from the history.
17 |
18 |
19 |
20 | Close projects - CTRL + ALT + F2
21 | Reopen projects - CTRL + ALT + F3
22 |
23 |
24 |
Or set your own shortcut in Settings | Keymap.
25 |
26 |
See File | Settings | Other Settings | FrameSwitcher for options like using mnemonics instead of speed search, max recent projects size and other...
27 |
You can also include the whole workspace in the settings, so that all projects gets added to the list.
28 |
29 |
30 | (Big thanks to Eugene Mustaphin for contributions.)
31 | ]]>
32 |
33 |
34 |
36 |
39 | com.intellij.modules.lang
40 |
41 |
42 |
43 |
45 |
46 |
47 |
48 |
50 |
51 |
52 |
53 |
55 |
56 |
57 |
58 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
71 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/src/main/resources/frameswitcher-fast-local.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
14 |
19 |
20 |
21 |
23 |
24 |
25 |
26 |
27 |
28 |
30 |
31 |
33 |
35 |
36 |
37 |
--------------------------------------------------------------------------------