I am writing an IntelliJ IDEA plugin for saving sessions of open tabs called Tab Session. This question is a follow-up of IntelliJ IDEA Plugin Development: Save groups of tabs, save them persistently and reload a set of tabs if requested by the user.
Currently, splitted windows are not supported. Therefore i want to do two things:
Retrieve information about all splitted or unsplitted windows that are containers for editor tabs. I need their position and split direction (horizontal or vertical).
When this information is saved and a tab session needs to be loaded, i need to reconstruct the splitted panes and their tabs exactly as they were before.
Due to the lack of documentation i am currently browsing through the source code and found this promising piece of code:
private EditorsSplitters getSplittersFromFocus() {
return FileEditorManagerEx.getInstanceEx(myProject).getSplitters();
}
It allows me to iterate through the set of splitted windows by using EditorWindow[] windows = getSplittersFromFocus.getOrderedWindows(). They contain the editor tabs and information about their width and height. But i did not find any information about the split direction and how to reconstruct the splitted windows as they were before.
Can anyone help?
This is untested code, but as it closely resmbles the procedures inside EditorsSplitters writeExternal and writePanel functions I am positive this will work.
Presented are two methods:
access output of writeExternal -> should be the more stable API and offers easier access to file information
access components of splitter -> this way writeExternal creates it's information; sadly there is at least one protected field without getter involved (window.myPanel inside findWindowWith)
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.impl.EditorsSplitters;
import com.intellij.openapi.fileEditor.impl.FileEditorManagerImpl;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Splitter;
import org.jdom.Element;
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
public class SplitterAction extends AnAction {
public SplitterAction() {
super("Splitter _Action");
}
private static class Info {
}
private static class SplitInfo extends Info {
public Info first;
public Info second;
public boolean vertical;
public float proportions;
}
private static class FileInfo extends Info {
public String[] fileNames;
}
#Override
public void actionPerformed(AnActionEvent anActionEvent) {
final Project project = anActionEvent.getProject();
final FileEditorManagerImpl fileEditorManager = (FileEditorManagerImpl) FileEditorManager.getInstance(project);
EditorsSplitters splitters = fileEditorManager.getSplitters();
// com.intellij.openapi.fileEditor.impl.EditorsSplitters.writeExternal() and
// com.intellij.openapi.fileEditor.impl.EditorsSplitters#writePanel inspired this
final Component component = splitters.getComponent(0);
final SplitInfo infos = splitterVisitor(component);
// or you could use this
Element root = new Element("root");
splitters.writeExternal(root);
elementVisitor(root);
// to restore from writeExternal the following should suffice
splitters.readExternal(root);
splitters.openFiles();
}
/**
* Reads writeExternal output
*/
private Info elementVisitor(Element root) {
final Element splitter = root.getChild("splitter");
if (splitter != null) {
// see com.intellij.openapi.fileEditor.impl.EditorsSplitters#writePanel
final SplitInfo splitInfo = new SplitInfo();
// "vertical" or "horizontal"
splitInfo.vertical = "vertical".equals(splitter.getAttributeValue("split-orientation"));
splitInfo.proportions = Float.parseFloat(splitter.getAttributeValue("split-proportion"));
Element first = splitter.getChild("split-first");
if (first != null) {
splitInfo.first = elementVisitor(first);
}
Element second = splitter.getChild("split-second");
if (second != null) {
splitInfo.second = elementVisitor(second);
}
return splitInfo;
}
final Element leaf = root.getChild("leaf");
if (leaf != null) {
final ArrayList<String> fileNames = new ArrayList<String>();
for (Element file : leaf.getChildren("file")) {
final String fileName = file.getAttributeValue("leaf-file-name");
fileNames.add(fileName);
// further attributes see com.intellij.openapi.fileEditor.impl.EditorsSplitters#writeComposite
}
final FileInfo fileInfo = new FileInfo();
fileInfo.fileNames = fileNames.toArray(new String[fileNames.size()]);
return fileInfo;
}
return null;
}
/**
* Acts directly upon Component
*/
private SplitInfo splitterVisitor(Component component) {
if (component instanceof JPanel && ((JPanel) component).getComponentCount() > 0) {
final Component child = ((JPanel) component).getComponent(0);
if (child instanceof Splitter) {
final Splitter splitter = (Splitter) child;
final SplitInfo splitInfos = new SplitInfo();
splitInfos.vertical = splitter.isVertical();
splitInfos.proportions = splitter.getProportion();
splitInfos.first = splitterVisitor(splitter.getFirstComponent());
splitInfos.second = splitterVisitor(splitter.getSecondComponent());
return splitInfos;
}
// TODO: retrieve file information
}
return null;
}
}
Related
I have a sub-method that builds my FtpInboundFileSynchronizer object. (from a factory object generated out of scope)
private FtpInboundFileSynchronizer createFtpInboundFileSynchronizer(SessionFactory<FTPFile> factory) {
var synchronizer = new FtpInboundFileSynchronizer(factory);
CompositeFileListFilter filter = new CompositeFileListFilter<>();
filter.addFilter(new FtpSimplePatternFileListFilter("filename1.txt"));
filter.addFilter(new FtpSimplePatternFileListFilter("filename2.txt"));
synchronizer.setFilter(filter);
synchronizer.setRemoteDirectory(ftpConfiguration.getPath());
synchronizer.setDeleteRemoteFiles(false);
return synchronizer;
}
However, in this case, I get no files. If I remove one of the FTPSimplePAtternFileListFilter instances, it correctly retrieves that one filename from the FTP server.
The aim is to only download a predefined list of full file names, so both name and extension.
I cannot seem to wrap my head around how to do this. Anyone able to help?
import java.util.Arrays;
import org.apache.commons.net.ftp.FTPFile;
import org.springframework.integration.file.filters.AbstractDirectoryAwareFileListFilter;
import org.springframework.util.AntPathMatcher;
/**
* Filter to accept specific file name list.
*/
public class MultiPatternFileListFilter extends AbstractDirectoryAwareFileListFilter<FTPFile> {
private final AntPathMatcher matcher = new AntPathMatcher();
private String[] patterns;
public MultiPatternFileListFilter(String... patterns) {
this.patterns = patterns;
}
#Override
public boolean accept(FTPFile file) {
return alwaysAccept(file) || (file != null && this.matchesAnyPattern(file));
}
#Override
protected boolean isDirectory(FTPFile file) {
return false;
}
protected String getFilename(FTPFile file) {
return (file != null) ? file.getName() : null;
}
private boolean matchesAnyPattern(FTPFile file) {
return Arrays.stream(patterns).anyMatch(pattern -> this.matcher.match(pattern, this.getFilename(file)));
}
}
This solved my problem.
-I’m writing a confluence server plugin for page events listener module that sync with custom system.
Whenever page copy events happened, I needed info about original copied page.
-EventListener triggers when copying page with children or hierarchy.
-However, when copying page without children or hierarchy, it falls into the category of page creation events and unable to know where this page originally from. I needed to differentiate between create and copy though technically, they are the same.
Code Sample Below:
package com.linn.aung;
import com.atlassian.confluence.event.events.content.ContentEvent;
import com.atlassian.confluence.event.events.content.page.PageCopyEvent;
import com.atlassian.confluence.event.events.content.page.PageCreateEvent;
import com.atlassian.confluence.event.events.content.page.PageViewEvent;
import com.atlassian.confluence.event.events.content.pagehierarchy.CopyPageHierarchyFinishEvent;
import com.atlassian.confluence.event.events.content.pagehierarchy.CopyPageHierarchyStartEvent;
import com.atlassian.confluence.pages.Page;
import com.atlassian.event.Event;
import com.atlassian.event.EventListener;
import org.apache.log4j.Logger;
public class PageListener implements EventListener{
private static final Logger log = Logger.getLogger(PageListener.class);
private Class[] handledClasses = new Class[]{
ContentEvent.class,
PageViewEvent.class,
PageCreateEvent.class,
PageCopyEvent.class,
CopyPageHierarchyStartEvent.class,
CopyPageHierarchyFinishEvent.class
};
public void handleEvent(Event event) {
if (event instanceof PageCreateEvent) {
PageCreateEvent pageCreateEvent = (PageCreateEvent) event;
Page currentPage = pageCreateEvent.getPage();
String pageTitle = currentPage.getTitle();
log.warn("-----here page created-----");
log.warn(pageTitle);
}
else if(event instanceof CopyPageHierarchyStartEvent) {
CopyPageHierarchyStartEvent copyStart = (CopyPageHierarchyStartEvent) event;
Page destinationPage = copyStart.getDestination();
String pageTitle = destinationPage.getTitle();
log.warn("-----here page copy start-----");
log.warn(pageTitle);
}
else if(event instanceof PageCopyEvent) {
PageCopyEvent pageCopyEvent = (PageCopyEvent) event;
Page currentPage = pageCopyEvent.getPage();
String pageTitle = currentPage.getTitle();
log.warn("-----here page copied----");
log.warn(pageTitle);
}
else if(event instanceof CopyPageHierarchyFinishEvent) {
CopyPageHierarchyFinishEvent copyFinish = (CopyPageHierarchyFinishEvent) event;
Page destinationPage = copyFinish.getDestination();
String pageTitle = destinationPage.getTitle();
log.warn("-----here page copy finish-----");
log.warn(pageTitle);
}
}
I am trying to make a properties file in Java. Sadly, when I startup Minecraft (As this is a mod in Forge) the file is not created. I will be so thankful to anyone who helps me. Here is the code:
package mymod.properties;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Properties;
public class WriteToProperties {
public static void main(String[] args) {
Properties prop = new Properties();
try {
prop.setProperty("Test", "Yay");
prop.store(new FileOutputStream("Test.properties"), null);
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
Basically what you want is initialize(event.getSuggestedConfigurationFile());
Forge basically wishes to hand you the default settings to you. I also suggest you structure things logically, so it's nice, clean and accessible later on, and easier to manage.
I use this as a config loader
package tschallacka.magiccookies.init;
import java.io.File;
import net.minecraftforge.common.config.Configuration;
import net.minecraftforge.common.config.Property;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import tschallacka.magiccookies.MagicCookies;
import tschallacka.magiccookies.api.ModHooks;
import tschallacka.magiccookies.api.Preferences;
public class ConfigLoader {
public static Configuration config;
public static final String CATEGORY_GENERAL = "GeneralSettings";
public static final String CATEGORY_SERVER = "ServerPerformance";
public static void init(FMLPreInitializationEvent event) {
ModHooks.MODID = MagicCookies.MODID;
ModHooks.MODNAME = MagicCookies.MODNAME;
ModHooks.VERSION = MagicCookies.VERSION;
try {
initialize(event.getSuggestedConfigurationFile());
}
catch (Exception e) {
MagicCookies.log.error("MagicCookie failed to load preferences. Reverting to default");
}
finally {
if (config != null) {
save();
}
}
}
private static void initialize(final File file) {
config = new Configuration(file);
config.addCustomCategoryComment(CATEGORY_GENERAL, "General Settings");
config.addCustomCategoryComment(CATEGORY_SERVER, "Server performance settings");
Preferences.magicCookieIsLoaded = true;
config.load();
syncConfigurable();
}
private static void save() {
config.save();
}
private static void syncConfigurable() {
final Property awesomeMod = config.get(Configuration.CATEGORY_GENERAL, "awesome_mod", Preferences.awesomeMod);
awesomeMod.comment = "Set this to yes if you think this mod is awesome, maybe it will at some time unlock a special thing.... or not... but that's only for people who think this is an wesome Mod ;-)";
Preferences.awesomeMod = awesomeMod.getString();
final Property numberOfBlocksPlacingPerTickByStripper = config.get(CATEGORY_SERVER,"number_of_blocks_placing_per_tick_by_stripper",Preferences.numberOfBlocksPlacingPerTickByStripper);
numberOfBlocksPlacingPerTickByStripper.comment = "This affects how many blocks will be placed per tick by the creative stripper tool per tick. The stripper tool will only place blocks if ticks are take less than 50ms. If you experience lag lower this number, if you don't experience lag and want faster copy pasting, make this number higher. For an awesome slowmo build of your caste set this to 1 ;-). Set to 0 to render everything in one go per chunk";
Preferences.numberOfBlocksPlacingPerTickByStripper = numberOfBlocksPlacingPerTickByStripper.getInt();
final Property averageTickTimeCalculationSpan = config.get(CATEGORY_SERVER,"number_of_ticks_used_for_average_time_per_tick_calculation",Preferences.averageTickTimeCalculationSpan);
averageTickTimeCalculationSpan.comment = "This number is the number of tick execution times are added together to calculation an average. The higher number means less lag by some things like the strippers, but can also lead to longer execution times for the strippers. Basically if your server is always running behind on server ticks set this value to 1, to at least get some work done when your server is running under 50ms tickspeed";
Preferences.averageTickTimeCalculationSpan = averageTickTimeCalculationSpan.getInt();
final Property acceptableTickduration = config.get(CATEGORY_SERVER,"acceptable_tick_duration",Preferences.acceptableTickduration);
acceptableTickduration.comment = "Define here what you see as acceptable tick speed where MagicCookies can do some of its magic. If average tick speed or current tick speed is higher than this value it won't perform some tasks to help manage server load.";
Preferences.acceptableTickduration = (long)acceptableTickduration.getDouble();
}
}
The value holder Preferences class.
This is purely so I can do Preferences.valuename everywhere.
package tschallacka.magiccookies.api;
/**
* Here the preferences of MagicCookies will be stored
* and kept after the config's are loaded.
* #author Tschallacka
*
*/
public class Preferences {
public static boolean magicCookieIsLoaded = false;
public static String awesomeMod = "Well?";
public static boolean isLoadedThaumcraft = false;
public static int numberOfBlocksPlacingPerTickByStripper = 0;
public static int averageTickTimeCalculationSpan = 60;
public static long acceptableTickduration = 50l;
public static int darkShrineFrequency = 0;
public static boolean darkShrineSpawnLogging = true;
public static boolean entropyTempleSpawnLogging = true;
public static int entropySize = 30;
}
In your main mod file:
#EventHandler
public void preInit(FMLPreInitializationEvent e) {
MagicCookies.log.warn("Preinit starting");
MinecraftForge.EVENT_BUS.register((Object)MagicCookies.instance);
ConfigLoader.init(e);
}
And after that you can just fetch all your preferences from the Preferences class
This file was created in root of your project. If you want some specific path to save, create folder in root of your project like props and change path in FileOutputStream constructor to "props\\Test.properties"
Is it possible to determine, what client libs have been loaded prior to a component?
We are running multiple site backed by different Javascript frameworks. In order to run a single component across the board, it's not sufficient to just use
<cq:includeClientLib categories="blah"/>
We need to identify the respective framework (i.e. AngularJS, Vanilla, jQuery, blah) in order to facilitate the integration.
We are looking for a decent server side solution.
I haven't actually done this, but it would presumably be possible if you are buffering your output to clone the JspWriter buffer or examine it to see what it already contains. That sounds ugly to me, though. But this is decompiled code for how the cq:includeClientLib tag adds libraries to the output, which may show you how you can read back what was previously written:
package com.adobe.granite.ui.tags;
import com.day.cq.widget.HtmlLibraryManager;
import java.io.IOException;
import javax.servlet.ServletRequest;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.TagSupport;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.scripting.SlingBindings;
import org.apache.sling.scripting.jsp.util.TagUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class IncludeClientLibraryTag extends TagSupport {
private static final long serialVersionUID = -3068291967085012331L;
private static final Logger log = LoggerFactory.getLogger(IncludeClientLibraryTag.class);
private String categories;
private String js;
private String css;
private String theme;
private Boolean themed;
public IncludeClientLibraryTag() {
}
public void setPageContext(PageContext pageContext) {
super.setPageContext(pageContext);
this.categories = null;
this.js = null;
this.css = null;
this.theme = null;
this.themed = null;
}
public void setCategories(String categories) {
this.categories = categories;
}
public void setJs(String js) {
this.js = js;
}
public void setCss(String css) {
this.css = css;
}
public void setTheme(String theme) {
this.theme = theme;
}
public void setThemed(boolean themed) {
this.themed = Boolean.valueOf(themed);
}
public int doEndTag() throws JspException {
SlingHttpServletRequest request = TagUtil.getRequest(this.pageContext);
HtmlLibraryManager libManager = this.getHtmlLibraryManager(request);
if(libManager == null) {
log.warn("<ui:includeClientLib>: Could not retrieve HtmlLibraryManager service, skipping inclusion.");
return 6;
} else {
JspWriter out = this.pageContext.getOut();
try {
if(this.categories != null) {
libManager.writeIncludes(request, out, toArray(this.categories));
} else if(this.theme != null) {
libManager.writeThemeInclude(request, out, toArray(this.theme));
} else if(this.js != null) {
if(this.themed != null) {
libManager.writeJsInclude(request, out, this.themed.booleanValue(), toArray(this.js));
} else {
libManager.writeJsInclude(request, out, toArray(this.js));
}
} else if(this.css != null) {
if(this.themed != null) {
libManager.writeCssInclude(request, out, this.themed.booleanValue(), toArray(this.css));
} else {
libManager.writeCssInclude(request, out, toArray(this.css));
}
}
return 6;
} catch (IOException var6) {
String libs = this.categories != null?"categories: " + this.categories:(this.theme != null?"theme: " + this.theme:(this.js != null?"js: " + this.js:(this.css != null?"css: " + this.css:"")));
throw new JspException("Could not include client library: " + libs, var6);
}
}
}
private HtmlLibraryManager getHtmlLibraryManager(ServletRequest request) {
SlingBindings bindings = (SlingBindings)request.getAttribute(SlingBindings.class.getName());
return (HtmlLibraryManager)bindings.getSling().getService(HtmlLibraryManager.class);
}
private static String[] toArray(String commaSeparatedList) {
if(commaSeparatedList == null) {
return new String[0];
} else {
String[] split = commaSeparatedList.split(",");
for(int i = 0; i < split.length; ++i) {
split[i] = split[i].trim();
}
return split;
}
}
}
I think the best solution may be to use the client library dependencies or embed attributes in your library, though, or let the client-side JavaScript test if a library is present (ex. test if the jQuery object is undefined) and then take appropriate action. In other words, let the client side determine the final rendering based on what libraries exist on in the client. It sounds like this may not be possible for your situation, though.
dependencies: This is a list of other client library categories on
which this library folder depends. For example, given two
cq:ClientLibraryFolder nodes F and G, if a file in F requires another
file in G in order to function properly, then at least one of the
categories of G should be among the dependencies of F.
embed: Used to > embed code from other libraries. If node F embeds nodes G and H, the
resulting HTML will be a concetration of content from nodes G and H.
When I use HelpBroker.setCurrentID("[some help id]"), JavaHelp correctly shows the page I want but the associated topic is not automatically selected/highlighted in the table of contents. This makes it difficult for a user to know where in the topic tree the current page is located.
Is there any way to programmatically highlight the current topic in the TOC when I use a HelpBroker to navigate to a specific Help page?
Note that when the user follows links within the Java help system, the new topic is properly selected in the table of contents.
You should be able to just call the navigate() function, the rest are helpers. id is the id of course, and nodeLabel is the visible text on the node you want to select. Do note that this code doesn't check for errors, namely preventing trying to go somewhere that doesn't exist.
import java.awt.Component;
import java.util.Enumeration;
import javax.help.*;
import javax.help.plaf.basic.BasicTOCNavigatorUI;
import javax.swing.JTree;
import javax.swing.JScrollPane;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.DefaultMutableTreeNode;
public class Nav
{
private static DefaultMutableTreeNode match = null;
private Nav()
{
}
public static void navigate(HelpSet set, HelpBroker broker, String id, String nodeLabel)
{
broker.setCurrentID(id);
JHelp jhelp = new JHelp(set);
JHelpNavigator nav = getNavigator(jhelp);
JTree tree = getTree(nav);
TreeModel model = tree.getModel();
Object root = model.getRoot();
match = null;
findNode(model, root, nodeLabel);
TreePath path = new TreePath(match.getPath());
tree.setSelectionPath(path);
tree.scrollPathToVisible(path);
}
//http://www.google.com/codesearch/p?hl=en#WiboLAWeTd0/xena/ext/src/javahelp/jhMaster/JavaHelp/src/new/javax/help/WindowPresentation.java&t=0&d=30&l=272
private static JHelpNavigator getNavigator(JHelp jhelp)
{
JHelpNavigator nav = null;
for (Enumeration e = jhelp.getHelpNavigators(); e.hasMoreElements(); )
{
nav = (JHelpNavigator) e.nextElement();
if (nav.getNavigatorName().equals("TOC"))
{
return nav;
}
}
return null;
}
//http://forums.sun.com/thread.jspa?threadID=350180#1459484
private static JTree getTree(JHelpNavigator nav)
{
JTree tree = null;
Component[] components = nav.getComponents();
int count = components.length;
int i = 0;
while(i < count && !(components[i] instanceof JScrollPane))
{
i++;
}
if(i < count)
{
JScrollPane sp = (JScrollPane) components[i];
components = sp.getViewport().getComponents();
count = components.length;
i = 0;
while(i < count && !(components[i] instanceof JTree))
{
i++;
}
tree = (JTree) components[i];
}
return tree;
}
//http://www.rkcole.com/articles/swing/TraverseJtree-2000-11-17.html
private static void findNode(TreeModel model, Object o, String search)
{
if(match != null)
return;
int count = model.getChildCount(o);
for( int i=0; i < count; i++ )
{
Object child = model.getChild(o, i );
if ( model.isLeaf( child ) )
{
if(search.equals(((TOCItem)((DefaultMutableTreeNode)child).getUserObject()).getName()))
{
match = (DefaultMutableTreeNode)child;
return;
}
}
else
findNode( model, child, search );
}
}
}
Are you using a merged helpset? I get the behaviour you see if I use setCurrentID("topic") and topic is not the master helpset but in a sub-helpset. What you want to do instead is
setCurrentID(javax.help.Map.ID.create("topic",subhelpset))
For me, this gets the TOC view selection to happen correctly. Of course, these probably means you need to use dynamically merged helpsets since I don't see (off hand) an easy way to find the subhelpset if it's specified in the master helpset's .hs file.
Similarly, for context sensitive help, you need to set both the ID and the helpset on each Component.