PDF hypertext menus for intersheet references.

This commit is contained in:
Jeff Young 2022-08-27 23:28:31 +01:00
parent 560dc7d2b6
commit c0d2052e4b
9 changed files with 194 additions and 90 deletions

View File

@ -739,6 +739,23 @@ void PDF_PLOTTER::ClosePage()
m_hyperlinkHandles.insert( { hyperlinkHandles.back(), { userSpaceBox, url } } ); m_hyperlinkHandles.insert( { hyperlinkHandles.back(), { userSpaceBox, url } } );
} }
for( const std::pair<BOX2I, std::vector<wxString>>& menuPair : m_hyperlinkMenusInPage )
{
const BOX2I& box = menuPair.first;
const std::vector<wxString>& urls = menuPair.second;
VECTOR2D bottomLeft = iuToPdfUserSpace( box.GetPosition() );
VECTOR2D topRight = iuToPdfUserSpace( box.GetEnd() );
BOX2D userSpaceBox;
userSpaceBox.SetOrigin( bottomLeft );
userSpaceBox.SetEnd( topRight );
hyperlinkHandles.push_back( allocPdfObject() );
m_hyperlinkMenuHandles.insert( { hyperlinkHandles.back(), { userSpaceBox, urls } } );
}
int hyperLinkArrayHandle = -1; int hyperLinkArrayHandle = -1;
// If we have added any annotation links, create an array containing all the objects // If we have added any annotation links, create an array containing all the objects
@ -793,6 +810,7 @@ void PDF_PLOTTER::ClosePage()
// Clean up // Clean up
m_hyperlinksInPage.clear(); m_hyperlinksInPage.clear();
m_hyperlinkMenusInPage.clear();
} }
@ -804,7 +822,9 @@ bool PDF_PLOTTER::StartPlot( const wxString& aPageNumber )
m_xrefTable.clear(); m_xrefTable.clear();
m_xrefTable.push_back( 0 ); m_xrefTable.push_back( 0 );
m_hyperlinksInPage.clear(); m_hyperlinksInPage.clear();
m_hyperlinkMenusInPage.clear();
m_hyperlinkHandles.clear(); m_hyperlinkHandles.clear();
m_hyperlinkMenuHandles.clear();
/* The header (that's easy!). The second line is binary junk required /* The header (that's easy!). The second line is binary junk required
to make the file binary from the beginning (the important thing is to make the file binary from the beginning (the important thing is
@ -878,14 +898,12 @@ bool PDF_PLOTTER::EndPlot()
fputs( ">>\n", m_outputFile ); fputs( ">>\n", m_outputFile );
closePdfObject(); closePdfObject();
for( const std::pair<const int, std::pair<BOX2D, wxString>>& handlePair : m_hyperlinkHandles ) for( const auto& [ linkHandle, linkPair ] : m_hyperlinkHandles )
{ {
const int& linkhandle = handlePair.first; const BOX2D& box = linkPair.first;
const std::pair<BOX2D, wxString>& linkpair = handlePair.second; const wxString& url = linkPair.second;
const BOX2D& box = linkpair.first;
const wxString& url = linkpair.second;
startPdfObject( linkhandle ); startPdfObject( linkHandle );
fprintf( m_outputFile, fprintf( m_outputFile,
"<< /Type /Annot\n" "<< /Type /Annot\n"
@ -904,7 +922,7 @@ bool PDF_PLOTTER::EndPlot()
if( m_pageNumbers[ii] == pageNumber ) if( m_pageNumbers[ii] == pageNumber )
{ {
fprintf( m_outputFile, fprintf( m_outputFile,
" /Dest [%d 0 R /FitB] >>\n" " /Dest [%d 0 R /FitB]\n"
">>\n", ">>\n",
m_pageHandles[ii] ); m_pageHandles[ii] );
@ -932,6 +950,60 @@ bool PDF_PLOTTER::EndPlot()
closePdfObject(); closePdfObject();
} }
for( const auto& [ menuHandle, menuPair ] : m_hyperlinkMenuHandles )
{
const BOX2D& box = menuPair.first;
const std::vector<wxString>& urls = menuPair.second;
// We currently only support menu links for internal pages. This vector holds the
// page names and numbers.
std::vector<std::pair<wxString, int>> pages;
for( const wxString& url : urls )
{
wxString pageNumber;
if( EDA_TEXT::IsGotoPageHref( url, &pageNumber ) )
{
for( size_t ii = 0; ii < m_pageNumbers.size(); ++ii )
{
if( m_pageNumbers[ii] == pageNumber )
pages.push_back( { pageNumber, ii } );
}
}
}
wxString js = wxT( "var aParams = [ " );
for( const std::pair<wxString, int>& page : pages )
{
js += wxString::Format( wxT( "{ cName: '%s', cReturn: '%d' }, " ),
page.first,
page.second );
}
js += wxT( "]; " );
js += wxT( "var cChoice = app.popUpMenuEx.apply\\( app, aParams \\); " );
js += wxT( "if\\( cChoice != null \\) this.pageNum = cChoice; " );
startPdfObject( menuHandle );
fprintf( m_outputFile,
"<< /Type /Annot\n"
" /Subtype /Link\n"
" /Rect [%g %g %g %g]\n"
" /Border [16 16 0]\n",
box.GetLeft(), box.GetBottom(), box.GetRight(), box.GetTop() );
fprintf( m_outputFile,
" /A << /Type /Action /S /JavaScript /JS (%s) >>\n"
">>\n",
js.ToStdString().c_str() );
closePdfObject();
}
/* The page tree: it's a B-tree but luckily we only have few pages! /* The page tree: it's a B-tree but luckily we only have few pages!
So we use just an array... The handle was allocated at the beginning, So we use just an array... The handle was allocated at the beginning,
now we instantiate the corresponding object */ now we instantiate the corresponding object */
@ -1079,3 +1151,8 @@ void PDF_PLOTTER::HyperlinkBox( const BOX2I& aBox, const wxString& aDestinationU
m_hyperlinksInPage.push_back( std::make_pair( aBox, aDestinationURL ) ); m_hyperlinksInPage.push_back( std::make_pair( aBox, aDestinationURL ) );
} }
void PDF_PLOTTER::HyperlinkMenu( const BOX2I& aBox, const std::vector<wxString>& aDestURLs )
{
m_hyperlinkMenusInPage.push_back( std::make_pair( aBox, aDestURLs ) );
}

View File

@ -53,10 +53,9 @@
#include <trigo.h> #include <trigo.h>
#include <eeschema_id.h> #include <eeschema_id.h>
#include <tool/tool_manager.h> #include <tool/tool_manager.h>
#include <tools/ee_actions.h> #include <tools/sch_navigate_tool.h>
#include <font/outline_font.h> #include <font/outline_font.h>
SCH_FIELD::SCH_FIELD( const VECTOR2I& aPos, int aFieldId, SCH_ITEM* aParent, SCH_FIELD::SCH_FIELD( const VECTOR2I& aPos, int aFieldId, SCH_ITEM* aParent,
const wxString& aName ) : const wxString& aName ) :
SCH_ITEM( aParent, SCH_FIELD_T ), SCH_ITEM( aParent, SCH_FIELD_T ),
@ -740,53 +739,36 @@ void SCH_FIELD::DoHypertextAction( EDA_DRAW_FRAME* aFrame ) const
{ {
constexpr int START_ID = 1; constexpr int START_ID = 1;
static int back = -1; if( IsHypertext() )
wxMenu menu;
SCH_TEXT* label = dynamic_cast<SCH_TEXT*>( m_parent );
if( label && Schematic() )
{ {
auto it = Schematic()->GetPageRefsMap().find( label->GetText() ); SCH_LABEL_BASE* label = static_cast<SCH_LABEL_BASE*>( m_parent );
std::vector<std::pair<wxString, wxString>> pages;
wxMenu menu;
wxString href;
if( it != Schematic()->GetPageRefsMap().end() ) label->GetIntersheetRefs( &pages );
for( int i = 0; i < (int) pages.size(); ++i )
{ {
std::vector<int> pageListCopy; menu.Append( i + START_ID, wxString::Format( _( "Go to Page %s (%s)" ),
pages[i].first,
pages[i].second ) );
}
pageListCopy.insert( pageListCopy.end(), it->second.begin(), it->second.end() ); menu.AppendSeparator();
if( !Schematic()->Settings().m_IntersheetRefsListOwnPage ) menu.Append( 999 + START_ID, _( "Back to Previous Selected Sheet" ) );
{
int currentPage = Schematic()->CurrentSheet().GetVirtualPageNumber();
alg::delete_matching( pageListCopy, currentPage );
if( pageListCopy.empty() ) int sel = aFrame->GetPopupMenuSelectionFromUser( menu ) - START_ID;
return;
}
std::sort( pageListCopy.begin(), pageListCopy.end() ); if( sel >= 0 && sel < (int) pages.size() )
href = wxT( "#" ) + pages[ sel ].first;
else if( sel == 999 )
href = SCH_NAVIGATE_TOOL::g_BackLink;
std::map<int, wxString> sheetNames = Schematic()->GetVirtualPageToSheetNamesMap(); if( !href.IsEmpty() )
std::map<int, wxString> sheetPages = Schematic()->GetVirtualPageToSheetPagesMap(); {
SCH_NAVIGATE_TOOL* navTool = aFrame->GetToolManager()->GetTool<SCH_NAVIGATE_TOOL>();
for( int i = 0; i < (int) pageListCopy.size(); ++i ) navTool->HypertextCommand( m_hyperlink );
{
menu.Append( i + START_ID, wxString::Format( _( "Go to Page %s (%s)" ),
sheetPages[ pageListCopy[i] ],
sheetNames[ pageListCopy[i] ] ) );
}
menu.AppendSeparator();
menu.Append( 999 + START_ID, _( "Back to Previous Selected Sheet" ) );
int sel = aFrame->GetPopupMenuSelectionFromUser( menu ) - START_ID;
void* param = nullptr;
if( sel >= 0 && sel < (int) pageListCopy.size() )
param = (void*) &pageListCopy[ sel ];
else if( sel == 999 )
param = (void*) &back;
if( param )
aFrame->GetToolManager()->RunAction( EE_ACTIONS::hypertextCommand, true, param );
} }
} }
} }
@ -978,6 +960,20 @@ void SCH_FIELD::Plot( PLOTTER* aPlotter, bool aBackground ) const
aPlotter->Text( textpos, color, GetShownText(), orient, GetTextSize(), hjustify, vjustify, aPlotter->Text( textpos, color, GetShownText(), orient, GetTextSize(), hjustify, vjustify,
penWidth, IsItalic(), IsBold(), false, GetDrawFont() ); penWidth, IsItalic(), IsBold(), false, GetDrawFont() );
if( IsHypertext() )
{
SCH_LABEL_BASE* label = static_cast<SCH_LABEL_BASE*>( m_parent );
std::vector<std::pair<wxString, wxString>> pages;
std::vector<wxString> pageHrefs;
label->GetIntersheetRefs( &pages );
for( const std::pair<wxString, wxString>& page : pages )
pageHrefs.push_back( wxT( "#" ) + page.first );
aPlotter->HyperlinkMenu( GetBoundingBox(), pageHrefs );
}
} }

View File

@ -428,6 +428,39 @@ void SCH_LABEL_BASE::AutoplaceFields( SCH_SCREEN* aScreen, bool aManual )
} }
void SCH_LABEL_BASE::GetIntersheetRefs( std::vector<std::pair<wxString, wxString>>* pages )
{
if( Schematic() )
{
auto it = Schematic()->GetPageRefsMap().find( GetText() );
if( it != Schematic()->GetPageRefsMap().end() )
{
std::vector<int> pageListCopy;
pageListCopy.insert( pageListCopy.end(), it->second.begin(), it->second.end() );
if( !Schematic()->Settings().m_IntersheetRefsListOwnPage )
{
int currentPage = Schematic()->CurrentSheet().GetVirtualPageNumber();
alg::delete_matching( pageListCopy, currentPage );
if( pageListCopy.empty() )
return;
}
std::sort( pageListCopy.begin(), pageListCopy.end() );
std::map<int, wxString> sheetPages = Schematic()->GetVirtualPageToSheetPagesMap();
std::map<int, wxString> sheetNames = Schematic()->GetVirtualPageToSheetNamesMap();
for( int pageNum : pageListCopy )
pages->push_back( { sheetPages[ pageNum ], sheetNames[ pageNum ] } );
}
}
}
bool SCH_LABEL_BASE::ResolveTextVar( wxString* token, int aDepth ) const bool SCH_LABEL_BASE::ResolveTextVar( wxString* token, int aDepth ) const
{ {
if( token->Contains( ':' ) ) if( token->Contains( ':' ) )

View File

@ -113,6 +113,12 @@ public:
void AutoplaceFields( SCH_SCREEN* aScreen, bool aManual ) override; void AutoplaceFields( SCH_SCREEN* aScreen, bool aManual ) override;
/**
* Builds an array of { pageNumber, pageName } pairs.
* @param pages [out] Array of { pageNumber, pageName } pairs.
*/
void GetIntersheetRefs( std::vector<std::pair<wxString, wxString>>* pages );
virtual bool ResolveTextVar( wxString* token, int aDepth ) const; virtual bool ResolveTextVar( wxString* token, int aDepth ) const;
wxString GetShownText( int aDepth = 0 ) const override; wxString GetShownText( int aDepth = 0 ) const override;

View File

@ -779,10 +779,6 @@ TOOL_ACTION EE_ACTIONS::showHierarchy( "eeschema.EditorTool.showHierarchy",
_( "Hierarchy Navigator" ), _( "Show or hide the schematic sheet hierarchy navigator" ), _( "Hierarchy Navigator" ), _( "Show or hide the schematic sheet hierarchy navigator" ),
BITMAPS::hierarchy_nav ); BITMAPS::hierarchy_nav );
TOOL_ACTION EE_ACTIONS::hypertextCommand( "eeschema.NavigateTool.hypertextCommand",
AS_GLOBAL, 0, "",
_( "Navigate to page" ), _( "Navigate to page" ) );
// SCH_LINE_WIRE_BUS_TOOL // SCH_LINE_WIRE_BUS_TOOL
// //

View File

@ -28,6 +28,10 @@
#include <tools/sch_navigate_tool.h> #include <tools/sch_navigate_tool.h>
#include "eda_doc.h" #include "eda_doc.h"
wxString SCH_NAVIGATE_TOOL::g_BackLink = wxT( "HYPERTEXT_BACK" );
void SCH_NAVIGATE_TOOL::ResetHistory() void SCH_NAVIGATE_TOOL::ResetHistory()
{ {
m_navHistory.clear(); m_navHistory.clear();
@ -53,39 +57,16 @@ void SCH_NAVIGATE_TOOL::CleanHistory()
} }
int SCH_NAVIGATE_TOOL::HypertextCommand( const TOOL_EVENT& aEvent )
{
int* page = aEvent.Parameter<int*>();
wxCHECK( page, 0 );
auto goToPage =
[&]( int* aPage )
{
for( const SCH_SHEET_PATH& sheet : m_frame->Schematic().GetSheets() )
{
if( sheet.GetVirtualPageNumber() == *aPage )
{
changeSheet( sheet );
return;
}
}
};
if( *page == -1 )
Back( aEvent );
else
goToPage( page );
return 0;
}
void SCH_NAVIGATE_TOOL::HypertextCommand( const wxString& href ) void SCH_NAVIGATE_TOOL::HypertextCommand( const wxString& href )
{ {
wxString destPage; wxString destPage;
if( EDA_TEXT::IsGotoPageHref( href, &destPage ) && !destPage.IsEmpty() ) if( href == SCH_NAVIGATE_TOOL::g_BackLink )
{
TOOL_EVENT dummy;
Back( dummy );
}
else if( EDA_TEXT::IsGotoPageHref( href, &destPage ) && !destPage.IsEmpty() )
{ {
for( const SCH_SHEET_PATH& sheet : m_frame->Schematic().GetSheets() ) for( const SCH_SHEET_PATH& sheet : m_frame->Schematic().GetSheets() )
{ {
@ -245,7 +226,6 @@ void SCH_NAVIGATE_TOOL::setTransitions()
Go( &SCH_NAVIGATE_TOOL::ChangeSheet, EE_ACTIONS::changeSheet.MakeEvent() ); Go( &SCH_NAVIGATE_TOOL::ChangeSheet, EE_ACTIONS::changeSheet.MakeEvent() );
Go( &SCH_NAVIGATE_TOOL::EnterSheet, EE_ACTIONS::enterSheet.MakeEvent() ); Go( &SCH_NAVIGATE_TOOL::EnterSheet, EE_ACTIONS::enterSheet.MakeEvent() );
Go( &SCH_NAVIGATE_TOOL::LeaveSheet, EE_ACTIONS::leaveSheet.MakeEvent() ); Go( &SCH_NAVIGATE_TOOL::LeaveSheet, EE_ACTIONS::leaveSheet.MakeEvent() );
Go( &SCH_NAVIGATE_TOOL::HypertextCommand, EE_ACTIONS::hypertextCommand.MakeEvent() );
Go( &SCH_NAVIGATE_TOOL::Up, EE_ACTIONS::navigateUp.MakeEvent() ); Go( &SCH_NAVIGATE_TOOL::Up, EE_ACTIONS::navigateUp.MakeEvent() );
Go( &SCH_NAVIGATE_TOOL::Forward, EE_ACTIONS::navigateForward.MakeEvent() ); Go( &SCH_NAVIGATE_TOOL::Forward, EE_ACTIONS::navigateForward.MakeEvent() );

View File

@ -64,7 +64,6 @@ public:
int Previous( const TOOL_EVENT& aEvent ); int Previous( const TOOL_EVENT& aEvent );
///< Navigate to next sheet by numeric sheet number ///< Navigate to next sheet by numeric sheet number
int Next( const TOOL_EVENT& aEvent ); int Next( const TOOL_EVENT& aEvent );
int HypertextCommand( const TOOL_EVENT& aEvent );
void HypertextCommand( const wxString& href ); void HypertextCommand( const wxString& href );
@ -74,6 +73,9 @@ public:
bool CanGoPrevious(); bool CanGoPrevious();
bool CanGoNext(); bool CanGoNext();
public:
static wxString g_BackLink;
private: private:
///< Set up handlers for various events. ///< Set up handlers for various events.
void setTransitions() override; void setTransitions() override;

View File

@ -447,6 +447,17 @@ public:
// NOP for most plotters. // NOP for most plotters.
} }
/**
* Create a clickable hyperlink menu with a rectangular click area
*
* @aBox is the rectangular click target
* @aDestURLs is the list of target URLs for the menu
*/
virtual void HyperlinkMenu( const BOX2I& aBox, const std::vector<wxString>& aDestURLs )
{
// NOP for most plotters.
}
/** /**
* Draw a marker (used for the drill map). * Draw a marker (used for the drill map).
*/ */

View File

@ -352,13 +352,14 @@ public:
KIFONT::FONT* aFont = nullptr, KIFONT::FONT* aFont = nullptr,
void* aData = nullptr ) override; void* aData = nullptr ) override;
virtual void HyperlinkBox( const BOX2I& aBox, const wxString& aDestinationURL ) override; void HyperlinkBox( const BOX2I& aBox, const wxString& aDestinationURL ) override;
void HyperlinkMenu( const BOX2I& aBox, const std::vector<wxString>& aDestURLs ) override;
/** /**
* PDF images are handles as inline, not XObject streams... * PDF images are handles as inline, not XObject streams...
*/ */
virtual void PlotImage( const wxImage& aImage, const VECTOR2I& aPos, void PlotImage( const wxImage& aImage, const VECTOR2I& aPos, double aScaleFactor ) override;
double aScaleFactor ) override;
protected: protected:
@ -418,13 +419,15 @@ protected:
std::vector<long> m_xrefTable; ///< The PDF xref offset table std::vector<long> m_xrefTable; ///< The PDF xref offset table
///< List of user-space page numbers for resolving internal hyperlinks ///< List of user-space page numbers for resolving internal hyperlinks
std::vector<wxString> m_pageNumbers; std::vector<wxString> m_pageNumbers;
///< List of loaded hyperlinks in current page ///< List of loaded hyperlinks in current page
std::vector<std::pair<BOX2I, wxString>> m_hyperlinksInPage; std::vector<std::pair<BOX2I, wxString>> m_hyperlinksInPage;
std::vector<std::pair<BOX2I, std::vector<wxString>>> m_hyperlinkMenusInPage;
///< Handles for all the hyperlink objects that will be deferred ///< Handles for all the hyperlink objects that will be deferred
std::map<int, std::pair<BOX2D, wxString>> m_hyperlinkHandles; std::map<int, std::pair<BOX2D, wxString>> m_hyperlinkHandles;
std::map<int, std::pair<BOX2D, std::vector<wxString>>> m_hyperlinkMenuHandles;
}; };