How to implement the "Save target as" function

Dear SDL OpenExchange developers,

I would like to insert an action in the Studio UI which would first save the translation to its original format using the equivalent of "Save target as" and then apply further functions on the document.

I am not really sure on how to implement the "Save target as" function. I found the following action in the IntegrationApi: SaveDocumentTargetAsAction . However, I am not sure on how I can implement it in my code (and there are no example illustrating how to do it).

Can anyone help me or give me an indication on how I can declare/use it? it would be much appreciated. Or suggest an alternative?

Thanks in advance!

Regards,

Laurent

  • Dear Patrik,

    I have never been sure about what the Export files function really does. Is it just generating the target files in their native format or is it also changing something in the status of the file ? if changes need to be done after the Export files function has been used, does the Translator need to do a "Revert to SDLXLIFF"?

    Thanks in advance for these additional information.

    Regards,

    Laurent

  • Hi,

    nope Export task only creates requested version of the file in specified location.

    Regards

    Patrik

  • Dear Patrik,

    Then I will use it!

    Thanks for the information.

    regards,

    Laurent

  • Dear Patrik,

    Before implementing it, I have tested the function in the native environment directly and noticed that the Export files function does not exactly work as the "Save target as" function. Sometimes, the task "export files" could not complete, for example when there is no translation for some segments. This problem does not occur when using the "Save target as" function.

    I would rather prefer to use the equivalent of the "Save target as". Is there an equivalent in the API?

    Thanks in advance.

    Regards,

    Laurent

  • Dear Patrik,

    After some digging into the IntegrationAPI, I have detected that the EditorController has a function "SaveTargetAs". I tried to implement it but it does exactly the same as if I would click File-->Save Target As: ie the Save dialog box pops up.

    This means that the SaveTargetAs Function has probably a code block which prompts the save dialog box and continues the save procedure on the basis of the result returned by the dialog box.

    In order to automate this step, would it be possible to get the code of the SaveTargetAs void for what is happening after the dialog box returns its result? I know this is not really in the framework of the API. But even using a window handle to try to click the button will not work (since I would need to launch it at the same time as the SaveTargetAs function).

    Do you think it would be possible to get that part of the code?

    Thanks in advance.

    regards,

    Laurent

  • Dear Patrik,

    Have you already had the time to consider my request? Do you believe it would be feasible to get the requested part of the code?

    Thanks in advance for your support and assistance.

    Regards,

    Laurent

  • Dear Patrik,

    Have you had any chance to consider my request? As mentioned this would really help me to know how I can reproduce the save target as function.

    Thanks in advance.

    Regards,

    Laurent

  • Hi Laurent,

    There is no direct save target as functions in API. But there are some workarounds which might satisfy your needs. For example you mihgt use the "Generate Target translations" task in the Project API to generate the target files. However running this task in project automation api will change the file status. You need set the file status back to the original. An alternate way is to use the EditorController.SaveTargetAs. Unfortunately this function prompts user to select the target files. To get rid of this prompt you might use code to commit the default selection. To check if this works, I made a simple test:

    I add a button (save target as) in the sample SDK project EditOperations view. And add the following code (see the code at the end of mail) to response the button click. The code just starts a new thread to look for the Save target dialogbox and to minic click on the save button. This function assumes you do not have the target files in the corresponding language folder yet. If the files already exist, it will prompt you further message. So you might change a bit the code to clear the target files first before running this function.

    Hope this helps.

    Xingzeng

    //------------------------------ The code starts here

    private void buttonSaveAsTarget_Click(object sender, EventArgs e)
    {
       var targetFiles = SaveActiveDocumentAsTarget();
     
    var targetFileListAsString = string.Join(System.Environment.NewLine, targetFiles);
      
    MessageBox.Show("Saved the current document target as the following files: " + targetFileListAsString);
    }

    // save target for the active document, return the paths of saved files
    string[] SaveActiveDocumentAsTarget()
    {
      // this task is executed in a separate thread to dismiss the file dialog
     
    var task = System.Threading.Tasks.Task.Factory.StartNew(
            () =>
            {
               const string expectedTitle = "Save Target As";    
               const string expectedCommitButtonText = "&Save";
          
    for (var i = 0; i < 20; i ++ ) // loop 20 times to make sure we can catch the dialog
               {
                  var hWnd = FindWindow(null, expectedTitle);
            
    if (hWnd != IntPtr.Zero)
                   {
                       var childs = GetChildWindows(hWnd);  
                       var saveButtonHandle = childs.FirstOrDefault(h => string.Compare(GetWindowText(h), expectedCommitButtonText) == 0);
               
    if (saveButtonHandle != IntPtr.Zero)
                        {
                          this.Invoke(new Action( () =>
                                            {
                                                SendMessage(saveButtonHandle, WM_LBUTTONDOWN, MK_LBUTTON, IntPtr.Zero);
                                                SendMessage(saveButtonHandle, WM_LBUTTONUP, MK_LBUTTON, IntPtr.Zero);
                                            }));
                           break; // done to dismiss the dialogbox, no need to do further loop
                        }
                    }
                   System.Threading.Thread.Sleep(10); // wait for 0.1 second
                }
            });
        // save the active document as target
        GetEditorController().SaveTargetAs(GetEditorController().ActiveDocument);
        var targetFiles = GetEditorController().ActiveDocument.Files.Select(
            f => System.IO.Path.Combine(System.IO.Path.GetDirectoryName(f.LocalFilePath),
                                        System.IO.Path.GetFileNameWithoutExtension(f.LocalFilePath))).ToArray();
      
    return targetFiles;
    }

    static string GetWindowText(IntPtr hWnd)
    {
      const int bufferSize = 256;
     
    var sb = new StringBuilder(bufferSize);
      GetWindowText(hWnd, sb, bufferSize);
     return sb.ToString();
    }

    static IntPtr[] GetChildWindows(IntPtr hWnd)
    {
      var ret = new List<IntPtr>();
     
    var retHandle = System.Runtime.InteropServices.GCHandle.Alloc(ret);
     
    try
        {
            EnumChildWindows(hWnd, EnumWindowCallback, System.Runtime.InteropServices.GCHandle.ToIntPtr(retHandle));
        
    return ret.ToArray();
        }
    catch (Exception ex)    {
       Console.WriteLine(ex);
     
    throw;
        }
    }

    static bool EnumWindowCallback(IntPtr hWnd, IntPtr parameter)
    {  
      var handle = System.Runtime.InteropServices.GCHandle.FromIntPtr(parameter);
     
    var list = handle.Target as List<IntPtr>;
     
    list.Add(hWnd);
      return true;
    }

    private const int WM_LBUTTONDOWN = 0x0201;
    private const int WM_LBUTTONUP = 0x0202;
    private const int MK_LBUTTON = 1;

    public delegate bool EnumWindowProc(IntPtr hwnd, IntPtr parameter);

    [System.Runtime.InteropServices.DllImport("User32.dll")]
    private static extern IntPtr FindWindow(string className, string windowTitle);

    [System.Runtime.InteropServices.DllImport("User32.dll")]
    private static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);

    [System.Runtime.InteropServices.DllImport("User32.dll")]
    private static extern bool EnumChildWindows(IntPtr hwndParent, EnumWindowProc callback, IntPtr parameter);

    [System.Runtime.InteropServices.DllImport("User32.dll")]
    private static extern IntPtr GetActiveWindow();

    [System.Runtime.InteropServices.DllImport("User32.dll")]
    private static extern int SendMessage(IntPtr hWnd, UInt32 msg, int wParam, IntPtr lParam);

     

  • Dear Xingzeng,

    Sorry for the late reply. I was on holiday when you replied and I haven't had time this week to work on this since I have been back.

    Your solution looks very promising. I have also been looking into a solution to "click" the save button, however my attempts were not successful. I believe that the reason why I have not been successful is due to the fact that I have not started a new thread for this.

    I try your solution and will update this post accordingly.

    Thanks in advance.

    Regards,

    Laurent