From 88b2fd89ff0bc4be81acd8f6d5751dba96e295f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20=27sil2100=27=20Zemczak?= Date: Tue, 20 Aug 2013 10:53:01 +0200 Subject: Revert rev 352 as it is causing a SIGTRAP on start --- COPYING.GPLv3 | 674 --------- Makefile.am | 1 - autogen.sh | 14 +- common/Makefile.am | 32 - ...om.canonical.indicator.messages.application.xml | 39 - .../com.canonical.indicator.messages.service.xml | 28 - configure.ac | 43 +- data/Makefile.am | 5 +- data/com.canonical.indicator.messages | 10 - debian/changelog | 97 -- debian/control | 14 +- debian/indicator-messages.install | 2 +- debian/libmessaging-menu-dev.install | 1 - debian/libmessaging-menu0.symbols | 15 - debian/source/format | 1 - doc/reference/Makefile.am | 3 +- doc/reference/messaging-menu-docs.xml.in | 3 +- libmessaging-menu/Makefile.am | 55 +- libmessaging-menu/gtupleaction.c | 354 +++++ libmessaging-menu/gtupleaction.h | 40 + libmessaging-menu/messaging-menu-app.c | 1433 -------------------- libmessaging-menu/messaging-menu-app.h | 163 --- libmessaging-menu/messaging-menu-message.c | 547 -------- libmessaging-menu/messaging-menu-message.h | 70 - libmessaging-menu/messaging-menu.c | 1200 ++++++++++++++++ libmessaging-menu/messaging-menu.h | 125 +- po/POTFILES.in | 14 +- po/indicator-messages.pot | 46 + src/Makefile.am | 71 +- src/app-section.c | 434 +++--- src/dbus-data.h | 17 +- src/gactionmuxer.c | 8 - src/gactionmuxer.h | 3 - src/gmenuutils.c | 2 +- src/ido-detail-label.c | 401 ++++++ src/ido-detail-label.h | 59 + src/ido-menu-item.c | 439 ++++++ src/ido-menu-item.h | 54 + src/im-app-menu-item.c | 354 +++++ src/im-app-menu-item.h | 54 + src/im-application-list.c | 1139 ---------------- src/im-application-list.h | 62 - src/im-desktop-menu.c | 294 ---- src/im-desktop-menu.h | 35 - src/im-menu.c | 191 --- src/im-menu.h | 64 - src/im-phone-menu.c | 317 ----- src/im-phone-menu.h | 68 - src/im-source-menu-item.c | 407 ++++++ src/im-source-menu-item.h | 54 + src/indicator-messages.c | 382 ++++++ src/messages-service.c | 617 +++++++-- src/messages-service.xml | 28 + test/Makefile.am | 5 +- test/indicator-messages-service-activate.c | 4 - test/test-gactionmuxer.cpp | 34 - 56 files changed, 4829 insertions(+), 5797 deletions(-) delete mode 100644 COPYING.GPLv3 delete mode 100644 common/Makefile.am delete mode 100644 common/com.canonical.indicator.messages.application.xml delete mode 100644 common/com.canonical.indicator.messages.service.xml delete mode 100644 data/com.canonical.indicator.messages delete mode 100644 debian/source/format create mode 100644 libmessaging-menu/gtupleaction.c create mode 100644 libmessaging-menu/gtupleaction.h delete mode 100644 libmessaging-menu/messaging-menu-app.c delete mode 100644 libmessaging-menu/messaging-menu-app.h delete mode 100644 libmessaging-menu/messaging-menu-message.c delete mode 100644 libmessaging-menu/messaging-menu-message.h create mode 100644 libmessaging-menu/messaging-menu.c create mode 100644 po/indicator-messages.pot create mode 100644 src/ido-detail-label.c create mode 100644 src/ido-detail-label.h create mode 100644 src/ido-menu-item.c create mode 100644 src/ido-menu-item.h create mode 100644 src/im-app-menu-item.c create mode 100644 src/im-app-menu-item.h delete mode 100644 src/im-application-list.c delete mode 100644 src/im-application-list.h delete mode 100644 src/im-desktop-menu.c delete mode 100644 src/im-desktop-menu.h delete mode 100644 src/im-menu.c delete mode 100644 src/im-menu.h delete mode 100644 src/im-phone-menu.c delete mode 100644 src/im-phone-menu.h create mode 100644 src/im-source-menu-item.c create mode 100644 src/im-source-menu-item.h create mode 100644 src/indicator-messages.c create mode 100644 src/messages-service.xml diff --git a/COPYING.GPLv3 b/COPYING.GPLv3 deleted file mode 100644 index 94a9ed0..0000000 --- a/COPYING.GPLv3 +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - 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 . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/Makefile.am b/Makefile.am index f8141a8..daeb2b7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,6 +1,5 @@ SUBDIRS = \ - common \ src \ libmessaging-menu \ data \ diff --git a/autogen.sh b/autogen.sh index 1abf3a7..ab2f513 100755 --- a/autogen.sh +++ b/autogen.sh @@ -1,11 +1,5 @@ -#!/bin/sh +#!/bin/sh -e -PKG_NAME="indicator-messages" - -which gnome-autogen.sh || { - echo "You need gnome-common from GNOME SVN" - exit 1 -} - -USE_GNOME2_MACROS=1 \ -. gnome-autogen.sh +gtkdocize +autoreconf -i -f +intltoolize diff --git a/common/Makefile.am b/common/Makefile.am deleted file mode 100644 index 0b8bad4..0000000 --- a/common/Makefile.am +++ /dev/null @@ -1,32 +0,0 @@ - -noinst_LTLIBRARIES = libmessaging-common.la - -indicator-messages-service.c: com.canonical.indicator.messages.service.xml - $(AM_V_GEN) gdbus-codegen \ - --interface-prefix com.canonical.indicator.messages. \ - --generate-c-code indicator-messages-service \ - --c-namespace IndicatorMessages \ - $^ -indicator-messages-service.h: indicator-messages-service.c - -indicator-messages-application.c: com.canonical.indicator.messages.application.xml - $(AM_V_GEN) gdbus-codegen \ - --interface-prefix com.canonical.indicator.messages. \ - --generate-c-code indicator-messages-application \ - --c-namespace IndicatorMessages \ - $^ -indicator-messages-application.h: indicator-messages-application.c - -BUILT_SOURCES = \ - indicator-messages-service.c \ - indicator-messages-service.h \ - indicator-messages-application.c \ - indicator-messages-application.h - -libmessaging_common_la_SOURCES = \ - $(BUILT_SOURCES) - -libmessaging_common_la_CFLAGS = $(GIO_CFLAGS) -libmessaging_common_la_LIBADD = $(GIO_LIBS) - -CLEANFILES = $(BUILT_SOURCES) diff --git a/common/com.canonical.indicator.messages.application.xml b/common/com.canonical.indicator.messages.application.xml deleted file mode 100644 index 6f038e6..0000000 --- a/common/com.canonical.indicator.messages.application.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/common/com.canonical.indicator.messages.service.xml b/common/com.canonical.indicator.messages.service.xml deleted file mode 100644 index 3c3c779..0000000 --- a/common/com.canonical.indicator.messages.service.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/configure.ac b/configure.ac index e67b66a..08ac680 100644 --- a/configure.ac +++ b/configure.ac @@ -61,12 +61,6 @@ GTK_DOC_CHECK([1.18], [--flavour no-tmpl]) GOBJECT_INTROSPECTION_CHECK([$INTROSPECTION_REQUIRED_VERSION]) -AC_ARG_WITH([indicator-dir], - [AS_HELP_STRING([--with-indicator-dir=DIR], [Indicator directory [default=$datadir/unity/indicators]])], - [], - [with_indicator_dir=$datadir/unity/indicators]) -AC_SUBST([INDICATOR_DIR], [$with_indicator_dir]) - ########################### # gcov coverage reporting ########################### @@ -106,20 +100,33 @@ fi AM_CONDITIONAL([BUILD_TESTS],[test "x$enable_tests" = "xyes"]) ########################### -# Vala API Generation +# Check to see if we're local ########################### -AC_ARG_ENABLE([vala], - AC_HELP_STRING([--disable-vala], [Disable vala]), - [enable_vala=$enableval], [enable_vala=yes]) +with_localinstall="no" +AC_ARG_ENABLE(localinstall, AS_HELP_STRING([--enable-localinstall], [install all of the files localy instead of system directories (for distcheck)]), with_localinstall=$enableval, with_localinstall=no) -AS_IF([test "x$enable_vala" != "xno"],[ - AM_COND_IF([HAVE_INTROSPECTION],,[ - AC_MSG_ERROR([Vala bindings require introspection support, please --enable-introspection]) - ]) -AC_PATH_PROG([VALA_API_GEN], [vapigen]) -]) -AM_CONDITIONAL([HAVE_VALA], [test -n "$VALA_API_GEN"]) +########################### +# Indicator Info +########################### + +if test "x$with_localinstall" = "xyes"; then + INDICATORDIR="${libdir}/indicators/2/" +else + INDICATORDIR=`$PKG_CONFIG --variable=indicatordir indicator3-0.4` +fi +AC_SUBST(INDICATORDIR) + +########################### +# DBus Service Info +########################### + +if test "x$with_localinstall" = "xyes"; then + DBUSSERVICEDIR="${datadir}/dbus-1/services/" +else + DBUSSERVICEDIR=`$PKG_CONFIG --variable=session_bus_services_dir dbus-1` +fi +AC_SUBST(DBUSSERVICEDIR) ############################## # Custom Junk @@ -155,7 +162,6 @@ AM_GLIB_GNU_GETTEXT AC_OUTPUT([ Makefile src/Makefile -common/Makefile data/Makefile data/icons/Makefile data/icons/16x16/Makefile @@ -196,6 +202,5 @@ Messaging Indicator Configuration: tests: $enable_tests gcov: $use_gcov introspecion: $enable_introspection - Vala bindings: $enable_vala documentation: $enable_gtk_doc ]) diff --git a/data/Makefile.am b/data/Makefile.am index 45ff12f..488ce05 100644 --- a/data/Makefile.am +++ b/data/Makefile.am @@ -1,6 +1,6 @@ SUBDIRS = icons -dbus_servicesdir = $(datadir)/dbus-1/services +dbus_servicesdir = $(DBUSSERVICEDIR) dbus_services_DATA = indicator-messages.service %.service: %.service.in @@ -9,9 +9,6 @@ dbus_services_DATA = indicator-messages.service gsettings_SCHEMAS = com.canonical.indicator.messages.gschema.xml @GSETTINGS_RULES@ -indicatordir = $(INDICATOR_DIR) -dist_indicator_DATA = com.canonical.indicator.messages - EXTRA_DIST = \ $(gsettings_SCHEMAS) \ indicator-messages.service.in diff --git a/data/com.canonical.indicator.messages b/data/com.canonical.indicator.messages deleted file mode 100644 index fa0ac50..0000000 --- a/data/com.canonical.indicator.messages +++ /dev/null @@ -1,10 +0,0 @@ -[Indicator Service] -Name=indicator-messages -ObjectPath=/com/canonical/indicator/messages -Position=50 - -[desktop] -ObjectPath=/com/canonical/indicator/messages/desktop - -[phone] -ObjectPath=/com/canonical/indicator/messages/phone diff --git a/debian/changelog b/debian/changelog index a6fe645..3df5f41 100644 --- a/debian/changelog +++ b/debian/changelog @@ -9,12 +9,6 @@ indicator-messages (13.10.1+13.10.20130820-0ubuntu1) saucy; urgency=low -- Ubuntu daily release Tue, 20 Aug 2013 02:06:40 +0000 -indicator-messages (13.10.0phablet1) raring; urgency=low - - * Version bump to not pull from archives - - -- Sergio Schvezov Fri, 26 Apr 2013 13:53:53 -0300 - indicator-messages (12.10.6+13.10.20130702-0ubuntu1) saucy; urgency=low [ Mathias Dietrich ] @@ -25,12 +19,6 @@ indicator-messages (12.10.6+13.10.20130702-0ubuntu1) saucy; urgency=low -- Ubuntu daily release Tue, 02 Jul 2013 02:02:36 +0000 -indicator-messages (12.10.6phablet1) quantal; urgency=low - - * Adding guards for g_type_init - - -- Sergio Schvezov Fri, 22 Mar 2013 17:23:45 -0300 - indicator-messages (12.10.6daily13.06.19-0ubuntu1) saucy; urgency=low [ Sebastien Bacher ] @@ -144,91 +132,6 @@ indicator-messages (12.10.6daily12.11.21.1-0ubuntu1) raring; urgency=low -- Automatic PS uploader Wed, 21 Nov 2012 10:41:37 +0000 -indicator-messages (12.10.6-0ubuntu1phablet9) quantal; urgency=low - - * add "remove-all" signal to imapplicationlist (temporarily) - - -- Lars Uebernickel Thu, 20 Dec 2012 18:49:50 +0100 - -indicator-messages (12.10.6-0ubuntu1phablet8) quantal; urgency=low - - * Make messaging_menu_app_remove_message() work for messages with a ref count of 1 - * Make handling of multiple processes with the same desktop id more robust - * ImPhoneMenu: sort messages by time (fixes LP #1090266) - * Don't show message sources - - -- Lars Uebernickel Mon, 17 Dec 2012 14:42:03 +0100 - -indicator-messages (12.10.6-0ubuntu1phablet7) quantal; urgency=low - - * Remove variant wrapper from 'parameter' argument of the "activate" signal - - -- Lars Uebernickel Tue, 11 Dec 2012 14:28:15 +0100 - -indicator-messages (12.10.6-0ubuntu1phablet6) quantal; urgency=low - - * Don't show sources - * Always use the "messageitem" widget type for messages - - -- Lars Uebernickel Mon, 10 Dec 2012 14:38:16 +0100 - -indicator-messages (12.10.6-0ubuntu1phablet5) quantal; urgency=low - - * Don't shorten the app id to seven characters (fixes LP #1086729) - * Add messaging_menu_app_get_message - - -- Lars Uebernickel Thu, 06 Dec 2012 15:06:34 +0000 - -indicator-messages (12.10.6-0ubuntu1phablet4) quantal; urgency=low - - [Lars Uebernickel] - * Add the concept of actions to messages - * Stop using IndicatorService - * Reverse order of messages - * Expose symbolic application icon - * Change icon when there are any messages in the menu - - -- Lars Uebernickel Tue, 04 Dec 2012 21:10:26 +0000 - -indicator-messages (12.10.6-0ubuntu1phablet3) quantal; urgency=low - - [Lars Uebernickel] - * expose root menu item of which the indicator menu is a submenu - * fix crash in im-application-list on arm - - -- Lars Uebernickel Thu, 29 Nov 2012 21:44:19 +0100 - -indicator-messages (12.10.6-0ubuntu1phablet2) quantal; urgency=low - - [Lars Uebernickel] - * refactor messages-service.c - * notify applications about message and source activations - * add "Clear All" menu item - * allow dismissing of messages - * include application icons on message items - - -- Lars Uebernickel Mon, 26 Nov 2012 22:19:51 +0100 - -indicator-messages (12.10.6-0ubuntu1phablet1) quantal; urgency=low - - [ Mathieu Trudel-Lapierre ] - * debian/rules: - - Use autogen.sh for dh_autoreconf. - - Drop the override for dh_makeshlibs. - - Add DPKG_GENSYMBOLS_CHECK_LEVEL=4. - * debian/control: - - Fix styling: add trailing commas at the end of lists. - - Update Vcs-Bzr, Vcs-Browser fields and add a notice for developers. - - Add libgtest-dev to Build-Depends. - * Automatic snapshot from revision 329 (bootstrap): - - Clear the detail (count or time) of a source when another type of detail - is set. (LP: #1071640) - - [ Ricardo Mendoza ] - * Releasing upstream for phablet - - -- Ricardo Mendoza Tue, 20 Nov 2012 10:08:59 -0430 - indicator-messages (12.10.5-0ubuntu2) raring; urgency=low * Upload to raring diff --git a/debian/control b/debian/control index 47d4c0e..7773476 100644 --- a/debian/control +++ b/debian/control @@ -7,19 +7,19 @@ Uploaders: Evgeni Golov Build-Depends: debhelper (>= 9), dh-autoreconf, dh-translations, - gobject-introspection (>= 0.9.12-4~), - gtk-doc-tools, - intltool, + libglib2.0-dev (>= 2.35.4), + libgtk-3-dev (>= 3.5.12), libdbus-glib-1-dev, + intltool, + libindicator3-dev, libdbusmenu-glib-dev (>= 0.5.90), libdbusmenu-gtk3-dev (>= 0.5.90), + libtelepathy-glib-dev (>= 0.9.0), + gobject-introspection (>= 0.9.12-4~), libgirepository1.0-dev (>= 0.9.12), + gtk-doc-tools, libgtest-dev, - libgtk-3-dev (>= 3.5.18), - libindicator3-dev, - libtelepathy-glib-dev (>= 0.9.0), python3-dbusmock, - valac, Standards-Version: 3.9.3 Homepage: https://launchpad.net/indicator-messages # If you aren't a member of ~indicator-applet-developers but need to upload diff --git a/debian/indicator-messages.install b/debian/indicator-messages.install index 39c40f5..c0f6e98 100644 --- a/debian/indicator-messages.install +++ b/debian/indicator-messages.install @@ -1,5 +1,5 @@ +usr/lib/indicators3 usr/lib/indicator-messages/indicator-messages-service usr/share/dbus-1 usr/share/glib-2.0 usr/share/icons -usr/share/unity/indicators diff --git a/debian/libmessaging-menu-dev.install b/debian/libmessaging-menu-dev.install index 53e0312..75ef089 100644 --- a/debian/libmessaging-menu-dev.install +++ b/debian/libmessaging-menu-dev.install @@ -2,5 +2,4 @@ usr/lib/libmessaging-menu.so usr/lib/pkgconfig usr/include /usr/ usr/share/gir-1.0/ -usr/share/vala/ usr/share/gtk-doc diff --git a/debian/libmessaging-menu0.symbols b/debian/libmessaging-menu0.symbols index 6a54911..6261a2a 100644 --- a/debian/libmessaging-menu0.symbols +++ b/debian/libmessaging-menu0.symbols @@ -1,11 +1,9 @@ libmessaging-menu.so.0 libmessaging-menu0 #MINVER# - messaging_menu_app_append_message@Base 12.10.6-0ubuntu1phablet1 messaging_menu_app_append_source@Base 12.10.0 messaging_menu_app_append_source_with_count@Base 12.10.0 messaging_menu_app_append_source_with_string@Base 12.10.0 messaging_menu_app_append_source_with_time@Base 12.10.0 messaging_menu_app_draw_attention@Base 12.10.0 - messaging_menu_app_get_message@Base 13.10.1+13.10.20130820 messaging_menu_app_get_type@Base 12.10.0 messaging_menu_app_has_source@Base 12.10.0 messaging_menu_app_insert_source@Base 12.10.0 @@ -15,8 +13,6 @@ libmessaging-menu.so.0 libmessaging-menu0 #MINVER# messaging_menu_app_new@Base 12.10.0 messaging_menu_app_register@Base 12.10.0 messaging_menu_app_remove_attention@Base 12.10.0 - messaging_menu_app_remove_message@Base 12.10.6-0ubuntu1phablet1 - messaging_menu_app_remove_message_by_id@Base 12.10.6-0ubuntu1phablet1 messaging_menu_app_remove_source@Base 12.10.0 messaging_menu_app_set_source_count@Base 12.10.0 messaging_menu_app_set_source_icon@Base 12.10.2 @@ -25,14 +21,3 @@ libmessaging-menu.so.0 libmessaging-menu0 #MINVER# messaging_menu_app_set_source_time@Base 12.10.0 messaging_menu_app_set_status@Base 12.10.0 messaging_menu_app_unregister@Base 12.10.0 - messaging_menu_message_add_action@Base 12.10.6-0ubuntu1phablet3 - messaging_menu_message_get_body@Base 12.10.6-0ubuntu1phablet1 - messaging_menu_message_get_draws_attention@Base 12.10.6-0ubuntu1phablet1 - messaging_menu_message_get_icon@Base 12.10.6-0ubuntu1phablet1 - messaging_menu_message_get_id@Base 12.10.6-0ubuntu1phablet1 - messaging_menu_message_get_subtitle@Base 12.10.6-0ubuntu1phablet1 - messaging_menu_message_get_time@Base 12.10.6-0ubuntu1phablet1 - messaging_menu_message_get_title@Base 12.10.6-0ubuntu1phablet1 - messaging_menu_message_get_type@Base 12.10.6-0ubuntu1phablet1 - messaging_menu_message_new@Base 12.10.6-0ubuntu1phablet1 - messaging_menu_message_set_draws_attention@Base 12.10.6-0ubuntu1phablet1 diff --git a/debian/source/format b/debian/source/format deleted file mode 100644 index d3827e7..0000000 --- a/debian/source/format +++ /dev/null @@ -1 +0,0 @@ -1.0 diff --git a/doc/reference/Makefile.am b/doc/reference/Makefile.am index 3993b41..814a939 100644 --- a/doc/reference/Makefile.am +++ b/doc/reference/Makefile.am @@ -11,7 +11,8 @@ HFILE_GLOB = $(top_srcdir)/libmessaging-menu/*.h CFILE_GLOB = $(top_srcdir)/libmessaging-menu/*.c IGNORE_HFILES= \ - indicator-messages-service.h + indicator-messages-service.h \ + gtupleaction.h INCLUDES=-I$(top_srcdir)/libmessaging-menu $(GIO_CFLAGS) GTKDOC_LIBS=$(top_builddir)/libmessaging-menu/libmessaging-menu.la diff --git a/doc/reference/messaging-menu-docs.xml.in b/doc/reference/messaging-menu-docs.xml.in index 4c1cec1..742d37b 100644 --- a/doc/reference/messaging-menu-docs.xml.in +++ b/doc/reference/messaging-menu-docs.xml.in @@ -18,8 +18,7 @@ API Reference - - + diff --git a/libmessaging-menu/Makefile.am b/libmessaging-menu/Makefile.am index 1042d9f..7a6ee31 100644 --- a/libmessaging-menu/Makefile.am +++ b/libmessaging-menu/Makefile.am @@ -4,25 +4,36 @@ lib_LTLIBRARIES = libmessaging-menu.la libmessaging_menu_ladir = $(includedir)/messaging-menu libmessaging_menu_la_SOURCES = \ - messaging-menu-app.c \ - messaging-menu-message.c + messaging-menu.c \ + gtupleaction.c \ + gtupleaction.h \ + $(BUILT_SOURCES) libmessaging_menu_la_HEADERS = \ - messaging-menu-app.h \ - messaging-menu.h \ - messaging-menu-message.h + messaging-menu.h -libmessaging_menu_la_LIBADD = \ - $(GIO_LIBS) \ - $(top_builddir)/common/libmessaging-common.la +libmessaging_menu_la_LIBADD = $(GIO_LIBS) libmessaging_menu_la_CFLAGS = \ - -I$(top_builddir)/common \ $(GIO_CFLAGS) \ -Wall libmessaging_menu_la_LDFLAGS = -export-symbols-regex "^messaging_menu_.*" +BUILT_SOURCES = \ + indicator-messages-service.c \ + indicator-messages-service.h + +CLEANFILES = $(BUILT_SOURCES) + +indicator-messages-service.c: $(top_srcdir)/src/messages-service.xml + $(AM_V_GEN) gdbus-codegen \ + --interface-prefix com.canonical.indicator.messages. \ + --generate-c-code indicator-messages-service \ + --c-namespace IndicatorMessages \ + $^ +indicator-messages-service.h: indicator-messages-service.c + pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = messaging-menu.pc @@ -41,11 +52,7 @@ MessagingMenu_1_0_gir_INCLUDES = GObject-2.0 Gio-2.0 MessagingMenu_1_0_gir_CFLAGS = $(INCLUDES) $(GIO_CFLAGS) MessagingMenu_1_0_gir_SCANNERFLAGS = --c-include="messaging-menu.h" MessagingMenu_1_0_gir_LIBS = libmessaging-menu.la -MessagingMenu_1_0_gir_FILES = \ - messaging-menu-app.c \ - messaging-menu-app.h \ - messaging-menu-message.c \ - messaging-menu-message.h +MessagingMenu_1_0_gir_FILES = messaging-menu.c messaging-menu.h MessagingMenu_1_0_gir_EXPORT_PACKAGES = messaging-menu INTROSPECTION_GIRS += MessagingMenu-1.0.gir @@ -55,23 +62,5 @@ gir_DATA = $(INTROSPECTION_GIRS) typelibdir = $(libdir)/girepository-1.0 typelib_DATA = $(INTROSPECTION_GIRS:.gir=.typelib) -CLEANFILES = $(gir_DATA) $(typelib_DATA) - - -######################### -# VAPI Files -######################### - -if HAVE_VALA - -vapidir = $(datadir)/vala/vapi -vapi_DATA = MessagingMenu-1.0.vapi - -MessagingMenu-1.0.vapi: MessagingMenu-1.0.gir - $(VALA_API_GEN) --pkg gio-2.0 --library=MessagingMenu-1.0 $< - -CLEANFILES += $(vapi_DATA) - -endif - +CLEANFILES +=$(gir_DATA) $(typelib_DATA) endif diff --git a/libmessaging-menu/gtupleaction.c b/libmessaging-menu/gtupleaction.c new file mode 100644 index 0000000..21bc003 --- /dev/null +++ b/libmessaging-menu/gtupleaction.c @@ -0,0 +1,354 @@ +/* + * Copyright 2012 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . + * + * Authors: + * Lars Uebernickel + */ + +#include "gtupleaction.h" + +typedef GObjectClass GTupleActionClass; + +struct _GTupleAction +{ + GObject parent; + + gchar *name; + GVariantType *type; + gboolean enabled; + + gsize n_children; + GVariant **children; +}; + +static void action_interface_init (GActionInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (GTupleAction, g_tuple_action, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_ACTION, action_interface_init)); + +enum +{ + PROP_0, + PROP_NAME, + PROP_PARAMETER_TYPE, + PROP_ENABLED, + PROP_STATE_TYPE, + PROP_STATE, + N_PROPERTIES +}; + +enum +{ + SIGNAL_ACTIVATE, + N_SIGNALS +}; + +static GParamSpec *properties[N_PROPERTIES]; +static guint signal_ids[N_SIGNALS]; + +static const gchar * +g_tuple_action_get_name (GAction *action) +{ + GTupleAction *tuple = G_TUPLE_ACTION (action); + + return tuple->name; +} + +static const GVariantType * +g_tuple_action_get_parameter_type (GAction *action) +{ + return NULL; +} + +static const GVariantType * +g_tuple_action_get_state_type (GAction *action) +{ + GTupleAction *tuple = G_TUPLE_ACTION (action); + + return tuple->type; +} + +static GVariant * +g_tuple_action_get_state_hint (GAction *action) +{ + return NULL; +} + +static gboolean +g_tuple_action_get_enabled (GAction *action) +{ + GTupleAction *tuple = G_TUPLE_ACTION (action); + + return tuple->enabled; +} + +static GVariant * +g_tuple_action_get_state (GAction *action) +{ + GTupleAction *tuple = G_TUPLE_ACTION (action); + GVariant *result; + + result = g_variant_new_tuple (tuple->children, tuple->n_children); + return g_variant_ref_sink (result); +} + +static void +g_tuple_action_set_state (GTupleAction *tuple, + GVariant *state) +{ + int i; + + g_return_if_fail (g_variant_type_is_tuple (g_variant_get_type (state))); + + if (tuple->type == NULL) + { + tuple->type = g_variant_type_copy (g_variant_get_type (state)); + tuple->n_children = g_variant_n_children (state); + tuple->children = g_new0 (GVariant *, tuple->n_children); + } + + for (i = 0; i < tuple->n_children; i++) + { + if (tuple->children[i]) + g_variant_unref (tuple->children[i]); + tuple->children[i] = g_variant_get_child_value (state, i); + } + + g_object_notify_by_pspec (G_OBJECT (tuple), properties[PROP_STATE]); +} + +static void +g_tuple_action_change_state (GAction *action, + GVariant *value) +{ + GTupleAction *tuple = G_TUPLE_ACTION (action); + + g_return_if_fail (value != NULL); + g_return_if_fail (g_variant_is_of_type (value, tuple->type)); + + g_variant_ref_sink (value); + + /* TODO add a change-state signal similar to GSimpleAction */ + g_tuple_action_set_state (tuple, value); + + g_variant_unref (value); +} + +static void +g_tuple_action_activate (GAction *action, + GVariant *parameter) +{ + GTupleAction *tuple = G_TUPLE_ACTION (action); + + g_return_if_fail (parameter == NULL); + + if (tuple->enabled) + g_signal_emit (tuple, signal_ids[SIGNAL_ACTIVATE], 0, NULL); +} + +static void +g_tuple_action_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GAction *action = G_ACTION (object); + + switch (prop_id) + { + case PROP_NAME: + g_value_set_string (value, g_tuple_action_get_name (action)); + break; + + case PROP_PARAMETER_TYPE: + g_value_set_boxed (value, g_tuple_action_get_parameter_type (action)); + break; + + case PROP_ENABLED: + g_value_set_boolean (value, g_tuple_action_get_enabled (action)); + break; + + case PROP_STATE_TYPE: + g_value_set_boxed (value, g_tuple_action_get_state_type (action)); + break; + + case PROP_STATE: + g_value_take_variant (value, g_tuple_action_get_state (action)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +g_tuple_action_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GTupleAction *tuple = G_TUPLE_ACTION (object); + + switch (prop_id) + { + case PROP_NAME: + tuple->name = g_value_dup_string (value); + g_object_notify_by_pspec (object, properties[PROP_NAME]); + break; + + case PROP_ENABLED: + tuple->enabled = g_value_get_boolean (value); + g_object_notify_by_pspec (object, properties[PROP_ENABLED]); + break; + + case PROP_STATE: + g_tuple_action_set_state (tuple, g_value_get_variant (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +g_tuple_action_finalize (GObject *object) +{ + GTupleAction *tuple = G_TUPLE_ACTION (object); + int i; + + g_free (tuple->name); + g_variant_type_free (tuple->type); + + for (i = 0; i < tuple->n_children; i++) + g_variant_unref (tuple->children[i]); + + g_free (tuple->children); + + G_OBJECT_CLASS (g_tuple_action_parent_class)->finalize (object); +} + +static void +action_interface_init (GActionInterface *iface) +{ + iface->get_name = g_tuple_action_get_name; + iface->get_parameter_type = g_tuple_action_get_parameter_type; + iface->get_state_type = g_tuple_action_get_state_type; + iface->get_state_hint = g_tuple_action_get_state_hint; + iface->get_enabled = g_tuple_action_get_enabled; + iface->get_state = g_tuple_action_get_state; + iface->change_state = g_tuple_action_change_state; + iface->activate = g_tuple_action_activate; +} + +static void +g_tuple_action_class_init (GTupleActionClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + + object_class->get_property = g_tuple_action_get_property; + object_class->set_property = g_tuple_action_set_property; + object_class->finalize = g_tuple_action_finalize; + + properties[PROP_NAME] = g_param_spec_string ("name", + "Name", + "The name of the action", + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + properties[PROP_PARAMETER_TYPE] = g_param_spec_boxed ("parameter-type", + "Parameter Type", + "The variant type passed to activate", + G_TYPE_VARIANT_TYPE, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS); + + properties[PROP_ENABLED] = g_param_spec_boolean ("enabled", + "Enabled", + "Whether the action can be activated", + TRUE, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + properties[PROP_STATE_TYPE] = g_param_spec_boxed ("state-type", + "State Type", + "The variant type of the state, must be a tuple", + G_TYPE_VARIANT_TYPE, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS); + + properties[PROP_STATE] = g_param_spec_variant ("state", + "State", + "The state of the action", + G_VARIANT_TYPE_TUPLE, + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, N_PROPERTIES, properties); + + signal_ids[SIGNAL_ACTIVATE] = g_signal_new ("activate", + G_TYPE_TUPLE_ACTION, + G_SIGNAL_RUN_LAST | G_SIGNAL_MUST_COLLECT, + 0, NULL, NULL, + g_cclosure_marshal_VOID__VARIANT, + G_TYPE_NONE, 1, + G_TYPE_VARIANT); +} + +static void +g_tuple_action_init (GTupleAction *action) +{ + action->enabled = TRUE; +} + +GTupleAction * +g_tuple_action_new (const gchar *name, + GVariant *initial_state) +{ + const GVariantType *type; + + g_return_val_if_fail (name != NULL, NULL); + g_return_val_if_fail (initial_state != NULL, NULL); + + type = g_variant_get_type (initial_state); + g_return_val_if_fail (g_variant_type_is_tuple (type), NULL); + + return g_object_new (G_TYPE_TUPLE_ACTION, + "name", name, + "state", initial_state, + NULL); +} + +void +g_tuple_action_set_child (GTupleAction *action, + gsize index, + GVariant *value) +{ + const GVariantType *type; + + g_return_if_fail (G_IS_TUPLE_ACTION (action)); + g_return_if_fail (index < action->n_children); + g_return_if_fail (value != NULL); + + type = g_variant_get_type (value); + g_return_if_fail (g_variant_is_of_type (value, type)); + + g_variant_unref (action->children[index]); + action->children[index] = g_variant_ref_sink (value); + + g_object_notify_by_pspec (G_OBJECT (action), properties[PROP_STATE]); +} diff --git a/libmessaging-menu/gtupleaction.h b/libmessaging-menu/gtupleaction.h new file mode 100644 index 0000000..c447d71 --- /dev/null +++ b/libmessaging-menu/gtupleaction.h @@ -0,0 +1,40 @@ +/* + * Copyright 2012 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . + * + * Authors: + * Lars Uebernickel + */ + +#ifndef __g_tuple_action_h__ +#define __g_tuple_action_h__ + +#include + +#define G_TYPE_TUPLE_ACTION (g_tuple_action_get_type ()) +#define G_TUPLE_ACTION(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_TUPLE_ACTION, GTupleAction)) +#define G_IS_TUPLE_ACTION(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_TUPLE_ACTION)) + +typedef struct _GTupleAction GTupleAction; + +GType g_tuple_action_get_type (void) G_GNUC_CONST; + +GTupleAction * g_tuple_action_new (const gchar *name, + GVariant *initial_state); + +void g_tuple_action_set_child (GTupleAction *action, + gsize index, + GVariant *value); + +#endif diff --git a/libmessaging-menu/messaging-menu-app.c b/libmessaging-menu/messaging-menu-app.c deleted file mode 100644 index 421a09f..0000000 --- a/libmessaging-menu/messaging-menu-app.c +++ /dev/null @@ -1,1433 +0,0 @@ -/* - * Copyright 2012 Canonical Ltd. - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 3, as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranties of - * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . - * - * Authors: - * Lars Uebernickel - */ - -#include "messaging-menu-app.h" -#include "indicator-messages-service.h" -#include "indicator-messages-application.h" - -#include -#include - -/** - * SECTION:messaging-menu - * @title: MessagingMenuApp - * @short_description: An application section in the messaging menu - * @include: messaging-menu.h - * - * A #MessagingMenuApp represents an application section in the - * Messaging Menu. An application section is tied to an installed - * application through a desktop file id, which must be passed to - * messaging_menu_app_new(). - * - * To register the application with the Messaging Menu, call - * messaging_menu_app_register(). This signifies that the application - * should be present in the menu and be marked as "running". - * - * The first menu item in an application section represents the - * application itself, using the name and icon found in the associated - * desktop file. Activating this item starts the application. - * - * Following the application item, the Messaging Menu inserts all - * shortcut actions found in the desktop file. Actions whose - * NotShowIn keyword contains "Messaging Menu" or whose - * OnlyShowIn keyword does not contain "Messaging Menu" - * will not appear (the - * desktop file specification contains a detailed explanation of - * shortcut actions.) An application cannot add, remove, or change - * these shortcut items while it is running. - * - * Next, an application section contains menu items for message sources. - * What exactly constitutes a message source depends on the type of - * application: an email client's message sources are folders - * containing new messages, while those of a chat program are persons - * that have contacted the user. - * - * A message source is represented in the menu by a label and optionally - * also an icon. It can be associated with either a count, a time, or - * an arbitrary string, which will appear on the right side of the menu - * item. - * - * When the user activates a source, the source is immediately removed - * from the menu and the "activate-source" signal is emitted. - * - * Applications should always expose all the message sources available. - * However, the Messaging Menu might limit the amount of sources it - * displays to the user. - * - * The Messaging Menu offers users a way to set their chat status - * (available, away, busy, invisible, or offline) for multiple - * applications at once. Applications that appear in the Messaging Menu - * can integrate with this by setting the - * "X-MessagingMenu-UsesChatSection" key in their desktop file to True. - * Use messaging_menu_app_set_status() to signify that the application's - * chat status has changed. When the user changes status through the - * Messaging Menu, the ::status-changed signal will be emitted. - * - * If the application stops running without calling - * messaging_menu_app_unregister(), it will be marked as "not running". - * Its application and shortcut items stay in the menu, but all message - * sources are removed. If messaging_menu_app_unregister() is called, - * the application section is removed completely. - * - * More information about the design and recommended usage of the - * Messaging Menu is available at https://wiki.ubuntu.com/MessagingMenu. - */ - -/** - * MessagingMenuApp: - * - * #MessagingMenuApp is an opaque structure. - */ -struct _MessagingMenuApp -{ - GObject parent_instance; - - GDesktopAppInfo *appinfo; - int registered; /* -1 for unknown */ - MessagingMenuStatus status; - gboolean status_set; - GDBusConnection *bus; - - GHashTable *messages; - GList *sources; - IndicatorMessagesApplication *app_interface; - - IndicatorMessagesService *messages_service; - guint watch_id; - - GCancellable *cancellable; -}; - -G_DEFINE_TYPE (MessagingMenuApp, messaging_menu_app, G_TYPE_OBJECT); - -enum { - PROP_0, - PROP_DESKTOP_ID, - N_PROPERTIES -}; - -enum { - ACTIVATE_SOURCE, - STATUS_CHANGED, - N_SIGNALS -}; - -static GParamSpec *properties[N_PROPERTIES]; -static guint signals[N_SIGNALS]; - -static const gchar *status_ids[] = { "available", "away", "busy", "invisible", "offline" }; - -typedef struct -{ - gchar *id; - GIcon *icon; - gchar *label; - - guint32 count; - gint64 time; - gchar *string; - gboolean draws_attention; -} Source; - -static void global_status_changed (IndicatorMessagesService *service, - const gchar *status_str, - gpointer user_data); - -/* in messaging-menu-message.c */ -GVariant * _messaging_menu_message_to_variant (MessagingMenuMessage *msg); - -static void -source_free (gpointer data) -{ - Source *source = data; - - if (source) - { - g_free (source->id); - g_clear_object (&source->icon); - g_free (source->label); - g_free (source->string); - g_slice_free (Source, source); - } -} - -static GVariant * -source_to_variant (Source *source) -{ - GVariant *v; - gchar *iconstr; - - iconstr = source->icon ? g_icon_to_string (source->icon) : NULL; - - v = g_variant_new ("(sssuxsb)", source->id, - source->label, - iconstr ? iconstr : "", - source->count, - source->time, - source->string ? source->string : "", - source->draws_attention); - - g_free (iconstr); - - return v; -} - -static gchar * -messaging_menu_app_get_dbus_object_path (MessagingMenuApp *app) -{ - gchar *path; - - if (!app->appinfo) - return NULL; - - path = g_strconcat ("/com/canonical/indicator/messages/", - g_app_info_get_id (G_APP_INFO (app->appinfo)), - NULL); - - g_strcanon (path, "/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", '_'); - - return path; -} - -static void -messaging_menu_app_got_bus (GObject *source, - GAsyncResult *res, - gpointer user_data) -{ - MessagingMenuApp *app = user_data; - GError *error = NULL; - gchar *object_path; - - app->bus = g_bus_get_finish (res, &error); - if (app->bus == NULL) - { - g_warning ("unable to connect to session bus: %s", error->message); - g_error_free (error); - return; - } - - object_path = messaging_menu_app_get_dbus_object_path (app); - - if (object_path && - !g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (app->app_interface), - app->bus, object_path, &error)) - { - g_warning ("unable to export application interface: %s", error->message); - g_clear_error (&error); - } - - g_free (object_path); -} - -static void -messaging_menu_app_set_desktop_id (MessagingMenuApp *app, - const gchar *desktop_id) -{ - g_return_if_fail (desktop_id != NULL); - - /* no need to clean up, it's construct only */ - app->appinfo = g_desktop_app_info_new (desktop_id); - if (app->appinfo == NULL) - { - g_warning ("could not find the desktop file for '%s'", - desktop_id); - } - - g_bus_get (G_BUS_TYPE_SESSION, - app->cancellable, - messaging_menu_app_got_bus, - app); -} - -static void -messaging_menu_app_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - MessagingMenuApp *app = MESSAGING_MENU_APP (object); - - switch (prop_id) - { - case PROP_DESKTOP_ID: - messaging_menu_app_set_desktop_id (app, g_value_get_string (value)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -messaging_menu_app_finalize (GObject *object) -{ - G_OBJECT_CLASS (messaging_menu_app_parent_class)->finalize (object); -} - -static void -messaging_menu_app_dispose (GObject *object) -{ - MessagingMenuApp *app = MESSAGING_MENU_APP (object); - - if (app->watch_id > 0) - { - g_bus_unwatch_name (app->watch_id); - app->watch_id = 0; - } - - if (app->cancellable) - { - g_cancellable_cancel (app->cancellable); - g_object_unref (app->cancellable); - app->cancellable = NULL; - } - - if (app->messages_service) - { - indicator_messages_service_call_application_stopped_running (app->messages_service, - g_app_info_get_id (G_APP_INFO (app->appinfo)), - NULL, NULL, NULL); - - g_signal_handlers_disconnect_by_func (app->messages_service, - global_status_changed, - app); - g_clear_object (&app->messages_service); - } - - g_clear_pointer (&app->messages, g_hash_table_unref); - - g_list_free_full (app->sources, source_free); - app->sources = NULL; - - g_clear_object (&app->app_interface); - g_clear_object (&app->appinfo); - g_clear_object (&app->bus); - - G_OBJECT_CLASS (messaging_menu_app_parent_class)->dispose (object); -} - -static void -messaging_menu_app_class_init (MessagingMenuAppClass *class) -{ - GObjectClass *object_class = G_OBJECT_CLASS (class); - - object_class->set_property = messaging_menu_app_set_property; - object_class->finalize = messaging_menu_app_finalize; - object_class->dispose = messaging_menu_app_dispose; - - /** - * MessagingMenuApp:desktop-id: - * - * The desktop id of the application associated with this application - * section. Must be given when the #MessagingMenuApp is created. - */ - properties[PROP_DESKTOP_ID] = g_param_spec_string ("desktop-id", - "Desktop Id", - "The desktop id of the associated application", - NULL, - G_PARAM_WRITABLE | - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_STATIC_STRINGS); - - g_object_class_install_properties (object_class, N_PROPERTIES, properties); - - /** - * MessagingMenuApp::activate-source: - * @mmapp: the #MessagingMenuApp - * @source_id: the source id that was activated - * - * Emitted when the user has activated the message source with id - * @source_id. The source is immediately removed from the menu, - * handlers of this signal do not need to call - * messaging_menu_app_remove_source(). - */ - signals[ACTIVATE_SOURCE] = g_signal_new ("activate-source", - MESSAGING_MENU_TYPE_APP, - G_SIGNAL_RUN_FIRST | - G_SIGNAL_DETAILED, - 0, - NULL, NULL, - g_cclosure_marshal_VOID__STRING, - G_TYPE_NONE, 1, G_TYPE_STRING); - - /** - * MessagingMenuApp::status-changed: - * @mmapp: the #MessagingMenuApp - * @status: a #MessagingMenuStatus - * - * Emitted when the chat status is changed through the messaging menu. - * - * Applications which are registered to use the chat status should - * change their status to @status upon receiving this signal. Call - * messaging_menu_app_set_status() to acknowledge that the application - * changed its status. - */ - signals[STATUS_CHANGED] = g_signal_new ("status-changed", - MESSAGING_MENU_TYPE_APP, - G_SIGNAL_RUN_FIRST, - 0, - NULL, NULL, - g_cclosure_marshal_VOID__INT, - G_TYPE_NONE, 1, G_TYPE_INT); -} - -static void -created_messages_service (GObject *source_object, - GAsyncResult *result, - gpointer user_data) -{ - MessagingMenuApp *app = user_data; - GError *error = NULL; - - app->messages_service = indicator_messages_service_proxy_new_finish (result, &error); - if (!app->messages_service) - { - g_warning ("unable to connect to the mesaging menu service: %s", error->message); - g_error_free (error); - return; - } - - g_signal_connect (app->messages_service, "status-changed", - G_CALLBACK (global_status_changed), app); - - /* sync current status */ - if (app->registered == TRUE) - messaging_menu_app_register (app); - else if (app->registered == FALSE) - messaging_menu_app_unregister (app); - if (app->status_set) - messaging_menu_app_set_status (app, app->status); -} - -static void -indicator_messages_appeared (GDBusConnection *bus, - const gchar *name, - const gchar *name_owner, - gpointer user_data) -{ - MessagingMenuApp *app = user_data; - - indicator_messages_service_proxy_new (bus, - G_DBUS_PROXY_FLAGS_NONE, - "com.canonical.indicator.messages", - "/com/canonical/indicator/messages/service", - app->cancellable, - created_messages_service, - app); -} - -static void -indicator_messages_vanished (GDBusConnection *bus, - const gchar *name, - gpointer user_data) -{ - MessagingMenuApp *app = user_data; - - if (app->messages_service) - { - g_signal_handlers_disconnect_by_func (app->messages_service, - global_status_changed, - app); - g_clear_object (&app->messages_service); - } -} - -static gboolean -messaging_menu_app_list_sources (IndicatorMessagesApplication *app_interface, - GDBusMethodInvocation *invocation, - gpointer user_data) -{ - MessagingMenuApp *app = user_data; - GVariantBuilder builder; - GList *it; - - g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(sssuxsb)")); - - for (it = app->sources; it; it = it->next) - g_variant_builder_add_value (&builder, source_to_variant (it->data)); - - indicator_messages_application_complete_list_sources (app_interface, - invocation, - g_variant_builder_end (&builder)); - - return TRUE; -} - -static gint -compare_source_id (gconstpointer a, - gconstpointer b) -{ - const Source *source = a; - const gchar *id = b; - - return strcmp (source->id, id); -} - -static gboolean -messaging_menu_app_remove_source_internal (MessagingMenuApp *app, - const gchar *source_id) -{ - GList *node; - - node = g_list_find_custom (app->sources, source_id, compare_source_id); - if (node) - { - source_free (node->data); - app->sources = g_list_delete_link (app->sources, node); - return TRUE; - } - - return FALSE; -} - -static gboolean -messaging_menu_app_remove_message_internal (MessagingMenuApp *app, - const gchar *message_id) -{ - return g_hash_table_remove (app->messages, message_id); -} - -static gboolean -messaging_menu_app_activate_source (IndicatorMessagesApplication *app_interface, - GDBusMethodInvocation *invocation, - const gchar *source_id, - gpointer user_data) -{ - MessagingMenuApp *app = user_data; - GQuark q = g_quark_from_string (source_id); - - /* Activate implies removing the source, no need for SourceRemoved */ - if (messaging_menu_app_remove_source_internal (app, source_id)) - g_signal_emit (app, signals[ACTIVATE_SOURCE], q, source_id); - - indicator_messages_application_complete_activate_source (app_interface, invocation); - - return TRUE; -} - -static gboolean -messaging_menu_app_list_messages (IndicatorMessagesApplication *app_interface, - GDBusMethodInvocation *invocation, - gpointer user_data) -{ - MessagingMenuApp *app = user_data; - GVariantBuilder builder; - GHashTableIter iter; - MessagingMenuMessage *message; - - g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(sssssxaa{sv}b)")); - - g_hash_table_iter_init (&iter, app->messages); - while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &message)) - g_variant_builder_add_value (&builder, _messaging_menu_message_to_variant (message)); - - indicator_messages_application_complete_list_messages (app_interface, - invocation, - g_variant_builder_end (&builder)); - - return TRUE; -} - -static gboolean -messaging_menu_app_activate_message (IndicatorMessagesApplication *app_interface, - GDBusMethodInvocation *invocation, - const gchar *message_id, - const gchar *action_id, - GVariant *params, - gpointer user_data) -{ - MessagingMenuApp *app = user_data; - MessagingMenuMessage *msg; - - msg = g_hash_table_lookup (app->messages, message_id); - if (msg) - { - if (*action_id) - { - gchar *signal; - - signal = g_strconcat ("activate::", action_id, NULL); - - if (g_variant_n_children (params)) - { - GVariant *param; - - g_variant_get_child (params, 0, "v", ¶m); - g_signal_emit_by_name (msg, signal, action_id, param); - - g_variant_unref (param); - } - else - g_signal_emit_by_name (msg, signal, action_id, NULL); - - g_free (signal); - } - else - g_signal_emit_by_name (msg, "activate", NULL, NULL); - - - /* Activate implies removing the message, no need for MessageRemoved */ - messaging_menu_app_remove_message_internal (app, message_id); - } - - indicator_messages_application_complete_activate_message (app_interface, invocation); - - return TRUE; -} - -static gboolean -messaging_menu_app_dismiss (IndicatorMessagesApplication *app_interface, - GDBusMethodInvocation *invocation, - const gchar * const *sources, - const gchar * const *messages, - gpointer user_data) -{ - MessagingMenuApp *app = user_data; - const gchar * const *it; - - for (it = sources; *it; it++) - messaging_menu_app_remove_source_internal (app, *it); - - for (it = messages; *it; it++) - messaging_menu_app_remove_message_internal (app, *it); - - return TRUE; -} - -static void -messaging_menu_app_init (MessagingMenuApp *app) -{ - app->registered = -1; - app->status_set = FALSE; - app->bus = NULL; - - app->cancellable = g_cancellable_new (); - - app->app_interface = indicator_messages_application_skeleton_new (); - g_signal_connect (app->app_interface, "handle-list-sources", - G_CALLBACK (messaging_menu_app_list_sources), app); - g_signal_connect (app->app_interface, "handle-activate-source", - G_CALLBACK (messaging_menu_app_activate_source), app); - g_signal_connect (app->app_interface, "handle-list-messages", - G_CALLBACK (messaging_menu_app_list_messages), app); - g_signal_connect (app->app_interface, "handle-activate-message", - G_CALLBACK (messaging_menu_app_activate_message), app); - g_signal_connect (app->app_interface, "handle-dismiss", - G_CALLBACK (messaging_menu_app_dismiss), app); - - app->messages = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); - - app->watch_id = g_bus_watch_name (G_BUS_TYPE_SESSION, - "com.canonical.indicator.messages", - G_BUS_NAME_WATCHER_FLAGS_NONE, - indicator_messages_appeared, - indicator_messages_vanished, - app, - NULL); -} - -/** - * messaging_menu_new: - * @desktop_id: a desktop file id. See g_desktop_app_info_new() - * - * Creates a new #MessagingMenuApp for the application associated with - * @desktop_id. - * - * The application will not show up (nor be marked as "running") in the - * Messaging Menu before messaging_menu_app_register() has been called. - * - * Returns: (transfer full): a new #MessagingMenuApp - */ -MessagingMenuApp * -messaging_menu_app_new (const gchar *desktop_id) -{ - return g_object_new (MESSAGING_MENU_TYPE_APP, - "desktop-id", desktop_id, - NULL); -} - -/** - * messaging_menu_app_register: - * @app: a #MessagingMenuApp - * - * Registers @app with the Messaging Menu. - * - * If the application doesn't already have a section in the Messaging - * Menu, one will be created for it. The application will also be - * marked as "running". - * - * The application will be marked as "not running" as soon as @app is - * destroyed. The application launcher as well as shortcut actions will - * remain in the menu. To completely remove the application section - * from the Messaging Menu, call messaging_menu_app_unregister(). - */ -void -messaging_menu_app_register (MessagingMenuApp *app) -{ - gchar *object_path; - - g_return_if_fail (MESSAGING_MENU_IS_APP (app)); - - app->registered = TRUE; - - /* state will be synced right after connecting to the service */ - if (!app->messages_service) - return; - - object_path = messaging_menu_app_get_dbus_object_path (app); - if (!object_path) - return; - - indicator_messages_service_call_register_application (app->messages_service, - g_app_info_get_id (G_APP_INFO (app->appinfo)), - object_path, - app->cancellable, - NULL, NULL); - - g_free (object_path); -} - -/** - * messaging_menu_app_unregister: - * @app: a #MessagingMenuApp - * - * Completely removes the @app from the Messaging Menu. If the - * application's launcher and shortcut actions should remain in the - * menu, destroying @app with g_object_unref() suffices. - * - * Note: @app will remain valid and usable after this call. - */ -void -messaging_menu_app_unregister (MessagingMenuApp *app) -{ - g_return_if_fail (MESSAGING_MENU_IS_APP (app)); - - app->registered = FALSE; - - /* state will be synced right after connecting to the service */ - if (!app->messages_service) - return; - - if (!app->appinfo) - return; - - indicator_messages_service_call_unregister_application (app->messages_service, - g_app_info_get_id (G_APP_INFO (app->appinfo)), - app->cancellable, - NULL, NULL); -} - -/** - * messaging_menu_app_set_status: - * @app: a #MessagingMenuApp - * @status: a #MessagingMenuStatus - * - * Notify the Messaging Menu that the chat status of @app has changed to - * @status. - * - * Connect to the ::status-changed signal to receive notification about - * the user changing their global chat status through the Messaging - * Menu. - * - * This function does nothing for applications whose desktop file does - * not include X-MessagingMenu-UsesChatSection. - */ -void -messaging_menu_app_set_status (MessagingMenuApp *app, - MessagingMenuStatus status) -{ - g_return_if_fail (MESSAGING_MENU_IS_APP (app)); - g_return_if_fail (status >= MESSAGING_MENU_STATUS_AVAILABLE && - status <= MESSAGING_MENU_STATUS_OFFLINE); - - app->status = status; - app->status_set = TRUE; - - /* state will be synced right after connecting to the service */ - if (!app->messages_service) - return; - - if (!app->appinfo) - return; - - indicator_messages_service_call_set_status (app->messages_service, - g_app_info_get_id (G_APP_INFO (app->appinfo)), - status_ids [status], - app->cancellable, - NULL, NULL); -} - -static int -status_from_string (const gchar *s) -{ - int i; - - if (!s) - return -1; - - for (i = 0; i <= MESSAGING_MENU_STATUS_OFFLINE; i++) - { - if (g_str_equal (s, status_ids[i])) - return i; - } - - return -1; -} - -static void -global_status_changed (IndicatorMessagesService *service, - const gchar *status_str, - gpointer user_data) -{ - MessagingMenuApp *app = user_data; - int status; - - status = status_from_string (status_str); - g_return_if_fail (status >= 0); - - g_signal_emit (app, signals[STATUS_CHANGED], 0, status); -} - -static Source * -messaging_menu_app_lookup_source (MessagingMenuApp *app, - const gchar *id) -{ - GList *node; - - node = g_list_find_custom (app->sources, id, compare_source_id); - - return node ? node->data : NULL; -} - -static Source * -messaging_menu_app_get_source (MessagingMenuApp *app, - const gchar *id) -{ - Source *source; - - source = messaging_menu_app_lookup_source (app, id); - if (!source) - g_warning ("a source with id '%s' doesn't exist", id); - - return source; -} - -static void -messaging_menu_app_notify_source_changed (MessagingMenuApp *app, - Source *source) -{ - indicator_messages_application_emit_source_changed (app->app_interface, - source_to_variant (source)); -} - -static void -messaging_menu_app_insert_source_internal (MessagingMenuApp *app, - gint position, - const gchar *id, - GIcon *icon, - const gchar *label, - guint count, - gint64 time, - const gchar *string) -{ - Source *source; - - g_return_if_fail (MESSAGING_MENU_IS_APP (app)); - g_return_if_fail (id != NULL); - g_return_if_fail (label != NULL); - - if (messaging_menu_app_lookup_source (app, id)) - { - g_warning ("a source with id '%s' already exists", id); - return; - } - - source = g_slice_new0 (Source); - source->id = g_strdup (id); - source->label = g_strdup (label); - if (icon) - source->icon = g_object_ref (icon); - source->count = count; - source->time = time; - source->string = g_strdup (string); - app->sources = g_list_insert (app->sources, source, position); - - indicator_messages_application_emit_source_added (app->app_interface, - position, - source_to_variant (source)); -} - -/** - * messaging_menu_app_insert_source: - * @app: a #MessagingMenuApp - * @position: the position at which to insert the source - * @id: a unique identifier for the source to be added - * @icon: the icon associated with the source - * @label: a user-visible string best describing the source - * - * Inserts a new message source into the section representing @app. Equivalent - * to calling messaging_menu_app_insert_source_with_time() with the current - * time. - * - * It is an error to insert a source with an @id which already exists. Use - * messaging_menu_app_has_source() to find out whether there is such a source. - */ -void -messaging_menu_app_insert_source (MessagingMenuApp *app, - gint position, - const gchar *id, - GIcon *icon, - const gchar *label) -{ - messaging_menu_app_insert_source_with_time (app, position, id, icon, label, - g_get_real_time ()); -} - -/** - * messaging_menu_app_append_source: - * @app: a #MessagingMenuApp - * @id: a unique identifier for the source to be added - * @icon: (allow-none): the icon associated with the source - * @label: a user-visible string best describing the source - * - * Appends a new message source to the end of the section representing @app. - * Equivalent to calling messaging_menu_app_append_source_with_time() with the - * current time. - * - * It is an error to add a source with an @id which already exists. Use - * messaging_menu_app_has_source() to find out whether there is such a source. - */ -void -messaging_menu_app_append_source (MessagingMenuApp *app, - const gchar *id, - GIcon *icon, - const gchar *label) -{ - messaging_menu_app_insert_source (app, -1, id, icon, label); -} - -/** - * messaging_menu_app_insert_source_with_count: - * @app: a #MessagingMenuApp - * @position: the position at which to insert the source - * @id: a unique identifier for the source to be added - * @icon: (allow-none): the icon associated with the source - * @label: a user-visible string best describing the source - * @count: the count for the source - * - * Inserts a new message source into the section representing @app and - * initializes it with @count. - * - * To update the count, use messaging_menu_app_set_source_count(). - * - * It is an error to insert a source with an @id which already exists. Use - * messaging_menu_app_has_source() to find out whether there is such a source. - */ -void -messaging_menu_app_insert_source_with_count (MessagingMenuApp *app, - gint position, - const gchar *id, - GIcon *icon, - const gchar *label, - guint count) -{ - messaging_menu_app_insert_source_internal (app, position, id, icon, label, count, 0, ""); -} - -/** - * messaging_menu_app_append_source_with_count: - * @app: a #MessagingMenuApp - * @id: a unique identifier for the source to be added - * @icon: (allow-none): the icon associated with the source - * @label: a user-visible string best describing the source - * @count: the count for the source - * - * Appends a new message source to the end of the section representing @app and - * initializes it with @count. - * - * To update the count, use messaging_menu_app_set_source_count(). - * - * It is an error to add a source with an @id which already exists. Use - * messaging_menu_app_has_source() to find out whether there is such a source. - */ -void messaging_menu_app_append_source_with_count (MessagingMenuApp *app, - const gchar *id, - GIcon *icon, - const gchar *label, - guint count) -{ - messaging_menu_app_insert_source_with_count (app, -1, id, icon, label, count); -} - -/** - * messaging_menu_app_insert_source_with_time: - * @app: a #MessagingMenuApp - * @position: the position at which to insert the source - * @id: a unique identifier for the source to be added - * @icon: (allow-none): the icon associated with the source - * @label: a user-visible string best describing the source - * @time: the time when the source was created, in microseconds - * - * Inserts a new message source into the section representing @app and - * initializes it with @time. Use messaging_menu_app_insert_source() to - * insert a source with the current time. - * - * To change the time, use messaging_menu_app_set_source_time(). - * - * It is an error to insert a source with an @id which already exists. Use - * messaging_menu_app_has_source() to find out whether there is such a source. - */ -void -messaging_menu_app_insert_source_with_time (MessagingMenuApp *app, - gint position, - const gchar *id, - GIcon *icon, - const gchar *label, - gint64 time) -{ - messaging_menu_app_insert_source_internal (app, position, id, icon, label, 0, time, ""); -} - -/** - * messaging_menu_app_append_source_with_time: - * @app: a #MessagingMenuApp - * @id: a unique identifier for the source to be added - * @icon: (allow-none): the icon associated with the source - * @label: a user-visible string best describing the source - * @time: the time when the source was created, in microseconds - * - * Appends a new message source to the end of the section representing - * @app and initializes it with @time. Use - * messaging_menu_app_append_source() to append a source with the - * current time. - * - * To change the time, use messaging_menu_app_set_source_time(). - * - * It is an error to insert a source with an @id which already exists. Use - * messaging_menu_app_has_source() to find out whether there is such a source. - */ -void -messaging_menu_app_append_source_with_time (MessagingMenuApp *app, - const gchar *id, - GIcon *icon, - const gchar *label, - gint64 time) -{ - messaging_menu_app_insert_source_with_time (app, -1, id, icon, label, time); -} - -/** - * messaging_menu_app_insert_source_with_string: - * @app: a #MessagingMenuApp - * @position: the position at which to insert the source - * @id: a unique identifier for the source to be added - * @icon: (allow-none): the icon associated with the source - * @label: a user-visible string best describing the source - * @str: a string associated with the source - * - * Inserts a new message source into the section representing @app and - * initializes it with @str. - * - * To update the string, use messaging_menu_app_set_source_string(). - * - * It is an error to insert a source with an @id which already exists. Use - * messaging_menu_app_has_source() to find out whether there is such a source. - */ -void -messaging_menu_app_insert_source_with_string (MessagingMenuApp *app, - gint position, - const gchar *id, - GIcon *icon, - const gchar *label, - const gchar *str) -{ - messaging_menu_app_insert_source_internal (app, position, id, icon, label, 0, 0, str); -} - -/** - * messaging_menu_app_append_source_with_string: - * @app: a #MessagingMenuApp - * @id: a unique identifier for the source to be added - * @icon: (allow-none): the icon associated with the source - * @label: a user-visible string best describing the source - * @str: a string associated with the source - * - * Appends a new message source to the end of the section representing @app and - * initializes it with @str. - * - * To update the string, use messaging_menu_app_set_source_string(). - * - * It is an error to insert a source with an @id which already exists. Use - * messaging_menu_app_has_source() to find out whether there is such a source. - */ -void -messaging_menu_app_append_source_with_string (MessagingMenuApp *app, - const gchar *id, - GIcon *icon, - const gchar *label, - const gchar *str) -{ - messaging_menu_app_insert_source_with_string (app, -1, id, icon, label, str); -} - -/** - * messaging_menu_app_remove_source: - * @app: a #MessagingMenuApp - * @source_id: the id of the source to remove - * - * Removes the source corresponding to @source_id from the menu. - */ -void -messaging_menu_app_remove_source (MessagingMenuApp *app, - const gchar *source_id) -{ - g_return_if_fail (MESSAGING_MENU_IS_APP (app)); - g_return_if_fail (source_id != NULL); - - if (messaging_menu_app_remove_source_internal (app, source_id)) - indicator_messages_application_emit_source_removed (app->app_interface, source_id); -} - -/** - * messaging_menu_app_has_source: - * @app: a #MessagingMenuApp - * @source_id: a source id - * - * Returns: TRUE if there is a source associated with @source_id - */ -gboolean -messaging_menu_app_has_source (MessagingMenuApp *app, - const gchar *source_id) -{ - g_return_val_if_fail (MESSAGING_MENU_IS_APP (app), FALSE); - g_return_val_if_fail (source_id != NULL, FALSE); - - return messaging_menu_app_lookup_source (app, source_id) != NULL; -} - -/** - * messaging_menu_app_set_source_label: - * @app: a #MessagingMenuApp - * @source_id: a source id - * @label: the new label for the source - * - * Changes the label of @source_id to @label. - */ -void -messaging_menu_app_set_source_label (MessagingMenuApp *app, - const gchar *source_id, - const gchar *label) -{ - Source *source; - - g_return_if_fail (MESSAGING_MENU_IS_APP (app)); - g_return_if_fail (source_id != NULL); - g_return_if_fail (label != NULL); - - source = messaging_menu_app_get_source (app, source_id); - if (source) - { - g_free (source->label); - source->label = g_strdup (label); - messaging_menu_app_notify_source_changed (app, source); - } -} - -/** - * messaging_menu_app_set_source_icon: - * @app: a #MessagingMenuApp - * @source_id: a source id - * @icon: (allow-none): the new icon for the source - * - * Changes the icon of @source_id to @icon. - */ -void -messaging_menu_app_set_source_icon (MessagingMenuApp *app, - const gchar *source_id, - GIcon *icon) -{ - Source *source; - - g_return_if_fail (MESSAGING_MENU_IS_APP (app)); - g_return_if_fail (source_id != NULL); - - source = messaging_menu_app_get_source (app, source_id); - if (source) - { - g_clear_object (&source->icon); - if (icon) - source->icon = g_object_ref (icon); - messaging_menu_app_notify_source_changed (app, source); - } -} - -/** - * messaging_menu_app_set_source_count: - * @app: a #MessagingMenuApp - * @source_id: a source id - * @count: the new count for the source - * - * Updates the count of @source_id to @count. - */ -void messaging_menu_app_set_source_count (MessagingMenuApp *app, - const gchar *source_id, - guint count) -{ - Source *source; - - g_return_if_fail (MESSAGING_MENU_IS_APP (app)); - g_return_if_fail (source_id != NULL); - - source = messaging_menu_app_get_source (app, source_id); - if (source) - { - source->count = count; - messaging_menu_app_notify_source_changed (app, source); - } -} - -/** - * messaging_menu_app_set_source_time: - * @app: a #MessagingMenuApp - * @source_id: a source id - * @time: the new time for the source, in microseconds - * - * Updates the time of @source_id to @time. - */ -void -messaging_menu_app_set_source_time (MessagingMenuApp *app, - const gchar *source_id, - gint64 time) -{ - Source *source; - - g_return_if_fail (MESSAGING_MENU_IS_APP (app)); - g_return_if_fail (source_id != NULL); - - source = messaging_menu_app_get_source (app, source_id); - if (source) - { - source->time = time; - messaging_menu_app_notify_source_changed (app, source); - } -} - -/** - * messaging_menu_app_set_source_string: - * @app: a #MessagingMenuApp - * @source_id: a source id - * @str: the new string for the source - * - * Updates the string displayed next to @source_id to @str. - */ -void -messaging_menu_app_set_source_string (MessagingMenuApp *app, - const gchar *source_id, - const gchar *str) -{ - Source *source; - - g_return_if_fail (MESSAGING_MENU_IS_APP (app)); - g_return_if_fail (source_id != NULL); - - source = messaging_menu_app_get_source (app, source_id); - if (source) - { - g_free (source->string); - source->string = g_strdup (str); - messaging_menu_app_notify_source_changed (app, source); - } -} - -/** - * messaging_menu_app_draw_attention: - * @app: a #MessagingMenuApp - * @source_id: a source id - * - * Indicates that @source_id has important unread messages. Currently, this - * means that the messaging menu's envelope icon will turn blue. - * - * Use messaging_menu_app_remove_attention() to stop indicating that the source - * needs attention. - */ -void -messaging_menu_app_draw_attention (MessagingMenuApp *app, - const gchar *source_id) -{ - Source *source; - - g_return_if_fail (MESSAGING_MENU_IS_APP (app)); - g_return_if_fail (source_id != NULL); - - source = messaging_menu_app_get_source (app, source_id); - if (source) - { - source->draws_attention = TRUE; - messaging_menu_app_notify_source_changed (app, source); - } -} - -/** - * messaging_menu_app_remove_attention: - * @app: a #MessagingMenuApp - * @source_id: a source id - * - * Stop indicating that @source_id needs attention. - * - * This function does not need to be called when the source is removed - * with messaging_menu_app_remove_source() or the user has activated the - * source. - * - * Use messaging_menu_app_draw_attention() to make @source_id draw attention - * again. - */ -void -messaging_menu_app_remove_attention (MessagingMenuApp *app, - const gchar *source_id) -{ - Source *source; - - g_return_if_fail (MESSAGING_MENU_IS_APP (app)); - g_return_if_fail (source_id != NULL); - - source = messaging_menu_app_get_source (app, source_id); - if (source) - { - source->draws_attention = FALSE; - messaging_menu_app_notify_source_changed (app, source); - } -} - -/** - * messaging_menu_app_append_message: - * @app: a #MessagingMenuApp - * @msg: the #MessagingMenuMessage to append - * @source_id: (allow-none): the source id to which @msg is added, or NULL - * @notify: whether a notification bubble should be shown for this - * message - * - * Appends @msg to the source with id @source_id of @app. The messaging - * menu might not display this message immediately if other messages are - * queued before this one. - * - * If @source_id has a count associated with it, that count will be - * increased by one. - * - * If @source_id is %NULL, @msg won't be associated with a source. - */ -void -messaging_menu_app_append_message (MessagingMenuApp *app, - MessagingMenuMessage *msg, - const gchar *source_id, - gboolean notify) -{ - const gchar *id; - - g_return_if_fail (MESSAGING_MENU_IS_APP (app)); - g_return_if_fail (MESSAGING_MENU_IS_MESSAGE (msg)); - - id = messaging_menu_message_get_id (msg); - - if (g_hash_table_lookup (app->messages, id)) - { - g_warning ("a message with id '%s' already exists", id); - return; - } - - g_hash_table_insert (app->messages, g_strdup (id), g_object_ref (msg)); - indicator_messages_application_emit_message_added (app->app_interface, - _messaging_menu_message_to_variant (msg)); - - if (source_id) - { - Source *source; - - source = messaging_menu_app_get_source (app, source_id); - if (source && source->count >= 0) - { - source->count++; - messaging_menu_app_notify_source_changed (app, source); - } - } -} - -/** - * messaging_menu_app_get_message: - * @app: a #MessagingMenuApp - * @id: id of the message to retrieve - * - * Retrieves the message with @id, that was added with - * messaging_menu_app_append_message(). - * - * Returns: (transfer none) (allow-none): the #MessagingMenuApp with - * @id, or %NULL - */ -MessagingMenuMessage * -messaging_menu_app_get_message (MessagingMenuApp *app, - const gchar *id) -{ - g_return_val_if_fail (MESSAGING_MENU_IS_APP (app), NULL); - g_return_val_if_fail (id != NULL, NULL); - - return g_hash_table_lookup (app->messages, id); -} - -/** - * messaging_menu_app_remove_message: - * @app: a #MessagingMenuApp - * @msg: the #MessagingMenuMessage to remove - * - * Removes @msg from @app. - * - * If @source_id has a count associated with it, that count will be - * decreased by one. - */ -void -messaging_menu_app_remove_message (MessagingMenuApp *app, - MessagingMenuMessage *msg) -{ - /* take a ref of @msg here to make sure the pointer returned by - * _get_id() is valid for the duration of remove_message_by_id. */ - g_object_ref (msg); - messaging_menu_app_remove_message_by_id (app, messaging_menu_message_get_id (msg)); - g_object_unref (msg); -} - -/** - * messaging_menu_app_remove_message_by_id: - * @app: a #MessagingMenuApp - * @id: the unique id of @msg - * - * Removes the message with the id @id from @app. - * - * If @source_id has a count associated with it, that count will be - * decreased by one. - */ -void -messaging_menu_app_remove_message_by_id (MessagingMenuApp *app, - const gchar *id) -{ - g_return_if_fail (MESSAGING_MENU_IS_APP (app)); - g_return_if_fail (id != NULL); - - if (messaging_menu_app_remove_message_internal (app, id)) - indicator_messages_application_emit_message_removed (app->app_interface, id); -} diff --git a/libmessaging-menu/messaging-menu-app.h b/libmessaging-menu/messaging-menu-app.h deleted file mode 100644 index c8097e1..0000000 --- a/libmessaging-menu/messaging-menu-app.h +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright 2012 Canonical Ltd. - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 3, as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranties of - * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . - * - * Authors: - * Lars Uebernickel - */ - -#ifndef __messaging_menu_app_h__ -#define __messaging_menu_app_h__ - -#include -#include "messaging-menu-message.h" - -G_BEGIN_DECLS - -#define MESSAGING_MENU_TYPE_APP messaging_menu_app_get_type() -#define MESSAGING_MENU_APP(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), MESSAGING_MENU_TYPE_APP, MessagingMenuApp)) -#define MESSAGING_MENU_APP_CLASS(c) (G_TYPE_CHECK_CLASS_CAST ((c), MESSAGING_MENU_TYPE_APP, MessagingMenuAppClass)) -#define MESSAGING_MENU_IS_APP(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), MESSAGING_MENU_TYPE_APP)) - -/** - * MessagingMenuStatus: - * @MESSAGING_MENU_STATUS_AVAILABLE: available - * @MESSAGING_MENU_STATUS_AWAY: away - * @MESSAGING_MENU_STATUS_BUSY: busy - * @MESSAGING_MENU_STATUS_INVISIBLE: invisible - * @MESSAGING_MENU_STATUS_OFFLINE: offline - * - * An enumeration for the possible chat statuses the messaging menu can be in. - */ -typedef enum { - MESSAGING_MENU_STATUS_AVAILABLE, - MESSAGING_MENU_STATUS_AWAY, - MESSAGING_MENU_STATUS_BUSY, - MESSAGING_MENU_STATUS_INVISIBLE, - MESSAGING_MENU_STATUS_OFFLINE -} MessagingMenuStatus; - - -typedef GObjectClass MessagingMenuAppClass; -typedef struct _MessagingMenuApp MessagingMenuApp; - -GType messaging_menu_app_get_type (void) G_GNUC_CONST; - -MessagingMenuApp * messaging_menu_app_new (const gchar *desktop_id); - -void messaging_menu_app_register (MessagingMenuApp *app); -void messaging_menu_app_unregister (MessagingMenuApp *app); - -void messaging_menu_app_set_status (MessagingMenuApp *app, - MessagingMenuStatus status); - -void messaging_menu_app_insert_source (MessagingMenuApp *app, - gint position, - const gchar *id, - GIcon *icon, - const gchar *label); - -void messaging_menu_app_append_source (MessagingMenuApp *app, - const gchar *id, - GIcon *icon, - const gchar *label); - -void messaging_menu_app_insert_source_with_count (MessagingMenuApp *app, - gint position, - const gchar *id, - GIcon *icon, - const gchar *label, - guint count); - -void messaging_menu_app_append_source_with_count (MessagingMenuApp *app, - const gchar *id, - GIcon *icon, - const gchar *label, - guint count); - -void messaging_menu_app_insert_source_with_time (MessagingMenuApp *app, - gint position, - const gchar *id, - GIcon *icon, - const gchar *label, - gint64 time); - -void messaging_menu_app_append_source_with_time (MessagingMenuApp *app, - const gchar *id, - GIcon *icon, - const gchar *label, - gint64 time); - -void messaging_menu_app_append_source_with_string (MessagingMenuApp *app, - const gchar *id, - GIcon *icon, - const gchar *label, - const gchar *str); - -void messaging_menu_app_insert_source_with_string (MessagingMenuApp *app, - gint position, - const gchar *id, - GIcon *icon, - const gchar *label, - const gchar *str); - -void messaging_menu_app_remove_source (MessagingMenuApp *app, - const gchar *source_id); - -gboolean messaging_menu_app_has_source (MessagingMenuApp *app, - const gchar *source_id); - -void messaging_menu_app_set_source_label (MessagingMenuApp *app, - const gchar *source_id, - const gchar *label); - -void messaging_menu_app_set_source_icon (MessagingMenuApp *app, - const gchar *source_id, - GIcon *icon); - -void messaging_menu_app_set_source_count (MessagingMenuApp *app, - const gchar *source_id, - guint count); - -void messaging_menu_app_set_source_time (MessagingMenuApp *app, - const gchar *source_id, - gint64 time); - -void messaging_menu_app_set_source_string (MessagingMenuApp *app, - const gchar *source_id, - const gchar *str); - -void messaging_menu_app_draw_attention (MessagingMenuApp *app, - const gchar *source_id); - -void messaging_menu_app_remove_attention (MessagingMenuApp *app, - const gchar *source_id); - -void messaging_menu_app_append_message (MessagingMenuApp *app, - MessagingMenuMessage *msg, - const gchar *source_id, - gboolean notify); - -MessagingMenuMessage * messaging_menu_app_get_message (MessagingMenuApp *app, - const gchar *id); - -void messaging_menu_app_remove_message (MessagingMenuApp *app, - MessagingMenuMessage *msg); - -void messaging_menu_app_remove_message_by_id (MessagingMenuApp *app, - const gchar *id); - -G_END_DECLS - -#endif diff --git a/libmessaging-menu/messaging-menu-message.c b/libmessaging-menu/messaging-menu-message.c deleted file mode 100644 index b81cbea..0000000 --- a/libmessaging-menu/messaging-menu-message.c +++ /dev/null @@ -1,547 +0,0 @@ -/* - * Copyright 2012 Canonical Ltd. - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 3, as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranties of - * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . - * - * Authors: - * Lars Uebernickel - */ - -#include "messaging-menu-message.h" - -typedef GObjectClass MessagingMenuMessageClass; - -struct _MessagingMenuMessage -{ - GObject parent; - - gchar *id; - GIcon *icon; - gchar *title; - gchar *subtitle; - gchar *body; - gint64 time; - gboolean draws_attention; - - GSList *actions; -}; - -G_DEFINE_TYPE (MessagingMenuMessage, messaging_menu_message, G_TYPE_OBJECT); - -enum -{ - PROP_0, - PROP_ID, - PROP_ICON, - PROP_TITLE, - PROP_SUBTITLE, - PROP_BODY, - PROP_TIME, - PROP_DRAWS_ATTENTION, - NUM_PROPERTIES -}; - -static GParamSpec *properties[NUM_PROPERTIES]; - -typedef struct -{ - gchar *id; - gchar *label; - GVariantType *parameter_type; - GVariant *parameter_hint; -} Action; - -static void -action_free (gpointer data) -{ - Action *action = data; - - g_free (action->id); - g_free (action->label); - - if (action->parameter_type) - g_variant_type_free (action->parameter_type); - - if (action->parameter_hint) - g_variant_unref (action->parameter_hint); - - g_slice_free (Action, action); -} - -static void -messaging_menu_message_dispose (GObject *object) -{ - MessagingMenuMessage *msg = MESSAGING_MENU_MESSAGE (object); - - g_clear_object (&msg->icon); - - G_OBJECT_CLASS (messaging_menu_message_parent_class)->dispose (object); -} - -static void -messaging_menu_message_finalize (GObject *object) -{ - MessagingMenuMessage *msg = MESSAGING_MENU_MESSAGE (object); - - g_free (msg->id); - g_free (msg->title); - g_free (msg->subtitle); - g_free (msg->body); - - g_slist_free_full (msg->actions, action_free); - msg->actions = NULL; - - G_OBJECT_CLASS (messaging_menu_message_parent_class)->finalize (object); -} - -static void -messaging_menu_message_get_property (GObject *object, - guint property_id, - GValue *value, - GParamSpec *pspec) -{ - MessagingMenuMessage *msg = MESSAGING_MENU_MESSAGE (object); - - switch (property_id) - { - case PROP_ID: - g_value_set_string (value, msg->id); - break; - - case PROP_ICON: - g_value_set_object (value, msg->icon); - break; - - case PROP_TITLE: - g_value_set_string (value, msg->title); - break; - - case PROP_SUBTITLE: - g_value_set_string (value, msg->subtitle); - break; - - case PROP_BODY: - g_value_set_string (value, msg->body); - - case PROP_TIME: - g_value_set_int64 (value, msg->time); - break; - - case PROP_DRAWS_ATTENTION: - g_value_set_boolean (value, msg->draws_attention); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); - } -} - -static void -messaging_menu_message_set_property (GObject *object, - guint property_id, - const GValue *value, - GParamSpec *pspec) -{ - MessagingMenuMessage *msg = MESSAGING_MENU_MESSAGE (object); - - switch (property_id) - { - case PROP_ID: - msg->id = g_value_dup_string (value); - break; - - case PROP_ICON: - msg->icon = g_value_dup_object (value); - break; - - case PROP_TITLE: - msg->title = g_value_dup_string (value); - break; - - case PROP_SUBTITLE: - msg->subtitle = g_value_dup_string (value); - break; - - case PROP_BODY: - msg->body = g_value_dup_string (value); - - case PROP_TIME: - msg->time = g_value_get_int64 (value); - break; - - case PROP_DRAWS_ATTENTION: - messaging_menu_message_set_draws_attention (msg, g_value_get_boolean (value)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); - } -} - -static void -messaging_menu_message_class_init (MessagingMenuMessageClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->dispose = messaging_menu_message_dispose; - object_class->finalize = messaging_menu_message_finalize; - object_class->get_property = messaging_menu_message_get_property; - object_class->set_property = messaging_menu_message_set_property; - - properties[PROP_ID] = g_param_spec_string ("id", "Id", - "Unique id of the message", - NULL, - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_READWRITE | - G_PARAM_STATIC_STRINGS); - - properties[PROP_ICON] = g_param_spec_object ("icon", "Icon", - "Icon of the message", - G_TYPE_ICON, - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_READWRITE | - G_PARAM_STATIC_STRINGS); - - properties[PROP_TITLE] = g_param_spec_string ("title", "Title", - "Title of the message", - NULL, - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_READWRITE | - G_PARAM_STATIC_STRINGS); - - properties[PROP_SUBTITLE] = g_param_spec_string ("subtitle", "Subtitle", - "Subtitle of the message", - NULL, - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_READWRITE | - G_PARAM_STATIC_STRINGS); - - properties[PROP_BODY] = g_param_spec_string ("body", "Body", - "First lines of the body of the message", - NULL, - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_READWRITE | - G_PARAM_STATIC_STRINGS); - - properties[PROP_TIME] = g_param_spec_int64 ("time", "Time", - "Time the message was sent, in microseconds", 0, G_MAXINT64, 0, - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_READWRITE | - G_PARAM_STATIC_STRINGS); - - properties[PROP_DRAWS_ATTENTION] = g_param_spec_boolean ("draws-attention", "Draws attention", - "Whether the message should draw attention", - FALSE, - G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); - - g_object_class_install_properties (klass, NUM_PROPERTIES, properties); - - /** - * MessagingMenuMessage::activate: - * @msg: the #MessagingMenuMessage - * @action: (allow-none): the id of activated action, or %NULL - * @parameter: (allow-none): activation parameter, or %NULL - * - * Emitted when the user has activated the message. The message is - * immediately removed from the application's menu, handlers of this - * signal do not need to call messaging_menu_app_remove_message(). - */ - g_signal_new ("activate", - MESSAGING_MENU_TYPE_MESSAGE, - G_SIGNAL_RUN_FIRST | G_SIGNAL_DETAILED, - 0, - NULL, NULL, - g_cclosure_marshal_generic, - G_TYPE_NONE, 2, - G_TYPE_STRING, - G_TYPE_VARIANT); -} - -static void -messaging_menu_message_init (MessagingMenuMessage *self) -{ -} - -/** - * messaging_menu_message_new: - * @id: unique id of the message - * @icon: (transfer full) (allow-none): a #GIcon representing the message - * @title: the title of the message - * @subtitle: (allow-none): the subtitle of the message - * @body: (allow-none): the message body - * @time: the time the message was received - * - * Creates a new #MessagingMenuMessage. - * - * Returns: (transfer full): a new #MessagingMenuMessage - */ -MessagingMenuMessage * -messaging_menu_message_new (const gchar *id, - GIcon *icon, - const gchar *title, - const gchar *subtitle, - const gchar *body, - gint64 time) -{ - g_return_val_if_fail (id != NULL, NULL); - g_return_val_if_fail (title != NULL, NULL); - - return g_object_new (MESSAGING_MENU_TYPE_MESSAGE, - "id", id, - "icon", icon, - "title", title, - "subtitle", subtitle, - "body", body, - "time", time, - NULL); -} - -/** - * messaging_menu_message_get_id: - * @msg: a #MessagingMenuMessage - * - * Returns: the unique id of @msg - */ -const gchar * -messaging_menu_message_get_id (MessagingMenuMessage *msg) -{ - g_return_val_if_fail (MESSAGING_MENU_IS_MESSAGE (msg), NULL); - - return msg->id; -} - -/** - * messaging_menu_message_get_icon: - * @msg: a #MessagingMenuMessage - * - * Returns: (transfer none): the icon of @msg - */ -GIcon * -messaging_menu_message_get_icon (MessagingMenuMessage *msg) -{ - g_return_val_if_fail (MESSAGING_MENU_IS_MESSAGE (msg), NULL); - - return msg->icon; -} - -/** - * messaging_menu_message_get_title: - * @msg: a #MessagingMenuMessage - * - * Returns: the title of @msg - */ -const gchar * -messaging_menu_message_get_title (MessagingMenuMessage *msg) -{ - g_return_val_if_fail (MESSAGING_MENU_IS_MESSAGE (msg), NULL); - - return msg->title; -} - -/** - * messaging_menu_message_get_subtitle: - * @msg: a #MessagingMenuMessage - * - * Returns: the subtitle of @msg - */ -const gchar * -messaging_menu_message_get_subtitle (MessagingMenuMessage *msg) -{ - g_return_val_if_fail (MESSAGING_MENU_IS_MESSAGE (msg), NULL); - - return msg->subtitle; -} - -/** - * messaging_menu_message_get_body: - * @msg: a #MessagingMenuMessage - * - * Returns: the body of @msg - */ -const gchar * -messaging_menu_message_get_body (MessagingMenuMessage *msg) -{ - g_return_val_if_fail (MESSAGING_MENU_IS_MESSAGE (msg), NULL); - - return msg->body; -} - -/** - * messaging_menu_message_get_time: - * @msg: a #MessagingMenuMessage - * - * Returns: the time at which @msg was received - */ -gint64 -messaging_menu_message_get_time (MessagingMenuMessage *msg) -{ - g_return_val_if_fail (MESSAGING_MENU_IS_MESSAGE (msg), 0); - - return msg->time; -} - -/** - * messaging_menu_message_get_draws_attention: - * @msg: a #MessagingMenuMessage - * - * Returns: whether @msg is drawing attention - */ -gboolean -messaging_menu_message_get_draws_attention (MessagingMenuMessage *msg) -{ - g_return_val_if_fail (MESSAGING_MENU_IS_MESSAGE (msg), FALSE); - - return msg->draws_attention; -} - -/** - * messaging_menu_message_set_draws_attention: - * @msg: a #MessagingMenuMessage - * @draws_attention: whether @msg should draw attention - * - * Sets whether @msg is drawing attention. - */ -void -messaging_menu_message_set_draws_attention (MessagingMenuMessage *msg, - gboolean draws_attention) -{ - g_return_if_fail (MESSAGING_MENU_IS_MESSAGE (msg)); - - msg->draws_attention = draws_attention; - g_object_notify_by_pspec (G_OBJECT (msg), properties[PROP_DRAWS_ATTENTION]); -} - -/** - * messaging_menu_message_add_action: - * @msg: a #MessagingMenuMessage - * @id: unique id of the action - * @label: (allow-none): label of the action - * @parameter_type: (allow-none): a #GVariantType - * @parameter_hint: (allow-none): a #GVariant suggesting a valid range - * for parameters - * - * Adds an action with @id and @label to @message. Actions are an - * alternative way for users to activate a message. Note that messages - * can still be activated without an action. - * - * If @parameter_type is non-%NULL, the action is able to receive user - * input in addition to simply activating the action. Currently, only - * string parameters are supported. - * - * A list of predefined parameters can be supplied as a #GVariant array - * of @parameter_type in @parameter_hint. If @parameter_hint is - * floating, it will be consumed. - * - * It is recommended to add at most two actions to a message. - */ -void -messaging_menu_message_add_action (MessagingMenuMessage *msg, - const gchar *id, - const gchar *label, - const GVariantType *parameter_type, - GVariant *parameter_hint) -{ - Action *action; - - g_return_if_fail (MESSAGING_MENU_IS_MESSAGE (msg)); - g_return_if_fail (id != NULL); - - action = g_slice_new (Action); - action->id = g_strdup (id); - action->label = g_strdup (label); - action->parameter_type = parameter_type ? g_variant_type_copy (parameter_type) : NULL; - action->parameter_hint = parameter_hint ? g_variant_ref_sink (parameter_hint) : NULL; - - msg->actions = g_slist_append (msg->actions, action); -} - -static GVariant * -action_to_variant (Action *action) -{ - GVariantBuilder builder; - - g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); - - g_variant_builder_add (&builder, "{sv}", "name", g_variant_new_string (action->id)); - - if (action->label) - g_variant_builder_add (&builder, "{sv}", "label", g_variant_new_string (action->label)); - - if (action->parameter_type) - { - gchar *type = g_variant_type_dup_string (action->parameter_type); - g_variant_builder_add (&builder, "{sv}", "parameter-type", g_variant_new_signature (type)); - g_free (type); - } - - if (action->parameter_hint) - g_variant_builder_add (&builder, "{sv}", "parameter-hint", action->parameter_hint); - - return g_variant_builder_end (&builder); -} - -/* - * _messaging_menu_message_to_variant: - * @msg: a #MessagingMenuMessage - * - * Serializes @msg to a #GVariant of the form (sssssxaa{sv}b): - * - * id - * icon - * title - * subtitle - * body - * time - * array of action dictionaries - * draws_attention - * - * Returns: a new floating #GVariant instance - */ -GVariant * -_messaging_menu_message_to_variant (MessagingMenuMessage *msg) -{ - GVariantBuilder builder; - GSList *it; - - g_return_val_if_fail (MESSAGING_MENU_IS_MESSAGE (msg), NULL); - - g_variant_builder_init (&builder, G_VARIANT_TYPE ("(sssssxaa{sv}b)")); - - g_variant_builder_add (&builder, "s", msg->id); - - if (msg->icon) - { - gchar *iconstr; - - iconstr = g_icon_to_string (msg->icon); - g_variant_builder_add (&builder, "s", iconstr); - - g_free (iconstr); - } - else - g_variant_builder_add (&builder, "s", ""); - - g_variant_builder_add (&builder, "s", msg->title ? msg->title : ""); - g_variant_builder_add (&builder, "s", msg->subtitle ? msg->subtitle : ""); - g_variant_builder_add (&builder, "s", msg->body ? msg->body : ""); - g_variant_builder_add (&builder, "x", msg->time); - - g_variant_builder_open (&builder, G_VARIANT_TYPE ("aa{sv}")); - for (it = msg->actions; it; it = it->next) - g_variant_builder_add_value (&builder, action_to_variant (it->data)); - g_variant_builder_close (&builder); - - g_variant_builder_add (&builder, "b", msg->draws_attention); - - return g_variant_builder_end (&builder); -} diff --git a/libmessaging-menu/messaging-menu-message.h b/libmessaging-menu/messaging-menu-message.h deleted file mode 100644 index 4708246..0000000 --- a/libmessaging-menu/messaging-menu-message.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2012 Canonical Ltd. - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 3, as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranties of - * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . - * - * Authors: - * Lars Uebernickel - */ - -#ifndef __messaging_menu_message_h__ -#define __messaging_menu_message_h__ - -#include - -G_BEGIN_DECLS - -#define MESSAGING_MENU_TYPE_MESSAGE (messaging_menu_message_get_type ()) -#define MESSAGING_MENU_MESSAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MESSAGING_MENU_TYPE_MESSAGE, MessagingMenuMessage)) -#define MESSAGING_MENU_MESSAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MESSAGING_MENU_TYPE_MESSAGE, MessagingMenuMessageClass)) -#define MESSAGING_MENU_IS_MESSAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MESSAGING_MENU_TYPE_MESSAGE)) -#define MESSAGING_MENU_IS_MESSAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MESSAGING_MENU_TYPE_MESSAGE)) -#define MESSAGING_MENU_MESSAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MESSAGING_MENU_TYPE_MESSAGE, MessagingMenuMessageClass)) - -typedef struct _MessagingMenuMessage MessagingMenuMessage; - -GType messaging_menu_message_get_type (void) G_GNUC_CONST; - -MessagingMenuMessage * messaging_menu_message_new (const gchar *id, - GIcon *icon, - const gchar *title, - const gchar *subtitle, - const gchar *body, - gint64 time); - -const gchar * messaging_menu_message_get_id (MessagingMenuMessage *msg); - -GIcon * messaging_menu_message_get_icon (MessagingMenuMessage *msg); - -const gchar * messaging_menu_message_get_title (MessagingMenuMessage *msg); - -const gchar * messaging_menu_message_get_subtitle (MessagingMenuMessage *msg); - -const gchar * messaging_menu_message_get_body (MessagingMenuMessage *msg); - -gint64 messaging_menu_message_get_time (MessagingMenuMessage *msg); - -gboolean messaging_menu_message_get_draws_attention (MessagingMenuMessage *msg); - -void messaging_menu_message_set_draws_attention (MessagingMenuMessage *msg, - gboolean draws_attention); - -void messaging_menu_message_add_action (MessagingMenuMessage *msg, - const gchar *id, - const gchar *label, - const GVariantType *parameter_type, - GVariant *parameter_hint); - -G_END_DECLS - -#endif diff --git a/libmessaging-menu/messaging-menu.c b/libmessaging-menu/messaging-menu.c new file mode 100644 index 0000000..467a67a --- /dev/null +++ b/libmessaging-menu/messaging-menu.c @@ -0,0 +1,1200 @@ +/* + * Copyright 2012 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . + * + * Authors: + * Lars Uebernickel + */ + +#include "messaging-menu.h" +#include "indicator-messages-service.h" + +#include + +/** + * SECTION:messaging-menu + * @title: MessagingMenuApp + * @short_description: An application section in the messaging menu + * @include: messaging-menu.h + * + * A #MessagingMenuApp represents an application section in the + * Messaging Menu. An application section is tied to an installed + * application through a desktop file id, which must be passed to + * messaging_menu_app_new(). + * + * To register the application with the Messaging Menu, call + * messaging_menu_app_register(). This signifies that the application + * should be present in the menu and be marked as "running". + * + * The first menu item in an application section represents the + * application itself, using the name and icon found in the associated + * desktop file. Activating this item starts the application. + * + * Following the application item, the Messaging Menu inserts all + * shortcut actions found in the desktop file. Actions whose + * NotShowIn keyword contains "Messaging Menu" or whose + * OnlyShowIn keyword does not contain "Messaging Menu" + * will not appear (the + * desktop file specification contains a detailed explanation of + * shortcut actions.) An application cannot add, remove, or change + * these shortcut items while it is running. + * + * Next, an application section contains menu items for message sources. + * What exactly constitutes a message source depends on the type of + * application: an email client's message sources are folders + * containing new messages, while those of a chat program are persons + * that have contacted the user. + * + * A message source is represented in the menu by a label and optionally + * also an icon. It can be associated with either a count, a time, or + * an arbitrary string, which will appear on the right side of the menu + * item. + * + * When the user activates a source, the source is immediately removed + * from the menu and the "activate-source" signal is emitted. + * + * Applications should always expose all the message sources available. + * However, the Messaging Menu might limit the amount of sources it + * displays to the user. + * + * The Messaging Menu offers users a way to set their chat status + * (available, away, busy, invisible, or offline) for multiple + * applications at once. Applications that appear in the Messaging Menu + * can integrate with this by setting the + * "X-MessagingMenu-UsesChatSection" key in their desktop file to True. + * Use messaging_menu_app_set_status() to signify that the application's + * chat status has changed. When the user changes status through the + * Messaging Menu, the ::status-changed signal will be emitted. + * + * If the application stops running without calling + * messaging_menu_app_unregister(), it will be marked as "not running". + * Its application and shortcut items stay in the menu, but all message + * sources are removed. If messaging_menu_app_unregister() is called, + * the application section is removed completely. + * + * More information about the design and recommended usage of the + * Messaging Menu is available at https://wiki.ubuntu.com/MessagingMenu. + */ + +/** + * MessagingMenuApp: + * + * #MessagingMenuApp is an opaque structure. + */ +struct _MessagingMenuApp +{ + GObject parent_instance; + + GDesktopAppInfo *appinfo; + int registered; /* -1 for unknown */ + MessagingMenuStatus status; + gboolean status_set; + GSimpleActionGroup *source_actions; + GMenu *menu; + GDBusConnection *bus; + + IndicatorMessagesService *messages_service; + guint watch_id; + guint action_export_id; + guint menu_export_id; + + GCancellable *cancellable; +}; + +G_DEFINE_TYPE (MessagingMenuApp, messaging_menu_app, G_TYPE_OBJECT); + +enum { + PROP_0, + PROP_DESKTOP_ID, + N_PROPERTIES +}; + +enum { + ACTIVATE_SOURCE, + STATUS_CHANGED, + N_SIGNALS +}; + +static GParamSpec *properties[N_PROPERTIES]; +static guint signals[N_SIGNALS]; + +static const gchar *status_ids[] = { "available", "away", "busy", "invisible", "offline" }; + +static void global_status_changed (IndicatorMessagesService *service, + const gchar *status_str, + gpointer user_data); + +static gchar * +messaging_menu_app_get_dbus_object_path (MessagingMenuApp *app) +{ + gchar *path; + + if (!app->appinfo) + return NULL; + + path = g_strconcat ("/com/canonical/indicator/messages/", + g_app_info_get_id (G_APP_INFO (app->appinfo)), + NULL); + + g_strcanon (path, "/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", '_'); + + return path; +} + +static void +export_menus_and_actions (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + MessagingMenuApp *app = user_data; + GError *error = NULL; + gchar *object_path; + + object_path = messaging_menu_app_get_dbus_object_path (app); + if (!object_path) + return; + + app->bus = g_bus_get_finish (res, &error); + if (app->bus == NULL) + { + g_warning ("unable to connect to session bus: %s", error->message); + g_error_free (error); + return; + } + + app->action_export_id = g_dbus_connection_export_action_group (app->bus, + object_path, + G_ACTION_GROUP (app->source_actions), + &error); + if (!app->action_export_id) + { + g_warning ("unable to export action group: %s", error->message); + g_clear_error (&error); + } + + app->menu_export_id = g_dbus_connection_export_menu_model (app->bus, + object_path, + G_MENU_MODEL (app->menu), + &error); + if (!app->menu_export_id) + { + g_warning ("unable to export menu: %s", error->message); + g_clear_error (&error); + } + + g_free (object_path); +} + +static void +messaging_menu_app_set_desktop_id (MessagingMenuApp *app, + const gchar *desktop_id) +{ + g_return_if_fail (desktop_id != NULL); + + /* no need to clean up, it's construct only */ + app->appinfo = g_desktop_app_info_new (desktop_id); + if (app->appinfo == NULL) + { + g_warning ("could not find the desktop file for '%s'", + desktop_id); + } + + g_bus_get (G_BUS_TYPE_SESSION, + app->cancellable, + export_menus_and_actions, + app); +} + +static void +messaging_menu_app_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + MessagingMenuApp *app = MESSAGING_MENU_APP (object); + + switch (prop_id) + { + case PROP_DESKTOP_ID: + messaging_menu_app_set_desktop_id (app, g_value_get_string (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +messaging_menu_app_finalize (GObject *object) +{ + G_OBJECT_CLASS (messaging_menu_app_parent_class)->finalize (object); +} + +static void +messaging_menu_app_dispose (GObject *object) +{ + MessagingMenuApp *app = MESSAGING_MENU_APP (object); + + if (app->bus) + { + if (app->action_export_id > 0) + g_dbus_connection_unexport_action_group (app->bus, app->action_export_id); + + if (app->menu_export_id > 0) + g_dbus_connection_unexport_menu_model (app->bus, app->menu_export_id); + + app->action_export_id = 0; + app->menu_export_id = 0; + g_object_unref (app->bus); + app->bus = NULL; + } + + if (app->watch_id > 0) + { + g_bus_unwatch_name (app->watch_id); + app->watch_id = 0; + } + + if (app->cancellable) + { + g_cancellable_cancel (app->cancellable); + g_object_unref (app->cancellable); + app->cancellable = NULL; + } + + if (app->messages_service) + { + indicator_messages_service_call_application_stopped_running (app->messages_service, + g_app_info_get_id (G_APP_INFO (app->appinfo)), + NULL, NULL, NULL); + + g_signal_handlers_disconnect_by_func (app->messages_service, + global_status_changed, + app); + g_clear_object (&app->messages_service); + } + + g_clear_object (&app->appinfo); + g_clear_object (&app->source_actions); + g_clear_object (&app->menu); + + G_OBJECT_CLASS (messaging_menu_app_parent_class)->dispose (object); +} + +static void +messaging_menu_app_class_init (MessagingMenuAppClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + + object_class->set_property = messaging_menu_app_set_property; + object_class->finalize = messaging_menu_app_finalize; + object_class->dispose = messaging_menu_app_dispose; + + /** + * MessagingMenuApp:desktop-id: + * + * The desktop id of the application associated with this application + * section. Must be given when the #MessagingMenuApp is created. + */ + properties[PROP_DESKTOP_ID] = g_param_spec_string ("desktop-id", + "Desktop Id", + "The desktop id of the associated application", + NULL, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, N_PROPERTIES, properties); + + /** + * MessagingMenuApp::activate-source: + * @mmapp: the #MessagingMenuApp + * @source_id: the source id that was activated + * + * Emitted when the user has activated the message source with id + * @source_id. The source is immediately removed from the menu, + * handlers of this signal do not need to call + * messaging_menu_app_remove_source(). + */ + signals[ACTIVATE_SOURCE] = g_signal_new ("activate-source", + MESSAGING_MENU_TYPE_APP, + G_SIGNAL_RUN_FIRST | + G_SIGNAL_DETAILED, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, G_TYPE_STRING); + + /** + * MessagingMenuApp::status-changed: + * @mmapp: the #MessagingMenuApp + * @status: a #MessagingMenuStatus + * + * Emitted when the chat status is changed through the messaging menu. + * + * Applications which are registered to use the chat status should + * change their status to @status upon receiving this signal. Call + * messaging_menu_app_set_status() to acknowledge that the application + * changed its status. + */ + signals[STATUS_CHANGED] = g_signal_new ("status-changed", + MESSAGING_MENU_TYPE_APP, + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, 1, G_TYPE_INT); +} + +static void +created_messages_service (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + MessagingMenuApp *app = user_data; + GError *error = NULL; + + app->messages_service = indicator_messages_service_proxy_new_finish (result, &error); + if (!app->messages_service) + { + g_warning ("unable to connect to the mesaging menu service: %s", error->message); + g_error_free (error); + return; + } + + g_signal_connect (app->messages_service, "status-changed", + G_CALLBACK (global_status_changed), app); + + /* sync current status */ + if (app->registered == TRUE) + messaging_menu_app_register (app); + else if (app->registered == FALSE) + messaging_menu_app_unregister (app); + if (app->status_set) + messaging_menu_app_set_status (app, app->status); +} + +static void +indicator_messages_appeared (GDBusConnection *bus, + const gchar *name, + const gchar *name_owner, + gpointer user_data) +{ + MessagingMenuApp *app = user_data; + + indicator_messages_service_proxy_new (bus, + G_DBUS_PROXY_FLAGS_NONE, + "com.canonical.indicator.messages", + "/com/canonical/indicator/messages/service", + app->cancellable, + created_messages_service, + app); +} + +static void +indicator_messages_vanished (GDBusConnection *bus, + const gchar *name, + gpointer user_data) +{ + MessagingMenuApp *app = user_data; + + if (app->messages_service) + { + g_signal_handlers_disconnect_by_func (app->messages_service, + global_status_changed, + app); + g_clear_object (&app->messages_service); + } +} + +static void +messaging_menu_app_init (MessagingMenuApp *app) +{ + app->registered = -1; + app->status_set = FALSE; + app->bus = NULL; + + app->action_export_id = 0; + app->menu_export_id = 0; + + app->cancellable = g_cancellable_new (); + + app->source_actions = g_simple_action_group_new (); + app->menu = g_menu_new (); + + app->cancellable = g_cancellable_new (); + + app->watch_id = g_bus_watch_name (G_BUS_TYPE_SESSION, + "com.canonical.indicator.messages", + G_BUS_NAME_WATCHER_FLAGS_NONE, + indicator_messages_appeared, + indicator_messages_vanished, + app, + NULL); +} + +/** + * messaging_menu_new: + * @desktop_id: a desktop file id. See g_desktop_app_info_new() + * + * Creates a new #MessagingMenuApp for the application associated with + * @desktop_id. + * + * The application will not show up (nor be marked as "running") in the + * Messaging Menu before messaging_menu_app_register() has been called. + * + * Returns: (transfer full): a new #MessagingMenuApp + */ +MessagingMenuApp * +messaging_menu_app_new (const gchar *desktop_id) +{ + return g_object_new (MESSAGING_MENU_TYPE_APP, + "desktop-id", desktop_id, + NULL); +} + +/** + * messaging_menu_app_register: + * @app: a #MessagingMenuApp + * + * Registers @app with the Messaging Menu. + * + * If the application doesn't already have a section in the Messaging + * Menu, one will be created for it. The application will also be + * marked as "running". + * + * The application will be marked as "not running" as soon as @app is + * destroyed. The application launcher as well as shortcut actions will + * remain in the menu. To completely remove the application section + * from the Messaging Menu, call messaging_menu_app_unregister(). + */ +void +messaging_menu_app_register (MessagingMenuApp *app) +{ + gchar *object_path; + + g_return_if_fail (MESSAGING_MENU_IS_APP (app)); + + app->registered = TRUE; + + /* state will be synced right after connecting to the service */ + if (!app->messages_service) + return; + + object_path = messaging_menu_app_get_dbus_object_path (app); + if (!object_path) + return; + + indicator_messages_service_call_register_application (app->messages_service, + g_app_info_get_id (G_APP_INFO (app->appinfo)), + object_path, + app->cancellable, + NULL, NULL); + + g_free (object_path); +} + +/** + * messaging_menu_app_unregister: + * @app: a #MessagingMenuApp + * + * Completely removes the @app from the Messaging Menu. If the + * application's launcher and shortcut actions should remain in the + * menu, destroying @app with g_object_unref() suffices. + * + * Note: @app will remain valid and usable after this call. + */ +void +messaging_menu_app_unregister (MessagingMenuApp *app) +{ + g_return_if_fail (MESSAGING_MENU_IS_APP (app)); + + app->registered = FALSE; + + /* state will be synced right after connecting to the service */ + if (!app->messages_service) + return; + + if (!app->appinfo) + return; + + indicator_messages_service_call_unregister_application (app->messages_service, + g_app_info_get_id (G_APP_INFO (app->appinfo)), + app->cancellable, + NULL, NULL); +} + +/** + * messaging_menu_app_set_status: + * @app: a #MessagingMenuApp + * @status: a #MessagingMenuStatus + * + * Notify the Messaging Menu that the chat status of @app has changed to + * @status. + * + * Connect to the ::status-changed signal to receive notification about + * the user changing their global chat status through the Messaging + * Menu. + * + * This function does nothing for applications whose desktop file does + * not include X-MessagingMenu-UsesChatSection. + */ +void +messaging_menu_app_set_status (MessagingMenuApp *app, + MessagingMenuStatus status) +{ + g_return_if_fail (MESSAGING_MENU_IS_APP (app)); + g_return_if_fail (status >= MESSAGING_MENU_STATUS_AVAILABLE && + status <= MESSAGING_MENU_STATUS_OFFLINE); + + app->status = status; + app->status_set = TRUE; + + /* state will be synced right after connecting to the service */ + if (!app->messages_service) + return; + + if (!app->appinfo) + return; + + indicator_messages_service_call_set_status (app->messages_service, + g_app_info_get_id (G_APP_INFO (app->appinfo)), + status_ids [status], + app->cancellable, + NULL, NULL); +} + +static int +status_from_string (const gchar *s) +{ + int i; + + if (!s) + return -1; + + for (i = 0; i <= MESSAGING_MENU_STATUS_OFFLINE; i++) + { + if (g_str_equal (s, status_ids[i])) + return i; + } + + return -1; +} + +static void +global_status_changed (IndicatorMessagesService *service, + const gchar *status_str, + gpointer user_data) +{ + MessagingMenuApp *app = user_data; + int status; + + status = status_from_string (status_str); + g_return_if_fail (status >= 0); + + g_signal_emit (app, signals[STATUS_CHANGED], 0, status); +} + +static void +source_action_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + MessagingMenuApp *app = user_data; + const gchar *name = g_action_get_name (G_ACTION (action)); + GQuark q = g_quark_from_string (name); + + messaging_menu_app_remove_source (app, name); + + g_signal_emit (app, signals[ACTIVATE_SOURCE], q, name); +} + +static void +messaging_menu_app_insert_source_action (MessagingMenuApp *app, + gint position, + const gchar *id, + GIcon *icon, + const gchar *label, + GVariant *state) +{ + GSimpleAction *action; + GMenuItem *menuitem; + + g_return_if_fail (MESSAGING_MENU_IS_APP (app)); + g_return_if_fail (id != NULL); + + if (g_simple_action_group_lookup (app->source_actions, id)) + { + g_warning ("a source with id '%s' already exists", id); + return; + } + + action = g_simple_action_new_stateful (id, NULL, state); + g_signal_connect (action, "activate", + G_CALLBACK (source_action_activated), app); + g_simple_action_group_insert (app->source_actions, G_ACTION (action)); + g_object_unref (action); + + menuitem = g_menu_item_new (label, NULL); + g_menu_item_set_action_and_target_value (menuitem, id, NULL); + g_menu_item_set_attribute (menuitem, "x-canonical-type", "s", "ImSourceMenuItem"); + if (icon) + { + gchar *iconstr = g_icon_to_string (icon); + g_menu_item_set_attribute (menuitem, "x-canonical-icon", "s", iconstr); + g_free (iconstr); + } + g_menu_insert_item (app->menu, position, menuitem); + g_object_unref (menuitem); +} + +static GSimpleAction * +messaging_menu_app_get_source_action (MessagingMenuApp *app, + const gchar *source_id) + +{ + GAction *action; + + g_return_val_if_fail (MESSAGING_MENU_IS_APP (app), NULL); + g_return_val_if_fail (source_id != NULL, NULL); + + action = g_simple_action_group_lookup (app->source_actions, source_id); + if (action == NULL) + g_warning ("a source with id '%s' doesn't exist", source_id); + + return G_SIMPLE_ACTION (action); +} + +static void +messaging_menu_app_set_source_action (MessagingMenuApp *app, + const gchar *source_id, + guint count, + gint64 time, + const gchar *string) +{ + GSimpleAction *action; + GVariant *state; + gboolean draws_attention; + GVariant *new_state; + + action = messaging_menu_app_get_source_action (app, source_id); + if (!action) + return; + + state = g_action_get_state (G_ACTION (action)); + g_variant_get_child (state, 3, "b", &draws_attention); + + new_state = g_variant_new ("(uxsb)", count, time, string, draws_attention); + g_simple_action_set_state (action, new_state); + + g_variant_unref (state); +} + +static void +messaging_menu_app_set_draws_attention (MessagingMenuApp *app, + const gchar *source_id, + gboolean draws_attention) +{ + GSimpleAction *action; + GVariant *state; + guint count; + gint64 time; + const gchar *string; + GVariant *new_state; + + action = messaging_menu_app_get_source_action (app, source_id); + if (!action) + return; + + state = g_action_get_state (G_ACTION (action)); + g_variant_get (state, "(ux&sb)", &count, &time, &string, NULL); + + new_state = g_variant_new ("(uxsb)", count, time, string, draws_attention); + g_simple_action_set_state (action, new_state); + + g_variant_unref (state); +} + +/** + * messaging_menu_app_insert_source: + * @app: a #MessagingMenuApp + * @position: the position at which to insert the source + * @id: a unique identifier for the source to be added + * @icon: the icon associated with the source + * @label: a user-visible string best describing the source + * + * Inserts a new message source into the section representing @app. Equivalent + * to calling messaging_menu_app_insert_source_with_time() with the current + * time. + * + * It is an error to insert a source with an @id which already exists. Use + * messaging_menu_app_has_source() to find out whether there is such a source. + */ +void +messaging_menu_app_insert_source (MessagingMenuApp *app, + gint position, + const gchar *id, + GIcon *icon, + const gchar *label) +{ + messaging_menu_app_insert_source_with_time (app, position, id, icon, label, + g_get_real_time ()); +} + +/** + * messaging_menu_app_append_source: + * @app: a #MessagingMenuApp + * @id: a unique identifier for the source to be added + * @icon: (allow-none): the icon associated with the source + * @label: a user-visible string best describing the source + * + * Appends a new message source to the end of the section representing @app. + * Equivalent to calling messaging_menu_app_append_source_with_time() with the + * current time. + * + * It is an error to add a source with an @id which already exists. Use + * messaging_menu_app_has_source() to find out whether there is such a source. + */ +void +messaging_menu_app_append_source (MessagingMenuApp *app, + const gchar *id, + GIcon *icon, + const gchar *label) +{ + messaging_menu_app_insert_source (app, -1, id, icon, label); +} + +/** + * messaging_menu_app_insert_source_with_count: + * @app: a #MessagingMenuApp + * @position: the position at which to insert the source + * @id: a unique identifier for the source to be added + * @icon: (allow-none): the icon associated with the source + * @label: a user-visible string best describing the source + * @count: the count for the source + * + * Inserts a new message source into the section representing @app and + * initializes it with @count. + * + * To update the count, use messaging_menu_app_set_source_count(). + * + * It is an error to insert a source with an @id which already exists. Use + * messaging_menu_app_has_source() to find out whether there is such a source. + */ +void +messaging_menu_app_insert_source_with_count (MessagingMenuApp *app, + gint position, + const gchar *id, + GIcon *icon, + const gchar *label, + guint count) +{ + messaging_menu_app_insert_source_action (app, position, id, icon, label, + g_variant_new ("(uxsb)", count, 0, "", FALSE)); +} + +/** + * messaging_menu_app_append_source_with_count: + * @app: a #MessagingMenuApp + * @id: a unique identifier for the source to be added + * @icon: (allow-none): the icon associated with the source + * @label: a user-visible string best describing the source + * @count: the count for the source + * + * Appends a new message source to the end of the section representing @app and + * initializes it with @count. + * + * To update the count, use messaging_menu_app_set_source_count(). + * + * It is an error to add a source with an @id which already exists. Use + * messaging_menu_app_has_source() to find out whether there is such a source. + */ +void messaging_menu_app_append_source_with_count (MessagingMenuApp *app, + const gchar *id, + GIcon *icon, + const gchar *label, + guint count) +{ + messaging_menu_app_insert_source_with_count (app, -1, id, icon, label, count); +} + +/** + * messaging_menu_app_insert_source_with_time: + * @app: a #MessagingMenuApp + * @position: the position at which to insert the source + * @id: a unique identifier for the source to be added + * @icon: (allow-none): the icon associated with the source + * @label: a user-visible string best describing the source + * @time: the time when the source was created, in microseconds + * + * Inserts a new message source into the section representing @app and + * initializes it with @time. Use messaging_menu_app_insert_source() to + * insert a source with the current time. + * + * To change the time, use messaging_menu_app_set_source_time(). + * + * It is an error to insert a source with an @id which already exists. Use + * messaging_menu_app_has_source() to find out whether there is such a source. + */ +void +messaging_menu_app_insert_source_with_time (MessagingMenuApp *app, + gint position, + const gchar *id, + GIcon *icon, + const gchar *label, + gint64 time) +{ + messaging_menu_app_insert_source_action (app, position, id, icon, label, + g_variant_new ("(uxsb)", 0, time, "", FALSE)); +} + +/** + * messaging_menu_app_append_source_with_time: + * @app: a #MessagingMenuApp + * @id: a unique identifier for the source to be added + * @icon: (allow-none): the icon associated with the source + * @label: a user-visible string best describing the source + * @time: the time when the source was created, in microseconds + * + * Appends a new message source to the end of the section representing + * @app and initializes it with @time. Use + * messaging_menu_app_append_source() to append a source with the + * current time. + * + * To change the time, use messaging_menu_app_set_source_time(). + * + * It is an error to insert a source with an @id which already exists. Use + * messaging_menu_app_has_source() to find out whether there is such a source. + */ +void +messaging_menu_app_append_source_with_time (MessagingMenuApp *app, + const gchar *id, + GIcon *icon, + const gchar *label, + gint64 time) +{ + messaging_menu_app_insert_source_with_time (app, -1, id, icon, label, time); +} + +/** + * messaging_menu_app_insert_source_with_string: + * @app: a #MessagingMenuApp + * @position: the position at which to insert the source + * @id: a unique identifier for the source to be added + * @icon: (allow-none): the icon associated with the source + * @label: a user-visible string best describing the source + * @str: a string associated with the source + * + * Inserts a new message source into the section representing @app and + * initializes it with @str. + * + * To update the string, use messaging_menu_app_set_source_string(). + * + * It is an error to insert a source with an @id which already exists. Use + * messaging_menu_app_has_source() to find out whether there is such a source. + */ +void +messaging_menu_app_insert_source_with_string (MessagingMenuApp *app, + gint position, + const gchar *id, + GIcon *icon, + const gchar *label, + const gchar *str) +{ + messaging_menu_app_insert_source_action (app, position, id, icon, label, + g_variant_new ("(uxsb)", 0, 0, str, FALSE)); +} + +/** + * messaging_menu_app_append_source_with_string: + * @app: a #MessagingMenuApp + * @id: a unique identifier for the source to be added + * @icon: (allow-none): the icon associated with the source + * @label: a user-visible string best describing the source + * @str: a string associated with the source + * + * Appends a new message source to the end of the section representing @app and + * initializes it with @str. + * + * To update the string, use messaging_menu_app_set_source_string(). + * + * It is an error to insert a source with an @id which already exists. Use + * messaging_menu_app_has_source() to find out whether there is such a source. + */ +void +messaging_menu_app_append_source_with_string (MessagingMenuApp *app, + const gchar *id, + GIcon *icon, + const gchar *label, + const gchar *str) +{ + messaging_menu_app_insert_source_with_string (app, -1, id, icon, label, str); +} + +/** + * messaging_menu_app_remove_source: + * @app: a #MessagingMenuApp + * @source_id: the id of the source to remove + * + * Removes the source corresponding to @source_id from the menu. + */ +void +messaging_menu_app_remove_source (MessagingMenuApp *app, + const gchar *source_id) +{ + int n_items; + int i; + + g_return_if_fail (MESSAGING_MENU_IS_APP (app)); + g_return_if_fail (source_id != NULL); + + if (g_simple_action_group_lookup (app->source_actions, source_id) == NULL) + return; + + n_items = g_menu_model_get_n_items (G_MENU_MODEL (app->menu)); + for (i = 0; i < n_items; i++) + { + gchar *action; + + if (g_menu_model_get_item_attribute (G_MENU_MODEL (app->menu), i, + "action", "s", &action)) + { + if (!g_strcmp0 (action, source_id)) + { + g_menu_remove (app->menu, i); + break; + } + + g_free (action); + } + } + + g_simple_action_group_remove (app->source_actions, source_id); +} + +/** + * messaging_menu_app_has_source: + * @app: a #MessagingMenuApp + * @source_id: a source id + * + * Returns: TRUE if there is a source associated with @source_id + */ +gboolean +messaging_menu_app_has_source (MessagingMenuApp *app, + const gchar *source_id) +{ + g_return_val_if_fail (MESSAGING_MENU_IS_APP (app), FALSE); + g_return_val_if_fail (source_id != NULL, FALSE); + + return g_simple_action_group_lookup (app->source_actions, source_id) != NULL; +} + +static GMenuItem * +g_menu_find_item_with_action (GMenu *menu, + const gchar *action, + gint *out_pos) +{ + gint i; + gint n_elements; + GMenuItem *item = NULL; + + n_elements = g_menu_model_get_n_items (G_MENU_MODEL (menu)); + + for (i = 0; i < n_elements && item == NULL; i++) + { + GVariant *attr; + + item = g_menu_item_new_from_model (G_MENU_MODEL (menu), i); + attr = g_menu_item_get_attribute_value (item, G_MENU_ATTRIBUTE_ACTION, G_VARIANT_TYPE_STRING); + + if (!g_str_equal (action, g_variant_get_string (attr, NULL))) + g_clear_object (&item); + + g_variant_unref (attr); + } + + if (item && out_pos) + *out_pos = i - 1; + + return item; +} + +static void +g_menu_replace_item (GMenu *menu, + gint pos, + GMenuItem *item) +{ + g_menu_remove (menu, pos); + g_menu_insert_item (menu, pos, item); +} + +/** + * messaging_menu_app_set_source_label: + * @app: a #MessagingMenuApp + * @source_id: a source id + * @label: the new label for the source + * + * Changes the label of @source_id to @label. + */ +void +messaging_menu_app_set_source_label (MessagingMenuApp *app, + const gchar *source_id, + const gchar *label) +{ + gint pos; + GMenuItem *item; + + g_return_if_fail (MESSAGING_MENU_IS_APP (app)); + g_return_if_fail (source_id != NULL); + g_return_if_fail (label != NULL); + + item = g_menu_find_item_with_action (app->menu, source_id, &pos); + if (item == NULL) + return; + + g_menu_item_set_attribute (item, G_MENU_ATTRIBUTE_LABEL, "s", label); + g_menu_replace_item (app->menu, pos, item); + + g_object_unref (item); +} + +/** + * messaging_menu_app_set_source_icon: + * @app: a #MessagingMenuApp + * @source_id: a source id + * @icon: (allow-none): the new icon for the source + * + * Changes the icon of @source_id to @icon. + */ +void +messaging_menu_app_set_source_icon (MessagingMenuApp *app, + const gchar *source_id, + GIcon *icon) +{ + gint pos; + GMenuItem *item; + + g_return_if_fail (MESSAGING_MENU_IS_APP (app)); + g_return_if_fail (source_id != NULL); + + item = g_menu_find_item_with_action (app->menu, source_id, &pos); + if (item == NULL) + return; + + if (icon) + { + gchar *iconstr; + + iconstr = g_icon_to_string (icon); + g_menu_item_set_attribute (item, "x-canonical-icon", "s", iconstr); + + g_free (iconstr); + } + else + { + g_menu_item_set_attribute_value (item, "x-canonical-icon", NULL); + } + + g_menu_replace_item (app->menu, pos, item); + + g_object_unref (item); +} + +/** + * messaging_menu_app_set_source_count: + * @app: a #MessagingMenuApp + * @source_id: a source id + * @count: the new count for the source + * + * Updates the count of @source_id to @count. + */ +void messaging_menu_app_set_source_count (MessagingMenuApp *app, + const gchar *source_id, + guint count) +{ + messaging_menu_app_set_source_action (app, source_id, count, 0, ""); +} + +/** + * messaging_menu_app_set_source_time: + * @app: a #MessagingMenuApp + * @source_id: a source id + * @time: the new time for the source, in microseconds + * + * Updates the time of @source_id to @time. + */ +void +messaging_menu_app_set_source_time (MessagingMenuApp *app, + const gchar *source_id, + gint64 time) +{ + messaging_menu_app_set_source_action (app, source_id, 0, time, ""); +} + +/** + * messaging_menu_app_set_source_string: + * @app: a #MessagingMenuApp + * @source_id: a source id + * @str: the new string for the source + * + * Updates the string displayed next to @source_id to @str. + */ +void +messaging_menu_app_set_source_string (MessagingMenuApp *app, + const gchar *source_id, + const gchar *str) +{ + messaging_menu_app_set_source_action (app, source_id, 0, 0, str); +} + +/** + * messaging_menu_app_draw_attention: + * @app: a #MessagingMenuApp + * @source_id: a source id + * + * Indicates that @source_id has important unread messages. Currently, this + * means that the messaging menu's envelope icon will turn blue. + * + * Use messaging_menu_app_remove_attention() to stop indicating that the source + * needs attention. + */ +void +messaging_menu_app_draw_attention (MessagingMenuApp *app, + const gchar *source_id) +{ + messaging_menu_app_set_draws_attention (app, source_id, TRUE); +} + +/** + * messaging_menu_app_remove_attention: + * @app: a #MessagingMenuApp + * @source_id: a source id + * + * Stop indicating that @source_id needs attention. + * + * This function does not need to be called when the source is removed + * with messaging_menu_app_remove_source() or the user has activated the + * source. + * + * Use messaging_menu_app_draw_attention() to make @source_id draw attention + * again. + */ +void +messaging_menu_app_remove_attention (MessagingMenuApp *app, + const gchar *source_id) +{ + messaging_menu_app_set_draws_attention (app, source_id, FALSE); +} diff --git a/libmessaging-menu/messaging-menu.h b/libmessaging-menu/messaging-menu.h index 929b229..6c405c7 100644 --- a/libmessaging-menu/messaging-menu.h +++ b/libmessaging-menu/messaging-menu.h @@ -20,6 +20,129 @@ #ifndef __messaging_menu_h__ #define __messaging_menu_h__ -#include "messaging-menu-app.h" +#include + +G_BEGIN_DECLS + +#define MESSAGING_MENU_TYPE_APP messaging_menu_app_get_type() +#define MESSAGING_MENU_APP(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), MESSAGING_MENU_TYPE_APP, MessagingMenuApp)) +#define MESSAGING_MENU_APP_CLASS(c) (G_TYPE_CHECK_CLASS_CAST ((c), MESSAGING_MENU_TYPE_APP, MessagingMenuAppClass)) +#define MESSAGING_MENU_IS_APP(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), MESSAGING_MENU_TYPE_APP)) + +/** + * MessagingMenuStatus: + * @MESSAGING_MENU_STATUS_AVAILABLE: available + * @MESSAGING_MENU_STATUS_AWAY: away + * @MESSAGING_MENU_STATUS_BUSY: busy + * @MESSAGING_MENU_STATUS_INVISIBLE: invisible + * @MESSAGING_MENU_STATUS_OFFLINE: offline + * + * An enumeration for the possible chat statuses the messaging menu can be in. + */ +typedef enum { + MESSAGING_MENU_STATUS_AVAILABLE, + MESSAGING_MENU_STATUS_AWAY, + MESSAGING_MENU_STATUS_BUSY, + MESSAGING_MENU_STATUS_INVISIBLE, + MESSAGING_MENU_STATUS_OFFLINE +} MessagingMenuStatus; + + +typedef GObjectClass MessagingMenuAppClass; +typedef struct _MessagingMenuApp MessagingMenuApp; + +GType messaging_menu_app_get_type (void) G_GNUC_CONST; + +MessagingMenuApp * messaging_menu_app_new (const gchar *desktop_id); + +void messaging_menu_app_register (MessagingMenuApp *app); +void messaging_menu_app_unregister (MessagingMenuApp *app); + +void messaging_menu_app_set_status (MessagingMenuApp *app, + MessagingMenuStatus status); + +void messaging_menu_app_insert_source (MessagingMenuApp *app, + gint position, + const gchar *id, + GIcon *icon, + const gchar *label); + +void messaging_menu_app_append_source (MessagingMenuApp *app, + const gchar *id, + GIcon *icon, + const gchar *label); + +void messaging_menu_app_insert_source_with_count (MessagingMenuApp *app, + gint position, + const gchar *id, + GIcon *icon, + const gchar *label, + guint count); + +void messaging_menu_app_append_source_with_count (MessagingMenuApp *app, + const gchar *id, + GIcon *icon, + const gchar *label, + guint count); + +void messaging_menu_app_insert_source_with_time (MessagingMenuApp *app, + gint position, + const gchar *id, + GIcon *icon, + const gchar *label, + gint64 time); + +void messaging_menu_app_append_source_with_time (MessagingMenuApp *app, + const gchar *id, + GIcon *icon, + const gchar *label, + gint64 time); + +void messaging_menu_app_append_source_with_string (MessagingMenuApp *app, + const gchar *id, + GIcon *icon, + const gchar *label, + const gchar *str); + +void messaging_menu_app_insert_source_with_string (MessagingMenuApp *app, + gint position, + const gchar *id, + GIcon *icon, + const gchar *label, + const gchar *str); + +void messaging_menu_app_remove_source (MessagingMenuApp *app, + const gchar *source_id); + +gboolean messaging_menu_app_has_source (MessagingMenuApp *app, + const gchar *source_id); + +void messaging_menu_app_set_source_label (MessagingMenuApp *app, + const gchar *source_id, + const gchar *label); + +void messaging_menu_app_set_source_icon (MessagingMenuApp *app, + const gchar *source_id, + GIcon *icon); + +void messaging_menu_app_set_source_count (MessagingMenuApp *app, + const gchar *source_id, + guint count); + +void messaging_menu_app_set_source_time (MessagingMenuApp *app, + const gchar *source_id, + gint64 time); + +void messaging_menu_app_set_source_string (MessagingMenuApp *app, + const gchar *source_id, + const gchar *str); + +void messaging_menu_app_draw_attention (MessagingMenuApp *app, + const gchar *source_id); + +void messaging_menu_app_remove_attention (MessagingMenuApp *app, + const gchar *source_id); + +G_END_DECLS #endif diff --git a/po/POTFILES.in b/po/POTFILES.in index 70c7f6b..edb0388 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -1,15 +1,3 @@ [encoding: UTF-8] -common/indicator-messages-service.c -common/indicator-messages-application.c -test/indicator-messages-service-activate.c -src/im-phone-menu.c -src/app-section.c +src/indicator-messages.c src/messages-service.c -src/im-desktop-menu.c -src/im-menu.c -src/im-application-list.c -src/gsettingsstrv.c -src/gmenuutils.c -src/gactionmuxer.c -libmessaging-menu/messaging-menu-message.c -libmessaging-menu/messaging-menu-app.c diff --git a/po/indicator-messages.pot b/po/indicator-messages.pot new file mode 100644 index 0000000..3788bb5 --- /dev/null +++ b/po/indicator-messages.pot @@ -0,0 +1,46 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-11-16 14:53+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: ../src/messages-service.c:329 +msgid "Messages" +msgstr "" + +#: ../src/messages-service.c:552 +msgid "Available" +msgstr "" + +#: ../src/messages-service.c:553 +msgid "Away" +msgstr "" + +#: ../src/messages-service.c:554 +msgid "Busy" +msgstr "" + +#: ../src/messages-service.c:555 +msgid "Invisible" +msgstr "" + +#: ../src/messages-service.c:556 +msgid "Offline" +msgstr "" + +#: ../src/messages-service.c:659 +msgid "Clear" +msgstr "" diff --git a/src/Makefile.am b/src/Makefile.am index e03406a..1df80e5 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,10 +1,53 @@ +BUILT_SOURCES = EXTRA_DIST = +CLEANFILES = +DISTCLEANFILES = libexec_PROGRAMS = indicator-messages-service + +###################################### +# Building the messages indicator +###################################### + +messaginglibdir = $(INDICATORDIR) +messaginglib_LTLIBRARIES = libmessaging.la +libmessaging_la_SOURCES = \ + indicator-messages.c \ + ido-menu-item.c \ + ido-menu-item.h \ + im-app-menu-item.c \ + im-app-menu-item.h \ + im-source-menu-item.c \ + im-source-menu-item.h \ + ido-detail-label.c \ + ido-detail-label.h \ + indicator-messages-service.c \ + indicator-messages-service.h + dbus-data.h +libmessaging_la_CFLAGS = \ + $(APPLET_CFLAGS) \ + $(COVERAGE_CFLAGS) \ + -Wall \ + -Wl,-Bsymbolic-functions \ + -Wl,-z,defs \ + -Wl,--as-needed \ + -Werror \ + -DG_LOG_DOMAIN=\"Indicator-Messages\" +libmessaging_la_LIBADD = $(APPLET_LIBS) -lm +libmessaging_la_LDFLAGS = \ + $(COVERAGE_LDFLAGS) \ + -module -avoid-version + +###################################### +# Building the messages service +###################################### + indicator_messages_service_SOURCES = \ messages-service.c \ + indicator-messages-service.c \ + indicator-messages-service.h \ app-section.c \ app-section.h \ dbus-data.h \ @@ -13,20 +56,11 @@ indicator_messages_service_SOURCES = \ gsettingsstrv.c \ gsettingsstrv.h \ gmenuutils.c \ - gmenuutils.h \ - im-menu.c \ - im-menu.h \ - im-phone-menu.c \ - im-phone-menu.h \ - im-desktop-menu.c \ - im-desktop-menu.h \ - im-application-list.c \ - im-application-list.h + gmenuutils.h indicator_messages_service_CFLAGS = \ $(APPLET_CFLAGS) \ $(COVERAGE_CFLAGS) \ - -I$(top_builddir)/common \ -Wall \ -Wl,-Bsymbolic-functions \ -Wl,-z,defs \ @@ -35,11 +69,26 @@ indicator_messages_service_CFLAGS = \ -DG_LOG_DOMAIN=\"Indicator-Messages\" indicator_messages_service_LDADD = \ - $(top_builddir)/common/libmessaging-common.la \ $(APPLET_LIBS) indicator_messages_service_LDFLAGS = \ $(COVERAGE_LDFLAGS) +indicator-messages-service.c: $(top_srcdir)/src/messages-service.xml + $(AM_V_GEN) gdbus-codegen \ + --interface-prefix com.canonical.indicator.messages. \ + --generate-c-code indicator-messages-service \ + --c-namespace IndicatorMessages \ + $^ +indicator-messages-service.h: indicator-messages-service.c + +BUILT_SOURCES += \ + indicator-messages-service.c \ + indicator-messages-service.h + EXTRA_DIST += \ messages-service.xml + +CLEANFILES += \ + $(BUILT_SOURCES) + diff --git a/src/app-section.c b/src/app-section.c index aad994d..1106c62 100644 --- a/src/app-section.c +++ b/src/app-section.c @@ -34,7 +34,6 @@ with this program. If not, see . #include "dbus-data.h" #include "gmenuutils.h" #include "gactionmuxer.h" -#include "indicator-messages-application.h" struct _AppSectionPrivate { @@ -43,14 +42,11 @@ struct _AppSectionPrivate IndicatorDesktopShortcuts * ids; - GCancellable *app_proxy_cancellable; - IndicatorMessagesApplication *app_proxy; - GMenu *menu; - GMenu *source_menu; + GMenuModel *source_menu; GSimpleActionGroup *static_shortcuts; - GSimpleActionGroup *source_actions; + GActionGroup *source_actions; GActionMuxer *muxer; gboolean draws_attention; @@ -94,6 +90,19 @@ static void launch_action_change_state (GSimpleAction *action, gpointer user_data); static void app_section_set_app_info (AppSection *self, GDesktopAppInfo *appinfo); +static gboolean any_action_draws_attention (GActionGroup *group, + const gchar *ignored_action); +static void action_added (GActionGroup *group, + const gchar *action_name, + gpointer user_data); +static void action_state_changed (GActionGroup *group, + const gchar *action_name, + GVariant *value, + gpointer user_data); +static void action_removed (GActionGroup *group, + const gchar *action_name, + gpointer user_data); +static gboolean action_draws_attention (GVariant *state); static void desktop_file_changed_cb (GFileMonitor *monitor, GFile *file, GFile *other_file, @@ -161,7 +170,6 @@ static void app_section_init (AppSection *self) { AppSectionPrivate *priv; - GMenuItem *item; self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, APP_SECTION_TYPE, @@ -171,19 +179,10 @@ app_section_init (AppSection *self) priv->appinfo = NULL; priv->menu = g_menu_new (); - - priv->source_menu = g_menu_new (); - item = g_menu_item_new_section (NULL, G_MENU_MODEL (priv->source_menu)); - g_menu_item_set_attribute (item, "action-namespace", "s", "source"); - g_menu_append_item (priv->menu, item); - g_object_unref (item); - priv->static_shortcuts = g_simple_action_group_new (); - priv->source_actions = g_simple_action_group_new (); priv->muxer = g_action_muxer_new (); g_action_muxer_insert (priv->muxer, NULL, G_ACTION_GROUP (priv->static_shortcuts)); - g_action_muxer_insert (priv->muxer, "source", G_ACTION_GROUP (priv->source_actions)); priv->draws_attention = FALSE; @@ -250,30 +249,32 @@ app_section_dispose (GObject *object) AppSection * self = APP_SECTION(object); AppSectionPrivate * priv = self->priv; - if (priv->app_proxy_cancellable) { - g_cancellable_cancel (priv->app_proxy_cancellable); - g_clear_object (&priv->app_proxy_cancellable); - } - if (priv->desktop_file_monitor) { g_signal_handlers_disconnect_by_func (priv->desktop_file_monitor, desktop_file_changed_cb, self); g_clear_object (&priv->desktop_file_monitor); } - g_clear_object (&priv->app_proxy); - g_clear_object (&priv->menu); - g_clear_object (&priv->source_menu); g_clear_object (&priv->static_shortcuts); - g_clear_object (&priv->source_actions); if (priv->name_watch_id) { g_bus_unwatch_name (priv->name_watch_id); priv->name_watch_id = 0; } + if (priv->source_actions) { + g_action_muxer_remove (priv->muxer, "source"); + g_object_disconnect (priv->source_actions, + "any_signal::action-added", action_added, self, + "any_signal::action-state-changed", action_state_changed, self, + "any_signal::action-removed", action_removed, self, + NULL); + g_clear_object (&priv->source_actions); + } + g_clear_object (&priv->muxer); + g_clear_object (&priv->source_menu); g_clear_object (&priv->ids); g_clear_object (&priv->appinfo); @@ -313,12 +314,14 @@ nick_activate_cb (GSimpleAction *action, g_return_if_fail(priv->ids != NULL); - if (!indicator_desktop_shortcuts_nick_exec_with_context(priv->ids, nick, NULL)) { + GAppLaunchContext *context = get_launch_context (g_variant_get_uint32 (param)); + + if (!indicator_desktop_shortcuts_nick_exec_with_context(priv->ids, nick, context)) { g_warning("Unable to execute nick '%s' for desktop file '%s'", nick, g_desktop_app_info_get_filename (priv->appinfo)); } - return; + g_object_unref (context); } static void @@ -411,9 +414,7 @@ app_section_update_menu (AppSection *self) item = g_menu_item_new (g_app_info_get_name (G_APP_INFO (priv->appinfo)), "launch"); g_menu_item_set_attribute (item, "x-canonical-type", "s", "ImAppMenuItem"); iconstr = g_icon_to_string (g_app_info_get_icon (G_APP_INFO (priv->appinfo))); - if (iconstr != NULL) { - g_menu_item_set_attribute (item, "x-canonical-icon", "s", iconstr); - } + g_menu_item_set_attribute (item, "x-canonical-icon", "s", iconstr); g_free (iconstr); g_menu_append_item (priv->menu, item); @@ -444,11 +445,6 @@ app_section_update_menu (AppSection *self) g_free(name); } - item = g_menu_item_new_section (NULL, G_MENU_MODEL (priv->source_menu)); - g_menu_item_set_attribute (item, "action-namespace", "s", "source"); - g_menu_append_item (priv->menu, item); - g_object_unref (item); - keyfile = g_file_new_for_path (g_desktop_app_info_get_filename (priv->appinfo)); g_file_load_contents_async (keyfile, NULL, keyfile_loaded, self); @@ -587,242 +583,49 @@ app_section_get_draws_attention (AppSection *self) void app_section_clear_draws_attention (AppSection *self) { - self->priv->draws_attention = FALSE; - g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]); -} - -static void -application_vanished (GDBusConnection *bus, - const gchar *name, - gpointer user_data) -{ - AppSection *self = user_data; - - app_section_unset_object_path (self); -} - -static void -update_draws_attention (AppSection *self) -{ - AppSectionPrivate *priv = self->priv; - gchar **actions; + AppSectionPrivate * priv = self->priv; + gchar **action_names; gchar **it; - gboolean draws_attention = FALSE; - - actions = g_action_group_list_actions (G_ACTION_GROUP (priv->source_actions)); - - for (it = actions; *it; it++) { - GVariant *state; - - state = g_action_group_get_action_state (G_ACTION_GROUP (priv->source_actions), *it); - if (state) { - gboolean b; - g_variant_get (state, "(uxsb)", NULL, NULL, NULL, &b); - draws_attention = b || draws_attention; - g_variant_unref (state); - } - - if (draws_attention) - break; - } - - if (draws_attention != priv->draws_attention) { - priv->draws_attention = draws_attention; - g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]); - } - - g_strfreev (actions); -} - -static void -remove_source (AppSection *self, - const gchar *id) -{ - AppSectionPrivate *priv = self->priv; - guint n_items; - guint i; - - n_items = g_menu_model_get_n_items (G_MENU_MODEL (priv->source_menu)); - for (i = 0; i < n_items; i++) { - gchar *action; - gboolean found = FALSE; - - if (g_menu_model_get_item_attribute (G_MENU_MODEL (priv->source_menu), i, - G_MENU_ATTRIBUTE_ACTION, "s", &action)) { - found = g_str_equal (action, id); - g_free (action); - } - - if (found) { - g_menu_remove (priv->source_menu, i); - break; - } - } - - g_simple_action_group_remove (priv->source_actions, id); - update_draws_attention (self); -} - -static void -source_action_activated (GSimpleAction *action, - GVariant *parameter, - gpointer user_data) -{ - AppSection *self = APP_SECTION (user_data); - AppSectionPrivate *priv = APP_SECTION (user_data)->priv; - - g_return_if_fail (priv->app_proxy != NULL); - - indicator_messages_application_call_activate_source (priv->app_proxy, - g_action_get_name (G_ACTION (action)), - priv->app_proxy_cancellable, - NULL, NULL); - - remove_source (self, g_action_get_name (G_ACTION (action))); -} -static void -sources_listed (GObject *source_object, - GAsyncResult *result, - gpointer user_data) -{ - AppSection *self = user_data; - AppSectionPrivate *priv = self->priv; - GVariant *sources = NULL; - GError *error = NULL; - GVariantIter iter; - const gchar *id; - const gchar *label; - const gchar *iconstr; - guint32 count; - gint64 time; - const gchar *string; - gboolean draws_attention; - - if (!indicator_messages_application_call_list_sources_finish (INDICATOR_MESSAGES_APPLICATION (source_object), - &sources, result, &error)) - { - g_warning ("could not fetch the list of sources: %s", error->message); - g_error_free (error); + if (priv->source_actions == NULL) return; - } - g_menu_clear (priv->source_menu); - g_simple_action_group_clear (priv->source_actions); - priv->draws_attention = FALSE; + action_names = g_action_group_list_actions (priv->source_actions); - g_variant_iter_init (&iter, sources); - while (g_variant_iter_next (&iter, "(&s&s&sux&sb)", &id, &label, &iconstr, - &count, &time, &string, &draws_attention)) - { + for (it = action_names; *it; it++) { GVariant *state; - GSimpleAction *action; - GMenuItem *item; - state = g_variant_new ("(uxsb)", count, time, string, draws_attention); - action = g_simple_action_new_stateful (id, NULL, state); - g_signal_connect (action, "activate", G_CALLBACK (source_action_activated), self); - g_simple_action_group_insert (priv->source_actions, G_ACTION (action)); + state = g_action_group_get_action_state (priv->source_actions, *it); + if (!state) + continue; - item = g_menu_item_new (label, id); - g_menu_item_set_attribute (item, "x-canonical-type", "s", "ImSourceMenuItem"); - g_menu_append_item (priv->source_menu, item); + /* clear draws-attention while preserving other state */ + if (action_draws_attention (state)) { + guint32 count; + gint64 time; + const gchar *str; + GVariant *new_state; - priv->draws_attention = priv->draws_attention || draws_attention; - - g_object_unref (item); - g_object_unref (action); - } - - g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]); - - g_variant_unref (sources); -} + g_variant_get (state, "(ux&sb)", &count, &time, &str, NULL); -static void -source_added (IndicatorMessagesApplication *app, - const gchar *id, - const gchar *label, - const gchar *iconstr, - guint count, - gint64 time, - const gchar *string, - gboolean draws_attention, - gpointer user_data) -{ - AppSection *self = user_data; - AppSectionPrivate *priv = self->priv; - GVariant *state; - GSimpleAction *action; - - /* TODO put label and icon into the action as well */ - - state = g_variant_new ("(uxsb)", count, time, string, draws_attention); - action = g_simple_action_new_stateful (id, NULL, state); - - g_simple_action_group_insert (priv->source_actions, G_ACTION (action)); + new_state = g_variant_new ("(uxsb)", count, time, str, FALSE); + g_action_group_change_action_state (priv->source_actions, *it, new_state); + } - if (draws_attention && !priv->draws_attention) { - priv->draws_attention = TRUE; - g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]); + g_variant_unref (state); } - g_object_unref (action); -} -static void -source_changed (IndicatorMessagesApplication *app, - const gchar *id, - const gchar *label, - const gchar *iconstr, - guint count, - gint64 time, - const gchar *string, - gboolean draws_attention, - gpointer user_data) -{ - AppSection *self = user_data; - AppSectionPrivate *priv = self->priv; - GVariant *state; - - /* TODO put label and icon into the action as well */ - - state = g_variant_new ("(uxsb)", count, time, string, draws_attention); - g_action_group_change_action_state (G_ACTION_GROUP (priv->source_actions), id, state); - - update_draws_attention (self); + g_strfreev (action_names); } static void -source_removed (IndicatorMessagesApplication *app, - const gchar *id, - gpointer user_data) +application_vanished (GDBusConnection *bus, + const gchar *name, + gpointer user_data) { AppSection *self = user_data; - remove_source (self, id); -} - -static void -app_proxy_created (GObject *source_object, - GAsyncResult *result, - gpointer user_data) -{ - AppSectionPrivate *priv = APP_SECTION (user_data)->priv; - GError *error = NULL; - - priv->app_proxy = indicator_messages_application_proxy_new_finish (result, &error); - if (!priv->app_proxy) { - g_warning ("could not create application proxy: %s", error->message); - g_error_free (error); - return; - } - - indicator_messages_application_call_list_sources (priv->app_proxy, priv->app_proxy_cancellable, - sources_listed, user_data); - - g_signal_connect (priv->app_proxy, "source-added", G_CALLBACK (source_added), user_data); - g_signal_connect (priv->app_proxy, "source-changed", G_CALLBACK (source_changed), user_data); - g_signal_connect (priv->app_proxy, "source-removed", G_CALLBACK (source_removed), user_data); + app_section_unset_object_path (self); } /* @@ -843,20 +646,27 @@ app_section_set_object_path (AppSection *self, const gchar *object_path) { AppSectionPrivate *priv = self->priv; + GMenuItem *item; g_object_freeze_notify (G_OBJECT (self)); app_section_unset_object_path (self); - priv->app_proxy_cancellable = g_cancellable_new (); - indicator_messages_application_proxy_new (bus, - G_DBUS_PROXY_FLAGS_NONE, - bus_name, - object_path, - priv->app_proxy_cancellable, - app_proxy_created, - self); + priv->source_actions = G_ACTION_GROUP (g_dbus_action_group_get (bus, bus_name, object_path)); + g_action_muxer_insert (priv->muxer, "source", priv->source_actions); - priv->draws_attention = FALSE; + priv->draws_attention = any_action_draws_attention (priv->source_actions, NULL); + g_object_connect (priv->source_actions, + "signal::action-added", action_added, self, + "signal::action-state-changed", action_state_changed, self, + "signal::action-removed", action_removed, self, + NULL); + + priv->source_menu = G_MENU_MODEL (g_dbus_menu_model_get (bus, bus_name, object_path)); + + item = g_menu_item_new_section (NULL, priv->source_menu); + g_menu_item_set_attribute (item, "action-namespace", "s", "source"); + g_menu_append_item (priv->menu, item); + g_object_unref (item); priv->name_watch_id = g_bus_watch_name_on_connection (bus, bus_name, 0, NULL, application_vanished, @@ -884,19 +694,26 @@ app_section_unset_object_path (AppSection *self) { AppSectionPrivate *priv = self->priv; - if (priv->app_proxy_cancellable) { - g_cancellable_cancel (priv->app_proxy_cancellable); - g_clear_object (&priv->app_proxy_cancellable); - } - g_clear_object (&priv->app_proxy); - if (priv->name_watch_id) { g_bus_unwatch_name (priv->name_watch_id); priv->name_watch_id = 0; } - g_simple_action_group_clear (priv->source_actions); - g_menu_clear (priv->source_menu); + if (priv->source_actions) { + g_object_disconnect (priv->source_actions, + "any_signal::action-added", action_added, self, + "any_signal::action-state-changed", action_state_changed, self, + "any_signal::action-removed", action_removed, self, + NULL); + g_clear_object (&priv->source_actions); + } + + if (priv->source_menu) { + /* the last menu item points is linked to the app's menumodel */ + gint n_items = g_menu_model_get_n_items (G_MENU_MODEL (priv->menu)); + g_menu_remove (priv->menu, n_items -1); + g_clear_object (&priv->source_menu); + } priv->draws_attention = FALSE; g_clear_pointer (&priv->chat_status, g_free); @@ -910,6 +727,85 @@ app_section_unset_object_path (AppSection *self) "launch", g_variant_new_boolean (FALSE)); } +static gboolean +action_draws_attention (GVariant *state) +{ + gboolean attention; + + if (state && g_variant_is_of_type (state, G_VARIANT_TYPE ("(uxsb)"))) + g_variant_get_child (state, 3, "b", &attention); + else + attention = FALSE; + + return attention; +} + +static gboolean +any_action_draws_attention (GActionGroup *group, + const gchar *ignored_action) +{ + gchar **actions; + gchar **it; + gboolean attention = FALSE; + + actions = g_action_group_list_actions (group); + + for (it = actions; *it && !attention; it++) { + GVariant *state; + + if (ignored_action && g_str_equal (ignored_action, *it)) + continue; + + state = g_action_group_get_action_state (group, *it); + if (state) { + attention = action_draws_attention (state); + g_variant_unref (state); + } + } + + g_strfreev (actions); + return attention; +} + +static void +action_added (GActionGroup *group, + const gchar *action_name, + gpointer user_data) +{ + AppSection *self = user_data; + GVariant *state; + + state = g_action_group_get_action_state (group, action_name); + if (state) { + self->priv->draws_attention |= action_draws_attention (state); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]); + g_variant_unref (state); + } +} + +static void +action_state_changed (GActionGroup *group, + const gchar *action_name, + GVariant *value, + gpointer user_data) +{ + AppSection *self = user_data; + + self->priv->draws_attention = any_action_draws_attention (group, NULL); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]); +} + +static void +action_removed (GActionGroup *group, + const gchar *action_name, + gpointer user_data) +{ + AppSection *self = user_data; + + self->priv->draws_attention = any_action_draws_attention (group, action_name); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]); +} + gboolean app_section_get_uses_chat_status (AppSection *self) { diff --git a/src/dbus-data.h b/src/dbus-data.h index 59bd305..64747a9 100644 --- a/src/dbus-data.h +++ b/src/dbus-data.h @@ -1,24 +1,9 @@ -/* - * Copyright 2012-2013 Canonical Ltd. - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 3, as published - * by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranties of - * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . - */ #ifndef __DBUS_DATA_H__ #define __DBUS_DATA_H__ 1 #define INDICATOR_MESSAGES_DBUS_NAME "com.canonical.indicator.messages" -#define INDICATOR_MESSAGES_DBUS_OBJECT "/com/canonical/indicator/messages" +#define INDICATOR_MESSAGES_DBUS_OBJECT "/com/canonical/indicator/messages/menu" #define INDICATOR_MESSAGES_DBUS_SERVICE_OBJECT "/com/canonical/indicator/messages/service" #define INDICATOR_MESSAGES_DBUS_SERVICE_INTERFACE "com.canonical.indicator.messages.service" diff --git a/src/gactionmuxer.c b/src/gactionmuxer.c index 0f3cda4..2b1d11a 100644 --- a/src/gactionmuxer.c +++ b/src/gactionmuxer.c @@ -483,11 +483,3 @@ g_action_muxer_remove (GActionMuxer *muxer, g_clear_object (&muxer->global_actions); } -GActionGroup * -g_action_muxer_get_group (GActionMuxer *muxer, - const gchar *prefix) -{ - g_return_val_if_fail (G_IS_ACTION_MUXER (muxer), NULL); - - return prefix ? g_hash_table_lookup (muxer->groups, prefix) : muxer->global_actions; -} diff --git a/src/gactionmuxer.h b/src/gactionmuxer.h index caf9ec7..5c5e839 100644 --- a/src/gactionmuxer.h +++ b/src/gactionmuxer.h @@ -40,8 +40,5 @@ void g_action_muxer_insert (GActionMuxer *muxer, void g_action_muxer_remove (GActionMuxer *muxer, const gchar *prefix); -GActionGroup * g_action_muxer_get_group (GActionMuxer *muxer, - const gchar *prefix); - #endif diff --git a/src/gmenuutils.c b/src/gmenuutils.c index cfd751e..f63615b 100644 --- a/src/gmenuutils.c +++ b/src/gmenuutils.c @@ -79,7 +79,7 @@ g_menu_append_with_icon_name (GMenu *menu, GMenuItem *item; item = g_menu_item_new (label, detailed_action); - g_menu_item_set_attribute (item, "icon", "s", icon_name); + g_menu_item_set_attribute (item, "x-canonical-icon", "s", icon_name); g_menu_append_item (menu, item); diff --git a/src/ido-detail-label.c b/src/ido-detail-label.c new file mode 100644 index 0000000..8b7ef90 --- /dev/null +++ b/src/ido-detail-label.c @@ -0,0 +1,401 @@ +/* + * Copyright 2012 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . + * + * Authors: + * Lars Uebernickel + */ + +#include "ido-detail-label.h" + +#include + +G_DEFINE_TYPE (IdoDetailLabel, ido_detail_label, GTK_TYPE_WIDGET) + +struct _IdoDetailLabelPrivate +{ + gchar *text; + PangoLayout *layout; + gboolean draw_lozenge; +}; + +enum +{ + PROP_0, + PROP_TEXT, + NUM_PROPERTIES +}; + +static GParamSpec *properties[NUM_PROPERTIES]; + +static void +ido_detail_label_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + IdoDetailLabel *self = IDO_DETAIL_LABEL (object); + + switch (property_id) + { + case PROP_TEXT: + g_value_set_string (value, self->priv->text); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ido_detail_label_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + IdoDetailLabel *self = IDO_DETAIL_LABEL (object); + + switch (property_id) + { + case PROP_TEXT: + ido_detail_label_set_text (self, g_value_get_string (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + + +static void +ido_detail_label_finalize (GObject *object) +{ + IdoDetailLabelPrivate *priv = IDO_DETAIL_LABEL (object)->priv; + + g_free (priv->text); + + G_OBJECT_CLASS (ido_detail_label_parent_class)->finalize (object); +} + +static void +ido_detail_label_dispose (GObject *object) +{ + IdoDetailLabelPrivate *priv = IDO_DETAIL_LABEL (object)->priv; + + g_clear_object (&priv->layout); + + G_OBJECT_CLASS (ido_detail_label_parent_class)->dispose (object); +} + +static void +ido_detail_label_ensure_layout (IdoDetailLabel *label) +{ + IdoDetailLabelPrivate *priv = label->priv; + + if (priv->layout == NULL) + { + priv->layout = gtk_widget_create_pango_layout (GTK_WIDGET (label), priv->text); + pango_layout_set_alignment (priv->layout, PANGO_ALIGN_CENTER); + pango_layout_set_ellipsize (priv->layout, PANGO_ELLIPSIZE_END); + pango_layout_set_height (priv->layout, -1); + + // TODO update layout on "style-updated" and "direction-changed" + } +} + +static void +cairo_lozenge (cairo_t *cr, + double x, + double y, + double w, + double h, + double radius) +{ + double x1 = x + w - radius; + double x2 = x + radius; + double y1 = y + radius; + double y2 = y + h - radius; + + cairo_move_to (cr, x + radius, y); + cairo_arc (cr, x1, y1, radius, G_PI * 1.5, G_PI * 2); + cairo_arc (cr, x1, y2, radius, 0, G_PI * 0.5); + cairo_arc (cr, x2, y2, radius, G_PI * 0.5, G_PI); + cairo_arc (cr, x2, y1, radius, G_PI, G_PI * 1.5); +} + +static PangoFontMetrics * +gtk_widget_get_font_metrics (GtkWidget *widget, + PangoContext *context) +{ + PangoFontDescription *font; + PangoFontMetrics *metrics; + + gtk_style_context_get (gtk_widget_get_style_context (widget), + gtk_widget_get_state_flags (widget), + "font", &font, NULL); + + metrics = pango_context_get_metrics (context, + font, + pango_context_get_language (context)); + + pango_font_description_free (font); + return metrics; +} + +static gint +ido_detail_label_get_minimum_text_width (IdoDetailLabel *label) +{ + IdoDetailLabelPrivate *priv = label->priv; + PangoContext *context; + PangoFontMetrics *metrics; + gint char_width; + gint w; + + context = pango_layout_get_context (priv->layout); + metrics = gtk_widget_get_font_metrics (GTK_WIDGET (label), context); + char_width = pango_font_metrics_get_approximate_digit_width (metrics); + + w = 2 * char_width / PANGO_SCALE; + pango_font_metrics_unref (metrics); + return w; +} + +static gboolean +ido_detail_label_draw (GtkWidget *widget, + cairo_t *cr) +{ + IdoDetailLabel *label = IDO_DETAIL_LABEL (widget); + IdoDetailLabelPrivate *priv = IDO_DETAIL_LABEL (widget)->priv; + PangoRectangle extents; + GtkAllocation allocation; + double x, w, h, radius; + GdkRGBA color; + + if (!priv->text || !*priv->text) + return TRUE; + + gtk_widget_get_allocation (widget, &allocation); + + ido_detail_label_ensure_layout (IDO_DETAIL_LABEL (widget)); + + pango_layout_get_extents (priv->layout, NULL, &extents); + pango_extents_to_pixels (&extents, NULL); + + h = MIN (allocation.height, extents.height); + radius = floor (h / 2.0); + w = MAX (ido_detail_label_get_minimum_text_width (label), extents.width) + 2.0 * radius; + x = allocation.width - w; + + pango_layout_set_width (priv->layout, (allocation.width - 2 * radius) * PANGO_SCALE); + pango_layout_get_extents (priv->layout, NULL, &extents); + pango_extents_to_pixels (&extents, NULL); + + gtk_style_context_get_color (gtk_widget_get_style_context (widget), + gtk_widget_get_state_flags (widget), + &color); + gdk_cairo_set_source_rgba (cr, &color); + + cairo_set_line_width (cr, 1.0); + cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD); + + if (priv->draw_lozenge) + cairo_lozenge (cr, x, 0.0, w, h, radius); + + cairo_move_to (cr, x + radius, (allocation.height - extents.height) / 2.0); + pango_cairo_layout_path (cr, priv->layout); + cairo_fill (cr); + + return TRUE; +} + +static void +ido_detail_label_get_preferred_width (GtkWidget *widget, + gint *minimum, + gint *natural) +{ + IdoDetailLabelPrivate *priv = IDO_DETAIL_LABEL (widget)->priv; + PangoRectangle extents; + double radius; + + ido_detail_label_ensure_layout (IDO_DETAIL_LABEL (widget)); + + pango_layout_get_extents (priv->layout, NULL, &extents); + pango_extents_to_pixels (&extents, NULL); + + radius = floor (extents.height / 2.0); + + *minimum = ido_detail_label_get_minimum_text_width (IDO_DETAIL_LABEL (widget)) + 2.0 * radius; + *natural = MAX (*minimum, extents.width + 2.0 * radius); +} + +static void +ido_detail_label_get_preferred_height (GtkWidget *widget, + gint *minimum, + gint *natural) +{ + IdoDetailLabelPrivate *priv = IDO_DETAIL_LABEL (widget)->priv; + PangoContext *context; + PangoFontMetrics *metrics; + PangoRectangle extents; + + ido_detail_label_ensure_layout (IDO_DETAIL_LABEL (widget)); + + pango_layout_get_extents (priv->layout, NULL, &extents); + pango_extents_to_pixels (&extents, NULL); + context = pango_layout_get_context (priv->layout); + metrics = gtk_widget_get_font_metrics (widget, context); + + *minimum = *natural = (pango_font_metrics_get_ascent (metrics) + + pango_font_metrics_get_descent (metrics)) / PANGO_SCALE; + + pango_font_metrics_unref (metrics); +} + +static void +ido_detail_label_class_init (IdoDetailLabelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->get_property = ido_detail_label_get_property; + object_class->set_property = ido_detail_label_set_property; + object_class->finalize = ido_detail_label_finalize; + object_class->dispose = ido_detail_label_dispose; + + widget_class->draw = ido_detail_label_draw; + widget_class->get_preferred_width = ido_detail_label_get_preferred_width; + widget_class->get_preferred_height = ido_detail_label_get_preferred_height; + + g_type_class_add_private (klass, sizeof (IdoDetailLabelPrivate)); + + properties[PROP_TEXT] = g_param_spec_string ("text", + "Text", + "The text of the label", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, NUM_PROPERTIES, properties); +} + +static void +ido_detail_label_init (IdoDetailLabel *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + IDO_TYPE_DETAIL_LABEL, + IdoDetailLabelPrivate); + + gtk_widget_set_has_window (GTK_WIDGET (self), FALSE); +} + +GtkWidget * +ido_detail_label_new (const gchar *label) +{ + return g_object_new (IDO_TYPE_DETAIL_LABEL, + "text", label, + NULL); +} + +const gchar * +ido_detail_label_get_text (IdoDetailLabel *label) +{ + g_return_val_if_fail (IDO_IS_DETAIL_LABEL (label), NULL); + return label->priv->text; +} + +/* collapse_whitespace: + * @str: the source string + * + * Collapses all occurences of consecutive whitespace charactes in @str + * into a single space. + * + * Returns: (transfer full): a newly-allocated string + */ +static gchar * +collapse_whitespace (const gchar *str) +{ + GString *result; + gboolean in_space = FALSE; + + if (str == NULL) + return NULL; + + result = g_string_new (""); + + while (*str) + { + gunichar c = g_utf8_get_char_validated (str, -1); + + if (c < 0) + break; + + if (!g_unichar_isspace (c)) + { + g_string_append_unichar (result, c); + in_space = FALSE; + } + else if (!in_space) + { + g_string_append_c (result, ' '); + in_space = TRUE; + } + + str = g_utf8_next_char (str); + } + + return g_string_free (result, FALSE); +} + +static void +ido_detail_label_set_text_impl (IdoDetailLabel *label, + const gchar *text, + gboolean draw_lozenge) +{ + IdoDetailLabelPrivate * priv = label->priv; + + g_clear_object (&priv->layout); + g_free (priv->text); + + priv->text = g_strdup (text); + priv->draw_lozenge = draw_lozenge; + + g_object_notify_by_pspec (G_OBJECT (label), properties[PROP_TEXT]); + gtk_widget_queue_resize (GTK_WIDGET (label)); +} + +void +ido_detail_label_set_text (IdoDetailLabel *label, + const gchar *text) +{ + gchar *str; + + g_return_if_fail (IDO_IS_DETAIL_LABEL (label)); + + str = collapse_whitespace (text); + ido_detail_label_set_text_impl (label, str, FALSE); + g_free (str); +} + +void +ido_detail_label_set_count (IdoDetailLabel *label, + gint count) +{ + gchar *text; + + g_return_if_fail (IDO_IS_DETAIL_LABEL (label)); + + text = g_strdup_printf ("%d", count); + ido_detail_label_set_text_impl (label, text, TRUE); + g_free (text); +} diff --git a/src/ido-detail-label.h b/src/ido-detail-label.h new file mode 100644 index 0000000..1995fee --- /dev/null +++ b/src/ido-detail-label.h @@ -0,0 +1,59 @@ +/* + * Copyright 2012 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . + * + * Authors: + * Lars Uebernickel + */ + +#ifndef __IDO_DETAIL_LABEL_H__ +#define __IDO_DETAIL_LABEL_H__ + +#include + +#define IDO_TYPE_DETAIL_LABEL (ido_detail_label_get_type()) +#define IDO_DETAIL_LABEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IDO_TYPE_DETAIL_LABEL, IdoDetailLabel)) +#define IDO_DETAIL_LABEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), IDO_TYPE_DETAIL_LABEL, IdoDetailLabelClass)) +#define IDO_IS_DETAIL_LABEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IDO_TYPE_DETAIL_LABEL)) +#define IDO_IS_DETAIL_LABEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), IDO_TYPE_DETAIL_LABEL)) +#define IDO_DETAIL_LABEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), IDO_TYPE_DETAIL_LABEL, IdoDetailLabelClass)) + +typedef struct _IdoDetailLabel IdoDetailLabel; +typedef struct _IdoDetailLabelClass IdoDetailLabelClass; +typedef struct _IdoDetailLabelPrivate IdoDetailLabelPrivate; + +struct _IdoDetailLabel +{ + GtkWidget parent; + IdoDetailLabelPrivate *priv; +}; + +struct _IdoDetailLabelClass +{ + GtkWidgetClass parent_class; +}; + +GType ido_detail_label_get_type (void) G_GNUC_CONST; + +GtkWidget * ido_detail_label_new (const gchar *str); + +const gchar * ido_detail_label_get_text (IdoDetailLabel *label); + +void ido_detail_label_set_text (IdoDetailLabel *label, + const gchar *text); + +void ido_detail_label_set_count (IdoDetailLabel *label, + gint count); + +#endif diff --git a/src/ido-menu-item.c b/src/ido-menu-item.c new file mode 100644 index 0000000..f702828 --- /dev/null +++ b/src/ido-menu-item.c @@ -0,0 +1,439 @@ +/* + * Copyright 2012 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . + * + * Authors: + * Lars Uebernickel + */ + +#include "ido-menu-item.h" + +struct _IdoMenuItemPrivate +{ + GActionGroup *action_group; + gchar *action; + GVariant *target; + + GtkWidget *icon; + GtkWidget *label; + + gboolean has_indicator; + gboolean in_set_active; +}; + +enum +{ + PROP_0, + PROP_MENU_ITEM, + PROP_ACTION_GROUP, + NUM_PROPERTIES +}; + +static GParamSpec *properties[NUM_PROPERTIES]; + +G_DEFINE_TYPE (IdoMenuItem, ido_menu_item, GTK_TYPE_CHECK_MENU_ITEM); + +static void +ido_menu_item_constructed (GObject *object) +{ + IdoMenuItemPrivate *priv = IDO_MENU_ITEM (object)->priv; + GtkWidget *grid; + + priv->icon = g_object_ref (gtk_image_new ()); + gtk_widget_set_margin_right (priv->icon, 6); + + priv->label = g_object_ref (gtk_label_new ("")); + + grid = gtk_grid_new (); + gtk_grid_attach (GTK_GRID (grid), priv->icon, 0, 0, 1, 1); + gtk_grid_attach (GTK_GRID (grid), priv->label, 1, 0, 1, 1); + + gtk_container_add (GTK_CONTAINER (object), grid); + gtk_widget_show_all (grid); + + G_OBJECT_CLASS (ido_menu_item_parent_class)->constructed (object); +} + +static void +ido_menu_item_set_active (IdoMenuItem *self, + gboolean active) +{ + /* HACK gtk_check_menu_item_set_active calls gtk_menu_item_activate. + * Make sure our activate handler doesn't toggle the action as a + * result of calling this function. */ + + self->priv->in_set_active = TRUE; + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (self), active); + self->priv->in_set_active = FALSE; +} + +static void +ido_menu_item_set_has_indicator (IdoMenuItem *self, + gboolean has_indicator) +{ + if (has_indicator == self->priv->has_indicator) + return; + + self->priv->has_indicator = has_indicator; + + gtk_widget_queue_resize (GTK_WIDGET (self)); +} + +static void +ido_menu_item_set_state (IdoMenuItem *self, + GVariant *state) +{ + IdoMenuItemPrivate *priv = self->priv; + + if (priv->target) + { + ido_menu_item_set_has_indicator (self, TRUE); + ido_menu_item_set_active (self, FALSE); + gtk_check_menu_item_set_draw_as_radio (GTK_CHECK_MENU_ITEM (self), TRUE); + gtk_check_menu_item_set_inconsistent (GTK_CHECK_MENU_ITEM (self), FALSE); + + if (g_variant_is_of_type (state, G_VARIANT_TYPE_STRING)) + { + ido_menu_item_set_active (self, g_variant_equal (priv->target, state)); + } + else if (g_variant_is_of_type (state, G_VARIANT_TYPE ("as")) && + g_variant_is_of_type (priv->target, G_VARIANT_TYPE_STRING)) + { + const gchar *target_str; + const gchar **state_strs; + const gchar **it; + + target_str = g_variant_get_string (priv->target, NULL); + state_strs = g_variant_get_strv (state, NULL); + + it = state_strs; + while (*it != NULL && !g_str_equal (*it, target_str)) + it++; + + if (*it != NULL) + { + ido_menu_item_set_active (self, TRUE); + gtk_check_menu_item_set_inconsistent (GTK_CHECK_MENU_ITEM (self), + g_strv_length ((gchar **)state_strs) > 1); + } + + g_free (state_strs); + } + } + else if (g_variant_is_of_type (state, G_VARIANT_TYPE_BOOLEAN)) + { + ido_menu_item_set_has_indicator (self, TRUE); + gtk_check_menu_item_set_draw_as_radio (GTK_CHECK_MENU_ITEM (self), FALSE); + ido_menu_item_set_active (self, g_variant_get_boolean (state)); + } + else + { + ido_menu_item_set_has_indicator (self, FALSE); + } +} + +static void +ido_menu_item_set_action_name (IdoMenuItem *self, + const gchar *action_name) +{ + IdoMenuItemPrivate *priv = self->priv; + gboolean enabled = FALSE; + GVariant *state; + const GVariantType *param_type; + + if (priv->action != NULL) + g_free (priv->action); + + priv->action = g_strdup (action_name); + + if (priv->action_group != NULL && priv->action != NULL && + g_action_group_query_action (priv->action_group, priv->action, + &enabled, ¶m_type, NULL, NULL, &state)) + { + gtk_widget_set_sensitive (GTK_WIDGET (self), enabled); + + if (state) + { + ido_menu_item_set_state (self, state); + g_variant_unref (state); + } + } + else + { + ido_menu_item_set_active (self, FALSE); + gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE); + ido_menu_item_set_has_indicator (self, FALSE); + } +} + +static void +ido_menu_item_action_added (GActionGroup *action_group, + gchar *action_name, + gpointer user_data) +{ + IdoMenuItem *self = user_data; + + if (g_strcmp0 (self->priv->action, action_name) == 0) + ido_menu_item_set_action_name (self, action_name); +} + +static void +ido_menu_item_action_removed (GActionGroup *action_group, + gchar *action_name, + gpointer user_data) +{ + IdoMenuItem *self = user_data; + + if (g_strcmp0 (self->priv->action, action_name) == 0) + { + gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE); + } +} + +static void +ido_menu_item_action_enabled_changed (GActionGroup *action_group, + gchar *action_name, + gboolean enabled, + gpointer user_data) +{ + IdoMenuItem *self = user_data; + + if (g_strcmp0 (self->priv->action, action_name) == 0) + gtk_widget_set_sensitive (GTK_WIDGET (self), enabled); +} + +static void +ido_menu_item_action_state_changed (GActionGroup *action_group, + gchar *action_name, + GVariant *value, + gpointer user_data) +{ + IdoMenuItem *self = user_data; + + if (g_strcmp0 (self->priv->action, action_name) == 0) + ido_menu_item_set_state (self, value); +} + +static void +ido_menu_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + IdoMenuItem *self = IDO_MENU_ITEM (object); + + switch (property_id) + { + case PROP_MENU_ITEM: + ido_menu_item_set_menu_item (self, G_MENU_ITEM (g_value_get_object (value))); + break; + + case PROP_ACTION_GROUP: + ido_menu_item_set_action_group (self, G_ACTION_GROUP (g_value_get_object (value))); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ido_menu_item_dispose (GObject *object) +{ + IdoMenuItem *self = IDO_MENU_ITEM (object); + + if (self->priv->action_group) + ido_menu_item_set_action_group (self, NULL); + + g_clear_object (&self->priv->icon); + g_clear_object (&self->priv->label); + + if (self->priv->target) + { + g_variant_unref (self->priv->target); + self->priv->target = NULL; + } + + G_OBJECT_CLASS (ido_menu_item_parent_class)->dispose (object); +} + +static void +ido_menu_item_finalize (GObject *object) +{ + IdoMenuItemPrivate *priv = IDO_MENU_ITEM (object)->priv; + + g_free (priv->action); + + G_OBJECT_CLASS (ido_menu_item_parent_class)->finalize (object); +} + +static void +ido_menu_item_activate (GtkMenuItem *item) +{ + IdoMenuItemPrivate *priv = IDO_MENU_ITEM (item)->priv; + GVariant *parameter; + + /* see ido_menu_item_set_active */ + if (!priv->in_set_active && priv->action && priv->action_group) + { + guint32 event_time = gtk_get_current_event_time (); + + if (priv->target) + { + parameter = priv->target; + } + else + { + parameter = g_variant_new_uint32 (event_time); + } + + g_action_group_activate_action (priv->action_group, priv->action, parameter); + } + + if (priv->in_set_active) + GTK_MENU_ITEM_CLASS (ido_menu_item_parent_class)->activate (item); +} + +static void +ido_menu_item_draw_indicator (GtkCheckMenuItem *item, + cairo_t *cr) +{ + IdoMenuItem *self = IDO_MENU_ITEM (item); + + if (self->priv->has_indicator) + GTK_CHECK_MENU_ITEM_CLASS (ido_menu_item_parent_class) + ->draw_indicator (item, cr); +} + +static void +ido_menu_item_class_init (IdoMenuItemClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkMenuItemClass *menu_item_class = GTK_MENU_ITEM_CLASS (klass); + GtkCheckMenuItemClass *check_class = GTK_CHECK_MENU_ITEM_CLASS (klass); + + g_type_class_add_private (klass, sizeof (IdoMenuItemPrivate)); + + object_class->constructed = ido_menu_item_constructed; + object_class->set_property = ido_menu_set_property; + object_class->dispose = ido_menu_item_dispose; + object_class->finalize = ido_menu_item_finalize; + + menu_item_class->activate = ido_menu_item_activate; + + check_class->draw_indicator = ido_menu_item_draw_indicator; + + properties[PROP_MENU_ITEM] = g_param_spec_object ("menu-item", + "Menu item", + "The model GMenuItem for this menu item", + G_TYPE_MENU_ITEM, + G_PARAM_WRITABLE | + G_PARAM_STATIC_STRINGS); + + properties[PROP_ACTION_GROUP] = g_param_spec_object ("action-group", + "Action group", + "The action group associated with this menu item", + G_TYPE_ACTION_GROUP, + G_PARAM_WRITABLE | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, NUM_PROPERTIES, properties); +} + +static void +ido_menu_item_init (IdoMenuItem *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + IDO_TYPE_MENU_ITEM, + IdoMenuItemPrivate); +} + +void +ido_menu_item_set_menu_item (IdoMenuItem *self, + GMenuItem *menuitem) +{ + gchar *iconstr = NULL; + GIcon *icon = NULL; + gchar *label = NULL; + gchar *action = NULL; + + if (g_menu_item_get_attribute (menuitem, "x-canonical-icon", "s", &iconstr)) + { + GError *error; + + /* only indent the label if icon is set to "" */ + if (iconstr[0] == '\0') + { + gint width; + + gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &width, NULL); + gtk_widget_set_size_request (self->priv->icon, width, -1); + } + else + { + icon = g_icon_new_for_string (iconstr, &error); + if (icon == NULL) + { + g_warning ("unable to set icon: %s", error->message); + g_error_free (error); + } + } + g_free (iconstr); + } + gtk_image_set_from_gicon (GTK_IMAGE (self->priv->icon), icon, GTK_ICON_SIZE_MENU); + + g_menu_item_get_attribute (menuitem, "label", "s", &label); + gtk_label_set_label (GTK_LABEL (self->priv->label), label ? label : ""); + + self->priv->target = g_menu_item_get_attribute_value (menuitem, "target", NULL); + + g_menu_item_get_attribute (menuitem, "action", "s", &action); + ido_menu_item_set_action_name (self, action); + + if (icon) + g_object_unref (icon); + g_free (label); + g_free (action); +} + +void +ido_menu_item_set_action_group (IdoMenuItem *self, + GActionGroup *action_group) +{ + IdoMenuItemPrivate *priv = self->priv; + + if (priv->action_group != NULL) + { + g_signal_handlers_disconnect_by_func (priv->action_group, ido_menu_item_action_added, self); + g_signal_handlers_disconnect_by_func (priv->action_group, ido_menu_item_action_removed, self); + g_signal_handlers_disconnect_by_func (priv->action_group, ido_menu_item_action_enabled_changed, self); + g_signal_handlers_disconnect_by_func (priv->action_group, ido_menu_item_action_state_changed, self); + + g_clear_object (&priv->action_group); + } + + if (action_group != NULL) + { + priv->action_group = g_object_ref (action_group); + + g_signal_connect (priv->action_group, "action-added", + G_CALLBACK (ido_menu_item_action_added), self); + g_signal_connect (priv->action_group, "action-removed", + G_CALLBACK (ido_menu_item_action_removed), self); + g_signal_connect (priv->action_group, "action-enabled-changed", + G_CALLBACK (ido_menu_item_action_enabled_changed), self); + g_signal_connect (priv->action_group, "action-state-changed", + G_CALLBACK (ido_menu_item_action_state_changed), self); + } +} diff --git a/src/ido-menu-item.h b/src/ido-menu-item.h new file mode 100644 index 0000000..0521928 --- /dev/null +++ b/src/ido-menu-item.h @@ -0,0 +1,54 @@ +/* + * Copyright 2012 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . + * + * Authors: + * Lars Uebernickel + */ + +#ifndef __IDO_MENU_ITEM_H__ +#define __IDO_MENU_ITEM_H__ + +#include + +#define IDO_TYPE_MENU_ITEM (ido_menu_item_get_type ()) +#define IDO_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IDO_TYPE_MENU_ITEM, IdoMenuItem)) +#define IDO_MENU_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), IDO_TYPE_MENU_ITEM, IdoMenuItemClass)) +#define IS_IDO_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IDO_TYPE_MENU_ITEM)) +#define IS_IDO_MENU_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), IDO_TYPE_MENU_ITEM)) +#define IDO_MENU_ITEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), IDO_TYPE_MENU_ITEM, IdoMenuItemClass)) + +typedef struct _IdoMenuItem IdoMenuItem; +typedef struct _IdoMenuItemClass IdoMenuItemClass; +typedef struct _IdoMenuItemPrivate IdoMenuItemPrivate; + +struct _IdoMenuItemClass +{ + GtkCheckMenuItemClass parent_class; +}; + +struct _IdoMenuItem +{ + GtkCheckMenuItem parent; + IdoMenuItemPrivate *priv; +}; + +GType ido_menu_item_get_type (void); + +void ido_menu_item_set_menu_item (IdoMenuItem *item, + GMenuItem *menuitem); +void ido_menu_item_set_action_group (IdoMenuItem *self, + GActionGroup *action_group); + +#endif diff --git a/src/im-app-menu-item.c b/src/im-app-menu-item.c new file mode 100644 index 0000000..03b11c2 --- /dev/null +++ b/src/im-app-menu-item.c @@ -0,0 +1,354 @@ +/* + * Copyright 2012 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . + * + * Authors: + * Lars Uebernickel + */ + +#include "im-app-menu-item.h" + +struct _ImAppMenuItemPrivate +{ + GActionGroup *action_group; + gchar *action; + gboolean is_running; + + GtkWidget *icon; + GtkWidget *label; +}; + +enum +{ + PROP_0, + PROP_MENU_ITEM, + PROP_ACTION_GROUP, + NUM_PROPERTIES +}; + +static GParamSpec *properties[NUM_PROPERTIES]; + +G_DEFINE_TYPE (ImAppMenuItem, im_app_menu_item, GTK_TYPE_MENU_ITEM); + +static void +im_app_menu_item_constructed (GObject *object) +{ + ImAppMenuItemPrivate *priv = IM_APP_MENU_ITEM (object)->priv; + GtkWidget *grid; + + priv->icon = g_object_ref (gtk_image_new ()); + gtk_widget_set_margin_right (priv->icon, 6); + + priv->label = g_object_ref (gtk_label_new ("")); + + grid = gtk_grid_new (); + gtk_grid_attach (GTK_GRID (grid), priv->icon, 0, 0, 1, 1); + gtk_grid_attach (GTK_GRID (grid), priv->label, 1, 0, 1, 1); + + gtk_container_add (GTK_CONTAINER (object), grid); + gtk_widget_show_all (grid); + + G_OBJECT_CLASS (im_app_menu_item_parent_class)->constructed (object); +} + +static void +im_app_menu_item_set_action_name (ImAppMenuItem *self, + const gchar *action_name) +{ + ImAppMenuItemPrivate *priv = self->priv; + gboolean enabled = FALSE; + GVariant *state; + + if (priv->action != NULL) + g_free (priv->action); + + priv->action = g_strdup (action_name); + + priv->is_running = FALSE; + + if (priv->action_group != NULL && priv->action != NULL && + g_action_group_query_action (priv->action_group, priv->action, + &enabled, NULL, NULL, NULL, &state)) + { + if (state && g_variant_is_of_type (state, G_VARIANT_TYPE ("b"))) + priv->is_running = g_variant_get_boolean (state); + else + enabled = FALSE; + + if (state) + g_variant_unref (state); + } + + gtk_widget_set_sensitive (GTK_WIDGET (self), enabled); + gtk_widget_queue_draw (GTK_WIDGET (self)); +} + +static void +im_app_menu_item_action_added (GActionGroup *action_group, + gchar *action_name, + gpointer user_data) +{ + ImAppMenuItem *self = user_data; + + if (g_strcmp0 (self->priv->action, action_name) == 0) + im_app_menu_item_set_action_name (self, action_name); +} + +static void +im_app_menu_item_action_removed (GActionGroup *action_group, + gchar *action_name, + gpointer user_data) +{ + ImAppMenuItem *self = user_data; + + if (g_strcmp0 (self->priv->action, action_name) == 0) + { + gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE); + self->priv->is_running = FALSE; + gtk_widget_queue_draw (GTK_WIDGET (self)); + } +} + +static void +im_app_menu_item_action_enabled_changed (GActionGroup *action_group, + gchar *action_name, + gboolean enabled, + gpointer user_data) +{ + ImAppMenuItem *self = user_data; + + if (g_strcmp0 (self->priv->action, action_name) == 0) + gtk_widget_set_sensitive (GTK_WIDGET (self), enabled); +} + +static void +im_app_menu_item_action_state_changed (GActionGroup *action_group, + gchar *action_name, + GVariant *value, + gpointer user_data) +{ + ImAppMenuItem *self = user_data; + + if (g_strcmp0 (self->priv->action, action_name) == 0) + { + g_return_if_fail (g_variant_is_of_type (value, G_VARIANT_TYPE ("b"))); + + self->priv->is_running = g_variant_get_boolean (value); + gtk_widget_queue_draw (GTK_WIDGET (self)); + } +} + +static void +im_app_menu_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + ImAppMenuItem *self = IM_APP_MENU_ITEM (object); + + switch (property_id) + { + case PROP_MENU_ITEM: + im_app_menu_item_set_menu_item (self, G_MENU_ITEM (g_value_get_object (value))); + break; + + case PROP_ACTION_GROUP: + im_app_menu_item_set_action_group (self, G_ACTION_GROUP (g_value_get_object (value))); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +im_app_menu_item_dispose (GObject *object) +{ + ImAppMenuItem *self = IM_APP_MENU_ITEM (object); + + if (self->priv->action_group) + im_app_menu_item_set_action_group (self, NULL); + + g_clear_object (&self->priv->icon); + g_clear_object (&self->priv->label); + + G_OBJECT_CLASS (im_app_menu_item_parent_class)->dispose (object); +} + +static void +im_app_menu_item_finalize (GObject *object) +{ + ImAppMenuItemPrivate *priv = IM_APP_MENU_ITEM (object)->priv; + + g_free (priv->action); + + G_OBJECT_CLASS (im_app_menu_item_parent_class)->finalize (object); +} + +static gboolean +im_app_menu_item_draw (GtkWidget *widget, + cairo_t *cr) +{ + ImAppMenuItemPrivate *priv = IM_APP_MENU_ITEM (widget)->priv; + + GTK_WIDGET_CLASS (im_app_menu_item_parent_class)->draw (widget, cr); + + if (priv->is_running) + { + const int arrow_width = 5; + const double half_arrow_height = 4.5; + GtkAllocation alloc; + GdkRGBA color; + double center; + + gtk_widget_get_allocation (widget, &alloc); + + gtk_style_context_get_color (gtk_widget_get_style_context (widget), + gtk_widget_get_state_flags (widget), + &color); + gdk_cairo_set_source_rgba (cr, &color); + + center = alloc.height / 2 + 0.5; + + cairo_move_to (cr, 0, center - half_arrow_height); + cairo_line_to (cr, 0, center + half_arrow_height); + cairo_line_to (cr, arrow_width, center); + cairo_close_path (cr); + + cairo_fill (cr); + } + + return FALSE; +} + +static void +im_app_menu_item_activate (GtkMenuItem *item) +{ + ImAppMenuItemPrivate *priv = IM_APP_MENU_ITEM (item)->priv; + + if (priv->action && priv->action_group) + { + guint32 event_time = gtk_get_current_event_time (); + g_action_group_activate_action (priv->action_group, priv->action, g_variant_new_uint32 (event_time)); + } +} + +static void +im_app_menu_item_class_init (ImAppMenuItemClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GtkMenuItemClass *menu_item_class = GTK_MENU_ITEM_CLASS (klass); + + g_type_class_add_private (klass, sizeof (ImAppMenuItemPrivate)); + + object_class->constructed = im_app_menu_item_constructed; + object_class->set_property = im_app_menu_set_property; + object_class->dispose = im_app_menu_item_dispose; + object_class->finalize = im_app_menu_item_finalize; + + widget_class->draw = im_app_menu_item_draw; + + menu_item_class->activate = im_app_menu_item_activate; + + properties[PROP_MENU_ITEM] = g_param_spec_object ("menu-item", + "Menu item", + "The model GMenuItem for this menu item", + G_TYPE_MENU_ITEM, + G_PARAM_WRITABLE | + G_PARAM_STATIC_STRINGS); + + properties[PROP_ACTION_GROUP] = g_param_spec_object ("action-group", + "Action group", + "The action group associated with this menu item", + G_TYPE_ACTION_GROUP, + G_PARAM_WRITABLE | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, NUM_PROPERTIES, properties); +} + +static void +im_app_menu_item_init (ImAppMenuItem *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + IM_TYPE_APP_MENU_ITEM, + ImAppMenuItemPrivate); +} + +void +im_app_menu_item_set_menu_item (ImAppMenuItem *self, + GMenuItem *menuitem) +{ + gchar *iconstr = NULL; + GIcon *icon = NULL; + gchar *label; + gchar *action = NULL; + + if (g_menu_item_get_attribute (menuitem, "x-canonical-icon", "s", &iconstr)) + { + GError *error; + + icon = g_icon_new_for_string (iconstr, &error); + if (icon == NULL) + { + g_warning ("unable to set icon: %s", error->message); + g_error_free (error); + } + g_free (iconstr); + } + gtk_image_set_from_gicon (GTK_IMAGE (self->priv->icon), icon, GTK_ICON_SIZE_MENU); + + g_menu_item_get_attribute (menuitem, "label", "s", &label); + gtk_label_set_label (GTK_LABEL (self->priv->label), label ? label : ""); + + g_menu_item_get_attribute (menuitem, "action", "s", &action); + im_app_menu_item_set_action_name (self, action); + + if (icon) + g_object_unref (icon); + g_free (label); + g_free (action); +} + +void +im_app_menu_item_set_action_group (ImAppMenuItem *self, + GActionGroup *action_group) +{ + ImAppMenuItemPrivate *priv = self->priv; + + if (priv->action_group != NULL) + { + g_signal_handlers_disconnect_by_func (priv->action_group, im_app_menu_item_action_added, self); + g_signal_handlers_disconnect_by_func (priv->action_group, im_app_menu_item_action_removed, self); + g_signal_handlers_disconnect_by_func (priv->action_group, im_app_menu_item_action_enabled_changed, self); + g_signal_handlers_disconnect_by_func (priv->action_group, im_app_menu_item_action_state_changed, self); + + g_clear_object (&priv->action_group); + } + + if (action_group != NULL) + { + priv->action_group = g_object_ref (action_group); + + g_signal_connect (priv->action_group, "action-added", + G_CALLBACK (im_app_menu_item_action_added), self); + g_signal_connect (priv->action_group, "action-removed", + G_CALLBACK (im_app_menu_item_action_removed), self); + g_signal_connect (priv->action_group, "action-enabled-changed", + G_CALLBACK (im_app_menu_item_action_enabled_changed), self); + g_signal_connect (priv->action_group, "action-state-changed", + G_CALLBACK (im_app_menu_item_action_state_changed), self); + } +} diff --git a/src/im-app-menu-item.h b/src/im-app-menu-item.h new file mode 100644 index 0000000..519de8d --- /dev/null +++ b/src/im-app-menu-item.h @@ -0,0 +1,54 @@ +/* + * Copyright 2012 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . + * + * Authors: + * Lars Uebernickel + */ + +#ifndef __IM_APP_MENU_ITEM_H__ +#define __IM_APP_MENU_ITEM_H__ + +#include + +#define IM_TYPE_APP_MENU_ITEM (im_app_menu_item_get_type ()) +#define IM_APP_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IM_TYPE_APP_MENU_ITEM, ImAppMenuItem)) +#define IM_APP_MENU_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), IM_TYPE_APP_MENU_ITEM, ImAppMenuItemClass)) +#define IS_IM_APP_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IM_TYPE_APP_MENU_ITEM)) +#define IS_IM_APP_MENU_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), IM_TYPE_APP_MENU_ITEM)) +#define IM_APP_MENU_ITEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), IM_TYPE_APP_MENU_ITEM, ImAppMenuItemClass)) + +typedef struct _ImAppMenuItem ImAppMenuItem; +typedef struct _ImAppMenuItemClass ImAppMenuItemClass; +typedef struct _ImAppMenuItemPrivate ImAppMenuItemPrivate; + +struct _ImAppMenuItemClass +{ + GtkMenuItemClass parent_class; +}; + +struct _ImAppMenuItem +{ + GtkMenuItem parent; + ImAppMenuItemPrivate *priv; +}; + +GType im_app_menu_item_get_type (void); + +void im_app_menu_item_set_menu_item (ImAppMenuItem *item, + GMenuItem *menuitem); +void im_app_menu_item_set_action_group (ImAppMenuItem *self, + GActionGroup *action_group); + +#endif diff --git a/src/im-application-list.c b/src/im-application-list.c deleted file mode 100644 index bba7972..0000000 --- a/src/im-application-list.c +++ /dev/null @@ -1,1139 +0,0 @@ -/* - * Copyright 2012 Canonical Ltd. - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 3, as published - * by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranties of - * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . - * - * Authors: - * Lars Uebernickel - */ - -#include "im-application-list.h" - -#include "indicator-messages-application.h" -#include "gactionmuxer.h" - -#include -#include - -typedef GObjectClass ImApplicationListClass; - -struct _ImApplicationList -{ - GObject parent; - - GHashTable *applications; - GActionMuxer *muxer; - - GSimpleActionGroup * globalactions; - GSimpleAction * statusaction; - - GHashTable *app_status; -}; - -G_DEFINE_TYPE (ImApplicationList, im_application_list, G_TYPE_OBJECT); -G_DEFINE_QUARK (draws_attention, message_action_draws_attention); - -enum -{ - SOURCE_ADDED, - SOURCE_CHANGED, - SOURCE_REMOVED, - MESSAGE_ADDED, - MESSAGE_REMOVED, - APP_ADDED, - APP_STOPPED, - REMOVE_ALL, - STATUS_SET, - N_SIGNALS -}; - -static guint signals[N_SIGNALS]; - -typedef struct -{ - ImApplicationList *list; - GDesktopAppInfo *info; - gchar *id; - IndicatorMessagesApplication *proxy; - GActionMuxer *muxer; - GSimpleActionGroup *actions; - GSimpleActionGroup *source_actions; - GSimpleActionGroup *message_actions; - GActionMuxer *message_sub_actions; - GCancellable *cancellable; - gboolean draws_attention; -} Application; - - -/* Prototypes */ -static void status_activated (GSimpleAction * action, - GVariant * param, - gpointer user_data); - -static void -application_free (gpointer data) -{ - Application *app = data; - - if (!app) - return; - - g_object_unref (app->info); - g_free (app->id); - - if (app->cancellable) - { - g_cancellable_cancel (app->cancellable); - g_clear_object (&app->cancellable); - } - - if (app->proxy) - g_object_unref (app->proxy); - - if (app->muxer) - { - g_object_unref (app->muxer); - g_object_unref (app->source_actions); - g_object_unref (app->message_actions); - g_object_unref (app->message_sub_actions); - } - - g_slice_free (Application, app); -} - -static gboolean -application_draws_attention (gpointer key, - gpointer value, - gpointer user_data) -{ - Application *app = value; - - return app->draws_attention; -} - -static void -im_application_list_update_draws_attention (ImApplicationList *list) -{ - const gchar *icon_name; - GVariant *state; - GActionGroup *main_actions; - - if (g_hash_table_find (list->applications, application_draws_attention, NULL)) - icon_name = "indicator-messages-new"; - else - icon_name = "indicator-messages"; - - main_actions = g_action_muxer_get_group (list->muxer, NULL); - state = g_variant_new ("(sssb)", "", icon_name, "Messages", TRUE); - g_action_group_change_action_state (main_actions, "messages", state); -} - -/* Check a source action to see if it draws */ -static gboolean -app_source_action_check_draw (Application * app, const gchar * action_name) -{ - gboolean retval = FALSE; - GVariant * state; - GVariant * draw; - - state = g_action_group_get_action_state (G_ACTION_GROUP(app->source_actions), action_name); - - /* uxsb */ - draw = g_variant_get_child_value(state, 3); - retval = g_variant_get_boolean(draw); - - g_variant_unref(draw); - g_variant_unref(state); - - return retval; -} - -/* Check a message action to see if it draws */ -static gboolean -app_message_action_check_draw (Application * app, const gchar * action_name) -{ - GAction * action = NULL; - action = g_simple_action_group_lookup (app->message_actions, action_name); - return GPOINTER_TO_INT(g_object_get_qdata(G_OBJECT(action), message_action_draws_attention_quark())); -} - -/* Regenerate the draw attention flag based on the sources and messages - that we have in the action groups */ -static void -app_check_draw_attention (Application * app) -{ - gchar **source_actions = NULL; - gchar **message_actions = NULL; - gchar **it; - - source_actions = g_action_group_list_actions (G_ACTION_GROUP (app->source_actions)); - for (it = source_actions; *it && !app->draws_attention; it++) - app->draws_attention = app_source_action_check_draw (app, *it); - - message_actions = g_action_group_list_actions (G_ACTION_GROUP (app->message_actions)); - for (it = message_actions; *it; it++) - app->draws_attention = app_message_action_check_draw (app, *it); - - g_strfreev (source_actions); - g_strfreev (message_actions); - - return; -} - -/* Remove a source from an application, signal up and update the status - of the draws attention flag. */ -static void -im_application_list_source_removed (Application *app, - const gchar *id) -{ - g_simple_action_group_remove (app->source_actions, id); - - g_signal_emit (app->list, signals[SOURCE_REMOVED], 0, app->id, id); - - if (app->draws_attention) - { - app->draws_attention = FALSE; - app_check_draw_attention(app); - } - - im_application_list_update_draws_attention (app->list); -} - -static void -im_application_list_source_activated (GSimpleAction *action, - GVariant *parameter, - gpointer user_data) -{ - Application *app = user_data; - const gchar *source_id; - - source_id = g_action_get_name (G_ACTION (action)); - - if (g_variant_get_boolean (parameter)) - { - indicator_messages_application_call_activate_source (app->proxy, - source_id, - app->cancellable, - NULL, NULL); - } - else - { - const gchar *sources[] = { source_id, NULL }; - const gchar *messages[] = { NULL }; - indicator_messages_application_call_dismiss (app->proxy, sources, messages, - app->cancellable, NULL, NULL); - } - - im_application_list_source_removed (app, source_id); -} - -static void -im_application_list_message_removed (Application *app, - const gchar *id) -{ - g_simple_action_group_remove (app->message_actions, id); - g_action_muxer_remove (app->message_sub_actions, id); - - im_application_list_update_draws_attention (app->list); - - g_signal_emit (app->list, signals[MESSAGE_REMOVED], 0, app->id, id); -} - -static void -im_application_list_message_activated (GSimpleAction *action, - GVariant *parameter, - gpointer user_data) -{ - Application *app = user_data; - const gchar *message_id; - - message_id = g_action_get_name (G_ACTION (action)); - - if (g_variant_get_boolean (parameter)) - { - indicator_messages_application_call_activate_message (app->proxy, - message_id, - "", - g_variant_new_array (G_VARIANT_TYPE_VARIANT, NULL, 0), - app->cancellable, - NULL, NULL); - } - else - { - const gchar *sources[] = { NULL }; - const gchar *messages[] = { message_id, NULL }; - indicator_messages_application_call_dismiss (app->proxy, sources, messages, - app->cancellable, NULL, NULL); - } - - im_application_list_message_removed (app, message_id); -} - -static void -im_application_list_sub_message_activated (GSimpleAction *action, - GVariant *parameter, - gpointer user_data) -{ - Application *app = user_data; - const gchar *message_id; - const gchar *action_id; - GVariantBuilder builder; - - message_id = g_object_get_data (G_OBJECT (action), "message"); - action_id = g_action_get_name (G_ACTION (action)); - - g_variant_builder_init (&builder, G_VARIANT_TYPE ("av")); - if (parameter) - g_variant_builder_add (&builder, "v", parameter); - - indicator_messages_application_call_activate_message (app->proxy, - message_id, - action_id, - g_variant_builder_end (&builder), - app->cancellable, - NULL, NULL); - - im_application_list_message_removed (app, message_id); -} - -static void -im_application_list_remove_all (GSimpleAction *action, - GVariant *parameter, - gpointer user_data) -{ - ImApplicationList *list = user_data; - GHashTableIter iter; - Application *app; - - g_signal_emit (list, signals[REMOVE_ALL], 0); - - g_hash_table_iter_init (&iter, list->applications); - while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &app)) - { - gchar **source_actions; - gchar **message_actions; - gchar **it; - - app->draws_attention = FALSE; - - source_actions = g_action_group_list_actions (G_ACTION_GROUP (app->source_actions)); - for (it = source_actions; *it; it++) - im_application_list_source_removed (app, *it); - - message_actions = g_action_group_list_actions (G_ACTION_GROUP (app->message_actions)); - for (it = message_actions; *it; it++) - im_application_list_message_removed (app, *it); - - if (app->proxy != NULL) /* If it is remote, we tell the app we've cleared */ - { - indicator_messages_application_call_dismiss (app->proxy, - (const gchar * const *) source_actions, - (const gchar * const *) message_actions, - app->cancellable, NULL, NULL); - } - - g_strfreev (source_actions); - g_strfreev (message_actions); - } - - im_application_list_update_draws_attention (list); -} - -static void -im_application_list_dispose (GObject *object) -{ - ImApplicationList *list = IM_APPLICATION_LIST (object); - - g_clear_object (&list->statusaction); - g_clear_object (&list->globalactions); - g_clear_pointer (&list->app_status, g_hash_table_unref); - - g_clear_pointer (&list->applications, g_hash_table_unref); - g_clear_object (&list->muxer); - - G_OBJECT_CLASS (im_application_list_parent_class)->dispose (object); -} - -static void -im_application_list_finalize (GObject *object) -{ - G_OBJECT_CLASS (im_application_list_parent_class)->finalize (object); -} - -static void -im_application_list_class_init (ImApplicationListClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->dispose = im_application_list_dispose; - object_class->finalize = im_application_list_finalize; - - signals[SOURCE_ADDED] = g_signal_new ("source-added", - IM_TYPE_APPLICATION_LIST, - G_SIGNAL_RUN_FIRST, - 0, - NULL, NULL, - g_cclosure_marshal_generic, - G_TYPE_NONE, - 4, - G_TYPE_STRING, - G_TYPE_STRING, - G_TYPE_STRING, - G_TYPE_STRING); - - signals[SOURCE_CHANGED] = g_signal_new ("source-changed", - IM_TYPE_APPLICATION_LIST, - G_SIGNAL_RUN_FIRST, - 0, - NULL, NULL, - g_cclosure_marshal_generic, - G_TYPE_NONE, - 4, - G_TYPE_STRING, - G_TYPE_STRING, - G_TYPE_STRING, - G_TYPE_STRING); - - signals[SOURCE_REMOVED] = g_signal_new ("source-removed", - IM_TYPE_APPLICATION_LIST, - G_SIGNAL_RUN_FIRST, - 0, - NULL, NULL, - g_cclosure_marshal_generic, - G_TYPE_NONE, - 2, - G_TYPE_STRING, - G_TYPE_STRING); - - signals[MESSAGE_ADDED] = g_signal_new ("message-added", - IM_TYPE_APPLICATION_LIST, - G_SIGNAL_RUN_FIRST, - 0, - NULL, NULL, - g_cclosure_marshal_generic, - G_TYPE_NONE, - 10, - G_TYPE_STRING, - G_TYPE_STRING, - G_TYPE_STRING, - G_TYPE_STRING, - G_TYPE_STRING, - G_TYPE_STRING, - G_TYPE_STRING, - G_TYPE_VARIANT, - G_TYPE_INT64, - G_TYPE_BOOLEAN); - - signals[MESSAGE_REMOVED] = g_signal_new ("message-removed", - IM_TYPE_APPLICATION_LIST, - G_SIGNAL_RUN_FIRST, - 0, - NULL, NULL, - g_cclosure_marshal_generic, - G_TYPE_NONE, - 2, - G_TYPE_STRING, - G_TYPE_STRING); - - signals[APP_ADDED] = g_signal_new ("app-added", - IM_TYPE_APPLICATION_LIST, - G_SIGNAL_RUN_FIRST, - 0, - NULL, NULL, - g_cclosure_marshal_generic, - G_TYPE_NONE, - 2, - G_TYPE_STRING, - G_TYPE_DESKTOP_APP_INFO); - - signals[APP_STOPPED] = g_signal_new ("app-stopped", - IM_TYPE_APPLICATION_LIST, - G_SIGNAL_RUN_FIRST, - 0, - NULL, NULL, - g_cclosure_marshal_VOID__OBJECT, - G_TYPE_NONE, - 1, - G_TYPE_STRING); - - signals[REMOVE_ALL] = g_signal_new ("remove-all", - IM_TYPE_APPLICATION_LIST, - G_SIGNAL_RUN_FIRST, - 0, - NULL, NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, - 0); - - signals[STATUS_SET] = g_signal_new ("status-set", - IM_TYPE_APPLICATION_LIST, - G_SIGNAL_RUN_FIRST, - 0, - NULL, NULL, - g_cclosure_marshal_generic, - G_TYPE_NONE, - 1, - G_TYPE_STRING); -} - -static void -im_application_list_init (ImApplicationList *list) -{ - const GActionEntry action_entries[] = { - { "messages", NULL, NULL, "('', 'indicator-messages', 'Messages', true)", NULL }, - { "remove-all", im_application_list_remove_all } - }; - - list->applications = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, application_free); - list->app_status = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); - - list->globalactions = g_simple_action_group_new (); - g_simple_action_group_add_entries (list->globalactions, action_entries, G_N_ELEMENTS (action_entries), list); - - list->statusaction = g_simple_action_new_stateful("status", G_VARIANT_TYPE_STRING, g_variant_new_string("offline")); - g_signal_connect(list->statusaction, "activate", G_CALLBACK(status_activated), list); - g_simple_action_group_insert(list->globalactions, G_ACTION(list->statusaction)); - - list->muxer = g_action_muxer_new (); - g_action_muxer_insert (list->muxer, NULL, G_ACTION_GROUP (list->globalactions)); - -} - -ImApplicationList * -im_application_list_new (void) -{ - return g_object_new (IM_TYPE_APPLICATION_LIST, NULL); -} - -static gchar * -im_application_list_canonical_id (const gchar *id) -{ - gchar *str; - gchar *p; - int len; - - len = strlen (id); - if (g_str_has_suffix (id, ".desktop")) - len -= 8; - - str = g_strndup (id, len); - - for (p = str; *p; p++) - { - if (*p == '.') - *p = '_'; - } - - return str; -} - -static Application * -im_application_list_lookup (ImApplicationList *list, - const gchar *desktop_id) -{ - gchar *id; - Application *app; - - id = im_application_list_canonical_id (desktop_id); - app = g_hash_table_lookup (list->applications, id); - - g_free (id); - return app; -} - -void -im_application_list_activate_launch (GSimpleAction *action, - GVariant *parameter, - gpointer user_data) -{ - Application *app = user_data; - GError *error = NULL; - - if (!g_app_info_launch (G_APP_INFO (app->info), NULL, NULL, &error)) - { - g_warning ("unable to launch application: %s", error->message); - g_error_free (error); - } -} - -void -im_application_list_activate_app_action (GSimpleAction *action, - GVariant *parameter, - gpointer user_data) -{ - Application *app = user_data; - - g_desktop_app_info_launch_action (app->info, g_action_get_name (G_ACTION (action)), NULL); -} - -void -im_application_list_add (ImApplicationList *list, - const gchar *desktop_id) -{ - GDesktopAppInfo *info; - Application *app; - const gchar *id; - GSimpleActionGroup *actions; - GSimpleAction *launch_action; - - g_return_if_fail (IM_IS_APPLICATION_LIST (list)); - g_return_if_fail (desktop_id != NULL); - - if (im_application_list_lookup (list, desktop_id)) - return; - - info = g_desktop_app_info_new (desktop_id); - if (!info) - { - g_warning ("an application with id '%s' is not installed", desktop_id); - return; - } - - id = g_app_info_get_id (G_APP_INFO (info)); - g_return_if_fail (id != NULL); - - app = g_slice_new0 (Application); - app->info = info; - app->id = im_application_list_canonical_id (id); - app->list = list; - app->muxer = g_action_muxer_new (); - app->source_actions = g_simple_action_group_new (); - app->message_actions = g_simple_action_group_new (); - app->message_sub_actions = g_action_muxer_new (); - app->draws_attention = FALSE; - - actions = g_simple_action_group_new (); - - launch_action = g_simple_action_new_stateful ("launch", NULL, g_variant_new_boolean (FALSE)); - g_signal_connect (launch_action, "activate", G_CALLBACK (im_application_list_activate_launch), app); - g_action_map_add_action (G_ACTION_MAP (actions), G_ACTION (launch_action)); - - { - const gchar *const *app_actions; - - for (app_actions = g_desktop_app_info_list_actions (app->info); *app_actions; app_actions++) - { - GSimpleAction *action; - - action = g_simple_action_new (*app_actions, NULL); - g_signal_connect (action, "activate", G_CALLBACK (im_application_list_activate_app_action), app); - g_action_map_add_action (G_ACTION_MAP (actions), G_ACTION (action)); - - g_object_unref (action); - } - } - - g_action_muxer_insert (app->muxer, NULL, G_ACTION_GROUP (actions)); - g_action_muxer_insert (app->muxer, "src", G_ACTION_GROUP (app->source_actions)); - g_action_muxer_insert (app->muxer, "msg", G_ACTION_GROUP (app->message_actions)); - g_action_muxer_insert (app->muxer, "msg-actions", G_ACTION_GROUP (app->message_sub_actions)); - - g_hash_table_insert (list->applications, (gpointer) app->id, app); - g_action_muxer_insert (list->muxer, app->id, G_ACTION_GROUP (app->muxer)); - - g_signal_emit (app->list, signals[APP_ADDED], 0, app->id, app->info); - - g_object_unref (launch_action); - g_object_unref (actions); -} - -void -im_application_list_remove (ImApplicationList *list, - const gchar *id) -{ - Application *app; - - g_return_if_fail (IM_IS_APPLICATION_LIST (list)); - - app = im_application_list_lookup (list, id); - if (app) - { - if (app->proxy || app->cancellable) - g_signal_emit (app->list, signals[APP_STOPPED], 0, app->id); - - g_hash_table_remove (list->applications, id); - g_action_muxer_remove (list->muxer, id); - - im_application_list_update_draws_attention (list); - } -} - -static void -im_application_list_source_added (Application *app, - guint position, - GVariant *source) -{ - const gchar *id; - const gchar *label; - const gchar *iconstr; - guint32 count; - gint64 time; - const gchar *string; - gboolean draws_attention; - GVariant *state; - GSimpleAction *action; - - g_variant_get (source, "(&s&s&sux&sb)", - &id, &label, &iconstr, &count, &time, &string, &draws_attention); - - state = g_variant_new ("(uxsb)", count, time, string, draws_attention); - action = g_simple_action_new_stateful (id, G_VARIANT_TYPE_BOOLEAN, state); - g_signal_connect (action, "activate", G_CALLBACK (im_application_list_source_activated), app); - - g_simple_action_group_insert (app->source_actions, G_ACTION (action)); - - g_signal_emit (app->list, signals[SOURCE_ADDED], 0, app->id, id, label, iconstr); - - if (draws_attention) - app->draws_attention = TRUE; - - im_application_list_update_draws_attention (app->list); - - g_object_unref (action); -} - -static void -im_application_list_source_changed (Application *app, - GVariant *source) -{ - const gchar *id; - const gchar *label; - const gchar *iconstr; - guint32 count; - gint64 time; - const gchar *string; - gboolean draws_attention; - - g_variant_get (source, "(&s&s&sux&sb)", - &id, &label, &iconstr, &count, &time, &string, &draws_attention); - - g_action_group_change_action_state (G_ACTION_GROUP (app->source_actions), id, - g_variant_new ("(uxsb)", count, time, string, draws_attention)); - - g_signal_emit (app->list, signals[SOURCE_CHANGED], 0, app->id, id, label, iconstr); - - im_application_list_update_draws_attention (app->list); -} - -static void -im_application_list_sources_listed (GObject *source_object, - GAsyncResult *result, - gpointer user_data) -{ - Application *app = user_data; - GVariant *sources; - GError *error = NULL; - - if (indicator_messages_application_call_list_sources_finish (app->proxy, &sources, result, &error)) - { - GVariantIter iter; - GVariant *source; - guint i = 0; - - g_variant_iter_init (&iter, sources); - while ((source = g_variant_iter_next_value (&iter))) - { - im_application_list_source_added (app, i++, source); - g_variant_unref (source); - } - - g_variant_unref (sources); - } - else - { - g_warning ("could not fetch the list of sources: %s", error->message); - g_error_free (error); - } -} - -static gchar * -get_symbolic_app_icon_string (GIcon *icon) -{ - const gchar * const *names; - gchar *symbolic_name; - GIcon *symbolic_icon; - gchar *str; - - if (!G_IS_THEMED_ICON (icon)) - return NULL; - - names = g_themed_icon_get_names (G_THEMED_ICON (icon)); - if (!names || !names[0]) - return NULL; - - symbolic_icon = g_themed_icon_new_from_names ((gchar **) names, -1); - - symbolic_name = g_strconcat (names[0], "-symbolic", NULL); - g_themed_icon_prepend_name (G_THEMED_ICON (symbolic_icon), symbolic_name); - - str = g_icon_to_string (symbolic_icon); - - g_free (symbolic_name); - g_object_unref (symbolic_icon); - return str; -} - -static void -im_application_list_message_added (Application *app, - GVariant *message) -{ - const gchar *id; - const gchar *iconstr; - const gchar *title; - const gchar *subtitle; - const gchar *body; - gint64 time; - GVariantIter *action_iter; - gboolean draws_attention; - GSimpleAction *action; - GIcon *app_icon; - gchar *app_iconstr = NULL; - GVariant *actions = NULL; - - g_variant_get (message, "(&s&s&s&s&sxaa{sv}b)", - &id, &iconstr, &title, &subtitle, &body, &time, &action_iter, &draws_attention); - - app_icon = g_app_info_get_icon (G_APP_INFO (app->info)); - if (app_icon) - app_iconstr = get_symbolic_app_icon_string (app_icon); - - action = g_simple_action_new (id, G_VARIANT_TYPE_BOOLEAN); - g_object_set_qdata(G_OBJECT(action), message_action_draws_attention_quark(), GINT_TO_POINTER(draws_attention)); - g_signal_connect (action, "activate", G_CALLBACK (im_application_list_message_activated), app); - g_simple_action_group_insert (app->message_actions, G_ACTION (action)); - - { - GVariant *entry; - GSimpleActionGroup *action_group; - GVariantBuilder actions_builder; - - g_variant_builder_init (&actions_builder, G_VARIANT_TYPE ("aa{sv}")); - action_group = g_simple_action_group_new (); - - while ((entry = g_variant_iter_next_value (action_iter))) - { - const gchar *name; - GSimpleAction *action; - GVariant *label; - const gchar *type = NULL; - GVariant *hint; - GVariantBuilder dict_builder; - gchar *prefixed_name; - - if (!g_variant_lookup (entry, "name", "&s", &name)) - { - g_warning ("action dictionary for message '%s' is missing 'name' key", id); - continue; - } - - label = g_variant_lookup_value (entry, "label", G_VARIANT_TYPE_STRING); - g_variant_lookup (entry, "parameter-type", "&g", &type); - hint = g_variant_lookup_value (entry, "parameter-hint", NULL); - - action = g_simple_action_new (name, type ? G_VARIANT_TYPE (type) : NULL); - g_object_set_data_full (G_OBJECT (action), "message", g_strdup (id), g_free); - g_signal_connect (action, "activate", G_CALLBACK (im_application_list_sub_message_activated), app); - g_simple_action_group_insert (action_group, G_ACTION (action)); - - g_variant_builder_init (&dict_builder, G_VARIANT_TYPE ("a{sv}")); - - prefixed_name = g_strjoin (".", app->id, "msg-actions", id, name, NULL); - g_variant_builder_add (&dict_builder, "{sv}", "name", g_variant_new_string (prefixed_name)); - - if (label) - { - g_variant_builder_add (&dict_builder, "{sv}", "label", label); - g_variant_unref (label); - } - - if (type) - g_variant_builder_add (&dict_builder, "{sv}", "parameter-type", g_variant_new_string (type)); - - if (hint) - { - g_variant_builder_add (&dict_builder, "{sv}", "parameter-hint", hint); - g_variant_unref (hint); - } - - g_variant_builder_add (&actions_builder, "a{sv}", &dict_builder); - - g_object_unref (action); - g_variant_unref (entry); - g_free (prefixed_name); - } - - g_action_muxer_insert (app->message_sub_actions, id, G_ACTION_GROUP (action_group)); - actions = g_variant_builder_end (&actions_builder); - - g_object_unref (action_group); - } - - if (draws_attention) - app->draws_attention = TRUE; - - im_application_list_update_draws_attention (app->list); - - g_signal_emit (app->list, signals[MESSAGE_ADDED], 0, - app->id, app_iconstr, id, iconstr, title, - subtitle, body, actions, time, draws_attention); - - g_variant_iter_free (action_iter); - g_free (app_iconstr); - g_object_unref (action); -} - -static void -im_application_list_messages_listed (GObject *source_object, - GAsyncResult *result, - gpointer user_data) -{ - Application *app = user_data; - GVariant *messages; - GError *error = NULL; - - if (indicator_messages_application_call_list_messages_finish (app->proxy, &messages, result, &error)) - { - GVariantIter iter; - GVariant *message; - - g_variant_iter_init (&iter, messages); - while ((message = g_variant_iter_next_value (&iter))) - { - im_application_list_message_added (app, message); - g_variant_unref (message); - } - - g_variant_unref (messages); - } - else - { - g_warning ("could not fetch the list of messages: %s", error->message); - g_error_free (error); - } -} - -static void -im_application_list_unset_remote (Application *app) -{ - gboolean was_running; - - was_running = app->proxy || app->cancellable; - - if (app->cancellable) - { - g_cancellable_cancel (app->cancellable); - g_clear_object (&app->cancellable); - } - g_clear_object (&app->proxy); - - /* clear actions by creating a new action group and overriding it in - * the muxer */ - g_object_unref (app->source_actions); - g_object_unref (app->message_actions); - g_object_unref (app->message_sub_actions); - app->source_actions = g_simple_action_group_new (); - app->message_actions = g_simple_action_group_new (); - app->message_sub_actions = g_action_muxer_new (); - g_action_muxer_insert (app->muxer, "src", G_ACTION_GROUP (app->source_actions)); - g_action_muxer_insert (app->muxer, "msg", G_ACTION_GROUP (app->message_actions)); - g_action_muxer_insert (app->muxer, "msg-actions", G_ACTION_GROUP (app->message_sub_actions)); - - im_application_list_update_draws_attention (app->list); - g_action_group_change_action_state (G_ACTION_GROUP (app->muxer), "launch", g_variant_new_boolean (FALSE)); - - if (was_running) - g_signal_emit (app->list, signals[APP_STOPPED], 0, app->id); -} - -static void -im_application_list_app_vanished (GDBusConnection *connection, - const gchar *name, - gpointer user_data) -{ - Application *app = user_data; - - im_application_list_unset_remote (app); -} - -static void -im_application_list_proxy_created (GObject *source_object, - GAsyncResult *result, - gpointer user_data) -{ - Application *app = user_data; - GError *error = NULL; - - app->proxy = indicator_messages_application_proxy_new_finish (result, &error); - if (!app->proxy) - { - if (error->code != G_IO_ERROR_CANCELLED) - g_warning ("could not create application proxy: %s", error->message); - g_error_free (error); - return; - } - - indicator_messages_application_call_list_sources (app->proxy, app->cancellable, - im_application_list_sources_listed, app); - indicator_messages_application_call_list_messages (app->proxy, app->cancellable, - im_application_list_messages_listed, app); - - g_signal_connect_swapped (app->proxy, "source-added", G_CALLBACK (im_application_list_source_added), app); - g_signal_connect_swapped (app->proxy, "source-changed", G_CALLBACK (im_application_list_source_changed), app); - g_signal_connect_swapped (app->proxy, "source-removed", G_CALLBACK (im_application_list_source_removed), app); - g_signal_connect_swapped (app->proxy, "message-added", G_CALLBACK (im_application_list_message_added), app); - g_signal_connect_swapped (app->proxy, "message-removed", G_CALLBACK (im_application_list_message_removed), app); - - g_action_group_change_action_state (G_ACTION_GROUP (app->muxer), "launch", g_variant_new_boolean (TRUE)); - - g_bus_watch_name_on_connection (g_dbus_proxy_get_connection (G_DBUS_PROXY (app->proxy)), - g_dbus_proxy_get_name (G_DBUS_PROXY (app->proxy)), - G_BUS_NAME_WATCHER_FLAGS_NONE, - NULL, im_application_list_app_vanished, - app, NULL); -} - -void -im_application_list_set_remote (ImApplicationList *list, - const gchar *id, - GDBusConnection *connection, - const gchar *unique_bus_name, - const gchar *object_path) -{ - Application *app; - - g_return_if_fail (IM_IS_APPLICATION_LIST (list)); - - app = im_application_list_lookup (list, id); - if (!app) - { - g_warning ("'%s' is not a registered application", id); - return; - } - - if (app->cancellable) - { - gchar *name_owner = NULL; - - if (app->proxy) - name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (app->proxy)); - g_warning ("replacing '%s' at %s with %s", id, name_owner, unique_bus_name); - - im_application_list_unset_remote (app); - - g_free (name_owner); - } - - app->cancellable = g_cancellable_new (); - indicator_messages_application_proxy_new (connection, G_DBUS_PROXY_FLAGS_NONE, - unique_bus_name, object_path, app->cancellable, - im_application_list_proxy_created, app); -} - -GActionGroup * -im_application_list_get_action_group (ImApplicationList *list) -{ - g_return_val_if_fail (IM_IS_APPLICATION_LIST (list), NULL); - - return G_ACTION_GROUP (list->muxer); -} - -GList * -im_application_list_get_applications (ImApplicationList *list) -{ - g_return_val_if_fail (IM_IS_APPLICATION_LIST (list), NULL); - - return g_hash_table_get_keys (list->applications); -} - -GDesktopAppInfo * -im_application_list_get_application (ImApplicationList *list, - const gchar *id) -{ - Application *app; - - g_return_val_if_fail (IM_IS_APPLICATION_LIST (list), NULL); - - app = g_hash_table_lookup (list->applications, id); - return app ? app->info : NULL; -} - -static void -status_activated (GSimpleAction * action, GVariant * param, gpointer user_data) -{ - g_return_if_fail (IM_IS_APPLICATION_LIST(user_data)); - ImApplicationList * list = IM_APPLICATION_LIST(user_data); - const gchar * status = g_variant_get_string(param, NULL); - - g_simple_action_set_state(action, param); - - GList * appshash = g_hash_table_get_keys(list->app_status); - GList * appsfree = g_list_copy_deep(appshash, (GCopyFunc)g_strdup, NULL); - GList * app; - - for (app = appsfree; app != NULL; app = g_list_next(app)) { - g_hash_table_insert(list->app_status, app->data, g_strdup(status)); - } - - g_list_free(appshash); - g_list_free(appsfree); - - g_signal_emit (list, signals[STATUS_SET], 0, status); - - return; -} - -#define STATUS_ID_OFFLINE (G_N_ELEMENTS(status_ids) - 1) -static const gchar *status_ids[] = { "available", "away", "busy", "invisible", "offline" }; - -static guint -status2val (const gchar * string) -{ - if (string == NULL) return STATUS_ID_OFFLINE; - - guint i; - for (i = 0; i < G_N_ELEMENTS(status_ids); i++) { - if (g_strcmp0(status_ids[i], string) == 0) { - break; - } - } - - if (i > STATUS_ID_OFFLINE) - i = STATUS_ID_OFFLINE; - - return i; -} - -void -im_application_list_set_status (ImApplicationList * list, const gchar * id, const gchar *status) -{ - g_return_if_fail (IM_IS_APPLICATION_LIST (list)); - - g_hash_table_insert(list->app_status, im_application_list_canonical_id(id), g_strdup(status)); - - guint final_status = STATUS_ID_OFFLINE; - - GList * statuses = g_hash_table_get_values(list->app_status); - GList * statusentry; - - for (statusentry = statuses; statusentry != NULL; statusentry = g_list_next(statusentry)) { - guint statusval = status2val((gchar *)statusentry->data); - final_status = MIN(final_status, statusval); - } - - g_list_free(statuses); - - g_simple_action_set_state(list->statusaction, g_variant_new_string(status_ids[final_status])); - - return; -} - diff --git a/src/im-application-list.h b/src/im-application-list.h deleted file mode 100644 index f1de220..0000000 --- a/src/im-application-list.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2012 Canonical Ltd. - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 3, as published - * by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranties of - * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . - * - * Authors: - * Lars Uebernickel - */ - -#ifndef __IM_APPLICATION_LIST_H__ -#define __IM_APPLICATION_LIST_H__ - -#include -#include - -#define IM_TYPE_APPLICATION_LIST (im_application_list_get_type ()) -#define IM_APPLICATION_LIST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IM_TYPE_APPLICATION_LIST, ImApplicationList)) -#define IM_APPLICATION_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), IM_TYPE_APPLICATION_LIST, ImApplicationListClass)) -#define IM_IS_APPLICATION_LIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IM_TYPE_APPLICATION_LIST)) -#define IM_IS_APPLICATION_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), IM_TYPE_APPLICATION_LIST)) -#define IM_APPLICATION_LIST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), IM_TYPE_APPLICATION_LIST, ImApplicationListClass)) - -typedef struct _ImApplicationList ImApplicationList; - -GType im_application_list_get_type (void); - -ImApplicationList * im_application_list_new (void); - -void im_application_list_add (ImApplicationList *list, - const gchar *desktop_id); - -void im_application_list_remove (ImApplicationList *list, - const gchar *id); - -void im_application_list_set_remote (ImApplicationList *list, - const gchar *id, - GDBusConnection *connection, - const gchar *unique_bus_name, - const gchar *object_path); - -GActionGroup * im_application_list_get_action_group (ImApplicationList *list); - -GList * im_application_list_get_applications (ImApplicationList *list); - -GDesktopAppInfo * im_application_list_get_application (ImApplicationList *list, - const gchar *id); - -void im_application_list_set_status (ImApplicationList *list, - const gchar *id, - const gchar *status); - -#endif diff --git a/src/im-desktop-menu.c b/src/im-desktop-menu.c deleted file mode 100644 index 1040d62..0000000 --- a/src/im-desktop-menu.c +++ /dev/null @@ -1,294 +0,0 @@ -/* - * Copyright 2012 Canonical Ltd. - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 3, as published - * by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranties of - * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . - * - * Authors: - * Lars Uebernickel - */ - -#include "im-desktop-menu.h" -#include - -typedef ImMenuClass ImDesktopMenuClass; - -struct _ImDesktopMenu -{ - ImMenu parent; - - GHashTable *source_sections; -}; - -G_DEFINE_TYPE (ImDesktopMenu, im_desktop_menu, IM_TYPE_MENU); - -static void -im_desktop_menu_app_added (ImApplicationList *applist, - const gchar *app_id, - GDesktopAppInfo *app_info, - gpointer user_data) -{ - ImDesktopMenu *menu = user_data; - GMenu *section; - GMenu *app_section; - GMenu *source_section; - gchar *namespace; - - app_section = g_menu_new (); - - /* application launcher */ - { - GMenuItem *item; - GVariant *icon; - - item = g_menu_item_new (g_app_info_get_name (G_APP_INFO (app_info)), "launch"); - g_menu_item_set_attribute (item, "x-canonical-type", "s", "com.canonical.application"); - - icon = g_icon_serialize (g_app_info_get_icon (G_APP_INFO (app_info))); - if (icon) - { - g_menu_item_set_attribute_value (item, "icon", icon); - g_variant_unref (icon); - } - - g_menu_append_item (app_section, item); - - g_object_unref (item); - } - - /* application actions */ -#if 0 - { - const gchar *const *actions; - - for (actions = g_desktop_app_info_list_actions (app_info); *actions; actions++) - { - gchar *label; - - label = g_desktop_app_info_get_action_name (app_info, *actions); - g_menu_append (app_section, label, *actions); - - g_free (label); - } - } -#endif - - source_section = g_menu_new (); - - section = g_menu_new (); - g_menu_append_section (section, NULL, G_MENU_MODEL (app_section)); - g_menu_append_section (section, NULL, G_MENU_MODEL (source_section)); - - namespace = g_strconcat ("indicator.", app_id, NULL); - im_menu_insert_section (IM_MENU (menu), -1, namespace, G_MENU_MODEL (section)); - g_hash_table_insert (menu->source_sections, g_strdup (app_id), source_section); - - g_free (namespace); - g_object_unref (section); - g_object_unref (app_section); -} - -static void -im_desktop_menu_source_added (ImApplicationList *applist, - const gchar *app_id, - const gchar *source_id, - const gchar *label, - const gchar *icon, - gpointer user_data) -{ - ImDesktopMenu *menu = user_data; - GMenu *source_section; - GMenuItem *item; - gchar *action; - - source_section = g_hash_table_lookup (menu->source_sections, app_id); - g_return_if_fail (source_section != NULL); - - action = g_strconcat ("src.", source_id, NULL); - item = g_menu_item_new (label, action); - g_menu_item_set_attribute (item, "x-canonical-type", "s", "com.canonical.indicator.messages.source"); - if (icon && *icon) - g_menu_item_set_attribute (item, "icon", "s", icon); - - g_menu_append_item (source_section, item); - - g_free (action); - g_object_unref (item); -} - -static void -im_desktop_menu_source_removed (ImApplicationList *applist, - const gchar *app_id, - const gchar *source_id, - gpointer user_data) -{ - ImDesktopMenu *menu = user_data; - GMenu *source_section; - gint n_items; - gchar *action; - gint i; - - source_section = g_hash_table_lookup (menu->source_sections, app_id); - g_return_if_fail (source_section != NULL); - - n_items = g_menu_model_get_n_items (G_MENU_MODEL (source_section)); - action = g_strconcat ("src.", source_id, NULL); - - for (i = 0; i < n_items; i++) - { - gchar *item_action; - - if (g_menu_model_get_item_attribute (G_MENU_MODEL (source_section), i, "action", "s", &item_action)) - { - if (g_str_equal (action, item_action)) - g_menu_remove (source_section, i); - - g_free (item_action); - } - } - - g_free (action); -} - -static void -im_desktop_menu_remove_all (ImApplicationList *applist, - gpointer user_data) -{ - ImDesktopMenu *menu = user_data; - GHashTableIter it; - GMenu *section; - - g_hash_table_iter_init (&it, menu->source_sections); - while (g_hash_table_iter_next (&it, NULL, (gpointer *) §ion)) - { - while (g_menu_model_get_n_items (G_MENU_MODEL (section)) > 0) - g_menu_remove (section, 0); - } -} - -static GMenu * -create_status_section (void) -{ - GMenu *menu; - GMenuItem *item; - struct status_item { - gchar *label; - gchar *action; - gchar *icon_name; - } status_items[] = { - { _("Available"), "indicator.status::available", "user-available" }, - { _("Away"), "indicator.status::away", "user-away" }, - { _("Busy"), "indicator.status::busy", "user-busy" }, - { _("Invisible"), "indicator.status::invisible", "user-invisible" }, - { _("Offline"), "indicator.status::offline", "user-offline" } - }; - int i; - - menu = g_menu_new (); - - item = g_menu_item_new (NULL, NULL); - - for (i = 0; i < G_N_ELEMENTS (status_items); i++) { - g_menu_item_set_label (item, status_items[i].label); - g_menu_item_set_detailed_action (item, status_items[i].action); - g_menu_item_set_attribute (item, "icon", "s", status_items[i].icon_name); - g_menu_append_item (menu, item); - } - - g_object_unref (item); - return menu; -} - -static void -im_desktop_menu_constructed (GObject *object) -{ - ImDesktopMenu *menu = IM_DESKTOP_MENU (object); - ImApplicationList *applist; - - { - GMenu *status_section; - - status_section = create_status_section(); - im_menu_append_section (IM_MENU (menu), G_MENU_MODEL (status_section)); - - g_object_unref (status_section); - } - - { - GMenu *clear_section; - - clear_section = g_menu_new (); - g_menu_append (clear_section, "Clear", "indicator.remove-all"); - im_menu_append_section (IM_MENU (menu), G_MENU_MODEL (clear_section)); - - g_object_unref (clear_section); - } - - applist = im_menu_get_application_list (IM_MENU (menu)); - - { - GList *apps; - GList *it; - - apps = im_application_list_get_applications (applist); - for (it = apps; it != NULL; it = it->next) - { - const gchar *id = it->data; - im_desktop_menu_app_added (applist, id, im_application_list_get_application (applist, id), menu); - } - - g_list_free (apps); - } - - - g_signal_connect (applist, "app-added", G_CALLBACK (im_desktop_menu_app_added), menu); - g_signal_connect (applist, "source-added", G_CALLBACK (im_desktop_menu_source_added), menu); - g_signal_connect (applist, "source-removed", G_CALLBACK (im_desktop_menu_source_removed), menu); - g_signal_connect (applist, "remove-all", G_CALLBACK (im_desktop_menu_remove_all), menu); - - G_OBJECT_CLASS (im_desktop_menu_parent_class)->constructed (object); -} - -static void -im_desktop_menu_finalize (GObject *object) -{ - ImDesktopMenu *menu = IM_DESKTOP_MENU (object); - - g_hash_table_unref (menu->source_sections); - - G_OBJECT_CLASS (im_desktop_menu_parent_class)->finalize (object); -} - -static void -im_desktop_menu_class_init (ImDesktopMenuClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->constructed = im_desktop_menu_constructed; - object_class->finalize = im_desktop_menu_finalize; -} - -static void -im_desktop_menu_init (ImDesktopMenu *menu) -{ - menu->source_sections = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); -} - -ImDesktopMenu * -im_desktop_menu_new (ImApplicationList *applist) -{ - g_return_val_if_fail (IM_IS_APPLICATION_LIST (applist), NULL); - - return g_object_new (IM_TYPE_DESKTOP_MENU, - "application-list", applist, - NULL); -} diff --git a/src/im-desktop-menu.h b/src/im-desktop-menu.h deleted file mode 100644 index 9469ea6..0000000 --- a/src/im-desktop-menu.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2013 Canonical Ltd. - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 3, as published - * by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranties of - * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . - * - * Authors: - * Lars Uebernickel - */ - -#ifndef __IM_DESKTOP_MENU_H__ -#define __IM_DESKTOP_MENU_H__ - -#include "im-menu.h" - -#define IM_TYPE_DESKTOP_MENU (im_desktop_menu_get_type ()) -#define IM_DESKTOP_MENU(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IM_TYPE_DESKTOP_MENU, ImDesktopMenu)) -#define IM_IS_DESKTOP_MENU(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IM_TYPE_DESKTOP_MENU)) - -typedef struct _ImDesktopMenu ImDesktopMenu; - -GType im_desktop_menu_get_type (void); - -ImDesktopMenu * im_desktop_menu_new (ImApplicationList *applist); - -#endif diff --git a/src/im-menu.c b/src/im-menu.c deleted file mode 100644 index ac23a29..0000000 --- a/src/im-menu.c +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright 2013 Canonical Ltd. - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 3, as published - * by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranties of - * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . - * - * Authors: - * Lars Uebernickel - */ - -#include "im-menu.h" - -struct _ImMenuPrivate -{ - GMenu *toplevel_menu; - GMenu *menu; - ImApplicationList *applist; -}; - -G_DEFINE_TYPE_WITH_PRIVATE (ImMenu, im_menu, G_TYPE_OBJECT) - -enum -{ - PROP_0, - PROP_APPLICATION_LIST, - NUM_PROPERTIES -}; - -static void -im_menu_finalize (GObject *object) -{ - ImMenuPrivate *priv = im_menu_get_instance_private (IM_MENU (object)); - - g_object_unref (priv->toplevel_menu); - g_object_unref (priv->menu); - g_object_unref (priv->applist); - - G_OBJECT_CLASS (im_menu_parent_class)->finalize (object); -} - -static void -im_menu_get_property (GObject *object, - guint property_id, - GValue *value, - GParamSpec *pspec) -{ - ImMenuPrivate *priv = im_menu_get_instance_private (IM_MENU (object)); - - switch (property_id) - { - case PROP_APPLICATION_LIST: - g_value_set_object (value, priv->applist); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); - } -} - -static void -im_menu_set_property (GObject *object, - guint property_id, - const GValue *value, - GParamSpec *pspec) -{ - ImMenuPrivate *priv = im_menu_get_instance_private (IM_MENU (object)); - - switch (property_id) - { - case PROP_APPLICATION_LIST: /* construct only */ - priv->applist = g_value_dup_object (value); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); - } -} - -static void -im_menu_class_init (ImMenuClass *class) -{ - GObjectClass *object_class = G_OBJECT_CLASS (class); - - object_class->finalize = im_menu_finalize; - object_class->get_property = im_menu_get_property; - object_class->set_property = im_menu_set_property; - - g_object_class_install_property (object_class, PROP_APPLICATION_LIST, - g_param_spec_object ("application-list", "", "", - IM_TYPE_APPLICATION_LIST, - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_READWRITE | - G_PARAM_STATIC_STRINGS)); -} - -static void -im_menu_init (ImMenu *menu) -{ - ImMenuPrivate *priv = im_menu_get_instance_private (menu); - GMenuItem *root; - - priv->toplevel_menu = g_menu_new (); - priv->menu = g_menu_new (); - - root = g_menu_item_new (NULL, "indicator.messages"); - g_menu_item_set_attribute (root, "x-canonical-type", "s", "com.canonical.indicator.root"); - g_menu_item_set_attribute (root, "action-namespace", "s", "indicator"); - g_menu_item_set_submenu (root, G_MENU_MODEL (priv->menu)); - g_menu_append_item (priv->toplevel_menu, root); - - g_object_unref (root); -} - -ImApplicationList * -im_menu_get_application_list (ImMenu *menu) -{ - ImMenuPrivate *priv; - - g_return_val_if_fail (IM_IS_MENU (menu), FALSE); - - priv = im_menu_get_instance_private (menu); - return priv->applist; -} - -gboolean -im_menu_export (ImMenu *menu, - GDBusConnection *connection, - const gchar *object_path, - GError **error) -{ - ImMenuPrivate *priv; - - g_return_val_if_fail (IM_IS_MENU (menu), FALSE); - - priv = im_menu_get_instance_private (menu); - return g_dbus_connection_export_menu_model (connection, - object_path, - G_MENU_MODEL (priv->toplevel_menu), - error) > 0; -} - -void -im_menu_append_section (ImMenu *menu, - GMenuModel *section) -{ - ImMenuPrivate *priv; - - g_return_if_fail (IM_IS_MENU (menu)); - g_return_if_fail (G_IS_MENU_MODEL (section)); - - priv = im_menu_get_instance_private (menu); - - g_menu_append_section (priv->menu, NULL, section); -} - -void -im_menu_insert_section (ImMenu *menu, - gint position, - const gchar *namespace, - GMenuModel *section) -{ - ImMenuPrivate *priv; - GMenuItem *item; - - g_return_if_fail (IM_IS_MENU (menu)); - g_return_if_fail (G_IS_MENU_MODEL (section)); - - priv = im_menu_get_instance_private (menu); - - /* count from the back if position is < 0 */ - if (position < 0) - position = g_menu_model_get_n_items (G_MENU_MODEL (priv->menu)) + position; - - item = g_menu_item_new_section (NULL, section); - - if (namespace) - g_menu_item_set_attribute (item, "action-namespace", "s", namespace); - - g_menu_insert_item (priv->menu, position, item); - - g_object_unref (item); -} diff --git a/src/im-menu.h b/src/im-menu.h deleted file mode 100644 index d3775ad..0000000 --- a/src/im-menu.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2013 Canonical Ltd. - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 3, as published - * by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranties of - * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . - * - * Authors: - * Lars Uebernickel - */ - -#ifndef __IM_MENU_H__ -#define __IM_MENU_H__ - -#include -#include "im-application-list.h" - -#define IM_TYPE_MENU (im_menu_get_type ()) -#define IM_MENU(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IM_TYPE_MENU, ImMenu)) -#define IM_MENU_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), IM_TYPE_MENU, ImMenuClass)) -#define IM_IS_MENU(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IM_TYPE_MENU)) -#define IM_IS_MENU_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), IM_TYPE_MENU)) -#define IM_MENU_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), IM_TYPE_MENU, ImMenuClass)) - -typedef struct _ImMenuClass ImMenuClass; -typedef struct _ImMenu ImMenu; -typedef struct _ImMenuPrivate ImMenuPrivate; - -struct _ImMenuClass -{ - GObjectClass parent_class; -}; - -struct _ImMenu -{ - GObject parent_instance; -}; - -GType im_menu_get_type (void) G_GNUC_CONST; - -ImApplicationList * im_menu_get_application_list (ImMenu *menu); - -gboolean im_menu_export (ImMenu *menu, - GDBusConnection *connection, - const gchar *object_path, - GError **error); - -void im_menu_append_section (ImMenu *menu, - GMenuModel *section); - -void im_menu_insert_section (ImMenu *menu, - gint position, - const gchar *namespace, - GMenuModel *section); - -#endif diff --git a/src/im-phone-menu.c b/src/im-phone-menu.c deleted file mode 100644 index 7381097..0000000 --- a/src/im-phone-menu.c +++ /dev/null @@ -1,317 +0,0 @@ -/* - * Copyright 2012 Canonical Ltd. - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 3, as published - * by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranties of - * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . - * - * Authors: - * Lars Uebernickel - */ - -#include "im-phone-menu.h" - -#include - -typedef ImMenuClass ImPhoneMenuClass; - -struct _ImPhoneMenu -{ - ImMenu parent; - - GMenu *message_section; - GMenu *source_section; -}; - -G_DEFINE_TYPE (ImPhoneMenu, im_phone_menu, IM_TYPE_MENU); - -typedef void (*ImMenuForeachFunc) (GMenuModel *menu, gint pos); - -static void -im_phone_menu_foreach_item_with_action (GMenuModel *menu, - const gchar *action, - ImMenuForeachFunc func) -{ - gint n_items; - gint i; - - n_items = g_menu_model_get_n_items (menu); - for (i = 0; i < n_items; i++) - { - gchar *item_action; - - g_menu_model_get_item_attribute (menu, i, G_MENU_ATTRIBUTE_ACTION, "s", &item_action); - - if (g_str_equal (action, item_action)) - func (menu, i); - - g_free (item_action); - } -} - -static void -im_phone_menu_constructed (GObject *object) -{ - ImPhoneMenu *menu = IM_PHONE_MENU (object); - ImApplicationList *applist; - - im_menu_append_section (IM_MENU (menu), G_MENU_MODEL (menu->message_section)); - im_menu_append_section (IM_MENU (menu), G_MENU_MODEL (menu->source_section)); - - { - GMenu *clear_section; - GMenuItem *item; - - clear_section = g_menu_new (); - - item = g_menu_item_new ("Clear All", "remove-all"); - g_menu_item_set_attribute (item, "x-canonical-type", "s", "com.canonical.indicator.button"); - g_menu_append_item (clear_section, item); - - im_menu_append_section (IM_MENU (menu), G_MENU_MODEL (clear_section)); - - g_object_unref (item); - g_object_unref (clear_section); - } - - applist = im_menu_get_application_list (IM_MENU (menu)); - - g_signal_connect_swapped (applist, "message-added", G_CALLBACK (im_phone_menu_add_message), menu); - g_signal_connect_swapped (applist, "message-removed", G_CALLBACK (im_phone_menu_remove_message), menu); - g_signal_connect_swapped (applist, "app-stopped", G_CALLBACK (im_phone_menu_remove_application), menu); - g_signal_connect_swapped (applist, "remove-all", G_CALLBACK (im_phone_menu_remove_all), menu); - - G_OBJECT_CLASS (im_phone_menu_parent_class)->constructed (object); -} - -static void -im_phone_menu_dispose (GObject *object) -{ - ImPhoneMenu *menu = IM_PHONE_MENU (object); - - g_clear_object (&menu->message_section); - g_clear_object (&menu->source_section); - - G_OBJECT_CLASS (im_phone_menu_parent_class)->dispose (object); -} - -static void -im_phone_menu_finalize (GObject *object) -{ - G_OBJECT_CLASS (im_phone_menu_parent_class)->finalize (object); -} - -static void -im_phone_menu_class_init (ImPhoneMenuClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->constructed = im_phone_menu_constructed; - object_class->dispose = im_phone_menu_dispose; - object_class->finalize = im_phone_menu_finalize; -} - -static void -im_phone_menu_init (ImPhoneMenu *menu) -{ - menu->message_section = g_menu_new (); - menu->source_section = g_menu_new (); -} - -ImPhoneMenu * -im_phone_menu_new (ImApplicationList *applist) -{ - g_return_val_if_fail (IM_IS_APPLICATION_LIST (applist), NULL); - - return g_object_new (IM_TYPE_PHONE_MENU, - "application-list", applist, - NULL); -} - -static gint64 -im_phone_menu_get_message_time (GMenuModel *model, - gint i) -{ - gint64 time; - - g_menu_model_get_item_attribute (model, i, "x-canonical-time", "x", &time); - - return time; -} - -void -im_phone_menu_add_message (ImPhoneMenu *menu, - const gchar *app_id, - const gchar *app_icon, - const gchar *id, - const gchar *iconstr, - const gchar *title, - const gchar *subtitle, - const gchar *body, - GVariant *actions, - gint64 time) -{ - GMenuItem *item; - gchar *action_name; - gint n_messages; - gint pos; - - g_return_if_fail (IM_IS_PHONE_MENU (menu)); - g_return_if_fail (app_id); - - action_name = g_strconcat (app_id, ".msg.", id, NULL); - - item = g_menu_item_new (title, action_name); - - g_menu_item_set_attribute (item, "x-canonical-type", "s", "com.canonical.indicator.messages.messageitem"); - g_menu_item_set_attribute (item, "x-canonical-message-id", "s", id); - g_menu_item_set_attribute (item, "x-canonical-subtitle", "s", subtitle); - g_menu_item_set_attribute (item, "x-canonical-text", "s", body); - g_menu_item_set_attribute (item, "x-canonical-time", "x", time); - - if (iconstr) - g_menu_item_set_attribute (item, "icon", "s", iconstr); - - if (app_icon) - g_menu_item_set_attribute (item, "x-canonical-app-icon", "s", app_icon); - - if (actions) - g_menu_item_set_attribute (item, "x-canonical-message-actions", "v", actions); - - n_messages = g_menu_model_get_n_items (G_MENU_MODEL (menu->message_section)); - pos = 0; - while (pos < n_messages && - time < im_phone_menu_get_message_time (G_MENU_MODEL (menu->message_section), pos)) - pos++; - - g_menu_insert_item (menu->message_section, pos, item); - - g_free (action_name); - g_object_unref (item); -} - -void -im_phone_menu_remove_message (ImPhoneMenu *menu, - const gchar *app_id, - const gchar *id) -{ - gchar *action_name; - - g_return_if_fail (IM_IS_PHONE_MENU (menu)); - g_return_if_fail (app_id != NULL); - - action_name = g_strconcat (app_id, ".msg.", id, NULL); - im_phone_menu_foreach_item_with_action (G_MENU_MODEL (menu->message_section), - action_name, - (ImMenuForeachFunc) g_menu_remove); - - g_free (action_name); -} - -void -im_phone_menu_add_source (ImPhoneMenu *menu, - const gchar *app_id, - const gchar *id, - const gchar *label, - const gchar *iconstr) -{ - GMenuItem *item; - gchar *action_name; - - g_return_if_fail (IM_IS_PHONE_MENU (menu)); - g_return_if_fail (app_id != NULL); - - action_name = g_strconcat (app_id, ".src.", id, NULL); - - item = g_menu_item_new (label, action_name); - g_menu_item_set_attribute (item, "x-canonical-type", "s", "com.canonical.indicator.messages.sourceitem"); - - if (iconstr) - g_menu_item_set_attribute (item, "x-canonical-icon", "s", iconstr); - - g_menu_prepend_item (menu->source_section, item); - - g_free (action_name); - g_object_unref (item); -} - -void -im_phone_menu_remove_source (ImPhoneMenu *menu, - const gchar *app_id, - const gchar *id) -{ - gchar *action_name; - - g_return_if_fail (IM_IS_PHONE_MENU (menu)); - g_return_if_fail (app_id != NULL); - - action_name = g_strconcat (app_id, ".src.", id, NULL); - im_phone_menu_foreach_item_with_action (G_MENU_MODEL (menu->source_section), - action_name, - (ImMenuForeachFunc) g_menu_remove); - - g_free (action_name); -} - -static void -im_phone_menu_remove_all_for_app (GMenu *menu, - const gchar *app_id) -{ - gchar *prefix; - gint n_items; - gint i = 0; - - prefix = g_strconcat (app_id, ".", NULL); - - n_items = g_menu_model_get_n_items (G_MENU_MODEL (menu)); - while (i < n_items) - { - gchar *action; - - g_menu_model_get_item_attribute (G_MENU_MODEL (menu), i, G_MENU_ATTRIBUTE_ACTION, "s", &action); - if (g_str_has_prefix (action, prefix)) - { - g_menu_remove (menu, i); - n_items--; - } - else - { - i++; - } - - g_free (action); - } - - g_free (prefix); -} - -void -im_phone_menu_remove_application (ImPhoneMenu *menu, - const gchar *app_id) -{ - g_return_if_fail (IM_IS_PHONE_MENU (menu)); - g_return_if_fail (app_id != NULL); - - im_phone_menu_remove_all_for_app (menu->source_section, app_id); - im_phone_menu_remove_all_for_app (menu->message_section, app_id); -} - -void -im_phone_menu_remove_all (ImPhoneMenu *menu) -{ - g_return_if_fail (IM_IS_PHONE_MENU (menu)); - - while (g_menu_model_get_n_items (G_MENU_MODEL (menu->message_section))) - g_menu_remove (menu->message_section, 0); - - while (g_menu_model_get_n_items (G_MENU_MODEL (menu->source_section))) - g_menu_remove (menu->source_section, 0); -} diff --git a/src/im-phone-menu.h b/src/im-phone-menu.h deleted file mode 100644 index 9742f61..0000000 --- a/src/im-phone-menu.h +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2012 Canonical Ltd. - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 3, as published - * by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranties of - * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . - * - * Authors: - * Lars Uebernickel - */ - -#ifndef __IM_PHONE_MENU_H__ -#define __IM_PHONE_MENU_H__ - -#include "im-menu.h" - -#define IM_TYPE_PHONE_MENU (im_phone_menu_get_type ()) -#define IM_PHONE_MENU(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IM_TYPE_PHONE_MENU, ImPhoneMenu)) -#define IM_PHONE_MENU_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), IM_TYPE_PHONE_MENU, ImPhoneMenuClass)) -#define IM_IS_PHONE_MENU(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IM_TYPE_PHONE_MENU)) -#define IM_IS_PHONE_MENU_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), IM_TYPE_PHONE_MENU)) -#define IM_PHONE_MENU_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), IM_TYPE_PHONE_MENU, ImPhoneMenuClass)) - -typedef struct _ImPhoneMenu ImPhoneMenu; - -GType im_phone_menu_get_type (void); - -ImPhoneMenu * im_phone_menu_new (ImApplicationList *applist); - -void im_phone_menu_add_message (ImPhoneMenu *menu, - const gchar *app_id, - const gchar *app_icon, - const gchar *id, - const gchar *iconstr, - const gchar *title, - const gchar *subtitle, - const gchar *body, - GVariant *actions, - gint64 time); - -void im_phone_menu_remove_message (ImPhoneMenu *menu, - const gchar *app_id, - const gchar *id); - -void im_phone_menu_add_source (ImPhoneMenu *menu, - const gchar *app_id, - const gchar *id, - const gchar *label, - const gchar *iconstr); - -void im_phone_menu_remove_source (ImPhoneMenu *menu, - const gchar *app_id, - const gchar *id); - -void im_phone_menu_remove_application (ImPhoneMenu *menu, - const gchar *app_id); - -void im_phone_menu_remove_all (ImPhoneMenu *menu); - -#endif diff --git a/src/im-source-menu-item.c b/src/im-source-menu-item.c new file mode 100644 index 0000000..2577c30 --- /dev/null +++ b/src/im-source-menu-item.c @@ -0,0 +1,407 @@ +/* + * Copyright 2012 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . + * + * Authors: + * Lars Uebernickel + */ + +#include "im-source-menu-item.h" + +#include +#include "ido-detail-label.h" + +struct _ImSourceMenuItemPrivate +{ + GActionGroup *action_group; + gchar *action; + + GtkWidget *icon; + GtkWidget *label; + GtkWidget *detail; + + gint64 time; + guint timer_id; +}; + +enum +{ + PROP_0, + PROP_MENU_ITEM, + PROP_ACTION_GROUP, + NUM_PROPERTIES +}; + +static GParamSpec *properties[NUM_PROPERTIES]; + +G_DEFINE_TYPE (ImSourceMenuItem, im_source_menu_item, GTK_TYPE_MENU_ITEM); + +static void +im_source_menu_item_constructed (GObject *object) +{ + ImSourceMenuItemPrivate *priv = IM_SOURCE_MENU_ITEM (object)->priv; + GtkWidget *grid; + gint icon_width; + + gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &icon_width, NULL); + + priv->icon = g_object_ref (gtk_image_new ()); + gtk_widget_set_margin_left (priv->icon, icon_width + 6); + + priv->label = g_object_ref (gtk_label_new ("")); + gtk_label_set_max_width_chars (GTK_LABEL (priv->label), 40); + gtk_label_set_ellipsize (GTK_LABEL (priv->label), PANGO_ELLIPSIZE_END); + gtk_misc_set_alignment (GTK_MISC (priv->label), 0.0, 0.5); + + priv->detail = g_object_ref (ido_detail_label_new ("")); + gtk_widget_set_halign (priv->detail, GTK_ALIGN_END); + gtk_widget_set_hexpand (priv->detail, TRUE); + gtk_style_context_add_class (gtk_widget_get_style_context (priv->detail), "accelerator"); + + grid = gtk_grid_new (); + gtk_grid_attach (GTK_GRID (grid), priv->icon, 0, 0, 1, 1); + gtk_grid_attach (GTK_GRID (grid), priv->label, 1, 0, 1, 1); + gtk_grid_attach (GTK_GRID (grid), priv->detail, 2, 0, 1, 1); + + gtk_container_add (GTK_CONTAINER (object), grid); + gtk_widget_show_all (grid); + + G_OBJECT_CLASS (im_source_menu_item_parent_class)->constructed (object); +} + +static gchar * +im_source_menu_item_time_span_string (gint64 timestamp) +{ + gchar *str; + gint64 span; + gint hours; + gint minutes; + + span = MAX (g_get_real_time () - timestamp, 0) / G_USEC_PER_SEC; + hours = span / 3600; + minutes = (span / 60) % 60; + + if (hours == 0) + { + /* TRANSLATORS: number of minutes that have passed */ + str = g_strdup_printf (ngettext ("%d min", "%d min", minutes), minutes); + } + else + { + /* TRANSLATORS: number of hours that have passed */ + str = g_strdup_printf (ngettext ("%d h", "%d h", hours), hours); + } + + return str; +} + +static void +im_source_menu_item_set_detail_time (ImSourceMenuItem *self, + gint64 time) +{ + ImSourceMenuItemPrivate *priv = self->priv; + gchar *str; + + priv->time = time; + + str = im_source_menu_item_time_span_string (priv->time); + ido_detail_label_set_text (IDO_DETAIL_LABEL (priv->detail), str); + + g_free (str); +} + +static gboolean +im_source_menu_item_update_time (gpointer data) +{ + ImSourceMenuItem *self = data; + + im_source_menu_item_set_detail_time (self, self->priv->time); + + return TRUE; +} + +static gboolean +im_source_menu_item_set_state (ImSourceMenuItem *self, + GVariant *state) +{ + ImSourceMenuItemPrivate *priv = self->priv; + guint32 count; + gint64 time; + const gchar *str; + + if (priv->timer_id != 0) + { + g_source_remove (priv->timer_id); + priv->timer_id = 0; + } + + g_return_val_if_fail (g_variant_is_of_type (state, G_VARIANT_TYPE ("(uxsb)")), FALSE); + + g_variant_get (state, "(ux&sb)", &count, &time, &str, NULL); + + if (count != 0) + ido_detail_label_set_count (IDO_DETAIL_LABEL (priv->detail), count); + else if (time != 0) + { + im_source_menu_item_set_detail_time (self, time); + priv->timer_id = g_timeout_add_seconds (59, im_source_menu_item_update_time, self); + } + else if (str != NULL && *str) + ido_detail_label_set_text (IDO_DETAIL_LABEL (priv->detail), str); + + return TRUE; +} + +static void +im_source_menu_item_set_action_name (ImSourceMenuItem *self, + const gchar *action_name) +{ + ImSourceMenuItemPrivate *priv = self->priv; + gboolean enabled = FALSE; + GVariant *state; + + if (priv->action != NULL) + g_free (priv->action); + + priv->action = g_strdup (action_name); + + if (priv->action_group != NULL && priv->action != NULL && + g_action_group_query_action (priv->action_group, priv->action, + &enabled, NULL, NULL, NULL, &state)) + { + if (!state || !im_source_menu_item_set_state (self, state)) + enabled = FALSE; + + if (state) + g_variant_unref (state); + } + + gtk_widget_set_sensitive (GTK_WIDGET (self), enabled); +} + +static void +im_source_menu_item_action_added (GActionGroup *action_group, + gchar *action_name, + gpointer user_data) +{ + ImSourceMenuItem *self = user_data; + + if (g_strcmp0 (self->priv->action, action_name) == 0) + im_source_menu_item_set_action_name (self, action_name); +} + +static void +im_source_menu_item_action_removed (GActionGroup *action_group, + gchar *action_name, + gpointer user_data) +{ + ImSourceMenuItem *self = user_data; + + if (g_strcmp0 (self->priv->action, action_name) == 0) + { + gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE); + } +} + +static void +im_source_menu_item_action_enabled_changed (GActionGroup *action_group, + gchar *action_name, + gboolean enabled, + gpointer user_data) +{ + ImSourceMenuItem *self = user_data; + + if (g_strcmp0 (self->priv->action, action_name) == 0) + gtk_widget_set_sensitive (GTK_WIDGET (self), enabled); +} + +static void +im_source_menu_item_action_state_changed (GActionGroup *action_group, + gchar *action_name, + GVariant *value, + gpointer user_data) +{ + ImSourceMenuItem *self = user_data; + + if (g_strcmp0 (self->priv->action, action_name) == 0) + im_source_menu_item_set_state (self, value); +} + +static void +im_source_menu_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + ImSourceMenuItem *self = IM_SOURCE_MENU_ITEM (object); + + switch (property_id) + { + case PROP_MENU_ITEM: + im_source_menu_item_set_menu_item (self, G_MENU_ITEM (g_value_get_object (value))); + break; + + case PROP_ACTION_GROUP: + im_source_menu_item_set_action_group (self, G_ACTION_GROUP (g_value_get_object (value))); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +im_source_menu_item_dispose (GObject *object) +{ + ImSourceMenuItem *self = IM_SOURCE_MENU_ITEM (object); + + if (self->priv->timer_id != 0) + { + g_source_remove (self->priv->timer_id); + self->priv->timer_id = 0; + } + + if (self->priv->action_group) + im_source_menu_item_set_action_group (self, NULL); + + g_clear_object (&self->priv->icon); + g_clear_object (&self->priv->label); + g_clear_object (&self->priv->detail); + + G_OBJECT_CLASS (im_source_menu_item_parent_class)->dispose (object); +} + +static void +im_source_menu_item_finalize (GObject *object) +{ + ImSourceMenuItemPrivate *priv = IM_SOURCE_MENU_ITEM (object)->priv; + + g_free (priv->action); + + G_OBJECT_CLASS (im_source_menu_item_parent_class)->finalize (object); +} + +static void +im_source_menu_item_activate (GtkMenuItem *item) +{ + ImSourceMenuItemPrivate *priv = IM_SOURCE_MENU_ITEM (item)->priv; + + if (priv->action && priv->action_group) + g_action_group_activate_action (priv->action_group, priv->action, NULL); +} + +static void +im_source_menu_item_class_init (ImSourceMenuItemClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkMenuItemClass *menu_item_class = GTK_MENU_ITEM_CLASS (klass); + + g_type_class_add_private (klass, sizeof (ImSourceMenuItemPrivate)); + + object_class->constructed = im_source_menu_item_constructed; + object_class->set_property = im_source_menu_set_property; + object_class->dispose = im_source_menu_item_dispose; + object_class->finalize = im_source_menu_item_finalize; + + menu_item_class->activate = im_source_menu_item_activate; + + properties[PROP_MENU_ITEM] = g_param_spec_object ("menu-item", + "Menu item", + "The model GMenuItem for this menu item", + G_TYPE_MENU_ITEM, + G_PARAM_WRITABLE | + G_PARAM_STATIC_STRINGS); + + properties[PROP_ACTION_GROUP] = g_param_spec_object ("action-group", + "Action group", + "The action group associated with this menu item", + G_TYPE_ACTION_GROUP, + G_PARAM_WRITABLE | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, NUM_PROPERTIES, properties); +} + +static void +im_source_menu_item_init (ImSourceMenuItem *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + IM_TYPE_SOURCE_MENU_ITEM, + ImSourceMenuItemPrivate); +} + +void +im_source_menu_item_set_menu_item (ImSourceMenuItem *self, + GMenuItem *menuitem) +{ + gchar *iconstr = NULL; + GIcon *icon = NULL; + gchar *label; + gchar *action = NULL; + + if (g_menu_item_get_attribute (menuitem, "x-canonical-icon", "s", &iconstr)) + { + GError *error; + icon = g_icon_new_for_string (iconstr, &error); + if (icon == NULL) + { + g_warning ("unable to set icon: %s", error->message); + g_error_free (error); + } + g_free (iconstr); + } + gtk_image_set_from_gicon (GTK_IMAGE (self->priv->icon), icon, GTK_ICON_SIZE_MENU); + + g_menu_item_get_attribute (menuitem, "label", "s", &label); + gtk_label_set_label (GTK_LABEL (self->priv->label), label ? label : ""); + + g_menu_item_get_attribute (menuitem, "action", "s", &action); + im_source_menu_item_set_action_name (self, action); + + if (icon) + g_object_unref (icon); + g_free (label); + g_free (action); +} + +void +im_source_menu_item_set_action_group (ImSourceMenuItem *self, + GActionGroup *action_group) +{ + ImSourceMenuItemPrivate *priv = self->priv; + + if (priv->action_group != NULL) + { + g_signal_handlers_disconnect_by_func (priv->action_group, im_source_menu_item_action_added, self); + g_signal_handlers_disconnect_by_func (priv->action_group, im_source_menu_item_action_removed, self); + g_signal_handlers_disconnect_by_func (priv->action_group, im_source_menu_item_action_enabled_changed, self); + g_signal_handlers_disconnect_by_func (priv->action_group, im_source_menu_item_action_state_changed, self); + + g_clear_object (&priv->action_group); + } + + if (action_group != NULL) + { + priv->action_group = g_object_ref (action_group); + + g_signal_connect (priv->action_group, "action-added", + G_CALLBACK (im_source_menu_item_action_added), self); + g_signal_connect (priv->action_group, "action-removed", + G_CALLBACK (im_source_menu_item_action_removed), self); + g_signal_connect (priv->action_group, "action-enabled-changed", + G_CALLBACK (im_source_menu_item_action_enabled_changed), self); + g_signal_connect (priv->action_group, "action-state-changed", + G_CALLBACK (im_source_menu_item_action_state_changed), self); + } +} diff --git a/src/im-source-menu-item.h b/src/im-source-menu-item.h new file mode 100644 index 0000000..c359b94 --- /dev/null +++ b/src/im-source-menu-item.h @@ -0,0 +1,54 @@ +/* + * Copyright 2012 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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 . + * + * Authors: + * Lars Uebernickel + */ + +#ifndef __IM_SOURCE_MENU_ITEM_H__ +#define __IM_SOURCE_MENU_ITEM_H__ + +#include + +#define IM_TYPE_SOURCE_MENU_ITEM (im_source_menu_item_get_type ()) +#define IM_SOURCE_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IM_TYPE_SOURCE_MENU_ITEM, ImSourceMenuItem)) +#define IM_SOURCE_MENU_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), IM_TYPE_SOURCE_MENU_ITEM, ImSourceMenuItemClass)) +#define IS_IM_SOURCE_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IM_TYPE_SOURCE_MENU_ITEM)) +#define IS_IM_SOURCE_MENU_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), IM_TYPE_SOURCE_MENU_ITEM)) +#define IM_SOURCE_MENU_ITEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), IM_TYPE_SOURCE_MENU_ITEM, ImSourceMenuItemClass)) + +typedef struct _ImSourceMenuItem ImSourceMenuItem; +typedef struct _ImSourceMenuItemClass ImSourceMenuItemClass; +typedef struct _ImSourceMenuItemPrivate ImSourceMenuItemPrivate; + +struct _ImSourceMenuItemClass +{ + GtkMenuItemClass parent_class; +}; + +struct _ImSourceMenuItem +{ + GtkMenuItem parent; + ImSourceMenuItemPrivate *priv; +}; + +GType im_source_menu_item_get_type (void); + +void im_source_menu_item_set_menu_item (ImSourceMenuItem *item, + GMenuItem *menuitem); +void im_source_menu_item_set_action_group (ImSourceMenuItem *self, + GActionGroup *action_group); + +#endif diff --git a/src/indicator-messages.c b/src/indicator-messages.c new file mode 100644 index 0000000..5c5df31 --- /dev/null +++ b/src/indicator-messages.c @@ -0,0 +1,382 @@ +/* +An indicator to show information that is in messaging applications +that the user is using. + +Copyright 2012 Canonical Ltd. + +Authors: + Ted Gould + Lars Uebernickel + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License version 3, as published +by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranties of +MERCHANTABILITY, SATISFACTORY QUALITY, 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 . +*/ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "dbus-data.h" + +#include "ido-menu-item.h" +#include "im-app-menu-item.h" +#include "im-source-menu-item.h" + +#define INDICATOR_MESSAGES_TYPE (indicator_messages_get_type ()) +#define INDICATOR_MESSAGES(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), INDICATOR_MESSAGES_TYPE, IndicatorMessages)) +#define INDICATOR_MESSAGES_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), INDICATOR_MESSAGES_TYPE, IndicatorMessagesClass)) +#define IS_INDICATOR_MESSAGES(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), INDICATOR_MESSAGES_TYPE)) +#define IS_INDICATOR_MESSAGES_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), INDICATOR_MESSAGES_TYPE)) +#define INDICATOR_MESSAGES_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), INDICATOR_MESSAGES_TYPE, IndicatorMessagesClass)) + +typedef struct _IndicatorMessages IndicatorMessages; +typedef struct _IndicatorMessagesClass IndicatorMessagesClass; + +struct _IndicatorMessagesClass { + IndicatorObjectClass parent_class; +}; + +struct _IndicatorMessages { + IndicatorObject parent; + IndicatorServiceManager * service; + GActionGroup *actions; + GMenuModel *menu; + GtkWidget *image; + GtkWidget *gtkmenu; + gchar *accessible_desc; +}; + +GType indicator_messages_get_type (void); + +/* Indicator Module Config */ +INDICATOR_SET_VERSION +INDICATOR_SET_TYPE(INDICATOR_MESSAGES_TYPE) + +/* Prototypes */ +static void indicator_messages_class_init (IndicatorMessagesClass *klass); +static void indicator_messages_init (IndicatorMessages *self); +static void indicator_messages_dispose (GObject *object); +static void indicator_messages_finalize (GObject *object); +static void service_connection_changed (IndicatorServiceManager *sm, + gboolean connected, + gpointer user_data); +static GtkImage * get_image (IndicatorObject * io); +static GtkMenu * get_menu (IndicatorObject * io); +static const gchar * get_accessible_desc (IndicatorObject * io); +static const gchar * get_name_hint (IndicatorObject * io); +static void menu_items_changed (GMenuModel *menu, + gint position, + gint removed, + gint added, + gpointer user_data); +static void messages_action_added (GActionGroup *action_group, + gchar *action_name, + gpointer user_data); +static void messages_state_changed (GActionGroup *action_group, + gchar *action_name, + GVariant *value, + gpointer user_data); +static void indicator_messages_add_toplevel_menu (IndicatorMessages *self); + +G_DEFINE_TYPE (IndicatorMessages, indicator_messages, INDICATOR_OBJECT_TYPE); + +/* Initialize the one-timers */ +static void +indicator_messages_class_init (IndicatorMessagesClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = indicator_messages_dispose; + object_class->finalize = indicator_messages_finalize; + + IndicatorObjectClass * io_class = INDICATOR_OBJECT_CLASS(klass); + + io_class->get_image = get_image; + io_class->get_menu = get_menu; + io_class->get_accessible_desc = get_accessible_desc; + io_class->get_name_hint = get_name_hint; +} + +/* Build up our per-instance variables */ +static void +indicator_messages_init (IndicatorMessages *self) +{ + self->service = indicator_service_manager_new_version(INDICATOR_MESSAGES_DBUS_NAME, 1); + g_signal_connect (self->service, "connection-change", + G_CALLBACK (service_connection_changed), self); + + self->gtkmenu = gtk_menu_new (); + g_object_ref_sink (self->gtkmenu); + + self->image = g_object_ref_sink (gtk_image_new ()); + + /* make sure custom menu item types are registered (so that + * gtk_model_new_from_menu can pick them up */ + ido_menu_item_get_type (); + im_app_menu_item_get_type (); + im_source_menu_item_get_type (); +} + +/* Unref stuff */ +static void +indicator_messages_dispose (GObject *object) +{ + IndicatorMessages * self = INDICATOR_MESSAGES(object); + g_return_if_fail(self != NULL); + + g_clear_object (&self->service); + g_clear_object (&self->actions); + g_clear_object (&self->menu); + g_clear_object (&self->gtkmenu); + g_clear_object (&self->image); + + G_OBJECT_CLASS (indicator_messages_parent_class)->dispose (object); + return; +} + +/* Destory all memory users */ +static void +indicator_messages_finalize (GObject *object) +{ + IndicatorMessages *self = INDICATOR_MESSAGES (object); + + g_free (self->accessible_desc); + + G_OBJECT_CLASS (indicator_messages_parent_class)->finalize (object); + return; +} + + + +/* Functions */ + +static void service_connection_changed (IndicatorServiceManager *sm, + gboolean connected, + gpointer user_data) +{ + IndicatorMessages *self = user_data; + GDBusConnection *bus; + GError *error = NULL; + + if (self->actions != NULL) { + g_signal_handlers_disconnect_by_func (self->actions, messages_action_added, self); + g_signal_handlers_disconnect_by_func (self->actions, messages_state_changed, self); + g_clear_object (&self->actions); + } + if (self->menu != NULL) { + g_signal_handlers_disconnect_by_func (self->menu, menu_items_changed, self); + g_clear_object (&self->menu); + } + gtk_menu_shell_bind_model (GTK_MENU_SHELL (self->gtkmenu), NULL, NULL, FALSE); + + if (connected == FALSE) + return; + + bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + if (!bus) { + g_warning ("error connecting to the session bus: %s", error->message); + g_error_free (error); + return; + } + + self->actions = G_ACTION_GROUP (g_dbus_action_group_get (bus, + INDICATOR_MESSAGES_DBUS_NAME, + INDICATOR_MESSAGES_DBUS_OBJECT)); + gtk_widget_insert_action_group (self->gtkmenu, + get_name_hint (INDICATOR_OBJECT (self)), + self->actions); + g_signal_connect (self->actions, "action-added::messages", + G_CALLBACK (messages_action_added), self); + g_signal_connect (self->actions, "action-state-changed::messages", + G_CALLBACK (messages_state_changed), self); + + self->menu = G_MENU_MODEL (g_dbus_menu_model_get (bus, + INDICATOR_MESSAGES_DBUS_NAME, + INDICATOR_MESSAGES_DBUS_OBJECT)); + g_signal_connect (self->menu, "items-changed", G_CALLBACK (menu_items_changed), self); + + if (g_menu_model_get_n_items (self->menu) == 1) + indicator_messages_add_toplevel_menu (self); + else + indicator_object_set_visible (INDICATOR_OBJECT (self), FALSE); + + g_object_unref (bus); +} + +static GtkImage * +get_image (IndicatorObject * io) +{ + IndicatorMessages *self = INDICATOR_MESSAGES (io); + + gtk_widget_show (self->image); + return GTK_IMAGE (self->image); +} + +static GtkMenu * +get_menu (IndicatorObject * io) +{ + IndicatorMessages *self = INDICATOR_MESSAGES (io); + + return GTK_MENU (self->gtkmenu); +} + +static const gchar * +get_accessible_desc (IndicatorObject * io) +{ + IndicatorMessages *self = INDICATOR_MESSAGES (io); + return self->accessible_desc; +} + +static const gchar * +get_name_hint (IndicatorObject *io) +{ + return PACKAGE; +} + +static void +indicator_messages_accessible_desc_updated (IndicatorMessages *self) +{ + GList *entries; + + entries = indicator_object_get_entries (INDICATOR_OBJECT (self)); + g_return_if_fail (entries != NULL); + + g_signal_emit_by_name (self, INDICATOR_OBJECT_SIGNAL_ACCESSIBLE_DESC_UPDATE, entries->data); + + g_list_free (entries); +} + +static GIcon * +g_menu_model_get_item_attribute_icon (GMenuModel *menu, + gint index, + const gchar *attribute) +{ + gchar *iconstr; + GIcon *icon = NULL; + + if (g_menu_model_get_item_attribute (menu, index, attribute, "s", &iconstr)) { + GError *error; + + icon = g_icon_new_for_string (iconstr, &error); + if (icon == NULL) { + g_warning ("unable to load icon: %s", error->message); + g_error_free (error); + } + + g_free (iconstr); + } + + return icon; +} + +static void +indicator_messages_add_toplevel_menu (IndicatorMessages *self) +{ + GIcon *icon; + GMenuModel *popup; + + indicator_object_set_visible (INDICATOR_OBJECT (self), TRUE); + + icon = g_menu_model_get_item_attribute_icon (self->menu, 0, "x-canonical-icon"); + if (icon) { + gtk_image_set_from_gicon (GTK_IMAGE (self->image), icon, GTK_ICON_SIZE_LARGE_TOOLBAR); + g_object_unref (icon); + } + + g_free (self->accessible_desc); + self->accessible_desc = NULL; + if (g_menu_model_get_item_attribute (self->menu, 0, "x-canonical-accessible-description", + "s", &self->accessible_desc)) { + indicator_messages_accessible_desc_updated (self); + } + + popup = g_menu_model_get_item_link (self->menu, 0, G_MENU_LINK_SUBMENU); + if (popup) { + gtk_menu_shell_bind_model (GTK_MENU_SHELL (self->gtkmenu), + popup, + get_name_hint (INDICATOR_OBJECT (self)), + TRUE); + + g_object_unref (popup); + } +} + +static void +menu_items_changed (GMenuModel *menu, + gint position, + gint removed, + gint added, + gpointer user_data) +{ + IndicatorMessages *self = user_data; + + g_return_if_fail (position == 0); + + if (added == 1) + indicator_messages_add_toplevel_menu (self); + else if (removed == 1) + indicator_object_set_visible (INDICATOR_OBJECT (self), FALSE); +} + +static void +indicator_messages_update_icon (IndicatorMessages *self, + GVariant *state) +{ + GIcon *icon; + GError *error = NULL; + + g_return_if_fail (g_variant_is_of_type (state, G_VARIANT_TYPE_STRING)); + + icon = g_icon_new_for_string (g_variant_get_string (state, NULL), &error); + if (icon == NULL) { + g_warning ("unable to load icon: %s", error->message); + g_error_free (error); + } + else { + gtk_image_set_from_gicon (GTK_IMAGE (self->image), icon, GTK_ICON_SIZE_LARGE_TOOLBAR); + g_object_unref (icon); + } +} + +static void +messages_action_added (GActionGroup *action_group, + gchar *action_name, + gpointer user_data) +{ + IndicatorMessages *self = user_data; + GVariant *state; + + state = g_action_group_get_action_state (action_group, "messages"); + indicator_messages_update_icon (self, state); + + g_variant_unref (state); +} + +static void +messages_state_changed (GActionGroup *action_group, + gchar *action_name, + GVariant *value, + gpointer user_data) +{ + IndicatorMessages *self = user_data; + + indicator_messages_update_icon (self, value); +} diff --git a/src/messages-service.c b/src/messages-service.c index 71fa09b..25a19b9 100644 --- a/src/messages-service.c +++ b/src/messages-service.c @@ -23,25 +23,419 @@ with this program. If not, see . #include #include +#include +#include #include +#include #include +#include "app-section.h" #include "dbus-data.h" +#include "gactionmuxer.h" #include "gsettingsstrv.h" #include "gmenuutils.h" #include "indicator-messages-service.h" -#include "indicator-messages-application.h" -#include "im-phone-menu.h" -#include "im-desktop-menu.h" -#include "im-application-list.h" #define NUM_STATUSES 5 -static ImApplicationList *applications; +static GHashTable *applications; static IndicatorMessagesService *messages_service; -static GHashTable *menus; +static GSimpleActionGroup *actions; +static GActionMuxer *action_muxer; +static GMenu *toplevel_menu; +static GMenu *menu; +static GMenuModel *chat_section; static GSettings *settings; +static gboolean draws_attention; +static const gchar *global_status[6]; /* max 5: available, away, busy, invisible, offline */ + +static gchar * +indicator_messages_get_icon_name () +{ + GString *name; + GIcon *icon; + gchar *iconstr; + + name = g_string_new ("indicator-messages"); + + if (global_status[0] != NULL) + { + if (global_status[1] != NULL) + g_string_append (name, "-mixed"); + else + g_string_append_printf (name, "-%s", global_status[0]); + } + + if (draws_attention) + g_string_append (name, "-new"); + + icon = g_themed_icon_new (name->str); + g_themed_icon_append_name (G_THEMED_ICON (icon), + draws_attention ? "indicator-messages-new" + : "indicator-messages"); + + iconstr = g_icon_to_string (icon); + + g_object_unref (icon); + g_string_free (name, TRUE); + + return iconstr; +} + +static void +indicator_messages_update_icon () +{ + GSimpleAction *messages; + gchar *icon; + + messages = G_SIMPLE_ACTION (g_simple_action_group_lookup (actions, "messages")); + g_return_if_fail (messages != NULL); + + icon = indicator_messages_get_icon_name (); + g_simple_action_set_state (messages, g_variant_new_string (icon)); + + g_free (icon); +} + +static gchar * +g_app_info_get_simple_id (GAppInfo *appinfo) +{ + const gchar *id; + + id = g_app_info_get_id (appinfo); + if (!id) + return NULL; + + if (g_str_has_suffix (id, ".desktop")) + return g_strndup (id, strlen (id) - 8); + else + return g_strdup (id); +} + +static void +actions_changed (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + AppSection *section = APP_SECTION (object); + gchar *id; + GActionGroup *actions; + + id = g_app_info_get_simple_id (app_section_get_app_info (section)); + actions = app_section_get_actions (section); + + g_action_muxer_insert (action_muxer, id, actions); + g_free (id); +} + + +static gboolean +app_section_draws_attention (gpointer key, + gpointer value, + gpointer user_data) +{ + AppSection *section = value; + return app_section_get_draws_attention (section); +} + +static void +draws_attention_changed (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + GSimpleAction *clear; + + clear = G_SIMPLE_ACTION (g_simple_action_group_lookup (actions, "clear")); + g_return_if_fail (clear != NULL); + + draws_attention = g_hash_table_find (applications, app_section_draws_attention, NULL) != NULL; + + g_simple_action_set_enabled (clear, draws_attention); + + indicator_messages_update_icon (); +} + +static gboolean +app_section_uses_chat (gpointer key, + gpointer value, + gpointer user_data) +{ + AppSection *section = value; + return app_section_get_uses_chat_status (section); +} + +static void +update_chat_section () +{ + gboolean show_chat; + GMenuModel *first_section; + + show_chat = g_hash_table_find (applications, app_section_uses_chat, NULL) != NULL; + + first_section = g_menu_model_get_item_link (G_MENU_MODEL (menu), 0, G_MENU_LINK_SECTION); + if (first_section == chat_section) { + if (!show_chat) + g_menu_remove (menu, 0); + } + else { + if (show_chat) + g_menu_insert_section (menu, 0, NULL, chat_section); + } + + if (first_section != NULL) + g_object_unref (first_section); + + indicator_messages_update_icon (); +} + +static void +uses_chat_status_changed (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + update_chat_section (); +} + +static gboolean +strv_contains (const gchar **strv, + const gchar *needle) +{ + const gchar **it; + + it = strv; + while (*it != NULL && !g_str_equal (*it, needle)) + it++; + + return *it != NULL; +} + +static void +update_chat_status () +{ + GHashTableIter iter; + AppSection *section; + int pos; + GAction *status; + + for (pos = 0; pos < G_N_ELEMENTS (global_status); pos++) + global_status[pos] = NULL; + + pos = 0; + g_hash_table_iter_init (&iter, applications); + while (g_hash_table_iter_next (&iter, NULL, (gpointer) §ion) && + pos < G_N_ELEMENTS (global_status)) + { + const gchar *status_str = NULL; + + status_str = app_section_get_status (section); + if (status_str != NULL && !strv_contains (global_status, status_str)) + global_status[pos++] = status_str; + } + + if (pos == 0) + global_status[0] = "offline"; + + status = g_simple_action_group_lookup (actions, "status"); + g_return_if_fail (status != NULL); + + g_simple_action_set_state (G_SIMPLE_ACTION (status), g_variant_new_strv (global_status, -1)); + + indicator_messages_update_icon (); +} + +static void +chat_status_changed (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + update_chat_status (); +} + +static void +remove_section (AppSection *section, + const gchar *id) +{ + int pos = g_menu_find_section (menu, app_section_get_menu (section)); + if (pos >= 0) + g_menu_remove (menu, pos); + g_action_muxer_remove (action_muxer, id); + + g_signal_handlers_disconnect_by_func (section, actions_changed, NULL); + g_signal_handlers_disconnect_by_func (section, draws_attention_changed, NULL); + g_signal_handlers_disconnect_by_func (section, uses_chat_status_changed, NULL); + g_signal_handlers_disconnect_by_func (section, chat_status_changed, NULL); + g_signal_handlers_disconnect_by_func (section, remove_section, NULL); + + g_hash_table_remove (applications, id); + + if (g_hash_table_size (applications) == 0 && + g_menu_model_get_n_items (G_MENU_MODEL (toplevel_menu)) == 1) { + g_menu_remove (toplevel_menu, 0); + } + + update_chat_status (); + update_chat_section (); +} + +static AppSection * +add_application (const gchar *desktop_id) +{ + GDesktopAppInfo *appinfo; + gchar *id; + AppSection *section; + + appinfo = g_desktop_app_info_new (desktop_id); + if (!appinfo) { + g_warning ("could not add '%s', there's no desktop file with that id", desktop_id); + return NULL; + } + + id = g_app_info_get_simple_id (G_APP_INFO (appinfo)); + section = g_hash_table_lookup (applications, id); + + if (!section) { + GMenuItem *menuitem; + + section = app_section_new(appinfo); + g_hash_table_insert (applications, g_strdup (id), section); + + g_action_muxer_insert (action_muxer, id, app_section_get_actions (section)); + g_signal_connect (section, "notify::actions", + G_CALLBACK (actions_changed), NULL); + g_signal_connect (section, "notify::draws-attention", + G_CALLBACK (draws_attention_changed), NULL); + g_signal_connect (section, "notify::uses-chat-status", + G_CALLBACK (uses_chat_status_changed), NULL); + g_signal_connect (section, "notify::chat-status", + G_CALLBACK (chat_status_changed), NULL); + g_signal_connect_data (section, "destroy", + G_CALLBACK (remove_section), + g_strdup (id), + (GClosureNotify) g_free, + 0); + + /* TODO insert it at the right position (alphabetically by application name) */ + menuitem = g_menu_item_new_section (NULL, app_section_get_menu (section)); + g_menu_item_set_attribute (menuitem, "action-namespace", "s", id); + g_menu_insert_item (menu, g_menu_model_get_n_items (G_MENU_MODEL (menu)) -1, menuitem); + g_object_unref (menuitem); + } + + if (g_menu_model_get_n_items (G_MENU_MODEL (toplevel_menu)) == 0) { + GMenuItem *header; + + header = g_menu_item_new (NULL, "messages"); + g_menu_item_set_submenu (header, G_MENU_MODEL (menu)); + g_menu_item_set_attribute (header, "x-canonical-accessible-description", "s", _("Messages")); + g_menu_append_item (toplevel_menu, header); + + g_object_unref (header); + } + + g_free (id); + g_object_unref (appinfo); + return section; +} + +static void +remove_application (const char *desktop_id) +{ + GDesktopAppInfo *appinfo; + gchar *id; + AppSection *section; + + appinfo = g_desktop_app_info_new (desktop_id); + if (!appinfo) { + g_warning ("could not remove '%s', there's no desktop file with that id", desktop_id); + return; + } + + id = g_app_info_get_simple_id (G_APP_INFO (appinfo)); + + section = g_hash_table_lookup (applications, id); + if (section) { + remove_section (section, id); + } + else { + g_warning ("could not remove '%s', it's not registered", desktop_id); + } + + g_free (id); + g_object_unref (appinfo); +} + +/* This function turns a specific desktop id into a menu + item and registers it appropriately with everyone */ +static gboolean +build_launcher (gpointer data) +{ + gchar *desktop_id = data; + + add_application (desktop_id); + + g_free (desktop_id); + return FALSE; +} + +/* This function goes through all the launchers that we're + supposed to be grabbing and decides to show turn them + into menu items or not. It doens't do the work, but it + makes the decision. */ +static gboolean +build_launchers (gpointer data) +{ + gchar **applications = g_settings_get_strv (settings, "applications"); + gchar **app; + + g_return_val_if_fail (applications != NULL, FALSE); + + for (app = applications; *app; app++) + { + g_idle_add(build_launcher, g_strdup (*app)); + } + + g_strfreev (applications); + return FALSE; +} + +static void +service_shutdown (IndicatorService * service, gpointer user_data) +{ + GMainLoop *mainloop = user_data; + + g_warning("Shutting down service!"); + g_main_loop_quit(mainloop); +} + +static void +app_section_remove_attention (gpointer key, + gpointer value, + gpointer user_data) +{ + AppSection *section = value; + app_section_clear_draws_attention (section); +} + +static void +clear_action_activate (GSimpleAction *simple, + GVariant *param, + gpointer user_data) +{ + g_hash_table_foreach (applications, app_section_remove_attention, NULL); +} + +static void +status_action_activate (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + const gchar *status; + + status = g_variant_get_string (parameter, NULL); + + indicator_messages_service_emit_status_changed (messages_service, status); +} static void register_application (IndicatorMessagesService *service, @@ -50,15 +444,18 @@ register_application (IndicatorMessagesService *service, const gchar *menu_path, gpointer user_data) { + AppSection *section; GDBusConnection *bus; const gchar *sender; - im_application_list_add (applications, desktop_id); + section = add_application (desktop_id); + if (!section) + return; bus = g_dbus_interface_skeleton_get_connection (G_DBUS_INTERFACE_SKELETON (service)); sender = g_dbus_method_invocation_get_sender (invocation); - im_application_list_set_remote (applications, desktop_id, bus, sender, menu_path); + app_section_set_object_path (section, bus, sender, menu_path); g_settings_strv_append_unique (settings, "applications", desktop_id); indicator_messages_service_complete_register_application (service, invocation); @@ -70,12 +467,35 @@ unregister_application (IndicatorMessagesService *service, const gchar *desktop_id, gpointer user_data) { - im_application_list_remove (applications, desktop_id); + remove_application (desktop_id); g_settings_strv_remove (settings, "applications", desktop_id); indicator_messages_service_complete_unregister_application (service, invocation); } +static void +application_stopped_running (IndicatorMessagesService *service, + GDBusMethodInvocation *invocation, + const gchar *desktop_id, + gpointer user_data) +{ + GDesktopAppInfo *appinfo; + gchar *id; + AppSection *section; + + indicator_messages_service_complete_application_stopped_running (service, invocation); + + if (!(appinfo = g_desktop_app_info_new (desktop_id))) + return; + + id = g_app_info_get_simple_id (G_APP_INFO (appinfo)); + section = g_hash_table_lookup (applications, id); + app_section_unset_object_path (section); + + g_free (id); + g_object_unref (appinfo); +} + static void set_status (IndicatorMessagesService *service, GDBusMethodInvocation *invocation, @@ -84,7 +504,8 @@ set_status (IndicatorMessagesService *service, gpointer user_data) { GDesktopAppInfo *appinfo; - const gchar *id; + gchar *id; + AppSection *section; g_return_if_fail (g_str_equal (status_str, "available") || g_str_equal (status_str, "away")|| @@ -98,53 +519,113 @@ set_status (IndicatorMessagesService *service, return; } - id = g_app_info_get_id (G_APP_INFO (appinfo)); - - im_application_list_set_status(applications, id, status_str); + id = g_app_info_get_simple_id (G_APP_INFO (appinfo)); + section = g_hash_table_lookup (applications, id); + if (section != NULL) + app_section_set_status (section, status_str); indicator_messages_service_complete_set_status (service, invocation); + g_free (id); g_object_unref (appinfo); } -/* The status has been set by the user, let's tell the world! */ -static void -status_set_by_user (ImApplicationList * list, const gchar * status, gpointer user_data) +static GSimpleActionGroup * +create_action_group (void) +{ + GSimpleActionGroup *actions; + GSimpleAction *messages; + GSimpleAction *clear; + GSimpleAction *status; + const gchar *default_status[] = { "offline", NULL }; + gchar *icon; + + actions = g_simple_action_group_new (); + + /* state of the messages action is its icon name */ + icon = indicator_messages_get_icon_name (); + messages = g_simple_action_new_stateful ("messages", G_VARIANT_TYPE ("s"), + g_variant_new_string (icon)); + + status = g_simple_action_new_stateful ("status", G_VARIANT_TYPE ("s"), + g_variant_new_strv (default_status, -1)); + g_signal_connect (status, "activate", G_CALLBACK (status_action_activate), NULL); + + clear = g_simple_action_new ("clear", NULL); + g_simple_action_set_enabled (clear, FALSE); + g_signal_connect (clear, "activate", G_CALLBACK (clear_action_activate), NULL); + + g_simple_action_group_insert (actions, G_ACTION (messages)); + g_simple_action_group_insert (actions, G_ACTION (status)); + g_simple_action_group_insert (actions, G_ACTION (clear)); + + g_free (icon); + return actions; +} + +static GMenuModel * +create_status_section (void) { - indicator_messages_service_emit_status_changed(messages_service, status); - return; + GMenu *menu; + GMenuItem *item; + struct status_item { + gchar *label; + gchar *action; + gchar *icon_name; + } status_items[] = { + { _("Available"), "status::available", "user-available" }, + { _("Away"), "status::away", "user-away" }, + { _("Busy"), "status::busy", "user-busy" }, + { _("Invisible"), "status::invisible", "user-invisible" }, + { _("Offline"), "status::offline", "user-offline" } + }; + int i; + + menu = g_menu_new (); + + item = g_menu_item_new (NULL, NULL); + g_menu_item_set_attribute (item, "x-canonical-type", "s", "IdoMenuItem"); + + for (i = 0; i < G_N_ELEMENTS (status_items); i++) { + g_menu_item_set_label (item, status_items[i].label); + g_menu_item_set_detailed_action (item, status_items[i].action); + g_menu_item_set_attribute (item, "x-canonical-icon", "s", status_items[i].icon_name); + g_menu_append_item (menu, item); + } + + g_object_unref (item); + return G_MENU_MODEL (menu); } static void -on_bus_acquired (GDBusConnection *bus, - const gchar *name, - gpointer user_data) +got_bus (GObject *object, + GAsyncResult * res, + gpointer user_data) { + GDBusConnection *bus; GError *error = NULL; - GHashTableIter it; - const gchar *profile; - ImMenu *menu; + + bus = g_bus_get_finish (res, &error); + if (!bus) { + g_warning ("unable to connect to the session bus: %s", error->message); + g_error_free (error); + return; + } g_dbus_connection_export_action_group (bus, INDICATOR_MESSAGES_DBUS_OBJECT, - im_application_list_get_action_group (applications), - &error); + G_ACTION_GROUP (action_muxer), &error); if (error) { g_warning ("unable to export action group on dbus: %s", error->message); g_error_free (error); return; } - g_hash_table_iter_init (&it, menus); - while (g_hash_table_iter_next (&it, (gpointer *) &profile, (gpointer *) &menu)) { - gchar *object_path; - - object_path = g_strconcat (INDICATOR_MESSAGES_DBUS_OBJECT, "/", profile, NULL); - if (!im_menu_export (menu, bus, object_path, &error)) { - g_warning ("unable to export menu for profile '%s': %s", profile, error->message); - g_clear_error (&error); - } - - g_free (object_path); + g_dbus_connection_export_menu_model (bus, INDICATOR_MESSAGES_DBUS_OBJECT, + G_MENU_MODEL (toplevel_menu), &error); + if (error) { + g_warning ("unable to export menu on dbus: %s", error->message); + g_error_free (error); + return; } g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (messages_service), @@ -159,29 +640,19 @@ on_bus_acquired (GDBusConnection *bus, g_object_unref (bus); } -static void -on_name_lost (GDBusConnection *bus, - const gchar *name, - gpointer user_data) -{ - GMainLoop *mainloop = user_data; - - g_main_loop_quit (mainloop); -} - int main (int argc, char ** argv) { - GMainLoop * mainloop = NULL; - GBusNameOwnerFlags flags; - - /* Glib init */ -#if G_ENCODE_VERSION(GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION) <= GLIB_VERSION_2_34 - g_type_init(); -#endif + GMainLoop * mainloop; + IndicatorService * service; + gdk_init(&argc, &argv); mainloop = g_main_loop_new (NULL, FALSE); + /* Create the Indicator Service interface */ + service = indicator_service_new_version(INDICATOR_MESSAGES_DBUS_NAME, 1); + g_signal_connect(service, INDICATOR_SERVICE_SIGNAL_SHUTDOWN, G_CALLBACK(service_shutdown), mainloop); + /* Setting up i18n and gettext. Apparently, we need all of these. */ setlocale (LC_ALL, ""); @@ -191,46 +662,40 @@ main (int argc, char ** argv) /* Bring up the service DBus interface */ messages_service = indicator_messages_service_skeleton_new (); - flags = G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT; - if (argc >= 2 && g_str_equal (argv[1], "--replace")) - flags |= G_BUS_NAME_OWNER_FLAGS_REPLACE; + g_bus_get (G_BUS_TYPE_SESSION, NULL, got_bus, NULL); + + actions = create_action_group (); - g_bus_own_name (G_BUS_TYPE_SESSION, "com.canonical.indicator.messages", flags, - on_bus_acquired, NULL, on_name_lost, mainloop, NULL); + action_muxer = g_action_muxer_new (); + g_action_muxer_insert (action_muxer, NULL, G_ACTION_GROUP (actions)); g_signal_connect (messages_service, "handle-register-application", G_CALLBACK (register_application), NULL); g_signal_connect (messages_service, "handle-unregister-application", G_CALLBACK (unregister_application), NULL); + g_signal_connect (messages_service, "handle-application-stopped-running", + G_CALLBACK (application_stopped_running), NULL); g_signal_connect (messages_service, "handle-set-status", G_CALLBACK (set_status), NULL); - applications = im_application_list_new (); - g_signal_connect (applications, "status-set", - G_CALLBACK (status_set_by_user), NULL); + menu = g_menu_new (); + chat_section = create_status_section (); + g_menu_append (menu, _("Clear"), "clear"); - settings = g_settings_new ("com.canonical.indicator.messages"); - { - gchar **app_ids; - gchar **id; + toplevel_menu = g_menu_new (); - app_ids = g_settings_get_strv (settings, "applications"); - for (id = app_ids; *id; id++) - im_application_list_add (applications, *id); + settings = g_settings_new ("com.canonical.indicator.messages"); - g_strfreev (app_ids); - } + applications = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); - menus = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_object_unref); - g_hash_table_insert (menus, "phone", im_phone_menu_new (applications)); - g_hash_table_insert (menus, "desktop", im_desktop_menu_new (applications)); + g_idle_add(build_launchers, NULL); g_main_loop_run(mainloop); /* Clean up */ - g_hash_table_unref (menus); g_object_unref (messages_service); + g_object_unref (chat_section); g_object_unref (settings); - g_object_unref (applications); + g_hash_table_unref (applications); return 0; } diff --git a/src/messages-service.xml b/src/messages-service.xml new file mode 100644 index 0000000..3c3c779 --- /dev/null +++ b/src/messages-service.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/Makefile.am b/test/Makefile.am index c39aea4..4f1a163 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -38,8 +38,8 @@ noinst_LTLIBRARIES = \ libindicator-messages-service.la libindicator_messages_service_la_SOURCES = \ - $(top_builddir)/common/indicator-messages-service.c \ - $(top_builddir)/common/indicator-messages-service.h \ + $(top_builddir)/src/indicator-messages-service.c \ + $(top_builddir)/src/indicator-messages-service.h \ $(top_srcdir)/src/app-section.c \ $(top_srcdir)/src/app-section.h \ $(top_srcdir)/src/gactionmuxer.c \ @@ -53,7 +53,6 @@ libindicator_messages_service_la_CFLAGS = \ $(APPLET_CFLAGS) \ $(COVERAGE_CFLAGS) \ -I$(top_builddir)/src \ - -I$(top_builddir)/common \ -Wall \ -Wl,-Bsymbolic-functions \ -Wl,-z,defs \ diff --git a/test/indicator-messages-service-activate.c b/test/indicator-messages-service-activate.c index f5a26b0..b0ec9b7 100644 --- a/test/indicator-messages-service-activate.c +++ b/test/indicator-messages-service-activate.c @@ -28,10 +28,6 @@ with this program. If not, see . int main (int argc, char ** argv) { -#if G_ENCODE_VERSION(GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION) <= GLIB_VERSION_2_34 - g_type_init(); -#endif - guint returnval = 0; GError * error = NULL; diff --git a/test/test-gactionmuxer.cpp b/test/test-gactionmuxer.cpp index efd6e4f..b80f86d 100644 --- a/test/test-gactionmuxer.cpp +++ b/test/test-gactionmuxer.cpp @@ -1,21 +1,3 @@ -/* -An indicator to show information that is in messaging applications -that the user is using. - -Copyright 2012 Canonical Ltd. - -This program is free software: you can redistribute it and/or modify it -under the terms of the GNU General Public License version 3, as published -by the Free Software Foundation. - -This program is distributed in the hope that it will be useful, but -WITHOUT ANY WARRANTY; without even the implied warranties of -MERCHANTABILITY, SATISFACTORY QUALITY, 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 . -*/ #include #include @@ -43,10 +25,6 @@ strv_contains (gchar **str_array, TEST(GActionMuxerTest, Sanity) { GActionMuxer *muxer; -#if G_ENCODE_VERSION(GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION) <= GLIB_VERSION_2_34 - g_type_init (); -#endif - g_action_muxer_insert (NULL, NULL, NULL); g_action_muxer_remove (NULL, NULL); @@ -66,10 +44,6 @@ TEST(GActionMuxerTest, Empty) { GActionMuxer *muxer; gchar **actions; -#if G_ENCODE_VERSION(GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION) <= GLIB_VERSION_2_34 - g_type_init (); -#endif - muxer = g_action_muxer_new (); actions = g_action_group_list_actions (G_ACTION_GROUP (muxer)); @@ -89,10 +63,6 @@ TEST(GActionMuxerTest, AddAndRemove) { GActionMuxer *muxer; gchar **actions; -#if G_ENCODE_VERSION(GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION) <= GLIB_VERSION_2_34 - g_type_init (); -#endif - group1 = g_simple_action_group_new (); g_simple_action_group_add_entries (group1, entries1, @@ -184,10 +154,6 @@ TEST(GActionMuxerTest, ActionAttributes) { GVariant *state_hint[2]; GVariant *state[2]; -#if G_ENCODE_VERSION(GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION) <= GLIB_VERSION_2_34 - g_type_init (); -#endif - group = g_simple_action_group_new (); action = g_simple_action_new ("one", G_VARIANT_TYPE_STRING); g_simple_action_group_insert (group, G_ACTION (action)); -- cgit v1.2.3