From 1e613814ef706456b23b7a2cea4056a19aa77777 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Wed, 26 Jan 2022 19:00:01 +0100 Subject: [PATCH] Init --- README.md | 44 +- ant/build.xml | 579 +++++++++++ data/config/BundleManager.xml | 38 + data/config/BundleManager_en_US.properties | 44 + data/config/BundleManager_fr_FR.properties | 44 + data/config/Chat.xml | 60 ++ data/config/Chat_en_AU.properties | 1 + data/config/Chat_en_CA.properties | 1 + data/config/Chat_en_GB.properties | 1 + data/config/Chat_en_IE.properties | 1 + data/config/Chat_en_IN.properties | 1 + data/config/Chat_en_MT.properties | 1 + data/config/Chat_en_NZ.properties | 1 + data/config/Chat_en_PH.properties | 1 + data/config/Chat_en_SG.properties | 1 + data/config/Chat_en_US.properties | 12 + data/config/Chat_en_ZA.properties | 1 + data/config/Chat_fr_BE.properties | 1 + data/config/Chat_fr_CA.properties | 1 + data/config/Chat_fr_CH.properties | 1 + data/config/Chat_fr_FR.properties | 12 + data/config/Chat_fr_LU.properties | 1 + .../config/Controller_br_FR_breton.properties | 10 + data/config/Controller_br_FR_gallo.properties | 10 + data/config/Controller_en_US.properties | 10 + data/config/Controller_es_ES.properties | 10 + data/config/Controller_fr_FR.properties | 10 + data/config/Help_br_FR_breton.properties | 18 + data/config/Help_br_FR_gallo.properties | 18 + data/config/Help_en_AU.properties | 1 + data/config/Help_en_BG.properties | 1 + data/config/Help_en_CA.properties | 1 + data/config/Help_en_GB.properties | 1 + data/config/Help_en_IE.properties | 1 + data/config/Help_en_IN.properties | 1 + data/config/Help_en_MT.properties | 1 + data/config/Help_en_NZ.properties | 1 + data/config/Help_en_PH.properties | 1 + data/config/Help_en_SG.properties | 1 + data/config/Help_en_US.properties | 19 + data/config/Help_en_ZA.properties | 1 + data/config/Help_es_ES.properties | 18 + data/config/Help_fr_BE.properties | 1 + data/config/Help_fr_CA.properties | 1 + data/config/Help_fr_CH.properties | 1 + data/config/Help_fr_FR.properties | 19 + data/config/Help_fr_LU.properties | 1 + data/config/Login.xml | 69 ++ data/config/Login_en_US.properties | 38 + data/config/Login_fr_FR.properties | 38 + data/config/Misc.xml | 17 + data/config/Protocol_en_US.properties | 11 + data/config/Protocol_fr_FR.properties | 11 + data/config/Proxy_br_FR_breton.properties | 7 + data/config/Proxy_br_FR_gallo.properties | 7 + data/config/Proxy_en_AU.properties | 1 + data/config/Proxy_en_BG.properties | 1 + data/config/Proxy_en_CA.properties | 1 + data/config/Proxy_en_GB.properties | 1 + data/config/Proxy_en_IE.properties | 1 + data/config/Proxy_en_IN.properties | 1 + data/config/Proxy_en_MT.properties | 1 + data/config/Proxy_en_NZ.properties | 1 + data/config/Proxy_en_PH.properties | 1 + data/config/Proxy_en_SG.properties | 1 + data/config/Proxy_en_US.properties | 7 + data/config/Proxy_en_ZA.properties | 1 + data/config/Proxy_es_ES.properties | 9 + data/config/Proxy_fr_BE.properties | 1 + data/config/Proxy_fr_CA.properties | 1 + data/config/Proxy_fr_CH.properties | 1 + data/config/Proxy_fr_FR.properties | 7 + data/config/Proxy_fr_LU.properties | 1 + .../RemoteUpdate_br_FR_breton.properties | 36 + .../RemoteUpdate_br_FR_gallo.properties | 35 + data/config/RemoteUpdate_en_AU.properties | 1 + data/config/RemoteUpdate_en_BG.properties | 1 + data/config/RemoteUpdate_en_CA.properties | 1 + data/config/RemoteUpdate_en_GB.properties | 1 + data/config/RemoteUpdate_en_IE.properties | 1 + data/config/RemoteUpdate_en_IN.properties | 1 + data/config/RemoteUpdate_en_MT.properties | 1 + data/config/RemoteUpdate_en_NZ.properties | 1 + data/config/RemoteUpdate_en_PH.properties | 1 + data/config/RemoteUpdate_en_SG.properties | 1 + data/config/RemoteUpdate_en_US.properties | 35 + data/config/RemoteUpdate_en_ZA.properties | 1 + data/config/RemoteUpdate_es_ES.properties | 36 + data/config/RemoteUpdate_fr_BE.properties | 1 + data/config/RemoteUpdate_fr_CA.properties | 1 + data/config/RemoteUpdate_fr_CH.properties | 1 + data/config/RemoteUpdate_fr_FR.properties | 36 + data/config/RemoteUpdate_fr_LU.properties | 1 + data/config/Story_br_FR_breton.properties | 4 + data/config/Story_br_FR_gallo.properties | 4 + data/config/Story_en_AU.properties | 1 + data/config/Story_en_BG.properties | 1 + data/config/Story_en_CA.properties | 1 + data/config/Story_en_GB.properties | 1 + data/config/Story_en_IE.properties | 1 + data/config/Story_en_IN.properties | 1 + data/config/Story_en_MT.properties | 1 + data/config/Story_en_NZ.properties | 1 + data/config/Story_en_PH.properties | 1 + data/config/Story_en_SG.properties | 1 + data/config/Story_en_US.properties | 2 + data/config/Story_en_ZA.properties | 1 + data/config/Story_es_ES.properties | 2 + data/config/Story_fr_BE.properties | 1 + data/config/Story_fr_CA.properties | 1 + data/config/Story_fr_CH.properties | 1 + data/config/Story_fr_FR.properties | 2 + data/config/Story_fr_LU.properties | 1 + data/config/ToolBar_br_FR_breton.properties | 9 + data/config/ToolBar_br_FR_gallo.properties | 9 + data/config/ToolBar_en_AU.properties | 1 + data/config/ToolBar_en_BG.properties | 1 + data/config/ToolBar_en_CA.properties | 1 + data/config/ToolBar_en_GB.properties | 1 + data/config/ToolBar_en_IE.properties | 1 + data/config/ToolBar_en_IN.properties | 1 + data/config/ToolBar_en_MT.properties | 1 + data/config/ToolBar_en_NZ.properties | 1 + data/config/ToolBar_en_PH.properties | 1 + data/config/ToolBar_en_SG.properties | 1 + data/config/ToolBar_en_US.properties | 9 + data/config/ToolBar_en_ZA.properties | 1 + data/config/ToolBar_es_ES.properties | 9 + data/config/ToolBar_fr_BE.properties | 1 + data/config/ToolBar_fr_CA.properties | 1 + data/config/ToolBar_fr_CH.properties | 1 + data/config/ToolBar_fr_FR.properties | 9 + data/config/ToolBar_fr_LU.properties | 1 + data/images/bundle/bundle.png | Bin 0 -> 912 bytes data/images/button/Abort.png | Bin 0 -> 618 bytes data/images/button/About.png | Bin 0 -> 698 bytes data/images/button/AddKey.png | Bin 0 -> 502 bytes data/images/button/AddLocale.png | Bin 0 -> 605 bytes data/images/button/Bad.png | Bin 0 -> 714 bytes data/images/button/Begin.png | Bin 0 -> 584 bytes data/images/button/Black.png | Bin 0 -> 437 bytes data/images/button/BugReport.png | Bin 0 -> 733 bytes data/images/button/Clear.png | Bin 0 -> 802 bytes data/images/button/Clock.png | Bin 0 -> 776 bytes data/images/button/Computer.png | Bin 0 -> 740 bytes data/images/button/ComputerOff.png | Bin 0 -> 721 bytes data/images/button/ComputerOn.png | Bin 0 -> 817 bytes data/images/button/Connect.png | Bin 0 -> 418 bytes data/images/button/Cut.png | Bin 0 -> 891 bytes data/images/button/Details.png | Bin 0 -> 698 bytes data/images/button/Disconnect.png | Bin 0 -> 424 bytes data/images/button/Down.png | Bin 0 -> 414 bytes data/images/button/DownloadOff.png | Bin 0 -> 815 bytes data/images/button/DownloadOn.png | Bin 0 -> 871 bytes data/images/button/DrawAgreement.png | Bin 0 -> 891 bytes data/images/button/DrawReject.png | Bin 0 -> 910 bytes data/images/button/EditGroup.png | Bin 0 -> 882 bytes data/images/button/EditLogin.png | Bin 0 -> 914 bytes data/images/button/Empty.png | Bin 0 -> 774 bytes data/images/button/End.png | Bin 0 -> 564 bytes data/images/button/Find.png | Bin 0 -> 653 bytes data/images/button/ForcePack.png | Bin 0 -> 509 bytes data/images/button/Good.png | Bin 0 -> 535 bytes data/images/button/Help.png | Bin 0 -> 916 bytes data/images/button/Human.png | Bin 0 -> 636 bytes data/images/button/HumanOff.png | Bin 0 -> 635 bytes data/images/button/HumanOn.png | Bin 0 -> 764 bytes data/images/button/Info.png | Bin 0 -> 556 bytes data/images/button/JConsole.png | Bin 0 -> 564 bytes data/images/button/JConsoleOff.png | Bin 0 -> 564 bytes data/images/button/JConsoleOn.png | Bin 0 -> 564 bytes data/images/button/JoinGroup.png | Bin 0 -> 819 bytes data/images/button/LeftGroup.png | Bin 0 -> 846 bytes data/images/button/Licence.png | Bin 0 -> 385 bytes data/images/button/LinkType.png | Bin 0 -> 875 bytes data/images/button/LocalRemoveOff.png | Bin 0 -> 777 bytes data/images/button/LocalRemoveOn.png | Bin 0 -> 802 bytes data/images/button/Locale.png | Bin 0 -> 816 bytes data/images/button/Log.png | Bin 0 -> 533 bytes data/images/button/Manual.png | Bin 0 -> 665 bytes data/images/button/Merge.png | Bin 0 -> 485 bytes data/images/button/Network.png | Bin 0 -> 867 bytes data/images/button/NetworkOff.png | Bin 0 -> 862 bytes data/images/button/NetworkOn.png | Bin 0 -> 907 bytes data/images/button/Next.png | Bin 0 -> 561 bytes data/images/button/Online.png | Bin 0 -> 908 bytes data/images/button/Open.png | Bin 0 -> 588 bytes data/images/button/PackBug.png | Bin 0 -> 1103 bytes data/images/button/PackBugOff.png | Bin 0 -> 1116 bytes data/images/button/PackBugOn.png | Bin 0 -> 1103 bytes data/images/button/Players.png | Bin 0 -> 741 bytes data/images/button/Previous.png | Bin 0 -> 578 bytes data/images/button/Pseudo.png | Bin 0 -> 533 bytes data/images/button/Quit.png | Bin 0 -> 543 bytes data/images/button/Redo.png | Bin 0 -> 753 bytes data/images/button/RemoteRemoveOff.png | Bin 0 -> 777 bytes data/images/button/RemoteRemoveOn.png | Bin 0 -> 802 bytes data/images/button/RemoveKey.png | Bin 0 -> 522 bytes data/images/button/RemoveLocale.png | Bin 0 -> 615 bytes data/images/button/RenameLocale.png | Bin 0 -> 657 bytes data/images/button/Resign.png | Bin 0 -> 906 bytes data/images/button/Save.png | Bin 0 -> 836 bytes data/images/button/SaveAs.png | Bin 0 -> 810 bytes data/images/button/SetProxy.png | Bin 0 -> 905 bytes data/images/button/Stop.png | Bin 0 -> 533 bytes data/images/button/ToolBarProfil.png | Bin 0 -> 294 bytes data/images/button/Undo.png | Bin 0 -> 810 bytes data/images/button/Unlog.png | Bin 0 -> 611 bytes data/images/button/Up.png | Bin 0 -> 415 bytes data/images/button/Update.png | Bin 0 -> 1057 bytes data/images/button/UploadOff.png | Bin 0 -> 874 bytes data/images/button/UploadOn.png | Bin 0 -> 908 bytes data/images/button/Users.png | Bin 0 -> 682 bytes data/images/button/Warning.png | Bin 0 -> 514 bytes data/images/button/White.png | Bin 0 -> 574 bytes data/images/button/develop.png | Bin 0 -> 194 bytes data/images/chat/chat.png | Bin 0 -> 764 bytes data/images/flags/ad.png | Bin 0 -> 941 bytes data/images/flags/ae.png | Bin 0 -> 599 bytes data/images/flags/af.png | Bin 0 -> 748 bytes data/images/flags/ag.png | Bin 0 -> 1039 bytes data/images/flags/ai.png | Bin 0 -> 1035 bytes data/images/flags/al.png | Bin 0 -> 681 bytes data/images/flags/am.png | Bin 0 -> 531 bytes data/images/flags/an.png | Bin 0 -> 906 bytes data/images/flags/ao.png | Bin 0 -> 814 bytes data/images/flags/aq.png | Bin 0 -> 902 bytes data/images/flags/ar.png | Bin 0 -> 621 bytes data/images/flags/as.png | Bin 0 -> 1136 bytes data/images/flags/at.png | Bin 0 -> 461 bytes data/images/flags/au.png | Bin 0 -> 1211 bytes data/images/flags/aw.png | Bin 0 -> 646 bytes data/images/flags/ax.png | Bin 0 -> 794 bytes data/images/flags/az.png | Bin 0 -> 684 bytes data/images/flags/ba.png | Bin 0 -> 881 bytes data/images/flags/bb.png | Bin 0 -> 898 bytes data/images/flags/bd.png | Bin 0 -> 761 bytes data/images/flags/be.png | Bin 0 -> 501 bytes data/images/flags/bf.png | Bin 0 -> 669 bytes data/images/flags/bg.png | Bin 0 -> 466 bytes data/images/flags/bh.png | Bin 0 -> 551 bytes data/images/flags/bi.png | Bin 0 -> 1292 bytes data/images/flags/bj.png | Bin 0 -> 530 bytes data/images/flags/bl.png | Bin 0 -> 220 bytes data/images/flags/bm.png | Bin 0 -> 986 bytes data/images/flags/bn.png | Bin 0 -> 1218 bytes data/images/flags/bo.png | Bin 0 -> 498 bytes data/images/flags/br.png | Bin 0 -> 777 bytes data/images/flags/br2.png | Bin 0 -> 1093 bytes data/images/flags/bs.png | Bin 0 -> 681 bytes data/images/flags/bt.png | Bin 0 -> 1156 bytes data/images/flags/bv.png | Bin 0 -> 884 bytes data/images/flags/bw.png | Bin 0 -> 525 bytes data/images/flags/by.png | Bin 0 -> 652 bytes data/images/flags/bz.png | Bin 0 -> 1164 bytes data/images/flags/ca.png | Bin 0 -> 821 bytes data/images/flags/cc.png | Bin 0 -> 699 bytes data/images/flags/cd.png | Bin 0 -> 886 bytes data/images/flags/cf.png | Bin 0 -> 925 bytes data/images/flags/cg.png | Bin 0 -> 809 bytes data/images/flags/ch.png | Bin 0 -> 708 bytes data/images/flags/ci.png | Bin 0 -> 522 bytes data/images/flags/ck.png | Bin 0 -> 1095 bytes data/images/flags/cl.png | Bin 0 -> 615 bytes data/images/flags/cm.png | Bin 0 -> 699 bytes data/images/flags/cn.png | Bin 0 -> 634 bytes data/images/flags/co.png | Bin 0 -> 478 bytes data/images/flags/cr.png | Bin 0 -> 783 bytes data/images/flags/cs.png | Bin 0 -> 474 bytes data/images/flags/cu.png | Bin 0 -> 788 bytes data/images/flags/cv.png | Bin 0 -> 751 bytes data/images/flags/cx.png | Bin 0 -> 1383 bytes data/images/flags/cy.png | Bin 0 -> 562 bytes data/images/flags/cz.png | Bin 0 -> 562 bytes data/images/flags/de.png | Bin 0 -> 482 bytes data/images/flags/dj.png | Bin 0 -> 725 bytes data/images/flags/dk.png | Bin 0 -> 780 bytes data/images/flags/dm.png | Bin 0 -> 1338 bytes data/images/flags/do.png | Bin 0 -> 744 bytes data/images/flags/dz.png | Bin 0 -> 853 bytes data/images/flags/ec.png | Bin 0 -> 489 bytes data/images/flags/ee.png | Bin 0 -> 456 bytes data/images/flags/eg.png | Bin 0 -> 623 bytes data/images/flags/eh.png | Bin 0 -> 644 bytes data/images/flags/er.png | Bin 0 -> 1150 bytes data/images/flags/es.png | Bin 0 -> 479 bytes data/images/flags/et.png | Bin 0 -> 900 bytes data/images/flags/fi.png | Bin 0 -> 650 bytes data/images/flags/fj.png | Bin 0 -> 1255 bytes data/images/flags/fk.png | Bin 0 -> 1171 bytes data/images/flags/fm.png | Bin 0 -> 700 bytes data/images/flags/fo.png | Bin 0 -> 806 bytes data/images/flags/fr.png | Bin 0 -> 276 bytes data/images/flags/fx.png | Bin 0 -> 508 bytes data/images/flags/ga.png | Bin 0 -> 510 bytes data/images/flags/gb.png | Bin 0 -> 1495 bytes data/images/flags/gd.png | Bin 0 -> 1176 bytes data/images/flags/ge.png | Bin 0 -> 644 bytes data/images/flags/gf.png | Bin 0 -> 276 bytes data/images/flags/gg.png | Bin 0 -> 495 bytes data/images/flags/gh.png | Bin 0 -> 647 bytes data/images/flags/gi.png | Bin 0 -> 753 bytes data/images/flags/gl.png | Bin 0 -> 632 bytes data/images/flags/gm.png | Bin 0 -> 532 bytes data/images/flags/gn.png | Bin 0 -> 537 bytes data/images/flags/gp.png | Bin 0 -> 276 bytes data/images/flags/gq.png | Bin 0 -> 812 bytes data/images/flags/gr.png | Bin 0 -> 735 bytes data/images/flags/gs.png | Bin 0 -> 1187 bytes data/images/flags/gt.png | Bin 0 -> 507 bytes data/images/flags/gu.png | Bin 0 -> 674 bytes data/images/flags/gw.png | Bin 0 -> 638 bytes data/images/flags/gy.png | Bin 0 -> 1194 bytes data/images/flags/hk.png | Bin 0 -> 674 bytes data/images/flags/hm.png | Bin 0 -> 1211 bytes data/images/flags/hn.png | Bin 0 -> 696 bytes data/images/flags/hr.png | Bin 0 -> 872 bytes data/images/flags/ht.png | Bin 0 -> 1166 bytes data/images/flags/hu.png | Bin 0 -> 461 bytes data/images/flags/id.png | Bin 0 -> 400 bytes data/images/flags/ie.png | Bin 0 -> 510 bytes data/images/flags/il.png | Bin 0 -> 618 bytes data/images/flags/in.png | Bin 0 -> 648 bytes data/images/flags/io.png | Bin 0 -> 1329 bytes data/images/flags/iq.png | Bin 0 -> 450 bytes data/images/flags/ir.png | Bin 0 -> 621 bytes data/images/flags/is.png | Bin 0 -> 893 bytes data/images/flags/it.png | Bin 0 -> 504 bytes data/images/flags/je.png | Bin 0 -> 759 bytes data/images/flags/jm.png | Bin 0 -> 1185 bytes data/images/flags/jo.png | Bin 0 -> 905 bytes data/images/flags/jp.png | Bin 0 -> 572 bytes data/images/flags/ke.png | Bin 0 -> 912 bytes data/images/flags/kg.png | Bin 0 -> 714 bytes data/images/flags/kh.png | Bin 0 -> 836 bytes data/images/flags/ki.png | Bin 0 -> 1223 bytes data/images/flags/km.png | Bin 0 -> 780 bytes data/images/flags/kn.png | Bin 0 -> 1021 bytes data/images/flags/kp.png | Bin 0 -> 667 bytes data/images/flags/kr.png | Bin 0 -> 1142 bytes data/images/flags/kw.png | Bin 0 -> 606 bytes data/images/flags/ky.png | Bin 0 -> 1163 bytes data/images/flags/kz.png | Bin 0 -> 1056 bytes data/images/flags/la.png | Bin 0 -> 764 bytes data/images/flags/lb.png | Bin 0 -> 717 bytes data/images/flags/lc.png | Bin 0 -> 769 bytes data/images/flags/li.png | Bin 0 -> 601 bytes data/images/flags/lk.png | Bin 0 -> 1111 bytes data/images/flags/lr.png | Bin 0 -> 689 bytes data/images/flags/ls.png | Bin 0 -> 462 bytes data/images/flags/lt.png | Bin 0 -> 508 bytes data/images/flags/lu.png | Bin 0 -> 280 bytes data/images/flags/lv.png | Bin 0 -> 489 bytes data/images/flags/ly.png | Bin 0 -> 354 bytes data/images/flags/ma.png | Bin 0 -> 720 bytes data/images/flags/mc.png | Bin 0 -> 421 bytes data/images/flags/md.png | Bin 0 -> 850 bytes data/images/flags/me.png | Bin 0 -> 669 bytes data/images/flags/mf.png | Bin 0 -> 276 bytes data/images/flags/mg.png | Bin 0 -> 483 bytes data/images/flags/mh.png | Bin 0 -> 830 bytes data/images/flags/mk.png | Bin 0 -> 875 bytes data/images/flags/ml.png | Bin 0 -> 541 bytes data/images/flags/mm.png | Bin 0 -> 672 bytes data/images/flags/mn.png | Bin 0 -> 752 bytes data/images/flags/mo.png | Bin 0 -> 805 bytes data/images/flags/mp.png | Bin 0 -> 1036 bytes data/images/flags/mq.png | Bin 0 -> 276 bytes data/images/flags/mr.png | Bin 0 -> 769 bytes data/images/flags/ms.png | Bin 0 -> 1062 bytes data/images/flags/mt.png | Bin 0 -> 571 bytes data/images/flags/mu.png | Bin 0 -> 549 bytes data/images/flags/mv.png | Bin 0 -> 703 bytes data/images/flags/mw.png | Bin 0 -> 792 bytes data/images/flags/mx.png | Bin 0 -> 831 bytes data/images/flags/my.png | Bin 0 -> 950 bytes data/images/flags/mz.png | Bin 0 -> 750 bytes data/images/flags/na.png | Bin 0 -> 1235 bytes data/images/flags/nc.png | Bin 0 -> 508 bytes data/images/flags/ne.png | Bin 0 -> 638 bytes data/images/flags/nf.png | Bin 0 -> 848 bytes data/images/flags/ng.png | Bin 0 -> 519 bytes data/images/flags/ni.png | Bin 0 -> 1175 bytes data/images/flags/nl.png | Bin 0 -> 506 bytes data/images/flags/no.png | Bin 0 -> 884 bytes data/images/flags/np.png | Bin 0 -> 799 bytes data/images/flags/nr.png | Bin 0 -> 635 bytes data/images/flags/nu.png | Bin 0 -> 871 bytes data/images/flags/nz.png | Bin 0 -> 1372 bytes data/images/flags/om.png | Bin 0 -> 637 bytes data/images/flags/pa.png | Bin 0 -> 860 bytes data/images/flags/pe.png | Bin 0 -> 977 bytes data/images/flags/pf.png | Bin 0 -> 697 bytes data/images/flags/pg.png | Bin 0 -> 847 bytes data/images/flags/ph.png | Bin 0 -> 789 bytes data/images/flags/pk.png | Bin 0 -> 895 bytes data/images/flags/pl.png | Bin 0 -> 364 bytes data/images/flags/pm.png | Bin 0 -> 276 bytes data/images/flags/pn.png | Bin 0 -> 1335 bytes data/images/flags/pr.png | Bin 0 -> 825 bytes data/images/flags/ps.png | Bin 0 -> 569 bytes data/images/flags/pt.png | Bin 0 -> 1224 bytes data/images/flags/pw.png | Bin 0 -> 631 bytes data/images/flags/py.png | Bin 0 -> 675 bytes data/images/flags/qa.png | Bin 0 -> 509 bytes data/images/flags/re.png | Bin 0 -> 508 bytes data/images/flags/ro.png | Bin 0 -> 553 bytes data/images/flags/rs.png | Bin 0 -> 765 bytes data/images/flags/ru.png | Bin 0 -> 462 bytes data/images/flags/rw.png | Bin 0 -> 613 bytes data/images/flags/sa.png | Bin 0 -> 1007 bytes data/images/flags/sb.png | Bin 0 -> 1173 bytes data/images/flags/sc.png | Bin 0 -> 1120 bytes data/images/flags/sd.png | Bin 0 -> 641 bytes data/images/flags/se.png | Bin 0 -> 848 bytes data/images/flags/sg.png | Bin 0 -> 573 bytes data/images/flags/sh.png | Bin 0 -> 1046 bytes data/images/flags/si.png | Bin 0 -> 704 bytes data/images/flags/sj.png | Bin 0 -> 884 bytes data/images/flags/sk.png | Bin 0 -> 929 bytes data/images/flags/sl.png | Bin 0 -> 512 bytes data/images/flags/sm.png | Bin 0 -> 389 bytes data/images/flags/sn.png | Bin 0 -> 670 bytes data/images/flags/so.png | Bin 0 -> 637 bytes data/images/flags/sr.png | Bin 0 -> 600 bytes data/images/flags/st.png | Bin 0 -> 910 bytes data/images/flags/sv.png | Bin 0 -> 621 bytes data/images/flags/sy.png | Bin 0 -> 701 bytes data/images/flags/sz.png | Bin 0 -> 941 bytes data/images/flags/tc.png | Bin 0 -> 1188 bytes data/images/flags/td.png | Bin 0 -> 539 bytes data/images/flags/tf.png | Bin 0 -> 508 bytes data/images/flags/tg.png | Bin 0 -> 749 bytes data/images/flags/th.png | Bin 0 -> 492 bytes data/images/flags/tj.png | Bin 0 -> 591 bytes data/images/flags/tk.png | Bin 0 -> 786 bytes data/images/flags/tl.png | Bin 0 -> 756 bytes data/images/flags/tm.png | Bin 0 -> 1130 bytes data/images/flags/tn.png | Bin 0 -> 698 bytes data/images/flags/to.png | Bin 0 -> 563 bytes data/images/flags/tp.png | Bin 0 -> 756 bytes data/images/flags/tr.png | Bin 0 -> 849 bytes data/images/flags/tt.png | Bin 0 -> 975 bytes data/images/flags/tv.png | Bin 0 -> 1263 bytes data/images/flags/tw.png | Bin 0 -> 708 bytes data/images/flags/tz.png | Bin 0 -> 701 bytes data/images/flags/ua.png | Bin 0 -> 437 bytes data/images/flags/ug.png | Bin 0 -> 812 bytes data/images/flags/um.png | Bin 0 -> 1031 bytes data/images/flags/us.png | Bin 0 -> 1031 bytes data/images/flags/uy.png | Bin 0 -> 1239 bytes data/images/flags/uz.png | Bin 0 -> 724 bytes data/images/flags/va.png | Bin 0 -> 788 bytes data/images/flags/vc.png | Bin 0 -> 903 bytes data/images/flags/ve.png | Bin 0 -> 651 bytes data/images/flags/vg.png | Bin 0 -> 1290 bytes data/images/flags/vi.png | Bin 0 -> 1197 bytes data/images/flags/vn.png | Bin 0 -> 618 bytes data/images/flags/vu.png | Bin 0 -> 895 bytes data/images/flags/wf.png | Bin 0 -> 541 bytes data/images/flags/ws.png | Bin 0 -> 639 bytes data/images/flags/xt.png | Bin 0 -> 1252 bytes data/images/flags/ye.png | Bin 0 -> 431 bytes data/images/flags/yt.png | Bin 0 -> 508 bytes data/images/flags/yu.png | Bin 0 -> 474 bytes data/images/flags/za.png | Bin 0 -> 1118 bytes data/images/flags/zm.png | Bin 0 -> 676 bytes data/images/flags/zw.png | Bin 0 -> 766 bytes data/images/login/login.png | Bin 0 -> 636 bytes data/images/misc.png | Bin 0 -> 1439 bytes data/texts/AboutBundleManager.html | 36 + data/texts/AboutChat.html | 36 + data/texts/AboutLogin.html | 36 + data/texts/AboutMisc.html | 39 + data/texts/BundleManagerLicence.html | 653 ++++++++++++ data/texts/ChatLicence.html | 653 ++++++++++++ data/texts/LoginLicence.html | 653 ++++++++++++ data/texts/MiscLicence.html | 653 ++++++++++++ src/java/misc/ApplicationManager.java | 12 + src/java/misc/Bundle.java | 532 ++++++++++ src/java/misc/ColorCheckedLine.java | 24 + src/java/misc/CommandLineServer.java | 154 +++ src/java/misc/CommandLineWaiter.java | 165 +++ src/java/misc/Config.java | 523 ++++++++++ src/java/misc/Controller.java | 216 ++++ src/java/misc/DatePanel.java | 242 +++++ src/java/misc/DimensionDouble.java | 62 ++ src/java/misc/EasterEgg.java | 53 + src/java/misc/Guide.java | 208 ++++ src/java/misc/HelpManager.java | 197 ++++ src/java/misc/HourPanel.java | 253 +++++ src/java/misc/HtmlDialog.java | 85 ++ src/java/misc/ImagePreview.java | 89 ++ src/java/misc/JConsole.java | 77 ++ src/java/misc/JRemoteUpdate.java | 237 +++++ src/java/misc/LocalizedUserLabel.java | 79 ++ src/java/misc/Log.java | 102 ++ src/java/misc/MultiToolBarBorderLayout.java | 318 ++++++ src/java/misc/OwnFrame.java | 11 + src/java/misc/Plugins.java | 165 +++ src/java/misc/ProgressState.java | 67 ++ src/java/misc/ProgressStatePanel.java | 78 ++ src/java/misc/ProxyManager.java | 110 ++ src/java/misc/RealTime.java | 73 ++ src/java/misc/RemoteUpdate.java | 748 ++++++++++++++ src/java/misc/RemoteUpdateManager.java | 74 ++ src/java/misc/ScaledImage.java | 238 +++++ src/java/misc/SpinnerSlider.java | 125 +++ src/java/misc/StateNotifier.java | 109 ++ src/java/misc/Story.java | 156 +++ src/java/misc/StoryManager.java | 142 +++ src/java/misc/Timer.java | 58 ++ src/java/misc/Timestamp.java | 34 + src/java/misc/TitledDialog.java | 57 ++ src/java/misc/ToolBarManager.java | 462 +++++++++ src/java/misc/Util.java | 964 ++++++++++++++++++ src/java/misc/XML.java | 114 +++ src/java/misc/package-info.java | 34 + src/java/network/Client.java | 147 +++ src/java/network/HTTPInputStream.java | 61 ++ src/java/network/HTTPOutputStream.java | 49 + src/java/network/Protocol.java | 225 ++++ src/java/network/ProtocolManager.java | 318 ++++++ src/java/network/ProtocolObserver.java | 6 + src/java/network/Server.java | 278 +++++ src/java/network/Waiter.java | 14 + src/java/network/XMLConnection.java | 39 + src/java/network/chat/Chat.java | 192 ++++ src/java/network/chat/ChatController.java | 107 ++ src/java/network/chat/ChatManager.java | 142 +++ src/java/network/chat/ChatObserver.java | 17 + src/java/network/chat/ChatQuote.java | 45 + src/java/network/chat/JChat.java | 150 +++ src/java/network/chat/JChatDialog.java | 44 + src/java/network/chat/JChatMenuBar.java | 55 + src/java/network/chat/LaunchChat.java | 32 + src/java/network/login/JLogin.java | 66 ++ src/java/network/login/JLoginMenuBar.java | 53 + src/java/network/login/JLoginToolBar.java | 44 + src/java/network/login/LaunchLogin.java | 27 + src/java/network/login/Login.java | 370 +++++++ src/java/network/login/LoginController.java | 87 ++ src/java/network/login/LoginManager.java | 443 ++++++++ src/java/network/login/LoginObserver.java | 16 + src/java/overview.html | 24 + src/java/tool/LaunchBundleManager.java | 29 + .../controller/BundleManagerController.java | 110 ++ src/java/tool/model/BundleManagerModel.java | 248 +++++ .../tool/model/BundleManagerObserver.java | 11 + src/java/tool/model/SourceIteratorModel.java | 104 ++ src/java/tool/model/package-info.java | 9 + src/java/tool/package-info.java | 11 + src/java/tool/view/BundleManagerManager.java | 286 ++++++ src/java/tool/view/BundleManagerMenu.java | 44 + src/java/tool/view/BundleManagerToolBar.java | 45 + src/java/tool/view/BundleManagerView.java | 108 ++ src/java/tool/view/SourceIteratorView.java | 127 +++ src/javaTest/misc/BundleTest.java | 80 ++ src/javaTest/misc/ColorCheckedLineTest.java | 46 + src/javaTest/misc/ConfigTest.java | 153 +++ src/javaTest/misc/LocaleTest.java | 39 + src/javaTest/misc/TitledDialogTest.java | 63 ++ src/javaTest/misc/UtilTest.java | 147 +++ src/javaTest/misc/XMLTest.java | 70 ++ src/javaTest/network/HTTPInputStreamTest.java | 89 ++ .../network/HTTPOutputStreamTest.java | 52 + src/javaTest/network/ServerTest.java | 41 + src/javaTest/network/chat/ChatTest.java | 48 + src/javaTest/network/chat/JChatTest.java | 68 ++ .../tool/SourceIteratorModelTest.java | 38 + .../tool/model/BundleManagerModelTest.java | 34 + ws/bundle.sh | 2 + ws/bundleInclusion.sh | 49 + ws/chat.sh | 2 + ws/login.sh | 2 + ws/start_chat.sh | 20 + 576 files changed, 16953 insertions(+), 1 deletion(-) create mode 100644 ant/build.xml create mode 100644 data/config/BundleManager.xml create mode 100644 data/config/BundleManager_en_US.properties create mode 100644 data/config/BundleManager_fr_FR.properties create mode 100644 data/config/Chat.xml create mode 120000 data/config/Chat_en_AU.properties create mode 120000 data/config/Chat_en_CA.properties create mode 120000 data/config/Chat_en_GB.properties create mode 120000 data/config/Chat_en_IE.properties create mode 120000 data/config/Chat_en_IN.properties create mode 120000 data/config/Chat_en_MT.properties create mode 120000 data/config/Chat_en_NZ.properties create mode 120000 data/config/Chat_en_PH.properties create mode 120000 data/config/Chat_en_SG.properties create mode 100755 data/config/Chat_en_US.properties create mode 120000 data/config/Chat_en_ZA.properties create mode 120000 data/config/Chat_fr_BE.properties create mode 120000 data/config/Chat_fr_CA.properties create mode 120000 data/config/Chat_fr_CH.properties create mode 100755 data/config/Chat_fr_FR.properties create mode 120000 data/config/Chat_fr_LU.properties create mode 100644 data/config/Controller_br_FR_breton.properties create mode 100644 data/config/Controller_br_FR_gallo.properties create mode 100644 data/config/Controller_en_US.properties create mode 100644 data/config/Controller_es_ES.properties create mode 100644 data/config/Controller_fr_FR.properties create mode 100644 data/config/Help_br_FR_breton.properties create mode 100644 data/config/Help_br_FR_gallo.properties create mode 120000 data/config/Help_en_AU.properties create mode 120000 data/config/Help_en_BG.properties create mode 120000 data/config/Help_en_CA.properties create mode 120000 data/config/Help_en_GB.properties create mode 120000 data/config/Help_en_IE.properties create mode 120000 data/config/Help_en_IN.properties create mode 120000 data/config/Help_en_MT.properties create mode 120000 data/config/Help_en_NZ.properties create mode 120000 data/config/Help_en_PH.properties create mode 120000 data/config/Help_en_SG.properties create mode 100755 data/config/Help_en_US.properties create mode 120000 data/config/Help_en_ZA.properties create mode 100644 data/config/Help_es_ES.properties create mode 120000 data/config/Help_fr_BE.properties create mode 120000 data/config/Help_fr_CA.properties create mode 120000 data/config/Help_fr_CH.properties create mode 100755 data/config/Help_fr_FR.properties create mode 120000 data/config/Help_fr_LU.properties create mode 100644 data/config/Login.xml create mode 100644 data/config/Login_en_US.properties create mode 100644 data/config/Login_fr_FR.properties create mode 100644 data/config/Misc.xml create mode 100644 data/config/Protocol_en_US.properties create mode 100644 data/config/Protocol_fr_FR.properties create mode 100644 data/config/Proxy_br_FR_breton.properties create mode 100644 data/config/Proxy_br_FR_gallo.properties create mode 120000 data/config/Proxy_en_AU.properties create mode 120000 data/config/Proxy_en_BG.properties create mode 120000 data/config/Proxy_en_CA.properties create mode 120000 data/config/Proxy_en_GB.properties create mode 120000 data/config/Proxy_en_IE.properties create mode 120000 data/config/Proxy_en_IN.properties create mode 120000 data/config/Proxy_en_MT.properties create mode 120000 data/config/Proxy_en_NZ.properties create mode 120000 data/config/Proxy_en_PH.properties create mode 120000 data/config/Proxy_en_SG.properties create mode 100755 data/config/Proxy_en_US.properties create mode 120000 data/config/Proxy_en_ZA.properties create mode 100644 data/config/Proxy_es_ES.properties create mode 120000 data/config/Proxy_fr_BE.properties create mode 120000 data/config/Proxy_fr_CA.properties create mode 120000 data/config/Proxy_fr_CH.properties create mode 100755 data/config/Proxy_fr_FR.properties create mode 120000 data/config/Proxy_fr_LU.properties create mode 100644 data/config/RemoteUpdate_br_FR_breton.properties create mode 100644 data/config/RemoteUpdate_br_FR_gallo.properties create mode 120000 data/config/RemoteUpdate_en_AU.properties create mode 120000 data/config/RemoteUpdate_en_BG.properties create mode 120000 data/config/RemoteUpdate_en_CA.properties create mode 120000 data/config/RemoteUpdate_en_GB.properties create mode 120000 data/config/RemoteUpdate_en_IE.properties create mode 120000 data/config/RemoteUpdate_en_IN.properties create mode 120000 data/config/RemoteUpdate_en_MT.properties create mode 120000 data/config/RemoteUpdate_en_NZ.properties create mode 120000 data/config/RemoteUpdate_en_PH.properties create mode 120000 data/config/RemoteUpdate_en_SG.properties create mode 100755 data/config/RemoteUpdate_en_US.properties create mode 120000 data/config/RemoteUpdate_en_ZA.properties create mode 100644 data/config/RemoteUpdate_es_ES.properties create mode 120000 data/config/RemoteUpdate_fr_BE.properties create mode 120000 data/config/RemoteUpdate_fr_CA.properties create mode 120000 data/config/RemoteUpdate_fr_CH.properties create mode 100755 data/config/RemoteUpdate_fr_FR.properties create mode 120000 data/config/RemoteUpdate_fr_LU.properties create mode 100644 data/config/Story_br_FR_breton.properties create mode 100644 data/config/Story_br_FR_gallo.properties create mode 120000 data/config/Story_en_AU.properties create mode 120000 data/config/Story_en_BG.properties create mode 120000 data/config/Story_en_CA.properties create mode 120000 data/config/Story_en_GB.properties create mode 120000 data/config/Story_en_IE.properties create mode 120000 data/config/Story_en_IN.properties create mode 120000 data/config/Story_en_MT.properties create mode 120000 data/config/Story_en_NZ.properties create mode 120000 data/config/Story_en_PH.properties create mode 120000 data/config/Story_en_SG.properties create mode 100755 data/config/Story_en_US.properties create mode 120000 data/config/Story_en_ZA.properties create mode 100644 data/config/Story_es_ES.properties create mode 120000 data/config/Story_fr_BE.properties create mode 120000 data/config/Story_fr_CA.properties create mode 120000 data/config/Story_fr_CH.properties create mode 100755 data/config/Story_fr_FR.properties create mode 120000 data/config/Story_fr_LU.properties create mode 100644 data/config/ToolBar_br_FR_breton.properties create mode 100644 data/config/ToolBar_br_FR_gallo.properties create mode 120000 data/config/ToolBar_en_AU.properties create mode 120000 data/config/ToolBar_en_BG.properties create mode 120000 data/config/ToolBar_en_CA.properties create mode 120000 data/config/ToolBar_en_GB.properties create mode 120000 data/config/ToolBar_en_IE.properties create mode 120000 data/config/ToolBar_en_IN.properties create mode 120000 data/config/ToolBar_en_MT.properties create mode 120000 data/config/ToolBar_en_NZ.properties create mode 120000 data/config/ToolBar_en_PH.properties create mode 120000 data/config/ToolBar_en_SG.properties create mode 100755 data/config/ToolBar_en_US.properties create mode 120000 data/config/ToolBar_en_ZA.properties create mode 100644 data/config/ToolBar_es_ES.properties create mode 120000 data/config/ToolBar_fr_BE.properties create mode 120000 data/config/ToolBar_fr_CA.properties create mode 120000 data/config/ToolBar_fr_CH.properties create mode 100755 data/config/ToolBar_fr_FR.properties create mode 120000 data/config/ToolBar_fr_LU.properties create mode 100644 data/images/bundle/bundle.png create mode 100644 data/images/button/Abort.png create mode 100644 data/images/button/About.png create mode 100644 data/images/button/AddKey.png create mode 100644 data/images/button/AddLocale.png create mode 100644 data/images/button/Bad.png create mode 100644 data/images/button/Begin.png create mode 100644 data/images/button/Black.png create mode 100644 data/images/button/BugReport.png create mode 100644 data/images/button/Clear.png create mode 100644 data/images/button/Clock.png create mode 100644 data/images/button/Computer.png create mode 100644 data/images/button/ComputerOff.png create mode 100644 data/images/button/ComputerOn.png create mode 100644 data/images/button/Connect.png create mode 100644 data/images/button/Cut.png create mode 100644 data/images/button/Details.png create mode 100644 data/images/button/Disconnect.png create mode 100644 data/images/button/Down.png create mode 100644 data/images/button/DownloadOff.png create mode 100644 data/images/button/DownloadOn.png create mode 100644 data/images/button/DrawAgreement.png create mode 100644 data/images/button/DrawReject.png create mode 100644 data/images/button/EditGroup.png create mode 100644 data/images/button/EditLogin.png create mode 100644 data/images/button/Empty.png create mode 100644 data/images/button/End.png create mode 100644 data/images/button/Find.png create mode 100644 data/images/button/ForcePack.png create mode 100644 data/images/button/Good.png create mode 100644 data/images/button/Help.png create mode 100644 data/images/button/Human.png create mode 100644 data/images/button/HumanOff.png create mode 100644 data/images/button/HumanOn.png create mode 100644 data/images/button/Info.png create mode 100644 data/images/button/JConsole.png create mode 100644 data/images/button/JConsoleOff.png create mode 100644 data/images/button/JConsoleOn.png create mode 100644 data/images/button/JoinGroup.png create mode 100644 data/images/button/LeftGroup.png create mode 100644 data/images/button/Licence.png create mode 100644 data/images/button/LinkType.png create mode 100644 data/images/button/LocalRemoveOff.png create mode 100644 data/images/button/LocalRemoveOn.png create mode 100644 data/images/button/Locale.png create mode 100644 data/images/button/Log.png create mode 100644 data/images/button/Manual.png create mode 100644 data/images/button/Merge.png create mode 100644 data/images/button/Network.png create mode 100644 data/images/button/NetworkOff.png create mode 100644 data/images/button/NetworkOn.png create mode 100644 data/images/button/Next.png create mode 100644 data/images/button/Online.png create mode 100644 data/images/button/Open.png create mode 100644 data/images/button/PackBug.png create mode 100644 data/images/button/PackBugOff.png create mode 100644 data/images/button/PackBugOn.png create mode 100644 data/images/button/Players.png create mode 100644 data/images/button/Previous.png create mode 100644 data/images/button/Pseudo.png create mode 100644 data/images/button/Quit.png create mode 100644 data/images/button/Redo.png create mode 100644 data/images/button/RemoteRemoveOff.png create mode 100644 data/images/button/RemoteRemoveOn.png create mode 100644 data/images/button/RemoveKey.png create mode 100644 data/images/button/RemoveLocale.png create mode 100644 data/images/button/RenameLocale.png create mode 100644 data/images/button/Resign.png create mode 100644 data/images/button/Save.png create mode 100644 data/images/button/SaveAs.png create mode 100644 data/images/button/SetProxy.png create mode 100644 data/images/button/Stop.png create mode 100644 data/images/button/ToolBarProfil.png create mode 100644 data/images/button/Undo.png create mode 100644 data/images/button/Unlog.png create mode 100644 data/images/button/Up.png create mode 100644 data/images/button/Update.png create mode 100644 data/images/button/UploadOff.png create mode 100644 data/images/button/UploadOn.png create mode 100644 data/images/button/Users.png create mode 100644 data/images/button/Warning.png create mode 100644 data/images/button/White.png create mode 100644 data/images/button/develop.png create mode 100644 data/images/chat/chat.png create mode 100644 data/images/flags/ad.png create mode 100644 data/images/flags/ae.png create mode 100644 data/images/flags/af.png create mode 100644 data/images/flags/ag.png create mode 100644 data/images/flags/ai.png create mode 100644 data/images/flags/al.png create mode 100644 data/images/flags/am.png create mode 100644 data/images/flags/an.png create mode 100644 data/images/flags/ao.png create mode 100644 data/images/flags/aq.png create mode 100644 data/images/flags/ar.png create mode 100644 data/images/flags/as.png create mode 100644 data/images/flags/at.png create mode 100644 data/images/flags/au.png create mode 100644 data/images/flags/aw.png create mode 100644 data/images/flags/ax.png create mode 100644 data/images/flags/az.png create mode 100644 data/images/flags/ba.png create mode 100644 data/images/flags/bb.png create mode 100644 data/images/flags/bd.png create mode 100644 data/images/flags/be.png create mode 100644 data/images/flags/bf.png create mode 100644 data/images/flags/bg.png create mode 100644 data/images/flags/bh.png create mode 100644 data/images/flags/bi.png create mode 100644 data/images/flags/bj.png create mode 100644 data/images/flags/bl.png create mode 100644 data/images/flags/bm.png create mode 100644 data/images/flags/bn.png create mode 100644 data/images/flags/bo.png create mode 100644 data/images/flags/br.png create mode 100644 data/images/flags/br2.png create mode 100644 data/images/flags/bs.png create mode 100644 data/images/flags/bt.png create mode 100644 data/images/flags/bv.png create mode 100644 data/images/flags/bw.png create mode 100644 data/images/flags/by.png create mode 100644 data/images/flags/bz.png create mode 100644 data/images/flags/ca.png create mode 100644 data/images/flags/cc.png create mode 100644 data/images/flags/cd.png create mode 100644 data/images/flags/cf.png create mode 100644 data/images/flags/cg.png create mode 100644 data/images/flags/ch.png create mode 100644 data/images/flags/ci.png create mode 100644 data/images/flags/ck.png create mode 100644 data/images/flags/cl.png create mode 100644 data/images/flags/cm.png create mode 100644 data/images/flags/cn.png create mode 100644 data/images/flags/co.png create mode 100644 data/images/flags/cr.png create mode 100644 data/images/flags/cs.png create mode 100644 data/images/flags/cu.png create mode 100644 data/images/flags/cv.png create mode 100644 data/images/flags/cx.png create mode 100644 data/images/flags/cy.png create mode 100644 data/images/flags/cz.png create mode 100644 data/images/flags/de.png create mode 100644 data/images/flags/dj.png create mode 100644 data/images/flags/dk.png create mode 100644 data/images/flags/dm.png create mode 100644 data/images/flags/do.png create mode 100644 data/images/flags/dz.png create mode 100644 data/images/flags/ec.png create mode 100644 data/images/flags/ee.png create mode 100644 data/images/flags/eg.png create mode 100644 data/images/flags/eh.png create mode 100644 data/images/flags/er.png create mode 100644 data/images/flags/es.png create mode 100644 data/images/flags/et.png create mode 100644 data/images/flags/fi.png create mode 100644 data/images/flags/fj.png create mode 100644 data/images/flags/fk.png create mode 100644 data/images/flags/fm.png create mode 100644 data/images/flags/fo.png create mode 100644 data/images/flags/fr.png create mode 100644 data/images/flags/fx.png create mode 100644 data/images/flags/ga.png create mode 100644 data/images/flags/gb.png create mode 100644 data/images/flags/gd.png create mode 100644 data/images/flags/ge.png create mode 100644 data/images/flags/gf.png create mode 100644 data/images/flags/gg.png create mode 100644 data/images/flags/gh.png create mode 100644 data/images/flags/gi.png create mode 100644 data/images/flags/gl.png create mode 100644 data/images/flags/gm.png create mode 100644 data/images/flags/gn.png create mode 100644 data/images/flags/gp.png create mode 100644 data/images/flags/gq.png create mode 100644 data/images/flags/gr.png create mode 100644 data/images/flags/gs.png create mode 100644 data/images/flags/gt.png create mode 100644 data/images/flags/gu.png create mode 100644 data/images/flags/gw.png create mode 100644 data/images/flags/gy.png create mode 100644 data/images/flags/hk.png create mode 100644 data/images/flags/hm.png create mode 100644 data/images/flags/hn.png create mode 100644 data/images/flags/hr.png create mode 100644 data/images/flags/ht.png create mode 100644 data/images/flags/hu.png create mode 100644 data/images/flags/id.png create mode 100644 data/images/flags/ie.png create mode 100644 data/images/flags/il.png create mode 100644 data/images/flags/in.png create mode 100644 data/images/flags/io.png create mode 100644 data/images/flags/iq.png create mode 100644 data/images/flags/ir.png create mode 100644 data/images/flags/is.png create mode 100644 data/images/flags/it.png create mode 100644 data/images/flags/je.png create mode 100644 data/images/flags/jm.png create mode 100644 data/images/flags/jo.png create mode 100644 data/images/flags/jp.png create mode 100644 data/images/flags/ke.png create mode 100644 data/images/flags/kg.png create mode 100644 data/images/flags/kh.png create mode 100644 data/images/flags/ki.png create mode 100644 data/images/flags/km.png create mode 100644 data/images/flags/kn.png create mode 100644 data/images/flags/kp.png create mode 100644 data/images/flags/kr.png create mode 100644 data/images/flags/kw.png create mode 100644 data/images/flags/ky.png create mode 100644 data/images/flags/kz.png create mode 100644 data/images/flags/la.png create mode 100644 data/images/flags/lb.png create mode 100644 data/images/flags/lc.png create mode 100644 data/images/flags/li.png create mode 100644 data/images/flags/lk.png create mode 100644 data/images/flags/lr.png create mode 100644 data/images/flags/ls.png create mode 100644 data/images/flags/lt.png create mode 100644 data/images/flags/lu.png create mode 100644 data/images/flags/lv.png create mode 100644 data/images/flags/ly.png create mode 100644 data/images/flags/ma.png create mode 100644 data/images/flags/mc.png create mode 100644 data/images/flags/md.png create mode 100644 data/images/flags/me.png create mode 100644 data/images/flags/mf.png create mode 100644 data/images/flags/mg.png create mode 100644 data/images/flags/mh.png create mode 100644 data/images/flags/mk.png create mode 100644 data/images/flags/ml.png create mode 100644 data/images/flags/mm.png create mode 100644 data/images/flags/mn.png create mode 100644 data/images/flags/mo.png create mode 100644 data/images/flags/mp.png create mode 100644 data/images/flags/mq.png create mode 100644 data/images/flags/mr.png create mode 100644 data/images/flags/ms.png create mode 100644 data/images/flags/mt.png create mode 100644 data/images/flags/mu.png create mode 100644 data/images/flags/mv.png create mode 100644 data/images/flags/mw.png create mode 100644 data/images/flags/mx.png create mode 100644 data/images/flags/my.png create mode 100644 data/images/flags/mz.png create mode 100644 data/images/flags/na.png create mode 100644 data/images/flags/nc.png create mode 100644 data/images/flags/ne.png create mode 100644 data/images/flags/nf.png create mode 100644 data/images/flags/ng.png create mode 100644 data/images/flags/ni.png create mode 100644 data/images/flags/nl.png create mode 100644 data/images/flags/no.png create mode 100644 data/images/flags/np.png create mode 100644 data/images/flags/nr.png create mode 100644 data/images/flags/nu.png create mode 100644 data/images/flags/nz.png create mode 100644 data/images/flags/om.png create mode 100644 data/images/flags/pa.png create mode 100644 data/images/flags/pe.png create mode 100644 data/images/flags/pf.png create mode 100644 data/images/flags/pg.png create mode 100644 data/images/flags/ph.png create mode 100644 data/images/flags/pk.png create mode 100644 data/images/flags/pl.png create mode 100644 data/images/flags/pm.png create mode 100644 data/images/flags/pn.png create mode 100644 data/images/flags/pr.png create mode 100644 data/images/flags/ps.png create mode 100644 data/images/flags/pt.png create mode 100644 data/images/flags/pw.png create mode 100644 data/images/flags/py.png create mode 100644 data/images/flags/qa.png create mode 100644 data/images/flags/re.png create mode 100644 data/images/flags/ro.png create mode 100644 data/images/flags/rs.png create mode 100644 data/images/flags/ru.png create mode 100644 data/images/flags/rw.png create mode 100644 data/images/flags/sa.png create mode 100644 data/images/flags/sb.png create mode 100644 data/images/flags/sc.png create mode 100644 data/images/flags/sd.png create mode 100644 data/images/flags/se.png create mode 100644 data/images/flags/sg.png create mode 100644 data/images/flags/sh.png create mode 100644 data/images/flags/si.png create mode 100644 data/images/flags/sj.png create mode 100644 data/images/flags/sk.png create mode 100644 data/images/flags/sl.png create mode 100644 data/images/flags/sm.png create mode 100644 data/images/flags/sn.png create mode 100644 data/images/flags/so.png create mode 100644 data/images/flags/sr.png create mode 100644 data/images/flags/st.png create mode 100644 data/images/flags/sv.png create mode 100644 data/images/flags/sy.png create mode 100644 data/images/flags/sz.png create mode 100644 data/images/flags/tc.png create mode 100644 data/images/flags/td.png create mode 100644 data/images/flags/tf.png create mode 100644 data/images/flags/tg.png create mode 100644 data/images/flags/th.png create mode 100644 data/images/flags/tj.png create mode 100644 data/images/flags/tk.png create mode 100644 data/images/flags/tl.png create mode 100644 data/images/flags/tm.png create mode 100644 data/images/flags/tn.png create mode 100644 data/images/flags/to.png create mode 100644 data/images/flags/tp.png create mode 100644 data/images/flags/tr.png create mode 100644 data/images/flags/tt.png create mode 100644 data/images/flags/tv.png create mode 100644 data/images/flags/tw.png create mode 100644 data/images/flags/tz.png create mode 100644 data/images/flags/ua.png create mode 100644 data/images/flags/ug.png create mode 100644 data/images/flags/um.png create mode 100644 data/images/flags/us.png create mode 100644 data/images/flags/uy.png create mode 100644 data/images/flags/uz.png create mode 100644 data/images/flags/va.png create mode 100644 data/images/flags/vc.png create mode 100644 data/images/flags/ve.png create mode 100644 data/images/flags/vg.png create mode 100644 data/images/flags/vi.png create mode 100644 data/images/flags/vn.png create mode 100644 data/images/flags/vu.png create mode 100644 data/images/flags/wf.png create mode 100644 data/images/flags/ws.png create mode 100644 data/images/flags/xt.png create mode 100644 data/images/flags/ye.png create mode 100644 data/images/flags/yt.png create mode 100644 data/images/flags/yu.png create mode 100644 data/images/flags/za.png create mode 100644 data/images/flags/zm.png create mode 100644 data/images/flags/zw.png create mode 100644 data/images/login/login.png create mode 100644 data/images/misc.png create mode 100644 data/texts/AboutBundleManager.html create mode 100644 data/texts/AboutChat.html create mode 100644 data/texts/AboutLogin.html create mode 100644 data/texts/AboutMisc.html create mode 100644 data/texts/BundleManagerLicence.html create mode 100644 data/texts/ChatLicence.html create mode 100644 data/texts/LoginLicence.html create mode 100644 data/texts/MiscLicence.html create mode 100644 src/java/misc/ApplicationManager.java create mode 100644 src/java/misc/Bundle.java create mode 100644 src/java/misc/ColorCheckedLine.java create mode 100644 src/java/misc/CommandLineServer.java create mode 100644 src/java/misc/CommandLineWaiter.java create mode 100644 src/java/misc/Config.java create mode 100644 src/java/misc/Controller.java create mode 100644 src/java/misc/DatePanel.java create mode 100644 src/java/misc/DimensionDouble.java create mode 100644 src/java/misc/EasterEgg.java create mode 100644 src/java/misc/Guide.java create mode 100644 src/java/misc/HelpManager.java create mode 100644 src/java/misc/HourPanel.java create mode 100644 src/java/misc/HtmlDialog.java create mode 100644 src/java/misc/ImagePreview.java create mode 100644 src/java/misc/JConsole.java create mode 100644 src/java/misc/JRemoteUpdate.java create mode 100644 src/java/misc/LocalizedUserLabel.java create mode 100644 src/java/misc/Log.java create mode 100644 src/java/misc/MultiToolBarBorderLayout.java create mode 100644 src/java/misc/OwnFrame.java create mode 100644 src/java/misc/Plugins.java create mode 100644 src/java/misc/ProgressState.java create mode 100644 src/java/misc/ProgressStatePanel.java create mode 100644 src/java/misc/ProxyManager.java create mode 100644 src/java/misc/RealTime.java create mode 100644 src/java/misc/RemoteUpdate.java create mode 100644 src/java/misc/RemoteUpdateManager.java create mode 100644 src/java/misc/ScaledImage.java create mode 100644 src/java/misc/SpinnerSlider.java create mode 100644 src/java/misc/StateNotifier.java create mode 100644 src/java/misc/Story.java create mode 100644 src/java/misc/StoryManager.java create mode 100644 src/java/misc/Timer.java create mode 100644 src/java/misc/Timestamp.java create mode 100644 src/java/misc/TitledDialog.java create mode 100644 src/java/misc/ToolBarManager.java create mode 100644 src/java/misc/Util.java create mode 100644 src/java/misc/XML.java create mode 100644 src/java/misc/package-info.java create mode 100644 src/java/network/Client.java create mode 100644 src/java/network/HTTPInputStream.java create mode 100644 src/java/network/HTTPOutputStream.java create mode 100644 src/java/network/Protocol.java create mode 100644 src/java/network/ProtocolManager.java create mode 100644 src/java/network/ProtocolObserver.java create mode 100644 src/java/network/Server.java create mode 100644 src/java/network/Waiter.java create mode 100644 src/java/network/XMLConnection.java create mode 100644 src/java/network/chat/Chat.java create mode 100644 src/java/network/chat/ChatController.java create mode 100644 src/java/network/chat/ChatManager.java create mode 100644 src/java/network/chat/ChatObserver.java create mode 100644 src/java/network/chat/ChatQuote.java create mode 100644 src/java/network/chat/JChat.java create mode 100644 src/java/network/chat/JChatDialog.java create mode 100644 src/java/network/chat/JChatMenuBar.java create mode 100644 src/java/network/chat/LaunchChat.java create mode 100644 src/java/network/login/JLogin.java create mode 100644 src/java/network/login/JLoginMenuBar.java create mode 100644 src/java/network/login/JLoginToolBar.java create mode 100644 src/java/network/login/LaunchLogin.java create mode 100644 src/java/network/login/Login.java create mode 100644 src/java/network/login/LoginController.java create mode 100644 src/java/network/login/LoginManager.java create mode 100644 src/java/network/login/LoginObserver.java create mode 100644 src/java/overview.html create mode 100644 src/java/tool/LaunchBundleManager.java create mode 100644 src/java/tool/controller/BundleManagerController.java create mode 100644 src/java/tool/model/BundleManagerModel.java create mode 100644 src/java/tool/model/BundleManagerObserver.java create mode 100644 src/java/tool/model/SourceIteratorModel.java create mode 100644 src/java/tool/model/package-info.java create mode 100644 src/java/tool/package-info.java create mode 100644 src/java/tool/view/BundleManagerManager.java create mode 100644 src/java/tool/view/BundleManagerMenu.java create mode 100644 src/java/tool/view/BundleManagerToolBar.java create mode 100644 src/java/tool/view/BundleManagerView.java create mode 100644 src/java/tool/view/SourceIteratorView.java create mode 100644 src/javaTest/misc/BundleTest.java create mode 100644 src/javaTest/misc/ColorCheckedLineTest.java create mode 100644 src/javaTest/misc/ConfigTest.java create mode 100644 src/javaTest/misc/LocaleTest.java create mode 100644 src/javaTest/misc/TitledDialogTest.java create mode 100644 src/javaTest/misc/UtilTest.java create mode 100644 src/javaTest/misc/XMLTest.java create mode 100644 src/javaTest/network/HTTPInputStreamTest.java create mode 100644 src/javaTest/network/HTTPOutputStreamTest.java create mode 100644 src/javaTest/network/ServerTest.java create mode 100644 src/javaTest/network/chat/ChatTest.java create mode 100644 src/javaTest/network/chat/JChatTest.java create mode 100644 src/javaTest/tool/SourceIteratorModelTest.java create mode 100644 src/javaTest/tool/model/BundleManagerModelTest.java create mode 100755 ws/bundle.sh create mode 100755 ws/bundleInclusion.sh create mode 100755 ws/chat.sh create mode 100755 ws/login.sh create mode 100755 ws/start_chat.sh diff --git a/README.md b/README.md index aef75e0..653686b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,45 @@ # miscJar -Ensemble de fonctions pratiques pour programme java \ No newline at end of file +Misc est un atelier pour le dĂ©veloppement d'application Java pouvant servir Ă  l'illustration pĂ©dagogique. + + + +Objets de pratiques + + * Log : fonction de trace + * Config : gestion de valeurs initiales + * Bundle : internationalisation + * UpdateSender : alerte sur modification de valeur + * ColorCheckedLine : gestion des couleurs d'un terminal pour testes unitaires en mode texte + +Fonctions rĂ©currentes + + * Util : ensemble de fonction (souvent graphique) pour factoriser du code + +ÉlĂ©ments graphiques + + * SpinnerSlider : couplage d'un curseur (slider) et d'un champ numĂ©rique (spinner) + * DatePanel : saisie de date + * HourPanel : saisie d'heure + * ImagePreview : accessoire de visualisation d'image pour dialogue de choix de fichier. + * TitledDialog : fenĂŞtre avec gestion d'icĂ´ne de l'application + * HtmlDialog : affichage d'une page HTML dans une application + * Guide : outil d’auto-documentation pour surlignant l'action Ă  rĂ©aliser dans un scĂ©nario d'utilisation + +Modèle MVC + + * ApplicationManager : associe la gestion de bouton dans des menus dĂ©roulant ou des boĂ®te d'icĂ´ne + * HelpManager : menu d'aide standard avec gestion de langue + * ToolBarManager : boĂ®te d'icĂ´ne dĂ©tachable + * Controler : contrĂ´leur du modèle MVC + * ProgressState : gestion de modification du modèle pour connexion d'une barre de progression + +Contournement + + * XML : contournement d'un bug d'une version de Java + +RĂ©seau + + * CommandLineServer : fonctions gĂ©nĂ©rique d'un serveur socket en mode texte + * CommandLineWaiter : classe gĂ©nĂ©rique Ă  spĂ©cialiser en fonction du serveur souhaitĂ© + diff --git a/ant/build.xml b/ant/build.xml new file mode 100644 index 0000000..5006243 --- /dev/null +++ b/ant/build.xml @@ -0,0 +1,579 @@ + + + + + + + + + + Java Practice : the chess game, by Dr. F. Merciol (c) 2008 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Chess Game source files]]> + Copyright © 2008 Dr. F. Merciol. Tous droits réservés.]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/config/BundleManager.xml b/data/config/BundleManager.xml new file mode 100644 index 0000000..c223597 --- /dev/null +++ b/data/config/BundleManager.xml @@ -0,0 +1,38 @@ + + + +This file is automaticaly generated by BundleManager application at 6:58 PM on Aug 17, 2018. +0 +2 +North +/mnt/f/home/felix/Travail/IUT/M3101/data/config +false +false + +[x=50,y=50] +data/images/bundle/bundle.png +false +false +1 +true +[x=50,y=50] +false +/mnt/sata/stock/nomade/IRISA-UBS/ImageByTrees/src/java/imageByTrees/app +true +[x=50,y=50] +North +[x=50,y=50] +North + +[x=50,y=50] +data/log +true +[x=50,y=50] +[x=50,y=50] + +false +South +true +true +false + diff --git a/data/config/BundleManager_en_US.properties b/data/config/BundleManager_en_US.properties new file mode 100644 index 0000000..6425997 --- /dev/null +++ b/data/config/BundleManager_en_US.properties @@ -0,0 +1,44 @@ +#Produit automatiquement par BundleManager +#Thu Jun 30 17:35:12 CEST 2011 +ActionAddKey=add Key +ActionAddLocale=add Locale +ActionChangeLocale=Change Locale +ActionEmpty=New +ActionFile=File +ActionInit=Select sources \u2026 +ActionMerge=Merge +ActionNext=Next String +ActionOpen=Open \u2026 +ActionQuit=Quit +ActionRemoveKey=remove Key +ActionRemoveLocale=remove Locale +ActionRenameLocale=rename Locale +ActionSave=Save +ActionSaveAs=Save As \u2026 +AddKeyTitle=Add new key +AddLocaleTitle=New locale name\: +BundleEditorTitle=\ Bundle Editor ({0}) +BundleNotSavedTitle=Bundle not saved +BundleTitle=\ Bundle Editor +ExceptionCantLoad=Bundle {0} can''t be loaded\! +ExceptionCantSave=Bundle {0} can''t be saved\! +ExceptionDuplicatedKey=Duplicated key {0}\! +ExceptionDuplicatedLocale=Duplicated locale {0}\! +HeaderBundleFile=This file is generated automatically by BundleManager +LabelBundelPropertieFile=bundle properties file +LabelFileName=File name +LabelKeys=keys +LabelLine=Line +LabelLineNumber=Line Number +LabelNewKey=New key name\: +LabelRealyDiscardBundle=Do you want to discard current bundle ? +LabelSaveBundle=Do you want to save bundle ? +MessageAddLocale=\ Add Locale +MessageNoKey=You must have to select one key line\! +MessageNoLocale=You must have to select one locale column\! +MessageNoSourceDir=You must choose a directory source before (use Select Sources ...)\! +MessageRenameLocale=\ Rename Locale +NoKeyTitle=Can''t determine key +NoLocaleTitle=Can''t determine locale +NoSourceTitle=No source +RenameLocaleTitle=Rename locale diff --git a/data/config/BundleManager_fr_FR.properties b/data/config/BundleManager_fr_FR.properties new file mode 100644 index 0000000..1f55e26 --- /dev/null +++ b/data/config/BundleManager_fr_FR.properties @@ -0,0 +1,44 @@ +#Produit automatiquement par BundleManager +#Thu Jun 30 17:35:12 CEST 2011 +ActionAddKey=Ajouter R\u00E9f\u00E9rence +ActionAddLocale=Ajouter Langue +ActionChangeLocale=Changer de Langue +ActionEmpty=Nouveau +ActionFile=Fichiers +ActionInit=Choisir Sources \u2026 +ActionMerge=Fusione +ActionNext=Cha\u00EEne Suivante +ActionOpen=Ouvrir \u2026 +ActionQuit=Quitter +ActionRemoveKey=Supprimer R\u00E9f\u00E9rence +ActionRemoveLocale=Supprimer Langue +ActionRenameLocale=Renomer Langue +ActionSave=Sauver +ActionSaveAs=Sauver sous \u2026 +AddKeyTitle=AJouter une nouvelle r\u00E9f\u00E9rence +AddLocaleTitle=Nouvelle langue \: +BundleEditorTitle=\ Editeur de Baluchon (\= propri\u00E9t\u00E9s du programme) ({0}) +BundleNotSavedTitle=Baluchon non sauv\u00E9 +BundleTitle=\ Editeur de baluchon +ExceptionCantLoad=Impossible de charger {0} \! +ExceptionCantSave=Impossible de sauver {0} \! +ExceptionDuplicatedKey=R\u00E9f\u00E9rence {0} en double \! +ExceptionDuplicatedLocale=Localisation {0} en double \! +HeaderBundleFile=Produit automatiquement par BundleManager +LabelBundelPropertieFile=Baluchon de propri\u00E9t\u00E9s (Langue) +LabelFileName=Nom du fichier +LabelKeys=R\u00E9f\u00E9rences +LabelLine=Ligne +LabelLineNumber=Num\u00E9ro de Ligne +LabelNewKey=Nouvelle r\u00E9f\u00E9rences \: +LabelRealyDiscardBundle=Voulez-vous vraiment abandonner les modifications ? +LabelSaveBundle=Vouslez-vous sauver le baluchon ? +MessageAddLocale=\ Ajout d''une langue +MessageNoKey=Vous devez selectionner une ligne de key \! +MessageNoLocale=Vous devez selectionner une colonne de langue \! +MessageNoSourceDir=Vous devez d''abord choisir un r\u00E9pertoire de source en utilisant "Choisir Sources ..." \! +MessageRenameLocale=\ Changement de langue +NoKeyTitle=Impossible de trouver la r\u00E9f\u00E9rence +NoLocaleTitle=Impossible de trouver la langue +NoSourceTitle=Pas de source +RenameLocaleTitle=Renommer une langue diff --git a/data/config/Chat.xml b/data/config/Chat.xml new file mode 100644 index 0000000..095ad39 --- /dev/null +++ b/data/config/Chat.xml @@ -0,0 +1,60 @@ + + + +This file is automaticaly generated by Chat application at 8:59 AM on Aug 17, 2018. +1 +projets.iut-info-vannes.net +127.0.0.10 +3 +10 +data/images/chat/chat.png +false +/ +squidva.univ-ubs.fr +true +felix +10 +[x=50,y=50] +80 +false +[x=50,y=50] +1 + + +data/log +/home/felix +[x=50,y=50] +2 +10 + +10 +1 +64 +false +8080 +8080 +2 +localhost +2 +10 + + +data/log + +10 +127.0.0.1 +3 +false +false +[x=50,y=50] +isa +2 +inconnu_2212 +SetDodwan +18 +9090 +10 +10 +3128 +10 + diff --git a/data/config/Chat_en_AU.properties b/data/config/Chat_en_AU.properties new file mode 120000 index 0000000..bd679fa --- /dev/null +++ b/data/config/Chat_en_AU.properties @@ -0,0 +1 @@ +Chat_en_US.properties \ No newline at end of file diff --git a/data/config/Chat_en_CA.properties b/data/config/Chat_en_CA.properties new file mode 120000 index 0000000..bd679fa --- /dev/null +++ b/data/config/Chat_en_CA.properties @@ -0,0 +1 @@ +Chat_en_US.properties \ No newline at end of file diff --git a/data/config/Chat_en_GB.properties b/data/config/Chat_en_GB.properties new file mode 120000 index 0000000..bd679fa --- /dev/null +++ b/data/config/Chat_en_GB.properties @@ -0,0 +1 @@ +Chat_en_US.properties \ No newline at end of file diff --git a/data/config/Chat_en_IE.properties b/data/config/Chat_en_IE.properties new file mode 120000 index 0000000..bd679fa --- /dev/null +++ b/data/config/Chat_en_IE.properties @@ -0,0 +1 @@ +Chat_en_US.properties \ No newline at end of file diff --git a/data/config/Chat_en_IN.properties b/data/config/Chat_en_IN.properties new file mode 120000 index 0000000..bd679fa --- /dev/null +++ b/data/config/Chat_en_IN.properties @@ -0,0 +1 @@ +Chat_en_US.properties \ No newline at end of file diff --git a/data/config/Chat_en_MT.properties b/data/config/Chat_en_MT.properties new file mode 120000 index 0000000..bd679fa --- /dev/null +++ b/data/config/Chat_en_MT.properties @@ -0,0 +1 @@ +Chat_en_US.properties \ No newline at end of file diff --git a/data/config/Chat_en_NZ.properties b/data/config/Chat_en_NZ.properties new file mode 120000 index 0000000..bd679fa --- /dev/null +++ b/data/config/Chat_en_NZ.properties @@ -0,0 +1 @@ +Chat_en_US.properties \ No newline at end of file diff --git a/data/config/Chat_en_PH.properties b/data/config/Chat_en_PH.properties new file mode 120000 index 0000000..bd679fa --- /dev/null +++ b/data/config/Chat_en_PH.properties @@ -0,0 +1 @@ +Chat_en_US.properties \ No newline at end of file diff --git a/data/config/Chat_en_SG.properties b/data/config/Chat_en_SG.properties new file mode 120000 index 0000000..bd679fa --- /dev/null +++ b/data/config/Chat_en_SG.properties @@ -0,0 +1 @@ +Chat_en_US.properties \ No newline at end of file diff --git a/data/config/Chat_en_US.properties b/data/config/Chat_en_US.properties new file mode 100755 index 0000000..baa6df9 --- /dev/null +++ b/data/config/Chat_en_US.properties @@ -0,0 +1,12 @@ +#Produit automatiquement par BundleManager +#Thu Jun 30 17:41:47 CEST 2011 +ActionClear=Clear +ActionManageExtention=Managed extention file +ActionPseudo=Pseudonyme +ChangePseudoTitle=\ Change login +ChatNotSavedTitle=\ Chat not saved +ChatTitle=\ Chat ({0}) +LabelChatFilter=Chat text (.chat) +LabelNewPseudo=New login +MessageRealyClearChat=Do you realy want to clear Chat? +MessageSaveChat=Do you want to save chat? diff --git a/data/config/Chat_en_ZA.properties b/data/config/Chat_en_ZA.properties new file mode 120000 index 0000000..bd679fa --- /dev/null +++ b/data/config/Chat_en_ZA.properties @@ -0,0 +1 @@ +Chat_en_US.properties \ No newline at end of file diff --git a/data/config/Chat_fr_BE.properties b/data/config/Chat_fr_BE.properties new file mode 120000 index 0000000..f2d6181 --- /dev/null +++ b/data/config/Chat_fr_BE.properties @@ -0,0 +1 @@ +Chat_fr_FR.properties \ No newline at end of file diff --git a/data/config/Chat_fr_CA.properties b/data/config/Chat_fr_CA.properties new file mode 120000 index 0000000..f2d6181 --- /dev/null +++ b/data/config/Chat_fr_CA.properties @@ -0,0 +1 @@ +Chat_fr_FR.properties \ No newline at end of file diff --git a/data/config/Chat_fr_CH.properties b/data/config/Chat_fr_CH.properties new file mode 120000 index 0000000..f2d6181 --- /dev/null +++ b/data/config/Chat_fr_CH.properties @@ -0,0 +1 @@ +Chat_fr_FR.properties \ No newline at end of file diff --git a/data/config/Chat_fr_FR.properties b/data/config/Chat_fr_FR.properties new file mode 100755 index 0000000..8a1c5c6 --- /dev/null +++ b/data/config/Chat_fr_FR.properties @@ -0,0 +1,12 @@ +#Produit automatiquement par BundleManager +#Thu Jun 30 17:41:47 CEST 2011 +ActionClear=Effacer +ActionManageExtention=G\u00E9rer l''extension de fichier +ActionPseudo=Pseudonyme +ChangePseudoTitle=\ Changer de pseudo +ChatNotSavedTitle=\ Clavardage non sauv\u00E9 +ChatTitle=\ Clavardage ({0}) +LabelChatFilter=Clavardage (.chat) +LabelNewPseudo=Nouveau pseudo +MessageRealyClearChat=Voulez-vous vraiment oublier ce clavardage ? +MessageSaveChat=Vouvez-vous sauvegarder ce clavardage ? diff --git a/data/config/Chat_fr_LU.properties b/data/config/Chat_fr_LU.properties new file mode 120000 index 0000000..f2d6181 --- /dev/null +++ b/data/config/Chat_fr_LU.properties @@ -0,0 +1 @@ +Chat_fr_FR.properties \ No newline at end of file diff --git a/data/config/Controller_br_FR_breton.properties b/data/config/Controller_br_FR_breton.properties new file mode 100644 index 0000000..961e5c9 --- /dev/null +++ b/data/config/Controller_br_FR_breton.properties @@ -0,0 +1,10 @@ +#Produit automatiquement par BundleManager +#Sat Oct 03 19:34:22 CEST 2015 +ActionSave=Enrolla\u00F1\u2026 +ActionEmpty=Nevez +FileTitle=\ Fichenn +ActionLoadMessage=Karga\u00F1 ar gemenn\u2026 +ActionSaveAll=Enrolla\u00F1 holl\u2026 +ActionQuit=Kuitaat +ActionSaveAs=Enrolla\u00F1 dindan\u2026 +ActionOpen=Digeri\u00F1\u2026 diff --git a/data/config/Controller_br_FR_gallo.properties b/data/config/Controller_br_FR_gallo.properties new file mode 100644 index 0000000..085333e --- /dev/null +++ b/data/config/Controller_br_FR_gallo.properties @@ -0,0 +1,10 @@ +#Produit automatiquement par BundleManager +#Sat Oct 03 19:34:22 CEST 2015 +ActionSave=Garder\u2026 +ActionEmpty=Nouviao +FileTitle=\ Fichier +ActionLoadMessage=Charjer le messaije\u2026 +ActionQuit=Qhiter +ActionSaveAll=Garder tout +ActionSaveAs=Garder sou\u2026 +ActionOpen=Ouvri\u2026 diff --git a/data/config/Controller_en_US.properties b/data/config/Controller_en_US.properties new file mode 100644 index 0000000..2153a07 --- /dev/null +++ b/data/config/Controller_en_US.properties @@ -0,0 +1,10 @@ +#Produit automatiquement par BundleManager +#Sat Oct 03 19:34:22 CEST 2015 +ActionSave=Save\u2026 +ActionEmpty=Empty +ActionLoadMessage=Load message\u2026 +FileTitle=\ File +ActionQuit=Exit +ActionSaveAll=Save all +ActionSaveAs=Save as\u2026 +ActionOpen=Open\u2026 diff --git a/data/config/Controller_es_ES.properties b/data/config/Controller_es_ES.properties new file mode 100644 index 0000000..8ad5ac2 --- /dev/null +++ b/data/config/Controller_es_ES.properties @@ -0,0 +1,10 @@ +#Produit automatiquement par BundleManager +#Sat Oct 03 19:34:22 CEST 2015 +ActionEmpty=Nuevo +ActionLoadMessage=Carga el mensaje\u2026 +ActionOpen=Abrir\u2026 +ActionQuit=Dejar +ActionSave=Salveguardar\u2026 +ActionSaveAll=Salveguardar todo +ActionSaveAs=Salveguardar como\u2026 +FileTitle=\ Archivo diff --git a/data/config/Controller_fr_FR.properties b/data/config/Controller_fr_FR.properties new file mode 100644 index 0000000..6868a6e --- /dev/null +++ b/data/config/Controller_fr_FR.properties @@ -0,0 +1,10 @@ +#Produit automatiquement par BundleManager +#Sat Oct 03 19:34:22 CEST 2015 +ActionSave=Sauver\u2026 +ActionEmpty=Nouveau +ActionLoadMessage=Charge le message\u2026 +FileTitle=\ Fichier +ActionQuit=Quitter +ActionSaveAll=Sauver tout +ActionSaveAs=Sauver sous\u2026 +ActionOpen=Ouvrir\u2026 diff --git a/data/config/Help_br_FR_breton.properties b/data/config/Help_br_FR_breton.properties new file mode 100644 index 0000000..e9571d3 --- /dev/null +++ b/data/config/Help_br_FR_breton.properties @@ -0,0 +1,18 @@ +#Produit automatiquement par BundleManager +#Sat Oct 03 19:40:37 CEST 2015 +AboutTitle=\ Diwar-benn +ActionAbout=Diwar-benn\u2026 +ActionBugReport=Digontammadur\u2026 +ActionClear=FR Effacer +ActionForcePack=FR Ajuste la fen\u00EAtre +ActionJConsole=FR Console +ActionLicence=Aotre +ActionLocale=Yezh +ChangeLocaleTitle=\ Che\u00F1chamant yezh +DumpTitle=\ Choaz ur fichenn roudo\u00F9 +HelpTitle=\ Sikour +JConsoleTitle=FR Console Java +LabelDumpFilter=Fichenn roud (.log) +LicenceTitle=\ Aotre +LocalizedTitle=\ Etrebroadel +MessageChooseLocale=Choaz ur yezh diff --git a/data/config/Help_br_FR_gallo.properties b/data/config/Help_br_FR_gallo.properties new file mode 100644 index 0000000..65bcae2 --- /dev/null +++ b/data/config/Help_br_FR_gallo.properties @@ -0,0 +1,18 @@ +#Produit automatiquement par BundleManager +#Tue Oct 13 20:07:38 CEST 2015 +AboutTitle=\ Entour du lojici\u00EB ; raport ao... +ActionAbout=Entour\u2026 +ActionBugReport=D\u00E9pou\u00E9zoner\u2026 +ActionClear=FR Effacer +ActionForcePack=FR Ajuste la fen\u00EAtre +ActionJConsole=FR Console +ActionLicence=Licence +ActionLocale=Langaije +ChangeLocaleTitle=\ Chanjer de langue +DumpTitle=\ chou\u00E9zi un fichier de routes +HelpTitle=\ A\u00EFde +JConsoleTitle=FR Console Java +LabelDumpFilter=Fichier de route (.log) +LicenceTitle=\ Licence +LocalizedTitle=\ Empllat +MessageChooseLocale=Chou\u00E9zi une langue diff --git a/data/config/Help_en_AU.properties b/data/config/Help_en_AU.properties new file mode 120000 index 0000000..d0fdcbd --- /dev/null +++ b/data/config/Help_en_AU.properties @@ -0,0 +1 @@ +Help_en_US.properties \ No newline at end of file diff --git a/data/config/Help_en_BG.properties b/data/config/Help_en_BG.properties new file mode 120000 index 0000000..d0fdcbd --- /dev/null +++ b/data/config/Help_en_BG.properties @@ -0,0 +1 @@ +Help_en_US.properties \ No newline at end of file diff --git a/data/config/Help_en_CA.properties b/data/config/Help_en_CA.properties new file mode 120000 index 0000000..d0fdcbd --- /dev/null +++ b/data/config/Help_en_CA.properties @@ -0,0 +1 @@ +Help_en_US.properties \ No newline at end of file diff --git a/data/config/Help_en_GB.properties b/data/config/Help_en_GB.properties new file mode 120000 index 0000000..d0fdcbd --- /dev/null +++ b/data/config/Help_en_GB.properties @@ -0,0 +1 @@ +Help_en_US.properties \ No newline at end of file diff --git a/data/config/Help_en_IE.properties b/data/config/Help_en_IE.properties new file mode 120000 index 0000000..d0fdcbd --- /dev/null +++ b/data/config/Help_en_IE.properties @@ -0,0 +1 @@ +Help_en_US.properties \ No newline at end of file diff --git a/data/config/Help_en_IN.properties b/data/config/Help_en_IN.properties new file mode 120000 index 0000000..d0fdcbd --- /dev/null +++ b/data/config/Help_en_IN.properties @@ -0,0 +1 @@ +Help_en_US.properties \ No newline at end of file diff --git a/data/config/Help_en_MT.properties b/data/config/Help_en_MT.properties new file mode 120000 index 0000000..d0fdcbd --- /dev/null +++ b/data/config/Help_en_MT.properties @@ -0,0 +1 @@ +Help_en_US.properties \ No newline at end of file diff --git a/data/config/Help_en_NZ.properties b/data/config/Help_en_NZ.properties new file mode 120000 index 0000000..d0fdcbd --- /dev/null +++ b/data/config/Help_en_NZ.properties @@ -0,0 +1 @@ +Help_en_US.properties \ No newline at end of file diff --git a/data/config/Help_en_PH.properties b/data/config/Help_en_PH.properties new file mode 120000 index 0000000..d0fdcbd --- /dev/null +++ b/data/config/Help_en_PH.properties @@ -0,0 +1 @@ +Help_en_US.properties \ No newline at end of file diff --git a/data/config/Help_en_SG.properties b/data/config/Help_en_SG.properties new file mode 120000 index 0000000..d0fdcbd --- /dev/null +++ b/data/config/Help_en_SG.properties @@ -0,0 +1 @@ +Help_en_US.properties \ No newline at end of file diff --git a/data/config/Help_en_US.properties b/data/config/Help_en_US.properties new file mode 100755 index 0000000..96cb7ae --- /dev/null +++ b/data/config/Help_en_US.properties @@ -0,0 +1,19 @@ +#Produit automatiquement par BundleManager +#Sat Jun 25 19:24:13 CEST 2011 +#ActionPackBug=Bug: Window.pack +AboutTitle=\ About +ActionAbout=About\u2026 +ActionBugReport=Bug repport\u2026 +ActionClear=Clear +ActionForcePack=Adjust window +ActionJConsole=Console +ActionLicence=Licence +ActionLocale=Locale +ChangeLocaleTitle=\ Change local +DumpTitle=\ Dump file chossen +HelpTitle=\ Help +JConsoleTitle=Java Console +LabelDumpFilter=Log file (.log) +LicenceTitle=\ Licence +LocalizedTitle= Localized label +MessageChooseLocale=Choose new locale diff --git a/data/config/Help_en_ZA.properties b/data/config/Help_en_ZA.properties new file mode 120000 index 0000000..d0fdcbd --- /dev/null +++ b/data/config/Help_en_ZA.properties @@ -0,0 +1 @@ +Help_en_US.properties \ No newline at end of file diff --git a/data/config/Help_es_ES.properties b/data/config/Help_es_ES.properties new file mode 100644 index 0000000..c5041b8 --- /dev/null +++ b/data/config/Help_es_ES.properties @@ -0,0 +1,18 @@ +#Produit automatiquement par BundleManager +#Sun Feb 04 15:57:59 CET 2018 +LocalizedTitle=\ Internacionalizaci\u00F3n +ActionAbout=A prop\u00F3sito de\u2026 +ActionLicence=Licencia +JConsoleTitle=Consola Java +ActionClear=Borrar +MessageChooseLocale=Selecciona unidioma +ActionLocale=Idioma +ChangeLocaleTitle=\ Cambio de idioma +LabelDumpFilter=Archivo Log (.log) +HelpTitle=\ Ayuda +DumpTitle=\ Elecci\u00F3n de un archivo dump +LicenceTitle=\ Licencia +ActionJConsole=Consola +AboutTitle=\ A prop\u00F3sito de +ActionBugReport=Depuraci\u00F3n\u2026 +ActionForcePack=Ajusta la ventana diff --git a/data/config/Help_fr_BE.properties b/data/config/Help_fr_BE.properties new file mode 120000 index 0000000..40b2de9 --- /dev/null +++ b/data/config/Help_fr_BE.properties @@ -0,0 +1 @@ +Help_fr_FR.properties \ No newline at end of file diff --git a/data/config/Help_fr_CA.properties b/data/config/Help_fr_CA.properties new file mode 120000 index 0000000..40b2de9 --- /dev/null +++ b/data/config/Help_fr_CA.properties @@ -0,0 +1 @@ +Help_fr_FR.properties \ No newline at end of file diff --git a/data/config/Help_fr_CH.properties b/data/config/Help_fr_CH.properties new file mode 120000 index 0000000..40b2de9 --- /dev/null +++ b/data/config/Help_fr_CH.properties @@ -0,0 +1 @@ +Help_fr_FR.properties \ No newline at end of file diff --git a/data/config/Help_fr_FR.properties b/data/config/Help_fr_FR.properties new file mode 100755 index 0000000..1c30112 --- /dev/null +++ b/data/config/Help_fr_FR.properties @@ -0,0 +1,19 @@ +#Produit automatiquement par BundleManager +#Sat Jun 25 19:24:13 CEST 2011 +#ActionPackBug=Anomalie : Window.pack +AboutTitle=\ A propos +ActionAbout=A propos\u2026 +ActionBugReport=D\u00E9verminage\u2026 +ActionClear=Effacer +ActionForcePack=Ajuste la fen\u00EAtre +ActionJConsole=Console +ActionLicence=Licence +ActionLocale=Langage +ChangeLocaleTitle=\ Changement de langue +DumpTitle=\ Choix d''un fichier de traces +HelpTitle=\ Aide +JConsoleTitle=Console Java +LabelDumpFilter=Fichier de trace (.log) +LicenceTitle=\ Licence +LocalizedTitle=\ Internationalisation +MessageChooseLocale=Choisissez une langue diff --git a/data/config/Help_fr_LU.properties b/data/config/Help_fr_LU.properties new file mode 120000 index 0000000..40b2de9 --- /dev/null +++ b/data/config/Help_fr_LU.properties @@ -0,0 +1 @@ +Help_fr_FR.properties \ No newline at end of file diff --git a/data/config/Login.xml b/data/config/Login.xml new file mode 100644 index 0000000..72cc4df --- /dev/null +++ b/data/config/Login.xml @@ -0,0 +1,69 @@ + + + +This file is automaticaly generated by Login application at 8:54 AM on Aug 17, 2018. +1 +0 +3 +127.0.0.10 +data/images/login/login.png +2 +[x=50,y=50] +10 +false +true +felix +10 +true +[x=50,y=50] +false +[x=50,y=50] +false +1 + + +data/log +[x=50,y=50] +1 +10 + +10 +1 +true +false +1 +true +80 +8080 +1 +North +/ +localhost +1 +10 +false +North + + +data/log + +10 +projets.iut-info-vannes.net +false +1 +2 +false +false +North +[x=50,y=50] +2 +[x=50,y=50] +North +SetClient +[x=50,y=50] +10 +10 +10 +[x=50,y=50] +false + diff --git a/data/config/Login_en_US.properties b/data/config/Login_en_US.properties new file mode 100644 index 0000000..3b48ee1 --- /dev/null +++ b/data/config/Login_en_US.properties @@ -0,0 +1,38 @@ +#Produit automatiquement par BundleManager +#Thu Jun 30 17:50:09 CEST 2011 +ActionAddAdmin=Add administrator +ActionAddMember=Add member +#ActionConnect=Connect +ActionCreateGroup=Create group +#ActionDisconnect=Disconnect +ActionEditGroup=Edit group +ActionEditLogin=Edit profile +ActionJoinGroup=Join group +ActionLeftGroup=Left group +ActionLog=Log +ActionRemoveAdmin=Remove administrator +ActionRemoveGroup=Remove group +ActionRemoveMember=Remove member +ActionUnlog=Unlog +GroupTitle=\ Group +LabelConfirmPasswd=Confirm password\: +LabelCreatedAt=Created at\: +LabelEmail=Email\: +LabelGroup=Group\: +LabelGroupName=Group name\: +LabelJoinGroup=Choose a group to join\: +LabelLogged=Logged\: +LabelLogin=Login\: +LabelNewPasswd=New password\: +LabelOldPasswd=Old password\: +LabelPassword=Password\: +LabelState=State\: +LabelTry=Try n\u00B0\: +LabelUpdatedAt=Updated at\: +LabelUpdatedBy=By\: +LabelUpdatedByIP=Created by (IP@)\: +LoginTitle=\ Login ({0}) +ConnectionTitle=\ Connection +ManageGroupTitle=\ Manage Group +ManageLoginTitle=Manage profile +anonymous=anonymous diff --git a/data/config/Login_fr_FR.properties b/data/config/Login_fr_FR.properties new file mode 100644 index 0000000..cc64cab --- /dev/null +++ b/data/config/Login_fr_FR.properties @@ -0,0 +1,38 @@ +#Produit automatiquement par BundleManager +#Thu Jun 30 17:50:09 CEST 2011 +#ActionConnect=Connecter +#ActionDisconnect=D\u00E9connecter +ActionAddAdmin=Ajoute un administrateur +ActionAddMember=Ajoute un membre +ActionCreateGroup=Cr\u00E9er un nouveau groupe +ActionEditGroup=Modifie un group +ActionEditLogin=Modifie le profil +ActionJoinGroup=Rejoindre un groupe +ActionLeftGroup=Quitter un groupe +ActionLog=Indetification +ActionRemoveAdmin=Retire un administrateur +ActionRemoveGroup=supprime un groupe +ActionRemoveMember=Retire un membre +ActionUnlog=Retrai d''identit\u00E9 +ConnectionTitle=\ Connexion +GroupTitle=\ Groupe +LabelConfirmPasswd=V\u00E9rification du mot de passe \: +LabelCreatedAt=Cr\u00E9\u00E9 le \: +LabelEmail=Mel \: +LabelGroup=Groupe \: +LabelGroupName=Nom du groupe \: +LabelJoinGroup=Choisissez un groupe \u00E0 rejoindre \: +LabelLogged=Connect\u00E9 \: +LabelLogin=Pseudonyme \: +LabelNewPasswd=Nouveau mot de passe \: +LabelOldPasswd=Ancien mot de passe \: +LabelPassword=Mot de passe \: +LabelState=Etat \: +LabelTry=Essai n\u00B0 \: +LabelUpdatedAt=Mise \u00E0 jour le \: +LabelUpdatedBy=Par \: +LabelUpdatedByIP=Cr\u00E9\u00E9 par (@ IP) \: +LoginTitle=\ Connexion ({0}) +ManageGroupTitle=\ Gestion des groupes +ManageLoginTitle=Gestion du profil +anonymous=inconnu diff --git a/data/config/Misc.xml b/data/config/Misc.xml new file mode 100644 index 0000000..e0657a8 --- /dev/null +++ b/data/config/Misc.xml @@ -0,0 +1,17 @@ + + + +This file is automaticaly generated by Misc application at 10:48 AM on Jan 28, 2021. + +[x=50,y=50] +[x=50,y=50] +[x=50,y=50] +false +false +false +[x=50,y=50] + + +data/images/misc.png +false + diff --git a/data/config/Protocol_en_US.properties b/data/config/Protocol_en_US.properties new file mode 100644 index 0000000..89f0268 --- /dev/null +++ b/data/config/Protocol_en_US.properties @@ -0,0 +1,11 @@ +ActionLinkType=Link network +ActionPublishDodwan=Publish +ActionSetClient=Client +ActionSetDodwan=Dodwan +ActionSetProxy=Proxy +ActionSetServer=Server +ActionUnsetProtocol=None +LabelAvailableNetworkCard=Your network cards\: +LabelConnectionType=What kind of connexion do you want? +LinkTypeTitle=\ Connection type manager +NetworkTitle=\ Network diff --git a/data/config/Protocol_fr_FR.properties b/data/config/Protocol_fr_FR.properties new file mode 100644 index 0000000..544ee8c --- /dev/null +++ b/data/config/Protocol_fr_FR.properties @@ -0,0 +1,11 @@ +ActionLinkType=Relier le r\u00E9seau +ActionPublishDodwan=Publier +ActionSetClient=Client +ActionSetDodwan=Dodwan +ActionSetProxy=Mandataire +ActionSetServer=Serveur +ActionUnsetProtocol=Aucun +LabelAvailableNetworkCard=Vos cartes r\u00E9seau \: +LabelConnectionType=Quel type de connexion souhaitez-vous ? +LinkTypeTitle=\ Gestionnaire du type de connexion +NetworkTitle=\ R\u00E9seau diff --git a/data/config/Proxy_br_FR_breton.properties b/data/config/Proxy_br_FR_breton.properties new file mode 100644 index 0000000..3169988 --- /dev/null +++ b/data/config/Proxy_br_FR_breton.properties @@ -0,0 +1,7 @@ +ActionSetProxy=FR Configuration r\u00E9seau\u2026 +ProxyTitle=FR Configuration R\u00e9seau (Proxy) +LabelHost=FR Serveur du mandataire \: +LabelPort=FR Port du mandataire \: +ActionNoProxy=FR Ne pas utiliser de Mandataire +ActionSystemConfigProxy=FR Utiliser la configuration Java du mandataire +ActionManualConfigProxy=FR D\u00E9finir manuellement la configuration du mandataire diff --git a/data/config/Proxy_br_FR_gallo.properties b/data/config/Proxy_br_FR_gallo.properties new file mode 100644 index 0000000..3169988 --- /dev/null +++ b/data/config/Proxy_br_FR_gallo.properties @@ -0,0 +1,7 @@ +ActionSetProxy=FR Configuration r\u00E9seau\u2026 +ProxyTitle=FR Configuration R\u00e9seau (Proxy) +LabelHost=FR Serveur du mandataire \: +LabelPort=FR Port du mandataire \: +ActionNoProxy=FR Ne pas utiliser de Mandataire +ActionSystemConfigProxy=FR Utiliser la configuration Java du mandataire +ActionManualConfigProxy=FR D\u00E9finir manuellement la configuration du mandataire diff --git a/data/config/Proxy_en_AU.properties b/data/config/Proxy_en_AU.properties new file mode 120000 index 0000000..5050b62 --- /dev/null +++ b/data/config/Proxy_en_AU.properties @@ -0,0 +1 @@ +Proxy_en_US.properties \ No newline at end of file diff --git a/data/config/Proxy_en_BG.properties b/data/config/Proxy_en_BG.properties new file mode 120000 index 0000000..5050b62 --- /dev/null +++ b/data/config/Proxy_en_BG.properties @@ -0,0 +1 @@ +Proxy_en_US.properties \ No newline at end of file diff --git a/data/config/Proxy_en_CA.properties b/data/config/Proxy_en_CA.properties new file mode 120000 index 0000000..5050b62 --- /dev/null +++ b/data/config/Proxy_en_CA.properties @@ -0,0 +1 @@ +Proxy_en_US.properties \ No newline at end of file diff --git a/data/config/Proxy_en_GB.properties b/data/config/Proxy_en_GB.properties new file mode 120000 index 0000000..5050b62 --- /dev/null +++ b/data/config/Proxy_en_GB.properties @@ -0,0 +1 @@ +Proxy_en_US.properties \ No newline at end of file diff --git a/data/config/Proxy_en_IE.properties b/data/config/Proxy_en_IE.properties new file mode 120000 index 0000000..5050b62 --- /dev/null +++ b/data/config/Proxy_en_IE.properties @@ -0,0 +1 @@ +Proxy_en_US.properties \ No newline at end of file diff --git a/data/config/Proxy_en_IN.properties b/data/config/Proxy_en_IN.properties new file mode 120000 index 0000000..5050b62 --- /dev/null +++ b/data/config/Proxy_en_IN.properties @@ -0,0 +1 @@ +Proxy_en_US.properties \ No newline at end of file diff --git a/data/config/Proxy_en_MT.properties b/data/config/Proxy_en_MT.properties new file mode 120000 index 0000000..5050b62 --- /dev/null +++ b/data/config/Proxy_en_MT.properties @@ -0,0 +1 @@ +Proxy_en_US.properties \ No newline at end of file diff --git a/data/config/Proxy_en_NZ.properties b/data/config/Proxy_en_NZ.properties new file mode 120000 index 0000000..5050b62 --- /dev/null +++ b/data/config/Proxy_en_NZ.properties @@ -0,0 +1 @@ +Proxy_en_US.properties \ No newline at end of file diff --git a/data/config/Proxy_en_PH.properties b/data/config/Proxy_en_PH.properties new file mode 120000 index 0000000..5050b62 --- /dev/null +++ b/data/config/Proxy_en_PH.properties @@ -0,0 +1 @@ +Proxy_en_US.properties \ No newline at end of file diff --git a/data/config/Proxy_en_SG.properties b/data/config/Proxy_en_SG.properties new file mode 120000 index 0000000..5050b62 --- /dev/null +++ b/data/config/Proxy_en_SG.properties @@ -0,0 +1 @@ +Proxy_en_US.properties \ No newline at end of file diff --git a/data/config/Proxy_en_US.properties b/data/config/Proxy_en_US.properties new file mode 100755 index 0000000..2b166ed --- /dev/null +++ b/data/config/Proxy_en_US.properties @@ -0,0 +1,7 @@ +ActionSetProxy=Network configuration +ProxyTitle=Network configuration +LabelHost=Host\: +LabelPort=Port\: +ActionNoProxy=Use no proxy +ActionSystemConfigProxy=Use Java VM proxy configuration +ActionManualConfigProxy=Manual proxy definition diff --git a/data/config/Proxy_en_ZA.properties b/data/config/Proxy_en_ZA.properties new file mode 120000 index 0000000..5050b62 --- /dev/null +++ b/data/config/Proxy_en_ZA.properties @@ -0,0 +1 @@ +Proxy_en_US.properties \ No newline at end of file diff --git a/data/config/Proxy_es_ES.properties b/data/config/Proxy_es_ES.properties new file mode 100644 index 0000000..741a2d8 --- /dev/null +++ b/data/config/Proxy_es_ES.properties @@ -0,0 +1,9 @@ +#Produit automatiquement par BundleManager +#Sun Feb 04 16:05:53 CET 2018 +ActionNoProxy=No utilizar proxy +ActionSetProxy=Configuraci\u00F3n de red +LabelHost=Servidor proxy \: +ProxyTitle=CConfiguraci\u00F3n de red (Proxy) +ActionSystemConfigProxy=Utilizar la configuraci\u00F3n Java VM del proxy +ActionManualConfigProxy=Especificar manualmente las opciones del proxy +LabelPort=Puerto de proxy \: diff --git a/data/config/Proxy_fr_BE.properties b/data/config/Proxy_fr_BE.properties new file mode 120000 index 0000000..e261976 --- /dev/null +++ b/data/config/Proxy_fr_BE.properties @@ -0,0 +1 @@ +Proxy_fr_FR.properties \ No newline at end of file diff --git a/data/config/Proxy_fr_CA.properties b/data/config/Proxy_fr_CA.properties new file mode 120000 index 0000000..e261976 --- /dev/null +++ b/data/config/Proxy_fr_CA.properties @@ -0,0 +1 @@ +Proxy_fr_FR.properties \ No newline at end of file diff --git a/data/config/Proxy_fr_CH.properties b/data/config/Proxy_fr_CH.properties new file mode 120000 index 0000000..e261976 --- /dev/null +++ b/data/config/Proxy_fr_CH.properties @@ -0,0 +1 @@ +Proxy_fr_FR.properties \ No newline at end of file diff --git a/data/config/Proxy_fr_FR.properties b/data/config/Proxy_fr_FR.properties new file mode 100755 index 0000000..3e6341a --- /dev/null +++ b/data/config/Proxy_fr_FR.properties @@ -0,0 +1,7 @@ +ActionSetProxy=Configuration r\u00E9seau\u2026 +ProxyTitle=Configuration R\u00e9seau (Proxy) +LabelHost=Serveur du mandataire \: +LabelPort=Port du mandataire \: +ActionNoProxy=Ne pas utiliser de Mandataire +ActionSystemConfigProxy=Utiliser la configuration Java du mandataire +ActionManualConfigProxy=D\u00E9finir manuellement la configuration du mandataire diff --git a/data/config/Proxy_fr_LU.properties b/data/config/Proxy_fr_LU.properties new file mode 120000 index 0000000..e261976 --- /dev/null +++ b/data/config/Proxy_fr_LU.properties @@ -0,0 +1 @@ +Proxy_fr_FR.properties \ No newline at end of file diff --git a/data/config/RemoteUpdate_br_FR_breton.properties b/data/config/RemoteUpdate_br_FR_breton.properties new file mode 100644 index 0000000..638269a --- /dev/null +++ b/data/config/RemoteUpdate_br_FR_breton.properties @@ -0,0 +1,36 @@ +#Produit automatiquement par BundleManager +#Sat Feb 03 16:17:07 CET 2018 +ActionUpload=FR T\u00E9l\u00E9versement +LabelLocalDate=FR Date local +EnumCheckPeriodMonth=Miz +EnumCheckPeriodYear=Bloavezh +LabelRemove=FR Supprim\u00E9 +ActionOnline=FR Mise en ligne\u2026 +ActionLocalRemove=FR Nettoyer fichiers locaux +MessageUploadCompleted=FR {0,choice,0\# Aucun t\u00E9l\u00E9verssement|0< {0} fichiers t\u00E9l\u00E9vers\u00E9s.} +LabelLocal=FR Local +EnumCheckPeriodWeek=Sizhun +LabelFileName=FR Fichier +EnumCheckPeriodDay=Deiz +UpdateSoftTitle=FR Mise \u00E0 jour +LabelRemoteSize=FR Taille distante +MessageDownloadInfo={0} \: {1,choice,0\# (FR pas de serveur)|0< '{2,choice,0\# 0 fichenn|1\# 1 fichenn|1< {2,number,integer} fichenno\u00F9}' ({3}o)} +LabelRemote=FR Distant +ActionDetails=FR D\u00E9tails\u2026 +DownloadTitle=\ Pellgarga\u00F1 bremanaduro\u00F9 +MessageUpload=FR T\u00E9l\u00E9versement +LabelDownload=FR T\u00E9l\u00E9charg\u00E9 +ActionUpdate=Bremanadur\u2026 +MessageDownloadCompleted={0,choice,0\# ebet pellgarga\u00F1|0< {0} fichenno\u00F9 pellgarget. Kuitaat hag adla\u00F1sa\u00F1 ar poellad.} +MessageLocalRemove=FR Nettoyage local +LabelRemoteDate=FR Date distante +ActionDownload=FR T\u00E9l\u00E9chargement +MessageUpdateSoft=FR Relancer la nouvelle version +EnumCheckPeriodNoCheck=Gwech ebet +LabelLocalSize=FR Taille local +DetailsTitle=FR D\u00E9tails +ActionRemoteRemove=FR Nettoyer fichiers distants +LabelCheckingPeriod=Gwiria\u00F1 bremanadurio\u00F9 \: +MessageDownload=Pellgarga\u00F1 +UploadTitle=FR T\u00E9l\u00E9versement +LabelUpload=FR T\u00E9l\u00E9vers\u00E9 diff --git a/data/config/RemoteUpdate_br_FR_gallo.properties b/data/config/RemoteUpdate_br_FR_gallo.properties new file mode 100644 index 0000000..0528aa6 --- /dev/null +++ b/data/config/RemoteUpdate_br_FR_gallo.properties @@ -0,0 +1,35 @@ +#Produit automatiquement par BundleManager +#Sat Feb 03 16:17:07 CET 2018 +ActionUpload=FR T\u00E9l\u00E9versement +LabelLocalDate=FR Date local +EnumCheckPeriodMonth=Mou\u00E9z +EnumCheckPeriodYear=An\u00E9e +LabelRemove=FR Supprim\u00E9 +ActionOnline=FR Mise en ligne\u2026 +ActionLocalRemove=FR Nettoyer fichiers locaux +MessageUploadCompleted=FR {0,choice,0\# Aucun t\u00E9l\u00E9verssement|0< {0} fichiers t\u00E9l\u00E9vers\u00E9s.} +LabelLocal=FR Local +EnumCheckPeriodWeek=Smaine +LabelFileName=FR Fichier +EnumCheckPeriodDay=Jou +UpdateSoftTitle=FR Mise \u00E0 jour +LabelRemoteSize=FR Taille distante +MessageDownloadInfo=FR {0} \: {1,choice,0\# pas de serveur|0< '{2,choice,0\# pas de fichier|1\# 1 fichier|1< {2,number,integer} fichiers}' ({3}o) +LabelRemote=FR Distant +DownloadTitle=\ Telecharjer les abuteries +MessageUpload=FR T\u00E9l\u00E9versement +MessageDownloadCompleted=FR {0,choice,0\# Aucun t\u00E9l\u00E9chargement|0< {0} fichiers t\u00E9l\u00E9charg\u00E9s.\n Vous devez quitter et relancer le logiciel.} +LabelDownload=FR T\u00E9l\u00E9charg\u00E9 +ActionUpdate=Abuter\u2026 +MessageLocalRemove=FR Nettoyage local +LabelRemoteDate=FR Date distante +ActionDownload=FR T\u00E9l\u00E9chargement +MessageUpdateSoft=FR Relancer la nouvelle version +EnumCheckPeriodNoCheck=Pas jam\u00E9s +LabelLocalSize=FR Taille local +DetailsTitle=FR D\u00E9tails +ActionRemoteRemove=FR Nettoyer fichiers distants +LabelCheckingPeriod=Ergarder \u00E9s abuteries \: +UploadTitle=FR T\u00E9l\u00E9versement +MessageDownload=T\u00E9l\u00E9charjer +LabelUpload=FR T\u00E9l\u00E9vers\u00E9 diff --git a/data/config/RemoteUpdate_en_AU.properties b/data/config/RemoteUpdate_en_AU.properties new file mode 120000 index 0000000..057d0a9 --- /dev/null +++ b/data/config/RemoteUpdate_en_AU.properties @@ -0,0 +1 @@ +RemoteUpdate_en_US.properties \ No newline at end of file diff --git a/data/config/RemoteUpdate_en_BG.properties b/data/config/RemoteUpdate_en_BG.properties new file mode 120000 index 0000000..057d0a9 --- /dev/null +++ b/data/config/RemoteUpdate_en_BG.properties @@ -0,0 +1 @@ +RemoteUpdate_en_US.properties \ No newline at end of file diff --git a/data/config/RemoteUpdate_en_CA.properties b/data/config/RemoteUpdate_en_CA.properties new file mode 120000 index 0000000..057d0a9 --- /dev/null +++ b/data/config/RemoteUpdate_en_CA.properties @@ -0,0 +1 @@ +RemoteUpdate_en_US.properties \ No newline at end of file diff --git a/data/config/RemoteUpdate_en_GB.properties b/data/config/RemoteUpdate_en_GB.properties new file mode 120000 index 0000000..057d0a9 --- /dev/null +++ b/data/config/RemoteUpdate_en_GB.properties @@ -0,0 +1 @@ +RemoteUpdate_en_US.properties \ No newline at end of file diff --git a/data/config/RemoteUpdate_en_IE.properties b/data/config/RemoteUpdate_en_IE.properties new file mode 120000 index 0000000..057d0a9 --- /dev/null +++ b/data/config/RemoteUpdate_en_IE.properties @@ -0,0 +1 @@ +RemoteUpdate_en_US.properties \ No newline at end of file diff --git a/data/config/RemoteUpdate_en_IN.properties b/data/config/RemoteUpdate_en_IN.properties new file mode 120000 index 0000000..057d0a9 --- /dev/null +++ b/data/config/RemoteUpdate_en_IN.properties @@ -0,0 +1 @@ +RemoteUpdate_en_US.properties \ No newline at end of file diff --git a/data/config/RemoteUpdate_en_MT.properties b/data/config/RemoteUpdate_en_MT.properties new file mode 120000 index 0000000..057d0a9 --- /dev/null +++ b/data/config/RemoteUpdate_en_MT.properties @@ -0,0 +1 @@ +RemoteUpdate_en_US.properties \ No newline at end of file diff --git a/data/config/RemoteUpdate_en_NZ.properties b/data/config/RemoteUpdate_en_NZ.properties new file mode 120000 index 0000000..057d0a9 --- /dev/null +++ b/data/config/RemoteUpdate_en_NZ.properties @@ -0,0 +1 @@ +RemoteUpdate_en_US.properties \ No newline at end of file diff --git a/data/config/RemoteUpdate_en_PH.properties b/data/config/RemoteUpdate_en_PH.properties new file mode 120000 index 0000000..057d0a9 --- /dev/null +++ b/data/config/RemoteUpdate_en_PH.properties @@ -0,0 +1 @@ +RemoteUpdate_en_US.properties \ No newline at end of file diff --git a/data/config/RemoteUpdate_en_SG.properties b/data/config/RemoteUpdate_en_SG.properties new file mode 120000 index 0000000..057d0a9 --- /dev/null +++ b/data/config/RemoteUpdate_en_SG.properties @@ -0,0 +1 @@ +RemoteUpdate_en_US.properties \ No newline at end of file diff --git a/data/config/RemoteUpdate_en_US.properties b/data/config/RemoteUpdate_en_US.properties new file mode 100755 index 0000000..86739ec --- /dev/null +++ b/data/config/RemoteUpdate_en_US.properties @@ -0,0 +1,35 @@ +#Produit automatiquement par BundleManager +#Sat Feb 03 16:17:07 CET 2018 +ActionUpload=Upload +LabelLocalDate=Local date +LabelRemove=Removed +EnumCheckPeriodMonth=Month +EnumCheckPeriodYear=Year +ActionOnline=Online\u2026 +ActionLocalRemove=Clean local files +MessageUploadCompleted={0,choice,0\# No upload|0< {0} files to upload}. +LabelLocal=Local +EnumCheckPeriodWeek=Week +LabelFileName=File +EnumCheckPeriodDay=Day +UpdateSoftTitle=Update soft needed +LabelRemoteSize=Remote size +MessageDownloadInfo={0}\: {1,choice,0\#Server not responding|0< '{2,choice,0\# no file|1\# 1 file|1< {2,number,integer} files}' ({3}B)} +LabelRemote=Remote +ActionDetails=Details\u2026 +DownloadTitle=Download update +MessageUpload=Upload +MessageDownloadCompleted={0,choice,0\# No upload|0< {0} files uploaded.} +ActionUpdate=Update\u2026 +LabelDownload=Downloaded +LabelRemoteDate=Remote date +ActionDownload=Download +MessageUpdateSoft=Restart now +LabelLocalSize=Local size +DetailsTitle=Details +EnumCheckPeriodNoCheck=Never +ActionRemoteRemove=Clean remote files +LabelCheckingPeriod=Cheking update\: +UploadTitle=\ Upload +MessageDownload=Download +LabelUpload=Uploaded diff --git a/data/config/RemoteUpdate_en_ZA.properties b/data/config/RemoteUpdate_en_ZA.properties new file mode 120000 index 0000000..057d0a9 --- /dev/null +++ b/data/config/RemoteUpdate_en_ZA.properties @@ -0,0 +1 @@ +RemoteUpdate_en_US.properties \ No newline at end of file diff --git a/data/config/RemoteUpdate_es_ES.properties b/data/config/RemoteUpdate_es_ES.properties new file mode 100644 index 0000000..1697afa --- /dev/null +++ b/data/config/RemoteUpdate_es_ES.properties @@ -0,0 +1,36 @@ +!#Produit automatiquement par BundleManager +#Sun Feb 04 16:11:51 CET 2018 +ActionUpload=Cargar +LabelLocalDate=Data local +LabelRemove=Borrado +EnumCheckPeriodMonth=Mes +EnumCheckPeriodYear=A\u00F1o +ActionOnline=Puesta en l\u00EDnea\u2026 +ActionLocalRemove=Limpiar los archives locales +MessageUploadCompleted={0,choice,0\# Ningunacarga|0< {0} archivos cargados} +LabelLocal=Local +EnumCheckPeriodWeek=Semana +LabelFileName=Archivo +EnumCheckPeriodDay=D\u00EDa +UpdateSoftTitle=UpdateSoftTitle +LabelRemoteSize=Tama\u00F1o Remoto +MessageDownloadInfo={0}\: {1,choice,0\#Server not responding|0< '{2,choice,0\# no file|1\# 1 file|1< {2,number,integer} files}' ({3}B)} +LabelRemote=Remoto +ActionDetails=Detalles\u2026 +DownloadTitle=Descarga de las actualizaciones +MessageUpload=Carga +MessageDownloadCompleted={0,choice,0\# No upload|0< {0} files uploaded.} +ActionUpdate=Actualizaci\u00F3n\u2026 +LabelDownload=Descargado +MessageLocalRemove=MessageLocalRemove +LabelRemoteDate=Data Remoto +ActionDownload=Descarga +MessageUpdateSoft=MessageUpdateSoft +LabelLocalSize=Tama\u00F1o local +DetailsTitle=Detalles +EnumCheckPeriodNoCheck=Nunca +ActionRemoteRemove=Limpiar los archivos remotos +LabelCheckingPeriod=Verificaci\u00F3n de las actualizaciones \: +UploadTitle=Carga +MessageDownload=Descarga +LabelUpload=Cargado diff --git a/data/config/RemoteUpdate_fr_BE.properties b/data/config/RemoteUpdate_fr_BE.properties new file mode 120000 index 0000000..c0a598c --- /dev/null +++ b/data/config/RemoteUpdate_fr_BE.properties @@ -0,0 +1 @@ +RemoteUpdate_fr_FR.properties \ No newline at end of file diff --git a/data/config/RemoteUpdate_fr_CA.properties b/data/config/RemoteUpdate_fr_CA.properties new file mode 120000 index 0000000..c0a598c --- /dev/null +++ b/data/config/RemoteUpdate_fr_CA.properties @@ -0,0 +1 @@ +RemoteUpdate_fr_FR.properties \ No newline at end of file diff --git a/data/config/RemoteUpdate_fr_CH.properties b/data/config/RemoteUpdate_fr_CH.properties new file mode 120000 index 0000000..c0a598c --- /dev/null +++ b/data/config/RemoteUpdate_fr_CH.properties @@ -0,0 +1 @@ +RemoteUpdate_fr_FR.properties \ No newline at end of file diff --git a/data/config/RemoteUpdate_fr_FR.properties b/data/config/RemoteUpdate_fr_FR.properties new file mode 100755 index 0000000..d0b4aca --- /dev/null +++ b/data/config/RemoteUpdate_fr_FR.properties @@ -0,0 +1,36 @@ +#Produit automatiquement par BundleManager +#Sat Feb 03 16:17:07 CET 2018 +ActionUpload=T\u00E9l\u00E9versement +LabelLocalDate=Date local +LabelRemove=Supprim\u00E9 +EnumCheckPeriodMonth=Mois +EnumCheckPeriodYear=Ann\u00E9e +ActionOnline=Mise en ligne\u2026 +ActionLocalRemove=Nettoyer fichiers locaux +MessageUploadCompleted={0,choice,0\# Aucun t\u00E9l\u00E9verssement|0< {0} fichiers t\u00E9l\u00E9vers\u00E9s}. +LabelLocal=Local +EnumCheckPeriodWeek=Semaine +LabelFileName=Fichier +EnumCheckPeriodDay=Jour +UpdateSoftTitle=Mise \u00E0 jour +LabelRemoteSize=Taille distante +MessageDownloadInfo={0} \: {1,choice,0\# Le serveur ne r\u00E9pond pas|0< '{2,choice,0\# pas de fichier|1\# 1 fichier|1< {2,number,integer} fichiers}' ({3}o)} +LabelRemote=Distant +ActionDetails=D\u00E9tails\u2026 +DownloadTitle=T\u00E9l\u00E9chargement des mises \u00E0 jour +MessageUpload=T\u00E9l\u00E9versement +MessageDownloadCompleted={0,choice,0\# Aucun t\u00E9l\u00E9chargement|0< {0} fichiers t\u00E9l\u00E9charg\u00E9s.\n Vous devez quitter et relancer le logiciel.} +ActionUpdate=Mise \u00E0 jour\u2026 +LabelDownload=T\u00E9l\u00E9charg\u00E9 +MessageLocalRemove=Nettoyage local +LabelRemoteDate=Date distante +ActionDownload=T\u00E9l\u00E9chargement +MessageUpdateSoft=Relancer la nouvelle version ? \n (si ce message revient lancez UpdatedAdecWatt.jar) +LabelLocalSize=Taille local +DetailsTitle=D\u00E9tails +EnumCheckPeriodNoCheck=Jamais +ActionRemoteRemove=Nettoyer fichiers distants +LabelCheckingPeriod=V\u00E9rification des mises \u00E0 jour \: +UploadTitle=\ T\u00E9l\u00E9versement +MessageDownload=T\u00E9l\u00E9chargement +LabelUpload=T\u00E9l\u00E9vers\u00E9 diff --git a/data/config/RemoteUpdate_fr_LU.properties b/data/config/RemoteUpdate_fr_LU.properties new file mode 120000 index 0000000..c0a598c --- /dev/null +++ b/data/config/RemoteUpdate_fr_LU.properties @@ -0,0 +1 @@ +RemoteUpdate_fr_FR.properties \ No newline at end of file diff --git a/data/config/Story_br_FR_breton.properties b/data/config/Story_br_FR_breton.properties new file mode 100644 index 0000000..8bae49f --- /dev/null +++ b/data/config/Story_br_FR_breton.properties @@ -0,0 +1,4 @@ +#Produit automatiquement par BundleManager +#Sat Oct 03 19:46:00 CEST 2015 +ActionUndo=Dispenn +ActionRedo=Adkregi\u00F1 diff --git a/data/config/Story_br_FR_gallo.properties b/data/config/Story_br_FR_gallo.properties new file mode 100644 index 0000000..d803e64 --- /dev/null +++ b/data/config/Story_br_FR_gallo.properties @@ -0,0 +1,4 @@ +#Produit automatiquement par BundleManager +#Sat Oct 03 19:46:00 CEST 2015 +ActionUndo=D\u00E9marrer +ActionRedo=Erf\u00E9re diff --git a/data/config/Story_en_AU.properties b/data/config/Story_en_AU.properties new file mode 120000 index 0000000..a4ee065 --- /dev/null +++ b/data/config/Story_en_AU.properties @@ -0,0 +1 @@ +Story_en_US.properties \ No newline at end of file diff --git a/data/config/Story_en_BG.properties b/data/config/Story_en_BG.properties new file mode 120000 index 0000000..a4ee065 --- /dev/null +++ b/data/config/Story_en_BG.properties @@ -0,0 +1 @@ +Story_en_US.properties \ No newline at end of file diff --git a/data/config/Story_en_CA.properties b/data/config/Story_en_CA.properties new file mode 120000 index 0000000..a4ee065 --- /dev/null +++ b/data/config/Story_en_CA.properties @@ -0,0 +1 @@ +Story_en_US.properties \ No newline at end of file diff --git a/data/config/Story_en_GB.properties b/data/config/Story_en_GB.properties new file mode 120000 index 0000000..a4ee065 --- /dev/null +++ b/data/config/Story_en_GB.properties @@ -0,0 +1 @@ +Story_en_US.properties \ No newline at end of file diff --git a/data/config/Story_en_IE.properties b/data/config/Story_en_IE.properties new file mode 120000 index 0000000..a4ee065 --- /dev/null +++ b/data/config/Story_en_IE.properties @@ -0,0 +1 @@ +Story_en_US.properties \ No newline at end of file diff --git a/data/config/Story_en_IN.properties b/data/config/Story_en_IN.properties new file mode 120000 index 0000000..a4ee065 --- /dev/null +++ b/data/config/Story_en_IN.properties @@ -0,0 +1 @@ +Story_en_US.properties \ No newline at end of file diff --git a/data/config/Story_en_MT.properties b/data/config/Story_en_MT.properties new file mode 120000 index 0000000..a4ee065 --- /dev/null +++ b/data/config/Story_en_MT.properties @@ -0,0 +1 @@ +Story_en_US.properties \ No newline at end of file diff --git a/data/config/Story_en_NZ.properties b/data/config/Story_en_NZ.properties new file mode 120000 index 0000000..a4ee065 --- /dev/null +++ b/data/config/Story_en_NZ.properties @@ -0,0 +1 @@ +Story_en_US.properties \ No newline at end of file diff --git a/data/config/Story_en_PH.properties b/data/config/Story_en_PH.properties new file mode 120000 index 0000000..a4ee065 --- /dev/null +++ b/data/config/Story_en_PH.properties @@ -0,0 +1 @@ +Story_en_US.properties \ No newline at end of file diff --git a/data/config/Story_en_SG.properties b/data/config/Story_en_SG.properties new file mode 120000 index 0000000..a4ee065 --- /dev/null +++ b/data/config/Story_en_SG.properties @@ -0,0 +1 @@ +Story_en_US.properties \ No newline at end of file diff --git a/data/config/Story_en_US.properties b/data/config/Story_en_US.properties new file mode 100755 index 0000000..c2af387 --- /dev/null +++ b/data/config/Story_en_US.properties @@ -0,0 +1,2 @@ +ActionRedo=Redo +ActionUndo=Undo diff --git a/data/config/Story_en_ZA.properties b/data/config/Story_en_ZA.properties new file mode 120000 index 0000000..a4ee065 --- /dev/null +++ b/data/config/Story_en_ZA.properties @@ -0,0 +1 @@ +Story_en_US.properties \ No newline at end of file diff --git a/data/config/Story_es_ES.properties b/data/config/Story_es_ES.properties new file mode 100644 index 0000000..728059a --- /dev/null +++ b/data/config/Story_es_ES.properties @@ -0,0 +1,2 @@ +ActionRedo=Repetir +ActionUndo=Deshacer diff --git a/data/config/Story_fr_BE.properties b/data/config/Story_fr_BE.properties new file mode 120000 index 0000000..9f4d1cc --- /dev/null +++ b/data/config/Story_fr_BE.properties @@ -0,0 +1 @@ +Story_fr_FR.properties \ No newline at end of file diff --git a/data/config/Story_fr_CA.properties b/data/config/Story_fr_CA.properties new file mode 120000 index 0000000..9f4d1cc --- /dev/null +++ b/data/config/Story_fr_CA.properties @@ -0,0 +1 @@ +Story_fr_FR.properties \ No newline at end of file diff --git a/data/config/Story_fr_CH.properties b/data/config/Story_fr_CH.properties new file mode 120000 index 0000000..9f4d1cc --- /dev/null +++ b/data/config/Story_fr_CH.properties @@ -0,0 +1 @@ +Story_fr_FR.properties \ No newline at end of file diff --git a/data/config/Story_fr_FR.properties b/data/config/Story_fr_FR.properties new file mode 100755 index 0000000..2931fb4 --- /dev/null +++ b/data/config/Story_fr_FR.properties @@ -0,0 +1,2 @@ +ActionRedo=Refaire +ActionUndo=D\u00E9faire diff --git a/data/config/Story_fr_LU.properties b/data/config/Story_fr_LU.properties new file mode 120000 index 0000000..9f4d1cc --- /dev/null +++ b/data/config/Story_fr_LU.properties @@ -0,0 +1 @@ +Story_fr_FR.properties \ No newline at end of file diff --git a/data/config/ToolBar_br_FR_breton.properties b/data/config/ToolBar_br_FR_breton.properties new file mode 100644 index 0000000..c38e2f9 --- /dev/null +++ b/data/config/ToolBar_br_FR_breton.properties @@ -0,0 +1,9 @@ +#Produit automatiquement par BundleManager +#Sat Oct 03 19:49:15 CEST 2015 +NotCardinalPoint=FR {0} n''est pas un point cardinal \! +ActionUp=Pignat +AlreadyRegistredTool=Barre d\u00E9tachable {0} d\u00E9j\u00E0 enregistr\u00E9e au {1} \! +LabelToolBarNamed=Barrenno\u00F9 oustilho\u00F9 +ActionDown=Diskenn +LabelToolBarShowed=Diskouez +ActionToolBarProfil=Mererezh barrenno\u00F9 oustilho\u00F9\u2026 diff --git a/data/config/ToolBar_br_FR_gallo.properties b/data/config/ToolBar_br_FR_gallo.properties new file mode 100644 index 0000000..bbe3f6a --- /dev/null +++ b/data/config/ToolBar_br_FR_gallo.properties @@ -0,0 +1,9 @@ +#Produit automatiquement par BundleManager +#Sat Oct 03 19:49:15 CEST 2015 +NotCardinalPoint=FR {0} n''est pas un point cardinal \! +ActionUp=Aler olmont +AlreadyRegistredTool=FR Barre d\u00E9tachable {0} d\u00E9j\u00E0 enregistr\u00E9e au {1} \! +ActionDown=Aler olva +LabelToolBarNamed=Bare \u00E9s outis +LabelToolBarShowed=Amontr\u00EB +ActionToolBarProfil=Se chevi de la bare \u00E9s outis\u2026 diff --git a/data/config/ToolBar_en_AU.properties b/data/config/ToolBar_en_AU.properties new file mode 120000 index 0000000..bac78cd --- /dev/null +++ b/data/config/ToolBar_en_AU.properties @@ -0,0 +1 @@ +ToolBar_en_US.properties \ No newline at end of file diff --git a/data/config/ToolBar_en_BG.properties b/data/config/ToolBar_en_BG.properties new file mode 120000 index 0000000..bac78cd --- /dev/null +++ b/data/config/ToolBar_en_BG.properties @@ -0,0 +1 @@ +ToolBar_en_US.properties \ No newline at end of file diff --git a/data/config/ToolBar_en_CA.properties b/data/config/ToolBar_en_CA.properties new file mode 120000 index 0000000..bac78cd --- /dev/null +++ b/data/config/ToolBar_en_CA.properties @@ -0,0 +1 @@ +ToolBar_en_US.properties \ No newline at end of file diff --git a/data/config/ToolBar_en_GB.properties b/data/config/ToolBar_en_GB.properties new file mode 120000 index 0000000..bac78cd --- /dev/null +++ b/data/config/ToolBar_en_GB.properties @@ -0,0 +1 @@ +ToolBar_en_US.properties \ No newline at end of file diff --git a/data/config/ToolBar_en_IE.properties b/data/config/ToolBar_en_IE.properties new file mode 120000 index 0000000..bac78cd --- /dev/null +++ b/data/config/ToolBar_en_IE.properties @@ -0,0 +1 @@ +ToolBar_en_US.properties \ No newline at end of file diff --git a/data/config/ToolBar_en_IN.properties b/data/config/ToolBar_en_IN.properties new file mode 120000 index 0000000..bac78cd --- /dev/null +++ b/data/config/ToolBar_en_IN.properties @@ -0,0 +1 @@ +ToolBar_en_US.properties \ No newline at end of file diff --git a/data/config/ToolBar_en_MT.properties b/data/config/ToolBar_en_MT.properties new file mode 120000 index 0000000..bac78cd --- /dev/null +++ b/data/config/ToolBar_en_MT.properties @@ -0,0 +1 @@ +ToolBar_en_US.properties \ No newline at end of file diff --git a/data/config/ToolBar_en_NZ.properties b/data/config/ToolBar_en_NZ.properties new file mode 120000 index 0000000..bac78cd --- /dev/null +++ b/data/config/ToolBar_en_NZ.properties @@ -0,0 +1 @@ +ToolBar_en_US.properties \ No newline at end of file diff --git a/data/config/ToolBar_en_PH.properties b/data/config/ToolBar_en_PH.properties new file mode 120000 index 0000000..bac78cd --- /dev/null +++ b/data/config/ToolBar_en_PH.properties @@ -0,0 +1 @@ +ToolBar_en_US.properties \ No newline at end of file diff --git a/data/config/ToolBar_en_SG.properties b/data/config/ToolBar_en_SG.properties new file mode 120000 index 0000000..bac78cd --- /dev/null +++ b/data/config/ToolBar_en_SG.properties @@ -0,0 +1 @@ +ToolBar_en_US.properties \ No newline at end of file diff --git a/data/config/ToolBar_en_US.properties b/data/config/ToolBar_en_US.properties new file mode 100755 index 0000000..8160ed2 --- /dev/null +++ b/data/config/ToolBar_en_US.properties @@ -0,0 +1,9 @@ +#Produit automatiquement par BundleManager +#Fri Oct 02 10:48:46 CEST 2015 +NotCardinalPoint={0} is not a cardinal point\! +AlreadyRegistredTool=ToolBar {0} already regitered at {1}\! +ActionUp=Up +ActionDown=Down +LabelToolBarNamed=Tool bar +LabelToolBarShowed=Showed +ActionToolBarProfil=Tool bar managment\u2026 diff --git a/data/config/ToolBar_en_ZA.properties b/data/config/ToolBar_en_ZA.properties new file mode 120000 index 0000000..bac78cd --- /dev/null +++ b/data/config/ToolBar_en_ZA.properties @@ -0,0 +1 @@ +ToolBar_en_US.properties \ No newline at end of file diff --git a/data/config/ToolBar_es_ES.properties b/data/config/ToolBar_es_ES.properties new file mode 100644 index 0000000..ea29cbe --- /dev/null +++ b/data/config/ToolBar_es_ES.properties @@ -0,0 +1,9 @@ +#Produit automatiquement par BundleManager +#Sun Feb 04 16:01:44 CET 2018 +NotCardinalPoint={0} no es un punto cardinal \! +ActionUp=Arriba +AlreadyRegistredTool=Barra de herramientas {0} ya registrada en {1} \! +LabelToolBarNamed=Barra de herramientas +ActionDown=Abajo +LabelToolBarShowed=Ense\u00F1a +ActionToolBarProfil=Gesti\u00F3n de las barras de herramientas\u2026 diff --git a/data/config/ToolBar_fr_BE.properties b/data/config/ToolBar_fr_BE.properties new file mode 120000 index 0000000..7a113da --- /dev/null +++ b/data/config/ToolBar_fr_BE.properties @@ -0,0 +1 @@ +ToolBar_fr_FR.properties \ No newline at end of file diff --git a/data/config/ToolBar_fr_CA.properties b/data/config/ToolBar_fr_CA.properties new file mode 120000 index 0000000..7a113da --- /dev/null +++ b/data/config/ToolBar_fr_CA.properties @@ -0,0 +1 @@ +ToolBar_fr_FR.properties \ No newline at end of file diff --git a/data/config/ToolBar_fr_CH.properties b/data/config/ToolBar_fr_CH.properties new file mode 120000 index 0000000..7a113da --- /dev/null +++ b/data/config/ToolBar_fr_CH.properties @@ -0,0 +1 @@ +ToolBar_fr_FR.properties \ No newline at end of file diff --git a/data/config/ToolBar_fr_FR.properties b/data/config/ToolBar_fr_FR.properties new file mode 100755 index 0000000..c5f9b55 --- /dev/null +++ b/data/config/ToolBar_fr_FR.properties @@ -0,0 +1,9 @@ +#Produit automatiquement par BundleManager +#Fri Oct 02 10:48:46 CEST 2015 +NotCardinalPoint={0} n''est pas un point cardinal \! +AlreadyRegistredTool=Barre d\u00E9tachable {0} d\u00E9j\u00E0 enregistr\u00E9e au {1} \! +ActionUp=Monter +ActionDown=Descendre +LabelToolBarNamed=Barre d''outils +LabelToolBarShowed=Montr\u00E9e +ActionToolBarProfil=Gestion des barres d''outils \u2026 diff --git a/data/config/ToolBar_fr_LU.properties b/data/config/ToolBar_fr_LU.properties new file mode 120000 index 0000000..7a113da --- /dev/null +++ b/data/config/ToolBar_fr_LU.properties @@ -0,0 +1 @@ +ToolBar_fr_FR.properties \ No newline at end of file diff --git a/data/images/bundle/bundle.png b/data/images/bundle/bundle.png new file mode 100644 index 0000000000000000000000000000000000000000..829f1f9d73f55cb66a45118a52676a580dc72492 GIT binary patch literal 912 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 literal 0 HcmV?d00001 diff --git a/data/images/button/AddLocale.png b/data/images/button/AddLocale.png new file mode 100644 index 0000000000000000000000000000000000000000..71fb5e48ed73df1f86df29ec609c51265d9fb7cc GIT binary patch literal 605 zcmV-j0;2tiP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iXMz z6cZO>A8$nf00G@eL_t(I%cYXfN?Sn~#eXw53pdT8(qti62&9HSfiKV}a3M;_uA6of z$lIj51PNr3MJT?62tfqNPHk^VyyTCU%e{vC=X7Bbs*xgihQl!5oH_H&d>?G$80Z4m zK)mGjGIq>=@CCSRG#a|NxX^4ilkfYwzrUB~c{)EoSG`^raJkezy|q{@YB(G!2m*y+ zs3?jQhM|HWknj7NPN(ubPlLfgg+f8AYz*)T7`m=YKA-30_i-Et zV+_{Xb$M0?w%>09;Qnz*)0DHbGY$?8eypo-cXtOsr_;e0vo?^#(a{lw4+Q{jf87#> zA(cu6U~i?=${@=!j4>1oMP38D5wMnrhX-DZ+`o&Pn;W{_E>~Aq@DqS!x#c20^4NOj rxrv_f8}OM;)#G>}1H&6&c_aP;bm}T+1v0qh00000NkvXXu0mjfhiL>O literal 0 HcmV?d00001 diff --git a/data/images/button/Bad.png b/data/images/button/Bad.png new file mode 100644 index 0000000000000000000000000000000000000000..5196d433399591e4bdc6073f8330e903d420b0fa GIT binary patch literal 714 zcmV;*0yX`KP)ypVO?pVPv2K+D9LWRcsZQs`+Odr^PczR0AyM`TbVrn z2p?KocSb8K=Yu?-dR|oYbF{4NSJ-6w7_F+BXS0j}iMqP(M+U?4c+FuX8yoT2=fjuw zcBH&s#Hy979eghKma^am0n0hI&k0^k7ibtugPW zfN84M&(hDGLE`i&bc@1buS(Sv)ajg0^!hz9OYTyv)`dxr2Ql{vFfU`$0U`S4aA`57 zE}Tbaj`+kr#w}LT@T@UD>m)m9?dd(6BBxzT~VqS(1!GocvsD1*dbzqn_j>V#qz9;k=gvrCajM;RM9?;=vQ}b(~JOOcn5KC*x&`P&FTY w-(`+VHOul!i_y4pB`Yh%w8$2h>A$b@Z`Lt11j{z4y#N3J07*qoM6N<$f}?p$y8r+H literal 0 HcmV?d00001 diff --git a/data/images/button/Begin.png b/data/images/button/Begin.png new file mode 100644 index 0000000000000000000000000000000000000000..5f06866017668b4087d5e7e91da6fe2b1c0a919e GIT binary patch literal 584 zcmV-O0=NB%P)!Jnn19gmA9Ux!z0k$rq7-o< z6hV+KR48#Y)mCjXT};4aGBel3L~Q573lARmo_p>+@7>Ef57z3jx|Ec4AXuB z;F;@KK|01616zG`AB?dm9ZZs+t^rsF!1LlV&pE9sgd(3e>XlXRXc!maa#;=2)`XOs zS1-38-&tAI7y}g%WI8ZRyD6l2wYbn(xY%wfQ9OZKl58{%eZ#cVa7q)*aVfTLT%SMF zI^B@HUp{JO1^4c*aIk;fG5}F9!pp69Cp+MRZ{NCkZDw}1#=jj75yb@0AH(yfd^#YEvsUJ=TK>{I7!3Me7{`QZauYx`b;`Mu=sMP}VcJUvKMvna!l;F0;Kq4- zk~@i0k{*bb`hD*!08xbFM4*iqn;@bL$aSorAX)~nT|~g>e}6SK!?Y^^e!Gs9S*kxL WAgn^rV?cEP00008Tdf} literal 0 HcmV?d00001 diff --git a/data/images/button/Black.png b/data/images/button/Black.png new file mode 100644 index 0000000000000000000000000000000000000000..b0974cb55064a72c159a3da7a5ef9b1922d16195 GIT binary patch literal 437 zcmV;m0ZRUfP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2igS= z5dtQvkwcOI00A{gL_t(I%dJu|Zi7G&+_Qpp=YopDCE_gj07$?WKsX;rg&RKLC%B<= z%O}#gI z0G7V*AEcD9Z5u%l8~`k56V!FRab5RJL?9x#u6wTQdNY>07z=uyCxj42?+YOe&-28r z?n)^mNs=Sy97NO+QO7w)k|al^l<}0nah#QDnx}sJ-b~XxIgYb>xraQ*iWAQ-37(<$-yGaq-wk-i*;)^o?Xxp}&oPcxwG1B>)-Es!J+&fz9HzMM{ f>7=#(r2oVh(BOjq;D&D`00000NkvXXu0mjf@ng4V literal 0 HcmV?d00001 diff --git a/data/images/button/BugReport.png b/data/images/button/BugReport.png new file mode 100644 index 0000000000000000000000000000000000000000..44c7ae1ed3140738c15fb87880d3bdff0418e552 GIT binary patch literal 733 zcmV<30wVp1P)Y`MskBx%3DsDvZFA;R(PTZ9i7sXN#H%cK3+iqH+jSp&K+6LMrGqID$T(8jJI9c=u zXEA5K^UXOQ3>ahol_-h|021tTLj0W^>w1upQw86rg273D;O(h`6b z_@D?-E|)*MsycWBxYjPq$NG7-0q2<@^g5371pz2pVN{l5y%VXYRzgZQI!L-}ggnwT ztsPDFF~Ios?E!e@VW(!ArK(}>*rD5ZM&+Xb$i4=us#6YZw6f#L=f~)&#Z#f>$p{~ zONW8bU+upQe53(84{WwqtA{+sP`7R5D-L!<5n2A6(zLA{P(_oV0ncG@j^XDiZZNsK z3mZU+e>ufZDpHE6%I}<_D4hV-TMYKXyqs}slQXf{S+`I?m=Hu6LygzVWD-X9*DMeE zwBLaGhDjokQ0FS;7k!28_uu%?3a^gDEsaU~GX3SIsdMei7bh)})pLEh`3!Xi^UM4W+LIZr$zxaOz7mw|A28X|&gK2(h zZfSoyEkX7Ar7SOshe$kJ@wtDv=f<5!bjpH@N4L9nR{6W;bYuBbX#H42w#$q|FGoYkR7TQxAVVC~AgvlcDbQ{K}v zz)cqHtE#WH+tMnPL>x$^K&cL@=%%l{d{cK)X)1KPX3v;gl4lwwgdERi@@M$WfsL{? zP3)eLTi{~|fOrH9%Ru@V9LpV0qngA+;sn5whQ6+jjvrr!>i^mx(;;26*la$T(-0mV zM6EV|5;_BFz$$UjH5GQd1)8dx zW$%Zmk#1*hIjlV+=?;EPa9k=7g`$b#;sWMW{kp}nRc!428shS@9j29Ip{OZjyKMj{ zG9;eH^E^zu)Bdikvh?_!mu@q(n3*v73Z?HG|HJ$KtcH9J10IQl4im gAunrc0{^W1Pl-A4;4vhDMBKPEn(x^Ha2x@ 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 M07*qoM6N<$f^AW`tN;K2 literal 0 HcmV?d00001 diff --git a/data/images/button/Cut.png b/data/images/button/Cut.png new file mode 100644 index 0000000000000000000000000000000000000000..f70287454424544dccd7b27b22ed34a107563509 GIT binary patch literal 891 zcmV->1BCpEP)n;TYB;0DMnA>>oYT?pXQyM~MF3y`KtyeBw|gXdCi2dc;Ak}R zzQ^boKO}S7WDM|1VgLh2QpgwyOS@0#ZXmqgfN^da<&q6%g;^-VsBudQk}FXBWlDe z5o>X|-D3mZ0r^4ZZ&Ym?@u`Gfu2zo$fB*p3u3lT%z30W;gKXB@(cU4MMj2bSc*NP+ znIlb4HNQ4E5RjJhS(GXj{Cwj^F`v)AcYJtcvL*n4tKa`{`-NT4|CvfJyu5DhdMO^i zNe2%eVyVC8Su!~XSy8O(zg*XI`9kpc@W|zd96gq_dU7+9m7lm}Z#a7RO>oZf)!0P{ zf^g3$TfR^zlzMcZ0APZ#cRBD0=iCWEv_(-;&YeZY>40U~Ff|Qz9~=X!RRDlMw8Du_ z4B7Ts8V3$EHZ_gy*txT@uD%{j5RgtL`SkQO-4A`ZfHiC6^(RK#`a>b^n3*A17K+6p zZpGuP+Vb*S_?EHRrEr+v>h9JjB&l>sQTR$Q$j1TuBCz(7!?A9R$X*Es`M4LNh9rS1$t@0NBs@?OZN*#O-o5_HEmSmgZ*px4U=s zQmGWC9|1ALmWB01IJv;yetqEeV$C3InA>+|2UZ}6>6suzB|&z{4N=N%gX@I}T1F(JdY`AwEd z`(^-ZnN^IRZ@=%b|App3d9uzle!R*2t-|za8|-(}qgAmDETzwJ3K zx*Y{wKW+}p;F;|UMyDOHOgT9IijL7aWL~v66LqFWQQ|8IORm#~=d`1JWDZSFW>Iy2 z8kbEUMO<-X&n%2D=8;r4gz!_hs{zO)0qrW>5^$hlv9i+!&7BFT+a{nhzQ>YN5aHOB z6KgdOrVyNF_#To}yUinzYrEsidXQ&$jhySRM2fljm#{3tv6Ro{9+hqYw7=Hm5h%2F zRpkKEuZ($-e`6eH4b~O;lv*=L)nFHADe_z>3xovSAQJV@CH_1IYlb;DEY(u$U4nXn zBF~2sMQGNOTq8uOd!a1tb5lM9#U-;Oh^ZRm&m)K^?8Am^BLY*~KyiA3wiblBpAfBa zP+~J=hi)M#-H0{WkFhSd9}5c$B6M;Pl+_P9Y62bU1f{fqV#*z!?_jsuN(qgiw02O& zBhU#R$R6?tPFp~^V`5&cCmTRf>T!=CTB9I&iIWa@fR6Qw13c&j6})weGP*(XQYQ

>no6;o$C1DJtd!l~oY{pn0dEV*=Vq zOz12ueFG93!p|1Qf*2bUqplzVDr6vT#aOsAfkkP#$>g57_nvcS4y-(caFT!mln~Bt zP%~#{Zk86;8~{}=6;(c$Re;LpvU3Z{r6Ri4s8%#qjcVnWQ>$L9Ue_}Mp%b4K>vcUN zpgDG@?aghhw_NHim-o*eKr_eyM57xEI1=_`e{XJSVrOf6sfYH{;{*2Ex+&yySqrfG zy^eKqeCQ7PUE{%Ys?40mwv>{|SmOKN04BT58sVPz`eFn$j!Y&KKKfnLD4d=vfaF06 zAaPVUb9L|ju0i$_AQE$Nc%~Pg;hgh=WK*<=0DuhRXm1EWRugy;^Q?O~5*wi- z=AdQMLgTiP`aM0dFC$JXWCh|{pzg{*Q>LHTl^@pxL+G$d*4OBLv^;k+NB^atjU#z}M9Oe8_%D%@2uob?nD zRSf?cB7BH=_r0Z1Hbhs-MN85`;XtTLF2K%|z9cLw7yoQQ^ot4Kk+0-FadY5sfXX!` zu?1@4+G6{vI#;(wtU#s-YaT}9qb1vpjiMy7ZKe zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00NguL_t(I%cYY|OKV3M$A9y3 zX7Wx&Ltaoqp_Ci&3KtDUu&drhbyKqtT0s{s-4^^F?Pn;|T}u~&+%EhMO2m~F%tWu(Q>9W#EiEmLkB*Lh9S(;-CrOex=NOO2 z!a3&-4-bFrbUKrVhX?=i^5RxjR#FXcd3pI&p-|W_6bkR-IIgr>t?y4xP8w;N5(EKS zYm`#7TCKmGb8pt#S8MI3^YimR^t6HRthK)ti$#=DR4NsO5GbXx15hj$Kjv~d0HP?` z1^$w!r>6>dXRSqRjg%4qr4(9gwAKVc06-7~NGXw0qO~TDy2=-e#ebxfACHfZxwyDsGMNy^F;Yr`ARq_= zoO28Y1BSyP2L}giY;16Qd;62j<#JL8@os*8p6%^zYPA}J!5|BsRV$avR4Ns^-7f3v z>p15q6be-tMUmB7FQ#eA;^HEEdwVn*jSQaF8eidjL9@fQ!AxDIOmv5CL~Ehk|a3isMqVPuC8)@ea+_PCQ%e&t;HCF zF$O6mLWr6;J3IUL?(Xh~e!tIjZAp@Z@pz1L4(~n27_`=uN+s&`I#NpV`8*2?3;*db z3_tXGJ#KDpvL*7~XP{e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00PfRL_t(I%cYaMuas33#ee7Q zea`vj!5ywJ_XR#K5Hc|!BB%j17Frk|p%4>a!5Bk~RwjhP%F0R$v@uo|n%J0VVbny3 z1;)|`8Xwoh2XWwrfqR+Z8)oKv9g9K#0d}+YN>vSa1Y;ph2TxmDeQZDd?36(4_z+MLRDfs@4^jh73|gBSsC9^d zwFc*eG#$5fZgX3lh8Zl0WB%9afhPs3h zI%&MX`)ki~=Il3cRZ;6P9wZnfKr&-F_SGHaJ>}@W6WqV^aYCY-Y?36_3Y|H>%;DJ+ zYzZBP=PX91lO+N1hQ@-Wbwh*0yN`44_Cw^wghsvAwu38aP|>XgCCm0sp4;{g^E+B- zt@=N{YZ%tMBM#^bJa+3#M5cQXktN&ftuH&L6vc#LmU0vOIeO!J_!*E8adpG`>X3uA z=Xv6`bVEjE?pwebMm|(&olC(q^KrDB5D%UDa}Tcg{4JoD5(1xHgDYYmv+0Wdw>1y zXU_ipC)U_&hZ%{0!4Tt=L&W(&3={rIPP1#~VXpMY3+9bC-+iq;H~SRCGFH)405O_K xG(?zUG%;d~H8`hbv_-VhX|LD+@Or)|Fi%A002ovPDHLkV1g{Tl_CHD literal 0 HcmV?d00001 diff --git a/data/images/button/DrawAgreement.png b/data/images/button/DrawAgreement.png new file mode 100644 index 0000000000000000000000000000000000000000..9a6f857b86964c30823052bb28460cd7bd37aa16 GIT binary patch literal 891 zcmV->1BCpEP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2igS? z5fB1ip!Ylg00R3-L_t(I%axK(NE>Mw#-Gng%$PdFF^a^AcvGoV3>&jziV6z4Ld8{b zP{Gzhskaj8Vq92RH!2<~6#w0dfkF`jf(s14^iN{r&y#_xAR> z@VzWAFTVnydcEEk|98jS7EVk|Je6hnH@#j@d_La~PN(zT&dyHwR>9`xrseSP@VUWY zc+4=2b#88s1cSjpW@cuL$z+mjY-}(L!Ca@fKp1y<>e*j=jU;8aY4(<%81YBqfV!jOioTBl}gckK9ADUQnIqLg0r); zUl0riDWz0ZRh53r;NT$b=;)w+zn`)!O96a4>e|{G9U2;%DTqWO002VBjdI*>H(FX+ zFgrVoY&QF!@{C3!>~_1t7z~ErYinysNl6KnWtm8lgiI!bWHO0xIE+vz1dT=mmSv%; zD%I(9WMN@}hsw&zhn=0Be-Qu}9UXn9D9X#ezCKh`R6tP_Fbo5~-;Y2b0IgOFqtS@Q z#zyG%`hQ}vSjW=RQW5|N05}|u!RqSjH+6M&$Y!%hrBaY(863xfVHik~^fj~DY;T8$ zho4kcRmA~70{}Xmj!h&INT<_O5Cp>WJj%<$*$lJUjGCGncsw4uzP?U)o(Idaq^YS1?d|QaEf&ki8-lB=t7sq) zXkJ}ieH03X?nWY!dpytI7e$e@wY4eR+uJJx0|TF$o16bEE-v09gwTHlp8=VMSN;PD R!B_wQ002ovPDHLkV1obFiq8N5 literal 0 HcmV?d00001 diff --git a/data/images/button/DrawReject.png b/data/images/button/DrawReject.png new file mode 100644 index 0000000000000000000000000000000000000000..41c166328fba0ecedf4ddef1d3ec50c07858449b GIT binary patch literal 910 zcmV;919AL`P)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2igS{ z3?3hV1$KG>00Rz5L_t(I%axNcNF#X^#=rk$V$3)hCB~fuaX}4d0!FXc6alewQ(UUU zRO$(O5VSj35Km6OJfi%Y-GWlf+Q9(9+*NDlt>8ZOcGR_i8Jr275A{Y zAH3qhdmp@y_W}Qdg@uJ!AP^uy5X@*adIA8Jw+4xb^ZWhZOOo^{91c&oT&@oj6BF#{ z=;-&u!^75kz0R(#t{m}r-1?2c_4PG-czCcLA0La2jg5cz_V$L0#bR$L6bkwM{*Sx6 zyYGv|A_oA5VepYiUqhi#+G4SEOifMU>FJ4hp11gXKF{LfB4V)^ zou8j0kw|d+`}>X;fyc+kANKb4RxU3u-CnP^MUo`k-Q5uY1^}5%2GM8~TU%Q!olYa0 z&0>9h{oC7DR#yHR8XD3QiNxo%wKW1@0-)Cw4u>h5&A#+69?ZFj0PuJ`h{xjy1OnhV4%KQEs;Yt!5qr;U- z1|q`n@GxXq1_0RYcDP(FfL?KNaY3V_qx4@)(HPx#24YJ`L;(K){{a7>y{D4^000SaNLh0L02{9W02{9XUK)`c00007bV*G`2iOcA z5F{<{p6BcU00Qz!L_t(|+Le)MNK^q3$Nz8d_uRGB)pgCyTzB)>QY;I%L%g7(C_4yI zOi;=~v52(8LW3@%FNH{{2_bZRNW;`JG^El(Q!_R1yv;TD&^=oBZS5<9qVS8&r(tG( zKbRQ;{~5RW2zOboxI@Wg)s&~_<^O`~D$a?`b&|onl>(-S%VUa2vWB%(_+Ae1T^ReI zpDIz6yM;g-#|PV$c3<)e`WOC(N54iEPay$bG8x#D`9}S4RoIeX?KAlrdr!vKxTr8D zM6G0+t1_Im&l^$}4Q8$95Lw}D!QZu!a>~Wgm+>(_dmppHSIIoR6~gpZ1mF1G=lo9c z_NdjQSXQG9Q2TD#OW%G|UYvRJJ>ad{UeM41^}~vGXykT$9+;i)thZfUByg9>&#c=N z?YHoa1=?oL;7F83<|bjN(L$wWUwI9Gu^v8r60M&Cml?(ft^@OF{|HtGt5&RwhydF~ z0<%sF+jQktE|1DfKR*bE)r83SC>+Z?0~Uvmyc0))(b4{k`}0PLE7j$s7vQv8;j|f< zsExCyua?oG_8vV>CNW6du?uXT@TY)_l*Ii;0RIZ!HNCnxH`Px&bYl?M%ZQdwLXXlX zCLD;eBXkwJ>&sYQslzf2yM7ckb=4mh3F_J=bTc--p{?^J^!gTL7ZOk{w<96iW;NP=ZHIZr*RLZCZ#DAK1ZbzQr6u@p>tjg5m}Do@dlV3F$2Dl zrz*wPK7WDO115_VYgMb#uibyNRr{(Wmy>)j`GRIsc&#on-S8x4SMHd~42mR15hFIp3O*tFna z4Udga);Bh`-kmeqj58LNRcm5Qd})x@D|mBv|KLRB07*qo IM6N<$f@lzw`~Uy| literal 0 HcmV?d00001 diff --git a/data/images/button/EditLogin.png b/data/images/button/EditLogin.png new file mode 100644 index 0000000000000000000000000000000000000000..1188fcaa202c742edf9af368ef392735739d7702 GIT binary patch literal 914 zcmV;D18w|?P)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01ejw01ejxLMWSf00007bV*G`2iOcA z5G4;I<(EkS00R<9L_t(|+I>=MNRwe0J>TZ$_U+r$Iq!3}yii^m)vA?gS%{RE3M-iu z7G2Du{$v+JqRXEkO6o_@pC~K}gzgz)%gV5;rb1=+n51^urhap}eV4acBrBx@FE0n4 zb2vQDq3{o^OEMKx_2EY~DrHGTR5a%kap7`9bM=h}Z9Yr@X&}j21XzqSPXMWqLde)qydWK>CTH?Fz zUSct&yi1H`)=>gSfggd7b&$rE&hq2u8||ZFt9A9>jn=_~?`ylVOu_QFaf`Xw@ZjO= zk6%9dTs`nQ>}c!ka$K`LeljZFvVz{dsW^UrSazK}c88IOp(Fe_17{y6%A=B9t#%KZ z+U<~r#11vLzQvZ6+C7`s7apkCcUjimdW?jnm7hP>&21aILUz_NN&;I+H-sQd7wBN+Yq=RgzEkANIWBoyxH@E?b(D5f>9-`9&xSq9|e% zwp(Wr#-70XBS9$39Ylt?5NQkcL!+VNHMfO z-poNs_IIS43otY92>kvaqJSwW#=NmA-)nwR%k!8NsSC-CjL^g~3@z@gCYX(6Ea{!j@$4h?f?J)07*qoM6N<$g6p55{{R30 literal 0 HcmV?d00001 diff --git a/data/images/button/Empty.png b/data/images/button/Empty.png new file mode 100644 index 0000000000000000000000000000000000000000..36ce14064409beca38e66d8604eea3776a8eba0e GIT binary patch literal 774 zcmV+h1Nr=kP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FZT01FZU(%pXi00007bV*G`2iO7u z77sVqOJ?N&0013yMObu0Z*6U5Zgc=ca%Ew3Wn>_CX>@2HRA^-&M@dak?_?!z0007A zNklvqpLJ+uAc_vp?jEVHoLc3@y)pn;LGU|^zPEP|2$fd7CR zFDpB+1_~P($bx9P8wi3e7>JI}IA2c~=k@D%s|?=j9t8_7_j0K^oO9|3!1CYsFNko* z%?~?(JB~5c*rD56vH9+cm2PYBak}w2lQ6ZSI{R1~-DeuxA0pMj-E(KpAMJK}jLXUm zEPu{0`&UOh|1a9;?c!+YXzY9eKP5V|ESCQf0m`yG+G%%L-&mteC1yFAUeU-df)4?s z^Xmox+pqS|q%!-h$luz-YU0OpS4l&K_H32BF5Z@^31OYaf927=Dg3Jzqi7Zm(V z1aLEONU0>H60w{h8Q}H#VF@B91Av(UP^MAEA{pjNbM`dZ;5dj(rraSL9Ajp#KN5*k zVJx8nFj7jC)}XL?Jj??D(Sv(jL_n0F?Oh1#VIpA+?_koTuz7O-9Ypc8Nj6v@8!R9a z0fjL2QY1W!-%1b>Bm{ye&Jl9&vv2M;+k+EKcP{}>_Ii=E-OD~>>wjuU&uf56f~pcE zfPfG&7RY^;_|pTDZy`YM#1)=Adg|@g?mvfmpFh+dSPc+SRbqXsP9u#YB>ja^=rhtj zH2ZwoYWdUumR958hxy(b1!7Q4LT$QGT@h#kUj4{ElELa`%K#=41PDQqx>3fflNxC3 zkqBmv*R1271VL55Ypk+Tv6-YtD$~7QeFTUa-*0Yh9P9NCeB1iN_-$`*5u>gShsAIL zP!z@0zn1UcD~jUhy~K?p>`b0GF*e_&R9?Gr{iXwa1sxY9i6+BW?*IS*07*qoM6N<$ Eg02!$h5!Hn literal 0 HcmV?d00001 diff --git a/data/images/button/End.png b/data/images/button/End.png new file mode 100644 index 0000000000000000000000000000000000000000..628c7ee448c1d3855b66c8dfeb6de5ecb12716de GIT binary patch literal 564 zcmV-40?Yl0P)K(FT<)i7{)o(cOJLh+#L~)d${T;CY|l%*zZTA|y#jwV-Y5U5eKZ8Hwpj zy%lek-V3Q%k~ur~`bGfjzm=OeIsYi6>Pl<64uBWW9`=lm7NS~ z8YVY*7!pdw!7gr^;~Sg5*MwA)+O`g2007zPmruCaodsBATm~GwE!3MXh6nmM&lsik z??1l@sU|xC2nZ-2o`53x=;QpYxESm!WG2QR^slaOt%QCM2l#$~M$-epz`y`prCEFZ zcWJbN}Jk zZDII!4qmf~?VY_-5X}Ji90kzV)y)7M;u(WIdOT)yn;a+g2I<|h3(zZVTPs4U*a-Rd zuo3w|*f{q?qgJbjM-peb!z8~+`oZL5_uvycjhM^4~Ws#Op@*S7U5sqfmK=v1l) zAbOe>z6r1fq6uwV`$^`99Gevgd!g*Nb0FK|BfDdD2R$E6QR;d)5L76nQrQ4WA3;8?(HF*R<<7c!hw4apZmS% z91i@a#P&;b+mXkXW%&UN!+7#~Z1iZZJbZ(yn`Tv#wqk^W^cAc>^ zXsv&`3o#(2v}~pBDy0CBrsUP=i|jkOd1yFvxV56ZwC{ZHX?&Fxn9^WzDblI64xBjN zC9e$j)6(3?+jmps7r7rUef=5)5KXOZmu;o)I9)D%@xnP*C>UgVdWsKoLF)I{kxV9; zUkGvg))3+FA`72F+`Iq4vTYj!F<>gKhp!IyJ8NpHnVFd-9*<>PI@nN8BobkMAp}4o zk-#t@(AnWkr7RvjeLig1ww<4s=OPkaW_5KX+yCFR99d>=?jr(?);iw=ux%U1);O|y zKRrt#k=W5Fok;^=8p22=lN&CNXD`LYCB$OuXst=56>lb|Yyot2`el*3h?3$G;<0tm zklIKgASD{x+pj4rbA5->m0=h+q?D<2I(?Jhb_PHQF(gaN%J%H@`FeWKoOC)Q>~c9+ zi^aby#o3ARmm}d&@R`S3F3n nHcb<)^)CT1F+L^$axp&u&WJj+;Ii-n00000NkvXXu0mjfA0{k- literal 0 HcmV?d00001 diff --git a/data/images/button/ForcePack.png b/data/images/button/ForcePack.png new file mode 100644 index 0000000000000000000000000000000000000000..6dea0c2a670a9f1aee538738c17d4ec5c41a24c9 GIT binary patch literal 509 zcmV71MgRZ*WRTNHiReX&=P-ZpFM#kbfbW}}qoARuq@}B*rL3x} zu&=SSud%keyTQc8%OH2}lBdOppvM$$?h|kC3TW;>i0{~vbn zV4n44qW@*0|8bMXcdYl1yZNQb`Logbyw>^A-^#B_u}mOHK_2=j3>gww2>+9|9?e6aG@9*#M@bL2T^7Hfa^z`)g_4W4l_x=6-|NsAqw$da3 z000_vQchC<7cn}YqN&Bj#l^+N#l^+U($?VN>-zrw{+d^Xr2qf`k4Z#9RCwA=%IOxt zFc5~}39408?KGrA?K@Qjx&Paeob+_O^J~8IKpX>Q9?k>w(Zf12WSr}@HOkW4^)hTQ zoniP{9`9-PG6RAU-^1c8<(q_3r(`c5B$QIS`S$rIbeq|l#MmsWX}j#?B53-qSL+Na zp%u(lawgqe*r6Ks`h}qMF9gHlgaC*lpy;7~B8v~IFbPCj00000NkvXXu0mjfW04hQ literal 0 HcmV?d00001 diff --git a/data/images/button/Good.png b/data/images/button/Good.png new file mode 100644 index 0000000000000000000000000000000000000000..790f5e14569f7f316e1d3560b48a7c843e698e6f GIT binary patch literal 535 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl482JKxLR|m<|IgsZz~ITi5X!*d z!@%Irz~IXO5&@FlFa}T@$OV!>aUTYz04A1T79e5{VrKGV0wG@}CJ!ctXa?aFA;xG% zz6>7mG*R(PQPFH6!E8Rh3~tVNcA;zmff!ziSP}6sL8%ZSnE*kV01=%QW%pftUcCms zwVHlSx_;e;0c*5EnsvhuNklAgik$BlJ;y$FlWxk!fRtTE#amL!wkDVDi7Y=5TDc>! zdUsm=fsFbSrcK9On-6()9IEQv>)m;*y6a$g*TLw%BME)SUHVVPOxT+};b8v6qnQ)$ z*-buHF!@B*w8J?wuDZ{>&_DCigjpvtW}U8{dop*~h4O8$7HYnEzXBLQ+9g4L!3?~7 z64J7YDn`a8re<#0IYqq_CQh9-cfsO4*KXW?_Wb4Rw_m@0`~LIiuV4Ro`3`&pYU}lM zaSW-rHTA5k(4hbU*Newwo7meQ$lZx$Vk!T&UocLzd4l8|$Lss=Hv89KH1LYdPQKgl z=(pV7{cLtj(RVsm_Q|Z9zE)m8`(0%51g$;MXI%XHT|Zp2ycWCgYDR^Z1jifJgfx~UoE|*^7qBsH|Ha_wCJXtQAxgd@%+8IgTe~DWM4f3Omk! literal 0 HcmV?d00001 diff --git a/data/images/button/Help.png b/data/images/button/Help.png new file mode 100644 index 0000000000000000000000000000000000000000..854249599fadb46c4af63d2f54fe3a5122caabc5 GIT binary patch literal 916 zcmV;F18e+=P)&pmx|FwBeq;7 z*2Ki5h?+hjqVYj9eet0|#xx;JXiQ8*F=K3O2Q4X~)Lx)qxR{Ami__AOmScMHoZ+0q z%sKO)?Nf=tuE(AIt)1+Z#js9&%}smlsvWPv@89QSvjG55JRTK>G2;F0%2$VnzPq@d z0RV)Bn@MQY}=+xCPSL`4<*iw$Zxik%7MDz<#0Icz0Yv~h{a-} zIDYcR=9-%7mb2r$mfc3XD?Z}@? z{L&I17WUDjz|s=3MMao1jqI<_)k^?)`sWK`=Ocw4p{k&=GKiLD{BZDfY3_XL8NOOo z)xIqf;d1dhD4B!|1TcPfjFMUV_=+%#R)kq}J(E5@HXJ8eQUaQv2kAPVjPx+GvhpoN zj`b|wy?dK9?H;AnWg2UG#f~;Me7^q4(H9#(8#}N+Yux^uH0>U#>Me>K>sjQE?F4dj z^FTxp`T3aNTj#2;Hhi+)Kz+IP$b8+NtnlZ9h>(}(gJU}Z4gf?1#yB`<4~+P}7-Qg^ zgE77~_xBDXi2#idA>R(8{8|;hnOm8n(K>?P8aB0Q(Q8aHT zSk4T5@%HAD5+Yc>+IK-}9z!RI0aU58d3Cl9Dnoa{kfN$T9#Pl?hZCV literal 0 HcmV?d00001 diff --git a/data/images/button/Human.png b/data/images/button/Human.png new file mode 100644 index 0000000000000000000000000000000000000000..b50ec42a5e86f71478526a925209af02c6dcaa7e GIT binary patch literal 636 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 literal 0 HcmV?d00001 diff --git a/data/images/button/LeftGroup.png b/data/images/button/LeftGroup.png new file mode 100644 index 0000000000000000000000000000000000000000..bfea85d3f7051b9950040105cbf14ccef10fd4dc GIT binary patch literal 846 zcmV-U1F`&xP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L02{9W02{9XUK)`c00007bV*G`2iOcA z5F{5Vl0IGl00PcQL_t(|+I^90NK6oaW3ZExRF9>c>p+OJJo^ zQXr;52oVwyB?OVBAEb+5P|=4lkjRimSz=VC)Ml4y%e&3%+){Jf**T|IT9AqU;Nju@ z|9KwX|2bHR>p*lh@QU1sf2aX;*MOJg$sky)Kp=2L3G5}cHuy{KPNp@423zM=pZ2Y=hb-%tn&h&0fdnF z0ATC>KQop+p?tfHDNo~+@Y4?H4^r>t~pjzy&FK$CTS1BqA$?N@eg98WnSPTY84x?PKBg%$AM? z%R_W7-pEk+nSncDSIGMU6V#EzL9uXlcI)zo-DLnseoVEct#dukrG=4?(xjzDsx8gR z?Ewl%+^Idm8>%XDq`B|y()?hjbS8VVwGWUj`37M@>Tc2NhUl3C>zVa({I3s2;66!+ zh>UU-9?0dn01Y^q42O>9_Za}+l7jcI+b%grd%V8sehl@RAD7j?9fqHCjXe`HBmKAe zIXgVl186ZlF)#ZIrmo?s@dbsW!)(R0TRw#Un3}YAy6i(3v{}*B@kRqbOm=Ru{P?lM z#qn{`CE=lp)KS|f#BJKeyPVGEFBVIUcHWv2laf)UowXX|L2J1mL;YyFd&8T5v2kFm zt-exLP*`wbcY0c-R->Rg21#Mjbg7Z{sB6E2gm{5HpSQ`yco7KRCxRuH{;o!4f&i8YUJtLxLDW()$HF0~5V0i_Kb_N1s??E8U47HZ zjYfS=pjw$ANm8gFN-WDT9?Fl;xMnTA=FYBIgJGva5fI05j8+sSf%gd$jwy%5{I28C YZ*CeAH1atXyZ`_I07*qoM6N<$f;sqtzyJUM literal 0 HcmV?d00001 diff --git a/data/images/button/Licence.png b/data/images/button/Licence.png new file mode 100644 index 0000000000000000000000000000000000000000..6a3f5a75fcad74625f806c492e06e260eb26c609 GIT binary patch literal 385 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_f1s;*b z3=G^tAk28_ZrvZCAbW|YuPgg~78y21^@{vWstgQ_e4Z|jAr`04Uf!tZ5-7m-KtJNF z>!KUSvNBZS4z_XeKVZDWA(zH|GHxyVhQJpSG(2q@xHfWf3URhNZMwZCEw89}^Yonb zSu>6Q&-wo+{hZ9d)oT|thkrR#e&J8{V~blalH@krSUszm+b8sk(!t8BGEXMYXq{y8Cs_CK>bGkcl7WS1a%o_+_; z2OmE;634&YhfE5hN>g=lAIXz#v{q`-<=5>^h~w8^_U z0)8LMg4Mj!xlQ_ac%*r3__EyH$fMjwqJ zZ+*82b?xuN5GnTV+KQFUbM59`o=s>uAP!`e6UUDN5RGf^T2h$kb94v0^rV_7(&-EO_SO9RMFsSe-DGd_tL$)gQ43oVpj(; z&iq6gI1gBgs+O<+?uVaZJqKUhL!rdha0`|G3KrDQ0)v1f>$DN8*d4Cw~K2y=oPfWfdgGfl1)rg7z&SS@=q6 zv979?*SoIUAj|S~0NZxnvn=}|U;%RrV0!U4{tu^0LqKmJ60-mR002ovPDHLkV1f;A BkR|{C literal 0 HcmV?d00001 diff --git a/data/images/button/LocalRemoveOff.png b/data/images/button/LocalRemoveOff.png new file mode 100644 index 0000000000000000000000000000000000000000..3a5110c0edee5d60890ff0f73eab7ced09635880 GIT binary patch literal 777 zcmV+k1NQuhP)_ zZf>owuTMFuDJKE!@9*CZhr_Am<>lbN0gH=^mkA-AQpy*{>Hh>wOiY9}H#eV3DZeL^ z$#=))Xn!;J_xG!ev3suT_7#i8x5;EOIB^GwM8X;v82HrE(lX_`E=r|RwQbu6#cbO? zGdMWd4*=>>b9+0`-F@v%Boet=E|(#MfUfHi$8jQ+N@Xb&3N`ff^t|fm=(xGFv-9H* z!071X;ZP{_5CAym;GDzv{r#qj(B04Qd&+0jfU;~k7JH8nL3;J#&9a2yAmbC{+HN-5UY*Bczip{lA9 z-}j+u8l#lPR8?J40bp`+^15YN&pgjlf*=563_=K)rU}luEEEcXVHk?<`(GJjRh3GG z8HRB_8jYS+Mn*<18HVv-V`D>WY;2U2QgF@@4u=5%x~>QH_4SIB@>_3j?;}FUZN^yM zG)?4kxi5-s+a&-&EEbbeN>EBGH8nNRbsd_f!8FY^*L7cab#)~y%Sy-N@z0#|;dDAZ zo=T-=$@KK}DIvraLP(qt(n=|9)-)|DgfOI(WdJXwluv{Z`IVIwv9Pdk=DEsaU~GX3SIsdMei7bh)})pLEh`3!Xi^UM4W+LIZr$zxaOz7mw|A28X|&gK2(h zZfSoyEkX7Ar7SOshe$kJ@wtDv=f<5!bjpH@N4L9nR{6W;bYuBbX#H42w#$q|FGoYkR7TQxAVVC~AgvlcDbQ{K}v zz)cqHtE#WH+tMnPL>x$^K&cL@=%%l{d{cK)X)1KPX3v;gl4lwwgdERi@@M$WfsL{? zP3)eLTi{~|fOrH9%Ru@V9LpV0qngA+;sn5whQ6+jjvrr!>i^mx(;;26*la$T(-0mV zM6EV|5;_BFz$$UjH5GQd1)8dx zW$%Zmk#1*hIjlV+=?;EPa9k=7g`$b#;sWMW{kp}nRc!428shS@9j29Ip{OZjyKMj{ zG9;eH^E^zu)Bdikvh?_!mu@q(n3*v73Z?HG|HJ$KtcH9J10IQl4im gAunrc0{^W1Pl-U0=YpJ z6o>*O-h+lWprAs$LOM_iIuZ(m0!S!A5r`m!gBOA0BAeKW*N)`%cxLC$ow+PeK;cjN ziX)v<9^sthJV?%o*!uYH+TM74PX^K7z7BuaVXzy8VY}0AwBx`}l=RkPa_iflAB-PPx1HDgRL#Z=6@|3uhu-vxbDYiB`gwpZP z(zBOH--#i zhbKKA9U1b{QkMo0FUTN_yM7QNMa`ophnPI$=ACWsj!$^$N}r7@7y0D#Uq}nkzT!9+ zXr&QCER-us$F0utN<>-Y*xKO*4MwXS&P0QIe>~x>j~+5e)_8xUnYs?&{q!eNMd&bM zZt8|=#$of|XomJ9LM8Zqr{LY!nrvO`a6AeabR(YUg4FMGmr=2+o~w* zSYdFLhjAX(mVEu=4!@0C3@>z<&K2*y^EzMM{vq3aa6iRS;r9X;_Cs_pP=m%ZQZ)0n7A!DIdwEzGCb4f%&RCwCW zlS?Q>VHk#=|ICC@lOZIzEL;~XY}CX~OjBoN&5&ZFM#;i%SXdxgku)R6za^tpbqer^nbbZCEYVl&4-_ zT~bk9ivnP`+nJi4!I6`fgmzCS(P$(IuP@I4m?M3@VM1GLfFP#pSQG&Ws+=wm;OXeX zk>#MgybQ0m7omtm1AwdRTLy;4*gXtUT3pD)@Ss6RSeOrfS;8<-;#((YVM;AwM%*^+ zkqd6_?oH0q*@ZtahqtdEBnU&F_=2Ls0?zKDwAE!$S(1w_Jq>_ZOe5%@{Wv^mS`5r0007DNkl$=NQn`Z zW$9ujiL#_jvl?0NJ_*(A#y_kMHFN zl!(fW6LCNWvbo$J<-te{cYY4x(02js1k67H2fbb|;YbWMp5Vat1gE-m0>KETnPTWd zA&*8iGDMPGrww2Pu%uUz0pr%gr#zns68afNQ532?Nm?2l0JPpzm|DmrG9NUvr8t#d z@4b%pw!`TOj@Q-IvG6O75{;qjI>mV@S{fYeKE1TuL6+E5yc*+Vb0Zc>IwwqXbv3!PY_s#3e;{XKe9ZkV zq^g0>vo_6Q$GssJAp{P)ovGm~mNj5!%WmAV=&00000NkvXXu0mjfKZYWX literal 0 HcmV?d00001 diff --git a/data/images/button/Merge.png b/data/images/button/Merge.png new file mode 100644 index 0000000000000000000000000000000000000000..2771bbe8f05ec21e791a928ebd7b958b77a2bcfc GIT binary patch literal 485 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 hodbx+D zQ%!3VQ4~FIl4z)*T_lYJ6+42iDu@dqiV6yfnWcO8`U~DaaQ=vYKzQIXp^}%9)e5g%B@o$6cC^3zD!Mw-2B&9n57d z+`Ci7*bsHEG5lC>Hc4LBO17p2!rI+x-m(r05&|HBo*$#@$EXw+u(`Q4UVs0s1M;R; zasvRe2ZWI!T3bDsl%BS>1C-0lnM;{$@$I|%E8B5vTFDoQz#txAH;iEn034`@!&?A4 zjBvKRB+j2ZUHIfRp4pCTCjwD4KoG<*Grsjysen$m2T!+l0o>P0{z?RfBLlm#_!of$ zx_*q`{Roxf2@IkLA3lHeY3OJrpHKE+^!m-lcsgS?V~N#^7pz=v0bvl(`_lA5-qK30 zC769*N*+96qp|4>h9>X#`{sM&XAk5Jt>pS7X5J`=%jMFsc6(X00000NkvXXu0mjf>~{D# literal 0 HcmV?d00001 diff --git a/data/images/button/Online.png b/data/images/button/Online.png new file mode 100644 index 0000000000000000000000000000000000000000..f30f95ae6662dd299f116cef1285cba4bd0c84ff GIT binary patch literal 908 zcmV;719SX|P)ih(29x*oiep#YsZ=fb@^iDQ($V^>xUxSvUIHc{qzlJb`-We=CBQZMtlUWUpW^~jZ(m4;>i1x^(ty)p2W$-5Cb>3R@bc_T z{@dxhbJ@e|{{b*#*U-&=i9U%OB&NmlyW_ImXA1 zUkA&05LO_hdoYxWL7$q$w&)S4?mo;F>llaz(Iv|$uUAm6@DS`(eDTqh)0+VLq49Au z=zR@pCm6j6&rmO-!YX|8qez52WH0)0@mdiUWnidl09vbr#o{8qKU@9q<|0vJFXpV? zXojG{kzTL{FoMUyFcfK~giCpiY@>n9umT%rpiY2jGjktjq&b*uH%$+#-lq zBua0RQGT=e=lrY*y=5Vt5#V;)P_MVOv)N|k4**Rh5QT`sNREtS_r6_FBP?o74C*Hk z3(tX}XmAWeDp9Df8TyxR7<(E;ocY^kK|k>GJgl^ixpO4@|?l|0000WFU8GbZ8({Xk{QrNlj4iWF>9@00FQ`L_t(I%cYV_YZFl% z#ea7?f+eK28l~>K3#g0v2n|KF8(pL?x^YpEBGGRnKCseVMfw4{sjU{oRS1CyZqgE6 z^{VN#on{`r_qrGxaytnYJev#u=Q;m-#FNVMu9R|ub0`!FUtc_bb(?>{l~PVrDvxlS zp>FUb= zf3%n>-y@161P%ycHOFzn#>S_~E`we)8hiMD0Mz?#9v{Do=iRX-^uqT8-oINPsCjN- zCVK;=6iE_Whitch_0fF|fFw!m0a|O)S9KG$S}il6RGK2aS;rm_MIoI|Xcc>&$A!Of z=#onuC01f*=Ob@dS5Zo3nl?ATkjv#*Se#oEv-4$>&*za+T15{Z+@)A7GBT3qOy;RN z#TYPJ@nP#T)oMLu@a*Xl#$-KneC*R~HW25005E-jifXkk-PAjVR_j||jgI4FVp?ks z4t#(k7oh$7C!?bUi^bVio0$0<6C57;0KRyauLApSe+{PmS0000Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iyY; z4J{WBfB)wI000?uMObu0Z*6U5Zgc=ca%Ew3Wn>_CX>@2HM@dakSAh-}000B3Nklnt{ZCL;&4FQ-K!^dua1%{;8?`><>$Z-_?b|=}z0z@KZD498>)deo*q|^z*@N~JAKmI3qPhHw+)|_^xve3@ z2)uB>JbeSiW*Kk?1FsPqe@0r-`u1zee!@T1kXD@AN<5~Hecc=j0Isnyg;>N>G#t^L zftjO>cyF$Ys%il|=LZ1yo!cj`Dau59NN{}QSclqm4(z&|-DvwJ_Qsm|^^RNMxm(;! zu!;NbLbmek5!g272Lby&;NCrU%UAqiTzuxVFZn#LX2aHx#`3uoBH#EHg z0ARD0{!HnksiE&9jL9LlG=0HXw>Jm!bv2_f zP@zOqBVLb3{ym%iM95_a?^3EX^ zKa6g6em6X79-pi>1oI6>6izHzxY-L29vBbhtlHaLuQx_BWr}9JWlqcQ7@Mo|%X?ma z(|hR9#Wgd*;h{R|>g4B^@n2hLIAVb3Zh~dQFfj%c)ecpyA&kkTywJ8RH$VOMsovg8 zUla15S^)U_(vX-*XJ2*A+&<3F1ihGF0M&Fb#U8BGY+25>G<_NQCvxt&ll`$(=3l|< Vr}8u-5V!yU002ovPDHLkV1jy_`wIX7 literal 0 HcmV?d00001 diff --git a/data/images/button/PackBugOff.png b/data/images/button/PackBugOff.png new file mode 100644 index 0000000000000000000000000000000000000000..1de84d53d161c0439baa75722743a5817d88832a GIT binary patch literal 1116 zcmV-i1f%Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iyY; z4gw|D@+eaP000?uMObu0Z*6U5Zgc=ca%Ew3Wn>_CX>@2HM@dakSAh-}000BGNklm)Iu(1Y=RSQaOJx}N5can3GgWwL` zOeUG7iOC4h^GEWT^*$|^i&07$&hALL%YCB-!#qJ#^OcK&-iU~V;8cH`ntpUDf0y;@FH zO^iR`IqpZN$1P%v$=2qk&oZrc8$xeT3f2Cd(fmrRld|EY`_9+0jv{ zh~kJMNhd-NMa(;m^XYP&NO zoX5PwaU`>R3pl&|O#k4A&)InHG>h@kJ+at}F1OH1!FEqsndj+_`|4N#fW-0Rx{c@G z%V)DzA)S5;=VkdBVwy$FvZDY1{U3jHYC#l#srBwCefFV;VP{1I;tqeMB^3{C4{mq6 zrZCG)WksY4`Sh=sikO>l2>v#YME>XfLXOz^sc5vxGPozQK1y_h7KlYi)JM0djLQs zCFOt+vIrr-+IYK2Dfl!^699k$05n%r!1>8Z*sCZI3hmXDa0RTW|EmcEIv!Nsu z+F}yDy-92sFBcT0;m{M0e}3ue)kLmPxF)N)eUwZp!uIsyJCFzfu+1cSpsSqj}Mw6r{1qPu{$ET-WYl_!2 zatI$JhQ24bE54aC%>)7fnD}|TPW*f3f;2b33v=wBHXHY@YUnc;uUsKgCS&yW?6<;B zceP;{{Y2MaDG3D&!TS0ax(*!p_V$^69UE)jSXvrhpI>axXR`#e%r%U$h!{+WX$izM z@5Y$%`s1-JArd*f=<$3v(A`bZop=8I@@0prs)?d3znvAuxRREE5CSei08cQuB~(_N i6Gd?_F)$!+Gyem8$iZlKX`wa%0000Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iyY; z4J{WBfB)wI000?uMObu0Z*6U5Zgc=ca%Ew3Wn>_CX>@2HM@dakSAh-}000B3Nklnt{ZCL;&4FQ-K!^dua1%{;8?`><>$Z-_?b|=}z0z@KZD498>)deo*q|^z*@N~JAKmI3qPhHw+)|_^xve3@ z2)uB>JbeSiW*Kk?1FsPqe@0r-`u1zee!@T1kXD@AN<5~Hecc=j0Isnyg;>N>G#t^L zftjO>cyF$Ys%il|=LZ1yo!cj`Dau59NN{}QSclqm4(z&|-DvwJ_Qsm|^^RNMxm(;! zu!;NbLbmek5!g272Lby&;NCrU%UAqiTzuxVFZn#LX2aHx#`3uoBH#EHg z0ARD0{!HnksiE&9jL9LlG=0HXw>Jm!bv2_f zP@zOqBVLb3{ym%iM95_a?^3EX^ zKa6g6em6X79-pi>1oI6>6izHzxY-L29vBbhtlHaLuQx_BWr}9JWlqcQ7@Mo|%X?ma z(|hR9#Wgd*;h{R|>g4B^@n2hLIAVb3Zh~dQFfj%c)ecpyA&kkTywJ8RH$VOMsovg8 zUla15S^)U_(vX-*XJ2*A+&<3F1ihGF0M&Fb#U8BGY+25>G<_NQCvxt&ll`$(=3l|< Vr}8u-5V!yU002ovPDHLkV1jy_`wIX7 literal 0 HcmV?d00001 diff --git a/data/images/button/Players.png b/data/images/button/Players.png new file mode 100644 index 0000000000000000000000000000000000000000..42acdfc2fe55010518c3dd26abcb8222894b6be8 GIT binary patch literal 741 zcmVPx#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2igS= z5db9tu5hXV00L!6L_t(I%Z*bpNE=}oeSeZ`?v!?k2*p~23|9!D92n7nLkK7&kSJvB zAfg~RWa{cB1t&*!a1S1IJi=inL6DHm%b5mZ0x1U}#~CO&6aKG@m-dRbeelD_5AXf& zd;AZ00uhB%snltyRH9O;M5$Ejl!(Ik2SVPEPN(OWmX=nt*(?B{TCJ|8)9HBt-~LNL zmgOnKFoe-)1OPA$Ly%>8>hkguM@L72R;#t)x-M2$R=#d;Z*!l@{{FsDC=^aKO(P=W zM8q{sqe7u@qU-wf;^N|AG#aIag#}6^5{E?e%HswApxti2j>TeI$z&1$7yy<`CJ~Fp zw(jokK0G`;Y&M%sv|24Vj*RNngpZf|ehZnt~oIL^eC<2dMcyH~O-2PH`oyn9KK z1X-3PpFpG0820=9YPnnn=Nz1Kl*?te)&A0J^D#z)gMrB0{w)>~~n9>cOMTi5jq+qOp_qDVfUA6u5? z1?YiWmPNT-jz=O9@*DslMNzb3u{Z?)$Ye6-s;UwZan677pJuMADkTz$33+B_hW7UM zK2O3@6s7ma%RX?-w9g^zn>CT3cIlUMz_yii6+xe(s2(IAAXcFvd2BNc>w98Dqnr Xsp@D9A7-Gw00000NkvXXu0mjfwX8pL literal 0 HcmV?d00001 diff --git a/data/images/button/Previous.png b/data/images/button/Previous.png new file mode 100644 index 0000000000000000000000000000000000000000..d3bc514400794d99b2aa0a54c6a5f6c75f7b1d55 GIT binary patch literal 578 zcmV-I0=@l-P)D zQ`>6PP!wG|NtsMFon)pp<4ng+rWB+UeGq(5@BQFn2#+0 zPXUN!IZ|<<5H0+2Zm`^i0Ijs#oD8LcW=&1qL9vVNzAA0hH2M` z=*4oYv2kN#O%^2)uXaAA$6i?0f_6e+n0A|pUiZ3JJFBbL<_?Ym$g+gSLKOl{>EikJ z`zTJ*rj!@2gJ|daT1RWOb#d>Hk2GUMUW~C9Psy(63MRsmVcHb{+nv^8L)Vw+XMc<| zAKGbmlLLXlh+`R99u!>1iU8c--5VVL-XCzSD!~szczy^!m>|j#A_yWxVSGYRsQ>@~ literal 0 HcmV?d00001 diff --git a/data/images/button/Pseudo.png b/data/images/button/Pseudo.png new file mode 100644 index 0000000000000000000000000000000000000000..38dbb26cc66ca58818107ccfd80aac2a0ff0552a GIT binary patch literal 533 zcmV+w0_y#VP)%ZQZ)0n7A!DIdwEzGCb4f%&RCwCW zlS?Q>VHk#=|ICC@lOZIzEL;~XY}CX~OjBoN&5&ZFM#;i%SXdxgku)R6za^tpbqer^nbbZCEYVl&4-_ zT~bk9ivnP`+nJi4!I6`fgmzCS(P$(IuP@I4m?M3@VM1GLfFP#pSQG&Ws+=wm;OXeX zk>#MgybQ0m7omtm1AwdRTLy;4*gXtUT3pD)@Ss6RSeOrfS;8<-;#((YVM;AwM%*^+ zkqd6_?oH0q*@ZtahqtdEBnU&F_=2Ls0?zKDwAE!$S(1w_Jq>_ZOe5%@{Wv^mS`BOU1*Z;y zV24fy1O5PqEFGn-= z{{bKEmkjk-aO2xoDwRuo-^ceaNT<`x&du}txI|YU^mk&DO zTJ3OYsX>x@Mc>XA<#HLm?%3`Rjr_oB$HG8djF2i%_0DHTw-+x{AumMbw1A- zw4LjG9)N1K%F4>ehN6g|EHmsu^D=(-MS$;~$GGcK@H__hc1iyIr(;qYNG0wQ6g*Em zW?WZ;Q$G*!egFRCsQ?j#4n zCV~LRN#HmMK7C%JxV24B>WoMtjv1|m1YeemdJb}yv8h&tBCwrBtV002ovPDHLkV1mO}@bCZt literal 0 HcmV?d00001 diff --git a/data/images/button/Redo.png b/data/images/button/Redo.png new file mode 100644 index 0000000000000000000000000000000000000000..c03bba0c8bf3acbe72d94f62720515e66b6d0f9e GIT binary patch literal 753 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 B7smhq literal 0 HcmV?d00001 diff --git a/data/images/button/RenameLocale.png b/data/images/button/RenameLocale.png new file mode 100644 index 0000000000000000000000000000000000000000..7f6fcf1a0d70489fc8d4cead729eed9e5a5159c5 GIT binary patch literal 657 zcmV;C0&e|@P)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iXMz z6dekmnl2Im00I$7L_t(I%YBluYTHm0hCf|taOA{fNbMoTLkMlAY#H(Z?U1QUmkfD^ zfcqAHj&{gUIs`vOLV`&NE`boJA+{RH60$6#d++H|+focYaJg{#&wu~({|DCV18~>r zbfn+!OB6+tB#FdvEI|;+_4T!!pPvhG_YYgC(^jh`gTX-3G?gsNWHy^gmSvKrsU%4v zVHipf1afl z&qHgC)*55X@0nL_w)1fZmm|sb=@@uv#g0Rrb42%t|-`bT^DODN-3u44oWE; z$3aA})>iJt@qkm{6JUy>aFtRRW7ukh08G;z>YINmYJ#!9BvPGD=WD;;|9p0K=3ZT0 zVT{3AOB6PUC);SPi6`4cVT15xtI{kYIBA-SQi_L%2M!Mp8IQ*RylG}@&M%r5USwH@ zBZ4ddj>D3`T8rbXJTA{~jA1kyadL8k%JUgo7qH?1Kq*C@=j3_5jB!4n^Z58kx7+3D z=;&t<1RuRxZDUR9)6)|d7Z=>$-*b6+iR-!m7-J0Z9ryu!16CgRzF#5fcDo!OAG5!| zkJcJ%E$wz2sO-+lgXuJ-Uf-;m7L1omQxpYzdwWCRZ5iW5QHW9s(8TeiVpKVk_z`-?Z7>QA00000NkvXXu0mjfvFsgj literal 0 HcmV?d00001 diff --git a/data/images/button/Resign.png b/data/images/button/Resign.png new file mode 100644 index 0000000000000000000000000000000000000000..322f1b4f77ad9687b04b7daf712dfb9999d15ca7 GIT binary patch literal 906 zcmV;519kj~P)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2igS? z5(p00OPv=000Rn1L_t(I%Z*cCNSX^2|9x%FT3nly5R7Vj2py)-AGL=HR->rT5Uz$X zVJw1JUgEA>!HrvaF-8PI*eH+~H++h$Hoa4;m-=AL+CyWR_~;(Y3M~`4Es(x*A8v-* z;NJUtI_Lb(?{GMOfGmW=;cOQtCnxydC@Cqqwu$Ig#_s6o008Xm?OpS`ySrbDS5{V# zC3sO=TkEN>uYYYY7~}vjI5-Ha)%xFEXfztf)YKGzc6LUgP>32E8Une6Gr#DuP(pg_E|wDhOb z>HK+daq;)My1HLXCevQd=H?~{Vv8F`~ttI6Z>@R3M_y1To1zu!;$`}-sm3f&yX)dhpW7|-)WM6|xXPI|rm zFTGwbXWH7@#Hm#3Q)_E$Nivy4Zf-8=bUMcAbi(0q%yJyJ?D2Su^gk(_@%tO`~v`_(`k}OB+S~{8X6iJMidIg$4DgdCY??f`ThQk7(~P<6bi&* zu_6F~N~Nmo>goy~92`(*XD7|i&reoVR6N_>-cGSBOB~1XR;!gRE-okwywPZ+8vqcE zMvs(A<-JrYrJkIeoL^mCeLFrr?yy)ax5viD$mMb|r>Cb#CX-+C^6~`HXcQKUg-Ik5 zKQTm*nU>2R%JKFRQAm!o$PETN@i2_y6?|TrL*?puN5QI%c=q1zN4Pz;3ty g@NNF(Mc{K~y-6osvyxR96^=pL6fIGj}4LS}w6nM7;WlK z?Rbb3&V<+fWAjYSD+#;}IC(meEEbD3N~sB$s1^YHlxjnn5}(!fA`tc`nQw}Rj~)WR zw(TI|MqWzK^J1OtT~bPk>sH`-1^O?1_fAzpSbW*lO(vZq8jTVJfjoQeOw0;`z>!ko zc^<}SJkR^Tt^mm8bL4Va#xi4QV^B&-`36to{^vYS-UMJ)yIyS>(Lc1KRH5uXgRf^4*`f+RV4FIIo`L6Z3_;vWOfUF$)5xW%c58) zFn`WsI+vbj@SkCN?|n;4Wf$wK_c2{Jkm&1S%e;fcKiEJnm&La1Nr2;o70|PO>S!lXPa3!J#CfaJZa7YGf2Q>~ib&9pdq3hKG{W z*Ei6%`VhaR*3tXRe)hEfK-6AH|AU_?6btmji_xk|H}&rr(zbBJA)Jtd zW7~uriEUd1N>lO!Jm1InOL%^X*vuKEv*S5w2_XS$W>q1Cz_J9E5QsMi%6BIuk O0000IK#&?isEe!h zy%1fP&_dM;wG^YEG1@F#=&ls{pco&Ek`P=-Rm?_QL`gJI3kE?+D%yoY9+1SOd3z@} z&fJ;P#SKxZUHHLSoCn|eINwqGE|>nC3DPGc2`v_(Elm_gybY}tQ6z72Wo2EH_>sLW z@$B^cn0zQEy%DKv&Ci%fLMs_Bqw`+JI~#Xy+r|E`zU22S*LWZDW6Kc{m)e$uOnM_9 ztYB{@*cKk#$P0F>60*dO!#j&8ahwbS1B6XniYRyfrF#C4M4^fQ_}j#Iz>H_A*E zmbTsy*tWyy*f0bRzt;9JH8Dfm{WGKr>e;@$iDgj*7j8GwwDl?_W)&l&!#J^6j$nLZ z3OD9*_g**s126ciua|FX&Qo2qg>!d~GBN##l*8AnTV%+;$AcSg5=>K{iB0h@z;#R8&?B4-F1giimpq^CA(n odZz?{Z7h^h5@bVaYSupf7s|$&o>%$3<^TWy07*qoM6N<$g5{NTTmS$7 literal 0 HcmV?d00001 diff --git a/data/images/button/SetProxy.png b/data/images/button/SetProxy.png new file mode 100644 index 0000000000000000000000000000000000000000..111c493b86dbf0c1c44924a262f65dc301f93105 GIT binary patch literal 905 zcmV;419tq0P)8@EKJ}qBSF^NQWl5PABn`w=$Rvo7dC4%$6_sL{2x%Zpu%wGZUZSW(V2T$~ zNdxmwrlmH!&CFWXaxJU1)mp8~)@rR^fBL|>o$dK>&e@*xzQ3J!3V^?vuCKZ}Q=Hqn zJ~O*fot$2;SQu2c+HbyUhWGw&e&0+RfAo6RLA9&y@KMh=HJ}B#M<52H2$ogI&`}|{w%y%I>Hw2#I64r{_5S400NPI2vhk(U7Rj4la zKxrCAscQm-H{W90wKqubdWKHo3dYADqOE5TQMpcprZ<3+WCE3Kl=uV*NyY~%!!_(N z-hili99!JakoInJ43m?S(3skwth9kfwjtJfkeFrvR%;%f=@o=iVyb?X zCOFiO#L8jt&W=J*e-9Tfx{#Zf1FO}FrsgJagi_FUwS&IB1yLy$tct zVgp}&8S$EaY|szEY;T3$pvQix6uTw)h~WysDRzOrryUU)jfhUOVo|ig|5pafq7APo zd@~qiNPTC@V3b$gXM2fJi3eXGN!H{($QQi$!-UYD54lGYR{mK_2g>G;JuQP$3 z-H0>i>cHi3Q6N`&aq^Sr9WEbImEQocg#V44%L>tHwSw2u!h}Xd=XHW5b7PZ|z_zkpB#`B=OtxT!!17^nOm(17;B-1y=ybXtku)kw)TM|O zRv}YOfM4js=Av$}B^?Oe(ty=jEm$RL1!bdI?E6R#hl66X+1_zDNhm2P`5wEy`Xf)< z55A@kqT>TtS44o8??RM}z?uU0+pr9K+8?tZm&=ctOs3s-yZtkp&E^G0RLl+*SajjL zI)=jccD;+tcfDs9ci&0a-&q@zZ1($S8ktOHh(@Ei#o=%=y$9r)LlQ?)K+;U&CMhRL fC0Rr=^>@KfCXZA$?WHc<}y}iw8x0?WOn$UCX-BG<#(Ss-= zNmJ?~?2F6_4@Ec>A;~fZ!y$X+vbH*r6Erro<}QwBMF0mP9LNJ=fYEr2c3rH0@xEKzXNBMt0)3euK XNy@!oTx8sk00000NkvXXu0mjfxo71= literal 0 HcmV?d00001 diff --git a/data/images/button/ToolBarProfil.png b/data/images/button/ToolBarProfil.png new file mode 100644 index 0000000000000000000000000000000000000000..bd731b3a9c1c722a18da083d562644174cae6f55 GIT binary patch literal 294 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbK}a)3{W>;M1%flN4%mX?+S105Y5 zLojf1a`N!-@bdD`&CSip$u%-Ej*N`FapTszcki!Xzqx$*iWMtX9z1vm2o4`Ua_rc# z+s0}(zK5E$E-*>!3+|TnDa^9zvAUwlCnkLBzr#n)j*q_>w>@I}Au5pkN2i(n X<}0xWiRV@7KwkB9^>bP0l+XkK;mCIk literal 0 HcmV?d00001 diff --git a/data/images/button/Undo.png b/data/images/button/Undo.png new file mode 100644 index 0000000000000000000000000000000000000000..f12394214dadb66bd90e7c671d87a5810c1571a6 GIT binary patch literal 810 zcmV+_1J(SAP)zz1OUPe zo)1aVX}s1#$L?N9pk7vg&d^@u+F#8>SvWgp*|R{@KeW?!3fxz47$#1qCH6_Y)^y5H zFib~-aq$M`CM?fzU}@|U$SOTo#9WkucPXo&vB2K&eIgE|bw0-T(%|)#nK)Sc{AX5X zIWXIK##ob14q*wyDlklNU<++L7^Y&uFc|~-!AO{IKaDvBOR`X$$6{h|77ub^ps^|y z@hyVorLkzxE2BU^9tFCgFqp3K1eL%6WF<}}X}+szD2o9fg+8#Ph}aT;>%R=;NxN6^ zTbv-8ut4|b3g|{6U|~2MCR^ChTgEaKC)ux7-lsJ^PbRA}nwbcU4XA6=i!6g4>T`XP|J5%#wT3-^=I~qQWyqB2C1H_ z_Me+?4L0-^9XAQ?kmH0TldgX)VE^c=aNbnZcyEq2A2q2uJxpWGE*p|Gavqm`%@Q#W z38dLJis=%!hEL*?QgvCt%%ga6-E0rSv|=_efIkj~u&`hia8X517NcV-o8oKpY%2!R zm>szsnsM8u7~qjeox1WMKkXTaknYw+SY5C^YeS z2csdwiC&Q6hH#OEDXr!rU1uJ%@Lh>*$V47J6bmAsBv4|U6>byYGSUbfk$D~#xucHK z{Zv!lu04uMceeV4v#+L{6@Vo{eGhCx%J0&2Sm19dTTu(y$TBP2jv!^5H#OdTc&DoF zfSX$6?4fzaau?mB-@@gq<{k6mUm5H6LvOo$DP@b7ou<~qR$lC4S9y(2GiMgTKheDM oL#k?5npWtHR9tJvD)=vc0s@|Dx>pGq$N&HU07*qoM6N<$f=L-|8vpPx#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FZT01FZU(%pXi00007bV*G`2igP@ z4m&o9cl(wA000woMObu0Z*6U5Zgc=tXlZjGa&Kd6b0K4)Ri&nLX5GjIL!3KE2JL~fwo#W>@jI0ct`=Y3juy@?CKl?M(S-t+N1@A-gjmkIdbg4lvOpc?1} zJTXPk#mMvLTM66Nwkcq8Y>d{Ps{j;LzAcAnA0O$IJy34xuZ?_2?tN^$-C zSqi?-oxAryU{>>S))zQ*@Bp**fQ};$Ng%V*U>86bQh3&na=TaAh5;sG0HFF}MS#w9 z9nLH_ts-|o;W8!o?BM4 zr9~DL7hXZGNYgifQ(`0bCF>0o=q1i xHSf{heGLP=0vLv;<6B&1KvSTH@O=A^@gL$_53pAwV!HqU002ovPDHLkV1kJk1_=NF literal 0 HcmV?d00001 diff --git a/data/images/button/Up.png b/data/images/button/Up.png new file mode 100644 index 0000000000000000000000000000000000000000..afca099f2eea3f6c0002243c5fff102c7152d328 GIT binary patch literal 415 zcmV;Q0bu@#P)w5Wx^^P;Z< z|9A@dJ}~4lNDyTJLjwaNZxruow@$Y|CiN!&Sz}rMv!}EF=C0+v0g@wV0BaEI48KW! zf83|I|JQHV|Ib#)_Mf4Y;XiL9-*3)Fo^^N)VDMu|^zHHe6TKw*zw=Dz|GJZP|1&i( z{b%T6_|MSK@LzPQ_#dWvwsNcnFnBN+IW{={EZAJ|KYVHUf4jMM|Fvdm{b!f})I1l6 zmjdw;Ans%S&Ctx^0@DW%G@}Bex6KEd|7CB?{vWh7=)cu`tN*I=RUw+!0r74i-o@~r zbq(u3v1wAD8QK{n5eE1&w0gC8{daA3{cqK3^VKKZGXFWJasB66!uy|p zgW!MREu#O$R!jU>U!wV+xsPo&+yEe8WC&p3BndM>Vq_R(CIA5^XsGGoKP&(M002ov JPDHLkV1jwc#ccop literal 0 HcmV?d00001 diff --git a/data/images/button/Update.png b/data/images/button/Update.png new file mode 100644 index 0000000000000000000000000000000000000000..67f739ea42b8fafa3cbbfd3c82538a743f10a557 GIT binary patch literal 1057 zcmV++1m63JP)e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00W6hL_t(I%YDk-Pg`XG2jJg( zzWRAQltN2u6t*l*wh?dysUt%NZizzTg~(iV7jnmp!Kn!s8X_c(aW@7prqLTmvav9k zy+KJ4-6YF0!^bj&4eE5mLTRBrhaTuT?P=fF|6tGcLjYiCXow693@}f*N4hq4tt(I! zI71mdNGWwnlC;Bd+mW2n#mBWXn`??s;U6$>gu3s+K)UzW0|7tn_IX$sA}p00f4h--#u{d-1|2* zRST-=t&-2{B~G_Y+%6aXi^s8%NYKq>k{oY+nagIgrPYOD`d&|u%K82N zH+5Y{Fc@Tc%R;%!1GK&4%RmHh7-Lq1svm0s;6cD@sPrkYDfUI1T`uRj`f_ z0(iY%LMer17?7M!a4r`{&!0zH*AWZ^zzYH>W7xlMKLG&KFi$Ir@)@_gw<{HkMaZ%Y z$!3Gw=YvBOv69W=?`RaycswAY2mlZS0kSL;p68KFCH*@)JI`~fs;#N2iexegLI~`3 zI|`pFNr zz}wSZNI8li9uHWth`n46ZnqmtiDmA>#syq7zUehIe8G*4ja|;9Gmn42ag*CjB&cbc zAcWwRniFXM^CXg50vyjHsi$ye>>c(|<;UbNb16qPTFpLT_jv$0x68F@v)j8enGAd3 z=uzf&yUDJW#pRYOh~ApQ%F+t@#1FC1yg;gdt}a&n=6`kVyS0b9t`okux0f3qA77MZ zd7D!DT09=-a=9EF)WNR)bCsl;QkVOQ|>zA(o5e|pBa5&8P+1XhZ z3WfNYnVCCA&X_U{^V!5^;_2;FTAaT(5A%=-#DSWNH4iK9RJ<68MD8{>Hwz;pBP9R; zA%p;sWLZ{ZS+)a^%apQ~hK7b7rCqs!ufa$g83o|-csz=tDCGd0gb)z`!4sXrLA2q^ bCzbyJMT4277oV^z00000NkvXXu0mjf;Md)k literal 0 HcmV?d00001 diff --git a/data/images/button/UploadOff.png b/data/images/button/UploadOff.png new file mode 100644 index 0000000000000000000000000000000000000000..7816e7b4b87acb3725fdabd68544c5f707e961ab GIT binary patch literal 874 zcmV-w1C{)VP)4(HAzh-^UWlg`M$3!rG?wGc{y-6ocHj)2LFS@!^5pYp|IL+ zw^ba+AC8ZY-+xSJ{G06W?;EXF>z(iWKh*1W&+|OOFy!|3mRhYgs@Lm39~>O~@==4m zy}eqcQaLLYi|auU&}cLmjYgD8B@DwrDTUS=&-312TwHv0baW&D@-GHj>(eaD)@!vI z&1REowMrC4l*?s`#Ufg3rqd~&=Y3JBRDK7x0CE7kySwkQEc?c`ZG7LSP$)1Q4rw$R zRI61M78Y<_7p*m}>vD5*vlIlu-`CgIf0zJDsUMY6SeC_jJm&814##n@ZTmm7P$2O$LG@%WR?&CS(( znx>xtq-jc;rkJLQ5P~R*NRkB8G?~q2G@H%WV#6@x`S}^8)S8)PnKlfATrNi($4Ds& z!;ryXz{A4>S65fm>va|v7g0*lYPE12hmDO5k|dFNDdh(#XLvw(VA{RhE~R+1}np2tgQzSeC`*iuh+ZD0d%|Fvz3*VuWj32%IEVWNrDi9$z(zt#|R;4x7$2DJ&~pz$3qvi(^JVJ=s$;rvL)9LijmuJcYHsAsaz>?!Q zpS!NxFbqRWDKp>q*DcE`P9~F!`}_M}fgYg#0hXA1*>S~r#{d8T07*qoM6N<$g3~ga A)&Kwi literal 0 HcmV?d00001 diff --git a/data/images/button/UploadOn.png b/data/images/button/UploadOn.png new file mode 100644 index 0000000000000000000000000000000000000000..f30f95ae6662dd299f116cef1285cba4bd0c84ff GIT binary patch literal 908 zcmV;719SX|P)ih(29x*oiep#YsZ=fb@^iDQ($V^>xUxSvUIHc{qzlJb`-We=CBQZMtlUWUpW^~jZ(m4;>i1x^(ty)p2W$-5Cb>3R@bc_T z{@dxhbJ@e|{{b*#*U-&=i9U%OB&NmlyW_ImXA1 zUkA&05LO_hdoYxWL7$q$w&)S4?mo;F>llaz(Iv|$uUAm6@DS`(eDTqh)0+VLq49Au z=zR@pCm6j6&rmO-!YX|8qez52WH0)0@mdiUWnidl09vbr#o{8qKU@9q<|0vJFXpV? zXojG{kzTL{FoMUyFcfK~giCpiY@>n9umT%rpiY2jGjktjq&b*uH%$+#-lq zBua0RQGT=e=lrY*y=5Vt5#V;)P_MVOv)N|k4**Rh5QT`sNREtS_r6_FBP?o74C*Hk z3(tX}XmAWeDp9Df8TyxR7<(E;ocY^kK|k>GJgl^ixpO4@|?l|0000WFU8GbZ8({Xk{QrNlj4iWF>9@00It4L_t(I%dL`4NK`=p z$A7cy_;|iP-h&7;qO$hOgNxoGf@Ix8P%a{fALXTEcnTK7Lv#?_W!uhO#1tyJYziT? zs}R z{#*V6pnzK=@zG}o8gxb@6Men?!r6AHRw{Vw>X@5*bym}~b2~d+HwOoX@CW=DM;dtk;wkz3*HW#~w&P$PH_6Rs5kh=+11?*ph1cUH6l|fjHGE9h z^~PGG(@kfXnayAr2D+}_Nu?84+>eND+my>T%gf76Zli~flIRUOKA(@lfgyl6K;2Gv zbG^7`G@ri+K;~t}vP_FoX@gL(g;1~sP$>0#vt?QU*Y^NY%1=|%lb77T%(iWW0Hr{o zP)e~!IV`DkVtsXOMOCZS1IOyxib|yu>$O+d7b)etsp-jUhaDoNoaKMy7oGLjcp?-; QV*mgE07*qoM6N<$f;l)YY5)KL literal 0 HcmV?d00001 diff --git a/data/images/button/Warning.png b/data/images/button/Warning.png new file mode 100644 index 0000000000000000000000000000000000000000..4a5c3dfaa3cc087c8e92da7743b4dbf938ed5cc9 GIT binary patch literal 514 zcmV+d0{#7oP)5q>TtOxZ>R4|wjM1RrYGl?Elpsa`5!V0YB z{4>+h=uvD8S~evYMAQAbChZ@0-raVezTL(m4BPa<`@!dq@9T~M*jKy0RNZ_}@r}8A zPe9SjU)H>By5{SGvcO-h8_;Gf39P<^{>cS(x;a%BQ1UihE7F(oWg3XMP<(q4>Ks2< zGoa2|(g;65V6YVd`x)qgTTt9hTQvdNyy>pyKM#Z*SUPva2*G}6%dJp6rlJ1=vd5Oy z!vh5L;{AoAMhJD#U$jGhZOQEl$Zl>_^`8blcLI?v1TP&&=*mgjZNSP|DE?EBUUB#T z4LBKJcyU0|Kkz_^0UsH@-Z+Nn^}~eGUS-f%8j*W$mJnq84j@0tIpswwutqaVeWSip z8`Nf{7=!%Iiu_Y<{BK}_HD{h2QuQcbK1fnq4Dn&B5%RRNHAy#BAoGY# zNlQZcSfqYZ1qN~02GL;yWZF3cFmTByAoGwts{*N;Of0Rn0C9n0K#EFN4$>7gLB)P2 zptODh;ysi22e{KP_G84nz1hd)T8i7vQt~!?Tm|;iFY+S->P)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2igS= z5dtsMP5U+g00F*9L_t(I%e9ifYTHm0#?P&62<&RG2cZnIC;jD)Dl|HajOb9%bxDI?g}qk@!>~+}48w9SAc~@=EXxAdb%8Mkj4^Os7qTo1q9}UWyMrvtzBU?-tMPc;>~uN+0I=C? zfKvL|^SqzF@85m&{>P?iKBsAlh{*6&EKO51P4gKMk9PtL!?mSt@Np63b2aYu+K6GDEx0~AH^psMOO z&+`rtu@*oG0aaBWi=udV@!kfO%jLm(y#_+at|fyY2+-|zAOCv40cVE?)3_JjTmS$7 M07*qoM6N<$g6Gro*Z=?k literal 0 HcmV?d00001 diff --git a/data/images/button/develop.png b/data/images/button/develop.png new file mode 100644 index 0000000000000000000000000000000000000000..a437e0a9bf854224c250a540fafadfdf6a144e99 GIT binary patch literal 194 zcmeAS@N?(olHy`uVBq!ia0vp^tRT$61|)m))t&+=wj^(N7l!{JxM1({$v_d#0*}aI z1_r((K>V7q-Q27UD9B#o>FdgVpF>vAko`x}?0%q-WQl7;iF1B#Zfaf$gL6@8Vo7R> zLV0FMhJw4NZ$Nk>pEyvFmZytj2*>s0J!LNs)w7u#IC0=WgW`#jx0hH&8ndVC85kUt hIAJ2ZiA#ckq4=J_^*w9afz~oGc)I$ztaD0e0suFxI+_3g literal 0 HcmV?d00001 diff --git a/data/images/chat/chat.png b/data/images/chat/chat.png new file mode 100644 index 0000000000000000000000000000000000000000..ed2d33b9f861db2daebf5d371047f3af5884f869 GIT binary patch literal 764 zcmVWFU8GbZ8({Xk{QrNlj4iWF>9@00Ll1L_t(|+O3mINK|1I z$A90wGmbNkI?d?hBOMtHja&p#1Z|_GElS%(3(>#~$u^16GH4UjqI}RQF^e8Vn+he0 zYK%xrdQzB4=A$v4JJ&mpd+*mG6-|j2{oyRm;e3Do=lstHe@8LU4#>r5Uab8yns4-V z%ZagJdv5xJ-PdzT0@qhIh?Pl2MY%5;ovl$-XSJdzo>*+YY2@Vq`FZ~39$&C>)jP*Z z0>enQ$K&zl+S*!Yc6K&hU0w9_-r_(>1lzJG-G6zFL}g_%H8oLxq@f|BEEE#mogG4v zHdn9R#BFP2%J{l7t|AC}q-URUn>~6b4~_=7*AyU_?FGiT3Z?cwy#T&FcAT- zz?bDF*X_dP0Iw6CkLx`7qyccM?Kr>-#iedrv&5|h4&z)+ZszOB6bTb{muGOeKgEV! z0Ybhoda=T^HjQW2V@1wKR6r~#5aDn|-F(_L7E2}hHgEB5&SEBJkP<3}kwM{(O6IK? z0D3@I|8$`$rfHpr8cuk6N1pNO-D9NUWYoKpO(8eKckWZQc>+hCN+K;WQzokj@`DAD zgAJ`Y_)GDGl3Y1`hO`O&gKzlwb&A5GY94iTQPxson@LOBhO`0aGJ((U_m+mY@MSWJ zDIMeoHzI|K+dE2*H;Yu-L`pY9Cx@0sR)rA9q?Es1uu+wTgn_2%q-?=lOy}pE4pLHI zx0g(p6QQVbap?jOQpyX<3QWspX66SAaScO{ad+S;LUHo`^EeL&-;z!yu&lJT-XSg7 u*qD=7baCzXkRXu~DYfYLHsCD(8@~V#yY_XBcII;c0000P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU#KS@MERCwBA zoU-!DLk0#0{!d?jGyMPmp8*B@W@WI|yAH&+fouj$z{tSx|0e^(r%fCT|9~_jBa$4@ ztlv)`F?`*&@gqP0v7Ep8R`~C~f85XCd}BbjgW)>|gPh zBg2m~+zkJ|0@=(+djI}o_<8LT!?qW1SO5Zu<@V!`e_p-&&T#MPCx(Cj&@B1F#bBdx ziGktQK8F847#RNiWCUCE_8AL^Y=dkh>Ze(VnA7j|8M^=Jbl5&z{UTUfe9EUjLd%+KED3X z@a-EDgN!6F9RK}c`2U9y#V7wD+QBqH0I{HiBUS)(08sJI3oH!Z9yFtCaJV-Obp%kY=yKLacKUto|iGJOBc#PIAMD=?Vx z`vxF@@LK{54N1j63_>En;9_NDcyx@N;kO(xqOS-tNIw6|z$@{WK|=920~`B)%#?`) z00M}KP;mVRCWcRM85x+lfkDIapMi(}4}&1v4+d`8e+<80Gchp!0jc|s*BF2RVj>(T zObma41_??1X7~vVDi%Nla{{RNrcSZ(6i3cEnmZJK}z*EIP?8`$H?#psP!*e)POT1!+&sK00a=r|5yM5h~@LM zQ*RmGo%zG?=QE~%|1vY2KK_c~^?e42Q?aE!U?}{2`xE48T$%qPK!5=Nm26D0yRXKM P00000NkvXXu0mjfN>j1= literal 0 HcmV?d00001 diff --git a/data/images/flags/ae.png b/data/images/flags/ae.png new file mode 100644 index 0000000000000000000000000000000000000000..007dc36a713e1d57bbe799ea7d5c1e65a754f7b7 GIT binary patch literal 599 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 literal 0 HcmV?d00001 diff --git a/data/images/flags/ai.png b/data/images/flags/ai.png new file mode 100644 index 0000000000000000000000000000000000000000..bbbd7b1978e7661db714a66089bfae43448f15bb GIT binary patch literal 1035 zcmV+m1oZofP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU#ok>JNRCwBA z{CMc_LuNU7{#}IGf&c&iGdy_kmSOekvmXHhh^1@e6Jbxu+uY8Y;tX8c5)6ATzGL|O z?G^+3;X@3MIdmC5-F?E~ugAh5DXzva_bMa9rY#p4eth}{G=>qcC4c|^V>o~QF~i4? z*H{1oh(%eN=g-am+zi6E9x=Eme`7EXGh+DIzMSF3jy(*ffiVn@M*kQ@EOi<7-1)=s z?Bqp;ua93c96bAkpiln({SWlb8zAfTAAkU2eD2}&;rGjz{O@nxW%%>^F9Y-6-wc2L z0lf>9W?=%lmz{&*&#&JMTr3O>;vB3D%|G=SCj2l3%Kn1-<_{46L$iXF0b;^iAinnz zAb?oDo7gjaefgS!P0xZsh~*yx%a5-N@6KOg_p3^5VmHpw5$eN@&j%OKmakN1Zgl-X?euhkY@kSaPIJR1{d$i3?27G8N6*}8RqCdVKC(Tjuh!kKwrLNV0dAQ7a3l@ zLX9JU0AjSY?fmfK#V7u|cV9F71IzsbyAWc~H>6bb8ycDHP#TTFShYYC!D;fU%1x5ZlaKr-y5DUaWMkxLdwEzTuq9s#SST;ivV`eyh@Hs=$ z>_-ew(qb6|?mTArSF;0X0Sih#0tg@$1`5F+1`dY%?fV&&UOr>^bm29_vx{G##v&X8 z5I_`L0`~JCR)*`dZa{sGX(T`Zv4FBa2><){A4{ep1;7kPHse1S0|XGu<;zc5K7R(K z{SRp2O(_7G^%;o2umA)Q(1Pp0^mi9nHvC|q5%>rYU;u$`Sy3`002ovPDHLk FV1lii+NuBm literal 0 HcmV?d00001 diff --git a/data/images/flags/al.png b/data/images/flags/al.png new file mode 100644 index 0000000000000000000000000000000000000000..c5d218cfa6705e69da31f8092581b8e2d15e9a26 GIT binary patch literal 681 zcmV;a0#^NrP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!J4r-ARCwBA z{Ih7@Lk0#0{{LS-Gcf%B&pZK4 z{xV$p`J3U~uit+F0*Hm--#@Ue41fPJfZ3S7X5a)GdiM8khNHiJGx&3`Gwl2Ii$RQu ziNTAVo#7`A--As03zP%78z6vK7@)4g20$+V22{8QXy~0ke;LmI`OWa*`+tV}KnF=P zGc&vaI!XyhTd}b*{Kga100G2;FId6W10AvO`wxZ>Km&P!?A?EUG6(>D@dC&{_Uk`G z1veK1BZ24v2p|?h@%Int!tX%*1L}QH+%Ie`49zyx#c&mRV6 z0+s*-5W(OAS)|Fr%pk(V$Z+cSABNY^xY1%^VUTAAi8FzN4j%vrAcDaKa&aOTCxa9d zGlLP(V3?&^K%4~>w_s((o3cOvAb~eCU$>dp5O-hM&dgYgCCI12ecfNAedmO z5L+lfB>)151)LFqx${3T82;jM5Hr-W2zCx|y!`%;h#%~Z`wun`qy`{>SpMC-&T{j^ z2Zo!#@cu`|q6=jCLtqAe^ye=NKmf69eD&t->aRcky!rc&fkxmXK!5=N5_{w_x1r6p P00000NkvXXu0mjfridvi literal 0 HcmV?d00001 diff --git a/data/images/flags/am.png b/data/images/flags/am.png new file mode 100644 index 0000000000000000000000000000000000000000..515451682befa72cae64d8a807a8567c491545ec GIT binary patch literal 531 zcmV+u0_^>XP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzrAb6VRCwBA z{5pNgLk0#0{{NppF);l9&p<8kA86L!hYuKjtXusNAb?nYUpz0&@b@qGzZcJ`Ysufg z41X@1XV~)YBMU$PvHZPr`_I4EuNeN`zsKh2if%CI)~2Vo~6H z#lZjb3j;ULJF3UY=RYqQUb1~=00+;HLgKZbQX|9k`p zAQo26KfsLhn}LDt7u79c=l;*Y%=V7~Ab?oFyube${)6lSh95O8`TP4HuoPir00zv;qA&T|NgvwM@1V@!AF1q0{|Q< V*x+fsKIi}d002ovPDHLkV1lKe_+bD5 literal 0 HcmV?d00001 diff --git a/data/images/flags/an.png b/data/images/flags/an.png new file mode 100644 index 0000000000000000000000000000000000000000..28d1f98b36812a329d213b1bc0875c49f8533c67 GIT binary patch literal 906 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 D@8H5* literal 0 HcmV?d00001 diff --git a/data/images/flags/au.png b/data/images/flags/au.png new file mode 100644 index 0000000000000000000000000000000000000000..c8b559976615ac9a21dd5d5a177e2239272f2427 GIT binary patch literal 1211 zcmV;s1VsCZP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU$O-V#SRCwBA zynW)_LuqLd{)caWFkE}|p5gWTZww|v{~3(G-DB9ss>1O68zX}xFEfLlBqxIyCj-N= zmkbPdpM7LtVP;}L2mk;7XJBMxgtK3~_`tC5;FXU60mQQS;6q_UiRawzJntC9^sN|H z9lFQxc>PzAVeZ+l4Cl_@Vc=nBVffF037DA}8U6tc z2O@@l|Nb-Fy8Vpd;e!h-00G3}BK_~r_pk36ZhZgBAai&-gNwKV!>?B_82+3*#NZWc z#=!OGF~gG|{0#fHo?!UP$il$%{SU*ji!T`1SefC51I=UL;$UG&_19*YxA_{w=Wo9l zK(6@m`31w*PtX4V1Q6r@yoe7B?_cx(d-#Ci$Im|uK*un!{Qt-BjERpy`2SZ1kms3! zI@sBP!NdBWVKK8YL+5W@20n)07`~7e;bM6D<{QJGzyBG2{`$@E`^OuG_iwI$1PCCO zGrXz{pT7$+aH=aYXvqsP{Qdfs;m7x146NslGrV(*U=UzsVEFdpIm3~!91M)Ve=^+s z!q33^>n{T+=zstH$ME~lKL$=<@cjjP=kD{*V9Pk!m>KL%R2UAQ`pEF^H3I`c0I|&Z ztHGdcBE;Y)|DWN(-3JWIU;SZFV)@39%6*)n_^l*^x|I<_kl|Z~zgHhKEV}uh;oiGX z44e$#fEN5`P?Q#AP?Z&6*mwFNI38Gmi2)QRAfKqp2{4?$$O0Az2q2~)J7tDc>CX)J z4jyMH-1>@P)!Ek!zu&zB`k$HM%Zrx`U45GvCY}7qU~HwwFhTDNgSi+3!;ilV41a;X z5EtWT&{7p+02&PsHjqUeY%C1ZSDs;b_U=0a6EKtk0*G;9>!J_Op1gWntsER6qw@dpeuAo>pss{i0* z3rPS961)so@4aRC{_Q2h+gH~=0t67trRU!n-oF3K@aNB824+w!1M!0|3=Fpz#2L7l z{(-~z|9?hsnt$@_Jp&6X!~zfi#RJFzu)G1HzWw;kaOv(_ppO{gz5@s#7LfPYfChn5 z6-W;g(0_dF%nZP&WO)DOF9Q=Wse&}KurL9|z}XD08DcggN-6_c$jZV9@fH*S1Q1HL z1!W*Mpn~)$ONRU$9|i#dZgAKW13+eiQW`b@5I`uFfb3>t_`_hR$j9K~sKcNOjMQI$ z{t;yX2P+dW3kab348jHoAeMjs{(-|5l=PT@Vg3HwKZc1*PBN$+xy^9>_EWIe|Ns9- z&=O$GFeu3gFg$qq8JH3O0eSzy`ali`2p~phpQbNw-#p{~{`C#Re`t7vQwz{?CQu>B z$Vg;xfeI{OoH8>)OabzL{`d$iTAqIe2q2ax4==ub^YYrCKfk}B1RG|Q5@8W`Js$xA Z3;P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!7)eAyRCwBA zeDLwdLk0#0{y+czGk^gz3j+fqBLl;q-wc!hkZIrl{9}0j_4h}B0Al&{>#y+t{|wwe z|Ndk6%fiOM|Lh(E+qcgQZI7=Qhs$dXS#|1uohwvh!OfLOl%{`cn>(8kX!91P41 ztPFBw=E@1}dA799`1Zd0s>sJ`=T)yxJAb^;_fyBYiAbolpgGs3` zgV2#J3~bLHF&O5!F~}X;$iU3X%J7eY5v++M{0|s1fB!K219AWYhzS@0K#{);%)dVY zy~v1g3KJ6p%cu7Y%-_F4;)ntO5I`(IH!}POhTTJ}NQNi&F%0_cDGa>VPBWaFu!G?< zuLuLv_b&{LKvz)$00M{wOadLs{NpRb9|kUlr>;>9Tt@a_3z&fkfEoZGfLM?@%s;*{ zywNlR2O70300DpiV)@U&1dfaUu*m%f4gdd)3=E8v8UO$Q literal 0 HcmV?d00001 diff --git a/data/images/flags/ax.png b/data/images/flags/ax.png new file mode 100644 index 0000000000000000000000000000000000000000..917ff47db12a686fdd9ed96f2513a28ebd17481c GIT binary patch literal 794 zcmV+#1LgdQP)P000jN1^@s65oToN00004XF*Lt007q5 z)K6G40000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!tVu*cRCwBq zRNYP!Q4s#NyUP{~3C7eI5)mZgUt*%-jXxL0E4?zpQ+Pvs12ypl^cB1V6A}}H328z^ zVj2}iqhPxfT3{)ZwrpFv-E+>av%5g4k}Z>*?Adc>=9~Fu=18yJEu7=>CnB%&T zuq%#&f!GR$E+$ZT8-pmzh&@`zbbbII^ecdDBTQUci@AzAI+NK@zAqJz3&-tTFIn(F z_Jy~B_30SunL_PzFIZkh>|PSfYYbCAx`8$;6oh=@i(Y`4`pA@|QO2fOhn_3@7t&rX zb`+g%61Wz}PU14`RShV9MS7(Z@wuyjRt*K=jsv&2Og3pK))h7F0T9{ky7#W;sVs0+mk2f>-foQdL3!7_#NM=fBp#p(G}+$VFe)CWo~N1j_ko`9 z@55!!p9|$@W*g=Py)ou$GYHkh&{7zW1BPjW+bWXNzP000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!K1oDDRCwBA zTzcg3Lk0#0{x3iOGBEuA&p<5zGV1C39}GJ$zWN9dKrGjvei8on|3CNZPd}+?2g6^W zVL-DPo?YI@0uVqf58r(I^X|(phR1JzF#MyUEg-YLzq-rt{pIaH00G4En}Lbp*MBC4 zpZ^(w5{%UI%Kv{r-+(M*U;qdp7T!PK7=Hc!$sq9S8)2USH8TA9!@$4@bPNj%P=FC{ zqSTLMh1WYVkwO0 zW_a_RhvCT=eqcQQBap*>1LNT=(22kVz#@P*ae*xU{@@qGhm&6!00Ic?lTSd^@Ba~# zPgtOsn0&&(@Qag?;U_Bt13&<=F#P+=!1(tc1JfU16#b)OxdJqb`Okl#nG6g70mO3i z<4u+i4?i&ce)1a_hg7r#ly6`E0bv$^0AhJ{r literal 0 HcmV?d00001 diff --git a/data/images/flags/ba.png b/data/images/flags/ba.png new file mode 100644 index 0000000000000000000000000000000000000000..6eff4f0dd4eae402dc9f00d1247863f90581af60 GIT binary patch literal 881 zcmV-%1CIQOP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU#14%?dRCwBA zoU-!DLk0#0{x6?@G8k%$F)%VRFx-9cg@KLrJwv$hQ3h_#UknWY7zhJa28P4eRDoC% z$o|dnA1L(T*=L5;d+vP%2q2d8H{S~X{r8Xi#oO-;2hKcakd@(OXv;BUSa*PjVbc14 z43z-~85sUD5VnYef#Ko9p9}|23j^6d828l)i|838)F|JX`>%{{DR~hJ7d085p>~ z0WJB*@c-Kr28M5s{s06J(?6hQkgb3I0x>YCUIKkoT)&^;?voD;E4v&S)?M*pn70=g z1U!F$mf*+#{{4lR^A~8re~_afK>!dyOrSVG24Kf>u`(P!`+{Np?%NE$HlhskE|oE? zKdi^Vz|KGx00pTj~Fz>UNC6Ny<%YaO`0VD z0mOtmB3T)(+Kw4t1xW6#uPsoHI7fFfsfFCe6aG zV+@DRK4q9)Z^5wfei_4=>kj94H52q31vKo^4WKTz!cCxE$GfI0dxgL&*41`T;uhL)m4hKAjZ3~%2t zfHU&Hf6xpFRu83t03d)^F5h{_^7-9k28JKc86dHVAN&L=x&P=l!!5;E4BS6N7*3se zz~FPok)gzIKf|qCKr_Ca1LmV2kZk?yEl|T-7JvX^WKhm{$nfqAKf|BT#HUH1V>sDa z7`QlD7=-yb8Qy&S%^=2kpW)XZCWd=2#Tgiw|01jY2oPWZA#6>QF+OyT00000NkvXX Hu0mjfC{uzH literal 0 HcmV?d00001 diff --git a/data/images/flags/bb.png b/data/images/flags/bb.png new file mode 100644 index 0000000000000000000000000000000000000000..18a1e0ddc29a36b1d4b073c6adbd493a365ba933 GIT binary patch literal 898 zcmV-|1AY97P)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU#6iGxuRCwBA zTzcfeLk0#0{x3iOFfjaQfCCuQN%lO0EbnWEzyFvS&;cXke}-Dmjz^3ETC z0924s4!|G`LYZOS|F~)biu$vCkXJ}(lhU4$qVRv@E-b~UG};izC;sMrOuXFk>FN}| z|ITcM#u7kGj36C201GoC!=tCa7_Qv>&cMgV%y8%7PlmFNrwrTne`H{1V`8}f_m*L){pA6Zxj~G6G z`OCn{hR+rT27mx!!YBC`=vxUfRt5_LZicxlUo*%^vNA}CF*9hXa58M(`<_8TnuWo| zh94Ys_y9lv;k5(=zW?~o;Ni&6@Z$AvhTVrhF>tUmF}!{MhvDVxKMZM6q6~k4@yUSS z4hDb#f+TG`zWx3CAA^Q62ZMtJFGEG=bB0gA;Ax-sg5eF&B7IG6hTne(*Z?#eAb?mH zhyWnpxY_Xo-NVSRdCz+WEmaN%cY8i?iu(Vbuzr95Vj{+pA3y&wSQv6Ml%$D+lV*gk z5Ez3j_)o|dpfo@LF@b}MkdGNb76CKY_kRpFro0TC9E{*pMHoQh0U&@_Kp7B%{{i#O zKcu|G2v!5isG!LG^#_laauRsf#86g?@KQPmQF;b2PDf<5(;t+rUV!8GF zGt2wu4;X&FeGD$K(2JFmtiZ??`o{44-!Hh^{v(xL-~Rk&_;C9)kY)pBO4I`K_s5qE zzdyZV0SF+L7gr9w{dDutpFiI|V)gLW`_CATvV8?xiY*xb{9|Hx^=>07*qoM6N<$f`xIV2LJ#7 literal 0 HcmV?d00001 diff --git a/data/images/flags/bd.png b/data/images/flags/bd.png new file mode 100644 index 0000000000000000000000000000000000000000..00770ad1f40a89aedb6589238c40cf296d83b8da GIT binary patch literal 761 zcmVP000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!i%CR5RCwBA zTy%HQLk0#0{x843F#HEXbN~|h&j?|IC`L>rSi%4Q{~4Zqd&02o>9&sm0mO3k!&Tvb z|Nn8n`tb_I5|DvESs59a{xL9c{QSqj`sY8xHx4F-zs!sb%zytgFcPrj@BhCH*FIch zcyagz3qSy|Joxh9&)c7G86JIo!~nDmuIM`_BZJME9}Ir`J~Jpj_{AXd_7B5d6?TTT z&b$npop~5Ie*6PF02ct6_4C0`hF^Do{Q(Fd77zzyD+t3Z`N6@+Aot`CLrdo~2AMB^ z8NPvB2E-QUelZxG`^;c=iie@GP>g{I=yROG2NeGg^f4&d00M}KkrCD7|CksUIDhK@@1g2cXz5s3l)P@<1GZ@C$>-p)X*^5CZ@LhzVWb2Rjpk{JyOpx)1BkUW10AgYw z6EOc}U|=LtEkFQ~Z3!s=Ab>C|VF9}Eo*Wy)ZwA~hWd-5~vaAf>IGKpboB#oYVF|~t z{|tvsIT(Ezd-B);30hv5@96ELq3wgn)7Ff3sLrtQx>Obp%WA`IVvxtkjl z{!k3^0f>KEiJf7iD=!1rPy80ZvIjr_vA{Cnzkk4#0(BMJxBm>6RM{Dl+9VlLSAJkH zy72=Xe1Cz(%-j$`hFvz?3}4xR#mldMI8z`vGlI+mB7gv5)Y+)><-@}d+`pgv2Io#> z@Qam+f$jG{u#5je3(C(t%nU5Ra*SXw0X6)2{fFV_-Jf3p0*K|+kymfO9{u{~@29_5 r)&2k){1KW27$LP000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzhe8<7|37)m z@ORthj{pJ0a{l~z;lF?XazB6m9IGXA()>V61Q{6q{lm0`5r}{PXZUyKD+9w9pk8Jq zxxfDy{$IPyu;Ilk7JvX^xqbWgpI5J5G2FX%4`>diK{ghmKvE27$zSX~`31D()F%dp z4``ME)nEGgo8jEA-+urChy`RY&?gLk{`|pe$-jSpK+b|>H1=wb;#0MXPESndD_ zAQn(Y1Yv9$5t12!`3#iNu$5gP02GHy{YTD>|ACniA_NdXESE1|X8HX2Gfvmsxb>4^ z<3^zT?|<0C;p=~f-=BUkFnmWXAVI1h{`t%B=+9pkfB*tn@D}7AT$Y?Z^PS=KTVRp$ r??3he5~%avTOjcp%EtsB0RjvF8#S~dLk^wY00000NkvXXu0mjfL1@%y literal 0 HcmV?d00001 diff --git a/data/images/flags/bf.png b/data/images/flags/bf.png new file mode 100644 index 0000000000000000000000000000000000000000..fc472787af0d17354a56f68c09218f50ce938609 GIT binary patch literal 669 zcmV;O0%HA%P)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!FG)l}RCwBA z{Ih7@Lk0#0{{LS-Gcf%B&pQ^)Fs#&=891H*gZCE$L@y(D zPqY30&%pABfdL?ZSXSDyGJO5S%JAVe8!&PG!VFr8Q57Fapsdhcm6T_VrF6h2p|^jpZ^(nfnMhS21*J4 zamF_rGb4jCJ0ru)2Y(n0co-REn3xzI{QSqj{`)^pOMU{){rcxW13&<=5S4j>8pJsm z8P>k~%dq~{KL!b4>H>O*frZF~2@pU;SpxI~!xNyZ?f`wu4NO=cfsy&?4@4XvV1#B5 zfB<6o`~NREF8%>C;(r1;o&#K5KoT1Rp%e@%yFlgv5kLU3-28Zx<-@}d48NcJ1_lc? zixptRzy1TlEC2z-^6JQ|w_lHb{qq-?dT0bb0t6TUEScmuVl^5+00000NkvXXu0mjf D=>;ET literal 0 HcmV?d00001 diff --git a/data/images/flags/bg.png b/data/images/flags/bg.png new file mode 100644 index 0000000000000000000000000000000000000000..d92441bda4cac385f63bc9b012ba0985673d060c GIT binary patch literal 466 zcmV;@0WJQCP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzWJyFpRCwBA z{Qv*|Lk0#0eg@isj{pJ00wjcin44BMtX;d71t5T!fb<_)S#a^iP}idW z3=H4zerNb_?*jus0I_`j^O@n(?@tWxf4!%=CBOdsV)*&@Cj&qLu`n|-Gk`D)BMZYn z2I>acUm#>;WMlvcAeKMCsQ(SbznFpH_@9BQKKTPOlaYY|Ab?mTKmKQ6`}UuK>GMCT z2iKPm{~6wV|IYvrKrHQRelh%e1H?~$f|3OFQWp?k`u>|?rCAW)?LWu&Gh{~3M*%>$VU5I`*dZe3@&`ThgL&7Xg$ znzlfR=;80b43B>QWdR5vmW?moyj}h2$DcQU{?V$0`3MkT0NYolc4yY19{>OV07*qo IM6N<$f)qN=`2YX_ literal 0 HcmV?d00001 diff --git a/data/images/flags/bh.png b/data/images/flags/bh.png new file mode 100644 index 0000000000000000000000000000000000000000..4f6d433ef7263e915d3be2fa3085962612f0b95f GIT binary patch literal 551 zcmV+?0@(eDP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzxk*GpRCwBA z{QK+I!~eg3^D{6pF@VAU{|t=G%nX0tykYpVcQ3=Q$B!Av0sn#K{Ce9{~c0 zh2h^nVJ0>sGQYxdrsibq0n%00G3p2n!}8@b~?DhEG6W zeAu;%;s2jMWLfeLWDb-D2p}eG3YY~1703_Fr%a-gJgF%1DmEM!yjm-A_s#~CMZq;0tn<2 zmK!&2FkHWW9axM(672t9zZgF6+sE+c;zhE30Wt}c%U%G@2M8dRb!*qYUAb!2pVzNn pqZTcnJ~8~{;sVnYf{y?J1^|9ctshBUsks0E002ovPDHLkV1j|}>q7tl literal 0 HcmV?d00001 diff --git a/data/images/flags/bi.png b/data/images/flags/bi.png new file mode 100644 index 0000000000000000000000000000000000000000..3832f22a1e051cec98725e0005eadb313d7f9186 GIT binary patch literal 1292 zcmV+n1@roeP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU$o=HSORCwBA zeE#XtLjiUX{_np&GyFSql7aE&RR)It{}~vVnHU%t85xKNKs|r|F);l8&%h|Jz`$%_ z%fQ4Z!0`U_%Z~s7#PaFKJ7Ec4S#AzCP6h@CUxt5d91OqKE@1fo_6-9A&>k?x2SDxs zn$7SFh=utX*qrO|DX-TS}gzH1KIE8fLIa8*MN$v0{NOO|35Y7{QvX#{(poa|Nqb2+4tWh zQuPBs05RLf>Q!9&b%x`_t33?-OhOFOyb27A99#@6`sNHQcJ2(nUp;1ExOS6)frFWW z!y}M^BRqkDRZ)rI@6$UB|6hG&_@|-5z}>%`fio%_96TWK?DGSL=~tT=HeXxC@b>nH zp8x^G*s?GE!#lqB{Et81WnlWx%wQ?%!r&nt&Y&)+2MOtae;NMn+seQwCeH9(Q=8%O znH>xqis}qvo}{)6Mk@9)6y{m*ds^Hql9Kwtg+{hL97M~#7vm6L%)~cc>kN>)7@`B z0Ro5xH4d2=I9YfY{`~*Vu;kHXhN~aWG8F1{Fz~Yrf#o$-Y#BbyUBzIaV$A?ccYlFC zoqDyN;lRty46Mv-3_L7+kP!HfFc%xwH`27v&u73<~f4*V( z#q^KC$|#gUh);^){qIi0RjXN6ZRDUpW)}<9}Fx& z$jHXVaO=z_h711s442nV0LwpqbBp18iWS58Rh6h z`^E6*?{5Z6VJC(ntxg6JHcWEE|YW_3;A(1HS;K6JNhz z_$Mj> =A7@zcU@F7yPgj5i1{=fYyFVEo?|kzSAb^-1Pe+zb!C++=w7<~_rgyFY&d1Q27*&bSXkGE)3OvPlfmoQiNq z{yBSu;YV{G!{2L{Aw|a@AZ7((SbF>cWdp?-P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzq)9|URCwBA zTy%HQLk0#0{x843F#HEX82AHZN;3Xu2v%TZU}6CZAf$){Kn?$&Ffjbx_7Na}SgwA! zD*W&NKkipQUZGg>3&>OlS`sA7$iTovjwOGAdag07-SC11Ab?mNe0lKa?a#LikG?)) z09poD@)Kz2PbQ$Xe}I-SlViz0hKrYeGMqj4>kmKxv49Nz`~NQ(BU=IruD}05CNnaS z1O5Sx`t$cckp9mA5I{_fjN}sx&n3*971|Wb~ZhpMU^5NkJhTl(qGyMOLFzGkYPk&Jr0S&$Xhk;?%`+p3K|0oKzj{pG%0684F U-bd^b{r~^~07*qoM6N<$f_DnsrvLx| literal 0 HcmV?d00001 diff --git a/data/images/flags/bl.png b/data/images/flags/bl.png new file mode 100644 index 0000000000000000000000000000000000000000..692f8de71f2bc1c21ebb906d667ac2131a892f18 GIT binary patch literal 220 zcmeAS@N?(olHy`uVBq!ia0vp^l0eMQ!2~4dv3%YH5-4$vC<)F_D=AMbN@Z|N$xljE z@XSq2PYp^a&*TD3zs#^NR%aaBPP0`yW zuB~$wulX?{@n_Mr%gg2e{P_6$=jZ+vllJrQh-{qw^^@x1b+Ip+#P!$x_|p0G+}!K_ z^Y`s9f6u$>N&gOuYPK~G&Pum2vtKx(XJ{}dJs}}!ft-YC_LVh(n>Cmj7}*#&));J2 TS)U;YbP0o}tDnm{r-UW|svue+ literal 0 HcmV?d00001 diff --git a/data/images/flags/bm.png b/data/images/flags/bm.png new file mode 100644 index 0000000000000000000000000000000000000000..b53c416ebd8168eb1e456b2c6dbcc1dc934e4f01 GIT binary patch literal 986 zcmV<0110>4P)P000jN1^@s65oToN0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU#d`Uz>RCwC7lv_*`R~*KFGdsHsuw0g1 zCZfwqf=IB$_QirGUSh;pHNiA#ZQ58L8|{-1v`OPzNq8=jNFSOs_)ttiinWyVVq6lz zf&nj}#fm`ba$DH!eV3hEhl{k2*0x_Jb8;s0o$ve3|9_6~*xx&?Y4!GaQzrr}2z7!fp!h4RNM;8lRqx z=D&@{yN4JR>a<;XQZaq3#E(O(cuE#^zG#w;6Vl2+6 zX>b=bscEp4f`rQC{5*=wjl1U=LMV#S(@CMVHHSonI?I%EOAhMntVPjStSrTGy$9PK$@+9*b75#%~b@gcV zCkX%YkTzE(!;We`SZ?7xo>CHsf~N$$C73_XV!zyEna?6`=T7qK-zKA~B<(iK#5$x< z*;Z;rsO=x6Sys@ij?kDR5_Y?}=-5wPnZV)kyNu<^gfh!(q`1S5kXoUurF?~^)cceb+tBEZ#$K=f}E~i}_b$R(b z)lSZme~lD$%fz%4fk_p2%`pN}Ilk)c$SxVC{7gmX*i}$S__>Jh(j|;=gr@OPWUC`Y zE2F3jc-q?OHNV1U>Lg8(9H$d05nChKU)e)>D9Ne5E}Dwc>?^9Uwy=Qm)8|pX_oMg{ zh*sLoB+}|Tox-85w%ulvW6P-6HfH%)9TSOclKO=pa}(XT4|+Iv=pyb&2$zseQZAwB z)gyF1_>vm+G0u(uMP35V2Sa@9ZRJK}ih-*aSda`Rinnn4i$=~bwD8&95nRF0UrAm@ z`yGrZW88S6C(&b6AJsO|1YE(~XjQpD<7$%=K$7s19XIquV%E2U# zSd=$^U*XERG#_3|tY0e@gta8A$KN`&rZwvSOo=zTVjS%j7_X3Uh75ec^%b6%SKk-~ zGe1B6Yc!hs==3@}~d;0PgyDd|vla{{R3007*qo IM6N<$f{iQI^#A|> literal 0 HcmV?d00001 diff --git a/data/images/flags/bn.png b/data/images/flags/bn.png new file mode 100644 index 0000000000000000000000000000000000000000..d8a6875d4393bbda267d32b242189882dcaa589f GIT binary patch literal 1218 zcmV;z1U>tSP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU$R7pfZRCwBA z{QvjdLx%sK_!<5`W?*3W2Eyx=cT%}s{28=kNL1P}|5_Xi@#$H2gn!2q`G-&G*K z0F+q|i>;U%xO23J5T;b8rF`1A~iE9B7dt&_XLuaGx&u?FqoN}F&G*eF$habGu%D)mEr4#qYUDgfM$Q@VYv22fO{*3}HxShyKpTsz6Ybm<7gw?7{lWZ8Kcp0H>z$hx*LJUPeDaQ&nZgS?I;Fg?Bo z8u=6GjjureF#&z@7wi*&0Ag_r;bpM(kzsgp`wzqUi{BXbZ~4NocIPLCuiyVM-2M57 z;r-_i4F9_-7~cM2VK^?Xz)%DXhR^AR46-c$7#_`;!0_(=Ee4I$0$@T^0~-DUh;IYq z?=l0!uQfmmpMz5wKmalR|CRKCf$=Rr1M^d0`2Pnb3Wk?=elgqu;vJhlGwfOMks*hV zgW>eg-wYF9ePWoXAjq)nHw%LT2RB0sKTx@*E`!^WO_*ux-)DyNXSXt}UbE^WKmf6T z6T?4HLWVMc!6~l7${?=B#$fHn!=T5*%JA#ZPX-Gp&mw`ur7KKJWk&jkK&B zLug0{gRz+@0~pz3E0V~5bQD%mpM}IOfYp^i<5MTnj=^ukO zH#5TzBOZpcZ@(~z-{xZY{OJ$Fm*4*w?%lh`(A?F@zy(a!Jlq@%>RLJs009K_NdnL( zZ}|WJd&=C#5QQ<|d}62BjvZR2H60wP9djZ1Hq)45_%4^k;L)J!dvHwtxTQ z?U+~o=LunUSp3hv_RNR!=g;~X7>|5<|4THnt-gNe|NV8GB1;#k*8eyBv%hX;;^EEV zIw1M~5BJ*tzrQ!}$A5_z3+qF+Jh>m==6%23c+T&Czxf>)HX8i-dHleK*ZKng8NL22 zo+qnto}rex$Q&e7U~FvgCm|`}0qdpz5|S&pW;n_vq%d$Oh_&$;DllgpEMIWoe|<=A zMceIoNeRBa$sj2A6`HA2if&7;m`2{XMfCZ6wk3% z4%Pk1>a#Y!Q9_|1BCl@G$xja-vbWv$uVdC-nD^?}=U@CBn;BRHPOny8f3ME=*Vps+ j{p&NQ?yzHGkzkm>wfOh0&dMZUq%nB9`njxgN@xNAT?5WE literal 0 HcmV?d00001 diff --git a/data/images/flags/br.png b/data/images/flags/br.png new file mode 100644 index 0000000000000000000000000000000000000000..9a9d5e6159df80cf96975ca370a50a3fc1780dc8 GIT binary patch literal 777 zcmV+k1NQuhP)P000jN1^@s65oToN00006VoOIv0Du61 z0Dwd&lT82s010qNS#tmY3ljhU3ljkVnw%H_000McNliru-vt2^00c(uHGcpA02y>e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00MDIL_t(I%dM2LPTOD9PNS}fMke9Bg?a1x{2{9lQkraiY2ogwa$KW`LW5;oP zJ0zmCLV_Vj`gQf+U;jDx=t@UPl0=%#Ce`aItDjc6zrSZ`X$jBsh@uEh({NoERaNmk zk01zm53<=TaU2r_0csRQ42MI)Amrxv4ZU6uNrZmC&nOx3ekbZ8MBx!hV3-@AnDAkZ!lj#l=sQREnP8Ba_ZBJ3EJEnS5MWK!9)%l8llU z^C~aMCmjU@g>JV)JRCbgM8q^r+27w6!!X46ec9UD62mY=L`2gxxxKv=5s|gEHJPx! zc}k^{Y;JB!u~-x}NfI2#p;#=kva-U#!2w4{M;sp?b8>QmX_^>@f$#flY;3T+yo{nK zZ|E@f?9gmB>2|xMQYjjZ2EOmJy}iwQq3b%CbQ-VI zL2b2Kj7B5umd)AMGjh2cx~@~LRuK_A&*S>~nrt@9!^6Y7TmU>iK4Lo#t(J{)ets^E zMuT$slHJ`eObew_iHAmmO1Xl%xVXrSIzzkN=J4=vY9fx~Fq4|0<9Vo>rZE@{&ceSiJq8yNHcJ3=Otes=N)Vv2&SSGjT#00000NkvXX Hu0mjfbn8(J literal 0 HcmV?d00001 diff --git a/data/images/flags/br2.png b/data/images/flags/br2.png new file mode 100644 index 0000000000000000000000000000000000000000..22a98684c0f509c48fc8573be1418416a8eb8608 GIT binary patch literal 1093 zcmV-L1iJf)P)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU#*GWV{RCwBA zTy%HQLk0#0{x843F#HEXeBeJ&`Y(`X0%1l50&4#=Jo)y7VcXMf9{~c0W-~Sodm>C#w+4cATUxsTRt}(ng{DK7_fLI=U zdGP1$&$kSZzCL0AT88N&ApZ7;fkBOjk)cZSKZBbL(1l+Z80s!FF>HJGkAa(+5v#92 zX8nBdli}ChUw;4shy}y}*$Tqwmiz&7zXHQ2O_7nIK;sv~&)0eklQ(EH2ylF7m|?q* zVYS$IhKkD|S2HkhFk`yzKTrY`Yybho!pO*o)%D+jh6}JVFr+9lF=Wc|F>F5Qz_9v+ z8N>CbG7PNDzZq`5lwqjw-@?GI_ne{k_J4*uU;hJrg*~nS0*D1OEFs}beE!bOFd=CwgR{tghR)l68CE_0%K(fj z239nS00M{w-3P3|q?0Vi!ceBl0t|xx4F7(zG90_D&7dgyoq?b02gAp2Obk!oGXwEg zhC??L86LlpWRMkp!qB11#-PRfhoSMtZ+HrX0e}EvLAC(oiz+o%hEydMhCe_jfqe8I z=>A76Qt z{bcy@7hwrN0KqL}gmM->`opjWh*^NCh7~IN_a755Q80kq3zahhClyNeF6|b$ZT+=-2+BoEzrx;?*3-@@SA~wo%1h) zuIzKLr+GOb`G^bXn`iI27;e9O&mb*&3m5{d44a?*WvIONo8i#gf8b1u94!C=gc0$e zqzFp3Ro8zr8~|!4(_mzXu{+3c@u4!qx%-L?KYsrOI)<6SMCmm{n#)Fpr$7HNv|VLn zSoP!&Fx4@FQzUwp1_&T#?L_U0Z-2gVeEj_pp5sA2;sk2A4fNH9Xa5=GdA~ALIbLR9 z`Y*un={paDj_ebL_Q<6S93s~k0*^8>oO=I{feTn1pr@*TAO11?di?7rKmf78GU2~} zKyUs-bpsbDK7hHs=E{FyczP000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!J4r-ARCwBA zoHl3XLv9Tz{=br34F4G!828yPW|GxcXkO($rkQQTP`1a*5 z1IbYZw1kD_H^UF6zYG8Y#DZP^AH%c7hZ(+|yv2~+A;1vo$-uw_)byW(r~(EM!$qzi z4D0WHVE_mq7JLdn+ULLxT<6Ejfff8s6SV)?_s#{8E7Ab?ni(U6w) z8t4@!hNL8J28N$Rdgnia1_&S`EMa7*Yms6I4`pHi#w0k269pI%G(Z61wM5-gf*~V? zlOZ~ci-D2#H^Y~oq{Yd%KmQnh{bgnV2q3JMFmtjqi24~Z2qjxG?B)B%u<-;ing1g_ zb$z+>n&I`++YA5!gkcE_FDHX^sy%~Ppb5i|uRj?czF{J}So!{!mEq?^DU1Lg0RjvF{-5e1PcX2C P00000NkvXXu0mjfm3%2q literal 0 HcmV?d00001 diff --git a/data/images/flags/bt.png b/data/images/flags/bt.png new file mode 100644 index 0000000000000000000000000000000000000000..b87008a544c50def39017a92ef007e4ffb32bcc1 GIT binary patch literal 1156 zcmV-~1bh35P)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU$7D+@wRCwBA zeDP@0Lsnh}{=a{}G5r7kpMffX5rTjJc){@Y`k{{i0mSn6*F9k#o`2kbe!T|HUB5 z{DtA#{Z9;+?tc0M5I`)DAo>e5{x1is z4EsO7Sr`O`-!T08_L1S;dtea#;$dK5mS^~Qe;0!=%Oi$&FW)k}`~8qXfCCutzNKQ99-!wUxC--j5!GxITsay|qa&d%`n9|Ob! zq>K#^KrF~9=AfrFi!fdS~+myEm&0*Vm~EU!;8{QiEB zL6-Fe!w*&shA&*^3`|0nKwoZTkoa>99FOP#Ab^m3@|WTB2T@?eDgyH@Ft>iW!0_wm zRR-o?zZrgU8#DZ75MW^85MmJGe#5}Q@tlDfnD`m~A7yy|o1Ni1n<@jlydT5&Tc;R! z*nb0k!U%Ub2mk~SFf)QOAuzcy0+Z;^PYkbb>||i$He`_exsQR1?*}+1t12fBShTne~!I>5mT)=<Eo z0gMM@fpYqjmp>TJ-2CztAb?o5ZMgsT;NjR0000#H literal 0 HcmV?d00001 diff --git a/data/images/flags/bv.png b/data/images/flags/bv.png new file mode 100644 index 0000000000000000000000000000000000000000..3f24fb54b32a1a6a0793287ffdcfff190eb6117c GIT binary patch literal 884 zcmV-)1B?8LP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU#21!IgRCwBA z{5pNgLk0#0{{NppF);l9&j1Gh7#LW2I2oRKL@=yB{ggppRh+>|@H@lTwQGQCfbxtC z1i^oxS$`isVED0a^+$jJV)=dXyfDMxzuf;`JV#jamw|yvh?n8LsW-#1i_aK%xj7h| z{(WZnb@(9I0Srt;Sn~HT!=DT18MeIp$N~^REPwCZ{`2qkD~7-K?=k%U_Yb1t56~x) zLJaRCKQSD>@Pt8GP8cZgmf_cdeLywDSpsx8!_|NP87}>2_yZ6?EDS({LAL(?12h@Q z5{BQu!CwCJ_a9LBKg5?{HN;s0Qug;hga!y87KY!z0R9PN{ea3qgZ$qQpa3Hz@EJk$ z-#-vFWB`BwVquSsX88B{3&X#6Z{cym2sH1Hf*iwtZaxM^pzr?t_{s2BQI&zSx&jhh zL`3{QpjqrMUox;G_pf5=eA z#KOR&5Q?G(f9m?j@QUds!>wlw3;+Sd=$F6x!>e~+`5!#{2(}#=Facf6#m>U;>(4)i zU%&qm zX(__MaQZaSnGu*~8O}32V))4Lo&g|$Sl(4uGW>n{g5mGIyC_MRM^cdC>-3!rjLggo zznR$>7*3pGco`HxbUpzodkb{XS762l2p|@?%l{)h0%8Bh2xAO&1OOvc9Y6rF{0F8Y zU~UBJ`^WGX=0YI-hlvT8X+d(J%m^$ZKuMViYCbW=3MexIz4IR!v;YCb^5^PhmfN2` zGu-|M&x>Fc9KeKd`{Y4}U$1X7+`06bVdKls3?Kdy?-P(&kAdo*{AXYR2q2cNA3nd` z2z2Dz|ESJj{``~S)7I4tKOTQ#IRE`T!za$)48Q)8mEk`E1Q-Ahn`kfDaIBvI0000< KMNUMnLSTZTaGM_h literal 0 HcmV?d00001 diff --git a/data/images/flags/bw.png b/data/images/flags/bw.png new file mode 100644 index 0000000000000000000000000000000000000000..46a79b45c3628713215c2bba769fa1f9fc0bab40 GIT binary patch literal 525 zcmV+o0`mQdP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzpGibPRCwBA zJo5bOLk0#0{-1yUGyG?u9srs4{>NX2i*J8?1PCCOr(b^y|Nqax{pt5V>RJLc^y$|> z443zAVF3srmN!5C{`ms5;4RQrYWf9e2g9p7R~cU3xbz1gfLMT`#PIjue}+H*s2p7X z{xSUd^OxZtkOl}K7EWeHhCeKf3|v4=T}v1lIDlreFflLy1Q1J#rXT|?0LTaDS)Vbi zzQ)Y}5I`(v&YWfV`Rfn}8)P&Wy` zy7iXf*3+*H00G2;5_$|&Emwd62nl+C0Al$M;Xz89e}Ady6HvVT1)B>HKrGL{{b6~2 z?>58R2e%QCOr_xZ`1~=$rx#CH00M~R%HFMSZ|>dr=iB?Y3^W2C0RjvFc)aW5SO>m3 P00000NkvXXu0mjfBP`}_ literal 0 HcmV?d00001 diff --git a/data/images/flags/by.png b/data/images/flags/by.png new file mode 100644 index 0000000000000000000000000000000000000000..030b0f6b9852d1351aa73848b3919a7a3c08f825 GIT binary patch literal 652 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 literal 0 HcmV?d00001 diff --git a/data/images/flags/cc.png b/data/images/flags/cc.png new file mode 100644 index 0000000000000000000000000000000000000000..e61fa7683b31bfa3bbbd2d6493abfccc7701f172 GIT binary patch literal 699 zcmV;s0!00ZP)P000jN0ssI2o&@u<0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!T}ebiRCwB?Q%h?TQ53$fyk=rZs!m%a z(v2X6QXy1|i!KyJbmhW7Ah>ex!lf%gq+Lji3$+U&2B|5uV2EN&GI>rq zc{KCB*E^HcM!{C;SzPAcbLacMbG~!od~%U8*3vuR0FhHmkJnd7s<5HkW=T~DVG-gq zOzbA%et%PMMg3j6oN28UNVbtLHtOR-=Ke&@W-wI`-cBEuH4G5@I}n_aGy$`flF+|a z)j(N8JP{9Di)Sk@^5Gjv$)NDzR1FT|FbEvzuDd5-JYr2mP4GJ%;aKN~wVaOE)WAlA z-0enR&QwN3yVNBvr;Ua}_UV|JZId7Tq*p#>3)zVKjhPEa`!tB>syaRuHD(jagR%0{ zsdCKkv@L$lxSVq%+Q*B(VH^vO0S6 zWOFoVzF!red=CRcxpGOclb2%&))pXqx@KYJiqWlutRZ hxdk*E95{XoFaRMh7OK^_e*gdg002ovPDHLkV1fcP000jN0ssI2o&@u<0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU#7)eAyRCwBqlzB)LQ5?sAZ{C}oc6L{H z^UB)L@{b@S4J)+EFiNP%h(bDulG-pRgX*8cqWmW?A}huAAI&c4FhVLd5(_#^&BTtS zG_l%UH8H`BaOqT6dnj&o^pRLSG}b9L9iA%28@w?pb$|j6IIdk(VQnSS&6)>!;|s! z%VJU z@tQ$_@;@~y9MB2rlX^-Lt?N4LHKl^(<^2`f87>RWAXnhNl41Kq!8x&s}UhpStn%N6V*f0opE- zh@q-xMjSIY@?q8Bc%yU2)GCX;Hu;#{)u3}y6B9jTKu%oX6}IbZ(zjF$?X-2z1SA6P z2BeT*V^S$Pjm}#%&06BIrk(2hazpEoj40}`sw-iZ8|@-D=d3ONy!%B@2G9^-_mA7; zXYphmqZQYuT-zg^%c|@0T;l9*Q?vz0t&?en7Z))txyw%acD24-05S#a`7NP{_Yl^p zX|9;wxR0&Mseclv!-K=I^!p#NQ+!&B{Ak(y^5E{Kw@X06K>UE|`0}YN?L2e)3Fc&e zOLM4(4)+@9_plA|f_{_KwPtn&Yq|b@r7ZG*{XmpIjRPr7Ezf>@1D$#^(i5Wq{zlR9 zLDS~I>%y6*wU)~R1)|>w*ayV>_t8Q^eq2YB_-?GeNJAt;wltiV)MUzQ9M1LoO~3@y zjQ%m9BcI@12dV^J14yG0kEte3mXbA2ZjvLIB!#%>vqAs=;rJ@R0LVXFx=0skp#T5? M07*qoM6N<$g3dXo`v3p{ literal 0 HcmV?d00001 diff --git a/data/images/flags/cf.png b/data/images/flags/cf.png new file mode 100644 index 0000000000000000000000000000000000000000..f136158df26c8d074ab35bbd3c6745820260ed52 GIT binary patch literal 925 zcmV;O17iG%P)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU#FG)l}RCwBA zT(sfpLk0#0{?A{3GcYpzW%&J)3&y%AJEdTxkc?=AP#dR32 zaVayf{{O>(4gCNApW(@iFAUob-v0;?KrB~pzZL%Vi-B8K2A8Xc7})>*V_*h~-BDp?IL61z!13=VR!jZ@9dqsWTZR`8j`|l4Nmi%M*_4x_IkB<-j00a;VHzzBDAm3kx$4^xlsuyT6uyOun5EcOW zmkqArH_#I9Uo1c`0WJOm@gWB*BLg2d8w1zhe+)Rni{bk}7KXncnHc~Ai1F+9Umt*c z{=Y!I|ABIhK$4LWO+66*yMBt{r;Q`S|DS)reD-O53@mXuVD;F*KcG)8UA(}se*M~y z00G3p#lgxz4EV>x#_$@6&&9#Qz`>1MFUS%uU?{P%GBW@K5EBEj;2)aO|BOUy1qdLP zZ-2f4J^Y{H-~WF&6@i26_h%HbZ-2fs{QC2mf$=|XpMcE%_3sx0Kmf61pUh@>^YabE zlW$LOTJ#&322^e^GE4!+8yAqz49qDV7iKf8JFt|2>-&G4F8hA>JHv;29~b}vh~+cT zHJ^TeVtD`SJx)u00r98btQbCd_xmTq&tJGL`Ss@)!_U7z82|!^<;P!UhVMZ9?GFoX zOMubw;}0{M#UMS*42*wpTk;2(+<*ULWB>>tmS7nshA+RF7~cP6!jTw(84H*vgn)^a z8EDVHKM)g~Ie#$-tMV|gf5YxDkb7SM6U)_Gj0^w)#FD7Y1Ps`11Z{2vT8#XHLwBXJEJplv^Xh%m5HTEDXPIvD~=%p5gk08-XeA_dgt#{AYOZ@HfN52ftYW0*GbZ#usl_uKx7r^_xGq6FShd z|GzK+OXz>_B+YW>I|K7upfCRYCzMS-0t6TU4|HJmQ3AD`00000NkvXXu0mjf(-y0p literal 0 HcmV?d00001 diff --git a/data/images/flags/cg.png b/data/images/flags/cg.png new file mode 100644 index 0000000000000000000000000000000000000000..2ec7d0e8ba3f3f8ddd3cdb6d73ff446774a68edc GIT binary patch literal 809 zcmV+^1J?YBP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!yGcYrRCwBA zTzO^6Lk0#0{;$73G5r7kpMhBL7pRn#iGjgRgn>bc7bt+Pl@Tbw^nl^hg3TWR0*K}2 z+w;Qz{{7{C^X)lM9dVZY0V?BVVPJ@r2U@@e6!?aw0BAV_%U_1C+pjV#Y5l?i5I`)C zKi>ZH?)xi-r=RXI`~yNl;QL<&1|FaVGmIG+^tgcnU$I!g%E0hp7AwP%DQpZ^e*FCd z5I`*d{sWEv{}<@}KMWu?KJeua1A`7914F$I1B0#*P~bO`Bq$I-zG4Te+sMZ7dL}2s zKW3nrKuZAvh=q}Xk)Z2A`o91Tv=L=s=+b9k5atAh8JZ#vU_5quJHzWvE(TztWMBmO z08bVJ2p|+oKoKkiOoq)sA2>*2iC`v35PjVQOrl*t3xK|3WM^O?l!pNV2yV$&pn(!V z4NbZX4AvkEexa#kg#_8hxf~2{XK*qAvlas*D}KXanFAnzSpEW4fByX!SO)!LsMKU& zkOJm`Ki{!91(;7>wQw_h-og%vU>5v7_y-PYNI?YYf}uZPSG nf8PSNG5x>{M#A7DK!5=N&YP000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!R!KxbRCwBA z{Qv(y13LISd*(wR#n15h69WTG9z-K6WdvbHxZM9o4;lWgU-yyi@e6r$^8f;fg#ior zf9awy!{5K$3@@G|3}ylv$jrMz5;OBWc{zkSaVj>SBH0K#s`-8+96 zUcYAe|KL8@0EWMR7?|2x8JHbiAwB}y_y6V%hCdk@3=F@1fyFNU`O9$b-@iZDECC20 ztd=nR`v)=n&mRc>|CfPLU4wy9OBbPrl?|v4sOI-?hJQdG{sC!YzzzTc2)iY)u>SFb zf#K(0NCihZy zk|;o_5tIsXxCtPDuv@~!1op+B%rt1k{bgXDzmS31%^MMyx2`k%_VEIzKu{9?_YvqI z7VO3W1Q21L0P_LJ5{5s&(aIj6*t@sDr2GS15-@_ifx{;N0fgNWa4!GL@E_=#zaZEB zX9TnVgMtL+6Ho>O$pJCQCx3xO43=UL3;+U%5nBQIOIrNP%}<}WZveykAGl!r&%mgu z$-p2cjwri6e`ff9@gg`rfVqg_AyCb^zyH3j`t^qg-8_H*!fMGXF7}73|NP~DjVuQ$ q4ADzrW{kx7@xbrD7^N^kfB^u#ur(*D1JN!30000igP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzoJmAMRCwBA z{CZ^3Lk0#0{{KI}Ffjc8&wvd6|Ng*EZ+ z|Ncg?Of5Dgm%@PKn>oCO7z<(eFx%$ulKMc%3h$O-A z|L=crumJ=R&}v2oS_6OpVq%~J00Umq}hz4{fS>|y{p@bw=MW&sEw zmaSJ_z1?{8>z}vZ{$dsxK#eRfjxaDEd4RX<`uO1^xcI;UJ^}<70QscTVOhf;>Hq)$ M07*qoM6N<$f;4pK00000 literal 0 HcmV?d00001 diff --git a/data/images/flags/ck.png b/data/images/flags/ck.png new file mode 100644 index 0000000000000000000000000000000000000000..a896cc24edc29d0c232badd5416b6c5b8c4e848f GIT binary patch literal 1095 zcmV-N1i1T&P)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU#*-1n}RCwBA z{C4cr!{6c({JSoEWw>?Y6$3LjH$#Eu2L|>lXBZA?_%Ix~@P>h#i;clrfr-IHg@a-D z&3_E1Z+>E6U|?dv2mb&6&+y>E2Zq(FuYCjvAeQn)&xL&z?s2OaA`-&v5?ybB2!} z&$9po5R0yc;Ge5M_!)Q}zh`h$dct6qVaveTzm9=n>uv^*j4B3UDHewB8d?kscfDr# z_2M4xfGoG=`a=CIA1w0J5I_0SF+*|K83Y7+$^PXSjKnf#KJG28Q21fhzt( zR5Aly%f=3IIVUi9*q9l5zF9Mrescy&{y+{KDE$vD7(td?{Rj|1EMJ@h8NR=H&%k2y zok5TXXy~tR3=GFkGcX)J!oW~m#K6GH%<%Wq4~Cb2I2hO&{xG~f3$*mqPh>YU@bPgm zFasSVA;HV=?Aa%VuU~%y-SrEu0w91`{EtX8$g3JKu;?}~L##DF1OJ)h z3>(c77&zG&865xKU|7EM3q${Lpy%(u25S2RjuRFZW(GAiAqGW7VTKnkzA%W0a4|?r zb2Iz{dg#%k_Y8OLJO^6>5I`&ifsza^{BIZzFFDANxa|+aWfp0MoM50KTz?tP@3_d& zy!0MJUWx%jrIP@IkJLkk%%#5=_J3kzU}R)uP*xTMS|Y-*b?Ysl%RzDQ7c8!%B*dVi zBFyma9TyP)X8;HwWAm50IF)+M(b^j+o z05RIy&iw%ND*xTP@4ym}$OEMu4j}%5l#2dBd8!7o>P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz`AI}URCwBA zT(sfRLx%qh{NH~3W?*J!V)*gv55vEI{~1L2IT^ydwZLK&f{y?J#B%la8{zN2{&0W) z_KQJJO_bsA`KJv3{{LrCmf~j!@zexbLYWO~*REv&2q31HuRi}_`tg%NLzbT*Hb9R- zjGKkw&*$$9m##l&_zUzACE()4iwtMap8W$5KulkN!NkYU#ZZuH&A`vc!BCuL&F~-S z{O>>hGB8qL!M}h1823xbk>TKlXABRXegI3Jxcq`)*U3i=EG!hq1snhb z5X*mH_=^g1GMv8fgkje5a|~iaTnvIhAN~JNH9Hsp0*K`s69>cZUw;`2r=Dea_3;OT zyf_cTcXl3zFDzUP4D2jau>>H1SmM7QXZZT&1H-%b&lwnjuK)k+KRCk07&sXiCf#EA zLuL{NrO>~RpE3M9e~AGgfLOx69%o?q@{)n!^F2^(K~#WZ{L_DiADzn?ev=#VK>P=& z`QLX227mx!`Nhc2@E;gt|CuRi62b!j0*I!T zz(N5afLIv*fxPsW;Xg1w{!%g7f&w079>_?50Al%f>l(|=_a7N<{``j=Ba~YLH2vZ4 ze+-X)|6>6NAeN0U-oIV_>DQk(fBw@d)II_P7yu%`!%Ux$Pm2Hm002ovPDHLkV1m<= B7l8l( literal 0 HcmV?d00001 diff --git a/data/images/flags/cm.png b/data/images/flags/cm.png new file mode 100644 index 0000000000000000000000000000000000000000..a5d828f1b35dd556dbffb9954b19d2d12a4e7842 GIT binary patch literal 699 zcmV;s0!00ZP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!O-V#SRCwBA zTy%HQLk0#0{x843F#HEXWbl)Xk-_ZZcLuX-KNx;7U;>b8CLv}9t^^(i2B2Doe`vD* zpD-}|-S!b6fLN}6xGMbb|3B_mKVD&2@{NO$LE_qH29twda9YAF&BE|MMwo$-1>KUr zKn2$r)^2#g0uVqf557G3^Y-UkhDTo?F#s(?G2}B36NAy!_Y8mcy=VA|!x9!1HU@@2 zl0Xi+CI1*MUi!&!_S~;O00HQnk`w?yAY{@1U#w$Rr-B4Q1edXjWo^DAv{7ON@rC+% zjdt0UDk}LhrT}7MWMpI@1u*h6L4t+^01!Y-q$maj4^M*-1Ct~(NtOTv5EHSgm_(Qv zSY%ij*zGwOn1S)YC=RrUnUMi4tzZQJ0mMX<^MS#`=E}t&d`y~wMTd<+aIF{vPk{gf zBL{k9V+Q~MMA#Bg@c;VH@NM~LhVM(hFfg#P05i~khA(aJ8UB9-+CW6e0R#}yal*vO z$iO7P#PDr8FiGG4!@vv-I${Ap01>T{iJg(*`-(3NKM#FpVCDm+FhSze3_t)8ZOOk6 z|G*Z2Lhs)vU@(0m92v0e0T4heuuS+5n0x;JLrW^4%mfqyWyL=@(gcY3^XESU;~!|A zM9PeRf%<{@nE@bxSZ;p2$@1ah2ZrBIegg$iE&0X4#BlxTFNQ5Z_IF%m7cla9w*h^~ z!GMy|K$bjs_=n-)qdzPF0mSm^$g8(skAD60_tRg@5(AhIPCoz5@D$aNs6he@M%Fo> hAgK&{p7{t6U;v=+>S{{>sNVnp002ovPDHLkV1jF+DA@o2 literal 0 HcmV?d00001 diff --git a/data/images/flags/cn.png b/data/images/flags/cn.png new file mode 100644 index 0000000000000000000000000000000000000000..2ad03f6d4015434b26d3882fa9820d0e733491f3 GIT binary patch literal 634 zcmV-=0)_pFP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!3`s;mRCwBA z{Ih8GLk0#0{{LS-Gcf%B&wv6Lff!AQXaF+n$s>k8+ctg#2q2b!S1$=O{QJxO|J6%~ z#6O7P|CxZ;iHm{p^iKxH4}TaKm>5a3@Ze;CgH`uhhUfS4Hm0iFIA zh?)N~{Neb=!05ot@So!^n9q3P3j>2cE5koQpr&6yi~s&18vg@^${$d`0BL{#Vqste z1_RK{KT2#2EHP{hY?e$6Kc#pX{x1E_!1N5L;pQKP|6l(zFam>%+~@%aASM{ebp9s; z^OwI2Op3s8dH|%a|6*YJ{g2`Qd!W%8tPKBulWPS713&<=z*#&@48LD9GW?qbjA$-k zq;mjW4h%&WVMdHZM=AgaAQo6sWn%iz@PFA?hJSB?CJQox#r|1xGW=y`WMJO^je&uK zk!(u<0*DEbFcfc5$hNWNib zq{tG00Aj(NV}W+DGEfBo1P~Jg4FMxG0007r1(f|kx${3TQ~v!=Jq!Lpq8XgG00M~R z-_2_*H$Qx2xCzV?|EO%q!$1ER9{>5r0uVqf>tB6%yYB1HKd-6j4V0*5{0IpF07*qoM6N<$f&!El9{>OV literal 0 HcmV?d00001 diff --git a/data/images/flags/co.png b/data/images/flags/co.png new file mode 100644 index 0000000000000000000000000000000000000000..362f006fb04aa928dac9bcfb15980a0c5169994c GIT binary patch literal 478 zcmV<40U`d0P)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUza7jc#RCwBA z{Qq(KLk0#0eujUafz*En>H(lxe}G1RUH1_nfLIuQT@+^c|CgKL?+Yqh@|S_(=LLqf z+upGN1P}|u?>m1O{=Np3xLk%86#Ab^+{=l}o$h=qaU7cjJbGBB|Ipl+OijN$;>zyJ_HENc!} zGkpE_f#KsvEr$R9sh7GyW<7cQk>SG4XAA%V#4`VYEyJsK-x=;d{S1sGDkfo|S%1Gg zWcc;^3ca!NAA;o$8kS0!PCi27mx!0p~v;W`tsDW_YlfKqf!{vEn7gW>4+PYeJ7#A5LF9>f3FFBty5ya&pXR83s`K${)` z82|yq^7Z;FM*GOz#y5X;sNpWkl$$MEOvf12gRj{pG%0K_Dp Ur@M!F;Q#;t07*qoM6N<$g4Au+fdBvi literal 0 HcmV?d00001 diff --git a/data/images/flags/cr.png b/data/images/flags/cr.png new file mode 100644 index 0000000000000000000000000000000000000000..a184a8dc7c0d20d4ff7e41ba29b70d2419a4a4b6 GIT binary patch literal 783 zcmV+q1MvKbP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!p-DtRRCwBA zoWA1pLk0#0{!d?jGW`F~Ks5j|>(TQM4C{7Z{|FF3Ea$F26aM@6Klh8*pBerECI0>U zheszA69N7L%{hPN8N<8B=U4y&h$YrX`_Hdme;B@g{|&U@Kf|{lARl3x#K_3N@aNZm zhTp&b0@;kj_=UkpiIL&tvFm>T0*J-dM45pW02CtU&$BTweZ9^A5I`*dzkUUYKro(A zV*Cg6!T0ZAb^qDf8UC}eF)#w*e`Z#kmi+tngW>!X%XE%o5f&vUYhmSC@-o6R6h?(I(hX5oj(JcYu_dkC!d;@v{Ab?o@fBMSs z|NUo%|8GBHw*=^o|3FK=#3wR5^7dzV_~tW%mZ>F!Y<>m<(~I{E49p+!CkCJ#Kmf5Y zFasGZP>jtZK(YUPoD6@o)EPcrxX$qTJ2S&S1s;aKN{S53FW)o#XT=vW5DXANEDV2u z3=sZ}BY6T9G5!9<@a5%ehEt!V8Gd~K#h}achJpG0TZsBU30I@J}bCH|} z7=?rgs{vZV#LLUT08H)x0mO3j*mZ^P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzY)M2xRCwBA zT($7@Lk0#0{;ywtGW`GlpMhEcWYqH~9~ky*z4j3xfLLx`eIfk!??3Lhuf9;#lE44{ zGXTwIc>MA_3qSy|Ji7n(&-=IE7@j_Q&+w0mmi+tskKyaj7YyHiJ^KR?KrDa${A2j_ z>kq@vpMMzs{iB{GzyM?T^Y;(XNB-x6!3Ffe#Y-0%)~{dt5g>qAIJwyvXaPW$Z~@I`V`pIi2p}c~ zIskwGqKhQ}0Yn!|00M~R-_k`43}3!5Fno9q$)?nb6QEiDo;_#yfB6anKmf7)pE-wt z;l&Gv|99_#VvBl~z>_iq13&<=FmM1vjFXAsKMxDTUuvcp{ QN&o-=07*qoM6N<$g6K-pjQ{`u literal 0 HcmV?d00001 diff --git a/data/images/flags/cu.png b/data/images/flags/cu.png new file mode 100644 index 0000000000000000000000000000000000000000..19c6f800dbf27643f26c36911596618326a73c36 GIT binary patch literal 788 zcmV+v1MB>WP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!rb$FWRCwBA zys>cQ!%Mu%{4ahmGcf=E%Rnvg|Nno6CoeuTY&&@8BR~MLh@9Rntip1Zdq0Z_!#idk z2G)PSK`JP=2kAEqLX&4Q@C)%X{Qv%) z;XlKFvMsrC`69!XO=ld*`~^h}oTla!`Q)Vl z7XuF`8v{T9u^Up`}CTDy#a@dr>rP6orSNoyD;%ZCC3jS&d{lajm` zzJGee@ag$A27my9Sn>zx3k?khh5THGA6Kt3yxg;kf#vOUh98_P3|_bQFns&`jiFN^ zhJgj>8%E-7Vfgj!H^a|ge;EJ*2x18f69em=YYboK&0_cpw1oM?TLuOupyxjSWH>6O z&ahI#nSqJ%9|I$qDH|YwAeI0lAC!4M^|UekVPa-r`v05Z?~mUMg6XLYiUFw%B|K6L zTz`H*-AAN9fN^s7_BDnh2R<BD^`N_&XL%^8f z3XdY#1&n`wQ=^1r`1|u6!|yMTzXAjhW1WE5!+A{n{4W_mrSLxn8i0=g0R{lLkQ+om SED}=y0000P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!fk{L`RCwBA zoW1nOLk0#0{?A{2GW-WZYJva%|1&&#`krCK&Wj%b0*K|(^{2vr|NZBF`T7%Bf?DA3 z-~S9ivl-q#yUYR*KrDA3zWMX|-4}+3kKZ!<`$t7ffM)&q{)*x6_m_VF0*D1<@V~$R z82$h;E=&IXV`N}rBGmyP_5Z+801!Y-jEuOI{rtrQrUm$ZF@V(l`O8R_uK)sw2^0;W zxIo7L{z1e{bl)-X@%&;i)qBezEbxQD+UN~f4kk|w2KfjefSCS*gX%xS??3-g@LvXI z1|7Ax41a*^&3nax5&4%vN&W-Fe?}$@wYV_ITu>AM1Q6rFbB{j!`1zOr>yKXy@Wl1+ z4+F!83k-~cdcaU+0}3#LUBt-ni-F<&C1Cg&FfcH&5JPy&`LDMO3>>GR7UKs0fM%V4@tWc7!&?jh0mOte zW&HaNGzTq3fmE}Q9{B(P#B#_efB_g>3@E_`Wc>Pii-D0%j)9ql3z)J%k;n*4>OUF& zeLlm$CSVSE@sRf4*UN4JhYB hlQ4`=BKQapU;rR#e?}eUzcBy+002ovPDHLkV1foXTFL+b literal 0 HcmV?d00001 diff --git a/data/images/flags/cx.png b/data/images/flags/cx.png new file mode 100644 index 0000000000000000000000000000000000000000..5c858cac7ddf14b78696e873a675b5838e83f327 GIT binary patch literal 1383 zcmV-t1(^DYP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU$`AI}URCwBA zoW1eEDQ_`e;61T|1%H;fXe}AiN(UJJ%bs9hWcm%##Xv0h|DOp= z|N76&kRZX#APnR#d-{Xn;+t~}pU-|`0SF+LTTegzdGO>D!~KVh41W(=GcYLsXJFA= z!oVVUkKyk>1_oxJ#{Z1}KqfITGB7cK$bXD~!EDC={~(qyGBbz)4gUCth2hSt-wgA$ zxft%e{K@e0(GLc;=gfZq0&qaaHUPpv40Mo?Qqm$uU;rv+U@bPGW)>t?U;-5KpZ{Pe zEV=R7a#M}RSPN~09EYAW(!N5Dg%E>;46DkO^zKMn@JK)vWv={6CYUg}*l)(cuuzYSL4$*t;pbl_1{R7`o|0kQ(##CWn}mP{mCxb2@8v`d3II0*}|A5SZ zxccdL7KZ14nHk>v`pfYA$3Ji|0R#}xbY=#o|9=>`SbhRi)lY^u9~c%AyPf(W(sj-Iu^#{|_|u>aEv6!AF%TLV))1Un<0>om4S;%7--QKhEEK?7-rr1$#DNG z&~SDJko&;~fr;VouYU}t27C-R?tf?a{OLOb6FUb3c6DuA*W#%||{58XWroTYJ{|rCB{bu;~`#;cEEMSF4?MK`hRvZXq`2XCJ;ln#6h8L_)7=8dPVq*Hoz=)KG|9=JQeaZ9_Ab?mPY3vsR zFv!9FV`cfp@Zl>bFvI-<`s*LWzreK5#Qv4x*ljI_W7qY8d|>8c`^CTvN|kqQ7#JR! zF#I!{!N8=s8E6Dh?k_Xg2mk*ug0lxe05Sdj_n+Y(Nc_)l23}xtS>0{I;Ah9r@c$bq zmjfk05du;Gj94b-A3(=^W?*9b26Vu0V9ElDvwQ((fd8k`82)Z=XZZU}7wDtE3_t`7 zfq&pI0|+3N>$g9!JbC(_f#K&PhTp&(y8qxkh9`GE0+ZNBaI(fx9zuKw&1{UIvI~g* z-DYR__t=GjK^_>7;ujeHe)-M7^nsBDAb=P-l?xw!e|4Il;qNDiMkHg11&q)LhQu@z pScH+|BLgGPO9m#^pC17N3;-Uv^+EzH2{-@%002ovPDHLkV1mm;hzS4y literal 0 HcmV?d00001 diff --git a/data/images/flags/cy.png b/data/images/flags/cy.png new file mode 100644 index 0000000000000000000000000000000000000000..97ed7e84694d2ba5dc4c4ba2fca40fb02ab0b160 GIT binary patch literal 562 zcmV-20?qx2P)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz#7RU!RCwBA zJaYWjLnD1Teojty1{#5n00G3z$YtL0>h(unphX-^OpF{rL8{`lYuB;>1Q64+mmmMk zT6~-#FnSup{fDoq>V=CJFEX4xd-e}N05Sdl|DS=0k&$8h{+kQ|F|!yhU3o+m3;zB4 z$MEORA21CNKp^uOK$wr0gW>9p=M4FkYZ-HBWZC0jO#!5?lJtkeVYLwfUsKfkAaclI~xZ>!s}xU`js6FZ-63yNsV|=1_9#l zKnxH-7?%8IU}pHu#LN);B;4j!$lmVFI00M~RFED~X|6^v* z{{4)h;_U$jtFI3kK>7YZg%*JH|Ao>30mO3V);kv7H`f@_UvFag`R6;sRx0HUP{cn5 zVqmsr0SF)#-WP}8O1(S&XZ_#rUP000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz#7RU!RCwBA zJaYWjLnD1Teojty1{#5n00G3z$YtL0>h(unphX-^OpF{rL8{`lYuB;>1Q64+mmmMk zT6~-#FnSup{fDoq>V=CJFEX4xd-e}N05Sdl|DS=0k&$8h{+kQ|F|!yhU3o+m3;zB4 z$MEORA21CNKp^uOK$wr0gW>9p=M4FkYZ-HBWZC0jO#!5?lJtkeVYLwfUsKfkAaclI~xZ>!s}xU`js6FZ-63yNsV|=1_9#l zKnxH-7?%8IU}pHu#LN);B;4j!$lmVFI00M~RFED~X|6^v* z{{4)h;_U$jtFI3kK>7YZg%*JH|Ao>30mO3V);kv7H`f@_UvFag`R6;sRx0HUP{cn5 zVqmsr0SF)#-WP}8O1(S&XZ_#rUP000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzbV)=(RCwBA zoHAv~Lk0#0{!gDiG5r7kpMhEcWY&WR4;WUjUi}dufLP9-KQH|E?_cic&!1D(5}=t3 zK(iS>e*DM+5I`)qZ{Pm&>eVZTd-v`!{QLKhs+K?<{Rbd`SU?7Y1pfT_Lsd(V0YCt; z@Gvqmd<8N<^lu<$q@v*(t?EGQ)|K~5m-+$CARzL~$@9%%W!1>Pr z5I`*dzTIWHdG9U5%?ICsUZk!i51;>Jc=X~Y3qSy|Y~1_g?drX6{=9krhgON}BS3%w Y0Q$3|4pBvt8UO$Q07*qoM6N<$f@w3#@Bjb+ literal 0 HcmV?d00001 diff --git a/data/images/flags/dj.png b/data/images/flags/dj.png new file mode 100644 index 0000000000000000000000000000000000000000..791c251d524744ab449131dee5d07ae09daf2bf9 GIT binary patch literal 725 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 literal 0 HcmV?d00001 diff --git a/data/images/flags/ec.png b/data/images/flags/ec.png new file mode 100644 index 0000000000000000000000000000000000000000..cb87ce83255e7e72ae5bdd499b0502f9447adc42 GIT binary patch literal 489 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 k+-wXg5$p8QV07*qoM6N<$g7?QH8~^|S literal 0 HcmV?d00001 diff --git a/data/images/flags/fj.png b/data/images/flags/fj.png new file mode 100644 index 0000000000000000000000000000000000000000..dd021d5ae9fe227705aed3cbd6d41d6b86f632e0 GIT binary patch literal 1255 zcmVP000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU$c}YY;RCwBA ze0K1_Lnc`T{yQ(38D2d9#_;p&F9sG)HU=}fuMBdRcQZWDac4O7kb~jh@4pP}Z2uX= z*uOLAi2Y{x#mmd^l1GMtiQzv30~lk1FF*b;JbLrxBR~MLtiHf0ta0l)x3=^j21yw? zh7;F*Gu(ani@|{R4Fl7~V+@}qEg0_J`plpz@RLDJ?l%J`uK>g0*Fp?0A2Kl*IIuA= zGBIMc2lU;VHKg1N-Uw3||lK zVG!}jWQcYVXONbXV7T+1o#Eo$pA28$e`R3jVP<&w!!Z z0#Gr>H~_(50EJ(*yM(xa!41Ud2oB;9R?cQ{2@+!?sRaE?SL1ubXS#VLaR;(*JRVLj z6e=Lrayv&l_2?|;l9w|6$|&>=Yu&Om14008{<>1UAsXUi20j6JAbTBvfG`Nooxc!7 zgeVmn@BcyyBuWG!E_c4l1MJSu`b*i8XBRgK#yFBRM)Bh23C01{_2{bR-VXLKCGsr6 zYdMQX${gx>(EEy(g;uPD2uAXO{$KnA0A&E0{{*1_OaKE50{{s&2>>i10{|)u^#IJ- z@Bke$>Hr_n#sKA6b^z`gE&wC+(*WDu^8mo#2>|N#2mluW2LLh$2mlSkxBw}OWdQRi zBLEEe@&E!iB>(~5;Q;yD^Z?yvkpKVz0ssPtg_rRcgFHVIgChHLhUXivGxXi#W4QK1 zjG@l$J%ifc?+o{@JY?wVeaGNvBgSAW#l+ysaGl}Q8y1FlKc#>{p$ByIABLZ15)41x zR2XyxhTa*8g+yEp%@%H2&F!H5MNgHF>fyfNcXZSQ-93 zzQ-VUZast8zpo73x2`cT@7}jXx3(Af+RtBfe9)+89o987y!pvv3HO0 RTY&%o002ovPDHLkV1f&&H6H)~ literal 0 HcmV?d00001 diff --git a/data/images/flags/fk.png b/data/images/flags/fk.png new file mode 100644 index 0000000000000000000000000000000000000000..7c277bc5718541e7f4d5f512cf5d2c7453ab4ebb GIT binary patch literal 1171 zcmV;E1Z?|>P)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU$B}qgtv`6mN|hX=#;pS%ov*Pdqh^Wr1J?JG|i9wGUJg_)7zKO++Z&};@a7LX}enMRI?=J(xZy;u3gy>-BWMKI98{|F) z1}-)R24*G(*AI>idwxkl#29`th?(dzn1z@#@c#e9aP;vHp#K;detmq%@csC@j{pJ0 z@)_vbUlQ*b1dS~jn123XVEFI`XbDi?rY#H%#>NZ`=0*$*FMwh{eldLF7iIYML)t~1BbLQL#Y`zL%@fB4FCT9XIOIJJH!4{tPB7F#8Sav!r&UF z$q*p$7HIG$h8@4f8ThSC87y~hV|d~0%dp-!gdtq&8-vin%?uBp{$=R=EX=U~?jMF% zpC17Y{|S#17NA9Y@BCnhP~c$Dm*Hjj_xm@)K2AoUW&aoe0*Ix{N1TEC+$M%Y+b%Qo zJr-ox_<)IFk@y=1OZI;ZEI+<6WTh`*m?+D^P?&1Z5U2QtVae&E44Z!aV#s;K#PIkB zBRry6S(q85h5j&f?O4XZ$IQU+=JRWy#S#o`tjr7m0mQO%`euebuh|*qoD^pG{qYkx zB6pm9$?*24KEwMnKN#5lvM}7d_?#gl=K;e+6AgwacRL0NmJreXAE+`o-lCoFf)9A^@oA$`E>@F&+H8UfT05rK-jY@N#H*N zBhW2zF**#q&-O8#x$}oXLq?S0Ifp)jumBr_Jnt(8fB<4)paNiGW@QlPe90ie^M&Et zi!%%lpU5-(e|Ur8{i_QM{5*^d00G1T%Kjh>O4pddKpgn{=NCiItS1b-ydXD!XW(T$ z&+y?h1H;$f(hT3f|6~9NAVyx^yf2?Wzu*SuLX7f<)L=sZSo{KQVtD-(Ab?mtf4&aP lg}~g!@PmOy;3GhQ0RSiigA#6I+2{ZO002ovPDHLkV1nF`CJF!m literal 0 HcmV?d00001 diff --git a/data/images/flags/fm.png b/data/images/flags/fm.png new file mode 100644 index 0000000000000000000000000000000000000000..6c4e137944da5d419401ea46fe337e6f408dc49e GIT binary patch literal 700 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 AtN;K2 literal 0 HcmV?d00001 diff --git a/data/images/flags/gb.png b/data/images/flags/gb.png new file mode 100644 index 0000000000000000000000000000000000000000..8558aa61bc7ee31bbdd6f537fa253dce0718d4ed GIT binary patch literal 1495 zcmXApX;4!K6oqd{cnUnWqVOVEf}&7CNyt=isSrgf)(y1^j+h4|p|&cqFcL~2MD($! z#bqpuVsxr?L5V1gP+5W@NCE;@(2CU}D8h;?CGEy}cv<4nY|okfCo7GWgtHu3n;_gOj1{CF)CyMuSnrJp<87C8$C;Dj^lOmWb4j=QRO~=Zn%3CcwG#;sN05aJ zSu|@IX^ua+vjFdy$|#Z#{BsaNRyhzR!hk+IAQRyerhGZ=#waDbxQkVgb?yX@)F&bD z^y3&QqsE+B0759H7}_3&3+J`c&fU`;+_}r3-Cz&ux>qo490w~aI?(Afn3ynw`vP~c zpZEYHjSVov!yXXs4(NU{0$?%$Wx2~lT7jJ%16b%NaLHcqupI>mT@U9gniy99N8N3s z%(V<(Skmzqti5vsILkv}ery!9KOKRqC9TlkHwI=i1%AQa0R4I}YwCdE{RIqd7=_L! zeK0vO1&FN;tX@O`Z&U;G7Wn|MgW#oU4iq213ssf3%vL!^huZ^)e-kOr`PAQ=#p^fa zHY8Hc$X)d?Iu`jH-Br?to(p;Pf(7t4yjmE^idU`NUSfyI>RU~BWX~` zK3gEb*(hH1=(_fH`oKn_C;Jd*r@W_T{l+ zw#SO;VnhG13p(^ln&9NzBfJV1XkV%Q6`Faj8oLZDWJ!6vf84bp9Lrjib0`ogI zAWIRkWO{j;gGX`|G+*Y|XS&h`Xfs)^BK(eNyyU1{u9&SpO;MNrUN)cipC~MJeeLWc{7MF9`2gPi5 zx{2XXoamRpH5YP^$s-SE%M-i95m5$>s1M0FyzNDR!zw}+%KbYUQ~gHLmlGeG79+iL z?=!(FN2txEP<`EWyu>u<7#w1r`ZS}kJDBOe|19%-A#lKyx}`hb@@Md^)1On!r^ zq*_&@PG{sjJ!uG0K-SE5Pl9rH<zW}Ql&@elz68L%6_UU!pe%X>Hc}tCh%0Uq=7l;z99aGs;}Y%qy?r?0=Jt&1 z5mj-<&=9nWc*4JeU69r~IwZP^5d86QfuUckME5DmxKdg;t?vaN-JCxplzf_6psv@; zusyUa^ZUmo=yzus%>I|#nckf!hHWkrcBBsH7`$BE?LvXmHuyfHD(#b$oH10#tubku zj2|3Vs6+5PJkIR6@#~96j0)cFJO!`ck$qi%|Dmc`t23@`IBC#+9O~#qBF7yI@xr2_ q%!9fHmK8epS5Hrj%$PDdvs7@J72TEomgQh+Fo;;YDXeTYe)vE8l%@y( literal 0 HcmV?d00001 diff --git a/data/images/flags/gd.png b/data/images/flags/gd.png new file mode 100644 index 0000000000000000000000000000000000000000..753538162a8c76277f10abe5efde9b0c60340c42 GIT binary patch literal 1176 zcmV;J1ZVq+P)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU$DoI2^RCwA% zi!lm-Fc1XiayBC9^DX>?;FGiw8zHevV$O(#!&W0@98FsvV z#{v*QEdOub`E!HwJ;O|!4-7FP>_^IdH)$ueEHz|X1>^)^ z@G)@zW?=XYbl%?YzZgz@`pw|Z#KMrl$H8DA%nEk(&wpr62Le#=G6VI0`1_Bc``vej zEnj{yJpS{KfrE*WffwW!s1|?#VqriEjlV$RKNG~j^?!de?0x%(A@DaFLl!Rwg90#8 z{{iKHvjHRj_kRY~KMY_44}JT^F#qFEhVwuFFmMAc6aZQPQitq)fB=G9@(U=T{+*G* zhLx4!D91mB+duy@0Mi!OfE{0dGo*-cFt~kWWRSe@jp4T%2g5d5W`^#U-xyv4En)&1 z&d)US(*MLcw37qOsEdvN3xFz3!qAWm-Cala1G5=T?A{00o*nnyDCpe+}ho_eR ze;~yPCTnFYokb@baHUR_>3jqe?R_VBoii9p!lzU3=Eig6Q~Dh88Ef608Rf0 zlz;Hy7sH)z|GokQ5X;UFAKvZ&mM`z#|AOcP5=_8Uz{t+X@b}YS6jy`1&%^`Flwba0 qlwDwb98i59|3ZQaqW2>}fB^u+a(!p(i&^6U0000P000jN0ssI2o&@u<0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!CP_p=RCwBaQ_m}WQ5-$*&0{DFDU(u4 zB0CmBWWmnP+6sFsHTG8g1NO={(_3SK8a5M2nK7kdWSS=uW+A^dKHqcQH}B4bIlaZZ z=YBr-b3W&DuO%V~P%Ogp(AEaq_L=Kztgb5FvM@QRwgTkyh(u6V7ZA4Ddw<8#5$5NS zOycv?Cm$cNvxBWIY;U7bFeNW9v9N%>J-obVE*6VH0wfaB+A6j!-QAK-`{coagu_%W zp^(JmK6!g9Lqp;?5{ZcGl1L_#(P*?xS8p%Y*3i|3l@)Y$q6(UtRO15!SYF2XctATQ zqP`v@BUoQYM~5M42c6Oghf7Hg+S}D}<<6?JQYn0V;O*_(JsY*PNF?y}rIs}{I6d|M ziQ{90LMoVG0Y={3DDQ}i3tU}UGB&1$MK6DX37j%#qQPVqE1!D_DUpB0qN7H}Qb9r$ zXhna+QKj_vm#&~k#%ShOh&WhWRA%XG!ccldr8qmH{YOIM;r zySoks2P=+$dO|~kS+=wo$-~1H!R3)0U_M{g8Y`sJ7#&qZ2^x8QHUE0n-|gVx0TUC5 z$BhFOZ-@_QY6>SOn3++nRl)r|rl+yLkJ(vlZdTmYY&M%=GM^s1I6qg7P&3sLXJ>w8 zYGLy8vp>+jJ{%sRzh4R5*vRE_p66Mv>+)F^^t)x+-5r{njYllND@vYYq4AB4Mja!$ eyI0000P000jN0ssI2o&@u<0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUywn;=mRCwBAoHp&izkmNfefrLT0{(h? z8|dlDFkpgPw;0y0MHhVdkYV|9meZ#n{P^+v`SXvclD`ZL6tSE2@+HHrUFd=rFRu9X ziRI3n*H53mzjyBqs^pKjIC}=H;LRI`0|(H=E-^5iXJBArpdJ`CYt*cvViwD_YtP=k zea^t}0WCjXx^!&o*7sO+oIQ&v_JD!mG0?2HZ!a@2++|?+i7NYV$By?GF7RW|k2nB8 afB^s!7E63(I%YKh0000P000jN0ssI2o&@u<00004XF*Lt007q5 z)K6G40000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzfk{L`RCwB~ zlfO&CK^VqAhLo;SSkMq|(Oi={IkgE*ZO%a;L|cDBYeeAIB$zEFf{QLiVPOi9ih(Ic zkqL=@4Z^PT(EO2pb?D`IdAWPu=brby_Y6V^bS#@C7;MK52X?y-b_#tfzEX(bj_?)X z;ZXjS+@rKmAe}}y9KvQ3s*cv0LO2l%hz@+Sb*zUAk5-q~Hx;Ly&P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!8A(JzRCwBA z{Ihi4Lk0#0{{LS-1F8QER0BYx{y%-p@OS6tj{pJ0^6&Z;VTONyx&Oa@2^6KGC4c`h z{J(ygVZ*amEC2z-^8ewzKmXsoVfgu`vAl$H4Fd zh`;{@O8g_Jmk}5a|A8byJ3yZK{U2l(13&<=FvznoFz_)kFmN&hgA2cAMxZn^3nRnd zKmQ4bEJ*$n(5mN53;+Sd!d3^g`}L2>c|X!fVy3=9AP#8P^Rf#J;$28PGq@MjZ{CPr2!1|D8!hQ9&~49iddXJBH) zpSphB16uN!fdL?ZSU}n18xVi_LpU#jeDw9>f1-<(Uw;|F*$^OrSpGv}i1GhF1|}kM zJR=EZ7t?=8wgm_vmYW}MvV3^>f#LU)-@pt;&0+-@&#(W0FbhBcvAjC+>h0H~U;q65 h^p}A~;3GhQ0RS4=;wWKo3q1e;002ovPDHLkV1iHrBp?6) literal 0 HcmV?d00001 diff --git a/data/images/flags/gi.png b/data/images/flags/gi.png new file mode 100644 index 0000000000000000000000000000000000000000..5de3e79f42f5ea189d39b53eca41c552e638215c GIT binary patch literal 753 zcmVP000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!gGod|RCwBA z{Q2X@Lk0#0{{R2~Gf*4+`}dFG&+p$K0Ro7Hjh$T>$meFD5m>u+Eek*ZF#+j6w6fsh z#fuDQ&z}7Q5I{^sss8gDh<`DBe|(Q&cUlO;)@XNz=ZAKJ#Tb5k#T`_C{(xzK0AeA= zj=OudGVE%rXOK76XZUsIFvIURuNiJBC^M|CEo0EJw_$Kc$gU;?%Bd1b>kv~fTjUXGXMgJpe0O9ObkB+#26lbXJBAt`~~C#X(0Ry zj0@&}{~6f+F)^?RiQzQ^Ab^O96FH{e45FM23|~1J8Mv7KGq3`K=hc5E2I0TI8UC^T zV_;$Dz^fl1fC$FP*Sj|v7%!e>;1J{@)D$8NV>_e-vW)2b5?0 z`GBB|J zWB7|JCBh_tf&2R>FixKTXZQn52>+R(@*s=Qq6K8hKX5W+U;qdpmcO?yv)p?Bnc?Oy zkQWI=5HOj1{Q~sGoBs?fz>NMMPmF>rc?2vd9{*-w0SF+LP49lbUH^gk&znEQmBRl| jg9t`&{16E~0t6TUF_rQs(0kD(00000NkvXXu0mjfXTMK- literal 0 HcmV?d00001 diff --git a/data/images/flags/gl.png b/data/images/flags/gl.png new file mode 100644 index 0000000000000000000000000000000000000000..f10fc56b354791742eff6cf126f72e267ced693f GIT binary patch literal 632 zcmV-;0*C#HP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!3Q0skRCwBA z{Qv*|Lk0#0eg@isj{pJ00wjcin44BMtX;d71t5T!fb<_)S#a^Zp z8v{ctQ0~Y!qJs?}fG{m#WMp7y&tPEq4rBw(WB7!ILE`U$c-IP|EddB1#{V1VePH}z>V8wQ!p&cFZ=K&T#Ngoec;V9L6D9Jd9a_?X)bNp*~b z-2)Inn4t;M`0ySBL#i{-N7EP>oP000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzrb$FWRCwBA z{5oaILk0#0{{NpoF);l9&p<8kA86L!2M-v2tX};QAb?nYpFc0m@b@qGzvs`XYsufg z41dm_XV~)bBMU$PvHZP#`_I2uuNeN`yT|bV-#@BZ0(3aTRiL9UF);iA2p|>)pur$p z|Nr^Jz(7SyKqmc#(f|R(!XYFGwB$F#zb~JtZVBh_KMbrte=-0B5X+s0Zie5#elvXk z@e>%3)C;YD|Nk-EzI~J7)qy<>00G3(yWu3mn~z@^9=~`Gl%S$5K(oGoc+T+Y@qGq> z0Al(72N)l}{s8fBaI&MOB@Dpe0wn-|0Al(1^Do1XpMMy>fB!>upZo-x`}@y727mx! z@pIQ?`1<`P!-p^5sOl4tSRN0Ad008G(@LKNADvKSrur!u+2Z zY&JjuvE2N4ljXz14-CJbP&su01Le=_KOoEk5I`)ij=XyN_2}0>e?R?Ypb_{85MTgF W4C`RS_c}BH0000P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzt4TybRCwBA z{Ihi4Lk0#0{{LS-1F8QEDBvdp1Cs?S1CuEW1H&H%Ou)pz@bdeAhILQ=GW-QLU|^JCXJ9a8V_^8lfE9fC z^^f7yn?DSH&@B1;|1ZO}57!u89Dcz95I`*dAKv@(|J@sg|Brzl`iEx8=l=}q;SQ!2RW&eWkUj{H68GxMk?=Q$* zm_Z412+)flSA&!?qe?LR2ZkXi*Z=~EiGh)kfz|*ZfS4HQ0007rE|vfU5M3+*2q3yx z0uVrSu>>H1=wb;#0MXPEMrif`2p~vi1ZKGZ|Neo?AGG|$zzE9ef1nsMCo=p6ii0v8 zsMz_>h?W^adVvTafLQ+By3TU*{Rf7dKmTA9PyfFIbMfW>49pvWrQ>hx;qc(|KZdV2 zG0QHHVSir#0bv$^0Aktr;?3LDpMLy#^9M@_2rNw)kNjX@e87ym>;hW;@#8-ng$@{e b1PCwyzLm#qCqGQ@00000NkvXXu0mjf*jMR? literal 0 HcmV?d00001 diff --git a/data/images/flags/gp.png b/data/images/flags/gp.png new file mode 100644 index 0000000000000000000000000000000000000000..798f5ecd327517a3906722f6e65661bcf4c0b5e5 GIT binary patch literal 276 zcmV+v0qg#WP)P000jN0ssI2o&@u<0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUywn;=mRCwBAoHp&izkmNfefrLT0{(h? z8|dlDFkpgPw;0y0MHhVdkYV|9meZ#n{P^+v`SXvclD`ZL6tSE2@+HHrUFd=rFRu9X ziRI3n*H53mzjyBqs^pKjIC}=H;LRI`0|(H=E-^5iXJBArpdJ`CYt*cvViwD_YtP=k zea^t}0WCjXx^!&o*7sO+oIQ&v_JD!mG0?2HZ!a@2++|?+i7NYV$By?GF7RW|k2nB8 afB^s!7E63(I%YKh0000P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!zDYzuRCwBA zTypyC!+(4?_`fi`XZZi0k%3y^|Ns9CPrf~2*!FbWM}Pogxp9|E_%E9v_cO8W4F4Jb z0cBaJV#(kCe;KZQxW@3}@Cz1z0AhLg_T!&-pZFOb+|^|GskE4Zf%yjm!#~O``uG1I z!_Nml8GhaU^#>q;SU{Y=41X9HpIS4pe3D@Jr8$M+57$$M-@lkC3@-+T-@kt|{Q3Kb z0U&@_7#SGBBL7*wF))5nU|_lx%^q8eISWF`VbSz_8~28U}y>VnWx<%<`Mz<(IzL<-qI?W| zeEg6IzykmR2x~I>4a_h9S(q5|{Zttwg?X^WDZ|Sr?-)!J0yr%J z2p|+ofJQTXXJljGWBJ37WiHI%rwVlC=TF#j8pFT$-xwaq zgyGv)28Lfh82|zZ#gZS)>VBu!Km3~2@zd?zS zkr9~3AjuUQUP000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!aY;l$RCwBA zy!qh$Lk0#0{$GFoF)%SQFo+0nGJN~-hvDs1tSiy%+KN%i8{mj6|$_)06hm|aYxCj@?zPWhu zBE!K?5B>lI5DO@H6s7qYe*F5wz{kx7mXs9bVldYeXJBSxV)zU6!GmX?7+$>p#sKse z(H8vs_mAPmy|)Z+K7MBa2p|?gK6VBr89o#RqJo@Y3Q+^#0*vNAIygZ&xtE9A!(AN)b)?y(Y@yk&t89F00bm;~ocsX-h=qflg@K2Qm4SzYg@KB8 zF#HGlZ$+$002ovPDHLkV1m(&M!f(4 literal 0 HcmV?d00001 diff --git a/data/images/flags/gs.png b/data/images/flags/gs.png new file mode 100644 index 0000000000000000000000000000000000000000..700656b2cf60c8c52906d2c4d166148e8dc3da25 GIT binary patch literal 1187 zcmV;U1YG-xP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU$HAzH4RCwBA z{PgJQ!^=;9@}E3;pW*x0FAO5$A`HQTFBzDgJ!Uu}W5aOy+EWHbRt^Rw0Y(NB!JiC@ zlH3gEeh4z`I{B0V0vH*vf&c&iGdy_kmSOekvmXHhh~@Um&B8jW65QfOvJ9Kf{9(9u z`wfE!_caEVTUQwFd{bd~ed8g6pZsqIJppcpF9O;Ob8fRR?A&>V;mf_PeLHV3+&O=X;n$n@4EqiMO=4g|vf>{D9}hEw2F?^Rrj{U++C&VEpxyf%)HGhTp&bF#HEfv$3%-{O90g z`1|)iQ0@lwTa}tgWZpPmk*gw8w*lU@$e^<@F{;`P>>d7czX2-!?))T8Qwg*#K6hPzyJ_HOd0(5 z8Gg*4#ZWfw0z<$KR)#4X?lJKH`^F%_`JZ9?>SGMf4y_Dn+y5}Uwf15tx94P7t9zHh zPx>p+eAGzSGGb+5|M#8Y_qQ($9Ke+L=MxiyhM^$C?|)wy00M}alhwGQ_c8~^y3?N+ zzJ2}5zyj3#?&D8}L$BBw&ioc-c>eq&!=-b#8J6sP$Z$hIgW)|RC&RdtUM}Pogg183R>r7Asej`}`6K4lvRwN4`mi+qh4H!%x8Ki~(GQ52IjN!%yX$Cu2 zJq8seRR$Io4hDb#Vq%~e{A2hBECIy-b1-oI7GU`L<|o6qf6p0MIKMFfQ#=DJGdBZ3 z08zmbU^)XPw2u$|FgRKKW?=ax!tmp)7%&g+Vi4gv3oNRBGXMk-%iq8M7(f^iALRMw z{wGm}TbExj9NhjHm;k;p{C)eEfstK=;otu+3;+Sd$jh7e<@4tk+zbpK(88K@z=&c3 zi1h_jtb7FsAePUcufJtrxcdi^k|+Z>ocIwSzyMw%o6t|Iy5ax;002ovPDHLkV1jUE BER+BM literal 0 HcmV?d00001 diff --git a/data/images/flags/gt.png b/data/images/flags/gt.png new file mode 100644 index 0000000000000000000000000000000000000000..e8cce5864e3b3a4377ffdcc3f89aca4b3720824e GIT binary patch literal 507 zcmVP000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzjY&j7RCwBA zTzcfeLk0#0{x3iOFfjaQKmk8~|7EaN7hy0~6k@;y9>4v@uQ zegB#S&!0ayjrsfc4^ZD-lGv_s<_N{CxWuWGZR_ z2`sxV9e>8K@!M6LmfU#uiQ&uLI~c_Zs2u$L@g>9WPp?=20*K|sl|yeo-8}T?&$o}5 xt^^hrhhA_q+&#jHr|kO8@E&LpzTyoazyKhv(@%E`uIc~)002ovPDHLkV1ja(-{=4U literal 0 HcmV?d00001 diff --git a/data/images/flags/gu.png b/data/images/flags/gu.png new file mode 100644 index 0000000000000000000000000000000000000000..40ddc68400da376f2ce49797643fad145ced7c76 GIT binary patch literal 674 zcmV;T0$u%yP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!G)Y83RCwBA ze7JGj!%qy%!au%#|HHuWpMjd-f|Enx&gkPP47pA2UIJ~3Qp$eqm6M5Mg-ACC;G!{xid0MivG}MI{E)f1iQg zWFcq*Kmf52;}IE#zYKo_L>L^tzGq+oCK9IKPZ|Dji7_a!{%1HwM2Z9mAfkfnH6tU# zpBE1qn8leH9y5GrIK}jff&JfYhBtqi8HmYS00BgVCI1=jF!D3}eE)<&`rkALHo0pI z83t1svffB<6o56XA zWO&KQ!O;9#59l)>kMR$F<>1Wt4;&Bx0mSn9-ZO?fpMe?c>r03rzU+chY7y`P$dd0L z89x2|!T=CJEO#GX_^9&o4a>J*pi-E+;6DQ+3oFB)j{pG%00r3E>hAP}!2kdN07*qo IM6N<$f^BXgzyJUM literal 0 HcmV?d00001 diff --git a/data/images/flags/gw.png b/data/images/flags/gw.png new file mode 100644 index 0000000000000000000000000000000000000000..ec220ce4582502c5deb6dd5fa1327dd1784fb990 GIT binary patch literal 638 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 Z3;P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!N=ZaPRCwBA zoVVf9Lk0#0{?A{3G5lwsBKZHG;qi;l44V($`3Mj|ELU#75&rw{ANR|5-+nu8JHOU;qVC)6C(o~D-*+yUw;{3 z5y!^L40i3`zyEQ@$+!2P7~cQjU;qdp#_vD>d;s$K@decOdS238g( zu#Z4uYj)jWxOnpwUVR|5L3#iJ2o%{spWp|;VEO<4^N;_)c>7;D`SAby&p!VD@#_yz z9FHDgIG#Ck=6`v4`3HaiVj?CAuityi&^G5J12ZtFV!SjNq(pfbX05rvu=nHxqLKhW z0I~f3^A8qJxYIl^SqKBe|JUz-3|dOU48MSSSb>R!fsqj`hAU1$W&?f801!ZoxgGmH zy!-T>|JkdrI8zr3GZO>-#4@0t67t z-N$bkUcLRw01RgW`Gk>yfuw@xKf~WIZyA2Q`@#SaKtM+_GB5*O3QYwBG%}JZ{e}B9M;&&_n0mSm=;pw+OUY`5&@Anr5 e8i9`h0R{kZi(PMpe3~-=0000P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!`bk7VRCwBA zd^KgtLk0#0{=c6-F);l9&p<8kA86LE2M-wDuU`ETAb?oDoj))9|Lxa+kgJNdd2Yb-aQ6}fB&#*U;^TwK=KPSI|Cy- zD+AllpA4*je=)EFt@($?l3PGWUuR(W0}wzg|A7XB1pfW`gVhoiAbuB(f|R(!YU!bz{t$Z@bC9;td_6?@i(9a z_x0=<#Gc+`5EK((;NxUr_$eaD@c+RZ2KFEC!3O=uVhQW7UkuFOzA*p<5aZwP-#-B3 zf*)5H4yE!nhu&{tFU_`eBXx{mY7a7*9 zU;hyxfLNF~I0&Yczlh!?!=I4FCK*7?{}Ev1)3Fl35D);-C5PVs|6)oEcOoiP(!XkF zR?PIw^EXJ_MkNt)X3m9=LS3G_DB)_GxQ|*7r4ITqUID~XRI{An-TN;L&z=KQ2{wy> z`aga9$6&$to55M_I|I`{Mg}gX9}Jt{@G&&GO=MtWVZxCufjR#B3x;nWt}_4x5X-e& z9~oZ0`pj_e;ai~X|FNlM_zM(&Atl7%qbb5*ApD!*;n$xG^VeQtc+0H_OyYlWWk+CK z{$%*`^A7_+0I@JLF#?kzBLf>N&_)0M;Ve|XzW>M&zx+LeC?6BUr{ByBpLt{$xS9Uq zPnFD|l*zyV5I`)~Za!!E_~AJ)Grq-N7y-doApZOrm=G9&@xsW+fZqzBPyT!a;!i99 y0mSnD)s?ruzTW)v|L-@VOW41p704d}0t^89$u~%E5rO*v0000P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU$AW1|)RCwBA zoU`=E!~YBn{GY%4WcUw+j0_Aw5<5Va1JO)C%*yZ=NMq{w`=62F!PEB)J9nP_2oOLl zm##k*{`>Dg_p8?*8U6#+{$n5xfSkhyw19)*57cRB@_+v^T)h62;q|jCEC2z-a_8Zj zKX2ZBVR-oXEyMqR|Ddh_;=drR|4|HOWCY^>{}}!=GJ)mT|Nmy-1X}_DOiVy=CWwXq z{{3h8`TaG+ukWw^00iKHj8XuEVHg^_6^9e=e^MRU=3K2vDI^~yFO6pUOs#WGTN_ou zk<-WLPYla|_J|}KuKPq4B4EW1{vl*TDSTo4&gT(8ER1MA{r>Y8gM+0u!@`L%&`@Ut zyYS)1OAI~VyBT=-#Th=le8~{QlFVQ!;|TQGUk0$tfMD9}oeW)ln;H1|I8n?52p|?F zuu=bkuKEv-4~S2g8Tfg*kplP!1M^>2hCj?-8Fu|!#URBd!yqco2=wiDpd>FeNiZ;Q zak2yTVAD?--UXy~-dh#munCGMr)M5f=t+ zgLe${Vz!JZJR%rk&{0|IdBg6L(e;Ho?mta^s?Hj|DUyKaTcgQk4y~@uZ%k-8(?f*N5 zV;2rGSm@X>1pdFr;QjjmFlhL}CSCaRn_=z0p9}y2#PW*)XJGdE+PJG3n#(#i8vyg#dY#pe2I91Z~5>2XyK`NFsvfG=KoYu;d@h ze+HRn>aBc_X^glpF+E0Al%f>pIKL z_a7K;{``YdZZV=H_dgKT_)1TZ6eGgP000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzUr9tkRCwBA zd^KgtLk0#0{=c6-F);l9&p<8kA86LE2M-wDuU`ETAb?oDoj))9|LkJHk00M~RKhR*1z`s9# zsA>twq(4v^Ab?nyd3YH9fBVM3!p~22OIU!8XZrn{0U&@_l-I0b0QrWR7Jw}Qn)mn8 zC58(dHZTAL5DODGHv=sIBhV5iU??%Nvoin$5EBC(06+lI#S(x3qKhQ}0mQQA;Tndo zf4(w&{PmIHKLZsrXZZJ@iYW}}-oFq2 zGW>e=ivb{jSU6cY8Ms-v8TeTEz_wGf#LU)-{3-x znz`}M>pvjO0uVqfua3NW`}OG8KYu^{rB%Ft1PCwyzL%$%YY}y|00000NkvXXu0mjf Db6>_T literal 0 HcmV?d00001 diff --git a/data/images/flags/id.png b/data/images/flags/id.png new file mode 100644 index 0000000000000000000000000000000000000000..6aa9fde8961250236739984d4b6d3d08957db78b GIT binary patch literal 400 zcmV;B0dM|^P)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzB1uF+RCwBA z{5oaILk0#0{{NpoF);l9&p<8kA86L!2M-v2tX};QAb?nYpFc0m@b@qGzvs`XYsufg z41dm_XV~)bBMU$PvHZP#`_I2uuNeN`yT|bV-#@BZ0(3aTRiL9UF);iA2p|>)pur$p z|Nr^Jz(7SyKqmc#(f|R(#K6eNKx+UHKuipD0005R0`ks(sJEyI{y{OwY=8h_;Z96s z`2Xb#!@u|MshYa}0nOqDrdYOX*BAf-h=mhq2`#`sU?SoICSkVKs~G?Sh%Rvg5I}UX z1R#KDY6&AWdjJFw3rbn@_b*lB1Y{n_Tz~)qS;BJT#tnw+*RO*msAP000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzkV!;ARCwBA zTy%HQLk0#0{x843F#HEX82JAm$Yx|<@RjjpkmHtPzy#s_fHsp zT-^2%Ab?n|ez+?9@BcsUS3h2%SOOGdU}R)uuoJUmVBp4S2gASb9~pk#Jqa=vh?$Tq z`OEO<={1HemtU{|1Q5%EFAx5_{rQ&R(bq>{gVBBRLFEGjKJf3`2Zo=wk3l_x@CgIZ ztgEknGF*E3>kmKxv49Nz3$zu4ku3rH1PGCRhZTSv^Y1UjCyY!;`hh_SB>n;A0Ro7L z5!rSc0e}EvVxR*62q3yx0uVrSu>>H1=wb;#0MW$~fB>S4B>(|LQ%e}3#SB0IvA{Cn zzkmOLnG`AG0h#|9f#uI1VDa$h4^D&r`~}Lx^fCTN$)kUP1c(L*AeNgSZ?b%N_<`Z~ zli!e{1gTg7Wz-{|jxf9vc!$%H-_Ncwe7*Y}n&TN!a{S|ue;A&8`ojVcK#XFgVh_I_ z{mTFM(_gHHX!2_^2(Ss@ExZ0ayN)l^J^}<70MqQo6#k{=-T(jq07*qoM6N<$f}euj A`Tzg` literal 0 HcmV?d00001 diff --git a/data/images/flags/il.png b/data/images/flags/il.png new file mode 100644 index 0000000000000000000000000000000000000000..bc8523eee94bbae1582ace9e731ed77e05f9ca40 GIT binary patch literal 618 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 Hu0mjfF%To6 literal 0 HcmV?d00001 diff --git a/data/images/flags/is.png b/data/images/flags/is.png new file mode 100644 index 0000000000000000000000000000000000000000..aa90114254592b5b135f1ed3cbbcb9495831c197 GIT binary patch literal 893 zcmV-@1A_dCP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU#4@pEpRCwBA zoVWDELk0#0{?A{2F#P}jpW#0%EQ9j#jST-S>=`z_;$*mU<0%6RD>DO00BF|Z zrym$L@4WI6Ab?n|Tz@9~_uqf+m#;rF{QC>Egq4NCN|A>_`OI;K|B7l1*YEK$oIH7t zft{U&Bs)N6UB3Q|;nlMXEC2z-a_`~WKX2ZBWqA1b9mC&0{}}$Wu`wjtN-;2;Ji_qb z#+BjP?Vk*Xj@@D4;$$Pq5}?Bwet&-f#Lxc#1Q5$VAm`uTe+++t_zw{O2VtOBfi?oY z{12@74^)oC@B+erU>YERSU{S8{r=1F^Vc7SKOmnlFfo8EhJoLIfO0?oFfcPSk!;Cd zpt*m6!3GdOEQ-n!41B_T3|s=dK-d0fU}R%u5ESADdID$>FcHWrNigUdDlu?yu#g<_ zUp_K3ym|7R0U&@_dNNHJ{sUe6|MzdO93vAW1IwEi3_rjBW?+8%mZ3R7j^UrT6a%T? z-@kth7q4AoShxKh13&;V{*Lzj!0`GN|NqAiKr$d}82$m{;y=)<3`|T6Oe{=55z+z3 zqzga(Fs%Lj^CLh2v9RP8F);l6!NBnSE7*%5Oa4E9$?&7OhJo2Xl!49J85nGgP&pDT z0jg)YbDx3f@Nou!0AgA6On~9bw_gnJKXQUS@}Gr;A;dtFL5+=>fziy8VFi~d!}W`I z8CY4ENsN+z49}l@V7T&uivb{jSQali%JA~lX9i$?fut^WHU|BACk8E`C4YbZWLUB4 zB*Vs)M;JIs%qO7K_3!s9hQHtMFaQJ)3pW=#124}n27W#ch$XX%@aN|npx8ffoSZnv$nfj)PlkWzFEBiLzzZxI?f_jxdOrF4`y&Xm00a=D zfK2eiAD^!A|NHX|BFD(YpefJKApGze!+$wNhU;Hg8Q#AB1We83BxEr72oPWZK|po1 TMU|6c00000NkvXXu0mjfJxrIS literal 0 HcmV?d00001 diff --git a/data/images/flags/it.png b/data/images/flags/it.png new file mode 100644 index 0000000000000000000000000000000000000000..5212ade5fde153d65d7282e6cd2bb01f6b3c3f27 GIT binary patch literal 504 zcmVP000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzib+I4RCwBA zTy%HQLk0#0{x843F#HEXWbotf4+dK?TLx1RQwE&i=TC;OGo~;weE-S7z=RO~|NlS3 z--izveym&l5g>qAu70>G{O|ui?pHrvVORpPSdmAO!IS~FCIA2YVfc0YC>H1=wb;#0MW$~fB>S4B>(|LQ%hi(10aA{V43jW zzkdw>|DhHu41a-{>F+;aKKp~WSOLlYM;C`^2h#un#B%fFO_mQ2KQR1$@*7;JA%kCk zelc8rbD3e|&5gJ%`Sy+BP000jN1^@s65oToN00004XF*Lt007q5 z)K6G40000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!iAh93RCwB) zR9`4$VHE$(m=PHdq}^s}mIv!kJBU)0qC8vPOsf@(c1v6Hf_U(#kT);N!^9+wwPJUQ z!i&^yk_V!+vZEAJW46WOntOkHuW!dS4R&^ab#7nZ`SUyHeCJ#Phljr*At4P#ML=mO zU^3x8kdpR=qI`$Z_)I+-U@#`(L9DL>Za3iZgiOfGdxF{7RLso*+1Wr_8&FUXOCkp# z1*c#*^MlDxVaj+3Q{s!*;Cp+(=%^ss+QP@`YO|UlgFHAO>+4CgSx9Fmd2+%i1Vg-N zPvhlVGo^QCD*l~l{lx{@*GDENix^c^-0*nV-Q68D-mu&4z~rPT&uRq*27vN%s1@oN zN(vHw5Jh zZg1b;`2Bsdp+UE8ZzltRzmRBS8yQQhm2|rq7pA7jtSsS|nMqDh-;#87b^RexM^J8V z)_#FPw6mI}BuVj$y0DU3NhHRl)5$M0GwKF(1Pz>+__^b9rEUiTP;54!u@NXK!GB|O p6BrwV?Dxa3PUJ>@{^@T41_1ezMa`XP000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU$Gf6~2RCwBA z{Q6<(LuOV6{v)qGG2H$7kKxCE1_owE1_mG^3;YA>2O+`B&l1Q5%# zS*L}={Mosk72Y$*Jpaou>){`Ur@#L*urNX_Bie8v{tYD9fO<@LnHc<)m>K?myT*`N zexC&(fEeA~y*_;W_=exi=p{o*0S|+aFcZV#JAW7^-Tlq*8K{Y!iLkG}|7Boc0XpAb zoS7j@i+aroCr)^zg?=fB<4vRaLI|@Zk%`*6q(3)@}R7@bAw*h5~O6 zh7dVshHpSyj=lTK@Q3j~&{zK%(BY5&{~130`p@7c!pP8Nz{-%K&&qJ?_z#BI?6(XH zmV9CO@cu6YBjewn009KFM784k_pcn^zkg)-@b)jmkyF1IZr}UMAjZeU5N*iDpeo43 z@ZsBkh6i8&GyDa52^5UL&;+|)jf;^XMS+!}#(-)@b`RXqQZZ0MU zW@ezCzkh!M1Q6JguV24%eE9GIC)6EICy*M49K$^O6qQrv0A%AgOlpnBpLhOo#t z4Da9lW8ej+L?!{k*pFl<2K>358{xKXm@sq(#oQa{-iJf7S4+q1( z13wvBCw^u)efAePh*?+>aRU%QV4r;a_=V&7%l8cI>NvuAt9WL;r%~GhRZ-bpde%f z<*omJe*y#$qphvmhi^Y#^ZR(eWk~SnU=Rfc!%CpnSHAqqa1)pr|AP!<#9vl{uo5ux z)8tthLZz7*o&fVq@8WL^ix+-m`1a}7M}PogEUMJ_Vz2X^`!CBshKoSSEzkdf3rbMx zVkM#M`UNUjfmj8Y+&!h37_>x~8IB+O!LWYom#+W;#A2fU8Kv+!phZ`JUY7#qH3?veAS2265g@<-n18^Ty^AA;00000NkvXXu0mjf11K>J literal 0 HcmV?d00001 diff --git a/data/images/flags/jo.png b/data/images/flags/jo.png new file mode 100644 index 0000000000000000000000000000000000000000..f4332604b23d243a5d2141b7ad406fd99764d336 GIT binary patch literal 905 zcmV;419tq0P)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU#8%ab#RCwBA zd^}_7!*h&G{MX)nVEFg{KLY~@qX8yHMg~R@2C_l)f1nyT8zj#F=77au0Ho&8!-otT z)~)*p5I`(Kr_TvH{rk)P;O%RM{ftZu-+ujJV1`=u52%=hi3zM3Ff%j!0kTqG|JeKlbTrUGmo8mmSifNd13&;_Si-={%D`oA&cGlf#PD<5 zHU{?Hdl(oG9%A6^>SAC`PiJ7@=4K!UfGpwW;bCBBXJ-HiASQH4W-&1azSXN4_}8ul zTl5#?ZD1I`DJWogqou|0=lF33QUO2!VFnj441WMEVC3b6gaBuo8aoNdTz)_v_ybKR*9p00-iH0WAUPU}Hcjn1}?7OpFY_8GkbX1Q6pJUfwTHK7Zz(qsqas z(}suPEzod~D_H*iXJGixKt2HK{qy<{!_WIazXAjh;{-0Ihx7QE`S0>EGyDengy|n8 fCSwI30RjvF(KSDa|8mvk00000NkvXXu0mjf_8Oe} literal 0 HcmV?d00001 diff --git a/data/images/flags/jp.png b/data/images/flags/jp.png new file mode 100644 index 0000000000000000000000000000000000000000..fb4f7573a5b9b1a1c3f324aa91bcd5ab12705c66 GIT binary patch literal 572 zcmV-C0>k}@P)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz&PhZ;RCwBA zeE*%4M{g~| z?=$-u{(t$zz{thN@Y~jtfwQoYfk{!FpniY=B5cW@-wfX>;u-!NJI}z#%fP@0@;lI? zU#nI!{CjqXfp^7m1_l;ZyhZ>75W(R3wQ(`SpEE!U1fWi0g3uuT-&1E9eym$S(47DQ zgx8Y)K>YLEQJ`r+1|ymzkpCZ;>i?ZROwba50K#ht$Xq5KKA=gsViy>i99&?>-~#{w zL@>BGydxMGg*kzyp-KD(IzW(vfh#Bh!;|O$Ab<#3!mMY`z@C)H@c%0V7&H6?(qDiW z7=-M(H4H5J76gL}Ab^NU!kk4N3{2v33_o{nVEF(31p||S1OrD%ECZW=EM9$#j1WHp z1Q6Vk|NsAk%Nm?M3QuF;1Y(9?K(DZ|gM$c^+3;mXka-|;0Ro5xsF3BxjT;QtuU}`N zW+4eO>%oHu438f_W&sEwmJJ&=yxp~H*Pl0U-q57{0$TPFAix01(W+HR5liR*0000< KMNUMnLSTZk8u#1) literal 0 HcmV?d00001 diff --git a/data/images/flags/ke.png b/data/images/flags/ke.png new file mode 100644 index 0000000000000000000000000000000000000000..3538e8d2edbe3a889d4a876a4c6500cb575dc1c7 GIT binary patch literal 912 zcmV;B18@9^P)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU#B1uF+RCwBA zoHAv~Lk0#0{!gDiG5r7kpMhEcWY&WR4;WUjUi}dufLP9-KQH|E?_cic&!1D(5}=t3 zK(iS>e*DM+5I_uec6J{$G&KIRv9bMUVPV0FwUw0rCj^Fq*+Q(WVA{{k`M;Ee1WrAS zjEo=`gYXA{0Al+0?;qIKKY#w<#CIRwWw3YGV=xlpXDCyVWv~?#V9?OgWO(`NB~Cra zp#l&y zUq3TEymy=7)~&lA0Ro7{fsd2n4=V%1ZPwA*mV8n=VZ9@^9uvZiP90CG3 zEdjc&5Cby~pMb&usVPMVo@-$X7~k+^zX_q zaYj5lGc!Zf{re36AKYfRaC$4l-*4|2yma&#^bPcZ4*SpWAB#00v)JFiXL$1PAp<}F zvD}YPWBB^#E5pa1(pVLPBawxHLGTn816SJ<20mE@h95jv89sZ+F`N%GU|{}(BgX?R ze{$m~!<*ed7ytr@W#OI046lB?VtDZR0mHxlSfh~P2h%?WD<(MxR|#eYzI#s?m^gSD zP7B;;ICAL(1Jm#SkRZhpUVk3`VfgjxCj&qLv2ZZ4GjK6+GVm~QgKb9#|A4C18Tc3^ zc_kSZMqXnGSaq7=up=V_ySyxe)VE&@Um1S_Ex~FH!!M>^!1(;n01!ZoI;(WPe0cbQ z`}ecoSSP000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!TuDShRCwBA z{5oaILk0#0{{NpoF);l9&p<8kA86L!2M-v2tX};QAb?nYpFc0m@b@qGzvs`XYsufg z41dm_XV~)bBMU$PvHZP#`_I2uuNeN`yT|bV-#>zy8QB;a{{JGvF+fYM0v&ycf#DB8 z0I@It4F=iz|IZ%=20WH9%d#^taxgKl+w(E}d-I3k_w{cKe{O!qsUBp~UnmU_Kr9T5 zj07FUEXKybs>{Xj{}a$L?|@o?9$_=#0cvGr_*N0|+1%g2BVi#K7Uk4>art zFd~0|4FpB%zw3V)I9vsQ_Wfu0bK^T+GXMgJiI62sf{ax(ll{uvlt ze}Qqs#PILk9|mSYg6?Dh2p|?>EcyHF7XzCMF9Q=7P&qR&;@|#d`1k%V!{5h08JOkR z85n>T;{yNzgx4qFkpA3_rJiWDr=P$iU{n3l7HrKmIZNef|sR68!NB z5I_Wj>mM+(nfRH3DG3-KtiU+p0j3pUW`=*SelxHraWee>{1@sP+yEee@CFyieScp4 zV)z4$a5igRhQIfJGJMW`%<%Wl4+a)3pasAGG5mS>lVBzW2q40#3#k9sxvvcDz#w4( z@@1CWpFcC)hUQM30GI&&1A_+Sy8mx}GZ4zcAhR9= w@e>9H7JvX^+4}kO+l@dY-ZIb(d;|zE0L-!U^Vp}HApigX07*qoM6N<$f+dJSkN^Mx literal 0 HcmV?d00001 diff --git a/data/images/flags/kh.png b/data/images/flags/kh.png new file mode 100644 index 0000000000000000000000000000000000000000..772f5870b103290c6f824d72d51e8339327c045e GIT binary patch literal 836 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 literal 0 HcmV?d00001 diff --git a/data/images/flags/kp.png b/data/images/flags/kp.png new file mode 100644 index 0000000000000000000000000000000000000000..f8fbd308fc9ef16a6af1c5a16182a912060ae985 GIT binary patch literal 667 zcmV;M0%ZM(P)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!ElET{RCwBA zT(tSpLk0#0{x9EtG5r6}KsE3m2%o(C%&_gqosR$k#B%xeE8)NY{&T;2_mzQ)b};BoNz|Ni|)cMbysBYBqm0|v`4hEEL44DbH{ z1Q1IX=S_xxe?K$);eN*e3$On``u~?N42+CS4FCW9VPNFuW?*DxA=#44-(E6o<$l8e z5I`(UE0!=Yym-ODeD^L;<{w-Um$w%Ko0S#AzxVGLzV`Pp`~iBO6i8#8c4N0Xf}00M{!J)T)~bQqY##TkC@-p%my>Q#mx^XD@#a&Ry(o0*Yq z2|xg0_~hT?#|#WWL)m?O8JM`Z7+BQR8JMJ{8UCF;OKw7A00s{W`<poM=fUS#;Wa3RC@4I9X|1R#Jg(q#y zmf_Fm$1DH=#Pa#+*|$HQpZoLo*B1sFfsX(I1^_3)@N7Ef{cr#P002ovPDHLkV1j3s BD&znF literal 0 HcmV?d00001 diff --git a/data/images/flags/kr.png b/data/images/flags/kr.png new file mode 100644 index 0000000000000000000000000000000000000000..f83ebb31eb8fdd97ba29a262b85385212d7e7243 GIT binary patch literal 1142 zcmV-+1d02JP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU$2uVaiRCwBA z{Qv*|Lk0#0eg-4}6k~Y#@)d)Ggg67S;K9Sk3?jlp3_LvC=u#g60*D1j2m>*S%>q#tVnPn06)RRT?A*B<$YTV$ij_f4O@%>0UJfk(?dnyASFy1SZ(Lj$Ud6;PeA&Gl zY#9#^4}%~-9|H?38^fQ!{}?uJ+QKkp%2bBmzkf6Q`SXY2-@kth00G1Tv*hpJzYO2M z|72igWdTb3Vh|M(XGlnh2l81NzHHvi@Gc{Rfeq-&KVT;?Fnj`v{avzzf#27cAt^b5 z;oP~433{VR|nz%lFVqmDQ1i20@uCK2RH24<-I|mnolamtz7Z(@Y5`X|=L5>e*W@d)C z_-F=hpaM`ZfWR-HVLy%@Vc>+u1;~Q$%&ZJ21r!+AUVLXTx^%YnfR$H(;exCdIGBWh_>`Cw!vQfJ2Ijwi z82ShP*PHWn+p&?aG!u8 zt-EI;!_Av_7`}Y{!BAV<0Es6cE=YD_c;)8D&?M-@@aPX8!?zEg8I+{C7}^S57#JBD z!Rqead&KYyn4&O;QRvv009KIgpG}rL4cp1;m40340?K64B`?J3~Sb`1M+_| zhzkoaENk*+C<#zzu+|Y}2zFIvm|E+@AT1>ZOkDpM_U_%!Fnz`>24xjh26YWBhUd>; zgHspG0RRDn5?m?CaX^E47%rT@0M3l(&R=3MGBRM0lam9MO9BjSCEnot1WJV<0E#3 z|LfPU;hI22!pDyv8AL=xh%SXceE0y&ylf2I+*}x?FhGC-0MXHSm`u5RkN^Mx07*qo IM6N<$f+APy9smFU literal 0 HcmV?d00001 diff --git a/data/images/flags/kw.png b/data/images/flags/kw.png new file mode 100644 index 0000000000000000000000000000000000000000..24748c6c3b1c79a152ecd81b28e485703849d8d5 GIT binary patch literal 606 zcmV-k0-^nhP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz@JU2LRCwBA zoV#ZBLl#Xo{;zCb8UFwM&p<8k|Nno6C*PhhYq7=AwZ$?)s$uRj0*#PaXoKL!x|@#qJ`_ulUe?3cJ1xKsHU{;~XL`1|uO0|O(u z;l;r4=kFf|kof=s#KOo3F@=$hk>UTJ{|rC2d;!Y;XAlS!X5f_ICqJHm+P<@VXZXkb zj{zWnSWqOGfR?c{GQ8UIjv?DLo1rkS6s(x6-~#&K(uGS5>z}V@00yhB{xoh`2FAC3}1i$WB>>t zxFx@V_%qOk*gp&mlYpE*zrQj3{_%y9Yyu_!GXMk-%iq6$8NLHC7tni6K*IxpPX7)n z7C`Blf&6&+4>kiBWIzlMK#YQ-qF?wved130^^Za6Kg0rRq$`jakAYTPV)*|RAb?ne sK74p9{^#GHoxoD_F9S`$M}PnW0AqK?)%EBcYXATM07*qoM6N<$g2VO^ng9R* literal 0 HcmV?d00001 diff --git a/data/images/flags/ky.png b/data/images/flags/ky.png new file mode 100644 index 0000000000000000000000000000000000000000..69737716164f03b82abdb50aef46f96e03657c83 GIT binary patch literal 1163 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 literal 0 HcmV?d00001 diff --git a/data/images/flags/lc.png b/data/images/flags/lc.png new file mode 100644 index 0000000000000000000000000000000000000000..a1205be94b729a7d7458b5937ad5f3acdf9a2907 GIT binary patch literal 769 zcmV+c1OEJpP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!lSxEDRCwBA zT(tSpLk0#0{x9EtG5r6}KoEeGFftIM?mxqmm!BE79l7%nAb?n|-hCtd@83V}SMR=o zC5Qna$1oDG>F>Y)4A<_yVR&)(6bnEAu{?P8;m_L--x(gg_{8uJk5^y@=Y;AotlD#v z;rW|i49v{9Z2~%+;pfMv48J};`U4O^EFgnHw*LM553eP^|NLW+7UO3yQuxkrNtT1* z_M=7_2pz8FuYH$nfdaeFiUEb%yW1{t#seKmZYC$&cTE85~XI7$uxj}#U=VRK+`a#df#=^t25(yh zhVMTJ`vxF@2wDP)a7#UDhCRFYGJN~`nW40Zhe21Hhhg`wlMFU`q72MTOay%c5I{`$ zBl|yt03SPp2>WM-{kvB%tXv|-(ACS$uycnr!>?b*8O|I%2y}o1ko_00B>(}0*ODJU z|1mh3C^HCh+-6v|@GpaZ2r~o2&%X?kGT#_hFK1;?kvPuaW+unL2=)zb{{RFK3%(@F z$P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz>q$gGRCwBA zoW1DCLk0#0{?A{2GW`GlpMjB)0Z21Y3_xZ*dh(uO!?ueb0Ro8S($%NJfBydGe*W?c z0~gy@22S>$46i;4F|aWG16oMFZGZp%XSj6jDZ|?rmstP;h~@5sH-Fx~|HSb4@jC`# z?#B!~oc|c^zmf$iV`TXKhly;9{{H*V@aN|%hQGgF{s9OemhV4V7);deGAyXv$Z+U5 z4}$>j9|l$BeGD~|^%xFcHDO?4`cI}sKt2EefrAYofLMT0z`)1(gW><*uMFx+5J6VP zUku`c?-^KG{sCP?dHetb5DUnq&)y0#y!*fg^u-UL!T%ZlF)%ayXA)%i2J|Hd+i!|3 z0SF+L-)zhbCm$&=eBWZupd|I0fr*)s;l=|ohApQI7=E*}F#Kj@CO3Tn9m>Y=AE+N7 zfEXuwWq$bh@*V%1dru)@$N)*gprj0m3q~@nU|@Lt_Y1@AUvEDG1Q1K$=PL|Fj5#qAd{{G@pd2v2q2bkKmru%KN+YCK$d(3;$J`v5I{@}bN~PWL{m#( zK@Jc=EWrE^Dg%IV{*U1wm4oXaGywnv5X<+=ms#$9{>*S6DnLz3o&oU-1_l;@0Aktw n`SaV|3=DtX(=OCL0t6TUw=Bagf=)KA00000NkvXXu0mjf1oZ!{ literal 0 HcmV?d00001 diff --git a/data/images/flags/lk.png b/data/images/flags/lk.png new file mode 100644 index 0000000000000000000000000000000000000000..9ff43b5eb7e13340732be0f3901905a1692de328 GIT binary patch literal 1111 zcmV-d1gQIoP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU#=}AOERCwBq zjIj-XFc1UJB0c-JEWtcTED$MO;El5ZZYje+>6;TxKxv{lEecKrFxhKKQfr+gpaq-ySjiVE@nHa`PL5 z+q#!v!~XmQT4FE6aQiPOgQ(PB24;2!hUIgl1&;&2W1E zcZMII|1d~vGcw3(Ffjc5@rOY`>_3B=2{QvH?>`2QI93LSU{(eumcIpz2v6*~jt_kRpqXMbn-4GbAcpniY=Vj?aQffj)r160k$0rJIv1~zsk24!U? zhU?ewl4h60E}ih00hetK~|25;oU0+hU@44GCaNW zpJDgv9}I?8z|mi=Hjz4He{S~VBL z-P`{ecC7iqaPPt&hWB3?7(V`FVBqCpWcd6JsDl-v7a)L$vgAKdJqIwgC}^=V>{;`Z z;mezU4B}GE46MKu_W9i(23{b|Ey~RB?!|wG&)-35o{>S#l#Su$&7a_u0uVsplIb_l zl;8jU0gD0#F#huo8V3xJT>s_Ee}>=`4hA_r7KX00&%j6qCIDdSSvBoD!O-~fyE2N d?vDTg1^~CUe8c|?MPC2_002ovPDHLkV1oN61Cjs$ literal 0 HcmV?d00001 diff --git a/data/images/flags/lr.png b/data/images/flags/lr.png new file mode 100644 index 0000000000000000000000000000000000000000..dc4c555b57235b78a7b6173ccc504fe8135b96ad GIT binary patch literal 689 zcmV;i0#5yjP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!LrFwIRCwBA zT(tSpLk0#0{;%JEGl0OK{|pQqe}6LsKRC$1{{Iide+Kfv|Ns9P{yuoX@MHDrj{pJ0 za`o;T;cq{Ga|>{=Ffg&QFkHU(mO+S)B6+&oXQb`~vJi)r@3Ya^>Skh70fC{{aXfmY=`> zFleZVGK9Km1I=S(xc%%C!I|FNP9mCgO-x&T<5L}?3`US)Q0mKMQx*z`iXW$1VIdFV|qxwGs_n#l+MmfY1 zU=W?Zbctcj`t=_H0*K{*c_9PCyLSx#pFRWH@DJ{a4~)zVlmJk0eg5)=;m5023;+Sd z@)u~>e<1$L1T^$N69ZKNFtvg(Kmf7u*3^Lf2F&VzsA@haxOlHzVc^)Zg#jRdSU!Y@ zG5me;g5mGIdl1X0WeF(AfkE~aXeK}av2a&aF#P}VgW=!j&kTR5X$jB*&WDc}*iN2g z00P000jN0ssI2o&@u<0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUza7jc#RCwB?lfO>FP!z_`E%!nK)j?bc z8WP{ZLtH) z0BeBN-|-UyAOLO?9Dl~G+Bl!jsj4}dEWK?IVr)$*&17;I6UXtkQ`JPj{|-)=UOUeh zm*=fVF@TX}xmYZ6p`BoMtljR|b~qVMh?01HQ*JcG@Bst|tk!Co><*46w$j^zAOvT` z0nb>*>OrU$PEH9S%yC-v=HdQ9ax>*S@2i(P&6=yG$9{2$TP~JEMbYOv&-1(4Y?4ng z#{QGz4sG9y=;*dU^>I&Ayz&FYI3NT#&K&_*Yw$Aq6*Zwb!+?_zPeFXczoYINx|d5P zd>MivW}`2+sCE`hvL)k@X|M05;YbMJHzrpa1{>07*qoM6N<$ Eg0+9fcmMzZ literal 0 HcmV?d00001 diff --git a/data/images/flags/lt.png b/data/images/flags/lt.png new file mode 100644 index 0000000000000000000000000000000000000000..e951f7d80b88eb1985949b91d92a50b52fd0440f GIT binary patch literal 508 zcmVP000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzj!8s8RCwBA z{Qq(KLk0#0eujUafz*En>H(lxe}G1RUH1_nfLIuQT@+^c|CgKL?+Yqh@|S_(=LLqf z+upGN1P}|u?>m1O{=NpP000jN0ssI2o&@u<0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUyx=BPqRCwB~lEDgsP!L6r@fNNW{JcKQ zPpF`Np=B!2))8VTn!^SL4jXd3cd)D?yc ztrc-hmXRcqsvT@$!gweghq}h|L^W-TF^bu2aNkEl6tPP*!GydMs=^>bl)Ko zZZDb^HC>ZGINfDuOfiRkE<@E&K4t3}`1Jo}{iO-LSwqymqPWCdkD{*PM475$vm{th em-0LQ1Q-CIKT*u2n}lls0000P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzdr3q=RCwBA zJUwseLk0#0{$HQJF#P||Kt=GM;ltyn4A(dB{0I<0EHAHI7ykG6ANQA+uNkOm%fEkr z8D3t#&T#J4GZugVVtIG(;h(Q>-Z6Z5_!wx%Kk8ZX_aDQH-`^RY{r>(3Ab?o@|NF=A zA86~pzkeA(Z0ZFT(4zkk8X$mJ{{H&K@aOvvhTq@5Q{9q3e}6Ij{qu(bAb?opEKM1H zeE7&9rl|%DF6zYz$SjWcZy8=ZzQ+I%K#V}n2TqAUR}S%^7O-dhSz_7GW@4%@%W$N{omgVAO8Mk0SF+L3vXV$z5L_TpRfP^ f&?<3#1PCwyG%n39%k=vo00000NkvXXu0mjfTJ75) literal 0 HcmV?d00001 diff --git a/data/images/flags/ly.png b/data/images/flags/ly.png new file mode 100644 index 0000000000000000000000000000000000000000..55f5edd500de7c99e332ec844b8fa2f1886b097d GIT binary patch literal 354 zcmV-o0iFJdP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUy^hrcPRCwBA zTy%HQLk0#0{x843F#HEXYJva%|1&)K_Jm>E(`_FC0*K}6hpWQ>{{Q2C_2U&)E&2QZ zFT=GD*BD+Le!&6|Kr9cwJoxkW=UawHUmr05Eu)$xAhUix_{s3=?yo-p0mK60fNTX} zYFYvcl>dMKgM$qqfS4E=85w8|00M}Kferv5faqcgKmgIj5`X}rizNU7L>Ef{0*Ee_ z00az}`${?aPcJ^}<708;pRaH`3SQ~&?~07*qoM6N<$f-{Sa A!2kdN literal 0 HcmV?d00001 diff --git a/data/images/flags/ma.png b/data/images/flags/ma.png new file mode 100644 index 0000000000000000000000000000000000000000..f6f9d1fefe26920130658dbb84a9d1a36509ce8a GIT binary patch literal 720 zcmV;>0x$iEP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!Vo5|nRCwBA zd^KgtLk0#0{=c6-F);l9&p<8kA86LE2M-wDuU`ETAb?oDoj))9|LO_y;=^!yRP~hL_@O3~WepgaAMQ;Z*|)j_+)Y3_n>J82$onxuwd^ zzzFpDSv?Mhuk4Hrzkp)jf%HEDHUR_>3!dNy6Pw+67%m!cFbI73%kWf&gW(4!Gs9sU zUWU)S3=ChmnHa=h{$Yq;{*i(I>pyVtU;_XF#Dvcmj6lMF&sPTH3qKhiC~$xUTbiCR z7+w6va9^I2LHO-|hS1HQ8Tjy600IC3#6l>zIKKa5h}!&#f#=JAhQ(1r3|~2z7~Tl8 zFi5@l%}_Gq4Orc80&xNmKm_CMFEr@bfBa(*eEXYWL6jf^%ddY7{Ga|Z`~fDKKSV@4 zKmaijqu?_uBg1xAUIx*(e;LZBzGiUQ|ApbQHW$Nf1y%-DA}j$2AQoaQc_PNbAo2PS zL-y>q3|xQyGbla(!od3bKf@I*4hG%(zZmc)9v}b+AQoT_V*ug*z@&*Szk|fYUj1Q6 zpY@i3>FsvXa+t41Q-A?%@V9EI<8*;0000 CG&JP^ literal 0 HcmV?d00001 diff --git a/data/images/flags/mc.png b/data/images/flags/mc.png new file mode 100644 index 0000000000000000000000000000000000000000..05b3bb4800d49d0901572bfe5af1e8b281bdc17b GIT binary patch literal 421 zcmV;W0b2fvP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzH%UZ6RCwBA z{5pNgLk0#0{{NppF);l9&p<8kA86L!hYuKjtXusNAb?nYUpz0&@b@qGzZcJ`Ysufg z41X@1XV~)YBMU$PvHZPr`_I4EuNeN`zsKP000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!Lk0#0{x9EtGyMP000$uE-@l9u?nbv5)KyzZiZUc?opHe^g8U{$==c;XK2ZcOO{*0*K|ovrm8Ce)z%g=*1_7f5<)o zTJrlBBSWa!WuQq{fR?a7Z80;$w@*MJ4xkw0Uyzf5 z4)}wC!RG#lxCJ19fNo)AAP9giJad2<=vXF(vzJ&HB-NQ1K3`yFFtho?z{*5eJ3s)j z5K_Pd3WBeU3~Sf0F>t;9&A`g`jbV5HKZdtloIp!}o*}}G00BhM5|AOkzx`(r5oBXf zmS$%7c=sE_m#=>rn55Vle*6OZgs>e900BhU5@2wA`NGKX^}}C=v)?5dWc5BW+`b0% z;iE4MZ{9F4@G%p%1R#J2hUh20f!E4B!6)wE~m*e()A*f@ZB=^tTB00M}GffxYv4I3K+gS7lV25H$Z;Nk=v zO#caL0|S5nV)+ZqmH$Ba|37efgPh|*!2!yofBpc)fBi@3{Dp|oKOlK57W{`~VK5C4 zKrA;Oykq(B`Z2@rFV9fQE(U1Xb^7#k2B!btfW^iyOp}1V`+Msb!{P000jN1^@s65oToN00004XF*Lt007q5 z)K6G40000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!FG)l}RCwC7 zR84QwKoEVqUdQ<=r3t1g0)z^xR3X6;!KpXO1^5sA7k&#uf(!KAW6GuE1VvO8snW)x zA^k{f#~-`Ptec7o#V&_3(rQmMEhWCo z6kmF?5SnP|G(BD9CLper2ZRTq)Z(Z0Ea*1ZQ9dJNU)F7yobsBWE;bPAI~Zx+Ea zMw}}gt=>g9nTIb+aGwQOsQ8F>W9Z|wP;yZSVCaVus&$A2v#^^L9QQ&rs(n-&6471+ z`L0JX1Y+?5kfNp=SF9knd<36Q;J#9L(j3w!4)-8HzZJp1vw-XEVZI)epVw>Rf~nHk zvR=$1Uh89#uK99FBK_D$sRqmpN2kT)pg1Ld0TW{+gWe4x-+N%g6mg)yD52_vus43e zCIoF+`C@P**JKzaRhavzj45k(oE?+b+XPI7otQr4-y1!d05d`&?0v)5&Nez!(LcRh zW**KOCW}YRBPee$hFCxF)3t|lVlYbZKa44s{uW>W%9{dnDiX8d00000NkvXXu0mjf DTF)em literal 0 HcmV?d00001 diff --git a/data/images/flags/mf.png b/data/images/flags/mf.png new file mode 100644 index 0000000000000000000000000000000000000000..798f5ecd327517a3906722f6e65661bcf4c0b5e5 GIT binary patch literal 276 zcmV+v0qg#WP)P000jN0ssI2o&@u<0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUywn;=mRCwBAoHp&izkmNfefrLT0{(h? z8|dlDFkpgPw;0y0MHhVdkYV|9meZ#n{P^+v`SXvclD`ZL6tSE2@+HHrUFd=rFRu9X ziRI3n*H53mzjyBqs^pKjIC}=H;LRI`0|(H=E-^5iXJBArpdJ`CYt*cvViwD_YtP=k zea^t}0WCjXx^!&o*7sO+oIQ&v_JD!mG0?2HZ!a@2++|?+i7NYV$By?GF7RW|k2nB8 afB^s!7E63(I%YKh0000P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzbxA})RCwBA z{Qv*|Lk0#0eg+)i*U`fazYZQ|U}Rw+2>{Jy`1|ky!;f{VKLP|0NUJaqbK_F{_t8U! zp9c>zFtL$n$=|;We=eM7*z)cp3qSyYEct^^@!tpc8Gi2D$H2rvnk5WCORoO=&v5BK z!ykYEA{+?+{sEowhk#SCXam{vA3_5J5EBC(06+i{^a&%-H6WKT{U_aUMyP&(0Ae9V zCCGeI?f8#G0|XF3OLkcEFf6FxVc=osAu$OvFuZ^9o8j@*9}EBiM9`84(yR=7%-9$N z*tkgc%I~{B8GgL`%K#8S1TA6t^Phq9ClLStPqHOUzkul%n8W}Ah%TuMAb@CU2_qvT zm<9+S7L>9Em`gD-)n8zC`U@&1NDr?6|Net%fB*tn!gAxr4TkI2uLCppKMb{(pIm16 z_3#(NHd Z0RW_Xl`(d0IQ0Mk002ovPDHLkV1iZ9$x#3R literal 0 HcmV?d00001 diff --git a/data/images/flags/mh.png b/data/images/flags/mh.png new file mode 100644 index 0000000000000000000000000000000000000000..2a87b00ab1671aadf3e870a81c0e7d3a369aa95f GIT binary patch literal 830 zcmV-E1Ht@>P)P000jN0ssI2o&@u<0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!;7LS5RCwB)lkH1WVHn2mGmrDQ=CEy! zp_`=1bjlXtThuf#3nZwBq6oqiMeRkTzapY9e(@g=@r$G&hMSoZ8sglGb1zP(ZMK)w zw&UKXYi{c~llehNw43wkJkRrcuIIY$8ygu}Os6y9@Bsi|7?#Oo;NsEH+MMUy+`7Y?#cej9%N4NODL$WFT`e@3+<`zms}4H{C7(}( z2kI<}dgHt&jss+HoFSKs9z2lCWMvx7-sGg-W+PLnGjKGegb4T6YWr(V;-Z+r4R8cF zvWUxNb#%yvhT1tCqP10mo;*4lq^N^`P;fzEq^m~%rp_R~6o&)=A%OauAYkD_VKIlp zY-*}NAhcH?C~9oHVlw@*SiDYW@DzndM0=!`w^E&m^@kQ+Ru8v2q2Vdmy)ZV0lVr&0 zOqG`IDil?0c7fj?U0K;(Uw8U^vD_PJ3YUoXNlmXM8WB4U>>`jq2SqnXT5>^oBauTK zx$O7HW@Zcw1}2x6X|;Rv^P3bE&-MmQ;S-Vm8bj|@T{#~>EQjW9Xm||8;$yWaE2Yvn zF)^prE+g-;qocH5?{v9>C`Y5wg>p=%k)nbUBGg-@?~@qHD_KzY0&a9d%{_EJk%HBw z?XPoYmCCHqIFiXY0BWfuevD70QY0B>Fc=gSr76r{SLM>XYg?l05@`BBo5B>Ll~tvA z_RG3bX|`HDK--Rv=K~Z1pr9~3XDt%KaJvt9d6Ax~DuQ>|V z^yJ^?C$N(A6j)Rg4!~jGY*6Qs=opZ0DwO}8Gf52-v9sr07*qo IM6N<$f@3Rs5dZ)H literal 0 HcmV?d00001 diff --git a/data/images/flags/mk.png b/data/images/flags/mk.png new file mode 100644 index 0000000000000000000000000000000000000000..618475d224ab97af47cee081a64cbe429aa392ca GIT binary patch literal 875 zcmV-x1C;!UP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!{YgYYRCwBA z{QrN-Lk0#0euiV87#I%!XJGitfE|bcaWW7y1Mz(zUIE1aai|pnVqc)7^aCJc^+$jJ zVqrLYUYJ4uFE@kLa|Q-3pu|+5_#-SD*@4&(h$VpdJP;qiVSytMM}nLUbnWx=3~LKN zvH%1S!+$%w5C20o{{LUZ_W%EXmjC}h193VKvtYp+Ss-c>vB*gR@j|Hl6Cf^U{C`1| z;eRCq!v}x>V)$=k^Wnd$>i_>tO#lBIGyMO*9Ekq`@dhB41Y!mx+ziD3fmk0++#QJT z0r49kt^#5)hW|i+{67Q4dS0*Hx$krCn5vrw10q1P}|>a6Jme=0KbY#JWJ-2gIL&cqdtX&{yX;s79C ziX;z;3mvFB2B^h_KwJaF`9ORDni`>T1Iru$0mQ`c7w9~dzYPDU|6}-bj)CD%JP;QF zF)%^=0S4h81|+Nv#6ZR$eKc`k}|pZ&=LdYlhZya5Cd3q#svmK(aC z8Lk5(=-*Um_};P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzuSrBfRCwBA zTy%HQLk0#0{x843F#HEXWbotfe+F9-W(HGHCI*JT448nKf#LtVzYO0NzGq`d! zr2hZ^&+zx*1BM^#R(}KtAeO5it_uJA|Bw6Ck5?F$eEIXAL6MV%!4zl_!#^CBeEZGt z>*x!hi~gfp^7k*pp9|+1w!Hhu0uVqf557G3^Y-UkhDTo?F#s(?G2`R!{|pYotUw(= z{O>=eCI1@oOa1`8{O>;# zG#s#4!oUb}GEm_kkYW@iAd`Th4W4Mrk%86#Ab^+{=l}o$h%S}@1Q1;;0SF+v zSOO40bg={=faqcgKmgIy5?JN{2p|?%Cj9sBAH#q2Tn)@k|A6=pD5L%PkJX8PLGoC# zDv19VN&^HC%gv8BSw1}c!0`LYZ*ZZ841NK1Uw-+EVdHIP>;)tXFh@W8!|>@B14h{f z;ywl%_vAkV3qSy|ygKsg?boAU|NQ-gX$r{kN4Oao9&j;%id8J(0W7<||6}<56Iy=! f#~NxM0RjvFc@@r*!XXTl00000NkvXXu0mjf!n*2y literal 0 HcmV?d00001 diff --git a/data/images/flags/mm.png b/data/images/flags/mm.png new file mode 100644 index 0000000000000000000000000000000000000000..f96999501bb5460e48c1c425bbe0aeef57a665d1 GIT binary patch literal 672 zcmV;R0$=@!P)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!GD$>1RCwBA zoU(HNLk0#0{!d@OGyMM#WHEpV1{PLUhCjc4GcYiMcwmBofr**H@Y53p^Di$Lelg$& z|AB`6dhmeZ{p!^p0Ro8S{LM$gfB*gGe*WeoSc09Eg@KiwgMp8ii{btIj|{(m|6%y^ z=P$$_W>yC7*Vh>I-ri>TM#z%Ce;K}?KhLoH<3|>N0AjiQ_|>0R?>;l!d-?_}AtEBg z5anyjz{kbL@ci{BhU+&TF+6+s5$Fphp!e7rl%AYs`2Or1!)GEaxdn9ebq0n%00G4E z@85rhzyJO*`~eF7`|*oGT!4$gL|X|MGAs-t!a@v3PG14a{ey%e<3EOfKwtg;^9LHJ zxB*ZH!(S*35I`)9jEryzMrI}kCU#DSQ&;aZ+`0dhK|@)Vff3{b3IRX>p_uR=XyKD* zuNa;_eaXPf%foQ^)MW+^c2P!VGUd z{$OBW(}$;`izb!^c-|8D2kr!tfs$H~$|0XZU^bFM|^cGsE8- z{}@!eIRIF~biMEddB1mWVeu8UDO_!SMUpU7$z)5!CP#h<_k*4p9IgfLOi) ziQiDo!~#--3m||fPdY>ZfB>SYB`_ZY1P}`YFcX3>IMS(T!9OSlWm~M@(hSy024ky0I}@;{Q2!}VEOW%cA@qWAiw}YkJts^i*&630000P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!f=NU{RCwBA z{5oaILk0#0{{NpoF);l9&wvcrf%rI=48w75c?S0Xe;Clge+EVd4xo70yYoQ$F9Wjf z|3I_;K6t?JWA*Bf00G4E`}}!fhQEKg|2=;WHkFYLsNnN|kO>SdK*bOFwHS^F{$t?! z_Y1?4e+*0vJpX<&_&z<%!2JI=kdI`^-@goh&Yx%4^6?`JKmf7)y?y)7zgMpq{@%OC zz@h=P$Bvod%ckET6BsyvitmelXE-3n%fSEl2Zkko7?~IZ{(fipe&+xKJJ2!|zXC0} z3iQn-pfZ2}Vqy6A4{YoIKYtkh-2BhLB>b0w;rCw#nBV{X0lMh#KQP9$1Z37fuwI}- zG+zScpfo@LF)=VQLPUWSyFC*FcP229m>8%E00IbZ93v3!UT>H({gwuFhB zk%6b0o#F4}-wZ5zz#zK?6cYscto}E{|9}4}wge!6m|!F$FVN6`j0}v-z_h>zq`9dV zTmS*Y1kJiY3xE~@ee@rg=UCN%^iS$p0uVqP000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!w@E}nRCwBA zoN{H#Lk0#0{!hO@G5r7kpMhH7KM+3n{D5Kgz11H90*K}O+w;PI|NZ5D{_Qzf0v})m zO0zICF#G}1VE2G9BX+fa|Nmt;|K>cy#}gk}00M~R_Q%_QUVVSXaPQMShJQea4KOo8 z3b?D7N8V2JJ8jy7#Oa9U}jJjRA%s$?P0JK_XhfmgJCN$Hd&b%7&uw+#}7aNG2w|h z20@?&ZZZrEKfW_C{QS$v&}7=gP-9fgFw1s2gO0F1!M-H5Le>>00D&C z5}+?$0}bE#gq6We%8Y@Rl^Yl*%wQ=tW;URuKN<9-^cX(>mSx!g6cll|EddB17JNQo z{Ljdcte(YCpqs~V@AEx|&%Zu{r9gDHb~b~%tUJTm*XI~A4rVaC{QiQ00f&)Tk!icg9^V27=r*iGdlwt3o8S@)DI9qEck*81UB5?z`)PO&tNQK z%<$#U7lwH^<}p}HS~FOQS%H1G`N3v}ufMieqm=l?^90R#{u?>ycwpC5nbW_ZqkH}V*e%c}nbGXO-*TOfYO@D(6{SU#Wn{FdPi j!yksv4736t0RjvFrb{pW?8bN{00000NkvXXu0mjf!M$gM literal 0 HcmV?d00001 diff --git a/data/images/flags/mp.png b/data/images/flags/mp.png new file mode 100644 index 0000000000000000000000000000000000000000..52625e3d4e131b015e5720e1e245ae0f068408ab GIT binary patch literal 1036 zcmV+n1oQieP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU#o=HSORCwBA zoU-r#Lk0#0{!iZ-82$ z`19%$1H(O_t^fX^srva|e1~%OB0}wz= z*y4Z_7z*|J3=9UZFEG5cEMi!6N}WMX^dUpEqy&REyFA0ZwbvNdF@Ius{xOxo@B3y3 zWkpEP;sO8xgw>MY>^(s5zhz^XzhEE3*=yezuAH`K zxGXt=LHhe?1_zn9;NZmt00IbW+W5!(hvEL~xeNzC{$tpFo}Iy1oq<7IOoTy5T%5te z+Lj?aM4REoV{L}+Lv9RgI=KwY8Z#K!B_9AI7rP|@0fg0(-;D1Vp6h)9CRG*&ZAk`( zcb^#;ob~w`I5>G3EUnBL6r{NrK79eD#(xaI_{11YnI#!`MV}(_5gGspAWTa@Dd960 z!`+{C49|WZVMx>2%kc3B1H+oL;DpKW_Va&+?wt$_lE8$KWq6q3-rq$GXYPhG@I5yL zrWov&00a;VHcS5h=3zKP000jN0ssI2o&@u<0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUywn;=mRCwBAoHp&izkmNfefrLT0{(h? z8|dlDFkpgPw;0y0MHhVdkYV|9meZ#n{P^+v`SXvclD`ZL6tSE2@+HHrUFd=rFRu9X ziRI3n*H53mzjyBqs^pKjIC}=H;LRI`0|(H=E-^5iXJBArpdJ`CYt*cvViwD_YtP=k zea^t}0WCjXx^!&o*7sO+oIQ&v_JD!mG0?2HZ!a@2++|?+i7NYV$By?GF7RW|k2nB8 afB^s!7E63(I%YKh0000P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!lSxEDRCwBA zTy%HQLk0#0{x843F#HEXYJva%|1&)K_Jm>E(`_FC0*K}6hpWQ>{{Q2C_2U&$mi+n0 zz`)GJz`#gA-QWLz8LoY}#_;0s3l@L?VtMf8!JoH3-!eS<`iKE&8E%(BEzsd-V7T{{ zf#Jtrpk;V$0-5#m!B2)?cYpl>2p}d92V^Tg{0E3x82>UzaQtP+QTfYYAn=!glNln8 zOAjcx{{Q_CrU3$oiIEYva~R=XX8;IT`v%gVqY$pLW;HUJPn zO!$1^D$c+l2(;+We~6_&f$

7-E?eCnyC09RU;zmt|m(=EQ9YKmf7evg9Ms#qJUe z4BS8!T{jpQgxMJwj=zN%bnP<(13S=pzyCuV5hKUIu;n=e0~3yr2M8c0T)qGW(=ni- zIqD1yo{|g;Vu!ETD`7%8>v5{bTt54>Ngi0rAP#3=Gk47#Ku3AgSmJ(6UFs zB>nLh)KYBu1SAH{KmY;6a`WR&mJbg}t^kw% z>pvjO0uVqfua3NW`}OG8KYu^{WuOuG2oPWZ`r#&>Knl(I00000NkvXXu0mjfn&e7D literal 0 HcmV?d00001 diff --git a/data/images/flags/ms.png b/data/images/flags/ms.png new file mode 100644 index 0000000000000000000000000000000000000000..022ca1a1a9f7678430905afb556f99a8e46e4aee GIT binary patch literal 1062 zcmV+>1ljwEP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU#xJg7oRCwBA z{J4A1LvCdS{%fDu7*3vl%JAyh8wLgj76w04AqKgt2N_;y*)XiR@Rfn#?;i#sK2`=p zfnN-I!v7e4ipem{J@A_0=Z{}N^^6SY;Q#;s3=bZ>WmvuX>_>nAV(HxRO4yR;Hn)+` zABI3(J%*!PA`BZ3JYsk*^qoQc*b#S!Sd2s1|NNG2J0#ZhL8L9 zGd$nCgF(vPm!aN;k3sV9Zw3}e7luQ(e=r>0d7gp!{db0=NA5EG{Pi2FMSuVP2l@sS zDzE&{}`D6{AT$7_bnha zr^Yy^_27Dc1wu-9#ls3mtKgP=0*EE^j39%AygEZzoH~O(+ardjn|Cv;`y|ewV<*cX zy>lzWbK3xhL+Z8+enM{@~y5FD4AnkADCL!C!{6N3Sv@ z9=*vh&0dee-Pn@h=evWzp!fq$?kLs(J@)1!8^fQUpBOjDG zjsNc5=b$);rj-BC$m9eE0YnTY{s$_@jwFTv!HEnE3BeB-j&FL)Anu~Vu%PeSM}PnV zI)fRii3v#qNHa+M7gU@9Nt_ieB_RVAhP#KJF{ryAVlWE&$*|+ZeTGjD`4|8Kh=qZw z;19!vyOIpXNs$au-gg+b9^_zn^6Cu(KmbwM5^y$T`23BXVdiRGAo~?4Gco`K5DO^# zgYdt9|IsoPCEy?2LQs$b1Q5&R%THN8e|`bve83XMlmL)fpMm%b3qSw?Ew~QM`*;69 gQV~tSM}PnW01TdNEf=&oaR2}S07*qoM6N<$f;PL{?EnA( literal 0 HcmV?d00001 diff --git a/data/images/flags/mt.png b/data/images/flags/mt.png new file mode 100644 index 0000000000000000000000000000000000000000..ada70c644aaaa99e1ed4e1692346a472d52e8f12 GIT binary patch literal 571 zcmV-B0>u4^P)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz%}GQ-RCwBA z{Qv*|Lk0#0{=eV9G5r4am4Tg4fPs;fl>ry{xn~!{?^9QfB!Q4xp1Cg%e#*( z00G4E_s5SvzaBkexO4jf!>y;U8E##?#GvpW=ru_x1_nk(49$OT-(vW=Zyy6A2YyQ! zfRH-A00M}KRY-)v z0vHd@vf>OU4((^KGB;tckBnttz!UkT0)PNw0+WnP3_JqD4EFlQ3@ltcU_LbgKmfrL zqL76RgQuw_0|N^S1GND_03l3dVuD~A0Du6Zjj3n=Ab{v%2|xhR)Dl=$1_&S)l(Ghx zO9_ELz|8mu$pDP000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzw@E}nRCwBA z{5oaILk0#0{{NpoF);l9&p<8kA86L!2M-v2tX};QAb?nYpFc0m@b@qGzvs`XYsufg z41dm_XV~)bBMU$Pv2fhJ{pbIyR}BB}-D6<*_m66p03FV78R+Pn3=Dq&0*Ga?xH-cY z=JyQmek(Kl|3^ii0O5L^q(Ix{C{zV z0U&@_xc~fQ;QjrBf&ce6s$25+&ku%Q|9&$71Q5%YZ)^;ozW!nO@Pz{yB~-Ko7+efr z*%=squ`mDx5KFrMMTW26KQesy{0f}xsF{SHJ$lD*>GWd;fB<5N_Pz)ddJV*nfS7uz z3zSgLpa03Q`sY^$fB<4)_{9da7U-@298|RgY}79x2F5u+0I@KzFaj+AVg?wdq9q`+ znHd=X0*GbJgFg&k|NLS2`13a~s;K9a|3IUj-v7gJ{>@(ofB<5dbN?5^s~d33N nUypwM^Y_zV1{#5n009O7`@qzd*39DL00000NkvXXu0mjfK&JW3 literal 0 HcmV?d00001 diff --git a/data/images/flags/mv.png b/data/images/flags/mv.png new file mode 100644 index 0000000000000000000000000000000000000000..8f12c5b36d0fb525c34cf8ed3901e79b719dfe9c GIT binary patch literal 703 zcmV;w0zmzVP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!QAtEWRCwBA z{5pNgLk0#0{{NppF);l9&p<8kA86L!hYuKjtXusNAb?nYUpz0&@b@qGzZcJ`Ysufg z41X@1XV~)YBMU$PvHZPr`_I4EuNeN`zsKU|awH`NP2e>mS1r15Sq1I-Crwzy31(`1gx}kA;`v@BhCH z{}}!PwKL+k04UA=>pw&I_Kyr~e}KUSv=QiD27mx!VPJ%4WC9BO1v+`5zW~E#J8lNH zZ@(A{)G`_F{CLH%_VGrr`HTcC`OD16An@rgL%`lo4D3Jt!`%cBKrG0L{s9GpuoYG-Ut*gc00-|NUW*=ayxt(JW?Y zyWY?6_UBuu1>^&O0K#I)Uj|7|380(*Gd%eEn1P*{o#K=R5I`7~FahJ}!>^CP)clWu znURU%9~F`{KmcJ_!pg+TaOcZihP6+(FvKfHf>YLi2Ffe~2p|kgez7t!{N!L^sJqwC zz{V@U5UZWY@Qsau;Tt<6!*?R^4XnV?;3GhQ0RRs~^-(7=;Mo8G002ovPDHLkV1hl9MVJ5p literal 0 HcmV?d00001 diff --git a/data/images/flags/mw.png b/data/images/flags/mw.png new file mode 100644 index 0000000000000000000000000000000000000000..1ff74952ebd609074d8f17db57d886b5ba3e5924 GIT binary patch literal 792 zcmV+z1LypSP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!s!2paRCwBS zi9rg0Fbo8Dd#}D;`VJ5MPke$wCDVI{Qd+VC zCgM{d7jEp+oi%SU?g76X!l%hu;H(+N3Ly_FUhJB1h@#Fw=iY?XNH~L9!8R8DLQ9Sq z1aD0&J^@G|cN+kK7=}8RT)|A#NX)`M>_>qZ0r5!!KImLn&w92ZhASNI!F66lKrIH3 z#GWUfyfLU?Az<4Hed|>2YCQ20*P0Un(ILRHlXhS0*FNi=>7jd{150NhJRQh5XsX2@Ib(x znvi8d`gnnSP9O~sKrA3v{)J*{f`3pmK{P-B0TU)9SeR&F3CwJO0AhI|$jb2h4=cmZ zFKk$YixNwo|NhVL>E}NNfB<4?&J|{O^Wz`G<1arL{{5$(C7|f}@!%K3rxRZp00M~R z2L~gtm|$f13QW-d{xee5l3zfxe*@D#Kmf7){|k&BV8&zo{f~j^-+!uF!UQZ07=eKU z5I`(9Ki*{d@bCk}?~KrFA0yn6ff=+{4gKT$go!@);@00RI$ W3N2?n?IH~T0000P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!(Md!>RCwBA zTy%HQLk0#0{x843F#HEX82JAm$Yx|<@RjjpkmHtPzzKf-WcWH`3IoIUp9~C43@G~l zK77FNW8LbH00G2u^}|)+fB*k+zxweC#S)+x10y3NgPoWi0|Pg1JO2Oq!|?0)Q3i%j zUl|yfk;MQ0W%zU9Jj0fEA6Wnbh~>eT2Y=rFe9Q3Y>m#ti=sx+N@__*_09kV65X1ks z9~l@}ki>zOT>bZ-;nIJGKL7#50y6mT|G!|2Yzf#WK#1%+5ZJtB0|Up;9}KR+5hxac zF%kfo^!Go61_&S)P>>JmN!%GuKle}4aB zV3F2fc>m=SgQBGi!;{azI9k1uft!mHuO$Ehgx3;Kq;s&bGjMS6GU%JzFx-Fmlz~-2 zgW>1*uMBKJ2Qad-Fz^F|>+iq+cr5`4ASOaV#V;VhAS)xq@cP9w1|BXhhRa9xG4KhC zF?{&^jp5JVUkn<$It;&m|Hf+xKmZYpli$C7F}QfTGVt;7G4P9uGAQ$MGUzEQF^Eb^ zFhoa0Ge|2c0aGu2O8^3hg{X)Jrm(cM42IY5KQQc-;APO#H)hZ>wPs*pVPW|H0e5DF zrEY)#Vu58skYWFk!xqT=&j>7kfT`yXG@}Xf@-T#??1&c0RSS6I@dmNtPB7E002ov JPDHLkV1k3}e5(Ke literal 0 HcmV?d00001 diff --git a/data/images/flags/my.png b/data/images/flags/my.png new file mode 100644 index 0000000000000000000000000000000000000000..4eead54bdb20dd8f5752f568eb56f75ca3f78324 GIT binary patch literal 950 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 literal 0 HcmV?d00001 diff --git a/data/images/flags/nc.png b/data/images/flags/nc.png new file mode 100644 index 0000000000000000000000000000000000000000..c1a539549a4025143c149dbaf30f945ee069178a GIT binary patch literal 508 zcmVP000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzj!8s8RCwBA zTzcg3Lk0#0{x3iOGBEuA&wvbm{Qk#atuDl1tSG>M3;cidkm2v<4G<0^vIG!4dCc&4 z+vbk|0mO3s=@;RD|NnEp{`3>w4u-Ek|1cb@c+YmhJVM7K%CCVgpm06 zm*LXS-wfw|{r&?GKrH|MgDnN({}`71!6Jrf3&?;!P@fP000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!5J^NqRCwBA z{JL+^Lk0#0{{P>XE$|N*D1ZO_0S3}P27mx!VdUatU<6vi$RkM55=Leg21ZtPhW~&6fE|V} z4jF+`jNiXAFtGk(00rG+b2LD2wuFz zz`brg13&<=FtT$I>HohBpXL@Z{5Z9lfr(FqL8y2s1FNDQZYKdP0h`Uj#sCmNOvHxq z?}t|zew^OQz{tYN@aN@yh976Q6CFPQ0YtPVtP-jWT*C4UY~Q{yaI*0+@W^NrZ3#dC zvE2D`hvCn^KMX(r{UoUPPf`ImxR}Ki893FX7#KcW#2Y8KKHp+^_45@2KmajD9E$kx z=I0y!M_(TSlifc&(as3;4jTg_8y7gj|Ns07OfUcNWqF35_kJ>bIrilvKmf6TeDVA5 zZ-!rgf8n(R4E_Rr`{OgQ?20uu~C0I~ds7A1`T85x-VF;OwcGcYjyX99-+ zKmf7a{CJb)!@~~@zn=heJ{1c{24JB4dHn~3SpWiv<<*f_Z@(V>`seSbzYH`29{~ak Y01YPYX{e(xd;kCd07*qoM6N<$g5P@@RR910 literal 0 HcmV?d00001 diff --git a/data/images/flags/nf.png b/data/images/flags/nf.png new file mode 100644 index 0000000000000000000000000000000000000000..46f61f0adac5efadf86523e942bf0609e78c3988 GIT binary patch literal 848 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 literal 0 HcmV?d00001 diff --git a/data/images/flags/nl.png b/data/images/flags/nl.png new file mode 100644 index 0000000000000000000000000000000000000000..08d2f2abb7e1ba6ae28d669e4345cf42d79d3bd2 GIT binary patch literal 506 zcmVP000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzj7da6RCwBA z{5oaILk0#0{{NpoF);l9&p<8kA86L!2M-v2tX};QAb?nYpFc0m@b@qGzvs`XYsufg z41dm_XV~)bBMU$PvHZP#`_I2uuNeN`yT|bV-#@BZ0(3aTRiL9UF);iA2p|>)pur$p z|Nr^Jz(7SyKqmc#(f|R(!pOtJ!1(PO10z2_RV`rzVW8s~e*b0w2p|^jrOSXmf%t}+ z;2+SzJXbC=uy5YX01!YdjC}l{XaXZ@0!C1%@Cz_7a&j^N1P}`-g21WjKb36(S@IVW z&j10$!py`7^aLZ-E%^sDl7)$hfr*ik0U&@_K7acOv<#SW{{EwSoP7HFli|m&-wXf& z#8TF{nc?lbuMAJ0ePH-UP2T{``uh1P!@F0P7ytr@PhWm8y#M?i>?11L!tnF! zFNPmK|1baq5DOzTO#`Ebfsu*2aSx0Vu(<#M#B%HIE0*_fpECUX{+haF7ZCpb`5uTr wumA)Q%Zn!$-hO&>`Olv}Um0iwJ^}<70Ozy61wq?hi~s-t07*qoM6N<$f{pXsdjJ3c literal 0 HcmV?d00001 diff --git a/data/images/flags/no.png b/data/images/flags/no.png new file mode 100644 index 0000000000000000000000000000000000000000..3f24fb54b32a1a6a0793287ffdcfff190eb6117c GIT binary patch literal 884 zcmV-)1B?8LP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU#21!IgRCwBA z{5pNgLk0#0{{NppF);l9&j1Gh7#LW2I2oRKL@=yB{ggppRh+>|@H@lTwQGQCfbxtC z1i^oxS$`isVED0a^+$jJV)=dXyfDMxzuf;`JV#jamw|yvh?n8LsW-#1i_aK%xj7h| z{(WZnb@(9I0Srt;Sn~HT!=DT18MeIp$N~^REPwCZ{`2qkD~7-K?=k%U_Yb1t56~x) zLJaRCKQSD>@Pt8GP8cZgmf_cdeLywDSpsx8!_|NP87}>2_yZ6?EDS({LAL(?12h@Q z5{BQu!CwCJ_a9LBKg5?{HN;s0Qug;hga!y87KY!z0R9PN{ea3qgZ$qQpa3Hz@EJk$ z-#-vFWB`BwVquSsX88B{3&X#6Z{cym2sH1Hf*iwtZaxM^pzr?t_{s2BQI&zSx&jhh zL`3{QpjqrMUox;G_pf5=eA z#KOR&5Q?G(f9m?j@QUds!>wlw3;+Sd=$F6x!>e~+`5!#{2(}#=Facf6#m>U;>(4)i zU%&qm zX(__MaQZaSnGu*~8O}32V))4Lo&g|$Sl(4uGW>n{g5mGIyC_MRM^cdC>-3!rjLggo zznR$>7*3pGco`HxbUpzodkb{XS762l2p|@?%l{)h0%8Bh2xAO&1OOvc9Y6rF{0F8Y zU~UBJ`^WGX=0YI-hlvT8X+d(J%m^$ZKuMViYCbW=3MexIz4IR!v;YCb^5^PhmfN2` zGu-|M&x>Fc9KeKd`{Y4}U$1X7+`06bVdKls3?Kdy?-P(&kAdo*{AXYR2q2cNA3nd` z2z2Dz|ESJj{``~S)7I4tKOTQ#IRE`T!za$)48Q)8mEk`E1Q-Ahn`kfDaIBvI0000< KMNUMnLSTZTaGM_h literal 0 HcmV?d00001 diff --git a/data/images/flags/np.png b/data/images/flags/np.png new file mode 100644 index 0000000000000000000000000000000000000000..f561629dfb5444dbeee26e0443f241c0558847f4 GIT binary patch literal 799 zcmV+)1K|9LP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!u}MThRCwBA zShIBV+$GbO%m4rXpMh2wAb^;DynP?e`uxe6c^xwfPORL-!9W855I`)SZ{Pjn`SQBR zgIDkRZ|hpb?Y#VO(Vw5cW;b^if2Ecs00G2orKnq>diw$g@3SWiVsD;Evb=xm@%SIB z!`(ZNZkyN`Kca>u00G4E_xEoGCQeQUMrCCNF?Tlx{@ptn)bAXzy7Kw?))h_PHZX{Z zO@88cAk6J_3uA< z9sh%wRsGbdD@QGyEXWE%fB<4)VrOOee&Hs=z0eQ_4hu_$11B#sT>kQh;ROR5!=EQF z7(|{tXZw5RRPqXTc{QCK>@B8=9|Nj2_%fj;i4M(X>dO#QNX6EnG%00a=r z|9?R8?_Y)|wRH@?fB#|le&q_oo4Ip=Dd88xS707;`SXY2{PvY89n4G&e>R=U?U>#2 zgH%fZ0*En9+wDWZ)m{8|ft=rv@M8ek%Lrw&gMt!>udxX*>{7GbCL}7Hv|-=km!wz% z5I~Gt(nb#%-rf=Z_V4c>km1b0;QPY_jw%LbaBTetTFS@(^fJ?L4t@?se(~84A798O d#S(x30|4h3P>0)+jEP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!4M{{nRCwBA zoU(HNLk0#0{!d@OGyMP0KsE3m2p>Fq%dmRS*^dAL#B%=TBjLaQ{&PQn^ATtX^=$e3 z?;peY8;==2KEB2R5I`)qAHVwZ>fL9Cdr#jm`~yl*&k~?tzP({!`1bk_Kmf7)19CvN z{`vct0mP~Y{yH9u^sSD@>QJ@9+>0t~u`YH_PZa!it>08b4 z@ZK{91{N05QrGt%3=ALG7ytr@g@FagVf_ceKuZ`I|1nq?$T0*v>M(F~urVk}X)|m- zaGGJslV=S7nE#Px$!{QL{?7mqKup-AU%dLjaPz?nF#G1iR}7C|yknrG%mD}>mcPL8 z2Vrnf{r|`CpNWy-(#?kq`CZEyw3HaN^<}hJPUE{QpOC3Iq8FAb=QoZS%f- ze)oc#;l~G5*CH1&OrQv7Bt6If`pm%a_03m+0Al(4?D|`VcX$6V0P_irz(;@p0|0_m V_NuPV5S{=4002ovPDHLkV1kbiAi)3t literal 0 HcmV?d00001 diff --git a/data/images/flags/nu.png b/data/images/flags/nu.png new file mode 100644 index 0000000000000000000000000000000000000000..fd9f9e032bb90f960ede1b4678fd8489d6ff87c9 GIT binary patch literal 871 zcmV-t1DO1YP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!`AI}URCwBA z{J4A1LvCdS{%fDu7*3vl%JAyh8wLgj76v~HAqK^#2N|A9+Ayp+`;~#=-ya4cK2`=p zfnN+dg8vzqqAI(NJhw&c0ZZ6x%EAy8M3;V72~ z!^VA&7~aTzW6%*g!0_R@9K)^4-x!?5KQq{~{A1vjRbe>vN0wpFidzhIrF$9p`TpRr z{kij3h=GCeJA?V@vkcy*+6*=|4h+|K9b;%ne8gZ=`G=vw zjf+9z?{5ZXD;I`C4}LHl-F1P1^WO&scD5rxgTCRkp*{9)Lsr^z6*_#eZj%1I1~x2`gL;$UVFN&Cs5wCz8G(o;4D zHhu;Mwnktua{R|?#lL?H41fMWXn+7>`E2II@a^?mV35CK5OEP@`2XrH1MByn45}Og z437`&WzaM>XOQ!-XZZg783TtdBg4D@{0!e8d}rX|`^_N9x)NyIF9t#YAb?mh&j>O| z$g4Ai`l>T%zI(**j&(P~+Hc|vy6Vyln$s6D{7%JPkeK|3 zVdIOh42cDE85A|88FJ#?83N3oGU)w1#juzC8w1mSCI(^vKmf4>n=WSfbnPs|c}GTu zpXz@YLce@vVCP_CurLv1_^CKMVi?1hnKb%Z(eK8LnT4NKn&~2f*_2 x;X?)%fB<4yx9;=Xl`9$kynaoyQ2PiFU;qL0N@<6+?M?sy002ovPDHLkV1k@1kPrX> literal 0 HcmV?d00001 diff --git a/data/images/flags/nz.png b/data/images/flags/nz.png new file mode 100644 index 0000000000000000000000000000000000000000..5bcbefd913d1137d47611a60d3afef10c2f78a9b GIT binary patch literal 1372 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 literal 0 HcmV?d00001 diff --git a/data/images/flags/pf.png b/data/images/flags/pf.png new file mode 100644 index 0000000000000000000000000000000000000000..c9eaf6d42b32493421e1044cd63cf881c41dff45 GIT binary patch literal 697 zcmV;q0!ICbP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!OG!jQRCwBA z{6A&NLk0#0euht<7#RNlXP_DYnf2fS1H-S`|1tp0 zW?1|2BMU$Pu`oP)_J`rk8;Gs{{^8NZ%D}+H#=yY&n}Ojw(13r;K(SwVExGvT55rk@ z_CEjt!~*mV#BjV801aoj$ME;-1%^k*MHwWmwHY`#fj(!@0pg!nb^HUG_vg|jhJPD2 zFaQJ)3j;Sd5q`f5#OoNoUEIaMP%p>uZLbXj2mcMASP~HHGvKg=fq{pIfq|W!0U&^w z2wU(Sh-U$#>pa8XKXMGBSJyK9yS*F8ehCzx3dC;-ng9?$L|6h!d^Z`sUHZfD_N6Ps zhdHYmzWsD#c+?9@B(H$@B0&=X0*Hl>g4?%+8LnP0V&J*9#)H97PnJPf7rz+*0YuOe4h~U<3+F`{{=WIfpz`Y=!~Nsm7?j1HF`VQJXZUtr zpTWwUa998Y5W(P*mS$iuHez6S_LGa@h_ELEj~Exjdo6Q@`(pYGnyL&8N=k$+0SF+1 zK4Ahn!`ByBB*-%`tXRRoz$?zca0{4vgX0(&0$qUeM5IlC0D|O`uU{D$K7PcV;~5zk z?ARF?3^*7VzI*}Z8epmyxdjxz21=^9ixtMFPZ=04TwnkQAQpxR6Brm?ykPi$5182q zxQGjiLEsMowII7e=7F3I5I`&pmoKy2`23mSIvz)n13+dyfMfs`fB<4y_xbbNl|YBR fW}q4P2oPWZ|CGUPd+H~I00000NkvXXu0mjfnyx2t literal 0 HcmV?d00001 diff --git a/data/images/flags/pg.png b/data/images/flags/pg.png new file mode 100644 index 0000000000000000000000000000000000000000..1aa244ebe7346b9ea0ebaf18cf0d5e169ce8fef9 GIT binary patch literal 847 zcmV-V1F-ywP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!;Ymb6RCwBA zTsdXRLt_R8e!)+l82*JnXt(L&HDA=0mJ*%t3Lt+5X-Lf=Y=}bDP=W`j;5Sq=Aq!Z6iaCTC7(PD4wCMlezYO2cpJ&+p@goaB0I}S@ef!U=SFaf6 z-n+*j|L-3|5>P=P5dQ_@A9yVI1H`iK3=E zV7N0GX!tis*gc!i!0_cP?sx=T$r$LKzd+mm0_lG^!V4gPz?Og_-^$90K~qx` zEco{ATZWt0uQUAq{Tr*MyFiOdfVdpUJ_qs*&=N*qQu^@@k%W-}Kmf78a_7d48^OWF z#Kgp)t)s)R4#>v`KuLQ&5N`qEd?5A(#)&92Z=shO00D#%@tnXQx^d$M1A5ZN1wel7 zfhLZVKs_J>*P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!r%6OXRCwBA zy#L_&!&8@@@jra>j)94Zk%5tsfhyqt|Njh+p1x<;u=C=Ki!LA)$|kwF@6q)H}AhP z{QmQg;m=?4@c;k+fK~x%AOr{?#()3*efa;Mk)MeP6p+6e7#LX@j-I^BFl)(vhLu|` zF)%VSk?E`d41a&TV)*sx)<=Kcw|~B600qWwA$ZwF$400LXW#KOVAE@scb{FxtU z$u9`3l4!X8{BdG}$mRu*vx|u>JQ3F&X}T z`@!&{vyS*Gbp3hp@F4@03Se~b5g@<-^lvg$ TyEO5f00000NkvXXu0mjf$1-?W literal 0 HcmV?d00001 diff --git a/data/images/flags/pk.png b/data/images/flags/pk.png new file mode 100644 index 0000000000000000000000000000000000000000..c77f3fd240d78a173cb63e6b3994130483818905 GIT binary patch literal 895 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 literal 0 HcmV?d00001 diff --git a/data/images/flags/pr.png b/data/images/flags/pr.png new file mode 100644 index 0000000000000000000000000000000000000000..e12c95831d92257ab43689dbf8cbf3a99f46be99 GIT binary patch literal 825 zcmV-91IGM`P)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!%Sl8*RCwBA z+_>bxLvz+={5;>@G5r3=$Ur^tpW*kjrwm{BZ2JfhKrEXs{}evTz{VZ=`6h!J(_4lg z|Ct&7{-=T+fB!N3ym^&j+oR_!00G2u`{A2EN8kQrnDJPeA(G`iLxsRO2G+m77=Hbs z%%=bU{xMwt_LJet_g{Yi0*K|`zyAz>|Ndt9@`ssW=4U;IXTqusvuiCFq$T+oetiAK z@b5o)mi+@-^8D6q2F`s47ytr@g^>|%H3K6D1Hu#jxY zzi;0d{{Q;L01!YR-3(mZYz+2p>I_-YMhuEF0t~A*Tw%EW;5|e4loJf6R%~Zz;yccu z#QcHb&wt`mAJABa7oWc~e0u$X0U&_D&inKIH$!NY0fV`LB+!e@44Zb|gs5cu&v5Xa zC`0H6D~1Dn%NaNs|1kVzU?kd-f8T#G{QvQt0U&@_7?>Csetr7MaQVg?hM&LwGF-j= zhT;6B=L`(2%nS_Q|1z+%{$?oWJ;%V!3QUy@Yz)Nu1*DFV8E6#~69Yg1fqlXZ46aAd zJ~9}2&u5U7;AP+zh$xpe4ii|7;BZNsN=1z|_SJOkDs01h(WK zD-*-^J+~SD{rSgm?aE7_txODYN#+c5yFB30%}GJ(0tg@$Na_W;krl`U7Aq>!EDYso zCJZUjh71hfzA%9D5xGhD|M&0U)CCYgEPwxkG9x3yKOhd}y3A0*beTbE-4cd-Oa4GC zCpVt}@v|@A89u)L$N&&PESGP-XZiBs5koZNeg?-s4;e1LVqiG^f(peW(0xyS{b6|W z{TB;B05OV6gg%t}agd+$??-U*{Letmk{Ap=0t6TUwu~ZwfaVLR00000NkvXXu0mjf DyUu_O literal 0 HcmV?d00001 diff --git a/data/images/flags/ps.png b/data/images/flags/ps.png new file mode 100644 index 0000000000000000000000000000000000000000..fe3a6768646ff73be555778454c9309725cc9966 GIT binary patch literal 569 zcmV-90>=G`P)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz%Sl8*RCwBA zJTYg^!}C9W@W21`iQ)hM{|wXuAhRAkc)+lF_3DoR0mNc@{J8Mjzkj(mJbzAAOaA`- z%W(eud4`W4Ke7M>5X;BgxBpnbdd0Bd-aUr(|Nc?c5~!p900a=rf1tttfdbS1{9*V5 zWUptSJ^%PgK0*D1!0%WAv=PwMwyY@1$r)DrP$jXv!$vCY82(ll zGyEjMKo0I@JYlP?<(Z`TrGSnkQe@a5f8hVQ>A%>6*^zkdH>`1$uI13&<=0JA0o zGbl6q^E1p05M=nv_KSh-7Zvi#e^A^q14D?B0U&@Fzwq*YSuOaJyEIe~oVJ<&Qmcdn zrqtJe7=GUW`4u357+WM*A5N5D;r|58_KZ|4Ad$dFfB*vkcd@rOmavh(00000NkvXX Hu0mjf+*tp8 literal 0 HcmV?d00001 diff --git a/data/images/flags/pt.png b/data/images/flags/pt.png new file mode 100644 index 0000000000000000000000000000000000000000..0911015a0b59d3ef355397b7a233014cf63b906d GIT binary patch literal 1224 zcmV;(1ULJMP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU$T1iAfRCwBA zTzYfSLk0#0{x8430IB~BFaTpYp83Hb|LhmTUuK{f6B7gD-+w@w5iG~}|35_RA5e^u z39O3|sPP|Ap6TyjhCh!UG5lD!?jt||v0Q(DRrufkf84LXzXB>jSn?N${{aowJNb!0 z?(PqUpX@*m)Nr8jV71sSfrv5u`NQz<+e&|t_S>Hr7}JkFheab-ZO@ z{rQK10q7|PpldIczvoZX9%ffK({BMSz*M2g*-S&&&fw>UF%Oo*||C~%fOU}d4hrA5;FS9Z5CUG(R za1sQ%kP)l{9CSd;4D>PMUr2oX1H}g;#7F<276SwjiY4qo-oJP34FB2xGYEb9#qi5N zh~bu1AcLTgD#OorzZh8985!Pb^8rJO5$IYbun%Affe{)X|A9fq3=FzoK!-Ac;t6Om zKmf78l>Gzh5dO-+pd|a3;nUlH48OE_8BRYs#qi)UF9S30D+XRkW`;M<|1hxdvx0p9 zGUzWGGe(jJCGp?vEDS8bi2o1r6fh0|0*DD&)n8Vi#J|4`AAbNfU;fG<$0o}lrK-Uo z_LPs|@8^FEY~24DJ~1#c{NrE-8^H1#m`s_N!4~{wVFJ6F8E6S8`2K^FIU_i(00Ic^ z6K0^m8@68zFM+`z1Prc!GJhFl-#lR8|Mr!ELFfg8lnl^71{Ma+)1Mevzx`(T#|jKG zpqH3{dE_5*{$YY9@BfHA2M|C^h)`u<_yWvKDU57NC>421{w;=iobyo5AqSnAdu_-f-)#5QGm)Xka?gC3giO>5X-HPH(B04{J`+@ z*>9i>#5JIz@*q(1IFK8)^Cbi8jV}zpfWh*g6%q`8z%lrrf$7^Xu;HN02M#`HH2eek z=GR|_hd>WL0Y)T10I|F{_Ui4Y6JP)Q`Sh0o9ee|7-ue7D!%3j)|AC6k-_YFsACmqd mxt;+N{DFoZB-%a#1Q-D3gXIm|-NjJ=0000P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!2}wjjRCwBA zoObp9!+-x7_&@z-VEF%^focF`)Pv6q4D0Uy{|FF3Ea%_+7ykW^k^A{Khy+d@FiA!z zCIJ2d)t`UE!0_S3S{8r+V!88);Sb1yd!HB>{$Vrx4+8@y$9D!lg;7!>#(fW>Zq zQe~KYIhSG1wG@y7+!p-@+Vb}gm<9+S7MzX&s_C&QVJOt?M-kQ&y2LQsK9xb5`zb@? zsd|Wy2mpWpV!|p4bby`AAq)$UL5)!hgQ?_cuv$U@Ab^-~MRbVL8oU}=nEo)tsxHQp zS`YvrfS9nRBxVK%8SWF{B-s z#qi>r1Vg;~LIz>>w_x#C-^CbaT~A|ZK3faPC-?v(#ESp{#Av(m--oB)82Rsg1jgR~ zzZiK1gk||2GpGyR0Ef%9cUlb3K1)F&9A7Z~18M~2_;YJM0t67tm3Is*?;imTczzv{ zOR<2*{~;LStkXo5U0}1`0!xW^4_N>Lh~?ACb#EEYtop<7`85NLz(;@p0|4t>x(AA< Rn63Z-002ovPDHLkV1o3U1@8a= literal 0 HcmV?d00001 diff --git a/data/images/flags/py.png b/data/images/flags/py.png new file mode 100644 index 0000000000000000000000000000000000000000..78e16f1ad800670e7df58c18647a6bd7586f34ed GIT binary patch literal 675 zcmV;U0$lxxP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!HAzH4RCwBA z{5pNgLk0#0{{NppF);l9&p<8kA86L!hYuKjtXusNAb?nYUpz0&@b@qGzZcJ`Ysufg z41X@1XV~)YBMU$PvHZPr`_I4EuNeN`zsKmF004tP><%3Z4*mZ(ORWm^1rjnR zN?YJN;I=WsMpQt*pejn*%<+X$fCs(+Vi8!j24Xl7zIgrg1;gz_hZq<*I2d%Str&#G zL~#2AXrADuOAOo_HZTAL5DOzWH&H?S?dunY4Tml>Y(MgnL4@%YLm-C$gI9zvZl3@x zVdUXqU|?rw002onWq}qj5~UX)fLLC?`v^>J{}}!NlN^EE#h|31!SLn#PX4+ASO^8ft)gV#|%zC2_2^y<<_fB<6o4$L3lzWrwS@(rl@AE6xoi-D1m36e#A zd?S?S8Ge2L!|)534*>#*1!gQG(4|aF#DwraNB|KL9U${S<^lu|%gwv5SU$Xc%JBR9 zYjB}PJpe3Ee!d6d4=exy#PaINg|}bdT>kU-&sPQ-fsX(I1^`!u?}pPdSEv90002ov JPDHLkV1lEZCM^H} literal 0 HcmV?d00001 diff --git a/data/images/flags/qa.png b/data/images/flags/qa.png new file mode 100644 index 0000000000000000000000000000000000000000..1e63f04827ba71bfb0be8de089cfadaf54116833 GIT binary patch literal 509 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 literal 0 HcmV?d00001 diff --git a/data/images/flags/ru.png b/data/images/flags/ru.png new file mode 100644 index 0000000000000000000000000000000000000000..e6f8bb0ebf681a5da773bb5fc86d634b8069eae2 GIT binary patch literal 462 zcmV;<0WtoGP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzU`a$lRCwBA z{Qv*|Lk0#0eg@isj{pJ00wjcin44BMtX;d71t5T!fb<_)S#a^xH^cw`Ai@7sx8&>h-wZ#0|78FOAeMy29SpDEe`k33{1d~! z|5UUHXx6V!j~Ko^yTSkvKrElW{$lv>iXm_Gb6)aCPoH;0Al&e$i(mmjF}n!F;LkjKr{a{FfsrH5DV|0?+icwd}9#& z^M&fc^$Te3_kX_`00M~dv4hKpKQCVL|Gsw*lq9H^x`6oF|9=cS{{Q_55I`)ve}6Lk z`}>38FVIKSwggmGaR4zu0I~e}`xj`*Uxq(G3;t3yuoxJA0Wru-fB<6obNMpM?a!YX zZbNOSrcWLN@e>9H7JvX^+4}kO+l|2T>n#mSVSoSw0Jl7(%VU31o&W#<07*qoM6N<$ Ef*YmGzW@LL literal 0 HcmV?d00001 diff --git a/data/images/flags/rw.png b/data/images/flags/rw.png new file mode 100644 index 0000000000000000000000000000000000000000..05543ea12ccf6b12613bc9710a47a82926d18f89 GIT binary patch literal 613 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 literal 0 HcmV?d00001 diff --git a/data/images/flags/sc.png b/data/images/flags/sc.png new file mode 100644 index 0000000000000000000000000000000000000000..369368ba6058863a7e04d7a9031e767afddd2270 GIT binary patch literal 1120 zcmV-m1fTnfP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU#@<~KNRCwBA zoU(HNLk0#0{!d@OGyMP0fC>IFG1$sJW3ZI}$nft!BLj*O5QmY0fr*WQ;rnA|hA)@C zGcbMm4UqyG_Upj|hWD#ie*_31mh(3s3IF}~pZocnk6;N@3z!+iSU)pl>K$ib;r_|M z@CVHS%s|Yd&ippBe5w zeZ%nY-#;W*GBI%d`^&H^a|eSU&r=45*Ju_1UH|hpGsE49Yz$wP{sP*EumEPsEuf>X z1DysCKrH|M0bKwz{0~qN#D;_KtPII+rx?slfuZ>m8Z-=$;9=lpVEA;0mEle;1H-Sg zUl~{#{=q{J9tc4E7fJ&J5DOzCBW67OVPTLFddHCOdIKCB48Nh`AT|dR!;2j(49_Ps zGW@yy6KDZp01!Ydm_m#U%)qekbw9%(ulR+5;Ts}&{xdT(Je$SE@MHoof`JjlOu%rM z&j11l(~@tj3<1Ws8N$47GBEsrig5x%>>V@1qZynG@0NaLU}5-2&~Q*tv54_8Fbj(@ z00a=KCBIo1)RmqyOp7@HQ~?bpeg=lmr`Z_p*Z*bsaT{d8f2;<>t(D|qU=598kaDsG z#+?X*$gTqn00D$*$u|~;45OP2VhX^R{tk*@CWg0LSQ+lN{AXZ#`xhg4{y{M_3nK%U zjx2+CX$=Frtu+IutQG?kFfly`CSg#F0|XEYvM(G>?=nQY-UR#i|34OnyPYfyAC~-K zVEXk3k^Z3`WaVdP;P!B5;J312;4?R6;4w62U}WRP^cfHU1Q5iMzsw9AoZlEGhaY4R zmHEx^^&Tg~qds6TZTkUq{eQS7MquP~*;+9$n&~r$g+(w38`$BEoA1AVGQ9rujsYNm zpq4N))Vf|~(AN3F@E#cckE$6NzTNo-PO*R4fr(U3l0nGcjzK6RhC#^K9W}^+!Seg> zABOLLelomx|C-^(iw6u(-oIejcl{K@$+Kq}US7P*01!Yde}6JDC`&(MDD*kcaBBh^ z!|SQP7+C)QV&GDeWDqGTXW(^qX5doMfE)Vh$2W$XPwy}szH@=$;^UhPci%i^`0({J z!?RCsfiC~S@Z%>iGXMSphAS|DKLP{x?>`2B0Adt!EcjBRyP8|*sNpPV)V4oc*yeU1^=BdTnsP000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!6G=otRCwBA z+;eQ+L+kV3`2Td0z3YJ?19n%0mQ<{2r-K1 z>pzC8YU~Wzwc-rT^S(1U++}BY|Mwd>Y{>y2QyGE2Vfg)<0U&@_kR`aj|6{l(#l%ou zuguV*P|V;a>kD+sZ-)O2q+9e47(#-VE-`R#*uVe~KrHBzyuXL;=k8yI4~^d#7JXU4F!SGhvLYQ}(O+P8`ON?jKyXVKIe`{D{loBi z>34=-um3RoV*5>%q6Gv10*K`=(6)bU{}{d<{K@cP$`^*e@BT9|aWGPt6=BKvKLaQm z0t67t?SHpfUhRC%@P5i?hJW9HVb8({$*NQ-B>#ah3qSxd3it{={Ic&0KiC3B23mlR b009O7>;2js$*bjV00000NkvXXu0mjf!0Ray literal 0 HcmV?d00001 diff --git a/data/images/flags/se.png b/data/images/flags/se.png new file mode 100644 index 0000000000000000000000000000000000000000..0b7156f7019bb5cc371d8046bf09cf94b4851ab2 GIT binary patch literal 848 zcmV-W1F!svP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!;z>k7RCwBA zT(tSpLk0#0{x9EtG5r6}00;m6Gcs_n{bq=BI>W%m{+r>~PgaIS`%D;q{9_2~)7ytr@#a#Un!^h8`8Pp};0L4+`gqQON11l(K z{xO1S7yVldpMh~hOq_tsdiU}b!vn^53;+Sd`2U^X2Zq0|`5FE`z#a_1R0an`q$)(5 zFfg3I@|$7xrk@`H0*Hx$4ges4SQyxU0bTnOX!Q?FpMZP;OtN4aAb?ob>@j2b`u!ur$4{y#aq=;GEvE#);wG zCvITsA|eR`)8pUIj~ITvW?=vbAQm2=E58GSL4fN!S`ubr;OF|mzz7W<5Dnu01Bw%6 z3DB%xe||E2Vf(`X5I`({fyn@rJOBOvhZ0<%jPd_JDD(XXrga7|4dOF^)e)H)K@kI_ z0Ro8S=7YB^A6`FU`2FPtO4$X96Q<8U8Mf~J&cMn2li~YUCWcSXt}uKDCIn*Q1Z4Q1 z?{9(l9ScAJF^U`B8HV4$;9_JXDFuH7 a2rvK%RDcHc859Em0000b@?P)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz&q+ie;NLqKhLn`<3|>N0Al%j`}Uu|uU;{* zT3Io0XJ#_I(bEI_hRxEF;q#0c3{2$O0<`2R&^MPD82$hR5ED3PfCAiUX$;>NEn;90 z6lD1O;RD0><;xkkBO@6YIXHmI{*a9Sfr1NY7tnMDfB<4)U}S`N@Aq%85B}Y~3-;>& zFJBn`T)xckpTf8S0e}F4hsMwC+ZotBJsDVxjTzV+92i*i^%?#{EvFO!1P}|1@uj1K zfq|8kf!)Q0;cHJ1!#`j`00Bk@$^k$Cu`sf;gTtK(NPlf?WMC2&X88Z>7Xu^ELWp0< zi+Ygb7@_eE5I`*abLWENfB|SQFmXfj2*_YY%KY#TXpX@3>kM2wb}#@05DSx>90M%? z$QMlCzA-Qg2{8Z!5EBC(06+lI)DlK$kOKq|3rbl7iW@2cP@I6w1DOjDKp;z4Zrr%R za2*&)APH((@&H&sK79C)1t5S})~#FjcIC>Ie_p?SO{-A*2oPWZ?VrP000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU#s7XXYRCwBA z{IhTWLnZ||{u3`)8O~gM#qjyvX9gA?UWR;w9}Mg#4l>-b2w+%qr{`?Duj~~yo00a<=fjGmT=f4CPuD^QCp#NY4LxPDhgS(GB1N(-P3=BK= zGH826GIXoFWZ)I&V)&r^Vp$D-y{02MQb{g+NJ$KMd?_zZic0 zdJAM<`v?#~ET0|y8GgKa%fMvznL${Bmx1BQL!dfV28LVr85j;9W?)E9V_=Y%Vqmy= zhv5f1AH&06Tnx;=e=^*A%+A1Y2-yNg24<%J44ENE8O(I2*8 zh$ZB_GJ~AD4MV&iKZDKZ^9;9v!MuRenxWk1E(71m!wg4lq8ZMx$}uFGK4joKb%^2M zIcA0_k0cqcJ^Btb_!Zc2xFyVg7)s)fG04llXPCG`guz7P4a2?rTnqpK#5}*FydqQV z0SEi$^$cyR-!o(%6JXeN{SQOE!3PGG13c@HIQbJ3c3d+Yf&;ym|KRCqMu(+S<aQ3 zeBb~E$}I+f0AgaGI{3@*{}(62kM~+Y3)sLx4-h~se}Negg#Z0R&s3BEkXgT>@%Ntr zAb?mdUw+B*`SUX%=QR~Afh8Vb!OHNF1t5Tc7F-18{VPB#zcJ7Vd;|zE0EaDOE17i$ Q5&!@I07*qoM6N<$f<`9h&Hw-a literal 0 HcmV?d00001 diff --git a/data/images/flags/si.png b/data/images/flags/si.png new file mode 100644 index 0000000000000000000000000000000000000000..5c8e9a99277084507ca904b43f658f0bd4927f27 GIT binary patch literal 704 zcmV;x0zdtUP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!Qb|NXRCwBA z{Qv*|Lk0#0eg@isj{pJ00wjcim>VYY^XG4dzyJO*Ff%bRu(PvLY{S~MYgqsS2xQ40 z5a;flM-26SI~lIse$Mdq+b;$NMh1qi>R<+cPeZaTxOnj*!`ZWE{{RFK6HMUF?FS4? zR~=_~_WCP>j+z+5t&A21CNKr9UZ{y;?k|7XzGkzgpz_GD0! z6J-$N<7VJw`Ue#M4a8*G!T>h=|33zR0Aj3eT>Rnvhp+s%Z$D*7k9A`353m5b;2*=^ zZ{HZ|(>oY$u!}ITF_LZzFhMbVeE*E$>7y$j0Ro8S_|V z820C&0yhT}11kp`!_M6| z8RUSH?*8r!FOFVdc=ql$10Odl0~ZGiS(t;JnSq6ci2)#hSpG6HG5i5yMkYpv+icrTKKTp8oIsy|lO2@+$Rxgh{~0)d7$AUH{sIY5G4dPe za8MXh(~@7n`25TGj{zWnSbkl(!gBl5Cx*NK|1?c42pZ;TD0SF+L?H@nB m-TLSMpLZ0-0lrcgAiw~QN%+7znBCR@0000P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU#21!IgRCwBA z{5pNgLk0#0{{NppF);l9&j1Gh7#LW2I2oRKL@=yB{ggppRh+>|@H@lTwQGQCfbxtC z1i^oxS$`isVED0a^+$jJV)=dXyfDMxzuf;`JV#jamw|yvh?n8LsW-#1i_aK%xj7h| z{(WZnb@(9I0Srt;Sn~HT!=DT18MeIp$N~^REPwCZ{`2qkD~7-K?=k%U_Yb1t56~x) zLJaRCKQSD>@Pt8GP8cZgmf_cdeLywDSpsx8!_|NP87}>2_yZ6?EDS({LAL(?12h@Q z5{BQu!CwCJ_a9LBKg5?{HN;s0Qug;hga!y87KY!z0R9PN{ea3qgZ$qQpa3Hz@EJk$ z-#-vFWB`BwVquSsX88B{3&X#6Z{cym2sH1Hf*iwtZaxM^pzr?t_{s2BQI&zSx&jhh zL`3{QpjqrMUox;G_pf5=eA z#KOR&5Q?G(f9m?j@QUds!>wlw3;+Sd=$F6x!>e~+`5!#{2(}#=Facf6#m>U;>(4)i zU%&qm zX(__MaQZaSnGu*~8O}32V))4Lo&g|$Sl(4uGW>n{g5mGIyC_MRM^cdC>-3!rjLggo zznR$>7*3pGco`HxbUpzodkb{XS762l2p|@?%l{)h0%8Bh2xAO&1OOvc9Y6rF{0F8Y zU~UBJ`^WGX=0YI-hlvT8X+d(J%m^$ZKuMViYCbW=3MexIz4IR!v;YCb^5^PhmfN2` zGu-|M&x>Fc9KeKd`{Y4}U$1X7+`06bVdKls3?Kdy?-P(&kAdo*{AXYR2q2cNA3nd` z2z2Dz|ESJj{``~S)7I4tKOTQ#IRE`T!za$)48Q)8mEk`E1Q-Ahn`kfDaIBvI0000< KMNUMnLSTZTaGM_h literal 0 HcmV?d00001 diff --git a/data/images/flags/sk.png b/data/images/flags/sk.png new file mode 100644 index 0000000000000000000000000000000000000000..ee88a07364ffefa7ad152a26c18d5ffd3b7e25f5 GIT binary patch literal 929 zcmV;S177@zP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU#Gf6~2RCwBA z{Qv*|Lk0#0eg@isj{pJ00wjcim>bOf|DWOCw{H;U-@gq1fBaxzWMySwWMc!1A+w1E zYuB!20SF+FC4XQ7->zL_xZ~!=@bBkO1~zGF27$0JhObACFnm3In&BT%UMwS>L8hgZ zXbUc0yvT6&?Abp60mOtX_~ZU9h9A$KG5mV_mf`39`wZf_xj-NNVfYE;{|4bFkBPS6 z-@kthfByUd(*Oa)^7Y#(?KKkDq@)f{qAFKxY30>H!EKmW0AZ46omQX1MwM3xm_EQw*g*-7FH~41eFf zXZZK)7sKE8?-`hQxEXj@m>IV2yui@o+Rni8{~yqj{{*c0&+zN(3x;p+t}_4x5X&c^ z%RYSi!SL?=SBA&mnHYWpwF`s>G4Na3GcXGYG6;BhFmRa|Gd!I=li}0P{|v7`d}m<) z_lKZQK$iUb`Geulk3S3m0mQ=0%*en3#GK6k8SZh*GJI!dV|X`xCc~H28yFPk&R}>u zbuz=dii`gaK&oU!YY0 z0mQi7)%U}Xm#_Fg-+REo1k8Z1Sos;cB|RBlunI8n|NYMJgNYUBfIkdHFLp4fe}4{) zGbRRNJOZ-fAu#Sv{r&k7Ab?oZe|%v0{rw%ouWzqG>5hT@`x{`8Y-OmD4FOuf2E;53 zNiPpFM8CVj@C&M$xS(QS-~w3$!~g-r@(bwt-$4B1-#@U#cObU@e4nB1@oI+2;!X^1 zAFnb*zCFkA3G8{|0m!T$(BcFjfEds7@_xDd`7`%@G*_|$@dsvB2EM<)!Q%f&EEPfO zp8@d=28OQy0mQQV^XIp_ffl~UqL2}40i~rdK!5=Nif&Rplvh@I00000NkvXXu0mjf DxS_v6 literal 0 HcmV?d00001 diff --git a/data/images/flags/sl.png b/data/images/flags/sl.png new file mode 100644 index 0000000000000000000000000000000000000000..608e0fe0dc41ee33b50ca8c992b6e4cd6c904736 GIT binary patch literal 512 zcmV+b0{{JqP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzl1W5CRCwBA zTy%HQLk0#0{x843F#HEXYJva%|1&)K_Jm>E(`_FC0*K}6hpWQ>{{Q2C_2U&)E&2QZ zFT=GD*BD+Le!&6|Kr9cwJoxkW=UawHUmr05Eu)$xAhUix_{s3=?yo-p0mSnA|8It0 z|9&z2{P&ZpmVg4~&)+``|Ni}B00r?=s{-x144iRVqs=t1c%>$Mk@P+fsp}ZHWMQw13&<=eE9)P0YG2;{r8{h!Sxwv z_Rrsc82|!^rF_|ChPR)8GCY0v9c&pDvpmDs7k3!mUw^^?5I|s`fNcEm?Kjme`T6rN z!;e4z7ytr@1r!w^%*6PgfsvZ|1Q^jwz?fkIvH${z<<|4hEbpH`VEFm=F*v%YSwQ~& z_>$rGr&lZh0mSm+%AvQPZXWvc=i5gH8i9`h0R{l&*x1^+m&fV=0000C#5QQ<|d}62BjvZR2H60wP9dj6!&y-45_%4^ymNo|ITb|Y;|n6 z8v7X-k1(mUG|reEX0|4F_cT_8h8YcCA0vl?T2$nR_xJ7F?CbwE zWHvA)B`siP;855V!6e{NvXMohA!6U(A7?-Ox7XnLA8hNlulE1h53j$+vjJ6}%)4{& z!?VBTZR+Rk4gdW1ckOOStNs6_P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!FiAu~RCwBA zTy%HQLk0#0{x843F#HEX82ArjGBPmuN-{9W@c`NX7%%}75dUFd_`duz!{4WW85mg@ zko5m&`1|ky!;f{VKLP|0%heB8h5!Bk$NlQZD-=uq0hx?IOYDSz94;J|fWTh{hTrGk zGW@yzn}LxH$&$Z+8U9>2&#>j)M;3qpVtMf8!JoH3-!eS<`Uq(7KMbFIkOOgWSi%Uk z^nStTtI|hcAK=zNn#90CmK!hy;+5&R;+Q-0@wx5CF(nkh{f{P3c4VM`h-u{8Q zf&c&rAcB^F5&$<)Rf-w|gBLK*JoyF@Yt&|7kmh0_DuMt4i0F|0^AFMxWA2q2c5A8)dJ zc=&P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!4@pEpRCwBA zTy*fkLk0#0{x3iNF#P||KsE3m2%o(D&amzL^N#=l#B%lVC*gnp|8u|k_!BHaE%5i> ze}-$1KQX+xa*zcefLI>9{`%+b=U)tu-h5;DM}$}Y12r=;F%nbV2!37ov(U0DLVQAlYmEqM#{1$)!KmZX8E@ohGy!!NmVd~C1 z4DY}EhKnya@POgwv(I38LI5Cun21gHnlgM0+#Jjd%a1$;TKbbgSB{^7iII^gy#N8k zLaZfXyzC5ROD-`iIrNahP*IS`}OvrKYzb}WS|lF2oPWZ XsVCmHrCFuX00000NkvXXu0mjfnQ#v% literal 0 HcmV?d00001 diff --git a/data/images/flags/sr.png b/data/images/flags/sr.png new file mode 100644 index 0000000000000000000000000000000000000000..8643fae872316f515ebfb083062e86c466da2210 GIT binary patch literal 600 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 Hu0mjf3@so3 literal 0 HcmV?d00001 diff --git a/data/images/flags/sy.png b/data/images/flags/sy.png new file mode 100644 index 0000000000000000000000000000000000000000..8816583f1094ff31d22eb8a795a5c033622cba82 GIT binary patch literal 701 zcmV;u0z&P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!Pf0{URCwBA z{5oaILk0#0{{NpoF);l9&p<8kA86L!2M-v2tX};QAb?nYpFc0m@b@qGzvs`XYsufg z41dm_XV~)bBMU$PvHZP#`_I2uuNeN`yT|bV-#@BZ0(3aTRiL9UF);iA2p|>)pur$p z|Nr^Jz(7SyKqmc#(f|R(!pOtJ!1(PO10z2_h9yjlObj6S=l>s^hA}fTGyDg_fB#YS zGJ-JB@eIFzGXMk-i@=&S5X1kWD`I5${^vWxi*L^v6#12b(S_+tMxX;8e}2dy#4g0Z z&Bg<^4DJ)4d4iWNF>r6#zyJ_HER5XTSY7z#{||;E&rUI1{B(gKO(|rw%qIb}-}5zdsCr|Nn)gCMIT-l*Pu(2F~3-{{Fy{RsI4K*YA%2 z0mSnC+joX+vIZbt(E0mQ(|%L{Vue;VR1009IF jdT=RBJ8v+21PCwyiIxkl#Fz|W00000NkvXXu0mjfQiDP7 literal 0 HcmV?d00001 diff --git a/data/images/flags/sz.png b/data/images/flags/sz.png new file mode 100644 index 0000000000000000000000000000000000000000..fc4bb02320ed30c5d4e5bb05ad9af3b6798aba68 GIT binary patch literal 941 zcmV;e15*5nP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU#KS@MERCwBA zT(tS>Lk0#0{x9EtGyMP0KsE3m2%o(C!m#bg{f__v#B%!j7vX>Z{&T;0`yFTr^=tu} z$#CiRdxn?y&anUl5Q~$+mOtNrd}R3e z_xCgW`}~sO-@Cg&1O8$0HqbmE=J*F;e*w`y7#LWX85o#2@ma#aa0%${udf&w00M~R z`VvNlpD!2~zN6W}h=f7H=NY9Kt^!T=NC;vukyB)Nwqyyz_nT)JSfJ|v0x=WRQ835d z0pjOC3=lv}AVsWbm<@=Tfx+>YgN=cOot5E`m^8x_6Dx+D3cL(AquahnL|Trwqe?7Dfj2BmodW7(U_r_mAPqUr~lNZ+zq3@pd*GxW>JGBENfGFbk93^e5@ikScb#PS~+ zNiYnO)_?VaA?D|I2FbUt86^MxV0d}>6vIYQAqIwT-x=hjWf-Eq_|5Qn{$hq7 z$4)Wu0QES&`^dol;T|vr{00Ztf22eJ5I`&}T)@=B4U8mSNHPLRX#Ib|p!ewo!w+r< z-|^v9hR@$b7)}8r*x19IL7MqH!_y@*8U8F;%D~DCG06SjJ7DU3$M6%#XNIOo7GRQN z`oq8g5I~IopV)q2`169F;rCsr6t0BN0M-r5Hw@o_m>tH!mg5;1E?j3|ShbDeBR~ML ztUhf6422I2A3o|5ELNEQLvs8-kUAzNoECsnDKIxaee#jv!h@#_00G1(>$UpJ$2SkT z|9yK()v^oZ=YKz5GyMI0|0_TMvHX0o|Lx!Br~mx@^_hVd0O;S3009O7=nz15rDu1= P00000NkvXXu0mjfRCTjJ literal 0 HcmV?d00001 diff --git a/data/images/flags/tc.png b/data/images/flags/tc.png new file mode 100644 index 0000000000000000000000000000000000000000..162ed8ab1a4693a534f683155015624f84e8616d GIT binary patch literal 1188 zcmV;V1Y7%wP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU$Hc3Q5RCwBA zeD~n-!_#-Z@Si{Tkm2v|Zwz`m3JgB~&oD5svNNpvq0MmP##07nAX{7HAA_v|0|URL z2*ZlAzZmX4e8<4Rz{G$H{Qu9u@ZiB4hSjUjd;|y}mQRQF2)n9Ea!Xo>GOXD9gyHUk z_Y4f*fR=FZFx>tv!SMU>BZfeo{|p++@(fp56d1ZUykaU02XAGC_eSrIfiRnMs9+2x9{(~$8G5#_9`}Y<|zxe|Y zK#c!0Y(6l&c*)Q3W*8qcyi-935m4V^cHwK2MPZ(aaOEA0!`jQi9(1#bmpnCTk>=Rb@zYMn8Um3XH z2r;nm{bpd&y~ptS4JSiOb3DW6&o3AR1a>h11Q5$cQBQ^-T^0t8YkL{i-(Y5Vr{Kkq zEOnfLflGj4I!7RbqC6Xehw3?o@29UY%(%`!^)wfWge{HN#V2LYQ`!g+b^e4?|`2IR>H23=A7K2rvKy5EI1P$mtwp z$X{?0gs@=%cD>K9IUyKZ2e*#nMejF-#AFg@PmXJGijKpnt{?%0n20R{l00EMbAGBZ#B0000P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUztw}^dRCwBA zoW1hMLk0#0{?A{2GW`Glp8*;C_|3xLsQ;M3QvVSH!!H&F48Y95@aN5chNr9kGW`FG zCI>X@$Abq9uU4=A2oOLlmu@^2{`>Dg_siFx82&S$TJq&P8-t3_6$VSK%Rn31Ff9Sv z`}e~?hL4B;F#P?^z`%%X$KSsUU(TOr*!S@x3qSy|+emwye9seK( zqUZP*K>QM@3Lt=3-aoza_Sc7-fBygehH1!OCWhn3KQcUh0Al~e8Xo_E+CKedKr32N dz(;@p0|0)L$0H+azOw)T002ovPDHLkV1o7W>^A@a literal 0 HcmV?d00001 diff --git a/data/images/flags/tf.png b/data/images/flags/tf.png new file mode 100644 index 0000000000000000000000000000000000000000..c1a539549a4025143c149dbaf30f945ee069178a GIT binary patch literal 508 zcmVP000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzj!8s8RCwBA zTzcg3Lk0#0{x3iOGBEuA&wvbm{Qk#atuDl1tSG>M3;cidkm2v<4G<0^vIG!4dCc&4 z+vbk|0mO3s=@;RD|NnEp{`3>w4u-Ek|1cb@c+YmhJVM7K%CCVgpm06 zm*LXS-wfw|{r&?GKrH|MgDnN({}`71!6Jrf3&?;!P@fP000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!e@R3^RCwBA z{5oaILk0#0{{NpoF);l9&wvhg+i){HlxAgM{lh>K_zyJe$+ssA+n#Rw2oOLlzt5i+ zX88M;```2DD3&mS6aw*8X+egI92^X6zy6bC$>0Bf8Lob~#_;^m3l@L?V)=Xf_Md;R zUNQW=caMRAnT3H#R)XQry+;hpP)i=`K+M3-!N6)R&%kPA$M9wHe1`vje=_|4^_QVb_6-BG4l@JeHxexY8hMHF55u|( ztPB7F#KOP`3>YRR26lfh2A;}%21agSpap*zek@qb@c->M1_9=O42-NmU96;A!o~8B zft~R`13&<=KmrixwT+t?xbq7bn8X*b~Uo!k%xdJT9$iWUzU7!9kGQ9c2%)t4FbW45%gZ(=&Bme@4Nk~Sb^aR!lZ)#AX9<*00M}Gr+}N`|2H0n{~!5*qW@6x2?$i` zd}l}(`NqKfm-INf4NRQ}1b#CB1P}|m(|4c<5dZxOlEP4|{}U9JNVbw-2`}G&hPxbp z82|!^h2c9;H!$28elwD85?)(?ru_p3F)+9R0*IyM2{*&XU%U*jzX>3R7R3NmcD;M_ zhvDJ-?+gF|#IoS&eU=XoKQR1#`WIVhQD6x$fL{M)`16>71t5S}J|BDe_Q%n$fBt^@ f%RnRW5g@<-Yr_8Dz4^6700000NkvXXu0mjf8#Ptz literal 0 HcmV?d00001 diff --git a/data/images/flags/th.png b/data/images/flags/th.png new file mode 100644 index 0000000000000000000000000000000000000000..bbe1475833f3f6b458627d73503c4a6f9596813e GIT binary patch literal 492 zcmVP000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzen~_@RCwBA z{5oaILk0#0{{NpoF);l9&p<8kA86L!2M-v2tX};QAb?nYoj)(k@b@qG-{;S%Y6;Lx zhTrGUGi?6&kp&=tSlDfB{`~*?mEqs}_tdrI-#><1s;Ue(uU`EF5I`)P2?-3e0RMoN zumOF-vU)WGKmf6P`}G&>fq&HW1=JFd*}wk$V*m&smcm6>7~X#V#qjjqcc4X7wB`Rl zhOaMfGrYV0lmQ@sSU&&w!|(}+-+%iEZ>3^=sFB1PCA&E;eSGyO4FA7;Vfg#* z9XM{NW(m-&&o5pu{JeIJ0U&@FpYig3x&8Sw_ic~_brToEV<5i7!0;6yfLOMF{`__$ iFtxp9pc(iG5MTiH7{P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz;Ymb6RCwBA z{5pNgLk0#0{{NppF);l9&p<8kA86L!hYuKjtXusNAb?nYUpz0&@b@qGzZcJ`Ysufg z41X@1XV~)YBMU$PvHZP#`_I2uuNeN`zsKW7$3A>(uK(l!7-Dlu9c8mcafLK^U>>7%FV%&oeN; ze#-z5Kup9L{uAhf<3L&%s9Ol?ftNtyD^a=u0*FXUelsvI1A~Zx8)(^WAk790E;*tt z0SF)#;wb>!7#@Co2uybWi10YbA&hvFFT@sLOy2*^@ZP000jN0ssI2o&@u<0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!v`IukRCwBqQ%y)zQ4~J+-TRzsoJ1FC z&Dcm;l3ANbWFSQygD4?!C0Ha@n>G*KNF!(w8Er~}TvQl@v=J90Eo^i~O))2b z#+hmSoBw(9e(rnS=SWOCl85uShx6|F_|A9EMQ?f?s;Whz!l9x(L6Ad{B*!7x*)$Ew zGMAqxSPG-FivUb+spg%hAb6A0WUOb6ggnREqFCwcCC* zRgb@Ze`aXJy2qff{EsRBa?nMVd~1|y&e4V8Z?lXM&y!OXW{<}oi;I~AB^gT+w%a_N z*Boc7{6F0VPhVC5%y68>{>svdEG`q1X|KJu+~xKvRApI=QA7R7_4PzJB5)i|Hx*0d zl$b*uH-=jq-N3U_7@ICi2)xNCWwIvWaPmdZpqP@=EEGk3{NxKEnCEpMQNom>mP_Lg zZVj2OF-QOi07giO1Z`85Xd1{e)iP5Ar|-oRIhLZxquYZ`7p8#%#Qy3Jc_vXmwqUUj zr5VYT1x$1Xq^f#f7i(s^?tL{M-k_6NsK9aSs>vub%4xmQwmB_Pl-t|R)l`?UK%tzF zB6r$`-*$gAAB;hK8v_MOt)ZjEYm$O9)0kf(Xqvesko&u+1vHuYKse!plh^~DbU<7wLdpSRkRh0buNg;x+?GI zo_BRMb6NV@Vj`54uU&6;vibaj#Re}cTt~a3?|ENIaadP+Hzq>oO-JSFrC8jMo;yBr ztnom8pu4m0N~@bNGHhY-?i>N1tJ*8)98SxkpQv<&6zuzcxxRnF8=ZNT%T`>|7hQM# zuJ(U&*r1-b4FrSM!0!-hYbkY2tR|9_J$L5pNpA*74|db*MwJ#z82$(_03|?1k)H0{ QCIA2c07*qoM6N<$f+1^TIsgCw literal 0 HcmV?d00001 diff --git a/data/images/flags/tl.png b/data/images/flags/tl.png new file mode 100644 index 0000000000000000000000000000000000000000..1babb2b836672960966576decd806d88af291d37 GIT binary patch literal 756 zcmVP000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!hDk(0RCwBA z+_PlrLmO@ee)c;b85n;3XP_GR4>aq+0|tiGt3Lt+5XoZk!#2Z1i}_{YHT2T1?< zLo)sc3@#vx0cZjPKmai@GBTq0a_*G>4DPP~8BTElr8fhEZ5L49kBNbSnUU;}0|+1{ z5CO8t#Kf3^iHV7Ug@u*j#BnwT7b^yaS+jtWu0YE+0`Xp;C4tNg3{0e31`t3@kc9A` zAv83MVfpgq3=IwS47|MT44=O+Fr=sdXGlwBWO(rsXs|udqIE#L6Ug=kTE z9JpPN%O?Ncn2BthP000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU#{7FPXRCwBA zTz_QcL(!MK{J(yDVffF;$ngKqe};R?9~gd%{bXSN%gn$46#M)C4+D?^h71h{Rj|1EO&0+6Bhcz#ceBgh2bwy)9$Nc49A$> zF|e@wWRPZ(Wcb4Hi{Z_W7YwY7%nZMQ@-Ryn8CZbiAE=S6a5e+fA^(8Nu70@4@Z#_* z7JvX^dG_nxpF8JS7`UZB0NwMC;nJCR45t|HFt7>zVYv5~pW)%J=L{T-Yz(#n<_sr4 zoo4v)?>E#(3}Bx?e1ysV_x~@$j|aaPe%}4{2Ot0sRFVQ95W`ZZpr?6>j-D9mH%hz0vSP|3W?7+H&=J0C-Y^Sh|1v@f zj3Uk<#T+L*0*K|+Hx35L-+vgw4IeYS{lv_$@VGd`kKfD;-~N1H;QY(ZAjvAs;3MwD z@Z$ehhWEeU11(?z8~z{cat3HzF+qcn5$p?)MGVl`0?_~g#KI-|hXEJ@3_C6gGu(T_ z26QPS11IZ0hEG7npZ_Gd!vppq4>=@CO=Xj8NY( zfz<#65DN$UKL#F-{|smE^D+GU$;hB6@P&a57+j##!2(QK@BV&Zc=h=$11l5I`;7mg zK45~z3M2vi0|w83U;<%gU;`Tc8}1_*00bF&j8OnShW5Xvi-}#6sN*aWOMAy8l8gfCcGKusA>fv51TRVvv0PgJH^E8HOJ} zm>84{}f z86r*JGW`4dlVR>rQHHNSnSl}f7p&+%JclzN1;am-oc|A;beZ6m{)ak_5t=vv0*FOg z^cMpgFbOX^#>sH+B|n2O=Whm4-aibl|FZ+7e*m?zLf!ozZUHM$iU6V#GoShl|fVL6T_WXybPbdF){r83$)-b6F4&eL(45hI5Wbd zodIqsBh+G$@0lQ;{0Az-{xSdr5X*&^_gRGR{$pT~dIbzFV6pPzH^aOCUldKZ*u?&$BmgA2j{pG%0HAE7MPGv@ApigX07*qoM6N<$f(vd3+W-In literal 0 HcmV?d00001 diff --git a/data/images/flags/tn.png b/data/images/flags/tn.png new file mode 100644 index 0000000000000000000000000000000000000000..9d7f7e32fbc2c409085651073e11371a0b335233 GIT binary patch literal 698 zcmV;r0!96aP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!Oi4sRRCwBA ze7|zaLpBBm{(oOTF);l9&p8GxhTjr^0AeEO3u99T2B#nfhU2>!7-rTpFlZV9BlH3T z!_~7442l{I4Dn?Uhu}#w00G2;*OH$=Y~{(oz{$mdB@h^V#S9EA%)t10 z3~>yes09ch{5}D>@YsF^hTW?e7~I1lJ_1F!BQW^Hq=4+RkjN*HoB;v|za=1z|9}DB zn$E!R?F$2gxeEh>umps?cRd3`S1tp?6JSc?!XGC90mMQu=YoRl;|B(Y`eb0?!vjph zDhv$oUIK#&SV91^7Xv453t*lD2p|@Qzo3lxm*GD!BmTwZ8fK_LpMXBNbOFL=f;#a( z?#u`>4P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz#Ysd#RCwBA z{Qv*|Lk0#0eg+Wu`EgwI!00a=o zl0OXp{xSUj{F&kZ$BzuZ_U&U}mXl-n2V^sGaxyTmu`&F)ex2bbP@EJ?K%sIK=$lJG z^8o^g3Cw@~oZ+>S62og16`;@`hVS#{F}xHJVfe9hDMSO%5g>6=@IO#+fwTfKKmdV! z!oUc0EsLfmIJkbFKFz=^CB?w3uFk+DA;CZm01!Z6OF$7Vbl?Cu$X*HwF|d1kGw{ur z!9Xnl5I{@_>sS~V1qB(HWn@rN7TBlk>=ao75I`*7=g(*O|K$tAzxVIKk;UfZ#PIL+ zYlbh4jc`T3PMjbu($N4w0I_@pTJrD33x+>;?}8&3X4rpZpFlCW03d)^n4ktSp-C_@ zP!9kE5EBC(06+lI)Dl=|00a;VDEosV{68>0{!-BbP!#-y(f|R(0xS?&ZUghjZD{VK z766&`7>J($GY3Ebv26YP`RztvLGYG#q4p6VzyO9lxxNPDq1yle002ovPDHLkV1nZ~ B?-~FA literal 0 HcmV?d00001 diff --git a/data/images/flags/tp.png b/data/images/flags/tp.png new file mode 100644 index 0000000000000000000000000000000000000000..1babb2b836672960966576decd806d88af291d37 GIT binary patch literal 756 zcmVP000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!hDk(0RCwBA z+_PlrLmO@ee)c;b85n;3XP_GR4>aq+0|tiGt3Lt+5XoZk!#2Z1i}_{YHT2T1?< zLo)sc3@#vx0cZjPKmai@GBTq0a_*G>4DPP~8BTElr8fhEZ5L49kBNbSnUU;}0|+1{ z5CO8t#Kf3^iHV7Ug@u*j#BnwT7b^yaS+jtWu0YE+0`Xp;C4tNg3{0e31`t3@kc9A` zAv83MVfpgq3=IwS47|MT44=O+Fr=sdXGlwBWO(rsXs|udqIE#L6Ug=kTE z9JpPN%O?Ncn2BthP000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!<4Ht8RCwBA ze7}0hL*}19`Tu_X!oa}rpMh!sXw;vlj~Tx1-24$BfLMOsx-QK8`7`&Qx37Q_|1lIo z2u3g>3H<%X@bmg*hHcMYu>b@R%df`||1dqg&+z-{BL;?l2uuF|{K@eD-ya4>R(7BU zAkECez`(*nyhr~1Ww`$RH^b%czyAOP5DUY7Ce7|*_;p4(t3=A)xGcYhS5oO6gkY|9Q0CF}!05LHzB83Ccg8x8Q^Ce|5NUz?( zz+q^@@crB=hF{k%Gq5VFGB8R=0(JidJBkzl5I`vY|Ns3v1DC58gJ^vxF!-J@JPq?? z`1kBF10x$dFxXfb7&y6rK4D>C&rHw~fB-`A`~N?`892#`N1rR_?$Uy~i84D;# zK;a2Y42;MT4T>uu=JE+*;P($@;0udo`2Y1QQNaZeKnPQSar5ujPljJ_UPHJ*SN;dO zoPir5&jhsO%j(4pY??X@KQEkNVBi;EVEFQxpd|nSgc4j#z;OSuXFJ2cAKw}HV^e`C zOo!p$#}B}``UXtWKN$XleD?b{!-wuBhR^e+G5mY+jHqM{5J1d@CVCZ&AKr8P{rnM} z7k=Nl$?)s#8wNfPUr3NJGBGfVNiZ-esxtfqTEzJN9m9X1>p?-w$jFE7 z&p$r_0th3~GXW#{J229phIuf2JFuI9%fg0%)7pXIKgbcl6ve>AL{g>%0e}Ev0cXU2 zAp8%S;eoDW=VbVC<2u8Of>fY^z@+=<4>(R3+1VlT|B1Bm1C4l~?C&2$yB=i06QG$-fB$6x2q2anPhPy;@#f2)cYmo^0;7VD b009O71#Kiwpck|400000NkvXXu0mjf^HPW! literal 0 HcmV?d00001 diff --git a/data/images/flags/tt.png b/data/images/flags/tt.png new file mode 100644 index 0000000000000000000000000000000000000000..4d18c037118e3f0eb38e73abb939410cd6b5d82d GIT binary patch literal 975 zcmV;=12FuFP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU#VM#Y{HxZiW%&918v`;>)zD;cHPmEaK68NK-}`qA3@i*Jfd34Co;_rEzh})y zfB<4)+_O$t*5o<2nVA{Gj9If7-n@7YS9b8=L5BNoo(#FZ&J3JurZOUl!4*gF$M-k1_;I) zBS1aB85l18VE73TKujnyJV1Qx5QB7ACd15(7>2mibQI}p7cVdbL_{%c*LG)MNULCA z_yiREOE`J}0*DDigbV1nJ2x4)@_iTz*gdxz+k71plHUmRm83O}5 zFeUuKZwWvEF<}*D2jb5_XXg4dbeJnK^i7+NBK`B*SBB`6G=?rlc?O2bTNxPG*ccdo z;kE=IfS7Q~umUkK->}xFG31CdGt6GHjDeMd6E6MZ+c$>tyc~w2wR;&DI+rpq@bLo$ zu^R;tKumc33ABib@h<~wOD03gm+K50S1xA|mzF__-Tw^zoox(-yUsJb>siOZU}(<3 z@DniFF}x|6$iSeli(wH! z05K7jdqJvrfoXpEJO;aSn;7=4UdCWxW{#p{_2PvLaSh!J@5*N~FqoP!{0Ej$jQ=5W z00G2YqAydy^Ys(Q|4;82{z5YkF8rSvNS`>yz<2i~gL_#M!-Z#W7#`lc3%BUbjq3~> z4xeN&s_15rP000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU$fk{L`RCwBA ze0A~S!~fje{AV8jVz~e81H+F${}{~q{xFz-xW}+rScl>M`=1N~Jj@JwQp^lm!dwi; z7`Pa2{o`R^W%$p43xH()|NqbM^xJQSolie}1PCCO2}fTF`$#cx+wpy4V3$*5Sg`*N zgD~?a29p=(7%s~TGyG#_VDRNB4EMeXGswO?%<%XA zhd%%T#P~KkGW-I1m6eZ;;V%m(!~cJO7&w{!GVuTX z!O-sJD~ctIAk6rmL7x2$g8<8ShHGEN8NS`U&G7EnzK;L_#PZqPo#Fet zj|@MwJ}@wGvNJG=0hPaa&A_mBJHxMnEQWuq&l$dcW@mWtpN~P{-#3OQyvhtLzkZ`w z@EaIJs+=zvf`rdA^gMNE`1KFySjJxrKmW6U;|CysSjt`qGqCf>FxWYAGq?#eGweBg zn}Lf>oWX(X0KZTPJ~P8-EglAL^M4HgeRrgQMVGhEA4$3=e?O!HVP)X2yRE?|$KqdosEz`z6ys`YOT8NUAE00z%bhBv@?ne@yB7$-m< z1LNi|)W-k;#6*fEpxms*^^~DVdKZH*%NMXu-UAKhV*bUDCUJm4hT|RBm!M#ShdmGg z1Q5$VU@(Ae{s$!f|NDp65=LN@Uj8J>@cySY!P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!R!KxbRCwBA zT(tSpLk0#0{x9EtG5q__z`)GNz`zK^Y%EL+-+%oDN&z`g@ROO9!R*a-2D5i}8GbR~ z2mk;7XZZWz0mF~gt3Lt+5X;rOZ-oE-`^Ww2-8Y86KnY2`0R{kn0_;WWTfWf%0000U^@i> literal 0 HcmV?d00001 diff --git a/data/images/flags/tz.png b/data/images/flags/tz.png new file mode 100644 index 0000000000000000000000000000000000000000..8816583f1094ff31d22eb8a795a5c033622cba82 GIT binary patch literal 701 zcmV;u0z&P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!Pf0{URCwBA z{5oaILk0#0{{NpoF);l9&p<8kA86L!2M-v2tX};QAb?nYpFc0m@b@qGzvs`XYsufg z41dm_XV~)bBMU$PvHZP#`_I2uuNeN`yT|bV-#@BZ0(3aTRiL9UF);iA2p|>)pur$p z|Nr^Jz(7SyKqmc#(f|R(!pOtJ!1(PO10z2_h9yjlObj6S=l>s^hA}fTGyDg_fB#YS zGJ-JB@eIFzGXMk-i@=&S5X1kWD`I5${^vWxi*L^v6#12b(S_+tMxX;8e}2dy#4g0Z z&Bg<^4DJ)4d4iWNF>r6#zyJ_HER5XTSY7z#{||;E&rUI1{B(gKO(|rw%qIb}-}5zdsCr|Nn)gCMIT-l*Pu(2F~3-{{Fy{RsI4K*YA%2 z0mSnC+joX+vIZbt(E0mQ(|%L{Vue;VR1009IF jdT=RBJ8v+21PCwyiIxkl#Fz|W00000NkvXXu0mjfQiDP7 literal 0 HcmV?d00001 diff --git a/data/images/flags/ua.png b/data/images/flags/ua.png new file mode 100644 index 0000000000000000000000000000000000000000..1ff1a18b5977930c6c0cc345416783885e95600d GIT binary patch literal 437 zcmV;m0ZRUfP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzM@d9MRCwBA zJo5bOLk0#0{-1yUGyG?u9srs4{>NX2i*J8?1PCCOr(b^y|Nqax{pt5V>RJLc^y$|> z443zAVF3srmN!5C{`ms5;4RQrYWf9e2g9p7R~cU3xbz1gfLK7Be?S6+sp^w|{}}%L z`3t520*Hx$k%8s_Ab^+{=l}o$h~?*BE`}ff{xf|0`;Y3u_2VBG!|(rW3;+SdVk@?m z;pgA44Bvl$pl-wi9e(HIGX^H!OAG)3#9}J44k+{*C<@9h|EOjO&?5{YuYWN-=Kan9 z5I`&pKrb=;=LTY4s#*dz>fa9_{>uOmKy-SYC5+JQ0T4heP=5h)=s$+PfB#c8 zPC({?GCM#30psKr%Z;1w8Lr>_36`LqCI1;7Jp9e@@X>D;fB<4yxADc>m8(DfdHv=O ftwQZ1K!5=N#%833q)U+k00000NkvXXu0mjfwJf=n literal 0 HcmV?d00001 diff --git a/data/images/flags/ug.png b/data/images/flags/ug.png new file mode 100644 index 0000000000000000000000000000000000000000..69c83f81d2c56ca2897fa36a99a2ded6359c9e37 GIT binary patch literal 812 zcmV+{1JnG8P)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!zDYzuRCwBA zoHAv~Lk0#0{!gDiG5r7kpMhEcWY&WR4;WUjUi}dufLP9-KQH|E?_cic&!1D(5}=t3 zK(iS>e*DM+5I`(8`uF~P{ql<8z0iH2CI6^q$-n=98B`_yFIKN)`B z{{eL^77dJy3<^z63>Q_j8QA~xGRT;?FuXZ5pW$gr6439zvFiciJAWA%UITp%5I{_9 z-xwI!zA`Ydf5wj4fS89zhJi1=oZ;5FiwxRET0q}0Fo^iaGH^&}faP)NWd)ke3=Cm_ z0Ae}F&d%_agM;BcCnv*yEI#@D`!|D*g(-sqD-*+wdyg1e8yXmV1qB$KYwH;92l+6Z z?77Xr#>R#j!a)2SXx=SgDgy{023uSE52~u_|CyLrfZ|M8FyntiGt2)VaOu*8|Iq;f z{~Ppl|G&9=@4t6Y2v{DwKDho500G3p%KDE1gxT2s0iy(aoIJUEh2ijlT@0e)Y7Aap z!3>%PMhrI}KVUd_Y#&e`?gRr$FCZfU0*LY6N#_s0f4=1Z_5B`D6uTvV{`_I!=4NLQ zmCa^&|5J@YQH6`);khP;cW-V29mtL&pD-}oxbu%;=iz@J0Ro6cQ4g3m{zEa&d;-i9 ze}FOgw~Il5g@=LR^A`qrO(q6;Rt~(`>JJ|y!@2tm3;+Sd@^;100RI9Lp2o9;M2_j0000P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU#nMp)JRCwBA zJh=PzO*S?b@u$zf{1M<~W%&C2FM|LdE5ox_KN!S?*%;n_{KFt2$indS?H>j~88HUK zi*p%7fBj?l&CI~S$bcXGe|Vqe@8;Dn0Ro8S(f#*w3Sz7rzrOrpc=Y@y11}FV&_G58 z2}wSN&ktWReE-M7aP8?o26<@?hA%hoGkjCGWMGhHVqpBm3bf=u9vgsipA8u{^9!>B z1Q5&1=U=`&dd9@@_VF8rZ~vGW#JL$5Bt$qFPMiW-@asQ=0LOm@UTzkKn>QI4?*A8N z(A(F~z`${z;qPBQ{FeOt%W&b{7lzXh-+ciHAeQgH{xbgg{+)r9nTg@+`=1P7nOPYw z-u}cOCCbTg`_U($ft(Bvp8sG_mgQtPbM7evlZO=pgNQc6|8MNT@WO4$-+%uY{ycfX z@bC9kMt}ffVPaxtP?G0ixP0jcgNg_v!*4cb239T(hW|j9XoxW}JpaYSz|6+N@bAxm z1`E-j3=9qd3=GD?3>@$O;*FHQfB!PDUB1k~@`IfLAb?o7IT#tf0VDhKm){KEIN2B! zIevpJ;`;Z4;pztt2G)N+8RP`m82FffGhCL`X84%m4Gil$46m3N8JO?{7tjd|AAoN9 z{)K@7Ab?oD{QS%C0ceSoI2*(5r@t7!^0G19ee#>ZP=TA_@AuCPGGgoux8M9`&{yDM zczNOygV3ql3=F`K5@NugxB1KY%nVFfRZpuwj3IjDJAB+_!%WTz|hX{9$He zz?Zl{KDlu5BE#AZ>)rwc5R2@x5(YuG?+n7v9sy1H2ML4!4A|+v{~5lp3o*Q9V8fq$ z!Ak!#e0}qQ;n&?K3;+Sd!YM1uz`)1B@S9f*l7W!SMF)&P3C{oj7&sXSgTKIV;$dfJ zV1N6O0U&@_zMJ_oa2RPbFn#}t5|fM!lmSLis9ZSDz`(Me0U&@_INQ?yf4};i;oq%$ z41Xv!8Oed5i2v~)9QpqN0*LYP^Z(z!e7M1J`RY|*2K)yMvHz4?0u0xGK(ih`e8_O_ z+_@hB0mO3Dgz?epPYmL3{xJMupgv#$;+Fsc1^_t`Tl%)~>WlyY002ovPDHLkV1h6T B?0^6O literal 0 HcmV?d00001 diff --git a/data/images/flags/us.png b/data/images/flags/us.png new file mode 100644 index 0000000000000000000000000000000000000000..ac54a983ffb661b8ca22e196884349b13d8af110 GIT binary patch literal 1031 zcmV+i1o-=jP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU#nMp)JRCwBA zJh=PzO*S?b@u$zf{1M<~W%&C2FM|LdE5ox_KN!S?*%;n_{KFt2$indS?H>j~88HUK zi*p%7fBj?l&CI~S$bcXGe|Vqe@8;Dn0Ro8S(f#*w3Sz7rzrOrpc=Y@y11}FV&_G58 z2}wSN&ktWReE-M7aP8?o26<@?hA%hoGkjCGWMGhHVqpBm3bf=u9vgsipA8u{^9!>B z1Q5&1=U=`&dd9@@_VF8rZ~vGW#JL$5Bt$qFPMiW-@asQ=0LOm@UTzkKn>QI4?*A8N z(A(F~z`${z;qPBQ{FeOt%W&b{7lzXh-+ciHAeQgH{xbgg{+)r9nTg@+`=1P7nOPYw z-u}cOCCbTg`_U($ft(Bvp8sG_mgQtPbM7evlZO=pgNQc6|8MNT@WO4$-+%uY{ycfX z@bC9kMt}ffVPaxtP?G0ixP0jcgNg_v!*4cb239T(hW|j9XoxW}JpaYSz|6+N@bAxm z1`E-j3=9qd3=GD?3>@$O;*FHQfB!PDUB1k~@`IfLAb?o7IT#tf0VDhKm){KEIN2B! zIevpJ;`;Z4;pztt2G)N+8RP`m82FffGhCL`X84%m4Gil$46m3N8JO?{7tjd|AAoN9 z{)K@7Ab?oD{QS%C0ceSoI2*(5r@t7!^0G19ee#>ZP=TA_@AuCPGGgoux8M9`&{yDM zczNOygV3ql3=F`K5@NugxB1KY%nVFfRZpuwj3IjDJAB+_!%WTz|hX{9$He zz?Zl{KDlu5BE#AZ>)rwc5R2@x5(YuG?+n7v9sy1H2ML4!4A|+v{~5lp3o*Q9V8fq$ z!Ak!#e0}qQ;n&?K3;+Sd!YM1uz`)1B@S9f*l7W!SMF)&P3C{oj7&sXSgTKIV;$dfJ zV1N6O0U&@_zMJ_oa2RPbFn#}t5|fM!lmSLis9ZSDz`(Me0U&@_INQ?yf4};i;oq%$ z41Xv!8Oed5i2v~)9QpqN0*LYP^Z(z!e7M1J`RY|*2K)yMvHz4?0u0xGK(ih`e8_O_ z+_@hB0mO3Dgz?epPYmL3{xJMupgv#$;+Fsc1^_t`Tl%)~>WlyY002ovPDHLkV1h6T B?0^6O literal 0 HcmV?d00001 diff --git a/data/images/flags/uy.png b/data/images/flags/uy.png new file mode 100644 index 0000000000000000000000000000000000000000..7e58ac4e2e8ce90ccf3a51d2555c59c17d4983c1 GIT binary patch literal 1239 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 literal 0 HcmV?d00001 diff --git a/data/images/flags/vg.png b/data/images/flags/vg.png new file mode 100644 index 0000000000000000000000000000000000000000..8d226efe52d397b1893301c48fa3b89558e9016c GIT binary patch literal 1290 zcmV+l1@-!gP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU$oJmAMRCwBA zys&@ALlz-H{{1ib89u%L%AhCD!(e-MBg1#jfc;F1PCCOrPo=7eR%(KTMEBo*!)kJ;n^Pn z2L7FU8Gc!_GTf8YV6go6fJ;TgTx(s`-{$=>}mj`IbUm#|}Vi6;h{?G9DA2Y-G zC(I0=9^PRA2q2c5ADRCgWtL`8y>*o#jpsgt^jCI*z{JDA?UU= z!<$=g8Gd|ZXZZB#7Xu^XZwAKy{~7)c3@!wwtroTTK{<3m0{Qdur;s4va z49xHD`~e6cmfmMe83eEWVR-rA9Rm+13xn*J=M0RWzc7d#J;Ctx$rA>b-#iR{Y(E(U zfViE-g`q7+5*LmNNbR1q`y^3=BXF z5I`(51U(pD%SbS2c>H9De0_}JA&V=6+^JIxpX?kM-ddP5yngbSVFim7!>zAu4EMh= zfi*Gx;$+}bS%R=E z8B~Cw0SqPvZie^&fv#p}V_^R9lfnI$F9X;6lMG7t?l4@J2Sz+26C@1);|U;uSk@kT z%5a^B;4{0da{hhfQCW`>nKmJI*@{s4wKFr0t0 zGd$z|%b?EA%wTq&gMsIhI)fmy0fY9(p9}}JuQR*>h6KwyMsOMg1`7i~020X<2S5;t zp_Pxu!Ng5xkK@_p5bh2x3Q@jb14Gv)eSPNcMoyAy9}}}duojx=de$-*k`Vm^wM{vg zyrA-7TLbzj!?+W}a^dwml$7R!R9mewu=W=~OiT=a5Xl>s)Pczlk_Q+uvj77mi}3&a z!=QVTiQy#wABNNJ?-^7+^D}5l*f2;wWM}xv4@~0B-@ujtoyq_ZKuiq(e?l`Baexu% zQ$~j041b@AGw{9yrmRn&fT{Bn!*$NP4FCB4GjN&xXZY}2isA1=HDJ>I3wA3&0I@JI z3jn?Uhk2?RWckVPACy4A<}ot>1Q5&BM{ihO{=Ut?@b(HcE(im3RiKcEMd3eqc?MMV`31xO zFK<`?0*K|)ty6EAUT*mF|HoSfnt*@5KLP|804E8yI))4nw*UYD07*qoM6N<$f;28+ AmjD0& literal 0 HcmV?d00001 diff --git a/data/images/flags/vi.png b/data/images/flags/vi.png new file mode 100644 index 0000000000000000000000000000000000000000..7916f3aaa68564a4229be4081f6a7abcbad860cf GIT binary patch literal 1197 zcmV;e1XBBnP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU$KS@MERCwAv ziaQQ~FbqTO1ZE}>_dgXv2(?FGC2d3a2zz+)hiPB)G|odYgHTYtLPcSngTZVa!B@FYciy5Rvr5KDo0x?|xj3a;mVtV=WBg5O* zuNl7o{sgr34a4iVkAM-%$RI4E#=yqS%dr2*35HXrjx+rI_>$rHiX{w8yo?NwUqXWN z?c;k4D_1XKc*DZZz|70Xz{AZ3G59~w0Y6?beERkpXyzXVfB<5V{Kv#FZNeD_UQs>< zCWcQ8$IjhkSh1)8n6g+IzW)2qz{AhWz$?JV@ZuLIgS0X?!_|Yk8NNM#$iU{p&A=hb z%fKff!0`OlYlg3^oWRt@1$F&@hDGyFGEABNhQZkQI|D!fvAp~&$>0|v$&j3|n&A(N zBtvtB8^iOTZy0v|y3HV?p~3Lr$`ytqbJ`f1mV9RT_K=6+GZPns(0>^QwjFyJEI8IN z{CfM2L0C+JAug?u0i1@RUitQemEq$Dc80(Ielq|B5Q~VwF9v00Sq4E#U4{?uc^T|n zBp9B2dBt$zo+mHK6;QL$?7)48)Y>HLDpXke_y|6c=_uV!=;a(7~~8r z8BA=f8Gio%$nXXjS0e1ZU`zh~<7Z%J{{YNG%nSel#Ps9m9|mS-7KSPFG#OT}m1E!+ z6lD1J?i~Zuzn=_C-(O)63i4ox>s!d6BE`U9|8^}yfc7T_M~OQO+Mh2oWQ3(Mbj_c~ zz{1MJaP7uT28M4xp*~?|2oIKJXl@Z@;O63C00G?4nxN(i)$+No*jI56sUfzGm@LJQ1;q0Bq3@3mYpF22&L0a$!!|~Hg8UB5E&Jf}o z#vmdnf=HnXG7Rc!LJVwd%nSel#KHoMV06F?ED}77ofu|)QD6||{>1Qv;VFZ2&s>Hl zH!m@qIIy2VQNxJA$laOY>dn&(^CnGZm^^bS0}GchmedO=VgLe&g@Xl>>_D8qz)T1O zOib(y{*Ibp_OD;R85o%v86>1s7@Q5iGq6fZGq5mmFj$)SFzC*7VBqH90B1a80Lp|Q z^MJ-O00a=r$FHAQKHj*&aQ*squmnK>a>z$uDRAQ!US%M&9z1x!@Z{-J7JvX^*|Kr- z+oLB=|9Si7ExeRP_cADRfl6aU5I|BZNDLMv*Z@fG_wU~y0RjvFF=e0hi0wPH00000 LNkvXXu0mjfDYq^5 literal 0 HcmV?d00001 diff --git a/data/images/flags/vn.png b/data/images/flags/vn.png new file mode 100644 index 0000000000000000000000000000000000000000..538b0810d4a286f82933cf0125af78f56d620472 GIT binary patch literal 618 zcmV-w0+s!VP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz{7FPXRCwBA z{5oaILk0#0{{NpoF);l9&p<8kA86L!2M-v2tX};QAb?nYpFc0m@b@qGzvs`XYsufg z41dm_XV~)bBMU$PvHZP#`_I2uuNeN`yT|bV-#@BZ0(3aTRiL9UF);iA2p|>)pur$p z|Nr^Jz(B|nCI$v}dlrUY$AId8;`cSkq`y!aAb^+{7#WFn93u+@16L$71G6|VI7sjm zKmaijRKW}~oe^v(vkDUfyAu-wt0|H;X2OmH2p|?h3fLW38Q3hC8UB9ejfkLz%22ffwvrJC38V8NRRi3rq-1ghK!b00M{wlo3I> z6O`Nk;!^$YKf~LaUkv|$A%s3}{{!|RuKe;Jni)YfKmf7)xqO-B_UF$Gx1qTcXZHPt zMeHB`9RC=IpD-}600a=r*3X~cZUiR6w+u7`9{~ak05k2d2HdW(MF0Q*07*qoM6N<$ Ef{dg2H2?qr literal 0 HcmV?d00001 diff --git a/data/images/flags/vu.png b/data/images/flags/vu.png new file mode 100644 index 0000000000000000000000000000000000000000..e514f7b52bda7f3bb7a8a8a4b426cd5c8f5eaa5c GIT binary patch literal 895 zcmV-_1AzRAP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU#5lKWrRCwBA zTsv>-Lo2rb{1U%EF);l955x>q0ziYFJz#jhd-X?v0Al$octv>Wy`S8&7oIUVu>WHa z<3OmP2>koYz;NR{!@egUSpWivW$l{lf8&rq=KH-pS2Muu9Ee+<0rAcd4! za`D@LhO^%o{s06J%ZulK8P1>i%^)KAi^1BGf#J8oe}=$Se;A@)F)+l7F)%Q(kY^c4 z%b))c8X$mJSXmhv*f<#&P8|BjaPq)^21i#W2Au$AhV%daF&y5_$l&<$A49AJP$e_@ zJ_86K7EoL>0kN+i3xh1{ABI27{}|RUWMEJ?Vqh=}W@6ZMkCEZ%vHuJ)|Nk-A@RMf= zKmf7)`NP1VslmjcB>j)!&}lXX4f$UTS*1)26{UX}PMrGB5EsM5z@NgvP_!QC%NGm` zLEMZCOhg9Pt)C1G&w*hE5I`)zP+<_0_|Ne8<$s3%Ul|x4Tw!Lga${x?5&6sT{>wjx zsgwRP$jCD>lvgk@aPj|V*!h?VVhJOG1PlzF=THAL+`q)Y01!YdEWn7pbeWMMI)<4+ zN&P2-v;jZEv)i8--n;>YBO`;MHWP!37YoC+n&hJQc*G5r4ghxB9xjW>V*V)^ysFN2lEUj|JxHiq+ue=_X6 zE5yKOEy7?D!^Q9)sOa~nKV;^3V37TVl!X8R#5id(|CeiLK5`$r$;H5}Ex;hC$;rUT z!pQLN4`qcVC{8}V_{H${;^(gb0mNu##s2WYD<*yxZeY>`=KlYG8K?{X{r?CMU;x)m V6a{k8@2LO)002ovPDHLkV1iUWpnCuS literal 0 HcmV?d00001 diff --git a/data/images/flags/wf.png b/data/images/flags/wf.png new file mode 100644 index 0000000000000000000000000000000000000000..2154ef8028e7d3f9eb628d5a6274dbf72e69a6ee GIT binary patch literal 541 zcmV+&0^P000jN0ssI2o&@u<0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzzez+vRCwB~lg~@SU>L`ry_+p9LntYY zzz$Iqc+4ehU@3~UzfVn*0U&NS)pf9K__g%=`^QX% zySl3BI+Y|f7#w$4c=+Y)tflM7;(WfAEZcvv)O`L_Q8-al%Vn)pdKH9#vw$r5;=-!e zk>&FHt>`STWeCAd`0SDhnOI$IO44#VjT;KF*jPBM7mHNWzF0dGM{2#QOjhQPy*IfJ z95oEQ3ND_2nugYVk;v@URz4bSRw|6kHAwb^ zkh?^}JvSH5X7TlQd%Li?iN6FhNFOP!7YgoB=q4V=IYz08;`cuGQ<&q^$w?<{cF4O* f23qR7-US!{Il9XmGj&}y00000NkvXXu0mjfvp@IN literal 0 HcmV?d00001 diff --git a/data/images/flags/ws.png b/data/images/flags/ws.png new file mode 100644 index 0000000000000000000000000000000000000000..8a26405b7ba2abab1315d09744912955d17c8ac6 GIT binary patch literal 639 zcmV-_0)YLAP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!5lKWrRCwBA zoHAwqLk0#0{!gF2GyMP000sXUSXh`DBqaqHUcUUq@axxapco?q0x&YLGyGx5VYmvU z|1w|&Aj2L!U|?9i`XfL9v7A5uNcivH|J=`?e*}vE2V1}ZG=PVPhaoA+hGEB!D-3Vm ze1clS2)Bfr;WtAD!y&L`SixVQ<>${cto`_r1t5S}Zr^_O=hdsv4EOH6Vfgp&H-oCG z6oaX$0>i3RCmFVGy#!SIlOZ+L2`K-TVej6{K;0}r%*4RY@B?W40U-MqPD?HV9eoz) zVt@c*`SNW#DXaV z340zM4hAbLWrib1Zi9V6t_2_f5I{^2&1^u-j)YkmSj9ydid(}Oc;%&ld}cIxc7~5Y z{GUim00M|5k>Lu%7lyYC?-`x|HT^@_{(y<$j@Lehl#kzl>F6(pZ$YVw6Ib#@0{{U8 zwB#m`dI7|D;gJeS9YFk=fhqtXfS4HQ0007rE|vfU5KS$CWe$JVheE!UE9W~miVhON#WB``FEC2z-vhMTew=01qz-!ut+DCu@ Z0|2=6!mX)F3o`%!002ovPDHLkV1h&m3f}+# literal 0 HcmV?d00001 diff --git a/data/images/flags/xt.png b/data/images/flags/xt.png new file mode 100644 index 0000000000000000000000000000000000000000..ce3d8b35b400f3cf3087a5fad9424513d1407397 GIT binary patch literal 1252 zcmVP000jN1^@s65oToN0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU$hDk(0RCwBKlv`|6RTPH5bD2x0?Q}X7 z+bNxP+B95k1w<@G2tqWXJXj5cOV9+3K4{da_`r+t5*}=#iN+9kK+u4}h@ePR2vI^4 z5K0QQ9okAeEk!8Pb~>HubS~%SoB7f)mg~#$3{?{S-Wj1+0Cd>9#Ut~>#kxbFpJrBB^UZXG+{F2qnrB8_E@H&tM|S8o zD`d;UV`gc{4bhMtB8i*omqrP$7_*s_=h4FX*GawV=Hu2)^u}k4Vk)Rsem3x%NiYpE z`VhYfO|k`?rvlH905DNjR9QgB%s6-JzeQxTeOwNuk8RM+k0!l`-O zzJ81K+gH;OJIH>$f(B(m$aT9fPD{7#bHY!tk+uEUAiCJh1G%g;HG0%8)3Qt-p~*o? z$Wr~lqlB+^vTc1W zYnIL7lg}^F*5Tq}aUDKyn)>X&)FjT4(z=ig!LI(7Wq<0}Eexkgb~=Yt;AVW~60R+8 zq;mdzc76B(b873^vT3WxT0&N2Mj{bT|9P6RC#5QQ<|d}62BjvZR2H60wP9djbo6v_45_%4^vA09lrtL}+rR(w z?F|k7{6FuY`$0bA+WmHZiMln>e+3m9BI@F8^v`_wuK#_%`1zIDC-3j)sn?s6e&f?S zWo`$CjeqtQ|31I}`g)0f_x2zE@c+L3sz|01i^Esz|4ZuqP5;3x;Gn~^|G(j%|39Zc z{NHcS!xOTFCFN3l!U2Z2i3}VHVs3SEY{%m7OGvc-T$#nG%<=!<=>s1=n==EA=Fo{| zHk>odk@XYj1)GE3(sFXA)8>;C^c3)GY_zvkbc znGYWy?+@9|!p6YV!I>h>arqRJ0MIVBxVU@z3l_Dt{Q!El(MWBF$g;#ozc>UE7;0Qr VyII}Ydl?ww44$rjF6*2UngCRju1f#_ literal 0 HcmV?d00001 diff --git a/data/images/flags/yt.png b/data/images/flags/yt.png new file mode 100644 index 0000000000000000000000000000000000000000..c1a539549a4025143c149dbaf30f945ee069178a GIT binary patch literal 508 zcmVP000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzj!8s8RCwBA zTzcg3Lk0#0{x3iOGBEuA&wvbm{Qk#atuDl1tSG>M3;cidkm2v<4G<0^vIG!4dCc&4 z+vbk|0mO3s=@;RD|NnEp{`3>w4u-Ek|1cb@c+YmhJVM7K%CCVgpm06 zm*LXS-wfw|{r&?GKrH|MgDnN({}`71!6Jrf3&?;!P@fP000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzY)M2xRCwBA zT($7@Lk0#0{;ywtGW`GlpMhEcWYqH~9~ky*z4j3xfLLx`eIfk!??3Lhuf9;#lE44{ zGXTwIc>MA_3qSy|Ji7n(&-=IE7@j_Q&+w0mmi+tskKyaj7YyHiJ^KR?KrDa${A2j_ z>kq@vpMMzs{iB{GzyM?T^Y;(XNB-x6!3Ffe#Y-0%)~{dt5g>qAIJwyvXaPW$Z~@I`V`pIi2p}c~ zIskwGqKhQ}0Yn!|00M~R-_k`43}3!5Fno9q$)?nb6QEiDo;_#yfB6anKmf7)pE-wt z;l&Gv|99_#VvBl~z>_iq13&<=FmM1vjFXAsKMxDTUuvcp{ QN&o-=07*qoM6N<$g6K-pjQ{`u literal 0 HcmV?d00001 diff --git a/data/images/flags/za.png b/data/images/flags/za.png new file mode 100644 index 0000000000000000000000000000000000000000..38a122ce02bda2ef19cc0d43948a065a93f8ed0b GIT binary patch literal 1118 zcmV-k1flzhP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU#@JU2LRCwBA z-2ZCjLk0#0{_lUkGW`AjmqCz4kU>w-k%8k62LtnoT?|ZjuQLE87#LU>NCZGV&mJ%^ z>{NmsVug@8nnEo&@0_8y#9e=-_L6%#K z!A{(d!J@E^fouPM2Bw}o28I{!7#KK+uncI)#c%%^&VFO~0}w!rS6}#k;1++%zwzOB zhVw7^K(Y*M%>TgZe*gQ$@bCXm26bLThESyn2IUXJ44>AwGjPwJ#lZ3o=u$>Jb^!7D zp9~DEKQnv;2p~qkK<5wU&ae3$LLM-%2=Xx;e!|Ca^$jlrBhz077RLWz4Zr?~Ck*!wXtJp3-saNq?O!}Bkk3~WHlm>5AW`^WI>?{@}XRzU^_$xsF*F=ucbf*@8) z{{3UPb^aE^uGL#V0t67;lDl{B0ww-4*!XZU)HnZS5K-o4IPzMU;npV}hVOrvfEGav z1Es*fz#wF00!0k&@cIu7uXi`UGu++t_9H+5F`*~BBU`>QxElUt=&JqC5U6yQAzJ+& zgE0GVpurFWnHiaZzT-k9Q#|-TIA$0b00Iaj^@~bzGUQkNXK?lX&2Z(d7{l>bLJS{% zGsArX()bIQApZXUg+G@7&HwrDCorY{W&j8vxFu}tEDYhv{~1Cvm>6U<`4~<;=VCZ@ zPnh8k&{#I6f8c=m4b;TO#Li$UV9lT)U;ycLz5I{gnm>5iK zco`;SvN7nHvM?NaEyS?loCwgh{~6es|1z*a}_wFzN1Q3f|APd8$d(sRS-f{pX zpm_tB&wc=1&cevT;4B)(;365t@a;K2!;HQo3^#5(1-p`!l^L&3Ku!di2Z}|20AiVW z=@ZMN+wU1ZKl}!=9+-*#G5iKbstkuBgBdXMG2d5Z=sdBO;l}kR48MN=0s4{&6cvPv zN1$1sKfh)8^5q>1Kmakib}2u+yZasgkN1DTk;ul#${@%l0SqD$hR>gVGrWKQnc@5Q kU%;Rt&TuT?BS3%w0LRy$00ap%a{vGU07*qoM6N<$f)+CdW&i*H literal 0 HcmV?d00001 diff --git a/data/images/flags/zm.png b/data/images/flags/zm.png new file mode 100644 index 0000000000000000000000000000000000000000..7503819b94149edd6bd0c24df08a9cffe592e949 GIT binary patch literal 676 zcmV;V0$crwP)P000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!Hc3Q5RCwBq z!LbQ|Kny_9AJEtcZs7hmf{jzySc$bZhXjOZ;tIYeWhpx_m>ZeNZz4zp<7M31aH`VB z3jtMII)CtEV_v8F2_Tl6Z*L0!`}dFg&9^rU{~3t4?)l|k?QM+WuppBW5+_}foLh9wdr413u48E$>}_6Hzvx8SzkV?Q1P}`Yncx>IBZK;_Zw%8W z+-6_|#>WMqm=#DI=xBMK{|r-u85nMVVqjSF?mq)S0FiA82rvN+XZZt3EUci!07()I z41a)pV8}B8F~|V`0mMX!CI7*x3>tipbO5y-5?>5Z3=lvRSwbuT2q3yx0uVrSu>>H1 zXle-~G?D-Uhy|1h!5I;l#z`r={sW7Ye?a^P8oz(heaG$Z++`ABJgAcYQ&!HH%=fB^u(4d<8!P2#-(0000< KMNUMnLSTZH1ta$W literal 0 HcmV?d00001 diff --git a/data/images/flags/zw.png b/data/images/flags/zw.png new file mode 100644 index 0000000000000000000000000000000000000000..0ccea687d0c1b01ff75e5b39e08575f2a7bddf2e GIT binary patch literal 766 zcmVP000jN1^@s65oToN00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!kV!;ARCwBA zy!QC)!_%Lx@PB6g%)s!Mk%3y^KM+3o_Jm>UlWiXX0*LYdzyDt@Uq8;>d840!S&E(E z|6eAmSn}`xUxuq6t}#42^z1rWV2Q`r{mi{b~LT79ZX){A6dRAh;M9K7aVj@bmF+27mwpTk@Zi znc?xi4hEJxJPf+=u?+8n7cv}8PG``2^@xEB9Eqd@P!eK>(f|R(`0eAn51;q!;D60K zjp5__{|w?^f*B+O;}{-3xzBJtE{@^poqG(7Ow44YE(V4tfB!L@|MT}FKmf6@aq%$x zRup02`tzOP-r4sI|Afvl$g*WJXzH3Uux{SOkZ)nl@Z`-$1{PK_Ecy?@00G3p%)-jR zEF{UWzxN-*&4<4k!ZM5)*tj_uzW@2bFtwnJ;or-54ARWZ42-}3l5EK@pbg)Fz5xgz zaGWqQvoXk-%Q46ru`uuoDKgwXdx7D0NghMufx`@$Ao)LkNRAg^;$pZC#M^-wAb`M0 z_&>uxeny6$KrS1@!+UoaZlpRf7{7YOz{1JN@Sl;qQUGMhrLTV(Hop7w5g>pdK4Adm za!!Uf4<9o8dQ`#Sz7(j0T@Dzaj1=V){wqHjI5vD?00);_*{9s?6|KZBC)2Zr}7 z91IM9{!yG6KLd%Mz<2=&AW(39*|l*kclwqLhJT{}7?^-X%K!gVDIpntz52uO{m##? w00G2O+f?v&=9Z;@K7IcT%{o*E9{~ak0M%RuYlk*fhX4Qo07*qoM6N<$f>kSM@Bjb+ literal 0 HcmV?d00001 diff --git a/data/images/login/login.png b/data/images/login/login.png new file mode 100644 index 0000000000000000000000000000000000000000..b50ec42a5e86f71478526a925209af02c6dcaa7e GIT binary patch literal 636 zcmV-?0)zdDP)ioX&l9Qg5)@#=1TUz0#I#>Q1{5;Lx$e~Y#YI3)8HD<0{QUDSN(~~VY%#_ka4v$(pV;jV zZemQHIWENnlQeyd@u3fx7E8edS4fz$)OHvFC4ME@Ek3K5UrPbm|Qxndm zrs|WElD>U~fax4K5Z_W%RD{8S0rcy1=;`W0M{6t8&CMvtFMvR>ts@{Hz>G5iu{gF; ztyZ(;r@hdJ)|M71ZYq!~&4p5V3z?aj@b}-IEffl^-nxLFpP%{hW5-*cm%pQ}4W~|? zLPW%G2&X*~6cmia#6(nAS0g<=-5?T$m4$?afBM!00s{ks6bgl$Eq`x!HxiB{z|U{v zQ+nbOZ*T8gJRYxO@#4=B6}6jILy5Y&I!GiL&xFFAt|ljsjEr>3zm%`LQ(KGcWo4CV zY3YxGgF_Pee6JVH>g(%k<>JC?cX$5*n>K9*je$&7_%u5DcMFq}M?^%J(bCJ+DivNr8ub*06UET6cZo<8L_b#lhsx`0;cUz%A-2VMV0GR*1(D;1wB}<&gnIsS^ zT56M>nSGb-_{WWnkeoZ$DH4gMz3zUK#xN^}Ioy_KZ>W=P?9^&hP*hYPCML#ulfjv}NqoGJ4 zpvlwObxs8i3CV6vjwKBA6&cB?K*BF1 zOLK8N2Fhonw|FX4+2e5MBtc3j16q1lHik2SxNXFjZM2@^>FB>Y1L}(t(3nObi)TPj zZk&6L1md?7$J!G_tIfB^!1Xz_^mb^@G(&m9aMx=CJ&8SM0+(Y6lO01!+AmwfQ0|28 zqWAGA!veRD6G(OspMD)1$C*H4AQ7pv#-g*(5xP=GsB^4w|13el0S5jnNe&ysnLtVi zncpN~o@omv)7$PtbmZBh`TR(fA0eRQsG_B};Y@&*-n=S>RJBXr9lq_l3MZrK{Fvd} zP7=)w34bK3*f`DvQg@McYC2}>%QMkSJOA+oYt*F@2KlJW&*(Q^S~Vn>QS#fD;OJgwOqO+i}pSc}6unWN$@L?c>D t{xBw6LRoA~G>76qnP^Nhiegd^{0nWMs(2>k+J685002ovPDHLkV1jsQt8D-P literal 0 HcmV?d00001 diff --git a/data/texts/AboutBundleManager.html b/data/texts/AboutBundleManager.html new file mode 100644 index 0000000..7157d1e --- /dev/null +++ b/data/texts/AboutBundleManager.html @@ -0,0 +1,36 @@ + + + + + A propos de BundleManager V1 + + + +

BundleManager V1

+
+

+ Ce programme est libre, a été conçu pour illustrer l'apprentissage du langage Java. +

+

+ Tout ou partie de ce programme à vocation à êre recopié. + En revanche, les explications qui accompagnent ce programme ne sont pas libre de droits. +

+
+

misc@merciol.fr

+
+ + diff --git a/data/texts/AboutChat.html b/data/texts/AboutChat.html new file mode 100644 index 0000000..d2cae99 --- /dev/null +++ b/data/texts/AboutChat.html @@ -0,0 +1,36 @@ + + + + + A propos de BundleManager V1 + + + +

Chat V1

+
+

+ Ce programme est libre, a été conçu pour illustrer l'apprentissage du langage Java. +

+

+ Tout ou partie de ce programme à vocation à êre recopié. + En revanche, les explications qui accompagnent ce programme ne sont pas libre de droits. +

+
+

misc@merciol.fr

+
+ + diff --git a/data/texts/AboutLogin.html b/data/texts/AboutLogin.html new file mode 100644 index 0000000..34ece3f --- /dev/null +++ b/data/texts/AboutLogin.html @@ -0,0 +1,36 @@ + + + + + A propos de BundleManager V1 + + + +

Login V1

+
+

+ Ce programme est libre, a été conçu pour illustrer l'apprentissage du langage Java. +

+

+ Tout ou partie de ce programme à vocation à êre recopié. + En revanche, les explications qui accompagnent ce programme ne sont pas libre de droits. +

+
+

misc@merciol.fr

+
+ + diff --git a/data/texts/AboutMisc.html b/data/texts/AboutMisc.html new file mode 100644 index 0000000..baeda63 --- /dev/null +++ b/data/texts/AboutMisc.html @@ -0,0 +1,39 @@ + + + + + A propos de Misc + + + +

Misc (23/05/2016)

+
+

+ Ce programme est libre et a été conçu pour illustrer l'apprentissage du langage Java. +

+

+ Tout ou partie de ce programme à vocation à êre recopié. + En revanche, les explications qui accompagnent ce programme ne sont pas libre de droits. +

+

+ http://misc.parlenet.org/ +

+
+

misc@merciol.fr

+
+ + diff --git a/data/texts/BundleManagerLicence.html b/data/texts/BundleManagerLicence.html new file mode 100644 index 0000000..b6f1e61 --- /dev/null +++ b/data/texts/BundleManagerLicence.html @@ -0,0 +1,653 @@ + + + + + CONTRAT DE LICENCE DE LOGICIEL LIBRE CeCILL-B + + + + +

CONTRAT DE LICENCE DE LOGICIEL LIBRE CeCILL-B

+ +
+

Avertissement

+ +

Ce contrat est une licence de logiciel libre issue d'une + concertation entre ses auteurs afin que le respect de deux + grands principes préside ŕ sa rédaction:

+
    +
  • d'une part, le respect des principes de diffusion des + logiciels libres: accčs au code source, droits étendus + conférés aux utilisateurs,
  • +
  • d'autre part, la désignation d'un droit applicable, le + droit français, auquel elle est conforme, tant au regard du + droit de la responsabilité civile que du droit de la + propriété intellectuelle et de la protection qu'il offre aux + auteurs et titulaires des droits patrimoniaux sur un + logiciel.
  • +
+ +

Les auteurs de la licence + CeCILL-B1 sont:

+ +

Commissariat ŕ l'Energie Atomique - CEA, établissement public + de recherche ŕ caractčre scientifique, technique et + industriel, dont le sičge est situé 25 rue Leblanc, immeuble + Le Ponant D, 75015 Paris.

+

Centre National de la Recherche Scientifique - CNRS, + établissement public ŕ caractčre scientifique et + technologique, dont le sičge est situé 3 rue Michel-Ange, + 75794 Paris cedex 16.

+ +

Institut National de Recherche en Informatique et en + Automatique - INRIA, établissement public ŕ caractčre + scientifique et technologique, dont le sičge est situé Domaine + de Voluceau, Rocquencourt, BP 105, 78153 Le Chesnay cedex.

+ +
+
+

Préambule

+

Ce contrat est une licence de logiciel libre dont l'objectif + est de conférer aux utilisateurs une trčs large liberté de + modification et de redistribution du logiciel régi par cette + licence.

+

L'exercice de cette liberté est assorti d'une obligation + forte de citation ŕ la charge de ceux qui distribueraient un + logiciel incorporant un logiciel régi par la présente licence + afin d'assurer que les contributions de tous soient + correctement identifiées et reconnues.

+

L'accessibilité au code source et les droits de copie, de + modification et de redistribution qui découlent de ce contrat + ont pour contrepartie de n'offrir aux utilisateurs qu'une + garantie limitée et de ne faire peser sur l'auteur du + logiciel, le titulaire des droits patrimoniaux et les + concédants successifs qu'une responsabilité restreinte.

+

A cet égard l'attention de l'utilisateur est attirée sur les + risques associés au chargement, ŕ l'utilisation, ŕ la + modification et/ou au développement et ŕ la reproduction du + logiciel par l'utilisateur étant donné sa spécificité de + logiciel libre, qui peut le rendre complexe ŕ manipuler et qui + le réserve donc ŕ des développeurs ou des professionnels + avertis possédant des connaissances informatiques + approfondies. Les utilisateurs sont donc invités ŕ charger et + tester l'adéquation du logiciel ŕ leurs besoins dans des + conditions permettant d'assurer la sécurité de leurs systčmes + et/ou de leurs données et, plus généralement, ŕ l'utiliser et + l'exploiter dans les męmes conditions de sécurité. Ce contrat + peut ętre reproduit et diffusé librement, sous réserve de le + conserver en l'état, sans ajout ni suppression de + clauses.

+

Ce contrat est susceptible de s'appliquer ŕ tout logiciel + dont le titulaire des droits patrimoniaux décide de soumettre + l'exploitation aux dispositions qu'il contient.

+ +
+
+

Article 1 - DEFINITIONS

+

Dans ce contrat, les termes suivants, lorsqu'ils seront + écrits avec une lettre capitale, auront la signification + suivante:

+

Contrat: + désigne le présent contrat de licence, ses éventuelles + versions postérieures et annexes.

+

Logiciel: + désigne le logiciel sous sa forme de Code Objet et/ou de Code + Source et le cas échéant sa documentation, dans leur état au + moment de l'acceptation du Contrat par le Licencié.

+

Logiciel + Initial: désigne le Logiciel sous sa forme de Code + Source et éventuellement de Code Objet et le cas échéant sa + documentation, dans leur état au moment de leur premičre + diffusion sous les termes du Contrat.

+

Logiciel + Modifié: désigne le Logiciel modifié par au moins une + Contribution.

+

Code + Source: désigne l'ensemble des instructions et des + lignes de programme du Logiciel et auquel l'accčs est + nécessaire en vue de modifier le Logiciel.

+

Code + Objet: désigne les fichiers binaires issus de la + compilation du Code Source.

+

Titulaire: + désigne le ou les détenteurs des droits patrimoniaux d'auteur + sur le Logiciel Initial.

+

Licencié: + désigne le ou les utilisateurs du Logiciel ayant accepté le + Contrat.

+

Contributeur: + désigne le Licencié auteur d'au moins une Contribution.

+

Concédant: + désigne le Titulaire ou toute personne physique ou morale + distribuant le Logiciel sous le Contrat.

+

Contribution: + désigne l'ensemble des modifications, corrections, + traductions, adaptations et/ou nouvelles fonctionnalités + intégrées dans le Logiciel par tout Contributeur, ainsi que + tout Module Interne.

+

Module: + désigne un ensemble de fichiers sources y compris leur + documentation qui permet de réaliser des fonctionnalités ou + services supplémentaires ŕ ceux fournis par le Logiciel.

+

Module + Externe: désigne tout Module, non dérivé du Logiciel, tel + que ce Module et le Logiciel s'exécutent dans des espaces + d'adressage différents, l'un appelant l'autre au moment de leur + exécution.

+

Module + Interne: désigne tout Module lié au Logiciel de telle + sorte qu'ils s'exécutent dans le męme espace + d'adressage.

+

Parties: + désigne collectivement le Licencié et le Concédant.

+

Ces termes s'entendent au singulier comme au pluriel.

+
+
+

Article 2 - OBJET

+

Le Contrat a pour objet la concession par le Concédant au + Licencié d'une licence non exclusive, cessible et mondiale du + Logiciel telle que définie ci-aprčs ŕ + l'article 5 + pour toute la durée de protection des droits portant sur ce + Logiciel.

+
+
+

Article 3 - ACCEPTATION

+
+

3.1 + L'acceptation par le Licencié des termes du Contrat est + réputée acquise du fait du premier des faits suivants:

+
    +
  • (i) le chargement du Logiciel par tout moyen notamment + par téléchargement ŕ partir d'un serveur distant ou par + chargement ŕ partir d'un support physique;
  • +
  • (ii) le premier exercice par le Licencié de l'un + quelconque des droits concédés par le Contrat.
  • +
+
+
+

3.2 Un exemplaire du + Contrat, contenant notamment un avertissement relatif aux + spécificités du Logiciel, ŕ la restriction de garantie et ŕ + la limitation ŕ un usage par des utilisateurs expérimentés a + été mis ŕ disposition du Licencié préalablement ŕ son + acceptation telle que définie ŕ + l'article 3.1 + ci dessus et le Licencié reconnaît en avoir pris + connaissance.

+
+
+
+

Article 4 - ENTREE EN VIGUEUR ET DUREE

+
+

4.1 ENTREE EN VIGUEUR

+

Le Contrat entre en vigueur ŕ la date de son acceptation + par le Licencié telle que définie + en 3.1.

+
+
+

4.2 DUREE

+

Le Contrat produira ses effets pendant toute la durée + légale de protection des droits patrimoniaux portant sur le + Logiciel.

+
+
+
+

+ Article 5 - ETENDUE DES DROITS + CONCEDES

+

Le Concédant concčde au Licencié, qui accepte, les droits + suivants sur le Logiciel pour toutes destinations et pour la + durée du Contrat dans les conditions ci-aprčs + détaillées.

+

Par ailleurs, si le Concédant détient ou venait ŕ détenir un + ou plusieurs brevets d'invention protégeant tout ou partie des + fonctionnalités du Logiciel ou de ses composants, il s'engage + ŕ ne pas opposer les éventuels droits conférés par ces brevets + aux Licenciés successifs qui utiliseraient, exploiteraient ou + modifieraient le Logiciel. En cas de cession de ces brevets, + le Concédant s'engage ŕ faire reprendre les obligations du + présent alinéa aux cessionnaires.

+
+

5.1 DROIT + D'UTILISATION

+ +

Le Licencié est autorisé ŕ utiliser le Logiciel, sans + restriction quant aux domaines d'application, étant ci-aprčs + précisé que cela comporte:

+
    +
  1. la reproduction permanente ou provisoire du Logiciel + en tout ou partie par tout moyen et sous toute + forme.

  2. +
  3. le chargement, l'affichage, l'exécution, ou le + stockage du Logiciel sur tout support.

  4. +
  5. la possibilité d'en observer, d'en étudier, ou d'en + tester le fonctionnement afin de déterminer les idées et + principes qui sont ŕ la base de n'importe quel élément + de ce Logiciel; et ceci, lorsque le Licencié effectue + toute opération de chargement, d'affichage, d'exécution, + de transmission ou de stockage du Logiciel qu'il est en + droit d'effectuer en vertu du Contrat.

  6. +
+
+
+

5.2 DROIT D'APPORTER DES + CONTRIBUTIONS

+

Le droit d'apporter des Contributions comporte le droit de + traduire, d'adapter, d'arranger ou d'apporter toute autre + modification au Logiciel et le droit de reproduire le + logiciel en résultant.

+

Le Licencié est autorisé ŕ apporter toute Contribution au + Logiciel sous réserve de mentionner, de façon explicite, son + nom en tant qu'auteur de cette Contribution et la date de + création de celle-ci.

+
+
+

5.3 DROIT DE + DISTRIBUTION

+

Le droit de distribution comporte notamment le droit de + diffuser, de transmettre et de communiquer le Logiciel au + public sur tout support et par tout moyen ainsi que le droit + de mettre sur le marché ŕ titre onéreux ou gratuit, un ou + des exemplaires du Logiciel par tout procédé.

+

Le Licencié est autorisé ŕ distribuer des copies du + Logiciel, modifié ou non, ŕ des tiers dans les conditions + ci-aprčs détaillées.

+
+

5.3.1 DISTRIBUTION DU + LOGICIEL SANS MODIFICATION

+

Le Licencié est autorisé ŕ distribuer des copies + conformes du Logiciel, sous forme de Code Source ou de + Code Objet, ŕ condition que cette distribution respecte + les dispositions du Contrat dans leur totalité et soit + accompagnée:

+
    +
  1. d'un exemplaire du Contrat,

  2. +
  3. d'un avertissement relatif ŕ la restriction de + garantie et de responsabilité du Concédant telle que + prévue aux + articles 8 + et 9,

  4. +
+

et que, dans le cas oů seul le Code Objet du Logiciel est + redistribué, le Licencié permette un accčs effectif au + Code Source complet du Logiciel pendant au moins toute la + durée de sa distribution du Logiciel, étant entendu que le + coűt additionnel d'acquisition du Code Source ne devra pas + excéder le simple coűt de transfert des données.

+
+
+

+ 5.3.2 DISTRIBUTION DU LOGICIEL MODIFIE

+

Lorsque le Licencié apporte une Contribution au Logiciel, + le Logiciel Modifié peut ętre distribué sous un contrat de + licence autre que le présent Contrat sous réserve du + respect des dispositions de l'article + 5.3.4.

+
+
+

5.3.3 DISTRIBUTION DES + MODULES EXTERNES

+

Lorsque le Licencié a développé un Module Externe les + conditions du Contrat ne s'appliquent pas ŕ ce Module + Externe, qui peut ętre distribué sous un contrat de + licence différent.

+
+
+

5.3.4 CITATIONS

+

Le Licencié qui distribue un Logiciel Modifié s'engage + expressément:

+
    +
  1. ŕ indiquer dans sa documentation qu'il a été réalisé + ŕ partir du Logiciel régi par le Contrat, en + reproduisant les mentions de propriété intellectuelle + du Logiciel,

  2. + +
  3. ŕ faire en sorte que l'utilisation du Logiciel, ses + mentions de propriété intellectuelle et le fait qu'il + est régi par le Contrat soient indiqués dans un texte + facilement accessible depuis l'interface du Logiciel + Modifié,

  4. +
  5. ŕ mentionner, sur un site Web librement accessible + décrivant le Logiciel Modifié, et pendant au moins + toute la durée de sa distribution, qu'il a été réalisé + ŕ partir du Logiciel régi par le Contrat, en + reproduisant les mentions de propriété intellectuelle + du Logiciel,

  6. +
  7. lorsqu'il le distribue ŕ un tiers susceptible de + distribuer lui-męme un Logiciel Modifié, sans avoir ŕ + en distribuer le code source, ŕ faire ses meilleurs + efforts pour que les obligations du présent + article 5.3.4 + soient reprises par le dit tiers.

  8. +
+

Lorsque le Logiciel modifié ou non est distribué avec un + Module Externe qui a été conçu pour l'utiliser, le + Licencié doit soumettre le dit Module Externe aux + obligations précédentes.

+
+
+

5.3.5 COMPATIBILITE + AVEC LES LICENCES CeCILL et CeCILL-C

+

Lorsqu'un Logiciel Modifié contient une Contribution + soumise au contrat de licence CeCILL, les stipulations + prévues ŕ + l'article 5.3.4 + sont facultatives.

+

Un Logiciel Modifié peut ętre distribué sous le contrat + de licence CeCILL-C. Les stipulations prévues ŕ + l'article 5.3.4 + sont alors facultatives.

+
+
+
+
+

Article 6 - PROPRIETE INTELLECTUELLE

+
+

6.1 SUR LE LOGICIEL + INITIAL

+

Le Titulaire est détenteur des droits patrimoniaux sur le + Logiciel Initial. Toute utilisation du Logiciel Initial est + soumise au respect des conditions dans lesquelles le + Titulaire a choisi de diffuser son oeuvre et nul autre n'a + la faculté de modifier les conditions de diffusion de ce + Logiciel Initial.

+

Le Titulaire s'engage ŕ ce que le Logiciel Initial reste au + moins régi par le Contrat et ce, pour la durée visée ŕ + l'article 4.2.

+
+
+

6.2 SUR LES + CONTRIBUTIONS

+

Le Licencié qui a développé une Contribution est titulaire + sur celle-ci des droits de propriété intellectuelle dans les + conditions définies par la législation applicable.

+
+
+

6.3 SUR LES MODULES + EXTERNES

+

Le Licencié qui a développé un Module Externe est + titulaire sur celui-ci des droits de propriété + intellectuelle dans les conditions définies par la + législation applicable et reste libre du choix du contrat + régissant sa diffusion.

+
+
+

6.4 DISPOSITIONS + COMMUNES

+
+

Le Licencié s'engage expressément:

+
    +
  1. ŕ ne pas supprimer ou modifier de quelque maničre + que ce soit les mentions de propriété intellectuelle + apposées sur le Logiciel;

  2. +
  3. ŕ reproduire ŕ l'identique lesdites mentions de + propriété intellectuelle sur les copies du Logiciel + modifié ou non.

  4. +
+
+
+

Le Licencié s'engage ŕ ne pas porter atteinte, + directement ou indirectement, aux droits de propriété + intellectuelle du Titulaire et/ou des Contributeurs sur le + Logiciel et ŕ prendre, le cas échéant, ŕ l'égard de son + personnel toutes les mesures nécessaires pour assurer le + respect des dits droits de propriété intellectuelle du + Titulaire et/ou des Contributeurs.

+
+
+
+
+

Article 7 - SERVICES ASSOCIES

+
+

7.1 Le Contrat n'oblige en + aucun cas le Concédant ŕ la réalisation de prestations + d'assistance technique ou de maintenance du Logiciel.

+

Cependant le Concédant reste libre de proposer ce type de + services. Les termes et conditions d'une telle assistance + technique et/ou d'une telle maintenance seront alors + déterminés dans un acte séparé. Ces actes de maintenance + et/ou assistance technique n'engageront que la seule + responsabilité du Concédant qui les propose.

+
+
+

7.2 De męme, tout Concédant + est libre de proposer, sous sa seule responsabilité, ŕ ses + licenciés une garantie, qui n'engagera que lui, lors de la + redistribution du Logiciel et/ou du Logiciel Modifié et ce, + dans les conditions qu'il souhaite. Cette garantie et les + modalités financičres de son application feront l'objet d'un + acte séparé entre le Concédant et le Licencié.

+
+
+
+

+ Article 8 - + RESPONSABILITE

+
+

8.1 Sous réserve des + dispositions de + l'article 8.2, + le Licencié a la faculté, sous réserve de prouver la faute + du Concédant concerné, de solliciter la réparation du + préjudice direct qu'il subirait du fait du Logiciel et dont + il apportera la preuve.

+
+
+

8.2 + La responsabilité du Concédant est limitée aux engagements + pris en application du Contrat et ne saurait ętre engagée en + raison notamment: (i) des dommages dus ŕ l'inexécution, + totale ou partielle, de ses obligations par le Licencié, + (ii) des dommages directs ou indirects découlant de + l'utilisation ou des performances du Logiciel subis par le + Licencié et (iii) plus généralement d'un quelconque dommage + indirect. En particulier, les Parties conviennent + expressément que tout préjudice financier ou commercial (par + exemple perte de données, perte de bénéfices, perte + d'exploitation, perte de clientčle ou de commandes, manque ŕ + gagner, trouble commercial quelconque) ou toute action + dirigée contre le Licencié par un tiers, constitue un + dommage indirect et n'ouvre pas droit ŕ réparation par le + Concédant.

+
+
+
+

+ Article 9 - GARANTIE

+
+

9.1 Le Licencié reconnaît + que l'état actuel des connaissances scientifiques et + techniques au moment de la mise en circulation du Logiciel + ne permet pas d'en tester et d'en vérifier toutes les + utilisations ni de détecter l'existence d'éventuels défauts. + L'attention du Licencié a été attirée sur ce point sur les + risques associés au chargement, ŕ l'utilisation, la + modification et/ou au développement et ŕ la reproduction du + Logiciel qui sont réservés ŕ des utilisateurs avertis.

+

Il relčve de la responsabilité du Licencié de contrôler, + par tous moyens, l'adéquation du produit ŕ ses besoins, son + bon fonctionnement et de s'assurer qu'il ne causera pas de + dommages aux personnes et aux biens.

+
+
+

9.2 + Le Concédant déclare de bonne foi ętre en droit de concéder + l'ensemble des droits attachés au Logiciel (comprenant + notamment les droits visés ŕ l'article + 5).

+
+
+

9.3 Le Licencié reconnaît + que le Logiciel est fourni "en l'état" par le Concédant sans + autre garantie, expresse ou tacite, que celle prévue ŕ + l'article 9.2 + et notamment sans aucune garantie sur sa valeur commerciale, + son caractčre sécurisé, innovant ou pertinent.

+

En particulier, le Concédant ne garantit pas que le + Logiciel est exempt d'erreur, qu'il fonctionnera sans + interruption, qu'il sera compatible avec l'équipement du + Licencié et sa configuration logicielle ni qu'il remplira + les besoins du Licencié.

+
+
+

9.4 Le Concédant ne garantit + pas, de maničre expresse ou tacite, que le Logiciel ne porte + pas atteinte ŕ un quelconque droit de propriété + intellectuelle d'un tiers portant sur un brevet, un logiciel + ou sur tout autre droit de propriété. Ainsi, le Concédant + exclut toute garantie au profit du Licencié contre les + actions en contrefaçon qui pourraient ętre diligentées au + titre de l'utilisation, de la modification, et de la + redistribution du Logiciel. Néanmoins, si de telles actions + sont exercées contre le Licencié, le Concédant lui apportera + son aide technique et juridique pour sa défense. Cette aide + technique et juridique est déterminée au cas par cas entre + le Concédant concerné et le Licencié dans le cadre d'un + protocole d'accord. Le Concédant dégage toute responsabilité + quant ŕ l'utilisation de la dénomination du Logiciel par le + Licencié. Aucune garantie n'est apportée quant ŕ l'existence + de droits antérieurs sur le nom du Logiciel et sur + l'existence d'une marque.

+
+
+
+

Article 10 - + RESILIATION

+
+

10.1 En cas de manquement + par le Licencié aux obligations mises ŕ sa charge par le + Contrat, le Concédant pourra résilier de plein droit le + Contrat trente (30) jours aprčs notification adressée au + Licencié et restée sans effet.

+
+
+

10.2 Le Licencié dont le + Contrat est résilié n'est plus autorisé ŕ utiliser, modifier + ou distribuer le Logiciel. Cependant, toutes les licences + qu'il aura concédées antérieurement ŕ la résiliation du + Contrat resteront valides sous réserve qu'elles aient été + effectuées en conformité avec le Contrat.

+
+
+
+

Article 11 - DISPOSITIONS + DIVERSES

+
+

+ 11.1 CAUSE EXTERIEURE

+

Aucune + des Parties ne sera responsable d'un retard ou d'une + défaillance d'exécution du Contrat qui serait dű + ŕ un cas de force majeure, un cas fortuit ou une cause + extérieure, telle que, notamment, le mauvais fonctionnement + ou les interruptions du réseau électrique ou de + télécommunication, la paralysie du réseau liée + ŕ une attaque informatique, l'intervention des + autorités gouvernementales, les catastrophes naturelles, les + dégâts des eaux, les tremblements de terre, le feu, les + explosions, les grčves et les conflits sociaux, l'état + de guerre...

+
+
+

11.2 Le + fait, par l'une ou l'autre des Parties, d'omettre + en une ou plusieurs occasions de se prévaloir d'une ou + plusieurs dispositions du Contrat, ne pourra en aucun cas impliquer + renonciation par la Partie intéressée ŕ s'en + prévaloir ultérieurement.

+
+
+

11.3 Le Contrat annule et + remplace toute convention antérieure, écrite ou orale, entre + les Parties sur le męme objet et constitue l'accord entier + entre les Parties sur cet objet. Aucune addition ou + modification aux termes du Contrat n'aura d'effet ŕ l'égard + des Parties ŕ moins d'ętre faite par écrit et signée par + leurs représentants dűment habilités.

+
+
+

11.4 Dans l'hypothčse oů une + ou plusieurs des dispositions du Contrat s'avčrerait + contraire ŕ une loi ou ŕ un texte applicable, existants ou + futurs, cette loi ou ce texte prévaudrait, et les Parties + feraient les amendements nécessaires pour se conformer ŕ + cette loi ou ŕ ce texte. Toutes les autres dispositions + resteront en vigueur. De męme, la nullité, pour quelque + raison que ce soit, d'une des dispositions du Contrat ne + saurait entraîner la nullité de l'ensemble du Contrat.

+
+
+

+ 11.5 LANGUE

+

Le Contrat est rédigé en langue française et en langue + anglaise, ces deux versions faisant également foi. +

+
+
+
+

Article 12 - NOUVELLES + VERSIONS DU CONTRAT

+
+

12.1 Toute personne est + autorisée ŕ copier et distribuer des copies de ce + Contrat.

+
+
+

12.2 Afin d'en préserver la + cohérence, le texte du Contrat est protégé et ne peut ętre + modifié que par les auteurs de la licence, lesquels se + réservent le droit de publier périodiquement des mises ŕ + jour ou de nouvelles versions du Contrat, qui posséderont + chacune un numéro distinct. Ces versions ultérieures seront + susceptibles de prendre en compte de nouvelles + problématiques rencontrées par les logiciels libres.

+
+
+

12.3 Tout Logiciel diffusé + sous une version donnée du Contrat ne pourra faire l'objet + d'une diffusion ultérieure que sous la męme version du + Contrat ou une version postérieure.

+
+
+
+

Article 13 - LOI APPLICABLE + ET COMPETENCE TERRITORIALE

+
+

13.1 Le Contrat est régi + par la loi française. Les Parties conviennent de tenter de + régler ŕ l'amiable les différends ou litiges qui viendraient + ŕ se produire par suite ou ŕ l'occasion du Contrat. +

+
+
+

13.2 A défaut d'accord + amiable dans un délai de deux (2) mois ŕ compter de leur + survenance et sauf situation relevant d'une procédure + d'urgence, les différends ou litiges seront portés par la + Partie la plus diligente devant les Tribunaux compétents de + Paris.

+
+
+ +
Version 1.0 du 2006-09-05.
+ + diff --git a/data/texts/ChatLicence.html b/data/texts/ChatLicence.html new file mode 100644 index 0000000..b6f1e61 --- /dev/null +++ b/data/texts/ChatLicence.html @@ -0,0 +1,653 @@ + + + + + CONTRAT DE LICENCE DE LOGICIEL LIBRE CeCILL-B + + + + +

CONTRAT DE LICENCE DE LOGICIEL LIBRE CeCILL-B

+ +
+

Avertissement

+ +

Ce contrat est une licence de logiciel libre issue d'une + concertation entre ses auteurs afin que le respect de deux + grands principes préside ŕ sa rédaction:

+
    +
  • d'une part, le respect des principes de diffusion des + logiciels libres: accčs au code source, droits étendus + conférés aux utilisateurs,
  • +
  • d'autre part, la désignation d'un droit applicable, le + droit français, auquel elle est conforme, tant au regard du + droit de la responsabilité civile que du droit de la + propriété intellectuelle et de la protection qu'il offre aux + auteurs et titulaires des droits patrimoniaux sur un + logiciel.
  • +
+ +

Les auteurs de la licence + CeCILL-B1 sont:

+ +

Commissariat ŕ l'Energie Atomique - CEA, établissement public + de recherche ŕ caractčre scientifique, technique et + industriel, dont le sičge est situé 25 rue Leblanc, immeuble + Le Ponant D, 75015 Paris.

+

Centre National de la Recherche Scientifique - CNRS, + établissement public ŕ caractčre scientifique et + technologique, dont le sičge est situé 3 rue Michel-Ange, + 75794 Paris cedex 16.

+ +

Institut National de Recherche en Informatique et en + Automatique - INRIA, établissement public ŕ caractčre + scientifique et technologique, dont le sičge est situé Domaine + de Voluceau, Rocquencourt, BP 105, 78153 Le Chesnay cedex.

+ +
+
+

Préambule

+

Ce contrat est une licence de logiciel libre dont l'objectif + est de conférer aux utilisateurs une trčs large liberté de + modification et de redistribution du logiciel régi par cette + licence.

+

L'exercice de cette liberté est assorti d'une obligation + forte de citation ŕ la charge de ceux qui distribueraient un + logiciel incorporant un logiciel régi par la présente licence + afin d'assurer que les contributions de tous soient + correctement identifiées et reconnues.

+

L'accessibilité au code source et les droits de copie, de + modification et de redistribution qui découlent de ce contrat + ont pour contrepartie de n'offrir aux utilisateurs qu'une + garantie limitée et de ne faire peser sur l'auteur du + logiciel, le titulaire des droits patrimoniaux et les + concédants successifs qu'une responsabilité restreinte.

+

A cet égard l'attention de l'utilisateur est attirée sur les + risques associés au chargement, ŕ l'utilisation, ŕ la + modification et/ou au développement et ŕ la reproduction du + logiciel par l'utilisateur étant donné sa spécificité de + logiciel libre, qui peut le rendre complexe ŕ manipuler et qui + le réserve donc ŕ des développeurs ou des professionnels + avertis possédant des connaissances informatiques + approfondies. Les utilisateurs sont donc invités ŕ charger et + tester l'adéquation du logiciel ŕ leurs besoins dans des + conditions permettant d'assurer la sécurité de leurs systčmes + et/ou de leurs données et, plus généralement, ŕ l'utiliser et + l'exploiter dans les męmes conditions de sécurité. Ce contrat + peut ętre reproduit et diffusé librement, sous réserve de le + conserver en l'état, sans ajout ni suppression de + clauses.

+

Ce contrat est susceptible de s'appliquer ŕ tout logiciel + dont le titulaire des droits patrimoniaux décide de soumettre + l'exploitation aux dispositions qu'il contient.

+ +
+
+

Article 1 - DEFINITIONS

+

Dans ce contrat, les termes suivants, lorsqu'ils seront + écrits avec une lettre capitale, auront la signification + suivante:

+

Contrat: + désigne le présent contrat de licence, ses éventuelles + versions postérieures et annexes.

+

Logiciel: + désigne le logiciel sous sa forme de Code Objet et/ou de Code + Source et le cas échéant sa documentation, dans leur état au + moment de l'acceptation du Contrat par le Licencié.

+

Logiciel + Initial: désigne le Logiciel sous sa forme de Code + Source et éventuellement de Code Objet et le cas échéant sa + documentation, dans leur état au moment de leur premičre + diffusion sous les termes du Contrat.

+

Logiciel + Modifié: désigne le Logiciel modifié par au moins une + Contribution.

+

Code + Source: désigne l'ensemble des instructions et des + lignes de programme du Logiciel et auquel l'accčs est + nécessaire en vue de modifier le Logiciel.

+

Code + Objet: désigne les fichiers binaires issus de la + compilation du Code Source.

+

Titulaire: + désigne le ou les détenteurs des droits patrimoniaux d'auteur + sur le Logiciel Initial.

+

Licencié: + désigne le ou les utilisateurs du Logiciel ayant accepté le + Contrat.

+

Contributeur: + désigne le Licencié auteur d'au moins une Contribution.

+

Concédant: + désigne le Titulaire ou toute personne physique ou morale + distribuant le Logiciel sous le Contrat.

+

Contribution: + désigne l'ensemble des modifications, corrections, + traductions, adaptations et/ou nouvelles fonctionnalités + intégrées dans le Logiciel par tout Contributeur, ainsi que + tout Module Interne.

+

Module: + désigne un ensemble de fichiers sources y compris leur + documentation qui permet de réaliser des fonctionnalités ou + services supplémentaires ŕ ceux fournis par le Logiciel.

+

Module + Externe: désigne tout Module, non dérivé du Logiciel, tel + que ce Module et le Logiciel s'exécutent dans des espaces + d'adressage différents, l'un appelant l'autre au moment de leur + exécution.

+

Module + Interne: désigne tout Module lié au Logiciel de telle + sorte qu'ils s'exécutent dans le męme espace + d'adressage.

+

Parties: + désigne collectivement le Licencié et le Concédant.

+

Ces termes s'entendent au singulier comme au pluriel.

+
+
+

Article 2 - OBJET

+

Le Contrat a pour objet la concession par le Concédant au + Licencié d'une licence non exclusive, cessible et mondiale du + Logiciel telle que définie ci-aprčs ŕ + l'article 5 + pour toute la durée de protection des droits portant sur ce + Logiciel.

+
+
+

Article 3 - ACCEPTATION

+
+

3.1 + L'acceptation par le Licencié des termes du Contrat est + réputée acquise du fait du premier des faits suivants:

+
    +
  • (i) le chargement du Logiciel par tout moyen notamment + par téléchargement ŕ partir d'un serveur distant ou par + chargement ŕ partir d'un support physique;
  • +
  • (ii) le premier exercice par le Licencié de l'un + quelconque des droits concédés par le Contrat.
  • +
+
+
+

3.2 Un exemplaire du + Contrat, contenant notamment un avertissement relatif aux + spécificités du Logiciel, ŕ la restriction de garantie et ŕ + la limitation ŕ un usage par des utilisateurs expérimentés a + été mis ŕ disposition du Licencié préalablement ŕ son + acceptation telle que définie ŕ + l'article 3.1 + ci dessus et le Licencié reconnaît en avoir pris + connaissance.

+
+
+
+

Article 4 - ENTREE EN VIGUEUR ET DUREE

+
+

4.1 ENTREE EN VIGUEUR

+

Le Contrat entre en vigueur ŕ la date de son acceptation + par le Licencié telle que définie + en 3.1.

+
+
+

4.2 DUREE

+

Le Contrat produira ses effets pendant toute la durée + légale de protection des droits patrimoniaux portant sur le + Logiciel.

+
+
+
+

+ Article 5 - ETENDUE DES DROITS + CONCEDES

+

Le Concédant concčde au Licencié, qui accepte, les droits + suivants sur le Logiciel pour toutes destinations et pour la + durée du Contrat dans les conditions ci-aprčs + détaillées.

+

Par ailleurs, si le Concédant détient ou venait ŕ détenir un + ou plusieurs brevets d'invention protégeant tout ou partie des + fonctionnalités du Logiciel ou de ses composants, il s'engage + ŕ ne pas opposer les éventuels droits conférés par ces brevets + aux Licenciés successifs qui utiliseraient, exploiteraient ou + modifieraient le Logiciel. En cas de cession de ces brevets, + le Concédant s'engage ŕ faire reprendre les obligations du + présent alinéa aux cessionnaires.

+
+

5.1 DROIT + D'UTILISATION

+ +

Le Licencié est autorisé ŕ utiliser le Logiciel, sans + restriction quant aux domaines d'application, étant ci-aprčs + précisé que cela comporte:

+
    +
  1. la reproduction permanente ou provisoire du Logiciel + en tout ou partie par tout moyen et sous toute + forme.

  2. +
  3. le chargement, l'affichage, l'exécution, ou le + stockage du Logiciel sur tout support.

  4. +
  5. la possibilité d'en observer, d'en étudier, ou d'en + tester le fonctionnement afin de déterminer les idées et + principes qui sont ŕ la base de n'importe quel élément + de ce Logiciel; et ceci, lorsque le Licencié effectue + toute opération de chargement, d'affichage, d'exécution, + de transmission ou de stockage du Logiciel qu'il est en + droit d'effectuer en vertu du Contrat.

  6. +
+
+
+

5.2 DROIT D'APPORTER DES + CONTRIBUTIONS

+

Le droit d'apporter des Contributions comporte le droit de + traduire, d'adapter, d'arranger ou d'apporter toute autre + modification au Logiciel et le droit de reproduire le + logiciel en résultant.

+

Le Licencié est autorisé ŕ apporter toute Contribution au + Logiciel sous réserve de mentionner, de façon explicite, son + nom en tant qu'auteur de cette Contribution et la date de + création de celle-ci.

+
+
+

5.3 DROIT DE + DISTRIBUTION

+

Le droit de distribution comporte notamment le droit de + diffuser, de transmettre et de communiquer le Logiciel au + public sur tout support et par tout moyen ainsi que le droit + de mettre sur le marché ŕ titre onéreux ou gratuit, un ou + des exemplaires du Logiciel par tout procédé.

+

Le Licencié est autorisé ŕ distribuer des copies du + Logiciel, modifié ou non, ŕ des tiers dans les conditions + ci-aprčs détaillées.

+
+

5.3.1 DISTRIBUTION DU + LOGICIEL SANS MODIFICATION

+

Le Licencié est autorisé ŕ distribuer des copies + conformes du Logiciel, sous forme de Code Source ou de + Code Objet, ŕ condition que cette distribution respecte + les dispositions du Contrat dans leur totalité et soit + accompagnée:

+
    +
  1. d'un exemplaire du Contrat,

  2. +
  3. d'un avertissement relatif ŕ la restriction de + garantie et de responsabilité du Concédant telle que + prévue aux + articles 8 + et 9,

  4. +
+

et que, dans le cas oů seul le Code Objet du Logiciel est + redistribué, le Licencié permette un accčs effectif au + Code Source complet du Logiciel pendant au moins toute la + durée de sa distribution du Logiciel, étant entendu que le + coűt additionnel d'acquisition du Code Source ne devra pas + excéder le simple coűt de transfert des données.

+
+
+

+ 5.3.2 DISTRIBUTION DU LOGICIEL MODIFIE

+

Lorsque le Licencié apporte une Contribution au Logiciel, + le Logiciel Modifié peut ętre distribué sous un contrat de + licence autre que le présent Contrat sous réserve du + respect des dispositions de l'article + 5.3.4.

+
+
+

5.3.3 DISTRIBUTION DES + MODULES EXTERNES

+

Lorsque le Licencié a développé un Module Externe les + conditions du Contrat ne s'appliquent pas ŕ ce Module + Externe, qui peut ętre distribué sous un contrat de + licence différent.

+
+
+

5.3.4 CITATIONS

+

Le Licencié qui distribue un Logiciel Modifié s'engage + expressément:

+
    +
  1. ŕ indiquer dans sa documentation qu'il a été réalisé + ŕ partir du Logiciel régi par le Contrat, en + reproduisant les mentions de propriété intellectuelle + du Logiciel,

  2. + +
  3. ŕ faire en sorte que l'utilisation du Logiciel, ses + mentions de propriété intellectuelle et le fait qu'il + est régi par le Contrat soient indiqués dans un texte + facilement accessible depuis l'interface du Logiciel + Modifié,

  4. +
  5. ŕ mentionner, sur un site Web librement accessible + décrivant le Logiciel Modifié, et pendant au moins + toute la durée de sa distribution, qu'il a été réalisé + ŕ partir du Logiciel régi par le Contrat, en + reproduisant les mentions de propriété intellectuelle + du Logiciel,

  6. +
  7. lorsqu'il le distribue ŕ un tiers susceptible de + distribuer lui-męme un Logiciel Modifié, sans avoir ŕ + en distribuer le code source, ŕ faire ses meilleurs + efforts pour que les obligations du présent + article 5.3.4 + soient reprises par le dit tiers.

  8. +
+

Lorsque le Logiciel modifié ou non est distribué avec un + Module Externe qui a été conçu pour l'utiliser, le + Licencié doit soumettre le dit Module Externe aux + obligations précédentes.

+
+
+

5.3.5 COMPATIBILITE + AVEC LES LICENCES CeCILL et CeCILL-C

+

Lorsqu'un Logiciel Modifié contient une Contribution + soumise au contrat de licence CeCILL, les stipulations + prévues ŕ + l'article 5.3.4 + sont facultatives.

+

Un Logiciel Modifié peut ętre distribué sous le contrat + de licence CeCILL-C. Les stipulations prévues ŕ + l'article 5.3.4 + sont alors facultatives.

+
+
+
+
+

Article 6 - PROPRIETE INTELLECTUELLE

+
+

6.1 SUR LE LOGICIEL + INITIAL

+

Le Titulaire est détenteur des droits patrimoniaux sur le + Logiciel Initial. Toute utilisation du Logiciel Initial est + soumise au respect des conditions dans lesquelles le + Titulaire a choisi de diffuser son oeuvre et nul autre n'a + la faculté de modifier les conditions de diffusion de ce + Logiciel Initial.

+

Le Titulaire s'engage ŕ ce que le Logiciel Initial reste au + moins régi par le Contrat et ce, pour la durée visée ŕ + l'article 4.2.

+
+
+

6.2 SUR LES + CONTRIBUTIONS

+

Le Licencié qui a développé une Contribution est titulaire + sur celle-ci des droits de propriété intellectuelle dans les + conditions définies par la législation applicable.

+
+
+

6.3 SUR LES MODULES + EXTERNES

+

Le Licencié qui a développé un Module Externe est + titulaire sur celui-ci des droits de propriété + intellectuelle dans les conditions définies par la + législation applicable et reste libre du choix du contrat + régissant sa diffusion.

+
+
+

6.4 DISPOSITIONS + COMMUNES

+
+

Le Licencié s'engage expressément:

+
    +
  1. ŕ ne pas supprimer ou modifier de quelque maničre + que ce soit les mentions de propriété intellectuelle + apposées sur le Logiciel;

  2. +
  3. ŕ reproduire ŕ l'identique lesdites mentions de + propriété intellectuelle sur les copies du Logiciel + modifié ou non.

  4. +
+
+
+

Le Licencié s'engage ŕ ne pas porter atteinte, + directement ou indirectement, aux droits de propriété + intellectuelle du Titulaire et/ou des Contributeurs sur le + Logiciel et ŕ prendre, le cas échéant, ŕ l'égard de son + personnel toutes les mesures nécessaires pour assurer le + respect des dits droits de propriété intellectuelle du + Titulaire et/ou des Contributeurs.

+
+
+
+
+

Article 7 - SERVICES ASSOCIES

+
+

7.1 Le Contrat n'oblige en + aucun cas le Concédant ŕ la réalisation de prestations + d'assistance technique ou de maintenance du Logiciel.

+

Cependant le Concédant reste libre de proposer ce type de + services. Les termes et conditions d'une telle assistance + technique et/ou d'une telle maintenance seront alors + déterminés dans un acte séparé. Ces actes de maintenance + et/ou assistance technique n'engageront que la seule + responsabilité du Concédant qui les propose.

+
+
+

7.2 De męme, tout Concédant + est libre de proposer, sous sa seule responsabilité, ŕ ses + licenciés une garantie, qui n'engagera que lui, lors de la + redistribution du Logiciel et/ou du Logiciel Modifié et ce, + dans les conditions qu'il souhaite. Cette garantie et les + modalités financičres de son application feront l'objet d'un + acte séparé entre le Concédant et le Licencié.

+
+
+
+

+ Article 8 - + RESPONSABILITE

+
+

8.1 Sous réserve des + dispositions de + l'article 8.2, + le Licencié a la faculté, sous réserve de prouver la faute + du Concédant concerné, de solliciter la réparation du + préjudice direct qu'il subirait du fait du Logiciel et dont + il apportera la preuve.

+
+
+

8.2 + La responsabilité du Concédant est limitée aux engagements + pris en application du Contrat et ne saurait ętre engagée en + raison notamment: (i) des dommages dus ŕ l'inexécution, + totale ou partielle, de ses obligations par le Licencié, + (ii) des dommages directs ou indirects découlant de + l'utilisation ou des performances du Logiciel subis par le + Licencié et (iii) plus généralement d'un quelconque dommage + indirect. En particulier, les Parties conviennent + expressément que tout préjudice financier ou commercial (par + exemple perte de données, perte de bénéfices, perte + d'exploitation, perte de clientčle ou de commandes, manque ŕ + gagner, trouble commercial quelconque) ou toute action + dirigée contre le Licencié par un tiers, constitue un + dommage indirect et n'ouvre pas droit ŕ réparation par le + Concédant.

+
+
+
+

+ Article 9 - GARANTIE

+
+

9.1 Le Licencié reconnaît + que l'état actuel des connaissances scientifiques et + techniques au moment de la mise en circulation du Logiciel + ne permet pas d'en tester et d'en vérifier toutes les + utilisations ni de détecter l'existence d'éventuels défauts. + L'attention du Licencié a été attirée sur ce point sur les + risques associés au chargement, ŕ l'utilisation, la + modification et/ou au développement et ŕ la reproduction du + Logiciel qui sont réservés ŕ des utilisateurs avertis.

+

Il relčve de la responsabilité du Licencié de contrôler, + par tous moyens, l'adéquation du produit ŕ ses besoins, son + bon fonctionnement et de s'assurer qu'il ne causera pas de + dommages aux personnes et aux biens.

+
+
+

9.2 + Le Concédant déclare de bonne foi ętre en droit de concéder + l'ensemble des droits attachés au Logiciel (comprenant + notamment les droits visés ŕ l'article + 5).

+
+
+

9.3 Le Licencié reconnaît + que le Logiciel est fourni "en l'état" par le Concédant sans + autre garantie, expresse ou tacite, que celle prévue ŕ + l'article 9.2 + et notamment sans aucune garantie sur sa valeur commerciale, + son caractčre sécurisé, innovant ou pertinent.

+

En particulier, le Concédant ne garantit pas que le + Logiciel est exempt d'erreur, qu'il fonctionnera sans + interruption, qu'il sera compatible avec l'équipement du + Licencié et sa configuration logicielle ni qu'il remplira + les besoins du Licencié.

+
+
+

9.4 Le Concédant ne garantit + pas, de maničre expresse ou tacite, que le Logiciel ne porte + pas atteinte ŕ un quelconque droit de propriété + intellectuelle d'un tiers portant sur un brevet, un logiciel + ou sur tout autre droit de propriété. Ainsi, le Concédant + exclut toute garantie au profit du Licencié contre les + actions en contrefaçon qui pourraient ętre diligentées au + titre de l'utilisation, de la modification, et de la + redistribution du Logiciel. Néanmoins, si de telles actions + sont exercées contre le Licencié, le Concédant lui apportera + son aide technique et juridique pour sa défense. Cette aide + technique et juridique est déterminée au cas par cas entre + le Concédant concerné et le Licencié dans le cadre d'un + protocole d'accord. Le Concédant dégage toute responsabilité + quant ŕ l'utilisation de la dénomination du Logiciel par le + Licencié. Aucune garantie n'est apportée quant ŕ l'existence + de droits antérieurs sur le nom du Logiciel et sur + l'existence d'une marque.

+
+
+
+

Article 10 - + RESILIATION

+
+

10.1 En cas de manquement + par le Licencié aux obligations mises ŕ sa charge par le + Contrat, le Concédant pourra résilier de plein droit le + Contrat trente (30) jours aprčs notification adressée au + Licencié et restée sans effet.

+
+
+

10.2 Le Licencié dont le + Contrat est résilié n'est plus autorisé ŕ utiliser, modifier + ou distribuer le Logiciel. Cependant, toutes les licences + qu'il aura concédées antérieurement ŕ la résiliation du + Contrat resteront valides sous réserve qu'elles aient été + effectuées en conformité avec le Contrat.

+
+
+
+

Article 11 - DISPOSITIONS + DIVERSES

+
+

+ 11.1 CAUSE EXTERIEURE

+

Aucune + des Parties ne sera responsable d'un retard ou d'une + défaillance d'exécution du Contrat qui serait dű + ŕ un cas de force majeure, un cas fortuit ou une cause + extérieure, telle que, notamment, le mauvais fonctionnement + ou les interruptions du réseau électrique ou de + télécommunication, la paralysie du réseau liée + ŕ une attaque informatique, l'intervention des + autorités gouvernementales, les catastrophes naturelles, les + dégâts des eaux, les tremblements de terre, le feu, les + explosions, les grčves et les conflits sociaux, l'état + de guerre...

+
+
+

11.2 Le + fait, par l'une ou l'autre des Parties, d'omettre + en une ou plusieurs occasions de se prévaloir d'une ou + plusieurs dispositions du Contrat, ne pourra en aucun cas impliquer + renonciation par la Partie intéressée ŕ s'en + prévaloir ultérieurement.

+
+
+

11.3 Le Contrat annule et + remplace toute convention antérieure, écrite ou orale, entre + les Parties sur le męme objet et constitue l'accord entier + entre les Parties sur cet objet. Aucune addition ou + modification aux termes du Contrat n'aura d'effet ŕ l'égard + des Parties ŕ moins d'ętre faite par écrit et signée par + leurs représentants dűment habilités.

+
+
+

11.4 Dans l'hypothčse oů une + ou plusieurs des dispositions du Contrat s'avčrerait + contraire ŕ une loi ou ŕ un texte applicable, existants ou + futurs, cette loi ou ce texte prévaudrait, et les Parties + feraient les amendements nécessaires pour se conformer ŕ + cette loi ou ŕ ce texte. Toutes les autres dispositions + resteront en vigueur. De męme, la nullité, pour quelque + raison que ce soit, d'une des dispositions du Contrat ne + saurait entraîner la nullité de l'ensemble du Contrat.

+
+
+

+ 11.5 LANGUE

+

Le Contrat est rédigé en langue française et en langue + anglaise, ces deux versions faisant également foi. +

+
+
+
+

Article 12 - NOUVELLES + VERSIONS DU CONTRAT

+
+

12.1 Toute personne est + autorisée ŕ copier et distribuer des copies de ce + Contrat.

+
+
+

12.2 Afin d'en préserver la + cohérence, le texte du Contrat est protégé et ne peut ętre + modifié que par les auteurs de la licence, lesquels se + réservent le droit de publier périodiquement des mises ŕ + jour ou de nouvelles versions du Contrat, qui posséderont + chacune un numéro distinct. Ces versions ultérieures seront + susceptibles de prendre en compte de nouvelles + problématiques rencontrées par les logiciels libres.

+
+
+

12.3 Tout Logiciel diffusé + sous une version donnée du Contrat ne pourra faire l'objet + d'une diffusion ultérieure que sous la męme version du + Contrat ou une version postérieure.

+
+
+
+

Article 13 - LOI APPLICABLE + ET COMPETENCE TERRITORIALE

+
+

13.1 Le Contrat est régi + par la loi française. Les Parties conviennent de tenter de + régler ŕ l'amiable les différends ou litiges qui viendraient + ŕ se produire par suite ou ŕ l'occasion du Contrat. +

+
+
+

13.2 A défaut d'accord + amiable dans un délai de deux (2) mois ŕ compter de leur + survenance et sauf situation relevant d'une procédure + d'urgence, les différends ou litiges seront portés par la + Partie la plus diligente devant les Tribunaux compétents de + Paris.

+
+
+ +
Version 1.0 du 2006-09-05.
+ + diff --git a/data/texts/LoginLicence.html b/data/texts/LoginLicence.html new file mode 100644 index 0000000..b6f1e61 --- /dev/null +++ b/data/texts/LoginLicence.html @@ -0,0 +1,653 @@ + + + + + CONTRAT DE LICENCE DE LOGICIEL LIBRE CeCILL-B + + + + +

CONTRAT DE LICENCE DE LOGICIEL LIBRE CeCILL-B

+ +
+

Avertissement

+ +

Ce contrat est une licence de logiciel libre issue d'une + concertation entre ses auteurs afin que le respect de deux + grands principes préside ŕ sa rédaction:

+
    +
  • d'une part, le respect des principes de diffusion des + logiciels libres: accčs au code source, droits étendus + conférés aux utilisateurs,
  • +
  • d'autre part, la désignation d'un droit applicable, le + droit français, auquel elle est conforme, tant au regard du + droit de la responsabilité civile que du droit de la + propriété intellectuelle et de la protection qu'il offre aux + auteurs et titulaires des droits patrimoniaux sur un + logiciel.
  • +
+ +

Les auteurs de la licence + CeCILL-B1 sont:

+ +

Commissariat ŕ l'Energie Atomique - CEA, établissement public + de recherche ŕ caractčre scientifique, technique et + industriel, dont le sičge est situé 25 rue Leblanc, immeuble + Le Ponant D, 75015 Paris.

+

Centre National de la Recherche Scientifique - CNRS, + établissement public ŕ caractčre scientifique et + technologique, dont le sičge est situé 3 rue Michel-Ange, + 75794 Paris cedex 16.

+ +

Institut National de Recherche en Informatique et en + Automatique - INRIA, établissement public ŕ caractčre + scientifique et technologique, dont le sičge est situé Domaine + de Voluceau, Rocquencourt, BP 105, 78153 Le Chesnay cedex.

+ +
+
+

Préambule

+

Ce contrat est une licence de logiciel libre dont l'objectif + est de conférer aux utilisateurs une trčs large liberté de + modification et de redistribution du logiciel régi par cette + licence.

+

L'exercice de cette liberté est assorti d'une obligation + forte de citation ŕ la charge de ceux qui distribueraient un + logiciel incorporant un logiciel régi par la présente licence + afin d'assurer que les contributions de tous soient + correctement identifiées et reconnues.

+

L'accessibilité au code source et les droits de copie, de + modification et de redistribution qui découlent de ce contrat + ont pour contrepartie de n'offrir aux utilisateurs qu'une + garantie limitée et de ne faire peser sur l'auteur du + logiciel, le titulaire des droits patrimoniaux et les + concédants successifs qu'une responsabilité restreinte.

+

A cet égard l'attention de l'utilisateur est attirée sur les + risques associés au chargement, ŕ l'utilisation, ŕ la + modification et/ou au développement et ŕ la reproduction du + logiciel par l'utilisateur étant donné sa spécificité de + logiciel libre, qui peut le rendre complexe ŕ manipuler et qui + le réserve donc ŕ des développeurs ou des professionnels + avertis possédant des connaissances informatiques + approfondies. Les utilisateurs sont donc invités ŕ charger et + tester l'adéquation du logiciel ŕ leurs besoins dans des + conditions permettant d'assurer la sécurité de leurs systčmes + et/ou de leurs données et, plus généralement, ŕ l'utiliser et + l'exploiter dans les męmes conditions de sécurité. Ce contrat + peut ętre reproduit et diffusé librement, sous réserve de le + conserver en l'état, sans ajout ni suppression de + clauses.

+

Ce contrat est susceptible de s'appliquer ŕ tout logiciel + dont le titulaire des droits patrimoniaux décide de soumettre + l'exploitation aux dispositions qu'il contient.

+ +
+
+

Article 1 - DEFINITIONS

+

Dans ce contrat, les termes suivants, lorsqu'ils seront + écrits avec une lettre capitale, auront la signification + suivante:

+

Contrat: + désigne le présent contrat de licence, ses éventuelles + versions postérieures et annexes.

+

Logiciel: + désigne le logiciel sous sa forme de Code Objet et/ou de Code + Source et le cas échéant sa documentation, dans leur état au + moment de l'acceptation du Contrat par le Licencié.

+

Logiciel + Initial: désigne le Logiciel sous sa forme de Code + Source et éventuellement de Code Objet et le cas échéant sa + documentation, dans leur état au moment de leur premičre + diffusion sous les termes du Contrat.

+

Logiciel + Modifié: désigne le Logiciel modifié par au moins une + Contribution.

+

Code + Source: désigne l'ensemble des instructions et des + lignes de programme du Logiciel et auquel l'accčs est + nécessaire en vue de modifier le Logiciel.

+

Code + Objet: désigne les fichiers binaires issus de la + compilation du Code Source.

+

Titulaire: + désigne le ou les détenteurs des droits patrimoniaux d'auteur + sur le Logiciel Initial.

+

Licencié: + désigne le ou les utilisateurs du Logiciel ayant accepté le + Contrat.

+

Contributeur: + désigne le Licencié auteur d'au moins une Contribution.

+

Concédant: + désigne le Titulaire ou toute personne physique ou morale + distribuant le Logiciel sous le Contrat.

+

Contribution: + désigne l'ensemble des modifications, corrections, + traductions, adaptations et/ou nouvelles fonctionnalités + intégrées dans le Logiciel par tout Contributeur, ainsi que + tout Module Interne.

+

Module: + désigne un ensemble de fichiers sources y compris leur + documentation qui permet de réaliser des fonctionnalités ou + services supplémentaires ŕ ceux fournis par le Logiciel.

+

Module + Externe: désigne tout Module, non dérivé du Logiciel, tel + que ce Module et le Logiciel s'exécutent dans des espaces + d'adressage différents, l'un appelant l'autre au moment de leur + exécution.

+

Module + Interne: désigne tout Module lié au Logiciel de telle + sorte qu'ils s'exécutent dans le męme espace + d'adressage.

+

Parties: + désigne collectivement le Licencié et le Concédant.

+

Ces termes s'entendent au singulier comme au pluriel.

+
+
+

Article 2 - OBJET

+

Le Contrat a pour objet la concession par le Concédant au + Licencié d'une licence non exclusive, cessible et mondiale du + Logiciel telle que définie ci-aprčs ŕ + l'article 5 + pour toute la durée de protection des droits portant sur ce + Logiciel.

+
+
+

Article 3 - ACCEPTATION

+
+

3.1 + L'acceptation par le Licencié des termes du Contrat est + réputée acquise du fait du premier des faits suivants:

+
    +
  • (i) le chargement du Logiciel par tout moyen notamment + par téléchargement ŕ partir d'un serveur distant ou par + chargement ŕ partir d'un support physique;
  • +
  • (ii) le premier exercice par le Licencié de l'un + quelconque des droits concédés par le Contrat.
  • +
+
+
+

3.2 Un exemplaire du + Contrat, contenant notamment un avertissement relatif aux + spécificités du Logiciel, ŕ la restriction de garantie et ŕ + la limitation ŕ un usage par des utilisateurs expérimentés a + été mis ŕ disposition du Licencié préalablement ŕ son + acceptation telle que définie ŕ + l'article 3.1 + ci dessus et le Licencié reconnaît en avoir pris + connaissance.

+
+
+
+

Article 4 - ENTREE EN VIGUEUR ET DUREE

+
+

4.1 ENTREE EN VIGUEUR

+

Le Contrat entre en vigueur ŕ la date de son acceptation + par le Licencié telle que définie + en 3.1.

+
+
+

4.2 DUREE

+

Le Contrat produira ses effets pendant toute la durée + légale de protection des droits patrimoniaux portant sur le + Logiciel.

+
+
+
+

+ Article 5 - ETENDUE DES DROITS + CONCEDES

+

Le Concédant concčde au Licencié, qui accepte, les droits + suivants sur le Logiciel pour toutes destinations et pour la + durée du Contrat dans les conditions ci-aprčs + détaillées.

+

Par ailleurs, si le Concédant détient ou venait ŕ détenir un + ou plusieurs brevets d'invention protégeant tout ou partie des + fonctionnalités du Logiciel ou de ses composants, il s'engage + ŕ ne pas opposer les éventuels droits conférés par ces brevets + aux Licenciés successifs qui utiliseraient, exploiteraient ou + modifieraient le Logiciel. En cas de cession de ces brevets, + le Concédant s'engage ŕ faire reprendre les obligations du + présent alinéa aux cessionnaires.

+
+

5.1 DROIT + D'UTILISATION

+ +

Le Licencié est autorisé ŕ utiliser le Logiciel, sans + restriction quant aux domaines d'application, étant ci-aprčs + précisé que cela comporte:

+
    +
  1. la reproduction permanente ou provisoire du Logiciel + en tout ou partie par tout moyen et sous toute + forme.

  2. +
  3. le chargement, l'affichage, l'exécution, ou le + stockage du Logiciel sur tout support.

  4. +
  5. la possibilité d'en observer, d'en étudier, ou d'en + tester le fonctionnement afin de déterminer les idées et + principes qui sont ŕ la base de n'importe quel élément + de ce Logiciel; et ceci, lorsque le Licencié effectue + toute opération de chargement, d'affichage, d'exécution, + de transmission ou de stockage du Logiciel qu'il est en + droit d'effectuer en vertu du Contrat.

  6. +
+
+
+

5.2 DROIT D'APPORTER DES + CONTRIBUTIONS

+

Le droit d'apporter des Contributions comporte le droit de + traduire, d'adapter, d'arranger ou d'apporter toute autre + modification au Logiciel et le droit de reproduire le + logiciel en résultant.

+

Le Licencié est autorisé ŕ apporter toute Contribution au + Logiciel sous réserve de mentionner, de façon explicite, son + nom en tant qu'auteur de cette Contribution et la date de + création de celle-ci.

+
+
+

5.3 DROIT DE + DISTRIBUTION

+

Le droit de distribution comporte notamment le droit de + diffuser, de transmettre et de communiquer le Logiciel au + public sur tout support et par tout moyen ainsi que le droit + de mettre sur le marché ŕ titre onéreux ou gratuit, un ou + des exemplaires du Logiciel par tout procédé.

+

Le Licencié est autorisé ŕ distribuer des copies du + Logiciel, modifié ou non, ŕ des tiers dans les conditions + ci-aprčs détaillées.

+
+

5.3.1 DISTRIBUTION DU + LOGICIEL SANS MODIFICATION

+

Le Licencié est autorisé ŕ distribuer des copies + conformes du Logiciel, sous forme de Code Source ou de + Code Objet, ŕ condition que cette distribution respecte + les dispositions du Contrat dans leur totalité et soit + accompagnée:

+
    +
  1. d'un exemplaire du Contrat,

  2. +
  3. d'un avertissement relatif ŕ la restriction de + garantie et de responsabilité du Concédant telle que + prévue aux + articles 8 + et 9,

  4. +
+

et que, dans le cas oů seul le Code Objet du Logiciel est + redistribué, le Licencié permette un accčs effectif au + Code Source complet du Logiciel pendant au moins toute la + durée de sa distribution du Logiciel, étant entendu que le + coűt additionnel d'acquisition du Code Source ne devra pas + excéder le simple coűt de transfert des données.

+
+
+

+ 5.3.2 DISTRIBUTION DU LOGICIEL MODIFIE

+

Lorsque le Licencié apporte une Contribution au Logiciel, + le Logiciel Modifié peut ętre distribué sous un contrat de + licence autre que le présent Contrat sous réserve du + respect des dispositions de l'article + 5.3.4.

+
+
+

5.3.3 DISTRIBUTION DES + MODULES EXTERNES

+

Lorsque le Licencié a développé un Module Externe les + conditions du Contrat ne s'appliquent pas ŕ ce Module + Externe, qui peut ętre distribué sous un contrat de + licence différent.

+
+
+

5.3.4 CITATIONS

+

Le Licencié qui distribue un Logiciel Modifié s'engage + expressément:

+
    +
  1. ŕ indiquer dans sa documentation qu'il a été réalisé + ŕ partir du Logiciel régi par le Contrat, en + reproduisant les mentions de propriété intellectuelle + du Logiciel,

  2. + +
  3. ŕ faire en sorte que l'utilisation du Logiciel, ses + mentions de propriété intellectuelle et le fait qu'il + est régi par le Contrat soient indiqués dans un texte + facilement accessible depuis l'interface du Logiciel + Modifié,

  4. +
  5. ŕ mentionner, sur un site Web librement accessible + décrivant le Logiciel Modifié, et pendant au moins + toute la durée de sa distribution, qu'il a été réalisé + ŕ partir du Logiciel régi par le Contrat, en + reproduisant les mentions de propriété intellectuelle + du Logiciel,

  6. +
  7. lorsqu'il le distribue ŕ un tiers susceptible de + distribuer lui-męme un Logiciel Modifié, sans avoir ŕ + en distribuer le code source, ŕ faire ses meilleurs + efforts pour que les obligations du présent + article 5.3.4 + soient reprises par le dit tiers.

  8. +
+

Lorsque le Logiciel modifié ou non est distribué avec un + Module Externe qui a été conçu pour l'utiliser, le + Licencié doit soumettre le dit Module Externe aux + obligations précédentes.

+
+
+

5.3.5 COMPATIBILITE + AVEC LES LICENCES CeCILL et CeCILL-C

+

Lorsqu'un Logiciel Modifié contient une Contribution + soumise au contrat de licence CeCILL, les stipulations + prévues ŕ + l'article 5.3.4 + sont facultatives.

+

Un Logiciel Modifié peut ętre distribué sous le contrat + de licence CeCILL-C. Les stipulations prévues ŕ + l'article 5.3.4 + sont alors facultatives.

+
+
+
+
+

Article 6 - PROPRIETE INTELLECTUELLE

+
+

6.1 SUR LE LOGICIEL + INITIAL

+

Le Titulaire est détenteur des droits patrimoniaux sur le + Logiciel Initial. Toute utilisation du Logiciel Initial est + soumise au respect des conditions dans lesquelles le + Titulaire a choisi de diffuser son oeuvre et nul autre n'a + la faculté de modifier les conditions de diffusion de ce + Logiciel Initial.

+

Le Titulaire s'engage ŕ ce que le Logiciel Initial reste au + moins régi par le Contrat et ce, pour la durée visée ŕ + l'article 4.2.

+
+
+

6.2 SUR LES + CONTRIBUTIONS

+

Le Licencié qui a développé une Contribution est titulaire + sur celle-ci des droits de propriété intellectuelle dans les + conditions définies par la législation applicable.

+
+
+

6.3 SUR LES MODULES + EXTERNES

+

Le Licencié qui a développé un Module Externe est + titulaire sur celui-ci des droits de propriété + intellectuelle dans les conditions définies par la + législation applicable et reste libre du choix du contrat + régissant sa diffusion.

+
+
+

6.4 DISPOSITIONS + COMMUNES

+
+

Le Licencié s'engage expressément:

+
    +
  1. ŕ ne pas supprimer ou modifier de quelque maničre + que ce soit les mentions de propriété intellectuelle + apposées sur le Logiciel;

  2. +
  3. ŕ reproduire ŕ l'identique lesdites mentions de + propriété intellectuelle sur les copies du Logiciel + modifié ou non.

  4. +
+
+
+

Le Licencié s'engage ŕ ne pas porter atteinte, + directement ou indirectement, aux droits de propriété + intellectuelle du Titulaire et/ou des Contributeurs sur le + Logiciel et ŕ prendre, le cas échéant, ŕ l'égard de son + personnel toutes les mesures nécessaires pour assurer le + respect des dits droits de propriété intellectuelle du + Titulaire et/ou des Contributeurs.

+
+
+
+
+

Article 7 - SERVICES ASSOCIES

+
+

7.1 Le Contrat n'oblige en + aucun cas le Concédant ŕ la réalisation de prestations + d'assistance technique ou de maintenance du Logiciel.

+

Cependant le Concédant reste libre de proposer ce type de + services. Les termes et conditions d'une telle assistance + technique et/ou d'une telle maintenance seront alors + déterminés dans un acte séparé. Ces actes de maintenance + et/ou assistance technique n'engageront que la seule + responsabilité du Concédant qui les propose.

+
+
+

7.2 De męme, tout Concédant + est libre de proposer, sous sa seule responsabilité, ŕ ses + licenciés une garantie, qui n'engagera que lui, lors de la + redistribution du Logiciel et/ou du Logiciel Modifié et ce, + dans les conditions qu'il souhaite. Cette garantie et les + modalités financičres de son application feront l'objet d'un + acte séparé entre le Concédant et le Licencié.

+
+
+
+

+ Article 8 - + RESPONSABILITE

+
+

8.1 Sous réserve des + dispositions de + l'article 8.2, + le Licencié a la faculté, sous réserve de prouver la faute + du Concédant concerné, de solliciter la réparation du + préjudice direct qu'il subirait du fait du Logiciel et dont + il apportera la preuve.

+
+
+

8.2 + La responsabilité du Concédant est limitée aux engagements + pris en application du Contrat et ne saurait ętre engagée en + raison notamment: (i) des dommages dus ŕ l'inexécution, + totale ou partielle, de ses obligations par le Licencié, + (ii) des dommages directs ou indirects découlant de + l'utilisation ou des performances du Logiciel subis par le + Licencié et (iii) plus généralement d'un quelconque dommage + indirect. En particulier, les Parties conviennent + expressément que tout préjudice financier ou commercial (par + exemple perte de données, perte de bénéfices, perte + d'exploitation, perte de clientčle ou de commandes, manque ŕ + gagner, trouble commercial quelconque) ou toute action + dirigée contre le Licencié par un tiers, constitue un + dommage indirect et n'ouvre pas droit ŕ réparation par le + Concédant.

+
+
+
+

+ Article 9 - GARANTIE

+
+

9.1 Le Licencié reconnaît + que l'état actuel des connaissances scientifiques et + techniques au moment de la mise en circulation du Logiciel + ne permet pas d'en tester et d'en vérifier toutes les + utilisations ni de détecter l'existence d'éventuels défauts. + L'attention du Licencié a été attirée sur ce point sur les + risques associés au chargement, ŕ l'utilisation, la + modification et/ou au développement et ŕ la reproduction du + Logiciel qui sont réservés ŕ des utilisateurs avertis.

+

Il relčve de la responsabilité du Licencié de contrôler, + par tous moyens, l'adéquation du produit ŕ ses besoins, son + bon fonctionnement et de s'assurer qu'il ne causera pas de + dommages aux personnes et aux biens.

+
+
+

9.2 + Le Concédant déclare de bonne foi ętre en droit de concéder + l'ensemble des droits attachés au Logiciel (comprenant + notamment les droits visés ŕ l'article + 5).

+
+
+

9.3 Le Licencié reconnaît + que le Logiciel est fourni "en l'état" par le Concédant sans + autre garantie, expresse ou tacite, que celle prévue ŕ + l'article 9.2 + et notamment sans aucune garantie sur sa valeur commerciale, + son caractčre sécurisé, innovant ou pertinent.

+

En particulier, le Concédant ne garantit pas que le + Logiciel est exempt d'erreur, qu'il fonctionnera sans + interruption, qu'il sera compatible avec l'équipement du + Licencié et sa configuration logicielle ni qu'il remplira + les besoins du Licencié.

+
+
+

9.4 Le Concédant ne garantit + pas, de maničre expresse ou tacite, que le Logiciel ne porte + pas atteinte ŕ un quelconque droit de propriété + intellectuelle d'un tiers portant sur un brevet, un logiciel + ou sur tout autre droit de propriété. Ainsi, le Concédant + exclut toute garantie au profit du Licencié contre les + actions en contrefaçon qui pourraient ętre diligentées au + titre de l'utilisation, de la modification, et de la + redistribution du Logiciel. Néanmoins, si de telles actions + sont exercées contre le Licencié, le Concédant lui apportera + son aide technique et juridique pour sa défense. Cette aide + technique et juridique est déterminée au cas par cas entre + le Concédant concerné et le Licencié dans le cadre d'un + protocole d'accord. Le Concédant dégage toute responsabilité + quant ŕ l'utilisation de la dénomination du Logiciel par le + Licencié. Aucune garantie n'est apportée quant ŕ l'existence + de droits antérieurs sur le nom du Logiciel et sur + l'existence d'une marque.

+
+
+
+

Article 10 - + RESILIATION

+
+

10.1 En cas de manquement + par le Licencié aux obligations mises ŕ sa charge par le + Contrat, le Concédant pourra résilier de plein droit le + Contrat trente (30) jours aprčs notification adressée au + Licencié et restée sans effet.

+
+
+

10.2 Le Licencié dont le + Contrat est résilié n'est plus autorisé ŕ utiliser, modifier + ou distribuer le Logiciel. Cependant, toutes les licences + qu'il aura concédées antérieurement ŕ la résiliation du + Contrat resteront valides sous réserve qu'elles aient été + effectuées en conformité avec le Contrat.

+
+
+
+

Article 11 - DISPOSITIONS + DIVERSES

+
+

+ 11.1 CAUSE EXTERIEURE

+

Aucune + des Parties ne sera responsable d'un retard ou d'une + défaillance d'exécution du Contrat qui serait dű + ŕ un cas de force majeure, un cas fortuit ou une cause + extérieure, telle que, notamment, le mauvais fonctionnement + ou les interruptions du réseau électrique ou de + télécommunication, la paralysie du réseau liée + ŕ une attaque informatique, l'intervention des + autorités gouvernementales, les catastrophes naturelles, les + dégâts des eaux, les tremblements de terre, le feu, les + explosions, les grčves et les conflits sociaux, l'état + de guerre...

+
+
+

11.2 Le + fait, par l'une ou l'autre des Parties, d'omettre + en une ou plusieurs occasions de se prévaloir d'une ou + plusieurs dispositions du Contrat, ne pourra en aucun cas impliquer + renonciation par la Partie intéressée ŕ s'en + prévaloir ultérieurement.

+
+
+

11.3 Le Contrat annule et + remplace toute convention antérieure, écrite ou orale, entre + les Parties sur le męme objet et constitue l'accord entier + entre les Parties sur cet objet. Aucune addition ou + modification aux termes du Contrat n'aura d'effet ŕ l'égard + des Parties ŕ moins d'ętre faite par écrit et signée par + leurs représentants dűment habilités.

+
+
+

11.4 Dans l'hypothčse oů une + ou plusieurs des dispositions du Contrat s'avčrerait + contraire ŕ une loi ou ŕ un texte applicable, existants ou + futurs, cette loi ou ce texte prévaudrait, et les Parties + feraient les amendements nécessaires pour se conformer ŕ + cette loi ou ŕ ce texte. Toutes les autres dispositions + resteront en vigueur. De męme, la nullité, pour quelque + raison que ce soit, d'une des dispositions du Contrat ne + saurait entraîner la nullité de l'ensemble du Contrat.

+
+
+

+ 11.5 LANGUE

+

Le Contrat est rédigé en langue française et en langue + anglaise, ces deux versions faisant également foi. +

+
+
+
+

Article 12 - NOUVELLES + VERSIONS DU CONTRAT

+
+

12.1 Toute personne est + autorisée ŕ copier et distribuer des copies de ce + Contrat.

+
+
+

12.2 Afin d'en préserver la + cohérence, le texte du Contrat est protégé et ne peut ętre + modifié que par les auteurs de la licence, lesquels se + réservent le droit de publier périodiquement des mises ŕ + jour ou de nouvelles versions du Contrat, qui posséderont + chacune un numéro distinct. Ces versions ultérieures seront + susceptibles de prendre en compte de nouvelles + problématiques rencontrées par les logiciels libres.

+
+
+

12.3 Tout Logiciel diffusé + sous une version donnée du Contrat ne pourra faire l'objet + d'une diffusion ultérieure que sous la męme version du + Contrat ou une version postérieure.

+
+
+
+

Article 13 - LOI APPLICABLE + ET COMPETENCE TERRITORIALE

+
+

13.1 Le Contrat est régi + par la loi française. Les Parties conviennent de tenter de + régler ŕ l'amiable les différends ou litiges qui viendraient + ŕ se produire par suite ou ŕ l'occasion du Contrat. +

+
+
+

13.2 A défaut d'accord + amiable dans un délai de deux (2) mois ŕ compter de leur + survenance et sauf situation relevant d'une procédure + d'urgence, les différends ou litiges seront portés par la + Partie la plus diligente devant les Tribunaux compétents de + Paris.

+
+
+ +
Version 1.0 du 2006-09-05.
+ + diff --git a/data/texts/MiscLicence.html b/data/texts/MiscLicence.html new file mode 100644 index 0000000..b6f1e61 --- /dev/null +++ b/data/texts/MiscLicence.html @@ -0,0 +1,653 @@ + + + + + CONTRAT DE LICENCE DE LOGICIEL LIBRE CeCILL-B + + + + +

CONTRAT DE LICENCE DE LOGICIEL LIBRE CeCILL-B

+ +
+

Avertissement

+ +

Ce contrat est une licence de logiciel libre issue d'une + concertation entre ses auteurs afin que le respect de deux + grands principes préside ŕ sa rédaction:

+
    +
  • d'une part, le respect des principes de diffusion des + logiciels libres: accčs au code source, droits étendus + conférés aux utilisateurs,
  • +
  • d'autre part, la désignation d'un droit applicable, le + droit français, auquel elle est conforme, tant au regard du + droit de la responsabilité civile que du droit de la + propriété intellectuelle et de la protection qu'il offre aux + auteurs et titulaires des droits patrimoniaux sur un + logiciel.
  • +
+ +

Les auteurs de la licence + CeCILL-B1 sont:

+ +

Commissariat ŕ l'Energie Atomique - CEA, établissement public + de recherche ŕ caractčre scientifique, technique et + industriel, dont le sičge est situé 25 rue Leblanc, immeuble + Le Ponant D, 75015 Paris.

+

Centre National de la Recherche Scientifique - CNRS, + établissement public ŕ caractčre scientifique et + technologique, dont le sičge est situé 3 rue Michel-Ange, + 75794 Paris cedex 16.

+ +

Institut National de Recherche en Informatique et en + Automatique - INRIA, établissement public ŕ caractčre + scientifique et technologique, dont le sičge est situé Domaine + de Voluceau, Rocquencourt, BP 105, 78153 Le Chesnay cedex.

+ +
+
+

Préambule

+

Ce contrat est une licence de logiciel libre dont l'objectif + est de conférer aux utilisateurs une trčs large liberté de + modification et de redistribution du logiciel régi par cette + licence.

+

L'exercice de cette liberté est assorti d'une obligation + forte de citation ŕ la charge de ceux qui distribueraient un + logiciel incorporant un logiciel régi par la présente licence + afin d'assurer que les contributions de tous soient + correctement identifiées et reconnues.

+

L'accessibilité au code source et les droits de copie, de + modification et de redistribution qui découlent de ce contrat + ont pour contrepartie de n'offrir aux utilisateurs qu'une + garantie limitée et de ne faire peser sur l'auteur du + logiciel, le titulaire des droits patrimoniaux et les + concédants successifs qu'une responsabilité restreinte.

+

A cet égard l'attention de l'utilisateur est attirée sur les + risques associés au chargement, ŕ l'utilisation, ŕ la + modification et/ou au développement et ŕ la reproduction du + logiciel par l'utilisateur étant donné sa spécificité de + logiciel libre, qui peut le rendre complexe ŕ manipuler et qui + le réserve donc ŕ des développeurs ou des professionnels + avertis possédant des connaissances informatiques + approfondies. Les utilisateurs sont donc invités ŕ charger et + tester l'adéquation du logiciel ŕ leurs besoins dans des + conditions permettant d'assurer la sécurité de leurs systčmes + et/ou de leurs données et, plus généralement, ŕ l'utiliser et + l'exploiter dans les męmes conditions de sécurité. Ce contrat + peut ętre reproduit et diffusé librement, sous réserve de le + conserver en l'état, sans ajout ni suppression de + clauses.

+

Ce contrat est susceptible de s'appliquer ŕ tout logiciel + dont le titulaire des droits patrimoniaux décide de soumettre + l'exploitation aux dispositions qu'il contient.

+ +
+
+

Article 1 - DEFINITIONS

+

Dans ce contrat, les termes suivants, lorsqu'ils seront + écrits avec une lettre capitale, auront la signification + suivante:

+

Contrat: + désigne le présent contrat de licence, ses éventuelles + versions postérieures et annexes.

+

Logiciel: + désigne le logiciel sous sa forme de Code Objet et/ou de Code + Source et le cas échéant sa documentation, dans leur état au + moment de l'acceptation du Contrat par le Licencié.

+

Logiciel + Initial: désigne le Logiciel sous sa forme de Code + Source et éventuellement de Code Objet et le cas échéant sa + documentation, dans leur état au moment de leur premičre + diffusion sous les termes du Contrat.

+

Logiciel + Modifié: désigne le Logiciel modifié par au moins une + Contribution.

+

Code + Source: désigne l'ensemble des instructions et des + lignes de programme du Logiciel et auquel l'accčs est + nécessaire en vue de modifier le Logiciel.

+

Code + Objet: désigne les fichiers binaires issus de la + compilation du Code Source.

+

Titulaire: + désigne le ou les détenteurs des droits patrimoniaux d'auteur + sur le Logiciel Initial.

+

Licencié: + désigne le ou les utilisateurs du Logiciel ayant accepté le + Contrat.

+

Contributeur: + désigne le Licencié auteur d'au moins une Contribution.

+

Concédant: + désigne le Titulaire ou toute personne physique ou morale + distribuant le Logiciel sous le Contrat.

+

Contribution: + désigne l'ensemble des modifications, corrections, + traductions, adaptations et/ou nouvelles fonctionnalités + intégrées dans le Logiciel par tout Contributeur, ainsi que + tout Module Interne.

+

Module: + désigne un ensemble de fichiers sources y compris leur + documentation qui permet de réaliser des fonctionnalités ou + services supplémentaires ŕ ceux fournis par le Logiciel.

+

Module + Externe: désigne tout Module, non dérivé du Logiciel, tel + que ce Module et le Logiciel s'exécutent dans des espaces + d'adressage différents, l'un appelant l'autre au moment de leur + exécution.

+

Module + Interne: désigne tout Module lié au Logiciel de telle + sorte qu'ils s'exécutent dans le męme espace + d'adressage.

+

Parties: + désigne collectivement le Licencié et le Concédant.

+

Ces termes s'entendent au singulier comme au pluriel.

+
+
+

Article 2 - OBJET

+

Le Contrat a pour objet la concession par le Concédant au + Licencié d'une licence non exclusive, cessible et mondiale du + Logiciel telle que définie ci-aprčs ŕ + l'article 5 + pour toute la durée de protection des droits portant sur ce + Logiciel.

+
+
+

Article 3 - ACCEPTATION

+
+

3.1 + L'acceptation par le Licencié des termes du Contrat est + réputée acquise du fait du premier des faits suivants:

+
    +
  • (i) le chargement du Logiciel par tout moyen notamment + par téléchargement ŕ partir d'un serveur distant ou par + chargement ŕ partir d'un support physique;
  • +
  • (ii) le premier exercice par le Licencié de l'un + quelconque des droits concédés par le Contrat.
  • +
+
+
+

3.2 Un exemplaire du + Contrat, contenant notamment un avertissement relatif aux + spécificités du Logiciel, ŕ la restriction de garantie et ŕ + la limitation ŕ un usage par des utilisateurs expérimentés a + été mis ŕ disposition du Licencié préalablement ŕ son + acceptation telle que définie ŕ + l'article 3.1 + ci dessus et le Licencié reconnaît en avoir pris + connaissance.

+
+
+
+

Article 4 - ENTREE EN VIGUEUR ET DUREE

+
+

4.1 ENTREE EN VIGUEUR

+

Le Contrat entre en vigueur ŕ la date de son acceptation + par le Licencié telle que définie + en 3.1.

+
+
+

4.2 DUREE

+

Le Contrat produira ses effets pendant toute la durée + légale de protection des droits patrimoniaux portant sur le + Logiciel.

+
+
+
+

+ Article 5 - ETENDUE DES DROITS + CONCEDES

+

Le Concédant concčde au Licencié, qui accepte, les droits + suivants sur le Logiciel pour toutes destinations et pour la + durée du Contrat dans les conditions ci-aprčs + détaillées.

+

Par ailleurs, si le Concédant détient ou venait ŕ détenir un + ou plusieurs brevets d'invention protégeant tout ou partie des + fonctionnalités du Logiciel ou de ses composants, il s'engage + ŕ ne pas opposer les éventuels droits conférés par ces brevets + aux Licenciés successifs qui utiliseraient, exploiteraient ou + modifieraient le Logiciel. En cas de cession de ces brevets, + le Concédant s'engage ŕ faire reprendre les obligations du + présent alinéa aux cessionnaires.

+
+

5.1 DROIT + D'UTILISATION

+ +

Le Licencié est autorisé ŕ utiliser le Logiciel, sans + restriction quant aux domaines d'application, étant ci-aprčs + précisé que cela comporte:

+
    +
  1. la reproduction permanente ou provisoire du Logiciel + en tout ou partie par tout moyen et sous toute + forme.

  2. +
  3. le chargement, l'affichage, l'exécution, ou le + stockage du Logiciel sur tout support.

  4. +
  5. la possibilité d'en observer, d'en étudier, ou d'en + tester le fonctionnement afin de déterminer les idées et + principes qui sont ŕ la base de n'importe quel élément + de ce Logiciel; et ceci, lorsque le Licencié effectue + toute opération de chargement, d'affichage, d'exécution, + de transmission ou de stockage du Logiciel qu'il est en + droit d'effectuer en vertu du Contrat.

  6. +
+
+
+

5.2 DROIT D'APPORTER DES + CONTRIBUTIONS

+

Le droit d'apporter des Contributions comporte le droit de + traduire, d'adapter, d'arranger ou d'apporter toute autre + modification au Logiciel et le droit de reproduire le + logiciel en résultant.

+

Le Licencié est autorisé ŕ apporter toute Contribution au + Logiciel sous réserve de mentionner, de façon explicite, son + nom en tant qu'auteur de cette Contribution et la date de + création de celle-ci.

+
+
+

5.3 DROIT DE + DISTRIBUTION

+

Le droit de distribution comporte notamment le droit de + diffuser, de transmettre et de communiquer le Logiciel au + public sur tout support et par tout moyen ainsi que le droit + de mettre sur le marché ŕ titre onéreux ou gratuit, un ou + des exemplaires du Logiciel par tout procédé.

+

Le Licencié est autorisé ŕ distribuer des copies du + Logiciel, modifié ou non, ŕ des tiers dans les conditions + ci-aprčs détaillées.

+
+

5.3.1 DISTRIBUTION DU + LOGICIEL SANS MODIFICATION

+

Le Licencié est autorisé ŕ distribuer des copies + conformes du Logiciel, sous forme de Code Source ou de + Code Objet, ŕ condition que cette distribution respecte + les dispositions du Contrat dans leur totalité et soit + accompagnée:

+
    +
  1. d'un exemplaire du Contrat,

  2. +
  3. d'un avertissement relatif ŕ la restriction de + garantie et de responsabilité du Concédant telle que + prévue aux + articles 8 + et 9,

  4. +
+

et que, dans le cas oů seul le Code Objet du Logiciel est + redistribué, le Licencié permette un accčs effectif au + Code Source complet du Logiciel pendant au moins toute la + durée de sa distribution du Logiciel, étant entendu que le + coűt additionnel d'acquisition du Code Source ne devra pas + excéder le simple coűt de transfert des données.

+
+
+

+ 5.3.2 DISTRIBUTION DU LOGICIEL MODIFIE

+

Lorsque le Licencié apporte une Contribution au Logiciel, + le Logiciel Modifié peut ętre distribué sous un contrat de + licence autre que le présent Contrat sous réserve du + respect des dispositions de l'article + 5.3.4.

+
+
+

5.3.3 DISTRIBUTION DES + MODULES EXTERNES

+

Lorsque le Licencié a développé un Module Externe les + conditions du Contrat ne s'appliquent pas ŕ ce Module + Externe, qui peut ętre distribué sous un contrat de + licence différent.

+
+
+

5.3.4 CITATIONS

+

Le Licencié qui distribue un Logiciel Modifié s'engage + expressément:

+
    +
  1. ŕ indiquer dans sa documentation qu'il a été réalisé + ŕ partir du Logiciel régi par le Contrat, en + reproduisant les mentions de propriété intellectuelle + du Logiciel,

  2. + +
  3. ŕ faire en sorte que l'utilisation du Logiciel, ses + mentions de propriété intellectuelle et le fait qu'il + est régi par le Contrat soient indiqués dans un texte + facilement accessible depuis l'interface du Logiciel + Modifié,

  4. +
  5. ŕ mentionner, sur un site Web librement accessible + décrivant le Logiciel Modifié, et pendant au moins + toute la durée de sa distribution, qu'il a été réalisé + ŕ partir du Logiciel régi par le Contrat, en + reproduisant les mentions de propriété intellectuelle + du Logiciel,

  6. +
  7. lorsqu'il le distribue ŕ un tiers susceptible de + distribuer lui-męme un Logiciel Modifié, sans avoir ŕ + en distribuer le code source, ŕ faire ses meilleurs + efforts pour que les obligations du présent + article 5.3.4 + soient reprises par le dit tiers.

  8. +
+

Lorsque le Logiciel modifié ou non est distribué avec un + Module Externe qui a été conçu pour l'utiliser, le + Licencié doit soumettre le dit Module Externe aux + obligations précédentes.

+
+
+

5.3.5 COMPATIBILITE + AVEC LES LICENCES CeCILL et CeCILL-C

+

Lorsqu'un Logiciel Modifié contient une Contribution + soumise au contrat de licence CeCILL, les stipulations + prévues ŕ + l'article 5.3.4 + sont facultatives.

+

Un Logiciel Modifié peut ętre distribué sous le contrat + de licence CeCILL-C. Les stipulations prévues ŕ + l'article 5.3.4 + sont alors facultatives.

+
+
+
+
+

Article 6 - PROPRIETE INTELLECTUELLE

+
+

6.1 SUR LE LOGICIEL + INITIAL

+

Le Titulaire est détenteur des droits patrimoniaux sur le + Logiciel Initial. Toute utilisation du Logiciel Initial est + soumise au respect des conditions dans lesquelles le + Titulaire a choisi de diffuser son oeuvre et nul autre n'a + la faculté de modifier les conditions de diffusion de ce + Logiciel Initial.

+

Le Titulaire s'engage ŕ ce que le Logiciel Initial reste au + moins régi par le Contrat et ce, pour la durée visée ŕ + l'article 4.2.

+
+
+

6.2 SUR LES + CONTRIBUTIONS

+

Le Licencié qui a développé une Contribution est titulaire + sur celle-ci des droits de propriété intellectuelle dans les + conditions définies par la législation applicable.

+
+
+

6.3 SUR LES MODULES + EXTERNES

+

Le Licencié qui a développé un Module Externe est + titulaire sur celui-ci des droits de propriété + intellectuelle dans les conditions définies par la + législation applicable et reste libre du choix du contrat + régissant sa diffusion.

+
+
+

6.4 DISPOSITIONS + COMMUNES

+
+

Le Licencié s'engage expressément:

+
    +
  1. ŕ ne pas supprimer ou modifier de quelque maničre + que ce soit les mentions de propriété intellectuelle + apposées sur le Logiciel;

  2. +
  3. ŕ reproduire ŕ l'identique lesdites mentions de + propriété intellectuelle sur les copies du Logiciel + modifié ou non.

  4. +
+
+
+

Le Licencié s'engage ŕ ne pas porter atteinte, + directement ou indirectement, aux droits de propriété + intellectuelle du Titulaire et/ou des Contributeurs sur le + Logiciel et ŕ prendre, le cas échéant, ŕ l'égard de son + personnel toutes les mesures nécessaires pour assurer le + respect des dits droits de propriété intellectuelle du + Titulaire et/ou des Contributeurs.

+
+
+
+
+

Article 7 - SERVICES ASSOCIES

+
+

7.1 Le Contrat n'oblige en + aucun cas le Concédant ŕ la réalisation de prestations + d'assistance technique ou de maintenance du Logiciel.

+

Cependant le Concédant reste libre de proposer ce type de + services. Les termes et conditions d'une telle assistance + technique et/ou d'une telle maintenance seront alors + déterminés dans un acte séparé. Ces actes de maintenance + et/ou assistance technique n'engageront que la seule + responsabilité du Concédant qui les propose.

+
+
+

7.2 De męme, tout Concédant + est libre de proposer, sous sa seule responsabilité, ŕ ses + licenciés une garantie, qui n'engagera que lui, lors de la + redistribution du Logiciel et/ou du Logiciel Modifié et ce, + dans les conditions qu'il souhaite. Cette garantie et les + modalités financičres de son application feront l'objet d'un + acte séparé entre le Concédant et le Licencié.

+
+
+
+

+ Article 8 - + RESPONSABILITE

+
+

8.1 Sous réserve des + dispositions de + l'article 8.2, + le Licencié a la faculté, sous réserve de prouver la faute + du Concédant concerné, de solliciter la réparation du + préjudice direct qu'il subirait du fait du Logiciel et dont + il apportera la preuve.

+
+
+

8.2 + La responsabilité du Concédant est limitée aux engagements + pris en application du Contrat et ne saurait ętre engagée en + raison notamment: (i) des dommages dus ŕ l'inexécution, + totale ou partielle, de ses obligations par le Licencié, + (ii) des dommages directs ou indirects découlant de + l'utilisation ou des performances du Logiciel subis par le + Licencié et (iii) plus généralement d'un quelconque dommage + indirect. En particulier, les Parties conviennent + expressément que tout préjudice financier ou commercial (par + exemple perte de données, perte de bénéfices, perte + d'exploitation, perte de clientčle ou de commandes, manque ŕ + gagner, trouble commercial quelconque) ou toute action + dirigée contre le Licencié par un tiers, constitue un + dommage indirect et n'ouvre pas droit ŕ réparation par le + Concédant.

+
+
+
+

+ Article 9 - GARANTIE

+
+

9.1 Le Licencié reconnaît + que l'état actuel des connaissances scientifiques et + techniques au moment de la mise en circulation du Logiciel + ne permet pas d'en tester et d'en vérifier toutes les + utilisations ni de détecter l'existence d'éventuels défauts. + L'attention du Licencié a été attirée sur ce point sur les + risques associés au chargement, ŕ l'utilisation, la + modification et/ou au développement et ŕ la reproduction du + Logiciel qui sont réservés ŕ des utilisateurs avertis.

+

Il relčve de la responsabilité du Licencié de contrôler, + par tous moyens, l'adéquation du produit ŕ ses besoins, son + bon fonctionnement et de s'assurer qu'il ne causera pas de + dommages aux personnes et aux biens.

+
+
+

9.2 + Le Concédant déclare de bonne foi ętre en droit de concéder + l'ensemble des droits attachés au Logiciel (comprenant + notamment les droits visés ŕ l'article + 5).

+
+
+

9.3 Le Licencié reconnaît + que le Logiciel est fourni "en l'état" par le Concédant sans + autre garantie, expresse ou tacite, que celle prévue ŕ + l'article 9.2 + et notamment sans aucune garantie sur sa valeur commerciale, + son caractčre sécurisé, innovant ou pertinent.

+

En particulier, le Concédant ne garantit pas que le + Logiciel est exempt d'erreur, qu'il fonctionnera sans + interruption, qu'il sera compatible avec l'équipement du + Licencié et sa configuration logicielle ni qu'il remplira + les besoins du Licencié.

+
+
+

9.4 Le Concédant ne garantit + pas, de maničre expresse ou tacite, que le Logiciel ne porte + pas atteinte ŕ un quelconque droit de propriété + intellectuelle d'un tiers portant sur un brevet, un logiciel + ou sur tout autre droit de propriété. Ainsi, le Concédant + exclut toute garantie au profit du Licencié contre les + actions en contrefaçon qui pourraient ętre diligentées au + titre de l'utilisation, de la modification, et de la + redistribution du Logiciel. Néanmoins, si de telles actions + sont exercées contre le Licencié, le Concédant lui apportera + son aide technique et juridique pour sa défense. Cette aide + technique et juridique est déterminée au cas par cas entre + le Concédant concerné et le Licencié dans le cadre d'un + protocole d'accord. Le Concédant dégage toute responsabilité + quant ŕ l'utilisation de la dénomination du Logiciel par le + Licencié. Aucune garantie n'est apportée quant ŕ l'existence + de droits antérieurs sur le nom du Logiciel et sur + l'existence d'une marque.

+
+
+
+

Article 10 - + RESILIATION

+
+

10.1 En cas de manquement + par le Licencié aux obligations mises ŕ sa charge par le + Contrat, le Concédant pourra résilier de plein droit le + Contrat trente (30) jours aprčs notification adressée au + Licencié et restée sans effet.

+
+
+

10.2 Le Licencié dont le + Contrat est résilié n'est plus autorisé ŕ utiliser, modifier + ou distribuer le Logiciel. Cependant, toutes les licences + qu'il aura concédées antérieurement ŕ la résiliation du + Contrat resteront valides sous réserve qu'elles aient été + effectuées en conformité avec le Contrat.

+
+
+
+

Article 11 - DISPOSITIONS + DIVERSES

+
+

+ 11.1 CAUSE EXTERIEURE

+

Aucune + des Parties ne sera responsable d'un retard ou d'une + défaillance d'exécution du Contrat qui serait dű + ŕ un cas de force majeure, un cas fortuit ou une cause + extérieure, telle que, notamment, le mauvais fonctionnement + ou les interruptions du réseau électrique ou de + télécommunication, la paralysie du réseau liée + ŕ une attaque informatique, l'intervention des + autorités gouvernementales, les catastrophes naturelles, les + dégâts des eaux, les tremblements de terre, le feu, les + explosions, les grčves et les conflits sociaux, l'état + de guerre...

+
+
+

11.2 Le + fait, par l'une ou l'autre des Parties, d'omettre + en une ou plusieurs occasions de se prévaloir d'une ou + plusieurs dispositions du Contrat, ne pourra en aucun cas impliquer + renonciation par la Partie intéressée ŕ s'en + prévaloir ultérieurement.

+
+
+

11.3 Le Contrat annule et + remplace toute convention antérieure, écrite ou orale, entre + les Parties sur le męme objet et constitue l'accord entier + entre les Parties sur cet objet. Aucune addition ou + modification aux termes du Contrat n'aura d'effet ŕ l'égard + des Parties ŕ moins d'ętre faite par écrit et signée par + leurs représentants dűment habilités.

+
+
+

11.4 Dans l'hypothčse oů une + ou plusieurs des dispositions du Contrat s'avčrerait + contraire ŕ une loi ou ŕ un texte applicable, existants ou + futurs, cette loi ou ce texte prévaudrait, et les Parties + feraient les amendements nécessaires pour se conformer ŕ + cette loi ou ŕ ce texte. Toutes les autres dispositions + resteront en vigueur. De męme, la nullité, pour quelque + raison que ce soit, d'une des dispositions du Contrat ne + saurait entraîner la nullité de l'ensemble du Contrat.

+
+
+

+ 11.5 LANGUE

+

Le Contrat est rédigé en langue française et en langue + anglaise, ces deux versions faisant également foi. +

+
+
+
+

Article 12 - NOUVELLES + VERSIONS DU CONTRAT

+
+

12.1 Toute personne est + autorisée ŕ copier et distribuer des copies de ce + Contrat.

+
+
+

12.2 Afin d'en préserver la + cohérence, le texte du Contrat est protégé et ne peut ętre + modifié que par les auteurs de la licence, lesquels se + réservent le droit de publier périodiquement des mises ŕ + jour ou de nouvelles versions du Contrat, qui posséderont + chacune un numéro distinct. Ces versions ultérieures seront + susceptibles de prendre en compte de nouvelles + problématiques rencontrées par les logiciels libres.

+
+
+

12.3 Tout Logiciel diffusé + sous une version donnée du Contrat ne pourra faire l'objet + d'une diffusion ultérieure que sous la męme version du + Contrat ou une version postérieure.

+
+
+
+

Article 13 - LOI APPLICABLE + ET COMPETENCE TERRITORIALE

+
+

13.1 Le Contrat est régi + par la loi française. Les Parties conviennent de tenter de + régler ŕ l'amiable les différends ou litiges qui viendraient + ŕ se produire par suite ou ŕ l'occasion du Contrat. +

+
+
+

13.2 A défaut d'accord + amiable dans un délai de deux (2) mois ŕ compter de leur + survenance et sauf situation relevant d'une procédure + d'urgence, les différends ou litiges seront portés par la + Partie la plus diligente devant les Tribunaux compétents de + Paris.

+
+
+ +
Version 1.0 du 2006-09-05.
+ + diff --git a/src/java/misc/ApplicationManager.java b/src/java/misc/ApplicationManager.java new file mode 100644 index 0000000..4fe4368 --- /dev/null +++ b/src/java/misc/ApplicationManager.java @@ -0,0 +1,12 @@ +package misc; + +import java.awt.Container; +import java.util.Hashtable; +import javax.swing.AbstractButton; +import javax.swing.JMenu; + +public interface ApplicationManager { + public void addMenuItem (JMenu... jMenu); + public void addIconButtons (Container... containers); + public void addActiveButtons (Hashtable buttons); +} diff --git a/src/java/misc/Bundle.java b/src/java/misc/Bundle.java new file mode 100644 index 0000000..c2ba267 --- /dev/null +++ b/src/java/misc/Bundle.java @@ -0,0 +1,532 @@ +package misc; + +import java.awt.Component; +import java.awt.Dialog; +import java.awt.Graphics2D; +import java.awt.Polygon; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URLConnection; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Hashtable; +import java.util.Locale; +import java.util.Properties; +import java.util.PropertyResourceBundle; +import java.util.ResourceBundle; +import java.util.Vector; +import javax.swing.AbstractButton; +import javax.swing.BorderFactory; +import javax.swing.DefaultListCellRenderer; +import javax.swing.ImageIcon; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JOptionPane; +import javax.swing.border.TitledBorder; + +/** + Managed the internalization by using files properties and ResourceBundle. + This class could be used to managed all texts show in a application (buttons, menus, labels). + The files properties could be embedded in a jar (mix of class and data) or a dedicated directory. + The name file look like : applicationBundle[_langage[_COUNTRY[_VARIANT]]].properties . + Each file contains a "bundle" of key/value association. + The values are string represent message or formated sentences (with numbered holes to put variables values). +*/ +public class Bundle { + + static public final String FS = System.getProperty ("file.separator"); + + // = Constants ============================ + /** Bundle file extension. */ + static public final String bundleExt = ".properties"; + + /** Postfix for title. */ + static public final String titlePostfix = "Title"; + + /** Prefixe for all actioners (button, menus, menuItem) */ + static public final String actionPrefix = "Action"; + + /** XXX commentaire */ + static public final String labelPrefix = "Label"; + static public final String storyPrefix = "Story"; + + /** XXX commentaire */ + static public final String messagePrefix = "Message"; + + /** XXX commentaire */ + static public final String exceptionPrefix = "Exception"; + + /** XXX commentaire */ + static public final String userPrefix = "User_"; + + /** XXX commentaire */ + static public final String enumPrefix = "Enum"; + + /** Message shows when when try use bundle before load it. */ + static private final String bundleUseException = "Can''t find token \"{0}\" in Bundle. Continue?"; + static private final String bundleSaveException = "Bundle {0} can''t be save."; + + // = Attributs ============================ + static class ApplicationInfo { + public ResourceBundle messages; + public Hashtable patch = new Hashtable (); + public boolean modified; + public ApplicationInfo (ResourceBundle messages) { this.messages = messages; } + } + + /** Labels to bundle to find to display.*/ + static private Hashtable labels = new Hashtable (); + /** Applications bundle name to bundle. */ + static private Hashtable applications = new Hashtable (); + static private ArrayList applicationsOrder = new ArrayList (); + /** The current locale used to display messages. */ + static private Locale locale; + + + // = Accessors ============================ + /** + Accessors to locale. + @return current locale. + */ + static public Locale getLocale () { return locale; } + /** + Accesor to locale. + @param locale the new locale value. + */ + static public void setLocale (Locale locale) { + resetLocale (locale); + broadcastBundleReloaded (); + } + + static private final void initLocale () { + if (locale != null) + return; + Locale locale = Locale.getDefault (); + if (Config.getString ("Language") != null) + try { + locale = new Locale (Config.getString ("Language"), + Config.getString ("Country"), + Config.getString ("Variant")); + } catch (Exception e) { + } + resetLocale (locale); + } + + static private void resetLocale (Locale locale) { + if (Bundle.locale == locale || + (Bundle.locale != null && Bundle.locale.equals (locale))) + return; + Locale.setDefault (locale); + Bundle.locale = locale; + for (String application : applicationsOrder) { + ResourceBundle messages = ResourceBundle.getBundle (application, locale, bundleControl); + applications.put (application, new ApplicationInfo (messages)); + } + } + + static ResourceBundle.Control bundleControl = new ResourceBundle.Control () { + private String toResourceName0 (String bundleName, String suffix) { + if (bundleName.contains ("://")) + return null; + else + return toResourceName (bundleName, suffix); + } + public ResourceBundle newBundle (String baseName, Locale locale, String format, ClassLoader loader, boolean reload) + throws IllegalAccessException, InstantiationException, IOException { + String bundleName = toBundleName (baseName, locale); + ResourceBundle bundle = null; + final String resourceName = toResourceName0 (bundleName, "properties"); + if (resourceName == null) + return bundle; + URLConnection connection = Config.getDataUrl (Config.dataDirname, Config.configDirname, resourceName).openConnection (); + connection.setUseCaches (false); + InputStream stream = connection.getInputStream (); + try { + bundle = new PropertyResourceBundle (stream); + } finally { + stream.close (); + } + return bundle; + } + }; + + // = Reading files ======================== + /** + Set the new application and load bundle with parameter saved in configuration or the default system locale. + @param application the application bundle name. + */ + static public final void load (String application) { + initLocale (); + load (application, locale); + } + + /** + Set the new application and load bundle according the locale in parameter. + @param application the application bundle name. + @param locale the localization to used. + */ + static public final void load (String application, Locale locale) { + try { + resetLocale (locale); + if (applicationsOrder.contains (application)) + return; + applicationsOrder.add (application); + ResourceBundle messages = ResourceBundle.getBundle (application, locale, bundleControl); + // XXX anciennes valeurs de patch perdues + applications.put (application, new ApplicationInfo (messages)); + for (String key : messages.keySet ()) + labels.put (key, application); + boolean needSetLanguage = Config.getString ("Language") == null; + broadcastBundleReloaded (); + if (needSetLanguage) + HelpManager.actionLocale (null); + } catch (RuntimeException e) { + if (JOptionPane.YES_OPTION != JOptionPane.showConfirmDialog + (null, "Would you like to continue?", + " Can't load Bundle "+application, JOptionPane.YES_NO_OPTION)) + throw e; + } + } + + static public final void save (String applicationName) { + File configDir = new File (Config.findDataDir (true), Config.configDirname); + File bundleFile = new File (configDir, applicationName+"_"+locale+bundleExt); + try { + save (applicationName, bundleFile); + } catch (IOException e) { + try { + configDir.mkdirs (); + save (applicationName, bundleFile); + } catch (IOException e2) { + System.err.println (MessageFormat.format (bundleSaveException, applicationName)); + } + } catch (NullPointerException e) { + throw new IllegalArgumentException (bundleUseException); + } + } + static private boolean configurationModified = false; + static public final void save (String applicationName, File file) + throws IOException { + ApplicationInfo applicationInfo = applications.get (applicationName); + if (!applicationInfo.modified && file.exists ()) + return; + if (!file.exists ()) + file.createNewFile (); + Properties properties = new Properties (); + ResourceBundle messages = applicationInfo.messages; + for (String key : messages.keySet ()) + properties.setProperty (key, messages.getString (key)); + Hashtable patch = applicationInfo.patch; + for (String key : patch.keySet ()) + properties.setProperty (key, patch.get (key)); + FileOutputStream fileOutputStream = new FileOutputStream (file); + properties.store (fileOutputStream, "Produit automatiquement par Bundle"); + fileOutputStream.close (); + FileInputStream fileInputStream = new FileInputStream (file); + applicationInfo.messages = new PropertyResourceBundle (fileInputStream); + fileInputStream.close (); + applicationInfo.patch.clear (); + applicationInfo.modified = false; + } + static public void setString (String applicationName, String key, String val) { + ApplicationInfo applicationInfo = applications.get (applicationName); + String oldVal = null; + try { + oldVal = applicationInfo.messages.getString (key); + } catch (Exception e) { + } + if (val == oldVal || val.equals (oldVal)) { + applicationInfo.patch.remove (key); + return; + } + oldVal = applicationInfo.patch.get (key); + if (val == oldVal || val.equals (oldVal)) + return; + applicationInfo.patch.put (key, val); + applicationInfo.modified = true; + labels.put (key, applicationName); + } + + // = Available files ====================== + /** + Give the list of locale available for a dedicated application. + @param application the application bundle name. + @return a array of string denoted locale that could be used for this application. + */ + static public final Locale[] getApplicationsLocales () { + Vector result = new Vector (); + scanLocale: for (Locale locale : getAvailableLocales ()) { + for (String application : applications.keySet ()) + if (ClassLoader.getSystemResource (Config.configSystemDir+application+"_"+locale+bundleExt) == null && + ClassLoader.getSystemResource (Config.configJarDir+application+"_"+locale+bundleExt) == null) + continue scanLocale; + result.add (locale); + } + Locale [] locales = result.toArray (new Locale[0]); + Arrays.sort (locales, new Comparator () { + public int compare(Locale o1, Locale o2) { + return o1.toString ().compareTo (o2.toString ()); + } + }); + return locales; + } + + // ======================================== + + static public Locale[] getAvailableLocales () { + ArrayList all = new ArrayList (Arrays.asList (Locale.getAvailableLocales ())); + //all.add (new Locale.Builder ().setLanguage ("br").setRegion ("BR").setVariant ("gallo").build ()); + //all.add (new Locale.Builder ().setLanguage ("br").setRegion ("BR").setVariant ("breton").build ()); + all.add (new Locale.Builder ().setLanguage ("br").setRegion ("FR").setVariant ("gallo").build ()); + all.add (new Locale.Builder ().setLanguage ("br").setRegion ("FR").setVariant ("breton").build ()); + //all.add (new Locale.Builder ().setLanguage ("es").setRegion ("ES").build ()); + return all.toArray (new Locale[0]); + } + static public final Locale[] getAllLocales () { + Locale[] locales = getAvailableLocales (); + Arrays.sort (locales, new Comparator () { + public int compare(Locale o1, Locale o2) { + return o1.toString ().compareTo (o2.toString ()); + } + }); + return locales; + } + + static public ImageIcon getMixFlag (String language, String country) { + ImageIcon countryFlag = Util.loadImageIcon (Config.dataDirname, Config.iconsDirname, Config.flagsDirname, + country.toLowerCase () + Config.iconsExt); + ImageIcon langFlag = Util.loadImageIcon (Config.dataDirname, Config.iconsDirname, Config.flagsDirname, + language.toLowerCase () + Config.iconsExt); + if (countryFlag == null) + return langFlag; + if (langFlag == null) + return countryFlag; + int width = countryFlag.getIconWidth (); + int height = countryFlag.getIconHeight (); + BufferedImage newFlag = new BufferedImage (width, height, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2 = newFlag.createGraphics (); + Polygon topLeftCorner = new Polygon (new int[] {0, width, 0}, new int[] {0, 0, height}, 3); + g2.setClip (topLeftCorner); + g2.drawImage (langFlag.getImage (), 0, 0, width, height, null); + Polygon bottomRightCorner = new Polygon (new int[] {0, width, width}, new int[] {height, 0, height}, 3); + g2.setClip (bottomRightCorner); + g2.drawImage (countryFlag.getImage (), 0, 0, width, height, null); + return new ImageIcon (newFlag); + } + + // ======================================== + static public final JComboBox getJComboFlags (final Locale[] locales) { + final ImageIcon [] flags = new ImageIcon [locales.length]; + String [] labels = new String [locales.length]; + final Hashtable labelIndex = new Hashtable (); + for (int i = 0; i < locales.length; i++) { + labelIndex.put (labels [i] = locales[i].toString (), i); + flags [i] = getMixFlag (locales[i].getLanguage (), locales[i].getCountry ()); + if (flags [i] != null) + flags [i].setDescription (locales [i].toString ()); + } + JComboBox localesList = new JComboBox (labels); + localesList.setRenderer (new DefaultListCellRenderer () { + { + setOpaque (true); + setVerticalAlignment (CENTER); + } + static private final long serialVersionUID = 1L; + public Component getListCellRendererComponent (JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + int selectedIndex = labelIndex.get (value); + + if (isSelected) { + setBackground (list.getSelectionBackground ()); + setForeground (list.getSelectionForeground ()); + } else { + setBackground (list.getBackground ()); + setForeground (list.getForeground ()); + } + setIcon (flags[selectedIndex]); + setText (locales[selectedIndex].toString ()); + return this; + } + }); + return localesList; + } + + // = Use localized texts ================== + /** + Give the string to display according to the message identifier. + @param messageId a sting used to tag message. + @return the string to display. + */ + static public boolean askBundleUseException = true; + static public final String getString (String messageId, String defaultValue) { + try { + ApplicationInfo applicationInfo = applications.get (labels.get (messageId)); + String val = applicationInfo.patch.get (messageId); + if (val != null) + return val; + return applicationInfo.messages.getString (messageId); + } catch (Exception e) { + if (defaultValue != null) + return defaultValue; + if (askBundleUseException && JOptionPane.YES_OPTION != + JOptionPane.showConfirmDialog (null, MessageFormat.format (bundleUseException, messageId), + " Bundle corrupted ", JOptionPane.YES_NO_OPTION)) + throw new IllegalArgumentException (MessageFormat.format (bundleUseException, messageId)); + askBundleUseException = false; + System.err.println (messageId+"= ### Bundle ###"); + return messageId; + } + } + static public final String getStory (String messageId) { return getString (storyPrefix+messageId, messageId); } + static public final String getLabel (String messageId) { return messageId == null ? "" : getString (labelPrefix+messageId, null); } + static public final String getMessage (String messageId) { return getString (messagePrefix+messageId, null); } + static public final String getException (String messageId) { return getString (exceptionPrefix+messageId, null); } + static public final String getAction (String messageId) { return getString (actionPrefix+messageId, null); } + static public final String getTitle (String messageId) { return getString (messageId+titlePostfix, null); } + static public final String getUser (String messageId) { return getString (userPrefix+messageId, messageId); } + static public final void setUser (String applicationName, String messageId, String value) { + setString (applicationName, userPrefix+messageId, value); + } + static public final String getEnum (String enumId, String valueId) { return getString (enumPrefix+enumId+valueId, null); } + static public final String getEnum (Enum enumValue) { return getEnum (enumValue.getClass ().getSimpleName (), enumValue.toString ()); } + static public final JComboBox getEnum (Class enumClass, Enum defaultValue) { + String enumName = enumClass.getSimpleName (); + if (!enumClass.isEnum ()) + throw new IllegalArgumentException (enumName+" is not Enum!"); + JComboBox jComboBox = new JComboBox (); + try { + Object[] values = (Object[]) enumClass.getMethod ("values").invoke (null); + for (Object value : values) + jComboBox.addItem (getEnum (enumName, value.toString ())); + jComboBox.setEditable (false); + if (defaultValue != null) + jComboBox.setSelectedIndex (defaultValue.ordinal ()); + } catch (Exception e) { + throw new IllegalArgumentException (enumName+" is not Enum!"); + } + return jComboBox; + } + + // = Automatic update ===================== + + static public void setBorder (JComponent jComponent) { + jComponent.setBorder (BorderFactory.createTitledBorder (Bundle.getTitle (Util.getClassName (jComponent.getClass ())))); + } + static public void updateBorder (JComponent jComponent) { + ((TitledBorder) jComponent.getBorder ()).setTitle (Bundle.getTitle (Util.getClassName (jComponent.getClass ()))); + } + + /** List of actioners (button, ...) to update if the locale is updated. */ + static private Vector allActioners = new Vector (); + + /** List of actioners (menu, ...) to update if the locale is updated. */ + static private Vector allMenus = new Vector (); + + /** List of frame to update if the locale is updated. */ + static private Hashtable allDialogs = new Hashtable (); + + /** List of icon ToolTips to update if the locale is updated. */ + static private Vector allIconToolTips = new Vector (); + + /** List of application module which managed localized texts to update if the locale is updated. */ + static private Hashtable allLabels = new Hashtable (); + + /** XXX commentaire. */ + static private Hashtable, Class> allEnums = new Hashtable, Class> (); + + /** XXX commentaire. */ + static private StateNotifier bundleObservable = new StateNotifier (); + + /** + Say to each actioner and application module that locale change. + */ + static void broadcastBundleReloaded () { + if (Config.isLoaded ()) { + Config.setString ("Country", locale.getCountry ()); + Config.setString ("Language", locale.getLanguage ()); + Config.setString ("Variant", locale.getVariant ()); + } + for (AbstractButton actioner : allActioners) + if (!actioner.getText ().isEmpty ()) + actioner.setText (getAction (actioner.getActionCommand ())); + for (AbstractButton menu : allMenus) + if (!menu.getText ().isEmpty ()) + menu.setText (getTitle (menu.getActionCommand ())); + for (Dialog dialog : allDialogs.keySet ()) + dialog.setTitle (getTitle (allDialogs.get (dialog))); + for (AbstractButton actioner : allIconToolTips) + actioner.setToolTipText (Bundle.getAction (actioner.getActionCommand ())); + for (JLabel jLabel : allLabels.keySet ()) + jLabel.setText (getLabel (allLabels.get (jLabel))); + for (JComboBox jComboBox : allEnums.keySet ()) + try { + Class enumClass = allEnums.get (jComboBox); + String enumName = enumClass.getSimpleName (); + // XXX remove listeners + // XXX sauve selection multiple ? + int idx = jComboBox.getSelectedIndex (); + jComboBox.removeAllItems (); + Object[] values = (Object[]) enumClass.getMethod ("values").invoke (null); + for (Object value : values) + jComboBox.addItem (getEnum (enumName, value.toString ())); + jComboBox.setEditable (false); + jComboBox.setSelectedIndex (idx); + // XXX add listeners + } catch (Exception e) { + } + bundleObservable.broadcastUpdate ("Bundle"); + } + + /** + Declare an actioner to updated if the locale is updated. + @param button the actioner to update. The messageId is find by performed getActionCommand method on it. + */ + static public void addLocalizedActioner (AbstractButton button) { + allActioners.add (button); + } + + static public void addLocalizedMenu (AbstractButton button) { + allMenus.add (button); + } + + static public void addLocalizedDialog (Dialog dialog, String titleId) { + allDialogs.put (dialog, titleId); + } + /** + XXX commentaire + */ + static public void addLocalizedToolTip (AbstractButton button) { + allIconToolTips.add (button); + } + + /** + XXX commentaire + */ + static public void addLocalizedLabel (JLabel jLabel, String messageId) { + allLabels.put (jLabel, messageId); + } + + /** + XXX commentaire + */ + static public void addLocalizedEnum (JComboBox jComboBox, Class enumClass) { + allEnums.put (jComboBox, enumClass); + } + + /** + Declare an application module which managed localized to updated if the locale is updated. + @param object the application module that managed some texts to localized. + */ + static public void addBundleObserver (Object object) { + bundleObservable.addUpdateObserver (object, "Bundle"); + } + + // ======================================== +} diff --git a/src/java/misc/ColorCheckedLine.java b/src/java/misc/ColorCheckedLine.java new file mode 100644 index 0000000..3cd828b --- /dev/null +++ b/src/java/misc/ColorCheckedLine.java @@ -0,0 +1,24 @@ +package misc; + +public class ColorCheckedLine { + + // ============================================================ + static public final int NBV = 8; + static public final int NBT = 60; + + /** voir http://www.termsys.demon.co.uk/vtansi.htm */ + static public final String + MOVE_TO_COL_START = "\033[300C\033[", + MOVE_TO_COL_END = "D", + SETCOLOR_SUCCESS = "\033[1;32m", + SETCOLOR_FAILURE ="\033[1;31m", + SETCOLOR_WARNING ="\033[1;33m", + SETCOLOR_NORMAL ="\033[0;39m"; + + static public final String + MOVE_TO_60 = MOVE_TO_COL_START+(80-60)+MOVE_TO_COL_END, + MSG_OK = MOVE_TO_60+"["+SETCOLOR_SUCCESS+"OK"+SETCOLOR_NORMAL+"]", + MSG_KO = MOVE_TO_60+"["+SETCOLOR_FAILURE+"KO"+SETCOLOR_NORMAL+"]"; + + // ======================================== +} diff --git a/src/java/misc/CommandLineServer.java b/src/java/misc/CommandLineServer.java new file mode 100644 index 0000000..0b9d2b6 --- /dev/null +++ b/src/java/misc/CommandLineServer.java @@ -0,0 +1,154 @@ +package misc; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.net.ServerSocket; +import java.net.Socket; +import java.text.MessageFormat; +import java.util.Collection; +import java.util.Hashtable; +import java.util.TreeSet; +import java.util.Vector; + +public class CommandLineServer { + + // ======================================== + private Model model; + private Class> classWaiter; + private String classWaiterName; + /** List of commands. */ + private TreeSet commands = new TreeSet (); + /** Help for each command. */ + private Hashtable help = new Hashtable (); + private Hashtable actionsMethod = new Hashtable (); + + // ======================================== + public TreeSet getCommands () { return commands; } + public Hashtable getHelp () { return help; } + public Hashtable getActionsMethod () { return actionsMethod; } + public String getClassWaiterName () { return classWaiterName; } + + // ======================================== + public void showMsg (String msg) {} + public void showStatus (String msg) {} + + // ======================================== + public CommandLineServer (Model model, Class> classWaiter, Collection commands) { + this.model = model; + + this.classWaiter = classWaiter; + String[] path = classWaiter.getName ().split ("\\."); + classWaiterName = path [path.length-1]; + + for (String action : commands) + this.commands.add (action.toLowerCase ()); + for (String action : this.commands) { + try { + String methodName = "action"+Util.toCapital (action); + actionsMethod.put (action, classWaiter.getMethod (methodName)); + } catch (NoSuchMethodException e) { + Log.keepLastException ("CommandLineServer", e); + } + } + updateBundle (); + Bundle.addBundleObserver (this); + } + + // ======================================== + public void updateBundle () { + help.clear (); + for (String cmd : commands) + help.put (cmd, Bundle.getString ("Help"+classWaiterName+"_"+cmd, null).replaceAll ("\\\\n", "\n")); + } + + /** In case of multiple started, this is the only thread wich have right to run. */ + private Thread thread; + + // ======================================== + public boolean isAliveServer () { + return thread != null && thread.isAlive (); + } + + // ======================================== + private int lastPort; + /** Interrupt the server after a TIME_OUT. */ + public void stopServer () { + thread = null; + try { + // force le accept pour sortir du run + (new Socket ("localhost", lastPort)).close (); + } catch (Exception e) { + } + stopCalls (); + } + + Vector calls = new Vector (); + public synchronized void addCall (Socket call) { + calls.add (call); + } + public synchronized void removeCall (Socket call) { + calls.remove (call); + } + public synchronized void stopCalls () { + for (Socket call : calls) { + try { + call.close (); + } catch (Exception e) { + } + } + calls.clear (); + } + + // ======================================== + /** + * Start a server on a dedicated port. + * @param port the internet port number. + */ + public void startServer (final int port) { + (new Thread () { + public void run () { + ServerSocket serverSocket = null; + try { + serverSocket = new ServerSocket (port); + CommandLineServer.this.lastPort = port; + } catch (IOException e) { + showMsg (Bundle.getMessage ("CanTCreateSocket")); + Log.keepLastException ("CommandLineServer::startServer", e); + return; + } + showMsg (MessageFormat.format (Bundle.getMessage (classWaiterName+"Starts"), port)); + showStatus (MessageFormat.format (Bundle.getMessage (classWaiterName+"Starts"), port)); + thread = Thread.currentThread (); + try { + while (thread == Thread.currentThread ()) { + try { + Socket call = serverSocket.accept (); + addCall (call); + classWaiter.getConstructor (CommandLineServer.class, model.getClass (), Socket.class). + newInstance (CommandLineServer.this, model, call); + } catch (IOException e) { + showMsg (Bundle.getMessage ("ClientAbort")); + Log.keepLastException ("CommandLineServer::startServer", e); + } + } + } catch (java.lang.reflect.InvocationTargetException e) { + Log.keepLastException ("CommandLineServer::startServer", e); + } catch (NoSuchMethodException e) { + Log.keepLastException ("CommandLineServer::startServer", e); + } catch (IllegalAccessException e) { + Log.keepLastException ("CommandLineServer::startServer", e); + } catch (InstantiationException e) { + Log.keepLastException ("CommandLineServer::startServer", e); + } + try { + showMsg (Bundle.getMessage (classWaiterName+"Stopped")); + showStatus (Bundle.getMessage (classWaiterName+"Stopped")); + serverSocket.close (); + } catch (IOException e) { + } + } + }).start(); + } + + // ======================================== +} diff --git a/src/java/misc/CommandLineWaiter.java b/src/java/misc/CommandLineWaiter.java new file mode 100644 index 0000000..c5aa906 --- /dev/null +++ b/src/java/misc/CommandLineWaiter.java @@ -0,0 +1,165 @@ +package misc; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.lang.reflect.Method; +import java.net.Socket; +import java.net.SocketException; +import java.text.MessageFormat; +import java.util.Hashtable; +import java.util.StringTokenizer; +import java.util.TreeSet; + +public class CommandLineWaiter { + + // ======================================== + static public final int MAX_COL = 6; + + // ======================================== + protected Model model; + protected CommandLineServer commandLineServer; + + private TreeSet commands; + private Hashtable help; + private Hashtable actionsMethod; + + /** The original call socket to be close at the end of analyse. */ + protected Socket call; + /** Input chanel (of the socket). */ + protected BufferedReader in; + /** Output chanel (of the socket). */ + protected PrintWriter out; + protected String line; + protected StringTokenizer arguments; + + public boolean hasMoreArg () { + return arguments.hasMoreTokens (); + } + public String nextArg () { + return arguments.nextToken ().trim (); + } + + public void startCall () {} + + // ======================================== + public String getCommandLine (BufferedReader in) + throws IOException { + try { + return in.readLine ().trim (); + } catch (SocketException e) { + return null; + } catch (NullPointerException e) { + return null; + } + } + // ======================================== + /** + * Send a answer to the client with a newline and flush the output chanel. + * @param str the answer to send. + */ + public void answerln (String str) { + out.println (str); + out.flush (); + } + + public void syntaxError () { + answerln (Bundle.getMessage ("SyntaxeError")); + } + + // ======================================== + /** + * Give a help to the client. + * @param cmd is null or empty, give the list of commands. Else, give a appropriate help for the command cmd. + */ + public void actionHelp () { + String arg = null; + if (hasMoreArg ()) + arg = nextArg ().toLowerCase (); + if (arg == null || arg.isEmpty ()) + answerln (Bundle.getString ("Help"+commandLineServer.getClassWaiterName ()+"_", null).replaceAll ("\\\\n", "\n")+ + Util.toColumn (Util.set2string (commands), MAX_COL)); + else { + String explanation = help.get (arg); + if (explanation == null) + answerln (MessageFormat.format (Bundle.getMessage ("NotACommand"), arg)); + else + answerln (arg.toLowerCase ()+" "+explanation); + } + } + + // ======================================== + public void actionQuit () { + answerln (Bundle.getMessage ("SeeYouSoon").replaceAll ("\\\\n", "\n")); + try { + call.close (); + } catch (Exception e) { + } + } + + // ======================================== + public void actionExit () { + answerln (Bundle.getMessage ("GoodBye").replaceAll ("\\\\n", "\n")); + System.exit (0); + } + + // ======================================== + /** + * Open a chanel and start a shell interpretor. + * @param call the client connection. + */ + public CommandLineWaiter (CommandLineServer commandLineServer, Model model, Socket call) { + this.commandLineServer = commandLineServer; + this.model = model; + this.call = call; + + commands = commandLineServer.getCommands (); + help = commandLineServer.getHelp (); + actionsMethod = commandLineServer.getActionsMethod (); + try { + in = new BufferedReader (new InputStreamReader (call.getInputStream ())); + out = new PrintWriter (call.getOutputStream ()); + startCall (); + (new Thread () { + public void run() { + analyse (); + } + }).start(); + } catch (IOException e) { + if (!call.isClosed ()) + Log.keepLastException ("CommandLineWaiter", e); + } + } + + // ======================================== + public void analyse () { + String cmd = null; + try { + for (; ! call.isClosed (); ) { + line = getCommandLine (in); + if (line == null) + break; + if (line.isEmpty ()) + continue; + arguments = new StringTokenizer (line); + if (!arguments.hasMoreTokens ()) + continue; + cmd = arguments.nextToken ().trim ().toLowerCase (); + if (commands.contains (cmd)) + actionsMethod.get (cmd).invoke (this); + else + answerln (MessageFormat.format (Bundle.getMessage ("NotACommand"), cmd)); + } + } catch (Exception e) { + Log.keepLastException ("CommandLineWaiter::analyse ("+cmd+")", e); + } finally { + try { + call.close (); + } catch (Exception e) { + } + } + } + + // ======================================== +} diff --git a/src/java/misc/Config.java b/src/java/misc/Config.java new file mode 100644 index 0000000..9b263d8 --- /dev/null +++ b/src/java/misc/Config.java @@ -0,0 +1,523 @@ +package misc; + +import java.awt.Component; +import java.awt.Point; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.URL; +import java.text.DecimalFormat; +import java.text.MessageFormat; +import java.util.Date; +import java.util.Locale; +import java.util.Properties; +import java.util.Vector; +import javax.swing.JComboBox; +import javax.swing.JList; +import javax.swing.JOptionPane; +import javax.swing.ListModel; + +/** + Managed the persistance key/value strings association. + This class could be used to managed all constants values in a application. + The values commme from a bundle jar (mix of class and data) or a dedicated directeory. +*/ +public class Config { + + static public final String FS = System.getProperty ("file.separator"); + + static public final String + dataDirname = "data", + configDirname = "config", + textsDirname = "texts", + logDirname = "log", + imagesDirname = "images", + iconsDirname = "images", + buttonDirname = "button", + flagsDirname = "flags"; + + /** Path to configuration files in files system. */ + static public final String + configSystemDir = dataDirname+FS+configDirname+FS, + logSystemDir = dataDirname+FS+logDirname+FS, + buttonSystemDir = dataDirname+FS+imagesDirname+FS+buttonDirname+FS; + + /** Path to configuration files in jar. */ + static public final String + configJarDir = dataDirname+"/"+configDirname+"/", + imagesJarDir = dataDirname+"/"+imagesDirname+"/", + textsJarDir = dataDirname+"/"+textsDirname+"/"; + + /** Configuration file extension. */ + static public final String + configExt = ".xml", + iconsExt = ".png", + imagesExt = ".png", + htmlExt = ".html", + logExt = ".log"; + + + /** Enconding charset for saving configuration. */ + static public final String configEncoding = "UTF8"; + + /** Comment used as header configuration file. */ + static public final String configHeaderFile = + "This file is automaticaly generated by {0} application at {1,time,short} on {1,date}."; + + /** Somme postfix for constants name in configuration file. */ + static public final String + checkedPostfix = "Checked", + undockedPostfix = "Undocked", + locationPostfix = "Location"; + + /** Message shows when trying to get string with a null key. */ + static private final String configNullKeyException = "No configuration associates with null key."; + + /** Message shows when trying to use configuration before load it. */ + static private final String configUseException = "No configuration loaded."; + + /** Message shows when load exception appears. */ + static private final String configLoadException = "Configuration {0} can''t be load (corrupted file ?)."; + + /** Message shows when save exception appears. */ + static private final String configSaveException = "Configuration {0} can''t be save."; + + /** Liste of constants (key/value strings association). */ + //static private Properties configuration; + static public Properties configuration; + + /** True if configuration need to be saved. */ + static private boolean configurationModified = false; + + // ======================================== + static private File PWD; + static private String[] dataPath; + static { + setPWD (Util.class); + setDataPath (".:.."); + } + + static public File getJarFileOrClassDir (Class applicationClass) { + try { + return new File (applicationClass.getProtectionDomain ().getCodeSource ().getLocation ().toURI ()); + } catch (Exception e) { + // dosn't work for applet + return null; + } + } + static public File getPWD () { return PWD; } + // XXX static public String getPWD () { return PWD.getAbsolutePath (); } + static public void setPWD (Class applicationClass) { + try { + PWD = getJarFileOrClassDir (applicationClass).getParentFile (); + } catch (Exception e) { + } + } + static public void setDataPath (String path) { + dataPath = path.split (":"); + } + static public File findDataDir () { + return findDataDir (false); + } + static public File findDataDir (boolean writable) { + String name = dataDirname; + try { + // search from excecution dir + File file = (new File (name)); + if (file.exists () && file.isDirectory () && (!writable || file.canWrite ())) + return file; + } catch (Exception e) { + } + for (String path : dataPath) { + try { + // search in relative path from jar + File file = (new File (PWD, path+FS+name)); + if (file.exists () && file.isDirectory () && (!writable || file.canWrite ())) + return file; + } catch (Exception e) { + } + } + return null; + } + + static public URL getDataUrl (String... names) { + if (names.length < 1) + return null; + String name = names[names.length - 1]; + String jarPath = "", systemPath = ""; + for (int i = 0; i < names.length - 1; i++) { + jarPath += names[i]+"/"; + systemPath += names[i]+FS; + } + try { + // search from excecution dir + File file = new File (systemPath+name); + if (file.exists ()) + return file.toURI ().toURL (); + } catch (Exception e) { + } + for (String path : dataPath) { + try { + // search in relative path from jar + File file = new File (PWD, path+FS+systemPath+name); + if (file.exists ()) + return file.getCanonicalFile ().toURI ().toURL (); + } catch (Exception e) { + } + } + try { + // search in jar + URL result = ClassLoader.getSystemResource (jarPath+name); + return result; + } catch (Exception e) { + } + return null; + } + + // ======================================== + /** + Load XML configuration file contains constants (key/value strings association). + @param applicationName the application name use to find file. + */ + static public final void load (String applicationName) { + configuration = new Properties (); + try { + File configDir = new File (findDataDir (), configDirname); + File configFile = new File (configDir, applicationName+configExt); + FileInputStream fileInputStream = new FileInputStream (configFile); + configuration.loadFromXML (fileInputStream); + fileInputStream.close (); + configurationModified = false; + } catch (Exception e) { + try { + configuration.loadFromXML (ClassLoader.getSystemResourceAsStream + (configJarDir+applicationName+configExt)); + configurationModified = false; + } catch (Exception e2) { + try { + configuration.loadFromXML (ClassLoader.getSystemResourceAsStream + (configJarDir+applicationName+configExt)); + configurationModified = false; + } catch (Exception e3) { + if (JOptionPane.YES_OPTION != JOptionPane.showConfirmDialog + (null, "Would you like to continue?", + " Can't load Configuration ", JOptionPane.YES_NO_OPTION)) + throw new IllegalArgumentException (MessageFormat.format (configLoadException, applicationName)); + } + } + } + } + + // ======================================== + /** + Return the loading state of the configuration. + @return true if the configuration is loaded. + */ + static public boolean isLoaded () { + return configuration != null; + } + + // ======================================== + /** + Save XML configuration file contains key/value strings association. + The file is not writing if no modification occure. + @param applicationName the application name use to save file. + */ + static public final void save (String applicationName) { + File configDir = new File (findDataDir (true), configDirname); + File configFile = new File (configDir, applicationName+configExt); + try { + save (applicationName, configFile); + } catch (IOException e) { + try { + e.printStackTrace (); + configDir.mkdirs (); + save (applicationName,configFile); + } catch (IOException e2) { + System.err.println (MessageFormat.format (configSaveException, applicationName)); + } + } catch (NullPointerException e) { + throw new IllegalArgumentException (configUseException); + } + } + + static public final void save (String applicationName, File file) + throws IOException { + if (!configurationModified && file.exists ()) + return; + if (!file.exists ()) + file.createNewFile (); + FileOutputStream fileOutputStream = new FileOutputStream (file); + configuration.storeToXML (fileOutputStream, + (new MessageFormat (configHeaderFile, Locale.US)).format (new Object[] {applicationName, new Date ()}, + new StringBuffer(), null).toString (), + configEncoding); + fileOutputStream.close (); + configurationModified = false; + } + + // ======================================== + + // XXX defaultValue peut être placé systématiquement (voir à null) + /** + Searches for the value with the specified key in this configuration list. + @param key the name of the configuration parameter. + @return the configuration value associated with the key or null if not found. + */ + static public final String getString (String key) { + if (key == null) + throw new IllegalArgumentException (configNullKeyException); + try { + return configuration.getProperty (key); + } catch (NullPointerException e) { + throw new IllegalArgumentException (configUseException); + } + } + + /** + Searches for the value with the specified key in this configuration list. + @param key the name of the configuration parameter. + @param defaultValue the defaultValue in case the key is not found. + @return the configuration value associated with the key or defaultValue if not found. + */ + static public final String getString (String key, String defaultValue) { + String value = configuration.getProperty (key); + if (value != null) + return value; + configuration.setProperty (key, defaultValue); + return defaultValue; + } + + /** + Change the value with the specified key in this configuration list. + @param key the name of the configuration parameter. + @param value the new configuration value associated with the key. + */ + static public final void setString (String key, String value) { + try { + configuration.setProperty (key, value); + configurationModified = true; + } catch (NullPointerException e) { + throw new IllegalArgumentException (configUseException); + } + } + + // ======================================== + static public final File getFile (String key) { + String fileName = getString (key); + if (fileName == null) + return null; + return new File (fileName); + } + + static public final void setFile (String key, File file) { + setString (key, file.getPath ()); + } + + // ======================================== + static public final boolean getBoolean (String key) { + return Boolean.parseBoolean (getString (key)); + } + + static public final boolean getBoolean (String key, boolean defaultValue) { + return Boolean.parseBoolean (getString (key, ""+defaultValue)); + } + + static public final void setBoolean (String key, boolean value) { + setString (key, ""+value); + } + + // ======================================== + static public final int getInt (String key) { + return Integer.parseInt (getString (key)); + } + + static public final int getInt (String key, int defaultValue) { + try { + return Integer.parseInt (getString (key, ""+defaultValue)); + } catch (Exception e) { + return defaultValue; + } + } + + static public final void setInt (String key, int value) { + setString (key, ""+value); + } + + // ======================================== + static public final float getFloat (String key) { + return Float.parseFloat (getString (key)); + } + + static public final float getFloat (String key, float defaultValue) { + try { + return Float.parseFloat (getString (key, ""+defaultValue)); + } catch (Exception e) { + return defaultValue; + } + } + + static public final void setFloat (String key, float value) { + setString (key, ""+value); + } + + // ======================================== + static public final double getDouble (String key) { + return Double.parseDouble (getString (key)); + } + + static public final double getDouble (String key, double defaultValue) { + try { + return Double.parseDouble (getString (key, ""+defaultValue)); + } catch (Exception e) { + return defaultValue; + } + } + + static public final void setDouble (String key, double value) { + setString (key, ""+value); + } + + // ======================================== + static public final T getEnum (String key, T defaultValue) { + return Util.toEnum (getString (key, ""+defaultValue), defaultValue); + } + + // ======================================== + static public final DecimalFormat indexFormat = new DecimalFormat ("-0000"); + + static public final Vector getList (String key, String defaultValue) { + Vector result = new Vector (); + int size = Integer.parseInt (getString (key+"-size", "1")); + result.add (getString (key, defaultValue)); + for (int i = 1; i < size; i++) + result.add (getString (key+indexFormat.format (i))); + return result; + } + + static public final void setList (String key, Vector value) { + // XXX suppression des anciennes valeurs + int size = value.size (); + int max = Integer.parseInt (getString (key+"-max", "10")); + setString (key+"-max", ""+max); + setString (key+"-size", ""+size); + setString (key, value.elementAt (0)); + size = Math.min (size, max); + for (int i = 1; i < size; i++) + setString (key+indexFormat.format (i), value.elementAt (i)); + } + + // ======================================== + // XXX faire un defaultValue en vector + static public final void loadJList (String key, JList jList, String defaultValue) { + Vector list = getList (key, defaultValue); + jList.setListData (list); + jList.setSelectedIndex (0); + } + + static public final void saveJList (String key, JList jList) { + Vector result = new Vector (); + ListModel model = jList.getModel (); + int size = model.getSize (); + for (int i = 0; i < size; i++) + result.add (model.getElementAt (i)); + int index = jList.getSelectedIndex (); + if (index >= 0) { + result.insertElementAt (result.remove (index), 0); + jList.setListData (result); + jList.setSelectedIndex (0); + } + setList (key, result); + } + + // ======================================== + // XXX faire un defaultValue en vector + static public final void loadJComboBox (String key, JComboBox jComboBox, String defaultValue) { + Vector list = getList (key, defaultValue); + jComboBox.removeAllItems (); + for (int i = 0; i < list.size (); i++) + jComboBox.addItem (list.elementAt (i)); + jComboBox.setSelectedIndex (0); + } + + static public final void loadJComboBoxInteger (String key, JComboBox jComboBox, String defaultValue) { + Vector list = getList (key, defaultValue); + jComboBox.removeAllItems (); + for (int i = 0; i < list.size (); i++) { + try { + jComboBox.addItem (Integer.parseInt (list.elementAt (i))); + } catch (Exception e) { + } + } + jComboBox.setSelectedIndex (0); + } + + static public final void saveJComboBox (String key, JComboBox jComboBox) { + // XX peut être ajouter la valeur éditer dans la liste + Vector result = new Vector (); + int size = jComboBox.getItemCount (); + for (int i = 0; i < size; i++) + result.add (jComboBox.getItemAt (i)); + int index = jComboBox.getSelectedIndex (); + if (index < 0) + result.insertElementAt ((String) jComboBox.getSelectedItem (), 0); + else + result.insertElementAt (result.remove (index), 0); + if (index != 0) { + jComboBox.removeAllItems (); + for (int i = 0; i < result.size (); i++) + jComboBox.addItem (result.elementAt (i)); + jComboBox.setSelectedIndex (0); + } + setList (key, result); + } + + static public final void saveJComboBoxInteger (String key, JComboBox jComboBox) { + // XX peut être ajouter la valeur éditer dans la liste + Vector result = new Vector (); + int size = jComboBox.getItemCount (); + for (int i = 0; i < size; i++) + result.add (""+jComboBox.getItemAt (i)); + int index = jComboBox.getSelectedIndex (); + if (index < 0) + result.insertElementAt ((String) jComboBox.getSelectedItem (), 0); + else + result.insertElementAt (result.remove (index), 0); + setList (key, result); + } + + // ======================================== + /** Text format used to represent coordonates (i.e. [x=123,y=456] ). */ + static public final MessageFormat coordonateFormat = new MessageFormat ("[x={0,number,integer},y={1,number,integer}]"); + + /** + Set component location from a constant coordonates in configuration. + @param key the name used to denoted the contant. + @param component modified by the coordonates retreived in configuration. + @param defaultLocation default coordonates used if non key present. + */ + static public final void loadLocation (String key, Component component, Point defaultLocation) { + try { + Object [] location = coordonateFormat.parse (getString (key+locationPostfix), + new java.text.ParsePosition (0)); + component.setLocation (((Number) location [0]).intValue (), ((Number) location [1]).intValue ()); + } catch (Exception e) { + component.setLocation (defaultLocation); + } + } + + /** + Save constant coordonates to configuration form a component location. + @param key the name used to denoted the contant. + @param component used to set the contant coordonates value. + */ + static public final void saveLocation (String key, Component component) { + Point location = component.getLocation (); + setString (key+locationPostfix, + coordonateFormat.format (new Object [] {location.x, location.y}, + new StringBuffer(), null).toString ()); + } + + // ======================================== +} diff --git a/src/java/misc/Controller.java b/src/java/misc/Controller.java new file mode 100644 index 0000000..15b4c31 --- /dev/null +++ b/src/java/misc/Controller.java @@ -0,0 +1,216 @@ +package misc; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Container; +import java.awt.Image; +import java.awt.Point; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Hashtable; +import java.util.List; + +import javax.swing.AbstractButton; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JOptionPane; +import javax.swing.SwingUtilities; + +public abstract class Controller implements ApplicationManager, OwnFrame, ActionListener { + + // ======================================== + // remplace + // a placer avant la création de socket (y compris celle pour X11 faite par swing) + static { + java.util.Properties props = System.getProperties (); + props.setProperty ("java.net.preferIPv4Stack", ""+true); + System.setProperties (props); + } + + // ======================================== + static public final List actionsNames = Arrays.asList ("Quit"); + @SuppressWarnings("unchecked") + static public final Hashtable actionsMethod = Util.collectMethod (Controller.class, actionsNames); + + public void actionPerformed (ActionEvent e) { + Util.actionPerformed (actionsMethod, e, this); + } + + // ======================================== + protected JFrame jFrame; + protected Component component; + protected JMenuBar menuBar; + public JFrame getJFrame () { return jFrame; } + + // ======================================== + public Controller (T t) { + createModel (t); + createAndShowFrame (createGUI (), createMenuBar ()); + Plugins.hideSate (); + } + + // ======================================== + // XXX ATTENTION + // createModel est appelé avant la création des attribut pas le constructer de "super" + // les attrubuts ne doivent donc pas être initialisé durant leur déclaration + protected abstract void createModel (T t); + public abstract String getTitle (); + public abstract Image getIcon (); + + public void updateBundle () { + SwingUtilities.invokeLater (new Runnable () { + public void run () { + jFrame.setTitle (getTitle ()); + } + }); + } + + // ======================================== + public void reborn () { + // XXX on pert les boites de dialog attachées à l'ancienne fenêtre + jFrame.dispose (); + jFrame.setJMenuBar (null); + jFrame.getContentPane ().remove (component); + createAndShowFrame (component, menuBar); + } + + // ======================================== + protected void createAndShowFrame (final Component component, final JMenuBar menuBar) { + final JFrame newJFrame = new JFrame (getTitle ()); + newJFrame.setIconImage (getIcon ()); + newJFrame.setJMenuBar (menuBar); + newJFrame.getContentPane ().add (component, BorderLayout.CENTER); + newJFrame.pack (); + newJFrame.addWindowListener (new WindowAdapter () { + public void windowClosing (WindowEvent e) { + Config.saveLocation ("Frame", jFrame); + if (tryClosingWindows ()) + quit (); + else + reborn (); + } + }); + if (jFrame == null) { + newJFrame.setLocationRelativeTo (null); + Config.loadLocation ("Frame", newJFrame, newJFrame.getLocation ()); + } else + newJFrame.setLocation (jFrame.getLocation ()); + jFrame = newJFrame; + this.component = component; + this.menuBar = menuBar; + jFrame.setVisible (true); + newJFrame (); + } + + // ======================================== + protected void newJFrame () { } + + // ======================================== + protected Component createGUI () { + Bundle.addBundleObserver (this); + return new JLabel ("Empty controller"); + } + + // ======================================== + protected JMenuBar createMenuBar () { + return null; + } + + // ======================================== + public void quit () { + System.exit (0); + } + + // ======================================== + public void actionQuit () { + Config.saveLocation ("Frame", jFrame); + if (tryClosingWindows ()) + quit (); + } + + // ======================================== + protected boolean tryClosingWindows () { + switch (JOptionPane.showConfirmDialog (jFrame, "Do you want to quit ?", "Quit", + JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE)) { + case JOptionPane.YES_OPTION: + return true; + case JOptionPane.NO_OPTION: + case JOptionPane.CLOSED_OPTION: + return false; + } + return true; + } + + // ======================================== + public void addMenuItem (JMenu... jMenu) { + Util.addMenuItem (actionsNames, this, jMenu[0]); + } + + // ======================================== + public void addIconButtons (Container... containers) { + Util.addIconButton (actionsNames, this, containers[0]); + } + + // ======================================== + public void addActiveButtons (Hashtable buttons) { + } + + // ======================================== + static public void main (String[] args) { + Config.load ("Misc"); + Bundle.load ("Help"); + Bundle.load ("Controller"); + + @SuppressWarnings ("serial") + class JControllerTestMenu extends JMenuBar { + public JControllerTestMenu (ApplicationManager controllerManager, ApplicationManager helpManager) { + setLayout (new BoxLayout (this, BoxLayout.X_AXIS)); + JMenu fileMenu = Util.addJMenu (this, "File"); + add (Box.createHorizontalGlue ()); + JMenu helpMenu = Util.addJMenu (this, "Help"); + controllerManager.addMenuItem (fileMenu); + helpManager.addMenuItem (helpMenu); + Hashtable buttons = new Hashtable (); + Util.collectButtons (buttons, fileMenu); + Util.collectButtons (buttons, helpMenu); + controllerManager.addActiveButtons (buttons); + helpManager.addActiveButtons (buttons); + } + }; + + class ControllerTest extends Controller { + public ControllerTest () { super (""); } + protected void createModel (String s) {} + public String getTitle () { return null; } + public Image getIcon () { + return Util.loadImageIcon (Config.getString ("MiscIcon", + Config.imagesJarDir+"misc"+Config.imagesExt)).getImage (); + } + protected JMenuBar createMenuBar () { + return new JControllerTestMenu (this, new HelpManager (this, "Misc")); + } + protected Component createGUI () { + return new JLabel ("Affichage de la licence."); + } + protected boolean tryClosingWindows () { + Config.save ("Misc"); + return true; + } + }; + SwingUtilities.invokeLater (new Runnable () { + public void run () { + new ControllerTest (); + } + }); + } + + // ======================================== +} diff --git a/src/java/misc/DatePanel.java b/src/java/misc/DatePanel.java new file mode 100644 index 0000000..f824abc --- /dev/null +++ b/src/java/misc/DatePanel.java @@ -0,0 +1,242 @@ +// ================================================================================ +// Copyright (c) Francois Merciol 2002 +// Project : Classe +// Name : DatePanel.java +// Language : Java +// Type : Source file for DatePanel class +// Author : Francois Merciol +// Creation : 02/09/2002 +// ================================================================================ +// History +// -------------------------------------------------------------------------------- +// Author : +// Date : +// Reason : +// ================================================================================ +// To be improved : +// ================================================================================ +package misc; + +import java.awt.Dimension; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import javax.swing.JTextField; + +/** + Champ de saisie d'une date au format dd/MM/yyyy. + Autres fonctions :
    +
  • (espace) : Ă©fface le champs +
  • '+' ou (fleche haut) : incrĂ©mente d'un jour +
  • '-' ou (fleche bas) : dĂ©crĂ©mente d'un jour +
  • (retoure chariot) ou (perte de "focus") : transforme une date sur deux chiffres en relatif Ă  la date actuelle. +
+*/ +@SuppressWarnings ("serial") public class DatePanel extends JTextField implements KeyListener, FocusListener { + + // ======================================== + /** Format d'une date en francais. */ + static public final SimpleDateFormat dateFormat = new SimpleDateFormat ("dd/MM/yyyy"); + /** Ensemble des caractères ayant une action sur ce champs. + Il faudrais ajouter le retoure chariot et le caractère d'effacement. */ + static public final String acceptChar = "0123456789/+- "+KeyEvent.VK_UP+KeyEvent.VK_DOWN; + + // ======================================== + /** + Création d'une date en reservant une taille maximal puis mis à la date actuelle. + */ + public DatePanel () { + super ("31/12/9999"); + addKeyListener (this); + addFocusListener (this); + set (new Date ()); + } + + // ======================================== + // api + // ======================================== + /** + Interprétation d'une année sur 2 chiffres. + */ + public void enter () { + tune2ky (); + } + + // ======================================== + /** + Fixe une date. + @param date la nouvelle valeur. + */ + public void set (Date date) { + setText (dateFormat.format (date)); + } + + // ======================================== + // Listener + // ======================================== + /** Interface KeyListener. */ + public void keyPressed (KeyEvent e) { + } + + // ======================================== + /** Interface KeyListener. */ + public void keyReleased (KeyEvent e) { + int c = e.getKeyCode (); + switch (c) { + case KeyEvent.VK_UP: + incr (1); + e.consume (); + return; + case KeyEvent.VK_DOWN: + incr (-1); + e.consume (); + return; + } + } + + // ======================================== + /** Interface KeyListener. */ + public void keyTyped (KeyEvent e) { + char c = e.getKeyChar (); + + switch (c) { + case KeyEvent.VK_ENTER: + enter (); + return; + case ';': + // le ; sera s�parateur + e.consume (); + return; + case KeyEvent.VK_BACK_SPACE: + // on laisse java faire + return; + case KeyEvent.VK_SPACE: + setText (""); + e.consume (); + return; + case '+': + incr (1); + e.consume (); + return; + case '-': + incr (-1); + e.consume (); + return; + } + + if (acceptChar.indexOf (c) < 0) { + e.consume (); + return; + } + + if (getSelectedText () == null && getText ().length () >= 12) { + e.consume (); + return; + } + } + + // ======================================== + /** Interface FocusListener. */ + public void focusGained (FocusEvent e) { + } + + // ======================================== + /** Interface FocusListener. */ + public void focusLost (FocusEvent e) { + tune2ky (); + } + + // ======================================== + // Listener spécial + // ======================================== + /** + Interprétation d'une année sur 2 chiffre. + */ + public void tune2ky () { + try { + Date origDate = dateFormat.parse (getText ().trim ()); + GregorianCalendar cal = new GregorianCalendar (); + cal.setTime (origDate); + int y = cal.get (Calendar.YEAR); + if (y < 100) { + cal.add (Calendar.YEAR, cur2ky); + if (y < min2ky) + cal.add (Calendar.YEAR, 100); + else if (y > max2ky) + cal.add (Calendar.YEAR, -100); + } + setText (dateFormat.format (cal.getTime ())); + getSize (tmpSize); + if (tmpSize.width != 0) + size = tmpSize; + } catch (Exception ex) { + } + } + + // ======================================== + /** Taille actuelle du champ. */ + private Dimension tmpSize = new Dimension (); + /** Derniére taille maximum utilisée. */ + private Dimension size; + + // ======================================== + /** + @return la taille maximum déjà utilisé. + */ + public Dimension getPreferredSize () { + if (size != null) + return size; + return super.getPreferredSize (); + } + + // ======================================== + /** + @return la taille maximum déjà utilisé. + */ + public Dimension getMinimumSize () { + if (size != null) + return size; + return super.getPreferredSize (); + } + + // ======================================== + /** + Change la date d'une certain nombre de jours. + @param incr le nombre de jour à avancer (diminuer si négatif). + */ + public void incr (int incr) { + try { + tune2ky (); + Date origDate = dateFormat.parse (getText ().trim ()); + GregorianCalendar cal = new GregorianCalendar (); + cal.setTime (origDate); + cal.add (Calendar.DATE, incr); + setText (dateFormat.format (cal.getTime ())); + } catch (Exception ex) { + } + } + + // ======================================== + // valeurs d'une année sur 2 chiffres + // ======================================== + /** XXX commentaire sur l'optimisation d'une année sur 2 chiffre. */ + static public int min2ky, max2ky, cur2ky; + + /** Initialisation des champs. */ + static { + GregorianCalendar cal = new GregorianCalendar (); + cal.setTime (new Date ()); + int y = cal.get(Calendar.YEAR); + int yMod1ky = y % 100; + min2ky = yMod1ky - 50; + max2ky = yMod1ky + 50; + cur2ky = y - yMod1ky; + } + + // ======================================== +} diff --git a/src/java/misc/DimensionDouble.java b/src/java/misc/DimensionDouble.java new file mode 100644 index 0000000..58379bf --- /dev/null +++ b/src/java/misc/DimensionDouble.java @@ -0,0 +1,62 @@ +// ================================================================================ +// François MERCIOL 2015 +// Name : Dimension2D.java +// Language : Java +// Author : François Merciol +// CopyLeft : Cecil B +// Creation : 2015 +// Version : 0.1 (xx/xx/xx) +// ================================================================================ +package misc; + +import java.awt.geom.Dimension2D; + +@SuppressWarnings ("overrides") +public class DimensionDouble extends Dimension2D { + + // ======================================== + public double width, height; + + public DimensionDouble () { + } + + public DimensionDouble (double width, double height) { + this.width = width; + this.height = height; + } + + public boolean equals (Object obj) { + try { + DimensionDouble dim = (DimensionDouble) obj; + return width == dim.width && height == dim.height; + } catch (Exception e) { + return false; + } + } + + // ======================================== + public double getHeight () { + return height; + } + + public double getWidth () { + return width; + } + + public void setSize (Dimension2D d) { + width = d.getWidth (); + height = d.getHeight (); + } + + public void setSize (double width, double height) { + this.width = width; + this.height = height; + } + + // ======================================== + public String toString () { + return DimensionDouble.class.getSimpleName ()+"[width="+width+",height="+height+"]"; + } + + // ======================================== +} diff --git a/src/java/misc/EasterEgg.java b/src/java/misc/EasterEgg.java new file mode 100644 index 0000000..65aadb6 --- /dev/null +++ b/src/java/misc/EasterEgg.java @@ -0,0 +1,53 @@ +package misc; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; + +public class EasterEgg { + public static final SimpleDateFormat birthFormat = new SimpleDateFormat("MMdd"); + + String defaultValue; + + class Periode { + String start; + + String stop; + + String value; + + Periode(String start, String stop, String value) { + this.start = start; + this.stop = stop; + this.value = value; + } + } + + ArrayList periodes = new ArrayList<>(); + + public EasterEgg(String defaultValue) { + this.defaultValue = defaultValue; + } + + public void addPeriode(String start, String stop, String value) { + this.periodes.add(new Periode(start, stop, value)); + } + + public String getValue() { + return get(new Date()); + } + + public String get(Date date) { + String day = birthFormat.format(date); + for (Periode periode : this.periodes) { + if (periode.start.compareTo(periode.stop) <= 0) { + if (day.compareTo(periode.start) >= 0 && day.compareTo(periode.stop) <= 0) + return periode.value; + continue; + } + if (day.compareTo(periode.start) <= 0 || day.compareTo(periode.stop) >= 0) + return periode.value; + } + return this.defaultValue; + } +} diff --git a/src/java/misc/Guide.java b/src/java/misc/Guide.java new file mode 100644 index 0000000..70092ac --- /dev/null +++ b/src/java/misc/Guide.java @@ -0,0 +1,208 @@ +package misc; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Frame; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.HyperlinkListener; +import javax.swing.text.html.HTMLDocument; +import javax.swing.text.html.HTMLFrameHyperlinkEvent; + +@SuppressWarnings ("serial") public class Guide extends HtmlDialog { + + // ======================================== + static public final int H_SPACE = 5; + static public final int V_SPACE = 2; + + // ======================================== + Color standardBackground = Color.gray; + Color highlightColor = Color.orange; + Component [] component; + Color [] componentBackgroundColor; + int next = -1; + boolean [] done; + + // ======================================== + public Guide (Frame frame, String titleName, String fileName, Component [] component, + Color standardBackground, Color highlightColor) { + super (frame, titleName, fileName); + this.component = component; + this.standardBackground = standardBackground; + this.highlightColor = highlightColor; + done = new boolean [component.length]; + componentBackgroundColor = new Color [component.length]; + for (int i = 0; i < component.length; i++) + if (component [i] != null) + componentBackgroundColor [i] = component [i].getBackground (); + reset (); + editorPane.addHyperlinkListener (new Hyperactive ()); + } + + // ======================================== + class Hyperactive implements HyperlinkListener { + + public void hyperlinkUpdate (HyperlinkEvent e) { + if (e.getEventType () == HyperlinkEvent.EventType.ACTIVATED) { + String file = e.getURL ().getPath (); + String command = file.substring (file.lastIndexOf ("/")+1); + int idx = command.indexOf ("?"); + if (idx >= 0) + command = command.substring (0, idx); + if ("Reset".equals (command)) { + reset (); + setNext (); + } else if (command.startsWith ("ActionG")) { + String [] bound = command.substring ("ActionG".length ()).split ("\\-"); + flipGroup (Integer.parseInt (bound[0]), Integer.parseInt (bound[1])); + } else if (command.startsWith ("Action")) + flipStep (Integer.parseInt (command.substring ("Action".length ()))); + else if (e instanceof HTMLFrameHyperlinkEvent) { + HTMLFrameHyperlinkEvent evt = (HTMLFrameHyperlinkEvent) e; + HTMLDocument doc = (HTMLDocument) editorPane.getDocument (); + doc.processHTMLFrameHyperlinkEvent (evt); + } else { + try { + editorPane.setPage (e.getURL ()); + } catch (Throwable t) { + Log.keepLastException ("Guide::hyperlinkUpdate", t); + } + } + } + } + } + + // ======================================== + public void setVisible (boolean visible) { + reset (); + super.setVisible (visible); + if (visible) { + setNext (); + editorPane.scrollToReference ("Top"); + } + } + + // ======================================== + public void changeHtmlClassAction (String actionName, String className) { + String newPage = editorPane.getText (). + replaceAll ("]*name=\"Action("+actionName+")\"[^>]*>", + "
"). + replaceAll ("]*name=\"Action("+actionName+")\"[^>]*>", + ""); + editorPane.setText (newPage); + } + + // ======================================== + public void reset () { + next = -1; + if (done == null) + return; + for (int step = 0; step < done.length; step++) { + done [step] = false; + if (component [step] != null) + component [step].setBackground (componentBackgroundColor [step]); + } + // XXX pb d'affichage + editorPane.getText (); + try { + Thread.sleep (200); + } catch (InterruptedException e) { + } + changeHtmlClassAction ("(G[0-9]+-)?[0-9]+", "Unknown"); + editorPane.scrollToReference ("Top"); + } + + // ======================================== + public void unsetNext () { + if (next < 0) + return; + changeHtmlClassAction (""+next, "Unknown"); + if (component [next] != null) + component [next].setBackground (componentBackgroundColor [next]); + next = -1; + } + + // ======================================== + public void setNext () { + if (next >= 0 && ! done [next]) + return; + next = -1; + for (int step = 0; step < done.length; step++) + if (!done [step]) { + next = step; + changeHtmlClassAction (""+next, "Todo"); + if (component [next] != null) + component [next].setBackground (highlightColor); + editorPane.scrollToReference ("Action"+next); + return; + } + } + + // ======================================== + private void stepIsDone (int step) { + done [step] = true; + changeHtmlClassAction (""+step, "Done"); + if (component [step] != null) + component [step].setBackground (componentBackgroundColor [step]); + } + + // ======================================== + private void stepIsUnknown (int step) { + done [step] = false; + changeHtmlClassAction (""+step, "Unknown"); + } + + // ======================================== + public void flipStep (int step) { + unsetNext (); + if (done [step]) + stepIsUnknown (step); + else + stepIsDone (step); + checkGroup (); + setNext (); + } + + // ======================================== + public void flipGroup (int step1, int step2) { + unsetNext (); + boolean allDone = true; + for (int step = step1; step <= step2; step++) + if (!done [step]) { + allDone = false; + break; + } + if (allDone) { + for (int step = step1; step <= step2; step++) + stepIsUnknown (step); + changeHtmlClassAction ("G"+step1+"-"+step2, "Unknown"); + } else { + for (int step = step1; step <= step2; step++) + stepIsDone (step); + changeHtmlClassAction ("G"+step1+"-"+step2, "Done"); + } + checkGroup (); + setNext (); + } + + // ======================================== + public void checkGroup () { + String newPage = editorPane.getText (); + Pattern pattern = Pattern.compile ("]*name=\"ActionG(([0-9]+)-([0-9]+))\"[^>]*>"); + Matcher matcher = pattern.matcher (newPage); + while (matcher.find()) { + int step1 = Integer.parseInt (matcher.group (2)); + int step2 = Integer.parseInt (matcher.group (3)); + boolean allDone = true; + for (int step = step1; step <= step2; step++) + if (!done [step]) { + allDone = false; + break; + } + changeHtmlClassAction ("G"+step1+"-"+step2, allDone ? "Done" : "Unknown"); + } + } + + // ======================================== +} diff --git a/src/java/misc/HelpManager.java b/src/java/misc/HelpManager.java new file mode 100644 index 0000000..889b8d1 --- /dev/null +++ b/src/java/misc/HelpManager.java @@ -0,0 +1,197 @@ +package misc; + +import java.awt.BorderLayout; +import java.awt.Container; +import java.awt.Frame; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Hashtable; +import java.util.List; +import java.util.Locale; +import java.util.Vector; +import javax.swing.AbstractButton; +import javax.swing.ImageIcon; +import javax.swing.JComboBox; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JMenu; +import javax.swing.JOptionPane; +import javax.swing.JPanel; + +import misc.Bundle; +import misc.Log; +import misc.Util; + +/** + Les drapeaux peuvent être trouvé sur http://flags.blogpotato.de/ +*/ +@SuppressWarnings ("serial") public class HelpManager implements ApplicationManager, ActionListener { + + // ======================================== + private Frame frame; + private OwnFrame controller; + private String applicationName; + private JConsole jConsole; + + // ======================================== + static public final String + //actionPackBug = "PackBug", + actionForcePack = "ForcePack", + actionBugReport = "BugReport", + actionJConsole = "JConsole"; + + static public final List actionsNames = + Arrays.asList ("About", "Licence", "Locale"); + static public final List actionsBugNames = + Arrays.asList (/*actionPackBug, */actionForcePack, actionBugReport, actionJConsole); + @SuppressWarnings("unchecked") + static public final Hashtable actionsMethod = + Util.collectMethod (HelpManager.class, actionsNames, actionsBugNames); + + public void actionPerformed (ActionEvent e) { + Util.actionPerformed (actionsMethod, e, this); + } + + // ======================================== + public HelpManager (OwnFrame controller, String applicationName) { + this.controller = controller; + this.applicationName = applicationName; + frame = new Frame (); + frame.setIconImage (controller.getIcon ()); + aboutDialog = new HtmlDialog (frame, "About", Config.textsJarDir+"About"+applicationName+Config.htmlExt); + licenceDialog = new HtmlDialog (frame, "Licence", Config.textsJarDir+applicationName+"Licence"+Config.htmlExt); + jConsole = new JConsole (frame); + //Util.packBug = Config.getBoolean (actionPackBug+Config.checkedPostfix, false); + } + + // ======================================== + private HtmlDialog aboutDialog; + private HtmlDialog licenceDialog; + + // ======================================== + public void actionAbout () { + aboutDialog.restart (); + aboutDialog.setVisible (true); + } + + // ======================================== + public void actionLicence () { + licenceDialog.restart (); + licenceDialog.setVisible (true); + } + + // ======================================== + public void actionLocale () { + actionLocale (controller); + } + + static public void actionLocale (OwnFrame controller) { + try { + Locale [] locales = Bundle.getApplicationsLocales (); + JComboBox localesList = Bundle.getJComboFlags (locales); + Locale currentLocale = Bundle.getLocale (); + int currentIndex = 0; + if (currentLocale != null) + for (int i = 0; i < locales.length; i++) + if (currentLocale.equals (locales[i])) { + currentIndex = i; + break; + } + localesList.setSelectedIndex (currentIndex); + JPanel jPanel = new JPanel (new BorderLayout ()); + jPanel.add (new JLabel (Bundle.getMessage ("ChooseLocale")), BorderLayout.PAGE_START); + jPanel.add (localesList, BorderLayout.CENTER); + + Frame frame = controller != null ? controller.getJFrame () : null; + Frame frmOpt = null; + if (frame == null) { + frmOpt = new JFrame (); + frmOpt.setVisible (true); + frmOpt.setLocationRelativeTo (null); + frmOpt.setAlwaysOnTop (true); + frame = frmOpt; + } + try { + if (JOptionPane.OK_OPTION != + JOptionPane.showConfirmDialog (frame, jPanel, Bundle.getTitle ("ChangeLocale"), + JOptionPane.OK_CANCEL_OPTION, JOptionPane.INFORMATION_MESSAGE)) + return; + } finally { + if (frmOpt != null) + frmOpt.dispose(); + } + Bundle.setLocale (locales[localesList.getSelectedIndex ()]); + Util.packWindow (frame); + } catch (Exception e) { + Log.keepLastException ("HelpManager::actionLocale", e); + } + } + + // // ======================================== + // public void actionPackBug () { + // Util.packBug = ! Util.packBug; + // Config.setBoolean (actionPackBug+Config.checkedPostfix, Util.packBug); + // ImageIcon icon = Util.loadActionIcon (actionPackBug+(Util.packBug ? "On" : "Off")); + // for (AbstractButton packBugCommand : packBugCommands) { + // packBugCommand<.setSelected (Util.packBug); + // // XXX bug Java + // packBugCommand.setIcon (icon); + // } + // } + + // ======================================== + public void actionForcePack () { + controller.reborn (); + } + + // ======================================== + public void actionBugReport () { + Log.dumpSaveDialog (controller.getJFrame ()); + } + + // ======================================== + public void actionJConsole (boolean checked) { + jConsole.setVisible (checked); + Util.updateCheckBox (actionJConsole, checked); + } + + // ======================================== + // static private Vector packBugCommands = new Vector (); + // static public void addPackBugCommand (AbstractButton packBugCommand) { + // if (packBugCommand == null) + // return; + // packBugCommands.add (packBugCommand); + // packBugCommand.setSelected (Util.packBug); + // // XXX bug Java + // packBugCommand.setIcon (Util.loadActionIcon (actionPackBug+(Util.packBug ? "On" : "Off"))); + // } + // static public void removePackBugCommand (AbstractButton packBugCommand) { + // packBugCommands.remove (packBugCommand); + // } + + // ======================================== + public void addMenuItem (JMenu... jMenu) { + Util.addMenuItem (actionsNames, this, jMenu[0]); + //Util.addCheckMenuItem (actionPackBug, this, Util.packBug, jMenu[0]); + Util.addMenuItem (actionForcePack, this, jMenu[0]); + Util.addMenuItem (actionBugReport, this, jMenu[0]); + Util.addCheckMenuItem (actionJConsole, this, jMenu[0]); + } + + // ======================================== + public void addIconButtons (Container... containers) { + Util.addIconButton (actionsNames, this, containers[0]); + //Util.addIconButton (actionPackBug, this, containers[0]); + Util.addIconButton (actionForcePack, this, containers[0]); + Util.addIconButton (actionBugReport, this, containers[0]); + } + + // ======================================== + public void addActiveButtons (Hashtable buttons) { + //addPackBugCommand (buttons.get (actionPackBug)); + Log.addDumpCommand (buttons.get (actionBugReport)); + } + // ======================================== +} diff --git a/src/java/misc/HourPanel.java b/src/java/misc/HourPanel.java new file mode 100644 index 0000000..8d37920 --- /dev/null +++ b/src/java/misc/HourPanel.java @@ -0,0 +1,253 @@ +// ================================================================================ +// Copyright (c) Francois Merciol 2002 +// Project : Perso +// Name : HourPanel.java +// Language : Java +// Type : Source file for HourPanel class +// Author : Francois Merciol +// Creation : 02/09/2002 +// ================================================================================ +// History +// -------------------------------------------------------------------------------- +// Author : +// Date : +// Reason : +// ================================================================================ +// To be improved : +// ================================================================================ +package misc; + +import java.awt.Dimension; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.text.SimpleDateFormat; +import java.util.Date; +import javax.swing.JTextField; + +/** + Champ de saisie d'une heure au format HH:mm. + Autres fonctions :
    +
  • (espace) : Ă©fface le champs +
  • (retoure chariot) ou (perte de "focus") : remet le champ au format. +
  • Le premier caractère frappĂ© Ă©fface le champs. Des caractères peuvent ĂŞtre concervĂ© si le prmier caractère + concerne un dĂ©placement ou un Ă©ffacement. +
+*/ +@SuppressWarnings ("serial") public class HourPanel extends JTextField implements KeyListener, FocusListener { + + // ======================================== + /** Format d'une heure. */ + static public final SimpleDateFormat hourFormat = new SimpleDateFormat ("HH:mm"); + /** Ensemble des caractères ayant une action sur ce champs. + Il faudrais ajouter le retoure chariot et le caractère d'effacement. */ + static public final String acceptChar = "0123456789: "; + + // ======================================== + /** mémorise si le caractère frappé et le premier dans le champs. */ + private boolean justFocus = false; + + // ======================================== + /** + Création d'une date en reservant une taille maximal puis mis à la date actuelle. + */ + public HourPanel () { + super ("23:59"); + addKeyListener (this); + addFocusListener (this); + set (new Date ()); + } + + // ======================================== + // api + // ======================================== + /** + Remet en forme le champs. + */ + public void enter () { + tuneHour (); + } + + // ======================================== + /** + Fixe une heure. + @param date la nouvelle valeur. + */ + public void set (Date date) { + setText (hourFormat.format (date)); + } + + // ======================================== + // Listener + // ======================================== + /** Dernière possition du curseur. */ + int lastJTextPos = -1; + + // ======================================== + /** Interface KeyListener. */ + public void keyPressed (KeyEvent e) { + lastJTextPos = getCaretPosition (); + } + + // ======================================== + /** Interface KeyListener. */ + public void keyReleased (KeyEvent e) { + } + + // ======================================== + /** Interface KeyListener. */ + public void keyTyped (KeyEvent e) { + char c = e.getKeyChar (); + + switch (c) { + case KeyEvent.VK_ENTER: + enter (); + return; + case ';': + // le ; sera s�parateur + e.consume (); + return; + case KeyEvent.VK_BACK_SPACE: + // on laisse java faire + justFocus = false; + return; + case KeyEvent.VK_SPACE: + setText (""); + e.consume (); + return; + } + + if (acceptChar.indexOf (c) < 0) { + e.consume (); + return; + } + + if (getSelectedText () == null) { + if (justFocus) + setText (""); + if (c != ':' && getText ().length () >= 5) { + e.consume (); + return; + } + } else { + String text = getText (); + lastJTextPos = getSelectionStart (); + setText (text.substring (0, getSelectionStart ())+ text.substring (getSelectionEnd ())); + } + justFocus = false; + processHour (e); + } + + // ======================================== + /** Interface FocusListener. */ + public void focusGained (FocusEvent e) { + justFocus = true; + } + + // ======================================== + /** Interface FocusListener. */ + public void focusLost (FocusEvent e) { + justFocus = false; + tuneHour (); + } + + // ======================================== + // Listener spécial + // ======================================== + /** + Traite l'insertion d'un caractère dur champ (chiffre ou ':'). + @param e touche frappé au clavier. + */ + public void processHour (KeyEvent e) { + char c = e.getKeyChar (); + String text = getText (); + int dpPos = text.indexOf (":"); + if (dpPos >= 0) { + if (c == ':') { + if (lastJTextPos <= dpPos) + setText (((lastJTextPos == 0) ? "0" : text.substring (0, lastJTextPos))+":"+ + text.substring (dpPos+1)); + else + setText (text.substring (0, dpPos)+":"+text.substring (lastJTextPos)); + setCaretPosition (text.indexOf (":")+1); + e.consume (); + return; + } + } else { + if (c == ':') + return; + // traiter l'apparition du : + if (text.length () < 1) + return; + if (text.charAt (0) > '2') { + // XXX pb si lastJTextPos = 0 + setText (text.charAt (0)+":"+text.substring (1)); + setCaretPosition (lastJTextPos+1); + return; + } + if (text.length () > 1) { + // XXX pb si lastJTextPos < 2 + setText (text.substring (0, 2)+":"+text.substring (2)); + setCaretPosition (lastJTextPos+1); + return; + } + } + } + + // ======================================== + /** + Remise en forme de l'heure. + */ + public void tuneHour () { + try { + String text = getText ().trim (); + if (text.isEmpty ()) { + setText ("00:00"); + return; + } + switch (text.indexOf (":")) { + case -1: + text += ":0"; + break; + case 0: + text = "0"+text; + } + if (text.indexOf (":") == (text.length ()-1)) + text += "0"; + setText (hourFormat.format (hourFormat.parse (text))); + getSize (tmpSize); + if (tmpSize.width != 0) + size = tmpSize; + } catch (Exception ex) { + } + } + + // ======================================== + /** Taille actuelle du champ. */ + private Dimension tmpSize = new Dimension (); + /** Derni�re taille maximum utilisée. */ + private Dimension size; + + // ======================================== + /** + @return la taille maximum déjà utilisé. + */ + public Dimension getPreferredSize () { + if (size != null) + return size; + return super.getPreferredSize (); + } + + // ======================================== + /** + @return la taille maximum déjà utilisé. + */ + public Dimension getMinimumSize () { + if (size != null) + return size; + return super.getPreferredSize (); + } + + // ======================================== +} diff --git a/src/java/misc/HtmlDialog.java b/src/java/misc/HtmlDialog.java new file mode 100644 index 0000000..f9fe778 --- /dev/null +++ b/src/java/misc/HtmlDialog.java @@ -0,0 +1,85 @@ +package misc; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.Frame; +import java.io.IOException; +import java.net.URL; +import java.text.MessageFormat; +import javax.swing.BorderFactory; +import javax.swing.JEditorPane; +import javax.swing.JLabel; +import javax.swing.JScrollPane; +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.HyperlinkListener; +import javax.swing.text.html.HTMLDocument; +import javax.swing.text.html.HTMLFrameHyperlinkEvent; + +// XXX rechercher le fichier à afficher en fonction de la langue +// XXX mise à jour dynamique en cas de changement de langue + +@SuppressWarnings ("serial") public class HtmlDialog extends TitledDialog { + + // ======================================== + public JEditorPane editorPane; + + protected URL startedURL; + + // ======================================== + public HtmlDialog (Frame frame, String titleName, String fileName) { + super (frame, titleName); + editorPane = new JEditorPane (); + changeFileName (fileName); + editorPane.setBackground (getBackground ()); + editorPane.setBorder (BorderFactory.createCompoundBorder (BorderFactory.createRaisedBevelBorder (), + BorderFactory.createEmptyBorder (2, 5, 2, 5))); + editorPane.setEditable (false); + editorPane.setCaretPosition (0); + editorPane.scrollToReference ("Top"); + JScrollPane editorScrollPane = new JScrollPane (editorPane); + editorScrollPane.setVerticalScrollBarPolicy (JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); + editorScrollPane.setPreferredSize (new Dimension (600, 300)); + getContentPane ().add (editorScrollPane, BorderLayout.CENTER); + } + + // ======================================== + public void changeFileName (String fileName) { + try { + URL newURL = Config.getDataUrl (fileName); + if (newURL.equals (startedURL)) + return; + startedURL = newURL; + restart (); + } catch (Exception e) { + } + } + + // ======================================== + HyperlinkListener hyperlinkListener = new HyperlinkListener () { + public void hyperlinkUpdate (HyperlinkEvent e) { + if (e.getEventType () == HyperlinkEvent.EventType.ACTIVATED) { + if (e instanceof HTMLFrameHyperlinkEvent) { + ((HTMLDocument) editorPane.getDocument ()).processHTMLFrameHyperlinkEvent ((HTMLFrameHyperlinkEvent) e); + } else { + try { + editorPane.setPage (e.getURL ()); + } catch (Exception ioe) { + System.err.println ("IOE: " + ioe); + } + } + } + } + }; + + public void restart () { + try { + editorPane.setPage (startedURL); + editorPane.setDragEnabled (true); + editorPane.removeHyperlinkListener (hyperlinkListener); + editorPane.addHyperlinkListener (hyperlinkListener); + } catch (Exception e) { + } + } + + // ======================================== +} diff --git a/src/java/misc/ImagePreview.java b/src/java/misc/ImagePreview.java new file mode 100644 index 0000000..0d06473 --- /dev/null +++ b/src/java/misc/ImagePreview.java @@ -0,0 +1,89 @@ +package misc; + +import javax.swing.*; +import java.beans.*; +import java.awt.*; +import java.io.File; + +/* ImagePreview.java by FileChooserDemo2.java. */ +@SuppressWarnings("serial") +public class ImagePreview extends JComponent + implements PropertyChangeListener { + + static public int maxInSide = 90; + static public int border = 5; + static public int maxOutSide = maxInSide+2*border; + + protected ImageIcon thumbnail = null; + protected File file = null; + protected long maxSize; + + public ImagePreview (JFileChooser fc, long maxSize) { + this.maxSize = maxSize; + setPreferredSize (new Dimension (maxOutSide, maxOutSide)); + fc.addPropertyChangeListener (this); + } + + public void loadImage () { + if (file == null || (maxSize > 0 && file.length () > maxSize)) { + thumbnail = null; + return; + } + //Don't use createImageIcon (which is a wrapper for getResource) + //because the image we're trying to load is probably not one + //of this program's own resources. + ImageIcon tmpIcon = new ImageIcon (file.getPath ()); + if (tmpIcon != null) { + thumbnail = tmpIcon; + if (tmpIcon.getIconWidth () > tmpIcon.getIconHeight ()) { + if (tmpIcon.getIconWidth () > maxInSide) + thumbnail = + new ImageIcon (tmpIcon.getImage ().getScaledInstance (maxInSide, -1, Image.SCALE_DEFAULT)); + } else { + if (tmpIcon.getIconHeight () > maxInSide) + thumbnail = + new ImageIcon (tmpIcon.getImage ().getScaledInstance (-1, maxInSide, Image.SCALE_DEFAULT)); + + } + } + } + + public void propertyChange (PropertyChangeEvent e) { + boolean update = false; + String prop = e.getPropertyName (); + + //If the directory changed, don't show an image. + if (JFileChooser.DIRECTORY_CHANGED_PROPERTY.equals (prop)) { + file = null; + update = true; + + //If a file became selected, find out which one. + } else if (JFileChooser.SELECTED_FILE_CHANGED_PROPERTY.equals (prop)) { + file = (File) e.getNewValue (); + update = true; + } + + //Update the preview accordingly. + if (update) { + thumbnail = null; + if (isShowing ()) { + loadImage (); + repaint (); + } + } + } + + protected void paintComponent (Graphics g) { + if (thumbnail == null) + loadImage (); + if (thumbnail != null) { + int x = getWidth ()/2 - thumbnail.getIconWidth ()/2; + int y = getHeight ()/2 - thumbnail.getIconHeight ()/2; + if (y < border) + y = border; + if (x < border) + x = border; + thumbnail.paintIcon (this, g, x, y); + } + } +} diff --git a/src/java/misc/JConsole.java b/src/java/misc/JConsole.java new file mode 100644 index 0000000..006aa6e --- /dev/null +++ b/src/java/misc/JConsole.java @@ -0,0 +1,77 @@ +package misc; + +import java.io.*; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; + +@SuppressWarnings ("serial") public class JConsole extends TitledDialog { + + // ======================================== + private JTextArea textArea = new JTextArea (5, 20); + public static boolean copy = true; + + // ======================================== + public JConsole (Frame frame) { + super (frame, "JConsole"); + textArea.setEditable (false); + setPreferredSize (new Dimension (550, 300)); + getContentPane ().add (new JScrollPane (textArea), BorderLayout.CENTER); + getContentPane ().add (Util.newButton ("Clear", new ActionListener () { + public void actionPerformed (ActionEvent evt) { + synchronized (textArea) { + textArea.setText (""); + } + } + }), BorderLayout.SOUTH); + if (copy) { + copyOut (true); + copyOut (false); + } + pack (); + } + + + // ======================================== + public void copyOut (boolean err) { + Thread thread = new Thread () { + public void run () { + for (;;) { + try { + PipedInputStream pin = new PipedInputStream (); + PrintStream out = new PrintStream (new PipedOutputStream (pin), true, "UTF-8"); + if (err) + System.setErr (out); + else + System.setOut (out); + BufferedReader in = new BufferedReader (new InputStreamReader (pin)); + for (;;) { + String line = in.readLine (); + synchronized (textArea) { + textArea.append (line+"\n"); + } + } + } catch (Exception e) { + textArea.append (e.toString()); + ByteArrayOutputStream buf = new ByteArrayOutputStream (); + e.printStackTrace (new PrintStream (buf)); + textArea.append (buf.toString ()); + try { + Thread.sleep (1000); + } catch (InterruptedException e2) { + } + } + } + } + }; + thread.setDaemon (true); + thread.start (); + } + + // ======================================== + public static void main (String[] arg) { + new JConsole (null); + } + + // ======================================== +} diff --git a/src/java/misc/JRemoteUpdate.java b/src/java/misc/JRemoteUpdate.java new file mode 100644 index 0000000..bad2a8f --- /dev/null +++ b/src/java/misc/JRemoteUpdate.java @@ -0,0 +1,237 @@ +package misc; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.TreeSet; +import java.util.Vector; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.SwingConstants; +import javax.swing.table.DefaultTableModel; + +import static misc.Util.GBC; +import static misc.Util.GBCNL; + +import static misc.RemoteUpdate.CheckPeriod; +import static misc.RemoteUpdate.FileDescriptor; +import static misc.RemoteUpdate.FileInfo; +import static misc.RemoteUpdate.TodoFile; + +public class JRemoteUpdate implements SwingConstants { + + static public String + actionDetails = "Details", + labelFileName = "FileName", labelLocal= "Local", labelRemote = "Remote"; + + + // ======================================== + private OwnFrame controller; + RemoteUpdate remoteUpdate; + Runnable actionAfterDownLoad; + + JPanel jPanel = Util.getGridBagPanel (); + ArrayList allMirrorInfo; + JFrame jFrame = new JFrame (); + JButton startAction; + + // ======================================== + public JRemoteUpdate (OwnFrame controller, RemoteUpdate remoteUpdate, Runnable actionAfterDownLoad) { + this.controller = controller; + this.remoteUpdate = remoteUpdate; + this.actionAfterDownLoad = actionAfterDownLoad; + jFrame.setIconImage (controller.getIcon ()); + } + + public class MirrorInfo { + RemoteUpdate.Mirror mirror; + StateNotifier notifier = new StateNotifier (); + public ProgressState progressState = new ProgressState (notifier, "Progress"); + TodoFile transfertAction, cleanAction; + JCheckBox transfertBoutton, cleanBoutton; + JLabel info; + ProgressStatePanel progressStatePanel = new ProgressStatePanel (progressState); + + MirrorInfo (RemoteUpdate.Mirror mirror, TodoFile transfertAction) { + this.mirror = mirror; + // XXX check transfertAction in Download|Upload + this.transfertAction = transfertAction; + cleanAction = transfertAction == TodoFile.Download ? TodoFile.LocalRemove : TodoFile.RemoteRemove; + transfertBoutton = Util.addCheckIcon (""+transfertAction, null, + Config.getBoolean (transfertAction+mirror.token+Config.checkedPostfix, false), + jPanel, GBC); + cleanBoutton = Util.addCheckIcon (""+cleanAction, null, + Config.getBoolean (cleanAction+mirror.token+Config.checkedPostfix, false), + jPanel, GBC); + notifier.addUpdateObserver (progressStatePanel, "Progress"); + info = new JLabel (mirror.getInfo (mirror.check (transfertAction), transfertAction)); + Util.addComponent (info, jPanel, GBC); + Util.addIconButton (actionDetails, new ActionListener () { + public void actionPerformed (ActionEvent e) { + JOptionPane.showMessageDialog (jFrame, getDetails (), Bundle.getTitle (""+actionDetails), JOptionPane.INFORMATION_MESSAGE); + } + }, jPanel); + Util.unBoxButton (jPanel); + Util.addComponent (progressStatePanel, jPanel, GBCNL); + } + public void setConfig () { + Config.setBoolean (transfertAction+mirror.token+Config.checkedPostfix, transfertBoutton.isSelected ()); + Config.setBoolean (cleanAction+mirror.token+Config.checkedPostfix, cleanBoutton.isSelected ()); + } + public void update () { + (new Thread () { + public void run () { + mirror.update (); + info.setText (mirror.getInfo (mirror.check (transfertAction), transfertAction)); + //Util.packWindow (jPanel); + } + }).start (); + } + public JScrollPane getDetails () { + @SuppressWarnings ("serial") + DefaultTableModel dataObjectModel = new DefaultTableModel () { + public Class getColumnClass (int column) { + if (column != 1) + return String.class; + return javax.swing.ImageIcon.class; + } + }; + JTable jTableObject = new JTable (dataObjectModel); + jTableObject.getTableHeader ().setReorderingAllowed (true); + jTableObject.setShowVerticalLines (false); + JScrollPane scrollPane = Util.getJScrollPane (jTableObject, true, false); + jTableObject.setFillsViewportHeight (true); + dataObjectModel.setColumnCount (6); + Util.setColumnLabels (jTableObject, new String[] {"FileName", null, "LocalDate", "LocalSize", "RemoteDate", "RemoteSize"}); + + for (TodoFile action : new TodoFile []{transfertAction, cleanAction}) + for (String fileName : mirror.check (action)) { + FileDescriptor fd = mirror.getInfo (fileName); + FileInfo lfi = fd.local, rfi = fd.remote; + dataObjectModel.addRow (new Object [] {fileName, Util.loadActionIcon (""+fd.todo+Util.ON), + FileInfo.getDate (lfi), FileInfo.getSize (lfi), + FileInfo.getDate (rfi), FileInfo.getSize (rfi)}); + } + scrollPane.setPreferredSize (new java.awt.Dimension (800, 400)); + return scrollPane; + } + public void startAction () { + (new Thread () { + public void run () { + try { + String messagePrefix = transfertAction == TodoFile.Download ? "Download" : "Upload"; + if (!(cleanBoutton.isSelected () || transfertBoutton.isSelected ())) + return; + mirror.update (); + TreeSet removed = new TreeSet (); + if (cleanBoutton.isSelected ()) + removed = mirror.performe (cleanAction, null); + TreeSet transfered = new TreeSet (); + if (transfertBoutton.isSelected ()) { + transfered = mirror.performe (transfertAction, progressState); + mirror.update (); + info.setText (mirror.getInfo (mirror.check (transfertAction), transfertAction)); + } + if (removed.size () == 0 && transfered.size () == 0) + return; + JPanel msgPanel = Util.getGridBagPanel (); + Util.addComponent (new JLabel (MessageFormat.format (Bundle.getMessage (messagePrefix+"Completed"), + transfered.size ())), msgPanel, GBCNL); + if (transfered.size () > 0) { + Util.addLabel (""+transfertAction, LEFT, msgPanel, GBCNL); + Util.addComponent (Util.getJScrollPane (new JList (new Vector (transfered))), + msgPanel, GBCNL); + } + if (removed.size () > 0) { + Util.addLabel ("Remove", LEFT, msgPanel, GBCNL); + Util.addComponent (Util.getJScrollPane (new JList (new Vector (removed))), + msgPanel, GBCNL); + } + JOptionPane.showMessageDialog (jFrame, + msgPanel, + Bundle.getTitle (""+transfertAction), JOptionPane.INFORMATION_MESSAGE); + } finally { + decrThreads (); + } + } + }).start (); + } + }; + private int nbThreads; + private boolean isDownload; + private synchronized void resetThreads (int nbThreads, boolean isDownload) { + if (nbThreads < 1) + return; + while (this.nbThreads != 0) + try { + wait (); + } catch (Exception InterruptedException) { + } + this.isDownload = isDownload; + startAction.setEnabled (false); + this.nbThreads = nbThreads; + } + private synchronized void decrThreads () { + nbThreads--; + if (nbThreads != 0) + return; + notifyAll (); + startAction.setEnabled (true); + if (isDownload && actionAfterDownLoad != null) + actionAfterDownLoad.run (); + } + + // ======================================== + public void dialog (TodoFile action, boolean checkPeriod) { + jPanel.removeAll (); + allMirrorInfo = new ArrayList (); + for (RemoteUpdate.Mirror mirror : remoteUpdate.mirrors) + allMirrorInfo.add (new MirrorInfo (mirror, action)); + for (MirrorInfo mirrorInfo : allMirrorInfo) + mirrorInfo.update (); + startAction = Util.addButton (""+action, null, jPanel, GBCNL); + startAction.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + (new Thread () { + public void run () { + resetThreads (allMirrorInfo.size (), action == TodoFile.Download); + for (MirrorInfo mirrorInfo : allMirrorInfo) + mirrorInfo.startAction (); + } + }).start (); + } + }); + if (checkPeriod) { + JPanel line = Util.getGridBagPanel (); + Util.addLabel ("CheckingPeriod", LEFT, line, GBC); + JComboBox periodChooser = Util.addEnum (CheckPeriod.class, remoteUpdate.currentPeriod (), line, GBCNL); + Util.addComponent (line, jPanel, GBCNL); + if (JOptionPane.showConfirmDialog (jFrame, jPanel, Bundle.getTitle (""+action), + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION) + Config.setString ("CheckPeriod", ""+CheckPeriod.values () [periodChooser.getSelectedIndex ()]); + } else + JOptionPane.showMessageDialog (jFrame, jPanel, Bundle.getTitle (""+action), JOptionPane. QUESTION_MESSAGE); + for (MirrorInfo mirrorInfo : allMirrorInfo) + mirrorInfo.progressState.abort (); + for (MirrorInfo mirrorInfo : allMirrorInfo) + mirrorInfo.setConfig (); + } + public void downloadDialog () { + dialog (TodoFile.Download, true); + } + public void uploadDialog () { + dialog (TodoFile.Upload, false); + } + + // ======================================== +} diff --git a/src/java/misc/LocalizedUserLabel.java b/src/java/misc/LocalizedUserLabel.java new file mode 100644 index 0000000..2ff802d --- /dev/null +++ b/src/java/misc/LocalizedUserLabel.java @@ -0,0 +1,79 @@ +// ================================================================================ +// François MERCIOL 2015 +// Name : LocalizedUserLabel.java +// Language : Java +// Author : François Merciol +// CopyLeft : Cecil B +// Creation : 2015 +// Version : 0.1 (xx/xx/xx) +// ================================================================================ +package misc; + +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.Locale; +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; + +import static misc.Util.GBC; +import static misc.Util.GBCNL; + +public class LocalizedUserLabel { + + // ======================================== + protected boolean isCustomized; + protected String labelName; + protected String currentLabel; + protected String newLabel; + protected JLabel jLabel; + + public String getLabel () { return labelName; } + public String getNewLabel () { return isCustomized ? newLabel : null; } + public String getCurrentLabel () { return currentLabel; } + public JLabel getJLabel () { return jLabel; } + + // ======================================== + public LocalizedUserLabel (String labelName, boolean isLocalized, boolean admin) { + isCustomized = isLocalized && admin; + this.labelName = labelName; + currentLabel = isLocalized ? Bundle.getUser (labelName) : labelName; + jLabel = new JLabel (currentLabel, SwingConstants.RIGHT); + if (!isCustomized) + return; + jLabel.addMouseListener (new MouseAdapter () { + public void mousePressed (MouseEvent e) { + LocalizedUserLabel.this.mousePressed (e); + } + }); + } + + // ======================================== + public void mousePressed (MouseEvent e) { + if (!isCustomized || !SwingUtilities.isRightMouseButton (e)) + return; + Locale locale = Bundle.getLocale (); + ImageIcon flag = Util.loadImageIcon (Config.dataDirname, Config.iconsDirname, Config.flagsDirname, + locale.getCountry ().toLowerCase () + Config.iconsExt); + if (flag != null) + flag.setDescription (locale.toString ()); + JLabel jLocal = new JLabel (locale.toString (), flag, SwingConstants.LEFT); + JTextField jValue = new JTextField (newLabel == null ? Bundle.getUser (labelName) : newLabel, 9); + JPanel content = Util.getGridBagPanel (); + Util.addComponent (new JLabel (), content, GBC); + Util.addComponent (jLocal, content, GBCNL); + Util.addComponent (new JLabel (labelName+" : ", SwingConstants.RIGHT), content, GBC); + Util.addComponent (jValue, content, GBCNL); + if (JOptionPane.showConfirmDialog (e.getComponent (), content, Bundle.getTitle ("Localized"), + JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) + return; + currentLabel = newLabel = jValue.getText (); + jLabel.setText (currentLabel); + } + + // ======================================== +} diff --git a/src/java/misc/Log.java b/src/java/misc/Log.java new file mode 100644 index 0000000..a29a17c --- /dev/null +++ b/src/java/misc/Log.java @@ -0,0 +1,102 @@ +package misc; + +import java.awt.Component; +import java.io.BufferedWriter; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Hashtable; +import java.util.Vector; +import javax.swing.AbstractButton; +import javax.swing.JFileChooser; +import javax.swing.filechooser.FileNameExtensionFilter; + +public class Log { + + // ======================================== + static private final SimpleDateFormat dateFormat = new SimpleDateFormat ("[yyyy/MM/dd HH:mm:ss] "); + // static private final String separator = System.getProperty ("file.separator"); + // static public final String dumpExtension = "log"; + static public final boolean debug = true; + + // ======================================== + static public void writeLog (String serviceName, String message) { + try { + BufferedWriter out = new BufferedWriter + (new FileWriter (Config.getString ("logPath", Config.logSystemDir)+Config.FS+serviceName+Config.logExt, true)); + out.write (dateFormat.format (new Date ())+message+"\n"); + out.flush (); + out.close (); + } catch (IOException e) { + } + } + + // ======================================== + static public Hashtable lastExceptions = new Hashtable (); + static public boolean keepLastException; + + // ======================================== + static public void keepLastException (String where, Throwable t) { + try { + if (debug) + t.printStackTrace (); + ByteArrayOutputStream baos = new ByteArrayOutputStream (); + PrintWriter printWriter = new PrintWriter (baos); + printWriter.println ("Exception:\nDate: "+dateFormat.format (new Date ())+"\n"); + t.printStackTrace (printWriter); + printWriter.flush (); + printWriter.close (); + lastExceptions.put (where, baos.toString ("ISO-8859-1")); + System.err.println ("Exception "+t+" find in "+where+"\nSee log file."); + keepLastException = true; + for (AbstractButton dumpCommand : dumpCommands) + dumpCommand.setEnabled (keepLastException); + } catch (Exception e2) { + } + } + + // ======================================== + static public void dumpLastException (File file) { + PrintWriter printWriter = null; + try { + printWriter = new PrintWriter (new FileWriter (file)); + for (String where : lastExceptions.keySet ()) + printWriter.println ("\n\nContext: "+where+"\n"+lastExceptions.get (where)); + printWriter.flush (); + } catch (Exception e) { + } finally { + try { + printWriter.close (); + } catch (Exception e) { + } + } + } + + // ======================================== + static public void dumpSaveDialog (Component component) { + JFileChooser dumpChooser = + new JFileChooser (Config.getString ("dumpDir", Config.logSystemDir)); + dumpChooser.setFileFilter (new FileNameExtensionFilter (Bundle.getLabel ("DumpFilter"), Config.logExt)); + dumpChooser.setDialogTitle (Bundle.getTitle ("Dump")); + if (dumpChooser.showSaveDialog (component) != JFileChooser.APPROVE_OPTION) + return; + dumpLastException (dumpChooser.getSelectedFile ()); + } + + // ======================================== + static private Vector dumpCommands = new Vector (); + static public void addDumpCommand (AbstractButton dumpCommand) { + if (dumpCommand == null) + return; + dumpCommands.add (dumpCommand); + dumpCommand.setEnabled (keepLastException); + } + static public void removeDumpCommand (AbstractButton dumpCommand) { + dumpCommands.remove (dumpCommand); + } + // ======================================== +} diff --git a/src/java/misc/MultiToolBarBorderLayout.java b/src/java/misc/MultiToolBarBorderLayout.java new file mode 100644 index 0000000..1958fd9 --- /dev/null +++ b/src/java/misc/MultiToolBarBorderLayout.java @@ -0,0 +1,318 @@ +package misc; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Insets; +import java.awt.LayoutManager2; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Hashtable; +import java.util.List; +import java.util.TreeSet; +import javax.swing.SwingConstants; + +public class MultiToolBarBorderLayout implements LayoutManager2, SwingConstants { + static public final Hashtable positionStringToInt; + static public final Hashtable positionIntToString; + + static { + positionIntToString = new Hashtable (); + positionIntToString.put (CENTER, BorderLayout.CENTER); + positionIntToString.put (NORTH, BorderLayout.NORTH); + positionIntToString.put (EAST, BorderLayout.EAST); + positionIntToString.put (SOUTH, BorderLayout.SOUTH); + positionIntToString.put (WEST, BorderLayout.WEST); + + positionStringToInt = new Hashtable (); + for (int key : positionIntToString.keySet ()) + positionStringToInt.put (positionIntToString.get (key), key); + } + + // ======================================== + int hgap; + int vgap; + Container menu; + Hashtable positions = new Hashtable (); + Component center; + + public MultiToolBarBorderLayout () { + this (0, 0); + } + public MultiToolBarBorderLayout (int hgap, int vgap) { + this.hgap = hgap; + this.vgap = vgap; + } + public int getHgap () { + return hgap; + } + public void setHgap (int hgap) { + this.hgap = hgap; + } + public int getVgap () { + return vgap; + } + public void setVgap (int vgap) { + this.vgap = vgap; + } + public void setMenu (Container menu) { + this.menu = menu; + } + private ArrayList orderedComponents = new ArrayList (); + public void setLayoutOrderedComponents (ArrayList orderedComponents) { + if (orderedComponents== null) + return; + this.orderedComponents = orderedComponents; + } + // ======================================== + public void addLayoutComponent (Component comp, Object constraints) { + synchronized (comp.getTreeLock ()) { + if ((constraints == null) || (constraints instanceof String)) + addLayoutComponent ((String)constraints, comp); + else + throw new IllegalArgumentException ("cannot add to layout: constraint must be a string (or null)"); + } + } + @Deprecated + public void addLayoutComponent (String name, Component comp) { + synchronized (comp.getTreeLock ()) { + if (name == null) + name = BorderLayout.CENTER; + Integer position = positionStringToInt.get (name); + if (position == CENTER) { + if (center != null) + // XXX ou erreur + positions.remove (comp); + center = comp; + } + positions.put (comp, position); + } + } + public void removeLayoutComponent (Component comp) { + synchronized (comp.getTreeLock ()) { + positions.remove (comp); + if (center == comp) + center = null; + } + } + public Object getConstraints (Component comp) { + try { + return positionIntToString.get (positions.get (comp)); + } catch (Exception e) { + return null; + } + } + // ======================================== + public Dimension minimumLayoutSize (Container target) { + return layoutSize (target, true); + } + public Dimension preferredLayoutSize (Container target) { + return layoutSize (target, false); + } + public Dimension maximumLayoutSize (Container target) { + return new Dimension (Integer.MAX_VALUE, Integer.MAX_VALUE); + } + Hashtable> getSortedComponents (Container target) { + ArrayList allComponents = new ArrayList (orderedComponents); + for (Component c : target.getComponents ()) + if (!allComponents.contains (c)) + allComponents.add (c); + Hashtable> sortedComponents = new Hashtable> (); + for (int pos : positionIntToString.keySet ()) + sortedComponents.put (pos, new ArrayList ()); + for (Component c : allComponents) { + Integer position = positions.get (c); + if (!c.isVisible () || position == null) + continue; + sortedComponents.get (position).add (c); + } + return sortedComponents; + } + int maxSize (ArrayList components, boolean isMinimum, boolean isHorizontal) { + int max = 0; + int componentCount = components.size (); + for (int i = 0; i < componentCount; i++) { + Component c = components.get (i); + Dimension d = isMinimum ? c.getMinimumSize () : c.getPreferredSize (); + max = isHorizontal ? Math.max (max, d.width) : Math.max (max, d.height); + } + return max; + } + TreeSet getIntermediate (ArrayList components, boolean isMinimum, boolean isHorizontal, int minSize) { + ArrayList sizes = new ArrayList (); + int componentCount = components.size (); + for (int i = 0; i < componentCount; i++) { + Component c = components.get (i); + Dimension d = isMinimum ? c.getMinimumSize () : c.getPreferredSize (); + sizes.add (isHorizontal ? d.width : d.height); + } + TreeSet result = new TreeSet (); + int maxComp = componentCount-1; + for (int i = 0; i < componentCount; i++) { + int size = 0; + for (int j = i; j < maxComp; j++) { + size += sizes.get (j); + if (size > minSize) + result.add (size); + } + maxComp = componentCount; + } + return result; + } + void swapAxes (Dimension d) { + int tmp = d.width; + d.width = d.height; + d.height = tmp; + } + Dimension fill (ArrayList components, boolean isMinimum, boolean isHorizontal, int maxWidth) { + int componentCount = components.size (); + int x = 0, y = 0, maxX = 0, rowHeight = 0; + for (int i = 0; i < componentCount; i++) { + Component c = components.get (i); + Dimension d = isMinimum ? c.getMinimumSize () : c.getPreferredSize (); + if (!isHorizontal) + swapAxes (d); + if (x == 0 || x + d.width <= maxWidth) { + if (x > 0) + x += isHorizontal ? hgap : vgap; + x += d.width; + rowHeight = Math.max (rowHeight, d.height); + continue; + } + maxX = Math.max (maxX, x); + x = d.width; + y += (isHorizontal ? vgap : hgap) + rowHeight; + rowHeight = d.height; + } + Dimension result = new Dimension (Math.max (maxX, x), y+rowHeight); + // !!! if !isHorizontal : width => main axes + return result; + } + Dimension getExtra (Hashtable> sortedComponents, + int northPos, int southPos, boolean isMinimum, boolean isHorizontal, + int menuWidth, int extraCenter) { + int minWidth = Util.max (maxSize (sortedComponents.get (CENTER), isMinimum, isHorizontal)+extraCenter, + menuWidth, + maxSize (sortedComponents.get (northPos), isMinimum, isHorizontal), + maxSize (sortedComponents.get (southPos), isMinimum, isHorizontal)); + TreeSet sizes = new TreeSet (); + sizes.add (minWidth); + sizes.addAll (getIntermediate (sortedComponents.get (northPos), isMinimum, isHorizontal, minWidth)); + sizes.addAll (getIntermediate (sortedComponents.get (southPos), isMinimum, isHorizontal, minWidth)); + int minSurface = Integer.MAX_VALUE; + int extraHeight = 0; + for (int size : sizes) { + Dimension northDim = fill (sortedComponents.get (northPos), isMinimum, isHorizontal, size); + Dimension southDim = fill (sortedComponents.get (southPos), isMinimum, isHorizontal, size); + int width = Util.max (size, northDim.width, southDim.width); + int height = northDim.height+southDim.height; + int surface = height*width; + if (surface < minSurface) { + minSurface = surface; + minWidth = width; + extraHeight = height; + } + } + Dimension result = new Dimension (minWidth, extraHeight); + if (!isHorizontal) + swapAxes (result); + return result; + } + Dimension layoutSize (Container target, boolean isMinimum) { + synchronized (target.getTreeLock ()) { + Hashtable> sortedComponents = getSortedComponents (target); + Dimension extraSide = getExtra (sortedComponents, WEST, EAST, isMinimum, false, 0, 0); + int menuWidth = menu == null ? 0 : (isMinimum ? menu.getMinimumSize () : menu.getPreferredSize ()).width; + Dimension extra = getExtra (sortedComponents, NORTH, SOUTH, isMinimum, true, menuWidth, extraSide.width); + Insets insets = target.getInsets (); + return new Dimension (extra.width+insets.left+insets.right, + extraSide.height+extra.height+insets.top+insets.bottom); + } + } + public float getLayoutAlignmentX (Container parent) { + return 0.5f; + } + public float getLayoutAlignmentY (Container parent) { + return 0.5f; + } + // ======================================== + public void invalidateLayout (Container target) { + } + public void layoutContainer (Container target) { + synchronized (target.getTreeLock ()) { + Hashtable> sortedComponents = getSortedComponents (target); + Insets insets = target.getInsets (); + int top = insets.top; + int bottom = target.getHeight () - insets.bottom; + int left = insets.left; + int right = target.getWidth () - insets.right; + + int maxWidth = right-left; + top += setComponents (target, sortedComponents.get (NORTH), insets.top, insets.left, true, maxWidth); + bottom -= setComponents (target, sortedComponents.get (SOUTH), insets.top, insets.left, true, maxWidth); + moveComponents (sortedComponents.get (SOUTH), 0, bottom-insets.top); + + int maxHeight = bottom-top; + left += setComponents (target, sortedComponents.get (WEST), top, insets.left, false, maxHeight); + right -= setComponents (target, sortedComponents.get (EAST), top, insets.left, false, maxHeight); + moveComponents (sortedComponents.get (EAST), right-insets.left, 0); + + if (center != null && center.isVisible ()) + center.setBounds (left, top, right - left, bottom - top); + target.repaint (); + } + } + private void moveComponents (ArrayList components, int dx, int dy) { + int componentCount = components.size (); + for (int i = 0; i < componentCount; i++) { + Component c = components.get (i); + c.setLocation (c.getX ()+dx, c.getY ()+dy); + } + } + private int setComponents (Container target, ArrayList components, int top, int left, boolean isHorizontal, int maxWidth) { + int x = 0, y = 0, rowHeight = 0, rowStart = 0; + int componentCount = components.size (); + for (int i = 0; i < componentCount; i++) { + Component c = components.get (i); + Dimension d = c.getPreferredSize (); + c.setSize (d.width, d.height); + if (!isHorizontal) + swapAxes (d); + if (x == 0 || x + d.width <= maxWidth) { + if (x > 0) + x += isHorizontal ? hgap : vgap; + x += d.width; + rowHeight = Math.max (rowHeight, d.height); + continue; + } + setComponents (target, left, top, components, rowStart, i, isHorizontal, y, rowHeight); + x = d.width; + y += (isHorizontal ? vgap : hgap) + rowHeight; + rowHeight = d.height; + rowStart = i; + } + setComponents (target, left, top, components, rowStart, componentCount, isHorizontal, y, rowHeight); + return y+rowHeight; + } + private void setComponents (Container target, int x, int y, + ArrayList components, int rowStart, int rowEnd, + boolean isHorizontal, int delta, int maxSize) { + for (int i = rowStart; i < rowEnd; i++) { + Component c = components.get (i); + if (isHorizontal) { + int cy = y + delta + (maxSize - c.getHeight ())/2; + c.setLocation (x, cy); + x += c.getWidth () + hgap; + } else { + int cx = x + delta + (maxSize - c.getWidth ())/2; + c.setLocation (cx, y); + y += c.getHeight () + vgap; + } + } + } + // ======================================== + public String toString () { + return getClass ().getName () + "[hgap=" + hgap + ",vgap=" + vgap + "]"; + } +} diff --git a/src/java/misc/OwnFrame.java b/src/java/misc/OwnFrame.java new file mode 100644 index 0000000..b07ae2b --- /dev/null +++ b/src/java/misc/OwnFrame.java @@ -0,0 +1,11 @@ +package misc; + +import java.awt.Image; +import javax.swing.JFrame; + +public interface OwnFrame { + public JFrame getJFrame (); + public String getTitle (); + public Image getIcon (); + public void reborn (); +} diff --git a/src/java/misc/Plugins.java b/src/java/misc/Plugins.java new file mode 100644 index 0000000..09d1d93 --- /dev/null +++ b/src/java/misc/Plugins.java @@ -0,0 +1,165 @@ +package misc; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Dimension; +import java.io.File; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Vector; +import javax.swing.ImageIcon; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTable; +import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumn; + +public class Plugins { + + // ======================================== + static public URL getDataUrl (String name) { + try { + File file = (new File (name)); + if (file.exists ()) { + URL result = file.toURI ().toURL (); + return result; + } + } catch (Exception e) { + } + try { + URL result = ClassLoader.getSystemResource (name); + return result; + } catch (Exception e) { + } + return null; + } + // ======================================== + static public ImageIcon loadImageIcon (String name) { + URL url = getDataUrl ("data/images/button/"+name+".png"); + return (url != null) ? new ImageIcon (url) : null; + } + // ======================================== + // Change Util.version too ! + static public final Long version = 20171101L; + static public final String[] stateLabel = {"not found", "old version", "updated"}; + static public final ImageIcon[] stateIcon = { loadImageIcon ("Bad"), loadImageIcon ("Warning"), loadImageIcon ("Good")}; + + static public class PluginInfo { + public String name; + public String url; + public boolean mandatory, loaded, update; + public PluginInfo (String name, String url, boolean mandatory) { + this.name = name; + this.url = url; + this.mandatory = mandatory; + } + }; + + static ArrayList pluginsInfo = new ArrayList (); + + // ======================================== + @SuppressWarnings ("rawtypes") + static public void check (String name, String url, String className, boolean mandatory, Long dateNeed) { + PluginInfo pluginInfo = new PluginInfo (name, url, mandatory); + pluginsInfo.add (pluginInfo); + try { + Class plugin = ClassLoader.getSystemClassLoader ().loadClass (className); + pluginInfo.loaded = true; + pluginInfo.update = + dateNeed == null || + dateNeed <= (Long) plugin.getDeclaredField ("version").get (null); + } catch (Exception e) { + } + } + + // ======================================== + @SuppressWarnings ("serial") + static public class ImageCellRenderer extends JLabel implements TableCellRenderer { + public Component getTableCellRendererComponent (JTable jTable, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + if (value instanceof ImageIcon) { + setIcon ((ImageIcon) value); + setText (null); + + } else { + setIcon (null); + setText ((String) value); + } + return this; + } + } + + // ======================================== + static public JDialog jDialog; + static public boolean warning = false; + + static public void showSate () { + Vector> data = new Vector> (); + boolean ready = true; + for (PluginInfo pluginInfo : pluginsInfo) { + ready &= !pluginInfo.mandatory || pluginInfo.loaded; + int state = (pluginInfo.loaded ? (pluginInfo.update ? 2 : 1) : 0); + if (state != 2) + warning = true; + data.add + (new Vector + (Arrays.asList (pluginInfo.name, stateIcon[state], stateLabel[state], (pluginInfo.update ? "" : pluginInfo.url) ))); + } + Vector columnNames = new Vector (Arrays.asList ("Plugin name", "", "State", "URL")); + @SuppressWarnings ("serial") + JTable table = new JTable (data, columnNames) { + public Component prepareRenderer (final TableCellRenderer renderer, final int row, final int column) { + final Component prepareRenderer = super.prepareRenderer (renderer, row, column); + final TableColumn tableColumn = getColumnModel ().getColumn (column); + int width = Math.max (prepareRenderer.getPreferredSize ().width, tableColumn.getPreferredWidth ()); + tableColumn.setPreferredWidth (width); + return prepareRenderer; + } + }; + table.setDefaultRenderer (Object.class, new ImageCellRenderer ()); + + final TableCellRenderer renderer = table.getTableHeader ().getDefaultRenderer (); + for (int i = 0; i < table.getColumnCount (); ++i) { + int width = renderer.getTableCellRendererComponent (table, table.getModel ().getColumnName (i), false, false, 0, i).getPreferredSize ().width; + TableColumn tableColumn = table.getColumnModel ().getColumn (i); + tableColumn.setPreferredWidth (width); + } + + JPanel container = new JPanel (); + container.setLayout (new BorderLayout()); + container.add (table.getTableHeader(), BorderLayout.PAGE_START); + container.add (table, BorderLayout.CENTER); + + if (!ready) { + Dimension containerSize = container.getSize (); + containerSize.width = Math.max (containerSize.width, 600); + containerSize.height = Math.max (containerSize.height, 100); + container.setPreferredSize (containerSize); + + container.add (new JLabel ("Would you like to continue?"), BorderLayout.PAGE_END); + if (JOptionPane.YES_OPTION != + JOptionPane.showConfirmDialog (null, container, " Can't load plugins ", JOptionPane.YES_NO_OPTION)) + System.exit (1); + return; + } + jDialog = new JDialog ((java.awt.Frame)null, " Plugins state ", false); + jDialog.getContentPane ().add (container, BorderLayout.CENTER); + jDialog.setVisible (true); + jDialog.pack (); + jDialog.setLocationRelativeTo (null); + jDialog.toFront (); + } + + static public void hideSate () { + if (jDialog == null) + return; + jDialog.pack (); + //jDialog.toFront (); + if (!warning) + jDialog.setVisible (false); + } + + // ======================================== +} diff --git a/src/java/misc/ProgressState.java b/src/java/misc/ProgressState.java new file mode 100644 index 0000000..1680f5a --- /dev/null +++ b/src/java/misc/ProgressState.java @@ -0,0 +1,67 @@ +package misc; + +import java.util.HashSet; + +public class ProgressState { + + // ======================================== + HashSet workingThreads = new HashSet (); + StateNotifier observable; + String valueName; + + static public enum State {Init, Value, End}; + public State state; + public String domain; + public int value; + public int maxValue; + + // ======================================== + public ProgressState (StateNotifier observable, String valueName) { + this.observable = observable; + this.valueName = valueName; + } + // ======================================== + public synchronized void init (String domain, int maxValue) { + this.domain = domain; + this.value = 0; + this.maxValue = maxValue; + state = State.Init; + workingThreads = new HashSet (); + workingThreads.add (Thread.currentThread ()); + observable.broadcastUpdate (valueName); + } + public synchronized void addThread (Thread thread) { + // XXX test ! State.end + workingThreads.add (thread); + } + // ======================================== + public synchronized void abort () { + workingThreads.clear (); + } + // ======================================== + public synchronized boolean isInterrupted () { + return !workingThreads.contains (Thread.currentThread ()); + } + // ======================================== + public synchronized boolean setValue (int value) { + this.value = value; + state = State.Value; + observable.broadcastUpdate (valueName); + return workingThreads.contains (Thread.currentThread ()); + } + // ======================================== + public synchronized boolean addValue (int delta) { + if (delta > 0) { + value += delta; + state = State.Value; + observable.broadcastUpdate (valueName); + } + return workingThreads.contains (Thread.currentThread ()); + } + // ======================================== + public synchronized void end () { + state = State.End; + observable.broadcastUpdate (valueName); + } + // ======================================== +} diff --git a/src/java/misc/ProgressStatePanel.java b/src/java/misc/ProgressStatePanel.java new file mode 100644 index 0000000..a6313ab --- /dev/null +++ b/src/java/misc/ProgressStatePanel.java @@ -0,0 +1,78 @@ +// ================================================================================ +// Copyright (c) Francois Merciol 2002 +// Project : Classe +// Name : DatePanel.java +// Language : Java +// Type : Source file for DatePanel class +// Author : Francois Merciol +// Creation : 02/09/2002 +// ================================================================================ +// History +// -------------------------------------------------------------------------------- +// Author : +// Date : +// Reason : +// ================================================================================ +// To be improved : +// ================================================================================ +package misc; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.JButton; +import javax.swing.JPanel; +import javax.swing.JProgressBar; + +@SuppressWarnings ("serial") public class ProgressStatePanel extends JPanel implements ActionListener { + + // ======================================== + JProgressBar jProgressBar = new JProgressBar (); + JButton stopButton = new JButton (Util.loadActionIcon ("Abort")); + ProgressState progressState; + + // ======================================== + public ProgressStatePanel (ProgressState progressState) { + this.progressState = progressState; + jProgressBar.setMaximum (1); + jProgressBar.setString (""); + jProgressBar.setStringPainted (true); + jProgressBar.setAlignmentX (Component.LEFT_ALIGNMENT); + + setLayout (new BorderLayout ()); + add (jProgressBar, BorderLayout.CENTER); + stopButton.setActionCommand ("Abort"); + stopButton.addActionListener (this); + stopButton.setEnabled (false); + add (stopButton, BorderLayout.LINE_END); + } + + public void actionPerformed (ActionEvent e) { + if (e.getSource () != stopButton) + return; + progressState.abort (); + } + + // ======================================== + public void updateProgress () { + switch (progressState.state) { + case Value: + jProgressBar.setValue (progressState.value); + break; + case Init: + jProgressBar.setString (progressState.domain); + jProgressBar.setValue (progressState.value); + jProgressBar.setMaximum (progressState.maxValue); + stopButton.setEnabled (true); + break; + case End: + jProgressBar.setString (""); + jProgressBar.setValue (0); + stopButton.setEnabled (false); + break; + } + } + + // ======================================== +} diff --git a/src/java/misc/ProxyManager.java b/src/java/misc/ProxyManager.java new file mode 100644 index 0000000..09cb21e --- /dev/null +++ b/src/java/misc/ProxyManager.java @@ -0,0 +1,110 @@ +package misc; + +import java.awt.Container; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Hashtable; +import java.util.List; +import javax.swing.AbstractButton; +import javax.swing.ButtonGroup; +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import static misc.Util.GBCNL; + +@SuppressWarnings ("serial") +public class ProxyManager implements ApplicationManager, ActionListener { + static public final String + actionSetProxy = "SetProxy", + actionNoProxy = "NoProxy", + actionSystemConfigProxy = "SystemConfigProxy", + actionManualConfigProxy = "ManualConfigProxy"; + + static public final List radioButtonNames = Arrays.asList (actionNoProxy, actionSystemConfigProxy, actionManualConfigProxy); + static public final List actionsNames = Arrays.asList (actionSetProxy); + @SuppressWarnings("unchecked") + static public final Hashtable actionsMethod = + Util.collectMethod (ProxyManager.class, actionsNames, radioButtonNames); + public void actionPerformed (ActionEvent e) { + Util.actionPerformed (actionsMethod, e, this); + } + // ======================================== + public void addMenuItem (JMenu... jMenu) { + Util.addMenuItem (actionsNames, this, jMenu[0]); + } + public void addIconButtons (Container... containers) { + Util.addIconButton (actionsNames, this, containers[0]); + } + public void addActiveButtons (Hashtable buttons) { + // disable if no network ? + } + JPanel manualPanel; + private String getConfig () { + if ("true".equals (System.getProperty ("java.net.useSystemProxies"))) + return actionSystemConfigProxy; + String host = System.getProperty ("http.proxyHost"); + String port = System.getProperty ("http.proxyPort"); + if (host == null || port == null || host.isEmpty () || port.isEmpty ()) + return actionNoProxy; + return actionManualConfigProxy; + } + public void actionSetProxy () { + JFrame jFrame = new JFrame (); + jFrame.setIconImage (controller.getIcon ()); + JPanel msgPanel = Util.getGridBagPanel (); + String proxyConfig = getConfig (); + ButtonGroup group = new ButtonGroup (); + Util.addRadioButton (actionNoProxy, this, group, proxyConfig, msgPanel, GBCNL); + Util.addRadioButton (actionSystemConfigProxy, this, group, proxyConfig, msgPanel, GBCNL); + Util.addRadioButton (actionManualConfigProxy, this, group, proxyConfig, msgPanel, GBCNL); + manualPanel = Util.getGridBagPanel (); + JTextField hostTF = new JTextField (Config.getString ("ProxyHostName", "squid"), 18); + JTextField portTF = new JTextField (Config.getString ("ProxyPort", "3128"), 6); + // System.setProperty("http.proxyUser", "user"); + // System.setProperty("http.proxyPassword", "password"); + Util.addLabelFields (manualPanel, "Host", hostTF); + Util.addLabelFields (manualPanel, "Port", portTF); + Util.addComponent (manualPanel, msgPanel, GBCNL); + Util.setEnabled (manualPanel, proxyConfig.equals (actionManualConfigProxy)); + if (JOptionPane.showConfirmDialog (jFrame, msgPanel, misc.Bundle.getTitle ("Proxy"), JOptionPane.YES_NO_OPTION) + != JOptionPane.YES_OPTION) + return; + Config.setString ("ProxyHostName", hostTF.getText ()); + Config.setString ("ProxyPort", portTF.getText ()); + proxyConfig = group.getSelection ().getActionCommand (); + if (actionSystemConfigProxy.equals (proxyConfig)) { + System.setProperty ("java.net.useSystemProxies", "true"); + System.setProperty ("http.proxyHost", ""); + } else { + System.setProperty ("java.net.useSystemProxies", "false"); + if (actionNoProxy.equals (proxyConfig)) + System.setProperty ("http.proxyHost", ""); + else if (actionManualConfigProxy.equals (proxyConfig)) { + System.setProperty ("http.proxyHost", hostTF.getText ()); + System.setProperty ("http.proxyPort", portTF.getText ()); + } + } + java.net.ProxySelector.setDefault (java.net.ProxySelector.getDefault ()); + } + public void actionNoProxy () { + Util.setEnabled (manualPanel, false); + } + public void actionSystemConfigProxy () { + Util.setEnabled (manualPanel, false); + } + public void actionManualConfigProxy () { + Util.setEnabled (manualPanel, true); + } + + // ======================================== + private OwnFrame controller; + public ProxyManager (OwnFrame controller) { + this.controller = controller; + } + // ======================================== +} diff --git a/src/java/misc/RealTime.java b/src/java/misc/RealTime.java new file mode 100644 index 0000000..050b92c --- /dev/null +++ b/src/java/misc/RealTime.java @@ -0,0 +1,73 @@ +package misc; + +@SuppressWarnings ("serial") +class NewChalenger extends RuntimeException { + public NewChalenger () { + super ("NewChalenger"); + } +} + +public class RealTime { + // ======================================== + private int nbChalengers; + private Thread leadership; + + public void start (Runnable task) { + (new Thread () { + public void run () { + if (!getLeadership ()) + return; + try { + task.run (); + } catch (NewChalenger e) { + } finally { + loseLeaderShip (); + } + } + }).start (); + } + + // ======================================== + public synchronized void waitTerminaison () { + if (leadership == Thread.currentThread ()) + return; + while (leadership != null && nbChalengers > 0) + try { + wait (); + } catch (InterruptedException e) { + } + } + + // ======================================== + public synchronized boolean getLeadership () { + if (leadership != null) { + nbChalengers++; + try { + wait (); + } catch (InterruptedException e) { + } + nbChalengers--; + if (nbChalengers != 0) { + notifyAll (); + return false; + } + } + leadership = Thread.currentThread (); + return true; + } + public synchronized void loseLeaderShip () { + leadership = null; + notifyAll (); + } + public synchronized void checkChalengers () { + if (nbChalengers != 0) + throw new NewChalenger (); + } + + // ======================================== + static public void checkChalengers (RealTime realTime) { + if (realTime != null) + realTime.checkChalengers (); + } + // ======================================== +} diff --git a/src/java/misc/RemoteUpdate.java b/src/java/misc/RemoteUpdate.java new file mode 100644 index 0000000..3647309 --- /dev/null +++ b/src/java/misc/RemoteUpdate.java @@ -0,0 +1,748 @@ +package misc; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileFilter; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.net.CookieHandler; +import java.net.CookieManager; +import java.net.CookiePolicy; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLEncoder; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.text.MessageFormat; +import java.text.NumberFormat; +import java.text.ParsePosition; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Random; +import java.util.TimeZone; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +public class RemoteUpdate { + + // ======================================== + static long delay = 2000; + static CookieManager cookieManager = new CookieManager(); + static { + try { + ((CookieManager) CookieHandler.getDefault ()).setCookiePolicy (CookiePolicy.ACCEPT_ALL); + } catch (Exception e) { + cookieManager.setCookiePolicy (CookiePolicy.ACCEPT_ALL); + CookieHandler.setDefault (cookieManager); + } + } + + // ======================================== + static public final String backExtention = "back"; + static public final int bufSize = 1024*1024; + static public final SimpleDateFormat dateFormat = new SimpleDateFormat ("yyyyMMdd"); + static public final SimpleDateFormat timeFormat = new SimpleDateFormat ("yyyyMMddHHmmss"); + static public final SimpleDateFormat displayFormat = new SimpleDateFormat ("dd/MM/yyyy HH:mm:ss"); + static public final NumberFormat numberFormat = NumberFormat.getInstance (); + + static public enum CheckPeriod { NoCheck, Day, Week, Month, Year; }; + static public final List excludeFileName = Arrays.asList ("timestamp"); + + static public enum TodoFile {LocalRemove, Download, NoChange, Upload, RemoteRemove}; + static public class FileInfo { + long date, size; + public FileInfo (long date, long size) { this.date = date; this.size = size; } + public String toString () { return "{"+displayFormat.format (new Date (date))+" "+size+"}"; } + static public String getDate (FileInfo fileInfo) { return fileInfo == null ? "" : displayFormat.format (fileInfo.date); } + static public String getSize (FileInfo fileInfo) { return fileInfo == null ? "" : ""+fileInfo.size; } + }; + + static public FilenameFilter mirrorFilter = new FilenameFilter () { + public boolean accept (File dir, String name) { + return ! (excludeFileName.contains (name) || name.endsWith ("."+backExtention) || name.endsWith (".new")); + } + }; + + // ======================================== + static public class FileDescriptor { + // ---------------------------------------- + public TodoFile todo = TodoFile.NoChange; + public FileInfo local, remote; + public String toString () { return ""+todo+" "+local+" "+remote; } + // ---------------------------------------- + public void updateTodo (TodoFile action) { + todo = TodoFile.NoChange; + if (remote == null) { + switch (action) { + case LocalRemove: + case Upload: + todo = action; + case Download: + break; + case NoChange: + case RemoteRemove: + break; + } + return; + } + if (local == null) { + switch (action) { + case RemoteRemove: + case Download: + todo = action; + case LocalRemove: + break; + case NoChange: + case Upload: + break; + } + return; + } + if (local.size != remote.size) { + switch (action) { + case LocalRemove: + todo = TodoFile.Download; + break; + case RemoteRemove: + todo = TodoFile.Upload; + break; + default: + todo = action; + } + return; + } + if (local.date+delay < remote.date) { + if (action == TodoFile.Download || action == TodoFile.LocalRemove) + todo = TodoFile.Download; + return; + } + if (local.date > remote.date+delay) { + if (action == TodoFile.Upload || action == TodoFile.RemoteRemove) + todo = TodoFile.Upload; + //System.err.print (" "+local.date+" "+remote.date+" "+action); + return; + } + } + // ---------------------------------------- + }; + + // ======================================== + public class Mirror { + String token; + File localRepository; + FilenameFilter mirrorFilter = RemoteUpdate.mirrorFilter; + TreeSet excludeFiles; + TreeMap localFiles, remoteFiles; + TreeMap allFiles; + + // ---------------------------------------- + public Mirror (File root, String... params) { + token = params[0]; + if (params.length > 1) { + localRepository = root; + for (int i = 1; i < params.length; i++) + localRepository = new File (localRepository, params[i]); + } else + localRepository = new File (root, token); + } + public String toString () { return token+": "+localFiles+" "+remoteFiles; } + public synchronized void update () { + updateRemoteFiles (); + updateLocalFiles (); + if (remoteFiles == null) + return; + TreeSet allFilesName = new TreeSet (); + allFilesName.addAll (localFiles.keySet ()); + allFilesName.addAll (remoteFiles.keySet ()); + allFiles = new TreeMap (); + for (String fileName : allFilesName) + allFiles.put (fileName, new FileDescriptor ()); + for (String fileName : localFiles.keySet ()) + allFiles.get (fileName).local = localFiles.get (fileName); + for (String fileName : remoteFiles.keySet ()) + allFiles.get (fileName).remote = remoteFiles.get (fileName); + } + public void updateTodo (TodoFile action) { + if (allFiles == null) + return; + for (String fileName : allFiles.keySet ()) { + //System.err.print ("coucou : "+fileName+" "); + allFiles.get (fileName).updateTodo (action); + //System.err.println (); + } + } + public long getSize (TreeSet filesName, TodoFile action) { + long result = 0; + if (allFiles == null || action == TodoFile.NoChange) + return result; + boolean remote = false; + switch (action) { + case LocalRemove: + case Download: + remote = true; + case NoChange: + break; + case RemoteRemove: + case Upload: + break; + } + for (String fileName : filesName) { + FileDescriptor file = allFiles.get (fileName); + if (file == null) + continue; + try { + if (file.todo == action) + result += remote ? file.remote.size : file.local.size; + } catch (Exception e) { + } + } + return result; + } + public TreeSet getFileAction (TodoFile action) { + TreeSet result = new TreeSet (); + if (allFiles == null) + return result; + for (String fileName : allFiles.keySet ()) + if (allFiles.get (fileName).todo == action) + result.add (fileName); + return result; + } + public synchronized boolean hasNewVersion () { + update (); + return check (TodoFile.Download).size () > 0; + } + public synchronized TreeSet check (TodoFile action) { + updateTodo (action); + return getFileAction (action); + } + public TreeSet performe (TodoFile action, ProgressState progressState) { + try { + TreeSet files = check (action); + if (progressState != null) + progressState.init (Bundle.getMessage (""+action), (int) getSize (files, action)); + switch (action) { + case LocalRemove: + return localRemove (this, files, progressState); + case RemoteRemove: + return remoteRemove (this, files, progressState); + case Download: + return getRemoteFiles (this, files, progressState); + case Upload: + return putRemoteFiles (this, files, progressState); + case NoChange: + break; + } + } finally { + if (progressState != null) + progressState.end (); + } + return new TreeSet (); + } + public String getInfo (TreeSet files, TodoFile action) { + return MessageFormat.format (Bundle.getMessage ("DownloadInfo"), + Bundle.getLabel (Util.toCapital (token)), + allFiles == null ? 0 : 1, files.size (), Util.toNumIn2Units (getSize (files, action))); + } + public FileDescriptor getInfo (String fileName) { + return allFiles.get (fileName); + } + + // ---------------------------------------- + private void updateLocalFiles () { + excludeFiles = new TreeSet (); + localFiles = new TreeMap (); + updateLocalFiles (localRepository, token); + } + private void updateLocalFiles (File localRepository, String name) { + if (localRepository.isFile ()) { + if (excludeFileName.contains (name) || name.endsWith ("."+backExtention)) { + excludeFiles.add (name); + return; + } + localFiles.put (name, new FileInfo (localRepository.lastModified (), localRepository.length ())); + return; + } + if (!localRepository.isDirectory ()) + return; + for (String child : localRepository.list ()) { + updateLocalFiles (new File (localRepository, child), name+"/"+child); + } + } + private void getExcludeFiles (File localRepository, String name) { + if (localRepository.isFile ()) { + localFiles.put (name, new FileInfo (localRepository.lastModified (), localRepository.length ())); + return; + } + if (!localRepository.isDirectory ()) + return; + for (String child : localRepository.list (mirrorFilter)) { + if (excludeFileName.contains (child)) + continue; + updateLocalFiles (new File (localRepository, child), name+"/"+child); + } + } + // ---------------------------------------- + private void updateRemoteFiles () { + try { + allFiles = null; + remoteFiles = null; + URLConnection urlConnection = getUrlConnection ("zipList", token); + urlConnection.connect (); + remoteFiles = new TreeMap (); + receiveZip (urlConnection, new ZipLineReader () { + public void readLine (String line) { + ParsePosition pos = new ParsePosition (0); + try { + long date = timeFormat.parse (line, pos).getTime (); + pos.setIndex (pos.getIndex ()+1); + long size = numberFormat.parse (line, pos).longValue (); + String fileName = line.substring (pos.getIndex ()+1); + remoteFiles.put (fileName, new FileInfo (date, size)); + } catch (Exception e) { + } + }}, token); + } catch (Exception e) { + System.err.println (e); + //e.printStackTrace (); + } + } + // ---------------------------------------- + }; + + // ======================================== + public String protocol; + public String serverName; + public String versionName; + public String requestModel; + public Mirror[] mirrors; + public RemoteUpdate (String protocol, String serverName, String versionName, String requestModel, String []... mirrorsParams) { + this.protocol = protocol; + this.serverName = serverName; + this.versionName = versionName; + this.requestModel = requestModel; + File root = Config.getPWD ().getParentFile (); + this.mirrors = new Mirror[mirrorsParams.length]; + int idx = 0; + for (String [] params : mirrorsParams) + mirrors[idx++] = new Mirror (root, params); + } + public CheckPeriod currentPeriod () { + return CheckPeriod.valueOf (CheckPeriod.class, Config.getString ("CheckPeriod", ""+CheckPeriod.Month)); + } + public boolean hasNewVersion () { + CheckPeriod period = currentPeriod (); + Date lastCheck = new Date (); + try { + lastCheck = dateFormat.parse (Config.getString ("LastCheck", "20150401")); + } catch (Exception e) { + } + Date today = new Date (); + long age = (today.getTime ()-lastCheck.getTime ())/(24*60*60*1000); + switch (period) { + case NoCheck: + return false; + case Day: + if (age < 1) + return false; + break; + case Week: + if (age < 7) + return false; + break; + case Month: + if (age < 31) + return false; + break; + case Year: + if (age < 366) + return false; + break; + } + Config.setString ("LastCheck", dateFormat.format (today)); + for (Mirror mirror : mirrors) + if (mirror.hasNewVersion ()) + return true; + return false; + } + + // ======================================== + private URLConnection getUrlConnection (String cmd, String arg) + throws IOException { + return getUrlConnection (MessageFormat.format (requestModel, versionName, cmd, arg)); + } + private URLConnection getUrlConnection (String uri) + throws IOException { + URL url = new URL (protocol+"://"+serverName+uri); + URLConnection urlConnection = url.openConnection (); + urlConnection.setRequestProperty ("User-Agent", "Adecwatt Agent"); + urlConnection.setUseCaches (true); + return urlConnection; + } + + static private Random random = new Random (); + static private final String charset = "UTF-8"; + static private final String LINE_FEED = "\r\n"; + static public String getBoundary () { + // XXX we must check not apears in message :-( + return "=-="+Long.toString (random.nextLong (), 36).toUpperCase ()+"=-="; + } + + // ======================================== + public abstract class ZipBuilder { + public abstract void writeEntry (String token, ZipOutputStream zipOut) throws IOException; + }; + public class TextZipBuilder extends ZipBuilder { + private TreeSet filesName; + public TextZipBuilder (TreeSet filesName) { this.filesName = filesName; } + public void writeEntry (String token, ZipOutputStream zipOut) throws IOException { + ZipEntry zipOutEntry = new ZipEntry (token); + zipOut.putNextEntry (zipOutEntry); + PrintWriter entryWriter = new PrintWriter (new OutputStreamWriter (zipOut)); + for (String fileName : filesName) + entryWriter.print (fileName+"\n"); + entryWriter.flush (); + zipOut.closeEntry (); + } + }; + public abstract class ZipFileReader { + public abstract boolean readEntry (ZipInputStream zipIn, String entryName, String token) throws IOException; + }; + public abstract class ZipLineReader extends ZipFileReader { + public boolean readEntry (ZipInputStream zipIn, String entryName, String token) throws IOException { + if (! entryName.equals (token)) + return true; + BufferedReader in = new BufferedReader (new InputStreamReader (zipIn)); + for (;;) { + String line = in.readLine (); + if (line == null) + break; + if ("".equals (line)) + continue; + readLine (line); + } + return false; + } + + public abstract void readLine (String line); + }; + + // ======================================== + public void receiveZip (URLConnection urlConnection, ZipFileReader zipFileReader, String token) { + try { + String contentType = urlConnection.getContentType (); + if (contentType == null || !contentType.startsWith ("application/zip")) + return; + ZipInputStream zipIn = new ZipInputStream (urlConnection.getInputStream ()); + for (;;) { + ZipEntry zipInEntry = zipIn.getNextEntry (); + if (zipInEntry == null) + break; + String entryName = zipInEntry.getName (); + if (!entryName.startsWith (token)) + continue; + if (!zipFileReader.readEntry (zipIn, entryName, token)) + break; + } + } catch (Exception e) { + e.printStackTrace (); + } + } + + // ======================================== + public URLConnection sendZip (String cmd, String token, ZipBuilder zipBuilder) { + try { + URLConnection urlConnection = getUrlConnection (cmd, token); + String boundary = getBoundary (); + urlConnection.setRequestProperty ("Content-Type", "multipart/form-data; boundary="+boundary); + urlConnection.setDoOutput (true); + urlConnection.setDoInput (true); + OutputStream httpStream = urlConnection.getOutputStream (); + PrintWriter httpWriter = new PrintWriter (new OutputStreamWriter (httpStream, charset), true); + httpWriter.append ("--"+boundary).append (LINE_FEED); + httpWriter.append ("Content-Disposition: form-data; name=\""+cmd+"\"; filename=\""+cmd+"\"").append (LINE_FEED); + httpWriter.append ("Content-Type: application/zip").append (LINE_FEED); + httpWriter.append ("Content-Transfer-Encoding: binary").append (LINE_FEED); + httpWriter.append (LINE_FEED).flush (); + ByteArrayOutputStream memStream = new ByteArrayOutputStream (); + ZipOutputStream zipOut = new ZipOutputStream (memStream); + + zipBuilder.writeEntry (token, zipOut); + + zipOut.flush (); + zipOut.close (); + httpWriter.flush (); + httpStream.write (memStream.toByteArray ()); + httpWriter.append (LINE_FEED).flush (); + httpWriter.append ("--"+boundary+"--").append(LINE_FEED); + httpWriter.close (); + + urlConnection.connect (); + return urlConnection; + } catch (Exception e) { + e.printStackTrace (); + } + return null; + } + + // ======================================== + public TreeSet localRemove (Mirror mirror, TreeSet filesName, ProgressState progressState) { + if (filesName == null) + filesName = new TreeSet (); + if (mirror.excludeFiles != null) + filesName.addAll (mirror.excludeFiles); + TreeSet removed = new TreeSet (); + for (String fileName : filesName) { + if (! fileName.startsWith (mirror.token)) + continue; + fileName = fileName.substring (mirror.token.length ()); + File oldFile = new File (mirror.localRepository.getAbsolutePath ()+fileName); + if (!oldFile.delete ()) + System.err.println ("coucou pb delete: "+oldFile); + removed.add (fileName); + } + return removed; + } + + // ======================================== + public TreeSet remoteRemove (Mirror mirror, TreeSet filesName, ProgressState progressState) { + if (filesName == null) + filesName = new TreeSet (); + if (mirror.excludeFiles != null) + filesName.addAll (mirror.excludeFiles); + TreeSet removed = new TreeSet (); + if (filesName.size () < 1) + return removed; + URLConnection urlConnection = sendZip ("zipRemove", mirror.token, new TextZipBuilder (filesName)); + receiveZip (urlConnection, new ZipLineReader () { + public void readLine (String line) { + removed.add (line); + }}, mirror.token); + return removed; + } + + // ======================================== + public TreeSet getRemoteFiles (Mirror mirror, TreeSet filesName, ProgressState progressState) { + TreeSet downloaded = new TreeSet (); + try { + if (filesName.size () < 1) + return downloaded; + URLConnection urlConnection = sendZip ("zipGets", mirror.token, new TextZipBuilder (filesName)); + byte[] tmp = new byte[bufSize]; + receiveZip (urlConnection, new ZipFileReader () { + public boolean readEntry (ZipInputStream zipIn, String entryName, String token) throws IOException { + File newFile = mirror.localRepository; + for (String item : entryName.substring (mirror.token.length ()).split ("/")) { + if (item == null || item.isEmpty ()) + continue; + newFile = new File (newFile, item); + } + File dirFile = newFile.getParentFile (); + dirFile.mkdirs (); + File tmpFile = File.createTempFile ("download", "tmp", dirFile); + tmpFile.deleteOnExit (); + Util.copy (zipIn, new FileOutputStream (tmpFile), tmp, progressState, false, true); + // XXX tmpFile.setLastModified (zipInEntry.getLastModifiedTime ().toMillis ()); + tmpFile.setLastModified (mirror.allFiles.get (entryName).remote.date); + if (progressState != null && progressState.isInterrupted ()) + return false; + Util.backup (newFile, Util.getExtention (newFile), backExtention); + try { + Files.move (tmpFile.toPath (), newFile.toPath (), StandardCopyOption.REPLACE_EXISTING); + } catch (Exception e) { + } + if (tmpFile.exists ()) { + System.err.println ("can't change open file!"); + tmpFile.renameTo (new File (""+newFile+".new")); + } + downloaded.add (entryName); + return true; + }}, mirror.token); + } catch (Exception e) { + e.printStackTrace (); + } + return downloaded; + } + + // ======================================== + public static boolean newFileExists (File file) { + if (file.isFile () && file.exists () && "new".equals (Util.getExtention (file))) + return true; + if (!file.isDirectory ()) + return false; + for (File subFile : file.listFiles (new FileFilter () { + public boolean accept (File file2) { + return file2.isDirectory () || (file2.isFile () && "new".equals (Util.getExtention (file2))); + } + })) + if (newFileExists (subFile)) + return true; + return false; + } + public static void renameNewFile (File file) { + if (file.isFile () && file.exists () && "new".equals (Util.getExtention (file))) { + try { + File newFile = new File (file.getParentFile (), Util.getBase (file)); + Files.move (file.toPath (), newFile.toPath (), StandardCopyOption.REPLACE_EXISTING); + } catch (Exception e) { + } + } + if (!file.isDirectory ()) + return; + for (File subFile : file.listFiles (new FileFilter () { + public boolean accept (File file2) { + return file2.isDirectory () || (file2.isFile () && "new".equals (Util.getExtention (file2))); + } + })) + renameNewFile (subFile); + } + + // ======================================== + public static void launch (File jarFile) { + try { + Runtime.getRuntime ().exec ("java -jar "+jarFile.getName (), null, jarFile.getParentFile ()); + System.exit (0); + } catch (Exception e) { + e.printStackTrace (); + } + } + + // ======================================== + public TreeSet putRemoteFiles (Mirror mirror, TreeSet filesName, ProgressState progressState) { + TreeSet uploaded = new TreeSet (); + if (filesName == null || filesName.size () < 1) + return uploaded; + try { + byte[] tmp = new byte[bufSize]; + TimeZone timeZone = TimeZone.getDefault (); + URLConnection urlConnection = sendZip ("zipPuts", mirror.token, new ZipBuilder () { + public void writeEntry (String token, ZipOutputStream zipOut) throws IOException { + for (String fileName : filesName) { + if (! fileName.startsWith (token)) + continue; + ZipEntry zipOutEntry = new ZipEntry (fileName); + File file = new File (mirror.localRepository, fileName.substring (token.length ())); + long dateMs = file.lastModified (); + dateMs -= timeZone.getOffset (dateMs); + zipOutEntry.setTime (dateMs); + zipOut.putNextEntry (zipOutEntry); + PrintWriter entryWriter = new PrintWriter (new OutputStreamWriter (zipOut)); + Util.copy (new FileInputStream (file), zipOut, tmp, progressState, true, false); + entryWriter.flush (); + zipOut.closeEntry (); + } + }}); + receiveZip (urlConnection, new ZipLineReader () { + public void readLine (String line) { + uploaded.add (line); + }}, mirror.token); + } catch (Exception e) { + e.printStackTrace (); + } + return uploaded; + } + + // ======================================== + public String getRoles (String login) { + try { + URLConnection urlConnection = getUrlConnection ("getRoles", login); + urlConnection.connect (); + BufferedReader br = new BufferedReader (new InputStreamReader (urlConnection.getInputStream ())); + String roles = br.readLine (); + for (;;) { + String line = br.readLine (); + if (line == null) + break; + } + if (roles.indexOf ("|") < 0) + return null; + return roles; + } catch (Exception e) { + return null; + } + } + public void logoutDokuwiki () { + try { + URLConnection urlConnection = getUrlConnection ("?do=logout"); + urlConnection.connect (); + forceOpenConnection (urlConnection); + } catch (Exception e) { + } + } + public void loginDokuwiki (String login, String password) { + try { + // page de connexion + URLConnection urlConnection = getUrlConnection ("?do=login"); + urlConnection.connect (); + String sectok = null; + BufferedReader br = new BufferedReader (new InputStreamReader (urlConnection.getInputStream ())); + for (;;) { + String line = br.readLine (); + if (line == null) + break; + if (line.indexOf ("name=\"sectok\"") < 0) + continue; + Pattern p = Pattern.compile (".*<([^<]*name=\"sectok\"[^>]*)>.*"); + Matcher m = p.matcher (line); + if (!m.matches ()) + break; + line = m.group (1); + p = Pattern.compile (".*value=\"([^\"]*)\".*"); + m = p.matcher (line); + if (!m.matches ()) + break; + sectok = m.group (1); + break; + } + if (sectok == null) + return; + // envoie de mot de passe + urlConnection = getUrlConnection ("?do=login"); + urlConnection.setDoOutput (true); + urlConnection.setDoInput (true); + urlConnection.setRequestProperty ("Content-Type", "application/x-www-form-urlencoded"); + String post = + "sectok="+sectok+"&"+ + "id=debut&"+ + "do=login&"+ + "u="+URLEncoder.encode (login, charset)+"&"+ + "p="+URLEncoder.encode (password, charset); + urlConnection.setRequestProperty ("Content-Length", ""+post.getBytes ().length); + OutputStream httpStream = urlConnection.getOutputStream (); + PrintWriter httpWriter = new PrintWriter (new OutputStreamWriter (httpStream, charset), true); + httpWriter.append (post).append (LINE_FEED).flush (); + urlConnection.connect (); + forceOpenConnection (urlConnection); + } catch (Exception e) { + e.printStackTrace (); + } + } + + // ======================================== + static public void forceOpenConnection (URLConnection urlConnection) { + try { + BufferedReader br = new BufferedReader (new InputStreamReader (urlConnection.getInputStream ())); + for (;;) { + String line = br.readLine (); + if (line == null) + break; + } + } catch (Exception e) { + } + } + + // ======================================== +} diff --git a/src/java/misc/RemoteUpdateManager.java b/src/java/misc/RemoteUpdateManager.java new file mode 100644 index 0000000..28b4cac --- /dev/null +++ b/src/java/misc/RemoteUpdateManager.java @@ -0,0 +1,74 @@ +package misc; + +import java.awt.Container; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Hashtable; +import java.util.List; +import javax.swing.AbstractButton; +import javax.swing.JMenu; + +@SuppressWarnings ("serial") +public class RemoteUpdateManager implements ApplicationManager, ActionListener { + + // ======================================== + static public final String + actionUpdate = "Update", + actionOnline = "Online"; + static public final List actionsNames = Arrays.asList (actionUpdate, actionOnline); + @SuppressWarnings("unchecked") + static public final Hashtable actionsMethod = + Util.collectMethod (RemoteUpdateManager.class, actionsNames); + public void actionPerformed (ActionEvent e) { + Util.actionPerformed (actionsMethod, e, this); + } + + // ======================================== + public void addMenuItem (JMenu... jMenu) { + Util.addMenuItem (actionsNames, this, jMenu[0]); + } + public void addIconButtons (Container... containers) { + Util.addIconButton (actionsNames, this, containers[0]); + } + private ArrayList enabledConnectedButtons = new ArrayList (); + public void addActiveButtons (Hashtable buttons) { + enabledConnectedButtons.add (buttons.get (actionOnline)); + updateActiveButtons (); + } + public void updateActiveButtons () { + for (AbstractButton button : enabledConnectedButtons) + button.setEnabled (connected); + } + public void actionUpdate () { + jRemoteUpdate.downloadDialog (); + } + public void actionOnline () { + jRemoteUpdate.uploadDialog (); + } + + // ======================================== + private boolean connected = false; + private RemoteUpdate remoteUpdate; + private JRemoteUpdate jRemoteUpdate; + + public void setConnected (boolean connected) { this.connected = connected; updateActiveButtons (); } + public RemoteUpdate getRemoteUpdate () { return remoteUpdate; } + public JRemoteUpdate getJRemoteUpdate () { return jRemoteUpdate; } + + public RemoteUpdateManager (OwnFrame controller, RemoteUpdate remoteUpdate, Runnable actionAfterUpdate) { + this.remoteUpdate = remoteUpdate; + jRemoteUpdate = new JRemoteUpdate (controller, remoteUpdate, actionAfterUpdate); + } + + public void check () { + (new Thread () { public void run () { + if (remoteUpdate.hasNewVersion ()) + jRemoteUpdate.downloadDialog (); + } }).start (); + } + + // ======================================== +} diff --git a/src/java/misc/ScaledImage.java b/src/java/misc/ScaledImage.java new file mode 100644 index 0000000..eedd252 --- /dev/null +++ b/src/java/misc/ScaledImage.java @@ -0,0 +1,238 @@ +// ================================================================================ +// François MERCIOL 2015 +// Name : ScaledImage.java +// Language : Java +// Author : François Merciol +// CopyLeft : Cecil B +// Creation : 2015 +// Version : 0.1 (xx/xx/xx) +// ================================================================================ +package misc; + +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.geom.AffineTransform; +import java.awt.geom.Dimension2D; +import java.awt.geom.Point2D; +import java.awt.image.AffineTransformOp; +import java.awt.image.BufferedImage; +import java.util.Hashtable; +import javax.swing.ImageIcon; + +public class ScaledImage { + + static public class ImageInfo { + public AffineTransform at; + public double [] bounds; + public double x, y; + Hashtable tiles = new Hashtable (); + public ImageInfo (AffineTransform at, double [] bounds, double x, double y) { + this.at = at; + this.bounds = bounds; + this.x = x; + this.y = y; + } + } + // ======================================== + static public final RenderingHints renderingHints; + static { + renderingHints = new RenderingHints (RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + // renderingHints.put (RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + // renderingHints.put (RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); + // renderingHints.put (RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE); + // renderingHints.put (RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); + } + + static public final int defaultAdjust = 8; + public BufferedImage reference; + public boolean horizontalSpin, verticalSpin; + + Hashtable> scalesRotations = new Hashtable> (); + + public ScaledImage (BufferedImage image) { + reference = image; + } + public ScaledImage (BufferedImage image, boolean horizontalSpin, boolean verticalSpin) { + reference = image; + this.horizontalSpin = horizontalSpin; + this.verticalSpin = verticalSpin; + } + + public ScaledImage getNewSpin (boolean horizontalSpin, boolean verticalSpin) { + if (this.horizontalSpin == horizontalSpin && this.verticalSpin == verticalSpin) + return this; + return new ScaledImage (reference, this.horizontalSpin ^ horizontalSpin, this.verticalSpin ^ verticalSpin); + } + + public void changeReference (BufferedImage image) { + reference = image; + clearCache (); + } + + public void clearCache () { + scalesRotations.clear (); + } + + // ======================================== + public AffineTransform getAffineTransform (Dimension2D size, double theta) { + int width = reference.getWidth (); + int height = reference.getHeight (); + double scaleX = size.getWidth () / width; + double scaleY = size.getHeight () / height; + if (scaleX == 0 || scaleY == 0) + return null; + if (horizontalSpin) + scaleX = -scaleX; + if (verticalSpin) + scaleY = -scaleY; + AffineTransform at = AffineTransform.getRotateInstance (Math.toRadians ((int) Math.toDegrees (theta))); + at.scale (scaleX, scaleY); + at.translate (-width/2., -height/2.); + return at; + } + + static public final Point2D ORIGINE = new Point2D.Double (0, 0); + static public final Dimension ONE_TILE = new Dimension (1, 1); + public ImageIcon get (Dimension size, double theta) { + return get (size, theta, ONE_TILE); + } + public ImageIcon get (Dimension size, double theta, Dimension tile) { + if (tile.width <= 0 || tile.height <= 0) + return null; + ImageInfo imageInfo = getInfo (size, theta); + if (imageInfo == null) + return null; + ImageIcon imageIcon = imageInfo.tiles.get (tile); + if (imageIcon != null) + return imageIcon; + AffineTransformOp op = new AffineTransformOp (imageInfo.at, renderingHints); + BufferedImage image = op.createCompatibleDestImage (reference, null); + Graphics2D printGraphics = (Graphics2D) image.getGraphics (); + printGraphics.setRenderingHint (RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + print (printGraphics, null, imageInfo, tile); + imageIcon = new ImageIcon (image); + imageInfo.tiles.put (tile, imageIcon); + return imageIcon; + } + public void print (Graphics2D printGraphics, Point2D pos, ImageInfo imageInfo, Dimension tile) { + if (pos != null) + printGraphics.translate (pos.getX ()+imageInfo.x, pos.getY ()+imageInfo.y); + if (ONE_TILE.equals (tile)) + printGraphics.drawImage (reference, imageInfo.at, null); + else { + double width = reference.getWidth (), height = reference.getHeight (); + double scaleX = 1.0/tile.width, scaleY = 1.0/tile.height; + double stepX = width/tile.width, stepY = height/tile.height; + for (int i = 0; i < tile.width; i++) + for (int j = 0; j < tile.height; j++) { + AffineTransform at = new AffineTransform (imageInfo.at); + at.translate (i*stepX, j*stepY); + at.scale (scaleX, scaleY); + printGraphics.drawImage (reference, at, null); + } + } + if (pos != null) + printGraphics.translate (-pos.getX ()-imageInfo.x, -pos.getY ()-imageInfo.y); + } + public void print (Graphics2D printGraphics, Point2D pos, DimensionDouble size, double theta, Dimension tile) { + print (printGraphics, pos, newInfo (size, theta), tile); + } + + public double[] getBounds (Dimension size, double theta) { + ImageInfo imageInfo = getInfo (size, theta); + if (imageInfo == null) + return null; + return imageInfo.bounds; + } + public ImageInfo getInfo (Dimension size, double theta) { + int angle = (int) Math.toDegrees (theta); + Hashtable rotated = scalesRotations.get (angle); + if (rotated == null) { + rotated = new Hashtable (); + scalesRotations.put (angle, rotated); + } + ImageInfo imageInfo = rotated.get (size); + if (imageInfo != null) + return imageInfo; + imageInfo = newInfo (new DimensionDouble (size.width, size.height), theta); + if (imageInfo == null) + return null; + rotated.put (size, imageInfo); + return imageInfo; + } + public ImageInfo newInfo (DimensionDouble size, double theta) { + int width = reference.getWidth (); + int height = reference.getHeight (); + double scaleX = size.getWidth () / width; + double scaleY = size.getHeight () / height; + if (scaleX == 0 || scaleY == 0) + return null; + if (horizontalSpin) + scaleX = -scaleX; + if (verticalSpin) + scaleY = -scaleY; + AffineTransform at = getAffineTransform (size, theta); + double [] bounds = new double [] {0, 0, 0, height, width, height, width, 0}; + at.transform (bounds, 0, bounds, 0, 4); + double dx = bounds[0]; + double dy = bounds[1]; + for (int i = 1; i < 4; i++) { + dx = Math.min (dx, bounds [i*2]); + dy = Math.min (dy, bounds [i*2+1]); + } + at.preConcatenate (AffineTransform.getTranslateInstance (-dx, -dy)); + return new ImageInfo (at, bounds, dx, dy); + } + + public ImageInfo newInfo2 (DimensionDouble size, double theta) { + int width = reference.getWidth (); + int height = reference.getHeight (); + double scaleX = size.getWidth () / width; + double scaleY = size.getHeight () / height; + if (scaleX == 0 || scaleY == 0) + return null; + if (horizontalSpin) + scaleX = -scaleX; + if (verticalSpin) + scaleY = -scaleY; + AffineTransform at = AffineTransform.getRotateInstance (theta); + at.scale (scaleX, scaleY); + at.translate (-width/2., -height/2.); + double [] bounds = new double [] {0, 0, 0, height, width, height, width, 0}; + at.transform (bounds, 0, bounds, 0, 4); + double dx = bounds[0]; + double dy = bounds[1]; + for (int i = 1; i < 4; i++) { + dx = Math.min (dx, bounds [i*2]); + dy = Math.min (dy, bounds [i*2+1]); + } + at.preConcatenate (AffineTransform.getTranslateInstance (-dx, -dy)); + return new ImageInfo (at, bounds, dx, dy); + } + + public ImageIcon getSide (int max) { + int width = reference.getWidth (); + int height = reference.getHeight (); + return get (width>height ? new Dimension (max, (height*max)/width) : new Dimension ((width*max)/height, max), 0); + } + + public ImageIcon getInsideFit (int maxWidth, int maxHeight, int adjust) { + return getInside (Math.min (reference.getWidth (), (maxWidth/adjust)*adjust), + Math.min (reference.getHeight (), (maxHeight/adjust)*adjust)); + } + + public ImageIcon getInside (int maxWidth, int maxHeight) { + int width = reference.getWidth (); + int height = reference.getHeight (); + return get ((width*maxHeight)/height > maxWidth ? + new Dimension (maxWidth, (height*maxWidth)/width) : + new Dimension ((width*maxHeight)/height, maxHeight), 0); + } + + public ImageIcon get (DimensionDouble real, double scale, double theta) { + return get (new Dimension ((int)(real.getWidth ()*scale), (int)(real.getHeight ()*scale)), 0); + } + + // ======================================== +} diff --git a/src/java/misc/SpinnerSlider.java b/src/java/misc/SpinnerSlider.java new file mode 100644 index 0000000..013129d --- /dev/null +++ b/src/java/misc/SpinnerSlider.java @@ -0,0 +1,125 @@ +package misc; + +import java.awt.Dimension; +import java.awt.GridBagLayout; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.Locale; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSlider; +import javax.swing.JSpinner; +import javax.swing.SpinnerNumberModel; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.ChangeListener; + +import misc.Util; +import static misc.Util.GBC; +import static misc.Util.GBCNL; + +/** + Spinner and Slider +*/ +@SuppressWarnings ("serial") +public class SpinnerSlider extends JPanel implements ChangeListener { + + static public int MaxPrecision = 10000; + static public NumberFormat numberFormat; + static { + numberFormat = NumberFormat.getInstance (Locale.FRENCH); + //numberFormat = new DecimalFormat ("#,##0.###,#"); + numberFormat.setGroupingUsed (true); + numberFormat.setMinimumFractionDigits (0); + numberFormat.setMaximumFractionDigits (12); + } + + // ======================================== + public interface Accessor { + /** current */ + public void set (double value); + /** minValue, maxValue, current */ + public double [] get (); + }; + Accessor accessor; + + // ======================================== + public double minValue, maxValue; + public SpinnerNumberModel snm = new SpinnerNumberModel (0., 0., 0., 1.) { + public Object getPreviousValue () { + Number value = (Number) super.getPreviousValue (); + return SpinnerSlider.this.getPreviousValue (value); + } + public Object getNextValue () { + Number value = (Number) super.getNextValue (); + return SpinnerSlider.this.getNextValue (value); + } + }; + public JSpinner spinner = new JSpinner (snm); + public JLabel label = new JLabel ("/0"); + private JSlider slider = new JSlider (0, MaxPrecision, 1); + + // ======================================== + public SpinnerSlider (Accessor accessor, int textLength, float slideWidthCoef) { + super (null); + this.accessor = accessor; + + snm.addChangeListener (this); + slider.addChangeListener (this); + ((JSpinner.DefaultEditor) spinner.getEditor ()).getTextField ().setColumns (textLength); + + JSpinner.NumberEditor editor = (JSpinner.NumberEditor) spinner.getEditor (); + DecimalFormat format = editor.getFormat (); + format.setDecimalSeparatorAlwaysShown (true); + format.setMinimumFractionDigits (0); + format.setMaximumFractionDigits (12); + + setLayout (new GridBagLayout ()); + Util.addComponent (spinner, this, GBC); + Util.addComponent (label, this, GBC); + Util.addComponent (slider, this, GBCNL); + + Dimension d = slider.getPreferredSize (); + d.width = (int)(slideWidthCoef*d.width); + slider.setPreferredSize (d); + slider.setMinimumSize (d); + } + + // ======================================== + public void stateChanged (ChangeEvent e) { + try { + if (e.getSource () == slider) + accessor.set (((double)slider.getValue ()/MaxPrecision)*(maxValue - minValue)+minValue); + else if (e.getSource () == snm) + accessor.set (((Number)snm.getValue ()).doubleValue ()); + } catch (Exception e2) { + e2.printStackTrace (); + } + } + + // ======================================== + public void update () { + double [] doubleValues = accessor.get (); + minValue = doubleValues [0]; + maxValue = doubleValues [1]; + slider.removeChangeListener (this); + snm.removeChangeListener (this); + snm.setMinimum (doubleValues [0]); + snm.setMaximum (doubleValues [1]); // XXX ? + snm.setValue (doubleValues [2]); + //snm.setStepSize ((maxValue-minValue)/MaxPrecision); + slider.setValue ((int)((doubleValues [2]-minValue)/(maxValue-minValue)*MaxPrecision)); + label.setText ("/"+numberFormat.format ((float)doubleValues [1])); + snm.addChangeListener (this); + slider.addChangeListener (this); + } + + // ======================================== + public Number getPreviousValue (Number value) { + return value; + } + public Number getNextValue (Number value) { + return value; + } + // ======================================== +} diff --git a/src/java/misc/StateNotifier.java b/src/java/misc/StateNotifier.java new file mode 100644 index 0000000..665d879 --- /dev/null +++ b/src/java/misc/StateNotifier.java @@ -0,0 +1,109 @@ +// ================================================================================ +// François MERCIOL 2013 +// Name : StateNotifier.java +// Language : Java +// Author : François Merciol +// CopyLeft : Cecil B +// Creation : 2012 +// Version : 0.1 (xx/xx/xx) +// ================================================================================ + +package misc; + +import java.lang.reflect.Method; +import java.util.Hashtable; +import java.util.List; +import java.util.TreeSet; + +public class StateNotifier { + + // ======================================== + private Hashtable> observers = new Hashtable> (); + private Hashtable> observableNames= new Hashtable> (); + + // ======================================== + private void broadcast (String observable) { + Hashtable objectMethod = observableNames.get (observable); + if (objectMethod == null) + return; + try { + for (Object object : objectMethod.keySet ()) + objectMethod.get (object).invoke (object); + } catch (Exception e) { + Log.keepLastException ("ModelInfoSender::broadcast <"+observable+">", e); + } + } + + // ======================================== + private void broadcast (String observable, Object... args) { + Hashtable objectMethod = observableNames.get (observable); + if (objectMethod == null) + return; + try { + for (Object object : objectMethod.keySet ()) + objectMethod.get (object).invoke (object, new Object[]{args}); + } catch (Exception e) { + Log.keepLastException ("ModelInfoSender::broadcast <"+observable+">", e); + } + } + + // ======================================== + public void broadcastUpdate (String observable) { + broadcast ("update"+observable); + } + public void broadcastDisplay (String observable, Object... args) { + broadcast ("display"+observable, args); + } + + // ======================================== + private void addObserver (Object object, boolean arg, String... observable) { + TreeSet newNames = new TreeSet (); + for (String obs : observable) { + String name = (arg ? "display" : "update")+obs; + try { + Method method = arg ? + object.getClass ().getMethod (name, Object[].class): + object.getClass ().getMethod (name); + Hashtable objectMethod = observableNames.get (name); + if (objectMethod == null) + observableNames.put (name, objectMethod = new Hashtable ()); + objectMethod.put (object, method); + newNames.add (name); + } catch (NoSuchMethodException e) { + Log.keepLastException ("StateNotifier::addObserver no method <"+name+"> in class <"+object.getClass ()+">", e); + } + } + if (newNames.size () == 0) + return; + try { + TreeSet oldNames = observers.get (object); + oldNames.addAll (newNames); + } catch (Exception e) { + observers.put (object, newNames); + } + } + + // ======================================== + public void addUpdateObserver (Object object, String... observable) { + addObserver (object, false, observable); + } + public void addMsgObserver (Object object, String... observable) { + addObserver (object, true, observable); + } + + // ======================================== + public void removeObserver (Object object) { + TreeSet names = observers.get (object); + if (names == null) + return; + for (String name : names) { + Hashtable objectMethod = observableNames.get (name); + objectMethod.remove (object); + if (objectMethod.size () == 0) + observableNames.remove (name); + } + observers.remove (object); + } + + // ======================================== +} diff --git a/src/java/misc/Story.java b/src/java/misc/Story.java new file mode 100644 index 0000000..13e920e --- /dev/null +++ b/src/java/misc/Story.java @@ -0,0 +1,156 @@ +package misc; + +import java.util.ArrayList; +import java.util.Stack; +import java.util.Vector; + +public class Story { + + // ======================================== + static public final String + BroadcastStory = "Story"; + + public abstract class Command { + public final String name; + public Command (String name) { this.name = name; } + public abstract void exec (); + public abstract void undo (); + public void displayExec () { display (); } + public void displayUndo () { display (); } + public void display () {} + } + + public abstract class Commands extends Command { + ArrayList commands = new ArrayList (); + public Commands (String name) { super (name); } + public Story getStory () { return Story.this; } + public int size () { return commands.size (); } + public void add (Command command) { + commands.add (command); + } + public void exec () { + // XXX si erreur undo jusqu'a l'etape + for (Command command : commands) { + command.exec (); + command.displayExec (); + } + } + public void undo () { + for (int i = commands.size ()-1; i >= 0; i--) { + Command command = commands.get (i); + command.undo (); + command.displayUndo (); + } + } + public String toString () { + String result = ""; + for (Command command : commands) + result += " "+command.name; + return result; + } + } + + // ======================================== + StateNotifier observable; + Stack undo = new Stack (); + Stack redo = new Stack (); + + public Vector getUndoCmd () { + return getCmd (undo); + } + public Vector getRedoCmd () { + return getCmd (redo); + } + public Vector getCmd (Stack commands) { + Vector result = new Vector (commands.size ()); + for (int i = commands.size () - 1; i >= 0; i--) + result.add (commands.get (i).name); + return result; + } + + public Story (StateNotifier observable) { + this.observable = observable; + } + public void addUpdateObserver (Object object) { + observable.addUpdateObserver (object, BroadcastStory); + } + public void removeObserver (Object object) { + observable.removeObserver (object); + } + + public void add (Command command) { + try { + if (command instanceof Commands && ((Commands) command).commands.size () < 1) + return; + command.exec (); + command.displayExec (); + undo.push (command); + } finally { + redo.clear (); + observable.broadcastUpdate (BroadcastStory); + } + }; + + // ======================================== + Command lastSaved; + Command lastCommand () { + return undo.empty () ? null : undo.peek (); + } + + public void markNotSaved () { + lastSaved = new Command ("") { public void exec () {} public void undo () {} }; + observable.broadcastUpdate (BroadcastStory); + } + public void markSaved () { + lastSaved = lastCommand (); + observable.broadcastUpdate (BroadcastStory); + } + public boolean isModified () { + return lastSaved != lastCommand (); + } + + // ======================================== + public boolean undoAvailable () { + return !undo.empty (); + } + public boolean redoAvailable () { + return !redo.empty (); + } + public ArrayList getUndoCommande () { return getString (undo); } + public ArrayList getRedoCommande () { return getString (redo); } + ArrayList getString (Stack stack) { + ArrayList result = new ArrayList (); + for (Command command : stack) + result.add (command.name); + return result; + } + + // ======================================== + public void undo (int level) { + try { + for (int i = 0; i < level; i++) { + Command command = undo.pop (); + command.undo (); + command.displayUndo (); + redo.push (command); + } + } catch (Exception e) { + } + observable.broadcastUpdate (BroadcastStory); + } + + public void redo (int level) { + try { + for (int i = 0; i < level; i++) { + Command command = redo.pop (); + command.exec (); + command.displayExec (); + undo.push (command); + } + } catch (Exception e) { + } + observable.broadcastUpdate (BroadcastStory); + } + + // ======================================== +} diff --git a/src/java/misc/StoryManager.java b/src/java/misc/StoryManager.java new file mode 100644 index 0000000..61391d1 --- /dev/null +++ b/src/java/misc/StoryManager.java @@ -0,0 +1,142 @@ +package misc; + +import java.awt.BorderLayout; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Point; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Hashtable; +import java.util.List; +import java.util.Vector; +import javax.swing.AbstractButton; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; +import javax.swing.SwingUtilities; + +public class StoryManager implements ApplicationManager, ActionListener { + + // ======================================== + static public final String + actionUndo = "Undo", + actionRedo = "Redo"; + static public final List + actionsNames = Arrays.asList (actionUndo, actionRedo); + @SuppressWarnings("unchecked") + static public final Hashtable actionsMethod = + Util.collectMethod (StoryManager.class, actionsNames); + public void actionPerformed (ActionEvent e) { + Util.actionPerformed (actionsMethod, e, this); + } + private ArrayList enabledUndoButtons = new ArrayList (); + private ArrayList enabledRedoButtons = new ArrayList (); + + // ======================================== + public void addMenuItem (JMenu... jMenu) { + Util.addMenuItem (actionsNames, this, jMenu[0]); + } + public void addIconButtons (Container... containers) { + addListButton (Util.addIconButton (actionUndo, this, containers[0]), true); + addListButton (Util.addIconButton (actionRedo, this, containers[0]), false); + } + public void addListButton (JButton button, boolean undo) { + Dimension currentSize = button.getPreferredSize (); + button.setLayout (new BorderLayout ()); + JLabel develop = new JLabel (Util.loadActionIcon ("develop")); + Dimension addedSize = develop.getPreferredSize (); + button.add (develop, BorderLayout.EAST); + currentSize.width += addedSize.width; + button.setPreferredSize (currentSize); + + develop.addMouseListener (new MouseAdapter () { + public void mousePressed (MouseEvent e) { + button.setSelected (false); + if (!button.isEnabled () || story == null) + return; + new JStoryPopup (button, undo, SwingUtilities.convertMouseEvent (develop, e, button).getPoint ()); + } + }); + } + + @SuppressWarnings ("serial") + class JStoryPopup extends JPopupMenu { + public JStoryPopup (JButton button, boolean undo, Point pos) { + Vector cmds = undo ? story.getUndoCmd () : story.getRedoCmd (); + for (int i = 0; i < cmds.size (); i++) { + if (i >= 10) { + add (new JLabel ("...")); + break; + } + JMenuItem jMenu = new JMenuItem (Bundle.getStory (cmds.get (i))); + final int count = i+1; + jMenu.addActionListener (new ActionListener () { + public void actionPerformed (ActionEvent e) { + if (undo) + story.undo (count); + else + story.redo (count); + } + }); + add (jMenu); + } + show (button, pos.x, pos.y); + } + } + + public void addActiveButtons (Hashtable buttons) { + enabledUndoButtons.add (buttons.get (actionUndo)); + enabledRedoButtons.add (buttons.get (actionRedo)); + updateActiveButtons (); + } + public void updateActiveButtons () { + updateStory (); + } + + public void updateStory () { + boolean undo = false, redo = false; + if (story != null) { + undo = story.undoAvailable (); + redo = story.redoAvailable (); + } + for (AbstractButton button : enabledUndoButtons) + button.setEnabled (undo); + for (AbstractButton button : enabledRedoButtons) + button.setEnabled (redo); + } + + public void actionUndo () { + if (story == null) + return; + story.undo (1); + } + + public void actionRedo () { + if (story == null) + return; + story.redo (1); + } + + // ======================================== + Story story; + public void setStory (Story story) { + if (this.story != null) + this.story.removeObserver (this); + this.story = story; + if (story != null) + story.addUpdateObserver (this); + updateStory (); + } + + // public StoryManager () { + // } + + // ======================================== +} diff --git a/src/java/misc/Timer.java b/src/java/misc/Timer.java new file mode 100644 index 0000000..36654d4 --- /dev/null +++ b/src/java/misc/Timer.java @@ -0,0 +1,58 @@ +package misc; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.TimeZone; + +public class Timer { + // ======================================== + static public SimpleDateFormat sdf = new SimpleDateFormat ("H:mm:ss.S"); + static { + sdf.setTimeZone (TimeZone.getTimeZone ("GMT")); + } + class Milestone { + String name; + long end; + long last; + Milestone (String name, long end, long last) { + this.name = name; + this.end = end; + this.last = last; + } + } + // ======================================== + long start = System.currentTimeMillis (); + long lastEnd = start; + + ArrayList milestones = new ArrayList (); + + public String getLap (String milestoneName) { + long now = System.currentTimeMillis (); + long end = now-start; + long last = now - lastEnd; + lastEnd = now; + milestones.add (new Milestone (milestoneName, end, last)); + return milestoneName+": "+sdf.format (end)+" ("+end+")"+": "+sdf.format (last)+" ("+last+")"; + } + + public String getNames () { + String result = ""; + for (Milestone milestone : milestones) + result += ":"+milestone.name; + return result; + } + public String getEnds () { + String result = ""; + for (Milestone milestone : milestones) + result += ":"+sdf.format (milestone.end)+" ("+milestone.end+")"; + return result; + } + public String getLasts () { + String result = ""; + for (Milestone milestone : milestones) + result += ":"+sdf.format (milestone.last)+" ("+milestone.last+")"; + return result; + } + + // ======================================== +} diff --git a/src/java/misc/Timestamp.java b/src/java/misc/Timestamp.java new file mode 100644 index 0000000..2afd164 --- /dev/null +++ b/src/java/misc/Timestamp.java @@ -0,0 +1,34 @@ +package misc; + +public class Timestamp { + // ======================================== + private long changeTimestamp, updateTimestamp; + public long[] getValues () { return new long[]{changeTimestamp, updateTimestamp}; } + + // ======================================== + public void reset () { + changeTimestamp = updateTimestamp = System.currentTimeMillis (); + } + + // ======================================== + public void change () { + changeTimestamp = System.currentTimeMillis (); + } + + // ======================================== + public void update () { + updateTimestamp = System.currentTimeMillis (); + } + + // ======================================== + public boolean isUpdated (Timestamp... others) { + if (changeTimestamp > updateTimestamp) + return false; + for (Timestamp other : others) + if (other.updateTimestamp > updateTimestamp) + return false; + return true; + } + + // ======================================== +} diff --git a/src/java/misc/TitledDialog.java b/src/java/misc/TitledDialog.java new file mode 100644 index 0000000..277ae63 --- /dev/null +++ b/src/java/misc/TitledDialog.java @@ -0,0 +1,57 @@ +package misc; + +import java.awt.Frame; +import java.awt.Point; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.SwingConstants; + +@SuppressWarnings ("serial") public class TitledDialog extends JDialog implements SwingConstants { + // ======================================== + protected String titleId; + + // ======================================== + public TitledDialog (Frame frame, final String titleId) { + super (frame, Bundle.getTitle (titleId), false); + this.titleId = titleId; + Bundle.addLocalizedDialog (this, titleId); + addWindowListener (new WindowAdapter () { + public void windowClosing (WindowEvent e) { + Util.updateCheckBox (titleId, false); + } + }); + boolean visible = Config.getBoolean (titleId+Config.checkedPostfix, false); + setVisible (visible); + //Util.updateCheckBox (titleId, visible); + } + + // ======================================== + public void setVisible (boolean visible) { + // if (visible == isVisible ()) + // return; + super.setVisible (visible); + Util.updateCheckBox (titleId, visible); + if (visible) { + pack (); + setLocationRelativeTo (null); + Config.loadLocation (titleId, this, getLocation ()); + toFront (); + } else { + saveLocation (); + } + } + + // ======================================== + public void setJFrame (JFrame jFrame) { + setIconImage (jFrame.getIconImage ()); + } + + // ======================================== + public void saveLocation () { + Config.saveLocation (titleId, this); + } + + // ======================================== +} diff --git a/src/java/misc/ToolBarManager.java b/src/java/misc/ToolBarManager.java new file mode 100644 index 0000000..39e7dd6 --- /dev/null +++ b/src/java/misc/ToolBarManager.java @@ -0,0 +1,462 @@ +package misc; + +import java.util.Comparator; +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Image; +import java.awt.Point; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ContainerEvent; +import java.awt.event.ContainerListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Hashtable; +import java.util.List; +import java.util.TreeSet; +import java.util.Vector; +import javax.swing.AbstractButton; +import javax.swing.BorderFactory; +import javax.swing.JCheckBox; +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTable; +import javax.swing.JToolBar; +import javax.swing.SwingConstants; +import javax.swing.border.Border; +import javax.swing.plaf.basic.BasicButtonUI; +import javax.swing.table.AbstractTableModel; + +import misc.TitledDialog; +import static misc.Util.GBCNL; + +/** + ... +*/ + +public class ToolBarManager implements ApplicationManager, ActionListener, ContainerListener, SwingConstants { + + // ======================================== + static public BasicButtonUI buttonUI = new BasicButtonUI (); + static public Border buttonBorder = BorderFactory.createEmptyBorder (2, 2, 2, 2); + + static public final String + actionUp = "Up", + actionDown = "Down", + actionToolBarProfil = "ToolBarProfil"; + static public final List actionsToolBar = + Arrays.asList (actionToolBarProfil); + @SuppressWarnings("unchecked") + static public final Hashtable actionsMethod = + Util.collectMethod (ToolBarManager.class, actionsToolBar); + public void actionPerformed (ActionEvent e) { + try { + AbstractButton button = ((AbstractButton) e.getSource ()); + Component component = toolBarComponent.get (button.getActionCommand ()); + if (component != null) { + if (button.isSelected ()) + show (component); + else + hide (component); + return; + } + } catch (Exception e2) { + Log.keepLastException ("ToolBarManager::actionPerformed", e2); + } + Util.actionPerformed (actionsMethod, e, this); + } + public void addMenuItem (JMenu... jMenu) { + Util.addMenuItem (actionsToolBar, this, jMenu[0]); + ((MultiToolBarBorderLayout) container.getLayout ()).setMenu (jMenu[0].getParent ()); + } + public void addIconButtons (Container... containers) { + Util.addIconButton (actionsToolBar, this, containers[0]); + } + public void addActiveButtons (Hashtable buttons) { + // disable if no toolbar to manage (orderedComponents.size () < 1 => container listener) + } + // ======================================== + @SuppressWarnings ("serial") public class TableObjectModel extends AbstractTableModel { + public int getColumnCount () { return 2; } + public int getRowCount () { return orderedComponents.size (); } + public Object getValueAt (int row, int column) { + if (row >= orderedComponents.size ()) + return null; + return column == 0 ? + orderedComponents.get (row).getParent () != null : + Bundle.getTitle (toolBarName.get (orderedComponents.get (row))); + } + public String getColumnName (int column) { + return column == 0 ? Bundle.getLabel ("ToolBarShowed") : Bundle.getLabel ("ToolBarNamed"); + } + public Class getColumnClass (int column) { + return column == 0 ? Boolean.class : String.class; + } + public boolean isCellEditable (int row, int column) { + return column == 0; + } + public void setValueAt (Object aValue, int row, int column) { + if (column > 0 || row >= orderedComponents.size ()) + return; + Component component = orderedComponents.get (row); + if ((Boolean) aValue) + show (component); + else + hide (component); + } + public void move (int delta) { + int idx = table.getSelectedRow (); + if (idx < 0) + return; + int mod = orderedComponents.size (); + Component old = orderedComponents.remove (idx); + idx = (idx+delta+mod) % mod; + if (idx == orderedComponents.size ()) + orderedComponents.add (old); + else + orderedComponents.add (idx, old); + table.revalidate (); + table.getSelectionModel ().setLeadSelectionIndex (idx); + container.getLayout ().layoutContainer (container); + } + }; + TableObjectModel dataObjectModel = new TableObjectModel (); + JTable table = new JTable (dataObjectModel); + public void actionToolBarProfil () { + JPanel panel = new JPanel (new BorderLayout ()); + panel.add (Util.getJScrollPane (table), BorderLayout.CENTER); + JPanel leftPropCmdPanel = new JPanel (); + panel.add (leftPropCmdPanel, BorderLayout.SOUTH); + leftPropCmdPanel.add (Util.newIconButton (actionUp, new ActionListener () { + public void actionPerformed (ActionEvent e) { dataObjectModel.move (-1); } + })); + leftPropCmdPanel.add (Util.newIconButton (actionDown, new ActionListener () { + public void actionPerformed (ActionEvent e) { dataObjectModel.move (+1); } + })); + Hashtable buttons = Util.collectButtons (null, leftPropCmdPanel); + Util.collectButtons (buttons, leftPropCmdPanel); + for (AbstractButton button : buttons.values ()) { + button.setUI (buttonUI); + button.setBorder (buttonBorder); + } + //table.getTableHeader ().setReorderingAllowed (true); + table.setShowHorizontalLines (false); + table.setShowVerticalLines (false); + table.setRowSelectionAllowed (true); + table.setColumnSelectionAllowed (false); + table.setSelectionMode (0); + Dimension preferredSize = new Dimension (table.getPreferredSize ()); + preferredSize.height += 40; + table.getParent ().getParent ().setPreferredSize (preferredSize); + for (int row = 0; row < table.getRowCount (); row++) + // check bundle before grab dialog + table.getValueAt (row, 1); + JOptionPane.showMessageDialog (frame, panel); + int i = 0; + for (Component component : orderedComponents) + Config.setInt (toolBarName.get (component)+"Order", i++); + } + + // ======================================== + /** Les points cardinaux sur lesquels l'orientation des boîtes doit être horizontal. */ + static final List horizontalCardinal = Arrays.asList (NORTH, SOUTH); + + // ======================================== + /** receptacle qui accueille les boîtes (doit avoir une mise en forme de type bordure "BorderLayout"). */ + private Container container; + /** + * Nom donné à chaque boîte. Ce nom est utilisé comme racine pour trouver des valeur dans la configuration ou l'internationnalisation. + * Exemple si le nom est "Horloge" on trouvera dans la configuration : + * "HorlogeChecked" (true indiquant que la boîte est visible) et + * "HorlogePlace" (East indiquant que la boîte s'attachera à droite) + * On trouvera dans le fichier des + * "ActionHorloge" associant le texte à la case à cocher ("Montre l'horloge" pour le texte en français) + * Enfin un évenement de type "Horloge" pourra être traité par "actionPerformed". + */ + private Hashtable toolBarName = new Hashtable(); + private Hashtable toolBarOrder = new Hashtable(); + private ArrayList orderedComponents = new ArrayList (); + /** Assoication permettant de trouver un composant d'après son nom. */ + private Hashtable toolBarComponent = new Hashtable(); + /** mémorise la position relative (points cardinaux) de la boîte lorsqu'elle est attachée. */ + private Hashtable toolBarPlace = new Hashtable(); + /** Liste des cases à cocher associé à la visibilité d'une boîte. */ + private Hashtable> nameCheckBox = new Hashtable>(); + + JFrame frame = new JFrame (); + // ======================================== + /** + * créer un gestionnaire de boîte à outil détachable gravitant autour d'un receptacle. + * Le receptacle doit avoir une mise en forme de type bordure "BorderLayout". + * Le gestionnaire se mets automatiquement à l'écoute de l'ajout et de la suppression de composant qui peuvent résulter de l'attachement ou du détachment de boîte. + */ + public ToolBarManager (Image icon, Container container) { + this.container = container; + ((MultiToolBarBorderLayout) container.getLayout ()).setLayoutOrderedComponents (orderedComponents); + frame.setIconImage (icon); + container.addContainerListener (this); + } + + // ======================================== + public JToolBar newJToolBar (String toolBarId, String defaultCardinalPoint) { + JToolBar toolBar = new JToolBar (toolBarId); + // XXX + //Bundle.addLocalizedActioner (toolBar); + add (toolBar, defaultCardinalPoint); + return toolBar; + } + + /** + * Enregistre un composant détachable. + * Il ne devrait pas y avoir plus de 4 boîtes visible en même temps (au maximum un par point cardinal). + */ + public void add (Component component, String defaultCardinalPoint) { + String name = component.getName (); //+toolBarPostfix; + //Util.getClassName (component.getClass ()); + if (toolBarName.contains (component)) + throw new IllegalArgumentException (MessageFormat.format (Bundle.getException ("AlreadyRegistredTool"), + name, defaultCardinalPoint)); + toolBarName.put (component, name); + toolBarComponent.put (name, component); + nameCheckBox.put (name, new Vector()); + toolBarOrder.put (component, Config.getInt (name+"Order", toolBarOrder.size ())); + orderedComponents.add (component); + orderedComponents.sort (componentsComparator); + String place = Config.getString (name+"Place", defaultCardinalPoint); + Integer cardinal = MultiToolBarBorderLayout.positionStringToInt.get (place); + if (cardinal == null || cardinal == CENTER) + throw new IllegalArgumentException (MessageFormat.format (Bundle.getException ("NotCardinalPoint"), place)); + put (component, place); + showIfVisible (component); + } + + // ======================================== + /** + * Ajoute un case à cocher concernée par la visibilité d'une boîte détachable. + * La case sera mise à jour suivant le changement de visibilité du composant. + * Le gestionaire de boîte se mets automatiquement à l'écoute du changement d'état de la case (que cela soit un JButton ou un JCheckBoxMenuItem). + */ + public void addCheckBox (AbstractButton checkbox) { + String name = checkbox.getActionCommand (); + Vector buttons = nameCheckBox.get (name); + if (buttons == null) + return; + buttons.add (checkbox); + checkbox.addActionListener (this); + } + + // ======================================== + /** + * les seuls évènement pris en charge provienne du changement d'état de case à cocher. + * L'action doit avoir le même nom que celui utilisé pour désigner la boîte détachable. + */ + + // ======================================== + /** Mets à jour les boîtes à cocher refletant la visibilité d'une boîte à outils. */ + public void checkBoxComponent (Component component) { + boolean showed = component.getParent () != null; + for (AbstractButton checkbox : nameCheckBox.get (toolBarName.get (component))) { + try { + checkbox.getClass ().getMethod ("setState", boolean.class).invoke (checkbox, showed); + } catch (NoSuchMethodException e) { + Log.keepLastException ("ToolBarManager::checkBoxComponent", e); + } catch (InvocationTargetException e) { + Log.keepLastException ("ToolBarManager::checkBoxComponent", e); + } catch (IllegalAccessException e) { + Log.keepLastException ("ToolBarManager::checkBoxComponent", e); + } + } + } + + // ======================================== + /** Creer une boîte de dialogue pour une boîte detache au lancement de l'application. */ + private void newUndocked (final Component component) { + final String name = toolBarName.get (component); + final TitledDialog jdialog = new TitledDialog (frame, name); + jdialog.add (component); + ((JToolBar) component).setFloatable (false); + String place = get (component); + try { + int cardinal = MultiToolBarBorderLayout.positionStringToInt.get (place); + ((JToolBar) component).setOrientation (horizontalCardinal.contains (cardinal) ? + SwingConstants.HORIZONTAL : SwingConstants.VERTICAL); + } catch (Exception e) { + } + ((JToolBar) component).addContainerListener (new ContainerListener () { + public void componentAdded (ContainerEvent e) { Util.packWindow (component); } + public void componentRemoved (ContainerEvent e) { Util.packWindow (component); } + }); + + jdialog.addWindowListener (new WindowAdapter () { + public void windowClosing (WindowEvent e) { + jdialog.remove (component); + ((JToolBar) component).setFloatable (true); + show (component); + } + }); + jdialog.setLocationRelativeTo (null); + Config.loadLocation (name, jdialog, jdialog.getLocation ()); + jdialog.setVisible (true); + jdialog.pack (); + } + + public void saveLocation () { + for (Component component : toolBarName.keySet ()) { + if (component.getParent () == null || container.isAncestorOf (component)) + continue; + Config.saveLocation (toolBarName.get (component), getUndocked (component)); + } + } + + // ======================================== + /** Montre le composant au lancement de l'application s'il est marqué comme visible dans le fichier de configuration. */ + public void showIfVisible (final Component component) { + if (Config.getBoolean (toolBarName.get (component)+Config.checkedPostfix, true)) + if (Config.getBoolean (toolBarName.get (component)+Config.undockedPostfix, false)) + newUndocked (component); + else { + + show (component); + } + } + + // ======================================== + /** Montre le composant, adapte l'orientation (horizontal ou non), mémorise l'état dans le fichier de configuration et mets à jour les cases à cocher. */ + public void show (Component component) { + String place = get (component); + try { + int cardinal = MultiToolBarBorderLayout.positionStringToInt.get (place); + ((JToolBar) component).setOrientation (horizontalCardinal.contains (cardinal) ? + SwingConstants.HORIZONTAL : SwingConstants.VERTICAL); + } catch (Exception e){ + } + Container parent = component.getParent (); + if (parent!= null) + return; + ((JToolBar) component).setFloatable (true); + container.invalidate (); + container.add (component, place); + container.validate (); + Config.setBoolean (toolBarName.get (component)+Config.checkedPostfix, true); + checkBoxComponent (component); + } + + // ======================================== + private Container getUndocked (Component component) { + Container frame = component.getParent (); + for (;;) { + if (frame instanceof Window) + return frame; + frame = frame.getParent (); + } + } + + // ======================================== + /** Cache le composant, mémorise l'état dans le fichier de configuration et mets à jour les cases à cocher. */ + public void hide (Component component) { + Container parent = component.getParent (); + if (parent == null) + return; + if (container.isAncestorOf (component)) { + container.invalidate (); + container.remove (component); + container.validate (); + } else { + try { + getUndocked (component).setVisible (false); + } catch (Exception e) { + } + parent.remove (component); + } + Config.setBoolean (toolBarName.get (component)+Config.checkedPostfix, false); + checkBoxComponent (component); + } + + // ======================================== + /** Change l'état de visibilité de la boîte détachable. */ + public void showHide (Component component) { + if (component.getParent () == null) + show (component); + else + hide (component); + } + + // ======================================== + /** Recherche le point cardinal réel d'une boîte detachable dans le receptacle (exemple : cas d'une boîte vient d'être réttachée). */ + private String find (Component component) { + return (String) ((MultiToolBarBorderLayout) container.getLayout ()).getConstraints (component); + } + + // ======================================== + /** Recherche un point cardinal pour une boîte. */ + private String get (Component component) { + String place = toolBarPlace.get (component); + if (place != null) + return place; + place = BorderLayout.NORTH; + put (component, place); + return place; + } + + // ======================================== + /** Mémorise, sans controle, le point cardinal pour une boîte (y compris dans le fichier de configuration). */ + private void put (Component component, String place) { + toolBarPlace.put (component, place); + Config.setString (toolBarName.get (component)+"Place", place); + } + + // ======================================== + /** + * Traite l'ajout d'une boîte détachable par insertion (coche d'une case ou fermeture de sa boîte de dialogue quand elle est détachée). + * Vérifie si elle attérie sur une place déjà prise. + */ + public void componentAdded (ContainerEvent e) { + Component component = e.getChild (); + if (! toolBarName.keySet ().contains (component)) + // boîte non géré par ce gestionnaire + return; + String place = find (component); + put (component, place); + Util.packWindow (container); + Config.setBoolean (toolBarName.get (component)+Config.undockedPostfix, false); + } + + // ======================================== + /** + * Traite le retrait d'une boîte détachable par suppression (décoche d'une case ou détachement vers une doite de dialogue). + */ + public void componentRemoved (ContainerEvent e) { + Util.packWindow (container); + Component component = e.getChild (); + if (! toolBarName.keySet ().contains (component)) + // boîte non géré par ce gestionnaire + return; + Config.setBoolean (toolBarName.get (component)+Config.undockedPostfix, true); + } + + // ======================================== + public Comparator componentsComparator = + new Comparator () { + public int compare (Component o1, Component o2) { + Integer i1 = toolBarOrder.get (o1); + Integer i2 = toolBarOrder.get (o2); + if (i1 == i2) + return o1.hashCode ()-o2.hashCode (); + if (i1 == null || i2 == null) + return i2 == null ? 1 : -1; + return i1-i2; + } + }; + + // ======================================== +} diff --git a/src/java/misc/Util.java b/src/java/misc/Util.java new file mode 100644 index 0000000..30953fc --- /dev/null +++ b/src/java/misc/Util.java @@ -0,0 +1,964 @@ +// ================================================================================ +// François MERCIOL 2012 +// Name : Util.java +// Language : Java +// Author : François Merciol +// CopyLeft : Cecil B +// Creation : 2012 +// Version : 0.1 (xx/xx/xx) +// ================================================================================ +package misc; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Image; +import java.awt.Insets; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.InputEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Method; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.text.Normalizer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Hashtable; +import java.util.List; +import java.util.Set; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.Clip; +import javax.sound.sampled.DataLine; +import javax.sound.sampled.LineEvent; +import javax.sound.sampled.LineListener; +import javax.sound.sampled.LineUnavailableException; +import javax.swing.AbstractButton; +import javax.swing.BorderFactory; +import javax.swing.ButtonGroup; +import javax.swing.DefaultCellEditor; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JCheckBoxMenuItem; +import javax.swing.JComboBox; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JScrollPane; +import javax.swing.JSlider; +import javax.swing.JSplitPane; +import javax.swing.JTable; +import javax.swing.JTextField; +import javax.swing.KeyStroke; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; +import javax.swing.border.Border; +import javax.swing.plaf.basic.BasicButtonUI; +import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumn; + +public class Util implements SwingConstants { + + // ======================================== + // Change Plugins.version too ! + static public final Long version = 20171101L; + + static public final int bufSize = 1024*1024; + static public final String NL = System.getProperty ("line.separator"); + static public final String ON = "On"; + static public final String OFF = "Off"; + + static public BasicButtonUI buttonNoUI = new BasicButtonUI (); + static public Border buttonNoBorder = BorderFactory.createEmptyBorder (2, 2, 2, 2); + + // ======================================== + static public final String toNumIn2Units (long bytes) { + if (bytes == 0) + return "0 "; + int u = 0; + for (; bytes > 1024*1024; bytes >>= 10) + u++; + + if (bytes < 1024) + return String.format ("%d %c", bytes, " kMGTPEZY".charAt (u)).replace (" ", ""); + u++; + return String.format ("%.1f %c", bytes/1024f, " kMGTPEZY".charAt (u)).replace (",0 ", " ").replace (" ", ""); + } + static public final String toNumIn10Units (long bytes) { + if (bytes == 0) + return "0 "; + int u = 0; + for (; bytes > 1000*1000; bytes >>= 10) + u++; + + if (bytes < 1000) + return String.format ("%d %c", bytes, " kMGTPEZY".charAt (u)).replace (" ", ""); + u++; + return String.format ("%.1f %c", bytes/1000f, " kMGTPEZY".charAt (u)).replace (",0 ", " ").replace (" ", ""); + } + @SuppressWarnings ({"unchecked", "rawtypes"}) + static public final T toEnum (String value, T defaultValue) { + try { + return (T) Enum.valueOf ((Class)defaultValue.getClass (), value); + } catch (Exception e) { + } + return defaultValue; + } + + // ======================================== + static public final String toColumn (String [] lines, int nbColumn) { + int size = lines.length; + if (size < 1) + return ""; + if (size < 2) + return lines [0]+"\n"; + String result = ""; + int nbLignes = size/nbColumn; + int nbLongColumn = size % nbColumn; + for (int i = 0, k = 0; k < size; i++) { + int idx = i; + String sep = ""; + for (int j = 0; j < nbColumn && k < size; j++, k++) { + result += sep + lines [idx]; + sep = "\t"; + idx += nbLignes; + if (j < nbLongColumn) + idx++; + } + result += "\n"; + } + return result; + } + + // ======================================== + static public final String[] set2string (Set set) { + String [] result = new String [set.size ()]; + int idx = 0; + for (String key : set) + result [idx++] = key; + return result; + } + + // ====================================================================== + static public JScrollPane getJScrollPane (Component view) { + return getJScrollPane (view, true, true); + } + static public JScrollPane getJScrollPane (Component view, boolean vNeed, boolean hNeed) { + return new JScrollPane (view, + vNeed ? JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED : JScrollPane.VERTICAL_SCROLLBAR_NEVER, + hNeed ? JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED : JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + } + + // ======================================== + static public JSplitPane getJSplitPane (int newOrientation, Component newLeftComponent, Component newRightComponent) { + JSplitPane result = new JSplitPane (newOrientation, newLeftComponent, newRightComponent); + result.setResizeWeight (0); + result.setOneTouchExpandable (true); + result.setContinuousLayout (true); + return result; + } + + // ======================================== + static public final void setEnabled (Component component, boolean enabled) { + component.setEnabled (enabled); + try { + for (Component component2 : ((Container) component).getComponents ()) + setEnabled (component2, enabled); + } catch (Exception e) { + } + } + + // ======================================== + static public final GridBagConstraints + GBC, GBCNL, GBCLNL, GBCBNL; + static { + GBC = new GridBagConstraints (); + GBC.insets = new Insets (1, 2, 1, 2); + GBC.weightx = GBC.weighty = 1.; + GBC.fill = GridBagConstraints.HORIZONTAL; + + GBCLNL = (GridBagConstraints) GBC.clone (); + GBCLNL.anchor = GridBagConstraints.WEST; + GBCLNL.fill = GridBagConstraints.NONE; + GBCLNL.gridwidth = GridBagConstraints.REMAINDER; + + GBCNL = (GridBagConstraints) GBC.clone (); + GBCNL.gridwidth = GridBagConstraints.REMAINDER; + + + GBCBNL = (GridBagConstraints) GBCNL.clone (); + GBCBNL.fill = GridBagConstraints.BOTH; + } + static public JPanel getGridBagPanel () { + return new JPanel (new GridBagLayout ()); + } + static public void addLabelFields (Container container, String label, Component... components) { + addLabel (label, RIGHT, container, GBC); + for (int i = 0; i < components.length; i++) + addComponent (components[i], container, i == components.length -1 ? GBCNL : GBC); + } + + // ======================================== + static public AbstractButton activeButton (AbstractButton button, String action, ActionListener actionListener) { + button.setActionCommand (action); + Bundle.addLocalizedActioner (button); + button.setToolTipText (Bundle.getAction (action)); + Bundle.addLocalizedToolTip (button); + if (actionListener != null) + button.addActionListener (actionListener); + return button; + } + + // ======================================== + static public final JCheckBox newCheckIcon (String action, ActionListener actionListener) { + JCheckBox button = new JCheckBox (loadActionIcon (action+OFF)); + button.setSelectedIcon (loadActionIcon (action+ON)); + activeButton (button, action, actionListener); + return button; + } + static public final JCheckBox newCheckIcon (String action, ActionListener actionListener, boolean defaultValue) { + JCheckBox button = newCheckIcon (action, actionListener); + button.setSelected (defaultValue); + return button; + } + static public final JCheckBox addCheckIcon (String action, ActionListener actionListener, Container container) { + JCheckBox button = newCheckIcon (action, actionListener); + container.add (button); + return button; + } + static public final JCheckBox addCheckIcon (String action, ActionListener actionListener, boolean defaultValue, Container container) { + JCheckBox button = newCheckIcon (action, actionListener); + container.add (button); + button.setSelected (defaultValue); + return button; + } + static public final void addCheckIcon (List actions, ActionListener actionListener, Container container) { + for (String action : actions) + addCheckIcon (action, actionListener, container); + } + static public final JCheckBox addCheckIcon (String action, ActionListener actionListener, boolean defaultValue, + Container container, GridBagConstraints constraint) { + JCheckBox button = newCheckIcon (action, actionListener, defaultValue); + addComponent (button, container, constraint); + return button; + } + + static public void setCheckIconTableCellEditorRenderer (String action, TableColumn tableColumn) { + tableColumn.setCellRenderer (new CheckIconTableCellEditorRenderer (action)); + tableColumn.setCellEditor (new DefaultCellEditor (newCheckIcon (action, null))); + } + + static public class CheckIconTableCellEditorRenderer implements TableCellRenderer { + JCheckBox jCheckBox; + public CheckIconTableCellEditorRenderer (String action) { + jCheckBox = Util.newCheckIcon (action, null); + } + public Component getTableCellRendererComponent (JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + jCheckBox.setBackground (isSelected ? table.getSelectionBackground () : table.getBackground ()); + jCheckBox.setForeground (isSelected ? table.getSelectionForeground () : table.getForeground ()); + if (value instanceof Boolean) + jCheckBox.setSelected (((Boolean) value).booleanValue ()); + return jCheckBox; + } + } + + // ======================================== + static public final JCheckBox newCheckButton (String action, ActionListener actionListener) { + JCheckBox button = new JCheckBox (Bundle.getAction (action), loadActionIcon (action+OFF)); + button.setSelectedIcon (loadActionIcon (action+ON)); + activeButton (button, action, actionListener); + return button; + } + static public final JCheckBox addCheckButton (String action, ActionListener actionListener, Container container) { + JCheckBox button = newCheckButton (action, actionListener); + container.add (button); + return button; + } + static public final void addCheckButton (List actions, ActionListener actionListener, Container container) { + for (String action : actions) + addCheckButton (action, actionListener, container); + } + static public final JCheckBox addCheckButton (String action, ActionListener actionListener, + Container container, GridBagConstraints constraint) { + JCheckBox button = newCheckButton (action, actionListener); + addComponent (button, container, constraint); + return button; + } + // ======================================== + static public final JCheckBox newCheckButtonConfig (String action, ActionListener actionListener, boolean defaultValue) { + boolean startValue = Config.getBoolean (action+Config.checkedPostfix, defaultValue); + JCheckBox button = newCheckButton (action, actionListener); + button.setSelected (startValue); + return button; + } + + static public final JCheckBox addCheckButtonConfig (String action, ActionListener actionListener, boolean defaultValue, + Container container) { + JCheckBox button = newCheckButtonConfig (action, actionListener, defaultValue); + container.add (button); + return button; + } + + static public final JCheckBox addCheckButtonConfig (String action, ActionListener actionListener, boolean defaultValue, + Container container, GridBagConstraints constraint) { + JCheckBox button = newCheckButtonConfig (action, actionListener, defaultValue); + // XXX le test suivant a été supprimer, il faut vérifier que cela tourne toujours + //if (container != null) + addComponent (button, container, constraint); + return button; + } + + // ======================================== + static public final JRadioButton newRadioButton (String action, ActionListener actionListener, ButtonGroup group, String selected) { + JRadioButton button = new JRadioButton (Bundle.getAction (action), loadActionIcon (action+OFF), + action.equals (selected)); + button.setSelectedIcon(loadActionIcon (action+ON)); + activeButton (button, action, actionListener); + group.add (button); + return button; + } + + static public final void addRadioButton (List actions, ActionListener actionListener, ButtonGroup group, String selected, + Container container) { + for (String action : actions) { + JRadioButton button = newRadioButton (action, actionListener, group, selected); + container.add (button); + } + } + + static public final JRadioButton addRadioButton (String action, ActionListener actionListener, ButtonGroup group, String selected, + Container container, GridBagConstraints constraint) { + JRadioButton button = newRadioButton (action, actionListener, group, selected); + addComponent (button, container, constraint); + return button; + } + + // ======================================== + static public final JButton newButton (String action, ActionListener actionListener) { + JButton button = new JButton (Bundle.getAction (action), loadActionIcon (action)); + activeButton (button, action, actionListener); + return button; + } + static public final void newButton (List actions, ActionListener actionListener) { + for (String action : actions) + newButton (action, actionListener); + } + + static public final JButton addButton (String action, ActionListener actionListener, Container container) { + JButton button = newButton (action, actionListener); + container.add (button); + return button; + } + static public final void addButton (List actions, ActionListener actionListener, Container container) { + for (String action : actions) + addButton (action, actionListener, container); + } + static public final JButton addButton (String action, ActionListener actionListener, Container container, GridBagConstraints constraint) { + JButton button = newButton (action, actionListener); + addComponent (button, container, constraint); + return button; + } + + // ======================================== + static public final JButton newIconButton (String action, ActionListener actionListener) { + JButton button = new JButton (loadActionIcon (action)); + button.setAlignmentX (Component.CENTER_ALIGNMENT); + activeButton (button, action, actionListener); + return button; + } + static public final void newIconButton (List actions, ActionListener actionListener) { + for (String action : actions) + newIconButton (action, actionListener); + } + + static public final JButton addIconButton (String action, ActionListener actionListener, Container container) { + JButton button = newIconButton (action, actionListener); + container.add (button); + return button; + } + static public final void addIconButton (List actions, ActionListener actionListener, Container container) { + for (String action : actions) + addIconButton (action, actionListener, container); + } + static public final JButton addIconButton (String action, ActionListener actionListener, Container container, GridBagConstraints constraint) { + JButton button = new JButton (loadActionIcon (action)); + activeButton (button, action, actionListener); + addComponent (button, container, constraint); + return button; + } + + static public final void unBoxButton (Container container) { + for (AbstractButton button : collectButtons (null, container).values ()) + unBoxButton (button); + } + static public final void unBoxButton (AbstractButton button) { + button.setUI (buttonNoUI); + button.setBorder (buttonNoBorder); + } + + // ======================================== + static public final void addMenuItem (String action, ActionListener actionListener, Container container) { + JMenuItem item = new JMenuItem (Bundle.getAction (action), loadActionIcon (action)); + activeButton (item, action, actionListener); + container.add (item); + } + static public final void addMenuItem (List actions, ActionListener actionListener, Container container) { + for (String action : actions) + addMenuItem (action, actionListener, container); + } + + + // ======================================== + static public Hashtable> allCheckBox = new Hashtable> (); + static public void updateCheckBox (String action, boolean value) { + ArrayList buttons = allCheckBox.get (action); + if (buttons == null) + return; + Config.setBoolean (action+Config.checkedPostfix, value); + for (AbstractButton button : buttons) + if (button.isSelected () != value) + button.setSelected (value); + } + + static public final void addCheckMenuItem (List actions, ActionListener actionListener, Container container) { + for (String action : actions) + addCheckMenuItem (action, actionListener, container); + } + static public final JCheckBoxMenuItem addCheckMenuItem (String action, ActionListener actionListener, Container container) { + return addCheckMenuItem (action, actionListener, Config.getBoolean (action+Config.checkedPostfix, false), container); + } + static public final JCheckBoxMenuItem addCheckMenuItem (String action, ActionListener actionListener, + boolean defaultValue, Container container) { + boolean startValue = Config.getBoolean (action+Config.checkedPostfix, defaultValue); + JCheckBoxMenuItem item = new JCheckBoxMenuItem (Bundle.getAction (action), loadActionIcon (action+OFF), startValue); + item.setSelectedIcon (loadActionIcon (action+ON)); + activeButton (item, action, actionListener); + container.add (item); + ArrayList checkList = allCheckBox.get (action); + if (checkList == null) + allCheckBox.put (action, checkList = new ArrayList ()); + checkList.add (item); + return item; + } + + // ======================================== + static public final JMenu addJMenu (JMenuBar jMenuBar, String menuId) { + JMenu menu = new JMenu (Bundle.getTitle (menuId)); + menu.setActionCommand (menuId); + Bundle.addLocalizedMenu (menu); + jMenuBar.add (menu); + return menu; + } + + // ======================================== + static public void addComponent (Component component, Container container, GridBagConstraints constraint) { + GridBagLayout gridbag = (GridBagLayout) container.getLayout (); + gridbag.setConstraints (component, constraint); + container.add (component); + } + + // ======================================== + static public final Dimension space = new Dimension (10, 10); + static public void addTextFieldSlider (JTextField textField, JLabel label, JSlider slider, Container container, GridBagConstraints constraint) { + JPanel panel = getGridBagPanel (); + addComponent (textField, panel, GBC); + addComponent (label, panel, GBC); + addComponent (slider, panel, GBCBNL); + addComponent (panel, container, constraint); + } + + // ======================================== + static public JLabel newLabel (String messageId, int position) { + JLabel jLabel = new JLabel (Bundle.getLabel (messageId), position); + Bundle.addLocalizedLabel (jLabel, messageId); + return jLabel; + } + + // ======================================== + static public JLabel addLabel (String messageId, int position, Container container) { + JLabel jLabel = newLabel (messageId, position); + container.add (jLabel); + return jLabel; + } + + // ======================================== + static public JLabel addLabel (String messageId, int position, Container container, GridBagConstraints constraint) { + JLabel jLabel = newLabel (messageId, position); + addComponent (jLabel, container, constraint); + return jLabel; + } + + // ======================================== + static public JComboBox newEnum (Class> enumClass, Enum defaultValue) { + JComboBox jComboBox = Bundle.getEnum (enumClass, defaultValue); + Bundle.addLocalizedEnum (jComboBox, enumClass); + return jComboBox; + } + + // ======================================== + static public JComboBox addEnum (Class> enumClass, Enum defaultValue, Container container) { + JComboBox jComboBox = newEnum (enumClass, defaultValue); + container.add (jComboBox); + return jComboBox; + } + + // ======================================== + static public JComboBox addEnum (Class> enumClass, Enum defaultValue, Container container, GridBagConstraints constraint) { + JComboBox jComboBox = newEnum (enumClass, defaultValue); + addComponent (jComboBox, container, constraint); + return jComboBox; + } + + // ======================================== + static public final void setColumnLabels (JTable jTable, String [] columnLabels) { + for (int i = 0; i < columnLabels.length; i++) + jTable.getColumnModel ().getColumn (i).setHeaderValue (Bundle.getLabel (columnLabels [i])); + try { + Container parent = jTable.getParent (); + parent.repaint (); + } catch (Exception e) { + } + } + + static public int viewToModel (JTable table, int vColIndex) { + if (vColIndex >= table.getColumnCount()) { + return -1; + } + return table.getColumnModel ().getColumn (vColIndex).getModelIndex (); + } + public int modelToView (JTable table, int mColIndex) { + for (int c = 0; c < table.getColumnCount (); c++) { + TableColumn col = table.getColumnModel ().getColumn (c); + if (col.getModelIndex () == mColIndex) { + return c; + } + } + return -1; + } + + // ======================================== + static public boolean packBug = false; + static public final void packWindow (final Component startComponent) { + if (packBug) + return; + SwingUtilities.invokeLater (new Runnable() { + public void run () { + for (Component component = startComponent; + component != null; + component = component.getParent ()) { + try { + ((Window) component).pack (); + return; + } catch (Exception e) { + } + } + } + }); + } + + // ======================================== + static public final JFrame newJFrame (String title, Component component, boolean exit) { + JFrame jFrame = new JFrame (title); + jFrame.addWindowListener (new WindowAdapter () { + public void windowClosing (WindowEvent e) { + if (exit) + System.exit (0); + jFrame.setVisible (false); + } + }); + jFrame.getContentPane ().add (component, BorderLayout.CENTER); + jFrame.pack (); + jFrame.setLocationRelativeTo (null); + jFrame.setVisible (true); + return jFrame; + } + + // ======================================== + static public ImageIcon loadActionIcon (String action) { + return loadImageIcon (Config.dataDirname, Config.iconsDirname, Config.buttonDirname, action+Config.iconsExt); + } + + // ======================================== + static public ImageIcon loadImageIcon (String... names) { + URL url = Config.getDataUrl (names); + return (url != null) ? new ImageIcon (url) : null; + } + + // ======================================== + static public Image loadImage (String... names) { + try { + return loadImageIcon (names).getImage (); + } catch (Exception e) { + return null; + } + } + + // ======================================== + static public final AudioInputStream loadAudio (String... names) { + URL url = Config.getDataUrl (names); + try { + return AudioSystem.getAudioInputStream (url); + } catch (Exception e) { + return null; + } + } + + static public final void play (AudioInputStream stream) { + if (stream == null) + return; + try { + AudioFormat format = stream.getFormat (); + DataLine.Info info = new DataLine.Info (Clip.class, format); + Clip clip = (Clip) AudioSystem.getLine (info); + clip.open (stream); + clip.addLineListener (new LineListener () { + public void update (LineEvent event) { + LineEvent.Type type = event.getType(); + if (type == LineEvent.Type.START) { + // System.err.println ("Playback started."); + } else if (type == LineEvent.Type.STOP) { + // System.er.println ("Playback completed."); + clip.close (); + } + } + }); + clip.start (); + } catch (LineUnavailableException e) { + Log.keepLastException ("Audio line for playing back is unavailable", e); + } catch (IOException e) { + Log.keepLastException ("Error playing the audio file", e); + } + + } + + // ======================================== + @SuppressWarnings({"rawtypes", "unchecked"}) + static public final Hashtable collectMethod (Class javaClass, List... actions) { + Hashtable actionsMethod = new Hashtable (); + for (List arg : actions) + for (String action : arg) { + try { + actionsMethod.put (action, javaClass.getMethod ("action"+action)); + } catch (NoSuchMethodException e) { + try { + actionsMethod.put (action, javaClass.getMethod ("action"+action, new Class[] { boolean.class })); + } catch (NoSuchMethodException e1) { + Log.keepLastException ("Util::collectMethod can't find methode "+"action"+action, e1); + e.printStackTrace (); + } + } + } + return actionsMethod; + } + + // ======================================== + static public final Hashtable collectButtons (Hashtable buttons, JMenu jMenu) { + if (buttons == null) + buttons = new Hashtable (); + for (int i = 0; i < jMenu.getItemCount (); i++) { + try { + AbstractButton button = jMenu.getItem (i); + buttons.put (button.getActionCommand (), button); + } catch (Exception e) { + } + } + return buttons; + } + + static public class ActionControl { + String action; + int key; + boolean control; + public ActionControl (String action, int key) { + this (action, key, true); + } + public ActionControl (String action, int key, boolean control) { + this.action = action; + this.key = key; + this.control = control; + } + } + static public void setAccelerator (Hashtable buttons, ActionControl... actionsControl) { + for (ActionControl actionControl : actionsControl) + ((JMenuItem) buttons.get (actionControl.action)).setAccelerator (KeyStroke.getKeyStroke (actionControl.key, actionControl.control ? InputEvent.CTRL_DOWN_MASK : 0)); + } + + + // ======================================== + static public final Hashtable collectButtons (Hashtable buttons, Container container) { + if (buttons == null) + buttons = new Hashtable (); + for (Component component : container.getComponents ()) { + try { + AbstractButton button = (AbstractButton) component; + buttons.put (button.getActionCommand (), button); + } catch (Exception e) { + } + } + return buttons; + } + + // ======================================== + @SuppressWarnings("rawtypes") + static public final void actionPerformed (final Hashtable actionsMethod, ActionEvent e, final Object object) { + String cmd = null; + try { + final Object source = e.getSource (); + if (source instanceof AbstractButton) + cmd = ((AbstractButton) source).getActionCommand (); + else if (source instanceof JComboBox) + cmd = ((JComboBox) source).getActionCommand (); + final String cmd2 = cmd; + if (cmd2 != null) + SwingUtilities.invokeLater (new Runnable() { + public void run () { + if (source instanceof JCheckBoxMenuItem) { + try { + boolean value = ((JCheckBoxMenuItem) source).isSelected (); + actionsMethod.get (cmd2).invoke (object, value); + updateCheckBox (cmd2, value); + return; + } catch (IllegalArgumentException e2) { + } catch (Exception e1) { + Log.keepLastException ("Util::actionPerformed (action "+cmd2+" on "+object+")", e1); + return; + } + } + try { + actionsMethod.get (cmd2).invoke (object); + } catch (Exception e1) { + Log.keepLastException ("Util::actionPerformed (action "+cmd2+" on "+object+")", e1); + } + } + }); + } catch (Exception e2) { + e2.printStackTrace (); + Log.keepLastException ("Util::actionPerformed (action "+cmd+" on "+object+")", e2); + } + } + + // ======================================== + @SuppressWarnings("unchecked") + static public List merge (List... args) { + List result = new ArrayList (); + for (List arg : args) + result.addAll (arg); + return result; + } + + // ======================================== + // static public File checkExt (File file, String ext) { + // try { + // String fileExt = file.getName (); + // fileExt = fileExt.substring (fileExt.lastIndexOf (".")).toLowerCase (); + // if (ext.toLowerCase ().equals (fileExt)) + // return file; + // } catch (Exception e) { + // } + // return new File (file.getParent (), file.getName ()+ext); + // } + + static public String getExtention (File file) { + return getExtention (file.getName ()); + } + static public String getExtention (String filename) { + try { + filename = filename.substring (filename.lastIndexOf (".")).toLowerCase (); + return filename.substring (1); + } catch (Exception e) { + return null; + } + } + + static public String getBase (File file) { + return getBase (file.getName ()); + } + static public String getBase (String filename) { + try { + return filename.substring (0, filename.lastIndexOf (".")); + } catch (Exception e) { + return filename; + } + } + + static public String changeExtention (String filename, String extention) { + try { + return filename.substring (0, filename.lastIndexOf ("."))+"."+extention; + } catch (Exception e) { + return filename+"."+extention; + } + } + + // ======================================== + static public void backup (File file, String extention, String backExtention) { + if (!file.exists ()) + return; + String newFileName = file.getName (); + if (newFileName.endsWith ("."+extention)) + newFileName = newFileName.substring (0, newFileName.length () - ("."+extention).length ()); + File backFile = new File (file.getParent (), newFileName+"."+backExtention); + backFile.delete (); + try { + Files.move (file.toPath (), backFile.toPath (), StandardCopyOption.REPLACE_EXISTING); + } catch (Exception e) { + } + } + + // ======================================== + static public void copy (InputStream src, OutputStream dst, byte[] tmp, ProgressState progressState) + throws IOException { + copy (src, dst, tmp, progressState, true, true); + } + static public void copy (InputStream src, OutputStream dst, byte[] tmp, ProgressState progressState, boolean srcClose, boolean dstClose) + throws IOException { + try { + if (tmp == null) + tmp = new byte[bufSize]; + for (;;) { + if (progressState != null && progressState.isInterrupted ()) + return; + int nbRead = src.read (tmp, 0, tmp.length); + if (nbRead < 0) + break; + dst.write (tmp, 0, nbRead); + if (progressState != null && !progressState.addValue (nbRead)) + return; + } + } finally { + dst.flush (); + try { + if (dstClose) + dst.close (); + if (srcClose) + src.close (); + } catch (Exception e) { + } + } + } + + // ======================================== + static public final String ctrlName (String s) { + return s.replaceAll ("[^0-9a-zA-Z_\\-.]", ""); + } + + // ======================================== + static public final String toCapital (final String s) { + return s.substring (0, 1).toUpperCase ()+s.substring (1).toLowerCase (); + } + + static public final boolean containsOne (Collection set1, Collection set2) { + try { + if (set1.size () > set2.size ()) { + Collection tmp = set1; + set1 = set2; + set2 = tmp; + } + for (T e : set1) + if (set2.contains (e)) + return true; + } catch (Exception e) { + } + return false; + } + + static public final boolean containsPart (String subString, Collection set) { + if (set == null) + return false; + for (String s : set) + if (s.indexOf (subString) >= 0) + return true; + return false; + } + + // ======================================== + @SuppressWarnings("rawtypes") + static public final String getClassName (final Class aClass) { + String[] path = aClass.getName ().split ("\\."); + return path [path.length-1]; + } + + // ======================================== + /** + retourne vrai si s1 existe et est égale à s2 + */ + static public boolean cmp (String s1, String s2) { + return (s1 == s2) || ((s1 != null) && (s1.equals (s2))); + } + + // ======================================== + static int max (int... values) { + int result = 0; + for (int val : values) + result = Math.max (result, val); + return result; + } + + // ======================================== + static public void sleep (int s) { + try { + Thread.sleep (s*1000); + } catch (InterruptedException e) { + } + } + + // ======================================== + static public void deciSleep (int s) { + try { + Thread.sleep (s*100); + } catch (InterruptedException e) { + } + } + + // ======================================== + static private String convertToHex (byte[] data) { + StringBuffer buf = new StringBuffer (); + for (int i = 0; i < data.length; i++) { + int halfbyte = (data[i] >>> 4) & 0x0F; + int two_halfs = 0; + do { + if ((0 <= halfbyte) && (halfbyte <= 9)) + buf.append ((char) ('0' + halfbyte)); + else + buf.append ((char) ('a' + (halfbyte - 10))); + halfbyte = data[i] & 0x0F; + } while (two_halfs++ < 1); + } + return buf.toString (); + } + + public static String removeAccent (String source) { + return Normalizer.normalize (source, Normalizer.Form.NFD).replaceAll ("[\u0300-\u036F]", ""); + } + + static public String sha1 (String text) + throws NoSuchAlgorithmException, UnsupportedEncodingException { + MessageDigest md; + md = MessageDigest.getInstance ("SHA-1"); + byte[] sha1hash = new byte[40]; + md.update (text.getBytes ("iso-8859-1"), 0, text.length ()); + sha1hash = md.digest (); + return convertToHex (sha1hash); + } + + // ======================================== +} diff --git a/src/java/misc/XML.java b/src/java/misc/XML.java new file mode 100644 index 0000000..7dbd36d --- /dev/null +++ b/src/java/misc/XML.java @@ -0,0 +1,114 @@ +package misc; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Hashtable; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.Text; + +public class XML { + + // ======================================== + /** une idée de Kohsuke Kawaguchi + http://weblogs.java.net/blog/kohsuke/archive/2005/07/socket_xml_pitf.html */ + static public class NoWaittingNoCloseInputStream extends java.io.FilterInputStream { + public NoWaittingNoCloseInputStream (InputStream in) { super (in); } + + public int read (byte[] b, int off, int len) throws IOException { + if (super.available () <= 0) + return -1; + int nb = super.read (b, off, len); + return nb; + } + public void close () throws IOException {} + } + + // ======================================== + static public Document readDocument (InputStream stream) + throws java.io.IOException { + try { + DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance ().newDocumentBuilder (); + Document document = documentBuilder.parse (new NoWaittingNoCloseInputStream (stream)); + document.normalizeDocument (); + return document; + } catch (javax.xml.parsers.ParserConfigurationException e) { + throw new IOException (e); + } catch (org.xml.sax.SAXException e) { + throw new IOException (e); + } + } + + // ======================================== + static public void writeDocument (Document document, OutputStream stream) { + try { + Source source = new DOMSource (document); + Result result = new StreamResult (stream); + Transformer xformer = TransformerFactory.newInstance ().newTransformer (); + xformer.setOutputProperty (OutputKeys.INDENT, "yes"); + xformer.setOutputProperty ("{http://xml.apache.org/xslt}indent-amount", "2"); + xformer.transform (source, result); + stream.write ("\n".getBytes ()); + stream.flush (); + } catch (Exception e) { + Log.keepLastException ("XML::writeDocument", e); + } + } + + // ======================================== + static public void writeElement (Element element, OutputStream stream) { + try { + DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance ().newDocumentBuilder (); + Document document = documentBuilder.newDocument (); + document.setXmlStandalone (true); + document.appendChild (document.importNode (element, true)); + XML.writeDocument (document, stream); + stream.flush (); + } catch (Exception e) { + Log.keepLastException ("XML::writeElement", e); + } + } + + // ======================================== + static public final void putToken (Hashtable hashtable, String token, String value) { + hashtable.put (token, (value == null) ? "" : value); + } + + // ======================================== + static public final Hashtable node2hashtable (Node child) { + Hashtable hashtable = new Hashtable (); + for (; child != null; child = child.getNextSibling ()) { + if (child.getNodeType () == Node.ELEMENT_NODE) { + Element elementTag = (Element) child; + String token = child.getNodeName (); + NodeList nodeList = ((Element) child).getChildNodes (); + if (nodeList.getLength () > 0) + hashtable.put (token, ((Text) nodeList.item (0)).getWholeText ()); + } + } + return hashtable; + } + + // ======================================== + static public final void hashtable2node (Document document, Element container, Hashtable hashtable) { + for (String token : hashtable.keySet ()) { + Element tag = document.createElement (token); + tag.appendChild (document.createTextNode (hashtable.get (token))); + container.appendChild (tag); + } + } + + // ======================================== +} diff --git a/src/java/misc/package-info.java b/src/java/misc/package-info.java new file mode 100644 index 0000000..5505354 --- /dev/null +++ b/src/java/misc/package-info.java @@ -0,0 +1,34 @@ +/** + * Ordre de lecture des classes
    + *
  • Log
  • + *
  • Config
  • + *
  • Bundle
  • + *
  • Util
  • + *
  • StateNotifier
  • + *
  • ProgressState
  • + + *
  • SpinnerSlider
  • + *
  • DatePanel
  • + *
  • HourPanel
  • + *
  • ImagePreview
  • + *
  • TitledDialog
  • + *
  • HtmlDialog
  • + + *
  • ApplicationManager
  • + *
  • OwnFrame
  • + *
  • Controller
  • + *
  • HelpManager
  • + *
  • ToolBarManager
  • + + *
  • Guide
  • + + *
  • XML
  • + + *
  • ColorCheckedLine
  • + *
  • CommandLineServer
  • + *
  • CommandLineWaiter
  • + + *
+ * @author F. Merciol + */ +package misc; diff --git a/src/java/network/Client.java b/src/java/network/Client.java new file mode 100644 index 0000000..f56a549 --- /dev/null +++ b/src/java/network/Client.java @@ -0,0 +1,147 @@ +package network; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.URL; +import java.net.URLConnection; + +import org.w3c.dom.Element; + +import misc.Log; +import misc.XML; + +public class Client extends Protocol { + + // ======================================== + static public final boolean trace = false; + static public final boolean socketNotURL = false; + + // ======================================== + private String host; + private int port; + private String uri = "/"; + private Proxy proxy; + + // ======================================== + public Client (String host, int port) { + super (ClientProtocolType); + this.host = host; + this.port = port; + } + + public Client (String host, int port, String uri) { + this (host, port); + this.uri = uri; + } + + public Client (String host, int port, String uri, String proxyHost, int proxyPort) { + this (host, port, uri); + proxy = new Proxy (Proxy.Type.HTTP, new InetSocketAddress (proxyHost, proxyPort)); + if (trace) + System.err.println (type+": proxy: "+proxy.address ()); + } + + public Client (String host, int port, String proxyHost, int proxyPort) { + this (host, port, "/", proxyHost, proxyPort); + } + + // ======================================== + public void start () { + (new Thread () { + public void run () { + Log.writeLog ("Protocol", "Client started pulling to "+host+":"+port+"("+proxy+")"); + for (currentThread = Thread.currentThread (); currentThread == Thread.currentThread (); ) { + long startTime = System.currentTimeMillis (); + try { + + // XXX prendre la date + + Log.writeLog ("Protocol", "client connected to "+host+":"+port+"("+proxy+")"); + URLConnection call = XMLConnection.getXMLConnection (new URL ("http", host, port, uri), proxy); + exchangeXML (call, true); + } catch (IOException e) { + // include : ConnectException, MalformedURLException, UnknownHostException + long stopTime = System.currentTimeMillis (); + if (stopTime - startTime < 1000) { + Log.keepLastException ("Client::start", e); + break; + } + // XXX si date trop courte sortir de la boucle + System.err.println ("coucou:"+e); + } + } + Log.writeLog ("Protocol", "Client stopped pulling to "+host+":"+port+"("+proxy+")"); + } + }).start (); + } + + // ======================================== + public synchronized void send (String applicationName, Element applicationArgument) { + super.send (applicationName, applicationArgument); + if (trace) + System.err.println (type+":send: immediat send "+applicationName); + // puis force l'envoie immediat + (new Thread () { + public void run () { + try { + Log.writeLog ("Protocol", "client connected to "+host+":"+port+"("+proxy+")"); + URLConnection call = XMLConnection.getXMLConnection (new URL ("http", host, port, uri), proxy); + exchangeXML (call, false); + } catch (IOException e) { + //} catch (UnknownHostException e) { + Log.keepLastException ("Client::send", e); + } + } + }).start (); + } + + + + // ======================================== + private void exchangeXML (URLConnection urlConnection, boolean askWaiting) + throws IOException { + OutputStream out = urlConnection.getOutputStream (); + PendingRequest pendingRequest = getPendingRequest (); + if (askWaiting) + pendingRequest.request.getDocumentElement ().setAttribute (waitAnswerToken, "yes"); + XML.writeDocument (pendingRequest.request, out); + out.flush (); + out.close (); + InputStream in = urlConnection.getInputStream (); + Element bundle = getBundle (in); + in.close (); + broadcastApplications (bundle); + } + + // ======================================== + private PendingRequest pendingRequest = new PendingRequest (); + + // ======================================== + private synchronized PendingRequest getPendingRequest () { + if (trace) + System.err.println (type+":getPendingRequest:"); + PendingRequest old = pendingRequest; + pendingRequest = new PendingRequest (); + return old; + } + + // ======================================== + protected void addPendingApplication (String applicationName, Element applicationArgument) { + Element applicationNode = pendingRequest.request.createElement (applicationToken); + applicationNode.setAttribute (nameToken, applicationName); + if (applicationArgument != null) + applicationNode.appendChild (pendingRequest.request.importNode (applicationArgument, true)); + pendingRequest.localPaquet.appendChild (applicationNode); + pendingRequest.readyToSend = true; + if (trace) { + System.err.println (type+":addPendingApplication: "); + XML.writeDocument (pendingRequest.request, System.err); + System.err.flush (); + } + } + + // ======================================== +} diff --git a/src/java/network/HTTPInputStream.java b/src/java/network/HTTPInputStream.java new file mode 100644 index 0000000..80ce88b --- /dev/null +++ b/src/java/network/HTTPInputStream.java @@ -0,0 +1,61 @@ +package network; + +import java.io.IOException; +import java.io.InputStream; +import java.io.FilterInputStream; +import java.util.Hashtable; + +public class HTTPInputStream extends FilterInputStream { + + // ======================================== + public String command; + public Hashtable headers = new Hashtable (); + private String lastHeader; + + // ======================================== + public HTTPInputStream (InputStream in) + throws IOException { + super (in); + parseHeaders (); + } + + // ======================================== + private void parseHeaders () + throws IOException { + for (;;) { + String line = readLine (); + if (line.equals ("")) { + break; + } + int pos = line.indexOf (":"); + if (pos < 0) { + if (lastHeader == null) + command = line; + else + throw new IllegalArgumentException (line+" is not a mime line"); + } else { + lastHeader = line.substring (0, pos).trim (); + headers.put (lastHeader, line.substring (pos+1).trim ()); + } + } + } + + // ======================================== + public String readLine () + throws IOException { + StringBuffer result = new StringBuffer (); + for (;;) { + int c = super.read (); + if (c < 0) + break; + if (c == '\r') + continue; + if (c == '\n') + break; + result.append ((char) c); + } + return result.toString (); + } + + // ======================================== +} diff --git a/src/java/network/HTTPOutputStream.java b/src/java/network/HTTPOutputStream.java new file mode 100644 index 0000000..42184e6 --- /dev/null +++ b/src/java/network/HTTPOutputStream.java @@ -0,0 +1,49 @@ +package network; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Hashtable; + +public class HTTPOutputStream extends ByteArrayOutputStream { + + // ======================================== + private static SimpleDateFormat dateFormat = new SimpleDateFormat ("EEE, d MMM yyyy HH:mm:ss z"); + + // ======================================== + private OutputStream out; + private String status = "HTTP/1.0 200 OK"; + public Hashtable headers = new Hashtable (); + + // ======================================== + public HTTPOutputStream (OutputStream out) { + this.out = out; + headers.put ("Server", "Merciol"); + headers.put ("Date", dateFormat.format (new Date ())); + headers.put ("Content-Type", "text/html"); + headers.put ("Connection", "close"); + } + + public void setStatus (String status) { + this.status = status; + } + + public void setHeader (String key, String value) { + headers.put (key, value); + } + + public void close () + throws IOException { + out.write ((status+"\n").getBytes ()); + for (String key : headers.keySet ()) { + out.write ((key+": "+headers.get (key)+"\n").getBytes ()); + } + out.write (("Content-Length: "+size ()+"\n").getBytes ()); + out.write ("\n".getBytes ()); + writeTo (out); + } + + // ======================================== +} diff --git a/src/java/network/Protocol.java b/src/java/network/Protocol.java new file mode 100644 index 0000000..db50d77 --- /dev/null +++ b/src/java/network/Protocol.java @@ -0,0 +1,225 @@ +package network; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Hashtable; +import java.util.List; +import java.util.Random; +import java.util.Vector; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +import misc.Log; +import misc.XML; + +/** + + + + msg + + + +*/ + +public abstract class Protocol { + + // remplace + // a placer avant la création de socket dans le cas ou elle n'a pas déjà été fait avant l'utilisation de X11 (via swing) + static public final void setIpV4 () { + java.util.Properties props = System.getProperties (); + props.setProperty ("java.net.preferIPv4Stack", ""+true); + System.setProperties (props); + } + + static { + setIpV4 (); + } + + // ======================================== + static public final boolean trace = false; + + static public final String bundleToken = "bundle"; + static public final String waitAnswerToken = "waitAnswer"; + static public final String paquetToken = "paquet"; + static public final String pearIdToken = "pearId"; + static public final String applicationToken = "application"; + static public final String nameToken = "name"; + static public final String argumentToken = "argument"; + + // ======================================== + static public final String ServerProtocolType = "ServerProtocol"; + static public final String ClientProtocolType = "ClientProtocol"; + + /** socket, serverHTTP or clientHTTP */ + protected String type; + public String getType () { return type; } + protected final String pearId = Long.toHexString ((new Random ()).nextLong ()).toUpperCase (); + + public Protocol (String type) { + this.type = type; + if (trace) + System.err.println (type+":pearId: "+pearId); + } + + // ======================================== + protected Thread currentThread; + + public abstract void start (); + + public synchronized boolean isAlive () { + return currentThread != null && currentThread.isAlive (); + } + + public void stop () { + currentThread = null; + } + + // ======================================== + public class PendingRequest { + public Document request; + public Element bundle; + public Element localPaquet; + public boolean readyToSend; + + public PendingRequest () { + try { + DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance ().newDocumentBuilder (); + request = documentBuilder.newDocument (); + request.setXmlStandalone (true); + bundle = request.createElement (bundleToken); + request.appendChild (bundle); + localPaquet = request.createElement (paquetToken); + localPaquet.setAttribute (pearIdToken, pearId); + bundle.appendChild (localPaquet); + } catch (ParserConfigurationException e) { + Log.keepLastException ("Protocol::PendingRequest", e); + } + } + } + + // ======================================== + static public Element getBundle (InputStream in) + throws IOException { + Document request = null; + request = XML.readDocument (in); + if (trace) { + System.err.println ("Protocol:getBundle: "); + XML.writeDocument (request, System.err); + System.err.flush (); + } + return request.getDocumentElement (); + } + + // ======================================== + protected void broadcastApplication (final String applicationName, final Element applicationArgument) { + Vector waiters = namedWaiters.get (applicationName); + if (waiters == null) { + if (trace) + Log.writeLog ("Protocol", "no "+applicationName+" application registered for "+type); + return; + } + for (final Waiter waiter : waiters) + (new Thread () { + public void run () { + if (trace) + System.err.println (type+" start "+applicationName); + waiter.receive (applicationArgument); + } + }).start (); + } + + // ======================================== + // c'est la réception + protected synchronized void broadcastApplications (Element bundle) { + NodeList paquets = bundle.getElementsByTagName (paquetToken); + for (int i = paquets.getLength () - 1; i >= 0; i--) { + Element paquet = (Element) paquets.item (i); + NodeList applications = paquet.getElementsByTagName (applicationToken); + for (int j = applications.getLength () - 1; j >= 0; j--) { + Element application = (Element) applications.item (j); + String applicationName = application.getAttribute (nameToken); + NodeList applicationArguments = application.getElementsByTagName (argumentToken); + if (applicationArguments.getLength () > 1) + throw new IllegalArgumentException ("too many argument for application "+applicationName); + broadcastApplication (applicationName, (Element) applicationArguments.item (0)); + } + } + } + + // ======================================== + abstract protected void addPendingApplication (String applicationName, Element applicationArgument); + + // ======================================== + // gestion des applications + // ======================================== + + private Hashtable> namedWaiters = new Hashtable> (); + + public synchronized void addWaiter (String applicationName, Waiter waiter) { + if (trace) + System.err.println (type+" addWaiter: "+applicationName+" waiter:"+waiter); + if (waiter == null) + return; + Vector waiters = namedWaiters.get (applicationName); + if (waiters == null) { + waiters = new Vector (); + namedWaiters.put (applicationName, waiters); + } + if (! waiters.contains (waiter)) + waiters.add (waiter); + } + + public synchronized void removeWaiter (String applicationName, Waiter waiter) { + if (trace) + System.err.println (type+" removeWaiter: "+applicationName+" waiter:"+waiter); + if (waiter == null) + return; + Vector waiters = namedWaiters.get (applicationName); + if (waiters == null) + return; + waiters.remove (waiter); + if (waiters.size () < 1) + namedWaiters.remove (applicationName); + } + + // ======================================== + // c'est l'envoi + public synchronized void send (String applicationName, Element applicationArgument) { + if (trace) + System.err.println (type+": send "+applicationName); + addPendingApplication (applicationName, applicationArgument); + } + + // ======================================== + // c'est l'envoi + public synchronized void send (String applicationName, String... args) { + if (trace) + System.err.print (type+": send "+applicationName); + Element applicationArgument = createArgument (); + for (int i = 0; i < args.length; i += 2) { + if (trace) + System.err.print (" "+args[i]+"=\""+args[i+1]+"\""); + applicationArgument.setAttribute (args[i], args[i+1]); + } + if (trace) + System.err.println (); + send (applicationName, applicationArgument); + } + + // ======================================== + static public Element createArgument () { + try { + Document document = DocumentBuilderFactory.newInstance ().newDocumentBuilder ().newDocument (); + return document.createElement (argumentToken); + } catch (ParserConfigurationException e) { + return null; + } + } + + // ======================================== +} diff --git a/src/java/network/ProtocolManager.java b/src/java/network/ProtocolManager.java new file mode 100644 index 0000000..765aab9 --- /dev/null +++ b/src/java/network/ProtocolManager.java @@ -0,0 +1,318 @@ +package network; + +import java.awt.Container; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.lang.reflect.Method; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.List; +import java.util.TreeSet; +import java.util.Vector; + +import javax.swing.AbstractButton; +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JMenu; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.SwingConstants; + +import misc.ApplicationManager; +import misc.Bundle; +import misc.Config; +import misc.OwnFrame; +import misc.Util; +import static misc.Util.GBC; +import static misc.Util.GBCNL; + +@SuppressWarnings ("serial") public class ProtocolManager implements ApplicationManager, ActionListener { + + // ======================================== + private OwnFrame controller; + private Protocol protocol; + + private JPanel protoPanel; + private ButtonGroup group = new ButtonGroup (); + private JComboBox nsHostCB; + private JComboBox hostCB; + private JComboBox portCB; + private JComboBox uriCB; + private JCheckBox proxyCheckBox; + private JComboBox proxyHostCB; + private JComboBox proxyPortCB; + + public Protocol getProtocol () { return protocol; } + public void setProtocol (Protocol protocol) { + if (this.protocol != null) + this.protocol.stop (); + this.protocol = protocol; + if (protocol != null) + protocol.start (); + + Thread.yield (); + updateCommands (); + updateProtocol (); + broadcastStartProtocol (); + } + + // ======================================== + public ProtocolManager (OwnFrame controller) { + this.controller = controller; + createGUI (); + start (); + } + + // ======================================== + protected Thread currentThread; + + public void stop () { + currentThread = null; + } + + public void start () { + (new Thread () { + public void run () { + for (currentThread = Thread.currentThread (); currentThread == Thread.currentThread (); ) { + updateCommands (); + Util.sleep (5); + } + } + }).start (); + } + + // ======================================== + public void createGUI () { + final TreeSet hosts = new TreeSet (); + hosts.add ("localhost"); + nsHostCB = new JComboBox (new Vector (hosts)); + nsHostCB.setSelectedItem ("localhost"); + + + (new Thread () { + public void run () { + try { + for (Enumeration e = NetworkInterface.getNetworkInterfaces (); e.hasMoreElements (); ) { + NetworkInterface ni = e.nextElement (); + for (Enumeration e2 = ni.getInetAddresses (); e2.hasMoreElements (); ) { + InetAddress ia = e2.nextElement (); + for (String host : Arrays.asList (ia.getHostAddress (), ia.getHostName ())) + if (!hosts.contains (host)) { + nsHostCB.addItem (host); + hosts.add (host); + } + } + } + } catch (Exception e3) { + } + } + }).start (); + + hostCB = new JComboBox (); + portCB = new JComboBox (); + uriCB = new JComboBox (); + proxyHostCB = new JComboBox (); + proxyPortCB = new JComboBox (); + + Config.loadJComboBox ("hostProtocol", hostCB, "localhost"); + Config.loadJComboBoxInteger ("portProtocol", portCB, "8080"); + Config.loadJComboBox ("uriProtocol", uriCB, "/"); + Config.loadJComboBox ("hostProxy", proxyHostCB, "localhost"); + Config.loadJComboBoxInteger ("portProxy", proxyPortCB, "3128"); + + hostCB.setEditable (true); + portCB.setEditable (true); + uriCB.setEditable (true); + proxyHostCB.setEditable (true); + proxyPortCB.setEditable (true); + + String protocolType = Config.getString (protocolTypeToken, setServerToken); + protoPanel = Util.getGridBagPanel (); + + Util.addLabelFields (protoPanel, "AvailableNetworkCard", nsHostCB); + Util.addLabel ("ConnectionType", SwingConstants.CENTER, protoPanel, GBCNL); + + Util.addRadioButton (setServerToken, this, group, protocolType, protoPanel, GBC); + Util.addComponent (hostCB, protoPanel, GBC); + Util.addComponent (new JLabel (":"), protoPanel, GBC); + Util.addComponent (portCB, protoPanel, GBCNL); + + Util.addRadioButton (setClientToken, this, group, protocolType, protoPanel, GBC); + Util.addComponent (uriCB, protoPanel, GBCNL); + + proxyCheckBox = Util.addCheckButtonConfig (setProxyToken, this, false, protoPanel, GBC); + Util.addComponent (proxyHostCB, protoPanel, GBC); + Util.addComponent (new JLabel (":"), protoPanel, GBC); + Util.addComponent (proxyPortCB, protoPanel, GBCNL); + + + Util.addComponent (new JLabel (), protoPanel, GBCNL); + + Util.addRadioButton (unsetProtocolToken, this, group, protocolType, protoPanel, GBC); + Util.addComponent (new JLabel (), protoPanel, GBCNL); + + if (unsetProtocolToken.equals (protocolType)) + actionUnsetProtocol (); + else if (setClientToken.equals (protocolType)) + actionSetClient (); + else + actionSetServer (); + } + + // ======================================== + static public final List actionsNames = Arrays.asList ("LinkType"); + static public final String protocolTypeToken = "ProtocolType"; + static public final String setServerToken = "SetServer"; + static public final String setClientToken = "SetClient"; + static public final String unsetProtocolToken = "UnsetProtocol"; + static public final List typeActionsNames = + Arrays.asList (setClientToken, setServerToken, unsetProtocolToken); + static public final String setProxyToken = "SetProxy"; + @SuppressWarnings("unchecked") + static public final Hashtable actionsMethod = + Util.collectMethod (ProtocolManager.class, + Util.merge (actionsNames, typeActionsNames, + Arrays.asList (setProxyToken))); + + public void actionPerformed (ActionEvent e) { + Util.actionPerformed (actionsMethod, e, this); + } + + // ======================================== + public synchronized void setIPEnable (boolean enable) { + hostCB.setEnabled (enable); + portCB.setEnabled (enable); + uriCB.setEnabled (enable); + proxyCheckBox.setEnabled (enable); + } + public synchronized void setProxyEnable (boolean enable) { + proxyHostCB.setEnabled (enable); + proxyPortCB.setEnabled (enable); + } + + // ======================================== + public synchronized void actionSetServer () { + setIPEnable (false); + portCB.setEnabled (true); + setProxyEnable (false); + } + + public synchronized void actionSetClient () { + setIPEnable (true); + actionSetProxy (); + } + + public synchronized void actionSetProxy () { + boolean selected = proxyCheckBox.isSelected (); + Config.setBoolean (setProxyToken+Config.checkedPostfix, selected); + setProxyEnable (selected); + } + + public synchronized void actionUnsetProtocol () { + setIPEnable (false); + setProxyEnable (false); + } + + // ======================================== + public synchronized void actionLinkType () { + if (JOptionPane.OK_OPTION != + JOptionPane.showConfirmDialog (controller.getJFrame (), protoPanel, + Bundle.getTitle ("LinkType"), + JOptionPane.OK_CANCEL_OPTION)) + return; + String protocolType = group.getSelection ().getActionCommand (); + Config.setString (protocolTypeToken, protocolType); + Config.saveJComboBox ("hostProtocol", hostCB); + Config.saveJComboBoxInteger ("portProtocol", portCB); + Config.saveJComboBox ("uriProtocol", uriCB); + Config.saveJComboBox ("hostProxy", proxyHostCB); + Config.saveJComboBoxInteger ("portProxy", proxyPortCB); + + Protocol protocol = null; + if (unsetProtocolToken.equals (protocolType)) + ; + } else if (setClientToken.equals (protocolType)) { + if (proxyCheckBox.isSelected ()) + protocol = new Client (hostCB.getItemAt (hostCB.getSelectedIndex ()), + portCB.getItemAt (portCB.getSelectedIndex ()), + uriCB.getItemAt (uriCB.getSelectedIndex ()), + proxyHostCB.getItemAt (proxyHostCB.getSelectedIndex ()), + proxyPortCB.getItemAt (proxyPortCB.getSelectedIndex ())); + else + protocol = new Client (hostCB.getItemAt (hostCB.getSelectedIndex ()), + portCB.getItemAt (portCB.getSelectedIndex ()), + uriCB.getItemAt (uriCB.getSelectedIndex ())); + } else if (setServerToken.equals (protocolType)) + protocol = new Server (portCB.getItemAt (portCB.getSelectedIndex ())); + setProtocol (protocol); + } + + // ======================================== + private Vector noNetworkCommands = new Vector (); + public synchronized void addNoNetworkCommand (AbstractButton noNetworkCommand) { + if (noNetworkCommand == null) + return; + boolean isAlive = protocol != null && protocol.isAlive (); + noNetworkCommands.add (noNetworkCommand); + noNetworkCommand.setEnabled (!isAlive); + } + public synchronized void updateCommands () { + boolean isAlive = protocol != null && protocol.isAlive (); + for (AbstractButton noNetworkCommand : noNetworkCommands) + noNetworkCommand.setEnabled (!isAlive); + } + + // ======================================== + private Vector waiters = new Vector (); + public synchronized void addWaiter (Waiter waiter) { + waiters.add (waiter); + } + + public synchronized void updateProtocol () { + for (Waiter waiter : waiters) + waiter.setProtocol (protocol); + } + + // ======================================== + Vector protocolObservers = new Vector (); + public void addProtocolObserver (ProtocolObserver protocolObserver) { + protocolObservers.add (protocolObserver); + } + public void removeProtocolObserver (ProtocolObserver protocolObserver) { + protocolObservers.remove (protocolObserver); + } + + // ======================================== + public void broadcastStartProtocol () { + if (protocol != null) + for (ProtocolObserver protocolObserver : protocolObservers) + protocolObserver.startProtocol (protocol); + } + + // ======================================== + public void addMenuItem (JMenu... jMenu) { + Util.addMenuItem (actionsNames, this, jMenu[0]); + } + + // ======================================== + public void addIconButtons (Container... container) { + Util.addIconButton (actionsNames, this, container[0]); + } + + // ======================================== + public void addActiveButtons (Hashtable buttons) { + // XXX ??? fournir la sous-liste + for (String action : Arrays.asList (GameManager.actionPlayersClass, GameManager.actionCut, + GameManager.actionEmpty, GameManager.actionOpen)) + addNoNetworkCommand (buttons.get (action)); + } + + // ======================================== +} diff --git a/src/java/network/ProtocolObserver.java b/src/java/network/ProtocolObserver.java new file mode 100644 index 0000000..1d3f5ec --- /dev/null +++ b/src/java/network/ProtocolObserver.java @@ -0,0 +1,6 @@ +package network; + +public interface ProtocolObserver { + + public void startProtocol (Protocol protocol); +} diff --git a/src/java/network/Server.java b/src/java/network/Server.java new file mode 100644 index 0000000..1a1bb7e --- /dev/null +++ b/src/java/network/Server.java @@ -0,0 +1,278 @@ +package network; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.net.SocketTimeoutException; +import java.util.Hashtable; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +import misc.Log; +import misc.XML; + +public class Server extends Protocol { + + // ======================================== + static public final boolean trace = false; + + static public final String loginApplicationName = "login"; + static public final String askToken = "ask"; + static public final String connectToken = "connect"; + static public final String disconnectToken = "disconnect"; + static public final String userToken = "user"; + static public final String acceptToken = "accept"; + static public final String unknownToken = "unknown"; + + // ======================================== + private int port; + + public Server (int port) { + super (ServerProtocolType); + this.port = port; + } + + // ======================================== + public void start () { + (new Thread () { + public void run () { + try { + final ServerSocket serverSocket = new ServerSocket (port); + Log.writeLog ("Protocol", "Server started on port "+port); + // pour pouvoir arreter proprement l'activité + for (currentThread = Thread.currentThread (); currentThread == Thread.currentThread (); ) { + try { + final Socket call = serverSocket.accept (); + (new Thread () { + public void run () { + try { + Log.writeLog ("Protocol", "Server accept "+call); + exchangeXML (new HTTPInputStream (call.getInputStream ()), + new HTTPOutputStream (call.getOutputStream ())); + call.close (); + } catch (SocketException e) { + Log.writeLog ("Protocol", "Server connection lost "+call); + try { + serverSocket.close (); + Log.writeLog ("Protocol", "Server close"); + } catch (IOException e2) { + Log.writeLog ("Protocol", "Server: "+e2); + } + } catch (IOException e) { + Log.keepLastException ("Server::start", e); + } + } + }).start (); + } catch (SocketTimeoutException e) { + } + } + serverSocket.close (); + } catch (SocketException e) { + Log.writeLog ("Protocol", "Server connection lost: "+e); + } catch (IOException e) { + //} catch (SocketException e) { + Log.keepLastException ("Server::start", e); + } + Log.writeLog ("Protocol", "Server stopped on port "+port); + } + }).start (); + } + + // ======================================== + public void stop () { + currentThread = null; + try { + // force le accept pour sortir du run + (new Socket ("localhost", port)).close (); + } catch (Exception e) { + } + pendingRequests = new Hashtable (); + } + + // ======================================== + private String getAskAndDelLogin (Element paquet) { + if (trace) + System.err.println (type+":getConnectAndDelLogin:"); + + String result = null; + NodeList applications = paquet.getElementsByTagName (applicationToken); + for (int j = applications.getLength () - 1; j >= 0; j--) { + Element application = (Element) applications.item (j); + if (loginApplicationName.equals (application.getAttribute (nameToken))) { + NodeList applicationArguments = application.getElementsByTagName (argumentToken); + if (applicationArguments.getLength () < 1) + continue; + Element applicationArgument = (Element) applicationArguments.item (0); + String ask = applicationArgument.getAttribute (askToken); + if (connectToken.equals (ask) || disconnectToken.equals (ask)) + result = ask; + paquet.removeChild (application); + } + } + return result; + } + + // ======================================== + private Element createUserAnswer (String state) { + Element argument = createArgument (); + argument.setAttribute (userToken, state); + return argument; + } + + // ======================================== + /** Server way : receive then send. */ + private synchronized void exchangeXML (InputStream in, OutputStream out) + throws IOException { + Element bundle = getBundle (in); + String waitRequest = bundle.getAttribute (waitAnswerToken); + NodeList paquets = bundle.getElementsByTagName (paquetToken); + if (paquets.getLength () != 1) + // Si c'est un client, il n'y a qu'un paquet + Log.writeLog ("Protocol", "exchangeXML: Only one paquet expected ("+paquets.getLength ()+")!"); + // XXX test si nombre incohérant (normalement un seul paquet par bundle) + Element paquet = (Element) paquets.item (0); + String pearId = paquet.getAttribute (pearIdToken); + addClient (pearId); + + String ask = getAskAndDelLogin (paquet); + if (ask != null) { + try { + DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance ().newDocumentBuilder (); + Document emptyRequest = documentBuilder.newDocument (); + emptyRequest.setXmlStandalone (true); + + Element applicationNode = emptyRequest.createElement (applicationToken); + applicationNode.setAttribute (nameToken, loginApplicationName); + applicationNode.appendChild (emptyRequest.importNode (createUserAnswer (connectToken.equals (ask) ? acceptToken : unknownToken), true)); + + addPendingApplicationToClient (pearId, applicationNode); + } catch (ParserConfigurationException e) { + Log.keepLastException ("Server::exchangeXML", e); + } + } + + broadcastClients (paquet, pearId); + broadcastApplications (bundle); + PendingRequest pendingRequest = getPendingRequest (pearId, waitRequest != null && "yes".equals (waitRequest)); + XML.writeDocument (pendingRequest.request, out); + out.flush (); + out.close (); + } + + // ======================================== + private Hashtable pendingRequests = new Hashtable (); + + // ======================================== + private synchronized void addClient (String pearId) { + if (trace) + System.err.println (type+":addClient: "+pearId); + // XXX si pearId est null ??? + if (pearId == null || pendingRequests.get (pearId) != null) + return; + resetClient (pearId); + // YYY nouveau client => startProtocol + } + + // ======================================== + private synchronized void resetClient (String pearId) { + if (trace) + System.err.println (type+":resetClient: "+pearId); + pendingRequests.put (pearId, new PendingRequest ()); + } + + // ======================================== + private synchronized void sendClient (String toClientId, Element paquet) { + PendingRequest pendingRequest = pendingRequests.get (toClientId); + pendingRequest.bundle.appendChild (pendingRequest.request.importNode (paquet, true)); + pendingRequest.readyToSend = true; + notifyAll (); + if (trace) { + System.err.println (type+":broadcastClients: "+pearId); + XML.writeDocument (pendingRequest.request, System.err); + System.err.flush (); + } + } + + // ======================================== + private synchronized void broadcastClients (Element paquet, String fromClientId) { + NodeList applications = paquet.getElementsByTagName (applicationToken); + if (applications.getLength () < 1) + return; + for (String toClientId : pendingRequests.keySet ()) { + if (toClientId.equals (fromClientId)) + continue; + sendClient (toClientId, paquet); + } + } + + // ======================================== + private synchronized PendingRequest getPendingRequest (String pearId, boolean waitRequest) { + if (waitRequest) { + if (trace) + System.err.println (type+":getPendingRequest: waitting for "+pearId); + for (;;) { + PendingRequest pendingRequest = pendingRequests.get (pearId); + if (pendingRequest.readyToSend) + break; + try { + wait (); + } catch (InterruptedException e) { + } + } + if (trace) + System.err.println (type+": getPendingRequest "+pearId+" is ready"); + } + PendingRequest old = pendingRequests.get (pearId); + resetClient (pearId); + return old; + } + + // ======================================== + protected void addPendingApplicationToClient (String pearId, Element applicationNode) { + PendingRequest pendingRequest = pendingRequests.get (pearId); + pendingRequest.localPaquet.appendChild (pendingRequest.request.importNode (applicationNode, true)); + pendingRequest.readyToSend = true; + notifyAll (); + if (trace) { + System.err.println (type+":addPendingApplication: "+pearId); + XML.writeDocument (pendingRequest.request, System.err); + System.err.flush (); + } + } + + // ======================================== + protected void addPendingApplication (String applicationName, Element applicationArgument) { + try { + if (loginApplicationName.equals (applicationName)) { + String ask = applicationArgument.getAttribute (askToken); + if (connectToken.equals (ask)) + broadcastApplication (loginApplicationName, createUserAnswer (acceptToken)); + else if (disconnectToken.equals (ask)) + broadcastApplication (loginApplicationName, createUserAnswer (unknownToken)); + return; + } + + DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance ().newDocumentBuilder (); + Document emptyRequest = documentBuilder.newDocument (); + emptyRequest.setXmlStandalone (true); + + Element applicationNode = emptyRequest.createElement (applicationToken); + applicationNode.setAttribute (nameToken, applicationName); + if (applicationArgument != null) + applicationNode.appendChild (emptyRequest.importNode (applicationArgument, true)); + for (String pearId : pendingRequests.keySet ()) + addPendingApplicationToClient (pearId, applicationNode); + } catch (ParserConfigurationException e) { + Log.keepLastException ("Server::addPendingApplication", e); + } + } + + // ======================================== +} diff --git a/src/java/network/Waiter.java b/src/java/network/Waiter.java new file mode 100644 index 0000000..a5a573b --- /dev/null +++ b/src/java/network/Waiter.java @@ -0,0 +1,14 @@ +package network; + +import org.w3c.dom.Element; + +public interface Waiter { + + // ======================================== + public void setProtocol (Protocol protocol); + + // ======================================== + public void receive (Element argument); + + // ======================================== +} diff --git a/src/java/network/XMLConnection.java b/src/java/network/XMLConnection.java new file mode 100644 index 0000000..a91ceb1 --- /dev/null +++ b/src/java/network/XMLConnection.java @@ -0,0 +1,39 @@ +package network; + +import java.io.IOException; +import java.net.URLConnection; +import java.net.URL; +import java.net.Proxy; + +public class XMLConnection { + + // ======================================== + static public URLConnection getXMLConnection (URL url) + throws IOException { + URLConnection urlConnection = url.openConnection (); + urlConnection.setDoOutput (true); + urlConnection.setUseCaches (false); + urlConnection.setRequestProperty ("Accept", "application/xml"); + urlConnection.setRequestProperty ("User-Agent", "Merciol"); + urlConnection.setRequestProperty ("Content-type", "application/xml"); + return urlConnection; + } + + // ======================================== + static public URLConnection getXMLConnection (URL url, Proxy proxy) + throws IOException{ + URLConnection urlConnection = (proxy == null) ? url.openConnection () : url.openConnection (proxy); + urlConnection.setDoOutput (true); + urlConnection.setUseCaches (false); + urlConnection.setRequestProperty ("Accept", "application/xml"); + urlConnection.setRequestProperty ("User-Agent", "Merciol"); + urlConnection.setRequestProperty ("Content-type", "application/xml"); + return urlConnection; + } + + public void connect () { + throw new IllegalArgumentException ("Can't reconnect an XMLConnection"); + } + + // ======================================== +} diff --git a/src/java/network/chat/Chat.java b/src/java/network/chat/Chat.java new file mode 100644 index 0000000..f896967 --- /dev/null +++ b/src/java/network/chat/Chat.java @@ -0,0 +1,192 @@ +package network.chat; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.Date; +import java.util.Random; +import java.util.Vector; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.w3c.dom.Text; + +import misc.Bundle; +import misc.Config; +import misc.Log; +import misc.XML; +import network.Protocol; +import network.Waiter; + +/** + * Modèle. + + * + * + * + * + * azertyuiop + * + * + * + */ + +public class Chat implements Waiter { + + // ======================================== + static public final String applicationName = "chat"; + static public final String quoteToken = "quote"; + static public final String sequenceToken = "sequence"; + static public final String dateToken = "date"; + static public final String speakerToken = "speaker"; + static public final SimpleDateFormat dateFormat = new SimpleDateFormat ("HH:mm:ss"); + + private Protocol protocol; + private String pseudo; + + private int lastSequence; + private Vector dialog = new Vector (); + private boolean modified = false; + + public synchronized void setProtocol (Protocol protocol) { + if (this.protocol != null) + this.protocol.removeWaiter (applicationName, this); + this.protocol = protocol; + if (protocol != null) + protocol.addWaiter (applicationName, this); + } + + static public String getRandomPseudo () { + return + Bundle.getString ("anonymous", null)+"_"+Integer.toHexString ((new Random ()).nextInt ()).toUpperCase ().substring (0, 4); + } + public String getPseudo () { return pseudo; } + public void setPseudo (String pseudo) { + this.pseudo = pseudo; + broadcastRenameSpeaker (); + Config.setString ("chatLogin", pseudo); + } + + public boolean getModified () { return modified; } + + // ======================================== + public Chat (String pseudo) { + this.pseudo = pseudo; + dateFormat.setLenient (false); + } + + public Chat (String pseudo, Protocol protocol) { + this (pseudo); + setProtocol (protocol); + } + + // ======================================== + public void clear () { + dialog = new Vector (); + modified = false; + broadcastClearChat (); + } + + // ======================================== + public void save (File file) + throws IOException { + try { + file.setExecutable (false); + + Collections.sort (dialog, new ChatQuote (0, new Date (), "", "")); + + DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance ().newDocumentBuilder (); + Document document = documentBuilder.newDocument (); + document.setXmlStandalone (true); + + Element applicationNode = document.createElement (applicationName); + document.appendChild (applicationNode); + for (ChatQuote quote : dialog) { + Element quoteElement = document.createElement (quoteToken); + applicationNode.appendChild (quoteElement); + quoteElement.setAttribute (sequenceToken, ""+quote.sequence); + quoteElement.setAttribute (dateToken, dateFormat.format (quote.date)); + quoteElement.setAttribute (speakerToken, quote.speaker); + quoteElement.appendChild (document.createTextNode (quote.sentence)); + } + FileOutputStream out = new FileOutputStream (file); + XML.writeDocument (document, out); + out.close (); + modified = false; + broadcastChatModifiedChange (); + } catch (ParserConfigurationException e) { + Log.keepLastException ("Chat::save", e); + } + } + + // ======================================== + public synchronized void say (String sentence) { + if (sentence == null) + sentence = ""; + ChatQuote quote = new ChatQuote (++lastSequence, new Date (), pseudo, sentence); + broadcastTalk (quote); + if (protocol == null) + return; + Element argument = Protocol.createArgument (); + argument.setAttribute (sequenceToken, ""+quote.sequence); + argument.setAttribute (dateToken, dateFormat.format (quote.date)); + argument.setAttribute (speakerToken, quote.speaker); + argument.appendChild (argument.getOwnerDocument ().createTextNode (quote.sentence)); + protocol.send (applicationName, argument); + } + + // ======================================== + public synchronized void receive (Element argument) { + String speaker = argument.getAttribute (speakerToken); + Date date = new Date (); + try { + dateFormat.parse (argument.getAttribute (dateToken)); + } catch (ParseException e) { + Log.keepLastException ("Chat::receive", e); + } + int sequence = Integer.parseInt (argument.getAttribute (sequenceToken)); + lastSequence = Math.max (lastSequence, sequence); + String sentence = ""; + NodeList nodeList = argument.getChildNodes (); + if (nodeList.getLength () > 0) + sentence = ((Text) nodeList.item (0)).getWholeText (); + broadcastTalk (new ChatQuote (sequence, date, speaker, sentence)); + } + + // ======================================== + Vector chatObservers = new Vector (); + public void addChatObserver (ChatObserver chatObserver) { chatObservers.add (chatObserver); } + public void removeChatObserver (ChatObserver chatObserver) { chatObservers.remove (chatObserver); } + + public void broadcastTalk (ChatQuote quote) { + dialog.add (quote); + modified = true; + for (ChatObserver chatObserver : chatObservers) + chatObserver.talk (quote); + broadcastChatModifiedChange (); + } + + public void broadcastRenameSpeaker () { + for (ChatObserver chatObserver : chatObservers) + chatObserver.renameSpeaker (pseudo); + } + + public void broadcastChatModifiedChange () { + for (ChatObserver chatObserver : chatObservers) + chatObserver.chatModifiedChange (modified); + } + + public void broadcastClearChat () { + for (ChatObserver chatObserver : chatObservers) + chatObserver.clearChat (); + broadcastChatModifiedChange (); + } + + // ======================================== +} diff --git a/src/java/network/chat/ChatController.java b/src/java/network/chat/ChatController.java new file mode 100644 index 0000000..56b8ff0 --- /dev/null +++ b/src/java/network/chat/ChatController.java @@ -0,0 +1,107 @@ +package network.chat; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Image; +import java.text.MessageFormat; +import javax.swing.JMenuBar; +import javax.swing.JOptionPane; +import javax.swing.JPanel; + +import network.login.Login; +import network.login.LoginManager; +import network.ProtocolManager; +import misc.Controller; +import misc.Bundle; +import misc.Config; +import misc.HelpManager; +import misc.MultiToolBarBorderLayout; +import misc.Util; + +/** + Créer et relie le modèle avec le graphisme. +*/ +public class ChatController extends Controller implements ChatObserver { + + // ======================================== + public ChatController () { + super (new Chat (Config.getString ("chatLogin", Bundle.getString ("anonymous", null)))); + } + + // ======================================== + Chat chat; + Login login; + + ProtocolManager protocolManager; + ChatManager chatManager; + LoginManager loginManager; + HelpManager helpManager; + + JChat jChat; + JChatMenuBar jChatMenuBar; + + // ======================================== + public String getTitle () { return MessageFormat.format (Bundle.getTitle ("Chat"), chat.getPseudo ()); } + public Image getIcon () { return Util.loadImage (Config.getString ("ChatIcon", "data/images/chat/chat.png")); } + + // ======================================== + protected boolean tryClosingWindows () { + Config.save ("Chat"); + if (chat.getModified ()) { + switch (JOptionPane.showConfirmDialog (jFrame, Bundle.getMessage ("SaveChat"), + Bundle.getTitle ("ChatNotSaved"), + JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE)) { + case JOptionPane.YES_OPTION: + chatManager.actionSaveAs (); + return true; + case JOptionPane.NO_OPTION: + return true; + case JOptionPane.CANCEL_OPTION: + case JOptionPane.CLOSED_OPTION: + return false; + } + } + return true; + } + + // ======================================== + protected void createModel (Chat chat) { + this.chat = chat; + login = new Login (Config.getString ("pseudo", Login.getRandomLogin ())); + chat.addChatObserver (this); + } + + // ======================================== + protected Component createGUI () { + JPanel contentPane = new JPanel (new MultiToolBarBorderLayout ()); + + protocolManager = new ProtocolManager (this); + protocolManager.addWaiter (login); + protocolManager.addWaiter (chat); + protocolManager.start (); + + chatManager = new ChatManager (this, chat); + loginManager = new LoginManager (this, login); + + helpManager = new HelpManager (this, "Chat"); + + jChat = new JChat (protocolManager, loginManager, chatManager); + + contentPane.add (jChat, BorderLayout.CENTER); + return contentPane; + } + + // ======================================== + protected JMenuBar createMenuBar () { + jChatMenuBar = new JChatMenuBar (this, protocolManager, loginManager, chatManager, helpManager, null); + return jChatMenuBar; + } + + // ======================================== + public void talk (ChatQuote quote) {} + public void renameSpeaker (String speaker) { jFrame.setTitle (getTitle ()); } + public void chatModifiedChange (boolean modified) {} + public void clearChat () {} + + // ======================================== +} diff --git a/src/java/network/chat/ChatManager.java b/src/java/network/chat/ChatManager.java new file mode 100644 index 0000000..5408125 --- /dev/null +++ b/src/java/network/chat/ChatManager.java @@ -0,0 +1,142 @@ +package network.chat; + +import java.awt.Container; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Hashtable; +import java.util.List; +import java.util.Vector; +import javax.swing.AbstractButton; +import javax.swing.JCheckBox; +import javax.swing.JFileChooser; +import javax.swing.JMenu; +import javax.swing.JOptionPane; +import javax.swing.filechooser.FileNameExtensionFilter; + +import misc.ApplicationManager; +import misc.Bundle; +import misc.Config; +import misc.OwnFrame; +import misc.Util; + +/** + comportement déclanché par des actionneurs graphiques (menu ou bouton). +*/ +@SuppressWarnings ("serial") public class ChatManager implements ApplicationManager, ActionListener, ChatObserver { + + // ======================================== + private OwnFrame controller; + + static public final String actionPseudo = "Pseudo"; + static public final String extention = "chat"; + static public final String actionClear = "Clear"; + static public final List loginActionsNames = Arrays.asList (actionPseudo); + static public final List saveActionsNames = Arrays.asList (actionClear, "SaveAs"); + @SuppressWarnings("unchecked") + static public final List actionsNames = + Util.merge (loginActionsNames, saveActionsNames, Arrays.asList ("ManageExtention")); + @SuppressWarnings("unchecked") + static public final Hashtable actionsMethod = + Util.collectMethod (ChatManager.class, actionsNames); + + public void actionPerformed (ActionEvent e) { + Util.actionPerformed (actionsMethod, e, this); + } + + // ======================================== + private Chat chat; + + public Chat getChat () { return chat; } + + // ======================================== + public ChatManager (OwnFrame controller, Chat chat) { + this.controller = controller; + this.chat = chat; + chat.addChatObserver (this); + jFileChooser.setFileFilter (new FileNameExtensionFilter (Bundle.getLabel ("ChatFilter"), extention)); + jFileChooser.setAccessory (manageExtensionCheckBox = + Util.newCheckButtonConfig ("ManageExtention", this, true)); + } + + // ======================================== + public void actionManageExtention () { + // rien a faire + } + + public void actionPseudo () { + String newName = + (String) JOptionPane.showInputDialog (controller.getJFrame (), Bundle.getLabel ("NewPseudo"), + Bundle.getTitle ("ChangePseudo"), JOptionPane.INFORMATION_MESSAGE, + null, null, Chat.getRandomPseudo ()); + if (newName == null) + return; + chat.setPseudo (newName); + } + + public synchronized void actionClear () { + if (chat.getModified () && + JOptionPane.YES_OPTION != + JOptionPane.showConfirmDialog (controller.getJFrame (), Bundle.getMessage ("RealyClearChat"), + Bundle.getTitle ("ChatNotSaved"), JOptionPane.WARNING_MESSAGE)) + return; + chat.clear (); + } + + final JFileChooser jFileChooser = new JFileChooser (Config.getString ("ChatDir", "data/chat")); + final JCheckBox manageExtensionCheckBox; + + public synchronized void actionSaveAs () { + jFileChooser.setFileSelectionMode (JFileChooser.FILES_ONLY); + if (jFileChooser.showSaveDialog (controller.getJFrame ()) != JFileChooser.APPROVE_OPTION) + return; + File file = jFileChooser.getSelectedFile (); + Config.setString ("ChatDir", file.getParent ()); + Config.save ("Chat"); + try { + if (manageExtensionCheckBox.isSelected () && !file.getName ().endsWith ("."+extention)) + file = new File (file.getParent (), file.getName () + "."+extention); + chat.save (file); + } catch (IOException e) { + JOptionPane.showMessageDialog (controller.getJFrame (), e.getMessage (), "alert", JOptionPane.WARNING_MESSAGE); + } + } + + // ======================================== + private Vector saveCommands = new Vector (); + public void addSaveCommand (AbstractButton saveCommand) { + saveCommands.add (saveCommand); + saveCommand.setEnabled (chat.getModified ()); + } + + // ======================================== + public void talk (ChatQuote quote) {} + public void renameSpeaker (String speaker) {} + public void chatModifiedChange (boolean modified) { + for (AbstractButton saveCommand : saveCommands) + saveCommand.setEnabled (modified); + } + public void clearChat () {} + + // ======================================== + public void addMenuItem (JMenu... jMenu) { + Util.addMenuItem (loginActionsNames, this, jMenu[0]); + Util.addMenuItem (saveActionsNames, this, jMenu[0]); + } + + // ======================================== + public void addIconButtons (Container... containers) { + Util.addIconButton (loginActionsNames, this, containers[0]); + Util.addIconButton (saveActionsNames, this, containers[0]); + } + + // ======================================== + public void addActiveButtons (Hashtable buttons) { + addSaveCommand (buttons.get (actionClear)); + } + + // ======================================== +} diff --git a/src/java/network/chat/ChatObserver.java b/src/java/network/chat/ChatObserver.java new file mode 100644 index 0000000..9d618fd --- /dev/null +++ b/src/java/network/chat/ChatObserver.java @@ -0,0 +1,17 @@ +package network.chat; + +import java.util.Date; + +/** + Modificaion de modèle qui peuvent être observé. +*/ +public interface ChatObserver { + + // ======================================== + public void talk (ChatQuote quote); + public void renameSpeaker (String speaker); + public void chatModifiedChange (boolean modified); + public void clearChat (); + + // ======================================== +} diff --git a/src/java/network/chat/ChatQuote.java b/src/java/network/chat/ChatQuote.java new file mode 100644 index 0000000..dfe41a5 --- /dev/null +++ b/src/java/network/chat/ChatQuote.java @@ -0,0 +1,45 @@ +package network.chat; + +import java.util.Comparator; +import java.util.Date; + +/** + Modificaion de modèle qui peuvent être observé. +*/ +public class ChatQuote implements Comparator { + + int sequence; + Date date; + String speaker; + String sentence; + + // ======================================== + public ChatQuote (int sequence, Date date, String speaker, String sentence) { + this.sequence = sequence; + this.date = date; + this.speaker = speaker; + this.sentence = sentence; + } + + // ======================================== + public int compare (ChatQuote o1, ChatQuote o2) { + if (o1.sequence != o2.sequence) + return o1.sequence - o2.sequence; + int diff = o1.date.compareTo (o2.date); + if (diff != 0) + return diff; + diff = o1.speaker.compareTo (o2.speaker); + if (diff != 0) + return diff; + return o1.sentence.compareTo (o2.sentence); + } + + // ======================================== + public boolean equals (ChatQuote obj) { + return + sentence == obj.sentence && date == obj.date && + speaker.equals (obj.speaker) && sentence.equals (obj.sentence); + } + + // ======================================== +} diff --git a/src/java/network/chat/JChat.java b/src/java/network/chat/JChat.java new file mode 100644 index 0000000..9f282f7 --- /dev/null +++ b/src/java/network/chat/JChat.java @@ -0,0 +1,150 @@ +package network.chat; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Collections; +import java.util.Date; +import java.util.Hashtable; +import java.util.Vector; +import javax.swing.AbstractButton; +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import javax.swing.ScrollPaneConstants; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; +import javax.swing.border.EtchedBorder; +import javax.swing.plaf.basic.BasicBorders; +import javax.swing.text.BadLocationException; + +import misc.Config; +import misc.Log; +import misc.Util; +import network.ProtocolManager; +import network.login.Login; +import network.login.LoginManager; +import network.login.LoginObserver; + +@SuppressWarnings ("serial") public class JChat extends JPanel implements ChatObserver, LoginObserver, ActionListener { + + // ======================================== + private Chat chat; + + public JTextArea forum; + public Vector dialog; + public JScrollPane jScrollPane; + public JLabel pseudo; + public JTextField message; + + public Chat getChat () { return chat; } + + // ======================================== + public JChat (ProtocolManager protocolManager, LoginManager loginManager, ChatManager chatManager) { + super (new BorderLayout ()); + chat = chatManager.getChat (); + chat.addChatObserver (this); + loginManager.getLogin ().addLoginObserver (this); + + forum = new JTextArea (Integer.parseInt (Config.getString ("ChatRows", "18")), + Integer.parseInt (Config.getString ("ChatColumns", "64"))); + dialog = new Vector (); + jScrollPane = new JScrollPane (forum); + jScrollPane.setVerticalScrollBarPolicy (ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); + forum.setEditable (false); + message = new JTextField (); + message.setEnabled (true); + message.addActionListener (this); + + pseudo = new JLabel (chat.getPseudo ()+" : "); + pseudo.setEnabled (true); + add (jScrollPane, BorderLayout.CENTER); + JPanel buttonsPanel = new JPanel (null); + buttonsPanel.setLayout (new BoxLayout (buttonsPanel, BoxLayout.X_AXIS)); + Util.addIconButton (ChatManager.loginActionsNames, chatManager, buttonsPanel); + Util.addIconButton (ProtocolManager.actionsNames, protocolManager, buttonsPanel); + Util.addIconButton (ChatManager.saveActionsNames, chatManager, buttonsPanel); + Hashtable buttons = new Hashtable (); + Util.collectButtons (buttons, buttonsPanel); + + Border buttonBorder = + BorderFactory.createCompoundBorder (new EtchedBorder (), + BorderFactory.createCompoundBorder (BasicBorders.getMenuBarBorder (), + new EmptyBorder (3, 3, 3, 3))); + for (Component component : buttonsPanel.getComponents ()) { + try { + AbstractButton button = (AbstractButton) component; + button.setBorder (buttonBorder); + } catch (Exception e) { + } + } + chatManager.addSaveCommand (buttons.get (ChatManager.actionClear)); + JPanel footer = new JPanel (new BorderLayout ()); + footer.add (pseudo, BorderLayout.WEST); + footer.add (message, BorderLayout.CENTER); + footer.add (buttonsPanel, BorderLayout.EAST); + add (footer, BorderLayout.SOUTH); + } + + // ======================================== + public void actionPerformed (ActionEvent e) { + actionSay (); + } + + public void actionSay () { + chat.say (message.getText ()); + message.setText (""); + } + + // ======================================== + private ChatQuote control = new ChatQuote (0, new Date (), "", ""); + + public synchronized void talk (ChatQuote quote) { + dialog.add (quote); + Collections.sort (dialog, control); + String newLine = Chat.dateFormat.format (quote.date)+" "+quote.speaker+" > "+quote.sentence+"\n"; + int quoteIndex = dialog.indexOf (quote); + try { + int lineOffset = forum.getLineStartOffset (quoteIndex); + forum.insert (newLine, lineOffset); + forum.setCaretPosition (lineOffset); + } catch (BadLocationException e) { + try { + forum.append (newLine); + forum.setCaretPosition (forum.getLineEndOffset (forum.getLineCount ()-1)); + } catch (BadLocationException e2) { + Log.keepLastException ("JChat::talk", e2); + } + } + } + + public void renameSpeaker (String speaker) { + pseudo.setText (speaker+" : "); + } + + public void chatModifiedChange (boolean modified) { } + + public void clearChat () { + forum.setText (""); + dialog = new Vector (); + message.setText (""); + } + + // ======================================== + public void updateLogin (String login) {} + public void updateGroup (String groupName) {} + public void updateGroups (String[] groupsName) {} + public void infoGroup (Login.Group group) {} + public void loginState (String state) { + boolean connected = !Login.unknownToken.equals (state); + pseudo.setEnabled (connected); + message.setEnabled (connected); + } + + // ======================================== +} diff --git a/src/java/network/chat/JChatDialog.java b/src/java/network/chat/JChatDialog.java new file mode 100644 index 0000000..8f0e1d5 --- /dev/null +++ b/src/java/network/chat/JChatDialog.java @@ -0,0 +1,44 @@ +package network.chat; + +import java.awt.Frame; +import java.text.MessageFormat; + +import misc.Bundle; +import misc.TitledDialog; + +/** + fenêtre d'habillage pour inclusion. +*/ +@SuppressWarnings ("serial") public class JChatDialog extends TitledDialog implements ChatObserver { + + // ======================================== + public String pseudo; + + // ======================================== + public JChatDialog (Frame frame, JChat jChat) { + super (frame, "Chat"); + pseudo = jChat.getChat ().getPseudo (); + jChat.getChat ().addChatObserver (this); + add (jChat); + updateBundle (); + Bundle.addBundleObserver (this); + } + + // ======================================== + public void updateBundle () { + setTitle (getTitle ()); + } + + // ======================================== + public String getTitle () { + return MessageFormat.format (Bundle.getTitle (titleId), pseudo); + } + + // ======================================== + public void talk (ChatQuote quote) {} + public void renameSpeaker (String speaker) { pseudo = speaker; updateBundle (); } + public void chatModifiedChange (boolean modified) {} + public void clearChat () {} + + // ======================================== +} diff --git a/src/java/network/chat/JChatMenuBar.java b/src/java/network/chat/JChatMenuBar.java new file mode 100644 index 0000000..797b8f7 --- /dev/null +++ b/src/java/network/chat/JChatMenuBar.java @@ -0,0 +1,55 @@ +package network.chat; + +import java.util.Hashtable; +import javax.swing.AbstractButton; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JMenu; +import javax.swing.JMenuBar; + +import network.ProtocolManager; +import network.login.LoginManager; + +import misc.ApplicationManager; +import misc.Log; +import misc.ToolBarManager; +import misc.Util; + +/** + La barre de menu. +*/ +@SuppressWarnings ("serial") public class JChatMenuBar extends JMenuBar { + + // ======================================== + public JChatMenuBar (ApplicationManager controllerManager, + ProtocolManager protocolManager, LoginManager loginManager, ChatManager chatManager, + ApplicationManager helpManager, ToolBarManager toolBarManager) { + setLayout (new BoxLayout (this, BoxLayout.X_AXIS)); + JMenu fileMenu = Util.addJMenu (this, "File"); + JMenu networkMenu = Util.addJMenu (this, "Network"); + add (Box.createHorizontalGlue ()); + JMenu helpMenu = Util.addJMenu (this, "Help"); + + Util.addMenuItem (ChatManager.loginActionsNames, chatManager, fileMenu); + Util.addMenuItem (ChatManager.saveActionsNames, chatManager, fileMenu); + controllerManager.addMenuItem (fileMenu); + protocolManager.addMenuItem (networkMenu); + loginManager.addMenuItem (networkMenu); + helpManager.addMenuItem (helpMenu); + if (toolBarManager != null) + toolBarManager.addMenuItem (helpMenu); + + Hashtable buttons = new Hashtable (); + Util.collectButtons (buttons, fileMenu); + Util.collectButtons (buttons, networkMenu); + Util.collectButtons (buttons, helpMenu); + + controllerManager.addActiveButtons (buttons); + helpManager.addActiveButtons (buttons); + chatManager.addSaveCommand (buttons.get (ChatManager.actionClear)); + if (toolBarManager != null) + toolBarManager.addActiveButtons (buttons); + } + + // ======================================== +} diff --git a/src/java/network/chat/LaunchChat.java b/src/java/network/chat/LaunchChat.java new file mode 100644 index 0000000..a2bf418 --- /dev/null +++ b/src/java/network/chat/LaunchChat.java @@ -0,0 +1,32 @@ +package network.chat; + +import javax.swing.SwingUtilities; + +import misc.Bundle; +import misc.Config; + +/** + Lance le controleur. +*/ +public class LaunchChat { + + // ======================================== + static public void main (String[] args) { + Config.setPWD (LaunchChat.class); + Config.load ("Chat"); + Bundle.load ("Help"); + Bundle.load ("ToolBar"); + Bundle.load ("Controller"); + Bundle.load ("Protocol"); + Bundle.load ("Login"); + Bundle.load ("Chat"); + + SwingUtilities.invokeLater (new Runnable () { + public void run () { + new ChatController (); + } + }); + } + + // ======================================== +} diff --git a/src/java/network/login/JLogin.java b/src/java/network/login/JLogin.java new file mode 100644 index 0000000..2d86ca7 --- /dev/null +++ b/src/java/network/login/JLogin.java @@ -0,0 +1,66 @@ +package network.login; + +import java.awt.Component; +import java.awt.GridBagLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Hashtable; +import javax.swing.AbstractButton; +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.SwingConstants; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; +import javax.swing.border.EtchedBorder; +import javax.swing.plaf.basic.BasicBorders; + +import misc.Util; +import network.ProtocolManager; +import static misc.Util.GBC; +import static misc.Util.GBCNL; + +@SuppressWarnings ("serial") public class JLogin extends JPanel implements LoginObserver { + + // ======================================== + private Login login; + + public JLabel loggedLabel = new JLabel (); + public JLabel groupLabel = new JLabel (); + private JLabel stateLabel = new JLabel (); + + public Login getLogin () { return login; } + + // ======================================== + public JLogin (ProtocolManager protocolManager, LoginManager loginManager) { + super (new GridBagLayout ()); + login = loginManager.getLogin (); + login.addLoginObserver (this); + + Util.addLabelFields (this, "Logged", loggedLabel); + Util.addLabelFields (this, "Group", groupLabel); + Util.addLabelFields (this, "State", stateLabel); + } + + // ======================================== + public void updateLogin (String login) { + loggedLabel.setText (login); + } + + public void updateGroup (String group) { + groupLabel.setText (group); + } + + public void updateGroups (String[] groupsName) { + } + + public void infoGroup (Login.Group group) { + } + + public void loginState (String state) { + stateLabel.setText (state); + } + + // ======================================== +} diff --git a/src/java/network/login/JLoginMenuBar.java b/src/java/network/login/JLoginMenuBar.java new file mode 100644 index 0000000..7e7a525 --- /dev/null +++ b/src/java/network/login/JLoginMenuBar.java @@ -0,0 +1,53 @@ +package network.login; + +import java.util.Hashtable; +import javax.swing.AbstractButton; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JMenu; +import javax.swing.JMenuBar; + +import network.ProtocolManager; + +import misc.ApplicationManager; +import misc.Log; +import misc.ToolBarManager; +import misc.Util; + +/** + La barre de menu. +*/ +@SuppressWarnings ("serial") public class JLoginMenuBar extends JMenuBar { + + // ======================================== + public JLoginMenuBar (ApplicationManager controllerManager, + ProtocolManager protocolManager, LoginManager loginManager, + ApplicationManager helpManager, ToolBarManager toolBarManager) { + setLayout (new BoxLayout (this, BoxLayout.X_AXIS)); + JMenu fileMenu = Util.addJMenu (this, "File"); + JMenu networkMenu = Util.addJMenu (this, "Network"); + JMenu loginMenu = Util.addJMenu (this, "Connection"); + add (Box.createHorizontalGlue ()); + JMenu helpMenu = Util.addJMenu (this, "Help"); + + controllerManager.addMenuItem (fileMenu); + protocolManager.addMenuItem (networkMenu); + loginManager.addMenuItem (loginMenu); + helpManager.addMenuItem (helpMenu); + toolBarManager.addMenuItem (helpMenu); + + Hashtable buttons = new Hashtable (); + Util.collectButtons (buttons, fileMenu); + Util.collectButtons (buttons, networkMenu); + Util.collectButtons (buttons, loginMenu); + Util.collectButtons (buttons, helpMenu); + + controllerManager.addActiveButtons (buttons); + protocolManager.addActiveButtons (buttons); + loginManager.addActiveButtons (buttons); + helpManager.addActiveButtons (buttons); + toolBarManager.addActiveButtons (buttons); + } + + // ======================================== +} diff --git a/src/java/network/login/JLoginToolBar.java b/src/java/network/login/JLoginToolBar.java new file mode 100644 index 0000000..660fa86 --- /dev/null +++ b/src/java/network/login/JLoginToolBar.java @@ -0,0 +1,44 @@ +package network.login; + +import java.awt.Component; +import java.util.Hashtable; +import javax.swing.AbstractButton; +import javax.swing.JToolBar; + +import network.ProtocolManager; + +import misc.ApplicationManager; +import misc.ToolBarManager; +import misc.Util; + +public class JLoginToolBar { + static public String defaultCardinalPoint = "North"; + + public JLoginToolBar (ApplicationManager controllerManager, + ProtocolManager protocolManager, LoginManager loginManager, + ApplicationManager helpManager, ToolBarManager toolBarManager) { + JToolBar fileToolBar = toolBarManager.newJToolBar ("File", defaultCardinalPoint); + JToolBar loginToolBar = toolBarManager.newJToolBar ("Connection", defaultCardinalPoint); + JToolBar protocolToolBar = toolBarManager.newJToolBar ("Network", defaultCardinalPoint); + JToolBar helpToolBar = toolBarManager.newJToolBar ("Help", defaultCardinalPoint); + + controllerManager.addIconButtons (fileToolBar); + loginManager.addIconButtons (loginToolBar); + protocolManager.addIconButtons (protocolToolBar); + if (helpManager != null) + helpManager.addIconButtons (helpToolBar); + toolBarManager.addIconButtons (helpToolBar); + + Hashtable buttons = new Hashtable (); + Util.collectButtons (buttons, fileToolBar); + Util.collectButtons (buttons, loginToolBar); + Util.collectButtons (buttons, protocolToolBar); + Util.collectButtons (buttons, helpToolBar); + + controllerManager.addActiveButtons (buttons); + loginManager.addActiveButtons (buttons); + protocolManager.addActiveButtons (buttons); + helpManager.addActiveButtons (buttons); + toolBarManager.addActiveButtons (buttons); + } +} diff --git a/src/java/network/login/LaunchLogin.java b/src/java/network/login/LaunchLogin.java new file mode 100644 index 0000000..b157293 --- /dev/null +++ b/src/java/network/login/LaunchLogin.java @@ -0,0 +1,27 @@ +package network.login; + +import javax.swing.SwingUtilities; +import misc.Bundle; +import misc.Config; + +public class LaunchLogin { + + // ======================================== + static public void main (String[] args) { + Config.setPWD (LaunchLogin.class); + Config.load ("Login"); + Bundle.load ("Help"); + Bundle.load ("ToolBar"); + Bundle.load ("Controller"); + Bundle.load ("Protocol"); + Bundle.load ("Login"); + + SwingUtilities.invokeLater (new Runnable () { + public void run () { + new LoginController (); + } + }); + } + + // ======================================== +} diff --git a/src/java/network/login/Login.java b/src/java/network/login/Login.java new file mode 100644 index 0000000..6df813b --- /dev/null +++ b/src/java/network/login/Login.java @@ -0,0 +1,370 @@ +package network.login; + +import java.io.UnsupportedEncodingException; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.Random; +import java.util.Vector; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +import misc.Bundle; +import misc.Log; +import misc.Util; +import network.Protocol; +import network.Waiter; + +/** + * + * + * + * + * + * + * + * + + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ + +public class Login implements Waiter { + + static public final boolean trace = false; + + // ======================================== + static public final String applicationName = "login"; + static public final String userToken = "user"; + static public final String askToken = "ask"; + static public final String loginToken = "login"; + static public final String challengeToken = "challenge"; + static public final String tryIndexToken = "try"; + static public final String resultToken = "result"; + static public final String oldToken = "old"; + static public final String newToken = "new"; + static public final String unknownToken = "unknown"; + static public final String rejectToken = "reject"; + static public final String acceptToken = "accept"; + static public final String connectToken = "connect"; + static public final String disconnectToken = "disconnect"; + static public final String changepassToken = "changepass"; + static public final String groupToken = "group"; + static public final String groupsToken = "groups"; + static public final String setGroupToken = "setGroup"; + static public final String infoToken = "info"; + static public final String nameToken = "name"; + static public final String joinToken = "join"; + static public final String leftToken = "left"; + static public final String createdToken = "created"; + static public final String updatedToken = "updated"; + static public final String ipToken = "ip"; + static public final String updatedByToken = "updatedBy"; + static public final String adminToken = "admin"; + static public final String memberToken = "member"; + static public final String createGroupToken = "createGroup"; + static public final String removeGroupToken = "removeGroup"; + static public final String addGroupAdminToken = "addGroupAdmin"; + static public final String removeGroupAdminToken = "removeGroupAdmin"; + static public final String addGroupMemberToken = "addGroupMember"; + static public final String removeGroupMemberToken = "removeGroupMember"; + + // ======================================== + private Protocol protocol; + /** unknown, reject, accept, untrusted */ + private String state = unknownToken; + private String login = ""; + private String tryIndex = ""; + private String cifferPasswd = ""; + private String challenge; + private String group = ""; + private String[] groups = new String [0]; + + public synchronized void setProtocol (Protocol protocol) { + if (trace) + System.err.println ("setProtocol: protocol:"+protocol); + if (this.protocol != null) + this.protocol.removeWaiter (applicationName, this); + this.protocol = protocol; + if (protocol != null) + protocol.addWaiter (applicationName, this); + } + + public String getState () { return state; } + public String getTryIndex () { return tryIndex; } + public String getLogin () { return login; } + public String getGroup () { return group; } + public Protocol getProtocol () { return protocol; } + + // ======================================== + static public String getRandomLogin () { + return + Bundle.getString ("anonymous", null)+"_"+Integer.toHexString ((new Random ()).nextInt ()).toUpperCase ().substring (0, 4); + } + + // ======================================== + public Login (String login) { + this.login = login; + } + + public Login (String login, Protocol protocol) { + this (login); + setProtocol (protocol); + } + + // ======================================== + public synchronized void unlog () { + if (protocol == null) + return; + protocol.send (applicationName, askToken, disconnectToken); + } + + // ======================================== + public synchronized void log (String login, String cifferPasswd) { + if (trace) + System.err.println ("log: login:"+login+" cifferPasswd:"+cifferPasswd+" challenge:"+challenge); + if (login == null) + return; + if (protocol == null) + return; + this.login = login; + try { + if (challenge != null) { + this.cifferPasswd = cifferPasswd; + protocol.send (applicationName, askToken, connectToken, loginToken, login, resultToken, Util.sha1 (challenge+cifferPasswd)); + } else + protocol.send (applicationName, askToken, connectToken, loginToken, login); + } catch (Exception e) { + Log.keepLastException ("Login::log", e); + protocol.send (applicationName, askToken, connectToken, loginToken, login); + } + } + + // ======================================== + public synchronized void setGroup (String groupName) { + if (protocol == null) + return; + protocol.send (applicationName, askToken, setGroupToken, groupToken, groupName); + } + + public synchronized void unsetGroup () { + setGroup (""); + } + + public synchronized void askGroups () { + if (protocol == null) + return; + groups = new String [0]; + protocol.send (applicationName, askToken, groupsToken); + } + + public synchronized void askGroup (String groupName) { + if (protocol == null) + return; + protocol.send (applicationName, askToken, groupToken, groupToken, groupName); + } + + public synchronized void createGroup (String groupName) { + if (protocol == null) + return; + protocol.send (applicationName, askToken, createGroupToken, groupToken, groupName); + } + + public synchronized void removeGroup (String groupName) { + if (protocol == null) + return; + protocol.send (applicationName, askToken, removeGroupToken, groupToken, groupName); + } + + public synchronized void addGroupAdmin (String groupName, String admin) { + if (protocol == null) + return; + protocol.send (applicationName, askToken, addGroupAdminToken, groupToken, groupName, loginToken, admin); + } + + public synchronized void removeGroupAdmin (String groupName, String admin) { + if (protocol == null) + return; + protocol.send (applicationName, askToken, removeGroupAdminToken, groupToken, groupName, loginToken, admin); + } + + public synchronized void addGroupMember (String groupName, String member) { + if (protocol == null) + return; + protocol.send (applicationName, askToken, addGroupMemberToken, groupToken, groupName, loginToken, member); + } + + public synchronized void removeGroupMember (String groupName, String member) { + if (protocol == null) + return; + protocol.send (applicationName, askToken, removeGroupMemberToken, groupToken, groupName, loginToken, member); + } + + // ======================================== + public synchronized void receiveUser (String state, String result, String challenge, String tryIndex) { + if (unknownToken.equals (state)) { + this.state = state; + this.challenge = challenge; + this.tryIndex = tryIndex; + group = ""; + broadcastUpdateGroup (); + broadcastLoginState (); + } else if (rejectToken.equals (state)) { + this.state = state; + broadcastLoginState (); + } else if (acceptToken.equals (state)) { + this.tryIndex = ""; + String check = null; + try { + check = Util.sha1 (this.challenge+login+cifferPasswd); + } catch (Exception e) { + } + if (trace) + System.err.println ("receive: challenge:"+this.challenge+" login:"+login+" cifferPasswd:"+cifferPasswd+" check:"+check); + if (result.equals (check)) { + this.state = state; + broadcastLoginState (); + broadcastUpdateLogin (); + } else { + this.state = "Untrusted"; + broadcastLoginState (); + } + } else + System.err.println ("receive: unknown state:"+state+" challenge:"+challenge+" result:"+result); + } + + public class Group { + public String name; + public String created; + public String updated; + public String ip; + public String updatedBy; + public String[] admin = new String [0]; + public String[] member = new String [0]; + } + + // ======================================== + public synchronized void receive (Element argument) { + String state = argument.getAttribute (userToken); + if (!state.isEmpty ()) { + String tryIndex = argument.getAttribute (tryIndexToken); + String challenge = argument.getAttribute (challengeToken); + String result = argument.getAttribute (resultToken); + if (trace) + System.err.println ("receive: state:"+state+" challenge:"+challenge+" result:"+result); + receiveUser (state, result, challenge, tryIndex); + return; + } + String groupA = argument.getAttribute (groupToken); + if (!groupA.isEmpty ()) { + if (joinToken.equals (groupA)) + this.group = argument.getAttribute (nameToken); + else if (leftToken.equals (groupA)) + this.group = ""; + broadcastUpdateGroup (); + return; + } + String info = argument.getAttribute (infoToken); + if (!info.isEmpty ()) { + if (groupsToken.equals (info)) { + NodeList groupsName = argument.getElementsByTagName (groupToken); + Vector result = new Vector (); + for (int i = 0; i < groupsName.getLength (); i++) { + Element groupName = (Element) groupsName.item (i); + result.add (groupName.getAttribute (nameToken)); + } + groups = result.toArray (new String [0]); + Arrays.sort (groups); + broadcastUpdateGroups (); + } else if (groupToken.equals (info)) { + + Group group = new Group (); + NodeList groupsNL = argument.getElementsByTagName (groupToken); + // XXX vérification 1 seul élément + Element groupE = (Element) groupsNL.item (0); + group.name = groupE.getAttribute (nameToken); + group.created = groupE.getAttribute (createdToken); + group.updated = groupE.getAttribute (updatedToken); + group.ip = groupE.getAttribute (ipToken); + group.updatedBy = groupE.getAttribute (updatedByToken); + + NodeList AdminNL = argument.getElementsByTagName (adminToken); + Vector result = new Vector (); + for (int i = 0; i < AdminNL.getLength (); i++) { + Element AdminE = (Element) AdminNL.item (i); + result.add (AdminE.getAttribute (nameToken)); + } + group.admin = result.toArray (new String [0]); + Arrays.sort (group.admin); + NodeList memberNL = argument.getElementsByTagName (memberToken); + result = new Vector (); + for (int i = 0; i < memberNL.getLength (); i++) { + Element memberE = (Element) memberNL.item (i); + result.add (memberE.getAttribute (nameToken)); + } + group.member = result.toArray (new String [0]); + Arrays.sort (group.member); + + broadcastInfoGroup (group); + } + // XXX passwdUnchanged + // XXX passwdUpdated + } + } + + // ======================================== + Vector loginObservers = new Vector (); + public void addLoginObserver (LoginObserver loginObserver) { loginObservers.add (loginObserver); } + public void removeLoginObserver (LoginObserver loginObserver) { loginObservers.remove (loginObserver); } + + public void broadcastUpdateLogin () { + if (trace) + System.err.println ("broadcastUpdateLogin: login:"+login); + for (LoginObserver loginObserver : loginObservers) + loginObserver.updateLogin (login); + } + + public void broadcastUpdateGroup () { + if (trace) + System.err.println ("broadcastUpdateGroup: group:"+group); + for (LoginObserver loginObserver : loginObservers) + loginObserver.updateGroup (group); + } + + public void broadcastUpdateGroups () { + if (trace) + System.err.println ("broadcastUpdateGroups: groups size:"+groups.length); + for (LoginObserver loginObserver : loginObservers) + loginObserver.updateGroups (groups); + } + + public void broadcastInfoGroup (Group group) { + if (trace) + System.err.println ("broadcastInfoGroup: group:"); + for (LoginObserver loginObserver : loginObservers) + loginObserver.infoGroup (group); + } + + public void broadcastLoginState () { + if (trace) + System.err.println ("broadcastLoginState: state:"+state); + for (LoginObserver loginObserver : loginObservers) + loginObserver.loginState (state); + } + + // ======================================== +} diff --git a/src/java/network/login/LoginController.java b/src/java/network/login/LoginController.java new file mode 100644 index 0000000..06ec3d6 --- /dev/null +++ b/src/java/network/login/LoginController.java @@ -0,0 +1,87 @@ +package network.login; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Image; +import java.text.MessageFormat; +import javax.swing.JMenuBar; +import javax.swing.JPanel; + +import network.ProtocolManager; + +import misc.Controller; +import misc.Bundle; +import misc.Config; +import misc.HelpManager; +import misc.MultiToolBarBorderLayout; +import misc.ToolBarManager; +import misc.Util; + +public class LoginController extends Controller implements LoginObserver { + + // ======================================== + public LoginController () { + super (new Login (Config.getString ("pseudo", Login.getRandomLogin ()))); + } + + // ======================================== + Login login; + + ProtocolManager protocolManager; + LoginManager loginManager; + HelpManager helpManager; + ToolBarManager toolBarManager; + + JLogin jLogin; + JLoginMenuBar jLoginMenuBar; + JLoginToolBar jLoginToolBar; + + // ======================================== + public String getTitle () { return MessageFormat.format (Bundle.getTitle ("Login"), login.getLogin ()); } + public Image getIcon () { return Util.loadImage (Config.getString ("LoginIcon", "data/images/login/login.png")); } + + // ======================================== + protected boolean tryClosingWindows () { + Config.save ("Login"); + return true; + } + + // ======================================== + protected void createModel (Login login) { + this.login = login; + login.addLoginObserver (this); + } + + // ======================================== + protected Component createGUI () { + JPanel contentPane = new JPanel (new MultiToolBarBorderLayout ()); + toolBarManager = new ToolBarManager (getIcon (), contentPane); + + protocolManager = new ProtocolManager (this); + protocolManager.addWaiter (login); + protocolManager.start (); + + loginManager = new LoginManager (this, login); + helpManager = new HelpManager (this, "Login"); + jLogin = new JLogin (protocolManager, loginManager); + + jLoginToolBar = new JLoginToolBar (this, protocolManager, loginManager, helpManager, toolBarManager); + contentPane.add (jLogin, BorderLayout.CENTER); + return contentPane; + } + + // ======================================== + protected JMenuBar createMenuBar () { + jLoginMenuBar = new JLoginMenuBar (this, protocolManager, loginManager, helpManager, toolBarManager); + return jLoginMenuBar; + } + + // ======================================== + public void updateLogin (String login) {} + public void updateGroup (String group) {} + public void updateGroups (String[] groupsName) {} + public void infoGroup (Login.Group group) {} + public void loginState (String state) {} + + // ======================================== +} diff --git a/src/java/network/login/LoginManager.java b/src/java/network/login/LoginManager.java new file mode 100644 index 0000000..fafd2d0 --- /dev/null +++ b/src/java/network/login/LoginManager.java @@ -0,0 +1,443 @@ +package network.login; + +import java.awt.Container; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Hashtable; +import java.util.List; +import java.util.Vector; + +import javax.swing.AbstractButton; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JMenu; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPasswordField; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.ListSelectionModel; +import javax.swing.SwingConstants; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; + +import misc.ApplicationManager; +import misc.Bundle; +import misc.Config; +import misc.Log; +import misc.OwnFrame; +import misc.Util; +import static misc.Util.GBC; +import static misc.Util.GBCNL; + +/** + XXX loginTitle a mettre à jour "Login"+Bundle.titlePostfix + + comportement déclanché par des actionneurs graphiques (menu ou bouton). +*/ +@SuppressWarnings ("serial") public class LoginManager implements ApplicationManager, ActionListener, LoginObserver { + + // ======================================== + static public final String actionLog = "Log"; + static public final String actionUnlog = "Unlog"; + static public final String actionJoinGroup = "JoinGroup"; + static public final String actionLeftGroup = "LeftGroup"; + static public final String actionEditLogin = "EditLogin"; + static public final String actionEditGroup = "EditGroup"; + static public final String actionCreateGroup = "CreateGroup"; + static public final String actionRemoveGroup = "RemoveGroup"; + static public final String actionAddAdmin = "AddAdmin"; + static public final String actionRemoveAdmin = "RemoveAdmin"; + static public final String actionAddMember = "AddMember"; + static public final String actionRemoveMember = "RemoveMember"; + + static public final List loginActionsNames = + Arrays.asList (actionLog, actionUnlog, actionJoinGroup, actionLeftGroup, + actionEditLogin, actionEditGroup); + static public final List dialogActionsNames = + Arrays.asList (actionCreateGroup, actionRemoveGroup, + actionAddAdmin, actionRemoveAdmin, actionAddMember, actionRemoveMember); + @SuppressWarnings ("unchecked") + static public final List actionsNames = + Util.merge (loginActionsNames, dialogActionsNames); + @SuppressWarnings ("unchecked") + static public final Hashtable actionsMethod = + Util.collectMethod (LoginManager.class, actionsNames); + + public void actionPerformed (ActionEvent e) { + Util.actionPerformed (actionsMethod, e, this); + } + + // ======================================== + private OwnFrame controller; + private Login login; + + private JPanel loginPanel; + private JComboBox pseudoCB; + private JPasswordField pseudoPasswordField; + private JLabel tryLabel; + + private JPanel groupPanel; + private JList groupsList; + + private JPanel editLoginPanel; + + private JLabel pseudoLabel; + private JLabel createdAtLoginLabel; + private JLabel updatedAtLoginLabel; + private JLabel updatedByIPLoginLabel; + private JLabel updatedByLoginLabel; + private JTextField emailTF; + private JPasswordField oldPasswordField; + private JPasswordField newPasswordField; + private JPasswordField confirmPasswordField; + + private String askedGroupName; + + private JPanel editGroupPanel; + private JComboBox groupNameCB; + private JList groupsNameList; + private JButton createGroupButton; + private JButton removeGroupButton; + + private JLabel groupNameLabel; + private JLabel createdLabel; + private JLabel updatedLabel; + private JLabel ipLabel; + private JLabel updatedByLabel; + + private JComboBox adminCB; + private JList adminList; + private JButton addAdminButton; + private JButton removeAdminButton; + + private JComboBox memberCB; + private JList memberList; + private JButton addMemberButton; + private JButton removeMemberButton; + + public Login getLogin () { return login; } + + public void addMenuItem (JMenu... jMenu) { + int idx = 0; + Util.addMenuItem (loginActionsNames, this, jMenu[idx++]); + } + public void addIconButtons (Container... containers) { + int idx = 0; + Util.addIconButton (loginActionsNames, this, containers[idx++]); + } + + // ======================================== + public LoginManager (OwnFrame controller, Login login) { + this.controller = controller; + this.login = login; + + for (String actionName : Arrays.asList (actionLog, actionUnlog, + actionEditLogin, actionEditGroup, actionJoinGroup, actionLeftGroup)) + commands.put (actionName, new Vector ()); + + + pseudoCB = new JComboBox (); + Config.loadJComboBox ("pseudo", pseudoCB, Login.getRandomLogin ()); + pseudoCB.setEditable (true); + pseudoPasswordField = new JPasswordField (); + tryLabel = new JLabel (); + + loginPanel = Util.getGridBagPanel (); + + Util.addLabelFields (loginPanel, "Login", pseudoCB); + Util.addLabelFields (loginPanel, "Password", pseudoPasswordField); + Util.addLabelFields (loginPanel, "Try", tryLabel); + + groupsList = new JList (); + groupsList.setSelectionMode (ListSelectionModel.SINGLE_INTERVAL_SELECTION); + groupsList.setLayoutOrientation (JList.VERTICAL_WRAP); + groupsList.setVisibleRowCount (-1); + JScrollPane listScroller = new JScrollPane (groupsList); + listScroller.setPreferredSize (new Dimension (250, 80)); + + groupPanel = Util.getGridBagPanel (); + + Util.addLabel ("JoinGroup", SwingConstants.RIGHT, groupPanel, GBCNL); + Util.addComponent (listScroller, groupPanel, GBCNL); + + groupsNameList = new JList (); + groupsNameList.setSelectionMode (ListSelectionModel.SINGLE_INTERVAL_SELECTION); + groupsNameList.setLayoutOrientation (JList.VERTICAL_WRAP); + groupsNameList.setVisibleRowCount (-1); + JScrollPane groupsNameListScroller = new JScrollPane (groupsNameList); + groupsNameListScroller.setPreferredSize (new Dimension (200, 50)); + + groupsNameList.addListSelectionListener (new ListSelectionListener () { + public void valueChanged (ListSelectionEvent e) { + actionSelectGroup (); + } + }); + + adminList = new JList (); + adminList.setSelectionMode (ListSelectionModel.SINGLE_INTERVAL_SELECTION); + adminList.setLayoutOrientation (JList.VERTICAL_WRAP); + adminList.setVisibleRowCount (-1); + JScrollPane adminListScroller = new JScrollPane (adminList); + adminListScroller.setPreferredSize (new Dimension (200, 50)); + + memberList = new JList (); + memberList.setSelectionMode (ListSelectionModel.SINGLE_INTERVAL_SELECTION); + memberList.setLayoutOrientation (JList.VERTICAL_WRAP); + memberList.setVisibleRowCount (-1); + JScrollPane memberListScroller = new JScrollPane (memberList); + memberListScroller.setPreferredSize (new Dimension (200, 50)); + + editLoginPanel = Util.getGridBagPanel (); + + pseudoLabel = new JLabel (); + createdAtLoginLabel = new JLabel (); + updatedAtLoginLabel = new JLabel (); + updatedByLoginLabel = new JLabel (); + updatedByIPLoginLabel = new JLabel (); + emailTF = new JTextField (20); + oldPasswordField = new JPasswordField (10); + newPasswordField = new JPasswordField (10); + confirmPasswordField = new JPasswordField (10); + + Util.addLabelFields (editLoginPanel, "Login", pseudoLabel); + Util.addLabelFields (editLoginPanel, "CreatedAt", createdAtLoginLabel); + Util.addLabelFields (editLoginPanel, "UpdatedAt", updatedAtLoginLabel); + Util.addLabelFields (editLoginPanel, "UpdatedBy", updatedByLoginLabel); + Util.addLabelFields (editLoginPanel, "UpdatedByIP", updatedByIPLoginLabel); + Util.addLabelFields (editLoginPanel, "Email", emailTF); + Util.addLabelFields (editLoginPanel, "OldPasswd", oldPasswordField); + Util.addLabelFields (editLoginPanel, "NewPasswd", newPasswordField); + Util.addLabelFields (editLoginPanel, "ConfirmPasswd", confirmPasswordField); + + groupNameCB = new JComboBox (); + adminCB = new JComboBox (); + memberCB = new JComboBox (); + Config.loadJComboBox ("groupName", groupNameCB, ""); + Config.loadJComboBox ("groupAdmin", adminCB, ""); + Config.loadJComboBox ("groupMember", memberCB, ""); + groupNameCB.setEditable (true); + adminCB.setEditable (true); + memberCB.setEditable (true); + + editGroupPanel = Util.getGridBagPanel (); + + Util.addComponent (groupNameCB, editGroupPanel, GBC); + createGroupButton = Util.addButton (actionCreateGroup, this, editGroupPanel, GBCNL); + Util.addComponent (groupsNameListScroller, editGroupPanel, GBC); + removeGroupButton = Util.addButton (actionRemoveGroup, this, editGroupPanel, GBCNL); + + groupNameLabel = new JLabel (); + createdLabel = new JLabel (); + updatedLabel = new JLabel (); + ipLabel = new JLabel (); + updatedByLabel = new JLabel (); + + Util.addLabelFields (editGroupPanel, "GroupName", groupNameLabel); + Util.addLabelFields (editGroupPanel, "CreatedAt", createdLabel); + Util.addLabelFields (editGroupPanel, "UpdatedAt", updatedLabel); + Util.addLabelFields (editGroupPanel, "UpdatedByIP", ipLabel); + Util.addLabelFields (editGroupPanel, "UpdatedBy", updatedByLabel); + + + Util.addComponent (adminCB, editGroupPanel, GBC); + addAdminButton = Util.addButton (actionAddAdmin, this, editGroupPanel, GBCNL); + Util.addComponent (adminListScroller, editGroupPanel, GBC); + removeAdminButton = Util.addButton (actionRemoveAdmin, this, editGroupPanel, GBCNL); + + Util.addComponent (memberCB, editGroupPanel, GBC); + addMemberButton = Util.addButton (actionAddMember, this, editGroupPanel, GBCNL); + Util.addComponent (memberListScroller, editGroupPanel, GBC); + removeMemberButton = Util.addButton (actionRemoveMember, this, editGroupPanel, GBCNL); + + login.addLoginObserver (this); + } + + // ======================================== + public void actionLog () { + tryLabel.setText (login.getTryIndex ()); + if (JOptionPane.showConfirmDialog (controller.getJFrame (), + loginPanel, + Bundle.getTitle ("Login"), + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE) != JOptionPane.YES_OPTION) + return; + Config.saveJComboBox ("pseudo", pseudoCB); + String passwd = null; + try { + if (pseudoPasswordField.getPassword ().length > 0) + passwd = Util.sha1 (new String (pseudoPasswordField.getPassword ())); + } catch (Exception e) { + Log.keepLastException ("LoginManager::actionLog", e); + } + login.log ((String) pseudoCB.getSelectedItem (), passwd); + } + + // ======================================== + public void actionUnlog () { + login.unlog (); + } + + // ======================================== + public void actionJoinGroup () { + login.askGroups (); + if (JOptionPane. + showConfirmDialog (controller.getJFrame (), + groupPanel, + Bundle.getTitle ("Group"), + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE) != JOptionPane.YES_OPTION) + return; + login.setGroup (groupsList.getSelectedValue ()); + } + + // ======================================== + public void actionLeftGroup () { + login.unsetGroup (); + } + + // ======================================== + public void actionEditLogin () { + if (JOptionPane.showConfirmDialog (controller.getJFrame (), + editLoginPanel, + Bundle.getTitle ("ManageLogin"), + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE) != JOptionPane.YES_OPTION) + return; + // XXX ecrire la fonction : edit login, password et email + JOptionPane. + showMessageDialog (controller.getJFrame (), + "Cette fonction n'est encore pas developpee", + "Developpement en cours", + JOptionPane.QUESTION_MESSAGE); + } + + // ======================================== + public void actionEditGroup () { + login.askGroups (); + JOptionPane. + showMessageDialog (controller.getJFrame (), + editGroupPanel, + Bundle.getTitle ("ManageGroup"), + JOptionPane.QUESTION_MESSAGE); + groupsNameList.setListData (new String [0]); + adminList.setListData (new String [0]); + memberList.setListData (new String [0]); + Config.saveJComboBox ("groupName", groupNameCB); + Config.saveJComboBox ("groupAdmin", adminCB); + Config.saveJComboBox ("groupMember", memberCB); + } + + // ======================================== + public void actionSelectGroup () { + infoGroup (null); + askedGroupName = groupsNameList.getSelectedValue (); + if (!askedGroupName.isEmpty ()) + login.askGroup (askedGroupName); + } + + // ======================================== + public void actionCreateGroup () { + login.createGroup ((String) groupNameCB.getSelectedItem ()); + } + public void actionRemoveGroup () { + login.removeGroup (groupsNameList.getSelectedValue ()); + } + public void actionAddAdmin () { + login.addGroupAdmin (groupNameLabel.getText (), adminCB.getItemAt (adminCB.getSelectedIndex ())); + } + public void actionRemoveAdmin () { + login.removeGroupAdmin (groupNameLabel.getText (), adminList.getSelectedValue ()); + } + public void actionAddMember () { + login.addGroupMember (groupNameLabel.getText (), memberCB.getItemAt (memberCB.getSelectedIndex ())); + } + public void actionRemoveMember () { + login.removeGroupMember (groupNameLabel.getText (), memberList.getSelectedValue ()); + } + + // ======================================== + public void updateLogin (String login) {} + + public void updateGroup (String group) { + updateCommands (); + groupsList.setSelectedValue (group, true); + } + + public void updateGroups (String[] groupsName) { + if (groupsName == null) + return; + groupsNameList.setListData (groupsName); + if (askedGroupName != null && Arrays.binarySearch (groupsName, askedGroupName) < 0) { + askedGroupName = null; + infoGroup (null); + } + groupsList.setListData (groupsName); + groupsList.setSelectedValue (login.getGroup (), true); + } + + public void infoGroup (Login.Group group) { + if (askedGroupName == null || group == null) { + groupNameLabel.setText (""); + createdLabel.setText (""); + updatedLabel.setText (""); + ipLabel.setText (""); + updatedByLabel.setText (""); + adminList.setListData (new String [0]); + memberList.setListData (new String [0]); + } else if (askedGroupName.equals (group.name)) { + groupNameLabel.setText (group.name); + createdLabel.setText (group.created); + updatedLabel.setText (group.updated); + ipLabel.setText (group.ip); + updatedByLabel.setText (group.updatedBy); + adminList.setListData (group.admin); + memberList.setListData (group.member); + } + } + + public void loginState (String state) { + updateCommands (); + if (Login.unknownToken.equals (state)) { + actionLog (); + } + } + + // ======================================== + private Hashtable> commands = new Hashtable> (); + public void addActiveButtons (Hashtable buttons) { + for (String actionName : loginActionsNames) + commands.get (actionName).add (buttons.get (actionName)); + updateCommands (); + } + + // public void removeCommand (String actionName, AbstractButton command) { + // commands.get (actionName).remove (command); + // } + + // ======================================== + public synchronized void updateCommands () { + boolean isUnknowned = Login.unknownToken.equals (login.getState ()); + for (AbstractButton logCommand : commands.get (actionLog)) + logCommand.setEnabled (isUnknowned); + for (AbstractButton unlogCommand : commands.get (actionUnlog)) + unlogCommand.setEnabled (!isUnknowned); + for (AbstractButton editLoginCommand : commands.get (actionEditLogin)) + editLoginCommand.setEnabled (!isUnknowned); + for (AbstractButton editGroupCommand : commands.get (actionEditGroup)) + editGroupCommand.setEnabled (!isUnknowned); + boolean isGroup = !login.getGroup ().isEmpty (); + for (AbstractButton joinGroupCommand : commands.get (actionJoinGroup)) + joinGroupCommand.setEnabled (!isUnknowned && !isGroup); + for (AbstractButton leftGroupCommand : commands.get (actionLeftGroup)) + leftGroupCommand.setEnabled (!isUnknowned && isGroup); + } + + // ======================================== +} diff --git a/src/java/network/login/LoginObserver.java b/src/java/network/login/LoginObserver.java new file mode 100644 index 0000000..7f57da4 --- /dev/null +++ b/src/java/network/login/LoginObserver.java @@ -0,0 +1,16 @@ +package network.login; + +/** + Modificaion de modèle qui peuvent être observé. +*/ +public interface LoginObserver { + + // ======================================== + public void updateLogin (String login); + public void updateGroup (String groupName); + public void updateGroups (String[] groupsName); + public void infoGroup (Login.Group group); + public void loginState (String state); + + // ======================================== +} diff --git a/src/java/overview.html b/src/java/overview.html new file mode 100644 index 0000000..e36b8f1 --- /dev/null +++ b/src/java/overview.html @@ -0,0 +1,24 @@ + +

+ En résumé, les fichiers sources qui vous sont présentés, + sont les versions succéssives de la même application, + pour vous permettre d'aprendre Java pas après pas. +

+

+ La première version à consulter est bien évidement + la version 1. + Les versions suivantes suivent l'ordre numérique. +

+

+ Ce texte provient de la documentation mise dans le "pseudo" fichier HTML de nom + "overview.html" à la racines des fichiers sources. + Voici un ordre de lecture des paquetages. +

    +
  • misc
  • +
  • tool
  • +
  • network
  • +
+ + @author F. Merciol +

+ diff --git a/src/java/tool/LaunchBundleManager.java b/src/java/tool/LaunchBundleManager.java new file mode 100644 index 0000000..5c8cb17 --- /dev/null +++ b/src/java/tool/LaunchBundleManager.java @@ -0,0 +1,29 @@ +package tool; + +import javax.swing.SwingUtilities; + +import misc.Bundle; +import misc.Config; + +import tool.controller.BundleManagerController; + +public class LaunchBundleManager { + + // ======================================== + static public void main (String [] arg) { + Config.setPWD (LaunchBundleManager.class); + Config.load ("BundleManager"); + Bundle.load ("Help"); + Bundle.load ("ToolBar"); + Bundle.load ("Controller"); + Bundle.load ("BundleManager"); + + SwingUtilities.invokeLater (new Runnable() { + public void run() { + new BundleManagerController (); + } + }); + } + + // ======================================== +} diff --git a/src/java/tool/controller/BundleManagerController.java b/src/java/tool/controller/BundleManagerController.java new file mode 100644 index 0000000..86d0abc --- /dev/null +++ b/src/java/tool/controller/BundleManagerController.java @@ -0,0 +1,110 @@ +package tool.controller; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Image; +import java.text.MessageFormat; +import javax.swing.JMenuBar; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JSplitPane; + +import misc.Bundle; +import misc.Config; +import misc.Controller; +import misc.HelpManager; +import misc.MultiToolBarBorderLayout; +import misc.ToolBarManager; +import misc.Util; +import tool.model.BundleManagerModel; +import tool.model.BundleManagerObserver; +import tool.view.BundleManagerManager; +import tool.view.BundleManagerMenu; +import tool.view.BundleManagerToolBar; +import tool.view.BundleManagerView; +import tool.view.SourceIteratorView; + +public class BundleManagerController extends Controller implements BundleManagerObserver { + + // ======================================== + public BundleManagerController () { + super (new BundleManagerModel ()); + } + + // ======================================== + BundleManagerModel bundleManagerModel; + + BundleManagerManager bundleManagerManager; + HelpManager helpManager; + + BundleManagerView bundleManagerView; + BundleManagerMenu bundleManagerMenu; + + BundleManagerToolBar bundleManagerToolBar; + ToolBarManager toolBarManager; + + // ======================================== + public String getTitle () { + String bundleName = bundleManagerManager.getBundleName (); + return MessageFormat.format (Bundle.getTitle ("BundleEditor"), + (bundleName == null) ? Bundle.getAction ("Empty") : bundleName); + } + public Image getIcon () { return Util.loadImage (Config.getString ("BundleIcon", "data/images/bundle/bundle.png")); } + + // ======================================== + protected void createModel (BundleManagerModel bundleManagerModel) { + this.bundleManagerModel = bundleManagerModel; + bundleManagerModel.addBundleObserver (this); + } + + // ======================================== + protected Component createGUI () { + JPanel contentPane = new JPanel (new MultiToolBarBorderLayout ()); + toolBarManager = new ToolBarManager (getIcon (), contentPane); + + bundleManagerView = new BundleManagerView (bundleManagerModel); + bundleManagerManager = new BundleManagerManager (this, bundleManagerModel, bundleManagerView); + + JSplitPane splitPane = new JSplitPane (JSplitPane.VERTICAL_SPLIT, + bundleManagerView, new SourceIteratorView ()); + splitPane.setOneTouchExpandable (true); + + helpManager = new HelpManager (this, "BundleManager"); + + bundleManagerToolBar = new BundleManagerToolBar (this, bundleManagerManager, helpManager, toolBarManager); + contentPane.add (splitPane, BorderLayout.CENTER); + return contentPane; + } + + // ======================================== + protected JMenuBar createMenuBar () { + return bundleManagerMenu = new BundleManagerMenu (this, bundleManagerManager, helpManager, toolBarManager); + } + + // ======================================== + protected boolean tryClosingWindows () { + toolBarManager.saveLocation (); + Config.save ("BundleManager"); + if (bundleManagerModel.getModified ()) { + switch (JOptionPane.showConfirmDialog (jFrame, Bundle.getLabel ("SaveBundle"), Bundle.getTitle ("BundleNotSaved"), + JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE)) { + case JOptionPane.YES_OPTION: + bundleManagerManager.actionSave (); + return true; + case JOptionPane.NO_OPTION: + return true; + case JOptionPane.CANCEL_OPTION: + case JOptionPane.CLOSED_OPTION: + return false; + } + } + return true; + } + + // ======================================== + public void dataModifiedChange () { jFrame.setTitle (getTitle ()); } + public void localesUpdated () {} + public void dataUpdated () {} + + // ======================================== +} diff --git a/src/java/tool/model/BundleManagerModel.java b/src/java/tool/model/BundleManagerModel.java new file mode 100644 index 0000000..09f37e7 --- /dev/null +++ b/src/java/tool/model/BundleManagerModel.java @@ -0,0 +1,248 @@ +package tool.model; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FilenameFilter; +import java.io.IOException; +import java.lang.reflect.Method; +import java.text.MessageFormat; +import java.util.Hashtable; +import java.util.Properties; +import java.util.TreeSet; +import java.util.Vector; + +import misc.Bundle; +import misc.Log; + +public class BundleManagerModel { + + // ======================================== + static public final String extention = ".properties"; + + private Hashtable bundles; + private TreeSet keys; + private TreeSet locales; + + // ======================================== + public final TreeSet getKeys () { + return keys; + } + + // ======================================== + public String getKeyByIndex (int idx) { + return (String) keys.toArray () [idx]; + } + + // ======================================== + public final TreeSet getLocales () { + return locales; + } + + // ======================================== + public String getLocaleByIndex (int idx) { + return (String) locales.toArray () [idx]; + } + + // ======================================== + private boolean modified = false; + public boolean getModified () { + return modified; + } + + // ======================================== + public BundleManagerModel () { + empty (); + } + + // ======================================== + public void empty () { + bundles = new Hashtable (); + keys = new TreeSet (); + locales = new TreeSet (); + modified = false; + broadcastMethode ("localesUpdated"); + broadcastMethode ("dataUpdated"); + broadcastMethode ("dataModifiedChange"); + } + + // ======================================== + public void open (File bundleDir, final String bundleName, boolean merge) { + File [] bundleFile = bundleDir.listFiles (new FilenameFilter () { + public boolean accept (File dir, String name) { + return name.startsWith (bundleName) && name.endsWith (extention); + } + }); + if (!merge) + empty (); + int bundleNameLength = bundleName.length (); + int extentionLength = extention.length (); + for (File file : bundleFile) { + String fileName = file.getName (); + locales.add (fileName.substring (bundleNameLength, fileName.length ()-extentionLength)); + } + for (String locale : locales) { + try { + Properties properties = new Properties (); + FileInputStream fileInputStream = new FileInputStream (new File (bundleDir, bundleName+locale+extention)); + properties.load (fileInputStream); + fileInputStream.close (); + Properties old = bundles.get (locale); + if (old != null && merge) { + for (Object key : old.keySet ()) + properties.put (key, old.get (key)); + } + bundles.put (locale, properties); + for (Object key : properties.keySet ()) + keys.add ((String) key); + } catch (IOException e) { + System.err.println (MessageFormat.format (Bundle.getException ("CantLoad"), locale)); + } + } + modified = merge; + broadcastMethode ("localesUpdated"); + broadcastMethode ("dataUpdated"); + broadcastMethode ("dataModifiedChange"); + } + + // ======================================== + public void saveAs (File bundleDir, final String bundleName) { + // XXX en cas de réécriture dans le meme repertoire supprimer les "locale" supprimes + bundleDir.mkdirs (); + boolean trouble = false; + for (String locale : locales) { + try { + FileOutputStream fileOutputStream = new FileOutputStream (new File (bundleDir, bundleName+locale+extention)); + bundles.get (locale).store (fileOutputStream, Bundle.getString ("HeaderBundleFile", null)); + fileOutputStream.close (); + } catch (IOException e) { + System.err.println (MessageFormat.format (Bundle.getException ("CantSave"), locale)); + trouble = true; + } + } + modified = trouble; + broadcastMethode ("dataModifiedChange"); + } + + // ======================================== + public void addKey (String newKey) { + if (keys.contains (newKey)) + throw new IllegalArgumentException (MessageFormat.format (Bundle.getException ("DuplicatedKey"), newKey)); + keys.add (newKey); + broadcastMethode ("dataUpdated"); + } + + // ======================================== + public void renameKey (String key, String newKey) { + if (keys.contains (newKey)) + throw new IllegalArgumentException (MessageFormat.format (Bundle.getException ("DuplicatedKey"), newKey)); + for (String locale : locales) { + try { + Properties properties = bundles.get (locale); + String val = properties.getProperty (key); + properties.remove (key); + properties.setProperty (newKey, val); + modified = true; + } catch (Exception e) { + // key not defined on this locale + } + } + keys.remove (key); + keys.add (newKey); + broadcastMethode ("dataUpdated"); + broadcastMethode ("dataModifiedChange"); + } + + // ======================================== + public void removeKey (String key) { + if (!keys.contains (key)) + return; + for (String locale : locales) { + try { + Properties properties = bundles.get (locale); + properties.remove (key); + modified = true; + } catch (Exception e) { + // key not defined on this locale + } + } + keys.remove (key); + broadcastMethode ("dataUpdated"); + broadcastMethode ("dataModifiedChange"); + } + + // ======================================== + public void add (String locale) { + if (locales.contains (locale)) + return; + locales.add (locale); + bundles.put (locale, new Properties ()); + modified = true; + broadcastMethode ("localesUpdated"); + broadcastMethode ("dataModifiedChange"); + } + + // ======================================== + public void renameLocale (String locale, String newLocale) { + if (!locales.contains (locale)) + return; + if (locales.contains (newLocale)) + throw new IllegalArgumentException (MessageFormat.format (Bundle.getException ("DuplicatedLocale"), + newLocale)); + locales.remove (locale); + bundles.put (newLocale, bundles.remove (locale)); + locales.add (newLocale); + modified = true; + broadcastMethode ("localesUpdated"); + broadcastMethode ("dataModifiedChange"); + } + + // ======================================== + public void removeLocale (String locale) { + if (!locales.contains (locale)) + return; + locales.remove (locale); + bundles.remove (locale); + modified = true; + broadcastMethode ("localesUpdated"); + broadcastMethode ("dataModifiedChange"); + } + + // ======================================== + public String get (String locale, String key) { + try { + return bundles.get (locale).getProperty (key); + } catch (Exception e) { + return null; + } + } + + // ======================================== + public void set (String locale, String key, String val) { + String oldVal = get (locale, key); + if (val == oldVal || val.equals (oldVal)) + return; + bundles.get (locale).setProperty (key, val); + modified = true; + broadcastMethode ("dataUpdated"); + broadcastMethode ("dataModifiedChange"); + } + + // ======================================== + Vector bundleObservers = new Vector (); + public void addBundleObserver (BundleManagerObserver bundleObserver) { bundleObservers.add (bundleObserver); } + public void removeBundleObserver (BundleManagerObserver bundleObserver) { bundleObservers.remove (bundleObserver); } + + // ======================================== + public void broadcastMethode (String methodeName) { + try { + Method methode = BundleManagerObserver.class.getMethod (methodeName); + for (BundleManagerObserver bundleObserver : bundleObservers) + methode.invoke (bundleObserver); + } catch (Exception e) { + Log.keepLastException ("BundleManagerModel::broadcastMethode", e); + } + } + + // ======================================== +} diff --git a/src/java/tool/model/BundleManagerObserver.java b/src/java/tool/model/BundleManagerObserver.java new file mode 100644 index 0000000..32d03a9 --- /dev/null +++ b/src/java/tool/model/BundleManagerObserver.java @@ -0,0 +1,11 @@ +package tool.model; + +public interface BundleManagerObserver { + + // ======================================== + public void dataModifiedChange (); + public void localesUpdated (); + public void dataUpdated (); + + // ======================================== +} diff --git a/src/java/tool/model/SourceIteratorModel.java b/src/java/tool/model/SourceIteratorModel.java new file mode 100644 index 0000000..81e6196 --- /dev/null +++ b/src/java/tool/model/SourceIteratorModel.java @@ -0,0 +1,104 @@ +package tool.model; + +import java.io.*; +import java.util.*; + +public class SourceIteratorModel { + + // ======================================== + Vector files = new Vector (); + Enumeration javaEnumeration; + + // ======================================== + public SourceIteratorModel (File dir) { + add (dir); + javaEnumeration = files.elements (); + } + + // ======================================== + private void add (File dir) { + for (File file : + dir.listFiles (new FilenameFilter () { + public boolean accept (File dir, String name) { + return name.endsWith (".java"); + } + })) + files.add (file); + for (File subdir : + dir.listFiles (new FileFilter () { + public boolean accept (File child) { + return child.isDirectory (); + } + })) + add (subdir); + } + + // ======================================== + public boolean hasMoreJava () { + return javaEnumeration.hasMoreElements (); + } + + // ======================================== + public File nextJava () { + return javaEnumeration.nextElement (); + } + + // ======================================== + String nextString = null; + int currentLine = 0; + File currentFile; + BufferedReader currentBuffered = null; + + // ======================================== + public int getCurrentLine () { + return currentLine; + } + + // ======================================== + public File getCurrentFile () { + return currentFile; + } + + // ======================================== + public boolean hasMoreString () { + for (;;) { + if (nextString != null) + return true; + if (currentBuffered == null) { + if (! hasMoreJava ()) + return false; + try { + currentLine = -1; + currentFile = nextJava (); + currentBuffered = new BufferedReader (new FileReader (currentFile)); + } catch (FileNotFoundException e) { + System.err.println ("skip not found file ("+e+")"); + continue; + } + } + try { + String line = currentBuffered.readLine (); + currentLine++; + if (line == null) { + currentBuffered = null; + continue; + } + if (line.indexOf ("\"") >= 0) + nextString = line; + } catch (IOException e) { + System.err.println ("skip premature end of file ("+e+")"); + } + } + } + + // ======================================== + public String nextString () { + if (! hasMoreString ()) + return null; + String result = nextString; + nextString = null; + return result; + } + + // ======================================== +} diff --git a/src/java/tool/model/package-info.java b/src/java/tool/model/package-info.java new file mode 100644 index 0000000..67db557 --- /dev/null +++ b/src/java/tool/model/package-info.java @@ -0,0 +1,9 @@ +/** + * Ordre de lecture des paquetages et des classes
    + *
  • BundleManagerObserver
  • + *
  • BundleManagerModel
  • + *
  • SourceIteratorModel
  • + *
+ * @author F. Merciol + */ +package tool.model; diff --git a/src/java/tool/package-info.java b/src/java/tool/package-info.java new file mode 100644 index 0000000..ba1e708 --- /dev/null +++ b/src/java/tool/package-info.java @@ -0,0 +1,11 @@ +/** + * Ordre de lecture des paquetages et des classes
    + *
  • model
  • + *
  • view
  • + *
  • controller
  • + *
  • LaunchBundleManager
  • + *
+ * @author F. Merciol + */ +package tool; + diff --git a/src/java/tool/view/BundleManagerManager.java b/src/java/tool/view/BundleManagerManager.java new file mode 100644 index 0000000..2ee7f14 --- /dev/null +++ b/src/java/tool/view/BundleManagerManager.java @@ -0,0 +1,286 @@ +package tool.view; + +import java.awt.BorderLayout; +import java.awt.Container; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Hashtable; +import java.util.List; +import java.util.Locale; +import java.util.Vector; +import java.util.regex.Pattern; +import javax.swing.AbstractButton; +import javax.swing.Box; +import javax.swing.JComboBox; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JMenu; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.filechooser.FileNameExtensionFilter; + +import misc.ApplicationManager; +import misc.Bundle; +import misc.Config; +import misc.OwnFrame; +import misc.Util; +import tool.model.BundleManagerModel; +import tool.model.BundleManagerObserver; + +@SuppressWarnings ("serial") public class BundleManagerManager + implements ApplicationManager, ActionListener, BundleManagerObserver { + + // ======================================== + BundleManagerModel bundleManagerModel; + + OwnFrame controller; + + BundleManagerView bundleManagerView; + JFileChooser jFileChooser = new JFileChooser (Config.getString ("BundleDir", ".")); + File bundleDir = null; + String bundleName = null; + + public String getBundleName () { return bundleName; } + + // ======================================== + static public final List fileActionsNames = Arrays.asList ("Empty", "Open", "Merge", "Save", "SaveAs"); + static public final List modelActionsNames = Arrays.asList ("AddKey", "RemoveKey", "AddLocale", "RenameLocale", "RemoveLocale"); + @SuppressWarnings ("unchecked") + static public final Hashtable actionsMethod = + Util.collectMethod (BundleManagerManager.class, Util.merge (fileActionsNames, modelActionsNames)); + + public void actionPerformed (ActionEvent e) { + Util.actionPerformed (actionsMethod, e, this); + } + + // ======================================== + public void updateBundle () { + jFileChooser.setFileFilter (new FileNameExtensionFilter (Bundle.getLabel ("BundelPropertieFile"), "properties")); + } + + // ======================================== + public BundleManagerManager (OwnFrame controller, BundleManagerModel bundleManagerModel, BundleManagerView bundleManagerView) { + this.controller = controller; + this.bundleManagerModel = bundleManagerModel; + this.bundleManagerView = bundleManagerView; + jFileChooser.setMultiSelectionEnabled (false); + + Bundle.addBundleObserver (this); + bundleManagerModel.addBundleObserver (this); + updateBundle (); + } + + // ======================================== + public void actionEmpty () { + if (bundleManagerModel.getModified () && + JOptionPane.showConfirmDialog (controller.getJFrame (), Bundle.getLabel ("RealyDiscardBundle"), + Bundle.getTitle ("BundleNotSaved"), + JOptionPane.YES_NO_OPTION, + JOptionPane.WARNING_MESSAGE) != JOptionPane.YES_OPTION) + return; + bundleName = null; + bundleManagerModel.empty (); + } + + // ======================================== + private void actionOpen (boolean merge) { + if (bundleManagerModel.getModified () && + JOptionPane.showConfirmDialog (controller.getJFrame (), Bundle.getLabel ("RealyDiscardBundle"), + Bundle.getTitle ("BundleNotSaved"), + JOptionPane.YES_NO_OPTION, + JOptionPane.WARNING_MESSAGE) != JOptionPane.YES_OPTION) + return; + jFileChooser.setFileSelectionMode (JFileChooser.FILES_ONLY); + if (jFileChooser.showOpenDialog (controller.getJFrame ()) != JFileChooser.APPROVE_OPTION) + return; + File file = jFileChooser.getSelectedFile (); + setBundleDir (file.getParentFile ()); + setBundleName (file.getName ()); + bundleManagerModel.open (bundleDir, bundleName, merge); + Config.save ("BundleManager"); + } + + // ======================================== + public void actionOpen () { + actionOpen (false); + } + + // ======================================== + public void actionMerge () { + actionOpen (true); + } + + // ======================================== + private void setBundleName (String fileName) { + if (fileName.endsWith (BundleManagerModel.extention)) + fileName = fileName.substring (0, fileName.length () - BundleManagerModel.extention.length ()); + Pattern pattern = Pattern.compile (".*_[a-zA-Z]{2}"); + if (pattern.matcher (fileName).matches ()) + fileName = fileName.substring (0, fileName.length () - 3); + if (pattern.matcher (fileName).matches ()) + fileName = fileName.substring (0, fileName.length () - 3); + bundleName = fileName; + } + + // ======================================== + public void actionSave () { + if (bundleDir == null || bundleName == null) { + actionSaveAs (); + return; + } + bundleManagerModel.saveAs (bundleDir, bundleName); + Config.save ("BundleManager"); + } + + // ======================================== + public void actionSaveAs () { + jFileChooser.setFileSelectionMode ((bundleName == null) ? + JFileChooser.FILES_ONLY : JFileChooser.FILES_AND_DIRECTORIES); + if (jFileChooser.showSaveDialog (controller.getJFrame ()) != JFileChooser.APPROVE_OPTION) + return; + File file = jFileChooser.getSelectedFile (); + if (file.isDirectory ()) + setBundleDir (file); + else { + setBundleDir (file.getParentFile ()); + setBundleName (file.getName ()); + } + actionSave (); + } + + // ======================================== + public void actionAddKey () { + String newKey = + JOptionPane.showInputDialog (controller.getJFrame (), Bundle.getLabel ("NewKey"), + Bundle.getTitle ("AddKey"), + JOptionPane.INFORMATION_MESSAGE); + if (newKey == null) + return; + bundleManagerModel.addKey (newKey); + } + + // ======================================== + public void actionRemoveKey () { + if (bundleManagerView.bundleJTable.getSelectedRowCount () != 1) { + JOptionPane.showMessageDialog (controller.getJFrame (), Bundle.getMessage ("NoKey"), Bundle.getTitle ("NoKey"), + JOptionPane.WARNING_MESSAGE); + return; + } + int selected = bundleManagerView.bundleJTable.getSelectedRow (); + bundleManagerModel.removeKey (bundleManagerModel.getKeyByIndex (selected)); + } + + // ======================================== + public void actionAddLocale () { + + Locale [] locales = Bundle.getAllLocales (); + JComboBox localesList = Bundle.getJComboFlags (locales); + localesList.setEditable (true); + JPanel jPanel = new JPanel (new BorderLayout ()); + jPanel.add (new JLabel (Bundle.getMessage ("AddLocale")), BorderLayout.PAGE_START); + jPanel.add (localesList, BorderLayout.CENTER); + + if (JOptionPane.OK_OPTION != JOptionPane.showConfirmDialog (controller.getJFrame (), jPanel, Bundle.getTitle ("AddLocale"), + JOptionPane.OK_CANCEL_OPTION, JOptionPane.INFORMATION_MESSAGE)) + return; + bundleManagerModel.add ("_"+locales[localesList.getSelectedIndex ()].toString ()); + } + + // ======================================== + public void actionRenameLocale () { + if (bundleManagerView.bundleJTable.getSelectedColumnCount () != 1) { + JOptionPane.showMessageDialog (controller.getJFrame (), Bundle.getMessage ("NoLocale"), + Bundle.getTitle ("NoLocale"), + JOptionPane.WARNING_MESSAGE); + return; + } + String newLocale = + JOptionPane.showInputDialog (controller.getJFrame (), Bundle.getMessage ("RenameLocale"), + Bundle.getTitle ("RenameLocale"), + JOptionPane.INFORMATION_MESSAGE); + if (newLocale == null) + return; + bundleManagerModel.renameLocale (bundleManagerView.bundleColumnNames.get (bundleManagerView.bundleJTable.getSelectedColumn ()), newLocale); + } + + // ======================================== + public void actionRemoveLocale () { + if (bundleManagerView.bundleJTable.getSelectedColumnCount () != 1) { + JOptionPane.showMessageDialog (controller.getJFrame (), Bundle.getMessage ("NoLocale"), + Bundle.getTitle ("NoLocale"), + JOptionPane.WARNING_MESSAGE); + return; + } + bundleManagerModel.removeLocale (bundleManagerView.bundleColumnNames.get (bundleManagerView.bundleJTable.getSelectedColumn ())); + } + + // ======================================== + private void setBundleDir (File dir) { + bundleDir = dir; + try { + Config.setString ("BundleDir", dir.getCanonicalPath ()); + } catch (Exception e) { + } + } + + // ======================================== + private Vector saveCommands = new Vector (); + public void addSaveCommand (AbstractButton saveCommand) { + if (saveCommand == null) + return; + saveCommands.add (saveCommand); + saveCommand.setEnabled (bundleName != null && bundleManagerModel.getModified ()); + } + + // ======================================== + private Vector saveAsCommands = new Vector (); + public void addSaveAsCommand (AbstractButton saveAsCommand) { + if (saveAsCommand == null) + return; + saveAsCommands.add (saveAsCommand); + saveAsCommand.setEnabled (bundleManagerModel.getModified ()); + } + + // ======================================== + public void broadcastSaveUpdate () { + boolean enable = bundleName != null && bundleManagerModel.getModified (); + for (AbstractButton saveCommand : saveCommands) + saveCommand.setEnabled (enable); + } + + // ======================================== + public void broadcastSaveAsUpdate () { + boolean enable = bundleManagerModel.getModified (); + for (AbstractButton saveAsCommand : saveAsCommands) + saveAsCommand.setEnabled (enable); + } + + // ======================================== + public void dataModifiedChange () { + broadcastSaveUpdate (); + broadcastSaveAsUpdate (); + } + public void localesUpdated () {} + public void dataUpdated () {} + + // ======================================== + public void addMenuItem (JMenu... jMenu) { + int idx = 0; + Util.addMenuItem (fileActionsNames, this, jMenu[idx++]); + Util.addMenuItem (modelActionsNames, this, jMenu[idx++]); + } + public void addIconButtons (Container... containers) { + int idx = 0; + Util.addIconButton (fileActionsNames, this, containers[idx++]); + Util.addIconButton (modelActionsNames, this, containers[idx++]); + } + public void addActiveButtons (Hashtable buttons) { + addSaveCommand (buttons.get ("Save")); + addSaveAsCommand (buttons.get ("SaveAs")); + } + + // ======================================== +} diff --git a/src/java/tool/view/BundleManagerMenu.java b/src/java/tool/view/BundleManagerMenu.java new file mode 100644 index 0000000..bd27046 --- /dev/null +++ b/src/java/tool/view/BundleManagerMenu.java @@ -0,0 +1,44 @@ +package tool.view; + +import java.util.Hashtable; +import javax.swing.AbstractButton; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JMenu; +import javax.swing.JMenuBar; + +import misc.ApplicationManager; +import misc.HelpManager; +import misc.Log; +import misc.ToolBarManager; +import misc.Util; + +@SuppressWarnings ("serial") public class BundleManagerMenu extends JMenuBar { + // ======================================== + public BundleManagerMenu (ApplicationManager controllerManager, ApplicationManager bundleManagerManager, + ApplicationManager helpManager, ToolBarManager toolBarManager) { + setLayout (new BoxLayout (this, BoxLayout.X_AXIS)); + + JMenu fileMenu = Util.addJMenu (this, "File"); + JMenu bundleMenu = Util.addJMenu (this, "Bundle"); + add (Box.createHorizontalGlue ()); + JMenu helpMenu = Util.addJMenu (this, "Help"); + + controllerManager.addMenuItem (fileMenu); + bundleManagerManager.addMenuItem (fileMenu, bundleMenu); + helpManager.addMenuItem (helpMenu); + toolBarManager.addMenuItem (helpMenu); + + Hashtable buttons = new Hashtable (); + Util.collectButtons (buttons, fileMenu); + Util.collectButtons (buttons, bundleMenu); + Util.collectButtons (buttons, helpMenu); + + controllerManager.addActiveButtons (buttons); + bundleManagerManager.addActiveButtons (buttons); + helpManager.addActiveButtons (buttons); + toolBarManager.addActiveButtons (buttons); + } + + // ======================================== +} diff --git a/src/java/tool/view/BundleManagerToolBar.java b/src/java/tool/view/BundleManagerToolBar.java new file mode 100644 index 0000000..c567ef7 --- /dev/null +++ b/src/java/tool/view/BundleManagerToolBar.java @@ -0,0 +1,45 @@ +package tool.view; + +import java.awt.Component; +import java.util.Hashtable; +import javax.swing.AbstractButton; +import javax.swing.Box; +import javax.swing.JComponent; +import javax.swing.JToolBar; + +import misc.ApplicationManager; +import misc.Bundle; +import misc.HelpManager; +import misc.Log; +import misc.ToolBarManager; +import misc.Util; + +public class BundleManagerToolBar { + + // ======================================== + static public String defaultCardinalPoint = "North"; + + public BundleManagerToolBar (ApplicationManager controllerManager, BundleManagerManager bundleManagerManager, + HelpManager helpManager, ToolBarManager toolBarManager) { + JToolBar fileToolBar = toolBarManager.newJToolBar ("File", defaultCardinalPoint); + JToolBar bundleToolBar = toolBarManager.newJToolBar ("Bundle", defaultCardinalPoint); + JToolBar helpToolBar = toolBarManager.newJToolBar ("Help", defaultCardinalPoint); + + controllerManager.addIconButtons (fileToolBar); + bundleManagerManager.addIconButtons (fileToolBar, bundleToolBar); + helpManager.addIconButtons (helpToolBar); + toolBarManager.addIconButtons (helpToolBar); + + Hashtable buttons = new Hashtable (); + Util.collectButtons (buttons, fileToolBar); + Util.collectButtons (buttons, bundleToolBar); + Util.collectButtons (buttons, helpToolBar); + + controllerManager.addActiveButtons (buttons); + bundleManagerManager.addActiveButtons (buttons); + helpManager.addActiveButtons (buttons); + toolBarManager.addActiveButtons (buttons); + } + + // ======================================== +} diff --git a/src/java/tool/view/BundleManagerView.java b/src/java/tool/view/BundleManagerView.java new file mode 100644 index 0000000..f0b35b2 --- /dev/null +++ b/src/java/tool/view/BundleManagerView.java @@ -0,0 +1,108 @@ +package tool.view; + +import java.awt.BorderLayout; +import java.util.Vector; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.table.DefaultTableModel; + +import misc.Bundle; +import tool.model.BundleManagerModel; +import tool.model.BundleManagerObserver; + +@SuppressWarnings ("serial") public class BundleManagerView extends JPanel implements BundleManagerObserver { + + // ======================================== + BundleManagerModel bundleManagerModel; + + // ======================================== + Vector bundleColumnNames; + DefaultTableModel bundleTableModel; + JTable bundleJTable; + + // ======================================== + public void updateBundle () { + localesUpdated (); + } + + // ======================================== + public BundleManagerView (final BundleManagerModel bundleManagerModel) { + super (new BorderLayout ()); + this.bundleManagerModel = bundleManagerModel; + bundleJTable = new JTable (); + bundleJTable.getColumnModel ().setColumnSelectionAllowed (true); + bundleJTable.setSelectionMode (ListSelectionModel.SINGLE_SELECTION); + JScrollPane scrollBundle = new JScrollPane (bundleJTable); + bundleJTable.setFillsViewportHeight (true); + add (scrollBundle, BorderLayout.CENTER); + bundleManagerModel.addBundleObserver (this); + updateBundle (); + Bundle.addBundleObserver (this); + } + + // ======================================== + public void dataModifiedChange () {} + + // ======================================== + public void localesUpdated () { + bundleColumnNames = new Vector (); + bundleColumnNames.add (Bundle.getLabel ("Keys")); + bundleColumnNames.addAll (bundleManagerModel.getLocales ()); + bundleTableModel = new DefaultTableModel (bundleColumnNames, 0) { + + private static final long serialVersionUID = 1L; + + public String getColumnName(int col) { + return bundleColumnNames.get (col); + } + + public int getColumnCount () { + return bundleColumnNames.size (); + } + + public Object getValueAt (int row, int col) { + String key = bundleManagerModel.getKeyByIndex (row); + if (col == 0) + return key; + String locale = bundleManagerModel.getLocaleByIndex (col-1); + return bundleManagerModel.get (locale, key); + } + + public void setValueAt (Object value, int row, int col) { + String key = bundleManagerModel.getKeyByIndex (row); + if (col == 0) { + String newKey = (String) value; + if (newKey.equals (key)) + return; + bundleManagerModel.renameKey (key, newKey); + return; + } + String locale = bundleManagerModel.getLocaleByIndex (col-1); + bundleManagerModel.set (locale, key, (String) value); + //fireTableCellUpdated (row, col); + } + public boolean isCellEditable (int row, int column) { + return true; + } + }; + bundleJTable.setModel (bundleTableModel); + dataUpdated (); + } + + // ======================================== + public void dataUpdated () { + bundleTableModel.setRowCount (0); + for (String key : bundleManagerModel.getKeys ()) { + Vector row = new Vector (); + row.add (key); + for (String locale : bundleManagerModel.getLocales ()) + row.add (bundleManagerModel.get (locale, key)); + bundleTableModel.addRow (row); + } + bundleTableModel.fireTableDataChanged (); + } + + // ======================================== +} diff --git a/src/java/tool/view/SourceIteratorView.java b/src/java/tool/view/SourceIteratorView.java new file mode 100644 index 0000000..b8a9250 --- /dev/null +++ b/src/java/tool/view/SourceIteratorView.java @@ -0,0 +1,127 @@ +package tool.view; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Hashtable; +import java.util.List; +import javax.swing.JFileChooser; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.table.DefaultTableModel; + +import misc.Bundle; +import misc.Config; +import misc.Util; +import tool.model.SourceIteratorModel; + +@SuppressWarnings ("serial") public class SourceIteratorView extends JPanel implements ActionListener { + + // ======================================== + SourceIteratorModel sourceIteratorModel = null; + + // ======================================== + String[] columnLabels = { "FileName", "LineNumber", "Line" }; + DefaultTableModel sourceIteratorTableModel; + JTable sourceIteratorJTable; + + // ======================================== + static public final List actionsNames = Arrays.asList ("Init", "Next"); + @SuppressWarnings ("unchecked") + static public final Hashtable actionsMethod = + Util.collectMethod (SourceIteratorView.class, actionsNames); + + public void actionPerformed (ActionEvent e) { + Util.actionPerformed (actionsMethod, e, this); + } + + // ======================================== + public void updateBundle () { + Util.setColumnLabels (sourceIteratorJTable, columnLabels); + } + + // ======================================== + DefaultTableCellRenderer greenRendered = new DefaultTableCellRenderer (); + DefaultTableCellRenderer whiteRendered = new DefaultTableCellRenderer (); + + // ======================================== + public SourceIteratorView () { + super (new BorderLayout ()); + + greenRendered.setBackground (Color.GREEN); + whiteRendered.setBackground (Color.WHITE); + + int [] columnWidth = { + 200, + 5, + 1000 + }; + sourceIteratorTableModel = new DefaultTableModel (columnLabels, 1); + sourceIteratorJTable = new JTable (sourceIteratorTableModel); + updateBundle (); + for (int i = 0; i < sourceIteratorTableModel.getColumnCount (); i++) + sourceIteratorJTable.getColumnModel ().getColumn (i).setPreferredWidth (columnWidth [i]); + sourceIteratorJTable.getColumnModel ().setColumnSelectionAllowed (true); + sourceIteratorJTable.setSelectionMode (ListSelectionModel.SINGLE_SELECTION); + add (sourceIteratorJTable.getTableHeader (), BorderLayout.PAGE_START); + add (sourceIteratorJTable, BorderLayout.CENTER); + + JPanel buttonPanel = new JPanel (); + Util.addButton (actionsNames, this, buttonPanel); + add (buttonPanel, BorderLayout.PAGE_END); + + Bundle.addBundleObserver (this); + } + + // ======================================== + JFileChooser jFileChooser = new JFileChooser (Config.getString ("SourceIteratorDir", ".")); + + // ======================================== + public void actionInit () { + jFileChooser.setFileSelectionMode (JFileChooser.DIRECTORIES_ONLY); + if (jFileChooser.showOpenDialog (this) != JFileChooser.APPROVE_OPTION) + return; + File dir = jFileChooser.getSelectedFile (); + sourceIteratorModel = new SourceIteratorModel (dir); + actionNext (); + try { + Config.setString ("SourceIteratorDir", dir.getCanonicalPath ()); + } catch (Exception e) { + } + } + + + // ======================================== + public void actionNext () { + if (sourceIteratorModel == null) { + JOptionPane.showMessageDialog (this, Bundle.getMessage ("NoSourceDir"), Bundle.getTitle ("NoSource"), + JOptionPane.WARNING_MESSAGE); + return; + } + if (! sourceIteratorModel.hasMoreString ()) { + sourceIteratorTableModel.setRowCount (0); + sourceIteratorTableModel.addRow (new String [] {"", "", ""}); + sourceIteratorModel = null; + return; + } + String next = sourceIteratorModel.nextString (); + sourceIteratorTableModel.setRowCount (0); + sourceIteratorTableModel.addRow (new String [] { + ""+sourceIteratorModel.getCurrentFile (), + ""+sourceIteratorModel.getCurrentLine (), + next + }); + + sourceIteratorJTable.getColumnModel ().getColumn (2).setCellRenderer + ((next.indexOf ("Bundle.") >= 0 || next.indexOf ("Config.") >= 0 ) ? greenRendered : whiteRendered); + } + + // ======================================== +} diff --git a/src/javaTest/misc/BundleTest.java b/src/javaTest/misc/BundleTest.java new file mode 100644 index 0000000..0687d8c --- /dev/null +++ b/src/javaTest/misc/BundleTest.java @@ -0,0 +1,80 @@ +package misc; + +import java.util.Locale; +import javax.swing.JButton; + +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.JUnitCore; + +public class BundleTest extends Assert { + + static public junit.framework.Test suite () { + return new junit.framework.JUnit4TestAdapter (BundleTest.class); + } + + static public void main (String args[]) { + JUnitCore.main ("misc.BundleTest"); + } + + // ======================================== + static public int countObserver; + static public int countActioner; + + @BeforeClass static public void setBundleObserver () { + Bundle.addBundleObserver (new Object () { + public void updateBundle () { + countObserver++; + } + }); + Bundle.load ("BundleManager", Locale.US); + String action = "Quit"; + JButton button = new JButton (Bundle.getAction (action)) { + private static final long serialVersionUID = 1L; + + public void setText (String text) { + super.setText (text); + countActioner++; + } + }; + Bundle.addLocalizedActioner (button); + button.setActionCommand (action); + } + + // ======================================== + @Test public void setLocale () { + Bundle.load ("BundleManager", Locale.US); + int oldValue = countObserver; + Bundle.setLocale (Locale.FRANCE); + assertTrue (oldValue+1 == countObserver); + assertTrue (countActioner == countObserver); + } + + @Test public void load () { + int oldValue = countObserver; + Bundle.load ("BundleManager"); + assertTrue (oldValue+1 == countObserver); + assertTrue (countActioner == countObserver); + } + + @Test public void getLocales () { + Locale[] locales = Bundle.getApplicationsLocales (); + assertTrue (locales.length > 0); + Bundle.load ("BundleManager"); + for (Locale locale : locales) + Bundle.setLocale (locale); + } + + @Test public void getString () { + Bundle.load ("BundleManager", Locale.US); + String s1 = Bundle.getTitle ("BundleEditor"); + Bundle.setLocale (Locale.FRANCE); + String s2 = Bundle.getTitle ("BundleEditor"); + assertNotNull (s1); + assertNotNull (s2); + assertFalse (s1.equals (s2)); + } + + // ======================================== +} diff --git a/src/javaTest/misc/ColorCheckedLineTest.java b/src/javaTest/misc/ColorCheckedLineTest.java new file mode 100644 index 0000000..e0d397e --- /dev/null +++ b/src/javaTest/misc/ColorCheckedLineTest.java @@ -0,0 +1,46 @@ +package misc; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.JUnitCore; + +public class ColorCheckedLineTest extends Assert { + + static public junit.framework.Test suite () { + return new junit.framework.JUnit4TestAdapter (ColorCheckedLineTest.class); + } + + static public void main (String args[]) { + JUnitCore.main ("misc.ColorCheckedLineTest"); + } + + // ======================================== + @Test public void ok () { + try { + System.err.print ("Test ok : "); + + // something right + + System.err.println (ColorCheckedLine.MSG_OK); + } catch (Exception e) { + System.err.println (ColorCheckedLine.MSG_OK); + } + } + + // ======================================== + @Test public void ko () { + try { + System.err.print ("Test ko : "); + + // something wrong + int i = 0; + i = 1/i; + + System.err.println (ColorCheckedLine.MSG_OK); + } catch (Exception e) { + System.err.println (ColorCheckedLine.MSG_KO); + } + } + + // ======================================== +} diff --git a/src/javaTest/misc/ConfigTest.java b/src/javaTest/misc/ConfigTest.java new file mode 100644 index 0000000..620490f --- /dev/null +++ b/src/javaTest/misc/ConfigTest.java @@ -0,0 +1,153 @@ +package misc; + +import java.awt.Container; +import java.awt.Point; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.LineNumberReader; +import java.io.PrintWriter; + +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.JUnitCore; + +public class ConfigTest extends Assert { + + static public junit.framework.Test suite () { + return new junit.framework.JUnit4TestAdapter (ConfigTest.class); + } + + static public void main (String args[]) { + JUnitCore.main ("misc.ConfigTest"); + } + + // ======================================== + static public final String testApplicationName = "Test"; + static public final String testConfigFileName = + Config.configSystemDir+System.getProperty ("file.separator")+testApplicationName+Config.configExt; + static public final String result = "result"; + static public final String booleanTrue = "booleanTrue"; + static public final String example = "example"; + static public final String value = "value"; + + static public final String configContent = + "\n"+ + "\n"+ + "\n"+ + " [x=100,y=200]\n"+ + " "+value+"\n"+ + " "+true+"\n"+ + "\n"; + + public int getLines (String fileName) + throws FileNotFoundException, IOException { + long fileSize = (new File (fileName)).length (); + LineNumberReader lineNumberReader = new LineNumberReader (new FileReader (fileName)); + lineNumberReader.skip (fileSize); + return lineNumberReader.getLineNumber (); + } + + @BeforeClass static public void createTestConfig () + throws FileNotFoundException { + PrintWriter configFile = new PrintWriter (testConfigFileName); + configFile.write (configContent); + configFile.close (); + } + + @AfterClass static public void removeTestConfig () { + (new File (testConfigFileName)).delete (); + } + + + // ======================================== + @Test public void load () { + boolean findFile = false; + for (String fileName : + (new File (Config.configSystemDir)).list (new FilenameFilter () { + public boolean accept (File dir, String name) { + return name.endsWith (Config.configExt); + } + })) { + findFile = true; + Config.setPWD (ConfigTest.class); + Config.load (fileName.substring (0, fileName.length () - Config.configExt.length ())); + assertTrue (Config.isLoaded ()); + } + assertTrue (findFile); + } + + @Test public void getString () { + Config.setPWD (ConfigTest.class); + Config.load (testApplicationName); + assertEquals (result, Config.getString (null, result)); + assertEquals (value, Config.getString (example)); + } + + @Test (expected=IllegalArgumentException.class) public void getStringNullKey () { + Config.getString (null); + } + + @Test public void getBoolean () { + Config.setPWD (ConfigTest.class); + Config.load (testApplicationName); + assertTrue (Config.getBoolean (booleanTrue, false)); + assertFalse (Config.getBoolean (example, false)); + } + + @Test (expected=IllegalArgumentException.class) public void getBooleanNullKey () { + Config.getBoolean (null); + } + + @Test public void save () + throws FileNotFoundException, IOException { + int lines = getLines (testConfigFileName); + Config.setPWD (ConfigTest.class); + Config.load (testApplicationName); + Config.setString ("a", "b"); + Config.save (testApplicationName); + assertTrue (lines < getLines (testConfigFileName)); + Config.load (testApplicationName); + assertEquals ("b", Config.getString ("a")); + } + + @Test public void setString () { + Config.setPWD (ConfigTest.class); + Config.load (testApplicationName); + Config.setString ("a", "b"); + assertEquals ("b", Config.getString ("a")); + } + + @Test public void setBoolean () { + Config.setPWD (ConfigTest.class); + Config.load (testApplicationName); + Config.setBoolean ("bool", true); + assertTrue (Config.getBoolean ("bool")); + Config.setBoolean ("bool", false); + assertFalse (Config.getBoolean ("bool")); + } + + @Test public void saveLocation () { + Config.setPWD (ConfigTest.class); + Config.load (testApplicationName); + assertNull (Config.getString ("test"+Config.locationPostfix, null)); + Container container = new Container (); + container.setLocation (10, 20); + Config.saveLocation ("test", container); + assertEquals ("[x=10,y=20]", Config.getString ("test"+Config.locationPostfix)); + } + + @Test public void loadLocation () { + Config.setPWD (ConfigTest.class); + Config.load (testApplicationName); + Container container = new Container (); + Config.loadLocation ("Frame", container, new Point (0, 0)); + assertEquals (new Point (100, 200), container.getLocation ()); + } + + // ======================================== +} diff --git a/src/javaTest/misc/LocaleTest.java b/src/javaTest/misc/LocaleTest.java new file mode 100644 index 0000000..6d5f3ce --- /dev/null +++ b/src/javaTest/misc/LocaleTest.java @@ -0,0 +1,39 @@ +package misc; + +import java.util.ResourceBundle; +import java.util.Locale; +import java.io.FileOutputStream; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.JUnitCore; + +public class LocaleTest extends Assert { + + static public junit.framework.Test suite () { + return new junit.framework.JUnit4TestAdapter (LocaleTest.class); + } + + static public void main (String args[]) { + JUnitCore.main ("misc.LocaleTest"); + } + + // ======================================== + @Test public void launch () { + Locale currentLocale = Locale.getDefault (); + //Locale currentLocale = new Locale ("fr", "FR"); + ResourceBundle messages = ResourceBundle.getBundle ("Misc", currentLocale, Bundle.bundleControl); + assertNotNull (messages.getString ("ActionQuit")); + } + + // ======================================== + @Test public void save () + throws java.io.IOException { + + String arg = "/tmp/RemDumpProperties"; + Bundle.load ("Misc"); + System.getProperties ().storeToXML (new FileOutputStream (arg), Bundle.getString ("RemDumpProperties", null)); + } + + // ======================================== +} diff --git a/src/javaTest/misc/TitledDialogTest.java b/src/javaTest/misc/TitledDialogTest.java new file mode 100644 index 0000000..c45b737 --- /dev/null +++ b/src/javaTest/misc/TitledDialogTest.java @@ -0,0 +1,63 @@ +package misc; + +import java.awt.Frame; +import java.util.Locale; + +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.JUnitCore; + +public class TitledDialogTest extends Assert { + + static public junit.framework.Test suite () { + return new junit.framework.JUnit4TestAdapter (TitledDialogTest.class); + } + + static public void main (String args[]) { + JUnitCore.main ("misc.TitledDialogTest"); + } + + // ======================================== + static public TitledDialog titledDialog; + + @BeforeClass static public void setBundleObserver () { + Bundle.load ("BundleManager"); + titledDialog = new TitledDialog (new Frame (), "BundleEditor"); + } + + // ======================================== + @Test public void bundleReloaded () { + Bundle.load ("BundleManager", Locale.US); + String s1 = titledDialog.getTitle (); + Bundle.setLocale (Locale.FRANCE); + String s2 = titledDialog.getTitle (); + assertNotNull (s1); + assertNotNull (s2); + assertFalse (s1.equals (s2)); + } + + @Test public void setVisible () { + Config.setPWD (TitledDialogTest.class); + Config.load ("BundleManager"); + titledDialog.setVisible (true); + assertTrue (titledDialog.isVisible ()); + titledDialog.setVisible (false); + assertFalse (titledDialog.isVisible ()); + } + + @Test public void saveLocation () { + Config.setPWD (TitledDialogTest.class); + Config.load ("BundleManager"); + assertNull (Config.getString ("BundleEditor"+Config.locationPostfix, null)); + titledDialog.setVisible (true); + titledDialog.setVisible (false); + titledDialog.saveLocation (); + Object [] location = Config.coordonateFormat.parse (Config.getString ("BundleEditor"+Config.locationPostfix, null), + new java.text.ParsePosition (0)); + assertTrue (((Number) location [0]).intValue () > 0); + assertTrue (((Number) location [1]).intValue () > 0); + } + + // ======================================== +} diff --git a/src/javaTest/misc/UtilTest.java b/src/javaTest/misc/UtilTest.java new file mode 100644 index 0000000..b0b8056 --- /dev/null +++ b/src/javaTest/misc/UtilTest.java @@ -0,0 +1,147 @@ +package misc; + +import java.awt.Component; +import java.awt.Container; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Arrays; +import java.util.List; +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JMenuItem; +import javax.swing.JRadioButton; +import javax.swing.JMenuBar; + +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.JUnitCore; + +public class UtilTest extends Assert { + + static public junit.framework.Test suite () { + return new junit.framework.JUnit4TestAdapter (UtilTest.class); + } + + static public void main (String args[]) { + JUnitCore.main ("misc.UtilTest"); + } + + // ======================================== + static public final void isCapital (String s) { + assertTrue (Character.isUpperCase (s.charAt(0))); + for (int i = 1; i < s.length (); i++) + assertTrue (Character.isLowerCase (s.charAt(i))); + } + + static public final List actions = Arrays.asList ("Open", "Save", "Quit"); + static public final ActionListener actionListener = new ActionListener () { + public void actionPerformed (ActionEvent e) { + } + }; + + @BeforeClass static public void createTestConfig () { + Bundle.load ("BundleManager"); + } + + // ======================================== + @Test public void addRadioButton () { + Container container = new Container (); + ButtonGroup group = new ButtonGroup (); + Util.addRadioButton (actions, actionListener, group, "Quit", container); + assertTrue (actions.contains (group.getSelection ().getActionCommand ())); + assertTrue (container.getComponentCount () == actions.size ()); + for (Component component : container.getComponents ()) { + JRadioButton button = (JRadioButton) component; + assertTrue (actions.contains (button.getActionCommand ())); + Arrays.asList (button.getActionListeners ()).contains (actionListener); + if ("Quit".equals (button.getActionCommand ())) { + assertEquals (group.getSelection ().getActionCommand (), button.getActionCommand ()); + assertTrue (button.isSelected ()); + } else + assertFalse (button.isSelected ()); + } + } + + @Test public void addButton () { + Container container = new Container (); + Util.addButton (actions, actionListener, container); + assertTrue (container.getComponentCount () == actions.size ()); + for (Component component : container.getComponents ()) { + JButton button = (JButton) component; + assertTrue (actions.contains (button.getActionCommand ())); + Arrays.asList (button.getActionListeners ()).contains (actionListener); + } + } + + @Test public void addIconButton () { + Container container = new Container (); + Util.addIconButton (actions, actionListener, container); + assertTrue (container.getComponentCount () == actions.size ()); + for (Component component : container.getComponents ()) { + JButton button = (JButton) component; + assertNotNull (button.getIcon ()); + assertTrue (actions.contains (button.getActionCommand ())); + Arrays.asList (button.getActionListeners ()).contains (actionListener); + } + } + + @Test public void addMenuItem () { + Container container = new Container (); + Util.addMenuItem (actions, actionListener, container); + assertTrue (container.getComponentCount () == actions.size ()); + for (Component component : container.getComponents ()) { + JMenuItem item = (JMenuItem) component; + assertTrue (actions.contains (item.getActionCommand ())); + Arrays.asList (item.getActionListeners ()).contains (actionListener); + } + } + + @Test public void addCheckMenuItem () { + Container container = new Container (); + Util.addCheckMenuItem (actions, actionListener, container); + assertTrue (container.getComponentCount () == actions.size ()); + for (Component component : container.getComponents ()) { + JMenuItem item = (JMenuItem) component; + assertTrue (actions.contains (item.getActionCommand ())); + Arrays.asList (item.getActionListeners ()).contains (actionListener); + } + } + + @Test public void addJMenu () { + JMenuBar jMenuBar = new JMenuBar (); + Util.addJMenu (jMenuBar, "Help"); + } + + @Test public void setColumnLabels () { + // YYY + } + + + @Test public void packWindow () {} + @Test public void newJFrame () {} + @Test public void WindowAdapter () {} + @Test public void windowClosing () {} + + @Test public void loadActionIcon () {} + @Test public void loadImageIcon () {} + @Test public void getDataUrl () {} + + @Test public void collectMethod () {} + @Test public void collectButtons () {} + @Test public void actionPerformed () {} + @Test public void merge () {} + + @Test public void toCapital () { + for (String s : Arrays.asList ("abcd", "AbCd", "aBcD", "ABCD")) + isCapital (Util.toCapital (s)); + } + + @Test (timeout=1500) public void sleep () { + long before = System.currentTimeMillis (); + Util.sleep (1); + long after = System.currentTimeMillis (); + long delta = after-before; + assertTrue (delta > 900 && delta < 1100); + } +} diff --git a/src/javaTest/misc/XMLTest.java b/src/javaTest/misc/XMLTest.java new file mode 100644 index 0000000..362ae67 --- /dev/null +++ b/src/javaTest/misc/XMLTest.java @@ -0,0 +1,70 @@ +package misc; + +import java.io.StringBufferInputStream; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import org.junit.Test; +import org.junit.Ignore; +import org.junit.runner.JUnitCore; + +import misc.XML; + +@SuppressWarnings("deprecation") + public class XMLTest { + + @Ignore ("for exemple") @Test (expected=IndexOutOfBoundsException.class, timeout=100) public void empty () { + new java.util.ArrayList ().get (0); + } + + static public junit.framework.Test suite () { + return new junit.framework.JUnit4TestAdapter (XMLTest.class); + } + + static public void main (String args[]) { + JUnitCore.main ("misc.XMLTest"); + } + + // ======================================== + + // XXX lecture de chaine, exriture dans une chaine différence + + String badXmlString = "Hello Word"; + String xmlString = "\n"+ + "\n"+ + " \n"+ + "\n"; + + // ======================================== + @Test (expected=java.io.IOException.class) public void badRead () + throws javax.xml.parsers.ParserConfigurationException, org.xml.sax.SAXException, java.io.IOException { + XML.readDocument (new StringBufferInputStream (badXmlString)); + } + + // ======================================== + @Test public void read () + throws javax.xml.parsers.ParserConfigurationException, org.xml.sax.SAXException, java.io.IOException { + XML.readDocument (new StringBufferInputStream (xmlString)); + } + + // ======================================== + @Test public void write () + throws javax.xml.parsers.ParserConfigurationException { + DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance ().newDocumentBuilder (); + Document doc = documentBuilder.newDocument (); + doc.setXmlStandalone (true); + Element elem = doc.createElement ("noeud1"); + doc.appendChild (elem); + elem.setAttribute ("attr1", "val 1"); + elem.appendChild (doc.createElement ("noeud2")).appendChild (doc.createElement ("noeud3")); + elem.appendChild (doc.createElement ("noeud4")); + Element elem3 = doc.createElement ("noeud5"); + elem.appendChild (elem3); + elem3.setAttribute ("attr5", "val 5&"); + XML.writeDocument (doc, System.out); + } + + // ======================================== + } diff --git a/src/javaTest/network/HTTPInputStreamTest.java b/src/javaTest/network/HTTPInputStreamTest.java new file mode 100644 index 0000000..124241f --- /dev/null +++ b/src/javaTest/network/HTTPInputStreamTest.java @@ -0,0 +1,89 @@ +package network; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStreamWriter; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.URL; +import java.net.URLConnection; + +import org.junit.Test; +import org.junit.runner.JUnitCore; + +import misc.XML; + +public class HTTPInputStreamTest { + + static public junit.framework.Test suite () { + return new junit.framework.JUnit4TestAdapter (HTTPInputStreamTest.class); + } + + static public void main (String args[]) { + JUnitCore.main ("network.HTTPInputStreamTest"); + } + + static { + Protocol.setIpV4 (); + } + + // ======================================== + @Test (timeout=3000) public void launch () + throws IOException { + + String xmlRequest = + "\n"+ + "\n"+ + " \n"+ + "\n"; + + final String xmlResponse = + "\n"+ + "\n"+ + " \n"+ + "\n"; + + System.err.println ("start test"); + System.err.flush (); + final ServerSocket serverSocket = new ServerSocket (8080); + serverSocket.setReuseAddress (true); + (new Thread () { + public void run () { + try { + System.err.println ("run"); + System.err.flush (); + Socket call = serverSocket.accept (); + System.err.println ("Accept call "+call); + HTTPInputStream httpInputStream = new HTTPInputStream (call.getInputStream ()); + System.err.println ("Server:main headers "+httpInputStream.headers+ + " available:"+httpInputStream.available ()); + System.err.println ("Server:main server receiveXML : "); + XML.writeDocument (XML.readDocument (httpInputStream), System.err); + System.err.flush (); + + HTTPOutputStream httpPOutputStream = new HTTPOutputStream (call.getOutputStream ()); + httpPOutputStream.setHeader ("Content-type", "application/xml"); + httpPOutputStream.write (xmlResponse.getBytes ()); + httpPOutputStream.close (); + } catch (IOException e) { + e.printStackTrace (); + } + } + }).start (); + + System.err.println ("url"); + System.err.flush (); + URLConnection urlConnection = XMLConnection.getXMLConnection (new URL ("http://localhost:8080/")); + OutputStreamWriter out = new OutputStreamWriter (urlConnection.getOutputStream ()); + out.write (xmlRequest); + out.close (); + + // attention l'ouverture de inputstream provoque la fermeture de outputstream + InputStream in = urlConnection.getInputStream (); + System.err.println ("Server:main client receiveXML : "); + XML.writeDocument (XML.readDocument (in), System.err); + System.err.flush (); + } + + // ======================================== +} diff --git a/src/javaTest/network/HTTPOutputStreamTest.java b/src/javaTest/network/HTTPOutputStreamTest.java new file mode 100644 index 0000000..bd03b01 --- /dev/null +++ b/src/javaTest/network/HTTPOutputStreamTest.java @@ -0,0 +1,52 @@ +package network; + +import java.io.IOException; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.JUnitCore; + +public class HTTPOutputStreamTest extends Assert { + + static public junit.framework.Test suite () { + return new junit.framework.JUnit4TestAdapter (HTTPOutputStreamTest.class); + } + + static public void main (String args[]) { + JUnitCore.main ("network.HTTPOutputStreamTest"); + } + + static { + Protocol.setIpV4 (); + } + + // ======================================== + @Test (timeout=3000) public void launch () + throws IOException { + final java.net.ServerSocket serverSocket = new java.net.ServerSocket (8080); + serverSocket.setReuseAddress (true); + (new Thread () { + public void run () { + try { + java.net.Socket call = serverSocket.accept (); + HTTPOutputStream out = new HTTPOutputStream (call.getOutputStream ()); + out.setHeader ("Content-type", "application/xml"); + out.write ("coucou\n".getBytes ()); + out.close (); + } catch (Exception e) { + fail ("Server side exception:"+e); + } + } + }).start (); + + java.net.Socket client = new java.net.Socket ("localhost", 8080); + java.io.InputStream in = client.getInputStream (); + + byte[] b = new byte [8196]; + int nb = in.read (b); + System.err.println ("Client:in:"+new String (b, 0, nb)); + System.err.flush (); + } + + // ======================================== +} diff --git a/src/javaTest/network/ServerTest.java b/src/javaTest/network/ServerTest.java new file mode 100644 index 0000000..7ae8901 --- /dev/null +++ b/src/javaTest/network/ServerTest.java @@ -0,0 +1,41 @@ +package network; + +import org.junit.Test; +import org.junit.runner.JUnitCore; + +import misc.Util; + +public class ServerTest { + + static public junit.framework.Test suite () { + return new junit.framework.JUnit4TestAdapter (ServerTest.class); + } + + static public void main (String args[]) { + JUnitCore.main ("network.ServerTest"); + } + + // ======================================== + @Test public void launch () { + Server server = new Server (8080); + Client client = new Client ("127.0.0.1", 8080); + + server.start (); + + server.send ("service avant ecoute client"); + + Util.sleep (2); + + client.start (); + + Util.sleep (2); + + server.send ("service apres ecoute client"); + + Util.sleep (2); + + client.send ("service immediat"); + } + + // ======================================== +} diff --git a/src/javaTest/network/chat/ChatTest.java b/src/javaTest/network/chat/ChatTest.java new file mode 100644 index 0000000..779d73d --- /dev/null +++ b/src/javaTest/network/chat/ChatTest.java @@ -0,0 +1,48 @@ +package network.chat; + +import org.junit.Test; +import org.junit.runner.JUnitCore; + +import network.Client; +import network.Server; + +public class ChatTest { + + static public junit.framework.Test suite () { + return new junit.framework.JUnit4TestAdapter (ChatTest.class); + } + + static public void main (String args[]) { + JUnitCore.main ("network.chat.ChatTest"); + } + + // ======================================== + @Test public void launch () { + Server server = new Server (8080); + Client client = new Client ("127.0.0.1", 8080); + + Chat chatServer = new Chat ("chatServer", server); + Chat chatClient = new Chat ("chatClient", client); + + ChatObserver chatObserver = new ChatObserver () { + public void talk (ChatQuote quote) { + System.err.println (Chat.dateFormat.format (quote.date)+" "+quote.speaker+" > "+quote.sentence); + } + public void renameSpeaker (String speaker) {} + public void chatModifiedChange (boolean modified) {} + public void clearChat () {} + }; + chatServer.addChatObserver (chatObserver); + chatClient.addChatObserver (chatObserver); + + server.start (); + misc.Util.sleep (1); + client.start (); + misc.Util.sleep (1); + chatServer.say ("hello"); + misc.Util.sleep (1); + chatClient.say ("world"); + } + + // ======================================== +} diff --git a/src/javaTest/network/chat/JChatTest.java b/src/javaTest/network/chat/JChatTest.java new file mode 100644 index 0000000..1c1d41a --- /dev/null +++ b/src/javaTest/network/chat/JChatTest.java @@ -0,0 +1,68 @@ +package network.chat; + +import java.awt.Image; +import org.junit.Test; +import org.junit.runner.JUnitCore; + +import misc.Bundle; +import misc.Config; +import misc.Controller; +import misc.Util; + +import network.Client; +import network.ProtocolManager; +import network.Server; +import network.login.Login; +import network.login.LoginManager; + +public class JChatTest { + + static public junit.framework.Test suite () { + return new junit.framework.JUnit4TestAdapter (JChatTest.class); + } + + static public void main (String args[]) { + JUnitCore.main ("network.chat.JChatTest"); + } + + public class Controller2 extends Controller { + public Controller2 () { super (""); } + protected void createModel (String s) {} + public String getTitle () { return null; } + public Image getIcon () { return null; } + }; + + // ======================================== + @Test public void launch () { + Config.setPWD (JChatTest.class); + Config.load ("Chat"); + Bundle.load ("Chat"); + + Controller controller = new Controller2 (); + + ProtocolManager serverProtocolManager = new ProtocolManager (controller); + ProtocolManager clientProtocolManager = new ProtocolManager (controller); + + Server server = new Server (8080); + Client client = new Client ("localhost", 8080); + + server.start (); + client.start (); + + serverProtocolManager.setProtocol (server); + clientProtocolManager.setProtocol (client); + + Login login1 = new Login (""); + Login login2 = new Login (""); + LoginManager loginManager1 = new LoginManager (controller, login1); + LoginManager loginManager2 = new LoginManager (controller, login2); + + + Util.newJFrame ("Serge", new JChat (serverProtocolManager, loginManager1, + new ChatManager (controller, new Chat ("Serge", server))), true); + Util.newJFrame ("Clement", new JChat (clientProtocolManager, loginManager2, + new ChatManager (controller, new Chat ("Clement", client))), true); + } + + // ======================================== +} diff --git a/src/javaTest/tool/SourceIteratorModelTest.java b/src/javaTest/tool/SourceIteratorModelTest.java new file mode 100644 index 0000000..b6075b2 --- /dev/null +++ b/src/javaTest/tool/SourceIteratorModelTest.java @@ -0,0 +1,38 @@ +package tool; + +import java.io.File; +import java.util.Vector; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.JUnitCore; + +import tool.model.SourceIteratorModel; + +public class SourceIteratorModelTest extends Assert { + + static public junit.framework.Test suite () { + return new junit.framework.JUnit4TestAdapter (SourceIteratorModelTest.class); + } + + static public void main (String args[]) { + JUnitCore.main ("tool.SourceIteratorModelTest"); + } + + // ======================================== + @Test public void parse () { + Vector files = new Vector (); + for (SourceIteratorModel si = new SourceIteratorModel (new File ("../src")); si.hasMoreJava (); ) { + File file = si.nextJava (); + files.add (file); + assertTrue (file.isFile ()); + } + for (SourceIteratorModel si = new SourceIteratorModel (new File ("../src")); si.hasMoreString (); ) { + String next = si.nextString (); + assertTrue (files.contains (si.getCurrentFile ())); + assertTrue (next.indexOf ("\"") >= 0); + } + } + + // ======================================== +} diff --git a/src/javaTest/tool/model/BundleManagerModelTest.java b/src/javaTest/tool/model/BundleManagerModelTest.java new file mode 100644 index 0000000..0f77bfe --- /dev/null +++ b/src/javaTest/tool/model/BundleManagerModelTest.java @@ -0,0 +1,34 @@ +package tool.model; + +import java.io.File; + +import org.junit.Test; +import org.junit.Ignore; +import org.junit.runner.JUnitCore; + +import misc.Bundle; + +public class BundleManagerModelTest { + + @Ignore ("for exemple") @Test (expected=IndexOutOfBoundsException.class, timeout=100) public void empty () { + new java.util.ArrayList ().get (0); + } + + static public junit.framework.Test suite () { + return new junit.framework.JUnit4TestAdapter (BundleManagerModelTest.class); + } + + static public void main (String args[]) { + JUnitCore.main ("tool.BundleManagerModelTest"); + } + + // ======================================== + @Test public void copy () { + Bundle.load ("BundleManager"); + BundleManagerModel bmm = new BundleManagerModel (); + bmm.open (new File ("data/config"), "BundleManager", false); + bmm.saveAs (new File ("/tmp"), "BundleManager"); + } + + // ======================================== +} diff --git a/ws/bundle.sh b/ws/bundle.sh new file mode 100755 index 0000000..038314c --- /dev/null +++ b/ws/bundle.sh @@ -0,0 +1,2 @@ +cd `dirname "$0"` +ant -f ../ant/build.xml runBundleJar diff --git a/ws/bundleInclusion.sh b/ws/bundleInclusion.sh new file mode 100755 index 0000000..05e0b68 --- /dev/null +++ b/ws/bundleInclusion.sh @@ -0,0 +1,49 @@ +usage () { + echo `basename "$0"` " [-h] [-help] bundleRefDir bundleDir" + echo " -h" + echo " -help Display this help." +} + +case "$1" in + '-' ) + shift;; + '-v' ) + SVN_OPT="" + shift;; + '-h' | '-help' ) + usage + shift + exit;; +esac + +curDir=`pwd` +cd $1 +refDir=`pwd` +# cd ${curDir} +# cd $2 +# bundleDir=`pwd` + +# cd "${refDir}" +for refFile in *.properties +do + if test -f "${refFile}" -a ! -L "${refFile}" + then + cd "${refDir}" + grep -v '^#' ${refFile} | cut -d = -f 1 | sort -u | { + while read key + do + #cd "${bundleDir}" + for bundleFile in $2 + do + if test -f "${bundleFile}" -a ! -L "${bundleFile}" + then + if grep -q "^${key}=" "${bundleFile}" + then + echo "${bundleFile}: ${key} already in ${refFile}" + fi + fi + done + done + } + fi +done diff --git a/ws/chat.sh b/ws/chat.sh new file mode 100755 index 0000000..f498c2c --- /dev/null +++ b/ws/chat.sh @@ -0,0 +1,2 @@ +cd `dirname "$0"` +ant -f ../ant/build.xml runChatJar diff --git a/ws/login.sh b/ws/login.sh new file mode 100755 index 0000000..472e3a4 --- /dev/null +++ b/ws/login.sh @@ -0,0 +1,2 @@ +cd `dirname "$0"` +ant -f ../ant/build.xml runLoginJar diff --git a/ws/start_chat.sh b/ws/start_chat.sh new file mode 100755 index 0000000..76aaa3a --- /dev/null +++ b/ws/start_chat.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# Title : start_dodwand.sh +# Role : script for launching the DoDWAN daemon + +# Syntax : ./start_dodwand.sh [-Dkey=value ...] + +DODWAN_VERSION=1.0.1 + +DODWAN_ROOT=${HOME}/DoDWAN + + +if [ "${HOSTNAME}" == "" ] ; then + echo "Variable HOSTNAME not defined." + exit 1 +fi + +java -Ddodwan.root=${DODWAN_ROOT} \ + -Ddodwan.host=${HOSTNAME} \ + $* -cp ./dodwan-${DODWAN_VERSION}.jar:Chat.jar -Ddodwan.announce_period=1 network.chat.LaunchChat