Standardize the initial Oxygen layout, toolbar, and default Preferences users

Oxygen general issues.
kondalamca123
Posts: 12
Joined: Tue Sep 10, 2024 8:48 am

Standardize the initial Oxygen layout, toolbar, and default Preferences users

Post by kondalamca123 »

Hi All,
We need a requirement Standardize the initial Oxygen layout, toolbar, and default Preferences users,
Configure Oxygen to show only the following views for the DITA Perspective by default (either in Window > Load Layout or other method):
DITA Maps Manager
Editor
Attributes
Elements
Please help on it.
Regards,
Kondala.
Radu
Posts: 9297
Joined: Fri Jul 09, 2004 5:18 pm

Re: Standardize the initial Oxygen layout, toolbar, and default Preferences users

Post by Radu »

Hi Kondala,
In the Oxygen Preferences->"Application Layout" page one can choose to use a custom layout file:
https://www.oxygenxml.com/doc/versions/ ... ayout.html
If for example you open Oxygen in the DITA perspective, hide and show various views and then use the Oxygen main menu Window->"Export layout" action, you can save the custom layout to a file. And then the others can configure the Preferences->"Application Layout" page to use the layout which has been saved by you.
People will still be able to open other side views from the Oxygen main menu "Window->Show view" submenu.
Regards,
Radu
Radu Coravu
<oXygen/> XML Editor
http://www.oxygenxml.com
kondalamca123
Posts: 12
Joined: Tue Sep 10, 2024 8:48 am

Re: Standardize the initial Oxygen layout, toolbar, and default Preferences users

Post by kondalamca123 »

Hi Radu,
Thanks for quick reply,
Actually we are implemented the plugin for oxygen and it is using many of the writers and they are technically poor and we need to write java program part of plugin and it need to customize the required View,so is there any chance to implement programmatically for below custom Oxygen layout,

DITA Maps Manager
Editor
Attributes
Elements

Regards,
Kondala.
Radu
Posts: 9297
Joined: Fri Jul 09, 2004 5:18 pm

Re: Standardize the initial Oxygen layout, toolbar, and default Preferences users

Post by Radu »

Hi Kondala,

With a plugin there are two possibilities:

1) When the application starts a plugin can impose custom options:
https://www.oxygenxml.com/InstData/Edit ... orage.html
Like for example this custom Javascript-based plugin which uses the API above to impose a custom XML options file (which was exported from Oxygen):
https://github.com/oxygenxml/wsaccess-j ... se-options

2) We have a specific plugin extension type which can remove side views completely, so that they can no longer be shown by the end user:
https://www.oxygenxml.com/doc/versions/ ... lugin.html
The "ro.sync.exml.ComponentsValidator.validateComponent(String)" callback should be called both for toolbar names and side view names, allowing you to completely remove entire toolbars or views from the application.

Regards,
Radu
Radu Coravu
<oXygen/> XML Editor
http://www.oxygenxml.com
kondalamca123
Posts: 12
Joined: Tue Sep 10, 2024 8:48 am

Re: Standardize the initial Oxygen layout, toolbar, and default Preferences users

Post by kondalamca123 »

HI Radu,
Greetings of the day !!!
Thanks for below solutions for me.
Regarding the option 1, we are developed the my plugin using java and we did not implemented any java script program in my plugin project,
so is there any alternative way to achieve same using Java,if yes please share to me same Code .

Regarding Option 2 if you have any implemented project or sample code with you please share with me it will very helpful for me.

Have a advance happy weekend.

Thanks And Regards,
Kondala.
Radu
Posts: 9297
Joined: Fri Jul 09, 2004 5:18 pm

Re: Standardize the initial Oxygen layout, toolbar, and default Preferences users

Post by Radu »

Hi Kondala,

For (1) the Javascript plugins I exemplified call the existing Java APIs.
From the Java code of any plugin you can call for example:

Code: Select all

        PluginWorkspaceProvider.getPluginWorkspace().setGlobalObjectProperty("key", "value");
        PluginWorkspaceProvider.getPluginWorkspace().importGlobalOptions(new File("..."));
For (2) please see:
https://github.com/oxygenxml/oxygen-com ... ter-plugin

Regards,
Radu
Radu Coravu
<oXygen/> XML Editor
http://www.oxygenxml.com
kondalamca123
Posts: 12
Joined: Tue Sep 10, 2024 8:48 am

Re: Standardize the initial Oxygen layout, toolbar, and default Preferences users

Post by kondalamca123 »

Hi Radu,
Thanks for the detailed explanation of the 2 options so here i have chosen option 1 to implement and i have exported the global option file but i didn't find the setting for "outline" view which i want to hide (One example.)please help me how i can get those values.
image.png
image.png (2.48 KiB) Viewed 2219 times
and also if possible could you please share the sample file for importGlobalOptions(new File("...") i mean is it a XML file or java File ?

Regards,
Kondala.
Attachments
image.png
image.png (4.19 KiB) Viewed 2219 times
Radu
Posts: 9297
Joined: Fri Jul 09, 2004 5:18 pm

Re: Standardize the initial Oxygen layout, toolbar, and default Preferences users

Post by Radu »

Hi Kondala,

The exported global XML file contains in general settings you have changed in the Oxygen "Preferences" dialog.
The way in which different toolbars and side views are shown/hidden by default is not saved in side the global preferences XML export file. Please see the answer I gave below in this same thread:
post75141.html#p75141

So you could export separately the views/toolbars layout to a file, then in the Oxygen Preferences->"Application Layout" page configure to use a custom layout file and the path to the custom layout file can contain editor variables like "${pluginDir(pluginID)}":
https://www.oxygenxml.com/doc/versions/ ... ables.html
Then export also the global settings to an XML document which should have the "Preferences->"Application Layout" configured. So your plugin could contain both the global preferences XML file to be imposed and a custom layout file with the visible states of views and toolbars.
and also if possible could you please share the sample file for importGlobalOptions(new File("...") i mean is it a XML file or java File ?
The Java JDK "java.io.File" class allows manipulating any type of file including XML files. So importGlobalOptions is called with a path to an XML file containing the exported settings.

Regards,
Radu
Radu Coravu
<oXygen/> XML Editor
http://www.oxygenxml.com
kondalamca123
Posts: 12
Joined: Tue Sep 10, 2024 8:48 am

Re: Standardize the initial Oxygen layout, toolbar, and default Preferences users

Post by kondalamca123 »

H Radu,
I have achieved the above requirement using below process.
I have created the .layoutfile and called it from my Plugin code at the time of applicationStarted and kept the layout file under resource directory.

Code: Select all

File layoutFile = new File(baseDir, "application.layout");
if (layoutFile.exists()) {
 ro.sync.exml.options.PerspectivesLayoutInfo info = new PerspectivesLayoutInfo(true, false, "", layoutFile.getAbsolutePath());
 pluginWorkspaceAccess.setGlobalObjectProperty("perspectives.layout.info", info);
}
but there are 2 issues,
1.i kept the layout file under resource directory but my plugin code not reading it directly so i have read it as stream and created the temp file locally and point that file as a baseDir.
image.png
image.png (30.12 KiB) Viewed 2070 times
So please suggest me where should i keep the layout file instead of resource Directory(I need to point out that path directly to baseDir).
2.my custom layout is working after click on reset layout button under window Menu but it should show the custom layout at the time of oxygen start only so please suggest for this issue
image.png
image.png (18.55 KiB) Viewed 2070 times
Radu
Posts: 9297
Joined: Fri Jul 09, 2004 5:18 pm

Re: Standardize the initial Oxygen layout, toolbar, and default Preferences users

Post by Radu »

Hi,
You seem to be on the right track.
So:
1.i kept the layout file under resource directory but my plugin code not reading it directly so i have read it as stream and created the temp file locally and point that file as a baseDir.
Assuming you have a workspace access plugin extension like the sample one here: https://github.com/oxygenxml/sample-plu ... /workspace
and it has a singleton class similar to this: https://github.com/oxygenxml/sample-plu ... /workspace
You can call WorkspaceAccessPlugin.getInstance().getDescriptor().getBaseDir() to find out the base folder of the plugin.

An alternative, your plugin.xml has an @id attribute with a certain custom value.
You can use as a file path editor variables like "${pluginDir(your.plugin.id.value)}", so the code would look like:

Code: Select all

new PerspectivesLayoutInfo(true, false, "", "${pluginDir(your.plugin.id.value)}/resources/application.layout" );
Or you can use the API:

Code: Select all

PluginWorkspaceProvider.getPluginWorkspace().getUtilAccess().expandEditorVariables("${pluginDir(your.plugin.id.value)}", null)
to expand such a pluginDir editor variable to the actual folder where it points.

More about editor variables:
https://www.oxygenxml.com/doc/versions/ ... iables.htm
2.my custom layout is working after click on reset layout button under window Menu but it should show the custom layout at the time of oxygen start only so please suggest for this issue
How about if you also impose the "Reset layout on startup" checkbox setting?
So something like:

Code: Select all

pluginWorkspaceAccess.setGlobalObjectProperty("reset.layout.at.startup", Boolean.TRUE)
?

Regards,
Radu
Radu Coravu
<oXygen/> XML Editor
http://www.oxygenxml.com
kondalamca123
Posts: 12
Joined: Tue Sep 10, 2024 8:48 am

Re: Standardize the initial Oxygen layout, toolbar, and default Preferences users

Post by kondalamca123 »

Thanks Radu for your Answer ,
Need one more info,
along with reset layout Perspective i need to select Dita Perspective at the time of Oxygen load/Start Time please let me know Solution for the same (I mean both the Perspective need to Select at the time of Oxygen load/Start Time ).
Regards,
Kondala.
Radu
Posts: 9297
Joined: Fri Jul 09, 2004 5:18 pm

Re: Standardize the initial Oxygen layout, toolbar, and default Preferences users

Post by Radu »

HI Kondala,

For forcing a certain perspective when starting Oxygen, setting this global option when the applicationStarted callback is received in your plugin extension might work:

Code: Select all

pluginWorkspaceAccess.setGlobalObjectProperty("current.perspective.ID", new Integer(1));
Regards,
Radu
Radu Coravu
<oXygen/> XML Editor
http://www.oxygenxml.com
kondalamca123
Posts: 12
Joined: Tue Sep 10, 2024 8:48 am

Re: Standardize the initial Oxygen layout, toolbar, and default Preferences users

Post by kondalamca123 »

HI Radu,
I have implemented the below 2 functionalities in my plugin code but importunately both are not working so please suggest any configuration need or if i am wrong.
oxygenWorkspace.setGlobalObjectProperty("reset.layout.at.startup", Boolean.TRUE);

oxygenWorkspace.setGlobalObjectProperty("current.perspective.ID", new Integer(1));

Regards,
Kondala.
Radu
Posts: 9297
Joined: Fri Jul 09, 2004 5:18 pm

Re: Standardize the initial Oxygen layout, toolbar, and default Preferences users

Post by Radu »

Hello Kondala,
So I create on my side this minimal plugin which when the "applicationStarted" callback is received sets these two settings:

Code: Select all

   @Override
    public void applicationStarted(StandalonePluginWorkspace pluginWorkspaceAccess) {
      pluginWorkspaceAccess.setGlobalObjectProperty("reset.layout.at.startup", Boolean.TRUE);
      pluginWorkspaceAccess.setGlobalObjectProperty("current.perspective.ID", new Integer(1));
    }
When I close Oxygen the default perspective is the editor perspective and the "Reset layout at startup" chekbox from the "Application Layout" preferences page is unchecked.
I start Oxygen with my plugin installed, when it starts up the current perspective is now "DITA" and the "Reset layout at startup" chekbox from the "Application Layout" preferences page is checked.
Regards,
Radu
Radu Coravu
<oXygen/> XML Editor
http://www.oxygenxml.com
kondalamca123
Posts: 12
Joined: Tue Sep 10, 2024 8:48 am

Re: Standardize the initial Oxygen layout, toolbar, and default Preferences users

Post by kondalamca123 »

Hi Radu,
Yes my case also "Reset layout at startup" chekbox from the "Application Layout" preferences page is checked.but whenever i clicked on Windows-->Reset layout then only my requirement like above Attributes and Elements option is enabling.
i mean my layout file changes are reflecting.
This is my Issue.

Regards,
Kondala.
Radu
Posts: 9297
Joined: Fri Jul 09, 2004 5:18 pm

Re: Standardize the initial Oxygen layout, toolbar, and default Preferences users

Post by Radu »

Hi Kondala,
Not sure, can you give me a screenshot from the Oxygen "Preferences->Application Layout" page from your side? Is the custom reference to the layout file properly set there by the API you used tto impose it?
Regards,
Radu
Radu Coravu
<oXygen/> XML Editor
http://www.oxygenxml.com
kondalamca123
Posts: 12
Joined: Tue Sep 10, 2024 8:48 am

Re: Standardize the initial Oxygen layout, toolbar, and default Preferences users

Post by kondalamca123 »

HI Radu,
Sorry for the late reply actually this layout ticket carry to next sprint and other priority task i did not provided required info on time ,
apologies for same.
Below is the required info.
image.png
image.png (85.2 KiB) Viewed 1300 times
Please review and help on it.
With Best Regards,
Kondala.
Radu
Posts: 9297
Joined: Fri Jul 09, 2004 5:18 pm

Re: Standardize the initial Oxygen layout, toolbar, and default Preferences users

Post by Radu »

Hi Kondala,
So can you give me again details about how your Java code which attempts to impose the custom layout file looks like?
Regards,
Radu
Radu Coravu
<oXygen/> XML Editor
http://www.oxygenxml.com
kondalamca123
Posts: 12
Joined: Tue Sep 10, 2024 8:48 am

Re: Standardize the initial Oxygen layout, toolbar, and default Preferences users

Post by kondalamca123 »

Hi Radu,
Below is the my Code,

Code: Select all

		InputStream inputStream = getClass().getResourceAsStream("/oakf_layout.layout");
		if (inputStream != null) {
			try {

				Path tempFile = Files.createTempFile("oakf_layout", ".layout");
				Files.copy(inputStream, tempFile, java.nio.file.StandardCopyOption.REPLACE_EXISTING);

				File layoutFile = tempFile.toFile();

				ro.sync.exml.options.PerspectivesLayoutInfo info = new PerspectivesLayoutInfo(true, false, "",
						layoutFile.getAbsolutePath());

				oxygenWorkspace.setGlobalObjectProperty("perspectives.layout.info", info);
				

			} catch (Exception e) {
				logger.error("Exception at oakf_layout Set Up: {}", e.getMessage());
			} finally {
				try {
					inputStream.close();
				} catch (Exception e) {
					logger.error("Exception at oakf_layout inputStream Close Time", e.getMessage());
				}
			}
		}
		oxygenWorkspace.setGlobalObjectProperty("reset.layout.at.startup", Boolean.TRUE);
		oxygenWorkspace.setGlobalObjectProperty("current.perspective.ID", new Integer(1));
		
	}
Reagdrs,
Kondala.
Radu
Posts: 9297
Joined: Fri Jul 09, 2004 5:18 pm

Re: Standardize the initial Oxygen layout, toolbar, and default Preferences users

Post by Radu »

Hi Kondala,
I assume your code is called directly from the plugin's applicationStarted callback, not on a separate execution thread or delayed in some way until the application starts.
I created something similar on my side:

Code: Select all

    @Override
    public void applicationStarted(StandalonePluginWorkspace pluginWorkspaceAccess) {
      pluginWorkspaceAccess.setGlobalObjectProperty("reset.layout.at.startup", Boolean.TRUE);
      pluginWorkspaceAccess.setGlobalObjectProperty("current.perspective.ID", new Integer(1));
      ro.sync.exml.options.PerspectivesLayoutInfo info = new PerspectivesLayoutInfo(true, false, "",
          "/Users/raducoravu/Desktop/test.layout");
      pluginWorkspaceAccess.setGlobalObjectProperty("perspectives.layout.info", info);
    }
and this works for me.
Some observations:
There are a number of perspectives in Oxygen, among which the "Editor Perspective" and the "DITA Perspective". When you save a layout file, it will remember for each individual perspective the position of the views and toolbars.
So if for example you are working in the "Editor Perspective", move views around and then export the layout file, the layout file will contain a custom views configuration for the "Editor Perspective", if you load the layout file while working in the "DITA Perspective" the layout will not change.
So as you are imposing the "DITA Perspective" to the end user you should also export the layout file when working in the "DITA Perspective" as you want the custom views layout to be imposed for the "DITA Perspective".

Regards,
Radu
Radu Coravu
<oXygen/> XML Editor
http://www.oxygenxml.com
kondalamca123
Posts: 12
Joined: Tue Sep 10, 2024 8:48 am

Re: Standardize the initial Oxygen layout, toolbar, and default Preferences users

Post by kondalamca123 »

sample-plugin-workspace-access.zip
(170.85 KiB) Downloaded 58 times
Oxy_layout.zip
(2.51 KiB) Downloaded 57 times
Hi Radu,

I have created the sample Plugin project and i tried it in my local but its not working ,
For your reference please find the sample project and layout file.
Please validate and let us know where we did wrong ?

With Best Regards,
Kondala.
Radu
Posts: 9297
Joined: Fri Jul 09, 2004 5:18 pm

Re: Standardize the initial Oxygen layout, toolbar, and default Preferences users

Post by Radu »

Hi Kondala,

I'm working with Oxygen 27 but I think the version should not matter much.
I looked inside the "Oxy_layout.layout" and it contains customizations for the DITA perspective so that looks good.
I reduced the sample plugin project to this code which points on my disk to the "Oxy_layout.layout" you gave me:

Code: Select all

    @Override
    public void applicationStarted(StandalonePluginWorkspace pluginWorkspaceAccess) {
      pluginWorkspaceAccess.setGlobalObjectProperty("reset.layout.at.startup", Boolean.TRUE);
      pluginWorkspaceAccess.setGlobalObjectProperty("current.perspective.ID", new Integer(1));
          ro.sync.exml.options.PerspectivesLayoutInfo info = new PerspectivesLayoutInfo(true, false, "",
              "/Users/raducoravu/Downloads/Oxy_layout.layout");
          pluginWorkspaceAccess.setGlobalObjectProperty("perspectives.layout.info", info);
    }
Then I started Oxygen, it started in the DITA perspective with the side views customized.
Screenshot 2024-12-06 at 15.39.22.png
Screenshot 2024-12-06 at 15.39.22.png (261.95 KiB) Viewed 756 times
And the imposed settings also look good in the Preferences page.
Screenshot 2024-12-06 at 15.32.41.png
Screenshot 2024-12-06 at 15.32.41.png (291.66 KiB) Viewed 756 times
Regards,
Radu
Radu Coravu
<oXygen/> XML Editor
http://www.oxygenxml.com
vishwavaranasi
Posts: 165
Joined: Fri Feb 28, 2020 4:02 pm

Re: Standardize the initial Oxygen layout, toolbar, and default Preferences users

Post by vishwavaranasi »

Hello Radu , when we exported our layout file we kept the following view
image.png
image.png (77.94 KiB) Viewed 681 times
just we wanted "Elements" and "Attributes" and then exported our layout file. that's what we shared with you.
and the applicationStarted() method as below.

Code: Select all

/**
   * @see ro.sync.exml.plugin.workspace.WorkspaceAccessPluginExtension#applicationStarted(ro.sync.exml.workspace.api.standalone.StandalonePluginWorkspace)
   */
  public void applicationStarted(final StandalonePluginWorkspace pluginWorkspaceAccess) {
	 		
	  ro.sync.exml.options.PerspectivesLayoutInfo customLayout = new PerspectivesLayoutInfo(true, false, "","C:\\Users\\xxxx\\Desktop\\tickets\\oxygen26\\my_custom_layout.layout");
	
	MenuItem1Action = createMenuItem1Action(pluginWorkspaceAccess);
	 MenuItem2Action = createMenuItem2Action(pluginWorkspaceAccess);
	

	  // Create your own main menu and add it to Oxygen or remove one of Oxygen's menus...
	  pluginWorkspaceAccess.addMenuBarCustomizer(new MenuBarCustomizer() {
		  /**
		   * @see ro.sync.exml.workspace.api.standalone.MenuBarCustomizer#customizeMainMenu(javax.swing.JMenuBar)
		   */
		  public void customizeMainMenu(JMenuBar mainMenuBar) {
			  JMenu myMenu = new JMenu("My menu");
			
			  myMenu.add(MenuItem1Action);
			  MenuItem2Action.setEnabled(false);
			  myMenu.add(MenuItem2Action);
			  // Add your menu before the Help menu
			  mainMenuBar.add(myMenu, mainMenuBar.getMenuCount() - 1);
		  }
	  });


	  pluginWorkspaceAccess.addEditorChangeListener(
			  new WSEditorChangeListener() {
				  @Override
				  public boolean editorAboutToBeOpenedVeto(URL editorLocation) {
					  //You can reject here the opening of an URL if you want
					  return true;
				  }
				  @Override
				  public void editorOpened(URL editorLocation) {
					  
				  }

				  // Check actions status
				  private void checkActionsStatus(URL editorLocation) {
					  
				  }

				  @Override
				  public void editorClosed(URL editorLocation) {
					  //An edited XML document has been closed.
				  }

				  /**
				   * @see ro.sync.exml.workspace.api.listeners.WSEditorChangeListener#editorAboutToBeClosed(java.net.URL)
				   */
				  @Override
				  public boolean editorAboutToBeClosed(URL editorLocation) {
					  //You can veto the closing of an XML document.
					  //Allow close
					  return true;
				  }

				  /**
				   * The editor was relocated (Save as was called).
				   * 
				   * @see ro.sync.exml.workspace.api.listeners.WSEditorChangeListener#editorRelocated(java.net.URL, java.net.URL)
				   */
				  @Override
				  public void editorRelocated(URL previousEditorLocation, URL newEditorLocation) {
					  //
				  }

				  @Override
				  public void editorPageChanged(URL editorLocation) {
					  
				  }

				  @Override
				  public void editorSelected(URL editorLocation) {
					  
				  }

				  @Override
				  public void editorActivated(URL editorLocation) {
					  
				  }
			  }, 
			  StandalonePluginWorkspace.MAIN_EDITING_AREA);

	  SwingUtilities.invokeLater(() -> {
			try {
								
				pluginWorkspaceAccess.setGlobalObjectProperty("current.perspective.ID", new Integer(1));
			     			     
				pluginWorkspaceAccess.setGlobalObjectProperty("perspectives.layout.info", customLayout);		
				pluginWorkspaceAccess.setGlobalObjectProperty("reset.layout.at.startup", Boolean.TRUE);
			      
				
			} catch (Exception exception) {
				exception.printStackTrace();
				

			}
			
		});
	 
  }
what we are troubling with that , first time when we launch oxygen the exported layout views are not showing , but when we go Widow -> Reset Layout
it is showing us the correct layout views.
as we thought that the below lines of code will help in startup reset and sets the DITA prospective.
pluginWorkspaceAccess.setGlobalObjectProperty("current.perspective.ID", new Integer(1));
pluginWorkspaceAccess.setGlobalObjectProperty("reset.layout.at.startup", Boolean.TRUE);
would you please let us know , are we missing anything here?
one more question we have , is there anyways that we can see the contents of .layout file? when we try to open it is not opening for us giving error that some encoding problem.
Thanks,
vishwa
Radu
Posts: 9297
Joined: Fri Jul 09, 2004 5:18 pm

Re: Standardize the initial Oxygen layout, toolbar, and default Preferences users

Post by Radu »

Hi,
I'm afraid I do not know what the problem could be.
About this remark:
one more question we have , is there anyways that we can see the contents of .layout file? when we try to open it is not opening for us giving error that some encoding problem.
The ".layout" file is actually a zip, it can be opened with a zip utility but inside the layout files corresponding to each perspective are saved in a binary format by the third party layout manager that we are using. So looking inside the zip will not help much.
Regards,
Radu
Radu Coravu
<oXygen/> XML Editor
http://www.oxygenxml.com
vishwavaranasi
Posts: 165
Joined: Fri Feb 28, 2020 4:02 pm

Re: Standardize the initial Oxygen layout, toolbar, and default Preferences users

Post by vishwavaranasi »

Thanks Radu for your reply.
one thing we observed that , with our .layout file , when we launches the oxygen editor initially , no views were showed up , but we open any of our DITA files , then the "Elements" and "Attributes" showing up.
is this what we can conclude that , the .layout works fine in our case?

Thanks,
Vishwa
Thanks,
vishwa
Radu
Posts: 9297
Joined: Fri Jul 09, 2004 5:18 pm

Re: Standardize the initial Oxygen layout, toolbar, and default Preferences users

Post by Radu »

Hi Vishwa,
Certain side views like Outline, Attributes, Elements are connected to an opened document. So if no document is opened or a non XML document is currently opened and selected these contextual side views hide themselves automatically.
Regards,
Radu
Radu Coravu
<oXygen/> XML Editor
http://www.oxygenxml.com
Post Reply