├── .gitignore
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── WindowLayoutManager.iml
├── appveyor.yml
├── build.gradle
├── config
└── checkstyle
│ └── checkstyle.xml
├── doc
├── dev-setup.md
└── images
│ ├── add-gradle-task.png
│ ├── configure-gradle-task.png
│ └── edit-configuration.png
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
└── main
├── java
└── com
│ └── layoutmanager
│ ├── cleanup
│ └── EmptyLayoutRemoverService.java
│ ├── layout
│ ├── LayoutAction.java
│ ├── delete
│ │ └── DeleteLayoutAction.java
│ ├── restore
│ │ └── RestoreLayoutAction.java
│ └── store
│ │ ├── LayoutCreator.java
│ │ ├── create
│ │ └── NewLayoutAction.java
│ │ ├── overwrite
│ │ └── OverwriteLayoutAction.java
│ │ ├── smartdock
│ │ ├── SmartDocker.java
│ │ ├── SmartDockerFactory.java
│ │ ├── ToolWindowDocking.java
│ │ └── dockers
│ │ │ ├── ScreenBorderDocker.java
│ │ │ ├── ToolWindowDocker.java
│ │ │ └── ToolWindowToScreenShrinker.java
│ │ └── validation
│ │ └── LayoutValidationHelper.java
│ ├── localization
│ └── MessagesHelper.java
│ ├── migration
│ └── LayoutMigratorService.java
│ ├── persistence
│ ├── Layout.java
│ ├── LayoutConfig.java
│ ├── LayoutSettings.java
│ └── ToolWindowInfo.java
│ ├── startup
│ └── PluginBootstrapper.java
│ └── ui
│ ├── action
│ ├── ActionNameGenerator.java
│ └── ActionRegistry.java
│ ├── dialogs
│ ├── LayoutNameDialog.java
│ └── LayoutNameValidator.java
│ ├── helpers
│ ├── BalloonNotificationHelper.java
│ ├── ComponentNotificationHelper.java
│ ├── ScreenSizeHelper.java
│ └── ToolWindowHelper.java
│ ├── icons
│ └── Icons.java
│ ├── menu
│ └── WindowMenuService.java
│ └── settings
│ ├── EditLayout.java
│ ├── ImportExportConstants.java
│ ├── LayoutDuplicator.java
│ ├── LayoutManagerSettingsPanel.form
│ ├── LayoutManagerSettingsPanel.java
│ ├── LayoutSerializer.java
│ ├── SettingsPage.java
│ ├── WindowMenuChangesApplier.java
│ ├── exporting
│ ├── ExportDialog.form
│ └── ExportDialog.java
│ └── importing
│ ├── ImportDialog.form
│ └── ImportDialog.java
└── resources
├── META-INF
└── plugin.xml
└── com
└── layoutmanager
└── ui
├── icons
├── DeleteLayout.svg
├── NewLayout.svg
├── OverwriteLayout.svg
└── RestoreLayout.svg
└── messages.properties
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/**/*
2 | /out
3 | /build
4 | *.jar
5 | !gradle/wrapper/gradle-wrapper.jar
6 | .gradle/**/*
7 | .DS_Store
8 | src/.idea/
9 | src/WindowLayoutManager.iml
10 |
11 | src/out/
12 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "java.configuration.updateBuildConfiguration": "interactive"
3 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2024 Michael Estermann
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WindowLayoutManager
2 |
3 | A plugin for managing window layout in IntelliJ based IDEs.
4 |
5 | [](https://opensource.org/licenses/MIT)
6 |
7 | ## Build
8 |
9 | **Master** [](https://ci.appveyor.com/project/michaelestermann/windowlayoutmanager/branch/master)
10 | **Overall** [](https://ci.appveyor.com/project/michaelestermann/windowlayoutmanager)
11 | **Quality** [](https://app.codacy.com/gh/michaelestermann/WindowLayoutManager/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
12 |
13 | ## Contribute
14 |
15 | Contribution to the plugin is very much appreciated.
16 | The [documentation](doc/dev-setup.md) describes how-to set up your development environment.
17 |
--------------------------------------------------------------------------------
/WindowLayoutManager.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | image: Visual Studio 2022
2 | version: 1.5.1.{build}
3 | environment:
4 | JAVA_HOME: C:\Program Files\Java\jdk17
5 | #
6 |
7 | build_script:
8 | - gradlew.bat buildPlugin --warning-mode none
9 | #
10 |
11 | artifacts:
12 | - path: 'build\distributions\*.zip'
13 | name: window-layout-manager
14 | #
15 |
16 | deploy_script:
17 | - ps: >-
18 | if ($env:APPVEYOR_REPO_TAG -eq $TRUE -And $env:APPVEYOR_REPO_TAG_NAME -match '^V\d+\.\d+(\.\d+)?$') {
19 | .\gradlew publishPlugin --warning-mode none
20 | }
21 | #
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'java'
3 | id 'org.jetbrains.intellij' version '1.17.4'
4 | id 'checkstyle'
5 | }
6 |
7 | dependencies {
8 | implementation group: 'com.google.code.gson', name: 'gson', version: '2.10.1'
9 | implementation group: 'com.github.tommyettinger', name: 'blazingchain', version: '1.4.4.4'
10 | }
11 |
12 | group 'com.layoutmanager'
13 | version '1.5.1'
14 |
15 | java {
16 | sourceCompatibility = JavaVersion.VERSION_17
17 | targetCompatibility = JavaVersion.VERSION_17
18 | }
19 |
20 | buildSearchableOptions.enabled = false
21 |
22 | repositories {
23 | mavenLocal()
24 | mavenCentral()
25 | }
26 |
27 | // https://www.jetbrains.org/intellij/sdk/docs/reference_guide/intellij_artifacts.html
28 | // https://www.jetbrains.com/intellij-repository/releases/
29 | // https://www.jetbrains.com/intellij-repository/snapshots/
30 | intellij {
31 | type = 'RD'
32 | // Update the version if required for testing compatibility with newer versions
33 | // This will raise a a build warning, since the version 231 is 2023.1 (2023.1 fails on the build server!)
34 | version = "2024.1.5"
35 | updateSinceUntilBuild = false
36 | }
37 |
38 | publishPlugin {
39 | token = System.getenv("ORG_GRADLE_PROJECT_intellijPublishToken")
40 | }
41 |
42 | wrapper {
43 | gradleVersion = '8.6'
44 | distributionUrl = "https://cache-redirector.jetbrains.com/services.gradle.org/distributions/gradle-${gradleVersion}-all.zip"
45 | }
46 |
47 | checkstyle {
48 | project.ext.checkstyleVersion = '10.13.0'
49 | }
50 |
51 | checkstyleMain {
52 | source ='src/main/java'
53 | }
--------------------------------------------------------------------------------
/config/checkstyle/checkstyle.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
44 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
59 |
60 |
61 |
62 |
63 |
64 |
67 |
68 |
69 |
70 |
71 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
83 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
127 |
128 |
129 |
131 |
132 |
133 |
134 |
136 |
137 |
138 |
139 |
141 |
142 |
143 |
144 |
146 |
147 |
148 |
149 |
151 |
152 |
153 |
154 |
155 |
157 |
158 |
159 |
160 |
162 |
163 |
164 |
165 |
167 |
168 |
169 |
170 |
172 |
173 |
174 |
175 |
177 |
179 |
181 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
207 |
208 |
209 |
210 |
211 |
212 |
215 |
216 |
217 |
218 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
--------------------------------------------------------------------------------
/doc/dev-setup.md:
--------------------------------------------------------------------------------
1 | # Developer Setup
2 | The following instructions will show you how to set up your development environment to start developing the plugin.
3 | Thank you for your interest in the further development of this plugin. Have fun tinkering around and don't be afraid to make a pull request for your cool extensions.
4 |
5 | ## Setup your Environment
6 |
7 | ### Download IDE
8 | When it comes to the Java development, the IntelliJ IDE is a good fit. The community version is free and can be downloaded from [here](https://www.jetbrains.com/de-de/idea/download/#section=windows).
9 |
10 | ### Installing Java
11 | For the development of Java, the JDK (or Open JDK) is required, which can be downloaded either from [Oracle](https://www.oracle.com/ch-de/java/technologies/javase-downloads.html) or the [Open JDK Website](https://openjdk.java.net/). For this repository, Java 8 is the minimum.
12 | If you have already installed a java development kit (JDK), ensure that the JAVA_HOME environment variable is set and points to the path where the JDK is installed. A step-by-step instruction can be found [here](https://javatutorial.net/set-java-home-windows-10).
13 |
14 | ## Download the code
15 | First of all, you need a GitHub account If you don't have any yet. Afterward you can fork the repository and make it your own. A step-by-step guide can be found [here](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo).
16 |
17 | After forking, you can clone the repository. With the locally cloned repository, you can start with the development. hurray! :)
18 |
19 | ## Run it
20 |
21 | ### Command line
22 | This projects uses [gradle](https://gradle.org/) as its build foundation. The first execution of any task can take a while since it needs to download a few things like the IDE and libraries. A progressbar will keep you informed about the progress.
23 |
24 | Following build tasks are the most common task to is it in relation to the plugin development:
25 |
26 | * Build plugin
27 | ```
28 | gradlew buildPlugin
29 | ```
30 |
31 | * Run plugin in IDE
32 | ```
33 | gradlew runIde
34 | ```
35 |
36 | * Verify plugin
37 | ```
38 | gradlew verifyPlugin
39 | ```
40 | * Publish plugin
41 | ```
42 | gradlew publishPlugin
43 | ```
44 |
45 | A complete list of all task can be retrieved by executing the command:
46 | ```
47 | gradlew tasks
48 | ```
49 |
50 | ### IDE
51 | If your IDE supports gradle, like IntelliJ does, you can just execute the build. It should start the build process.
52 | It's recommended to configure the run plugin task in the IDE, otherwise the plugin must be started from the console as described in the former chapter.
53 |
54 | #### Setup IntelliJ
55 |
56 | The following steps are required to debug the plugin within an IDE:
57 |
58 | 1. Open the build configurations by navigation to the window menu [Run] -> [Edit Configurations]
59 |
60 | 
61 |
62 | 2. A dialog should be displayed with all configurations. Hit the "+" icon and select gradle task.
63 |
64 | 
65 |
66 | 3. Now you can configure the gradle task.
67 |
68 | 
69 |
70 | 4. Finish the configuration by hitting the "apply" button. Now the configuration can be executed ([Run] -> [Run...] or [Debug...]).
--------------------------------------------------------------------------------
/doc/images/add-gradle-task.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michaelestermann/WindowLayoutManager/1c50e645ce8d1e75dc8bbc1249f1ed0d1c58bade/doc/images/add-gradle-task.png
--------------------------------------------------------------------------------
/doc/images/configure-gradle-task.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michaelestermann/WindowLayoutManager/1c50e645ce8d1e75dc8bbc1249f1ed0d1c58bade/doc/images/configure-gradle-task.png
--------------------------------------------------------------------------------
/doc/images/edit-configuration.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michaelestermann/WindowLayoutManager/1c50e645ce8d1e75dc8bbc1249f1ed0d1c58bade/doc/images/edit-configuration.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michaelestermann/WindowLayoutManager/1c50e645ce8d1e75dc8bbc1249f1ed0d1c58bade/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.10-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/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/HEAD/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 | # This is normally unused
84 | # shellcheck disable=SC2034
85 | APP_BASE_NAME=${0##*/}
86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
88 |
89 | # Use the maximum available, or set MAX_FD != -1 to use that value.
90 | MAX_FD=maximum
91 |
92 | warn () {
93 | echo "$*"
94 | } >&2
95 |
96 | die () {
97 | echo
98 | echo "$*"
99 | echo
100 | exit 1
101 | } >&2
102 |
103 | # OS specific support (must be 'true' or 'false').
104 | cygwin=false
105 | msys=false
106 | darwin=false
107 | nonstop=false
108 | case "$( uname )" in #(
109 | CYGWIN* ) cygwin=true ;; #(
110 | Darwin* ) darwin=true ;; #(
111 | MSYS* | MINGW* ) msys=true ;; #(
112 | NONSTOP* ) nonstop=true ;;
113 | esac
114 |
115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
116 |
117 |
118 | # Determine the Java command to use to start the JVM.
119 | if [ -n "$JAVA_HOME" ] ; then
120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
121 | # IBM's JDK on AIX uses strange locations for the executables
122 | JAVACMD=$JAVA_HOME/jre/sh/java
123 | else
124 | JAVACMD=$JAVA_HOME/bin/java
125 | fi
126 | if [ ! -x "$JAVACMD" ] ; then
127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
128 |
129 | Please set the JAVA_HOME variable in your environment to match the
130 | location of your Java installation."
131 | fi
132 | else
133 | JAVACMD=java
134 | if ! command -v java >/dev/null 2>&1
135 | then
136 | 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 | fi
142 |
143 | # Increase the maximum file descriptors if we can.
144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
145 | case $MAX_FD in #(
146 | max*)
147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
148 | # shellcheck disable=SC2039,SC3045
149 | MAX_FD=$( ulimit -H -n ) ||
150 | warn "Could not query maximum file descriptor limit"
151 | esac
152 | case $MAX_FD in #(
153 | '' | soft) :;; #(
154 | *)
155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
156 | # shellcheck disable=SC2039,SC3045
157 | ulimit -n "$MAX_FD" ||
158 | warn "Could not set maximum file descriptor limit to $MAX_FD"
159 | esac
160 | fi
161 |
162 | # Collect all arguments for the java command, stacking in reverse order:
163 | # * args from the command line
164 | # * the main class name
165 | # * -classpath
166 | # * -D...appname settings
167 | # * --module-path (only if needed)
168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
169 |
170 | # For Cygwin or MSYS, switch paths to Windows format before running java
171 | if "$cygwin" || "$msys" ; then
172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
174 |
175 | JAVACMD=$( cygpath --unix "$JAVACMD" )
176 |
177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
178 | for arg do
179 | if
180 | case $arg in #(
181 | -*) false ;; # don't mess with options #(
182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
183 | [ -e "$t" ] ;; #(
184 | *) false ;;
185 | esac
186 | then
187 | arg=$( cygpath --path --ignore --mixed "$arg" )
188 | fi
189 | # Roll the args list around exactly as many times as the number of
190 | # args, so each arg winds up back in the position where it started, but
191 | # possibly modified.
192 | #
193 | # NB: a `for` loop captures its iteration list before it begins, so
194 | # changing the positional parameters here affects neither the number of
195 | # iterations, nor the values presented in `arg`.
196 | shift # remove old arg
197 | set -- "$@" "$arg" # push replacement arg
198 | done
199 | fi
200 |
201 |
202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
204 |
205 | # Collect all arguments for the java command:
206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
207 | # and any embedded shellness will be escaped.
208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
209 | # treated as '${Hostname}' itself on the command line.
210 |
211 | set -- \
212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
213 | -classpath "$CLASSPATH" \
214 | org.gradle.wrapper.GradleWrapperMain \
215 | "$@"
216 |
217 | # Stop when "xargs" is not available.
218 | if ! command -v xargs >/dev/null 2>&1
219 | then
220 | die "xargs is not available"
221 | fi
222 |
223 | # Use "xargs" to parse quoted args.
224 | #
225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
226 | #
227 | # In Bash we could simply go:
228 | #
229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
230 | # set -- "${ARGS[@]}" "$@"
231 | #
232 | # but POSIX shell has neither arrays nor command substitution, so instead we
233 | # post-process each arg (as a line of input to sed) to backslash-escape any
234 | # character that might be a shell metacharacter, then use eval to reverse
235 | # that process (while maintaining the separation between arguments), and wrap
236 | # the whole thing up as a single "set" statement.
237 | #
238 | # This will of course break if any of these variables contains a newline or
239 | # an unmatched quote.
240 | #
241 |
242 | eval "set -- $(
243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
244 | xargs -n1 |
245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
246 | tr '\n' ' '
247 | )" '"$@"'
248 |
249 | exec "$JAVACMD" "$@"
250 |
--------------------------------------------------------------------------------
/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 | @rem This is normally unused
30 | set APP_BASE_NAME=%~n0
31 | set APP_HOME=%DIRNAME%
32 |
33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
35 |
36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
38 |
39 | @rem Find java.exe
40 | if defined JAVA_HOME goto findJavaFromJavaHome
41 |
42 | set JAVA_EXE=java.exe
43 | %JAVA_EXE% -version >NUL 2>&1
44 | if %ERRORLEVEL% equ 0 goto execute
45 |
46 | echo. 1>&2
47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
48 | echo. 1>&2
49 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
50 | echo location of your Java installation. 1>&2
51 |
52 | goto fail
53 |
54 | :findJavaFromJavaHome
55 | set JAVA_HOME=%JAVA_HOME:"=%
56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
57 |
58 | if exist "%JAVA_EXE%" goto execute
59 |
60 | echo. 1>&2
61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
62 | echo. 1>&2
63 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
64 | echo location of your Java installation. 1>&2
65 |
66 | goto fail
67 |
68 | :execute
69 | @rem Setup the command line
70 |
71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
72 |
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if %ERRORLEVEL% equ 0 goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | set EXIT_CODE=%ERRORLEVEL%
85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
87 | exit /b %EXIT_CODE%
88 |
89 | :mainEnd
90 | if "%OS%"=="Windows_NT" endlocal
91 |
92 | :omega
93 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | maven {
4 | url 'https://cache-redirector.jetbrains.com/plugins.gradle.org'
5 | }
6 | }
7 | }
8 |
9 | rootProject.name = 'window-layout-manager'
10 | includeBuild '..'
11 |
12 |
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/cleanup/EmptyLayoutRemoverService.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.cleanup;
2 |
3 | import com.layoutmanager.persistence.Layout;
4 | import com.layoutmanager.persistence.LayoutConfig;
5 |
6 | public class EmptyLayoutRemoverService {
7 | public void execute() {
8 | LayoutConfig layoutConfig = LayoutConfig.getInstance();
9 |
10 | for (Layout layout : layoutConfig.getLayouts()) {
11 | if (this.isEmpty(layout)) {
12 | layoutConfig.removeLayout(layout);
13 | }
14 | }
15 | }
16 |
17 | private boolean isEmpty(Layout layout) {
18 | return layout.getToolWindows().length == 0;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/layout/LayoutAction.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.layout;
2 |
3 | import com.intellij.openapi.actionSystem.AnAction;
4 | import com.layoutmanager.persistence.Layout;
5 |
6 | public abstract class LayoutAction extends AnAction {
7 | public abstract Layout getLayout();
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/layout/delete/DeleteLayoutAction.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.layout.delete;
2 |
3 | import com.intellij.openapi.actionSystem.ActionUpdateThread;
4 | import com.intellij.openapi.actionSystem.AnActionEvent;
5 | import com.intellij.openapi.actionSystem.Presentation;
6 | import com.intellij.openapi.application.ApplicationManager;
7 | import com.intellij.openapi.project.DumbAware;
8 | import com.layoutmanager.layout.LayoutAction;
9 | import com.layoutmanager.localization.MessagesHelper;
10 | import com.layoutmanager.persistence.Layout;
11 | import com.layoutmanager.persistence.LayoutConfig;
12 | import com.layoutmanager.ui.helpers.BalloonNotificationHelper;
13 | import com.layoutmanager.ui.icons.Icons;
14 | import com.layoutmanager.ui.menu.WindowMenuService;
15 | import org.jetbrains.annotations.NotNull;
16 |
17 |
18 | public class DeleteLayoutAction
19 | extends LayoutAction
20 | implements DumbAware {
21 |
22 | private final Layout layout;
23 |
24 | public DeleteLayoutAction(Layout layout) {
25 | this.layout = layout;
26 | Presentation presentation = this.getTemplatePresentation();
27 | presentation.setText(layout.getName());
28 | presentation.setIcon(Icons.Menu.DeleteLayout);
29 | }
30 |
31 | @Override
32 | public @NotNull ActionUpdateThread getActionUpdateThread() {
33 | return ActionUpdateThread.BGT;
34 | }
35 |
36 | @Override
37 | public void actionPerformed(@NotNull AnActionEvent event) {
38 | this.deleteLayout();
39 | this.updateWindowMenuItems();
40 | this.showNotification();
41 | }
42 |
43 | @Override
44 | public void update(AnActionEvent e) {
45 | e.getPresentation().setText(this.layout.getName());
46 | }
47 |
48 | public Layout getLayout() {
49 | return this.layout;
50 | }
51 |
52 | private void deleteLayout() {
53 | LayoutConfig layoutConfig = LayoutConfig.getInstance();
54 | layoutConfig.removeLayout(this.layout);
55 | }
56 |
57 | private void showNotification() {
58 | BalloonNotificationHelper.info(
59 | MessagesHelper.message("DeleteLayout.Notification.Title"),
60 | MessagesHelper.message("DeleteLayout.Notification.Content", this.layout.getName()));
61 | }
62 |
63 | private void updateWindowMenuItems() {
64 | WindowMenuService windowMenuService = ApplicationManager
65 | .getApplication()
66 | .getService(WindowMenuService.class);
67 | windowMenuService.deleteLayout(this.layout);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/layout/restore/RestoreLayoutAction.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.layout.restore;
2 |
3 | import com.intellij.ide.ui.UISettings;
4 | import com.intellij.openapi.actionSystem.ActionUpdateThread;
5 | import com.intellij.openapi.actionSystem.AnActionEvent;
6 | import com.intellij.openapi.actionSystem.Presentation;
7 | import com.intellij.openapi.project.DumbAware;
8 | import com.intellij.openapi.project.Project;
9 | import com.intellij.openapi.util.Pair;
10 | import com.intellij.openapi.wm.ToolWindowAnchor;
11 | import com.intellij.openapi.wm.ToolWindowManager;
12 | import com.intellij.openapi.wm.ex.ToolWindowEx;
13 | import com.layoutmanager.layout.LayoutAction;
14 | import com.layoutmanager.localization.MessagesHelper;
15 | import com.layoutmanager.persistence.Layout;
16 | import com.layoutmanager.persistence.ToolWindowInfo;
17 | import com.layoutmanager.ui.helpers.BalloonNotificationHelper;
18 | import com.layoutmanager.ui.helpers.ToolWindowHelper;
19 | import com.layoutmanager.ui.icons.Icons;
20 | import java.util.Map;
21 | import java.util.Objects;
22 | import java.util.stream.Collectors;
23 | import java.util.stream.Stream;
24 | import org.jetbrains.annotations.NotNull;
25 |
26 | public class RestoreLayoutAction
27 | extends LayoutAction
28 | implements DumbAware {
29 |
30 | private final Layout layout;
31 |
32 | public RestoreLayoutAction(Layout layout) {
33 | super();
34 | this.layout = layout;
35 |
36 | Presentation presentation = this.getTemplatePresentation();
37 | presentation.setText(layout.getName());
38 | presentation.setIcon(Icons.Menu.RestoreLayout);
39 | }
40 |
41 | @Override
42 | public @NotNull ActionUpdateThread getActionUpdateThread() {
43 | return ActionUpdateThread.BGT;
44 | }
45 |
46 | @Override
47 | public void update(AnActionEvent actionEvent) {
48 | actionEvent
49 | .getPresentation()
50 | .setText(this.layout.getName());
51 | }
52 |
53 |
54 | @Override
55 | public void actionPerformed(@NotNull AnActionEvent event) {
56 | if (this.isProjectLoaded(event)) {
57 | this.applyLayout(event, this.layout);
58 | this.showNotification(this.layout);
59 | }
60 | }
61 |
62 | public Layout getLayout() {
63 | return this.layout;
64 | }
65 |
66 | private void applyLayout(AnActionEvent event, Layout layout) {
67 | this.applyEditorTabPlacement(layout);
68 | ToolWindowManager toolWindowManager = this.getToolWindowManager(event);
69 |
70 | Map toolWindows = this.getToolWindows(toolWindowManager, layout.getToolWindows());
71 | this.hideAllToolWindows(toolWindows);
72 | this.applyToolWindowLayout(toolWindows);
73 | }
74 |
75 | private Map getToolWindows(ToolWindowManager toolWindowManager, ToolWindowInfo[] toolWindows) {
76 | return Stream
77 | .of(toolWindows)
78 | .map(x -> new Pair<>(x, (ToolWindowEx) toolWindowManager.getToolWindow(x.getId())))
79 | .filter(x -> x.second != null)
80 | .collect(Collectors.toMap(x -> x.first, x -> x.second));
81 | }
82 |
83 | private void applyEditorTabPlacement(Layout layout) {
84 | if (layout.getEditorPlacement() >= 0) {
85 | UISettings uiSettings = UISettings.getInstance();
86 | uiSettings.setEditorTabPlacement(layout.getEditorPlacement());
87 | uiSettings.setWideScreenSupport(layout.getWideScreenSupport());
88 | uiSettings.fireUISettingsChanged();
89 | }
90 | }
91 |
92 | private void hideAllToolWindows(Map toolWindows) {
93 | toolWindows.forEach((info, toolWindow) -> {
94 | if (!info.isVisible()) {
95 | toolWindow.hide(null);
96 | }
97 | });
98 | }
99 |
100 | private void applyToolWindowLayout(Map toolWindows) {
101 | toolWindows.forEach((info, toolWindow) -> {
102 | if (info.isVisible()) {
103 | // !! Workaround !!
104 | // decorator is not set and throws exception. When calling this method, the content manager lazy
105 | // variable will be loaded and therefore also the decorator...
106 | // See: https://github.com/JetBrains/intellij-community/blob/a63286c3b29fe467399fb353c71ed15cd65db8dd/
107 | // platform/platform-impl/src/com/intellij/openapi/wm/impl/ToolWindowImpl.kt
108 | toolWindow.getComponent();
109 |
110 | toolWindow.setAnchor(ToolWindowAnchor.fromText(info.getAnchor()), null);
111 | toolWindow.setType(info.getType(), null);
112 | toolWindow.setSplitMode(info.isToolWindow(), null);
113 |
114 | ToolWindowHelper.setBounds(toolWindow, info.getBounds());
115 |
116 | toolWindow.show();
117 | }
118 | });
119 | }
120 |
121 | private ToolWindowManager getToolWindowManager(AnActionEvent event) {
122 | Project project = event.getProject();
123 | return ToolWindowManager.getInstance(
124 | Objects.requireNonNull(project));
125 | }
126 |
127 | private void showNotification(Layout updatedLayout) {
128 | BalloonNotificationHelper.info(
129 | MessagesHelper.message("RestoreLayout.Notification.Title"),
130 | MessagesHelper.message("RestoreLayout.Notification.Content", updatedLayout.getName()));
131 | }
132 |
133 | private boolean isProjectLoaded(AnActionEvent event) {
134 | return event.getProject() != null;
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/layout/store/LayoutCreator.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.layout.store;
2 |
3 | import com.intellij.ide.ui.UISettings;
4 | import com.intellij.openapi.wm.ToolWindowManager;
5 | import com.intellij.openapi.wm.ex.ToolWindowEx;
6 | import com.layoutmanager.layout.store.smartdock.SmartDocker;
7 | import com.layoutmanager.layout.store.smartdock.SmartDockerFactory;
8 | import com.layoutmanager.layout.store.validation.LayoutValidationHelper;
9 | import com.layoutmanager.localization.MessagesHelper;
10 | import com.layoutmanager.persistence.Layout;
11 | import com.layoutmanager.persistence.LayoutSettings;
12 | import com.layoutmanager.persistence.ToolWindowInfo;
13 | import com.layoutmanager.ui.dialogs.LayoutNameDialog;
14 | import com.layoutmanager.ui.helpers.BalloonNotificationHelper;
15 | import com.layoutmanager.ui.helpers.ToolWindowHelper;
16 | import java.util.ArrayList;
17 | import java.util.List;
18 | import java.util.Objects;
19 | import java.util.stream.Stream;
20 | import org.jetbrains.annotations.NotNull;
21 |
22 | public class LayoutCreator {
23 | private final LayoutSettings layoutSettings;
24 | private final SmartDockerFactory smartDockerFactory;
25 | private final LayoutNameDialog layoutNameDialog;
26 |
27 | public LayoutCreator(
28 | LayoutSettings layoutSettings,
29 | SmartDockerFactory smartDockerFactory,
30 | LayoutNameDialog layoutNameDialog) {
31 | this.layoutSettings = layoutSettings;
32 | this.smartDockerFactory = smartDockerFactory;
33 | this.layoutNameDialog = layoutNameDialog;
34 | }
35 |
36 | public Layout create(
37 | ToolWindowManager toolWindowManager,
38 | int id,
39 | String defaultName) {
40 |
41 | String name = this.layoutNameDialog.show(defaultName);
42 | return name != null ?
43 | this.createLayout(toolWindowManager, id, name) :
44 | null;
45 | }
46 |
47 | private Layout createLayout(ToolWindowManager toolWindowManager, int id, String name) {
48 | List toolWindows = getToolWindows(toolWindowManager);
49 | Layout layout = new Layout(
50 | id,
51 | name,
52 | toolWindows.toArray(ToolWindowInfo[]::new),
53 | getEditorPlacement(),
54 | getWideScreenSupport());
55 |
56 | if (this.layoutSettings.getUseSmartDock()) {
57 | this.dock(toolWindowManager, layout);
58 | }
59 |
60 | validateLayout(layout);
61 |
62 | return layout;
63 | }
64 |
65 | @NotNull
66 | private static List getToolWindows(ToolWindowManager toolWindowManager) {
67 | String[] toolWindowIds = toolWindowManager.getToolWindowIds();
68 | List toolWindows = new ArrayList<>();
69 | for (String id : toolWindowIds) {
70 | ToolWindowEx toolWindow = (ToolWindowEx)toolWindowManager.getToolWindow(id);
71 |
72 | ToolWindowInfo info = new ToolWindowInfo(
73 | id,
74 | Objects.requireNonNull(toolWindow).getType(),
75 | toolWindow.getAnchor().toString(),
76 | ToolWindowHelper.getBounds(toolWindow),
77 | toolWindow.isVisible(),
78 | toolWindow.isSplitMode());
79 | toolWindows.add(info);
80 | }
81 |
82 | return toolWindows;
83 | }
84 |
85 | private void dock(ToolWindowManager toolWindowManager, Layout layout) {
86 | SmartDocker smartDocker = this.smartDockerFactory.create(toolWindowManager);
87 | smartDocker.dock(layout);
88 | }
89 |
90 | private static void validateLayout(Layout layout) {
91 | ToolWindowInfo[] invalidToolWindows = LayoutValidationHelper.retrieveToolWindowsOutsideOfScreen(layout);
92 | if (invalidToolWindows.length != 0) {
93 | String invalidToolWindowNames = String.join(
94 | ", ",
95 | Stream.of(invalidToolWindows)
96 | .map(ToolWindowInfo::getId)
97 | .toArray(String[]::new));
98 |
99 | BalloonNotificationHelper.warning(
100 | MessagesHelper.message("StoreLayout.Validation.ToolWindowOutOfScreen.Title"),
101 | MessagesHelper.message("StoreLayout.Validation.ToolWindowOutOfScreen.Content", invalidToolWindowNames));
102 | }
103 | }
104 |
105 | private static int getEditorPlacement() {
106 | return UISettings
107 | .getInstance()
108 | .getEditorTabPlacement();
109 | }
110 |
111 | private static boolean getWideScreenSupport() {
112 | return UISettings
113 | .getInstance()
114 | .getWideScreenSupport();
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/layout/store/create/NewLayoutAction.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.layout.store.create;
2 |
3 | import com.intellij.openapi.actionSystem.ActionUpdateThread;
4 | import com.intellij.openapi.actionSystem.AnAction;
5 | import com.intellij.openapi.actionSystem.AnActionEvent;
6 | import com.intellij.openapi.actionSystem.Presentation;
7 | import com.intellij.openapi.application.ApplicationManager;
8 | import com.intellij.openapi.project.DumbAware;
9 | import com.intellij.openapi.project.Project;
10 | import com.intellij.openapi.wm.ToolWindowManager;
11 | import com.layoutmanager.layout.store.LayoutCreator;
12 | import com.layoutmanager.localization.MessagesHelper;
13 | import com.layoutmanager.persistence.Layout;
14 | import com.layoutmanager.persistence.LayoutConfig;
15 | import com.layoutmanager.ui.helpers.BalloonNotificationHelper;
16 | import com.layoutmanager.ui.icons.Icons;
17 | import com.layoutmanager.ui.menu.WindowMenuService;
18 | import java.util.Objects;
19 | import org.jetbrains.annotations.NotNull;
20 |
21 | public class NewLayoutAction
22 | extends AnAction
23 | implements DumbAware {
24 |
25 | private final LayoutCreator layoutCreator;
26 | private final int id;
27 |
28 | public NewLayoutAction(LayoutCreator layoutCreator, int id) {
29 | this.layoutCreator = layoutCreator;
30 | this.id = id;
31 | Presentation presentation = this.getTemplatePresentation();
32 | presentation.setText(MessagesHelper.message("StoreLayout.New.Menu"));
33 | presentation.setIcon(Icons.Menu.CreateNewLayout);
34 | }
35 |
36 | @Override
37 | public @NotNull ActionUpdateThread getActionUpdateThread() {
38 | return ActionUpdateThread.BGT;
39 | }
40 |
41 | @Override
42 | public void actionPerformed(@NotNull AnActionEvent event) {
43 | if (this.isProjectLoaded(event)) {
44 | ToolWindowManager toolWindowManager = this.getToolWindowManager(event);
45 | Layout newLayout = this.layoutCreator.create(
46 | toolWindowManager,
47 | this.id,
48 | "");
49 |
50 | if (newLayout != null) {
51 | this.storeLayout(newLayout);
52 | this.updateWindowMenuItems(newLayout);
53 | this.showNotification(newLayout);
54 | }
55 | }
56 | }
57 |
58 | private void storeLayout(Layout layout) {
59 | LayoutConfig
60 | .getInstance()
61 | .addLayout(layout);
62 | }
63 |
64 | private void updateWindowMenuItems(Layout newLayout) {
65 | WindowMenuService windowMenuService = ApplicationManager
66 | .getApplication()
67 | .getService(WindowMenuService.class);
68 | windowMenuService.addLayout(newLayout);
69 | }
70 |
71 | private void showNotification(Layout newLayout) {
72 | BalloonNotificationHelper.info(
73 | MessagesHelper.message("StoreLayout.New.Notification.Title"),
74 | MessagesHelper.message("StoreLayout.New.Notification.Content", newLayout.getName()));
75 | }
76 |
77 | private ToolWindowManager getToolWindowManager(AnActionEvent event) {
78 | Project project = event.getProject();
79 | return ToolWindowManager.getInstance(
80 | Objects.requireNonNull(project));
81 | }
82 |
83 | private boolean isProjectLoaded(AnActionEvent event) {
84 | return event.getProject() != null;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/layout/store/overwrite/OverwriteLayoutAction.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.layout.store.overwrite;
2 |
3 | import com.intellij.openapi.actionSystem.ActionUpdateThread;
4 | import com.intellij.openapi.actionSystem.AnActionEvent;
5 | import com.intellij.openapi.actionSystem.Presentation;
6 | import com.intellij.openapi.application.ApplicationManager;
7 | import com.intellij.openapi.project.DumbAware;
8 | import com.intellij.openapi.project.Project;
9 | import com.intellij.openapi.wm.ToolWindowManager;
10 | import com.layoutmanager.layout.LayoutAction;
11 | import com.layoutmanager.layout.store.LayoutCreator;
12 | import com.layoutmanager.localization.MessagesHelper;
13 | import com.layoutmanager.persistence.Layout;
14 | import com.layoutmanager.ui.helpers.BalloonNotificationHelper;
15 | import com.layoutmanager.ui.icons.Icons;
16 | import com.layoutmanager.ui.menu.WindowMenuService;
17 | import java.util.Objects;
18 | import org.jetbrains.annotations.NotNull;
19 |
20 | public class OverwriteLayoutAction
21 | extends LayoutAction
22 | implements DumbAware {
23 |
24 | private final LayoutCreator layoutCreator;
25 | public final Layout layout;
26 |
27 | public OverwriteLayoutAction(
28 | LayoutCreator layoutCreator,
29 | Layout layout) {
30 | this.layoutCreator = layoutCreator;
31 | this.layout = layout;
32 |
33 | Presentation presentation = this.getTemplatePresentation();
34 | presentation.setText(this.layout.getName());
35 | presentation.setIcon(Icons.Menu.OverwriteLayout);
36 | }
37 |
38 | @Override
39 | public @NotNull ActionUpdateThread getActionUpdateThread() {
40 | return ActionUpdateThread.BGT;
41 | }
42 |
43 | @Override
44 | public void update(AnActionEvent e) {
45 | e.getPresentation().setText(this.layout.getName());
46 | }
47 |
48 | @Override
49 | public void actionPerformed(@NotNull AnActionEvent event) {
50 | if (this.isProjectLoaded(event)) {
51 | ToolWindowManager toolWindowManager = this.getToolWindowManager(event);
52 | String previousName = this.layout.getName();
53 | Layout updatedLayout = this.layoutCreator.create(
54 | toolWindowManager,
55 | this.layout.getId(),
56 | this.layout.getName());
57 |
58 | if (updatedLayout != null) {
59 | this.updateLayout(updatedLayout);
60 | this.updateWindowMenu(previousName);
61 | this.showNotification(this.layout.getName(), previousName);
62 | }
63 | }
64 | }
65 |
66 | public Layout getLayout() {
67 | return this.layout;
68 | }
69 |
70 |
71 | private void updateLayout(Layout updatedLayout) {
72 | this.layout.setEditorPlacement(updatedLayout.getEditorPlacement());
73 | this.layout.setWideScreenSupport(updatedLayout.getWideScreenSupport());
74 | this.layout.setName(updatedLayout.getName());
75 | this.layout.setToolWindows(updatedLayout.getToolWindows());
76 | }
77 |
78 | private void updateWindowMenu(String previousName) {
79 | if (!previousName.equals(this.layout.getName())) {
80 | WindowMenuService windowMenuService = ApplicationManager
81 | .getApplication()
82 | .getService(WindowMenuService.class);
83 | windowMenuService.renameLayout(this.layout);
84 | }
85 | }
86 |
87 | private void showNotification(String currentLayoutName, String previousLayoutName) {
88 | BalloonNotificationHelper.info(
89 | MessagesHelper.message("StoreLayout.Overwrite.Notification.Title"),
90 | MessagesHelper.message("StoreLayout.Overwrite.Notification.Content", previousLayoutName, currentLayoutName));
91 | }
92 |
93 | private ToolWindowManager getToolWindowManager(AnActionEvent event) {
94 | Project project = event.getProject();
95 | return ToolWindowManager.getInstance(
96 | Objects.requireNonNull(project));
97 | }
98 |
99 | private boolean isProjectLoaded(AnActionEvent event) {
100 | return event.getProject() != null;
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/layout/store/smartdock/SmartDocker.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.layout.store.smartdock;
2 |
3 | import com.intellij.openapi.wm.ToolWindowManager;
4 | import com.intellij.openapi.wm.ToolWindowType;
5 | import com.layoutmanager.layout.store.smartdock.dockers.ScreenBorderDocker;
6 | import com.layoutmanager.layout.store.smartdock.dockers.ToolWindowDocker;
7 | import com.layoutmanager.layout.store.smartdock.dockers.ToolWindowToScreenShrinker;
8 | import com.layoutmanager.persistence.Layout;
9 | import com.layoutmanager.persistence.ToolWindowInfo;
10 | import com.layoutmanager.ui.helpers.ScreenSizeHelper;
11 | import java.util.Arrays;
12 |
13 | public class SmartDocker {
14 | private static final int THRESHOLD = 20;
15 |
16 | private final ToolWindowManager toolWindowManager;
17 | private final ToolWindowToScreenShrinker shrinker;
18 | private final ToolWindowDocker toolWindowDocker;
19 | private final ScreenBorderDocker screenBorderDocker;
20 |
21 | public SmartDocker(
22 | ToolWindowManager toolWindowManager,
23 | ToolWindowToScreenShrinker shrinker,
24 | ToolWindowDocker toolWindowDocker,
25 | ScreenBorderDocker screenBorderDocker) {
26 | this.toolWindowManager = toolWindowManager;
27 | this.shrinker = shrinker;
28 | this.toolWindowDocker = toolWindowDocker;
29 | this.screenBorderDocker = screenBorderDocker;
30 | }
31 |
32 | public void dock(Layout layout) {
33 | ToolWindowDocking[] floatedOrWindowsToolWindows = this.getFloatedOrWindowsToolWindows(layout);
34 |
35 | this.shrinker.shrink(floatedOrWindowsToolWindows);
36 | this.toolWindowDocker.dock(floatedOrWindowsToolWindows);
37 | this.screenBorderDocker.dock(floatedOrWindowsToolWindows, THRESHOLD);
38 | }
39 |
40 |
41 | private ToolWindowDocking[] getFloatedOrWindowsToolWindows(Layout layout) {
42 | return Arrays.stream(layout.getToolWindows())
43 | .filter(this::isFloatingOrWindowedToolWindow)
44 | .map(x -> new ToolWindowDocking(
45 | x,
46 | ScreenSizeHelper.getContainingScreenBounds(x),
47 | THRESHOLD))
48 | .toArray(ToolWindowDocking[]::new);
49 | }
50 |
51 | private boolean isFloatingOrWindowedToolWindow(ToolWindowInfo toolWindow) {
52 | return this.toolWindowManager.getToolWindow(toolWindow.getId()) != null &&
53 | toolWindow.getType() == ToolWindowType.FLOATING ||
54 | toolWindow.getType() == ToolWindowType.WINDOWED;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/layout/store/smartdock/SmartDockerFactory.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.layout.store.smartdock;
2 |
3 | import com.intellij.openapi.wm.ToolWindowManager;
4 | import com.layoutmanager.layout.store.smartdock.dockers.ScreenBorderDocker;
5 | import com.layoutmanager.layout.store.smartdock.dockers.ToolWindowDocker;
6 | import com.layoutmanager.layout.store.smartdock.dockers.ToolWindowToScreenShrinker;
7 |
8 | public class SmartDockerFactory {
9 | public SmartDocker create(ToolWindowManager toolWindowManager) {
10 | return new SmartDocker(
11 | toolWindowManager,
12 | new ToolWindowToScreenShrinker(),
13 | new ToolWindowDocker(),
14 | new ScreenBorderDocker());
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/layout/store/smartdock/ToolWindowDocking.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.layout.store.smartdock;
2 |
3 | import com.layoutmanager.persistence.ToolWindowInfo;
4 | import java.awt.Rectangle;
5 |
6 | public class ToolWindowDocking {
7 |
8 | private final ToolWindowInfo toolWindowInfo;
9 | private final Rectangle containingScreen;
10 | private final int threshold;
11 |
12 | public ToolWindowDocking(
13 | ToolWindowInfo toolWindowInfo,
14 | Rectangle containingScreen,
15 | int threshold) {
16 | this.toolWindowInfo = toolWindowInfo;
17 | this.containingScreen = containingScreen;
18 | this.threshold = threshold;
19 | }
20 |
21 | public ToolWindowInfo getToolWindowInfo() {
22 | return this.toolWindowInfo;
23 | }
24 |
25 | public Rectangle getContainingScreen() {
26 | return this.containingScreen;
27 | }
28 |
29 | public Rectangle getBounds() {
30 | return this.toolWindowInfo.getBounds();
31 | }
32 |
33 | public Rectangle getLeftDockingBounds() {
34 | return new Rectangle(
35 | (int) Math.max(0, this.toolWindowInfo.getBounds().getX() - this.threshold),
36 | (int) this.toolWindowInfo.getBounds().getY(),
37 | this.threshold * 2,
38 | (int) this.toolWindowInfo.getBounds().getHeight());
39 | }
40 |
41 | public Rectangle getTopDockingBounds() {
42 | return new Rectangle(
43 | (int) this.toolWindowInfo.getBounds().getX(),
44 | (int) Math.max(0, this.toolWindowInfo.getBounds().getY() - this.threshold),
45 | (int) this.toolWindowInfo.getBounds().getWidth(),
46 | this.threshold * 2);
47 | }
48 |
49 | public Rectangle getRightDockingBounds() {
50 | return new Rectangle(
51 | (int) (this.toolWindowInfo.getBounds().getMaxX() - this.threshold),
52 | (int) this.toolWindowInfo.getBounds().getY(),
53 | (int) Math.min(this.threshold * 2, this.threshold + this.containingScreen.getMaxX() - this.toolWindowInfo.getBounds().getMaxX()),
54 | (int) this.toolWindowInfo.getBounds().getHeight());
55 | }
56 |
57 | public Rectangle getBottomDockingBounds() {
58 | return new Rectangle(
59 | (int) this.toolWindowInfo.getBounds().getX(),
60 | (int) this.toolWindowInfo.getBounds().getMaxY() - this.threshold,
61 | (int) this.toolWindowInfo.getBounds().getWidth(),
62 | (int) Math.min(this.threshold * 2, this.threshold + this.containingScreen.getMaxY() - this.toolWindowInfo.getBounds().getMaxY()));
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/layout/store/smartdock/dockers/ScreenBorderDocker.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.layout.store.smartdock.dockers;
2 |
3 | import com.layoutmanager.layout.store.smartdock.ToolWindowDocking;
4 | import java.awt.Rectangle;
5 | import java.util.function.BiFunction;
6 | import java.util.function.Function;
7 |
8 | public class ScreenBorderDocker {
9 | public void dock(ToolWindowDocking[] floatedOrWindowsToolWindows, int threshold) {
10 | this.pinLeft(floatedOrWindowsToolWindows, threshold);
11 | this.pinTop(floatedOrWindowsToolWindows, threshold);
12 | this.pinRight(floatedOrWindowsToolWindows, threshold);
13 | this.pinBottom(floatedOrWindowsToolWindows, threshold);
14 | }
15 |
16 | private void pinLeft(ToolWindowDocking[] floatedOrWindowsToolWindows, int threshold) {
17 | this.pinToScreenBorder(
18 | floatedOrWindowsToolWindows,
19 | threshold,
20 | toolWindowDocking -> (int)(toolWindowDocking.getBounds().getX() - toolWindowDocking.getContainingScreen().getX()),
21 | (toolWindowDocking, difference) -> new Rectangle(
22 | (int)toolWindowDocking.getContainingScreen().getX(),
23 | (int)toolWindowDocking.getBounds().getY(),
24 | (int)(toolWindowDocking.getBounds().getWidth() + difference),
25 | (int)toolWindowDocking.getBounds().getHeight()));
26 | }
27 |
28 | private void pinTop(ToolWindowDocking[] floatedOrWindowsToolWindows, int threshold) {
29 | this.pinToScreenBorder(
30 | floatedOrWindowsToolWindows,
31 | threshold,
32 | x -> (int)(x.getBounds().getY() - x.getContainingScreen().getY()),
33 | (toolWindowDocking, difference) -> new Rectangle(
34 | (int)toolWindowDocking.getBounds().getX(),
35 | (int)toolWindowDocking.getContainingScreen().getY(),
36 | (int)toolWindowDocking.getBounds().getWidth(),
37 | (int)(toolWindowDocking.getBounds().getHeight() + 5)));
38 | }
39 |
40 | private void pinRight(ToolWindowDocking[] floatedOrWindowsToolWindows, int threshold) {
41 | this.pinToScreenBorder(
42 | floatedOrWindowsToolWindows,
43 | threshold,
44 | toolWindowDocking -> (int)(toolWindowDocking.getContainingScreen().getMaxX() - toolWindowDocking.getBounds().getMaxX()),
45 | (toolWindowDocking, difference) -> new Rectangle(
46 | (int)toolWindowDocking.getBounds().getX(),
47 | (int)toolWindowDocking.getBounds().getY(),
48 | (int)(toolWindowDocking.getBounds().getWidth() + difference),
49 | (int)toolWindowDocking.getBounds().getHeight()));
50 | }
51 |
52 | private void pinBottom(ToolWindowDocking[] floatedOrWindowsToolWindows, int threshold) {
53 | this.pinToScreenBorder(
54 | floatedOrWindowsToolWindows,
55 | threshold,
56 | toolWindowDocking -> (int)(toolWindowDocking.getBounds().getY() - toolWindowDocking.getContainingScreen().getY()),
57 | (toolWindowDocking, difference) -> new Rectangle(
58 | (int)toolWindowDocking.getBounds().getX(),
59 | (int)toolWindowDocking.getBounds().getY(),
60 | (int)toolWindowDocking.getBounds().getWidth(),
61 | (int)(toolWindowDocking.getBounds().getHeight() + difference)));
62 | }
63 |
64 | private void pinToScreenBorder(
65 | ToolWindowDocking[] toolWindowDockings,
66 | int threshold,
67 | Function getDifference,
68 | BiFunction calculateBounds) {
69 |
70 | for (ToolWindowDocking toolWindowDocking : toolWindowDockings) {
71 | Integer difference = getDifference.apply(toolWindowDocking);
72 | if (difference != 0 && difference < threshold) {
73 | toolWindowDocking.getToolWindowInfo().setBounds(calculateBounds.apply(toolWindowDocking, difference));
74 | }
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/layout/store/smartdock/dockers/ToolWindowDocker.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.layout.store.smartdock.dockers;
2 |
3 | import com.layoutmanager.layout.store.smartdock.ToolWindowDocking;
4 | import java.awt.Rectangle;
5 | import java.util.Arrays;
6 | import java.util.Comparator;
7 | import java.util.function.BiFunction;
8 | import java.util.function.BiPredicate;
9 |
10 | public class ToolWindowDocker {
11 | public void dock(ToolWindowDocking[] floatedOrWindowsToolWindows) {
12 | this.dockLeft(floatedOrWindowsToolWindows);
13 | this.dockTop(floatedOrWindowsToolWindows);
14 | this.dockRight(floatedOrWindowsToolWindows);
15 | this.dockBottom(floatedOrWindowsToolWindows);
16 | }
17 |
18 | private void dockLeft(ToolWindowDocking[] floatedOrWindowsToolWindows) {
19 | this.dockToolWindow(
20 | floatedOrWindowsToolWindows,
21 | (x, y) -> (int) (x.getBounds().getX() - y.getBounds().getX()),
22 | (x, y) -> x.getRightDockingBounds().intersects(y.getLeftDockingBounds()),
23 | Comparator.comparingInt(x -> (int) x.getBounds().getY()),
24 | (toolWindow, boundsToDock) -> {
25 | int x = (int) toolWindow.getBounds().getX();
26 | int newX = (int) boundsToDock.getMaxX();
27 | return new Rectangle(
28 | newX,
29 | (int) toolWindow.getBounds().getY(),
30 | (int) (toolWindow.getBounds().getWidth() + (x - newX)),
31 | (int) toolWindow.getBounds().getHeight());
32 | });
33 | }
34 |
35 | private void dockTop(ToolWindowDocking[] floatedOrWindowsToolWindows) {
36 | this.dockToolWindow(
37 | floatedOrWindowsToolWindows,
38 | (x, y) -> (int) (x.getBounds().getY() - y.getBounds().getY()),
39 | (x, y) -> x.getBottomDockingBounds().intersects(y.getTopDockingBounds()),
40 | Comparator.comparingInt(x -> (int) x.getBounds().getX()),
41 | (toolWindow, boundsToDock) -> {
42 | int y = (int) toolWindow.getBounds().getY();
43 | int newY = (int) boundsToDock.getMaxY();
44 | return new Rectangle(
45 | (int) toolWindow.getBounds().getX(),
46 | newY,
47 | (int) toolWindow.getBounds().getWidth(),
48 | (int) (toolWindow.getBounds().getHeight() + (y - newY)));
49 | });
50 | }
51 |
52 | private void dockRight(ToolWindowDocking[] floatedOrWindowsToolWindows) {
53 | this.dockToolWindow(
54 | floatedOrWindowsToolWindows,
55 | (x, y) -> (int) (x.getBounds().getMaxX() - y.getBounds().getMaxX()),
56 | (x, y) -> x.getLeftDockingBounds().intersects(y.getRightDockingBounds()),
57 | Comparator.comparingInt(x -> (int) x.getBounds().getY()),
58 | (toolWindow, boundsToDock) -> new Rectangle(
59 | (int) toolWindow.getBounds().getX(),
60 | (int) toolWindow.getBounds().getY(),
61 | (int) (boundsToDock.getX() - toolWindow.getBounds().getX()),
62 | (int) toolWindow.getBounds().getHeight()));
63 | }
64 |
65 | private void dockBottom(ToolWindowDocking[] floatedOrWindowsToolWindows) {
66 | this.dockToolWindow(
67 | floatedOrWindowsToolWindows,
68 | (x, y) -> (int) (x.getBounds().getMaxY() - y.getBounds().getMaxY()),
69 | (x, y) -> x.getTopDockingBounds().intersects(y.getBottomDockingBounds()),
70 | Comparator.comparingInt(x -> (int) x.getBounds().getY()),
71 | (toolWindow, boundsToDock) -> new Rectangle(
72 | (int) toolWindow.getBounds().getX(),
73 | (int) toolWindow.getBounds().getY(),
74 | (int) toolWindow.getBounds().getWidth(),
75 | (int) (boundsToDock.getY() - toolWindow.getBounds().getY())));
76 | }
77 |
78 | private void dockToolWindow(
79 | ToolWindowDocking[] floatedOrWindowsToolWindows,
80 | Comparator super ToolWindowDocking> sortForProcessing,
81 | BiPredicate isIntersecting,
82 | Comparator super ToolWindowDocking> sortForClosestToDock,
83 | BiFunction calculateNewBounds) {
84 |
85 | ToolWindowDocking[] sortedByLeftPosition = Arrays.stream(floatedOrWindowsToolWindows)
86 | .sorted(sortForProcessing)
87 | .skip(1)
88 | .toArray(ToolWindowDocking[]::new);
89 |
90 | for (ToolWindowDocking toolWindowDocking : sortedByLeftPosition) {
91 | ToolWindowDocking dockingTarget = Arrays.stream(floatedOrWindowsToolWindows)
92 | .filter(x ->
93 | !x.getToolWindowInfo().getId().equals(toolWindowDocking.getToolWindowInfo().getId()) &&
94 | isIntersecting.test(x, toolWindowDocking))
95 | .min(sortForClosestToDock)
96 | .orElse(null);
97 |
98 | if (dockingTarget != null) {
99 | Rectangle newBounds = calculateNewBounds.apply(toolWindowDocking, dockingTarget.getBounds());
100 | toolWindowDocking.getToolWindowInfo().setBounds(newBounds);
101 | }
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/layout/store/smartdock/dockers/ToolWindowToScreenShrinker.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.layout.store.smartdock.dockers;
2 |
3 | import com.layoutmanager.layout.store.smartdock.ToolWindowDocking;
4 | import java.awt.Rectangle;
5 |
6 | public class ToolWindowToScreenShrinker {
7 | public void shrink(ToolWindowDocking[] floatedOrWindowsToolWindowDockings) {
8 | for (ToolWindowDocking toolWindowDocking : floatedOrWindowsToolWindowDockings) {
9 | Rectangle containingScreen = toolWindowDocking.getContainingScreen();
10 | Rectangle toolWindowBounds = toolWindowDocking.getBounds();
11 |
12 | if (!containingScreen.contains(toolWindowBounds)) {
13 | double x = Math.max(toolWindowBounds.getX(), containingScreen.getX());
14 | double y = Math.max(toolWindowBounds.getY(), containingScreen.getY());
15 | Rectangle newToolWindowBounds = new Rectangle(
16 | (int)x,
17 | (int)y,
18 | (int)Math.min(toolWindowBounds.getWidth(), containingScreen.getMaxX() - x),
19 | (int)Math.min(toolWindowBounds.getHeight(), containingScreen.getMaxY() - y));
20 |
21 | toolWindowDocking.getToolWindowInfo().setBounds(newToolWindowBounds);
22 | }
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/layout/store/validation/LayoutValidationHelper.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.layout.store.validation;
2 |
3 | import com.intellij.openapi.wm.ToolWindowType;
4 | import com.intellij.openapi.wm.WindowManager;
5 | import com.layoutmanager.persistence.Layout;
6 | import com.layoutmanager.persistence.ToolWindowInfo;
7 | import java.awt.Rectangle;
8 | import java.util.stream.Stream;
9 |
10 | public class LayoutValidationHelper {
11 | public static ToolWindowInfo[] retrieveToolWindowsOutsideOfScreen(Layout layout) {
12 | return Stream
13 | .of(layout.getToolWindows())
14 | .filter(x -> x.isVisible() && isWindowType(x) && !isValid(x))
15 | .toArray(ToolWindowInfo[]::new);
16 | }
17 |
18 | private static boolean isWindowType(ToolWindowInfo toolWindowInfo) {
19 | return toolWindowInfo.getType() == ToolWindowType.FLOATING || toolWindowInfo.getType() == ToolWindowType.WINDOWED;
20 | }
21 |
22 | private static boolean isValid(ToolWindowInfo toolWindowInfo) {
23 | Rectangle bounds = toolWindowInfo.getBounds();
24 | return WindowManager.getInstance().isInsideScreenBounds(bounds.x, bounds.y, bounds.width);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/localization/MessagesHelper.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.localization;
2 |
3 | import com.intellij.AbstractBundle;
4 | import java.lang.ref.SoftReference;
5 | import java.util.Locale;
6 | import java.util.MissingResourceException;
7 | import java.util.ResourceBundle;
8 | import org.jetbrains.annotations.NonNls;
9 | import org.jetbrains.annotations.NotNull;
10 | import org.jetbrains.annotations.PropertyKey;
11 |
12 | public class MessagesHelper {
13 | @NonNls
14 | public static final String BUNDLE_NAME = "com.layoutmanager.ui.messages";
15 |
16 | private static SoftReference thisBundle;
17 |
18 | private static ResourceBundle getBundle() {
19 | ResourceBundle bundle = thisBundle != null ? thisBundle.get() : null;
20 | if (bundle == null) {
21 | try {
22 | bundle = ResourceBundle.getBundle(BUNDLE_NAME);
23 | } catch (MissingResourceException e) {
24 | bundle = ResourceBundle.getBundle(BUNDLE_NAME, Locale.ENGLISH);
25 | }
26 | thisBundle = new SoftReference<>(bundle);
27 | }
28 | return bundle;
29 | }
30 |
31 | public static String message(@NotNull @PropertyKey(resourceBundle = BUNDLE_NAME) String key,
32 | @NotNull Object... params) {
33 | return AbstractBundle.message(getBundle(), key, params);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/migration/LayoutMigratorService.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.migration;
2 |
3 | import com.layoutmanager.persistence.Layout;
4 | import com.layoutmanager.persistence.LayoutConfig;
5 | import java.util.Arrays;
6 |
7 | public class LayoutMigratorService {
8 |
9 | public void migrate() {
10 | LayoutConfig layoutConfig = LayoutConfig.getInstance();
11 | this.addMissingIds(layoutConfig);
12 | }
13 |
14 | private void addMissingIds(LayoutConfig layoutConfig) {
15 | for (int index = 1; index < layoutConfig.getLayoutCount(); index++) {
16 |
17 | Layout layout = layoutConfig.getLayout(index);
18 | if (this.isTaken(layoutConfig, layout.getId())) {
19 | layout.setId(layoutConfig.getNextAvailableId());
20 | }
21 | }
22 | }
23 |
24 | private boolean isTaken(LayoutConfig config, int id) {
25 | return Arrays
26 | .stream(config.getLayouts())
27 | .filter(x -> x.getId() == id)
28 | .count() > 1;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/persistence/Layout.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.persistence;
2 |
3 |
4 | import java.util.Arrays;
5 | import java.util.Objects;
6 |
7 | public class Layout {
8 | private int id;
9 | private String name;
10 | private ToolWindowInfo[] toolWindows;
11 | private int editorTabPlacement;
12 | private boolean wideScreenSupport;
13 |
14 | @SuppressWarnings({"unused", "Used for serialization."})
15 | public Layout() {
16 | this.id = 0;
17 | this.name = "";
18 | this.toolWindows = new ToolWindowInfo[0];
19 | this.editorTabPlacement = -1;
20 | }
21 |
22 | public Layout(
23 | int id,
24 | String name,
25 | ToolWindowInfo[] toolWindows,
26 | int editorTabPlacement,
27 | boolean wideScreenSupport) {
28 | this.id = id;
29 | this.name = name;
30 | this.toolWindows = toolWindows;
31 | this.editorTabPlacement = editorTabPlacement;
32 | this.wideScreenSupport = wideScreenSupport;
33 | }
34 |
35 | public int getId() {
36 | return this.id;
37 | }
38 |
39 | public void setId(int id) {
40 | this.id = id;
41 | }
42 |
43 | public String getName() {
44 | return this.name;
45 | }
46 |
47 | public void setName(String name) {
48 | this.name = name;
49 | }
50 |
51 | public int getEditorPlacement() {
52 | return this.editorTabPlacement;
53 | }
54 |
55 | public void setEditorPlacement(int editorTabPlacement) {
56 | this.editorTabPlacement = editorTabPlacement;
57 | }
58 |
59 | public boolean getWideScreenSupport() {
60 | return this.wideScreenSupport;
61 | }
62 |
63 | public void setWideScreenSupport(boolean wideScreenSupport) {
64 | this.wideScreenSupport = wideScreenSupport;
65 | }
66 |
67 | public ToolWindowInfo[] getToolWindows() {
68 | return this.toolWindows;
69 | }
70 |
71 | public void setToolWindows(ToolWindowInfo[] toolWindows) {
72 | this.toolWindows = toolWindows;
73 | }
74 |
75 | @Override
76 | public boolean equals(Object other) {
77 | if (this == other) {
78 | return true;
79 | }
80 |
81 | if (other == null || this.getClass() != other.getClass()) {
82 | return false;
83 | }
84 |
85 | Layout that = (Layout) other;
86 | return this.editorTabPlacement == that.editorTabPlacement &&
87 | this.name.equals(that.name) &&
88 | Arrays.equals(this.toolWindows, that.toolWindows);
89 | }
90 |
91 | @Override
92 | public int hashCode() {
93 | int result = Objects.hash(this.name, this.editorTabPlacement);
94 | result = 31 * result + Arrays.hashCode(this.toolWindows);
95 | return result;
96 | }
97 | }
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/persistence/LayoutConfig.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.persistence;
2 |
3 | import com.intellij.openapi.application.ApplicationManager;
4 | import com.intellij.openapi.components.PersistentStateComponent;
5 | import com.intellij.openapi.components.State;
6 | import com.intellij.openapi.components.Storage;
7 | import com.intellij.util.xmlb.XmlSerializerUtil;
8 | import java.util.ArrayList;
9 | import java.util.Arrays;
10 | import java.util.List;
11 | import org.jetbrains.annotations.NotNull;
12 | import org.jetbrains.annotations.Nullable;
13 |
14 | @State(
15 | name = "Layout",
16 | storages = {
17 | @Storage("layout.xml")
18 | }
19 | )
20 | public class LayoutConfig implements PersistentStateComponent {
21 | private List layouts = new ArrayList<>();
22 | private LayoutSettings settings = new LayoutSettings();
23 |
24 | @Nullable
25 | @Override
26 | public LayoutConfig getState() {
27 | return this;
28 | }
29 |
30 | @Override
31 | public void loadState(@NotNull LayoutConfig layoutConfig) {
32 | XmlSerializerUtil.copyBean(layoutConfig, this);
33 | }
34 |
35 | @SuppressWarnings({"unused", "Used for serialization."})
36 | public Layout getLayout(int number) {
37 | return this.layouts.get(number);
38 | }
39 |
40 | @SuppressWarnings({"unused", "Used for serialization."})
41 | public Layout[] getLayouts() {
42 | return this.layouts.toArray(Layout[]::new);
43 | }
44 |
45 | @SuppressWarnings({"unused", "Used for serialization."})
46 | public void setLayouts(Layout[] layouts) {
47 | this.layouts = new ArrayList<>(Arrays.asList(layouts));
48 | }
49 |
50 | public LayoutSettings getSettings() {
51 | return this.settings;
52 | }
53 |
54 | public void setSettings(LayoutSettings settings) {
55 | this.settings = settings;
56 | }
57 |
58 | public int getLayoutCount() {
59 | return this.layouts.size();
60 | }
61 |
62 | @SuppressWarnings({"unused", "Used for serialization."})
63 | public void setLayout(int number, Layout layout) {
64 | this.layouts.set(number, layout);
65 | }
66 |
67 | public void addLayout(Layout layout) {
68 | this.layouts.add(layout);
69 | }
70 |
71 | public void removeLayout(Layout layout) {
72 | this.layouts.remove(layout);
73 | }
74 |
75 | public int getNextAvailableId() {
76 | for (int id = 0; id < Integer.MAX_VALUE; id++) {
77 | int finalId = id;
78 | if (this.layouts.stream().noneMatch(x -> x.getId() == finalId)) {
79 | return id;
80 | }
81 | }
82 |
83 | return Integer.MAX_VALUE;
84 | }
85 |
86 | public static LayoutConfig getInstance() {
87 | return ApplicationManager
88 | .getApplication()
89 | .getService(LayoutConfig.class);
90 | }
91 | }
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/persistence/LayoutSettings.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.persistence;
2 |
3 | public class LayoutSettings {
4 | private boolean useSmartDock = true;
5 |
6 | public boolean getUseSmartDock() {
7 | return this.useSmartDock;
8 | }
9 |
10 | public void setUseSmartDock(boolean useSmartDock) {
11 | this.useSmartDock = useSmartDock;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/persistence/ToolWindowInfo.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.persistence;
2 |
3 | import com.intellij.openapi.wm.ToolWindowType;
4 | import java.awt.Rectangle;
5 | import java.util.Objects;
6 |
7 | public class ToolWindowInfo {
8 | private String id;
9 | private ToolWindowType type;
10 | private String anchor;
11 | private Rectangle bounds;
12 | private boolean isVisible;
13 | private boolean isToolWindow;
14 |
15 | @SuppressWarnings({"unused", "Used for serialization."})
16 | public ToolWindowInfo() {
17 | this.id = null;
18 | this.type = null;
19 | this.anchor = "top";
20 | this.bounds = null;
21 | this.isVisible = false;
22 | this.isToolWindow = false;
23 | }
24 |
25 | public ToolWindowInfo(
26 | String id,
27 | ToolWindowType type,
28 | String anchor,
29 | Rectangle bounds,
30 | boolean isVisible,
31 | boolean isToolWindow) {
32 | this.id = id;
33 | this.type = type;
34 | this.anchor = anchor;
35 | this.bounds = bounds;
36 | this.isVisible = isVisible;
37 | this.isToolWindow = isToolWindow;
38 | }
39 |
40 | public String getId() {
41 | return this.id;
42 | }
43 |
44 | public ToolWindowType getType() {
45 | return this.type;
46 | }
47 |
48 | public String getAnchor() {
49 | return this.anchor;
50 | }
51 |
52 | public Rectangle getBounds() {
53 | return this.bounds;
54 | }
55 |
56 | public boolean isVisible() {
57 | return this.isVisible;
58 | }
59 |
60 | public boolean isToolWindow() {
61 | return this.isToolWindow;
62 | }
63 |
64 | @SuppressWarnings({"unused", "Used for serialization."})
65 | public void setId(String id) {
66 | this.id = id;
67 | }
68 |
69 | @SuppressWarnings({"unused", "Used for serialization."})
70 | public void setType(ToolWindowType type) {
71 | this.type = type;
72 | }
73 |
74 | @SuppressWarnings({"unused", "Used for serialization."})
75 | public void setAnchor(String anchor) {
76 | this.anchor = anchor;
77 | }
78 |
79 | @SuppressWarnings({"unused", "Used for serialization."})
80 | public void setBounds(Rectangle bounds) {
81 | this.bounds = bounds;
82 | }
83 |
84 | @SuppressWarnings({"unused", "Used for serialization."})
85 | public void setVisible(boolean visible) {
86 | this.isVisible = visible;
87 | }
88 |
89 | @SuppressWarnings({"unused", "Used for serialization."})
90 | public void setIsToolWindow(boolean isToolWindow) {
91 | this.isToolWindow = isToolWindow;
92 | }
93 |
94 | @Override
95 | public boolean equals(Object other) {
96 | if (this == other) {
97 | return true;
98 | }
99 |
100 | if (other == null || this.getClass() != other.getClass()) {
101 | return false;
102 | }
103 |
104 | ToolWindowInfo that = (ToolWindowInfo) other;
105 | return this.isVisible == that.isVisible &&
106 | this.isToolWindow == that.isToolWindow &&
107 | this.id.equals(that.id) &&
108 | this.type == that.type &&
109 | this.anchor.equals(that.anchor) &&
110 | this.bounds.equals(that.bounds);
111 | }
112 |
113 | @Override
114 | public int hashCode() {
115 | return Objects.hash(this.id, this.type, this.anchor, this.bounds, this.isVisible, this.isToolWindow);
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/startup/PluginBootstrapper.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.startup;
2 |
3 | import com.intellij.ide.AppLifecycleListener;
4 | import com.intellij.openapi.application.ApplicationManager;
5 | import com.layoutmanager.cleanup.EmptyLayoutRemoverService;
6 | import com.layoutmanager.migration.LayoutMigratorService;
7 | import com.layoutmanager.ui.menu.WindowMenuService;
8 |
9 | public class PluginBootstrapper implements AppLifecycleListener {
10 |
11 | @Override
12 | public void welcomeScreenDisplayed() {
13 | EmptyLayoutRemoverService emptyLayoutRemoverService = ApplicationManager
14 | .getApplication()
15 | .getService(EmptyLayoutRemoverService.class);
16 | emptyLayoutRemoverService.execute();
17 |
18 | LayoutMigratorService layoutMigratorService = ApplicationManager
19 | .getApplication()
20 | .getService(LayoutMigratorService.class);
21 | layoutMigratorService.migrate();
22 |
23 | WindowMenuService windowMenuService = ApplicationManager
24 | .getApplication()
25 | .getService(WindowMenuService.class);
26 | windowMenuService.create();
27 | }
28 | }
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/ui/action/ActionNameGenerator.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.ui.action;
2 |
3 | import static com.layoutmanager.ui.action.ActionRegistry.ACTION_PREFIX;
4 |
5 | import com.layoutmanager.layout.LayoutAction;
6 | import com.layoutmanager.layout.restore.RestoreLayoutAction;
7 | import com.layoutmanager.persistence.Layout;
8 |
9 | public class ActionNameGenerator {
10 | public static String getActionNameForLayoutAction(LayoutAction layoutAction) {
11 | String className = layoutAction.getClass().getSimpleName();
12 | String typeName = className.substring(0, className.length() - "LayoutAction".length());
13 |
14 | return getActionName(layoutAction.getLayout().getId(), typeName);
15 | }
16 |
17 | public static String getActionNameForLayout(Layout layout) {
18 | String className = RestoreLayoutAction.class.getSimpleName();
19 | String typeName = className.substring(0, className.length() - "LayoutAction".length());
20 |
21 | return getActionName(layout.getId(), typeName);
22 | }
23 |
24 | private static String getActionName(int id, String typeName) {
25 | return ACTION_PREFIX + typeName + "." + id;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/ui/action/ActionRegistry.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.ui.action;
2 |
3 | import com.intellij.openapi.actionSystem.ActionManager;
4 | import com.intellij.openapi.actionSystem.AnAction;
5 | import com.intellij.openapi.actionSystem.Shortcut;
6 | import com.intellij.openapi.extensions.PluginId;
7 | import com.intellij.openapi.keymap.Keymap;
8 | import com.intellij.openapi.keymap.KeymapManager;
9 | import com.layoutmanager.layout.LayoutAction;
10 | import java.util.Objects;
11 |
12 | public class ActionRegistry {
13 | private static final String PLUGIN_ID = "com.layoutmanager";
14 | public static final String ACTION_PREFIX = "WindowLayoutManager.";
15 |
16 | public void register(LayoutAction layoutAction) {
17 | PluginId pluginId = this.getPluginId();
18 | String actionName = ActionNameGenerator.getActionNameForLayoutAction(layoutAction);
19 |
20 | ActionManager.getInstance().registerAction(actionName, layoutAction, pluginId);
21 | }
22 |
23 | public void register(AnAction action, String name) {
24 | PluginId pluginId = this.getPluginId();
25 | String actionName = ACTION_PREFIX + name;
26 |
27 | ActionManager
28 | .getInstance()
29 | .registerAction(actionName, action, pluginId);
30 | }
31 |
32 | public void unregister(LayoutAction layoutAction) {
33 | String actionName = ActionNameGenerator.getActionNameForLayoutAction(layoutAction);
34 | ActionManager.getInstance().unregisterAction(actionName);
35 | }
36 |
37 | public void rename(LayoutAction layoutAction) {
38 | ActionManager actionManager = ActionManager.getInstance();
39 | Keymap activeKeymap = KeymapManager.getInstance().getActiveKeymap();
40 | String previousActionId = actionManager.getId(layoutAction);
41 | Shortcut[] shortcuts = activeKeymap.getShortcuts(previousActionId);
42 |
43 | if (shortcuts.length > 0) {
44 | String newActionName = ActionNameGenerator.getActionNameForLayoutAction(layoutAction);
45 | this.reRegisterAction(actionManager, layoutAction, previousActionId, newActionName);
46 | activeKeymap.removeAllActionShortcuts(Objects.requireNonNull(previousActionId));
47 |
48 | for (Shortcut shortcut : shortcuts) {
49 | activeKeymap.addShortcut(newActionName, shortcut);
50 | }
51 | }
52 | }
53 |
54 | private void reRegisterAction(ActionManager actionManager, LayoutAction layoutAction, String previousActionId, String newActionName) {
55 | actionManager.unregisterAction(previousActionId);
56 | actionManager.registerAction(newActionName, layoutAction, this.getPluginId());
57 | }
58 |
59 |
60 | private PluginId getPluginId() {
61 | return PluginId.getId(PLUGIN_ID);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/ui/dialogs/LayoutNameDialog.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.ui.dialogs;
2 |
3 | import com.intellij.icons.AllIcons;
4 | import com.intellij.openapi.ui.Messages;
5 | import com.layoutmanager.localization.MessagesHelper;
6 |
7 | public class LayoutNameDialog {
8 |
9 | private final LayoutNameValidator layoutNameValidator;
10 |
11 | public LayoutNameDialog(LayoutNameValidator layoutNameValidator) {
12 |
13 | this.layoutNameValidator = layoutNameValidator;
14 | }
15 |
16 | public String show(String defaultName) {
17 | String name;
18 | do {
19 | name = Messages.showInputDialog(
20 | MessagesHelper.message("StoreLayout.Dialog.Title"),
21 | MessagesHelper.message("StoreLayout.Dialog.Content"),
22 | AllIcons.Actions.Edit,
23 | defaultName,
24 | null);
25 | } while (name != null && !this.layoutNameValidator.isValid(name));
26 |
27 | return name;
28 | }
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/ui/dialogs/LayoutNameValidator.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.ui.dialogs;
2 |
3 | public class LayoutNameValidator {
4 | public Boolean isValid(String name) {
5 | return name != null && !this.isBlank(name);
6 | }
7 |
8 | private boolean isBlank(String name) {
9 | return name.trim().isEmpty();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/ui/helpers/BalloonNotificationHelper.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.ui.helpers;
2 |
3 | import com.intellij.notification.Notification;
4 | import com.intellij.notification.NotificationGroupManager;
5 | import com.intellij.notification.NotificationType;
6 | import com.intellij.openapi.project.Project;
7 | import com.intellij.openapi.project.ProjectManager;
8 | import java.util.Arrays;
9 | import java.util.concurrent.Executors;
10 | import java.util.concurrent.ScheduledExecutorService;
11 | import java.util.concurrent.TimeUnit;
12 |
13 | public class BalloonNotificationHelper {
14 | private static final int NOTIFICATION_DISPLAY_TIME_IN_SECONDS = 6;
15 |
16 | public static void info(String title, String content) {
17 | Notification notification = NotificationGroupManager.getInstance()
18 | .getNotificationGroup("WindowLayoutManager")
19 | .createNotification(title, content, NotificationType.INFORMATION);
20 | notify(notification);
21 | }
22 |
23 | public static void warning(String title, String content) {
24 | Notification notification = NotificationGroupManager.getInstance()
25 | .getNotificationGroup("WindowLayoutManager")
26 | .createNotification(title, content, NotificationType.WARNING);
27 | notify(notification);
28 | }
29 |
30 | private static void notify(Notification notification) {
31 | Project currentProject = getCurrentProject();
32 | notification.notify(currentProject);
33 | hideAfterTimeSpan(notification);
34 | }
35 |
36 | private static void hideAfterTimeSpan(Notification notification) {
37 | ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
38 | scheduledExecutorService.schedule(
39 | notification::expire,
40 | NOTIFICATION_DISPLAY_TIME_IN_SECONDS,
41 | TimeUnit.SECONDS);
42 | }
43 |
44 | private static Project getCurrentProject() {
45 | Project[] openedProjects = ProjectManager.getInstance().getOpenProjects();
46 | return Arrays.stream(openedProjects)
47 | .findFirst()
48 | .orElse(null);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/ui/helpers/ComponentNotificationHelper.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.ui.helpers;
2 |
3 | import com.intellij.openapi.ui.MessageType;
4 | import com.intellij.openapi.ui.popup.Balloon;
5 | import com.intellij.openapi.ui.popup.JBPopupFactory;
6 | import com.intellij.ui.awt.RelativePoint;
7 | import javax.swing.JComponent;
8 |
9 | public class ComponentNotificationHelper {
10 |
11 | public static void info(JComponent component, String message) {
12 | show(component, message, MessageType.INFO);
13 | }
14 |
15 | public static void error(JComponent component, String message) {
16 | show(component, message, MessageType.ERROR);
17 | }
18 |
19 | private static void show(JComponent component, String message, MessageType type) {
20 | JBPopupFactory.getInstance()
21 | .createHtmlTextBalloonBuilder(message, type, null)
22 | .setFadeoutTime(7500)
23 | .createBalloon()
24 | .show(
25 | RelativePoint.getCenterOf(component),
26 | Balloon.Position.above);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/ui/helpers/ScreenSizeHelper.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.ui.helpers;
2 |
3 | import com.layoutmanager.persistence.ToolWindowInfo;
4 | import java.awt.GraphicsDevice;
5 | import java.awt.GraphicsEnvironment;
6 | import java.awt.Insets;
7 | import java.awt.Rectangle;
8 | import java.awt.Toolkit;
9 | import java.util.Arrays;
10 | import java.util.Comparator;
11 |
12 | public class ScreenSizeHelper {
13 | public static Rectangle getContainingScreenBounds(ToolWindowInfo toolWindow) {
14 | return Arrays
15 | .stream(GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices())
16 | .map(ScreenSizeHelper::getScreenRectangle)
17 | .min(Comparator.comparingInt(x -> -getIntersectionSize(x, toolWindow.getBounds())))
18 | .orElse(new Rectangle());
19 | }
20 |
21 | private static Rectangle getScreenRectangle(GraphicsDevice device) {
22 | Rectangle bounds = device.getDefaultConfiguration().getBounds();
23 | Insets insets = Toolkit
24 | .getDefaultToolkit()
25 | .getScreenInsets(device.getDefaultConfiguration());
26 |
27 | int x = bounds.x + insets.left;
28 | int y = bounds.y + insets.top;
29 | int width = bounds.width - (insets.left + insets.right);
30 | int height = bounds.height - (insets.top + insets.bottom);
31 |
32 | return new Rectangle(x, y, width, height);
33 | }
34 |
35 | private static int getIntersectionSize(Rectangle screen, Rectangle window) {
36 | return (int)(Math.max(0, Math.min(screen.getMaxX(), window.getMaxX()) - Math.max(screen.getX(), window.getX())) *
37 | Math.max(0, Math.min(screen.getMaxY(), window.getMaxY()) - Math.max(screen.getY(), window.getY())));
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/ui/helpers/ToolWindowHelper.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.ui.helpers;
2 |
3 | import com.intellij.openapi.wm.ToolWindowAnchor;
4 | import com.intellij.openapi.wm.ToolWindowType;
5 | import com.intellij.openapi.wm.ex.ToolWindowEx;
6 | import java.awt.Rectangle;
7 | import java.awt.Window;
8 | import java.awt.event.HierarchyEvent;
9 | import java.awt.event.HierarchyListener;
10 | import javax.swing.SwingUtilities;
11 | import org.jetbrains.annotations.Nullable;
12 |
13 | public class ToolWindowHelper {
14 | public static Rectangle getBounds(ToolWindowEx toolWindow) {
15 | if (toolWindow.isVisible()) {
16 | if (toolWindow.getType() == ToolWindowType.FLOATING || toolWindow.getType() == ToolWindowType.WINDOWED) {
17 | Window window = getWindow(toolWindow);
18 | return window != null ? window.getBounds() : new Rectangle(100, 100);
19 | } else {
20 | return toolWindow
21 | .getComponent()
22 | .getBounds();
23 | }
24 | }
25 | return new Rectangle(0, 0, 0, 0);
26 | }
27 |
28 | public static void setBounds(ToolWindowEx toolWindow, Rectangle bounds) {
29 | if (toolWindow.getType() == ToolWindowType.FLOATING || toolWindow.getType() == ToolWindowType.WINDOWED) {
30 | setWindowBounds(toolWindow, bounds);
31 | } else {
32 | Rectangle currentBounds = toolWindow
33 | .getComponent()
34 | .getBounds();
35 | if (toolWindow.getAnchor() == ToolWindowAnchor.TOP || toolWindow.getAnchor() == ToolWindowAnchor.BOTTOM) {
36 | toolWindow.stretchHeight(bounds.height - currentBounds.height);
37 | } else {
38 | toolWindow.stretchWidth(bounds.width - currentBounds.width);
39 | }
40 | }
41 | }
42 |
43 | private static void setWindowBounds(ToolWindowEx toolWindow, Rectangle bounds) {
44 | SwingUtilities.invokeLater(() -> {
45 | Window window = getWindow(toolWindow);
46 | if (window != null) {
47 | window.setBounds(bounds);
48 | } else {
49 | toolWindow
50 | .getComponent()
51 | .addHierarchyListener(new HierarchyListener() {
52 |
53 | @Override
54 | public void hierarchyChanged(HierarchyEvent e) {
55 | if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) {
56 | Window window = getWindow(toolWindow);
57 | if (window != null) {
58 | window.setBounds(bounds);
59 | toolWindow
60 | .getComponent()
61 | .removeHierarchyListener(this);
62 | }
63 | }
64 | }
65 | });
66 | }
67 | });
68 | }
69 |
70 | @Nullable
71 | private static Window getWindow(ToolWindowEx toolWindow) {
72 | return SwingUtilities.getWindowAncestor(toolWindow.getComponent());
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/ui/icons/Icons.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.ui.icons;
2 |
3 | import com.intellij.openapi.util.IconLoader;
4 | import javax.swing.Icon;
5 | import org.jetbrains.annotations.NotNull;
6 |
7 |
8 | public class Icons {
9 | public static final class Menu {
10 | @NotNull
11 | public static final Icon CreateNewLayout = IconLoader.getIcon("com/layoutmanager/ui/icons/NewLayout.svg", Icons.class);
12 |
13 | @NotNull
14 | public static final Icon OverwriteLayout = IconLoader.getIcon("com/layoutmanager/ui/icons/OverwriteLayout.svg", Icons.class);
15 |
16 | @NotNull
17 | public static final Icon RestoreLayout = IconLoader.getIcon("com/layoutmanager/ui/icons/RestoreLayout.svg", Icons.class);
18 |
19 | @NotNull
20 | public static final Icon DeleteLayout = IconLoader.getIcon("com/layoutmanager/ui/icons/DeleteLayout.svg", Icons.class);
21 | }
22 | }
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/ui/menu/WindowMenuService.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.ui.menu;
2 |
3 | import com.intellij.openapi.actionSystem.ActionManager;
4 | import com.intellij.openapi.actionSystem.AnAction;
5 | import com.intellij.openapi.actionSystem.DefaultActionGroup;
6 | import com.layoutmanager.layout.LayoutAction;
7 | import com.layoutmanager.layout.delete.DeleteLayoutAction;
8 | import com.layoutmanager.layout.restore.RestoreLayoutAction;
9 | import com.layoutmanager.layout.store.LayoutCreator;
10 | import com.layoutmanager.layout.store.create.NewLayoutAction;
11 | import com.layoutmanager.layout.store.overwrite.OverwriteLayoutAction;
12 | import com.layoutmanager.layout.store.smartdock.SmartDockerFactory;
13 | import com.layoutmanager.localization.MessagesHelper;
14 | import com.layoutmanager.persistence.Layout;
15 | import com.layoutmanager.persistence.LayoutConfig;
16 | import com.layoutmanager.ui.action.ActionRegistry;
17 | import com.layoutmanager.ui.dialogs.LayoutNameDialog;
18 | import com.layoutmanager.ui.dialogs.LayoutNameValidator;
19 | import java.util.Arrays;
20 |
21 | public class WindowMenuService {
22 | private final ActionRegistry actionRegistry;
23 |
24 | private DefaultActionGroup storeLayout;
25 | private DefaultActionGroup restoreLayout;
26 | private DefaultActionGroup deleteLayout;
27 |
28 | public WindowMenuService() {
29 | this.actionRegistry = new ActionRegistry();
30 | }
31 |
32 | public void create() {
33 | DefaultActionGroup windowMenu = (DefaultActionGroup) ActionManager
34 | .getInstance()
35 | .getAction("WindowMenu");
36 | this.createMainActions(windowMenu);
37 | this.createStoreRestoreActions();
38 | }
39 |
40 | public void addLayout(Layout layout) {
41 | LayoutCreator layoutCreator = this.createLayoutCreator();
42 |
43 | this.addStoreLayoutActionBeforeSeparator(layout, layoutCreator);
44 | this.addRestoreLayoutAction(layout);
45 | this.addDeleteLayout(layout);
46 | }
47 |
48 | public void deleteLayout(Layout layout) {
49 | this.deleteActionInGroup(layout, this.storeLayout, false);
50 | this.deleteActionInGroup(layout, this.restoreLayout, true);
51 | this.deleteActionInGroup(layout, this.deleteLayout, false);
52 | }
53 |
54 | public void renameLayout(Layout layout) {
55 | LayoutAction layoutAction = this.getActionForLayout(this.restoreLayout, layout);
56 | this.actionRegistry.rename(layoutAction);
57 | }
58 |
59 | private void deleteActionInGroup(Layout layout, DefaultActionGroup actionGroup, boolean unregister) {
60 | LayoutAction layoutAction = this.getActionForLayout(actionGroup, layout);
61 |
62 | if (unregister) {
63 | this.actionRegistry.unregister(layoutAction);
64 | }
65 |
66 | actionGroup.remove(layoutAction);
67 | }
68 |
69 | private LayoutAction getActionForLayout(DefaultActionGroup group, Layout layout) {
70 | return Arrays
71 | .stream(group.getChildActionsOrStubs())
72 | .filter(x ->
73 | x instanceof LayoutAction &&
74 | ((LayoutAction)x).getLayout() == layout)
75 | .map(x -> (LayoutAction)x)
76 | .findFirst()
77 | .orElse(null);
78 | }
79 |
80 | private LayoutCreator createLayoutCreator() {
81 | LayoutConfig config = LayoutConfig.getInstance();
82 | return new LayoutCreator(
83 | config.getSettings(),
84 | new SmartDockerFactory(),
85 | new LayoutNameDialog(new LayoutNameValidator()));
86 | }
87 |
88 | private void createMainActions(DefaultActionGroup windowMenu) {
89 | this.storeLayout = this.createMainAction(MessagesHelper.message("StoreLayout.Menu"), windowMenu);
90 | this.restoreLayout = this.createMainAction(MessagesHelper.message("RestoreLayout.Menu"), windowMenu);
91 | this.deleteLayout = this.createMainAction(MessagesHelper.message("DeleteLayout.Menu"), windowMenu);
92 | }
93 |
94 | private DefaultActionGroup createMainAction(String name, DefaultActionGroup windowMenu) {
95 | DefaultActionGroup windowMenuItem = new DefaultActionGroup(name, true);
96 | windowMenu.add(windowMenuItem);
97 |
98 | return windowMenuItem;
99 | }
100 |
101 | private void createStoreRestoreActions() {
102 | LayoutConfig config = LayoutConfig.getInstance();
103 | this.addStoreLayoutActions(config);
104 | this.addRestoreLayoutActions(config);
105 | this.addDeleteLayoutActions(config);
106 | }
107 |
108 | private void addStoreLayoutActions(LayoutConfig config) {
109 | LayoutCreator layoutCreator = this.createLayoutCreator();
110 |
111 | for (Layout layout : config.getLayouts()) {
112 | this.addOverwriteLayoutActionAtTheEnd(layout, layoutCreator);
113 | }
114 |
115 | if (config.getLayoutCount() > 0) {
116 | this.storeLayout.addSeparator();
117 | }
118 |
119 | int nextAvailableId = config.getNextAvailableId();
120 |
121 | NewLayoutAction newLayoutAction = new NewLayoutAction(layoutCreator, nextAvailableId);
122 | this.storeLayout.add(newLayoutAction);
123 | this.actionRegistry.register(newLayoutAction, "NewLayout");
124 | }
125 |
126 | private void addOverwriteLayoutActionAtTheEnd(Layout layout, LayoutCreator layoutCreator) {
127 | this.storeLayout.add(new OverwriteLayoutAction(layoutCreator, layout));
128 | }
129 |
130 | private void addStoreLayoutActionBeforeSeparator(Layout layout, LayoutCreator layoutCreator) {
131 | AnAction[] actions = this.storeLayout.getChildActionsOrStubs();
132 | AnAction newLayoutAction = actions[actions.length - 1];
133 |
134 | this.removeSeparator(actions);
135 | this.storeLayout.remove(newLayoutAction);
136 |
137 | this.addOverwriteLayoutActionAtTheEnd(layout, layoutCreator);
138 |
139 | this.storeLayout.addSeparator();
140 | this.storeLayout.add(newLayoutAction);
141 | }
142 |
143 | private void removeSeparator(AnAction[] actions) {
144 | boolean hasSeparator = actions.length > 1;
145 | if (hasSeparator) {
146 | AnAction separator = actions[actions.length - 2];
147 | this.storeLayout.remove(separator);
148 | }
149 | }
150 |
151 | private void addRestoreLayoutActions(LayoutConfig config) {
152 | for (Layout layout : config.getLayouts()) {
153 | this.addRestoreLayoutAction(layout);
154 | }
155 | }
156 |
157 | private void addRestoreLayoutAction(Layout layout) {
158 | RestoreLayoutAction restoreLayoutAction = new RestoreLayoutAction(layout);
159 | this.restoreLayout.add(restoreLayoutAction);
160 | this.actionRegistry.register(restoreLayoutAction);
161 | }
162 |
163 | private void addDeleteLayoutActions(LayoutConfig config) {
164 | for (Layout layout : config.getLayouts()) {
165 | this.addDeleteLayout(layout);
166 | }
167 | }
168 |
169 | private void addDeleteLayout(Layout layout) {
170 | this.deleteLayout.add(new DeleteLayoutAction(layout));
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/ui/settings/EditLayout.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.ui.settings;
2 |
3 | import com.layoutmanager.persistence.Layout;
4 | import org.jetbrains.annotations.NotNull;
5 |
6 | public record EditLayout(Layout originalLayout, Layout editedLayout) {
7 | public EditLayout(
8 | Layout originalLayout,
9 | @NotNull Layout editedLayout) {
10 | this.originalLayout = originalLayout;
11 | this.editedLayout = editedLayout;
12 | }
13 |
14 | public boolean nameHasChanged() {
15 | return this.originalLayout() == null ||
16 | !this.originalLayout.getName().equals(this.editedLayout.getName());
17 | }
18 |
19 | public void applyNameChange() {
20 | this.originalLayout.setName(this.editedLayout.getName());
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/ui/settings/ImportExportConstants.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.ui.settings;
2 |
3 | public class ImportExportConstants {
4 | public static final String FILE_ENDING = "wl";
5 |
6 | public static final String FILE_ENDING_WITH_DOT = "." + FILE_ENDING;
7 |
8 | public static final String FILE_TYPE_NAME = "Window layouts";
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/ui/settings/LayoutDuplicator.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.ui.settings;
2 |
3 | import com.layoutmanager.persistence.Layout;
4 |
5 | public class LayoutDuplicator {
6 | private final LayoutSerializer serializer;
7 |
8 | public LayoutDuplicator(LayoutSerializer serializer) {
9 | this.serializer = serializer;
10 | }
11 |
12 | public Layout duplicate(Layout origin) {
13 | return this.serializer.deserialize(this.serializer.serialize(origin));
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/ui/settings/LayoutManagerSettingsPanel.form:
--------------------------------------------------------------------------------
1 |
2 |
144 |
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/ui/settings/LayoutManagerSettingsPanel.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.ui.settings;
2 |
3 | import static com.intellij.openapi.keymap.impl.ui.KeymapPanel.addKeyboardShortcut;
4 | import static javax.swing.JComponent.WHEN_FOCUSED;
5 |
6 | import com.intellij.ide.IdeBundle;
7 | import com.intellij.openapi.actionSystem.ActionManager;
8 | import com.intellij.openapi.actionSystem.AnActionEvent;
9 | import com.intellij.openapi.actionSystem.DefaultActionGroup;
10 | import com.intellij.openapi.actionSystem.Separator;
11 | import com.intellij.openapi.actionSystem.Shortcut;
12 | import com.intellij.openapi.keymap.KeyMapBundle;
13 | import com.intellij.openapi.keymap.Keymap;
14 | import com.intellij.openapi.keymap.KeymapManager;
15 | import com.intellij.openapi.keymap.KeymapUtil;
16 | import com.intellij.openapi.keymap.impl.ActionShortcutRestrictions;
17 | import com.intellij.openapi.keymap.impl.ShortcutRestrictions;
18 | import com.intellij.openapi.keymap.impl.SystemShortcuts;
19 | import com.intellij.openapi.project.DumbAwareAction;
20 | import com.intellij.openapi.util.text.StringUtil;
21 | import com.layoutmanager.localization.MessagesHelper;
22 | import com.layoutmanager.persistence.Layout;
23 | import com.layoutmanager.persistence.LayoutConfig;
24 | import com.layoutmanager.ui.action.ActionNameGenerator;
25 | import com.layoutmanager.ui.dialogs.LayoutNameDialog;
26 | import com.layoutmanager.ui.dialogs.LayoutNameValidator;
27 | import com.layoutmanager.ui.helpers.ComponentNotificationHelper;
28 | import com.layoutmanager.ui.settings.exporting.ExportDialog;
29 | import com.layoutmanager.ui.settings.importing.ImportDialog;
30 | import java.awt.event.ActionEvent;
31 | import java.awt.event.KeyEvent;
32 | import java.awt.event.MouseAdapter;
33 | import java.awt.event.MouseEvent;
34 | import java.util.ArrayList;
35 | import java.util.Arrays;
36 | import java.util.Collections;
37 | import java.util.stream.Collectors;
38 | import javax.swing.AbstractAction;
39 | import javax.swing.ActionMap;
40 | import javax.swing.InputMap;
41 | import javax.swing.JButton;
42 | import javax.swing.JCheckBox;
43 | import javax.swing.JDialog;
44 | import javax.swing.JPanel;
45 | import javax.swing.JTable;
46 | import javax.swing.KeyStroke;
47 | import javax.swing.ListSelectionModel;
48 | import javax.swing.SwingUtilities;
49 | import javax.swing.event.TableModelEvent;
50 | import javax.swing.table.DefaultTableModel;
51 | import org.jetbrains.annotations.NotNull;
52 | import org.jetbrains.annotations.Nullable;
53 |
54 | public class LayoutManagerSettingsPanel {
55 | private final LayoutConfig layoutConfig;
56 | private final LayoutNameDialog layoutNameDialog;
57 | private final LayoutNameValidator layoutNameValidator;
58 | private final LayoutSerializer layoutSerializer;
59 | private final LayoutDuplicator layoutDuplicator;
60 | private final WindowMenuChangesApplier windowMenuChangesApplier;
61 | private final ArrayList editLayouts = new ArrayList<>();
62 |
63 | private JCheckBox useSmartDockingCheckbox;
64 | private JButton deleteButton;
65 | private JButton renameButton;
66 | private JButton exportButton;
67 | private JButton importButton;
68 | private JPanel settingsPanel;
69 | private JTable layoutsTable;
70 |
71 | public LayoutManagerSettingsPanel(
72 | LayoutConfig layoutConfig,
73 | LayoutNameDialog layoutNameDialog,
74 | LayoutNameValidator layoutNameValidator,
75 | LayoutSerializer layoutSerializer,
76 | LayoutDuplicator layoutDuplicator,
77 | WindowMenuChangesApplier windowMenuChangesApplier) {
78 | this.layoutConfig = layoutConfig;
79 | this.layoutNameDialog = layoutNameDialog;
80 | this.layoutNameValidator = layoutNameValidator;
81 | this.layoutSerializer = layoutSerializer;
82 | this.layoutDuplicator = layoutDuplicator;
83 | this.windowMenuChangesApplier = windowMenuChangesApplier;
84 |
85 | this.loadSettings(layoutConfig);
86 |
87 | this.layoutsTable
88 | .getSelectionModel()
89 | .addListSelectionListener(listSelectionEvent -> this.selectedLayoutChanged());
90 | this.deleteButton.addActionListener(e -> this.deleteLayout());
91 | this.renameButton.addActionListener(e -> this.renameLayout());
92 | this.exportButton.addActionListener(actionEvent -> this.exportLayout());
93 | this.importButton.addActionListener(actionEvent -> this.importLayout());
94 | }
95 |
96 | private void loadSettings(LayoutConfig layoutConfig) {
97 | Collections.addAll(
98 | this.editLayouts,
99 | Arrays
100 | .stream(layoutConfig.getLayouts())
101 | .map(x -> new EditLayout(x, this.layoutDuplicator.duplicate(x)))
102 | .toArray(EditLayout[]::new));
103 |
104 | DefaultTableModel table = this.createTableContent();
105 | this.setKeyBindings(table);
106 | this.layoutsTable.setModel(table);
107 | this.layoutsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
108 | this.layoutsTable.addMouseListener(new DoubleClickOpenShortcutListener());
109 |
110 | this.useSmartDockingCheckbox.setSelected(layoutConfig.getSettings().getUseSmartDock());
111 | }
112 |
113 | private void setKeyBindings(DefaultTableModel tableModel) {
114 | InputMap inputMap = this.layoutsTable.getInputMap(WHEN_FOCUSED);
115 | ActionMap actionMap = this.layoutsTable.getActionMap();
116 |
117 | inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "delete");
118 | actionMap.put("delete", new AbstractAction() {
119 | public void actionPerformed(ActionEvent evt) {
120 | int selectedRow = LayoutManagerSettingsPanel.this.layoutsTable.getSelectedRow();
121 | if (selectedRow >= 0) {
122 | tableModel.removeRow(selectedRow);
123 | }
124 | }
125 | });
126 | }
127 |
128 | private void selectedLayoutChanged() {
129 | this.deleteButton.setEnabled(this.layoutsTable.getSelectedRow() >= 0);
130 | this.renameButton.setEnabled(this.layoutsTable.getSelectedRow() >= 0);
131 | this.exportButton.setEnabled(this.layoutsTable.getSelectedRow() >= 0);
132 | }
133 |
134 | private void deleteLayout() {
135 | DefaultTableModel table = (DefaultTableModel) this.layoutsTable.getModel();
136 | table.removeRow(this.layoutsTable.getSelectedRow());
137 | }
138 |
139 | private void renameLayout() {
140 | int selectedRow = this.layoutsTable.getSelectedRow();
141 | String newName = this.layoutNameDialog.show(
142 | this.editLayouts
143 | .get(selectedRow)
144 | .editedLayout()
145 | .getName());
146 |
147 | if (newName != null) {
148 | this.layoutsTable.setValueAt(newName, selectedRow, 0);
149 | }
150 | }
151 |
152 | private void exportLayout() {
153 | int selectedRow = this.layoutsTable.getSelectedRow();
154 | EditLayout selectedLayout = this.editLayouts.get(selectedRow);
155 |
156 | String encodedContent = this.layoutSerializer.serialize(selectedLayout.editedLayout());
157 | this.showExportDialog(selectedLayout.editedLayout(), encodedContent);
158 | }
159 |
160 | private void showExportDialog(Layout selectedLayout, String encodedContent) {
161 | ExportDialog exportDialog = new ExportDialog(selectedLayout.getName(), encodedContent);
162 | JDialog parent = this.getParentDialog();
163 | exportDialog.showDialogInCenterOf(parent);
164 | }
165 |
166 | private void importLayout() {
167 | ImportDialog importDialog = new ImportDialog(this.layoutNameValidator, this.layoutSerializer);
168 | JDialog parent = this.getParentDialog();
169 | if (importDialog.showDialogInCenterOf(parent) == ImportDialog.OK_RESULT) {
170 | this.editLayouts.add(new EditLayout(null, importDialog.getImportedLayout()));
171 | ((DefaultTableModel) this.layoutsTable.getModel()).fireTableDataChanged();
172 | }
173 | }
174 |
175 | @Nullable
176 | private JDialog getParentDialog() {
177 | return (JDialog) SwingUtilities.getAncestorOfClass(JDialog.class, this.settingsPanel);
178 | }
179 |
180 | public boolean hasChanged() {
181 | return this.useSmartDockingCheckbox.isSelected() != this.layoutConfig.getSettings().getUseSmartDock() ||
182 | this.layoutsHasBeenAddedOrRemoved() ||
183 | this.layoutsHasBeenRenamed();
184 | }
185 |
186 | private boolean layoutsHasBeenAddedOrRemoved() {
187 | return !Arrays.equals(
188 | this.layoutConfig.getLayouts(),
189 | this.editLayouts
190 | .stream()
191 | .map(EditLayout::originalLayout)
192 | .toArray(Layout[]::new));
193 | }
194 |
195 | private boolean layoutsHasBeenRenamed() {
196 | return this.editLayouts
197 | .stream()
198 | .anyMatch(x -> !x.originalLayout().getName().equals(x.editedLayout().getName()));
199 | }
200 |
201 | private void keymapChanged() {
202 | this.layoutsTable.repaint();
203 | }
204 |
205 | public void apply() {
206 | this.windowMenuChangesApplier.apply(this.editLayouts, this.layoutConfig.getLayouts());
207 |
208 | this.layoutConfig.getSettings().setUseSmartDock(this.useSmartDockingCheckbox.isSelected());
209 | this.layoutConfig.setLayouts(this.editLayouts
210 | .stream()
211 | .map(x ->
212 | x.originalLayout() == null ?
213 | x.editedLayout() :
214 | x.originalLayout())
215 | .toArray(Layout[]::new));
216 | }
217 |
218 | public JPanel getPanel() {
219 | return this.settingsPanel;
220 | }
221 |
222 | @NotNull
223 | private DefaultTableModel createTableContent() {
224 | return new DefaultTableModel(
225 | new String[]{
226 | MessagesHelper.message("SettingsPage.NameColumn"),
227 | MessagesHelper.message("SettingsPage.ConfiguredWindowsColumn"),
228 | MessagesHelper.message("SettingsPage.ShortcutColumn")
229 | },
230 | this.editLayouts.size()) {
231 |
232 | @Override
233 | public boolean isCellEditable(int row, int column) {
234 | return column == 0;
235 | }
236 |
237 | @Override
238 | public void setValueAt(Object value, int row, int column) {
239 | String newLayoutName = value.toString();
240 | if (LayoutManagerSettingsPanel.this.layoutNameValidator.isValid(newLayoutName)) {
241 | LayoutManagerSettingsPanel.this.editLayouts
242 | .get(row)
243 | .editedLayout()
244 | .setName(value.toString());
245 |
246 | this.fireTableChanged(new TableModelEvent(this, row));
247 | } else {
248 | ComponentNotificationHelper.error(
249 | LayoutManagerSettingsPanel.this.layoutsTable,
250 | MessagesHelper.message("LayoutNameValidation.InvalidName"));
251 | }
252 | }
253 |
254 | @Override
255 | public int getRowCount() {
256 | return LayoutManagerSettingsPanel.this.editLayouts.size();
257 | }
258 |
259 | @Override
260 | public void removeRow(int row) {
261 | LayoutManagerSettingsPanel.this.editLayouts.remove(row);
262 | this.fireTableRowsDeleted(row, row);
263 | }
264 |
265 | @Override
266 | public Object getValueAt(int row, int column) {
267 | Layout layout = LayoutManagerSettingsPanel.this.editLayouts.get(row).editedLayout();
268 |
269 | return switch (column) {
270 | case 0 -> layout.getName();
271 | case 1 -> layout.getToolWindows().length;
272 | case 2 -> this.getKeyMap(layout);
273 | default -> null;
274 | };
275 | }
276 |
277 | private String getKeyMap(Layout layout) {
278 | Keymap keymap = KeymapManager.getInstance().getActiveKeymap();
279 | String actionName = ActionNameGenerator.getActionNameForLayout(layout);
280 |
281 | Shortcut[] shortcuts = keymap.getShortcuts(actionName);
282 | return Arrays.stream(shortcuts)
283 | .map(KeymapUtil::getShortcutText)
284 | .collect(Collectors.joining(", "));
285 | }
286 | };
287 | }
288 |
289 | private @NotNull DefaultActionGroup createEditActionGroup(@NotNull String actionId, Keymap selectedKeymap) {
290 | DefaultActionGroup group = new DefaultActionGroup();
291 | final ShortcutRestrictions restrictions = ActionShortcutRestrictions.getInstance().getForActionId(actionId);
292 | if (restrictions.allowKeyboardShortcut) {
293 | group.add(new AddKeyboardShortcutAction(actionId, restrictions, selectedKeymap));
294 | }
295 |
296 | group.addSeparator();
297 |
298 | Shortcut[] shortcuts = selectedKeymap.getShortcuts(actionId);
299 | for (Shortcut shortcut : shortcuts) {
300 | group.add(new RemoveShortcutAction(shortcut, selectedKeymap, actionId));
301 | }
302 |
303 | if (shortcuts.length > 2) {
304 | group.add(new Separator());
305 | group.add(new RemoveAllShortcuts(selectedKeymap, actionId));
306 | }
307 |
308 | return group;
309 | }
310 |
311 | private class DoubleClickOpenShortcutListener extends MouseAdapter {
312 | @Override
313 | public void mouseClicked(MouseEvent e) {
314 | if (e.getClickCount() == 2 || SwingUtilities.isRightMouseButton(e)) {
315 | JTable table = (JTable) e.getSource();
316 | int column = table.getSelectedColumn();
317 | if (column == 2) {
318 | EditLayout layout = LayoutManagerSettingsPanel.this.editLayouts.get(table.getSelectedRow());
319 | String actionId = ActionNameGenerator.getActionNameForLayout(layout.editedLayout());
320 |
321 | DefaultActionGroup group = LayoutManagerSettingsPanel.this.createEditActionGroup(
322 | actionId,
323 | KeymapManager.getInstance().getActiveKeymap());
324 | ActionManager.getInstance()
325 | .createActionPopupMenu("popup@Keymap.ActionsTree.Menu", group)
326 | .getComponent()
327 | .show(e.getComponent(), e.getX(), e.getY());
328 | }
329 | }
330 | }
331 | }
332 |
333 | private final class AddKeyboardShortcutAction extends DumbAwareAction {
334 | private final @NotNull String actionId;
335 | private final ShortcutRestrictions restrictions;
336 | private final Keymap keymap;
337 |
338 | private AddKeyboardShortcutAction(@NotNull String actionId, ShortcutRestrictions restrictions, Keymap keymap) {
339 | super(IdeBundle.messagePointer("action.Anonymous.text.add.keyboard.shortcut"));
340 | this.actionId = actionId;
341 | this.restrictions = restrictions;
342 | this.keymap = keymap;
343 | }
344 |
345 | @Override
346 | public void actionPerformed(@NotNull AnActionEvent e) {
347 | addKeyboardShortcut(
348 | this.actionId,
349 | this.restrictions,
350 | this.keymap,
351 | LayoutManagerSettingsPanel.this.layoutsTable,
352 | null,
353 | SystemShortcuts.getInstance());
354 | LayoutManagerSettingsPanel.this.keymapChanged();
355 | }
356 | }
357 |
358 | private final class RemoveShortcutAction extends DumbAwareAction {
359 | private final Shortcut shortcut;
360 | private final Keymap keymap;
361 | private final @NotNull String actionId;
362 |
363 | private RemoveShortcutAction(Shortcut shortcut, Keymap keymap, @NotNull String actionId) {
364 | super(IdeBundle.message("action.text.remove.0", KeymapUtil.getShortcutText(shortcut)));
365 | this.shortcut = shortcut;
366 | this.keymap = keymap;
367 | this.actionId = actionId;
368 | }
369 |
370 | @Override
371 | public void actionPerformed(@NotNull AnActionEvent e) {
372 | this.keymap.removeShortcut(this.actionId, this.shortcut);
373 | if (StringUtil.startsWithChar(this.actionId, '$')) {
374 | this.keymap.removeShortcut(KeyMapBundle.message("editor.shortcut", this.actionId.substring(1)), this.shortcut);
375 | }
376 | LayoutManagerSettingsPanel.this.keymapChanged();
377 | }
378 | }
379 |
380 | private final class RemoveAllShortcuts extends DumbAwareAction {
381 | private final Keymap keymap;
382 | private final String actionId;
383 |
384 | private RemoveAllShortcuts(Keymap selectedKeymap, @NotNull String actionId) {
385 | super(IdeBundle.messagePointer("action.text.remove.all.shortcuts"));
386 | this.keymap = selectedKeymap;
387 | this.actionId = actionId;
388 | }
389 |
390 | @Override
391 | public void actionPerformed(@NotNull AnActionEvent event) {
392 | this.keymap.removeAllActionShortcuts(this.actionId);
393 | LayoutManagerSettingsPanel.this.keymapChanged();
394 | }
395 | }
396 | }
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/ui/settings/LayoutSerializer.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.ui.settings;
2 |
3 | import blazing.chain.LZSEncoding;
4 | import com.google.gson.Gson;
5 | import com.google.gson.GsonBuilder;
6 | import com.layoutmanager.persistence.Layout;
7 |
8 | public class LayoutSerializer {
9 | public String serialize(Layout layout) {
10 | Gson gson = new GsonBuilder().create();
11 | String jsonContent = gson.toJson(layout);
12 | return LZSEncoding.compressToBase64(jsonContent);
13 | }
14 |
15 | public Layout deserialize(String encodedContent) {
16 | String jsonContent = LZSEncoding.decompressFromBase64(encodedContent);
17 | Gson gson = new GsonBuilder().create();
18 | return gson.fromJson(jsonContent, Layout.class);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/ui/settings/SettingsPage.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.ui.settings;
2 |
3 | import com.intellij.openapi.options.Configurable;
4 | import com.layoutmanager.localization.MessagesHelper;
5 | import com.layoutmanager.persistence.LayoutConfig;
6 | import com.layoutmanager.ui.dialogs.LayoutNameDialog;
7 | import com.layoutmanager.ui.dialogs.LayoutNameValidator;
8 | import javax.swing.JComponent;
9 | import org.jetbrains.annotations.Nls;
10 | import org.jetbrains.annotations.Nullable;
11 |
12 | public class SettingsPage implements Configurable {
13 | private final LayoutManagerSettingsPanel panel;
14 |
15 | public SettingsPage() {
16 | LayoutNameValidator layoutNameValidator = new LayoutNameValidator();
17 | LayoutSerializer layoutSerializer = new LayoutSerializer();
18 | this.panel = new LayoutManagerSettingsPanel(
19 | LayoutConfig.getInstance(),
20 | new LayoutNameDialog(layoutNameValidator),
21 | layoutNameValidator,
22 | layoutSerializer,
23 | new LayoutDuplicator(layoutSerializer),
24 | new WindowMenuChangesApplier());
25 | }
26 |
27 | @Nls(capitalization = Nls.Capitalization.Title)
28 | @Override
29 | public String getDisplayName() {
30 | return MessagesHelper.message("SettingsPage.Title");
31 | }
32 |
33 | @Nullable
34 | @Override
35 | public JComponent createComponent() {
36 | return this.panel.getPanel();
37 | }
38 |
39 | @Override
40 | public boolean isModified() {
41 | return this.panel.hasChanged();
42 | }
43 |
44 | @Override
45 | public void apply() {
46 | this.panel.apply();
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/ui/settings/WindowMenuChangesApplier.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.ui.settings;
2 |
3 | import com.intellij.openapi.application.ApplicationManager;
4 | import com.layoutmanager.persistence.Layout;
5 | import com.layoutmanager.ui.menu.WindowMenuService;
6 | import java.util.List;
7 |
8 | public class WindowMenuChangesApplier {
9 | public void apply(List editedLayouts, Layout[] originalLayouts) {
10 | WindowMenuService windowMenuService = ApplicationManager
11 | .getApplication()
12 | .getService(WindowMenuService.class);
13 |
14 | this.addOrUpdateLayouts(editedLayouts, windowMenuService);
15 |
16 | this.removeObsoleteLayouts(editedLayouts, originalLayouts, windowMenuService);
17 | }
18 |
19 | private void addOrUpdateLayouts(List editedLayouts, WindowMenuService windowMenuService) {
20 | for (EditLayout editLayout : editedLayouts) {
21 | if (this.isNew(editLayout)) {
22 | windowMenuService.addLayout(editLayout.editedLayout());
23 | } else if (editLayout.nameHasChanged()) {
24 | editLayout.applyNameChange();
25 | windowMenuService.renameLayout(editLayout.originalLayout());
26 | }
27 | }
28 | }
29 |
30 | private boolean isNew(EditLayout editLayout) {
31 | return editLayout.originalLayout() == null;
32 | }
33 |
34 | private void removeObsoleteLayouts(List editedLayouts, Layout[] originalLayouts, WindowMenuService windowMenuService) {
35 | for (Layout layout : originalLayouts) {
36 | if (editedLayouts.stream().noneMatch(x -> x.originalLayout().equals(layout))) {
37 | windowMenuService.deleteLayout(layout);
38 | }
39 | }
40 | }
41 | }
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/ui/settings/exporting/ExportDialog.form:
--------------------------------------------------------------------------------
1 |
2 |
120 |
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/ui/settings/exporting/ExportDialog.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.ui.settings.exporting;
2 |
3 | import com.layoutmanager.localization.MessagesHelper;
4 | import com.layoutmanager.ui.helpers.ComponentNotificationHelper;
5 | import com.layoutmanager.ui.settings.ImportExportConstants;
6 | import java.awt.Toolkit;
7 | import java.awt.datatransfer.StringSelection;
8 | import java.awt.event.KeyEvent;
9 | import java.io.File;
10 | import java.io.IOException;
11 | import java.nio.file.Files;
12 | import java.nio.file.Path;
13 | import java.nio.file.Paths;
14 | import javax.swing.JButton;
15 | import javax.swing.JComponent;
16 | import javax.swing.JDialog;
17 | import javax.swing.JFileChooser;
18 | import javax.swing.JLabel;
19 | import javax.swing.JPanel;
20 | import javax.swing.JTextArea;
21 | import javax.swing.KeyStroke;
22 | import javax.swing.SwingUtilities;
23 | import javax.swing.filechooser.FileNameExtensionFilter;
24 | import org.jetbrains.annotations.NotNull;
25 |
26 | public class ExportDialog extends JDialog {
27 | private JTextArea exportTextBox;
28 | private JPanel contentPanel;
29 | private JButton exportToFileButton;
30 | private JButton exportToClipboardButton;
31 | private JButton closeButton;
32 | private JLabel layoutNameLabel;
33 |
34 | private final String layoutName;
35 | private final String content;
36 |
37 | public ExportDialog(String layoutName, String content) {
38 | this.layoutName = layoutName;
39 | this.content = content;
40 |
41 | this.setContentPane(this.contentPanel);
42 | this.setModal(true);
43 | this.getRootPane().setDefaultButton(this.closeButton);
44 |
45 | this.exportToClipboardButton.addActionListener(actionEvent -> this.exportToClipboard());
46 | this.exportToFileButton.addActionListener(actionEvent -> this.exportToFile());
47 | this.closeButton.addActionListener(actionEvent -> this.onClose());
48 |
49 | this.layoutNameLabel.setText(layoutName);
50 | this.exportTextBox.setText(content);
51 |
52 | this.contentPanel.registerKeyboardAction(
53 | e -> this.onClose(),
54 | KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
55 | JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
56 | }
57 |
58 | public void showDialogInCenterOf(JDialog parent) {
59 | this.setTitle(MessagesHelper.message("ExportDialog.Title"));
60 | this.setSize(this.getPreferredSize());
61 | this.setLocationRelativeTo(parent);
62 | this.setVisible(true);
63 | }
64 |
65 | private void exportToClipboard() {
66 | this.exportTextBox.requestFocus();
67 | this.exportTextBox.selectAll();
68 | Toolkit
69 | .getDefaultToolkit()
70 | .getSystemClipboard()
71 | .setContents(
72 | new StringSelection(this.exportTextBox.getText()),
73 | null);
74 | ComponentNotificationHelper.info(this.exportToClipboardButton, MessagesHelper.message("ExportDialog.CopiedToClipboard"));
75 | }
76 |
77 | private void exportToFile() {
78 | File selectedFile = this.selectFile();
79 | if (selectedFile != null) {
80 | Path path = this.getPath(selectedFile);
81 | this.writeContentToFile(path);
82 | }
83 | }
84 |
85 | private File selectFile() {
86 | JFileChooser fileChooser = new JFileChooser();
87 | fileChooser.setDialogTitle(MessagesHelper.message("ExportDialog.SaveFileTitle"));
88 | fileChooser.setSelectedFile(new File(this.layoutName + ImportExportConstants.FILE_ENDING_WITH_DOT));
89 | fileChooser.setAcceptAllFileFilterUsed(false);
90 | FileNameExtensionFilter filter = new FileNameExtensionFilter(ImportExportConstants.FILE_TYPE_NAME, ImportExportConstants.FILE_ENDING);
91 | fileChooser.setFileFilter(filter);
92 |
93 | int result = fileChooser.showSaveDialog(null);
94 | return result == JFileChooser.APPROVE_OPTION ?
95 | fileChooser.getSelectedFile() :
96 | null;
97 | }
98 |
99 | @NotNull
100 | private Path getPath(File selectedFile) {
101 | String fullPath = selectedFile.getAbsolutePath();
102 | if (!fullPath.toLowerCase().endsWith(ImportExportConstants.FILE_ENDING_WITH_DOT)) {
103 | fullPath += ImportExportConstants.FILE_ENDING_WITH_DOT;
104 | }
105 |
106 | return Paths.get(fullPath);
107 | }
108 |
109 | private void writeContentToFile(Path path) {
110 | try {
111 | byte[] encodedContent = this.content.getBytes();
112 | Files.write(path, encodedContent);
113 | ComponentNotificationHelper.info(this.exportToFileButton, MessagesHelper.message("ExportDialog.SavedTo", path.getFileName().toString()));
114 | } catch (IOException e) {
115 | ComponentNotificationHelper.error(this.exportToFileButton, MessagesHelper.message("ExportDialog.FailedToWriteFile", e.getMessage()));
116 | }
117 | }
118 |
119 | private void onClose() {
120 | JDialog window = (JDialog) SwingUtilities.getAncestorOfClass(JDialog.class, this.contentPanel);
121 | window.dispose();
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/ui/settings/importing/ImportDialog.form:
--------------------------------------------------------------------------------
1 |
2 |
147 |
--------------------------------------------------------------------------------
/src/main/java/com/layoutmanager/ui/settings/importing/ImportDialog.java:
--------------------------------------------------------------------------------
1 | package com.layoutmanager.ui.settings.importing;
2 |
3 | import com.layoutmanager.localization.MessagesHelper;
4 | import com.layoutmanager.persistence.Layout;
5 | import com.layoutmanager.ui.dialogs.LayoutNameValidator;
6 | import com.layoutmanager.ui.helpers.ComponentNotificationHelper;
7 | import com.layoutmanager.ui.settings.ImportExportConstants;
8 | import com.layoutmanager.ui.settings.LayoutSerializer;
9 | import java.awt.Toolkit;
10 | import java.awt.datatransfer.DataFlavor;
11 | import java.awt.event.KeyEvent;
12 | import java.awt.event.WindowAdapter;
13 | import java.awt.event.WindowEvent;
14 | import java.io.File;
15 | import java.io.IOException;
16 | import java.nio.file.Files;
17 | import java.nio.file.Paths;
18 | import javax.swing.JButton;
19 | import javax.swing.JComponent;
20 | import javax.swing.JDialog;
21 | import javax.swing.JFileChooser;
22 | import javax.swing.JLabel;
23 | import javax.swing.JPanel;
24 | import javax.swing.JTextField;
25 | import javax.swing.KeyStroke;
26 | import javax.swing.filechooser.FileNameExtensionFilter;
27 |
28 | public class ImportDialog extends JDialog {
29 | public static final int OK_RESULT = 1;
30 | public static final int ABORT_RESULT = 0;
31 | private final LayoutNameValidator layoutNameValidator;
32 | private final LayoutSerializer layoutSerializer;
33 |
34 | private JPanel contentPanel;
35 | private JButton importButton;
36 | private JButton abortButton;
37 | private JButton importFromFileButton;
38 | private JButton importFromClipboardButton;
39 | private JLabel layoutConfiguredWindowCountLabel;
40 | private JTextField layoutNameTextBox;
41 |
42 | private Layout importedLayout;
43 | private int result;
44 |
45 | public ImportDialog(
46 | LayoutNameValidator layoutNameValidator,
47 | LayoutSerializer layoutSerializer) {
48 | this.layoutNameValidator = layoutNameValidator;
49 | this.layoutSerializer = layoutSerializer;
50 |
51 | this.setContentPane(this.contentPanel);
52 | this.setModal(true);
53 | this.setResizable(false);
54 | this.getRootPane().setDefaultButton(this.importButton);
55 |
56 | this.importButton.addActionListener(e -> this.onOK());
57 | this.abortButton.addActionListener(e -> this.onCancel());
58 | this.importFromClipboardButton.addActionListener(actionEvent -> this.importFromClipboard());
59 | this.importFromFileButton.addActionListener(actionEvent -> this.importFromFile());
60 |
61 | // call onCancel() when cross is clicked
62 | this.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
63 | this.addWindowListener(new WindowAdapter() {
64 | public void windowClosing(WindowEvent e) {
65 | ImportDialog.this.onCancel();
66 | }
67 | });
68 |
69 | this.contentPanel.registerKeyboardAction(
70 | e -> this.onCancel(),
71 | KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
72 | JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
73 | }
74 |
75 | public Layout getImportedLayout() {
76 | return this.importedLayout;
77 | }
78 |
79 | public int showDialogInCenterOf(JDialog parent) {
80 | this.setTitle(MessagesHelper.message("ImportDialog.Title"));
81 | this.setSize(this.getPreferredSize());
82 | this.setLocationRelativeTo(parent);
83 | this.setVisible(true);
84 | return this.result;
85 | }
86 |
87 | private void importFromFile() {
88 | File selectedFile = this.selectFile();
89 | if (selectedFile != null) {
90 | this.importFile(selectedFile);
91 | }
92 | }
93 |
94 | private File selectFile() {
95 | JFileChooser fileChooser = new JFileChooser();
96 | fileChooser.setDialogTitle(MessagesHelper.message("ImportDialog.SelectFileTitle"));
97 | fileChooser.setAcceptAllFileFilterUsed(false);
98 | FileNameExtensionFilter filter = new FileNameExtensionFilter(
99 | ImportExportConstants.FILE_TYPE_NAME,
100 | ImportExportConstants.FILE_ENDING);
101 | fileChooser.setFileFilter(filter);
102 |
103 | int result = fileChooser.showOpenDialog(null);
104 | return result == JFileChooser.APPROVE_OPTION && fileChooser.getSelectedFile().exists() ?
105 | fileChooser.getSelectedFile() : null;
106 | }
107 |
108 | private void importFile(File file) {
109 | try {
110 | String encodedContent = Files.readString(Paths.get(file.getPath()));
111 | this.importLayout(encodedContent);
112 | } catch (IOException e) {
113 | ComponentNotificationHelper.error(
114 | this.importFromFileButton,
115 | MessagesHelper.message("ImportDialog.IOException", file.getName(), e.getMessage()));
116 | this.deselectLayout();
117 | } catch (Exception e) {
118 | ComponentNotificationHelper.error(
119 | this.importFromFileButton,
120 | MessagesHelper.message("ImportDialog.UnknownFormat"));
121 | this.deselectLayout();
122 | }
123 | }
124 |
125 | private void importFromClipboard() {
126 | try {
127 | String lzEncodedContent = (String) Toolkit
128 | .getDefaultToolkit()
129 | .getSystemClipboard()
130 | .getData(DataFlavor.stringFlavor);
131 |
132 | this.importLayout(lzEncodedContent);
133 | } catch (Exception e) {
134 | ComponentNotificationHelper.error(
135 | this.importFromClipboardButton,
136 | MessagesHelper.message("ImportDialog.UnknownFormat"));
137 | this.deselectLayout();
138 | }
139 | }
140 |
141 | private void importLayout(String encodedContent) {
142 | Layout selectedLayout = this.layoutSerializer.deserialize(encodedContent);
143 | this.selectLayout(selectedLayout);
144 | }
145 |
146 | private void selectLayout(Layout layout) {
147 | this.importedLayout = layout;
148 |
149 | this.layoutNameTextBox.setText(layout.getName());
150 | this.layoutNameTextBox.requestFocus();
151 | this.layoutConfiguredWindowCountLabel.setText(Integer.toString(layout.getToolWindows().length));
152 |
153 | ComponentNotificationHelper.info(
154 | this.layoutNameTextBox,
155 | MessagesHelper.message("ImportDialog.SuccessfullyLoadedLayout", layout.getName()));
156 |
157 | this.importButton.setEnabled(true);
158 | this.layoutNameTextBox.setEnabled(true);
159 | }
160 |
161 | private void deselectLayout() {
162 | this.importedLayout = null;
163 | this.importButton.setEnabled(false);
164 | this.layoutNameTextBox.setText("");
165 | this.layoutNameTextBox.setEnabled(false);
166 | this.layoutConfiguredWindowCountLabel.setText("-");
167 | }
168 |
169 | private void onOK() {
170 | if (!this.layoutNameValidator.isValid(this.layoutNameTextBox.getText())) {
171 | ComponentNotificationHelper.error(this.importButton, MessagesHelper.message("LayoutNameValidation.InvalidName"));
172 | return;
173 | }
174 | this.importedLayout.setName(this.layoutNameTextBox.getText());
175 |
176 | this.result = OK_RESULT;
177 | this.dispose();
178 | }
179 |
180 | private void onCancel() {
181 | this.result = ABORT_RESULT;
182 | this.dispose();
183 | }
184 | }
185 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/plugin.xml:
--------------------------------------------------------------------------------
1 |
2 | com.layoutmanager
3 | Window Layout Manager
4 | Michael Estermann
5 |
6 |
10 | Plugin is accessible via Window Menu :
11 |
12 | Store Layout: Stores the current window layout
13 | Restore Layout: Restores the selected layout immediately
14 |
15 | Feel free to contribute at GitHub .
16 | ]]>
17 |
18 |
20 | Sept 12, 2019 (ver 1.0) - Initial version
21 | Nov 29, 2019 (ver 1.1.1) - Support of the latest IDE versions
22 | Dec 11, 2019 (ver 1.2) - Support variable amount of windows
23 | Dec 11, 2019 (ver 1.2.1) - Support of other languages and notifications
24 | Dec 11, 2019 (ver 1.2.2) - Support of the latest IDE versions 2019.3
25 | Dec 11, 2019 (ver 1.2.3) - Various bug fixes
26 | Dec 19, 2019 (ver 1.3.0) - Store placement of editor tabs and tool window labels.
27 | Dec 26, 2019 (ver 1.3.1) - Fix support of Java 8.
28 | Jan 07, 2020 (ver 1.3.2) - Minor bugfixes.
29 | Feb 25, 2020 (ver 1.3.3) - Issues when storing and restoring invisible windows fixed.
30 |
31 | May 8, 2020 (ver 1.4.0)
32 | - Introduced settings page.
33 | - Introduced smart docking when saving a layout (Configurable).
34 | - Store docked tool window sizes and restore them accordingly.
35 |
36 |
37 | September 17, 2024 (ver 1.5.0)
38 | - Hide hints after a short time
39 | - Support key-shortcuts for layouts
40 | - Improved icons
41 | - Minor Bugfixes
42 | - Support of new UI
43 |
44 |
45 | ]]>
46 |
47 |
48 |
49 |
50 |
51 | com.intellij.modules.lang
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/src/main/resources/com/layoutmanager/ui/icons/DeleteLayout.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/com/layoutmanager/ui/icons/NewLayout.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/com/layoutmanager/ui/icons/OverwriteLayout.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/com/layoutmanager/ui/icons/RestoreLayout.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/com/layoutmanager/ui/messages.properties:
--------------------------------------------------------------------------------
1 | RestoreLayout.Menu=Apply Window Layout
2 | RestoreLayout.Notification.Title=Window layout restored
3 | RestoreLayout.Notification.Content=The window layout ''{0}'' has been successfully restored.
4 |
5 | StoreLayout.Menu=Store Window Layout
6 | StoreLayout.Dialog.Title=Layout name
7 | StoreLayout.Dialog.Content=Enter the Name for the Layout
8 | StoreLayout.Validation.ToolWindowOutOfScreen.Title=Layout contains invalid tool windows
9 | StoreLayout.Validation.ToolWindowOutOfScreen.Content=The tool windows ''{0}'' are (partially) outside the screen bounds. When reopening the project, the IDE will restore them in the center of the main window. Please adjust this windows or apply the layout every time the project gets opened in the IDE.
10 | StoreLayout.New.Menu=New Layout
11 | StoreLayout.New.Notification.Title=Window layout created
12 | StoreLayout.New.Notification.Content=The window layout ''{0}'' has been successfully created.
13 | StoreLayout.Overwrite.Notification.Title=Window layout overwritten
14 | StoreLayout.Overwrite.Notification.Content=The window layout ''{0}'' has been successfully overwritten with the window layout ''{1}''.
15 |
16 | DeleteLayout.Menu=Delete Layout
17 | DeleteLayout.Notification.Title=Window layout deleted
18 | DeleteLayout.Notification.Content=The window layout ''{0}'' has been successfully deleted.
19 |
20 | SettingsPage.Title=Window Layout Manager
21 | SettingsPage.NameColumn=Name
22 | SettingsPage.ConfiguredWindowsColumn=Configured Windows
23 | SettingsPage.ShortcutColumn=Shortcut
24 | SettingsPage.WarningShortcut=The shortcut will only update after the settings have been saved.
25 |
26 | ExportDialog.Title=Export layout
27 | ExportDialog.CopiedToClipboard=Copied to clipboard!
28 | ExportDialog.SaveFileTitle=Specify a file to save
29 | ExportDialog.SavedTo=Saved to file ''{0}'' !
30 | ExportDialog.FailedToWriteFile=Failed to write export file: {0}
31 |
32 | ImportDialog.Title=Import layout
33 | ImportDialog.SelectFileTitle=Select layout file
34 | ImportDialog.IOException=Failed to read file ''{0}''. Reason: {1}
35 | ImportDialog.UnknownFormat=Import failed due to an unknown format
36 | ImportDialog.SuccessfullyLoadedLayout=Successfully loaded the layout ''{0}''
37 |
38 | LayoutNameValidation.InvalidName=The layout name must not be empty or contain illegal characters.
--------------------------------------------------------------------------------