Ticket #3315: 0001-sl-3315.patch
File 0001-sl-3315.patch, 68.8 KB (added by ajay_garg, 12 years ago) |
---|
-
configure.ac
From 7292a106212489b2573afecddf6a0e94c6963f22 Mon Sep 17 00:00:00 2001 From: Ajay Garg <ajay@activitycentral.com> Date: Sun, 29 Jan 2012 14:20:39 +0530 Subject: [PATCH] sl#3315: Journal-Entry transfer from 1-to-N users. Organization: Sugar Labs Foundation Signed-off-by: Ajay Garg <ajay@activitycentral.com> --- The workflow can be assessed via the screenshots at :: http://wiki.sugarlabs.org/go/Features/Transfer_to_many_screenshots configure.ac | 1 + data/icons/Makefile.am | 1 + data/icons/module-configuration.svg | 190 +++++++++++ extensions/cpsection/Makefile.am | 1 + extensions/cpsection/configuration/Makefile.am | 6 + extensions/cpsection/configuration/__init__.py | 22 ++ extensions/cpsection/configuration/model.py | 21 ++ extensions/cpsection/configuration/view.py | 432 ++++++++++++++++++++++++ src/jarabe/frame/activitiestray.py | 69 ++++- src/jarabe/journal/palettes.py | 128 +++++++- src/jarabe/model/buddy.py | 12 + src/jarabe/model/filetransfer.py | 18 +- src/jarabe/model/friends.py | 267 ++++++++++++++- src/jarabe/view/buddymenu.py | 87 +++++- 14 files changed, 1222 insertions(+), 33 deletions(-) create mode 100644 data/icons/module-configuration.svg create mode 100644 extensions/cpsection/configuration/Makefile.am create mode 100644 extensions/cpsection/configuration/__init__.py create mode 100644 extensions/cpsection/configuration/model.py create mode 100644 extensions/cpsection/configuration/view.py diff --git a/configure.ac b/configure.ac index fa7165c..a70621d 100644
a b data/sugar-emulator.desktop 52 52 extensions/cpsection/aboutcomputer/Makefile 53 53 extensions/cpsection/accessibility/Makefile 54 54 extensions/cpsection/aboutme/Makefile 55 extensions/cpsection/configuration/Makefile 55 56 extensions/cpsection/datetime/Makefile 56 57 extensions/cpsection/frame/Makefile 57 58 extensions/cpsection/keyboard/Makefile -
data/icons/Makefile.am
diff --git a/data/icons/Makefile.am b/data/icons/Makefile.am index 8e01626..60f735a 100644
a b sugar_DATA = \ 4 4 module-about_me.svg \ 5 5 module-about_my_computer.svg \ 6 6 module-accessibility.svg \ 7 module-configuration.svg \ 7 8 module-date_and_time.svg \ 8 9 module-frame.svg \ 9 10 module-keyboard.svg \ -
new file data/icons/module-configuration.svg
diff --git a/data/icons/module-configuration.svg b/data/icons/module-configuration.svg new file mode 100644 index 0000000..16ca355
- + 1 <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 <!-- Created with Inkscape (http://www.inkscape.org/) --> 3 4 <svg 5 xmlns:dc="http://purl.org/dc/elements/1.1/" 6 xmlns:cc="http://creativecommons.org/ns#" 7 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 8 xmlns:svg="http://www.w3.org/2000/svg" 9 xmlns="http://www.w3.org/2000/svg" 10 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" 11 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" 12 width="744.09448819" 13 height="1052.3622047" 14 id="svg2" 15 version="1.1" 16 inkscape:version="0.48.1 r9760" 17 sodipodi:docname="module-configuration.svg"> 18 <defs 19 id="defs4"> 20 <inkscape:perspective 21 sodipodi:type="inkscape:persp3d" 22 inkscape:vp_x="0 : 526.18109 : 1" 23 inkscape:vp_y="0 : 1000 : 0" 24 inkscape:vp_z="744.09448 : 526.18109 : 1" 25 inkscape:persp3d-origin="372.04724 : 350.78739 : 1" 26 id="perspective6637" /> 27 <inkscape:perspective 28 id="perspective6615" 29 inkscape:persp3d-origin="0.5 : 0.33333333 : 1" 30 inkscape:vp_z="1 : 0.5 : 1" 31 inkscape:vp_y="0 : 1000 : 0" 32 inkscape:vp_x="0 : 0.5 : 1" 33 sodipodi:type="inkscape:persp3d" /> 34 </defs> 35 <sodipodi:namedview 36 id="base" 37 pagecolor="#ffffff" 38 bordercolor="#666666" 39 borderopacity="1.0" 40 inkscape:pageopacity="0.0" 41 inkscape:pageshadow="2" 42 inkscape:zoom="0.35" 43 inkscape:cx="375" 44 inkscape:cy="514.28571" 45 inkscape:document-units="px" 46 inkscape:current-layer="layer1" 47 showgrid="false" 48 inkscape:window-width="1366" 49 inkscape:window-height="693" 50 inkscape:window-x="0" 51 inkscape:window-y="25" 52 inkscape:window-maximized="1" /> 53 <metadata 54 id="metadata7"> 55 <rdf:RDF> 56 <cc:Work 57 rdf:about=""> 58 <dc:format>image/svg+xml</dc:format> 59 <dc:type 60 rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> 61 <dc:title></dc:title> 62 </cc:Work> 63 </rdf:RDF> 64 </metadata> 65 <g 66 inkscape:label="Layer 1" 67 inkscape:groupmode="layer" 68 id="layer1"> 69 <g 70 id="layer1-3" 71 inkscape:label="Layer 1" 72 transform="matrix(2.0112611,0,0,2.8271726,-382.79436,-991.72999)"> 73 <g 74 inkscape:label="Layer 1" 75 id="layer1-1" 76 transform="translate(170.0671,-314.28571)"> 77 <g 78 transform="matrix(9.8137136,0,0,9.8137136,-2250.1262,598.08659)" 79 id="g6596"> 80 <g 81 id="g7197"> 82 <path 83 sodipodi:type="arc" 84 style="fill:none;stroke:#00000f;stroke-width:9.03419971;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" 85 id="path5989" 86 sodipodi:cx="307.3714" 87 sodipodi:cy="24.611456" 88 sodipodi:rx="12.380237" 89 sodipodi:ry="11.953332" 90 d="m 319.75164,24.611456 c 0,6.601643 -5.54283,11.953332 -12.38024,11.953332 -6.83742,0 -12.38024,-5.351689 -12.38024,-11.953332 0,-6.601643 5.54282,-11.953332 12.38024,-11.953332 6.83741,0 12.38024,5.351689 12.38024,11.953332 z" 91 transform="matrix(0.77926228,0,0,0.79380856,10.613376,6.1120011)" /> 92 <rect 93 style="fill:#00000f;fill-opacity:1;stroke:none" 94 id="rect6505" 95 width="7.9117804" 96 height="9.2506971" 97 x="246.11057" 98 y="7.1083627" 99 ry="0.82999998" 100 rx="0.82999998" /> 101 <rect 102 style="fill:#00000f;fill-opacity:1;stroke:none" 103 id="rect6505-9" 104 width="7.9117804" 105 height="9.2506971" 106 x="246.21878" 107 y="35.461403" 108 rx="0.82999998" 109 ry="0.82999998" /> 110 <rect 111 style="fill:#00000f;fill-opacity:1;stroke:none" 112 id="rect6505-7" 113 width="7.9117804" 114 height="9.2506971" 115 x="-29.96331" 116 y="231.45294" 117 transform="matrix(0,-1,1,0,0,0)" 118 ry="0.82999998" 119 rx="0.82999998" /> 120 <rect 121 style="fill:#00000f;fill-opacity:1;stroke:none" 122 id="rect6505-6" 123 width="7.9117804" 124 height="9.2506971" 125 x="-29.848055" 126 y="259.62869" 127 transform="matrix(0,-1,1,0,0,0)" 128 ry="0.82999998" 129 rx="0.82999998" /> 130 <rect 131 style="fill:#00000f;fill-opacity:1;stroke:none" 132 id="rect6505-76" 133 width="7.9117804" 134 height="9.2506971" 135 x="154.41437" 136 y="176.47606" 137 transform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,0,0)" 138 ry="0.82999998" 139 rx="0.82999998" /> 140 <rect 141 style="fill:#00000f;fill-opacity:1;stroke:none" 142 id="rect6505-76-7" 143 width="7.9117804" 144 height="9.2506971" 145 x="-199.0881" 146 y="-177.31622" 147 transform="matrix(-0.70710678,-0.70710678,-0.70710678,0.70710678,0,0)" 148 ry="0.82999998" 149 rx="0.82999998" /> 150 <rect 151 style="fill:#00000f;fill-opacity:1;stroke:none" 152 id="rect6505-76-5" 153 width="7.9117804" 154 height="9.2506971" 155 x="154.6358" 156 y="204.75911" 157 transform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,0,0)" 158 ry="0.82999998" 159 rx="0.82999998" /> 160 <rect 161 style="fill:#00000f;fill-opacity:1;stroke:none" 162 id="rect6505-76-0" 163 width="7.9117804" 164 height="9.2506971" 165 x="-199.24612" 166 y="-148.95982" 167 transform="matrix(-0.70710678,-0.70710678,-0.70710678,0.70710678,0,0)" 168 ry="0.82999998" 169 rx="0.82999998" /> 170 </g> 171 </g> 172 </g> 173 <path 174 inkscape:connector-curvature="0" 175 id="path3073" 176 d="M 340.90476,719.02884 C 339.85714,717.98124 339,703.77275 339,687.4545 l 0,-29.6696 -10.32864,-4.3156 -10.32863,-4.31557 -22.38347,21.78237 c -18.44567,17.95034 -23.2886,21.29505 -27.52851,19.01228 -6.09928,-3.28383 -46.57361,-44.63814 -46.57361,-47.5862 0,-1.12068 9.16077,-11.48513 20.35725,-23.03211 l 20.35725,-20.99453 -4.31522,-10.84382 -4.31523,-10.84382 -29.61345,-1.42857 -29.61345,-1.42858 -0.80918,-34.05845 c -0.60171,-25.32665 0.13079,-34.65488 2.85715,-36.38482 2.01647,-1.2795 15.35566,-2.34605 29.64263,-2.37012 l 25.97632,-0.0437 3.54536,-11.83336 3.54537,-11.83336 -18.8074,-18.95263 c -10.34407,-10.42395 -18.8074,-20.23446 -18.8074,-21.80115 0,-1.56668 10.92551,-13.71726 24.2789,-27.00129 l 24.2789,-24.15277 19.97202,19.81898 19.97202,19.81898 12.17765,-4.4318 12.17766,-4.4318 0.71651,-24.02847 c 1.19845,-40.19036 -1.99777,-37.19155 38.64555,-36.25866 L 409,356.6479 l 0.80987,28.88986 0.80986,28.88986 11.95928,4.69773 11.95928,4.69774 18.73113,-18.5876 c 10.30212,-10.22317 20.65581,-18.58759 23.00821,-18.58759 2.3524,0 15.0678,10.84703 28.2564,24.10452 l 23.97931,24.10453 -20.52888,21.17152 -20.52889,21.17153 4.32606,8.29538 c 2.37931,4.56245 4.33394,9.85754 4.34363,11.76685 0.0114,2.44368 8.68823,3.71255 29.30331,4.28572 l 29.28572,0.81423 0,35.71429 0,35.71428 -29.28915,0.81428 -29.28914,0.81428 -4.3258,10.35309 -4.3258,10.3531 20.04351,20.67097 c 11.02395,11.36904 20.04352,22.24137 20.04352,24.16074 0,5.18526 -43.1282,48.54783 -48.28543,48.54783 -2.44054,0 -13.64577,-9.14697 -24.90053,-20.32663 l -20.46319,-20.3266 -10.31828,3.40534 -10.31829,3.40532 0,28.11617 c 0,15.46391 -0.78041,30.14991 -1.73425,32.63557 -1.44498,3.76557 -7.32266,4.5194 -35.23809,4.5194 -18.42712,0 -34.36099,-0.85714 -35.40861,-1.90477 l 0,0 z M 394.3828,591.70551 c 16.14693,-4.82459 33.99418,-23.23962 38.65434,-39.88406 7.17003,-25.60883 -3.3713,-52.07528 -26.41559,-66.32247 -11.46728,-7.08967 -16.74237,-8.49394 -31.90726,-8.49394 -25.11984,0 -42.50709,10.48723 -53.57143,32.31195 -12.59921,24.85226 -8.97102,47.7484 10.68602,67.43544 17.12848,17.15462 38.27107,22.20862 62.55392,14.95308 l 0,0 z" 177 style="fill:#a0a0a0;stroke:none" /> 178 <path 179 inkscape:connector-curvature="0" 180 id="path3075" 181 d="m 340.31812,686.89964 -2.03213,-31.35394 -10.35728,-4.13652 -10.35728,-4.13651 -22.11154,21.11617 c -12.16135,11.61391 -23.18839,21.1162 -24.50454,21.1162 -1.31614,0 -12.4099,-10.29334 -24.65281,-22.87409 -25.3507,-26.05026 -25.50888,-21.71631 1.82765,-50.07477 l 16.61352,-17.23457 -4.608,-11.73082 -4.608,-11.73082 -29.78067,-2.02985 -29.78067,-2.02985 0.80253,-33.29047 0.80253,-33.29047 28.48749,-2.85715 28.48749,-2.85714 3.28334,-11.42857 3.28334,-11.42857 -18.1994,-19.00791 c -10.00967,-10.45436 -18.1994,-20.12738 -18.1994,-21.4956 0,-1.36824 10.28354,-12.71092 22.85231,-25.20598 l 22.85231,-22.71828 19.3964,19.24778 19.3964,19.24777 13.13154,-4.47437 13.13155,-4.47437 1.15055,-26.27381 c 0.63281,-14.45059 2.14472,-27.88095 3.3598,-29.84523 1.55902,-2.52031 11.82996,-3.57143 34.89776,-3.57143 l 32.68852,0 0,22.67114 c 0,30.00654 2.58714,36.16256 17.01434,40.48506 l 11.55808,3.46288 18.32364,-17.59525 c 10.078,-9.67739 19.89161,-17.59526 21.80802,-17.59526 1.9164,0 13.92055,10.23947 26.67586,22.75438 l 23.19149,22.75438 -19.88883,20.06797 -19.88886,20.06796 4.43392,12.17766 c 4.09622,11.25017 5.3934,12.32472 17.03171,14.10873 6.92877,1.06209 19.99063,2.02637 29.02634,2.14285 l 16.42857,0.21178 0,34.28572 0,34.28571 -21.07142,0 c -33.44695,0 -38.35323,1.70239 -42.23369,14.65427 l -3.32149,11.0861 19.02758,19.70916 c 10.4652,10.84003 19.0276,21.34799 19.0276,23.35102 0,4.76402 -40.74506,45.48517 -45.51189,45.48517 -2.01771,0 -12.90991,-8.96083 -24.20489,-19.91295 -20.44586,-19.82525 -20.58136,-19.89914 -30.76296,-16.77431 l -10.22664,3.13866 -2.50396,31.77428 L 409,716.6479 l -33.32487,0.80283 -33.32488,0.80282 -2.03213,-31.35391 z m 58.05936,-96.11759 c 41.48929,-17.33107 50.46415,-70.92953 16.82283,-100.467 -5.72605,-5.02754 -15.82082,-10.5977 -22.43283,-12.37813 -14.95988,-4.02828 -38.40229,-1.33506 -50.05351,5.75047 -22.67204,13.78769 -35.67939,49.00392 -26.7625,72.45711 3.99028,10.49523 19.70634,27.24005 31.3352,33.38637 12.72992,6.72828 36.58078,7.31236 51.09081,1.25118 z" 182 style="fill:#a0a0a0;stroke:none" /> 183 <path 184 inkscape:connector-curvature="0" 185 id="path3077" 186 d="m 340.31239,686.8111 -2.0264,-31.2654 -10.35728,-4.13652 -10.35728,-4.13651 -22.11154,21.11617 c -12.16135,11.61391 -23.18839,21.1162 -24.50454,21.1162 -1.31614,0 -12.4099,-10.29334 -24.65281,-22.87409 -25.3507,-26.05026 -25.50888,-21.71631 1.82765,-50.07477 l 16.61352,-17.23457 -4.73383,-12.05114 c -4.33898,-11.04598 -5.59133,-12.05284 -15.01471,-12.07146 -5.65448,-0.0112 -18.95946,-0.83994 -29.5666,-1.84172 l -19.28571,-1.82142 0,-33.48294 0,-33.48293 29.21025,-2.53248 29.21025,-2.53248 3.27487,-11.42857 3.27486,-11.42857 -18.1994,-19.00791 c -10.00967,-10.45436 -18.1994,-20.12738 -18.1994,-21.4956 0,-1.36824 10.28354,-12.71092 22.85231,-25.20598 l 22.85231,-22.71828 19.3964,19.24778 19.3964,19.24777 13.13154,-4.47437 13.13155,-4.47437 1.15055,-26.27381 c 0.63281,-14.45059 2.14472,-27.88095 3.3598,-29.84523 1.55902,-2.52031 11.82996,-3.57143 34.89776,-3.57143 l 32.68852,0 0.2848,22.14286 c 0.406,31.56613 2.03422,35.66433 16.12267,40.58042 l 11.8983,4.15186 18.45696,-17.72329 c 10.15133,-9.7478 20.02494,-17.72328 21.94135,-17.72328 1.9164,0 13.92055,10.23947 26.67586,22.75438 l 23.19149,22.75438 -19.88883,20.06797 -19.88886,20.06796 4.43392,12.17766 c 4.09622,11.25017 5.3934,12.32472 17.03171,14.10873 6.92877,1.06209 19.99063,2.02637 29.02634,2.14285 l 16.42857,0.21178 0,34.28572 0,34.28571 -23.57142,0.0421 c -27.72475,0.0495 -36.66832,3.7013 -40.32949,16.46697 -2.54951,8.88964 -1.8394,10.14932 16.33206,28.97166 10.44857,10.82287 18.99743,21.31677 18.99743,23.3198 0,4.74805 -40.73712,45.48517 -45.48518,45.48517 -2.00302,0 -12.29835,-8.35715 -22.87852,-18.57143 -18.70259,-18.05583 -23.52162,-20.28709 -36.37094,-16.84 -5.69473,1.52771 -6.31799,4.13083 -8.1997,34.24668 l -2.03651,32.59332 -33.70233,0 -33.70233,0 -2.02639,-31.26537 z m 58.06509,-96.02905 c 31.3829,-13.10939 46.15865,-49.6639 32.35734,-80.05056 -16.96561,-37.35354 -70.38585,-46.62407 -98.95583,-17.17275 -24.62497,25.38461 -24.40007,61.43104 0.53077,85.07441 16.93513,16.06056 44.54805,21.13818 66.06772,12.1489 l 0,0 z" 187 style="fill:#ffffff;stroke:none" /> 188 </g> 189 </g> 190 </svg> -
extensions/cpsection/Makefile.am
diff --git a/extensions/cpsection/Makefile.am b/extensions/cpsection/Makefile.am index 2074d11..fef9e52 100644
a b SUBDIRS = \ 2 2 aboutme \ 3 3 aboutcomputer \ 4 4 accessibility \ 5 configuration \ 5 6 datetime \ 6 7 frame \ 7 8 keyboard \ -
new file extensions/cpsection/configuration/Makefile.am
diff --git a/extensions/cpsection/configuration/Makefile.am b/extensions/cpsection/configuration/Makefile.am new file mode 100644 index 0000000..9f3718a
- + 1 sugardir = $(pkgdatadir)/extensions/cpsection/configuration 2 3 sugar_PYTHON = \ 4 __init__.py \ 5 model.py \ 6 view.py -
new file extensions/cpsection/configuration/__init__.py
diff --git a/extensions/cpsection/configuration/__init__.py b/extensions/cpsection/configuration/__init__.py new file mode 100644 index 0000000..dd61992
- + 1 # Copyright (C) 2012 Simon Schampijer <erikos@sugarlabs.org> 2 # Copyright (C) 2012 Ajay Garg <ajay@activitycentral.com> 3 # 4 # This program is free software; you can redistribute it and/or modify 5 # it under the terms of the GNU General Public License as published by 6 # the Free Software Foundation; either version 2 of the License, or 7 # (at your option) any later version. 8 # 9 # This program is distributed in the hope that it will be useful, 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 # GNU General Public License for more details. 13 # 14 # You should have received a copy of the GNU General Public License 15 # along with this program; if not, write to the Free Software 16 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 17 18 from gettext import gettext as _ 19 20 CLASS = 'Configuration' 21 ICON = 'module-configuration' 22 TITLE = _('Configuration') -
new file extensions/cpsection/configuration/model.py
diff --git a/extensions/cpsection/configuration/model.py b/extensions/cpsection/configuration/model.py new file mode 100644 index 0000000..4fd798d
- + 1 # Copyright (C) 2012 Simon Schampijer <erikos@sugarlabs.org> 2 # Copyright (C) 2012 Ajay Garg <ajay@activitycentral.com> 3 # 4 # This program is free software; you can redistribute it and/or modify 5 # it under the terms of the GNU General Public License as published by 6 # the Free Software Foundation; either version 2 of the License, or 7 # (at your option) any later version. 8 # 9 # This program is distributed in the hope that it will be useful, 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 # GNU General Public License for more details. 13 # 14 # You should have received a copy of the GNU General Public License 15 # along with this program; if not, write to the Free Software 16 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 17 # 18 19 import logging 20 21 _logger = logging.getLogger('ControlPanel - Configuration') -
new file extensions/cpsection/configuration/view.py
diff --git a/extensions/cpsection/configuration/view.py b/extensions/cpsection/configuration/view.py new file mode 100644 index 0000000..8389fd3
- + 1 # Copyright (C) 2012 Simon Schampijer <erikos@sugarlabs.org> 2 # Copyright (C) 2012 Ajay Garg <ajay@activitycentral.com> 3 # 4 # This program is free software; you can redistribute it and/or modify 5 # it under the terms of the GNU General Public License as published by 6 # the Free Software Foundation; either version 2 of the License, or 7 # (at your option) any later version. 8 # 9 # This program is distributed in the hope that it will be useful, 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 # GNU General Public License for more details. 13 # 14 # You should have received a copy of the GNU General Public License 15 # along with this program; if not, write to the Free Software 16 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 17 18 import gtk 19 import gobject 20 from gettext import gettext as _ 21 22 from sugar.graphics import style 23 from sugar.graphics.alert import Alert 24 from sugar.graphics.icon import Icon 25 26 from jarabe.model import buddy 27 from jarabe.model import friends 28 from jarabe.model import neighborhood 29 30 from jarabe.controlpanel.sectionview import SectionView 31 from jarabe.controlpanel.inlinealert import InlineAlert 32 33 owner_model = buddy.get_owner_instance() 34 friends_model = friends.get_model() 35 neighborhood_model = neighborhood.get_model() 36 37 class LeftAlignedLabelWidget(gtk.HBox): 38 39 def __init__(self, label): 40 gtk.HBox.__init__(self) 41 label_widget = gtk.Label(label) 42 label_widget.set_line_wrap(True) 43 self.pack_start(label_widget, expand=False) 44 self.show_all() 45 46 47 class CenterAlignedLabelWidget(gtk.VBox): 48 49 def __init__(self, label): 50 gtk.VBox.__init__(self) 51 label_widget = gtk.Label(label) 52 label_widget.set_line_wrap(True) 53 self.pack_start(label_widget, expand=False) 54 self.show_all() 55 56 57 class BuddyWidget(gtk.HBox): 58 59 def __init__(self, buddy): 60 gtk.HBox.__init__(self) 61 self._buddy = buddy 62 63 self._check_button = gtk.CheckButton(label=self._buddy.nick) 64 self._check_button.set_active(False) 65 self.pack_start(self._check_button, expand=False) 66 self.show_all() 67 68 def _is_buddy_selected(self): 69 return self._check_button.get_active() 70 71 def _get_buddy_key(self): 72 return self._buddy.key 73 74 def _get_buddy_nick(self): 75 return self._buddy.nick 76 77 def _get_buddy_account(self): 78 if hasattr(self._buddy, 'account'): 79 return self._buddy.account 80 return None 81 82 def _get_buddy_contact_id(self): 83 if hasattr(self._buddy, 'contact_id'): 84 return self._buddy.contact_id 85 return None 86 87 88 class AddRemoveWidget(gtk.VBox): 89 90 def __init__(self, label, group_detail, add_button_clicked_cb, 91 remove_button_clicked_cb, index): 92 gtk.VBox.__init__(self) 93 self.set_homogeneous(False) 94 self.set_spacing(10) 95 96 self._potential_new_group = False 97 self._group_name = label 98 self._group_detail = group_detail 99 100 self._primary_box = gtk.HBox() 101 self._primary_box.set_homogeneous(False) 102 self._primary_box.set_spacing(10) 103 self.pack_start(self._primary_box, expand=False) 104 self._primary_box.show_all() 105 106 self._index = index 107 self._add_button_added = False 108 self._remove_button_added = False 109 110 self._label = gtk.Entry() 111 self._label.set_text(label) 112 113 # Do not allow an already existing group name to be modified. 114 if len(label) > 0: 115 self._label.set_sensitive(False) 116 # Else, this is a potentially new group. 117 # Mark this is as new. 118 # However, this will ACTUALLY be a new group, 119 # only if it is given a name. 120 # That check will be done, after the user clicks the final save 121 # button. 122 else: 123 self._potential_new_group = True 124 125 self._primary_box.pack_start(self._label, expand=False) 126 127 if not self._potential_new_group: 128 self._details_button = gtk.Button(_('View Details')) 129 self._view_id = self._details_button.connect('clicked', self.__view_details_cb) 130 self._primary_box.pack_start(self._details_button, 131 expand=False) 132 133 add_icon = Icon(icon_name='list-add') 134 self._add_button = gtk.Button() 135 self._add_button.set_image(add_icon) 136 self._add_button.connect('clicked', 137 add_button_clicked_cb, 138 self) 139 140 remove_icon = Icon(icon_name='list-remove') 141 self._remove_button = gtk.Button() 142 self._remove_button.set_image(remove_icon) 143 self._remove_button.connect('clicked', 144 remove_button_clicked_cb, 145 self) 146 147 self.__add_add_button() 148 self.__add_remove_button() 149 150 self._details_table = gtk.VBox() 151 self._details_table.set_spacing(20) 152 self._details_table.show_all() 153 self.pack_start(self._details_table, expand=False) 154 155 if self._potential_new_group: 156 info_label = LeftAlignedLabelWidget(_('You may batch add' 157 ' from the following available online buddies. Any' 158 ' currently offline buddy may be added later from the' 159 ' neighborhood view, when it comes online.')) 160 self.pack_start(info_label, expand=False) 161 self._friends_list_box = gtk.VBox() 162 self._friends_list_box.set_homogeneous(True) 163 self._friends_list_box.set_spacing(20) 164 self.pack_start(self._friends_list_box, expand=False) 165 self._populate_friends_list() 166 167 self.pack_start(gtk.HSeparator()) 168 169 self._primary_box.show_all() 170 self.show_all() 171 172 def _populate_friends_list(self): 173 buddies = neighborhood_model.get_buddies() 174 for buddy in buddies: 175 176 # Only make the buddy visible, if it has a valid key at 177 # this time. 178 if ((hasattr(buddy, 'key')) and (buddy.key is not None)): 179 180 # Do not show self :) 181 if buddy.key == owner_model.get_key(): 182 continue 183 184 self._friends_list_box.pack_start(BuddyWidget(buddy)) 185 186 self._friends_list_box.show_all() 187 188 def _get_buddy_widgets(self): 189 return self._friends_list_box.get_children() 190 191 def _get_index(self): 192 return self._index 193 194 def _set_index(self, value): 195 self._index = value 196 197 def _get_entry(self): 198 return self._label.get_text() 199 200 def __add_add_button(self): 201 self._primary_box.pack_start(self._add_button, expand=False) 202 self._add_button_added = True 203 204 def _remove_remove_button_if_not_already(self): 205 if self._remove_button_added: 206 self.__remove_remove_button() 207 208 def __remove_remove_button(self): 209 self._primary_box.remove(self._remove_button) 210 self._remove_button_added = False 211 212 def _add_remove_button_if_not_already(self): 213 if not self._remove_button_added: 214 self.__add_remove_button() 215 216 def __add_remove_button(self): 217 self._primary_box.pack_start(self._remove_button, expand=False) 218 self._remove_button_added = True 219 220 def __activate_view_id(self): 221 self._details_button.disconnect(self._hide_id) 222 self._view_id = self._details_button.connect('clicked', 223 self.__view_details_cb) 224 225 def __activate_hide_id(self): 226 self._details_button.disconnect(self._view_id) 227 self._hide_id = self._details_button.connect('clicked', 228 self.__hide_details_cb) 229 230 def __view_details_cb(self, widget): 231 if self._group_detail is None: 232 return 233 234 last_operation_value = friends_model._get_last_group_operation(self._group_name) 235 236 self._last_operation_box = gtk.VBox() 237 self._last_operation_box.pack_start(LeftAlignedLabelWidget(_('Last' 238 ' Operation On This Group :: ')), expand=False) 239 self._last_operation_box.pack_start(CenterAlignedLabelWidget( 240 last_operation_value), expand=False) 241 self._last_operation_box.show_all() 242 243 self._details_table.pack_start(self._last_operation_box, 244 expand=False) 245 246 self._container = gtk.Table() 247 self._details_table.pack_start(self._container, expand=False) 248 249 headings_list = [_('Nick'), _('Last Operation Status')] 250 for i in range(0, len(headings_list)): 251 self._container.attach(gtk.Label(headings_list[i]), i, i+1, 252 0, 1) 253 for i in range(0, len(headings_list)): 254 self._container.attach(gtk.Label(''), i, i+1, 1, 2) 255 256 index = 2 257 friend_keys_of_group = \ 258 friends_model._get_friend_keys_of_group(self._group_name) 259 260 for friend_key in friend_keys_of_group: 261 friend_model = friends_model._get_friend_by_key(friend_key) 262 label_widgets = [] 263 nick = friend_model.get_nick() 264 label_widgets.append(CenterAlignedLabelWidget(nick)) 265 266 last_operation_status = \ 267 friends_model._get_last_operation_status_of_friend_in_group(self._group_name, 268 friend_key) 269 label_widgets.append(CenterAlignedLabelWidget(last_operation_status)) 270 271 for i in range(0, len(label_widgets)): 272 self._container.attach(label_widgets[i], i, i+1, index, 273 index+1) 274 index = index + 1 275 276 self._container.show_all() 277 278 self._details_button.set_label(_('Hide Details')) 279 self.__activate_hide_id() 280 281 def __hide_details_cb(self, widget): 282 self._details_table.remove(self._last_operation_box) 283 self._details_table.remove(self._container) 284 285 self._details_button.set_label(_('View Details')) 286 self.__activate_view_id() 287 288 289 class MultiWidget(gtk.VBox): 290 291 def __init__(self): 292 gtk.VBox.__init__(self) 293 self.set_spacing(10) 294 295 def _add_widget(self, label, metadata): 296 new_widget = AddRemoveWidget(label, 297 metadata, 298 self.__add_button_clicked_cb, 299 self.__remove_button_clicked_cb, 300 len(self.get_children())) 301 self.add(new_widget) 302 self.show_all() 303 self._update_remove_button_statuses() 304 305 def _add_blank_entry(self): 306 self._add_widget('', None) 307 308 def __add_button_clicked_cb(self, add_button, 309 add_button_container): 310 self._add_blank_entry() 311 self._update_remove_button_statuses() 312 313 def __remove_button_clicked_cb(self, remove_button, 314 remove_button_container): 315 # Remove group from the model. 316 group_name = remove_button_container._get_entry() 317 friends_model.remove_group(group_name) 318 319 # Remove group from the view. 320 self.remove(remove_button_container) 321 322 self._update_remove_button_statuses() 323 324 def _update_remove_button_statuses(self): 325 children = self.get_children() 326 327 # Now, if there is only one entry, remove-button 328 # should not be shown. 329 if len(children) == 1: 330 children[0]._remove_remove_button_if_not_already() 331 332 # Alternatively, if there are more than 1 entries, 333 # remove-button should be shown for all. 334 if len(children) > 1: 335 for child in children: 336 child._add_remove_button_if_not_already() 337 338 def set_groups(self, groups): 339 self._groups = groups 340 341 def _pre_save_operations(self): 342 for child in self.get_children(): 343 if child._potential_new_group: 344 group_name = child._get_entry() 345 if len(group_name) > 0: 346 friends_model.add_group(group_name, False) 347 348 # Also, add all the selected buddies as friends. 349 buddy_widgets = child._get_buddy_widgets() 350 for widget in buddy_widgets: 351 if widget._is_buddy_selected(): 352 # Add as friend. 353 friends_model.make_friend_by_parameters( 354 widget._get_buddy_key(), 355 widget._get_buddy_nick(), 356 widget._get_buddy_account(), 357 widget._get_buddy_contact_id()) 358 359 # Add as friend in group. 360 friends_model.add_friend_to_group(widget._get_buddy_key(), 361 group_name, 362 False) 363 364 # Perform just one disk-write for groups. 365 friends_model.save_groups() 366 367 368 class Configuration(SectionView): 369 def __init__(self, model, alerts=None): 370 SectionView.__init__(self) 371 372 self._model = model 373 374 self.set_border_width(style.DEFAULT_SPACING * 2) 375 self.set_spacing(style.DEFAULT_SPACING) 376 group = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) 377 378 workspace = gtk.VBox() 379 workspace.show() 380 381 separator = gtk.HSeparator() 382 workspace.pack_start(separator, expand=False) 383 384 label_friend_groups = gtk.Label(_('Groups configuration')) 385 label_friend_groups.set_alignment(0, 0) 386 workspace.pack_start(label_friend_groups, expand=False) 387 388 box_friend_groups = gtk.VBox() 389 box_friend_groups.set_border_width(style.DEFAULT_SPACING * 2) 390 box_friend_groups.set_spacing(style.DEFAULT_SPACING) 391 392 self._widget_table = MultiWidget() 393 box_friend_groups.pack_start(self._widget_table, expand=False) 394 395 save_button = gtk.Button() 396 save_button.set_alignment(0, 0) 397 save_button.set_label('Save') 398 save_button.connect('clicked', self.__save_button_clicked_cb) 399 box_save_button = gtk.HBox() 400 box_save_button.set_homogeneous(False) 401 box_save_button.pack_start(save_button, expand=False) 402 box_save_button.show_all() 403 404 box_friend_groups.pack_start(box_save_button, expand=False) 405 406 box_friend_groups.show_all() 407 workspace.pack_start(box_friend_groups, expand=False) 408 409 scrolled = gtk.ScrolledWindow() 410 scrolled.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) 411 scrolled.add_with_viewport(workspace) 412 scrolled.show() 413 self.add(scrolled) 414 415 workspace.show_all() 416 self.setup() 417 418 def setup(self): 419 groups = friends_model._get_groups() 420 groups.sort() 421 422 if len(groups) == 0: 423 self._widget_table._add_blank_entry() 424 else: 425 for group_name in groups: 426 group_detail = \ 427 friends_model._get_group_by_key_name(group_name) 428 self._widget_table._add_widget(group_name, group_detail) 429 430 def __save_button_clicked_cb(self, save_button): 431 save_button.set_sensitive(False) 432 self._widget_table._pre_save_operations() -
src/jarabe/frame/activitiestray.py
diff --git a/src/jarabe/frame/activitiestray.py b/src/jarabe/frame/activitiestray.py index 941b174..d2902be 100644
a b class BaseTransferButton(ToolButton): 354 354 def remove(self): 355 355 frame = jarabe.frame.get_view() 356 356 frame.remove_notification(self.notif_icon) 357 self.props.parent.remove(self) 357 if (self.props.parent is not None) and \ 358 (self in self.props.parent.get_children()): 359 self.props.parent.remove(self) 358 360 359 361 def __notify_state_cb(self, file_transfer, pspec): 360 362 logging.debug('_update state: %r %r', file_transfer.props.state, … … class OutgoingTransferButton(BaseTransferButton): 471 473 frame.add_notification(self.notif_icon, 472 474 gtk.CORNER_TOP_LEFT) 473 475 476 # TODO: figure out why this is necessary to do. 477 # if this step is not done, then invoking 478 # "__dismiss_clicked_cb()" WITHOUT clicking the "Dismiss" 479 # option (as in the case of auto-dismiss for bulk 480 # operations), DOES NOT WORK. 481 self.create_palette() 482 474 483 def create_palette(self): 475 484 palette = OutgoingTransferPalette(self.file_transfer) 476 485 palette.connect('dismiss-clicked', self.__dismiss_clicked_cb) … … class OutgoingTransferButton(BaseTransferButton): 480 489 481 490 def __dismiss_clicked_cb(self, palette): 482 491 self.remove() 492 bulk_operation_details = \ 493 self.file_transfer._get_bulk_operation_details() 494 if bulk_operation_details is not None: 495 group_name = bulk_operation_details._get_group_name() 496 friend_keys = bulk_operation_details._get_friend_keys() 497 counter = bulk_operation_details._get_counter() 498 proceed_cb = bulk_operation_details._get_proceed_cb() 499 proceed_cb(group_name, friend_keys, counter) 483 500 484 501 485 502 class BaseTransferPalette(Palette): … … class IncomingTransferPalette(BaseTransferPalette): 565 582 566 583 def _update(self): 567 584 logging.debug('_update state: %r', self.file_transfer.props.state) 585 568 586 if self.file_transfer.props.state == filetransfer.FT_STATE_PENDING: 569 587 menu_item = MenuItem(_('Accept'), icon_name='dialog-ok') 570 588 menu_item.connect('activate', self.__accept_activate_cb) … … class IncomingTransferPalette(BaseTransferPalette): 595 613 596 614 elif self.file_transfer.props.state in \ 597 615 [filetransfer.FT_STATE_ACCEPTED, filetransfer.FT_STATE_OPEN]: 598 599 616 for item in self.menu.get_children(): 600 617 self.menu.remove(item) 601 618 … … class IncomingTransferPalette(BaseTransferPalette): 619 636 self.update_progress() 620 637 621 638 elif self.file_transfer.props.state == filetransfer.FT_STATE_COMPLETED: 622 623 639 for item in self.menu.get_children(): 624 640 self.menu.remove(item) 625 641 … … class IncomingTransferPalette(BaseTransferPalette): 629 645 menu_item.show() 630 646 631 647 self.update_progress() 632 elif self.file_transfer.props.state == filetransfer.FT_STATE_CANCELLED:633 648 649 elif self.file_transfer.props.state == filetransfer.FT_STATE_CANCELLED: 634 650 for item in self.menu.get_children(): 635 651 self.menu.remove(item) 636 652 … … class OutgoingTransferPalette(BaseTransferPalette): 684 700 685 701 self.progress_bar = None 686 702 self.progress_label = None 703 self._bulk_operation_details = \ 704 file_transfer._get_bulk_operation_details() 687 705 688 706 self.file_transfer.connect('notify::state', self.__notify_state_cb) 689 707 690 708 nick = str(file_transfer.buddy.props.nick) 691 label = glib.markup_escape_text(_('Transfer to %s') % (nick,)) 709 710 label = None 711 if self._bulk_operation_details is None: 712 label = glib.markup_escape_text(_('Transfer to %s') % (nick,)) 713 else: 714 counter = self._bulk_operation_details._get_counter() 715 total = self._bulk_operation_details._get_total() 716 label = glib.markup_escape_text(_('( %d / %d ) Transfer to %s') \ 717 % (counter, total, nick,)) 692 718 self.props.secondary_text = label 693 719 694 720 self._update() … … class OutgoingTransferPalette(BaseTransferPalette): 699 725 def _update(self): 700 726 new_state = self.file_transfer.props.state 701 727 logging.debug('_update state: %r', new_state) 702 if new_state == filetransfer.FT_STATE_PENDING:703 728 729 if new_state == filetransfer.FT_STATE_PENDING: 704 730 menu_item = MenuItem(_('Cancel'), icon_name='dialog-cancel') 705 731 menu_item.connect('activate', self.__cancel_activate_cb) 706 732 self.menu.append(menu_item) … … class OutgoingTransferPalette(BaseTransferPalette): 725 751 726 752 elif new_state in [filetransfer.FT_STATE_ACCEPTED, 727 753 filetransfer.FT_STATE_OPEN]: 728 729 754 for item in self.menu.get_children(): 730 755 self.menu.remove(item) 731 756 … … class OutgoingTransferPalette(BaseTransferPalette): 750 775 751 776 elif new_state in [filetransfer.FT_STATE_COMPLETED, 752 777 filetransfer.FT_STATE_CANCELLED]: 753 754 778 for item in self.menu.get_children(): 755 779 self.menu.remove(item) 756 780 … … class OutgoingTransferPalette(BaseTransferPalette): 758 782 menu_item.connect('activate', self.__dismiss_activate_cb) 759 783 self.menu.append(menu_item) 760 784 menu_item.show() 761 762 785 self.update_progress() 763 786 787 # Perform actions specific to bulk-operation. 788 if self._bulk_operation_details is not None: 789 790 # Update the peration status. 791 status = None 792 if new_state == filetransfer.FT_STATE_COMPLETED: 793 status = _('Success.') 794 elif new_state == filetransfer.FT_STATE_CANCELLED: 795 if self.file_transfer.reason_last_change == \ 796 filetransfer.FT_REASON_REMOTE_STOPPED: 797 status = _('FAILURE:\tOperation Cancelled Remotely.') 798 elif self.file_transfer.reason_last_change == \ 799 filetransfer.FT_REASON_LOCAL_STOPPED: 800 status = _('FAILURE:\tOperation Cancelled Locally.') 801 802 self._set_bulk_operation_status_if_applicable(status) 803 804 # "Dismiss' automatically for all, except the last of 805 # the bulk-operation. 806 counter = self._bulk_operation_details._get_counter() 807 total = self._bulk_operation_details._get_total() 808 if counter < total: 809 self.__dismiss_activate_cb(None) 810 811 def _set_bulk_operation_status_if_applicable(self, status): 812 if self._bulk_operation_details is not None: 813 self._bulk_operation_details._set_operation_status(status) 814 764 815 def __cancel_activate_cb(self, menu_item): 765 816 self.file_transfer.cancel() 766 817 -
src/jarabe/journal/palettes.py
diff --git a/src/jarabe/journal/palettes.py b/src/jarabe/journal/palettes.py index 8fc1e5d..e1d1e7d 100644
a b from jarabe.model import mimeregistry 37 37 from jarabe.journal import misc 38 38 from jarabe.journal import model 39 39 40 friends_model = friends.get_model() 41 42 43 class BulkOperationDetails(): 44 45 def __init__(self, group_name, friend, friend_keys, total, counter, proceed_cb): 46 self._group_name = group_name 47 self._friend = friend 48 self._friend_keys = friend_keys 49 self._counter = counter 50 self._total = total 51 self._proceed_cb = proceed_cb 52 53 def _get_group_name(self): 54 return self._group_name 55 56 def _get_friend_keys(self): 57 return self._friend_keys 58 59 def _get_counter(self): 60 return self._counter 61 62 def _get_total(self): 63 return self._total 64 65 def _get_proceed_cb(self): 66 return self._proceed_cb 67 68 def _set_operation_status(self, status): 69 friends_model._set_last_operation_status_of_friend_in_group(self._group_name, 70 self._friend.get_key(), 71 status) 72 40 73 41 74 class ObjectPalette(Palette): 42 75 … … class ObjectPalette(Palette): 117 150 friends_menu.connect('friend-selected', self.__friend_selected_cb) 118 151 menu_item.set_submenu(friends_menu) 119 152 153 groups_menu = GroupsMenu() 154 groups_menu.connect('group-selected', self.__group_selected_cb) 155 friends_menu._set_group_menu(groups_menu) 156 120 157 if detail == True: 121 158 menu_item = MenuItem(_('View Details'), 'go-right') 122 159 menu_item.connect('activate', self.__detail_activate_cb) … … class ObjectPalette(Palette): 150 187 def __volume_error_cb(self, menu_item, message, severity): 151 188 self.emit('volume-error', message, severity) 152 189 153 def __friend_selected_cb(self, menu_item, buddy): 190 def __friend_selected_cb(self, menu_item, buddy, 191 bulk_operation_details=None): 154 192 logging.debug('__friend_selected_cb') 155 193 file_name = model.get_file(self._metadata['uid']) 156 194 … … class ObjectPalette(Palette): 167 205 168 206 if not mime_type: 169 207 mime_type = mime.get_for_file(file_name) 170 171 208 filetransfer.start_transfer(buddy, file_name, title, description, 172 mime_type) 209 mime_type, bulk_operation_details) 210 211 def __group_selected_cb(self, menu_item, group_name): 212 logging.debug('__group_selected_cb') 213 if group_name is not None: 214 friends_model._set_last_group_operation(group_name, 215 _('(TRANSFER) %s') % (self._metadata['title'],)) 216 friends_model._set_last_operation_status_of_friends_in_group_with_common_status( 217 group_name, _('PENDING ...')) 218 friend_keys = \ 219 friends_model._get_friend_keys_of_group(group_name) 220 221 self._proceed_with_next_friend(group_name, friend_keys, 0) 222 223 """ 224 This is the (callback) function that needs to be called per friend. 225 Note that this function is a callback (and not a looped one), since 226 the "next" friend iteration begins, only when the current iteration 227 has finished - which is asynchronous. 228 """ 229 def _proceed_with_next_friend(self, group_name, friend_keys, counter): 230 counter = counter + 1 231 232 if counter <= len(friend_keys): 233 friend_key = friend_keys[counter-1] 234 friend = friends_model._get_friend_by_key(friend_key) 235 236 bulk_operation_details = \ 237 BulkOperationDetails(group_name, 238 friend, 239 friend_keys, 240 len(friend_keys), 241 counter, 242 self._proceed_with_next_friend) 243 244 # Only proceed if the friend is online. 245 # Else, set the failure-status, and move forward. 246 if friend.is_present(): 247 self.__friend_selected_cb(None, friend, bulk_operation_details) 248 else: 249 bulk_operation_details._set_operation_status(_('FAILURE:\tFriend' 250 ' is offline.')) 251 self._proceed_with_next_friend(group_name, friend_keys, 252 counter) 173 253 174 254 175 255 class CopyMenu(gtk.Menu): … … class ClipboardMenu(MenuItem): 295 375 self._temp_file_path = None 296 376 297 377 378 class GroupsMenu(gtk.Menu): 379 __gtype_name__ = 'GroupsMenu' 380 381 __gsignals__ = { 382 'group-selected': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, 383 ([object])), 384 } 385 386 def __init__(self): 387 gobject.GObject.__init__(self) 388 389 if filetransfer.file_transfer_available(): 390 for group in friends_model._get_groups(): 391 menu_item = MenuItem(text_label=group, 392 icon_name='zoom-groups') 393 menu_item.connect('activate', self.__item_activate_cb, 394 group) 395 self.append(menu_item) 396 menu_item.show() 397 398 if not self.get_children(): 399 menu_item = MenuItem(_('No groups present')) 400 menu_item.set_sensitive(False) 401 self.append(menu_item) 402 menu_item.show() 403 else: 404 menu_item = MenuItem(_('No valid connection found')) 405 menu_item.set_sensitive(False) 406 self.append(menu_item) 407 menu_item.show() 408 409 def __item_activate_cb(self, menu_item, group): 410 self.emit('group-selected', group) 411 412 298 413 class FriendsMenu(gtk.Menu): 299 414 __gtype_name__ = 'JournalFriendsMenu' 300 415 … … class FriendsMenu(gtk.Menu): 307 422 gobject.GObject.__init__(self) 308 423 309 424 if filetransfer.file_transfer_available(): 310 friends_model = friends.get_model()311 425 for friend in friends_model: 312 426 if friend.is_present(): 313 427 menu_item = MenuItem(text_label=friend.get_nick(), … … class FriendsMenu(gtk.Menu): 332 446 def __item_activate_cb(self, menu_item, friend): 333 447 self.emit('friend-selected', friend) 334 448 449 def _set_group_menu(self, group_menu): 450 menu_item = MenuItem(_('Select a group, to send')) 451 menu_item.set_submenu(group_menu) 452 self.append(menu_item) 453 menu_item.show() 454 335 455 336 456 class StartWithMenu(gtk.Menu): 337 457 __gtype_name__ = 'JournalStartWithMenu' -
src/jarabe/model/buddy.py
diff --git a/src/jarabe/model/buddy.py b/src/jarabe/model/buddy.py index 8f17d7e..c6c6a4c 100644
a b class BaseBuddyModel(gobject.GObject): 43 43 self._color = None 44 44 self._tags = None 45 45 self._current_activity = None 46 self._groups = None 46 47 47 48 gobject.GObject.__init__(self, **kwargs) 48 49 … … class BaseBuddyModel(gobject.GObject): 87 88 getter=get_current_activity, 88 89 setter=set_current_activity) 89 90 91 def get_groups(self): 92 return self._groups 93 94 def set_groups(self, groups): 95 self._groups = groups 96 97 groups = gobject.property(type=object, 98 getter=get_groups, 99 setter=set_groups) 100 90 101 def is_owner(self): 91 102 raise NotImplementedError 92 103 … … class BuddyModel(BaseBuddyModel): 179 190 self._account = None 180 191 self._contact_id = None 181 192 self._handle = None 193 self._groups_list = [] 182 194 183 195 BaseBuddyModel.__init__(self, **kwargs) 184 196 -
src/jarabe/model/filetransfer.py
diff --git a/src/jarabe/model/filetransfer.py b/src/jarabe/model/filetransfer.py index 447a74a..3de8c20 100644
a b class BaseFileTransfer(gobject.GObject): 124 124 self.mime_type = None 125 125 self.initial_offset = 0 126 126 self.reason_last_change = FT_REASON_NONE 127 self._bulk_operation_details = None 127 128 128 129 def set_channel(self, channel): 129 130 self.channel = channel … … class BaseFileTransfer(gobject.GObject): 156 157 def _get_transferred_bytes(self): 157 158 return self._transferred_bytes 158 159 160 def _get_bulk_operation_details(self): 161 return self._bulk_operation_details 162 163 def _set_bulk_operation_details(self, bulk_operation_details): 164 self._bulk_operation_details = bulk_operation_details 165 159 166 transferred_bytes = gobject.property(type=int, default=0, 160 167 getter=_get_transferred_bytes, setter=_set_transferred_bytes) 161 168 … … class IncomingFileTransfer(BaseFileTransfer): 226 233 227 234 228 235 class OutgoingFileTransfer(BaseFileTransfer): 229 def __init__(self, buddy, file_name, title, description, mime_type): 236 def __init__(self, buddy, file_name, title, description, mime_type, 237 bulk_operation_details): 230 238 231 239 presence_service = presenceservice.get_instance() 232 240 name, path = presence_service.get_preferred_connection() … … class OutgoingFileTransfer(BaseFileTransfer): 241 249 self._socket = None 242 250 self._splicer = None 243 251 self._output_stream = None 252 self._set_bulk_operation_details(bulk_operation_details) 244 253 245 254 self.buddy = buddy 246 255 self.title = title … … def init(): 324 333 _monitor_connection(connection) 325 334 326 335 327 def start_transfer(buddy, file_name, title, description, mime_type): 336 def start_transfer(buddy, file_name, title, description, mime_type, 337 bulk_operation_details): 328 338 outgoing_file_transfer = OutgoingFileTransfer(buddy, file_name, title, 329 description, mime_type) 339 description, 340 mime_type, 341 bulk_operation_details) 330 342 new_file_transfer.send(None, file_transfer=outgoing_file_transfer) 331 343 332 344 -
src/jarabe/model/friends.py
diff --git a/src/jarabe/model/friends.py b/src/jarabe/model/friends.py index 7605af1..448a36e 100644
a b 14 14 # along with this program; if not, write to the Free Software 15 15 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 16 16 17 from gettext import gettext as _ 18 17 19 import os 18 20 import logging 19 21 from ConfigParser import ConfigParser … … import gobject 22 24 import dbus 23 25 24 26 from sugar import env 27 from sugar.util import unique_id 25 28 from sugar.graphics.xocolor import XoColor 26 29 27 30 from jarabe.model.buddy import BuddyModel … … class FriendBuddyModel(BuddyModel): 36 39 37 40 _NOT_PRESENT_COLOR = '#D5D5D5,#FFFFFF' 38 41 39 def __init__(self, nick, key, account=None, contact_id=None ):42 def __init__(self, nick, key, account=None, contact_id=None, groups=[]): 40 43 self._online_buddy = None 41 44 42 45 BuddyModel.__init__(self, nick=nick, key=key, account=account, 43 contact_id=contact_id )46 contact_id=contact_id, groups=groups) 44 47 45 48 neighborhood_model = neighborhood.get_model() 46 49 neighborhood_model.connect('buddy-added', self.__buddy_added_cb) … … class Friends(gobject.GObject): 110 113 111 114 def __init__(self): 112 115 gobject.GObject.__init__(self) 113 114 self._friends = {}115 116 self._path = os.path.join(env.get_profile_path(), 'friends') 117 self._groups_path = os.path.join(env.get_profile_path(), 'groups') 118 self.reinit() 116 119 120 def reinit(self): 121 self._friends = {} 122 self._groups = {} 117 123 self.load() 118 124 119 125 def has_buddy(self, buddy): 120 return buddy.get_key() in self._friends 126 return self.check_buddy_existence_by_key(buddy.get_key()) 127 128 """ 129 Ideally, this should be the only publically exposed API, since 130 only the buddy_key is the deciding factor of the existence of 131 a friend. There can never be two friends of the same key. 132 """ 133 def check_buddy_existence_by_key(self, buddy_key): 134 return buddy_key in self._friends 121 135 122 136 def add_friend(self, buddy_info): 123 137 self._friends[buddy_info.get_key()] = buddy_info 124 138 self.emit('friend-added', buddy_info) 125 139 126 def make_friend(self, buddy): 127 if not self.has_buddy(buddy): 128 buddy = FriendBuddyModel(key=buddy.key, nick=buddy.nick, 129 account=buddy.account, 130 contact_id=buddy.contact_id) 140 def add_friend_to_group(self, buddy_key, group_name, 141 save_group=True): 142 if not self.__group_exists(group_name): 143 return _('Cannot add to group !! Create this group first !!') 144 145 if self.__friend_exists_in_group(group_name, buddy_key): 146 return _('Friend already exists in this group') 147 148 # If we reach here, it is safe to add this buddy :) 149 self._groups[group_name]['friends'][buddy_key] = {} 150 151 # Also, update the association from the buddy side, and save. 152 groups = self._get_groups_of_a_friend(buddy_key) 153 if group_name not in groups: 154 groups.append(group_name) 155 groups.sort() 156 self.save() 157 158 if save_group: 159 self.save_groups() 160 161 def remove_friend_group_assoc_using_friend_key(self, group_name, 162 friend_key, save_friend=True, save_group=True): 163 if not self.__group_exists(group_name): 164 return _('No such group exists !!') 165 166 # Remove association from friend. 167 if group_name in self._friends[friend_key].get_groups(): 168 self._friends[friend_key].get_groups().remove(group_name) 169 170 # Remove association from group. 171 if friend_key in self._groups[group_name]['friends'].keys(): 172 del self._groups[group_name]['friends'][friend_key] 173 174 if save_friend: 175 self.save() 176 if save_group: 177 self.save_groups() 178 179 def _get_groups_of_a_friend(self, friend_key): 180 if friend_key not in self._friends.keys(): 181 return [] 182 183 return self._friends[friend_key].get_groups() 184 185 def add_group(self, group_name, save_group=True): 186 if self.__group_exists(group_name): 187 return _('A group with the same name already exists !!' 188 'Choose a different group-name.') 189 190 # If we reach here, it is safe to add a group of this name. 191 self._groups[group_name] = {} 192 self._groups[group_name]['friends'] = {} 193 self._groups[group_name]['last-operation'] = 'NONE.' 194 if save_group: 195 self.save_groups() 196 197 def remove_group(self, group_name, save_friend=True, 198 save_group=True): 199 if not self.__group_exists(group_name): 200 return _('No group exists of this name !!') 201 202 # Remove this group, from all the friend-associations. 203 for friend_key in self._friends.keys(): 204 friend_groups_list = self._friends[friend_key].get_groups() 205 if group_name in friend_groups_list: 206 friend_groups_list.remove(group_name) 207 if save_friend: 208 self.save() 209 210 # Remove the group itself. 211 del self._groups[group_name] 212 if save_group: 213 self.save_groups() 214 215 def __group_exists(self, group_name): 216 if group_name in self._groups.keys(): 217 return True 218 return False 219 220 def __friend_exists_in_group(self, group_name, friend_key): 221 if friend_key in \ 222 self._groups[group_name]['friends'].keys(): 223 return True 224 return False 225 226 """ 227 This method is useful in the case, when the buddy is to be added as 228 a friend in deferred sense. For example, the buddies list may first be 229 fetched by virtue of their being online, but may go offline by the 230 time they are about to be saved. So, in that case, we retrieve the 231 parameters that are required for saving in the early stages itself. 232 """ 233 def make_friend_by_parameters(self, buddy_key, buddy_nick, buddy_account, buddy_contact_id): 234 if not self.check_buddy_existence_by_key(buddy_key): 235 buddy = FriendBuddyModel(key=buddy_key, 236 nick=buddy_nick, 237 account=buddy_account, 238 contact_id=buddy_contact_id) 131 239 self.add_friend(buddy) 132 240 self.save() 133 241 242 def make_friend(self, buddy): 243 self.make_friend_by_parameters(buddy.key, buddy.nick, 244 buddy.account, buddy.contact_id) 245 246 def remove_groups_of_friend(self, buddy_info, save_friend=True, 247 save_group=True): 248 groups = self._get_groups_of_a_friend(buddy_info.get_key()) 249 for group in groups: 250 self.remove_friend_group_assoc_using_friend_key(group, 251 buddy_info.get_key(), False, False) 252 253 if save_friend: 254 self.save() 255 if save_group: 256 self.save_groups() 257 134 258 def remove(self, buddy_info): 259 # First remove its association from all its groups 260 self.remove_groups_of_friend(buddy_info, False, True) 261 262 # Now, remove the friend-entity itself. 135 263 del self._friends[buddy_info.get_key()] 136 264 self.save() 265 137 266 self.emit('friend-removed', buddy_info.get_key()) 138 267 139 268 def __iter__(self): 140 269 return self._friends.values().__iter__() 141 270 271 def _get_friend_by_key(self, key): 272 if not (key in self._friends.keys()): 273 return _('No friend found !!') 274 return self._friends[key] 275 276 def _get_friend_keys_of_group(self, group_name): 277 if not self.__group_exists(group_name): 278 return [] 279 280 return self._groups[group_name]['friends'].keys() 281 282 def _get_groups(self): 283 # Sascha's wonderful feedback: always export only the thing 284 # required. Here, we required only the group-names; so export 285 # just the keys 286 return self._groups.keys() 287 288 def _set_groups(self, groups): 289 self._groups = groups 290 291 def _get_group_by_key_name(self, group_name): 292 if not self.__group_exists(group_name): 293 return None 294 return self._groups[group_name] 295 296 def _get_last_group_operation(self, group_name): 297 if not self.__group_exists(group_name): 298 return _('No group of this name exists !!') 299 return self._groups[group_name]['last-operation'] 300 301 def _set_last_group_operation(self, group_name, operation): 302 if not self.__group_exists(group_name): 303 return _('No group exists.') 304 self._groups[group_name]['last-operation'] = operation 305 self.save_groups 306 307 def _get_last_operation_status_of_friend_in_group(self, group_name, 308 friend_key): 309 if not self.__group_exists(group_name): 310 return _('No group exists.') 311 312 if not self.__friend_exists_in_group(group_name, friend_key): 313 return _('Friend does not exist in group') 314 315 if 'last-operation-status' in \ 316 self._groups[group_name]['friends'][friend_key].keys(): 317 return self._groups[group_name]['friends'][friend_key]['last-operation-status'] 318 319 def _set_last_operation_status_of_friend_in_group(self, group_name, 320 friend_key, status, 321 save_group=True): 322 if not self.__group_exists(group_name): 323 return _('No group exists.') 324 325 if not self.__friend_exists_in_group(group_name, friend_key): 326 return _('Friend does not exist in group') 327 328 self._groups[group_name]['friends'][friend_key]['last-operation-status'] = \ 329 status 330 331 if save_group: 332 self.save_groups() 333 334 def _set_last_operation_status_of_friends_in_group_with_common_status( 335 self, group_name, status): 336 if not self.__group_exists(group_name): 337 return _('No group exists.') 338 339 friend_keys = self._get_friend_keys_of_group(group_name) 340 for friend_key in friend_keys: 341 self._set_last_operation_status_of_friend_in_group(group_name, 342 friend_key, 343 status, 344 False) 345 346 # Save the groups in one go. 347 self.save_groups() 348 142 349 def load(self): 143 350 cp = ConfigParser() 144 351 … … class Friends(gobject.GObject): 149 356 # HACK: don't screw up on old friends files 150 357 if len(key) < 20: 151 358 continue 152 buddy = FriendBuddyModel(key=key, nick=cp.get(key, 'nick')) 359 360 # Check for the existence of 'groups' option (for 361 # backwards compatability) 362 groups = [] 363 if cp.has_option(key, 'groups'): 364 groups = eval(cp.get(key, 'groups')) 365 366 buddy = FriendBuddyModel(key=key, nick=cp.get(key, 367 'nick'), groups=groups) 153 368 self.add_friend(buddy) 154 369 except Exception: 155 370 logging.exception('Error parsing friends file') 156 371 372 self.__load_groups() 373 374 def __load_groups(self): 375 cp = ConfigParser() 376 377 try: 378 success = cp.read([self._groups_path]) 379 if success: 380 for group_name in cp.sections(): 381 self._groups[group_name] = {} 382 self._groups[group_name]['friends'] = \ 383 eval(cp.get(group_name, 'friends')) 384 self._groups[group_name]['last-operation'] = \ 385 cp.get(group_name, 'last-operation') 386 except: 387 logging.exception('Error while loading config') 388 157 389 def save(self): 158 390 cp = ConfigParser() 159 391 … … class Friends(gobject.GObject): 161 393 section = friend.get_key() 162 394 cp.add_section(section) 163 395 cp.set(section, 'nick', friend.get_nick()) 396 cp.set(section, 'groups', friend.get_groups()) 164 397 165 398 fileobject = open(self._path, 'w') 166 399 cp.write(fileobject) 167 400 fileobject.close() 168 401 402 def save_groups(self): 403 cp = ConfigParser() 404 405 for group in self._groups.keys(): 406 cp.add_section(group) 407 cp.set(group, 'friends', self._groups[group]['friends']) 408 cp.set(group, 'last-operation', 409 self._groups[group]['last-operation']) 410 411 fileobject = open(self._groups_path, 'w') 412 cp.write(fileobject) 413 fileobject.close() 169 414 170 415 def get_model(): 171 416 global _model -
src/jarabe/view/buddymenu.py
diff --git a/src/jarabe/view/buddymenu.py b/src/jarabe/view/buddymenu.py index de5a772..544faeb 100644
a b import gtk 22 22 import gconf 23 23 import glib 24 24 import dbus 25 import gobject 25 26 26 27 from sugar.graphics.palette import Palette 27 28 from sugar.graphics.menuitem import MenuItem … … from jarabe.model.session import get_session_manager 33 34 from jarabe.controlpanel.gui import ControlPanel 34 35 import jarabe.desktop.homewindow 35 36 37 friends_model = friends.get_model() 38 39 class GroupsMenu(gtk.Menu): 40 __gtype_name__ = 'NeighbourhoodGroupsMenu' 41 42 __gsignals__ = { 43 'group-selected': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, 44 ([object])), 45 } 46 47 def __init__(self, groups, no_groups_label): 48 gobject.GObject.__init__(self) 49 50 for group in groups: 51 menu_item = MenuItem(text_label=group, icon_name='zoom-groups') 52 menu_item.connect('activate', self.__item_activate_cb, group) 53 self.append(menu_item) 54 menu_item.show() 55 56 if not self.get_children(): 57 menu_item = MenuItem(no_groups_label) 58 menu_item.set_sensitive(False) 59 self.append(menu_item) 60 menu_item.show() 61 62 def __item_activate_cb(self, menu_item, group): 63 self.emit('group-selected', group) 64 36 65 37 66 class BuddyMenu(Palette): 38 67 def __init__(self, buddy): … … class BuddyMenu(Palette): 64 93 65 94 def _add_buddy_items(self): 66 95 if friends.get_model().has_buddy(self._buddy): 67 menu_item = MenuItem(_('Remove friend'), 'list-remove') 96 menu_item = MenuItem(_('Remove friend, and its association' 97 ' from all groups'), 'list-remove') 68 98 menu_item.connect('activate', self._remove_friend_cb) 99 self.menu.append(menu_item) 69 100 else: 70 menu_item = MenuItem(_('Make friend'), 'list-add') 101 menu_item = MenuItem(_('Make friend (without associating' 102 ' to any group)'), 'list-add') 71 103 menu_item.connect('activate', self._make_friend_cb) 72 73 self.menu.append(menu_item) 74 menu_item.show() 104 self.menu.append(menu_item) 105 106 menu_item_2 = MenuItem(_('Make friend (if not already), and add to group'), 107 'list-add') 108 109 all_groups = friends_model._get_groups() 110 groups_of_friend = \ 111 friends_model._get_groups_of_a_friend(self._buddy.get_key()) 112 113 # Make a local copy of groups, of which the friend is not 114 # associated with. 115 groups_not_of_friend = [] 116 for group in all_groups: 117 groups_not_of_friend.append(group) 118 for group in groups_of_friend: 119 groups_not_of_friend.remove(group) 120 121 groups_of_friend.sort() 122 groups_not_of_friend.sort() 123 124 add_groups_menu = GroupsMenu(groups_not_of_friend, _('No groups to' 125 ' associate')) 126 add_groups_menu.connect('group-selected', 127 self.__make_friend_and_add_to_group) 128 self.menu.append(menu_item_2) 129 menu_item_2.set_submenu(add_groups_menu) 130 add_groups_menu.show() 131 132 menu_item_3 = MenuItem(_('Remove friend from group'), 'list-remove') 133 remove_groups_menu = GroupsMenu(groups_of_friend, _('No groups to' 134 ' disassociate')) 135 remove_groups_menu.connect('group-selected', 136 self.__remove_friend_from_group) 137 self.menu.append(menu_item_3) 138 menu_item_3.set_submenu(remove_groups_menu) 139 remove_groups_menu.show() 75 140 76 141 self._invite_menu = MenuItem('') 77 142 self._invite_menu.connect('activate', self._invite_friend_cb) 78 143 self.menu.append(self._invite_menu) 79 144 145 self.menu.show_all() 146 80 147 home_model = shell.get_model() 81 148 self._active_activity_changed_hid = home_model.connect( 82 149 'active-activity-changed', self._cur_activity_changed_cb) … … class BuddyMenu(Palette): 154 221 def __buddy_notify_nick_cb(self, buddy, pspec): 155 222 self.set_primary_text(glib.markup_escape_text(buddy.props.nick)) 156 223 157 def _make_friend_cb(self, menuitem ):224 def _make_friend_cb(self, menuitem=None): 158 225 friends.get_model().make_friend(self._buddy) 159 226 160 227 def _remove_friend_cb(self, menuitem): … … class BuddyMenu(Palette): 178 245 raise 179 246 else: 180 247 logging.error('Invite failed, activity service not ') 248 249 def __make_friend_and_add_to_group(self, menu_item, group): 250 self._make_friend_cb() 251 friends_model.add_friend_to_group(self._buddy.get_key(), group) 252 253 def __remove_friend_from_group(self, menu_item, group): 254 friends_model.remove_friend_group_assoc_using_friend_key(group, 255 self._buddy.get_key())