diff --git a/common/plotters/PDF_plotter.cpp b/common/plotters/PDF_plotter.cpp index 463d76bb8f..46bab51963 100644 --- a/common/plotters/PDF_plotter.cpp +++ b/common/plotters/PDF_plotter.cpp @@ -739,6 +739,23 @@ void PDF_PLOTTER::ClosePage() 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; // If we have added any annotation links, create an array containing all the objects @@ -793,6 +810,7 @@ void PDF_PLOTTER::ClosePage() // Clean up m_hyperlinksInPage.clear(); + m_hyperlinkMenusInPage.clear(); } @@ -804,7 +822,9 @@ bool PDF_PLOTTER::StartPlot( const wxString& aPageNumber ) m_xrefTable.clear(); m_xrefTable.push_back( 0 ); m_hyperlinksInPage.clear(); + m_hyperlinkMenusInPage.clear(); m_hyperlinkHandles.clear(); + m_hyperlinkMenuHandles.clear(); /* The header (that's easy!). The second line is binary junk required to make the file binary from the beginning (the important thing is @@ -878,14 +898,12 @@ bool PDF_PLOTTER::EndPlot() fputs( ">>\n", m_outputFile ); 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 std::pair<BOX2D, wxString>& linkpair = handlePair.second; - const BOX2D& box = linkpair.first; - const wxString& url = linkpair.second; + const BOX2D& box = linkPair.first; + const wxString& url = linkPair.second; - startPdfObject( linkhandle ); + startPdfObject( linkHandle ); fprintf( m_outputFile, "<< /Type /Annot\n" @@ -904,7 +922,7 @@ bool PDF_PLOTTER::EndPlot() if( m_pageNumbers[ii] == pageNumber ) { fprintf( m_outputFile, - " /Dest [%d 0 R /FitB] >>\n" + " /Dest [%d 0 R /FitB]\n" ">>\n", m_pageHandles[ii] ); @@ -932,6 +950,60 @@ bool PDF_PLOTTER::EndPlot() 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! So we use just an array... The handle was allocated at the beginning, 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 ) ); } + +void PDF_PLOTTER::HyperlinkMenu( const BOX2I& aBox, const std::vector<wxString>& aDestURLs ) +{ + m_hyperlinkMenusInPage.push_back( std::make_pair( aBox, aDestURLs ) ); +} \ No newline at end of file diff --git a/eeschema/sch_field.cpp b/eeschema/sch_field.cpp index 39ba4879de..cab34c7a40 100644 --- a/eeschema/sch_field.cpp +++ b/eeschema/sch_field.cpp @@ -53,10 +53,9 @@ #include <trigo.h> #include <eeschema_id.h> #include <tool/tool_manager.h> -#include <tools/ee_actions.h> +#include <tools/sch_navigate_tool.h> #include <font/outline_font.h> - SCH_FIELD::SCH_FIELD( const VECTOR2I& aPos, int aFieldId, SCH_ITEM* aParent, const wxString& aName ) : SCH_ITEM( aParent, SCH_FIELD_T ), @@ -740,53 +739,36 @@ void SCH_FIELD::DoHypertextAction( EDA_DRAW_FRAME* aFrame ) const { constexpr int START_ID = 1; - static int back = -1; - wxMenu menu; - SCH_TEXT* label = dynamic_cast<SCH_TEXT*>( m_parent ); - - if( label && Schematic() ) + if( IsHypertext() ) { - 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() ); - if( !Schematic()->Settings().m_IntersheetRefsListOwnPage ) - { - int currentPage = Schematic()->CurrentSheet().GetVirtualPageNumber(); - alg::delete_matching( pageListCopy, currentPage ); + menu.AppendSeparator(); + menu.Append( 999 + START_ID, _( "Back to Previous Selected Sheet" ) ); - if( pageListCopy.empty() ) - return; - } + int sel = aFrame->GetPopupMenuSelectionFromUser( menu ) - START_ID; - 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(); - std::map<int, wxString> sheetPages = Schematic()->GetVirtualPageToSheetPagesMap(); - - for( int i = 0; i < (int) pageListCopy.size(); ++i ) - { - 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 ); + if( !href.IsEmpty() ) + { + SCH_NAVIGATE_TOOL* navTool = aFrame->GetToolManager()->GetTool<SCH_NAVIGATE_TOOL>(); + navTool->HypertextCommand( m_hyperlink ); } } } @@ -978,6 +960,20 @@ void SCH_FIELD::Plot( PLOTTER* aPlotter, bool aBackground ) const aPlotter->Text( textpos, color, GetShownText(), orient, GetTextSize(), hjustify, vjustify, 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 ); + } } diff --git a/eeschema/sch_label.cpp b/eeschema/sch_label.cpp index 35207084a8..431f08c875 100644 --- a/eeschema/sch_label.cpp +++ b/eeschema/sch_label.cpp @@ -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 { if( token->Contains( ':' ) ) diff --git a/eeschema/sch_label.h b/eeschema/sch_label.h index 010f26adda..1cc160c523 100644 --- a/eeschema/sch_label.h +++ b/eeschema/sch_label.h @@ -113,6 +113,12 @@ public: 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; wxString GetShownText( int aDepth = 0 ) const override; diff --git a/eeschema/tools/ee_actions.cpp b/eeschema/tools/ee_actions.cpp index c478c7eceb..7e72710649 100644 --- a/eeschema/tools/ee_actions.cpp +++ b/eeschema/tools/ee_actions.cpp @@ -779,10 +779,6 @@ TOOL_ACTION EE_ACTIONS::showHierarchy( "eeschema.EditorTool.showHierarchy", _( "Hierarchy Navigator" ), _( "Show or hide the schematic sheet hierarchy navigator" ), 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 // diff --git a/eeschema/tools/sch_navigate_tool.cpp b/eeschema/tools/sch_navigate_tool.cpp index 1a8a731d0d..c568b74bc7 100644 --- a/eeschema/tools/sch_navigate_tool.cpp +++ b/eeschema/tools/sch_navigate_tool.cpp @@ -28,6 +28,10 @@ #include <tools/sch_navigate_tool.h> #include "eda_doc.h" + +wxString SCH_NAVIGATE_TOOL::g_BackLink = wxT( "HYPERTEXT_BACK" ); + + void SCH_NAVIGATE_TOOL::ResetHistory() { 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 ) { 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() ) { @@ -245,7 +226,6 @@ void SCH_NAVIGATE_TOOL::setTransitions() Go( &SCH_NAVIGATE_TOOL::ChangeSheet, EE_ACTIONS::changeSheet.MakeEvent() ); Go( &SCH_NAVIGATE_TOOL::EnterSheet, EE_ACTIONS::enterSheet.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::Forward, EE_ACTIONS::navigateForward.MakeEvent() ); diff --git a/eeschema/tools/sch_navigate_tool.h b/eeschema/tools/sch_navigate_tool.h index 9991c01704..b87f5cd0d0 100644 --- a/eeschema/tools/sch_navigate_tool.h +++ b/eeschema/tools/sch_navigate_tool.h @@ -64,7 +64,6 @@ public: int Previous( const TOOL_EVENT& aEvent ); ///< Navigate to next sheet by numeric sheet number int Next( const TOOL_EVENT& aEvent ); - int HypertextCommand( const TOOL_EVENT& aEvent ); void HypertextCommand( const wxString& href ); @@ -74,6 +73,9 @@ public: bool CanGoPrevious(); bool CanGoNext(); +public: + static wxString g_BackLink; + private: ///< Set up handlers for various events. void setTransitions() override; diff --git a/include/plotters/plotter.h b/include/plotters/plotter.h index 0b708519bd..b9f9638db2 100644 --- a/include/plotters/plotter.h +++ b/include/plotters/plotter.h @@ -447,6 +447,17 @@ public: // 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). */ diff --git a/include/plotters/plotters_pslike.h b/include/plotters/plotters_pslike.h index 7cfbd475c5..1df8d839ef 100644 --- a/include/plotters/plotters_pslike.h +++ b/include/plotters/plotters_pslike.h @@ -352,13 +352,14 @@ public: KIFONT::FONT* aFont = nullptr, 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... */ - virtual void PlotImage( const wxImage& aImage, const VECTOR2I& aPos, - double aScaleFactor ) override; + void PlotImage( const wxImage& aImage, const VECTOR2I& aPos, double aScaleFactor ) override; protected: @@ -418,13 +419,15 @@ protected: std::vector<long> m_xrefTable; ///< The PDF xref offset table ///< 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 - 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 - 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; };