Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--data/icons/Makefile.am11
-rw-r--r--data/icons/module-about_me.svg7
-rw-r--r--data/icons/module-about_my_xo.svg6
-rw-r--r--data/icons/module-date_and_time.svg19
-rw-r--r--data/icons/module-frame.svg22
-rw-r--r--data/icons/module-language.svg59
-rw-r--r--data/icons/module-network.svg32
-rw-r--r--src/controlpanel/gui.py394
-rw-r--r--src/controlpanel/inlinealert.py83
-rw-r--r--src/controlpanel/model/Makefile.am13
-rw-r--r--src/controlpanel/model/__init__.py17
-rw-r--r--src/controlpanel/model/aboutme.py114
-rw-r--r--src/controlpanel/model/aboutxo.py77
-rw-r--r--src/controlpanel/model/datetime.py90
-rw-r--r--src/controlpanel/model/frame.py64
-rw-r--r--src/controlpanel/model/language.py125
-rw-r--r--src/controlpanel/model/network.py85
-rw-r--r--src/controlpanel/sectionview.py47
-rw-r--r--src/controlpanel/toolbar.py157
-rw-r--r--src/controlpanel/view/Makefile.am15
-rw-r--r--src/controlpanel/view/__init__.py17
-rw-r--r--src/controlpanel/view/aboutme.py229
-rw-r--r--src/controlpanel/view/aboutxo.py118
-rw-r--r--src/controlpanel/view/datetime.py146
-rw-r--r--src/controlpanel/view/frame.py256
-rw-r--r--src/controlpanel/view/language.py150
-rw-r--r--src/controlpanel/view/network.py209
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