Custom IOperation  SOLVED

PDF-XChange Editor SDK for Developers

Moderators: TrackerSupp-Daniel, Tracker Support, Vasyl-Tracker Dev Team, Sean - Tracker, Paul - Tracker Supp, Chris - Tracker Supp, Tracker Supp-Stefan, Ivan - Tracker Software

Forum rules
DO NOT post your license/serial key, or your activation code - these forums, and all posts within, are public and we will be forced to immediately deactivate your license.

When experiencing some errors, use the IAUX_Inst::FormatHRESULT method to see their description and include it in your post along with the error code.
Post Reply
zarkogajic
User
Posts: 648
Joined: Thu Sep 05, 2019 12:35 pm

Custom IOperation

Post by zarkogajic » Sat Nov 30, 2019 2:45 pm

Hi support,

How would I go about creating my custom IOperation? I would need something similar to op.opendoc with input, options, output.

I guess I need to have a class implementing IOperation. I'm not sure what should I return for get_Params(out pParam).

Also, should the following be used as well: IPXV_Inst::RegisterOpCreator, IPXV_OperationsCreator::CreateNewOp, IOperation::CreateInputItem, IAUX_Inst::GetOutputItem, ...


-žarko

User avatar
Sasha - Tracker Dev Team
User
Posts: 4885
Joined: Fri Nov 21, 2014 8:27 am
Contact:

Re: Custom IOperation

Post by Sasha - Tracker Dev Team » Tue Dec 03, 2019 10:18 am

Hello žarko,

For the custom operation creation you will need to implement all of the methods and properties of the IOperation interface.
The main methods that are required are Do() where all of the work is done and ShowSetupUI where the custom dialog is being shown to customize the options. Though if you don't need the dialog at all, the E_NOTIMPL should be returned from the ShowSetupUI method.

Also, you will have to store the ICab in your operation's class. In the operation class constructor, the cab should be created by using the IAUX_Inst::CreateEmptyCab method. Then you should obtain the Root node from it and do the SetTemplate2 method, where the XML string should be passed and the third parameter is the ID of the root node in the strings file. This will initialize the cab structure with the default parameters.
The example of the XML:

Code: Select all

<?xml version="1.0" encoding="utf-8"?>

<CabTemplate>

	<ItemSet id="StringArray">
		<Item type="String"/>
	</ItemSet>

	<ItemSet id="NumArray">
		<Item type="Double"/>
	</ItemSet>

	<ItemSet id="OutItem">
		<Item type="IUnknown"/>
	</ItemSet>

	<ItemSet id="FontParams">
		<Item id="Name" type="String" defVal="ArialMT" />
		<Item id="Size" type="Double" defVal="12.0" />
		<Item id="Underline" type="Int" defVal="0" />
		<Item id="StrikeOut" type="Int" defVal="0" />
		<Item id="FColor" type="String" defVal="Gray(0)" />
		<Item id="SColor" type="String" defVal="N" />
		<Item id="StrokeWidth" type="Double" defVal="0.0" />
		<Item id="HScale" type="Double" defVal="100.0" />
		<Item id="TextRise" type="Double" defVal="0.0" />
	</ItemSet>

	<ItemSet id="PageProps">
		<Item id="PaperType" type="Int" defVal="1" />
		<Item id="Landscape" type="Bool" defVal="false" />
		<Item id="StdPaperIndex" type="Int" defVal="4" />
		<Item id="Width" type="Double" defVal="595.3" />
		<Item id="Height" type="Double" defVal="841.9" />
	</ItemSet>

	<ItemSet id="RectF">
		<Item id="top" type="Double" defVal="40.0" />
		<Item id="bottom" type="Double" defVal="40.0" />
		<Item id="left" type="Double" defVal="20.0" />
		<Item id="right" type="Double" defVal="20.0" />
	</ItemSet>

	<ItemSet id="CSVToPDFOperationOptions">
		<Item id="Font" type="Dictionary" itemSet="FontParams" />
		<Item id="Page" type="Dictionary" itemSet="PageProps" />
		<Item id="Margins" type="Dictionary" itemSet="RectF" />
		<Item id="Delimiter" type="String" defVal="" /> <!--Empty means auto-->
		<Item id="Quotes" type="String" defVal="&quot;" />
		<Item id="Flags" type="Int" defVal="0" />
		<Item id="BorderColor" type="String" defVal="Gray(0)" />
		<Item id="BorderWidth" type="Double" defVal="0.0" />
		<Item id="ColWidth" type="Array" itemSet="NumArray"/>
	</ItemSet>

	<!--Operation Block-->
	
	<ItemSet id="CSVToPDFOperation"> <!-- CSV To PDF Operation -->
		<Item id="Input" type="Array" itemSet="OutItem"/>
		<Item id="Output" type="Array" itemSet="OutItem"/>
		<Item id="Options" type="Dictionary" itemSet="CSVToPDFOperationOptions" />
	</ItemSet>
	
	<Root type="Dictionary" itemSet="CSVToPDFOperation" id="CSVToPDFOperationRoot"  />
	
</CabTemplate>
In case of these settings, the operation's SetTemplate2 method will look like:

Code: Select all

pRoot->SetTemplate2((void*)m_pPlugin->m_sCabOptTemplString.c_str(), (long)m_pPlugin->m_sCabOptTemplString.length(), CComBSTR(L"CSVToPDFOperationRoot"));
After this, you will need a way to get/store the settings from/into the Registry. For this we will need to get to the Settings Operations node and place our node if it was not there yet.
From what I see, the https://sdkhelp.tracker-software.com/vi ... ode_Insert method can be used after checking for the node existence via the https://sdkhelp.tracker-software.com/vi ... ItemExists method.
We use two methods LoadParams and SaveParams in our classes. The LoadParams method should load the params from registry (if the node exists):

Code: Select all

	CComPtr<ICabNode> pSett;
	// Getting settings Operations node
	//Then writing the settings from it into our local copy inside of the operation
	CComPtr<ICabNode> pLastOpts;
	CComVariant idx(L"CSVToPDFLastUsedOptions");
	pSett->get_Item(idx, &pLastOpts);
	if (pLastOpts)
	{
		CComPtr<ICabNode> pParamsRoot;
		m_pCabParams->get_Root(&pParamsRoot);
		CComPtr<ICabNode> pOptions;
		pParamsRoot->get_Item(CComVariant(L"Options"), &pOptions);
		pOptions->Copy(pLastOpts);
	}

	return DS_OK;
The SaveParams method saves the settings into the Operations node as a sub dictionary:

Code: Select all

	CComPtr<ICabNode> pSett;
	// Getting the Operation node from settings
	//Then trying to get our node if not, we'll have to create it
	CComPtr<ICabNode> pLastOpts;
	CComVariant idx(L"CSVToPDFLastUsedOptions"); // bp_optp.xml:Plugin
	pSett->get_Item(idx, &pLastOpts);
	if (!pLastOpts)
	{
		//Here we should insert our node into the settings and initialize it via template like we've done in the constructor
		
	}

	_ASSERT(pLastOpts != NULL);

	if (pLastOpts)
	{
		CComPtr<ICabNode> pParamsRoot;
		m_pCabParams->get_Root(&pParamsRoot);
		CComPtr<ICabNode> pOptions;
		pParamsRoot->get_Item(CComVariant(L"Options"), &pOptions);
		pLastOpts->Copy(pOptions);
	}

	return DS_OK;
The Load/SaveParams methods are needed only when you want to store and load settings from the registry. If not, these can be omitted.

The get_Params property should return your inner operation ICab so that it can be filled from the outside like so:

Code: Select all

get_Params(ICab **pParams)
{
	if (m_pCabParams == nullptr)
		return E_INVALIDARG;
	ICab* pPara = m_pCabParams;
	if (pPara)
		pPara->AddRef();
	*pParams = pPara;
	return S_OK;
}
The Do() method should:
1) Get the input document/data.
2) Check for security, etc.
3) Convert CAB parameters into the inner class variables.
4) Create Undo/Redo data class if required (also check the operation flags).
5) Create the IProgressMon if required (check the flags if you can do this).
6) Do all of the work by using the Undo/Redo class and IProgressMon for canceling
7) Modify the resulting document
8) Set the Undo/Redo class as a new history item
9) Send all of the needed events
10) Fill the output node with document

The ShowSetupUI method should create the dialog, load the current operation settings into it and if user clicks OK, then we load the new settings from the dialog back into the operation for further usage.


The operation itself should be created in the next pattern:
1) The operation class is created.
2) Do the LoadParams() if needed to update the operation's inner CAB data with the Registry values
3) Do the ShowSetupUI() method if you need the dialog
4) Do the Operation() (sync or async depends on what you need - the progress can be shown only when you use AsyncDoAndWaitForFinish execution)
5) Save the parameters back into registry if the operation was successful
6) Read the document from the output if needed

If the operation has an ID and you want to use the CreateOp method then you should create an https://sdkhelp.tracker-software.com/vi ... rOpCreator class that will create your operation by the given ID and will return an IOperation pointer.

Cheers,
Alex
Join us at Google+:
https://plus.google.com/+PDFXChangeEditorTS
Subscribe at:
https://www.youtube.com/channel/UC-TwAMNi1haxJ1FX3LvB4CQ

zarkogajic
User
Posts: 648
Joined: Thu Sep 05, 2019 12:35 pm

Re: Custom IOperation

Post by zarkogajic » Tue Dec 03, 2019 11:11 am

Hi Alex,

Thanks much!

Frankly I thought that will be a few lines of code :)

I'll mark as "solved" and when the time comes to implement this - if there are some questions - I'll post here...

-žarko

User avatar
Sasha - Tracker Dev Team
User
Posts: 4885
Joined: Fri Nov 21, 2014 8:27 am
Contact:

Re: Custom IOperation

Post by Sasha - Tracker Dev Team » Tue Dec 03, 2019 11:58 am

:)
Join us at Google+:
https://plus.google.com/+PDFXChangeEditorTS
Subscribe at:
https://www.youtube.com/channel/UC-TwAMNi1haxJ1FX3LvB4CQ

zarkogajic
User
Posts: 648
Joined: Thu Sep 05, 2019 12:35 pm

Re: Custom IOperation

Post by zarkogajic » Fri Mar 27, 2020 10:29 am

Hi support.

Can you please confirm/explain the following:

When Inst.CreateOp is called for a standard operation, like "op.openDoc", your internal operations creator does the job.

If Inst.CreateOp is called for an ID that does not match any of standard operation IDs, you will look into registered creators (IPXV_OperationsCreator) and CreateNewOp will be called (in there I would instantiate my IOperation implementation for the passed ID)?

Can there be 2 registered creators?

-žarko

User avatar
Sasha - Tracker Dev Team
User
Posts: 4885
Joined: Fri Nov 21, 2014 8:27 am
Contact:

Re: Custom IOperation

Post by Sasha - Tracker Dev Team » Fri Mar 27, 2020 10:42 am

Hello žarko,

Of course - you should register your own operation creator for such cases.

Cheers,
Alex
Join us at Google+:
https://plus.google.com/+PDFXChangeEditorTS
Subscribe at:
https://www.youtube.com/channel/UC-TwAMNi1haxJ1FX3LvB4CQ

zarkogajic
User
Posts: 648
Joined: Thu Sep 05, 2019 12:35 pm

Re: Custom IOperation

Post by zarkogajic » Fri Mar 27, 2020 11:19 am

Hi Alex,

Yes, I know :)

I just want to be sure I understand what happening in the background .. so if you can comment on the two raised questions...


User avatar
Sasha - Tracker Dev Team
User
Posts: 4885
Joined: Fri Nov 21, 2014 8:27 am
Contact:

Re: Custom IOperation  SOLVED

Post by Sasha - Tracker Dev Team » Fri Mar 27, 2020 11:22 am

Hello zarkogajic,

There can be multiple registered creators. If the ID of the operation is not in the default creator, we'll try to find it in other registered creators.

Cheers,
Alex
Join us at Google+:
https://plus.google.com/+PDFXChangeEditorTS
Subscribe at:
https://www.youtube.com/channel/UC-TwAMNi1haxJ1FX3LvB4CQ

zarkogajic
User
Posts: 648
Joined: Thu Sep 05, 2019 12:35 pm

Re: Custom IOperation

Post by zarkogajic » Fri Mar 27, 2020 11:30 am

That's it, thanks.


User avatar
Sasha - Tracker Dev Team
User
Posts: 4885
Joined: Fri Nov 21, 2014 8:27 am
Contact:

Re: Custom IOperation

Post by Sasha - Tracker Dev Team » Fri Mar 27, 2020 11:35 am

:)
Join us at Google+:
https://plus.google.com/+PDFXChangeEditorTS
Subscribe at:
https://www.youtube.com/channel/UC-TwAMNi1haxJ1FX3LvB4CQ

Post Reply