Skip to content

Commit

Permalink
Paste cells (stevencohn#245)
Browse files Browse the repository at this point in the history
* paste-cells poc

* paste-cell poc

* paste-cells poc

* Paste Table Cells command

* Paste Table Cells command, v3.27

* Paste Table Cells command
  • Loading branch information
stevencohn authored Jul 30, 2021
1 parent 0741289 commit 74e4211
Show file tree
Hide file tree
Showing 18 changed files with 16,249 additions and 1 deletion.
3 changes: 3 additions & 0 deletions OneMore/AddInCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,9 @@ public async Task NumberSectionsCmd(IRibbonControl control)
public async Task OutlineCmd(IRibbonControl control)
=> await factory.Run<OutlineCommand>();

public async Task PasteCellsCmd(IRibbonControl control)
=> await factory.Run<PasteCellsCommand>();

public async Task PasteRtfCmd(IRibbonControl control)
=> await factory.Run<PasteRtfCommand>();

Expand Down
238 changes: 238 additions & 0 deletions OneMore/Commands/Tables/PasteCellsCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
//************************************************************************************************
// Copyright © 2021 Steven M Cohn. All rights reserved.
//************************************************************************************************

namespace River.OneMoreAddIn.Commands
{
using River.OneMoreAddIn.Models;
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Xml.Linq;
using Hap = HtmlAgilityPack;
using Resx = River.OneMoreAddIn.Properties.Resources;
using Win = System.Windows;


/// <summary>
/// Paste the copied cells into the target table, overlaying cells rather than inserting
/// a nested table. The target table is expanded with extra rows or columns as needed.
/// </summary>
internal class PasteCellsCommand : Command
{
private OneNote one;


public PasteCellsCommand()
{
}


public override async Task Execute(params object[] args)
{
using (one = new OneNote(out var page, out var ns))
{
// make sure cursor is positioned in a target table...

var targetRoot = GetTargetTableRoot(page, ns);
if (targetRoot == null)
{
return;
}

var table = new Table(targetRoot);

// anchor is the upper-left cell into which pasting will begin
var anchor = table.GetSelectedCells(out _).FirstOrDefault();

if (anchor == null)
{
logger.WriteLine("could not find anchor cell");
UIHelper.ShowInfo(one.Window, "could not find anchor cell; this shouldn't happen!");
return;
}

// get the content to paste...

var spage = await GetSourcePage();
if (spage == null)
{
UIHelper.ShowInfo(one.Window, Resx.PasteCellsCommand_NoContent);
return;
}

var source = GetSourceTable(spage);
if (source == null)
{
UIHelper.ShowInfo(one.Window, Resx.PasteCellsCommand_NoContent);
return;
}

var mapping = page.MergeQuickStyles(spage);

EnsureRoom(table, anchor, source);

// paste...

for (int r = 0, row = anchor.RowNum - 1; r < source.RowCount; r++, row++)
{
for (int c = 0, col = anchor.ColNum - 1; c < source.ColumnCount; c++, col++)
{
table[row][col].SetContent(source[r][c].GetContent());
page.ApplyStyleMapping(mapping, table[row][col].Root);

var shading = source[r][c].ShadingColor;
if (shading != null)
{
table[row][col].ShadingColor = shading;
}
}
}

await one.Update(page);
}
}


private XElement GetTargetTableRoot(Page page, XNamespace ns)
{
// Find first selected cell as anchor point to locate table; by filtering on
// selected=all, we avoid including the parent table of a selected nested table.

var cell = page.Root.Descendants(ns + "Cell")
// first dive down to find the selected T
.Elements(ns + "OEChildren").Elements(ns + "OE")
.Elements(ns + "T")
.Where(e => e.Attribute("selected")?.Value == "all")
// now move back up to the Cell
.Select(e => e.Parent.Parent.Parent)
.FirstOrDefault();

if (cell == null)
{
UIHelper.ShowInfo(one.Window, Resx.PasteCellsCommand_SelectCell);
return null;
}

var root = cell.Ancestors(ns + "Table").FirstOrDefault();
if (root == null)
{
logger.WriteLine("error finding <one:Table>");
UIHelper.ShowInfo(one.Window, "error finding <one:Table>; this shouldn't happen!");
return null;
}

return root;
}


private async Task<Page> GetSourcePage()
{
// the Clipboard will contain HTML of the copied cells wrapped in a <table>
var content = await SingleThreaded.Invoke(() =>
{
return Win.Clipboard.ContainsText(Win.TextDataFormat.Html)
? Win.Clipboard.GetText(Win.TextDataFormat.Html)
: null;
});

if (string.IsNullOrEmpty(content))
{
return null;
}

if (!ValidateClipboardContent(content))
{
return null;
}

return await CreateTemplatePage();
}


private Table GetSourceTable(Page page)
{
var element = page.Root.Descendants(page.Namespace + "Table").FirstOrDefault();
if (element == null)
{
return null;
}

return new Table(element);
}


private bool ValidateClipboardContent(string content)
{
var index = content.IndexOf("<html");
if (index < 0)
{
return false;
}

try
{
var html = content.Substring(index);
var doc = new Hap.HtmlDocument();
doc.LoadHtml(html);

var table = doc.DocumentNode.SelectSingleNode("//body//table");
if (table == null)
{
logger.WriteLine("no <table> found in content");
return false;
}
}
catch (Exception exc)
{
logger.WriteLine("error parsing clipboard content", exc);
return false;
}

return true;
}


private async Task<Page> CreateTemplatePage()
{
// use a temporary scratch page to convert the HTML into OneNote XML by pasting the
// HTML contents of the clipboard and let OneNote do its magic...

var currentPageId = one.CurrentPageId;

one.CreatePage(one.CurrentSectionId, out var pageId);
await one.NavigateTo(pageId);

// since the Hotkey message loop is watching all input, explicitly setting
// focus on the OneNote main window provides a direct path for SendKeys
Native.SetForegroundWindow(one.WindowHandle);
SendKeys.SendWait("^(v)");

var page = one.GetPage(pageId);
one.DeleteHierarchy(pageId);

await one.NavigateTo(currentPageId);

return page;
}


private void EnsureRoom(Table table, TableCell anchor, Table source)
{
var room = table.RowCount - anchor.RowNum + 1;
while (room < source.RowCount)
{
table.AddRow();
room++;
}

room = table.ColumnCount - anchor.ColNum + 1;
while (room < source.ColumnCount)
{
table.AddColumn(40);
room++;
}
}
}
}
12 changes: 12 additions & 0 deletions OneMore/Models/Table.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,18 @@ public bool HasHeaderRow
}


/// <summary>
/// Get the number of columns in the table.
/// </summary>
public int ColumnCount => columns.Elements().Count();


/// <summary>
/// Get the number of rows in the table.
/// </summary>
public int RowCount => rows.Count;


/// <summary>
/// Gets the rows in this table.
/// </summary>
Expand Down
1 change: 1 addition & 0 deletions OneMore/OneMore.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@
<Compile Include="Commands\Settings\FavoritesSheet.Designer.cs">
<DependentUpon>FavoritesSheet.cs</DependentUpon>
</Compile>
<Compile Include="Commands\Tables\PasteCellsCommand.cs" />
<Compile Include="Commands\Tables\SplitTableCommand.cs" />
<Compile Include="Commands\Tables\SplitTableDialog.cs">
<SubType>Form</SubType>
Expand Down
2 changes: 1 addition & 1 deletion OneMore/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ internal static class AssemblyInfo
* NOTE - also update the version in the Setup project
* by clicking on the Setup project node in VS and update its properties
*/
public const string Version = "3.26";
public const string Version = "3.27";

public const string Product = "OneMore";

Expand Down
18 changes: 18 additions & 0 deletions OneMore/Properties/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions OneMore/Properties/Resources.ar-SA.resx
Original file line number Diff line number Diff line change
Expand Up @@ -3139,4 +3139,12 @@ ISO-code then comma then language name</comment>
<value>... حفظ مخطط تفصيلي مطوي</value>
<comment>context menu option</comment>
</data>
<data name="PasteCellsCommand_NoContent" xml:space="preserve">
<value>لا يوجد محتوى للصقه ؛ </value>
<comment>message box</comment>
</data>
<data name="PasteCellsCommand_SelectCell" xml:space="preserve">
<value>حدد خلية جدول حيث يجب أن تبدأ الخلايا الملصقة في التراكب</value>
<comment>message box</comment>
</data>
</root>
8 changes: 8 additions & 0 deletions OneMore/Properties/Resources.de-DE.resx
Original file line number Diff line number Diff line change
Expand Up @@ -3125,4 +3125,12 @@ ISO-code then comma then language name</comment>
<value>... Zugeklappte Gliederung speichern</value>
<comment>context menu option</comment>
</data>
<data name="PasteCellsCommand_NoContent" xml:space="preserve">
<value>Kein Inhalt zum Einfügen; </value>
<comment>message box</comment>
</data>
<data name="PasteCellsCommand_SelectCell" xml:space="preserve">
<value>Wählen Sie eine Tabellenzelle aus, in der eingefügte Zellen überlagert werden sollen</value>
<comment>message box</comment>
</data>
</root>
8 changes: 8 additions & 0 deletions OneMore/Properties/Resources.es-ES.resx
Original file line number Diff line number Diff line change
Expand Up @@ -3139,4 +3139,12 @@ ISO-code then comma then language name</comment>
<value>... Guardar esquema contraído</value>
<comment>context menu option</comment>
</data>
<data name="PasteCellsCommand_NoContent" xml:space="preserve">
<value>Sin contenido para pegar; </value>
<comment>message box</comment>
</data>
<data name="PasteCellsCommand_SelectCell" xml:space="preserve">
<value>Seleccione una celda de la tabla donde las celdas pegadas deberían comenzar a superponerse</value>
<comment>message box</comment>
</data>
</root>
8 changes: 8 additions & 0 deletions OneMore/Properties/Resources.fr-FR.resx
Original file line number Diff line number Diff line change
Expand Up @@ -3134,4 +3134,12 @@ ISO-code then comma then language name</comment>
<value>... Enregistrer le contour réduit</value>
<comment>context menu option</comment>
</data>
<data name="PasteCellsCommand_NoContent" xml:space="preserve">
<value>Aucun contenu à coller ; </value>
<comment>message box</comment>
</data>
<data name="PasteCellsCommand_SelectCell" xml:space="preserve">
<value>Sélectionnez une cellule de tableau où les cellules collées doivent commencer à se superposer</value>
<comment>message box</comment>
</data>
</root>
8 changes: 8 additions & 0 deletions OneMore/Properties/Resources.nl-NL.resx
Original file line number Diff line number Diff line change
Expand Up @@ -3140,4 +3140,12 @@ ISO-code then comma then language name</comment>
<value>... Samengevouwen overzicht opslaan</value>
<comment>context menu option</comment>
</data>
<data name="PasteCellsCommand_NoContent" xml:space="preserve">
<value>Geen inhoud om te plakken; </value>
<comment>message box</comment>
</data>
<data name="PasteCellsCommand_SelectCell" xml:space="preserve">
<value>Selecteer een tabelcel waar geplakte cellen moeten beginnen over elkaar heen te leggen</value>
<comment>message box</comment>
</data>
</root>
8 changes: 8 additions & 0 deletions OneMore/Properties/Resources.pt-BR.resx
Original file line number Diff line number Diff line change
Expand Up @@ -3140,4 +3140,12 @@ ISO-code then comma then language name</comment>
<value>... Salvar contorno recolhido</value>
<comment>context menu option</comment>
</data>
<data name="PasteCellsCommand_NoContent" xml:space="preserve">
<value>Nenhum conteúdo para colar; </value>
<comment>message box</comment>
</data>
<data name="PasteCellsCommand_SelectCell" xml:space="preserve">
<value>Selecione uma célula da tabela onde as células coladas devem começar a se sobrepor</value>
<comment>message box</comment>
</data>
</root>
Loading

0 comments on commit 74e4211

Please sign in to comment.