diff --git a/build.gradle b/build.gradle
index 16c991c..496ed6a 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,18 +1,35 @@
plugins {
id 'java'
id 'org.jetbrains.intellij' version '0.4.15'
+ id 'checkstyle'
}
+
+dependencies {
+ compile group: 'com.google.code.gson', name: 'gson', version: '2.8.5'
+ compile group: 'com.github.tommyettinger', name: 'blazingchain', version: '1.4.4.2'
+}
group 'com.layoutmanager'
-version '1.3.3'
+version '1.4.0'
sourceCompatibility = 1.8
buildSearchableOptions.enabled = false
+repositories {
+ mavenLocal()
+ mavenCentral()
+}
+
+// https://www.jetbrains.org/intellij/sdk/docs/reference_guide/intellij_artifacts.html
+// https://www.jetbrains.com/intellij-repository/releases/
+// https://www.jetbrains.com/intellij-repository/snapshots/
intellij {
type = 'RD'
- version = "2020.1-SNAPSHOT"
+ version = "2020.2-SNAPSHOT"
+ // version = "2019.3.4"
+ // version = "2020.1.0"
+ // version = "2020.1-SNAPSHOT"
updateSinceUntilBuild = false
}
@@ -23,4 +40,12 @@ publishPlugin {
wrapper {
gradleVersion = '6.0.1'
distributionUrl = "https://cache-redirector.jetbrains.com/services.gradle.org/distributions/gradle-${gradleVersion}-all.zip"
+}
+
+checkstyle {
+ project.ext.checkstyleVersion = '8.27'
+}
+
+checkstyleMain {
+ source ='src/main/java'
}
\ No newline at end of file
diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
new file mode 100644
index 0000000..0dec546
--- /dev/null
+++ b/config/checkstyle/checkstyle.xml
@@ -0,0 +1,257 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/com/layoutmanager/cleanup/EmptyLayoutRemoverService.java b/src/main/java/com/layoutmanager/cleanup/EmptyLayoutRemoverService.java
index 7f1d62f..e607fc7 100644
--- a/src/main/java/com/layoutmanager/cleanup/EmptyLayoutRemoverService.java
+++ b/src/main/java/com/layoutmanager/cleanup/EmptyLayoutRemoverService.java
@@ -9,7 +9,7 @@ public void execute() {
LayoutConfig layoutConfig = ServiceManager.getService(LayoutConfig.class);
for (Layout layout : layoutConfig.getLayouts()) {
- if (isEmpty(layout)) {
+ if (this.isEmpty(layout)) {
layoutConfig.removeLayout(layout);
}
}
diff --git a/src/main/java/com/layoutmanager/actions/DeleteLayoutAction.java b/src/main/java/com/layoutmanager/layout/delete/DeleteLayoutAction.java
similarity index 80%
rename from src/main/java/com/layoutmanager/actions/DeleteLayoutAction.java
rename to src/main/java/com/layoutmanager/layout/delete/DeleteLayoutAction.java
index 9249f7e..05791e9 100644
--- a/src/main/java/com/layoutmanager/actions/DeleteLayoutAction.java
+++ b/src/main/java/com/layoutmanager/layout/delete/DeleteLayoutAction.java
@@ -1,15 +1,17 @@
-package com.layoutmanager.actions;
+package com.layoutmanager.layout.delete;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.Presentation;
import com.intellij.openapi.components.ServiceManager;
+
import com.layoutmanager.localization.MessagesHelper;
-import com.layoutmanager.menu.WindowMenuService;
import com.layoutmanager.persistence.Layout;
import com.layoutmanager.persistence.LayoutConfig;
-import com.layoutmanager.ui.NotificationHelper;
+import com.layoutmanager.ui.helpers.BaloonNotificationHelper;
+import com.layoutmanager.ui.menu.WindowMenuService;
+
import org.jetbrains.annotations.NotNull;
public class DeleteLayoutAction extends AnAction {
@@ -25,9 +27,9 @@ public DeleteLayoutAction(Layout layout) {
@Override
public void actionPerformed(@NotNull AnActionEvent event) {
- deleteLayout();
- updateWindowMenuItems();
- showNotification();
+ this.deleteLayout();
+ this.updateWindowMenuItems();
+ this.showNotification();
}
private void deleteLayout() {
@@ -36,7 +38,7 @@ private void deleteLayout() {
}
private void showNotification() {
- NotificationHelper.info(
+ BaloonNotificationHelper.info(
MessagesHelper.message("DeleteLayout.Notification.Title"),
MessagesHelper.message("DeleteLayout.Notification.Content", this.layout.getName()));
}
diff --git a/src/main/java/com/layoutmanager/actions/RestoreLayoutAction.java b/src/main/java/com/layoutmanager/layout/restore/RestoreLayoutAction.java
similarity index 85%
rename from src/main/java/com/layoutmanager/actions/RestoreLayoutAction.java
rename to src/main/java/com/layoutmanager/layout/restore/RestoreLayoutAction.java
index 49858a9..f4edbdf 100644
--- a/src/main/java/com/layoutmanager/actions/RestoreLayoutAction.java
+++ b/src/main/java/com/layoutmanager/layout/restore/RestoreLayoutAction.java
@@ -1,4 +1,4 @@
-package com.layoutmanager.actions;
+package com.layoutmanager.layout.restore;
import com.intellij.icons.AllIcons;
import com.intellij.ide.ui.UISettings;
@@ -13,8 +13,8 @@
import com.layoutmanager.localization.MessagesHelper;
import com.layoutmanager.persistence.Layout;
import com.layoutmanager.persistence.ToolWindowInfo;
-import com.layoutmanager.ui.NotificationHelper;
-import com.layoutmanager.ui.ToolWindowHelper;
+import com.layoutmanager.ui.helpers.BaloonNotificationHelper;
+import com.layoutmanager.ui.helpers.ToolWindowHelper;
import org.jetbrains.annotations.NotNull;
import java.util.Map;
@@ -43,8 +43,8 @@ public void update(AnActionEvent e) {
@Override
public void actionPerformed(@NotNull AnActionEvent event) {
- applyLayout(event, this.layout);
- showNotification(this.layout);
+ this.applyLayout(event, this.layout);
+ this.showNotification(this.layout);
}
private void applyLayout(AnActionEvent event, Layout layout) {
@@ -58,9 +58,9 @@ private void applyLayout(AnActionEvent event, Layout layout) {
private Map getToolWindows(ToolWindowManager toolWindowManager, ToolWindowInfo[] toolWindows) {
return Stream.of(toolWindows)
- .map(x -> new Pair(x, (ToolWindowImpl)toolWindowManager.getToolWindow(x.getId())))
- .filter(x -> x.second != null)
- .collect(Collectors.toMap(x -> x.first, x -> x.second));
+ .map(x -> new Pair(x, (ToolWindowImpl)toolWindowManager.getToolWindow(x.getId())))
+ .filter(x -> x.second != null)
+ .collect(Collectors.toMap(x -> x.first, x -> x.second));
}
private void applyEditorTabPlacement(Layout layout) {
@@ -102,8 +102,9 @@ private ToolWindowManager getToolWindowManager(AnActionEvent event) {
Project project = event.getProject();
return ToolWindowManager.getInstance(project);
}
+
private void showNotification(Layout updatedLayout) {
- NotificationHelper.info(
+ BaloonNotificationHelper.info(
MessagesHelper.message("RestoreLayout.Notification.Title"),
MessagesHelper.message("RestoreLayout.Notification.Content", updatedLayout.getName()));
}
diff --git a/src/main/java/com/layoutmanager/actions/LayoutCreator.java b/src/main/java/com/layoutmanager/layout/store/LayoutCreator.java
similarity index 60%
rename from src/main/java/com/layoutmanager/actions/LayoutCreator.java
rename to src/main/java/com/layoutmanager/layout/store/LayoutCreator.java
index 832e5ec..d9e64c7 100644
--- a/src/main/java/com/layoutmanager/actions/LayoutCreator.java
+++ b/src/main/java/com/layoutmanager/layout/store/LayoutCreator.java
@@ -1,16 +1,18 @@
-package com.layoutmanager.actions;
+package com.layoutmanager.layout.store;
-import com.intellij.icons.AllIcons;
import com.intellij.ide.ui.UISettings;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.openapi.wm.impl.ToolWindowImpl;
+import com.layoutmanager.layout.store.smartdock.SmartDocker;
+import com.layoutmanager.layout.store.smartdock.SmartDockerFactory;
+import com.layoutmanager.layout.store.validation.LayoutValidationHelper;
import com.layoutmanager.localization.MessagesHelper;
import com.layoutmanager.persistence.Layout;
+import com.layoutmanager.persistence.LayoutSettings;
import com.layoutmanager.persistence.ToolWindowInfo;
-import com.layoutmanager.ui.NotificationHelper;
-import com.layoutmanager.ui.ToolWindowHelper;
+import com.layoutmanager.ui.dialogs.LayoutNameDialog;
+import com.layoutmanager.ui.helpers.BaloonNotificationHelper;
+import com.layoutmanager.ui.helpers.ToolWindowHelper;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
@@ -18,30 +20,28 @@
import java.util.stream.Stream;
public class LayoutCreator {
+ private final LayoutSettings layoutSettings;
+ private final SmartDockerFactory smartDockerFactory;
+ private final LayoutNameDialog layoutNameDialog;
- public static Layout create(Project project, String defaultName) {
+ public LayoutCreator(
+ LayoutSettings layoutSettings,
+ SmartDockerFactory smartDockerFactory,
+ LayoutNameDialog layoutNameDialog) {
+ this.layoutSettings = layoutSettings;
+ this.smartDockerFactory = smartDockerFactory;
+ this.layoutNameDialog = layoutNameDialog;
+ }
+
+ public Layout create(ToolWindowManager toolWindowManager, String defaultName) {
- String name = getLayoutName(defaultName);
+ String name = this.layoutNameDialog.show(defaultName);
return name != null ?
- createLayout(ToolWindowManager.getInstance(project), name) :
+ this.createLayout(toolWindowManager, name) :
null;
}
- private static String getLayoutName(String defaultName) {
- String name;
- do {
- name = Messages.showInputDialog(
- MessagesHelper.message("StoreLayout.Dialog.Title"),
- MessagesHelper.message("StoreLayout.Dialog.Content"),
- AllIcons.Actions.Edit,
- defaultName,
- null);
- } while (name != null && name.isEmpty());
-
- return name;
- }
-
- private static Layout createLayout(ToolWindowManager toolWindowManager, String name) {
+ private Layout createLayout(ToolWindowManager toolWindowManager, String name) {
List toolWindows = getToolWindows(toolWindowManager);
Layout layout = new Layout(
name,
@@ -49,6 +49,11 @@ private static Layout createLayout(ToolWindowManager toolWindowManager, String n
.stream()
.toArray(ToolWindowInfo[]::new),
getEditorPlacement());
+
+ if (this.layoutSettings.getUseSmartDock()) {
+ this.dock(toolWindowManager, layout);
+ }
+
validateLayout(layout);
return layout;
@@ -74,6 +79,11 @@ private static List getToolWindows(ToolWindowManager toolWindowM
return toolWindows;
}
+ private void dock(ToolWindowManager toolWindowManager, Layout layout) {
+ SmartDocker smartDocker = this.smartDockerFactory.create(toolWindowManager);
+ smartDocker.dock(layout);
+ }
+
private static void validateLayout(Layout layout) {
ToolWindowInfo[] invalidToolWindows = LayoutValidationHelper.retrieveToolWindowsOutsideOfScreen(layout);
if (invalidToolWindows.length != 0) {
@@ -83,7 +93,7 @@ private static void validateLayout(Layout layout) {
.map(ToolWindowInfo::getId)
.toArray(String[]::new));
- NotificationHelper.warning(
+ BaloonNotificationHelper.warning(
MessagesHelper.message("StoreLayout.Validation.ToolWindowOutOfScreen.Title"),
MessagesHelper.message("StoreLayout.Validation.ToolWindowOutOfScreen.Content", invalidToolWindowNames));
}
diff --git a/src/main/java/com/layoutmanager/actions/NewLayoutAction.java b/src/main/java/com/layoutmanager/layout/store/create/NewLayoutAction.java
similarity index 57%
rename from src/main/java/com/layoutmanager/actions/NewLayoutAction.java
rename to src/main/java/com/layoutmanager/layout/store/create/NewLayoutAction.java
index 43c893b..6159a9d 100644
--- a/src/main/java/com/layoutmanager/actions/NewLayoutAction.java
+++ b/src/main/java/com/layoutmanager/layout/store/create/NewLayoutAction.java
@@ -1,20 +1,27 @@
-package com.layoutmanager.actions;
+package com.layoutmanager.layout.store.create;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.Presentation;
import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.wm.ToolWindowManager;
+
+import com.layoutmanager.layout.store.LayoutCreator;
import com.layoutmanager.localization.MessagesHelper;
-import com.layoutmanager.menu.WindowMenuService;
import com.layoutmanager.persistence.Layout;
import com.layoutmanager.persistence.LayoutConfig;
-import com.layoutmanager.ui.NotificationHelper;
+import com.layoutmanager.ui.helpers.BaloonNotificationHelper;
+import com.layoutmanager.ui.menu.WindowMenuService;
+
import org.jetbrains.annotations.NotNull;
public class NewLayoutAction extends AnAction {
- public NewLayoutAction() {
+ private final LayoutCreator layoutCreator;
+
+ public NewLayoutAction(LayoutCreator layoutCreator) {
+ this.layoutCreator = layoutCreator;
Presentation presentation = this.getTemplatePresentation();
presentation.setText(MessagesHelper.message("StoreLayout.New.Menu"));
presentation.setIcon(AllIcons.Welcome.CreateNewProject);
@@ -22,12 +29,13 @@ public NewLayoutAction() {
@Override
public void actionPerformed(@NotNull AnActionEvent event) {
- Layout updatedLayout = LayoutCreator.create(event.getProject(), "");
+ ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(event.getProject());
+ Layout newLayout = this.layoutCreator.create(toolWindowManager, "");
- if (updatedLayout != null) {
- this.storeLayout(updatedLayout);
- updateWindowMenuItems();
- showNotification(updatedLayout);
+ if (newLayout != null) {
+ this.storeLayout(newLayout);
+ this.updateWindowMenuItems();
+ this.showNotification(newLayout);
}
}
@@ -40,9 +48,9 @@ private void updateWindowMenuItems() {
windowMenuService.recreate();
}
- private void showNotification(Layout updatedLayout) {
- NotificationHelper.info(
+ private void showNotification(Layout newLayout) {
+ BaloonNotificationHelper.info(
MessagesHelper.message("StoreLayout.New.Notification.Title"),
- MessagesHelper.message("StoreLayout.New.Notification.Content", updatedLayout.getName()));
+ MessagesHelper.message("StoreLayout.New.Notification.Content", newLayout.getName()));
}
}
diff --git a/src/main/java/com/layoutmanager/actions/OverwriteLayoutAction.java b/src/main/java/com/layoutmanager/layout/store/overwrite/OverwriteLayoutAction.java
similarity index 66%
rename from src/main/java/com/layoutmanager/actions/OverwriteLayoutAction.java
rename to src/main/java/com/layoutmanager/layout/store/overwrite/OverwriteLayoutAction.java
index 6ae4712..ea95769 100644
--- a/src/main/java/com/layoutmanager/actions/OverwriteLayoutAction.java
+++ b/src/main/java/com/layoutmanager/layout/store/overwrite/OverwriteLayoutAction.java
@@ -1,23 +1,30 @@
-package com.layoutmanager.actions;
+package com.layoutmanager.layout.store.overwrite;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.Presentation;
import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.wm.ToolWindowManager;
+
+import com.layoutmanager.layout.store.LayoutCreator;
import com.layoutmanager.localization.MessagesHelper;
-import com.layoutmanager.menu.WindowMenuService;
import com.layoutmanager.persistence.Layout;
import com.layoutmanager.persistence.LayoutConfig;
-import com.layoutmanager.ui.NotificationHelper;
+import com.layoutmanager.ui.helpers.BaloonNotificationHelper;
+import com.layoutmanager.ui.menu.WindowMenuService;
+
import org.jetbrains.annotations.NotNull;
public class OverwriteLayoutAction extends AnAction {
+ private final LayoutCreator layoutCreator;
public final int number;
- public OverwriteLayoutAction(int number) {
-
+ public OverwriteLayoutAction(
+ LayoutCreator layoutCreator,
+ int number) {
+ this.layoutCreator = layoutCreator;
this.number = number;
Layout layout = LayoutConfig.getInstance().getLayout(number);
@@ -28,20 +35,21 @@ public OverwriteLayoutAction(int number) {
@Override
public void update(AnActionEvent e) {
- Layout layout = LayoutConfig.getInstance().getLayout(number);
+ Layout layout = LayoutConfig.getInstance().getLayout(this.number);
e.getPresentation().setText(layout.getName());
super.update(e);
}
@Override
public void actionPerformed(@NotNull AnActionEvent event) {
- Layout previousLayout = LayoutConfig.getInstance().getLayout(number);
- Layout updatedLayout = LayoutCreator.create(event.getProject(), previousLayout.getName());
+ Layout previousLayout = LayoutConfig.getInstance().getLayout(this.number);
+ ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(event.getProject());
+ Layout updatedLayout = this.layoutCreator.create(toolWindowManager, previousLayout.getName());
if (updatedLayout != null) {
this.storeLayout(updatedLayout);
- updateWindowMenuItems();
- showNotification(updatedLayout, previousLayout);
+ this.updateWindowMenuItems();
+ this.showNotification(updatedLayout, previousLayout);
}
}
@@ -55,7 +63,7 @@ private void updateWindowMenuItems() {
}
private void showNotification(Layout updatedLayout, Layout previousLayout) {
- NotificationHelper.info(
+ BaloonNotificationHelper.info(
MessagesHelper.message("StoreLayout.Overwrite.Notification.Title"),
MessagesHelper.message("StoreLayout.Overwrite.Notification.Content", previousLayout.getName(), updatedLayout.getName()));
}
diff --git a/src/main/java/com/layoutmanager/layout/store/smartdock/SmartDocker.java b/src/main/java/com/layoutmanager/layout/store/smartdock/SmartDocker.java
new file mode 100644
index 0000000..7f34382
--- /dev/null
+++ b/src/main/java/com/layoutmanager/layout/store/smartdock/SmartDocker.java
@@ -0,0 +1,59 @@
+package com.layoutmanager.layout.store.smartdock;
+
+import com.intellij.openapi.wm.ToolWindowManager;
+import com.intellij.openapi.wm.ToolWindowType;
+import com.intellij.openapi.wm.impl.ToolWindowImpl;
+import com.layoutmanager.layout.store.smartdock.dockers.ScreenBorderDocker;
+import com.layoutmanager.layout.store.smartdock.dockers.ToolWindowDocker;
+import com.layoutmanager.layout.store.smartdock.dockers.ToolWindowToScreenShrinker;
+import com.layoutmanager.persistence.Layout;
+import com.layoutmanager.persistence.ToolWindowInfo;
+import com.layoutmanager.ui.helpers.ScreenSizeHelper;
+
+import java.util.Arrays;
+
+public class SmartDocker {
+ private static final int THRESHOLD = 20;
+
+ private final ToolWindowManager toolWindowManager;
+ private final ToolWindowToScreenShrinker shrinker;
+ private final ToolWindowDocker toolWindowDocker;
+ private final ScreenBorderDocker screenBorderDocker;
+
+ public SmartDocker(
+ ToolWindowManager toolWindowManager,
+ ToolWindowToScreenShrinker shrinker,
+ ToolWindowDocker toolWindowDocker,
+ ScreenBorderDocker screenBorderDocker) {
+ this.toolWindowManager = toolWindowManager;
+ this.shrinker = shrinker;
+ this.toolWindowDocker = toolWindowDocker;
+ this.screenBorderDocker = screenBorderDocker;
+ }
+
+ public void dock(Layout layout) {
+ ToolWindowDocking[] floatedOrWindowsToolWindows = this.getFloatedOrWindowsToolWindows(layout);
+
+ this.shrinker.shrink(floatedOrWindowsToolWindows);
+ this.toolWindowDocker.dock(floatedOrWindowsToolWindows);
+ this.screenBorderDocker.dock(floatedOrWindowsToolWindows, THRESHOLD);
+ }
+
+
+ private ToolWindowDocking[] getFloatedOrWindowsToolWindows(Layout layout) {
+ return Arrays.stream(layout.getToolWindows())
+ .filter(this::isFloatingOrWindowedToolWindow)
+ .map(x -> new ToolWindowDocking(
+ x,
+ (ToolWindowImpl)this.toolWindowManager.getToolWindow(x.getId()),
+ ScreenSizeHelper.getContainingScreenBounds(x),
+ THRESHOLD))
+ .toArray(ToolWindowDocking[]::new);
+ }
+
+ private boolean isFloatingOrWindowedToolWindow(ToolWindowInfo toolWindow) {
+ return this.toolWindowManager.getToolWindow(toolWindow.getId()) != null &&
+ toolWindow.getType() == ToolWindowType.FLOATING ||
+ toolWindow.getType() == ToolWindowType.WINDOWED;
+ }
+}
diff --git a/src/main/java/com/layoutmanager/layout/store/smartdock/SmartDockerFactory.java b/src/main/java/com/layoutmanager/layout/store/smartdock/SmartDockerFactory.java
new file mode 100644
index 0000000..d1a1525
--- /dev/null
+++ b/src/main/java/com/layoutmanager/layout/store/smartdock/SmartDockerFactory.java
@@ -0,0 +1,16 @@
+package com.layoutmanager.layout.store.smartdock;
+
+import com.intellij.openapi.wm.ToolWindowManager;
+import com.layoutmanager.layout.store.smartdock.dockers.ScreenBorderDocker;
+import com.layoutmanager.layout.store.smartdock.dockers.ToolWindowDocker;
+import com.layoutmanager.layout.store.smartdock.dockers.ToolWindowToScreenShrinker;
+
+public class SmartDockerFactory {
+ public SmartDocker create(ToolWindowManager toolWindowManager) {
+ return new SmartDocker(
+ toolWindowManager,
+ new ToolWindowToScreenShrinker(),
+ new ToolWindowDocker(),
+ new ScreenBorderDocker());
+ }
+}
diff --git a/src/main/java/com/layoutmanager/layout/store/smartdock/ToolWindowDocking.java b/src/main/java/com/layoutmanager/layout/store/smartdock/ToolWindowDocking.java
new file mode 100644
index 0000000..428f0fc
--- /dev/null
+++ b/src/main/java/com/layoutmanager/layout/store/smartdock/ToolWindowDocking.java
@@ -0,0 +1,73 @@
+package com.layoutmanager.layout.store.smartdock;
+
+import com.intellij.openapi.wm.impl.ToolWindowImpl;
+import com.layoutmanager.persistence.ToolWindowInfo;
+
+import java.awt.Rectangle;
+
+public class ToolWindowDocking {
+
+ private final ToolWindowInfo toolWindowInfo;
+ private final ToolWindowImpl toolWindow;
+ private final Rectangle containingScreen;
+ private final int threshold;
+
+ public ToolWindowDocking(
+ ToolWindowInfo toolWindowInfo,
+ ToolWindowImpl toolWindow,
+ Rectangle containingScreen,
+ int threshold) {
+ this.toolWindowInfo = toolWindowInfo;
+ this.toolWindow = toolWindow;
+ this.containingScreen = containingScreen;
+ this.threshold = threshold;
+ }
+
+ public ToolWindowInfo getToolWindowInfo() {
+ return this.toolWindowInfo;
+ }
+
+ public ToolWindowImpl getToolWindow() {
+ return this.toolWindow;
+ }
+
+ public Rectangle getContainingScreen() {
+ return this.containingScreen;
+ }
+
+ public Rectangle getBounds() {
+ return this.toolWindowInfo.getBounds();
+ }
+
+ public Rectangle getLeftDockingBounds() {
+ return new Rectangle(
+ (int) Math.max(0, this.toolWindowInfo.getBounds().getX() - threshold),
+ (int) this.toolWindowInfo.getBounds().getY(),
+ this.threshold * 2,
+ (int) this.toolWindowInfo.getBounds().getHeight());
+ }
+
+ public Rectangle getTopDockingBounds() {
+ return new Rectangle(
+ (int) this.toolWindowInfo.getBounds().getX(),
+ (int) Math.max(0, this.toolWindowInfo.getBounds().getY() - threshold),
+ (int) this.toolWindowInfo.getBounds().getWidth(),
+ this.threshold * 2);
+ }
+
+ public Rectangle getRightDockingBounds() {
+ return new Rectangle(
+ (int) (this.toolWindowInfo.getBounds().getMaxX() - threshold),
+ (int) this.toolWindowInfo.getBounds().getY(),
+ (int) Math.min(this.threshold * 2, this.threshold + this.containingScreen.getMaxX() - this.toolWindowInfo.getBounds().getMaxX()),
+ (int) this.toolWindowInfo.getBounds().getHeight());
+ }
+
+ public Rectangle getBottomDockingBounds() {
+ return new Rectangle(
+ (int) this.toolWindowInfo.getBounds().getX(),
+ (int) this.toolWindowInfo.getBounds().getMaxY() - threshold,
+ (int) this.toolWindowInfo.getBounds().getWidth(),
+ (int) Math.min(this.threshold * 2, this.threshold + this.containingScreen.getMaxY() - this.toolWindowInfo.getBounds().getMaxY()));
+ }
+}
diff --git a/src/main/java/com/layoutmanager/layout/store/smartdock/dockers/ScreenBorderDocker.java b/src/main/java/com/layoutmanager/layout/store/smartdock/dockers/ScreenBorderDocker.java
new file mode 100644
index 0000000..8cad370
--- /dev/null
+++ b/src/main/java/com/layoutmanager/layout/store/smartdock/dockers/ScreenBorderDocker.java
@@ -0,0 +1,78 @@
+package com.layoutmanager.layout.store.smartdock.dockers;
+
+import com.layoutmanager.layout.store.smartdock.ToolWindowDocking;
+
+import java.awt.Rectangle;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+public class ScreenBorderDocker {
+ public void dock(ToolWindowDocking[] floatedOrWindowsToolWindows, int threshold) {
+ this.pinLeft(floatedOrWindowsToolWindows, threshold);
+ this.pinTop(floatedOrWindowsToolWindows, threshold);
+ this.pinRight(floatedOrWindowsToolWindows, threshold);
+ this.pinBottom(floatedOrWindowsToolWindows, threshold);
+ }
+
+ private void pinLeft(ToolWindowDocking[] floatedOrWindowsToolWindows, int threshold) {
+ this.pinToScreenBorder(
+ floatedOrWindowsToolWindows,
+ threshold,
+ toolWindowDocking -> (int)(toolWindowDocking.getBounds().getX() - toolWindowDocking.getContainingScreen().getX()),
+ (toolWindowDocking, difference) -> new Rectangle(
+ (int)toolWindowDocking.getContainingScreen().getX(),
+ (int)toolWindowDocking.getBounds().getY(),
+ (int)(toolWindowDocking.getBounds().getWidth() + difference),
+ (int)toolWindowDocking.getBounds().getHeight()));
+ }
+
+ private void pinTop(ToolWindowDocking[] floatedOrWindowsToolWindows, int threshold) {
+ this.pinToScreenBorder(
+ floatedOrWindowsToolWindows,
+ threshold,
+ x -> (int)(x.getBounds().getY() - x.getContainingScreen().getY()),
+ (toolWindowDocking, difference) -> new Rectangle(
+ (int)toolWindowDocking.getBounds().getX(),
+ (int)toolWindowDocking.getContainingScreen().getY(),
+ (int)toolWindowDocking.getBounds().getWidth(),
+ (int)(toolWindowDocking.getBounds().getHeight() + 5)));
+ }
+
+ private void pinRight(ToolWindowDocking[] floatedOrWindowsToolWindows, int threshold) {
+ this.pinToScreenBorder(
+ floatedOrWindowsToolWindows,
+ threshold,
+ toolWindowDocking -> (int)(toolWindowDocking.getContainingScreen().getMaxX() - toolWindowDocking.getBounds().getMaxX()),
+ (toolWindowDocking, difference) -> new Rectangle(
+ (int)toolWindowDocking.getBounds().getX(),
+ (int)toolWindowDocking.getBounds().getY(),
+ (int)(toolWindowDocking.getBounds().getWidth() + difference),
+ (int)toolWindowDocking.getBounds().getHeight()));
+ }
+
+ private void pinBottom(ToolWindowDocking[] floatedOrWindowsToolWindows, int threshold) {
+ this.pinToScreenBorder(
+ floatedOrWindowsToolWindows,
+ threshold,
+ toolWindowDocking -> (int)(toolWindowDocking.getBounds().getY() - toolWindowDocking.getContainingScreen().getY()),
+ (toolWindowDocking, difference) -> new Rectangle(
+ (int)toolWindowDocking.getBounds().getX(),
+ (int)toolWindowDocking.getBounds().getY(),
+ (int)toolWindowDocking.getBounds().getWidth(),
+ (int)(toolWindowDocking.getBounds().getHeight() + difference)));
+ }
+
+ private void pinToScreenBorder(
+ ToolWindowDocking[] toolWindowDockings,
+ int threshold,
+ Function getDifference,
+ BiFunction calculateBounds) {
+
+ for (ToolWindowDocking toolWindowDocking : toolWindowDockings) {
+ Integer difference = getDifference.apply(toolWindowDocking);
+ if (difference != 0 && difference < threshold) {
+ toolWindowDocking.getToolWindowInfo().setBounds(calculateBounds.apply(toolWindowDocking, difference));
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/layoutmanager/layout/store/smartdock/dockers/ToolWindowDocker.java b/src/main/java/com/layoutmanager/layout/store/smartdock/dockers/ToolWindowDocker.java
new file mode 100644
index 0000000..1f4c035
--- /dev/null
+++ b/src/main/java/com/layoutmanager/layout/store/smartdock/dockers/ToolWindowDocker.java
@@ -0,0 +1,105 @@
+package com.layoutmanager.layout.store.smartdock.dockers;
+
+import com.layoutmanager.layout.store.smartdock.ToolWindowDocking;
+
+import java.awt.Rectangle;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.function.BiFunction;
+import java.util.function.BiPredicate;
+
+public class ToolWindowDocker {
+ public void dock(ToolWindowDocking[] floatedOrWindowsToolWindows) {
+ this.dockLeft(floatedOrWindowsToolWindows);
+ this.dockTop(floatedOrWindowsToolWindows);
+ this.dockRight(floatedOrWindowsToolWindows);
+ this.dockBottom(floatedOrWindowsToolWindows);
+ }
+
+ private void dockLeft(ToolWindowDocking[] floatedOrWindowsToolWindows) {
+ this.dockToolWindow(
+ floatedOrWindowsToolWindows,
+ (x, y) -> (int) (x.getBounds().getX() - y.getBounds().getX()),
+ (x, y) -> x.getRightDockingBounds().intersects(y.getLeftDockingBounds()),
+ Comparator.comparingInt(x -> (int) x.getBounds().getY()),
+ (toolWindow, boundsToDock) -> {
+ int x = (int) toolWindow.getBounds().getX();
+ int newX = (int) boundsToDock.getMaxX();
+ return new Rectangle(
+ newX,
+ (int) toolWindow.getBounds().getY(),
+ (int) (toolWindow.getBounds().getWidth() + (x - newX)),
+ (int) toolWindow.getBounds().getHeight());
+ });
+ }
+
+ private void dockTop(ToolWindowDocking[] floatedOrWindowsToolWindows) {
+ this.dockToolWindow(
+ floatedOrWindowsToolWindows,
+ (x, y) -> (int) (x.getBounds().getY() - y.getBounds().getY()),
+ (x, y) -> x.getBottomDockingBounds().intersects(y.getTopDockingBounds()),
+ Comparator.comparingInt(x -> (int) x.getBounds().getX()),
+ (toolWindow, boundsToDock) -> {
+ int y = (int) toolWindow.getBounds().getY();
+ int newY = (int) boundsToDock.getMaxY();
+ return new Rectangle(
+ (int) toolWindow.getBounds().getX(),
+ newY,
+ (int) toolWindow.getBounds().getWidth(),
+ (int) (toolWindow.getBounds().getHeight() + (y - newY)));
+ });
+ }
+
+ private void dockRight(ToolWindowDocking[] floatedOrWindowsToolWindows) {
+ this.dockToolWindow(
+ floatedOrWindowsToolWindows,
+ (x, y) -> (int) (x.getBounds().getMaxX() - y.getBounds().getMaxX()),
+ (x, y) -> x.getLeftDockingBounds().intersects(y.getRightDockingBounds()),
+ Comparator.comparingInt(x -> (int) x.getBounds().getY()),
+ (toolWindow, boundsToDock) -> new Rectangle(
+ (int) toolWindow.getBounds().getX(),
+ (int) toolWindow.getBounds().getY(),
+ (int) (boundsToDock.getX() - toolWindow.getBounds().getX()),
+ (int) toolWindow.getBounds().getHeight()));
+ }
+
+ private void dockBottom(ToolWindowDocking[] floatedOrWindowsToolWindows) {
+ this.dockToolWindow(
+ floatedOrWindowsToolWindows,
+ (x, y) -> (int) (x.getBounds().getMaxY() - y.getBounds().getMaxY()),
+ (x, y) -> x.getTopDockingBounds().intersects(y.getBottomDockingBounds()),
+ Comparator.comparingInt(x -> (int) x.getBounds().getY()),
+ (toolWindow, boundsToDock) -> new Rectangle(
+ (int) toolWindow.getBounds().getX(),
+ (int) toolWindow.getBounds().getY(),
+ (int) toolWindow.getBounds().getWidth(),
+ (int) (boundsToDock.getY() - toolWindow.getBounds().getY())));
+ }
+
+ private void dockToolWindow(
+ ToolWindowDocking[] floatedOrWindowsToolWindows,
+ Comparator super ToolWindowDocking> sortForProcessing,
+ BiPredicate isIntersecting,
+ Comparator super ToolWindowDocking> sortForClosestToDock,
+ BiFunction calculateNewBounds) {
+
+ ToolWindowDocking[] sortedByLeftPosition = Arrays.stream(floatedOrWindowsToolWindows)
+ .sorted(sortForProcessing)
+ .skip(1)
+ .toArray(ToolWindowDocking[]::new);
+
+ for (ToolWindowDocking toolWindowDocking : sortedByLeftPosition) {
+ ToolWindowDocking dockingTarget = Arrays.stream(floatedOrWindowsToolWindows)
+ .filter(x ->
+ !x.getToolWindowInfo().getId().equals(toolWindowDocking.getToolWindowInfo().getId()) &&
+ isIntersecting.test(x, toolWindowDocking))
+ .min(sortForClosestToDock)
+ .orElse(null);
+
+ if (dockingTarget != null) {
+ Rectangle newBounds = calculateNewBounds.apply(toolWindowDocking, dockingTarget.getBounds());
+ toolWindowDocking.getToolWindowInfo().setBounds(newBounds);
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/layoutmanager/layout/store/smartdock/dockers/ToolWindowToScreenShrinker.java b/src/main/java/com/layoutmanager/layout/store/smartdock/dockers/ToolWindowToScreenShrinker.java
new file mode 100644
index 0000000..7a490f5
--- /dev/null
+++ b/src/main/java/com/layoutmanager/layout/store/smartdock/dockers/ToolWindowToScreenShrinker.java
@@ -0,0 +1,26 @@
+package com.layoutmanager.layout.store.smartdock.dockers;
+
+import com.layoutmanager.layout.store.smartdock.ToolWindowDocking;
+
+import java.awt.Rectangle;
+
+public class ToolWindowToScreenShrinker {
+ public void shrink(ToolWindowDocking[] floatedOrWindowsToolWindowDockings) {
+ for (ToolWindowDocking toolWindowDocking : floatedOrWindowsToolWindowDockings) {
+ Rectangle containingScreen = toolWindowDocking.getContainingScreen();
+ Rectangle toolWindowBounds = toolWindowDocking.getBounds();
+
+ if (!containingScreen.contains(toolWindowBounds)) {
+ double x = Math.max(toolWindowBounds.getX(), containingScreen.getX());
+ double y = Math.max(toolWindowBounds.getY(), containingScreen.getY());
+ Rectangle newToolWindowBounds = new Rectangle(
+ (int)x,
+ (int)y,
+ (int)Math.min(toolWindowBounds.getWidth(), containingScreen.getMaxX() - x),
+ (int)Math.min(toolWindowBounds.getHeight(), containingScreen.getMaxY() - y));
+
+ toolWindowDocking.getToolWindowInfo().setBounds(newToolWindowBounds);
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/layoutmanager/actions/LayoutValidationHelper.java b/src/main/java/com/layoutmanager/layout/store/validation/LayoutValidationHelper.java
similarity index 88%
rename from src/main/java/com/layoutmanager/actions/LayoutValidationHelper.java
rename to src/main/java/com/layoutmanager/layout/store/validation/LayoutValidationHelper.java
index 013be40..4e0e658 100644
--- a/src/main/java/com/layoutmanager/actions/LayoutValidationHelper.java
+++ b/src/main/java/com/layoutmanager/layout/store/validation/LayoutValidationHelper.java
@@ -1,15 +1,15 @@
-package com.layoutmanager.actions;
+package com.layoutmanager.layout.store.validation;
import com.intellij.openapi.wm.ToolWindowType;
import com.intellij.openapi.wm.WindowManager;
import com.layoutmanager.persistence.Layout;
import com.layoutmanager.persistence.ToolWindowInfo;
-import java.awt.*;
+import java.awt.Rectangle;
import java.util.stream.Stream;
public class LayoutValidationHelper {
- public static ToolWindowInfo[] retrieveToolWindowsOutsideOfScreen(Layout layout){
+ public static ToolWindowInfo[] retrieveToolWindowsOutsideOfScreen(Layout layout) {
return Stream
.of(layout.getToolWindows())
.filter(x -> x.isVisible() && isWindowType(x) && !isValid(x))
diff --git a/src/main/java/com/layoutmanager/localization/MessagesHelper.java b/src/main/java/com/layoutmanager/localization/MessagesHelper.java
index 46d402c..c7f9412 100644
--- a/src/main/java/com/layoutmanager/localization/MessagesHelper.java
+++ b/src/main/java/com/layoutmanager/localization/MessagesHelper.java
@@ -1,17 +1,17 @@
package com.layoutmanager.localization;
-
import com.intellij.CommonBundle;
import com.intellij.reference.SoftReference;
-import org.jetbrains.annotations.NonNls;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.PropertyKey;
import java.lang.ref.Reference;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.PropertyKey;
+
public class MessagesHelper {
@NonNls
public static final String BUNDLE_NAME = "com.layoutmanager.ui.messages";
diff --git a/src/main/java/com/layoutmanager/menu/WindowMenuService.java b/src/main/java/com/layoutmanager/menu/WindowMenuService.java
deleted file mode 100644
index 05d8e6d..0000000
--- a/src/main/java/com/layoutmanager/menu/WindowMenuService.java
+++ /dev/null
@@ -1,78 +0,0 @@
-package com.layoutmanager.menu;
-
-import com.intellij.openapi.actionSystem.ActionManager;
-import com.intellij.openapi.actionSystem.DefaultActionGroup;
-import com.layoutmanager.actions.DeleteLayoutAction;
-import com.layoutmanager.actions.NewLayoutAction;
-import com.layoutmanager.actions.OverwriteLayoutAction;
-import com.layoutmanager.actions.RestoreLayoutAction;
-import com.layoutmanager.localization.MessagesHelper;
-import com.layoutmanager.persistence.Layout;
-import com.layoutmanager.persistence.LayoutConfig;
-
-public class WindowMenuService {
- private DefaultActionGroup storeLayout;
- private DefaultActionGroup restoreLayout;
- private DefaultActionGroup deleteLayout;
-
- public void create() {
- if (storeLayout != null) {
- return;
- }
-
- createMainActions( (DefaultActionGroup) ActionManager.getInstance().getAction("WindowMenu"));
- createStoreRestoreActions();
- }
-
- public void recreate() {
- this.restoreLayout.removeAll();
- this.storeLayout.removeAll();
- this.deleteLayout.removeAll();
-
- createStoreRestoreActions();
- }
-
- private void createMainActions(DefaultActionGroup windowMenu) {
- this.storeLayout = createMainAction(MessagesHelper.message("StoreLayout.Menu"), windowMenu);
- this.restoreLayout = createMainAction(MessagesHelper.message("RestoreLayout.Menu"), windowMenu);
- this.deleteLayout = createMainAction(MessagesHelper.message("DeleteLayout.Menu"), windowMenu);
- }
-
- private DefaultActionGroup createMainAction(String name, DefaultActionGroup windowMenu){
- DefaultActionGroup windowMenuItem = new DefaultActionGroup(name, true);
- windowMenu.add(windowMenuItem);
-
- return windowMenuItem;
- }
-
- private void createStoreRestoreActions() {
- LayoutConfig config = LayoutConfig.getInstance();
- addStoreLayoutActions(config);
- addRestoreLayoutActions(config);
- addDeleteLayoutActions(config);
- }
-
- private void addStoreLayoutActions(LayoutConfig config) {
- for (int index = 0; index < config.getLayoutCount(); index++) {
- this.storeLayout.add(new OverwriteLayoutAction(index));
- }
-
- if (config.getLayoutCount() > 0) {
- this.storeLayout.addSeparator();
- }
-
- this.storeLayout.add(new NewLayoutAction());
- }
-
- private void addRestoreLayoutActions(LayoutConfig config) {
- for (Layout layout : config.getLayouts()) {
- this.restoreLayout.add(new RestoreLayoutAction(layout));
- }
- }
-
- private void addDeleteLayoutActions(LayoutConfig config) {
- for (Layout layout : config.getLayouts()) {
- this.deleteLayout.add(new DeleteLayoutAction(layout));
- }
- }
-}
diff --git a/src/main/java/com/layoutmanager/persistence/Layout.java b/src/main/java/com/layoutmanager/persistence/Layout.java
index c2d716d..e7675fd 100644
--- a/src/main/java/com/layoutmanager/persistence/Layout.java
+++ b/src/main/java/com/layoutmanager/persistence/Layout.java
@@ -1,15 +1,19 @@
package com.layoutmanager.persistence;
+import java.util.Arrays;
+import java.util.Objects;
+
public class Layout {
private String name;
private int editorTabPlacement;
private ToolWindowInfo[] toolWindows;
+ @SuppressWarnings({"unused", "Used for serialization."})
public Layout() {
- name = "";
- toolWindows = new ToolWindowInfo[0];
- editorTabPlacement = -1;
+ this.name = "";
+ this.toolWindows = new ToolWindowInfo[0];
+ this.editorTabPlacement = -1;
}
public Layout(String name, ToolWindowInfo[] toolWindows, int editorTabPlacement) {
@@ -28,7 +32,7 @@ public void setName(String name) {
}
public int getEditorPlacement() {
- return editorTabPlacement;
+ return this.editorTabPlacement;
}
@SuppressWarnings({"unused", "Used for serialization."})
@@ -37,11 +41,34 @@ public void setEditorPlacement(int editorTabPlacement) {
}
public ToolWindowInfo[] getToolWindows() {
- return toolWindows;
+ return this.toolWindows;
}
@SuppressWarnings({"unused", "Used for serialization."})
public void setToolWindows(ToolWindowInfo[] toolWindows) {
this.toolWindows = toolWindows;
}
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+
+ if (other == null || getClass() != other.getClass()) {
+ return false;
+ }
+
+ Layout that = (Layout) other;
+ return this.editorTabPlacement == that.editorTabPlacement &&
+ this.name.equals(that.name) &&
+ Arrays.equals(this.toolWindows, that.toolWindows);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Objects.hash(this.name, this.editorTabPlacement);
+ result = 31 * result + Arrays.hashCode(toolWindows);
+ return result;
+ }
}
\ No newline at end of file
diff --git a/src/main/java/com/layoutmanager/persistence/LayoutConfig.java b/src/main/java/com/layoutmanager/persistence/LayoutConfig.java
index 04d3b4d..625b822 100644
--- a/src/main/java/com/layoutmanager/persistence/LayoutConfig.java
+++ b/src/main/java/com/layoutmanager/persistence/LayoutConfig.java
@@ -6,13 +6,13 @@
import com.intellij.openapi.components.Storage;
import com.intellij.util.xmlb.XmlSerializerUtil;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
@State(
name = "Layout",
storages = {
@@ -21,6 +21,7 @@
)
public class LayoutConfig implements PersistentStateComponent {
private List layouts = new ArrayList<>();
+ private LayoutSettings settings = new LayoutSettings();
@Nullable
@Override
@@ -44,6 +45,19 @@ public Layout[] getLayouts() {
.toArray(Layout[]::new);
}
+ @SuppressWarnings({"unused", "Used for serialization."})
+ public void setLayouts(Layout[] layouts) {
+ this.layouts = new ArrayList<>(Arrays.asList(layouts));
+ }
+
+ public LayoutSettings getSettings() {
+ return this.settings;
+ }
+
+ public void setSettings(LayoutSettings settings) {
+ this.settings = settings;
+ }
+
public int getLayoutCount() {
return this.layouts.size();
}
@@ -64,10 +78,5 @@ public void removeLayout(Layout layout) {
public static LayoutConfig getInstance() {
return ServiceManager.getService(LayoutConfig.class);
}
-
- @SuppressWarnings({"unused", "Used for serialization."})
- public void setLayouts(Layout[] layouts) {
- this.layouts = new ArrayList<>(Arrays.asList(layouts));
- }
}
diff --git a/src/main/java/com/layoutmanager/persistence/LayoutSettings.java b/src/main/java/com/layoutmanager/persistence/LayoutSettings.java
new file mode 100644
index 0000000..3e32356
--- /dev/null
+++ b/src/main/java/com/layoutmanager/persistence/LayoutSettings.java
@@ -0,0 +1,13 @@
+package com.layoutmanager.persistence;
+
+public class LayoutSettings {
+ private boolean useSmartDock = true;
+
+ public boolean getUseSmartDock() {
+ return this.useSmartDock;
+ }
+
+ public void setUseSmartDock(boolean useSmartDock) {
+ this.useSmartDock = useSmartDock;
+ }
+}
diff --git a/src/main/java/com/layoutmanager/persistence/ToolWindowInfo.java b/src/main/java/com/layoutmanager/persistence/ToolWindowInfo.java
index cc2eec5..5190faa 100644
--- a/src/main/java/com/layoutmanager/persistence/ToolWindowInfo.java
+++ b/src/main/java/com/layoutmanager/persistence/ToolWindowInfo.java
@@ -2,7 +2,8 @@
import com.intellij.openapi.wm.ToolWindowType;
-import java.awt.*;
+import java.awt.Rectangle;
+import java.util.Objects;
public class ToolWindowInfo {
private String id;
@@ -87,7 +88,31 @@ public void setVisible(boolean visible) {
}
@SuppressWarnings({"unused", "Used for serialization."})
- public void setIsToolWindow(boolean isSplit) {
+ public void setIsToolWindow(boolean isToolWindow) {
this.isToolWindow = isToolWindow;
}
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+
+ if (other == null || getClass() != other.getClass()) {
+ return false;
+ }
+
+ ToolWindowInfo that = (ToolWindowInfo) other;
+ return this.isVisible == that.isVisible &&
+ this.isToolWindow == that.isToolWindow &&
+ this.id.equals(that.id) &&
+ this.type == that.type &&
+ this.anchor.equals(that.anchor) &&
+ this.bounds.equals(that.bounds);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.id, this.type, this.anchor, this.bounds, this.isVisible, this.isToolWindow);
+ }
}
diff --git a/src/main/java/com/layoutmanager/startup/PluginBootstrapper.java b/src/main/java/com/layoutmanager/startup/PluginBootstrapper.java
index ce24aba..4cfe15f 100644
--- a/src/main/java/com/layoutmanager/startup/PluginBootstrapper.java
+++ b/src/main/java/com/layoutmanager/startup/PluginBootstrapper.java
@@ -4,7 +4,7 @@
import com.intellij.openapi.project.Project;
import com.intellij.openapi.startup.StartupActivity;
import com.layoutmanager.cleanup.EmptyLayoutRemoverService;
-import com.layoutmanager.menu.WindowMenuService;
+import com.layoutmanager.ui.menu.WindowMenuService;
import org.jetbrains.annotations.NotNull;
public class PluginBootstrapper implements StartupActivity {
diff --git a/src/main/java/com/layoutmanager/ui/ToolWindowHelper.java b/src/main/java/com/layoutmanager/ui/ToolWindowHelper.java
deleted file mode 100644
index 87a5295..0000000
--- a/src/main/java/com/layoutmanager/ui/ToolWindowHelper.java
+++ /dev/null
@@ -1,65 +0,0 @@
-package com.layoutmanager.ui;
-
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.wm.ToolWindowType;
-import com.intellij.openapi.wm.impl.FloatingDecorator;
-import com.intellij.openapi.wm.impl.InternalDecorator;
-import com.intellij.openapi.wm.impl.ToolWindowImpl;
-import com.intellij.util.ui.UIUtil;
-import org.jetbrains.annotations.Nullable;
-
-import javax.swing.*;
-import java.awt.*;
-
-public class ToolWindowHelper {
-
- private static final Logger log = Logger.getInstance(ToolWindowHelper.class);
-
- public static Rectangle getBounds(ToolWindowImpl toolWindow) {
- try {
- if (toolWindow.isVisible()) {
- if (toolWindow.getType() == ToolWindowType.FLOATING) {
- FloatingDecorator floatingDecorator = getFloatingDecorator(toolWindow);
- return floatingDecorator.getBounds();
- } else if (toolWindow.getType() == ToolWindowType.WINDOWED) {
- Window window = getWindow(toolWindow);
- return window.getBounds();
- }
- }
- }
- catch(Exception e) {
- log.error("Could not fetch bounds of window " + toolWindow.getId() + "\n" + e.getStackTrace());
- }
-
- return new Rectangle(0, 0, 0, 0);
- }
-
- public static void setBounds(ToolWindowImpl toolWindow, Rectangle bounds) {
- try {
- if (toolWindow.isVisible()) {
- if (toolWindow.getType() == ToolWindowType.FLOATING) {
- FloatingDecorator floatingDecorator = getFloatingDecorator(toolWindow);
- floatingDecorator.setBounds(bounds);
- } else if (toolWindow.getType() == ToolWindowType.WINDOWED) {
- Window window = getWindow(toolWindow);
- window.setBounds(bounds);
- }
- }
- }
- catch(Exception e) {
- log.error("Could not set bounds of window " + toolWindow.getId() + "\n" + e.getStackTrace());
- }
- }
-
- @Nullable
- private static FloatingDecorator getFloatingDecorator(ToolWindowImpl toolWindow) {
- InternalDecorator decorator = toolWindow.getDecorator();
- return (FloatingDecorator) SwingUtilities.getAncestorOfClass(FloatingDecorator.class, decorator);
- }
-
- @Nullable
- private static Window getWindow(ToolWindowImpl toolWindow) {
- JComponent component = toolWindow.getComponent();
- return UIUtil.getWindow(component);
- }
-}
diff --git a/src/main/java/com/layoutmanager/ui/dialogs/LayoutNameDialog.java b/src/main/java/com/layoutmanager/ui/dialogs/LayoutNameDialog.java
new file mode 100644
index 0000000..3abc064
--- /dev/null
+++ b/src/main/java/com/layoutmanager/ui/dialogs/LayoutNameDialog.java
@@ -0,0 +1,30 @@
+package com.layoutmanager.ui.dialogs;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.openapi.ui.Messages;
+import com.layoutmanager.localization.MessagesHelper;
+
+public class LayoutNameDialog {
+
+ private final LayoutNameValidator layoutNameValidator;
+
+ public LayoutNameDialog(LayoutNameValidator layoutNameValidator) {
+
+ this.layoutNameValidator = layoutNameValidator;
+ }
+
+ public String show(String defaultName) {
+ String name;
+ do {
+ name = Messages.showInputDialog(
+ MessagesHelper.message("StoreLayout.Dialog.Title"),
+ MessagesHelper.message("StoreLayout.Dialog.Content"),
+ AllIcons.Actions.Edit,
+ defaultName,
+ null);
+ } while (name != null && !this.layoutNameValidator.isValid(name));
+
+ return name;
+ }
+}
+
diff --git a/src/main/java/com/layoutmanager/ui/dialogs/LayoutNameValidator.java b/src/main/java/com/layoutmanager/ui/dialogs/LayoutNameValidator.java
new file mode 100644
index 0000000..971d852
--- /dev/null
+++ b/src/main/java/com/layoutmanager/ui/dialogs/LayoutNameValidator.java
@@ -0,0 +1,11 @@
+package com.layoutmanager.ui.dialogs;
+
+public class LayoutNameValidator {
+ public Boolean isValid(String name) {
+ return name != null && !this.isBlank(name);
+ }
+
+ private boolean isBlank(String name) {
+ return name.trim().length() == 0;
+ }
+}
diff --git a/src/main/java/com/layoutmanager/ui/NotificationHelper.java b/src/main/java/com/layoutmanager/ui/helpers/BaloonNotificationHelper.java
similarity index 94%
rename from src/main/java/com/layoutmanager/ui/NotificationHelper.java
rename to src/main/java/com/layoutmanager/ui/helpers/BaloonNotificationHelper.java
index ffc8e10..b2bf895 100644
--- a/src/main/java/com/layoutmanager/ui/NotificationHelper.java
+++ b/src/main/java/com/layoutmanager/ui/helpers/BaloonNotificationHelper.java
@@ -1,41 +1,41 @@
-package com.layoutmanager.ui;
-
-import com.intellij.notification.Notification;
-import com.intellij.notification.NotificationDisplayType;
-import com.intellij.notification.NotificationGroup;
-import com.intellij.notification.Notifications;
-import com.intellij.notification.NotificationType;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.project.ProjectManager;
-
-import java.util.Arrays;
-import java.util.UUID;
-
-public class NotificationHelper {
- private static final NotificationGroup DEFAULT_GROUP =
- new NotificationGroup("demo.notifications.balloon", NotificationDisplayType.STICKY_BALLOON, true);
-
- public static void info(String title, String content) {
- Notification notification = DEFAULT_GROUP.createNotification(title, null, content, NotificationType.INFORMATION);
- notify(notification);
- }
-
- public static void warning(String title, String content) {
- String groupId = UUID.nameUUIDFromBytes(title.getBytes()).toString();
- NotificationGroup groupByNotificationTitle = new NotificationGroup(groupId, NotificationDisplayType.STICKY_BALLOON, true);
- Notification notification = groupByNotificationTitle.createNotification(title, null, content, NotificationType.WARNING);
- notify(notification);
- }
-
- private static void notify(Notification notification) {
- Project currentProject = getCurrentProject();
- Notifications.Bus.notify(notification, currentProject);
- }
-
- private static Project getCurrentProject() {
- Project[] openedProjects = ProjectManager.getInstance().getOpenProjects();
- return Arrays.stream(openedProjects)
- .findFirst()
- .orElse(null);
- }
-}
+package com.layoutmanager.ui.helpers;
+
+import com.intellij.notification.Notification;
+import com.intellij.notification.NotificationDisplayType;
+import com.intellij.notification.NotificationGroup;
+import com.intellij.notification.NotificationType;
+import com.intellij.notification.Notifications;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ProjectManager;
+
+import java.util.Arrays;
+import java.util.UUID;
+
+public class BaloonNotificationHelper {
+ private static final NotificationGroup DEFAULT_GROUP =
+ new NotificationGroup("demo.notifications.balloon", NotificationDisplayType.STICKY_BALLOON, true);
+
+ public static void info(String title, String content) {
+ Notification notification = DEFAULT_GROUP.createNotification(title, null, content, NotificationType.INFORMATION);
+ notify(notification);
+ }
+
+ public static void warning(String title, String content) {
+ String groupId = UUID.nameUUIDFromBytes(title.getBytes()).toString();
+ NotificationGroup groupByNotificationTitle = new NotificationGroup(groupId, NotificationDisplayType.STICKY_BALLOON, true);
+ Notification notification = groupByNotificationTitle.createNotification(title, null, content, NotificationType.WARNING);
+ notify(notification);
+ }
+
+ private static void notify(Notification notification) {
+ Project currentProject = getCurrentProject();
+ Notifications.Bus.notify(notification, currentProject);
+ }
+
+ private static Project getCurrentProject() {
+ Project[] openedProjects = ProjectManager.getInstance().getOpenProjects();
+ return Arrays.stream(openedProjects)
+ .findFirst()
+ .orElse(null);
+ }
+}
diff --git a/src/main/java/com/layoutmanager/ui/helpers/ComponentNotificationHelper.java b/src/main/java/com/layoutmanager/ui/helpers/ComponentNotificationHelper.java
new file mode 100644
index 0000000..7a58cf1
--- /dev/null
+++ b/src/main/java/com/layoutmanager/ui/helpers/ComponentNotificationHelper.java
@@ -0,0 +1,29 @@
+package com.layoutmanager.ui.helpers;
+
+import com.intellij.openapi.ui.MessageType;
+import com.intellij.openapi.ui.popup.Balloon;
+import com.intellij.openapi.ui.popup.JBPopupFactory;
+import com.intellij.ui.awt.RelativePoint;
+
+import javax.swing.*;
+
+public class ComponentNotificationHelper {
+
+ public static void info(JComponent component, String message) {
+ show(component, message, MessageType.INFO);
+ }
+
+ public static void error(JComponent component, String message) {
+ show(component, message, MessageType.ERROR);
+ }
+
+ private static void show(JComponent component, String message, MessageType type) {
+ JBPopupFactory.getInstance()
+ .createHtmlTextBalloonBuilder(message, type, null)
+ .setFadeoutTime(7500)
+ .createBalloon()
+ .show(
+ RelativePoint.getCenterOf(component),
+ Balloon.Position.above);
+ }
+}
diff --git a/src/main/java/com/layoutmanager/ui/helpers/ScreenSizeHelper.java b/src/main/java/com/layoutmanager/ui/helpers/ScreenSizeHelper.java
new file mode 100644
index 0000000..8524fc2
--- /dev/null
+++ b/src/main/java/com/layoutmanager/ui/helpers/ScreenSizeHelper.java
@@ -0,0 +1,38 @@
+package com.layoutmanager.ui.helpers;
+
+import com.layoutmanager.persistence.ToolWindowInfo;
+
+import java.awt.GraphicsDevice;
+import java.awt.GraphicsEnvironment;
+import java.awt.Rectangle;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+import sun.java2d.SunGraphicsEnvironment;
+
+public class ScreenSizeHelper {
+ public static Rectangle getContainingScreenBounds(ToolWindowInfo toolWindow) {
+ return Arrays
+ .stream(GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices())
+ .map(ScreenSizeHelper::getScreenRectangle)
+ .min(Comparator.comparingInt(x -> -getIntersectionSize(x, toolWindow.getBounds())))
+ .orElse(new Rectangle());
+ }
+
+
+
+ private static Rectangle getScreenRectangle(GraphicsDevice device) {
+ Rectangle defaultBounds = SunGraphicsEnvironment.getUsableBounds(device);
+ return new Rectangle(
+ (int)defaultBounds.getX(),
+ (int)defaultBounds.getY(),
+ (int)defaultBounds.getWidth(),
+ (int)defaultBounds.getHeight());
+ }
+
+ private static int getIntersectionSize(Rectangle screen, Rectangle window) {
+ return (int)(Math.max(0, Math.min(screen.getMaxX(), window.getMaxX()) - Math.max(screen.getX(), window.getX())) *
+ Math.max(0, Math.min(screen.getMaxY(), window.getMaxY()) - Math.max(screen.getY(), window.getY())));
+ }
+}
diff --git a/src/main/java/com/layoutmanager/ui/helpers/ToolWindowHelper.java b/src/main/java/com/layoutmanager/ui/helpers/ToolWindowHelper.java
new file mode 100644
index 0000000..9b65b35
--- /dev/null
+++ b/src/main/java/com/layoutmanager/ui/helpers/ToolWindowHelper.java
@@ -0,0 +1,65 @@
+package com.layoutmanager.ui.helpers;
+
+import com.intellij.openapi.wm.ToolWindowAnchor;
+import com.intellij.openapi.wm.ToolWindowType;
+import com.intellij.openapi.wm.impl.FloatingDecorator;
+import com.intellij.openapi.wm.impl.InternalDecorator;
+import com.intellij.openapi.wm.impl.ToolWindowImpl;
+import com.intellij.util.ui.UIUtil;
+
+import java.awt.Rectangle;
+import java.awt.Window;
+import javax.swing.JComponent;
+import javax.swing.SwingUtilities;
+
+import org.jetbrains.annotations.Nullable;
+
+public class ToolWindowHelper {
+ public static Rectangle getBounds(ToolWindowImpl toolWindow) {
+ if (toolWindow.isVisible()) {
+ if (toolWindow.getType() == ToolWindowType.FLOATING) {
+ FloatingDecorator floatingDecorator = getFloatingDecorator(toolWindow);
+ return floatingDecorator.getBounds();
+ } else if (toolWindow.getType() == ToolWindowType.WINDOWED) {
+ Window window = getWindow(toolWindow);
+ return window.getBounds();
+ } else {
+ InternalDecorator decorator = toolWindow.getDecorator();
+ return decorator.getBounds();
+ }
+ }
+
+ return new Rectangle(0, 0, 0, 0);
+ }
+
+ public static void setBounds(ToolWindowImpl toolWindow, Rectangle bounds) {
+ if (toolWindow.isVisible()) {
+ if (toolWindow.getType() == ToolWindowType.FLOATING) {
+ FloatingDecorator floatingDecorator = getFloatingDecorator(toolWindow);
+ floatingDecorator.setBounds(bounds);
+ } else if (toolWindow.getType() == ToolWindowType.WINDOWED) {
+ Window window = getWindow(toolWindow);
+ window.setBounds(bounds);
+ } else {
+ Rectangle currentBounds = toolWindow.getDecorator().getBounds();
+ if (toolWindow.getAnchor() == ToolWindowAnchor.TOP || toolWindow.getAnchor() == ToolWindowAnchor.BOTTOM) {
+ toolWindow.stretchHeight(bounds.height - currentBounds.height);
+ } else {
+ toolWindow.stretchWidth(bounds.width - currentBounds.width);
+ }
+ }
+ }
+ }
+
+ @Nullable
+ private static FloatingDecorator getFloatingDecorator(ToolWindowImpl toolWindow) {
+ InternalDecorator decorator = toolWindow.getDecorator();
+ return (FloatingDecorator) SwingUtilities.getAncestorOfClass(FloatingDecorator.class, decorator);
+ }
+
+ @Nullable
+ private static Window getWindow(ToolWindowImpl toolWindow) {
+ JComponent component = toolWindow.getComponent();
+ return UIUtil.getWindow(component);
+ }
+}
diff --git a/src/main/java/com/layoutmanager/ui/menu/WindowMenuService.java b/src/main/java/com/layoutmanager/ui/menu/WindowMenuService.java
new file mode 100644
index 0000000..45787dd
--- /dev/null
+++ b/src/main/java/com/layoutmanager/ui/menu/WindowMenuService.java
@@ -0,0 +1,96 @@
+package com.layoutmanager.ui.menu;
+
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.DefaultActionGroup;
+
+import com.layoutmanager.layout.delete.DeleteLayoutAction;
+import com.layoutmanager.layout.restore.RestoreLayoutAction;
+import com.layoutmanager.layout.store.LayoutCreator;
+import com.layoutmanager.layout.store.create.NewLayoutAction;
+import com.layoutmanager.layout.store.overwrite.OverwriteLayoutAction;
+import com.layoutmanager.layout.store.smartdock.SmartDockerFactory;
+import com.layoutmanager.localization.MessagesHelper;
+import com.layoutmanager.persistence.Layout;
+import com.layoutmanager.persistence.LayoutConfig;
+import com.layoutmanager.ui.dialogs.LayoutNameDialog;
+import com.layoutmanager.ui.dialogs.LayoutNameValidator;
+
+public class WindowMenuService {
+ private DefaultActionGroup storeLayout;
+ private DefaultActionGroup restoreLayout;
+ private DefaultActionGroup deleteLayout;
+
+ public void create() {
+ if (this.hasBeenCreated()) {
+ return;
+ }
+
+ this.createMainActions((DefaultActionGroup) ActionManager.getInstance().getAction("WindowMenu"));
+ this.createStoreRestoreActions();
+ }
+
+ public void recreate() {
+ if (!this.hasBeenCreated()) {
+ return;
+ }
+
+ this.restoreLayout.removeAll();
+ this.storeLayout.removeAll();
+ this.deleteLayout.removeAll();
+
+ this.createStoreRestoreActions();
+ }
+
+ private boolean hasBeenCreated() {
+ return this.storeLayout != null;
+ }
+
+ private void createMainActions(DefaultActionGroup windowMenu) {
+ this.storeLayout = this.createMainAction(MessagesHelper.message("StoreLayout.Menu"), windowMenu);
+ this.restoreLayout = this.createMainAction(MessagesHelper.message("RestoreLayout.Menu"), windowMenu);
+ this.deleteLayout = this.createMainAction(MessagesHelper.message("DeleteLayout.Menu"), windowMenu);
+ }
+
+ private DefaultActionGroup createMainAction(String name, DefaultActionGroup windowMenu) {
+ DefaultActionGroup windowMenuItem = new DefaultActionGroup(name, true);
+ windowMenu.add(windowMenuItem);
+
+ return windowMenuItem;
+ }
+
+ private void createStoreRestoreActions() {
+ LayoutConfig config = LayoutConfig.getInstance();
+ this.addStoreLayoutActions(config);
+ this.addRestoreLayoutActions(config);
+ this.addDeleteLayoutActions(config);
+ }
+
+ private void addStoreLayoutActions(LayoutConfig config) {
+ LayoutCreator layoutCreator = new LayoutCreator(
+ config.getSettings(),
+ new SmartDockerFactory(),
+ new LayoutNameDialog(new LayoutNameValidator()));
+
+ for (int index = 0; index < config.getLayoutCount(); index++) {
+ this.storeLayout.add(new OverwriteLayoutAction(layoutCreator, index));
+ }
+
+ if (config.getLayoutCount() > 0) {
+ this.storeLayout.addSeparator();
+ }
+
+ this.storeLayout.add(new NewLayoutAction(layoutCreator));
+ }
+
+ private void addRestoreLayoutActions(LayoutConfig config) {
+ for (Layout layout : config.getLayouts()) {
+ this.restoreLayout.add(new RestoreLayoutAction(layout));
+ }
+ }
+
+ private void addDeleteLayoutActions(LayoutConfig config) {
+ for (Layout layout : config.getLayouts()) {
+ this.deleteLayout.add(new DeleteLayoutAction(layout));
+ }
+ }
+}
diff --git a/src/main/java/com/layoutmanager/ui/settings/ImportExportConstants.java b/src/main/java/com/layoutmanager/ui/settings/ImportExportConstants.java
new file mode 100644
index 0000000..9800e6e
--- /dev/null
+++ b/src/main/java/com/layoutmanager/ui/settings/ImportExportConstants.java
@@ -0,0 +1,9 @@
+package com.layoutmanager.ui.settings;
+
+public class ImportExportConstants {
+ public static final String FILE_ENDING = "wl";
+ public static final String FILE_ENDING_WITH_DOT = "." + FILE_ENDING;
+
+
+ public static final String FILE_TYPE_NAME = "Window layouts";
+}
diff --git a/src/main/java/com/layoutmanager/ui/settings/LayoutManagerSettingsPanel.form b/src/main/java/com/layoutmanager/ui/settings/LayoutManagerSettingsPanel.form
new file mode 100644
index 0000000..e01fd92
--- /dev/null
+++ b/src/main/java/com/layoutmanager/ui/settings/LayoutManagerSettingsPanel.form
@@ -0,0 +1,113 @@
+
+
diff --git a/src/main/java/com/layoutmanager/ui/settings/LayoutManagerSettingsPanel.java b/src/main/java/com/layoutmanager/ui/settings/LayoutManagerSettingsPanel.java
new file mode 100644
index 0000000..d890705
--- /dev/null
+++ b/src/main/java/com/layoutmanager/ui/settings/LayoutManagerSettingsPanel.java
@@ -0,0 +1,209 @@
+package com.layoutmanager.ui.settings;
+
+import com.intellij.openapi.components.ServiceManager;
+import com.layoutmanager.localization.MessagesHelper;
+import com.layoutmanager.persistence.Layout;
+import com.layoutmanager.persistence.LayoutConfig;
+import com.layoutmanager.ui.dialogs.LayoutNameDialog;
+import com.layoutmanager.ui.dialogs.LayoutNameValidator;
+import com.layoutmanager.ui.helpers.ComponentNotificationHelper;
+import com.layoutmanager.ui.menu.WindowMenuService;
+import com.layoutmanager.ui.settings.exporting.ExportDialog;
+import com.layoutmanager.ui.settings.importing.ImportDialog;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import javax.swing.event.TableModelEvent;
+import javax.swing.table.DefaultTableModel;
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+
+import static javax.swing.JComponent.WHEN_FOCUSED;
+
+public class LayoutManagerSettingsPanel {
+ private final LayoutConfig layoutConfig;
+ private final LayoutNameDialog layoutNameDialog;
+ private final LayoutNameValidator layoutNameValidator;
+ private final LayoutSerializer layoutSerializer;
+
+ private ArrayList currentLayouts = new ArrayList<>();
+ private JCheckBox useSmartDockingCheckbox;
+ private JButton deleteButton;
+ private JButton renameButton;
+ private JButton exportButton;
+ private JButton importButton;
+ private JPanel settingsPanel;
+ private JTable layoutsTable;
+
+ public LayoutManagerSettingsPanel(
+ LayoutConfig layoutConfig,
+ LayoutNameDialog layoutNameDialog,
+ LayoutNameValidator layoutNameValidator,
+ LayoutSerializer layoutSerializer) {
+ this.layoutConfig = layoutConfig;
+ this.layoutNameDialog = layoutNameDialog;
+ this.layoutNameValidator = layoutNameValidator;
+ this.layoutSerializer = layoutSerializer;
+
+ this.loadSettings(layoutConfig);
+
+ this.layoutsTable.getSelectionModel().addListSelectionListener(listSelectionEvent -> this.selectedLayoutChanged());
+ this.deleteButton.addActionListener(e -> this.deleteLayout());
+ this.renameButton.addActionListener(e -> this.renameLayout());
+ this.exportButton.addActionListener(actionEvent -> this.exportLayout());
+ this.importButton.addActionListener(actionEvent -> this.importLayout());
+ }
+
+ private void loadSettings(LayoutConfig layoutConfig) {
+ Collections.addAll(this.currentLayouts, layoutConfig.getLayouts());
+
+ DefaultTableModel table = this.createTableContent();
+ this.setKeyBindings(table);
+ this.layoutsTable.setModel(table);
+ this.layoutsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+
+ this.useSmartDockingCheckbox.setSelected(layoutConfig.getSettings().getUseSmartDock());
+ }
+
+ private void setKeyBindings(DefaultTableModel tableModel){
+ InputMap inputMap = layoutsTable.getInputMap(WHEN_FOCUSED);
+ ActionMap actionMap = layoutsTable.getActionMap();
+
+ inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "delete");
+ actionMap.put("delete", new AbstractAction() {
+ public void actionPerformed(ActionEvent evt) {
+ int selectedRow = layoutsTable.getSelectedRow();
+ if (selectedRow >= 0) {
+ tableModel.removeRow(selectedRow);
+ }
+ }
+ });
+ }
+
+ private void selectedLayoutChanged() {
+ this.deleteButton.setEnabled(this.layoutsTable.getSelectedRow() >= 0);
+ this.renameButton.setEnabled(this.layoutsTable.getSelectedRow() >= 0);
+ this.exportButton.setEnabled(this.layoutsTable.getSelectedRow() >= 0);
+ }
+
+ private void deleteLayout() {
+ DefaultTableModel table = (DefaultTableModel)this.layoutsTable.getModel();
+ table.removeRow(this.layoutsTable.getSelectedRow());
+ }
+
+ private void renameLayout() {
+ int selectedRow = this.layoutsTable.getSelectedRow();
+ String newName = this.layoutNameDialog.show(this.currentLayouts.get(selectedRow).getName());
+
+ if (newName != null) {
+ this.layoutsTable.setValueAt(newName, selectedRow, 0);
+ }
+ }
+
+ private void exportLayout() {
+ int selectedRow = this.layoutsTable.getSelectedRow();
+ Layout selectedLayout = this.currentLayouts.get(selectedRow);
+
+ String encodedContent = this.layoutSerializer.serialize(selectedLayout);
+ showExportDialog(selectedLayout, encodedContent);
+ }
+
+ private void showExportDialog(Layout selectedLayout, String encodedContent) {
+ ExportDialog exportDialog = new ExportDialog(selectedLayout.getName(), encodedContent);
+ JDialog parent = this.getParentDialog();
+ exportDialog.showDialogInCenterOf(parent);
+ }
+
+ private void importLayout() {
+ ImportDialog importDialog = new ImportDialog(this.layoutNameValidator, this.layoutSerializer);
+ JDialog parent = this.getParentDialog();
+ if (importDialog.showDialogInCenterOf(parent) == ImportDialog.OK_RESULT) {
+ this.currentLayouts.add(importDialog.getImportedLayout());
+ ((DefaultTableModel) layoutsTable.getModel()).fireTableDataChanged();
+ }
+ }
+
+ @Nullable
+ private JDialog getParentDialog() {
+ return (JDialog) SwingUtilities.getAncestorOfClass(JDialog.class, this.settingsPanel);
+ }
+
+ public boolean hasChanged() {
+ return this.useSmartDockingCheckbox.isSelected() != this.layoutConfig.getSettings().getUseSmartDock() ||
+ !Arrays.equals(
+ this.layoutConfig.getLayouts(),
+ this.currentLayouts
+ .stream()
+ .toArray(Layout[]::new));
+ }
+
+ public void apply() {
+ this.layoutConfig.getSettings().setUseSmartDock(this.useSmartDockingCheckbox.isSelected());
+ this.layoutConfig.setLayouts(this.currentLayouts
+ .stream()
+ .toArray(Layout[]::new));
+
+ WindowMenuService windowMenuService = ServiceManager.getService(WindowMenuService.class);
+ windowMenuService.recreate();
+ }
+
+ public JPanel getPanel() {
+ return this.settingsPanel;
+ }
+
+ @NotNull
+ private DefaultTableModel createTableContent() {
+ return new DefaultTableModel(
+ new String[]{
+ MessagesHelper.message("SettingsPage.NameColumn"),
+ MessagesHelper.message("SettingsPage.ConfiguredWindowsColumn")
+ },
+ currentLayouts.size()) {
+
+ @Override
+ public boolean isCellEditable(int row, int column) {
+ return column == 0;
+ }
+
+ @Override
+ public void setValueAt(Object aValue, int row, int column) {
+ String newLayoutName = aValue.toString();
+ if (layoutNameValidator.isValid(newLayoutName)) {
+ currentLayouts.get(row).setName(aValue.toString());
+ this.fireTableChanged(new TableModelEvent(this, row));
+ } else {
+ ComponentNotificationHelper.error(layoutsTable, MessagesHelper.message("LayoutNameValidation.InvalidName"));
+ }
+ }
+
+ @Override
+ public int getRowCount() {
+ return currentLayouts.size();
+ }
+
+ @Override
+ public void removeRow(int row) {
+ currentLayouts.remove(row);
+ this.fireTableRowsDeleted(row, row);
+ }
+
+ @Override
+ public Object getValueAt(int row, int column) {
+ Layout layout = currentLayouts.get(row);
+
+ switch (column) {
+ case 0:
+ return layout.getName();
+ case 1:
+ return layout.getToolWindows().length;
+ default:
+ return null;
+ }
+ }
+ };
+ }
+}
diff --git a/src/main/java/com/layoutmanager/ui/settings/LayoutSerializer.java b/src/main/java/com/layoutmanager/ui/settings/LayoutSerializer.java
new file mode 100644
index 0000000..1329ac7
--- /dev/null
+++ b/src/main/java/com/layoutmanager/ui/settings/LayoutSerializer.java
@@ -0,0 +1,20 @@
+package com.layoutmanager.ui.settings;
+
+import blazing.chain.LZSEncoding;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.layoutmanager.persistence.Layout;
+
+public class LayoutSerializer {
+ public String serialize(Layout layout) {
+ Gson gson = new GsonBuilder().create();
+ String jsonContent = gson.toJson(layout);
+ return LZSEncoding.compressToBase64(jsonContent);
+ }
+
+ public Layout deserialize(String encodedContent) {
+ String jsonContent = LZSEncoding.decompressFromBase64(encodedContent);
+ Gson gson = new GsonBuilder().create();
+ return gson.fromJson(jsonContent, Layout.class);
+ }
+}
diff --git a/src/main/java/com/layoutmanager/ui/settings/SettingsPage.java b/src/main/java/com/layoutmanager/ui/settings/SettingsPage.java
new file mode 100644
index 0000000..8d6d23b
--- /dev/null
+++ b/src/main/java/com/layoutmanager/ui/settings/SettingsPage.java
@@ -0,0 +1,47 @@
+package com.layoutmanager.ui.settings;
+
+import com.intellij.openapi.options.Configurable;
+
+import javax.swing.JComponent;
+
+import com.layoutmanager.localization.MessagesHelper;
+import com.layoutmanager.persistence.LayoutConfig;
+import com.layoutmanager.ui.dialogs.LayoutNameDialog;
+import com.layoutmanager.ui.dialogs.LayoutNameValidator;
+import org.jetbrains.annotations.Nls;
+import org.jetbrains.annotations.Nullable;
+
+public class SettingsPage implements Configurable {
+ private final LayoutManagerSettingsPanel panel;
+
+ public SettingsPage() {
+ LayoutNameValidator layoutNameValidator = new LayoutNameValidator();
+ this.panel = new LayoutManagerSettingsPanel(
+ LayoutConfig.getInstance(),
+ new LayoutNameDialog(layoutNameValidator),
+ layoutNameValidator,
+ new LayoutSerializer());
+ }
+
+ @Nls(capitalization = Nls.Capitalization.Title)
+ @Override
+ public String getDisplayName() {
+ return MessagesHelper.message("SettingsPage.Title");
+ }
+
+ @Nullable
+ @Override
+ public JComponent createComponent() {
+ return this.panel.getPanel();
+ }
+
+ @Override
+ public boolean isModified() {
+ return this.panel.hasChanged();
+ }
+
+ @Override
+ public void apply() {
+ this.panel.apply();
+ }
+}
diff --git a/src/main/java/com/layoutmanager/ui/settings/exporting/ExportDialog.form b/src/main/java/com/layoutmanager/ui/settings/exporting/ExportDialog.form
new file mode 100644
index 0000000..34903ca
--- /dev/null
+++ b/src/main/java/com/layoutmanager/ui/settings/exporting/ExportDialog.form
@@ -0,0 +1,119 @@
+
+
diff --git a/src/main/java/com/layoutmanager/ui/settings/exporting/ExportDialog.java b/src/main/java/com/layoutmanager/ui/settings/exporting/ExportDialog.java
new file mode 100644
index 0000000..abd5287
--- /dev/null
+++ b/src/main/java/com/layoutmanager/ui/settings/exporting/ExportDialog.java
@@ -0,0 +1,117 @@
+package com.layoutmanager.ui.settings.exporting;
+
+import com.layoutmanager.localization.MessagesHelper;
+import com.layoutmanager.ui.helpers.ComponentNotificationHelper;
+import com.layoutmanager.ui.settings.ImportExportConstants;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import java.awt.*;
+import java.awt.datatransfer.StringSelection;
+import java.awt.event.KeyEvent;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+public class ExportDialog extends JDialog {
+ private JTextArea exportTextBox;
+ private JPanel contentPanel;
+ private JButton exportToFileButton;
+ private JButton exportToClipboardButton;
+ private JButton closeButton;
+ private JLabel layoutNameLabel;
+
+ private String layoutName;
+ private String content;
+
+ public ExportDialog(String layoutName, String content) {
+ this.layoutName = layoutName;
+ this.content = content;
+
+ this.setContentPane(contentPanel);
+ this.setModal(true);
+ this.getRootPane().setDefaultButton(closeButton);
+
+ this.exportToClipboardButton.addActionListener(actionEvent -> this.exportToClipboard());
+ this.exportToFileButton.addActionListener(actionEvent -> this.exportToFile());
+ this.closeButton.addActionListener(actionEvent -> this.onClose());
+
+ this.layoutNameLabel.setText(layoutName);
+ this.exportTextBox.setText(content);
+
+ this.contentPanel.registerKeyboardAction(e -> onClose(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
+ }
+
+ public void showDialogInCenterOf(JDialog parent) {
+ this.setTitle(MessagesHelper.message("ExportDialog.Title"));
+ this.setSize(this.getPreferredSize());
+ this.setLocationRelativeTo(parent);
+ this.setVisible(true);
+ }
+
+ public JPanel getPanel() {
+ return this.contentPanel;
+ }
+
+ private void exportToClipboard() {
+ this.exportTextBox.requestFocus();
+ this.exportTextBox.selectAll();
+ Toolkit
+ .getDefaultToolkit()
+ .getSystemClipboard()
+ .setContents(
+ new StringSelection(this.exportTextBox.getText()),
+ null);
+ ComponentNotificationHelper.info(this.exportToClipboardButton, MessagesHelper.message("ExportDialog.CopiedToClipboard"));
+ }
+
+ private void exportToFile() {
+ File selectedFile = this.selectFile();
+ if (selectedFile != null) {
+ Path path = this.getPath(selectedFile);
+ this.writeContentToFile(path);
+ }
+ }
+
+ private File selectFile() {
+ JFileChooser fileChooser = new JFileChooser();
+ fileChooser.setDialogTitle(MessagesHelper.message("ExportDialog.SaveFileTitle"));
+ fileChooser.setSelectedFile(new File(this.layoutName + ImportExportConstants.FILE_ENDING_WITH_DOT));
+ fileChooser.setAcceptAllFileFilterUsed(false);
+ FileNameExtensionFilter filter = new FileNameExtensionFilter(ImportExportConstants.FILE_TYPE_NAME, ImportExportConstants.FILE_ENDING);
+ fileChooser.setFileFilter(filter);
+
+ int result = fileChooser.showSaveDialog(null);
+ return result == JFileChooser.APPROVE_OPTION ?
+ fileChooser.getSelectedFile() :
+ null;
+ }
+
+ @NotNull
+ private Path getPath(File selectedFile) {
+ String fullPath = selectedFile.getAbsolutePath();
+ if (!fullPath.toLowerCase().endsWith(ImportExportConstants.FILE_ENDING_WITH_DOT)) {
+ fullPath += ImportExportConstants.FILE_ENDING_WITH_DOT;
+ }
+
+ return Paths.get(fullPath);
+ }
+
+ private void writeContentToFile(Path path) {
+ try {
+ byte[] encodedContent = this.content.getBytes();
+ Files.write(path, encodedContent);
+ ComponentNotificationHelper.info(this.exportToFileButton, MessagesHelper.message("ExportDialog.SavedTo", path.getFileName().toString()));
+ } catch (IOException e) {
+ ComponentNotificationHelper.error(this.exportToFileButton, MessagesHelper.message("ExportDialog.FailedToWriteFile", e.getMessage()));
+ }
+ }
+
+ private void onClose() {
+ JDialog window = (JDialog) SwingUtilities.getAncestorOfClass(JDialog.class, this.contentPanel);
+ window.dispose();
+ }
+}
diff --git a/src/main/java/com/layoutmanager/ui/settings/importing/ImportDialog.form b/src/main/java/com/layoutmanager/ui/settings/importing/ImportDialog.form
new file mode 100644
index 0000000..1028af4
--- /dev/null
+++ b/src/main/java/com/layoutmanager/ui/settings/importing/ImportDialog.form
@@ -0,0 +1,146 @@
+
+
diff --git a/src/main/java/com/layoutmanager/ui/settings/importing/ImportDialog.java b/src/main/java/com/layoutmanager/ui/settings/importing/ImportDialog.java
new file mode 100644
index 0000000..2595a19
--- /dev/null
+++ b/src/main/java/com/layoutmanager/ui/settings/importing/ImportDialog.java
@@ -0,0 +1,166 @@
+package com.layoutmanager.ui.settings.importing;
+
+import com.layoutmanager.localization.MessagesHelper;
+import com.layoutmanager.persistence.Layout;
+import com.layoutmanager.ui.dialogs.LayoutNameValidator;
+import com.layoutmanager.ui.helpers.ComponentNotificationHelper;
+import com.layoutmanager.ui.settings.ImportExportConstants;
+import com.layoutmanager.ui.settings.LayoutSerializer;
+
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import java.awt.*;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.event.KeyEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+public class ImportDialog extends JDialog {
+ public static final int OK_RESULT = 1;
+ public static final int ABORT_RESULT = 0;
+ private final LayoutNameValidator layoutNameValidator;
+ private final LayoutSerializer layoutSerializer;
+
+ private JPanel contentPanel;
+ private JButton importButton;
+ private JButton abortButton;
+ private JButton importFromFileButton;
+ private JButton importFromClipboardButton;
+ private JLabel layoutConfiguredWindowCountLabel;
+ private JTextField layoutNameTextBox;
+
+ private Layout importedLayout;
+ private int result;
+
+ public ImportDialog(
+ LayoutNameValidator layoutNameValidator,
+ LayoutSerializer layoutSerializer) {
+ this.layoutNameValidator = layoutNameValidator;
+ this.layoutSerializer = layoutSerializer;
+
+ this.setContentPane(contentPanel);
+ this.setModal(true);
+ this.setResizable(false);
+ this.getRootPane().setDefaultButton(importButton);
+
+ this.importButton.addActionListener(e -> this.onOK());
+ this.abortButton.addActionListener(e -> this.onCancel());
+ this.importFromClipboardButton.addActionListener(actionEvent -> this.importFromClipboard());
+ this.importFromFileButton.addActionListener(actionEvent -> this.importFromFile());
+
+ // call onCancel() when cross is clicked
+ this.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
+ this.addWindowListener(new WindowAdapter() {
+ public void windowClosing(WindowEvent e) {
+ onCancel();
+ }
+ });
+
+ this.contentPanel.registerKeyboardAction(e -> onCancel(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
+ }
+
+ public Layout getImportedLayout() {
+ return this.importedLayout;
+ }
+
+ public int showDialogInCenterOf(JDialog parent) {
+ this.setTitle(MessagesHelper.message("ImportDialog.Title"));
+ this.setSize(this.getPreferredSize());
+ this.setLocationRelativeTo(parent);
+ this.setVisible(true);
+ return this.result;
+ }
+
+ private void importFromFile() {
+ File selectedFile = this.selectFile();
+ if (selectedFile != null) {
+ this.importFile(selectedFile);
+ }
+ }
+
+ private File selectFile() {
+ JFileChooser fileChooser = new JFileChooser();
+ fileChooser.setDialogTitle(MessagesHelper.message("ImportDialog.SelectFileTitle"));
+ fileChooser.setAcceptAllFileFilterUsed(false);
+ FileNameExtensionFilter filter = new FileNameExtensionFilter(ImportExportConstants.FILE_TYPE_NAME, ImportExportConstants.FILE_ENDING);
+ fileChooser.setFileFilter(filter);
+
+ int result = fileChooser.showOpenDialog(null);
+ return result == JFileChooser.APPROVE_OPTION && fileChooser.getSelectedFile().exists() ?
+ fileChooser.getSelectedFile() : null;
+ }
+
+ private void importFile(File file) {
+ try {
+ String encodedContent = new String(Files.readAllBytes(Paths.get(file.getPath())), StandardCharsets.UTF_8);
+ this.importLayout(encodedContent);
+ } catch (IOException e) {
+ ComponentNotificationHelper.error(this.importFromFileButton, MessagesHelper.message("ImportDialog.IOException", file.getName(), e.getMessage()));
+ this.deselectLayout();
+ } catch(Exception e) {
+ ComponentNotificationHelper.error(this.importFromFileButton, MessagesHelper.message("ImportDialog.UnknownFormat"));
+ this.deselectLayout();
+ }
+ }
+
+ private void importFromClipboard() {
+ try {
+ String lzEncodedContent = (String) Toolkit
+ .getDefaultToolkit()
+ .getSystemClipboard()
+ .getData(DataFlavor.stringFlavor);
+
+ this.importLayout(lzEncodedContent);
+ } catch (Exception e) {
+ ComponentNotificationHelper.error(this.importFromClipboardButton, MessagesHelper.message("ImportDialog.UnknownFormat"));
+ this.deselectLayout();
+ }
+ }
+
+ private void importLayout(String encodedContent) {
+ Layout selectedLayout = this.layoutSerializer.deserialize(encodedContent);
+ this.selectLayout(selectedLayout);
+ }
+
+ private void selectLayout(Layout layout) {
+ this.importedLayout = layout;
+
+ this.layoutNameTextBox.setText(layout.getName());
+ this.layoutNameTextBox.requestFocus();
+ this.layoutConfiguredWindowCountLabel.setText(Integer.toString(layout.getToolWindows().length));
+
+ ComponentNotificationHelper.info(this.layoutNameTextBox, MessagesHelper.message("ImportDialog.SuccessfullyLoadedLayout", layout.getName()));
+
+ this.importButton.setEnabled(true);
+ this.layoutNameTextBox.setEnabled(true);
+ }
+
+ private void deselectLayout() {
+ this.importedLayout = null;
+ this.importButton.setEnabled(false);
+ this.layoutNameTextBox.setText("");
+ this.layoutNameTextBox.setEnabled(false);
+ this.layoutConfiguredWindowCountLabel.setText("-");
+ }
+
+ private void onOK() {
+ if (!this.layoutNameValidator.isValid(this.layoutNameTextBox.getText())) {
+ ComponentNotificationHelper.error(this.importButton, MessagesHelper.message("LayoutNameValidation.InvalidName"));
+ return;
+ }
+ this.importedLayout.setName(this.layoutNameTextBox.getText());
+
+ this.result = OK_RESULT;
+ dispose();
+ }
+
+ private void onCancel() {
+ this.result = ABORT_RESULT;
+ dispose();
+ }
+}
diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml
index b4423a0..7c0142d 100644
--- a/src/main/resources/META-INF/plugin.xml
+++ b/src/main/resources/META-INF/plugin.xml
@@ -27,7 +27,13 @@
Dec 19, 2019 (ver 1.3.0) - Store placement of editor tabs and tool window labels.
Dec 26, 2019 (ver 1.3.1) - Fix support of Java 8.
Jan 07, 2020 (ver 1.3.2) - Minor bugfixes.
- Jan 07, 2020 (ver 1.3.3) - Issues when storing and restoring invisible windows fixed.
+ Feb 25, 2020 (ver 1.3.3) - Issues when storing and restoring invisible windows fixed.
+
+ May 8, 2020 (ver 1.4.0)
+ - Introduced settings page.
+ - Introduced smart docking when saving a layout (Configurable).
+ - Store docked tool window sizes and restore them accordingly.
+
]]>
@@ -39,9 +45,11 @@
-
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/com/layoutmanager/ui/messages.properties b/src/main/resources/com/layoutmanager/ui/messages.properties
index d9e5cf8..1ec14e9 100644
--- a/src/main/resources/com/layoutmanager/ui/messages.properties
+++ b/src/main/resources/com/layoutmanager/ui/messages.properties
@@ -17,3 +17,20 @@ DeleteLayout.Menu=Delete Layout
DeleteLayout.Notification.Title=Window layout deleted
DeleteLayout.Notification.Content=The window layout ''{0}'' has been successfully deleted.
+SettingsPage.Title=Window Layout Manager
+SettingsPage.NameColumn=Name
+SettingsPage.ConfiguredWindowsColumn=Configured Windows
+
+ExportDialog.Title=Export layout
+ExportDialog.CopiedToClipboard=Copied to clipboard!
+ExportDialog.SaveFileTitle=Specify a file to save
+ExportDialog.SavedTo=Saved to file ''{0}'' !
+ExportDialog.FailedToWriteFile=Failed to write export file: {0}
+
+ImportDialog.Title=Import layout
+ImportDialog.SelectFileTitle=Select layout file
+ImportDialog.IOException=Failed to read file '{0}'. Reason: {1}
+ImportDialog.UnknownFormat=Import failed due to a unknown format
+ImportDialog.SuccessfullyLoadedLayout=Successfully loaded the layout ''{0}''
+
+LayoutNameValidation.InvalidName=The layout name must not be empty or contain illegal characters.
\ No newline at end of file