Jump between folio report ends with a pointing hand and a highlight

A linked folio report now shows a pointing hand under the mouse, on its
arrow as well as on the text that shows its label (which already turned
blue), and a double click on the arrow goes to the other end, as a
double click on the text did. Before, a double click on the arrow opened
the report's properties, which stay reachable from the context menu and
the properties panel.

The end jumped to is highlighted for two seconds, with the same blue as
the hover highlight but independent of it: the hover highlight is
cleared as soon as the mouse leaves the end jumped from, so on the same
folio the user saw nothing mark where the jump landed.

The text of a linked slave that shows its master's label gets the same
blue and hand, since a double click on it already jumped to the master.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
DaTweety
2026-10-18 22:22:59 +02:00
parent adf3ea301c
commit 5641b031f9
9 changed files with 631 additions and 26 deletions
@@ -758,14 +758,11 @@ void DynamicElementTextItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
if(isSelected())
return;
if ((parentElement()->linkType() & Element::AllReport) && m_other_report)
if (linkedElementShown())
{
if( (m_text_from == ElementInfo && m_info_name == "label") ||
(m_text_from == CompositeText && m_composite_text.contains("%{label}")) )
{
m_user_color = color();
setDefaultTextColor(Qt::blue);
}
m_user_color = color();
setDefaultTextColor(Qt::blue);
setCursor(Qt::PointingHandCursor);
}
}
@@ -781,6 +778,7 @@ void DynamicElementTextItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
{
setDefaultTextColor(m_user_color);
m_user_color = QColor(); //m_user_color is now invalid
unsetCursor();
}
}
@@ -1601,6 +1599,27 @@ QString DynamicElementTextItem::reportReplacedCompositeText() const
return string;
}
/**
@brief DynamicElementTextItem::linkedElementShown
@return the element this text leads to: the master of a linked slave
or the other end of a linked folio report, when the text shows its
label; nullptr otherwise. Such a text turns blue under the mouse, shows
a pointing hand and jumps there on a double click.
*/
Element *DynamicElementTextItem::linkedElementShown() const
{
if(!parentElement())
return nullptr;
if (!((m_text_from == ElementInfo && m_info_name == "label") ||
(m_text_from == CompositeText && m_composite_text.contains("%{label}"))))
return nullptr;
if(parentElement()->linkType() == Element::Slave && m_master_element)
return m_master_element.data();
if((parentElement()->linkType() & Element::AllReport) && m_other_report)
return m_other_report.data();
return nullptr;
}
/**
@brief DynamicElementTextItem::zoomToLinkedElement
If the parent element is a folio report or a slave element,
@@ -1608,24 +1627,7 @@ QString DynamicElementTextItem::reportReplacedCompositeText() const
*/
void DynamicElementTextItem::zoomToLinkedElement()
{
if(!parentElement())
return;
Element *zoomed_element = nullptr;
if(parentElement()->linkType() == Element::Slave && m_master_element)
{
if ((m_text_from == ElementInfo && m_info_name == "label") ||
(m_text_from == CompositeText && m_composite_text.contains("%{label}")))
zoomed_element = m_master_element.data();
}
if((parentElement()->linkType() & Element::AllReport) && m_other_report)
{
if((m_text_from == ElementInfo && m_info_name == "label") ||
(m_text_from == CompositeText && m_composite_text.contains("%{label}")))
zoomed_element = m_other_report.data();
}
Element *zoomed_element = linkedElementShown();
if(zoomed_element)
{
//Unselect and ungrab mouse to prevent unwanted
@@ -105,6 +105,7 @@ class DynamicElementTextItem : public DiagramTextItem
static QString xmlTagName() {return QString("dynamic_elmt_text");}
void setInfoName(const QString &info_name);
QString infoName() const;
Element *linkedElementShown() const;
void setCompositeText(const QString &text);
QString compositeText() const;
void setFrame(const bool frame);
+67 -1
View File
@@ -49,6 +49,7 @@
#include <QApplication>
#include <QCollator>
#include <QScreen>
#include <QTimer>
static const QString plcTerminalKeys[] = {
QETInformation::ELMT_PLC_T1,
@@ -253,6 +254,36 @@ void Element::setHighlighted(bool hl)
update();
}
/**
@brief Element::showAsJumpTarget
Highlight this element for a moment, so the user sees where a jump
from a folio report (or a cross reference) landed. Independent of
setHighlighted(), which follows the mouse over a linked element and
would be cleared as soon as the mouse leaves the element jumped from.
*/
void Element::showAsJumpTarget()
{
++m_jump_highlight;
update();
QTimer::singleShot(2000, this, [this]() {
if (m_jump_highlight > 0)
--m_jump_highlight;
update();
});
}
/**
@brief Element::jumpTarget
@return the element a double click on this one jumps to: the other end
of a linked folio report, or nullptr.
*/
Element *Element::jumpTarget() const
{
if ((linkType() & AllReport) && !connected_elements.isEmpty())
return connected_elements.first();
return nullptr;
}
/**
@brief Element::displayHelpLine
Display the help line of each terminal if b is true
@@ -474,7 +505,7 @@ void Element::paint(
const QStyleOptionGraphicsItem *options,
QWidget *)
{
if (m_must_highlight) {
if (m_must_highlight || m_jump_highlight > 0) {
drawHighlight(painter, options);
}
@@ -2354,6 +2385,29 @@ void Element::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
}
}
/**
@brief Element::mouseDoubleClickEvent
A double click on a linked folio report goes to its other end, as a
double click on its text does; on any other element it opens the
properties. A report's properties stay reachable from the context menu
and the properties panel.
@param event
*/
void Element::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
{
if (Element *target = jumpTarget())
{
event->accept();
//Unselect and ungrab mouse to prevent unwanted
//move when the target is in the same scene as this.
setSelected(false);
ungrabMouse();
QetGraphicsItem::showItem(target);
return;
}
QetGraphicsItem::mouseDoubleClickEvent(event);
}
/**
When mouse over element
change m_mouse_over to true (used in paint() function )
@@ -2367,6 +2421,13 @@ void Element::hoverEnterEvent(QGraphicsSceneHoverEvent *e)
foreach (Element *elmt, linkedElements())
elmt -> setHighlighted(true);
//A linked folio report jumps to its other end on a double
//click: the pointing hand says so
if (jumpTarget()) {
setCursor(Qt::PointingHandCursor);
m_jump_cursor = true;
}
m_mouse_over = true;
setToolTip( name() );
update();
@@ -2385,6 +2446,11 @@ void Element::hoverLeaveEvent(QGraphicsSceneHoverEvent *e)
foreach (Element *elmt, linkedElements())
elmt -> setHighlighted(false);
if (m_jump_cursor) {
unsetCursor();
m_jump_cursor = false;
}
m_mouse_over = false;
update();
}
+6
View File
@@ -176,6 +176,9 @@ class Element : public QetGraphicsItem
};
ReloadPictureResult reloadPicture();
virtual void setHighlighted(bool);
void showAsJumpTarget();
bool isShownAsJumpTarget() const { return m_jump_highlight > 0; }
Element *jumpTarget() const;
void displayHelpLine(bool b = true);
QSize size() const;
QPixmap pixmap();
@@ -316,6 +319,7 @@ class Element : public QetGraphicsItem
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
void mouseReleaseEvent(
QGraphicsSceneMouseEvent *event) override;
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
void hoverEnterEvent(QGraphicsSceneHoverEvent *) override;
void hoverLeaveEvent(QGraphicsSceneHoverEvent *) override;
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;
@@ -359,6 +363,8 @@ class Element : public QetGraphicsItem
void storeInformation();
projectDataBase *placedStore() const;
bool m_must_highlight = false;
int m_jump_highlight = 0; ///< pending jump-target highlights, see showAsJumpTarget()
bool m_jump_cursor = false;
QSize dimensions;
QPoint hotspot_coord;
bool m_mouse_over = false;
@@ -18,6 +18,7 @@
#include "qetgraphicsitem.h"
#include "../diagram.h"
#include "element.h"
/**
@brief QetGraphicsItem::QetGraphicsItem
@@ -38,6 +39,10 @@ void QetGraphicsItem::showItem(QetGraphicsItem *item)
fit.adjust(-200, -200, 200, 200);
view->fitInView(fit, Qt::KeepAspectRatioByExpanding);
}
//Mark where the jump landed
if (Element *element = dynamic_cast<Element *>(item))
element->showAsJumpTarget();
}
}
+10
View File
@@ -112,6 +112,16 @@ if(Python3_Interpreter_FOUND AND CMAKE_GENERATOR STREQUAL "Ninja")
--fixture ${CMAKE_CURRENT_SOURCE_DIR}/fixtures/unlinked_contact_label.qet)
set_tests_properties(tst_crosshairintegration PROPERTIES TIMEOUT 300 RUN_SERIAL TRUE
ENVIRONMENT "QT_QPA_PLATFORM=offscreen")
# A linked folio report shows a pointing hand and jumps to its other
# end on a double click, on the arrow and on its label text; the end
# jumped to is highlighted for a moment.
add_test(NAME tst_reportjumpintegration
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/run_report_jump_probe.py
--build ${CMAKE_BINARY_DIR} --binary $<TARGET_FILE:qelectrotech>
--source ${CMAKE_CURRENT_SOURCE_DIR}/report_jump_probe.cpp
--fixture ${CMAKE_CURRENT_SOURCE_DIR}/fixtures/linked_reports.qet)
set_tests_properties(tst_reportjumpintegration PROPERTIES TIMEOUT 300 RUN_SERIAL TRUE
ENVIRONMENT "QT_QPA_PLATFORM=offscreen")
endif()
if(Python3_Interpreter_FOUND AND CMAKE_GENERATOR STREQUAL "Ninja")
add_test(NAME tst_darkimageintegration
+312
View File
@@ -0,0 +1,312 @@
<project title="Linked reports" uuid="{3782ad69-0e05-52a7-950b-4c34633b4add}" version="0.200.1">
<properties/>
<usage enabled="true" time_spent="0"/>
<symbol_texts upright="true"/>
<newdiagrams>
<border cols="17" colsize="60" displaycols="true" displayrows="true" rows="8" rowsize="80"/>
<inset author="" auto_page_num="" date="null" displayAt="bottom" filename="" folio="" indexrev="" locmach="" plant="" title="" version=""/>
<conductors bicolor="false" bus="" cable="" color2="#000000" condsize="1" conductor_color="" conductor_section="" dash-size="1" displaytext="1" formula="" function="" horizontal-alignment="AlignBottom" horizrotatetext="0" num="_" numsize="7" onetextperfolio="0" tension_protocol="" text_color="#000000" type="multi" vertical-alignment="AlignRight" vertirotatetext="270"/>
<report label="%f-%l%c"/>
<xrefs>
<xref delayprefix="" displayhas="cross" master_label="%f-%l%c" offset="0" powerprefix="" showallconfiguredslaves="false" showpowerctc="true" showterminalname="true" slave_label="(%f-%l%c)" slave_offset="0" snapto="label" stackoverlapping="true" switchprefix="" type="cable" xrefpos="AlignBottom"/>
<xref delayprefix="" displayhas="cross" master_label="%f-%l%c" offset="0" powerprefix="" showallconfiguredslaves="false" showpowerctc="true" showterminalname="true" slave_label="(%f-%l%c)" slave_offset="0" snapto="label" stackoverlapping="true" switchprefix="" type="coil" xrefpos="AlignBottom"/>
<xref delayprefix="" displayhas="cross" master_label="%f-%l%c" offset="0" powerprefix="" showallconfiguredslaves="false" showpowerctc="true" showterminalname="true" slave_label="(%f-%l%c)" slave_offset="0" snapto="label" stackoverlapping="true" switchprefix="" type="commutator" xrefpos="AlignBottom"/>
<xref delayprefix="" displayhas="cross" master_label="%f-%l%c" offset="0" powerprefix="" showallconfiguredslaves="false" showpowerctc="true" showterminalname="true" slave_label="(%f-%l%c)" slave_offset="0" snapto="label" stackoverlapping="true" switchprefix="" type="plc" xrefpos="AlignBottom"/>
<xref delayprefix="" displayhas="cross" master_label="%f-%l%c" offset="0" powerprefix="" showallconfiguredslaves="false" showpowerctc="true" showterminalname="true" slave_label="(%f-%l%c)" slave_offset="0" snapto="label" stackoverlapping="true" switchprefix="" type="protection" xrefpos="AlignBottom"/>
</xrefs>
<conductors_autonums auto_break_conductors="false" current_autonum="" freeze_new_conductors="false"/>
<folio_autonums/>
<element_autonums current_autonum="" freeze_new_elements="false"/>
<guides/>
</newdiagrams>
<diagram author="" auto_page_num="" cols="17" colsize="60" date="null" displayAt="bottom" displaycols="true" displayrows="true" filename="" folio="" freezeNewConductor="false" freezeNewElement="false" height="660" indexrev="" locmach="" order="1" plant="" rows="8" rowsize="80" title="One" uuid="{5c67e69f-cfc9-4757-8318-7d6ec611f7bc}" version="0.200.1-dev">
<defaultconductor bicolor="false" bus="" cable="" color2="#000000" condsize="1" conductor_color="" conductor_section="" dash-size="1" displaytext="1" formula="" function="" horizontal-alignment="AlignBottom" horizrotatetext="0" num="_" numsize="7" onetextperfolio="0" tension_protocol="" text_color="#000000" type="multi" vertical-alignment="AlignRight" vertirotatetext="270"/>
<elements>
<element freezeLabel="false" is_movable="1" orientation="0" prefix="" type="embed://import/10_electric/10_allpole/100_folio_referencing/02going_arrow.elmt" uuid="{139e1748-290b-4bfa-aa31-2d092f4aeb14}" x="300" y="200" z="10">
<terminals>
<terminal id="0" orientation="3" x="-9" y="0"/>
</terminals>
<inputs/>
<links_uuids>
<link_uuid uuid="{f85d9f0e-0068-460e-a241-8da7ef1ae4e9}"/>
</links_uuids>
<dynamic_texts>
<dynamic_elmt_text Halignment="AlignLeft" Valignment="AlignTop" font="Liberation Sans,9,-1,5,50,0,0,0,0,0,Regular" frame="false" keep_visual_rotation="true" rotation="0" rotation_point_center="false" text_from="ElementInfo" text_width="-1" uuid="{f76e0745-462f-47a6-b900-00790ded6e24}" x="6" y="-11.5">
<text>2-C4</text>
<info_name>label</info_name>
</dynamic_elmt_text>
</dynamic_texts>
<texts_groups/>
</element>
<element freezeLabel="false" is_movable="1" orientation="0" prefix="" type="embed://import/10_electric/10_allpole/100_folio_referencing/02going_arrow.elmt" uuid="{2b833a53-8066-433a-b1a1-93997bfdfaf5}" x="500" y="200" z="10">
<terminals>
<terminal id="1" orientation="3" x="-9" y="0"/>
</terminals>
<inputs/>
<dynamic_texts>
<dynamic_elmt_text Halignment="AlignLeft" Valignment="AlignTop" font="Liberation Sans,9,-1,5,50,0,0,0,0,0,Regular" frame="false" keep_visual_rotation="true" rotation="0" rotation_point_center="false" text_from="ElementInfo" text_width="-1" uuid="{d370fe1a-1e2f-4d8b-9449-87e23de3ee89}" x="6" y="-11.5">
<text></text>
<info_name>label</info_name>
</dynamic_elmt_text>
</dynamic_texts>
<texts_groups/>
</element>
</elements>
</diagram>
<diagram author="" auto_page_num="" cols="17" colsize="60" date="null" displayAt="bottom" displaycols="true" displayrows="true" filename="" folio="" freezeNewConductor="false" freezeNewElement="false" height="660" indexrev="" locmach="" order="2" plant="" rows="8" rowsize="80" title="Two" uuid="{2971356b-b745-43ed-b954-89d3b659ff08}" version="0.200.1-dev">
<defaultconductor bicolor="false" bus="" cable="" color2="#000000" condsize="1" conductor_color="" conductor_section="" dash-size="1" displaytext="1" formula="" function="" horizontal-alignment="AlignBottom" horizrotatetext="0" num="_" numsize="7" onetextperfolio="0" tension_protocol="" text_color="#000000" type="multi" vertical-alignment="AlignRight" vertirotatetext="270"/>
<elements>
<element freezeLabel="false" is_movable="1" orientation="0" prefix="" type="embed://import/10_electric/10_allpole/100_folio_referencing/01coming_arrow.elmt" uuid="{f85d9f0e-0068-460e-a241-8da7ef1ae4e9}" x="300" y="200" z="10">
<terminals>
<terminal id="0" orientation="1" x="9" y="0"/>
</terminals>
<inputs/>
<links_uuids>
<link_uuid uuid="{139e1748-290b-4bfa-aa31-2d092f4aeb14}"/>
</links_uuids>
<dynamic_texts>
<dynamic_elmt_text Halignment="AlignRight" Valignment="AlignTop" font="Liberation Sans,9,-1,5,50,0,0,0,0,0,Regular" frame="false" keep_visual_rotation="true" rotation="0" rotation_point_center="false" text_from="ElementInfo" text_width="-1" uuid="{97fc5aa3-779f-4cbb-afdb-6ae2743339ca}" x="-37.9844" y="-11.5">
<text>1-C4</text>
<info_name>label</info_name>
</dynamic_elmt_text>
</dynamic_texts>
<texts_groups/>
</element>
</elements>
</diagram>
<collection>
<category name="import">
<names>
<name lang="ca">Elements importats</name>
<name lang="cs">Zavedené prvky</name>
<name lang="da">Importerede elementer</name>
<name lang="de">Importierte Elemente</name>
<name lang="el">Εισηγμένα στοιχεία</name>
<name lang="en">Imported elements</name>
<name lang="es">Elementos importados</name>
<name lang="fr">Éléments importés</name>
<name lang="hr">Uvezeni elementi</name>
<name lang="it">Elementi importati</name>
<name lang="ja">インバートされた要素</name>
<name lang="ko">전기 심볼 요소</name>
<name lang="ko_KR">전기 심볼 요소</name>
<name lang="nl">Elementen geïmporteerd</name>
<name lang="nl_BE">Elementen geïmporteerd</name>
<name lang="pl">Elementy importowane</name>
<name lang="pt">Elementos importados</name>
<name lang="pt_BR">Elementos importados</name>
<name lang="ro">Elemente importate</name>
<name lang="ru">Импортированные элементы</name>
<name lang="sl">Uvoženi elementi</name>
<name lang="sv">Importerade element</name>
<name lang="tr">İthal öğeler</name>
<name lang="uk">Імпортовані елементи</name>
<name lang="zh">导入元件</name>
</names>
<category name="10_electric">
<names>
<name lang="ar">كهربائي</name>
<name lang="ca">Elèctric</name>
<name lang="cs">Elektrotechnika</name>
<name lang="da">Elektrisk</name>
<name lang="de">Elektrik</name>
<name lang="el">Ηλεκτρικά</name>
<name lang="en">Electric</name>
<name lang="es">Eléctrica</name>
<name lang="fi">Sähkö</name>
<name lang="fr">Electrique</name>
<name lang="hr">Električno</name>
<name lang="hu">Elektromos</name>
<name lang="it">Elettrica</name>
<name lang="ja">電気</name>
<name lang="ko">전기</name>
<name lang="mn">Цахилгаан</name>
<name lang="nb">Elektrisk</name>
<name lang="nl">Elektrotechniek</name>
<name lang="nl_BE">Elektrisch</name>
<name lang="pl">Elektrotechnika</name>
<name lang="pt">Elétrico</name>
<name lang="pt_BR">Elétrica</name>
<name lang="ro">Electric</name>
<name lang="ru">Электротехника</name>
<name lang="sk">Elektrické</name>
<name lang="sl">Elektrika</name>
<name lang="sr">Електрика</name>
<name lang="sv">El</name>
<name lang="tr">Elektrik</name>
<name lang="uk">Електрика</name>
<name lang="zh">10_电气</name>
<name lang="zn">电气工程</name>
</names>
<category name="10_allpole">
<names>
<name lang="ar">متعدد الأقطاب</name>
<name lang="ca">Multifilar</name>
<name lang="cs">Vícežilový</name>
<name lang="da">Flere ledere</name>
<name lang="de">Allpolig</name>
<name lang="el">Πολυγραμμικό</name>
<name lang="en">All-pole</name>
<name lang="es">Multifilar</name>
<name lang="fi">Yleisnapa</name>
<name lang="fr">Multifilaire</name>
<name lang="hr">Univerzalni</name>
<name lang="hu">Többvonalas szimbólumok</name>
<name lang="it">Multifilare</name>
<name lang="ja">複線</name>
<name lang="ko">다-극</name>
<name lang="mn">Бүх туйл</name>
<name lang="nb">Alle poler</name>
<name lang="nl">Veel polig</name>
<name lang="nl_BE">All-pole</name>
<name lang="pl">Schematy wieloliniowe</name>
<name lang="pt">Multipolar</name>
<name lang="pt_BR">Multifilar</name>
<name lang="ro">Bipolar</name>
<name lang="ru">Многополюсные</name>
<name lang="sk">Všepólové</name>
<name lang="sl">Vsi poli</name>
<name lang="sr">Свe-полни</name>
<name lang="sv">Allpolig</name>
<name lang="tr">Çok kutuplu</name>
<name lang="uk">Багатополюсні</name>
<name lang="zh">10_全极</name>
</names>
<category name="100_folio_referencing">
<names>
<name lang="ar">إحالات</name>
<name lang="ca">Referències als fulls</name>
<name lang="cs">Odkazování listů</name>
<name lang="da">Ark henvisninger</name>
<name lang="de">Folienverweise</name>
<name lang="el">Αναφορές σελίδας</name>
<name lang="en">Folio referencing</name>
<name lang="es">Referencias</name>
<name lang="fi">Lehtiviittaus</name>
<name lang="fr">Reports de folio</name>
<name lang="hr">Referenciranje folija</name>
<name lang="hu">Tervlap hivatkozások</name>
<name lang="it">Riferimenti</name>
<name lang="ja">フォリオ参照</name>
<name lang="ko">도면 간 참조</name>
<name lang="mn">Хуудасны холбоос</name>
<name lang="nb">Folio-referanse</name>
<name lang="nl">Referenties</name>
<name lang="nl_BE">Folioverwijzing</name>
<name lang="pl">Odsyłacze</name>
<name lang="pt">Renvoi de folio</name>
<name lang="pt_BR">Referências</name>
<name lang="ro">Renvoi de folio</name>
<name lang="ru">Точки разрыва</name>
<name lang="sk">Odkazy na listy</name>
<name lang="sl">Sklici na list</name>
<name lang="sr">Референцирање фолија</name>
<name lang="sv">Bladhänvisning</name>
<name lang="tr">Sayfa referansı</name>
<name lang="uk">Посилання на аркуші</name>
<name lang="zh">图页引用</name>
</names>
<element name="02going_arrow.elmt">
<definition height="20" hotspot_x="23" hotspot_y="10" link_type="next_report" type="element" version="0.80" width="30">
<uuid uuid="{5ec3a096-68e7-9b2c-d1b8-b960176e2512}"/>
<names>
<name lang="ar">الصفحة التالية</name>
<name lang="ca">Enllaç sortint</name>
<name lang="cs">Další list</name>
<name lang="da">Går pil</name>
<name lang="de">Nächste Folie</name>
<name lang="el">Επόμενη σελίδα</name>
<name lang="en">Going arrow</name>
<name lang="es">Folio siguiente</name>
<name lang="fi">Lähtevä nuoli</name>
<name lang="fr">Folio suivant</name>
<name lang="hr">Odlazna strelica</name>
<name lang="hu">Elmenő hivatkozás</name>
<name lang="it">Foglio successivo</name>
<name lang="ja">出矢印</name>
<name lang="ko">나가는 화살표</name>
<name lang="mn">Явж буй сум</name>
<name lang="nb">Går pil</name>
<name lang="nl">Referentie volgend</name>
<name lang="nl_BE">Gaande pijl</name>
<name lang="pl">Wyjście adresowe</name>
<name lang="pt">Seta de saída</name>
<name lang="pt_BR">Seta de saída (vai)</name>
<name lang="ro">Săgeată de ieșire</name>
<name lang="ru">Следующая страница</name>
<name lang="sk">Odchádzajúca šípka</name>
<name lang="sl">Odhodna puščica</name>
<name lang="sr">Одлазна стрелица</name>
<name lang="sv">Utgående pil</name>
<name lang="tr">Giden ok</name>
<name lang="uk">Стрілка виходу</name>
<name lang="zh">02引出箭头</name>
</names>
<informations>Author: The QElectroTech team
License: see http://qelectrotech.org/wiki/doc/elements_license</informations>
<description>
<polygon antialias="true" style="line-style:normal;line-weight:normal;filling:none;color:black" x1="-9" x2="-9" x3="2" y1="-4" y2="4" y3="0"/>
<line antialias="false" end1="none" end2="none" length1="1.5" length2="1.5" style="line-style:normal;line-weight:normal;filling:none;color:black" x1="-9" x2="-12" y1="0" y2="0"/>
<dynamic_text Halignment="AlignLeft" Valignment="AlignTop" font="Liberation Sans,9,-1,5,50,0,0,0,0,0,Regular" frame="false" rotation="0" text_from="ElementInfo" text_width="-1" uuid="{93888a84-3ffd-438d-acef-5396acba0750}" x="6" y="-11.5" z="3">
<text/>
<info_name>label</info_name>
</dynamic_text>
<terminal name="1" orientation="w" type="Generic" uuid="{975add6f-4ba5-44af-a4c0-e1f1c2024601}" x="-13" y="0"/>
</description>
</definition>
</element>
<element name="01coming_arrow.elmt">
<definition height="20" hotspot_x="6" hotspot_y="10" link_type="previous_report" type="element" version="0.100.0" width="30">
<uuid uuid="{52169153-2558-21ab-63ec-96ce53bfc7c1}"/>
<names>
<name lang="ar">الصفحة السابقة</name>
<name lang="ca">Enllaç entrant</name>
<name lang="cs">Předchozí list</name>
<name lang="da">Kommer pil</name>
<name lang="de">Vorherige Folie</name>
<name lang="el">Προηγούμενη σελίδα</name>
<name lang="en">Coming arrow</name>
<name lang="es">folio anterior</name>
<name lang="fi">Saapuva nuoli</name>
<name lang="fr">Folio précédent</name>
<name lang="hr">Dolazna strelica</name>
<name lang="hu">Beérkező hivatkozás</name>
<name lang="it">Foglio precedente</name>
<name lang="ja">入り矢印</name>
<name lang="ko">들어오는 화살표</name>
<name lang="mn">Ирж буй сум</name>
<name lang="nb">Kommer pil</name>
<name lang="nl">Referentie Vorig</name>
<name lang="nl_BE">Komende pijl</name>
<name lang="pl">Wejście adresowe</name>
<name lang="pt">Seta de entrada</name>
<name lang="pt_BR">Seta de entrada (vem)</name>
<name lang="ro">Săgeată de intrare</name>
<name lang="ru">Предыдущая страница</name>
<name lang="sk">Prichádzajúca šípka</name>
<name lang="sl">Prihajajoča puščica</name>
<name lang="sr">Долазна стрелица</name>
<name lang="sv">Inkommande pil</name>
<name lang="tr">Gelen ok</name>
<name lang="uk">Стрілка входу</name>
<name lang="zh">01引入箭头</name>
</names>
<informations>Author: The QElectroTech team
License: see http://qelectrotech.org/wiki/doc/elements_license</informations>
<description>
<polygon antialias="true" style="line-style:normal;line-weight:normal;filling:none;color:black" x1="-1" x2="-1" x3="9" y1="-4" y2="4" y3="0"/>
<line antialias="false" end1="none" end2="none" length1="1.5" length2="1.5" style="line-style:normal;line-weight:normal;filling:none;color:black" x1="9" x2="12" y1="0" y2="0"/>
<dynamic_text Halignment="AlignRight" Valignment="AlignTop" font="Liberation Sans,9,-1,5,50,0,0,0,0,0,Regular" frame="false" rotation="0" text_from="ElementInfo" text_width="-1" uuid="{8ec41ce2-36d6-4fe3-b299-c8a48cb2182c}" x="-12" y="-11.5" z="3">
<text/>
<info_name>label</info_name>
</dynamic_text>
<terminal name="1" orientation="e" type="Generic" uuid="{d7fd0bec-9f05-4651-a50b-29d7ffabeb41}" x="13" y="0"/>
</description>
</definition>
</element>
</category>
</category>
</category>
</category>
</collection>
</project>
+143
View File
@@ -0,0 +1,143 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Jumping between the two ends of a folio report, linked against the
// application's objects.
//
// A linked folio report shows a pointing hand under the mouse, on its
// arrow and on the text that shows its label, and a double click on
// either goes to the other end. The end jumped to is highlighted for a
// moment, independently of the hover highlight, which the mouse leaving
// the end jumped from would clear at once. An unlinked report shows no
// hand and has nowhere to jump to.
#include <QApplication>
#include <QEventLoop>
#include <QFontDatabase>
#include <QGraphicsSceneHoverEvent>
#include <QGraphicsSceneMouseEvent>
#include <QSettings>
#include <QTemporaryDir>
#include <QTimer>
#include <cstdio>
#include <stdexcept>
#include "../../sources/diagram.h"
#include "../../sources/qetproject.h"
#include "../../sources/qetmessagebox.h"
#include "../../sources/qetgraphicsitem/element.h"
#include "../../sources/qetgraphicsitem/dynamicelementtextitem.h"
static void check(bool value, const char *message)
{
if (!value) throw std::runtime_error(message);
}
static void hover(Diagram *scene, QGraphicsItem *item, QEvent::Type type)
{
QGraphicsSceneHoverEvent event(type);
event.setScenePos(item->sceneBoundingRect().center());
event.setPos(item->boundingRect().center());
scene->sendEvent(item, &event);
}
static void doubleClick(Diagram *scene, QGraphicsItem *item)
{
QGraphicsSceneMouseEvent event(QEvent::GraphicsSceneMouseDoubleClick);
event.setButton(Qt::LeftButton);
event.setButtons(Qt::LeftButton);
event.setScenePos(item->sceneBoundingRect().center());
event.setPos(item->boundingRect().center());
scene->sendEvent(item, &event);
}
static void wait(int ms)
{
QEventLoop loop;
QTimer::singleShot(ms, &loop, &QEventLoop::quit);
loop.exec();
}
static bool showsHand(const QGraphicsItem *item)
{
return item->hasCursor() && item->cursor().shape() == Qt::PointingHandCursor;
}
int main(int argc, char **argv)
{
QApplication app(argc, argv);
std::freopen(argv[2], "w", stdout);
QTemporaryDir settings;
QSettings::setDefaultFormat(QSettings::IniFormat);
QSettings::setPath(QSettings::IniFormat, QSettings::UserScope, settings.path());
QCoreApplication::setOrganizationName("QETReportJumpRegression");
QETProject::setBackupEnabled(false);
QET::QetMessageBox::setNonInteractive(true);
QFontDatabase::addApplicationFont(":/fonts/LiberationSans-Regular.ttf");
try
{
QETProject project(QString::fromLocal8Bit(argv[1]));
check(project.state() == QETProject::Ok, "fixture opens");
check(project.diagrams().size() == 2, "fixture has two folios");
project.buildFolios(project.diagrams());
Diagram *one = project.diagrams().at(0);
Diagram *two = project.diagrams().at(1);
// Folio one: a going report linked to folio two, and one alone.
Element *going = nullptr, *alone = nullptr;
for (Element *element : one->elements()) {
if (!(element->linkType() & Element::AllReport)) continue;
(element->linkedElements().isEmpty() ? alone : going) = element;
}
check(going && alone, "a linked and an unlinked report on folio one");
Element *coming = going->linkedElements().first();
check(coming->diagram() == two, "the linked end is on folio two");
check(going->jumpTarget() == coming, "a linked report jumps to its other end");
check(alone->jumpTarget() == nullptr, "an unlinked report has nowhere to jump to");
// The arrow: a pointing hand while the mouse is over it, only
// when linked.
hover(one, going, QEvent::GraphicsSceneHoverEnter);
check(showsHand(going), "pointing hand over a linked report");
hover(one, going, QEvent::GraphicsSceneHoverLeave);
check(!going->hasCursor(), "hand gone when the mouse leaves");
hover(one, alone, QEvent::GraphicsSceneHoverEnter);
check(!showsHand(alone), "no hand over an unlinked report");
hover(one, alone, QEvent::GraphicsSceneHoverLeave);
// Double click on the arrow: the other end is selected and
// highlighted, and stays highlighted when the mouse leaves the
// end jumped from.
hover(one, going, QEvent::GraphicsSceneHoverEnter);
doubleClick(one, going);
check(coming->isSelected(), "the end jumped to is selected");
check(coming->isShownAsJumpTarget(), "the end jumped to is highlighted");
hover(one, going, QEvent::GraphicsSceneHoverLeave);
check(coming->isShownAsJumpTarget(), "leaving the report keeps the jump highlight");
wait(2300);
check(!coming->isShownAsJumpTarget(), "the jump highlight goes after a moment");
// The text showing the label: a pointing hand, and a double click
// jumps back.
DynamicElementTextItem *label = nullptr;
for (DynamicElementTextItem *text : coming->dynamicTextItems())
if (text->linkedElementShown()) label = text;
check(label, "the coming report has a text that leads to its other end");
check(label->linkedElementShown() == going, "the text leads to the going report");
hover(two, label, QEvent::GraphicsSceneHoverEnter);
check(showsHand(label), "pointing hand over the label text");
hover(two, label, QEvent::GraphicsSceneHoverLeave);
check(!label->hasCursor(), "hand gone when the mouse leaves the text");
doubleClick(two, label);
check(going->isSelected(), "a double click on the text jumps back");
check(going->isShownAsJumpTarget(), "the end jumped back to is highlighted");
std::puts("PASS: linked folio reports show a hand and jump to their highlighted other end");
}
catch (const std::exception &error)
{
std::printf("FAIL: %s\n", error.what());
return 1;
}
return 0;
}
+60
View File
@@ -0,0 +1,60 @@
#!/usr/bin/env python3
"""Link the report jump integration probe using an existing Ninja app build.
Reuses app objects without duplicating the app or introducing a core-library
refactor. No application source or existing executable is replaced. The
generated manifest, probe binary, reports and export fixtures stay in build/.
"""
import argparse
import pathlib
import re
import shutil
import subprocess
import sys
parser = argparse.ArgumentParser()
parser.add_argument('--build', required=True)
parser.add_argument('--binary', required=True)
parser.add_argument('--source', required=True)
parser.add_argument('--fixture', required=True)
args = parser.parse_args()
build = pathlib.Path(args.build).resolve()
binary = pathlib.Path(args.binary).resolve()
source = pathlib.Path(args.source).resolve()
fixture = pathlib.Path(args.fixture).resolve()
manifest = (build / 'build.ninja').read_text(encoding='utf-8')
match = re.search(r'^build (\S*?/sources/main\.cpp\.(?:obj|o)):', manifest, re.MULTILINE)
if not match:
sys.exit('Cannot find the application main object in the Ninja build.')
main_object = match.group(1)
probe_object = main_object.replace('main.cpp.', 'report_jump_probe.cpp.')
probe_binary = binary.with_name('report_jump_probe' + binary.suffix)
def escape(path):
return pathlib.Path(path).as_posix().replace('$', '$$').replace(':', '$:').replace(' ', '$ ')
main_source = source.parents[2] / 'sources' / 'main.cpp'
manifest = manifest.replace(main_object, probe_object)
manifest = manifest.replace(escape(main_source), escape(source))
if binary.suffix:
manifest = manifest.replace(binary.name, probe_binary.name)
else:
old_target = escape(binary.name if binary.parent == build else binary)
new_target = escape(probe_binary.name if probe_binary.parent == build else probe_binary)
manifest = manifest.replace('build ' + old_target + ':', 'build ' + new_target + ':')
old_file = binary.name if binary.parent == build else binary.as_posix()
new_file = probe_binary.name if probe_binary.parent == build else probe_binary.as_posix()
manifest = re.sub(r'^ TARGET_FILE = ' + re.escape(old_file) + r'$',
' TARGET_FILE = ' + new_file, manifest, flags=re.MULTILINE)
probe_manifest = build / 'report-jump-probe.ninja'
probe_manifest.write_text(manifest, encoding='utf-8')
ninja = shutil.which('ninja')
if not ninja:
sys.exit('Ninja is required for the integration probe.')
target = probe_binary.name if probe_binary.parent == build else str(probe_binary)
subprocess.run([ninja, '-C', str(build), '-f', probe_manifest.name, target], check=True)
output = build / 'report-jump'
output.mkdir(exist_ok=True)
report = output / 'results.txt'
result = subprocess.run([str(probe_binary), str(fixture), str(report), str(output)], timeout=90)
if report.exists():
print(report.read_text(encoding='utf-8', errors='replace'))
sys.exit(result.returncode)