diff options
author | Simon Schampijer <simon@schampijer.de> | 2008-06-04 17:23:14 (GMT) |
---|---|---|
committer | Simon Schampijer <simon@schampijer.de> | 2008-06-04 17:23:14 (GMT) |
commit | 9ac7db79d130893116bf3fc063b867f9c8cfcf4e (patch) | |
tree | 4400a334589b9d206cd614c38ab3f46903e48cd7 | |
parent | 26209d51c3e1572215fc1621b3e41760ec0e4270 (diff) |
Landed the graphical frontend for the control panel
- use a MVC to split the model from the view
- the model is used by the command line interface
and the graphical frontend
- this commit depends on the sugar-toolkit commit
4bdddfd20d8cb3f76ff03ab196dd5229f166bd75
- added src/view/devices/speaker.py to POTFILES.in
27 files changed, 2562 insertions, 0 deletions
diff --git a/data/icons/Makefile.am b/data/icons/Makefile.am new file mode 100644 index 0000000..625e78b --- /dev/null +++ b/data/icons/Makefile.am @@ -0,0 +1,11 @@ +sugardir = $(pkgdatadir)/data/icons + +sugar_DATA = \ + module-about_me.svg \ + module-about_my_xo.svg \ + module-date_and_time.svg \ + module-frame.svg \ + module-language.svg \ + module-network.svg + +EXTRA_DIST = $(sugar_DATA) diff --git a/data/icons/module-about_me.svg b/data/icons/module-about_me.svg new file mode 100644 index 0000000..7abe926 --- /dev/null +++ b/data/icons/module-about_me.svg @@ -0,0 +1,7 @@ +<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [ + <!ENTITY stroke_color "#666666"> + <!ENTITY fill_color "#ffffff"> +]><svg enable-background="new 0 0 55 55" height="55px" id="Layer_1" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="module-about_x5F_me_1_"> + <path d="M33.359,35.101L43.46,45.201c0.752,0.75,1.217,1.784,1.217,2.932 c0,2.287-1.855,4.143-4.146,4.143c-1.145,0-2.178-0.463-2.932-1.211L27.498,40.963l-10.1,10.1c-0.75,0.75-1.787,1.211-2.933,1.211 c-2.285,0-4.143-1.854-4.143-4.141c0-1.146,0.465-2.184,1.212-2.934l10.104-10.101L11.535,24.997 c-0.747-0.749-1.212-1.785-1.212-2.93c0-2.289,1.854-4.145,4.146-4.145c1.143,0,2.18,0.465,2.93,1.214l10.099,10.101l10.101-10.102 c0.754-0.749,1.787-1.214,2.934-1.214c2.289,0,4.146,1.856,4.146,4.145c0,1.145-0.467,2.179-1.217,2.93L33.359,35.101z" fill="&fill_color;" stroke="&stroke_color;" stroke-width="3.5"/> + <circle cx="27.497" cy="10.849" fill="&fill_color;" r="8.122" stroke="&stroke_color;" stroke-width="3.5"/> +</g></svg>
\ No newline at end of file diff --git a/data/icons/module-about_my_xo.svg b/data/icons/module-about_my_xo.svg new file mode 100644 index 0000000..cf3528e --- /dev/null +++ b/data/icons/module-about_my_xo.svg @@ -0,0 +1,6 @@ +<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [ + <!ENTITY stroke_color "#666666"> + <!ENTITY fill_color "#ffffff"> +]><svg enable-background="new 0 0 55 55" height="55px" id="Layer_1" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="module-about_x5F_my_x5F_xo_1_"> + <path d="M52.957,40.602h0.002l-0.025-0.017 c-0.152-0.11-0.315-0.21-0.483-0.302l-12.204-7.605V8.667c0-1.624-1.316-2.943-2.941-2.943H6.291c-1.625,0-2.942,1.319-2.942,2.943 V35.08c0,1.1,0.61,2.045,1.503,2.551l-0.019,0.004L19.49,46.77c0.694,0.436,1.534,0.691,2.438,0.691h28.319 c2.362,0,4.296-1.74,4.296-3.865C54.543,42.391,53.923,41.312,52.957,40.602z M9.072,12.392c0-0.619,0.506-1.124,1.124-1.124H33.4 c0.617,0,1.123,0.505,1.123,1.124v16.561c0,0.617-0.506,1.126-1.123,1.126H10.196c-0.617,0-1.124-0.509-1.124-1.126V12.392z" display="inline" fill="&fill_color;" id="module-about_x5F_my_x5F_xo"/> +</g></svg>
\ No newline at end of file diff --git a/data/icons/module-date_and_time.svg b/data/icons/module-date_and_time.svg new file mode 100644 index 0000000..605dbeb --- /dev/null +++ b/data/icons/module-date_and_time.svg @@ -0,0 +1,19 @@ +<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [ + <!ENTITY stroke_color "#666666"> + <!ENTITY fill_color "#ffffff"> +]><svg enable-background="new 0 0 55 55" height="55px" id="Layer_1" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="module-date_x5F_and_x5F_time"> + <g display="inline"> + <defs> + <path d="M29.891,31.641h17.255c0.346-1.563,0.534-3.187,0.534-4.854c0-12.35-10.013-22.362-22.362-22.362 c-12.351,0-22.362,10.012-22.362,22.362c0,12.351,10.011,22.362,22.362,22.362c1.567,0,3.097-0.163,4.573-0.47V31.641z M26.286,28.242c-0.034,0.022-0.071,0.038-0.107,0.058c-0.064,0.037-0.127,0.075-0.196,0.104 c-0.039,0.016-0.079,0.023-0.118,0.036c-0.069,0.023-0.138,0.049-0.21,0.062c-0.048,0.01-0.098,0.01-0.147,0.015 c-0.063,0.007-0.126,0.02-0.191,0.02c-0.071,0-0.139-0.013-0.208-0.021c-0.043-0.006-0.086-0.005-0.129-0.014 c-0.078-0.015-0.152-0.041-0.226-0.066c-0.034-0.012-0.069-0.019-0.102-0.032c-0.077-0.031-0.147-0.072-0.217-0.113 c-0.028-0.017-0.059-0.028-0.086-0.047c-0.193-0.129-0.359-0.294-0.487-0.487c-0.028-0.042-0.047-0.086-0.071-0.13 c-0.031-0.057-0.065-0.111-0.09-0.171c-0.023-0.056-0.037-0.115-0.054-0.173c-0.015-0.051-0.035-0.101-0.045-0.153 c-0.023-0.114-0.035-0.229-0.035-0.344V10.349c0-0.966,0.783-1.75,1.75-1.75s1.75,0.784,1.75,1.75v12.212l3.973-3.973 c0.684-0.683,1.792-0.683,2.476,0c0.683,0.683,0.683,1.792,0,2.475l-6.96,6.96c-0.001,0.002-0.003,0.003-0.005,0.004 C26.469,28.107,26.381,28.179,26.286,28.242z" id="SVGID_5_"/> + </defs> + <clipPath id="SVGID_6_"> + <use overflow="visible" xlink:href="#SVGID_5_"/> + </clipPath> + <circle clip-path="url(#SVGID_6_)" cx="25.318" cy="26.786" fill="&fill_color;" r="22.362"/> + </g> + <rect display="inline" fill="none" height="19.319" stroke="&fill_color;" stroke-width="2" width="21.064" x="29.891" y="31.641"/> + <g display="inline"> + <path d="M39.056,44.155c0.527,0,0.936,0.239,0.936,0.792c0,0.551-0.408,0.791-0.864,0.791h-4.006 c-0.527,0-0.936-0.24-0.936-0.791c0-0.252,0.156-0.469,0.276-0.612c0.995-1.188,2.075-2.267,2.986-3.586 c0.216-0.312,0.42-0.684,0.42-1.115c0-0.491-0.372-0.924-0.863-0.924c-1.38,0-0.72,1.943-1.871,1.943 c-0.575,0-0.876-0.408-0.876-0.876c0-1.511,1.344-2.723,2.818-2.723c1.476,0,2.663,0.972,2.663,2.495 c0,1.667-1.858,3.322-2.878,4.605H39.056z" fill="&fill_color;"/> + <path d="M46.339,39.25c0,0.756-0.323,1.415-0.983,1.835c0.863,0.396,1.463,1.199,1.463,2.146 c0,1.439-1.318,2.651-3.021,2.651c-1.775,0-2.879-1.309-2.879-2.256c0-0.467,0.492-0.803,0.924-0.803 c0.815,0,0.623,1.402,1.979,1.402c0.623,0,1.127-0.479,1.127-1.115c0-1.679-2.039-0.443-2.039-1.858 c0-1.259,1.703-0.408,1.703-1.739c0-0.455-0.323-0.804-0.863-0.804c-1.139,0-0.982,1.176-1.799,1.176 c-0.492,0-0.779-0.444-0.779-0.888c0-0.936,1.283-1.943,2.614-1.943C45.512,37.055,46.339,38.314,46.339,39.25z" fill="&fill_color;"/> + </g> +</g></svg>
\ No newline at end of file diff --git a/data/icons/module-frame.svg b/data/icons/module-frame.svg new file mode 100644 index 0000000..caf4f6a --- /dev/null +++ b/data/icons/module-frame.svg @@ -0,0 +1,22 @@ +<?xml version="1.0" ?> +<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' + 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [ + <!ENTITY stroke_color "#666666"> + <!ENTITY fill_color "#ffffff"> + ]> +<svg enable-background="new 0 0 55 55" height="55px" id="Layer_1" + version="1.1" viewBox="0 0 55 55" width="55px" x="0px" + xml:space="preserve" xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"> + <g display="block" id="module-display"> + <g display="inline"> + <g> + <path d="M46.824,8.027H8.176c-3.581,0-6.51,2.929-6.51,6.509v25.926c0, + 3.58,2.929,6.511,6.51,6.511h38.648 c3.579,0,6.51-2.931, + 6.51-6.511V14.537C53.334,10.957,50.403, + 8.027,46.824,8.027z M45.978, + 39.962H9.282V11.575h36.695V39.962z" fill="&fill_color;"/> + </g> + </g> + </g> +</svg> diff --git a/data/icons/module-language.svg b/data/icons/module-language.svg new file mode 100644 index 0000000..ce04cb4 --- /dev/null +++ b/data/icons/module-language.svg @@ -0,0 +1,59 @@ +<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [ + <!ENTITY stroke_color "#666666"> + <!ENTITY fill_color "#ffffff"> +]><svg enable-background="new 0 0 55 55" height="55px" id="Layer_1" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="module-language"> + <g display="inline"> + <g> + <path clip-rule="evenodd" d="M35.805,13.962c-0.346-0.084-0.779-0.072-0.725-0.6 c0.24,0,0.482,0,0.725,0l-0.092,0.301L35.805,13.962z" fill="&fill_color;" fill-rule="evenodd"/> + <path clip-rule="evenodd" d="M36.438,14.063l0.09-0.3c1.146-0.067,1.871,0.331,2.533,0.8 c3.684,0.596,9.051-0.669,10.676,2.2c-0.164,0.485-0.727,0.53-1.268,0.6c0.531,0.215,0.637,0.898,0.543,1.8 c-0.928-0.174-1.295-0.968-1.447-2c-0.553,0.389-1.396,0.457-2.352,0.4c-0.121,0.867,0.84,0.539,1.447,0.6 c0.057,0.472,0.629,0.373,0.543,1l-0.271,0.1l0.09,0.3c-0.549,0.658-0.812,1.635-0.723,3c-0.551,0.01-0.703-0.422-1.086-0.6 c0.26,1.426,0.908,1.89,0.723,3.4c-1.1-1.013-1.793,1.516-1.99,2.799c-0.627,0.161-0.693-0.297-1.266-0.199l-0.182-0.1l-0.18,0.1 c-0.475-1.659-2.096-3.383-3.076-1c0,0.4,0,0.8,0,1.199l-0.271-0.1l0.09,0.301c-1.533,0.073-0.889-3.936-3.438-3.4 c-0.357,1.338-1.098,2.254-2.535,2.4c-0.164,0.784,0.955,0.145,1.086,0.6c-0.146,1.638-1.449,1.998-1.99,3.2 c0.646,2.376-0.838,2.551-0.902,4.601c-0.834,0.479-0.881,1.828-1.811,2.2c-3.492-0.651-1.328-6.379-3.258-8.601 c-0.848-0.263-1.984-0.205-3.075-0.2c-2.032-1.712-0.716-5.163,0.724-6.8c-0.372-0.323-0.419-1.003-0.362-1.801 c0.481-0.002,1.034,0.076,1.267-0.2c-0.148-0.703-0.412-1.279-1.267-1.201c0.081-0.912,0.522-1.423,1.267-1.6 c0.269,0.237,0.282,0.755,0.543,1c0.512,0.165,0.469-0.282,0.904-0.201c0.191-0.877-0.611-0.657-0.543-1.4 c0.357-0.828,1.436-1.889,2.354-2c1.213-0.147,1.916,0.831,3.617,0.6c0.695-0.094,1.172-0.681,2.172-1 c0.594-0.19,1.852,0.085,2.354-0.601L36.438,14.063z M27.842,17.963c0.438-0.392,0.416-1-0.18-1.2 C27.766,17.115,27.441,17.938,27.842,17.963z M27.48,21.563c-0.186-0.944-2.604-0.928-2.353,0.4c0.483,0,0.966,0,1.448,0 l0.09,0.3l0.271-0.101c0,0.066,0,0.134,0,0.201c0.442,1.067,3.484,1.27,3.98,0c-1.439,0.273-2.379-0.674-3.256-0.4l0.09-0.301 L27.48,21.563z M32.365,26.764c0.031-0.501-0.277-0.626-0.541-0.8C31.791,26.464,32.102,26.59,32.365,26.764z" fill="&fill_color;" fill-rule="evenodd"/> + <path clip-rule="evenodd" d="M48.018,21.863l0.09-0.3c0.316,0.184,0.4,0.625,0.361,1.2 c-0.547,0.261-1.043,0.579-1.627,0.8c-0.492-1.168,0.654-0.949,0.904-1.8L48.018,21.863z" fill="&fill_color;" fill-rule="evenodd"/> + <path clip-rule="evenodd" d="M42.77,28.864l0.092-0.301c0.662,0.467,0.711,1.615,0.904,2.601 l-0.332-0.1l-0.211,0.3c-0.619-0.582-0.996-1.434-1.268-2.4c0.146,0.105,0.295,0.209,0.543,0.2c0-0.134,0-0.267,0-0.399 L42.77,28.864z" fill="&fill_color;" fill-rule="evenodd"/> + <path clip-rule="evenodd" d="M11.376,25.163c0.423,0.067,0.78,0.206,0.905,0.601 l-0.271,0.233l0.09,0.367c-0.21-0.033-0.322,0.044-0.361,0.199c-0.465-0.02-0.291-0.744-0.543-1l0.271-0.1L11.376,25.163z" fill="&fill_color;" fill-rule="evenodd"/> + <path clip-rule="evenodd" d="M46.299,27.764c-0.281-0.29-0.381-0.78-0.363-1.399 c0.182,0,0.363,0,0.545,0c0.104,0.418,0.447,0.57,0.361,1.199l-0.332-0.1L46.299,27.764z" fill="&fill_color;" fill-rule="evenodd"/> + <path clip-rule="evenodd" d="M45.936,30.364c0.131,0.877-0.602,0.8-1.266,0.8 c-0.264-0.44-0.549-0.858-0.543-1.601c0.738-0.051,0.895-0.744,1.809-0.6c-0.057,0.465,0.029,0.768,0.182,1l-0.271,0.101 L45.936,30.364z" fill="&fill_color;" fill-rule="evenodd"/> + <path clip-rule="evenodd" d="M46.389,30.265l-0.09-0.301c0.42-0.063,0.693,0.033,0.904,0.2 c-0.332,0.501-0.145,1.574-1.086,1.4c0-0.4,0-0.8,0-1.2L46.389,30.265z" fill="&fill_color;" fill-rule="evenodd"/> + <path clip-rule="evenodd" d="M48.65,31.164c-0.553,0.146-0.531-0.345-1.086-0.199 c-0.086-0.716,1.123-1.144,1.268-0.4l-0.271,0.233L48.65,31.164z" fill="&fill_color;" fill-rule="evenodd"/> + <path clip-rule="evenodd" d="M49.104,30.931l-0.092-0.366c0.926-0.487,1.51,0.948,2.715,0.8 c-0.617,0.261-0.084,0.855-0.361,1.199" fill="&fill_color;" fill-rule="evenodd"/> + <path clip-rule="evenodd" d="M49.918,32.765c0.291,0.117,1.43,2.391,1.447,3 c0.045,1.674-1.633,3.377-2.715,3.201c-0.922-0.152-0.688-0.955-1.447-1.601c-0.98,0.12-1.854,0.957-2.895,0.399 c-0.109-1.053,0.133-1.718,0.18-2.6c1.658-0.205,3.107-3.646,4.705-1.4C49.775,33.807,49.633,33.051,49.918,32.765z" fill="&fill_color;" fill-rule="evenodd"/> + <path clip-rule="evenodd" d="M33.451,33.164c0.725,1.004,0.084,2.368-0.725,2.8 C32.002,34.962,32.742,33.707,33.451,33.164z" fill="&fill_color;" fill-rule="evenodd"/> + <path clip-rule="evenodd" d="M48.65,39.165c0.383,0.062,0.039,0.948-0.361,0.8 C48.082,39.336,48.572,39.479,48.65,39.165z" fill="&fill_color;" fill-rule="evenodd"/> + <path clip-rule="evenodd" d="M16.805,12.962c2.05,0,4.102,0,6.15,0 c0.007,0.195,0.223,0.155,0.363,0.2c0.324,0.701-0.65,1.677-1.084,2.2c-1.523,0.317-2.302,1.457-3.621,2 c-0.971-0.425-0.523-1.94-0.724-2.8c-0.439-0.464-2.139-0.687-2.532,0c1.822-0.026,1.732,2.399,0.18,2.6 c1.181-0.023,1.775,2.452,0.543,2.8l0.091-0.3l-0.272-0.1c-0.099-0.657-1.186,0.234-0.541,0.4l-0.092,0.3l0.271,0.101 c-2.093,0.085-2.461,2.079-3.98,2.799c0,0.201,0,0.4,0,0.601l-0.332-0.1l-0.21,0.3c-0.761-0.482-2.09-0.11-2.534,0.399 c0,0.534,0,1.067,0,1.601c0.622,0.076,1.287-0.828,1.628-0.4c-0.564,1.371,0.746,1.471,0.906,2.6l-0.272,0.201l0.272,0.199 c-0.433,0.062-1.751-0.617-2.534-1.199C7.746,26.816,7,25.966,6.673,25.163c-0.38-0.229-0.707,0.123-1.086-0.399 c-0.229-1.306-0.774-2.063-0.724-3.2c0.032-0.728,0.597-0.782,0.724-1.601c0.033-0.217-0.352-0.856-0.362-1.399 c-0.01-0.526,0.43-0.783,0-1.201c-1.454-0.274-1.845,0.627-3.076,0.6l0.09-0.199l-0.09-0.2c-0.231-0.521-0.397-0.604-0.182-1.2 c1.83-1.044,4.094-1.609,7.057-1.4c0.688-0.04,0.687-0.841,1.448-0.8c1.325-0.132,2.296,0.13,3.257,0.4 c0.705-0.021,0.311-1.256,0.904-1.4C15.303,13.035,16.436,13.422,16.805,12.962z M14.452,15.963 c0.286-0.02,0.628,0.027,0.542-0.4c-0.119,0-0.24,0-0.361,0C14.633,15.764,14.486,15.801,14.452,15.963z M11.92,17.763 c0.789-0.139,0.803,0.577,1.447,0.6c0.266-0.505,0.639-0.893,0.723-1.6C13.093,16.545,11.914,16.393,11.92,17.763z" fill="&fill_color;" fill-rule="evenodd"/> + <path clip-rule="evenodd" d="M11.467,28.364l-0.091-0.201 c1.866-1.496,4.541,0.503,5.247,2.201c0.822,0.426,2.057,0.393,2.352,1.4c-0.152,1.164-0.635,1.965-0.723,3.199 c-0.226,0.484-0.742,0.648-1.268,0.801c-0.201,2.777-2.827,3.017-2.17,6.201c0.453-0.035,0.566,0.307,0.723,0.6 c-0.36,0-0.723,0-1.085,0c-1.601-1.566-1.526-4.982-1.81-8.001c-1.498-1.305-2.468-3.841-1.267-6.001L11.467,28.364z" fill="&fill_color;" fill-rule="evenodd"/> + <g> + + <path clip-rule="evenodd" d=" M48.018,21.863" fill="&fill_color;" fill-rule="evenodd" stroke="#000000" stroke-linecap="round" stroke-width="2.1637"/> + + <path clip-rule="evenodd" d=" M46.932,19.263" fill="&fill_color;" fill-rule="evenodd" stroke="#000000" stroke-linecap="round" stroke-width="2.1637"/> + + <path clip-rule="evenodd" d=" M43.434,31.064" fill="&fill_color;" fill-rule="evenodd" stroke="#000000" stroke-linecap="round" stroke-width="2.8752"/> + + <path clip-rule="evenodd" d=" M42.498,27.864" fill="&fill_color;" fill-rule="evenodd" stroke="#000000" stroke-linecap="round" stroke-width="2.1506"/> + + <path clip-rule="evenodd" d=" M38.971,28.063" fill="&fill_color;" fill-rule="evenodd" stroke="#000000" stroke-linecap="round" stroke-width="1.2247"/> + + <path clip-rule="evenodd" d=" M26.851,21.793" fill="&fill_color;" fill-rule="evenodd" stroke="#000000" stroke-linecap="round" stroke-width="2.1448"/> + + <path clip-rule="evenodd" d=" M12.01,25.997" fill="&fill_color;" fill-rule="evenodd" stroke="#000000" stroke-linecap="round" stroke-width="2.8087"/> + + <path clip-rule="evenodd" d=" M46.51,27.464" fill="&fill_color;" fill-rule="evenodd" stroke="#000000" stroke-linecap="round" stroke-width="3.5214"/> + + <path clip-rule="evenodd" d=" M46.752,28.264" fill="&fill_color;" fill-rule="evenodd" stroke="#000000" stroke-linecap="round" stroke-width="3.5214"/> + + <path clip-rule="evenodd" d=" M45.846,30.064" fill="&fill_color;" fill-rule="evenodd" stroke="#000000" stroke-linecap="round" stroke-width="2.1602"/> + + <path clip-rule="evenodd" d=" M49.104,30.931" fill="&fill_color;" fill-rule="evenodd" stroke="#000000" stroke-linecap="round" stroke-width="3.1091"/> + + <path clip-rule="evenodd" d=" M46.752,32.165" fill="&fill_color;" fill-rule="evenodd" stroke="#000000" stroke-linecap="round" stroke-width="2.1381"/> + + <path clip-rule="evenodd" d=" M15.266,20.263" fill="&fill_color;" fill-rule="evenodd" stroke="#000000" stroke-linecap="round" stroke-width="1.6279"/> + + <path clip-rule="evenodd" d=" M11.225,23.664" fill="&fill_color;" fill-rule="evenodd" stroke="#000000" stroke-linecap="round" stroke-width="2.9155"/> + + <path clip-rule="evenodd" d=" M11.467,28.364" fill="&fill_color;" fill-rule="evenodd" stroke="#000000" stroke-linecap="round" stroke-width="2.0976"/> + + <path clip-rule="evenodd" d=" M10.743,28.364" fill="&fill_color;" fill-rule="evenodd" stroke="#000000" stroke-linecap="round" stroke-width="2.0976"/> + </g> + </g> + </g> + <rect display="inline" fill="none" height="30.334" stroke="&fill_color;" stroke-width="3.5" width="50.67" x="2.25" y="12.625"/> +</g></svg>
\ No newline at end of file diff --git a/data/icons/module-network.svg b/data/icons/module-network.svg new file mode 100644 index 0000000..a750a38 --- /dev/null +++ b/data/icons/module-network.svg @@ -0,0 +1,32 @@ +<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [ + <!ENTITY stroke_color "#666666"> + <!ENTITY fill_color "#ffffff"> +]><svg enable-background="new 0 0 55 55" height="55px" id="Layer_1" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="module-network"> + <g display="inline"> + <defs> + <path d="M14.897,34.339c0-10.144,8.224-18.367,18.367-18.367c3.929,0,7.562,1.244,10.549,3.345V0H0v43.812h17.55 C15.877,41.044,14.897,37.81,14.897,34.339z" id="SVGID_1_"/> + </defs> + <clipPath id="SVGID_2_"> + <use overflow="visible" xlink:href="#SVGID_1_"/> + </clipPath> + <g clip-path="url(#SVGID_2_)"> + + <circle cx="21.47" cy="21.073" fill="none" r="18.368" stroke="&fill_color;" stroke-linecap="round" stroke-linejoin="round" stroke-width="4"/> + + <circle cx="21.469" cy="21.073" fill="none" r="10.476" stroke="&fill_color;" stroke-linecap="round" stroke-linejoin="round" stroke-width="4"/> + <circle cx="21.469" cy="21.073" fill="&fill_color;" r="3.966"/> + </g> + </g> + <g display="inline"> + <g> + <defs> + <rect height="18.367" id="SVGID_3_" width="36.736" x="14.897" y="34.339"/> + </defs> + <clipPath id="SVGID_4_"> + <use overflow="visible" xlink:href="#SVGID_3_"/> + </clipPath> + <circle clip-path="url(#SVGID_4_)" cx="33.265" cy="34.339" fill="&fill_color;" r="18.368"/> + </g> + <circle cx="33.265" cy="34.339" fill="none" r="18.368" stroke="&fill_color;" stroke-width="3.5"/> + </g> +</g></svg>
\ No newline at end of file diff --git a/src/controlpanel/gui.py b/src/controlpanel/gui.py new file mode 100644 index 0000000..e51f9e3 --- /dev/null +++ b/src/controlpanel/gui.py @@ -0,0 +1,394 @@ +# Copyright (C) 2008 One Laptop Per Child +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import gtk +import os +import gobject +import logging +from gettext import gettext as _ + +from sugar.graphics.icon import Icon +from sugar.graphics import style +from sugar.graphics.alert import Alert +import config + +from controlpanel.toolbar import MainToolbar +from controlpanel.toolbar import SectionToolbar + +_logger = logging.getLogger('ControlPanel') +_MAX_COLUMNS = 5 + +class ControlPanel(gtk.Window): + __gtype_name__ = 'SugarControlPanel' + + def __init__(self): + gtk.Window.__init__(self) + + self.set_border_width(style.LINE_WIDTH) + offset = style.GRID_CELL_SIZE + width = gtk.gdk.screen_width() - offset * 2 + height = gtk.gdk.screen_height() - offset * 2 + self.set_size_request(width, height) + self.set_position(gtk.WIN_POS_CENTER_ALWAYS) + self.set_decorated(False) + self.set_resizable(False) + self.set_modal(True) + + self._toolbar = None + self._canvas = None + self._table = None + self._separator = None + self._section_view = None + self._section_toolbar = None + self._main_toolbar = None + + self._vbox = gtk.VBox() + self._hbox = gtk.HBox() + self._vbox.pack_start(self._hbox) + self._hbox.show() + + self._main_view = gtk.EventBox() + self._hbox.pack_start(self._main_view) + self._main_view.modify_bg(gtk.STATE_NORMAL, + style.COLOR_BLACK.get_gdk_color()) + self._main_view.show() + + self.add(self._vbox) + self._vbox.show() + + self.connect("realize", self.__realize_cb) + + self._options = self._get_options() + self._current_option = None + self._setup_main() + self._setup_section() + self._show_main_view() + + def __realize_cb(self, widget): + self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG) + self.window.set_accept_focus(True) + + def _set_canvas(self, canvas): + if self._canvas: + self._main_view.remove(self._canvas) + if canvas: + self._main_view.add(canvas) + self._canvas = canvas + + def _set_toolbar(self, toolbar): + if self._toolbar: + self._vbox.remove(self._toolbar) + self._vbox.pack_start(toolbar, False) + self._vbox.reorder_child(toolbar, 0) + self._toolbar = toolbar + if not self._separator: + self._separator = gtk.HSeparator() + self._vbox.pack_start(self._separator, False) + self._vbox.reorder_child(self._separator, 1) + self._separator.show() + + def _setup_main(self): + self._main_toolbar = MainToolbar() + + self._table = gtk.Table() + self._table.set_col_spacings(style.GRID_CELL_SIZE) + self._table.set_border_width(style.GRID_CELL_SIZE) + self._setup_options() + self._main_toolbar.connect('stop-clicked', + self.__stop_clicked_cb) + self._main_toolbar.connect('search-changed', + self.__search_changed_cb) + + def _setup_options(self): + row = 0 + column = 0 + for option in self._options: + sectionicon = _SectionIcon(icon_name=self._options[option]['icon'], + title=self._options[option]['title'], + xo_color=self._options[option]['color'], + pixel_size=style.GRID_CELL_SIZE) + sectionicon.connect('button_press_event', + self.__select_option_cb, option) + sectionicon.show() + + self._table.attach(sectionicon, column, column + 1, row, row + 1) + self._options[option]['button'] = sectionicon + + column += 1 + if column == _MAX_COLUMNS: + column = 0 + row += 1 + + def _show_main_view(self): + self._set_toolbar(self._main_toolbar) + self._main_toolbar.show() + self._set_canvas(self._table) + self._main_view.modify_bg(gtk.STATE_NORMAL, + style.COLOR_BLACK.get_gdk_color()) + self._table.show() + entry = self._main_toolbar.get_entry() + entry.grab_focus() + entry.set_text('') + + def _update(self, query): + for option in self._options: + found = False + for key in self._options[option]['keywords']: + if query.lower() in key.lower(): + self._options[option]['button'].set_sensitive(True) + found = True + break + if not found: + self._options[option]['button'].set_sensitive(False) + + def _setup_section(self): + self._section_toolbar = SectionToolbar() + self._section_toolbar.connect('cancel-clicked', + self.__cancel_clicked_cb) + self._section_toolbar.connect('accept-clicked', + self.__accept_clicked_cb) + + def _show_section_view(self, option): + self._set_toolbar(self._section_toolbar) + + icon = self._section_toolbar.get_icon() + icon.set_from_icon_name(self._options[option]['icon'], + gtk.ICON_SIZE_LARGE_TOOLBAR) + icon.props.xo_color = self._options[option]['color'] + title = self._section_toolbar.get_title() + title.set_text(self._options[option]['title']) + self._section_toolbar.show() + self._section_toolbar.accept_button.set_sensitive(True) + + self._current_option = option + view_class = self._options[option]['view'] + model = self._options[option]['model'] + self._section_view = view_class(model, + self._options[option]['alerts']) + self._set_canvas(self._section_view) + self._section_view.show() + self._section_view.connect('notify::is-valid', + self.__valid_section_cb) + self._main_view.modify_bg(gtk.STATE_NORMAL, + style.COLOR_WHITE.get_gdk_color()) + + def _get_options(self): + '''Get the available option information from the subfolders + model and view. + ''' + options = {} + + subpath = ['controlpanel', 'view'] + names = os.listdir(os.path.join(config.shell_path, '/'.join(subpath))) + + for name in names: + if name.endswith('.py') and name != '__init__.py': + tmp = name.strip('.py') + mod = __import__('.'.join(subpath) + '.' + tmp, globals(), + locals(), [tmp]) + view_class_str = getattr(mod, 'CLASS', None) + if view_class_str: + view_class = getattr(mod, view_class_str, None) + if not view_class: + _logger.error('The CLASS constant \'%s\' does not ' \ + 'match a class name.' % view_class) + else: + options[tmp] = {} + options[tmp]['alerts'] = [] + options[tmp]['view'] = view_class + options[tmp]['icon'] = getattr(mod, 'ICON', tmp) + options[tmp]['title'] = getattr(mod, 'TITLE', + tmp) + options[tmp]['color'] = getattr(mod, 'COLOR', + None) + else: + _logger.error('There is no CLASS constant specified in ' \ + 'the view file \'%s\'.' % tmp) + + subpath = ['controlpanel', 'model'] + names = os.listdir(os.path.join(config.shell_path, '/'.join(subpath))) + + for name in names: + if name.endswith('.py') and name != '__init__.py': + tmp = name.strip('.py') + if tmp in options: + mod = __import__('.'.join(subpath) + '.' + tmp, + globals(), locals(), [tmp]) + keywords = getattr(mod, 'KEYWORDS', []) + keywords.append(options[tmp]['title'].lower()) + if tmp not in keywords: + keywords.append(tmp) + options[tmp]['model'] = ModelWrapper(mod) + options[tmp]['keywords'] = keywords + + return options + + def __cancel_clicked_cb(self, widget): + self._section_view.undo() + self._options[self._current_option]['alerts'] = [] + self._show_main_view() + + def __accept_clicked_cb(self, widget): + if self._section_view.needs_restart: + self._section_toolbar.accept_button.set_sensitive(False) + alert = Alert() + alert.props.title = _('Warning') + alert.props.msg = _('Changes require restart to take effect') + + cancel_icon = Icon(icon_name='dialog-cancel') + alert.add_button(gtk.RESPONSE_CANCEL, _('Cancel changes'), + cancel_icon) + cancel_icon.show() + + later_icon = Icon(icon_name='dialog-ok') + alert.add_button(gtk.RESPONSE_ACCEPT, _('Later'), later_icon) + later_icon.show() + + # TODO + # Handle restart + + self._vbox.pack_start(alert, False) + self._vbox.reorder_child(alert, 2) + alert.connect('response', self.__response_cb) + alert.show() + else: + self._show_main_view() + + def __response_cb(self, alert, response_id): + self._vbox.remove(alert) + if response_id is gtk.RESPONSE_CANCEL: + self._section_view.undo() + self._section_view.setup() + self._options[self._current_option]['alerts'] = [] + self._section_toolbar.accept_button.set_sensitive(True) + elif response_id is gtk.RESPONSE_ACCEPT: + self._options[self._current_option]['alerts'] = \ + self._section_view.restart_alerts + self._show_main_view() + elif response_id is gtk.RESPONSE_APPLY: + _logger.debug('Restart...') + + def __select_option_cb(self, button, event, option): + self._show_section_view(option) + + def __search_changed_cb(self, maintoolbar, query): + self._update(query) + + def __stop_clicked_cb(self, widget): + self.destroy() + + def __valid_section_cb(self, section_view, pspec): + section_is_valid = section_view.props.is_valid + self._section_toolbar.accept_button.set_sensitive(section_is_valid) + +class ModelWrapper(object): + def __init__(self, module): + self._module = module + self._options = {} + self._setup() + + def _setup(self): + methods = dir(self._module) + for method in methods: + if method.startswith('get_') and method[4:] != 'color': + try: + self._options[method[4:]] = getattr(self._module, method)() + except Exception: + self._options[method[4:]] = None + + def __getattr__(self, name): + if name.startswith('get_') or name.startswith('set_') or \ + name.startswith('read_'): + return getattr(self._module, name) + + def undo(self): + for key in self._options.keys(): + method = getattr(self._module, 'set_' + key, None) + if method and self._options[key] is not None: + try: + method(self._options[key]) + except Exception, detail: + _logger.debug('Error undo option: %s' % detail) + +class _SectionIcon(gtk.EventBox): + __gtype_name__ = "SugarSectionIcon" + + __gproperties__ = { + 'icon-name' : (str, None, None, None, + gobject.PARAM_READWRITE), + 'pixel-size' : (object, None, None, + gobject.PARAM_READWRITE), + 'xo-color' : (object, None, None, + gobject.PARAM_READWRITE), + 'title' : (str, None, None, None, + gobject.PARAM_READWRITE) + } + + def __init__(self, **kwargs): + self._icon_name = None + self._pixel_size = style.GRID_CELL_SIZE + self._xo_color = None + self._title = 'No Title' + + gobject.GObject.__init__(self, **kwargs) + + self._vbox = gtk.VBox() + self._icon = Icon(icon_name=self._icon_name, + pixel_size=self._pixel_size, + xo_color=self._xo_color) + self._vbox.pack_start(self._icon, expand=False, fill=False) + + self._label = gtk.Label(self._title) + self._label.modify_fg(gtk.STATE_NORMAL, + style.COLOR_WHITE.get_gdk_color()) + self._vbox.pack_start(self._label, expand=False, fill=False) + + self._vbox.set_spacing(style.DEFAULT_SPACING) + self.set_visible_window(False) + self.set_app_paintable(True) + self.set_events(gtk.gdk.BUTTON_PRESS_MASK) + + self.add(self._vbox) + self._vbox.show() + self._label.show() + self._icon.show() + + def get_icon(self): + return self._icon + + def do_set_property(self, pspec, value): + if pspec.name == 'icon-name': + if self._icon_name != value: + self._icon_name = value + elif pspec.name == 'pixel-size': + if self._pixel_size != value: + self._pixel_size = value + elif pspec.name == 'xo-color': + if self._xo_color != value: + self._xo_color = value + elif pspec.name == 'title': + if self._title != value: + self._title = value + + def do_get_property(self, pspec): + if pspec.name == 'icon-name': + return self._icon_name + elif pspec.name == 'pixel-size': + return self._pixel_size + elif pspec.name == 'xo-color': + return self._xo_color + elif pspec.name == 'title': + return self._title diff --git a/src/controlpanel/inlinealert.py b/src/controlpanel/inlinealert.py new file mode 100644 index 0000000..619a379 --- /dev/null +++ b/src/controlpanel/inlinealert.py @@ -0,0 +1,83 @@ +# Copyright (C) 2008, OLPC +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import gtk +import gobject +import pango + +from sugar.graphics import style +from sugar.graphics.icon import Icon + +class InlineAlert(gtk.HBox): + """UI interface for Inline alerts + + Inline alerts are different from the other alerts beause they are + no dialogs, they only inform about a current event. + + Properties: + 'msg': the message of the alert, + 'icon': the icon that appears at the far left + See __gproperties__ + """ + + __gtype_name__ = 'SugarInlineAlert' + + __gproperties__ = { + 'msg' : (str, None, None, None, + gobject.PARAM_READWRITE), + 'icon' : (object, None, None, + gobject.PARAM_WRITABLE) + } + + def __init__(self, **kwargs): + + self._msg = None + self._msg_color = None + self._icon = Icon(icon_name='emblem-warning', + fill_color=style.COLOR_SELECTION_GREY.get_svg(), + stroke_color=style.COLOR_WHITE.get_svg()) + + self._msg_label = gtk.Label() + self._msg_label.set_max_width_chars(50) + self._msg_label.set_ellipsize(pango.ELLIPSIZE_MIDDLE) + self._msg_label.set_alignment(0, 0.5) + self._msg_label.modify_fg(gtk.STATE_NORMAL, + style.COLOR_SELECTION_GREY.get_gdk_color()) + + gobject.GObject.__init__(self, **kwargs) + + self.set_spacing(style.DEFAULT_SPACING) + self.modify_bg(gtk.STATE_NORMAL, + style.COLOR_WHITE.get_gdk_color()) + + self.pack_start(self._icon, False) + self.pack_start(self._msg_label, False) + self._msg_label.show() + self._icon.show() + + def do_set_property(self, pspec, value): + if pspec.name == 'msg': + if self._msg != value: + self._msg = value + self._msg_label.set_markup(self._msg) + elif pspec.name == 'icon': + if self._icon != value: + self._icon = value + + def do_get_property(self, pspec): + if pspec.name == 'msg': + return self._msg + diff --git a/src/controlpanel/model/Makefile.am b/src/controlpanel/model/Makefile.am new file mode 100644 index 0000000..0e7a80d --- /dev/null +++ b/src/controlpanel/model/Makefile.am @@ -0,0 +1,13 @@ +sugardir = $(pkgdatadir)/shell/controlpanel/model +sugar_PYTHON = \ + __init__.py \ + aboutme.py \ + aboutxo.py \ + datetime.py \ + frame.py \ + language.py \ + network.py + + + + diff --git a/src/controlpanel/model/__init__.py b/src/controlpanel/model/__init__.py new file mode 100644 index 0000000..2b0f269 --- /dev/null +++ b/src/controlpanel/model/__init__.py @@ -0,0 +1,17 @@ +# Copyright (C) 2008 One Laptop Per Child +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# + diff --git a/src/controlpanel/model/aboutme.py b/src/controlpanel/model/aboutme.py new file mode 100644 index 0000000..3804c1b --- /dev/null +++ b/src/controlpanel/model/aboutme.py @@ -0,0 +1,114 @@ +# Copyright (C) 2008 One Laptop Per Child +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# + +from gettext import gettext as _ + +from sugar import profile +from sugar.graphics.xocolor import XoColor + +_COLORS = {'red': {'dark':'#b20008', 'medium':'#e6000a', 'light':'#ffadce'}, + 'orange': {'dark':'#9a5200', 'medium':'#c97e00', 'light':'#ffc169'}, + 'yellow': {'dark':'#807500', 'medium':'#be9e00', 'light':'#fffa00'}, + 'green': {'dark':'#008009', 'medium':'#00b20d', 'light':'#8bff7a'}, + 'blue': {'dark':'#00588c', 'medium':'#005fe4', 'light':'#bccdff'}, + 'purple': {'dark':'#5e008c', 'medium':'#7f00bf', 'light':'#d1a3ff'} + } + +_MODIFIERS = ('dark', 'medium', 'light') + +def get_nick(): + return profile.get_nick_name() + +def print_nick(): + print get_nick() + +def set_nick(nick): + """Set the nickname. + nick : e.g. 'walter' + """ + if not nick: + raise ValueError(_("You must enter a name.")) + pro = profile.get_profile() + pro.nick_name = nick + pro.save() + return 1 + +def get_color(): + return profile.get_color() + +def print_color(): + color_string = get_color().to_string() + tmp = color_string.split(',') + + stroke_tuple = None + fill_tuple = None + for color in _COLORS: + for hue in _COLORS[color]: + if _COLORS[color][hue] == tmp[0]: + stroke_tuple = (color, hue) + if _COLORS[color][hue] == tmp[1]: + fill_tuple = (color, hue) + + if stroke_tuple is not None: + print _('stroke: color=%s hue=%s') % (stroke_tuple[0], + stroke_tuple[1]) + else: + print _('stroke: %s') % (tmp[0]) + if fill_tuple is not None: + print _('fill: color=%s hue=%s') % (fill_tuple[0], fill_tuple[1]) + else: + print _('fill: %s') % (tmp[1]) + +def set_color(stroke, fill, stroke_modifier='medium', fill_modifier='medium'): + """Set the system color by setting a fill and stroke color. + fill : [red, orange, yellow, blue, green, purple] + stroke : [red, orange, yellow, blue, green, purple] + hue stroke : [dark, medium, light] (optional) + hue fill : [dark, medium, light] (optional) + """ + + if stroke_modifier not in _MODIFIERS or fill_modifier not in _MODIFIERS: + print (_("Error in specified color modifiers.")) + return + if stroke not in _COLORS or fill not in _COLORS: + print (_("Error in specified colors.")) + return + + if stroke_modifier == fill_modifier: + if fill_modifier == 'medium': + fill_modifier = 'light' + else: + fill_modifier = 'medium' + + color = _COLORS[stroke][stroke_modifier] + ',' \ + + _COLORS[fill][fill_modifier] + pro = profile.get_profile() + pro.color = XoColor(color) + pro.save() + return 1 + +def get_color_xo(): + return profile.get_color() + +def set_color_xo(color): + """Set a color with an XoColor + This method is used by the graphical user interface + """ + pro = profile.get_profile() + pro.color = color + pro.save() + return 1 diff --git a/src/controlpanel/model/aboutxo.py b/src/controlpanel/model/aboutxo.py new file mode 100644 index 0000000..5d17cca --- /dev/null +++ b/src/controlpanel/model/aboutxo.py @@ -0,0 +1,77 @@ +# Copyright (C) 2008 One Laptop Per Child +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# + +import os +import logging +import re +from gettext import gettext as _ + +_logger = logging.getLogger('ControlPanel - AboutXO') +_not_available = _('Not available') + +def get_aboutxo(): + msg = 'Serial Number: %s \nBuild Number: %s \nFirmware Number: %s \n' \ + % (get_serial_number(), get_build_number(), get_firmware_number()) + return msg + +def print_aboutxo(): + print get_aboutxo() + +def get_serial_number(): + serial_no = _read_file('/ofw/serial-number') + if serial_no is None: + serial_no = _not_available + return serial_no + +def print_serial_number(): + print get_serial_number() + +def get_build_number(): + build_no = _read_file('/boot/olpc_build') + if build_no is None: + build_no = _not_available + return build_no + +def print_build_number(): + print get_build_number() + +def get_firmware_number(): + firmware_no = _read_file('/ofw/openprom/model') + if firmware_no is None: + firmware_no = _not_available + else: + firmware_no = re.split(" +", firmware_no) + if len(firmware_no) == 3: + firmware_no = firmware_no[1] + return firmware_no + +def print_firmware_number(): + print get_firmware_number() + +def _read_file(path): + if os.access(path, os.R_OK) == 0: + return None + + fd = open(path, 'r') + value = fd.read() + fd.close() + if value: + value = value.strip('\n') + return value + else: + _logger.debug('No information in file or directory: %s' % path) + return None diff --git a/src/controlpanel/model/datetime.py b/src/controlpanel/model/datetime.py new file mode 100644 index 0000000..a449fd0 --- /dev/null +++ b/src/controlpanel/model/datetime.py @@ -0,0 +1,90 @@ +# Copyright (C) 2007, 2008 One Laptop Per Child +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +# +# The timezone config is based on the system-config-date +# (http://fedoraproject.org/wiki/SystemConfig/date) tool. +# Parts of the code were reused. +# + +import os +from gettext import gettext as _ + +from sugar import profile + +_zone_tab = '/usr/share/zoneinfo/zone.tab' + +def _initialize(): + '''Initialize the docstring of the set function''' + timezones = read_all_timezones() + for timezone in timezones: + set_timezone.__doc__ += timezone + '\n' + +def read_all_timezones(fn=_zone_tab): + fd = open (fn, 'r') + lines = fd.readlines() + fd.close() + timezones = [] + for line in lines: + if line.startswith('#'): + continue + line = line.split() + if len(line) > 1: + timezones.append(line[2]) + timezones.sort() + + for offset in xrange(-12, 13): + if offset < 0: + tz = 'GMT%d' % offset + elif offset > 0: + tz = 'GMT+%d' % offset + else: + tz = 'GMT' + timezones.append(tz) + for offset in xrange(-12, 13): + if offset < 0: + tz = 'UTC%d' % offset + elif offset > 0: + tz = 'UTC+%d' % offset + else: + tz = 'UTC' + timezones.append(tz) + return timezones + +def get_timezone(): + pro = profile.get_profile() + return pro.timezone + +def print_timezone(): + print get_timezone() + +def set_timezone(timezone): + """Set the system timezone + timezone : e.g. 'America/Los_Angeles' + """ + timezones = read_all_timezones() + if timezone in timezones: + os.environ['TZ'] = timezone + pro = profile.get_profile() + pro.timezone = timezone + pro.save() + else: + raise ValueError(_("Error timezone does not exist.")) + return 1 + +# inilialize the docstrings for the timezone +_initialize() + diff --git a/src/controlpanel/model/frame.py b/src/controlpanel/model/frame.py new file mode 100644 index 0000000..d53359b --- /dev/null +++ b/src/controlpanel/model/frame.py @@ -0,0 +1,64 @@ +# Copyright (C) 2008 One Laptop Per Child +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# + +from gettext import gettext as _ + +from sugar import profile + +def get_hot_corners_delay(): + pro = profile.get_profile() + return pro.hot_corners_delay + +def print_hot_corners_delay(): + print get_hot_corners_delay() + +def set_hot_corners_delay(delay): + """Set a delay for the revealing of the frame using hot corners. + instantaneous: 0 (0 milliseconds) + delay: 100 (100 milliseconds) + never: 1000 (disable activation) + """ + try: + int(delay) + except ValueError: + raise ValueError(_("Value must be an int.")) + pro = profile.get_profile() + pro.hot_corners_delay = int(delay) + pro.save() + return 1 + +def get_warm_edges_delay(): + pro = profile.get_profile() + return pro.warm_edges_delay + +def print_warm_edges_delay(): + print get_warm_edges_delay() + +def set_warm_edges_delay(delay): + """Set a delay for the revealing of the frame using warm edges. + instantaneous: 0 (0 milliseconds) + delay: 100 (100 milliseconds) + never: 1000 (disable activation) + """ + try: + int(delay) + except ValueError: + raise ValueError(_("Value must be an int.")) + pro = profile.get_profile() + pro.warm_edges_delay = int(delay) + pro.save() + return 1 diff --git a/src/controlpanel/model/language.py b/src/controlpanel/model/language.py new file mode 100644 index 0000000..3215465 --- /dev/null +++ b/src/controlpanel/model/language.py @@ -0,0 +1,125 @@ +# Copyright (C) 2007, 2008 One Laptop Per Child +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +# +# The language config is based on the system-config-language +# (http://fedoraproject.org/wiki/SystemConfig/language) tool +# Parts of the code were reused. +# + +import os +from gettext import gettext as _ +import subprocess + +_default_lang = 'en_US.utf8' +_standard_msg = _("Could not access ~/.i18n. Create standard settings.") + +def read_all_languages(): + fdp = subprocess.Popen(['locale', '-av'], stdout=subprocess.PIPE) + lines = fdp.stdout.read().split('\n') + locales = [] + + for line in lines: + if line.find('locale:') != -1: + locale = line.lstrip('locale:') + locale = locale.split('archive:')[0].strip() + elif line.find('language |') != -1: + lang = line.lstrip('language |') + elif line.find('territory |') != -1: + territory = line.lstrip('territory |') + if locale.endswith('utf8') and len(lang): + locales.append((lang, territory, locale)) + + locales.sort() + return locales + +def _initialize(): + languages = read_all_languages() + set_language.__doc__ += '\n' + for lang in languages: + set_language.__doc__ += '%s \n' % (lang[0].replace(' ', '_') + '/' + + lang[1].replace(' ', '_')) + +def _write_i18n(lang): + path = os.path.join(os.environ.get("HOME"), '.i18n') + if os.access(path, os.W_OK) == 0: + print _standard_msg + fd = open(path, 'w') + fd.write('LANG="%s"\n' % _default_lang) + fd.close() + else: + fd = open(path, 'r') + lines = fd.readlines() + fd.close() + for i in range(len(lines)): + if lines[i][:5] == "LANG=": + lines[i] = 'LANG="%s"\n' % lang + fd = open(path, 'w') + fd.writelines(lines) + fd.close() + +def get_language(): + path = os.path.join(os.environ.get("HOME"), '.i18n') + if os.access(path, os.R_OK) == 0: + print _standard_msg + fd = open(path, 'w') + fd.write('LANG="%s"\n' % _default_lang) + fd.close() + return _default_lang + + fd = open(path, "r") + lines = fd.readlines() + fd.close() + + lang = None + + for line in lines: + if line[:5] == "LANG=": + lang = line[5:].replace('"', '') + lang = lang.strip() + + return lang + +def print_language(): + code = get_language() + + languages = read_all_languages() + for lang in languages: + if lang[2].split('.')[0] == code.split('.')[0]: + print lang[0].replace(' ', '_') + '/' + lang[1].replace(' ', '_') + return + print (_("Language for code=%s could not be determined.") % code) + +def set_language(language): + """Set the system language. + languages : + """ + if language.endswith('utf8'): + _write_i18n(language) + return 1 + else: + languages = read_all_languages() + for lang, territory, locale in languages: + code = lang.replace(' ', '_') + '/' \ + + territory.replace(' ', '_') + if code == language: + _write_i18n(locale) + return 1 + print (_("Sorry I do not speak \'%s\'.") % language) + +# inilialize the docstrings for the language +_initialize() + diff --git a/src/controlpanel/model/network.py b/src/controlpanel/model/network.py new file mode 100644 index 0000000..d24c986 --- /dev/null +++ b/src/controlpanel/model/network.py @@ -0,0 +1,85 @@ +# Copyright (C) 2008 One Laptop Per Child +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# + +import dbus +from gettext import gettext as _ + +from sugar import profile + +NM_SERVICE_NAME = 'org.freedesktop.NetworkManager' +NM_SERVICE_PATH = '/org/freedesktop/NetworkManager' +NM_SERVICE_IFACE = 'org.freedesktop.NetworkManager' +NM_ASLEEP = 1 + +KEYWORDS = ['network', 'jabber', 'radio', 'server'] + +class ReadError(Exception): + def __init__(self, value): + self.value = value + def __str__(self): + return repr(self.value) + +def get_jabber(): + pro = profile.get_profile() + return pro.jabber_server + +def print_jabber(): + print get_jabber() + +def set_jabber(server): + """Set the jabber server + server : e.g. 'olpc.collabora.co.uk' + """ + if not server: + raise ValueError(_("You must enter a server.")) + pro = profile.get_profile() + pro.jabber_server = server + pro.jabber_registered = False + pro.save() + return 1 + +def get_radio(): + bus = dbus.SystemBus() + proxy = bus.get_object(NM_SERVICE_NAME, NM_SERVICE_PATH) + nm = dbus.Interface(proxy, NM_SERVICE_IFACE) + state = nm.getWirelessEnabled() + if state in (0, 1): + return state + else: + raise ReadError(_('State is unknown.')) + +def print_radio(): + print ('off', 'on')[get_radio()] + +def set_radio(state): + """Turn Radio 'on' or 'off' + state : 'on/off' + """ + if state == 'on' or state == 1: + bus = dbus.SystemBus() + proxy = bus.get_object(NM_SERVICE_NAME, NM_SERVICE_PATH) + nm = dbus.Interface(proxy, NM_SERVICE_IFACE) + nm.setWirelessEnabled(True) + elif state == 'off' or state == 0: + bus = dbus.SystemBus() + proxy = bus.get_object(NM_SERVICE_NAME, NM_SERVICE_PATH) + nm = dbus.Interface(proxy, NM_SERVICE_IFACE) + nm.setWirelessEnabled(False) + else: + raise ValueError(_("Error in specified radio argument use on/off.")) + + return 0 diff --git a/src/controlpanel/sectionview.py b/src/controlpanel/sectionview.py new file mode 100644 index 0000000..d9830bc --- /dev/null +++ b/src/controlpanel/sectionview.py @@ -0,0 +1,47 @@ +# Copyright (C) 2008, OLPC +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import gobject +import gtk +from gettext import gettext as _ + +class SectionView(gtk.VBox): + __gproperties__ = { + 'is_valid' : (bool, None, None, True, + gobject.PARAM_READWRITE) + } + + _APPLY_TIMEOUT = 1000 + + def __init__(self): + gtk.VBox.__init__(self) + self._is_valid = True + self.needs_restart = False + self.restart_alerts = [] + self.restart_msg = _('Changes require a sugar restart to take effect.') + + def do_set_property(self, pspec, value): + if pspec.name == 'is-valid': + if self._is_valid != value: + self._is_valid = value + + def do_get_property(self, pspec): + if pspec.name == 'is-valid': + return self._is_valid + + def undo(self): + '''Undo here the changes that have been made in this section.''' + pass diff --git a/src/controlpanel/toolbar.py b/src/controlpanel/toolbar.py new file mode 100644 index 0000000..98d3792 --- /dev/null +++ b/src/controlpanel/toolbar.py @@ -0,0 +1,157 @@ +# Copyright (C) 2007, 2008 One Laptop Per Child +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import gtk +import gettext +import gobject + +_ = lambda msg: gettext.dgettext('sugar', msg) + +from sugar.graphics.icon import Icon +from sugar.graphics.toolbutton import ToolButton +from sugar.graphics import iconentry +from sugar.graphics import style + +class MainToolbar(gtk.Toolbar): + """ Main toolbar of the control panel + """ + __gtype_name__ = 'MainToolbar' + + __gsignals__ = { + 'stop-clicked': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([])), + 'search-changed': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([str])) + } + def __init__(self): + gtk.Toolbar.__init__(self) + + self._add_separator() + + tool_item = gtk.ToolItem() + self.insert(tool_item, -1) + tool_item.show() + self._search_entry = iconentry.IconEntry() + self._search_entry.set_icon_from_name(iconentry.ICON_ENTRY_PRIMARY, + 'system-search') + self._search_entry.add_clear_button() + self._search_entry.set_width_chars(25) + self._search_entry.connect('changed', self.__search_entry_changed_cb) + tool_item.add(self._search_entry) + self._search_entry.show() + + self._add_separator(True) + + self.stop = ToolButton(icon_name='dialog-cancel') + self.stop.set_tooltip(_('Done')) + self.stop.connect('clicked', self.__stop_clicked_cb) + self.stop.show() + self.insert(self.stop, -1) + self.stop.show() + + def get_entry(self): + return self._search_entry + + def _add_separator(self, expand=False): + separator = gtk.SeparatorToolItem() + separator.props.draw = False + if expand: + separator.set_expand(True) + else: + separator.set_size_request(style.DEFAULT_SPACING, -1) + self.insert(separator, -1) + separator.show() + + def __search_entry_changed_cb(self, search_entry): + self.emit('search-changed', search_entry.props.text) + + def __stop_clicked_cb(self, button): + self.emit('stop-clicked') + +class SectionToolbar(gtk.Toolbar): + """ Toolbar of the sections of the control panel + """ + __gtype_name__ = 'SectionToolbar' + + __gsignals__ = { + 'cancel-clicked': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([])), + 'accept-clicked': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([])) + } + def __init__(self): + gtk.Toolbar.__init__(self) + + self._add_separator() + + self._icon = Icon() + self._add_widget(self._icon) + + self._add_separator() + + self._title = gtk.Label() + self._add_widget(self._title) + + self._add_separator(True) + + cancel_button = ToolButton('dialog-cancel') + cancel_button.set_tooltip(_('Cancel')) + cancel_button.connect('clicked', self.__cancel_button_clicked_cb) + self.insert(cancel_button, -1) + cancel_button.show() + + self.accept_button = ToolButton('dialog-ok') + self.accept_button.set_tooltip(_('Ok')) + self.accept_button.connect('clicked', self.__accept_button_clicked_cb) + self.insert(self.accept_button, -1) + self.accept_button.show() + + def get_icon(self): + return self._icon + + def get_title(self): + return self._title + + def _add_separator(self, expand=False): + separator = gtk.SeparatorToolItem() + separator.props.draw = False + if expand: + separator.set_expand(True) + else: + separator.set_size_request(style.DEFAULT_SPACING, -1) + self.insert(separator, -1) + separator.show() + + def _add_widget(self, widget, expand=False): + tool_item = gtk.ToolItem() + tool_item.set_expand(expand) + + tool_item.add(widget) + widget.show() + + self.insert(tool_item, -1) + tool_item.show() + + def __cancel_button_clicked_cb(self, widget, data=None): + self.emit('cancel-clicked') + + def __accept_button_clicked_cb(self, widget, data=None): + self.emit('accept-clicked') + diff --git a/src/controlpanel/view/Makefile.am b/src/controlpanel/view/Makefile.am new file mode 100644 index 0000000..0fd9445 --- /dev/null +++ b/src/controlpanel/view/Makefile.am @@ -0,0 +1,15 @@ +sugardir = $(pkgdatadir)/shell/controlpanel/view +sugar_PYTHON = \ + __init__.py \ + aboutme.py \ + aboutxo.py \ + datetime.py \ + frame.py \ + language.py \ + network.py + + + + + + diff --git a/src/controlpanel/view/__init__.py b/src/controlpanel/view/__init__.py new file mode 100644 index 0000000..2b0f269 --- /dev/null +++ b/src/controlpanel/view/__init__.py @@ -0,0 +1,17 @@ +# Copyright (C) 2008 One Laptop Per Child +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# + diff --git a/src/controlpanel/view/aboutme.py b/src/controlpanel/view/aboutme.py new file mode 100644 index 0000000..2245540 --- /dev/null +++ b/src/controlpanel/view/aboutme.py @@ -0,0 +1,229 @@ +# Copyright (C) 2008, OLPC +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import gtk +import gobject +from gettext import gettext as _ + +from sugar.graphics.icon import Icon +from sugar.graphics import style +from sugar.graphics.xocolor import XoColor +from sugar import profile + +from controlpanel.sectionview import SectionView +from controlpanel.inlinealert import InlineAlert + +CLASS = 'AboutMe' +ICON = 'module-about_me' +COLOR = profile.get_color() +TITLE = _('About Me') + +class EventIcon(gtk.EventBox): + __gtype_name__ = "SugarEventIcon" + def __init__(self, **kwargs): + gtk.EventBox.__init__(self) + + self.icon = Icon(pixel_size = style.XLARGE_ICON_SIZE, **kwargs) + + self.set_visible_window(False) + self.set_app_paintable(True) + self.set_events(gtk.gdk.BUTTON_PRESS_MASK) + + self.add(self.icon) + self.icon.show() + +class ColorPicker(EventIcon): + __gsignals__ = { + 'color-changed': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([object])) + } + def __init__(self, xocolor=None): + EventIcon.__init__(self) + self.icon.props.xo_color = xocolor + self.icon.props.icon_name = 'computer-xo' + self.icon.props.pixel_size = style.XLARGE_ICON_SIZE + self.connect('button_press_event', self.__pressed_cb) + + def __pressed_cb(self, button, event): + self._set_random_colors() + + def _set_random_colors(self): + xocolor = XoColor() + self.icon.props.xo_color = xocolor + self.emit('color-changed', xocolor) + +class AboutMe(SectionView): + def __init__(self, model, alerts): + SectionView.__init__(self) + + self._model = model + self.restart_alerts = alerts + self._nick_sid = 0 + self._color_valid = True + self._nick_valid = True + self._color_change_handler = None + self._nick_change_handler = None + + self.set_border_width(style.DEFAULT_SPACING * 2) + self.set_spacing(style.DEFAULT_SPACING) + self._group = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) + + self._nick_box = gtk.HBox(spacing=style.DEFAULT_SPACING) + self._nick_alert_box = gtk.HBox(spacing=style.DEFAULT_SPACING) + self._nick_entry = None + self._nick_alert = None + self._setup_nick() + + self._color_box = gtk.HBox(spacing=style.DEFAULT_SPACING) + self._color_alert_box = gtk.HBox(spacing=style.DEFAULT_SPACING) + self._color_picker = None + self._color_alert = None + self._setup_color() + + self.setup() + + def _setup_nick(self): + label_entry = gtk.Label(_('Name:')) + label_entry.modify_fg(gtk.STATE_NORMAL, + style.COLOR_SELECTION_GREY.get_gdk_color()) + self._group.add_widget(label_entry) + label_entry.set_alignment(1, 0.5) + self._nick_box.pack_start(label_entry, expand=False) + label_entry.show() + + self._nick_entry = gtk.Entry() + self._nick_entry.modify_bg(gtk.STATE_INSENSITIVE, + style.COLOR_WHITE.get_gdk_color()) + self._nick_entry.modify_base(gtk.STATE_INSENSITIVE, + style.COLOR_WHITE.get_gdk_color()) + self._nick_entry.set_width_chars(25) + self._nick_box.pack_start(self._nick_entry, expand=False) + self._nick_entry.show() + + label_entry_error = gtk.Label() + self._group.add_widget(label_entry_error) + self._nick_alert_box.pack_start(label_entry_error, expand=False) + label_entry_error.show() + + self._nick_alert = InlineAlert() + self._nick_alert_box.pack_start(self._nick_alert) + if 'nick' in self.restart_alerts: + self._nick_alert.props.msg = self.restart_msg + self._nick_alert.show() + + self.pack_start(self._nick_box, False) + self.pack_start(self._nick_alert_box, False) + self._nick_box.show() + self._nick_alert_box.show() + + def _setup_color(self): + label_color = gtk.Label(_('Click to change your color:')) + label_color.modify_fg(gtk.STATE_NORMAL, + style.COLOR_SELECTION_GREY.get_gdk_color()) + self._group.add_widget(label_color) + self._color_box.pack_start(label_color, expand=False) + label_color.show() + + self._color_picker = ColorPicker() + self._color_box.pack_start(self._color_picker, expand=False) + self._color_picker.show() + + label_color_error = gtk.Label() + self._group.add_widget(label_color_error) + self._color_alert_box.pack_start(label_color_error, expand=False) + label_color_error.show() + + self._color_alert = InlineAlert() + self._color_alert_box.pack_start(self._color_alert) + if 'color' in self.restart_alerts: + self._color_alert.props.msg = self.restart_msg + self._color_alert.show() + + self.pack_start(self._color_box, False) + self.pack_start(self._color_alert_box, False) + self._color_box.show() + self._color_alert_box.show() + + def setup(self): + self._nick_entry.set_text(self._model.get_nick()) + self._color_picker.icon.props.xo_color = self._model.get_color_xo() + + self._color_valid = True + self._nick_valid = True + self.needs_restart = False + self._nick_change_handler = self._nick_entry.connect( \ + 'changed', self.__nick_changed_cb) + self._color_change_handler = self._color_picker.connect( \ + 'color-changed', self.__color_changed_cb) + + def undo(self): + self._color_picker.disconnect(self._color_change_handler) + self._nick_entry.disconnect(self._nick_change_handler) + self._model.undo() + self._nick_alert.hide() + self._color_alert.hide() + + def __nick_changed_cb(self, widget, data=None): + if self._nick_sid: + gobject.source_remove(self._nick_sid) + self._nick_sid = gobject.timeout_add(self._APPLY_TIMEOUT, + self.__nick_timeout_cb, widget) + + def __nick_timeout_cb(self, widget): + self._nick_sid = 0 + + if widget.get_text() == self._model.get_nick(): + return False + try: + self._model.set_nick(widget.get_text()) + except ValueError, detail: + self._nick_alert.props.msg = detail + self._nick_valid = False + self.needs_restart = False + else: + self._nick_alert.props.msg = self.restart_msg + self._nick_valid = True + self.needs_restart = True + self.restart_alerts.append('nick') + + if self._nick_valid and self._color_valid: + self.props.is_valid = True + else: + self.props.is_valid = False + + self._nick_alert.show() + return False + + def __color_changed_cb(self, colorpicker, xocolor): + self._model.set_color_xo(xocolor) + self.needs_restart = True + self._color_alert.props.msg = self.restart_msg + self._color_valid = True + self.restart_alerts.append('color') + + if self._nick_valid and self._color_valid: + self.props.is_valid = True + else: + self.props.is_valid = False + + self._color_alert.show() + + + + + + diff --git a/src/controlpanel/view/aboutxo.py b/src/controlpanel/view/aboutxo.py new file mode 100644 index 0000000..04833f0 --- /dev/null +++ b/src/controlpanel/view/aboutxo.py @@ -0,0 +1,118 @@ +# Copyright (C) 2008, OLPC +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import gtk +from gettext import gettext as _ + +from sugar.graphics import style + +from controlpanel.sectionview import SectionView + +CLASS = 'AboutXO' +ICON = 'module-about_my_xo' +TITLE = _('About my XO') + +class AboutXO(SectionView): + def __init__(self, model, alerts=None): + SectionView.__init__(self) + + self._model = model + + self.set_border_width(style.DEFAULT_SPACING * 2) + self.set_spacing(style.DEFAULT_SPACING) + + self._group = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) + + self._setup_identity() + self._setup_software() + + def _setup_identity(self): + separator_identity = gtk.HSeparator() + self.pack_start(separator_identity, expand=False) + separator_identity.show() + + label_identity = gtk.Label(_('Identity')) + label_identity.set_alignment(0, 0) + self.pack_start(label_identity, expand=False) + label_identity.show() + vbox_identity = gtk.VBox() + vbox_identity.set_border_width(style.DEFAULT_SPACING * 2) + vbox_identity.set_spacing(style.DEFAULT_SPACING) + + box_identity = gtk.HBox(spacing=style.DEFAULT_SPACING) + label_serial = gtk.Label(_('Serial Number:')) + label_serial.set_alignment(1, 0) + label_serial.modify_fg(gtk.STATE_NORMAL, + style.COLOR_SELECTION_GREY.get_gdk_color()) + box_identity.pack_start(label_serial, expand=False) + self._group.add_widget(label_serial) + label_serial.show() + label_serial_no = gtk.Label(self._model.get_serial_number()) + label_serial_no.set_alignment(0, 0) + box_identity.pack_start(label_serial_no, expand=False) + label_serial_no.show() + vbox_identity.pack_start(box_identity, expand=False) + box_identity.show() + + self.pack_start(vbox_identity, expand=False) + vbox_identity.show() + + + def _setup_software(self): + separator_software = gtk.HSeparator() + self.pack_start(separator_software, expand=False) + separator_software.show() + + label_software = gtk.Label(_('Software')) + label_software.set_alignment(0, 0) + self.pack_start(label_software, expand=False) + label_software.show() + box_software = gtk.VBox() + box_software.set_border_width(style.DEFAULT_SPACING * 2) + box_software.set_spacing(style.DEFAULT_SPACING) + + box_build = gtk.HBox(spacing=style.DEFAULT_SPACING) + label_build = gtk.Label(_('Build:')) + label_build.set_alignment(1, 0) + label_build.modify_fg(gtk.STATE_NORMAL, + style.COLOR_SELECTION_GREY.get_gdk_color()) + box_build.pack_start(label_build, expand=False) + self._group.add_widget(label_build) + label_build.show() + label_build_no = gtk.Label(self._model.get_build_number()) + label_build_no.set_alignment(0, 0) + box_build.pack_start(label_build_no, expand=False) + label_build_no.show() + box_software.pack_start(box_build, expand=False) + box_build.show() + + box_firmware = gtk.HBox(spacing=style.DEFAULT_SPACING) + label_firmware = gtk.Label(_('Firmware:')) + label_firmware.set_alignment(1, 0) + label_firmware.modify_fg(gtk.STATE_NORMAL, + style.COLOR_SELECTION_GREY.get_gdk_color()) + box_firmware.pack_start(label_firmware, expand=False) + self._group.add_widget(label_firmware) + label_firmware.show() + label_firmware_no = gtk.Label(self._model.get_firmware_number()) + label_firmware_no.set_alignment(0, 0) + box_firmware.pack_start(label_firmware_no, expand=False) + label_firmware_no.show() + box_software.pack_start(box_firmware, expand=False) + box_firmware.show() + + self.pack_start(box_software, expand=False) + box_software.show() diff --git a/src/controlpanel/view/datetime.py b/src/controlpanel/view/datetime.py new file mode 100644 index 0000000..47ba433 --- /dev/null +++ b/src/controlpanel/view/datetime.py @@ -0,0 +1,146 @@ +# Copyright (C) 2008, OLPC +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import gtk +import gobject +from gettext import gettext as _ + +from sugar.graphics import style +from sugar.graphics import iconentry + +from controlpanel.sectionview import SectionView +from controlpanel.inlinealert import InlineAlert + +CLASS = 'TimeZone' +ICON = 'module-date_and_time' +TITLE = _('Date & Time') + +class TimeZone(SectionView): + def __init__(self, model, alerts): + SectionView.__init__(self) + + self._model = model + self.restart_alerts = alerts + self._zone_sid = 0 + self._cursor_change_handler = None + + self.set_border_width(style.DEFAULT_SPACING * 2) + self.set_spacing(style.DEFAULT_SPACING) + + self.connect("realize", self.__realize_cb) + + self._entry = iconentry.IconEntry() + self._entry.set_icon_from_name(iconentry.ICON_ENTRY_PRIMARY, + 'system-search') + self._entry.add_clear_button() + self._entry.modify_bg(gtk.STATE_INSENSITIVE, + style.COLOR_WHITE.get_gdk_color()) + self._entry.modify_base(gtk.STATE_INSENSITIVE, + style.COLOR_WHITE.get_gdk_color()) + self.pack_start(self._entry, False) + self._entry.show() + + self._scrolled_window = gtk.ScrolledWindow() + self._scrolled_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + self._scrolled_window.set_shadow_type(gtk.SHADOW_IN) + + self._store = gtk.ListStore(gobject.TYPE_STRING) + zones = model.read_all_timezones() + for zone in zones: + self._store.append([zone]) + + self._treeview = gtk.TreeView(self._store) + self._treeview.set_search_entry(self._entry) + self._treeview.set_search_equal_func(self._search) + self._treeview.set_search_column(0) + self._scrolled_window.add(self._treeview) + self._treeview.show() + + self._timezone_column = gtk.TreeViewColumn(_('Timezone')) + self._cell = gtk.CellRendererText() + self._timezone_column.pack_start(self._cell, True) + self._timezone_column.add_attribute(self._cell, 'text', 0) + self._timezone_column.set_sort_column_id(0) + self._treeview.append_column(self._timezone_column) + + self.pack_start(self._scrolled_window) + self._scrolled_window.show() + + self._zone_alert_box = gtk.HBox(spacing=style.DEFAULT_SPACING) + self.pack_start(self._zone_alert_box, False) + + self._zone_alert = InlineAlert() + self._zone_alert_box.pack_start(self._zone_alert) + if 'zone' in self.restart_alerts: + self._zone_alert.props.msg = self.restart_msg + self._zone_alert.show() + self._zone_alert_box.show() + + self.setup() + + def setup(self): + zone = self._model.get_timezone() + for row in self._store: + if zone == row[0]: + self._treeview.set_cursor(row.path, self._timezone_column, + False) + self._treeview.scroll_to_cell(row.path, self._timezone_column, + True, 0.5, 0.5) + break + + self.needs_restart = False + self._cursor_change_handler = self._treeview.connect( \ + "cursor-changed", self.__zone_changed_cd) + + def undo(self): + self._treeview.disconnect(self._cursor_change_handler) + self._model.undo() + self._zone_alert.hide() + + def __realize_cb(self, widget): + self._entry.grab_focus() + + def _search(self, model, column_, key, iter_, data=None): + for row in model: + if key.lower() in row[0].lower(): + self._treeview.set_cursor(row.path, self._timezone_column, + False) + self._treeview.scroll_to_cell(row.path, self._timezone_column, + True, 0.5, 0.5) + return True + return False + + def __zone_changed_cd(self, treeview, data=None): + list_, row = treeview.get_selection().get_selected() + if not row: + return False + if self._model.get_timezone() == self._store.get_value(row, 0): + return False + + if self._zone_sid: + gobject.source_remove(self._zone_sid) + self._zone_sid = gobject.timeout_add(self._APPLY_TIMEOUT, + self.__zone_timeout_cb, row) + return True + + def __zone_timeout_cb(self, row): + self._zone_sid = 0 + self._model.set_timezone(self._store.get_value(row, 0)) + self.restart_alerts.append('zone') + self.needs_restart = True + self._zone_alert.props.msg = self.restart_msg + self._zone_alert.show() + return False diff --git a/src/controlpanel/view/frame.py b/src/controlpanel/view/frame.py new file mode 100644 index 0000000..63bbb39 --- /dev/null +++ b/src/controlpanel/view/frame.py @@ -0,0 +1,256 @@ +# Copyright (C) 2008, OLPC +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import gtk +import gobject +from gettext import gettext as _ + +from sugar.graphics import style + +from controlpanel.sectionview import SectionView +from controlpanel.inlinealert import InlineAlert + +CLASS = 'Frame' +ICON = 'module-frame' +TITLE = _('Frame') + +_never = _('never') +_instantaneous = _('instantaneous') +_delay_label = _('Delay in milliseconds:') + +class Frame(SectionView): + def __init__(self, model, alerts): + SectionView.__init__(self) + + self._model = model + self._hot_delay_sid = 0 + self._hot_delay_is_valid = True + self._hot_delay_change_handler = None + self._warm_delay_sid = 0 + self._warm_delay_is_valid = True + self._warm_delay_change_handler = None + self.restart_alerts = alerts + + self.set_border_width(style.DEFAULT_SPACING * 2) + self.set_spacing(style.DEFAULT_SPACING) + self._group = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) + + self._hot_delay_slider = None + self._hot_delay_alert = None + self._setup_hot_corners() + + self._warm_delay_slider = None + self._warm_delay_alert = None + self._setup_warm_edges() + + self.setup() + + def _setup_hot_corners(self): + separator_hot = gtk.HSeparator() + self.pack_start(separator_hot, expand=False) + separator_hot.show() + + label_hot_corners = gtk.Label(_('Hot Corners')) + label_hot_corners.set_alignment(0, 0) + self.pack_start(label_hot_corners, expand=False) + label_hot_corners.show() + + box_hot_corners = gtk.VBox() + box_hot_corners.set_border_width(style.DEFAULT_SPACING * 2) + box_hot_corners.set_spacing(style.DEFAULT_SPACING) + + box_delay = gtk.HBox(spacing=style.DEFAULT_SPACING) + label_delay = gtk.Label(_delay_label) + label_delay.set_alignment(1, 0.75) + label_delay.modify_fg(gtk.STATE_NORMAL, + style.COLOR_SELECTION_GREY.get_gdk_color()) + box_delay.pack_start(label_delay, expand=False) + self._group.add_widget(label_delay) + label_delay.show() + + adj = gtk.Adjustment(value=100, lower=0, upper=1000, step_incr=100, + page_incr=100, page_size=0) + self._hot_delay_slider = gtk.HScale(adj) + self._hot_delay_slider.set_digits(0) + self._hot_delay_slider.connect('format-value', + self.__hot_delay_format_cb) + box_delay.pack_start(self._hot_delay_slider) + self._hot_delay_slider.show() + box_hot_corners.pack_start(box_delay, expand=False) + box_delay.show() + + self._hot_delay_alert = InlineAlert() + label_delay_error = gtk.Label() + self._group.add_widget(label_delay_error) + + delay_alert_box = gtk.HBox(spacing=style.DEFAULT_SPACING) + delay_alert_box.pack_start(label_delay_error, expand=False) + label_delay_error.show() + delay_alert_box.pack_start(self._hot_delay_alert, expand=False) + box_hot_corners.pack_start(delay_alert_box, expand=False) + delay_alert_box.show() + if 'hot_delay' in self.restart_alerts: + self._hot_delay_alert.props.msg = self.restart_msg + self._hot_delay_alert.show() + + self.pack_start(box_hot_corners, expand=False) + box_hot_corners.show() + + def _setup_warm_edges(self): + separator_warm = gtk.HSeparator() + self.pack_start(separator_warm, expand=False) + separator_warm.show() + + label_warm_edges = gtk.Label(_('Warm Edges')) + label_warm_edges.set_alignment(0, 0) + self.pack_start(label_warm_edges, expand=False) + label_warm_edges.show() + + box_warm_edges = gtk.VBox() + box_warm_edges.set_border_width(style.DEFAULT_SPACING * 2) + box_warm_edges.set_spacing(style.DEFAULT_SPACING) + + box_delay = gtk.HBox(spacing=style.DEFAULT_SPACING) + label_delay = gtk.Label(_delay_label) + label_delay.set_alignment(1, 0.75) + label_delay.modify_fg(gtk.STATE_NORMAL, + style.COLOR_SELECTION_GREY.get_gdk_color()) + box_delay.pack_start(label_delay, expand=False) + self._group.add_widget(label_delay) + label_delay.show() + + adj = gtk.Adjustment(value=100, lower=0, upper=1000, step_incr=100, + page_incr=100, page_size=0) + self._warm_delay_slider = gtk.HScale(adj) + self._warm_delay_slider.set_digits(0) + self._warm_delay_slider.connect('format-value', + self.__warm_delay_format_cb) + box_delay.pack_start(self._warm_delay_slider) + self._warm_delay_slider.show() + box_warm_edges.pack_start(box_delay, expand=False) + box_delay.show() + + self._warm_delay_alert = InlineAlert() + label_delay_error = gtk.Label() + self._group.add_widget(label_delay_error) + + delay_alert_box = gtk.HBox(spacing=style.DEFAULT_SPACING) + delay_alert_box.pack_start(label_delay_error, expand=False) + label_delay_error.show() + delay_alert_box.pack_start(self._warm_delay_alert, expand=False) + box_warm_edges.pack_start(delay_alert_box, expand=False) + delay_alert_box.show() + if 'warm_delay' in self.restart_alerts: + self._warm_delay_alert.props.msg = self.restart_msg + self._warm_delay_alert.show() + + self.pack_start(box_warm_edges, expand=False) + box_warm_edges.show() + + def setup(self): + self._hot_delay_slider.set_value(self._model.get_hot_corners_delay()) + self._warm_delay_slider.set_value(self._model.get_warm_edges_delay()) + self._hot_delay_is_valid = True + self._warm_delay_is_valid = True + self.needs_restart = False + self._hot_delay_change_handler = self._hot_delay_slider.connect( \ + 'value-changed', self.__hot_delay_changed_cb) + self._warm_delay_change_handler = self._warm_delay_slider.connect( \ + 'value-changed', self.__warm_delay_changed_cb) + + def undo(self): + self._hot_delay_slider.disconnect(self._hot_delay_change_handler) + self._warm_delay_slider.disconnect(self._warm_delay_change_handler) + self._model.undo() + self._hot_delay_alert.hide() + self._warm_delay_alert.hide() + + def __hot_delay_changed_cb(self, scale, data=None): + if self._hot_delay_sid: + gobject.source_remove(self._hot_delay_sid) + self._hot_delay_sid = gobject.timeout_add(self._APPLY_TIMEOUT, + self.__hot_delay_timeout_cb, + scale) + + def __hot_delay_timeout_cb(self, scale): + self._hot_delay_sid = 0 + if scale.get_value() == self._model.get_hot_corners_delay(): + return + try: + self._model.set_hot_corners_delay(scale.get_value()) + except ValueError, detail: + self._hot_delay_alert.props.msg = detail + self._hot_delay_is_valid = False + self.needs_restart = False + else: + self._hot_delay_alert.props.msg = self.restart_msg + self._hot_delay_is_valid = True + self.needs_restart = True + self.restart_alerts.append('hot_delay') + + if self._hot_delay_is_valid: + self.props.is_valid = True + else: + self.props.is_valid = False + + self._hot_delay_alert.show() + return False + + def __hot_delay_format_cb(self, scale, value): + if value == 1000.0: + return _never + elif value == 0.0: + return _instantaneous + else: + return '%s ms' % value + + def __warm_delay_changed_cb(self, scale, data=None): + if self._warm_delay_sid: + gobject.source_remove(self._warm_delay_sid) + self._warm_delay_sid = gobject.timeout_add( \ + self._APPLY_TIMEOUT, self.__warm_delay_timeout_cb, scale) + + def __warm_delay_timeout_cb(self, scale): + self._warm_delay_sid = 0 + if scale.get_value() == self._model.get_warm_edges_delay(): + return + try: + self._model.set_warm_edges_delay(scale.get_value()) + except ValueError, detail: + self._warm_delay_alert.props.msg = detail + self._warm_delay_is_valid = False + self.needs_restart = False + else: + self._warm_delay_alert.props.msg = self.restart_msg + self._warm_delay_is_valid = True + self.needs_restart = True + self.restart_alerts.append('warm_delay') + + if self._warm_delay_is_valid: + self.props.is_valid = True + else: + self.props.is_valid = False + + self._warm_delay_alert.show() + return False + + def __warm_delay_format_cb(self, scale, value): + if value == 1000.0: + return _never + elif value == 0.0: + return _instantaneous + else: + return '%s ms' % value diff --git a/src/controlpanel/view/language.py b/src/controlpanel/view/language.py new file mode 100644 index 0000000..3c47691 --- /dev/null +++ b/src/controlpanel/view/language.py @@ -0,0 +1,150 @@ +# Copyright (C) 2008, OLPC +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import gtk +import gobject +from gettext import gettext as _ + +from sugar.graphics import style +from sugar.graphics import iconentry + +from controlpanel.sectionview import SectionView +from controlpanel.inlinealert import InlineAlert + +CLASS = 'Language' +ICON = 'module-language' +TITLE = _('Language') + +class Language(SectionView): + def __init__(self, model, alerts): + SectionView.__init__(self) + + self._model = model + self.restart_alerts = alerts + self._lang_sid = 0 + self._cursor_change_handler = None + + self.set_border_width(style.DEFAULT_SPACING * 2) + self.set_spacing(style.DEFAULT_SPACING) + + self.connect("realize", self.__realize_cb) + + self._entry = iconentry.IconEntry() + self._entry.set_icon_from_name(iconentry.ICON_ENTRY_PRIMARY, + 'system-search') + self._entry.add_clear_button() + self._entry.modify_bg(gtk.STATE_INSENSITIVE, + style.COLOR_WHITE.get_gdk_color()) + self._entry.modify_base(gtk.STATE_INSENSITIVE, + style.COLOR_WHITE.get_gdk_color()) + self.pack_start(self._entry, False) + self._entry.show() + + self._scrolled_window = gtk.ScrolledWindow() + self._scrolled_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + self._scrolled_window.set_shadow_type(gtk.SHADOW_IN) + + self._store = gtk.ListStore(gobject.TYPE_STRING, + gobject.TYPE_STRING) + locales = model.read_all_languages() + for locale in locales: + self._store.append([locale[2], '%s (%s)' % + (locale[0], locale[1])]) + + self._treeview = gtk.TreeView(self._store) + self._treeview.set_search_entry(self._entry) + self._treeview.set_search_equal_func(self._search) + self._treeview.set_search_column(1) + self._scrolled_window.add(self._treeview) + self._treeview.show() + + self._language_column = gtk.TreeViewColumn(_('Language')) + self._cell = gtk.CellRendererText() + self._language_column.pack_start(self._cell, True) + self._language_column.add_attribute(self._cell, 'text', 1) + self._language_column.set_sort_column_id(1) + self._treeview.append_column(self._language_column) + + self.pack_start(self._scrolled_window) + self._scrolled_window.show() + + self._lang_alert_box = gtk.HBox(spacing=style.DEFAULT_SPACING) + self.pack_start(self._lang_alert_box, False) + + self._lang_alert = InlineAlert() + self._lang_alert_box.pack_start(self._lang_alert) + if 'lang' in self.restart_alerts: + self._lang_alert.props.msg = self.restart_msg + self._lang_alert.show() + self._lang_alert_box.show() + + self.setup() + + def setup(self): + lang_code = self._model.get_language() + for row in self._store: + lang = lang_code.split('.')[0] + lang_column = row[0].split('.')[0] + if lang in lang_column: + self._treeview.set_cursor(row.path, self._language_column, + False) + self._treeview.scroll_to_cell(row.path, self._language_column, + True, 0.5, 0.5) + break + + self.needs_restart = False + self._cursor_change_handler = self._treeview.connect( \ + "cursor-changed", self.__lang_changed_cd) + + def undo(self): + self._treeview.disconnect(self._cursor_change_handler) + self._model.undo() + self._lang_alert.hide() + + def __realize_cb(self, widget): + self._entry.grab_focus() + + def _search(self, model, column_, key, iter_, data=None): + for row in model: + if key.lower() in row[1].lower(): + self._treeview.set_cursor(row.path, self._language_column, + False) + self._treeview.scroll_to_cell(row.path, self._language_column, + True, 0.5, 0.5) + return True + return False + + def __lang_changed_cd(self, treeview, data=None): + row = treeview.get_selection().get_selected() + if not row[1]: + return False + if self._model.get_language() == self._store.get_value(row[1], 0): + return False + + if self._lang_sid: + gobject.source_remove(self._lang_sid) + self._lang_sid = gobject.timeout_add(self._APPLY_TIMEOUT, + self.__lang_timeout_cb, + self._store.get_value(row[1], 0)) + + def __lang_timeout_cb(self, code): + self._lang_sid = 0 + self._model.set_language(code) + self.restart_alerts.append('lang') + self.needs_restart = True + self._lang_alert.props.msg = self.restart_msg + self._lang_alert.show() + return False diff --git a/src/controlpanel/view/network.py b/src/controlpanel/view/network.py new file mode 100644 index 0000000..af64a1a --- /dev/null +++ b/src/controlpanel/view/network.py @@ -0,0 +1,209 @@ +# Copyright (C) 2008, OLPC +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import gtk +import gobject +from gettext import gettext as _ + +from sugar.graphics import style + +from controlpanel.sectionview import SectionView +from controlpanel.inlinealert import InlineAlert + +CLASS = 'Network' +ICON = 'module-network' +TITLE = _('Network') + +class Network(SectionView): + def __init__(self, model, alerts): + SectionView.__init__(self) + + self._model = model + self.restart_alerts = alerts + self._jabber_sid = 0 + self._jabber_valid = True + self._radio_valid = True + self._jabber_change_handler = None + self._radio_change_handler = None + + self.set_border_width(style.DEFAULT_SPACING * 2) + self.set_spacing(style.DEFAULT_SPACING) + group = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) + + self._radio_alert_box = gtk.HBox(spacing=style.DEFAULT_SPACING) + self._jabber_alert_box = gtk.HBox(spacing=style.DEFAULT_SPACING) + + separator_wireless = gtk.HSeparator() + self.pack_start(separator_wireless, expand=False) + separator_wireless.show() + + label_wireless = gtk.Label(_('Wireless')) + label_wireless.set_alignment(0, 0) + self.pack_start(label_wireless, expand=False) + label_wireless.show() + box_wireless = gtk.VBox() + box_wireless.set_border_width(style.DEFAULT_SPACING * 2) + box_wireless.set_spacing(style.DEFAULT_SPACING) + box_radio = gtk.HBox(spacing=style.DEFAULT_SPACING) + label_radio = gtk.Label(_('Radio:')) + label_radio.set_alignment(1, 0.5) + label_radio.modify_fg(gtk.STATE_NORMAL, + style.COLOR_SELECTION_GREY.get_gdk_color()) + box_radio.pack_start(label_radio, expand=False) + group.add_widget(label_radio) + label_radio.show() + self._button = gtk.CheckButton() + self._button.set_alignment(0, 0) + box_radio.pack_start(self._button, expand=False) + self._button.show() + box_wireless.pack_start(box_radio, expand=False) + box_radio.show() + + self._radio_alert = InlineAlert() + label_radio_error = gtk.Label() + group.add_widget(label_radio_error) + self._radio_alert_box.pack_start(label_radio_error, expand=False) + label_radio_error.show() + self._radio_alert_box.pack_start(self._radio_alert, expand=False) + box_wireless.pack_end(self._radio_alert_box, expand=False) + self._radio_alert_box.show() + if 'radio' in self.restart_alerts: + self._radio_alert.props.msg = self.restart_msg + self._radio_alert.show() + + self.pack_start(box_wireless, expand=False) + box_wireless.show() + + separator_mesh = gtk.HSeparator() + self.pack_start(separator_mesh, False) + separator_mesh.show() + + label_mesh = gtk.Label(_('Mesh')) + label_mesh.set_alignment(0, 0) + self.pack_start(label_mesh, expand=False) + label_mesh.show() + box_mesh = gtk.VBox() + box_mesh.set_border_width(style.DEFAULT_SPACING * 2) + box_mesh.set_spacing(style.DEFAULT_SPACING) + + box_server = gtk.HBox(spacing=style.DEFAULT_SPACING) + label_server = gtk.Label(_('Server:')) + label_server.set_alignment(1, 0.5) + label_server.modify_fg(gtk.STATE_NORMAL, + style.COLOR_SELECTION_GREY.get_gdk_color()) + box_server.pack_start(label_server, expand=False) + group.add_widget(label_server) + label_server.show() + self._entry = gtk.Entry() + self._entry.set_alignment(0) + self._entry.modify_bg(gtk.STATE_INSENSITIVE, + style.COLOR_WHITE.get_gdk_color()) + self._entry.modify_base(gtk.STATE_INSENSITIVE, + style.COLOR_WHITE.get_gdk_color()) + self._entry.set_size_request(int(gtk.gdk.screen_width() / 3), -1) + box_server.pack_start(self._entry, expand=False) + self._entry.show() + box_mesh.pack_start(box_server, expand=False) + box_server.show() + + self._jabber_alert = InlineAlert() + label_jabber_error = gtk.Label() + group.add_widget(label_jabber_error) + self._jabber_alert_box.pack_start(label_jabber_error, expand=False) + label_jabber_error.show() + self._jabber_alert_box.pack_start(self._jabber_alert, expand=False) + box_mesh.pack_end(self._jabber_alert_box, expand=False) + self._jabber_alert_box.show() + if 'jabber' in self.restart_alerts: + self._jabber_alert.props.msg = self.restart_msg + self._jabber_alert.show() + + self.pack_start(box_mesh, expand=False) + box_mesh.show() + + self.setup() + + def setup(self): + self._entry.set_text(self._model.get_jabber()) + try: + radio_state = self._model.get_radio() + except Exception, detail: + self._radio_alert.props.msg = detail + self._radio_alert.show() + else: + self._button.set_active(radio_state) + + self._jabber_valid = True + self._radio_valid = True + self.needs_restart = False + self._radio_change_handler = self._button.connect( \ + 'toggled', self.__radio_toggled_cb) + self._jabber_change_handler = self._entry.connect( \ + 'changed', self.__jabber_changed_cb) + + def undo(self): + self._button.disconnect(self._radio_change_handler) + self._entry.disconnect(self._jabber_change_handler) + self._model.undo() + self._jabber_alert.hide() + self._radio_alert.hide() + + def __radio_toggled_cb(self, widget, data=None): + radio_state = widget.get_active() + try: + self._model.set_radio(radio_state) + except Exception, detail: + self._radio_alert.props.msg = detail + self._radio_valid = False + else: + self._radio_valid = True + + if self._radio_valid and self._jabber_valid: + self.props.is_valid = True + else: + self.props.is_valid = False + + return False + + def __jabber_changed_cb(self, widget, data=None): + if self._jabber_sid: + gobject.source_remove(self._jabber_sid) + self._jabber_sid = gobject.timeout_add(self._APPLY_TIMEOUT, + self.__jabber_timeout_cb, widget) + + def __jabber_timeout_cb(self, widget): + self._jabber_sid = 0 + if widget.get_text() == self._model.get_jabber: + return + try: + self._model.set_jabber(widget.get_text()) + except ValueError, detail: + self._jabber_alert.props.msg = detail + self._jabber_valid = False + self.needs_restart = False + else: + self._jabber_alert.props.msg = self.restart_msg + self._jabber_valid = True + self.needs_restart = True + self.restart_alerts.append('jabber') + + if self._jabber_valid and self._radio_valid: + self.props.is_valid = True + else: + self.props.is_valid = False + + self._jabber_alert.show() + return False |