From 8ef973313b99aa312368b6a1875661f870b94aca Mon Sep 17 00:00:00 2001 From: Sean Qureshi Date: Mon, 10 Nov 2014 23:27:18 -0800 Subject: [PATCH 001/253] Add files used to generate OS X pkgs --- res/gplv3.rtf | 154 ++++++++++++++++++++++++++++++++++++++++++++++++ res/makedist.sh | 26 ++++++++ res/welcome.txt | 3 + 3 files changed, 183 insertions(+) create mode 100644 res/gplv3.rtf create mode 100644 res/makedist.sh create mode 100644 res/welcome.txt diff --git a/res/gplv3.rtf b/res/gplv3.rtf new file mode 100644 index 000000000..4b9186427 --- /dev/null +++ b/res/gplv3.rtf @@ -0,0 +1,154 @@ +{\rtf1\ansi\deff0\adeflang1025 +{\fonttbl{\f0\froman\fprq2\fcharset0 Times New Roman;}{\f1\froman\fprq2\fcharset0 Times New Roman;}{\f2\fswiss\fprq2\fcharset0 Arial;}{\f3\fnil\fprq2\fcharset0 Arial Unicode MS;}{\f4\fnil\fprq2\fcharset0 MS Mincho;}{\f5\fnil\fprq2\fcharset0 Tahoma;}{\f6\fnil\fprq0\fcharset0 Tahoma;}} +{\colortbl;\red0\green0\blue0;\red128\green128\blue128;} +{\stylesheet{\s1\cf0{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\rtlch\af5\afs24\lang255\ltrch\dbch\af3\langfe255\hich\f0\fs24\lang1035\loch\f0\fs24\lang1035\snext1 Normal;} +{\s2\sb240\sa120\keepn\cf0{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\rtlch\afs28\lang255\ltrch\dbch\af4\langfe255\hich\f2\fs28\lang1035\loch\f2\fs28\lang1035\sbasedon1\snext3 Heading;} +{\s3\sa120\cf0{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\rtlch\af5\afs24\lang255\ltrch\dbch\af3\langfe255\hich\f0\fs24\lang1035\loch\f0\fs24\lang1035\sbasedon1\snext3 Body Text;} +{\s4\sa120\cf0{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\rtlch\af6\afs24\lang255\ltrch\dbch\af3\langfe255\hich\f0\fs24\lang1035\loch\f0\fs24\lang1035\sbasedon3\snext4 List;} +{\s5\sb120\sa120\cf0{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\rtlch\af6\afs24\lang255\ai\ltrch\dbch\af3\langfe255\hich\f0\fs24\lang1035\i\loch\f0\fs24\lang1035\i\sbasedon1\snext5 caption;} +{\s6\cf0{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\rtlch\af6\afs24\lang255\ltrch\dbch\af3\langfe255\hich\f0\fs24\lang1035\loch\f0\fs24\lang1035\sbasedon1\snext6 Index;} +} +{\info{\author Kimmo Varis}{\creatim\yr2010\mo1\dy17\hr1\min15}{\revtim\yr0\mo0\dy0\hr0\min0}{\printim\yr0\mo0\dy0\hr0\min0}{\comment StarWriter}{\vern3100}}\deftab709 +{\*\pgdsctbl +{\pgdsc0\pgdscuse195\pgwsxn12240\pghsxn15840\marglsxn1134\margrsxn1134\margtsxn1134\margbsxn1134\pgdscnxt0 Standard;}} +\paperh15840\paperw12240\margl1134\margr1134\margt1134\margb1134\sectd\sbknone\pgwsxn12240\pghsxn15840\marglsxn1134\margrsxn1134\margtsxn1134\margbsxn1134\ftnbj\ftnstart1\ftnrstcont\ftnnar\aenddoc\aftnrstcont\aftnstart1\aftnnrlc +\pard\plain \ltrpar\s1\cf0{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\rtlch\af5\afs24\lang255\ltrch\dbch\af3\langfe255\hich\f0\fs24\lang1035\loch\f0\fs24\lang1035 {\rtlch \ltrch\loch }{\rtlch \ltrch\loch\f0\fs24\lang1035\i0\b0 GNU GENERAL PUBLIC LICENSE\line Version 3, 29 June 2007\line \line Copyright (C) 2007 Free Software Foundation, Inc. \line Everyone is permitted to copy and distribute verbatim copies\line of this license document, but + changing it is not allowed.\line \line Preamble\line \line The GNU General Public License is a free, copyleft license for\line software and other kinds of works.\line \line The licenses for most software and other practical works are designed\line to take away yo +ur freedom to share and change the works. By contrast,\line the GNU General Public License is intended to guarantee your freedom to\line share and change all versions of a program--to make sure it remains free\line software for all its users. We, the Free Software Foun +dation, use the\line GNU General Public License for most of our software; it applies also to\line any other work released this way by its authors. You can apply it to\line your programs, too.\line \line When we speak of free software, we are referring to freedom, not\line price. Ou +r General Public Licenses are designed to make sure that you\line have the freedom to distribute copies of free software (and charge for\line them if you wish), that you receive source code or can get it if you\line want it, that you can change the software or use pieces + of it in new\line free programs, and that you know you can do these things.\line \line To protect your rights, we need to prevent others from denying you\line these rights or asking you to surrender the rights. Therefore, you have\line certain responsibilities if you distribut +e copies of the software, or if\line you modify it: responsibilities to respect the freedom of others.\line \line For example, if you distribute copies of such a program, whether\line gratis or for a fee, you must pass on to the recipients the same\line freedoms that you receive +d. You must make sure that they, too, receive\line or can get the source code. And you must show them these terms so they\line know their rights.\line \line Developers that use the GNU GPL protect your rights with two steps:\line (1) assert copyright on the software, and (2) o +ffer you this License\line giving you legal permission to copy, distribute and/or modify it.\line \line For the developers' and authors' protection, the GPL clearly explains\line that there is no warranty for this free software. For both users' and\line authors' sake, the GPL r +equires that modified versions be marked as\line changed, so that their problems will not be attributed erroneously to\line authors of previous versions.\line \line Some devices are designed to deny users access to install or run\line modified versions of the software inside the +m, although the manufacturer\line can do so. This is fundamentally incompatible with the aim of\line protecting users' freedom to change the software. The systematic\line pattern of such abuse occurs in the area of products for individuals to\line use, which is precisely wh +ere it is most unacceptable. Therefore, we\line have designed this version of the GPL to prohibit the practice for those\line products. If such problems arise substantially in other domains, we\line stand ready to extend this provision to those domains in future versio +ns\line of the GPL, as needed to protect the freedom of users.\line \line Finally, every program is threatened constantly by software patents.\line States should not allow patents to restrict development and use of\line software on general-purpose computers, but in those that do +, we wish to\line avoid the special danger that patents applied to a free program could\line make it effectively proprietary. To prevent this, the GPL assures that\line patents cannot be used to render the program non-free.\line \line The precise terms and conditions for copyin +g, distribution and\line modification follow.\line \line TERMS AND CONDITIONS\line \line 0. Definitions.\line \line "This License" refers to version 3 of the GNU General Public License.\line \line "Copyright" also means copyright-like laws that apply to other kinds of\line wor +ks, such as semiconductor masks.\line \line "The Program" refers to any copyrightable work licensed under this\line License. Each licensee is addressed as "you". "Licensees" and\line "recipients" may be individuals or organizations.\line \line To "modify" a work means to copy fro +m or adapt all or part of the work\line in a fashion requiring copyright permission, other than the making of an\line exact copy. The resulting work is called a "modified version" of the\line earlier work or a work "based on" the earlier work.\line \line A "covered work" means +either the unmodified Program or a work based\line on the Program.\line \line To "propagate" a work means to do anything with it that, without\line permission, would make you directly or secondarily liable for\line infringement under applicable copyright law, except executing it + on a\line computer or modifying a private copy. Propagation includes copying,\line distribution (with or without modification), making available to the\line public, and in some countries other activities as well.\line \line To "convey" a work means any kind of propagation that + enables other\line parties to make or receive copies. Mere interaction with a user through\line a computer network, with no transfer of a copy, is not conveying.\line \line An interactive user interface displays "Appropriate Legal Notices"\line to the extent that it includes a + convenient and prominently visible\line feature that (1) displays an appropriate copyright notice, and (2)\line tells the user that there is no warranty for the work (except to the\line extent that warranties are provided), that licensees may convey the\line work under this +License, and how to view a copy of this License. If\line the interface presents a list of user commands or options, such as a\line menu, a prominent item in the list meets this criterion.\line \line 1. Source Code.\line \line The "source code" for a work means the preferred form o +f the work\line for making modifications to it. "Object code" means any non-source\line form of a work.\line \line A "Standard Interface" means an interface that either is an official\line standard defined by a recognized standards body, or, in the case of\line interfaces specified +for a particular programming language, one that\line is widely used among developers working in that language.\line \line The "System Libraries" of an executable work include anything, other\line than the work as a whole, that (a) is included in the normal form of\line packaging + a Major Component, but which is not part of that Major\line Component, and (b) serves only to enable use of the work with that\line Major Component, or to implement a Standard Interface for which an\line implementation is available to the public in source code form. A\line +"Major Component", in this context, means a major essential component\line (kernel, window system, and so on) of the specific operating system\line (if any) on which the executable work runs, or a compiler used to\line produce the work, or an object code interpreter used + to run it.\line \line The "Corresponding Source" for a work in object code form means all\line the source code needed to generate, install, and (for an executable\line work) run the object code and to modify the work, including scripts to\line control those activities. However +, it does not include the work's\line System Libraries, or general-purpose tools or generally available free\line programs which are used unmodified in performing those activities but\line which are not part of the work. For example, Corresponding Source\line includes interf +ace definition files associated with source files for\line the work, and the source code for shared libraries and dynamically\line linked subprograms that the work is specifically designed to require,\line such as by intimate data communication or control flow between th +ose\line subprograms and other parts of the work.\line \line The Corresponding Source need not include anything that users\line can regenerate automatically from other parts of the Corresponding\line Source.\line \line The Corresponding Source for a work in source code form is that\line same + work.\line \line 2. Basic Permissions.\line \line All rights granted under this License are granted for the term of\line copyright on the Program, and are irrevocable provided the stated\line conditions are met. This License explicitly affirms your unlimited\line permission to run the + unmodified Program. The output from running a\line covered work is covered by this License only if the output, given its\line content, constitutes a covered work. This License acknowledges your\line rights of fair use or other equivalent, as provided by copyright law. +\line \line You may make, run and propagate covered works that you do not\line convey, without conditions so long as your license otherwise remains\line in force. You may convey covered works to others for the sole purpose\line of having them make modifications exclusively for +you, or provide you\line with facilities for running those works, provided that you comply with\line the terms of this License in conveying all material for which you do\line not control copyright. Those thus making or running the covered works\line for you must do so exclus +ively on your behalf, under your direction\line and control, on terms that prohibit them from making any copies of\line your copyrighted material outside their relationship with you.\line \line Conveying under any other circumstances is permitted solely under\line the conditions + stated below. Sublicensing is not allowed; section 10\line makes it unnecessary.\line \line 3. Protecting Users' Legal Rights From Anti-Circumvention Law.\line \line No covered work shall be deemed part of an effective technological\line measure under any applicable law fulfillin +g obligations under article\line 11 of the WIPO copyright treaty adopted on 20 December 1996, or\line similar laws prohibiting or restricting circumvention of such\line measures.\line \line When you convey a covered work, you waive any legal power to forbid\line circumvention of tech +nological measures to the extent such circumvention\line is effected by exercising rights under this License with respect to\line the covered work, and you disclaim any intention to limit operation or\line modification of the work as a means of enforcing, against the wor +k's\line users, your or third parties' legal rights to forbid circumvention of\line technological measures.\line \line 4. Conveying Verbatim Copies.\line \line You may convey verbatim copies of the Program's} +\par \pard\plain \ltrpar\s1\cf0{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\rtlch\af5\afs24\lang255\ltrch\dbch\af3\langfe255\hich\f0\fs24\lang1035\loch\f0\fs24\lang1035 {\rtlch \ltrch\loch }{\rtlch \ltrch\loch\f0\fs24\lang1035\i0\b0 source code as you\line receive it, in any medium, provided that you conspicuously and\line appropriately publish on each copy an appropriate copyright notice;\line keep intact all notices stating that this License and any\line non-permissive terms added in accord with secti +on 7 apply to the code;\line keep intact all notices of the absence of any warranty; and give all\line recipients a copy of this License along with the Program.\line \line You may charge any price or no price for each copy that you convey,\line and you may offer support or warra +nty protection for a fee.\line \line 5. Conveying Modified Source Versions.\line \line You may convey a work based on the Program, or the modifications to\line produce it from the Program, in the form of source code under the\line terms of section 4, provided that you also meet all + of these conditions:\line \line a) The work must carry prominent notices stating that you modified\line it, and giving a relevant date.\line \line b) The work must carry prominent notices stating that it is\line released under this License and any conditions added unde +r section\line 7. This requirement modifies the requirement in section 4 to\line "keep intact all notices".\line \line c) You must license the entire work, as a whole, under this\line License to anyone who comes into possession of a copy. This\line License will th +erefore apply, along with any applicable section 7\line additional terms, to the whole of the work, and all its parts,\line regardless of how they are packaged. This License gives no\line permission to license the work in any other way, but it does not\line i +nvalidate such permission if you have separately received it.\line \line d) If the work has interactive user interfaces, each must display\line Appropriate Legal Notices; however, if the Program has interactive\line interfaces that do not display Appropriate Legal + Notices, your\line work need not make them do so.\line \line A compilation of a covered work with other separate and independent\line works, which are not by their nature extensions of the covered work,\line and which are not combined with it such as to form a larger progra +m,\line in or on a volume of a storage or distribution medium, is called an\line "aggregate" if the compilation and its resulting copyright are not\line used to limit the access or legal rights of the compilation's users\line beyond what the individual works permit. Inclusio +n of a covered work\line in an aggregate does not cause this License to apply to the other\line parts of the aggregate.\line \line 6. Conveying Non-Source Forms.\line \line You may convey a covered work in object code form under the terms\line of sections 4 and 5, provided that you also + convey the\line machine-readable Corresponding Source under the terms of this License,\line in one of these ways:\line \line a) Convey the object code in, or embodied in, a physical product\line (including a physical distribution medium), accompanied by the\line Correspond +ing Source fixed on a durable physical medium\line customarily used for software interchange.\line \line b) Convey the object code in, or embodied in, a physical product\line (including a physical distribution medium), accompanied by a\line written offer, valid for + at least three years and valid for as\line long as you offer spare parts or customer support for that product\line model, to give anyone who possesses the object code either (1) a\line copy of the Corresponding Source for all the software in the\line product +that is covered by this License, on a durable physical\line medium customarily used for software interchange, for a price no\line more than your reasonable cost of physically performing this\line conveying of source, or (2) access to copy the\line Correspondin +g Source from a network server at no charge.\line \line c) Convey individual copies of the object code with a copy of the\line written offer to provide the Corresponding Source. This\line alternative is allowed only occasionally and noncommercially, and\line only +if you received the object code with such an offer, in accord\line with subsection 6b.\line \line d) Convey the object code by offering access from a designated\line place (gratis or for a charge), and offer equivalent access to the\line Corresponding Source in the + same way through the same place at no\line further charge. You need not require recipients to copy the\line Corresponding Source along with the object code. If the place to\line copy the object code is a network server, the Corresponding Source\line may be +on a different server (operated by you or a third party)\line that supports equivalent copying facilities, provided you maintain\line clear directions next to the object code saying where to find the\line Corresponding Source. Regardless of what server hosts + the\line Corresponding Source, you remain obligated to ensure that it is\line available for as long as needed to satisfy these requirements.\line \line e) Convey the object code using peer-to-peer transmission, provided\line you inform other peers where the object + code and Corresponding\line Source of the work are being offered to the general public at no\line charge under subsection 6d.\line \line A separable portion of the object code, whose source code is excluded\line from the Corresponding Source as a System Library, need no +t be\line included in conveying the object code work.\line \line A "User Product" is either (1) a "consumer product", which means any\line tangible personal property which is normally used for personal, family,\line or household purposes, or (2) anything designed or sold for inc +orporation\line into a dwelling. In determining whether a product is a consumer product,\line doubtful cases shall be resolved in favor of coverage. For a particular\line product received by a particular user, "normally used" refers to a\line typical or common use of that c +lass of product, regardless of the status\line of the particular user or of the way in which the particular user\line actually uses, or expects or is expected to use, the product. A product\line is a consumer product regardless of whether the product has substantial\line com +mercial, industrial or non-consumer uses, unless such uses represent\line the only significant mode of use of the product.\line \line "Installation Information" for a User Product means any methods,\line procedures, authorization keys, or other information required to insta +ll\line and execute modified versions of a covered work in that User Product from\line a modified version of its Corresponding Source. The information must\line suffice to ensure that the continued functioning of the modified object\line code is in no case prevented or inter +fered with solely because\line modification has been made.\line \line If you convey an object code work under this section in, or with, or\line specifically for use in, a User Product, and the conveying occurs as\line part of a transaction in which the right of possession and us +e of the\line User Product is transferred to the recipient in perpetuity or for a\line fixed term (regardless of how the transaction is characterized), the\line Corresponding Source conveyed under this section must be accompanied\line by the Installation Information. But thi +s requirement does not apply\line if neither you nor any third party retains the ability to install\line modified object code on the User Product (for example, the work has\line been installed in ROM).\line \line The requirement to provide Installation Information does not inclu +de a\line requirement to continue to provide support service, warranty, or updates\line for a work that has been modified or installed by the recipient, or for\line the User Product in which it has been modified or installed. Access to a\line network may be denied when the m +odification itself materially and\line adversely affects the operation of the network or violates the rules and\line protocols for communication across the network.\line \line Corresponding Source conveyed, and Installation Information provided,\line in accord with this section +must be in a format that is publicly\line documented (and with an implementation available to the public in\line source code form), and must require no special password or key for\line unpacking, reading or copying.\line \line 7. Additional Terms.\line \line "Additional permissions" are + terms that supplement the terms of this\line License by making exceptions from one or more of its conditions.\line Additional permissions that are applicable to the entire Program shall\line be treated as though they were included in this License, to the extent\line that the +y are valid under applicable law. If additional permissions\line apply only to part of the Program, that part may be used separately\line under those permissions, but the entire Program remains governed by\line this License without regard to the additional permissions.\line +\line When you convey a copy of a covered work, you may at your option\line remove any additional permissions from that copy, or from any part of\line it. (Additional permissions may be written to require their own\line removal in certain cases when you modify the work.) +You may place\line additional permissions on material, added by you to a covered work,\line for which you have or can give appropriate copyright permission.\line \line Notwithstanding any other provision of this License, for material you\line add to a covered work, you may (if a +uthorized by the copyright holders of\line that material) supplement the terms of this License with terms:\line \line a) Disclaiming warranty or limiting liability differently from the\line terms of sections 15 and 16 of this License; or\line \line b) Requiring preservation + of specified reasonable legal notices or\line author attributions in that material or in the Appropriate Legal\line Notices displayed by works containing it; or\line \line c) Prohibiting misrepresentation of the origin of that material, or\line requiring that modi +fied versions of such material be marked in\line reasonable ways as different from the original version; or\line \line d) Limiting the use for publicity purposes of names of licensors or\line authors of the material; or\line \line e) Declining to grant rights under trad +emark law for use of some\line trade names, trademarks, or service marks; or\line \line f) Requiring indemnification of licensors and authors of that\line material by anyone who conveys} +\par \pard\plain \ltrpar\s1\cf0{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\rtlch\af5\afs24\lang255\ltrch\dbch\af3\langfe255\hich\f0\fs24\lang1035\loch\f0\fs24\lang1035 {\rtlch \ltrch\loch }{\rtlch \ltrch\loch\f0\fs24\lang1035\i0\b0 the material (or modified versions of\line it) with contractual assumptions of liability to the recipient, for\line any liability that these contractual assumptions directly impose on\line those licensors and authors.\line \line All other non-permissive additional t +erms are considered "further\line restrictions" within the meaning of section 10. If the Program as you\line received it, or any part of it, contains a notice stating that it is\line governed by this License along with a term that is a further\line restriction, you may remov +e that term. If a license document contains\line a further restriction but permits relicensing or conveying under this\line License, you may add to a covered work material governed by the terms\line of that license document, provided that the further restriction does\line no +t survive such relicensing or conveying.\line \line If you add terms to a covered work in accord with this section, you\line must place, in the relevant source files, a statement of the\line additional terms that apply to those files, or a notice indicating\line where to find th +e applicable terms.\line \line Additional terms, permissive or non-permissive, may be stated in the\line form of a separately written license, or stated as exceptions;\line the above requirements apply either way.\line \line 8. Termination.\line \line You may not propagate or modify a cove +red work except as expressly\line provided under this License. Any attempt otherwise to propagate or\line modify it is void, and will automatically terminate your rights under\line this License (including any patent licenses granted under the third\line paragraph of section +11).\line \line However, if you cease all violation of this License, then your\line license from a particular copyright holder is reinstated (a)\line provisionally, unless and until the copyright holder explicitly and\line finally terminates your license, and (b) permanently, if + the copyright\line holder fails to notify you of the violation by some reasonable means\line prior to 60 days after the cessation.\line \line Moreover, your license from a particular copyright holder is\line reinstated permanently if the copyright holder notifies you of the\line vio +lation by some reasonable means, this is the first time you have\line received notice of violation of this License (for any work) from that\line copyright holder, and you cure the violation prior to 30 days after\line your receipt of the notice.\line \line Termination of your ri +ghts under this section does not terminate the\line licenses of parties who have received copies or rights from you under\line this License. If your rights have been terminated and not permanently\line reinstated, you do not qualify to receive new licenses for the same\line +material under section 10.\line \line 9. Acceptance Not Required for Having Copies.\line \line You are not required to accept this License in order to receive or\line run a copy of the Program. Ancillary propagation of a covered work\line occurring solely as a consequence of using + peer-to-peer transmission\line to receive a copy likewise does not require acceptance. However,\line nothing other than this License grants you permission to propagate or\line modify any covered work. These actions infringe copyright if you do\line not accept this License. + Therefore, by modifying or propagating a\line covered work, you indicate your acceptance of this License to do so.\line \line 10. Automatic Licensing of Downstream Recipients.\line \line Each time you convey a covered work, the recipient automatically\line receives a license from + the original licensors, to run, modify and\line propagate that work, subject to this License. You are not responsible\line for enforcing compliance by third parties with this License.\line \line An "entity transaction" is a transaction transferring control of an\line organizat +ion, or substantially all assets of one, or subdividing an\line organization, or merging organizations. If propagation of a covered\line work results from an entity transaction, each party to that\line transaction who receives a copy of the work also receives whatever\line l +icenses to the work the party's predecessor in interest had or could\line give under the previous paragraph, plus a right to possession of the\line Corresponding Source of the work from the predecessor in interest, if\line the predecessor has it or can get it with reason +able efforts.\line \line You may not impose any further restrictions on the exercise of the\line rights granted or affirmed under this License. For example, you may\line not impose a license fee, royalty, or other charge for exercise of\line rights granted under this License, a +nd you may not initiate litigation\line (including a cross-claim or counterclaim in a lawsuit) alleging that\line any patent claim is infringed by making, using, selling, offering for\line sale, or importing the Program or any portion of it.\line \line 11. Patents.\line \line A "contrib +utor" is a copyright holder who authorizes use under this\line License of the Program or a work on which the Program is based. The\line work thus licensed is called the contributor's "contributor version".\line \line A contributor's "essential patent claims" are all patent + claims\line owned or controlled by the contributor, whether already acquired or\line hereafter acquired, that would be infringed by some manner, permitted\line by this License, of making, using, or selling its contributor version,\line but do not include claims that would be + infringed only as a\line consequence of further modification of the contributor version. For\line purposes of this definition, "control" includes the right to grant\line patent sublicenses in a manner consistent with the requirements of\line this License.\line \line Each contributo +r grants you a non-exclusive, worldwide, royalty-free\line patent license under the contributor's essential patent claims, to\line make, use, sell, offer for sale, import and otherwise run, modify and\line propagate the contents of its contributor version.\line \line In the foll +owing three paragraphs, a "patent license" is any express\line agreement or commitment, however denominated, not to enforce a patent\line (such as an express permission to practice a patent or covenant not to\line sue for patent infringement). To "grant" such a patent l +icense to a\line party means to make such an agreement or commitment not to enforce a\line patent against the party.\line \line If you convey a covered work, knowingly relying on a patent license,\line and the Corresponding Source of the work is not available for anyone\line to copy, + free of charge and under the terms of this License, through a\line publicly available network server or other readily accessible means,\line then you must either (1) cause the Corresponding Source to be so\line available, or (2) arrange to deprive yourself of the benefi +t of the\line patent license for this particular work, or (3) arrange, in a manner\line consistent with the requirements of this License, to extend the patent\line license to downstream recipients. "Knowingly relying" means you have\line actual knowledge that, but for the pa +tent license, your conveying the\line covered work in a country, or your recipient's use of the covered work\line in a country, would infringe one or more identifiable patents in that\line country that you have reason to believe are valid.\line \line If, pursuant to or in connec +tion with a single transaction or\line arrangement, you convey, or propagate by procuring conveyance of, a\line covered work, and grant a patent license to some of the parties\line receiving the covered work authorizing them to use, propagate, modify\line or convey a specific + copy of the covered work, then the patent license\line you grant is automatically extended to all recipients of the covered\line work and works based on it.\line \line A patent license is "discriminatory" if it does not include within\line the scope of its coverage, prohibits t +he exercise of, or is\line conditioned on the non-exercise of one or more of the rights that are\line specifically granted under this License. You may not convey a covered\line work if you are a party to an arrangement with a third party that is\line in the business of distr +ibuting software, under which you make payment\line to the third party based on the extent of your activity of conveying\line the work, and under which the third party grants, to any of the\line parties who would receive the covered work from you, a discriminatory\line patent + license (a) in connection with copies of the covered work\line conveyed by you (or copies made from those copies), or (b) primarily\line for and in connection with specific products or compilations that\line contain the covered work, unless you entered into that arrange +ment,\line or that patent license was granted, prior to 28 March 2007.\line \line Nothing in this License shall be construed as excluding or limiting\line any implied license or other defenses to infringement that may\line otherwise be available to you under applicable patent la +w.\line \line 12. No Surrender of Others' Freedom.\line \line If conditions are imposed on you (whether by court order, agreement or\line otherwise) that contradict the conditions of this License, they do not\line excuse you from the conditions of this License. If you cannot conve +y a\line covered work so as to satisfy simultaneously your obligations under this\line License and any other pertinent obligations, then as a consequence you may\line not convey it at all. For example, if you agree to terms that obligate you\line to collect a royalty for fur +ther conveying from those to whom you convey\line the Program, the only way you could satisfy both those terms and this\line License would be to refrain entirely from conveying the Program.\line \line 13. Use with the GNU Affero General Public License.\line \line Notwithstanding an +y other provision of this License, you have\line permission to link or combine any covered work with a work licensed\line under version 3 of the GNU Affero General Public License into a single\line combined work, and to convey the resulting work. The terms of this\line Licen +se will continue to apply to the part which is the covered work,\line but the special requirements of the GNU Affero General Public License,\line section 13, concerning interaction through a network will apply to the\line combination as such.\line \line 14. Revised Versions of t +his License.\line \line The Free Software Foundation may publish revised and/or new versions of\line the GNU General Public License from time to time. Such new versions will\line be similar} +\par \pard\plain \ltrpar\s1\cf0{\*\hyphen2\hyphlead2\hyphtrail2\hyphmax0}\rtlch\af5\afs24\lang255\ltrch\dbch\af3\langfe255\hich\f0\fs24\lang1035\loch\f0\fs24\lang1035 {\rtlch \ltrch\loch }{\rtlch \ltrch\loch\f0\fs24\lang1035\i0\b0 in spirit to the present version, but may differ in detail to\line address new problems or concerns.\line \line Each version is given a distinguishing version number. If the\line Program specifies that a certain numbered version of the GNU General\line Public License "or any l +ater version" applies to it, you have the\line option of following the terms and conditions either of that numbered\line version or of any later version published by the Free Software\line Foundation. If the Program does not specify a version number of the\line GNU General P +ublic License, you may choose any version ever published\line by the Free Software Foundation.\line \line If the Program specifies that a proxy can decide which future\line versions of the GNU General Public License can be used, that proxy's\line public statement of acceptance o +f a version permanently authorizes you\line to choose that version for the Program.\line \line Later license versions may give you additional or different\line permissions. However, no additional obligations are imposed on any\line author or copyright holder as a result of your + choosing to follow a\line later version.\line \line 15. Disclaimer of Warranty.\line \line THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\line APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\line HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM +"AS IS" WITHOUT WARRANTY\line OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\line THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\line PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\line IS WITH YOU. + SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\line ALL NECESSARY SERVICING, REPAIR OR CORRECTION.\line \line 16. Limitation of Liability.\line \line IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\line WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PAR +TY WHO MODIFIES AND/OR CONVEYS\line THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\line GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\line USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\line DA +TA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\line PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\line EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\line SUCH DAMAGES.\line \line 17. Interpretation +of Sections 15 and 16.\line \line If the disclaimer of warranty and limitation of liability provided\line above cannot be given local legal effect according to their terms,\line reviewing courts shall apply local law that most closely approximates\line an absolute waiver of all +civil liability in connection with the\line Program, unless a warranty or assumption of liability accompanies a\line copy of the Program in return for a fee.\line \line END OF TERMS AND CONDITIONS\line \line How to Apply These Terms to Your New Programs +\line \line If you develop a new program, and you want it to be of the greatest\line possible use to the public, the best way to achieve this is to make it\line free software which everyone can redistribute and change under these terms.\line \line To do so, attach the following not +ices to the program. It is safest\line to attach them to the start of each source file to most effectively\line state the exclusion of warranty; and each file should have at least\line the "copyright" line and a pointer to where the full notice is found.\line \line \line Copyright (C) \line \line This program is free software: you can redistribute it and/or modify\line it under the terms of the GNU General Public License as published by\line +the Free Software Foundation, either version 3 of the License, or\line (at your option) any later version.\line \line This program is distributed in the hope that it will be useful,\line but WITHOUT ANY WARRANTY; without even the implied warranty of\line MERCHANTAB +ILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\line GNU General Public License for more details.\line \line You should have received a copy of the GNU General Public License\line along with this program. If not, see .\line \line Also add +information on how to contact you by electronic and paper mail.\line \line If the program does terminal interaction, make it output a short\line notice like this when it starts in an interactive mode:\line \line Copyright (C) \line This prog +ram comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\line This is free software, and you are welcome to redistribute it\line under certain conditions; type `show c' for details.\line \line The hypothetical commands `show w' and `show c' should show the ap +propriate\line parts of the General Public License. Of course, your program's commands\line might be different; for a GUI interface, you would use an "about box".\line \line You should also get your employer (if you work as a programmer) or school,\line if any, to sign a "copyr +ight disclaimer" for the program, if necessary.\line For more information on this, and how to apply and follow the GNU GPL, see\line .\line \line The GNU General Public License does not permit incorporating your program\line into proprietary program +s. If your program is a subroutine library, you\line may consider it more useful to permit linking proprietary applications with\line the library. If this is what you want to do, use the GNU Lesser General\line Public License instead of this License. But first, please + read\line .\line } +\par } diff --git a/res/makedist.sh b/res/makedist.sh new file mode 100644 index 000000000..767cc4e4f --- /dev/null +++ b/res/makedist.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +PWD=`pwd` + +echo " + + + + qTox + + + + + + + + + + + + + + + + qtox.pkg +" > distribution.xml diff --git a/res/welcome.txt b/res/welcome.txt new file mode 100644 index 000000000..13096863d --- /dev/null +++ b/res/welcome.txt @@ -0,0 +1,3 @@ +Welcome to the qTox for OS X internal nightly installer! + +Please report all bugs to https://support.libtoxcore.so From bb3416bdac5349420501936b571eac67020873fa Mon Sep 17 00:00:00 2001 From: Sean Date: Tue, 11 Nov 2014 00:04:00 -0800 Subject: [PATCH 002/253] Forgot the first rule of fight club --- res/makedist.sh | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/res/makedist.sh b/res/makedist.sh index 767cc4e4f..20e59e179 100644 --- a/res/makedist.sh +++ b/res/makedist.sh @@ -2,25 +2,25 @@ PWD=`pwd` -echo " - - - +echo " + + + qTox - - - + + + - - + + - + - - - + + + - qtox.pkg + qtox.pkg " > distribution.xml From 7c8a927773b65d46f35b0884fcff7a32ea463f55 Mon Sep 17 00:00:00 2001 From: Sean Date: Tue, 11 Nov 2014 01:01:21 -0800 Subject: [PATCH 003/253] The 2nd rule of fight club is actually the first rule --- res/makedist.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/makedist.sh b/res/makedist.sh index 20e59e179..76c2753e1 100644 --- a/res/makedist.sh +++ b/res/makedist.sh @@ -19,7 +19,7 @@ echo " - + qtox.pkg From 029d6cb5c50f4c135cb43347f9b9499ae69cc47a Mon Sep 17 00:00:00 2001 From: Sean Date: Tue, 11 Nov 2014 01:09:45 -0800 Subject: [PATCH 004/253] The 3rd rule sucks --- res/makedist.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/makedist.sh b/res/makedist.sh index 76c2753e1..977318510 100644 --- a/res/makedist.sh +++ b/res/makedist.sh @@ -19,7 +19,7 @@ echo " - + qtox.pkg From 59f9f9551b5df45ce69afc91364e5866057e63ce Mon Sep 17 00:00:00 2001 From: Sean Date: Tue, 11 Nov 2014 02:52:02 -0800 Subject: [PATCH 005/253] Enable local user installs --- res/makedist.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/res/makedist.sh b/res/makedist.sh index 977318510..8f6fa2145 100644 --- a/res/makedist.sh +++ b/res/makedist.sh @@ -9,6 +9,7 @@ echo " qTox + From 64f9a24ce6c4d441ec4d0622d7806c47bda4cbcc Mon Sep 17 00:00:00 2001 From: zero-one Date: Tue, 11 Nov 2014 15:58:20 -0800 Subject: [PATCH 006/253] fixes #207 Adds parsing for command-line arguments/flags --- src/main.cpp | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 6b3c6a386..889d64c30 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,11 +21,12 @@ #include "src/widget/toxsave.h" #include "src/autoupdate.h" #include -#include -#include -#include -#include +#include #include +#include +#include +#include +#include #include #include @@ -57,6 +58,15 @@ int main(int argc, char *argv[]) QApplication a(argc, argv); a.setApplicationName("qTox"); a.setOrganizationName("Tox"); + a.setApplicationVersion("\nGit commit: " + QString(GIT_VERSION)); + + // Process arguments + QCommandLineParser parser; + parser.setApplicationDescription("qTox, version: " + QString(GIT_VERSION) + "\nBuilt: " + __TIME__ + " " + __DATE__); + parser.addHelpOption(); + parser.addVersionOption(); + parser.addPositionalArgument("uri", QObject::tr("Tox URI to parse")); + parser.process(a); Settings::getInstance(); // Build our Settings singleton as soon as QApplication is ready, not before @@ -83,7 +93,7 @@ int main(int argc, char *argv[]) // Windows platform plugins DLL hell fix QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath()); a.addLibraryPath("platforms"); - + qDebug() << "built on: " << __TIME__ << __DATE__; qDebug() << "commit: " << GIT_VERSION << "\n"; @@ -101,10 +111,9 @@ int main(int argc, char *argv[]) ipc.registerEventHandler(&toxURIEventHandler); ipc.registerEventHandler(&toxSaveEventHandler); - // Process arguments - if (argc >= 2) + if (parser.positionalArguments().size() > 0) { - QString firstParam(argv[1]); + QString firstParam(parser.positionalArguments()[0]); // Tox URIs. If there's already another qTox instance running, we ask it to handle the URI and we exit // Otherwise we start a new qTox instance and process it ourselves if (firstParam.startsWith("tox:")) From 9eea6125d40db61e5011035c6a27a1daa2a9b42e Mon Sep 17 00:00:00 2001 From: agilob Date: Wed, 12 Nov 2014 11:42:26 +0000 Subject: [PATCH 007/253] fixes unreported bug in styles --- ui/settings/mainContent.css | 36 ++++-------------------------------- 1 file changed, 4 insertions(+), 32 deletions(-) diff --git a/ui/settings/mainContent.css b/ui/settings/mainContent.css index 22367b661..1b9d4bfee 100644 --- a/ui/settings/mainContent.css +++ b/ui/settings/mainContent.css @@ -18,16 +18,6 @@ QListWidget background: white; } -QScrollArea -{ - background: white; -} - -QGroupBox -{ - background: white; -} - QMessageBox { background: white; @@ -44,23 +34,6 @@ QSpinBox background: white; } -QSlider::groove:horizontal -{ - border: 1px solid #999999; - height: 8px; /* the groove expands to the size of the slider by default. by giving it a height, it has a fixed size */ - background: white; - margin: 2px 0; -} - -QSlider::handle:horizontal -{ - background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #b4b4b4, stop:1 #8f8f8f); - border: 1px solid #5c5c5c; - width: 18px; - margin: -2px 0; /* handle is placed by default on the contents rect of the groove. Expand outside the groove */ - border-radius: 3px; -} - QPushButton { background: white; @@ -71,6 +44,9 @@ QGroupBox color: black; background-color: white; font: @bigBold; + border: 2px solid gray; + border-radius: 5px; + margin-top: 2ex; /* leave space at the top for the title */ } QComboBox @@ -101,11 +77,7 @@ QTabBar QScrollArea { background-color: white; -} - -QScrollArea -{ - background: transparent; + background: transparent; } QScrollArea > QWidget > QWidget From ec2e19e4bbca54fe8dcc8b7490b0fd8a9e0d9716 Mon Sep 17 00:00:00 2001 From: apprb Date: Wed, 12 Nov 2014 22:13:46 +0900 Subject: [PATCH 008/253] split message fix --- src/widget/form/chatform.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/widget/form/chatform.cpp b/src/widget/form/chatform.cpp index 9850e4c36..7e52136ed 100644 --- a/src/widget/form/chatform.cpp +++ b/src/widget/form/chatform.cpp @@ -121,13 +121,13 @@ void ChatForm::onSendTriggered() int id = HistoryKeeper::getInstance()->addChatEntry(f->getToxID().publicKey, qt_msg_hist, Core::getInstance()->getSelfId().publicKey, timestamp, status); - MessageActionPtr ma = addSelfMessage(msg, isAction, timestamp, false); + MessageActionPtr ma = addSelfMessage(qt_msg, isAction, timestamp, false); int rec; if (isAction) - rec = Core::getInstance()->sendAction(f->getFriendID(), msg); + rec = Core::getInstance()->sendAction(f->getFriendID(), qt_msg); else - rec = Core::getInstance()->sendMessage(f->getFriendID(), msg); + rec = Core::getInstance()->sendMessage(f->getFriendID(), qt_msg); registerReceipt(rec, id, ma); } From 91f0542aef21c2d46f465fceda62e61f091cf1dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=83=D1=85=D0=B0=D1=80=D0=B8=D0=BA?= Date: Wed, 12 Nov 2014 21:28:38 +0300 Subject: [PATCH 009/253] update russian translation --- translations/ru.ts | 538 ++++++++++++++++++++++++++++++++------------- 1 file changed, 384 insertions(+), 154 deletions(-) diff --git a/translations/ru.ts b/translations/ru.ts index d3c173954..fb80af21a 100644 --- a/translations/ru.ts +++ b/translations/ru.ts @@ -70,146 +70,150 @@ AddFriendForm - + Add Friends Добавить друзей - + Tox ID Tox ID of the person you're sending a friend request to Tox ID - + Message The message you send in friend requests Сообщение - + Send friend request Мне не нравится, но другого не придумал, и фейсбук использует это Отправить запрос на добавление в друзья - + Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! Привет, добавите меня в друзья? - + Please fill in a valid Tox ID Tox ID of the friend you're sending a friend request to Пожалуйста, введите корректный Tox ID - + You can't add yourself as a friend! When trying to add your own Tox ID as friend Вы не можете добавить самого себя в друзья! - + + qTox needs to use the Tox DNS, but can't do it through a proxy. +Ignore the proxy and connect to the Internet directly ? + qTox необходимо воспользоваться Tox DNS, но это нельзя сделать через прокси. +Игнорировать прокси и подлючиться напрямую через интернет? + + + + This Tox ID does not exist + DNS error + Такого Tox ID не существует + + The connection timed out The DNS gives the Tox ID associated to toxme.se addresses - Время ожидания соединения истекло + Время ожидания соединения истекло - This address does not exist The DNS gives the Tox ID associated to toxme.se addresses - Нет такого адреса + Нет такого адреса - Error while looking up DNS The DNS gives the Tox ID associated to toxme.se addresses - Ошибка при просмотре DNS + Ошибка при просмотре DNS - No text record found Error with the DNS - Текстовых записей не найдено + Текстовых записей не найдено - Unexpected number of values in text record Error with the DNS - Непредвиденное количество значений в текстовой записи + Непредвиденное количество значений в текстовой записи - The version of Tox DNS used by this server is not supported Error with the DNS - Используемая сервером версия Tox DNS не поддерживается + Используемая сервером версия Tox DNS не поддерживается - The DNS lookup does not contain any Tox ID Error with the DNS - В ответе DNS ни одного Tox ID + В ответе DNS ни одного Tox ID - - The DNS lookup does not contain a valid Tox ID Error with the DNS - Ответ DNS не содержит корректных Tox ID + Ответ DNS не содержит корректных Tox ID ChatForm - + Load History... Загрузить историю... - + Send a file Отправить файл - + Bad Idea Плохая идея - + You're trying to send a special (sequential) file, that's not going to work! ...передаёте последовательный файл и получаете te-le-fun-ken. И переводчик работает по другой линии. По линии «Библиотека». Вы пытаетесь отправить специальный (последовательный) файл. Это так не работает! - + %1 calling %1 звонит - + %1 stopped calling %1 прекратил звонить - + Calling to %1 Звоним %1 - + Call rejected Звонок отклонён - + Call with %1 ended. %2 Разговор с %1 завершился. %2 - + Call duration: Длительность разговора: @@ -236,90 +240,95 @@ Пользователь qTox - + + Friend is already added + Друг уже добавлен + + + Encryption error Ошибка шифрования - + The .tox file is encrypted, but encryption was not checked, continuing regardless. Файл .tox зашифрован, однако шифрование в настройках включено не было. Продолжаем вопреки. - + Tox datafile decryption password Пароль для расшифровки файла данных Tox - - - + + + Password error Ошибка пароля - - + + Failed to setup password. Empty password. Не удалось установить пароль. Пустой пароль. - + Try Again Попробуйте ещё - + Change profile Сменить профиль - + Reinit current profile Увы, никто не знает, что разработчики имели в виду. Ту би транслейтед. Сбросить данные текущего профиля - + Wrong password has been entered Введён неправильный пароль - + History Log decryption password Пароль для расшифровки журнала переписки - + Encrypted log Зашифрованный журнал - + Your history is encrypted with different password Do you want to try another password? Ваша переписка зашифрована другим паролем Ходите попробовать другой пароль? - + Loggin Журналирование - + Due to incorret password logging will be disabled Из-за некорректного пароля журналирование будет отключено - + NO Password БЕЗ пароля - + Will be saved without encryption! Будет сохранено без шифрования! @@ -410,63 +419,79 @@ Do you want to try another password? FriendWidget - + Copy friend ID Menu to copy the Tox ID of that friend Копировать ID друга - + Auto accept files from this friend context menu entry Автоматически принимать файлы от этого друга - + Invite to group Menu to invite a friend to a groupchat Пригласить в группу - + + Set alias... + Псевдоним? Псевдоним. Ну в самом деле, нельзя же перевести как «кликуха», это же вежливый пользовательский интерфейс для приличных кругов. + Установить псевдоним... + + + Remove friend Menu to remove the friend from our friendlist Удалить друга - + Choose an auto accept directory popup title Выбрать папку для автоматического приёма + + + User alias + Псевдоним пользователя + + + + Alias: + Псевдоним: + GeneralForm - + General Общие - - + + None Отсутствует - + Choose an auto accept directory popup title Выбрать папку для автоматического приёма - + Call active popup title Идёт звонок - + You can't disconnect while a call is active! popup text Нельзя отключиться пока идёт звонок! @@ -486,93 +511,114 @@ Do you want to try another password? Перевод не изменится до перезапуска qTox. - + Close to tray Сворачивать в трей при закрытии - + Minimize to tray Сворачивать в трей - + + Check for updates on startup (unstable) + Проверять на наличие обновлений (нестабильно) + + + + Focus qTox when a message is received + Захватывать фокус при приёме сообщений + + + + Faux offline messaging + Как-бы оффлайновые сообщения + + + Auto away after (0 to disable) Менять статус на «Отошёл» после (0 — не менять) - + Set to 0 to disable Укажите 0, чтобы отключить - + Use emoticons Использовать смайлики - + Smiley Pack Text on smiley pack label Набор смайликов - + Style Стиль - + Emoticon size Размер смайликов - + px По аналогии с Мпикс. Хотя, может лучше принять http://ilyabirman.ru/meanwhile/all/px/? пикс - + Timestamp format Формат времени - + Connection Settings Настройки соединения - + Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Включить IPv6 (рекомендуется) - + + Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. + force tcp checkbox tooltip + Отключение позволяет, например, использовать Tox поверх Tor. Однако это добавляет нагрузку на сеть Tox, так что отключайте только в случае необходимости. + + + Enable UDP (recommended) Text on checkbox to disable UDP Включить UDP (рекомендуется) - + Use proxy (SOCKS5) Использовать прокси (SOCKS5) - + Address Text on proxy addr label Адрес - + Port Text on proxy port label Порт - + Reconnect reconnect button Переподключиться @@ -594,102 +640,101 @@ Do you want to try another password? Портативный режим - + Start in tray Запускать свёрнутым в трей - + Show contacts' status changes Показывать изменения статусов контактов - + Provided in minutes Выставлено в минутах - + minutes минут - + Autoaccept files Автоматически принимать файлы - + Save files in Сохранять в - + PushButton - + Theme Тема - This allows, e.g., toxing over Tor. It adds load to the Tox network however, so use only when necessary. force tcp checkbox tooltip - Это позволяет, например, использовать tox поверх Tor. Однако это добавляет нагрузку на сеть Tox, так что используйте только в случае необходимости. + Это позволяет, например, использовать tox поверх Tor. Однако это добавляет нагрузку на сеть Tox, так что используйте только в случае необходимости. GenericChatForm - + Send message Отправить сообщение - + Smileys Смайлики - + Send file(s) Отправить файл(ы) - + Audio call Позвонить - + Video call Видеозвонок - + Toggle speakers volume Включить или выключить звук - + Toggle microphone Включить или выключить микрофон - - + + Save chat log Сохранить журнал чата - + Clear displayed messages Очистить показываемые сообщения - + Cleared Очищено @@ -737,103 +782,123 @@ Do you want to try another password? Личность - + Call active popup title Идёт звонок - + You can't switch profiles while a call is active! popup text Вы не можете переключить профиль, пока идёт звонок! - + Rename "%1" renaming a profile Переименовать «%1» - + Profile already exists rename confirm title Профиль уже существует - + A profile named "%1" already exists. Do you want to erase it? rename confirm text Профиль с именем «%1» уже существует. Желаете его стереть? - + Export profile save dialog title Экспортировать профиль - + Tox save file (*.tox) save dialog filter Файл Tox (*.tox) - + + Failed to remove file + Не удалось удалить файл + + + + The file you chose to overwrite could not be removed first. + Файл, выбранный вами для перезаписи, не может быть предварительно удалён. + + + + Failed to copy file + Не удалось скопировать файл + + + + The file you chose could not be written to. + Выбранный файл не может быть записан. + + + Profile currently loaded current profile deletion warning title Профиль в настоящее время загружен - + This profile is currently in use. Please load a different profile before deleting this one. current profile deletion warning text Этот профиль используется. Загрузите другой профиль, прежде чем удалять этот. - + Deletion imminent! deletion confirmation title Необратимое удаление! - + Are you sure you want to delete this profile? deletion confirmation text Вы действительно хотите удалить этот профиль? - + Import profile import dialog title Импортировать профиль - + Tox save file (*.tox) import dialog filter Файл Tox (*.tox) - + Ignoring non-Tox file popup title Выбран не файл Tox - + Warning: you've chosen a file that is not a Tox save file; ignoring. popup text Внимание: вы выбрали не файл Tox; игнорирование. - + Profile already exists import confirm title Профиль уже существует - + A profile named "%1" already exists. Do you want to erase it? import confirm text Профиль с именем «%1» уже существует. Перезаписать? @@ -965,27 +1030,27 @@ Do you want to try another password? Ваш статус - + Add friends Добавить друзей - + Create a group chat Создать групповой чат - + View completed file transfers Завершённые передачи файлов - + Change your settings Изменить ваши настройки - + Close Закрыть @@ -1006,12 +1071,12 @@ Do you want to try another password? Конфиденциальность - + Encrypted log Зашифрованный журнал - + You already have history log file encrypted with different password Do you want to delete old history file? У вас уже есть файл журнала переписки, зашифрованный другим паролем @@ -1046,6 +1111,67 @@ Do you want to delete old history file? Шифровать историю + + QObject + + + Update + The title of a message box + Обновление + + + + An update is available, do you want to download it now ? +It will be installed when qTox restarts. + Обновление доступно, не желаете ли скачать его прамо сейчас? +Оно будет установлено после того, как qTox будет перезапущен. + + + + Ignoring non-Tox file + popup title + Выбран не файл Tox + + + + Warning: you've chosen a file that is not a Tox save file; ignoring. + popup text + Внимание: вы выбрали не файл Tox; игнорирование. + + + + Profile already exists + import confirm title + Профиль уже существует + + + + A profile named "%1" already exists. Do you want to erase it? + import confirm text + Профиль с именем «%1» уже существует. Желаете его стереть? + + + + Profile imported + Профиль импортирован + + + + %1.tox was successfully imported + %1.tox успешно импортирован + + + + Tox me maybe? + Default message in Tox URI friend requests. Write something appropriate! + Привет, добавите меня в друзья? + + + + Tox URI to parse + + + SetPasswordDialog @@ -1059,22 +1185,122 @@ Do you want to delete old history file? Повторите пароль + + ToxDNS + + + The connection timed out + The DNS gives the Tox ID associated to toxme.se addresses + Время ожидания соединения истекло + + + + This address does not exist + The DNS gives the Tox ID associated to toxme.se addresses + Нет такого адреса + + + + Error while looking up DNS + The DNS gives the Tox ID associated to toxme.se addresses + Ошибка при просмотре DNS + + + + No text record found + Error with the DNS + Текстовых записей не найдено + + + + Unexpected number of values in text record + Error with the DNS + Непредвиденное количество значений в текстовой записи + + + + The version of Tox DNS used by this server is not supported + Error with the DNS + Используемая сервером версия Tox DNS не поддерживается + + + + The DNS lookup does not contain any Tox ID + Error with the DNS + В ответе DNS ни одного Tox ID + + + + + The DNS lookup does not contain a valid Tox ID + Error with the DNS + Ответ DNS не содержит корректных Tox ID + + + + ToxURIDialog + + + Add a friend + Title of the window to add a friend through Tox URI + Добавить друга + + + + Do you want to add %1 as a friend ? + Хотите добавить %1 в друзья? + + + + User ID: + ID пользователя: + + + + Friend request message: + Текст запроса: + + + + Send + Send a friend request + Отправить + + + + Cancel + Don't send a friend request + Отмена + + Widget - online - в сети + в сети + + + away + отошёл + + + busy + занят + + + + Online + В сети - away - отошёл + Away + Отошёл - busy - занят + Busy + Занят @@ -1106,107 +1332,111 @@ Do you want to delete old history file? Занят - + Choose a profile Выберите профиль - + Please choose which identity to use Выберите личность, которую хотите использовать - + Choose a profile picture Выбрать картинку для профиля - - - + + + Error Ошибка - + Unable to open this file Невозможно открыть файл - + Unable to read this image Невозможно прочесть это изображение - + This image is too big Это изображение слишком большое - + Toxcore failed to start, the application will terminate after you close this message. Не удалось запустить toxcore, приложение будет завершено после того как вы закроете это сообщение. - + toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text Не удалось запустить toxcore с вашими настройками прокси, qTox не может работать; измените ваши настройки и перезапустите его. - + Add friend Добавить друга - + File transfers Передачи файлов - + Settings Настройки - + + Couldn't request friendship + Невозможно запросить дружбомагию + + + away contact status отсутствует - + busy contact status занят - + offline contact status офлайн - + online contact status в сети - + %1 is now %2 e.g. "Dubslow is now online" %1 сейчас %2 - + <Unknown> Placeholder when we don't know someone's name in a group chat <Неизвестный> - - + Message failed to send Не удалось отправить сообщение From 4bb91143d7288006dc3a46091f497c2e0f74e8ae Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Thu, 13 Nov 2014 00:13:11 +0100 Subject: [PATCH 010/253] Fix use after free of groupchat invites Fixes #728, fixes #706, fixes #394 --- src/core.cpp | 6 ++++-- src/core.h | 2 +- src/widget/widget.cpp | 4 ++-- src/widget/widget.h | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/core.cpp b/src/core.cpp index f9bbd2cf4..9e5d3e37a 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -481,15 +481,16 @@ void Core::onGroupAction(Tox*, int groupnumber, int peernumber, const uint8_t *a void Core::onGroupInvite(Tox*, int friendnumber, uint8_t type, const uint8_t *data, uint16_t length,void *core) { + QByteArray pk((char*)data, length); if (type == TOX_GROUPCHAT_TYPE_TEXT) { qDebug() << QString("Core: Text group invite by %1").arg(friendnumber); - emit static_cast(core)->groupInviteReceived(friendnumber,type,data,length); + emit static_cast(core)->groupInviteReceived(friendnumber,type,pk); } else if (type == TOX_GROUPCHAT_TYPE_AV) { qDebug() << QString("Core: AV group invite by %1").arg(friendnumber); - emit static_cast(core)->groupInviteReceived(friendnumber,type,data,length); + emit static_cast(core)->groupInviteReceived(friendnumber,type,pk); } else { @@ -1491,6 +1492,7 @@ int Core::joinGroupchat(int32_t friendnumber, uint8_t type, const uint8_t* frien else if (type == TOX_GROUPCHAT_TYPE_AV) { qDebug() << QString("Trying to join AV groupchat invite sent by friend %1").arg(friendnumber); + const_cast(friend_group_public_key)[2] = TOX_GROUPCHAT_TYPE_AV; return toxav_join_av_groupchat(tox, friendnumber, friend_group_public_key, length, playGroupAudio, const_cast(this)); } else diff --git a/src/core.h b/src/core.h index 0c393ebfc..2366c380a 100644 --- a/src/core.h +++ b/src/core.h @@ -145,7 +145,7 @@ signals: void friendLastSeenChanged(int friendId, const QDateTime& dateTime); void emptyGroupCreated(int groupnumber); - void groupInviteReceived(int friendnumber, uint8_t type, const uint8_t *group_public_key,uint16_t length); + void groupInviteReceived(int friendnumber, uint8_t type, QByteArray publicKey); void groupMessageReceived(int groupnumber, const QString& message, const QString& author, bool isAction); void groupNamelistChanged(int groupnumber, int peernumber, uint8_t change); diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index a7802bba1..ce183f705 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -872,11 +872,11 @@ void Widget::copyFriendIdToClipboard(int friendId) } } -void Widget::onGroupInviteReceived(int32_t friendId, uint8_t type, const uint8_t* publicKey,uint16_t length) +void Widget::onGroupInviteReceived(int32_t friendId, uint8_t type, QByteArray invite) { if (type == TOX_GROUPCHAT_TYPE_TEXT || type == TOX_GROUPCHAT_TYPE_AV) { - int groupId = core->joinGroupchat(friendId, type, publicKey,length); + int groupId = core->joinGroupchat(friendId, type, (uint8_t*)invite.data(), invite.length()); if (groupId < 0) { qWarning() << "Widget::onGroupInviteReceived: Unable to accept group invite"; diff --git a/src/widget/widget.h b/src/widget/widget.h index bccf24552..17c737ce2 100644 --- a/src/widget/widget.h +++ b/src/widget/widget.h @@ -112,7 +112,7 @@ private slots: void onFriendRequestReceived(const QString& userId, const QString& message); void onReceiptRecieved(int friendId, int receipt); void onEmptyGroupCreated(int groupId); - void onGroupInviteReceived(int32_t friendId, uint8_t type, const uint8_t *publicKey,uint16_t length); + void onGroupInviteReceived(int32_t friendId, uint8_t type, QByteArray invite); void onGroupMessageReceived(int groupnumber, const QString& message, const QString& author, bool isAction); void onGroupNamelistChanged(int groupnumber, int peernumber, uint8_t change); void removeFriend(int friendId); From ccd6cb79db68ce517f380d82517ab4bff5019a95 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Thu, 13 Nov 2014 00:18:11 +0100 Subject: [PATCH 011/253] Remove forgotten debug statement --- src/core.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core.cpp b/src/core.cpp index 9e5d3e37a..b23822668 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -1492,7 +1492,6 @@ int Core::joinGroupchat(int32_t friendnumber, uint8_t type, const uint8_t* frien else if (type == TOX_GROUPCHAT_TYPE_AV) { qDebug() << QString("Trying to join AV groupchat invite sent by friend %1").arg(friendnumber); - const_cast(friend_group_public_key)[2] = TOX_GROUPCHAT_TYPE_AV; return toxav_join_av_groupchat(tox, friendnumber, friend_group_public_key, length, playGroupAudio, const_cast(this)); } else From d87cf99d8fb786ba102b7ac86e96c0dad9a12523 Mon Sep 17 00:00:00 2001 From: dubslow Date: Wed, 12 Nov 2014 17:17:16 -0600 Subject: [PATCH 012/253] minor tweaks to autoaway, one new debug message --- src/misc/settings.cpp | 2 +- src/widget/widget.cpp | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/misc/settings.cpp b/src/misc/settings.cpp index 3c2deece4..d9c746a76 100644 --- a/src/misc/settings.cpp +++ b/src/misc/settings.cpp @@ -138,7 +138,7 @@ void Settings::load() smileyPack = s.value("smileyPack", ":/smileys/cylgom/emoticons.xml").toString(); customEmojiFont = s.value("customEmojiFont", true).toBool(); emojiFontFamily = s.value("emojiFontFamily", "DejaVu Sans").toString(); - emojiFontPointSize = s.value("emojiFontPointSize", QApplication::font().pointSize()).toInt(); + emojiFontPointSize = s.value("emojiFontPointSize", 12).toInt(); firstColumnHandlePos = s.value("firstColumnHandlePos", 50).toInt(); secondColumnHandlePosFromRight = s.value("secondColumnHandlePosFromRight", 50).toInt(); timestampFormat = s.value("timestampFormat", "hh:mm").toString(); diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index ce183f705..5b0f2afe7 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -171,6 +171,7 @@ void Widget::init() ui->statusButton->setEnabled(false); idleTimer = new QTimer(); + idleTimer->setSingleShot(true); int mins = Settings::getInstance().getAutoAwayTime(); if (mins > 0) idleTimer->start(mins * 1000*60); @@ -497,6 +498,7 @@ void Widget::onStatusSet(Status status) { case Status::Online: ui->statusButton->setProperty("status" ,"online"); + qDebug() << "Widget: something set the status to online"; break; case Status::Away: ui->statusButton->setProperty("status" ,"away"); @@ -1028,7 +1030,7 @@ bool Widget::event(QEvent * e) case QEvent::KeyRelease: if (autoAwayActive) { - qDebug() << "Widget: auto away deactivated"; + qDebug() << "Widget: auto away deactivated at" << QTime::currentTime().toString(); autoAwayActive = false; emit statusSet(Status::Online); int mins = Settings::getInstance().getAutoAwayTime(); @@ -1047,7 +1049,7 @@ void Widget::onUserAway() if (Settings::getInstance().getAutoAwayTime() > 0 && ui->statusButton->property("status").toString() == "online") // leave user-set statuses in place { - qDebug() << "Widget: auto away activated"; + qDebug() << "Widget: auto away activated" << QTime::currentTime().toString(); emit statusSet(Status::Away); autoAwayActive = true; } From 625c9792a213801789927f4459f36e9ea31a273d Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Thu, 13 Nov 2014 00:41:35 +0100 Subject: [PATCH 013/253] Update update-server tools Forgot to update them. But it turns out they are already multiplatform. --- tools/update-server/genflist.sh | 3 --- tools/update-server/genversion.sh | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) delete mode 100644 tools/update-server/genflist.sh diff --git a/tools/update-server/genflist.sh b/tools/update-server/genflist.sh deleted file mode 100644 index ee9492462..000000000 --- a/tools/update-server/genflist.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -./qtox-updater-genflist /var/www/html/qtox/win32/ - diff --git a/tools/update-server/genversion.sh b/tools/update-server/genversion.sh index 774397f00..61db4e354 100644 --- a/tools/update-server/genversion.sh +++ b/tools/update-server/genversion.sh @@ -1,4 +1,4 @@ #!/bin/bash -echo -n 1 > /var/www/html/qtox/win32/version -./qtox-updater-sign $1 >> /var/www/html/qtox/win32/version +echo -n 1 > version +./qtox-updater-sign $1 >> version From 232d30f51067299fab076e53cb7600ada1730bc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=83=D1=85=D0=B0=D1=80=D0=B8=D0=BA?= Date: Thu, 13 Nov 2014 03:33:52 +0300 Subject: [PATCH 014/253] fix something --- translations/ru.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/translations/ru.ts b/translations/ru.ts index fb80af21a..d8120fe12 100644 --- a/translations/ru.ts +++ b/translations/ru.ts @@ -462,7 +462,7 @@ Do you want to try another password? Alias: - Псевдоним: + Новый псевдоним: @@ -533,7 +533,7 @@ Do you want to try another password? Faux offline messaging - Как-бы оффлайновые сообщения + Имитация офлайнового обмена сообщениями @@ -1397,7 +1397,7 @@ It will be installed when qTox restarts. Couldn't request friendship - Невозможно запросить дружбомагию + Не удалось запросить добавление в друзья From c6cb49c99179045fa9f2ad0d9b1aa3096ef91a3f Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Thu, 13 Nov 2014 01:46:17 +0100 Subject: [PATCH 015/253] Fix #735 --- src/main.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 889d64c30..3fa5b0108 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -146,6 +146,11 @@ int main(int argc, char *argv[]) return EXIT_SUCCESS; } } + else + { + fprintf(stderr, "Invalid argument\n"); + return EXIT_FAILURE; + } } // Run From 09790c95a43ae25c4f439faf2ae1950c5d7d9d33 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Thu, 13 Nov 2014 11:00:02 +0100 Subject: [PATCH 016/253] simple_make: Install lupdate/lrelease with APT --- simple_make.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simple_make.sh b/simple_make.sh index cec381b86..35b1c6b8a 100755 --- a/simple_make.sh +++ b/simple_make.sh @@ -2,7 +2,7 @@ if which apt-get; then sudo apt-get install build-essential qt5-qmake qt5-default libopenal-dev libopencv-dev \ - libtool autotools-dev automake checkinstall check libopus-dev libvpx-dev + libtool autotools-dev automake checkinstall check libopus-dev libvpx-dev qttools5-dev-tools qtchooser elif which pacman; then sudo pacman -S --needed base-devel qt5 opencv openal opus libvpx elif which yum; then From 54462f0276401bfe9272fb6bad1d0e983952561b Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Thu, 13 Nov 2014 12:18:04 +0100 Subject: [PATCH 017/253] Prepare Core for av groupchats --- src/core.cpp | 18 ++++++++--- src/core.h | 4 +++ src/coreav.cpp | 84 +++++++++++++++++++++++++++++++++++++++++++++----- src/coreav.h | 12 +++++++- 4 files changed, 106 insertions(+), 12 deletions(-) diff --git a/src/core.cpp b/src/core.cpp index b23822668..8f3ecb38a 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -45,6 +45,7 @@ const QString Core::CONFIG_FILE_NAME = "data"; const QString Core::TOX_EXT = ".tox"; QList Core::fileSendQueue; QList Core::fileRecvQueue; +QHash Core::groupCalls; Core::Core(Camera* cam, QThread *coreThread, QString loadPath) : tox(nullptr), camera(cam), loadPath(loadPath), ready{false} @@ -95,12 +96,15 @@ Core::Core(Camera* cam, QThread *coreThread, QString loadPath) : } QString inDevDescr = Settings::getInstance().getInDev(); + int stereoFlag = av_DefaultSettings.audio_channels==1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16; if (inDevDescr.isEmpty()) - alInDev = alcCaptureOpenDevice(nullptr,av_DefaultSettings.audio_sample_rate, AL_FORMAT_MONO16, - (av_DefaultSettings.audio_frame_duration * av_DefaultSettings.audio_sample_rate * 4) / 1000); + alInDev = alcCaptureOpenDevice(nullptr,av_DefaultSettings.audio_sample_rate, stereoFlag, + (av_DefaultSettings.audio_frame_duration * av_DefaultSettings.audio_sample_rate * 4) + / 1000 * av_DefaultSettings.audio_channels); else - alInDev = alcCaptureOpenDevice(inDevDescr.toStdString().c_str(),av_DefaultSettings.audio_sample_rate, AL_FORMAT_MONO16, - (av_DefaultSettings.audio_frame_duration * av_DefaultSettings.audio_sample_rate * 4) / 1000); + alInDev = alcCaptureOpenDevice(inDevDescr.toStdString().c_str(),av_DefaultSettings.audio_sample_rate, stereoFlag, + (av_DefaultSettings.audio_frame_duration * av_DefaultSettings.audio_sample_rate * 4) + / 1000 * av_DefaultSettings.audio_channels); if (!alInDev) qWarning() << "Core: Cannot open input audio device"; } @@ -1632,11 +1636,17 @@ void Core::groupInviteFriend(int friendId, int groupId) void Core::createGroup(uint8_t type) { if (type == TOX_GROUPCHAT_TYPE_TEXT) + { emit emptyGroupCreated(tox_add_groupchat(tox)); + } else if (type == TOX_GROUPCHAT_TYPE_AV) + { emit emptyGroupCreated(toxav_add_av_groupchat(tox, playGroupAudio, this)); + } else + { qWarning() << "Core::createGroup: Unknown type "< fileSendQueue, fileRecvQueue; static ToxCall calls[]; + static QHash groupCalls; // Maps group IDs to ToxGroupCalls QMutex fileSendMutex, messageSendMutex; bool ready; diff --git a/src/coreav.cpp b/src/coreav.cpp index cf036dee9..62fa09a23 100644 --- a/src/coreav.cpp +++ b/src/coreav.cpp @@ -176,7 +176,6 @@ void Core::startCall(int friendId, bool video) emit avCallFailed(friendId); return; } - } } @@ -222,7 +221,7 @@ void Core::sendCallAudio(int callId, ToxAv* toxav) return; } - int framesize = (calls[callId].codecSettings.audio_frame_duration * calls[callId].codecSettings.audio_sample_rate) / 1000; + int framesize = (calls[callId].codecSettings.audio_frame_duration * calls[callId].codecSettings.audio_sample_rate) / 1000 * av_DefaultSettings.audio_channels; uint8_t buf[framesize*2], dest[framesize*2]; bool frame = false; @@ -578,7 +577,7 @@ void Core::playAudioBuffer(ALuint alSource, const int16_t *data, int samples, un if(state != AL_PLAYING) { alSourcePlay(alSource); - qDebug() << "Core: Starting audio source " << (int)alSource; + //qDebug() << "Core: Starting audio source " << (int)alSource; } } @@ -587,10 +586,81 @@ VideoSource *Core::getVideoSourceFromCall(int callNumber) return &calls[callNumber].videoSource; } -void Core::playGroupAudio(Tox* /*tox*/, int /*groupnumber*/, int /*friendgroupnumber*/, const int16_t* out_audio, +void Core::playGroupAudio(Tox* /*tox*/, int groupnumber, int /*friendgroupnumber*/, const int16_t* out_audio, unsigned out_audio_samples, uint8_t decoder_channels, unsigned audio_sample_rate, void* /*userdata*/) { - /// TODO: FIXME: Don't play groupchat audio on the main source! - /// We'll need some sort of call[] array but for groupchats, when that's done use this source - playAudioBuffer(alMainSource, out_audio, out_audio_samples, decoder_channels, audio_sample_rate); + if (!groupCalls[groupnumber].active) + return; + + playAudioBuffer(groupCalls[groupnumber].alSource, out_audio, out_audio_samples, decoder_channels, audio_sample_rate); +} + +void Core::joinGroupCall(int groupId, ToxAv *toxav) +{ + qDebug() << QString("Core: Joining group call %1").arg(groupId); + groupCalls[groupId].groupId = groupId; + groupCalls[groupId].muteMic = false; + groupCalls[groupId].muteVol = false; + // the following three lines are also now redundant from startCall, but are + // necessary there for outbound and here for inbound + groupCalls[groupId].codecSettings = av_DefaultSettings; + groupCalls[groupId].codecSettings.max_video_width = TOXAV_MAX_VIDEO_WIDTH; + groupCalls[groupId].codecSettings.max_video_height = TOXAV_MAX_VIDEO_HEIGHT; + + // Audio + alGenSources(1, &calls[groupId].alSource); + alcCaptureStart(alInDev); + + // Go + groupCalls[groupId].active = true; + groupCalls[groupId].sendAudioTimer->setInterval(5); + groupCalls[groupId].sendAudioTimer->setSingleShot(true); + connect(groupCalls[groupId].sendAudioTimer, &QTimer::timeout, [=](){sendGroupCallAudio(groupId,toxav);}); + groupCalls[groupId].sendAudioTimer->start(); +} + +void Core::leaveGroupCall(int groupId, ToxAv *) +{ + qDebug() << QString("Core: Leaving group call %1").arg(groupId); + groupCalls[groupId].active = false; + disconnect(groupCalls[groupId].sendAudioTimer,0,0,0); + groupCalls[groupId].sendAudioTimer->stop(); + alcCaptureStop(alInDev); +} + +void Core::sendGroupCallAudio(int groupId, ToxAv* toxav) +{ + if (!groupCalls[groupId].active) + return; + + if (groupCalls[groupId].muteMic) + { + groupCalls[groupId].sendAudioTimer->start(); + return; + } + + int framesize = (groupCalls[groupId].codecSettings.audio_frame_duration * groupCalls[groupId].codecSettings.audio_sample_rate) / 1000 * av_DefaultSettings.audio_channels; + uint8_t buf[framesize*2]; + + bool frame = false; + ALint samples; + alcGetIntegerv(alInDev, ALC_CAPTURE_SAMPLES, sizeof(samples), &samples); + if(samples >= framesize) + { + alcCaptureSamples(alInDev, buf, framesize); + frame = 1; + } + + if(frame) + { + int r; + if((r = toxav_group_send_audio(toxav_get_tox(toxav), groupId, (int16_t*)buf, + framesize, av_DefaultSettings.audio_channels, av_DefaultSettings.audio_sample_rate)) < 0) + { + qDebug() << "Core: toxav_group_send_audio error"; + groupCalls[groupId].sendAudioTimer->start(); + return; + } + } + groupCalls[groupId].sendAudioTimer->start(); } diff --git a/src/coreav.h b/src/coreav.h index bbb2a62f8..4619796d7 100644 --- a/src/coreav.h +++ b/src/coreav.h @@ -16,7 +16,6 @@ class QTimer; struct ToxCall { -public: ToxAvCSettings codecSettings; QTimer *sendAudioTimer, *sendVideoTimer; int callId; @@ -29,4 +28,15 @@ public: NetVideoSource videoSource; }; +struct ToxGroupCall +{ + ToxAvCSettings codecSettings; + QTimer *sendAudioTimer; + int groupId; + bool active; + bool muteMic; + bool muteVol; + ALuint alSource; +}; + #endif // COREAV_H From eac712e68a40749c3ddd4d79f7a3fc6a0974f23e Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Thu, 13 Nov 2014 13:11:23 +0100 Subject: [PATCH 018/253] Add GUI for audio groupchats --- src/core.cpp | 5 +- src/core.h | 11 +++- src/coreav.cpp | 34 ++++++++++-- src/group.cpp | 4 +- src/group.h | 3 +- src/grouplist.cpp | 4 +- src/grouplist.h | 2 +- src/widget/form/chatform.cpp | 2 - src/widget/form/chatform.h | 2 - src/widget/form/genericchatform.cpp | 2 + src/widget/form/genericchatform.h | 2 + src/widget/form/groupchatform.cpp | 84 +++++++++++++++++++++++++++-- src/widget/form/groupchatform.h | 4 ++ src/widget/widget.cpp | 2 +- 14 files changed, 138 insertions(+), 23 deletions(-) diff --git a/src/core.cpp b/src/core.cpp index 8f3ecb38a..4a2ad0279 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -46,12 +46,15 @@ const QString Core::TOX_EXT = ".tox"; QList Core::fileSendQueue; QList Core::fileRecvQueue; QHash Core::groupCalls; +QThread* Core::coreThread{nullptr}; -Core::Core(Camera* cam, QThread *coreThread, QString loadPath) : +Core::Core(Camera* cam, QThread *CoreThread, QString loadPath) : tox(nullptr), camera(cam), loadPath(loadPath), ready{false} { qDebug() << "Core: loading Tox from" << loadPath; + coreThread = CoreThread; + videobuf = new uint8_t[videobufsize]; for (int i = 0; i < ptCounter; i++) diff --git a/src/core.h b/src/core.h index e0b9a23d2..2bdb97c4d 100644 --- a/src/core.h +++ b/src/core.h @@ -117,6 +117,13 @@ public slots: void micMuteToggle(int callId); void volMuteToggle(int callId); + static void joinGroupCall(int groupId); ///< Starts a call in an existing AV groupchat + static void leaveGroupCall(int groupId); ///< Will not leave the group, just stop the call + static void disableGroupCallMic(int groupId); + static void disableGroupCallVol(int groupId); + static void enableGroupCallMic(int groupId); + static void enableGroupCallVol(int groupId); + void setPassword(QString& password, PasswordType passtype, uint8_t* salt = nullptr); void clearPassword(PasswordType passtype); QByteArray encryptData(const QByteArray& data, PasswordType passtype); @@ -233,8 +240,6 @@ private: static void onAvPeerTimeout(void* toxav, int32_t call_index, void* core); static void onAvMediaChange(void *toxav, int32_t call_index, void* core); - static void joinGroupCall(int groupId, ToxAv *toxav); ///< Starts a call in an existing AV groupchat - static void leaveGroupCall(int groupId, ToxAv *toxav); ///< Will not leave the group, just stop the call static void playGroupAudio(Tox* tox, int groupnumber, int friendgroupnumber, const int16_t* out_audio, unsigned out_audio_samples, uint8_t decoder_channels, unsigned audio_sample_rate, void* userdata); static void sendGroupCallAudio(int groupId, ToxAv* toxav); @@ -282,6 +287,8 @@ private: static ALCdevice* alOutDev, *alInDev; static ALCcontext* alContext; + + static QThread *coreThread; public: static ALuint alMainSource; }; diff --git a/src/coreav.cpp b/src/coreav.cpp index 62fa09a23..08824f530 100644 --- a/src/coreav.cpp +++ b/src/coreav.cpp @@ -592,10 +592,13 @@ void Core::playGroupAudio(Tox* /*tox*/, int groupnumber, int /*friendgroupnumbe if (!groupCalls[groupnumber].active) return; - playAudioBuffer(groupCalls[groupnumber].alSource, out_audio, out_audio_samples, decoder_channels, audio_sample_rate); + if (!groupCalls[groupbumber].muteVol) + return; + + playAudioBuffer(alMainSource, out_audio, out_audio_samples, decoder_channels, audio_sample_rate); } -void Core::joinGroupCall(int groupId, ToxAv *toxav) +void Core::joinGroupCall(int groupId) { qDebug() << QString("Core: Joining group call %1").arg(groupId); groupCalls[groupId].groupId = groupId; @@ -608,10 +611,13 @@ void Core::joinGroupCall(int groupId, ToxAv *toxav) groupCalls[groupId].codecSettings.max_video_height = TOXAV_MAX_VIDEO_HEIGHT; // Audio - alGenSources(1, &calls[groupId].alSource); + alGenSources(1, &groupCalls[groupId].alSource); alcCaptureStart(alInDev); // Go + ToxAv* toxav = Core::getInstance()->toxav; + groupCalls[groupId].sendAudioTimer = new QTimer(); + groupCalls[groupId].sendAudioTimer->moveToThread(coreThread); groupCalls[groupId].active = true; groupCalls[groupId].sendAudioTimer->setInterval(5); groupCalls[groupId].sendAudioTimer->setSingleShot(true); @@ -619,7 +625,7 @@ void Core::joinGroupCall(int groupId, ToxAv *toxav) groupCalls[groupId].sendAudioTimer->start(); } -void Core::leaveGroupCall(int groupId, ToxAv *) +void Core::leaveGroupCall(int groupId) { qDebug() << QString("Core: Leaving group call %1").arg(groupId); groupCalls[groupId].active = false; @@ -664,3 +670,23 @@ void Core::sendGroupCallAudio(int groupId, ToxAv* toxav) } groupCalls[groupId].sendAudioTimer->start(); } + +void Core::disableGroupCallMic(int groupId) +{ + groupCalls[groupId].muteMic = true; +} + +void Core::disableGroupCallVol(int groupId) +{ + groupCalls[groupId].muteVol = true; +} + +void Core::enableGroupCallMic(int groupId) +{ + groupCalls[groupId].muteMic = false; +} + +void Core::enableGroupCallVol(int groupId) +{ + groupCalls[groupId].muteVol = false; +} diff --git a/src/group.cpp b/src/group.cpp index d74610131..37160fef0 100644 --- a/src/group.cpp +++ b/src/group.cpp @@ -23,8 +23,8 @@ #include #include -Group::Group(int GroupId, QString Name) - : groupId(GroupId), nPeers{0} +Group::Group(int GroupId, QString Name, bool IsAvGroupchat) + : groupId(GroupId), nPeers{0}, avGroupchat{IsAvGroupchat} { widget = new GroupWidget(groupId, Name); chatForm = new GroupChatForm(this); diff --git a/src/group.h b/src/group.h index cd263538f..56c3bebb0 100644 --- a/src/group.h +++ b/src/group.h @@ -30,7 +30,7 @@ class Group : public QObject { Q_OBJECT public: - Group(int GroupId, QString Name); + Group(int GroupId, QString Name, bool IsAvGroupchat); ~Group(); void addPeer(int peerId, QString name); void removePeer(int peerId); @@ -43,6 +43,7 @@ public: GroupWidget* widget; GroupChatForm* chatForm; int hasNewMessages, userWasMentioned; + bool avGroupchat; }; #endif // GROUP_H diff --git a/src/grouplist.cpp b/src/grouplist.cpp index 10163adfa..72b7cf872 100644 --- a/src/grouplist.cpp +++ b/src/grouplist.cpp @@ -19,9 +19,9 @@ QList GroupList::groupList; -Group* GroupList::addGroup(int groupId, const QString& name) +Group* GroupList::addGroup(int groupId, const QString& name, bool isAvGroupchat) { - Group* newGroup = new Group(groupId, name); + Group* newGroup = new Group(groupId, name, isAvGroupchat); groupList.append(newGroup); return newGroup; } diff --git a/src/grouplist.h b/src/grouplist.h index 4e2b3e35b..36d8281f7 100644 --- a/src/grouplist.h +++ b/src/grouplist.h @@ -26,7 +26,7 @@ class GroupList { public: GroupList(); - static Group* addGroup(int groupId, const QString& name); + static Group* addGroup(int groupId, const QString& name, bool isAvGroupchat); static Group* findGroup(int groupId); static void removeGroup(int groupId, bool fake = false); diff --git a/src/widget/form/chatform.cpp b/src/widget/form/chatform.cpp index 7e52136ed..ac46e2340 100644 --- a/src/widget/form/chatform.cpp +++ b/src/widget/form/chatform.cpp @@ -43,8 +43,6 @@ ChatForm::ChatForm(Friend* chatFriend) : f(chatFriend) - , audioInputFlag(false) - , audioOutputFlag(false) , callId(0) { nameLabel->setText(f->getDisplayedName()); diff --git a/src/widget/form/chatform.h b/src/widget/form/chatform.h index 2a051f84e..4ec1735d7 100644 --- a/src/widget/form/chatform.h +++ b/src/widget/form/chatform.h @@ -95,8 +95,6 @@ private: Friend* f; CroppingLabel *statusMessageLabel; NetCamView* netcam; - bool audioInputFlag; - bool audioOutputFlag; int callId; QLabel *callDuration; QTimer *timer; diff --git a/src/widget/form/genericchatform.cpp b/src/widget/form/genericchatform.cpp index ffe6f8ecb..71dc14771 100644 --- a/src/widget/form/genericchatform.cpp +++ b/src/widget/form/genericchatform.cpp @@ -37,6 +37,8 @@ GenericChatForm::GenericChatForm(QWidget *parent) : QWidget(parent), earliestMessage(nullptr) + , audioInputFlag(false) + , audioOutputFlag(false) { curRow = 0; diff --git a/src/widget/form/genericchatform.h b/src/widget/form/genericchatform.h index ddd451ca7..47128a0f3 100644 --- a/src/widget/form/genericchatform.h +++ b/src/widget/form/genericchatform.h @@ -91,6 +91,8 @@ protected: QPushButton *sendButton; ChatAreaWidget *chatWidget; QDateTime *earliestMessage; + bool audioInputFlag; + bool audioOutputFlag; }; #endif // GENERICCHATFORM_H diff --git a/src/widget/form/groupchatform.cpp b/src/widget/form/groupchatform.cpp index 9666b7e4f..fb8a4faef 100644 --- a/src/widget/form/groupchatform.cpp +++ b/src/widget/form/groupchatform.cpp @@ -30,17 +30,25 @@ #include "src/misc/flowlayout.h" GroupChatForm::GroupChatForm(Group* chatGroup) - : group(chatGroup) + : group(chatGroup), inCall{false} { nusersLabel = new QLabel(); tabber = new TabCompleter(msgEdit, group); fileButton->setEnabled(false); - callButton->setVisible(false); - videoButton->setVisible(false); - volButton->setVisible(false); - micButton->setVisible(false); + if (group->avGroupchat) + { + videoButton->setEnabled(false); + videoButton->setObjectName("grey"); + } + else + { + videoButton->setVisible(false); + callButton->setVisible(false); + volButton->setVisible(false); + micButton->setVisible(false); + } nameLabel->setText(group->widget->getName()); @@ -68,6 +76,9 @@ GroupChatForm::GroupChatForm(Group* chatGroup) connect(msgEdit, SIGNAL(enterPressed()), this, SLOT(onSendTriggered())); connect(msgEdit, &ChatTextEdit::tabPressed, tabber, &TabCompleter::complete); connect(msgEdit, &ChatTextEdit::keyPressed, tabber, &TabCompleter::reset); + connect(callButton, &QPushButton::clicked, this, &GroupChatForm::onCallClicked); + connect(micButton, SIGNAL(clicked()), this, SLOT(onMicMuteToggle())); + connect(volButton, SIGNAL(clicked()), this, SLOT(onVolMuteToggle())); setAcceptDrops(true); } @@ -129,3 +140,66 @@ void GroupChatForm::dropEvent(QDropEvent *ev) } } +void GroupChatForm::onMicMuteToggle() +{ + if (audioInputFlag == true) + { + if (micButton->objectName() == "red") + { + Core::getInstance()->enableGroupCallMic(group->groupId); + micButton->setObjectName("green"); + } + else + { + Core::getInstance()->disableGroupCallMic(group->groupId); + micButton->setObjectName("red"); + } + + Style::repolish(micButton); + } +} + +void GroupChatForm::onVolMuteToggle() +{ + if (audioOutputFlag == true) + { + if (volButton->objectName() == "red") + { + Core::getInstance()->enableGroupCallVol(group->groupId); + volButton->setObjectName("green"); + } + else + { + Core::getInstance()->disableGroupCallVol(group->groupId); + volButton->setObjectName("red"); + } + + Style::repolish(volButton); + } +} + +void GroupChatForm::onCallClicked() +{ + if (!inCall) + { + Core::getInstance()->joinGroupCall(group->groupId); + audioInputFlag = true; + audioOutputFlag = true; + callButton->setObjectName("red"); + callButton->style()->polish(callButton); + inCall = true; + } + else + { + Core::getInstance()->leaveGroupCall(group->groupId); + audioInputFlag = false; + audioOutputFlag = false; + micButton->setObjectName("green"); + micButton->style()->polish(micButton); + volButton->setObjectName("green"); + volButton->style()->polish(volButton); + callButton->setObjectName("green"); + callButton->style()->polish(callButton); + inCall = false; + } +} diff --git a/src/widget/form/groupchatform.h b/src/widget/form/groupchatform.h index 9ea0e0655..70399be80 100644 --- a/src/widget/form/groupchatform.h +++ b/src/widget/form/groupchatform.h @@ -34,6 +34,9 @@ public: private slots: void onSendTriggered(); + void onMicMuteToggle(); + void onVolMuteToggle(); + void onCallClicked(); protected: // drag & drop @@ -45,6 +48,7 @@ private: FlowLayout* namesListLayout; QLabel *nusersLabel; TabCompleter* tabber; + bool inCall; }; #endif // GROUPCHATFORM_H diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index 5b0f2afe7..29812072f 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -985,7 +985,7 @@ Group *Widget::createGroup(int groupId) } QString groupName = QString("Groupchat #%1").arg(groupId); - Group* newgroup = GroupList::addGroup(groupId, groupName); + Group* newgroup = GroupList::addGroup(groupId, groupName, true); QLayout* layout = contactListWidget->getGroupLayout(); layout->addWidget(newgroup->widget); newgroup->widget->updateStatusLight(); From c9f3bf54ac74dfe21e3fa89ecd50a91ca1557b64 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Thu, 13 Nov 2014 13:18:32 +0100 Subject: [PATCH 019/253] Fix typo --- src/coreav.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreav.cpp b/src/coreav.cpp index 08824f530..95560315b 100644 --- a/src/coreav.cpp +++ b/src/coreav.cpp @@ -592,7 +592,7 @@ void Core::playGroupAudio(Tox* /*tox*/, int groupnumber, int /*friendgroupnumbe if (!groupCalls[groupnumber].active) return; - if (!groupCalls[groupbumber].muteVol) + if (!groupCalls[groupnumber].muteVol) return; playAudioBuffer(alMainSource, out_audio, out_audio_samples, decoder_channels, audio_sample_rate); From d0dae300cc52f05047cf0e5ec6dded4a57ee7357 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Thu, 13 Nov 2014 13:59:04 +0100 Subject: [PATCH 020/253] Remember compile time timestamp Will be used to guarantee we don't downgrade if there's a bad update --- qtox.pro | 2 ++ src/main.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/qtox.pro b/qtox.pro index 2b0f79d19..6fa31b7e0 100644 --- a/qtox.pro +++ b/qtox.pro @@ -46,6 +46,8 @@ RESOURCES += res.qrc GIT_VERSION = $$system(git rev-parse HEAD 2> /dev/null || echo "built without git") DEFINES += GIT_VERSION=\"\\\"$$quote($$GIT_VERSION)\\\"\" +TIMESTAMP = $$system(date +"%s" || echo 0) +DEFINES += TIMESTAMP=$$TIMESTAMP DEFINES += LOG_TO_FILE contains(JENKINS,YES) { diff --git a/src/main.cpp b/src/main.cpp index 3fa5b0108..baab1f558 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -94,7 +94,7 @@ int main(int argc, char *argv[]) QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath()); a.addLibraryPath("platforms"); - qDebug() << "built on: " << __TIME__ << __DATE__; + qDebug() << "built on: " << __TIME__ << __DATE__ << "(" << TIMESTAMP << ")"; qDebug() << "commit: " << GIT_VERSION << "\n"; // Install Unicode 6.1 supporting font From 05c9ae939002aaff98bf333594e4483f395c2fb3 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Thu, 13 Nov 2014 14:09:35 +0100 Subject: [PATCH 021/253] Auto-updater: Check timestamps before updating Update the version file format to 2, breaks backward compatibility. --- src/autoupdate.cpp | 30 ++++++++++++++++++------------ src/autoupdate.h | 10 ++++++++-- tools/update-server/genversion.sh | 4 ++-- tools/update-server/version | 1 + 4 files changed, 29 insertions(+), 16 deletions(-) create mode 100644 tools/update-server/version diff --git a/src/autoupdate.cpp b/src/autoupdate.cpp index 185a83bb7..688deb084 100644 --- a/src/autoupdate.cpp +++ b/src/autoupdate.cpp @@ -68,20 +68,22 @@ const QString AutoUpdater::filesURI = AutoUpdater::updateServer+"/qtox/"+AutoUpd bool AutoUpdater::isUpdateAvailable() { - QString newVersion = getUpdateVersion(); - if (newVersion.isEmpty() || newVersion == GIT_VERSION) + VersionInfo newVersion = getUpdateVersion(); + if (newVersion.timestamp <= TIMESTAMP + || newVersion.versionString.isEmpty() || newVersion.versionString == GIT_VERSION) return false; else return true; } -QString AutoUpdater::getUpdateVersion() +AutoUpdater::VersionInfo AutoUpdater::getUpdateVersion() { - QString version; + VersionInfo versionInfo; + versionInfo.timestamp = 0; // Updates only for supported platforms if (platform.isEmpty()) - return version; + return versionInfo; QNetworkAccessManager *manager = new QNetworkAccessManager; QNetworkReply* reply = manager->get(QNetworkRequest(QUrl(checkURI))); @@ -93,20 +95,20 @@ QString AutoUpdater::getUpdateVersion() qWarning() << "AutoUpdater: getUpdateVersion: network error: "<errorString(); reply->deleteLater(); manager->deleteLater(); - return version; + return versionInfo; } QByteArray data = reply->readAll(); reply->deleteLater(); manager->deleteLater(); if (data.size() < (int)(1+crypto_sign_BYTES)) - return version; + return versionInfo; // Check updater protocol version - if ((int)data[0] != '1') + if ((int)data[0] != '2') { qWarning() << "AutoUpdater: getUpdateVersion: Bad version "<<(uint8_t)data[0]; - return version; + return versionInfo; } // Check the signature @@ -118,12 +120,16 @@ QString AutoUpdater::getUpdateVersion() if (crypto_sign_verify_detached(sig, msg, msgData.size(), key) != 0) { qCritical() << "AutoUpdater: getUpdateVersion: RECEIVED FORGED VERSION FILE FROM "< AutoUpdater::parseFlist(QByteArray flistData) diff --git a/src/autoupdate.h b/src/autoupdate.h index c685a88d1..346d9a787 100644 --- a/src/autoupdate.h +++ b/src/autoupdate.h @@ -58,6 +58,12 @@ public: QByteArray data; }; + struct VersionInfo + { + uint64_t timestamp; + QString versionString; + }; + public: /// Connects to the qTox update server, if an updat is found shows a dialog to the user asking to download it /// Runs asynchronously in its own thread, and will return immediatly @@ -66,9 +72,9 @@ public: /// Connects to the qTox update server, returns true if an update is available for download /// Will call getUpdateVersion, and as such may block and processEvents static bool isUpdateAvailable(); - /// Fetch the version string of the last update available from the qTox update server + /// Fetch the version info of the last update available from the qTox update server /// Will try to follow qTox's proxy settings, may block and processEvents - static QString getUpdateVersion(); + static VersionInfo getUpdateVersion(); /// Will try to download an update, if successful returns true and qTox will apply it after a restart /// Will try to follow qTox's proxy settings, may block and processEvents static bool downloadUpdate(); diff --git a/tools/update-server/genversion.sh b/tools/update-server/genversion.sh index 61db4e354..2c34e9641 100644 --- a/tools/update-server/genversion.sh +++ b/tools/update-server/genversion.sh @@ -1,4 +1,4 @@ #!/bin/bash -echo -n 1 > version -./qtox-updater-sign $1 >> version +echo -n 2 > version +./qtox-updater-sign `date +%s`!$1 >> version diff --git a/tools/update-server/version b/tools/update-server/version new file mode 100644 index 000000000..d8263ee98 --- /dev/null +++ b/tools/update-server/version @@ -0,0 +1 @@ +2 \ No newline at end of file From a255d13b54bb31e3d20d9a207e97f34f028c96b9 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Thu, 13 Nov 2014 18:20:28 +0100 Subject: [PATCH 022/253] Windows: Uninstall start menu entries --- tools/update-server/version | 1 - windows/qtox.nsi | 17 ++++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) delete mode 100644 tools/update-server/version diff --git a/tools/update-server/version b/tools/update-server/version deleted file mode 100644 index d8263ee98..000000000 --- a/tools/update-server/version +++ /dev/null @@ -1 +0,0 @@ -2 \ No newline at end of file diff --git a/windows/qtox.nsi b/windows/qtox.nsi index 36b01db21..1594b680b 100644 --- a/windows/qtox.nsi +++ b/windows/qtox.nsi @@ -338,17 +338,20 @@ Section Uninstall Delete "$INSTDIR\${UninstLog}" noLog: Delete /REBOOTOK "$INSTDIR\uninstall.exe" - RMDir /r /REBOOTOK $INSTDIR\bin" + RMDir /r /REBOOTOK "$INSTDIR\bin" RMDir /REBOOTOK "$INSTDIR" Pop $R2 Pop $R1 Pop $R0 + ;Remove start menu entries + RMDir /r /REBOOTOK "$SMPROGRAMS\qTox" + ;Remove registry keys - DeleteRegKey ${REG_ROOT} "${REG_APP_PATH}" - DeleteRegKey ${REG_ROOT} "${UNINSTALL_PATH}" - DeleteRegKey HKCR "Applications\qtox.exe" - DeleteRegKey HKCR ".tox" - DeleteRegKey HKCR "tox" - DeleteRegKey HKCR "toxsave" + DeleteRegKey ${REG_ROOT} "${REG_APP_PATH}" + DeleteRegKey ${REG_ROOT} "${UNINSTALL_PATH}" + DeleteRegKey HKCR "Applications\qtox.exe" + DeleteRegKey HKCR ".tox" + DeleteRegKey HKCR "tox" + DeleteRegKey HKCR "toxsave" SectionEnd From 211005e8dee75d23ae7c921ef8fc1e8d04524ae0 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Thu, 13 Nov 2014 18:31:21 +0100 Subject: [PATCH 023/253] Don't 'style' QGroupbot borders in settings --- ui/settings/mainContent.css | 3 --- 1 file changed, 3 deletions(-) diff --git a/ui/settings/mainContent.css b/ui/settings/mainContent.css index 1b9d4bfee..ee239f209 100644 --- a/ui/settings/mainContent.css +++ b/ui/settings/mainContent.css @@ -44,9 +44,6 @@ QGroupBox color: black; background-color: white; font: @bigBold; - border: 2px solid gray; - border-radius: 5px; - margin-top: 2ex; /* leave space at the top for the title */ } QComboBox From 0c393e717ac59b2dec68fc6cf7cba515c39ed2b8 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Thu, 13 Nov 2014 19:20:06 +0100 Subject: [PATCH 024/253] Add simple push to talk with P Fixes #734 --- src/core.h | 2 ++ src/coreav.cpp | 10 +++++++++ src/widget/form/groupchatform.cpp | 37 +++++++++++++++++++++++++++++++ src/widget/form/groupchatform.h | 2 ++ 4 files changed, 51 insertions(+) diff --git a/src/core.h b/src/core.h index 2bdb97c4d..e65710c26 100644 --- a/src/core.h +++ b/src/core.h @@ -123,6 +123,8 @@ public slots: static void disableGroupCallVol(int groupId); static void enableGroupCallMic(int groupId); static void enableGroupCallVol(int groupId); + static bool isGroupCallMicEnabled(int groupId); + static bool isGroupCallVolEnabled(int groupId); void setPassword(QString& password, PasswordType passtype, uint8_t* salt = nullptr); void clearPassword(PasswordType passtype); diff --git a/src/coreav.cpp b/src/coreav.cpp index 95560315b..89a9ae1da 100644 --- a/src/coreav.cpp +++ b/src/coreav.cpp @@ -690,3 +690,13 @@ void Core::enableGroupCallVol(int groupId) { groupCalls[groupId].muteVol = false; } + +bool Core::isGroupCallMicEnabled(int groupId) +{ + return !groupCalls[groupId].muteMic; +} + +bool Core::isGroupCallVolEnabled(int groupId) +{ + return !groupCalls[groupId].muteVol; +} diff --git a/src/widget/form/groupchatform.cpp b/src/widget/form/groupchatform.cpp index fb8a4faef..2047f8e47 100644 --- a/src/widget/form/groupchatform.cpp +++ b/src/widget/form/groupchatform.cpp @@ -28,6 +28,7 @@ #include #include "src/historykeeper.h" #include "src/misc/flowlayout.h" +#include GroupChatForm::GroupChatForm(Group* chatGroup) : group(chatGroup), inCall{false} @@ -203,3 +204,39 @@ void GroupChatForm::onCallClicked() inCall = false; } } + +void GroupChatForm::keyPressEvent(QKeyEvent* ev) +{ + qDebug() << "Press:"<key(); + // Push to talk + if (ev->key() == Qt::Key_P && inCall) + { + qDebug() << "Press:"<key(); + Core* core = Core::getInstance(); + if (!core->isGroupCallMicEnabled(group->groupId)) + { + qDebug() << "Press:"<key(); + core->enableGroupCallMic(group->groupId); + micButton->setObjectName("green"); + micButton->style()->polish(micButton); + Style::repolish(micButton); + } + } +} + +void GroupChatForm::keyReleaseEvent(QKeyEvent* ev) +{ + qDebug() << "Release:"<key(); + // Push to talk + if (ev->key() == Qt::Key_P && inCall) + { + Core* core = Core::getInstance(); + if (core->isGroupCallMicEnabled(group->groupId)) + { + core->disableGroupCallMic(group->groupId); + micButton->setObjectName("red"); + micButton->style()->polish(micButton); + Style::repolish(micButton); + } + } +} diff --git a/src/widget/form/groupchatform.h b/src/widget/form/groupchatform.h index 70399be80..8b7d07339 100644 --- a/src/widget/form/groupchatform.h +++ b/src/widget/form/groupchatform.h @@ -42,6 +42,8 @@ protected: // drag & drop void dragEnterEvent(QDragEnterEvent* ev); void dropEvent(QDropEvent* ev); + void keyPressEvent(QKeyEvent* ev); + void keyReleaseEvent(QKeyEvent* ev); private: Group* group; From d5ab03d03d6e87e6bec47adf497bb7de3c5a4d74 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Thu, 13 Nov 2014 19:25:45 +0100 Subject: [PATCH 025/253] Listen for push-to-talk shortcut on more widgets --- src/widget/form/groupchatform.cpp | 4 ---- src/widget/form/groupchatform.h | 5 +++-- src/widget/groupwidget.cpp | 14 ++++++++++++++ src/widget/groupwidget.h | 2 ++ 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/widget/form/groupchatform.cpp b/src/widget/form/groupchatform.cpp index 2047f8e47..2e3766f03 100644 --- a/src/widget/form/groupchatform.cpp +++ b/src/widget/form/groupchatform.cpp @@ -207,15 +207,12 @@ void GroupChatForm::onCallClicked() void GroupChatForm::keyPressEvent(QKeyEvent* ev) { - qDebug() << "Press:"<key(); // Push to talk if (ev->key() == Qt::Key_P && inCall) { - qDebug() << "Press:"<key(); Core* core = Core::getInstance(); if (!core->isGroupCallMicEnabled(group->groupId)) { - qDebug() << "Press:"<key(); core->enableGroupCallMic(group->groupId); micButton->setObjectName("green"); micButton->style()->polish(micButton); @@ -226,7 +223,6 @@ void GroupChatForm::keyPressEvent(QKeyEvent* ev) void GroupChatForm::keyReleaseEvent(QKeyEvent* ev) { - qDebug() << "Release:"<key(); // Push to talk if (ev->key() == Qt::Key_P && inCall) { diff --git a/src/widget/form/groupchatform.h b/src/widget/form/groupchatform.h index 8b7d07339..d89c82730 100644 --- a/src/widget/form/groupchatform.h +++ b/src/widget/form/groupchatform.h @@ -32,6 +32,9 @@ public: void onUserListChanged(); + void keyPressEvent(QKeyEvent* ev); + void keyReleaseEvent(QKeyEvent* ev); + private slots: void onSendTriggered(); void onMicMuteToggle(); @@ -42,8 +45,6 @@ protected: // drag & drop void dragEnterEvent(QDragEnterEvent* ev); void dropEvent(QDropEvent* ev); - void keyPressEvent(QKeyEvent* ev); - void keyReleaseEvent(QKeyEvent* ev); private: Group* group; diff --git a/src/widget/groupwidget.cpp b/src/widget/groupwidget.cpp index 92b4ddb0b..1ad06b5b8 100644 --- a/src/widget/groupwidget.cpp +++ b/src/widget/groupwidget.cpp @@ -122,3 +122,17 @@ void GroupWidget::dropEvent(QDropEvent *ev) Core::getInstance()->groupInviteFriend(friendId, groupId); } } + +void GroupWidget::keyPressEvent(QKeyEvent* ev) +{ + Group* g = GroupList::findGroup(groupId); + if (g) + g->chatForm->keyPressEvent(ev); +} + +void GroupWidget::keyReleaseEvent(QKeyEvent* ev) +{ + Group* g = GroupList::findGroup(groupId); + if (g) + g->chatForm->keyReleaseEvent(ev); +} diff --git a/src/widget/groupwidget.h b/src/widget/groupwidget.h index f71e49f78..eeb14dae0 100644 --- a/src/widget/groupwidget.h +++ b/src/widget/groupwidget.h @@ -41,6 +41,8 @@ protected: // drag & drop void dragEnterEvent(QDragEnterEvent* ev); void dropEvent(QDropEvent* ev); + void keyPressEvent(QKeyEvent* ev); + void keyReleaseEvent(QKeyEvent* ev); public: int groupId; From ae91311241aa405bca586c0465af9b590fc68c69 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Thu, 13 Nov 2014 19:29:50 +0100 Subject: [PATCH 026/253] Silence IPC:processEvents failed to lock --- src/ipc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ipc.cpp b/src/ipc.cpp index fe0144d59..143f374ba 100644 --- a/src/ipc.cpp +++ b/src/ipc.cpp @@ -132,7 +132,7 @@ void IPC::processEvents() } else { - qWarning() << "IPC:processEvents failed to lock"; + //qWarning() << "IPC:processEvents failed to lock"; goto restartTimer; } From a3b419827049a490788fadcffc4fdadee5057c87 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Thu, 13 Nov 2014 19:43:03 +0100 Subject: [PATCH 027/253] Fix possible crash on ~IPC --- src/ipc.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ipc.cpp b/src/ipc.cpp index 143f374ba..b7aedf533 100644 --- a/src/ipc.cpp +++ b/src/ipc.cpp @@ -71,7 +71,9 @@ IPC::~IPC() { if (globalMemory.lock()) { - *(time_t*)((char*)globalMemory.data()+sizeof(globalId)) = 0; + char* data = (char*)globalMemory.data(); + if (data) + *(time_t*)(data+sizeof(globalId)) = 0; globalMemory.unlock(); } } From 12431c30477e2aee7dc4b51bd5fc0ea0cda6698a Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Thu, 13 Nov 2014 19:43:32 +0100 Subject: [PATCH 028/253] Better debug output for groupchat not found --- src/widget/widget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index 29812072f..b9cc9b3ce 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -923,7 +923,7 @@ void Widget::onGroupNamelistChanged(int groupnumber, int peernumber, uint8_t Cha Group* g = GroupList::findGroup(groupnumber); if (!g) { - qDebug() << "Widget::onGroupNamelistChanged: Group not found, creating it"; + qDebug() << "Widget::onGroupNamelistChanged: Group "< Date: Thu, 13 Nov 2014 19:43:53 +0100 Subject: [PATCH 029/253] Fix vol must be muted to actually hear things --- src/coreav.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreav.cpp b/src/coreav.cpp index 89a9ae1da..a92364d2b 100644 --- a/src/coreav.cpp +++ b/src/coreav.cpp @@ -592,7 +592,7 @@ void Core::playGroupAudio(Tox* /*tox*/, int groupnumber, int /*friendgroupnumbe if (!groupCalls[groupnumber].active) return; - if (!groupCalls[groupnumber].muteVol) + if (groupCalls[groupnumber].muteVol) return; playAudioBuffer(alMainSource, out_audio, out_audio_samples, decoder_channels, audio_sample_rate); From c550658e3851962a75a1642b2a660fce0d10a472 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Thu, 13 Nov 2014 20:18:02 +0100 Subject: [PATCH 030/253] Use a source per call peer --- src/coreav.cpp | 12 ++++++++---- src/coreav.h | 3 ++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/coreav.cpp b/src/coreav.cpp index a92364d2b..551f009f8 100644 --- a/src/coreav.cpp +++ b/src/coreav.cpp @@ -546,7 +546,7 @@ void Core::playAudioBuffer(ALuint alSource, const int16_t *data, int samples, un } ALuint bufid; - ALint processed, queued; + ALint processed = 0, queued = 16; alGetSourcei(alSource, AL_BUFFERS_PROCESSED, &processed); alGetSourcei(alSource, AL_BUFFERS_QUEUED, &queued); alSourcei(alSource, AL_LOOPING, AL_FALSE); @@ -586,7 +586,7 @@ VideoSource *Core::getVideoSourceFromCall(int callNumber) return &calls[callNumber].videoSource; } -void Core::playGroupAudio(Tox* /*tox*/, int groupnumber, int /*friendgroupnumber*/, const int16_t* out_audio, +void Core::playGroupAudio(Tox* /*tox*/, int groupnumber, int friendgroupnumber, const int16_t* out_audio, unsigned out_audio_samples, uint8_t decoder_channels, unsigned audio_sample_rate, void* /*userdata*/) { if (!groupCalls[groupnumber].active) @@ -595,7 +595,11 @@ void Core::playGroupAudio(Tox* /*tox*/, int groupnumber, int /*friendgroupnumbe if (groupCalls[groupnumber].muteVol) return; - playAudioBuffer(alMainSource, out_audio, out_audio_samples, decoder_channels, audio_sample_rate); + if (!groupCalls[groupnumber].alSources.contains(friendgroupnumber)) + alGenSources(1, &groupCalls[groupnumber].alSources[friendgroupnumber]); + + playAudioBuffer(groupCalls[groupnumber].alSources[friendgroupnumber], out_audio, + out_audio_samples, decoder_channels, audio_sample_rate); } void Core::joinGroupCall(int groupId) @@ -611,7 +615,7 @@ void Core::joinGroupCall(int groupId) groupCalls[groupId].codecSettings.max_video_height = TOXAV_MAX_VIDEO_HEIGHT; // Audio - alGenSources(1, &groupCalls[groupId].alSource); + //alGenSources(1, &groupCalls[groupId].alSource); alcCaptureStart(alInDev); // Go diff --git a/src/coreav.h b/src/coreav.h index 4619796d7..1ac4fc12a 100644 --- a/src/coreav.h +++ b/src/coreav.h @@ -1,6 +1,7 @@ #ifndef COREAV_H #define COREAV_H +#include #include #include "video/netvideosource.h" @@ -36,7 +37,7 @@ struct ToxGroupCall bool active; bool muteMic; bool muteVol; - ALuint alSource; + QHash alSources; }; #endif // COREAV_H From 858971fd942136791a10bf79e9923c84f14fb25b Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Thu, 13 Nov 2014 20:21:28 +0100 Subject: [PATCH 031/253] Augment audio queue size, fixes quality issues Closes #690 --- src/coreav.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreav.cpp b/src/coreav.cpp index 551f009f8..b11ff84d9 100644 --- a/src/coreav.cpp +++ b/src/coreav.cpp @@ -558,7 +558,7 @@ void Core::playAudioBuffer(ALuint alSource, const int16_t *data, int samples, un alDeleteBuffers(processed - 1, bufids + 1); bufid = bufids[0]; } - else if(queued < 16) + else if(queued < 128) { alGenBuffers(1, &bufid); } From 5eeae0e142b348331d907ea94b4cc0b84b3f947d Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Thu, 13 Nov 2014 20:32:09 +0100 Subject: [PATCH 032/253] Fix push to talk even when text edit focused --- src/widget/form/groupchatform.cpp | 6 ++++++ src/widget/groupwidget.cpp | 1 + 2 files changed, 7 insertions(+) diff --git a/src/widget/form/groupchatform.cpp b/src/widget/form/groupchatform.cpp index 2e3766f03..28e295f98 100644 --- a/src/widget/form/groupchatform.cpp +++ b/src/widget/form/groupchatform.cpp @@ -207,6 +207,9 @@ void GroupChatForm::onCallClicked() void GroupChatForm::keyPressEvent(QKeyEvent* ev) { + if (msgEdit->hasFocus()) + return; + // Push to talk if (ev->key() == Qt::Key_P && inCall) { @@ -223,6 +226,9 @@ void GroupChatForm::keyPressEvent(QKeyEvent* ev) void GroupChatForm::keyReleaseEvent(QKeyEvent* ev) { + if (msgEdit->hasFocus()) + return; + // Push to talk if (ev->key() == Qt::Key_P && inCall) { diff --git a/src/widget/groupwidget.cpp b/src/widget/groupwidget.cpp index 1ad06b5b8..3e9f149f0 100644 --- a/src/widget/groupwidget.cpp +++ b/src/widget/groupwidget.cpp @@ -128,6 +128,7 @@ void GroupWidget::keyPressEvent(QKeyEvent* ev) Group* g = GroupList::findGroup(groupId); if (g) g->chatForm->keyPressEvent(ev); + } void GroupWidget::keyReleaseEvent(QKeyEvent* ev) From 56b0559d8587412f9c8008f7f76425504b8fbf39 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Thu, 13 Nov 2014 21:14:17 +0100 Subject: [PATCH 033/253] Fix memory disclosure in audio send code (valgrind) --- src/coreav.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/coreav.cpp b/src/coreav.cpp index b11ff84d9..1f85d631a 100644 --- a/src/coreav.cpp +++ b/src/coreav.cpp @@ -657,6 +657,7 @@ void Core::sendGroupCallAudio(int groupId, ToxAv* toxav) alcGetIntegerv(alInDev, ALC_CAPTURE_SAMPLES, sizeof(samples), &samples); if(samples >= framesize) { + memset(buf, 0, framesize*2); // Avoid uninitialized values (Valgrind) alcCaptureSamples(alInDev, buf, framesize); frame = 1; } From 11f9cb0cbfcd8eb1c42401affe14d30f21af1977 Mon Sep 17 00:00:00 2001 From: Maximilian Date: Thu, 13 Nov 2014 21:33:11 +0100 Subject: [PATCH 034/253] I implemented a new setting, which toggles the visibility of the system tray icon. Background: On Ubuntu 14.04, system trays in Qt5 don't integrate into Unity. --- src/misc/settings.cpp | 12 ++++ src/misc/settings.h | 6 +- src/widget/form/settings/generalform.cpp | 8 +++ src/widget/form/settings/generalform.h | 1 + src/widget/form/settings/generalsettings.ui | 68 +++++++++++++++++++-- src/widget/form/settingswidget.h | 3 + src/widget/widget.cpp | 11 +++- src/widget/widget.h | 1 + 8 files changed, 102 insertions(+), 8 deletions(-) diff --git a/src/misc/settings.cpp b/src/misc/settings.cpp index d9c746a76..d6ef0b506 100644 --- a/src/misc/settings.cpp +++ b/src/misc/settings.cpp @@ -112,6 +112,7 @@ void Settings::load() s.beginGroup("General"); enableIPv6 = s.value("enableIPv6", true).toBool(); translation = s.value("translation", "en").toString(); + showSystemTray = s.value("showSystemTray", false).toBool(); makeToxPortable = s.value("makeToxPortable", false).toBool(); autostartInTray = s.value("autostartInTray", false).toBool(); closeToTray = s.value("closeToTray", false).toBool(); @@ -252,6 +253,7 @@ void Settings::save(QString path) s.setValue("enableIPv6", enableIPv6); s.setValue("translation",translation); s.setValue("makeToxPortable",makeToxPortable); + s.setValue("showSystemTray", showSystemTray); s.setValue("autostartInTray",autostartInTray); s.setValue("closeToTray", closeToTray); s.setValue("useProxy", useProxy); @@ -430,6 +432,16 @@ void Settings::setStyle(const QString& newStyle) style = newStyle; } +bool Settings::getShowSystemTray() const +{ + return showSystemTray; +} + +void Settings::setShowSystemTray(const bool& newValue) +{ + showSystemTray = newValue; +} + void Settings::setUseEmoticons(bool newValue) { useEmoticons = newValue; diff --git a/src/misc/settings.h b/src/misc/settings.h index cebb20986..1ec15bbdd 100644 --- a/src/misc/settings.h +++ b/src/misc/settings.h @@ -62,6 +62,9 @@ public: QString getStyle() const; void setStyle(const QString& newValue); + + bool getShowSystemTray() const; + void setShowSystemTray(const bool& newValue); bool getUseEmoticons() const; void setUseEmoticons(bool newValue); @@ -265,7 +268,8 @@ private: QByteArray windowState; QByteArray splitterState; QString style; - + bool showSystemTray; + // ChatView int firstColumnHandlePos; int secondColumnHandlePosFromRight; diff --git a/src/widget/form/settings/generalform.cpp b/src/widget/form/settings/generalform.cpp index 496dccc9f..a4c305a16 100644 --- a/src/widget/form/settings/generalform.cpp +++ b/src/widget/form/settings/generalform.cpp @@ -51,6 +51,7 @@ GeneralForm::GeneralForm(SettingsWidget *myParent) : bodyUI->transComboBox->insertItem(i, langs[i]); bodyUI->transComboBox->setCurrentIndex(locales.indexOf(Settings::getInstance().getTranslation())); bodyUI->cbMakeToxPortable->setChecked(Settings::getInstance().getMakeToxPortable()); + bodyUI->showSystemTray->setChecked(Settings::getInstance().getShowSystemTray()); bodyUI->startInTray->setChecked(Settings::getInstance().getAutostartInTray()); bodyUI->closeToTray->setChecked(Settings::getInstance().getCloseToTray()); bodyUI->minimizeToTray->setChecked(Settings::getInstance().getMinimizeToTray()); @@ -104,6 +105,7 @@ GeneralForm::GeneralForm(SettingsWidget *myParent) : connect(bodyUI->checkUpdates, &QCheckBox::stateChanged, this, &GeneralForm::onCheckUpdateChanged); connect(bodyUI->transComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(onTranslationUpdated())); connect(bodyUI->cbMakeToxPortable, &QCheckBox::stateChanged, this, &GeneralForm::onMakeToxPortableUpdated); + connect(bodyUI->showSystemTray, &QCheckBox::stateChanged, this, &GeneralForm::onSetShowSystemTray); connect(bodyUI->startInTray, &QCheckBox::stateChanged, this, &GeneralForm::onSetAutostartInTray); connect(bodyUI->closeToTray, &QCheckBox::stateChanged, this, &GeneralForm::onSetCloseToTray); connect(bodyUI->minimizeToTray, &QCheckBox::stateChanged, this, &GeneralForm::onSetMinimizeToTray); @@ -150,6 +152,12 @@ void GeneralForm::onMakeToxPortableUpdated() Settings::getInstance().setMakeToxPortable(bodyUI->cbMakeToxPortable->isChecked()); } +void GeneralForm::onSetShowSystemTray() +{ + Settings::getInstance().setShowSystemTray(bodyUI->showSystemTray->isChecked()); + emit parent->setShowSystemTray(bodyUI->showSystemTray->isChecked()); +} + void GeneralForm::onSetAutostartInTray() { Settings::getInstance().setAutostartInTray(bodyUI->startInTray->isChecked()); diff --git a/src/widget/form/settings/generalform.h b/src/widget/form/settings/generalform.h index 493cdd499..6f902a523 100644 --- a/src/widget/form/settings/generalform.h +++ b/src/widget/form/settings/generalform.h @@ -34,6 +34,7 @@ private slots: void onEnableIPv6Updated(); void onTranslationUpdated(); void onMakeToxPortableUpdated(); + void onSetShowSystemTray(); void onSetAutostartInTray(); void onSetCloseToTray(); void onSmileyBrowserIndexChanged(int index); diff --git a/src/widget/form/settings/generalsettings.ui b/src/widget/form/settings/generalsettings.ui index 19a18c8cd..b0bf7d38d 100644 --- a/src/widget/form/settings/generalsettings.ui +++ b/src/widget/form/settings/generalsettings.ui @@ -39,8 +39,8 @@ 0 0 - 511 - 698 + 583 + 748 @@ -95,6 +95,13 @@ + + + + Show system tray + + + @@ -190,9 +197,6 @@ Set to 0 to disable - - true - minutes @@ -202,6 +206,9 @@ 2147483647 + + true + @@ -503,5 +510,54 @@ - + + + showSystemTray + toggled(bool) + startInTray + setEnabled(bool) + + + 125 + 122 + + + 207 + 124 + + + + + showSystemTray + toggled(bool) + closeToTray + setEnabled(bool) + + + 66 + 119 + + + 346 + 116 + + + + + showSystemTray + toggled(bool) + minimizeToTray + setEnabled(bool) + + + 83 + 121 + + + 476 + 120 + + + + diff --git a/src/widget/form/settingswidget.h b/src/widget/form/settingswidget.h index 175a3f56e..2c415f696 100644 --- a/src/widget/form/settingswidget.h +++ b/src/widget/form/settingswidget.h @@ -45,6 +45,9 @@ public: private slots: void onTabChanged(int); +signals: + void setShowSystemTray(bool newValue); + private: QWidget *head, *body; // keep the others private IdentityForm *ifrm; diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index 29812072f..10fa25f65 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -96,7 +96,9 @@ void Widget::init() this, SLOT(onIconClick(QSystemTrayIcon::ActivationReason))); - icon->show(); + if (Settings::getInstance().getShowSystemTray()){ + icon->show(); + } if(Settings::getInstance().getAutostartInTray() == false) this->show(); @@ -197,6 +199,8 @@ void Widget::init() addFriendForm = new AddFriendForm; settingsWidget = new SettingsWidget(); + connect(settingsWidget, SIGNAL(setShowSystemTray(bool)), this, SLOT(onSetShowSystemTray(bool))); + connect(core, &Core::connected, this, &Widget::onConnected); connect(core, &Core::disconnected, this, &Widget::onDisconnected); connect(core, &Core::failedToStart, this, &Widget::onFailedToStartCore); @@ -1105,6 +1109,11 @@ void Widget::getPassword(QString info, int passtype, uint8_t* salt) } } +void Widget::onSetShowSystemTray(bool newValue){ + icon->setVisible(newValue); +} + + QMessageBox::StandardButton Widget::showWarningMsgBox(const QString& title, const QString& msg, QMessageBox::StandardButtons buttons) { // We can only display widgets from the GUI thread diff --git a/src/widget/widget.h b/src/widget/widget.h index 17c737ce2..1131da49a 100644 --- a/src/widget/widget.h +++ b/src/widget/widget.h @@ -127,6 +127,7 @@ private slots: void onIconClick(QSystemTrayIcon::ActivationReason); void onUserAway(); void getPassword(QString info, int passtype, uint8_t* salt); + void onSetShowSystemTray(bool newValue); private: void init(); From d6cb03a4c65386aa569719c0c4f08ebdecae9b86 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Thu, 13 Nov 2014 21:52:38 +0100 Subject: [PATCH 035/253] Update french translation --- translations/fr.ts | 337 +++++++++++++++++++++++++++------------------ 1 file changed, 205 insertions(+), 132 deletions(-) diff --git a/translations/fr.ts b/translations/fr.ts index b47436b67..206cf9356 100644 --- a/translations/fr.ts +++ b/translations/fr.ts @@ -115,9 +115,15 @@ + qTox needs to use the Tox DNS, but can't do it through a proxy. +Ignore the proxy and connect to the Internet directly ? + qTox as besoin d'utiliser le DNS Tox, mais ne peut pas le faire avec un proxy. +Ignorer le proxy et se connecter directement à Internet ? + + qTox needs to use the Tox DNS, but can't do it through a proxy Ignore the proxy and connect to the Internet directly ? - qTox as besoin d'utiliser le DNS Tox, mais ne peut pas le faire avec un proxy + qTox as besoin d'utiliser le DNS Tox, mais ne peut pas le faire avec un proxy Ignorer le proxy et se connecter directement à Internet ? @@ -160,52 +166,52 @@ Ignorer le proxy et se connecter directement à Internet ? ChatForm - + Load History... Charger l'historique... - + Send a file Envoyer un fichier - + Bad Idea Mauvaise Idée - + You're trying to send a special (sequential) file, that's not going to work! Vous êtes en train d'essayer d'envoyer un fichier spécial (sequentiel), ça ne marchera pas! - + %1 calling %1 appelle - + %1 stopped calling %1a arreté l'appel - + Calling to %1 Appel de %1 en cours - + Call rejected Appel rejeté - + Call with %1 ended. %2 Appel avec %1 terminé. %2 - + Call duration: Durée de l'appel: @@ -221,99 +227,104 @@ Ignorer le proxy et se connecter directement à Internet ? Core - + Toxing on qTox Toxer avec qTox - + qTox User Utilisateur de qTox - + + Friend is already added + Cet ami est déjà dans cos contact + + + Encryption error Erreur de chiffrement - + The .tox file is encrypted, but encryption was not checked, continuing regardless. Le fichier .tox est chiffré, mais l'encryption n'as pas été activée. Le problème sera ignoré. - + Tox datafile decryption password Mot de passe des données Tox - - - + + + Password error Mod de passe invalide - - + + Failed to setup password. Empty password. Impossible de mettre ne place le mot de passe. Le mot de passe est vide. - + Try Again Réessayer - + Change profile Changer de profil - + Reinit current profile Réinitialiser le profil courant - + Wrong password has been entered Un mauvais mot de passe à été entré - + History Log decryption password Mot de passe de l'historique - + Your history is encrypted with different password Do you want to try another password? Votre historique de conversation est chiffré avec un autre mot de passe Voulez-vous essayer un mot de passe différent? - + Encrypted log Historique chiffré - + Loggin Historique - + Due to incorret password logging will be disabled À cause d'un mauvais mot de passe, l'historique sera désactivé - + NO Password PAS de mot de passe - + Will be saved without encryption! L'historique sera sauvegardé sans être chiffré! @@ -465,30 +476,30 @@ Voulez-vous essayer un mot de passe différent? GeneralForm - + General Général - - + + None Aucun - + Choose an auto accept directory popup title Choisir un dossier de téléchargement - + Call active popup title Appel en cours - + You can't disconnect while a call is active! popup text Vous ne pouvez pas vous déconnecter avec un appel en cours! @@ -527,27 +538,27 @@ Voulez-vous essayer un mot de passe différent? Rendre Tox portable - + Start in tray Démarrer dans la barre d'état - + Close to tray Fermer dans la barre d'état - + Minimize to tray Minimizer dans la barre d'état - + Show contacts' status changes Montrer les changements de status des contacts - + Provided in minutes En minutes @@ -556,17 +567,17 @@ Voulez-vous essayer un mot de passe différent? Auto-absent après (0 pour désactiver): - + Set to 0 to disable Mettre à 0 pour désactiver - + minutes minutes - + Theme Thème @@ -600,19 +611,19 @@ Voulez-vous essayer un mot de passe différent? Style: - + Enable UDP (recommended) Text on checkbox to disable UDP Activer UDP (recommandé) - + Reconnect reconnect button Reconnection - + Connection Settings Options de réseau @@ -622,67 +633,87 @@ Voulez-vous essayer un mot de passe différent? Traduction - + + Check for updates on startup (unstable) + Chercher des mises à jour au démarrage (instable) + + + + Focus qTox when a message is received + Montrer qTox quand un message est reçu + + + + Faux offline messaging + Retransmettre les messages en cas d'échec + + + Auto away after (0 to disable) Indisponible après (0 désactive) - + Autoaccept files Auto-accepter les fichiers - + Save files in Sauvegarder les fichiers vers - + PushButton PushButton - + Use emoticons Utiliser les émoticones - + Smiley Pack Text on smiley pack label Pack de smileys - + Style Style - + Emoticon size Taille d'émoticone - + px px - + Timestamp format Format de timestamp - + Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Activer IPv6 (recommandé) - + + Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. + force tcp checkbox tooltip + Désactiver ceci permet par exemple d'utiliser Tox à travers Tor, mais ce n'est à utiliser que si nécessaire, car cela ralenti le réseau Tox. + + This allows, e.g., toxing over Tor. It adds load to the Tox network however, so use only when necessary. force tcp checkbox tooltip - Permet par exemple d'utiliser Tox à travers Tor, mais ce n'est à utiliser que si nécessaire, car cela ralenti le réseau Tox. + Permet par exemple d'utiliser Tox à travers Tor, mais ce n'est à utiliser que si nécessaire, car cela ralenti le réseau Tox. Disable UDP (not recommended) @@ -690,18 +721,18 @@ Voulez-vous essayer un mot de passe différent? Désactiver UDP (non recommandé) - + Use proxy (SOCKS5) Utiliser un proxy (SOCKS5) - + Address Text on proxy addr label Addresse - + Port Text on proxy port label Port @@ -710,53 +741,53 @@ Voulez-vous essayer un mot de passe différent? GenericChatForm - + Send message Envoyer un message - + Smileys Emoticones - + Send file(s) Envoyer des fichiers - + Audio call Appel audio - + Video call Appel vidéo - + Toggle speakers volume Couper les haut parleurs - + Toggle microphone Couper le microphone - - + + Save chat log Sauvegarder l'historique de conversation - + Clear displayed messages Effacer les messages affichés - + Cleared Effacé @@ -764,13 +795,13 @@ Voulez-vous essayer un mot de passe différent? GroupChatForm - + %1 users in chat Number of users in chat %1 personnes - + %1 users in chat %1 personnes @@ -804,103 +835,123 @@ Voulez-vous essayer un mot de passe différent? Identité - + Call active popup title Appel en cours - + You can't switch profiles while a call is active! popup text Vous ne pouvez pas changer de profil quand un appel est en cours! - + Rename "%1" renaming a profile Renommer "%1" - + Profile already exists rename confirm title Ce profil existe déjà - + A profile named "%1" already exists. Do you want to erase it? rename confirm text Un profil appelé "%1" existe déjà. Voulez-vous le supprimer ? - + Export profile save dialog title Exporter le profil - + Tox save file (*.tox) save dialog filter Fichier sauvegarde Tox (*.tox) - + + Failed to remove file + Impossible de supprimer le fichier + + + + The file you chose to overwrite could not be removed first. + Le fichier que vous avez choisi d'écraser n'as pas pu être supprimé. + + + + Failed to copy file + Impossible de copier le fichier + + + + The file you chose could not be written to. + Le fichier que vous avez choisi n'as pas pu être écrit. + + + Profile currently loaded current profile deletion warning title Profil en cours d'utilisation - + This profile is currently in use. Please load a different profile before deleting this one. current profile deletion warning text Ce profil est en cours d'utilisation. Merci de choisir un autre profil avant de supprimer celui-ci. - + Deletion imminent! deletion confirmation title Suppression imminente! - + Are you sure you want to delete this profile? deletion confirmation text Êtes-vous sur de vouloir supprimer ce profil ? - + Import profile import dialog title Importer un profil - + Tox save file (*.tox) import dialog filter Fichier sauvegarde Tox (*.tox) - + Ignoring non-Tox file popup title Fichier non-Tox ignoré - + Warning: you've chosen a file that is not a Tox save file; ignoring. popup text Attention: Vous avez sélectionné un fichier qui n'est pas une sauvegarde Tox: il sera ignoré. - + Profile already exists import confirm title Ce profil existe déjà - + A profile named "%1" already exists. Do you want to erase it? import confirm text Un profil appelé "%1" existe déjà. Voulez-vous le supprimer ? @@ -1040,27 +1091,27 @@ Voulez-vous essayer un mot de passe différent? Votre status - + Add friends Ajouter des amis - + Create a group chat Creer un groupe - + View completed file transfers Voir les transfers de fichiers terminés - + Change your settings Changer les options - + Close Fermer @@ -1085,12 +1136,12 @@ Voulez-vous essayer un mot de passe différent? Vie privée - + Encrypted log Historique chiffré - + You already have history log file encrypted with different password Do you want to delete old history file? Vous avez déjà un historique chiffré avec un autre mot de passe @@ -1171,6 +1222,24 @@ Voulez vous supprimer l'ancien historique? Default message in Tox URI friend requests. Write something appropriate! Je souhaiterais vous ajouter à mes contacts + + + Update + The title of a message box + Mise à jour + + + + An update is available, do you want to download it now ? +It will be installed when qTox restarts. + Une mise à jour est disponible, voulez vous la télécharger maintenant ? +Elle sera installée au prochain démarrage de qTox + + + + Tox URI to parse + URI Tox à utiliser + SetPasswordDialog @@ -1292,150 +1361,154 @@ Voulez vous supprimer l'ancien historique? occupé - + Online Connecté - + Away Indisponible - + Busy Occupé - + &Quit &Quitter - + Change status to: Changer le status en: - + Online Button to set your status to 'Online' Connecté - + Away Button to set your status to 'Away' Indisponible - + Busy Button to set your status to 'Busy' Occupé - + Choose a profile Choisir un profil - + Please choose which identity to use Merci de choisir l'identité à utiliser - + Choose a profile picture Choisissez une image de profil - - - + + + Error Erreur - + Unable to open this file Impossible d'ouvrir ce fichier - + Unable to read this image Impossible de lire cette image - + This image is too big Cette image est trop volumineuse - + Toxcore failed to start, the application will terminate after you close this message. Toxcore n'as pas pu démarrer correctement, l'application va quitter quand vous fermerez ce message. - + toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text Toxcore n'as pas pu démarrer avec ces paramètres de proxy, qTox ne peut pas continuer; merci de modifier vos paramètres et redémarrer. - + Add friend Ajouter un ami - + File transfers Transfers - + Settings Options - + + Couldn't request friendship + Impossible de demander en ami + + + away contact status indisponnible - + busy contact status occupé - + offline contact status déconnecté - + online contact status connecté - + %1 is now %2 e.g. "Dubslow is now online" %1 est maintenant %2 - + <Unknown> Placeholder when we don't know someone's name in a group chat <Inconnu> - - + Message failed to send Le message n'as pas pu être envoyé From 3b4ac83e06ca57aec1e566a90ff9e709d9a50ba9 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Thu, 13 Nov 2014 22:01:43 +0100 Subject: [PATCH 036/253] Updater: Update OS X update server public key --- src/autoupdate.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/autoupdate.cpp b/src/autoupdate.cpp index 688deb084..2f0ac4266 100644 --- a/src/autoupdate.cpp +++ b/src/autoupdate.cpp @@ -52,8 +52,8 @@ const QString AutoUpdater::updateServer = "https://dist-build.tox.im"; unsigned char AutoUpdater::key[crypto_sign_PUBLICKEYBYTES] = { - 0xa5, 0x80, 0xf3, 0xb7, 0xd0, 0x10, 0xc0, 0xf9, 0xd6, 0xcf, 0x48, 0x15, 0x99, 0x70, 0x92, 0x49, - 0xf6, 0xe8, 0xe5, 0xe2, 0x6c, 0x73, 0x8c, 0x48, 0x25, 0xed, 0x01, 0x72, 0xf7, 0x6c, 0x17, 0x28 + 0x9c, 0x7a, 0x0b, 0x2a, 0xe9, 0xdc, 0x33, 0xf7, 0x74, 0x9f, 0xec, 0x14, 0x29, 0x22, 0x06, 0x4b, + 0xbe, 0x37, 0xe1, 0x9b, 0xb7, 0x18, 0xb4, 0xae, 0x8f, 0x29, 0x6b, 0x9d, 0xfe, 0xd6, 0x39, 0x3f }; #else From 848bf3e2401a9beee378e6712ddb9103c31882f7 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Thu, 13 Nov 2014 22:39:18 +0100 Subject: [PATCH 037/253] Fix conflicting declaration struct/class ToxID --- src/misc/settings.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/misc/settings.h b/src/misc/settings.h index 1ec15bbdd..842892b95 100644 --- a/src/misc/settings.h +++ b/src/misc/settings.h @@ -21,7 +21,7 @@ #include #include -class ToxID; +struct ToxID; class Settings : public QObject { From ee2507f8e0c1ba5a91e1b4fc83b42e76a9e2e5fd Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Thu, 13 Nov 2014 22:40:10 +0100 Subject: [PATCH 038/253] Fix conflicting declarations struct/class vpx_image --- src/widget/netcamview.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widget/netcamview.h b/src/widget/netcamview.h index c7f424c85..3f3eb9b1f 100644 --- a/src/widget/netcamview.h +++ b/src/widget/netcamview.h @@ -20,7 +20,7 @@ #include class QHBoxLayout; -class vpx_image; +struct vpx_image; class VideoSurface; class VideoSource; From d0e8d291bdd14512036c2a35934c685c7aae2248 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Thu, 13 Nov 2014 22:45:29 +0100 Subject: [PATCH 039/253] Remove unused variable --- src/historykeeper.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/historykeeper.h b/src/historykeeper.h index e2d1b8b02..bbe050c13 100644 --- a/src/historykeeper.h +++ b/src/historykeeper.h @@ -68,7 +68,6 @@ private: GenericDdInterface *db; QMap aliases; QMap> chats; - bool isEncrypted; int messageID; }; From d566f5f9b37f113530ff06aefed3a5c2d3212ef9 Mon Sep 17 00:00:00 2001 From: dubslow Date: Thu, 13 Nov 2014 16:53:41 -0600 Subject: [PATCH 040/253] minor autoaway tweak --- src/widget/widget.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index 0828cd0d5..e4c9fb91c 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -1032,8 +1032,8 @@ bool Widget::event(QEvent * e) case QEvent::Wheel: case QEvent::KeyPress: case QEvent::KeyRelease: - if (autoAwayActive) - { + if (autoAwayActive && ui->statusButton->property("status").toString() == "away") + { // be sure nothing else has changed the status in the meantime qDebug() << "Widget: auto away deactivated at" << QTime::currentTime().toString(); autoAwayActive = false; emit statusSet(Status::Online); From 66eebc2e11fc6c3d0c66eff75bab29f586ce3d38 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Fri, 14 Nov 2014 00:01:49 +0100 Subject: [PATCH 041/253] Enable auto-updates for OS X --- src/autoupdate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/autoupdate.h b/src/autoupdate.h index 346d9a787..4462db105 100644 --- a/src/autoupdate.h +++ b/src/autoupdate.h @@ -27,7 +27,7 @@ #ifdef Q_OS_WIN #define AUTOUPDATE_ENABLED 1 #elif defined(Q_OS_OSX) -#define AUTOUPDATE_ENABLED 0 +#define AUTOUPDATE_ENABLED 1 #else #define AUTOUPDATE_ENABLED 0 #endif From 2d608a4f2ef7da69ab169c3164c618e97908f691 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Fri, 14 Nov 2014 00:30:14 +0100 Subject: [PATCH 042/253] Update README.md --- README.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 80326c000..a95aeae29 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,20 @@ qTox ====== -Powerful Tox client that tries to follow the Tox UI mockup while running on all major systems.
-This GUI uses code from @nurupo'tos ProjectTox-Qt-GUI, in particular the "Core" Toxcore wrapper.
-However, it is not a fork. +qTox is a powerful Tox client that tries to follow the Tox design guidelines while running on all major platforms.

Features

- One to one chat with friends - Group chats - File transfers, with previewing of images -- Audio calls +- Audio calls, including group calls - Video calls -- Tox DNS +- Tox DNS and Tox URI support - Translations in various languages - Avatars +- Auto-updates on Windows and Mac +- And many more options!

Downloads

@@ -33,3 +33,9 @@ This client runs on Windows, Linux and Mac natively.
##Documentation: [Compiling](/INSTALL.md) + +##Developer overview: + +[GitStats](http://207.12.89.155/index.html)
+[Mac & Linux jenkins](https://jenkins.libtoxcore.so/user/tux3/my-views/view/qTox/)
+[Windows jenkins](http://207.12.89.155:8080)
From 76a1aefec270fb218ccf05e81b0d48850d1fc606 Mon Sep 17 00:00:00 2001 From: Sean Date: Thu, 13 Nov 2014 15:36:30 -0800 Subject: [PATCH 043/253] Force install to local user --- src/autoupdate.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/autoupdate.cpp b/src/autoupdate.cpp index 2f0ac4266..2c62ff691 100644 --- a/src/autoupdate.cpp +++ b/src/autoupdate.cpp @@ -46,8 +46,8 @@ unsigned char AutoUpdater::key[crypto_sign_PUBLICKEYBYTES] = #elif defined(Q_OS_OSX) const QString AutoUpdater::platform = "osx"; -const QString AutoUpdater::updaterBin = "installer -store -pkg "+Settings::getInstance().getSettingsDirPath() - +"/update/qtox.pkg -target /"; +const QString AutoUpdater::updaterBin = "installer -pkg "+Settings::getInstance().getSettingsDirPath() + +"/update/qtox.pkg -target CurrentUserHomeDirectory"; const QString AutoUpdater::updateServer = "https://dist-build.tox.im"; unsigned char AutoUpdater::key[crypto_sign_PUBLICKEYBYTES] = From b7da6d3a5e4af31901451bf14c215c8bb3da1d5d Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Fri, 14 Nov 2014 00:56:41 +0100 Subject: [PATCH 044/253] README: Mention package managers on Linux --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a95aeae29..401604384 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ qTox is a powerful Tox client that tries to follow the Tox design guidelines whi - Tox DNS and Tox URI support - Translations in various languages - Avatars -- Auto-updates on Windows and Mac +- Auto-updates on Windows and Mac, packages on Linux - And many more options!

Downloads

From 59b26f3d33f72b548b1d5014db1f3f23061588fc Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Fri, 14 Nov 2014 01:05:43 +0100 Subject: [PATCH 045/253] Add some debug info when toxav_prepare_transmission fails Should help with #743 --- src/coreav.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/coreav.cpp b/src/coreav.cpp index 1f85d631a..604426bdc 100644 --- a/src/coreav.cpp +++ b/src/coreav.cpp @@ -48,7 +48,9 @@ void Core::prepareCall(int friendId, int callId, ToxAv* toxav, bool videoEnabled calls[callId].codecSettings.max_video_width = TOXAV_MAX_VIDEO_WIDTH; calls[callId].codecSettings.max_video_height = TOXAV_MAX_VIDEO_HEIGHT; calls[callId].videoEnabled = videoEnabled; - toxav_prepare_transmission(toxav, callId, av_jbufdc, av_VADd, videoEnabled); + int r = toxav_prepare_transmission(toxav, callId, av_jbufdc, av_VADd, videoEnabled); + if (r < 0) + qWarning() << QString("Error starting call %1: toxav_prepare_transmission failed with %2").arg(callId).arg(r); // Audio alGenSources(1, &calls[callId].alSource); From 9db8f27742918d7177081b43691277c1ddd4b847 Mon Sep 17 00:00:00 2001 From: Sean Date: Thu, 13 Nov 2014 16:16:40 -0800 Subject: [PATCH 046/253] Update welcome.txt --- res/welcome.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/res/welcome.txt b/res/welcome.txt index 13096863d..6cf472a4a 100644 --- a/res/welcome.txt +++ b/res/welcome.txt @@ -1,3 +1,12 @@ Welcome to the qTox for OS X internal nightly installer! Please report all bugs to https://support.libtoxcore.so + +######################################################### +## WARNING: Install me to your user ONLY ## +## ## +## Failure to do so WILL break automatic updating ## +## and you WILL be left with and old and ## +## potentially broken qTox install! ## +## ## +######################################################### From 6fe336ad929ebe7d2f6243b68f8e0b9f2971c836 Mon Sep 17 00:00:00 2001 From: Sean Date: Thu, 13 Nov 2014 18:01:45 -0800 Subject: [PATCH 047/253] Update welcome.txt --- res/welcome.txt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/res/welcome.txt b/res/welcome.txt index 6cf472a4a..413ea2451 100644 --- a/res/welcome.txt +++ b/res/welcome.txt @@ -2,11 +2,11 @@ Welcome to the qTox for OS X internal nightly installer! Please report all bugs to https://support.libtoxcore.so -######################################################### -## WARNING: Install me to your user ONLY ## -## ## -## Failure to do so WILL break automatic updating ## -## and you WILL be left with and old and ## -## potentially broken qTox install! ## -## ## -######################################################### +######################################################## +## WARNING: Install me to your user ONLY +## +## Failure to do so WILL break automatic updating +## and you WILL be left with and old and +## potentially broken qTox install! +## +######################################################## From 483d235af9cfb237834b6d0b17f65442dee42a3d Mon Sep 17 00:00:00 2001 From: Sean Date: Thu, 13 Nov 2014 19:42:00 -0800 Subject: [PATCH 048/253] Add tox: integration for OS X --- res/info.plist | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/res/info.plist b/res/info.plist index 16ab31c70..6705cb480 100644 --- a/res/info.plist +++ b/res/info.plist @@ -14,5 +14,11 @@ qtox CFBundleIdentifier im.tox.qtox + CFBundleURLName + qtox + CFBundleURLSchemes + + tox + From c166a4813db1a1aaba251a51150946dc66c4e019 Mon Sep 17 00:00:00 2001 From: Sean Date: Thu, 13 Nov 2014 20:49:32 -0800 Subject: [PATCH 049/253] Make everything magically work --- res/info.plist | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/res/info.plist b/res/info.plist index 6705cb480..34ced64a2 100644 --- a/res/info.plist +++ b/res/info.plist @@ -15,10 +15,24 @@ CFBundleIdentifier im.tox.qtox CFBundleURLName - qtox + tox CFBundleURLSchemes - - tox - + + tox + + CFBundleTypeExtensions + + tox + + CFBundleTypeName + Tox profile + CFBundleTypeMIMETypes + + + + CFBundleTypeIconFile + qtox.icns + CFBundleURLIconFile + qtox.icns From 9d96110e9bbf58f8a5ef1d3438e1b0b61261d66a Mon Sep 17 00:00:00 2001 From: apprb Date: Wed, 12 Nov 2014 21:56:24 +0900 Subject: [PATCH 050/253] mysql synchronous set --- src/historykeeper.cpp | 24 ++++++++++++++++++++++++ src/historykeeper.h | 3 +++ src/misc/db/plaindb.h | 4 ++++ src/misc/settings.cpp | 25 ++++++++++++++++++++++++- src/misc/settings.h | 5 +++++ 5 files changed, 60 insertions(+), 1 deletion(-) diff --git a/src/historykeeper.cpp b/src/historykeeper.cpp index a145b1fc4..13d3c9d81 100644 --- a/src/historykeeper.cpp +++ b/src/historykeeper.cpp @@ -130,6 +130,8 @@ HistoryKeeper::HistoryKeeper(GenericDdInterface *db_) : updateChatsID(); updateAliases(); + setSyncType(Settings::getInstance().getDbSyncType()); + QSqlQuery sqlAnswer = db->exec("select seq from sqlite_sequence where name=\"history\";"); sqlAnswer.first(); messageID = sqlAnswer.value(0).toInt(); @@ -315,3 +317,25 @@ void HistoryKeeper::markAsSent(int m_id) { db->exec(QString("UPDATE sent_status SET status = 1 WHERE id = %1;").arg(m_id)); } + +void HistoryKeeper::setSyncType(Db::syncType sType) +{ + QString syncCmd; + + switch (sType) { + case Db::syncType::stFull: + syncCmd = "FULL"; + break; + case Db::syncType::stNormal: + syncCmd = "NORMAL"; + break; + case Db::syncType::stOff: + syncCmd = "OFF"; + break; + default: + syncCmd = "FULL"; + break; + } + + db->exec(QString("PRAGMA synchronous=%1;").arg(syncCmd)); +} diff --git a/src/historykeeper.h b/src/historykeeper.h index bbe050c13..65e7b9e75 100644 --- a/src/historykeeper.h +++ b/src/historykeeper.h @@ -22,6 +22,7 @@ #include class GenericDdInterface; +namespace Db { enum class syncType; } class HistoryKeeper { @@ -51,6 +52,8 @@ public: QList getChatHistory(ChatType ct, const QString &chat, const QDateTime &time_from, const QDateTime &time_to); void markAsSent(int m_id); + void setSyncType(Db::syncType sType); + private: HistoryKeeper(GenericDdInterface *db_); HistoryKeeper(HistoryKeeper &hk) = delete; diff --git a/src/misc/db/plaindb.h b/src/misc/db/plaindb.h index a0b85b8cc..4584bb461 100644 --- a/src/misc/db/plaindb.h +++ b/src/misc/db/plaindb.h @@ -21,6 +21,10 @@ #include +namespace Db { + enum class syncType : int {stOff = 0, stNormal = 1, stFull = 2}; +} + class PlainDb : public GenericDdInterface { public: diff --git a/src/misc/settings.cpp b/src/misc/settings.cpp index d6ef0b506..c6f309c33 100644 --- a/src/misc/settings.cpp +++ b/src/misc/settings.cpp @@ -17,6 +17,7 @@ #include "settings.h" #include "smileypack.h" #include "src/corestructs.h" +#include "src/misc/db/plaindb.h" #include #include @@ -121,12 +122,17 @@ void Settings::load() proxyAddr = s.value("proxyAddr", "").toString(); proxyPort = s.value("proxyPort", 0).toInt(); currentProfile = s.value("currentProfile", "").toString(); - autoAwayTime = s.value("autoAwayTime", 10).toInt(); + autoAwayTime = s.value("autoAwayTime", 10).toInt(); checkUpdates = s.value("checkUpdates", false).toBool(); showInFront = s.value("showInFront", false).toBool(); fauxOfflineMessaging = s.value("fauxOfflineMessaging", true).toBool(); s.endGroup(); + s.beginGroup("Advanced"); + int sType = s.value("dbSyncType", static_cast(Db::syncType::stFull)).toInt(); + setDbSyncType(sType); + s.endGroup(); + s.beginGroup("Widgets"); QList objectNames = s.childKeys(); for (const QString& name : objectNames) { @@ -267,6 +273,10 @@ void Settings::save(QString path) s.setValue("fauxOfflineMessaging", fauxOfflineMessaging); s.endGroup(); + s.beginGroup("Advanced"); + s.setValue("dbSyncType", static_cast(dbSyncType)); + s.endGroup(); + s.beginGroup("Widgets"); const QList widgetNames = widgetSettings.keys(); for (const QString& name : widgetNames) { @@ -597,6 +607,19 @@ void Settings::setEncryptTox(bool newValue) encryptTox = newValue; } +Db::syncType Settings::getDbSyncType() const +{ + return dbSyncType; +} + +void Settings::setDbSyncType(int newValue) +{ + if (newValue >= 0 && newValue <= 2) + dbSyncType = static_cast(newValue); + else + dbSyncType = Db::syncType::stFull; +} + int Settings::getAutoAwayTime() const { return autoAwayTime; diff --git a/src/misc/settings.h b/src/misc/settings.h index 842892b95..49f60f58d 100644 --- a/src/misc/settings.h +++ b/src/misc/settings.h @@ -22,6 +22,7 @@ #include struct ToxID; +namespace Db { enum class syncType; } class Settings : public QObject { @@ -99,6 +100,9 @@ public: bool getEncryptTox() const; void setEncryptTox(bool newValue); + Db::syncType getDbSyncType() const; + void setDbSyncType(int newValue); + int getAutoAwayTime() const; void setAutoAwayTime(int newValue); @@ -278,6 +282,7 @@ private: // Privacy bool typingNotification; + Db::syncType dbSyncType; // Audio QString inDev; From e782a9d5876bdfdeb9e6b1cf9722080d6b813017 Mon Sep 17 00:00:00 2001 From: apprb Date: Thu, 13 Nov 2014 11:30:29 +0900 Subject: [PATCH 051/253] batch processing --- src/historykeeper.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/historykeeper.cpp b/src/historykeeper.cpp index 13d3c9d81..37ba7cb9d 100644 --- a/src/historykeeper.cpp +++ b/src/historykeeper.cpp @@ -147,10 +147,12 @@ int HistoryKeeper::addChatEntry(const QString& chat, const QString& message, con int chat_id = getChatID(chat, ctSingle).first; int sender_id = getAliasID(sender); + db->exec("BEGIN TRANSACTION"); db->exec(QString("INSERT INTO history (timestamp, chat_id, sender, message)") + QString("VALUES (%1, %2, %3, '%4');") .arg(dt.toMSecsSinceEpoch()).arg(chat_id).arg(sender_id).arg(wrapMessage(message))); db->exec(QString("INSERT INTO sent_status (status) VALUES (%1)").arg(isSent)); + db->exec("COMMIT TRANSACTION"); messageID++; return messageID; From e05101942e041f9f4c3ee799f68228acc22478c7 Mon Sep 17 00:00:00 2001 From: Sean Date: Fri, 14 Nov 2014 00:30:37 -0800 Subject: [PATCH 052/253] Fix mimetype, fix displayed name in titlebar, add a version string for internal tracking, declare language support for localization --- res/info.plist | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/res/info.plist b/res/info.plist index 34ced64a2..9368b2f90 100644 --- a/res/info.plist +++ b/res/info.plist @@ -12,6 +12,14 @@ ???? CFBundleExecutable qtox + CFBundleDisplayName + qTox + CFBundleName + qTox + CFBundleVersion + 1.0.0 + CFBundleShortVersionString + 1.0.0-EXPERIMENTIAL CFBundleIdentifier im.tox.qtox CFBundleURLName @@ -28,11 +36,25 @@ Tox profile CFBundleTypeMIMETypes - + tox/x-profile CFBundleTypeIconFile qtox.icns CFBundleURLIconFile qtox.icns + CFBundleLocalizations + + en_US + en + bg_BG + de_DE + fi_FI + fr_FR + it_IT + pl_PL + ru_RU + uk_UA + sv + From 3aa83993022a5d64e6782fb087d93c1242872ae7 Mon Sep 17 00:00:00 2001 From: Sean Date: Fri, 14 Nov 2014 02:48:07 -0800 Subject: [PATCH 053/253] Refactor info.plist This actually works now. --- res/info.plist | 70 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 56 insertions(+), 14 deletions(-) diff --git a/res/info.plist b/res/info.plist index 9368b2f90..7b137ac51 100644 --- a/res/info.plist +++ b/res/info.plist @@ -22,26 +22,68 @@ 1.0.0-EXPERIMENTIAL CFBundleIdentifier im.tox.qtox - CFBundleURLName - tox - CFBundleURLSchemes + CFBundleURLTypes - tox + + CFBundleURLName + Tox URL + CFBundleTypeRole + Viewer + CFBundleURLSchemes + + tox + + CFBundleURLIconFile + qtox.icns + - CFBundleTypeExtensions + CFBundleDocumentTypes - tox + + CFBundleTypeExtensions + + tox + + CFBundleTypeName + Tox profile + CFBundleTypeRole + Editor + CFBundleTypeIconFile + qtox.icns + CFBundleTypeMIMETypes + + tox/x-profile + + LSHandlerRank + Owner + LSItemContentTypes + + public.tox + + - CFBundleTypeName - Tox profile - CFBundleTypeMIMETypes + UTImportedTypeDeclarations - tox/x-profile + + UTTypeConformsTo + + public.data + + UTTypeIdentifier + public.tox + UTTypeTagSpecification + + com.apple.ostype + TOX + public.filename-extension + + tox + + public.mime-type + tox/x-profile + + - CFBundleTypeIconFile - qtox.icns - CFBundleURLIconFile - qtox.icns CFBundleLocalizations en_US From 08f523b8ff410683d8f1429e83dfa28919daac28 Mon Sep 17 00:00:00 2001 From: apprb Date: Thu, 13 Nov 2014 01:30:55 +0900 Subject: [PATCH 054/253] AdvancedSettings tab introduced --- qtox.pro | 9 +- src/widget/form/settings/advancedform.cpp | 62 +++++++++++ src/widget/form/settings/advancedform.h | 43 ++++++++ src/widget/form/settings/advancedsettings.ui | 109 +++++++++++++++++++ src/widget/form/settingswidget.cpp | 4 +- 5 files changed, 223 insertions(+), 4 deletions(-) create mode 100644 src/widget/form/settings/advancedform.cpp create mode 100644 src/widget/form/settings/advancedform.h create mode 100644 src/widget/form/settings/advancedsettings.ui diff --git a/qtox.pro b/qtox.pro index 6fa31b7e0..9543b3af9 100644 --- a/qtox.pro +++ b/qtox.pro @@ -33,7 +33,8 @@ FORMS += \ src/widget/form/settings/privacysettings.ui \ src/widget/form/loadhistorydialog.ui \ src/widget/form/inputpassworddialog.ui \ - src/widget/form/setpassworddialog.ui + src/widget/form/setpassworddialog.ui \ + src/widget/form/settings/advancedsettings.ui CONFIG += c++11 @@ -150,7 +151,8 @@ HEADERS += src/widget/form/addfriendform.h \ src/toxdns.h \ src/widget/toxsave.h \ src/autoupdate.h \ - src/misc/serialize.h + src/misc/serialize.h \ + src/widget/form/settings/advancedform.h SOURCES += \ src/widget/form/addfriendform.cpp \ @@ -215,4 +217,5 @@ SOURCES += \ src/ipc.cpp \ src/widget/toxsave.cpp \ src/autoupdate.cpp \ - src/misc/serialize.cpp + src/misc/serialize.cpp \ + src/widget/form/settings/advancedform.cpp diff --git a/src/widget/form/settings/advancedform.cpp b/src/widget/form/settings/advancedform.cpp new file mode 100644 index 000000000..a579f93e6 --- /dev/null +++ b/src/widget/form/settings/advancedform.cpp @@ -0,0 +1,62 @@ +/* + Copyright (C) 2014 by Project Tox + + This file is part of qTox, a Qt-based graphical interface for Tox. + + This program is libre software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the COPYING file for more details. +*/ + +#include "ui_advancedsettings.h" + +#include "advancedform.h" +#include "src/historykeeper.h" +#include "src/misc/settings.h" +#include "src/misc/db/plaindb.h" + +AdvancedForm::AdvancedForm() : + GenericForm(tr("Advanced"), QPixmap(":/img/settings/general.png")) +{ + bodyUI = new Ui::AdvancedSettings; + bodyUI->setupUi(this); + + bodyUI->dbLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); + bodyUI->dbLabel->setOpenExternalLinks(true); + + bodyUI->syncTypeComboBox->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength); + bodyUI->syncTypeComboBox->addItems({"FULL - very safe, slowest (recommended)", + "NORMAL - almost as safe as FULL, about 20% faster than FULL", + "OFF - disables all safety, when something goes wrong your history may be lost, fastest (not recommended)" + }); + int index = 2 - static_cast(Settings::getInstance().getDbSyncType()); + bodyUI->syncTypeComboBox->setCurrentIndex(index); + + connect(bodyUI->syncTypeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(onDbSyncTypeUpdated())); + connect(bodyUI->resetButton, SIGNAL(clicked()), this, SLOT(resetToDefault())); +} + +AdvancedForm::~AdvancedForm() +{ + delete bodyUI; +} + +void AdvancedForm::onDbSyncTypeUpdated() +{ + int index = 2 - bodyUI->syncTypeComboBox->currentIndex(); + Settings::getInstance().setDbSyncType(index); + HistoryKeeper::getInstance()->setSyncType(Settings::getInstance().getDbSyncType()); +} + +void AdvancedForm::resetToDefault() +{ + int index = 2 - static_cast(Db::syncType::stFull); + bodyUI->syncTypeComboBox->setCurrentIndex(index); + onDbSyncTypeUpdated(); +} diff --git a/src/widget/form/settings/advancedform.h b/src/widget/form/settings/advancedform.h new file mode 100644 index 000000000..2b0d3cbcb --- /dev/null +++ b/src/widget/form/settings/advancedform.h @@ -0,0 +1,43 @@ +/* + Copyright (C) 2014 by Project Tox + + This file is part of qTox, a Qt-based graphical interface for Tox. + + This program is libre software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the COPYING file for more details. +*/ + +#ifndef ADVANCEDFORM_H +#define ADVANCEDFORM_H + +#include "genericsettings.h" + +class Core; + +namespace Ui { +class AdvancedSettings; +} + +class AdvancedForm : public GenericForm +{ + Q_OBJECT +public: + AdvancedForm(); + virtual ~AdvancedForm(); + +private slots: + void onDbSyncTypeUpdated(); + void resetToDefault(); + +private: + Ui::AdvancedSettings* bodyUI; +}; + +#endif // ADVANCEDFORM_H diff --git a/src/widget/form/settings/advancedsettings.ui b/src/widget/form/settings/advancedsettings.ui new file mode 100644 index 000000000..33df4b8ee --- /dev/null +++ b/src/widget/form/settings/advancedsettings.ui @@ -0,0 +1,109 @@ + + + AdvancedSettings + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + + true + + + + + 0 + 0 + 380 + 280 + + + + + + + <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">IMPORTANT NOTE</span></p><p><span style=" color:#ff0000;">Unless you </span><span style=" font-weight:600; color:#ff0000;">really</span><span style=" color:#ff0000;"> know what you are doing, please do </span><span style=" font-weight:600; color:#ff0000;">not</span><span style=" color:#ff0000;"> change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history.</span></p></body></html> + + + Qt::RichText + + + Qt::AlignCenter + + + true + + + + + + + Reset to default settings + + + + + + + History + + + + + + + + <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_synchronous"><span style=" text-decoration: underline; color:#0000ff;">Synchronous writing to DB</span></a></p></body></html> + + + Qt::RichText + + + + + + + + 0 + 0 + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + diff --git a/src/widget/form/settingswidget.cpp b/src/widget/form/settingswidget.cpp index 00c46c514..ad4a95091 100644 --- a/src/widget/form/settingswidget.cpp +++ b/src/widget/form/settingswidget.cpp @@ -22,6 +22,7 @@ #include "src/widget/form/settings/identityform.h" #include "src/widget/form/settings/privacyform.h" #include "src/widget/form/settings/avform.h" +#include "src/widget/form/settings/advancedform.h" #include SettingsWidget::SettingsWidget(QWidget* parent) @@ -54,8 +55,9 @@ SettingsWidget::SettingsWidget(QWidget* parent) IdentityForm* ifrm = new IdentityForm; PrivacyForm* pfrm = new PrivacyForm; AVForm* avfrm = new AVForm; + AdvancedForm *expfrm = new AdvancedForm; - GenericForm* cfgForms[] = { gfrm, ifrm, pfrm, avfrm }; + GenericForm* cfgForms[] = { gfrm, ifrm, pfrm, avfrm, expfrm }; for (GenericForm* cfgForm : cfgForms) settingsWidgets->addTab(cfgForm, cfgForm->getFormIcon(), cfgForm->getFormName()); From d834eaca33fbfb46e57aeb27acd8443635928696 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Fri, 14 Nov 2014 18:12:00 +0100 Subject: [PATCH 055/253] Horrible hack to get timestamps not to hang on windows Turns out date is a valid command on windowndows, and running it waits for user input. That hangs qmake. This hack is a valid batch command that returns 0 and at the same time a valid bash command that tries to run date or return 0 I'm so sorry. --- qtox.pro | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qtox.pro b/qtox.pro index 6fa31b7e0..d3864afa2 100644 --- a/qtox.pro +++ b/qtox.pro @@ -46,7 +46,9 @@ RESOURCES += res.qrc GIT_VERSION = $$system(git rev-parse HEAD 2> /dev/null || echo "built without git") DEFINES += GIT_VERSION=\"\\\"$$quote($$GIT_VERSION)\\\"\" -TIMESTAMP = $$system(date +"%s" || echo 0) +# date works on linux/mac, but it would hangs qmake on windows +# This hack returns 0 on batch (windows), but executes "date +%s" or return 0 if it fails on bash (linux/mac) +TIMESTAMP = $$system($1 2>null||echo 0||a;rm null;date +%s||echo 0) # I'm so sorry DEFINES += TIMESTAMP=$$TIMESTAMP DEFINES += LOG_TO_FILE From 3bc6909c96f01e3e5b0cdd2f4a9a98fa03ab2e95 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Fri, 14 Nov 2014 18:14:33 +0100 Subject: [PATCH 056/253] Apply valgrind fix to 1on1 audio too I haven't seen valgrind complain too much, but let's fix it preemptively --- src/coreav.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/coreav.cpp b/src/coreav.cpp index 604426bdc..6993a12fc 100644 --- a/src/coreav.cpp +++ b/src/coreav.cpp @@ -231,6 +231,7 @@ void Core::sendCallAudio(int callId, ToxAv* toxav) alcGetIntegerv(alInDev, ALC_CAPTURE_SAMPLES, sizeof(samples), &samples); if(samples >= framesize) { + memset(buf, 0, framesize*2); // Avoid uninitialized values (Valgrind) alcCaptureSamples(alInDev, buf, framesize); frame = 1; } From 71c34b9d696bc91df551bb56ee083a8fc01c509f Mon Sep 17 00:00:00 2001 From: dubslow Date: Wed, 5 Nov 2014 18:22:50 -0600 Subject: [PATCH 057/253] group title support --- src/core.cpp | 19 +++++++++++++++++++ src/core.h | 8 ++++++-- src/group.cpp | 6 ++++++ src/group.h | 1 + src/widget/form/groupchatform.cpp | 2 ++ src/widget/form/groupchatform.h | 3 +++ src/widget/groupwidget.cpp | 5 +++++ src/widget/groupwidget.h | 1 + src/widget/widget.cpp | 17 +++++++++++++++-- src/widget/widget.h | 1 + 10 files changed, 59 insertions(+), 4 deletions(-) diff --git a/src/core.cpp b/src/core.cpp index 4a2ad0279..b3eec8ad3 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -269,6 +269,7 @@ void Core::start() tox_callback_group_invite(tox, onGroupInvite, this); tox_callback_group_message(tox, onGroupMessage, this); tox_callback_group_namelist_change(tox, onGroupNamelistChange, this); + tox_callback_group_title(tox, onGroupTitleChange, this); tox_callback_group_action(tox, onGroupAction, this); tox_callback_file_send_request(tox, onFileSendRequestCallback, this); tox_callback_file_control(tox, onFileControlCallback, this); @@ -518,6 +519,16 @@ void Core::onGroupNamelistChange(Tox*, int groupnumber, int peernumber, uint8_t emit static_cast(core)->groupNamelistChanged(groupnumber, peernumber, change); } +void Core::onGroupTitleChange(Tox*, int groupnumber, int peernumber, const uint8_t* title, uint8_t len, void* _core) +{ + qDebug() << "Core: group" << groupnumber << "title changed by" << peernumber; + Core* core = static_cast(_core); + QString author; + if (peernumber >= 0 && !tox_group_peernumber_is_ours(core->tox, groupnumber, peernumber)) + author = core->getGroupPeerName(groupnumber, peernumber); + emit core->groupTitleChanged(groupnumber, author, CString::toString(title, len)); +} + void Core::onFileSendRequestCallback(Tox*, int32_t friendnumber, uint8_t filenumber, uint64_t filesize, const uint8_t *filename, uint16_t filename_length, void *core) { @@ -816,6 +827,14 @@ void Core::sendGroupAction(int groupId, const QString& message) } } +void Core::changeGroupTitle(int groupId, const QString& title) +{ + CString cTitle(title); + int err = tox_group_set_title(tox, groupId, cTitle.data(), cTitle.size()); + if (!err) + emit groupTitleChanged(groupId, "", title); +} + void Core::sendFile(int32_t friendId, QString Filename, QString FilePath, long long filesize) { QMutexLocker mlocker(&fileSendMutex); diff --git a/src/core.h b/src/core.h index e65710c26..738c0a431 100644 --- a/src/core.h +++ b/src/core.h @@ -95,10 +95,11 @@ public slots: void setStatusMessage(const QString& message); void setAvatar(uint8_t format, const QByteArray& data); - int sendMessage(int friendId, const QString& message); + int sendMessage(int friendId, const QString& message); void sendGroupMessage(int groupId, const QString& message); void sendGroupAction(int groupId, const QString& message); - int sendAction(int friendId, const QString& action); + void changeGroupTitle(int groupId, const QString& title); + int sendAction(int friendId, const QString& action); void sendTyping(int friendId, bool typing); void sendFile(int32_t friendId, QString Filename, QString FilePath, long long filesize); @@ -157,6 +158,7 @@ signals: void groupInviteReceived(int friendnumber, uint8_t type, QByteArray publicKey); void groupMessageReceived(int groupnumber, const QString& message, const QString& author, bool isAction); void groupNamelistChanged(int groupnumber, int peernumber, uint8_t change); + void groupTitleChanged(int groupnumber, const QString& author, const QString& title); void usernameSet(const QString& username); void statusMessageSet(const QString& message); @@ -166,6 +168,7 @@ signals: void messageSentResult(int friendId, const QString& message, int messageId); void groupSentResult(int groupId, const QString& message, int result); + void actionSentResult(int friendId, const QString& action, int success); void receiptRecieved(int friedId, int receipt); @@ -221,6 +224,7 @@ private: static void onGroupInvite(Tox *tox, int friendnumber, uint8_t type, const uint8_t *data, uint16_t length,void *userdata); static void onGroupMessage(Tox *tox, int groupnumber, int friendgroupnumber, const uint8_t * message, uint16_t length, void *userdata); static void onGroupNamelistChange(Tox *tox, int groupnumber, int peernumber, uint8_t change, void *userdata); + static void onGroupTitleChange(Tox*, int groupnumber, int peernumber, const uint8_t* title, uint8_t len, void* _core); static void onFileSendRequestCallback(Tox *tox, int32_t friendnumber, uint8_t filenumber, uint64_t filesize, const uint8_t *filename, uint16_t filename_length, void *userdata); static void onFileControlCallback(Tox *tox, int32_t friendnumber, uint8_t receive_send, uint8_t filenumber, diff --git a/src/group.cpp b/src/group.cpp index 37160fef0..7f517ab41 100644 --- a/src/group.cpp +++ b/src/group.cpp @@ -69,3 +69,9 @@ void Group::updatePeer(int peerId, QString name) widget->onUserListChanged(); chatForm->onUserListChanged(); } + +void Group::setName(const QString& name) +{ + widget->setName(name); + chatForm->setName(name); +} diff --git a/src/group.h b/src/group.h index 56c3bebb0..86790b61c 100644 --- a/src/group.h +++ b/src/group.h @@ -35,6 +35,7 @@ public: void addPeer(int peerId, QString name); void removePeer(int peerId); void updatePeer(int peerId, QString newName); + void setName(const QString& name); public: int groupId; diff --git a/src/widget/form/groupchatform.cpp b/src/widget/form/groupchatform.cpp index 28e295f98..0731cf278 100644 --- a/src/widget/form/groupchatform.cpp +++ b/src/widget/form/groupchatform.cpp @@ -52,6 +52,7 @@ GroupChatForm::GroupChatForm(Group* chatGroup) } nameLabel->setText(group->widget->getName()); + nameLabel->setEditable(true); nusersLabel->setFont(Style::getFont(Style::Medium)); nusersLabel->setText(GroupChatForm::tr("%1 users in chat","Number of users in chat").arg(group->peers.size())); @@ -80,6 +81,7 @@ GroupChatForm::GroupChatForm(Group* chatGroup) connect(callButton, &QPushButton::clicked, this, &GroupChatForm::onCallClicked); connect(micButton, SIGNAL(clicked()), this, SLOT(onMicMuteToggle())); connect(volButton, SIGNAL(clicked()), this, SLOT(onVolMuteToggle())); + connect(nameLabel, &CroppingLabel::textChanged, this, [=](QString s, QString) {emit groupTitleChanged(group->groupId, s);} ); setAcceptDrops(true); } diff --git a/src/widget/form/groupchatform.h b/src/widget/form/groupchatform.h index d89c82730..221b1ceae 100644 --- a/src/widget/form/groupchatform.h +++ b/src/widget/form/groupchatform.h @@ -35,6 +35,9 @@ public: void keyPressEvent(QKeyEvent* ev); void keyReleaseEvent(QKeyEvent* ev); +signals: + void groupTitleChanged(int groupnum, const QString& name); + private slots: void onSendTriggered(); void onMicMuteToggle(); diff --git a/src/widget/groupwidget.cpp b/src/widget/groupwidget.cpp index 3e9f149f0..dd52cce94 100644 --- a/src/widget/groupwidget.cpp +++ b/src/widget/groupwidget.cpp @@ -137,3 +137,8 @@ void GroupWidget::keyReleaseEvent(QKeyEvent* ev) if (g) g->chatForm->keyReleaseEvent(ev); } + +void GroupWidget::setName(const QString& name) +{ + nameLabel->setText(name); +} diff --git a/src/widget/groupwidget.h b/src/widget/groupwidget.h index eeb14dae0..53cba6ac8 100644 --- a/src/widget/groupwidget.h +++ b/src/widget/groupwidget.h @@ -32,6 +32,7 @@ public: void updateStatusLight(); void setChatForm(Ui::MainWindow &); void resetEventFlags(); + void setName(const QString& name); signals: void groupWidgetClicked(GroupWidget* widget); diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index e4c9fb91c..d951682f1 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -222,6 +222,7 @@ void Widget::init() connect(core, &Core::groupInviteReceived, this, &Widget::onGroupInviteReceived); connect(core, &Core::groupMessageReceived, this, &Widget::onGroupMessageReceived); connect(core, &Core::groupNamelistChanged, this, &Widget::onGroupNamelistChanged); + connect(core, &Core::groupTitleChanged, this, &Widget::onGroupTitleChanged); connect(core, &Core::emptyGroupCreated, this, &Widget::onEmptyGroupCreated); connect(core, &Core::avInvite, this, &Widget::playRingtone); connect(core, &Core::blockingClearContacts, this, &Widget::clearContactsList, Qt::BlockingQueuedConnection); @@ -847,7 +848,7 @@ void Widget::removeFriend(Friend* f, bool fake) FriendList::removeFriend(f->getFriendID(), fake); core->removeFriend(f->getFriendID(), fake); delete f; - if (ui->mainHead->layout()->isEmpty()) // tux3: this should have covered the case of the bug you "fixed" 5 lines above + if (ui->mainHead->layout()->isEmpty()) onAddClicked(); contactListWidget->hide(); @@ -951,6 +952,17 @@ void Widget::onGroupNamelistChanged(int groupnumber, int peernumber, uint8_t Cha g->updatePeer(peernumber,core->getGroupPeerName(groupnumber, peernumber)); } +void Widget::onGroupTitleChanged(int groupnumber, const QString& author, const QString& title) +{ + Group* g = GroupList::findGroup(groupnumber); + if (!g) + return; + + g->setName(title); + if (!author.isEmpty()) + g->chatForm->addSystemInfoMessage(tr("%1 has set the title to %2").arg(author, title), "silver", QDateTime::currentDateTime()); +} + void Widget::removeGroup(Group* g, bool fake) { g->widget->setAsInactiveChatroom(); @@ -999,6 +1011,7 @@ Group *Widget::createGroup(int groupId) connect(newgroup->widget, SIGNAL(chatroomWidgetClicked(GenericChatroomWidget*)), newgroup->chatForm, SLOT(focusInput())); connect(newgroup->chatForm, SIGNAL(sendMessage(int,QString)), core, SLOT(sendGroupMessage(int,QString))); connect(newgroup->chatForm, SIGNAL(sendAction(int,QString)), core, SLOT(sendGroupAction(int,QString))); + connect(newgroup->chatForm, &GroupChatForm::groupTitleChanged, core, &Core::changeGroupTitle); return newgroup; } @@ -1171,4 +1184,4 @@ void Widget::clearAllReceipts() { f->getChatForm()->clearReciepts(); } -} +} \ No newline at end of file diff --git a/src/widget/widget.h b/src/widget/widget.h index 1131da49a..09a6814a6 100644 --- a/src/widget/widget.h +++ b/src/widget/widget.h @@ -115,6 +115,7 @@ private slots: void onGroupInviteReceived(int32_t friendId, uint8_t type, QByteArray invite); void onGroupMessageReceived(int groupnumber, const QString& message, const QString& author, bool isAction); void onGroupNamelistChanged(int groupnumber, int peernumber, uint8_t change); + void onGroupTitleChanged(int groupnumber, const QString& author, const QString& title); void removeFriend(int friendId); void copyFriendIdToClipboard(int friendId); void removeGroup(int groupId); From f4e0ea83efeb5125ea201d3ffc5dfed0d09ce2c3 Mon Sep 17 00:00:00 2001 From: dubslow Date: Wed, 5 Nov 2014 20:50:32 -0600 Subject: [PATCH 058/253] show self title changes --- src/core.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core.cpp b/src/core.cpp index b3eec8ad3..1456b997b 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -524,7 +524,7 @@ void Core::onGroupTitleChange(Tox*, int groupnumber, int peernumber, const uint8 qDebug() << "Core: group" << groupnumber << "title changed by" << peernumber; Core* core = static_cast(_core); QString author; - if (peernumber >= 0 && !tox_group_peernumber_is_ours(core->tox, groupnumber, peernumber)) + if (peernumber >= 0) author = core->getGroupPeerName(groupnumber, peernumber); emit core->groupTitleChanged(groupnumber, author, CString::toString(title, len)); } @@ -832,7 +832,7 @@ void Core::changeGroupTitle(int groupId, const QString& title) CString cTitle(title); int err = tox_group_set_title(tox, groupId, cTitle.data(), cTitle.size()); if (!err) - emit groupTitleChanged(groupId, "", title); + emit groupTitleChanged(groupId, getUsername(), title); } void Core::sendFile(int32_t friendId, QString Filename, QString FilePath, long long filesize) From b270b1fd2e6267a5f051de3f4d273387204ba313 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=83=D1=85=D0=B0=D1=80=D0=B8=D0=BA?= Date: Fri, 14 Nov 2014 21:38:02 +0300 Subject: [PATCH 059/253] update --- translations/ru.ts | 295 ++++++++++++++++++++++----------------------- 1 file changed, 142 insertions(+), 153 deletions(-) diff --git a/translations/ru.ts b/translations/ru.ts index d8120fe12..1384d5122 100644 --- a/translations/ru.ts +++ b/translations/ru.ts @@ -123,97 +123,93 @@ Ignore the proxy and connect to the Internet directly ? DNS error Такого Tox ID не существует +
+ + AdvancedForm - The connection timed out - The DNS gives the Tox ID associated to toxme.se addresses - Время ожидания соединения истекло + + Advanced + Расширенные + + + + AdvancedSettings + + + Form + - This address does not exist - The DNS gives the Tox ID associated to toxme.se addresses - Нет такого адреса + + <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">IMPORTANT NOTE</span></p><p><span style=" color:#ff0000;">Unless you </span><span style=" font-weight:600; color:#ff0000;">really</span><span style=" color:#ff0000;"> know what you are doing, please do </span><span style=" font-weight:600; color:#ff0000;">not</span><span style=" color:#ff0000;"> change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history.</span></p></body></html> + <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">ВНИМАНИЕ</span></p><p><span style=" color:#ff0000;">За исключением случая, когда вы </span><span style=" font-weight:600; color:#ff0000;">точно</span><span style=" color:#ff0000;"> знаете, что делаете, пожалуйста, </span><span style=" font-weight:600; color:#ff0000;">не</span><span style=" color:#ff0000;"> меняйте здесь ничего. Изменения могут привести к неприятностям, и даже вызвать потерю данных (например, истории переписки).</span></p></body></html> - Error while looking up DNS - The DNS gives the Tox ID associated to toxme.se addresses - Ошибка при просмотре DNS + + Reset to default settings + Вернуть стандартные настройки - No text record found - Error with the DNS - Текстовых записей не найдено + + History + История - Unexpected number of values in text record - Error with the DNS - Непредвиденное количество значений в текстовой записи - - - The version of Tox DNS used by this server is not supported - Error with the DNS - Используемая сервером версия Tox DNS не поддерживается - - - The DNS lookup does not contain any Tox ID - Error with the DNS - В ответе DNS ни одного Tox ID - - - The DNS lookup does not contain a valid Tox ID - Error with the DNS - Ответ DNS не содержит корректных Tox ID + + <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_synchronous"><span style=" text-decoration: underline; color:#0000ff;">Synchronous writing to DB</span></a></p></body></html> + <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_synchronous"><span style=" text-decoration: underline; color:#0000ff;">Синхронизация записи в БД</span></a></p></body></html> ChatForm - + Load History... Загрузить историю... - + Send a file Отправить файл - + Bad Idea Плохая идея - + You're trying to send a special (sequential) file, that's not going to work! ...передаёте последовательный файл и получаете te-le-fun-ken. И переводчик работает по другой линии. По линии «Библиотека». Вы пытаетесь отправить специальный (последовательный) файл. Это так не работает! - + %1 calling %1 звонит - + %1 stopped calling %1 прекратил звонить - + Calling to %1 Звоним %1 - + Call rejected Звонок отклонён - + Call with %1 ended. %2 Разговор с %1 завершился. %2 - + Call duration: Длительность разговора: @@ -229,106 +225,106 @@ Ignore the proxy and connect to the Internet directly ? Core - + Toxing on qTox Как-то так. Может, можно ещё что-нибудь придумать? Всем привет из qTox'а - + qTox User Пользователь qTox - + Friend is already added Друг уже добавлен - + Encryption error Ошибка шифрования - + The .tox file is encrypted, but encryption was not checked, continuing regardless. Файл .tox зашифрован, однако шифрование в настройках включено не было. Продолжаем вопреки. - + Tox datafile decryption password Пароль для расшифровки файла данных Tox - - - + + + Password error Ошибка пароля - - + + Failed to setup password. Empty password. Не удалось установить пароль. Пустой пароль. - + Try Again Попробуйте ещё - + Change profile Сменить профиль - + Reinit current profile Увы, никто не знает, что разработчики имели в виду. Ту би транслейтед. Сбросить данные текущего профиля - + Wrong password has been entered Введён неправильный пароль - + History Log decryption password Пароль для расшифровки журнала переписки - + Encrypted log Зашифрованный журнал - + Your history is encrypted with different password Do you want to try another password? Ваша переписка зашифрована другим паролем Ходите попробовать другой пароль? - + Loggin Журналирование - + Due to incorret password logging will be disabled Из-за некорректного пароля журналирование будет отключено - + NO Password БЕЗ пароля - + Will be saved without encryption! Будет сохранено без шифрования! @@ -473,25 +469,25 @@ Do you want to try another password? Общие - - + + None Отсутствует - + Choose an auto accept directory popup title Выбрать папку для автоматического приёма - + Call active popup title Идёт звонок - + You can't disconnect while a call is active! popup text Нельзя отключиться пока идёт звонок! @@ -511,114 +507,119 @@ Do you want to try another password? Перевод не изменится до перезапуска qTox. - + + Show system tray + Показывать иконку в трее + + + Close to tray Сворачивать в трей при закрытии - + Minimize to tray Сворачивать в трей - + Check for updates on startup (unstable) Проверять на наличие обновлений (нестабильно) - + Focus qTox when a message is received Захватывать фокус при приёме сообщений - + Faux offline messaging Имитация офлайнового обмена сообщениями - + Auto away after (0 to disable) Менять статус на «Отошёл» после (0 — не менять) - + Set to 0 to disable Укажите 0, чтобы отключить - + Use emoticons Использовать смайлики - + Smiley Pack Text on smiley pack label Набор смайликов - + Style Стиль - + Emoticon size Размер смайликов - + px По аналогии с Мпикс. Хотя, может лучше принять http://ilyabirman.ru/meanwhile/all/px/? пикс - + Timestamp format Формат времени - + Connection Settings Настройки соединения - + Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Включить IPv6 (рекомендуется) - + Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Отключение позволяет, например, использовать Tox поверх Tor. Однако это добавляет нагрузку на сеть Tox, так что отключайте только в случае необходимости. - + Enable UDP (recommended) Text on checkbox to disable UDP Включить UDP (рекомендуется) - + Use proxy (SOCKS5) Использовать прокси (SOCKS5) - + Address Text on proxy addr label Адрес - + Port Text on proxy port label Порт - + Reconnect reconnect button Переподключиться @@ -640,101 +641,96 @@ Do you want to try another password? Портативный режим - + Start in tray Запускать свёрнутым в трей - + Show contacts' status changes Показывать изменения статусов контактов - + Provided in minutes Выставлено в минутах - + minutes минут - + Autoaccept files Автоматически принимать файлы - + Save files in Сохранять в - + PushButton - + Theme Тема - - This allows, e.g., toxing over Tor. It adds load to the Tox network however, so use only when necessary. - force tcp checkbox tooltip - Это позволяет, например, использовать tox поверх Tor. Однако это добавляет нагрузку на сеть Tox, так что используйте только в случае необходимости. - GenericChatForm - + Send message Отправить сообщение - + Smileys Смайлики - + Send file(s) Отправить файл(ы) - + Audio call Позвонить - + Video call Видеозвонок - + Toggle speakers volume Включить или выключить звук - + Toggle microphone Включить или выключить микрофон - - + + Save chat log Сохранить журнал чата - + Clear displayed messages Очистить показываемые сообщения - + Cleared Очищено @@ -742,13 +738,13 @@ Do you want to try another password? GroupChatForm - + %1 users in chat Number of users in chat %1 пользователей в чате - + %1 users in chat %1 пользователей в чате @@ -1114,13 +1110,13 @@ Do you want to delete old history file? QObject - + Update The title of a message box Обновление - + An update is available, do you want to download it now ? It will be installed when qTox restarts. Обновление доступно, не желаете ли скачать его прамо сейчас? @@ -1275,18 +1271,6 @@ It will be installed when qTox restarts. Widget - - online - в сети - - - away - отошёл - - - busy - занят - Online @@ -1313,130 +1297,135 @@ It will be installed when qTox restarts. Сменить статус на: - + Online Button to set your status to 'Online' В сети - + Away Button to set your status to 'Away' Вероятно, это не столь долгое путешествие Отошёл - + Busy Button to set your status to 'Busy' Занят - + Choose a profile Выберите профиль - + Please choose which identity to use Выберите личность, которую хотите использовать - + Choose a profile picture Выбрать картинку для профиля - - - + + + Error Ошибка - + Unable to open this file Невозможно открыть файл - + Unable to read this image Невозможно прочесть это изображение - + This image is too big Это изображение слишком большое - + Toxcore failed to start, the application will terminate after you close this message. Не удалось запустить toxcore, приложение будет завершено после того как вы закроете это сообщение. - + toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text Не удалось запустить toxcore с вашими настройками прокси, qTox не может работать; измените ваши настройки и перезапустите его. - + Add friend Добавить друга - + File transfers Передачи файлов - + Settings Настройки - + Couldn't request friendship Не удалось запросить добавление в друзья - + away contact status отсутствует - + busy contact status занят - + offline contact status офлайн - + online contact status в сети - + %1 is now %2 e.g. "Dubslow is now online" %1 сейчас %2 - + <Unknown> Placeholder when we don't know someone's name in a group chat <Неизвестный> - + + %1 has set the title to %2 + %1 сменил заголовок на %2 + + + Message failed to send Не удалось отправить сообщение From b8031498db6077c26adef418018c5ae75976d222 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Fri, 14 Nov 2014 20:12:23 +0100 Subject: [PATCH 060/253] Fix CroppingLabel emitting textChanged twice --- src/widget/croppinglabel.cpp | 3 +++ src/widget/widget.cpp | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/widget/croppinglabel.cpp b/src/widget/croppinglabel.cpp index d9022402a..9db23d0b1 100644 --- a/src/widget/croppinglabel.cpp +++ b/src/widget/croppinglabel.cpp @@ -94,6 +94,9 @@ bool CroppingLabel::eventFilter(QObject *obj, QEvent *e) // events fired by the QLineEdit if (obj == textEdit) { + if (!textEdit->isVisible()) + return false; + if (e->type() == QEvent::KeyPress) { QKeyEvent* keyEvent = static_cast(e); diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index d951682f1..6d61708df 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -1184,4 +1184,4 @@ void Widget::clearAllReceipts() { f->getChatForm()->clearReciepts(); } -} \ No newline at end of file +} From c7d68824794e1564406cdcf1e66861cd53219c7b Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Fri, 14 Nov 2014 20:31:23 +0100 Subject: [PATCH 061/253] Fix timers called from the wrong threads --- src/core.h | 4 ++-- src/coreav.cpp | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/core.h b/src/core.h index 738c0a431..904a8212c 100644 --- a/src/core.h +++ b/src/core.h @@ -118,8 +118,8 @@ public slots: void micMuteToggle(int callId); void volMuteToggle(int callId); - static void joinGroupCall(int groupId); ///< Starts a call in an existing AV groupchat - static void leaveGroupCall(int groupId); ///< Will not leave the group, just stop the call + static void joinGroupCall(int groupId); ///< Starts a call in an existing AV groupchat. Call from the GUI thread. + static void leaveGroupCall(int groupId); ///< Will not leave the group, just stop the call. Call from the GUI thread. static void disableGroupCallMic(int groupId); static void disableGroupCallVol(int groupId); static void enableGroupCallMic(int groupId); diff --git a/src/coreav.cpp b/src/coreav.cpp index 6993a12fc..a00561110 100644 --- a/src/coreav.cpp +++ b/src/coreav.cpp @@ -624,7 +624,6 @@ void Core::joinGroupCall(int groupId) // Go ToxAv* toxav = Core::getInstance()->toxav; groupCalls[groupId].sendAudioTimer = new QTimer(); - groupCalls[groupId].sendAudioTimer->moveToThread(coreThread); groupCalls[groupId].active = true; groupCalls[groupId].sendAudioTimer->setInterval(5); groupCalls[groupId].sendAudioTimer->setSingleShot(true); From d99c682e0009d18ee7be6d557695801f5878e645 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Fri, 14 Nov 2014 20:55:32 +0100 Subject: [PATCH 062/253] Add some debug messages --- src/coreav.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/coreav.cpp b/src/coreav.cpp index a00561110..3b3de1785 100644 --- a/src/coreav.cpp +++ b/src/coreav.cpp @@ -622,12 +622,20 @@ void Core::joinGroupCall(int groupId) alcCaptureStart(alInDev); // Go - ToxAv* toxav = Core::getInstance()->toxav; + Core* core = Core::getInstance(); + ToxAv* toxav = core->toxav; + + qWarning()<<"Core is "<<(long)core<<", toxav is "<<(long)toxav; + groupCalls[groupId].sendAudioTimer = new QTimer(); groupCalls[groupId].active = true; groupCalls[groupId].sendAudioTimer->setInterval(5); groupCalls[groupId].sendAudioTimer->setSingleShot(true); - connect(groupCalls[groupId].sendAudioTimer, &QTimer::timeout, [=](){sendGroupCallAudio(groupId,toxav);}); + connect(groupCalls[groupId].sendAudioTimer, &QTimer::timeout, [=]() + { + qWarning()<<"In the lambda toxav is "<<(long)toxav; + sendGroupCallAudio(groupId,toxav);} + ); groupCalls[groupId].sendAudioTimer->start(); } From 7c6f233d537613f56ef9d9b969242c83ded02cf3 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Fri, 14 Nov 2014 21:16:28 +0100 Subject: [PATCH 063/253] Remove debug statements --- src/coreav.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/coreav.cpp b/src/coreav.cpp index 3b3de1785..75733b2d8 100644 --- a/src/coreav.cpp +++ b/src/coreav.cpp @@ -625,17 +625,11 @@ void Core::joinGroupCall(int groupId) Core* core = Core::getInstance(); ToxAv* toxav = core->toxav; - qWarning()<<"Core is "<<(long)core<<", toxav is "<<(long)toxav; - groupCalls[groupId].sendAudioTimer = new QTimer(); groupCalls[groupId].active = true; groupCalls[groupId].sendAudioTimer->setInterval(5); groupCalls[groupId].sendAudioTimer->setSingleShot(true); - connect(groupCalls[groupId].sendAudioTimer, &QTimer::timeout, [=]() - { - qWarning()<<"In the lambda toxav is "<<(long)toxav; - sendGroupCallAudio(groupId,toxav);} - ); + connect(groupCalls[groupId].sendAudioTimer, &QTimer::timeout, [=](){sendGroupCallAudio(groupId,toxav);}); groupCalls[groupId].sendAudioTimer->start(); } From 89e90d07342b96c1b8460f3c5373497b1cf8881d Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Fri, 14 Nov 2014 22:33:39 +0100 Subject: [PATCH 064/253] Update OSX auto-updates public key --- src/autoupdate.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/autoupdate.cpp b/src/autoupdate.cpp index 2c62ff691..a9189f1c9 100644 --- a/src/autoupdate.cpp +++ b/src/autoupdate.cpp @@ -52,8 +52,8 @@ const QString AutoUpdater::updateServer = "https://dist-build.tox.im"; unsigned char AutoUpdater::key[crypto_sign_PUBLICKEYBYTES] = { - 0x9c, 0x7a, 0x0b, 0x2a, 0xe9, 0xdc, 0x33, 0xf7, 0x74, 0x9f, 0xec, 0x14, 0x29, 0x22, 0x06, 0x4b, - 0xbe, 0x37, 0xe1, 0x9b, 0xb7, 0x18, 0xb4, 0xae, 0x8f, 0x29, 0x6b, 0x9d, 0xfe, 0xd6, 0x39, 0x3f + 0x12, 0x86, 0x25, 0x05, 0xb8, 0x9b, 0x39, 0x6f, 0xf1, 0xb1, 0xc4, 0x4d, 0x6f, 0x39, 0x35, 0x4d, + 0xea, 0xdf, 0x6c, 0x97, 0x98, 0x7d, 0x6f, 0x1c, 0x29, 0xf5, 0xb2, 0x3a, 0x5b, 0x78, 0xc1, 0x34 }; #else From ed76d1621ee44ca76f9be17af4de4f653df3b8f5 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Fri, 14 Nov 2014 22:44:09 +0100 Subject: [PATCH 065/253] Fix #752 --- src/widget/form/genericchatform.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/widget/form/genericchatform.cpp b/src/widget/form/genericchatform.cpp index 71dc14771..671327cc5 100644 --- a/src/widget/form/genericchatform.cpp +++ b/src/widget/form/genericchatform.cpp @@ -127,10 +127,12 @@ GenericChatForm::GenericChatForm(QWidget *parent) : headLayout->addWidget(videoButton); headLayout->setSpacing(0); + volMicLayout->addStretch(); volMicLayout->addSpacing(1); volMicLayout->addWidget(micButton); volMicLayout->addSpacing(2); volMicLayout->addWidget(volButton); + volMicLayout->addStretch(); headTextLayout->addStretch(); headTextLayout->addWidget(nameLabel); From 6aceab27467df25545eff7130a7802085be7f777 Mon Sep 17 00:00:00 2001 From: Sean Qureshi Date: Fri, 14 Nov 2014 13:59:46 -0800 Subject: [PATCH 066/253] Move to its own folder, fix mimetype --- {res => osx}/gplv3.rtf | 0 {res => osx}/info.plist | 2 +- {res => osx}/welcome.txt | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename {res => osx}/gplv3.rtf (100%) rename {res => osx}/info.plist (98%) rename {res => osx}/welcome.txt (100%) diff --git a/res/gplv3.rtf b/osx/gplv3.rtf similarity index 100% rename from res/gplv3.rtf rename to osx/gplv3.rtf diff --git a/res/info.plist b/osx/info.plist similarity index 98% rename from res/info.plist rename to osx/info.plist index 7b137ac51..4f210b78c 100644 --- a/res/info.plist +++ b/osx/info.plist @@ -52,7 +52,7 @@ qtox.icns CFBundleTypeMIMETypes - tox/x-profile + application/x-tox.profile LSHandlerRank Owner diff --git a/res/welcome.txt b/osx/welcome.txt similarity index 100% rename from res/welcome.txt rename to osx/welcome.txt From 96e7acaeaa02d2f5237fa5fba888b316c7eee11f Mon Sep 17 00:00:00 2001 From: Sean Qureshi Date: Fri, 14 Nov 2014 17:05:33 -0800 Subject: [PATCH 067/253] port over makedist --- {res => osx}/makedist.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {res => osx}/makedist.sh (100%) diff --git a/res/makedist.sh b/osx/makedist.sh similarity index 100% rename from res/makedist.sh rename to osx/makedist.sh From 6565ebc0e4ef5c8b603b8d22adb8377a587502be Mon Sep 17 00:00:00 2001 From: Sean Qureshi Date: Fri, 14 Nov 2014 17:06:28 -0800 Subject: [PATCH 068/253] Local install only --- osx/makedist.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osx/makedist.sh b/osx/makedist.sh index 8f6fa2145..10059b9b0 100644 --- a/osx/makedist.sh +++ b/osx/makedist.sh @@ -9,7 +9,7 @@ echo " qTox - + From d607c089d89af48acd303b71da461d3c770550ed Mon Sep 17 00:00:00 2001 From: Sean Date: Fri, 14 Nov 2014 17:30:47 -0800 Subject: [PATCH 069/253] Update for new folder --- qtox.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qtox.pro b/qtox.pro index 0831fccfd..5733a9aa0 100644 --- a/qtox.pro +++ b/qtox.pro @@ -69,7 +69,7 @@ win32 { macx { BUNDLEID = im.tox.qtox ICON = img/icons/qtox.icns - QMAKE_INFO_PLIST = res/info.plist + QMAKE_INFO_PLIST = osx/info.plist LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -ltoxencryptsave -ltoxdns -lsodium -lvpx -framework OpenAL -lopencv_core -lopencv_highgui } else { # If we're building a package, static link libtox[core,av] and libsodium, since they are not provided by any package From 843cd82034f3484fdcddd18bbdc0e4fb4f800a30 Mon Sep 17 00:00:00 2001 From: Sean Qureshi Date: Fri, 14 Nov 2014 19:42:12 -0800 Subject: [PATCH 070/253] New icon proposal --- img/icons/qtox.icns | Bin 66589 -> 142320 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/img/icons/qtox.icns b/img/icons/qtox.icns index bb96d3ae33376642336b63d6ff2eb3fac8a8127f..27c89d3dbbc6c206d687acbcadc6631c93c37755 100644 GIT binary patch literal 142320 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�a<-xYZ|Wc_8`33KwU; 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 Date: Fri, 14 Nov 2014 20:07:05 -0800 Subject: [PATCH 071/253] Proper folder icon for OS X --- img/icons/qtox_profile.icns | Bin 0 -> 69309 bytes osx/info.plist | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 img/icons/qtox_profile.icns diff --git a/img/icons/qtox_profile.icns b/img/icons/qtox_profile.icns new file mode 100644 index 0000000000000000000000000000000000000000..a2f448f34fe62e7456592077c5e93217ece2321c GIT binary patch literal 69309 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 hjX!XUZM50065v?^FN) literal 0 HcmV?d00001 diff --git a/osx/info.plist b/osx/info.plist index 4f210b78c..db5325176 100644 --- a/osx/info.plist +++ b/osx/info.plist @@ -34,7 +34,7 @@ tox CFBundleURLIconFile - qtox.icns + qtox_profile.icns CFBundleDocumentTypes @@ -49,7 +49,7 @@ CFBundleTypeRole Editor CFBundleTypeIconFile - qtox.icns + qtox_profile.icns CFBundleTypeMIMETypes application/x-tox.profile From d289c5e7b8443407dfca24bf2e24b727cd78ec69 Mon Sep 17 00:00:00 2001 From: Ansa89 Date: Sat, 15 Nov 2014 10:40:26 +0100 Subject: [PATCH 072/253] Italian translation: update --- translations/it.ts | 303 ++++++++++++++++++++++++++++----------------- 1 file changed, 187 insertions(+), 116 deletions(-) diff --git a/translations/it.ts b/translations/it.ts index 9ce6fb68f..01e0637e8 100644 --- a/translations/it.ts +++ b/translations/it.ts @@ -123,55 +123,91 @@ Ignorare le impostazioni del proxy e connettersi direttamente alla rete Tox?Questo Tox ID non esiste + + AdvancedForm + + + Advanced + Avanzate + + + + AdvancedSettings + + + Form + Form + + + + <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">IMPORTANT NOTE</span></p><p><span style=" color:#ff0000;">Unless you </span><span style=" font-weight:600; color:#ff0000;">really</span><span style=" color:#ff0000;"> know what you are doing, please do </span><span style=" font-weight:600; color:#ff0000;">not</span><span style=" color:#ff0000;"> change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history.</span></p></body></html> + <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">IMPORTATNTE</span></p><p><span style=" color:#ff0000;">Se non sai </span><span style=" font-weight:600; color:#ff0000;">esattamente</span><span style=" color:#ff0000;"> cosa stai facendo, </span><span style=" font-weight:600; color:#ff0000;">non</span><span style=" color:#ff0000;"> cambiare niente. Le modifiche possono portare a problemi nel funzionamento di qTox e/o alla perdita di dati (per esempio i log delle chat).</span></p></body></html> + + + + Reset to default settings + Reimposta impostazioni di default + + + + History + Chat Log + + + + <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_synchronous"><span style=" text-decoration: underline; color:#0000ff;">Synchronous writing to DB</span></a></p></body></html> + <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_synchronous"><span style=" text-decoration: underline; color:#0000ff;">Scritture sincrone sul DB + + ChatForm - + Load History... Carica log... - + Send a file Invia un file - + Bad Idea Pessima idea - + You're trying to send a special (sequential) file, that's not going to work! Stai cercando di inviare un file speciale (sequenziale), questo non funzionerà! - + %1 calling %1 ti sta chiamando - + %1 stopped calling %1 ha fermato la chiamata - + Calling to %1 Stai chiamando %1 - + Call with %1 ended. %2 Chiamata con %1 terminata. %2 - + Call duration: Durata chiamata: - + Call rejected Chiamata rifiutata @@ -187,105 +223,105 @@ Ignorare le impostazioni del proxy e connettersi direttamente alla rete Tox? Core - + Toxing on qTox Toxing on qTox - + qTox User qTox User - + Friend is already added Questo contatto è già presente nella tua lista - + Encryption error Errore crittografia - + The .tox file is encrypted, but encryption was not checked, continuing regardless. Il Tox datafile è criptato, ma la crittografia non è abilitata nelle impostazioni. Continuo ignorando le impostazioni. - + Tox datafile decryption password Password per decriptare il Tox datafile - - - + + + Password error Errore password - - + + Failed to setup password. Empty password. Impossibile impostare la password. Password vuota. - + Try Again Riprova - + Change profile Cambia profilo - + Reinit current profile Reinizializza il profilo corrente - + Wrong password has been entered È stata inserita una password sbagliata - + History Log decryption password Password per decriptare i log - + Encrypted log Log criptato - + Your history is encrypted with different password Do you want to try another password? I log sono criptati con una password diversa. Vuoi provare un'altra password? - + Loggin Logging - + Due to incorret password logging will be disabled I log saranno disabilitati a causa di una password incorretta - + NO Password Nessuna password - + Will be saved without encryption! Il Tox datafile sarà salvato senza password! @@ -427,25 +463,25 @@ Vuoi provare un'altra password? Generale - - + + None Nessuno - + Choose an auto accept directory popup title Scegli dove salvare i files accettati automaticamente - + Call active popup title Chiamata in corso - + You can't disconnect while a call is active! popup text Non puoi disconnetterti mentre c'è una chiamata in corso! @@ -465,17 +501,17 @@ Vuoi provare un'altra password? La traduzione potrebbe non essere caricata fino al prossimo riavvio di qTox. - + Set to 0 to disable Imposta 0 per disabilitare - + Connection Settings Impostazioni Connessione - + Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Abilita IPv6 (consigliato) @@ -497,137 +533,142 @@ Vuoi provare un'altra password? Rendi qTox portabile - + + Show system tray + Mostra icona nella traybar + + + Start in tray Avvia nella traybar - + Close to tray Chiudi nella traybar - + Minimize to tray Minimizza nella traybar - + Show contacts' status changes Mostra quando i contatti cambiano stato - + Check for updates on startup (unstable) Controlla aggiornamenti all'avvio (unstable) - + Focus qTox when a message is received Dai il focus a qTox quando arriva un messaggio - + Faux offline messaging Falsi messaggi offline - + Provided in minutes Espresso in minuti - + Auto away after (0 to disable) Imposta assenza dopo - + minutes minuti - + Autoaccept files Accetta automaticamente i trasferimenti di files - + Save files in Salva i files in - + PushButton Sfoglia - + Theme Impostazioni Tema - + Use emoticons Usa emoticons - + Smiley Pack Text on smiley pack label Emoticons - + Style Stile - + Emoticon size Dimensione emoticons - + px px - + Timestamp format Formato data/ora - + Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Disabilitando questo sarà possibile usare qTox con Tor. Tuttavia verrà aggiunto carico alla rete Tox, quindi disabilitare solo se necessario. - + Enable UDP (recommended) Text on checkbox to disable UDP Abilita UDP (consigliato) - + Use proxy (SOCKS5) Usa proxy (SOCKS5) - + Address Text on proxy addr label IP - + Port Text on proxy port label Porta - + Reconnect reconnect button Riconnetti @@ -636,53 +677,53 @@ Vuoi provare un'altra password? GenericChatForm - + Send message Invia messaggio - + Smileys Emoticons - + Send file(s) Invia file(s) - + Audio call Chiamata audio - + Video call Videochiamata - + Toggle speakers volume Cambia il volume degli altoparlanti - + Toggle microphone Attiva/Disattiva microfono - - + + Save chat log Salva il log della chat - + Clear displayed messages Rimuovi messaggi visualizzati - + Cleared Pulito @@ -690,13 +731,13 @@ Vuoi provare un'altra password? GroupChatForm - + %1 users in chat Number of users in chat %1 utenti in chat - + %1 users in chat %1 utenti in chat @@ -772,61 +813,81 @@ Vuoi provare un'altra password? Tox save file (*.tox) - + + Failed to remove file + Impossibile rimuovere il file + + + + The file you chose to overwrite could not be removed first. + Il file che hai scelto di sovrascrivere non può essere prima rimosso. + + + + Failed to copy file + Impossibile copiare il file + + + + The file you chose could not be written to. + Il file che hai scelto non può essere copiato. + + + Profile currently loaded current profile deletion warning title Profilo attualmente in uso - + This profile is currently in use. Please load a different profile before deleting this one. current profile deletion warning text Questo profilo è attualmente in uso. Per favore carica un profilo differente prima di eliminare questo. - + Deletion imminent! deletion confirmation title Eliminazione imminente! - + Are you sure you want to delete this profile? deletion confirmation text Sei sicuro di voler eliminare questo profilo? - + Import profile import dialog title Importa profilo - + Tox save file (*.tox) import dialog filter Tox save file (*.tox) - + Ignoring non-Tox file popup title File ignorato - + Warning: you've chosen a file that is not a Tox save file; ignoring. popup text Attenzione: hai scelto un file che non contiente un profilo Tox.\nQuesto file verrà ignorato. - + Profile already exists import confirm title Profilo già esistente - + A profile named "%1" already exists. Do you want to erase it? import confirm text Un profilo chiamato "%1" esiste già. Vuoi sovrascriverlo? @@ -1082,18 +1143,23 @@ Vuoi eliminare il vecchio file? %1.tox è stato importato con successo - + Update The title of a message box Nuova versione - + An update is available, do you want to download it now ? It will be installed when qTox restarts. È disponibile una nuova versione di qTox, vuoi scaricarla adesso? Verrà installata al riavvio del programma. + + + Tox URI to parse + URI Tox da interpretare + SetPasswordDialog @@ -1224,129 +1290,134 @@ Verrà installata al riavvio del programma. Cambia stato in: - + Online Button to set your status to 'Online' Online - + Away Button to set your status to 'Away' Assente - + Busy Button to set your status to 'Busy' Occupato - + Choose a profile Scegli un profilo - + Please choose which identity to use Per favore scegli quale identità usare - + Choose a profile picture Scegli un'immagine per il profilo - - - + + + Error Errore - + Unable to open this file Impossibile aprire il file - + Unable to read this image Impossibile leggere l'immagine - + This image is too big L'immagine è troppo grande - + Toxcore failed to start, the application will terminate after you close this message. Impossibile avviare Toxcore.\nqTox terminerà dopo che avrai chiuso questo messaggio. - + toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text Impossibile avviare Toxcore con le tue impostazione proxy.\nqTox non può funzionare correttamente, per favore modifica le impostazioni e riavvia il programma. - + Add friend Aggiungi contatto - + File transfers Files trasferiti - + Settings Impostazioni - + Couldn't request friendship Impossibile inviare la richiesta d'amicizia - + away contact status assente - + busy contact status occupato - + offline contact status offline - + online contact status online - + %1 is now %2 e.g. "Dubslow is now online" %1 è ora %2 - + <Unknown> Placeholder when we don't know someone's name in a group chat <Sconosciuto> - + + %1 has set the title to %2 + %1 ha impostato il titolo in %2 + + + Message failed to send Impossibile inviare il messaggio From 1d2c0e1cac92ea6db8b7fd071bd41d0ddb59c439 Mon Sep 17 00:00:00 2001 From: Zetok Zalbavar Date: Sat, 15 Nov 2014 10:07:55 +0000 Subject: [PATCH 073/253] Make sql tuning strings translatable --- src/widget/form/settings/advancedform.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/widget/form/settings/advancedform.cpp b/src/widget/form/settings/advancedform.cpp index a579f93e6..44d4ecd13 100644 --- a/src/widget/form/settings/advancedform.cpp +++ b/src/widget/form/settings/advancedform.cpp @@ -31,9 +31,9 @@ AdvancedForm::AdvancedForm() : bodyUI->dbLabel->setOpenExternalLinks(true); bodyUI->syncTypeComboBox->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength); - bodyUI->syncTypeComboBox->addItems({"FULL - very safe, slowest (recommended)", - "NORMAL - almost as safe as FULL, about 20% faster than FULL", - "OFF - disables all safety, when something goes wrong your history may be lost, fastest (not recommended)" + bodyUI->syncTypeComboBox->addItems({tr("FULL - very safe, slowest (recommended)"), + tr("NORMAL - almost as safe as FULL, about 20% faster than FULL"), + tr("OFF - disables all safety, when something goes wrong your history may be lost, fastest (not recommended)") }); int index = 2 - static_cast(Settings::getInstance().getDbSyncType()); bodyUI->syncTypeComboBox->setCurrentIndex(index); From e51c154c2082b879c52ca6578c95ab559bcdf099 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Sat, 15 Nov 2014 16:28:43 +0100 Subject: [PATCH 074/253] Add debug output --- src/coreav.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/coreav.cpp b/src/coreav.cpp index 75733b2d8..089ba93ef 100644 --- a/src/coreav.cpp +++ b/src/coreav.cpp @@ -231,6 +231,8 @@ void Core::sendCallAudio(int callId, ToxAv* toxav) alcGetIntegerv(alInDev, ALC_CAPTURE_SAMPLES, sizeof(samples), &samples); if(samples >= framesize) { + qDebug() << "Samples:"< Date: Sat, 15 Nov 2014 16:36:07 +0100 Subject: [PATCH 075/253] Remove debug output --- src/coreav.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/coreav.cpp b/src/coreav.cpp index 089ba93ef..75733b2d8 100644 --- a/src/coreav.cpp +++ b/src/coreav.cpp @@ -231,8 +231,6 @@ void Core::sendCallAudio(int callId, ToxAv* toxav) alcGetIntegerv(alInDev, ALC_CAPTURE_SAMPLES, sizeof(samples), &samples); if(samples >= framesize) { - qDebug() << "Samples:"< Date: Sat, 15 Nov 2014 17:04:09 +0100 Subject: [PATCH 076/253] Fix buffer overflow with >1 audio input channel Fixes #760 --- src/coreav.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/coreav.cpp b/src/coreav.cpp index 75733b2d8..969b49f02 100644 --- a/src/coreav.cpp +++ b/src/coreav.cpp @@ -223,15 +223,16 @@ void Core::sendCallAudio(int callId, ToxAv* toxav) return; } - int framesize = (calls[callId].codecSettings.audio_frame_duration * calls[callId].codecSettings.audio_sample_rate) / 1000 * av_DefaultSettings.audio_channels; - uint8_t buf[framesize*2], dest[framesize*2]; + const int framesize = (calls[callId].codecSettings.audio_frame_duration * calls[callId].codecSettings.audio_sample_rate) / 1000 * av_DefaultSettings.audio_channels; + const int bufsize = framesize * 2 * av_DefaultSettings.audio_channels; + uint8_t buf[bufsize], dest[bufsize]; bool frame = false; ALint samples; alcGetIntegerv(alInDev, ALC_CAPTURE_SAMPLES, sizeof(samples), &samples); if(samples >= framesize) { - memset(buf, 0, framesize*2); // Avoid uninitialized values (Valgrind) + memset(buf, 0, bufsize); // Avoid uninitialized values (Valgrind) alcCaptureSamples(alInDev, buf, framesize); frame = 1; } @@ -653,15 +654,16 @@ void Core::sendGroupCallAudio(int groupId, ToxAv* toxav) return; } - int framesize = (groupCalls[groupId].codecSettings.audio_frame_duration * groupCalls[groupId].codecSettings.audio_sample_rate) / 1000 * av_DefaultSettings.audio_channels; - uint8_t buf[framesize*2]; + const int framesize = (groupCalls[groupId].codecSettings.audio_frame_duration * groupCalls[groupId].codecSettings.audio_sample_rate) / 1000 * av_DefaultSettings.audio_channels; + const int bufsize = framesize * 2 * av_DefaultSettings.audio_channels; + uint8_t buf[bufsize]; bool frame = false; ALint samples; alcGetIntegerv(alInDev, ALC_CAPTURE_SAMPLES, sizeof(samples), &samples); if(samples >= framesize) { - memset(buf, 0, framesize*2); // Avoid uninitialized values (Valgrind) + memset(buf, 0, bufsize); // Avoid uninitialized values (Valgrind) alcCaptureSamples(alInDev, buf, framesize); frame = 1; } From c541291aac30346c64072216197d64d1287a4f55 Mon Sep 17 00:00:00 2001 From: apprb Date: Sun, 16 Nov 2014 01:10:04 +0900 Subject: [PATCH 077/253] Nospam id part edit --- src/core.cpp | 7 ++++ src/core.h | 2 + src/widget/form/settings/privacyform.cpp | 41 ++++++++++++++++++--- src/widget/form/settings/privacyform.h | 4 ++ src/widget/form/settings/privacysettings.ui | 40 ++++++++++++++++++++ 5 files changed, 88 insertions(+), 6 deletions(-) diff --git a/src/core.cpp b/src/core.cpp index 1456b997b..e08bd464d 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -1866,3 +1866,10 @@ bool Core::isReady() { return ready; } + +void Core::setNospam(uint32_t nospam) +{ + uint8_t *nspm = reinterpret_cast(&nospam); + std::reverse(nspm, nspm + 4); + tox_set_nospam(tox, nospam); +} diff --git a/src/core.h b/src/core.h index 904a8212c..301a463dd 100644 --- a/src/core.h +++ b/src/core.h @@ -118,6 +118,8 @@ public slots: void micMuteToggle(int callId); void volMuteToggle(int callId); + void setNospam(uint32_t nospam); + static void joinGroupCall(int groupId); ///< Starts a call in an existing AV groupchat. Call from the GUI thread. static void leaveGroupCall(int groupId); ///< Will not leave the group, just stop the call. Call from the GUI thread. static void disableGroupCallMic(int groupId); diff --git a/src/widget/form/settings/privacyform.cpp b/src/widget/form/settings/privacyform.cpp index 8345f0299..6dc38804b 100644 --- a/src/widget/form/settings/privacyform.cpp +++ b/src/widget/form/settings/privacyform.cpp @@ -30,16 +30,12 @@ PrivacyForm::PrivacyForm() : bodyUI = new Ui::PrivacySettings; bodyUI->setupUi(this); - bodyUI->cbTypingNotification->setChecked(Settings::getInstance().isTypingNotificationEnabled()); - bodyUI->cbKeepHistory->setChecked(Settings::getInstance().getEnableLogging()); - bodyUI->cbEncryptHistory->setChecked(Settings::getInstance().getEncryptLogs()); - bodyUI->cbEncryptHistory->setEnabled(Settings::getInstance().getEnableLogging()); - bodyUI->cbEncryptTox->setChecked(Settings::getInstance().getEncryptTox()); - connect(bodyUI->cbTypingNotification, SIGNAL(stateChanged(int)), this, SLOT(onTypingNotificationEnabledUpdated())); connect(bodyUI->cbKeepHistory, SIGNAL(stateChanged(int)), this, SLOT(onEnableLoggingUpdated())); connect(bodyUI->cbEncryptHistory, SIGNAL(clicked()), this, SLOT(onEncryptLogsUpdated())); connect(bodyUI->cbEncryptTox, SIGNAL(clicked()), this, SLOT(onEncryptToxUpdated())); + connect(bodyUI->nospamLineEdit, SIGNAL(editingFinished()), this, SLOT(setNospam())); + connect(bodyUI->randomNosapamButton, SIGNAL(clicked()), this, SLOT(generateRandomNospam())); } PrivacyForm::~PrivacyForm() @@ -134,3 +130,36 @@ void PrivacyForm::onEncryptToxUpdated() if (!Settings::getInstance().getEncryptTox()) Core::getInstance()->clearPassword(Core::ptMain); } + +void PrivacyForm::setNospam() +{ + QString newNospam = bodyUI->nospamLineEdit->text(); + + bool ok; + uint32_t nospam = newNospam.toLongLong(&ok, 16); + if (ok) + Core::getInstance()->setNospam(nospam); +} + +void PrivacyForm::present() +{ + bodyUI->nospamLineEdit->setText(Core::getInstance()->getSelfId().noSpam); + bodyUI->cbTypingNotification->setChecked(Settings::getInstance().isTypingNotificationEnabled()); + bodyUI->cbKeepHistory->setChecked(Settings::getInstance().getEnableLogging()); + bodyUI->cbEncryptHistory->setChecked(Settings::getInstance().getEncryptLogs()); + bodyUI->cbEncryptHistory->setEnabled(Settings::getInstance().getEnableLogging()); + bodyUI->cbEncryptTox->setChecked(Settings::getInstance().getEncryptTox()); +} + +void PrivacyForm::generateRandomNospam() +{ + QTime time = QTime::currentTime(); + qsrand((uint)time.msec()); + + uint8_t *newNospam = new uint8_t[4]; + for (int i = 0; i < 4; i++) + newNospam[i] = qrand() % 256; + + Core::getInstance()->setNospam(*reinterpret_cast(newNospam)); + bodyUI->nospamLineEdit->setText(Core::getInstance()->getSelfId().noSpam); +} diff --git a/src/widget/form/settings/privacyform.h b/src/widget/form/settings/privacyform.h index 2a0d79a53..e7a844b92 100644 --- a/src/widget/form/settings/privacyform.h +++ b/src/widget/form/settings/privacyform.h @@ -30,9 +30,13 @@ public: PrivacyForm(); ~PrivacyForm(); + virtual void present(); + private slots: void onEnableLoggingUpdated(); void onTypingNotificationEnabledUpdated(); + void setNospam(); + void generateRandomNospam(); void onEncryptLogsUpdated(); void onEncryptToxUpdated(); diff --git a/src/widget/form/settings/privacysettings.ui b/src/widget/form/settings/privacysettings.ui index 899df3cb4..84b4aecda 100644 --- a/src/widget/form/settings/privacysettings.ui +++ b/src/widget/form/settings/privacysettings.ui @@ -96,6 +96,46 @@ + + + + Nospam + + + + + + + + HHHHHHHH + + + + + + + Generate random nospam + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + From 2c229684805ca98f31698d3013d77237b7f6c40b Mon Sep 17 00:00:00 2001 From: apprb Date: Sun, 16 Nov 2014 02:16:56 +0900 Subject: [PATCH 078/253] improved usability --- src/widget/form/settings/privacyform.cpp | 15 ++++++++++++++- src/widget/form/settings/privacyform.h | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/widget/form/settings/privacyform.cpp b/src/widget/form/settings/privacyform.cpp index 6dc38804b..101eb41b0 100644 --- a/src/widget/form/settings/privacyform.cpp +++ b/src/widget/form/settings/privacyform.cpp @@ -36,6 +36,7 @@ PrivacyForm::PrivacyForm() : connect(bodyUI->cbEncryptTox, SIGNAL(clicked()), this, SLOT(onEncryptToxUpdated())); connect(bodyUI->nospamLineEdit, SIGNAL(editingFinished()), this, SLOT(setNospam())); connect(bodyUI->randomNosapamButton, SIGNAL(clicked()), this, SLOT(generateRandomNospam())); + connect(bodyUI->nospamLineEdit, SIGNAL(textChanged(QString)), this, SLOT(onNospamEdit())); } PrivacyForm::~PrivacyForm() @@ -156,10 +157,22 @@ void PrivacyForm::generateRandomNospam() QTime time = QTime::currentTime(); qsrand((uint)time.msec()); - uint8_t *newNospam = new uint8_t[4]; + uint8_t newNospam[4]; for (int i = 0; i < 4; i++) newNospam[i] = qrand() % 256; Core::getInstance()->setNospam(*reinterpret_cast(newNospam)); bodyUI->nospamLineEdit->setText(Core::getInstance()->getSelfId().noSpam); } + +void PrivacyForm::onNospamEdit() +{ + QString str = bodyUI->nospamLineEdit->text(); + int curs = bodyUI->nospamLineEdit->cursorPosition(); + if (str.length() != 8) + { + str = QString("00000000").replace(0, str.length(), str); + bodyUI->nospamLineEdit->setText(str); + bodyUI->nospamLineEdit->setCursorPosition(curs); + }; +} diff --git a/src/widget/form/settings/privacyform.h b/src/widget/form/settings/privacyform.h index e7a844b92..fc0e4eb0d 100644 --- a/src/widget/form/settings/privacyform.h +++ b/src/widget/form/settings/privacyform.h @@ -37,7 +37,7 @@ private slots: void onTypingNotificationEnabledUpdated(); void setNospam(); void generateRandomNospam(); - + void onNospamEdit(); void onEncryptLogsUpdated(); void onEncryptToxUpdated(); From b0e6f1168c759781700e020daafaf3f52b611453 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Sat, 15 Nov 2014 18:29:41 +0100 Subject: [PATCH 079/253] Prepare for theme colors support --- src/mainwindow.ui | 11 +---------- src/misc/style.cpp | 10 ++++++++++ src/misc/style.h | 4 ++++ src/widget/widget.cpp | 5 +++-- ui/chatroomWidgets/genericChatroomWidget.css | 4 ++-- ui/friendList/friendList.css | 12 ++++-------- ui/window/statusPanel.css | 2 +- 7 files changed, 25 insertions(+), 23 deletions(-) diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 9e4f32bd6..f56a52c48 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1053,16 +1053,7 @@ QSplitter:handle{ - QPushButton{ - background-color:#1c1c1c; - border:none; -} - -QPushButton:hover{ - background-color:#292929; - border:none; -} - + diff --git a/src/misc/style.cpp b/src/misc/style.cpp index e9dec32e3..bb5f3ce39 100644 --- a/src/misc/style.cpp +++ b/src/misc/style.cpp @@ -71,6 +71,12 @@ QColor Style::getColor(Style::ColorPalette entry) QColor("#d1d1d1"), QColor("#ffffff"), QColor("#ff7700"), + + // Theme colors + QColor("#1c1c1c"), + QColor("#2a2a2a"), + QColor("#414141"), + QColor("#4e4e4e"), }; return palette[entry]; @@ -110,6 +116,10 @@ QString Style::resolve(QString qss) {"@lightGrey", getColor(LightGrey).name()}, {"@white", getColor(White).name()}, {"@orange", getColor(Orange).name()}, + {"@themeDark", getColor(ThemeDark).name()}, + {"@themeMediumDark", getColor(ThemeMediumDark).name()}, + {"@themeMedium", getColor(ThemeMedium).name()}, + {"@themeLight", getColor(ThemeLight).name()}, // fonts {"@extraBig", qssifyFont(getFont(ExtraBig))}, diff --git a/src/misc/style.h b/src/misc/style.h index e818b2ddb..33800d9e4 100644 --- a/src/misc/style.h +++ b/src/misc/style.h @@ -38,6 +38,10 @@ public: LightGrey, White, Orange, + ThemeDark, + ThemeMediumDark, + ThemeMedium, + ThemeLight, }; enum Font diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index 6d61708df..850565953 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -118,7 +118,7 @@ void Widget::init() ui->mainSplitter->restoreState(Settings::getInstance().getSplitterState()); layout()->setContentsMargins(0, 0, 0, 0); - ui->friendList->setStyleSheet(Style::getStylesheet(":ui/friendList/friendList.css")); + ui->friendList->setStyleSheet(Style::resolve(Style::getStylesheet(":ui/friendList/friendList.css"))); profilePicture = new MaskablePixmapWidget(this, QSize(40, 40), ":/img/avatar_mask.png"); profilePicture->setPixmap(QPixmap(":/img/contact_dark.png")); @@ -130,8 +130,9 @@ void Widget::init() ui->mainHead->setLayout(new QVBoxLayout()); ui->mainHead->layout()->setMargin(0); ui->mainHead->layout()->setSpacing(0); - + ui->tooliconsZone->setStyleSheet(Style::resolve("QPushButton{background-color:@themeDark;border:none;}QPushButton:hover{background-color:@themeMediumDark;border:none;}")); + if(QStyleFactory::keys().contains(Settings::getInstance().getStyle()) && Settings::getInstance().getStyle() != "None") { diff --git a/ui/chatroomWidgets/genericChatroomWidget.css b/ui/chatroomWidgets/genericChatroomWidget.css index 4ed573fb0..c83bb9958 100644 --- a/ui/chatroomWidgets/genericChatroomWidget.css +++ b/ui/chatroomWidgets/genericChatroomWidget.css @@ -1,6 +1,6 @@ GenericChatroomWidget { - background-color: @mediumGrey; + background-color: @themeMedium; } GenericChatroomWidget[active="true"] @@ -10,7 +10,7 @@ GenericChatroomWidget[active="true"] GenericChatroomWidget[active="false"]:hover { - background-color: @mediumGreyLight; + background-color: @themeLight; } GenericChatroomWidget[active="true"] > QLabel#status diff --git a/ui/friendList/friendList.css b/ui/friendList/friendList.css index 8358f5a04..57f1a807e 100644 --- a/ui/friendList/friendList.css +++ b/ui/friendList/friendList.css @@ -1,26 +1,22 @@ -QScrollArea { - background: #414141; -} - QScrollBar:vertical { - background: rgb(65,65,65); + background: @themeMedium; width: 16px; padding: 0px 3px 0px 3px; } QScrollBar:handle:vertical { - background: rgba(18, 18, 18, 204); + background: @themeDark; min-height: 20px; border-radius: 5px; margin: 3px 0px 3px 0px; } QScrollBar:handle:vertical:hover { - background: rgba(35, 35, 35, 204); + background: @themeMediumDark; } QScrollBar:handle:vertical:pressed { - background: rgba(13, 13, 13, 204); + background: @themeDark; } QScrollBar:add-line:vertical {height: 0px;subcontrol-position: bottom;subcontrol-origin: margin;} diff --git a/ui/window/statusPanel.css b/ui/window/statusPanel.css index de4dcd043..c9b8249e0 100644 --- a/ui/window/statusPanel.css +++ b/ui/window/statusPanel.css @@ -9,7 +9,7 @@ QLineEdit #statusPanel { - background-color: @darkGrey; + background-color: @themeDark; } #statusPanel > #statusHead > #nameLabel { From bee6c95650a2ea4ef22823afdcb2a02d4c433f3e Mon Sep 17 00:00:00 2001 From: Yavor Stefanov Date: Sat, 15 Nov 2014 19:35:36 +0200 Subject: [PATCH 080/253] mic tooltip clarification --- src/widget/form/genericchatform.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widget/form/genericchatform.cpp b/src/widget/form/genericchatform.cpp index 671327cc5..167c00d85 100644 --- a/src/widget/form/genericchatform.cpp +++ b/src/widget/form/genericchatform.cpp @@ -77,7 +77,7 @@ GenericChatForm::GenericChatForm(QWidget *parent) : volButton->setToolTip(tr("Toggle speakers volume")); micButton = new QPushButton(); micButton->setFixedSize(25,20); - micButton->setToolTip(tr("Toggle microphone")); + micButton->setToolTip(tr("Toggle microphone: RED is OFF")); footButtonsSmall->setSpacing(2); From 7eca6b7b248dffbb98b5c605a47e56c87d18d458 Mon Sep 17 00:00:00 2001 From: Yavor Stefanov Date: Sat, 15 Nov 2014 19:43:10 +0200 Subject: [PATCH 081/253] VA buttons tooltip clarifications --- src/widget/form/genericchatform.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/widget/form/genericchatform.cpp b/src/widget/form/genericchatform.cpp index 167c00d85..dc4d00ea6 100644 --- a/src/widget/form/genericchatform.cpp +++ b/src/widget/form/genericchatform.cpp @@ -68,13 +68,13 @@ GenericChatForm::GenericChatForm(QWidget *parent) : fileButton->setToolTip(tr("Send file(s)")); callButton = new QPushButton(); callButton->setFixedSize(50,40); - callButton->setToolTip(tr("Audio call")); + callButton->setToolTip(tr("Audio call: RED means you're on a call")); videoButton = new QPushButton(); videoButton->setFixedSize(50,40); - videoButton->setToolTip(tr("Video call")); + videoButton->setToolTip(tr("Video call: RED means you're on a call")); volButton = new QPushButton(); volButton->setFixedSize(25,20); - volButton->setToolTip(tr("Toggle speakers volume")); + volButton->setToolTip(tr("Toggle speakers volume: RED is OFF")); micButton = new QPushButton(); micButton->setFixedSize(25,20); micButton->setToolTip(tr("Toggle microphone: RED is OFF")); From 2f452b6eff0bf4e0aef1166067f93da64d76a7b0 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Sat, 15 Nov 2014 21:30:20 +0100 Subject: [PATCH 082/253] Add GUI to change theme color --- src/misc/settings.cpp | 12 ++ src/misc/settings.h | 4 + src/misc/style.cpp | 144 +++++++++++++------- src/misc/style.h | 9 ++ src/widget/form/settings/generalform.cpp | 13 ++ src/widget/form/settings/generalform.h | 1 + src/widget/form/settings/generalsettings.ui | 39 +++++- src/widget/genericchatroomwidget.cpp | 5 + src/widget/genericchatroomwidget.h | 4 + src/widget/widget.cpp | 17 +++ src/widget/widget.h | 2 + ui/statusButton/menu_indicator.png | Bin 262 -> 268 bytes ui/statusButton/statusButton.css | 8 +- ui/window/statusPanel.css | 8 +- 14 files changed, 206 insertions(+), 60 deletions(-) diff --git a/src/misc/settings.cpp b/src/misc/settings.cpp index c6f309c33..0770c42c8 100644 --- a/src/misc/settings.cpp +++ b/src/misc/settings.cpp @@ -154,6 +154,7 @@ void Settings::load() useNativeStyle = s.value("nativeStyle", false).toBool(); useEmoticons = s.value("useEmoticons", true).toBool(); statusChangeNotificationEnabled = s.value("statusChangeNotificationEnabled", false).toBool(); + themeColor = s.value("themeColor", 0).toInt(); style = s.value("style", "").toString(); if (style == "") // Default to Fusion if available, otherwise no style { @@ -297,6 +298,7 @@ void Settings::save(QString path) s.setValue("minimizeToTray", minimizeToTray); s.setValue("nativeStyle", useNativeStyle); s.setValue("useEmoticons", useEmoticons); + s.setValue("themeColor", themeColor); s.setValue("style", style); s.setValue("statusChangeNotificationEnabled", statusChangeNotificationEnabled); s.endGroup(); @@ -909,3 +911,13 @@ void Settings::setFauxOfflineMessaging(bool value) { fauxOfflineMessaging = value; } + +int Settings::getThemeColor() const +{ + return themeColor; +} + +void Settings::setThemeColor(const int &value) +{ + themeColor = value; +} diff --git a/src/misc/settings.h b/src/misc/settings.h index 49f60f58d..7468fe3fe 100644 --- a/src/misc/settings.h +++ b/src/misc/settings.h @@ -152,6 +152,9 @@ public: QString getSmileyPack() const; void setSmileyPack(const QString &value); + int getThemeColor() const; + void setThemeColor(const int& value); + bool isCurstomEmojiFont() const; void setCurstomEmojiFont(bool value); @@ -296,6 +299,7 @@ private: QHash friendLst; + int themeColor; signals: //void dataChanged(); diff --git a/src/misc/style.cpp b/src/misc/style.cpp index bb5f3ce39..ad6f93c7b 100644 --- a/src/misc/style.cpp +++ b/src/misc/style.cpp @@ -17,6 +17,10 @@ #include "style.h" #include "settings.h" +#include "src/widget/widget.h" +#include "ui_mainwindow.h" +#include "src/widget/genericchatroomwidget.h" + #include #include #include @@ -42,6 +46,33 @@ QString qssifyFont(QFont font) .arg(font.family()); } +// colors as defined in +// https://github.com/ItsDuke/Tox-UI/blob/master/UI%20GUIDELINES.md +static QColor palette[] = { + QColor("#6bc260"), + QColor("#cebf44"), + QColor("#c84e4e"), + QColor("#000000"), + QColor("#1c1c1c"), + QColor("#414141"), + QColor("#414141").lighter(120), + QColor("#d1d1d1"), + QColor("#ffffff"), + QColor("#ff7700"), + + // Theme colors + QColor("#1c1c1c"), + QColor("#2a2a2a"), + QColor("#414141"), + QColor("#4e4e4e"), +}; + +static QMap dict; + +QStringList Style::themeColorNames = {QObject::tr("Default"), QObject::tr("Blue"), QObject::tr("Olive"), QObject::tr("Red"), QObject::tr("Violet")}; +QList Style::themeColorColors = {QColor(), QColor("#004aa4"), QColor("#97ba00"), QColor("#c23716"), QColor("#4617b5")}; + + QString Style::getStylesheet(const QString &filename) { if (!Settings::getInstance().getUseNativeStyle()) @@ -58,27 +89,6 @@ QString Style::getStylesheet(const QString &filename) QColor Style::getColor(Style::ColorPalette entry) { - // colors as defined in - // https://github.com/ItsDuke/Tox-UI/blob/master/UI%20GUIDELINES.md - static QColor palette[] = { - QColor("#6bc260"), - QColor("#cebf44"), - QColor("#c84e4e"), - QColor("#000000"), - QColor("#1c1c1c"), - QColor("#414141"), - QColor("#414141").lighter(120), - QColor("#d1d1d1"), - QColor("#ffffff"), - QColor("#ff7700"), - - // Theme colors - QColor("#1c1c1c"), - QColor("#2a2a2a"), - QColor("#414141"), - QColor("#4e4e4e"), - }; - return palette[entry]; } @@ -104,32 +114,35 @@ QFont Style::getFont(Style::Font font) QString Style::resolve(QString qss) { - static QMap dict = { - // colors - {"@green", getColor(Green).name()}, - {"@yellow", getColor(Yellow).name()}, - {"@red", getColor(Red).name()}, - {"@black", getColor(Black).name()}, - {"@darkGrey", getColor(DarkGrey).name()}, - {"@mediumGrey", getColor(MediumGrey).name()}, - {"@mediumGreyLight", getColor(MediumGreyLight).name()}, - {"@lightGrey", getColor(LightGrey).name()}, - {"@white", getColor(White).name()}, - {"@orange", getColor(Orange).name()}, - {"@themeDark", getColor(ThemeDark).name()}, - {"@themeMediumDark", getColor(ThemeMediumDark).name()}, - {"@themeMedium", getColor(ThemeMedium).name()}, - {"@themeLight", getColor(ThemeLight).name()}, + if (dict.isEmpty()) + { + dict = { + // colors + {"@green", Style::getColor(Style::Green).name()}, + {"@yellow", Style::getColor(Style::Yellow).name()}, + {"@red", Style::getColor(Style::Red).name()}, + {"@black", Style::getColor(Style::Black).name()}, + {"@darkGrey", Style::getColor(Style::DarkGrey).name()}, + {"@mediumGrey", Style::getColor(Style::MediumGrey).name()}, + {"@mediumGreyLight", Style::getColor(Style::MediumGreyLight).name()}, + {"@lightGrey", Style::getColor(Style::LightGrey).name()}, + {"@white", Style::getColor(Style::White).name()}, + {"@orange", Style::getColor(Style::Orange).name()}, + {"@themeDark", Style::getColor(Style::ThemeDark).name()}, + {"@themeMediumDark", Style::getColor(Style::ThemeMediumDark).name()}, + {"@themeMedium", Style::getColor(Style::ThemeMedium).name()}, + {"@themeLight", Style::getColor(Style::ThemeLight).name()}, - // fonts - {"@extraBig", qssifyFont(getFont(ExtraBig))}, - {"@big", qssifyFont(getFont(Big))}, - {"@bigBold", qssifyFont(getFont(BigBold))}, - {"@medium", qssifyFont(getFont(Medium))}, - {"@mediumBold", qssifyFont(getFont(MediumBold))}, - {"@small", qssifyFont(getFont(Small))}, - {"@smallLight", qssifyFont(getFont(SmallLight))}, - }; + // fonts + {"@extraBig", qssifyFont(Style::getFont(Style::ExtraBig))}, + {"@big", qssifyFont(Style::getFont(Style::Big))}, + {"@bigBold", qssifyFont(Style::getFont(Style::BigBold))}, + {"@medium", qssifyFont(Style::getFont(Style::Medium))}, + {"@mediumBold", qssifyFont(Style::getFont(Style::MediumBold))}, + {"@small", qssifyFont(Style::getFont(Style::Small))}, + {"@smallLight", qssifyFont(Style::getFont(Style::SmallLight))}, + }; + } for (const QString& key : dict.keys()) { @@ -154,3 +167,42 @@ void Style::repolish(QWidget *w) } } } + +void Style::setThemeColor(int color) +{ + if (color < 0 || color >= themeColorColors.size()) + setThemeColor(QColor()); + else + setThemeColor(themeColorColors[color]); +} + +void Style::setThemeColor(QColor color) +{ + if (!color.isValid()) + { + // Reset to default + palette[ThemeDark] = QColor("#1c1c1c"); + palette[ThemeMediumDark] = QColor("#2a2a2a"); + palette[ThemeMedium] = QColor("#414141"); + palette[ThemeLight] = QColor("#4e4e4e"); + } + else + { + palette[ThemeDark] = color.darker(155); + palette[ThemeMediumDark] = color.darker(135); + palette[ThemeMedium] = color.darker(120); + palette[ThemeLight] = color.lighter(110); + } + + dict["@themeDark"] = getColor(ThemeDark).name(); + dict["@themeMediumDark"] = getColor(ThemeMediumDark).name(); + dict["@themeMedium"] = getColor(ThemeMedium).name(); + dict["@themeLight"] = getColor(ThemeLight).name(); + + applyTheme(); +} + +void Style::applyTheme() +{ + Widget::getInstance()->reloadTheme(); +} diff --git a/src/misc/style.h b/src/misc/style.h index 33800d9e4..87412387c 100644 --- a/src/misc/style.h +++ b/src/misc/style.h @@ -60,6 +60,15 @@ public: static QFont getFont(Font font); static QString resolve(QString qss); static void repolish(QWidget* w); + static void setThemeColor(int color); + static void setThemeColor(QColor color); ///< Pass an invalid QColor to reset to defaults + static void applyTheme(); ///< Reloads some CCS + + static QStringList themeColorNames; + static QList themeColorColors; + +signals: + void themeChanged(); private: Style(); diff --git a/src/widget/form/settings/generalform.cpp b/src/widget/form/settings/generalform.cpp index a4c305a16..0d513d165 100644 --- a/src/widget/form/settings/generalform.cpp +++ b/src/widget/form/settings/generalform.cpp @@ -21,6 +21,7 @@ #include "src/misc/settings.h" #include "src/misc/smileypack.h" #include "src/core.h" +#include "src/misc/style.h" #include #include #include @@ -77,6 +78,10 @@ GeneralForm::GeneralForm(SettingsWidget *myParent) : else bodyUI->styleBrowser->setCurrentText(tr("None")); + for (QString color : Style::themeColorNames) + bodyUI->themeColorCBox->addItem(color); + bodyUI->themeColorCBox->setCurrentIndex(Settings::getInstance().getThemeColor()); + bodyUI->emoticonSize->setValue(Settings::getInstance().getEmojiFontPointSize()); QStringList timestamps; @@ -119,6 +124,7 @@ GeneralForm::GeneralForm(SettingsWidget *myParent) : connect(bodyUI->useEmoticons, &QCheckBox::stateChanged, this, &GeneralForm::onUseEmoticonsChange); connect(bodyUI->smileyPackBrowser, SIGNAL(currentIndexChanged(int)), this, SLOT(onSmileyBrowserIndexChanged(int))); connect(bodyUI->styleBrowser, SIGNAL(currentTextChanged(QString)), this, SLOT(onStyleSelected(QString))); + connect(bodyUI->themeColorCBox, SIGNAL(currentIndexChanged(int)), this, SLOT(onThemeColorChanged(int))); connect(bodyUI->emoticonSize, SIGNAL(editingFinished()), this, SLOT(onEmoticonSizeChanged())); connect(bodyUI->timestamp, SIGNAL(currentIndexChanged(int)), this, SLOT(onTimestampSelected(int))); //connection @@ -314,3 +320,10 @@ void GeneralForm::onFauxOfflineMessaging() { Settings::getInstance().setFauxOfflineMessaging(bodyUI->cbFauxOfflineMessaging->isChecked()); } + +void GeneralForm::onThemeColorChanged(int) +{ + int index = bodyUI->themeColorCBox->currentIndex(); + Settings::getInstance().setThemeColor(index); + Style::setThemeColor(index); +} diff --git a/src/widget/form/settings/generalform.h b/src/widget/form/settings/generalform.h index 6f902a523..ec41fb0ad 100644 --- a/src/widget/form/settings/generalform.h +++ b/src/widget/form/settings/generalform.h @@ -55,6 +55,7 @@ private slots: void onCheckUpdateChanged(); void onSetShowInFront(); void onFauxOfflineMessaging(); + void onThemeColorChanged(int); private: Ui::GeneralSettings *bodyUI; diff --git a/src/widget/form/settings/generalsettings.ui b/src/widget/form/settings/generalsettings.ui index b0bf7d38d..f74e805e4 100644 --- a/src/widget/form/settings/generalsettings.ui +++ b/src/widget/form/settings/generalsettings.ui @@ -39,8 +39,8 @@ 0 0 - 583 - 748 + 524 + 726 @@ -197,6 +197,9 @@ Set to 0 to disable + + true + minutes @@ -206,9 +209,6 @@ 2147483647 - - true - @@ -251,8 +251,14 @@ - + + + + 0 + 232 + + Theme @@ -363,6 +369,27 @@ + + + + + + Theme color + + + + + + + + 0 + 0 + + + + + + diff --git a/src/widget/genericchatroomwidget.cpp b/src/widget/genericchatroomwidget.cpp index 4a4e7198d..ef5cf5df9 100644 --- a/src/widget/genericchatroomwidget.cpp +++ b/src/widget/genericchatroomwidget.cpp @@ -97,3 +97,8 @@ void GenericChatroomWidget::mouseReleaseEvent(QMouseEvent*) { emit chatroomWidgetClicked(this); } + +void GenericChatroomWidget::reloadTheme() +{ + setStyleSheet(Style::getStylesheet(":/ui/chatroomWidgets/genericChatroomWidget.css")); +} diff --git a/src/widget/genericchatroomwidget.h b/src/widget/genericchatroomwidget.h index 1dd6d8250..333ef109c 100644 --- a/src/widget/genericchatroomwidget.h +++ b/src/widget/genericchatroomwidget.h @@ -51,6 +51,8 @@ public: QString getName() const; QString getStatusMsg() const; + void reloadTheme(); + signals: void chatroomWidgetClicked(GenericChatroomWidget* widget); @@ -63,6 +65,8 @@ protected: MaskablePixmapWidget* avatar; QLabel statusPic; CroppingLabel *nameLabel, *statusMessageLabel; + + friend class Style; ///< To update our stylesheets }; #endif // GENERICCHATROOMWIDGET_H diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index 850565953..c7d28daf5 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -173,6 +173,9 @@ void Widget::init() // Disable some widgets until we're connected to the DHT ui->statusButton->setEnabled(false); + Style::setThemeColor(Settings::getInstance().getThemeColor()); + Style::applyTheme(); + idleTimer = new QTimer(); idleTimer->setSingleShot(true); int mins = Settings::getInstance().getAutoAwayTime(); @@ -1186,3 +1189,17 @@ void Widget::clearAllReceipts() f->getChatForm()->clearReciepts(); } } + +void Widget::reloadTheme() +{ + ui->tooliconsZone->setStyleSheet(Style::resolve("QPushButton{background-color:@themeDark;border:none;}QPushButton:hover{background-color:@themeMediumDark;border:none;}")); + ui->statusPanel->setStyleSheet(Style::getStylesheet(":/ui/window/statusPanel.css")); + ui->friendList->setStyleSheet(Style::getStylesheet(":ui/friendList/friendList.css")); + ui->statusButton->setStyleSheet(Style::getStylesheet(":ui/statusButton/statusButton.css")); + + for (Friend* f : FriendList::getAllFriends()) + f->getFriendWidget()->reloadTheme(); + + for (Group* g : GroupList::groupList) + g->widget->reloadTheme(); +} diff --git a/src/widget/widget.h b/src/widget/widget.h index 09a6814a6..fc4bac241 100644 --- a/src/widget/widget.h +++ b/src/widget/widget.h @@ -75,6 +75,8 @@ public: void clearAllReceipts(); + void reloadTheme(); + public slots: void onSettingsClicked(); diff --git a/ui/statusButton/menu_indicator.png b/ui/statusButton/menu_indicator.png index 0015b5d08be56bbc60182e81bf4eb324d46ec65e..92abefa556ae35027f792be26368153c5e815b82 100644 GIT binary patch delta 194 zcmV;z06qVP0*nHXQhy5%6DT3X2UTSN005auL_t&t*L99N4uU`w0O#!{Y+R56ODYUk z*v=Yn;+;I0L<%vHM6#{W@C#NN8>gORMvg^s%JclpA1-An;+$ijWosPAyt)nmr4-vJ z;@P%%??nMTnnspo!CH&fn$TMMzQ=nn019B9XKr;ZCI|?vMLR^O?|1nmb(*FCOw+_T wj+2iWB3&S-q7(j22JgcBLDyZ07*qoM6N<$f~I*;q5uE@ delta 188 zcmV;t07L(b0)_&RQhx^p6&^Y8w=IzX005IoL_t&t*L9A;3W7isMbC_b!DxPk-(vs2 z$VHN7LLv+ajS9lZYY_%5x~sc5mvhyrC?wBw{^eYjnCCd+oFmIJUfu(swdR #statusHead > #statusButton { background: none; - background-color: @mediumGrey; + background-color: @themeMedium; border: none; border-radius: 6px; width: 20px; @@ -55,12 +55,12 @@ QLineEdit /*Bugged in Qt, but it's probably better to leave enabled so that users can tell it's clickable*/ #statusPanel > #statusHead > #statusButton:hover { - background-color: @mediumGreyLight; + background-color: @themeLight; } #statusPanel > #statusHead > #statusButton:pressed { - background-color: @mediumGrey; + background-color: @themeMedium; } #statusPanel > #statusHead > #statusButton:focus { From d271e01e0261ae875c2bb522a1518a58ffb4a7a5 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Sat, 15 Nov 2014 21:32:56 +0100 Subject: [PATCH 083/253] Audio buffer limit: 128 -> 32 --- src/coreav.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreav.cpp b/src/coreav.cpp index 969b49f02..a36b263a4 100644 --- a/src/coreav.cpp +++ b/src/coreav.cpp @@ -562,7 +562,7 @@ void Core::playAudioBuffer(ALuint alSource, const int16_t *data, int samples, un alDeleteBuffers(processed - 1, bufids + 1); bufid = bufids[0]; } - else if(queued < 128) + else if(queued < 32) { alGenBuffers(1, &bufid); } From 5bed0c248a847a9f6ed505c4b468547aca96810a Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Sat, 15 Nov 2014 23:04:51 +0100 Subject: [PATCH 084/253] Fix friendlist having a bad gray background Thanks to toweI for reporting the issue --- ui/friendList/friendList.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ui/friendList/friendList.css b/ui/friendList/friendList.css index 57f1a807e..2cf285908 100644 --- a/ui/friendList/friendList.css +++ b/ui/friendList/friendList.css @@ -1,3 +1,7 @@ +QScrollArea { + background: @themeMedium; +} + QScrollBar:vertical { background: @themeMedium; width: 16px; From 3de63c0061bd97ce434147dbee94aedb78ce6c9d Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Sat, 15 Nov 2014 23:17:30 +0100 Subject: [PATCH 085/253] Fix #767 --- src/widget/croppinglabel.cpp | 5 +++++ src/widget/croppinglabel.h | 2 ++ src/widget/genericchatroomwidget.cpp | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/widget/croppinglabel.cpp b/src/widget/croppinglabel.cpp index 9db23d0b1..f7b0b939e 100644 --- a/src/widget/croppinglabel.cpp +++ b/src/widget/croppinglabel.cpp @@ -144,3 +144,8 @@ void CroppingLabel::showTextEdit() textEdit->setFocus(); textEdit->setText(origText); } + +QString CroppingLabel::fullText() +{ + return origText; +} diff --git a/src/widget/croppinglabel.h b/src/widget/croppinglabel.h index 678829cbe..d1bd1677a 100644 --- a/src/widget/croppinglabel.h +++ b/src/widget/croppinglabel.h @@ -37,6 +37,8 @@ public: virtual void mouseReleaseEvent(QMouseEvent *e); virtual bool eventFilter(QObject *obj, QEvent *e); + QString fullText(); ///< Returns the un-cropped text + signals: void textChanged(QString newText, QString oldText); void clicked(); diff --git a/src/widget/genericchatroomwidget.cpp b/src/widget/genericchatroomwidget.cpp index ef5cf5df9..724e2e419 100644 --- a/src/widget/genericchatroomwidget.cpp +++ b/src/widget/genericchatroomwidget.cpp @@ -85,7 +85,7 @@ void GenericChatroomWidget::setStatusMsg(const QString &status) QString GenericChatroomWidget::getName() const { - return nameLabel->text(); + return nameLabel->fullText(); } QString GenericChatroomWidget::getStatusMsg() const From 007a10346db4a5c2f32e5958fb9a7570ec2b2322 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Sat, 15 Nov 2014 23:26:30 +0100 Subject: [PATCH 086/253] Rewrite horrible type casting pointer magic with shifts Why would you generate that random number byte by byte, anyway ? --- src/widget/form/settings/privacyform.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/widget/form/settings/privacyform.cpp b/src/widget/form/settings/privacyform.cpp index 101eb41b0..00c914091 100644 --- a/src/widget/form/settings/privacyform.cpp +++ b/src/widget/form/settings/privacyform.cpp @@ -157,11 +157,11 @@ void PrivacyForm::generateRandomNospam() QTime time = QTime::currentTime(); qsrand((uint)time.msec()); - uint8_t newNospam[4]; + uint32_t newNospam; for (int i = 0; i < 4; i++) - newNospam[i] = qrand() % 256; + newNospam = (newNospam<<8) + (qrand() % 256); // Generate byte by byte. For some reason. - Core::getInstance()->setNospam(*reinterpret_cast(newNospam)); + Core::getInstance()->setNospam(newNospam); bodyUI->nospamLineEdit->setText(Core::getInstance()->getSelfId().noSpam); } From 546b90c4c9d22a79db2a804e007112d88ea1756a Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Sun, 16 Nov 2014 11:24:15 +0100 Subject: [PATCH 087/253] Windows updater: Create backup and restore it on failure --- updater/widget.cpp | 34 ++++++++++++++++++++++++++++------ updater/widget.h | 3 +++ 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/updater/widget.cpp b/updater/widget.cpp index a87813204..8cfe0b58a 100644 --- a/updater/widget.cpp +++ b/updater/widget.cpp @@ -65,6 +65,7 @@ void Widget::fatalError(QString message) { QMessageBox::critical(this,tr("Error"), message+'\n'+tr("qTox will restart now.")); deleteUpdate(); + restoreBackups(); startQToxAndExit(); } @@ -80,6 +81,18 @@ void Widget::startQToxAndExit() exit(0); } +void Widget::deleteBackups() +{ + for (QString file : backups) + QFile(file+".bak").remove(); +} + +void Widget::restoreBackups() +{ + for (QString file : backups) + QFile(file+".bak").rename(file); +} + void Widget::update() { /// 1. Find and parse the update (0-5%) @@ -138,17 +151,26 @@ void Widget::update() float installProgress = 50; for (UpdateFileMeta fileMeta : diff) { - QFile fileFile(updateDirStr+fileMeta.installpath); - if (!fileFile.copy(fileMeta.installpath)) - fatalError(tr("Unable to copy the update's files.")); + // Backup old files + if (QFile(fileMeta.installpath).exists()) + { + QFile(fileMeta.installpath).rename(fileMeta.installpath+".bak"); + backups.append(fileMeta.installpath); + } - installProgress += installProgressStep; - setProgress(installProgress); + // Install new ones + QFile fileFile(updateDirStr+fileMeta.installpath); + if (!fileFile.copy(fileMeta.installpath)) + fatalError(tr("Unable to copy the update's files.")); + installProgress += installProgressStep; + setProgress(installProgress); } setProgress(95); - /// 4. Delete the update (95-100%) + /// 4. Delete the update and backups (95-100%) deleteUpdate(); + setProgress(97); + deleteBackups(); setProgress(100); /// 5. Start qTox and exit diff --git a/updater/widget.h b/updater/widget.h index c757fa7a0..90c5a89f7 100644 --- a/updater/widget.h +++ b/updater/widget.h @@ -33,6 +33,8 @@ public: ~Widget(); // Utilities + void deleteBackups(); + void restoreBackups(); void setProgress(int value); // Noreturn @@ -46,6 +48,7 @@ public slots: private: Ui::Widget *ui; + QStringList backups; }; #endif // WIDGET_H From 7e2dab6676742d10f97a0ca9046103746ef8d218 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Sun, 16 Nov 2014 13:36:33 +0100 Subject: [PATCH 088/253] Search libs in ../../PlugIns on OS X --- src/main.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index baab1f558..b8adbccd2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -93,6 +93,9 @@ int main(int argc, char *argv[]) // Windows platform plugins DLL hell fix QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath()); a.addLibraryPath("platforms"); +#ifdef Q_OS_OSX + a.addLibraryPath("../../PlugIns"); +#endif qDebug() << "built on: " << __TIME__ << __DATE__ << "(" << TIMESTAMP << ")"; qDebug() << "commit: " << GIT_VERSION << "\n"; From bb48852d876a230197e5f61f3057a5264fc0841c Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Sun, 16 Nov 2014 13:50:45 +0100 Subject: [PATCH 089/253] Fix tox: url highlighting breaking the :tox: smiley Fixes #772 --- src/widget/tool/chatactions/messageaction.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/widget/tool/chatactions/messageaction.cpp b/src/widget/tool/chatactions/messageaction.cpp index 42e46bc79..06f27ef1d 100644 --- a/src/widget/tool/chatactions/messageaction.cpp +++ b/src/widget/tool/chatactions/messageaction.cpp @@ -41,6 +41,13 @@ QString MessageAction::getMessage(QString div) { QString url = exp.cap(); + // If there's a trailing " it's a HTML attribute, e.g. a smiley img's title=":tox:" + if (url == "tox:\"") + { + offset += url.length(); + continue; + } + // add scheme if not specified if (exp.cap(1) == "www.") url.prepend("http://"); From ce6e50b9128d1ab4e74cbfa3f5cde61a9fcc395f Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Sun, 16 Nov 2014 13:58:03 +0100 Subject: [PATCH 090/253] Add more plugin search paths for OS X --- src/main.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index b8adbccd2..878b3fc12 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -94,7 +94,19 @@ int main(int argc, char *argv[]) QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath()); a.addLibraryPath("platforms"); #ifdef Q_OS_OSX - a.addLibraryPath("../../PlugIns"); + QString startPath = a.applicationDirPath(); + QDir pluginsDir(startPath); + pluginsDir.cdUp(); + pluginsDir.cd("PlugIns"); + a.addLibraryPath(pluginsDir.absolutePath()); + pluginsDir.cd(startPath); + pluginsDir.cdUp(); + pluginsDir.cd("Plugins"); + a.addLibraryPath(pluginsDir.absolutePath()); + pluginsDir.cd(startPath); + pluginsDir.cdUp(); + pluginsDir.cd("plugins"); + a.addLibraryPath(pluginsDir.absolutePath()); #endif qDebug() << "built on: " << __TIME__ << __DATE__ << "(" << TIMESTAMP << ")"; From 02e29d66009f0eacfdefbbdedd1a00fcb16571bb Mon Sep 17 00:00:00 2001 From: dubslow Date: Fri, 14 Nov 2014 17:43:38 -0600 Subject: [PATCH 091/253] auto away done right (fixes #751) --- src/widget/widget.cpp | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index c7d28daf5..4e77b5d9a 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -178,9 +178,7 @@ void Widget::init() idleTimer = new QTimer(); idleTimer->setSingleShot(true); - int mins = Settings::getInstance().getAutoAwayTime(); - if (mins > 0) - idleTimer->start(mins * 1000*60); + setIdleTimer(Settings::getInstance().getAutoAwayTime()); qRegisterMetaType("Status"); qRegisterMetaType("vpx_image"); @@ -409,11 +407,6 @@ QString Widget::askProfiles() return profile; } -void Widget::setIdleTimer(int minutes) -{ - idleTimer->start(minutes * 1000*60); -} - QString Widget::getUsername() { return core->getUsername(); @@ -1054,10 +1047,8 @@ bool Widget::event(QEvent * e) qDebug() << "Widget: auto away deactivated at" << QTime::currentTime().toString(); autoAwayActive = false; emit statusSet(Status::Online); - int mins = Settings::getInstance().getAutoAwayTime(); - if (mins > 0) - idleTimer->start(mins * 1000*60); } + setIdleTimer(Settings::getInstance().getAutoAwayTime()); default: break; } @@ -1065,6 +1056,12 @@ bool Widget::event(QEvent * e) return QWidget::event(e); } +void Widget::setIdleTimer(int minutes) +{ + if (minutes > 0) + idleTimer->start(minutes * 1000*60); +} + void Widget::onUserAway() { if (Settings::getInstance().getAutoAwayTime() > 0 From 40addf915583a4770924e6aee68a5826e284f42c Mon Sep 17 00:00:00 2001 From: dubslow Date: Fri, 14 Nov 2014 18:28:44 -0600 Subject: [PATCH 092/253] "overhaul" window title, fix #753 --- src/friend.cpp | 5 ++++- src/group.cpp | 4 ++++ src/widget/friendwidget.cpp | 3 --- src/widget/widget.cpp | 13 +++++++++---- src/widget/widget.h | 1 + 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/friend.cpp b/src/friend.cpp index 30fa485bd..61931078d 100644 --- a/src/friend.cpp +++ b/src/friend.cpp @@ -46,7 +46,7 @@ void Friend::setName(QString name) chatForm->setName(name); if (widget->isActive()) - Widget::getInstance()->setWindowTitle(name + " - qTox"); + Widget::getInstance()->setWindowTitle(name); } } @@ -57,6 +57,9 @@ void Friend::setAlias(QString name) widget->setName(dispName); chatForm->setName(dispName); + + if (widget->isActive()) + Widget::getInstance()->setWindowTitle(dispName); } void Friend::setStatusMessage(QString message) diff --git a/src/group.cpp b/src/group.cpp index 7f517ab41..0dbd2ad6b 100644 --- a/src/group.cpp +++ b/src/group.cpp @@ -20,6 +20,7 @@ #include "friendlist.h" #include "friend.h" #include "core.h" +#include "widget/widget.h" #include #include @@ -74,4 +75,7 @@ void Group::setName(const QString& name) { widget->setName(name); chatForm->setName(name); + + if (widget->isActive()) + Widget::getInstance()->setWindowTitle(name); } diff --git a/src/widget/friendwidget.cpp b/src/widget/friendwidget.cpp index 3fa4f0f6b..4da65a98b 100644 --- a/src/widget/friendwidget.cpp +++ b/src/widget/friendwidget.cpp @@ -235,8 +235,5 @@ void FriendWidget::setFriendAlias() Settings::getInstance().setFriendAlias(f->getToxID(), alias); hide(); show(); - - if (f->getFriendWidget()->isActive()) - Widget::getInstance()->setWindowTitle(f->getFriendWidget()->getName() + " - qTox"); } } diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index 4e77b5d9a..f89b3702b 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -516,11 +516,16 @@ void Widget::onStatusSet(Status status) Style::repolish(ui->statusButton); } +void Widget::setWindowTitle(const QString& title) +{ + QMainWindow::setWindowTitle("qTox - " + title); +} + void Widget::onAddClicked() { hideMainForms(); addFriendForm->show(*ui); - setWindowTitle(tr("Add friend") + " - qTox"); + setWindowTitle(tr("Add friend")); } void Widget::onGroupClicked() @@ -532,7 +537,7 @@ void Widget::onTransferClicked() { hideMainForms(); filesForm->show(*ui); - setWindowTitle(tr("File transfers") + " - qTox"); + setWindowTitle(tr("File transfers")); activeChatroomWidget = nullptr; } @@ -557,7 +562,7 @@ void Widget::onSettingsClicked() { hideMainForms(); settingsWidget->show(*ui); - setWindowTitle(tr("Settings") + " - qTox"); + setWindowTitle(tr("Settings")); activeChatroomWidget = nullptr; } @@ -734,7 +739,7 @@ void Widget::onChatroomWidgetClicked(GenericChatroomWidget *widget) } activeChatroomWidget = widget; widget->setAsActiveChatroom(); - setWindowTitle(widget->getName() + " - qTox"); + setWindowTitle(widget->getName()); widget->resetEventFlags(); widget->updateStatusLight(); } diff --git a/src/widget/widget.h b/src/widget/widget.h index fc4bac241..0cf98f6e6 100644 --- a/src/widget/widget.h +++ b/src/widget/widget.h @@ -79,6 +79,7 @@ public: public slots: void onSettingsClicked(); + void setWindowTitle(const QString& title); signals: void friendRequestAccepted(const QString& userId); From e6c08ad208fd6bc3f17a60f2fa2dd952923829b5 Mon Sep 17 00:00:00 2001 From: dubslow Date: Fri, 14 Nov 2014 19:29:14 -0600 Subject: [PATCH 093/253] fix title editing issue, reported by "whoever" --- src/widget/form/groupchatform.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/widget/form/groupchatform.cpp b/src/widget/form/groupchatform.cpp index 0731cf278..de6fd951a 100644 --- a/src/widget/form/groupchatform.cpp +++ b/src/widget/form/groupchatform.cpp @@ -81,7 +81,8 @@ GroupChatForm::GroupChatForm(Group* chatGroup) connect(callButton, &QPushButton::clicked, this, &GroupChatForm::onCallClicked); connect(micButton, SIGNAL(clicked()), this, SLOT(onMicMuteToggle())); connect(volButton, SIGNAL(clicked()), this, SLOT(onVolMuteToggle())); - connect(nameLabel, &CroppingLabel::textChanged, this, [=](QString s, QString) {emit groupTitleChanged(group->groupId, s);} ); + connect(nameLabel, &CroppingLabel::textChanged, this, [=](QString text, QString orig) + {if (text != orig) emit groupTitleChanged(group->groupId, text);} ); setAcceptDrops(true); } From e5b34115c63c5a03afc78f3aadbcb9b0da009198 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Sun, 16 Nov 2014 15:18:10 +0100 Subject: [PATCH 094/253] Cherry pick most of #756 From b97e66d5899050bb9cc6142ade12497eac2668de Mon Sep 17 00:00:00 2001 From: Maximilian Date: Sat, 15 Nov 2014 18:13:41 +0100 Subject: [PATCH 095/253] Minor bug fix: The main window, will never be visible, when the user unchecked 'Show system try' and checked 'Start in tray'. --- src/widget/widget.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index f89b3702b..10068500b 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -98,10 +98,12 @@ void Widget::init() if (Settings::getInstance().getShowSystemTray()){ icon->show(); + if(Settings::getInstance().getAutostartInTray() == false) + this->show(); } - - if(Settings::getInstance().getAutostartInTray() == false) + else this->show(); + } else { From 52255eb33208f5282cdaee560e2e87ba98b0a8a6 Mon Sep 17 00:00:00 2001 From: Maximilian Date: Sat, 15 Nov 2014 18:34:30 +0100 Subject: [PATCH 096/253] similar bug, when closing the window... --- src/widget/widget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index 10068500b..3410845d4 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -321,7 +321,7 @@ QThread* Widget::getCoreThread() void Widget::closeEvent(QCloseEvent *event) { - if(Settings::getInstance().getCloseToTray() == true) + if(Settings::getInstance().getShowSystemTray() && Settings::getInstance().getCloseToTray() == true) { event->ignore(); this->hide(); From 8ed7f09803d6db7ff5291ffcc0bf483032e5a171 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Sun, 16 Nov 2014 15:23:37 +0100 Subject: [PATCH 097/253] Cherry pick the first part of #762 From 664c7626259de7203221c28cf523a5ca0a209239 Mon Sep 17 00:00:00 2001 From: apprb Date: Sun, 16 Nov 2014 23:48:18 +0900 Subject: [PATCH 098/253] using ToxID instead of bare QString + proper alias using during widget creation --- src/friend.cpp | 15 +++++++++++---- src/friend.h | 2 +- src/friendlist.cpp | 10 +++++----- src/friendlist.h | 5 +++-- src/widget/form/chatform.cpp | 3 --- src/widget/form/chatform.h | 2 +- src/widget/form/genericchatform.cpp | 2 +- src/widget/widget.cpp | 7 ++++--- 8 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/friend.cpp b/src/friend.cpp index 30fa485bd..350ba282d 100644 --- a/src/friend.cpp +++ b/src/friend.cpp @@ -19,15 +19,22 @@ #include "widget/friendwidget.h" #include "widget/form/chatform.h" #include "widget/widget.h" +#include "src/core.h" +#include "src/misc/settings.h" -Friend::Friend(int FriendId, QString UserId) +Friend::Friend(int FriendId, const ToxID &UserId) : friendId(FriendId) { hasNewEvents = 0; friendStatus = Status::Offline; - userID = ToxID::fromString(UserId); - userName = UserId; - widget = new FriendWidget(friendId, UserId); + userID = UserId; + userName = Core::getInstance()->getPeerName(UserId); + if (userName.size() == 0) + userName = UserId.publicKey; + + userAlias = Settings::getInstance().getFriendAlias(UserId); + + widget = new FriendWidget(friendId, getDisplayedName()); chatForm = new ChatForm(this); } diff --git a/src/friend.h b/src/friend.h index 899b9c9cb..7117acc1c 100644 --- a/src/friend.h +++ b/src/friend.h @@ -26,7 +26,7 @@ class ChatForm; struct Friend { public: - Friend(int FriendId, QString UserId); + Friend(int FriendId, const ToxID &UserId); ~Friend(); void setName(QString name); diff --git a/src/friendlist.cpp b/src/friendlist.cpp index 87e8f0c01..d75da5796 100644 --- a/src/friendlist.cpp +++ b/src/friendlist.cpp @@ -24,7 +24,7 @@ QHash FriendList::friendList; QHash FriendList::tox2id; -Friend* FriendList::addFriend(int friendId, const QString& userId) +Friend* FriendList::addFriend(int friendId, const ToxID& userId) { auto friendChecker = friendList.find(friendId); if (friendChecker != friendList.end()) @@ -32,7 +32,7 @@ Friend* FriendList::addFriend(int friendId, const QString& userId) Friend* newfriend = new Friend(friendId, userId); friendList[friendId] = newfriend; - tox2id[userId] = friendId; + tox2id[userId.publicKey] = friendId; return newfriend; } @@ -63,15 +63,15 @@ void FriendList::clear() delete friendptr; } -Friend* FriendList::findFriend(QString userId) +Friend* FriendList::findFriend(const ToxID& userId) { - auto id = tox2id.find(userId); + auto id = tox2id.find(userId.publicKey); if (id != tox2id.end()) { Friend *f = findFriend(*id); if (!f) return nullptr; - if (f->getToxID() == ToxID::fromString(userId)) + if (f->getToxID() == userId) return f; } diff --git a/src/friendlist.h b/src/friendlist.h index 67e7292a2..6d41d44ce 100644 --- a/src/friendlist.h +++ b/src/friendlist.h @@ -21,14 +21,15 @@ template class QList; template class QHash; struct Friend; class QString; +struct ToxID; class FriendList { public: FriendList(); - static Friend* addFriend(int friendId, const QString& userId); + static Friend* addFriend(int friendId, const ToxID &userId); static Friend* findFriend(int friendId); - static Friend* findFriend(QString userId); + static Friend* findFriend(const ToxID &userId); static QList getAllFriends(); static void removeFriend(int friendId, bool fake = false); static void clear(); diff --git a/src/widget/form/chatform.cpp b/src/widget/form/chatform.cpp index ac46e2340..d221fdc78 100644 --- a/src/widget/form/chatform.cpp +++ b/src/widget/form/chatform.cpp @@ -78,9 +78,6 @@ ChatForm::ChatForm(Friend* chatFriend) connect(this, SIGNAL(chatAreaCleared()), this, SLOT(clearReciepts())); setAcceptDrops(true); - - if (Settings::getInstance().getEnableLogging()) - loadHistory(QDateTime::currentDateTime().addDays(-7), true); } ChatForm::~ChatForm() diff --git a/src/widget/form/chatform.h b/src/widget/form/chatform.h index 4ec1735d7..b8920b86f 100644 --- a/src/widget/form/chatform.h +++ b/src/widget/form/chatform.h @@ -36,6 +36,7 @@ public: ChatForm(Friend* chatFriend); ~ChatForm(); void setStatusMessage(QString newMessage); + void loadHistory(QDateTime since, bool processUndelivered = false); void dischargeReceipt(int receipt); @@ -85,7 +86,6 @@ private slots: void updateTime(); protected: - void loadHistory(QDateTime since, bool processUndelivered = false); // drag & drop void dragEnterEvent(QDragEnterEvent* ev); void dropEvent(QDropEvent* ev); diff --git a/src/widget/form/genericchatform.cpp b/src/widget/form/genericchatform.cpp index dc4d00ea6..6b9d2bdd5 100644 --- a/src/widget/form/genericchatform.cpp +++ b/src/widget/form/genericchatform.cpp @@ -364,7 +364,7 @@ MessageActionPtr GenericChatForm::genMessageActionAction(const ToxID& author, QS if (isMe) authorStr = core->getUsername(); else { - Friend *f = FriendList::findFriend(author.publicKey); + Friend *f = FriendList::findFriend(author); if (f) authorStr = f->getDisplayedName(); else diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index c7d28daf5..f7a44b46c 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -611,12 +611,13 @@ void Widget::setStatusMessage(const QString &statusMessage) void Widget::addFriend(int friendId, const QString &userId) { //qDebug() << "Widget: Adding friend with id" << userId; - Friend* newfriend = FriendList::addFriend(friendId, userId); + ToxID userToxId = ToxID::fromString(userId); + Friend* newfriend = FriendList::addFriend(friendId, userToxId); QLayout* layout = contactListWidget->getFriendLayout(Status::Offline); layout->addWidget(newfriend->getFriendWidget()); - QString alias = Settings::getInstance().getFriendAlias(ToxID::fromString(userId)); - newfriend->setAlias(alias); + if (Settings::getInstance().getEnableLogging()) + newfriend->getChatForm()->loadHistory(QDateTime::currentDateTime().addDays(-7), true); connect(newfriend->getFriendWidget(), SIGNAL(chatroomWidgetClicked(GenericChatroomWidget*)), this, SLOT(onChatroomWidgetClicked(GenericChatroomWidget*))); connect(newfriend->getFriendWidget(), SIGNAL(removeFriend(int)), this, SLOT(removeFriend(int))); From 20ba8e6192ca88f4f25bdd79656e0b321444bd8f Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Sun, 16 Nov 2014 16:41:30 +0100 Subject: [PATCH 099/253] Allow to change i/o audio devices without restart Still needs to restart the calls for now Fixes #713 --- src/core.cpp | 50 +++++++++++++++++++++++++++++ src/core.h | 3 ++ src/coreav.cpp | 5 +-- src/widget/form/settings/avform.cpp | 3 ++ 4 files changed, 59 insertions(+), 2 deletions(-) diff --git a/src/core.cpp b/src/core.cpp index e08bd464d..5b33361ed 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -1873,3 +1873,53 @@ void Core::setNospam(uint32_t nospam) std::reverse(nspm, nspm + 4); tox_set_nospam(tox, nospam); } + +void Core::useAudioInput(const QString& inDevDescr) +{ + auto* tmp = alInDev; + alInDev = nullptr; + alcCaptureCloseDevice(tmp); + int stereoFlag = av_DefaultSettings.audio_channels==1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16; + if (inDevDescr.isEmpty()) + alInDev = alcCaptureOpenDevice(nullptr,av_DefaultSettings.audio_sample_rate, stereoFlag, + (av_DefaultSettings.audio_frame_duration * av_DefaultSettings.audio_sample_rate * 4) + / 1000 * av_DefaultSettings.audio_channels); + else + alInDev = alcCaptureOpenDevice(inDevDescr.toStdString().c_str(),av_DefaultSettings.audio_sample_rate, stereoFlag, + (av_DefaultSettings.audio_frame_duration * av_DefaultSettings.audio_sample_rate * 4) + / 1000 * av_DefaultSettings.audio_channels); + if (!alInDev) + qWarning() << "Core: Cannot open input audio device"; + else + qDebug() << "Core: Opening audio input "<start(); return; @@ -641,6 +641,7 @@ void Core::leaveGroupCall(int groupId) disconnect(groupCalls[groupId].sendAudioTimer,0,0,0); groupCalls[groupId].sendAudioTimer->stop(); alcCaptureStop(alInDev); + groupCalls[groupId].alSources.clear(); } void Core::sendGroupCallAudio(int groupId, ToxAv* toxav) @@ -648,7 +649,7 @@ void Core::sendGroupCallAudio(int groupId, ToxAv* toxav) if (!groupCalls[groupId].active) return; - if (groupCalls[groupId].muteMic) + if (groupCalls[groupId].muteMic || !alInDev) { groupCalls[groupId].sendAudioTimer->start(); return; diff --git a/src/widget/form/settings/avform.cpp b/src/widget/form/settings/avform.cpp index 2a658f218..c4920291b 100644 --- a/src/widget/form/settings/avform.cpp +++ b/src/widget/form/settings/avform.cpp @@ -17,6 +17,7 @@ #include "avform.h" #include "ui_avsettings.h" #include "src/misc/settings.h" +#include "src/core.h" #if defined(__APPLE__) && defined(__MACH__) #include @@ -179,9 +180,11 @@ void AVForm::getAudioOutDevices() void AVForm::onInDevChanged(const QString &deviceDescriptor) { Settings::getInstance().setInDev(deviceDescriptor); + Core::getInstance()->useAudioInput(deviceDescriptor); } void AVForm::onOutDevChanged(const QString& deviceDescriptor) { Settings::getInstance().setOutDev(deviceDescriptor); + Core::getInstance()->useAudioOutput(deviceDescriptor); } From 4932b8fed6f626204ff4a91faf817e3e973ffed3 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Sun, 16 Nov 2014 16:51:33 +0100 Subject: [PATCH 100/253] Allow to change audio i/o device mid-call --- src/core.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/core.cpp b/src/core.cpp index 5b33361ed..e28cfacde 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -1892,6 +1892,14 @@ void Core::useAudioInput(const QString& inDevDescr) qWarning() << "Core: Cannot open input audio device"; else qDebug() << "Core: Opening audio input "< Date: Sun, 16 Nov 2014 17:45:06 +0100 Subject: [PATCH 101/253] Leave group calls when leaving a group --- src/core.cpp | 2 ++ src/coreav.h | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core.cpp b/src/core.cpp index e28cfacde..ffdb72317 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -1035,6 +1035,8 @@ void Core::removeGroup(int groupId, bool fake) if (!tox || fake) return; tox_del_groupchat(tox, groupId); + + leaveGroupCall(groupId); } QString Core::getUsername() const diff --git a/src/coreav.h b/src/coreav.h index 1ac4fc12a..4e196fc81 100644 --- a/src/coreav.h +++ b/src/coreav.h @@ -34,7 +34,7 @@ struct ToxGroupCall ToxAvCSettings codecSettings; QTimer *sendAudioTimer; int groupId; - bool active; + bool active = false; bool muteMic; bool muteVol; QHash alSources; From c1f2e9a71d0ec6269df991f103f5a95cf26a8ca0 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Sun, 16 Nov 2014 19:04:45 +0100 Subject: [PATCH 102/253] Refactor audio out of code, better resource management Fixes #739 and fixes #777 --- qtox.pro | 6 +- src/audio.cpp | 112 ++++++++++++++++++++++++++++ src/audio.h | 43 +++++++++++ src/core.cpp | 107 ++++---------------------- src/core.h | 10 +-- src/coreav.cpp | 30 ++++---- src/widget/form/settings/avform.cpp | 6 +- src/widget/widget.cpp | 13 +--- 8 files changed, 196 insertions(+), 131 deletions(-) create mode 100644 src/audio.cpp create mode 100644 src/audio.h diff --git a/qtox.pro b/qtox.pro index 5733a9aa0..ad7d2f34d 100644 --- a/qtox.pro +++ b/qtox.pro @@ -154,7 +154,8 @@ HEADERS += src/widget/form/addfriendform.h \ src/widget/toxsave.h \ src/autoupdate.h \ src/misc/serialize.h \ - src/widget/form/settings/advancedform.h + src/widget/form/settings/advancedform.h \ + src/audio.h SOURCES += \ src/widget/form/addfriendform.cpp \ @@ -220,4 +221,5 @@ SOURCES += \ src/widget/toxsave.cpp \ src/autoupdate.cpp \ src/misc/serialize.cpp \ - src/widget/form/settings/advancedform.cpp + src/widget/form/settings/advancedform.cpp \ + src/audio.cpp diff --git a/src/audio.cpp b/src/audio.cpp new file mode 100644 index 000000000..c8e27109b --- /dev/null +++ b/src/audio.cpp @@ -0,0 +1,112 @@ +#include "audio.h" +#include "src/core.h" + +#include + +std::atomic Audio::userCount{0}; +ALCdevice* Audio::alInDev{nullptr}; +ALCdevice* Audio::alOutDev{nullptr}; +ALCcontext* Audio::alContext{0}; +ALuint Audio::alMainSource{0}; + +void Audio::suscribeInput() +{ + if (userCount++) + if (alInDev) + alcCaptureStart(alInDev); +} + +void Audio::unsuscribeInput() +{ + if (--userCount) + if (alInDev) + alcCaptureStop(alInDev); +} + +void Audio::openInput(const QString& inDevDescr) +{ + auto* tmp = alInDev; + alInDev = nullptr; + alcCaptureCloseDevice(tmp); + int stereoFlag = av_DefaultSettings.audio_channels==1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16; + if (inDevDescr.isEmpty()) + alInDev = alcCaptureOpenDevice(nullptr,av_DefaultSettings.audio_sample_rate, stereoFlag, + (av_DefaultSettings.audio_frame_duration * av_DefaultSettings.audio_sample_rate * 4) + / 1000 * av_DefaultSettings.audio_channels); + else + alInDev = alcCaptureOpenDevice(inDevDescr.toStdString().c_str(),av_DefaultSettings.audio_sample_rate, stereoFlag, + (av_DefaultSettings.audio_frame_duration * av_DefaultSettings.audio_sample_rate * 4) + / 1000 * av_DefaultSettings.audio_channels); + if (!alInDev) + qWarning() << "Audio: Cannot open input audio device"; + else + qDebug() << "Audio: Opening audio input "<resetCallSources(); // Force to regen each group call's sources + + // Restart the capture if necessary + if (userCount.load() != 0) + alcCaptureStart(alInDev); +} + +void Audio::openOutput(const QString& outDevDescr) +{ + auto* tmp = alOutDev; + alOutDev = nullptr; + alcCloseDevice(tmp); + if (outDevDescr.isEmpty()) + alOutDev = alcOpenDevice(nullptr); + else + alOutDev = alcOpenDevice(outDevDescr.toStdString().c_str()); + if (!alOutDev) + { + qWarning() << "Audio: Cannot open output audio device"; + } + else + { + alcDestroyContext(alContext); + alContext=alcCreateContext(alOutDev,nullptr); + if (!alcMakeContextCurrent(alContext)) + { + qWarning() << "Audio: Cannot create output audio context"; + alcCloseDevice(alOutDev); + } + else + alGenSources(1, &alMainSource); + + + qDebug() << "Audio: Opening audio output "<resetCallSources(); // Force to regen each group call's sources +} + +void Audio::closeInput() +{ + if (alInDev) + alcCaptureCloseDevice(alInDev); + + userCount = 0; +} + +void Audio::closeOutput() +{ + if (alContext) + { + alcMakeContextCurrent(nullptr); + alcDestroyContext(alContext); + } + + if (alOutDev) + alcCloseDevice(alOutDev); +} + +void Audio::playMono16Sound(const QByteArray& data) +{ + ALuint buffer; + alGenBuffers(1, &buffer); + alBufferData(buffer, AL_FORMAT_MONO16, data.data(), data.size(), 44100); + alSourcei(alMainSource, AL_BUFFER, buffer); + alSourcePlay(alMainSource); + alDeleteBuffers(1, &buffer); +} diff --git a/src/audio.h b/src/audio.h new file mode 100644 index 000000000..5fb946f28 --- /dev/null +++ b/src/audio.h @@ -0,0 +1,43 @@ +#ifndef AUDIO_H +#define AUDIO_H + +#include + +#if defined(__APPLE__) && defined(__MACH__) + #include + #include +#else + #include + #include +#endif + +class QString; +class QByteArray; + +class Audio +{ +public: + static void suscribeInput(); ///< Call when you need to capture sound from the open input device. + static void unsuscribeInput(); ///< Call once you don't need to capture on the open input device anymore. + + static void openInput(const QString& inDevDescr); ///< Open an input device, use before suscribing + static void openOutput(const QString& outDevDescr); ///< Open an output device + + static void closeInput(); ///< Close an input device, please don't use unless everyone's unsuscribed + static void closeOutput(); ///< Close an output device + + static void playMono16Sound(const QByteArray& data); ///< Play a 44100Hz mono 16bit PCM sound + +public: + static ALCdevice* alOutDev, *alInDev; + static ALCcontext* alContext; + static ALuint alMainSource; + +private: + Audio(); + +private: + static std::atomic userCount; +}; + +#endif // AUDIO_H diff --git a/src/core.cpp b/src/core.cpp index ffdb72317..5b83a0bcd 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -20,6 +20,7 @@ #include "misc/settings.h" #include "widget/widget.h" #include "historykeeper.h" +#include "src/audio.h" #include #include @@ -69,6 +70,8 @@ Core::Core(Camera* cam, QThread *CoreThread, QString loadPath) : for (int i=0; imoveToThread(coreThread); @@ -78,38 +81,9 @@ Core::Core(Camera* cam, QThread *CoreThread, QString loadPath) : // OpenAL init QString outDevDescr = Settings::getInstance().getOutDev(); ; - if (outDevDescr.isEmpty()) - alOutDev = alcOpenDevice(nullptr); - else - alOutDev = alcOpenDevice(outDevDescr.toStdString().c_str()); - if (!alOutDev) - { - qWarning() << "Core: Cannot open output audio device"; - } - else - { - alContext=alcCreateContext(alOutDev,nullptr); - if (!alcMakeContextCurrent(alContext)) - { - qWarning() << "Core: Cannot create output audio context"; - alcCloseDevice(alOutDev); - } - else - alGenSources(1, &alMainSource); - } - + Audio::openOutput(outDevDescr); QString inDevDescr = Settings::getInstance().getInDev(); - int stereoFlag = av_DefaultSettings.audio_channels==1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16; - if (inDevDescr.isEmpty()) - alInDev = alcCaptureOpenDevice(nullptr,av_DefaultSettings.audio_sample_rate, stereoFlag, - (av_DefaultSettings.audio_frame_duration * av_DefaultSettings.audio_sample_rate * 4) - / 1000 * av_DefaultSettings.audio_channels); - else - alInDev = alcCaptureOpenDevice(inDevDescr.toStdString().c_str(),av_DefaultSettings.audio_sample_rate, stereoFlag, - (av_DefaultSettings.audio_frame_duration * av_DefaultSettings.audio_sample_rate * 4) - / 1000 * av_DefaultSettings.audio_channels); - if (!alInDev) - qWarning() << "Core: Cannot open input audio device"; + Audio::openInput(inDevDescr); } Core::~Core() @@ -125,15 +99,8 @@ Core::~Core() videobuf=nullptr; } - if (alContext) - { - alcMakeContextCurrent(nullptr); - alcDestroyContext(alContext); - } - if (alOutDev) - alcCloseDevice(alOutDev); - if (alInDev) - alcCaptureCloseDevice(alInDev); + Audio::closeInput(); + Audio::closeOutput(); clearPassword(Core::ptMain); clearPassword(Core::ptHistory); @@ -1876,64 +1843,20 @@ void Core::setNospam(uint32_t nospam) tox_set_nospam(tox, nospam); } -void Core::useAudioInput(const QString& inDevDescr) +void Core::resetCallSources() { - auto* tmp = alInDev; - alInDev = nullptr; - alcCaptureCloseDevice(tmp); - int stereoFlag = av_DefaultSettings.audio_channels==1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16; - if (inDevDescr.isEmpty()) - alInDev = alcCaptureOpenDevice(nullptr,av_DefaultSettings.audio_sample_rate, stereoFlag, - (av_DefaultSettings.audio_frame_duration * av_DefaultSettings.audio_sample_rate * 4) - / 1000 * av_DefaultSettings.audio_channels); - else - alInDev = alcCaptureOpenDevice(inDevDescr.toStdString().c_str(),av_DefaultSettings.audio_sample_rate, stereoFlag, - (av_DefaultSettings.audio_frame_duration * av_DefaultSettings.audio_sample_rate * 4) - / 1000 * av_DefaultSettings.audio_channels); - if (!alInDev) - qWarning() << "Core: Cannot open input audio device"; - else - qDebug() << "Core: Opening audio input "< dhtServerList; int dhtServerId; static QList fileSendQueue, fileRecvQueue; - static ToxCall calls[]; + static ToxCall calls[TOXAV_MAX_CALLS]; static QHash groupCalls; // Maps group IDs to ToxGroupCalls QMutex fileSendMutex, messageSendMutex; bool ready; @@ -296,12 +295,7 @@ private: static const int videobufsize; static uint8_t* videobuf; - static ALCdevice* alOutDev, *alInDev; - static ALCcontext* alContext; - static QThread *coreThread; -public: - static ALuint alMainSource; }; #endif // CORE_HPP diff --git a/src/coreav.cpp b/src/coreav.cpp index 80c6cf75e..fb6ad3343 100644 --- a/src/coreav.cpp +++ b/src/coreav.cpp @@ -16,6 +16,7 @@ #include "core.h" #include "video/camera.h" +#include "audio.h" #include #include @@ -23,10 +24,6 @@ ToxCall Core::calls[TOXAV_MAX_CALLS]; const int Core::videobufsize{TOXAV_MAX_VIDEO_WIDTH * TOXAV_MAX_VIDEO_HEIGHT * 4}; uint8_t* Core::videobuf; -ALCdevice* Core::alOutDev, *Core::alInDev; -ALCcontext* Core::alContext; -ALuint Core::alMainSource; - bool Core::anyActiveCalls() { for (auto& call : calls) @@ -53,8 +50,7 @@ void Core::prepareCall(int friendId, int callId, ToxAv* toxav, bool videoEnabled qWarning() << QString("Error starting call %1: toxav_prepare_transmission failed with %2").arg(callId).arg(r); // Audio - alGenSources(1, &calls[callId].alSource); - alcCaptureStart(alInDev); + Audio::suscribeInput(); // Go calls[callId].active = true; @@ -197,7 +193,7 @@ void Core::cleanupCall(int callId) calls[callId].sendVideoTimer->stop(); if (calls[callId].videoEnabled) Camera::getInstance()->unsubscribe(); - alcCaptureStop(alInDev); + Audio::unsuscribeInput(); } void Core::playCallAudio(ToxAv* toxav, int32_t callId, int16_t *data, int samples, void *user_data) @@ -207,6 +203,9 @@ void Core::playCallAudio(ToxAv* toxav, int32_t callId, int16_t *data, int sample if (!calls[callId].active) return; + if (!calls[callId].alSource) + alGenSources(1, &calls[callId].alSource); + ToxAvCSettings dest; if(toxav_get_peer_csettings(toxav, callId, 0, &dest) == 0) playAudioBuffer(calls[callId].alSource, data, samples, dest.audio_channels, dest.audio_sample_rate); @@ -217,7 +216,7 @@ void Core::sendCallAudio(int callId, ToxAv* toxav) if (!calls[callId].active) return; - if (calls[callId].muteMic || !alInDev) + if (calls[callId].muteMic || !Audio::alInDev) { calls[callId].sendAudioTimer->start(); return; @@ -229,11 +228,11 @@ void Core::sendCallAudio(int callId, ToxAv* toxav) bool frame = false; ALint samples; - alcGetIntegerv(alInDev, ALC_CAPTURE_SAMPLES, sizeof(samples), &samples); + alcGetIntegerv(Audio::alInDev, ALC_CAPTURE_SAMPLES, sizeof(samples), &samples); if(samples >= framesize) { memset(buf, 0, bufsize); // Avoid uninitialized values (Valgrind) - alcCaptureSamples(alInDev, buf, framesize); + alcCaptureSamples(Audio::alInDev, buf, framesize); frame = 1; } @@ -619,8 +618,7 @@ void Core::joinGroupCall(int groupId) groupCalls[groupId].codecSettings.max_video_height = TOXAV_MAX_VIDEO_HEIGHT; // Audio - //alGenSources(1, &groupCalls[groupId].alSource); - alcCaptureStart(alInDev); + Audio::suscribeInput(); // Go Core* core = Core::getInstance(); @@ -640,8 +638,8 @@ void Core::leaveGroupCall(int groupId) groupCalls[groupId].active = false; disconnect(groupCalls[groupId].sendAudioTimer,0,0,0); groupCalls[groupId].sendAudioTimer->stop(); - alcCaptureStop(alInDev); groupCalls[groupId].alSources.clear(); + Audio::unsuscribeInput(); } void Core::sendGroupCallAudio(int groupId, ToxAv* toxav) @@ -649,7 +647,7 @@ void Core::sendGroupCallAudio(int groupId, ToxAv* toxav) if (!groupCalls[groupId].active) return; - if (groupCalls[groupId].muteMic || !alInDev) + if (groupCalls[groupId].muteMic || !Audio::alInDev) { groupCalls[groupId].sendAudioTimer->start(); return; @@ -661,11 +659,11 @@ void Core::sendGroupCallAudio(int groupId, ToxAv* toxav) bool frame = false; ALint samples; - alcGetIntegerv(alInDev, ALC_CAPTURE_SAMPLES, sizeof(samples), &samples); + alcGetIntegerv(Audio::alInDev, ALC_CAPTURE_SAMPLES, sizeof(samples), &samples); if(samples >= framesize) { memset(buf, 0, bufsize); // Avoid uninitialized values (Valgrind) - alcCaptureSamples(alInDev, buf, framesize); + alcCaptureSamples(Audio::alInDev, buf, framesize); frame = 1; } diff --git a/src/widget/form/settings/avform.cpp b/src/widget/form/settings/avform.cpp index c4920291b..962819c26 100644 --- a/src/widget/form/settings/avform.cpp +++ b/src/widget/form/settings/avform.cpp @@ -17,7 +17,7 @@ #include "avform.h" #include "ui_avsettings.h" #include "src/misc/settings.h" -#include "src/core.h" +#include "src/audio.h" #if defined(__APPLE__) && defined(__MACH__) #include @@ -180,11 +180,11 @@ void AVForm::getAudioOutDevices() void AVForm::onInDevChanged(const QString &deviceDescriptor) { Settings::getInstance().setInDev(deviceDescriptor); - Core::getInstance()->useAudioInput(deviceDescriptor); + Audio::openInput(deviceDescriptor); } void AVForm::onOutDevChanged(const QString& deviceDescriptor) { Settings::getInstance().setOutDev(deviceDescriptor); - Core::getInstance()->useAudioOutput(deviceDescriptor); + Audio::openOutput(deviceDescriptor); } diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index 81e359370..6077b60ec 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -34,6 +34,7 @@ #include "src/historykeeper.h" #include "form/inputpassworddialog.h" #include "src/autoupdate.h" +#include "src/audio.h" #include #include #include @@ -807,11 +808,7 @@ void Widget::newMessageAlert(GenericChatroomWidget* chat) sndFile.close(); } - ALuint buffer; - alGenBuffers(1, &buffer); - alBufferData(buffer, AL_FORMAT_MONO16, sndData.data(), sndData.size(), 44100); - alSourcei(core->alMainSource, AL_BUFFER, buffer); - alSourcePlay(core->alMainSource); + Audio::playMono16Sound(sndData); } void Widget::playRingtone() @@ -827,11 +824,7 @@ void Widget::playRingtone() sndFile1.close(); } - ALuint buffer; - alGenBuffers(1, &buffer); - alBufferData(buffer, AL_FORMAT_MONO16, sndData1.data(), sndData1.size(), 44100); - alSourcei(core->alMainSource, AL_BUFFER, buffer); - alSourcePlay(core->alMainSource); + Audio::playMono16Sound(sndData1); } void Widget::onFriendRequestReceived(const QString& userId, const QString& message) From 8bc4bbdce06bd3d11585fa3ff00a819dbeb6f71c Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Sun, 16 Nov 2014 19:11:37 +0100 Subject: [PATCH 103/253] Fix #778 --- src/core.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core.cpp b/src/core.cpp index 5b83a0bcd..9d49c6f18 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -1003,7 +1003,8 @@ void Core::removeGroup(int groupId, bool fake) return; tox_del_groupchat(tox, groupId); - leaveGroupCall(groupId); + if (groupCalls[groupId].active) + leaveGroupCall(groupId); } QString Core::getUsername() const From 366f98f264b664a8db6e0af4bbc89bd3d49f3f94 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Sun, 16 Nov 2014 20:18:00 +0100 Subject: [PATCH 104/253] Fix #635 --- src/widget/form/genericchatform.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widget/form/genericchatform.cpp b/src/widget/form/genericchatform.cpp index 6b9d2bdd5..15f089a30 100644 --- a/src/widget/form/genericchatform.cpp +++ b/src/widget/form/genericchatform.cpp @@ -336,7 +336,7 @@ MessageActionPtr GenericChatForm::genMessageActionAction(const QString &author, if (isAction) { previousId = ToxID(); // next msg has a name regardless - return MessageActionPtr(new ActionAction (getElidedName(author), message, date, isMe)); + return MessageActionPtr(new ActionAction (author, message, date, isMe)); } MessageActionPtr res; From e8964b178d7f10a3aa0fe547b52dbae5d861ae5e Mon Sep 17 00:00:00 2001 From: dubslow Date: Sun, 16 Nov 2014 13:28:40 -0600 Subject: [PATCH 105/253] Extra tooltip and word from the un-picked commit --- src/widget/form/settings/generalsettings.ui | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/widget/form/settings/generalsettings.ui b/src/widget/form/settings/generalsettings.ui index f74e805e4..3b745c8e8 100644 --- a/src/widget/form/settings/generalsettings.ui +++ b/src/widget/form/settings/generalsettings.ui @@ -98,7 +98,7 @@ - Show system tray + Show system tray icon @@ -217,6 +217,9 @@ + + You can set this on a per-friend basis by right clicking them. + Autoaccept files From 44c5b596e7229d5c39db64c772c6c979f8f218ed Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Sun, 16 Nov 2014 20:32:05 +0100 Subject: [PATCH 106/253] Cherry pick f3019a8111f25ef0400c287d851b992d4ca3d839 From e4197920c3a07ee288e222061707d6e26f2c0e78 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Sun, 16 Nov 2014 20:49:20 +0100 Subject: [PATCH 107/253] Add missing license headers --- src/audio.cpp | 17 +++++++++++++++++ src/audio.h | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/audio.cpp b/src/audio.cpp index c8e27109b..d51b31951 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -1,3 +1,20 @@ +/* + Copyright (C) 2014 by Project Tox + + This file is part of qTox, a Qt-based graphical interface for Tox. + + This program is libre software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the COPYING file for more details. +*/ + + #include "audio.h" #include "src/core.h" diff --git a/src/audio.h b/src/audio.h index 5fb946f28..6a3875243 100644 --- a/src/audio.h +++ b/src/audio.h @@ -1,3 +1,20 @@ +/* + Copyright (C) 2014 by Project Tox + + This file is part of qTox, a Qt-based graphical interface for Tox. + + This program is libre software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the COPYING file for more details. +*/ + + #ifndef AUDIO_H #define AUDIO_H From 9176cf54ce5080a98721a1a6c98d9bcd8402c4af Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Sun, 16 Nov 2014 20:51:44 +0100 Subject: [PATCH 108/253] Fix logic error in audio subscription code --- src/audio.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/audio.cpp b/src/audio.cpp index d51b31951..46620b6e9 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -28,16 +28,14 @@ ALuint Audio::alMainSource{0}; void Audio::suscribeInput() { - if (userCount++) - if (alInDev) - alcCaptureStart(alInDev); + if (!userCount++ && alInDev) + alcCaptureStart(alInDev); } void Audio::unsuscribeInput() { - if (--userCount) - if (alInDev) - alcCaptureStop(alInDev); + if (!--userCount && alInDev) + alcCaptureStop(alInDev); } void Audio::openInput(const QString& inDevDescr) From ed1e864aed111c9d22f6330b563c94980e6c564e Mon Sep 17 00:00:00 2001 From: Nokta-strigo Date: Mon, 17 Nov 2014 00:44:12 +0300 Subject: [PATCH 109/253] There was wrong english string --- translations/ru.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/translations/ru.ts b/translations/ru.ts index 1384d5122..a86642041 100644 --- a/translations/ru.ts +++ b/translations/ru.ts @@ -508,7 +508,7 @@ Do you want to try another password? - Show system tray + Show system tray icon Показывать иконку в трее From 5cf670aa7ed2407d620ee2377a3a15d7e84a9eba Mon Sep 17 00:00:00 2001 From: Nokta-strigo Date: Mon, 17 Nov 2014 01:04:07 +0300 Subject: [PATCH 110/253] Translation of synchronous writing to DB settings --- translations/ru.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/translations/ru.ts b/translations/ru.ts index a86642041..7bc735c59 100644 --- a/translations/ru.ts +++ b/translations/ru.ts @@ -131,6 +131,21 @@ Ignore the proxy and connect to the Internet directly ? Advanced Расширенные + + + FULL - very safe, slowest (recommended) + Полная - полностью безопасно, самая медленная (рекомендуется) + + + + NORMAL - almost as safe as FULL, about 20% faster than FULL + Нормальная - почти так же безопасно, как полная, но на 20% быстрее + + + + OFF - disables all safety, when something goes wrong your history may be lost, fastest (not recommended) + Выключена - небезопасно, если что-то пойдёт не так, история может быть утрачена, самая быстрая (не рекомендуется) + AdvancedSettings From eab7a01c799fe8830f3a0052b41ac355a3daae8e Mon Sep 17 00:00:00 2001 From: Nokta-strigo Date: Mon, 17 Nov 2014 01:40:50 +0300 Subject: [PATCH 111/253] Translated call control buttons --- translations/ru.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/translations/ru.ts b/translations/ru.ts index 7bc735c59..4c6b02c3b 100644 --- a/translations/ru.ts +++ b/translations/ru.ts @@ -715,23 +715,23 @@ Do you want to try another password? - Audio call - Позвонить + Audio call: RED means you're on a call + Позвонить, только аудио (красная кнопка значит что вы на связи) - Video call - Видеозвонок + Video call: RED means you're on a call + Видеозвонок (красная кнопка значит что вы на связи) - Toggle speakers volume - Включить или выключить звук + Toggle speakers volume: RED is OFF + Включить или выключить звук (красная кнопка значит звук выключен) - Toggle microphone - Включить или выключить микрофон + Toggle microphone: RED is OFF + Включить или выключить микрофон (красная кнопка значит микрофон выключен) From d7f32ac7cf85eab7df7f60e167ffa6436d624f0c Mon Sep 17 00:00:00 2001 From: Nokta-strigo Date: Mon, 17 Nov 2014 01:50:17 +0300 Subject: [PATCH 112/253] Slightly improoved translation --- translations/ru.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/translations/ru.ts b/translations/ru.ts index 4c6b02c3b..ac9740120 100644 --- a/translations/ru.ts +++ b/translations/ru.ts @@ -726,12 +726,12 @@ Do you want to try another password? Toggle speakers volume: RED is OFF - Включить или выключить звук (красная кнопка значит звук выключен) + Включить или выключить звук (красная кнопка значит что звук выключен) Toggle microphone: RED is OFF - Включить или выключить микрофон (красная кнопка значит микрофон выключен) + Включить или выключить микрофон (красная кнопка значит что микрофон выключен) From b3ed62f72b3637036be5b6d117b7cf87749e848c Mon Sep 17 00:00:00 2001 From: Ansa89 Date: Mon, 17 Nov 2014 09:21:52 +0100 Subject: [PATCH 113/253] Italian translation: update --- translations/it.ts | 264 ++++++++++++++++++++++++++++----------------- 1 file changed, 165 insertions(+), 99 deletions(-) diff --git a/translations/it.ts b/translations/it.ts index 01e0637e8..45bcccca8 100644 --- a/translations/it.ts +++ b/translations/it.ts @@ -4,7 +4,7 @@ AVForm - + Audio/Video Audio/Video @@ -130,6 +130,21 @@ Ignorare le impostazioni del proxy e connettersi direttamente alla rete Tox?Advanced Avanzate + + + FULL - very safe, slowest (recommended) + COMPLETO - molto sicuro; lento (consigliato) + + + + NORMAL - almost as safe as FULL, about 20% faster than FULL + NORMALE - quasi sicuro come COMPLETO, ma circa 20% più veloce + + + + OFF - disables all safety, when something goes wrong your history may be lost, fastest (not recommended) + DISABILITATO - disabilita tutta la sicurezza, quando qualcosa va male i log possono essere persi; veloce (non consigliato) + AdvancedSettings @@ -167,47 +182,47 @@ Ignorare le impostazioni del proxy e connettersi direttamente alla rete Tox?Carica log... - + Send a file Invia un file - + Bad Idea Pessima idea - + You're trying to send a special (sequential) file, that's not going to work! Stai cercando di inviare un file speciale (sequenziale), questo non funzionerà! - + %1 calling %1 ti sta chiamando - + %1 stopped calling %1 ha fermato la chiamata - + Calling to %1 Stai chiamando %1 - + Call with %1 ended. %2 Chiamata con %1 terminata. %2 - + Call duration: Durata chiamata: - + Call rejected Chiamata rifiutata @@ -223,105 +238,105 @@ Ignorare le impostazioni del proxy e connettersi direttamente alla rete Tox? Core - + Toxing on qTox Toxing on qTox - + qTox User qTox User - + Friend is already added Questo contatto è già presente nella tua lista - + Encryption error Errore crittografia - + The .tox file is encrypted, but encryption was not checked, continuing regardless. Il Tox datafile è criptato, ma la crittografia non è abilitata nelle impostazioni. Continuo ignorando le impostazioni. - + Tox datafile decryption password Password per decriptare il Tox datafile - - - + + + Password error Errore password - - + + Failed to setup password. Empty password. Impossibile impostare la password. Password vuota. - + Try Again Riprova - + Change profile Cambia profilo - + Reinit current profile Reinizializza il profilo corrente - + Wrong password has been entered È stata inserita una password sbagliata - + History Log decryption password Password per decriptare i log - + Encrypted log Log criptato - + Your history is encrypted with different password Do you want to try another password? I log sono criptati con una password diversa. Vuoi provare un'altra password? - + Loggin Logging - + Due to incorret password logging will be disabled I log saranno disabilitati a causa di una password incorretta - + NO Password Nessuna password - + Will be saved without encryption! Il Tox datafile sarà salvato senza password! @@ -458,30 +473,30 @@ Vuoi provare un'altra password? GeneralForm - + General Generale - - + + None Nessuno - + Choose an auto accept directory popup title Scegli dove salvare i files accettati automaticamente - + Call active popup title Chiamata in corso - + You can't disconnect while a call is active! popup text Non puoi disconnetterti mentre c'è una chiamata in corso! @@ -500,18 +515,23 @@ Vuoi provare un'altra password? The translation may not load until qTox restarts. La traduzione potrebbe non essere caricata fino al prossimo riavvio di qTox. + + + Show system tray icon + Mostra icona nella traybar + Set to 0 to disable Imposta 0 per disabilitare - + Connection Settings Impostazioni Connessione - + Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Abilita IPv6 (consigliato) @@ -532,11 +552,6 @@ Vuoi provare un'altra password? Make Tox portable Rendi qTox portabile - - - Show system tray - Mostra icona nella traybar - Start in tray @@ -583,92 +598,103 @@ Vuoi provare un'altra password? Imposta assenza dopo - + minutes minuti + You can set this on a per-friend basis by right clicking them. + autoaccept cb tooltip + Puoi impostare questa preferenza per ogni singolo contatto usando il click destro sul suo nome. + + + Autoaccept files Accetta automaticamente i trasferimenti di files - + Save files in Salva i files in - + PushButton Sfoglia - + Theme Impostazioni Tema - + Use emoticons Usa emoticons - + Smiley Pack Text on smiley pack label Emoticons - + Style Stile - + + Theme color + Colore + + + Emoticon size Dimensione emoticons - + px px - + Timestamp format Formato data/ora - + Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Disabilitando questo sarà possibile usare qTox con Tor. Tuttavia verrà aggiunto carico alla rete Tox, quindi disabilitare solo se necessario. - + Enable UDP (recommended) Text on checkbox to disable UDP Abilita UDP (consigliato) - + Use proxy (SOCKS5) Usa proxy (SOCKS5) - + Address Text on proxy addr label IP - + Port Text on proxy port label Porta - + Reconnect reconnect button Riconnetti @@ -693,23 +719,23 @@ Vuoi provare un'altra password? - Audio call - Chiamata audio + Audio call: RED means you're on a call + Chiamata audio: ROSSO significa che la chiamata è in corso - Video call - Videochiamata + Video call: RED means you're on a call + Videochiamata: ROSSO significa che la chiamata è in corso - Toggle speakers volume - Cambia il volume degli altoparlanti + Toggle speakers volume: RED is OFF + Imposta volume altoparlanti: ROSSO è SPENTO - Toggle microphone - Attiva/Disattiva microfono + Toggle microphone: RED is OFF + Imposta microfono: ROSSO è SPENTO @@ -737,7 +763,7 @@ Vuoi provare un'altra password? %1 utenti in chat - + %1 users in chat %1 utenti in chat @@ -1019,27 +1045,27 @@ Vuoi provare un'altra password? Toxing on qTox - + Add friends Aggiungi contatto - + Create a group chat Crea un gruppo - + View completed file transfers Visualizza i trasferimenti completati - + Change your settings Cambia le impostazioni - + Close Chiudi @@ -1060,12 +1086,12 @@ Vuoi provare un'altra password? Privacy - + Encrypted log Log criptato - + You already have history log file encrypted with different password Do you want to delete old history file? Hai già un file di log criptato con una password diversa. @@ -1099,6 +1125,21 @@ Vuoi eliminare il vecchio file? Encrypt History Cripta log delle chat + + + Nospam + Nospam + + + + HHHHHHHH + HHHHHHHH + + + + Generate random nospam + Genera valore nospam casuale + QObject @@ -1160,6 +1201,31 @@ Verrà installata al riavvio del programma. Tox URI to parse URI Tox da interpretare + + + Default + Default + + + + Blue + Blu + + + + Olive + Oliva + + + + Red + Rosso + + + + Violet + Viola + SetPasswordDialog @@ -1265,55 +1331,55 @@ Verrà installata al riavvio del programma. Widget - + Online Online - + Away Assente - + Busy Occupato - + &Quit &Esci - + Change status to: Cambia stato in: - + Online Button to set your status to 'Online' Online - + Away Button to set your status to 'Away' Assente - + Busy Button to set your status to 'Busy' Occupato - + Choose a profile Scegli un profilo - + Please choose which identity to use Per favore scegli quale identità usare @@ -1356,68 +1422,68 @@ Verrà installata al riavvio del programma. Impossibile avviare Toxcore con le tue impostazione proxy.\nqTox non può funzionare correttamente, per favore modifica le impostazioni e riavvia il programma. - + Add friend Aggiungi contatto - + File transfers Files trasferiti - + Settings Impostazioni - + Couldn't request friendship Impossibile inviare la richiesta d'amicizia - + away contact status assente - + busy contact status occupato - + offline contact status offline - + online contact status online - + %1 is now %2 e.g. "Dubslow is now online" %1 è ora %2 - + <Unknown> Placeholder when we don't know someone's name in a group chat <Sconosciuto> - + %1 has set the title to %2 %1 ha impostato il titolo in %2 - + Message failed to send Impossibile inviare il messaggio From e78e3886764c76a5d396e7bc8d2e669668ad3691 Mon Sep 17 00:00:00 2001 From: Sean Date: Mon, 17 Nov 2014 01:45:55 -0800 Subject: [PATCH 114/253] Update makedist.sh --- osx/makedist.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osx/makedist.sh b/osx/makedist.sh index 10059b9b0..dc0b6a3c7 100644 --- a/osx/makedist.sh +++ b/osx/makedist.sh @@ -10,7 +10,7 @@ echo " - + From 5bd6cc7445116a904c9952bef28c7a1e3458204c Mon Sep 17 00:00:00 2001 From: Sean Date: Mon, 17 Nov 2014 01:48:24 -0800 Subject: [PATCH 115/253] Create postinstall --- osx/postinstall | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 osx/postinstall diff --git a/osx/postinstall b/osx/postinstall new file mode 100644 index 000000000..e1af30717 --- /dev/null +++ b/osx/postinstall @@ -0,0 +1,3 @@ +#!/bin/sh + +rm -rf ~/Library/Preferences/tox/update From 04fd006d2f0022fb3dad72e94febc143978f1ec9 Mon Sep 17 00:00:00 2001 From: Sean Date: Mon, 17 Nov 2014 01:50:04 -0800 Subject: [PATCH 116/253] Create preinstall --- osx/preinstall | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 osx/preinstall diff --git a/osx/preinstall b/osx/preinstall new file mode 100644 index 000000000..e63de0b32 --- /dev/null +++ b/osx/preinstall @@ -0,0 +1,3 @@ +#!/bin/sh + +killall qtox From 8f3e1c2ef3c4c5b7af184979940da02b00b8cd9b Mon Sep 17 00:00:00 2001 From: apprb Date: Mon, 17 Nov 2014 19:10:38 +0900 Subject: [PATCH 117/253] fix #775 --- src/filetransferinstance.cpp | 4 ++- src/misc/settings.cpp | 59 ++++++++++++++++++++---------------- src/misc/settings.h | 5 +-- src/widget/form/chatform.cpp | 2 +- src/widget/friendwidget.cpp | 2 +- 5 files changed, 41 insertions(+), 31 deletions(-) diff --git a/src/filetransferinstance.cpp b/src/filetransferinstance.cpp index ddb1ab267..2c50e2d46 100644 --- a/src/filetransferinstance.cpp +++ b/src/filetransferinstance.cpp @@ -18,6 +18,8 @@ #include "core.h" #include "misc/settings.h" #include "misc/style.h" +#include "src/friendlist.h" +#include "src/friend.h" #include #include #include @@ -208,7 +210,7 @@ bool isFileWritable(QString& path) void FileTransferInstance::acceptRecvRequest() { - QString path = Settings::getInstance().getAutoAcceptDir(Core::getInstance()->getFriendAddress(friendId)); + QString path = Settings::getInstance().getAutoAcceptDir(FriendList::findFriend(friendId)->getToxID()); if (path.isEmpty()) path = Settings::getInstance().getGlobalAutoAcceptDir(); diff --git a/src/misc/settings.cpp b/src/misc/settings.cpp index 0770c42c8..6087708f6 100644 --- a/src/misc/settings.cpp +++ b/src/misc/settings.cpp @@ -98,13 +98,14 @@ void Settings::load() friendLst.clear(); s.beginGroup("Friends"); - int size = s.beginReadArray("fullAddresses"); + int size = s.beginReadArray("Friend"); for (int i = 0; i < size; i ++) { s.setArrayIndex(i); friendProp fp; fp.addr = s.value("addr").toString(); fp.alias = s.value("alias").toString(); + fp.autoAcceptDir = s.value("autoAcceptDir").toString(); friendLst[ToxID::fromString(fp.addr).publicKey] = fp; } s.endArray(); @@ -126,6 +127,10 @@ void Settings::load() checkUpdates = s.value("checkUpdates", false).toBool(); showInFront = s.value("showInFront", false).toBool(); fauxOfflineMessaging = s.value("fauxOfflineMessaging", true).toBool(); + autoSaveEnabled = s.value("autoSaveEnabled", false).toBool(); + globalAutoAcceptDir = s.value("globalAutoAcceptDir", + QStandardPaths::locate(QStandardPaths::HomeLocation, QString(), QStandardPaths::LocateDirectory) + ).toString(); s.endGroup(); s.beginGroup("Advanced"); @@ -178,16 +183,6 @@ void Settings::load() encryptTox = s.value("encryptTox", false).toBool(); s.endGroup(); - s.beginGroup("AutoAccept"); - autoSaveEnabled = s.value("autoSaveEnabled", false).toBool(); - globalAutoAcceptDir = s.value("globalAutoAcceptDir", - QStandardPaths::locate(QStandardPaths::HomeLocation, QString(), QStandardPaths::LocateDirectory) - ).toString(); - - for (auto& key : s.childKeys()) - autoAccept[key] = s.value(key).toString(); - s.endGroup(); - s.beginGroup("Audio"); inDev = s.value("inDev", "").toString(); outDev = s.value("outDev", "").toString(); @@ -244,13 +239,14 @@ void Settings::save(QString path) s.endGroup(); s.beginGroup("Friends"); - s.beginWriteArray("fullAddresses", friendLst.size()); + s.beginWriteArray("Friend", friendLst.size()); int index = 0; for (auto &frnd : friendLst) { s.setArrayIndex(index); s.setValue("addr", frnd.addr); s.setValue("alias", frnd.alias); + s.setValue("autoAcceptDir", frnd.autoAcceptDir); index++; } s.endArray(); @@ -272,6 +268,8 @@ void Settings::save(QString path) s.setValue("checkUpdates", checkUpdates); s.setValue("showInFront", showInFront); s.setValue("fauxOfflineMessaging", fauxOfflineMessaging); + s.setValue("autoSaveEnabled", autoSaveEnabled); + s.setValue("globalAutoAcceptDir", globalAutoAcceptDir); s.endGroup(); s.beginGroup("Advanced"); @@ -316,13 +314,6 @@ void Settings::save(QString path) s.setValue("encryptTox", encryptTox); s.endGroup(); - s.beginGroup("AutoAccept"); - s.setValue("autoSaveEnabled", autoSaveEnabled); - s.setValue("globalAutoAcceptDir", globalAutoAcceptDir); - for (auto& id : autoAccept.keys()) - s.setValue(id, autoAccept.value(id)); - s.endGroup(); - s.beginGroup("Audio"); s.setValue("inDev", inDev); s.setValue("outDev", outDev); @@ -634,17 +625,31 @@ void Settings::setAutoAwayTime(int newValue) autoAwayTime = newValue; } -QString Settings::getAutoAcceptDir(const QString& id) const +QString Settings::getAutoAcceptDir(const ToxID& id) const { - return autoAccept.value(id.left(TOX_ID_PUBLIC_KEY_LENGTH)); + QString key = id.publicKey; + + auto it = friendLst.find(key); + if (it != friendLst.end()) + { + return it->autoAcceptDir; + } + + return QString(); } -void Settings::setAutoAcceptDir(const QString& id, const QString& dir) +void Settings::setAutoAcceptDir(const ToxID &id, const QString& dir) { - if (dir.isEmpty()) - autoAccept.remove(id.left(TOX_ID_PUBLIC_KEY_LENGTH)); - else - autoAccept[id.left(TOX_ID_PUBLIC_KEY_LENGTH)] = dir; + QString key = id.publicKey; + + auto it = friendLst.find(key); + if (it != friendLst.end()) + { + it->autoAcceptDir = dir; + } else { + updateFriendAdress(id.toString()); + setAutoAcceptDir(id, dir); + } } QString Settings::getGlobalAutoAcceptDir() const @@ -865,6 +870,7 @@ void Settings::updateFriendAdress(const QString &newAddr) friendProp fp; fp.addr = newAddr; fp.alias = ""; + fp.autoAcceptDir = ""; friendLst[newAddr] = fp; } } @@ -892,6 +898,7 @@ void Settings::setFriendAlias(const ToxID &id, const QString &alias) friendProp fp; fp.addr = key; fp.alias = alias; + fp.autoAcceptDir = ""; friendLst[key] = fp; } } diff --git a/src/misc/settings.h b/src/misc/settings.h index 7468fe3fe..5b923d8b1 100644 --- a/src/misc/settings.h +++ b/src/misc/settings.h @@ -164,8 +164,8 @@ public: int getEmojiFontPointSize() const; void setEmojiFontPointSize(int value); - QString getAutoAcceptDir(const QString& id) const; - void setAutoAcceptDir(const QString&id, const QString& dir); + QString getAutoAcceptDir(const ToxID& id) const; + void setAutoAcceptDir(const ToxID&id, const QString& dir); QString getGlobalAutoAcceptDir() const; void setGlobalAutoAcceptDir(const QString& dir); @@ -295,6 +295,7 @@ private: { QString alias; QString addr; + QString autoAcceptDir; }; QHash friendLst; diff --git a/src/widget/form/chatform.cpp b/src/widget/form/chatform.cpp index d221fdc78..d4cde243b 100644 --- a/src/widget/form/chatform.cpp +++ b/src/widget/form/chatform.cpp @@ -217,7 +217,7 @@ void ChatForm::onFileRecvRequest(ToxFile file) chatWidget->insertMessage(ChatActionPtr(new FileTransferAction(fileTrans, getElidedName(name), QTime::currentTime().toString("hh:mm"), false))); - if (!Settings::getInstance().getAutoAcceptDir(Core::getInstance()->getFriendAddress(f->getFriendID())).isEmpty() + if (!Settings::getInstance().getAutoAcceptDir(f->getToxID()).isEmpty() || Settings::getInstance().getAutoSaveEnabled()) fileTrans->pressFromHtml("btnB"); } diff --git a/src/widget/friendwidget.cpp b/src/widget/friendwidget.cpp index 4da65a98b..1904f02b2 100644 --- a/src/widget/friendwidget.cpp +++ b/src/widget/friendwidget.cpp @@ -49,7 +49,7 @@ FriendWidget::FriendWidget(int FriendId, QString id) void FriendWidget::contextMenuEvent(QContextMenuEvent * event) { QPoint pos = event->globalPos(); - QString id = Core::getInstance()->getFriendAddress(friendId); + ToxID id = FriendList::findFriend(friendId)->getToxID(); QString dir = Settings::getInstance().getAutoAcceptDir(id); QMenu menu; QMenu* inviteMenu = menu.addMenu(tr("Invite to group","Menu to invite a friend to a groupchat")); From 827472e3b5fdeb4419ce5dda90c73f75495a2bd6 Mon Sep 17 00:00:00 2001 From: apprb Date: Tue, 18 Nov 2014 00:01:56 +0900 Subject: [PATCH 118/253] use GlobalAutoAcceptDir only if autosaving enabled --- src/filetransferinstance.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/filetransferinstance.cpp b/src/filetransferinstance.cpp index 2c50e2d46..bf690cdd1 100644 --- a/src/filetransferinstance.cpp +++ b/src/filetransferinstance.cpp @@ -212,7 +212,7 @@ void FileTransferInstance::acceptRecvRequest() { QString path = Settings::getInstance().getAutoAcceptDir(FriendList::findFriend(friendId)->getToxID()); - if (path.isEmpty()) + if (path.isEmpty() && Settings::getInstance().getAutoSaveEnabled()) path = Settings::getInstance().getGlobalAutoAcceptDir(); if (!path.isEmpty()) From 0660d2974f7ec15473a7fcd6333ca0bd47c80bf6 Mon Sep 17 00:00:00 2001 From: Maximilian Date: Mon, 17 Nov 2014 16:35:30 +0100 Subject: [PATCH 119/253] The showSystemTrayIcon's default value is false for linux else it is false. --- src/misc/settings.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/misc/settings.cpp b/src/misc/settings.cpp index 6087708f6..874913ec5 100644 --- a/src/misc/settings.cpp +++ b/src/misc/settings.cpp @@ -29,6 +29,13 @@ #include #include + +#ifdef Q_OS_LINUX +#define SHOW_SYSTEM_TRAY_DEFAULT (bool) false +#else // OS is not linux +#define SHOW_SYSTEM_TRAY_DEFAULT (bool) true +#endif + const QString Settings::FILENAME = "settings.ini"; bool Settings::makeToxPortable{false}; @@ -114,7 +121,7 @@ void Settings::load() s.beginGroup("General"); enableIPv6 = s.value("enableIPv6", true).toBool(); translation = s.value("translation", "en").toString(); - showSystemTray = s.value("showSystemTray", false).toBool(); + showSystemTray = s.value("showSystemTray", SHOW_SYSTEM_TRAY_DEFAULT).toBool(); makeToxPortable = s.value("makeToxPortable", false).toBool(); autostartInTray = s.value("autostartInTray", false).toBool(); closeToTray = s.value("closeToTray", false).toBool(); From 4783dd072807bc75490db55a3384d225b6d21580 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Mon, 17 Nov 2014 17:35:57 +0100 Subject: [PATCH 120/253] Cherry pick pull request #793 Closes #793 From 32e36b8d10aa7f448710cb862c27f0df760eddcd Mon Sep 17 00:00:00 2001 From: ovalseven8 Date: Mon, 17 Nov 2014 17:53:04 +0100 Subject: [PATCH 121/253] Update de.ts Added some translations. --- translations/de.ts | 102 ++++++++++++++++++++++----------------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/translations/de.ts b/translations/de.ts index c4e3bb642..b651b13a6 100644 --- a/translations/de.ts +++ b/translations/de.ts @@ -6,7 +6,7 @@ Audio/Video settings - + Audio/Video Einstellungen @@ -19,7 +19,7 @@ Volume Settings (Stubs) - + Lautstärke-Einstellungen @@ -29,42 +29,42 @@ Microphone - + Mikrofon Video settings - + Video-Einstellungen Modes - + Modi Hue - + Farbe Brightness - + Helligkeit Saturation - + Sättigung Contrast - + Kontrast Preview - + Vorschau @@ -95,7 +95,7 @@ Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! - Lass uns Toxen! + Lass uns toxen! @@ -107,13 +107,13 @@ You can't add yourself as a friend! When trying to add your own Tox ID as friend - + Es ist nicht möglich, sich selbst als Freund hinzuzufügen This address does not exist The DNS gives the Tox ID associated to toxme.se addresses - + Diese Adresse existiert nicht. @@ -125,7 +125,7 @@ Unexpected number of text records Error with the DNS - Unererwartete Anzahl von Texteinträgen + Unerwartete Anzahl von Texteinträgen @@ -301,7 +301,7 @@ Do you want to erase this profile ? General Settings - + Allgemeine Einstellungen @@ -322,13 +322,13 @@ Do you want to erase this profile ? General Settings - + Allgemeine Einstellungen Use translations Text on a checkbox to enable translations - + Übersetzungen @@ -364,7 +364,7 @@ Do you want to erase this profile ? minutes - + Minuten @@ -405,12 +405,12 @@ Do you want to erase this profile ? Style: - + Style: Connection Settings - + Verbindungs-Einstellungen @@ -433,19 +433,19 @@ Do you want to erase this profile ? Use proxy (SOCKS5) - + Proxy verwenden (SOCKS5) Address Text on proxy addr label - + Adresse Port Text on proxy port label - + Port @@ -517,7 +517,7 @@ Do you want to erase this profile ? You can't switch profiles while a call is active! popup text - + Du kannst dein Profil nicht wechseln, solange ein Anruf läuft. @@ -547,7 +547,7 @@ Do you want to erase this profile ? This profile is currently in use. Please load a different profile before deleting this one. current profile deletion warning text - + Dieses Profil wird gerade verwendet. Bitte lade ein anderes Profil, um dieses löschen zu können. @@ -559,13 +559,13 @@ Do you want to erase this profile ? Are you sure you want to delete this profile? deletion confirmation text - + Bist du sicher, dieses Profil löschen zu wollen? Import profile import dialog title - + Profil importieren @@ -633,23 +633,23 @@ Do you want to erase this profile ? Your Tox ID (click to copy) - + Deine TOX-ID (klicken um zu kopieren) Profiles - + Profile: Available profiles: - + Verfügbare Profile: Load load profile button - + Laden @@ -661,25 +661,25 @@ Do you want to erase this profile ? Export export profile button - + Exportieren Delete delete profile button - + Löschen This is useful to remain safe on public computers delete profile button tooltip - + Dies ist nützlich, um die Sicherheit an öffentlichen Computern zu erhöhen. Import a profile import profile button - + Profil importieren @@ -687,7 +687,7 @@ Do you want to erase this profile ? qTox - + qTox @@ -702,22 +702,22 @@ Do you want to erase this profile ? Add friends - + Freunde hinzufügen Create a group chat - + Gruppenchat eröffnen View completed file transfers - + Abgeschlossene Dateiübertragungen anzeigen Change your settings - + Einstellungen bearbeiten @@ -735,7 +735,7 @@ Do you want to erase this profile ? Privacy settings - + Privatsphäre-Einstellungen @@ -822,24 +822,24 @@ Do you want to erase this profile ? Please choose which identity to use - + Bitte wähle, welche Identität du benutzen möchtest Choose a profile picture - + Wähle ein Profilbild Error - + Fehler Unable to open this file - + Diese Datei kann nicht geöffnet werden. @@ -849,18 +849,18 @@ Do you want to erase this profile ? This image is too big - + Das Bild ist zu groß. Toxcore failed to start, the application will terminate after you close this message. - + Tox konnte nicht gestartet werden. Die Anwendung wird geschlossen, sobald du dieses Fenster schließt. toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text - + Tox konnte mit deinen Proxy-Einstellungen nicht gestartet werden. Bitte bearbeite deine Einstellungen und starte Tox anschließend neu. @@ -872,19 +872,19 @@ Do you want to erase this profile ? busy contact status - + beschäftigt offline contact status - + offline online contact status - + online From cb38e82324256574c17f25f34cebc23c6f08fb79 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Mon, 17 Nov 2014 20:26:19 +0100 Subject: [PATCH 122/253] Vol button: Less blury, use Tox Red when pressed Helps with #554 and #795 --- ui/volButton/volButton.png | Bin 582 -> 385 bytes ui/volButton/volButtonHover.png | Bin 621 -> 365 bytes ui/volButton/volButtonPressed.png | Bin 621 -> 444 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/ui/volButton/volButton.png b/ui/volButton/volButton.png index d50d8603c74fe9d76768448a4a185d5b12bdec02..29c3045805d0b30ea554f74414930af231240b16 100644 GIT binary patch delta 311 zcmV-70m%Nw1c3vPQ-2W?6cr|h_=5ld0S-w-K~y-)t(37&0znXlzn#5=z}Z+M;Q@#y zR=$T)8>KT8cGfgBCKgn78hdyNU%|HVv^wwNLxd|fro2y` zbHD4aWflgWW%J6%v&QoAy$A!}zuu}&E<4v8?;mX`-${)h-$u-`xmSQ_!=) zPQ8kAZMgWXRxp$+7hoLP6#!zy=WHxM?+0Fl)VXklHZ~_fZw0S|LN8eJmFxfj002ov JPDHLkV1f%?j#2;s delta 509 zcmV2k_8tHL0n z;qc6P&->2E(^u;a;5o1cIR1Za0*@nL0Q62nP!vIqAKwQGcYhkFs*sFLs+^fJ&3}nIG)xeZI57>3X3a7fQ4*Po7O{)L0M9fp7>3wcwlAE~u^h^_GJGIi zQPO;9JrW!2#j~4klji#qB2@!P++?xdMp0A)`{N&o(SQkVL=6V-o>6FVR0Ktt(`hc# z>ptYh!YzUZcGDeGnX~5`@Xiyv81Foh^LX)8qZ)g^K6de9YrszWk#QKaJ^X+df3l7P z0YL~FrVNUGh415Uy#D->cG4j$M^t^y1eeGEi=$98ViNQI=N50jzM zYZ(|A;#e3M80JySPzDAD1_@rN;1{2t{b6iAU-h4X27rkY&4CIrIOSN#H8fYNoPmdh zo4PIzREU8aip$k3_%zFN%VL$|WaeaGU|?YQ`~NTLK_SmA%YVSm%#P$W?4Fb1k|sTj znTQA}T;a^bKqG+H#f*%MSjGSUMBQwL3Z%;`xc>RMVIDO(N z7s_BLsE6Wms)X_ON2?hgetnGB0c1zs(mS&lZhX2$o3a95a5263{A?w)liG{V&sH)q pFfb&&`21`Q6&wv+%fP^p1OO>7Mo+0#69xbP002ovPDHLkV1lqXg@phB delta 549 zcmV+=0^0rU0__BlQ-1;s0Vj+yQ!4-f0s2WqK~y-)m6Sbi6G0G$pV`~99mkffd?yMa zk~^eGG&GbHX`n&~DH0`r0w^#Q4Gn*Q4k~^E9TGJKNC+W{C@2NQ4%o5p?A;xT1QX}Z zV2^agX=b0DedpE4{Wp8mOZCUr#7)kHqWIvjCT803>PcL`DSyZlTPxNwPyj*x3sn?B zM*opSRN`vWtH;DqvB`{~C`+Yf=Atr+W^zVM({Ld%4R+vDcDHVFV{;c5hPhQ{)^1&J zER|N+t~R)~dYx)qLp4kX7KFqnB)0*fVx|KZqarUqzsUI0t7E0S&VQ}#+w3e}C1~JV_W(037{Hor zDyBdyIpXz~SJaDj>V*XkdozH8hTP&m7xrA2cZY9TE3Y#QjzUz(E-)WF*4VLSG6_1` zV#c!H`O2sEN8%{XSTY&*F~>>k*)R-pCp#$wp`Etr``*|&;S~D4_v4}YmHvFx>bIUd nbu+KdiyqAc0s}vIAn4QI_;%0uc^?5F00000NkvXXu0mjfF2wy@ diff --git a/ui/volButton/volButtonPressed.png b/ui/volButton/volButtonPressed.png index 60cdde9598fdc26c076a0be5f4e3c6d990848b54..e1cc44c793754d78949790fefe6219feeb747fc9 100644 GIT binary patch delta 370 zcmV-&0ge9c1iS-~Q-2W?3JQPE%BcVV0ZB4!dO`Uheb)XZE9<36$v-Bk|^1BW~3p8H@3 z06f=a6on~(cb%ZwlBe5O*FExf-7_e3Q33=7hD`D+O9xcOEq@6HYYGVofEk!`Y)ObR zPFZV!G@66{C!r9C0sy@!7$^mat>F-c?PNxja@-vB1JS_s0*t)JcD<4Aw_2IEL}ez= z6t?RP$ch!6SZZ8tb*-3io>)vEqt62Wgj~l&l)i2puKsNLQcQR{x&L-}BqaQ&$6~_c z;$6Hyd~pL6fcG@o`BqNt_V z5+kTsMCwjlh`&S;btMX28PQ@_U5HEnhAR>M1#T*I)hY(M=t89vP1B5-xyMD&n#@#_ z9=Mxx?>y)0JtMDfTnCZIz*8nLF$VQM>v#5%rmQ%uY2tMvD1R`3s)ER{KS`ejwaOQR z*X6@NYx8piVTh_`?)%-|OT!^7>Fsgh_8rdMyoHbBY~VCyVsP5)5uRyqq+aL5)oWB{ zX3#XvyvqlRknHZ#?{tPv97Rkw8Z10`NTFH-D;&MJfKQSP!sTYw?Q*HvBq)~lhhv1a zyT^3BPHk=;P=AcI)aFj3>c<27B;okt6^davGBgk?fc~khP^}KsF%B3QWHBNovDtUV z6;Q8Z_Sgv8_v1-VKyv&CW-KTod2w}LyQ=h~cBZle!6JreV}rQ8GrCm-VMyiVsX+km ziK89Fn(@G{T;~1qlT0FMFD7i9<@~+79J{=TCZ6`@CVyh>m^}uJF^CKwS1!~3^pUmI zRepSK;rlV)*4HsMo1gnjJeeiQGdnd!cYB+yH?OJA&eHF6aOHAFa!#zxUCDo$2?D-; zSYzwmTWqnIt79X^-p{-E-@o{Kt3@0|$Y^D%*n)j-e3HJfL19UZ!4w>fPWbnSB17#_ n?=j8|gC0e2rP7N3>6d;1GWo{gkC_xe00000NkvXXu0mjfJxCCp From d373e45fd1e44c3bb4b6e7990ec94358f2197102 Mon Sep 17 00:00:00 2001 From: Olexandr Nesterenko Date: Mon, 17 Nov 2014 21:55:41 +0200 Subject: [PATCH 123/253] Update Ukrainian translation --- translations/uk.ts | 868 +++++++++++++++++++++++++++++++-------------- 1 file changed, 602 insertions(+), 266 deletions(-) diff --git a/translations/uk.ts b/translations/uk.ts index 568deb233..a2ecf9015 100644 --- a/translations/uk.ts +++ b/translations/uk.ts @@ -4,18 +4,13 @@ AVForm - + Audio/Video Аудіо/Відео AVSettings - - - Form - Form - Playback @@ -103,67 +98,134 @@ Може поспілкуємось? - + Please fill in a valid Tox ID Tox ID of the friend you're sending a friend request to - Заповніть коректним Tox ID + Введіть коректний Tox ID - + You can't add yourself as a friend! When trying to add your own Tox ID as friend Ви не можете додати самого себе до друзів! - - This address does not exist - The DNS gives the Tox ID associated to toxme.se addresses - Цієї адреси не існує + + qTox needs to use the Tox DNS, but can't do it through a proxy. +Ignore the proxy and connect to the Internet directly ? + qTox потребує Tox DNS, але не може скористатися ним через проксі. +Проігнорувати проксі та під'єднатися напряму? - - Error while looking up DNS - The DNS gives the Tox ID associated to toxme.se addresses - Помилка під час перегляду DNS + + This Tox ID does not exist + DNS error + Даного Tox ID не існує + + + + AdvancedForm + + + Advanced + Додатково - - Unexpected number of text records - Error with the DNS - Неочікуване число текстових записів + + FULL - very safe, slowest (recommended) + ПОВНА - найбезпечніше, але повільніше (рекомендується) - - Unexpected number of values in text record - Error with the DNS - Неочікуване число значень в текстових записах + + NORMAL - almost as safe as FULL, about 20% faster than FULL + ЗВИЧАЙНА - менш безпечно, а ніж ПОВНА, але на 20% швидше - - The DNS lookup does not contain any Tox ID - Error with the DNS - Відповідь DNS не містить жодного Tox ID + + OFF - disables all safety, when something goes wrong your history may be lost, fastest (not recommended) + ВІДСУТНЯ - вимкнені всі перевірки, якщо щось піде не так Вашу історію буде втрачено, найшвидша робота (не рекомендується) + + + + AdvancedSettings + + + Form + Form - - - The DNS lookup does not contain a valid Tox ID - Error with the DNS - Відповідь DNS не містить жодного коректного Tox ID + + <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">IMPORTANT NOTE</span></p><p><span style=" color:#ff0000;">Unless you </span><span style=" font-weight:600; color:#ff0000;">really</span><span style=" color:#ff0000;"> know what you are doing, please do </span><span style=" font-weight:600; color:#ff0000;">not</span><span style=" color:#ff0000;"> change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history.</span></p></body></html> + <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">ВАЖЛИВЕ ЗАУВАЖЕННЯ</span></p><p><span style=" color:#ff0000;">Доки ви </span><span style=" font-weight:600; color:#ff0000;">дійсно</span><span style=" color:#ff0000;"> не зрозумієте, що робите, будь ласка, </span><span style=" font-weight:600; color:#ff0000;">не</span><span style=" color:#ff0000;"> змінюйте жодні параметри тут. Внесені зміни можуть викликати проблеми із qTox, і призвести до втрати Ваших даних, наприклад історії.</span></p></body></html> + + + + Reset to default settings + Скинути на типові значення + + + + History + Історія + + + + <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_synchronous"><span style=" text-decoration: underline; color:#0000ff;">Synchronous writing to DB</span></a></p></body></html> + <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_synchronous"><span style=" text-decoration: underline; color:#0000ff;">Синхронізація запису в БД</span></a></p></body></html> ChatForm - + Load History... Завантажити історію… - + Send a file Надіслати файл + + + Bad Idea + Погана ідея + + + + You're trying to send a special (sequential) file, that's not going to work! + Ви намагаєтесь передати спеціальний (послідовний) файл, це не так працює! + + + + %1 calling + %1 викликає + + + + %1 stopped calling + %1 припинив виклик + + + + Calling to %1 + Викликаємо %1 + + + + Call with %1 ended. %2 + Виклик із %1 завершено. %2 + + + + Call duration: + Тривалість дзвінка: + + + + Call rejected + Дзвінок відхилено + ChatTextEdit @@ -176,99 +238,104 @@ Core - + Toxing on qTox Вітання з qTox - + qTox User Користувач qTox - + + Friend is already added + Друга вже додано + + + Encryption error Помилка шифрування - + The .tox file is encrypted, but encryption was not checked, continuing regardless. Файл .tox зашифровано, але шифрування не перевірено, попри це продовжуємо. - + Tox datafile decryption password Розшифрування файлу даних Tox - - - + + + Password error Помилка паролю - - + + Failed to setup password. Empty password. Не вдалось встановити пароль. Пароль пустий. - + Try Again Спробуйте ще раз - + Change profile Змінити профіль - + Reinit current profile Пере ініціалізувати поточний профіль - + Wrong password has been entered Введено хибний пароль - + History Log decryption password Розшифрування історії - + Your history is encrypted with different password Do you want to try another password? Ваша історія зашифрована інакшим паролем Бажаєте спробувати інший пароль? - + Encrypted log Зашифрований звіт - + Loggin Звітування - + Due to incorret password logging will be disabled Введено некоректний пароль, звітування вимкнено - + NO Password Відсутній пароль - + Will be saved without encryption! Буде збережено без шифрування! @@ -276,23 +343,28 @@ Do you want to try another password? FileTransferInstance - + Save a file Title of the file saving dialog Зберегти файл - + Location not writable Title of permissions popup Немає прав на запис - + You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Ви не маєте прав на запис за цим розташуванням. Оберіть інше місце призначення, або скасуйте передачу. + + + ETA + РЧЗ + FilesForm @@ -352,50 +424,46 @@ Do you want to try another password? FriendWidget - + Invite to group Menu to invite a friend to a groupchat Запросити до групи - + Copy friend ID Menu to copy the Tox ID of that friend Копіювати дружній ID - + + Set alias... + Встановити псевдонім… + + + Auto accept files from this friend context menu entry Автоматично приймати файли від даного друга - - Manually accept files from this friend - context menu entry - Вручну підтверджувати отримання файли від даного друга + + User alias + Псевдонім користувача - - Auto accept files from all friends - context menu entry - Автоматично приймати файли від всіх друзів + + Alias: + Псевдонім: - - Disable global auto accept - context menu entry - Вимкнути автоматичне прийняття файлів - - - - + Choose an auto accept directory popup title Оберіть теку, для автоматичного отримання файлів - + Remove friend Menu to remove the friend from our friendlist Вилучити з друзів @@ -404,18 +472,30 @@ Do you want to try another password? GeneralForm - + General Основні - + + + None + Відсутній + + + + Choose an auto accept directory + popup title + Оберіть теку, для автоматичного отримання файлів + + + Call active popup title Дзвінок активний - + You can't disconnect while a call is active! popup text Ви не можете від'єднатись під час активного дзвінка! @@ -424,215 +504,255 @@ Do you want to try another password? GeneralSettings - - Form - Form - - - + General Settings Основні параметри - - + + The translation may not load until qTox restarts. Переклад буде застосовано після перезавантаження qTox. - - Translation: - Переклад: - - - + Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox Зберігати налаштування в робочий теці - + Make Tox portable Портативний запуск - + + Show system tray icon + Показувати піктрограму в системному лотку + + + Start in tray Запускати у системному лотку - + Close to tray Закривати до лотку - + Minimize to tray Мінімізувати до лотку - + Show contacts' status changes Показувати зміну статусів контактів - + Provided in minutes Встановлено в хвилинах - - Auto away after (0 to disable): - Авто-статус «Відійшов» (0 - вимкнено): - - - + Set to 0 to disable Встановіть 0, аби вимкнути - + minutes хвилин - + Theme Графічна тема - - Smiley Pack: - Text on smiley pack label - Графічний пакунок емоцій: + + Translation + Мова інтерфейсу - - :) - :) + Show system tray + Показувати в системному лотку - - ;) - ;) + + Check for updates on startup (unstable) + Перевіряти оновлення під час запуску (нестабільна функція) + + + + Focus qTox when a message is received + Перехоплювати фокус вікна при отриманні повідомлення + + + + Faux offline messaging + Фальшивий поза мережевий обмін повідомленнями + + + + Auto away after (0 to disable) + Авто-статус «Відійшов» (0=вимкнено) - :p - :p + You can set this on a per-friend basis by right clicking them. + autoaccept cb tooltip + - - :O - :O + + Autoaccept files + Автоматично приймати файли - - :'( - :'( + + Save files in + Зберігати файли до - - Style: - Стиль: + + PushButton + Тисніть кнопку - + + Use emoticons + Використовувати смайлики + + + + Smiley Pack + Text on smiley pack label + Пакунок смайликів + + + + Style + Стиль + + + + Theme color + Колір графічної теми + + + + Emoticon size + Розмір смайликів + + + + px + px + + + + Timestamp format + Формати часового відбитку + + + Connection Settings Параметри підключення - + Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Дозволити IPv6 (рекомендовано) - - This allows, e.g., toxing over Tor. It adds load to the Tox network however, so use only when necessary. + + Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip - Це дозволяє використовувати tox, наприклад, поверх протоколу Tor. Але це збільшує навантаження на мережу, тому використовуйте лише в разі необхідності. + - - Disable UDP (not recommended) + + Enable UDP (recommended) Text on checkbox to disable UDP - Вимкнути UDP (не рекомендовано) + Дозволити UDP (рекомендовано) - + Use proxy (SOCKS5) Використовувати проксі (SOCKS5) - + Address Text on proxy addr label Адреса - + Port Text on proxy port label Порт - + Reconnect reconnect button - Перепід'єднатись + Повторно під'єднатись GenericChatForm - + Send message Відправити повідомлення - + Smileys Смайлики - + Send file(s) Відправити файл(и) - - - Audio call - Аудіо дзвінок - - - - Video call - Відео дзвінок - - Toggle speakers volume - Перемкнути стан гучності відтворення + Audio call: RED means you're on a call + Аудіо дзвінок: Червоний - дзвінок активний - Toggle microphone - Перемкнути стан мікрофону + Video call: RED means you're on a call + Відео дзвінок: Червоний - дзвінок активний - - + + Toggle speakers volume: RED is OFF + Перемкнути стан гучності відтворення: Червоний - вимкнено + + + + Toggle microphone: RED is OFF + Перемкнути рівень підсилення мікрофону: Червоний - вимкнено + + + + Save chat log Зберегти чат - + Clear displayed messages Очистити показані повідомлення - + Cleared Очищено @@ -640,13 +760,13 @@ Do you want to try another password? GroupChatForm - + %1 users in chat Number of users in chat Користувачів у чаті: %1 - + %1 users in chat Користувачів у чаті: %1 @@ -680,103 +800,123 @@ Do you want to try another password? Ідентифікація - + Call active popup title Дзвінок активний - + You can't switch profiles while a call is active! popup text Ви не можете перемикати профіль під час активного дзвінка! - + Rename "%1" renaming a profile Перейменувати «%1» - + Profile already exists rename confirm title Профіль вже існує - + A profile named "%1" already exists. Do you want to erase it? rename confirm text Профіль із назвою «%1» вже існує. Бажаєте стерти його? - + Export profile save dialog title Експорт профілю - + Tox save file (*.tox) save dialog filter Файл збереження Tox (*.tox) - + + Failed to remove file + Не вдалось вилучити файл + + + + The file you chose to overwrite could not be removed first. + Неможливо вилучити файл, який ви обрали для перезапису. + + + + Failed to copy file + На вдалось скопіювати файл + + + + The file you chose could not be written to. + Неможливо записати в файл, який ви обрали. + + + Profile currently loaded current profile deletion warning title Поточний профіль завантажено - + This profile is currently in use. Please load a different profile before deleting this one. current profile deletion warning text Даний профіль зараз використовується. Завантажте інший, парад вилученням поточного профілю. - + Deletion imminent! deletion confirmation title Небезпечне вилучення! - + Are you sure you want to delete this profile? deletion confirmation text Дійсно вилучити даний профіль? - + Import profile import dialog title Імпортувати профіль - + Tox save file (*.tox) import dialog filter Файл збереження Tox (*.tox) - + Ignoring non-Tox file popup title Ігнорування не Tox файлу - + Warning: you've chosen a file that is not a Tox save file; ignoring. popup text Увага: вказаний вами файл не є файлом збереження Tox; ігнорую. - + Profile already exists import confirm title Профіль вже існує - + A profile named "%1" already exists. Do you want to erase it? import confirm text Профіль із назвою «%1» вже існує. Бажаєте стерти його? @@ -785,90 +925,85 @@ Do you want to try another password? IdentitySettings - - Form - Form - - - + Public Information Публічна інформація - + Name Ім'я - + Status Статус - + Tox ID Tox ID - + Your Tox ID (click to copy) Ваш Tox ID (клацніть аби скопіювати) - + Profiles Профілі - + Available profiles: Доступні профілі: - + Load load profile button Завантажити - - + + Switching profiles is disabled during calls tooltip Перемикання профілю заблоковано під час дзвінків - + Rename rename profile button Перейменувати - + Export export profile button Експорт - + Delete delete profile button Вилучити - + This is useful to remain safe on public computers delete profile button tooltip Це стає в нагоді, при користуванні публічними комп'ютерами - + Import a profile import profile button Імпортувати профіль - + New Tox ID new profile button Новий Tox ID @@ -902,11 +1037,6 @@ Do you want to try another password? MainWindow - - - qTox - qTox - Your name @@ -918,34 +1048,37 @@ Do you want to try another password? Ваш статус - + Add friends Додати друзів - + Create a group chat Створити груповий чат - + View completed file transfers Переглянути завершені передачі файлів - + Change your settings Змінити параметри - + Close Закрити + + + NetCamView - - Ctrl+Q - Ctrl+Q + + Tox video + Tox Відео @@ -956,12 +1089,12 @@ Do you want to try another password? Приватність - + Encrypted log Зашифрований звіт - + You already have history log file encrypted with different password Do you want to delete old history file? Ви вже маєте зашифрований файл історії іншим паролем @@ -971,43 +1104,134 @@ Do you want to delete old history file? PrivacySettings - - Form - Form - - - + Typing Notification Увімкнути сповіщення про набір - + Keep History (unstable) Лишати історію (нестабільна функція) - + Encryption Шифрування - + Encrypt Tox datafile Шифрувати файл даних Tox - + Encrypt History Шифрувати історію + + + Nospam + Nospam + + + + HHHHHHHH + HHHHHHHH + + + + Generate random nospam + Генерувати випадковий «nospam» + + + + QObject + + + Tox me maybe? + Default message in Tox URI friend requests. Write something appropriate! + Може поспілкуємось? + + + + Ignoring non-Tox file + popup title + Ігнорування не Tox файлу + + + + Warning: you've chosen a file that is not a Tox save file; ignoring. + popup text + Увага: Вказаний Вами файл не є файлом збереження Tox; він буде проігнорований. + + + + Profile already exists + import confirm title + Профіль вже існує + + + + A profile named "%1" already exists. Do you want to erase it? + import confirm text + Профіль із назвою «%1» вже існує. Бажаєте перезаписати його? + + + + Profile imported + Профіль імпортовано + + + + %1.tox was successfully imported + %1.tox успішно імпортовано + + + + Update + The title of a message box + Оновити + + + + An update is available, do you want to download it now ? +It will be installed when qTox restarts. + Доступне оновлення, бажаєте завантажити його зараз? +Оновлення буде встановлено після перезапуску qTox. + + + + Tox URI to parse + Tox URI для розбору + + + + Default + Типовий + + + + Blue + Синій + + + + Olive + Оливковий + + + + Red + Червоний + + + + Violet + Фіолетовий + SetPasswordDialog - - - Dialog - Діалог - Type Password @@ -1019,138 +1243,250 @@ Do you want to delete old history file? Повторіть пароль + + ToxDNS + + + The connection timed out + The DNS gives the Tox ID associated to toxme.se addresses + Час очікування з'єднання вийшов + + + + This address does not exist + The DNS gives the Tox ID associated to toxme.se addresses + Даної адреси не існує + + + + Error while looking up DNS + The DNS gives the Tox ID associated to toxme.se addresses + Помилка під час перегляду DNS + + + + No text record found + Error with the DNS + Не виявлено текстового запису + + + + Unexpected number of values in text record + Error with the DNS + Неочікуване число значень в текстових записах + + + + The version of Tox DNS used by this server is not supported + Error with the DNS + Версія Tox DNS даного серверу не підтримується + + + + The DNS lookup does not contain any Tox ID + Error with the DNS + Відповідь DNS не містить жодного Tox ID + + + + + The DNS lookup does not contain a valid Tox ID + Error with the DNS + Відповідь DNS не містить жодного коректного Tox ID + + + + ToxURIDialog + + + Add a friend + Title of the window to add a friend through Tox URI + Додати друга + + + + Do you want to add %1 as a friend ? + Бажаєте додати %1 як друга? + + + + User ID: + ID користувача: + + + + Friend request message: + Повідомлення запиту: + + + + Send + Send a friend request + Надіслати + + + + Cancel + Don't send a friend request + Скасувати + + Widget - - online + + Online В мережі - - away + + Away Відійшов - - busy + + Busy Зайнятий - + &Quit &Вийти - + Change status to: Змінити статус на: - + Online Button to set your status to 'Online' В мережі - + Away Button to set your status to 'Away' Відійшов - + Busy Button to set your status to 'Busy' Зайнятий - + Choose a profile Оберіть профіль - + Please choose which identity to use Оберіть ідентифікатор для використання - + Choose a profile picture Оберіть зображення для профілю - - - + + + Error Помилка - + Unable to open this file Неможливо відкрити цей файл - + Unable to read this image Неможливо прочитати це зображення - + This image is too big Зображення завелике - + Toxcore failed to start, the application will terminate after you close this message. Помилка запуску ядра tox, програма буде завершена після закриття цього повідомлення. - + toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text Помилка запуску ядра tox із поточними параметрами проксі. qTox не працює; змініть параметри і перезапустіть. - + + Add friend + Додати друга + + + + File transfers + Передачі файлів + + + + Settings + Параметри + + + + Couldn't request friendship + Не вдалось надіслати запит на дружбу + + + away contact status Відійшов - + busy contact status Зайнятий - + offline contact status Поза мережею - + online contact status В мережі - + %1 is now %2 e.g. "Dubslow is now online" %1 тепер вже відомий як %2 - + <Unknown> Placeholder when we don't know someone's name in a group chat <Невідомо> - - + + %1 has set the title to %2 + %1 встановив тему %2 + + + Message failed to send Не вдалось відправити повідомлення From 29a2d30458c06feeb3f152509a6935eb9dd86512 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Mon, 17 Nov 2014 21:00:19 +0100 Subject: [PATCH 124/253] Mic button: Less blury, use Tox Red when pressed Helps with #554 and #795 --- ui/micButton/micButton.png | Bin 634 -> 389 bytes ui/micButton/micButtonDisabled.png | Bin 523 -> 461 bytes ui/micButton/micButtonHover.png | Bin 534 -> 327 bytes ui/micButton/micButtonPressed.png | Bin 651 -> 379 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/ui/micButton/micButton.png b/ui/micButton/micButton.png index 7f0cf0c4d24eee0fe208f24843f10fa36fe4a157..faefbb7285796676c4a3b2eec28c42b601713f38 100644 GIT binary patch delta 316 zcmV-C0mJ_K1cd{TQhy5(6EX&2<~n%*009z7L_t(I%e9m}PQySDg})iOh|)mANsy>$ zprptdf+!<#7^+Z!#1+!eM^urZ=L|`aDx&hE!7H2{k>lm8{_x&DuFlWW3%efTcG&m^AUjavx`L3{QD~tn=Pm6 zD%OA{)Ka|L-!&IPNHvy?Z@e#p*g3EOws|jG0%&}0tmpyV0t@@EMgI)|k!j~o!$PRQc`00IX|L_t(I%cYdhZW2)x#((GDI|G80Q4wt# zjV5Yrq9*zRM%}sSGw9kE>dJ>`VqA1%dG0Vj^N$}R4$+@oYm7-JatK3|W%aM0Z+Xh0+fPk;?K4?55R_0mnAufAga z$|J0aFbILbjnW$1jSn20>=T(7lZThgETaVQAti|u9wh5rE!L>URVwic)i~jP^&v?i z!K=><(;UO(p??AhDqzLoBpUjyis}#$NraUMB!lL;n0f7$tbWuo9a9!YPkJt7Y4CV9 z{{2ob0G5eu;hK?*1z??2GM?FyIS82%7X%v&Moi9QBLX6zI`>R&o)031po-U!!Ok_P z22?$&lRsPt)(qmr(LQUjbNrpMD`U;D)N9Ia=Lbjqc7JxQSypkDRTJ^M-{Mnqo89AY z#I^tkX*grAvqP)@i^#-?nB}cd6cIsHIXr37>^4X>1pzA-BNkPiuxA#yH$5*DMKw@% zm$_ZJ!}VgFf-Qo;ssF>z-XV?d9^FA_9=vCOEufvd)*^U3s!ILRDo<+9czo?CwPKy1 z0Uy$tF)YhiTe4`-kGk!|VT~O&&wEnRT(+kN{a?Z@(eYxUz#;$u002ovPDHLkV1hdU B08an_ diff --git a/ui/micButton/micButtonDisabled.png b/ui/micButton/micButtonDisabled.png index 9cb38c9be85cc49d09297613f2420c237dd31bf4..9f2af8c1136fc6d2e7803860c3ae9981f0771ffe 100644 GIT binary patch delta 388 zcmV-~0ek+71kD4GQhy5(6Ey|5uKff600CM_L_t(I%bk?HPQx%1h0nb)ZK_suU}8pM z^uXj>jk7DDw_WIGNg^v(8LawqR2`7`P_3)Oua$x zW~kv5T*c=~;QtISa8cyS!6e>R?3?EkJ8vths3svWq*`Nq6XH~?wa%3#DQTI)=$)*6$^#A(gJFD(El2Tqa%qwy$xyR%Ixg?hbSnr|g+vy&`; zL=q9Ycip24<6&HG#r*8!&2n)=;$aM9Og1;hIH9X>`AH9t_vhPS``VhfFAL+t1aMlB i*zzndn+sh_!s}nb0000MfVO!ZoLpE; z=;k-jiNV?UkxX2TyMu$HAAk)_vh{^DM$(4yV;ec-4S7bZy&zzPkq0+ zy+Nf?S=bF(23ZEDNjHY7>g4fDeyZf0WiHlQtQwiXbA$RX@LwVmILUI$!K9g`7!NPC z5Yp^YtWq^8fq$W^4JNBYQY&t?S|B1YGng47g7$Me6)dxh*#bkFvh?bgZ6V9jXXvJVHhStgTnBYPR@_FN6x6~ t^)^e1_F)1z?nv!&FAv?%R_?2C@C~Spud0|mBLe^c002ovPDHLkV1jS5)o}m- diff --git a/ui/micButton/micButtonHover.png b/ui/micButton/micButtonHover.png index 462337253b66b66b11d62091acadf2c8ca1d6748..069f74d60e9f2c405c4ca6b1c757cdc654abbe63 100644 GIT binary patch delta 253 zcmVbm&s>r-&UVffseQh2 delta 462 zcmV;<0Wto^0+s}jQhx{&5(^XV)Qdy_00E;(L_t(I%k7l0Yg17a#((GB`)*RGDW<`> z6wyVn5a{6O;Nm1Mot(v0|D#SF9K@lEl?)cT2r6_aqOHLwv_;WWOJ4f!afk`8NnR7; z>bYFF_uRwxedk>Grr@P=(BlB|*| zlpH}1sdpQY$|xv+6Gur)x1+^|4_aCsE{GOEKvCwCOI$8hQE`AFq8y(da?)*&1y@lN zz3PV_1Q5}I^1Dm-SgF;|)x14^&Bpun^IZ)SjHYrU5n-iP$IOTp2m5BNUO0{mfJfsZ zW&Z>`OZ*3(9e;S*kJM=kt-s8sR~(7s!OZyd^CK#X6NeLrS5LFOKas!-GS*P~DxV!Z z<=Vn^ye1fi7tijucN0hAC$<0K#~yp%cmFyX7!Wj2_H%`T0DO@J8b&2q;QsO&^GStn z>;e#6;OO*_tG)oJ+ zOk-z9o@_t5Viva&4L6F|MfyL{^XX`@P-z;|H!6DP50DV60ddNU^#A|>07*qoM6N<$ Ef(+i$;c!y>W_W?*1oVEFqN!T$fBC@sVp%zwy8ayTsh0^Z}_!OK93}pCu^(vhI^XgTC zhW-%v_K5M4rz^eIFE+*#fBy6FQX>ey3V**Z`0lq00Pk{Rqb9fj^BUHSOz@tvzm;oa@_U|XCbWQPI*H~}?+I7Ku#lYgin9PQn;cC|ZsVlK9B zyxq7MZzoO@sERlT&LO_TjD1WmMEQ^VLhlDJnBiAiPX4%o_@bP--?& zK%EPwDy2pP6@STq#)nB`X`Xw&pU^yNc^)W$Rc7wyf)X&#?uGT5G*QESJ3~#moM(GZ z1T(&bo!CSUHI++^wVz@wu|-@GNlOv2&W&>fstZI-o9BBn)pn5?Q&5fNavl-Dda=)Q z%KCqRmob(lb;FYr#2CcdU=|7tTdk$QHbX2jLjZMj-+#Pj_5K57WhH2;P+)L;49+bC zmeYD&k!*BS0NP@DHbv1a(=!e#(&p6O27S?fzgdlF`JuIpTEG{sG_5B zuwN`P>HMI#zYl}Ke2vfUt$p&XB!47f#N6d2zYY! Date: Mon, 17 Nov 2014 21:06:04 +0100 Subject: [PATCH 125/253] Call buttons: Use tox red --- ui/callButton/callButtonRed.png | Bin 884 -> 959 bytes ui/videoButton/videoButtonRed.png | Bin 571 -> 651 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/ui/callButton/callButtonRed.png b/ui/callButton/callButtonRed.png index a0db8c3fac8e96e793310acede1480f29d4a6465..9f306f16be835601f6f9244bef806578a210f4ff 100644 GIT binary patch delta 931 zcmV;U16=&{2EPZ8Du3nxWdLO@6BQf)000SaNLh0L01m_e01m_fl`9S#00007bV*G` z2i^-26az4APQKRw00T@(L_t(o!|j>hOH*MO$3N%jT<2`Z%!oocXFBLYgD66WSlx6J zlHIkNNV@5wuJx~||G+K+@n#4SLW!bBIn4x z(f9IwUpVK(^Xq-yM`Di_`uh59+YZJptAi?*p=l9hIn*>X^k8RA0{C#}j_;dkhT@jh zi9j+mEsUxLn+6A$WdIQ&0Ai-uiDiWVq&Izi{hJ#b_Yqje(W3)$*0zI)u530aS9@Up zp^J6M{3H4gQh#l#J0FX;0NQ{k_A5nw}N|#Mjb-x2LD5UgQ!9;!mHl9F0~#h|e(4dV5glFxm-Ut9Ea4*4A%HKv6Yp-53&QG zbaip$+CnpGA^GF$?YyY=s4I*OKd!PbYO_GL4VwBZNI-u={k+~?jd`<1=iZlPFq`~ zrlv5TJgEpo*&w-Ovg8jI^cyAIXr~aypFP8R_3A(|Q4x^z{CqyR_an!_jz%#bKj!_! zM0ND+*61j8em`=3{hu*>{D?I=N@{wV=(~5-^Y~ydF0vU4?Pj8any{xzJ72z#j6_J! z&VQ2m{P~ylr4o`}U%#F!yVGlHqt5ToTc@!R$?Ycd=@Xf+U$K{#a8_6M{Tgy8&q2|- zIjB(95waio5Ar(@L(?LnLl~MCk&)$)_~wv9`5EHPo0Boq>=e`*;ilo?Kt4uPHE3vB zSVRa^H3-{N-5q_nb*rCjHW)L_>K{+5Y8y{D4^000SaNLh0L01m_e01m_fl`9S#00007bV*G` z2i^u81T7QN@_Cg200RL@L_t(o!`+!nOB+!bhM$?3iDMG6iYO5qbSHucYJ+&$xDnB% zuHAG|*Y#Jl{~%I??t&nKh#*D~H(n4?1QiVyr6}6ersgu^IDhFP6Kk5Zc%#Y5yF6!L z&cpd`=ldk?>Fe9K?|~7Z87QN0wGIS4e*eexf@A@BfdKFZv9tz^c>Mk^vb7*01lE}Y z0Fq@+9}rvPvgH$^YecpjTXZ$c1sS$Fyv2H>E8tS?iLjQr6uq4v3?<8Cy+F&OLiW zgBT!kc{yrVm-tc3?)6eqTPr?DQq#aN#0Sw24~Z@<6+uEYO`?m7B-Yk)gYNB#6Sb0> zhPJ<-Wn#&AocQXhI3RLGMJivEWEK}UCnqGfw!{FDRDTuMz(7{MNJgVv&(4yJ#|s}s zayW1g4WascS$o&>^XSLNc`O~(dhJ|C*j zmr=o1jDL=DH8FvHaPYgzZEM3dFo3PPIy=-J9Ps1wXUuT;|G!6Gy?cl9`Zbc%nX`^^ z4-a$o=@a(4I?8%`v3tEp4oBvPhJhXmaWy&lY=|d=IGURNT}##1Q#m$<>~dw?)?IYE zxgoK+$Jk5tD)4A^_Qyu_iu(E0CTct|I2>4PedV_xsY$K0<1+C>(@! h_S952Es@95zW^s=6NurWGB5xD002ovPDHLkV1oN&i-`aL diff --git a/ui/videoButton/videoButtonRed.png b/ui/videoButton/videoButtonRed.png index f8bd07b378d6e5904d7bca8178d13cd309c5ffc6..0599d252f40031739b593a5c01f29336372ef646 100644 GIT binary patch delta 620 zcmV-y0+ao_1d9cbDu3nxWdLO@6BQf)000SaNLh0L01m_e01m_fl`9S#00007bV*G` z2i^-26a_B98zeFS00J0EL_t(o!|jS`{F5K}aMSSP&Zv z8y-Mpf{8W#2u$ra2peMY7YI`(5Y3+{LnJB`7b6)ah~i+Uj(_EJprA^imb4=Ep?k*D zt2_Plynjx5CH$hV`Fz3meZA|t8AMnrV>J+Br1$nd-W zlB%$x?;9X&;lc*Q=6H_ch@IoX& z1c(3;Abd%Fu78W)X&Tt-D&m<8AV2+?zGfi?eOqn zD#$WFHA0d}yjfm`GB*dMTt=^6$2ZgDx7g+JalBny!+-1fc}$f`@J~-vp^!xBC6$G};>0}QA_^lRhN+tNM7S1dS9~Kt)0tqfJ;r{2= z`%fTYrvvZk2<~IUkQb0%y^ij|0Rr28^m_sT1J8>-NMmQ`k1^j*PC&1(F_uaV-7x&W za9H}XvSl(5?N@*Z5CI}UMh>DfR^uH)WvoU(h{3-d#JJrduCG@t$H{W)Mm4>^pS!IQ zN_Ca7DvuDLR0n=;b?;IJ-_4E&~48pqW%~M?f0000y{D4^000SaNLh0L01m_e01m_fl`9S#00007bV*G` z2i^u81v(z60_%wY00GEJL_t(o!|j>9PQp+ahMyi=8*1cdGc1HfV;G17va)&sFc?Qk zOkDLU^bX1ua056wVGPO&2@Ax9un-8*R(=EriZPL>#E6{QcYi!-+Vk{$@AsYLRd}an zHhTtSfEZw~y1EDQVbi>vEhr8^1jqyX$fY|V6E@8U;Vj4qfwSj4fZ~+X1#;_LIG&JQ z8Q~aPcE!Yscc<^l5V%AhhzH_<{1XuM*Zz=Uu$fNdOC&)3{%3}b2Hjkamv;O2PehN! zP&DoHXX5dNA%6h0XcQxrT25&NkB{*m9ic@cAm+bfEf823;_XVV>&q!E#plD9Orr1X zVBg+i6^kEBK~?4PhzJJRI5HJg%7_R)wzy?@SAp}?SCXVUM>2Qq54{=D`6 z2xQ#rVc*=$ABM7kJeSKnUR_PSFdonMi*2tyNH>@J@|k|EhSBM;778ugFb37?;@75T zHaqd!uLt6Rcpy@NST5kO1W<7SNJZczK9Vax%MeS*8NC7Ag-tU#Gkb< Date: Mon, 17 Nov 2014 22:49:35 +0100 Subject: [PATCH 126/253] Widget: Don't delete icon, it's a child --- src/widget/widget.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index 6077b60ec..3c9c856a6 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -299,7 +299,6 @@ Widget::~Widget() delete g; GroupList::groupList.clear(); delete trayMenu; - delete icon; delete ui; delete translator; instance = nullptr; From 36d4460fef2732b9059d735d15f4dac546d300fa Mon Sep 17 00:00:00 2001 From: mannol Date: Mon, 17 Nov 2014 23:17:54 +0100 Subject: [PATCH 127/253] Core update --- src/core.cpp | 12 ++-- src/core.h | 6 +- src/coreav.cpp | 116 +++++++++++++++++------------------ src/video/netvideosource.cpp | 2 +- src/video/netvideosource.h | 2 +- 5 files changed, 67 insertions(+), 71 deletions(-) diff --git a/src/core.cpp b/src/core.cpp index 9d49c6f18..247ca4c88 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -251,14 +251,13 @@ void Core::start() toxav_register_callstate_callback(toxav, onAvReject, av_OnReject, this); toxav_register_callstate_callback(toxav, onAvEnd, av_OnEnd, this); toxav_register_callstate_callback(toxav, onAvRinging, av_OnRinging, this); - toxav_register_callstate_callback(toxav, onAvStarting, av_OnStarting, this); - toxav_register_callstate_callback(toxav, onAvEnding, av_OnEnding, this); - toxav_register_callstate_callback(toxav, onAvMediaChange, av_OnMediaChange, this); + toxav_register_callstate_callback(toxav, onAvMediaChange, av_OnPeerCSChange, this); + toxav_register_callstate_callback(toxav, onAvMediaChange, av_OnSelfCSChange, this); toxav_register_callstate_callback(toxav, onAvRequestTimeout, av_OnRequestTimeout, this); toxav_register_callstate_callback(toxav, onAvPeerTimeout, av_OnPeerTimeout, this); - toxav_register_audio_recv_callback(toxav, playCallAudio, this); - toxav_register_video_recv_callback(toxav, playCallVideo, this); + toxav_register_audio_callback(playCallAudio, this); + toxav_register_video_callback(playCallVideo, this); QPixmap pic = Settings::getInstance().getSavedAvatar(getSelfId().toString()); if (!pic.isNull() && !pic.size().isEmpty()) @@ -294,6 +293,7 @@ void Core::process() static int tolerance = CORE_DISCONNECT_TOLERANCE; tox_do(tox); + toxav_do(toxav); #ifdef DEBUG //we want to see the debug messages immediately @@ -307,7 +307,7 @@ void Core::process() bootstrapDht(); } - toxTimer->start(tox_do_interval(tox)); + toxTimer->start(qMin(tox_do_interval(tox), toxav_do_interval(toxav))); } bool Core::checkConnection() diff --git a/src/core.h b/src/core.h index 98cd9399b..6214539d7 100644 --- a/src/core.h +++ b/src/core.h @@ -244,8 +244,6 @@ private: static void onAvReject(void* toxav, int32_t call_index, void* core); static void onAvEnd(void* toxav, int32_t call_index, void* core); static void onAvRinging(void* toxav, int32_t call_index, void* core); - static void onAvStarting(void* toxav, int32_t call_index, void* core); - static void onAvEnding(void* toxav, int32_t call_index, void* core); static void onAvRequestTimeout(void* toxav, int32_t call_index, void* core); static void onAvPeerTimeout(void* toxav, int32_t call_index, void* core); static void onAvMediaChange(void *toxav, int32_t call_index, void* core); @@ -256,10 +254,10 @@ private: static void prepareCall(int friendId, int callId, ToxAv *toxav, bool videoEnabled); static void cleanupCall(int callId); - static void playCallAudio(ToxAv *toxav, int32_t callId, int16_t *data, int samples, void *user_data); // Callback + static void playCallAudio(void *toxav, int32_t callId, const int16_t *data, uint16_t samples, void *user_data); // Callback static void sendCallAudio(int callId, ToxAv* toxav); static void playAudioBuffer(ALuint alSource, const int16_t *data, int samples, unsigned channels, int sampleRate); - static void playCallVideo(ToxAv* toxav, int32_t callId, vpx_image_t* img, void *user_data); + static void playCallVideo(void *toxav, int32_t callId, const vpx_image_t* img, void *user_data); void sendCallVideo(int callId); bool checkConnection(); diff --git a/src/coreav.cpp b/src/coreav.cpp index fb6ad3343..f2a48f4e5 100644 --- a/src/coreav.cpp +++ b/src/coreav.cpp @@ -45,7 +45,7 @@ void Core::prepareCall(int friendId, int callId, ToxAv* toxav, bool videoEnabled calls[callId].codecSettings.max_video_width = TOXAV_MAX_VIDEO_WIDTH; calls[callId].codecSettings.max_video_height = TOXAV_MAX_VIDEO_HEIGHT; calls[callId].videoEnabled = videoEnabled; - int r = toxav_prepare_transmission(toxav, callId, av_jbufdc, av_VADd, videoEnabled); + int r = toxav_prepare_transmission(toxav, callId, videoEnabled); if (r < 0) qWarning() << QString("Error starting call %1: toxav_prepare_transmission failed with %2").arg(callId).arg(r); @@ -79,7 +79,7 @@ void Core::onAvMediaChange(void* toxav, int32_t callId, void* core) qDebug() << "Core: Received media change from friend "<stop(); @@ -111,14 +111,14 @@ void Core::answerCall(int callId) ToxAvCSettings* transSettings = new ToxAvCSettings; int err = toxav_get_peer_csettings(toxav, callId, 0, transSettings); - if (err != ErrorNone) + if (err != av_ErrorNone) { qWarning() << "Core::answerCall: error getting call settings"; delete transSettings; return; } - if (transSettings->call_type == TypeVideo) + if (transSettings->call_type == av_TypeVideo) { qDebug() << QString("Core: answering call %1 with video").arg(callId); toxav_answer(toxav, callId, transSettings); @@ -148,7 +148,7 @@ void Core::startCall(int friendId, bool video) if (video) { qDebug() << QString("Core: Starting new call with %1 with video").arg(friendId); - cSettings.call_type = TypeVideo; + cSettings.call_type = av_TypeVideo; if (toxav_call(toxav, &callId, friendId, &cSettings, TOXAV_RINGING_TIME) == 0) { calls[callId].videoEnabled=true; @@ -163,7 +163,7 @@ void Core::startCall(int friendId, bool video) else { qDebug() << QString("Core: Starting new call with %1 without video").arg(friendId); - cSettings.call_type = TypeAudio; + cSettings.call_type = av_TypeAudio; if (toxav_call(toxav, &callId, friendId, &cSettings, TOXAV_RINGING_TIME) == 0) { calls[callId].videoEnabled=false; @@ -196,7 +196,7 @@ void Core::cleanupCall(int callId) Audio::unsuscribeInput(); } -void Core::playCallAudio(ToxAv* toxav, int32_t callId, int16_t *data, int samples, void *user_data) +void Core::playCallAudio(void* toxav, int32_t callId, const int16_t *data, uint16_t samples, void *user_data) { Q_UNUSED(user_data); @@ -207,7 +207,7 @@ void Core::playCallAudio(ToxAv* toxav, int32_t callId, int16_t *data, int sample alGenSources(1, &calls[callId].alSource); ToxAvCSettings dest; - if(toxav_get_peer_csettings(toxav, callId, 0, &dest) == 0) + if(toxav_get_peer_csettings((ToxAv*)toxav, callId, 0, &dest) == 0) playAudioBuffer(calls[callId].alSource, data, samples, dest.audio_channels, dest.audio_sample_rate); } @@ -252,7 +252,7 @@ void Core::sendCallAudio(int callId, ToxAv* toxav) calls[callId].sendAudioTimer->start(); } -void Core::playCallVideo(ToxAv*, int32_t callId, vpx_image_t* img, void *user_data) +void Core::playCallVideo(void*, int32_t callId, const vpx_image_t* img, void *user_data) { Q_UNUSED(user_data); @@ -260,8 +260,6 @@ void Core::playCallVideo(ToxAv*, int32_t callId, vpx_image_t* img, void *user_da return; calls[callId].videoSource.pushVPXFrame(img); - - vpx_img_free(img); } void Core::sendCallVideo(int callId) @@ -381,58 +379,58 @@ void Core::onAvRinging(void* _toxav, int32_t call_index, void* core) } } -void Core::onAvStarting(void* _toxav, int32_t call_index, void* core) -{ - ToxAv* toxav = static_cast(_toxav); +//void Core::onAvStarting(void* _toxav, int32_t call_index, void* core) +//{ +// ToxAv* toxav = static_cast(_toxav); - int friendId = toxav_get_peer_id(toxav, call_index, 0); - if (friendId < 0) - { - qWarning() << "Core: Received invalid AV starting"; - return; - } +// int friendId = toxav_get_peer_id(toxav, call_index, 0); +// if (friendId < 0) +// { +// qWarning() << "Core: Received invalid AV starting"; +// return; +// } - ToxAvCSettings* transSettings = new ToxAvCSettings; - int err = toxav_get_peer_csettings(toxav, call_index, 0, transSettings); - if (err != ErrorNone) - { - qWarning() << "Core::onAvStarting: error getting call type"; - delete transSettings; - return; - } +// ToxAvCSettings* transSettings = new ToxAvCSettings; +// int err = toxav_get_peer_csettings(toxav, call_index, 0, transSettings); +// if (err != ErrorNone) +// { +// qWarning() << "Core::onAvStarting: error getting call type"; +// delete transSettings; +// return; +// } - if (transSettings->call_type == TypeVideo) - { - qDebug() << QString("Core: AV starting from %1 with video").arg(friendId); - prepareCall(friendId, call_index, toxav, true); - emit static_cast(core)->avStarting(friendId, call_index, true); - } - else - { - qDebug() << QString("Core: AV starting from %1 without video").arg(friendId); - prepareCall(friendId, call_index, toxav, false); - emit static_cast(core)->avStarting(friendId, call_index, false); - } +// if (transSettings->call_type == TypeVideo) +// { +// qDebug() << QString("Core: AV starting from %1 with video").arg(friendId); +// prepareCall(friendId, call_index, toxav, true); +// emit static_cast(core)->avStarting(friendId, call_index, true); +// } +// else +// { +// qDebug() << QString("Core: AV starting from %1 without video").arg(friendId); +// prepareCall(friendId, call_index, toxav, false); +// emit static_cast(core)->avStarting(friendId, call_index, false); +// } - delete transSettings; -} +// delete transSettings; +//} -void Core::onAvEnding(void* _toxav, int32_t call_index, void* core) -{ - ToxAv* toxav = static_cast(_toxav); +//void Core::onAvEnding(void* _toxav, int32_t call_index, void* core) +//{ +// ToxAv* toxav = static_cast(_toxav); - int friendId = toxav_get_peer_id(toxav, call_index, 0); - if (friendId < 0) - { - qWarning() << "Core: Received invalid AV ending"; - return; - } - qDebug() << QString("Core: AV ending from %1").arg(friendId); +// int friendId = toxav_get_peer_id(toxav, call_index, 0); +// if (friendId < 0) +// { +// qWarning() << "Core: Received invalid AV ending"; +// return; +// } +// qDebug() << QString("Core: AV ending from %1").arg(friendId); - cleanupCall(call_index); +// cleanupCall(call_index); - emit static_cast(core)->avEnding(friendId, call_index); -} +// emit static_cast(core)->avEnding(friendId, call_index); +//} void Core::onAvRequestTimeout(void* _toxav, int32_t call_index, void* core) { @@ -482,14 +480,14 @@ void Core::onAvInvite(void* _toxav, int32_t call_index, void* core) ToxAvCSettings* transSettings = new ToxAvCSettings; int err = toxav_get_peer_csettings(toxav, call_index, 0, transSettings); - if (err != ErrorNone) + if (err != av_ErrorNone) { qWarning() << "Core::onAvInvite: error getting call type"; delete transSettings; return; } - if (transSettings->call_type == TypeVideo) + if (transSettings->call_type == av_TypeVideo) { qDebug() << QString("Core: AV invite from %1 with video").arg(friendId); emit static_cast(core)->avInvite(friendId, call_index, true); @@ -516,14 +514,14 @@ void Core::onAvStart(void* _toxav, int32_t call_index, void* core) ToxAvCSettings* transSettings = new ToxAvCSettings; int err = toxav_get_peer_csettings(toxav, call_index, 0, transSettings); - if (err != ErrorNone) + if (err != av_ErrorNone) { qWarning() << "Core::onAvStart: error getting call type"; delete transSettings; return; } - if (transSettings->call_type == TypeVideo) + if (transSettings->call_type == av_TypeVideo) { qDebug() << QString("Core: AV start from %1 with video").arg(friendId); prepareCall(friendId, call_index, toxav, true); diff --git a/src/video/netvideosource.cpp b/src/video/netvideosource.cpp index 3e433935f..6623e03ad 100644 --- a/src/video/netvideosource.cpp +++ b/src/video/netvideosource.cpp @@ -28,7 +28,7 @@ void NetVideoSource::pushFrame(VideoFrame frame) emit frameAvailable(frame); } -void NetVideoSource::pushVPXFrame(vpx_image *image) +void NetVideoSource::pushVPXFrame(const vpx_image *image) { const int dw = image->d_w; const int dh = image->d_h; diff --git a/src/video/netvideosource.h b/src/video/netvideosource.h index ef7556e96..4ad6dfd51 100644 --- a/src/video/netvideosource.h +++ b/src/video/netvideosource.h @@ -27,7 +27,7 @@ public: NetVideoSource(); void pushFrame(VideoFrame frame); - void pushVPXFrame(vpx_image* image); + void pushVPXFrame(const vpx_image *image); virtual void subscribe() {} virtual void unsubscribe() {} From 69189034e3fa317107868ed2538900321363de4a Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Tue, 18 Nov 2014 01:25:15 +0100 Subject: [PATCH 128/253] Put group chats audio playing in its own thread --- src/core.cpp | 3 +++ src/core.h | 2 +- src/coreav.cpp | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/core.cpp b/src/core.cpp index 9d49c6f18..19e1725ce 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -56,6 +56,9 @@ Core::Core(Camera* cam, QThread *CoreThread, QString loadPath) : coreThread = CoreThread; + audioThread = new QThread(); + audioThread->start(); + videobuf = new uint8_t[videobufsize]; for (int i = 0; i < ptCounter; i++) diff --git a/src/core.h b/src/core.h index 98cd9399b..16e3523a9 100644 --- a/src/core.h +++ b/src/core.h @@ -295,7 +295,7 @@ private: static const int videobufsize; static uint8_t* videobuf; - static QThread *coreThread; + static QThread *coreThread, *audioThread; }; #endif // CORE_HPP diff --git a/src/coreav.cpp b/src/coreav.cpp index fb6ad3343..8c8eaae5a 100644 --- a/src/coreav.cpp +++ b/src/coreav.cpp @@ -23,6 +23,7 @@ ToxCall Core::calls[TOXAV_MAX_CALLS]; const int Core::videobufsize{TOXAV_MAX_VIDEO_WIDTH * TOXAV_MAX_VIDEO_HEIGHT * 4}; uint8_t* Core::videobuf; +QThread* Core::audioThread{nullptr}; bool Core::anyActiveCalls() { @@ -630,6 +631,7 @@ void Core::joinGroupCall(int groupId) groupCalls[groupId].sendAudioTimer->setSingleShot(true); connect(groupCalls[groupId].sendAudioTimer, &QTimer::timeout, [=](){sendGroupCallAudio(groupId,toxav);}); groupCalls[groupId].sendAudioTimer->start(); + groupCalls[groupId].sendAudioTimer->moveToThread(audioThread); } void Core::leaveGroupCall(int groupId) @@ -640,6 +642,7 @@ void Core::leaveGroupCall(int groupId) groupCalls[groupId].sendAudioTimer->stop(); groupCalls[groupId].alSources.clear(); Audio::unsuscribeInput(); + delete groupCalls[groupId].sendAudioTimer; } void Core::sendGroupCallAudio(int groupId, ToxAv* toxav) From 1197f296a4a27d03291d3ca4409f6b8836e1b02a Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Tue, 18 Nov 2014 01:48:44 +0100 Subject: [PATCH 129/253] Fix incorrect HINSTANCE -> int cast --- src/autoupdate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/autoupdate.cpp b/src/autoupdate.cpp index a9189f1c9..2fbcbe206 100644 --- a/src/autoupdate.cpp +++ b/src/autoupdate.cpp @@ -393,7 +393,7 @@ void AutoUpdater::installLocalUpdate() // Workaround QTBUG-7645 // QProcess fails silently when elevation is required instead of showing a UAC prompt on Win7/Vista #ifdef Q_OS_WIN - int result = (int)::ShellExecuteA(0, "open", updaterBin.toUtf8().constData(), 0, 0, SW_SHOWNORMAL); + HINSTANCE result = (int)::ShellExecuteA(0, "open", updaterBin.toUtf8().constData(), 0, 0, SW_SHOWNORMAL); if (SE_ERR_ACCESSDENIED == result) { // Requesting elevation From 0376302fe7571c8d63acba9884a886f8c16872ae Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Tue, 18 Nov 2014 02:09:04 +0100 Subject: [PATCH 130/253] Fix more incorrect HINSTANCE casts --- src/autoupdate.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/autoupdate.cpp b/src/autoupdate.cpp index 2fbcbe206..50d9ed1ff 100644 --- a/src/autoupdate.cpp +++ b/src/autoupdate.cpp @@ -393,13 +393,13 @@ void AutoUpdater::installLocalUpdate() // Workaround QTBUG-7645 // QProcess fails silently when elevation is required instead of showing a UAC prompt on Win7/Vista #ifdef Q_OS_WIN - HINSTANCE result = (int)::ShellExecuteA(0, "open", updaterBin.toUtf8().constData(), 0, 0, SW_SHOWNORMAL); - if (SE_ERR_ACCESSDENIED == result) + HINSTANCE result = ::ShellExecuteA(0, "open", updaterBin.toUtf8().constData(), 0, 0, SW_SHOWNORMAL); + if (result == (HINSTANCE)SE_ERR_ACCESSDENIED) { // Requesting elevation - result = (int)::ShellExecuteA(0, "runas", updaterBin.toUtf8().constData(), 0, 0, SW_SHOWNORMAL); + result = ::ShellExecuteA(0, "runas", updaterBin.toUtf8().constData(), 0, 0, SW_SHOWNORMAL); } - if (result <= 32) + if (result <= (HINSTANCE)32) { goto fail; } From 6eaf8f272eb17ecbc41c3652430655eedad7d245 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Tue, 18 Nov 2014 02:12:19 +0100 Subject: [PATCH 131/253] Silence potentially uninitialized warning Turns out the warning is wrong, but let's initialize anyways --- src/widget/form/settings/privacyform.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widget/form/settings/privacyform.cpp b/src/widget/form/settings/privacyform.cpp index 00c914091..928ed7538 100644 --- a/src/widget/form/settings/privacyform.cpp +++ b/src/widget/form/settings/privacyform.cpp @@ -157,7 +157,7 @@ void PrivacyForm::generateRandomNospam() QTime time = QTime::currentTime(); qsrand((uint)time.msec()); - uint32_t newNospam; + uint32_t newNospam{0}; for (int i = 0; i < 4; i++) newNospam = (newNospam<<8) + (qrand() % 256); // Generate byte by byte. For some reason. From d533c4089dadb93a9d55438b952cf8d1fd996634 Mon Sep 17 00:00:00 2001 From: dubslow Date: Mon, 17 Nov 2014 19:40:36 -0600 Subject: [PATCH 132/253] remember status when disconnected (should help with #715) --- src/widget/widget.cpp | 15 ++++++++++++++- src/widget/widget.h | 1 + 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index 3c9c856a6..78741038e 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -466,11 +466,24 @@ void Widget::onSelfAvatarLoaded(const QPixmap& pic) void Widget::onConnected() { ui->statusButton->setEnabled(true); - emit statusSet(Status::Online); + if (beforeDisconnect == Status::Offline) + emit statusSet(Status::Online); + else + emit statusSet(beforeDisconnect); } void Widget::onDisconnected() { + QString stat = ui->statusButton->property("status").toString(); + if (stat == "online") + beforeDisconnect = Status::Online; + else if (stat == "busy") + beforeDisconnect = Status::Busy; + else if (stat == "away") + beforeDisconnect = Status::Away; + else + beforeDisconnect = Status::Offline; + ui->statusButton->setEnabled(false); emit statusSet(Status::Offline); } diff --git a/src/widget/widget.h b/src/widget/widget.h index 0cf98f6e6..057704e7e 100644 --- a/src/widget/widget.h +++ b/src/widget/widget.h @@ -163,6 +163,7 @@ private: MaskablePixmapWidget* profilePicture; bool notify(QObject *receiver, QEvent *event); bool autoAwayActive = false; + Status beforeDisconnect = Status::Offline; QTimer* idleTimer; QTranslator* translator; }; From bc45ff3a28d02c58e38ec3c5c360e91bc85fe4f3 Mon Sep 17 00:00:00 2001 From: dubslow Date: Mon, 17 Nov 2014 19:41:34 -0600 Subject: [PATCH 133/253] fix crash if no boostrap nodes (fix #791 where "fix" means "mask the underlying problem") --- src/core.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/core.cpp b/src/core.cpp index 9d49c6f18..0b7f1ea94 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -337,6 +337,11 @@ void Core::bootstrapDht() QList dhtServerList = s.getDhtServerList(); int listSize = dhtServerList.size(); + if (listSize == 0) + { + qDebug() << "Settings: no bootstrap list?!?"; + return; + } static int j = qrand() % listSize; qDebug() << "Core: Bootstraping to the DHT ..."; From 845216fa2fbc4f4c7447b6e6ebf816dd72685a18 Mon Sep 17 00:00:00 2001 From: dubslow Date: Mon, 17 Nov 2014 20:15:44 -0600 Subject: [PATCH 134/253] set aliases from chat form (half of #780) --- src/widget/form/chatform.cpp | 2 ++ src/widget/form/chatform.h | 1 + src/widget/form/genericchatform.cpp | 1 + src/widget/form/groupchatform.cpp | 1 - src/widget/friendwidget.cpp | 22 +++++++++++++--------- src/widget/friendwidget.h | 1 + src/widget/widget.cpp | 1 + 7 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/widget/form/chatform.cpp b/src/widget/form/chatform.cpp index d4cde243b..44017003a 100644 --- a/src/widget/form/chatform.cpp +++ b/src/widget/form/chatform.cpp @@ -76,6 +76,8 @@ ChatForm::ChatForm(Friend* chatFriend) connect(chatWidget, &ChatAreaWidget::onFileTranfertInterract, this, &ChatForm::onFileTansBtnClicked); connect(Core::getInstance(), &Core::fileSendFailed, this, &ChatForm::onFileSendFailed); connect(this, SIGNAL(chatAreaCleared()), this, SLOT(clearReciepts())); + connect(nameLabel, &CroppingLabel::textChanged, this, [=](QString text, QString orig) + {if (text != orig) emit aliasChanged(text);} ); setAcceptDrops(true); } diff --git a/src/widget/form/chatform.h b/src/widget/form/chatform.h index b8920b86f..6f6505a8b 100644 --- a/src/widget/form/chatform.h +++ b/src/widget/form/chatform.h @@ -49,6 +49,7 @@ signals: void cancelCall(int callId, int friendId); void micMuteToggle(int callId); void volMuteToggle(int callId); + void aliasChanged(const QString& alias); public slots: void deliverOfflineMsgs(); diff --git a/src/widget/form/genericchatform.cpp b/src/widget/form/genericchatform.cpp index 15f089a30..9fbdb9d4a 100644 --- a/src/widget/form/genericchatform.cpp +++ b/src/widget/form/genericchatform.cpp @@ -47,6 +47,7 @@ GenericChatForm::GenericChatForm(QWidget *parent) : nameLabel = new CroppingLabel(); nameLabel->setObjectName("nameLabel"); nameLabel->setMinimumHeight(Style::getFont(Style::Medium).pixelSize()); + nameLabel->setEditable(true); avatar = new MaskablePixmapWidget(this, QSize(40,40), ":/img/avatar_mask.png"); QHBoxLayout *headLayout = new QHBoxLayout(), *mainFootLayout = new QHBoxLayout(); diff --git a/src/widget/form/groupchatform.cpp b/src/widget/form/groupchatform.cpp index de6fd951a..fb1566a66 100644 --- a/src/widget/form/groupchatform.cpp +++ b/src/widget/form/groupchatform.cpp @@ -52,7 +52,6 @@ GroupChatForm::GroupChatForm(Group* chatGroup) } nameLabel->setText(group->widget->getName()); - nameLabel->setEditable(true); nusersLabel->setFont(Style::getFont(Style::Medium)); nusersLabel->setText(GroupChatForm::tr("%1 users in chat","Number of users in chat").arg(group->peers.size())); diff --git a/src/widget/friendwidget.cpp b/src/widget/friendwidget.cpp index 1904f02b2..a5cf11043 100644 --- a/src/widget/friendwidget.cpp +++ b/src/widget/friendwidget.cpp @@ -218,6 +218,18 @@ void FriendWidget::mouseMoveEvent(QMouseEvent *ev) } } +void FriendWidget::setAlias(const QString& _alias) +{ + QString alias = _alias.trimmed(); + alias.remove(QRegExp("[\\t\\n\\v\\f\\r\\x0000]")); // we should really treat regular names this way as well (*ahem* zetok) + alias = alias.left(128); // same as TOX_MAX_NAME_LENGTH + Friend* f = FriendList::findFriend(friendId); + f->setAlias(alias); + Settings::getInstance().setFriendAlias(f->getToxID(), alias); + hide(); + show(); +} + void FriendWidget::setFriendAlias() { bool ok; @@ -227,13 +239,5 @@ void FriendWidget::setFriendAlias() f->getDisplayedName(), &ok); if (ok) - { - alias = alias.trimmed(); - alias.remove(QRegExp("[\t\n\v\f\r]")); - alias = alias.left(128); // same as TOX_MAX_NAME_LENGTH - f->setAlias(alias); - Settings::getInstance().setFriendAlias(f->getToxID(), alias); - hide(); - show(); - } + setAlias(alias); } diff --git a/src/widget/friendwidget.h b/src/widget/friendwidget.h index 1aa2840a5..0223149b2 100644 --- a/src/widget/friendwidget.h +++ b/src/widget/friendwidget.h @@ -44,6 +44,7 @@ signals: public slots: void onAvatarChange(int FriendId, const QPixmap& pic); void onAvatarRemoved(int FriendId); + void setAlias(const QString& alias); protected: void mousePressEvent(QMouseEvent* ev); diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index 78741038e..3e9aa8d38 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -646,6 +646,7 @@ void Widget::addFriend(int friendId, const QString &userId) connect(newfriend->getChatForm(), SIGNAL(cancelCall(int,int)), core, SLOT(cancelCall(int,int))); connect(newfriend->getChatForm(), SIGNAL(micMuteToggle(int)), core, SLOT(micMuteToggle(int))); connect(newfriend->getChatForm(), SIGNAL(volMuteToggle(int)), core, SLOT(volMuteToggle(int))); + connect(newfriend->getChatForm(), &ChatForm::aliasChanged, newfriend->getFriendWidget(), &FriendWidget::setAlias); connect(core, &Core::fileReceiveRequested, newfriend->getChatForm(), &ChatForm::onFileRecvRequest); connect(core, &Core::avInvite, newfriend->getChatForm(), &ChatForm::onAvInvite); connect(core, &Core::avStart, newfriend->getChatForm(), &ChatForm::onAvStart); From 6831778371b47d05945f3e8de990b58667956410 Mon Sep 17 00:00:00 2001 From: dubslow Date: Mon, 17 Nov 2014 21:22:40 -0600 Subject: [PATCH 135/253] fix bug (which is actually why aliasing was menu'd in the first place) --- src/widget/croppinglabel.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/widget/croppinglabel.cpp b/src/widget/croppinglabel.cpp index f7b0b939e..508c97134 100644 --- a/src/widget/croppinglabel.cpp +++ b/src/widget/croppinglabel.cpp @@ -129,8 +129,9 @@ void CroppingLabel::hideTextEdit(bool acceptText) { if (acceptText) { - emit textChanged(textEdit->text(), origText); - setText(textEdit->text()); + QString oldOrigText = origText; + setText(textEdit->text()); // set before emitting so we don't override external reactions to signal + emit textChanged(textEdit->text(), oldOrigText); } textEdit->hide(); From d006ed5f2c41d56e3a123caa44ac53d63e717afa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=83=D1=85=D0=B0=D1=80=D0=B8=D0=BA?= Date: Tue, 18 Nov 2014 09:44:58 +0300 Subject: [PATCH 136/253] update russian translation --- translations/ru.ts | 276 +++++++++++++++++++++++++++------------------ 1 file changed, 164 insertions(+), 112 deletions(-) diff --git a/translations/ru.ts b/translations/ru.ts index ac9740120..c7d4fbd76 100644 --- a/translations/ru.ts +++ b/translations/ru.ts @@ -4,7 +4,7 @@ AVForm - + Audio/Video Аудио/Видео @@ -132,17 +132,17 @@ Ignore the proxy and connect to the Internet directly ? Расширенные - + FULL - very safe, slowest (recommended) Полная - полностью безопасно, самая медленная (рекомендуется) - + NORMAL - almost as safe as FULL, about 20% faster than FULL Нормальная - почти так же безопасно, как полная, но на 20% быстрее - + OFF - disables all safety, when something goes wrong your history may be lost, fastest (not recommended) Выключена - небезопасно, если что-то пойдёт не так, история может быть утрачена, самая быстрая (не рекомендуется) @@ -183,48 +183,48 @@ Ignore the proxy and connect to the Internet directly ? Загрузить историю... - + Send a file Отправить файл - + Bad Idea Плохая идея - + You're trying to send a special (sequential) file, that's not going to work! ...передаёте последовательный файл и получаете te-le-fun-ken. И переводчик работает по другой линии. По линии «Библиотека». Вы пытаетесь отправить специальный (последовательный) файл. Это так не работает! - + %1 calling %1 звонит - + %1 stopped calling %1 прекратил звонить - + Calling to %1 Звоним %1 - + Call rejected Звонок отклонён - + Call with %1 ended. %2 Разговор с %1 завершился. %2 - + Call duration: Длительность разговора: @@ -240,106 +240,106 @@ Ignore the proxy and connect to the Internet directly ? Core - + Toxing on qTox Как-то так. Может, можно ещё что-нибудь придумать? Всем привет из qTox'а - + qTox User Пользователь qTox - + Friend is already added Друг уже добавлен - + Encryption error Ошибка шифрования - + The .tox file is encrypted, but encryption was not checked, continuing regardless. Файл .tox зашифрован, однако шифрование в настройках включено не было. Продолжаем вопреки. - + Tox datafile decryption password Пароль для расшифровки файла данных Tox - - - + + + Password error Ошибка пароля - - + + Failed to setup password. Empty password. Не удалось установить пароль. Пустой пароль. - + Try Again Попробуйте ещё - + Change profile Сменить профиль - + Reinit current profile Увы, никто не знает, что разработчики имели в виду. Ту би транслейтед. Сбросить данные текущего профиля - + Wrong password has been entered Введён неправильный пароль - + History Log decryption password Пароль для расшифровки журнала переписки - + Encrypted log Зашифрованный журнал - + Your history is encrypted with different password Do you want to try another password? Ваша переписка зашифрована другим паролем Ходите попробовать другой пароль? - + Loggin Журналирование - + Due to incorret password logging will be disabled Из-за некорректного пароля журналирование будет отключено - + NO Password БЕЗ пароля - + Will be saved without encryption! Будет сохранено без шифрования! @@ -347,25 +347,25 @@ Do you want to try another password? FileTransferInstance - + Save a file Title of the file saving dialog Сохранить файл - + Location not writable Title of permissions popup Нет прав на запись - + You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup У вас нет прав для записи в эту директорию. Выберете другую. - + ETA Осталось @@ -479,30 +479,30 @@ Do you want to try another password? GeneralForm - + General Общие - - + + None Отсутствует - + Choose an auto accept directory popup title Выбрать папку для автоматического приёма - + Call active popup title Идёт звонок - + You can't disconnect while a call is active! popup text Нельзя отключиться пока идёт звонок! @@ -549,7 +549,7 @@ Do you want to try another password? Faux offline messaging - Имитация офлайнового обмена сообщениями + Имитация офлайнового обмена сообщениями @@ -562,79 +562,90 @@ Do you want to try another password? Укажите 0, чтобы отключить - + + You can set this on a per-friend basis by right clicking them. + autoaccept cb tooltip + Можно настроить отдельно для каждого друга (щёлкнув правой кнопкой мыши по другу и выбрав соответствующий пункт меню). + + + Use emoticons Использовать смайлики - + Smiley Pack Text on smiley pack label Набор смайликов - + Style Стиль - + + Theme color + Цвет темы + + + Emoticon size Размер смайликов - + px По аналогии с Мпикс. Хотя, может лучше принять http://ilyabirman.ru/meanwhile/all/px/? пикс - + Timestamp format Формат времени - + Connection Settings Настройки соединения - + Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Включить IPv6 (рекомендуется) - + Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Отключение позволяет, например, использовать Tox поверх Tor. Однако это добавляет нагрузку на сеть Tox, так что отключайте только в случае необходимости. - + Enable UDP (recommended) Text on checkbox to disable UDP Включить UDP (рекомендуется) - + Use proxy (SOCKS5) Использовать прокси (SOCKS5) - + Address Text on proxy addr label Адрес - + Port Text on proxy port label Порт - + Reconnect reconnect button Переподключиться @@ -671,27 +682,27 @@ Do you want to try another password? Выставлено в минутах - + minutes минут - + Autoaccept files Автоматически принимать файлы - + Save files in Сохранять в - + PushButton - + Theme Тема @@ -715,12 +726,12 @@ Do you want to try another password? - Audio call: RED means you're on a call + Audio call: RED means you're on a call Позвонить, только аудио (красная кнопка значит что вы на связи) - Video call: RED means you're on a call + Video call: RED means you're on a call Видеозвонок (красная кнопка значит что вы на связи) @@ -734,18 +745,18 @@ Do you want to try another password? Включить или выключить микрофон (красная кнопка значит что микрофон выключен) - - + + Save chat log Сохранить журнал чата - + Clear displayed messages Очистить показываемые сообщения - + Cleared Очищено @@ -759,7 +770,7 @@ Do you want to try another password? %1 пользователей в чате - + %1 users in chat %1 пользователей в чате @@ -1041,27 +1052,27 @@ Do you want to try another password? Ваш статус - + Add friends Добавить друзей - + Create a group chat Создать групповой чат - + View completed file transfers Завершённые передачи файлов - + Change your settings Изменить ваши настройки - + Close Закрыть @@ -1082,12 +1093,12 @@ Do you want to try another password? Конфиденциальность - + Encrypted log Зашифрованный журнал - + You already have history log file encrypted with different password Do you want to delete old history file? У вас уже есть файл журнала переписки, зашифрованный другим паролем @@ -1121,6 +1132,21 @@ Do you want to delete old history file? Encrypt History Шифровать историю + + + Nospam + Антиспам + + + + HHHHHHHH + HHHHHHHH + + + + Generate random nospam + Сгенерировать случайное значение + QObject @@ -1180,7 +1206,33 @@ It will be installed when qTox restarts. Tox URI to parse - + Без перевода, так как весь остальной CLI на английском + Tox URI to parse + + + + Default + По-умолчанию + + + + Blue + Синяя + + + + Olive + Оливковая + + + + Red + Красная + + + + Violet + Фиолетовая @@ -1287,160 +1339,160 @@ It will be installed when qTox restarts. Widget - + Online В сети - + Away Отошёл - + Busy Занят - + &Quit В&ыход - + Change status to: Сменить статус на: - + Online Button to set your status to 'Online' В сети - + Away Button to set your status to 'Away' Вероятно, это не столь долгое путешествие Отошёл - + Busy Button to set your status to 'Busy' Занят - + Choose a profile Выберите профиль - + Please choose which identity to use Выберите личность, которую хотите использовать - + Choose a profile picture Выбрать картинку для профиля - - - + + + Error Ошибка - + Unable to open this file Невозможно открыть файл - + Unable to read this image Невозможно прочесть это изображение - + This image is too big Это изображение слишком большое - + Toxcore failed to start, the application will terminate after you close this message. Не удалось запустить toxcore, приложение будет завершено после того как вы закроете это сообщение. - + toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text Не удалось запустить toxcore с вашими настройками прокси, qTox не может работать; измените ваши настройки и перезапустите его. - + Add friend Добавить друга - + File transfers Передачи файлов - + Settings Настройки - + Couldn't request friendship Не удалось запросить добавление в друзья - + away contact status отсутствует - + busy contact status занят - + offline contact status офлайн - + online contact status в сети - + %1 is now %2 e.g. "Dubslow is now online" %1 сейчас %2 - + <Unknown> Placeholder when we don't know someone's name in a group chat <Неизвестный> - + %1 has set the title to %2 - %1 сменил заголовок на %2 + %1 сменил заголовок на %2 - + Message failed to send Не удалось отправить сообщение From d68788c6445f3b7e64db7abe3f850f8bac5a7352 Mon Sep 17 00:00:00 2001 From: dubslow Date: Tue, 18 Nov 2014 05:00:09 -0600 Subject: [PATCH 137/253] add group title menu option, other half of closes #780 also truncate all group titles to max len --- src/widget/form/groupchatform.cpp | 2 +- src/widget/friendwidget.cpp | 2 +- src/widget/groupwidget.cpp | 20 ++++++++++++++++++-- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/widget/form/groupchatform.cpp b/src/widget/form/groupchatform.cpp index fb1566a66..ff0c66dbb 100644 --- a/src/widget/form/groupchatform.cpp +++ b/src/widget/form/groupchatform.cpp @@ -81,7 +81,7 @@ GroupChatForm::GroupChatForm(Group* chatGroup) connect(micButton, SIGNAL(clicked()), this, SLOT(onMicMuteToggle())); connect(volButton, SIGNAL(clicked()), this, SLOT(onVolMuteToggle())); connect(nameLabel, &CroppingLabel::textChanged, this, [=](QString text, QString orig) - {if (text != orig) emit groupTitleChanged(group->groupId, text);} ); + {if (text != orig) emit groupTitleChanged(group->groupId, text.left(128));} ); setAcceptDrops(true); } diff --git a/src/widget/friendwidget.cpp b/src/widget/friendwidget.cpp index a5cf11043..7d675a8c6 100644 --- a/src/widget/friendwidget.cpp +++ b/src/widget/friendwidget.cpp @@ -235,7 +235,7 @@ void FriendWidget::setFriendAlias() bool ok; Friend* f = FriendList::findFriend(friendId); - QString alias = QInputDialog::getText(nullptr, tr("User alias"), tr("Alias:"), QLineEdit::Normal, + QString alias = QInputDialog::getText(nullptr, tr("User alias"), tr("You can also set this by clicking the chat form name.\nAlias:"), QLineEdit::Normal, f->getDisplayedName(), &ok); if (ok) diff --git a/src/widget/groupwidget.cpp b/src/widget/groupwidget.cpp index dd52cce94..94c2d16c2 100644 --- a/src/widget/groupwidget.cpp +++ b/src/widget/groupwidget.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include "ui_mainwindow.h" @@ -51,10 +52,25 @@ void GroupWidget::contextMenuEvent(QContextMenuEvent * event) QPoint pos = event->globalPos(); QMenu menu; QAction* quitGroup = menu.addAction(tr("Quit group","Menu to quit a groupchat")); + QAction* setAlias = menu.addAction(tr("Set title...")); QAction* selectedItem = menu.exec(pos); - if (selectedItem == quitGroup) - emit removeGroup(groupId); + if (selectedItem) + { + if (selectedItem == quitGroup) + emit removeGroup(groupId); + else if (selectedItem == setAlias) + { + bool ok; + Group* g = GroupList::findGroup(groupId); + + QString alias = QInputDialog::getText(nullptr, tr("Group title"), tr("You can also set this by clicking the chat form name.\nTitle:"), QLineEdit::Normal, + nameLabel->fullText(), &ok); + + if (ok && alias != nameLabel->fullText()) + emit g->chatForm->groupTitleChanged(groupId, alias.left(128)); + } + } } void GroupWidget::onUserListChanged() From 6afe34fe02f1500a2a70162b572f7b8e096a690e Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Tue, 18 Nov 2014 20:04:44 +0100 Subject: [PATCH 138/253] Fix zlib link order on windows x64 --- qtox.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qtox.pro b/qtox.pro index ad7d2f34d..013619a6f 100644 --- a/qtox.pro +++ b/qtox.pro @@ -64,7 +64,7 @@ win32 { RC_FILE = windows/qtox.rc LIBS += -liphlpapi -L$$PWD/libs/lib -lsodium -ltoxav -ltoxcore -ltoxencryptsave -ltoxdns -lvpx -lpthread LIBS += -L$$PWD/libs/lib -lopencv_core248 -lopencv_highgui248 -lopencv_imgproc248 -lOpenAL32 -lopus - LIBS += -lz -lopengl32 -lole32 -loleaut32 -luuid -lvfw32 -ljpeg -ltiff -lpng -ljasper -lIlmImf -lHalf -lws2_32 + LIBS += -lopengl32 -lole32 -loleaut32 -luuid -lvfw32 -ljpeg -ltiff -lpng -ljasper -lIlmImf -lHalf -lws2_32 -lz } else { macx { BUNDLEID = im.tox.qtox From 33cb5a03ec6a41327e47acf0eb20c70deb8a299a Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Tue, 18 Nov 2014 20:31:06 +0100 Subject: [PATCH 139/253] Update windows installer for x64 --- windows/qtox.nsi | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/windows/qtox.nsi b/windows/qtox.nsi index 1594b680b..4e3af7941 100644 --- a/windows/qtox.nsi +++ b/windows/qtox.nsi @@ -249,21 +249,21 @@ Section "Install" ${CreateDirectory} "bin" ${SetOutPath} "$INSTDIR\bin" - ${File} "qTox-win32\*.*" + ${File} "qtox\*.*" ${CreateDirectory} "imageformats" ${SetOutPath} "$INSTDIR\bin\imageformats" - ${File} "qTox-win32\imageformats\*.*" + ${File} "qtox\imageformats\*.*" ${SetOutPath} "$INSTDIR\bin" ${CreateDirectory} "platforms" ${SetOutPath} "$INSTDIR\bin\platforms" - ${File} "qTox-win32\platforms\*.*" + ${File} "qtox\platforms\*.*" ${SetOutPath} "$INSTDIR\bin" ${CreateDirectory} "sqldrivers" ${SetOutPath} "$INSTDIR\bin\sqldrivers" - ${File} "qTox-win32\sqldrivers\*.*" + ${File} "qtox\sqldrivers\*.*" ${SetOutPath} "$INSTDIR\bin" # Create shortcuts From 53e304b9688ece19fd16e03578b76a4f8253a5ca Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Tue, 18 Nov 2014 23:31:01 +0100 Subject: [PATCH 140/253] Remove unnecessary OSX lib search paths --- src/main.cpp | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 878b3fc12..baab1f558 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -93,21 +93,6 @@ int main(int argc, char *argv[]) // Windows platform plugins DLL hell fix QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath()); a.addLibraryPath("platforms"); -#ifdef Q_OS_OSX - QString startPath = a.applicationDirPath(); - QDir pluginsDir(startPath); - pluginsDir.cdUp(); - pluginsDir.cd("PlugIns"); - a.addLibraryPath(pluginsDir.absolutePath()); - pluginsDir.cd(startPath); - pluginsDir.cdUp(); - pluginsDir.cd("Plugins"); - a.addLibraryPath(pluginsDir.absolutePath()); - pluginsDir.cd(startPath); - pluginsDir.cdUp(); - pluginsDir.cd("plugins"); - a.addLibraryPath(pluginsDir.absolutePath()); -#endif qDebug() << "built on: " << __TIME__ << __DATE__ << "(" << TIMESTAMP << ")"; qDebug() << "commit: " << GIT_VERSION << "\n"; From 4d4067cf88d41201a3a17d5053507f3f3e0196e2 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Tue, 18 Nov 2014 23:35:02 +0100 Subject: [PATCH 141/253] README: Add qTox windows 64 bit download link --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 401604384..26bd66749 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,9 @@ qTox is a powerful Tox client that tries to follow the Tox design guidelines whi This client runs on Windows, Linux and Mac natively.
-Windows download
-Mac download
+Windows 64 bit download
+Windows 32 bit download (for older hardware)
+Mac OS X download
Linux binary download
Linux packages
From 6e25dd76048272d0e61c180d7d6ecad73333bd9d Mon Sep 17 00:00:00 2001 From: Ansa89 Date: Wed, 19 Nov 2014 13:10:16 +0100 Subject: [PATCH 142/253] Italian translation: update --- translations/it.ts | 179 +++++++++++++++++++++++++-------------------- 1 file changed, 99 insertions(+), 80 deletions(-) diff --git a/translations/it.ts b/translations/it.ts index 45bcccca8..e4d72e1f7 100644 --- a/translations/it.ts +++ b/translations/it.ts @@ -182,47 +182,47 @@ Ignorare le impostazioni del proxy e connettersi direttamente alla rete Tox?Carica log... - + Send a file Invia un file - + Bad Idea Pessima idea - + You're trying to send a special (sequential) file, that's not going to work! Stai cercando di inviare un file speciale (sequenziale), questo non funzionerà! - + %1 calling %1 ti sta chiamando - + %1 stopped calling %1 ha fermato la chiamata - + Calling to %1 Stai chiamando %1 - + Call with %1 ended. %2 Chiamata con %1 terminata. %2 - + Call duration: Durata chiamata: - + Call rejected Chiamata rifiutata @@ -238,105 +238,105 @@ Ignorare le impostazioni del proxy e connettersi direttamente alla rete Tox? Core - + Toxing on qTox Toxing on qTox - + qTox User qTox User - + Friend is already added Questo contatto è già presente nella tua lista - + Encryption error Errore crittografia - + The .tox file is encrypted, but encryption was not checked, continuing regardless. Il Tox datafile è criptato, ma la crittografia non è abilitata nelle impostazioni. Continuo ignorando le impostazioni. - + Tox datafile decryption password Password per decriptare il Tox datafile - - - + + + Password error Errore password - - + + Failed to setup password. Empty password. Impossibile impostare la password. Password vuota. - + Try Again Riprova - + Change profile Cambia profilo - + Reinit current profile Reinizializza il profilo corrente - + Wrong password has been entered È stata inserita una password sbagliata - + History Log decryption password Password per decriptare i log - + Encrypted log Log criptato - + Your history is encrypted with different password Do you want to try another password? I log sono criptati con una password diversa. Vuoi provare un'altra password? - + Loggin Logging - + Due to incorret password logging will be disabled I log saranno disabilitati a causa di una password incorretta - + NO Password Nessuna password - + Will be saved without encryption! Il Tox datafile sarà salvato senza password! @@ -344,25 +344,25 @@ Vuoi provare un'altra password? FileTransferInstance - + Save a file Title of the file saving dialog Salva file - + Location not writable Title of permissions popup Errore - + You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Non hai sufficienti permessi per scrivere in questa locazione. Scegli un'altra posizione, o annulla il salvataggio. - + ETA Tempo rimanente @@ -460,14 +460,16 @@ Vuoi provare un'altra password? Scegli dove salvare i files accettati automaticamente - + User alias Rinomina contatto - - Alias: - Soprannome: + + You can also set this by clicking the chat form name. +Alias: + Per impostare un soprannome puoi anche cliccare sul nome direttamente dalla chat. +Soprannome: @@ -703,53 +705,53 @@ Vuoi provare un'altra password? GenericChatForm - + Send message Invia messaggio - + Smileys Emoticons - + Send file(s) Invia file(s) - + Audio call: RED means you're on a call Chiamata audio: ROSSO significa che la chiamata è in corso - + Video call: RED means you're on a call Videochiamata: ROSSO significa che la chiamata è in corso - + Toggle speakers volume: RED is OFF Imposta volume altoparlanti: ROSSO è SPENTO - + Toggle microphone: RED is OFF Imposta microfono: ROSSO è SPENTO - - + + Save chat log Salva il log della chat - + Clear displayed messages Rimuovi messaggi visualizzati - + Cleared Pulito @@ -757,13 +759,13 @@ Vuoi provare un'altra password? GroupChatForm - + %1 users in chat Number of users in chat %1 utenti in chat - + %1 users in chat %1 utenti in chat @@ -771,23 +773,40 @@ Vuoi provare un'altra password? GroupWidget - - + + %1 users in chat %1 utenti in chat - - + + 0 users in chat 0 utenti in chat - + Quit group Menu to quit a groupchat Esci dal gruppo + + + Set title... + Imposta nome gruppo... + + + + Group title + Nome gruppo + + + + You can also set this by clicking the chat form name. +Title: + Per impostare un nome puoi anche cliccare sul gruppo direttamente dalla chat. +Nome gruppo: + IdentityForm @@ -1374,116 +1393,116 @@ Verrà installata al riavvio del programma. Occupato - + Choose a profile Scegli un profilo - + Please choose which identity to use Per favore scegli quale identità usare - + Choose a profile picture Scegli un'immagine per il profilo - - - + + + Error Errore - + Unable to open this file Impossibile aprire il file - + Unable to read this image Impossibile leggere l'immagine - + This image is too big L'immagine è troppo grande - + Toxcore failed to start, the application will terminate after you close this message. Impossibile avviare Toxcore.\nqTox terminerà dopo che avrai chiuso questo messaggio. - + toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text Impossibile avviare Toxcore con le tue impostazione proxy.\nqTox non può funzionare correttamente, per favore modifica le impostazioni e riavvia il programma. - + Add friend Aggiungi contatto - + File transfers Files trasferiti - + Settings Impostazioni - + Couldn't request friendship Impossibile inviare la richiesta d'amicizia - + away contact status assente - + busy contact status occupato - + offline contact status offline - + online contact status online - + %1 is now %2 e.g. "Dubslow is now online" %1 è ora %2 - + <Unknown> Placeholder when we don't know someone's name in a group chat <Sconosciuto> - + %1 has set the title to %2 %1 ha impostato il titolo in %2 - + Message failed to send Impossibile inviare il messaggio From 451d0c7999a7741674047d1ecd3fb58a3ab0b714 Mon Sep 17 00:00:00 2001 From: apprb Date: Wed, 19 Nov 2014 22:46:50 +0900 Subject: [PATCH 143/253] check incoming data from DB --- src/historykeeper.cpp | 10 +++++----- src/widget/form/chatform.cpp | 5 +++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/historykeeper.cpp b/src/historykeeper.cpp index 37ba7cb9d..309c760d9 100644 --- a/src/historykeeper.cpp +++ b/src/historykeeper.cpp @@ -122,7 +122,7 @@ HistoryKeeper::HistoryKeeper(GenericDdInterface *db_) : if (idCur != idMax) { - QString cmd = QString("INSERT INTO sent_status (id, status) VALUES (%1, 1)").arg(idMax); + QString cmd = QString("INSERT INTO sent_status (id, status) VALUES (%1, 1);").arg(idMax); db->exec(cmd); } } @@ -147,12 +147,12 @@ int HistoryKeeper::addChatEntry(const QString& chat, const QString& message, con int chat_id = getChatID(chat, ctSingle).first; int sender_id = getAliasID(sender); - db->exec("BEGIN TRANSACTION"); - db->exec(QString("INSERT INTO history (timestamp, chat_id, sender, message)") + + db->exec("BEGIN TRANSACTION;"); + db->exec(QString("INSERT INTO history (timestamp, chat_id, sender, message) ") + QString("VALUES (%1, %2, %3, '%4');") .arg(dt.toMSecsSinceEpoch()).arg(chat_id).arg(sender_id).arg(wrapMessage(message))); - db->exec(QString("INSERT INTO sent_status (status) VALUES (%1)").arg(isSent)); - db->exec("COMMIT TRANSACTION"); + db->exec(QString("INSERT INTO sent_status (status) VALUES (%1);").arg(isSent)); + db->exec("COMMIT TRANSACTION;"); messageID++; return messageID; diff --git a/src/widget/form/chatform.cpp b/src/widget/form/chatform.cpp index 44017003a..511034c2f 100644 --- a/src/widget/form/chatform.cpp +++ b/src/widget/form/chatform.cpp @@ -743,8 +743,9 @@ void ChatForm::loadHistory(QDateTime since, bool processUndelivered) } // Show each messages - MessageActionPtr ca = genMessageActionAction(ToxID::fromString(it.sender), it.message, false, msgDateTime); - if (it.isSent) + ToxID msgSender = ToxID::fromString(it.sender); + MessageActionPtr ca = genMessageActionAction(msgSender, it.message, false, msgDateTime); + if (it.isSent || !msgSender.isMine()) { ca->markAsSent(); } else { From 599c246ec7d167eabfac2dbc1f0e5139f5e4a29a Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Wed, 19 Nov 2014 22:26:04 +0100 Subject: [PATCH 144/253] Move group audio playing to Audio singletong/thread This is a bit of a quick fix for a new bug, as but part of cleaning up the audio code and moving it to the Audio singleton --- src/audio.cpp | 81 ++++++++++++++++++++++++++++++++++++++++++- src/audio.h | 26 ++++++++++++-- src/core.cpp | 8 ++--- src/core.h | 6 ++-- src/coreav.cpp | 18 ---------- src/widget/widget.cpp | 2 ++ 6 files changed, 113 insertions(+), 28 deletions(-) diff --git a/src/audio.cpp b/src/audio.cpp index 46620b6e9..b1efce7bf 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -19,13 +19,31 @@ #include "src/core.h" #include +#include + +#include std::atomic Audio::userCount{0}; +Audio* Audio::instance{nullptr}; +QThread* Audio::audioThread{nullptr}; ALCdevice* Audio::alInDev{nullptr}; ALCdevice* Audio::alOutDev{nullptr}; -ALCcontext* Audio::alContext{0}; +ALCcontext* Audio::alContext{nullptr}; ALuint Audio::alMainSource{0}; +Audio& Audio::getInstance() +{ + if (!instance) + { + instance = new Audio(); + audioThread = new QThread(instance); + audioThread->setObjectName("qTox Audio"); + audioThread->start(); + instance->moveToThread(audioThread); + } + return *instance; +} + void Audio::suscribeInput() { if (!userCount++ && alInDev) @@ -125,3 +143,64 @@ void Audio::playMono16Sound(const QByteArray& data) alSourcePlay(alMainSource); alDeleteBuffers(1, &buffer); } + +void Audio::playGroupAudioQueued(Tox*,int group, int peer, const int16_t* data, + unsigned samples, uint8_t channels, unsigned sample_rate,void*) +{ + QMetaObject::invokeMethod(instance, "playGroupAudio", Qt::BlockingQueuedConnection, + Q_ARG(int,group), Q_ARG(int,peer), Q_ARG(const int16_t*,data), + Q_ARG(unsigned,samples), Q_ARG(uint8_t,channels), Q_ARG(unsigned,sample_rate)); +} + +void Audio::playGroupAudio(int group, int peer, const int16_t* data, + unsigned samples, uint8_t channels, unsigned sample_rate) +{ + assert(QThread::currentThread() == audioThread); + + ToxGroupCall& call = Core::groupCalls[group]; + + if (!call.active || call.muteVol) + return; + + if (!call.alSources.contains(peer)) + alGenSources(1, &call.alSources[peer]); + + playAudioBuffer(call.alSources[peer], data, samples, channels, sample_rate); +} + +void Audio::playAudioBuffer(ALuint alSource, const int16_t *data, int samples, unsigned channels, int sampleRate) +{ + assert(channels == 1 || channels == 2); + + ALuint bufid; + ALint processed = 0, queued = 16; + alGetSourcei(alSource, AL_BUFFERS_PROCESSED, &processed); + alGetSourcei(alSource, AL_BUFFERS_QUEUED, &queued); + alSourcei(alSource, AL_LOOPING, AL_FALSE); + + if(processed) + { + ALuint bufids[processed]; + alSourceUnqueueBuffers(alSource, processed, bufids); + alDeleteBuffers(processed - 1, bufids + 1); + bufid = bufids[0]; + } + else if(queued < 16) + { + alGenBuffers(1, &bufid); + } + else + { + qDebug() << "Audio: Dropped frame"; + return; + } + + alBufferData(bufid, (channels == 1) ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16, data, + samples * 2 * channels, sampleRate); + alSourceQueueBuffers(alSource, 1, &bufid); + + ALint state; + alGetSourcei(alSource, AL_SOURCE_STATE, &state); + if(state != AL_PLAYING) + alSourcePlay(alSource); +} diff --git a/src/audio.h b/src/audio.h index 6a3875243..9c4f87353 100644 --- a/src/audio.h +++ b/src/audio.h @@ -18,6 +18,8 @@ #ifndef AUDIO_H #define AUDIO_H +#include +#include #include #if defined(__APPLE__) && defined(__MACH__) @@ -30,10 +32,17 @@ class QString; class QByteArray; +class QTimer; +class QThread; +struct Tox; -class Audio +class Audio : QObject { + Q_OBJECT + public: + static Audio& getInstance(); ///< Returns the singleton's instance. Will construct on first call. + static void suscribeInput(); ///< Call when you need to capture sound from the open input device. static void unsuscribeInput(); ///< Call once you don't need to capture on the open input device anymore. @@ -45,15 +54,28 @@ public: static void playMono16Sound(const QByteArray& data); ///< Play a 44100Hz mono 16bit PCM sound + /// May be called from any thread, will always queue a call to playGroupAudio + /// The first and last argument are ignored, but allow direct compatibility with toxcore + static void playGroupAudioQueued(Tox*, int group, int peer, const int16_t* data, + unsigned samples, uint8_t channels, unsigned sample_rate, void*); + +public slots: + /// Must be called from the audio thread, plays a group call's received audio + void playGroupAudio(int group, int peer, const int16_t* data, + unsigned samples, uint8_t channels, unsigned sample_rate); + public: + static QThread* audioThread; static ALCdevice* alOutDev, *alInDev; static ALCcontext* alContext; static ALuint alMainSource; private: - Audio(); + explicit Audio()=default; + static void playAudioBuffer(ALuint alSource, const int16_t *data, int samples, unsigned channels, int sampleRate); private: + static Audio* instance; static std::atomic userCount; }; diff --git a/src/core.cpp b/src/core.cpp index 715cd5641..88c7e118f 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -56,8 +56,7 @@ Core::Core(Camera* cam, QThread *CoreThread, QString loadPath) : coreThread = CoreThread; - audioThread = new QThread(); - audioThread->start(); + Audio::getInstance(); videobuf = new uint8_t[videobufsize]; @@ -1496,7 +1495,8 @@ int Core::joinGroupchat(int32_t friendnumber, uint8_t type, const uint8_t* frien else if (type == TOX_GROUPCHAT_TYPE_AV) { qDebug() << QString("Trying to join AV groupchat invite sent by friend %1").arg(friendnumber); - return toxav_join_av_groupchat(tox, friendnumber, friend_group_public_key, length, playGroupAudio, const_cast(this)); + return toxav_join_av_groupchat(tox, friendnumber, friend_group_public_key, length, + &Audio::playGroupAudioQueued, const_cast(this)); } else { @@ -1641,7 +1641,7 @@ void Core::createGroup(uint8_t type) } else if (type == TOX_GROUPCHAT_TYPE_AV) { - emit emptyGroupCreated(toxav_add_av_groupchat(tox, playGroupAudio, this)); + emit emptyGroupCreated(toxav_add_av_groupchat(tox, &Audio::playGroupAudioQueued, this)); } else { diff --git a/src/core.h b/src/core.h index 16e3523a9..061fe4047 100644 --- a/src/core.h +++ b/src/core.h @@ -250,8 +250,6 @@ private: static void onAvPeerTimeout(void* toxav, int32_t call_index, void* core); static void onAvMediaChange(void *toxav, int32_t call_index, void* core); - static void playGroupAudio(Tox* tox, int groupnumber, int friendgroupnumber, const int16_t* out_audio, - unsigned out_audio_samples, uint8_t decoder_channels, unsigned audio_sample_rate, void* userdata); static void sendGroupCallAudio(int groupId, ToxAv* toxav); static void prepareCall(int friendId, int callId, ToxAv *toxav, bool videoEnabled); @@ -295,7 +293,9 @@ private: static const int videobufsize; static uint8_t* videobuf; - static QThread *coreThread, *audioThread; + static QThread *coreThread; + + friend class Audio; ///< Audio can access our calls directly to reduce latency }; #endif // CORE_HPP diff --git a/src/coreav.cpp b/src/coreav.cpp index 8c8eaae5a..e2134fb69 100644 --- a/src/coreav.cpp +++ b/src/coreav.cpp @@ -23,7 +23,6 @@ ToxCall Core::calls[TOXAV_MAX_CALLS]; const int Core::videobufsize{TOXAV_MAX_VIDEO_WIDTH * TOXAV_MAX_VIDEO_HEIGHT * 4}; uint8_t* Core::videobuf; -QThread* Core::audioThread{nullptr}; bool Core::anyActiveCalls() { @@ -590,22 +589,6 @@ VideoSource *Core::getVideoSourceFromCall(int callNumber) return &calls[callNumber].videoSource; } -void Core::playGroupAudio(Tox* /*tox*/, int groupnumber, int friendgroupnumber, const int16_t* out_audio, - unsigned out_audio_samples, uint8_t decoder_channels, unsigned audio_sample_rate, void* /*userdata*/) -{ - if (!groupCalls[groupnumber].active) - return; - - if (groupCalls[groupnumber].muteVol) - return; - - if (!groupCalls[groupnumber].alSources.contains(friendgroupnumber)) - alGenSources(1, &groupCalls[groupnumber].alSources[friendgroupnumber]); - - playAudioBuffer(groupCalls[groupnumber].alSources[friendgroupnumber], out_audio, - out_audio_samples, decoder_channels, audio_sample_rate); -} - void Core::joinGroupCall(int groupId) { qDebug() << QString("Core: Joining group call %1").arg(groupId); @@ -631,7 +614,6 @@ void Core::joinGroupCall(int groupId) groupCalls[groupId].sendAudioTimer->setSingleShot(true); connect(groupCalls[groupId].sendAudioTimer, &QTimer::timeout, [=](){sendGroupCallAudio(groupId,toxav);}); groupCalls[groupId].sendAudioTimer->start(); - groupCalls[groupId].sendAudioTimer->moveToThread(audioThread); } void Core::leaveGroupCall(int groupId) diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index 3e9aa8d38..238f769db 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -187,6 +187,7 @@ void Widget::init() qRegisterMetaType("vpx_image"); qRegisterMetaType("uint8_t"); qRegisterMetaType("uint16_t"); + qRegisterMetaType("const int16_t*"); qRegisterMetaType("int32_t"); qRegisterMetaType("int64_t"); qRegisterMetaType("QPixmap"); @@ -196,6 +197,7 @@ void Widget::init() QString profilePath = detectProfile(); coreThread = new QThread(this); + coreThread->setObjectName("qTox Core"); core = new Core(Camera::getInstance(), coreThread, profilePath); core->moveToThread(coreThread); connect(coreThread, &QThread::started, core, &Core::start); From c270166daa475cf96e494b6c84b705a4a2f73282 Mon Sep 17 00:00:00 2001 From: dubslow Date: Tue, 18 Nov 2014 21:47:52 -0600 Subject: [PATCH 145/253] fix #811 --- src/widget/tool/chatactions/systemmessageaction.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widget/tool/chatactions/systemmessageaction.cpp b/src/widget/tool/chatactions/systemmessageaction.cpp index 506370871..21b260201 100644 --- a/src/widget/tool/chatactions/systemmessageaction.cpp +++ b/src/widget/tool/chatactions/systemmessageaction.cpp @@ -25,5 +25,5 @@ SystemMessageAction::SystemMessageAction(const QString &message, const QString & QString SystemMessageAction::getMessage() { - return QString("
" + message + "
"); + return QString("
" + toHtmlChars(message) + "
"); } From 90e6ec63166a2a6af618c1787c799522ebb8d44b Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Wed, 19 Nov 2014 22:31:57 +0100 Subject: [PATCH 146/253] Cherry pick c459263d27531b7856457b13e9b556b2aeb21dae From f8063fc809a93c362944deaf0cffc46f6ca17fa8 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Thu, 20 Nov 2014 00:22:44 +0100 Subject: [PATCH 147/253] Fix idleTimer use before construction --- src/widget/widget.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index 238f769db..3caaecd44 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -115,6 +115,10 @@ void Widget::init() ui->statusbar->hide(); ui->menubar->hide(); + idleTimer = new QTimer(); + idleTimer->setSingleShot(true); + setIdleTimer(Settings::getInstance().getAutoAwayTime()); + //restore window state restoreGeometry(Settings::getInstance().getWindowGeometry()); restoreState(Settings::getInstance().getWindowState()); @@ -179,10 +183,6 @@ void Widget::init() Style::setThemeColor(Settings::getInstance().getThemeColor()); Style::applyTheme(); - idleTimer = new QTimer(); - idleTimer->setSingleShot(true); - setIdleTimer(Settings::getInstance().getAutoAwayTime()); - qRegisterMetaType("Status"); qRegisterMetaType("vpx_image"); qRegisterMetaType("uint8_t"); From 5d53d0eec2670bc513ffe068894bf0be0d29e1bf Mon Sep 17 00:00:00 2001 From: Sean Date: Wed, 19 Nov 2014 23:02:02 -0800 Subject: [PATCH 148/253] Add OS X updater Lovely tool I wrote to do an admin permissions prompt on OS X for every file in the update folder and install it, kills qtox and relaunches it automatically tool. Go master race --- osx/updater.go | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 osx/updater.go diff --git a/osx/updater.go b/osx/updater.go new file mode 100644 index 000000000..1ee6ddb46 --- /dev/null +++ b/osx/updater.go @@ -0,0 +1,96 @@ +package main + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "os/exec" + "os/user" +) + +func fs_type(path string) int { + //name := "FileOrDir" + f, err := os.Open(path) + if err != nil { + fmt.Println(err) + return -1 + } + defer f.Close() + fi, err := f.Stat() + if err != nil { + fmt.Println(err) + return -1 + } + switch mode := fi.Mode(); { + case mode.IsDir(): + return 0 + case mode.IsRegular(): + return 1 + } + + return -1 +} + +func main() { + usr, e := user.Current() + if e != nil { + log.Fatal(e) + } + + update_dir := usr.HomeDir + "/Library/Preferences/tox/update/" + if _, err := os.Stat(update_dir); os.IsNotExist(err) { + fmt.Println("Error: No update folder, is check for updates enabled?") + return + } + fmt.Println("qTox Updater") + + files, _ := ioutil.ReadDir(update_dir) + killqtox := exec.Command("/usr/bin/killall", "qtox") + _ = killqtox.Run() + + for _, file := range files { + if fs_type(update_dir+file.Name()) == 1 { + fmt.Print("Installing: ") + fmt.Println("/Applications/qtox.app/Contents/" + file.Name()) + if _, err := os.Stat("/Applications/qtox.app/Contents/" + file.Name()); os.IsNotExist(err) { + newfile := exec.Command("/usr/libexec/authopen", "-c", "-x", "-m", "drwxrwxr-x+", "/Applications/qtox.app/Contents/"+file.Name()) + newfile.Run() + } + + cat := exec.Command("/bin/cat", update_dir+file.Name()) + auth := exec.Command("/usr/libexec/authopen", "-w", "/Applications/qtox.app/Contents/"+file.Name()) + auth.Stdin, _ = cat.StdoutPipe() + auth.Stdout = os.Stdout + auth.Stderr = os.Stderr + _ = auth.Start() + _ = cat.Run() + _ = auth.Wait() + + } else { + files, _ := ioutil.ReadDir(update_dir + file.Name()) + for _, file2 := range files { + fmt.Print("Installing: ") + fmt.Println("/Applications/qtox.app/Contents/" + file.Name() + "/" + file2.Name()) + + if _, err := os.Stat("/Applications/qtox.app/Contents/" + file.Name() + "/" + file2.Name()); os.IsNotExist(err) { + newfile := exec.Command("/usr/libexec/authopen", "-c", "-x", "-m", "drwxrwxr-x+", "/Applications/qtox.app/Contents/"+file.Name()+"/"+file2.Name()) + newfile.Run() + } + + cat := exec.Command("/bin/cat", update_dir+file.Name()+"/"+file2.Name()) + auth := exec.Command("/usr/libexec/authopen", "-w", "/Applications/qtox.app/Contents/"+file.Name()+"/"+file2.Name()) + auth.Stdin, _ = cat.StdoutPipe() + auth.Stdout = os.Stdout + auth.Stderr = os.Stderr + _ = auth.Start() + _ = cat.Run() + _ = auth.Wait() + } + } + + } + os.RemoveAll(update_dir) + fmt.Println("Update metadata wiped, launching qTox") + launchqtox := exec.Command("/usr/bin/open", "-b", "im.tox.qtox") +} From 70c34124cf6ccd6204b7b10b5b605b3f5159f8e1 Mon Sep 17 00:00:00 2001 From: Sean Date: Wed, 19 Nov 2014 23:02:34 -0800 Subject: [PATCH 149/253] Not needed --- osx/postinstall | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 osx/postinstall diff --git a/osx/postinstall b/osx/postinstall deleted file mode 100644 index e1af30717..000000000 --- a/osx/postinstall +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -rm -rf ~/Library/Preferences/tox/update From d6a6e18c36b6cb4e6749baa6c43513a5af03e694 Mon Sep 17 00:00:00 2001 From: Sean Date: Wed, 19 Nov 2014 23:02:59 -0800 Subject: [PATCH 150/253] Also not needed --- osx/preinstall | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 osx/preinstall diff --git a/osx/preinstall b/osx/preinstall deleted file mode 100644 index e63de0b32..000000000 --- a/osx/preinstall +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -killall qtox From 6667b76bded561de5b68dd0523fcf335f22d7be0 Mon Sep 17 00:00:00 2001 From: Sean Date: Wed, 19 Nov 2014 23:05:23 -0800 Subject: [PATCH 151/253] Replace pkg installer with new updater --- src/autoupdate.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/autoupdate.cpp b/src/autoupdate.cpp index 50d9ed1ff..f8ffdf9c7 100644 --- a/src/autoupdate.cpp +++ b/src/autoupdate.cpp @@ -46,8 +46,7 @@ unsigned char AutoUpdater::key[crypto_sign_PUBLICKEYBYTES] = #elif defined(Q_OS_OSX) const QString AutoUpdater::platform = "osx"; -const QString AutoUpdater::updaterBin = "installer -pkg "+Settings::getInstance().getSettingsDirPath() - +"/update/qtox.pkg -target CurrentUserHomeDirectory"; +const QString AutoUpdater::updaterBin = "/Applications/qtox.app/Contents/MacOS/updater"; const QString AutoUpdater::updateServer = "https://dist-build.tox.im"; unsigned char AutoUpdater::key[crypto_sign_PUBLICKEYBYTES] = From 6797ebf0dcb4540ee8ecfe1bee4754c4725857d4 Mon Sep 17 00:00:00 2001 From: Sean Date: Wed, 19 Nov 2014 23:11:12 -0800 Subject: [PATCH 152/253] Gotta automatically launch qTox too --- osx/updater.go | 1 + 1 file changed, 1 insertion(+) diff --git a/osx/updater.go b/osx/updater.go index 1ee6ddb46..45232536f 100644 --- a/osx/updater.go +++ b/osx/updater.go @@ -93,4 +93,5 @@ func main() { os.RemoveAll(update_dir) fmt.Println("Update metadata wiped, launching qTox") launchqtox := exec.Command("/usr/bin/open", "-b", "im.tox.qtox") + launchqtox.Run() } From 0412f4d1a8df0d74de72d25df764393d3bdc37c2 Mon Sep 17 00:00:00 2001 From: JoGitqTox Date: Fri, 21 Nov 2014 16:07:38 +0100 Subject: [PATCH 153/253] Update de.ts New translations --- translations/de.ts | 1361 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 1048 insertions(+), 313 deletions(-) diff --git a/translations/de.ts b/translations/de.ts index b651b13a6..49618acf5 100644 --- a/translations/de.ts +++ b/translations/de.ts @@ -4,67 +4,67 @@ AVForm - - Audio/Video settings - Audio/Video Einstellungen + + Audio/Video + Audio/Video AVSettings - - Form - - - - - Volume Settings (Stubs) - Lautstärke-Einstellungen - - - + Playback - + Wiedergabe - + Microphone Mikrofon - - Video settings - Video-Einstellungen + + Audio Settings + Audio Einstellungen - - Modes - Modi + + Playback device + Wiedergabegerät - + + Capture device + Aufnahmegerät + + + + Video Settings + Video Einstellungen + + + + Resolution + Auflösung + + + Hue - Farbe + Farbton - + Brightness - Helligkeit + Helligkeit - + Saturation - Sättigung + Sättigung - + Contrast - Kontrast - - - - Preview - Vorschau + Kontrast @@ -89,62 +89,114 @@ Send friend request - Freundschaftseinladung versenden + Freundschaftsanfrage versenden Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! - Lass uns toxen! + Lass uns Toxen! - + Please fill in a valid Tox ID Tox ID of the friend you're sending a friend request to - Bitte gib eine gültige Tox ID ein + Geben Sie bitte eine gültige Tox ID ein - + You can't add yourself as a friend! When trying to add your own Tox ID as friend - Es ist nicht möglich, sich selbst als Freund hinzuzufügen + Du kannst dich nicht selbst als Freund hinzufügen! - - This address does not exist - The DNS gives the Tox ID associated to toxme.se addresses - Diese Adresse existiert nicht. + + qTox needs to use the Tox DNS, but can't do it through a proxy. +Ignore the proxy and connect to the Internet directly ? + qTox muss die Tox DNS nutzen. Dies klappt nicht über einen Proxy. +Ignoriere diesen und nutze eine direkte Internetverbindung? + + + + This Tox ID does not exist + DNS error + Tox ID existiert nicht - Error while looking up DNS The DNS gives the Tox ID associated to toxme.se addresses - Fehler beim Auflösen des DNS + Fehler beim Auflösen des DNS - Unexpected number of text records Error with the DNS - Unerwartete Anzahl von Texteinträgen + Unererwartete Anzahl von Texteinträgen - Unexpected number of values in text record Error with the DNS - Unerwartete Anzahl von Werten innerhalb des Texteintrages + Unerwartete Anzahl von Werten innerhalb des Texteintrages - The DNS lookup does not contain any Tox ID Error with the DNS - Der DNS Eintrag enthält keine gültige TOX ID + Der DNS Eintrag enthält keine gültige TOX ID - - The DNS lookup does not contain a valid Tox ID Error with the DNS - Der DNS Eintrag enthält keine gültige TOX ID + Der DNS Eintrag enthält keine gültige TOX ID + + + + AdvancedForm + + + Advanced + Fortgeschritten + + + + FULL - very safe, slowest (recommended) + VOLL - sehr sicher, langsam (empfohlen) + + + + NORMAL - almost as safe as FULL, about 20% faster than FULL + NORMAL - fast so sicher wie VOLL, ca. 20% schneller + + + + OFF - disables all safety, when something goes wrong your history may be lost, fastest (not recommended) + AUS - keinerlei Sicherheit, geht etwas schief kann die Historie verloren gehen, ist am schnellsten (nicht empfohlen) + + + + AdvancedSettings + + + Form + + + + + <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">IMPORTANT NOTE</span></p><p><span style=" color:#ff0000;">Unless you </span><span style=" font-weight:600; color:#ff0000;">really</span><span style=" color:#ff0000;"> know what you are doing, please do </span><span style=" font-weight:600; color:#ff0000;">not</span><span style=" color:#ff0000;"> change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history.</span></p></body></html> + <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">WICHTIGE NOTIZ</span></p><p><span style=" color:#ff0000;">Wenn Sie nicht </span><span style=" font-weight:600; color:#ff0000;">wirklich</span><span style=" color:#ff0000;"> wissen, was Sie tun, ändern Sie hier bitte </span><span style=" font-weight:600; color:#ff0000;">nichts</span><span style=" color:#ff0000;">. Hier getätigte Änderungen können zu Problemen mit qTox führen, evtl. sogar zum Datenverlust, z.B. die Historie.</span></p></body></html> + + + + Reset to default settings + Grundeinstellungen wiederherstellen + + + + History + Historie + + + + <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_synchronous"><span style=" text-decoration: underline; color:#0000ff;">Synchronous writing to DB</span></a></p></body></html> + Synchrones Schreiben in die DB @@ -161,15 +213,68 @@ ChatForm - + + Load History... + Historie laden... + + + Send a file Datei versenden + + + Bad Idea + Schlechte Idee + + + + You're trying to send a special (sequential) file, that's not going to work! + Sie versuchen eine spezielle (sequentielle) Datei zu senden, das funktioniert nicht! + + + + %1 calling + %1 wird angerufen + + + + %1 stopped calling + %1 stoppte den Anruf + + + + Calling to %1 + Rufe %1 an + + + + Call rejected + Anruf abgewiesen + + + + Call with %1 ended. %2 + Anruf zu %1 beendet. %2 + + + + Call duration: + Anrufdauer: + Save chat log Chatverlauf speichern + + ChatTextEdit + + + Type your message here... + Nachricht hier eingeben... + + CopyableElideLabel @@ -180,36 +285,132 @@ Core - - Encrypted profile - + + Toxing on qTox + Toxen mit qTox - - Your tox profile seems to be encrypted, qTox can't open it -Do you want to erase this profile ? - + + qTox User + qTox Benutzer + + + + Friend is already added + Freund wurde schon hinzugefügt + + + + Encryption error + Verschlüsselungsfehler + + + + The .tox file is encrypted, but encryption was not checked, continuing regardless. + Die .tox Datei ist verschlüsselt, aber die Verschlüsselung wurde nicht geprüft, Vorgang wird trotzdem fortgesetzt. + + + + Tox datafile decryption password + Entschlüsselungspasswort für Tox Datendatei + + + + + + Password error + Passwortfehler + + + + + Failed to setup password. +Empty password. + Fehler beim Setzen des Passwortes. +Leeres Passwort. + + + + Try Again + Nochmal versuchen + + + + Change profile + Profil ändern + + + + Reinit current profile + Aktuelles Profil neu starten + + + + Wrong password has been entered + Falsches Passwort eingegeben + + + + History Log decryption password + Passwort zur Entschlüsselung der Historie + + + + Encrypted log + Verschlüsselte Logdatei + + + + Your history is encrypted with different password +Do you want to try another password? + Ihre Historie wurde mit einem anderen Passwort verschlüsselt +Wollen Sie ein anderes probieren? + + + + Loggin + Anmeldung + + + + Due to incorret password logging will be disabled + Falsches Passwort, Anmeldung wird beendet + + + + NO Password + KEIN Passwort + + + + Will be saved without encryption! + Wird ohne Verschlüsselung gespeichert! FileTransferInstance - + Save a file Title of the file saving dialog - Datei speichern + Datei speichern - + Location not writable Title of permissions popup - + Ort schreibgeschützt - + You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup - + Sie haben keine Erlaubnis, die Datei in diesen Ort zu speichern. Wählen Sie einen anderen Ort oder beenden Sie den Dialog. + + + + ETA + ETA @@ -226,17 +427,17 @@ Do you want to erase this profile ? Transfered Files "Headline" of the window - + Übertragene Dateien Downloads - + Runtergeladen Uploads - + Hochgeladen @@ -250,7 +451,7 @@ Do you want to erase this profile ? Someone wants to make friends with you - Es möchte jemand mit dir befreundet sein + Es möchte jemand mit ihnen befreundet sein @@ -260,13 +461,13 @@ Do you want to erase this profile ? Friend request message: - Nachricht + Freundschaftsanfrage: Accept Accept a friend request - Akzeptieren + Annehmen @@ -278,19 +479,51 @@ Do you want to erase this profile ? FriendWidget - + + Invite to group + Menu to invite a friend to a groupchat + In Gruppe einladen + + + Copy friend ID Menu to copy the Tox ID of that friend Tox ID kopieren - - Invite in group - Menu to invite a friend in a groupchat - In Gruppe einladen + + Set alias... + Alias setzen... - + + Auto accept files from this friend + context menu entry + Dateien von diesem Freund automatisch annehmen + + + + Choose an auto accept directory + popup title + Wähle ein Verzeichnis für die automatische Dateiannahme + + + + User alias + Benutzeralias + + + + Alias: + Alias: + + + Invite in group + Menu to invite a friend in a groupchat + In Gruppe einladen + + + Remove friend Menu to remove the friend from our friendlist Freund entfernen @@ -299,9 +532,33 @@ Do you want to erase this profile ? GeneralForm - - General Settings - Allgemeine Einstellungen + + General + Allgemein + + + + + None + Keine + + + + Choose an auto accept directory + popup title + Wählen Sie ein Verzeichnis + + + + Call active + popup title + Anwahl aktiviert + + + + You can't disconnect while a call is active! + popup text + Abbruch während der Anwahl nicht möglich! @@ -315,164 +572,265 @@ Do you want to erase this profile ? GeneralSettings - - Form - - - - + General Settings - Allgemeine Einstellungen + Allgemeine Einstellungen - - Use translations - Text on a checkbox to enable translations - Übersetzungen + + + The translation may not load until qTox restarts. + Änderung wird erst nach Neustart aktiv. - + + Translation + Sprache + + + Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox - + Speichert die Einstellungen im Arbeits- statt im normalen Konfigurationsverzeichnis - + Make Tox portable - + Macht Tox portabel - + + Show system tray + Im Systemtray zeigen + + + Start in tray - + Ins Tray starten - + + Close to tray + Ins Tray schließen + + + + Minimize to tray + Ins Tray minimieren + + + Show contacts' status changes + Zeigt Statusänderungen der Kontakte + + + + Check for updates on startup (unstable) + Prüft beim Start auf Updates (nicht stabil) + + + + Focus qTox when a message is received + Bringt qTox in den Vordergrund, wenn eine Nachricht eintrifft + + + + Faux offline messaging - + Provided in minutes - + Bereitgestellt in Minuten - - Auto away after (0 to disable): - + + Auto away after (0 to disable) + Automatisch abwesend nach (0 deaktiviert) - - minutes - Minuten + + Set to 0 to disable + Zum Deaktivieren auf 0 setzen - - Theme - + + Use emoticons + Smileys benutzen - - Smiley Pack: + + Smiley Pack Text on smiley pack label - + Smiley Paket - - :) - + + Style + Stil - - ;) - + + Theme color + Farbe - - :p - + + Emoticon size + Smiley Größe - - :O - + + px + Pixel - - :'( - + + Timestamp format + Zeitformat - - Style: - Style: + + Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. + force tcp checkbox tooltip + Wenn deaktiviert, kann z.B. über Tor getoxt werden. Dies belastet das Tox Netzwerk zusätzlich und sollte nur deaktiviert werden wenn notwendig. - + + Enable UDP (recommended) + Text on checkbox to disable UDP + UDP aktivieren (empfohlen) + + + + Reconnect + reconnect button + Erneut verbinden + + + + minutes + Minuten + + + + Autoaccept files + Dateien automatisch annehmen + + + + Save files in + Speichern unter + + + + PushButton + Schaltfläche + + + + Theme + Benutzeroberfläche + + + Connection Settings - Verbindungs-Einstellungen + Verbindungseinstellungen - + Enable IPv6 (recommended) Text on a checkbox to enable IPv6 - IPv6 aktivieren (empfohlen) + IPv6 aktivieren (empfohlen) - - This allows, e.g., toxing over Tor. It adds load to the Tox network however, so use only when necessary. - force tcp checkbox tooltip - - - - - Disable UDP (not recommended) - Text on checkbox to disable UDP - - - - + Use proxy (SOCKS5) - Proxy verwenden (SOCKS5) + Proy benutzen (SOCKS5) - + Address Text on proxy addr label - Adresse + Adresse - + Port Text on proxy port label - Port + Port GenericChatForm - - + + Send message + Nachricht senden + + + + Smileys + Smileys + + + + Send file(s) + Datei(en) senden + + + + Audio call: RED means you're on a call + Audio Anruf: ROT bedeutet verbunden + + + + Video call: RED means you're on a call + Video Anruf: ROT bedeutet verbunden + + + + Toggle speakers volume: RED is OFF + Schaltet den Lautsprecher ein/aus: ROT ist AUS + + + + Toggle microphone: RED is OFF + Schaltet das Mikrofon ein/aus: ROT ist AUS + + + + Save chat log - Chatverlauf speichern + Chatverlauf speichern + + + + Clear displayed messages + Angezeigte Nachrichten ausblenden + + + + Cleared + Ausgeblendet GroupChatForm - + %1 users in chat Number of users in chat - %1 Personen im Chat + %1 User im Chat <Unknown> <Unbekannt> - + %1 users in chat - %1 Personen im Chat + %1 User im Chat Save chat log @@ -482,19 +840,19 @@ Do you want to erase this profile ? GroupWidget - - + + %1 users in chat - %1 Personen im Chat + %1 User im Chat - - + + 0 users in chat - 0 Personen im Chat + kein User im Chat - + Quit group Menu to quit a groupchat Gruppe verlassen @@ -503,87 +861,131 @@ Do you want to erase this profile ? IdentityForm - - Your identity - + + Identity + Identität - + Call active popup title - + Anruf aktiv - + You can't switch profiles while a call is active! popup text - Du kannst dein Profil nicht wechseln, solange ein Anruf läuft. + Profil kann während eines Anrufes nicht gewechselt werden! - + Rename "%1" renaming a profile - + "%1" umbenennen - + + Profile already exists + rename confirm title + Profil existiert bereits + + + + A profile named "%1" already exists. Do you want to erase it? + rename confirm text + Ein Profil mit dem Namen "%1" existiert bereits. Wollen Sie es löschen? + + + Export profile save dialog title - + Profil exportieren - + Tox save file (*.tox) save dialog filter - + Toxdatei speichern (*.tox) - + + Failed to remove file + Datei konnte nicht entfernt werden + + + + The file you chose to overwrite could not be removed first. + Die gewählte Datei kann nicht überschrieben werden. + + + + Failed to copy file + Datei konnte nicht kopiert werden + + + + The file you chose could not be written to. + Die gewählte Datei kann nicht überschrieben werden. + + + Profile currently loaded current profile deletion warning title - + Profil ist zurzeit geladen - + This profile is currently in use. Please load a different profile before deleting this one. current profile deletion warning text - Dieses Profil wird gerade verwendet. Bitte lade ein anderes Profil, um dieses löschen zu können. + Dieses Profil ist aktuell in Gebrauch. Bitte laden Sie vor dem Löschen ein anderes Profil. - + Deletion imminent! deletion confirmation title - + Löschen steht an! - + Are you sure you want to delete this profile? deletion confirmation text - Bist du sicher, dieses Profil löschen zu wollen? + Sind Sie sicher, dass dasProfil gelöscht werden soll? - + Import profile import dialog title - Profil importieren + Profil importieren - + Tox save file (*.tox) import dialog filter - + Toxdatei speichern (*.tox) - + Ignoring non-Tox file popup title - + Keine Toxdatei, wird ignoriert - + Warning: you've chosen a file that is not a Tox save file; ignoring. popup text - + Warnung: Sie haben eine Datei gewählt, die keine Toxdatei ist, wird ignoriert. + + + + Profile already exists + import confirm title + Profil existiert bereits + + + + A profile named "%1" already exists. Do you want to erase it? + import confirm text + Ein Profil mit dem Namen "%1" existiert bereits. Wollen Sie es löschen? @@ -591,151 +993,328 @@ Do you want to erase this profile ? Name Username/nick - Benutzername + Benutzername Status Status message - Status + Status Tox ID - Tox ID + Tox ID IdentitySettings - - Form - - - - + Public Information - + Öffentliche Information - + Name - Benutzername + Benutzername - + Status - Status + Status - + Tox ID - Tox ID + Tox ID - + Your Tox ID (click to copy) - Deine TOX-ID (klicken um zu kopieren) + Ihre Tox ID (klicken zum Kopieren) - + Profiles - Profile: + Profile - + Available profiles: - Verfügbare Profile: - - - - Load - load profile button - Laden - - - - Rename - rename profile button - - - - - Export - export profile button - Exportieren - - - - Delete - delete profile button - Löschen - - - - This is useful to remain safe on public computers - delete profile button tooltip - Dies ist nützlich, um die Sicherheit an öffentlichen Computern zu erhöhen. + Verfügbare Profile: + + Switching profiles is disabled during calls + tooltip + Profilwechsel ist während eines Anrufes deaktiviert + + + + Load + load profile button + Laden + + + + Rename + rename profile button + Umbenennen + + + + Export + export profile button + Exportieren + + + + Delete + delete profile button + Löschen + + + + New Tox ID + new profile button + Neue Tox ID + + + + This is useful to remain safe on public computers + delete profile button tooltip + Dies ist nützlich, um auf öffentlichen Computern sicher zu sein + + + Import a profile import profile button - Profil importieren + Importieren + + + + InputPasswordDialog + + + Password Dialog + Passwort + + + + Input password: + Passwort eingeben: + + + + LoadHistoryDialog + + + Load History Dialog + Historie laden + + + + Load history from: + Lade die Historie vom: MainWindow - - - qTox - qTox - Your name - Dein Name + Ihr Name Your status - Dein Status + Ihr Status - + Add friends - Freunde hinzufügen + Freund hinzufügen - + Create a group chat - Gruppenchat eröffnen + Gruppenchat erstellen - + View completed file transfers - Abgeschlossene Dateiübertragungen anzeigen + Alle Dateiübertragungen anzeigen - + Change your settings - Einstellungen bearbeiten + Einstellungen ändern - + Close - Schließen + Schließen - Ctrl+Q - Strg+Q + Strg+Q + + + + NetCamView + + + Tox video + Tox Video PrivacyForm - - Privacy settings - Privatsphäre-Einstellungen + + Privacy + Datenschutz + + + + Encrypted log + Verschlüsselte Logdatei + + + + You already have history log file encrypted with different password +Do you want to delete old history file? + Es gibt schon eine Historie Logdatei mit einem anderen Passwort +Soll die alte Historiedatei gelöscht werden? + + + + PrivacySettings + + + Typing Notification + Tippen anzeigen + + + + Keep History (unstable) + Historie behalten (instabil) + + + + Encryption + Verschlüsselung + + + + Encrypt Tox datafile + Tox Datendatei verschlüsseln + + + + Encrypt History + Historie verschlüsseln + + + + Nospam + Spam-Schutz + + + + HHHHHHHH + HHHHHHHH + + + + Generate random nospam + Zufälligen Spam-Schutz erzeugen + + + + QObject + + + Update + The title of a message box + Update + + + + An update is available, do you want to download it now ? +It will be installed when qTox restarts. + Ein Update steht zur Verfügung, soll es runtergeladen werden? +Es wird beim Neustart von qTox installiert. + + + + Tox URI to parse + Tox URI parsen + + + + Default + Default + + + + Blue + Blau + + + + Olive + Oliv + + + + Red + Rot + + + + Violet + Violett + + + + Ignoring non-Tox file + popup title + Keine Toxdatei, wird ignoriert + + + + Warning: you've chosen a file that is not a Tox save file; ignoring. + popup text + Warnung: Sie haben eine Datei gewählt, die keine Toxdatei ist, wird ignoriert. + + + + Profile already exists + import confirm title + Profil existiert bereits + + + + A profile named "%1" already exists. Do you want to erase it? + import confirm text + Ein Profil mit dem Namen "%1" existiert bereits. Wollen Sie es löschen? + + + + Profile imported + Profil importiert + + + + %1.tox was successfully imported + %1.tox wurde erfolgreich importiert + + + + Tox me maybe? + Default message in Tox URI friend requests. Write something appropriate! + Lass uns Toxen! @@ -746,6 +1325,19 @@ Do you want to erase this profile ? Tox Video testen + + SetPasswordDialog + + + Type Password + Passwort eingeben + + + + Repeat Password + Passworteingabe wiederholen + + SettingsForm @@ -766,7 +1358,7 @@ Do you want to erase this profile ? Test video Text on a button to test the video/webcam - Video testen + Testvideo Enable IPv6 (recommended) @@ -774,6 +1366,94 @@ Do you want to erase this profile ? IPv6 aktivieren (empfohlen) + + ToxDNS + + + The connection timed out + The DNS gives the Tox ID associated to toxme.se addresses + Zeitlimit der Verbindung überschritten + + + + This address does not exist + The DNS gives the Tox ID associated to toxme.se addresses + Diese Adresse existiert nicht + + + + Error while looking up DNS + The DNS gives the Tox ID associated to toxme.se addresses + Fehler beim Auflösen des DNS + + + + No text record found + Error with the DNS + Keine Textaufzeichnung gefunden + + + + Unexpected number of values in text record + Error with the DNS + Unerwartete Anzahl von Werten im Texteintrag + + + + The version of Tox DNS used by this server is not supported + Error with the DNS + Die Tox DNS Version dieses Servers wird nicht unterstützt + + + + The DNS lookup does not contain any Tox ID + Error with the DNS + Der DNS Eintrag enthält keine TOX ID + + + + + The DNS lookup does not contain a valid Tox ID + Error with the DNS + Der DNS Eintrag enthält keine gültige TOX ID + + + + ToxURIDialog + + + Add a friend + Title of the window to add a friend through Tox URI + Einen Freund hinzufügen + + + + Do you want to add %1 as a friend ? + Wollen Sie %1 als Freund hinzufügen? + + + + User ID: + Benutzer ID: + + + + Friend request message: + Freundschaftsanfrage: + + + + Send + Send a friend request + Senden + + + + Cancel + Don't send a friend request + Abbrechen + + Widget @@ -797,106 +1477,161 @@ Do you want to erase this profile ? Strg+Q - + + Online + Online + + + + Away + Abwesend + + + + Busy + Beschäftigt + + + + &Quit + &Beenden + + + + Change status to: + Ändere den Status zu: + + + Online Button to set your status to 'Online' - Online + Online - + Away Button to set your status to 'Away' - Abwesend + Abwesend - + Busy Button to set your status to 'Busy' - Beschäftigt + Beschäftigt - + Choose a profile - + Wähle ein Profil - + Please choose which identity to use - Bitte wähle, welche Identität du benutzen möchtest + Wähle die Identität, die benutzt werden soll - + Choose a profile picture - Wähle ein Profilbild + Wähle ein Profilbild - - - + + + Error - Fehler + Fehler - + Unable to open this file - Diese Datei kann nicht geöffnet werden. + Kann diese Datei nicht öffnen - + Unable to read this image - + Kann dieses Bild nicht einlesen - + This image is too big - Das Bild ist zu groß. + Dieses Bild ist zu groß - + Toxcore failed to start, the application will terminate after you close this message. - Tox konnte nicht gestartet werden. Die Anwendung wird geschlossen, sobald du dieses Fenster schließt. + Tox startet nicht, die Anwendung wird nach Schließen dieses Fensters beendet. - + toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text - Tox konnte mit deinen Proxy-Einstellungen nicht gestartet werden. Bitte bearbeite deine Einstellungen und starte Tox anschließend neu. + Tox startet mit ihren Proxy-Einstellungen nicht. qTox funktioniert nicht, ändern Sie bitte ihre Einstellungen und starten qTox neu. - + + Add friend + Freund hinzufügen + + + + File transfers + Dateiübertragungen + + + + Settings + Einstellungen + + + + Couldn't request friendship + Freundschaftsanfrage fehlgeschlagen + + + away contact status - + abwesend - + busy contact status beschäftigt - + offline contact status - offline + Offline - + online contact status - online + Online - + %1 is now %2 e.g. "Dubslow is now online" - + %1 ist nun %2 - + <Unknown> Placeholder when we don't know someone's name in a group chat - <Unbekannt> + <Unbekannt> + + + + %1 has set the title to %2 + %1 hat den Titel auf %2 gesetzt + + + + Message failed to send + Senden der Nachricht fehlgeschlagen From 64b3cdf139c1ae8ff1e2c32d796997fa7566157f Mon Sep 17 00:00:00 2001 From: nachfuellbar Date: Fri, 21 Nov 2014 20:23:31 +0100 Subject: [PATCH 154/253] Update generalform.cpp Fixed typo --- src/widget/form/settings/generalform.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widget/form/settings/generalform.cpp b/src/widget/form/settings/generalform.cpp index 0d513d165..7cae0847f 100644 --- a/src/widget/form/settings/generalform.cpp +++ b/src/widget/form/settings/generalform.cpp @@ -31,7 +31,7 @@ #include "src/autoupdate.h" static QStringList locales = {"bg", "de", "en", "fr", "it", "mannol", "pirate", "pl", "ru", "fi", "sv", "uk"}; -static QStringList langs = {"Български", "Deustch", "English", "Français", "Italiano", "mannol", "Pirate", "Polski", "Русский", "Suomi", "Svenska", "Українська"}; +static QStringList langs = {"Български", "Deutsch", "English", "Français", "Italiano", "mannol", "Pirate", "Polski", "Русский", "Suomi", "Svenska", "Українська"}; static QStringList timeFormats = {"hh:mm AP", "hh:mm", "hh:mm:ss AP", "hh:mm:ss"}; From 084c8b67370f89c22fbe5b5c12d9377b68ab233c Mon Sep 17 00:00:00 2001 From: JoGitqTox Date: Sat, 22 Nov 2014 10:27:29 +0100 Subject: [PATCH 155/253] Update de.ts Corrections (some of them according to the comments from 'nachfuellbar'). The vanished translation too, may be reactivating them. --- translations/de.ts | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/translations/de.ts b/translations/de.ts index 49618acf5..047fc414d 100644 --- a/translations/de.ts +++ b/translations/de.ts @@ -95,7 +95,7 @@ Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! - Lass uns Toxen! + Lass uns toxen! @@ -107,14 +107,14 @@ You can't add yourself as a friend! When trying to add your own Tox ID as friend - Du kannst dich nicht selbst als Freund hinzufügen! + Sie können sich nicht selbst als Freund hinzufügen! qTox needs to use the Tox DNS, but can't do it through a proxy. Ignore the proxy and connect to the Internet directly ? qTox muss die Tox DNS nutzen. Dies klappt nicht über einen Proxy. -Ignoriere diesen und nutze eine direkte Internetverbindung? +Ignoriere Sie diesen und nutzen Sie eine direkte Internetverbindung. @@ -347,7 +347,7 @@ Leeres Passwort. Wrong password has been entered - Falsches Passwort eingegeben + Es wurde ein falsches Passwort eingegeben @@ -451,7 +451,7 @@ Wollen Sie ein anderes probieren? Someone wants to make friends with you - Es möchte jemand mit ihnen befreundet sein + Es möchte jemand mit Ihnen befreundet sein @@ -636,7 +636,7 @@ Wollen Sie ein anderes probieren? Faux offline messaging - + @@ -656,7 +656,7 @@ Wollen Sie ein anderes probieren? Use emoticons - Smileys benutzen + Emoticons benutzen @@ -949,7 +949,7 @@ Wollen Sie ein anderes probieren? Are you sure you want to delete this profile? deletion confirmation text - Sind Sie sicher, dass dasProfil gelöscht werden soll? + Sind Sie sicher, dass dieses Profil gelöscht werden soll? @@ -973,7 +973,7 @@ Wollen Sie ein anderes probieren? Warning: you've chosen a file that is not a Tox save file; ignoring. popup text - Warnung: Sie haben eine Datei gewählt, die keine Toxdatei ist, wird ignoriert. + Warnung: Sie haben eine Datei gewählt, die keine Toxdatei ist, wird ignoriert. @@ -1184,7 +1184,7 @@ Wollen Sie ein anderes probieren? You already have history log file encrypted with different password Do you want to delete old history file? - Es gibt schon eine Historie Logdatei mit einem anderen Passwort + Es gibt schon eine Historie Logdatei mit einem anderen Passwort. Soll die alte Historiedatei gelöscht werden? @@ -1286,7 +1286,7 @@ Es wird beim Neustart von qTox installiert. Warning: you've chosen a file that is not a Tox save file; ignoring. popup text - Warnung: Sie haben eine Datei gewählt, die keine Toxdatei ist, wird ignoriert. + Warnung: Sie haben eine Datei gewählt, die keine Toxdatei ist, wird ignoriert. @@ -1314,7 +1314,7 @@ Es wird beim Neustart von qTox installiert. Tox me maybe? Default message in Tox URI friend requests. Write something appropriate! - Lass uns Toxen! + Lass uns toxen! @@ -1358,7 +1358,7 @@ Es wird beim Neustart von qTox installiert. Test video Text on a button to test the video/webcam - Testvideo + Video testen Enable IPv6 (recommended) @@ -1462,11 +1462,11 @@ Es wird beim Neustart von qTox installiert. Your name - Dein Name + Ihr Name Your status - Dein Status + Ihr Status Close @@ -1499,7 +1499,7 @@ Es wird beim Neustart von qTox installiert. Change status to: - Ändere den Status zu: + Ändern Sie den Status zu: @@ -1522,12 +1522,12 @@ Es wird beim Neustart von qTox installiert. Choose a profile - Wähle ein Profil + Wählen Sie ein Profil Please choose which identity to use - Wähle die Identität, die benutzt werden soll + Wählen Sie die Identität, die benutzt werden soll @@ -1565,7 +1565,7 @@ Es wird beim Neustart von qTox installiert. toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text - Tox startet mit ihren Proxy-Einstellungen nicht. qTox funktioniert nicht, ändern Sie bitte ihre Einstellungen und starten qTox neu. + Tox startet mit ihren Proxy-Einstellungen nicht. qTox funktioniert nicht, ändern Sie bitte Ihre Einstellungen und starten qTox neu. @@ -1591,13 +1591,13 @@ Es wird beim Neustart von qTox installiert. away contact status - abwesend + Abwesend busy contact status - beschäftigt + Beschäftigt From 03cfab8c5044015077806bd39ea3506db68ba0b9 Mon Sep 17 00:00:00 2001 From: Maximilian Date: Sun, 23 Nov 2014 14:44:12 +0100 Subject: [PATCH 156/253] prevents division by 0 in updater --- updater/widget.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/updater/widget.cpp b/updater/widget.cpp index 8cfe0b58a..940af6c33 100644 --- a/updater/widget.cpp +++ b/updater/widget.cpp @@ -118,6 +118,11 @@ void Widget::update() for (UpdateFileMeta fileMeta : diff) if (!QFile::exists(updateDirStr+fileMeta.installpath)) fatalError(tr("The update is incomplete.")); + + if (diff.size() == 0){ + fatalError(tr("The diff list is empty.")); + } + setProgress(5); /// 2. Check the update (5-50%) From 92c330b7bd1a5c0915683869a1f6b1f49f678a76 Mon Sep 17 00:00:00 2001 From: apprb Date: Thu, 20 Nov 2014 18:13:13 +0900 Subject: [PATCH 157/253] GroupList refactoring --- src/friendlist.cpp | 1 + src/friendlist.h | 1 - src/grouplist.cpp | 44 +++++++++++++++++++++++++++---------- src/grouplist.h | 11 +++++----- src/widget/friendwidget.cpp | 2 +- src/widget/widget.cpp | 10 ++++----- 6 files changed, 46 insertions(+), 23 deletions(-) diff --git a/src/friendlist.cpp b/src/friendlist.cpp index d75da5796..4429215e5 100644 --- a/src/friendlist.cpp +++ b/src/friendlist.cpp @@ -61,6 +61,7 @@ void FriendList::clear() { for (auto friendptr : friendList) delete friendptr; + friendList.clear(); } Friend* FriendList::findFriend(const ToxID& userId) diff --git a/src/friendlist.h b/src/friendlist.h index 6d41d44ce..9e6807197 100644 --- a/src/friendlist.h +++ b/src/friendlist.h @@ -26,7 +26,6 @@ struct ToxID; class FriendList { public: - FriendList(); static Friend* addFriend(int friendId, const ToxID &userId); static Friend* findFriend(int friendId); static Friend* findFriend(const ToxID &userId); diff --git a/src/grouplist.cpp b/src/grouplist.cpp index 72b7cf872..52ea2f66f 100644 --- a/src/grouplist.cpp +++ b/src/grouplist.cpp @@ -16,32 +16,54 @@ #include "grouplist.h" #include "group.h" +#include +#include -QList GroupList::groupList; +QHash GroupList::groupList; Group* GroupList::addGroup(int groupId, const QString& name, bool isAvGroupchat) { + auto checker = groupList.find(groupId); + if (checker != groupList.end()) + qWarning() << "GroupList::addGroup: groupId already taken"; + Group* newGroup = new Group(groupId, name, isAvGroupchat); - groupList.append(newGroup); + groupList[groupId] = newGroup; + return newGroup; } Group* GroupList::findGroup(int groupId) { - for (Group* g : groupList) - if (g->groupId == groupId) - return g; + auto g_it = groupList.find(groupId); + if (g_it != groupList.end()) + return *g_it; + return nullptr; } void GroupList::removeGroup(int groupId, bool /*fake*/) { - for (int i=0; igroupId == groupId) - { - groupList.removeAt(i); - return; - } + groupList.erase(g_it); } } + +QList GroupList::getAllGroups() +{ + QList res; + + for (auto it : groupList) + res.append(it); + + return res; +} + +void GroupList::clear() +{ + for (auto groupptr : groupList) + delete groupptr; + groupList.clear(); +} diff --git a/src/grouplist.h b/src/grouplist.h index 36d8281f7..39fd66508 100644 --- a/src/grouplist.h +++ b/src/grouplist.h @@ -17,21 +17,22 @@ #ifndef GROUPLIST_H #define GROUPLIST_H -template -class QList; +template class QHash; +template class QList; class Group; class QString; class GroupList { public: - GroupList(); static Group* addGroup(int groupId, const QString& name, bool isAvGroupchat); static Group* findGroup(int groupId); static void removeGroup(int groupId, bool fake = false); + static QList getAllGroups(); + static void clear(); -public: - static QList groupList; +private: + static QHash groupList; }; #endif // GROUPLIST_H diff --git a/src/widget/friendwidget.cpp b/src/widget/friendwidget.cpp index 7d675a8c6..e06568078 100644 --- a/src/widget/friendwidget.cpp +++ b/src/widget/friendwidget.cpp @@ -56,7 +56,7 @@ void FriendWidget::contextMenuEvent(QContextMenuEvent * event) QAction* copyId = menu.addAction(tr("Copy friend ID","Menu to copy the Tox ID of that friend")); QMap groupActions; - for (Group* group : GroupList::groupList) + for (Group* group : GroupList::getAllGroups()) { QAction* groupAction = inviteMenu->addAction(group->widget->getName()); groupActions[groupAction] = group; diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index 3caaecd44..cc3afb3ce 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -297,9 +297,7 @@ Widget::~Widget() delete filesForm; FriendList::clear(); - for (Group* g : GroupList::groupList) - delete g; - GroupList::groupList.clear(); + GroupList::clear(); delete trayMenu; delete ui; delete translator; @@ -878,7 +876,9 @@ void Widget::clearContactsList() QList friends = FriendList::getAllFriends(); for (Friend* f : friends) removeFriend(f, true); - for (Group* g : GroupList::groupList) + + QList groups = GroupList::getAllGroups(); + for (Group* g : groups) removeGroup(g, true); } @@ -1213,6 +1213,6 @@ void Widget::reloadTheme() for (Friend* f : FriendList::getAllFriends()) f->getFriendWidget()->reloadTheme(); - for (Group* g : GroupList::groupList) + for (Group* g : GroupList::getAllGroups()) g->widget->reloadTheme(); } From 85718fa6375a959e955e81c1c40ebbc7702a82bb Mon Sep 17 00:00:00 2001 From: apprb Date: Sun, 23 Nov 2014 21:07:12 +0900 Subject: [PATCH 158/253] auto-update audio devices list on tab show --- src/widget/form/settings/avform.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/widget/form/settings/avform.cpp b/src/widget/form/settings/avform.cpp index 962819c26..d32bbd335 100644 --- a/src/widget/form/settings/avform.cpp +++ b/src/widget/form/settings/avform.cpp @@ -33,9 +33,6 @@ AVForm::AVForm() : bodyUI = new Ui::AVSettings; bodyUI->setupUi(this); - getAudioOutDevices(); - getAudioInDevices(); - connect(Camera::getInstance(), &Camera::propProbingFinished, this, &AVForm::onPropProbingFinished); connect(Camera::getInstance(), &Camera::resolutionProbingFinished, this, &AVForm::onResProbingFinished); @@ -51,6 +48,9 @@ AVForm::~AVForm() void AVForm::present() { + getAudioOutDevices(); + getAudioInDevices(); + bodyUI->CamVideoSurface->setSource(Camera::getInstance()); Camera::getInstance()->probeProp(Camera::SATURATION); From f59a67f08a03c1475f65bf69ef0dc32329f11372 Mon Sep 17 00:00:00 2001 From: apprb Date: Mon, 24 Nov 2014 00:22:29 +0900 Subject: [PATCH 159/253] Group class refactoring --- src/group.cpp | 62 +++++++++++++++++++++++++++++++ src/group.h | 28 +++++++++++--- src/widget/form/groupchatform.cpp | 40 ++++++++++---------- src/widget/form/tabcompleter.cpp | 2 +- src/widget/friendwidget.cpp | 4 +- src/widget/groupwidget.cpp | 20 +++++----- src/widget/widget.cpp | 50 +++++++++++++------------ 7 files changed, 144 insertions(+), 62 deletions(-) diff --git a/src/group.cpp b/src/group.cpp index 0dbd2ad6b..62eef3650 100644 --- a/src/group.cpp +++ b/src/group.cpp @@ -79,3 +79,65 @@ void Group::setName(const QString& name) if (widget->isActive()) Widget::getInstance()->setWindowTitle(name); } + +void Group::regeneratePeerList() +{ + QList peerLst = Core::getInstance()->getGroupPeerNames(groupId); + peers.clear(); + nPeers = peerLst.size(); + for (int i = 0; i < peerLst.size(); i++) + peers[i] = peerLst.at(i); + + widget->onUserListChanged(); + chatForm->onUserListChanged(); +} + +bool Group::isAvGroupchat() const +{ + return avGroupchat; +} + +int Group::getGroupId() const +{ + return groupId; +} + +int Group::getPeersCount() const +{ + return nPeers; +} + +GroupChatForm *Group::getChatForm() +{ + return chatForm; +} + +GroupWidget *Group::getGroupWidget() +{ + return widget; +} + +QStringList Group::getPeerList() const +{ + return peers.values(); +} + +void Group::setEventFlag(int f) +{ + hasNewMessages = f; +} + +int Group::getEventFlag() const +{ + return hasNewMessages; +} + +void Group::setMentionedFlag(int f) +{ + userWasMentioned = f; +} + +int Group::getMentionedFlag() const +{ + return userWasMentioned; +} diff --git a/src/group.h b/src/group.h index 86790b61c..6161f6e7e 100644 --- a/src/group.h +++ b/src/group.h @@ -31,20 +31,38 @@ class Group : public QObject Q_OBJECT public: Group(int GroupId, QString Name, bool IsAvGroupchat); - ~Group(); + virtual ~Group(); + + bool isAvGroupchat() const; + int getGroupId() const; + int getPeersCount() const; + void regeneratePeerList(); + QStringList getPeerList() const; + + GroupChatForm *getChatForm(); + GroupWidget *getGroupWidget(); + + void setEventFlag(int f); + int getEventFlag() const; + + void setMentionedFlag(int f); + int getMentionedFlag() const; + void addPeer(int peerId, QString name); void removePeer(int peerId); + void updatePeer(int peerId, QString newName); void setName(const QString& name); -public: - int groupId; - QMap peers; - int nPeers; +private: GroupWidget* widget; GroupChatForm* chatForm; + QMap peers; int hasNewMessages, userWasMentioned; + int groupId; + int nPeers; bool avGroupchat; + }; #endif // GROUP_H diff --git a/src/widget/form/groupchatform.cpp b/src/widget/form/groupchatform.cpp index ff0c66dbb..cbb41df6b 100644 --- a/src/widget/form/groupchatform.cpp +++ b/src/widget/form/groupchatform.cpp @@ -38,7 +38,7 @@ GroupChatForm::GroupChatForm(Group* chatGroup) tabber = new TabCompleter(msgEdit, group); fileButton->setEnabled(false); - if (group->avGroupchat) + if (group->isAvGroupchat()) { videoButton->setEnabled(false); videoButton->setObjectName("grey"); @@ -51,10 +51,10 @@ GroupChatForm::GroupChatForm(Group* chatGroup) micButton->setVisible(false); } - nameLabel->setText(group->widget->getName()); + nameLabel->setText(group->getGroupWidget()->getName()); nusersLabel->setFont(Style::getFont(Style::Medium)); - nusersLabel->setText(GroupChatForm::tr("%1 users in chat","Number of users in chat").arg(group->peers.size())); + nusersLabel->setText(GroupChatForm::tr("%1 users in chat","Number of users in chat").arg(group->getPeersCount())); nusersLabel->setObjectName("statusLabel"); avatar->setPixmap(QPixmap(":/img/group_dark.png"), Qt::transparent); @@ -62,7 +62,7 @@ GroupChatForm::GroupChatForm(Group* chatGroup) msgEdit->setObjectName("group"); namesListLayout = new FlowLayout(0,5,0); - QStringList names(group->peers.values()); + QStringList names(group->getPeerList()); for (const QString& name : names) namesListLayout->addWidget(new QLabel(name)); @@ -81,7 +81,7 @@ GroupChatForm::GroupChatForm(Group* chatGroup) connect(micButton, SIGNAL(clicked()), this, SLOT(onMicMuteToggle())); connect(volButton, SIGNAL(clicked()), this, SLOT(onVolMuteToggle())); connect(nameLabel, &CroppingLabel::textChanged, this, [=](QString text, QString orig) - {if (text != orig) emit groupTitleChanged(group->groupId, text.left(128));} ); + {if (text != orig) emit groupTitleChanged(group->getGroupId(), text.left(128));} ); setAcceptDrops(true); } @@ -97,15 +97,15 @@ void GroupChatForm::onSendTriggered() if (msg.startsWith("/me ")) { msg = msg.right(msg.length() - 4); - emit sendAction(group->groupId, msg); + emit sendAction(group->getGroupId(), msg); } else { - emit sendMessage(group->groupId, msg); + emit sendMessage(group->getGroupId(), msg); } } void GroupChatForm::onUserListChanged() { - nusersLabel->setText(tr("%1 users in chat").arg(group->nPeers)); + nusersLabel->setText(tr("%1 users in chat").arg(group->getPeersCount())); QLayoutItem *child; while ((child = namesListLayout->takeAt(0))) @@ -115,7 +115,7 @@ void GroupChatForm::onUserListChanged() delete child; } - QStringList names(group->peers.values()); + QStringList names(group->getPeerList()); unsigned nNames = names.size(); for (unsigned i=0; imimeData()->hasFormat("friend")) { int friendId = ev->mimeData()->data("friend").toInt(); - Core::getInstance()->groupInviteFriend(friendId, group->groupId); + Core::getInstance()->groupInviteFriend(friendId, group->getGroupId()); } } @@ -149,12 +149,12 @@ void GroupChatForm::onMicMuteToggle() { if (micButton->objectName() == "red") { - Core::getInstance()->enableGroupCallMic(group->groupId); + Core::getInstance()->enableGroupCallMic(group->getGroupId()); micButton->setObjectName("green"); } else { - Core::getInstance()->disableGroupCallMic(group->groupId); + Core::getInstance()->disableGroupCallMic(group->getGroupId()); micButton->setObjectName("red"); } @@ -168,12 +168,12 @@ void GroupChatForm::onVolMuteToggle() { if (volButton->objectName() == "red") { - Core::getInstance()->enableGroupCallVol(group->groupId); + Core::getInstance()->enableGroupCallVol(group->getGroupId()); volButton->setObjectName("green"); } else { - Core::getInstance()->disableGroupCallVol(group->groupId); + Core::getInstance()->disableGroupCallVol(group->getGroupId()); volButton->setObjectName("red"); } @@ -185,7 +185,7 @@ void GroupChatForm::onCallClicked() { if (!inCall) { - Core::getInstance()->joinGroupCall(group->groupId); + Core::getInstance()->joinGroupCall(group->getGroupId()); audioInputFlag = true; audioOutputFlag = true; callButton->setObjectName("red"); @@ -194,7 +194,7 @@ void GroupChatForm::onCallClicked() } else { - Core::getInstance()->leaveGroupCall(group->groupId); + Core::getInstance()->leaveGroupCall(group->getGroupId()); audioInputFlag = false; audioOutputFlag = false; micButton->setObjectName("green"); @@ -216,9 +216,9 @@ void GroupChatForm::keyPressEvent(QKeyEvent* ev) if (ev->key() == Qt::Key_P && inCall) { Core* core = Core::getInstance(); - if (!core->isGroupCallMicEnabled(group->groupId)) + if (!core->isGroupCallMicEnabled(group->getGroupId())) { - core->enableGroupCallMic(group->groupId); + core->enableGroupCallMic(group->getGroupId()); micButton->setObjectName("green"); micButton->style()->polish(micButton); Style::repolish(micButton); @@ -235,9 +235,9 @@ void GroupChatForm::keyReleaseEvent(QKeyEvent* ev) if (ev->key() == Qt::Key_P && inCall) { Core* core = Core::getInstance(); - if (core->isGroupCallMicEnabled(group->groupId)) + if (core->isGroupCallMicEnabled(group->getGroupId())) { - core->disableGroupCallMic(group->groupId); + core->disableGroupCallMic(group->getGroupId()); micButton->setObjectName("red"); micButton->style()->polish(micButton); Style::repolish(micButton); diff --git a/src/widget/form/tabcompleter.cpp b/src/widget/form/tabcompleter.cpp index fa4691e13..73d3ffeef 100644 --- a/src/widget/form/tabcompleter.cpp +++ b/src/widget/form/tabcompleter.cpp @@ -52,7 +52,7 @@ void TabCompleter::buildCompletionList() // that section is then used as the completion regex QRegExp regex(QString("^[-_\\[\\]{}|`^.\\\\]*").append(QRegExp::escape(tabAbbrev)), Qt::CaseInsensitive); - for(auto name : group->peers.values()) + for(auto name : group->getPeerList()) if (regex.indexIn(name) > -1) completionMap[name.toLower()] = name; diff --git a/src/widget/friendwidget.cpp b/src/widget/friendwidget.cpp index e06568078..7ceb47df0 100644 --- a/src/widget/friendwidget.cpp +++ b/src/widget/friendwidget.cpp @@ -58,7 +58,7 @@ void FriendWidget::contextMenuEvent(QContextMenuEvent * event) for (Group* group : GroupList::getAllGroups()) { - QAction* groupAction = inviteMenu->addAction(group->widget->getName()); + QAction* groupAction = inviteMenu->addAction(group->getGroupWidget()->getName()); groupActions[groupAction] = group; } @@ -115,7 +115,7 @@ void FriendWidget::contextMenuEvent(QContextMenuEvent * event) else if (groupActions.contains(selectedItem)) { Group* group = groupActions[selectedItem]; - Core::getInstance()->groupInviteFriend(friendId, group->groupId); + Core::getInstance()->groupInviteFriend(friendId, group->getGroupId()); } } } diff --git a/src/widget/groupwidget.cpp b/src/widget/groupwidget.cpp index 94c2d16c2..4d1b33628 100644 --- a/src/widget/groupwidget.cpp +++ b/src/widget/groupwidget.cpp @@ -40,7 +40,7 @@ GroupWidget::GroupWidget(int GroupId, QString Name) Group* g = GroupList::findGroup(groupId); if (g) - statusMessageLabel->setText(GroupWidget::tr("%1 users in chat").arg(g->peers.size())); + statusMessageLabel->setText(GroupWidget::tr("%1 users in chat").arg(g->getPeersCount())); else statusMessageLabel->setText(GroupWidget::tr("0 users in chat")); @@ -68,7 +68,7 @@ void GroupWidget::contextMenuEvent(QContextMenuEvent * event) nameLabel->fullText(), &ok); if (ok && alias != nameLabel->fullText()) - emit g->chatForm->groupTitleChanged(groupId, alias.left(128)); + emit g->getChatForm()->groupTitleChanged(groupId, alias.left(128)); } } } @@ -77,7 +77,7 @@ void GroupWidget::onUserListChanged() { Group* g = GroupList::findGroup(groupId); if (g) - statusMessageLabel->setText(tr("%1 users in chat").arg(g->nPeers)); + statusMessageLabel->setText(tr("%1 users in chat").arg(g->getPeersCount())); else statusMessageLabel->setText(tr("0 users in chat")); } @@ -98,13 +98,13 @@ void GroupWidget::updateStatusLight() { Group *g = GroupList::findGroup(groupId); - if (g->hasNewMessages == 0) + if (!g->getEventFlag()) { statusPic.setPixmap(QPixmap(":img/status/dot_online.png")); } else { - if (g->userWasMentioned == 0) + if (!g->getMentionedFlag()) statusPic.setPixmap(QPixmap(":img/status/dot_online_notification.png")); else statusPic.setPixmap(QPixmap(":img/status/dot_online_notification.png")); @@ -114,14 +114,14 @@ void GroupWidget::updateStatusLight() void GroupWidget::setChatForm(Ui::MainWindow &ui) { Group* g = GroupList::findGroup(groupId); - g->chatForm->show(ui); + g->getChatForm()->show(ui); } void GroupWidget::resetEventFlags() { Group* g = GroupList::findGroup(groupId); - g->hasNewMessages = 0; - g->userWasMentioned = 0; + g->setEventFlag(false); + g->setMentionedFlag(false); } void GroupWidget::dragEnterEvent(QDragEnterEvent *ev) @@ -143,7 +143,7 @@ void GroupWidget::keyPressEvent(QKeyEvent* ev) { Group* g = GroupList::findGroup(groupId); if (g) - g->chatForm->keyPressEvent(ev); + g->getChatForm()->keyPressEvent(ev); } @@ -151,7 +151,7 @@ void GroupWidget::keyReleaseEvent(QKeyEvent* ev) { Group* g = GroupList::findGroup(groupId); if (g) - g->chatForm->keyReleaseEvent(ev); + g->getChatForm()->keyReleaseEvent(ev); } void GroupWidget::setName(const QString& name) diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index cc3afb3ce..9c20e1839 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -920,19 +920,19 @@ void Widget::onGroupMessageReceived(int groupnumber, const QString& message, con bool targeted = (author != name) && message.contains(name, Qt::CaseInsensitive); if (targeted) - g->chatForm->addAlertMessage(author, message, QDateTime::currentDateTime()); + g->getChatForm()->addAlertMessage(author, message, QDateTime::currentDateTime()); else - g->chatForm->addMessage(author, message, isAction, QDateTime::currentDateTime()); + g->getChatForm()->addMessage(author, message, isAction, QDateTime::currentDateTime()); - if ((static_cast(g->widget) != activeChatroomWidget) || isMinimized() || !isActiveWindow()) + if ((static_cast(g->getGroupWidget()) != activeChatroomWidget) || isMinimized() || !isActiveWindow()) { - g->hasNewMessages = 1; + g->setEventFlag(true); if (targeted) { - newMessageAlert(g->widget); - g->userWasMentioned = 1; // useful for highlighting line or desktop notifications + newMessageAlert(g->getGroupWidget()); + g->setMentionedFlag(true); // useful for highlighting line or desktop notifications } - g->widget->updateStatusLight(); + g->getGroupWidget()->updateStatusLight(); } } @@ -951,14 +951,16 @@ void Widget::onGroupNamelistChanged(int groupnumber, int peernumber, uint8_t Cha { if (name.isEmpty()) name = tr("", "Placeholder when we don't know someone's name in a group chat"); - g->addPeer(peernumber,name); + // g->addPeer(peernumber,name); + g->regeneratePeerList(); //g->chatForm->addSystemInfoMessage(tr("%1 has joined the chat").arg(name), "green"); // we can't display these messages until irungentoo fixes peernumbers // https://github.com/irungentoo/toxcore/issues/1128 } else if (change == TOX_CHAT_CHANGE_PEER_DEL) { - g->removePeer(peernumber); + // g->removePeer(peernumber); + g->regeneratePeerList(); //g->chatForm->addSystemInfoMessage(tr("%1 has left the chat").arg(name), "silver"); } else if (change == TOX_CHAT_CHANGE_PEER_NAME) // core overwrites old name before telling us it changed... @@ -973,19 +975,19 @@ void Widget::onGroupTitleChanged(int groupnumber, const QString& author, const Q g->setName(title); if (!author.isEmpty()) - g->chatForm->addSystemInfoMessage(tr("%1 has set the title to %2").arg(author, title), "silver", QDateTime::currentDateTime()); + g->getChatForm()->addSystemInfoMessage(tr("%1 has set the title to %2").arg(author, title), "silver", QDateTime::currentDateTime()); } void Widget::removeGroup(Group* g, bool fake) { - g->widget->setAsInactiveChatroom(); - if (static_cast(g->widget) == activeChatroomWidget) + g->getGroupWidget()->setAsInactiveChatroom(); + if (static_cast(g->getGroupWidget()) == activeChatroomWidget) { activeChatroomWidget = nullptr; onAddClicked(); } - GroupList::removeGroup(g->groupId, fake); - core->removeGroup(g->groupId, fake); + GroupList::removeGroup(g->getGroupId(), fake); + core->removeGroup(g->getGroupId(), fake); delete g; if (ui->mainHead->layout()->isEmpty()) onAddClicked(); @@ -1016,15 +1018,15 @@ Group *Widget::createGroup(int groupId) QString groupName = QString("Groupchat #%1").arg(groupId); Group* newgroup = GroupList::addGroup(groupId, groupName, true); QLayout* layout = contactListWidget->getGroupLayout(); - layout->addWidget(newgroup->widget); - newgroup->widget->updateStatusLight(); + layout->addWidget(newgroup->getGroupWidget()); + newgroup->getGroupWidget()->updateStatusLight(); - connect(newgroup->widget, SIGNAL(chatroomWidgetClicked(GenericChatroomWidget*)), this, SLOT(onChatroomWidgetClicked(GenericChatroomWidget*))); - connect(newgroup->widget, SIGNAL(removeGroup(int)), this, SLOT(removeGroup(int))); - connect(newgroup->widget, SIGNAL(chatroomWidgetClicked(GenericChatroomWidget*)), newgroup->chatForm, SLOT(focusInput())); - connect(newgroup->chatForm, SIGNAL(sendMessage(int,QString)), core, SLOT(sendGroupMessage(int,QString))); - connect(newgroup->chatForm, SIGNAL(sendAction(int,QString)), core, SLOT(sendGroupAction(int,QString))); - connect(newgroup->chatForm, &GroupChatForm::groupTitleChanged, core, &Core::changeGroupTitle); + connect(newgroup->getGroupWidget(), SIGNAL(chatroomWidgetClicked(GenericChatroomWidget*)), this, SLOT(onChatroomWidgetClicked(GenericChatroomWidget*))); + connect(newgroup->getGroupWidget(), SIGNAL(removeGroup(int)), this, SLOT(removeGroup(int))); + connect(newgroup->getGroupWidget(), SIGNAL(chatroomWidgetClicked(GenericChatroomWidget*)), newgroup->getChatForm(), SLOT(focusInput())); + connect(newgroup->getChatForm(), SIGNAL(sendMessage(int,QString)), core, SLOT(sendGroupMessage(int,QString))); + connect(newgroup->getChatForm(), SIGNAL(sendAction(int,QString)), core, SLOT(sendGroupAction(int,QString))); + connect(newgroup->getChatForm(), &GroupChatForm::groupTitleChanged, core, &Core::changeGroupTitle); return newgroup; } @@ -1122,7 +1124,7 @@ void Widget::onGroupSendResult(int groupId, const QString& message, int result) return; if (result == -1) - g->chatForm->addSystemInfoMessage(tr("Message failed to send"), "red", QDateTime::currentDateTime()); + g->getChatForm()->addSystemInfoMessage(tr("Message failed to send"), "red", QDateTime::currentDateTime()); } void Widget::getPassword(QString info, int passtype, uint8_t* salt) @@ -1214,5 +1216,5 @@ void Widget::reloadTheme() f->getFriendWidget()->reloadTheme(); for (Group* g : GroupList::getAllGroups()) - g->widget->reloadTheme(); + g->getGroupWidget()->reloadTheme(); } From 56ea1f1e667e0da6631f3d7bbc7bf2ca61d5a658 Mon Sep 17 00:00:00 2001 From: apprb Date: Mon, 24 Nov 2014 00:55:49 +0900 Subject: [PATCH 160/253] using ToxID in groupchats --- src/core.cpp | 23 ++++++-- src/core.h | 3 +- src/group.cpp | 21 +++++++ src/group.h | 4 ++ src/widget/form/genericchatform.cpp | 90 ++++++++--------------------- src/widget/form/genericchatform.h | 5 +- src/widget/widget.cpp | 7 ++- src/widget/widget.h | 2 +- 8 files changed, 78 insertions(+), 77 deletions(-) diff --git a/src/core.cpp b/src/core.cpp index 88c7e118f..e64f3661e 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -457,8 +457,7 @@ void Core::onAction(Tox*/* tox*/, int friendId, const uint8_t *cMessage, uint16_ void Core::onGroupAction(Tox*, int groupnumber, int peernumber, const uint8_t *action, uint16_t length, void* _core) { Core* core = static_cast(_core); - emit core->groupMessageReceived(groupnumber, CString::toString(action, length), - core->getGroupPeerName(groupnumber, peernumber), true); + emit core->groupMessageReceived(groupnumber, peernumber, CString::toString(action, length), true); } void Core::onGroupInvite(Tox*, int friendnumber, uint8_t type, const uint8_t *data, uint16_t length,void *core) @@ -483,8 +482,8 @@ void Core::onGroupInvite(Tox*, int friendnumber, uint8_t type, const uint8_t *da void Core::onGroupMessage(Tox*, int groupnumber, int peernumber, const uint8_t * message, uint16_t length, void *_core) { Core* core = static_cast(_core); - emit core->groupMessageReceived(groupnumber, CString::toString(message, length), - core->getGroupPeerName(groupnumber, peernumber), false); + + emit core->groupMessageReceived(groupnumber, peernumber, CString::toString(message, length), false); } void Core::onGroupNamelistChange(Tox*, int groupnumber, int peernumber, uint8_t change, void *core) @@ -1463,6 +1462,22 @@ QString Core::getGroupPeerName(int groupId, int peerId) const return name; } +ToxID Core::getGroupPeerToxID(int groupId, int peerId) const +{ + ToxID peerToxID; + + uint8_t rawID[TOX_CLIENT_ID_SIZE]; + int res = tox_group_peer_pubkey(tox, groupId, peerId, rawID); + if (res == -1) + { + qWarning() << "Core::getGroupPeerToxID: Unknown error"; + return peerToxID; + } + + peerToxID = ToxID::fromString(CUserId::toString(rawID)); + return peerToxID; +} + QList Core::getGroupPeerNames(int groupId) const { QList names; diff --git a/src/core.h b/src/core.h index 061fe4047..960c52b6e 100644 --- a/src/core.h +++ b/src/core.h @@ -53,6 +53,7 @@ public: int getGroupNumberPeers(int groupId) const; ///< Return the number of peers in the group chat on success, or -1 on failure QString getGroupPeerName(int groupId, int peerId) const; ///< Get the name of a peer of a group + ToxID getGroupPeerToxID(int groupId, int peerId) const; ///< Get the ToxID of a peer of a group QList getGroupPeerNames(int groupId) const; ///< Get the names of the peers of a group QString getFriendAddress(int friendNumber) const; ///< Get the full address if known, or Tox ID of a friend QString getFriendUsername(int friendNumber) const; ///< Get the username of a friend @@ -160,7 +161,7 @@ signals: void emptyGroupCreated(int groupnumber); void groupInviteReceived(int friendnumber, uint8_t type, QByteArray publicKey); - void groupMessageReceived(int groupnumber, const QString& message, const QString& author, bool isAction); + void groupMessageReceived(int groupnumber, int peernumber, const QString& message, bool isAction); void groupNamelistChanged(int groupnumber, int peernumber, uint8_t change); void groupTitleChanged(int groupnumber, const QString& author, const QString& title); diff --git a/src/group.cpp b/src/group.cpp index 62eef3650..a81186bc9 100644 --- a/src/group.cpp +++ b/src/group.cpp @@ -67,6 +67,9 @@ void Group::removePeer(int peerId) void Group::updatePeer(int peerId, QString name) { peers[peerId] = name; + QString toxid = Core::getInstance()->getGroupPeerToxID(groupId, peerId).publicKey; + toxids[toxid] = name; + widget->onUserListChanged(); chatForm->onUserListChanged(); } @@ -84,9 +87,14 @@ void Group::regeneratePeerList() { QList peerLst = Core::getInstance()->getGroupPeerNames(groupId); peers.clear(); + toxids.clear(); nPeers = peerLst.size(); for (int i = 0; i < peerLst.size(); i++) + { peers[i] = peerLst.at(i); + QString toxid = Core::getInstance()->getGroupPeerToxID(groupId, i).publicKey; + toxids[toxid] = peerLst.at(i); + } widget->onUserListChanged(); chatForm->onUserListChanged(); @@ -141,3 +149,16 @@ int Group::getMentionedFlag() const { return userWasMentioned; } + +QString Group::resolveToxID(const ToxID &id) const +{ + QString key = id.publicKey; + auto it = toxids.find(key); + + if (it != toxids.end()) + { + return *it; + } + + return QString(); +} diff --git a/src/group.h b/src/group.h index 6161f6e7e..63d40a5e4 100644 --- a/src/group.h +++ b/src/group.h @@ -25,6 +25,7 @@ struct Friend; class GroupWidget; class GroupChatForm; +struct ToxID; class Group : public QObject { @@ -54,10 +55,13 @@ public: void updatePeer(int peerId, QString newName); void setName(const QString& name); + QString resolveToxID(const ToxID &id) const; + private: GroupWidget* widget; GroupChatForm* chatForm; QMap peers; + QMap toxids; int hasNewMessages, userWasMentioned; int groupId; int nPeers; diff --git a/src/widget/form/genericchatform.cpp b/src/widget/form/genericchatform.cpp index 9fbdb9d4a..a047d9337 100644 --- a/src/widget/form/genericchatform.cpp +++ b/src/widget/form/genericchatform.cpp @@ -31,6 +31,8 @@ #include "src/widget/tool/chattextedit.h" #include "src/widget/maskablepixmapwidget.h" #include "src/core.h" +#include "src/grouplist.h" +#include "src/group.h" #include "src/friendlist.h" #include "src/friend.h" @@ -201,16 +203,6 @@ void GenericChatForm::onSaveLogClicked() file.close(); } -/** - * @deprecated The only reason it's still alive is because the groupchat API is a bit limited - */ -void GenericChatForm::addMessage(const QString& author, const QString &message, bool isAction, const QDateTime &datetime) -{ - MessageActionPtr ca = genMessageActionAction(author, message, isAction, datetime); - ca->markAsSent(); - chatWidget->insertMessage(ca); -} - MessageActionPtr GenericChatForm::addMessage(const ToxID& author, const QString &message, bool isAction, const QDateTime &datetime, bool isSent) { @@ -230,22 +222,12 @@ MessageActionPtr GenericChatForm::addSelfMessage(const QString &message, bool is return ca; } -/** - * @deprecated The only reason it's still alive is because the groupchat API is a bit limited - */ -void GenericChatForm::addAlertMessage(const QString& author, QString message, QDateTime datetime) -{ - QString date = datetime.toString(Settings::getInstance().getTimestampFormat()); - AlertAction *alact = new AlertAction(author, message, date); - alact->markAsSent(); - chatWidget->insertMessage(ChatActionPtr(alact)); - - previousId.publicKey = author; -} - void GenericChatForm::addAlertMessage(const ToxID &author, QString message, QDateTime datetime) { - QString authorStr = Core::getInstance()->getPeerName(author); + QString authorStr = resolveToxID(author); + if (authorStr.isEmpty()) + authorStr = author.publicKey; + QString date = datetime.toString(Settings::getInstance().getTimestampFormat()); chatWidget->insertMessage(ChatActionPtr(new AlertAction(authorStr, message, date))); previousId = author; @@ -314,42 +296,6 @@ void GenericChatForm::clearChatArea(bool notinform) emit chatAreaCleared(); } -/** - * @deprecated The only reason it's still alive is because the groupchat API is a bit limited - */ -MessageActionPtr GenericChatForm::genMessageActionAction(const QString &author, QString message, bool isAction, - const QDateTime &datetime) -{ - if (earliestMessage == nullptr) - { - earliestMessage = new QDateTime(datetime); - } - - QString date = datetime.toString(Settings::getInstance().getTimestampFormat()); - bool isMe = (author == Widget::getInstance()->getUsername()); - - if (!isAction && message.startsWith("/me ")) - { // always render actions regardless of what core thinks - isAction = true; - message = message.right(message.length()-4); - } - - if (isAction) - { - previousId = ToxID(); // next msg has a name regardless - return MessageActionPtr(new ActionAction (author, message, date, isMe)); - } - - MessageActionPtr res; - if (previousId.publicKey == author) - res = MessageActionPtr(new MessageAction(QString(), message, date, isMe)); - else - res = MessageActionPtr(new MessageAction(getElidedName(author), message, date, isMe)); - - previousId.publicKey = author; - return res; -} - MessageActionPtr GenericChatForm::genMessageActionAction(const ToxID& author, QString message, bool isAction, const QDateTime &datetime) { if (earliestMessage == nullptr) @@ -365,11 +311,7 @@ MessageActionPtr GenericChatForm::genMessageActionAction(const ToxID& author, QS if (isMe) authorStr = core->getUsername(); else { - Friend *f = FriendList::findFriend(author); - if (f) - authorStr = f->getDisplayedName(); - else - authorStr = core->getPeerName(author); + authorStr = resolveToxID(author); } if (authorStr.isEmpty()) // Fallback if we can't find a username @@ -438,3 +380,21 @@ ChatActionPtr GenericChatForm::genSystemInfoAction(const QString &message, const return ChatActionPtr(new SystemMessageAction(message, type, date)); } + +QString GenericChatForm::resolveToxID(const ToxID &id) +{ + Friend *f = FriendList::findFriend(id); + if (f) + { + return f->getDisplayedName(); + } else { + for (auto it : GroupList::getAllGroups()) + { + QString res = it->resolveToxID(id); + if (res.size()) + return res; + } + } + + return QString(); +} diff --git a/src/widget/form/genericchatform.h b/src/widget/form/genericchatform.h index 47128a0f3..d838fd31e 100644 --- a/src/widget/form/genericchatform.h +++ b/src/widget/form/genericchatform.h @@ -49,11 +49,9 @@ public: virtual void setName(const QString &newName); virtual void show(Ui::MainWindow &ui); - void addMessage(const QString& author, const QString &message, bool isAction, const QDateTime &datetime); ///< Deprecated MessageActionPtr addMessage(const ToxID& author, const QString &message, bool isAction, const QDateTime &datetime, bool isSent); MessageActionPtr addSelfMessage(const QString &message, bool isAction, const QDateTime &datetime, bool isSent); void addSystemInfoMessage(const QString &message, const QString &type, const QDateTime &datetime); - void addAlertMessage(const QString& author, QString message, QDateTime datetime); ///< Deprecated void addAlertMessage(const ToxID& author, QString message, QDateTime datetime); bool isEmpty(); @@ -74,11 +72,12 @@ protected slots: protected: QString getElidedName(const QString& name); - MessageActionPtr genMessageActionAction(const QString& author, QString message, bool isAction, const QDateTime &datetime); ///< Deprecated MessageActionPtr genMessageActionAction(const ToxID& author, QString message, bool isAction, const QDateTime &datetime); MessageActionPtr genSelfActionAction(QString message, bool isAction, const QDateTime &datetime); ChatActionPtr genSystemInfoAction(const QString &message, const QString &type, const QDateTime &datetime); + QString resolveToxID(const ToxID &id); + ToxID previousId; QMenu menu; int curRow; diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index 9c20e1839..1c64eceec 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -910,19 +910,20 @@ void Widget::onGroupInviteReceived(int32_t friendId, uint8_t type, QByteArray in } } -void Widget::onGroupMessageReceived(int groupnumber, const QString& message, const QString& author, bool isAction) +void Widget::onGroupMessageReceived(int groupnumber, int peernumber, const QString& message, bool isAction) { Group* g = GroupList::findGroup(groupnumber); if (!g) return; + ToxID author = Core::getInstance()->getGroupPeerToxID(groupnumber, peernumber); QString name = core->getUsername(); - bool targeted = (author != name) && message.contains(name, Qt::CaseInsensitive); + bool targeted = (!author.isMine()) && message.contains(name, Qt::CaseInsensitive); if (targeted) g->getChatForm()->addAlertMessage(author, message, QDateTime::currentDateTime()); else - g->getChatForm()->addMessage(author, message, isAction, QDateTime::currentDateTime()); + g->getChatForm()->addMessage(author, message, isAction, QDateTime::currentDateTime(), true); if ((static_cast(g->getGroupWidget()) != activeChatroomWidget) || isMinimized() || !isActiveWindow()) { diff --git a/src/widget/widget.h b/src/widget/widget.h index 057704e7e..ef0a615fc 100644 --- a/src/widget/widget.h +++ b/src/widget/widget.h @@ -116,7 +116,7 @@ private slots: void onReceiptRecieved(int friendId, int receipt); void onEmptyGroupCreated(int groupId); void onGroupInviteReceived(int32_t friendId, uint8_t type, QByteArray invite); - void onGroupMessageReceived(int groupnumber, const QString& message, const QString& author, bool isAction); + void onGroupMessageReceived(int groupnumber, int peernumber, const QString& message, bool isAction); void onGroupNamelistChanged(int groupnumber, int peernumber, uint8_t change); void onGroupTitleChanged(int groupnumber, const QString& author, const QString& title); void removeFriend(int friendId); From 41d799b25116695041adec16f271f5caa8780e13 Mon Sep 17 00:00:00 2001 From: apprb Date: Mon, 24 Nov 2014 00:58:22 +0900 Subject: [PATCH 161/253] GroupWidget: menu reordering --- src/widget/groupwidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widget/groupwidget.cpp b/src/widget/groupwidget.cpp index 4d1b33628..7d8892cda 100644 --- a/src/widget/groupwidget.cpp +++ b/src/widget/groupwidget.cpp @@ -51,8 +51,8 @@ void GroupWidget::contextMenuEvent(QContextMenuEvent * event) { QPoint pos = event->globalPos(); QMenu menu; - QAction* quitGroup = menu.addAction(tr("Quit group","Menu to quit a groupchat")); QAction* setAlias = menu.addAction(tr("Set title...")); + QAction* quitGroup = menu.addAction(tr("Quit group","Menu to quit a groupchat")); QAction* selectedItem = menu.exec(pos); if (selectedItem) From cf54aa6577a7df8d824d4a0d0847c570f9d14fa3 Mon Sep 17 00:00:00 2001 From: apprb Date: Mon, 24 Nov 2014 01:22:41 +0900 Subject: [PATCH 162/253] groupchats: friend's aliases (updates on rejoin) --- src/group.cpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/group.cpp b/src/group.cpp index a81186bc9..48c0b94c3 100644 --- a/src/group.cpp +++ b/src/group.cpp @@ -66,10 +66,18 @@ void Group::removePeer(int peerId) void Group::updatePeer(int peerId, QString name) { + ToxID id = Core::getInstance()->getGroupPeerToxID(groupId, peerId); + QString toxid = id.publicKey; peers[peerId] = name; - QString toxid = Core::getInstance()->getGroupPeerToxID(groupId, peerId).publicKey; toxids[toxid] = name; + Friend *f = FriendList::findFriend(id); + if (f) + { + peers[peerId] = f->getDisplayedName(); + toxids[toxid] = f->getDisplayedName(); + } + widget->onUserListChanged(); chatForm->onUserListChanged(); } @@ -91,9 +99,17 @@ void Group::regeneratePeerList() nPeers = peerLst.size(); for (int i = 0; i < peerLst.size(); i++) { + ToxID id = Core::getInstance()->getGroupPeerToxID(groupId, i); + QString toxid = id.publicKey; peers[i] = peerLst.at(i); - QString toxid = Core::getInstance()->getGroupPeerToxID(groupId, i).publicKey; toxids[toxid] = peerLst.at(i); + + Friend *f = FriendList::findFriend(id); + if (f) + { + peers[i] = f->getDisplayedName(); + toxids[toxid] = f->getDisplayedName(); + } } widget->onUserListChanged(); From dcf318991fc2e8056bd6905d18b571658bd967ea Mon Sep 17 00:00:00 2001 From: apprb Date: Mon, 24 Nov 2014 01:41:42 +0900 Subject: [PATCH 163/253] commented currently unused functions in Group class --- src/group.cpp | 2 ++ src/group.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/group.cpp b/src/group.cpp index 48c0b94c3..6a5ff4d8f 100644 --- a/src/group.cpp +++ b/src/group.cpp @@ -43,6 +43,7 @@ Group::~Group() delete widget; } +/* void Group::addPeer(int peerId, QString name) { if (peers.contains(peerId)) @@ -63,6 +64,7 @@ void Group::removePeer(int peerId) widget->onUserListChanged(); chatForm->onUserListChanged(); } +*/ void Group::updatePeer(int peerId, QString name) { diff --git a/src/group.h b/src/group.h index 63d40a5e4..971e6f5c2 100644 --- a/src/group.h +++ b/src/group.h @@ -49,8 +49,10 @@ public: void setMentionedFlag(int f); int getMentionedFlag() const; + /* void addPeer(int peerId, QString name); void removePeer(int peerId); + */ void updatePeer(int peerId, QString newName); void setName(const QString& name); From ba4d877b892751f78b80f5d656d172a321cba02a Mon Sep 17 00:00:00 2001 From: apprb Date: Tue, 25 Nov 2014 01:17:34 +0900 Subject: [PATCH 164/253] rescan audio devices button --- src/widget/form/settings/avform.cpp | 1 + src/widget/form/settings/avsettings.ui | 27 ++++++++++++++++---------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/widget/form/settings/avform.cpp b/src/widget/form/settings/avform.cpp index d32bbd335..a506b287a 100644 --- a/src/widget/form/settings/avform.cpp +++ b/src/widget/form/settings/avform.cpp @@ -39,6 +39,7 @@ AVForm::AVForm() : auto qcomboboxIndexChanged = (void(QComboBox::*)(const QString&)) &QComboBox::currentIndexChanged; connect(bodyUI->inDevCombobox, qcomboboxIndexChanged, this, &AVForm::onInDevChanged); connect(bodyUI->outDevCombobox, qcomboboxIndexChanged, this, &AVForm::onOutDevChanged); + connect(bodyUI->rescanButton, &QPushButton::clicked, this, [=](){getAudioInDevices(); getAudioOutDevices();}); } AVForm::~AVForm() diff --git a/src/widget/form/settings/avsettings.ui b/src/widget/form/settings/avsettings.ui index b44e68180..f7e1afc8d 100644 --- a/src/widget/form/settings/avsettings.ui +++ b/src/widget/form/settings/avsettings.ui @@ -30,8 +30,8 @@ 0 0 - 824 - 489 + 810 + 496 @@ -41,54 +41,61 @@ Audio Settings - + Microphone - + Playback - + Qt::Horizontal - + Qt::Horizontal - + Playback device - + Capture device - + - + + + + + Rescan audio devices + + + From 465da29a43df99cfa12b1372c0df7f4bd15b07f4 Mon Sep 17 00:00:00 2001 From: apprb Date: Tue, 25 Nov 2014 01:58:57 +0900 Subject: [PATCH 165/253] minor fixes in ChatAction's creation/processing --- src/widget/form/chatform.cpp | 5 +++-- src/widget/form/genericchatform.cpp | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/widget/form/chatform.cpp b/src/widget/form/chatform.cpp index 511034c2f..661afbfc6 100644 --- a/src/widget/form/chatform.cpp +++ b/src/widget/form/chatform.cpp @@ -216,8 +216,9 @@ void ChatForm::onFileRecvRequest(ToxFile file) previousId = friendId; } - chatWidget->insertMessage(ChatActionPtr(new FileTransferAction(fileTrans, getElidedName(name), - QTime::currentTime().toString("hh:mm"), false))); + QString dateStr = QTime::currentTime().toString(Settings::getInstance().getTimestampFormat()); + FileTransferAction *fa = new FileTransferAction(fileTrans, getElidedName(name), dateStr, false); + chatWidget->insertMessage(ChatActionPtr(fa)); if (!Settings::getInstance().getAutoAcceptDir(f->getToxID()).isEmpty() || Settings::getInstance().getAutoSaveEnabled()) diff --git a/src/widget/form/genericchatform.cpp b/src/widget/form/genericchatform.cpp index a047d9337..6dea94104 100644 --- a/src/widget/form/genericchatform.cpp +++ b/src/widget/form/genericchatform.cpp @@ -229,7 +229,9 @@ void GenericChatForm::addAlertMessage(const ToxID &author, QString message, QDat authorStr = author.publicKey; QString date = datetime.toString(Settings::getInstance().getTimestampFormat()); - chatWidget->insertMessage(ChatActionPtr(new AlertAction(authorStr, message, date))); + MessageActionPtr ca = MessageActionPtr(new AlertAction(authorStr, message, date)); + ca->markAsSent(); + chatWidget->insertMessage(ca); previousId = author; } From 1c8954b50d2085e4148c60a4824f6c19edad2d8e Mon Sep 17 00:00:00 2001 From: Ruben Barkow Date: Mon, 24 Nov 2014 20:16:30 +0100 Subject: [PATCH 166/253] Update de.ts "calling" will be "ruft an" --- translations/de.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/translations/de.ts b/translations/de.ts index 49618acf5..08abb61e3 100644 --- a/translations/de.ts +++ b/translations/de.ts @@ -235,7 +235,7 @@ Ignoriere diesen und nutze eine direkte Internetverbindung? %1 calling - %1 wird angerufen + %1 ruft an From f59c102caa832bbbd4d22b8ae243f86796dff6c2 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 25 Nov 2014 03:36:33 +0100 Subject: [PATCH 167/253] added opencv imgproc to dependencies ``` $ ./qtox ./qtox: error while loading shared libraries: libopencv_imgproc.so.2.4: cannot open shared object file: No such file or directory ``` --- INSTALL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/INSTALL.md b/INSTALL.md index 25280f7a0..718034e83 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -12,7 +12,7 @@ | Qt | >= 5.2.0 | core, gui, network, widget, xml, sql | | GCC/MinGW | >= 4.8 | C++11 enabled | | Tox Core | most recent | core, av | -| OpenCV | >= 2.4.9 | core, highgui | +| OpenCV | >= 2.4.9 | core, highgui, imgproc | | OpenAL Soft | >= 1.16.0 | | From d5c593d08875ebf9dc1a68b38a9c9556bc458974 Mon Sep 17 00:00:00 2001 From: novist Date: Tue, 25 Nov 2014 19:10:00 +0200 Subject: [PATCH 168/253] No border for message area (as in mockup) --- ui/chatArea/chatArea.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ui/chatArea/chatArea.css b/ui/chatArea/chatArea.css index be4ac1742..9608af802 100644 --- a/ui/chatArea/chatArea.css +++ b/ui/chatArea/chatArea.css @@ -3,3 +3,8 @@ QTextEdit background: white; color: back; } + +QTextBrowser +{ + border: none; +} From beae534d66deb1db2a44941c4f942f536009aeb6 Mon Sep 17 00:00:00 2001 From: novist Date: Tue, 25 Nov 2014 19:11:05 +0200 Subject: [PATCH 169/253] Starting client activates existing instance if such exists --- src/main.cpp | 10 +++++++++- src/widget/widget.cpp | 11 +++++++++++ src/widget/widget.h | 2 ++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index baab1f558..7fbf61682 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -110,6 +110,7 @@ int main(int argc, char *argv[]) IPC ipc; ipc.registerEventHandler(&toxURIEventHandler); ipc.registerEventHandler(&toxSaveEventHandler); + ipc.registerEventHandler(&toxActivateEventHandler); if (parser.positionalArguments().size() > 0) { @@ -152,9 +153,16 @@ int main(int argc, char *argv[]) return EXIT_FAILURE; } } + else if(!ipc.isCurrentOwner()) + { + time_t event = ipc.postEvent("$activate"); + ipc.waitUntilProcessed(event); + if (!ipc.isCurrentOwner()) + return EXIT_SUCCESS; + } // Run - Widget* w = Widget::getInstance(); + Widget* w = Widget::getInstance(); int errorcode = a.exec(); delete w; diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index 3caaecd44..5293462aa 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -51,6 +51,14 @@ #include #include +void toxActivateEventHandler(const QByteArray& data) +{ + if(data != "$activate") + return; + Widget::getInstance()->show(); + Widget::getInstance()->activateWindow(); +} + Widget *Widget::instance{nullptr}; Widget::Widget(QWidget *parent) @@ -563,7 +571,10 @@ void Widget::onIconClick(QSystemTrayIcon::ActivationReason reason) switch (reason) { case QSystemTrayIcon::Trigger: if(this->isHidden() == true) + { this->show(); + this->activateWindow(); + } else this->hide(); case QSystemTrayIcon::DoubleClick: diff --git a/src/widget/widget.h b/src/widget/widget.h index 057704e7e..2f7c408e2 100644 --- a/src/widget/widget.h +++ b/src/widget/widget.h @@ -168,4 +168,6 @@ private: QTranslator* translator; }; +void toxActivateEventHandler(const QByteArray& data); + #endif // WIDGET_H From a9134956db8e054ad85897eaa0a009d9630825e3 Mon Sep 17 00:00:00 2001 From: novist Date: Tue, 25 Nov 2014 19:56:35 +0200 Subject: [PATCH 170/253] Added -P parameter which makes qTox start (and not activate any existing instances) and load specified profile --- src/main.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 7fbf61682..468641809 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -66,9 +66,12 @@ int main(int argc, char *argv[]) parser.addHelpOption(); parser.addVersionOption(); parser.addPositionalArgument("uri", QObject::tr("Tox URI to parse")); + parser.addOption(QCommandLineOption("P", QObject::tr("Starts new instance and loads specified profile."), "profile")); parser.process(a); Settings::getInstance(); // Build our Settings singleton as soon as QApplication is ready, not before + if(parser.isSet("P")) + Settings::getInstance().setCurrentProfile(parser.value("P")); sodium_init(); // For the auto-updater @@ -153,7 +156,7 @@ int main(int argc, char *argv[]) return EXIT_FAILURE; } } - else if(!ipc.isCurrentOwner()) + else if(!ipc.isCurrentOwner() && !parser.isSet("P")) { time_t event = ipc.postEvent("$activate"); ipc.waitUntilProcessed(event); From c7d8679b8e31d866cd99bc2a885aa3ffa5038679 Mon Sep 17 00:00:00 2001 From: novist Date: Tue, 25 Nov 2014 20:32:40 +0200 Subject: [PATCH 171/253] -P value name also translated --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 468641809..3ecb63d1c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -66,7 +66,7 @@ int main(int argc, char *argv[]) parser.addHelpOption(); parser.addVersionOption(); parser.addPositionalArgument("uri", QObject::tr("Tox URI to parse")); - parser.addOption(QCommandLineOption("P", QObject::tr("Starts new instance and loads specified profile."), "profile")); + parser.addOption(QCommandLineOption("P", QObject::tr("Starts new instance and loads specified profile."), QObject::tr("profile"))); parser.process(a); Settings::getInstance(); // Build our Settings singleton as soon as QApplication is ready, not before From 161a1be99a5fa3b2ad766d00653ea5d56be2fad8 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 25 Nov 2014 21:20:43 +0100 Subject: [PATCH 172/253] Update de.ts revised all of german translation. --- translations/de.ts | 81 ++++++++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 43 deletions(-) diff --git a/translations/de.ts b/translations/de.ts index 2ce95fb71..a8f3892ea 100644 --- a/translations/de.ts +++ b/translations/de.ts @@ -113,8 +113,8 @@ qTox needs to use the Tox DNS, but can't do it through a proxy. Ignore the proxy and connect to the Internet directly ? - qTox muss die Tox DNS nutzen. Dies klappt nicht über einen Proxy. -Ignoriere Sie diesen und nutzen Sie eine direkte Internetverbindung. + qTox muss Tox DNS nutzen, dies klappt allerdings nicht über einen Proxy. +Soll der Proxy ignoriert und eine direkte Internetverbindung genutzt werden? @@ -140,12 +140,12 @@ Ignoriere Sie diesen und nutzen Sie eine direkte Internetverbindung. The DNS lookup does not contain any Tox ID Error with the DNS - Der DNS Eintrag enthält keine gültige TOX ID + Der DNS Eintrag enthält keine Tox ID The DNS lookup does not contain a valid Tox ID Error with the DNS - Der DNS Eintrag enthält keine gültige TOX ID + Der DNS Eintrag enthält keine gültige Tox ID @@ -168,7 +168,7 @@ Ignoriere Sie diesen und nutzen Sie eine direkte Internetverbindung. OFF - disables all safety, when something goes wrong your history may be lost, fastest (not recommended) - AUS - keinerlei Sicherheit, geht etwas schief kann die Historie verloren gehen, ist am schnellsten (nicht empfohlen) + AUS - keinerlei Sicherheit, geht etwas schief, kann die Historie verloren gehen, ist am schnellsten (nicht empfohlen) @@ -181,7 +181,7 @@ Ignoriere Sie diesen und nutzen Sie eine direkte Internetverbindung. <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">IMPORTANT NOTE</span></p><p><span style=" color:#ff0000;">Unless you </span><span style=" font-weight:600; color:#ff0000;">really</span><span style=" color:#ff0000;"> know what you are doing, please do </span><span style=" font-weight:600; color:#ff0000;">not</span><span style=" color:#ff0000;"> change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history.</span></p></body></html> - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">WICHTIGE NOTIZ</span></p><p><span style=" color:#ff0000;">Wenn Sie nicht </span><span style=" font-weight:600; color:#ff0000;">wirklich</span><span style=" color:#ff0000;"> wissen, was Sie tun, ändern Sie hier bitte </span><span style=" font-weight:600; color:#ff0000;">nichts</span><span style=" color:#ff0000;">. Hier getätigte Änderungen können zu Problemen mit qTox führen, evtl. sogar zum Datenverlust, z.B. die Historie.</span></p></body></html> + <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">WICHTIGE NOTIZ</span></p><p><span style=" color:#ff0000;">Wenn Sie nicht </span><span style=" font-weight:600; color:#ff0000;">wirklich</span><span style=" color:#ff0000;"> wissen, was Sie tun, ändern Sie hier bitte </span><span style=" font-weight:600; color:#ff0000;">nichts</span><span style=" color:#ff0000;">. Hier getätigte Änderungen können zu Problemen mit qTox führen, evtl. sogar zum Datenverlust, z.B. der Historie.</span></p></body></html> @@ -240,7 +240,7 @@ Ignoriere Sie diesen und nutzen Sie eine direkte Internetverbindung. %1 stopped calling - %1 stoppte den Anruf + %1 hat den Anruf beendet @@ -361,20 +361,15 @@ Leeres Passwort. - Your history is encrypted with different password + Your history is encrypted with different password. Do you want to try another password? - Ihre Historie wurde mit einem anderen Passwort verschlüsselt + Ihre Historie wurde mit einem anderen Passwort verschlüsselt. Wollen Sie ein anderes probieren? - Loggin - Anmeldung - - - - Due to incorret password logging will be disabled - Falsches Passwort, Anmeldung wird beendet + Due to incorret password history will be disabled. + Falsches Passwort, Historie wird deaktiviert. @@ -432,7 +427,7 @@ Wollen Sie ein anderes probieren? Downloads - Runtergeladen + Heruntergeladen @@ -451,7 +446,7 @@ Wollen Sie ein anderes probieren? Someone wants to make friends with you - Es möchte jemand mit Ihnen befreundet sein + Es hat Ihnen jemand eine Freundschaftsanfrage gesendet @@ -580,7 +575,7 @@ Wollen Sie ein anderes probieren? The translation may not load until qTox restarts. - Änderung wird erst nach Neustart aktiv. + Änderung wird erst nach Neustart von qTox aktiv. @@ -591,12 +586,12 @@ Wollen Sie ein anderes probieren? Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox - Speichert die Einstellungen im Arbeits- statt im normalen Konfigurationsverzeichnis + Speichert die Einstellungen im Arbeits- statt im normalen Konfigurationsverzeichnis Make Tox portable - Macht Tox portabel + Mache Tox portabel @@ -621,17 +616,17 @@ Wollen Sie ein anderes probieren? Show contacts' status changes - Zeigt Statusänderungen der Kontakte + Zeige Statusänderungen der Kontakte Check for updates on startup (unstable) - Prüft beim Start auf Updates (nicht stabil) + Beim Start auf Updates prüfen (nicht stabil) Focus qTox when a message is received - Bringt qTox in den Vordergrund, wenn eine Nachricht eintrifft + Bringe qTox in den Vordergrund, wenn eine Nachricht eintrifft @@ -641,7 +636,7 @@ Wollen Sie ein anderes probieren? Provided in minutes - Bereitgestellt in Minuten + Angabe in Minuten @@ -662,7 +657,7 @@ Wollen Sie ein anderes probieren? Smiley Pack Text on smiley pack label - Smiley Paket + Emoticon Paket @@ -677,7 +672,7 @@ Wollen Sie ein anderes probieren? Emoticon size - Smiley Größe + Emoticon Größe @@ -705,7 +700,7 @@ Wollen Sie ein anderes probieren? Reconnect reconnect button - Erneut verbinden + Neu verbinden @@ -746,7 +741,7 @@ Wollen Sie ein anderes probieren? Use proxy (SOCKS5) - Proy benutzen (SOCKS5) + Proxy benutzen (SOCKS5) @@ -781,12 +776,12 @@ Wollen Sie ein anderes probieren? Audio call: RED means you're on a call - Audio Anruf: ROT bedeutet verbunden + Sprachanruf: ROT bedeutet verbunden Video call: RED means you're on a call - Video Anruf: ROT bedeutet verbunden + Videoanruf: ROT bedeutet verbunden @@ -821,7 +816,7 @@ Wollen Sie ein anderes probieren? %1 users in chat Number of users in chat - %1 User im Chat + %1 Teilnehmer im Chat <Unknown> @@ -830,7 +825,7 @@ Wollen Sie ein anderes probieren? %1 users in chat - %1 User im Chat + %1 Teilnehmer im Chat Save chat log @@ -843,13 +838,13 @@ Wollen Sie ein anderes probieren? %1 users in chat - %1 User im Chat + %1 Teilnehmer im Chat 0 users in chat - kein User im Chat + kein Teilnehmer im Chat @@ -875,7 +870,7 @@ Wollen Sie ein anderes probieren? You can't switch profiles while a call is active! popup text - Profil kann während eines Anrufes nicht gewechselt werden! + Das Profil kann während eines Anrufes nicht gewechselt werden! @@ -910,7 +905,7 @@ Wollen Sie ein anderes probieren? Failed to remove file - Datei konnte nicht entfernt werden + Die Datei konnte nicht entfernt werden @@ -920,7 +915,7 @@ Wollen Sie ein anderes probieren? Failed to copy file - Datei konnte nicht kopiert werden + Die Datei konnte nicht kopiert werden @@ -1010,7 +1005,7 @@ Wollen Sie ein anderes probieren? Public Information - Öffentliche Information + Öffentliche Informationen @@ -1173,7 +1168,7 @@ Wollen Sie ein anderes probieren? Privacy - Datenschutz + Privatsphäre @@ -1243,7 +1238,7 @@ Soll die alte Historiedatei gelöscht werden? An update is available, do you want to download it now ? It will be installed when qTox restarts. - Ein Update steht zur Verfügung, soll es runtergeladen werden? + Ein Update steht zur Verfügung, soll es heruntergeladen werden? Es wird beim Neustart von qTox installiert. @@ -1390,7 +1385,7 @@ Es wird beim Neustart von qTox installiert. No text record found Error with the DNS - Keine Textaufzeichnung gefunden + Kein Text Eintrag im DNS gefunden @@ -1408,7 +1403,7 @@ Es wird beim Neustart von qTox installiert. The DNS lookup does not contain any Tox ID Error with the DNS - Der DNS Eintrag enthält keine TOX ID + Der DNS Eintrag enthält keine Tox ID From cac7e76ce3b34b133da99d95051dfd68d8eaa99c Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 25 Nov 2014 21:22:21 +0100 Subject: [PATCH 173/253] fixed some original translation strings --- src/core.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core.cpp b/src/core.cpp index e64f3661e..39d6d6b8a 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -1259,7 +1259,7 @@ bool Core::loadConfiguration(QString path) if (!HistoryKeeper::checkPassword()) { if (QMessageBox::Ok == Widget::getInstance()->showWarningMsgBox(tr("Encrypted log"), - tr("Your history is encrypted with different password\nDo you want to try another password?"), + tr("Your history is encrypted with different password.\nDo you want to try another password?"), QMessageBox::Ok | QMessageBox::Cancel)) { error = true; @@ -1269,7 +1269,7 @@ bool Core::loadConfiguration(QString path) { error = false; clearPassword(ptHistory); - Widget::getInstance()->showWarningMsgBox(tr("Loggin"), tr("Due to incorret password logging will be disabled")); + Widget::getInstance()->showWarningMsgBox(tr("History"), tr("Due to incorret password history will be disabled.")); Settings::getInstance().setEncryptLogs(false); Settings::getInstance().setEnableLogging(false); } From b1b1c468e34e1e422b026f1575e5b6a7ade14449 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 25 Nov 2014 21:39:21 +0100 Subject: [PATCH 174/253] adjusted translation messages as discusses here: https://github.com/tux3/qTox/pull/848#discussion_r20851990 --- src/widget/form/chatform.cpp | 2 +- translations/de.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/widget/form/chatform.cpp b/src/widget/form/chatform.cpp index 661afbfc6..4f62a4b2c 100644 --- a/src/widget/form/chatform.cpp +++ b/src/widget/form/chatform.cpp @@ -251,7 +251,7 @@ void ChatForm::onAvInvite(int FriendId, int CallId, bool video) connect(callButton, SIGNAL(clicked()), this, SLOT(onAnswerCallTriggered())); } - addSystemInfoMessage(tr("%1 calling").arg(f->getDisplayedName()), "white", QDateTime::currentDateTime()); + addSystemInfoMessage(tr("%1 is calling").arg(f->getDisplayedName()), "white", QDateTime::currentDateTime()); Widget* w = Widget::getInstance(); if (!w->isFriendWidgetCurActiveWidget(f)|| w->isMinimized() || !w->isActiveWindow()) diff --git a/translations/de.ts b/translations/de.ts index a8f3892ea..373221256 100644 --- a/translations/de.ts +++ b/translations/de.ts @@ -234,7 +234,7 @@ Soll der Proxy ignoriert und eine direkte Internetverbindung genutzt werden? - %1 calling + %1 is calling %1 ruft an From 8eea7662392103dd8dada9be9195ac32bccb7060 Mon Sep 17 00:00:00 2001 From: Ansa89 Date: Wed, 26 Nov 2014 09:40:51 +0100 Subject: [PATCH 175/253] Italian translation: update --- translations/it.ts | 175 ++++++++++++++++++++++++--------------------- 1 file changed, 95 insertions(+), 80 deletions(-) diff --git a/translations/it.ts b/translations/it.ts index e4d72e1f7..159fe2b3c 100644 --- a/translations/it.ts +++ b/translations/it.ts @@ -12,12 +12,12 @@ AVSettings - + Video Settings Impostazioni Video - + Resolution Risoluzione @@ -47,22 +47,27 @@ Dispositivo di input - + + Rescan audio devices + Cerca dispositivi audio + + + Hue Colore - + Brightness Luminoistà - + Saturation Saturazione - + Contrast Contrasto @@ -197,32 +202,32 @@ Ignorare le impostazioni del proxy e connettersi direttamente alla rete Tox?Stai cercando di inviare un file speciale (sequenziale), questo non funzionerà! - + %1 calling %1 ti sta chiamando - + %1 stopped calling %1 ha fermato la chiamata - + Calling to %1 Stai chiamando %1 - + Call with %1 ended. %2 Chiamata con %1 terminata. %2 - + Call duration: Durata chiamata: - + Call rejected Chiamata rifiutata @@ -238,105 +243,105 @@ Ignorare le impostazioni del proxy e connettersi direttamente alla rete Tox? Core - + Toxing on qTox Toxing on qTox - + qTox User qTox User - + Friend is already added Questo contatto è già presente nella tua lista - + Encryption error Errore crittografia - + The .tox file is encrypted, but encryption was not checked, continuing regardless. Il Tox datafile è criptato, ma la crittografia non è abilitata nelle impostazioni. Continuo ignorando le impostazioni. - + Tox datafile decryption password Password per decriptare il Tox datafile - - - + + + Password error Errore password - - + + Failed to setup password. Empty password. Impossibile impostare la password. Password vuota. - + Try Again Riprova - + Change profile Cambia profilo - + Reinit current profile Reinizializza il profilo corrente - + Wrong password has been entered È stata inserita una password sbagliata - + History Log decryption password Password per decriptare i log - + Encrypted log Log criptato - + Your history is encrypted with different password Do you want to try another password? I log sono criptati con una password diversa. Vuoi provare un'altra password? - + Loggin Logging - + Due to incorret password logging will be disabled I log saranno disabilitati a causa di una password incorretta - + NO Password Nessuna password - + Will be saved without encryption! Il Tox datafile sarà salvato senza password! @@ -705,53 +710,53 @@ Soprannome: GenericChatForm - + Send message Invia messaggio - + Smileys Emoticons - + Send file(s) Invia file(s) - + Audio call: RED means you're on a call Chiamata audio: ROSSO significa che la chiamata è in corso - + Video call: RED means you're on a call Videochiamata: ROSSO significa che la chiamata è in corso - + Toggle speakers volume: RED is OFF Imposta volume altoparlanti: ROSSO è SPENTO - + Toggle microphone: RED is OFF Imposta microfono: ROSSO è SPENTO - - + + Save chat log Salva il log della chat - + Clear displayed messages Rimuovi messaggi visualizzati - + Cleared Pulito @@ -785,13 +790,13 @@ Soprannome: 0 utenti in chat - + Quit group Menu to quit a groupchat Esci dal gruppo - + Set title... Imposta nome gruppo... @@ -1203,13 +1208,13 @@ Vuoi eliminare il vecchio file? %1.tox è stato importato con successo - + Update The title of a message box Nuova versione - + An update is available, do you want to download it now ? It will be installed when qTox restarts. È disponibile una nuova versione di qTox, vuoi scaricarla adesso? @@ -1220,6 +1225,16 @@ Verrà installata al riavvio del programma. Tox URI to parse URI Tox da interpretare + + + Starts new instance and loads specified profile. + Avvia una nuova istanza caricando il profilo selezionato. + + + + profile + profilo + Default @@ -1350,159 +1365,159 @@ Verrà installata al riavvio del programma. Widget - + Online Online - + Away Assente - + Busy Occupato - + &Quit &Esci - + Change status to: Cambia stato in: - + Online Button to set your status to 'Online' Online - + Away Button to set your status to 'Away' Assente - + Busy Button to set your status to 'Busy' Occupato - + Choose a profile Scegli un profilo - + Please choose which identity to use Per favore scegli quale identità usare - + Choose a profile picture Scegli un'immagine per il profilo - - - + + + Error Errore - + Unable to open this file Impossibile aprire il file - + Unable to read this image Impossibile leggere l'immagine - + This image is too big L'immagine è troppo grande - + Toxcore failed to start, the application will terminate after you close this message. Impossibile avviare Toxcore.\nqTox terminerà dopo che avrai chiuso questo messaggio. - + toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text Impossibile avviare Toxcore con le tue impostazione proxy.\nqTox non può funzionare correttamente, per favore modifica le impostazioni e riavvia il programma. - + Add friend Aggiungi contatto - + File transfers Files trasferiti - + Settings Impostazioni - + Couldn't request friendship Impossibile inviare la richiesta d'amicizia - + away contact status assente - + busy contact status occupato - + offline contact status offline - + online contact status online - + %1 is now %2 e.g. "Dubslow is now online" %1 è ora %2 - + <Unknown> Placeholder when we don't know someone's name in a group chat <Sconosciuto> - + %1 has set the title to %2 %1 ha impostato il titolo in %2 - + Message failed to send Impossibile inviare il messaggio From c224517acc8dc82ef40914dcc672e5d43c353262 Mon Sep 17 00:00:00 2001 From: novist Date: Wed, 26 Nov 2014 12:11:57 +0200 Subject: [PATCH 176/253] Group chat button at the bottom of contact list resized to 16x16 like other buttons --- img/group_button.png | Bin 0 -> 454 bytes res.qrc | 1 + src/mainwindow.ui | 2 +- 3 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 img/group_button.png diff --git a/img/group_button.png b/img/group_button.png new file mode 100644 index 0000000000000000000000000000000000000000..88aa77d35ef7a93e33528bde39bef39173e28a9d GIT binary patch literal 454 zcmV;%0XhDOP)*MtT93)D9#D3q<4Mt~nMvKbhVA z6;CyD@BO|zb7vr;&Z?JcSzS^elc0{PC+dy5qjsjrm+CM7bEQty8SGih96%xFTaq=v z1#m?zXTZN?-(Hhdccb&3`dV#Kht*NFGwv7Eids@9>kZzk-=knq{h1uMq)w?tbt`X1 zs9mv3P#10FL>-8=9hpHveW9)a$G{TM3cR6_Zs4st0-ORr)Uijv3h*HoH3KE!PmaM2 z@HY;)3FrsD6cRB&5AYP&%rQ6s4uOFr)*RHnBv1yMLL-nymw0LkN}nx(0(d-H*U1unO!0pQzq>IfQU@S5wqpbu;G)Gk&YnY9UYjME!P; wIOAIVoM-SZc6bOInJzubbE=={A(XPkEvI6Rs`Yayh5!Hn07*qoM6N<$g4VdTkN^Mx literal 0 HcmV?d00001 diff --git a/res.qrc b/res.qrc index 239df056e..bec7d5077 100644 --- a/res.qrc +++ b/res.qrc @@ -10,6 +10,7 @@ img/contact.png img/contact_dark.png img/group.png + img/group_button.png img/status/dot_away.png img/status/dot_away_2x.png img/status/dot_away_notification.png diff --git a/src/mainwindow.ui b/src/mainwindow.ui index f56a52c48..221ef052d 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1137,7 +1137,7 @@ QSplitter:handle{ - :/img/group_2x.png:/img/group_2x.png + :/img/group_button.png:/img/group_button.png true From a083d8dbc645c98033d8856f52dfd0939249a320 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Wed, 26 Nov 2014 23:21:57 +0100 Subject: [PATCH 177/253] Fix some nullptr dereferences in Audio Fixes #859 --- src/audio.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/audio.cpp b/src/audio.cpp index b1efce7bf..e9f4eca38 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -60,7 +60,8 @@ void Audio::openInput(const QString& inDevDescr) { auto* tmp = alInDev; alInDev = nullptr; - alcCaptureCloseDevice(tmp); + if (tmp) + alcCaptureCloseDevice(tmp); int stereoFlag = av_DefaultSettings.audio_channels==1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16; if (inDevDescr.isEmpty()) alInDev = alcCaptureOpenDevice(nullptr,av_DefaultSettings.audio_sample_rate, stereoFlag, @@ -78,7 +79,7 @@ void Audio::openInput(const QString& inDevDescr) Core::getInstance()->resetCallSources(); // Force to regen each group call's sources // Restart the capture if necessary - if (userCount.load() != 0) + if (userCount.load() != 0 && alInDev) alcCaptureStart(alInDev); } @@ -86,7 +87,8 @@ void Audio::openOutput(const QString& outDevDescr) { auto* tmp = alOutDev; alOutDev = nullptr; - alcCloseDevice(tmp); + if (tmp) + alcCloseDevice(tmp); if (outDevDescr.isEmpty()) alOutDev = alcOpenDevice(nullptr); else @@ -97,7 +99,8 @@ void Audio::openOutput(const QString& outDevDescr) } else { - alcDestroyContext(alContext); + if (alContext) + alcDestroyContext(alContext); alContext=alcCreateContext(alOutDev,nullptr); if (!alcMakeContextCurrent(alContext)) { From 307460ad6c7be40c571cd36706004475e4e3b881 Mon Sep 17 00:00:00 2001 From: Ansa89 Date: Thu, 27 Nov 2014 13:02:13 +0100 Subject: [PATCH 178/253] Italian translation: update --- translations/it.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/translations/it.ts b/translations/it.ts index 159fe2b3c..e8f94de98 100644 --- a/translations/it.ts +++ b/translations/it.ts @@ -203,7 +203,7 @@ Ignorare le impostazioni del proxy e connettersi direttamente alla rete Tox? - %1 calling + %1 is calling %1 ti sta chiamando @@ -320,20 +320,20 @@ Password vuota. - Your history is encrypted with different password + Your history is encrypted with different password. Do you want to try another password? - I log sono criptati con una password diversa. -Vuoi provare un'altra password? + I log delle chat sono criptati con una password diversa. +Vuoi provare ad inserire un'altra password? - Loggin - Logging + History + Chat Log - Due to incorret password logging will be disabled - I log saranno disabilitati a causa di una password incorretta + Due to incorret password history will be disabled. + Password errata, i log delle chat non saranno caricati. From 479644833e900a3c213a3bdb4dd43e4fa5939145 Mon Sep 17 00:00:00 2001 From: novist Date: Wed, 26 Nov 2014 19:58:30 +0200 Subject: [PATCH 179/253] Option for tray icon to display user status --- src/misc/settings.cpp | 12 ++ src/misc/settings.h | 4 + src/widget/form/settings/generalform.cpp | 8 + src/widget/form/settings/generalform.h | 1 + src/widget/form/settings/generalsettings.ui | 171 ++++++++++---------- src/widget/widget.cpp | 24 ++- src/widget/widget.h | 1 + 7 files changed, 135 insertions(+), 86 deletions(-) diff --git a/src/misc/settings.cpp b/src/misc/settings.cpp index 874913ec5..5ad19cb81 100644 --- a/src/misc/settings.cpp +++ b/src/misc/settings.cpp @@ -125,6 +125,7 @@ void Settings::load() makeToxPortable = s.value("makeToxPortable", false).toBool(); autostartInTray = s.value("autostartInTray", false).toBool(); closeToTray = s.value("closeToTray", false).toBool(); + trayShowsUserStatus = s.value("trayShowsUserStatus", false).toBool(); forceTCP = s.value("forceTCP", false).toBool(); useProxy = s.value("useProxy", false).toBool(); proxyAddr = s.value("proxyAddr", "").toString(); @@ -266,6 +267,7 @@ void Settings::save(QString path) s.setValue("showSystemTray", showSystemTray); s.setValue("autostartInTray",autostartInTray); s.setValue("closeToTray", closeToTray); + s.setValue("trayShowsUserStatus", trayShowsUserStatus); s.setValue("useProxy", useProxy); s.setValue("forceTCP", forceTCP); s.setValue("proxyAddr", proxyAddr); @@ -487,6 +489,16 @@ void Settings::setCloseToTray(bool newValue) closeToTray = newValue; } +bool Settings::getTrayShowsUserStatus() const +{ + return trayShowsUserStatus; +} + +void Settings::setTrayShowsUserStatus(bool newValue) +{ + trayShowsUserStatus = newValue; +} + bool Settings::getMinimizeToTray() const { return minimizeToTray; diff --git a/src/misc/settings.h b/src/misc/settings.h index 5b923d8b1..9e7c8076a 100644 --- a/src/misc/settings.h +++ b/src/misc/settings.h @@ -57,6 +57,9 @@ public: bool getCloseToTray() const; void setCloseToTray(bool newValue); + + bool getTrayShowsUserStatus() const; + void setTrayShowsUserStatus(bool newValue); bool getMinimizeToTray() const; void setMinimizeToTray(bool newValue); @@ -240,6 +243,7 @@ private: bool autostartInTray; bool closeToTray; bool minimizeToTray; + bool trayShowsUserStatus; bool useEmoticons; bool checkUpdates; bool showInFront; diff --git a/src/widget/form/settings/generalform.cpp b/src/widget/form/settings/generalform.cpp index 7cae0847f..fc4b5ed20 100644 --- a/src/widget/form/settings/generalform.cpp +++ b/src/widget/form/settings/generalform.cpp @@ -56,6 +56,7 @@ GeneralForm::GeneralForm(SettingsWidget *myParent) : bodyUI->startInTray->setChecked(Settings::getInstance().getAutostartInTray()); bodyUI->closeToTray->setChecked(Settings::getInstance().getCloseToTray()); bodyUI->minimizeToTray->setChecked(Settings::getInstance().getMinimizeToTray()); + bodyUI->trayShowsUserStatus->setChecked(Settings::getInstance().getTrayShowsUserStatus()); bodyUI->statusChanges->setChecked(Settings::getInstance().getStatusChangeNotificationEnabled()); bodyUI->useEmoticons->setChecked(Settings::getInstance().getUseEmoticons()); bodyUI->autoacceptFiles->setChecked(Settings::getInstance().getAutoSaveEnabled()); @@ -114,6 +115,7 @@ GeneralForm::GeneralForm(SettingsWidget *myParent) : connect(bodyUI->startInTray, &QCheckBox::stateChanged, this, &GeneralForm::onSetAutostartInTray); connect(bodyUI->closeToTray, &QCheckBox::stateChanged, this, &GeneralForm::onSetCloseToTray); connect(bodyUI->minimizeToTray, &QCheckBox::stateChanged, this, &GeneralForm::onSetMinimizeToTray); + connect(bodyUI->trayShowsUserStatus, &QCheckBox::stateChanged, this, &GeneralForm::onSettrayShowsUserStatus); connect(bodyUI->statusChanges, &QCheckBox::stateChanged, this, &GeneralForm::onSetStatusChange); connect(bodyUI->autoAwaySpinBox, SIGNAL(editingFinished()), this, SLOT(onAutoAwayChanged())); connect(bodyUI->showInFront, &QCheckBox::stateChanged, this, &GeneralForm::onSetShowInFront); @@ -179,6 +181,12 @@ void GeneralForm::onSetMinimizeToTray() Settings::getInstance().setMinimizeToTray(bodyUI->minimizeToTray->isChecked()); } +void GeneralForm::onSettrayShowsUserStatus() +{ + Settings::getInstance().setTrayShowsUserStatus(bodyUI->trayShowsUserStatus->isChecked()); + Widget::getInstance()->updateTrayIcon(); +} + void GeneralForm::onStyleSelected(QString style) { if(bodyUI->styleBrowser->currentIndex() == 0) diff --git a/src/widget/form/settings/generalform.h b/src/widget/form/settings/generalform.h index ec41fb0ad..dba72d404 100644 --- a/src/widget/form/settings/generalform.h +++ b/src/widget/form/settings/generalform.h @@ -49,6 +49,7 @@ private slots: void onAutoAwayChanged(); void onUseEmoticonsChange(); void onSetMinimizeToTray(); + void onSettrayShowsUserStatus(); void onReconnectClicked(); void onAutoAcceptFileChange(); void onAutoSaveDirChange(); diff --git a/src/widget/form/settings/generalsettings.ui b/src/widget/form/settings/generalsettings.ui index 3b745c8e8..0bf981d69 100644 --- a/src/widget/form/settings/generalsettings.ui +++ b/src/widget/form/settings/generalsettings.ui @@ -143,6 +143,19 @@ + + + + + 0 + 0 + + + + Tray icon displays user status + + + @@ -274,15 +287,21 @@ - - + + + 0 + + + 0 + + Smiley Pack - + @@ -292,75 +311,14 @@ - - - - - - QLayout::SetDefaultConstraint - - - - - :) - - - - - - - - - - ;) - - - - - - - - - - :p - - - - - - - - - - :O - - - - - - - - - - :'( - - - - - - - - - - - + Style - + @@ -370,18 +328,71 @@ - - - - - + + + + QLayout::SetDefaultConstraint + + + + + :) + + + + + + + + + + ;) + + + + + + + + + + :p + + + + + + + + + + :O + + + + + + + + + + :'( + + + + + + + + + Theme color - + @@ -391,18 +402,14 @@ - - - - - + Emoticon size - + @@ -427,18 +434,14 @@ - - - - - + Timestamp format - + diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index 9256d4e97..70ade7fcd 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -77,7 +77,7 @@ void Widget::init() if (QSystemTrayIcon::isSystemTrayAvailable()) { icon = new QSystemTrayIcon(this); - icon->setIcon(this->windowIcon()); + updateTrayIcon(); trayMenu = new QMenu; statusOnline = new QAction(tr("Online"), this); @@ -292,6 +292,26 @@ void Widget::setTranslation() QCoreApplication::installTranslator(translator); } +void Widget::updateTrayIcon() +{ + if(Settings::getInstance().getTrayShowsUserStatus()) + { + QString status = ui->statusButton->property("status").toString(); + QString icon; + if(status == "online") + icon = ":img/status/dot_online_2x.png"; + else if(status == "away") + icon = ":img/status/dot_idle_2x.png"; + else if(status == "busy") + icon = ":img/status/dot_busy_2x.png"; + else + icon = ":img/status/dot_away_2x.png"; + this->icon->setIcon(QIcon(icon)); + } + else + icon->setIcon(windowIcon()); +} + Widget::~Widget() { core->saveConfiguration(); @@ -535,7 +555,7 @@ void Widget::onStatusSet(Status status) ui->statusButton->setProperty("status" ,"offline"); break; } - + updateTrayIcon(); Style::repolish(ui->statusButton); } diff --git a/src/widget/widget.h b/src/widget/widget.h index 285e59bf6..bfd34c1f2 100644 --- a/src/widget/widget.h +++ b/src/widget/widget.h @@ -64,6 +64,7 @@ public: void clearContactsList(); void setIdleTimer(int minutes); void setTranslation(); + void updateTrayIcon(); Q_INVOKABLE QMessageBox::StandardButton showWarningMsgBox(const QString& title, const QString& msg, QMessageBox::StandardButtons buttonss = QMessageBox::Ok); Q_INVOKABLE void setEnabledThreadsafe(bool enabled); From e989379535ae4f9c1edeea62633b4e7a5b5395b9 Mon Sep 17 00:00:00 2001 From: Maximilian Date: Wed, 26 Nov 2014 16:57:25 +0100 Subject: [PATCH 180/253] Refactored system tray settings in generalform --- src/widget/form/settings/generalform.cpp | 12 +- src/widget/form/settings/generalsettings.ui | 180 +++++++++++--------- 2 files changed, 114 insertions(+), 78 deletions(-) diff --git a/src/widget/form/settings/generalform.cpp b/src/widget/form/settings/generalform.cpp index fc4b5ed20..d1a31a2ec 100644 --- a/src/widget/form/settings/generalform.cpp +++ b/src/widget/form/settings/generalform.cpp @@ -45,18 +45,26 @@ GeneralForm::GeneralForm(SettingsWidget *myParent) : bodyUI->checkUpdates->setVisible(AUTOUPDATE_ENABLED); bodyUI->checkUpdates->setChecked(Settings::getInstance().getCheckUpdates()); - bodyUI->trayLayout->addStretch(); + bodyUI->trayBehavior->addStretch(); bodyUI->cbEnableIPv6->setChecked(Settings::getInstance().getEnableIPv6()); for (int i = 0; i < langs.size(); i++) bodyUI->transComboBox->insertItem(i, langs[i]); + bodyUI->transComboBox->setCurrentIndex(locales.indexOf(Settings::getInstance().getTranslation())); bodyUI->cbMakeToxPortable->setChecked(Settings::getInstance().getMakeToxPortable()); - bodyUI->showSystemTray->setChecked(Settings::getInstance().getShowSystemTray()); + + bool showSystemTray = Settings::getInstance().getShowSystemTray(); + + bodyUI->showSystemTray->setChecked(showSystemTray); bodyUI->startInTray->setChecked(Settings::getInstance().getAutostartInTray()); + bodyUI->startInTray->setEnabled(showSystemTray); bodyUI->closeToTray->setChecked(Settings::getInstance().getCloseToTray()); + bodyUI->closeToTray->setEnabled(showSystemTray); bodyUI->minimizeToTray->setChecked(Settings::getInstance().getMinimizeToTray()); + bodyUI->minimizeToTray->setEnabled(showSystemTray); bodyUI->trayShowsUserStatus->setChecked(Settings::getInstance().getTrayShowsUserStatus()); + bodyUI->trayShowsUserStatus->setEnabled(showSystemTray); bodyUI->statusChanges->setChecked(Settings::getInstance().getStatusChangeNotificationEnabled()); bodyUI->useEmoticons->setChecked(Settings::getInstance().getUseEmoticons()); bodyUI->autoacceptFiles->setChecked(Settings::getInstance().getAutoSaveEnabled()); diff --git a/src/widget/form/settings/generalsettings.ui b/src/widget/form/settings/generalsettings.ui index 0bf981d69..79dc5712f 100644 --- a/src/widget/form/settings/generalsettings.ui +++ b/src/widget/form/settings/generalsettings.ui @@ -39,8 +39,8 @@ 0 0 - 524 - 726 + 509 + 849 @@ -94,66 +94,78 @@ - - - - - Show system tray icon - - - - - - - - 0 - 0 - - - - Start in tray - - - - - - - - 0 - 0 - - - - Close to tray - - - - - - - - 0 - 0 - - - - Minimize to tray - - - - - - - - - - 0 - 0 - - - - Tray icon displays user status + + + System tray integration + + + QFormLayout::AllNonFixedFieldsGrow + + + + + Show system tray icon + + + + + + + + + + 0 + 0 + + + + Start in tray + + + + + + + + 0 + 0 + + + + Close to tray + + + + + + + + 0 + 0 + + + + Minimize to tray + + + + + + + + + + 0 + 0 + + + + Tray icon displays user status + + + + @@ -210,9 +222,6 @@ Set to 0 to disable - - true - minutes @@ -222,6 +231,9 @@ 2147483647 + + true + @@ -551,12 +563,12 @@ setEnabled(bool) - 125 - 122 + 105 + 144 - 207 - 124 + 119 + 177 @@ -567,12 +579,12 @@ setEnabled(bool) - 66 - 119 + 205 + 146 - 346 - 116 + 224 + 178 @@ -583,12 +595,28 @@ setEnabled(bool) - 83 - 121 + 180 + 144 - 476 - 120 + 359 + 178 + + + + + showSystemTray + toggled(bool) + trayShowsUserStatus + setEnabled(bool) + + + 148 + 143 + + + 158 + 205 From 5f1bf8ce3bd2ced0c70d2515c97e168da5556f7b Mon Sep 17 00:00:00 2001 From: novist Date: Thu, 27 Nov 2014 15:39:35 +0200 Subject: [PATCH 181/253] Checkboxes in settings grayed out when disabled --- ui/settings/mainContent.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ui/settings/mainContent.css b/ui/settings/mainContent.css index ee239f209..76fcef3b4 100644 --- a/ui/settings/mainContent.css +++ b/ui/settings/mainContent.css @@ -29,6 +29,11 @@ QCheckBox color: black; } +QCheckBox:disabled +{ + color: grey; +} + QSpinBox { background: white; From bc381a845953971db87a5c3c60d72d313c1a377d Mon Sep 17 00:00:00 2001 From: dubslow Date: Thu, 27 Nov 2014 12:42:04 -0500 Subject: [PATCH 182/253] warn on can't read file, fixes #860 --- src/widget/form/chatform.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/widget/form/chatform.cpp b/src/widget/form/chatform.cpp index 4f62a4b2c..6d95b4a5b 100644 --- a/src/widget/form/chatform.cpp +++ b/src/widget/form/chatform.cpp @@ -141,7 +141,10 @@ void ChatForm::onAttachClicked() { QFile file(path); if (!file.exists() || !file.open(QIODevice::ReadOnly)) + { + QMessageBox::warning(this, tr("File not read"), tr("qTox wasn't able to open %1").arg(QFileInfo(path).fileName())); continue; + } if (file.isSequential()) { QMessageBox::critical(0, tr("Bad Idea"), tr("You're trying to send a special (sequential) file, that's not going to work!")); From a560909b6d53e5ae42d3f1c6c3015d7fec66cc79 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Thu, 27 Nov 2014 22:48:38 +0100 Subject: [PATCH 183/253] Attempt to fix #859 --- src/audio.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/audio.cpp b/src/audio.cpp index e9f4eca38..6ade89edd 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -100,7 +100,10 @@ void Audio::openOutput(const QString& outDevDescr) else { if (alContext) + { + alcMakeContextCurrent(nullptr); alcDestroyContext(alContext); + } alContext=alcCreateContext(alOutDev,nullptr); if (!alcMakeContextCurrent(alContext)) { From d69039cb83e7cf941be43b526ec46f8de0b47160 Mon Sep 17 00:00:00 2001 From: Sean Date: Wed, 26 Nov 2014 08:31:03 -0800 Subject: [PATCH 184/253] Lemmie fix those for you --- src/platform.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/platform.cpp b/src/platform.cpp index b514ede29..cbdd79240 100644 --- a/src/platform.cpp +++ b/src/platform.cpp @@ -1,8 +1,8 @@ #include "platform.h" #include -#if defined(Q_OS_WIN) +#if defined(Q_OS_WIN32) #include -#elif defined(Q_OS_DARWIN) && defined(HAVE_IOKIT) +#elif defined(__APPLE__) && defined(__MACH__) #include #include #else // Q_OS_UNIX @@ -17,11 +17,11 @@ u_int32_t Platform::getIdleTime() // https://hg.pidgin.im/pidgin/main/file/13e4ae613a6a/pidgin/gtkidle.c u_int32_t idleTime = 0; -#if defined(Q_OS_WIN) +#if defined(Q_OS_WIN32) LASTINPUTINFO info = { 0 }; if(GetLastInputInfo(&info)) idleTime = info.dwTime / 1000; -#elif defined(Q_OS_DARWIN) && defined(HAVE_IOKIT) +#elif defined(__APPLE__) && defined(__MACH__) static io_service_t service = NULL; CFTypeRef property; u_int64_t idleTime_ns = 0; From fb375cd07e3d2088333eb05f717f7035747c5def Mon Sep 17 00:00:00 2001 From: Sean Date: Wed, 26 Nov 2014 08:43:02 -0800 Subject: [PATCH 185/253] Link against IOKit --- qtox.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qtox.pro b/qtox.pro index e32a95ac8..5c0e796d4 100644 --- a/qtox.pro +++ b/qtox.pro @@ -70,7 +70,7 @@ win32 { BUNDLEID = im.tox.qtox ICON = img/icons/qtox.icns QMAKE_INFO_PLIST = osx/info.plist - LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -ltoxencryptsave -ltoxdns -lsodium -lvpx -framework OpenAL -lopencv_core -lopencv_highgui + LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -ltoxencryptsave -ltoxdns -lsodium -lvpx -framework OpenAL -framework IOKit -lopencv_core -lopencv_highgui } else { # If we're building a package, static link libtox[core,av] and libsodium, since they are not provided by any package contains(STATICPKG, YES) { From 985c25f77ec362cc34edcdc0f9d6aa405938a45a Mon Sep 17 00:00:00 2001 From: Sean Date: Wed, 26 Nov 2014 08:52:07 -0800 Subject: [PATCH 186/253] Can't forget CoreFoundation --- qtox.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qtox.pro b/qtox.pro index 5c0e796d4..d06ce4603 100644 --- a/qtox.pro +++ b/qtox.pro @@ -70,7 +70,7 @@ win32 { BUNDLEID = im.tox.qtox ICON = img/icons/qtox.icns QMAKE_INFO_PLIST = osx/info.plist - LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -ltoxencryptsave -ltoxdns -lsodium -lvpx -framework OpenAL -framework IOKit -lopencv_core -lopencv_highgui + LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -ltoxencryptsave -ltoxdns -lsodium -lvpx -framework OpenAL -framework IOKit -lopencv_core -lopencv_highgui -framework CoreFoundation } else { # If we're building a package, static link libtox[core,av] and libsodium, since they are not provided by any package contains(STATICPKG, YES) { From 99fa5d9e19b29e21087dd131531c512f5dbc6f9a Mon Sep 17 00:00:00 2001 From: novist Date: Wed, 26 Nov 2014 14:34:08 +0200 Subject: [PATCH 187/253] Global auto-away --- qtox.pro | 10 ++-- src/platform.cpp | 67 ++++++++++++++++++++++++ src/platform.h | 13 +++++ src/widget/form/settings/generalform.cpp | 1 - src/widget/widget.cpp | 57 +++++++++++--------- src/widget/widget.h | 3 +- 6 files changed, 118 insertions(+), 33 deletions(-) create mode 100644 src/platform.cpp create mode 100644 src/platform.h diff --git a/qtox.pro b/qtox.pro index 013619a6f..e32a95ac8 100644 --- a/qtox.pro +++ b/qtox.pro @@ -78,10 +78,10 @@ win32 { INSTALLS += target LIBS += -L$$PWD/libs/lib/ -lopus -lvpx -lopenal -Wl,-Bstatic -ltoxcore -ltoxav -ltoxencryptsave -ltoxdns -lsodium -lopencv_highgui -lopencv_imgproc -lopencv_core -lz -Wl,-Bdynamic LIBS += -Wl,-Bstatic -ljpeg -ltiff -lpng -ljasper -lIlmImf -lIlmThread -lIex -ldc1394 -lraw1394 -lHalf -lz -llzma -ljbig - LIBS += -Wl,-Bdynamic -lv4l1 -lv4l2 -lavformat -lavcodec -lavutil -lswscale -lusb-1.0 + LIBS += -Wl,-Bdynamic -lv4l1 -lv4l2 -lavformat -lavcodec -lavutil -lswscale -lusb-1.0 -lX11 -lXss } else { - LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -ltoxencryptsave -ltoxdns -lvpx -lsodium -lopenal -lopencv_core -lopencv_highgui -lopencv_imgproc + LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -ltoxencryptsave -ltoxdns -lvpx -lsodium -lopenal -lopencv_core -lopencv_highgui -lopencv_imgproc -lX11 -lXss } contains(JENKINS, YES) { @@ -155,7 +155,8 @@ HEADERS += src/widget/form/addfriendform.h \ src/autoupdate.h \ src/misc/serialize.h \ src/widget/form/settings/advancedform.h \ - src/audio.h + src/audio.h \ + src/platform.h SOURCES += \ src/widget/form/addfriendform.cpp \ @@ -222,4 +223,5 @@ SOURCES += \ src/autoupdate.cpp \ src/misc/serialize.cpp \ src/widget/form/settings/advancedform.cpp \ - src/audio.cpp + src/audio.cpp \ + src/platform.cpp diff --git a/src/platform.cpp b/src/platform.cpp new file mode 100644 index 000000000..b514ede29 --- /dev/null +++ b/src/platform.cpp @@ -0,0 +1,67 @@ +#include "platform.h" +#include +#if defined(Q_OS_WIN) +#include +#elif defined(Q_OS_DARWIN) && defined(HAVE_IOKIT) +#include +#include +#else // Q_OS_UNIX +#include +#endif + +u_int32_t Platform::getIdleTime() +{ + // http://qt-project.org/faq/answer/how_can_i_detect_a_period_of_no_user_interaction + // Detecting global inactivity, like Skype, is possible but not via Qt: + // http://stackoverflow.com/a/21905027/1497645 + // https://hg.pidgin.im/pidgin/main/file/13e4ae613a6a/pidgin/gtkidle.c + u_int32_t idleTime = 0; + +#if defined(Q_OS_WIN) + LASTINPUTINFO info = { 0 }; + if(GetLastInputInfo(&info)) + idleTime = info.dwTime / 1000; +#elif defined(Q_OS_DARWIN) && defined(HAVE_IOKIT) + static io_service_t service = NULL; + CFTypeRef property; + u_int64_t idleTime_ns = 0; + + if (!service) + { + mach_port_t master; + IOMasterPort(MACH_PORT_NULL, &master); + service = IOServiceGetMatchingService(master, IOServiceMatching("IOHIDSystem")); + } + + property = IORegistryEntryCreateCFProperty(service, CFSTR("HIDIdleTime"), kCFAllocatorDefault, 0); + CFNumberGetValue((CFNumberRef)property, kCFNumberSInt64Type, &idleTime_ns); + CFRelease(property); + + idleTime = idleTime_ns / 1000000; +#else // Q_OS_UNIX + Display *display = XOpenDisplay(NULL); + if(!display) + { + qDebug() << "XOpenDisplay(NULL) failed"; + return 0; + } + + int32_t x11event = 0, x11error = 0; + static int32_t hasExtension = XScreenSaverQueryExtension(display, &x11event, &x11error); + if(hasExtension) + { + XScreenSaverInfo *info = XScreenSaverAllocInfo(); + if(info) + { + XScreenSaverQueryInfo(display, DefaultRootWindow(display), info); + idleTime = info->idle; + XFree(info); + } + else + qDebug() << "XScreenSaverAllocInfo() failed"; + } + XCloseDisplay(display); +#endif + return idleTime; +} + diff --git a/src/platform.h b/src/platform.h new file mode 100644 index 000000000..d943582f0 --- /dev/null +++ b/src/platform.h @@ -0,0 +1,13 @@ +#ifndef PLATFORM_H +#define PLATFORM_H + +#include + +/* Platform-dependent code */ + +namespace Platform +{ + u_int32_t getIdleTime(); +} + +#endif // PLATFORM_H diff --git a/src/widget/form/settings/generalform.cpp b/src/widget/form/settings/generalform.cpp index 7cae0847f..8eac238db 100644 --- a/src/widget/form/settings/generalform.cpp +++ b/src/widget/form/settings/generalform.cpp @@ -204,7 +204,6 @@ void GeneralForm::onAutoAwayChanged() { int minutes = bodyUI->autoAwaySpinBox->value(); Settings::getInstance().setAutoAwayTime(minutes); - Widget::getInstance()->setIdleTimer(minutes); } void GeneralForm::onAutoAcceptFileChange() diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index 9256d4e97..cb2ea4703 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -35,6 +35,7 @@ #include "form/inputpassworddialog.h" #include "src/autoupdate.h" #include "src/audio.h" +#include "src/platform.h" #include #include #include @@ -124,8 +125,7 @@ void Widget::init() ui->menubar->hide(); idleTimer = new QTimer(); - idleTimer->setSingleShot(true); - setIdleTimer(Settings::getInstance().getAutoAwayTime()); + idleTimer->start(10000); //restore window state restoreGeometry(Settings::getInstance().getWindowGeometry()); @@ -262,7 +262,7 @@ void Widget::init() connect(setStatusAway, SIGNAL(triggered()), this, SLOT(setStatusAway())); connect(setStatusBusy, SIGNAL(triggered()), this, SLOT(setStatusBusy())); connect(addFriendForm, SIGNAL(friendRequested(QString, QString)), this, SIGNAL(friendRequested(QString, QString))); - connect(idleTimer, &QTimer::timeout, this, &Widget::onUserAway); + connect(idleTimer, &QTimer::timeout, this, &Widget::onUserAwayCheck); coreThread->start(); @@ -1064,21 +1064,13 @@ bool Widget::event(QEvent * e) activeChatroomWidget->resetEventFlags(); activeChatroomWidget->updateStatusLight(); } - // http://qt-project.org/faq/answer/how_can_i_detect_a_period_of_no_user_interaction - // Detecting global inactivity, like Skype, is possible but not via Qt: - // http://stackoverflow.com/a/21905027/1497645 case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::Wheel: case QEvent::KeyPress: case QEvent::KeyRelease: - if (autoAwayActive && ui->statusButton->property("status").toString() == "away") - { // be sure nothing else has changed the status in the meantime - qDebug() << "Widget: auto away deactivated at" << QTime::currentTime().toString(); - autoAwayActive = false; - emit statusSet(Status::Online); - } - setIdleTimer(Settings::getInstance().getAutoAwayTime()); + if (autoAwayActive) + onUserAwayCheck(); // Just so we get back from away faster when interacting with app default: break; } @@ -1086,22 +1078,35 @@ bool Widget::event(QEvent * e) return QWidget::event(e); } -void Widget::setIdleTimer(int minutes) +void Widget::onUserAwayCheck() { - if (minutes > 0) - idleTimer->start(minutes * 1000*60); -} - -void Widget::onUserAway() -{ - if (Settings::getInstance().getAutoAwayTime() > 0 - && ui->statusButton->property("status").toString() == "online") // leave user-set statuses in place + u_int32_t autoAwayTime = Settings::getInstance().getAutoAwayTime() * 60 * 1000; + if(ui->statusButton->property("status").toString() == "online") { - qDebug() << "Widget: auto away activated" << QTime::currentTime().toString(); - emit statusSet(Status::Away); - autoAwayActive = true; + if(autoAwayTime) + { + if (Platform::getIdleTime() >= autoAwayTime) + { + qDebug() << "Widget: auto away activated" << QTime::currentTime().toString(); + emit statusSet(Status::Away); + autoAwayActive = true; + } + } } - idleTimer->stop(); + else if(ui->statusButton->property("status").toString() == "away") + { + if(autoAwayActive) + { + if(!autoAwayTime || Platform::getIdleTime() < autoAwayTime) + { + qDebug() << "Widget: auto away deactivated" << QTime::currentTime().toString(); + emit statusSet(Status::Online); + autoAwayActive = false; + } + } + } + else if(autoAwayActive) + autoAwayActive = false; } void Widget::setStatusOnline() diff --git a/src/widget/widget.h b/src/widget/widget.h index 285e59bf6..3d0bd6323 100644 --- a/src/widget/widget.h +++ b/src/widget/widget.h @@ -62,7 +62,6 @@ public: bool getIsWindowMinimized(); static QList searchProfiles(); void clearContactsList(); - void setIdleTimer(int minutes); void setTranslation(); Q_INVOKABLE QMessageBox::StandardButton showWarningMsgBox(const QString& title, const QString& msg, QMessageBox::StandardButtons buttonss = QMessageBox::Ok); @@ -129,7 +128,7 @@ private slots: void onGroupSendResult(int groupId, const QString& message, int result); void playRingtone(); void onIconClick(QSystemTrayIcon::ActivationReason); - void onUserAway(); + void onUserAwayCheck(); void getPassword(QString info, int passtype, uint8_t* salt); void onSetShowSystemTray(bool newValue); From 8e4f69aa79c9d6d4ceb7e9454ee9e6a9a37bd517 Mon Sep 17 00:00:00 2001 From: novist Date: Wed, 26 Nov 2014 20:27:20 +0200 Subject: [PATCH 188/253] Added copyright notices to platform.* Changed u_int** to uint** --- src/platform.cpp | 22 +++++++++++++++++++--- src/platform.h | 20 ++++++++++++++++++-- src/widget/widget.cpp | 2 +- 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/platform.cpp b/src/platform.cpp index cbdd79240..ed3cea9e3 100644 --- a/src/platform.cpp +++ b/src/platform.cpp @@ -1,3 +1,19 @@ +/* + Copyright (C) 2014 by Project Tox + + This file is part of qTox, a Qt-based graphical interface for Tox. + + This program is libre software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the COPYING file for more details. +*/ + #include "platform.h" #include #if defined(Q_OS_WIN32) @@ -9,13 +25,13 @@ #include #endif -u_int32_t Platform::getIdleTime() +uint32_t Platform::getIdleTime() { // http://qt-project.org/faq/answer/how_can_i_detect_a_period_of_no_user_interaction // Detecting global inactivity, like Skype, is possible but not via Qt: // http://stackoverflow.com/a/21905027/1497645 // https://hg.pidgin.im/pidgin/main/file/13e4ae613a6a/pidgin/gtkidle.c - u_int32_t idleTime = 0; + uint32_t idleTime = 0; #if defined(Q_OS_WIN32) LASTINPUTINFO info = { 0 }; @@ -24,7 +40,7 @@ u_int32_t Platform::getIdleTime() #elif defined(__APPLE__) && defined(__MACH__) static io_service_t service = NULL; CFTypeRef property; - u_int64_t idleTime_ns = 0; + uint64_t idleTime_ns = 0; if (!service) { diff --git a/src/platform.h b/src/platform.h index d943582f0..b1be2d945 100644 --- a/src/platform.h +++ b/src/platform.h @@ -1,13 +1,29 @@ +/* + Copyright (C) 2014 by Project Tox + + This file is part of qTox, a Qt-based graphical interface for Tox. + + This program is libre software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the COPYING file for more details. +*/ + #ifndef PLATFORM_H #define PLATFORM_H -#include +#include /* Platform-dependent code */ namespace Platform { - u_int32_t getIdleTime(); + uint32_t getIdleTime(); } #endif // PLATFORM_H diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index cb2ea4703..b7d0bfdf3 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -1080,7 +1080,7 @@ bool Widget::event(QEvent * e) void Widget::onUserAwayCheck() { - u_int32_t autoAwayTime = Settings::getInstance().getAutoAwayTime() * 60 * 1000; + uint32_t autoAwayTime = Settings::getInstance().getAutoAwayTime() * 60 * 1000; if(ui->statusButton->property("status").toString() == "online") { if(autoAwayTime) From 677058eb65b2d73fa9f8524550ab195561db809b Mon Sep 17 00:00:00 2001 From: novist Date: Thu, 27 Nov 2014 15:13:35 +0200 Subject: [PATCH 189/253] Reorganized platform-dependent code Auto-away timer check reduced to 1s --- qtox.pro | 6 ++- src/platform/osx_timer.cpp | 45 ++++++++++++++++++++ src/{platform.h => platform/timer.h} | 7 ++- src/platform/win_timer.cpp | 31 ++++++++++++++ src/{platform.cpp => platform/x11_timer.cpp} | 39 ++--------------- src/widget/widget.cpp | 4 +- 6 files changed, 89 insertions(+), 43 deletions(-) create mode 100644 src/platform/osx_timer.cpp rename src/{platform.h => platform/timer.h} (88%) create mode 100644 src/platform/win_timer.cpp rename src/{platform.cpp => platform/x11_timer.cpp} (53%) diff --git a/qtox.pro b/qtox.pro index d06ce4603..7778d7cdd 100644 --- a/qtox.pro +++ b/qtox.pro @@ -156,7 +156,7 @@ HEADERS += src/widget/form/addfriendform.h \ src/misc/serialize.h \ src/widget/form/settings/advancedform.h \ src/audio.h \ - src/platform.h + src/platform/timer.h SOURCES += \ src/widget/form/addfriendform.cpp \ @@ -224,4 +224,6 @@ SOURCES += \ src/misc/serialize.cpp \ src/widget/form/settings/advancedform.cpp \ src/audio.cpp \ - src/platform.cpp + src/platform/osx_timer.cpp \ + src/platform/win_timer.cpp \ + src/platform/x11_timer.cpp diff --git a/src/platform/osx_timer.cpp b/src/platform/osx_timer.cpp new file mode 100644 index 000000000..43f731e5a --- /dev/null +++ b/src/platform/osx_timer.cpp @@ -0,0 +1,45 @@ +/* + Copyright (C) 2014 by Project Tox + + This file is part of qTox, a Qt-based graphical interface for Tox. + + This program is libre software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the COPYING file for more details. +*/ + +#if defined(__APPLE__) && defined(__MACH__) +#include "src/platform/timer.h" +#include +#include +#include + + +uint32_t Platform::getIdleTime() +{ + // https://hg.pidgin.im/pidgin/main/file/13e4ae613a6a/pidgin/gtkidle.c + static io_service_t service = NULL; + CFTypeRef property; + uint64_t idleTime_ns = 0; + + if (!service) + { + mach_port_t master; + IOMasterPort(MACH_PORT_NULL, &master); + service = IOServiceGetMatchingService(master, IOServiceMatching("IOHIDSystem")); + } + + property = IORegistryEntryCreateCFProperty(service, CFSTR("HIDIdleTime"), kCFAllocatorDefault, 0); + CFNumberGetValue((CFNumberRef)property, kCFNumberSInt64Type, &idleTime_ns); + CFRelease(property); + + return idleTime_ns / 1000000; +} + +#endif // defined(__APPLE__) && defined(__MACH__) diff --git a/src/platform.h b/src/platform/timer.h similarity index 88% rename from src/platform.h rename to src/platform/timer.h index b1be2d945..b6576bc61 100644 --- a/src/platform.h +++ b/src/platform/timer.h @@ -14,16 +14,15 @@ See the COPYING file for more details. */ -#ifndef PLATFORM_H -#define PLATFORM_H +#ifndef PLATFORM_TIMER_H +#define PLATFORM_TIMER_H #include -/* Platform-dependent code */ namespace Platform { uint32_t getIdleTime(); } -#endif // PLATFORM_H +#endif // PLATFORM_TIMER_H diff --git a/src/platform/win_timer.cpp b/src/platform/win_timer.cpp new file mode 100644 index 000000000..c15bf0047 --- /dev/null +++ b/src/platform/win_timer.cpp @@ -0,0 +1,31 @@ +/* + Copyright (C) 2014 by Project Tox + + This file is part of qTox, a Qt-based graphical interface for Tox. + + This program is libre software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the COPYING file for more details. +*/ + +#include +#ifdef Q_OS_WIN32 +#include "src/platform/timer.h" +#include + + +uint32_t Platform::getIdleTime() +{ + LASTINPUTINFO info = { 0 }; + if(GetLastInputInfo(&info)) + return info.dwTime / 1000; + return 0; +} + +#endif // Q_OS_WIN32 diff --git a/src/platform.cpp b/src/platform/x11_timer.cpp similarity index 53% rename from src/platform.cpp rename to src/platform/x11_timer.cpp index ed3cea9e3..9d01adb41 100644 --- a/src/platform.cpp +++ b/src/platform/x11_timer.cpp @@ -14,47 +14,16 @@ See the COPYING file for more details. */ -#include "platform.h" #include -#if defined(Q_OS_WIN32) -#include -#elif defined(__APPLE__) && defined(__MACH__) -#include -#include -#else // Q_OS_UNIX +#ifdef Q_OS_UNIX +#include "src/platform/timer.h" #include -#endif + uint32_t Platform::getIdleTime() { - // http://qt-project.org/faq/answer/how_can_i_detect_a_period_of_no_user_interaction - // Detecting global inactivity, like Skype, is possible but not via Qt: - // http://stackoverflow.com/a/21905027/1497645 - // https://hg.pidgin.im/pidgin/main/file/13e4ae613a6a/pidgin/gtkidle.c uint32_t idleTime = 0; -#if defined(Q_OS_WIN32) - LASTINPUTINFO info = { 0 }; - if(GetLastInputInfo(&info)) - idleTime = info.dwTime / 1000; -#elif defined(__APPLE__) && defined(__MACH__) - static io_service_t service = NULL; - CFTypeRef property; - uint64_t idleTime_ns = 0; - - if (!service) - { - mach_port_t master; - IOMasterPort(MACH_PORT_NULL, &master); - service = IOServiceGetMatchingService(master, IOServiceMatching("IOHIDSystem")); - } - - property = IORegistryEntryCreateCFProperty(service, CFSTR("HIDIdleTime"), kCFAllocatorDefault, 0); - CFNumberGetValue((CFNumberRef)property, kCFNumberSInt64Type, &idleTime_ns); - CFRelease(property); - - idleTime = idleTime_ns / 1000000; -#else // Q_OS_UNIX Display *display = XOpenDisplay(NULL); if(!display) { @@ -77,7 +46,7 @@ uint32_t Platform::getIdleTime() qDebug() << "XScreenSaverAllocInfo() failed"; } XCloseDisplay(display); -#endif return idleTime; } +#endif // Q_OS_UNIX diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index b7d0bfdf3..bb2136d1a 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -35,7 +35,7 @@ #include "form/inputpassworddialog.h" #include "src/autoupdate.h" #include "src/audio.h" -#include "src/platform.h" +#include "src/platform/timer.h" #include #include #include @@ -125,7 +125,7 @@ void Widget::init() ui->menubar->hide(); idleTimer = new QTimer(); - idleTimer->start(10000); + idleTimer->start(1000); //restore window state restoreGeometry(Settings::getInstance().getWindowGeometry()); From a71e98abbbb6ff9b7c0eed3f11f7357081a4d33f Mon Sep 17 00:00:00 2001 From: novist Date: Fri, 28 Nov 2014 11:26:52 +0200 Subject: [PATCH 190/253] Switched places platform name and file name --- qtox.pro | 6 +++--- src/platform/{osx_timer.cpp => timer_osx.cpp} | 0 src/platform/{win_timer.cpp => timer_win.cpp} | 0 src/platform/{x11_timer.cpp => timer_x11.cpp} | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename src/platform/{osx_timer.cpp => timer_osx.cpp} (100%) rename src/platform/{win_timer.cpp => timer_win.cpp} (100%) rename src/platform/{x11_timer.cpp => timer_x11.cpp} (100%) diff --git a/qtox.pro b/qtox.pro index 7778d7cdd..f002f7e76 100644 --- a/qtox.pro +++ b/qtox.pro @@ -224,6 +224,6 @@ SOURCES += \ src/misc/serialize.cpp \ src/widget/form/settings/advancedform.cpp \ src/audio.cpp \ - src/platform/osx_timer.cpp \ - src/platform/win_timer.cpp \ - src/platform/x11_timer.cpp + src/platform/timer_osx.cpp \ + src/platform/timer_win.cpp \ + src/platform/timer_x11.cpp diff --git a/src/platform/osx_timer.cpp b/src/platform/timer_osx.cpp similarity index 100% rename from src/platform/osx_timer.cpp rename to src/platform/timer_osx.cpp diff --git a/src/platform/win_timer.cpp b/src/platform/timer_win.cpp similarity index 100% rename from src/platform/win_timer.cpp rename to src/platform/timer_win.cpp diff --git a/src/platform/x11_timer.cpp b/src/platform/timer_x11.cpp similarity index 100% rename from src/platform/x11_timer.cpp rename to src/platform/timer_x11.cpp From a004fc872fa70299e4d1e4d5b6cb37869973b51e Mon Sep 17 00:00:00 2001 From: accino Date: Fri, 28 Nov 2014 16:40:29 +0100 Subject: [PATCH 191/253] Added Spanish translation --- res.qrc | 1 + src/widget/form/settings/generalform.cpp | 4 +- translations/es.ts | 1639 ++++++++++++++++++++++ translations/i18n.pri | 3 +- 4 files changed, 1644 insertions(+), 3 deletions(-) create mode 100644 translations/es.ts diff --git a/res.qrc b/res.qrc index bec7d5077..a1f968612 100644 --- a/res.qrc +++ b/res.qrc @@ -216,5 +216,6 @@ smileys/cylgom/yawn.png translations/bg.qm translations/sv.qm + translations/es.qm diff --git a/src/widget/form/settings/generalform.cpp b/src/widget/form/settings/generalform.cpp index 7cae0847f..586f3280c 100644 --- a/src/widget/form/settings/generalform.cpp +++ b/src/widget/form/settings/generalform.cpp @@ -30,8 +30,8 @@ #include "src/autoupdate.h" -static QStringList locales = {"bg", "de", "en", "fr", "it", "mannol", "pirate", "pl", "ru", "fi", "sv", "uk"}; -static QStringList langs = {"Български", "Deutsch", "English", "Français", "Italiano", "mannol", "Pirate", "Polski", "Русский", "Suomi", "Svenska", "Українська"}; +static QStringList locales = {"bg", "de", "en", "fr", "es", "it", "mannol", "pirate", "pl", "ru", "fi", "sv", "uk"}; +static QStringList langs = {"Български", "Deutsch", "English", "Français", "Español", "Italiano", "mannol", "Pirate", "Polski", "Русский", "Suomi", "Svenska", "Українська"}; static QStringList timeFormats = {"hh:mm AP", "hh:mm", "hh:mm:ss AP", "hh:mm:ss"}; diff --git a/translations/es.ts b/translations/es.ts new file mode 100644 index 000000000..ac42ff310 --- /dev/null +++ b/translations/es.ts @@ -0,0 +1,1639 @@ + + + + + AVForm + + + Audio/Video + Audio/Vídeo + + + + AVSettings + + Form + qTox + + + + Video Settings + Opciones Vídeo + + + + Resolution + Resolución + + + + Playback + Volumen Altavoces + + + + Microphone + Volumen Micrófono + + + + Audio Settings + Opciones Audio + + + + Playback device + Dispositivo de Reproducción + + + + Capture device + Dispositivo de Captura + + + + Rescan audio devices + Escanear dispositivos de audio + + + + Hue + Tinte + + + + Brightness + Brillo + + + + Saturation + Saturación + + + + Contrast + Contraste + + + + AddFriendForm + + + Add Friends + Agregar Amig@s + + + + Tox ID + Tox ID of the person you're sending a friend request to + ID Tox + + + + Message + The message you send in friend requests + Mensaje + + + + Send friend request + Mandar solicitud de Amig@ + + + + Tox me maybe? + Default message in friend requests if the field is left blank. Write something appropriate! + Hola! Me gustaría agregarte a mis contactos en Tox. + + + + Please fill in a valid Tox ID + Tox ID of the friend you're sending a friend request to + Favor de ingresar un ID Tox válido + + + + You can't add yourself as a friend! + When trying to add your own Tox ID as friend + No puedes agregarte a ti mismo como amigo! + + + + qTox needs to use the Tox DNS, but can't do it through a proxy. +Ignore the proxy and connect to the Internet directly ? + qTox necesita utilizar el DNS de Tox, pero no se puede a través de un proxy. +¿Ignorar el proxy y conectarse directamente al Internet ? + + + qTox needs to use the Tox DNS, but can't do it through a proxy +Ignore the proxy and connect to the Internet directly ? + qTox necesita utilisar el DNS de Tox, pero no se puede a través de un proxy. +¿Ignorar el proxy y conectarse directamente al Internet ? + + + + This Tox ID does not exist + DNS error + ID Tox no existe + + + This address does not exist + The DNS gives the Tox ID associated to toxme.se addresses + La dirección no existe + + + Error while looking up DNS + The DNS gives the Tox ID associated to toxme.se addresses + Error durante la búsqueda en DNS + + + Unexpected number of text records + Error with the DNS + Numero de entradas de texto inesperado + + + Unexpected number of values in text record + Error with the DNS + Numero de valores inesperado + + + The DNS lookup does not contain any Tox ID + Error with the DNS + La respuesta DNS no contiene ningún ID Tox + + + The DNS lookup does not contain a valid Tox ID + Error with the DNS + La respuesta DNS no contiene un ID Tox valido + + + + AdvancedForm + + + Advanced + Avanzado + + + + FULL - very safe, slowest (recommended) + COMPLETO - muy seguro, más lento (recomendado) + + + + NORMAL - almost as safe as FULL, about 20% faster than FULL + NORMAL - casi tan seguro como COMPLETO, más o menos 20% más rápido que COMPLETO + + + + OFF - disables all safety, when something goes wrong your history may be lost, fastest (not recommended) + DESACTIVADO - nada de seguridad, si pasa algo se puede perder toda el registro de mensajes, el más rápido (no recomendado) + + + + AdvancedSettings + + + Form + Formato + + + + <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">IMPORTANT NOTE</span></p><p><span style=" color:#ff0000;">Unless you </span><span style=" font-weight:600; color:#ff0000;">really</span><span style=" color:#ff0000;"> know what you are doing, please do </span><span style=" font-weight:600; color:#ff0000;">not</span><span style=" color:#ff0000;"> change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history.</span></p></body></html> + <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">IMPORTANTE</span></p><p><span style=" color:#ff0000;">Al menos que </span><span style=" font-weight:600; color:#ff0000;">de veras</span><span style=" color:#ff0000;"> sepas lo que estas haciendo, </span><span style=" font-weight:600; color:#ff0000;">no hagas</span><span style=" color:#ff0000;"> cambios aquí. Cambios aquí pueden causar problemas con qTox, hasta a la perdida de datos, como por ejemplo, el registro.</span></p></body></html> + + + + Reset to default settings + Restablecer configuración por defecto + + + + History + Registro + + + + <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_synchronous"><span style=" text-decoration: underline; color:#0000ff;">Synchronous writing to DB</span></a></p></body></html> + <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_synchronous"><span style=" text-decoration: underline; color:#0000ff;">Escritura sincrónico a BD</span></a></p></body></html> + + + + ChatForm + + + Load History... + Cargar Historial + + + + Send a file + Mandar un Archivo + + + + Bad Idea + Mala Idea + + + + You're trying to send a special (sequential) file, that's not going to work! + Intentas mandar un archivo especial (secuencial), no va a funcionar! + + + + %1 calling + %1 llamando + + + + %1 stopped calling + %1a dejo de llamar + + + + Calling to %1 + Llamando a %1 + + + + Call rejected + Llamada rechazada + + + + Call with %1 ended. %2 + Llamada con %1 terminada. %2 + + + + Call duration: + Duración de la llamada: + + + + ChatTextEdit + + + Type your message here... + Tecla tu mensaje aquí... + + + + Core + + + Toxing on qTox + Toxeando con qTox + + + + qTox User + Usuari@ de qTox + + + + Friend is already added + Amig@ ya esta agregad@ a tu lista de contactos + + + + Encryption error + Error de Cifrado + + + + The .tox file is encrypted, but encryption was not checked, continuing regardless. + El Archivo .tox esta cifrado, pero la criptografía no ha sido verificada. Seguiremos igual. + + + + Tox datafile decryption password + Contraseña para archivo de datos Tox + + + + + + Password error + Contraseña Incorrecta + + + + + Failed to setup password. +Empty password. + No se pudo establecer la contraseña. +Contraseña en blanco. + + + + Try Again + Intentar otra vez + + + + Change profile + Cambiar Perfil + + + + Reinit current profile + Restablecer perfil actual + + + + Wrong password has been entered + Contraseña incorrecta ingresada + + + + History Log decryption password + Contraseña para Archivo de Registro + + + + Your history is encrypted with different password +Do you want to try another password? + Tu registro esta encriptado con una contraseña distinta +¿Quieres volver a probar con otra contraseña? + + + + Encrypted log + Registro encriptado + + + + Loggin + Registro + + + + Due to incorret password logging will be disabled + Debido a contraseña incorrecta, el registro será desactivado + + + + NO Password + Ninguna + + + + Will be saved without encryption! + Sera guardado sin encripción! + + + + FileTransferInstance + + + Save a file + Title of the file saving dialog + Guardar un Archivo + + + + Location not writable + Title of permissions popup + Imposible de guardar aquí + + + + You do not have permission to write that location. Choose another, or cancel the save dialog. + text of permissions popup + No hay permiso de escritura. Elije otra ubicación, o cancela. + + + + ETA + ETA + + + + FilesForm + + + Transfered Files + "Headline" of the window + Transferencias + + + + Downloads + Descargas + + + + Uploads + Subidas + + + + FriendRequestDialog + + + Friend request + Title of the window to aceept/deny a friend request + Solicitud de Contacto + + + + Someone wants to make friends with you + Alguien quiere agregarse a tu contactos + + + + User ID: + ID usuari@: + + + + Friend request message: + Mensaje de solicitud: + + + + Accept + Accept a friend request + Aceptar + + + + Reject + Reject a friend request + Rechazar + + + + FriendWidget + + + Invite to group + Menu to invite a friend to a groupchat + Invitar al grupo + + + + Copy friend ID + Menu to copy the Tox ID of that friend + Copiar ID de contacto + + + + Set alias... + Establecer Alias... + + + + Auto accept files from this friend + context menu entry + Aceptar archivos de este contacto automáticamente + + + + User alias + Alias de Usuari@ + + + + You can also set this by clicking the chat form name. +Alias: + También se puede establecer dandole clik en el nombre del chat. +Alias: + + + Alias: + Alias: + + + + Choose an auto accept directory + popup title + Elije una carpeta para descargas automáticas + + + + Remove friend + Menu to remove the friend from our friendlist + Eliminar contacto + + + + GeneralForm + + + General + General + + + + + None + Ninguno + + + + Choose an auto accept directory + popup title + Elije una carpeta para transferencias automáticas + + + + Call active + popup title + Llamada activa + + + + You can't disconnect while a call is active! + popup text + No puedes desconectar mientras haya una llamada activa! + + + + GeneralSettings + + Form + qTox + + + + General Settings + Opciones Generales + + + + + The translation may not load until qTox restarts. + La traducción puede no activarse hasta que se reinicie qTox. + + + Translation: + Traducción: + + + + Save settings to the working directory instead of the usual conf dir + describes makeToxPortable checkbox + Guardar la configuración en la carpeta actual en vez de la habitual + + + + Make Tox portable + Hacer Tox portátil + + + + Start in tray + Arrancar en la bandeja + + + + Close to tray + Cerrar a bandeja + + + + Minimize to tray + Minimizar a bandeja + + + + Show contacts' status changes + Mostrar cambios de estado de contactos + + + + Provided in minutes + En minutos + + + Auto away after (0 to disable): + Ausente después de (0 para desactivar): + + + + Set to 0 to disable + 0 para desactivar + + + + minutes + minutos + + + + Theme + Tema + + + Smiley Pack: + Text on smiley pack label + Pack de emoticonos: + + + :) + :) + + + ;) + :) + + + :p + :p + + + :O + :O + + + :'( + :'( + + + Style: + Style: + + + + Enable UDP (recommended) + Text on checkbox to disable UDP + Habilitar UDP (recomendado) + + + + Reconnect + reconnect button + Reconectar + + + + Connection Settings + Opciones de Red + + + + Translation + Traducción + + + + Show system tray icon + Mostrar icono en la bandeja + + + + Check for updates on startup (unstable) + Buscar actualizaciones al iniciar (inestable) + + + + Focus qTox when a message is received + Mostrar ventana de qTox cuando se recibe un mensaje + + + + Faux offline messaging + Envío de mensajes "offline" falso + + + + Auto away after (0 to disable) + Ausente después de (0 para desactivar]) + + + + You can set this on a per-friend basis by right clicking them. + autoaccept cb tooltip + Se puede configurar por amigo con un clik de botón derecho + + + + Autoaccept files + Auto-aceptar Archivos + + + + Save files in + Guardar Archivos en + + + + PushButton + PushButton + + + + Use emoticons + Utilizar emoticonos + + + + Smiley Pack + Text on smiley pack label + Pack de Emoticonos + + + + Style + Estilo + + + + Theme color + Color de Tema + + + + Emoticon size + Tamaño de Emoticonos + + + + px + px + + + + Timestamp format + Formato de sello de tiempo + + + + Enable IPv6 (recommended) + Text on a checkbox to enable IPv6 + Habilitar IPv6 (recomendado) + + + + Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. + force tcp checkbox tooltip + Desactivar esto permite por ejemplo el uso de Tox sobre Tor, a utilizarse solo en caso de necesidad. + + + This allows, e.g., toxing over Tor. It adds load to the Tox network however, so use only when necessary. + force tcp checkbox tooltip + Desactivar esto permite por ejemplo el uso de Tox sobre Tor, a utilizarse solo en caso de necesidad. + + + Disable UDP (not recommended) + Text on checkbox to disable UDP + Deshabilitar UDP (no recomendado) + + + + Use proxy (SOCKS5) + Utilizar un proxy (SOCKS5) + + + + Address + Text on proxy addr label + Dirección + + + + Port + Text on proxy port label + Puerto + + + + GenericChatForm + + + Send message + Mandar un mensaje + + + + Smileys + Emoticonos + + + + Send file(s) + Mandar Archivo(s) + + + + Audio call: RED means you're on a call + Llamada de Audio: ROJO significa llamada actual. + + + + Video call: RED means you're on a call + Llamada de Vídeo: ROJO significa llamada actual. + + + + Toggle speakers volume: RED is OFF + Silenciar altavoces: ROJO es apagado + + + + Toggle microphone: RED is OFF + Silenciar Micrófono: ROJO es apagado + + + + + Save chat log + Guardar registro de conversación + + + + Clear displayed messages + Borrar mensajes mostrados + + + + Cleared + Borrado + + + + GroupChatForm + + + %1 users in chat + Number of users in chat + %1 personas en el chat + + + + %1 users in chat + %1 personas + + + + GroupWidget + + + + %1 users in chat + %1 personas presentes + + + + + 0 users in chat + 0 personas presentes + + + + Set title... + Establecer Título... + + + + Quit group + Menu to quit a groupchat + Dejar el grupo + + + + Group title + Título del grupo + + + + You can also set this by clicking the chat form name. +Title: + También se puede establecer dandole clik en el nombre del chat. +Título: + + + + IdentityForm + + + Identity + Identidad + + + + Call active + popup title + Llamado Activo + + + + You can't switch profiles while a call is active! + popup text + No se puede cambiar de perfil mientras haya una llamada activa! + + + + Rename "%1" + renaming a profile + Renombrar "%1" + + + + Profile already exists + rename confirm title + Perfil ya existe + + + + A profile named "%1" already exists. Do you want to erase it? + rename confirm text + Un perfil llamado "%1" ya existe. ¿Lo quieres borrar? + + + + Export profile + save dialog title + Exportar perfil + + + + Tox save file (*.tox) + save dialog filter + Archivo Tox (*.tox) + + + + Failed to remove file + No se pudo borrar el archivo + + + + The file you chose to overwrite could not be removed first. + El archivo que quisiste sobre-escribir no se pudo borrar primero. + + + + Failed to copy file + No se pudo copiar el archivo + + + + The file you chose could not be written to. + No se pudo escribir en el archivo elegido. + + + + Profile currently loaded + current profile deletion warning title + Perfil en uso actual + + + + This profile is currently in use. Please load a different profile before deleting this one. + current profile deletion warning text + Este perfil esta actualmente siendo utilizado. Elije otro perfil antes de borrarlo. + + + + Deletion imminent! + deletion confirmation title + Eliminación inminente! + + + + Are you sure you want to delete this profile? + deletion confirmation text + ¿Estas segur@ de querer eliminar este perfil? + + + + Import profile + import dialog title + Importar un perfil + + + + Tox save file (*.tox) + import dialog filter + Archivo Tox (*.tox) + + + + Ignoring non-Tox file + popup title + Archivo no Tox ignorado + + + + Warning: you've chosen a file that is not a Tox save file; ignoring. + popup text + Atención: El archivo seleccionado no es un archivo Tox; pasando por alto. + + + + Profile already exists + import confirm title + Ya existe el perfil + + + + A profile named "%1" already exists. Do you want to erase it? + import confirm text + Un Perfil llamado "%1" ya existe. ¿Lo quieres eliminar? + + + + IdentitySettings + + Form + qTox + + + + Public Information + Datos Públicos + + + + Name + Nombre + + + + Status + Estado + + + + Tox ID + ID Tox + + + + Your Tox ID (click to copy) + Tu ID Tox (clik para copiar) + + + + Profiles + Perfiles + + + + Available profiles: + Perfiles disponibles: + + + + Load + load profile button + Cargar + + + + + Switching profiles is disabled during calls + tooltip + Cambio de perfil desactivado durante una llamada + + + + Rename + rename profile button + Renombrar + + + + Export + export profile button + Exportar + + + + Delete + delete profile button + Suprimir + + + + This is useful to remain safe on public computers + delete profile button tooltip + Útil en una computadora publica + + + + Import a profile + import profile button + Importar perfil + + + + New Tox ID + new profile button + ID Tox Nuevo + + + + InputPasswordDialog + + + Password Dialog + Contraseña + + + + Input password: + Contraseña: + + + + LoadHistoryDialog + + + Load History Dialog + Historial + + + + Load history from: + Cargar registro desde: + + + + MainWindow + + qTox + qTox + + + + Your name + Tu Nombre + + + + Your status + Tu estatus + + + + Add friends + Agregar Contactos + + + + Create a group chat + Crear un grupo + + + + View completed file transfers + Ver transferencias de archivos completados + + + + Change your settings + Cambiar Opciones + + + + Close + Cerrar + + + Ctrl+Q + Ctrl+Q + + + + NetCamView + + + Tox video + Vidéo Tox + + + + PrivacyForm + + + Privacy + Privacidad + + + + Encrypted log + Historial cifrado + + + + You already have history log file encrypted with different password +Do you want to delete old history file? + Ya tienes un archivo de registro encriptado con una contraseña distinta +¿Quieres eliminar el viejo archivo de registro? + + + + PrivacySettings + + Form + qTox + + + + Typing Notification + Notificaciones de contacto escribiendo + + + + Keep History (unstable) + Mantener registro (inestable) + + + + Encryption + Cifrado + + + + Encrypt Tox datafile + Encriptar archivo de datos Tox + + + + Encrypt History + Encriptar Registro + + + + Nospam + Nospam + + + + HHHHHHHH + + + + + Generate random nospam + Generar un nospam al azar + + + + QObject + + + Ignoring non-Tox file + popup title + Pasando por alto un archivo no-Tox + + + + Warning: you've chosen a file that is not a Tox save file; ignoring. + popup text + Atención: El archivo seleccionado no es un archivo Tox; pasando por alto. + + + + Profile already exists + import confirm title + Perfil ya existe + + + + A profile named "%1" already exists. Do you want to erase it? + import confirm text + Un Perfil llamdo "%1" ya existe. ¿Lo quieres eliminar? + + + + Profile imported + Perfil importado + + + + %1.tox was successfully imported + %1 ha sido importado con éxito + + + + Tox me maybe? + Default message in Tox URI friend requests. Write something appropriate! + Hola! Me gustaría agregarte a mis contactos en Tox. + + + + Update + The title of a message box + Actualización + + + + An update is available, do you want to download it now ? +It will be installed when qTox restarts. + Hay una actualización disponible. ¿Lo quieres bajar ahora ? +Será instalado al reinicar qTox. + + + + Tox URI to parse + URI Tox a utilizar + + + + Starts new instance and loads specified profile. + Arranca nueva instancia y carga perfil especificada. + + + + profile + perfil + + + + Default + Predeterminado + + + + Blue + Azul + + + + Olive + Olivo + + + + Red + Rojo + + + + Violet + Violeto + + + + SetPasswordDialog + + Dialog + qTox + + + + Type Password + Teclea Contraseña + + + + Repeat Password + Contraseña otra vez + + + + ToxDNS + + + The connection timed out + The DNS gives the Tox ID associated to toxme.se addresses + El tiempo de conexión ha expirado. + + + + This address does not exist + The DNS gives the Tox ID associated to toxme.se addresses + Esta dirección no existe + + + + Error while looking up DNS + The DNS gives the Tox ID associated to toxme.se addresses + Error al consultar DNS + + + + No text record found + Error with the DNS + Ningún registro encontrado + + + + Unexpected number of values in text record + Error with the DNS + Número de entradas inesperado + + + + The version of Tox DNS used by this server is not supported + Error with the DNS + La versión de DNS Tox en este servidor no es compatible + + + + The DNS lookup does not contain any Tox ID + Error with the DNS + La respuesta DNS no contiene ninguna ID Tox + + + + + The DNS lookup does not contain a valid Tox ID + Error with the DNS + La respuesta DNS no contiene una ID Tox válido + + + + ToxURIDialog + + + Add a friend + Title of the window to add a friend through Tox URI + Agregar un Contacto + + + + Do you want to add %1 as a friend ? + ̣¿Quieres agregar %1 a tus contactos? + + + + User ID: + ID usuari@: + + + + Friend request message: + Mensaje de solicitud de contacto: + + + + Send + Send a friend request + Mandar + + + + Cancel + Don't send a friend request + Cancelar + + + + Widget + + + Online + Conectad@ + + + + Away + No Disponible + + + + Busy + Ocupad@ + + + + &Quit + Cerrar + + + + Change status to: + Cambiar estatus a: + + + + Online + Button to set your status to 'Online' + Conectado + + + + Away + Button to set your status to 'Away' + No Disponible + + + + Busy + Button to set your status to 'Busy' + Ocupad@ + + + + Choose a profile + Elegir perfil + + + + Please choose which identity to use + Elegir identidad a utilizar + + + + Choose a profile picture + Elegir un imagen de perfil + + + + + + Error + Error + + + + Unable to open this file + No se puede abrir el archivo + + + + Unable to read this image + No se puede leer el archivo de imagen + + + + This image is too big + Imagen demasiado grande + + + + Toxcore failed to start, the application will terminate after you close this message. + Arranque de Toxcore fallido, el programa terminará al cerrar este mensaje. + + + + toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. + popup text + Toxcore no puede iniciarse con la configuración actual de proxy. Modifica la configuración y reinicia qTox. + + + + Add friend + Agregar Contacto + + + + File transfers + Transferencias + + + + Settings + Opciones + + + + Couldn't request friendship + No se pudo solicitar conexión con contacto + + + + away + contact status + no disponible + + + + busy + contact status + ocupad@ + + + + offline + contact status + desconectad@ + + + + online + contact status + conectad@ + + + + %1 is now %2 + e.g. "Dubslow is now online" + %1 ahora es %2 + + + + <Unknown> + Placeholder when we don't know someone's name in a group chat + <Desconocido> + + + + %1 has set the title to %2 + %1 ha establecido el título: %2 + + + + Message failed to send + Mensaje no enviado + + + diff --git a/translations/i18n.pri b/translations/i18n.pri index 231e6f896..668a89f07 100644 --- a/translations/i18n.pri +++ b/translations/i18n.pri @@ -1,6 +1,7 @@ # For autocompiling qm-files. -TRANSLATIONS = translations/bg.ts \ +TRANSLATIONS = translations/es.ts \ + translations/bg.ts \ translations/de.ts \ translations/fi.ts \ translations/fr.ts \ From 5d297a78d51fb559ef86c2098f30296c7934ec05 Mon Sep 17 00:00:00 2001 From: Zetok Zalbavar Date: Sat, 29 Nov 2014 11:27:36 +0000 Subject: [PATCH 192/253] Sort res.qrc and remove some unnecessary spaces --- res.qrc | 272 +++++++++++++++--------------- src/autoupdate.cpp | 2 +- src/widget/form/addfriendform.cpp | 2 +- src/widget/toxuri.cpp | 2 +- 4 files changed, 139 insertions(+), 139 deletions(-) diff --git a/res.qrc b/res.qrc index bec7d5077..4c199f21f 100644 --- a/res.qrc +++ b/res.qrc @@ -6,14 +6,28 @@ res/DejaVuSans.ttf - img/icon.png + audio/notification.pcm + audio/ToxicIncomingCall.pcm + img/add.png + img/avatar_mask.png img/contact.png img/contact_dark.png img/group.png + img/group_2x.png img/group_button.png + img/group_dark.png + img/icon.png + img/settings.png + img/settings/av.png + img/settings/general.png + img/settings/identity.png + img/settings/privacy.png img/status/dot_away.png img/status/dot_away_2x.png img/status/dot_away_notification.png + img/status/dot_busy.png + img/status/dot_busy_2x.png + img/status/dot_busy_notification.png img/status/dot_groupchat.png img/status/dot_groupchat_newmessages.png img/status/dot_groupchat_notification.png @@ -23,142 +37,7 @@ img/status/dot_online.png img/status/dot_online_2x.png img/status/dot_online_notification.png - img/add.png - img/settings.png img/transfer.png - ui/acceptFileButton/default.png - ui/acceptFileButton/hover.png - ui/acceptFileButton/pressed.png - ui/acceptFileButton/style.css - ui/callButton/callButton.css - ui/callButton/callButton.png - ui/callButton/callButtonDisabled.png - ui/callButton/callButtonHover.png - ui/callButton/callButtonPressed.png - ui/callButton/callButtonRed.png - ui/callButton/callButtonRedHover.png - ui/callButton/callButtonRedPressed.png - ui/callButton/callButtonYellow.png - ui/callButton/callButtonYellowHover.png - ui/callButton/callButtonYellowPressed.png - ui/chatArea/chatArea.css - ui/chatArea/innerStyle.css - ui/chatArea/scrollBarDownArrow.png - ui/chatArea/scrollBarDownArrowHover.png - ui/chatArea/scrollBarDownArrowPressed.png - ui/chatArea/scrollBarHandle.png - ui/chatArea/scrollBarUpArrow.png - ui/chatArea/scrollBarUpArrowHover.png - ui/chatArea/scrollBarUpArrowPressed.png - ui/emoteButton/emoteButton.css - ui/emoteButton/emoteButton.png - ui/emoteButton/emoteButtonHover.png - ui/emoteButton/emoteButtonPressed.png - ui/fileButton/fileButton.css - ui/fileButton/fileButton.png - ui/fileButton/fileButtonHover.png - ui/fileButton/fileButtonPressed.png - ui/fileButton/fileButtonDisabled.png - ui/msgEdit/msgEdit.css - ui/pauseFileButton/default.png - ui/pauseFileButton/hover.png - ui/pauseFileButton/pressed.png - ui/pauseFileButton/style.css - ui/sendButton/sendButton.css - ui/sendButton/sendButton.png - ui/sendButton/sendButtonHover.png - ui/sendButton/sendButtonPressed.png - ui/stopFileButton/default.png - ui/stopFileButton/hover.png - ui/stopFileButton/pressed.png - ui/stopFileButton/style.css - ui/videoButton/videoButton.css - ui/videoButton/videoButton.png - ui/videoButton/videoButtonDisabled.png - ui/videoButton/videoButtonHover.png - ui/videoButton/videoButtonPressed.png - ui/videoButton/videoButtonRed.png - ui/videoButton/videoButtonRedHover.png - ui/videoButton/videoButtonRedPressed.png - ui/videoButton/videoButtonYellow.png - ui/videoButton/videoButtonYellowHover.png - ui/videoButton/videoButtonYellowPressed.png - img/group_dark.png - ui/window/applicationIcon.png - ui/friendList/friendList.css - ui/window/window.css - img/status/dot_busy.png - img/status/dot_busy_2x.png - img/status/dot_busy_notification.png - translations/fr.qm - translations/ru.qm - ui/fileTransferWidget/fileTransferWidget.css - ui/statusButton/dot_away.png - ui/statusButton/dot_busy.png - ui/statusButton/dot_idle.png - ui/statusButton/dot_online.png - ui/statusButton/statusButton.css - ui/statusButton/menu_indicator.png - translations/de.qm - translations/it.qm - ui/emoticonWidget/dot_page.png - ui/emoticonWidget/dot_page_current.png - ui/emoticonWidget/emoticonWidget.css - ui/emoticonWidget/dot_page_hover.png - ui/volButton/volButton.png - ui/volButton/volButtonHover.png - ui/volButton/volButtonPressed.png - ui/micButton/micButton.png - ui/micButton/micButtonDisabled.png - ui/micButton/micButtonHover.png - ui/micButton/micButtonPressed.png - ui/micButton/micButton.css - ui/volButton/volButton.css - ui/fileTransferInstance/acceptFileButton.png - ui/fileTransferInstance/pauseFileButton.png - ui/fileTransferInstance/pauseGreyFileButton.png - ui/fileTransferInstance/resumeFileButton.png - ui/fileTransferInstance/stopFileButton.png - ui/fileTransferInstance/emptyLGreenFileButton.png - ui/fileTransferInstance/emptyLRedFileButton.png - ui/fileTransferInstance/emptyRGreenFileButton.png - ui/fileTransferInstance/emptyRRedFileButton.png - audio/notification.pcm - audio/ToxicIncomingCall.pcm - img/settings/general.png - img/settings/identity.png - img/settings/privacy.png - img/settings/av.png - translations/pl.qm - translations/fi.qm - translations/mannol.qm - translations/uk.qm - img/avatar_mask.png - img/group_2x.png - ui/chatroomWidgets/genericChatroomWidget.css - ui/fileTransferInstance/sliverRTEdge.png - ui/window/statusPanel.css - ui/settings/mainContent.css - ui/settings/mainHead.css - translations/pirate.qm - ui/chatArea/chatHead.css - smileys/krepa098/angry.png - smileys/krepa098/cool.png - smileys/krepa098/crying.png - smileys/krepa098/emoticons.xml - smileys/krepa098/happy.png - smileys/krepa098/Kappa.png - smileys/krepa098/laugh_closed_eyes.png - smileys/krepa098/laugh.png - smileys/krepa098/plain.png - smileys/krepa098/raw.svg - smileys/krepa098/sad.png - smileys/krepa098/scared.png - smileys/krepa098/smile.png - smileys/krepa098/stunned.png - smileys/krepa098/tongue.png - smileys/krepa098/uncertain.png - smileys/krepa098/wink.png smileys/cylgom/angel.png smileys/cylgom/angry.png smileys/cylgom/beer.png @@ -214,7 +93,128 @@ smileys/cylgom/XD.png smileys/cylgom/XP.png smileys/cylgom/yawn.png + smileys/krepa098/angry.png + smileys/krepa098/cool.png + smileys/krepa098/crying.png + smileys/krepa098/emoticons.xml + smileys/krepa098/happy.png + smileys/krepa098/Kappa.png + smileys/krepa098/laugh_closed_eyes.png + smileys/krepa098/laugh.png + smileys/krepa098/plain.png + smileys/krepa098/raw.svg + smileys/krepa098/sad.png + smileys/krepa098/scared.png + smileys/krepa098/smile.png + smileys/krepa098/stunned.png + smileys/krepa098/tongue.png + smileys/krepa098/uncertain.png + smileys/krepa098/wink.png translations/bg.qm + translations/de.qm + translations/fi.qm + translations/fr.qm + translations/it.qm + translations/mannol.qm + translations/pirate.qm + translations/pl.qm + translations/ru.qm translations/sv.qm + translations/uk.qm + ui/acceptFileButton/default.png + ui/acceptFileButton/hover.png + ui/acceptFileButton/pressed.png + ui/acceptFileButton/style.css + ui/callButton/callButton.css + ui/callButton/callButton.png + ui/callButton/callButtonDisabled.png + ui/callButton/callButtonHover.png + ui/callButton/callButtonPressed.png + ui/callButton/callButtonRed.png + ui/callButton/callButtonRedHover.png + ui/callButton/callButtonRedPressed.png + ui/callButton/callButtonYellow.png + ui/callButton/callButtonYellowHover.png + ui/callButton/callButtonYellowPressed.png + ui/chatArea/chatArea.css + ui/chatArea/chatHead.css + ui/chatArea/innerStyle.css + ui/chatArea/scrollBarDownArrow.png + ui/chatArea/scrollBarDownArrowHover.png + ui/chatArea/scrollBarDownArrowPressed.png + ui/chatArea/scrollBarHandle.png + ui/chatArea/scrollBarUpArrow.png + ui/chatArea/scrollBarUpArrowHover.png + ui/chatArea/scrollBarUpArrowPressed.png + ui/chatroomWidgets/genericChatroomWidget.css + ui/emoteButton/emoteButton.css + ui/emoteButton/emoteButton.png + ui/emoteButton/emoteButtonHover.png + ui/emoteButton/emoteButtonPressed.png + ui/emoticonWidget/dot_page.png + ui/emoticonWidget/dot_page_current.png + ui/emoticonWidget/dot_page_hover.png + ui/emoticonWidget/emoticonWidget.css + ui/fileButton/fileButton.css + ui/fileButton/fileButton.png + ui/fileButton/fileButtonHover.png + ui/fileButton/fileButtonPressed.png + ui/fileButton/fileButtonDisabled.png + ui/fileTransferInstance/acceptFileButton.png + ui/fileTransferInstance/emptyLGreenFileButton.png + ui/fileTransferInstance/emptyLRedFileButton.png + ui/fileTransferInstance/emptyRGreenFileButton.png + ui/fileTransferInstance/emptyRRedFileButton.png + ui/fileTransferInstance/pauseFileButton.png + ui/fileTransferInstance/pauseGreyFileButton.png + ui/fileTransferInstance/resumeFileButton.png + ui/fileTransferInstance/stopFileButton.png + ui/fileTransferInstance/sliverRTEdge.png + ui/fileTransferWidget/fileTransferWidget.css + ui/friendList/friendList.css + ui/micButton/micButton.css + ui/micButton/micButton.png + ui/micButton/micButtonDisabled.png + ui/micButton/micButtonHover.png + ui/micButton/micButtonPressed.png + ui/msgEdit/msgEdit.css + ui/pauseFileButton/default.png + ui/pauseFileButton/hover.png + ui/pauseFileButton/pressed.png + ui/pauseFileButton/style.css + ui/sendButton/sendButton.css + ui/sendButton/sendButton.png + ui/sendButton/sendButtonHover.png + ui/sendButton/sendButtonPressed.png + ui/settings/mainContent.css + ui/settings/mainHead.css + ui/statusButton/dot_away.png + ui/statusButton/dot_busy.png + ui/statusButton/dot_idle.png + ui/statusButton/dot_online.png + ui/statusButton/menu_indicator.png + ui/statusButton/statusButton.css + ui/stopFileButton/default.png + ui/stopFileButton/hover.png + ui/stopFileButton/pressed.png + ui/stopFileButton/style.css + ui/videoButton/videoButton.css + ui/videoButton/videoButton.png + ui/videoButton/videoButtonDisabled.png + ui/videoButton/videoButtonHover.png + ui/videoButton/videoButtonPressed.png + ui/videoButton/videoButtonRed.png + ui/videoButton/videoButtonRedHover.png + ui/videoButton/videoButtonRedPressed.png + ui/videoButton/videoButtonYellow.png + ui/videoButton/videoButtonYellowHover.png + ui/videoButton/videoButtonYellowPressed.png + ui/volButton/volButton.png + ui/volButton/volButtonHover.png + ui/volButton/volButtonPressed.png + ui/volButton/volButton.css + ui/window/applicationIcon.png + ui/window/statusPanel.css + ui/window/window.css diff --git a/src/autoupdate.cpp b/src/autoupdate.cpp index f8ffdf9c7..8c2a2ef02 100644 --- a/src/autoupdate.cpp +++ b/src/autoupdate.cpp @@ -428,7 +428,7 @@ void AutoUpdater::checkUpdatesAsyncInteractiveWorker() return; if (Widget::getInstance()->askMsgboxQuestion(QObject::tr("Update", "The title of a message box"), - QObject::tr("An update is available, do you want to download it now ?\nIt will be installed when qTox restarts."))) + QObject::tr("An update is available, do you want to download it now?\nIt will be installed when qTox restarts."))) { downloadUpdate(); } diff --git a/src/widget/form/addfriendform.cpp b/src/widget/form/addfriendform.cpp index beef7c853..e0623efa7 100644 --- a/src/widget/form/addfriendform.cpp +++ b/src/widget/form/addfriendform.cpp @@ -98,7 +98,7 @@ void AddFriendForm::onSendTriggered() if (Settings::getInstance().getUseProxy()) { QMessageBox::StandardButton btn = QMessageBox::warning(main, "qTox", tr("qTox needs to use the Tox DNS, but can't do it through a proxy.\n\ -Ignore the proxy and connect to the Internet directly ?"), QMessageBox::Ok|QMessageBox::No, QMessageBox::No); +Ignore the proxy and connect to the Internet directly?"), QMessageBox::Ok|QMessageBox::No, QMessageBox::No); if (btn != QMessageBox::Ok) return; } diff --git a/src/widget/toxuri.cpp b/src/widget/toxuri.cpp index 62f221e44..a3f08d53e 100644 --- a/src/widget/toxuri.cpp +++ b/src/widget/toxuri.cpp @@ -79,7 +79,7 @@ ToxURIDialog::ToxURIDialog(QWidget *parent, const QString &userId, const QString setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); setWindowTitle(tr("Add a friend","Title of the window to add a friend through Tox URI")); - QLabel *friendsLabel = new QLabel(tr("Do you want to add %1 as a friend ?").arg(userId), this); + QLabel *friendsLabel = new QLabel(tr("Do you want to add %1 as a friend?").arg(userId), this); QLabel *userIdLabel = new QLabel(tr("User ID:"), this); QLineEdit *userIdEdit = new QLineEdit(userId, this); userIdEdit->setCursorPosition(0); From f2b14ccf5ec240fed3e4132c29b011efb214be87 Mon Sep 17 00:00:00 2001 From: JoGitqTox Date: Sat, 29 Nov 2014 15:53:02 +0100 Subject: [PATCH 193/253] Update de.ts Two chnages --- translations/de.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/translations/de.ts b/translations/de.ts index 373221256..25ca913e6 100644 --- a/translations/de.ts +++ b/translations/de.ts @@ -616,7 +616,7 @@ Wollen Sie ein anderes probieren? Show contacts' status changes - Zeige Statusänderungen der Kontakte + Zeigt Statusänderungen der Kontakte @@ -626,7 +626,7 @@ Wollen Sie ein anderes probieren? Focus qTox when a message is received - Bringe qTox in den Vordergrund, wenn eine Nachricht eintrifft + Bringt qTox in den Vordergrund, wenn eine Nachricht eintrifft From 6452cfa70276180ac07ab3757e394a6f26e1758b Mon Sep 17 00:00:00 2001 From: Dubslow Date: Sat, 29 Nov 2014 22:32:12 -0600 Subject: [PATCH 194/253] put tr list in "alphabetical" order --- src/widget/form/settings/generalform.cpp | 42 ++++++++++++------------ 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/widget/form/settings/generalform.cpp b/src/widget/form/settings/generalform.cpp index 586f3280c..9a7436b4a 100644 --- a/src/widget/form/settings/generalform.cpp +++ b/src/widget/form/settings/generalform.cpp @@ -30,23 +30,23 @@ #include "src/autoupdate.h" -static QStringList locales = {"bg", "de", "en", "fr", "es", "it", "mannol", "pirate", "pl", "ru", "fi", "sv", "uk"}; -static QStringList langs = {"Български", "Deutsch", "English", "Français", "Español", "Italiano", "mannol", "Pirate", "Polski", "Русский", "Suomi", "Svenska", "Українська"}; +static QStringList locales = {"bg", "de", "en", "es", "fr", "it", "mannol", "pirate", "pl", "ru", "fi", "sv", "uk"}; +static QStringList langs = {"Български", "Deutsch", "English", "Español", "Français", "Italiano", "mannol", "Pirate", "Polski", "Русский", "Suomi", "Svenska", "Українська"}; static QStringList timeFormats = {"hh:mm AP", "hh:mm", "hh:mm:ss AP", "hh:mm:ss"}; GeneralForm::GeneralForm(SettingsWidget *myParent) : GenericForm(tr("General"), QPixmap(":/img/settings/general.png")) { - parent = myParent; - + parent = myParent; + bodyUI = new Ui::GeneralSettings; bodyUI->setupUi(this); bodyUI->checkUpdates->setVisible(AUTOUPDATE_ENABLED); bodyUI->checkUpdates->setChecked(Settings::getInstance().getCheckUpdates()); bodyUI->trayLayout->addStretch(); - + bodyUI->cbEnableIPv6->setChecked(Settings::getInstance().getEnableIPv6()); for (int i = 0; i < langs.size(); i++) bodyUI->transComboBox->insertItem(i, langs[i]); @@ -62,7 +62,7 @@ GeneralForm::GeneralForm(SettingsWidget *myParent) : bodyUI->autoSaveFilesDir->setText(Settings::getInstance().getGlobalAutoAcceptDir()); bodyUI->showInFront->setChecked(Settings::getInstance().getShowInFront()); bodyUI->cbFauxOfflineMessaging->setChecked(Settings::getInstance().getFauxOfflineMessaging()); - + for (auto entry : SmileyPack::listSmileyPacks()) { bodyUI->smileyPackBrowser->addItem(entry.first, entry.second); @@ -72,31 +72,31 @@ GeneralForm::GeneralForm(SettingsWidget *myParent) : bodyUI->styleBrowser->addItem(tr("None")); bodyUI->styleBrowser->addItems(QStyleFactory::keys()); - + if(QStyleFactory::keys().contains(Settings::getInstance().getStyle())) bodyUI->styleBrowser->setCurrentText(Settings::getInstance().getStyle()); else bodyUI->styleBrowser->setCurrentText(tr("None")); - + for (QString color : Style::themeColorNames) bodyUI->themeColorCBox->addItem(color); bodyUI->themeColorCBox->setCurrentIndex(Settings::getInstance().getThemeColor()); bodyUI->emoticonSize->setValue(Settings::getInstance().getEmojiFontPointSize()); - + QStringList timestamps; timestamps << QString("%1 - %2").arg(timeFormats[0],QTime::currentTime().toString(timeFormats[0])) << QString("%1 - %2").arg(timeFormats[1],QTime::currentTime().toString(timeFormats[1])) << QString("%1 - %2").arg(timeFormats[2],QTime::currentTime().toString(timeFormats[2])) << QString("%1 - %2").arg(timeFormats[3],QTime::currentTime().toString(timeFormats[3])); bodyUI->timestamp->addItems(timestamps); - + bodyUI->timestamp->setCurrentText(QString("%1 - %2").arg(Settings::getInstance().getTimestampFormat(), QTime::currentTime().toString(Settings::getInstance().getTimestampFormat())) ); //idiot proof enough? - + bodyUI->autoAwaySpinBox->setValue(Settings::getInstance().getAutoAwayTime()); - + bodyUI->cbEnableUDP->setChecked(!Settings::getInstance().getForceTCP()); bodyUI->proxyAddr->setText(Settings::getInstance().getProxyAddr()); int port = Settings::getInstance().getProxyPort(); @@ -125,12 +125,12 @@ GeneralForm::GeneralForm(SettingsWidget *myParent) : connect(bodyUI->smileyPackBrowser, SIGNAL(currentIndexChanged(int)), this, SLOT(onSmileyBrowserIndexChanged(int))); connect(bodyUI->styleBrowser, SIGNAL(currentTextChanged(QString)), this, SLOT(onStyleSelected(QString))); connect(bodyUI->themeColorCBox, SIGNAL(currentIndexChanged(int)), this, SLOT(onThemeColorChanged(int))); - connect(bodyUI->emoticonSize, SIGNAL(editingFinished()), this, SLOT(onEmoticonSizeChanged())); + connect(bodyUI->emoticonSize, SIGNAL(editingFinished()), this, SLOT(onEmoticonSizeChanged())); connect(bodyUI->timestamp, SIGNAL(currentIndexChanged(int)), this, SLOT(onTimestampSelected(int))); //connection connect(bodyUI->cbEnableIPv6, &QCheckBox::stateChanged, this, &GeneralForm::onEnableIPv6Updated); connect(bodyUI->cbEnableUDP, &QCheckBox::stateChanged, this, &GeneralForm::onUDPUpdated); - connect(bodyUI->cbUseProxy, &QCheckBox::stateChanged, this, &GeneralForm::onUseProxyUpdated); + connect(bodyUI->cbUseProxy, &QCheckBox::stateChanged, this, &GeneralForm::onUseProxyUpdated); connect(bodyUI->proxyAddr, &QLineEdit::editingFinished, this, &GeneralForm::onProxyAddrEdited); connect(bodyUI->proxyPort, SIGNAL(valueChanged(int)), this, SLOT(onProxyPortEdited(int))); connect(bodyUI->reconnectButton, &QPushButton::clicked, this, &GeneralForm::onReconnectClicked); @@ -185,7 +185,7 @@ void GeneralForm::onStyleSelected(QString style) Settings::getInstance().setStyle("None"); else Settings::getInstance().setStyle(style); - + this->setStyle(QStyleFactory::create(style)); parent->setBodyHeadStyle(style); } @@ -210,7 +210,7 @@ void GeneralForm::onAutoAwayChanged() void GeneralForm::onAutoAcceptFileChange() { Settings::getInstance().setAutoSaveEnabled(bodyUI->autoacceptFiles->isChecked()); - + if(bodyUI->autoacceptFiles->isChecked() == true) connect(bodyUI->autoSaveFilesDir, SIGNAL(clicked()), this, SLOT(onAutoSaveDirChange())); else @@ -223,7 +223,7 @@ void GeneralForm::onAutoSaveDirChange() QString directory = QFileDialog::getExistingDirectory(0, tr("Choose an auto accept directory","popup title")); if(directory.isEmpty()) directory = previousDir; - + Settings::getInstance().setGlobalAutoAcceptDir(directory); bodyUI->autoSaveFilesDir->setText(directory); } @@ -289,17 +289,17 @@ void GeneralForm::reloadSmiles() QStringList smiles; smiles << ":)" << ";)" << ":p" << ":O" << ":["; //just in case... - for(int i = 0; i < emoticons.size(); i++) + for(int i = 0; i < emoticons.size(); i++) smiles.push_front(emoticons.at(i).first()); - + int pixSize = 30; bodyUI->smile1->setPixmap(SmileyPack::getInstance().getAsIcon(smiles[0]).pixmap(pixSize, pixSize)); bodyUI->smile2->setPixmap(SmileyPack::getInstance().getAsIcon(smiles[1]).pixmap(pixSize, pixSize)); bodyUI->smile3->setPixmap(SmileyPack::getInstance().getAsIcon(smiles[2]).pixmap(pixSize, pixSize)); bodyUI->smile4->setPixmap(SmileyPack::getInstance().getAsIcon(smiles[3]).pixmap(pixSize, pixSize)); bodyUI->smile5->setPixmap(SmileyPack::getInstance().getAsIcon(smiles[4]).pixmap(pixSize, pixSize)); - - bodyUI->smile1->setToolTip(smiles[0]); + + bodyUI->smile1->setToolTip(smiles[0]); bodyUI->smile2->setToolTip(smiles[1]); bodyUI->smile3->setToolTip(smiles[2]); bodyUI->smile4->setToolTip(smiles[3]); From 2d0fb3595b58fa67850b6b698c70fd5d9587dbb4 Mon Sep 17 00:00:00 2001 From: Zetok Zalbavar Date: Sun, 30 Nov 2014 09:06:42 +0000 Subject: [PATCH 195/253] Update Polish translation --- translations/pl.ts | 575 +++++++++++++++++++++++++++++++-------------- 1 file changed, 402 insertions(+), 173 deletions(-) diff --git a/translations/pl.ts b/translations/pl.ts index 68da9823f..4c7666729 100644 --- a/translations/pl.ts +++ b/translations/pl.ts @@ -4,7 +4,7 @@ AVForm - + Audio/Video Audio/Wideo @@ -29,12 +29,12 @@ Urządzenie wejściowe - + Video Settings Ustawienia wideo - + Resolution Rozdzielczość @@ -49,22 +49,27 @@ Mikrofon - + + Rescan audio devices + Ponownie skanuj urządzenia audio + + + Hue Odcień - + Brightness Jasność - + Saturation Nasycenie - + Contrast Kontrast @@ -114,7 +119,7 @@ qTox needs to use the Tox DNS, but can't do it through a proxy. -Ignore the proxy and connect to the Internet directly ? +Ignore the proxy and connect to the Internet directly? qTox ma użyć Tox DNS, lecz nie może tego zrobić przez proxy. Zignorować proxy i połączyć się z Internetem bezpośrednio? @@ -125,57 +130,120 @@ Zignorować proxy i połączyć się z Internetem bezpośrednio? Ten Tox ID nie istnieje + + AdvancedForm + + + Advanced + Zaawansowane + + + + FULL - very safe, slowest (recommended) + Pełne - bardzo bezpieczne, najwolniejsze (rekomendowane) + + + + NORMAL - almost as safe as FULL, about 20% faster than FULL + Normalne - prawie tak bezpieczne jak Pełne, około 20% od niego szybsze + + + + OFF - disables all safety, when something goes wrong your history may be lost, fastest (not recommended) + Wyłączone - wyłącza bezpieczeństwo, gdy coś pójdzie źle twoja historia może zostać utracona. Najszybsze (nie rekomendowane) + + + + AdvancedSettings + + + Form + + + + + <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">IMPORTANT NOTE</span></p><p><span style=" color:#ff0000;">Unless you </span><span style=" font-weight:600; color:#ff0000;">really</span><span style=" color:#ff0000;"> know what you are doing, please do </span><span style=" font-weight:600; color:#ff0000;">not</span><span style=" color:#ff0000;"> change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history.</span></p></body></html> + + + + + Reset to default settings + Reset do domyślnych ustawień + + + + History + Historia + + + + <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_synchronous"><span style=" text-decoration: underline; color:#0000ff;">Synchronous writing to DB</span></a></p></body></html> + better translation? + <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_synchronous"><span style=" text-decoration: underline; color:#0000ff;">Zsynchronizowany zapis do DB</span></a></p></body></html> + + ChatForm - + Load History... Wczytaj historię... - + Send a file Wyślij plik - + + File not read + better translation? + Nie otwarto pliku + + + + qTox wasn't able to open %1 + qTox nie był w stanie otworzyć %1 + + + Bad Idea Zły pomysł - + You're trying to send a special (sequential) file, that's not going to work! Nie można przesłać tego pliku! - - %1 calling + + %1 is calling %1 dzwoni - + %1 stopped calling ? Nieodebrana rozmowa od %1 - + Calling to %1 at least gender neutral... Dzwonisz do %1 - + Call rejected Połączenie zostało odrzucone - + Call with %1 ended. %2 Rozmowa z %1 została zakończona. %2 - + Call duration: Czas trwania rozmowy: @@ -191,105 +259,108 @@ Zignorować proxy i połączyć się z Internetem bezpośrednio? Core - + Toxing on qTox better translation? Toxuję na qTox - + qTox User Użytkownik qToxa - + + Friend is already added + Znajomy jest już dodany + + + Encryption error Błąd szyfrowania - + The .tox file is encrypted, but encryption was not checked, continuing regardless. better translation? Plik .tox jest zaszyfrowany, lecz szyfrowanie nie zostało sprawdzone. Kontynuowanie. - + Tox datafile decryption password Hasło odszyfrowujące plik danych Tox - - - + + + Password error better translation? Błąd hasła - - + + Failed to setup password. Empty password. Nie udało się ustawić hasła. Puste hasło. - + Try Again Spróbuj ponownie - + Change profile Zmień profil - + Reinit current profile Zainicjuj ponownie obecny profil - + Wrong password has been entered Podano złe hasło - + History Log decryption password Hasło odszyfrowujące historię - + Encrypted log Zaszyfrowany log - - Your history is encrypted with different password + + Your history is encrypted with different password. Do you want to try another password? Twoja historia jest zaszyfrowana innym hasłem Czy chcesz spróbować z innym hasłem? - - Loggin - pasuje do kontekstu - Błąd + + History + Historia - - Due to incorret password logging will be disabled - better translation? - Logowanie będzie wyłączone z powodu złego hasła + + Due to incorret password history will be disabled. + Historia będzie wyłączona z powodu złego hasła. - + NO Password better translation? BRAK hasła - + Will be saved without encryption! Zostanie zapisane bez szyfrowania! @@ -297,25 +368,25 @@ Czy chcesz spróbować z innym hasłem? FileTransferInstance - + Save a file Title of the file saving dialog Zapisz plik - + Location not writable Title of permissions popup Nie można zapisać w lokacji - + You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Nie masz uprawnienia by zapisać w tej lokacji. Wybierz inną lub anuluj zapis. - + ETA ETA @@ -395,14 +466,17 @@ Czy chcesz spróbować z innym hasłem? Odbieraj pliki automatycznie - + User alias Alias użytkownika - - Alias: - Alias: + + You can also set this by clicking the chat form name. +Alias: + better translation? + Możesz także to ustawić klikając na nick w oknie rozmowy. +Alias: @@ -426,31 +500,31 @@ Czy chcesz spróbować z innym hasłem? GeneralForm - + General Główne - - + + None Brak - + Choose an auto accept directory popup title better translation? Wybierz domyślną ścieżkę dla plików - + Call active popup title Aktywna rozmowa - + You can't disconnect while a call is active! popup text Nie możesz się rozłączyć w trakcie rozmowy! @@ -481,49 +555,55 @@ Czy chcesz spróbować z innym hasłem? Zrób Tox przenośnym - + Start in tray Uruchamiaj w trayu - + Close to tray Zamykaj do traya - + Minimize to tray Minimalizuj do traya - + Show contacts' status changes Pokazuj zmiany statusów - + Provided in minutes Podane w minutach - + Set to 0 to disable Ustaw na 0 by wyłączyć - + + Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. + force tcp checkbox tooltip + Wyłączenie pozwala np. na toxowanie przez Tora. Niestety obciąża to sieć Tox, więc używaj tylko w razie potrzeby. + + + Enable UDP (recommended) Text on checkbox to disable UDP Używaj UDP (zalecane) - + Reconnect reconnect button Połącz ponownie - + minutes min @@ -533,93 +613,122 @@ Czy chcesz spróbować z innym hasłem? Tłumaczenie - + + Show system tray icon + better translation? + Pokaż ikonę w trayu + + + + Check for updates on startup (unstable) + Sprawdzaj czy są dostępne aktualizacje przy uruchomieniu (niestabilne) + + + + Focus qTox when a message is received + better translation? + Przywołaj qTox gdy otrzyma się nową wiadomość + + + + Faux offline messaging + better translation? + Faux wiadomości offline + + + Auto away after (0 to disable) Automatycznie nieobecny/a po (0 by wyłączyć) - + + You can set this on a per-friend basis by right clicking them. + autoaccept cb tooltip + better translation? + Możesz to ustawić dla każdego znajomego klikając nań prawym. + + + Autoaccept files Automatycznie akceptuj pliki - + Save files in Zapisz pliki w - + PushButton - + Theme Motyw - + Use emoticons better translation? Używaj emotikony - + Smiley Pack Text on smiley pack label Paczka Uśmiechów - + Style Styl - + + Theme color + Kolor motywu + + + Emoticon size better translation? Rozmiar emotikonów - + px px - + Timestamp format Format czasu - + Connection Settings Ustawienia połączenia - + Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Używaj IPv6 (zalecane) - - This allows, e.g., toxing over Tor. It adds load to the Tox network however, so use only when necessary. - force tcp checkbox tooltip - To pozwala np. na toxowanie przez Tora. Niestety obciąża to sieć Tox, więc używaj tylko w razie potrzeby. - - - + Use proxy (SOCKS5) Używaj proxy (SOCKS5) - + Address Text on proxy addr label Adres - + Port Text on proxy port label Port @@ -628,53 +737,55 @@ Czy chcesz spróbować z innym hasłem? GenericChatForm - + Send message Wyślij wiadomość - + Smileys Uśmiechy - + Send file(s) Wyślij plik(i) - - Audio call - Rozmowa audio + + Audio call: RED means you're on a call + better translation? + Rozmowa audio: Czerwone oznacza aktywną rozmowę - - Video call - Rozmowa wideo + + Video call: RED means you're on a call + better translation? + Rozmowa wideo: Czerwone oznacza aktywną rozmowę - - Toggle speakers volume - Włącz/Wyłącz głośniki + + Toggle speakers volume: RED is OFF + Włącz/Wyłącz dźwięk: Czerwony - wyłączony - - Toggle microphone - Włącz/wyłącz mikrofon + + Toggle microphone: RED is OFF + Włącz/Wyłącz mikrofon: Czerwony - wyłączony - - + + Save chat log Zapisz historię rozmowy - + Clear displayed messages Wyczyść wyświetlane wiadomości - + Cleared Wyczyszczono @@ -682,13 +793,13 @@ Czy chcesz spróbować z innym hasłem? GroupChatForm - + %1 users in chat Number of users in chat %1 użytkowników w czacie - + %1 users in chat %1 użytkowników w czacie @@ -696,23 +807,41 @@ Czy chcesz spróbować z innym hasłem? GroupWidget - - + + %1 users in chat %1 użytkowników w czacie - - + + 0 users in chat 0 użytkowników w czacie - + + Set title... + better translation? (I have considered translating this as 'tytuł', but it doesn't look well along with other things IMHO) + Ustaw nazwę... + + + Quit group Menu to quit a groupchat Opuść grupę + + + Group title + Nazwa grupy + + + + You can also set this by clicking the chat form name. +Title: + Możesz to też ustawić klikając na nazwę w oknie czatu. +Nazwa: + IdentityForm @@ -722,103 +851,124 @@ Czy chcesz spróbować z innym hasłem? Tożsamość - + Call active popup title Rozmowa w trakcie - + You can't switch profiles while a call is active! popup text Nie można zmienić profilu podczas aktywnego połączenia! - + Rename "%1" renaming a profile Zmień nazwę "%1" - + Profile already exists rename confirm title Profil już istnieje - + A profile named "%1" already exists. Do you want to erase it? rename confirm text Profil pod nazwą "%1" już istnieje. Czy chcesz go usunąć? - + Export profile save dialog title Eksportuj profil - + Tox save file (*.tox) save dialog filter Plik zapisu Tox (*.tox) - + + Failed to remove file + Nie udało się usunąć pliku + + + + The file you chose to overwrite could not be removed first. + better translation? + Nie udało się usunąć wybranego pliku do nadpisania. + + + + Failed to copy file + Nie udało się skopiować pliku + + + + The file you chose could not be written to. + Nie udało się zapisać do wybranego pliku. + + + Profile currently loaded current profile deletion warning title Profil obecnie załadowany - + This profile is currently in use. Please load a different profile before deleting this one. current profile deletion warning text Ten profil jest obecnie w użyciu. Proszę załaduj inny profil przed usunięciem tego. - + Deletion imminent! deletion confirmation title Usuwanie profilu! - + Are you sure you want to delete this profile? deletion confirmation text Czy na pewno chcesz usunąć ten profil? - + Import profile import dialog title Importuj profil - + Tox save file (*.tox) import dialog filter Plik zapisu Tox (*.tox) - + Ignoring non-Tox file popup title Zignorowano niepoprawny plik profilu - + Warning: you've chosen a file that is not a Tox save file; ignoring. popup text Ostrzeżenie: Wybrano plik który nie jest plikiem zapisu Tox; zignorowano. - + Profile already exists import confirm title Profil już istnieje - + A profile named "%1" already exists. Do you want to erase it? import confirm text Profil pod nazwą "%1" już istnieje. Czy chcesz go usunąć? @@ -950,28 +1100,28 @@ Czy chcesz spróbować z innym hasłem? Twój status - + Add friends Dodaj znajomych - + Create a group chat Utwórz czat grupowy - + View completed file transfers Zobacz zakończone transfery plików - + Change your settings translated as "change settings"; seems to be simpler this way Zmień ustawienia - + Close Zamknij @@ -992,12 +1142,12 @@ Czy chcesz spróbować z innym hasłem? Prywatność - + Encrypted log Zaszyfrowany log - + You already have history log file encrypted with different password Do you want to delete old history file? Już masz zaszyfrowany plik historii z innym hasłem. @@ -1032,6 +1182,21 @@ Czy chcesz usunąć stary plik historii? Encrypt History Zaszyfruj historię + + + Nospam + Nospam + + + + HHHHHHHH + + + + + Generate random nospam + Wygeneruj losowy nospam + QObject @@ -1076,6 +1241,60 @@ Czy chcesz usunąć stary plik historii? Default message in Tox URI friend requests. Write something appropriate! Może Tox ze mną? + + + Update + The title of a message box + Aktualizacja + + + + An update is available, do you want to download it now? +It will be installed when qTox restarts. + Aktualizacja jest dostępna, czy chcesz ją teraz pobrać? +Zostanie zainstalowana po restarcie qToxa. + + + + Tox URI to parse + + + + + Starts new instance and loads specified profile. + better translation? + Uruchamia nową instancję i ładuje wybrany profil. + + + + profile + profil + + + + Default + Domyślny + + + + Blue + Niebieski + + + + Olive + Oliwkowy + + + + Red + Czerwony + + + + Violet + Fioletowy + SetPasswordDialog @@ -1156,7 +1375,7 @@ Czy chcesz usunąć stary plik historii? - Do you want to add %1 as a friend ? + Do you want to add %1 as a friend? Czy chcesz dodać %1 do znajomych? @@ -1185,150 +1404,160 @@ Czy chcesz usunąć stary plik historii? Widget - + Online Online - + Away Nieobecny/a - + Busy Zajęty/a - + &Quit &Wyjdź - + Change status to: Zmień status na: - + Online Button to set your status to 'Online' Online - + Away Button to set your status to 'Away' Nieobecny/a - + Busy Button to set your status to 'Busy' Zajęty/a - + Choose a profile Wybierz profil - + Please choose which identity to use Proszę wybierz która tożsamość ma być użyta - + Choose a profile picture Wybierz obrazek profilu - - - + + + Error Błąd - + Unable to open this file Nie można otworzyć tego pliku - + Unable to read this image Nie można odczytać tego obrazka - + This image is too big Ten obrazek jest zbyt wielki - + Toxcore failed to start, the application will terminate after you close this message. Nie udało się uruchomić Toxcore, aplikacja zamknie się po zamknięciu tej wiadomości. - + toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text Nie udało się uruchomić toxcore z twoimi ustawieniami proxy. qTox nie może działać, proszę zmodyfikuj ustawienia i zrestartuj. - + Add friend Dodaj znajomych - + File transfers Transfery plików - + Settings Ustawienia - + + Couldn't request friendship + better translation? + Nie udało się dodać do znajomych + + + away contact status nieobecna/y - + busy contact status zajęta/y - + offline contact status offline - + online contact status online - + %1 is now %2 e.g. "Dubslow is now online" %1 jest teraz %2 - + <Unknown> Placeholder when we don't know someone's name in a group chat <Nieznany/a> - - + + %1 has set the title to %2 + %1 ustawił(a) nazwę na %2 + + + Message failed to send Nie udało się wysłać wiadmości From 90e8afa24de0834b1e9215e17f3609cad3ca30d8 Mon Sep 17 00:00:00 2001 From: Dubslow Date: Sun, 30 Nov 2014 09:20:32 -0600 Subject: [PATCH 196/253] update core api fixes #882 --- src/core.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core.cpp b/src/core.cpp index 2abd56f54..5268e4285 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -258,8 +258,8 @@ void Core::start() toxav_register_callstate_callback(toxav, onAvRequestTimeout, av_OnRequestTimeout, this); toxav_register_callstate_callback(toxav, onAvPeerTimeout, av_OnPeerTimeout, this); - toxav_register_audio_callback(playCallAudio, this); - toxav_register_video_callback(playCallVideo, this); + toxav_register_audio_callback(toxav, playCallAudio, this); + toxav_register_video_callback(toxav, playCallVideo, this); QPixmap pic = Settings::getInstance().getSavedAvatar(getSelfId().toString()); if (!pic.isNull() && !pic.size().isEmpty()) From 545cde519c48a2b147fe202151bbdaaa5e1ff9b2 Mon Sep 17 00:00:00 2001 From: "A. L'mao" Date: Sun, 30 Nov 2014 11:04:55 -0800 Subject: [PATCH 197/253] Destroy AL context before closing old device I refuse to compile this code, so it's not tested. This should fix the segfault in alcDestroyContext. --- src/audio.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/audio.cpp b/src/audio.cpp index 6ade89edd..a394e72d4 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -87,8 +87,6 @@ void Audio::openOutput(const QString& outDevDescr) { auto* tmp = alOutDev; alOutDev = nullptr; - if (tmp) - alcCloseDevice(tmp); if (outDevDescr.isEmpty()) alOutDev = alcOpenDevice(nullptr); else @@ -104,6 +102,8 @@ void Audio::openOutput(const QString& outDevDescr) alcMakeContextCurrent(nullptr); alcDestroyContext(alContext); } + if (tmp) + alcCloseDevice(tmp); alContext=alcCreateContext(alOutDev,nullptr); if (!alcMakeContextCurrent(alContext)) { From 0810559bcbf51ead566ea399fbb009f5f1d8aa8f Mon Sep 17 00:00:00 2001 From: novist Date: Fri, 28 Nov 2014 11:16:37 +0200 Subject: [PATCH 198/253] Window geometry saved when window is resized Splitter position saved when splitter is moved Fixed restoring of window geometry when "Start in tray" is unchecked --- src/widget/widget.cpp | 41 ++++++++++++++++++++++++++++++++--------- src/widget/widget.h | 6 +++++- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index 9256d4e97..bc39991c5 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -73,7 +73,12 @@ Widget::Widget(QWidget *parent) void Widget::init() { ui->setupUi(this); - + + //restore window state + restoreGeometry(Settings::getInstance().getWindowGeometry()); + restoreState(Settings::getInstance().getWindowState()); + ui->mainSplitter->restoreState(Settings::getInstance().getSplitterState()); + if (QSystemTrayIcon::isSystemTrayAvailable()) { icon = new QSystemTrayIcon(this); @@ -127,11 +132,6 @@ void Widget::init() idleTimer->setSingleShot(true); setIdleTimer(Settings::getInstance().getAutoAwayTime()); - //restore window state - restoreGeometry(Settings::getInstance().getWindowGeometry()); - restoreState(Settings::getInstance().getWindowState()); - ui->mainSplitter->restoreState(Settings::getInstance().getSplitterState()); - layout()->setContentsMargins(0, 0, 0, 0); ui->friendList->setStyleSheet(Style::resolve(Style::getStylesheet(":ui/friendList/friendList.css"))); @@ -257,6 +257,7 @@ void Widget::init() connect(ui->settingsButton, SIGNAL(clicked()), this, SLOT(onSettingsClicked())); connect(ui->nameLabel, SIGNAL(textChanged(QString, QString)), this, SLOT(onUsernameChanged(QString, QString))); connect(ui->statusLabel, SIGNAL(textChanged(QString, QString)), this, SLOT(onStatusMessageChanged(QString, QString))); + connect(ui->mainSplitter, &QSplitter::splitterMoved, this, &Widget::onSplitterMoved); connect(profilePicture, SIGNAL(clicked()), this, SLOT(onAvatarClicked())); connect(setStatusOnline, SIGNAL(triggered()), this, SLOT(setStatusOnline())); connect(setStatusAway, SIGNAL(triggered()), this, SLOT(setStatusAway())); @@ -336,9 +337,8 @@ void Widget::closeEvent(QCloseEvent *event) } else { - Settings::getInstance().setWindowGeometry(saveGeometry()); - Settings::getInstance().setWindowState(saveState()); - Settings::getInstance().setSplitterState(ui->mainSplitter->saveState()); + saveWindowGeometry(); + saveSplitterGeometry(); QWidget::closeEvent(event); } } @@ -354,6 +354,12 @@ void Widget::changeEvent(QEvent *event) } } +void Widget::resizeEvent(QResizeEvent *event) +{ + Q_UNUSED(event); + saveWindowGeometry(); +} + QString Widget::detectProfile() { QDir dir(Settings::getSettingsDirPath()); @@ -1008,6 +1014,17 @@ void Widget::removeGroup(Group* g, bool fake) contactListWidget->show(); } +void Widget::saveWindowGeometry() +{ + Settings::getInstance().setWindowGeometry(saveGeometry()); + Settings::getInstance().setWindowState(saveState()); +} + +void Widget::saveSplitterGeometry() +{ + Settings::getInstance().setSplitterState(ui->mainSplitter->saveState()); +} + void Widget::removeGroup(int groupId) { removeGroup(GroupList::findGroup(groupId)); @@ -1157,6 +1174,12 @@ void Widget::onSetShowSystemTray(bool newValue){ icon->setVisible(newValue); } +void Widget::onSplitterMoved(int pos, int index) +{ + Q_UNUSED(pos); + Q_UNUSED(index); + saveSplitterGeometry(); +} QMessageBox::StandardButton Widget::showWarningMsgBox(const QString& title, const QString& msg, QMessageBox::StandardButtons buttons) { diff --git a/src/widget/widget.h b/src/widget/widget.h index 285e59bf6..5ffc59df0 100644 --- a/src/widget/widget.h +++ b/src/widget/widget.h @@ -72,7 +72,8 @@ public: virtual void closeEvent(QCloseEvent *event); virtual void changeEvent(QEvent *event); - + virtual void resizeEvent(QResizeEvent *event); + void clearAllReceipts(); void reloadTheme(); @@ -132,6 +133,7 @@ private slots: void onUserAway(); void getPassword(QString info, int passtype, uint8_t* salt); void onSetShowSystemTray(bool newValue); + void onSplitterMoved(int pos, int index); private: void init(); @@ -140,6 +142,8 @@ private: Group* createGroup(int groupId); void removeFriend(Friend* f, bool fake = false); void removeGroup(Group* g, bool fake = false); + void saveWindowGeometry(); + void saveSplitterGeometry(); QString askProfiles(); QString detectProfile(); QSystemTrayIcon *icon; From 9df3fdc2a099fd30731d171c3f59d40f6336d366 Mon Sep 17 00:00:00 2001 From: Dubslow Date: Thu, 27 Nov 2014 16:35:56 -0600 Subject: [PATCH 199/253] move new functions to a more logical location --- src/widget/widget.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index bc39991c5..ccb2f24e5 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -1014,17 +1014,6 @@ void Widget::removeGroup(Group* g, bool fake) contactListWidget->show(); } -void Widget::saveWindowGeometry() -{ - Settings::getInstance().setWindowGeometry(saveGeometry()); - Settings::getInstance().setWindowState(saveState()); -} - -void Widget::saveSplitterGeometry() -{ - Settings::getInstance().setSplitterState(ui->mainSplitter->saveState()); -} - void Widget::removeGroup(int groupId) { removeGroup(GroupList::findGroup(groupId)); @@ -1174,6 +1163,17 @@ void Widget::onSetShowSystemTray(bool newValue){ icon->setVisible(newValue); } +void Widget::saveWindowGeometry() +{ + Settings::getInstance().setWindowGeometry(saveGeometry()); + Settings::getInstance().setWindowState(saveState()); +} + +void Widget::saveSplitterGeometry() +{ + Settings::getInstance().setSplitterState(ui->mainSplitter->saveState()); +} + void Widget::onSplitterMoved(int pos, int index) { Q_UNUSED(pos); From 1340e00f69ee42cb67250162a1183fba7e486b2b Mon Sep 17 00:00:00 2001 From: Dubslow Date: Mon, 1 Dec 2014 14:02:44 -0600 Subject: [PATCH 200/253] re-move idle timer construction before restoreGeometry --- src/widget/widget.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index ccb2f24e5..f395de6e0 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -74,6 +74,10 @@ void Widget::init() { ui->setupUi(this); + idleTimer = new QTimer(); + idleTimer->setSingleShot(true); + setIdleTimer(Settings::getInstance().getAutoAwayTime()); + //restore window state restoreGeometry(Settings::getInstance().getWindowGeometry()); restoreState(Settings::getInstance().getWindowState()); @@ -128,10 +132,6 @@ void Widget::init() ui->statusbar->hide(); ui->menubar->hide(); - idleTimer = new QTimer(); - idleTimer->setSingleShot(true); - setIdleTimer(Settings::getInstance().getAutoAwayTime()); - layout()->setContentsMargins(0, 0, 0, 0); ui->friendList->setStyleSheet(Style::resolve(Style::getStylesheet(":ui/friendList/friendList.css"))); From 48e5315cdf2f3e24b0f860145ce86a125bb9a300 Mon Sep 17 00:00:00 2001 From: Dubslow Date: Mon, 1 Dec 2014 14:55:35 -0600 Subject: [PATCH 201/253] clarify systray show-status status --- src/widget/form/settings/generalsettings.ui | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/widget/form/settings/generalsettings.ui b/src/widget/form/settings/generalsettings.ui index 79dc5712f..c4d07dfd8 100644 --- a/src/widget/form/settings/generalsettings.ui +++ b/src/widget/form/settings/generalsettings.ui @@ -163,6 +163,9 @@ Tray icon displays user status + + This is a temporary work around until proper systray status icons are available. + From 4adfa986b52e854b928d5b8e2b82bfec8d164822 Mon Sep 17 00:00:00 2001 From: Dubslow Date: Mon, 1 Dec 2014 15:28:53 -0600 Subject: [PATCH 202/253] touchup, more appropriate legal message --- src/platform/timer_osx.cpp | 7 ++++++- src/widget/widget.cpp | 30 ++++++++++++------------------ 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/platform/timer_osx.cpp b/src/platform/timer_osx.cpp index 43f731e5a..5f55e4b48 100644 --- a/src/platform/timer_osx.cpp +++ b/src/platform/timer_osx.cpp @@ -1,5 +1,10 @@ /* - Copyright (C) 2014 by Project Tox + Pidgin is the legal property of its developers, whose names are too numerous + to list here. Please refer to the COPYRIGHT file distributed with this + source distribution (which can be found at + ). + + Additional Copyright (C) 2014 by Project Tox This file is part of qTox, a Qt-based graphical interface for Tox. diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index bb2136d1a..500213a37 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -523,7 +523,6 @@ void Widget::onStatusSet(Status status) { case Status::Online: ui->statusButton->setProperty("status" ,"online"); - qDebug() << "Widget: something set the status to online"; break; case Status::Away: ui->statusButton->setProperty("status" ,"away"); @@ -1081,31 +1080,26 @@ bool Widget::event(QEvent * e) void Widget::onUserAwayCheck() { uint32_t autoAwayTime = Settings::getInstance().getAutoAwayTime() * 60 * 1000; - if(ui->statusButton->property("status").toString() == "online") + + if (ui->statusButton->property("status").toString() == "online") { - if(autoAwayTime) + if (autoAwayTime && Platform::getIdleTime() >= autoAwayTime) { - if (Platform::getIdleTime() >= autoAwayTime) - { - qDebug() << "Widget: auto away activated" << QTime::currentTime().toString(); - emit statusSet(Status::Away); - autoAwayActive = true; - } + qDebug() << "Widget: auto away activated at" << QTime::currentTime().toString(); + emit statusSet(Status::Away); + autoAwayActive = true; } } - else if(ui->statusButton->property("status").toString() == "away") + else if (ui->statusButton->property("status").toString() == "away") { - if(autoAwayActive) + if (autoAwayActive && (!autoAwayTime || Platform::getIdleTime() < autoAwayTime)) { - if(!autoAwayTime || Platform::getIdleTime() < autoAwayTime) - { - qDebug() << "Widget: auto away deactivated" << QTime::currentTime().toString(); - emit statusSet(Status::Online); - autoAwayActive = false; - } + qDebug() << "Widget: auto away deactivated at" << QTime::currentTime().toString(); + emit statusSet(Status::Online); + autoAwayActive = false; } } - else if(autoAwayActive) + else if (autoAwayActive) autoAwayActive = false; } From aae9233dc14bce060be3ebfbe375ea121f777eba Mon Sep 17 00:00:00 2001 From: Dubslow Date: Mon, 1 Dec 2014 16:51:53 -0600 Subject: [PATCH 203/253] fix os x compiling, update dependencies --- INSTALL.md | 8 ++++---- simple_make.sh | 6 +++--- src/platform/timer_x11.cpp | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 718034e83..e94510b1a 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -115,23 +115,23 @@ The following steps assumes that you cloned the repository at "/home/user/qTox". Debian: ```bash -sudo apt-get install build-essential qt5-qmake qt5-default libopenal-dev libopencv-dev +sudo apt-get install build-essential qt5-qmake qt5-default libopenal-dev libopencv-dev libxss-dev ``` Ubuntu: ```bash -sudo apt-get install build-essential qt5-qmake qt5-default qttools5-dev-tools libopenal-dev libopencv-dev +sudo apt-get install build-essential qt5-qmake qt5-default qttools5-dev-tools libopenal-dev libopencv-dev libxss-dev ``` Arch Linux: ```bash -sudo pacman -S --needed base-devel qt5 opencv openal +sudo pacman -S --needed base-devel qt5 opencv openal libxss ``` Fedora: ```bash yum groupinstall "Development Tools" -yum install qt-devel qt-doc qt-creator opencv-devel openal-soft-devel +yum install qt-devel qt-doc qt-creator opencv-devel openal-soft-devel libXScrnSaver-devel ``` ###Tox Core diff --git a/simple_make.sh b/simple_make.sh index 35b1c6b8a..23a8682ac 100755 --- a/simple_make.sh +++ b/simple_make.sh @@ -2,12 +2,12 @@ if which apt-get; then sudo apt-get install build-essential qt5-qmake qt5-default libopenal-dev libopencv-dev \ - libtool autotools-dev automake checkinstall check libopus-dev libvpx-dev qttools5-dev-tools qtchooser + libtool autotools-dev automake checkinstall check libopus-dev libvpx-dev qttools5-dev-tools qtchooser libxss-dev elif which pacman; then - sudo pacman -S --needed base-devel qt5 opencv openal opus libvpx + sudo pacman -S --needed base-devel qt5 opencv openal opus libvpx libxss elif which yum; then yum groupinstall "Development Tools" - yum install qt-devel qt-doc qt-creator opencv-devel openal-soft-devel libtool autoconf automake check check-devel + yum install qt-devel qt-doc qt-creator opencv-devel openal-soft-devel libtool autoconf automake check check-devel libXScrnSaver-devel else echo "Unknown package manager, attempting to compile anyways" fi diff --git a/src/platform/timer_x11.cpp b/src/platform/timer_x11.cpp index 9d01adb41..5c9ac1b20 100644 --- a/src/platform/timer_x11.cpp +++ b/src/platform/timer_x11.cpp @@ -15,7 +15,7 @@ */ #include -#ifdef Q_OS_UNIX +#if defined(Q_OS_UNIX) && !defined(__APPLE__) && !defined(__MACH__) #include "src/platform/timer.h" #include From 1c78cbc0c996cb653465f659cbf341bd71282a68 Mon Sep 17 00:00:00 2001 From: Dubslow Date: Mon, 1 Dec 2014 18:14:06 -0600 Subject: [PATCH 204/253] add a basic platform-disabling define --- qtox.pro | 18 +++++++++++------- src/platform/timer.h | 4 ++++ src/widget/form/settings/generalform.cpp | 5 +++++ src/widget/widget.cpp | 2 ++ 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/qtox.pro b/qtox.pro index f002f7e76..ec204255f 100644 --- a/qtox.pro +++ b/qtox.pro @@ -52,6 +52,7 @@ DEFINES += GIT_VERSION=\"\\\"$$quote($$GIT_VERSION)\\\"\" TIMESTAMP = $$system($1 2>null||echo 0||a;rm null;date +%s||echo 0) # I'm so sorry DEFINES += TIMESTAMP=$$TIMESTAMP DEFINES += LOG_TO_FILE +DEFINES += QTOX_PLATFORM_EXT # Comment out to increase portability contains(JENKINS,YES) { INCLUDEPATH += ./libs/include/ @@ -85,7 +86,7 @@ win32 { } contains(JENKINS, YES) { - LIBS = ./libs/lib/libtoxav.a ./libs/lib/libvpx.a ./libs/lib/libopus.a ./libs/lib/libtoxdns.a ./libs/lib/libtoxencryptsave.a ./libs/lib/libtoxcore.a ./libs/lib/libsodium.a /usr/lib/libopencv_core.so /usr/lib/libopencv_highgui.so /usr/lib/libopencv_imgproc.so -lopenal -s + LIBS = ./libs/lib/libtoxav.a ./libs/lib/libvpx.a ./libs/lib/libopus.a ./libs/lib/libtoxdns.a ./libs/lib/libtoxencryptsave.a ./libs/lib/libtoxcore.a ./libs/lib/libsodium.a /usr/lib/libopencv_core.so /usr/lib/libopencv_highgui.so /usr/lib/libopencv_imgproc.so -lopenal -lX11 -lXss -s } } } @@ -155,8 +156,7 @@ HEADERS += src/widget/form/addfriendform.h \ src/autoupdate.h \ src/misc/serialize.h \ src/widget/form/settings/advancedform.h \ - src/audio.h \ - src/platform/timer.h + src/audio.h SOURCES += \ src/widget/form/addfriendform.cpp \ @@ -223,7 +223,11 @@ SOURCES += \ src/autoupdate.cpp \ src/misc/serialize.cpp \ src/widget/form/settings/advancedform.cpp \ - src/audio.cpp \ - src/platform/timer_osx.cpp \ - src/platform/timer_win.cpp \ - src/platform/timer_x11.cpp + src/audio.cpp + +contains(DEFINES, QTOX_PLATFORM_EXT) { + HEADERS += src/platform/timer.h + SOURCES += src/platform/timer_osx.cpp \ + src/platform/timer_win.cpp \ + src/platform/timer_x11.cpp +} diff --git a/src/platform/timer.h b/src/platform/timer.h index b6576bc61..2ea66ba0f 100644 --- a/src/platform/timer.h +++ b/src/platform/timer.h @@ -14,6 +14,8 @@ See the COPYING file for more details. */ +#ifdef QTOX_PLATFORM_EXT + #ifndef PLATFORM_TIMER_H #define PLATFORM_TIMER_H @@ -26,3 +28,5 @@ namespace Platform } #endif // PLATFORM_TIMER_H + +#endif // QTOX_PLATFORM_EXT diff --git a/src/widget/form/settings/generalform.cpp b/src/widget/form/settings/generalform.cpp index 972c2ce98..a28fafa9f 100644 --- a/src/widget/form/settings/generalform.cpp +++ b/src/widget/form/settings/generalform.cpp @@ -144,6 +144,11 @@ GeneralForm::GeneralForm(SettingsWidget *myParent) : connect(bodyUI->proxyPort, SIGNAL(valueChanged(int)), this, SLOT(onProxyPortEdited(int))); connect(bodyUI->reconnectButton, &QPushButton::clicked, this, &GeneralForm::onReconnectClicked); connect(bodyUI->cbFauxOfflineMessaging, &QCheckBox::stateChanged, this, &GeneralForm::onFauxOfflineMessaging); + +#ifndef QTOX_PLATFORM_EXT + bodyUI->autoAwayLabel->setEnabled(false); // these don't seem to change the appearance of the widgets, + bodyUI->autoAwaySpinBox->setEnabled(false); // though they are unusable +#endif } GeneralForm::~GeneralForm() diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index ac26f7698..b044c8c25 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -1105,6 +1105,7 @@ bool Widget::event(QEvent * e) void Widget::onUserAwayCheck() { +#ifdef QTOX_PLATFORM_EXT uint32_t autoAwayTime = Settings::getInstance().getAutoAwayTime() * 60 * 1000; if (ui->statusButton->property("status").toString() == "online") @@ -1127,6 +1128,7 @@ void Widget::onUserAwayCheck() } else if (autoAwayActive) autoAwayActive = false; +#endif } void Widget::setStatusOnline() From 7660950133a5cd2d552014b429b432b31f6f414b Mon Sep 17 00:00:00 2001 From: Ansa89 Date: Tue, 2 Dec 2014 11:59:59 +0100 Subject: [PATCH 205/253] Italian translation: update --- translations/it.ts | 177 ++++++++++++++++++++++++++------------------- 1 file changed, 101 insertions(+), 76 deletions(-) diff --git a/translations/it.ts b/translations/it.ts index e8f94de98..87cfae823 100644 --- a/translations/it.ts +++ b/translations/it.ts @@ -117,7 +117,7 @@ qTox needs to use the Tox DNS, but can't do it through a proxy. -Ignore the proxy and connect to the Internet directly ? +Ignore the proxy and connect to the Internet directly? qTox deve usare Tox DNS, ma non può farlo attraverso un proxy. Ignorare le impostazioni del proxy e connettersi direttamente alla rete Tox? @@ -192,42 +192,52 @@ Ignorare le impostazioni del proxy e connettersi direttamente alla rete Tox?Invia un file - + + File not read + Impossibile leggere il file + + + + qTox wasn't able to open %1 + qTox non è riuscito ad aprire %1 + + + Bad Idea Pessima idea - + You're trying to send a special (sequential) file, that's not going to work! Stai cercando di inviare un file speciale (sequenziale), questo non funzionerà! - + %1 is calling %1 ti sta chiamando - + %1 stopped calling %1 ha fermato la chiamata - + Calling to %1 Stai chiamando %1 - + Call with %1 ended. %2 Chiamata con %1 terminata. %2 - + Call duration: Durata chiamata: - + Call rejected Chiamata rifiutata @@ -485,25 +495,25 @@ Soprannome: Generale - - + + None Nessuno - + Choose an auto accept directory popup title Scegli dove salvare i files accettati automaticamente - + Call active popup title Chiamata in corso - + You can't disconnect while a call is active! popup text Non puoi disconnetterti mentre c'è una chiamata in corso! @@ -523,22 +533,22 @@ Soprannome: La traduzione potrebbe non essere caricata fino al prossimo riavvio di qTox. - + Show system tray icon Mostra icona nella traybar - + Set to 0 to disable Imposta 0 per disabilitare - + Connection Settings Impostazioni Connessione - + Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Abilita IPv6 (consigliato) @@ -560,148 +570,163 @@ Soprannome: Rendi qTox portabile - + + System tray integration + Integrazione Traybar + + + Start in tray Avvia nella traybar - + Close to tray Chiudi nella traybar - + Minimize to tray Minimizza nella traybar - + + Tray icon displays user status + Mostra lo stato nell'icona della traybar + + + + This is a temporary work around until proper systray status icons are available. + Questo è un workaround temporaneo fino a quando non saranno disponibili icone di stato adeguate. + + + Show contacts' status changes Mostra quando i contatti cambiano stato - + Check for updates on startup (unstable) Controlla aggiornamenti all'avvio (unstable) - + Focus qTox when a message is received Dai il focus a qTox quando arriva un messaggio - + Faux offline messaging Falsi messaggi offline - + Provided in minutes Espresso in minuti - + Auto away after (0 to disable) Imposta assenza dopo - + minutes minuti - + You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Puoi impostare questa preferenza per ogni singolo contatto usando il click destro sul suo nome. - + Autoaccept files Accetta automaticamente i trasferimenti di files - + Save files in Salva i files in - + PushButton Sfoglia - + Theme Impostazioni Tema - + Use emoticons Usa emoticons - + Smiley Pack Text on smiley pack label Emoticons - + Style Stile - + Theme color Colore - + Emoticon size Dimensione emoticons - + px px - + Timestamp format Formato data/ora - + Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Disabilitando questo sarà possibile usare qTox con Tor. Tuttavia verrà aggiunto carico alla rete Tox, quindi disabilitare solo se necessario. - + Enable UDP (recommended) Text on checkbox to disable UDP Abilita UDP (consigliato) - + Use proxy (SOCKS5) Usa proxy (SOCKS5) - + Address Text on proxy addr label IP - + Port Text on proxy port label Porta - + Reconnect reconnect button Riconnetti @@ -1215,7 +1240,7 @@ Vuoi eliminare il vecchio file? - An update is available, do you want to download it now ? + An update is available, do you want to download it now? It will be installed when qTox restarts. È disponibile una nuova versione di qTox, vuoi scaricarla adesso? Verrà installata al riavvio del programma. @@ -1336,7 +1361,7 @@ Verrà installata al riavvio del programma. - Do you want to add %1 as a friend ? + Do you want to add %1 as a friend? Vuoi aggiungere %1 come contatto? @@ -1365,27 +1390,27 @@ Verrà installata al riavvio del programma. Widget - + Online Online - + Away Assente - + Busy Occupato - + &Quit &Esci - + Change status to: Cambia stato in: @@ -1408,116 +1433,116 @@ Verrà installata al riavvio del programma. Occupato - + Choose a profile Scegli un profilo - + Please choose which identity to use Per favore scegli quale identità usare - + Choose a profile picture Scegli un'immagine per il profilo - - - + + + Error Errore - + Unable to open this file Impossibile aprire il file - + Unable to read this image Impossibile leggere l'immagine - + This image is too big L'immagine è troppo grande - + Toxcore failed to start, the application will terminate after you close this message. Impossibile avviare Toxcore.\nqTox terminerà dopo che avrai chiuso questo messaggio. - + toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text Impossibile avviare Toxcore con le tue impostazione proxy.\nqTox non può funzionare correttamente, per favore modifica le impostazioni e riavvia il programma. - + Add friend Aggiungi contatto - + File transfers Files trasferiti - + Settings Impostazioni - + Couldn't request friendship Impossibile inviare la richiesta d'amicizia - + away contact status assente - + busy contact status occupato - + offline contact status offline - + online contact status online - + %1 is now %2 e.g. "Dubslow is now online" %1 è ora %2 - + <Unknown> Placeholder when we don't know someone's name in a group chat <Sconosciuto> - + %1 has set the title to %2 %1 ha impostato il titolo in %2 - + Message failed to send Impossibile inviare il messaggio From a6f3f5f1691c0f7c050bcfb955d9f4d28538bf69 Mon Sep 17 00:00:00 2001 From: novist Date: Tue, 2 Dec 2014 15:33:53 +0200 Subject: [PATCH 206/253] Fixed getIdleTime() on windows --- src/platform/timer_win.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/platform/timer_win.cpp b/src/platform/timer_win.cpp index c15bf0047..07650a0e1 100644 --- a/src/platform/timer_win.cpp +++ b/src/platform/timer_win.cpp @@ -23,8 +23,9 @@ uint32_t Platform::getIdleTime() { LASTINPUTINFO info = { 0 }; + info.cbSize = sizeof(info); if(GetLastInputInfo(&info)) - return info.dwTime / 1000; + return GetTickCount() - info.dwTime; return 0; } From 3f234fb2eb33fb70bd30d350b4a48171671ad36f Mon Sep 17 00:00:00 2001 From: Dubslow Date: Tue, 2 Dec 2014 15:03:29 -0600 Subject: [PATCH 207/253] disabling platform extensions now affects linking, and can be set from cmd line `qmake DISABLE_PLATFORM_EXT=YES` --- qtox.pro | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/qtox.pro b/qtox.pro index ec204255f..cbd4f3dc1 100644 --- a/qtox.pro +++ b/qtox.pro @@ -52,7 +52,12 @@ DEFINES += GIT_VERSION=\"\\\"$$quote($$GIT_VERSION)\\\"\" TIMESTAMP = $$system($1 2>null||echo 0||a;rm null;date +%s||echo 0) # I'm so sorry DEFINES += TIMESTAMP=$$TIMESTAMP DEFINES += LOG_TO_FILE -DEFINES += QTOX_PLATFORM_EXT # Comment out to increase portability + +contains(DISABLE_PLATFORM_EXT, YES) { + +} else { + DEFINES += QTOX_PLATFORM_EXT +} contains(JENKINS,YES) { INCLUDEPATH += ./libs/include/ @@ -71,7 +76,8 @@ win32 { BUNDLEID = im.tox.qtox ICON = img/icons/qtox.icns QMAKE_INFO_PLIST = osx/info.plist - LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -ltoxencryptsave -ltoxdns -lsodium -lvpx -framework OpenAL -framework IOKit -lopencv_core -lopencv_highgui -framework CoreFoundation + LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -ltoxencryptsave -ltoxdns -lsodium -lvpx -framework OpenAL -lopencv_core -lopencv_highgui + contains(DEFINES, QTOX_PLATFORM_EXT) { LIBS += -framework IOKit -framework CoreFoundation } } else { # If we're building a package, static link libtox[core,av] and libsodium, since they are not provided by any package contains(STATICPKG, YES) { @@ -79,10 +85,13 @@ win32 { INSTALLS += target LIBS += -L$$PWD/libs/lib/ -lopus -lvpx -lopenal -Wl,-Bstatic -ltoxcore -ltoxav -ltoxencryptsave -ltoxdns -lsodium -lopencv_highgui -lopencv_imgproc -lopencv_core -lz -Wl,-Bdynamic LIBS += -Wl,-Bstatic -ljpeg -ltiff -lpng -ljasper -lIlmImf -lIlmThread -lIex -ldc1394 -lraw1394 -lHalf -lz -llzma -ljbig - LIBS += -Wl,-Bdynamic -lv4l1 -lv4l2 -lavformat -lavcodec -lavutil -lswscale -lusb-1.0 -lX11 -lXss - + LIBS += -Wl,-Bdynamic -lv4l1 -lv4l2 -lavformat -lavcodec -lavutil -lswscale -lusb-1.0 } else { - LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -ltoxencryptsave -ltoxdns -lvpx -lsodium -lopenal -lopencv_core -lopencv_highgui -lopencv_imgproc -lX11 -lXss + LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -ltoxencryptsave -ltoxdns -lvpx -lsodium -lopenal -lopencv_core -lopencv_highgui -lopencv_imgproc + } + + contains(DEFINES, QTOX_PLATFORM_EXT) { + LIBS += -lX11 -lXss } contains(JENKINS, YES) { From 4a14155724ca0f69dcc490c3868f94b127170b45 Mon Sep 17 00:00:00 2001 From: Dubslow Date: Tue, 2 Dec 2014 16:47:55 -0600 Subject: [PATCH 208/253] move settings to qtox.ini --- src/misc/settings.cpp | 30 ++++++++++++++++++++---------- src/misc/settings.h | 1 + 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/misc/settings.cpp b/src/misc/settings.cpp index 5ad19cb81..b40e4f2d1 100644 --- a/src/misc/settings.cpp +++ b/src/misc/settings.cpp @@ -36,7 +36,8 @@ #define SHOW_SYSTEM_TRAY_DEFAULT (bool) true #endif -const QString Settings::FILENAME = "settings.ini"; +const QString Settings::OLDFILENAME = "settings.ini"; +const QString Settings::FILENAME = "qtox.ini"; bool Settings::makeToxPortable{false}; Settings::Settings() : @@ -55,28 +56,37 @@ Settings& Settings::getInstance() void Settings::load() { - if (loaded) { + if (loaded) return; - } - QFile portableSettings(FILENAME); - if (portableSettings.exists()) + if (QFile(FILENAME).exists()) { QSettings ps(FILENAME, QSettings::IniFormat); ps.beginGroup("General"); makeToxPortable = ps.value("makeToxPortable", false).toBool(); ps.endGroup(); } + else if (QFile(OLDFILENAME).exists()) + { + QSettings ps(OLDFILENAME, QSettings::IniFormat); + ps.beginGroup("General"); + makeToxPortable = ps.value("makeToxPortable", false).toBool(); + ps.endGroup(); + } else makeToxPortable = false; - QString filePath = QDir(getSettingsDirPath()).filePath(FILENAME); + QDir dir(getSettingsDirPath()); + QString filePath = dir.filePath(FILENAME); //if no settings file exist -- use the default one - QFile file(filePath); - if (!file.exists()) { - qDebug() << "No settings file found, using defaults"; - filePath = ":/conf/" + FILENAME; + if (!QFile(filePath).exists()) + { + if (!QFile(filePath = dir.filePath(OLDFILENAME)).exists()) + { + qDebug() << "No settings file found, using defaults"; + filePath = ":/conf/" + FILENAME; + } } qDebug() << "Settings: Loading from "< Date: Tue, 2 Dec 2014 18:26:17 -0600 Subject: [PATCH 209/253] move friend information to a separate ini file; fixes #594 --- src/core.cpp | 13 +++++-- src/misc/settings.cpp | 88 +++++++++++++++++++++++++++---------------- src/misc/settings.h | 7 +++- 3 files changed, 70 insertions(+), 38 deletions(-) diff --git a/src/core.cpp b/src/core.cpp index 5268e4285..8cb27e8a8 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -1370,12 +1370,13 @@ void Core::switchConfiguration(const QString& profile) qDebug() << "Core: creating new Id"; else qDebug() << "Core: switching from" << Settings::getInstance().getCurrentProfile() << "to" << profile; - + saveConfiguration(); + + ready = false; clearPassword(ptMain); clearPassword(ptHistory); - ready = false; toxTimer->stop(); Widget::getInstance()->setEnabledThreadsafe(false); if (tox) { @@ -1384,6 +1385,7 @@ void Core::switchConfiguration(const QString& profile) tox_kill(tox); tox = nullptr; } + emit selfAvatarChanged(QPixmap(":/img/contact_dark.png")); emit blockingClearContacts(); // we need this to block, but signals are required for thread safety @@ -1391,7 +1393,12 @@ void Core::switchConfiguration(const QString& profile) loadPath = ""; else loadPath = QDir(Settings::getSettingsDirPath()).filePath(profile + TOX_EXT); - Settings::getInstance().setCurrentProfile(profile); + + // the new profile needs to be set before resetting the settings, so that + // we don't load the old profile's profile.ini + Settings::getInstance().setCurrentProfile(profile); + Settings::getInstance().save(false); // save new profile, but don't write old profile info to newprofile.ini + Settings::resetInstance(); HistoryKeeper::getInstance()->resetInstance(); start(); diff --git a/src/misc/settings.cpp b/src/misc/settings.cpp index b40e4f2d1..c5e13b1e7 100644 --- a/src/misc/settings.cpp +++ b/src/misc/settings.cpp @@ -38,6 +38,7 @@ const QString Settings::OLDFILENAME = "settings.ini"; const QString Settings::FILENAME = "qtox.ini"; +Settings* Settings::settings{nullptr}; bool Settings::makeToxPortable{false}; Settings::Settings() : @@ -48,12 +49,20 @@ Settings::Settings() : Settings& Settings::getInstance() { - static Settings* settings{nullptr}; if (!settings) settings = new Settings(); return *settings; } +void Settings::resetInstance() +{ + if (settings) + { + delete settings; + settings = nullptr; + } +} + void Settings::load() { if (loaded) @@ -113,21 +122,6 @@ void Settings::load() useCustomDhtList=false; s.endGroup(); - friendLst.clear(); - s.beginGroup("Friends"); - int size = s.beginReadArray("Friend"); - for (int i = 0; i < size; i ++) - { - s.setArrayIndex(i); - friendProp fp; - fp.addr = s.value("addr").toString(); - fp.alias = s.value("alias").toString(); - fp.autoAcceptDir = s.value("autoAcceptDir").toString(); - friendLst[ToxID::fromString(fp.addr).publicKey] = fp; - } - s.endArray(); - s.endGroup(); - s.beginGroup("General"); enableIPv6 = s.value("enableIPv6", true).toBool(); translation = s.value("translation", "en").toString(); @@ -227,15 +221,39 @@ void Settings::load() } loaded = true; + + if (currentProfile.isEmpty()) // new profile in Core::switchConfiguration + return; + + // load from a profile specific friend data list if possible + QString tmp = dir.filePath(currentProfile + ".ini"); + if (QFile(tmp).exists()) + filePath = tmp; + + QSettings fs(filePath, QSettings::IniFormat); + friendLst.clear(); + fs.beginGroup("Friends"); + int size = fs.beginReadArray("Friend"); + for (int i = 0; i < size; i ++) + { + fs.setArrayIndex(i); + friendProp fp; + fp.addr = fs.value("addr").toString(); + fp.alias = fs.value("alias").toString(); + fp.autoAcceptDir = fs.value("autoAcceptDir").toString(); + friendLst[ToxID::fromString(fp.addr).publicKey] = fp; + } + fs.endArray(); + fs.endGroup(); } -void Settings::save() +void Settings::save(bool writeFriends) { QString filePath = QDir(getSettingsDirPath()).filePath(FILENAME); - save(filePath); + save(filePath, writeFriends); } -void Settings::save(QString path) +void Settings::save(QString path, bool writeFriends) { qDebug() << "Settings: Saving in "< Date: Tue, 2 Dec 2014 18:48:50 -0600 Subject: [PATCH 210/253] deleting a profile now cleans out all personal data --- src/historykeeper.cpp | 9 +++++---- src/historykeeper.h | 2 +- src/widget/form/settings/identityform.cpp | 11 +++++++++-- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/historykeeper.cpp b/src/historykeeper.cpp index 309c760d9..a923f53c5 100644 --- a/src/historykeeper.cpp +++ b/src/historykeeper.cpp @@ -291,12 +291,13 @@ HistoryKeeper::ChatType HistoryKeeper::convertToChatType(int ct) return static_cast(ct); } -QString HistoryKeeper::getHistoryPath() +QString HistoryKeeper::getHistoryPath(QString currentProfile, int encrypted) { - QDir baseDir(Settings::getInstance().getSettingsDirPath()); - QString currentProfile = Settings::getInstance().getCurrentProfile(); + QDir baseDir(Settings::getSettingsDirPath()); + if (currentProfile.isEmpty()) + currentProfile = Settings::getInstance().getCurrentProfile(); - if (Settings::getInstance().getEncryptLogs()) + if (encrypted == 1 || (encrypted == -1 && Settings::getInstance().getEncryptLogs())) return baseDir.filePath(currentProfile + ".qtox_history.encrypted"); else return baseDir.filePath(currentProfile + ".qtox_history"); diff --git a/src/historykeeper.h b/src/historykeeper.h index 65e7b9e75..300232e33 100644 --- a/src/historykeeper.h +++ b/src/historykeeper.h @@ -43,7 +43,7 @@ public: static HistoryKeeper* getInstance(); static void resetInstance(); - static QString getHistoryPath(); + static QString getHistoryPath(QString currentProfile = QString(), int encrypted = -1); // -1 defaults to checking settings, 0 or 1 to specify static bool checkPassword(); static void renameHistory(QString from, QString to); diff --git a/src/widget/form/settings/identityform.cpp b/src/widget/form/settings/identityform.cpp index fa8c32082..e1f46d7e4 100644 --- a/src/widget/form/settings/identityform.cpp +++ b/src/widget/form/settings/identityform.cpp @@ -196,9 +196,16 @@ void IdentityForm::onDeleteClicked() else { if (checkContinue(tr("Deletion imminent!","deletion confirmation title"), - tr("Are you sure you want to delete this profile?","deletion confirmation text"))) + tr("Are you sure you want to delete this profile?\nAssociated friend information and chat logs will be deleted as well.","deletion confirmation text"))) { - QFile::remove(QDir(Settings::getSettingsDirPath()).filePath(bodyUI->profiles->currentText()+Core::TOX_EXT)); + QString profile = bodyUI->profiles->currentText(); + QDir dir(Settings::getSettingsDirPath()); + + QFile::remove(dir.filePath(profile + Core::TOX_EXT)); + QFile::remove(dir.filePath(profile + ".ini")); + QFile::remove(HistoryKeeper::getHistoryPath(profile, 0)); + QFile::remove(HistoryKeeper::getHistoryPath(profile, 1)); + bodyUI->profiles->removeItem(bodyUI->profiles->currentIndex()); bodyUI->profiles->setCurrentText(Settings::getInstance().getCurrentProfile()); } From 3c15909ffcc7a6c9c7d7631e75dcf82614acc0cc Mon Sep 17 00:00:00 2001 From: apprb Date: Wed, 3 Dec 2014 23:47:07 +0600 Subject: [PATCH 211/253] correct displaying of the targeted messages --- src/widget/form/genericchatform.cpp | 2 +- src/widget/widget.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/widget/form/genericchatform.cpp b/src/widget/form/genericchatform.cpp index 6dea94104..d9912a091 100644 --- a/src/widget/form/genericchatform.cpp +++ b/src/widget/form/genericchatform.cpp @@ -229,7 +229,7 @@ void GenericChatForm::addAlertMessage(const ToxID &author, QString message, QDat authorStr = author.publicKey; QString date = datetime.toString(Settings::getInstance().getTimestampFormat()); - MessageActionPtr ca = MessageActionPtr(new AlertAction(authorStr, message, date)); + MessageActionPtr ca = MessageActionPtr(new AlertAction(getElidedName(authorStr), message, date)); ca->markAsSent(); chatWidget->insertMessage(ca); previousId = author; diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index b044c8c25..4736f3642 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -956,7 +956,7 @@ void Widget::onGroupMessageReceived(int groupnumber, int peernumber, const QStri QString name = core->getUsername(); bool targeted = (!author.isMine()) && message.contains(name, Qt::CaseInsensitive); - if (targeted) + if (targeted && !isAction) g->getChatForm()->addAlertMessage(author, message, QDateTime::currentDateTime()); else g->getChatForm()->addMessage(author, message, isAction, QDateTime::currentDateTime(), true); From 85fcdd765312b066e368a4d3adcfa901b1059c0a Mon Sep 17 00:00:00 2001 From: Dubslow Date: Wed, 3 Dec 2014 18:52:10 -0600 Subject: [PATCH 212/253] Fix not setting tox null after cleanup also reordered functions for super extra safety --- src/core.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/core.cpp b/src/core.cpp index 8cb27e8a8..05ce62261 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -90,9 +90,15 @@ Core::Core(Camera* cam, QThread *CoreThread, QString loadPath) : Core::~Core() { - if (tox) { + clearPassword(Core::ptMain); + clearPassword(Core::ptHistory); + + if (tox) + { toxav_kill(toxav); + toxav = nullptr; tox_kill(tox); + tox = nullptr; } if (videobuf) @@ -103,9 +109,6 @@ Core::~Core() Audio::closeInput(); Audio::closeOutput(); - - clearPassword(Core::ptMain); - clearPassword(Core::ptHistory); } Core* Core::getInstance() From 561f5f9895ef230edacc42fdef8448c7019d6d5b Mon Sep 17 00:00:00 2001 From: agilob Date: Thu, 4 Dec 2014 15:51:01 +0000 Subject: [PATCH 213/253] fixing bug #891 --- src/widget/form/chatform.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/widget/form/chatform.cpp b/src/widget/form/chatform.cpp index 6d95b4a5b..a97faf93c 100644 --- a/src/widget/form/chatform.cpp +++ b/src/widget/form/chatform.cpp @@ -305,6 +305,8 @@ void ChatForm::onAvCancel(int FriendId, int) if (FriendId != f->getFriendID()) return; + + stopCounter(); audioInputFlag = false; audioOutputFlag = false; From 9d754440812cc9dc03109627eca5c296c53d3e75 Mon Sep 17 00:00:00 2001 From: Gethyn ThomasQuail Date: Thu, 4 Dec 2014 10:57:52 -0700 Subject: [PATCH 214/253] Fixed up Emote button and created new images for the Volume and Mic buttons --- ui/emoteButton/emoteButton.png | Bin 758 -> 825 bytes ui/emoteButton/emoteButtonHover.png | Bin 745 -> 767 bytes ui/emoteButton/emoteButtonPressed.png | Bin 754 -> 771 bytes ui/micButton/micButton.png | Bin 389 -> 593 bytes ui/micButton/micButtonDisabled.png | Bin 461 -> 533 bytes ui/micButton/micButtonHover.png | Bin 327 -> 504 bytes ui/micButton/micButtonPressed.png | Bin 379 -> 521 bytes ui/volButton/volButton.png | Bin 385 -> 637 bytes ui/volButton/volButtonHover.png | Bin 365 -> 597 bytes ui/volButton/volButtonPressed.png | Bin 444 -> 629 bytes 10 files changed, 0 insertions(+), 0 deletions(-) diff --git a/ui/emoteButton/emoteButton.png b/ui/emoteButton/emoteButton.png index 867f7aca07e9a9b536774d0a16d31dbfc8646fe8..390c92a262c7d914f7c6840bac84d17982133c69 100644 GIT binary patch delta 796 zcmV+%1LOSm1-S;0Dt~JL!T?~%INntN000SaNLh0L01m_e01m_fl`9S#00007bV*G` z2i^<>5hMny9Uy%G00PEIL_t(Y$Gw!xPZMDj#eeh7OsCq1v}k~;grdfvR7Dd_lz=f9 zB_X;nnz&P!x_0j$;MP6S#2DBZ<0BZNiEa#vj~EmLp->8iQh(@^*0#*JkxFR`H384= z`*P-ubDBDvyI2PY3j`FQViKmjJ5{K8XH7OE zq`#j^NXSAdCa|?YAeMHW2^Z`*!kp%6`nnM(Le3pCf9p{3>w0FTGI>79JOZgG>W zaiifTHr39X=~r}(JSaV)K*CQn9Rc8gqcP9xrQo*hK@}p-Aj!rV0z^s_Ds#a=kbzF2` z=0sI9nyfJq^YX;o&BCww@{(+pw{X6;gH~4?s(&zHV35efiDzPHvW8W)=iVoO!pFnm zPR5Ijr(}{-OG$cs&j_XFXmy>XYHKwX)rv*6<~U16mj2m3UQfMbGComW4pt(WUF7Zb zYlb63G&&FCw(rHEIjJx?_z@YTfA$^0)GUF;sq%wX41vX|T#=QIGBEx<$?KuqqW|?R a`V|f0lKoy{D4^000SaNLh0L01m_e01m_fl`9S#00007bV*G` z2i^u810NeS7PC_T00M(aL_t(Y$K937PZLoT#(#5Xrc5I>7?l_lBl0Q*5q}D$kF>Nf<3=i_4_wfV z-|n7#bG|v}eCJ;2_Q-XU{lh3Q(AU}jU^^h~v%ogq0lj^l{j-A7pbZ!(Wd+-~r|gah zNpQ0I3|BnYsI(kpe07W`lMh)>g)Sd+DPqA*PGKKU9F(K z&WByALP!E{b$=_TYR+bH9OZ{OU)PBu6cnM*QQw72cjv31IY&SdDi&eMim5`)I%~2O zA^rVSLP8cwK7q0vfoM9yMEDDd%>*VUONpOMtc_=J7UMytBR?=q1H&|!icAuW1@aCd z?~uz?8=i^=G+ARgu|yy?i(wjBoNDVK+`0#V>F5-pdcPPNDvu)zg-9yQLOfVp;7-e30G>_{GCcos&*TJ}Ta#NNOE`CVNYk*9*a|A43%?CMG|3{e516Pb=>MaO!TF>|P|Kg2~HO z8|`&IG=JGjcw?2B=+xc5hFLXg0_GF00N9jL_t(Y$K935PZLob$3LCv%rvDfv|!p21zPohfhdU^vJ{C+ zG)505BwmdtFP=Pj^Tx#s|AAgKfr}v?lmi+QqCp{YKv}dD*?+foy3vhxJb~6u0TX)h zdwak4d%w@T-}1)r=)(hzgVQIVx4XIL$+p38z<_NH16|$CJ+lI((OICklsDMcL!+M6 zTYj9|-aC}b6^sYRcrpE~z^BFC!PO%-0C+R^ilL=Q`F+}VOJao27$)PCQcnX zowsi{Ik@0z$A2goF$zXn-5og1F1=7^3ILKQBMWlAO_1`|ifBSG{61>HfGFrDg91P% zn`T1&N@6R4hQ>-_nTgPNp3ic8k(tO3a$1g@mct+U&SGp{7f`gwwKgw`sIZn;A(RZF zcTURZwLLP~G*jxto^2#hVt!OCdkq3nKVQ~A15ji2P-Csz6VT%Fp@=5bRFFW_zdJxpg&5ly zC7O<)h<^&#YHm_#swx`1t@ESn00000NkvXXu0mjf;qq9* delta 715 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 MNkvXXt^-0~f;Ou4&;S4c delta 316 zcmV-C0mJ^$1cd{TQhy5(6EX&2<~n%*009z7L_t(I%e9m}PQySDg})iOh|)mANsy>$ zprptdf+!<#7^+Z!#1+!eM^urZ=L|`aDx&hE!7H2{k>lm8{_x&DuFlWW3%efTcG&m^AUjavx`L3{QD~tn=Pm6 zD%OA{)Ka|L-!&IPNHvy?Z@e#p*g3EOws|jG0%&}0tmpyV0t@@EMgI)|k!j~o!$k7P8#JoO7!Lqw&wO+#eiB&dhvg zGMO{-G5u&O)Nesvl~qstKt{}8i~VCTZ?3>6!izH3q2uwm_J2N|P8F(zcSlL^$=J@zI~wL zhbqf+7aRluNsc zE|)}6gx0zibF&lxtJR9ZU_czl^uXj>jk7DDw_WIGNg^v(8LawqR2`7`P_3)Oua$x zW~kv5T*c=~;QtISa8cyS!6e>R?3?EkJ8vths3svWq*`Nq6XH~?wa%3#DQTI)=$)*6$^#A(gJFD(El2Tqa%qwy$xyR%Ixg?hbSnr|g+vy&`; zL=q9Ycip24<6&HG#r*8!&2n)=;$aM9Og1;hIH9X>`AH9t_vhPS``VhfFAL+t1aMlB i*zzndn+sh_!s}nb0000qfPL2*vCQgz#7{kQMNMh9*7eC@)(vWCWN*oNa&p}IQpdV4Z zO^0`T&#&#xJ(m<3`7nS3S%|G*1kvhfCji!b0QTLW8}r+i^MBUGx^1m2#Fkqf?SoX# z%ghaGFvF6gggU|jz~}aBCX^C7kaG&^wRem$FvdLh+7p-(3ifl26#SbQ0EcK8#Y_qD zK+07d52R4cln|m}cQ8jeh`fY|KWfPnkO6VTABD(E;3($~?i$_ic?`&wDIkPcCZoT` z)Hs55qYVI<6n_!`fU~D&4{qMdbJJ2|YCO%c$V*5D)2M4T0KnqJQtw4|t%hVU4Uv~@ z`#;oq)8i@RlBlYOd?R%)Y^0!@~>Izg{>48rlk3AWL6dL&>St*F+hCM9LRUE}Tmz#Dwxk~

bm&s>r-&UVffseQ^@MI?$^iC~N#*eD|G+IS(CkGu0@ zn!BBw`R&}y?%&wkT^k;GWPhUW*!+V%Oq-QviuH)~#9k0|p?^~GtgE?P-`MfGlT*U{ zFwT~ibQq{1M9Zg@@`VG4u)nCaMr$3qC6SjEdTS>{S5 zq#bteA5cQ?8!7P0bqVp4i? zJe})x^Gw@jYIPNW^R2C}iW12r^MwKcHwOneCnw>+0E3H*%x-S>J+p=oT=064W8uXlGe%Vqx?8wBnYqag8VfjW=3*A6Eyo#ilc*=hYYxt}qQ rA}?cX7|I;zrO9Ed>lFHzF*d#cM}?gRr6jG|00000NkvXXu0mjf7n#<_ delta 306 zcmV-20nPr21p5MzQhy5(6FMW}J7hKh009U|L_t(I%VRtp$;c!y>W_W?*1oVEFqN!T$fBC@sVp%zwy8ayTsh0^Z}_!OK93}pCu^(vhI^XgTC zhW-%v_K5M4rz^eIFE+*#fBy6FQX>ey3V**Z`0lq00Pk{Rqb9fj5h^En_Z_VO00Ih0L_t(I%dM2%OOs(3#((d}*m$?u%q(zgo3f%>E2x8rAe7W{ zqNRzQM1&_D1RnP11ce77E20l6Tja;V4vaJp5@BUxTj^RXt$!B#dOIjb+xu)T@;-XF z@9Vjqi;MfYr0Y{xHQ*M|56Jv~-36|wz)fJF8d5@{+tY_;(in}7Y(;z#$jN_`>bfpO zp55C+hbzdbJ*_4E0}ekft6OwsKn9eeGD$KZ+MPkV+`YmF&5D_8{$Xq;d-+JI>ggHh zIeNVPhQ5F25r3?zjn~On%qQpQ_VgJr6^YxLI_dR_tv}=l(C+F2U~Kt5#~V+esn!yV zx_KD%T{d`FsU|;$84lcFBL0l8nG|on%yYEi7?IU!qvDf|r)%D)we%`7Djs_?0PohL zxNY9rtk|lug$ylq`v6G(Oi*uW*dAigQ6&`#30aaU=zsa`AvP-(mNug3=vYm5(z&#l zIFgvEnRJ(BH`zjl#nb`-Ep$mUeAl+7PA)VL3W*Oo4zrkA;9GVB0N;-N zOvb~tS@9x1&V%6poEC{^4=O$0MJnnN7@;xAQ6x0xnewA%LeV4^d?m&7>J-v!7=~ sS3)W<^e5pe2EC5Xqvd=567)mA0REYg(puCk<^TWy07*qoM6N<$f`(NfHUIzs delta 352 zcmV-m0iXW;1c3vPDu4d~{{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G` z2i^-26BHFDhWLX3009n3L_t(I%dM2LP69y?hQFP?guvNYBjEvvCRV4>OynyknxLX=AU~cxZD^qQDvfuvy?0@W-{_{lxcmhrV;r}rK z&J*AP=*AEn4uko-eFiKg77CO>*U=Tg#)7X7Z?u$6Vxhy65qn9h<|S6?8mg&Ie23d< zt29@)bpUdgtCy?bcG{w5Ya75h^ctER~-C00H7jL_t(I%dM2(OH*MO$3J_|nUk~5oG#m#GNx&?Tp=V_2quvi z3E2-MysWE)qMQDiuDbRj2!d{6EZYbRb}@z}!D>c&I&(~92Y;NJn_UEhao+ucJy-Ah zd|y61@0aI!98VLE`hZsfJ@|kwglQRXfX6EEvK{Euz%?|dMj{_?`Yxac@0*r!Bm#PH zxfOKMJH`C)0yF-(y1ZBEN3E(d__AmPjd~`z9a@xXF2%)@iRV(SDR{zjg*#!pOFrjW z0P@EsnPQq7{(oETgClyB#qg3;yX3vhv~LE0ADr!|tpqCPdi((VEM*Cu3AgWYv-n#*X4EqdK=H6Zf3L4Q zU^qo}{C83~1l7|@cfc;CmztQPVIf}WYMqu13|Koa}q1W@Bjb+07*qoM6N<$ Ef^pvmTL1t6 delta 332 zcmV-S0ki(q1nmNlDu4d~{{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G` z2i^-26BjPLph9E-008<)L_t(I%VW$wl)%Nnz_3t)S1N>o3gE@(XKNW47~)tM7#QYJ z%TNXe1_lXUso)o%pZ#HMK40~pfd+ty63u}MF*xN|$Tc)qtACt5h@wDmH-9-00II@L_t(I%dM2XPg-FVho5`FN~lP~hqfv5k*J{&78lb&WigEl z24V~`4m5FaV)WmrVRA5?jA1aTi<3;c)YO=(%U#eEDQbEPrGJu21%VE>8ssY1gfpGI zzw=uHhnx@72Ft#&d{SQWUT^)*vKHv8`FT2wMo!}KN8?`0`A|)? zTnV3p!FnrbZ#0TC7zE%;_$~+I=qQz9k%}ZW-4R|`hwqwaD&4Q!vC z#T{<0Qdb~A&41}6x4%zLl5mAW+QGI+1o!l`s`kR~$1ynxKzd^X`^X5jzP^SS{eQ<9 zi_sehXzKh~GKqh2k<+a$I6fwSc!+g)n4g(UbH?_uG0h`Ym1WTDF?l=yT%=NTx!q4| z-a}R^m+2lD0N{_JV6a--1EyX_(BD*25Jl?MYJ0#sgMWcyDg{7?$;5T3qz+cLw>3jW zua{c6OhJ|b=KUt=XzN93wj5ty{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G` z2i^-26AB7{&&sI)00Bu!L_t(I%e7RyN&`U@J$GhzNwRC9y#`W9I?+x)VnM-gu!~kf zL?r|g(ux0&DhU3@_JRZpG5QB$7SzmK3*$br58YJ{3bP7nU9{S^U39%U$T$N4+}0000 Date: Fri, 5 Dec 2014 10:37:32 +0100 Subject: [PATCH 215/253] Italian translation: update --- translations/it.ts | 72 ++++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/translations/it.ts b/translations/it.ts index 87cfae823..6650ae750 100644 --- a/translations/it.ts +++ b/translations/it.ts @@ -217,27 +217,27 @@ Ignorare le impostazioni del proxy e connettersi direttamente alla rete Tox?%1 ti sta chiamando - + %1 stopped calling %1 ha fermato la chiamata - + Calling to %1 Stai chiamando %1 - + Call with %1 ended. %2 Chiamata con %1 terminata. %2 - + Call duration: Durata chiamata: - + Call rejected Chiamata rifiutata @@ -253,105 +253,105 @@ Ignorare le impostazioni del proxy e connettersi direttamente alla rete Tox? Core - + Toxing on qTox Toxing on qTox - + qTox User qTox User - + Friend is already added Questo contatto è già presente nella tua lista - + Encryption error Errore crittografia - + The .tox file is encrypted, but encryption was not checked, continuing regardless. Il Tox datafile è criptato, ma la crittografia non è abilitata nelle impostazioni. Continuo ignorando le impostazioni. - + Tox datafile decryption password Password per decriptare il Tox datafile - - - + + + Password error Errore password - - + + Failed to setup password. Empty password. Impossibile impostare la password. Password vuota. - + Try Again Riprova - + Change profile Cambia profilo - + Reinit current profile Reinizializza il profilo corrente - + Wrong password has been entered È stata inserita una password sbagliata - + History Log decryption password Password per decriptare i log - + Encrypted log Log criptato - + Your history is encrypted with different password. Do you want to try another password? I log delle chat sono criptati con una password diversa. Vuoi provare ad inserire un'altra password? - + History Chat Log - + Due to incorret password history will be disabled. Password errata, i log delle chat non saranno caricati. - + NO Password Nessuna password - + Will be saved without encryption! Il Tox datafile sarà salvato senza password! @@ -927,42 +927,44 @@ Nome gruppo: - Are you sure you want to delete this profile? + Are you sure you want to delete this profile? +Associated friend information and chat logs will be deleted as well. deletion confirmation text - Sei sicuro di voler eliminare questo profilo? + Sei sicuro di voler eliminare questo profilo? +I contatti e i log delle chat associati ad esso, saranno eliminati. - + Import profile import dialog title Importa profilo - + Tox save file (*.tox) import dialog filter Tox save file (*.tox) - + Ignoring non-Tox file popup title File ignorato - + Warning: you've chosen a file that is not a Tox save file; ignoring. popup text Attenzione: hai scelto un file che non contiente un profilo Tox.\nQuesto file verrà ignorato. - + Profile already exists import confirm title Profilo già esistente - + A profile named "%1" already exists. Do you want to erase it? import confirm text Un profilo chiamato "%1" esiste già. Vuoi sovrascriverlo? From 794d73a6ce8a5b2da0a38d88469069091d3a57cb Mon Sep 17 00:00:00 2001 From: Gethyn ThomasQuail Date: Fri, 5 Dec 2014 07:16:04 -0700 Subject: [PATCH 216/253] Update INSTALL.md Added dependency resolution for Slackware 14.1 users. --- INSTALL.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/INSTALL.md b/INSTALL.md index e94510b1a..14a647f47 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -134,6 +134,16 @@ yum groupinstall "Development Tools" yum install qt-devel qt-doc qt-creator opencv-devel openal-soft-devel libXScrnSaver-devel ``` +Slackware: + +You can grab slackbuilds of the needed dependencies here: + +http://slackbuilds.org/repository/14.1/libraries/OpenAL/ + +http://slackbuilds.org/repository/14.1/libraries/qt5/ + +http://slackbuilds.org/repository/14.1/libraries/opencv/ + ###Tox Core First of all install the dependencies of Tox Core. From 961d5830c3095520af91b24ed0efda5b2d2d2591 Mon Sep 17 00:00:00 2001 From: Dubslow Date: Fri, 5 Dec 2014 16:42:15 -0600 Subject: [PATCH 217/253] add fancier tray icons with status, remove relevant option --- img/taskbar/taskbar_busy.png | Bin 0 -> 420 bytes img/taskbar/taskbar_busy_2x.png | Bin 0 -> 716 bytes img/taskbar/taskbar_idle.png | Bin 0 -> 410 bytes img/taskbar/taskbar_idle_2x.png | Bin 0 -> 719 bytes img/taskbar/taskbar_invisible.png | Bin 0 -> 478 bytes img/taskbar/taskbar_invisible_2x.png | Bin 0 -> 704 bytes img/taskbar/taskbar_offline.png | Bin 0 -> 395 bytes img/taskbar/taskbar_offline_2x.png | Bin 0 -> 711 bytes img/taskbar/taskbar_online.png | Bin 0 -> 374 bytes img/taskbar/taskbar_online_2x.png | Bin 0 -> 637 bytes res.qrc | 4 +++ src/misc/settings.cpp | 12 -------- src/misc/settings.h | 4 --- src/widget/form/settings/generalform.cpp | 9 ------ src/widget/form/settings/generalform.h | 1 - src/widget/form/settings/generalsettings.ui | 32 -------------------- src/widget/widget.cpp | 25 ++++++--------- 17 files changed, 14 insertions(+), 73 deletions(-) create mode 100644 img/taskbar/taskbar_busy.png create mode 100644 img/taskbar/taskbar_busy_2x.png create mode 100644 img/taskbar/taskbar_idle.png create mode 100644 img/taskbar/taskbar_idle_2x.png create mode 100644 img/taskbar/taskbar_invisible.png create mode 100644 img/taskbar/taskbar_invisible_2x.png create mode 100644 img/taskbar/taskbar_offline.png create mode 100644 img/taskbar/taskbar_offline_2x.png create mode 100644 img/taskbar/taskbar_online.png create mode 100644 img/taskbar/taskbar_online_2x.png diff --git a/img/taskbar/taskbar_busy.png b/img/taskbar/taskbar_busy.png new file mode 100644 index 0000000000000000000000000000000000000000..451b4b4efb3361a3e18907dfc998a120322a2265 GIT binary patch literal 420 zcmV;V0bBlwP)dfQ_RGG8(cu+KZMc-$b$F`s0Z4rfW)`Qric;A@AIKf z#0EfN(T~Lc0F9MGMx-d)&p{@W&5^wXa?=ocZ(#U6}wA zF3q08!VHJrzh`K$ z|N507Lq>+dPC$TR#?z-@R}k%jZ-4)Soea|q@`^kM2g6sOI8g>{fBP1!0pyA|zkY!Y zGvVW7*Z~wL$_pUBgIwVuBEnFks0j8yC@`-94aAy|&@vFQ0GzP^0t^5JFKz~D({U^S O0000wt!aBH${ z#KFl*oYA8b{ROzW`M5bax#Q@jegS@g##y2+5<#kon}ZE0LYjD=F|)>+ZD+RAH+eJr zF|*Gz&ph+)Zptx?@v-q`YL+PHBDX4(N0g=8``bYGc_{;6pPEBTzeU#v$|8}dSR+8- zE;Ut3SCcOkDCm$#Yd-K;0basP0YtgaK}EQHO#2FO=`UN&j;nMQS)vu{S87**#mKpG zeYx?9k;@A;dhb% zVvk6y$LyFnQv#`&hae~Ue)eMuP{7MCVuSnROhKS5mFm-|iBpO?e`u%(h)nUqqceNF zD^~syAn+V7w@v~AuHl%1P$F&Ua8#Sz8td(Pea>jL?+Q)};@c77D>jVJ0ozFY{nG8EW4+7`u^OGHq_Vh3UqR&pX zs!c+hz^3OhaMdgUJUuwb?jxbn*4>@GLNqfx9Im$tTqyI_X#5f)k60ss*$LU3fkS%q z5k|E}V1Tdtb-msc9OP>6??1laidlUZ$P%l-lZ2KD>l_%e!uid>y}rJ%HdrHY@%s8x ze8>yJ?MiS`2HP>ZTx~DG`SUZIk=SN&;Mjf$T*nxzbzrwv`%{SKM@GWZ3w6ZIK@MBh zdL_iHFl!VfI2?ciR0LE4(WIrgqC;Zs=7tp}ctLDWm=r$Il|7EYgYF;zB>_`mrKNBB yiuC!^$q3|shv+US@_)WgMVWsMh5wP?0t^7pR3Wd`%vx#Al?YX`B2w@3{gaO2@4hnB)~KS z6&6C-2Y?2zhZ^Dl#Bf(IG2jLV(fAjkv;ndpJ_G83whAEe^|2{pMDlwBkg^A21t^Ar z13%7cfFKgZ#ChfpnnAf=kBUnw!Diikm?}mX+b!&7TZwHhpAx@c1{n zG6k$*;N`;0keMjN@bK|3hUxR)fyFxNCBTNE1FQyw`0+6;TJeElArPNF|DB;{>KleL zKsv;aA44rxFK}@&F^GyVqi6t$VFOqV`1b7|gD21%=m6vjqP%eM*jKE!ZQJviI2VBY z{`K2`U|{lqo&NgmA8?p}VgqYJ0;U5{@;pyYIsgbT0Jzj)qaALdPyhe`07*qoM6N<$ Eg6KV}-2eap literal 0 HcmV?d00001 diff --git a/img/taskbar/taskbar_idle_2x.png b/img/taskbar/taskbar_idle_2x.png new file mode 100644 index 0000000000000000000000000000000000000000..118826fcc724554975237a6a329fb8fc0e7c5a50 GIT binary patch literal 719 zcmV;=0xUqYezQrvC_t&>w_(C!)0P zOVI-BmHLgqbKn2K{EEWM`)<$aNI}+HU~H(|Ns*tS0Ka%{nYwDgTj62&;rX4`O&~3y zlTz7GnSdBX-Cz!q0IC@y_Xx1@v>3P40|7&EP0oo#RI$Q?VDzxYeUrw5Zg#_HJWA73 zBa|^mC?3D#e|~;LRqKK(U(b}8#FTn4`GDTPPSC{o{beBa;@KE|T5nSQ*Oh((Fut_- z(DoW0SH7Lm&%;aCI3faK&?&C`lO`ce0G`A632*#yqe+cs+kI9&xS-ucj6S?dP)bDU zP2sn`L6n^twY`Jqe&Y^sZ*%L^{taEM(MAB_wLRrR3o`D$sW>`F4e;dgu)Y*H)!t}c zOK%)hsC{<taniNf+9%JAq#JEoFDBIN(=koQt6F~t+FJ9=ev9Co^n zdd~Eg=lC>8w7lOA{u;=OZ@xjHEKWo2cjT=A0|3N^?sPFxJB0uM002ovPDHLkV1lxT BQw;zB literal 0 HcmV?d00001 diff --git a/img/taskbar/taskbar_invisible.png b/img/taskbar/taskbar_invisible.png new file mode 100644 index 0000000000000000000000000000000000000000..81b07550b2c6931387533a7b06fc9a12a6737f07 GIT binary patch literal 478 zcmV<40U`d0P)xAyD%L5Cceqya02)10e%&fsaVu!5#)c=YdSOhq}T6=;xPE ze?I`y^MPWZzy+}%0>xf{^e`fY`gwHIk-!E}Afky~0D8@k5y|HL=(?n3q#0zSWf-ns zzs~UK(TXHm^OVHgPFM*Tx{Wjg$%7Ntr%*t24YoB6$39HFT>;^o2 z`jjCdAps>!TwGlko;-bmp%!Z_fE0rQ5oD0Gv^1FJ<>h7Y_w#3X`s67}EHFbej{-FF zfWf_c_Zar<-oqdwDgq9NQzuU`WM^hGym|cw-2n?>E>M6bBmokV_5)x-QUnMv03C;Y U4QsG54FCWD07*qoM6N<$f~?8E2><{9 literal 0 HcmV?d00001 diff --git a/img/taskbar/taskbar_invisible_2x.png b/img/taskbar/taskbar_invisible_2x.png new file mode 100644 index 0000000000000000000000000000000000000000..f02f7e04730aedcd39399ab7fd93c9b3719b79de GIT binary patch literal 704 zcmV;x0zdtUP)70iSixy* zaIZSKu`j;Qcjqs5ju^_!^voJGe?b=$LtW5Q&{qGsKh&KsXW-a}<^VU{=PI#$s-olllBhUlpl#9dO`FR9b=6fDRE297#uhA@T&cqMiDwrW7G@GN44)skDH&u@b_65qsB zO{HLB?0Fv9-P@(DEEV{LVMuzt9@*L6p{@08h)&PW&xG^4|94_6iXx?rRRGO@O;Rit z>FR1rHF9)#B*t*n*f$!Ds0~KRDu8e$B(CcUXQ~im-|C-L5ecjjYXE{E5TPEIUmWA< zunGXfn#_ADh{q3re1};57GRETOWwq>PFTHJPjG`Z0C60PT5wD5*sY3upqnmj0MSK6 z27>{uh30Wn34)W9D8`G(E)zC38iHin*KP9DdlF;ln6DhugJeDVZy zj$>$JLdAu|-7XVEb)ia$3G0DxPh2(DiEh}H2NJ~vWRfB-EP3fn-<6I}la!%+cSzPi mo!$8+B^`De68Fe=0R{lshwwpNE1o0(00002xMX4_B^f-0g~9A^fB!Npdhvqc(EIo3$^=+&X|@*-VCZ}Fh~e7TuV8~R zq@}?Io%!^MfnXqlG|YJV6s#DeY3-|542M2^fV+sOSa|s32MYi7uU}ZzVKo3`tDS%V zikKERH^b}SzcJKe4Fgbsf&47W#s;<-q*5#oA?XV$x-5LA}%Hd43fA>!Ucj535oC3_R^epT#Mx; zFTLxveP6$R@7-O9C|Wj?-GOEse8csq0)7Qvs=ifi+4VvSfh}k*!L5Ly@8Ed=QPBnf zf*a7(!3{~g$Usg8L|AgcYmeY1+$jbEKW9O?8Enq~8DP{u1?`TpbniN%0*ovDmw_$U zz32Hd;pJW3n>vnQT@)AuD*FwDXDGlMq07>x0a4*W_AFedbOT69j4@RfR0hBb(I~Sf z2>|U3K|nK8y3=S>>%>I|~g`FZM#)6?|+b1)!}?KX8) zi>M4XqfxS(NYGZaCGa)FAdP;XTzq`cRyzO;dX!F+*{Lad|6tqX^ZT1#FV4(R0DR1G zy)F$x8bHxBX>h+?-Zh&JKxFB#R?}CA))p3=^Y)_{`*7!+E!2oM0ITu1L;EOd@fnWc zLe&Ofj-C5Uw;MRN(d+%{f-$D`UGOy=65S>RpvwZw&}znFPHoTz;IY$jw16o$DpfHZ zWsgf5z(cD=cZ4ZcOeQ@+@FL}i5$0&E1E-A!&2tQNlF3k3cq7Swf)Wmg0&Gh`rZWT9{A|JUvmQ$cQZMegNKOCt;v*f_cRX3*Yn=;qz&d tW{dw0?kvc&f4)gsnSBj8|B*ie3;+fJ_t}5ITdx2B002ovPDHLkV1gV(Knnl> literal 0 HcmV?d00001 diff --git a/img/taskbar/taskbar_online.png b/img/taskbar/taskbar_online.png new file mode 100644 index 0000000000000000000000000000000000000000..24b9a8d4bba31277b1bb6077478ab5528477c90c GIT binary patch literal 374 zcmV-+0g3*JP)9dI2Fv~a_J`rY?xzgzZhk~p zrhqLL7^JPm8T69WFbq7|bqT`|tbwQ*p@h}FP!|z4fRziY!@#Z}$^cy8`?z(*Y=b@5dGvyt3R3pC5l^07WIXbN~=w0F($* Ux26+IH2?qr07*qoM6N<$g3v^WVgLXD literal 0 HcmV?d00001 diff --git a/img/taskbar/taskbar_online_2x.png b/img/taskbar/taskbar_online_2x.png new file mode 100644 index 0000000000000000000000000000000000000000..44e6013b7c46926426da481713d49ebccaf455f5 GIT binary patch literal 637 zcmV-@0)qXCP){sdylEK$E%v z#0!CV!R@=ZKM-pkBMBB80IB&vY(PNi10W6rI^qJ=900Uj07%^jVgVxE3km^8VjRIl zq=D62aA1r{FUL|jSmbD_B>F~~xr94)XTkO7}8HyDE) zc74S?hNnkgQr`g}%gxL52!@Vck{XyztRt9+4R)Bp!~l?5P7x03IY0~;-^3aSPth(? z)N=qR!I27tGz6#{1*8H{qNG(_Kvh7L1Ac$|L$bl3vVm$2czOOU$qsl$q#mLi0Ls&( zlyIOBd$9W{b<@DPDc6XO3Q%cCtXd{g;`CJ4CA=X4vJ~V{a*N^%#5m$W`Du7%gq%G= z=>%jksR@>a#1=0+O_1VCe64FHq&EEpq~;SX!3X5N19}<=g!cJp1r9)4L-2kiK!5=N XH;}yv)F_({00000NkvXXu0mjfp&RpV literal 0 HcmV?d00001 diff --git a/res.qrc b/res.qrc index d58cef6c7..40b6d28f2 100644 --- a/res.qrc +++ b/res.qrc @@ -37,6 +37,10 @@ img/status/dot_online.png img/status/dot_online_2x.png img/status/dot_online_notification.png + img/taskbar/taskbar_online_2x.png + img/taskbar/taskbar_idle_2x.png + img/taskbar/taskbar_busy_2x.png + img/taskbar/taskbar_offline_2x.png img/transfer.png smileys/cylgom/angel.png smileys/cylgom/angry.png diff --git a/src/misc/settings.cpp b/src/misc/settings.cpp index c5e13b1e7..e04dc7f49 100644 --- a/src/misc/settings.cpp +++ b/src/misc/settings.cpp @@ -129,7 +129,6 @@ void Settings::load() makeToxPortable = s.value("makeToxPortable", false).toBool(); autostartInTray = s.value("autostartInTray", false).toBool(); closeToTray = s.value("closeToTray", false).toBool(); - trayShowsUserStatus = s.value("trayShowsUserStatus", false).toBool(); forceTCP = s.value("forceTCP", false).toBool(); useProxy = s.value("useProxy", false).toBool(); proxyAddr = s.value("proxyAddr", "").toString(); @@ -281,7 +280,6 @@ void Settings::save(QString path, bool writeFriends) s.setValue("showSystemTray", showSystemTray); s.setValue("autostartInTray",autostartInTray); s.setValue("closeToTray", closeToTray); - s.setValue("trayShowsUserStatus", trayShowsUserStatus); s.setValue("useProxy", useProxy); s.setValue("forceTCP", forceTCP); s.setValue("proxyAddr", proxyAddr); @@ -521,16 +519,6 @@ void Settings::setCloseToTray(bool newValue) closeToTray = newValue; } -bool Settings::getTrayShowsUserStatus() const -{ - return trayShowsUserStatus; -} - -void Settings::setTrayShowsUserStatus(bool newValue) -{ - trayShowsUserStatus = newValue; -} - bool Settings::getMinimizeToTray() const { return minimizeToTray; diff --git a/src/misc/settings.h b/src/misc/settings.h index 5622be204..7ebe39f66 100644 --- a/src/misc/settings.h +++ b/src/misc/settings.h @@ -58,9 +58,6 @@ public: bool getCloseToTray() const; void setCloseToTray(bool newValue); - - bool getTrayShowsUserStatus() const; - void setTrayShowsUserStatus(bool newValue); bool getMinimizeToTray() const; void setMinimizeToTray(bool newValue); @@ -247,7 +244,6 @@ private: bool autostartInTray; bool closeToTray; bool minimizeToTray; - bool trayShowsUserStatus; bool useEmoticons; bool checkUpdates; bool showInFront; diff --git a/src/widget/form/settings/generalform.cpp b/src/widget/form/settings/generalform.cpp index a28fafa9f..c7f35a2ae 100644 --- a/src/widget/form/settings/generalform.cpp +++ b/src/widget/form/settings/generalform.cpp @@ -62,8 +62,6 @@ GeneralForm::GeneralForm(SettingsWidget *myParent) : bodyUI->closeToTray->setEnabled(showSystemTray); bodyUI->minimizeToTray->setChecked(Settings::getInstance().getMinimizeToTray()); bodyUI->minimizeToTray->setEnabled(showSystemTray); - bodyUI->trayShowsUserStatus->setChecked(Settings::getInstance().getTrayShowsUserStatus()); - bodyUI->trayShowsUserStatus->setEnabled(showSystemTray); bodyUI->statusChanges->setChecked(Settings::getInstance().getStatusChangeNotificationEnabled()); bodyUI->useEmoticons->setChecked(Settings::getInstance().getUseEmoticons()); bodyUI->autoacceptFiles->setChecked(Settings::getInstance().getAutoSaveEnabled()); @@ -122,7 +120,6 @@ GeneralForm::GeneralForm(SettingsWidget *myParent) : connect(bodyUI->startInTray, &QCheckBox::stateChanged, this, &GeneralForm::onSetAutostartInTray); connect(bodyUI->closeToTray, &QCheckBox::stateChanged, this, &GeneralForm::onSetCloseToTray); connect(bodyUI->minimizeToTray, &QCheckBox::stateChanged, this, &GeneralForm::onSetMinimizeToTray); - connect(bodyUI->trayShowsUserStatus, &QCheckBox::stateChanged, this, &GeneralForm::onSettrayShowsUserStatus); connect(bodyUI->statusChanges, &QCheckBox::stateChanged, this, &GeneralForm::onSetStatusChange); connect(bodyUI->autoAwaySpinBox, SIGNAL(editingFinished()), this, SLOT(onAutoAwayChanged())); connect(bodyUI->showInFront, &QCheckBox::stateChanged, this, &GeneralForm::onSetShowInFront); @@ -193,12 +190,6 @@ void GeneralForm::onSetMinimizeToTray() Settings::getInstance().setMinimizeToTray(bodyUI->minimizeToTray->isChecked()); } -void GeneralForm::onSettrayShowsUserStatus() -{ - Settings::getInstance().setTrayShowsUserStatus(bodyUI->trayShowsUserStatus->isChecked()); - Widget::getInstance()->updateTrayIcon(); -} - void GeneralForm::onStyleSelected(QString style) { if(bodyUI->styleBrowser->currentIndex() == 0) diff --git a/src/widget/form/settings/generalform.h b/src/widget/form/settings/generalform.h index dba72d404..ec41fb0ad 100644 --- a/src/widget/form/settings/generalform.h +++ b/src/widget/form/settings/generalform.h @@ -49,7 +49,6 @@ private slots: void onAutoAwayChanged(); void onUseEmoticonsChange(); void onSetMinimizeToTray(); - void onSettrayShowsUserStatus(); void onReconnectClicked(); void onAutoAcceptFileChange(); void onAutoSaveDirChange(); diff --git a/src/widget/form/settings/generalsettings.ui b/src/widget/form/settings/generalsettings.ui index c4d07dfd8..4df38f286 100644 --- a/src/widget/form/settings/generalsettings.ui +++ b/src/widget/form/settings/generalsettings.ui @@ -152,22 +152,6 @@ - - - - - 0 - 0 - - - - Tray icon displays user status - - - This is a temporary work around until proper systray status icons are available. - - - @@ -607,21 +591,5 @@ - - showSystemTray - toggled(bool) - trayShowsUserStatus - setEnabled(bool) - - - 148 - 143 - - - 158 - 205 - - - diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index 4736f3642..f30e86544 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -295,22 +295,17 @@ void Widget::setTranslation() void Widget::updateTrayIcon() { - if(Settings::getInstance().getTrayShowsUserStatus()) - { - QString status = ui->statusButton->property("status").toString(); - QString icon; - if(status == "online") - icon = ":img/status/dot_online_2x.png"; - else if(status == "away") - icon = ":img/status/dot_idle_2x.png"; - else if(status == "busy") - icon = ":img/status/dot_busy_2x.png"; - else - icon = ":img/status/dot_away_2x.png"; - this->icon->setIcon(QIcon(icon)); - } + QString status = ui->statusButton->property("status").toString(); + QString icon; + if(status == "online") + icon = ":img/taskbar/taskbar_online_2x.png"; + else if(status == "away") + icon = ":img/taskbar/taskbar_idle_2x.png"; + else if(status == "busy") + icon = ":img/taskbar/taskbar_busy_2x.png"; else - icon->setIcon(windowIcon()); + icon = ":img/taskbar/taskbar_offline_2x.png"; + this->icon->setIcon(QIcon(icon)); } Widget::~Widget() From a5c5618b97a84275f6cb79504d29855b6d5ba6e2 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Sat, 6 Dec 2014 12:44:38 +0100 Subject: [PATCH 218/253] Fix include 'Windows.h' -> 'windows.h' MinGW is case sensitive --- src/platform/timer_win.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/timer_win.cpp b/src/platform/timer_win.cpp index 07650a0e1..e860a5ac6 100644 --- a/src/platform/timer_win.cpp +++ b/src/platform/timer_win.cpp @@ -17,7 +17,7 @@ #include #ifdef Q_OS_WIN32 #include "src/platform/timer.h" -#include +#include uint32_t Platform::getIdleTime() From 324d075cfe2960ff75b8a312c66de37143bdd949 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Sat, 6 Dec 2014 13:49:04 +0100 Subject: [PATCH 219/253] Change push to talk shortcut to CTRL+P --- src/widget/form/groupchatform.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/widget/form/groupchatform.cpp b/src/widget/form/groupchatform.cpp index cbb41df6b..b14db2fb5 100644 --- a/src/widget/form/groupchatform.cpp +++ b/src/widget/form/groupchatform.cpp @@ -212,8 +212,8 @@ void GroupChatForm::keyPressEvent(QKeyEvent* ev) if (msgEdit->hasFocus()) return; - // Push to talk - if (ev->key() == Qt::Key_P && inCall) + // Push to talk (CTRL+P) + if (ev->key() == Qt::Key_P && (ev->modifiers() & Qt::ControlModifier) && inCall) { Core* core = Core::getInstance(); if (!core->isGroupCallMicEnabled(group->getGroupId())) @@ -231,7 +231,7 @@ void GroupChatForm::keyReleaseEvent(QKeyEvent* ev) if (msgEdit->hasFocus()) return; - // Push to talk + // Push to talk (CTRL+P (only need to release P)) if (ev->key() == Qt::Key_P && inCall) { Core* core = Core::getInstance(); From 424af0505e6370ca0a1d438751ae8986e119ba4f Mon Sep 17 00:00:00 2001 From: Simon Vermeersch Date: Tue, 25 Nov 2014 22:20:39 +0100 Subject: [PATCH 220/253] Focus the text input area when clicking on the text area --- src/widget/chatareawidget.cpp | 1 + src/widget/chatareawidget.h | 1 + src/widget/form/genericchatform.cpp | 6 ++++++ src/widget/form/genericchatform.h | 1 + 4 files changed, 9 insertions(+) diff --git a/src/widget/chatareawidget.cpp b/src/widget/chatareawidget.cpp index 393a01c0e..22886ba8c 100644 --- a/src/widget/chatareawidget.cpp +++ b/src/widget/chatareawidget.cpp @@ -87,6 +87,7 @@ void ChatAreaWidget::mouseReleaseEvent(QMouseEvent * event) } } } + emit onClick(); } void ChatAreaWidget::onAnchorClicked(const QUrl &url) diff --git a/src/widget/chatareawidget.h b/src/widget/chatareawidget.h index 2eda1aad4..72a17823c 100644 --- a/src/widget/chatareawidget.h +++ b/src/widget/chatareawidget.h @@ -41,6 +41,7 @@ public slots: signals: void onFileTranfertInterract(QString widgetName, QString buttonName); + void onClick(); protected: void mouseReleaseEvent(QMouseEvent * event); diff --git a/src/widget/form/genericchatform.cpp b/src/widget/form/genericchatform.cpp index d9912a091..176e3c876 100644 --- a/src/widget/form/genericchatform.cpp +++ b/src/widget/form/genericchatform.cpp @@ -152,6 +152,7 @@ GenericChatForm::GenericChatForm(QWidget *parent) : connect(emoteButton, SIGNAL(clicked()), this, SLOT(onEmoteButtonClicked())); connect(chatWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onChatContextMenuRequested(QPoint))); + connect(chatWidget, SIGNAL(onClick()), this, SLOT(onChatWidgetClicked())); chatWidget->document()->setDefaultStyleSheet(Style::getStylesheet(":ui/chatArea/innerStyle.css")); chatWidget->setStyleSheet(Style::getStylesheet(":/ui/chatArea/chatArea.css")); @@ -252,6 +253,11 @@ void GenericChatForm::onEmoteButtonClicked() } } +void GenericChatForm::onChatWidgetClicked() +{ + msgEdit->setFocus(); +} + void GenericChatForm::onEmoteInsertRequested(QString str) { // insert the emoticon diff --git a/src/widget/form/genericchatform.h b/src/widget/form/genericchatform.h index d838fd31e..fe6de80fb 100644 --- a/src/widget/form/genericchatform.h +++ b/src/widget/form/genericchatform.h @@ -69,6 +69,7 @@ protected slots: void onEmoteButtonClicked(); void onEmoteInsertRequested(QString str); void clearChatArea(bool); + void onChatWidgetClicked(); protected: QString getElidedName(const QString& name); From 53d7bc9dc1237e2389331941f75e404ef5818221 Mon Sep 17 00:00:00 2001 From: "Tux3 / Mlkj / !Lev.uXFMLA" Date: Sat, 6 Dec 2014 13:59:19 +0100 Subject: [PATCH 221/253] Push to talk with input msgedit focused --- src/widget/form/groupchatform.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/widget/form/groupchatform.cpp b/src/widget/form/groupchatform.cpp index b14db2fb5..d1fb70518 100644 --- a/src/widget/form/groupchatform.cpp +++ b/src/widget/form/groupchatform.cpp @@ -209,9 +209,6 @@ void GroupChatForm::onCallClicked() void GroupChatForm::keyPressEvent(QKeyEvent* ev) { - if (msgEdit->hasFocus()) - return; - // Push to talk (CTRL+P) if (ev->key() == Qt::Key_P && (ev->modifiers() & Qt::ControlModifier) && inCall) { @@ -224,13 +221,13 @@ void GroupChatForm::keyPressEvent(QKeyEvent* ev) Style::repolish(micButton); } } + + if (msgEdit->hasFocus()) + return; } void GroupChatForm::keyReleaseEvent(QKeyEvent* ev) { - if (msgEdit->hasFocus()) - return; - // Push to talk (CTRL+P (only need to release P)) if (ev->key() == Qt::Key_P && inCall) { @@ -243,4 +240,7 @@ void GroupChatForm::keyReleaseEvent(QKeyEvent* ev) Style::repolish(micButton); } } + + if (msgEdit->hasFocus()) + return; } From cfae6104d43109c7e2036e44ac5908e2f6861111 Mon Sep 17 00:00:00 2001 From: Lednerb Date: Mon, 8 Dec 2014 14:39:39 +0100 Subject: [PATCH 222/253] Fixed language error. --- translations/de.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/translations/de.ts b/translations/de.ts index 25ca913e6..f3dbcf2ea 100644 --- a/translations/de.ts +++ b/translations/de.ts @@ -594,22 +594,27 @@ Wollen Sie ein anderes probieren? Mache Tox portabel - - Show system tray + + System tray integration + Systemtray Integration + + + + Show system tray icon Im Systemtray zeigen - + Start in tray Ins Tray starten - + Close to tray Ins Tray schließen - + Minimize to tray Ins Tray minimieren @@ -1629,4 +1634,4 @@ Es wird beim Neustart von qTox installiert. Senden der Nachricht fehlgeschlagen - + \ No newline at end of file From cd82ee9f115fd9d94103d7ae518a8a9409ba1a68 Mon Sep 17 00:00:00 2001 From: Lednerb Date: Mon, 8 Dec 2014 14:55:57 +0100 Subject: [PATCH 223/253] Added translation. --- translations/de.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/translations/de.ts b/translations/de.ts index f3dbcf2ea..cf02bf206 100644 --- a/translations/de.ts +++ b/translations/de.ts @@ -36,6 +36,11 @@ Capture device Aufnahmegerät + + + Rescan audio devices + Erneut nach Audiogeräten suchen + Video Settings From dfb51d7d2911e388dc625bbea366308eb76eb728 Mon Sep 17 00:00:00 2001 From: Lednerb Date: Mon, 8 Dec 2014 19:07:51 +0100 Subject: [PATCH 224/253] Added Translation for error message on sending a file. Also included the German Version. --- src/widget/form/chatform.cpp | 2 +- translations/de.ts | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/widget/form/chatform.cpp b/src/widget/form/chatform.cpp index a97faf93c..b9a7d6776 100644 --- a/src/widget/form/chatform.cpp +++ b/src/widget/form/chatform.cpp @@ -673,7 +673,7 @@ void ChatForm::onFileSendFailed(int FriendId, const QString &fname) if (FriendId != f->getFriendID()) return; - addSystemInfoMessage("File: \"" + fname + "\" failed to send.", "red", QDateTime::currentDateTime()); + addSystemInfoMessage(tr("Failed to send file") + ": \"" + fname + "\"", "red", QDateTime::currentDateTime()); } void ChatForm::onAvatarChange(int FriendId, const QPixmap &pic) diff --git a/translations/de.ts b/translations/de.ts index cf02bf206..6636bf5de 100644 --- a/translations/de.ts +++ b/translations/de.ts @@ -257,6 +257,11 @@ Soll der Proxy ignoriert und eine direkte Internetverbindung genutzt werden?Call rejected Anruf abgewiesen + + + Failed to send file + Fehler beim Senden der Datei + Call with %1 ended. %2 From 17bcd969b23fac89f938f7c5c638db4391420d2b Mon Sep 17 00:00:00 2001 From: Sascha Brendel Date: Mon, 8 Dec 2014 20:28:34 +0100 Subject: [PATCH 225/253] Update chatform.cpp Improved that line of code. Thanks to @dubslow --- src/widget/form/chatform.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widget/form/chatform.cpp b/src/widget/form/chatform.cpp index b9a7d6776..50b455289 100644 --- a/src/widget/form/chatform.cpp +++ b/src/widget/form/chatform.cpp @@ -673,7 +673,7 @@ void ChatForm::onFileSendFailed(int FriendId, const QString &fname) if (FriendId != f->getFriendID()) return; - addSystemInfoMessage(tr("Failed to send file") + ": \"" + fname + "\"", "red", QDateTime::currentDateTime()); + addSystemInfoMessage(tr("Failed to send file \"%1\"").arg(fname), "red", QDateTime::currentDateTime()); } void ChatForm::onAvatarChange(int FriendId, const QPixmap &pic) From fd6608b15432c0a7dd2900aa528346b67d00f779 Mon Sep 17 00:00:00 2001 From: Zetok Zalbavar Date: Mon, 8 Dec 2014 23:15:53 +0000 Subject: [PATCH 226/253] Update INSTALL.md --- INSTALL.md | 368 ++++++++++++++++++++++++++++------------------------- 1 file changed, 192 insertions(+), 176 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 14a647f47..63cefe8a1 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,8 +1,8 @@ #Install Instructions - [Dependencies](#dependencies) -- [Windows](#windows) - [Linux](#linux) - [OS X](#osx) +- [Windows](#windows) ##Dependencies @@ -15,6 +15,196 @@ | OpenCV | >= 2.4.9 | core, highgui, imgproc | | OpenAL Soft | >= 1.16.0 | | + + +##Linux +###Simple install +Easy qTox install is provided for variety of distributions: +https://wiki.tox.im/Binaries#Apt.2FAptitude_.28Debian.2C_Ubuntu.2C_Mint.2C_etc..29 + +If your distribution is not listed, or you want/need to compile qTox, there are provided instructions. + +**Please note that installing toxcore/qTox from AUR is not supported**, although installing other dependencies, provided that they met requirements, should be fine, unless you are installing cryptography library from AUR, which should rise red flags by itself… + +---- + +Most of the dependencies should be available through your package manger. You may either follow the directions below, or simply run `./simple_make.sh` after cloning, which will attempt to automatically download dependencies followed by compilation. + + +###Cloning the Repository +In order to clone the qTox repository you need Git. + +Arch Linux: +```bash +sudo pacman -S --needed git +``` + +Debian: +```bash +sudo apt-get install git +``` + +Fedora: +```bash +yum install git +``` + +Ubuntu: +```bash +sudo apt-get install git +``` + +Afterwards open a new Terminal, change to a directory of your choice and clone the repository: +```bash +cd /home/user/qTox +git clone https://github.com/tux3/qTox.git qTox +``` + +The following steps assumes that you cloned the repository at "/home/user/qTox". If you decided to choose another location, replace corresponding parts. + +###GCC, Qt, OpenCV and OpanAL Soft + +Arch Linux: +```bash +sudo pacman -S --needed base-devel qt5 opencv openal libxss +``` + +Debian: +```bash +sudo apt-get install build-essential qt5-qmake qt5-default libopenal-dev libopencv-dev libxss-dev +``` + +Fedora: +```bash +yum groupinstall "Development Tools" +yum install qt-devel qt-doc qt-creator opencv-devel openal-soft-devel libXScrnSaver-devel +``` + +Slackware: + +You can grab slackbuilds of the needed dependencies here: + +http://slackbuilds.org/repository/14.1/libraries/OpenAL/ + +http://slackbuilds.org/repository/14.1/libraries/qt5/ + +http://slackbuilds.org/repository/14.1/libraries/opencv/ + +Ubuntu: +```bash +sudo apt-get install build-essential qt5-qmake qt5-default qttools5-dev-tools libopenal-dev libopencv-dev libxss-dev +``` + +###Tox Core + +First of all install the dependencies of Tox Core. + +Arch Linux: +```bash +sudo pacman -S --needed opus vpx +``` + +``` +Debian: +```bash +sudo apt-get install libtool autotools-dev automake checkinstall check libopus-dev libvpx-dev +``` + +Fedora: +```bash +yum install libtool autoconf automake check check-devel +``` + +Ubuntu: +```bash +sudo apt-get install libtool autotools-dev automake checkinstall check libopus-dev libvpx-dev +``` + +Now you can either follow the instructions at https://github.com/irungentoo/toxcore/blob/master/INSTALL.md#unix or use the "bootstrap.sh" script located at "/home/user/qTox". +The script will automatically download and install Tox Core and libsodium to "/home/user/qTox/libs": +```bash +cd /home/user/qTox +./bootstrap.sh # use -h or --help for more information +``` + +After all the dependencies are thus reeady to go, compiling should be as simple as +```bash +qmake +make +``` + +###Building packages + +Alternately, qTox now has the experimental and probably-dodgy ability to package itself (in .deb +form natively, and .rpm form with alien). + +After installing the required dependencies, run `bootstrap.sh` and then run the +`buildPackages.sh` script, found in the tools folder. It will automatically get the +packages necessary for building .debs, so be prepared to type your password for sudo. + + + +##OS X + +###OSX Easy Install + +Since https://github.com/ReDetection/homebrew-qtox you can easily install qtox with homebrew +```bash +brew install --HEAD ReDetection/qtox/qtox +``` + + +###OSX Full Install Guide + +This guide is intended for people who wish to use an existing or new ProjectTox-Core installation separate to the bundled installation with qTox, if you do not wish to use a separate installation you can skip to the section titled 'Final Steps'. + +Installation on OSX, isn't quite straight forward, here is a quick guide on how to install; + +Note that qTox now requires OpenCV and OpenAL for video and audio. + +The first thing you need to do is install ProjectTox-Core with a/v support. Refer to the INSTALL guide in the PrjectTox-Core github repo. + +Next you need to download QtTools (http://qt-project.org/downloads), at the time of writing this is at version .3.0. +Make sure you deselect all the unnecessary components from the 5.3 checkbox (iOS/Android libs) otherwise you will end up with a very large download. + +Once that is installed you will most likely need to set the path for qmake. To do this, open up terminal and paste in the following; + +```bash +export PATH=/location/to/qmake/binary:$PATH +``` + +For myself, the qmake binary was located in /Users/mouseym/Qt/5.3/clang_64/bin/. + +This is not a permanent change, it will revert when you close the terminal window, to add it permanently you will need to add echo the above line to your .profile/.bash_profile. + +Once this is installed, do the following; + +```bash +git clone https://github.com/tux3/qTox +cd toxgui +qmake +``` + +Now, we need to create a symlink to /usr/local/lib/ and /usr/local/include/ +``` +mkdir -p $HOME/qTox/libs +sudo ln -s /usr/local/lib $HOME/qTox/libs/lib +sudo ln -s /usr/local/include $HOME/qTox/libs/include +``` +####Final Steps + +The final step is to run +```bash +make +``` +in the qTox directory, or if you are using the bundled tox core installation, you can use +```bash +./bootstrap.sh +make +``` +Assuming all went well you should now have a qTox.app file within the directory. Double click and it should open! + + ##Windows @@ -74,178 +264,4 @@ As for OpenCV there are no prebuild packages of OpenAL Softe compiled with MinGW make make install ``` -Copy the dll "OpenAL32.dll" located at "C:\qTox\libs\openal-build\install\bin" to "C:\qTox\libs\lib". Finally, copy the directory "AL" located at "C:\qTox\libs\openal-build\install\include" to "C:\qTox\libs\include". Unlike OpenCV you don't need to patch any files. Feel free to delete the directories "openal-soft-x.y.z" and "openal-build", but you don't need to. - - -##Linux -Most of the dependencies should be available through your package manger. You may either follow the directions below, or simply run `./simple_make.sh` after cloning, which will attempt to automatically download dependencies followed by compilation. - -###Cloning the Repository -In order to clone the qTox repository you need Git. - -Debian: -```bash -sudo apt-get install git -``` - -Ubuntu: -```bash -sudo apt-get install git -``` - -Arch Linux: -```bash -sudo pacman -S --needed git -``` - -Fedora: -```bash -yum install git -``` - -Afterwards open a new Terminal, change to a directory of your choice and clone the repository: -```bash -cd /home/user/qTox -git clone https://github.com/tux3/qTox.git qTox -``` - -The following steps assumes that you cloned the repository at "/home/user/qTox". If you decided to choose another location, replace corresponding parts. - -###GCC, Qt, OpenCV and OpanAL Soft - -Debian: -```bash -sudo apt-get install build-essential qt5-qmake qt5-default libopenal-dev libopencv-dev libxss-dev -``` - -Ubuntu: -```bash -sudo apt-get install build-essential qt5-qmake qt5-default qttools5-dev-tools libopenal-dev libopencv-dev libxss-dev -``` - -Arch Linux: -```bash -sudo pacman -S --needed base-devel qt5 opencv openal libxss -``` - -Fedora: -```bash -yum groupinstall "Development Tools" -yum install qt-devel qt-doc qt-creator opencv-devel openal-soft-devel libXScrnSaver-devel -``` - -Slackware: - -You can grab slackbuilds of the needed dependencies here: - -http://slackbuilds.org/repository/14.1/libraries/OpenAL/ - -http://slackbuilds.org/repository/14.1/libraries/qt5/ - -http://slackbuilds.org/repository/14.1/libraries/opencv/ - -###Tox Core - -First of all install the dependencies of Tox Core. - -Debian: -```bash -sudo apt-get install libtool autotools-dev automake checkinstall check libopus-dev libvpx-dev -``` - -Ubuntu: -```bash -sudo apt-get install libtool autotools-dev automake checkinstall check libopus-dev libvpx-dev -``` - -Arch Linux: (Arch Linux provides the package "tox-git" in AUR) -```bash -sudo pacman -S --needed opus vpx -``` - -Fedora: -```bash -yum install libtool autoconf automake check check-devel -``` - -Now you can either follow the instructions at https://github.com/irungentoo/toxcore/blob/master/INSTALL.md#unix or use the "bootstrap.sh" script located at "/home/user/qTox". -The script will automatically download and install Tox Core and libsodium to "/home/user/qTox/libs": -```bash -cd /home/user/qTox -./bootstrap.sh # use -h or --help for more information -``` - -After all the dependencies are thus reeady to go, compiling should be as simple as -```bash -qmake -make -``` - -###Building packages - -Alternately, qTox now has the experimental and probably-dodgy ability to package itself (in .deb -form natively, and .rpm form with alien). - -After installing the required dependencies, run `bootstrap.sh` and then run the -`buildPackages.sh` script, found in the tools folder. It will automatically get the -packages necessary for building .debs, so be prepared to type your password for sudo. - - -##OS X - -###OSX Easy Install - -Since https://github.com/ReDetection/homebrew-qtox you can easily install qtox with homebrew -```bash -brew install --HEAD ReDetection/qtox/qtox -``` - -###OSX Full Install Guide - -This guide is intended for people who wish to use an existing or new ProjectTox-Core installation separate to the bundled installation with qTox, if you do not wish to use a separate installation you can skip to the section titled 'Final Steps'. - -Installation on OSX, isn't quite straight forward, here is a quick guide on how to install; - -Note that qTox now requires OpenCV and OpenAL for video and audio. - -The first thing you need to do is install ProjectTox-Core with a/v support. Refer to the INSTALL guide in the PrjectTox-Core github repo. - -Next you need to download QtTools (http://qt-project.org/downloads), at the time of writing this is at version .3.0. -Make sure you deselect all the unnecessary components from the 5.3 checkbox (iOS/Android libs) otherwise you will end up with a very large download. - -Once that is installed you will most likely need to set the path for qmake. To do this, open up terminal and paste in the following; - -```bash -export PATH=/location/to/qmake/binary:$PATH -``` - -For myself, the qmake binary was located in /Users/mouseym/Qt/5.3/clang_64/bin/. - -This is not a permanent change, it will revert when you close the terminal window, to add it permanently you will need to add echo the above line to your .profile/.bash_profile. - -Once this is installed, do the following; - -```bash -git clone https://github.com/tux3/qTox -cd toxgui -qmake -``` - -Now, we need to create a symlink to /usr/local/lib/ and /usr/local/include/ -``` -mkdir -p $HOME/qTox/libs -sudo ln -s /usr/local/lib $HOME/qTox/libs/lib -sudo ln -s /usr/local/include $HOME/qTox/libs/include -``` -####Final Steps - -The final step is to run -```bash -make -``` -in the qTox directory, or if you are using the bundled tox core installation, you can use -```bash -./bootstrap.sh -make -``` -Assuming all went well you should now have a qTox.app file within the directory. Double click and it should open! +Copy the dll "OpenAL32.dll" located at "C:\qTox\libs\openal-build\install\bin" to "C:\qTox\libs\lib". Finally, copy the directory "AL" located at "C:\qTox\libs\openal-build\install\include" to "C:\qTox\libs\include". Unlike OpenCV you don't need to patch any files. Feel free to delete the directories "openal-soft-x.y.z" and "openal-build", but you don't need to. \ No newline at end of file From 5cb1b6fe8e2180bab003bc0a95231c82d074d582 Mon Sep 17 00:00:00 2001 From: Dubslow Date: Tue, 9 Dec 2014 14:18:12 -0600 Subject: [PATCH 227/253] check for no systray, fixes #908 cant believe it took this long to figure out ;_; --- src/widget/widget.cpp | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index f30e86544..dcf6c67a4 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -125,7 +125,8 @@ void Widget::init() } else { - qWarning() << "No system tray detected!"; + qWarning() << "Widget: No system tray detected!"; + icon = nullptr; this->show(); } @@ -295,17 +296,19 @@ void Widget::setTranslation() void Widget::updateTrayIcon() { + if (!icon) + return; QString status = ui->statusButton->property("status").toString(); - QString icon; - if(status == "online") - icon = ":img/taskbar/taskbar_online_2x.png"; - else if(status == "away") - icon = ":img/taskbar/taskbar_idle_2x.png"; - else if(status == "busy") - icon = ":img/taskbar/taskbar_busy_2x.png"; + QString pic; + if (status == "online") + pic = ":img/taskbar/taskbar_online_2x.png"; + else if (status == "away") + pic = ":img/taskbar/taskbar_idle_2x.png"; + else if (status == "busy") + pic = ":img/taskbar/taskbar_busy_2x.png"; else - icon = ":img/taskbar/taskbar_offline_2x.png"; - this->icon->setIcon(QIcon(icon)); + pic = ":img/taskbar/taskbar_offline_2x.png"; + icon->setIcon(QIcon(pic)); } Widget::~Widget() From 13306031e238258a3faf2909a01987cf093765dc Mon Sep 17 00:00:00 2001 From: apprb Date: Thu, 11 Dec 2014 21:33:38 +0600 Subject: [PATCH 228/253] fix #902 --- src/widget/tool/chatactions/chataction.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widget/tool/chatactions/chataction.cpp b/src/widget/tool/chatactions/chataction.cpp index a2aa3e140..00f872106 100644 --- a/src/widget/tool/chatactions/chataction.cpp +++ b/src/widget/tool/chatactions/chataction.cpp @@ -25,7 +25,7 @@ QTextBlockFormat ChatAction::nameFormat, ChatAction::dateFormat; QString ChatAction::toHtmlChars(const QString &str) { - static QList> replaceList = {{"&","&"}, {">",">"}, {"<","<"}}; + static QList> replaceList = {{"&","&"}, {">",">"}, {"<","<"}, {" ", " "}}; // {"&","&"} should be always first QString res = str; for (auto &it : replaceList) From e0993a72f6bf4a748bf35e24da86425e038beb69 Mon Sep 17 00:00:00 2001 From: Zetok Zalbavar Date: Thu, 11 Dec 2014 18:05:52 +0000 Subject: [PATCH 229/253] Style corrections MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 'if()' → 'if ()' 'for()' → 'for ()' --- src/audio.cpp | 6 ++-- src/core.cpp | 8 +++--- src/coreav.cpp | 28 +++++++++---------- src/main.cpp | 4 +-- src/misc/smileypack.cpp | 10 +++---- src/platform/timer_win.cpp | 2 +- src/platform/timer_x11.cpp | 6 ++-- src/video/cameraworker.cpp | 4 +-- src/widget/form/chatform.cpp | 8 +++--- src/widget/form/settings/generalform.cpp | 12 ++++---- src/widget/form/tabcompleter.cpp | 2 +- src/widget/maskablepixmapwidget.cpp | 2 +- src/widget/tool/chatactions/messageaction.cpp | 2 +- src/widget/widget.cpp | 18 ++++++------ 14 files changed, 56 insertions(+), 56 deletions(-) diff --git a/src/audio.cpp b/src/audio.cpp index a394e72d4..d90d5d48e 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -184,14 +184,14 @@ void Audio::playAudioBuffer(ALuint alSource, const int16_t *data, int samples, u alGetSourcei(alSource, AL_BUFFERS_QUEUED, &queued); alSourcei(alSource, AL_LOOPING, AL_FALSE); - if(processed) + if (processed) { ALuint bufids[processed]; alSourceUnqueueBuffers(alSource, processed, bufids); alDeleteBuffers(processed - 1, bufids + 1); bufid = bufids[0]; } - else if(queued < 16) + else if (queued < 16) { alGenBuffers(1, &bufid); } @@ -207,6 +207,6 @@ void Audio::playAudioBuffer(ALuint alSource, const int16_t *data, int samples, u ALint state; alGetSourcei(alSource, AL_SOURCE_STATE, &state); - if(state != AL_PLAYING) + if (state != AL_PLAYING) alSourcePlay(alSource); } diff --git a/src/core.cpp b/src/core.cpp index 05ce62261..637436fb2 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -728,7 +728,7 @@ void Core::requestFriendship(const QString& friendAddress, const QString& messag { const QString userId = friendAddress.mid(0, TOX_CLIENT_ID_SIZE * 2); - if(hasFriendWithAddress(friendAddress)) + if (hasFriendWithAddress(friendAddress)) { emit failedToAddFriend(userId, QString(tr("Friend is already added"))); } @@ -1677,7 +1677,7 @@ void Core::createGroup(uint8_t type) bool Core::hasFriendWithAddress(const QString &addr) const { // Valid length check - if(addr.length() != (TOX_FRIEND_ADDRESS_SIZE * 2)) + if (addr.length() != (TOX_FRIEND_ADDRESS_SIZE * 2)) { return false; } @@ -1689,7 +1689,7 @@ bool Core::hasFriendWithAddress(const QString &addr) const bool Core::hasFriendWithPublicKey(const QString &pubkey) const { // Valid length check - if(pubkey.length() != (TOX_CLIENT_ID_SIZE * 2)) + if (pubkey.length() != (TOX_CLIENT_ID_SIZE * 2)) { return false; } @@ -1706,7 +1706,7 @@ bool Core::hasFriendWithPublicKey(const QString &pubkey) const QString addrOrId = getFriendAddress(ids[i]); // Set true if found - if(addrOrId.toUpper().startsWith(pubkey.toUpper())) + if (addrOrId.toUpper().startsWith(pubkey.toUpper())) { found = true; break; diff --git a/src/coreav.cpp b/src/coreav.cpp index a00d54ae6..13f6dd8c0 100644 --- a/src/coreav.cpp +++ b/src/coreav.cpp @@ -207,7 +207,7 @@ void Core::playCallAudio(void* toxav, int32_t callId, const int16_t *data, uint1 alGenSources(1, &calls[callId].alSource); ToxAvCSettings dest; - if(toxav_get_peer_csettings((ToxAv*)toxav, callId, 0, &dest) == 0) + if (toxav_get_peer_csettings((ToxAv*)toxav, callId, 0, &dest) == 0) playAudioBuffer(calls[callId].alSource, data, samples, dest.audio_channels, dest.audio_sample_rate); } @@ -229,24 +229,24 @@ void Core::sendCallAudio(int callId, ToxAv* toxav) bool frame = false; ALint samples; alcGetIntegerv(Audio::alInDev, ALC_CAPTURE_SAMPLES, sizeof(samples), &samples); - if(samples >= framesize) + if (samples >= framesize) { memset(buf, 0, bufsize); // Avoid uninitialized values (Valgrind) alcCaptureSamples(Audio::alInDev, buf, framesize); frame = 1; } - if(frame) + if (frame) { int r; - if((r = toxav_prepare_audio_frame(toxav, callId, dest, framesize*2, (int16_t*)buf, framesize)) < 0) + if ((r = toxav_prepare_audio_frame(toxav, callId, dest, framesize*2, (int16_t*)buf, framesize)) < 0) { qDebug() << "Core: toxav_prepare_audio_frame error"; calls[callId].sendAudioTimer->start(); return; } - if((r = toxav_send_audio(toxav, callId, dest, r)) < 0) + if ((r = toxav_send_audio(toxav, callId, dest, r)) < 0) qDebug() << "Core: toxav_send_audio error"; } calls[callId].sendAudioTimer->start(); @@ -271,7 +271,7 @@ void Core::sendCallVideo(int callId) if (frame.w && frame.h) { int result; - if((result = toxav_prepare_video_frame(toxav, callId, videobuf, videobufsize, &frame)) < 0) + if ((result = toxav_prepare_video_frame(toxav, callId, videobuf, videobufsize, &frame)) < 0) { qDebug() << QString("Core: toxav_prepare_video_frame: error %1").arg(result); vpx_img_free(&frame); @@ -279,7 +279,7 @@ void Core::sendCallVideo(int callId) return; } - if((result = toxav_send_video(toxav, callId, (uint8_t*)videobuf, result)) < 0) + if ((result = toxav_send_video(toxav, callId, (uint8_t*)videobuf, result)) < 0) qDebug() << QString("Core: toxav_send_video error: %1").arg(result); vpx_img_free(&frame); @@ -540,7 +540,7 @@ void Core::onAvStart(void* _toxav, int32_t call_index, void* core) // This function's logic was shamelessly stolen from uTox void Core::playAudioBuffer(ALuint alSource, const int16_t *data, int samples, unsigned channels, int sampleRate) { - if(!channels || channels > 2) + if (!channels || channels > 2) { qWarning() << "Core::playAudioBuffer: trying to play on "<= framesize) + if (samples >= framesize) { memset(buf, 0, bufsize); // Avoid uninitialized values (Valgrind) alcCaptureSamples(Audio::alInDev, buf, framesize); frame = 1; } - if(frame) + if (frame) { int r; - if((r = toxav_group_send_audio(toxav_get_tox(toxav), groupId, (int16_t*)buf, + if ((r = toxav_group_send_audio(toxav_get_tox(toxav), groupId, (int16_t*)buf, framesize, av_DefaultSettings.audio_channels, av_DefaultSettings.audio_sample_rate)) < 0) { qDebug() << "Core: toxav_group_send_audio error"; diff --git a/src/main.cpp b/src/main.cpp index 3ecb63d1c..ef378bfbc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -70,7 +70,7 @@ int main(int argc, char *argv[]) parser.process(a); Settings::getInstance(); // Build our Settings singleton as soon as QApplication is ready, not before - if(parser.isSet("P")) + if (parser.isSet("P")) Settings::getInstance().setCurrentProfile(parser.value("P")); sodium_init(); // For the auto-updater @@ -156,7 +156,7 @@ int main(int argc, char *argv[]) return EXIT_FAILURE; } } - else if(!ipc.isCurrentOwner() && !parser.isSet("P")) + else if (!ipc.isCurrentOwner() && !parser.isSet("P")) { time_t event = ipc.postEvent("$activate"); ipc.waitUntilProcessed(event); diff --git a/src/misc/smileypack.cpp b/src/misc/smileypack.cpp index 1ec70f954..c640fffb4 100644 --- a/src/misc/smileypack.cpp +++ b/src/misc/smileypack.cpp @@ -69,9 +69,9 @@ QList > SmileyPack::listSmileyPacks(const QStringList &p if (relPath.leftRef(2) == "..") { - if(!smileyPacks.contains(QPair(packageName, absPath))) + if (!smileyPacks.contains(QPair(packageName, absPath))) smileyPacks << QPair(packageName, absPath); - else if(!smileyPacks.contains(QPair(packageName, relPath))) + else if (!smileyPacks.contains(QPair(packageName, relPath))) smileyPacks << QPair(packageName, relPath); // use relative path for subdirectories } } @@ -97,7 +97,7 @@ bool SmileyPack::load(const QString& filename) // open emoticons.xml QFile xmlFile(filename); - if(!xmlFile.open(QIODevice::ReadOnly)) + if (!xmlFile.open(QIODevice::ReadOnly)) return false; // cannot open file /* parse the cfg file @@ -138,14 +138,14 @@ bool SmileyPack::load(const QString& filename) QPixmap pm; pm.loadFromData(getCachedSmiley(emoticon), "PNG"); - if(pm.size().width() > 0) + if (pm.size().width() > 0) emoticonSet.push_back(emoticon); stringElement = stringElement.nextSibling().toElement(); } - if(emoticonSet.size() > 0) + if (emoticonSet.size() > 0) emoticons.push_back(emoticonSet); } diff --git a/src/platform/timer_win.cpp b/src/platform/timer_win.cpp index e860a5ac6..31542f76f 100644 --- a/src/platform/timer_win.cpp +++ b/src/platform/timer_win.cpp @@ -24,7 +24,7 @@ uint32_t Platform::getIdleTime() { LASTINPUTINFO info = { 0 }; info.cbSize = sizeof(info); - if(GetLastInputInfo(&info)) + if (GetLastInputInfo(&info)) return GetTickCount() - info.dwTime; return 0; } diff --git a/src/platform/timer_x11.cpp b/src/platform/timer_x11.cpp index 5c9ac1b20..263a3b9b6 100644 --- a/src/platform/timer_x11.cpp +++ b/src/platform/timer_x11.cpp @@ -25,7 +25,7 @@ uint32_t Platform::getIdleTime() uint32_t idleTime = 0; Display *display = XOpenDisplay(NULL); - if(!display) + if (!display) { qDebug() << "XOpenDisplay(NULL) failed"; return 0; @@ -33,10 +33,10 @@ uint32_t Platform::getIdleTime() int32_t x11event = 0, x11error = 0; static int32_t hasExtension = XScreenSaverQueryExtension(display, &x11event, &x11error); - if(hasExtension) + if (hasExtension) { XScreenSaverInfo *info = XScreenSaverAllocInfo(); - if(info) + if (info) { XScreenSaverQueryInfo(display, DefaultRootWindow(display), info); idleTime = info->idle; diff --git a/src/video/cameraworker.cpp b/src/video/cameraworker.cpp index 8671d6568..da98e58f4 100644 --- a/src/video/cameraworker.cpp +++ b/src/video/cameraworker.cpp @@ -129,7 +129,7 @@ void CameraWorker::applyProps() if (!cam.isOpened()) return; - for(int prop : props.keys()) + for (int prop : props.keys()) cam.set(prop, props.value(prop)); } @@ -148,7 +148,7 @@ void CameraWorker::subscribe() void CameraWorker::unsubscribe() { - if(--refCount <= 0) + if (--refCount <= 0) { cam.release(); refCount = 0; diff --git a/src/widget/form/chatform.cpp b/src/widget/form/chatform.cpp index 50b455289..4f2c73998 100644 --- a/src/widget/form/chatform.cpp +++ b/src/widget/form/chatform.cpp @@ -793,7 +793,7 @@ void ChatForm::onLoadHistory() void ChatForm::startCounter() { - if(!timer) + if (!timer) { timer = new QTimer(); connect(timer, SIGNAL(timeout()), this, SLOT(updateTime())); @@ -805,7 +805,7 @@ void ChatForm::startCounter() void ChatForm::stopCounter() { - if(timer) + if (timer) { addSystemInfoMessage(tr("Call with %1 ended. %2").arg(f->getDisplayedName(), secondsToDHMS(timeElapsed.elapsed()/1000)), @@ -834,10 +834,10 @@ QString ChatForm::secondsToDHMS(quint32 duration) int hours = (int) (duration % 24); int days = (int) (duration / 24); - if(minutes == 0) + if (minutes == 0) return cD + res.sprintf("%02ds", seconds); - if(hours == 0 && days == 0) + if (hours == 0 && days == 0) return cD + res.sprintf("%02dm %02ds", minutes, seconds); if (days == 0) diff --git a/src/widget/form/settings/generalform.cpp b/src/widget/form/settings/generalform.cpp index c7f35a2ae..0f3ab819d 100644 --- a/src/widget/form/settings/generalform.cpp +++ b/src/widget/form/settings/generalform.cpp @@ -79,7 +79,7 @@ GeneralForm::GeneralForm(SettingsWidget *myParent) : bodyUI->styleBrowser->addItem(tr("None")); bodyUI->styleBrowser->addItems(QStyleFactory::keys()); - if(QStyleFactory::keys().contains(Settings::getInstance().getStyle())) + if (QStyleFactory::keys().contains(Settings::getInstance().getStyle())) bodyUI->styleBrowser->setCurrentText(Settings::getInstance().getStyle()); else bodyUI->styleBrowser->setCurrentText(tr("None")); @@ -124,7 +124,7 @@ GeneralForm::GeneralForm(SettingsWidget *myParent) : connect(bodyUI->autoAwaySpinBox, SIGNAL(editingFinished()), this, SLOT(onAutoAwayChanged())); connect(bodyUI->showInFront, &QCheckBox::stateChanged, this, &GeneralForm::onSetShowInFront); connect(bodyUI->autoacceptFiles, &QCheckBox::stateChanged, this, &GeneralForm::onAutoAcceptFileChange); - if(bodyUI->autoacceptFiles->isChecked()) + if (bodyUI->autoacceptFiles->isChecked()) connect(bodyUI->autoSaveFilesDir, SIGNAL(clicked()), this, SLOT(onAutoSaveDirChange())); //theme connect(bodyUI->useEmoticons, &QCheckBox::stateChanged, this, &GeneralForm::onUseEmoticonsChange); @@ -192,7 +192,7 @@ void GeneralForm::onSetMinimizeToTray() void GeneralForm::onStyleSelected(QString style) { - if(bodyUI->styleBrowser->currentIndex() == 0) + if (bodyUI->styleBrowser->currentIndex() == 0) Settings::getInstance().setStyle("None"); else Settings::getInstance().setStyle(style); @@ -221,7 +221,7 @@ void GeneralForm::onAutoAcceptFileChange() { Settings::getInstance().setAutoSaveEnabled(bodyUI->autoacceptFiles->isChecked()); - if(bodyUI->autoacceptFiles->isChecked() == true) + if (bodyUI->autoacceptFiles->isChecked() == true) connect(bodyUI->autoSaveFilesDir, SIGNAL(clicked()), this, SLOT(onAutoSaveDirChange())); else disconnect(bodyUI->autoSaveFilesDir, SIGNAL(clicked()),this, SLOT(onAutoSaveDirChange())); @@ -231,7 +231,7 @@ void GeneralForm::onAutoSaveDirChange() { QString previousDir = Settings::getInstance().getGlobalAutoAcceptDir(); QString directory = QFileDialog::getExistingDirectory(0, tr("Choose an auto accept directory","popup title")); - if(directory.isEmpty()) + if (directory.isEmpty()) directory = previousDir; Settings::getInstance().setGlobalAutoAcceptDir(directory); @@ -299,7 +299,7 @@ void GeneralForm::reloadSmiles() QStringList smiles; smiles << ":)" << ";)" << ":p" << ":O" << ":["; //just in case... - for(int i = 0; i < emoticons.size(); i++) + for (int i = 0; i < emoticons.size(); i++) smiles.push_front(emoticons.at(i).first()); int pixSize = 30; diff --git a/src/widget/form/tabcompleter.cpp b/src/widget/form/tabcompleter.cpp index 73d3ffeef..2fbaa48c7 100644 --- a/src/widget/form/tabcompleter.cpp +++ b/src/widget/form/tabcompleter.cpp @@ -52,7 +52,7 @@ void TabCompleter::buildCompletionList() // that section is then used as the completion regex QRegExp regex(QString("^[-_\\[\\]{}|`^.\\\\]*").append(QRegExp::escape(tabAbbrev)), Qt::CaseInsensitive); - for(auto name : group->getPeerList()) + for (auto name : group->getPeerList()) if (regex.indexIn(name) > -1) completionMap[name.toLower()] = name; diff --git a/src/widget/maskablepixmapwidget.cpp b/src/widget/maskablepixmapwidget.cpp index f0015d6d0..5f8d2ea99 100644 --- a/src/widget/maskablepixmapwidget.cpp +++ b/src/widget/maskablepixmapwidget.cpp @@ -130,6 +130,6 @@ void MaskablePixmapWidget::paintEvent(QPaintEvent *) void MaskablePixmapWidget::mousePressEvent(QMouseEvent*) { - if(clickable) + if (clickable) emit clicked(); } diff --git a/src/widget/tool/chatactions/messageaction.cpp b/src/widget/tool/chatactions/messageaction.cpp index 06f27ef1d..93b8271ee 100644 --- a/src/widget/tool/chatactions/messageaction.cpp +++ b/src/widget/tool/chatactions/messageaction.cpp @@ -29,7 +29,7 @@ MessageAction::MessageAction(const QString &author, const QString &message, cons QString MessageAction::getMessage(QString div) { QString message_; - if(Settings::getInstance().getUseEmoticons()) + if (Settings::getInstance().getUseEmoticons()) message_ = SmileyPack::getInstance().smileyfied(toHtmlChars(message)); else message_ = toHtmlChars(message); diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index dcf6c67a4..4f53a4c4e 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -54,7 +54,7 @@ void toxActivateEventHandler(const QByteArray& data) { - if(data != "$activate") + if (data != "$activate") return; Widget::getInstance()->show(); Widget::getInstance()->activateWindow(); @@ -116,7 +116,7 @@ void Widget::init() if (Settings::getInstance().getShowSystemTray()){ icon->show(); - if(Settings::getInstance().getAutostartInTray() == false) + if (Settings::getInstance().getAutostartInTray() == false) this->show(); } else @@ -149,7 +149,7 @@ void Widget::init() ui->tooliconsZone->setStyleSheet(Style::resolve("QPushButton{background-color:@themeDark;border:none;}QPushButton:hover{background-color:@themeMediumDark;border:none;}")); - if(QStyleFactory::keys().contains(Settings::getInstance().getStyle()) + if (QStyleFactory::keys().contains(Settings::getInstance().getStyle()) && Settings::getInstance().getStyle() != "None") { ui->mainHead->setStyle(QStyleFactory::create(Settings::getInstance().getStyle())); @@ -348,7 +348,7 @@ QThread* Widget::getCoreThread() void Widget::closeEvent(QCloseEvent *event) { - if(Settings::getInstance().getShowSystemTray() && Settings::getInstance().getCloseToTray() == true) + if (Settings::getInstance().getShowSystemTray() && Settings::getInstance().getCloseToTray() == true) { event->ignore(); this->hide(); @@ -365,7 +365,7 @@ void Widget::changeEvent(QEvent *event) { if (event->type() == QEvent::WindowStateChange) { - if(isMinimized() && Settings::getInstance().getMinimizeToTray()) + if (isMinimized() && Settings::getInstance().getMinimizeToTray()) { this->hide(); } @@ -415,7 +415,7 @@ QList Widget::searchProfiles() QDir dir(Settings::getSettingsDirPath()); dir.setFilter(QDir::Files | QDir::NoDotAndDotDot); dir.setNameFilters(QStringList("*.tox")); - for(QFileInfo file : dir.entryInfoList()) + for (QFileInfo file : dir.entryInfoList()) out += file.completeBaseName(); return out; } @@ -591,7 +591,7 @@ void Widget::onIconClick(QSystemTrayIcon::ActivationReason reason) { switch (reason) { case QSystemTrayIcon::Trigger: - if(this->isHidden() == true) + if (this->isHidden() == true) { this->show(); this->activateWindow(); @@ -712,7 +712,7 @@ void Widget::addFriend(int friendId, const QString &userId) void Widget::addFriendFailed(const QString&, const QString& errorInfo) { QString info = QString(tr("Couldn't request friendship")); - if(!errorInfo.isEmpty()) { + if (!errorInfo.isEmpty()) { info = info + (QString(": ") + errorInfo); } @@ -733,7 +733,7 @@ void Widget::onFriendStatusChanged(int friendId, Status status) f->getFriendWidget()->updateStatusLight(); //won't print the message if there were no messages before - if(!f->getChatForm()->isEmpty() + if (!f->getChatForm()->isEmpty() && Settings::getInstance().getStatusChangeNotificationEnabled()) { QString fStatus = ""; From 4a00125f7cac03d7a8bcc7434317a64f08e5e1ed Mon Sep 17 00:00:00 2001 From: Sean Date: Thu, 11 Dec 2014 21:29:09 -0800 Subject: [PATCH 230/253] Refactor OS X updater Went and rewrote half of it in an actually sane manner, recursive functions > nested loops. --- osx/updater.go | 77 +++++++++++++++++++++++--------------------------- 1 file changed, 36 insertions(+), 41 deletions(-) diff --git a/osx/updater.go b/osx/updater.go index 45232536f..b063a251d 100644 --- a/osx/updater.go +++ b/osx/updater.go @@ -32,6 +32,41 @@ func fs_type(path string) int { return -1 } +func install(path string, pathlen int) int { + files, _ := ioutil.ReadDir(path) + + for _, file := range files { + if fs_type(path+file.Name()) == 1 { + + addpath := "" + if len(path) != pathlen { + addpath = path[pathlen:len(path)] + } + + fmt.Print("Installing: ") + fmt.Println("/Applications/qtox.app/Contents/" + addpath + file.Name()) + if _, err := os.Stat("/Applications/qtox.app/Contents/" + file.Name()); os.IsNotExist(err) { + newfile := exec.Command("/usr/libexec/authopen", "-c", "-x", "-m", "drwxrwxr-x+", "/Applications/qtox.app/Contents/"+addpath+file.Name()) + newfile.Run() + } + + cat := exec.Command("/bin/cat", path+file.Name()) + + auth := exec.Command("/usr/libexec/authopen", "-w", "/Applications/qtox.app/Contents/"+addpath+file.Name()) + auth.Stdin, _ = cat.StdoutPipe() + auth.Stdout = os.Stdout + auth.Stderr = os.Stderr + _ = auth.Start() + _ = cat.Run() + _ = auth.Wait() + + } else { + install(path+file.Name()+"/", pathlen) + } + } + return 0 +} + func main() { usr, e := user.Current() if e != nil { @@ -45,51 +80,11 @@ func main() { } fmt.Println("qTox Updater") - files, _ := ioutil.ReadDir(update_dir) killqtox := exec.Command("/usr/bin/killall", "qtox") _ = killqtox.Run() - for _, file := range files { - if fs_type(update_dir+file.Name()) == 1 { - fmt.Print("Installing: ") - fmt.Println("/Applications/qtox.app/Contents/" + file.Name()) - if _, err := os.Stat("/Applications/qtox.app/Contents/" + file.Name()); os.IsNotExist(err) { - newfile := exec.Command("/usr/libexec/authopen", "-c", "-x", "-m", "drwxrwxr-x+", "/Applications/qtox.app/Contents/"+file.Name()) - newfile.Run() - } + install(update_dir, len(update_dir)) - cat := exec.Command("/bin/cat", update_dir+file.Name()) - auth := exec.Command("/usr/libexec/authopen", "-w", "/Applications/qtox.app/Contents/"+file.Name()) - auth.Stdin, _ = cat.StdoutPipe() - auth.Stdout = os.Stdout - auth.Stderr = os.Stderr - _ = auth.Start() - _ = cat.Run() - _ = auth.Wait() - - } else { - files, _ := ioutil.ReadDir(update_dir + file.Name()) - for _, file2 := range files { - fmt.Print("Installing: ") - fmt.Println("/Applications/qtox.app/Contents/" + file.Name() + "/" + file2.Name()) - - if _, err := os.Stat("/Applications/qtox.app/Contents/" + file.Name() + "/" + file2.Name()); os.IsNotExist(err) { - newfile := exec.Command("/usr/libexec/authopen", "-c", "-x", "-m", "drwxrwxr-x+", "/Applications/qtox.app/Contents/"+file.Name()+"/"+file2.Name()) - newfile.Run() - } - - cat := exec.Command("/bin/cat", update_dir+file.Name()+"/"+file2.Name()) - auth := exec.Command("/usr/libexec/authopen", "-w", "/Applications/qtox.app/Contents/"+file.Name()+"/"+file2.Name()) - auth.Stdin, _ = cat.StdoutPipe() - auth.Stdout = os.Stdout - auth.Stderr = os.Stderr - _ = auth.Start() - _ = cat.Run() - _ = auth.Wait() - } - } - - } os.RemoveAll(update_dir) fmt.Println("Update metadata wiped, launching qTox") launchqtox := exec.Command("/usr/bin/open", "-b", "im.tox.qtox") From e2bf37c139136b064b14c15106f48adbd72d28f5 Mon Sep 17 00:00:00 2001 From: novist Date: Thu, 11 Dec 2014 21:34:12 +0200 Subject: [PATCH 231/253] Light tray icon option --- img/taskbar/{ => dark}/taskbar_busy.png | Bin img/taskbar/{ => dark}/taskbar_busy_2x.png | Bin img/taskbar/{ => dark}/taskbar_idle.png | Bin img/taskbar/{ => dark}/taskbar_idle_2x.png | Bin img/taskbar/{ => dark}/taskbar_invisible.png | Bin img/taskbar/{ => dark}/taskbar_invisible_2x.png | Bin img/taskbar/{ => dark}/taskbar_offline.png | Bin img/taskbar/{ => dark}/taskbar_offline_2x.png | Bin img/taskbar/{ => dark}/taskbar_online.png | Bin img/taskbar/{ => dark}/taskbar_online_2x.png | Bin img/taskbar/light/taskbar_busy.png | Bin 0 -> 386 bytes img/taskbar/light/taskbar_busy_2x.png | Bin 0 -> 685 bytes img/taskbar/light/taskbar_idle.png | Bin 0 -> 363 bytes img/taskbar/light/taskbar_idle_2x.png | Bin 0 -> 635 bytes img/taskbar/light/taskbar_invisible.png | Bin 0 -> 366 bytes img/taskbar/light/taskbar_invisible_2x.png | Bin 0 -> 670 bytes img/taskbar/light/taskbar_offline.png | Bin 0 -> 378 bytes img/taskbar/light/taskbar_offline_2x.png | Bin 0 -> 677 bytes img/taskbar/light/taskbar_online.png | Bin 0 -> 320 bytes img/taskbar/light/taskbar_online_2x.png | Bin 0 -> 578 bytes res.qrc | 12 ++++++++---- src/misc/settings.cpp | 12 ++++++++++++ src/misc/settings.h | 4 ++++ src/widget/form/settings/generalform.cpp | 9 +++++++++ src/widget/form/settings/generalform.h | 1 + src/widget/form/settings/generalsettings.ui | 13 ++++++++++--- src/widget/widget.cpp | 9 +++++---- 27 files changed, 49 insertions(+), 11 deletions(-) rename img/taskbar/{ => dark}/taskbar_busy.png (100%) rename img/taskbar/{ => dark}/taskbar_busy_2x.png (100%) rename img/taskbar/{ => dark}/taskbar_idle.png (100%) rename img/taskbar/{ => dark}/taskbar_idle_2x.png (100%) rename img/taskbar/{ => dark}/taskbar_invisible.png (100%) rename img/taskbar/{ => dark}/taskbar_invisible_2x.png (100%) rename img/taskbar/{ => dark}/taskbar_offline.png (100%) rename img/taskbar/{ => dark}/taskbar_offline_2x.png (100%) rename img/taskbar/{ => dark}/taskbar_online.png (100%) rename img/taskbar/{ => dark}/taskbar_online_2x.png (100%) create mode 100644 img/taskbar/light/taskbar_busy.png create mode 100644 img/taskbar/light/taskbar_busy_2x.png create mode 100644 img/taskbar/light/taskbar_idle.png create mode 100644 img/taskbar/light/taskbar_idle_2x.png create mode 100644 img/taskbar/light/taskbar_invisible.png create mode 100644 img/taskbar/light/taskbar_invisible_2x.png create mode 100644 img/taskbar/light/taskbar_offline.png create mode 100644 img/taskbar/light/taskbar_offline_2x.png create mode 100644 img/taskbar/light/taskbar_online.png create mode 100644 img/taskbar/light/taskbar_online_2x.png diff --git a/img/taskbar/taskbar_busy.png b/img/taskbar/dark/taskbar_busy.png similarity index 100% rename from img/taskbar/taskbar_busy.png rename to img/taskbar/dark/taskbar_busy.png diff --git a/img/taskbar/taskbar_busy_2x.png b/img/taskbar/dark/taskbar_busy_2x.png similarity index 100% rename from img/taskbar/taskbar_busy_2x.png rename to img/taskbar/dark/taskbar_busy_2x.png diff --git a/img/taskbar/taskbar_idle.png b/img/taskbar/dark/taskbar_idle.png similarity index 100% rename from img/taskbar/taskbar_idle.png rename to img/taskbar/dark/taskbar_idle.png diff --git a/img/taskbar/taskbar_idle_2x.png b/img/taskbar/dark/taskbar_idle_2x.png similarity index 100% rename from img/taskbar/taskbar_idle_2x.png rename to img/taskbar/dark/taskbar_idle_2x.png diff --git a/img/taskbar/taskbar_invisible.png b/img/taskbar/dark/taskbar_invisible.png similarity index 100% rename from img/taskbar/taskbar_invisible.png rename to img/taskbar/dark/taskbar_invisible.png diff --git a/img/taskbar/taskbar_invisible_2x.png b/img/taskbar/dark/taskbar_invisible_2x.png similarity index 100% rename from img/taskbar/taskbar_invisible_2x.png rename to img/taskbar/dark/taskbar_invisible_2x.png diff --git a/img/taskbar/taskbar_offline.png b/img/taskbar/dark/taskbar_offline.png similarity index 100% rename from img/taskbar/taskbar_offline.png rename to img/taskbar/dark/taskbar_offline.png diff --git a/img/taskbar/taskbar_offline_2x.png b/img/taskbar/dark/taskbar_offline_2x.png similarity index 100% rename from img/taskbar/taskbar_offline_2x.png rename to img/taskbar/dark/taskbar_offline_2x.png diff --git a/img/taskbar/taskbar_online.png b/img/taskbar/dark/taskbar_online.png similarity index 100% rename from img/taskbar/taskbar_online.png rename to img/taskbar/dark/taskbar_online.png diff --git a/img/taskbar/taskbar_online_2x.png b/img/taskbar/dark/taskbar_online_2x.png similarity index 100% rename from img/taskbar/taskbar_online_2x.png rename to img/taskbar/dark/taskbar_online_2x.png diff --git a/img/taskbar/light/taskbar_busy.png b/img/taskbar/light/taskbar_busy.png new file mode 100644 index 0000000000000000000000000000000000000000..6408db92a198a58a73b99577b6d73c18ec1f06d4 GIT binary patch literal 386 zcmV-|0e$|7P)M#c@uwl)064bI~bZ$Nf|J-X9=y?Vv){^ThT(?HPNjDbx|3|)ym&M$Py?g&p`v?Aivv(hu528VQ5dQh{C9)UxV|Bp?ptHFY z6&Zw`9Kp8UpEQ|4F)x>a9q5J6SFSP;9gWDEL5AT17zPLc1MJ)V`zV@U0>j}aFi?4Q zv~i~c7yvn6Dk7ZW*@~55jUWs%5M&sZm|$Rp1tMA^0&4<=2QxP}1CD4s08C7d$T{%+ gf6@RrX95Hm0DgE;(OMejw*UYD07*qoM6N<$f?Q;klmGw# literal 0 HcmV?d00001 diff --git a/img/taskbar/light/taskbar_busy_2x.png b/img/taskbar/light/taskbar_busy_2x.png new file mode 100644 index 0000000000000000000000000000000000000000..248e058605deaf28a99d81319e61ada917cc94a7 GIT binary patch literal 685 zcmV;e0#f~nP) z9$KU)d-UTM^e6ZOv>+Zt5UnDp2MLG=@uL60gPv_Y)=NFK2SG`%wn_`71qF#SOE$*1 zO?SiQ!BVoB?0frWX44QN3EQ?;VN~t^z`0ct6=3l!49gx4jLAkp-nPZjRw{yHITDyK zmYiX71Xfj%=Q+97IdM8iXkLYYE8n6^LsI3%l?nkR8C!iWl!nERNb2b*iU}=1Q{CRO z0QWHOVMZs)j+?7n4@*P|04ZvL4^F>kkbARt4G!WtP%rG(dv%aA|s?2_+Zk6ur& zERzYtpx1b>Q+cF9>KmIRZJr8?5E6hSj64CPv)fyL2v9(`R;TxISZC*C?(jg}LG%te z$Aw3eNxHC~oQcExEG0OvOaR);Y-c1qaukPk;C)7k0Ja;f^dNFf59i829k&#r^W z!$7DAG3VHNok|4=6M^K+EzvVUNPw1GpoN9S=r2ewIoi&0&uT$Q3Rv;6HXF3_m!o5s zT&V*{XcX360(KmKWm;4abLTg9P5%D?ybX)+vlkSM7NZDu8v22b=s+$|ko)j9EK0u% z(AN3O3!3LP6y$fWPv^%|Gt~AeNR`SDkSLKC{RE2w)u1jw53fmdC_5Z3egqf*n{!@0 Tm?GKp00000NkvXXu0mjfbSWst literal 0 HcmV?d00001 diff --git a/img/taskbar/light/taskbar_idle.png b/img/taskbar/light/taskbar_idle.png new file mode 100644 index 0000000000000000000000000000000000000000..fae60333e2dcc011bc7c7ca85aa1a9f38466ca4b GIT binary patch literal 363 zcmV-x0hIoUP)M#c@uwl)064bI~bZ$Nf|J-XArf4R%>;muJH(?D3lje(6r0$qtciWiW< z^9PH7cp(EPk1~|MkU?Ifj6qn!9cO^;hq>$X`wRci?sxkC=J^h|TS07)*w63Jk-e}V zs|!AVxCnMGI}m>XvOxgEW@h05%Lz+JV2=f4@axA5hFcekh)TH_=|Dil8mD4j0e!*- zfC3H_VCVoOh9xGjdjS;MAlsnP2zC`V&FDGs{eRK`IA;O`7yxUiFXmkj=s*Af002ov JPDHLkV1npuj$Qx& literal 0 HcmV?d00001 diff --git a/img/taskbar/light/taskbar_idle_2x.png b/img/taskbar/light/taskbar_idle_2x.png new file mode 100644 index 0000000000000000000000000000000000000000..93b33eba46b0f4ff9123153330d72e9423efcbb1 GIT binary patch literal 635 zcmV->0)+jEP)Eq45OM>;^Q~Q+Y%R6LT&2!sSM4Ufd@A?sAv>HU+2Qruq%*uy-Pi`CA zWXU+-CZX`A9V17eVRV2b{ConD>Df)!IqJ6W4#^_uNLxf09U4ZbLFXQjSiZ35{(kFS z5gSAr0k7dO?JVaWRBgr0hDSnL3ErW$h;(KqGBS^#-NOaAC5HeS{jKeh^v5n&3C19D zQK*_Bk3g2zRp{Zsd54PtHeWiE<90p)&B(h4MtFMwM=Wdt2FG!pj-%8EiMN2lGJ%l~ z+Y|p@#U6>m>WRCSaW{N@cIf$nTXWn4GCx3)M49&!ED2a)Jx~HUCdr{refanhU;qLc V^ZDON1N{I1002ovPDHLkV1iNU68!)G literal 0 HcmV?d00001 diff --git a/img/taskbar/light/taskbar_invisible.png b/img/taskbar/light/taskbar_invisible.png new file mode 100644 index 0000000000000000000000000000000000000000..cc45ab1f4b2cb6e16a7f48fcde4123950c2d511d GIT binary patch literal 366 zcmV-!0g?WRP)M#c@uwl)064bI~bZ$Nf|J-XALK7GotXZIcu)4;{mg+W?c8eNG!vJ3Vj z8@+bz+W)G`swfyFh6eUyHQ?H{Yj6!QQ8E7~O`HVAAh9P;o?sY&5epz^YiepT1O)}b z#d7m<8Kh;T8BU%&$v|{85(_X400r3f>(?39ty>2dTex5$!_y~E8O+ViaV8p+K!o`j zgpCc2@dhFz%mB1R1a<`|Joxzd7;r@60bpWsM9zWl|C0v5ITIkj0D9bhg~~(yn*aa+ M07*qoM6N<$f>Zm8;{X5v literal 0 HcmV?d00001 diff --git a/img/taskbar/light/taskbar_invisible_2x.png b/img/taskbar/light/taskbar_invisible_2x.png new file mode 100644 index 0000000000000000000000000000000000000000..19a736b3bf86fc38ae6c3b4cd8c570603443f13c GIT binary patch literal 670 zcmV;P0%84$P)T+%0Tc4oTHK?sO1%RgrZ;h1W3u6-7QjntWmtSk>L$ssswlYJmGB zNdVCcns|QIE2)%{Y=S35+nGDjYCx_(*(Rw}g|0&%EYh6JhvJRefcg%WRd9!>W9+xz zFrt4)s(?RS#xYXzwQ%YvSffgOE!IHXsg;YdM#M_TX)(*P;KTU<&-K{-W_}ZDwHg#l zMdfx*tI!^eMsRU{Ap&zajsy4RJ(Nl%Xy3HKFbw@|h{4ZJ&O~qw#<5%56+{mA55e=i zSTnyBRJN1JglunZla2Kaa-{alHjS~YqSt>J(pdv(!;kf=BA?IeBY?r%*KHA~TCRd= zn&SP{EqL*P-4E#tY?c|@zgk|gR;f5DQ#a9IshsF?}Qoq-=i;8%bF0BRv*s~AS%qW}N^07*qoM6N<$ Ef@(H4GXMYp literal 0 HcmV?d00001 diff --git a/img/taskbar/light/taskbar_offline.png b/img/taskbar/light/taskbar_offline.png new file mode 100644 index 0000000000000000000000000000000000000000..61c18265ea4c1c95f5e357648afa6e992f0bf1ea GIT binary patch literal 378 zcmV-=0fqjFP)M#c@uwl)064bI~bZ$Nf|J-X9=y?Vv){^ThT(?HPNjDbx|3|)ym&M$Py?g&p`v?Aivv(idtxuP)0E_*6`4ZU+`?0#<1JK!A zii!-vPL6Ofsfcg}cAytNU%ARabTkqRFbog?2H3az_fh=(5*Q9Yfq}}aqm4TqzyQek zASd6SG#RWB1V9EV=H+6G2?j=3AfhEAuqIG=FmrP=;E2Wpz{KQ;oCDwgCk=peCP07z Y0Jp+K;OSB1Bme*a07*qoM6N<$g50K<4*&oF literal 0 HcmV?d00001 diff --git a/img/taskbar/light/taskbar_offline_2x.png b/img/taskbar/light/taskbar_offline_2x.png new file mode 100644 index 0000000000000000000000000000000000000000..db79070bb6913e58ebc666f0363a7a4f430ec092 GIT binary patch literal 677 zcmV;W0$TlvP)zdr6P3@lyvor7D@{aCBE;Gt1(`Z zyKwP=BP4xu&vVcFzIP2FlCUgm7DmPT3w$<9q5@2wgJD|1z?fVVRklQe7#ly7N=S`6`t5x##_D=5; zx<*p@JQ>Vpq}w?zBfD*yTcdnOg*+~=kc@F8EJ8>Ck}&cFkdCge{vl{qAIbCn0gW+_PsrHbuDXrr z19FZN_a>8cV&3X7|D)?`-ISm!$^@Xj%ytyv1r&jG;Cx4k0Ja*e^dPWKz?3Kx5cgU2 zznq3F=Z$h%*e%gLK}bLGS^d2z6r!Tv7P}5+w?vpI}j-;@1V};RT5fC7Yv*Zvh4X+}|sDIPU7e00000 LNkvXXu0mjfkIf|( literal 0 HcmV?d00001 diff --git a/img/taskbar/light/taskbar_online.png b/img/taskbar/light/taskbar_online.png new file mode 100644 index 0000000000000000000000000000000000000000..007f0d1c96d5840bd16b283e4420347711878bd4 GIT binary patch literal 320 zcmV-G0l)rk7RCwBAV88M#c@uwl)064bI~bZ$Nf|J-XBGe7MPQ=i^Nf(?Eq^ok2xF9bJh%iWiW< zy1OeF4nEn9B4#FG%@D1g#6U0*pMH6ap&0~Dy*LV%BWS?ox45Gkn;a7Zg#g0>kdT3gydT&$n4dvxY?{$?;QRli0dUR)2rvMqV*Cw6 S6KVMX0000i z=Y*RN&JxbgcYc!qFk#zvRfuIz1D{nPE(qs2AzeEhsFRB$_|T?fsFZ}HhM1I_*ev07idkFKX1=c#JIJBQWAK~`x#vw6@*HOn2^Wz?l){bF+`9S>|V({zEHq)$07*qoM6N<$f@F#T9smFU literal 0 HcmV?d00001 diff --git a/res.qrc b/res.qrc index 40b6d28f2..5db98f01e 100644 --- a/res.qrc +++ b/res.qrc @@ -37,10 +37,14 @@ img/status/dot_online.png img/status/dot_online_2x.png img/status/dot_online_notification.png - img/taskbar/taskbar_online_2x.png - img/taskbar/taskbar_idle_2x.png - img/taskbar/taskbar_busy_2x.png - img/taskbar/taskbar_offline_2x.png + img/taskbar/dark/taskbar_online_2x.png + img/taskbar/dark/taskbar_idle_2x.png + img/taskbar/dark/taskbar_busy_2x.png + img/taskbar/dark/taskbar_offline_2x.png + img/taskbar/light/taskbar_online_2x.png + img/taskbar/light/taskbar_idle_2x.png + img/taskbar/light/taskbar_busy_2x.png + img/taskbar/light/taskbar_offline_2x.png img/transfer.png smileys/cylgom/angel.png smileys/cylgom/angry.png diff --git a/src/misc/settings.cpp b/src/misc/settings.cpp index e04dc7f49..b14fe2e34 100644 --- a/src/misc/settings.cpp +++ b/src/misc/settings.cpp @@ -167,6 +167,7 @@ void Settings::load() timestampFormat = s.value("timestampFormat", "hh:mm").toString(); minimizeOnClose = s.value("minimizeOnClose", false).toBool(); minimizeToTray = s.value("minimizeToTray", false).toBool(); + lightTrayIcon = s.value("lightTrayIcon", false).toBool(); useNativeStyle = s.value("nativeStyle", false).toBool(); useEmoticons = s.value("useEmoticons", true).toBool(); statusChangeNotificationEnabled = s.value("statusChangeNotificationEnabled", false).toBool(); @@ -315,6 +316,7 @@ void Settings::save(QString path, bool writeFriends) s.setValue("timestampFormat", timestampFormat); s.setValue("minimizeOnClose", minimizeOnClose); s.setValue("minimizeToTray", minimizeToTray); + s.setValue("lightTrayIcon", lightTrayIcon); s.setValue("nativeStyle", useNativeStyle); s.setValue("useEmoticons", useEmoticons); s.setValue("themeColor", themeColor); @@ -530,6 +532,16 @@ void Settings::setMinimizeToTray(bool newValue) minimizeToTray = newValue; } +bool Settings::getLightTrayIcon() const +{ + return lightTrayIcon; +} + +void Settings::setLightTrayIcon(bool newValue) +{ + lightTrayIcon = newValue; +} + bool Settings::getStatusChangeNotificationEnabled() const { return statusChangeNotificationEnabled; diff --git a/src/misc/settings.h b/src/misc/settings.h index 7ebe39f66..848ade407 100644 --- a/src/misc/settings.h +++ b/src/misc/settings.h @@ -61,6 +61,9 @@ public: bool getMinimizeToTray() const; void setMinimizeToTray(bool newValue); + + bool getLightTrayIcon() const; + void setLightTrayIcon(bool newValue); QString getStyle() const; void setStyle(const QString& newValue); @@ -244,6 +247,7 @@ private: bool autostartInTray; bool closeToTray; bool minimizeToTray; + bool lightTrayIcon; bool useEmoticons; bool checkUpdates; bool showInFront; diff --git a/src/widget/form/settings/generalform.cpp b/src/widget/form/settings/generalform.cpp index c7f35a2ae..3f54ec188 100644 --- a/src/widget/form/settings/generalform.cpp +++ b/src/widget/form/settings/generalform.cpp @@ -62,6 +62,8 @@ GeneralForm::GeneralForm(SettingsWidget *myParent) : bodyUI->closeToTray->setEnabled(showSystemTray); bodyUI->minimizeToTray->setChecked(Settings::getInstance().getMinimizeToTray()); bodyUI->minimizeToTray->setEnabled(showSystemTray); + bodyUI->lightTrayIcon->setChecked(Settings::getInstance().getLightTrayIcon()); + bodyUI->lightTrayIcon->setEnabled(showSystemTray); bodyUI->statusChanges->setChecked(Settings::getInstance().getStatusChangeNotificationEnabled()); bodyUI->useEmoticons->setChecked(Settings::getInstance().getUseEmoticons()); bodyUI->autoacceptFiles->setChecked(Settings::getInstance().getAutoSaveEnabled()); @@ -120,6 +122,7 @@ GeneralForm::GeneralForm(SettingsWidget *myParent) : connect(bodyUI->startInTray, &QCheckBox::stateChanged, this, &GeneralForm::onSetAutostartInTray); connect(bodyUI->closeToTray, &QCheckBox::stateChanged, this, &GeneralForm::onSetCloseToTray); connect(bodyUI->minimizeToTray, &QCheckBox::stateChanged, this, &GeneralForm::onSetMinimizeToTray); + connect(bodyUI->lightTrayIcon, &QCheckBox::stateChanged, this, &GeneralForm::onSetLightTrayIcon); connect(bodyUI->statusChanges, &QCheckBox::stateChanged, this, &GeneralForm::onSetStatusChange); connect(bodyUI->autoAwaySpinBox, SIGNAL(editingFinished()), this, SLOT(onAutoAwayChanged())); connect(bodyUI->showInFront, &QCheckBox::stateChanged, this, &GeneralForm::onSetShowInFront); @@ -185,6 +188,12 @@ void GeneralForm::onSetCloseToTray() Settings::getInstance().setCloseToTray(bodyUI->closeToTray->isChecked()); } +void GeneralForm::onSetLightTrayIcon() +{ + Settings::getInstance().setLightTrayIcon(bodyUI->lightTrayIcon->isChecked()); + Widget::getInstance()->updateTrayIcon(); +} + void GeneralForm::onSetMinimizeToTray() { Settings::getInstance().setMinimizeToTray(bodyUI->minimizeToTray->isChecked()); diff --git a/src/widget/form/settings/generalform.h b/src/widget/form/settings/generalform.h index ec41fb0ad..a560cf7ae 100644 --- a/src/widget/form/settings/generalform.h +++ b/src/widget/form/settings/generalform.h @@ -37,6 +37,7 @@ private slots: void onSetShowSystemTray(); void onSetAutostartInTray(); void onSetCloseToTray(); + void onSetLightTrayIcon(); void onSmileyBrowserIndexChanged(int index); void onUDPUpdated(); void onProxyAddrEdited(); diff --git a/src/widget/form/settings/generalsettings.ui b/src/widget/form/settings/generalsettings.ui index 4df38f286..47258e049 100644 --- a/src/widget/form/settings/generalsettings.ui +++ b/src/widget/form/settings/generalsettings.ui @@ -39,8 +39,8 @@ 0 0 - 509 - 849 + 513 + 819 @@ -109,7 +109,7 @@ - + @@ -152,6 +152,13 @@ + + + + Light icon + + + diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index dcf6c67a4..2bf189e75 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -300,14 +300,15 @@ void Widget::updateTrayIcon() return; QString status = ui->statusButton->property("status").toString(); QString pic; + QString color = Settings::getInstance().getLightTrayIcon() ? "light" : "dark"; if (status == "online") - pic = ":img/taskbar/taskbar_online_2x.png"; + pic = ":img/taskbar/" + color + "/taskbar_online_2x.png"; else if (status == "away") - pic = ":img/taskbar/taskbar_idle_2x.png"; + pic = ":img/taskbar/" + color + "/taskbar_idle_2x.png"; else if (status == "busy") - pic = ":img/taskbar/taskbar_busy_2x.png"; + pic = ":img/taskbar/" + color + "/taskbar_busy_2x.png"; else - pic = ":img/taskbar/taskbar_offline_2x.png"; + pic = ":img/taskbar/" + color + "/taskbar_offline_2x.png"; icon->setIcon(QIcon(pic)); } From 22002efc093d53cf0b85e84604371538a64cb57e Mon Sep 17 00:00:00 2001 From: Sabri Date: Sat, 13 Dec 2014 09:26:37 +0100 Subject: [PATCH 232/253] Huge French translation fixes --- translations/fr.ts | 140 ++++++++++++++++++++++----------------------- 1 file changed, 70 insertions(+), 70 deletions(-) diff --git a/translations/fr.ts b/translations/fr.ts index 206cf9356..48cc108ea 100644 --- a/translations/fr.ts +++ b/translations/fr.ts @@ -76,13 +76,13 @@ Add Friends - Ajouter des amis + Ajouter des contacts Tox ID Tox ID of the person you're sending a friend request to - ID Tox + Tox ID @@ -93,25 +93,25 @@ Send friend request - Envoyer la demande d'ami + Envoyer la demande de contact Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! - Je souhaiterais vous ajouter à mes contacts + Je souhaiterais vous ajouter à mes contacts. Please fill in a valid Tox ID Tox ID of the friend you're sending a friend request to - Merci de remplir un ID Tox valide + Merci d'entrer un Tox ID valide You can't add yourself as a friend! When trying to add your own Tox ID as friend - Vous ne pouvez pas vous ajouter vous même en temps qu'ami! + Vous ne pouvez pas vous ajouter vous-même ! @@ -123,19 +123,19 @@ Ignorer le proxy et se connecter directement à Internet ? qTox needs to use the Tox DNS, but can't do it through a proxy Ignore the proxy and connect to the Internet directly ? - qTox as besoin d'utiliser le DNS Tox, mais ne peut pas le faire avec un proxy + qTox a besoin d'utiliser le DNS Tox, mais ne peut pas le faire avec un proxy Ignorer le proxy et se connecter directement à Internet ? This Tox ID does not exist DNS error - Cet ID Tox n'existe pas + Ce Tox ID n'existe pas This address does not exist The DNS gives the Tox ID associated to toxme.se addresses - Cette addresse n'existe pas + Cette adresse n'existe pas Error while looking up DNS @@ -178,22 +178,22 @@ Ignorer le proxy et se connecter directement à Internet ? Bad Idea - Mauvaise Idée + Mauvaise idée You're trying to send a special (sequential) file, that's not going to work! - Vous êtes en train d'essayer d'envoyer un fichier spécial (sequentiel), ça ne marchera pas! + Vous êtes en train d'essayer d'envoyer un fichier spécial (sequentiel), ça ne fonctionnera pas ! %1 calling - %1 appelle + %1 appel %1 stopped calling - %1a arreté l'appel + %1 a arreté l'appel @@ -229,7 +229,7 @@ Ignorer le proxy et se connecter directement à Internet ? Toxing on qTox - Toxer avec qTox + Je Tox sur qTox @@ -239,7 +239,7 @@ Ignorer le proxy et se connecter directement à Internet ? Friend is already added - Cet ami est déjà dans cos contact + Ce contact est déjà dans vos contacts @@ -249,7 +249,7 @@ Ignorer le proxy et se connecter directement à Internet ? The .tox file is encrypted, but encryption was not checked, continuing regardless. - Le fichier .tox est chiffré, mais l'encryption n'as pas été activée. Le problème sera ignoré. + Le fichier .tox est chiffré, mais le chiffrement n'a pas été activé. Le problème sera ignoré. @@ -261,14 +261,14 @@ Ignorer le proxy et se connecter directement à Internet ? Password error - Mod de passe invalide + Mot de passe invalide Failed to setup password. Empty password. - Impossible de mettre ne place le mot de passe. + Impossible de mettre en place le mot de passe. Le mot de passe est vide. @@ -289,7 +289,7 @@ Le mot de passe est vide. Wrong password has been entered - Un mauvais mot de passe à été entré + Un mauvais mot de passe a été entré @@ -316,7 +316,7 @@ Voulez-vous essayer un mot de passe différent? Due to incorret password logging will be disabled - À cause d'un mauvais mot de passe, l'historique sera désactivé + Due à l'utilisation d'un mauvais mot de passe, l'historique sera désactivé @@ -326,7 +326,7 @@ Voulez-vous essayer un mot de passe différent? Will be saved without encryption! - L'historique sera sauvegardé sans être chiffré! + L'historique sera sauvegardé sans être chiffré ! @@ -380,12 +380,12 @@ Voulez-vous essayer un mot de passe différent? Friend request Title of the window to aceept/deny a friend request - Demande d'ami + Demande de contact Someone wants to make friends with you - Quelqu'un veut devenir votre ami + Quelqu'un vient de vous ajouter dans sa liste de contacts @@ -395,7 +395,7 @@ Voulez-vous essayer un mot de passe différent? Friend request message: - Message de demande d'ami: + Message au sujet de la demande: @@ -422,7 +422,7 @@ Voulez-vous essayer un mot de passe différent? Copy friend ID Menu to copy the Tox ID of that friend - Copier l'ID ami + Copier l'ID du contact @@ -458,7 +458,7 @@ Voulez-vous essayer un mot de passe différent? Disable global auto accept context menu entry - Désactiver l'acceptation automatique de fichier + Désactiver le téléchargement automatique de fichiers @@ -470,7 +470,7 @@ Voulez-vous essayer un mot de passe différent? Remove friend Menu to remove the friend from our friendlist - Supprimer ami + Supprimer ce contact @@ -502,7 +502,7 @@ Voulez-vous essayer un mot de passe différent? You can't disconnect while a call is active! popup text - Vous ne pouvez pas vous déconnecter avec un appel en cours! + Vous ne pouvez pas vous déconnecter avec un appel en cours ! @@ -520,7 +520,7 @@ Voulez-vous essayer un mot de passe différent? The translation may not load until qTox restarts. - La translation peut ne pas se charger jusqu'à ce que qTox redémarre. + La traduction peut ne pas prendre effet immédiatement. Redémarrez qTox si ce n'est pas le cas. Translation: @@ -550,7 +550,7 @@ Voulez-vous essayer un mot de passe différent? Minimize to tray - Minimizer dans la barre d'état + Minimiser dans la barre d'état @@ -564,7 +564,7 @@ Voulez-vous essayer un mot de passe différent? Auto away after (0 to disable): - Auto-absent après (0 pour désactiver): + Se rendre absent après (0 pour désactiver): @@ -620,7 +620,7 @@ Voulez-vous essayer un mot de passe différent? Reconnect reconnect button - Reconnection + Se reconnecter @@ -640,7 +640,7 @@ Voulez-vous essayer un mot de passe différent? Focus qTox when a message is received - Montrer qTox quand un message est reçu + Montrer la fênetre qTox quand un message est reçu @@ -713,7 +713,7 @@ Voulez-vous essayer un mot de passe différent? This allows, e.g., toxing over Tor. It adds load to the Tox network however, so use only when necessary. force tcp checkbox tooltip - Permet par exemple d'utiliser Tox à travers Tor, mais ce n'est à utiliser que si nécessaire, car cela ralenti le réseau Tox. + Permet par exemple d'utiliser Tox à travers Tor, mais ce n'est à utiliser que si nécessaire car cela ralenti le réseau Tox. Disable UDP (not recommended) @@ -748,7 +748,7 @@ Voulez-vous essayer un mot de passe différent? Smileys - Emoticones + Émoticônes @@ -768,7 +768,7 @@ Voulez-vous essayer un mot de passe différent? Toggle speakers volume - Couper les haut parleurs + Couper les haut-parleurs @@ -818,7 +818,7 @@ Voulez-vous essayer un mot de passe différent? 0 users in chat - 0 personnes + Le groupe est vide @@ -844,7 +844,7 @@ Voulez-vous essayer un mot de passe différent? You can't switch profiles while a call is active! popup text - Vous ne pouvez pas changer de profil quand un appel est en cours! + Vous ne pouvez pas changer de profil quand un appel est en cours ! @@ -894,7 +894,7 @@ Voulez-vous essayer un mot de passe différent? The file you chose could not be written to. - Le fichier que vous avez choisi n'as pas pu être écrit. + Le fichier que vous avez choisi n'es pas disponible en écriture. @@ -912,7 +912,7 @@ Voulez-vous essayer un mot de passe différent? Deletion imminent! deletion confirmation title - Suppression imminente! + Suppression imminente ! @@ -936,13 +936,13 @@ Voulez-vous essayer un mot de passe différent? Ignoring non-Tox file popup title - Fichier non-Tox ignoré + Fichier incompatible avec Tox ignoré Warning: you've chosen a file that is not a Tox save file; ignoring. popup text - Attention: Vous avez sélectionné un fichier qui n'est pas une sauvegarde Tox: il sera ignoré. + Attention: Vous avez sélectionné un fichier qui n'est pas une sauvegarde Tox, il sera ignoré. @@ -986,7 +986,7 @@ Voulez-vous essayer un mot de passe différent? Your Tox ID (click to copy) - Votre ID Tox (cliquez pour copier) + Votre Tox ID (cliquez pour copier) @@ -1033,7 +1033,7 @@ Voulez-vous essayer un mot de passe différent? This is useful to remain safe on public computers delete profile button tooltip - Util pour sécuriser sur un ordinateur public + Utile pour rester en sécurité sur un ordinateur public @@ -1045,7 +1045,7 @@ Voulez-vous essayer un mot de passe différent? New Tox ID new profile button - Nouvel ID Tox + Nouvel Tox ID @@ -1093,12 +1093,12 @@ Voulez-vous essayer un mot de passe différent? Add friends - Ajouter des amis + Ajouter des contacts Create a group chat - Creer un groupe + Créer un groupe @@ -1125,7 +1125,7 @@ Voulez-vous essayer un mot de passe différent? Tox video - Vidéo tox + Vidéo Tox @@ -1145,7 +1145,7 @@ Voulez-vous essayer un mot de passe différent? You already have history log file encrypted with different password Do you want to delete old history file? Vous avez déjà un historique chiffré avec un autre mot de passe -Voulez vous supprimer l'ancien historique? +Voulez vous supprimer l'ancien historique ? @@ -1192,7 +1192,7 @@ Voulez vous supprimer l'ancien historique? Warning: you've chosen a file that is not a Tox save file; ignoring. popup text - Attention: Vous avez sélectionné un fichier qui n'est pas une sauvegarde Tox: il sera ignoré. + Attention: Vous avez sélectionné un fichier qui n'est pas une sauvegarde Tox, il sera ignoré. @@ -1233,12 +1233,12 @@ Voulez vous supprimer l'ancien historique? An update is available, do you want to download it now ? It will be installed when qTox restarts. Une mise à jour est disponible, voulez vous la télécharger maintenant ? -Elle sera installée au prochain démarrage de qTox +Elle sera installée au prochain démarrage de qTox. Tox URI to parse - URI Tox à utiliser + URL Tox à utiliser @@ -1255,7 +1255,7 @@ Elle sera installée au prochain démarrage de qTox Repeat Password - Répetez le mot de passe + Retapez le mot de passe @@ -1270,13 +1270,13 @@ Elle sera installée au prochain démarrage de qTox This address does not exist The DNS gives the Tox ID associated to toxme.se addresses - Cette addresse n'existe pas + Cette adresse n'existe pas Error while looking up DNS The DNS gives the Tox ID associated to toxme.se addresses - Erreur en consultant le serveur DNS + Une erreur s'est produite en consultant le serveur DNS @@ -1316,22 +1316,22 @@ Elle sera installée au prochain démarrage de qTox Add a friend Title of the window to add a friend through Tox URI - Ajouter un ami + Ajouter un contact Do you want to add %1 as a friend ? - Voulez-vous ajouter %1 à vos amis ? + Voulez-vous ajouter %1 à vos contacts ? User ID: - ID utilisateur: + ID d'utilisateur: Friend request message: - Message de demande d'ami: + Associer un message à cette demande: @@ -1354,7 +1354,7 @@ Elle sera installée au prochain démarrage de qTox away - indisponnible + absent busy @@ -1368,7 +1368,7 @@ Elle sera installée au prochain démarrage de qTox Away - Indisponible + Absent @@ -1383,7 +1383,7 @@ Elle sera installée au prochain démarrage de qTox Change status to: - Changer le status en: + Changer le status par: @@ -1395,7 +1395,7 @@ Elle sera installée au prochain démarrage de qTox Away Button to set your status to 'Away' - Indisponible + Absent @@ -1443,18 +1443,18 @@ Elle sera installée au prochain démarrage de qTox Toxcore failed to start, the application will terminate after you close this message. - Toxcore n'as pas pu démarrer correctement, l'application va quitter quand vous fermerez ce message. + ToxCore n'as pas pu démarrer correctement, l'application va s'arrêter quand vous fermerez cette alerte. toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text - Toxcore n'as pas pu démarrer avec ces paramètres de proxy, qTox ne peut pas continuer; merci de modifier vos paramètres et redémarrer. + ToxCore n'as pas pu démarrer avec ces paramètres proxy. Merci de modifier ou désactiver vos paramètres et redémarrer l'application. Add friend - Ajouter un ami + Ajouter un contact @@ -1469,13 +1469,13 @@ Elle sera installée au prochain démarrage de qTox Couldn't request friendship - Impossible de demander en ami + Impossible d'envoyer la demande de contact away contact status - indisponnible + absent @@ -1487,7 +1487,7 @@ Elle sera installée au prochain démarrage de qTox offline contact status - déconnecté + hors ligne @@ -1510,7 +1510,7 @@ Elle sera installée au prochain démarrage de qTox Message failed to send - Le message n'as pas pu être envoyé + Le message n'as pu être envoyé From 84dea5e12cda03335b868f4930dc9d33e83a60d8 Mon Sep 17 00:00:00 2001 From: Dubslow Date: Sat, 13 Dec 2014 12:18:11 -0600 Subject: [PATCH 233/253] Revert "fix #902" @apprb #932 This reverts commit 13306031e238258a3faf2909a01987cf093765dc. --- src/widget/tool/chatactions/chataction.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widget/tool/chatactions/chataction.cpp b/src/widget/tool/chatactions/chataction.cpp index 00f872106..a2aa3e140 100644 --- a/src/widget/tool/chatactions/chataction.cpp +++ b/src/widget/tool/chatactions/chataction.cpp @@ -25,7 +25,7 @@ QTextBlockFormat ChatAction::nameFormat, ChatAction::dateFormat; QString ChatAction::toHtmlChars(const QString &str) { - static QList> replaceList = {{"&","&"}, {">",">"}, {"<","<"}, {" ", " "}}; // {"&","&"} should be always first + static QList> replaceList = {{"&","&"}, {">",">"}, {"<","<"}}; QString res = str; for (auto &it : replaceList) From e0cb122abe1ed6f9ad868ffe0eda5feb2d64ab0e Mon Sep 17 00:00:00 2001 From: Maximilian Wuttke Date: Sun, 14 Dec 2014 09:50:18 +0100 Subject: [PATCH 234/253] Merge branch 'filter_audio' of https://github.com/mwuttke97/qTox into filter_audio Added some `#ifdef QTOX_FILTER_AUDIO`s. Conflicts: qtox.pro Conflicts: src/coreav.cpp --- install_libfilteraudio.sh | 31 +++++++++++++++ qtox.pro | 6 +++ src/audiofilterer.cpp | 52 ++++++++++++++++++++++++++ src/audiofilterer.h | 41 ++++++++++++++++++++ src/core.h | 6 +++ src/coreav.cpp | 44 +++++++++++++++++++++- src/misc/settings.cpp | 10 +++++ src/misc/settings.h | 4 ++ src/widget/form/settings/avform.cpp | 11 ++++++ src/widget/form/settings/avform.h | 1 + src/widget/form/settings/avsettings.ui | 11 +++++- 11 files changed, 213 insertions(+), 4 deletions(-) create mode 100644 install_libfilteraudio.sh create mode 100644 src/audiofilterer.cpp create mode 100644 src/audiofilterer.h diff --git a/install_libfilteraudio.sh b/install_libfilteraudio.sh new file mode 100644 index 000000000..d0f5bdd38 --- /dev/null +++ b/install_libfilteraudio.sh @@ -0,0 +1,31 @@ +#!/bin/sh +CURRENT_DIR=$DIRSTACK +SOURCE_DIR="filter_audio/" +SOURCE_PATH="./../" +LIB_DIR="/usr/local/lib/" +INCLUDE_DIR="/usr/local/include/" + +echo "Clone filter_audio from GitHub.com" +cd $SOURCE_PATH +git clone --quiet https://github.com/irungentoo/filter_audio.git $SOURCE_DIR + +echo "Compile filter_audio" +cd $SOURCE_DIR +gcc -c -fPIC filter_audio.c aec/*.c agc/*.c ns/*.c other/*.c -lm -lpthread + +echo "Create shared object file" +gcc *.o -shared -o libfilteraudio.so + +echo "Clean up" +rm *.o + +echo "Install libfilteraudio.so" +sudo cp libfilteraudio.so $LIB_DIR + +echo "Install include files" +sudo cp *.h $INCLUDE_DIR + +echo "Finished." +cd $CURRENT_DIR +exit 1 + diff --git a/qtox.pro b/qtox.pro index cbd4f3dc1..9dc7722c0 100644 --- a/qtox.pro +++ b/qtox.pro @@ -234,6 +234,12 @@ SOURCES += \ src/widget/form/settings/advancedform.cpp \ src/audio.cpp +contains(DEFINES, QTOX_FILTER_AUDIO) { + HEADERS += src/audiofilterer.h + SOURCES += src/audiofilterer.cpp + unix|win32: LIBS += -lfilteraudio +} + contains(DEFINES, QTOX_PLATFORM_EXT) { HEADERS += src/platform/timer.h SOURCES += src/platform/timer_osx.cpp \ diff --git a/src/audiofilterer.cpp b/src/audiofilterer.cpp new file mode 100644 index 000000000..f88ae875b --- /dev/null +++ b/src/audiofilterer.cpp @@ -0,0 +1,52 @@ +/* + Copyright (C) 2014 by Project Tox + + This file is part of qTox, a Qt-based graphical interface for Tox. + + This program is libre software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the COPYING file for more details. +*/ + + +#ifdef QTOX_FILTER_AUDIO + +#include "audiofilterer.h" +extern "C"{ +#include +} + +void AudioFilterer::startFilter(unsigned int fs) +{ + closeFilter(); + filter = new_filter_audio(fs); +} + +void AudioFilterer::closeFilter() +{ + if (filter) + kill_filter_audio(filter); +} + + +void AudioFilterer::filterAudio(int16_t* data, int framesize) +{ + if (!filter) + return; + + filter_audio(filter, (int16_t*) data, framesize); +} + + +AudioFilterer::~AudioFilterer() +{ + closeFilter(); +} + +#endif // QTOX_FILTER_AUDIO diff --git a/src/audiofilterer.h b/src/audiofilterer.h new file mode 100644 index 000000000..3a61d4e9f --- /dev/null +++ b/src/audiofilterer.h @@ -0,0 +1,41 @@ +/* + Copyright (C) 2014 by Project Tox + + This file is part of qTox, a Qt-based graphical interface for Tox. + + This program is libre software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the COPYING file for more details. +*/ + +#ifdef QTOX_FILTER_AUDIO +#ifndef AUDIOFILTERER_H +#define AUDIOFILTERER_H +#include + +#ifndef _FILTER_AUDIO +typedef struct Filter_Audio Filter_Audio; +#endif + +class AudioFilterer +{ +public: + explicit AudioFilterer() = default; + ~AudioFilterer(); + + void startFilter(unsigned int fs); + void filterAudio(int16_t* data, int framesize); + void closeFilter(); + +private: + struct Filter_Audio* filter{nullptr}; +}; + +#endif // AUDIOFILTERER_H +#endif // QTOX_FILTER_AUDIO diff --git a/src/core.h b/src/core.h index 2f323bca2..ffa4ba0c9 100644 --- a/src/core.h +++ b/src/core.h @@ -33,6 +33,9 @@ class QTimer; class QString; class CString; class VideoSource; +#ifdef QTOX_FILTER_AUDIO +class AudioFilterer; +#endif class Core : public QObject { @@ -283,6 +286,9 @@ private: int dhtServerId; static QList fileSendQueue, fileRecvQueue; static ToxCall calls[TOXAV_MAX_CALLS]; +#ifdef QTOX_FILTER_AUDIO + static AudioFilterer * filterer[TOXAV_MAX_CALLS]; +#endif static QHash groupCalls; // Maps group IDs to ToxGroupCalls QMutex fileSendMutex, messageSendMutex; bool ready; diff --git a/src/coreav.cpp b/src/coreav.cpp index 13f6dd8c0..41d0fe490 100644 --- a/src/coreav.cpp +++ b/src/coreav.cpp @@ -17,10 +17,17 @@ #include "core.h" #include "video/camera.h" #include "audio.h" +#ifdef QTOX_FILTER_AUDIO +#include "audiofilterer.h" +#endif +#include "misc/settings.h" #include #include ToxCall Core::calls[TOXAV_MAX_CALLS]; +#ifdef QTOX_FILTER_AUDIO +AudioFilterer * Core::filterer[TOXAV_MAX_CALLS] { nullptr}; +#endif const int Core::videobufsize{TOXAV_MAX_VIDEO_WIDTH * TOXAV_MAX_VIDEO_HEIGHT * 4}; uint8_t* Core::videobuf; @@ -65,6 +72,20 @@ void Core::prepareCall(int friendId, int callId, ToxAv* toxav, bool videoEnabled calls[callId].sendVideoTimer->start(); Camera::getInstance()->subscribe(); } + +#ifdef QTOX_FILTER_AUDIO + if (Settings::getInstance().getFilterAudio()) + { + Core::filterer[callId] = new AudioFilterer(); + filterer[callId]->startFilter(48000); + } + else + { + if (filterer[callId]) + delete filterer[callId]; + filterer[callId] = nullptr; + } +#endif } void Core::onAvMediaChange(void* toxav, int32_t callId, void* core) @@ -246,8 +267,16 @@ void Core::sendCallAudio(int callId, ToxAv* toxav) return; } +#ifdef QTOX_FILTER_AUDIO + if (filterer[callId]) + { + filterer[callId]->filterAudio((int16_t*) buf, framesize); + } +#endif if ((r = toxav_send_audio(toxav, callId, dest, r)) < 0) + { qDebug() << "Core: toxav_send_audio error"; + } } calls[callId].sendAudioTimer->start(); } @@ -294,14 +323,16 @@ void Core::sendCallVideo(int callId) void Core::micMuteToggle(int callId) { - if (calls[callId].active) { + if (calls[callId].active) + { calls[callId].muteMic = !calls[callId].muteMic; } } void Core::volMuteToggle(int callId) { - if (calls[callId].active) { + if (calls[callId].active) + { calls[callId].muteVol = !calls[callId].muteVol; alSourcef(calls[callId].alSource, AL_GAIN, calls[callId].muteVol ? 0.f : 1.f); } @@ -321,6 +352,15 @@ void Core::onAvCancel(void* _toxav, int32_t callId, void* core) calls[callId].active = false; +#ifdef QTOX_FILTER_AUDIO + if (filterer[callId]) + { + filterer[callId]->closeFilter(); + delete filterer[callId]; + filterer[callId] = nullptr; + } +#endif + emit static_cast(core)->avCancel(friendId, callId); } diff --git a/src/misc/settings.cpp b/src/misc/settings.cpp index b14fe2e34..5ec16448a 100644 --- a/src/misc/settings.cpp +++ b/src/misc/settings.cpp @@ -198,6 +198,7 @@ void Settings::load() s.beginGroup("Audio"); inDev = s.value("inDev", "").toString(); outDev = s.value("outDev", "").toString(); + filterAudio = s.value("filterAudio", false).toBool(); s.endGroup(); // Read the embedded DHT bootsrap nodes list if needed @@ -340,6 +341,7 @@ void Settings::save(QString path, bool writeFriends) s.beginGroup("Audio"); s.setValue("inDev", inDev); s.setValue("outDev", outDev); + s.setValue("filterAudio", filterAudio); s.endGroup(); if (!writeFriends || currentProfile.isEmpty()) // Core::switchConfiguration @@ -898,6 +900,14 @@ void Settings::setOutDev(const QString& deviceSpecifier) outDev = deviceSpecifier; } +bool Settings::getFilterAudio() const{ + return filterAudio; +} + +void Settings::setFilterAudio(bool newValue){ + filterAudio = newValue; +} + QString Settings::getFriendAdress(const QString &publicKey) const { QString key = ToxID::fromString(publicKey).publicKey; diff --git a/src/misc/settings.h b/src/misc/settings.h index 848ade407..64ff6009e 100644 --- a/src/misc/settings.h +++ b/src/misc/settings.h @@ -128,6 +128,9 @@ public: QString getOutDev() const; void setOutDev(const QString& deviceSpecifier); + bool getFilterAudio() const; + void setFilterAudio(bool newValue); + // Assume all widgets have unique names // Don't use it to save every single thing you want to save, use it // for some general purpose widgets, such as MainWindows or Splitters, @@ -298,6 +301,7 @@ private: // Audio QString inDev; QString outDev; + bool filterAudio; struct friendProp { diff --git a/src/widget/form/settings/avform.cpp b/src/widget/form/settings/avform.cpp index a506b287a..903fc7e6a 100644 --- a/src/widget/form/settings/avform.cpp +++ b/src/widget/form/settings/avform.cpp @@ -33,12 +33,19 @@ AVForm::AVForm() : bodyUI = new Ui::AVSettings; bodyUI->setupUi(this); +#ifdef QTOX_FILTER_AUDIO + bodyUI->filterAudio->setChecked(Settings::getInstance().getFilterAudio()); +#else + bodyUI->filterAudio->setDisabled(true); +#endif + connect(Camera::getInstance(), &Camera::propProbingFinished, this, &AVForm::onPropProbingFinished); connect(Camera::getInstance(), &Camera::resolutionProbingFinished, this, &AVForm::onResProbingFinished); auto qcomboboxIndexChanged = (void(QComboBox::*)(const QString&)) &QComboBox::currentIndexChanged; connect(bodyUI->inDevCombobox, qcomboboxIndexChanged, this, &AVForm::onInDevChanged); connect(bodyUI->outDevCombobox, qcomboboxIndexChanged, this, &AVForm::onOutDevChanged); + connect(bodyUI->filterAudio, SIGNAL(toggled(bool)), this, SLOT(onFilterAudioToggled(bool))); connect(bodyUI->rescanButton, &QPushButton::clicked, this, [=](){getAudioInDevices(); getAudioOutDevices();}); } @@ -189,3 +196,7 @@ void AVForm::onOutDevChanged(const QString& deviceDescriptor) Settings::getInstance().setOutDev(deviceDescriptor); Audio::openOutput(deviceDescriptor); } + +void AVForm::onFilterAudioToggled(bool filterAudio){ + Settings::getInstance().setFilterAudio(filterAudio); +} diff --git a/src/widget/form/settings/avform.h b/src/widget/form/settings/avform.h index 2d1e873ad..64b34d410 100644 --- a/src/widget/form/settings/avform.h +++ b/src/widget/form/settings/avform.h @@ -52,6 +52,7 @@ private slots: // audio void onInDevChanged(const QString& deviceDescriptor); void onOutDevChanged(const QString& deviceDescriptor); + void onFilterAudioToggled(bool filterAudio); // camera void onPropProbingFinished(Camera::Prop prop, double val); diff --git a/src/widget/form/settings/avsettings.ui b/src/widget/form/settings/avsettings.ui index f7e1afc8d..e46c7d43f 100644 --- a/src/widget/form/settings/avsettings.ui +++ b/src/widget/form/settings/avsettings.ui @@ -30,8 +30,8 @@ 0 0 - 810 - 496 + 808 + 618 @@ -96,6 +96,13 @@ + + + + Filter audio + + + From 8f173af3d7b005c308a4df7da8832c08148d6790 Mon Sep 17 00:00:00 2001 From: agilob Date: Mon, 15 Dec 2014 17:14:17 +0100 Subject: [PATCH 235/253] removing border from contact name linedit --- ui/chatArea/chatHead.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ui/chatArea/chatHead.css b/ui/chatArea/chatHead.css index b953e86e5..206e09883 100644 --- a/ui/chatArea/chatHead.css +++ b/ui/chatArea/chatHead.css @@ -1,3 +1,9 @@ +QLineEdit { + color: @black; + background: white; + border: 0px; +} + #nameLabel { color: @black; font: @mediumBold; From 8934a11dbffce6b0611d97d5748668a5cdb8ba8a Mon Sep 17 00:00:00 2001 From: Sean Qureshi Date: Tue, 16 Dec 2014 02:51:13 -0800 Subject: [PATCH 236/253] Squashed 'osx/updater/' content from commit a558402 git-subtree-dir: osx/updater git-subtree-split: a5584021435fc74a1e5da447524b37dcf7311bbc --- .gitignore | 23 +++++ README.md | 13 +++ qtox_sudo.m | 273 ++++++++++++++++++++++++++++++++++++++++++++++++++++ updater.go | 133 +++++++++++++++++++++++++ 4 files changed, 442 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 qtox_sudo.m create mode 100644 updater.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..836562412 --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test diff --git a/README.md b/README.md new file mode 100644 index 000000000..66d65555f --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +The qTox OS X updater is a mix of objective C and Go compiled as static binaries used do effortless updates in the background. + +It uses Objective C to access Apples own security framework and call some long dead APIs in order to give the statically linked go updater permissions to install the latest build without prompting the user for every file. + +* Release commits: ``https://github.com/Tox/qTox_updater`` +* Development commits: ``https://github.mit.edu/sean-2/updater`` + +Compiling: + +* ```clang qtox_sudo.m -framework corefoundation -framework security -framework cocoa -Os -o qtox_sudo``` +* ```go build updater.go``` + +(Starting with this commit all commits will be signed with [this key](http://pgp.mit.edu/pks/lookup?op=get&search=0x13D2043169D25DF4).) diff --git a/qtox_sudo.m b/qtox_sudo.m new file mode 100644 index 000000000..f9de6bcc0 --- /dev/null +++ b/qtox_sudo.m @@ -0,0 +1,273 @@ +// Modifications listed here GPLv3: https://gist.githubusercontent.com/stqism/2e82352026915f8f6ab3/raw/fca6f6f16fb8d61a64b6053dacf50c3433c2e0af/gistfile1.txt +// +// Copyright 2009 Performant Design, LLC. All rights reserved. +// Copyright (C) 2014 Tox Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// + +#import + +#include +#include +#include +#include + +char *addFileToPath(const char *path, const char *filename) { + char *outbuf; + char *lc; + + lc = (char *)path + strlen(path) - 1; + + if (lc < path || *lc != '/') { + lc = NULL; + } + + while (*filename == '/') { + filename++; + } + + outbuf = malloc(strlen(path) + strlen(filename) + 1 + (lc == NULL ? 1 : 0)); + + sprintf(outbuf, "%s%s%s", path, (lc == NULL) ? "/" : "", filename); + + return outbuf; +} + +int isExecFile(const char *name) { + struct stat s; + + return (!access(name, X_OK) && !stat(name, &s) && S_ISREG(s.st_mode)); +} + +char *which(const char *filename) +{ + char *path, *p, *n; + + path = getenv("PATH"); + + if (!path) { + return NULL; + } + + p = path = strdup(path); + + while (p) { + n = strchr(p, ':'); + + if (n) { + *n++ = '\0'; + } + + if (*p != '\0') { + p = addFileToPath(p, filename); + + if (isExecFile(p)) { + free(path); + + return p; + } + + free(p); + } + + p = n; + } + + free(path); + + return NULL; +} + +int cocoaSudo(char *executable, char *commandArgs[], char *icon, char *prompt) { + int retVal = 1; + OSStatus status; + AuthorizationRef authRef; + + AuthorizationItem right = {kAuthorizationRightExecute, 0, NULL, 0}; + AuthorizationRights rightSet = {1, &right}; + + AuthorizationEnvironment myAuthorizationEnvironment; + AuthorizationItem kAuthEnv[2]; + myAuthorizationEnvironment.items = kAuthEnv; + + AuthorizationFlags flags = kAuthorizationFlagDefaults; + + + if (prompt && icon) { + kAuthEnv[0].name = kAuthorizationEnvironmentPrompt; + kAuthEnv[0].valueLength = strlen(prompt); + kAuthEnv[0].value = prompt; + kAuthEnv[0].flags = 0; + + kAuthEnv[1].name = kAuthorizationEnvironmentIcon; + kAuthEnv[1].valueLength = strlen(icon); + kAuthEnv[1].value = icon; + kAuthEnv[1].flags = 0; + + myAuthorizationEnvironment.count = 2; + } + else if (prompt) { + kAuthEnv[0].name = kAuthorizationEnvironmentPrompt; + kAuthEnv[0].valueLength = strlen(prompt); + kAuthEnv[0].value = prompt; + kAuthEnv[0].flags = 0; + + myAuthorizationEnvironment.count = 1; + } + else if (icon) { + kAuthEnv[0].name = kAuthorizationEnvironmentIcon; + kAuthEnv[0].valueLength = strlen(icon); + kAuthEnv[0].value = icon; + kAuthEnv[0].flags = 0; + + myAuthorizationEnvironment.count = 1; + } + else { + myAuthorizationEnvironment.count = 0; + } + + status = AuthorizationCreate(NULL, &myAuthorizationEnvironment, flags, &authRef); + + if (status != errAuthorizationSuccess) { + NSLog(@"Could not create authorization reference object."); + status = errAuthorizationBadAddress; + } + else { + flags = kAuthorizationFlagDefaults | + kAuthorizationFlagInteractionAllowed | + kAuthorizationFlagPreAuthorize | + kAuthorizationFlagExtendRights; + + status = AuthorizationCopyRights(authRef, &rightSet, &myAuthorizationEnvironment, flags, NULL); + } + + if (status == errAuthorizationSuccess) { + FILE *ioPipe; + char buffer[1024]; + int bytesRead; + + flags = kAuthorizationFlagDefaults; + status = AuthorizationExecuteWithPrivileges(authRef, executable, flags, commandArgs, &ioPipe); + + /* Just pipe processes' stdout to our stdout for now; hopefully can add stdin pipe later as well */ + + for (;;) { + bytesRead = fread(buffer, sizeof(char), 1024, ioPipe); + + if (bytesRead < 1) { + break; + } + + write(STDOUT_FILENO, buffer, bytesRead * sizeof(char)); + } + + pid_t pid; + int pidStatus; + + do { + pid = wait(&pidStatus); + } + while (pid != -1); + + if (status == errAuthorizationSuccess) { + retVal = 0; + } + } + else { + AuthorizationFree(authRef, kAuthorizationFlagDestroyRights); + authRef = NULL; + + if (status != errAuthorizationCanceled) { + // pre-auth failed + NSLog(@"Pre-auth failed"); + } + } + + return retVal; +} + +void usage(char *appNameFull) { + char *appName = strrchr(appNameFull, '/'); + + if (appName == NULL) { + appName = appNameFull; + } + else { + appName++; + } + + fprintf(stderr, "usage: %s command\n", appName); +} + +int main(int argc, char *argv[]) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + int retVal = 1; + int programArgsStartAt = 1; + char *icon = NULL; + char *prompt = NULL; + + if (programArgsStartAt >= argc) { + usage(argv[0]); + } + else { + char *executable; + + if (strchr(argv[programArgsStartAt], '/')) { + executable = isExecFile(argv[programArgsStartAt]) ? strdup(argv[programArgsStartAt]) : NULL; + } + else { + executable = which(argv[programArgsStartAt]); + } + + if (executable) { + char **commandArgs = malloc((argc - programArgsStartAt) * sizeof(char**)); + + memcpy(commandArgs, argv + programArgsStartAt + 1, (argc - programArgsStartAt - 1) * sizeof(char**)); + + commandArgs[argc - programArgsStartAt - 1] = NULL; + + retVal = cocoaSudo(executable, commandArgs, icon, prompt); + + free(commandArgs); + free(executable); + } + else { + fprintf(stderr, "Unable to find %s\n", argv[programArgsStartAt]); + + usage(argv[0]); + } + } + + if (prompt) { + free(prompt); + } + + [pool release]; + + return retVal; +} diff --git a/updater.go b/updater.go new file mode 100644 index 000000000..ae7b898d6 --- /dev/null +++ b/updater.go @@ -0,0 +1,133 @@ +package main + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "os/exec" + "os/user" + "syscall" + + "bitbucket.org/kardianos/osext" +) + +var custom_user string + +func fs_type(path string) int { + //name := "FileOrDir" + f, err := os.Open(path) + if err != nil { + fmt.Println(err) + return -1 + } + defer f.Close() + fi, err := f.Stat() + if err != nil { + fmt.Println(err) + return -1 + } + switch mode := fi.Mode(); { + case mode.IsDir(): + return 0 + case mode.IsRegular(): + return 1 + } + + return -1 +} + +func install(path string, pathlen int) int { + files, _ := ioutil.ReadDir(path) + + for _, file := range files { + if fs_type(path+file.Name()) == 1 { + + addpath := "" + if len(path) != pathlen { + addpath = path[pathlen:len(path)] + } + + fmt.Print("Installing: ") + fmt.Println("/Applications/qtox.app/Contents/" + addpath + file.Name()) + if _, err := os.Stat("/Applications/qtox.app/Contents/" + file.Name()); os.IsNotExist(err) { + newfile := exec.Command("/usr/libexec/authopen", "-c", "-x", "-m", "drwxrwxr-x+", "/Applications/qtox.app/Contents/"+addpath+file.Name()) + newfile.Run() + } + + cat := exec.Command("/bin/cat", path+file.Name()) + + auth := exec.Command("/usr/libexec/authopen", "-w", "/Applications/qtox.app/Contents/"+addpath+file.Name()) + auth.Stdin, _ = cat.StdoutPipe() + auth.Stdout = os.Stdout + auth.Stderr = os.Stderr + _ = auth.Start() + _ = cat.Run() + _ = auth.Wait() + + } else { + install(path+file.Name()+"/", pathlen) + } + } + return 0 +} + +func main() { + syscall.Setuid(0) + usr, e := user.Current() + if e != nil { + log.Fatal(e) + } + +CHECK: + if usr.Name != "System Administrator" { + fmt.Println("Not running as root, relaunching") + + appdir, _ := osext.Executable() + appdir_len := len(appdir) + sudo_path := appdir[0:(appdir_len-7)] + "qtox_sudo" //qtox_sudo is a fork of cocoasudo with all of its flags and other features stripped out + + if _, err := os.Stat(sudo_path); os.IsNotExist(err) { + fmt.Println("Error: No qtox_sudo binary installed, falling back") + custom_user = usr.Name + usr.Name = "System Administrator" + goto CHECK + } + + relaunch := exec.Command(sudo_path, appdir, usr.Name) + relaunch.Stdout = os.Stdout + relaunch.Stderr = os.Stderr + relaunch.Run() + return + + } else { + + if len(os.Args) > 1 || custom_user != "" { + + if custom_user == "" { + custom_user = os.Args[1] + } + + update_dir := "/Users/" + custom_user + "/Library/Preferences/tox/update/" + if _, err := os.Stat(update_dir); os.IsNotExist(err) { + fmt.Println("Error: No update folder, is check for updates enabled?") + return + } + fmt.Println("qTox Updater") + + killqtox := exec.Command("/usr/bin/killall", "qtox") + _ = killqtox.Run() + + install(update_dir, len(update_dir)) + + os.RemoveAll(update_dir) + fmt.Println("Update metadata wiped, launching qTox") + launchqtox := exec.Command("/usr/bin/open", "-b", "im.tox.qtox") + launchqtox.Run() + + } else { + fmt.Println("Error: no user passed") + } + + } +} From e30b74a984e8451f2df0eae85b2801225cb88faf Mon Sep 17 00:00:00 2001 From: Sean Qureshi Date: Tue, 16 Dec 2014 02:51:31 -0800 Subject: [PATCH 237/253] Switch to subtree --- osx/updater.go | 92 -------------------------------------------------- 1 file changed, 92 deletions(-) delete mode 100644 osx/updater.go diff --git a/osx/updater.go b/osx/updater.go deleted file mode 100644 index b063a251d..000000000 --- a/osx/updater.go +++ /dev/null @@ -1,92 +0,0 @@ -package main - -import ( - "fmt" - "io/ioutil" - "log" - "os" - "os/exec" - "os/user" -) - -func fs_type(path string) int { - //name := "FileOrDir" - f, err := os.Open(path) - if err != nil { - fmt.Println(err) - return -1 - } - defer f.Close() - fi, err := f.Stat() - if err != nil { - fmt.Println(err) - return -1 - } - switch mode := fi.Mode(); { - case mode.IsDir(): - return 0 - case mode.IsRegular(): - return 1 - } - - return -1 -} - -func install(path string, pathlen int) int { - files, _ := ioutil.ReadDir(path) - - for _, file := range files { - if fs_type(path+file.Name()) == 1 { - - addpath := "" - if len(path) != pathlen { - addpath = path[pathlen:len(path)] - } - - fmt.Print("Installing: ") - fmt.Println("/Applications/qtox.app/Contents/" + addpath + file.Name()) - if _, err := os.Stat("/Applications/qtox.app/Contents/" + file.Name()); os.IsNotExist(err) { - newfile := exec.Command("/usr/libexec/authopen", "-c", "-x", "-m", "drwxrwxr-x+", "/Applications/qtox.app/Contents/"+addpath+file.Name()) - newfile.Run() - } - - cat := exec.Command("/bin/cat", path+file.Name()) - - auth := exec.Command("/usr/libexec/authopen", "-w", "/Applications/qtox.app/Contents/"+addpath+file.Name()) - auth.Stdin, _ = cat.StdoutPipe() - auth.Stdout = os.Stdout - auth.Stderr = os.Stderr - _ = auth.Start() - _ = cat.Run() - _ = auth.Wait() - - } else { - install(path+file.Name()+"/", pathlen) - } - } - return 0 -} - -func main() { - usr, e := user.Current() - if e != nil { - log.Fatal(e) - } - - update_dir := usr.HomeDir + "/Library/Preferences/tox/update/" - if _, err := os.Stat(update_dir); os.IsNotExist(err) { - fmt.Println("Error: No update folder, is check for updates enabled?") - return - } - fmt.Println("qTox Updater") - - killqtox := exec.Command("/usr/bin/killall", "qtox") - _ = killqtox.Run() - - install(update_dir, len(update_dir)) - - os.RemoveAll(update_dir) - fmt.Println("Update metadata wiped, launching qTox") - launchqtox := exec.Command("/usr/bin/open", "-b", "im.tox.qtox") - launchqtox.Run() -} From fc749880ebaf259689fa26550c3332043dcb1971 Mon Sep 17 00:00:00 2001 From: Dubslow Date: Tue, 16 Dec 2014 18:05:13 -0600 Subject: [PATCH 238/253] minor style tweaks --- src/audiofilterer.cpp | 1 + src/coreav.cpp | 3 +-- src/misc/settings.cpp | 6 ++++-- src/widget/form/settings/avform.cpp | 3 ++- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/audiofilterer.cpp b/src/audiofilterer.cpp index f88ae875b..2253a6f60 100644 --- a/src/audiofilterer.cpp +++ b/src/audiofilterer.cpp @@ -32,6 +32,7 @@ void AudioFilterer::closeFilter() { if (filter) kill_filter_audio(filter); + filter = nullptr; } diff --git a/src/coreav.cpp b/src/coreav.cpp index 41d0fe490..5fb12e128 100644 --- a/src/coreav.cpp +++ b/src/coreav.cpp @@ -81,8 +81,7 @@ void Core::prepareCall(int friendId, int callId, ToxAv* toxav, bool videoEnabled } else { - if (filterer[callId]) - delete filterer[callId]; + delete filterer[callId]; filterer[callId] = nullptr; } #endif diff --git a/src/misc/settings.cpp b/src/misc/settings.cpp index 5ec16448a..ad2c9fbb3 100644 --- a/src/misc/settings.cpp +++ b/src/misc/settings.cpp @@ -900,11 +900,13 @@ void Settings::setOutDev(const QString& deviceSpecifier) outDev = deviceSpecifier; } -bool Settings::getFilterAudio() const{ +bool Settings::getFilterAudio() const +{ return filterAudio; } -void Settings::setFilterAudio(bool newValue){ +void Settings::setFilterAudio(bool newValue) +{ filterAudio = newValue; } diff --git a/src/widget/form/settings/avform.cpp b/src/widget/form/settings/avform.cpp index 903fc7e6a..31f9b6dd9 100644 --- a/src/widget/form/settings/avform.cpp +++ b/src/widget/form/settings/avform.cpp @@ -197,6 +197,7 @@ void AVForm::onOutDevChanged(const QString& deviceDescriptor) Audio::openOutput(deviceDescriptor); } -void AVForm::onFilterAudioToggled(bool filterAudio){ +void AVForm::onFilterAudioToggled(bool filterAudio) +{ Settings::getInstance().setFilterAudio(filterAudio); } From 5cbe175ddc490a6b2b2b68288e30913cfa7f4cb0 Mon Sep 17 00:00:00 2001 From: Dubslow Date: Tue, 16 Dec 2014 18:50:28 -0600 Subject: [PATCH 239/253] update the build system --- bootstrap.sh | 14 +++++++++++- install_libfilteraudio.sh | 45 +++++++++++++++++++++++---------------- qtox.pro | 18 ++++++++++++++-- 3 files changed, 56 insertions(+), 21 deletions(-) mode change 100644 => 100755 install_libfilteraudio.sh diff --git a/bootstrap.sh b/bootstrap.sh index 3188d9ecc..d705a02c0 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -15,6 +15,7 @@ SODIUM_VER=1.0.0 # directory names of cloned repositories SODIUM_DIR=libsodium-$SODIUM_VER TOX_CORE_DIR=libtoxcore-latest +FILTER_AUDIO_DIR=filter_audio # this boolean describes whether the installation of # libsodium should be skipped or not @@ -42,6 +43,11 @@ if [ -z "$TOX_CORE_DIR" ]; then exit 1 fi +if [ -z "$FILTER_AUDIO_DIR" ]; then + echo "internal error detected!" + echo "FILTER_AUDIO_DIR should not be empty... aborting" + exit 1 +fi ########## check input parameters ########## @@ -95,7 +101,7 @@ mkdir -p ${BASE_DIR} # if exists, otherwise cloning them may fail rm -rf ${BASE_DIR}/${SODIUM_DIR} rm -rf ${BASE_DIR}/${TOX_CORE_DIR} - +rm -rf ${BASE_DIR}/${FILTER_AUDIO_DIR} ############### install step ############### @@ -122,6 +128,12 @@ if [[ $TOX_ONLY = "false" ]]; then fi popd + + if [[ $GLOBAL = "false" ]]; then + ./install_libfilteraudio.sh ${BASE_DIR}/${FILTER_AUDIO_DIR} ${BASE_DIR} + else + ./install_libfilteraudio.sh ${BASE_DIR}/${FILTER_AUDIO_DIR} + fi fi # clone current master of libtoxcore diff --git a/install_libfilteraudio.sh b/install_libfilteraudio.sh old mode 100644 new mode 100755 index d0f5bdd38..010bcbbac --- a/install_libfilteraudio.sh +++ b/install_libfilteraudio.sh @@ -1,31 +1,40 @@ #!/bin/sh -CURRENT_DIR=$DIRSTACK -SOURCE_DIR="filter_audio/" -SOURCE_PATH="./../" -LIB_DIR="/usr/local/lib/" -INCLUDE_DIR="/usr/local/include/" -echo "Clone filter_audio from GitHub.com" -cd $SOURCE_PATH -git clone --quiet https://github.com/irungentoo/filter_audio.git $SOURCE_DIR +if [ -z $1 ]; then + SOURCE_DIR="filter_audio/" +else + SOURCE_DIR="$1/" +fi -echo "Compile filter_audio" +if [ -z "$2" ]; then + LIB_DIR="/usr/local/lib/" + INCLUDE_DIR="/usr/local/include/" +else + LIB_DIR="$2/lib/" + INCLUDE_DIR="$2/include/" +fi + +echo "Cloning filter_audio from GitHub.com" +git clone https://github.com/irungentoo/filter_audio.git $SOURCE_DIR + +echo "Compiling filter_audio" cd $SOURCE_DIR gcc -c -fPIC filter_audio.c aec/*.c agc/*.c ns/*.c other/*.c -lm -lpthread -echo "Create shared object file" +echo "Creating shared object file" gcc *.o -shared -o libfilteraudio.so -echo "Clean up" +echo "Cleaning up" rm *.o -echo "Install libfilteraudio.so" -sudo cp libfilteraudio.so $LIB_DIR +muhcmd="cp libfilteraudio.so $LIB_DIR" +[ -z "$2" ] && muhcmd="sudo $muhcmd" +echo "Installing libfilteraudio.so with $muhcmd" +$muhcmd -echo "Install include files" -sudo cp *.h $INCLUDE_DIR +muhcmd="cp *.h $INCLUDE_DIR" +[ -z "$2" ] && muhcmd="sudo $muhcmd" +echo "Installing include files with $muhcmd" +$muhcmd echo "Finished." -cd $CURRENT_DIR -exit 1 - diff --git a/qtox.pro b/qtox.pro index 9dc7722c0..46be9d92d 100644 --- a/qtox.pro +++ b/qtox.pro @@ -59,6 +59,12 @@ contains(DISABLE_PLATFORM_EXT, YES) { DEFINES += QTOX_PLATFORM_EXT } +contains(DISABLE_FILTER_AUDIO, YES) { + +} else { + DEFINES += QTOX_FILTER_AUDIO +} + contains(JENKINS,YES) { INCLUDEPATH += ./libs/include/ } else { @@ -78,6 +84,7 @@ win32 { QMAKE_INFO_PLIST = osx/info.plist LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -ltoxencryptsave -ltoxdns -lsodium -lvpx -framework OpenAL -lopencv_core -lopencv_highgui contains(DEFINES, QTOX_PLATFORM_EXT) { LIBS += -framework IOKit -framework CoreFoundation } + contains(DEFINES, QTOX_FILTER_AUDIO) { } } else { # If we're building a package, static link libtox[core,av] and libsodium, since they are not provided by any package contains(STATICPKG, YES) { @@ -94,8 +101,16 @@ win32 { LIBS += -lX11 -lXss } + contains(DEFINES, QTOX_FILTER_AUDIO) { + contains(STATICPKG, YES) { + LIBS += -Wl,-Bstatic -lfilteraudio + } else { + LIBS += -lfilteraudio + } + } + contains(JENKINS, YES) { - LIBS = ./libs/lib/libtoxav.a ./libs/lib/libvpx.a ./libs/lib/libopus.a ./libs/lib/libtoxdns.a ./libs/lib/libtoxencryptsave.a ./libs/lib/libtoxcore.a ./libs/lib/libsodium.a /usr/lib/libopencv_core.so /usr/lib/libopencv_highgui.so /usr/lib/libopencv_imgproc.so -lopenal -lX11 -lXss -s + LIBS = ./libs/lib/libtoxav.a ./libs/lib/libvpx.a ./libs/lib/libopus.a ./libs/lib/libtoxdns.a ./libs/lib/libtoxencryptsave.a ./libs/lib/libtoxcore.a ./libs/lib/libsodium.a ./libs/lib/libfilteraudio.a /usr/lib/libopencv_core.so /usr/lib/libopencv_highgui.so /usr/lib/libopencv_imgproc.so -lopenal -lX11 -lXss -s } } } @@ -237,7 +252,6 @@ SOURCES += \ contains(DEFINES, QTOX_FILTER_AUDIO) { HEADERS += src/audiofilterer.h SOURCES += src/audiofilterer.cpp - unix|win32: LIBS += -lfilteraudio } contains(DEFINES, QTOX_PLATFORM_EXT) { From f8d870f374749d059d91952df94880feb26ae69b Mon Sep 17 00:00:00 2001 From: agilob Date: Wed, 17 Dec 2014 12:00:41 +0100 Subject: [PATCH 240/253] removing spacing from microphone layout --- src/mainwindow.ui | 6 +++--- src/widget/form/genericchatform.cpp | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 221ef052d..ed58ca986 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1034,7 +1034,7 @@ QSplitter:handle{ 0 0 284 - 401 + 399 @@ -1665,7 +1665,7 @@ QSplitter:handle{ 0 - 57 + 0 @@ -1775,7 +1775,7 @@ QSplitter:handle{ 0 0 775 - 19 + 21 diff --git a/src/widget/form/genericchatform.cpp b/src/widget/form/genericchatform.cpp index 176e3c876..4f3572409 100644 --- a/src/widget/form/genericchatform.cpp +++ b/src/widget/form/genericchatform.cpp @@ -131,9 +131,7 @@ GenericChatForm::GenericChatForm(QWidget *parent) : headLayout->setSpacing(0); volMicLayout->addStretch(); - volMicLayout->addSpacing(1); volMicLayout->addWidget(micButton); - volMicLayout->addSpacing(2); volMicLayout->addWidget(volButton); volMicLayout->addStretch(); From e83c1a2ca116b46a4959146cf07d94bf2072e0fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=83=D1=85=D0=B0=D1=80=D0=B8=D0=BA?= Date: Wed, 17 Dec 2014 14:05:11 +0300 Subject: [PATCH 241/253] update russian translation --- translations/ru.ts | 414 ++++++++++++++++++++++++++++----------------- 1 file changed, 263 insertions(+), 151 deletions(-) diff --git a/translations/ru.ts b/translations/ru.ts index c7d4fbd76..731d53ce2 100644 --- a/translations/ru.ts +++ b/translations/ru.ts @@ -12,12 +12,12 @@ AVSettings - + Video Settings Настройки видео - + Resolution Разрешение @@ -47,22 +47,32 @@ Устройство записи - + + Rescan audio devices + Повторить поиск аудиоустройств + + + + Filter audio + Фильтрация звука + + + Hue Тон - + Brightness Яркость - + Saturation Насыщенность - + Contrast Контраст @@ -113,8 +123,14 @@ qTox needs to use the Tox DNS, but can't do it through a proxy. -Ignore the proxy and connect to the Internet directly ? +Ignore the proxy and connect to the Internet directly? qTox необходимо воспользоваться Tox DNS, но это нельзя сделать через прокси. +Игнорировать прокси и подлючиться напрямую через интернет? + + + qTox needs to use the Tox DNS, but can't do it through a proxy. +Ignore the proxy and connect to the Internet directly ? + qTox необходимо воспользоваться Tox DNS, но это нельзя сделать через прокси. Игнорировать прокси и подлючиться напрямую через интернет? @@ -152,7 +168,7 @@ Ignore the proxy and connect to the Internet directly ? Form - + Form @@ -183,48 +199,68 @@ Ignore the proxy and connect to the Internet directly ? Загрузить историю... - + Send a file Отправить файл + File not read + Файл не прочитать + + + + qTox wasn't able to open %1 + Паравозик не смог. Не сможешь и ты! + qTox не смог открыть %1 + + + Bad Idea Плохая идея - + You're trying to send a special (sequential) file, that's not going to work! ...передаёте последовательный файл и получаете te-le-fun-ken. И переводчик работает по другой линии. По линии «Библиотека». Вы пытаетесь отправить специальный (последовательный) файл. Это так не работает! - - %1 calling + + %1 is calling %1 звонит - + %1 calling + %1 звонит + + + %1 stopped calling %1 прекратил звонить - + Calling to %1 Звоним %1 - + Call rejected Звонок отклонён - + + Failed to send file "%1" + Не удалось отправить файл «%1» + + + Call with %1 ended. %2 Разговор с %1 завершился. %2 - + Call duration: Длительность разговора: @@ -240,106 +276,120 @@ Ignore the proxy and connect to the Internet directly ? Core - + Toxing on qTox Как-то так. Может, можно ещё что-нибудь придумать? Всем привет из qTox'а - + qTox User Пользователь qTox - + Friend is already added Друг уже добавлен - + Encryption error Ошибка шифрования - + The .tox file is encrypted, but encryption was not checked, continuing regardless. Файл .tox зашифрован, однако шифрование в настройках включено не было. Продолжаем вопреки. - + Tox datafile decryption password Пароль для расшифровки файла данных Tox - - - + + + Password error Ошибка пароля - - + + Failed to setup password. Empty password. Не удалось установить пароль. Пустой пароль. - + Try Again Попробуйте ещё - + Change profile Сменить профиль - + Reinit current profile Увы, никто не знает, что разработчики имели в виду. Ту би транслейтед. Сбросить данные текущего профиля - + Wrong password has been entered Введён неправильный пароль - + History Log decryption password Пароль для расшифровки журнала переписки - + Encrypted log Зашифрованный журнал - - Your history is encrypted with different password + + Your history is encrypted with different password. Do you want to try another password? - Ваша переписка зашифрована другим паролем + Ваша переписка зашифрована другим паролем. +Ходите попробовать другой пароль? + + + + History + История + + + + Due to incorret password history will be disabled. + Из-за некорректного пароля журналирование будет отключено. + + + Your history is encrypted with different password +Do you want to try another password? + Ваша переписка зашифрована другим паролем Ходите попробовать другой пароль? - Loggin - Журналирование + Журналирование - Due to incorret password logging will be disabled - Из-за некорректного пароля журналирование будет отключено + Из-за некорректного пароля журналирование будет отключено - + NO Password БЕЗ пароля - + Will be saved without encryption! Будет сохранено без шифрования! @@ -466,14 +516,20 @@ Do you want to try another password? Выбрать папку для автоматического приёма - + User alias Псевдоним пользователя - + + You can also set this by clicking the chat form name. +Alias: + Можно также установить, щёлкнув по имени вверху окна чата. +Псевдоним: + + Alias: - Новый псевдоним: + Новый псевдоним: @@ -484,25 +540,25 @@ Do you want to try another password? Общие - - + + None Отсутствует - + Choose an auto accept directory popup title Выбрать папку для автоматического приёма - + Call active popup title Идёт звонок - + You can't disconnect while a call is active! popup text Нельзя отключиться пока идёт звонок! @@ -522,130 +578,140 @@ Do you want to try another password? Перевод не изменится до перезапуска qTox. - + + System tray integration + Настройки трея + + + Show system tray icon Показывать иконку в трее - + Close to tray Сворачивать в трей при закрытии - + Minimize to tray Сворачивать в трей - + + Light icon + Светлая иконка + + + Check for updates on startup (unstable) Проверять на наличие обновлений (нестабильно) - + Focus qTox when a message is received Захватывать фокус при приёме сообщений - + Faux offline messaging Имитация офлайнового обмена сообщениями - + Auto away after (0 to disable) Менять статус на «Отошёл» после (0 — не менять) - + Set to 0 to disable Укажите 0, чтобы отключить - + You can set this on a per-friend basis by right clicking them. autoaccept cb tooltip Можно настроить отдельно для каждого друга (щёлкнув правой кнопкой мыши по другу и выбрав соответствующий пункт меню). - + Use emoticons Использовать смайлики - + Smiley Pack Text on smiley pack label Набор смайликов - + Style Стиль - + Theme color Цвет темы - + Emoticon size Размер смайликов - + px По аналогии с Мпикс. Хотя, может лучше принять http://ilyabirman.ru/meanwhile/all/px/? пикс - + Timestamp format Формат времени - + Connection Settings Настройки соединения - + Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Включить IPv6 (рекомендуется) - + Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Отключение позволяет, например, использовать Tox поверх Tor. Однако это добавляет нагрузку на сеть Tox, так что отключайте только в случае необходимости. - + Enable UDP (recommended) Text on checkbox to disable UDP Включить UDP (рекомендуется) - + Use proxy (SOCKS5) Использовать прокси (SOCKS5) - + Address Text on proxy addr label Адрес - + Port Text on proxy port label Порт - + Reconnect reconnect button Переподключиться @@ -667,42 +733,42 @@ Do you want to try another password? Портативный режим - + Start in tray Запускать свёрнутым в трей - + Show contacts' status changes Показывать изменения статусов контактов - + Provided in minutes Выставлено в минутах - + minutes минут - + Autoaccept files Автоматически принимать файлы - + Save files in Сохранять в - + PushButton - + Theme Тема @@ -710,53 +776,53 @@ Do you want to try another password? GenericChatForm - + Send message Отправить сообщение - + Smileys Смайлики - + Send file(s) Отправить файл(ы) - - - Audio call: RED means you're on a call - Позвонить, только аудио (красная кнопка значит что вы на связи) - - Video call: RED means you're on a call - Видеозвонок (красная кнопка значит что вы на связи) + Audio call: RED means you're on a call + Позвонить, только аудио (красная кнопка означает что вы на связи) - Toggle speakers volume: RED is OFF - Включить или выключить звук (красная кнопка значит что звук выключен) + Video call: RED means you're on a call + Видеозвонок (красная кнопка означает что вы на связи) - Toggle microphone: RED is OFF - Включить или выключить микрофон (красная кнопка значит что микрофон выключен) + Toggle speakers volume: RED is OFF + Включить или выключить звук (красная кнопка означает что звук выключен) - - + + Toggle microphone: RED is OFF + Включить или выключить микрофон (красная кнопка означает что микрофон выключен) + + + + Save chat log Сохранить журнал чата - + Clear displayed messages Очистить показываемые сообщения - + Cleared Очищено @@ -764,13 +830,13 @@ Do you want to try another password? GroupChatForm - + %1 users in chat Number of users in chat %1 пользователей в чате - + %1 users in chat %1 пользователей в чате @@ -778,23 +844,40 @@ Do you want to try another password? GroupWidget - + Quit group Menu to quit a groupchat Покинуть группу - - + + %1 users in chat %1 пользователей в чате - - + + 0 users in chat Ни одного пользователя в чате + + + Set title... + Установить заголовок... + + + + Group title + Заголовок группы + + + + You can also set this by clicking the chat form name. +Title: + Можно также установить, щёлкнув по заголовку вверху окна чата. +Заголовок: + IdentityForm @@ -885,42 +968,49 @@ Do you want to try another password? - Are you sure you want to delete this profile? + Are you sure you want to delete this profile? +Associated friend information and chat logs will be deleted as well. deletion confirmation text - Вы действительно хотите удалить этот профиль? + Вы действительно хотите удалить этот профиль? +Связанная с ним информация о друзьях и история переписки будут также удалены. - + Are you sure you want to delete this profile? + deletion confirmation text + Вы действительно хотите удалить этот профиль? + + + Import profile import dialog title Импортировать профиль - + Tox save file (*.tox) import dialog filter Файл Tox (*.tox) - + Ignoring non-Tox file popup title Выбран не файл Tox - + Warning: you've chosen a file that is not a Tox save file; ignoring. popup text Внимание: вы выбрали не файл Tox; игнорирование. - + Profile already exists import confirm title Профиль уже существует - + A profile named "%1" already exists. Do you want to erase it? import confirm text Профиль с именем «%1» уже существует. Перезаписать? @@ -1151,16 +1241,22 @@ Do you want to delete old history file? QObject - + Update The title of a message box Обновление - + + An update is available, do you want to download it now? +It will be installed when qTox restarts. + Обновление доступно, не желаете ли скачать его прямо сейчас? +Оно будет установлено после того, как qTox будет перезапущен. + + An update is available, do you want to download it now ? It will be installed when qTox restarts. - Обновление доступно, не желаете ли скачать его прамо сейчас? + Обновление доступно, не желаете ли скачать его прамо сейчас? Оно будет установлено после того, как qTox будет перезапущен. @@ -1209,6 +1305,18 @@ It will be installed when qTox restarts. Без перевода, так как весь остальной CLI на английском Tox URI to parse + + + Starts new instance and loads specified profile. + Без перевода, так как весь остальной CLI на английском + Starts new instance and loads specified profile. + + + + profile + Без перевода, так как весь остальной CLI на английском + profile + Default @@ -1309,8 +1417,12 @@ It will be installed when qTox restarts. Добавить друга - Do you want to add %1 as a friend ? + Хотите добавить %1 в друзья? + + + + Do you want to add %1 as a friend? Хотите добавить %1 в друзья? @@ -1339,160 +1451,160 @@ It will be installed when qTox restarts. Widget - + Online В сети - + Away Отошёл - + Busy Занят - + &Quit В&ыход - + Change status to: Сменить статус на: - + Online Button to set your status to 'Online' В сети - + Away Button to set your status to 'Away' Вероятно, это не столь долгое путешествие Отошёл - + Busy Button to set your status to 'Busy' Занят - + Choose a profile Выберите профиль - + Please choose which identity to use Выберите личность, которую хотите использовать - + Choose a profile picture Выбрать картинку для профиля - - - + + + Error Ошибка - + Unable to open this file Невозможно открыть файл - + Unable to read this image Невозможно прочесть это изображение - + This image is too big Это изображение слишком большое - + Toxcore failed to start, the application will terminate after you close this message. Не удалось запустить toxcore, приложение будет завершено после того как вы закроете это сообщение. - + toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text Не удалось запустить toxcore с вашими настройками прокси, qTox не может работать; измените ваши настройки и перезапустите его. - + Add friend Добавить друга - + File transfers Передачи файлов - + Settings Настройки - + Couldn't request friendship Не удалось запросить добавление в друзья - + away contact status отсутствует - + busy contact status занят - + offline contact status офлайн - + online contact status в сети - + %1 is now %2 e.g. "Dubslow is now online" %1 сейчас %2 - + <Unknown> Placeholder when we don't know someone's name in a group chat <Неизвестный> - + %1 has set the title to %2 %1 сменил заголовок на %2 - + Message failed to send Не удалось отправить сообщение From 5861c6daa56eca4870f83c34badd22a59ba72db5 Mon Sep 17 00:00:00 2001 From: agilob Date: Wed, 17 Dec 2014 12:36:34 +0100 Subject: [PATCH 242/253] removing fixed size from mic/volume buttons so they align nicely --- src/widget/form/genericchatform.cpp | 30 +++++++++++++++------------- ui/volButton/volButton.css | 4 ++-- ui/volButton/volButton.png | Bin 637 -> 624 bytes ui/volButton/volButtonHover.png | Bin 597 -> 590 bytes ui/volButton/volButtonPressed.png | Bin 629 -> 617 bytes 5 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/widget/form/genericchatform.cpp b/src/widget/form/genericchatform.cpp index 4f3572409..30dadae74 100644 --- a/src/widget/form/genericchatform.cpp +++ b/src/widget/form/genericchatform.cpp @@ -52,10 +52,13 @@ GenericChatForm::GenericChatForm(QWidget *parent) : nameLabel->setEditable(true); avatar = new MaskablePixmapWidget(this, QSize(40,40), ":/img/avatar_mask.png"); - QHBoxLayout *headLayout = new QHBoxLayout(), *mainFootLayout = new QHBoxLayout(); - headTextLayout = new QVBoxLayout(); - QVBoxLayout *mainLayout = new QVBoxLayout(); - QVBoxLayout *footButtonsSmall = new QVBoxLayout(), *volMicLayout = new QVBoxLayout(); + QHBoxLayout *headLayout = new QHBoxLayout(), + *mainFootLayout = new QHBoxLayout(); + + QVBoxLayout *mainLayout = new QVBoxLayout(), + *footButtonsSmall = new QVBoxLayout(), + *volMicLayout = new QVBoxLayout(); + headTextLayout = new QVBoxLayout(); chatWidget = new ChatAreaWidget(); @@ -76,10 +79,10 @@ GenericChatForm::GenericChatForm(QWidget *parent) : videoButton->setFixedSize(50,40); videoButton->setToolTip(tr("Video call: RED means you're on a call")); volButton = new QPushButton(); - volButton->setFixedSize(25,20); + //volButton->setFixedSize(25,20); volButton->setToolTip(tr("Toggle speakers volume: RED is OFF")); micButton = new QPushButton(); - micButton->setFixedSize(25,20); + // micButton->setFixedSize(25,20); micButton->setToolTip(tr("Toggle microphone: RED is OFF")); footButtonsSmall->setSpacing(2); @@ -119,6 +122,13 @@ GenericChatForm::GenericChatForm(QWidget *parent) : mainFootLayout->addSpacing(5); mainFootLayout->addWidget(sendButton); mainFootLayout->setSpacing(0); + + headTextLayout->addStretch(); + headTextLayout->addWidget(nameLabel); + + volMicLayout->addWidget(micButton, Qt::AlignTop); + volMicLayout->addSpacing(2); + volMicLayout->addWidget(volButton, Qt::AlignBottom); headWidget->setLayout(headLayout); headLayout->addWidget(avatar); @@ -130,14 +140,6 @@ GenericChatForm::GenericChatForm(QWidget *parent) : headLayout->addWidget(videoButton); headLayout->setSpacing(0); - volMicLayout->addStretch(); - volMicLayout->addWidget(micButton); - volMicLayout->addWidget(volButton); - volMicLayout->addStretch(); - - headTextLayout->addStretch(); - headTextLayout->addWidget(nameLabel); - //Fix for incorrect layouts on OS X as per //https://bugreports.qt-project.org/browse/QTBUG-14591 sendButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); diff --git a/ui/volButton/volButton.css b/ui/volButton/volButton.css index 3bf4d64a8..8210f4819 100644 --- a/ui/volButton/volButton.css +++ b/ui/volButton/volButton.css @@ -5,7 +5,7 @@ QPushButton#green background-repeat: none; border: none; width: 25px; - height: 20px; + height: 18px; } QPushButton#green:hover @@ -20,7 +20,7 @@ QPushButton#red background-repeat: none; border: none; width: 25px; - height: 20px; + height: 18px; } QPushButton:focus { diff --git a/ui/volButton/volButton.png b/ui/volButton/volButton.png index a1c474eab843e1cc93d58278740492626de1dcab..b07c132092c6c6428e93eb83a269d22e0e1858eb 100644 GIT binary patch delta 599 zcmV-d0;v7{1n>lqB!2{RLP=Bz2nYy#2xN!=000SaNLh0L01m_e01m_fl`9S#0006K zNklFyE8W4Z8kFt+}ftBDAx+=A|ePSwVY^aVmA@tO&5We{W(G5 zMaYckhmQILBXviKuS}aApQt~zB`CnB>1H9;0v^)gNARgCDvW#6^6p@Z{{15)ro_aPQC^L)#%0TA4=pUGrQ?zl3ZCC9n< zE-I~g9A0MAF92w+4U;YWAn|=!?pPL26OT|(iTKW8QAX>jH=Z7ePLR03I*jN2mhLs+nptpG{DJr~n4}J>jYj ldIN(8OLwZeu82`!@E87>kCwccBtpET53Q0skR5;76l-)~{VHn1L@5k79x7o}raBG{gqFO7c zgNPuM)N-PwiJe4*CmjSH_U8nJ2O%q>4=G#Z$H5MaG!GJCWq)E@=~^tU7W;ZTC`a4- zY%cOXdbscFxt@!Q`?;j+Q&%H=VF`98-R zPoSyR5{$Ze81!8>cvz_>KZY3&++ZU9jIWs#Z@$cPwBZ<$)oG*RlZ~fq-lw(nDl#e_ zdouv<)}y#>-rB6#s}IBm|Eal zb^`$4j{Qu=!?juQB0kQ8_aWiTX>~D|eEEB)>jR{7KZt!_5i1te^Vkz4WP1kMDIB!2{RLP=Bz2nYy#2xN!=000SaNLh0L01FcU01FcV0GgZ_0005- zNkl_Dy1E2Blz`{0NXL4vAiHH!#bncYAtiiQ)75<;q=e_4q@(^Zru%1@2u_*$ zez6;=F4@WF1t)3TH_FY(tS$419v+Q6wPj7kBfd-Aids_&xK9I6tZC%RS*`_dv@VV* zab}})w(NrcB7ft72>{YtNiO$}Tjm%6O$M)9GHg`o(p#Ryp7F8#9)Qo=IR@mj6n4K_ z-}q|pWJ}UN3nk8U1OfO~$rCvh{fqx+9n@sF{9R_(ftU&f;nUBK|fKSDmh&1 z0yB}@h=OSJm$wqec&cxjWi?@0>$rKVl`L;IUN__`TACl#@0Px#>FT1>4VT@?APEw( zTeh&D`!1W7o~Emd60o4@YKPOEoZ0@+44;%P0SmtXL9&o@w^K0F00000NkvXXu0mjf DmQN9Y delta 553 zcmV+^0@nS`1l0tPBo78+OGiWiYXHIkV8}S$Rgp0le*gz`Nliru-V6j0Dlc~|m7f3r z0pdwSK~y-)t(4zOQ(+j#KYP!ale5m8F58$grfIZXAtYD`CXp8j*$*VVtgD2goBo-u zy7nRnf^K3g+XxGGF@`0=j=hob9Ns1S;ox`~dtcWeJ@Lx9@SY_**?@)H4o1@vuODudh2`I7M~*cTzY6)zeCM zz%HeioRtN@?bIosl;z;{LZV|Ub`G}z81;;iD`n+fk;upE#xF^%IMBqwiP67N!{lH( z8N*T$G{DrS9xxG#sXCxBnm5vao<7P`^uEUN04!jm+^e8BxUU z_qPr<2ZK1~=6@8u9*+m>%nSgDogFOW<5UI)YI-z}g)JPW&+Bcp09jYD7@n0CF8B7} z{G9CRDWEXSkO0h`y&=3HB(rL6N zQ+vQvtEe;&JE;-`s^xNfz-q0Qd^`?7hhEQpp`Z*-?|<*N3>921D#ao>NdlmIaFCmS zlRQek6&I06&2u#egE;5s6~R4jH#tc{l}G?!7#`*_7E@0ATtA{wzHV(PdatckGDk-b z_0$yix3`o|PLvZXDZRM~f`Dyy7D26+zb98$<+^`*x-MzigKxXLFZ$O92jmod z_4~j(mq{UAa5sYg7DcLhJ%v@d>kSG00001oZ@vBo78+OGiWiYXHIkV8}S$Rgp0le*gz`Nliru-V6j0DjB$z00sa6 z0s=`yK~y-)t(3h_T45B2pL@Yds7S+ywkh(FsG$)S7t=vyF^vlbVhk}3G;wfZ^xvpq zaxk5YVKAwSlT5nQ)R?QwUC<(dMjveG>S7A z1mH{fE(hc2D3xN7iX=7N5nDm+b91-Q`=;ycWGEJ+?s7d1Y@eOQ9d52tS0F&m=_I$m zPfn6>g+kiFwnzl`^t7t>!tcj1ISD{|V*~rhe+ad{zJ?h6f5#b%(HjV8>ik(UiGOjC z)2%HyJ|=&7h;?|FpP5W^#`du>%_CKnWzg#}c{~7Iq*8Rb-A`-YLsl!7=^hvW;E$qU zuv*&#rd~(T-&9f%Me5aRd%!w_fnq8JK!?f1b*ZEdR<^e_Lq)HbTDeR?mI3JQ@8{y* zeb9ON{a)K-cgi3DHQ*HyLW4hOlty&Hdg zoa@U=DhCJZ8LOGHwg#ezb7~4vr@KAREG^wFor#I2Y&A7cSzYDZ=H`R?`OXdn6kqIuEhdRX`i XdIzfIRs)|900000NkvXXu0mjfO_>uB From e5b9e8c59c3e6009f56fae865933b7c039af3036 Mon Sep 17 00:00:00 2001 From: Zetok Zalbavar Date: Wed, 17 Dec 2014 15:37:07 +0000 Subject: [PATCH 243/253] Make quote colour more readable --- ui/chatArea/innerStyle.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/chatArea/innerStyle.css b/ui/chatArea/innerStyle.css index dc1f46d73..c8f3fece5 100644 --- a/ui/chatArea/innerStyle.css +++ b/ui/chatArea/innerStyle.css @@ -34,7 +34,7 @@ div.date_me { } span.quote { - color: #6bc260; + color: #279419; } div.green { From 9bc6ae57fa1028752c9d882139d740ab98255a48 Mon Sep 17 00:00:00 2001 From: Dubslow Date: Wed, 17 Dec 2014 16:49:17 -0600 Subject: [PATCH 244/253] try mac fix --- qtox.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qtox.pro b/qtox.pro index 46be9d92d..98323e4d9 100644 --- a/qtox.pro +++ b/qtox.pro @@ -84,7 +84,7 @@ win32 { QMAKE_INFO_PLIST = osx/info.plist LIBS += -L$$PWD/libs/lib/ -ltoxcore -ltoxav -ltoxencryptsave -ltoxdns -lsodium -lvpx -framework OpenAL -lopencv_core -lopencv_highgui contains(DEFINES, QTOX_PLATFORM_EXT) { LIBS += -framework IOKit -framework CoreFoundation } - contains(DEFINES, QTOX_FILTER_AUDIO) { } + contains(DEFINES, QTOX_FILTER_AUDIO) { LIBS += -lfilteraudio } } else { # If we're building a package, static link libtox[core,av] and libsodium, since they are not provided by any package contains(STATICPKG, YES) { From 30d9862c4fdcc9c698b612339af888d389000269 Mon Sep 17 00:00:00 2001 From: novist Date: Thu, 18 Dec 2014 20:32:28 +0200 Subject: [PATCH 245/253] Removed explanations from tray menu --- src/widget/widget.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index 77593bdf8..d11bd3e9f 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -96,12 +96,11 @@ void Widget::init() statusAway->setIcon(QIcon(":ui/statusButton/dot_idle.png")); connect(statusAway, SIGNAL(triggered()), this, SLOT(setStatusAway())); statusBusy = new QAction(tr("Busy"), this); - connect(statusBusy, SIGNAL(triggered()), this, SLOT(setStatusBusy())); statusBusy->setIcon(QIcon(":ui/statusButton/dot_busy.png")); + connect(statusBusy, SIGNAL(triggered()), this, SLOT(setStatusBusy())); actionQuit = new QAction(tr("&Quit"), this); connect(actionQuit, SIGNAL(triggered()), qApp, SLOT(quit())); - trayMenu->addAction(new QAction(tr("Change status to:"), this)); trayMenu->addAction(statusOnline); trayMenu->addAction(statusAway); trayMenu->addAction(statusBusy); From 0c2473d68fa797892501eaa5cb23bde9029a7e68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Bueno?= Date: Mon, 22 Dec 2014 13:22:02 +0100 Subject: [PATCH 246/253] update nodes list the previous one was 4 months old --- res/settings.ini | 100 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 72 insertions(+), 28 deletions(-) diff --git a/res/settings.ini b/res/settings.ini index e335c7c8d..46d3cc207 100644 --- a/res/settings.ini +++ b/res/settings.ini @@ -1,42 +1,86 @@ [DHT%20Server] -dhtServerList\size=9 +dhtServerList\size=21 dhtServerList\1\name=Nikolai Toryzin dhtServerList\1\userId=951C88B7E75C867418ACDB5D273821372BB5BD652740BCDF623A4FA293E75D2F dhtServerList\1\address=192.254.75.98 dhtServerList\1\port=33445 -dhtServerList\2\name=sonOfRa -dhtServerList\2\userId=04119E835DF3E78BACF0F84235B300546AF8B936F035185E2A8E9E0A67C8924F -dhtServerList\2\address=144.76.60.215 -dhtServerList\2\port=33445 -dhtServerList\3\name=stal -dhtServerList\3\userId=A09162D68618E742FFBCA1C2C70385E6679604B2D80EA6E84AD0996A1AC8A074 -dhtServerList\3\address=23.226.230.47 +dhtServerList\2\name=Nikolai Toryzin +dhtServerList\2\userId=2A4B50D1D525DA2E669592A20C327B5FAD6C7E5962DC69296F9FEC77C4436E4E +dhtServerList\2\address=31.7.57.236 +dhtServerList\2\port=443 +dhtServerList\3\name=sonOfRa +dhtServerList\3\userId=04119E835DF3E78BACF0F84235B300546AF8B936F035185E2A8E9E0A67C8924F +dhtServerList\3\address=144.76.60.215 dhtServerList\3\port=33445 -dhtServerList\4\name=aitjcize -dhtServerList\4\userId=7F9C31FE850E97CEFD4C4591DF93FC757C7C12549DDD55F8EEAECC34FE76C029 -dhtServerList\4\address=54.199.139.199 +dhtServerList\4\name=stal +dhtServerList\4\userId=A09162D68618E742FFBCA1C2C70385E6679604B2D80EA6E84AD0996A1AC8A074 +dhtServerList\4\address=23.226.230.47 dhtServerList\4\port=33445 dhtServerList\5\name=astonex -dhtServerList\5\userId=B98A2CEAA6C6A2FADC2C3632D284318B60FE5375CCB41EFA081AB67F500C1B0B -dhtServerList\5\address=37.59.102.176 +dhtServerList\5\userId=10B20C49ACBD968D7C80F2E8438F92EA51F189F4E70CFBBB2C2C8C799E97F03E +dhtServerList\5\address=178.62.125.224 dhtServerList\5\port=33445 -dhtServerList\6\name=nurupo -dhtServerList\6\userId=F404ABAA1C99A9D37D61AB54898F56793E1DEF8BD46B1038B9D822E8460FAB67 -dhtServerList\6\address=192.210.149.121 +dhtServerList\6\name=mousey +dhtServerList\6\userId=5EB67C51D3FF5A9D528D242B669036ED2A30F8A60E674C45E7D43010CB2E1331 +dhtServerList\6\address=37.187.46.132 dhtServerList\6\port=33445 -dhtServerList\7\name=mousey -dhtServerList\7\userId=5EB67C51D3FF5A9D528D242B669036ED2A30F8A60E674C45E7D43010CB2E1331 -dhtServerList\7\address=37.187.46.132 +dhtServerList\7\name=SylvieLorxu +dhtServerList\7\userId=4B2C19E924972CB9B57732FB172F8A8604DE13EEDA2A6234E348983344B23057 +dhtServerList\7\address=178.21.112.187 dhtServerList\7\port=33445 -dhtServerList\8\name=Proplex -dhtServerList\8\userId=7BE3951B97CA4B9ECDDA768E8C52BA19E9E2690AB584787BF4C90E04DBB75111 -dhtServerList\8\address=107.161.17.51 +dhtServerList\8\name=Munrek +dhtServerList\8\userId=E398A69646B8CEACA9F0B84F553726C1C49270558C57DF5F3C368F05A7D71354 +dhtServerList\8\address=195.154.119.113 dhtServerList\8\port=33445 -dhtServerList\9\name=SylvieLorxu -dhtServerList\9\userId=4B2C19E924972CB9B57732FB172F8A8604DE13EEDA2A6234E348983344B23057 -dhtServerList\9\address=178.21.112.187 +dhtServerList\9\name=nurupo +dhtServerList\9\userId=F404ABAA1C99A9D37D61AB54898F56793E1DEF8BD46B1038B9D822E8460FAB67 +dhtServerList\9\address=192.210.149.121 dhtServerList\9\port=33445 -dhtServerList\10\name=Unknown (uTox) -dhtServerList\10\userId=7187969BB10B54C98538BAE94C069CE5C84E650D54F7E596543D8FB1ECF4CF23 -dhtServerList\10\address=95.85.13.245 +dhtServerList\10\name=aitjcize +dhtServerList\10\userId=7F9C31FE850E97CEFD4C4591DF93FC757C7C12549DDD55F8EEAECC34FE76C029 +dhtServerList\10\address=54.199.139.199 dhtServerList\10\port=33445 +dhtServerList\11\name=Jfreegman +dhtServerList\11\userId=8CD087E31C67568103E8C2A28653337E90E6B8EDA0D765D57C6B5172B4F1F04C +dhtServerList\11\address=104.219.184.206 +dhtServerList\11\port=443 +dhtServerList\12\name=bunslow +dhtServerList\12\userId=93574A3FAB7D612FEA29FD8D67D3DD10DFD07A075A5D62E8AF3DD9F5D0932E11 +dhtServerList\12\address=76.191.23.96 +dhtServerList\12\port=33445 +dhtServerList\13\name=Martin Schröder +dhtServerList\13\userId=F5A1A38EFB6BD3C2C8AF8B10D85F0F89E931704D349F1D0720C3C4059AF2440A +dhtServerList\13\address=46.38.239.179 +dhtServerList\13\port=33445 +dhtServerList\14\name=lkwg82 +dhtServerList\14\userId=2C308B4518862740AD9A121598BCA7713AFB25858B747313A4D073E2F6AC506C +dhtServerList\14\address=144.76.93.230 +dhtServerList\14\port=33445 +dhtServerList\15\name=Impyy +dhtServerList\15\userId=788236D34978D1D5BD822F0A5BEBD2C53C64CC31CD3149350EE27D4D9A2F9B6B +dhtServerList\15\address=178.62.250.138 +dhtServerList\15\port=33445 +dhtServerList\16\name=Thierry Thomas +dhtServerList\16\userId=7A2306BFBA665E5480AE59B31E116BE9C04DCEFE04D9FE25082316FA34B4DA0C +dhtServerList\16\address=78.225.128.39 +dhtServerList\16\port=33445 +dhtServerList\17\name=Manolis +dhtServerList\17\userId=461FA3776EF0FA655F1A05477DF1B3B614F7D6B124F7DB1DD4FE3C08B03B640F +dhtServerList\17\address=130.133.110.14 +dhtServerList\17\port=33445 +dhtServerList\18\name=lawk1 +dhtServerList\18\userId=58D2DE4B169502669941E50780C1630FAA48A0B7026D6F4066C320D47AC6401E +dhtServerList\18\address=178.62.150.106 +dhtServerList\18\port=33445 +dhtServerList\19\name=noisykeyboard +dhtServerList\19\userId=5918AC3C06955962A75AD7DF4F80A5D7C34F7DB9E1498D2E0495DE35B3FE8A57 +dhtServerList\19\address=104.167.101.29 +dhtServerList\19\port=33445 +dhtServerList\20\name=aceawan +dhtServerList\20\userId=391C96CB67AE893D4782B8E4495EB9D89CF1031F48460C06075AA8CE76D50A21 +dhtServerList\20\address=195.154.109.148 +dhtServerList\20\port=33445 +dhtServerList\21\name=pastly +dhtServerList\21\userId=3E1FFDEB667BFF549F619EC6737834762124F50A89C8D0DBF1DDF64A2DD6CD1B +dhtServerList\21\address=192.3.173.88 +dhtServerList\21\port=33445 From 78f33edfb3331213a4f1c3cfc5ec5d9b61bbccd1 Mon Sep 17 00:00:00 2001 From: Toxicop Date: Wed, 24 Dec 2014 08:20:46 +0100 Subject: [PATCH 247/253] Update Deutsch translation --- translations/de.ts | 537 +++++++++++++++++++-------------------------- 1 file changed, 223 insertions(+), 314 deletions(-) diff --git a/translations/de.ts b/translations/de.ts index 6636bf5de..e54e3a6b2 100644 --- a/translations/de.ts +++ b/translations/de.ts @@ -4,7 +4,7 @@ AVForm - + Audio/Video Audio/Video @@ -42,32 +42,37 @@ Erneut nach Audiogeräten suchen - + + Filter audio + Audiofilter + + + Video Settings Video Einstellungen - + Resolution Auflösung - + Hue Farbton - + Brightness Helligkeit - + Saturation Sättigung - + Contrast Kontrast @@ -117,8 +122,8 @@ qTox needs to use the Tox DNS, but can't do it through a proxy. -Ignore the proxy and connect to the Internet directly ? - qTox muss Tox DNS nutzen, dies klappt allerdings nicht über einen Proxy. +Ignore the proxy and connect to the Internet directly? + qTox muss das Tox DNS nutzen, dies klappt allerdings nicht über einen Proxy. Soll der Proxy ignoriert und eine direkte Internetverbindung genutzt werden? @@ -127,31 +132,6 @@ Soll der Proxy ignoriert und eine direkte Internetverbindung genutzt werden?DNS error Tox ID existiert nicht - - Error while looking up DNS - The DNS gives the Tox ID associated to toxme.se addresses - Fehler beim Auflösen des DNS - - - Unexpected number of text records - Error with the DNS - Unererwartete Anzahl von Texteinträgen - - - Unexpected number of values in text record - Error with the DNS - Unerwartete Anzahl von Werten innerhalb des Texteintrages - - - The DNS lookup does not contain any Tox ID - Error with the DNS - Der DNS Eintrag enthält keine Tox ID - - - The DNS lookup does not contain a valid Tox ID - Error with the DNS - Der DNS Eintrag enthält keine gültige Tox ID - AdvancedForm @@ -201,18 +181,7 @@ Soll der Proxy ignoriert und eine direkte Internetverbindung genutzt werden? <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_synchronous"><span style=" text-decoration: underline; color:#0000ff;">Synchronous writing to DB</span></a></p></body></html> - Synchrones Schreiben in die DB - - - - Camera - - Camera eror - Kamerafehler - - - Camera format %1 not supported, can't use the camera - Kameraformat %1 wird nicht unterstützt. Die Kamera kann nicht verwendet werden + <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_synchronous"><span style=" text-decoration: underline; color:#0000ff;">Synchrones Schreiben in die DB</span></a></p></body></html> @@ -223,59 +192,65 @@ Soll der Proxy ignoriert und eine direkte Internetverbindung genutzt werden?Historie laden... - + Send a file Datei versenden - + + File not read + Datei nicht gelesen + + + + qTox wasn't able to open %1 + qTox konnte %1 nicht öffnen + + + Bad Idea Schlechte Idee - + You're trying to send a special (sequential) file, that's not going to work! Sie versuchen eine spezielle (sequentielle) Datei zu senden, das funktioniert nicht! - + %1 is calling %1 ruft an - + %1 stopped calling %1 hat den Anruf beendet - + Calling to %1 Rufe %1 an - + Call rejected Anruf abgewiesen - Failed to send file - Fehler beim Senden der Datei + Failed to send file "%1" + Senden der Datei "%1" fehlgeschlagen - + Call with %1 ended. %2 Anruf zu %1 beendet. %2 - + Call duration: Anrufdauer: - - Save chat log - Chatverlauf speichern - ChatTextEdit @@ -285,109 +260,107 @@ Soll der Proxy ignoriert und eine direkte Internetverbindung genutzt werden?Nachricht hier eingeben... - - CopyableElideLabel - - Copy - Kopieren - - Core - + Toxing on qTox Toxen mit qTox - + qTox User qTox Benutzer - + Friend is already added Freund wurde schon hinzugefügt - + Encryption error Verschlüsselungsfehler - + The .tox file is encrypted, but encryption was not checked, continuing regardless. Die .tox Datei ist verschlüsselt, aber die Verschlüsselung wurde nicht geprüft, Vorgang wird trotzdem fortgesetzt. - + Tox datafile decryption password Entschlüsselungspasswort für Tox Datendatei - - - + + + Password error Passwortfehler - - + + Failed to setup password. Empty password. - Fehler beim Setzen des Passwortes. + Setzen des Passwortes fehlgeschlagen. Leeres Passwort. - + Try Again Nochmal versuchen - + Change profile Profil ändern - + Reinit current profile - Aktuelles Profil neu starten + Aktuelles Profil erneut starten - + Wrong password has been entered Es wurde ein falsches Passwort eingegeben - + History Log decryption password Passwort zur Entschlüsselung der Historie - + Encrypted log Verschlüsselte Logdatei - + Your history is encrypted with different password. Do you want to try another password? Ihre Historie wurde mit einem anderen Passwort verschlüsselt. -Wollen Sie ein anderes probieren? +Wollen Sie ein weiteres probieren? - + Due to incorret password history will be disabled. Falsches Passwort, Historie wird deaktiviert. - + + History + Historie + + + NO Password KEIN Passwort - + Will be saved without encryption! Wird ohne Verschlüsselung gespeichert! @@ -395,37 +368,29 @@ Wollen Sie ein anderes probieren? FileTransferInstance - + Save a file Title of the file saving dialog Datei speichern - + Location not writable Title of permissions popup Ort schreibgeschützt - + You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup Sie haben keine Erlaubnis, die Datei in diesen Ort zu speichern. Wählen Sie einen anderen Ort oder beenden Sie den Dialog. - + ETA ETA - - FileTransfertWidget - - Save a file - Title of the file saving dialog - Datei speichern - - FilesForm @@ -456,7 +421,7 @@ Wollen Sie ein anderes probieren? Someone wants to make friends with you - Es hat Ihnen jemand eine Freundschaftsanfrage gesendet + Jemand möchte Ihr Freund werden @@ -513,19 +478,16 @@ Wollen Sie ein anderes probieren? Wähle ein Verzeichnis für die automatische Dateiannahme - + User alias Benutzeralias - - Alias: - Alias: - - - Invite in group - Menu to invite a friend in a groupchat - In Gruppe einladen + + You can also set this by clicking the chat form name. +Alias: + Sie können diesen auch durch Klick auf den Namen des Chatfensters festlegen. +Alias: @@ -542,38 +504,30 @@ Wollen Sie ein anderes probieren? Allgemein - - + + None - Keine + Kein - + Choose an auto accept directory popup title Wählen Sie ein Verzeichnis - + Call active popup title Anwahl aktiviert - + You can't disconnect while a call is active! popup text Abbruch während der Anwahl nicht möglich! - - GeneralPage - - Enable IPv6 (recommended) - Text on a checkbox to enable IPv6 - IPv6 aktivieren (empfohlen) - - GeneralSettings @@ -601,7 +555,7 @@ Wollen Sie ein anderes probieren? Make Tox portable - Mache Tox portabel + Macht Tox portabel @@ -629,143 +583,154 @@ Wollen Sie ein anderes probieren? Ins Tray minimieren - + + Light icon + Helles Icon + + + Show contacts' status changes Zeigt Statusänderungen der Kontakte - + Check for updates on startup (unstable) - Beim Start auf Updates prüfen (nicht stabil) + Prüft beim Start auf Updates (nicht stabil) - + Focus qTox when a message is received Bringt qTox in den Vordergrund, wenn eine Nachricht eintrifft - + Faux offline messaging - + Imitiert Offline Benachrichtigung - + Provided in minutes Angabe in Minuten - + Auto away after (0 to disable) Automatisch abwesend nach (0 deaktiviert) - + Set to 0 to disable Zum Deaktivieren auf 0 setzen - + + You can set this on a per-friend basis by right clicking them. + autoaccept cb tooltip + Sie können dies durch Rechtsklick auf den jeweiligen Freund festlegen. + + + Use emoticons Emoticons benutzen - + Smiley Pack Text on smiley pack label Emoticon Paket - + Style Stil - + Theme color Farbe - + Emoticon size Emoticon Größe - + px Pixel - + Timestamp format Zeitformat - + Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip - Wenn deaktiviert, kann z.B. über Tor getoxt werden. Dies belastet das Tox Netzwerk zusätzlich und sollte nur deaktiviert werden wenn notwendig. + Wenn deaktiviert kann z.B. über Tor getoxt werden. Dies belastet das Tox Netzwerk zusätzlich und sollte nur deaktiviert werden wenn notwendig. - + Enable UDP (recommended) Text on checkbox to disable UDP UDP aktivieren (empfohlen) - + Reconnect reconnect button - Neu verbinden + Erneut verbinden - + minutes Minuten - + Autoaccept files Dateien automatisch annehmen - + Save files in Speichern unter - + PushButton Schaltfläche - + Theme Benutzeroberfläche - + Connection Settings Verbindungseinstellungen - + Enable IPv6 (recommended) Text on a checkbox to enable IPv6 IPv6 aktivieren (empfohlen) - + Use proxy (SOCKS5) Proxy benutzen (SOCKS5) - + Address Text on proxy addr label Adresse - + Port Text on proxy port label Port @@ -774,53 +739,53 @@ Wollen Sie ein anderes probieren? GenericChatForm - + Send message Nachricht senden - + Smileys Smileys - + Send file(s) Datei(en) senden - + Audio call: RED means you're on a call Sprachanruf: ROT bedeutet verbunden - + Video call: RED means you're on a call Videoanruf: ROT bedeutet verbunden - + Toggle speakers volume: RED is OFF Schaltet den Lautsprecher ein/aus: ROT ist AUS - + Toggle microphone: RED is OFF Schaltet das Mikrofon ein/aus: ROT ist AUS - - + + Save chat log Chatverlauf speichern - + Clear displayed messages Angezeigte Nachrichten ausblenden - + Cleared Ausgeblendet @@ -828,45 +793,54 @@ Wollen Sie ein anderes probieren? GroupChatForm - + %1 users in chat Number of users in chat %1 Teilnehmer im Chat - - <Unknown> - <Unbekannt> - %1 users in chat %1 Teilnehmer im Chat - - Save chat log - Chatverlauf speichern - GroupWidget - - + + %1 users in chat %1 Teilnehmer im Chat - - + + 0 users in chat kein Teilnehmer im Chat - + + Set title... + Titel festlegen... + + + Quit group Menu to quit a groupchat Gruppe verlassen + + + Group title + Gruppentitel + + + + You can also set this by clicking the chat form name. +Title: + Sie können diesen auch durch Klick auf den Namen des Chatfensters festlegen. +Titel: + IdentityForm @@ -920,7 +894,7 @@ Wollen Sie ein anderes probieren? Failed to remove file - Die Datei konnte nicht entfernt werden + Entfernen der Datei fehlgeschlagen @@ -930,7 +904,7 @@ Wollen Sie ein anderes probieren? Failed to copy file - Die Datei konnte nicht kopiert werden + Kopieren der Datei fehlgeschlagen @@ -953,68 +927,53 @@ Wollen Sie ein anderes probieren? Deletion imminent! deletion confirmation title - Löschen steht an! + Löschen steht bevor! - Are you sure you want to delete this profile? + Are you sure you want to delete this profile? +Associated friend information and chat logs will be deleted as well. deletion confirmation text - Sind Sie sicher, dass dieses Profil gelöscht werden soll? + Wollen Sie dieses Profil wirklich löschen? +Dazugehörige Informationen und Chatprotokolle werden ebenfalls gelöscht. - + Import profile import dialog title Profil importieren - + Tox save file (*.tox) import dialog filter Toxdatei speichern (*.tox) - + Ignoring non-Tox file popup title Keine Toxdatei, wird ignoriert - + Warning: you've chosen a file that is not a Tox save file; ignoring. popup text Warnung: Sie haben eine Datei gewählt, die keine Toxdatei ist, wird ignoriert. - + Profile already exists import confirm title Profil existiert bereits - + A profile named "%1" already exists. Do you want to erase it? import confirm text Ein Profil mit dem Namen "%1" existiert bereits. Wollen Sie es löschen? - - IdentityPage - - Name - Username/nick - Benutzername - - - Status - Status message - Status - - - Tox ID - Tox ID - - IdentitySettings @@ -1143,17 +1102,17 @@ Wollen Sie ein anderes probieren? Add friends - Freund hinzufügen + Freunde hinzufügen Create a group chat - Gruppenchat erstellen + Gruppenchat anlegen View completed file transfers - Alle Dateiübertragungen anzeigen + Vollendete Dateiübertragungen anzeigen @@ -1165,10 +1124,6 @@ Wollen Sie ein anderes probieren? Close Schließen - - Ctrl+Q - Strg+Q - NetCamView @@ -1244,14 +1199,14 @@ Soll die alte Historiedatei gelöscht werden? QObject - + Update The title of a message box Update - - An update is available, do you want to download it now ? + + An update is available, do you want to download it now? It will be installed when qTox restarts. Ein Update steht zur Verfügung, soll es heruntergeladen werden? Es wird beim Neustart von qTox installiert. @@ -1261,6 +1216,16 @@ Es wird beim Neustart von qTox installiert. Tox URI to parse Tox URI parsen + + + Starts new instance and loads specified profile. + Startet eine neue Instanz und lädt das festgelegte Profil. + + + + profile + Profil + Default @@ -1327,14 +1292,6 @@ Es wird beim Neustart von qTox installiert. Lass uns toxen! - - SelfCamView - - Tox video test - Title of the window to test the video/webcam - Tox Video testen - - SetPasswordDialog @@ -1348,34 +1305,6 @@ Es wird beim Neustart von qTox installiert. Passworteingabe wiederholen - - SettingsForm - - User Settings - "Headline" of the window - Einstellungen - - - Name - Username/nick - Benutzername - - - Status - Status message - Status - - - Test video - Text on a button to test the video/webcam - Video testen - - - Enable IPv6 (recommended) - Text on a checkbox to enable IPv6 - IPv6 aktivieren (empfohlen) - - ToxDNS @@ -1438,7 +1367,7 @@ Es wird beim Neustart von qTox installiert. - Do you want to add %1 as a friend ? + Do you want to add %1 as a friend? Wollen Sie %1 als Freund hinzufügen? @@ -1467,181 +1396,161 @@ Es wird beim Neustart von qTox installiert. Widget - Tox - Tox - - - Your name - Ihr Name - - - Your status - Ihr Status - - - Close - Schließen - - - Ctrl+Q - Strg+Q - - - + Online Online - + Away Abwesend - + Busy Beschäftigt - + &Quit &Beenden - + Change status to: - Ändern Sie den Status zu: + Ändert den Status in: - + Online Button to set your status to 'Online' Online - + Away Button to set your status to 'Away' Abwesend - + Busy Button to set your status to 'Busy' Beschäftigt - + Choose a profile Wählen Sie ein Profil - + Please choose which identity to use Wählen Sie die Identität, die benutzt werden soll - + Choose a profile picture - Wähle ein Profilbild + Wählen Sie ein Profilbild - - + + Error Fehler - + Unable to open this file Kann diese Datei nicht öffnen - + Unable to read this image Kann dieses Bild nicht einlesen - + This image is too big Dieses Bild ist zu groß - + Toxcore failed to start, the application will terminate after you close this message. Tox startet nicht, die Anwendung wird nach Schließen dieses Fensters beendet. - + toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text - Tox startet mit ihren Proxy-Einstellungen nicht. qTox funktioniert nicht, ändern Sie bitte Ihre Einstellungen und starten qTox neu. + Tox startet nicht mit ihren Proxy-Einstellungen. qTox funktioniert nicht, ändern Sie bitte Ihre Einstellungen und starten qTox neu. - + Add friend Freund hinzufügen - + File transfers Dateiübertragungen - + Settings Einstellungen - + Couldn't request friendship Freundschaftsanfrage fehlgeschlagen - + away contact status Abwesend - + busy contact status Beschäftigt - + offline contact status Offline - + online contact status Online - + %1 is now %2 e.g. "Dubslow is now online" %1 ist nun %2 - + <Unknown> Placeholder when we don't know someone's name in a group chat <Unbekannt> - + %1 has set the title to %2 %1 hat den Titel auf %2 gesetzt - + Message failed to send Senden der Nachricht fehlgeschlagen - \ No newline at end of file + From a14da15d79530faf42df495f8b1b70b5be8a96c8 Mon Sep 17 00:00:00 2001 From: apprb Date: Sun, 28 Dec 2014 18:32:25 +0600 Subject: [PATCH 248/253] fix #963 --- src/core.cpp | 16 ++-- src/misc/settings.cpp | 16 ++-- src/misc/settings.h | 8 +- src/widget/form/addfriendform.cpp | 2 +- src/widget/form/settings/generalform.cpp | 12 +-- src/widget/form/settings/generalsettings.ui | 92 ++++++++++++++------- 6 files changed, 94 insertions(+), 52 deletions(-) diff --git a/src/core.cpp b/src/core.cpp index 637436fb2..fa098e0e3 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -121,7 +121,7 @@ void Core::make_tox() // IPv6 needed for LAN discovery, but can crash some weird routers. On by default, can be disabled in options. bool enableIPv6 = Settings::getInstance().getEnableIPv6(); bool forceTCP = Settings::getInstance().getForceTCP(); - bool useProxy = Settings::getInstance().getUseProxy(); + ProxyType proxyType = Settings::getInstance().getProxyType(); if (enableIPv6) qDebug() << "Core starting with IPv6 enabled"; @@ -133,11 +133,11 @@ void Core::make_tox() toxOptions.udp_disabled = forceTCP; // No proxy by default - toxOptions.proxy_enabled = false; + toxOptions.proxy_type = TOX_PROXY_NONE; toxOptions.proxy_address[0] = 0; toxOptions.proxy_port = 0; - if (useProxy) + if (proxyType != ProxyType::ptNone) { QString proxyAddr = Settings::getInstance().getProxyAddr(); int proxyPort = Settings::getInstance().getProxyPort(); @@ -149,7 +149,11 @@ void Core::make_tox() else if (proxyAddr != "" && proxyPort > 0) { qDebug() << "Core: using proxy" << proxyAddr << ":" << proxyPort; - toxOptions.proxy_enabled = true; + // protection against changings in TOX_PROXY_TYPE enum + if (proxyType == ProxyType::ptSOCKS5) + toxOptions.proxy_type = TOX_PROXY_SOCKS5; + else if (proxyType == ProxyType::ptHTTP) + toxOptions.proxy_type = TOX_PROXY_HTTP; uint16_t sz = CString::fromString(proxyAddr, (unsigned char*)toxOptions.proxy_address); toxOptions.proxy_address[sz] = 0; toxOptions.proxy_port = proxyPort; @@ -165,7 +169,7 @@ void Core::make_tox() tox = tox_new(&toxOptions); if (tox == nullptr) { - if (toxOptions.proxy_enabled) + if (toxOptions.proxy_type != TOX_PROXY_NONE) { //QMessageBox::critical(Widget::getInstance(), tr("Proxy failure", "popup title"), //tr("toxcore failed to start with your proxy settings. qTox cannot run; please modify your " @@ -183,7 +187,7 @@ void Core::make_tox() else qWarning() << "Core failed to start with IPv6, falling back to IPv4. LAN discovery may not work properly."; } - else if (toxOptions.proxy_enabled) + else if (toxOptions.proxy_type != TOX_PROXY_NONE) { emit badProxy(); return; diff --git a/src/misc/settings.cpp b/src/misc/settings.cpp index ad2c9fbb3..20f9121a5 100644 --- a/src/misc/settings.cpp +++ b/src/misc/settings.cpp @@ -130,7 +130,7 @@ void Settings::load() autostartInTray = s.value("autostartInTray", false).toBool(); closeToTray = s.value("closeToTray", false).toBool(); forceTCP = s.value("forceTCP", false).toBool(); - useProxy = s.value("useProxy", false).toBool(); + setProxyType(s.value("proxyType", 0).toInt()); proxyAddr = s.value("proxyAddr", "").toString(); proxyPort = s.value("proxyPort", 0).toInt(); currentProfile = s.value("currentProfile", "").toString(); @@ -282,7 +282,7 @@ void Settings::save(QString path, bool writeFriends) s.setValue("showSystemTray", showSystemTray); s.setValue("autostartInTray",autostartInTray); s.setValue("closeToTray", closeToTray); - s.setValue("useProxy", useProxy); + s.setValue("proxyType", static_cast(proxyType)); s.setValue("forceTCP", forceTCP); s.setValue("proxyAddr", proxyAddr); s.setValue("proxyPort", proxyPort); @@ -584,13 +584,17 @@ void Settings::setForceTCP(bool newValue) forceTCP = newValue; } -bool Settings::getUseProxy() const +ProxyType Settings::getProxyType() const { - return useProxy; + return proxyType; } -void Settings::setUseProxy(bool newValue) + +void Settings::setProxyType(int newValue) { - useProxy = newValue; + if (newValue >= 0 && newValue <= 3) + proxyType = static_cast(newValue); + else + proxyType = ProxyType::ptNone; } QString Settings::getProxyAddr() const diff --git a/src/misc/settings.h b/src/misc/settings.h index 64ff6009e..d05be0a03 100644 --- a/src/misc/settings.h +++ b/src/misc/settings.h @@ -24,6 +24,8 @@ struct ToxID; namespace Db { enum class syncType; } +enum ProxyType {ptNone, ptSOCKS5, ptHTTP}; + class Settings : public QObject { Q_OBJECT @@ -89,8 +91,8 @@ public: QString getProxyAddr() const; void setProxyAddr(const QString& newValue); - bool getUseProxy() const; - void setUseProxy(bool newValue); + ProxyType getProxyType() const; + void setProxyType(int newValue); int getProxyPort() const; void setProxyPort(int newValue); @@ -257,7 +259,7 @@ private: bool forceTCP; - bool useProxy; + ProxyType proxyType; QString proxyAddr; int proxyPort; diff --git a/src/widget/form/addfriendform.cpp b/src/widget/form/addfriendform.cpp index e0623efa7..efacf17e8 100644 --- a/src/widget/form/addfriendform.cpp +++ b/src/widget/form/addfriendform.cpp @@ -95,7 +95,7 @@ void AddFriendForm::onSendTriggered() this->toxId.clear(); this->message.clear(); } else { - if (Settings::getInstance().getUseProxy()) + if (Settings::getInstance().getProxyType() != ProxyType::ptNone) { QMessageBox::StandardButton btn = QMessageBox::warning(main, "qTox", tr("qTox needs to use the Tox DNS, but can't do it through a proxy.\n\ Ignore the proxy and connect to the Internet directly?"), QMessageBox::Ok|QMessageBox::No, QMessageBox::No); diff --git a/src/widget/form/settings/generalform.cpp b/src/widget/form/settings/generalform.cpp index 64efcbdf5..cf5555a68 100644 --- a/src/widget/form/settings/generalform.cpp +++ b/src/widget/form/settings/generalform.cpp @@ -111,7 +111,7 @@ GeneralForm::GeneralForm(SettingsWidget *myParent) : if (port != -1) bodyUI->proxyPort->setValue(port); - bodyUI->cbUseProxy->setChecked(Settings::getInstance().getUseProxy()); + bodyUI->proxyType->setCurrentIndex(static_cast(Settings::getInstance().getProxyType())); onUseProxyUpdated(); //general @@ -139,7 +139,7 @@ GeneralForm::GeneralForm(SettingsWidget *myParent) : //connection connect(bodyUI->cbEnableIPv6, &QCheckBox::stateChanged, this, &GeneralForm::onEnableIPv6Updated); connect(bodyUI->cbEnableUDP, &QCheckBox::stateChanged, this, &GeneralForm::onUDPUpdated); - connect(bodyUI->cbUseProxy, &QCheckBox::stateChanged, this, &GeneralForm::onUseProxyUpdated); + connect(bodyUI->proxyType, SIGNAL(currentIndexChanged(int)), this, SLOT(onUseProxyUpdated())); connect(bodyUI->proxyAddr, &QLineEdit::editingFinished, this, &GeneralForm::onProxyAddrEdited); connect(bodyUI->proxyPort, SIGNAL(valueChanged(int)), this, SLOT(onProxyPortEdited(int))); connect(bodyUI->reconnectButton, &QPushButton::clicked, this, &GeneralForm::onReconnectClicked); @@ -286,11 +286,11 @@ void GeneralForm::onProxyPortEdited(int port) void GeneralForm::onUseProxyUpdated() { - bool state = bodyUI->cbUseProxy->isChecked(); + int proxytype = bodyUI->proxyType->currentIndex(); - bodyUI->proxyAddr->setEnabled(state); - bodyUI->proxyPort->setEnabled(state); - Settings::getInstance().setUseProxy(state); + bodyUI->proxyAddr->setEnabled(proxytype); + bodyUI->proxyPort->setEnabled(proxytype); + Settings::getInstance().setProxyType(proxytype); } void GeneralForm::onReconnectClicked() diff --git a/src/widget/form/settings/generalsettings.ui b/src/widget/form/settings/generalsettings.ui index 47258e049..98dff8bef 100644 --- a/src/widget/form/settings/generalsettings.ui +++ b/src/widget/form/settings/generalsettings.ui @@ -38,12 +38,12 @@ 0 - 0 - 513 - 819 + -173 + 511 + 797 - + 9 @@ -216,6 +216,9 @@ Set to 0 to disable + + true + minutes @@ -225,9 +228,6 @@ 2147483647 - - true - @@ -294,6 +294,9 @@ + + QFormLayout::AllNonFixedFieldsGrow + 0 @@ -317,23 +320,6 @@ - - - - Style - - - - - - - - 0 - 0 - - - - @@ -391,6 +377,23 @@ + + + + Style + + + + + + + + 0 + 0 + + + + @@ -462,7 +465,7 @@ - + Connection Settings @@ -492,11 +495,40 @@ - - - Use proxy (SOCKS5) - - + + + + + Proxy type + + + + + + + + 0 + 0 + + + + + Direct Connect + + + + + SOCKS5 + + + + + HTTP + + + + + From 1719c72f670bc966114e332ff01ab2d1c0021c0c Mon Sep 17 00:00:00 2001 From: apprb Date: Sun, 28 Dec 2014 19:04:32 +0600 Subject: [PATCH 249/253] tiny polishing --- src/misc/settings.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/misc/settings.cpp b/src/misc/settings.cpp index 20f9121a5..1f0e8a0c8 100644 --- a/src/misc/settings.cpp +++ b/src/misc/settings.cpp @@ -130,7 +130,7 @@ void Settings::load() autostartInTray = s.value("autostartInTray", false).toBool(); closeToTray = s.value("closeToTray", false).toBool(); forceTCP = s.value("forceTCP", false).toBool(); - setProxyType(s.value("proxyType", 0).toInt()); + setProxyType(s.value("proxyType", static_cast(ProxyType::ptNone)).toInt()); proxyAddr = s.value("proxyAddr", "").toString(); proxyPort = s.value("proxyPort", 0).toInt(); currentProfile = s.value("currentProfile", "").toString(); @@ -591,7 +591,7 @@ ProxyType Settings::getProxyType() const void Settings::setProxyType(int newValue) { - if (newValue >= 0 && newValue <= 3) + if (newValue >= 0 && newValue <= 2) proxyType = static_cast(newValue); else proxyType = ProxyType::ptNone; From f8bdaac12120ad20f854d06bb7cb5e8771cbe827 Mon Sep 17 00:00:00 2001 From: apprb Date: Sun, 28 Dec 2014 23:26:35 +0600 Subject: [PATCH 250/253] messages splitting fix --- src/core.cpp | 14 ++++++++------ src/core.h | 2 +- src/widget/form/chatform.cpp | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/core.cpp b/src/core.cpp index 637436fb2..ba18b0aeb 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -49,6 +49,8 @@ QList Core::fileRecvQueue; QHash Core::groupCalls; QThread* Core::coreThread{nullptr}; +#define MAX_GROUP_MESSAGE_LEN 1024 + Core::Core(Camera* cam, QThread *CoreThread, QString loadPath) : tox(nullptr), camera(cam), loadPath(loadPath), ready{false} { @@ -779,7 +781,7 @@ void Core::sendTyping(int friendId, bool typing) void Core::sendGroupMessage(int groupId, const QString& message) { - QList cMessages = splitMessage(message); + QList cMessages = splitMessage(message, MAX_GROUP_MESSAGE_LEN); for (auto &cMsg :cMessages) { @@ -792,7 +794,7 @@ void Core::sendGroupMessage(int groupId, const QString& message) void Core::sendGroupAction(int groupId, const QString& message) { - QList cMessages = splitMessage(message); + QList cMessages = splitMessage(message, MAX_GROUP_MESSAGE_LEN); for (auto &cMsg :cMessages) { @@ -1741,17 +1743,17 @@ QString Core::getFriendUsername(int friendnumber) const return CString::toString(name, tox_get_name_size(tox, friendnumber)); } -QList Core::splitMessage(const QString &message) +QList Core::splitMessage(const QString &message, int maxLen) { QList splittedMsgs; QByteArray ba_message(message.toUtf8()); - while (ba_message.size() > TOX_MAX_MESSAGE_LENGTH) + while (ba_message.size() > maxLen) { - int splitPos = ba_message.lastIndexOf(' ', TOX_MAX_MESSAGE_LENGTH - 1); + int splitPos = ba_message.lastIndexOf(' ', maxLen - 1); if (splitPos <= 0) { - splitPos = TOX_MAX_MESSAGE_LENGTH; + splitPos = maxLen; if (ba_message[splitPos] & 0x80) { do { diff --git a/src/core.h b/src/core.h index ffa4ba0c9..5f55a73cf 100644 --- a/src/core.h +++ b/src/core.h @@ -50,7 +50,7 @@ public: static const QString TOX_EXT; static const QString CONFIG_FILE_NAME; static QString sanitize(QString name); - static QList splitMessage(const QString &message); + static QList splitMessage(const QString &message, int maxLen); QString getPeerName(const ToxID& id) const; diff --git a/src/widget/form/chatform.cpp b/src/widget/form/chatform.cpp index 4f2c73998..cc2ebe580 100644 --- a/src/widget/form/chatform.cpp +++ b/src/widget/form/chatform.cpp @@ -103,7 +103,7 @@ void ChatForm::onSendTriggered() if (isAction) msg = msg = msg.right(msg.length() - 4); - QList splittedMsg = Core::splitMessage(msg); + QList splittedMsg = Core::splitMessage(msg, TOX_MAX_MESSAGE_LENGTH); QDateTime timestamp = QDateTime::currentDateTime(); for (CString& c_msg : splittedMsg) From 1194df2f7846daf159f8b321d19fe96d2c7c3977 Mon Sep 17 00:00:00 2001 From: apprb Date: Tue, 30 Dec 2014 22:02:26 +0600 Subject: [PATCH 251/253] renaming: Direct Connect -> None --- src/widget/form/settings/generalsettings.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widget/form/settings/generalsettings.ui b/src/widget/form/settings/generalsettings.ui index 98dff8bef..762b39447 100644 --- a/src/widget/form/settings/generalsettings.ui +++ b/src/widget/form/settings/generalsettings.ui @@ -513,7 +513,7 @@ - Direct Connect + None From 956f680eed0d22b9da690236c5c0297595fe09ee Mon Sep 17 00:00:00 2001 From: Ansa89 Date: Wed, 31 Dec 2014 09:47:49 +0100 Subject: [PATCH 252/253] Italian translation: update --- translations/it.ts | 164 ++++++++++++++++++++++++--------------------- 1 file changed, 87 insertions(+), 77 deletions(-) diff --git a/translations/it.ts b/translations/it.ts index 77c63f5d9..0d76e67b6 100644 --- a/translations/it.ts +++ b/translations/it.ts @@ -263,105 +263,105 @@ Ignorare le impostazioni del proxy e connettersi direttamente alla rete Tox? Core - + Toxing on qTox Toxing on qTox - + qTox User qTox User - + Friend is already added Questo contatto è già presente nella tua lista - + Encryption error Errore crittografia - + The .tox file is encrypted, but encryption was not checked, continuing regardless. Il Tox datafile è criptato, ma la crittografia non è abilitata nelle impostazioni. Continuo ignorando le impostazioni. - + Tox datafile decryption password Password per decriptare il Tox datafile - - - + + + Password error Errore password - - + + Failed to setup password. Empty password. Impossibile impostare la password. Password vuota. - + Try Again Riprova - + Change profile Cambia profilo - + Reinit current profile Reinizializza il profilo corrente - + Wrong password has been entered È stata inserita una password sbagliata - + History Log decryption password Password per decriptare i log - + Encrypted log Log criptato - + Your history is encrypted with different password. Do you want to try another password? I log delle chat sono criptati con una password diversa. Vuoi provare ad inserire un'altra password? - + History Chat Log - + Due to incorret password history will be disabled. Password errata, i log delle chat non saranno caricati. - + NO Password Nessuna password - + Will be saved without encryption! Il Tox datafile sarà salvato senza password! @@ -558,12 +558,12 @@ Soprannome: Imposta 0 per disabilitare - + Connection Settings Impostazioni Connessione - + Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Abilita IPv6 (consigliato) @@ -635,7 +635,7 @@ Soprannome: Imposta assenza dopo - + minutes minuti @@ -671,67 +671,82 @@ Soprannome: Usa emoticons - + Smiley Pack Text on smiley pack label Emoticons - + Style Stile - + Theme color Colore - + Emoticon size Dimensione emoticons - + px px - + Timestamp format Formato data/ora - + Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Disabilitando questo sarà possibile usare qTox con Tor. Tuttavia verrà aggiunto carico alla rete Tox, quindi disabilitare solo se necessario. - + Enable UDP (recommended) Text on checkbox to disable UDP Abilita UDP (consigliato) - - Use proxy (SOCKS5) - Usa proxy (SOCKS5) + + Proxy type + Proxy - + + None + Nessuno + + + + SOCKS5 + SOCKS 5 + + + + HTTP + HTTP + + + Address Text on proxy addr label IP - + Port Text on proxy port label Porta - + Reconnect reconnect button Riconnetti @@ -740,37 +755,37 @@ Soprannome: GenericChatForm - + Send message Invia messaggio - + Smileys Emoticons - + Send file(s) Invia file(s) - + Audio call: RED means you're on a call Chiamata audio: ROSSO significa che la chiamata è in corso - + Video call: RED means you're on a call Videochiamata: ROSSO significa che la chiamata è in corso - + Toggle speakers volume: RED is OFF Imposta volume altoparlanti: ROSSO è SPENTO - + Toggle microphone: RED is OFF Imposta microfono: ROSSO è SPENTO @@ -1417,139 +1432,134 @@ Verrà installata al riavvio del programma. &Esci - - Change status to: - Cambia stato in: - - - + Online Button to set your status to 'Online' Online - + Away Button to set your status to 'Away' Assente - + Busy Button to set your status to 'Busy' Occupato - + Choose a profile Scegli un profilo - + Please choose which identity to use Per favore scegli quale identità usare - + Choose a profile picture Scegli un'immagine per il profilo - - - + + + Error Errore - + Unable to open this file Impossibile aprire il file - + Unable to read this image Impossibile leggere l'immagine - + This image is too big L'immagine è troppo grande - + Toxcore failed to start, the application will terminate after you close this message. Impossibile avviare Toxcore.\nqTox terminerà dopo che avrai chiuso questo messaggio. - + toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text Impossibile avviare Toxcore con le tue impostazione proxy.\nqTox non può funzionare correttamente, per favore modifica le impostazioni e riavvia il programma. - + Add friend Aggiungi contatto - + File transfers Files trasferiti - + Settings Impostazioni - + Couldn't request friendship Impossibile inviare la richiesta d'amicizia - + away contact status assente - + busy contact status occupato - + offline contact status offline - + online contact status online - + %1 is now %2 e.g. "Dubslow is now online" %1 è ora %2 - + <Unknown> Placeholder when we don't know someone's name in a group chat <Sconosciuto> - + %1 has set the title to %2 %1 ha impostato il titolo in %2 - + Message failed to send Impossibile inviare il messaggio From 86c672b8d7be356c26159f6eb13f7ec08cc4684e Mon Sep 17 00:00:00 2001 From: Zetok Zalbavar Date: Mon, 22 Dec 2014 10:50:08 +0000 Subject: [PATCH 253/253] Update INSTALL.md --- INSTALL.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/INSTALL.md b/INSTALL.md index 63cefe8a1..b03f17c7e 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -14,6 +14,7 @@ | Tox Core | most recent | core, av | | OpenCV | >= 2.4.9 | core, highgui, imgproc | | OpenAL Soft | >= 1.16.0 | | +| filter_audio | most recent | | @@ -127,6 +128,12 @@ cd /home/user/qTox ./bootstrap.sh # use -h or --help for more information ``` +###filter_audio +You also need to install filter_audio library separately if you did not run ``./bootstrap.sh``. +```bash +./install_libfilteraudio.sh +``` + After all the dependencies are thus reeady to go, compiling should be as simple as ```bash qmake