a3)bg7Z7PmBZKQ(j;%PM)N_$4-%VX&xGZDMrMV_mvY3v)qJ7jqQ3
z|DuIvaeT7o+%pUl+Oz)oZgz-S3XB~=KjVeb5V#vE?whhc(8TXm-6y^6EcD2`k%X5}
z7GRi&uJ&X~?4ySH-XaO0YFV3k&V{cuUiKoJ7a)f$EO)S*yL1jj@
zBf9ih!qk|WbTkA1N=$vHZQPU!@qLcv)Y+h{g}}b+U)?mv)($fB6fQ}ckfw>bJ6g(@
zl)<7;0i12oi8j~~vCWD#)BNo@{n!sWTJ05<#L@-R@mjc+?_sU>b%9Bzn7*L=yv#lL
z<-6@A2j0Zs#}8(;-8O*ofAqEI%0G?s=g?5|KXLb&i-_osp2{EUxHs(d;nrJLm`p`V
z@WPo^hWIKvaQ#T)39jHTI382p=*gJNe5C>lzK+SJyk(j%%QmBoUxlCnQGT$%!xo;e
zOPJybE9r_}&av!b7ixB8z0c^CbEdG`m5TKpOul7@N=Ds+#~@l!*UjI31l
z$o0~f@)K!0oO=Pz%=X*7mNIXGziqeEciVE>>3Vu$Z%Z8(Y30A!3#PNHt7;@Pqjb6~twbx-fk&h|(3OwUaB&a)@GXS({M>Zy9Vt7rE0Rn-Ok_tjSw
z{r;r-&KDn4A5~TLDg97Y_4^B5PnT8o%MVn6UqAo|00AHX1b_e#00KY&2mk>f00e*l
z5C8%|00;m9AOHk_01yBIKmZ5;0U!VbfB+Bx0zd!=00AHX1b_e#00KY&2mk>f00e*l
z5C8%|00;m9AOHk_01yBIKmZ5;0U!VbfB+Bx0zd!=00AHX1cpT5;r_$V9`0`5J>K8`
ziGLpMZ&&Z%zWJPgSoZb(ho1~NKgI?EIU}I?al3oC^Zzh+m^0u@&fbJ>sS%L!qs^
z*L?U{YJD&s5XcFEuKZ~71zgF=qtGQK0x9@$;L6QA9xtVS7z+qYi9lL@=rauVlPNul
zxlCDq(b2EzTyW4;7
zV?6NuH{9;#T`GMs4iLx*0p^e9jQ<@;h$D(`IKSuQTIiA(0mGGEdkgZd-d^Ez9p|n5
z-3MNm*dJ;Q1Gc@fO*MeZ%R1*^$hW%sLG!}Bmor!JT%;?y>HBr%3HvdoI2}ZS
z&fG9qgCjn8y|U2z4?goTXJYf&P>y`U9h@tze(KE)aKwtMv@*nxW*i%?3^6v(!#tFZ
zJ%@J5xB-rI4wo2y7_RWSS{lpB$9MV}r0MT1kqanaCg%n?(upHo_;HAD`S*3B&LlJE
z$Y9(kfFo?XIRkUa@yT_af;&N{e*7@c8N=sm<#XXaGUw;MgL5NOjwmh!`+6TgMlWHh`VPFOFn<+?X&&{As)xR8XK3x0a%Akiw9h)zr|)FkFfkZd
z)B4yJ>Kk<~HTqi0F?iCph2va(=gp!|knaJ^rSC`DI-T475k>cL!$&A@wSYOG?$%Z3{Qf5q4(ZQFV6HTrtXn|F+_RF@R(bY3Lq
z33J(}qjV4Pjgi8xDaOc-8z$B~Yg$U2l>KJTAbtJ?wn|`_0WzP*0s{?Z+W{p+Pd}y83
z`ItO^IXNSH;&6y~kv@kC#kVLA9oSz(;L{$n7;X&A5y_g^jO}*(7#z92*-myHewW?$
zTc3OJKI!4E2rp0nKKhT<*Z5TC!?<^4gN`}Cx+Gm!n|8VU6qU!+yjQ&HVb|mR#~;1F
zd-IcT{`lromtM_!sg4{Y&3?`6CKk5q_8QXcoBMUK+!$ydk;Y3i4t%lG8Q(~bJlwze
z>Eqq)-ziMPVo;P%diDSjW|8)UbRI+A*>@&3b14&IFN&3x-I8}2QTvi77Hx6y
zaDVgHQ(heQd0M~!_09d8AK3bGor;5&P3`e~dw`@rbI3J+j`e554dd5D9O=RjjwOkY
zbsV|R@H)4Oi=)T4w?A*kvi1J$?O%L4O7;NXI$)jC_=ZX!zkGU4C6nM0vN=0>0*v6NAYXysN;8t)xP}j
z>ompa
ze%`n0+0br(@ATizuO#z?)=!^pLF_!-lIS)@o1hI&d^6uGe0oQ4BpE&tb4vZr+_35w
z;K<|Mn|tXTA%3vjHCCvvk6W%W$F64`!)r~!TyXJ37_TYu;doBvU&*=g+qbu0zkhrC
zH}da)9`4>eQu*bB=b5fex6nMyPg_5(3uEWzmTSzh>(Bbxu8SL{UciyG`1Fs5lYGk9
zVk-sq4IOW+ukUB*`BRJ^+6T{ie+k5pnUPpPGh8xfx_~!
z5JxQW7srWXJY3I2eK8lxbDH`vH<(Ml@gR#sAZ;FT91l(3<#-)hYo%*;lx+b{HoX_i
z4c9xn=9svSM_|qz;TUuw7CvWoTn_{siS~)d4wrqXmm?7EkHoo0)Eb<4ZWl^JVIs+c
zqRxikhRcOX>xYVmDPvzUw=ZWOB*!9tr_1fSpZk*r&rAlJB}Y8@&R{u~+5_FOEtxmO
zv6+PI=w{B2BOZPXkA2oBeFZaZ=C)e2)11|0$NEC7%x7C-%B(%-0uZln4GM`iT97gzLbU|{`c=`=5S#(
z$NF>NNLOyKzsI$|U;K)6MV^(M#v>Pz5AS8i~8y7mwTO%!(t^Gt1r{cleBoG97vcx>Mokab5O
zXO8sYhFQN|$VlSOjFGhLu+~fVJKtL1!T|L$1hVFc;~c@3nX|YretqxkByr~(yR)pr
zJ8;QP<(jhp0!$(zc^yjJEyphEZ++6A+mAOM$G(18d}!{+dQbMwx}iQv&f+Hbru!dvtL#@$)v#i!-n)f3|
zdkFZ^FL+1l-G?LDal^#i8FD0)KaLbGvz(?2N3!Kc%@IjHBF+8#tozCOI?nd94A=>b
zj^T)u8>*dI(oL{BfnC^Rm$InzH9AOl)-%A2HAF!n}Al=&dQ@uKpsh_y?y2aU@Oy`N})`@id9
zkY^iI`w4PF8#a=&l+hh!-U#H9SJ~oB)kWm-Q}ZNd&(1q4pdBNS>)z4kO3Yqzuxq?`
zCafRlsJwsk{P>&)0@?2!?R+U>FJTX<^9l7C%1-PT=fMM;75Z#@{OqW`Zs&O4H6~MW
z#q1Tl_u6np-FYBTN^a%iXjh&X?`gi&-#6UvF`Sbf|8U;K=WYJsKBhm)^F|=QXS&_?
zGUf^5_Pk?t3hRc-$zALHVD*t_k}0spoaG^)@{{)Yx3qh3qrAW?MB~BM^RYMvZWIz}
z<*BL8renqN5c5QE180-+;&W<^v;S_Nk5d9}X#0-e5B7cDHMP$$|2PN~{aiP|4Ye;)
z8(;3Id4hKr<3w9ovvB+rdoCAnBg!_s-zzJ4N6=P%x7U}u
zqr`kHSHO|8=Y=sa=YQ+L+~Axk%QF%z>>$vip4n>vRFxWVA$xZf%Kz>!H}cZ^A=
ze1Fco;4={CP9a;f<%ZcO!I6*)ij!46;$aX0?qBtQqPlSLfjvL@e$fd=~_4Cu?f1BOq?9SYfoF_s+70V59
zB;?wnOCXPzuzvN8{kOM90)0|_7cjELi-1ELh&i`OyQgVY`6iA_}qLpvOQOr
zFZ5&1*h9P*J3DTGBWJ^dsSVNRpKgDPI<(JaAy2r+PVFnqKRyB}pPyyH2eWrHOK@cT
zz&e8&&wlR!N6sKhr!!%ScleX;G1y0@3$_Ah&hC4}&Gmk$YjC8%NS#LIS@MH<2aZe=
za0SjfX}&Ym9w8YQ94Rnb$5(k4zp30Y}CUxB_RK?H+<$tH5X-U**|7$7=5<*hj_>
zy256h?O9!M<>p;s?t;1#ATVc+u7Hsyf}jWW3oUic;eY`B*EXcDA>(Vy$1s4&JpBRJ%R&W6$0nVk(7Vm
z0--W*1kREp$gAcZ)QPP>D~^C0iK7F175pV;+JRkx^a|GNNFfuT-L7+&EfE&hr
zC`Uk`D2{*|BSZwIXd_SrN5GA?+i*W30$Fne+!!$uFqI2|EI9&hxZH*M2@uGNBjCma
z5r8Q}2xP$#a3ka<$R|W#QjUNd6M7y~OA(laBjASAJ!qE!0;6&S+{nP=P$3q9Q8)r_
z#JUCTGeTfcj({5(c^ay8MPLw)fE!)ifqoexkd!0fMur}SN_`PX!Vz$zuN%-mV+49}
z1l-8jvrsJ+0-ZPlZlr=A;}n2^lq34@jNGmYFbOIGfusoV|7TjofBXN-QQzZl@FayB
zgB6Ov1LaQYhj_u9;J>NP`%9tbKy4rZ1b_e#00KY&2mk>f00e*l5C8%|00;m9AOHk_
z01yBIKmZ5;0U!VbfB+Bx0zd!=00AHX1b_e#00KY&2mk>f00e*l5C8%|00;m9AOHk_
z01yBIKmZ5;0U!VbfB+Bx0zd!=00AHX1c1OK2<%)R(#9p)L1u>rxQKCiHPL0k?6tbq
z<5*=E9$B?iLTkTzP`m8X(@$$^?^mt3qeaGbOP7p<%0gSe-L|@F_Is|e;JV#Qr4%*&
zp0wIZL|Q0hjBHcmq=g%0gpsWc_`BWfY`g7f(xqdH|EnW7ZDcxITWj($^J-9t^gsb2KhEF<%j
zfn3t)ndKxr$jWx6Lq-QZC2~Wnub=*m3gcM$p}f7>}AS4)`Dc)D;jZgy)pf2*)_=)
z6pYn%L#9k;14;I>B&TmlTF<9UX9J(A>(>>_uG+E-l3lTN7FnfbFV*$a)wVTewR&RU
zXi|%8`NY6&tEEM@Af*`Gl|^=~WQ_Li$;?;VMX2^q+Z<$g=l;gag;bIm116cd_0RKvxFN*eATR8mASY3V~XTx_W%HNMy|WfY+gGHu}M
zYD+CvFRJ;zpEmIIlG~66d8oZi8F=xcWD7;;gG?EC@yt#lZEFwGwdr3~PwXTzf=Nps
zBU{r&^|_K=?3lC-*@k5mQJOxx&EiP5gXd58mJqX)iP?laP=dZG4NzesxXk+WX8ajj5)YBuq9&-?hQO#>9F3i
ztTyl0;c~vh3
zHa^huug0v0YK+Mnrshj)ZRJ5}sIBg|Xe}A*#viL}tweUv|Kmnaw^EFjiS@;DQ06A)2_<
z0cyqCWHKvTzuG`GR(niMBx_96nT$1->d3anV7*INW4BJ#7Is~f2bXr4^5DuYQwA#0
z31%u)JLR-)Otd$v`3WveU2UFaObStLo<&9;T)1>01D7rt8Mt!EXc{fD%(q+D=GkPM
m)3v-Fh&))EXY2R8aL>0N;1sjH$TsF)h<o&s#t_7V!;+sO6`L$K3%KSN*{V6h$6KX{~>~PHvgKQcWp7S+1Z)j
z{J!t|ecycZQgLo}JaT`8XL(>5I)6Ydz=E45H7nQl
z$o}?R3AdMU%HXFS1$$c>Bd|$;K+CQ>zSh$D4zI?>;k3wu4kTF8`RULgC>M$#O+5nC
zXk5TjEC~{+s1%PuCB6s9P)dm~DN2d}B@M}t@Q=88`*0aUFbiNh@t|p4j
zW;5DUqSUL3QYytbWKos{h7kM}CqOO1@hAHX8RBElas!JxAY?=px*X^{^Ryd+?G9)i
zzb6wDOl%=nl%g;UsRK0d0Ou}y^^S1^i=<9$;sid!N&~E0qXG46bQ9{q@+}6Kw1vXJ
z#)iJw_P~T6WE(7uo*)~d{nCm{#ChV=We<~VgSj~wM&oK}kD!2hB}(i4L>2ok1DQn)
z9y(w-25tHe4{abB6cC-4q0I7?WGO4jiWW;~DtrVcH3+-V0yQjiWgS!rp$bFCZh^9r
zF;IZ+2sW^0QqM*#V9Q2TBDzkM2LlxDs|i~6tukK2gt!BpV4_HPSmmtO|D=C;Z#yU2ucDZF({;n8WWTlRwZI!f}z~s
zpP_g;EP-%;_cRz3Gme|?K?qR-Vj`%9kq{ImCJRVS7>w6Uno`1yE2i3Kd)%|wfkgFT
z*3c`1F)HyCOv(J!%8wx^)YqFjleQ7`-Dt$PNf7xLH)2+VN52_7dG)$#i31C((aT
z$N!P2CqJ$sr%G5S7x^$4G3X
zT;`+lcJ62YiG_*F@0xF_TlQ?@X0A0xu8w@1Jo5DAotJ`(2cP`=TmPxu_ip2^UpRN-
z@cGh}m*2={4!+oa=ebvYnLF9udi0+i+mCUPwX@urorU*)d~nyqtG|EEwTr9Que^Tf
zg1YwCk+;Wlqid&jz+b=rruxS{&n$j25;?{-9$1*&{rQ(aoenR+e0DDL!OXMI{|l3-
BXdnOp
literal 0
HcmV?d00001
diff --git a/images/min.png b/images/min.png
new file mode 100644
index 0000000000000000000000000000000000000000..ca0f6dab22542c378f2826d97b839991cccc1a5d
GIT binary patch
literal 2010
zcmb_dJ8T?97~W7ohzNorO{!rn5Gc&;`>9-vb7#k=aKShSHWbLSGqZP#Z+F(a>$|f{
z1O-B(q@kfP1qB5NiAND=kU%MrqEso6=qOMc1Vsem-@Qj{%TA(*llFFY=9}+-%=gd#
z{>qh0r%#9Oac&U&%l
ze%YrYt`Q~zUK_iWBn27aB%hlyGJBE1dpK}R8+(*e>;%lP3|qGhjZF791>G(sQ0^aB
z0}~WD?z+dQX{t8iSPw0Z)zDP2tGWg@oPg_uhb&NoDcdVq1O-X@`&mn`7%-aPjjMSf
zb_1JWH7LEZbPv0Bpa+)C48~X~`$|dva+Jdv?@S8mD&Lxn`Vp$c;}cpIMO~()8aR_D
z)6o(6cp%G@{lgvy(tD_6Y&{oYF(esZ=tA!9t3u^i=S4E$**^DEk`cIzc$U}YFcUE%
zY1)q{DPvUz37eGo>~v5e&|Og-YbPgzd#8vo)zVC^VbkFOrpyenXLtcNjgW;*qn;Cj
zNoEs^e?gv^pLpMH6n?BT1LTO|HlfAIyX4|M*}Il=}Y{j8b0UVF^uPE>7FGCuh0sjb^j{Cs`*)!N@5J{$jYf}jX0*atz_m(m9xMFc?*v?79v4|+0_nNHW;wirlqll$H8
zob#P?&waD8cK+mv$4+n@cd}NkG~tc1d*L`dlkM%_;q^dN-AFjDu$bL*!`{ls)&XcSgUX7fm2QJV$+$Sh
zcLlq|@>m>T2-pvBn~G7`=FwC}tIWDA2E*W8arS?$TyDh7^SdySG|!(ywbgP-HcKTz
zDaa#t>vgLZCMgaHtyOFu#3}f`Yq=VcJr`raa0n4}Sycs-QbW)*&vkTFRvnp4)>jzm
zWhIcUAMXa25#YGxp6M78(Hu>ns!L-xnt$ZV0q*wA$*GHq`u`tOt
zh0T+!8mPxF^f``ay>R=^d(MY&~IKI>0eqX@homtwQlg=ULdN@r?TsjxqEi
z8YedI#VkNLin>0*S*&6|bn`7f%??T#x>Zz1+DUN#&Jx{Q$&6z^Aklw!$N!Nim!EWS
z*rw3QMLr8g%zEB9`QpL47|qS}#|a#oZZ@2Dd-#~17c>Ow7*2yPt{mHelO(QHR$A+?
z{rt{H4_-qHmG3)$|MT64FMQU2_!If`*ZXtgtK3h&yyaJ~u5t6H&Q|uSm%ZITUcULn
z{f!qluUFr{!8gBo>D~Qrdp|t%%&&Cgs=fH!dvo)rcDQ5c+P+}?cIJ)8xyGk&U-;_v
kAOHM#_c}MXaCYaBzvdrZx^MY|J+RKzR@W+f=PtkUFTNB@2mk;8
literal 0
HcmV?d00001
diff --git a/src/cpp/globalParams.cpp b/src/cpp/globalParams.cpp
new file mode 100644
index 0000000..a82056a
--- /dev/null
+++ b/src/cpp/globalParams.cpp
@@ -0,0 +1,7 @@
+#include "globalParams.h"
+
+namespace globalParams
+{
+ QString curUserId;
+ QString curUserType;
+}
diff --git a/src/cpp/interfaceTestWin.cpp b/src/cpp/interfaceTestWin.cpp
new file mode 100644
index 0000000..896319d
--- /dev/null
+++ b/src/cpp/interfaceTestWin.cpp
@@ -0,0 +1,86 @@
+#include "interfaceTestWin.h"
+#include "ui_interfaceTestWin.h"
+
+InterfaceTestWin::InterfaceTestWin(QWidget *parent)
+ : QWidget(parent)
+ , ui(new Ui::InterfaceTestWin)
+{
+ ui->setupUi(this);
+
+ serialModel = new QStandardItemModel(this);
+ serialSelect = new QItemSelectionModel(serialModel);
+ serialModel->setColumnCount(2);
+ ui->tableViewSerial->setModel(serialModel);
+ ui->tableViewSerial->setSelectionModel(serialSelect);
+ ui->tableViewSerial->horizontalHeader()->setVisible(false);
+ ui->tableViewSerial->verticalHeader()->setVisible(false);
+ ui->tableViewSerial->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
+ ui->tableViewSerial->verticalHeader()->setSectionResizeMode(QHeaderView::Stretch);
+
+ QFont font = ui->tableViewSerial->font();
+ //font.setPointSize(10);
+ ui->tableViewSerial->setFont(font);
+
+ QList aItemList;
+ QStandardItem *aItem = new QStandardItem("串口接收");
+ aItem->setEditable(false);
+ font.setBold(true);
+ aItem->setFont(font);
+ aItem->setTextAlignment(Qt::AlignCenter);
+ aItemList.append(aItem);
+ aItem = new QStandardItem("");
+ aItem->setTextAlignment(Qt::AlignCenter);
+ aItemList.append(aItem);
+ serialModel->insertRow(serialModel->rowCount(),aItemList);
+
+ aItemList.clear();
+ aItem = new QStandardItem("串口发送");
+ aItem->setEditable(false);
+ font.setBold(true);
+ aItem->setFont(font);
+ aItem->setTextAlignment(Qt::AlignCenter);
+ aItemList.append(aItem);
+ aItem = new QStandardItem("");
+ aItem->setTextAlignment(Qt::AlignCenter);
+ aItemList.append(aItem);
+ serialModel->insertRow(serialModel->rowCount(),aItemList);
+
+ canModel = new QStandardItemModel(this);
+ canSelect = new QItemSelectionModel(canModel);
+ canModel->setColumnCount(2);
+ ui->tableViewCan->setModel(canModel);
+ ui->tableViewCan->setSelectionModel(canSelect);
+ ui->tableViewCan->horizontalHeader()->setVisible(false);
+ ui->tableViewCan->verticalHeader()->setVisible(false);
+ ui->tableViewCan->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
+ ui->tableViewCan->verticalHeader()->setSectionResizeMode(QHeaderView::Stretch);
+
+ aItemList.clear();
+ aItem = new QStandardItem("CAN接收");
+ aItem->setEditable(false);
+ font.setBold(true);
+ aItem->setFont(font);
+ aItem->setTextAlignment(Qt::AlignCenter);
+ aItemList.append(aItem);
+ aItem = new QStandardItem("");
+ aItem->setTextAlignment(Qt::AlignCenter);
+ aItemList.append(aItem);
+ canModel->insertRow(canModel->rowCount(),aItemList);
+
+ aItemList.clear();
+ aItem = new QStandardItem("CAN发送");
+ aItem->setEditable(false);
+ font.setBold(true);
+ aItem->setFont(font);
+ aItem->setTextAlignment(Qt::AlignCenter);
+ aItemList.append(aItem);
+ aItem = new QStandardItem("");
+ aItem->setTextAlignment(Qt::AlignCenter);
+ aItemList.append(aItem);
+ canModel->insertRow(canModel->rowCount(),aItemList);
+}
+
+InterfaceTestWin::~InterfaceTestWin()
+{
+ delete ui;
+}
diff --git a/src/cpp/loginWin.cpp b/src/cpp/loginWin.cpp
new file mode 100644
index 0000000..ebabf20
--- /dev/null
+++ b/src/cpp/loginWin.cpp
@@ -0,0 +1,260 @@
+#include "loginwin.h"
+#include "ui_loginwin.h"
+#include "utils.h"
+#include "sqlitedb.h"
+#include "globalParams.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef defined(_MSC_VER)
+#pragma comment(lib, "user32.lib")
+#pragma comment(lib,"dwmapi.lib")
+#endif
+
+LoginWin::LoginWin(QWidget *parent) :
+ QWidget(parent),
+ ui(new Ui::LoginWin)
+{
+ ui->setupUi(this);
+ this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint);
+ this->setPalette(QPalette(QColor(240,240,240)));
+ ui->titleBar->setPalette(QPalette(QColor(240,240,240)));
+ this->setAutoFillBackground(true);
+
+ int w = this->width();
+ int h = this->height();
+ this->setMinimumSize(w,h);
+ this->setMaximumSize(w,h);
+
+#ifdef defined(_MSC_VER)
+ /*
+ 此行代码可以带回Aero效果,同时也带回了标题栏和边框,在nativeEvent()会再次去掉标题栏
+ 主要的关键是WS_THICKFRAME,可以实现窗口停靠边缘自动改变大小功能和Aero效果
+ */
+ HWND hwnd = (HWND)this->winId();
+ DWORD style = ::GetWindowLong(hwnd, GWL_STYLE);
+ ::SetWindowLong(hwnd, GWL_STYLE, style | WS_THICKFRAME);
+
+ //保留一个像素的边框宽度,否则系统不会绘制边框阴影
+ const MARGINS shadow = { 1, 1, 1, 1 };
+ DwmExtendFrameIntoClientArea(HWND(winId()), &shadow);
+#endif
+
+ connect(ui->titleBar,SIGNAL(windowCloseSignal()),this,SLOT(windowCloseHandle()),Qt::QueuedConnection);
+
+ readCfg();
+}
+
+LoginWin::~LoginWin()
+{
+ delete ui;
+}
+
+void LoginWin::windowCloseHandle()
+{
+ QSettings setting("config/cfg.ini",QSettings::IniFormat);
+ QString check;
+ if(ui->ckbRemember->isChecked()){
+ check = "1";
+ }else{
+ check = "0";
+ }
+ setting.setValue("users/remember",check);
+ close();
+}
+
+void LoginWin::on_btnLogin_clicked()
+{
+ if(ui->cmbName->currentText().isEmpty()){
+ QMessageBox::warning(this,"警告","请输入用户名!",QMessageBox::Ok,QMessageBox::NoButton);
+ return;
+ }
+ if(ui->lePassword->text().isEmpty()){
+ QMessageBox::warning(this,"警告","请输入密码!",QMessageBox::Ok,QMessageBox::NoButton);
+ return;
+ }
+ QString curName = ui->cmbName->currentText().trimmed();
+ QString curPassword = ui->lePassword->text().trimmed();
+ QMap map;
+ map["ID"] = curName;
+ char base64[256] = {'\0'};
+ int len = 0;
+ base64_encode((const unsigned char *)curPassword.toStdString().c_str(),strlen(curPassword.toStdString().c_str()),(unsigned char *)base64,&len);
+ map["PASSWORD"] = QString::fromLocal8Bit(base64,len);
+ QVector> result = SQLITEDB::getInstance().getRecord("USERS",map);
+ if(result.size()<=0){
+ QMessageBox::warning(this,"警告",QString("用户名或密码错误!"),QMessageBox::NoButton);
+ return;
+ }
+ nameMap[curName] = curPassword;
+ globalParams::curUserId = result[0].value("ID");
+ globalParams::curUserType = result[0].value("USER_TYPE");
+ saveCfg();
+ emit loginSignal();
+ windowCloseHandle();
+}
+
+bool LoginWin::nativeEvent(const QByteArray &eventType, void *message, long *result)
+{
+ MSG* param = static_cast(message);
+
+ switch (param->message)
+ {
+ case WM_NCCALCSIZE: {
+ /*
+ 此消息用于处理非客户区域,比如边框的绘制
+ 返回false,就是按系统的默认处理,如果返回true,而不做任何绘制,则非客户区域
+ 就不会被绘制,就相当于没有绘制非客户区域,所以就会看不到非客户区域的效果
+ */
+ return true;
+ }
+ case WM_NCHITTEST:
+ {
+
+ const LONG border_width = 3;
+ RECT winrect;
+ GetWindowRect(HWND(winId()), &winrect);
+
+ long x = GET_X_LPARAM(param->lParam);
+ long y = GET_Y_LPARAM(param->lParam);
+
+ /*
+ 只用这种办法设置动态改变窗口大小比手动通过鼠标事件效果好,可以
+ 避免闪烁问题
+ */
+ //bottom left corner
+ if (x >= winrect.left && x < winrect.left + border_width &&
+ y < winrect.bottom && y >= winrect.bottom - border_width)
+ {
+ *result = HTBOTTOMLEFT;
+ return true;
+ }
+ //bottom right corner
+ if (x < winrect.right && x >= winrect.right - border_width &&
+ y < winrect.bottom && y >= winrect.bottom - border_width)
+ {
+ *result = HTBOTTOMRIGHT;
+ return true;
+ }
+ //top left corner
+ if (x >= winrect.left && x < winrect.left + border_width &&
+ y >= winrect.top && y < winrect.top + border_width)
+ {
+ *result = HTTOPLEFT;
+ return true;
+ }
+ //top right corner
+ if (x < winrect.right && x >= winrect.right - border_width &&
+ y >= winrect.top && y < winrect.top + border_width)
+ {
+ *result = HTTOPRIGHT;
+ return true;
+ }
+ //left border
+ if (x >= winrect.left && x < winrect.left + border_width)
+ {
+ *result = HTLEFT;
+ return true;
+ }
+ //right border
+ if (x < winrect.right && x >= winrect.right - border_width)
+ {
+ *result = HTRIGHT;
+ return true;
+ }
+
+ //bottom border
+ if (y < winrect.bottom && y >= winrect.bottom - border_width)
+ {
+ *result = HTBOTTOM;
+ return true;
+ }
+ //top border
+ if (y >= winrect.top && y < winrect.top + border_width)
+ {
+ *result = HTTOP;
+ return true;
+ }
+
+ return QWidget::nativeEvent(eventType, message, result);
+ }
+ }
+
+ return QWidget::nativeEvent(eventType, message, result);
+}
+
+void LoginWin::readCfg()
+{
+ QSettings setting("config/cfg.ini",QSettings::IniFormat);
+ QString names = setting.value("users/names").toString().trimmed();
+ QStringList nameList = names.split(";");
+
+ for(int i=0;icmbName->addItem(name);
+ }
+ }
+ QString curName = setting.value("users/current").toString().trimmed();
+ if(!curName.isEmpty()){
+ ui->cmbName->setCurrentText(curName);
+ }
+
+ QString check = setting.value("users/remember").toString();
+ if(check.toInt()==1){
+ ui->ckbRemember->setChecked(true);
+ }else{
+ ui->ckbRemember->setChecked(false);
+ }
+}
+
+void LoginWin::saveCfg()
+{
+ QSettings setting("config/cfg.ini",QSettings::IniFormat);
+
+ QString names;
+ QString curname = ui->cmbName->currentText();
+ foreach (const QString &name, nameMap.keys()) {
+ if(!ui->ckbRemember->isChecked() && name==curname){
+ continue;
+ }
+ if(!name.isEmpty()){
+ QString password = nameMap.value(name,"");
+ char base64[256] = {'\0'};
+ int len = 0;
+ base64_encode((const unsigned char *)password.toStdString().c_str(),strlen(password.toStdString().c_str()),(unsigned char *)base64,&len);
+ names += QString("%1:%2;").arg(name,base64);
+ }
+ }
+ setting.setValue("users/names",names);
+}
+
+void LoginWin::on_cmbName_currentTextChanged(const QString &arg1)
+{
+ ui->lePassword->setText(nameMap.value(arg1,""));
+}
+
diff --git a/src/cpp/main.cpp b/src/cpp/main.cpp
new file mode 100644
index 0000000..7cf00a5
--- /dev/null
+++ b/src/cpp/main.cpp
@@ -0,0 +1,24 @@
+#include "mainwindow.h"
+#include "loginWin.h"
+#include "sqlitedb.h"
+#include
+#include
+#include
+
+int main(int argc, char *argv[])
+{
+ QTextCodec *codec = QTextCodec::codecForName("GB2312");
+ QTextCodec::setCodecForLocale(codec);
+ QApplication a(argc, argv);
+ QFont font = a.font();
+ font.setFamily("Microsoft YaHei UI");
+ a.setFont(font);
+ LoginWin login;
+ login.show();
+ MainWindow w;
+ w.hide();
+ w.connect(&login,SIGNAL(loginSignal()),&w,SLOT(loginHandle()));
+
+ SQLITEDB::getInstance();
+ return a.exec();
+}
diff --git a/src/cpp/mainwindow.cpp b/src/cpp/mainwindow.cpp
new file mode 100644
index 0000000..92808d5
--- /dev/null
+++ b/src/cpp/mainwindow.cpp
@@ -0,0 +1,184 @@
+#include "mainwindow.h"
+#include "ui_mainwindow.h"
+#include "globalParams.h"
+#include "msgInputWin.h"
+#include
+#include
+#include
+#include
+#include
+
+MainWindow::MainWindow(QWidget *parent)
+ : QMainWindow(parent)
+ , ui(new Ui::MainWindow)
+{
+ ui->setupUi(this);
+ this->setWindowTitle("北斗用户机测试软件");
+ //this->setPalette(QPalette(QColor(255,255,255)));
+ //this->setPalette(QPalette(QColor(225,225,225)));
+ serialPortInit();
+
+ //状态栏显示当前用户
+ QGroupBox *userBox = new QGroupBox();
+ userBox->setStyleSheet("QGroupBox{border:none;}");
+ QHBoxLayout *userLayout = new QHBoxLayout(userBox);
+ userLayout->setMargin(0);
+ QLabel *lbUserSpaceLeft = new QLabel();
+ QLabel *lbUserSpaceRight = new QLabel();
+ lbUserSpaceLeft->setFixedWidth(10);
+ lbUserSpaceRight->setFixedWidth(10);
+ QLabel *lbUserId = new QLabel("当前用户:");
+ lbCurUserId = new QLabel();
+ lbCurUserId->setText(globalParams::curUserId);
+ userLayout->addWidget(lbUserSpaceLeft);
+ userLayout->addWidget(lbUserId);
+ userLayout->addWidget(lbCurUserId);
+ userLayout->addWidget(lbUserSpaceRight);
+ ui->statusbar->addWidget(userBox);
+ //状态栏显示接收比特数
+ QGroupBox *byteBox = new QGroupBox();
+ byteBox->setStyleSheet("QGroupBox{border:none;}");
+ QHBoxLayout *byteLayout = new QHBoxLayout(byteBox);
+ byteLayout->setMargin(0);
+ QLabel *lbByteSpaceLeft = new QLabel();
+ QLabel *lbByteSpaceMid1 = new QLabel();
+ QLabel *lbByteSpaceMid2 = new QLabel();
+ QLabel *lbByteSpaceMid3 = new QLabel();
+ QLabel *lbByteSpaceRight = new QLabel();
+ lbByteSpaceLeft->setFixedWidth(5);
+ lbByteSpaceMid1->setFixedWidth(5);
+ lbByteSpaceMid2->setFixedWidth(5);
+ lbByteSpaceMid3->setFixedWidth(5);
+ lbByteSpaceRight->setFixedWidth(5);
+
+ QLabel *lbSend = new QLabel("发送:");
+ QLabel *lbRecv = new QLabel("接收:");
+ QPushButton *btnClean = new QPushButton();
+ connect(btnClean,SIGNAL(clicked()),this,SLOT(btnCleanClickedHandle()),Qt::QueuedConnection);
+ lbComStatus = new QLabel();
+ lbComStatus->setFixedSize(16,16);
+ lbComStatus->setPixmap(QPixmap(":/images/circle_gray.png"));
+ lbComStatus->setScaledContents(true);
+ btnClean->setFixedSize(18,18);
+ btnClean->setAutoRepeat(true);
+ btnClean->setStyleSheet("QPushButton{background-image:url(:/images/delete_black.png);\
+ background-color:rgb(240,240,240);background-position:center;;border:none}" \
+ "QPushButton:hover{background-color:rgba(233,233,233,150);" \
+ "background-image:url(:/images/delete_red.png);border:none;}");
+ lbSendByte = new QLabel("0");
+ lbRecvByte = new QLabel("0");
+ byteLayout->addWidget(lbByteSpaceLeft);
+ byteLayout->addWidget(lbComStatus);
+ byteLayout->addWidget(lbByteSpaceMid1);
+ byteLayout->addWidget(lbSend);
+ byteLayout->addWidget(lbSendByte);
+ byteLayout->addWidget(lbByteSpaceMid2);
+ byteLayout->addWidget(lbRecv);
+ byteLayout->addWidget(lbRecvByte);
+ byteLayout->addWidget(lbByteSpaceMid3);
+ byteLayout->addWidget(btnClean);
+ byteLayout->addWidget(lbByteSpaceRight);
+ ui->statusbar->addWidget(byteBox);
+
+}
+
+MainWindow::~MainWindow()
+{
+ serial->close();
+ delete serial;
+ delete ui;
+}
+
+void MainWindow::receiverSerialDataHandle(const QByteArray &data)
+{
+ long long totalLen = lbRecvByte->text().toLongLong();
+ totalLen += data.size();
+ lbRecvByte->setText(QString::number(totalLen));
+ try {
+ leftData.append(data);
+ } catch (...) {
+ qDebug()<<"new data can't append to buf";
+ return;
+ }
+
+}
+
+void MainWindow::loginHandle()
+{
+ lbCurUserId->setText(globalParams::curUserId);
+ this->show();
+}
+
+void MainWindow::btnCleanClickedHandle()
+{
+ lbSendByte->setText("0");
+ lbRecvByte->setText("0");
+}
+
+void MainWindow::on_tbtnFresh_clicked()
+{
+ QString curCom = ui->cmbCom->currentText();
+ ui->cmbCom->clear();
+ QStringList nameList;
+ foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
+ {
+ nameList.append(info.portName());
+ }
+ std::sort(nameList.begin(), nameList.end(), [&](QString &s1, QString &s2)
+ {
+ if(s1.size()==s2.size()){
+ return s1cmbCom->addItems(nameList);
+ ui->cmbCom->setCurrentText(curCom);
+}
+
+void MainWindow::serialPortInit()
+{
+ serial = new SerialPort();
+ connect(serial,SIGNAL(recvSerialDataSignal(QByteArray)),this,SLOT(receiverSerialDataHandle(QByteArray)),Qt::QueuedConnection);
+ QStringList nameList;
+ foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
+ {
+ nameList.append(info.portName());
+ }
+
+ std::sort(nameList.begin(), nameList.end(), [&](QString &s1, QString &s2)
+ {
+ if(s1.size()==s2.size()){
+ return s1cmbCom->addItems(nameList);
+}
+
+void MainWindow::on_tbtnConnect_clicked()
+{
+ if(!bconnected){
+ serial->setSerialconfig(ui->cmbCom->currentText(),ui->cmbBaud->currentText().toInt());
+ if(serial->open()){
+ ui->tbtnConnect->setText("断开");
+ ui->tbtnConnect->setStyleSheet("color:red");
+ lbComStatus->setPixmap(QPixmap(":/images/circle_green.png"));
+ bconnected = true;
+ ui->cmbBaud->setEnabled(false);
+ ui->cmbCom->setEnabled(false);
+ ui->tbtnFresh->setEnabled(false);
+ }
+ }else{
+ ui->tbtnConnect->setText("连接");
+ ui->tbtnConnect->setStyleSheet("color:green");
+ lbComStatus->setPixmap(QPixmap(":/images/circle_gray.png"));
+ bconnected = false;
+ serial->close();
+ ui->cmbBaud->setEnabled(true);
+ ui->cmbCom->setEnabled(true);
+ ui->tbtnFresh->setEnabled(true);
+ }
+}
+
diff --git a/src/cpp/msgInputWin.cpp b/src/cpp/msgInputWin.cpp
new file mode 100644
index 0000000..f96aa2d
--- /dev/null
+++ b/src/cpp/msgInputWin.cpp
@@ -0,0 +1,91 @@
+#include "msgInputWin.h"
+#include "ui_msgInputWin.h"
+#include
+
+MsgInputWin::MsgInputWin(QWidget *parent)
+ : QWidget(parent)
+ , ui(new Ui::MsgInputWin)
+{
+ ui->setupUi(this);
+ msgModel = new QStandardItemModel(this);
+ msgSelect = new QItemSelectionModel(msgModel);
+ msgModel->setColumnCount(2);
+ ui->tableView->setModel(msgModel);
+ ui->tableView->setSelectionModel(msgSelect);
+ ui->tableView->horizontalHeader()->setVisible(false);
+ ui->tableView->verticalHeader()->setVisible(false);
+ ui->tableView->horizontalHeader()->setStretchLastSection(true);
+ ui->tableView->verticalHeader()->setSectionResizeMode(QHeaderView::Stretch);
+
+ QFont font = ui->tableView->font();
+ //font.setPointSize(10);
+ ui->tableView->setFont(font);
+
+ QList aItemList;
+ QStandardItem *aItem = new QStandardItem("单位");
+ aItem->setEditable(false);
+ font.setBold(true);
+ aItem->setFont(font);
+ aItem->setTextAlignment(Qt::AlignCenter);
+ aItemList.append(aItem);
+ aItem = new QStandardItem("");
+ aItemList.append(aItem);
+ msgModel->insertRow(msgModel->rowCount(),aItemList);
+
+ aItemList.clear();
+ aItem = new QStandardItem("班组");
+ aItem->setEditable(false);
+ aItem->setFont(font);
+ aItem->setTextAlignment(Qt::AlignCenter);
+ aItemList.append(aItem);
+ aItem = new QStandardItem("");
+ aItemList.append(aItem);
+ msgModel->insertRow(msgModel->rowCount(),aItemList);
+
+ aItemList.clear();
+ aItem = new QStandardItem("车型");
+ aItem->setEditable(false);
+ aItem->setFont(font);
+ aItem->setTextAlignment(Qt::AlignCenter);
+ aItemList.append(aItem);
+ aItem = new QStandardItem("");
+ aItemList.append(aItem);
+ msgModel->insertRow(msgModel->rowCount(),aItemList);
+ QComboBox *carType = new QComboBox();
+ ui->tableView->setIndexWidget(msgModel->index(msgModel->rowCount()-1,1),carType);
+
+ aItemList.clear();
+ aItem = new QStandardItem("北斗型号");
+ aItem->setEditable(false);
+ aItem->setFont(font);
+ aItem->setTextAlignment(Qt::AlignCenter);
+ aItemList.append(aItem);
+ aItem = new QStandardItem("");
+ aItemList.append(aItem);
+ msgModel->insertRow(msgModel->rowCount(),aItemList);
+
+ aItemList.clear();
+ aItem = new QStandardItem("北斗序列号");
+ aItem->setEditable(false);
+ aItem->setFont(font);
+ aItem->setTextAlignment(Qt::AlignCenter);
+ aItemList.append(aItem);
+ aItem = new QStandardItem("");
+ aItemList.append(aItem);
+ msgModel->insertRow(msgModel->rowCount(),aItemList);
+
+ aItemList.clear();
+ aItem = new QStandardItem("生产单位");
+ aItem->setEditable(false);
+ aItem->setFont(font);
+ aItem->setTextAlignment(Qt::AlignCenter);
+ aItemList.append(aItem);
+ aItem = new QStandardItem("");
+ aItemList.append(aItem);
+ msgModel->insertRow(msgModel->rowCount(),aItemList);
+}
+
+MsgInputWin::~MsgInputWin()
+{
+ delete ui;
+}
diff --git a/src/cpp/posTestWin.cpp b/src/cpp/posTestWin.cpp
new file mode 100644
index 0000000..8522a77
--- /dev/null
+++ b/src/cpp/posTestWin.cpp
@@ -0,0 +1,263 @@
+#include "posTestWin.h"
+#include "ui_posTestWin.h"
+#include
+#include
+
+PosTestWin::PosTestWin(QWidget *parent)
+ : QWidget(parent)
+ , ui(new Ui::PosTestWin)
+{
+ ui->setupUi(this);
+
+ ui->gpbRdTest->setVisible(false);
+ ui->gpbRnTest->setVisible(true);
+ ui->gpbRnSignal->setVisible(false);
+
+ rnModel = new QStandardItemModel(this);
+ rnSelect = new QItemSelectionModel(rnModel);
+ rnModel->setColumnCount(10);
+ ui->tableViewRn->setModel(rnModel);
+ ui->tableViewRn->setSelectionModel(rnSelect);
+ ui->tableViewRn->horizontalHeader()->setVisible(false);
+ ui->tableViewRn->verticalHeader()->setVisible(false);
+ //ui->tableViewRn->horizontalHeader()->setStretchLastSection(true);
+ ui->tableViewRn->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
+ ui->tableViewRn->verticalHeader()->setSectionResizeMode(QHeaderView::Stretch);
+ ui->tableViewRn->setWordWrap(true);
+
+
+ rdModel = new QStandardItemModel(this);
+ rdSelect = new QItemSelectionModel(rdModel);
+ rdModel->setColumnCount(6);
+ ui->tableViewRd->setModel(rdModel);
+ ui->tableViewRd->setSelectionModel(rdSelect);
+ ui->tableViewRd->horizontalHeader()->setVisible(false);
+ ui->tableViewRd->verticalHeader()->setVisible(false);
+ //ui->tableViewRn->horizontalHeader()->setStretchLastSection(true);
+ ui->tableViewRd->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
+ ui->tableViewRd->verticalHeader()->setSectionResizeMode(QHeaderView::Stretch);
+ ui->tableViewRd->setWordWrap(true);
+
+ QFont font = ui->tableViewRn->font();
+ font.setBold(true);
+ QFont smallFont = font;
+ smallFont.setPointSize(font.pointSize()-2);
+ smallFont.setBold(false);
+
+ QList aItemList;
+ for(int r=0;r<9;r++){
+ aItemList.clear();
+ for(int c=0;c<11;c++){
+ QStandardItem *aItem = new QStandardItem("");
+ aItem->setTextAlignment(Qt::AlignCenter);
+ aItem->setFont(smallFont);
+ aItemList.append(aItem);
+ }
+ rnModel->insertRow(rnModel->rowCount(),aItemList);
+ ui->tableViewRn->setSpan(r,0,1,2);
+ }
+
+ for(int r=0;r<6;r++){
+ aItemList.clear();
+ for(int c=0;c<6;c++){
+ QStandardItem *aItem = new QStandardItem("");
+ aItem->setTextAlignment(Qt::AlignCenter);
+ aItem->setFont(smallFont);
+ aItemList.append(aItem);
+ }
+ rdModel->insertRow(rdModel->rowCount(),aItemList);
+ ui->tableViewRd->setSpan(r,0,1,2);
+ }
+
+ rnModel->item(0,0)->setText("定位模式");
+ rnModel->item(0,0)->setFont(font);
+ ui->tableViewRn->setSpan(0,0,2,2);
+
+ smallFont.setPointSize(font.pointSize()-1);
+ smallFont.setBold(true);
+ QPushButton *b1cBtn = new QPushButton("B1频点C码定位");
+ b1cBtn->setSizePolicy(QSizePolicy::Preferred,QSizePolicy::Preferred);
+ b1cBtn->setFont(smallFont);
+ b1cBtn->setDefault(true);
+ ui->tableViewRn->setIndexWidget(rnModel->index(2,0),b1cBtn);
+
+ QPushButton *b3cBtn = new QPushButton("B3频点C码定位");
+ b3cBtn->setSizePolicy(QSizePolicy::Preferred,QSizePolicy::Preferred);
+ b3cBtn->setFont(smallFont);
+ b3cBtn->setDefault(true);
+ ui->tableViewRn->setIndexWidget(rnModel->index(3,0),b3cBtn);
+
+ QPushButton *b1b3cBtn = new QPushButton("B1+B3频点C码定位");
+ b1b3cBtn->setSizePolicy(QSizePolicy::Preferred,QSizePolicy::Preferred);
+ b1b3cBtn->setFont(smallFont);
+ b1b3cBtn->setDefault(true);
+ ui->tableViewRn->setIndexWidget(rnModel->index(4,0),b1b3cBtn);
+
+ QPushButton *gpsBtn = new QPushButton("GPS频点C码定位");
+ gpsBtn->setSizePolicy(QSizePolicy::Preferred,QSizePolicy::Preferred);
+ gpsBtn->setFont(smallFont);
+ gpsBtn->setDefault(true);
+ ui->tableViewRn->setIndexWidget(rnModel->index(5,0),gpsBtn);
+
+ QPushButton *b1pBtn = new QPushButton("B1频点P码定位");
+ b1pBtn->setSizePolicy(QSizePolicy::Preferred,QSizePolicy::Preferred);
+ b1pBtn->setFont(smallFont);
+ b1pBtn->setDefault(true);
+ ui->tableViewRn->setIndexWidget(rnModel->index(6,0),b1pBtn);
+
+ QPushButton *b3pBtn = new QPushButton("B3频点P码定位");
+ b3pBtn->setSizePolicy(QSizePolicy::Preferred,QSizePolicy::Preferred);
+ b3pBtn->setFont(smallFont);
+ b3pBtn->setDefault(true);
+ ui->tableViewRn->setIndexWidget(rnModel->index(7,0),b3pBtn);
+
+ QPushButton *compatibleBtn = new QPushButton("兼容定位");
+ compatibleBtn->setSizePolicy(QSizePolicy::Preferred,QSizePolicy::Preferred);
+ compatibleBtn->setFont(smallFont);
+ compatibleBtn->setDefault(true);
+ ui->tableViewRn->setIndexWidget(rnModel->index(8,0),compatibleBtn);
+
+ rnModel->item(0,2)->setText("定位结果");
+ rnModel->item(0,2)->setFont(font);
+ ui->tableViewRn->setSpan(0,2,1,7);
+
+ rnModel->item(1,2)->setText("时间");
+ rnModel->item(1,2)->setFont(font);
+
+ rnModel->item(1,3)->setText("经度");
+ rnModel->item(1,3)->setFont(font);
+
+ rnModel->item(1,4)->setText("纬度");
+ rnModel->item(1,4)->setFont(font);
+
+ rnModel->item(1,5)->setText("高程");
+ rnModel->item(1,5)->setFont(font);
+
+ rnModel->item(1,6)->setText("PDOP");
+ rnModel->item(1,6)->setFont(font);
+
+ rnModel->item(1,7)->setText("VDOP");
+ rnModel->item(1,7)->setFont(font);
+
+ rnModel->item(1,8)->setText("HDOP");
+ rnModel->item(1,8)->setFont(font);
+
+ rnModel->item(0,9)->setText("卫星状态");
+ rnModel->item(0,9)->setFont(font);
+ ui->tableViewRn->setSpan(0,9,1,2);
+
+ rnModel->item(1,9)->setText("有效卫星");
+ rnModel->item(1,9)->setFont(font);
+
+ rnModel->item(1,10)->setText("信号");
+ rnModel->item(1,10)->setFont(font);
+
+ QPushButton *viewB1CBtn = new QPushButton("查看");
+ viewB1CBtn->setSizePolicy(QSizePolicy::Preferred,QSizePolicy::Preferred);
+ viewB1CBtn->setFont(smallFont);
+ viewB1CBtn->setDefault(true);
+ ui->tableViewRn->setIndexWidget(rnModel->index(2,10),viewB1CBtn);
+
+ QPushButton *viewB3CBtn = new QPushButton("查看");
+ viewB3CBtn->setSizePolicy(QSizePolicy::Preferred,QSizePolicy::Preferred);
+ viewB3CBtn->setFont(smallFont);
+ viewB3CBtn->setDefault(true);
+ ui->tableViewRn->setIndexWidget(rnModel->index(3,10),viewB3CBtn);
+
+ QPushButton *viewB1B3CBtn = new QPushButton("查看");
+ viewB1B3CBtn->setSizePolicy(QSizePolicy::Preferred,QSizePolicy::Preferred);
+ viewB1B3CBtn->setFont(smallFont);
+ viewB1B3CBtn->setDefault(true);
+ ui->tableViewRn->setIndexWidget(rnModel->index(4,10),viewB1B3CBtn);
+
+ QPushButton *viewGPSBtn = new QPushButton("查看");
+ viewGPSBtn->setSizePolicy(QSizePolicy::Preferred,QSizePolicy::Preferred);
+ viewGPSBtn->setFont(smallFont);
+ viewGPSBtn->setDefault(true);
+ ui->tableViewRn->setIndexWidget(rnModel->index(5,10),viewGPSBtn);
+
+ QPushButton *viewB1PBtn = new QPushButton("查看");
+ viewB1PBtn->setSizePolicy(QSizePolicy::Preferred,QSizePolicy::Preferred);
+ viewB1PBtn->setFont(smallFont);
+ viewB1PBtn->setDefault(true);
+ ui->tableViewRn->setIndexWidget(rnModel->index(6,10),viewB1PBtn);
+
+ QPushButton *viewB3PBtn = new QPushButton("查看");
+ viewB3PBtn->setSizePolicy(QSizePolicy::Preferred,QSizePolicy::Preferred);
+ viewB3PBtn->setFont(smallFont);
+ viewB3PBtn->setDefault(true);
+ ui->tableViewRn->setIndexWidget(rnModel->index(7,10),viewB3PBtn);
+
+ QPushButton *viewCompatibleBtn = new QPushButton("查看");
+ viewCompatibleBtn->setSizePolicy(QSizePolicy::Preferred,QSizePolicy::Preferred);
+ viewCompatibleBtn->setFont(smallFont);
+ viewCompatibleBtn->setDefault(true);
+ ui->tableViewRn->setIndexWidget(rnModel->index(8,10),viewCompatibleBtn);
+
+
+
+ rdModel->item(0,0)->setText("定位模式");
+ rdModel->item(0,0)->setFont(font);
+ ui->tableViewRd->setSpan(0,0,2,2);
+
+ QPushButton *rdPosBtn = new QPushButton("RDSS单次定位");
+ rdPosBtn->setSizePolicy(QSizePolicy::Preferred,QSizePolicy::Preferred);
+ rdPosBtn->setFont(font);
+ rdPosBtn->setDefault(true);
+ ui->tableViewRd->setIndexWidget(rdModel->index(2,0),rdPosBtn);
+
+ QPushButton *rdContinuePosBtn = new QPushButton("RDSS连续定位");
+ rdContinuePosBtn->setSizePolicy(QSizePolicy::Preferred,QSizePolicy::Preferred);
+ rdContinuePosBtn->setFont(font);
+ rdContinuePosBtn->setDefault(true);
+ ui->tableViewRd->setIndexWidget(rdModel->index(3,0),rdContinuePosBtn);
+
+ QPushButton *rdEmergencyPosBtn = new QPushButton("RDSS紧急定位");
+ rdEmergencyPosBtn->setSizePolicy(QSizePolicy::Preferred,QSizePolicy::Preferred);
+ rdEmergencyPosBtn->setFont(font);
+ rdEmergencyPosBtn->setDefault(true);
+ ui->tableViewRd->setIndexWidget(rdModel->index(4,0),rdEmergencyPosBtn);
+
+ QPushButton *rdStopContinuePosBtn = new QPushButton("结束RDSS连续定位");
+ rdStopContinuePosBtn->setSizePolicy(QSizePolicy::Preferred,QSizePolicy::Preferred);
+ rdStopContinuePosBtn->setFont(font);
+ rdStopContinuePosBtn->setDefault(true);
+ ui->tableViewRd->setIndexWidget(rdModel->index(5,0),rdStopContinuePosBtn);
+
+ rdModel->item(0,2)->setText("定位结果");
+ rdModel->item(0,2)->setFont(font);
+ ui->tableViewRd->setSpan(0,2,1,4);
+
+ rdModel->item(1,2)->setText("时间");
+ rdModel->item(1,2)->setFont(font);
+
+ rdModel->item(1,3)->setText("经度");
+ rdModel->item(1,3)->setFont(font);
+
+ rdModel->item(1,4)->setText("纬度");
+ rdModel->item(1,4)->setFont(font);
+
+ rdModel->item(1,5)->setText("高程");
+ rdModel->item(1,5)->setFont(font);
+}
+
+PosTestWin::~PosTestWin()
+{
+ delete ui;
+}
+
+void PosTestWin::on_btnRdTest_clicked()
+{
+ ui->gpbRdTest->setVisible(true);
+ ui->gpbRnTest->setVisible(false);
+ ui->gpbRnSignal->setVisible(false);
+}
+
+
+void PosTestWin::on_btnRnTest_clicked()
+{
+ ui->gpbRdTest->setVisible(false);
+ ui->gpbRnTest->setVisible(true);
+ ui->gpbRnSignal->setVisible(false);
+}
+
diff --git a/src/cpp/precisionTestWin.cpp b/src/cpp/precisionTestWin.cpp
new file mode 100644
index 0000000..9f5bca6
--- /dev/null
+++ b/src/cpp/precisionTestWin.cpp
@@ -0,0 +1,60 @@
+#include "precisionTestWin.h"
+#include "ui_precisionTestWin.h"
+#include
+#include
+
+PrecisionTestWin::PrecisionTestWin(QWidget *parent)
+ : QWidget(parent)
+ , ui(new Ui::PrecisionTestWin)
+{
+ ui->setupUi(this);
+ QFont font = ui->cmbFormat->font();
+ ui->cmbFormat->setCurrentIndex(0);
+}
+
+PrecisionTestWin::~PrecisionTestWin()
+{
+ delete ui;
+}
+
+void PrecisionTestWin::on_cmbFormat_currentIndexChanged(int index)
+{
+ if(index==0){
+ ui->lbLonDeg->setVisible(false);
+ ui->leLonMinute->setVisible(false);
+ ui->lbLonMinute->setVisible(false);
+ ui->leLonSec->setVisible(false);
+ ui->lbLonSec->setVisible(false);
+
+ ui->lbLatDeg->setVisible(false);
+ ui->leLatMinute->setVisible(false);
+ ui->lbLatMinute->setVisible(false);
+ ui->leLatSec->setVisible(false);
+ ui->lbLatSec->setVisible(false);
+ }else if(index==1){
+ ui->lbLonDeg->setVisible(true);
+ ui->leLonMinute->setVisible(false);
+ ui->lbLonMinute->setVisible(false);
+ ui->leLonSec->setVisible(false);
+ ui->lbLonSec->setVisible(false);
+
+ ui->lbLatDeg->setVisible(true);
+ ui->leLatMinute->setVisible(false);
+ ui->lbLatMinute->setVisible(false);
+ ui->leLatSec->setVisible(false);
+ ui->lbLatSec->setVisible(false);
+ }else if(index==2){
+ ui->lbLonDeg->setVisible(true);
+ ui->leLonMinute->setVisible(true);
+ ui->lbLonMinute->setVisible(true);
+ ui->leLonSec->setVisible(true);
+ ui->lbLonSec->setVisible(true);
+
+ ui->lbLatDeg->setVisible(true);
+ ui->leLatMinute->setVisible(true);
+ ui->lbLatMinute->setVisible(true);
+ ui->leLatSec->setVisible(true);
+ ui->lbLatSec->setVisible(true);
+ }
+}
+
diff --git a/src/cpp/qcustomplot.cpp b/src/cpp/qcustomplot.cpp
new file mode 100644
index 0000000..72b5bfb
--- /dev/null
+++ b/src/cpp/qcustomplot.cpp
@@ -0,0 +1,35529 @@
+/***************************************************************************
+** **
+** QCustomPlot, an easy to use, modern plotting widget for Qt **
+** Copyright (C) 2011-2022 Emanuel Eichhammer **
+** **
+** This program is free software: you can redistribute it and/or modify **
+** it under the terms of the GNU General Public License as published by **
+** the Free Software Foundation, either version 3 of the License, or **
+** (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, **
+** but WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program. If not, see http://www.gnu.org/licenses/. **
+** **
+****************************************************************************
+** Author: Emanuel Eichhammer **
+** Website/Contact: https://www.qcustomplot.com/ **
+** Date: 06.11.22 **
+** Version: 2.1.1 **
+****************************************************************************/
+
+#include "qcustomplot.h"
+
+
+/* including file 'src/vector2d.cpp' */
+/* modified 2022-11-06T12:45:56, size 7973 */
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//////////////////// QCPVector2D
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/*! \class QCPVector2D
+ \brief Represents two doubles as a mathematical 2D vector
+
+ This class acts as a replacement for QVector2D with the advantage of double precision instead of
+ single, and some convenience methods tailored for the QCustomPlot library.
+*/
+
+/* start documentation of inline functions */
+
+/*! \fn void QCPVector2D::setX(double x)
+
+ Sets the x coordinate of this vector to \a x.
+
+ \see setY
+*/
+
+/*! \fn void QCPVector2D::setY(double y)
+
+ Sets the y coordinate of this vector to \a y.
+
+ \see setX
+*/
+
+/*! \fn double QCPVector2D::length() const
+
+ Returns the length of this vector.
+
+ \see lengthSquared
+*/
+
+/*! \fn double QCPVector2D::lengthSquared() const
+
+ Returns the squared length of this vector. In some situations, e.g. when just trying to find the
+ shortest vector of a group, this is faster than calculating \ref length, because it avoids
+ calculation of a square root.
+
+ \see length
+*/
+
+/*! \fn double QCPVector2D::angle() const
+
+ Returns the angle of the vector in radians. The angle is measured between the positive x line and
+ the vector, counter-clockwise in a mathematical coordinate system (y axis upwards positive). In
+ screen/widget coordinates where the y axis is inverted, the angle appears clockwise.
+*/
+
+/*! \fn QPoint QCPVector2D::toPoint() const
+
+ Returns a QPoint which has the x and y coordinates of this vector, truncating any floating point
+ information.
+
+ \see toPointF
+*/
+
+/*! \fn QPointF QCPVector2D::toPointF() const
+
+ Returns a QPointF which has the x and y coordinates of this vector.
+
+ \see toPoint
+*/
+
+/*! \fn bool QCPVector2D::isNull() const
+
+ Returns whether this vector is null. A vector is null if \c qIsNull returns true for both x and y
+ coordinates, i.e. if both are binary equal to 0.
+*/
+
+/*! \fn QCPVector2D QCPVector2D::perpendicular() const
+
+ Returns a vector perpendicular to this vector, with the same length.
+*/
+
+/*! \fn double QCPVector2D::dot() const
+
+ Returns the dot/scalar product of this vector with the specified vector \a vec.
+*/
+
+/* end documentation of inline functions */
+
+/*!
+ Creates a QCPVector2D object and initializes the x and y coordinates to 0.
+*/
+QCPVector2D::QCPVector2D() :
+ mX(0),
+ mY(0)
+{
+}
+
+/*!
+ Creates a QCPVector2D object and initializes the \a x and \a y coordinates with the specified
+ values.
+*/
+QCPVector2D::QCPVector2D(double x, double y) :
+ mX(x),
+ mY(y)
+{
+}
+
+/*!
+ Creates a QCPVector2D object and initializes the x and y coordinates respective coordinates of
+ the specified \a point.
+*/
+QCPVector2D::QCPVector2D(const QPoint &point) :
+ mX(point.x()),
+ mY(point.y())
+{
+}
+
+/*!
+ Creates a QCPVector2D object and initializes the x and y coordinates respective coordinates of
+ the specified \a point.
+*/
+QCPVector2D::QCPVector2D(const QPointF &point) :
+ mX(point.x()),
+ mY(point.y())
+{
+}
+
+/*!
+ Normalizes this vector. After this operation, the length of the vector is equal to 1.
+
+ If the vector has both entries set to zero, this method does nothing.
+
+ \see normalized, length, lengthSquared
+*/
+void QCPVector2D::normalize()
+{
+ if (mX == 0.0 && mY == 0.0) return;
+ const double lenInv = 1.0/length();
+ mX *= lenInv;
+ mY *= lenInv;
+}
+
+/*!
+ Returns a normalized version of this vector. The length of the returned vector is equal to 1.
+
+ If the vector has both entries set to zero, this method returns the vector unmodified.
+
+ \see normalize, length, lengthSquared
+*/
+QCPVector2D QCPVector2D::normalized() const
+{
+ if (mX == 0.0 && mY == 0.0) return *this;
+ const double lenInv = 1.0/length();
+ return QCPVector2D(mX*lenInv, mY*lenInv);
+}
+
+/*! \overload
+
+ Returns the squared shortest distance of this vector (interpreted as a point) to the finite line
+ segment given by \a start and \a end.
+
+ \see distanceToStraightLine
+*/
+double QCPVector2D::distanceSquaredToLine(const QCPVector2D &start, const QCPVector2D &end) const
+{
+ const QCPVector2D v(end-start);
+ const double vLengthSqr = v.lengthSquared();
+ if (!qFuzzyIsNull(vLengthSqr))
+ {
+ const double mu = v.dot(*this-start)/vLengthSqr;
+ if (mu < 0)
+ return (*this-start).lengthSquared();
+ else if (mu > 1)
+ return (*this-end).lengthSquared();
+ else
+ return ((start + mu*v)-*this).lengthSquared();
+ } else
+ return (*this-start).lengthSquared();
+}
+
+/*! \overload
+
+ Returns the squared shortest distance of this vector (interpreted as a point) to the finite line
+ segment given by \a line.
+
+ \see distanceToStraightLine
+*/
+double QCPVector2D::distanceSquaredToLine(const QLineF &line) const
+{
+ return distanceSquaredToLine(QCPVector2D(line.p1()), QCPVector2D(line.p2()));
+}
+
+/*!
+ Returns the shortest distance of this vector (interpreted as a point) to the infinite straight
+ line given by a \a base point and a \a direction vector.
+
+ \see distanceSquaredToLine
+*/
+double QCPVector2D::distanceToStraightLine(const QCPVector2D &base, const QCPVector2D &direction) const
+{
+ return qAbs((*this-base).dot(direction.perpendicular()))/direction.length();
+}
+
+/*!
+ Scales this vector by the given \a factor, i.e. the x and y components are multiplied by \a
+ factor.
+*/
+QCPVector2D &QCPVector2D::operator*=(double factor)
+{
+ mX *= factor;
+ mY *= factor;
+ return *this;
+}
+
+/*!
+ Scales this vector by the given \a divisor, i.e. the x and y components are divided by \a
+ divisor.
+*/
+QCPVector2D &QCPVector2D::operator/=(double divisor)
+{
+ mX /= divisor;
+ mY /= divisor;
+ return *this;
+}
+
+/*!
+ Adds the given \a vector to this vector component-wise.
+*/
+QCPVector2D &QCPVector2D::operator+=(const QCPVector2D &vector)
+{
+ mX += vector.mX;
+ mY += vector.mY;
+ return *this;
+}
+
+/*!
+ subtracts the given \a vector from this vector component-wise.
+*/
+QCPVector2D &QCPVector2D::operator-=(const QCPVector2D &vector)
+{
+ mX -= vector.mX;
+ mY -= vector.mY;
+ return *this;
+}
+/* end of 'src/vector2d.cpp' */
+
+
+/* including file 'src/painter.cpp' */
+/* modified 2022-11-06T12:45:56, size 8656 */
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//////////////////// QCPPainter
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/*! \class QCPPainter
+ \brief QPainter subclass used internally
+
+ This QPainter subclass is used to provide some extended functionality e.g. for tweaking position
+ consistency between antialiased and non-antialiased painting. Further it provides workarounds
+ for QPainter quirks.
+
+ \warning This class intentionally hides non-virtual functions of QPainter, e.g. setPen, save and
+ restore. So while it is possible to pass a QCPPainter instance to a function that expects a
+ QPainter pointer, some of the workarounds and tweaks will be unavailable to the function (because
+ it will call the base class implementations of the functions actually hidden by QCPPainter).
+*/
+
+/*!
+ Creates a new QCPPainter instance and sets default values
+*/
+QCPPainter::QCPPainter() :
+ mModes(pmDefault),
+ mIsAntialiasing(false)
+{
+ // don't setRenderHint(QPainter::NonCosmeticDefautPen) here, because painter isn't active yet and
+ // a call to begin() will follow
+}
+
+/*!
+ Creates a new QCPPainter instance on the specified paint \a device and sets default values. Just
+ like the analogous QPainter constructor, begins painting on \a device immediately.
+
+ Like \ref begin, this method sets QPainter::NonCosmeticDefaultPen in Qt versions before Qt5.
+*/
+QCPPainter::QCPPainter(QPaintDevice *device) :
+ QPainter(device),
+ mModes(pmDefault),
+ mIsAntialiasing(false)
+{
+#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) // before Qt5, default pens used to be cosmetic if NonCosmeticDefaultPen flag isn't set. So we set it to get consistency across Qt versions.
+ if (isActive())
+ setRenderHint(QPainter::NonCosmeticDefaultPen);
+#endif
+}
+
+/*!
+ Sets the pen of the painter and applies certain fixes to it, depending on the mode of this
+ QCPPainter.
+
+ \note this function hides the non-virtual base class implementation.
+*/
+void QCPPainter::setPen(const QPen &pen)
+{
+ QPainter::setPen(pen);
+ if (mModes.testFlag(pmNonCosmetic))
+ makeNonCosmetic();
+}
+
+/*! \overload
+
+ Sets the pen (by color) of the painter and applies certain fixes to it, depending on the mode of
+ this QCPPainter.
+
+ \note this function hides the non-virtual base class implementation.
+*/
+void QCPPainter::setPen(const QColor &color)
+{
+ QPainter::setPen(color);
+ if (mModes.testFlag(pmNonCosmetic))
+ makeNonCosmetic();
+}
+
+/*! \overload
+
+ Sets the pen (by style) of the painter and applies certain fixes to it, depending on the mode of
+ this QCPPainter.
+
+ \note this function hides the non-virtual base class implementation.
+*/
+void QCPPainter::setPen(Qt::PenStyle penStyle)
+{
+ QPainter::setPen(penStyle);
+ if (mModes.testFlag(pmNonCosmetic))
+ makeNonCosmetic();
+}
+
+/*! \overload
+
+ Works around a Qt bug introduced with Qt 4.8 which makes drawing QLineF unpredictable when
+ antialiasing is disabled. Thus when antialiasing is disabled, it rounds the \a line to
+ integer coordinates and then passes it to the original drawLine.
+
+ \note this function hides the non-virtual base class implementation.
+*/
+void QCPPainter::drawLine(const QLineF &line)
+{
+ if (mIsAntialiasing || mModes.testFlag(pmVectorized))
+ QPainter::drawLine(line);
+ else
+ QPainter::drawLine(line.toLine());
+}
+
+/*!
+ Sets whether painting uses antialiasing or not. Use this method instead of using setRenderHint
+ with QPainter::Antialiasing directly, as it allows QCPPainter to regain pixel exactness between
+ antialiased and non-antialiased painting (Since Qt < 5.0 uses slightly different coordinate systems for
+ AA/Non-AA painting).
+*/
+void QCPPainter::setAntialiasing(bool enabled)
+{
+ setRenderHint(QPainter::Antialiasing, enabled);
+ if (mIsAntialiasing != enabled)
+ {
+ mIsAntialiasing = enabled;
+ if (!mModes.testFlag(pmVectorized)) // antialiasing half-pixel shift only needed for rasterized outputs
+ {
+ if (mIsAntialiasing)
+ translate(0.5, 0.5);
+ else
+ translate(-0.5, -0.5);
+ }
+ }
+}
+
+/*!
+ Sets the mode of the painter. This controls whether the painter shall adjust its
+ fixes/workarounds optimized for certain output devices.
+*/
+void QCPPainter::setModes(QCPPainter::PainterModes modes)
+{
+ mModes = modes;
+}
+
+/*!
+ Sets the QPainter::NonCosmeticDefaultPen in Qt versions before Qt5 after beginning painting on \a
+ device. This is necessary to get cosmetic pen consistency across Qt versions, because since Qt5,
+ all pens are non-cosmetic by default, and in Qt4 this render hint must be set to get that
+ behaviour.
+
+ The Constructor \ref QCPPainter(QPaintDevice *device) which directly starts painting also sets
+ the render hint as appropriate.
+
+ \note this function hides the non-virtual base class implementation.
+*/
+bool QCPPainter::begin(QPaintDevice *device)
+{
+ bool result = QPainter::begin(device);
+#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) // before Qt5, default pens used to be cosmetic if NonCosmeticDefaultPen flag isn't set. So we set it to get consistency across Qt versions.
+ if (result)
+ setRenderHint(QPainter::NonCosmeticDefaultPen);
+#endif
+ return result;
+}
+
+/*! \overload
+
+ Sets the mode of the painter. This controls whether the painter shall adjust its
+ fixes/workarounds optimized for certain output devices.
+*/
+void QCPPainter::setMode(QCPPainter::PainterMode mode, bool enabled)
+{
+ if (!enabled && mModes.testFlag(mode))
+ mModes &= ~mode;
+ else if (enabled && !mModes.testFlag(mode))
+ mModes |= mode;
+}
+
+/*!
+ Saves the painter (see QPainter::save). Since QCPPainter adds some new internal state to
+ QPainter, the save/restore functions are reimplemented to also save/restore those members.
+
+ \note this function hides the non-virtual base class implementation.
+
+ \see restore
+*/
+void QCPPainter::save()
+{
+ mAntialiasingStack.push(mIsAntialiasing);
+ QPainter::save();
+}
+
+/*!
+ Restores the painter (see QPainter::restore). Since QCPPainter adds some new internal state to
+ QPainter, the save/restore functions are reimplemented to also save/restore those members.
+
+ \note this function hides the non-virtual base class implementation.
+
+ \see save
+*/
+void QCPPainter::restore()
+{
+ if (!mAntialiasingStack.isEmpty())
+ mIsAntialiasing = mAntialiasingStack.pop();
+ else
+ qDebug() << Q_FUNC_INFO << "Unbalanced save/restore";
+ QPainter::restore();
+}
+
+/*!
+ Changes the pen width to 1 if it currently is 0. This function is called in the \ref setPen
+ overrides when the \ref pmNonCosmetic mode is set.
+*/
+void QCPPainter::makeNonCosmetic()
+{
+ if (qFuzzyIsNull(pen().widthF()))
+ {
+ QPen p = pen();
+ p.setWidth(1);
+ QPainter::setPen(p);
+ }
+}
+/* end of 'src/painter.cpp' */
+
+
+/* including file 'src/paintbuffer.cpp' */
+/* modified 2022-11-06T12:45:56, size 18915 */
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//////////////////// QCPAbstractPaintBuffer
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/*! \class QCPAbstractPaintBuffer
+ \brief The abstract base class for paint buffers, which define the rendering backend
+
+ This abstract base class defines the basic interface that a paint buffer needs to provide in
+ order to be usable by QCustomPlot.
+
+ A paint buffer manages both a surface to draw onto, and the matching paint device. The size of
+ the surface can be changed via \ref setSize. External classes (\ref QCustomPlot and \ref
+ QCPLayer) request a painter via \ref startPainting and then perform the draw calls. Once the
+ painting is complete, \ref donePainting is called, so the paint buffer implementation can do
+ clean up if necessary. Before rendering a frame, each paint buffer is usually filled with a color
+ using \ref clear (usually the color is \c Qt::transparent), to remove the contents of the
+ previous frame.
+
+ The simplest paint buffer implementation is \ref QCPPaintBufferPixmap which allows regular
+ software rendering via the raster engine. Hardware accelerated rendering via pixel buffers and
+ frame buffer objects is provided by \ref QCPPaintBufferGlPbuffer and \ref QCPPaintBufferGlFbo.
+ They are used automatically if \ref QCustomPlot::setOpenGl is enabled.
+*/
+
+/* start documentation of pure virtual functions */
+
+/*! \fn virtual QCPPainter *QCPAbstractPaintBuffer::startPainting() = 0
+
+ Returns a \ref QCPPainter which is ready to draw to this buffer. The ownership and thus the
+ responsibility to delete the painter after the painting operations are complete is given to the
+ caller of this method.
+
+ Once you are done using the painter, delete the painter and call \ref donePainting.
+
+ While a painter generated with this method is active, you must not call \ref setSize, \ref
+ setDevicePixelRatio or \ref clear.
+
+ This method may return 0, if a painter couldn't be activated on the buffer. This usually
+ indicates a problem with the respective painting backend.
+*/
+
+/*! \fn virtual void QCPAbstractPaintBuffer::draw(QCPPainter *painter) const = 0
+
+ Draws the contents of this buffer with the provided \a painter. This is the method that is used
+ to finally join all paint buffers and draw them onto the screen.
+*/
+
+/*! \fn virtual void QCPAbstractPaintBuffer::clear(const QColor &color) = 0
+
+ Fills the entire buffer with the provided \a color. To have an empty transparent buffer, use the
+ named color \c Qt::transparent.
+
+ This method must not be called if there is currently a painter (acquired with \ref startPainting)
+ active.
+*/
+
+/*! \fn virtual void QCPAbstractPaintBuffer::reallocateBuffer() = 0
+
+ Reallocates the internal buffer with the currently configured size (\ref setSize) and device
+ pixel ratio, if applicable (\ref setDevicePixelRatio). It is called as soon as any of those
+ properties are changed on this paint buffer.
+
+ \note Subclasses of \ref QCPAbstractPaintBuffer must call their reimplementation of this method
+ in their constructor, to perform the first allocation (this can not be done by the base class
+ because calling pure virtual methods in base class constructors is not possible).
+*/
+
+/* end documentation of pure virtual functions */
+/* start documentation of inline functions */
+
+/*! \fn virtual void QCPAbstractPaintBuffer::donePainting()
+
+ If you have acquired a \ref QCPPainter to paint onto this paint buffer via \ref startPainting,
+ call this method as soon as you are done with the painting operations and have deleted the
+ painter.
+
+ paint buffer subclasses may use this method to perform any type of cleanup that is necessary. The
+ default implementation does nothing.
+*/
+
+/* end documentation of inline functions */
+
+/*!
+ Creates a paint buffer and initializes it with the provided \a size and \a devicePixelRatio.
+
+ Subclasses must call their \ref reallocateBuffer implementation in their respective constructors.
+*/
+QCPAbstractPaintBuffer::QCPAbstractPaintBuffer(const QSize &size, double devicePixelRatio) :
+ mSize(size),
+ mDevicePixelRatio(devicePixelRatio),
+ mInvalidated(true)
+{
+}
+
+QCPAbstractPaintBuffer::~QCPAbstractPaintBuffer()
+{
+}
+
+/*!
+ Sets the paint buffer size.
+
+ The buffer is reallocated (by calling \ref reallocateBuffer), so any painters that were obtained
+ by \ref startPainting are invalidated and must not be used after calling this method.
+
+ If \a size is already the current buffer size, this method does nothing.
+*/
+void QCPAbstractPaintBuffer::setSize(const QSize &size)
+{
+ if (mSize != size)
+ {
+ mSize = size;
+ reallocateBuffer();
+ }
+}
+
+/*!
+ Sets the invalidated flag to \a invalidated.
+
+ This mechanism is used internally in conjunction with isolated replotting of \ref QCPLayer
+ instances (in \ref QCPLayer::lmBuffered mode). If \ref QCPLayer::replot is called on a buffered
+ layer, i.e. an isolated repaint of only that layer (and its dedicated paint buffer) is requested,
+ QCustomPlot will decide depending on the invalidated flags of other paint buffers whether it also
+ replots them, instead of only the layer on which the replot was called.
+
+ The invalidated flag is set to true when \ref QCPLayer association has changed, i.e. if layers
+ were added or removed from this buffer, or if they were reordered. It is set to false as soon as
+ all associated \ref QCPLayer instances are drawn onto the buffer.
+
+ Under normal circumstances, it is not necessary to manually call this method.
+*/
+void QCPAbstractPaintBuffer::setInvalidated(bool invalidated)
+{
+ mInvalidated = invalidated;
+}
+
+/*!
+ Sets the device pixel ratio to \a ratio. This is useful to render on high-DPI output devices.
+ The ratio is automatically set to the device pixel ratio used by the parent QCustomPlot instance.
+
+ The buffer is reallocated (by calling \ref reallocateBuffer), so any painters that were obtained
+ by \ref startPainting are invalidated and must not be used after calling this method.
+
+ \note This method is only available for Qt versions 5.4 and higher.
+*/
+void QCPAbstractPaintBuffer::setDevicePixelRatio(double ratio)
+{
+ if (!qFuzzyCompare(ratio, mDevicePixelRatio))
+ {
+#ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
+ mDevicePixelRatio = ratio;
+ reallocateBuffer();
+#else
+ qDebug() << Q_FUNC_INFO << "Device pixel ratios not supported for Qt versions before 5.4";
+ mDevicePixelRatio = 1.0;
+#endif
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//////////////////// QCPPaintBufferPixmap
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/*! \class QCPPaintBufferPixmap
+ \brief A paint buffer based on QPixmap, using software raster rendering
+
+ This paint buffer is the default and fall-back paint buffer which uses software rendering and
+ QPixmap as internal buffer. It is used if \ref QCustomPlot::setOpenGl is false.
+*/
+
+/*!
+ Creates a pixmap paint buffer instancen with the specified \a size and \a devicePixelRatio, if
+ applicable.
+*/
+QCPPaintBufferPixmap::QCPPaintBufferPixmap(const QSize &size, double devicePixelRatio) :
+ QCPAbstractPaintBuffer(size, devicePixelRatio)
+{
+ QCPPaintBufferPixmap::reallocateBuffer();
+}
+
+QCPPaintBufferPixmap::~QCPPaintBufferPixmap()
+{
+}
+
+/* inherits documentation from base class */
+QCPPainter *QCPPaintBufferPixmap::startPainting()
+{
+ QCPPainter *result = new QCPPainter(&mBuffer);
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+ result->setRenderHint(QPainter::HighQualityAntialiasing);
+#endif
+ return result;
+}
+
+/* inherits documentation from base class */
+void QCPPaintBufferPixmap::draw(QCPPainter *painter) const
+{
+ if (painter && painter->isActive())
+ painter->drawPixmap(0, 0, mBuffer);
+ else
+ qDebug() << Q_FUNC_INFO << "invalid or inactive painter passed";
+}
+
+/* inherits documentation from base class */
+void QCPPaintBufferPixmap::clear(const QColor &color)
+{
+ mBuffer.fill(color);
+}
+
+/* inherits documentation from base class */
+void QCPPaintBufferPixmap::reallocateBuffer()
+{
+ setInvalidated();
+ if (!qFuzzyCompare(1.0, mDevicePixelRatio))
+ {
+#ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
+ mBuffer = QPixmap(mSize*mDevicePixelRatio);
+ mBuffer.setDevicePixelRatio(mDevicePixelRatio);
+#else
+ qDebug() << Q_FUNC_INFO << "Device pixel ratios not supported for Qt versions before 5.4";
+ mDevicePixelRatio = 1.0;
+ mBuffer = QPixmap(mSize);
+#endif
+ } else
+ {
+ mBuffer = QPixmap(mSize);
+ }
+}
+
+
+#ifdef QCP_OPENGL_PBUFFER
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//////////////////// QCPPaintBufferGlPbuffer
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/*! \class QCPPaintBufferGlPbuffer
+ \brief A paint buffer based on OpenGL pixel buffers, using hardware accelerated rendering
+
+ This paint buffer is one of the OpenGL paint buffers which facilitate hardware accelerated plot
+ rendering. It is based on OpenGL pixel buffers (pbuffer) and is used in Qt versions before 5.0.
+ (See \ref QCPPaintBufferGlFbo used in newer Qt versions.)
+
+ The OpenGL paint buffers are used if \ref QCustomPlot::setOpenGl is set to true, and if they are
+ supported by the system.
+*/
+
+/*!
+ Creates a \ref QCPPaintBufferGlPbuffer instance with the specified \a size and \a
+ devicePixelRatio, if applicable.
+
+ The parameter \a multisamples defines how many samples are used per pixel. Higher values thus
+ result in higher quality antialiasing. If the specified \a multisamples value exceeds the
+ capability of the graphics hardware, the highest supported multisampling is used.
+*/
+QCPPaintBufferGlPbuffer::QCPPaintBufferGlPbuffer(const QSize &size, double devicePixelRatio, int multisamples) :
+ QCPAbstractPaintBuffer(size, devicePixelRatio),
+ mGlPBuffer(0),
+ mMultisamples(qMax(0, multisamples))
+{
+ QCPPaintBufferGlPbuffer::reallocateBuffer();
+}
+
+QCPPaintBufferGlPbuffer::~QCPPaintBufferGlPbuffer()
+{
+ if (mGlPBuffer)
+ delete mGlPBuffer;
+}
+
+/* inherits documentation from base class */
+QCPPainter *QCPPaintBufferGlPbuffer::startPainting()
+{
+ if (!mGlPBuffer->isValid())
+ {
+ qDebug() << Q_FUNC_INFO << "OpenGL frame buffer object doesn't exist, reallocateBuffer was not called?";
+ return 0;
+ }
+
+ QCPPainter *result = new QCPPainter(mGlPBuffer);
+ result->setRenderHint(QPainter::HighQualityAntialiasing);
+ return result;
+}
+
+/* inherits documentation from base class */
+void QCPPaintBufferGlPbuffer::draw(QCPPainter *painter) const
+{
+ if (!painter || !painter->isActive())
+ {
+ qDebug() << Q_FUNC_INFO << "invalid or inactive painter passed";
+ return;
+ }
+ if (!mGlPBuffer->isValid())
+ {
+ qDebug() << Q_FUNC_INFO << "OpenGL pbuffer isn't valid, reallocateBuffer was not called?";
+ return;
+ }
+ painter->drawImage(0, 0, mGlPBuffer->toImage());
+}
+
+/* inherits documentation from base class */
+void QCPPaintBufferGlPbuffer::clear(const QColor &color)
+{
+ if (mGlPBuffer->isValid())
+ {
+ mGlPBuffer->makeCurrent();
+ glClearColor(color.redF(), color.greenF(), color.blueF(), color.alphaF());
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ mGlPBuffer->doneCurrent();
+ } else
+ qDebug() << Q_FUNC_INFO << "OpenGL pbuffer invalid or context not current";
+}
+
+/* inherits documentation from base class */
+void QCPPaintBufferGlPbuffer::reallocateBuffer()
+{
+ if (mGlPBuffer)
+ delete mGlPBuffer;
+
+ QGLFormat format;
+ format.setAlpha(true);
+ format.setSamples(mMultisamples);
+ mGlPBuffer = new QGLPixelBuffer(mSize, format);
+}
+#endif // QCP_OPENGL_PBUFFER
+
+
+#ifdef QCP_OPENGL_FBO
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//////////////////// QCPPaintBufferGlFbo
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/*! \class QCPPaintBufferGlFbo
+ \brief A paint buffer based on OpenGL frame buffers objects, using hardware accelerated rendering
+
+ This paint buffer is one of the OpenGL paint buffers which facilitate hardware accelerated plot
+ rendering. It is based on OpenGL frame buffer objects (fbo) and is used in Qt versions 5.0 and
+ higher. (See \ref QCPPaintBufferGlPbuffer used in older Qt versions.)
+
+ The OpenGL paint buffers are used if \ref QCustomPlot::setOpenGl is set to true, and if they are
+ supported by the system.
+*/
+
+/*!
+ Creates a \ref QCPPaintBufferGlFbo instance with the specified \a size and \a devicePixelRatio,
+ if applicable.
+
+ All frame buffer objects shall share one OpenGL context and paint device, which need to be set up
+ externally and passed via \a glContext and \a glPaintDevice. The set-up is done in \ref
+ QCustomPlot::setupOpenGl and the context and paint device are managed by the parent QCustomPlot
+ instance.
+*/
+QCPPaintBufferGlFbo::QCPPaintBufferGlFbo(const QSize &size, double devicePixelRatio, QWeakPointer glContext, QWeakPointer glPaintDevice) :
+ QCPAbstractPaintBuffer(size, devicePixelRatio),
+ mGlContext(glContext),
+ mGlPaintDevice(glPaintDevice),
+ mGlFrameBuffer(0)
+{
+ QCPPaintBufferGlFbo::reallocateBuffer();
+}
+
+QCPPaintBufferGlFbo::~QCPPaintBufferGlFbo()
+{
+ if (mGlFrameBuffer)
+ delete mGlFrameBuffer;
+}
+
+/* inherits documentation from base class */
+QCPPainter *QCPPaintBufferGlFbo::startPainting()
+{
+ QSharedPointer paintDevice = mGlPaintDevice.toStrongRef();
+ QSharedPointer context = mGlContext.toStrongRef();
+ if (!paintDevice)
+ {
+ qDebug() << Q_FUNC_INFO << "OpenGL paint device doesn't exist";
+ return 0;
+ }
+ if (!context)
+ {
+ qDebug() << Q_FUNC_INFO << "OpenGL context doesn't exist";
+ return 0;
+ }
+ if (!mGlFrameBuffer)
+ {
+ qDebug() << Q_FUNC_INFO << "OpenGL frame buffer object doesn't exist, reallocateBuffer was not called?";
+ return 0;
+ }
+
+ if (QOpenGLContext::currentContext() != context.data())
+ context->makeCurrent(context->surface());
+ mGlFrameBuffer->bind();
+ QCPPainter *result = new QCPPainter(paintDevice.data());
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+ result->setRenderHint(QPainter::HighQualityAntialiasing);
+#endif
+ return result;
+}
+
+/* inherits documentation from base class */
+void QCPPaintBufferGlFbo::donePainting()
+{
+ if (mGlFrameBuffer && mGlFrameBuffer->isBound())
+ mGlFrameBuffer->release();
+ else
+ qDebug() << Q_FUNC_INFO << "Either OpenGL frame buffer not valid or was not bound";
+}
+
+/* inherits documentation from base class */
+void QCPPaintBufferGlFbo::draw(QCPPainter *painter) const
+{
+ if (!painter || !painter->isActive())
+ {
+ qDebug() << Q_FUNC_INFO << "invalid or inactive painter passed";
+ return;
+ }
+ if (!mGlFrameBuffer)
+ {
+ qDebug() << Q_FUNC_INFO << "OpenGL frame buffer object doesn't exist, reallocateBuffer was not called?";
+ return;
+ }
+ painter->drawImage(0, 0, mGlFrameBuffer->toImage());
+}
+
+/* inherits documentation from base class */
+void QCPPaintBufferGlFbo::clear(const QColor &color)
+{
+ QSharedPointer context = mGlContext.toStrongRef();
+ if (!context)
+ {
+ qDebug() << Q_FUNC_INFO << "OpenGL context doesn't exist";
+ return;
+ }
+ if (!mGlFrameBuffer)
+ {
+ qDebug() << Q_FUNC_INFO << "OpenGL frame buffer object doesn't exist, reallocateBuffer was not called?";
+ return;
+ }
+
+ if (QOpenGLContext::currentContext() != context.data())
+ context->makeCurrent(context->surface());
+ mGlFrameBuffer->bind();
+ glClearColor(color.redF(), color.greenF(), color.blueF(), color.alphaF());
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ mGlFrameBuffer->release();
+}
+
+/* inherits documentation from base class */
+void QCPPaintBufferGlFbo::reallocateBuffer()
+{
+ // release and delete possibly existing framebuffer:
+ if (mGlFrameBuffer)
+ {
+ if (mGlFrameBuffer->isBound())
+ mGlFrameBuffer->release();
+ delete mGlFrameBuffer;
+ mGlFrameBuffer = 0;
+ }
+
+ QSharedPointer paintDevice = mGlPaintDevice.toStrongRef();
+ QSharedPointer context = mGlContext.toStrongRef();
+ if (!paintDevice)
+ {
+ qDebug() << Q_FUNC_INFO << "OpenGL paint device doesn't exist";
+ return;
+ }
+ if (!context)
+ {
+ qDebug() << Q_FUNC_INFO << "OpenGL context doesn't exist";
+ return;
+ }
+
+ // create new fbo with appropriate size:
+ context->makeCurrent(context->surface());
+ QOpenGLFramebufferObjectFormat frameBufferFormat;
+ frameBufferFormat.setSamples(context->format().samples());
+ frameBufferFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
+ mGlFrameBuffer = new QOpenGLFramebufferObject(mSize*mDevicePixelRatio, frameBufferFormat);
+ if (paintDevice->size() != mSize*mDevicePixelRatio)
+ paintDevice->setSize(mSize*mDevicePixelRatio);
+#ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
+ paintDevice->setDevicePixelRatio(mDevicePixelRatio);
+#endif
+}
+#endif // QCP_OPENGL_FBO
+/* end of 'src/paintbuffer.cpp' */
+
+
+/* including file 'src/layer.cpp' */
+/* modified 2022-11-06T12:45:56, size 37615 */
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//////////////////// QCPLayer
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/*! \class QCPLayer
+ \brief A layer that may contain objects, to control the rendering order
+
+ The Layering system of QCustomPlot is the mechanism to control the rendering order of the
+ elements inside the plot.
+
+ It is based on the two classes QCPLayer and QCPLayerable. QCustomPlot holds an ordered list of
+ one or more instances of QCPLayer (see QCustomPlot::addLayer, QCustomPlot::layer,
+ QCustomPlot::moveLayer, etc.). When replotting, QCustomPlot goes through the list of layers
+ bottom to top and successively draws the layerables of the layers into the paint buffer(s).
+
+ A QCPLayer contains an ordered list of QCPLayerable instances. QCPLayerable is an abstract base
+ class from which almost all visible objects derive, like axes, grids, graphs, items, etc.
+
+ \section qcplayer-defaultlayers Default layers
+
+ Initially, QCustomPlot has six layers: "background", "grid", "main", "axes", "legend" and
+ "overlay" (in that order). On top is the "overlay" layer, which only contains the QCustomPlot's
+ selection rect (\ref QCustomPlot::selectionRect). The next two layers "axes" and "legend" contain
+ the default axes and legend, so they will be drawn above plottables. In the middle, there is the
+ "main" layer. It is initially empty and set as the current layer (see
+ QCustomPlot::setCurrentLayer). This means, all new plottables, items etc. are created on this
+ layer by default. Then comes the "grid" layer which contains the QCPGrid instances (which belong
+ tightly to QCPAxis, see \ref QCPAxis::grid). The Axis rect background shall be drawn behind
+ everything else, thus the default QCPAxisRect instance is placed on the "background" layer. Of
+ course, the layer affiliation of the individual objects can be changed as required (\ref
+ QCPLayerable::setLayer).
+
+ \section qcplayer-ordering Controlling the rendering order via layers
+
+ Controlling the ordering of layerables in the plot is easy: Create a new layer in the position
+ you want the layerable to be in, e.g. above "main", with \ref QCustomPlot::addLayer. Then set the
+ current layer with \ref QCustomPlot::setCurrentLayer to that new layer and finally create the
+ objects normally. They will be placed on the new layer automatically, due to the current layer
+ setting. Alternatively you could have also ignored the current layer setting and just moved the
+ objects with \ref QCPLayerable::setLayer to the desired layer after creating them.
+
+ It is also possible to move whole layers. For example, If you want the grid to be shown in front
+ of all plottables/items on the "main" layer, just move it above "main" with
+ QCustomPlot::moveLayer.
+
+ The rendering order within one layer is simply by order of creation or insertion. The item
+ created last (or added last to the layer), is drawn on top of all other objects on that layer.
+
+ When a layer is deleted, the objects on it are not deleted with it, but fall on the layer below
+ the deleted layer, see QCustomPlot::removeLayer.
+
+ \section qcplayer-buffering Replotting only a specific layer
+
+ If the layer mode (\ref setMode) is set to \ref lmBuffered, you can replot only this specific
+ layer by calling \ref replot. In certain situations this can provide better replot performance,
+ compared with a full replot of all layers. Upon creation of a new layer, the layer mode is
+ initialized to \ref lmLogical. The only layer that is set to \ref lmBuffered in a new \ref
+ QCustomPlot instance is the "overlay" layer, containing the selection rect.
+*/
+
+/* start documentation of inline functions */
+
+/*! \fn QList QCPLayer::children() const
+
+ Returns a list of all layerables on this layer. The order corresponds to the rendering order:
+ layerables with higher indices are drawn above layerables with lower indices.
+*/
+
+/*! \fn int QCPLayer::index() const
+
+ Returns the index this layer has in the QCustomPlot. The index is the integer number by which this layer can be
+ accessed via \ref QCustomPlot::layer.
+
+ Layers with higher indices will be drawn above layers with lower indices.
+*/
+
+/* end documentation of inline functions */
+
+/*!
+ Creates a new QCPLayer instance.
+
+ Normally you shouldn't directly instantiate layers, use \ref QCustomPlot::addLayer instead.
+
+ \warning It is not checked that \a layerName is actually a unique layer name in \a parentPlot.
+ This check is only performed by \ref QCustomPlot::addLayer.
+*/
+QCPLayer::QCPLayer(QCustomPlot *parentPlot, const QString &layerName) :
+ QObject(parentPlot),
+ mParentPlot(parentPlot),
+ mName(layerName),
+ mIndex(-1), // will be set to a proper value by the QCustomPlot layer creation function
+ mVisible(true),
+ mMode(lmLogical)
+{
+ // Note: no need to make sure layerName is unique, because layer
+ // management is done with QCustomPlot functions.
+}
+
+QCPLayer::~QCPLayer()
+{
+ // If child layerables are still on this layer, detach them, so they don't try to reach back to this
+ // then invalid layer once they get deleted/moved themselves. This only happens when layers are deleted
+ // directly, like in the QCustomPlot destructor. (The regular layer removal procedure for the user is to
+ // call QCustomPlot::removeLayer, which moves all layerables off this layer before deleting it.)
+
+ while (!mChildren.isEmpty())
+ mChildren.last()->setLayer(nullptr); // removes itself from mChildren via removeChild()
+
+ if (mParentPlot->currentLayer() == this)
+ qDebug() << Q_FUNC_INFO << "The parent plot's mCurrentLayer will be a dangling pointer. Should have been set to a valid layer or nullptr beforehand.";
+}
+
+/*!
+ Sets whether this layer is visible or not. If \a visible is set to false, all layerables on this
+ layer will be invisible.
+
+ This function doesn't change the visibility property of the layerables (\ref
+ QCPLayerable::setVisible), but the \ref QCPLayerable::realVisibility of each layerable takes the
+ visibility of the parent layer into account.
+*/
+void QCPLayer::setVisible(bool visible)
+{
+ mVisible = visible;
+}
+
+/*!
+ Sets the rendering mode of this layer.
+
+ If \a mode is set to \ref lmBuffered for a layer, it will be given a dedicated paint buffer by
+ the parent QCustomPlot instance. This means it may be replotted individually by calling \ref
+ QCPLayer::replot, without needing to replot all other layers.
+
+ Layers which are set to \ref lmLogical (the default) are used only to define the rendering order
+ and can't be replotted individually.
+
+ Note that each layer which is set to \ref lmBuffered requires additional paint buffers for the
+ layers below, above and for the layer itself. This increases the memory consumption and
+ (slightly) decreases the repainting speed because multiple paint buffers need to be joined. So
+ you should carefully choose which layers benefit from having their own paint buffer. A typical
+ example would be a layer which contains certain layerables (e.g. items) that need to be changed
+ and thus replotted regularly, while all other layerables on other layers stay static. By default,
+ only the topmost layer called "overlay" is in mode \ref lmBuffered, and contains the selection
+ rect.
+
+ \see replot
+*/
+void QCPLayer::setMode(QCPLayer::LayerMode mode)
+{
+ if (mMode != mode)
+ {
+ mMode = mode;
+ if (QSharedPointer pb = mPaintBuffer.toStrongRef())
+ pb->setInvalidated();
+ }
+}
+
+/*! \internal
+
+ Draws the contents of this layer with the provided \a painter.
+
+ \see replot, drawToPaintBuffer
+*/
+void QCPLayer::draw(QCPPainter *painter)
+{
+ foreach (QCPLayerable *child, mChildren)
+ {
+ if (child->realVisibility())
+ {
+ painter->save();
+ painter->setClipRect(child->clipRect().translated(0, -1));
+ child->applyDefaultAntialiasingHint(painter);
+ child->draw(painter);
+ painter->restore();
+ }
+ }
+}
+
+/*! \internal
+
+ Draws the contents of this layer into the paint buffer which is associated with this layer. The
+ association is established by the parent QCustomPlot, which manages all paint buffers (see \ref
+ QCustomPlot::setupPaintBuffers).
+
+ \see draw
+*/
+void QCPLayer::drawToPaintBuffer()
+{
+ if (QSharedPointer pb = mPaintBuffer.toStrongRef())
+ {
+ if (QCPPainter *painter = pb->startPainting())
+ {
+ if (painter->isActive())
+ draw(painter);
+ else
+ qDebug() << Q_FUNC_INFO << "paint buffer returned inactive painter";
+ delete painter;
+ pb->donePainting();
+ } else
+ qDebug() << Q_FUNC_INFO << "paint buffer returned nullptr painter";
+ } else
+ qDebug() << Q_FUNC_INFO << "no valid paint buffer associated with this layer";
+}
+
+/*!
+ If the layer mode (\ref setMode) is set to \ref lmBuffered, this method allows replotting only
+ the layerables on this specific layer, without the need to replot all other layers (as a call to
+ \ref QCustomPlot::replot would do).
+
+ QCustomPlot also makes sure to replot all layers instead of only this one, if the layer ordering
+ or any layerable-layer-association has changed since the last full replot and any other paint
+ buffers were thus invalidated.
+
+ If the layer mode is \ref lmLogical however, this method simply calls \ref QCustomPlot::replot on
+ the parent QCustomPlot instance.
+
+ \see draw
+*/
+void QCPLayer::replot()
+{
+ if (mMode == lmBuffered && !mParentPlot->hasInvalidatedPaintBuffers())
+ {
+ if (QSharedPointer pb = mPaintBuffer.toStrongRef())
+ {
+ pb->clear(Qt::transparent);
+ drawToPaintBuffer();
+ pb->setInvalidated(false); // since layer is lmBuffered, we know only this layer is on buffer and we can reset invalidated flag
+ mParentPlot->update();
+ } else
+ qDebug() << Q_FUNC_INFO << "no valid paint buffer associated with this layer";
+ } else
+ mParentPlot->replot();
+}
+
+/*! \internal
+
+ Adds the \a layerable to the list of this layer. If \a prepend is set to true, the layerable will
+ be prepended to the list, i.e. be drawn beneath the other layerables already in the list.
+
+ This function does not change the \a mLayer member of \a layerable to this layer. (Use
+ QCPLayerable::setLayer to change the layer of an object, not this function.)
+
+ \see removeChild
+*/
+void QCPLayer::addChild(QCPLayerable *layerable, bool prepend)
+{
+ if (!mChildren.contains(layerable))
+ {
+ if (prepend)
+ mChildren.prepend(layerable);
+ else
+ mChildren.append(layerable);
+ if (QSharedPointer pb = mPaintBuffer.toStrongRef())
+ pb->setInvalidated();
+ } else
+ qDebug() << Q_FUNC_INFO << "layerable is already child of this layer" << reinterpret_cast(layerable);
+}
+
+/*! \internal
+
+ Removes the \a layerable from the list of this layer.
+
+ This function does not change the \a mLayer member of \a layerable. (Use QCPLayerable::setLayer
+ to change the layer of an object, not this function.)
+
+ \see addChild
+*/
+void QCPLayer::removeChild(QCPLayerable *layerable)
+{
+ if (mChildren.removeOne(layerable))
+ {
+ if (QSharedPointer pb = mPaintBuffer.toStrongRef())
+ pb->setInvalidated();
+ } else
+ qDebug() << Q_FUNC_INFO << "layerable is not child of this layer" << reinterpret_cast(layerable);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//////////////////// QCPLayerable
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/*! \class QCPLayerable
+ \brief Base class for all drawable objects
+
+ This is the abstract base class most visible objects derive from, e.g. plottables, axes, grid
+ etc.
+
+ Every layerable is on a layer (QCPLayer) which allows controlling the rendering order by stacking
+ the layers accordingly.
+
+ For details about the layering mechanism, see the QCPLayer documentation.
+*/
+
+/* start documentation of inline functions */
+
+/*! \fn QCPLayerable *QCPLayerable::parentLayerable() const
+
+ Returns the parent layerable of this layerable. The parent layerable is used to provide
+ visibility hierarchies in conjunction with the method \ref realVisibility. This way, layerables
+ only get drawn if their parent layerables are visible, too.
+
+ Note that a parent layerable is not necessarily also the QObject parent for memory management.
+ Further, a layerable doesn't always have a parent layerable, so this function may return \c
+ nullptr.
+
+ A parent layerable is set implicitly when placed inside layout elements and doesn't need to be
+ set manually by the user.
+*/
+
+/* end documentation of inline functions */
+/* start documentation of pure virtual functions */
+
+/*! \fn virtual void QCPLayerable::applyDefaultAntialiasingHint(QCPPainter *painter) const = 0
+ \internal
+
+ This function applies the default antialiasing setting to the specified \a painter, using the
+ function \ref applyAntialiasingHint. It is the antialiasing state the painter is put in, when
+ \ref draw is called on the layerable. If the layerable has multiple entities whose antialiasing
+ setting may be specified individually, this function should set the antialiasing state of the
+ most prominent entity. In this case however, the \ref draw function usually calls the specialized
+ versions of this function before drawing each entity, effectively overriding the setting of the
+ default antialiasing hint.
+
+ First example: QCPGraph has multiple entities that have an antialiasing setting: The graph
+ line, fills and scatters. Those can be configured via QCPGraph::setAntialiased,
+ QCPGraph::setAntialiasedFill and QCPGraph::setAntialiasedScatters. Consequently, there isn't only
+ the QCPGraph::applyDefaultAntialiasingHint function (which corresponds to the graph line's
+ antialiasing), but specialized ones like QCPGraph::applyFillAntialiasingHint and
+ QCPGraph::applyScattersAntialiasingHint. So before drawing one of those entities, QCPGraph::draw
+ calls the respective specialized applyAntialiasingHint function.
+
+ Second example: QCPItemLine consists only of a line so there is only one antialiasing
+ setting which can be controlled with QCPItemLine::setAntialiased. (This function is inherited by
+ all layerables. The specialized functions, as seen on QCPGraph, must be added explicitly to the
+ respective layerable subclass.) Consequently it only has the normal
+ QCPItemLine::applyDefaultAntialiasingHint. The \ref QCPItemLine::draw function doesn't need to
+ care about setting any antialiasing states, because the default antialiasing hint is already set
+ on the painter when the \ref draw function is called, and that's the state it wants to draw the
+ line with.
+*/
+
+/*! \fn virtual void QCPLayerable::draw(QCPPainter *painter) const = 0
+ \internal
+
+ This function draws the layerable with the specified \a painter. It is only called by
+ QCustomPlot, if the layerable is visible (\ref setVisible).
+
+ Before this function is called, the painter's antialiasing state is set via \ref
+ applyDefaultAntialiasingHint, see the documentation there. Further, the clipping rectangle was
+ set to \ref clipRect.
+*/
+
+/* end documentation of pure virtual functions */
+/* start documentation of signals */
+
+/*! \fn void QCPLayerable::layerChanged(QCPLayer *newLayer);
+
+ This signal is emitted when the layer of this layerable changes, i.e. this layerable is moved to
+ a different layer.
+
+ \see setLayer
+*/
+
+/* end documentation of signals */
+
+/*!
+ Creates a new QCPLayerable instance.
+
+ Since QCPLayerable is an abstract base class, it can't be instantiated directly. Use one of the
+ derived classes.
+
+ If \a plot is provided, it automatically places itself on the layer named \a targetLayer. If \a
+ targetLayer is an empty string, it places itself on the current layer of the plot (see \ref
+ QCustomPlot::setCurrentLayer).
+
+ It is possible to provide \c nullptr as \a plot. In that case, you should assign a parent plot at
+ a later time with \ref initializeParentPlot.
+
+ The layerable's parent layerable is set to \a parentLayerable, if provided. Direct layerable
+ parents are mainly used to control visibility in a hierarchy of layerables. This means a
+ layerable is only drawn, if all its ancestor layerables are also visible. Note that \a
+ parentLayerable does not become the QObject-parent (for memory management) of this layerable, \a
+ plot does. It is not uncommon to set the QObject-parent to something else in the constructors of
+ QCPLayerable subclasses, to guarantee a working destruction hierarchy.
+*/
+QCPLayerable::QCPLayerable(QCustomPlot *plot, QString targetLayer, QCPLayerable *parentLayerable) :
+ QObject(plot),
+ mVisible(true),
+ mParentPlot(plot),
+ mParentLayerable(parentLayerable),
+ mLayer(nullptr),
+ mAntialiased(true)
+{
+ if (mParentPlot)
+ {
+ if (targetLayer.isEmpty())
+ setLayer(mParentPlot->currentLayer());
+ else if (!setLayer(targetLayer))
+ qDebug() << Q_FUNC_INFO << "setting QCPlayerable initial layer to" << targetLayer << "failed.";
+ }
+}
+
+QCPLayerable::~QCPLayerable()
+{
+ if (mLayer)
+ {
+ mLayer->removeChild(this);
+ mLayer = nullptr;
+ }
+}
+
+/*!
+ Sets the visibility of this layerable object. If an object is not visible, it will not be drawn
+ on the QCustomPlot surface, and user interaction with it (e.g. click and selection) is not
+ possible.
+*/
+void QCPLayerable::setVisible(bool on)
+{
+ mVisible = on;
+}
+
+/*!
+ Sets the \a layer of this layerable object. The object will be placed on top of the other objects
+ already on \a layer.
+
+ If \a layer is 0, this layerable will not be on any layer and thus not appear in the plot (or
+ interact/receive events).
+
+ Returns true if the layer of this layerable was successfully changed to \a layer.
+*/
+bool QCPLayerable::setLayer(QCPLayer *layer)
+{
+ return moveToLayer(layer, false);
+}
+
+/*! \overload
+ Sets the layer of this layerable object by name
+
+ Returns true on success, i.e. if \a layerName is a valid layer name.
+*/
+bool QCPLayerable::setLayer(const QString &layerName)
+{
+ if (!mParentPlot)
+ {
+ qDebug() << Q_FUNC_INFO << "no parent QCustomPlot set";
+ return false;
+ }
+ if (QCPLayer *layer = mParentPlot->layer(layerName))
+ {
+ return setLayer(layer);
+ } else
+ {
+ qDebug() << Q_FUNC_INFO << "there is no layer with name" << layerName;
+ return false;
+ }
+}
+
+/*!
+ Sets whether this object will be drawn antialiased or not.
+
+ Note that antialiasing settings may be overridden by QCustomPlot::setAntialiasedElements and
+ QCustomPlot::setNotAntialiasedElements.
+*/
+void QCPLayerable::setAntialiased(bool enabled)
+{
+ mAntialiased = enabled;
+}
+
+/*!
+ Returns whether this layerable is visible, taking the visibility of the layerable parent and the
+ visibility of this layerable's layer into account. This is the method that is consulted to decide
+ whether a layerable shall be drawn or not.
+
+ If this layerable has a direct layerable parent (usually set via hierarchies implemented in
+ subclasses, like in the case of \ref QCPLayoutElement), this function returns true only if this
+ layerable has its visibility set to true and the parent layerable's \ref realVisibility returns
+ true.
+*/
+bool QCPLayerable::realVisibility() const
+{
+ return mVisible && (!mLayer || mLayer->visible()) && (!mParentLayerable || mParentLayerable.data()->realVisibility());
+}
+
+/*!
+ This function is used to decide whether a click hits a layerable object or not.
+
+ \a pos is a point in pixel coordinates on the QCustomPlot surface. This function returns the
+ shortest pixel distance of this point to the object. If the object is either invisible or the
+ distance couldn't be determined, -1.0 is returned. Further, if \a onlySelectable is true and the
+ object is not selectable, -1.0 is returned, too.
+
+ If the object is represented not by single lines but by an area like a \ref QCPItemText or the
+ bars of a \ref QCPBars plottable, a click inside the area should also be considered a hit. In
+ these cases this function thus returns a constant value greater zero but still below the parent
+ plot's selection tolerance. (typically the selectionTolerance multiplied by 0.99).
+
+ Providing a constant value for area objects allows selecting line objects even when they are
+ obscured by such area objects, by clicking close to the lines (i.e. closer than
+ 0.99*selectionTolerance).
+
+ The actual setting of the selection state is not done by this function. This is handled by the
+ parent QCustomPlot when the mouseReleaseEvent occurs, and the finally selected object is notified
+ via the \ref selectEvent/\ref deselectEvent methods.
+
+ \a details is an optional output parameter. Every layerable subclass may place any information
+ in \a details. This information will be passed to \ref selectEvent when the parent QCustomPlot
+ decides on the basis of this selectTest call, that the object was successfully selected. The
+ subsequent call to \ref selectEvent will carry the \a details. This is useful for multi-part
+ objects (like QCPAxis). This way, a possibly complex calculation to decide which part was clicked
+ is only done once in \ref selectTest. The result (i.e. the actually clicked part) can then be
+ placed in \a details. So in the subsequent \ref selectEvent, the decision which part was
+ selected doesn't have to be done a second time for a single selection operation.
+
+ In the case of 1D Plottables (\ref QCPAbstractPlottable1D, like \ref QCPGraph or \ref QCPBars) \a
+ details will be set to a \ref QCPDataSelection, describing the closest data point to \a pos.
+
+ You may pass \c nullptr as \a details to indicate that you are not interested in those selection
+ details.
+
+ \see selectEvent, deselectEvent, mousePressEvent, wheelEvent, QCustomPlot::setInteractions,
+ QCPAbstractPlottable1D::selectTestRect
+*/
+double QCPLayerable::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
+{
+ Q_UNUSED(pos)
+ Q_UNUSED(onlySelectable)
+ Q_UNUSED(details)
+ return -1.0;
+}
+
+/*! \internal
+
+ Sets the parent plot of this layerable. Use this function once to set the parent plot if you have
+ passed \c nullptr in the constructor. It can not be used to move a layerable from one QCustomPlot
+ to another one.
+
+ Note that, unlike when passing a non \c nullptr parent plot in the constructor, this function
+ does not make \a parentPlot the QObject-parent of this layerable. If you want this, call
+ QObject::setParent(\a parentPlot) in addition to this function.
+
+ Further, you will probably want to set a layer (\ref setLayer) after calling this function, to
+ make the layerable appear on the QCustomPlot.
+
+ The parent plot change will be propagated to subclasses via a call to \ref parentPlotInitialized
+ so they can react accordingly (e.g. also initialize the parent plot of child layerables, like
+ QCPLayout does).
+*/
+void QCPLayerable::initializeParentPlot(QCustomPlot *parentPlot)
+{
+ if (mParentPlot)
+ {
+ qDebug() << Q_FUNC_INFO << "called with mParentPlot already initialized";
+ return;
+ }
+
+ if (!parentPlot)
+ qDebug() << Q_FUNC_INFO << "called with parentPlot zero";
+
+ mParentPlot = parentPlot;
+ parentPlotInitialized(mParentPlot);
+}
+
+/*! \internal
+
+ Sets the parent layerable of this layerable to \a parentLayerable. Note that \a parentLayerable does not
+ become the QObject-parent (for memory management) of this layerable.
+
+ The parent layerable has influence on the return value of the \ref realVisibility method. Only
+ layerables with a fully visible parent tree will return true for \ref realVisibility, and thus be
+ drawn.
+
+ \see realVisibility
+*/
+void QCPLayerable::setParentLayerable(QCPLayerable *parentLayerable)
+{
+ mParentLayerable = parentLayerable;
+}
+
+/*! \internal
+
+ Moves this layerable object to \a layer. If \a prepend is true, this object will be prepended to
+ the new layer's list, i.e. it will be drawn below the objects already on the layer. If it is
+ false, the object will be appended.
+
+ Returns true on success, i.e. if \a layer is a valid layer.
+*/
+bool QCPLayerable::moveToLayer(QCPLayer *layer, bool prepend)
+{
+ if (layer && !mParentPlot)
+ {
+ qDebug() << Q_FUNC_INFO << "no parent QCustomPlot set";
+ return false;
+ }
+ if (layer && layer->parentPlot() != mParentPlot)
+ {
+ qDebug() << Q_FUNC_INFO << "layer" << layer->name() << "is not in same QCustomPlot as this layerable";
+ return false;
+ }
+
+ QCPLayer *oldLayer = mLayer;
+ if (mLayer)
+ mLayer->removeChild(this);
+ mLayer = layer;
+ if (mLayer)
+ mLayer->addChild(this, prepend);
+ if (mLayer != oldLayer)
+ emit layerChanged(mLayer);
+ return true;
+}
+
+/*! \internal
+
+ Sets the QCPainter::setAntialiasing state on the provided \a painter, depending on the \a
+ localAntialiased value as well as the overrides \ref QCustomPlot::setAntialiasedElements and \ref
+ QCustomPlot::setNotAntialiasedElements. Which override enum this function takes into account is
+ controlled via \a overrideElement.
+*/
+void QCPLayerable::applyAntialiasingHint(QCPPainter *painter, bool localAntialiased, QCP::AntialiasedElement overrideElement) const
+{
+ if (mParentPlot && mParentPlot->notAntialiasedElements().testFlag(overrideElement))
+ painter->setAntialiasing(false);
+ else if (mParentPlot && mParentPlot->antialiasedElements().testFlag(overrideElement))
+ painter->setAntialiasing(true);
+ else
+ painter->setAntialiasing(localAntialiased);
+}
+
+/*! \internal
+
+ This function is called by \ref initializeParentPlot, to allow subclasses to react on the setting
+ of a parent plot. This is the case when \c nullptr was passed as parent plot in the constructor,
+ and the parent plot is set at a later time.
+
+ For example, QCPLayoutElement/QCPLayout hierarchies may be created independently of any
+ QCustomPlot at first. When they are then added to a layout inside the QCustomPlot, the top level
+ element of the hierarchy gets its parent plot initialized with \ref initializeParentPlot. To
+ propagate the parent plot to all the children of the hierarchy, the top level element then uses
+ this function to pass the parent plot on to its child elements.
+
+ The default implementation does nothing.
+
+ \see initializeParentPlot
+*/
+void QCPLayerable::parentPlotInitialized(QCustomPlot *parentPlot)
+{
+ Q_UNUSED(parentPlot)
+}
+
+/*! \internal
+
+ Returns the selection category this layerable shall belong to. The selection category is used in
+ conjunction with \ref QCustomPlot::setInteractions to control which objects are selectable and
+ which aren't.
+
+ Subclasses that don't fit any of the normal \ref QCP::Interaction values can use \ref
+ QCP::iSelectOther. This is what the default implementation returns.
+
+ \see QCustomPlot::setInteractions
+*/
+QCP::Interaction QCPLayerable::selectionCategory() const
+{
+ return QCP::iSelectOther;
+}
+
+/*! \internal
+
+ Returns the clipping rectangle of this layerable object. By default, this is the viewport of the
+ parent QCustomPlot. Specific subclasses may reimplement this function to provide different
+ clipping rects.
+
+ The returned clipping rect is set on the painter before the draw function of the respective
+ object is called.
+*/
+QRect QCPLayerable::clipRect() const
+{
+ if (mParentPlot)
+ return mParentPlot->viewport();
+ else
+ return {};
+}
+
+/*! \internal
+
+ This event is called when the layerable shall be selected, as a consequence of a click by the
+ user. Subclasses should react to it by setting their selection state appropriately. The default
+ implementation does nothing.
+
+ \a event is the mouse event that caused the selection. \a additive indicates, whether the user
+ was holding the multi-select-modifier while performing the selection (see \ref
+ QCustomPlot::setMultiSelectModifier). if \a additive is true, the selection state must be toggled
+ (i.e. become selected when unselected and unselected when selected).
+
+ Every selectEvent is preceded by a call to \ref selectTest, which has returned positively (i.e.
+ returned a value greater than 0 and less than the selection tolerance of the parent QCustomPlot).
+ The \a details data you output from \ref selectTest is fed back via \a details here. You may
+ use it to transport any kind of information from the selectTest to the possibly subsequent
+ selectEvent. Usually \a details is used to transfer which part was clicked, if it is a layerable
+ that has multiple individually selectable parts (like QCPAxis). This way selectEvent doesn't need
+ to do the calculation again to find out which part was actually clicked.
+
+ \a selectionStateChanged is an output parameter. If the pointer is non-null, this function must
+ set the value either to true or false, depending on whether the selection state of this layerable
+ was actually changed. For layerables that only are selectable as a whole and not in parts, this
+ is simple: if \a additive is true, \a selectionStateChanged must also be set to true, because the
+ selection toggles. If \a additive is false, \a selectionStateChanged is only set to true, if the
+ layerable was previously unselected and now is switched to the selected state.
+
+ \see selectTest, deselectEvent
+*/
+void QCPLayerable::selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged)
+{
+ Q_UNUSED(event)
+ Q_UNUSED(additive)
+ Q_UNUSED(details)
+ Q_UNUSED(selectionStateChanged)
+}
+
+/*! \internal
+
+ This event is called when the layerable shall be deselected, either as consequence of a user
+ interaction or a call to \ref QCustomPlot::deselectAll. Subclasses should react to it by
+ unsetting their selection appropriately.
+
+ just as in \ref selectEvent, the output parameter \a selectionStateChanged (if non-null), must
+ return true or false when the selection state of this layerable has changed or not changed,
+ respectively.
+
+ \see selectTest, selectEvent
+*/
+void QCPLayerable::deselectEvent(bool *selectionStateChanged)
+{
+ Q_UNUSED(selectionStateChanged)
+}
+
+/*!
+ This event gets called when the user presses a mouse button while the cursor is over the
+ layerable. Whether a cursor is over the layerable is decided by a preceding call to \ref
+ selectTest.
+
+ The current pixel position of the cursor on the QCustomPlot widget is accessible via \c
+ event->pos(). The parameter \a details contains layerable-specific details about the hit, which
+ were generated in the previous call to \ref selectTest. For example, One-dimensional plottables
+ like \ref QCPGraph or \ref QCPBars convey the clicked data point in the \a details parameter, as
+ \ref QCPDataSelection packed as QVariant. Multi-part objects convey the specific \c
+ SelectablePart that was hit (e.g. \ref QCPAxis::SelectablePart in the case of axes).
+
+ QCustomPlot uses an event propagation system that works the same as Qt's system. If your
+ layerable doesn't reimplement the \ref mousePressEvent or explicitly calls \c event->ignore() in
+ its reimplementation, the event will be propagated to the next layerable in the stacking order.
+
+ Once a layerable has accepted the \ref mousePressEvent, it is considered the mouse grabber and
+ will receive all following calls to \ref mouseMoveEvent or \ref mouseReleaseEvent for this mouse
+ interaction (a "mouse interaction" in this context ends with the release).
+
+ The default implementation does nothing except explicitly ignoring the event with \c
+ event->ignore().
+
+ \see mouseMoveEvent, mouseReleaseEvent, mouseDoubleClickEvent, wheelEvent
+*/
+void QCPLayerable::mousePressEvent(QMouseEvent *event, const QVariant &details)
+{
+ Q_UNUSED(details)
+ event->ignore();
+}
+
+/*!
+ This event gets called when the user moves the mouse while holding a mouse button, after this
+ layerable has become the mouse grabber by accepting the preceding \ref mousePressEvent.
+
+ The current pixel position of the cursor on the QCustomPlot widget is accessible via \c
+ event->pos(). The parameter \a startPos indicates the position where the initial \ref
+ mousePressEvent occurred, that started the mouse interaction.
+
+ The default implementation does nothing.
+
+ \see mousePressEvent, mouseReleaseEvent, mouseDoubleClickEvent, wheelEvent
+*/
+void QCPLayerable::mouseMoveEvent(QMouseEvent *event, const QPointF &startPos)
+{
+ Q_UNUSED(startPos)
+ event->ignore();
+}
+
+/*!
+ This event gets called when the user releases the mouse button, after this layerable has become
+ the mouse grabber by accepting the preceding \ref mousePressEvent.
+
+ The current pixel position of the cursor on the QCustomPlot widget is accessible via \c
+ event->pos(). The parameter \a startPos indicates the position where the initial \ref
+ mousePressEvent occurred, that started the mouse interaction.
+
+ The default implementation does nothing.
+
+ \see mousePressEvent, mouseMoveEvent, mouseDoubleClickEvent, wheelEvent
+*/
+void QCPLayerable::mouseReleaseEvent(QMouseEvent *event, const QPointF &startPos)
+{
+ Q_UNUSED(startPos)
+ event->ignore();
+}
+
+/*!
+ This event gets called when the user presses the mouse button a second time in a double-click,
+ while the cursor is over the layerable. Whether a cursor is over the layerable is decided by a
+ preceding call to \ref selectTest.
+
+ The \ref mouseDoubleClickEvent is called instead of the second \ref mousePressEvent. So in the
+ case of a double-click, the event succession is
+ pressEvent – releaseEvent – doubleClickEvent – releaseEvent.
+
+ The current pixel position of the cursor on the QCustomPlot widget is accessible via \c
+ event->pos(). The parameter \a details contains layerable-specific details about the hit, which
+ were generated in the previous call to \ref selectTest. For example, One-dimensional plottables
+ like \ref QCPGraph or \ref QCPBars convey the clicked data point in the \a details parameter, as
+ \ref QCPDataSelection packed as QVariant. Multi-part objects convey the specific \c
+ SelectablePart that was hit (e.g. \ref QCPAxis::SelectablePart in the case of axes).
+
+ Similarly to \ref mousePressEvent, once a layerable has accepted the \ref mouseDoubleClickEvent,
+ it is considered the mouse grabber and will receive all following calls to \ref mouseMoveEvent
+ and \ref mouseReleaseEvent for this mouse interaction (a "mouse interaction" in this context ends
+ with the release).
+
+ The default implementation does nothing except explicitly ignoring the event with \c
+ event->ignore().
+
+ \see mousePressEvent, mouseMoveEvent, mouseReleaseEvent, wheelEvent
+*/
+void QCPLayerable::mouseDoubleClickEvent(QMouseEvent *event, const QVariant &details)
+{
+ Q_UNUSED(details)
+ event->ignore();
+}
+
+/*!
+ This event gets called when the user turns the mouse scroll wheel while the cursor is over the
+ layerable. Whether a cursor is over the layerable is decided by a preceding call to \ref
+ selectTest.
+
+ The current pixel position of the cursor on the QCustomPlot widget is accessible via \c
+ event->pos().
+
+ The \c event->angleDelta() indicates how far the mouse wheel was turned, which is usually +/- 120
+ for single rotation steps. However, if the mouse wheel is turned rapidly, multiple steps may
+ accumulate to one event, making the delta larger. On the other hand, if the wheel has very smooth
+ steps or none at all, the delta may be smaller.
+
+ The default implementation does nothing.
+
+ \see mousePressEvent, mouseMoveEvent, mouseReleaseEvent, mouseDoubleClickEvent
+*/
+void QCPLayerable::wheelEvent(QWheelEvent *event)
+{
+ event->ignore();
+}
+/* end of 'src/layer.cpp' */
+
+
+/* including file 'src/axis/range.cpp' */
+/* modified 2022-11-06T12:45:56, size 12221 */
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//////////////////// QCPRange
+////////////////////////////////////////////////////////////////////////////////////////////////////
+/*! \class QCPRange
+ \brief Represents the range an axis is encompassing.
+
+ contains a \a lower and \a upper double value and provides convenience input, output and
+ modification functions.
+
+ \see QCPAxis::setRange
+*/
+
+/* start of documentation of inline functions */
+
+/*! \fn double QCPRange::size() const
+
+ Returns the size of the range, i.e. \a upper-\a lower
+*/
+
+/*! \fn double QCPRange::center() const
+
+ Returns the center of the range, i.e. (\a upper+\a lower)*0.5
+*/
+
+/*! \fn void QCPRange::normalize()
+
+ Makes sure \a lower is numerically smaller than \a upper. If this is not the case, the values are
+ swapped.
+*/
+
+/*! \fn bool QCPRange::contains(double value) const
+
+ Returns true when \a value lies within or exactly on the borders of the range.
+*/
+
+/*! \fn QCPRange &QCPRange::operator+=(const double& value)
+
+ Adds \a value to both boundaries of the range.
+*/
+
+/*! \fn QCPRange &QCPRange::operator-=(const double& value)
+
+ Subtracts \a value from both boundaries of the range.
+*/
+
+/*! \fn QCPRange &QCPRange::operator*=(const double& value)
+
+ Multiplies both boundaries of the range by \a value.
+*/
+
+/*! \fn QCPRange &QCPRange::operator/=(const double& value)
+
+ Divides both boundaries of the range by \a value.
+*/
+
+/* end of documentation of inline functions */
+
+/*!
+ Minimum range size (\a upper - \a lower) the range changing functions will accept. Smaller
+ intervals would cause errors due to the 11-bit exponent of double precision numbers,
+ corresponding to a minimum magnitude of roughly 1e-308.
+
+ \warning Do not use this constant to indicate "arbitrarily small" values in plotting logic (as
+ values that will appear in the plot)! It is intended only as a bound to compare against, e.g. to
+ prevent axis ranges from obtaining underflowing ranges.
+
+ \see validRange, maxRange
+*/
+const double QCPRange::minRange = 1e-280;
+
+/*!
+ Maximum values (negative and positive) the range will accept in range-changing functions.
+ Larger absolute values would cause errors due to the 11-bit exponent of double precision numbers,
+ corresponding to a maximum magnitude of roughly 1e308.
+
+ \warning Do not use this constant to indicate "arbitrarily large" values in plotting logic (as
+ values that will appear in the plot)! It is intended only as a bound to compare against, e.g. to
+ prevent axis ranges from obtaining overflowing ranges.
+
+ \see validRange, minRange
+*/
+const double QCPRange::maxRange = 1e250;
+
+/*!
+ Constructs a range with \a lower and \a upper set to zero.
+*/
+QCPRange::QCPRange() :
+ lower(0),
+ upper(0)
+{
+}
+
+/*! \overload
+
+ Constructs a range with the specified \a lower and \a upper values.
+
+ The resulting range will be normalized (see \ref normalize), so if \a lower is not numerically
+ smaller than \a upper, they will be swapped.
+*/
+QCPRange::QCPRange(double lower, double upper) :
+ lower(lower),
+ upper(upper)
+{
+ normalize();
+}
+
+/*! \overload
+
+ Expands this range such that \a otherRange is contained in the new range. It is assumed that both
+ this range and \a otherRange are normalized (see \ref normalize).
+
+ If this range contains NaN as lower or upper bound, it will be replaced by the respective bound
+ of \a otherRange.
+
+ If \a otherRange is already inside the current range, this function does nothing.
+
+ \see expanded
+*/
+void QCPRange::expand(const QCPRange &otherRange)
+{
+ if (lower > otherRange.lower || qIsNaN(lower))
+ lower = otherRange.lower;
+ if (upper < otherRange.upper || qIsNaN(upper))
+ upper = otherRange.upper;
+}
+
+/*! \overload
+
+ Expands this range such that \a includeCoord is contained in the new range. It is assumed that
+ this range is normalized (see \ref normalize).
+
+ If this range contains NaN as lower or upper bound, the respective bound will be set to \a
+ includeCoord.
+
+ If \a includeCoord is already inside the current range, this function does nothing.
+
+ \see expand
+*/
+void QCPRange::expand(double includeCoord)
+{
+ if (lower > includeCoord || qIsNaN(lower))
+ lower = includeCoord;
+ if (upper < includeCoord || qIsNaN(upper))
+ upper = includeCoord;
+}
+
+
+/*! \overload
+
+ Returns an expanded range that contains this and \a otherRange. It is assumed that both this
+ range and \a otherRange are normalized (see \ref normalize).
+
+ If this range contains NaN as lower or upper bound, the returned range's bound will be taken from
+ \a otherRange.
+
+ \see expand
+*/
+QCPRange QCPRange::expanded(const QCPRange &otherRange) const
+{
+ QCPRange result = *this;
+ result.expand(otherRange);
+ return result;
+}
+
+/*! \overload
+
+ Returns an expanded range that includes the specified \a includeCoord. It is assumed that this
+ range is normalized (see \ref normalize).
+
+ If this range contains NaN as lower or upper bound, the returned range's bound will be set to \a
+ includeCoord.
+
+ \see expand
+*/
+QCPRange QCPRange::expanded(double includeCoord) const
+{
+ QCPRange result = *this;
+ result.expand(includeCoord);
+ return result;
+}
+
+/*!
+ Returns this range, possibly modified to not exceed the bounds provided as \a lowerBound and \a
+ upperBound. If possible, the size of the current range is preserved in the process.
+
+ If the range shall only be bounded at the lower side, you can set \a upperBound to \ref
+ QCPRange::maxRange. If it shall only be bounded at the upper side, set \a lowerBound to -\ref
+ QCPRange::maxRange.
+*/
+QCPRange QCPRange::bounded(double lowerBound, double upperBound) const
+{
+ if (lowerBound > upperBound)
+ qSwap(lowerBound, upperBound);
+
+ QCPRange result(lower, upper);
+ if (result.lower < lowerBound)
+ {
+ result.lower = lowerBound;
+ result.upper = lowerBound + size();
+ if (result.upper > upperBound || qFuzzyCompare(size(), upperBound-lowerBound))
+ result.upper = upperBound;
+ } else if (result.upper > upperBound)
+ {
+ result.upper = upperBound;
+ result.lower = upperBound - size();
+ if (result.lower < lowerBound || qFuzzyCompare(size(), upperBound-lowerBound))
+ result.lower = lowerBound;
+ }
+
+ return result;
+}
+
+/*!
+ Returns a sanitized version of the range. Sanitized means for logarithmic scales, that
+ the range won't span the positive and negative sign domain, i.e. contain zero. Further
+ \a lower will always be numerically smaller (or equal) to \a upper.
+
+ If the original range does span positive and negative sign domains or contains zero,
+ the returned range will try to approximate the original range as good as possible.
+ If the positive interval of the original range is wider than the negative interval, the
+ returned range will only contain the positive interval, with lower bound set to \a rangeFac or
+ \a rangeFac *\a upper, whichever is closer to zero. Same procedure is used if the negative interval
+ is wider than the positive interval, this time by changing the \a upper bound.
+*/
+QCPRange QCPRange::sanitizedForLogScale() const
+{
+ double rangeFac = 1e-3;
+ QCPRange sanitizedRange(lower, upper);
+ sanitizedRange.normalize();
+ // can't have range spanning negative and positive values in log plot, so change range to fix it
+ //if (qFuzzyCompare(sanitizedRange.lower+1, 1) && !qFuzzyCompare(sanitizedRange.upper+1, 1))
+ if (sanitizedRange.lower == 0.0 && sanitizedRange.upper != 0.0)
+ {
+ // case lower is 0
+ if (rangeFac < sanitizedRange.upper*rangeFac)
+ sanitizedRange.lower = rangeFac;
+ else
+ sanitizedRange.lower = sanitizedRange.upper*rangeFac;
+ } //else if (!qFuzzyCompare(lower+1, 1) && qFuzzyCompare(upper+1, 1))
+ else if (sanitizedRange.lower != 0.0 && sanitizedRange.upper == 0.0)
+ {
+ // case upper is 0
+ if (-rangeFac > sanitizedRange.lower*rangeFac)
+ sanitizedRange.upper = -rangeFac;
+ else
+ sanitizedRange.upper = sanitizedRange.lower*rangeFac;
+ } else if (sanitizedRange.lower < 0 && sanitizedRange.upper > 0)
+ {
+ // find out whether negative or positive interval is wider to decide which sign domain will be chosen
+ if (-sanitizedRange.lower > sanitizedRange.upper)
+ {
+ // negative is wider, do same as in case upper is 0
+ if (-rangeFac > sanitizedRange.lower*rangeFac)
+ sanitizedRange.upper = -rangeFac;
+ else
+ sanitizedRange.upper = sanitizedRange.lower*rangeFac;
+ } else
+ {
+ // positive is wider, do same as in case lower is 0
+ if (rangeFac < sanitizedRange.upper*rangeFac)
+ sanitizedRange.lower = rangeFac;
+ else
+ sanitizedRange.lower = sanitizedRange.upper*rangeFac;
+ }
+ }
+ // due to normalization, case lower>0 && upper<0 should never occur, because that implies upper -maxRange &&
+ upper < maxRange &&
+ qAbs(lower-upper) > minRange &&
+ qAbs(lower-upper) < maxRange &&
+ !(lower > 0 && qIsInf(upper/lower)) &&
+ !(upper < 0 && qIsInf(lower/upper)));
+}
+
+/*!
+ \overload
+ Checks, whether the specified range is within valid bounds, which are defined
+ as QCPRange::maxRange and QCPRange::minRange.
+ A valid range means:
+ \li range bounds within -maxRange and maxRange
+ \li range size above minRange
+ \li range size below maxRange
+*/
+bool QCPRange::validRange(const QCPRange &range)
+{
+ return (range.lower > -maxRange &&
+ range.upper < maxRange &&
+ qAbs(range.lower-range.upper) > minRange &&
+ qAbs(range.lower-range.upper) < maxRange &&
+ !(range.lower > 0 && qIsInf(range.upper/range.lower)) &&
+ !(range.upper < 0 && qIsInf(range.lower/range.upper)));
+}
+/* end of 'src/axis/range.cpp' */
+
+
+/* including file 'src/selection.cpp' */
+/* modified 2022-11-06T12:45:56, size 21837 */
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//////////////////// QCPDataRange
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/*! \class QCPDataRange
+ \brief Describes a data range given by begin and end index
+
+ QCPDataRange holds two integers describing the begin (\ref setBegin) and end (\ref setEnd) index
+ of a contiguous set of data points. The \a end index corresponds to the data point just after the
+ last data point of the data range, like in standard iterators.
+
+ Data Ranges are not bound to a certain plottable, thus they can be freely exchanged, created and
+ modified. If a non-contiguous data set shall be described, the class \ref QCPDataSelection is
+ used, which holds and manages multiple instances of \ref QCPDataRange. In most situations, \ref
+ QCPDataSelection is thus used.
+
+ Both \ref QCPDataRange and \ref QCPDataSelection offer convenience methods to work with them,
+ e.g. \ref bounded, \ref expanded, \ref intersects, \ref intersection, \ref adjusted, \ref
+ contains. Further, addition and subtraction operators (defined in \ref QCPDataSelection) can be
+ used to join/subtract data ranges and data selections (or mixtures), to retrieve a corresponding
+ \ref QCPDataSelection.
+
+ %QCustomPlot's \ref dataselection "data selection mechanism" is based on \ref QCPDataSelection and
+ QCPDataRange.
+
+ \note Do not confuse \ref QCPDataRange with \ref QCPRange. A \ref QCPRange describes an interval
+ in floating point plot coordinates, e.g. the current axis range.
+*/
+
+/* start documentation of inline functions */
+
+/*! \fn int QCPDataRange::size() const
+
+ Returns the number of data points described by this data range. This is equal to the end index
+ minus the begin index.
+
+ \see length
+*/
+
+/*! \fn int QCPDataRange::length() const
+
+ Returns the number of data points described by this data range. Equivalent to \ref size.
+*/
+
+/*! \fn void QCPDataRange::setBegin(int begin)
+
+ Sets the begin of this data range. The \a begin index points to the first data point that is part
+ of the data range.
+
+ No checks or corrections are made to ensure the resulting range is valid (\ref isValid).
+
+ \see setEnd
+*/
+
+/*! \fn void QCPDataRange::setEnd(int end)
+
+ Sets the end of this data range. The \a end index points to the data point just after the last
+ data point that is part of the data range.
+
+ No checks or corrections are made to ensure the resulting range is valid (\ref isValid).
+
+ \see setBegin
+*/
+
+/*! \fn bool QCPDataRange::isValid() const
+
+ Returns whether this range is valid. A valid range has a begin index greater or equal to 0, and
+ an end index greater or equal to the begin index.
+
+ \note Invalid ranges should be avoided and are never the result of any of QCustomPlot's methods
+ (unless they are themselves fed with invalid ranges). Do not pass invalid ranges to QCustomPlot's
+ methods. The invalid range is not inherently prevented in QCPDataRange, to allow temporary
+ invalid begin/end values while manipulating the range. An invalid range is not necessarily empty
+ (\ref isEmpty), since its \ref length can be negative and thus non-zero.
+*/
+
+/*! \fn bool QCPDataRange::isEmpty() const
+
+ Returns whether this range is empty, i.e. whether its begin index equals its end index.
+
+ \see size, length
+*/
+
+/*! \fn QCPDataRange QCPDataRange::adjusted(int changeBegin, int changeEnd) const
+
+ Returns a data range where \a changeBegin and \a changeEnd were added to the begin and end
+ indices, respectively.
+*/
+
+/* end documentation of inline functions */
+
+/*!
+ Creates an empty QCPDataRange, with begin and end set to 0.
+*/
+QCPDataRange::QCPDataRange() :
+ mBegin(0),
+ mEnd(0)
+{
+}
+
+/*!
+ Creates a QCPDataRange, initialized with the specified \a begin and \a end.
+
+ No checks or corrections are made to ensure the resulting range is valid (\ref isValid).
+*/
+QCPDataRange::QCPDataRange(int begin, int end) :
+ mBegin(begin),
+ mEnd(end)
+{
+}
+
+/*!
+ Returns a data range that matches this data range, except that parts exceeding \a other are
+ excluded.
+
+ This method is very similar to \ref intersection, with one distinction: If this range and the \a
+ other range share no intersection, the returned data range will be empty with begin and end set
+ to the respective boundary side of \a other, at which this range is residing. (\ref intersection
+ would just return a range with begin and end set to 0.)
+*/
+QCPDataRange QCPDataRange::bounded(const QCPDataRange &other) const
+{
+ QCPDataRange result(intersection(other));
+ if (result.isEmpty()) // no intersection, preserve respective bounding side of otherRange as both begin and end of return value
+ {
+ if (mEnd <= other.mBegin)
+ result = QCPDataRange(other.mBegin, other.mBegin);
+ else
+ result = QCPDataRange(other.mEnd, other.mEnd);
+ }
+ return result;
+}
+
+/*!
+ Returns a data range that contains both this data range as well as \a other.
+*/
+QCPDataRange QCPDataRange::expanded(const QCPDataRange &other) const
+{
+ return {qMin(mBegin, other.mBegin), qMax(mEnd, other.mEnd)};
+}
+
+/*!
+ Returns the data range which is contained in both this data range and \a other.
+
+ This method is very similar to \ref bounded, with one distinction: If this range and the \a other
+ range share no intersection, the returned data range will be empty with begin and end set to 0.
+ (\ref bounded would return a range with begin and end set to one of the boundaries of \a other,
+ depending on which side this range is on.)
+
+ \see QCPDataSelection::intersection
+*/
+QCPDataRange QCPDataRange::intersection(const QCPDataRange &other) const
+{
+ QCPDataRange result(qMax(mBegin, other.mBegin), qMin(mEnd, other.mEnd));
+ if (result.isValid())
+ return result;
+ else
+ return {};
+}
+
+/*!
+ Returns whether this data range and \a other share common data points.
+
+ \see intersection, contains
+*/
+bool QCPDataRange::intersects(const QCPDataRange &other) const
+{
+ return !( (mBegin > other.mBegin && mBegin >= other.mEnd) ||
+ (mEnd <= other.mBegin && mEnd < other.mEnd) );
+}
+
+/*!
+ Returns whether all data points of \a other are also contained inside this data range.
+
+ \see intersects
+*/
+bool QCPDataRange::contains(const QCPDataRange &other) const
+{
+ return mBegin <= other.mBegin && mEnd >= other.mEnd;
+}
+
+
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//////////////////// QCPDataSelection
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/*! \class QCPDataSelection
+ \brief Describes a data set by holding multiple QCPDataRange instances
+
+ QCPDataSelection manages multiple instances of QCPDataRange in order to represent any (possibly
+ disjoint) set of data selection.
+
+ The data selection can be modified with addition and subtraction operators which take
+ QCPDataSelection and QCPDataRange instances, as well as methods such as \ref addDataRange and
+ \ref clear. Read access is provided by \ref dataRange, \ref dataRanges, \ref dataRangeCount, etc.
+
+ The method \ref simplify is used to join directly adjacent or even overlapping QCPDataRange
+ instances. QCPDataSelection automatically simplifies when using the addition/subtraction
+ operators. The only case when \ref simplify is left to the user, is when calling \ref
+ addDataRange, with the parameter \a simplify explicitly set to false. This is useful if many data
+ ranges will be added to the selection successively and the overhead for simplifying after each
+ iteration shall be avoided. In this case, you should make sure to call \ref simplify after
+ completing the operation.
+
+ Use \ref enforceType to bring the data selection into a state complying with the constraints for
+ selections defined in \ref QCP::SelectionType.
+
+ %QCustomPlot's \ref dataselection "data selection mechanism" is based on QCPDataSelection and
+ QCPDataRange.
+
+ \section qcpdataselection-iterating Iterating over a data selection
+
+ As an example, the following code snippet calculates the average value of a graph's data
+ \ref QCPAbstractPlottable::selection "selection":
+
+ \snippet documentation/doc-code-snippets/mainwindow.cpp qcpdataselection-iterating-1
+
+*/
+
+/* start documentation of inline functions */
+
+/*! \fn int QCPDataSelection::dataRangeCount() const
+
+ Returns the number of ranges that make up the data selection. The ranges can be accessed by \ref
+ dataRange via their index.
+
+ \see dataRange, dataPointCount
+*/
+
+/*! \fn QList QCPDataSelection::dataRanges() const
+
+ Returns all data ranges that make up the data selection. If the data selection is simplified (the
+ usual state of the selection, see \ref simplify), the ranges are sorted by ascending data point
+ index.
+
+ \see dataRange
+*/
+
+/*! \fn bool QCPDataSelection::isEmpty() const
+
+ Returns true if there are no data ranges, and thus no data points, in this QCPDataSelection
+ instance.
+
+ \see dataRangeCount
+*/
+
+/* end documentation of inline functions */
+
+/*!
+ Creates an empty QCPDataSelection.
+*/
+QCPDataSelection::QCPDataSelection()
+{
+}
+
+/*!
+ Creates a QCPDataSelection containing the provided \a range.
+*/
+QCPDataSelection::QCPDataSelection(const QCPDataRange &range)
+{
+ mDataRanges.append(range);
+}
+
+/*!
+ Returns true if this selection is identical (contains the same data ranges with the same begin
+ and end indices) to \a other.
+
+ Note that both data selections must be in simplified state (the usual state of the selection, see
+ \ref simplify) for this operator to return correct results.
+*/
+bool QCPDataSelection::operator==(const QCPDataSelection &other) const
+{
+ if (mDataRanges.size() != other.mDataRanges.size())
+ return false;
+ for (int i=0; i= other.end())
+ break; // since data ranges are sorted after the simplify() call, no ranges which contain other will come after this
+
+ if (thisEnd > other.begin()) // ranges which don't fulfill this are entirely before other and can be ignored
+ {
+ if (thisBegin >= other.begin()) // range leading segment is encompassed
+ {
+ if (thisEnd <= other.end()) // range fully encompassed, remove completely
+ {
+ mDataRanges.removeAt(i);
+ continue;
+ } else // only leading segment is encompassed, trim accordingly
+ mDataRanges[i].setBegin(other.end());
+ } else // leading segment is not encompassed
+ {
+ if (thisEnd <= other.end()) // only trailing segment is encompassed, trim accordingly
+ {
+ mDataRanges[i].setEnd(other.begin());
+ } else // other lies inside this range, so split range
+ {
+ mDataRanges[i].setEnd(other.begin());
+ mDataRanges.insert(i+1, QCPDataRange(other.end(), thisEnd));
+ break; // since data ranges are sorted (and don't overlap) after simplify() call, we're done here
+ }
+ }
+ }
+ ++i;
+ }
+
+ return *this;
+}
+
+/*!
+ Returns the total number of data points contained in all data ranges that make up this data
+ selection.
+*/
+int QCPDataSelection::dataPointCount() const
+{
+ int result = 0;
+ foreach (QCPDataRange dataRange, mDataRanges)
+ result += dataRange.length();
+ return result;
+}
+
+/*!
+ Returns the data range with the specified \a index.
+
+ If the data selection is simplified (the usual state of the selection, see \ref simplify), the
+ ranges are sorted by ascending data point index.
+
+ \see dataRangeCount
+*/
+QCPDataRange QCPDataSelection::dataRange(int index) const
+{
+ if (index >= 0 && index < mDataRanges.size())
+ {
+ return mDataRanges.at(index);
+ } else
+ {
+ qDebug() << Q_FUNC_INFO << "index out of range:" << index;
+ return {};
+ }
+}
+
+/*!
+ Returns a \ref QCPDataRange which spans the entire data selection, including possible
+ intermediate segments which are not part of the original data selection.
+*/
+QCPDataRange QCPDataSelection::span() const
+{
+ if (isEmpty())
+ return {};
+ else
+ return {mDataRanges.first().begin(), mDataRanges.last().end()};
+}
+
+/*!
+ Adds the given \a dataRange to this data selection. This is equivalent to the += operator but
+ allows disabling immediate simplification by setting \a simplify to false. This can improve
+ performance if adding a very large amount of data ranges successively. In this case, make sure to
+ call \ref simplify manually, after the operation.
+*/
+void QCPDataSelection::addDataRange(const QCPDataRange &dataRange, bool simplify)
+{
+ mDataRanges.append(dataRange);
+ if (simplify)
+ this->simplify();
+}
+
+/*!
+ Removes all data ranges. The data selection then contains no data points.
+
+ \ref isEmpty
+*/
+void QCPDataSelection::clear()
+{
+ mDataRanges.clear();
+}
+
+/*!
+ Sorts all data ranges by range begin index in ascending order, and then joins directly adjacent
+ or overlapping ranges. This can reduce the number of individual data ranges in the selection, and
+ prevents possible double-counting when iterating over the data points held by the data ranges.
+
+ This method is automatically called when using the addition/subtraction operators. The only case
+ when \ref simplify is left to the user, is when calling \ref addDataRange, with the parameter \a
+ simplify explicitly set to false.
+*/
+void QCPDataSelection::simplify()
+{
+ // remove any empty ranges:
+ for (int i=mDataRanges.size()-1; i>=0; --i)
+ {
+ if (mDataRanges.at(i).isEmpty())
+ mDataRanges.removeAt(i);
+ }
+ if (mDataRanges.isEmpty())
+ return;
+
+ // sort ranges by starting value, ascending:
+ std::sort(mDataRanges.begin(), mDataRanges.end(), lessThanDataRangeBegin);
+
+ // join overlapping/contiguous ranges:
+ int i = 1;
+ while (i < mDataRanges.size())
+ {
+ if (mDataRanges.at(i-1).end() >= mDataRanges.at(i).begin()) // range i overlaps/joins with i-1, so expand range i-1 appropriately and remove range i from list
+ {
+ mDataRanges[i-1].setEnd(qMax(mDataRanges.at(i-1).end(), mDataRanges.at(i).end()));
+ mDataRanges.removeAt(i);
+ } else
+ ++i;
+ }
+}
+
+/*!
+ Makes sure this data selection conforms to the specified \a type selection type. Before the type
+ is enforced, \ref simplify is called.
+
+ Depending on \a type, enforcing means adding new data points that were previously not part of the
+ selection, or removing data points from the selection. If the current selection already conforms
+ to \a type, the data selection is not changed.
+
+ \see QCP::SelectionType
+*/
+void QCPDataSelection::enforceType(QCP::SelectionType type)
+{
+ simplify();
+ switch (type)
+ {
+ case QCP::stNone:
+ {
+ mDataRanges.clear();
+ break;
+ }
+ case QCP::stWhole:
+ {
+ // whole selection isn't defined by data range, so don't change anything (is handled in plottable methods)
+ break;
+ }
+ case QCP::stSingleData:
+ {
+ // reduce all data ranges to the single first data point:
+ if (!mDataRanges.isEmpty())
+ {
+ if (mDataRanges.size() > 1)
+ mDataRanges = QList() << mDataRanges.first();
+ if (mDataRanges.first().length() > 1)
+ mDataRanges.first().setEnd(mDataRanges.first().begin()+1);
+ }
+ break;
+ }
+ case QCP::stDataRange:
+ {
+ if (!isEmpty())
+ mDataRanges = QList() << span();
+ break;
+ }
+ case QCP::stMultipleDataRanges:
+ {
+ // this is the selection type that allows all concievable combinations of ranges, so do nothing
+ break;
+ }
+ }
+}
+
+/*!
+ Returns true if the data selection \a other is contained entirely in this data selection, i.e.
+ all data point indices that are in \a other are also in this data selection.
+
+ \see QCPDataRange::contains
+*/
+bool QCPDataSelection::contains(const QCPDataSelection &other) const
+{
+ if (other.isEmpty()) return false;
+
+ int otherIndex = 0;
+ int thisIndex = 0;
+ while (thisIndex < mDataRanges.size() && otherIndex < other.mDataRanges.size())
+ {
+ if (mDataRanges.at(thisIndex).contains(other.mDataRanges.at(otherIndex)))
+ ++otherIndex;
+ else
+ ++thisIndex;
+ }
+ return thisIndex < mDataRanges.size(); // if thisIndex ran all the way to the end to find a containing range for the current otherIndex, other is not contained in this
+}
+
+/*!
+ Returns a data selection containing the points which are both in this data selection and in the
+ data range \a other.
+
+ A common use case is to limit an unknown data selection to the valid range of a data container,
+ using \ref QCPDataContainer::dataRange as \a other. One can then safely iterate over the returned
+ data selection without exceeding the data container's bounds.
+*/
+QCPDataSelection QCPDataSelection::intersection(const QCPDataRange &other) const
+{
+ QCPDataSelection result;
+ foreach (QCPDataRange dataRange, mDataRanges)
+ result.addDataRange(dataRange.intersection(other), false);
+ result.simplify();
+ return result;
+}
+
+/*!
+ Returns a data selection containing the points which are both in this data selection and in the
+ data selection \a other.
+*/
+QCPDataSelection QCPDataSelection::intersection(const QCPDataSelection &other) const
+{
+ QCPDataSelection result;
+ for (int i=0; iorientation() == Qt::Horizontal)
+ return {axis->pixelToCoord(mRect.left()), axis->pixelToCoord(mRect.left()+mRect.width())};
+ else
+ return {axis->pixelToCoord(mRect.top()+mRect.height()), axis->pixelToCoord(mRect.top())};
+ } else
+ {
+ qDebug() << Q_FUNC_INFO << "called with axis zero";
+ return {};
+ }
+}
+
+/*!
+ Sets the pen that will be used to draw the selection rect outline.
+
+ \see setBrush
+*/
+void QCPSelectionRect::setPen(const QPen &pen)
+{
+ mPen = pen;
+}
+
+/*!
+ Sets the brush that will be used to fill the selection rect. By default the selection rect is not
+ filled, i.e. \a brush is Qt::NoBrush.
+
+ \see setPen
+*/
+void QCPSelectionRect::setBrush(const QBrush &brush)
+{
+ mBrush = brush;
+}
+
+/*!
+ If there is currently a selection interaction going on (\ref isActive), the interaction is
+ canceled. The selection rect will emit the \ref canceled signal.
+*/
+void QCPSelectionRect::cancel()
+{
+ if (mActive)
+ {
+ mActive = false;
+ emit canceled(mRect, nullptr);
+ }
+}
+
+/*! \internal
+
+ This method is called by QCustomPlot to indicate that a selection rect interaction was initiated.
+ The default implementation sets the selection rect to active, initializes the selection rect
+ geometry and emits the \ref started signal.
+*/
+void QCPSelectionRect::startSelection(QMouseEvent *event)
+{
+ mActive = true;
+ mRect = QRect(event->pos(), event->pos());
+ emit started(event);
+}
+
+/*! \internal
+
+ This method is called by QCustomPlot to indicate that an ongoing selection rect interaction needs
+ to update its geometry. The default implementation updates the rect and emits the \ref changed
+ signal.
+*/
+void QCPSelectionRect::moveSelection(QMouseEvent *event)
+{
+ mRect.setBottomRight(event->pos());
+ emit changed(mRect, event);
+ layer()->replot();
+}
+
+/*! \internal
+
+ This method is called by QCustomPlot to indicate that an ongoing selection rect interaction has
+ finished by the user releasing the mouse button. The default implementation deactivates the
+ selection rect and emits the \ref accepted signal.
+*/
+void QCPSelectionRect::endSelection(QMouseEvent *event)
+{
+ mRect.setBottomRight(event->pos());
+ mActive = false;
+ emit accepted(mRect, event);
+}
+
+/*! \internal
+
+ This method is called by QCustomPlot when a key has been pressed by the user while the selection
+ rect interaction is active. The default implementation allows to \ref cancel the interaction by
+ hitting the escape key.
+*/
+void QCPSelectionRect::keyPressEvent(QKeyEvent *event)
+{
+ if (event->key() == Qt::Key_Escape && mActive)
+ {
+ mActive = false;
+ emit canceled(mRect, event);
+ }
+}
+
+/* inherits documentation from base class */
+void QCPSelectionRect::applyDefaultAntialiasingHint(QCPPainter *painter) const
+{
+ applyAntialiasingHint(painter, mAntialiased, QCP::aeOther);
+}
+
+/*! \internal
+
+ If the selection rect is active (\ref isActive), draws the selection rect defined by \a mRect.
+
+ \seebaseclassmethod
+*/
+void QCPSelectionRect::draw(QCPPainter *painter)
+{
+ if (mActive)
+ {
+ painter->setPen(mPen);
+ painter->setBrush(mBrush);
+ painter->drawRect(mRect);
+ }
+}
+/* end of 'src/selectionrect.cpp' */
+
+
+/* including file 'src/layout.cpp' */
+/* modified 2022-11-06T12:45:56, size 78863 */
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//////////////////// QCPMarginGroup
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/*! \class QCPMarginGroup
+ \brief A margin group allows synchronization of margin sides if working with multiple layout elements.
+
+ QCPMarginGroup allows you to tie a margin side of two or more layout elements together, such that
+ they will all have the same size, based on the largest required margin in the group.
+
+ \n
+ \image html QCPMarginGroup.png "Demonstration of QCPMarginGroup"
+ \n
+
+ In certain situations it is desirable that margins at specific sides are synchronized across
+ layout elements. For example, if one QCPAxisRect is below another one in a grid layout, it will
+ provide a cleaner look to the user if the left and right margins of the two axis rects are of the
+ same size. The left axis of the top axis rect will then be at the same horizontal position as the
+ left axis of the lower axis rect, making them appear aligned. The same applies for the right
+ axes. This is what QCPMarginGroup makes possible.
+
+ To add/remove a specific side of a layout element to/from a margin group, use the \ref
+ QCPLayoutElement::setMarginGroup method. To completely break apart the margin group, either call
+ \ref clear, or just delete the margin group.
+
+ \section QCPMarginGroup-example Example
+
+ First create a margin group:
+ \snippet documentation/doc-code-snippets/mainwindow.cpp qcpmargingroup-creation-1
+ Then set this group on the layout element sides:
+ \snippet documentation/doc-code-snippets/mainwindow.cpp qcpmargingroup-creation-2
+ Here, we've used the first two axis rects of the plot and synchronized their left margins with
+ each other and their right margins with each other.
+*/
+
+/* start documentation of inline functions */
+
+/*! \fn QList QCPMarginGroup::elements(QCP::MarginSide side) const
+
+ Returns a list of all layout elements that have their margin \a side associated with this margin
+ group.
+*/
+
+/* end documentation of inline functions */
+
+/*!
+ Creates a new QCPMarginGroup instance in \a parentPlot.
+*/
+QCPMarginGroup::QCPMarginGroup(QCustomPlot *parentPlot) :
+ QObject(parentPlot),
+ mParentPlot(parentPlot)
+{
+ mChildren.insert(QCP::msLeft, QList());
+ mChildren.insert(QCP::msRight, QList());
+ mChildren.insert(QCP::msTop, QList());
+ mChildren.insert(QCP::msBottom, QList());
+}
+
+QCPMarginGroup::~QCPMarginGroup()
+{
+ clear();
+}
+
+/*!
+ Returns whether this margin group is empty. If this function returns true, no layout elements use
+ this margin group to synchronize margin sides.
+*/
+bool QCPMarginGroup::isEmpty() const
+{
+ QHashIterator > it(mChildren);
+ while (it.hasNext())
+ {
+ it.next();
+ if (!it.value().isEmpty())
+ return false;
+ }
+ return true;
+}
+
+/*!
+ Clears this margin group. The synchronization of the margin sides that use this margin group is
+ lifted and they will use their individual margin sizes again.
+*/
+void QCPMarginGroup::clear()
+{
+ // make all children remove themselves from this margin group:
+ QHashIterator > it(mChildren);
+ while (it.hasNext())
+ {
+ it.next();
+ const QList elements = it.value();
+ for (int i=elements.size()-1; i>=0; --i)
+ elements.at(i)->setMarginGroup(it.key(), nullptr); // removes itself from mChildren via removeChild
+ }
+}
+
+/*! \internal
+
+ Returns the synchronized common margin for \a side. This is the margin value that will be used by
+ the layout element on the respective side, if it is part of this margin group.
+
+ The common margin is calculated by requesting the automatic margin (\ref
+ QCPLayoutElement::calculateAutoMargin) of each element associated with \a side in this margin
+ group, and choosing the largest returned value. (QCPLayoutElement::minimumMargins is taken into
+ account, too.)
+*/
+int QCPMarginGroup::commonMargin(QCP::MarginSide side) const
+{
+ // query all automatic margins of the layout elements in this margin group side and find maximum:
+ int result = 0;
+ foreach (QCPLayoutElement *el, mChildren.value(side))
+ {
+ if (!el->autoMargins().testFlag(side))
+ continue;
+ int m = qMax(el->calculateAutoMargin(side), QCP::getMarginValue(el->minimumMargins(), side));
+ if (m > result)
+ result = m;
+ }
+ return result;
+}
+
+/*! \internal
+
+ Adds \a element to the internal list of child elements, for the margin \a side.
+
+ This function does not modify the margin group property of \a element.
+*/
+void QCPMarginGroup::addChild(QCP::MarginSide side, QCPLayoutElement *element)
+{
+ if (!mChildren[side].contains(element))
+ mChildren[side].append(element);
+ else
+ qDebug() << Q_FUNC_INFO << "element is already child of this margin group side" << reinterpret_cast(element);
+}
+
+/*! \internal
+
+ Removes \a element from the internal list of child elements, for the margin \a side.
+
+ This function does not modify the margin group property of \a element.
+*/
+void QCPMarginGroup::removeChild(QCP::MarginSide side, QCPLayoutElement *element)
+{
+ if (!mChildren[side].removeOne(element))
+ qDebug() << Q_FUNC_INFO << "element is not child of this margin group side" << reinterpret_cast(element);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//////////////////// QCPLayoutElement
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/*! \class QCPLayoutElement
+ \brief The abstract base class for all objects that form \ref thelayoutsystem "the layout system".
+
+ This is an abstract base class. As such, it can't be instantiated directly, rather use one of its subclasses.
+
+ A Layout element is a rectangular object which can be placed in layouts. It has an outer rect
+ (QCPLayoutElement::outerRect) and an inner rect (\ref QCPLayoutElement::rect). The difference
+ between outer and inner rect is called its margin. The margin can either be set to automatic or
+ manual (\ref setAutoMargins) on a per-side basis. If a side is set to manual, that margin can be
+ set explicitly with \ref setMargins and will stay fixed at that value. If it's set to automatic,
+ the layout element subclass will control the value itself (via \ref calculateAutoMargin).
+
+ Layout elements can be placed in layouts (base class QCPLayout) like QCPLayoutGrid. The top level
+ layout is reachable via \ref QCustomPlot::plotLayout, and is a \ref QCPLayoutGrid. Since \ref
+ QCPLayout itself derives from \ref QCPLayoutElement, layouts can be nested.
+
+ Thus in QCustomPlot one can divide layout elements into two categories: The ones that are
+ invisible by themselves, because they don't draw anything. Their only purpose is to manage the
+ position and size of other layout elements. This category of layout elements usually use
+ QCPLayout as base class. Then there is the category of layout elements which actually draw
+ something. For example, QCPAxisRect, QCPLegend and QCPTextElement are of this category. This does
+ not necessarily mean that the latter category can't have child layout elements. QCPLegend for
+ instance, actually derives from QCPLayoutGrid and the individual legend items are child layout
+ elements in the grid layout.
+*/
+
+/* start documentation of inline functions */
+
+/*! \fn QCPLayout *QCPLayoutElement::layout() const
+
+ Returns the parent layout of this layout element.
+*/
+
+/*! \fn QRect QCPLayoutElement::rect() const
+
+ Returns the inner rect of this layout element. The inner rect is the outer rect (\ref outerRect, \ref
+ setOuterRect) shrinked by the margins (\ref setMargins, \ref setAutoMargins).
+
+ In some cases, the area between outer and inner rect is left blank. In other cases the margin
+ area is used to display peripheral graphics while the main content is in the inner rect. This is
+ where automatic margin calculation becomes interesting because it allows the layout element to
+ adapt the margins to the peripheral graphics it wants to draw. For example, \ref QCPAxisRect
+ draws the axis labels and tick labels in the margin area, thus needs to adjust the margins (if
+ \ref setAutoMargins is enabled) according to the space required by the labels of the axes.
+
+ \see outerRect
+*/
+
+/*! \fn QRect QCPLayoutElement::outerRect() const
+
+ Returns the outer rect of this layout element. The outer rect is the inner rect expanded by the
+ margins (\ref setMargins, \ref setAutoMargins). The outer rect is used (and set via \ref
+ setOuterRect) by the parent \ref QCPLayout to control the size of this layout element.
+
+ \see rect
+*/
+
+/* end documentation of inline functions */
+
+/*!
+ Creates an instance of QCPLayoutElement and sets default values.
+*/
+QCPLayoutElement::QCPLayoutElement(QCustomPlot *parentPlot) :
+ QCPLayerable(parentPlot), // parenthood is changed as soon as layout element gets inserted into a layout (except for top level layout)
+ mParentLayout(nullptr),
+ mMinimumSize(),
+ mMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX),
+ mSizeConstraintRect(scrInnerRect),
+ mRect(0, 0, 0, 0),
+ mOuterRect(0, 0, 0, 0),
+ mMargins(0, 0, 0, 0),
+ mMinimumMargins(0, 0, 0, 0),
+ mAutoMargins(QCP::msAll)
+{
+}
+
+QCPLayoutElement::~QCPLayoutElement()
+{
+ setMarginGroup(QCP::msAll, nullptr); // unregister at margin groups, if there are any
+ // unregister at layout:
+ if (qobject_cast(mParentLayout)) // the qobject_cast is just a safeguard in case the layout forgets to call clear() in its dtor and this dtor is called by QObject dtor
+ mParentLayout->take(this);
+}
+
+/*!
+ Sets the outer rect of this layout element. If the layout element is inside a layout, the layout
+ sets the position and size of this layout element using this function.
+
+ Calling this function externally has no effect, since the layout will overwrite any changes to
+ the outer rect upon the next replot.
+
+ The layout element will adapt its inner \ref rect by applying the margins inward to the outer rect.
+
+ \see rect
+*/
+void QCPLayoutElement::setOuterRect(const QRect &rect)
+{
+ if (mOuterRect != rect)
+ {
+ mOuterRect = rect;
+ mRect = mOuterRect.adjusted(mMargins.left(), mMargins.top(), -mMargins.right(), -mMargins.bottom());
+ }
+}
+
+/*!
+ Sets the margins of this layout element. If \ref setAutoMargins is disabled for some or all
+ sides, this function is used to manually set the margin on those sides. Sides that are still set
+ to be handled automatically are ignored and may have any value in \a margins.
+
+ The margin is the distance between the outer rect (controlled by the parent layout via \ref
+ setOuterRect) and the inner \ref rect (which usually contains the main content of this layout
+ element).
+
+ \see setAutoMargins
+*/
+void QCPLayoutElement::setMargins(const QMargins &margins)
+{
+ if (mMargins != margins)
+ {
+ mMargins = margins;
+ mRect = mOuterRect.adjusted(mMargins.left(), mMargins.top(), -mMargins.right(), -mMargins.bottom());
+ }
+}
+
+/*!
+ If \ref setAutoMargins is enabled on some or all margins, this function is used to provide
+ minimum values for those margins.
+
+ The minimum values are not enforced on margin sides that were set to be under manual control via
+ \ref setAutoMargins.
+
+ \see setAutoMargins
+*/
+void QCPLayoutElement::setMinimumMargins(const QMargins &margins)
+{
+ if (mMinimumMargins != margins)
+ {
+ mMinimumMargins = margins;
+ }
+}
+
+/*!
+ Sets on which sides the margin shall be calculated automatically. If a side is calculated
+ automatically, a minimum margin value may be provided with \ref setMinimumMargins. If a side is
+ set to be controlled manually, the value may be specified with \ref setMargins.
+
+ Margin sides that are under automatic control may participate in a \ref QCPMarginGroup (see \ref
+ setMarginGroup), to synchronize (align) it with other layout elements in the plot.
+
+ \see setMinimumMargins, setMargins, QCP::MarginSide
+*/
+void QCPLayoutElement::setAutoMargins(QCP::MarginSides sides)
+{
+ mAutoMargins = sides;
+}
+
+/*!
+ Sets the minimum size of this layout element. A parent layout tries to respect the \a size here
+ by changing row/column sizes in the layout accordingly.
+
+ If the parent layout size is not sufficient to satisfy all minimum size constraints of its child
+ layout elements, the layout may set a size that is actually smaller than \a size. QCustomPlot
+ propagates the layout's size constraints to the outside by setting its own minimum QWidget size
+ accordingly, so violations of \a size should be exceptions.
+
+ Whether this constraint applies to the inner or the outer rect can be specified with \ref
+ setSizeConstraintRect (see \ref rect and \ref outerRect).
+*/
+void QCPLayoutElement::setMinimumSize(const QSize &size)
+{
+ if (mMinimumSize != size)
+ {
+ mMinimumSize = size;
+ if (mParentLayout)
+ mParentLayout->sizeConstraintsChanged();
+ }
+}
+
+/*! \overload
+
+ Sets the minimum size of this layout element.
+
+ Whether this constraint applies to the inner or the outer rect can be specified with \ref
+ setSizeConstraintRect (see \ref rect and \ref outerRect).
+*/
+void QCPLayoutElement::setMinimumSize(int width, int height)
+{
+ setMinimumSize(QSize(width, height));
+}
+
+/*!
+ Sets the maximum size of this layout element. A parent layout tries to respect the \a size here
+ by changing row/column sizes in the layout accordingly.
+
+ Whether this constraint applies to the inner or the outer rect can be specified with \ref
+ setSizeConstraintRect (see \ref rect and \ref outerRect).
+*/
+void QCPLayoutElement::setMaximumSize(const QSize &size)
+{
+ if (mMaximumSize != size)
+ {
+ mMaximumSize = size;
+ if (mParentLayout)
+ mParentLayout->sizeConstraintsChanged();
+ }
+}
+
+/*! \overload
+
+ Sets the maximum size of this layout element.
+
+ Whether this constraint applies to the inner or the outer rect can be specified with \ref
+ setSizeConstraintRect (see \ref rect and \ref outerRect).
+*/
+void QCPLayoutElement::setMaximumSize(int width, int height)
+{
+ setMaximumSize(QSize(width, height));
+}
+
+/*!
+ Sets to which rect of a layout element the size constraints apply. Size constraints can be set
+ via \ref setMinimumSize and \ref setMaximumSize.
+
+ The outer rect (\ref outerRect) includes the margins (e.g. in the case of a QCPAxisRect the axis
+ labels), whereas the inner rect (\ref rect) does not.
+
+ \see setMinimumSize, setMaximumSize
+*/
+void QCPLayoutElement::setSizeConstraintRect(SizeConstraintRect constraintRect)
+{
+ if (mSizeConstraintRect != constraintRect)
+ {
+ mSizeConstraintRect = constraintRect;
+ if (mParentLayout)
+ mParentLayout->sizeConstraintsChanged();
+ }
+}
+
+/*!
+ Sets the margin \a group of the specified margin \a sides.
+
+ Margin groups allow synchronizing specified margins across layout elements, see the documentation
+ of \ref QCPMarginGroup.
+
+ To unset the margin group of \a sides, set \a group to \c nullptr.
+
+ Note that margin groups only work for margin sides that are set to automatic (\ref
+ setAutoMargins).
+
+ \see QCP::MarginSide
+*/
+void QCPLayoutElement::setMarginGroup(QCP::MarginSides sides, QCPMarginGroup *group)
+{
+ QVector sideVector;
+ if (sides.testFlag(QCP::msLeft)) sideVector.append(QCP::msLeft);
+ if (sides.testFlag(QCP::msRight)) sideVector.append(QCP::msRight);
+ if (sides.testFlag(QCP::msTop)) sideVector.append(QCP::msTop);
+ if (sides.testFlag(QCP::msBottom)) sideVector.append(QCP::msBottom);
+
+ foreach (QCP::MarginSide side, sideVector)
+ {
+ if (marginGroup(side) != group)
+ {
+ QCPMarginGroup *oldGroup = marginGroup(side);
+ if (oldGroup) // unregister at old group
+ oldGroup->removeChild(side, this);
+
+ if (!group) // if setting to 0, remove hash entry. Else set hash entry to new group and register there
+ {
+ mMarginGroups.remove(side);
+ } else // setting to a new group
+ {
+ mMarginGroups[side] = group;
+ group->addChild(side, this);
+ }
+ }
+ }
+}
+
+/*!
+ Updates the layout element and sub-elements. This function is automatically called before every
+ replot by the parent layout element. It is called multiple times, once for every \ref
+ UpdatePhase. The phases are run through in the order of the enum values. For details about what
+ happens at the different phases, see the documentation of \ref UpdatePhase.
+
+ Layout elements that have child elements should call the \ref update method of their child
+ elements, and pass the current \a phase unchanged.
+
+ The default implementation executes the automatic margin mechanism in the \ref upMargins phase.
+ Subclasses should make sure to call the base class implementation.
+*/
+void QCPLayoutElement::update(UpdatePhase phase)
+{
+ if (phase == upMargins)
+ {
+ if (mAutoMargins != QCP::msNone)
+ {
+ // set the margins of this layout element according to automatic margin calculation, either directly or via a margin group:
+ QMargins newMargins = mMargins;
+ const QList allMarginSides = QList() << QCP::msLeft << QCP::msRight << QCP::msTop << QCP::msBottom;
+ foreach (QCP::MarginSide side, allMarginSides)
+ {
+ if (mAutoMargins.testFlag(side)) // this side's margin shall be calculated automatically
+ {
+ if (mMarginGroups.contains(side))
+ QCP::setMarginValue(newMargins, side, mMarginGroups[side]->commonMargin(side)); // this side is part of a margin group, so get the margin value from that group
+ else
+ QCP::setMarginValue(newMargins, side, calculateAutoMargin(side)); // this side is not part of a group, so calculate the value directly
+ // apply minimum margin restrictions:
+ if (QCP::getMarginValue(newMargins, side) < QCP::getMarginValue(mMinimumMargins, side))
+ QCP::setMarginValue(newMargins, side, QCP::getMarginValue(mMinimumMargins, side));
+ }
+ }
+ setMargins(newMargins);
+ }
+ }
+}
+
+/*!
+ Returns the suggested minimum size this layout element (the \ref outerRect) may be compressed to,
+ if no manual minimum size is set.
+
+ if a minimum size (\ref setMinimumSize) was not set manually, parent layouts use the returned size
+ (usually indirectly through \ref QCPLayout::getFinalMinimumOuterSize) to determine the minimum
+ allowed size of this layout element.
+
+ A manual minimum size is considered set if it is non-zero.
+
+ The default implementation simply returns the sum of the horizontal margins for the width and the
+ sum of the vertical margins for the height. Reimplementations may use their detailed knowledge
+ about the layout element's content to provide size hints.
+*/
+QSize QCPLayoutElement::minimumOuterSizeHint() const
+{
+ return {mMargins.left()+mMargins.right(), mMargins.top()+mMargins.bottom()};
+}
+
+/*!
+ Returns the suggested maximum size this layout element (the \ref outerRect) may be expanded to,
+ if no manual maximum size is set.
+
+ if a maximum size (\ref setMaximumSize) was not set manually, parent layouts use the returned
+ size (usually indirectly through \ref QCPLayout::getFinalMaximumOuterSize) to determine the
+ maximum allowed size of this layout element.
+
+ A manual maximum size is considered set if it is smaller than Qt's \c QWIDGETSIZE_MAX.
+
+ The default implementation simply returns \c QWIDGETSIZE_MAX for both width and height, implying
+ no suggested maximum size. Reimplementations may use their detailed knowledge about the layout
+ element's content to provide size hints.
+*/
+QSize QCPLayoutElement::maximumOuterSizeHint() const
+{
+ return {QWIDGETSIZE_MAX, QWIDGETSIZE_MAX};
+}
+
+/*!
+ Returns a list of all child elements in this layout element. If \a recursive is true, all
+ sub-child elements are included in the list, too.
+
+ \warning There may be \c nullptr entries in the returned list. For example, QCPLayoutGrid may
+ have empty cells which yield \c nullptr at the respective index.
+*/
+QList QCPLayoutElement::elements(bool recursive) const
+{
+ Q_UNUSED(recursive)
+ return QList();
+}
+
+/*!
+ Layout elements are sensitive to events inside their outer rect. If \a pos is within the outer
+ rect, this method returns a value corresponding to 0.99 times the parent plot's selection
+ tolerance. However, layout elements are not selectable by default. So if \a onlySelectable is
+ true, -1.0 is returned.
+
+ See \ref QCPLayerable::selectTest for a general explanation of this virtual method.
+
+ QCPLayoutElement subclasses may reimplement this method to provide more specific selection test
+ behaviour.
+*/
+double QCPLayoutElement::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
+{
+ Q_UNUSED(details)
+
+ if (onlySelectable)
+ return -1;
+
+ if (QRectF(mOuterRect).contains(pos))
+ {
+ if (mParentPlot)
+ return mParentPlot->selectionTolerance()*0.99;
+ else
+ {
+ qDebug() << Q_FUNC_INFO << "parent plot not defined";
+ return -1;
+ }
+ } else
+ return -1;
+}
+
+/*! \internal
+
+ propagates the parent plot initialization to all child elements, by calling \ref
+ QCPLayerable::initializeParentPlot on them.
+*/
+void QCPLayoutElement::parentPlotInitialized(QCustomPlot *parentPlot)
+{
+ foreach (QCPLayoutElement *el, elements(false))
+ {
+ if (!el->parentPlot())
+ el->initializeParentPlot(parentPlot);
+ }
+}
+
+/*! \internal
+
+ Returns the margin size for this \a side. It is used if automatic margins is enabled for this \a
+ side (see \ref setAutoMargins). If a minimum margin was set with \ref setMinimumMargins, the
+ returned value will not be smaller than the specified minimum margin.
+
+ The default implementation just returns the respective manual margin (\ref setMargins) or the
+ minimum margin, whichever is larger.
+*/
+int QCPLayoutElement::calculateAutoMargin(QCP::MarginSide side)
+{
+ return qMax(QCP::getMarginValue(mMargins, side), QCP::getMarginValue(mMinimumMargins, side));
+}
+
+/*! \internal
+
+ This virtual method is called when this layout element was moved to a different QCPLayout, or
+ when this layout element has changed its logical position (e.g. row and/or column) within the
+ same QCPLayout. Subclasses may use this to react accordingly.
+
+ Since this method is called after the completion of the move, you can access the new parent
+ layout via \ref layout().
+
+ The default implementation does nothing.
+*/
+void QCPLayoutElement::layoutChanged()
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//////////////////// QCPLayout
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/*! \class QCPLayout
+ \brief The abstract base class for layouts
+
+ This is an abstract base class for layout elements whose main purpose is to define the position
+ and size of other child layout elements. In most cases, layouts don't draw anything themselves
+ (but there are exceptions to this, e.g. QCPLegend).
+
+ QCPLayout derives from QCPLayoutElement, and thus can itself be nested in other layouts.
+
+ QCPLayout introduces a common interface for accessing and manipulating the child elements. Those
+ functions are most notably \ref elementCount, \ref elementAt, \ref takeAt, \ref take, \ref
+ simplify, \ref removeAt, \ref remove and \ref clear. Individual subclasses may add more functions
+ to this interface which are more specialized to the form of the layout. For example, \ref
+ QCPLayoutGrid adds functions that take row and column indices to access cells of the layout grid
+ more conveniently.
+
+ Since this is an abstract base class, you can't instantiate it directly. Rather use one of its
+ subclasses like QCPLayoutGrid or QCPLayoutInset.
+
+ For a general introduction to the layout system, see the dedicated documentation page \ref
+ thelayoutsystem "The Layout System".
+*/
+
+/* start documentation of pure virtual functions */
+
+/*! \fn virtual int QCPLayout::elementCount() const = 0
+
+ Returns the number of elements/cells in the layout.
+
+ \see elements, elementAt
+*/
+
+/*! \fn virtual QCPLayoutElement* QCPLayout::elementAt(int index) const = 0
+
+ Returns the element in the cell with the given \a index. If \a index is invalid, returns \c
+ nullptr.
+
+ Note that even if \a index is valid, the respective cell may be empty in some layouts (e.g.
+ QCPLayoutGrid), so this function may return \c nullptr in those cases. You may use this function
+ to check whether a cell is empty or not.
+
+ \see elements, elementCount, takeAt
+*/
+
+/*! \fn virtual QCPLayoutElement* QCPLayout::takeAt(int index) = 0
+
+ Removes the element with the given \a index from the layout and returns it.
+
+ If the \a index is invalid or the cell with that index is empty, returns \c nullptr.
+
+ Note that some layouts don't remove the respective cell right away but leave an empty cell after
+ successful removal of the layout element. To collapse empty cells, use \ref simplify.
+
+ \see elementAt, take
+*/
+
+/*! \fn virtual bool QCPLayout::take(QCPLayoutElement* element) = 0
+
+ Removes the specified \a element from the layout and returns true on success.
+
+ If the \a element isn't in this layout, returns false.
+
+ Note that some layouts don't remove the respective cell right away but leave an empty cell after
+ successful removal of the layout element. To collapse empty cells, use \ref simplify.
+
+ \see takeAt
+*/
+
+/* end documentation of pure virtual functions */
+
+/*!
+ Creates an instance of QCPLayout and sets default values. Note that since QCPLayout
+ is an abstract base class, it can't be instantiated directly.
+*/
+QCPLayout::QCPLayout()
+{
+}
+
+/*!
+ If \a phase is \ref upLayout, calls \ref updateLayout, which subclasses may reimplement to
+ reposition and resize their cells.
+
+ Finally, the call is propagated down to all child \ref QCPLayoutElement "QCPLayoutElements".
+
+ For details about this method and the update phases, see the documentation of \ref
+ QCPLayoutElement::update.
+*/
+void QCPLayout::update(UpdatePhase phase)
+{
+ QCPLayoutElement::update(phase);
+
+ // set child element rects according to layout:
+ if (phase == upLayout)
+ updateLayout();
+
+ // propagate update call to child elements:
+ const int elCount = elementCount();
+ for (int i=0; iupdate(phase);
+ }
+}
+
+/* inherits documentation from base class */
+QList QCPLayout::elements(bool recursive) const
+{
+ const int c = elementCount();
+ QList result;
+#if QT_VERSION >= QT_VERSION_CHECK(4, 7, 0)
+ result.reserve(c);
+#endif
+ for (int i=0; ielements(recursive);
+ }
+ }
+ return result;
+}
+
+/*!
+ Simplifies the layout by collapsing empty cells. The exact behavior depends on subclasses, the
+ default implementation does nothing.
+
+ Not all layouts need simplification. For example, QCPLayoutInset doesn't use explicit
+ simplification while QCPLayoutGrid does.
+*/
+void QCPLayout::simplify()
+{
+}
+
+/*!
+ Removes and deletes the element at the provided \a index. Returns true on success. If \a index is
+ invalid or points to an empty cell, returns false.
+
+ This function internally uses \ref takeAt to remove the element from the layout and then deletes
+ the returned element. Note that some layouts don't remove the respective cell right away but leave an
+ empty cell after successful removal of the layout element. To collapse empty cells, use \ref
+ simplify.
+
+ \see remove, takeAt
+*/
+bool QCPLayout::removeAt(int index)
+{
+ if (QCPLayoutElement *el = takeAt(index))
+ {
+ delete el;
+ return true;
+ } else
+ return false;
+}
+
+/*!
+ Removes and deletes the provided \a element. Returns true on success. If \a element is not in the
+ layout, returns false.
+
+ This function internally uses \ref takeAt to remove the element from the layout and then deletes
+ the element. Note that some layouts don't remove the respective cell right away but leave an
+ empty cell after successful removal of the layout element. To collapse empty cells, use \ref
+ simplify.
+
+ \see removeAt, take
+*/
+bool QCPLayout::remove(QCPLayoutElement *element)
+{
+ if (take(element))
+ {
+ delete element;
+ return true;
+ } else
+ return false;
+}
+
+/*!
+ Removes and deletes all layout elements in this layout. Finally calls \ref simplify to make sure
+ all empty cells are collapsed.
+
+ \see remove, removeAt
+*/
+void QCPLayout::clear()
+{
+ for (int i=elementCount()-1; i>=0; --i)
+ {
+ if (elementAt(i))
+ removeAt(i);
+ }
+ simplify();
+}
+
+/*!
+ Subclasses call this method to report changed (minimum/maximum) size constraints.
+
+ If the parent of this layout is again a QCPLayout, forwards the call to the parent's \ref
+ sizeConstraintsChanged. If the parent is a QWidget (i.e. is the \ref QCustomPlot::plotLayout of
+ QCustomPlot), calls QWidget::updateGeometry, so if the QCustomPlot widget is inside a Qt QLayout,
+ it may update itself and resize cells accordingly.
+*/
+void QCPLayout::sizeConstraintsChanged() const
+{
+ if (QWidget *w = qobject_cast(parent()))
+ w->updateGeometry();
+ else if (QCPLayout *l = qobject_cast(parent()))
+ l->sizeConstraintsChanged();
+}
+
+/*! \internal
+
+ Subclasses reimplement this method to update the position and sizes of the child elements/cells
+ via calling their \ref QCPLayoutElement::setOuterRect. The default implementation does nothing.
+
+ The geometry used as a reference is the inner \ref rect of this layout. Child elements should stay
+ within that rect.
+
+ \ref getSectionSizes may help with the reimplementation of this function.
+
+ \see update
+*/
+void QCPLayout::updateLayout()
+{
+}
+
+
+/*! \internal
+
+ Associates \a el with this layout. This is done by setting the \ref QCPLayoutElement::layout, the
+ \ref QCPLayerable::parentLayerable and the QObject parent to this layout.
+
+ Further, if \a el didn't previously have a parent plot, calls \ref
+ QCPLayerable::initializeParentPlot on \a el to set the paret plot.
+
+ This method is used by subclass specific methods that add elements to the layout. Note that this
+ method only changes properties in \a el. The removal from the old layout and the insertion into
+ the new layout must be done additionally.
+*/
+void QCPLayout::adoptElement(QCPLayoutElement *el)
+{
+ if (el)
+ {
+ el->mParentLayout = this;
+ el->setParentLayerable(this);
+ el->setParent(this);
+ if (!el->parentPlot())
+ el->initializeParentPlot(mParentPlot);
+ el->layoutChanged();
+ } else
+ qDebug() << Q_FUNC_INFO << "Null element passed";
+}
+
+/*! \internal
+
+ Disassociates \a el from this layout. This is done by setting the \ref QCPLayoutElement::layout
+ and the \ref QCPLayerable::parentLayerable to zero. The QObject parent is set to the parent
+ QCustomPlot.
+
+ This method is used by subclass specific methods that remove elements from the layout (e.g. \ref
+ take or \ref takeAt). Note that this method only changes properties in \a el. The removal from
+ the old layout must be done additionally.
+*/
+void QCPLayout::releaseElement(QCPLayoutElement *el)
+{
+ if (el)
+ {
+ el->mParentLayout = nullptr;
+ el->setParentLayerable(nullptr);
+ el->setParent(mParentPlot);
+ // Note: Don't initializeParentPlot(0) here, because layout element will stay in same parent plot
+ } else
+ qDebug() << Q_FUNC_INFO << "Null element passed";
+}
+
+/*! \internal
+
+ This is a helper function for the implementation of \ref updateLayout in subclasses.
+
+ It calculates the sizes of one-dimensional sections with provided constraints on maximum section
+ sizes, minimum section sizes, relative stretch factors and the final total size of all sections.
+
+ The QVector entries refer to the sections. Thus all QVectors must have the same size.
+
+ \a maxSizes gives the maximum allowed size of each section. If there shall be no maximum size
+ imposed, set all vector values to Qt's QWIDGETSIZE_MAX.
+
+ \a minSizes gives the minimum allowed size of each section. If there shall be no minimum size
+ imposed, set all vector values to zero. If the \a minSizes entries add up to a value greater than
+ \a totalSize, sections will be scaled smaller than the proposed minimum sizes. (In other words,
+ not exceeding the allowed total size is taken to be more important than not going below minimum
+ section sizes.)
+
+ \a stretchFactors give the relative proportions of the sections to each other. If all sections
+ shall be scaled equally, set all values equal. If the first section shall be double the size of
+ each individual other section, set the first number of \a stretchFactors to double the value of
+ the other individual values (e.g. {2, 1, 1, 1}).
+
+ \a totalSize is the value that the final section sizes will add up to. Due to rounding, the
+ actual sum may differ slightly. If you want the section sizes to sum up to exactly that value,
+ you could distribute the remaining difference on the sections.
+
+ The return value is a QVector containing the section sizes.
+*/
+QVector QCPLayout::getSectionSizes(QVector maxSizes, QVector minSizes, QVector stretchFactors, int totalSize) const
+{
+ if (maxSizes.size() != minSizes.size() || minSizes.size() != stretchFactors.size())
+ {
+ qDebug() << Q_FUNC_INFO << "Passed vector sizes aren't equal:" << maxSizes << minSizes << stretchFactors;
+ return QVector();
+ }
+ if (stretchFactors.isEmpty())
+ return QVector();
+ int sectionCount = stretchFactors.size();
+ QVector sectionSizes(sectionCount);
+ // if provided total size is forced smaller than total minimum size, ignore minimum sizes (squeeze sections):
+ int minSizeSum = 0;
+ for (int i=0; i minimumLockedSections;
+ QList unfinishedSections;
+ for (int i=0; i result(sectionCount);
+ for (int i=0; iminimumOuterSizeHint();
+ QSize minOuter = el->minimumSize(); // depending on sizeConstraitRect this might be with respect to inner rect, so possibly add margins in next four lines (preserving unset minimum of 0)
+ if (minOuter.width() > 0 && el->sizeConstraintRect() == QCPLayoutElement::scrInnerRect)
+ minOuter.rwidth() += el->margins().left() + el->margins().right();
+ if (minOuter.height() > 0 && el->sizeConstraintRect() == QCPLayoutElement::scrInnerRect)
+ minOuter.rheight() += el->margins().top() + el->margins().bottom();
+
+ return {minOuter.width() > 0 ? minOuter.width() : minOuterHint.width(),
+ minOuter.height() > 0 ? minOuter.height() : minOuterHint.height()};
+}
+
+/*! \internal
+
+ This is a helper function for the implementation of subclasses.
+
+ It returns the maximum size that should finally be used for the outer rect of the passed layout
+ element \a el.
+
+ It takes into account whether a manual maximum size is set (\ref
+ QCPLayoutElement::setMaximumSize), which size constraint is set (\ref
+ QCPLayoutElement::setSizeConstraintRect), as well as the maximum size hint, if no manual maximum
+ size was set (\ref QCPLayoutElement::maximumOuterSizeHint).
+*/
+QSize QCPLayout::getFinalMaximumOuterSize(const QCPLayoutElement *el)
+{
+ QSize maxOuterHint = el->maximumOuterSizeHint();
+ QSize maxOuter = el->maximumSize(); // depending on sizeConstraitRect this might be with respect to inner rect, so possibly add margins in next four lines (preserving unset maximum of QWIDGETSIZE_MAX)
+ if (maxOuter.width() < QWIDGETSIZE_MAX && el->sizeConstraintRect() == QCPLayoutElement::scrInnerRect)
+ maxOuter.rwidth() += el->margins().left() + el->margins().right();
+ if (maxOuter.height() < QWIDGETSIZE_MAX && el->sizeConstraintRect() == QCPLayoutElement::scrInnerRect)
+ maxOuter.rheight() += el->margins().top() + el->margins().bottom();
+
+ return {maxOuter.width() < QWIDGETSIZE_MAX ? maxOuter.width() : maxOuterHint.width(),
+ maxOuter.height() < QWIDGETSIZE_MAX ? maxOuter.height() : maxOuterHint.height()};
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//////////////////// QCPLayoutGrid
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/*! \class QCPLayoutGrid
+ \brief A layout that arranges child elements in a grid
+
+ Elements are laid out in a grid with configurable stretch factors (\ref setColumnStretchFactor,
+ \ref setRowStretchFactor) and spacing (\ref setColumnSpacing, \ref setRowSpacing).
+
+ Elements can be added to cells via \ref addElement. The grid is expanded if the specified row or
+ column doesn't exist yet. Whether a cell contains a valid layout element can be checked with \ref
+ hasElement, that element can be retrieved with \ref element. If rows and columns that only have
+ empty cells shall be removed, call \ref simplify. Removal of elements is either done by just
+ adding the element to a different layout or by using the QCPLayout interface \ref take or \ref
+ remove.
+
+ If you use \ref addElement(QCPLayoutElement*) without explicit parameters for \a row and \a
+ column, the grid layout will choose the position according to the current \ref setFillOrder and
+ the wrapping (\ref setWrap).
+
+ Row and column insertion can be performed with \ref insertRow and \ref insertColumn.
+*/
+
+/* start documentation of inline functions */
+
+/*! \fn int QCPLayoutGrid::rowCount() const
+
+ Returns the number of rows in the layout.
+
+ \see columnCount
+*/
+
+/*! \fn int QCPLayoutGrid::columnCount() const
+
+ Returns the number of columns in the layout.
+
+ \see rowCount
+*/
+
+/* end documentation of inline functions */
+
+/*!
+ Creates an instance of QCPLayoutGrid and sets default values.
+*/
+QCPLayoutGrid::QCPLayoutGrid() :
+ mColumnSpacing(5),
+ mRowSpacing(5),
+ mWrap(0),
+ mFillOrder(foColumnsFirst)
+{
+}
+
+QCPLayoutGrid::~QCPLayoutGrid()
+{
+ // clear all child layout elements. This is important because only the specific layouts know how
+ // to handle removing elements (clear calls virtual removeAt method to do that).
+ clear();
+}
+
+/*!
+ Returns the element in the cell in \a row and \a column.
+
+ Returns \c nullptr if either the row/column is invalid or if the cell is empty. In those cases, a
+ qDebug message is printed. To check whether a cell exists and isn't empty, use \ref hasElement.
+
+ \see addElement, hasElement
+*/
+QCPLayoutElement *QCPLayoutGrid::element(int row, int column) const
+{
+ if (row >= 0 && row < mElements.size())
+ {
+ if (column >= 0 && column < mElements.first().size())
+ {
+ if (QCPLayoutElement *result = mElements.at(row).at(column))
+ return result;
+ else
+ qDebug() << Q_FUNC_INFO << "Requested cell is empty. Row:" << row << "Column:" << column;
+ } else
+ qDebug() << Q_FUNC_INFO << "Invalid column. Row:" << row << "Column:" << column;
+ } else
+ qDebug() << Q_FUNC_INFO << "Invalid row. Row:" << row << "Column:" << column;
+ return nullptr;
+}
+
+
+/*! \overload
+
+ Adds the \a element to cell with \a row and \a column. If \a element is already in a layout, it
+ is first removed from there. If \a row or \a column don't exist yet, the layout is expanded
+ accordingly.
+
+ Returns true if the element was added successfully, i.e. if the cell at \a row and \a column
+ didn't already have an element.
+
+ Use the overload of this method without explicit row/column index to place the element according
+ to the configured fill order and wrapping settings.
+
+ \see element, hasElement, take, remove
+*/
+bool QCPLayoutGrid::addElement(int row, int column, QCPLayoutElement *element)
+{
+ if (!hasElement(row, column))
+ {
+ if (element && element->layout()) // remove from old layout first
+ element->layout()->take(element);
+ expandTo(row+1, column+1);
+ mElements[row][column] = element;
+ if (element)
+ adoptElement(element);
+ return true;
+ } else
+ qDebug() << Q_FUNC_INFO << "There is already an element in the specified row/column:" << row << column;
+ return false;
+}
+
+/*! \overload
+
+ Adds the \a element to the next empty cell according to the current fill order (\ref
+ setFillOrder) and wrapping (\ref setWrap). If \a element is already in a layout, it is first
+ removed from there. If necessary, the layout is expanded to hold the new element.
+
+ Returns true if the element was added successfully.
+
+ \see setFillOrder, setWrap, element, hasElement, take, remove
+*/
+bool QCPLayoutGrid::addElement(QCPLayoutElement *element)
+{
+ int rowIndex = 0;
+ int colIndex = 0;
+ if (mFillOrder == foColumnsFirst)
+ {
+ while (hasElement(rowIndex, colIndex))
+ {
+ ++colIndex;
+ if (colIndex >= mWrap && mWrap > 0)
+ {
+ colIndex = 0;
+ ++rowIndex;
+ }
+ }
+ } else
+ {
+ while (hasElement(rowIndex, colIndex))
+ {
+ ++rowIndex;
+ if (rowIndex >= mWrap && mWrap > 0)
+ {
+ rowIndex = 0;
+ ++colIndex;
+ }
+ }
+ }
+ return addElement(rowIndex, colIndex, element);
+}
+
+/*!
+ Returns whether the cell at \a row and \a column exists and contains a valid element, i.e. isn't
+ empty.
+
+ \see element
+*/
+bool QCPLayoutGrid::hasElement(int row, int column)
+{
+ if (row >= 0 && row < rowCount() && column >= 0 && column < columnCount())
+ return mElements.at(row).at(column);
+ else
+ return false;
+}
+
+/*!
+ Sets the stretch \a factor of \a column.
+
+ Stretch factors control the relative sizes of rows and columns. Cells will not be resized beyond
+ their minimum and maximum widths/heights, regardless of the stretch factor. (see \ref
+ QCPLayoutElement::setMinimumSize, \ref QCPLayoutElement::setMaximumSize, \ref
+ QCPLayoutElement::setSizeConstraintRect.)
+
+ The default stretch factor of newly created rows/columns is 1.
+
+ \see setColumnStretchFactors, setRowStretchFactor
+*/
+void QCPLayoutGrid::setColumnStretchFactor(int column, double factor)
+{
+ if (column >= 0 && column < columnCount())
+ {
+ if (factor > 0)
+ mColumnStretchFactors[column] = factor;
+ else
+ qDebug() << Q_FUNC_INFO << "Invalid stretch factor, must be positive:" << factor;
+ } else
+ qDebug() << Q_FUNC_INFO << "Invalid column:" << column;
+}
+
+/*!
+ Sets the stretch \a factors of all columns. \a factors must have the size \ref columnCount.
+
+ Stretch factors control the relative sizes of rows and columns. Cells will not be resized beyond
+ their minimum and maximum widths/heights, regardless of the stretch factor. (see \ref
+ QCPLayoutElement::setMinimumSize, \ref QCPLayoutElement::setMaximumSize, \ref
+ QCPLayoutElement::setSizeConstraintRect.)
+
+ The default stretch factor of newly created rows/columns is 1.
+
+ \see setColumnStretchFactor, setRowStretchFactors
+*/
+void QCPLayoutGrid::setColumnStretchFactors(const QList &factors)
+{
+ if (factors.size() == mColumnStretchFactors.size())
+ {
+ mColumnStretchFactors = factors;
+ for (int i=0; i= 0 && row < rowCount())
+ {
+ if (factor > 0)
+ mRowStretchFactors[row] = factor;
+ else
+ qDebug() << Q_FUNC_INFO << "Invalid stretch factor, must be positive:" << factor;
+ } else
+ qDebug() << Q_FUNC_INFO << "Invalid row:" << row;
+}
+
+/*!
+ Sets the stretch \a factors of all rows. \a factors must have the size \ref rowCount.
+
+ Stretch factors control the relative sizes of rows and columns. Cells will not be resized beyond
+ their minimum and maximum widths/heights, regardless of the stretch factor. (see \ref
+ QCPLayoutElement::setMinimumSize, \ref QCPLayoutElement::setMaximumSize, \ref
+ QCPLayoutElement::setSizeConstraintRect.)
+
+ The default stretch factor of newly created rows/columns is 1.
+
+ \see setRowStretchFactor, setColumnStretchFactors
+*/
+void QCPLayoutGrid::setRowStretchFactors(const QList &factors)
+{
+ if (factors.size() == mRowStretchFactors.size())
+ {
+ mRowStretchFactors = factors;
+ for (int i=0; i tempElements;
+ if (rearrange)
+ {
+ tempElements.reserve(elCount);
+ for (int i=0; i());
+ mRowStretchFactors.append(1);
+ }
+ // go through rows and expand columns as necessary:
+ int newColCount = qMax(columnCount(), newColumnCount);
+ for (int i=0; i rowCount())
+ newIndex = rowCount();
+
+ mRowStretchFactors.insert(newIndex, 1);
+ QList newRow;
+ for (int col=0; col columnCount())
+ newIndex = columnCount();
+
+ mColumnStretchFactors.insert(newIndex, 1);
+ for (int row=0; row= 0 && row < rowCount())
+ {
+ if (column >= 0 && column < columnCount())
+ {
+ switch (mFillOrder)
+ {
+ case foRowsFirst: return column*rowCount() + row;
+ case foColumnsFirst: return row*columnCount() + column;
+ }
+ } else
+ qDebug() << Q_FUNC_INFO << "row index out of bounds:" << row;
+ } else
+ qDebug() << Q_FUNC_INFO << "column index out of bounds:" << column;
+ return 0;
+}
+
+/*!
+ Converts the linear index to row and column indices and writes the result to \a row and \a
+ column.
+
+ The way the cells are indexed depends on \ref setFillOrder. If it is \ref foRowsFirst, the
+ indices increase left to right and then top to bottom. If it is \ref foColumnsFirst, the indices
+ increase top to bottom and then left to right.
+
+ If there are no cells (i.e. column or row count is zero), sets \a row and \a column to -1.
+
+ For the retrieved \a row and \a column to be valid, the passed \a index must be valid itself,
+ i.e. greater or equal to zero and smaller than the current \ref elementCount.
+
+ \see rowColToIndex
+*/
+void QCPLayoutGrid::indexToRowCol(int index, int &row, int &column) const
+{
+ row = -1;
+ column = -1;
+ const int nCols = columnCount();
+ const int nRows = rowCount();
+ if (nCols == 0 || nRows == 0)
+ return;
+ if (index < 0 || index >= elementCount())
+ {
+ qDebug() << Q_FUNC_INFO << "index out of bounds:" << index;
+ return;
+ }
+
+ switch (mFillOrder)
+ {
+ case foRowsFirst:
+ {
+ column = index / nRows;
+ row = index % nRows;
+ break;
+ }
+ case foColumnsFirst:
+ {
+ row = index / nCols;
+ column = index % nCols;
+ break;
+ }
+ }
+}
+
+/* inherits documentation from base class */
+void QCPLayoutGrid::updateLayout()
+{
+ QVector minColWidths, minRowHeights, maxColWidths, maxRowHeights;
+ getMinimumRowColSizes(&minColWidths, &minRowHeights);
+ getMaximumRowColSizes(&maxColWidths, &maxRowHeights);
+
+ int totalRowSpacing = (rowCount()-1) * mRowSpacing;
+ int totalColSpacing = (columnCount()-1) * mColumnSpacing;
+ QVector colWidths = getSectionSizes(maxColWidths, minColWidths, mColumnStretchFactors.toVector(), mRect.width()-totalColSpacing);
+ QVector rowHeights = getSectionSizes(maxRowHeights, minRowHeights, mRowStretchFactors.toVector(), mRect.height()-totalRowSpacing);
+
+ // go through cells and set rects accordingly:
+ int yOffset = mRect.top();
+ for (int row=0; row 0)
+ yOffset += rowHeights.at(row-1)+mRowSpacing;
+ int xOffset = mRect.left();
+ for (int col=0; col 0)
+ xOffset += colWidths.at(col-1)+mColumnSpacing;
+ if (mElements.at(row).at(col))
+ mElements.at(row).at(col)->setOuterRect(QRect(xOffset, yOffset, colWidths.at(col), rowHeights.at(row)));
+ }
+ }
+}
+
+/*!
+ \seebaseclassmethod
+
+ Note that the association of the linear \a index to the row/column based cells depends on the
+ current setting of \ref setFillOrder.
+
+ \see rowColToIndex
+*/
+QCPLayoutElement *QCPLayoutGrid::elementAt(int index) const
+{
+ if (index >= 0 && index < elementCount())
+ {
+ int row, col;
+ indexToRowCol(index, row, col);
+ return mElements.at(row).at(col);
+ } else
+ return nullptr;
+}
+
+/*!
+ \seebaseclassmethod
+
+ Note that the association of the linear \a index to the row/column based cells depends on the
+ current setting of \ref setFillOrder.
+
+ \see rowColToIndex
+*/
+QCPLayoutElement *QCPLayoutGrid::takeAt(int index)
+{
+ if (QCPLayoutElement *el = elementAt(index))
+ {
+ releaseElement(el);
+ int row, col;
+ indexToRowCol(index, row, col);
+ mElements[row][col] = nullptr;
+ return el;
+ } else
+ {
+ qDebug() << Q_FUNC_INFO << "Attempt to take invalid index:" << index;
+ return nullptr;
+ }
+}
+
+/* inherits documentation from base class */
+bool QCPLayoutGrid::take(QCPLayoutElement *element)
+{
+ if (element)
+ {
+ for (int i=0; i QCPLayoutGrid::elements(bool recursive) const
+{
+ QList result;
+ const int elCount = elementCount();
+#if QT_VERSION >= QT_VERSION_CHECK(4, 7, 0)
+ result.reserve(elCount);
+#endif
+ for (int i=0; ielements(recursive);
+ }
+ }
+ return result;
+}
+
+/*!
+ Simplifies the layout by collapsing rows and columns which only contain empty cells.
+*/
+void QCPLayoutGrid::simplify()
+{
+ // remove rows with only empty cells:
+ for (int row=rowCount()-1; row>=0; --row)
+ {
+ bool hasElements = false;
+ for (int col=0; col=0; --col)
+ {
+ bool hasElements = false;
+ for (int row=0; row minColWidths, minRowHeights;
+ getMinimumRowColSizes(&minColWidths, &minRowHeights);
+ QSize result(0, 0);
+ foreach (int w, minColWidths)
+ result.rwidth() += w;
+ foreach (int h, minRowHeights)
+ result.rheight() += h;
+ result.rwidth() += qMax(0, columnCount()-1) * mColumnSpacing;
+ result.rheight() += qMax(0, rowCount()-1) * mRowSpacing;
+ result.rwidth() += mMargins.left()+mMargins.right();
+ result.rheight() += mMargins.top()+mMargins.bottom();
+ return result;
+}
+
+/* inherits documentation from base class */
+QSize QCPLayoutGrid::maximumOuterSizeHint() const
+{
+ QVector maxColWidths, maxRowHeights;
+ getMaximumRowColSizes(&maxColWidths, &maxRowHeights);
+
+ QSize result(0, 0);
+ foreach (int w, maxColWidths)
+ result.setWidth(qMin(result.width()+w, QWIDGETSIZE_MAX));
+ foreach (int h, maxRowHeights)
+ result.setHeight(qMin(result.height()+h, QWIDGETSIZE_MAX));
+ result.rwidth() += qMax(0, columnCount()-1) * mColumnSpacing;
+ result.rheight() += qMax(0, rowCount()-1) * mRowSpacing;
+ result.rwidth() += mMargins.left()+mMargins.right();
+ result.rheight() += mMargins.top()+mMargins.bottom();
+ if (result.height() > QWIDGETSIZE_MAX)
+ result.setHeight(QWIDGETSIZE_MAX);
+ if (result.width() > QWIDGETSIZE_MAX)
+ result.setWidth(QWIDGETSIZE_MAX);
+ return result;
+}
+
+/*! \internal
+
+ Places the minimum column widths and row heights into \a minColWidths and \a minRowHeights
+ respectively.
+
+ The minimum height of a row is the largest minimum height of any element's outer rect in that
+ row. The minimum width of a column is the largest minimum width of any element's outer rect in
+ that column.
+
+ This is a helper function for \ref updateLayout.
+
+ \see getMaximumRowColSizes
+*/
+void QCPLayoutGrid::getMinimumRowColSizes(QVector *minColWidths, QVector *minRowHeights) const
+{
+ *minColWidths = QVector(columnCount(), 0);
+ *minRowHeights = QVector(rowCount(), 0);
+ for (int row=0; rowat(col) < minSize.width())
+ (*minColWidths)[col] = minSize.width();
+ if (minRowHeights->at(row) < minSize.height())
+ (*minRowHeights)[row] = minSize.height();
+ }
+ }
+ }
+}
+
+/*! \internal
+
+ Places the maximum column widths and row heights into \a maxColWidths and \a maxRowHeights
+ respectively.
+
+ The maximum height of a row is the smallest maximum height of any element's outer rect in that
+ row. The maximum width of a column is the smallest maximum width of any element's outer rect in
+ that column.
+
+ This is a helper function for \ref updateLayout.
+
+ \see getMinimumRowColSizes
+*/
+void QCPLayoutGrid::getMaximumRowColSizes(QVector *maxColWidths, QVector *maxRowHeights) const
+{
+ *maxColWidths = QVector(columnCount(), QWIDGETSIZE_MAX);
+ *maxRowHeights = QVector(rowCount(), QWIDGETSIZE_MAX);
+ for (int row=0; rowat(col) > maxSize.width())
+ (*maxColWidths)[col] = maxSize.width();
+ if (maxRowHeights->at(row) > maxSize.height())
+ (*maxRowHeights)[row] = maxSize.height();
+ }
+ }
+ }
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//////////////////// QCPLayoutInset
+////////////////////////////////////////////////////////////////////////////////////////////////////
+/*! \class QCPLayoutInset
+ \brief A layout that places child elements aligned to the border or arbitrarily positioned
+
+ Elements are placed either aligned to the border or at arbitrary position in the area of the
+ layout. Which placement applies is controlled with the \ref InsetPlacement (\ref
+ setInsetPlacement).
+
+ Elements are added via \ref addElement(QCPLayoutElement *element, Qt::Alignment alignment) or
+ addElement(QCPLayoutElement *element, const QRectF &rect). If the first method is used, the inset
+ placement will default to \ref ipBorderAligned and the element will be aligned according to the
+ \a alignment parameter. The second method defaults to \ref ipFree and allows placing elements at
+ arbitrary position and size, defined by \a rect.
+
+ The alignment or rect can be set via \ref setInsetAlignment or \ref setInsetRect, respectively.
+
+ This is the layout that every QCPAxisRect has as \ref QCPAxisRect::insetLayout.
+*/
+
+/* start documentation of inline functions */
+
+/*! \fn virtual void QCPLayoutInset::simplify()
+
+ The QCPInsetLayout does not need simplification since it can never have empty cells due to its
+ linear index structure. This method does nothing.
+*/
+
+/* end documentation of inline functions */
+
+/*!
+ Creates an instance of QCPLayoutInset and sets default values.
+*/
+QCPLayoutInset::QCPLayoutInset()
+{
+}
+
+QCPLayoutInset::~QCPLayoutInset()
+{
+ // clear all child layout elements. This is important because only the specific layouts know how
+ // to handle removing elements (clear calls virtual removeAt method to do that).
+ clear();
+}
+
+/*!
+ Returns the placement type of the element with the specified \a index.
+*/
+QCPLayoutInset::InsetPlacement QCPLayoutInset::insetPlacement(int index) const
+{
+ if (elementAt(index))
+ return mInsetPlacement.at(index);
+ else
+ {
+ qDebug() << Q_FUNC_INFO << "Invalid element index:" << index;
+ return ipFree;
+ }
+}
+
+/*!
+ Returns the alignment of the element with the specified \a index. The alignment only has a
+ meaning, if the inset placement (\ref setInsetPlacement) is \ref ipBorderAligned.
+*/
+Qt::Alignment QCPLayoutInset::insetAlignment(int index) const
+{
+ if (elementAt(index))
+ return mInsetAlignment.at(index);
+ else
+ {
+ qDebug() << Q_FUNC_INFO << "Invalid element index:" << index;
+#if QT_VERSION < QT_VERSION_CHECK(5, 2, 0)
+ return nullptr;
+#else
+ return {};
+#endif
+ }
+}
+
+/*!
+ Returns the rect of the element with the specified \a index. The rect only has a
+ meaning, if the inset placement (\ref setInsetPlacement) is \ref ipFree.
+*/
+QRectF QCPLayoutInset::insetRect(int index) const
+{
+ if (elementAt(index))
+ return mInsetRect.at(index);
+ else
+ {
+ qDebug() << Q_FUNC_INFO << "Invalid element index:" << index;
+ return {};
+ }
+}
+
+/*!
+ Sets the inset placement type of the element with the specified \a index to \a placement.
+
+ \see InsetPlacement
+*/
+void QCPLayoutInset::setInsetPlacement(int index, QCPLayoutInset::InsetPlacement placement)
+{
+ if (elementAt(index))
+ mInsetPlacement[index] = placement;
+ else
+ qDebug() << Q_FUNC_INFO << "Invalid element index:" << index;
+}
+
+/*!
+ If the inset placement (\ref setInsetPlacement) is \ref ipBorderAligned, this function
+ is used to set the alignment of the element with the specified \a index to \a alignment.
+
+ \a alignment is an or combination of the following alignment flags: Qt::AlignLeft,
+ Qt::AlignHCenter, Qt::AlighRight, Qt::AlignTop, Qt::AlignVCenter, Qt::AlignBottom. Any other
+ alignment flags will be ignored.
+*/
+void QCPLayoutInset::setInsetAlignment(int index, Qt::Alignment alignment)
+{
+ if (elementAt(index))
+ mInsetAlignment[index] = alignment;
+ else
+ qDebug() << Q_FUNC_INFO << "Invalid element index:" << index;
+}
+
+/*!
+ If the inset placement (\ref setInsetPlacement) is \ref ipFree, this function is used to set the
+ position and size of the element with the specified \a index to \a rect.
+
+ \a rect is given in fractions of the whole inset layout rect. So an inset with rect (0, 0, 1, 1)
+ will span the entire layout. An inset with rect (0.6, 0.1, 0.35, 0.35) will be in the top right
+ corner of the layout, with 35% width and height of the parent layout.
+
+ Note that the minimum and maximum sizes of the embedded element (\ref
+ QCPLayoutElement::setMinimumSize, \ref QCPLayoutElement::setMaximumSize) are enforced.
+*/
+void QCPLayoutInset::setInsetRect(int index, const QRectF &rect)
+{
+ if (elementAt(index))
+ mInsetRect[index] = rect;
+ else
+ qDebug() << Q_FUNC_INFO << "Invalid element index:" << index;
+}
+
+/* inherits documentation from base class */
+void QCPLayoutInset::updateLayout()
+{
+ for (int i=0; i finalMaxSize.width())
+ insetRect.setWidth(finalMaxSize.width());
+ if (insetRect.size().height() > finalMaxSize.height())
+ insetRect.setHeight(finalMaxSize.height());
+ } else if (mInsetPlacement.at(i) == ipBorderAligned)
+ {
+ insetRect.setSize(finalMinSize);
+ Qt::Alignment al = mInsetAlignment.at(i);
+ if (al.testFlag(Qt::AlignLeft)) insetRect.moveLeft(rect().x());
+ else if (al.testFlag(Qt::AlignRight)) insetRect.moveRight(rect().x()+rect().width());
+ else insetRect.moveLeft(int( rect().x()+rect().width()*0.5-finalMinSize.width()*0.5 )); // default to Qt::AlignHCenter
+ if (al.testFlag(Qt::AlignTop)) insetRect.moveTop(rect().y());
+ else if (al.testFlag(Qt::AlignBottom)) insetRect.moveBottom(rect().y()+rect().height());
+ else insetRect.moveTop(int( rect().y()+rect().height()*0.5-finalMinSize.height()*0.5 )); // default to Qt::AlignVCenter
+ }
+ mElements.at(i)->setOuterRect(insetRect);
+ }
+}
+
+/* inherits documentation from base class */
+int QCPLayoutInset::elementCount() const
+{
+ return mElements.size();
+}
+
+/* inherits documentation from base class */
+QCPLayoutElement *QCPLayoutInset::elementAt(int index) const
+{
+ if (index >= 0 && index < mElements.size())
+ return mElements.at(index);
+ else
+ return nullptr;
+}
+
+/* inherits documentation from base class */
+QCPLayoutElement *QCPLayoutInset::takeAt(int index)
+{
+ if (QCPLayoutElement *el = elementAt(index))
+ {
+ releaseElement(el);
+ mElements.removeAt(index);
+ mInsetPlacement.removeAt(index);
+ mInsetAlignment.removeAt(index);
+ mInsetRect.removeAt(index);
+ return el;
+ } else
+ {
+ qDebug() << Q_FUNC_INFO << "Attempt to take invalid index:" << index;
+ return nullptr;
+ }
+}
+
+/* inherits documentation from base class */
+bool QCPLayoutInset::take(QCPLayoutElement *element)
+{
+ if (element)
+ {
+ for (int i=0; irealVisibility() && el->selectTest(pos, onlySelectable) >= 0)
+ return mParentPlot->selectionTolerance()*0.99;
+ }
+ return -1;
+}
+
+/*!
+ Adds the specified \a element to the layout as an inset aligned at the border (\ref
+ setInsetAlignment is initialized with \ref ipBorderAligned). The alignment is set to \a
+ alignment.
+
+ \a alignment is an or combination of the following alignment flags: Qt::AlignLeft,
+ Qt::AlignHCenter, Qt::AlighRight, Qt::AlignTop, Qt::AlignVCenter, Qt::AlignBottom. Any other
+ alignment flags will be ignored.
+
+ \see addElement(QCPLayoutElement *element, const QRectF &rect)
+*/
+void QCPLayoutInset::addElement(QCPLayoutElement *element, Qt::Alignment alignment)
+{
+ if (element)
+ {
+ if (element->layout()) // remove from old layout first
+ element->layout()->take(element);
+ mElements.append(element);
+ mInsetPlacement.append(ipBorderAligned);
+ mInsetAlignment.append(alignment);
+ mInsetRect.append(QRectF(0.6, 0.6, 0.4, 0.4));
+ adoptElement(element);
+ } else
+ qDebug() << Q_FUNC_INFO << "Can't add nullptr element";
+}
+
+/*!
+ Adds the specified \a element to the layout as an inset with free positioning/sizing (\ref
+ setInsetAlignment is initialized with \ref ipFree). The position and size is set to \a
+ rect.
+
+ \a rect is given in fractions of the whole inset layout rect. So an inset with rect (0, 0, 1, 1)
+ will span the entire layout. An inset with rect (0.6, 0.1, 0.35, 0.35) will be in the top right
+ corner of the layout, with 35% width and height of the parent layout.
+
+ \see addElement(QCPLayoutElement *element, Qt::Alignment alignment)
+*/
+void QCPLayoutInset::addElement(QCPLayoutElement *element, const QRectF &rect)
+{
+ if (element)
+ {
+ if (element->layout()) // remove from old layout first
+ element->layout()->take(element);
+ mElements.append(element);
+ mInsetPlacement.append(ipFree);
+ mInsetAlignment.append(Qt::AlignRight|Qt::AlignTop);
+ mInsetRect.append(rect);
+ adoptElement(element);
+ } else
+ qDebug() << Q_FUNC_INFO << "Can't add nullptr element";
+}
+/* end of 'src/layout.cpp' */
+
+
+/* including file 'src/lineending.cpp' */
+/* modified 2022-11-06T12:45:56, size 11189 */
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//////////////////// QCPLineEnding
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/*! \class QCPLineEnding
+ \brief Handles the different ending decorations for line-like items
+
+ \image html QCPLineEnding.png "The various ending styles currently supported"
+
+ For every ending a line-like item has, an instance of this class exists. For example, QCPItemLine
+ has two endings which can be set with QCPItemLine::setHead and QCPItemLine::setTail.
+
+ The styles themselves are defined via the enum QCPLineEnding::EndingStyle. Most decorations can
+ be modified regarding width and length, see \ref setWidth and \ref setLength. The direction of
+ the ending decoration (e.g. direction an arrow is pointing) is controlled by the line-like item.
+ For example, when both endings of a QCPItemLine are set to be arrows, they will point to opposite
+ directions, e.g. "outward". This can be changed by \ref setInverted, which would make the
+ respective arrow point inward.
+
+ Note that due to the overloaded QCPLineEnding constructor, you may directly specify a
+ QCPLineEnding::EndingStyle where actually a QCPLineEnding is expected, e.g.
+ \snippet documentation/doc-code-snippets/mainwindow.cpp qcplineending-sethead
+*/
+
+/*!
+ Creates a QCPLineEnding instance with default values (style \ref esNone).
+*/
+QCPLineEnding::QCPLineEnding() :
+ mStyle(esNone),
+ mWidth(8),
+ mLength(10),
+ mInverted(false)
+{
+}
+
+/*!
+ Creates a QCPLineEnding instance with the specified values.
+*/
+QCPLineEnding::QCPLineEnding(QCPLineEnding::EndingStyle style, double width, double length, bool inverted) :
+ mStyle(style),
+ mWidth(width),
+ mLength(length),
+ mInverted(inverted)
+{
+}
+
+/*!
+ Sets the style of the ending decoration.
+*/
+void QCPLineEnding::setStyle(QCPLineEnding::EndingStyle style)
+{
+ mStyle = style;
+}
+
+/*!
+ Sets the width of the ending decoration, if the style supports it. On arrows, for example, the
+ width defines the size perpendicular to the arrow's pointing direction.
+
+ \see setLength
+*/
+void QCPLineEnding::setWidth(double width)
+{
+ mWidth = width;
+}
+
+/*!
+ Sets the length of the ending decoration, if the style supports it. On arrows, for example, the
+ length defines the size in pointing direction.
+
+ \see setWidth
+*/
+void QCPLineEnding::setLength(double length)
+{
+ mLength = length;
+}
+
+/*!
+ Sets whether the ending decoration shall be inverted. For example, an arrow decoration will point
+ inward when \a inverted is set to true.
+
+ Note that also the \a width direction is inverted. For symmetrical ending styles like arrows or
+ discs, this doesn't make a difference. However, asymmetric styles like \ref esHalfBar are
+ affected by it, which can be used to control to which side the half bar points to.
+*/
+void QCPLineEnding::setInverted(bool inverted)
+{
+ mInverted = inverted;
+}
+
+/*! \internal
+
+ Returns the maximum pixel radius the ending decoration might cover, starting from the position
+ the decoration is drawn at (typically a line ending/\ref QCPItemPosition of an item).
+
+ This is relevant for clipping. Only omit painting of the decoration when the position where the
+ decoration is supposed to be drawn is farther away from the clipping rect than the returned
+ distance.
+*/
+double QCPLineEnding::boundingDistance() const
+{
+ switch (mStyle)
+ {
+ case esNone:
+ return 0;
+
+ case esFlatArrow:
+ case esSpikeArrow:
+ case esLineArrow:
+ case esSkewedBar:
+ return qSqrt(mWidth*mWidth+mLength*mLength); // items that have width and length
+
+ case esDisc:
+ case esSquare:
+ case esDiamond:
+ case esBar:
+ case esHalfBar:
+ return mWidth*1.42; // items that only have a width -> width*sqrt(2)
+
+ }
+ return 0;
+}
+
+/*!
+ Starting from the origin of this line ending (which is style specific), returns the length
+ covered by the line ending symbol, in backward direction.
+
+ For example, the \ref esSpikeArrow has a shorter real length than a \ref esFlatArrow, even if
+ both have the same \ref setLength value, because the spike arrow has an inward curved back, which
+ reduces the length along its center axis (the drawing origin for arrows is at the tip).
+
+ This function is used for precise, style specific placement of line endings, for example in
+ QCPAxes.
+*/
+double QCPLineEnding::realLength() const
+{
+ switch (mStyle)
+ {
+ case esNone:
+ case esLineArrow:
+ case esSkewedBar:
+ case esBar:
+ case esHalfBar:
+ return 0;
+
+ case esFlatArrow:
+ return mLength;
+
+ case esDisc:
+ case esSquare:
+ case esDiamond:
+ return mWidth*0.5;
+
+ case esSpikeArrow:
+ return mLength*0.8;
+ }
+ return 0;
+}
+
+/*! \internal
+
+ Draws the line ending with the specified \a painter at the position \a pos. The direction of the
+ line ending is controlled with \a dir.
+*/
+void QCPLineEnding::draw(QCPPainter *painter, const QCPVector2D &pos, const QCPVector2D &dir) const
+{
+ if (mStyle == esNone)
+ return;
+
+ QCPVector2D lengthVec = dir.normalized() * mLength*(mInverted ? -1 : 1);
+ if (lengthVec.isNull())
+ lengthVec = QCPVector2D(1, 0);
+ QCPVector2D widthVec = dir.normalized().perpendicular() * mWidth*0.5*(mInverted ? -1 : 1);
+
+ QPen penBackup = painter->pen();
+ QBrush brushBackup = painter->brush();
+ QPen miterPen = penBackup;
+ miterPen.setJoinStyle(Qt::MiterJoin); // to make arrow heads spikey
+ QBrush brush(painter->pen().color(), Qt::SolidPattern);
+ switch (mStyle)
+ {
+ case esNone: break;
+ case esFlatArrow:
+ {
+ QPointF points[3] = {pos.toPointF(),
+ (pos-lengthVec+widthVec).toPointF(),
+ (pos-lengthVec-widthVec).toPointF()
+ };
+ painter->setPen(miterPen);
+ painter->setBrush(brush);
+ painter->drawConvexPolygon(points, 3);
+ painter->setBrush(brushBackup);
+ painter->setPen(penBackup);
+ break;
+ }
+ case esSpikeArrow:
+ {
+ QPointF points[4] = {pos.toPointF(),
+ (pos-lengthVec+widthVec).toPointF(),
+ (pos-lengthVec*0.8).toPointF(),
+ (pos-lengthVec-widthVec).toPointF()
+ };
+ painter->setPen(miterPen);
+ painter->setBrush(brush);
+ painter->drawConvexPolygon(points, 4);
+ painter->setBrush(brushBackup);
+ painter->setPen(penBackup);
+ break;
+ }
+ case esLineArrow:
+ {
+ QPointF points[3] = {(pos-lengthVec+widthVec).toPointF(),
+ pos.toPointF(),
+ (pos-lengthVec-widthVec).toPointF()
+ };
+ painter->setPen(miterPen);
+ painter->drawPolyline(points, 3);
+ painter->setPen(penBackup);
+ break;
+ }
+ case esDisc:
+ {
+ painter->setBrush(brush);
+ painter->drawEllipse(pos.toPointF(), mWidth*0.5, mWidth*0.5);
+ painter->setBrush(brushBackup);
+ break;
+ }
+ case esSquare:
+ {
+ QCPVector2D widthVecPerp = widthVec.perpendicular();
+ QPointF points[4] = {(pos-widthVecPerp+widthVec).toPointF(),
+ (pos-widthVecPerp-widthVec).toPointF(),
+ (pos+widthVecPerp-widthVec).toPointF(),
+ (pos+widthVecPerp+widthVec).toPointF()
+ };
+ painter->setPen(miterPen);
+ painter->setBrush(brush);
+ painter->drawConvexPolygon(points, 4);
+ painter->setBrush(brushBackup);
+ painter->setPen(penBackup);
+ break;
+ }
+ case esDiamond:
+ {
+ QCPVector2D widthVecPerp = widthVec.perpendicular();
+ QPointF points[4] = {(pos-widthVecPerp).toPointF(),
+ (pos-widthVec).toPointF(),
+ (pos+widthVecPerp).toPointF(),
+ (pos+widthVec).toPointF()
+ };
+ painter->setPen(miterPen);
+ painter->setBrush(brush);
+ painter->drawConvexPolygon(points, 4);
+ painter->setBrush(brushBackup);
+ painter->setPen(penBackup);
+ break;
+ }
+ case esBar:
+ {
+ painter->drawLine((pos+widthVec).toPointF(), (pos-widthVec).toPointF());
+ break;
+ }
+ case esHalfBar:
+ {
+ painter->drawLine((pos+widthVec).toPointF(), pos.toPointF());
+ break;
+ }
+ case esSkewedBar:
+ {
+ QCPVector2D shift;
+ if (!qFuzzyIsNull(painter->pen().widthF()) || painter->modes().testFlag(QCPPainter::pmNonCosmetic))
+ shift = dir.normalized()*qMax(qreal(1.0), painter->pen().widthF())*qreal(0.5);
+ // if drawing with thick (non-cosmetic) pen, shift bar a little in line direction to prevent line from sticking through bar slightly
+ painter->drawLine((pos+widthVec+lengthVec*0.2*(mInverted?-1:1)+shift).toPointF(),
+ (pos-widthVec-lengthVec*0.2*(mInverted?-1:1)+shift).toPointF());
+ break;
+ }
+ }
+}
+
+/*! \internal
+ \overload
+
+ Draws the line ending. The direction is controlled with the \a angle parameter in radians.
+*/
+void QCPLineEnding::draw(QCPPainter *painter, const QCPVector2D &pos, double angle) const
+{
+ draw(painter, pos, QCPVector2D(qCos(angle), qSin(angle)));
+}
+/* end of 'src/lineending.cpp' */
+
+
+/* including file 'src/axis/labelpainter.cpp' */
+/* modified 2022-11-06T12:45:56, size 27519 */
+
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//////////////////// QCPLabelPainterPrivate
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/*! \class QCPLabelPainterPrivate
+
+ \internal
+ \brief (Private)
+
+ This is a private class and not part of the public QCustomPlot interface.
+
+*/
+
+const QChar QCPLabelPainterPrivate::SymbolDot(183);
+const QChar QCPLabelPainterPrivate::SymbolCross(215);
+
+/*!
+ Constructs a QCPLabelPainterPrivate instance. Make sure to not create a new
+ instance on every redraw, to utilize the caching mechanisms.
+
+ the \a parentPlot does not take ownership of the label painter. Make sure
+ to delete it appropriately.
+*/
+QCPLabelPainterPrivate::QCPLabelPainterPrivate(QCustomPlot *parentPlot) :
+ mAnchorMode(amRectangular),
+ mAnchorSide(asLeft),
+ mAnchorReferenceType(artNormal),
+ mColor(Qt::black),
+ mPadding(0),
+ mRotation(0),
+ mSubstituteExponent(true),
+ mMultiplicationSymbol(QChar(215)),
+ mAbbreviateDecimalPowers(false),
+ mParentPlot(parentPlot),
+ mLabelCache(16)
+{
+ analyzeFontMetrics();
+}
+
+QCPLabelPainterPrivate::~QCPLabelPainterPrivate()
+{
+}
+
+void QCPLabelPainterPrivate::setAnchorSide(AnchorSide side)
+{
+ mAnchorSide = side;
+}
+
+void QCPLabelPainterPrivate::setAnchorMode(AnchorMode mode)
+{
+ mAnchorMode = mode;
+}
+
+void QCPLabelPainterPrivate::setAnchorReference(const QPointF &pixelPoint)
+{
+ mAnchorReference = pixelPoint;
+}
+
+void QCPLabelPainterPrivate::setAnchorReferenceType(AnchorReferenceType type)
+{
+ mAnchorReferenceType = type;
+}
+
+void QCPLabelPainterPrivate::setFont(const QFont &font)
+{
+ if (mFont != font)
+ {
+ mFont = font;
+ analyzeFontMetrics();
+ }
+}
+
+void QCPLabelPainterPrivate::setColor(const QColor &color)
+{
+ mColor = color;
+}
+
+void QCPLabelPainterPrivate::setPadding(int padding)
+{
+ mPadding = padding;
+}
+
+void QCPLabelPainterPrivate::setRotation(double rotation)
+{
+ mRotation = qBound(-90.0, rotation, 90.0);
+}
+
+void QCPLabelPainterPrivate::setSubstituteExponent(bool enabled)
+{
+ mSubstituteExponent = enabled;
+}
+
+void QCPLabelPainterPrivate::setMultiplicationSymbol(QChar symbol)
+{
+ mMultiplicationSymbol = symbol;
+}
+
+void QCPLabelPainterPrivate::setAbbreviateDecimalPowers(bool enabled)
+{
+ mAbbreviateDecimalPowers = enabled;
+}
+
+void QCPLabelPainterPrivate::setCacheSize(int labelCount)
+{
+ mLabelCache.setMaxCost(labelCount);
+}
+
+int QCPLabelPainterPrivate::cacheSize() const
+{
+ return mLabelCache.maxCost();
+}
+
+void QCPLabelPainterPrivate::drawTickLabel(QCPPainter *painter, const QPointF &tickPos, const QString &text)
+{
+ double realRotation = mRotation;
+
+ AnchorSide realSide = mAnchorSide;
+ // for circular axes, the anchor side is determined depending on the quadrant of tickPos with respect to mCircularReference
+ if (mAnchorMode == amSkewedUpright)
+ {
+ realSide = skewedAnchorSide(tickPos, 0.2, 0.3);
+ } else if (mAnchorMode == amSkewedRotated) // in this mode every label is individually rotated to match circle tangent
+ {
+ realSide = skewedAnchorSide(tickPos, 0, 0);
+ realRotation += QCPVector2D(tickPos-mAnchorReference).angle()/M_PI*180.0;
+ if (realRotation > 90) realRotation -= 180;
+ else if (realRotation < -90) realRotation += 180;
+ }
+
+ realSide = rotationCorrectedSide(realSide, realRotation); // rotation angles may change the true anchor side of the label
+ drawLabelMaybeCached(painter, mFont, mColor, getAnchorPos(tickPos), realSide, realRotation, text);
+}
+
+/*! \internal
+
+ Returns the size ("margin" in QCPAxisRect context, so measured perpendicular to the axis backbone
+ direction) needed to fit the axis.
+*/
+/* TODO: needed?
+int QCPLabelPainterPrivate::size() const
+{
+ int result = 0;
+ // get length of tick marks pointing outwards:
+ if (!tickPositions.isEmpty())
+ result += qMax(0, qMax(tickLengthOut, subTickLengthOut));
+
+ // calculate size of tick labels:
+ if (tickLabelSide == QCPAxis::lsOutside)
+ {
+ QSize tickLabelsSize(0, 0);
+ if (!tickLabels.isEmpty())
+ {
+ for (int i=0; ibufferDevicePixelRatio()));
+ result.append(QByteArray::number(mRotation));
+ //result.append(QByteArray::number(int(tickLabelSide))); TODO: check whether this is really a cache-invalidating property
+ result.append(QByteArray::number(int(mSubstituteExponent)));
+ result.append(QString(mMultiplicationSymbol).toUtf8());
+ result.append(mColor.name().toLatin1()+QByteArray::number(mColor.alpha(), 16));
+ result.append(mFont.toString().toLatin1());
+ return result;
+}
+
+/*! \internal
+
+ Draws a single tick label with the provided \a painter, utilizing the internal label cache to
+ significantly speed up drawing of labels that were drawn in previous calls. The tick label is
+ always bound to an axis, the distance to the axis is controllable via \a distanceToAxis in
+ pixels. The pixel position in the axis direction is passed in the \a position parameter. Hence
+ for the bottom axis, \a position would indicate the horizontal pixel position (not coordinate),
+ at which the label should be drawn.
+
+ In order to later draw the axis label in a place that doesn't overlap with the tick labels, the
+ largest tick label size is needed. This is acquired by passing a \a tickLabelsSize to the \ref
+ drawTickLabel calls during the process of drawing all tick labels of one axis. In every call, \a
+ tickLabelsSize is expanded, if the drawn label exceeds the value \a tickLabelsSize currently
+ holds.
+
+ The label is drawn with the font and pen that are currently set on the \a painter. To draw
+ superscripted powers, the font is temporarily made smaller by a fixed factor (see \ref
+ getTickLabelData).
+*/
+void QCPLabelPainterPrivate::drawLabelMaybeCached(QCPPainter *painter, const QFont &font, const QColor &color, const QPointF &pos, AnchorSide side, double rotation, const QString &text)
+{
+ // warning: if you change anything here, also adapt getMaxTickLabelSize() accordingly!
+ if (text.isEmpty()) return;
+ QSize finalSize;
+
+ if (mParentPlot->plottingHints().testFlag(QCP::phCacheLabels) && !painter->modes().testFlag(QCPPainter::pmNoCaching)) // label caching enabled
+ {
+ QByteArray key = cacheKey(text, color, rotation, side);
+ CachedLabel *cachedLabel = mLabelCache.take(QString::fromUtf8(key)); // attempt to take label from cache (don't use object() because we want ownership/prevent deletion during our operations, we re-insert it afterwards)
+ if (!cachedLabel) // no cached label existed, create it
+ {
+ LabelData labelData = getTickLabelData(font, color, rotation, side, text);
+ cachedLabel = createCachedLabel(labelData);
+ }
+ // if label would be partly clipped by widget border on sides, don't draw it (only for outside tick labels):
+ bool labelClippedByBorder = false;
+ /*
+ if (tickLabelSide == QCPAxis::lsOutside)
+ {
+ if (QCPAxis::orientation(type) == Qt::Horizontal)
+ labelClippedByBorder = labelAnchor.x()+cachedLabel->offset.x()+cachedLabel->pixmap.width()/mParentPlot->bufferDevicePixelRatio() > viewportRect.right() || labelAnchor.x()+cachedLabel->offset.x() < viewportRect.left();
+ else
+ labelClippedByBorder = labelAnchor.y()+cachedLabel->offset.y()+cachedLabel->pixmap.height()/mParentPlot->bufferDevicePixelRatio() > viewportRect.bottom() || labelAnchor.y()+cachedLabel->offset.y() < viewportRect.top();
+ }
+ */
+ if (!labelClippedByBorder)
+ {
+ painter->drawPixmap(pos+cachedLabel->offset, cachedLabel->pixmap);
+ finalSize = cachedLabel->pixmap.size()/mParentPlot->bufferDevicePixelRatio(); // TODO: collect this in a member rect list?
+ }
+ mLabelCache.insert(QString::fromUtf8(key), cachedLabel);
+ } else // label caching disabled, draw text directly on surface:
+ {
+ LabelData labelData = getTickLabelData(font, color, rotation, side, text);
+ // if label would be partly clipped by widget border on sides, don't draw it (only for outside tick labels):
+ bool labelClippedByBorder = false;
+ /*
+ if (tickLabelSide == QCPAxis::lsOutside)
+ {
+ if (QCPAxis::orientation(type) == Qt::Horizontal)
+ labelClippedByBorder = finalPosition.x()+(labelData.rotatedTotalBounds.width()+labelData.rotatedTotalBounds.left()) > viewportRect.right() || finalPosition.x()+labelData.rotatedTotalBounds.left() < viewportRect.left();
+ else
+ labelClippedByBorder = finalPosition.y()+(labelData.rotatedTotalBounds.height()+labelData.rotatedTotalBounds.top()) > viewportRect.bottom() || finalPosition.y()+labelData.rotatedTotalBounds.top() < viewportRect.top();
+ }
+ */
+ if (!labelClippedByBorder)
+ {
+ drawText(painter, pos, labelData);
+ finalSize = labelData.rotatedTotalBounds.size();
+ }
+ }
+ /*
+ // expand passed tickLabelsSize if current tick label is larger:
+ if (finalSize.width() > tickLabelsSize->width())
+ tickLabelsSize->setWidth(finalSize.width());
+ if (finalSize.height() > tickLabelsSize->height())
+ tickLabelsSize->setHeight(finalSize.height());
+ */
+}
+
+QPointF QCPLabelPainterPrivate::getAnchorPos(const QPointF &tickPos)
+{
+ switch (mAnchorMode)
+ {
+ case amRectangular:
+ {
+ switch (mAnchorSide)
+ {
+ case asLeft: return tickPos+QPointF(mPadding, 0);
+ case asRight: return tickPos+QPointF(-mPadding, 0);
+ case asTop: return tickPos+QPointF(0, mPadding);
+ case asBottom: return tickPos+QPointF(0, -mPadding);
+ case asTopLeft: return tickPos+QPointF(mPadding*M_SQRT1_2, mPadding*M_SQRT1_2);
+ case asTopRight: return tickPos+QPointF(-mPadding*M_SQRT1_2, mPadding*M_SQRT1_2);
+ case asBottomRight: return tickPos+QPointF(-mPadding*M_SQRT1_2, -mPadding*M_SQRT1_2);
+ case asBottomLeft: return tickPos+QPointF(mPadding*M_SQRT1_2, -mPadding*M_SQRT1_2);
+ default: qDebug() << Q_FUNC_INFO << "invalid mode for anchor side: " << mAnchorSide; break;
+ }
+ break;
+ }
+ case amSkewedUpright:
+ // fall through
+ case amSkewedRotated:
+ {
+ QCPVector2D anchorNormal(tickPos-mAnchorReference);
+ if (mAnchorReferenceType == artTangent)
+ anchorNormal = anchorNormal.perpendicular();
+ anchorNormal.normalize();
+ return tickPos+(anchorNormal*mPadding).toPointF();
+ }
+ default: qDebug() << Q_FUNC_INFO << "invalid mode for anchor mode: " << mAnchorMode; break;
+ }
+ return tickPos;
+}
+
+/*! \internal
+
+ This is a \ref placeTickLabel helper function.
+
+ Draws the tick label specified in \a labelData with \a painter at the pixel positions \a x and \a
+ y. This function is used by \ref placeTickLabel to create new tick labels for the cache, or to
+ directly draw the labels on the QCustomPlot surface when label caching is disabled, i.e. when
+ QCP::phCacheLabels plotting hint is not set.
+*/
+void QCPLabelPainterPrivate::drawText(QCPPainter *painter, const QPointF &pos, const LabelData &labelData) const
+{
+ // backup painter settings that we're about to change:
+ QTransform oldTransform = painter->transform();
+ QFont oldFont = painter->font();
+ QPen oldPen = painter->pen();
+
+ // transform painter to position/rotation:
+ painter->translate(pos);
+ painter->setTransform(labelData.transform, true);
+
+ // draw text:
+ painter->setFont(labelData.baseFont);
+ painter->setPen(QPen(labelData.color));
+ if (!labelData.expPart.isEmpty()) // use superscripted exponent typesetting
+ {
+ painter->drawText(0, 0, 0, 0, Qt::TextDontClip, labelData.basePart);
+ if (!labelData.suffixPart.isEmpty())
+ painter->drawText(labelData.baseBounds.width()+1+labelData.expBounds.width(), 0, 0, 0, Qt::TextDontClip, labelData.suffixPart);
+ painter->setFont(labelData.expFont);
+ painter->drawText(labelData.baseBounds.width()+1, 0, labelData.expBounds.width(), labelData.expBounds.height(), Qt::TextDontClip, labelData.expPart);
+ } else
+ {
+ painter->drawText(0, 0, labelData.totalBounds.width(), labelData.totalBounds.height(), Qt::TextDontClip | Qt::AlignHCenter, labelData.basePart);
+ }
+
+ /* Debug code to draw label bounding boxes, baseline, and capheight
+ painter->save();
+ painter->setPen(QPen(QColor(0, 0, 0, 150)));
+ painter->drawRect(labelData.totalBounds);
+ const int baseline = labelData.totalBounds.height()-mLetterDescent;
+ painter->setPen(QPen(QColor(255, 0, 0, 150)));
+ painter->drawLine(QLineF(0, baseline, labelData.totalBounds.width(), baseline));
+ painter->setPen(QPen(QColor(0, 0, 255, 150)));
+ painter->drawLine(QLineF(0, baseline-mLetterCapHeight, labelData.totalBounds.width(), baseline-mLetterCapHeight));
+ painter->restore();
+ */
+
+ // reset painter settings to what it was before:
+ painter->setTransform(oldTransform);
+ painter->setFont(oldFont);
+ painter->setPen(oldPen);
+}
+
+/*! \internal
+
+ This is a \ref placeTickLabel helper function.
+
+ Transforms the passed \a text and \a font to a tickLabelData structure that can then be further
+ processed by \ref getTickLabelDrawOffset and \ref drawTickLabel. It splits the text into base and
+ exponent if necessary (member substituteExponent) and calculates appropriate bounding boxes.
+*/
+QCPLabelPainterPrivate::LabelData QCPLabelPainterPrivate::getTickLabelData(const QFont &font, const QColor &color, double rotation, AnchorSide side, const QString &text) const
+{
+ LabelData result;
+ result.rotation = rotation;
+ result.side = side;
+ result.color = color;
+
+ // determine whether beautiful decimal powers should be used
+ bool useBeautifulPowers = false;
+ int ePos = -1; // first index of exponent part, text before that will be basePart, text until eLast will be expPart
+ int eLast = -1; // last index of exponent part, rest of text after this will be suffixPart
+ if (mSubstituteExponent)
+ {
+ ePos = text.indexOf(QLatin1Char('e'));
+ if (ePos > 0 && text.at(ePos-1).isDigit())
+ {
+ eLast = ePos;
+ while (eLast+1 < text.size() && (text.at(eLast+1) == QLatin1Char('+') || text.at(eLast+1) == QLatin1Char('-') || text.at(eLast+1).isDigit()))
+ ++eLast;
+ if (eLast > ePos) // only if also to right of 'e' is a digit/+/- interpret it as beautifiable power
+ useBeautifulPowers = true;
+ }
+ }
+
+ // calculate text bounding rects and do string preparation for beautiful decimal powers:
+ result.baseFont = font;
+ if (result.baseFont.pointSizeF() > 0) // might return -1 if specified with setPixelSize, in that case we can't do correction in next line
+ result.baseFont.setPointSizeF(result.baseFont.pointSizeF()+0.05); // QFontMetrics.boundingRect has a bug for exact point sizes that make the results oscillate due to internal rounding
+
+ QFontMetrics baseFontMetrics(result.baseFont);
+ if (useBeautifulPowers)
+ {
+ // split text into parts of number/symbol that will be drawn normally and part that will be drawn as exponent:
+ result.basePart = text.left(ePos);
+ result.suffixPart = text.mid(eLast+1); // also drawn normally but after exponent
+ // in log scaling, we want to turn "1*10^n" into "10^n", else add multiplication sign and decimal base:
+ if (mAbbreviateDecimalPowers && result.basePart == QLatin1String("1"))
+ result.basePart = QLatin1String("10");
+ else
+ result.basePart += QString(mMultiplicationSymbol) + QLatin1String("10");
+ result.expPart = text.mid(ePos+1, eLast-ePos);
+ // clip "+" and leading zeros off expPart:
+ while (result.expPart.length() > 2 && result.expPart.at(1) == QLatin1Char('0')) // length > 2 so we leave one zero when numberFormatChar is 'e'
+ result.expPart.remove(1, 1);
+ if (!result.expPart.isEmpty() && result.expPart.at(0) == QLatin1Char('+'))
+ result.expPart.remove(0, 1);
+ // prepare smaller font for exponent:
+ result.expFont = font;
+ if (result.expFont.pointSize() > 0)
+ result.expFont.setPointSize(result.expFont.pointSize()*0.75);
+ else
+ result.expFont.setPixelSize(result.expFont.pixelSize()*0.75);
+ // calculate bounding rects of base part(s), exponent part and total one:
+ result.baseBounds = baseFontMetrics.boundingRect(0, 0, 0, 0, Qt::TextDontClip, result.basePart);
+ result.expBounds = QFontMetrics(result.expFont).boundingRect(0, 0, 0, 0, Qt::TextDontClip, result.expPart);
+ if (!result.suffixPart.isEmpty())
+ result.suffixBounds = QFontMetrics(result.baseFont).boundingRect(0, 0, 0, 0, Qt::TextDontClip, result.suffixPart);
+ result.totalBounds = result.baseBounds.adjusted(0, 0, result.expBounds.width()+result.suffixBounds.width()+2, 0); // +2 consists of the 1 pixel spacing between base and exponent (see drawTickLabel) and an extra pixel to include AA
+ } else // useBeautifulPowers == false
+ {
+ result.basePart = text;
+ result.totalBounds = baseFontMetrics.boundingRect(0, 0, 0, 0, Qt::TextDontClip | Qt::AlignHCenter, result.basePart);
+ }
+ result.totalBounds.moveTopLeft(QPoint(0, 0));
+ applyAnchorTransform(result);
+ result.rotatedTotalBounds = result.transform.mapRect(result.totalBounds);
+
+ return result;
+}
+
+void QCPLabelPainterPrivate::applyAnchorTransform(LabelData &labelData) const
+{
+ if (!qFuzzyIsNull(labelData.rotation))
+ labelData.transform.rotate(labelData.rotation); // rotates effectively clockwise (due to flipped y axis of painter vs widget coordinate system)
+
+ // from now on we translate in rotated label-local coordinate system.
+ // shift origin of coordinate system to appropriate point on label:
+ labelData.transform.translate(0, -labelData.totalBounds.height()+mLetterDescent+mLetterCapHeight); // shifts origin to true top of capital (or number) characters
+
+ if (labelData.side == asLeft || labelData.side == asRight) // anchor is centered vertically
+ labelData.transform.translate(0, -mLetterCapHeight/2.0);
+ else if (labelData.side == asTop || labelData.side == asBottom) // anchor is centered horizontally
+ labelData.transform.translate(-labelData.totalBounds.width()/2.0, 0);
+
+ if (labelData.side == asTopRight || labelData.side == asRight || labelData.side == asBottomRight) // anchor is at right
+ labelData.transform.translate(-labelData.totalBounds.width(), 0);
+ if (labelData.side == asBottomLeft || labelData.side == asBottom || labelData.side == asBottomRight) // anchor is at bottom (no elseif!)
+ labelData.transform.translate(0, -mLetterCapHeight);
+}
+
+/*! \internal
+
+ Simulates the steps done by \ref placeTickLabel by calculating bounding boxes of the text label
+ to be drawn, depending on number format etc. Since only the largest tick label is wanted for the
+ margin calculation, the passed \a tickLabelsSize is only expanded, if it's currently set to a
+ smaller width/height.
+*/
+/*
+void QCPLabelPainterPrivate::getMaxTickLabelSize(const QFont &font, const QString &text, QSize *tickLabelsSize) const
+{
+ // note: this function must return the same tick label sizes as the placeTickLabel function.
+ QSize finalSize;
+ if (mParentPlot->plottingHints().testFlag(QCP::phCacheLabels) && mLabelCache.contains(text)) // label caching enabled and have cached label
+ {
+ const CachedLabel *cachedLabel = mLabelCache.object(text);
+ finalSize = cachedLabel->pixmap.size()/mParentPlot->bufferDevicePixelRatio();
+ } else // label caching disabled or no label with this text cached:
+ {
+ // TODO: LabelData labelData = getTickLabelData(font, text);
+ // TODO: finalSize = labelData.rotatedTotalBounds.size();
+ }
+
+ // expand passed tickLabelsSize if current tick label is larger:
+ if (finalSize.width() > tickLabelsSize->width())
+ tickLabelsSize->setWidth(finalSize.width());
+ if (finalSize.height() > tickLabelsSize->height())
+ tickLabelsSize->setHeight(finalSize.height());
+}
+*/
+
+QCPLabelPainterPrivate::CachedLabel *QCPLabelPainterPrivate::createCachedLabel(const LabelData &labelData) const
+{
+ CachedLabel *result = new CachedLabel;
+
+ // allocate pixmap with the correct size and pixel ratio:
+ if (!qFuzzyCompare(1.0, mParentPlot->bufferDevicePixelRatio()))
+ {
+ result->pixmap = QPixmap(labelData.rotatedTotalBounds.size()*mParentPlot->bufferDevicePixelRatio());
+#ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
+# ifdef QCP_DEVICEPIXELRATIO_FLOAT
+ result->pixmap.setDevicePixelRatio(mParentPlot->devicePixelRatioF());
+# else
+ result->pixmap.setDevicePixelRatio(mParentPlot->devicePixelRatio());
+# endif
+#endif
+ } else
+ result->pixmap = QPixmap(labelData.rotatedTotalBounds.size());
+ result->pixmap.fill(Qt::transparent);
+
+ // draw the label into the pixmap
+ // offset is between label anchor and topleft of cache pixmap, so pixmap can be drawn at pos+offset to make the label anchor appear at pos.
+ // We use rotatedTotalBounds.topLeft() because rotatedTotalBounds is in a coordinate system where the label anchor is at (0, 0)
+ result->offset = labelData.rotatedTotalBounds.topLeft();
+ QCPPainter cachePainter(&result->pixmap);
+ drawText(&cachePainter, -result->offset, labelData);
+ return result;
+}
+
+QByteArray QCPLabelPainterPrivate::cacheKey(const QString &text, const QColor &color, double rotation, AnchorSide side) const
+{
+ return text.toUtf8()+
+ QByteArray::number(color.red()+256*color.green()+65536*color.blue(), 36)+
+ QByteArray::number(color.alpha()+256*int(side), 36)+
+ QByteArray::number(int(rotation*100), 36);
+}
+
+QCPLabelPainterPrivate::AnchorSide QCPLabelPainterPrivate::skewedAnchorSide(const QPointF &tickPos, double sideExpandHorz, double sideExpandVert) const
+{
+ QCPVector2D anchorNormal = QCPVector2D(tickPos-mAnchorReference);
+ if (mAnchorReferenceType == artTangent)
+ anchorNormal = anchorNormal.perpendicular();
+ const double radius = anchorNormal.length();
+ const double sideHorz = sideExpandHorz*radius;
+ const double sideVert = sideExpandVert*radius;
+ if (anchorNormal.x() > sideHorz)
+ {
+ if (anchorNormal.y() > sideVert) return asTopLeft;
+ else if (anchorNormal.y() < -sideVert) return asBottomLeft;
+ else return asLeft;
+ } else if (anchorNormal.x() < -sideHorz)
+ {
+ if (anchorNormal.y() > sideVert) return asTopRight;
+ else if (anchorNormal.y() < -sideVert) return asBottomRight;
+ else return asRight;
+ } else
+ {
+ if (anchorNormal.y() > 0) return asTop;
+ else return asBottom;
+ }
+ return asBottom; // should never be reached
+}
+
+QCPLabelPainterPrivate::AnchorSide QCPLabelPainterPrivate::rotationCorrectedSide(AnchorSide side, double rotation) const
+{
+ AnchorSide result = side;
+ const bool rotateClockwise = rotation > 0;
+ if (!qFuzzyIsNull(rotation))
+ {
+ if (!qFuzzyCompare(qAbs(rotation), 90)) // avoid graphical collision with anchor tangent (e.g. axis line) when rotating, so change anchor side appropriately:
+ {
+ if (side == asTop) result = rotateClockwise ? asLeft : asRight;
+ else if (side == asBottom) result = rotateClockwise ? asRight : asLeft;
+ else if (side == asTopLeft) result = rotateClockwise ? asLeft : asTop;
+ else if (side == asTopRight) result = rotateClockwise ? asTop : asRight;
+ else if (side == asBottomLeft) result = rotateClockwise ? asBottom : asLeft;
+ else if (side == asBottomRight) result = rotateClockwise ? asRight : asBottom;
+ } else // for full rotation by +/-90 degrees, other sides are more appropriate for centering on anchor:
+ {
+ if (side == asLeft) result = rotateClockwise ? asBottom : asTop;
+ else if (side == asRight) result = rotateClockwise ? asTop : asBottom;
+ else if (side == asTop) result = rotateClockwise ? asLeft : asRight;
+ else if (side == asBottom) result = rotateClockwise ? asRight : asLeft;
+ else if (side == asTopLeft) result = rotateClockwise ? asBottomLeft : asTopRight;
+ else if (side == asTopRight) result = rotateClockwise ? asTopLeft : asBottomRight;
+ else if (side == asBottomLeft) result = rotateClockwise ? asBottomRight : asTopLeft;
+ else if (side == asBottomRight) result = rotateClockwise ? asTopRight : asBottomLeft;
+ }
+ }
+ return result;
+}
+
+void QCPLabelPainterPrivate::analyzeFontMetrics()
+{
+ const QFontMetrics fm(mFont);
+ mLetterCapHeight = fm.tightBoundingRect(QLatin1String("8")).height(); // this method is slow, that's why we query it only upon font change
+ mLetterDescent = fm.descent();
+}
+/* end of 'src/axis/labelpainter.cpp' */
+
+
+/* including file 'src/axis/axisticker.cpp' */
+/* modified 2022-11-06T12:45:56, size 18693 */
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//////////////////// QCPAxisTicker
+////////////////////////////////////////////////////////////////////////////////////////////////////
+/*! \class QCPAxisTicker
+ \brief The base class tick generator used by QCPAxis to create tick positions and tick labels
+
+ Each QCPAxis has an internal QCPAxisTicker (or a subclass) in order to generate tick positions
+ and tick labels for the current axis range. The ticker of an axis can be set via \ref
+ QCPAxis::setTicker. Since that method takes a