From 46733ec3762b0e5afaeca02ae44c4206cd22ce31 Mon Sep 17 00:00:00 2001 From: Ben Marx Date: Tue, 12 Jan 2021 23:35:37 +0100 Subject: [PATCH 01/19] Synths winwood_lead, bass_foundation, bass_highend --- .../ruby/lib/sonicpi/synths/synthinfo.rb | 211 ++++++++++++++++++ .../sonic-pi-bass_foundation.scsyndef | Bin 0 -> 3536 bytes .../compiled/sonic-pi-bass_highend.scsyndef | Bin 0 -> 4813 bytes .../compiled/sonic-pi-winwood_lead.scsyndef | Bin 0 -> 4883 bytes .../designs/supercollider/bass_foundation.scd | 46 ++++ .../designs/supercollider/bass_highend.scd | 58 +++++ .../designs/supercollider/winwood_lead.scd | 58 +++++ 7 files changed, 373 insertions(+) create mode 100644 etc/synthdefs/compiled/sonic-pi-bass_foundation.scsyndef create mode 100644 etc/synthdefs/compiled/sonic-pi-bass_highend.scsyndef create mode 100644 etc/synthdefs/compiled/sonic-pi-winwood_lead.scsyndef create mode 100644 etc/synthdefs/designs/supercollider/bass_foundation.scd create mode 100644 etc/synthdefs/designs/supercollider/bass_highend.scd create mode 100644 etc/synthdefs/designs/supercollider/winwood_lead.scd diff --git a/app/server/ruby/lib/sonicpi/synths/synthinfo.rb b/app/server/ruby/lib/sonicpi/synths/synthinfo.rb index 650200152c..162c2ab218 100644 --- a/app/server/ruby/lib/sonicpi/synths/synthinfo.rb +++ b/app/server/ruby/lib/sonicpi/synths/synthinfo.rb @@ -3290,6 +3290,214 @@ def arg_defaults end end + class WinwoodLead < SonicPiSynth + def name + "Winwood Lead" + end + + def introduced + Version.new(3,3,0) + end + + def synth_name + "winwood_lead" + end + + def on_start(studio, args_h) + args_h[:rand_buf] = studio.rand_buf_id + end + + def doc + "A lead synth inspired by the Winwood songs from the early 80s. Adapted for Sonic Pi from [Steal This Sound](https://raw.githubusercontent.com/supercollider/supercollider/develop/examples/demonstrations/stealthissound.scd). Published there under [GPL v3](https://www.gnu.org/licenses/gpl-3.0.en.html), so re-published under the same terms. The source code is available [on the Sonic Pi GitHub repository](https://github.com/sonic-pi-net/sonic-pi). Date of modification: 10.01.2021" + end + + def arg_defaults + { + :note => 69, + :note_slide => 0, + :note_slide_shape => 1, + :note_slide_curve => 0, + :amp => 1, + :amp_slide => 0, + :amp_slide_shape => 1, + :amp_slide_curve => 0, + :pan => 0, + :pan_slide => 0, + :pan_slide_shape => 1, + :pan_slide_curve => 0, + + :attack => 0.01, + :decay => 0, + :sustain => 0.9, + :release => 0.05, + :attack_level => 1, + :decay_level => 0.5, + :sustain_level => 0.5, + + :cutoff => 119, + :cutoff_slide => 0, + :cutoff_slide_shape => 1, + :cutoff_slide_curve => 0, + + :res => 0.8, + :res_slide => 0, + :res_slide_shape => 1, + :res_slide_curve => 0, + :lfo_width => 0.01, + :lfo_width_slide => 0, + :lfo_width_slide_shape => 1, + :lfo_width_slide_curve => 0, + :lfo_rate => 8, + :lfo_rate_slide => 0, + :lfo_rate_slide_shape => 1, + :lfo_rate_slide_curve => 0, + :seed => 0, + } + end + + def specific_arg_info + { + :seed => + { + :doc => "Seed value for rand num generator used for the phase offset of the triangle low-frequency oscillator (LFO)", + :modulatable => false + }, + :lfo_width => + { + :doc => "Width of the low-frequency oscillator (LFO) which determines how wide base tones oscillate around their base frequencies", + :modulatable => true + }, + :lfo_rate => + { + :doc => "Width of the low-frequency oscillator (LFO) which determines how fast base tones oscillate around their base frequencies", + :modulatable => true + }, + } + end + end + + class BassFoundation < SonicPiSynth + def name + "Bass Foundation" + end + + def introduced + Version.new(3,3,0) + end + + def synth_name + "bass_foundation" + end + + def doc + "A soft bass synth inspired by the sounds of the 80s. Use together with :bass_highend if you want to give it a gargling component. Adapted for Sonic Pi from [Steal This Sound](https://raw.githubusercontent.com/supercollider/supercollider/develop/examples/demonstrations/stealthissound.scd). Published there under [GPL v3](https://www.gnu.org/licenses/gpl-3.0.en.html), so re-published under the same terms. The source code is available [on the Sonic Pi GitHub repository](https://github.com/sonic-pi-net/sonic-pi). Date of modification: 10.01.2021" + end + + def arg_defaults + { + :note => 40, + :note_slide => 0, + :note_slide_shape => 1, + :note_slide_curve => 0, + :amp => 1, + :amp_slide => 0, + :amp_slide_shape => 1, + :amp_slide_curve => 0, + :pan => 0, + :pan_slide => 0, + :pan_slide_shape => 1, + :pan_slide_curve => 0, + + :attack => 0.01, + :decay => 0, + :sustain => 0.9, + :release => 0.05, + :attack_level => 1, + :decay_level => 0.5, + :sustain_level => 0, + + :cutoff => 83, + :cutoff_slide => 0, + :cutoff_slide_shape => 1, + :cutoff_slide_curve => 0, + + :res => 0.5, + :res_slide => 0, + :res_slide_shape => 1, + :res_slide_curve => 0, + } + end + end + + class BassHighend < SonicPiSynth + def name + "Bass Highend" + end + + def introduced + Version.new(3,3,0) + end + + def synth_name + "bass_highend" + end + + def doc + "An addition to the :bass_foundation synth inspired by the sounds of the 80s. Use them together if you want to give it a rough, slurping, or gargling component. Adapted for Sonic Pi from [Steal This Sound](https://raw.githubusercontent.com/supercollider/supercollider/develop/examples/demonstrations/stealthissound.scd). Published there under [GPL v3](https://www.gnu.org/licenses/gpl-3.0.en.html), so re-published under the same terms. The source code is available [on the Sonic Pi GitHub repository](https://github.com/sonic-pi-net/sonic-pi). Date of modification: 11.01.2021" + end + + def arg_defaults + { + :note => 40, + :note_slide => 0, + :note_slide_shape => 1, + :note_slide_curve => 0, + :amp => 1, + :amp_slide => 0, + :amp_slide_shape => 1, + :amp_slide_curve => 0, + :pan => 0, + :pan_slide => 0, + :pan_slide_shape => 1, + :pan_slide_curve => 0, + + :attack => 0.01, + :decay => 0, + :sustain => 0.9, + :release => 0.05, + :attack_level => 1, + :decay_level => 0.5, + :sustain_level => 0, + + :cutoff => 102, + :cutoff_slide => 0, + :cutoff_slide_shape => 1, + :cutoff_slide_curve => 0, + + :res => 0.1, + :res_slide => 0, + :res_slide_shape => 1, + :res_slide_curve => 0, + + :drive => 2.0, + :drive_slide => 0, + :drive_slide_shape => 1, + :drive_slide_curve => 0, + } + end + + def specific_arg_info + { + :drive => + { + :doc => "Higher drive values make the sound louder and rougher.", + :validations => [v_positive(:drive)], + :modulatable => true + }, + } + end + end + class StudioInfo < SonicPiSynth def user_facing? false @@ -7949,6 +8157,9 @@ class BaseInfo :kalimba => SynthKalimba.new, :pluck => SynthPluck.new, :tech_saws => TechSaws.new, + :winwood_lead => WinwoodLead.new, + :bass_foundation => BassFoundation.new, + :bass_highend => BassHighend.new, :sound_in => SoundIn.new, :sound_in_stereo => SoundInStereo.new, diff --git a/etc/synthdefs/compiled/sonic-pi-bass_foundation.scsyndef b/etc/synthdefs/compiled/sonic-pi-bass_foundation.scsyndef new file mode 100644 index 0000000000000000000000000000000000000000..e4cef4d6bab752149e5d6a605338907f33d8064f GIT binary patch literal 3536 zcmbVOS#Q%o5MJ9!+%#!J586`7eIMn%#Zij5BoGx9FT6x1aUddfqS!9ttv`h)eh1S7HBx&_)a-(9EUFnjxR0z6}4`m!T^{~@!Lbdh`)Oz^A>%nNJjg7d! z38Uk98$H58nNY_UYg*IcrIbxYyH3x;vl=TC>gaM!YdU&7WmD0Wk{3l@<0B=g7Mp&< z`%HHeZWxD=*GAe&x9@j+FT_G?)r4H{_?v!*40|dZqV=f>8K^DOrA8ceTiENFYI3XB zI6Iv-=wnXV29 ze@TE@71*9r0ab*RQ=tTcuCN1f3?z~%O)Ra`k!VC{5NqT{g`zPP7m{hK^f=g*k8B?q zh$^#K2AXz9Rg*X_(N@R>=m<)zO1sJnGajOni3~q{(~>Z0`lYVoYo>pv4JqVo2+y;; z4XL7>s=QN$VorrgB*?2!`=1J9ITiTK=T+b*I;w|qPKDV~J?tIoA^QzeujWkFQ=I1t z*@1Xz7DlZAXL;m4&F1uo8I-$EG>=ACn9r$ja8!ju|5IThr^1m@6^;&7aG!7V;!fye zTqHwIKf%FiS&uiaf-smXE|QQOPe2ra>A5liot-CeHQ!THS8T(co-0W$Ja{iy+VI;khn&JO*Zfy-F;HXR(2g27Ao$9`rB!QTkF=T8BlYwfa+ zIhpXB#2H}rKAnflSl>1s9Fj4EXZE2!J8IVR-#Ln_?6!{sTvmt+5*LBleO!dgSl>3C zC_HWqUb+wVBNqt|KSG{ygmb&?;~27$Tuwk724?qh1ukQK+jMY9<_%uD5B4)lj!?Si zNlCb1Et`*lc3y zgTZu-G{_}fWa_eAc>QAS$uRL02qf1N5Lbcr(7!DN;Aipm1_&g4mhHlt7hF*pfkNEm ns}RdTS=zGmQr?mgXMv-Ln12b;c@;;hw9G(&kJoLf-bwHu!BXAA literal 0 HcmV?d00001 diff --git a/etc/synthdefs/compiled/sonic-pi-bass_highend.scsyndef b/etc/synthdefs/compiled/sonic-pi-bass_highend.scsyndef new file mode 100644 index 0000000000000000000000000000000000000000..633c0d9ed30c6cfafb528694b44d702811f9fa4d GIT binary patch literal 4813 zcmbVPSyLNF5T0G#NFZ)7U`*oN2}x|{j=@+M9|;cF3aM0HsI{~*Dho-a1DD_YEApJ5 zLh_Kx1AflWN&4%Z8SM^xP)yZMZ+CzF_4LeWW>wvO-Vj3A!kX)M+RfT}x4Hhz>Guzh zn$M5icAb1P^oTpJh4|}lAvQ_gIi-hen`dWlHt5-+_buwS{t)8yHQ9I1-ce6>wWg!Td^Qzb%sPXCQ+q)KrA*zeIj`t#Jl7xg2Tl{T6SM_nv`we=Sb3wBj>3pi^_K9Yp zKvHuombIn{uJ~*!dMQ)yHF>s|i~Ob;R;GNq=C~TLDwfq;XE-=~HpDot(erC=yVD-@ zIxUM7v^84ygjPIgJH1zX-Gc{i8)4ovg=jP@{c!)c8zTqXxC;-=kk4j+snQHG!)%EH z^(g@UqXrq4f$dgw24oRdOol7~Izt374-mB}4a`<)bJYeY7h{B^oy@)0sd<^UvODb) zIb~PKM^MRP8Pc@pRW>Q7rL>il=_aKtNxR~O8I!2mc!(dqX$hDx{Zd!)Wz!$EAnBL| zDS|C-K}rCq%uyLKF&QS3GL$cpAs>^0*EX&Op3$Tl3NaaGlWLd?)DXV^)fSuyo5cmr z5FUsh&0^9Bu$L3pX*Q?lVLR}1CGMP8Wv+Ryp@#U?Tchsj>+&&QigXg zl3^t#!~01YJ_uyUJv#0VTYVQto_A2|8655S>hSm$0Hd=p2NG%@c|xQi{b%DibasA> z-aKneU7-!T|7`Twm{Ky!8;4A(sGl!N`UEkOIAx`(&P!RSq*;gG4%`(0cp&tj0w4)8 zLEvcG0Q6@{hh+L3L_vaJrW*jrOga1fP&q*y!7U#_)=p#LkYTiNHvypbg(t*kkoF~a zfx4=`l4Mx)h+XhCT*m62(}AIuHgNu3z5@j>`055X>rfvU>Q?8!1Qc7mY#mo+;@eU#L5A0{0oMiU;ETn;`RiamxTx_p zN0M(dlJl3X<2u-=slP;PP{Qljg3B1+IUN{k3kJ?#2m2W%FH3soJCWqlW$XA*5>}*q z4>G)tZMcl_ozsD#wrt?cI`}Fk9N)Mk)wWoTIwf(6-J?e*RAa>1fz>2{kyGty)2{Zs zvKM5Wva7M0@naHKlu~{DW~6TV6~Dr1zny(NY~8KbgU8-|R7Y(SounMM(@%Hz?+1Zj z0zmD7C&XPy+y7w&JSNQI^XDP^BQ6j49-*Vg^BZ?`GZO)S06GeYEy+MxLp}-!l3jGv zcF={-%VW5x@hByy6~&25{zrxj@e`z7sO)xLR*&3P1G54@W=4oTbdt{eA5yk+m3`NF zvGXJtv+s+UR6~(+Dr!iec!EG`e0;_Mp8_Qr^LBdQY2QRVE=Ncqs@x0lJ)|0f9Y^`I djQ9pJ35dDzfT_LVK&6%pB=G%uAk|+y`9Hb0*3|$2 literal 0 HcmV?d00001 diff --git a/etc/synthdefs/compiled/sonic-pi-winwood_lead.scsyndef b/etc/synthdefs/compiled/sonic-pi-winwood_lead.scsyndef new file mode 100644 index 0000000000000000000000000000000000000000..17b50e0f7872e9c80fd3793a83b2c0d4971de458 GIT binary patch literal 4883 zcmbtX+fo}x5S^7)HxP(B7%&KM$dx3vbB_&1#Q25-K_;oBQl;8RtDuUIRJuU-jSZ0hxm^I(3*DA%GMrG%! zQoE|xONFZIlqfb%2iv=5?Dszzd%&-=f9xLp_4jV>m+$w;KKS*T{We80!jNp|e&(9z z;0n1AsXeP}-`o0UjK|C6@_nic9O~l8{fkHd_L{rdzNb{}J<5km=Mu1OyH;|~@$Tg)g+)mxg^ ze5&M)zKE>4oJ3C3;ZJa7QW~n}>Rhi&mAt73WW}{;(``|Q>nYNzPBx<~Rbs>Eli8bg z%XRUIY$cmctyK7-gIjkiQSY?r8g*ZzoMZF=8^lJT>e(|mc{ID6#Q zknA1Pya-JD@yU0$y8-w)hZUR{qj-da(z!}+WwzGsHuFHC-#vq5)pkuwA*;-gs#>j3X^=yRGg%(xQ9 zusRIGqJd#8jA4Bkh9v{TMi|59FbpdOhFf6_SkyzG!>WPd^)QAvhGAGYFuWDU@b)ka zSp&mP7{j|g46$6Nd|JYyZPVc`-T?5MO?pdP>7s)^cI;H|BU2a)0N4StxIO)H!Z)TE^}Py$l^Z$9;sf{`zc!@GVBe`RCwq z)FR;<9%;U>kzN>ejxAIp!QVAV7N_7j9w4Ok*JlGmvZ&$wbMQDviEWdv`R+)1dDJ=H z6a{la)6eula~vR~_19+uL$adb^f~yZOgO&xl9sQq`vBIP*%qGBR4;@0vWijW+8ioF9J{6(A~Bp;H{ zeKs&8>l#jfllX5d!es#_+3*zGwCvNPlS7QxeM`O+!Cj%hf{vteNA;`xxmzs*-QB8B z5f$|MLYXq%&pZ-I_-YxkPn_Bv*#|UZFZh|U&!Odj5mU)Ck?{$17!Y5N2aFzf+LBsS OAcfy@UkmdaPrm{#u(*8y literal 0 HcmV?d00001 diff --git a/etc/synthdefs/designs/supercollider/bass_foundation.scd b/etc/synthdefs/designs/supercollider/bass_foundation.scd new file mode 100644 index 0000000000..07e3da51c7 --- /dev/null +++ b/etc/synthdefs/designs/supercollider/bass_foundation.scd @@ -0,0 +1,46 @@ +// Adapted for Sonic Pi from +// https://raw.githubusercontent.com/supercollider/supercollider/develop/examples/demonstrations/stealthissound.scd +// Published there under GPL v3, so re-published under the same terms, see: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// Date of modification: 10.01.2021 + +( +SynthDef('sonic-pi-bass_foundation', {| + note = 40, note_slide = 0, note_slide_shape = 1, note_slide_curve = 0, + amp = 1, amp_slide = 0, amp_slide_shape = 1, amp_slide_curve = 0, + pan = 0, pan_slide = 0, pan_slide_shape = 1, pan_slide_curve = 0, + attack = 0.01, decay = 0, sustain = 0.9, release = 0.05, + attack_level = 1, decay_level = 0.5, sustain_level = 0, + cutoff = 83, cutoff_slide = 0, cutoff_slide_shape = 1, cutoff_slide_curve = 0, + res = 0.5, res_slide = 0, res_slide_shape = 1, res_slide_curve = 0, + out_bus = 0| + + var snd, osc, env, filterenv; + + note = note.midicps; + note = note.varlag(note_slide, note_slide_curve, note_slide_shape); + decay_level = Select.kr(decay_level < 0, [decay_level, sustain_level]); + amp = amp.varlag(amp_slide, amp_slide_curve, amp_slide_shape); + pan = pan.varlag(pan_slide, pan_slide_curve, pan_slide_shape); + + cutoff = cutoff.midicps; + cutoff = cutoff.varlag(cutoff_slide, cutoff_slide_curve, cutoff_slide_shape); + + res = res.varlag(res_slide, res_slide_curve, res_slide_shape); + + osc = Saw.ar(note); + + filterenv = EnvGen.ar(Env.adsr(0.0, 0.5, 0.2, 0.2), 1, doneAction:2); + snd = RLPF.ar(osc,cutoff*filterenv+100, res); + + env = Env.new( + [0, attack_level, decay_level, sustain_level, 0], + [attack,decay,sustain,release], + \lin + ); + + snd = Pan2.ar(Mix(snd) * EnvGen.kr(env, doneAction: 2), pan); + + Out.ar(out_bus, snd * amp); +}).writeDefFile("/home/bmarx/music/sonic_pi/synthdefs/compiled/"); +) \ No newline at end of file diff --git a/etc/synthdefs/designs/supercollider/bass_highend.scd b/etc/synthdefs/designs/supercollider/bass_highend.scd new file mode 100644 index 0000000000..ad94596e1f --- /dev/null +++ b/etc/synthdefs/designs/supercollider/bass_highend.scd @@ -0,0 +1,58 @@ +// Adapted for Sonic Pi from +// https://raw.githubusercontent.com/supercollider/supercollider/develop/examples/demonstrations/stealthissound.scd +// Published there under GPL v3, so re-published under the same terms, see: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// Date of modification: 11.01.2021 + +( +SynthDef('sonic-pi-bass_highend',{| + note = 40, note_slide = 0, note_slide_shape = 1, note_slide_curve = 0, + amp = 1, amp_slide = 0, amp_slide_shape = 1, amp_slide_curve = 0, + pan = 0, pan_slide = 0, pan_slide_shape = 1, pan_slide_curve = 0, + attack = 0.01, decay = 0, sustain = 0.9, release = 0.05, + attack_level = 1, decay_level = 0.5, sustain_level = 0, + cutoff = 102, cutoff_slide = 0, cutoff_slide_shape = 1, cutoff_slide_curve = 0, + res = 0.1, res_slide = 0, res_slide_shape = 1, res_slide_curve = 0, + drive = 2.0, drive_slide = 0, drive_slide_shape = 1, drive_slide_curve = 0, + out_bus = 0| + + var osc, snd, env, filterenv, ab; + + note = note.midicps; + note = note.varlag(note_slide, note_slide_curve, note_slide_shape); + decay_level = Select.kr(decay_level < 0, [decay_level, sustain_level]); + amp = amp.varlag(amp_slide, amp_slide_curve, amp_slide_shape); + pan = pan.varlag(pan_slide, pan_slide_curve, pan_slide_shape); + + cutoff = cutoff.midicps; + cutoff = cutoff.varlag(cutoff_slide, cutoff_slide_curve, cutoff_slide_shape); + + res = res.varlag(res_slide, res_slide_curve, res_slide_shape); + + drive = drive.varlag(drive_slide, drive_slide_curve, drive_slide_shape); + + osc = Mix(Saw.ar(note*[0.25,1,1.5],[0.5,0.4,0.1])); + filterenv = EnvGen.ar(Env.adsr(0.0,0.5,0.2,0.2), doneAction:2); + snd = RLPF.ar(osc,cutoff*filterenv+100,res); + + ab = abs(snd); + snd = (snd*(ab + drive)/(snd ** 2 + (drive - 1) * ab + 1)); + + // Remove low end + snd = BLowShelf.ar(snd, 300, 1.0, -12); + + // Dip at 1600Hz + snd = BPeakEQ.ar(snd, 1600, 1.0, -6); + + env = Env.new( + [0, attack_level, decay_level, sustain_level, 0], + [attack,decay,sustain,release], + \lin + ); + + snd = Pan2.ar(Mix(snd) * EnvGen.kr(env, doneAction: 2) * 2, pan); + + Out.ar(out_bus, snd * amp); + +}).writeDefFile("/home/bmarx/music/sonic_pi/synthdefs/compiled/"); +) \ No newline at end of file diff --git a/etc/synthdefs/designs/supercollider/winwood_lead.scd b/etc/synthdefs/designs/supercollider/winwood_lead.scd new file mode 100644 index 0000000000..40d90ccd9d --- /dev/null +++ b/etc/synthdefs/designs/supercollider/winwood_lead.scd @@ -0,0 +1,58 @@ +// Adapted for Sonic Pi from +// https://raw.githubusercontent.com/supercollider/supercollider/develop/examples/demonstrations/stealthissound.scd +// Published there under GPL v3, so re-published under the same terms, see: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// Date of modification: 10.01.2021 + +( +SynthDef('sonic-pi-winwood_lead', {| + note = 69, note_slide = 0, note_slide_shape = 1, note_slide_curve = 0, + amp = 1, amp_slide = 0, amp_slide_shape = 1, amp_slide_curve = 0, + pan = 0, pan_slide = 0, pan_slide_shape = 1, pan_slide_curve = 0, + attack = 0.01, decay = 0, sustain = 0.9, release = 0.05, + attack_level = 1, decay_level = 0.5, sustain_level = 0.5, + cutoff = 119, cutoff_slide = 0, cutoff_slide_shape = 1, cutoff_slide_curve = 0, + lfo_width = 0.01, lfo_width_slide = 0, lfo_width_slide_shape = 1, lfo_width_slide_curve = 0, + lfo_rate = 8, lfo_rate_slide = 0, lfo_rate_slide_shape = 1, lfo_rate_slide_curve = 0, + res = 0.8, res_slide = 0, res_slide_shape = 1, res_slide_curve = 0, + seed = 0, rand_buf = 0, + out_bus = 0| + + var snd, pulse, env, lfo; + var rand_val; + + note = note.midicps; + note = note.varlag(note_slide, note_slide_curve, note_slide_shape); + decay_level = Select.kr(decay_level < 0, [decay_level, sustain_level]); + amp = amp.varlag(amp_slide, amp_slide_curve, amp_slide_shape); + pan = pan.varlag(pan_slide, pan_slide_curve, pan_slide_shape); + + cutoff = cutoff.midicps; + cutoff = cutoff.varlag(cutoff_slide, cutoff_slide_curve, cutoff_slide_shape); + + lfo_width = lfo_width.varlag(lfo_width_slide, lfo_width_slide_curve, lfo_width_slide_shape); + lfo_rate = lfo_rate.varlag(lfo_rate_slide, lfo_rate_slide_curve, lfo_rate_slide_shape); + + res = res.varlag(res_slide, res_slide_curve, res_slide_shape); + + rand_val = BufRd.kr(1, rand_buf, seed, 1); + + lfo = LFTri.kr(lfo_rate,(rand_val*2.0)!2); + + pulse = Mix(Pulse.ar(note*[1,1.001]*(1.0+(lfo_width*lfo)),[0.2,0.19]))*0.5; + + snd = RLPF.ar(pulse,cutoff,res); + + snd = BLowShelf.ar(snd,351,1.0,-9); + + env = Env.new( + [0, attack_level, decay_level, sustain_level, 0], + [attack,decay,sustain,release], + \lin + ); + + snd = Pan2.ar(Mix(snd) * EnvGen.kr(env, doneAction: 2), pan); + + Out.ar(out_bus, snd * amp); +}).writeDefFile("/home/bmarx/music/sonic_pi/synthdefs/compiled/"); +) \ No newline at end of file From 985f2898ae18e90fd97cd5ad9fbe8a9714ec7c42 Mon Sep 17 00:00:00 2001 From: Ben Marx Date: Tue, 12 Jan 2021 23:43:27 +0100 Subject: [PATCH 02/19] Adjusted paths in synthdefs --- etc/synthdefs/designs/supercollider/bass_foundation.scd | 2 +- etc/synthdefs/designs/supercollider/bass_highend.scd | 2 +- etc/synthdefs/designs/supercollider/winwood_lead.scd | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/etc/synthdefs/designs/supercollider/bass_foundation.scd b/etc/synthdefs/designs/supercollider/bass_foundation.scd index 07e3da51c7..8effc5e139 100644 --- a/etc/synthdefs/designs/supercollider/bass_foundation.scd +++ b/etc/synthdefs/designs/supercollider/bass_foundation.scd @@ -42,5 +42,5 @@ SynthDef('sonic-pi-bass_foundation', {| snd = Pan2.ar(Mix(snd) * EnvGen.kr(env, doneAction: 2), pan); Out.ar(out_bus, snd * amp); -}).writeDefFile("/home/bmarx/music/sonic_pi/synthdefs/compiled/"); +}).writeDefFile("/Users/sam/Development/RPi/sonic-pi/etc/synthdefs/compiled/"); ) \ No newline at end of file diff --git a/etc/synthdefs/designs/supercollider/bass_highend.scd b/etc/synthdefs/designs/supercollider/bass_highend.scd index ad94596e1f..a73c8421d1 100644 --- a/etc/synthdefs/designs/supercollider/bass_highend.scd +++ b/etc/synthdefs/designs/supercollider/bass_highend.scd @@ -54,5 +54,5 @@ SynthDef('sonic-pi-bass_highend',{| Out.ar(out_bus, snd * amp); -}).writeDefFile("/home/bmarx/music/sonic_pi/synthdefs/compiled/"); +}).writeDefFile("/Users/sam/Development/RPi/sonic-pi/etc/synthdefs/compiled/"); ) \ No newline at end of file diff --git a/etc/synthdefs/designs/supercollider/winwood_lead.scd b/etc/synthdefs/designs/supercollider/winwood_lead.scd index 40d90ccd9d..8f544ffb22 100644 --- a/etc/synthdefs/designs/supercollider/winwood_lead.scd +++ b/etc/synthdefs/designs/supercollider/winwood_lead.scd @@ -54,5 +54,5 @@ SynthDef('sonic-pi-winwood_lead', {| snd = Pan2.ar(Mix(snd) * EnvGen.kr(env, doneAction: 2), pan); Out.ar(out_bus, snd * amp); -}).writeDefFile("/home/bmarx/music/sonic_pi/synthdefs/compiled/"); +}).writeDefFile("/Users/sam/Development/RPi/sonic-pi/etc/synthdefs/compiled/"); ) \ No newline at end of file From c078ecb88ec844b34dbeb06bd3799075676f0566 Mon Sep 17 00:00:00 2001 From: SunderB <20426079+SunderB@users.noreply.github.com> Date: Tue, 29 Jan 2019 22:55:15 +0000 Subject: [PATCH 03/19] GUI - Add UI language option --- .gitignore | 3 +- app/gui/qt/CMakeLists.txt | 3 + app/gui/qt/main.cpp | 22 +--- app/gui/qt/mainwindow.cpp | 82 ++++++++++++-- app/gui/qt/mainwindow.h | 13 ++- app/gui/qt/model/settings.h | 1 + app/gui/qt/utils/lang_list.tmpl | 16 +++ app/gui/qt/utils/sonic_pi_i18n.cpp | 154 ++++++++++++++++++++++++++ app/gui/qt/utils/sonic_pi_i18n.h | 43 +++++++ app/gui/qt/widgets/settingswidget.cpp | 70 +++++++++++- app/gui/qt/widgets/settingswidget.h | 18 ++- app/server/ruby/bin/qt-doc.rb | 122 +++++++++++++++++++- 12 files changed, 511 insertions(+), 36 deletions(-) create mode 100644 app/gui/qt/utils/lang_list.tmpl create mode 100644 app/gui/qt/utils/sonic_pi_i18n.cpp create mode 100644 app/gui/qt/utils/sonic_pi_i18n.h diff --git a/.gitignore b/.gitignore index 16242e2a62..cf752e21d3 100644 --- a/.gitignore +++ b/.gitignore @@ -61,7 +61,8 @@ app/gui/qt/moc_mainwindow.cpp app/gui/qt/moc_sonicpiudpserver.cpp app/gui/qt/qrc_SonicPi.cpp app/gui/qt/utils/ruby_help.h -app/gui/qt/help/*.html +app/gui/qt/utils/lang_list.h +app/gui/qt/help app/gui/qt/help_files.qrc app/gui/qt/lang/*.qm app/gui/qt/qrc_help_files.cpp diff --git a/app/gui/qt/CMakeLists.txt b/app/gui/qt/CMakeLists.txt index db13691d54..8e55ed65d4 100644 --- a/app/gui/qt/CMakeLists.txt +++ b/app/gui/qt/CMakeLists.txt @@ -101,12 +101,15 @@ set(QT_SOURCES set(SOURCES ${QTAPP_ROOT}/main.cpp ${QTAPP_ROOT}/utils/scintilla_api.cpp + ${QTAPP_ROOT}/utils/sonic_pi_i18n.cpp ${QTAPP_ROOT}/widgets/sonicpilog.cpp ${QTAPP_ROOT}/widgets/sonicpilog.h ${QTAPP_ROOT}/widgets/sonicpicontext.cpp ${QTAPP_ROOT}/widgets/sonicpicontext.h ${QTAPP_ROOT}/utils/scintilla_api.h + ${QTAPP_ROOT}/utils/sonic_pi_i18n.h ${QTAPP_ROOT}/utils/ruby_help.h + ${QTAPP_ROOT}/utils/lang_list.h ${QTAPP_ROOT}/model/settings.h ) diff --git a/app/gui/qt/main.cpp b/app/gui/qt/main.cpp index 35d6522fd4..660de9a750 100644 --- a/app/gui/qt/main.cpp +++ b/app/gui/qt/main.cpp @@ -11,12 +11,13 @@ // notice is included. //++ +#include + #include #include #include #include #include -#include #include #include "mainwindow.h" @@ -35,7 +36,7 @@ int main(int argc, char *argv[]) { - + std::cout << "Starting Sonic Pi..." << std::endl; #ifndef Q_OS_MAC Q_INIT_RESOURCE(SonicPi); #endif @@ -49,16 +50,6 @@ int main(int argc, char *argv[]) qRegisterMetaType("SonicPiLog::MultiMessage"); - QString systemLocale = QLocale::system().uiLanguages()[0].replace("-", "_"); - - QTranslator qtTranslator; - qtTranslator.load("qt_" + systemLocale, QLibraryInfo::location(QLibraryInfo::TranslationsPath)); - app.installTranslator(&qtTranslator); - - QTranslator translator; - bool i18n = translator.load(QLatin1String("sonic-pi_") + systemLocale, QLatin1String(":/lang")) || systemLocale.startsWith("en") || systemLocale == "C"; - app.installTranslator(&translator); - app.setApplicationName(QObject::tr("Sonic Pi")); app.setStyle("gtk"); @@ -75,7 +66,7 @@ int main(int argc, char *argv[]) splash->show(); splash->repaint(); app.processEvents(); - MainWindow mainWin(app, i18n, splash); + MainWindow mainWin(app, splash); return app.exec(); #elif _WIN32 @@ -109,7 +100,7 @@ int main(int argc, char *argv[]) splash->show(); splash->repaint(); app.processEvents(); - MainWindow mainWin(app, i18n, splash); + MainWindow mainWin(app, splash); // Fix for full screen mode. See: https://doc.qt.io/qt-5/windows-issues.html#fullscreen-opengl-based-windows QWindowsWindowFunctions::setHasBorderInFullScreen(mainWin.windowHandle(), true); @@ -139,9 +130,8 @@ int main(int argc, char *argv[]) splashWindow->show(); app.processEvents(); - MainWindow mainWin(app, i18n, splashWindow); + MainWindow mainWin(app, splashWindow); return app.exec(); #endif - } diff --git a/app/gui/qt/mainwindow.cpp b/app/gui/qt/mainwindow.cpp index 716545af7b..b4a7e31da2 100644 --- a/app/gui/qt/mainwindow.cpp +++ b/app/gui/qt/mainwindow.cpp @@ -52,6 +52,8 @@ #include "widgets/sonicpilexer.h" #include "widgets/sonicpiscintilla.h" +#include "utils/sonic_pi_i18n.h" + #include "utils/borderlesslinksproxystyle.h" // OSC stuff @@ -105,9 +107,9 @@ using namespace SonicPi; #endif #ifdef Q_OS_MAC -MainWindow::MainWindow(QApplication& app, bool i18n, QMainWindow* splash) +MainWindow::MainWindow(QApplication& app, QMainWindow* splash) #else -MainWindow::MainWindow(QApplication& app, bool i18n, QSplashScreen* splash) +MainWindow::MainWindow(QApplication& app, QSplashScreen* splash) #endif { app.installEventFilter(this); @@ -118,9 +120,6 @@ MainWindow::MainWindow(QApplication& app, bool i18n, QSplashScreen* splash) this->piSettings = new SonicPiSettings(); - this->splash = splash; - this->i18n = i18n; - #ifdef QT_OLD_API sonicPiOSCServer = NULL; #else @@ -153,7 +152,6 @@ MainWindow::MainWindow(QApplication& app, bool i18n, QSplashScreen* splash) version_num = 0; latest_version_num = 0; this->splash = splash; - this->i18n = i18n; #ifdef QT_OLD_API guiID = QUuid::createUuid().toString(); #endif @@ -163,6 +161,10 @@ MainWindow::MainWindow(QApplication& app, bool i18n, QSplashScreen* splash) initPaths(); bool startupOK = false; + this->sonicPii18n = new SonicPii18n(rootPath()); + this->ui_language = sonicPii18n->determineUILanguage(piSettings->language); + std::cout << "Using language: " << ui_language.toUtf8().constData() << std::endl; + this->i18n = sonicPii18n->loadTranslations(ui_language); #ifdef QT_OLD_API // Clear out old tasks from previous sessions if they still exist @@ -579,7 +581,8 @@ void MainWindow::setupWindowStructure() prefsWidget->setAllowedAreas(Qt::RightDockWidgetArea); prefsWidget->setFeatures(QDockWidget::DockWidgetClosable); - settingsWidget = new SettingsWidget(server_osc_cues_port, piSettings, this); + settingsWidget = new SettingsWidget(server_osc_cues_port, i18n, piSettings, sonicPii18n, this); + connect(settingsWidget, SIGNAL(uiLanguageChanged(QString)), this, SLOT(changeUILanguage(QString))); connect(settingsWidget, SIGNAL(volumeChanged(int)), this, SLOT(changeSystemPreAmp(int))); connect(settingsWidget, SIGNAL(mixerSettingsChanged()), this, SLOT(mixerSettingsChanged())); connect(settingsWidget, SIGNAL(midiSettingsChanged()), this, SLOT(toggleMidi())); @@ -2179,6 +2182,43 @@ void MainWindow::changeSystemPreAmp(int val, int silent) statusBar()->showMessage(tr("Updating System Volume..."), 2000); } +// TODO: Implement real-time language switching +void MainWindow::changeUILanguage(QString lang) { + if (lang != piSettings->language) { + std::cout << "Current language: " << piSettings->language.toUtf8().constData() << std::endl; + std::cout << "New language selected: " << lang.toUtf8().constData() << std::endl; + QString old_lang = sonicPii18n->getNativeLanguageName(piSettings->language); + QString new_lang = sonicPii18n->getNativeLanguageName(lang); + + // Load new language + //QString language = sonicPii18n->determineUILanguage(lang); + //sonicPii18n->loadTranslations(language); + //QString title_new = tr("Updated the UI language from %s to %s").arg(); + + QMessageBox msgBox(this); + msgBox.setText(QString(tr("You've selected a new language: %1")).arg(new_lang)); + msgBox.setInformativeText(tr("Do you want to apply this language?\nApplying the new language will restart Sonic Pi.")); + QPushButton *restartButton = msgBox.addButton(tr("Apply and Restart"), QMessageBox::ActionRole); + QPushButton *dismissButton = msgBox.addButton(tr("Cancel"), QMessageBox::RejectRole); + msgBox.setDefaultButton(restartButton); + msgBox.setIcon(QMessageBox::Information); + msgBox.exec(); + + if (msgBox.clickedButton() == (QAbstractButton*)restartButton) { + piSettings->language = lang; + writeSettings(); + restartApp(); + //statusBar()->showMessage(tr("Updated UI language setting, please restart Sonic Pi to apply it"), 2000); + } else if (msgBox.clickedButton() == (QAbstractButton*)dismissButton) { + // Don't apply the new language settings + settingsWidget->updateSelectedUILanguage(piSettings->language); + } + + // Load previously set language + //sonicPii18n->loadTranslations(ui_language); + } +} + void MainWindow::changeScopeKindVisibility(QString name) { foreach (QAction* action, scopeKindVisibilityMenu->actions()) @@ -3500,6 +3540,8 @@ void MainWindow::readSettings() QSettings settings(QSettings::IniFormat, QSettings::UserScope, "sonic-pi.net", "gui-settings"); // Read in preferences from previous session + piSettings->language = settings.value("prefs/language", "system_locale").toString(); + piSettings->show_buttons = settings.value("prefs/show-buttons", true).toBool(); piSettings->show_tabs = settings.value("prefs/show-tabs", true).toBool(); piSettings->show_log = settings.value("prefs/show-log", true).toBool(); @@ -3549,6 +3591,7 @@ void MainWindow::writeSettings() { std::cout << "[GUI] - writing settings" << std::endl; QSettings settings(QSettings::IniFormat, QSettings::UserScope, "sonic-pi.net", "gui-settings"); + settings.setValue("prefs/language", piSettings->language); settings.setValue("pos", pos()); settings.setValue("size", size()); settings.setValue("first_time", 0); @@ -3732,6 +3775,31 @@ void MainWindow::onExitCleanup() #endif } +void MainWindow::restartApp() { + QApplication* app = dynamic_cast(parent()); + statusBar()->showMessage(tr("Restarting Sonic Pi..."), 10000); + // Save settings and perform some cleanup + writeSettings(); + onExitCleanup(); + sleep(1); + std::cout << "Performing application restart... please wait..." << std::endl; + //this->hide(); // So it doesn't look like the app's frozen or crashed + sleep(4); // Allow cleanup to complete + // Create new process + QStringList args = qApp->arguments(); + args.removeFirst(); + QProcess process; + bool restart_success = process.startDetached(qApp->arguments()[0], args); + if (restart_success) { + std::cout << "Successfully restarted sonic-pi" << std::endl; + } else { + std::cout << "Failed to restart sonic-pi" << std::endl; + } + // Quit + app->exit(0); + exit(0); +} + #ifdef QT_OLD_API void MainWindow::cleanupRunningProcesses() { diff --git a/app/gui/qt/mainwindow.h b/app/gui/qt/mainwindow.h index 6cb37cd2b1..31a35c10c5 100644 --- a/app/gui/qt/mainwindow.h +++ b/app/gui/qt/mainwindow.h @@ -17,6 +17,7 @@ #include #include + #include #include #include @@ -70,6 +71,7 @@ class InfoWidget; class SettingsWidget; class Scope; class ScintillaAPI; +class SonicPii18n; class SonicPiLog; class SonicPiScintilla; class SonicPiTheme; @@ -94,9 +96,9 @@ class MainWindow : public QMainWindow public: #if defined(Q_OS_MAC) - MainWindow(QApplication &ref, bool i18n, QMainWindow* splash); + MainWindow(QApplication &ref, QMainWindow* splash); #else - MainWindow(QApplication &ref, bool i18n, QSplashScreen* splash); + MainWindow(QApplication &ref, QSplashScreen* splash); #endif SonicPiLog* GetOutputPane() const; @@ -123,6 +125,8 @@ class MainWindow : public QMainWindow bool loaded_workspaces; QString hash_salt; + QString ui_language; + protected: void closeEvent(QCloseEvent *event); @@ -137,6 +141,7 @@ class MainWindow : public QMainWindow private slots: + void changeUILanguage(QString lang); void updateContext(int line, int index); void updateContextWithCurrentWs(); void docLinkClicked(const QUrl &url); @@ -182,6 +187,7 @@ class MainWindow : public QMainWindow void help(); void toggleHelpIcon(); void onExitCleanup(); + void restartApp(); void toggleRecording(); void toggleRecordingOnIcon(); void changeSystemPreAmp(int val, int silent=0); @@ -351,10 +357,12 @@ class MainWindow : public QMainWindow QString rootPath(); void addUniversalCopyShortcuts(QTextEdit *te); + void updateTranslatedUIText(); QMenu *liveMenu, *codeMenu, *audioMenu, *displayMenu, *viewMenu, *ioMenu, *ioMidiInMenu, *ioMidiOutMenu, *ioMidiOutChannelMenu, *localIpAddressesMenu, *themeMenu, *scopeKindVisibilityMenu; SonicPiSettings *piSettings; + SonicPii18n *sonicPii18n; #ifdef QT_OLD_API QFuture osc_thread, server_thread; @@ -458,4 +466,3 @@ class MainWindow : public QMainWindow QSet cuePaths; }; - diff --git a/app/gui/qt/model/settings.h b/app/gui/qt/model/settings.h index 757da5134f..57f1e28df9 100644 --- a/app/gui/qt/model/settings.h +++ b/app/gui/qt/model/settings.h @@ -24,6 +24,7 @@ class SonicPiSettings { QString midi_default_channel_str; // EditorSettings + QString language; bool auto_indent_on_run; bool show_line_numbers; bool show_log; diff --git a/app/gui/qt/utils/lang_list.tmpl b/app/gui/qt/utils/lang_list.tmpl new file mode 100644 index 0000000000..c8f0703033 --- /dev/null +++ b/app/gui/qt/utils/lang_list.tmpl @@ -0,0 +1,16 @@ +//-- +// This file is part of Sonic Pi: http://sonic-pi.net +// Full project source: https://github.com/samaaron/sonic-pi +// License: https://github.com/samaaron/sonic-pi/blob/main/LICENSE.md +// +// Copyright 2020 by Sam Aaron (http://sam.aaron.name). +// All rights reserved. +// +// Permission is granted for use, copying, modification, distribution, +// and distribution of modified versions of this work as long as this +// notice is included. +//++ + +// AUTO-GENERATED-DOCS +// Do not manually add any code below this comment +// otherwise it may be removed diff --git a/app/gui/qt/utils/sonic_pi_i18n.cpp b/app/gui/qt/utils/sonic_pi_i18n.cpp new file mode 100644 index 0000000000..2a256adf88 --- /dev/null +++ b/app/gui/qt/utils/sonic_pi_i18n.cpp @@ -0,0 +1,154 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "sonic_pi_i18n.h" +#include "lang_list.h" + +SonicPii18n::SonicPii18n(QString rootpath) { + this->root_path = rootpath; + this->available_languages = findAvailableLanguages(); + // Set to true unless we can't load the system language + this->system_language_available = true; + + // // Print all Qt's language codes for debugging + // QList allLocales = QLocale::matchingLocales( + // QLocale::AnyLanguage, + // QLocale::AnyScript, + // QLocale::AnyCountry); + // QStringList iso639LanguageCodes; + // + // for(const QLocale &locale : allLocales) { + // iso639LanguageCodes << locale.name(); + // } + // + // std::cout << iso639LanguageCodes.join("\n").toUtf8().constData() << std::endl; +} + +SonicPii18n::~SonicPii18n() { +} + +QString SonicPii18n::determineUILanguage(QString lang_pref) { + QStringList available_languages = getAvailableLanguages(); + //std::cout << available_languages.join("\n").toUtf8().constData() << std::endl; + QLocale locale; + + if (lang_pref != "system_locale") { + if (available_languages.contains(lang_pref)) { + return lang_pref; + } + + // Add the general language as a fallback (e.g. pt_BR -> pt) + QString general_name = lang_pref; + general_name.truncate(lang_pref.lastIndexOf('_')); + general_name.truncate(general_name.lastIndexOf('-')); + + if (available_languages.contains(general_name)) { + return general_name; + } + } else { + QStringList preferred_languages = locale.uiLanguages(); + // If the specified language isn't available, or if the setting is set to system_locale... + // ...run through the list of preferred languages + std::cout << "Looping through preferred ui languages" << std::endl; + + QString l; + for (int i = 0; i < preferred_languages.length(); i += 1) { + l = preferred_languages[i]; + l.replace("-", "_"); + + //std::cout << preferred_languages[i].toUtf8().constData() << std::endl; + if (available_languages.contains(l)) { + return l; + } + } + } + + // Fallback to English + this->system_language_available = false; + return "en"; +} + +QStringList SonicPii18n::findAvailableLanguages() { + QStringList languages; + QLocale locale; + + QString m_langPath = root_path + "/app/gui/qt/lang"; + std::cout << m_langPath.toUtf8().constData() << "\n"; + QDir dir(m_langPath); + QStringList fileNames = dir.entryList(QStringList("sonic-pi_*.qm")); + + for (int i = 0; i < fileNames.size(); ++i) { + // get locale extracted by filename + QString locale; + locale = fileNames[i]; // "sonic-pi_pt_BR.qm" + locale.truncate(locale.lastIndexOf('.')); // "sonic-pi_pt_BR" + locale.remove(0, locale.lastIndexOf("sonic-pi_") + 9); // "pt_BR" + //locale.replace("_", "-"); // Replace underscores with dashes so it matches the language codes e.g: "pt-BR" + //std::cout << locale.toUtf8().constData() << '\n'; + languages << locale; + } + // Add the source language + languages << "en_GB"; + languages.sort(); + return languages; +} + +bool SonicPii18n::loadTranslations(QString lang) { + QString language = lang; + bool i18n = false; + QCoreApplication* app = QCoreApplication::instance(); + + // Remove any previous translations + app->removeTranslator(&translator); + app->removeTranslator(&qtTranslator); + + std::cout << "Loading translations for " << language.toUtf8().constData() << std::endl; + + i18n = translator.load("sonic-pi_" + language, ":/lang/") || language == "en_GB" || language == "en" || language == "C"; + if (!i18n) { + std::cout << language.toUtf8().constData() << ": Language translation not available" << std::endl; + language = "en"; + } + app->installTranslator(&translator); + + qtTranslator.load("qt_" + language, QLibraryInfo::location(QLibraryInfo::TranslationsPath)); + app->installTranslator(&qtTranslator); + + return i18n; +} + +QStringList SonicPii18n::getAvailableLanguages() { + return this->available_languages; +} + +std::map SonicPii18n::getNativeLanguageNameList() { + return native_language_names; +} + +QString SonicPii18n::getNativeLanguageName(QString lang) { + std::map::iterator it = native_language_names.find(lang); + if(it != native_language_names.end()) { + // language found + return native_language_names[lang]; + } else if (lang == "system_locale") { + return tr("System language"); + } else { + std::cout << "Warning: Predefined language name not found'" << lang.toUtf8().constData() << "'" << std::endl; + // Try using QLocale to find the native language name + QLocale locale(lang); + QString name = locale.nativeLanguageName(); + if (name != "C" && name != "") { + return locale.nativeLanguageName(); + } else { + std::cout << "Warning: Invalid language code '" << lang.toUtf8().constData() << "'" << std::endl; + return lang; + } + } +} diff --git a/app/gui/qt/utils/sonic_pi_i18n.h b/app/gui/qt/utils/sonic_pi_i18n.h new file mode 100644 index 0000000000..4e03f94bdc --- /dev/null +++ b/app/gui/qt/utils/sonic_pi_i18n.h @@ -0,0 +1,43 @@ +//-- +// This file is part of Sonic Pi: http://sonic-pi.net +// Full project source: https://github.com/samaaron/sonic-pi +// License: https://github.com/samaaron/sonic-pi/blob/main/LICENSE.md +// +// Copyright 2013, 2014, 2015, 2016 by Sam Aaron (http://sam.aaron.name). +// All rights reserved. +// +// Permission is granted for use, copying, modification, and +// distribution of modified versions of this work as long as this +// notice is included. +//++ + +#include +#include + +#ifndef SONIC_PI_I18N_H +#define SONIC_PI_I18N_H + +class SonicPii18n : public QObject { +public: + SonicPii18n(QString rootpath); + ~SonicPii18n(); + +public slots: + QString determineUILanguage(QString lang_pref); + QStringList getAvailableLanguages(); + std::map getNativeLanguageNameList(); + QString getNativeLanguageName(QString lang); + bool loadTranslations(QString lang); + + bool system_language_available; + +private: + QString root_path; + QTranslator qtTranslator; + QTranslator translator; + QStringList available_languages; + static std::map native_language_names; + + QStringList findAvailableLanguages(); +}; +#endif diff --git a/app/gui/qt/widgets/settingswidget.cpp b/app/gui/qt/widgets/settingswidget.cpp index e124349131..64fe304dcf 100644 --- a/app/gui/qt/widgets/settingswidget.cpp +++ b/app/gui/qt/widgets/settingswidget.cpp @@ -1,4 +1,5 @@ #include "settingswidget.h" +#include "utils/sonic_pi_i18n.h" #include #include @@ -20,9 +21,15 @@ /** * Default Constructor */ -SettingsWidget::SettingsWidget( int port, SonicPiSettings *piSettings, QWidget *parent) { +SettingsWidget::SettingsWidget(int port, bool i18n, SonicPiSettings *piSettings, SonicPii18n *sonicPii18n, QWidget *parent) { this->piSettings = piSettings; + this->i18n = i18n; + this->sonicPii18n = sonicPii18n; + this->localeNames = sonicPii18n->getNativeLanguageNameList(); + this->available_languages = sonicPii18n->getAvailableLanguages(); + available_languages.prepend("system_locale"); server_osc_cues_port = port; + prefTabs = new QTabWidget(); QGridLayout *grid = new QGridLayout; @@ -43,7 +50,7 @@ SettingsWidget::SettingsWidget( int port, SonicPiSettings *piSettings, QWidget QGroupBox *update_prefs_box = createUpdatePrefsTab(); prefTabs->addTab(update_prefs_box, tr("Updates")); - if (!i18n) { + if (!sonicPii18n->system_language_available) { QGroupBox *translation_box = new QGroupBox("Translation"); QVBoxLayout *translation_box_layout = new QVBoxLayout; QLabel *go_translate = new QLabel; @@ -53,7 +60,7 @@ SettingsWidget::SettingsWidget( int port, SonicPiSettings *piSettings, QWidget QLocale::languageToString(QLocale::system().language()) + " yet.
" + "We rely on crowdsourcing to help create and maintain translations.
" + - "" + + "" + "Please consider helping to translate Sonic Pi to your language. " ); go_translate->setTextFormat(Qt::RichText); @@ -365,10 +372,35 @@ QGroupBox* SettingsWidget::createEditorPrefsTab() { debug_box_layout->addWidget(clear_output_on_run); debug_box->setLayout(debug_box_layout); + + QGroupBox *language_box = new QGroupBox(tr("Language")); + language_box->setToolTip(tr("Configure language settings")); + + language_combo = new QComboBox(); + add_language_combo_box_entries(language_combo); + language_combo->setToolTip(tr("Change the language of the UI & Tutorial (Requires a restart to take effect)")); + language_combo->setMinimumContentsLength(2); + language_combo->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength); + + language_option_label = new QLabel; + language_option_label->setText(tr("UI & Tutorial Language (Requires a restart to take effect)")); + language_option_label->setToolTip(tr("Change the language of the UI & Tutorial (Requires a restart to take effect)")); + + language_info_label = new QLabel; + language_info_label->setText(tr("Translations have been generously provided by volunteers \non https://hosted.weblate.org/projects/sonic-pi/. Thank you! :)")); + + QVBoxLayout *language_box_layout = new QVBoxLayout; + language_box_layout->addWidget(language_combo); + language_box_layout->addWidget(language_option_label); + language_box_layout->addWidget(language_info_label); + + language_box->setLayout(language_box_layout); + gridEditorPrefs->addWidget(editor_display_box, 0, 0); gridEditorPrefs->addWidget(editor_look_feel_box, 0, 1); gridEditorPrefs->addWidget(automation_box, 1, 1); gridEditorPrefs->addWidget(debug_box, 1, 0); + gridEditorPrefs->addWidget(language_box, 2, 0, 1, 2); editor_box->setLayout(gridEditorPrefs); return editor_box; @@ -490,7 +522,11 @@ void SettingsWidget::updateScopeKindVisibility() { QCheckBox *cb = qobject_cast(scope_box_kinds_layout->itemAt(i)->widget()); cb->setChecked(piSettings->isScopeActive(cb->text())); } +} +void SettingsWidget::updateSelectedUILanguage(QString lang) { + int index = available_languages.indexOf(lang); + language_combo->setCurrentIndex(index); } void SettingsWidget::toggleScope( QWidget* qw ) { @@ -502,6 +538,11 @@ void SettingsWidget::toggleScope( QWidget* qw ) { emit scopeChanged(name); } +void SettingsWidget::updateUILanguage(int index) { + QString lang = available_languages[index]; + std::cout << "Changed language to " << lang.toUtf8().constData() << std::endl; + emit uiLanguageChanged(lang); +} void SettingsWidget::update_mixer_invert_stereo() { emit mixerSettingsChanged(); @@ -629,7 +670,7 @@ void SettingsWidget::autoIndentOnRun() { } void SettingsWidget::openSonicPiNet() { - QDesktopServices::openUrl(QUrl("http://sonic-pi.net", QUrl::TolerantMode)); + QDesktopServices::openUrl(QUrl("https://sonic-pi.net", QUrl::TolerantMode)); } void SettingsWidget::updateVersionInfo( QString info_string, QString visit, bool sonic_pi_net_visible, bool check_now_visible) { @@ -641,6 +682,7 @@ void SettingsWidget::updateVersionInfo( QString info_string, QString visit, bool void SettingsWidget::updateSettings() { std::cout << "[GUI] - Update Settings" << std::endl; + piSettings->language = available_languages[language_combo->currentIndex()]; piSettings->mixer_invert_stereo = mixer_invert_stereo->isChecked(); piSettings->mixer_force_mono = mixer_force_mono->isChecked(); piSettings->check_args = check_args->isChecked(); @@ -681,7 +723,7 @@ void SettingsWidget::updateSettings() { } void SettingsWidget::settingsChanged() { - + language_combo->setCurrentIndex(available_languages.indexOf(piSettings->language)); mixer_invert_stereo->setChecked(piSettings->mixer_invert_stereo); mixer_force_mono->setChecked(piSettings->mixer_force_mono); check_args->setChecked(piSettings->check_args); @@ -724,6 +766,8 @@ void SettingsWidget::settingsChanged() { } void SettingsWidget::connectAll() { + //connect(language_combo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateSettings())); + connect(language_combo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateUILanguage(int))); connect(mixer_invert_stereo, SIGNAL(clicked()), this, SLOT(updateSettings())); connect(mixer_force_mono, SIGNAL(clicked()), this, SLOT(updateSettings())); connect(check_args, SIGNAL(clicked()), this, SLOT(updateSettings())); @@ -798,3 +842,19 @@ void SettingsWidget::connectAll() { connect(clear_output_on_run, SIGNAL(clicked()), this, SLOT(clearOutputOnRun())); connect(auto_indent_on_run, SIGNAL(clicked()), this, SLOT(autoIndentOnRun())); } + +void SettingsWidget::add_language_combo_box_entries(QComboBox* combo) { + // Add language combo entries + std::cout << "[Debug] Adding language combo box entries..." << std::endl; + std::cout << (std::to_string(static_cast(available_languages.size()))) << std::endl; + + for (auto const &language : available_languages) { + std::cout << "[Debug] Adding language " << language.toUtf8().data() << " to the combo box" << std::endl; + if (language != "system_locale") { + // Add the language's name to the combo box + combo->addItem(sonicPii18n->getNativeLanguageName(language)); + } else { + combo->addItem(tr("Use system language")); + } + } +} diff --git a/app/gui/qt/widgets/settingswidget.h b/app/gui/qt/widgets/settingswidget.h index 5fd983f862..1a97148a01 100644 --- a/app/gui/qt/widgets/settingswidget.h +++ b/app/gui/qt/widgets/settingswidget.h @@ -2,6 +2,7 @@ #define SETTINGSWIDGET_H #include "model/settings.h" +#include "utils/sonic_pi_i18n.h" #include @@ -23,16 +24,19 @@ class SettingsWidget : public QWidget Q_OBJECT public: - SettingsWidget( int server_osc_cues_port, SonicPiSettings *piSettings, QWidget *parent = 0); + SettingsWidget(int server_osc_cues_port, bool i18n, SonicPiSettings *piSettings, SonicPii18n *sonicPii18n, QWidget *parent = nullptr); ~SettingsWidget(); void updateVersionInfo( QString info_string, QString visit, bool sonic_pi_net_visible, bool check_now_visible); void updateMidiInPorts( QString in ); void updateMidiOutPorts( QString out ); void updateScopeNames(std::vector); + void updateSelectedUILanguage(QString lang); + QSize sizeHint() const; private slots: + void updateUILanguage(int index); void update_mixer_invert_stereo(); void update_mixer_force_mono(); void toggleOscServer(); @@ -68,6 +72,7 @@ private slots: void autoIndentOnRun(); signals: + void uiLanguageChanged(QString lang); void mixerSettingsChanged(); void oscSettingsChanged(); void midiSettingsChanged(); @@ -100,6 +105,10 @@ private slots: private: SonicPiSettings* piSettings; + SonicPii18n* sonicPii18n; + std::map localeNames; + QStringList available_languages; + bool i18n; int server_osc_cues_port; QTabWidget *prefTabs; @@ -154,14 +163,19 @@ private slots: QSlider *system_vol_slider; QSlider *gui_transparency_slider; + QComboBox *language_combo; + QLabel *language_option_label; + QLabel *language_info_label; + // TODO - bool i18n = true; QGroupBox* createAudioPrefsTab(); QGroupBox* createIoPrefsTab(); QGroupBox* createEditorPrefsTab(); QGroupBox* createVisualizationPrefsTab(); QGroupBox* createUpdatePrefsTab(); + void add_language_combo_box_entries(QComboBox* combo); + QString tooltipStrShiftMeta(char key, QString str); void connectAll(); diff --git a/app/server/ruby/bin/qt-doc.rb b/app/server/ruby/bin/qt-doc.rb index 421a192f32..0d209b87e4 100755 --- a/app/server/ruby/bin/qt-doc.rb +++ b/app/server/ruby/bin/qt-doc.rb @@ -30,6 +30,61 @@ include SonicPi::Util +# List of all languages with GUI translation files +@lang_names = Hash[ + "bg" => "български", # Bulgarian + "bn" => "বাংলা", # Bengali/Bangla + "bs" => "Bosanski/босански", # Bosnian + "ca" => "Català", # Catalan + "ca@valencia" => "Valencià", # Valencian + "cs" => "Čeština", # Czech + "da" => "Dansk", # Danish + "de" => "Deutsch", # German + "el" => "ελληνικά", # Greek + "en_AU" => "English (Australian)", # English (Australian) + "en_GB" => "English (UK)", # English (UK) - default language + "en_US" => "English (US)", # English (US) + "eo" => "Esperanto", # Esperanto + "es" => "Español", # Spanish + "et" => "Eesti keel", # Estonian + "fa" => "فارسی", # Persian + "fi" => "Suomi", # Finnish + "fr" => "Français", # French + "ga" => "Gaeilge", # Irish + "gl" => "Galego", # Galician + "he" => "עברית", # Hebrew + "hi" => "हिन्दी", # Hindi + "hu" => "Magyar", # Hungarian + "hy" => "Հայերեն", # Armenian + "id" => "Bahasa Indonesia", # Indonesian + "is" => "Íslenska", # Icelandic + "it" => "Italiano", # Italian + "ja" => "日本語", # Japanese + "ka" => "ქართული", # Georgian + "ko" => "한국어", # Korean + "nb" => "Norsk Bokmål", # Norwegian Bokmål + "nl" => "Nederlands", # Dutch (Netherlands) + "pl" => "Polski", # Polish + "pt" => "Português", # Portuguese + "pt_BR" => "Português do Brasil", # Brazilian Portuguese + "ro" => "Română", # Romanian + "ru" => "Pусский", # Russian + "si" => "සිංහල", # Sinhala/Sinhalese + "sk" => "Slovenčina",#/Slovenský Jazyk", # Slovak/Slovakian + "sl" => "Slovenščina",#/Slovenski Jezik", # Slovenian + "sv" => "Svenska", # Swedish + "sw" => "Kiswahili", # Swahili + "th" => "ไทย", # Thai + "tr" => "Türkçe", # Turkish + "ug" => "ئۇيغۇر تىلى", # Uyghur + "uk" => "Українська", # Ukranian + "vi" => "Tiếng Việt", # Vietnamese + "zh" => "简体中文", # Mandarin Chinese (Simplified) + "zh_Hans" => "简体中文", # Mandarin Chinese (Simplified) + "zh_HK" => "廣東話", # Mandarin Chinese (Traditional, Hong Kong) + "zh_TW" => "臺灣華語" # Mandarin Chinese (Traditional, Taiwan) +] + FileUtils::rm_rf "#{qt_gui_path}/help/" FileUtils::mkdir "#{qt_gui_path}/help/" @@ -216,11 +271,11 @@ map { |p| File.basename(p).gsub(/sonic-pi-tutorial-(.*?).po/, '\1') }. sort_by {|n| -n.length} -docs << "\n QString systemLocale = QLocale::system().uiLanguages()[0];\n\n" unless languages.empty? +docs << "\n" # first, try to match all non-default languages (those that aren't "en") languages.each do |lang| - docs << "if (systemLocale.startsWith(\"#{lang}\")) {\n" + docs << "if (this->ui_language.startsWith(\"#{lang}\")) {\n" make_tutorial.call(lang) docs << "} else " end @@ -263,6 +318,67 @@ docs << " autocomplete->addSynthArgs(\":#{k}\", fxtmp);\n\n" end +def generate_ui_lang_names + # Make a function to define the locale list map ----- + ui_languages = @lang_names.keys + ui_languages = ui_languages.sort_by {|l| l.downcase} + locale_arrays = [] + locale_arrays << "std::map SonicPii18n::native_language_names = {\n" + # # Add each language + # locale_arrays << "{0, \"system_locale\"}" + # i = 1 + # ui_languages.each do |lang| + # locale_arrays << ",\n" + # locale_arrays << "{#{i.to_s}, \"#{lang}\"}" + # i += 1 + # end + # # End the map + # locale_arrays << "\n};\n" + # + # # Create a map of the locales to their indices in availableLocales, called localeIndex + # locale_arrays << "localeIndex = {\n" + # # Add each language + # locale_arrays << "{\"system_locale\", 0}" + # i = 1 + # ui_languages.each do |lang| + # locale_arrays << ",\n" + # locale_arrays << "{\"#{lang}\", #{i.to_s}}" + # i += 1 + # end + # # End the map + # locale_arrays << "\n};\n" + # + # # Create a map of the locales to their native names, called localeNames + # locale_arrays << "localeNames = {\n" + # Add each language + locale_arrays << "{\"system_locale\", \"\"}" + ui_languages.each do |lang| + locale_arrays << ",\n" + locale_arrays << "{\"#{lang}\", \"#{@lang_names[lang]}\"}" + end + # End the map + locale_arrays << "\n};\n" + + # End the function + #locale_arrays << "};\n" + + content = File.readlines("#{qt_gui_path}/utils/lang_list.tmpl") + lang_names_generated = content.take_while { |line| !line.start_with?("// AUTO-GENERATED")} + lang_names_generated << "// AUTO-GENERATED HEADER FILE\n" + lang_names_generated << "// Do not add any code to this file\n" + lang_names_generated << "// as it will be removed/overwritten\n" + lang_names_generated << "\n" + lang_names_generated << "#ifndef LANG_LIST_H\n" + lang_names_generated << "#define LANG_LIST_H\n" + lang_names_generated << "#include \n" + lang_names_generated << locale_arrays.join() + lang_names_generated << "#endif\n" + + File.open("#{qt_gui_path}/utils/lang_list.h", 'w') do |f| + f << lang_names_generated.join() + end +end + # update ruby_help.h if options[:output_name] then @@ -321,3 +437,5 @@ end end + +generate_ui_lang_names() From 7c663dae24a6597ad59b277bd7a5ddd886f80789 Mon Sep 17 00:00:00 2001 From: SunderB <20426079+SunderB@users.noreply.github.com> Date: Sun, 14 Feb 2021 12:13:51 +0000 Subject: [PATCH 04/19] GUI - Fix restartApp() on Windows; neaten and clean up changes made in the previous commit * Change the language setting value 'system_locale' to 'system_language' * Remove some verbose/unnecessary logging and comments * Remove the entry for system_language from SonicPii18n::native_language_names (we use tr("System language") instead) --- app/gui/qt/mainwindow.cpp | 21 +++++++------- app/gui/qt/utils/sonic_pi_i18n.cpp | 26 ++++++----------- app/gui/qt/widgets/settingswidget.cpp | 4 +-- app/server/ruby/bin/qt-doc.rb | 40 +++++---------------------- 4 files changed, 27 insertions(+), 64 deletions(-) diff --git a/app/gui/qt/mainwindow.cpp b/app/gui/qt/mainwindow.cpp index b4a7e31da2..a5e948c0fd 100644 --- a/app/gui/qt/mainwindow.cpp +++ b/app/gui/qt/mainwindow.cpp @@ -162,6 +162,7 @@ MainWindow::MainWindow(QApplication& app, QSplashScreen* splash) bool startupOK = false; this->sonicPii18n = new SonicPii18n(rootPath()); + std::cout << "Language setting: " << piSettings->language.toUtf8().constData() << std::endl; this->ui_language = sonicPii18n->determineUILanguage(piSettings->language); std::cout << "Using language: " << ui_language.toUtf8().constData() << std::endl; this->i18n = sonicPii18n->loadTranslations(ui_language); @@ -182,8 +183,8 @@ MainWindow::MainWindow(QApplication& app, QSplashScreen* splash) std::cout << "[GUI] - ===========================" << std::endl; std::cout << "[GUI] - " << std::endl; std::cout << "[GUI] - " << guiID.toStdString() << std::endl; - std::cout << "[GUI] - ui locale: " << QLocale::system().uiLanguages()[0].toStdString() << std::endl; - std::cout << "[GUI] - sys locale: " << QLocale::system().name().toStdString() << std::endl; + std::cout << "[GUI] - ui locale: " << ui_language.toUtf8().constData() << std::endl; + std::cout << "[GUI] - sys locale: " << QLocale::system().name().toStdString() << std::endl; if(i18n) { @@ -2206,16 +2207,12 @@ void MainWindow::changeUILanguage(QString lang) { if (msgBox.clickedButton() == (QAbstractButton*)restartButton) { piSettings->language = lang; - writeSettings(); restartApp(); - //statusBar()->showMessage(tr("Updated UI language setting, please restart Sonic Pi to apply it"), 2000); } else if (msgBox.clickedButton() == (QAbstractButton*)dismissButton) { // Don't apply the new language settings settingsWidget->updateSelectedUILanguage(piSettings->language); } - // Load previously set language - //sonicPii18n->loadTranslations(ui_language); } } @@ -3540,7 +3537,7 @@ void MainWindow::readSettings() QSettings settings(QSettings::IniFormat, QSettings::UserScope, "sonic-pi.net", "gui-settings"); // Read in preferences from previous session - piSettings->language = settings.value("prefs/language", "system_locale").toString(); + piSettings->language = settings.value("prefs/language", "system_language").toString(); piSettings->show_buttons = settings.value("prefs/show-buttons", true).toBool(); piSettings->show_tabs = settings.value("prefs/show-tabs", true).toBool(); @@ -3778,13 +3775,14 @@ void MainWindow::onExitCleanup() void MainWindow::restartApp() { QApplication* app = dynamic_cast(parent()); statusBar()->showMessage(tr("Restarting Sonic Pi..."), 10000); + // Save settings and perform some cleanup writeSettings(); onExitCleanup(); - sleep(1); std::cout << "Performing application restart... please wait..." << std::endl; - //this->hide(); // So it doesn't look like the app's frozen or crashed - sleep(4); // Allow cleanup to complete + // Allow cleanup to complete + std::this_thread::sleep_for(2s); + // Create new process QStringList args = qApp->arguments(); args.removeFirst(); @@ -3795,6 +3793,7 @@ void MainWindow::restartApp() { } else { std::cout << "Failed to restart sonic-pi" << std::endl; } + // Quit app->exit(0); exit(0); @@ -4373,7 +4372,7 @@ SonicPiLog* MainWindow::GetIncomingPane() const { return incomingPane; } - + SonicPiTheme* MainWindow::GetTheme() const { return theme; diff --git a/app/gui/qt/utils/sonic_pi_i18n.cpp b/app/gui/qt/utils/sonic_pi_i18n.cpp index 2a256adf88..b942a3b72a 100644 --- a/app/gui/qt/utils/sonic_pi_i18n.cpp +++ b/app/gui/qt/utils/sonic_pi_i18n.cpp @@ -14,21 +14,9 @@ SonicPii18n::SonicPii18n(QString rootpath) { this->root_path = rootpath; this->available_languages = findAvailableLanguages(); + // Set to true unless we can't load the system language this->system_language_available = true; - - // // Print all Qt's language codes for debugging - // QList allLocales = QLocale::matchingLocales( - // QLocale::AnyLanguage, - // QLocale::AnyScript, - // QLocale::AnyCountry); - // QStringList iso639LanguageCodes; - // - // for(const QLocale &locale : allLocales) { - // iso639LanguageCodes << locale.name(); - // } - // - // std::cout << iso639LanguageCodes.join("\n").toUtf8().constData() << std::endl; } SonicPii18n::~SonicPii18n() { @@ -39,7 +27,7 @@ QString SonicPii18n::determineUILanguage(QString lang_pref) { //std::cout << available_languages.join("\n").toUtf8().constData() << std::endl; QLocale locale; - if (lang_pref != "system_locale") { + if (lang_pref != "system_language") { if (available_languages.contains(lang_pref)) { return lang_pref; } @@ -54,7 +42,7 @@ QString SonicPii18n::determineUILanguage(QString lang_pref) { } } else { QStringList preferred_languages = locale.uiLanguages(); - // If the specified language isn't available, or if the setting is set to system_locale... + // If the specified language isn't available, or if the setting is set to system_language... // ...run through the list of preferred languages std::cout << "Looping through preferred ui languages" << std::endl; @@ -80,7 +68,7 @@ QStringList SonicPii18n::findAvailableLanguages() { QLocale locale; QString m_langPath = root_path + "/app/gui/qt/lang"; - std::cout << m_langPath.toUtf8().constData() << "\n"; + //std::cout << m_langPath.toUtf8().constData() << "\n"; QDir dir(m_langPath); QStringList fileNames = dir.entryList(QStringList("sonic-pi_*.qm")); @@ -133,12 +121,14 @@ std::map SonicPii18n::getNativeLanguageNameList() { } QString SonicPii18n::getNativeLanguageName(QString lang) { + if (lang == "system_language") { + return tr("System language"); + } + std::map::iterator it = native_language_names.find(lang); if(it != native_language_names.end()) { // language found return native_language_names[lang]; - } else if (lang == "system_locale") { - return tr("System language"); } else { std::cout << "Warning: Predefined language name not found'" << lang.toUtf8().constData() << "'" << std::endl; // Try using QLocale to find the native language name diff --git a/app/gui/qt/widgets/settingswidget.cpp b/app/gui/qt/widgets/settingswidget.cpp index 64fe304dcf..5a6b55758c 100644 --- a/app/gui/qt/widgets/settingswidget.cpp +++ b/app/gui/qt/widgets/settingswidget.cpp @@ -27,7 +27,7 @@ SettingsWidget::SettingsWidget(int port, bool i18n, SonicPiSettings *piSettings, this->sonicPii18n = sonicPii18n; this->localeNames = sonicPii18n->getNativeLanguageNameList(); this->available_languages = sonicPii18n->getAvailableLanguages(); - available_languages.prepend("system_locale"); + available_languages.prepend("system_language"); server_osc_cues_port = port; prefTabs = new QTabWidget(); @@ -850,7 +850,7 @@ void SettingsWidget::add_language_combo_box_entries(QComboBox* combo) { for (auto const &language : available_languages) { std::cout << "[Debug] Adding language " << language.toUtf8().data() << " to the combo box" << std::endl; - if (language != "system_locale") { + if (language != "system_language") { // Add the language's name to the combo box combo->addItem(sonicPii18n->getNativeLanguageName(language)); } else { diff --git a/app/server/ruby/bin/qt-doc.rb b/app/server/ruby/bin/qt-doc.rb index 0d209b87e4..159c8864b1 100755 --- a/app/server/ruby/bin/qt-doc.rb +++ b/app/server/ruby/bin/qt-doc.rb @@ -319,49 +319,23 @@ end def generate_ui_lang_names - # Make a function to define the locale list map ----- + # Define the language list map ----- ui_languages = @lang_names.keys ui_languages = ui_languages.sort_by {|l| l.downcase} locale_arrays = [] locale_arrays << "std::map SonicPii18n::native_language_names = {\n" + # # Add each language - # locale_arrays << "{0, \"system_locale\"}" - # i = 1 - # ui_languages.each do |lang| - # locale_arrays << ",\n" - # locale_arrays << "{#{i.to_s}, \"#{lang}\"}" - # i += 1 - # end - # # End the map - # locale_arrays << "\n};\n" - # - # # Create a map of the locales to their indices in availableLocales, called localeIndex - # locale_arrays << "localeIndex = {\n" - # # Add each language - # locale_arrays << "{\"system_locale\", 0}" - # i = 1 - # ui_languages.each do |lang| - # locale_arrays << ",\n" - # locale_arrays << "{\"#{lang}\", #{i.to_s}}" - # i += 1 - # end - # # End the map - # locale_arrays << "\n};\n" - # - # # Create a map of the locales to their native names, called localeNames - # locale_arrays << "localeNames = {\n" - # Add each language - locale_arrays << "{\"system_locale\", \"\"}" - ui_languages.each do |lang| - locale_arrays << ",\n" + for i in 0..(ui_languages.length()-1) do + lang = ui_languages[i] + locale_arrays << ",\n" if i != 0 locale_arrays << "{\"#{lang}\", \"#{@lang_names[lang]}\"}" end + # End the map locale_arrays << "\n};\n" - # End the function - #locale_arrays << "};\n" - + # Write the map to lang_list.h content = File.readlines("#{qt_gui_path}/utils/lang_list.tmpl") lang_names_generated = content.take_while { |line| !line.start_with?("// AUTO-GENERATED")} lang_names_generated << "// AUTO-GENERATED HEADER FILE\n" From 759fa020cf39875684a1cd4fd7fbab63b300318b Mon Sep 17 00:00:00 2001 From: SunderB <20426079+SunderB@users.noreply.github.com> Date: Sun, 14 Feb 2021 13:32:28 +0000 Subject: [PATCH 05/19] GUI - move language change message box handling from MainWindow to SettingsWidget --- app/gui/qt/mainwindow.cpp | 2 +- app/gui/qt/widgets/settingswidget.cpp | 37 ++++++++++++++++++++++++--- app/gui/qt/widgets/settingswidget.h | 3 ++- 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/app/gui/qt/mainwindow.cpp b/app/gui/qt/mainwindow.cpp index a5e948c0fd..f1e21477f6 100644 --- a/app/gui/qt/mainwindow.cpp +++ b/app/gui/qt/mainwindow.cpp @@ -583,7 +583,7 @@ void MainWindow::setupWindowStructure() prefsWidget->setFeatures(QDockWidget::DockWidgetClosable); settingsWidget = new SettingsWidget(server_osc_cues_port, i18n, piSettings, sonicPii18n, this); - connect(settingsWidget, SIGNAL(uiLanguageChanged(QString)), this, SLOT(changeUILanguage(QString))); + connect(settingsWidget, SIGNAL(restartApp()), this, SLOT(restartApp())); connect(settingsWidget, SIGNAL(volumeChanged(int)), this, SLOT(changeSystemPreAmp(int))); connect(settingsWidget, SIGNAL(mixerSettingsChanged()), this, SLOT(mixerSettingsChanged())); connect(settingsWidget, SIGNAL(midiSettingsChanged()), this, SLOT(toggleMidi())); diff --git a/app/gui/qt/widgets/settingswidget.cpp b/app/gui/qt/widgets/settingswidget.cpp index 5a6b55758c..ed9b06f794 100644 --- a/app/gui/qt/widgets/settingswidget.cpp +++ b/app/gui/qt/widgets/settingswidget.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include /** @@ -538,10 +539,40 @@ void SettingsWidget::toggleScope( QWidget* qw ) { emit scopeChanged(name); } +// TODO: Implement real-time language switching void SettingsWidget::updateUILanguage(int index) { - QString lang = available_languages[index]; - std::cout << "Changed language to " << lang.toUtf8().constData() << std::endl; - emit uiLanguageChanged(lang); + QString lang = available_languages[index]; + std::cout << "Changed language to " << lang.toUtf8().constData() << std::endl; + if (lang != piSettings->language) { + std::cout << "Current language: " << piSettings->language.toUtf8().constData() << std::endl; + std::cout << "New language selected: " << lang.toUtf8().constData() << std::endl; + QString old_lang = sonicPii18n->getNativeLanguageName(piSettings->language); + QString new_lang = sonicPii18n->getNativeLanguageName(lang); + + // Load new language + //QString language = sonicPii18n->determineUILanguage(lang); + //sonicPii18n->loadTranslations(language); + //QString title_new = tr("Updated the UI language from %s to %s").arg(); + + QMessageBox msgBox(this); + msgBox.setText(QString(tr("You've selected a new language: %1")).arg(new_lang)); + msgBox.setInformativeText(tr("Do you want to apply this language?\nApplying the new language will restart Sonic Pi.")); + QPushButton *restartButton = msgBox.addButton(tr("Apply and Restart"), QMessageBox::ActionRole); + QPushButton *dismissButton = msgBox.addButton(tr("Cancel"), QMessageBox::RejectRole); + msgBox.setDefaultButton(restartButton); + msgBox.setIcon(QMessageBox::Information); + msgBox.exec(); + + if (msgBox.clickedButton() == (QAbstractButton*)restartButton) { + piSettings->language = lang; + emit restartApp(); + //emit uiLanguageChanged(lang); + } else if (msgBox.clickedButton() == (QAbstractButton*)dismissButton) { + // Don't apply the new language settings + updateSelectedUILanguage(piSettings->language); + } + + } } void SettingsWidget::update_mixer_invert_stereo() { diff --git a/app/gui/qt/widgets/settingswidget.h b/app/gui/qt/widgets/settingswidget.h index 1a97148a01..2f67a518e3 100644 --- a/app/gui/qt/widgets/settingswidget.h +++ b/app/gui/qt/widgets/settingswidget.h @@ -72,7 +72,8 @@ private slots: void autoIndentOnRun(); signals: - void uiLanguageChanged(QString lang); + void restartApp(); + //void uiLanguageChanged(QString lang); // TODO: Implement real-time language switching void mixerSettingsChanged(); void oscSettingsChanged(); void midiSettingsChanged(); From 3c98ff1ba2cc2895bda9aa4e6c788a371c79c979 Mon Sep 17 00:00:00 2001 From: SunderB <20426079+SunderB@users.noreply.github.com> Date: Sun, 14 Feb 2021 14:06:20 +0000 Subject: [PATCH 06/19] Update .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index cf752e21d3..27ded1a9d1 100644 --- a/.gitignore +++ b/.gitignore @@ -62,7 +62,7 @@ app/gui/qt/moc_sonicpiudpserver.cpp app/gui/qt/qrc_SonicPi.cpp app/gui/qt/utils/ruby_help.h app/gui/qt/utils/lang_list.h -app/gui/qt/help +app/gui/qt/help/*.html app/gui/qt/help_files.qrc app/gui/qt/lang/*.qm app/gui/qt/qrc_help_files.cpp From 0422ed16f113fa4e17f2b4490155538df8271432 Mon Sep 17 00:00:00 2001 From: SunderB <20426079+SunderB@users.noreply.github.com> Date: Sun, 14 Feb 2021 15:09:52 +0000 Subject: [PATCH 07/19] GUI - Add function to test all found UI translations --- app/gui/qt/utils/sonic_pi_i18n.cpp | 59 +++++++++++++++++++++++------- app/gui/qt/utils/sonic_pi_i18n.h | 3 +- 2 files changed, 47 insertions(+), 15 deletions(-) diff --git a/app/gui/qt/utils/sonic_pi_i18n.cpp b/app/gui/qt/utils/sonic_pi_i18n.cpp index b942a3b72a..3c5ee7de30 100644 --- a/app/gui/qt/utils/sonic_pi_i18n.cpp +++ b/app/gui/qt/utils/sonic_pi_i18n.cpp @@ -13,10 +13,10 @@ SonicPii18n::SonicPii18n(QString rootpath) { this->root_path = rootpath; - this->available_languages = findAvailableLanguages(); + this->system_language_available = true; // Set to true unless we can't load the system language - // Set to true unless we can't load the system language - this->system_language_available = true; + this->available_languages = findAvailableLanguages(); + //checkAllTranslations(); // For testing and debugging purposes } SonicPii18n::~SonicPii18n() { @@ -65,22 +65,21 @@ QString SonicPii18n::determineUILanguage(QString lang_pref) { QStringList SonicPii18n::findAvailableLanguages() { QStringList languages; - QLocale locale; QString m_langPath = root_path + "/app/gui/qt/lang"; - //std::cout << m_langPath.toUtf8().constData() << "\n"; + //std::cout << m_langPath.toUtf8().constData() << std::endl; QDir dir(m_langPath); QStringList fileNames = dir.entryList(QStringList("sonic-pi_*.qm")); for (int i = 0; i < fileNames.size(); ++i) { - // get locale extracted by filename - QString locale; - locale = fileNames[i]; // "sonic-pi_pt_BR.qm" - locale.truncate(locale.lastIndexOf('.')); // "sonic-pi_pt_BR" - locale.remove(0, locale.lastIndexOf("sonic-pi_") + 9); // "pt_BR" - //locale.replace("_", "-"); // Replace underscores with dashes so it matches the language codes e.g: "pt-BR" - //std::cout << locale.toUtf8().constData() << '\n'; - languages << locale; + // extract the language from the filename + QString lang; + lang = fileNames[i]; // "sonic-pi_pt_BR.qm" + lang.truncate(lang.lastIndexOf('.')); // "sonic-pi_pt_BR" + lang.remove(0, lang.lastIndexOf("sonic-pi_") + 9); // "pt_BR" + //lang.replace("_", "-"); // Replace underscores with dashes so it matches the language codes e.g: "pt-BR" + //std::cout << lang.toUtf8().constData() << '\n'; + languages << lang; } // Add the source language languages << "en_GB"; @@ -101,7 +100,7 @@ bool SonicPii18n::loadTranslations(QString lang) { i18n = translator.load("sonic-pi_" + language, ":/lang/") || language == "en_GB" || language == "en" || language == "C"; if (!i18n) { - std::cout << language.toUtf8().constData() << ": Language translation not available" << std::endl; + std::cout << "Error: Failed to load language translation for " << language.toUtf8().constData() << std::endl; language = "en"; } app->installTranslator(&translator); @@ -142,3 +141,35 @@ QString SonicPii18n::getNativeLanguageName(QString lang) { } } } + +// For testing and debugging purposes +bool SonicPii18n::checkAllTranslations() { + QStringList failed_to_load = QStringList(); + bool all_succeeded = true; + + std::cout << "==========================================" << std::endl; + std::cout << "Testing all found language translations..." << std::endl; + for (int i = 0; i < this->available_languages.size(); ++i) { + QString lang = this->available_languages[i]; + bool success = loadTranslations(lang); + if (success) { + std::cout << lang.toUtf8().constData() << ": ✓" << std::endl; + } else { + std::cout << lang.toUtf8().constData() << ": ✗" << std::endl; + failed_to_load << lang; + all_succeeded = false; + } + } + std::cout << "Done" << std::endl; + + std::cout << "------------------------------------------" << std::endl; + if (failed_to_load.length() > 0) { + std::cout << "Some translations failed to load:" << std::endl; + std::cout << failed_to_load.join("\n").toUtf8().constData() << std::endl; + } else { + std::cout << "All found translations loaded successfully :)" << std::endl; + } + std::cout << "==========================================" << std::endl; + + return all_succeeded; +} diff --git a/app/gui/qt/utils/sonic_pi_i18n.h b/app/gui/qt/utils/sonic_pi_i18n.h index 4e03f94bdc..0509fb4a68 100644 --- a/app/gui/qt/utils/sonic_pi_i18n.h +++ b/app/gui/qt/utils/sonic_pi_i18n.h @@ -28,7 +28,7 @@ public slots: std::map getNativeLanguageNameList(); QString getNativeLanguageName(QString lang); bool loadTranslations(QString lang); - + bool system_language_available; private: @@ -39,5 +39,6 @@ public slots: static std::map native_language_names; QStringList findAvailableLanguages(); + bool checkAllTranslations(); }; #endif From 687316d7f3a3a624b6797e406c564fe7fda7966d Mon Sep 17 00:00:00 2001 From: SunderB <20426079+SunderB@users.noreply.github.com> Date: Sun, 14 Feb 2021 15:34:15 +0000 Subject: [PATCH 08/19] GUI - Update native language names --- app/server/ruby/bin/qt-doc.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/server/ruby/bin/qt-doc.rb b/app/server/ruby/bin/qt-doc.rb index 159c8864b1..0da7e8817e 100755 --- a/app/server/ruby/bin/qt-doc.rb +++ b/app/server/ruby/bin/qt-doc.rb @@ -34,7 +34,7 @@ @lang_names = Hash[ "bg" => "български", # Bulgarian "bn" => "বাংলা", # Bengali/Bangla - "bs" => "Bosanski/босански", # Bosnian + "bs" => "Bosanski", # Bosnian "ca" => "Català", # Catalan "ca@valencia" => "Valencià", # Valencian "cs" => "Čeština", # Czech @@ -79,10 +79,10 @@ "ug" => "ئۇيغۇر تىلى", # Uyghur "uk" => "Українська", # Ukranian "vi" => "Tiếng Việt", # Vietnamese - "zh" => "简体中文", # Mandarin Chinese (Simplified) - "zh_Hans" => "简体中文", # Mandarin Chinese (Simplified) - "zh_HK" => "廣東話", # Mandarin Chinese (Traditional, Hong Kong) - "zh_TW" => "臺灣華語" # Mandarin Chinese (Traditional, Taiwan) + "zh" => "简体中文", # Chinese (Simplified) + "zh_Hans" => "简体中文", # Chinese (Simplified) + "zh_HK" => "廣東話", # Chinese (Traditional, Hong Kong) + "zh_TW" => "臺灣華語" # Chinese (Traditional, Taiwan) ] FileUtils::rm_rf "#{qt_gui_path}/help/" From 1cbb2ef3490820ce659edf026a7cc4f51dc6933a Mon Sep 17 00:00:00 2001 From: SunderB <20426079+SunderB@users.noreply.github.com> Date: Mon, 15 Feb 2021 14:27:43 +0000 Subject: [PATCH 09/19] GUI - Apply the current theme's background colour to message boxes --- app/gui/qt/theme/app.qss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/gui/qt/theme/app.qss b/app/gui/qt/theme/app.qss index 9edc265cdc..aa70a384d6 100644 --- a/app/gui/qt/theme/app.qss +++ b/app/gui/qt/theme/app.qss @@ -535,3 +535,8 @@ QPushButton::pressed background-color: pressedButtonColor; color: pressedButtonTextColor; } + +QMessageBox +{ + background: windowColor; +} From 320d91f51365f8b008124effa7fe96a6d2342db2 Mon Sep 17 00:00:00 2001 From: SunderB <20426079+SunderB@users.noreply.github.com> Date: Mon, 15 Feb 2021 14:31:07 +0000 Subject: [PATCH 10/19] GUI - Tweak 'predefined language name not found' warning Co-authored-by: ethancrawford --- app/gui/qt/utils/sonic_pi_i18n.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/gui/qt/utils/sonic_pi_i18n.cpp b/app/gui/qt/utils/sonic_pi_i18n.cpp index 3c5ee7de30..bc79fa39df 100644 --- a/app/gui/qt/utils/sonic_pi_i18n.cpp +++ b/app/gui/qt/utils/sonic_pi_i18n.cpp @@ -129,7 +129,7 @@ QString SonicPii18n::getNativeLanguageName(QString lang) { // language found return native_language_names[lang]; } else { - std::cout << "Warning: Predefined language name not found'" << lang.toUtf8().constData() << "'" << std::endl; + std::cout << "Warning: Predefined language name not found: '" << lang.toUtf8().constData() << "'" << std::endl; // Try using QLocale to find the native language name QLocale locale(lang); QString name = locale.nativeLanguageName(); From f01c901264c7f4f6708601c4839d06e205b6174f Mon Sep 17 00:00:00 2001 From: SunderB <20426079+SunderB@users.noreply.github.com> Date: Mon, 15 Feb 2021 15:12:34 +0000 Subject: [PATCH 11/19] GUI - Output language loading logs to gui.log Load the language after program output has been redirected to gui.log --- app/gui/qt/mainwindow.cpp | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/app/gui/qt/mainwindow.cpp b/app/gui/qt/mainwindow.cpp index f1e21477f6..ff4524f103 100644 --- a/app/gui/qt/mainwindow.cpp +++ b/app/gui/qt/mainwindow.cpp @@ -161,11 +161,6 @@ MainWindow::MainWindow(QApplication& app, QSplashScreen* splash) initPaths(); bool startupOK = false; - this->sonicPii18n = new SonicPii18n(rootPath()); - std::cout << "Language setting: " << piSettings->language.toUtf8().constData() << std::endl; - this->ui_language = sonicPii18n->determineUILanguage(piSettings->language); - std::cout << "Using language: " << ui_language.toUtf8().constData() << std::endl; - this->i18n = sonicPii18n->loadTranslations(ui_language); #ifdef QT_OLD_API // Clear out old tasks from previous sessions if they still exist @@ -183,15 +178,6 @@ MainWindow::MainWindow(QApplication& app, QSplashScreen* splash) std::cout << "[GUI] - ===========================" << std::endl; std::cout << "[GUI] - " << std::endl; std::cout << "[GUI] - " << guiID.toStdString() << std::endl; - std::cout << "[GUI] - ui locale: " << ui_language.toUtf8().constData() << std::endl; - std::cout << "[GUI] - sys locale: " << QLocale::system().name().toStdString() << std::endl; - - - if(i18n) { - std::cout << "[GUI] - translations available " << std::endl; - } else { - std::cout << "[GUI] - translations unavailable (using EN)" << std::endl; - } // dynamically discover port numbers and then check them this will // show an error dialogue to the user and then kill the app if any of @@ -201,6 +187,19 @@ MainWindow::MainWindow(QApplication& app, QSplashScreen* splash) m_spAPI->Init(rootPath().toStdString()); #endif + this->sonicPii18n = new SonicPii18n(rootPath()); + std::cout << "[GUI] - Language setting: " << piSettings->language.toUtf8().constData() << std::endl; + std::cout << "[GUI] - System language: " << QLocale::system().name().toStdString() << std::endl; + this->ui_language = sonicPii18n->determineUILanguage(piSettings->language); + std::cout << "[GUI] - Using language: " << ui_language.toUtf8().constData() << std::endl; + this->i18n = sonicPii18n->loadTranslations(ui_language); + + if(i18n) { + std::cout << "[GUI] - translations available " << std::endl; + } else { + std::cout << "[GUI] - translations unavailable (using EN)" << std::endl; + } + std::cout << "[GUI] - hiding main window" << std::endl; hide(); From 30ceabf8ec9582a3e8d172916446811d146f250e Mon Sep 17 00:00:00 2001 From: SunderB <20426079+SunderB@users.noreply.github.com> Date: Mon, 15 Feb 2021 18:52:06 +0000 Subject: [PATCH 12/19] GUI - Show an error message when the selected language fails to load, and add a warning about stopping runs & recordings (due to restart) when changing language. --- app/gui/qt/mainwindow.cpp | 17 +++++++++++++++++ app/gui/qt/mainwindow.h | 1 + app/gui/qt/widgets/settingswidget.cpp | 2 +- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/app/gui/qt/mainwindow.cpp b/app/gui/qt/mainwindow.cpp index ff4524f103..a45f696459 100644 --- a/app/gui/qt/mainwindow.cpp +++ b/app/gui/qt/mainwindow.cpp @@ -301,6 +301,11 @@ MainWindow::MainWindow(QApplication& app, QSplashScreen* splash) toggleOSCServer(1); app.setActiveWindow(tabs->currentWidget()); + + if (!i18n) { + showLanguageLoadingError(); + } + showWelcomeScreen(); } @@ -1628,6 +1633,18 @@ void MainWindow::startupError(QString msg) #endif } +void MainWindow::showLanguageLoadingError() { + QMessageBox msgBox(this); + msgBox.setIcon(QMessageBox::Warning); + msgBox.setText(QString(tr("Failed to load translations for language: %1")).arg(sonicPii18n->getNativeLanguageName(this->ui_language))); + msgBox.setInformativeText(tr("Falling back to English. Sorry about this.") + "\n" + tr("Please consider reporting a bug at") + "\nhttp://github.com/sonic-pi-net/sonic-pi/issues"); + + QPushButton *okButton = msgBox.addButton(tr("OK"), QMessageBox::AcceptRole); + msgBox.setDefaultButton(okButton); + + msgBox.exec(); +} + void MainWindow::replaceBuffer(QString id, QString content, int line, int index, int first_line) { SonicPiScintilla* ws = filenameToWorkspace(id.toStdString()); diff --git a/app/gui/qt/mainwindow.h b/app/gui/qt/mainwindow.h index 31a35c10c5..68e21132ae 100644 --- a/app/gui/qt/mainwindow.h +++ b/app/gui/qt/mainwindow.h @@ -239,6 +239,7 @@ class MainWindow : public QMainWindow void splashClose(); void setMessageBoxStyle(); void startupError(QString msg); + void showLanguageLoadingError(); void tabNext(); void tabPrev(); void tabGoto(int index); diff --git a/app/gui/qt/widgets/settingswidget.cpp b/app/gui/qt/widgets/settingswidget.cpp index ed9b06f794..3f5d01d6cc 100644 --- a/app/gui/qt/widgets/settingswidget.cpp +++ b/app/gui/qt/widgets/settingswidget.cpp @@ -556,7 +556,7 @@ void SettingsWidget::updateUILanguage(int index) { QMessageBox msgBox(this); msgBox.setText(QString(tr("You've selected a new language: %1")).arg(new_lang)); - msgBox.setInformativeText(tr("Do you want to apply this language?\nApplying the new language will restart Sonic Pi.")); + msgBox.setInformativeText(tr("Do you want to apply this language?") + "\n" + tr("Applying the new language will stop any current runs & recordings, and restart Sonic Pi.")); QPushButton *restartButton = msgBox.addButton(tr("Apply and Restart"), QMessageBox::ActionRole); QPushButton *dismissButton = msgBox.addButton(tr("Cancel"), QMessageBox::RejectRole); msgBox.setDefaultButton(restartButton); From 1e287dd43ca66b4b2dc0b3f9d7c107ca3ac03baa Mon Sep 17 00:00:00 2001 From: SunderB <20426079+SunderB@users.noreply.github.com> Date: Mon, 15 Feb 2021 18:56:52 +0000 Subject: [PATCH 13/19] GUI - Update native language names --- app/server/ruby/bin/qt-doc.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/server/ruby/bin/qt-doc.rb b/app/server/ruby/bin/qt-doc.rb index 0da7e8817e..4ce9ede899 100755 --- a/app/server/ruby/bin/qt-doc.rb +++ b/app/server/ruby/bin/qt-doc.rb @@ -79,8 +79,8 @@ "ug" => "ئۇيغۇر تىلى", # Uyghur "uk" => "Українська", # Ukranian "vi" => "Tiếng Việt", # Vietnamese - "zh" => "简体中文", # Chinese (Simplified) - "zh_Hans" => "简体中文", # Chinese (Simplified) + "zh" => "中文", # Chinese + "zh-Hans" => "简体中文", # Chinese (Simplified) "zh_HK" => "廣東話", # Chinese (Traditional, Hong Kong) "zh_TW" => "臺灣華語" # Chinese (Traditional, Taiwan) ] From 74d19df9025b3646eb8515258044fef68c25d390 Mon Sep 17 00:00:00 2001 From: SunderB <20426079+SunderB@users.noreply.github.com> Date: Thu, 11 Mar 2021 20:48:55 +0000 Subject: [PATCH 14/19] GUI - rename sonic_pi_i18n.cpp to sonicpi_i18n.cpp --- app/gui/qt/CMakeLists.txt | 4 ++-- app/gui/qt/mainwindow.cpp | 2 +- app/gui/qt/utils/{sonic_pi_i18n.cpp => sonicpi_i18n.cpp} | 2 +- app/gui/qt/utils/{sonic_pi_i18n.h => sonicpi_i18n.h} | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) rename app/gui/qt/utils/{sonic_pi_i18n.cpp => sonicpi_i18n.cpp} (99%) rename app/gui/qt/utils/{sonic_pi_i18n.h => sonicpi_i18n.h} (95%) diff --git a/app/gui/qt/CMakeLists.txt b/app/gui/qt/CMakeLists.txt index 8e55ed65d4..8e6a78f310 100644 --- a/app/gui/qt/CMakeLists.txt +++ b/app/gui/qt/CMakeLists.txt @@ -101,13 +101,13 @@ set(QT_SOURCES set(SOURCES ${QTAPP_ROOT}/main.cpp ${QTAPP_ROOT}/utils/scintilla_api.cpp - ${QTAPP_ROOT}/utils/sonic_pi_i18n.cpp + ${QTAPP_ROOT}/utils/sonicpi_i18n.cpp ${QTAPP_ROOT}/widgets/sonicpilog.cpp ${QTAPP_ROOT}/widgets/sonicpilog.h ${QTAPP_ROOT}/widgets/sonicpicontext.cpp ${QTAPP_ROOT}/widgets/sonicpicontext.h ${QTAPP_ROOT}/utils/scintilla_api.h - ${QTAPP_ROOT}/utils/sonic_pi_i18n.h + ${QTAPP_ROOT}/utils/sonicpi_i18n.h ${QTAPP_ROOT}/utils/ruby_help.h ${QTAPP_ROOT}/utils/lang_list.h ${QTAPP_ROOT}/model/settings.h diff --git a/app/gui/qt/mainwindow.cpp b/app/gui/qt/mainwindow.cpp index a45f696459..62cadaa774 100644 --- a/app/gui/qt/mainwindow.cpp +++ b/app/gui/qt/mainwindow.cpp @@ -52,7 +52,7 @@ #include "widgets/sonicpilexer.h" #include "widgets/sonicpiscintilla.h" -#include "utils/sonic_pi_i18n.h" +#include "utils/sonicpi_i18n.h" #include "utils/borderlesslinksproxystyle.h" diff --git a/app/gui/qt/utils/sonic_pi_i18n.cpp b/app/gui/qt/utils/sonicpi_i18n.cpp similarity index 99% rename from app/gui/qt/utils/sonic_pi_i18n.cpp rename to app/gui/qt/utils/sonicpi_i18n.cpp index bc79fa39df..ac0447544c 100644 --- a/app/gui/qt/utils/sonic_pi_i18n.cpp +++ b/app/gui/qt/utils/sonicpi_i18n.cpp @@ -8,7 +8,7 @@ #include -#include "sonic_pi_i18n.h" +#include "sonicpi_i18n.h" #include "lang_list.h" SonicPii18n::SonicPii18n(QString rootpath) { diff --git a/app/gui/qt/utils/sonic_pi_i18n.h b/app/gui/qt/utils/sonicpi_i18n.h similarity index 95% rename from app/gui/qt/utils/sonic_pi_i18n.h rename to app/gui/qt/utils/sonicpi_i18n.h index 0509fb4a68..839df7ea66 100644 --- a/app/gui/qt/utils/sonic_pi_i18n.h +++ b/app/gui/qt/utils/sonicpi_i18n.h @@ -14,8 +14,8 @@ #include #include -#ifndef SONIC_PI_I18N_H -#define SONIC_PI_I18N_H +#ifndef SONICPI_I18N_H +#define SONICPI_I18N_H class SonicPii18n : public QObject { public: From fc7881ec158d7d54d6348f6b5be86b996d8b66eb Mon Sep 17 00:00:00 2001 From: SunderB <20426079+SunderB@users.noreply.github.com> Date: Sat, 20 Mar 2021 01:03:52 +0000 Subject: [PATCH 15/19] GUI - Add language section to toolbar; move the language setting to its own tab in the SettingsWidget; fix bugs from renaming source files Also, when the user selects 'System language', we now show them the list of detected system languages, and show them which one is in use, --- app/gui/qt/mainwindow.cpp | 66 ++++++++++----------- app/gui/qt/mainwindow.h | 8 ++- app/gui/qt/utils/sonicpi_i18n.cpp | 31 +++++++++- app/gui/qt/utils/sonicpi_i18n.h | 8 ++- app/gui/qt/widgets/settingswidget.cpp | 82 ++++++++++++++++++--------- app/gui/qt/widgets/settingswidget.h | 9 ++- 6 files changed, 134 insertions(+), 70 deletions(-) diff --git a/app/gui/qt/mainwindow.cpp b/app/gui/qt/mainwindow.cpp index 62cadaa774..ec4753e3ff 100644 --- a/app/gui/qt/mainwindow.cpp +++ b/app/gui/qt/mainwindow.cpp @@ -2199,39 +2199,6 @@ void MainWindow::changeSystemPreAmp(int val, int silent) statusBar()->showMessage(tr("Updating System Volume..."), 2000); } -// TODO: Implement real-time language switching -void MainWindow::changeUILanguage(QString lang) { - if (lang != piSettings->language) { - std::cout << "Current language: " << piSettings->language.toUtf8().constData() << std::endl; - std::cout << "New language selected: " << lang.toUtf8().constData() << std::endl; - QString old_lang = sonicPii18n->getNativeLanguageName(piSettings->language); - QString new_lang = sonicPii18n->getNativeLanguageName(lang); - - // Load new language - //QString language = sonicPii18n->determineUILanguage(lang); - //sonicPii18n->loadTranslations(language); - //QString title_new = tr("Updated the UI language from %s to %s").arg(); - - QMessageBox msgBox(this); - msgBox.setText(QString(tr("You've selected a new language: %1")).arg(new_lang)); - msgBox.setInformativeText(tr("Do you want to apply this language?\nApplying the new language will restart Sonic Pi.")); - QPushButton *restartButton = msgBox.addButton(tr("Apply and Restart"), QMessageBox::ActionRole); - QPushButton *dismissButton = msgBox.addButton(tr("Cancel"), QMessageBox::RejectRole); - msgBox.setDefaultButton(restartButton); - msgBox.setIcon(QMessageBox::Information); - msgBox.exec(); - - if (msgBox.clickedButton() == (QAbstractButton*)restartButton) { - piSettings->language = lang; - restartApp(); - } else if (msgBox.clickedButton() == (QAbstractButton*)dismissButton) { - // Don't apply the new language settings - settingsWidget->updateSelectedUILanguage(piSettings->language); - } - - } -} - void MainWindow::changeScopeKindVisibility(QString name) { foreach (QAction* action, scopeKindVisibilityMenu->actions()) @@ -3356,6 +3323,39 @@ void MainWindow::createToolBar() viewMenu->addAction(focusHelpListingAct); viewMenu->addAction(focusHelpDetailsAct); viewMenu->addAction(focusErrorsAct); + + languageMenu = menuBar()->addMenu(tr("Language")); + QStringList available_languages = sonicPii18n->getAvailableLanguages(); + + langActionGroup = new QActionGroup(this); + langActionGroup->setExclusionPolicy(QActionGroup::ExclusionPolicy::Exclusive); + + QSignalMapper *signalMapper = new QSignalMapper(this); + + for (size_t i = 0; i < available_languages.length(); i += 1) { + bool is_current_lang = (available_languages[i] == piSettings->language); + + QAction *langAct = new QAction(sonicPii18n->getNativeLanguageName(available_languages[i]), this); + langAct->setCheckable(true); + langAct->setChecked(is_current_lang); + + connect(langAct, SIGNAL(triggered()), signalMapper, SLOT(map())); + signalMapper->setMapping(langAct, i); + + langActionGroup->addAction(langAct); + languageMenu->addAction(langAct); + + if (i == 0) { // add separator after System language + languageMenu->addSeparator(); + } + } + + connect(signalMapper, SIGNAL(mappedInt(int)), settingsWidget, SLOT(updateUILanguage(int))); + connect(settingsWidget, SIGNAL(uiLanguageChanged(QString)), this, SLOT(updateSelectedUILanguageAction(QString))); +} + +void MainWindow::updateSelectedUILanguageAction(QString lang) { + langActionGroup->actions()[sonicPii18n->getAvailableLanguages().indexOf(lang)]->setChecked(true); } QString MainWindow::readFile(QString name) diff --git a/app/gui/qt/mainwindow.h b/app/gui/qt/mainwindow.h index 68e21132ae..a217d7badd 100644 --- a/app/gui/qt/mainwindow.h +++ b/app/gui/qt/mainwindow.h @@ -34,6 +34,7 @@ #include "config.h" class QAction; +class QActionGroup; class QMenu; class QToolBar; class QLineEdit; @@ -141,7 +142,7 @@ class MainWindow : public QMainWindow private slots: - void changeUILanguage(QString lang); + void updateSelectedUILanguageAction(QString lang); void updateContext(int line, int index); void updateContextWithCurrentWs(); void docLinkClicked(const QUrl &url); @@ -335,7 +336,7 @@ class MainWindow : public QMainWindow std::string number_name(int); std::string workspaceFilename(SonicPiScintilla* text); SonicPiScintilla* filenameToWorkspace(std::string filename); - + bool sendOSC(oscpkt::Message m); // void initPrefsWindow(); void initDocsWindow(); @@ -360,7 +361,7 @@ class MainWindow : public QMainWindow void addUniversalCopyShortcuts(QTextEdit *te); void updateTranslatedUIText(); - QMenu *liveMenu, *codeMenu, *audioMenu, *displayMenu, *viewMenu, *ioMenu, *ioMidiInMenu, *ioMidiOutMenu, *ioMidiOutChannelMenu, *localIpAddressesMenu, *themeMenu, *scopeKindVisibilityMenu; + QMenu *liveMenu, *codeMenu, *audioMenu, *displayMenu, *viewMenu, *ioMenu, *ioMidiInMenu, *ioMidiOutMenu, *ioMidiOutChannelMenu, *localIpAddressesMenu, *themeMenu, *scopeKindVisibilityMenu, *languageMenu; SonicPiSettings *piSettings; SonicPii18n *sonicPii18n; @@ -422,6 +423,7 @@ class MainWindow : public QMainWindow QAction *exitAct, *runAct, *stopAct, *saveAsAct, *loadFileAct, *recAct, *textAlignAct, *textIncAct, *textDecAct, *scopeAct, *infoAct, *helpAct, *prefsAct, *focusEditorAct, *focusLogsAct, *focusContextAct, *focusCuesAct, *focusPreferencesAct, *focusHelpListingAct, *focusHelpDetailsAct, *focusErrorsAct, *showLineNumbersAct, *showAutoCompletionAct, *showContextAct, *audioSafeAct, *audioTimingGuaranteesAct, *enableExternalSynthsAct, *mixerInvertStereoAct, *mixerForceMonoAct, *midiEnabledAct, *enableOSCServerAct, *allowRemoteOSCAct, *showLogAct, *showCuesAct, *logAutoScrollAct, *logCuesAct, *logSynthsAct, *clearOutputOnRunAct, *autoIndentOnRunAct, *showButtonsAct, *showTabsAct, *fullScreenAct, *lightThemeAct, *darkThemeAct, *proLightThemeAct, *proDarkThemeAct, *highContrastThemeAct, *showScopeLabelsAct; QShortcut *runSc, *stopSc, *saveAsSc, *loadFileSc, *recSc, *textAlignSc, *textIncSc, *textDecSc, *scopeSc, *infoSc, *helpSc, *prefsSc, *focusEditorSc, *focusLogsSc, *focusContextSc, *focusCuesSc, *focusPreferencesSc, *focusHelpListingSc, *focusHelpDetailsSc, *focusErrorsSc; + QActionGroup *langActionGroup; SettingsWidget *settingsWidget; diff --git a/app/gui/qt/utils/sonicpi_i18n.cpp b/app/gui/qt/utils/sonicpi_i18n.cpp index ac0447544c..b362a6da6d 100644 --- a/app/gui/qt/utils/sonicpi_i18n.cpp +++ b/app/gui/qt/utils/sonicpi_i18n.cpp @@ -13,15 +13,28 @@ SonicPii18n::SonicPii18n(QString rootpath) { this->root_path = rootpath; + this->system_languages = findSystemLanguages(); this->system_language_available = true; // Set to true unless we can't load the system language - this->available_languages = findAvailableLanguages(); + this->currently_loaded_language = "en"; + + //checkAllTranslations(); // For testing and debugging purposes } SonicPii18n::~SonicPii18n() { } +QStringList SonicPii18n::findSystemLanguages() { + QLocale locale; + QStringList preferred_languages = locale.uiLanguages(); + std::cout << "Looping through preferred ui languages" << std::endl; + for (int i = 0; i < preferred_languages.length(); i += 1) { + preferred_languages[i] = preferred_languages[i].replace("-", "_"); + } + return preferred_languages; +} + QString SonicPii18n::determineUILanguage(QString lang_pref) { QStringList available_languages = getAvailableLanguages(); //std::cout << available_languages.join("\n").toUtf8().constData() << std::endl; @@ -42,7 +55,7 @@ QString SonicPii18n::determineUILanguage(QString lang_pref) { } } else { QStringList preferred_languages = locale.uiLanguages(); - // If the specified language isn't available, or if the setting is set to system_language... + // If the setting is set to system_language... // ...run through the list of preferred languages std::cout << "Looping through preferred ui languages" << std::endl; @@ -108,11 +121,15 @@ bool SonicPii18n::loadTranslations(QString lang) { qtTranslator.load("qt_" + language, QLibraryInfo::location(QLibraryInfo::TranslationsPath)); app->installTranslator(&qtTranslator); + this->currently_loaded_language = language; + return i18n; } QStringList SonicPii18n::getAvailableLanguages() { - return this->available_languages; + QStringList list = this->available_languages; + list.prepend("system_language"); + return list; } std::map SonicPii18n::getNativeLanguageNameList() { @@ -142,6 +159,14 @@ QString SonicPii18n::getNativeLanguageName(QString lang) { } } +QStringList SonicPii18n::getNativeLanguageNames(QStringList languages) { + QStringList language_names; + for (size_t i = 0; i < languages.length(); i += 1) { + language_names << getNativeLanguageName(languages[i]); + } + return language_names; +} + // For testing and debugging purposes bool SonicPii18n::checkAllTranslations() { QStringList failed_to_load = QStringList(); diff --git a/app/gui/qt/utils/sonicpi_i18n.h b/app/gui/qt/utils/sonicpi_i18n.h index 839df7ea66..e30c956cb1 100644 --- a/app/gui/qt/utils/sonicpi_i18n.h +++ b/app/gui/qt/utils/sonicpi_i18n.h @@ -22,15 +22,18 @@ class SonicPii18n : public QObject { SonicPii18n(QString rootpath); ~SonicPii18n(); + QStringList system_languages; + bool system_language_available; + QString currently_loaded_language; + public slots: QString determineUILanguage(QString lang_pref); QStringList getAvailableLanguages(); std::map getNativeLanguageNameList(); QString getNativeLanguageName(QString lang); + QStringList getNativeLanguageNames(QStringList languages); bool loadTranslations(QString lang); - bool system_language_available; - private: QString root_path; QTranslator qtTranslator; @@ -39,6 +42,7 @@ public slots: static std::map native_language_names; QStringList findAvailableLanguages(); + QStringList findSystemLanguages(); bool checkAllTranslations(); }; #endif diff --git a/app/gui/qt/widgets/settingswidget.cpp b/app/gui/qt/widgets/settingswidget.cpp index 3f5d01d6cc..7765914a1c 100644 --- a/app/gui/qt/widgets/settingswidget.cpp +++ b/app/gui/qt/widgets/settingswidget.cpp @@ -1,5 +1,5 @@ #include "settingswidget.h" -#include "utils/sonic_pi_i18n.h" +#include "utils/sonicpi_i18n.h" #include #include @@ -28,7 +28,6 @@ SettingsWidget::SettingsWidget(int port, bool i18n, SonicPiSettings *piSettings, this->sonicPii18n = sonicPii18n; this->localeNames = sonicPii18n->getNativeLanguageNameList(); this->available_languages = sonicPii18n->getAvailableLanguages(); - available_languages.prepend("system_language"); server_osc_cues_port = port; prefTabs = new QTabWidget(); @@ -51,6 +50,9 @@ SettingsWidget::SettingsWidget(int port, bool i18n, SonicPiSettings *piSettings, QGroupBox *update_prefs_box = createUpdatePrefsTab(); prefTabs->addTab(update_prefs_box, tr("Updates")); + QGroupBox *language_prefs_box = createLanguagePrefsTab(); + prefTabs->addTab(language_prefs_box, tr("Language")); + if (!sonicPii18n->system_language_available) { QGroupBox *translation_box = new QGroupBox("Translation"); QVBoxLayout *translation_box_layout = new QVBoxLayout; @@ -374,34 +376,12 @@ QGroupBox* SettingsWidget::createEditorPrefsTab() { debug_box->setLayout(debug_box_layout); - QGroupBox *language_box = new QGroupBox(tr("Language")); - language_box->setToolTip(tr("Configure language settings")); - - language_combo = new QComboBox(); - add_language_combo_box_entries(language_combo); - language_combo->setToolTip(tr("Change the language of the UI & Tutorial (Requires a restart to take effect)")); - language_combo->setMinimumContentsLength(2); - language_combo->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength); - - language_option_label = new QLabel; - language_option_label->setText(tr("UI & Tutorial Language (Requires a restart to take effect)")); - language_option_label->setToolTip(tr("Change the language of the UI & Tutorial (Requires a restart to take effect)")); - - language_info_label = new QLabel; - language_info_label->setText(tr("Translations have been generously provided by volunteers \non https://hosted.weblate.org/projects/sonic-pi/. Thank you! :)")); - - QVBoxLayout *language_box_layout = new QVBoxLayout; - language_box_layout->addWidget(language_combo); - language_box_layout->addWidget(language_option_label); - language_box_layout->addWidget(language_info_label); - - language_box->setLayout(language_box_layout); gridEditorPrefs->addWidget(editor_display_box, 0, 0); gridEditorPrefs->addWidget(editor_look_feel_box, 0, 1); gridEditorPrefs->addWidget(automation_box, 1, 1); gridEditorPrefs->addWidget(debug_box, 1, 0); - gridEditorPrefs->addWidget(language_box, 2, 0, 1, 2); + editor_box->setLayout(gridEditorPrefs); return editor_box; @@ -496,6 +476,42 @@ QGroupBox* SettingsWidget::createUpdatePrefsTab() { return update_prefs_box; } +/** + * create Language Preferences Tab of Settings Widget + */ +QGroupBox* SettingsWidget::createLanguagePrefsTab() { + QGroupBox *language_box = new QGroupBox(tr("Language")); + language_box->setToolTip(tr("Configure language settings")); + QSizePolicy languagePrefSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + language_box->setSizePolicy(languagePrefSizePolicy); + + language_option_label = new QLabel; + language_option_label->setText(tr("UI & Tutorial Language (Requires a restart to take effect)")); + language_option_label->setToolTip(tr("Change the language of the UI & Tutorial (Requires a restart to take effect)")); + + language_combo = new QComboBox(); + add_language_combo_box_entries(language_combo); + language_combo->setToolTip(tr("Change the language of the UI & Tutorial (Requires a restart to take effect)")); + language_combo->setMinimumContentsLength(2); + language_combo->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength); + + language_info_label = new QLabel; + language_info_label->setText(tr("Translations have been generously provided by volunteers \non https://hosted.weblate.org/projects/sonic-pi/. Thank you! :)")); + + QVBoxLayout *language_box_layout = new QVBoxLayout; + + language_box_layout->addWidget(language_option_label); + language_box_layout->addWidget(language_combo); + language_box_layout->addWidget(language_info_label); + + language_box->setLayout(language_box_layout); + + QGroupBox *language_prefs_box = new QGroupBox(); + QGridLayout *language_prefs_box_layout = new QGridLayout; + language_prefs_box_layout->addWidget(language_box, 0, 0, 0, 0); + language_prefs_box->setLayout(language_prefs_box_layout); + return language_prefs_box; +} // TODO utils? QString SettingsWidget::tooltipStrShiftMeta(char key, QString str) { @@ -556,7 +572,13 @@ void SettingsWidget::updateUILanguage(int index) { QMessageBox msgBox(this); msgBox.setText(QString(tr("You've selected a new language: %1")).arg(new_lang)); - msgBox.setInformativeText(tr("Do you want to apply this language?") + "\n" + tr("Applying the new language will stop any current runs & recordings, and restart Sonic Pi.")); + QString info_text = tr("Do you want to apply this language?") + "\n" + tr("Applying the new language will stop any current runs & recordings, and restart Sonic Pi."); + + if (lang == "system_language") { + info_text = tr("System languages found %1").arg(sonicPii18n->getNativeLanguageNames(sonicPii18n->system_languages).join(", ")) + "\n" + info_text; + } + + msgBox.setInformativeText(info_text); QPushButton *restartButton = msgBox.addButton(tr("Apply and Restart"), QMessageBox::ActionRole); QPushButton *dismissButton = msgBox.addButton(tr("Cancel"), QMessageBox::RejectRole); msgBox.setDefaultButton(restartButton); @@ -570,6 +592,7 @@ void SettingsWidget::updateUILanguage(int index) { } else if (msgBox.clickedButton() == (QAbstractButton*)dismissButton) { // Don't apply the new language settings updateSelectedUILanguage(piSettings->language); + emit uiLanguageChanged(piSettings->language); } } @@ -755,6 +778,13 @@ void SettingsWidget::updateSettings() { void SettingsWidget::settingsChanged() { language_combo->setCurrentIndex(available_languages.indexOf(piSettings->language)); + if (piSettings->language == "system_language") { + language_info_label->setText( + tr("System languages: %1").arg(sonicPii18n->getNativeLanguageNames(sonicPii18n->system_languages).join(", ")) + "\n" + tr("Current UI language: %1").arg(sonicPii18n->getNativeLanguageName(sonicPii18n->currently_loaded_language)) + + "\n\n" + tr("Translations have been generously provided by volunteers \non https://hosted.weblate.org/projects/sonic-pi/. Thank you! :)") + ); + } + mixer_invert_stereo->setChecked(piSettings->mixer_invert_stereo); mixer_force_mono->setChecked(piSettings->mixer_force_mono); check_args->setChecked(piSettings->check_args); diff --git a/app/gui/qt/widgets/settingswidget.h b/app/gui/qt/widgets/settingswidget.h index 2f67a518e3..148f8242b0 100644 --- a/app/gui/qt/widgets/settingswidget.h +++ b/app/gui/qt/widgets/settingswidget.h @@ -2,7 +2,7 @@ #define SETTINGSWIDGET_H #include "model/settings.h" -#include "utils/sonic_pi_i18n.h" +#include "utils/sonicpi_i18n.h" #include @@ -35,8 +35,10 @@ class SettingsWidget : public QWidget QSize sizeHint() const; -private slots: +public slots: void updateUILanguage(int index); + +private slots: void update_mixer_invert_stereo(); void update_mixer_force_mono(); void toggleOscServer(); @@ -73,7 +75,7 @@ private slots: signals: void restartApp(); - //void uiLanguageChanged(QString lang); // TODO: Implement real-time language switching + void uiLanguageChanged(QString lang); // TODO: Implement real-time language switching void mixerSettingsChanged(); void oscSettingsChanged(); void midiSettingsChanged(); @@ -174,6 +176,7 @@ private slots: QGroupBox* createEditorPrefsTab(); QGroupBox* createVisualizationPrefsTab(); QGroupBox* createUpdatePrefsTab(); + QGroupBox* createLanguagePrefsTab(); void add_language_combo_box_entries(QComboBox* combo); From cee7fc062767e043f6113e1572bc778e4a11a54a Mon Sep 17 00:00:00 2001 From: SunderB <20426079+SunderB@users.noreply.github.com> Date: Sun, 21 Mar 2021 22:28:11 +0000 Subject: [PATCH 16/19] GUI - Give the user the option whether or not to restart Sonic Pi when changing the language Also clean up/refactor SonicPii18n class --- app/gui/qt/utils/sonicpi_i18n.cpp | 37 ++++++----- app/gui/qt/utils/sonicpi_i18n.h | 18 ++++-- app/gui/qt/widgets/settingswidget.cpp | 88 ++++++++++++++++++--------- app/gui/qt/widgets/settingswidget.h | 1 + 4 files changed, 94 insertions(+), 50 deletions(-) diff --git a/app/gui/qt/utils/sonicpi_i18n.cpp b/app/gui/qt/utils/sonicpi_i18n.cpp index b362a6da6d..ea75607d12 100644 --- a/app/gui/qt/utils/sonicpi_i18n.cpp +++ b/app/gui/qt/utils/sonicpi_i18n.cpp @@ -18,23 +18,12 @@ SonicPii18n::SonicPii18n(QString rootpath) { this->available_languages = findAvailableLanguages(); this->currently_loaded_language = "en"; - //checkAllTranslations(); // For testing and debugging purposes } SonicPii18n::~SonicPii18n() { } -QStringList SonicPii18n::findSystemLanguages() { - QLocale locale; - QStringList preferred_languages = locale.uiLanguages(); - std::cout << "Looping through preferred ui languages" << std::endl; - for (int i = 0; i < preferred_languages.length(); i += 1) { - preferred_languages[i] = preferred_languages[i].replace("-", "_"); - } - return preferred_languages; -} - QString SonicPii18n::determineUILanguage(QString lang_pref) { QStringList available_languages = getAvailableLanguages(); //std::cout << available_languages.join("\n").toUtf8().constData() << std::endl; @@ -76,6 +65,16 @@ QString SonicPii18n::determineUILanguage(QString lang_pref) { return "en"; } +QStringList SonicPii18n::findSystemLanguages() { + QLocale locale; + QStringList preferred_languages = locale.uiLanguages(); + std::cout << "Looping through preferred ui languages" << std::endl; + for (int i = 0; i < preferred_languages.length(); i += 1) { + preferred_languages[i] = preferred_languages[i].replace("-", "_"); + } + return preferred_languages; +} + QStringList SonicPii18n::findAvailableLanguages() { QStringList languages; @@ -132,9 +131,17 @@ QStringList SonicPii18n::getAvailableLanguages() { return list; } -std::map SonicPii18n::getNativeLanguageNameList() { - return native_language_names; -} +QStringList SonicPii18n::getSystemLanguages() { + return system_languages; +}; + +bool SonicPii18n::isSystemLanguageAvailable() { + return system_language_available; +}; + +QString SonicPii18n::currentlyLoadedLanguage() { + return currently_loaded_language; +}; QString SonicPii18n::getNativeLanguageName(QString lang) { if (lang == "system_language") { @@ -153,7 +160,7 @@ QString SonicPii18n::getNativeLanguageName(QString lang) { if (name != "C" && name != "") { return locale.nativeLanguageName(); } else { - std::cout << "Warning: Invalid language code '" << lang.toUtf8().constData() << "'" << std::endl; + std::cout << "Warning: Invalid language code: '" << lang.toUtf8().constData() << "'" << std::endl; return lang; } } diff --git a/app/gui/qt/utils/sonicpi_i18n.h b/app/gui/qt/utils/sonicpi_i18n.h index e30c956cb1..a12949a998 100644 --- a/app/gui/qt/utils/sonicpi_i18n.h +++ b/app/gui/qt/utils/sonicpi_i18n.h @@ -22,23 +22,31 @@ class SonicPii18n : public QObject { SonicPii18n(QString rootpath); ~SonicPii18n(); - QStringList system_languages; - bool system_language_available; - QString currently_loaded_language; public slots: QString determineUILanguage(QString lang_pref); + bool loadTranslations(QString lang); + QStringList getAvailableLanguages(); - std::map getNativeLanguageNameList(); + QStringList getSystemLanguages(); + bool isSystemLanguageAvailable(); + QString currentlyLoadedLanguage(); + QString getNativeLanguageName(QString lang); QStringList getNativeLanguageNames(QStringList languages); - bool loadTranslations(QString lang); + private: QString root_path; + QTranslator qtTranslator; QTranslator translator; + QStringList available_languages; + QStringList system_languages; + bool system_language_available; + QString currently_loaded_language; + static std::map native_language_names; QStringList findAvailableLanguages(); diff --git a/app/gui/qt/widgets/settingswidget.cpp b/app/gui/qt/widgets/settingswidget.cpp index 7765914a1c..7844a1cda2 100644 --- a/app/gui/qt/widgets/settingswidget.cpp +++ b/app/gui/qt/widgets/settingswidget.cpp @@ -26,7 +26,6 @@ SettingsWidget::SettingsWidget(int port, bool i18n, SonicPiSettings *piSettings, this->piSettings = piSettings; this->i18n = i18n; this->sonicPii18n = sonicPii18n; - this->localeNames = sonicPii18n->getNativeLanguageNameList(); this->available_languages = sonicPii18n->getAvailableLanguages(); server_osc_cues_port = port; @@ -53,25 +52,28 @@ SettingsWidget::SettingsWidget(int port, bool i18n, SonicPiSettings *piSettings, QGroupBox *language_prefs_box = createLanguagePrefsTab(); prefTabs->addTab(language_prefs_box, tr("Language")); - if (!sonicPii18n->system_language_available) { - QGroupBox *translation_box = new QGroupBox("Translation"); - QVBoxLayout *translation_box_layout = new QVBoxLayout; - QLabel *go_translate = new QLabel; - go_translate->setOpenExternalLinks(true); - go_translate->setText( - "Sonic Pi hasn't been translated to " + - QLocale::languageToString(QLocale::system().language()) + - " yet.
" + - "We rely on crowdsourcing to help create and maintain translations.
" + - "" + - "Please consider helping to translate Sonic Pi to your language. " - ); - go_translate->setTextFormat(Qt::RichText); - translation_box_layout->addWidget(go_translate); - translation_box->setLayout(translation_box_layout); - - grid->addWidget(translation_box, 3, 0, 1, 2); + if (piSettings->language == "system_language") { + if (!sonicPii18n->isSystemLanguageAvailable()) { + QGroupBox *translation_box = new QGroupBox("Translation"); + QVBoxLayout *translation_box_layout = new QVBoxLayout; + QLabel *go_translate = new QLabel; + go_translate->setOpenExternalLinks(true); + go_translate->setText( + "Sonic Pi hasn't been translated to " + + QLocale::languageToString(QLocale::system().language()) + + " yet.
" + + "We rely on crowdsourcing to help create and maintain translations.
" + + "" + + "Please consider helping to translate Sonic Pi to your language. " + ); + go_translate->setTextFormat(Qt::RichText); + translation_box_layout->addWidget(go_translate); + translation_box->setLayout(translation_box_layout); + + grid->addWidget(translation_box, 3, 0, 1, 2); + } } + settingsChanged(); connectAll(); setLayout(grid); @@ -491,10 +493,12 @@ QGroupBox* SettingsWidget::createLanguagePrefsTab() { language_combo = new QComboBox(); add_language_combo_box_entries(language_combo); - language_combo->setToolTip(tr("Change the language of the UI & Tutorial (Requires a restart to take effect)")); + language_combo->setToolTip(tr("Change the language of the UI & Tutorial")); language_combo->setMinimumContentsLength(2); language_combo->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength); + language_details_label = new QLabel; + language_info_label = new QLabel; language_info_label->setText(tr("Translations have been generously provided by volunteers \non https://hosted.weblate.org/projects/sonic-pi/. Thank you! :)")); @@ -502,6 +506,7 @@ QGroupBox* SettingsWidget::createLanguagePrefsTab() { language_box_layout->addWidget(language_option_label); language_box_layout->addWidget(language_combo); + language_box_layout->addWidget(language_details_label); language_box_layout->addWidget(language_info_label); language_box->setLayout(language_box_layout); @@ -572,22 +577,46 @@ void SettingsWidget::updateUILanguage(int index) { QMessageBox msgBox(this); msgBox.setText(QString(tr("You've selected a new language: %1")).arg(new_lang)); - QString info_text = tr("Do you want to apply this language?") + "\n" + tr("Applying the new language will stop any current runs & recordings, and restart Sonic Pi."); + QString info_text = ( + tr("Do you want to apply this language?") + + "\n" + + tr("The new language will be applied when you next start Sonic Pi.") + ); if (lang == "system_language") { - info_text = tr("System languages found %1").arg(sonicPii18n->getNativeLanguageNames(sonicPii18n->system_languages).join(", ")) + "\n" + info_text; + info_text = tr("System languages found: %1").arg(sonicPii18n->getNativeLanguageNames(sonicPii18n->getSystemLanguages()).join(", ")) + "\n" + info_text; } msgBox.setInformativeText(info_text); - QPushButton *restartButton = msgBox.addButton(tr("Apply and Restart"), QMessageBox::ActionRole); + QPushButton *applyButton = msgBox.addButton(tr("Apply"), QMessageBox::ActionRole); QPushButton *dismissButton = msgBox.addButton(tr("Cancel"), QMessageBox::RejectRole); - msgBox.setDefaultButton(restartButton); - msgBox.setIcon(QMessageBox::Information); + msgBox.setDefaultButton(applyButton); + msgBox.setIcon(QMessageBox::Question); msgBox.exec(); - if (msgBox.clickedButton() == (QAbstractButton*)restartButton) { - piSettings->language = lang; + if (msgBox.clickedButton() == (QAbstractButton*)applyButton) { + piSettings->language = lang; + updateSelectedUILanguage(piSettings->language); + emit uiLanguageChanged(piSettings->language); + + language_details_label->setText( + tr("The new language will be applied when you next start Sonic Pi.") + + " " + + tr("Current UI language: %1").arg(sonicPii18n->getNativeLanguageName(sonicPii18n->currentlyLoadedLanguage())) + ); + + QMessageBox restartMsgBox(this); + restartMsgBox.setText(QString(tr("Restart Sonic Pi?"))); + QString info_text = (tr("Do you want to restart Sonic Pi now? This will stop any current runs & recordings.")); + QPushButton *restartButton = restartMsgBox.addButton(tr("Restart"), QMessageBox::ActionRole); + QPushButton *dismissButton = restartMsgBox.addButton(tr("Dismiss"), QMessageBox::RejectRole); + restartMsgBox.setInformativeText(info_text); + restartMsgBox.setDefaultButton(dismissButton); + restartMsgBox.setIcon(QMessageBox::Question); + restartMsgBox.exec(); + if (restartMsgBox.clickedButton() == (QAbstractButton*)restartButton) { emit restartApp(); + } //emit uiLanguageChanged(lang); } else if (msgBox.clickedButton() == (QAbstractButton*)dismissButton) { // Don't apply the new language settings @@ -779,9 +808,8 @@ void SettingsWidget::updateSettings() { void SettingsWidget::settingsChanged() { language_combo->setCurrentIndex(available_languages.indexOf(piSettings->language)); if (piSettings->language == "system_language") { - language_info_label->setText( - tr("System languages: %1").arg(sonicPii18n->getNativeLanguageNames(sonicPii18n->system_languages).join(", ")) + "\n" + tr("Current UI language: %1").arg(sonicPii18n->getNativeLanguageName(sonicPii18n->currently_loaded_language)) - + "\n\n" + tr("Translations have been generously provided by volunteers \non https://hosted.weblate.org/projects/sonic-pi/. Thank you! :)") + language_details_label->setText( + tr("System languages: %1").arg(sonicPii18n->getNativeLanguageNames(sonicPii18n->getSystemLanguages()).join(", ")) + "\n" + tr("Current UI language: %1").arg(sonicPii18n->getNativeLanguageName(sonicPii18n->currentlyLoadedLanguage())) ); } diff --git a/app/gui/qt/widgets/settingswidget.h b/app/gui/qt/widgets/settingswidget.h index 148f8242b0..384f686b49 100644 --- a/app/gui/qt/widgets/settingswidget.h +++ b/app/gui/qt/widgets/settingswidget.h @@ -168,6 +168,7 @@ private slots: QComboBox *language_combo; QLabel *language_option_label; + QLabel *language_details_label; QLabel *language_info_label; // TODO From a2b1b17760df87dd8e5489aeae7dc3a55da89692 Mon Sep 17 00:00:00 2001 From: SunderB <20426079+SunderB@users.noreply.github.com> Date: Tue, 27 Apr 2021 21:04:16 +0100 Subject: [PATCH 17/19] GUI: Fix some compilation errors from previous commit --- app/gui/qt/mainwindow.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/gui/qt/mainwindow.cpp b/app/gui/qt/mainwindow.cpp index 1f81876734..2f0ba855ac 100644 --- a/app/gui/qt/mainwindow.cpp +++ b/app/gui/qt/mainwindow.cpp @@ -3140,7 +3140,7 @@ void MainWindow::readSettings() std::cout << "[GUI] - reading settings" << std::endl; // Read in preferences from previous session - piSettings->language = gui_settings.value("prefs/language", "system_language").toString(); + piSettings->language = gui_settings->value("prefs/language", "system_language").toString(); piSettings->show_buttons = gui_settings->value("prefs/show-buttons", true).toBool(); piSettings->show_tabs = gui_settings->value("prefs/show-tabs", true).toBool(); piSettings->show_log = gui_settings->value("prefs/show-log", true).toBool(); @@ -3167,7 +3167,7 @@ void MainWindow::readSettings() piSettings->show_scope_labels = gui_settings->value("prefs/scope/show-labels", false).toBool(); piSettings->show_cues = gui_settings->value("prefs/show_cues", true).toBool(); QString styleName = gui_settings->value("prefs/theme", "").toString(); - + piSettings->themeStyle = theme->themeNameToStyle(styleName); piSettings->show_autocompletion = gui_settings->value("prefs/show-autocompletion", true).toBool(); piSettings->show_context = gui_settings->value("prefs/show-context", true).toBool(); @@ -3189,11 +3189,11 @@ void MainWindow::restoreScopeState(std::vector names) void MainWindow::writeSettings() { std::cout << "[GUI] - writing settings" << std::endl; - + gui_settings->setValue("pos", pos()); gui_settings->setValue("size", size()); gui_settings->setValue("first_time", 0); - + gui_settings->setValue("prefs/language", piSettings->language); gui_settings->setValue("prefs/midi-default-channel", piSettings->midi_default_channel); @@ -3330,6 +3330,7 @@ void MainWindow::onExitCleanup() // Shuts down the client/server connection m_spAPI->Shutdown(); } +} void MainWindow::restartApp() { QApplication* app = dynamic_cast(parent()); From 9372da9c463facfa4e6dc31f5666fd52f47e5e3b Mon Sep 17 00:00:00 2001 From: SunderB <20426079+SunderB@users.noreply.github.com> Date: Tue, 27 Apr 2021 21:48:34 +0100 Subject: [PATCH 18/19] GUI: Ensure that the UI settings are saved before restarting Sonic Pi * Fixes an issue where the settings wouldn't be saved when restarting after selecting a new language. * Also, remove the unnecessary delay in restartApp(). Cleanup should be complete when onExitCleanup() returns. --- app/gui/qt/mainwindow.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/gui/qt/mainwindow.cpp b/app/gui/qt/mainwindow.cpp index 2f0ba855ac..bd7ac6c468 100644 --- a/app/gui/qt/mainwindow.cpp +++ b/app/gui/qt/mainwindow.cpp @@ -3243,6 +3243,9 @@ void MainWindow::writeSettings() gui_settings->setValue("docsplitState", docsplit->saveState()); gui_settings->setValue("windowState", saveState()); gui_settings->setValue("windowGeom", saveGeometry()); + + // Force Qt to write the settings to the ini file + gui_settings->sync(); } void MainWindow::loadFile(const QString& fileName, SonicPiScintilla*& text) @@ -3339,9 +3342,7 @@ void MainWindow::restartApp() { // Save settings and perform some cleanup writeSettings(); onExitCleanup(); - std::cout << "Performing application restart... please wait..." << std::endl; - // Allow cleanup to complete - std::this_thread::sleep_for(2s); + std::cout << "Performing application restart..." << std::endl; // Create new process QStringList args = qApp->arguments(); From 2f61cba58bfb5566fe3965dd7f4ee6f322d17825 Mon Sep 17 00:00:00 2001 From: SunderB <20426079+SunderB@users.noreply.github.com> Date: Wed, 28 Apr 2021 10:54:51 +0100 Subject: [PATCH 19/19] GUI - Minor tweaks relating to the UI language option * Add Basque and generic English to the list of language names * Add a message in the language options panel when the translations fail to load * Set fallback language code to en_GB instead of en (so it's clear which English translation is being used) * Tweak some log messages from SonicPii18n --- app/gui/qt/utils/sonicpi_i18n.cpp | 14 +++++++------- app/gui/qt/widgets/settingswidget.cpp | 15 ++++++++++----- app/server/ruby/bin/qt-doc.rb | 2 ++ 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/app/gui/qt/utils/sonicpi_i18n.cpp b/app/gui/qt/utils/sonicpi_i18n.cpp index ea75607d12..8f10fcb321 100644 --- a/app/gui/qt/utils/sonicpi_i18n.cpp +++ b/app/gui/qt/utils/sonicpi_i18n.cpp @@ -46,7 +46,7 @@ QString SonicPii18n::determineUILanguage(QString lang_pref) { QStringList preferred_languages = locale.uiLanguages(); // If the setting is set to system_language... // ...run through the list of preferred languages - std::cout << "Looping through preferred ui languages" << std::endl; + std::cout << "[GUI] [i18n] - Looping through preferred ui languages" << std::endl; QString l; for (int i = 0; i < preferred_languages.length(); i += 1) { @@ -68,7 +68,7 @@ QString SonicPii18n::determineUILanguage(QString lang_pref) { QStringList SonicPii18n::findSystemLanguages() { QLocale locale; QStringList preferred_languages = locale.uiLanguages(); - std::cout << "Looping through preferred ui languages" << std::endl; + std::cout << "[GUI] [i18n] - Looping through preferred ui languages" << std::endl; for (int i = 0; i < preferred_languages.length(); i += 1) { preferred_languages[i] = preferred_languages[i].replace("-", "_"); } @@ -108,12 +108,12 @@ bool SonicPii18n::loadTranslations(QString lang) { app->removeTranslator(&translator); app->removeTranslator(&qtTranslator); - std::cout << "Loading translations for " << language.toUtf8().constData() << std::endl; + std::cout << "[GUI] [i18n] - Loading translations for " << language.toUtf8().constData() << std::endl; i18n = translator.load("sonic-pi_" + language, ":/lang/") || language == "en_GB" || language == "en" || language == "C"; if (!i18n) { - std::cout << "Error: Failed to load language translation for " << language.toUtf8().constData() << std::endl; - language = "en"; + std::cout << "[GUI] [i18n] - Error: Failed to load language translation for " << language.toUtf8().constData() << std::endl; + language = "en_GB"; } app->installTranslator(&translator); @@ -153,14 +153,14 @@ QString SonicPii18n::getNativeLanguageName(QString lang) { // language found return native_language_names[lang]; } else { - std::cout << "Warning: Predefined language name not found: '" << lang.toUtf8().constData() << "'" << std::endl; + std::cout << "[GUI] [i18n] - Warning: Predefined language name not found: '" << lang.toUtf8().constData() << "'" << std::endl; // Try using QLocale to find the native language name QLocale locale(lang); QString name = locale.nativeLanguageName(); if (name != "C" && name != "") { return locale.nativeLanguageName(); } else { - std::cout << "Warning: Invalid language code: '" << lang.toUtf8().constData() << "'" << std::endl; + std::cout << "[GUI] [i18n] - Warning: Invalid language code: '" << lang.toUtf8().constData() << "'" << std::endl; return lang; } } diff --git a/app/gui/qt/widgets/settingswidget.cpp b/app/gui/qt/widgets/settingswidget.cpp index 7844a1cda2..66449cd059 100644 --- a/app/gui/qt/widgets/settingswidget.cpp +++ b/app/gui/qt/widgets/settingswidget.cpp @@ -600,9 +600,8 @@ void SettingsWidget::updateUILanguage(int index) { emit uiLanguageChanged(piSettings->language); language_details_label->setText( - tr("The new language will be applied when you next start Sonic Pi.") - + " " - + tr("Current UI language: %1").arg(sonicPii18n->getNativeLanguageName(sonicPii18n->currentlyLoadedLanguage())) + tr("The new language will be applied when you next start Sonic Pi.
") + + tr("Current UI language: %1\n").arg(sonicPii18n->getNativeLanguageName(sonicPii18n->currentlyLoadedLanguage())) ); QMessageBox restartMsgBox(this); @@ -807,11 +806,17 @@ void SettingsWidget::updateSettings() { void SettingsWidget::settingsChanged() { language_combo->setCurrentIndex(available_languages.indexOf(piSettings->language)); + QString language_detail_text = ""; + if (!i18n) { + language_detail_text += "Failed to load language translation. Using English (UK)."; + } if (piSettings->language == "system_language") { - language_details_label->setText( - tr("System languages: %1").arg(sonicPii18n->getNativeLanguageNames(sonicPii18n->getSystemLanguages()).join(", ")) + "\n" + tr("Current UI language: %1").arg(sonicPii18n->getNativeLanguageName(sonicPii18n->currentlyLoadedLanguage())) + language_detail_text += ( + tr("System languages: %1\n").arg(sonicPii18n->getNativeLanguageNames(sonicPii18n->getSystemLanguages()).join(", ")) + + tr("Current UI language: %1\n").arg(sonicPii18n->getNativeLanguageName(sonicPii18n->currentlyLoadedLanguage())) ); } + language_details_label->setText(language_detail_text); mixer_invert_stereo->setChecked(piSettings->mixer_invert_stereo); mixer_force_mono->setChecked(piSettings->mixer_force_mono); diff --git a/app/server/ruby/bin/qt-doc.rb b/app/server/ruby/bin/qt-doc.rb index 4ce9ede899..f63b67bec8 100755 --- a/app/server/ruby/bin/qt-doc.rb +++ b/app/server/ruby/bin/qt-doc.rb @@ -41,12 +41,14 @@ "da" => "Dansk", # Danish "de" => "Deutsch", # German "el" => "ελληνικά", # Greek + "en" => "English", # English "en_AU" => "English (Australian)", # English (Australian) "en_GB" => "English (UK)", # English (UK) - default language "en_US" => "English (US)", # English (US) "eo" => "Esperanto", # Esperanto "es" => "Español", # Spanish "et" => "Eesti keel", # Estonian + "eu" => "Euskara", # Basque "fa" => "فارسی", # Persian "fi" => "Suomi", # Finnish "fr" => "Français", # French