From f9ebf6a718ea1a9be3386126de8eea1ec85cc79f Mon Sep 17 00:00:00 2001 From: Automatic PS uploader Date: Tue, 20 Aug 2013 02:06:39 +0000 Subject: Update symbols --- debian/changelog | 4 ++++ debian/libmessaging-menu0.symbols | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index 6544d6e..06f7d54 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,7 +1,11 @@ indicator-messages (13.10.1-0ubuntu1) UNRELEASED; urgency=low + [ Ted Gould ] * Bumping version to ensure we override all PPAs when this lands + [ Ubuntu daily release ] + * debian/*symbols: auto-update new symbols to released version + -- Ted Gould Mon, 19 Aug 2013 10:09:40 -0500 indicator-messages (13.10.0phablet1) raring; urgency=low diff --git a/debian/libmessaging-menu0.symbols b/debian/libmessaging-menu0.symbols index d5eaed1..6a54911 100644 --- a/debian/libmessaging-menu0.symbols +++ b/debian/libmessaging-menu0.symbols @@ -5,7 +5,7 @@ libmessaging-menu.so.0 libmessaging-menu0 #MINVER# 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 0replaceme + 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 -- cgit v1.2.3 From 439330de4c1f27de844d8d9be7e5ad3d386115dd Mon Sep 17 00:00:00 2001 From: Automatic PS uploader Date: Tue, 20 Aug 2013 02:06:41 +0000 Subject: Releasing 13.10.1+13.10.20130820-0ubuntu1, based on r352 --- debian/changelog | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/debian/changelog b/debian/changelog index 06f7d54..a6fe645 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,12 +1,13 @@ -indicator-messages (13.10.1-0ubuntu1) UNRELEASED; urgency=low +indicator-messages (13.10.1+13.10.20130820-0ubuntu1) saucy; urgency=low [ Ted Gould ] * Bumping version to ensure we override all PPAs when this lands [ Ubuntu daily release ] * debian/*symbols: auto-update new symbols to released version + * Automatic snapshot from revision 352 - -- Ted Gould Mon, 19 Aug 2013 10:09:40 -0500 + -- Ubuntu daily release Tue, 20 Aug 2013 02:06:40 +0000 indicator-messages (13.10.0phablet1) raring; urgency=low -- cgit v1.2.3 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 From 0a6bc4aae9383abffd49da27430fa035878b126f Mon Sep 17 00:00:00 2001 From: Automatic PS uploader Date: Tue, 20 Aug 2013 08:59:34 +0000 Subject: Releasing 13.10.1+13.10.20130820.2-0ubuntu1, based on r354 --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index 3df5f41..e409bdc 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +indicator-messages (13.10.1+13.10.20130820.2-0ubuntu1) saucy; urgency=low + + * Automatic snapshot from revision 354 + + -- Ubuntu daily release Tue, 20 Aug 2013 08:59:34 +0000 + indicator-messages (13.10.1+13.10.20130820-0ubuntu1) saucy; urgency=low [ Ted Gould ] -- cgit v1.2.3 From 79c89af3ac3c0c04f947c98b360a3ccaccbfc5dc Mon Sep 17 00:00:00 2001 From: Pete Woods Date: Tue, 20 Aug 2013 11:38:10 +0100 Subject: Re-do merge --- 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/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 + 55 files changed, 5700 insertions(+), 4829 deletions(-) create mode 100644 COPYING.GPLv3 create mode 100644 common/Makefile.am create mode 100644 common/com.canonical.indicator.messages.application.xml create mode 100644 common/com.canonical.indicator.messages.service.xml create mode 100644 data/com.canonical.indicator.messages create mode 100644 debian/source/format delete mode 100644 libmessaging-menu/gtupleaction.c delete mode 100644 libmessaging-menu/gtupleaction.h create mode 100644 libmessaging-menu/messaging-menu-app.c create mode 100644 libmessaging-menu/messaging-menu-app.h create mode 100644 libmessaging-menu/messaging-menu-message.c create mode 100644 libmessaging-menu/messaging-menu-message.h delete mode 100644 libmessaging-menu/messaging-menu.c delete mode 100644 po/indicator-messages.pot delete mode 100644 src/ido-detail-label.c delete mode 100644 src/ido-detail-label.h delete mode 100644 src/ido-menu-item.c delete mode 100644 src/ido-menu-item.h delete mode 100644 src/im-app-menu-item.c delete mode 100644 src/im-app-menu-item.h create mode 100644 src/im-application-list.c create mode 100644 src/im-application-list.h create mode 100644 src/im-desktop-menu.c create mode 100644 src/im-desktop-menu.h create mode 100644 src/im-menu.c create mode 100644 src/im-menu.h create mode 100644 src/im-phone-menu.c create mode 100644 src/im-phone-menu.h delete mode 100644 src/im-source-menu-item.c delete mode 100644 src/im-source-menu-item.h delete mode 100644 src/indicator-messages.c delete mode 100644 src/messages-service.xml diff --git a/COPYING.GPLv3 b/COPYING.GPLv3 new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/COPYING.GPLv3 @@ -0,0 +1,674 @@ + 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 daeb2b7..f8141a8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,5 +1,6 @@ SUBDIRS = \ + common \ src \ libmessaging-menu \ data \ diff --git a/autogen.sh b/autogen.sh index ab2f513..1abf3a7 100755 --- a/autogen.sh +++ b/autogen.sh @@ -1,5 +1,11 @@ -#!/bin/sh -e +#!/bin/sh -gtkdocize -autoreconf -i -f -intltoolize +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 diff --git a/common/Makefile.am b/common/Makefile.am new file mode 100644 index 0000000..0b8bad4 --- /dev/null +++ b/common/Makefile.am @@ -0,0 +1,32 @@ + +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 new file mode 100644 index 0000000..6f038e6 --- /dev/null +++ b/common/com.canonical.indicator.messages.application.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/common/com.canonical.indicator.messages.service.xml b/common/com.canonical.indicator.messages.service.xml new file mode 100644 index 0000000..3c3c779 --- /dev/null +++ b/common/com.canonical.indicator.messages.service.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/configure.ac b/configure.ac index 08ac680..e67b66a 100644 --- a/configure.ac +++ b/configure.ac @@ -61,6 +61,12 @@ 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 ########################### @@ -100,33 +106,20 @@ fi AM_CONDITIONAL([BUILD_TESTS],[test "x$enable_tests" = "xyes"]) ########################### -# Check to see if we're local -########################### - -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) - -########################### -# Indicator Info +# Vala API Generation ########################### -if test "x$with_localinstall" = "xyes"; then - INDICATORDIR="${libdir}/indicators/2/" -else - INDICATORDIR=`$PKG_CONFIG --variable=indicatordir indicator3-0.4` -fi -AC_SUBST(INDICATORDIR) +AC_ARG_ENABLE([vala], + AC_HELP_STRING([--disable-vala], [Disable vala]), + [enable_vala=$enableval], [enable_vala=yes]) -########################### -# 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) +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"]) ############################## # Custom Junk @@ -162,6 +155,7 @@ AM_GLIB_GNU_GETTEXT AC_OUTPUT([ Makefile src/Makefile +common/Makefile data/Makefile data/icons/Makefile data/icons/16x16/Makefile @@ -202,5 +196,6 @@ 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 488ce05..45ff12f 100644 --- a/data/Makefile.am +++ b/data/Makefile.am @@ -1,6 +1,6 @@ SUBDIRS = icons -dbus_servicesdir = $(DBUSSERVICEDIR) +dbus_servicesdir = $(datadir)/dbus-1/services dbus_services_DATA = indicator-messages.service %.service: %.service.in @@ -9,6 +9,9 @@ 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 new file mode 100644 index 0000000..fa0ac50 --- /dev/null +++ b/data/com.canonical.indicator.messages @@ -0,0 +1,10 @@ +[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/control b/debian/control index 7773476..47d4c0e 100644 --- a/debian/control +++ b/debian/control @@ -7,19 +7,19 @@ Uploaders: Evgeni Golov Build-Depends: debhelper (>= 9), dh-autoreconf, dh-translations, - libglib2.0-dev (>= 2.35.4), - libgtk-3-dev (>= 3.5.12), - libdbus-glib-1-dev, + gobject-introspection (>= 0.9.12-4~), + gtk-doc-tools, intltool, - libindicator3-dev, + libdbus-glib-1-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 c0f6e98..39c40f5 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 75ef089..53e0312 100644 --- a/debian/libmessaging-menu-dev.install +++ b/debian/libmessaging-menu-dev.install @@ -2,4 +2,5 @@ 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 6261a2a..6a54911 100644 --- a/debian/libmessaging-menu0.symbols +++ b/debian/libmessaging-menu0.symbols @@ -1,9 +1,11 @@ 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 @@ -13,6 +15,8 @@ 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 @@ -21,3 +25,14 @@ 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 new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +1.0 diff --git a/doc/reference/Makefile.am b/doc/reference/Makefile.am index 814a939..3993b41 100644 --- a/doc/reference/Makefile.am +++ b/doc/reference/Makefile.am @@ -11,8 +11,7 @@ HFILE_GLOB = $(top_srcdir)/libmessaging-menu/*.h CFILE_GLOB = $(top_srcdir)/libmessaging-menu/*.c IGNORE_HFILES= \ - indicator-messages-service.h \ - gtupleaction.h + indicator-messages-service.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 742d37b..4c1cec1 100644 --- a/doc/reference/messaging-menu-docs.xml.in +++ b/doc/reference/messaging-menu-docs.xml.in @@ -18,7 +18,8 @@ API Reference - + + diff --git a/libmessaging-menu/Makefile.am b/libmessaging-menu/Makefile.am index 7a6ee31..1042d9f 100644 --- a/libmessaging-menu/Makefile.am +++ b/libmessaging-menu/Makefile.am @@ -4,36 +4,25 @@ lib_LTLIBRARIES = libmessaging-menu.la libmessaging_menu_ladir = $(includedir)/messaging-menu libmessaging_menu_la_SOURCES = \ - messaging-menu.c \ - gtupleaction.c \ - gtupleaction.h \ - $(BUILT_SOURCES) + messaging-menu-app.c \ + messaging-menu-message.c libmessaging_menu_la_HEADERS = \ - messaging-menu.h + messaging-menu-app.h \ + messaging-menu.h \ + messaging-menu-message.h -libmessaging_menu_la_LIBADD = $(GIO_LIBS) +libmessaging_menu_la_LIBADD = \ + $(GIO_LIBS) \ + $(top_builddir)/common/libmessaging-common.la 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 @@ -52,7 +41,11 @@ 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.c messaging-menu.h +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_EXPORT_PACKAGES = messaging-menu INTROSPECTION_GIRS += MessagingMenu-1.0.gir @@ -62,5 +55,23 @@ gir_DATA = $(INTROSPECTION_GIRS) typelibdir = $(libdir)/girepository-1.0 typelib_DATA = $(INTROSPECTION_GIRS:.gir=.typelib) -CLEANFILES +=$(gir_DATA) $(typelib_DATA) +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 + endif diff --git a/libmessaging-menu/gtupleaction.c b/libmessaging-menu/gtupleaction.c deleted file mode 100644 index 21bc003..0000000 --- a/libmessaging-menu/gtupleaction.c +++ /dev/null @@ -1,354 +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 "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 deleted file mode 100644 index c447d71..0000000 --- a/libmessaging-menu/gtupleaction.h +++ /dev/null @@ -1,40 +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 __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 new file mode 100644 index 0000000..421a09f --- /dev/null +++ b/libmessaging-menu/messaging-menu-app.c @@ -0,0 +1,1433 @@ +/* + * 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 new file mode 100644 index 0000000..c8097e1 --- /dev/null +++ b/libmessaging-menu/messaging-menu-app.h @@ -0,0 +1,163 @@ +/* + * 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 new file mode 100644 index 0000000..b81cbea --- /dev/null +++ b/libmessaging-menu/messaging-menu-message.c @@ -0,0 +1,547 @@ +/* + * 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 new file mode 100644 index 0000000..4708246 --- /dev/null +++ b/libmessaging-menu/messaging-menu-message.h @@ -0,0 +1,70 @@ +/* + * 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 deleted file mode 100644 index 467a67a..0000000 --- a/libmessaging-menu/messaging-menu.c +++ /dev/null @@ -1,1200 +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.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 6c405c7..929b229 100644 --- a/libmessaging-menu/messaging-menu.h +++ b/libmessaging-menu/messaging-menu.h @@ -20,129 +20,6 @@ #ifndef __messaging_menu_h__ #define __messaging_menu_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 +#include "messaging-menu-app.h" #endif diff --git a/po/POTFILES.in b/po/POTFILES.in index edb0388..70c7f6b 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -1,3 +1,15 @@ [encoding: UTF-8] -src/indicator-messages.c +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/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 deleted file mode 100644 index 3788bb5..0000000 --- a/po/indicator-messages.pot +++ /dev/null @@ -1,46 +0,0 @@ -# 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 1df80e5..e03406a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,53 +1,10 @@ -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 \ @@ -56,11 +13,20 @@ indicator_messages_service_SOURCES = \ gsettingsstrv.c \ gsettingsstrv.h \ gmenuutils.c \ - gmenuutils.h + 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 indicator_messages_service_CFLAGS = \ $(APPLET_CFLAGS) \ $(COVERAGE_CFLAGS) \ + -I$(top_builddir)/common \ -Wall \ -Wl,-Bsymbolic-functions \ -Wl,-z,defs \ @@ -69,26 +35,11 @@ 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 1106c62..aad994d 100644 --- a/src/app-section.c +++ b/src/app-section.c @@ -34,6 +34,7 @@ with this program. If not, see . #include "dbus-data.h" #include "gmenuutils.h" #include "gactionmuxer.h" +#include "indicator-messages-application.h" struct _AppSectionPrivate { @@ -42,11 +43,14 @@ struct _AppSectionPrivate IndicatorDesktopShortcuts * ids; + GCancellable *app_proxy_cancellable; + IndicatorMessagesApplication *app_proxy; + GMenu *menu; - GMenuModel *source_menu; + GMenu *source_menu; GSimpleActionGroup *static_shortcuts; - GActionGroup *source_actions; + GSimpleActionGroup *source_actions; GActionMuxer *muxer; gboolean draws_attention; @@ -90,19 +94,6 @@ 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, @@ -170,6 +161,7 @@ static void app_section_init (AppSection *self) { AppSectionPrivate *priv; + GMenuItem *item; self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, APP_SECTION_TYPE, @@ -179,10 +171,19 @@ 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; @@ -249,32 +250,30 @@ 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); @@ -314,14 +313,12 @@ nick_activate_cb (GSimpleAction *action, g_return_if_fail(priv->ids != NULL); - GAppLaunchContext *context = get_launch_context (g_variant_get_uint32 (param)); - - if (!indicator_desktop_shortcuts_nick_exec_with_context(priv->ids, nick, context)) { + if (!indicator_desktop_shortcuts_nick_exec_with_context(priv->ids, nick, NULL)) { g_warning("Unable to execute nick '%s' for desktop file '%s'", nick, g_desktop_app_info_get_filename (priv->appinfo)); } - g_object_unref (context); + return; } static void @@ -414,7 +411,9 @@ 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))); - g_menu_item_set_attribute (item, "x-canonical-icon", "s", iconstr); + if (iconstr != NULL) { + g_menu_item_set_attribute (item, "x-canonical-icon", "s", iconstr); + } g_free (iconstr); g_menu_append_item (priv->menu, item); @@ -445,6 +444,11 @@ 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); @@ -583,49 +587,242 @@ app_section_get_draws_attention (AppSection *self) void app_section_clear_draws_attention (AppSection *self) { - AppSectionPrivate * priv = self->priv; - gchar **action_names; + 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; 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))); +} - if (priv->source_actions == NULL) +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); return; + } - action_names = g_action_group_list_actions (priv->source_actions); + g_menu_clear (priv->source_menu); + g_simple_action_group_clear (priv->source_actions); + priv->draws_attention = FALSE; - for (it = action_names; *it; it++) { + g_variant_iter_init (&iter, sources); + while (g_variant_iter_next (&iter, "(&s&s&sux&sb)", &id, &label, &iconstr, + &count, &time, &string, &draws_attention)) + { GVariant *state; + GSimpleAction *action; + GMenuItem *item; - state = g_action_group_get_action_state (priv->source_actions, *it); - if (!state) - continue; + 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)); - /* clear draws-attention while preserving other state */ - if (action_draws_attention (state)) { - guint32 count; - gint64 time; - const gchar *str; - GVariant *new_state; + 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); - g_variant_get (state, "(ux&sb)", &count, &time, &str, NULL); + priv->draws_attention = priv->draws_attention || draws_attention; - new_state = g_variant_new ("(uxsb)", count, time, str, FALSE); - g_action_group_change_action_state (priv->source_actions, *it, new_state); - } + 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_unref (state); +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)); + + if (draws_attention && !priv->draws_attention) { + priv->draws_attention = TRUE; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DRAWS_ATTENTION]); } - g_strfreev (action_names); + 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); } static void -application_vanished (GDBusConnection *bus, - const gchar *name, - gpointer user_data) +source_removed (IndicatorMessagesApplication *app, + const gchar *id, + gpointer user_data) { AppSection *self = user_data; - app_section_unset_object_path (self); + 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); } /* @@ -646,27 +843,20 @@ 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->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 = 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->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_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->draws_attention = FALSE; priv->name_watch_id = g_bus_watch_name_on_connection (bus, bus_name, 0, NULL, application_vanished, @@ -694,26 +884,19 @@ 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; } - 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); - } + g_simple_action_group_clear (priv->source_actions); + g_menu_clear (priv->source_menu); priv->draws_attention = FALSE; g_clear_pointer (&priv->chat_status, g_free); @@ -727,85 +910,6 @@ 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 64747a9..59bd305 100644 --- a/src/dbus-data.h +++ b/src/dbus-data.h @@ -1,9 +1,24 @@ +/* + * 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/menu" +#define INDICATOR_MESSAGES_DBUS_OBJECT "/com/canonical/indicator/messages" #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 2b1d11a..0f3cda4 100644 --- a/src/gactionmuxer.c +++ b/src/gactionmuxer.c @@ -483,3 +483,11 @@ 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 5c5e839..caf9ec7 100644 --- a/src/gactionmuxer.h +++ b/src/gactionmuxer.h @@ -40,5 +40,8 @@ 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 f63615b..cfd751e 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, "x-canonical-icon", "s", icon_name); + g_menu_item_set_attribute (item, "icon", "s", icon_name); g_menu_append_item (menu, item); diff --git a/src/ido-detail-label.c b/src/ido-detail-label.c deleted file mode 100644 index 8b7ef90..0000000 --- a/src/ido-detail-label.c +++ /dev/null @@ -1,401 +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 "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 deleted file mode 100644 index 1995fee..0000000 --- a/src/ido-detail-label.h +++ /dev/null @@ -1,59 +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 __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 deleted file mode 100644 index f702828..0000000 --- a/src/ido-menu-item.c +++ /dev/null @@ -1,439 +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 "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 deleted file mode 100644 index 0521928..0000000 --- a/src/ido-menu-item.h +++ /dev/null @@ -1,54 +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 __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 deleted file mode 100644 index 03b11c2..0000000 --- a/src/im-app-menu-item.c +++ /dev/null @@ -1,354 +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-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 deleted file mode 100644 index 519de8d..0000000 --- a/src/im-app-menu-item.h +++ /dev/null @@ -1,54 +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_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 new file mode 100644 index 0000000..bba7972 --- /dev/null +++ b/src/im-application-list.c @@ -0,0 +1,1139 @@ +/* + * 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 new file mode 100644 index 0000000..f1de220 --- /dev/null +++ b/src/im-application-list.h @@ -0,0 +1,62 @@ +/* + * 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 new file mode 100644 index 0000000..1040d62 --- /dev/null +++ b/src/im-desktop-menu.c @@ -0,0 +1,294 @@ +/* + * 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 new file mode 100644 index 0000000..9469ea6 --- /dev/null +++ b/src/im-desktop-menu.h @@ -0,0 +1,35 @@ +/* + * 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 new file mode 100644 index 0000000..ac23a29 --- /dev/null +++ b/src/im-menu.c @@ -0,0 +1,191 @@ +/* + * 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 new file mode 100644 index 0000000..d3775ad --- /dev/null +++ b/src/im-menu.h @@ -0,0 +1,64 @@ +/* + * 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 new file mode 100644 index 0000000..7381097 --- /dev/null +++ b/src/im-phone-menu.c @@ -0,0 +1,317 @@ +/* + * 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 new file mode 100644 index 0000000..9742f61 --- /dev/null +++ b/src/im-phone-menu.h @@ -0,0 +1,68 @@ +/* + * 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 deleted file mode 100644 index 2577c30..0000000 --- a/src/im-source-menu-item.c +++ /dev/null @@ -1,407 +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-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 deleted file mode 100644 index c359b94..0000000 --- a/src/im-source-menu-item.h +++ /dev/null @@ -1,54 +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_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 deleted file mode 100644 index 5c5df31..0000000 --- a/src/indicator-messages.c +++ /dev/null @@ -1,382 +0,0 @@ -/* -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 25a19b9..71fa09b 100644 --- a/src/messages-service.c +++ b/src/messages-service.c @@ -23,419 +23,25 @@ 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 GHashTable *applications; +static ImApplicationList *applications; static IndicatorMessagesService *messages_service; -static GSimpleActionGroup *actions; -static GActionMuxer *action_muxer; -static GMenu *toplevel_menu; -static GMenu *menu; -static GMenuModel *chat_section; +static GHashTable *menus; 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, @@ -444,18 +50,15 @@ register_application (IndicatorMessagesService *service, const gchar *menu_path, gpointer user_data) { - AppSection *section; GDBusConnection *bus; const gchar *sender; - section = add_application (desktop_id); - if (!section) - return; + im_application_list_add (applications, desktop_id); bus = g_dbus_interface_skeleton_get_connection (G_DBUS_INTERFACE_SKELETON (service)); sender = g_dbus_method_invocation_get_sender (invocation); - app_section_set_object_path (section, bus, sender, menu_path); + im_application_list_set_remote (applications, desktop_id, bus, sender, menu_path); g_settings_strv_append_unique (settings, "applications", desktop_id); indicator_messages_service_complete_register_application (service, invocation); @@ -467,35 +70,12 @@ unregister_application (IndicatorMessagesService *service, const gchar *desktop_id, gpointer user_data) { - remove_application (desktop_id); + im_application_list_remove (applications, 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, @@ -504,8 +84,7 @@ set_status (IndicatorMessagesService *service, gpointer user_data) { GDesktopAppInfo *appinfo; - gchar *id; - AppSection *section; + const gchar *id; g_return_if_fail (g_str_equal (status_str, "available") || g_str_equal (status_str, "away")|| @@ -519,113 +98,53 @@ set_status (IndicatorMessagesService *service, return; } - 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); + id = g_app_info_get_id (G_APP_INFO (appinfo)); + + im_application_list_set_status(applications, id, status_str); indicator_messages_service_complete_set_status (service, invocation); - g_free (id); g_object_unref (appinfo); } -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) +/* 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) { - 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); + indicator_messages_service_emit_status_changed(messages_service, status); + return; } static void -got_bus (GObject *object, - GAsyncResult * res, - gpointer user_data) +on_bus_acquired (GDBusConnection *bus, + const gchar *name, + gpointer user_data) { - GDBusConnection *bus; GError *error = NULL; - - 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; - } + GHashTableIter it; + const gchar *profile; + ImMenu *menu; g_dbus_connection_export_action_group (bus, INDICATOR_MESSAGES_DBUS_OBJECT, - G_ACTION_GROUP (action_muxer), &error); + im_application_list_get_action_group (applications), + &error); if (error) { g_warning ("unable to export action group on dbus: %s", error->message); g_error_free (error); return; } - 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_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_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (messages_service), @@ -640,18 +159,28 @@ got_bus (GObject *object, 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; - IndicatorService * service; + GMainLoop * mainloop = NULL; + GBusNameOwnerFlags flags; - gdk_init(&argc, &argv); - mainloop = g_main_loop_new (NULL, FALSE); + /* Glib init */ +#if G_ENCODE_VERSION(GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION) <= GLIB_VERSION_2_34 + g_type_init(); +#endif - /* 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); + mainloop = g_main_loop_new (NULL, FALSE); /* Setting up i18n and gettext. Apparently, we need all of these. */ @@ -662,40 +191,46 @@ main (int argc, char ** argv) /* Bring up the service DBus interface */ messages_service = indicator_messages_service_skeleton_new (); - g_bus_get (G_BUS_TYPE_SESSION, NULL, got_bus, NULL); - - actions = create_action_group (); + flags = G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT; + if (argc >= 2 && g_str_equal (argv[1], "--replace")) + flags |= G_BUS_NAME_OWNER_FLAGS_REPLACE; - action_muxer = g_action_muxer_new (); - g_action_muxer_insert (action_muxer, NULL, G_ACTION_GROUP (actions)); + g_bus_own_name (G_BUS_TYPE_SESSION, "com.canonical.indicator.messages", flags, + on_bus_acquired, NULL, on_name_lost, mainloop, NULL); 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); - menu = g_menu_new (); - chat_section = create_status_section (); - g_menu_append (menu, _("Clear"), "clear"); - - toplevel_menu = g_menu_new (); + applications = im_application_list_new (); + g_signal_connect (applications, "status-set", + G_CALLBACK (status_set_by_user), NULL); settings = g_settings_new ("com.canonical.indicator.messages"); + { + gchar **app_ids; + gchar **id; + + app_ids = g_settings_get_strv (settings, "applications"); + for (id = app_ids; *id; id++) + im_application_list_add (applications, *id); - applications = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); + g_strfreev (app_ids); + } - g_idle_add(build_launchers, NULL); + 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_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_hash_table_unref (applications); + g_object_unref (applications); return 0; } diff --git a/src/messages-service.xml b/src/messages-service.xml deleted file mode 100644 index 3c3c779..0000000 --- a/src/messages-service.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/test/Makefile.am b/test/Makefile.am index 4f1a163..c39aea4 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)/src/indicator-messages-service.c \ - $(top_builddir)/src/indicator-messages-service.h \ + $(top_builddir)/common/indicator-messages-service.c \ + $(top_builddir)/common/indicator-messages-service.h \ $(top_srcdir)/src/app-section.c \ $(top_srcdir)/src/app-section.h \ $(top_srcdir)/src/gactionmuxer.c \ @@ -53,6 +53,7 @@ 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 b0ec9b7..f5a26b0 100644 --- a/test/indicator-messages-service-activate.c +++ b/test/indicator-messages-service-activate.c @@ -28,6 +28,10 @@ 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 b80f86d..efd6e4f 100644 --- a/test/test-gactionmuxer.cpp +++ b/test/test-gactionmuxer.cpp @@ -1,3 +1,21 @@ +/* +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 @@ -25,6 +43,10 @@ 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); @@ -44,6 +66,10 @@ 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)); @@ -63,6 +89,10 @@ 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, @@ -154,6 +184,10 @@ 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