Commits

Yuya Nishihara  committed 0e4f0a4 Merge

Merge with stable

  • Participants
  • Parent commits a383600, b32a6d7

Comments (0)

Files changed (5)

File icons/scalable/apps/tools-hooks.svg

+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   inkscape:export-ydpi="90.000000"
+   inkscape:export-xdpi="90.000000"
+   inkscape:export-filename="/home/jimmac/Desktop/wi-fi.png"
+   width="48px"
+   height="48px"
+   id="svg11300"
+   sodipodi:version="0.32"
+   inkscape:version="0.48.3.1 r9886"
+   sodipodi:docname="tools-spanner-hammer.svg"
+   version="1.1">
+  <defs
+     id="defs3">
+    <linearGradient
+       id="linearGradient3805">
+      <stop
+         style="stop-color:#c27e13;stop-opacity:1;"
+         offset="0"
+         id="stop3807" />
+      <stop
+         id="stop3813"
+         offset="0.26878971"
+         style="stop-color:#c27e13;stop-opacity:0.49803922;" />
+      <stop
+         style="stop-color:#c27e13;stop-opacity:0.9741379;"
+         offset="1"
+         id="stop3809" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3742">
+      <stop
+         style="stop-color:#666864;stop-opacity:1;"
+         offset="0"
+         id="stop3744" />
+      <stop
+         id="stop3750"
+         offset="0.5"
+         style="stop-color:#494a47;stop-opacity:0.49803922;" />
+      <stop
+         style="stop-color:#666864;stop-opacity:1;"
+         offset="1"
+         id="stop3746" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient2257">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop2259" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:0;"
+         offset="1"
+         id="stop2261" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3087">
+      <stop
+         style="stop-color:#3465a4;stop-opacity:1;"
+         offset="0"
+         id="stop3089" />
+      <stop
+         id="stop3095"
+         offset="0"
+         style="stop-color:#9fbce1;stop-opacity:1;" />
+      <stop
+         style="stop-color:#6b95ca;stop-opacity:1;"
+         offset="0"
+         id="stop2242" />
+      <stop
+         id="stop2244"
+         offset="0.75"
+         style="stop-color:#3d6aa5;stop-opacity:1;" />
+      <stop
+         style="stop-color:#386eb4;stop-opacity:1;"
+         offset="1"
+         id="stop3091" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3077">
+      <stop
+         style="stop-color:#98a0a9;stop-opacity:1;"
+         offset="0"
+         id="stop3079" />
+      <stop
+         style="stop-color:#c3d0dd;stop-opacity:1;"
+         offset="1"
+         id="stop3081" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3061">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop3063" />
+      <stop
+         style="stop-color:#000000;stop-opacity:1;"
+         offset="1"
+         id="stop3065" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3049">
+      <stop
+         style="stop-color:#b6b6b6;stop-opacity:1;"
+         offset="0"
+         id="stop3051" />
+      <stop
+         id="stop2262"
+         offset="0.5"
+         style="stop-color:#f2f2f2;stop-opacity:1;" />
+      <stop
+         style="stop-color:#fafafa;stop-opacity:1;"
+         offset="0.67612958"
+         id="stop2264" />
+      <stop
+         id="stop2268"
+         offset="0.84051722"
+         style="stop-color:#d8d8d8;stop-opacity:1;" />
+      <stop
+         id="stop2266"
+         offset="0.875"
+         style="stop-color:#f2f2f2;stop-opacity:1;" />
+      <stop
+         style="stop-color:#dbdbdb;stop-opacity:1;"
+         offset="1"
+         id="stop3053" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient3041">
+      <stop
+         style="stop-color:#000000;stop-opacity:1;"
+         offset="0"
+         id="stop3043" />
+      <stop
+         style="stop-color:#000000;stop-opacity:0;"
+         offset="1"
+         id="stop3045" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3049"
+       id="linearGradient3055"
+       x1="19.648342"
+       y1="42.253601"
+       x2="20.631224"
+       y2="6.7758031"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.87827,0,0,0.87827,-0.9434039,3.282804)" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3061"
+       id="linearGradient3067"
+       x1="50.152931"
+       y1="-3.6324477"
+       x2="25.291086"
+       y2="-4.3002653"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.87826988,0,0,0.87826988,1.6925621,2.9668149)" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3041"
+       id="radialGradient2260"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1,0,0,0.374558,0,24.47041)"
+       cx="24.8125"
+       cy="39.125"
+       fx="24.8125"
+       fy="39.125"
+       r="17.6875" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient2257"
+       id="linearGradient3139"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.007254,-0.02636526,0.02636526,1.007254,1.0126591,-1.2060461)"
+       x1="12.004697"
+       y1="35.688461"
+       x2="10.650805"
+       y2="33.194965" />
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3041"
+       id="radialGradient3145"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1,0,0,0.374558,0,24.47041)"
+       cx="24.8125"
+       cy="39.125"
+       fx="24.8125"
+       fy="39.125"
+       r="17.6875" />
+    <filter
+       y="0"
+       x="0"
+       filterUnits="userSpaceOnUse"
+       id="ShadowBlur">
+      <feGaussianBlur
+         id="feGaussianBlur6"
+         stdDeviation="2"
+         in="SourceGraphic" />
+    </filter>
+    <clipPath
+       id="clipoutline2"
+       clipPathUnits="userSpaceOnUse">
+      <path
+         d="M 55.8,12 L 56.56,12.16 C 65.4,9.22 85.24,20.8 80.82,31.12 C 79.7,32.74 79.89,35.62 81.24,37.43 L 56.43,62.25 C 55.04,60.86 51.95,60.89 50.53,62.25 L 9.5,101.63 C 5.76,105.22 8.7,108.27 10.28,109.88 L 17.64,117.24 C 19.25,118.84 23.5,121 26.52,117.8 L 65.24,76.99 C 66.59,75.57 66.6,72.52 65.24,71.12 C 65.24,71.12 65.25,71.1 65.24,71.1 L 90.15,46.2 C 92.78,48.57 95.263827,48.774854 97.62,48.13 C 102.17057,46.884568 104.70725,51.336643 107.43,54.59 L 106.2,55.8 C 104.38,57.64 104.55,60.74 106.59,62.78 C 108.63,64.82 111.74,64.99 113.56,63.17 L 121.7,55.04 C 123.52,53.2 123.35,50.1 121.3,48.07 C 119.46,46.22 116.79,45.98 114.93,47.3 C 109.35,41.04 112.28,40.1 104.92,31.95 L 91.13,17.86 C 81.2,8.12 68.3,8.13 55.8,12 z"
+         id="outline2" />
+    </clipPath>
+    <clipPath
+       id="clipoutline1"
+       clipPathUnits="userSpaceOnUse">
+      <path
+         d="M 22.42,17.2 L 35.38,30.15 L 32.88,38.93 L 24.1,41.43 L 11.14,28.47 L 5.28,34.33 L 20.17,55.79 L 37.73,52.45 L 99.36,118.07 C 108.8,127.58 121.53,113.32 112.7,105.2 L 46.85,42.92 L 50.86,25.16 L 28.72,10.89 L 22.42,17.2 z M 103.48,109.17 C 105.16,107.49 107.88,107.49 109.56,109.17 C 111.24,110.85 111.24,113.57 109.56,115.25 C 107.88,116.92 105.16,116.92 103.48,115.25 C 101.8,113.57 101.8,110.85 103.48,109.17 z"
+         id="outline1" />
+    </clipPath>
+    <linearGradient
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(-0.30267,-3.20138)"
+       y2="86.68"
+       x2="84.3"
+       y1="66.2"
+       x1="73.88"
+       id="lgdg"
+       xlink:href="#BlackTransparent" />
+    <linearGradient
+       gradientTransform="matrix(0.26,0,0,0.26,-106.57,101.57)"
+       gradientUnits="userSpaceOnUse"
+       y2="-241.2"
+       x2="655.42"
+       y1="-137.5"
+       x1="550.96"
+       id="lg55"
+       xlink:href="#WhiteTransparent" />
+    <linearGradient
+       gradientTransform="matrix(0.2,0,0,0.2,-80.94,88.3)"
+       gradientUnits="userSpaceOnUse"
+       y2="-186.26"
+       x2="607.66"
+       y1="-252.6"
+       x1="646.24"
+       id="lg51"
+       xlink:href="#WhiteTransparent" />
+    <linearGradient
+       y2="185.8"
+       x2="71.77"
+       y1="189.14"
+       x1="75.1"
+       id="lg19"
+       xlink:href="#BlackTransparent"
+       gradientTransform="matrix(1.05,0,0,1.05,0.13,-140.54)" />
+    <linearGradient
+       spreadMethod="pad"
+       gradientTransform="matrix(7.75e-2,7.75e-2,-0.36,0.36,78.84,-44.8)"
+       gradientUnits="userSpaceOnUse"
+       y2="124"
+       x2="626.1"
+       y1="124"
+       x1="577.03"
+       id="lg16"
+       xlink:href="#WhiteTransparent" />
+    <linearGradient
+       gradientTransform="matrix(0.3,0.3,-9e-2,9e-2,78.84,-44.8)"
+       gradientUnits="userSpaceOnUse"
+       y2="368.27"
+       x2="146.15"
+       y1="288.57"
+       x1="146.15"
+       id="lg12"
+       xlink:href="#WhiteTransparent" />
+    <linearGradient
+       gradientTransform="matrix(0.33,0.33,-9.44e-2,9.44e-2,9.03,-48.1)"
+       gradientUnits="userSpaceOnUse"
+       y2="-1.72"
+       x2="258.96"
+       y1="62.74"
+       x1="258.96"
+       id="lg09"
+       xlink:href="#WhiteTransparent" />
+    <linearGradient
+       spreadMethod="pad"
+       gradientTransform="matrix(9.98e-2,9.98e-2,-0.28,0.28,78.84,-44.8)"
+       gradientUnits="userSpaceOnUse"
+       y2="286.56"
+       x2="419.88"
+       y1="286.56"
+       x1="543.1"
+       id="lg05"
+       xlink:href="#BlackTransparent" />
+    <linearGradient
+       gradientTransform="matrix(8.07e-2,7.75e-2,-0.35,0.36,77.14,-45.89)"
+       gradientUnits="userSpaceOnUse"
+       y2="254.83"
+       x2="615.59"
+       y1="254.36"
+       x1="512.7"
+       id="lg02"
+       xlink:href="#WhiteTransparent" />
+    <linearGradient
+       gradientTransform="matrix(0.1,0.1,-0.22,0.2,75.6,-33.53)"
+       gradientUnits="userSpaceOnUse"
+       y2="118.3"
+       x2="638.3"
+       y1="69.99"
+       x1="637.03"
+       id="lg1999"
+       xlink:href="#WhiteTransparent" />
+    <linearGradient
+       id="BlackTransparent">
+      <stop
+         id="stop3272"
+         offset="0"
+         style="stop-color:black;stop-opacity:1" />
+      <stop
+         id="stop3274"
+         offset="1"
+         style="stop-color:black;stop-opacity:0" />
+    </linearGradient>
+    <linearGradient
+       id="WhiteTransparent">
+      <stop
+         id="stop3267"
+         offset="0"
+         style="stop-color:white;stop-opacity:1" />
+      <stop
+         id="stop3269"
+         offset="1"
+         style="stop-color:white;stop-opacity:0" />
+    </linearGradient>
+    <filter
+       y="0"
+       x="0"
+       filterUnits="userSpaceOnUse"
+       id="ShadowBlur-3">
+      <feGaussianBlur
+         id="feGaussianBlur6-4"
+         stdDeviation="2"
+         in="SourceGraphic" />
+    </filter>
+    <clipPath
+       id="clipoutline2-5"
+       clipPathUnits="userSpaceOnUse">
+      <path
+         d="M 55.8,12 L 56.56,12.16 C 65.4,9.22 85.24,20.8 80.82,31.12 C 79.7,32.74 79.89,35.62 81.24,37.43 L 56.43,62.25 C 55.04,60.86 51.95,60.89 50.53,62.25 L 9.5,101.63 C 5.76,105.22 8.7,108.27 10.28,109.88 L 17.64,117.24 C 19.25,118.84 23.5,121 26.52,117.8 L 65.24,76.99 C 66.59,75.57 66.6,72.52 65.24,71.12 C 65.24,71.12 65.25,71.1 65.24,71.1 L 90.15,46.2 C 92.78,48.57 95.263827,48.774854 97.62,48.13 C 102.17057,46.884568 104.70725,51.336643 107.43,54.59 L 106.2,55.8 C 104.38,57.64 104.55,60.74 106.59,62.78 C 108.63,64.82 111.74,64.99 113.56,63.17 L 121.7,55.04 C 123.52,53.2 123.35,50.1 121.3,48.07 C 119.46,46.22 116.79,45.98 114.93,47.3 C 109.35,41.04 112.28,40.1 104.92,31.95 L 91.13,17.86 C 81.2,8.12 68.3,8.13 55.8,12 z"
+         id="outline2-9" />
+    </clipPath>
+    <clipPath
+       id="clipoutline1-1"
+       clipPathUnits="userSpaceOnUse">
+      <path
+         d="M 22.42,17.2 L 35.38,30.15 L 32.88,38.93 L 24.1,41.43 L 11.14,28.47 L 5.28,34.33 L 20.17,55.79 L 37.73,52.45 L 99.36,118.07 C 108.8,127.58 121.53,113.32 112.7,105.2 L 46.85,42.92 L 50.86,25.16 L 28.72,10.89 L 22.42,17.2 z M 103.48,109.17 C 105.16,107.49 107.88,107.49 109.56,109.17 C 111.24,110.85 111.24,113.57 109.56,115.25 C 107.88,116.92 105.16,116.92 103.48,115.25 C 101.8,113.57 101.8,110.85 103.48,109.17 z"
+         id="outline1-3" />
+    </clipPath>
+    <linearGradient
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(-0.30267,-3.20138)"
+       y2="86.68"
+       x2="84.3"
+       y1="66.2"
+       x1="73.88"
+       id="lgdg-1"
+       xlink:href="#BlackTransparent-0" />
+    <linearGradient
+       gradientTransform="matrix(0.26,0,0,0.26,-106.57,101.57)"
+       gradientUnits="userSpaceOnUse"
+       y2="-241.2"
+       x2="655.42"
+       y1="-137.5"
+       x1="550.96"
+       id="lg55-4"
+       xlink:href="#WhiteTransparent-1" />
+    <linearGradient
+       gradientTransform="matrix(0.2,0,0,0.2,-80.94,88.3)"
+       gradientUnits="userSpaceOnUse"
+       y2="-186.26"
+       x2="607.66"
+       y1="-252.6"
+       x1="646.24"
+       id="lg51-0"
+       xlink:href="#WhiteTransparent-1" />
+    <linearGradient
+       y2="6037.5261"
+       x2="2375.4978"
+       y1="6145.3078"
+       x1="2482.957"
+       id="lg19-8"
+       xlink:href="#BlackTransparent-0"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       spreadMethod="pad"
+       gradientTransform="matrix(0.0775,0.0775,-0.36,0.36,78.84,-44.8)"
+       gradientUnits="userSpaceOnUse"
+       y2="124"
+       x2="626.1"
+       y1="124"
+       x1="577.03"
+       id="lg16-0"
+       xlink:href="#WhiteTransparent-1" />
+    <linearGradient
+       gradientTransform="matrix(0.3,0.3,-0.09,0.09,78.84,-44.8)"
+       gradientUnits="userSpaceOnUse"
+       y2="368.27"
+       x2="146.15"
+       y1="288.57"
+       x1="146.15"
+       id="lg12-5"
+       xlink:href="#WhiteTransparent-1" />
+    <linearGradient
+       gradientTransform="matrix(0.33,0.33,-0.0944,0.0944,9.03,-48.1)"
+       gradientUnits="userSpaceOnUse"
+       y2="-1.72"
+       x2="258.96"
+       y1="62.74"
+       x1="258.96"
+       id="lg09-3"
+       xlink:href="#WhiteTransparent-1" />
+    <linearGradient
+       spreadMethod="pad"
+       gradientTransform="matrix(0.0998,0.0998,-0.28,0.28,78.84,-44.8)"
+       gradientUnits="userSpaceOnUse"
+       y2="286.56"
+       x2="419.88"
+       y1="286.56"
+       x1="543.1"
+       id="lg05-6"
+       xlink:href="#BlackTransparent-0" />
+    <linearGradient
+       gradientTransform="matrix(0.0807,0.0775,-0.35,0.36,77.14,-45.89)"
+       gradientUnits="userSpaceOnUse"
+       y2="254.83"
+       x2="615.59"
+       y1="254.36"
+       x1="512.7"
+       id="lg02-7"
+       xlink:href="#WhiteTransparent-1" />
+    <linearGradient
+       gradientTransform="matrix(0.1,0.1,-0.22,0.2,75.6,-33.53)"
+       gradientUnits="userSpaceOnUse"
+       y2="118.3"
+       x2="638.3"
+       y1="69.99"
+       x1="637.03"
+       id="lg1999-8"
+       xlink:href="#WhiteTransparent-1" />
+    <linearGradient
+       id="BlackTransparent-0">
+      <stop
+         id="stop3550"
+         offset="0"
+         style="stop-color:black;stop-opacity:1" />
+      <stop
+         id="stop3552"
+         offset="1"
+         style="stop-color:black;stop-opacity:0" />
+    </linearGradient>
+    <linearGradient
+       id="WhiteTransparent-1">
+      <stop
+         id="stop3545"
+         offset="0"
+         style="stop-color:white;stop-opacity:1" />
+      <stop
+         id="stop3547"
+         offset="1"
+         style="stop-color:white;stop-opacity:0" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3742"
+       id="linearGradient3748"
+       x1="-32.163666"
+       y1="11.982862"
+       x2="-34.732647"
+       y2="14.757363"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(69.519608,-1.684877)" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3077"
+       id="linearGradient3758"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.87827,0,0,0.87827,1.8671111,3.903835)"
+       x1="29.555725"
+       y1="17.722818"
+       x2="30.433241"
+       y2="18.629581" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3077"
+       id="linearGradient3760"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.87827,0,0,0.87827,1.8671111,3.903835)"
+       x1="38.227654"
+       y1="13.602527"
+       x2="37.53537"
+       y2="6.6285896" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3805"
+       id="linearGradient3811"
+       x1="15.909903"
+       y1="29.261671"
+       x2="18.384775"
+       y2="31.736544"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3742"
+       id="linearGradient3820"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(69.519608,-1.684877)"
+       x1="-32.163666"
+       y1="11.982862"
+       x2="-34.732647"
+       y2="14.757363" />
+    <linearGradient
+       gradientTransform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,118.87476,36.669709)"
+       inkscape:collect="always"
+       xlink:href="#linearGradient3805-8"
+       id="linearGradient3811-9"
+       x1="15.909903"
+       y1="29.261671"
+       x2="18.384775"
+       y2="31.736544"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       id="linearGradient3805-8">
+      <stop
+         style="stop-color:#c27e13;stop-opacity:1;"
+         offset="0"
+         id="stop3807-3" />
+      <stop
+         id="stop3813-6"
+         offset="0.26878971"
+         style="stop-color:#c27e13;stop-opacity:0.49803922;" />
+      <stop
+         style="stop-color:#c27e13;stop-opacity:0.9741379;"
+         offset="1"
+         id="stop3809-4" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3077-1"
+       id="linearGradient3758-7"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.87827,0,0,0.87827,150.91626,23.296793)"
+       x1="29.555725"
+       y1="17.722818"
+       x2="30.433241"
+       y2="18.629581" />
+    <linearGradient
+       id="linearGradient3077-1">
+      <stop
+         style="stop-color:#98a0a9;stop-opacity:1;"
+         offset="0"
+         id="stop3079-5" />
+      <stop
+         style="stop-color:#c3d0dd;stop-opacity:1;"
+         offset="1"
+         id="stop3081-9" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3061-4"
+       id="linearGradient3067-4"
+       x1="50.152931"
+       y1="-3.6324477"
+       x2="25.291086"
+       y2="-4.3002653"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.87826988,0,0,0.87826988,120.30601,-90.957017)" />
+    <linearGradient
+       id="linearGradient3061-4">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop3063-2" />
+      <stop
+         style="stop-color:#000000;stop-opacity:1;"
+         offset="1"
+         id="stop3065-0" />
+    </linearGradient>
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3041-0"
+       id="radialGradient3145-0"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1,0,0,0.374558,0,24.47041)"
+       cx="24.8125"
+       cy="39.125"
+       fx="24.8125"
+       fy="39.125"
+       r="17.6875" />
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient3041-0">
+      <stop
+         style="stop-color:#000000;stop-opacity:1;"
+         offset="0"
+         id="stop3043-0" />
+      <stop
+         style="stop-color:#000000;stop-opacity:0;"
+         offset="1"
+         id="stop3045-8" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3077-1"
+       id="linearGradient3760-1"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.87827,0,0,0.87827,1.8671111,3.903835)"
+       x1="38.227654"
+       y1="13.602527"
+       x2="37.53537"
+       y2="6.6285896" />
+    <linearGradient
+       id="linearGradient3850">
+      <stop
+         style="stop-color:#98a0a9;stop-opacity:1;"
+         offset="0"
+         id="stop3852" />
+      <stop
+         style="stop-color:#c3d0dd;stop-opacity:1;"
+         offset="1"
+         id="stop3854" />
+    </linearGradient>
+    <linearGradient
+       y2="6.6285896"
+       x2="37.53537"
+       y1="13.602527"
+       x1="38.227654"
+       gradientTransform="matrix(0.87827,0,0,0.87827,150.91626,23.296793)"
+       gradientUnits="userSpaceOnUse"
+       id="linearGradient3863"
+       xlink:href="#linearGradient3077-1"
+       inkscape:collect="always" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3805-8"
+       id="linearGradient4719"
+       x1="166.95419"
+       y1="48.512695"
+       x2="171.73305"
+       y2="48.512695"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3805-8"
+       id="linearGradient4725"
+       gradientUnits="userSpaceOnUse"
+       x1="166.95419"
+       y1="48.512695"
+       x2="171.73305"
+       y2="48.512695" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3061-5"
+       id="linearGradient3067-0"
+       x1="50.152931"
+       y1="-3.6324477"
+       x2="25.291086"
+       y2="-4.3002653"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.87826988,0,0,0.87826988,4.0995359,1.1235463)" />
+    <linearGradient
+       id="linearGradient3061-5">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop3063-6" />
+      <stop
+         style="stop-color:#000000;stop-opacity:1;"
+         offset="1"
+         id="stop3065-7" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3049-7"
+       id="linearGradient3055-1"
+       x1="19.648342"
+       y1="42.253601"
+       x2="20.631224"
+       y2="6.7758031"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.87827,0,0,0.87827,2.0566,3.720093)" />
+    <linearGradient
+       id="linearGradient3049-7">
+      <stop
+         style="stop-color:#b6b6b6;stop-opacity:1;"
+         offset="0"
+         id="stop3051-5" />
+      <stop
+         id="stop2262-9"
+         offset="0.5"
+         style="stop-color:#f2f2f2;stop-opacity:1;" />
+      <stop
+         style="stop-color:#fafafa;stop-opacity:1;"
+         offset="0.67612958"
+         id="stop2264-8" />
+      <stop
+         id="stop2268-7"
+         offset="0.84051722"
+         style="stop-color:#d8d8d8;stop-opacity:1;" />
+      <stop
+         id="stop2266-3"
+         offset="0.875"
+         style="stop-color:#f2f2f2;stop-opacity:1;" />
+      <stop
+         style="stop-color:#dbdbdb;stop-opacity:1;"
+         offset="1"
+         id="stop3053-7" />
+    </linearGradient>
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3041-7"
+       id="radialGradient3145-9"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1,0,0,0.374558,0,24.47041)"
+       cx="24.8125"
+       cy="39.125"
+       fx="24.8125"
+       fy="39.125"
+       r="17.6875" />
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient3041-7">
+      <stop
+         style="stop-color:#000000;stop-opacity:1;"
+         offset="0"
+         id="stop3043-8" />
+      <stop
+         style="stop-color:#000000;stop-opacity:0;"
+         offset="1"
+         id="stop3045-6" />
+    </linearGradient>
+    <radialGradient
+       r="17.6875"
+       fy="39.125"
+       fx="24.8125"
+       cy="39.125"
+       cx="24.8125"
+       gradientTransform="matrix(1,0,0,0.374558,0,24.47041)"
+       gradientUnits="userSpaceOnUse"
+       id="radialGradient3173"
+       xlink:href="#linearGradient3041-7"
+       inkscape:collect="always" />
+    <filter
+       id="filter3081"
+       inkscape:label="Matte jelly"
+       x="0"
+       y="0"
+       width="1"
+       height="1"
+       inkscape:menu="ABCs"
+       inkscape:menu-tooltip="Bulging, matte jelly covering"
+       color-interpolation-filters="sRGB">
+      <feColorMatrix
+         id="feColorMatrix3083"
+         values="1"
+         result="result0"
+         in="SourceGraphic"
+         type="saturate" />
+      <feGaussianBlur
+         id="feGaussianBlur3085"
+         in="SourceAlpha"
+         stdDeviation="7" />
+      <feSpecularLighting
+         id="feSpecularLighting3087"
+         surfaceScale="5"
+         specularConstant="0.89999998"
+         specularExponent="25"
+         result="result1">
+        <feDistantLight
+           id="feDistantLight3089"
+           elevation="60"
+           azimuth="225" />
+      </feSpecularLighting>
+      <feComposite
+         id="feComposite3091"
+         in2="result0"
+         operator="atop" />
+    </filter>
+  </defs>
+  <sodipodi:namedview
+     stroke="#204a87"
+     fill="#3465a4"
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="0.25490196"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="4"
+     inkscape:cx="13.9707"
+     inkscape:cy="17.102584"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:grid-bbox="true"
+     inkscape:document-units="px"
+     inkscape:showpageshadow="false"
+     inkscape:window-width="1215"
+     inkscape:window-height="776"
+     inkscape:window-x="65"
+     inkscape:window-y="24"
+     inkscape:window-maximized="1"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:object-nodes="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid3762" />
+    <sodipodi:guide
+       orientation="1,0"
+       position="169.35207,15.556349"
+       id="guide3928" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata4">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:creator>
+          <cc:Agent>
+            <dc:title>Jakub Steiner</dc:title>
+          </cc:Agent>
+        </dc:creator>
+        <dc:source>http://jimmac.musichall.cz</dc:source>
+        <cc:license
+           rdf:resource="http://creativecommons.org/licenses/by-sa/2.0/" />
+        <dc:title></dc:title>
+        <dc:subject>
+          <rdf:Bag>
+            <rdf:li>preferences</rdf:li>
+            <rdf:li>settings</rdf:li>
+            <rdf:li>control panel</rdf:li>
+            <rdf:li>tweaks</rdf:li>
+            <rdf:li>system</rdf:li>
+          </rdf:Bag>
+        </dc:subject>
+      </cc:Work>
+      <cc:License
+         rdf:about="http://creativecommons.org/licenses/by-sa/2.0/">
+        <cc:permits
+           rdf:resource="http://web.resource.org/cc/Reproduction" />
+        <cc:permits
+           rdf:resource="http://web.resource.org/cc/Distribution" />
+        <cc:requires
+           rdf:resource="http://web.resource.org/cc/Notice" />
+        <cc:requires
+           rdf:resource="http://web.resource.org/cc/Attribution" />
+        <cc:permits
+           rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
+        <cc:requires
+           rdf:resource="http://web.resource.org/cc/ShareAlike" />
+      </cc:License>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer">
+    <g
+       style="font-size:225.01298523px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;display:inline;filter:url(#filter3081);font-family:Arial;-inkscape-font-specification:Arial"
+       id="text3004"
+       transform="matrix(0.11786773,0.07371716,-0.06131675,0.0921904,136.08204,5.045433)">
+      <path
+         d="m -564.05911,451.77299 33.02894,0 -28.63481,40.33833 0,317.71569 c 0,16.18751 -1.8313,29.04225 -5.49348,38.56424 -3.66245,9.52205 -10.27294,17.26786 -19.83146,23.23743 -9.55877,5.96958 -22.10221,8.95437 -37.63035,8.95437 -15.08883,0 -27.43084,-2.60025 -37.02606,-7.80074 -9.59532,-5.20049 -16.44385,-12.72655 -20.54562,-22.57821 -4.10182,-9.85161 -0.94637,-67.6195 -0.94637,-67.6195 l 0,-46.59802 34.84456,66.30082 -17.7611,-14.8832 c 0,0 1.62514,49.92687 2.92529,53.24124 2.6002,6.62883 7.06823,11.73777 13.40409,15.32681 6.33575,3.5891 14.08155,5.38363 23.23743,5.38362 15.67463,10e-6 26.8447,-3.55243 33.51023,-10.65736 6.6653,-7.10486 9.99801,-20.76531 9.99814,-40.98137 l 0,-317.66076 z"
+         id="path3009"
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="cccscssssccccccssscc" />
+    </g>
+    <path
+       transform="matrix(0.751118,0,0,0.578703,16.56048,18.115823)"
+       d="m 42.5,39.125 a 17.6875,6.625 0 1 1 -35.375,0 17.6875,6.625 0 1 1 35.375,0 z"
+       sodipodi:ry="6.625"
+       sodipodi:rx="17.6875"
+       sodipodi:cy="39.125"
+       sodipodi:cx="24.8125"
+       id="path2258-4"
+       style="opacity:0.19886367;color:#000000;fill:url(#radialGradient3173);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
+       sodipodi:type="arc" />
+    <path
+       sodipodi:type="arc"
+       style="opacity:0.3125;color:#000000;fill:url(#radialGradient3145-9);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
+       id="path3039-2"
+       sodipodi:cx="24.8125"
+       sodipodi:cy="39.125"
+       sodipodi:rx="17.6875"
+       sodipodi:ry="6.625"
+       d="m 42.5,39.125 a 17.6875,6.625 0 1 1 -35.375,0 17.6875,6.625 0 1 1 35.375,0 z"
+       transform="matrix(0.836071,0,0,0.685436,-4.94,15.470223)" />
+    <path
+       style="color:#000000;fill:url(#linearGradient3055-1);fill-opacity:1;fill-rule:nonzero;stroke:#888a85;stroke-width:0.9999997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+       d="m 17.42632,19.968093 18.99259,19.43172 c 0.76849,0.87827 3.20351,1.55706 4.83049,0 1.57112,-1.50361 1.20762,-3.62286 -0.32935,-5.15984 l -18.22411,-19.5415 c 2.25,-6.25 -2.30388,-11.49957 -8.17888,-10.37457 l -1.26252,1.15273 3.95222,3.73264 0.21956,3.29352 -2.95084,2.6936 -3.5264,-0.38814 -3.62286,-3.4033 c 0,0 -1.27012,1.25491 -1.27012,1.25491 -0.59076,5.64131 5.30772,10.68323 11.37022,7.30823 z"
+       id="path2140-8"
+       sodipodi:nodetypes="cczcccccccccsc"
+       inkscape:connector-curvature="0" />
+    <path
+       sodipodi:nodetypes="cczccccccccccc"
+       id="path3057-7"
+       d="m 17.637,18.692513 19.20288,20.02761 c 0.59491,0.67989 2.47993,1.20537 3.73941,0 1.21626,-1.16399 0.93485,-2.80456 -0.25496,-3.99437 L 21.8328,15.104593 c 1.5,-6.5 -1.85879,-10.00464 -6.85879,-9.87964 l -0.27013,0.27338 3.60292,3.23627 0.13017,4.18179 -3.61392,3.29849 -4.2423,-0.45821 -3.17658,-2.99148 -0.35264,0.43005 c -0.3125,5.96875 6.49172,8.68477 10.58547,5.49727 z"
+       style="opacity:0.42613639;color:#000000;fill:none;stroke:#ffffff;stroke-width:0.99999917;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+       inkscape:connector-curvature="0" />
+    <rect
+       style="opacity:0.17045456;color:#000000;fill:none;stroke:url(#linearGradient3067-0);stroke-width:0.99999708;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+       id="rect3059-7"
+       width="23.268272"
+       height="2.055491"
+       x="26.956554"
+       y="-3.1451559"
+       rx="0.88388062"
+       ry="0.88388062"
+       transform="matrix(0.69793809,0.7161581,-0.7161581,0.69793809,0,0)" />
+    <path
+       sodipodi:type="arc"
+       style="color:#000000;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#a1a1a1;stroke-width:1.13860166;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
+       id="path2146-3"
+       sodipodi:cx="41.875"
+       sodipodi:cy="37.5"
+       sodipodi:rx="1.375"
+       sodipodi:ry="1.375"
+       d="m 43.25,37.5 a 1.375,1.375 0 1 1 -2.75,0 1.375,1.375 0 1 1 2.75,0 z"
+       transform="matrix(0.87827,0,0,0.87827,1.94682,3.829873)" />
+  </g>
+  <style
+     id="style3292"
+     type="text/css">
+	/* Specular Highlighting */
+		.low-specularity	{opacity:0.25;}
+		.specularity		{opacity:0.5;}
+		.high-specularity	{opacity:0.75;}
+		.full-specularity	{opacity:1;}
+
+	/* Shading */
+		.low-shade	{opacity:0.25;}
+		.shade		{opacity:0.5;}
+		.high-shade	{opacity:0.75;}
+		.full-shade	{opacity:1;}
+
+	/* Tango palette fill/stroke */
+		.black		{fill:#000;}
+		.aluminium1	{fill:#eeeeec;}
+		.aluminium2	{fill:#d3d7cf;}
+		.aluminium6	{fill:#2e3436;}
+		.chocolate3	{fill:#8f5902;}
+		.chocolate2	{fill:#c17d11;}
+		.aluminium4	{fill:#888a85;}
+
+	/* Shadows: Back-Shadows &amp; Base Shadows */
+		.base-shadow	{opacity:0.4;}
+		.outline-big	{stroke:black;stroke-width:8;opacity:0.25;stroke-linejoin:round;}
+		.outline-small	{stroke:black;stroke-width:4;opacity:0.5;stroke-linejoin:round;}
+		.stroke-highlight	{fill:none;stroke:white;stroke-opacity:0.2;stroke-width:4;stroke-linejoin:round;}
+	</style>
+  <style
+     id="style3570"
+     type="text/css">
+	/* Specular Highlighting */
+		.low-specularity	{opacity:0.25;}
+		.specularity		{opacity:0.5;}
+		.high-specularity	{opacity:0.75;}
+		.full-specularity	{opacity:1;}
+
+	/* Shading */
+		.low-shade	{opacity:0.25;}
+		.shade		{opacity:0.5;}
+		.high-shade	{opacity:0.75;}
+		.full-shade	{opacity:1;}
+
+	/* Tango palette fill/stroke */
+		.black		{fill:#000;}
+		.aluminium1	{fill:#eeeeec;}
+		.aluminium2	{fill:#d3d7cf;}
+		.aluminium6	{fill:#2e3436;}
+		.chocolate3	{fill:#8f5902;}
+		.chocolate2	{fill:#c17d11;}
+		.aluminium4	{fill:#888a85;}
+
+	/* Shadows: Back-Shadows &amp; Base Shadows */
+		.base-shadow	{opacity:0.4;}
+		.outline-big	{stroke:black;stroke-width:8;opacity:0.25;stroke-linejoin:round;}
+		.outline-small	{stroke:black;stroke-width:4;opacity:0.5;stroke-linejoin:round;}
+		.stroke-highlight	{fill:none;stroke:white;stroke-opacity:0.2;stroke-width:4;stroke-linejoin:round;}
+	</style>
+</svg>

File tortoisehg/hgqt/branchop.py

             QString - open new named branch
         '''
         if self.branchCombo.isEnabled():
-            self.branchop = self.branchCombo.currentText()
+            # branch name cannot start/end with whitespace (see dirstate._branch)
+            self.branchop = self.branchCombo.currentText().trimmed()
         elif self.closebranch.isChecked():
             self.branchop = False
         else:

File tortoisehg/hgqt/customtools.py

 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2, incorporated herein by reference.
 
+import re
+
 from tortoisehg.hgqt.i18n import _
 from tortoisehg.hgqt import qtlib
 from tortoisehg.util import hglib
             w.refresh()
 
 
+class HooksFrame(QFrame):
+    def __init__(self, ini, parent=None, **opts):
+        super(HooksFrame, self).__init__(parent, **opts)
+        self.ini = ini
+        # The frame is created empty, and will be populated on 'refresh',
+        # which usually happens when the frames is activated
+        self.setValue({})
+
+        topbox = QHBoxLayout()
+        self.setLayout(topbox)
+        self.hooktable = QTableWidget(0, 3, parent)
+        self.hooktable.setHorizontalHeaderLabels((_('Type'), _('Name'), _('Command')))
+        self.hooktable.sortByColumn(0, Qt.AscendingOrder)
+        self.hooktable.setSelectionBehavior(self.hooktable.SelectRows)
+        self.hooktable.setSelectionMode(self.hooktable.SingleSelection)
+        self.hooktable.cellDoubleClicked.connect(self.editHook)
+        topbox.addWidget(self.hooktable)
+        buttonbox = QVBoxLayout()
+        self.btnnew = QPushButton(_('New hook'))
+        buttonbox.addWidget(self.btnnew)
+        self.btnnew.clicked.connect(self.newHook)
+        self.btnedit = QPushButton(_('Edit hook'))
+        buttonbox.addWidget(self.btnedit)
+        self.btnedit.clicked.connect(self.editCurrentHook)
+        self.btndelete = QPushButton(_('Delete hook'))
+        self.btndelete.clicked.connect(self.deleteCurrentHook)
+        buttonbox.addWidget(self.btndelete)
+        buttonbox.addStretch()
+        topbox.addLayout(buttonbox)
+
+    def newHook(self):
+        td = HookConfigDialog(self)
+        res = td.exec_()
+        if res:
+            hooktype, command, hookname = td.value()
+            # Does the new hook already exist?
+            hooks = self.value()
+            if hooktype in hooks:
+                existingcommand = hooks[hooktype].get(hookname, None)
+                if existingcommand is not None:
+                    if existingcommand == command:
+                        # The command already exists "as is"!
+                        return
+                    if not qtlib.QuestionMsgBox(
+                            _('Replace existing hook?'),
+                            _('There is an existing %s.%s hook.\n\n'
+                            'Do you want to replace it?')
+                            % (hooktype, hookname),
+                            parent=self):
+                        return
+                    # Delete existing matching hooks in reverse order
+                    # (otherwise the row numbers will be wrong after the first
+                    # deletion)
+                    for r in reversed(self.findHooks(
+                            hooktype=hooktype, hookname=hookname)):
+                        self.deleteHook(r)
+            self.hooktable.setSortingEnabled(False)
+            row = self.hooktable.rowCount()
+            self.hooktable.insertRow(row)
+            for c, text in enumerate((hooktype, hookname, command)):
+                self.hooktable.setItem(row, c, QTableWidgetItem(text))
+            # Make the hook column not editable (a dialog is used to edit it)
+            itemhook = self.hooktable.item(row, 0)
+            itemhook.setFlags(itemhook.flags() & ~Qt.ItemIsEditable)
+            self.hooktable.setSortingEnabled(True)
+            self.hooktable.resizeColumnsToContents()
+            self.updatebuttons()
+
+    def editHook(self, r, c=0):
+        if r < 0:
+            r = 0
+        numrows = self.hooktable.rowCount()
+        if not numrows or r >= numrows:
+            return False
+        if c > 0:
+            # Only show the edit dialog when clicking
+            # on the "Hook Type" (i.e. the 1st) column
+            return False
+        hooktype = self.hooktable.item(r, 0).text()
+        hookname = self.hooktable.item(r, 1).text()
+        command = self.hooktable.item(r, 2).text()
+        td = HookConfigDialog(self, hooktype=hooktype,
+                              command=command, hookname=hookname)
+        res = td.exec_()
+        if res:
+            hooktype, command, hookname = td.value()
+            # Update the table
+            # Note that we must disable the ordering while the table
+            # is updated to avoid updating the wrong cell!
+            self.hooktable.setSortingEnabled(False)
+            self.hooktable.item(r, 0).setText(hooktype)
+            self.hooktable.item(r, 1).setText(hookname)
+            self.hooktable.item(r, 2).setText(command)
+            self.hooktable.setSortingEnabled(True)
+            self.hooktable.clearSelection()
+            self.hooktable.setState(self.hooktable.NoState)
+            self.hooktable.resizeColumnsToContents()
+        return bool(res)
+
+    def editCurrentHook(self):
+        self.editHook(self.hooktable.currentRow())
+
+    def deleteHook(self, row=None):
+        if row is None:
+            row = self.hooktable.currentRow()
+            if row < 0:
+                row = self.hooktable.rowCount() - 1
+        self.hooktable.removeRow(row)
+        self.hooktable.resizeColumnsToContents()
+        self.updatebuttons()
+
+    def deleteCurrentHook(self):
+        self.deleteHook()
+
+    def findHooks(self, hooktype=None, hookname=None, command=None):
+        matchingrows = []
+        for r in range(self.hooktable.rowCount()):
+            currhooktype = hglib.fromunicode(self.hooktable.item(r, 0).text())
+            currhookname = hglib.fromunicode(self.hooktable.item(r, 1).text())
+            currcommand = hglib.fromunicode(self.hooktable.item(r, 2).text())
+            matchinghooktype = hooktype is None or hooktype == currhooktype
+            matchinghookname = hookname is None or hookname == currhookname
+            matchingcommand = command is None or command == currcommand
+            if matchinghooktype and matchinghookname and matchingcommand:
+                matchingrows.append(r)
+        return matchingrows
+
+    def updatebuttons(self):
+        tablehasitems = self.hooktable.rowCount() > 0
+        self.btnedit.setEnabled(tablehasitems)
+        self.btndelete.setEnabled(tablehasitems)
+
+    def applyChanges(self, ini):
+        # widget.value() returns the _NEW_ values
+        # widget.curvalue returns the _ORIGINAL_ values (yes, this is a bit
+        # misleading! "cur" means "current" as in currently valid)
+        emitChanged = False
+        if not self.isDirty():
+            return emitChanged
+        emitChanged = True
+
+        # 1. Delete the previous hook configurations
+        section = 'hooks'
+        hooks = self.curvalue
+        for hooktype in hooks:
+            for keyname in hooks[hooktype]:
+                try:
+                    keyname = '%s.%s' % (hooktype, keyname)
+                    del ini[section][keyname]
+                except KeyError:
+                    pass
+        # 2. Save the new configurations
+        hooks = self.value()
+        for hooktype in hooks:
+            for field in sorted(hooks[hooktype]):
+                if field:
+                    keyname = '%s.%s' % (hooktype, field)
+                else:
+                    keyname = hooktype
+                value = hooks[hooktype][field]
+                if value:
+                    ini.set(section, keyname, value)
+        return emitChanged
+
+    ## common APIs for all edit widgets
+    def setValue(self, curvalue):
+        self.curvalue = dict(curvalue)
+
+    def value(self):
+        hooks = {}
+        for r in range(self.hooktable.rowCount()):
+            hooktype = hglib.fromunicode(self.hooktable.item(r, 0).text())
+            hookname = hglib.fromunicode(self.hooktable.item(r, 1).text())
+            command = hglib.fromunicode(self.hooktable.item(r, 2).text())
+            if hooktype not in hooks:
+                hooks[hooktype] = {}
+            hooks[hooktype][hookname] = command
+        return hooks
+
+    def isDirty(self):
+        return self.value() != self.curvalue
+
+    def gethooks(self):
+        hooks = {}
+        for key, value in self.ini.items('hooks'):
+            keyparts = key.split('.', 1)
+            hooktype = keyparts[0]
+            if len(keyparts) == 1:
+                name = ''
+            else:
+                name = keyparts[1]
+            if hooktype not in hooks:
+                hooks[hooktype] = {}
+            hooks[hooktype][name] = value
+        return hooks
+
+    def refresh(self):
+        hooks = self.gethooks()
+        self.setValue(hooks)
+        self.hooktable.setSortingEnabled(False)
+        self.hooktable.setRowCount(0)
+        for hooktype in sorted(hooks):
+            for name in sorted(hooks[hooktype]):
+                itemhook = QTableWidgetItem(hglib.tounicode(hooktype))
+                # Make the hook column not editable
+                # (a dialog is used to edit it)
+                itemhook.setFlags(itemhook.flags() & ~Qt.ItemIsEditable)
+                itemname = QTableWidgetItem(hglib.tounicode(name))
+                itemtool = QTableWidgetItem(
+                    hglib.tounicode(hooks[hooktype][name]))
+                self.hooktable.insertRow(self.hooktable.rowCount())
+                self.hooktable.setItem(self.hooktable.rowCount() - 1, 0, itemhook)
+                self.hooktable.setItem(self.hooktable.rowCount() - 1, 1, itemname)
+                self.hooktable.setItem(self.hooktable.rowCount() - 1, 2, itemtool)
+        self.hooktable.setSortingEnabled(True)
+        self.hooktable.resizeColumnsToContents()
+        self.updatebuttons()
+
+
 class ToolListBox(QListWidget):
     SEPARATOR = '------'
     def __init__(self, ini, parent=None, location=None, minimumwidth=None,
             icon = validtools.get(toolname, {}).get('icon', None)
             self.addOrInsertItem(toolname, icon=icon)
 
-class CustomToolConfigDialog(QDialog):
-    'Dialog for editing the a custom tool configuration'
+
+class CustomConfigDialog(QDialog):
+    '''Custom Config Dialog base class'''
+
+    def __init__(self, parent=None, dialogname='', **kwargs):
+        QDialog.__init__(self, parent, **kwargs)
+        self.dialogname = dialogname
+        self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
+
+        self.hbox = QHBoxLayout()
+        self.formvbox = QFormLayout()
+
+        self.hbox.addLayout(self.formvbox)
+        vbox = QVBoxLayout()
+        self.okbutton = QPushButton(_('OK'))
+        self.okbutton.clicked.connect(self.okClicked)
+        vbox.addWidget(self.okbutton)
+        self.cancelbutton = QPushButton(_('Cancel'))
+        self.cancelbutton.clicked.connect(self.reject)
+        vbox.addWidget(self.cancelbutton)
+        vbox.addStretch()
+        self.hbox.addLayout(vbox)
+        self.setLayout(self.hbox)
+        self.setMaximumHeight(self.sizeHint().height())
+        self._readsettings()
+
+    def value(self):
+        return None
+
+    def _genCombo(self, items, selecteditem=None):
+        index = 0
+        if selecteditem:
+            try:
+                index = items.index(selecteditem)
+            except ValueError:
+                pass
+        combo = QComboBox()
+        combo.addItems(items)
+        if index:
+            combo.setCurrentIndex(index)
+        return combo
+
+    def _addConfigItem(self, parent, label, configwidget, tooltip=None):
+        if tooltip:
+            configwidget.setToolTip(tooltip)
+        parent.addRow(label, configwidget)
+        return configwidget
+
+    def okClicked(self):
+        errormsg = self.validateForm()
+        if errormsg:
+            qtlib.WarningMsgBox(_('Missing information'), errormsg)
+            return
+        return self.accept()
+
+    def validateForm(self):
+        return '' # No error
+
+    def _readsettings(self):
+        s = QSettings()
+        if self.dialogname:
+            self.restoreGeometry(s.value(self.dialogname + '/geom').toByteArray())
+        return s
+
+    def _writesettings(self):
+        s = QSettings()
+        if self.dialogname:
+            s.setValue(self.dialogname + '/geom', self.saveGeometry())
+
+    def done(self, r):
+        self._writesettings()
+        super(CustomConfigDialog, self).done(r)
+
+
+class CustomToolConfigDialog(CustomConfigDialog):
+    '''Dialog for editing custom tool configurations'''
 
     _enablemappings = [(_('All items'), 'istrue'),
                        (_('Working directory'), 'iswd'),
     _defaulticonstring = _('<default icon>')
 
     def __init__(self, parent=None, toolname=None, toolconfig={}):
-        QDialog.__init__(self, parent)
+        super(CustomToolConfigDialog, self).__init__(parent,
+            dialogname='customtools',
+            windowTitle=_('Configure Custom Tool'),
+            windowIcon=qtlib.geticon(self._defaulticonname))
 
-        self.setWindowIcon(qtlib.geticon(self._defaulticonname))
-        self.setWindowTitle(_('Configure Custom Tool'))
-        self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
-
-        self.hbox = QHBoxLayout()
-        vbox = QFormLayout()
+        vbox = self.formvbox
 
         command = toolconfig.get('command', '')
         workingdir = toolconfig.get('workingdir', '')
             combo, _('When enabled, automatically show the Output Log when the '
             'command is run.\nDefault: False.'))
 
-        self.hbox.addLayout(vbox)
-        vbox = QVBoxLayout()
-        self.okbutton = QPushButton(_('OK'))
-        self.okbutton.clicked.connect(self.okClicked)
-        vbox.addWidget(self.okbutton)
-        self.cancelbutton = QPushButton(_('Cancel'))
-        self.cancelbutton.clicked.connect(self.reject)
-        vbox.addWidget(self.cancelbutton)
-        vbox.addStretch()
-        self.hbox.addLayout(vbox)
-        self.setLayout(self.hbox)
-        self.setMaximumHeight(self.sizeHint().height())
-        self._readsettings()
-
     def value(self):
         toolname = str(self.name.text()).strip()
         toolconfig = {
             toolconfig['icon'] = ''
         return toolname, toolconfig
 
-    def _genCombo(self, items, selecteditem=None):
-        index = 0
-        if selecteditem:
-            try:
-                index = items.index(selecteditem)
-            except ValueError:
-                pass
-        combo = QComboBox()
-        combo.addItems(items)
-        if index:
-            combo.setCurrentIndex(index)
-        return combo
-
-    def _addConfigItem(self, parent, label, configwidget, tooltip=None):
-        if tooltip:
-            configwidget.setToolTip(tooltip)
-        parent.addRow(label, configwidget)
-        return configwidget
-
     def _enable2label(self, value):
         return dict((v, l) for l, v in self._enablemappings).get(value)
 
-    def okClicked(self):
-        errormsg = self.validateForm()
-        if errormsg:
-            qtlib.WarningMsgBox(_('Missing information'), errormsg)
-            return
-        return self.accept()
-
     def validateForm(self):
         name, config = self.value()
         if not name:
             return _('You must set a command to run.')
         return '' # No error
 
-    def _readsettings(self):
-        s = QSettings()
-        self.restoreGeometry(s.value('customtools/geom').toByteArray())
 
-    def _writesettings(self):
-        s = QSettings()
-        s.setValue('customtools/geom', self.saveGeometry())
+class HookConfigDialog(CustomConfigDialog):
+    '''Dialog for editing the a hook configuration'''
 
-    def done(self, r):
-        self._writesettings()
-        super(CustomToolConfigDialog, self).done(r)
+    _hooktypes = (
+        'changegroup',
+        'commit',
+        'incoming',
+        'outgoing',
+        'prechangegroup',
+        'precommit',
+        'prelistkeys',
+        'preoutgoing',
+        'prepushkey',
+        'pretag',
+        'pretxnchangegroup',
+        'pretxncommit',
+        'preupdate',
+        'listkeys',
+        'pushkey',
+        'tag',
+        'update',
+    )
+    _rehookname = re.compile('^[^=\s]*$')
+
+    def __init__(self, parent=None, hooktype=None, command='', hookname=''):
+        super(HookConfigDialog, self).__init__(parent,
+            dialogname='hookconfigdialog',
+            windowTitle=_('Configure Hook'),
+            windowIcon=qtlib.geticon('tools-hooks'))
+
+        vbox = self.formvbox
+        combo = self._genCombo(self._hooktypes, hooktype)
+        self.hooktype = self._addConfigItem(vbox, _('Hook type'),
+            combo, _('Select the when you command will be run'))
+        self.name = self._addConfigItem(vbox, _('Tool name'),
+            QLineEdit(hookname), _('The hook name. It cannot contain spaces.'))
+        self.command = self._addConfigItem(vbox, _('Command'),
+            QLineEdit(command), _('The command that will be executed.\n'
+                 'To execute a python function prepend the command with '
+                 '"python:".\n'))
+
+    def value(self):
+        hooktype = str(self.hooktype.currentText())
+        hookname = str(self.name.text()).strip()
+        command = str(self.command.text()).strip()
+        return hooktype, command, hookname
+
+    def validateForm(self):
+        hooktype, command, hookname = self.value()
+        if hooktype not in self._hooktypes:
+            return _('You must set a valid hook type.')
+        if self._rehookname.match(hookname) is None:
+            return _('The hook name cannot contain any spaces, '
+                     'tabs or \'=\' characters.')
+        if not command:
+            return _('You must set a command to run.')
+        return '' # No error

File tortoisehg/hgqt/settings.py

 ({'name': 'tools', 'label': _('Tools'), 'icon': 'tools-spanner-hammer'}, (
     )),
 
+({'name': 'hooks', 'label': _('Hooks'), 'icon': 'tools-hooks'}, (
+    )),
+
 ({'name': 'issue', 'label': _('Issue Tracking'), 'icon': 'edit-file'}, (
     _fi(_('Issue Regex'), 'tortoisehg.issue.regex', genEditCombo,
         _('Defines the regex to match when picking up issue numbers.')),
                 self.validateextensions()
         elif name == 'tools':
             self.toolsFrame.refresh()
+        elif name == 'hooks':
+            self.hooksFrame.refresh()
         else:
             for row, e in enumerate(info):
                 if not e.cpath:
         self.stack.addWidget(frame)
         return (), [frame]
 
+    def fillHooksFrame(self):
+        self.hooksFrame = frame = customtools.HooksFrame(self.ini, parent=self)
+        self.stack.addWidget(frame)
+        return (), [frame]
+
     def eventFilter(self, obj, event):
         if event.type() in (QEvent.Enter, QEvent.FocusIn):
             self.desctext.setHtml(obj.toolTip())
         elif name == 'tools':
             toolsinfo, widgets = self.fillToolsFrame()
             self.pages[name] = name, toolsinfo, widgets
+        elif name == 'hooks':
+            hooksinfo, widgets = self.fillHooksFrame()
+            self.pages[name] = name, hooksinfo, widgets
         else:
             widgets = self.fillFrame(info)
             self.pages[name] = name, info, widgets
                 self.applyChangesForExtensions()
             elif name == 'tools':
                 self.applyChangesForTools()
+            elif name == 'hooks':
+                self.applyChangesForHooks()
             else:
                 for row, e in enumerate(info):
                     if not e.cpath:
         if self.toolsFrame.applyChanges(self.ini):
             self.restartRequested.emit(_('Tools'))
 
+    def applyChangesForHooks(self):
+        if self.hooksFrame.applyChanges(self.ini):
+            self.restartRequested.emit(_('Hooks'))
+
 def run(ui, *pats, **opts):
     return SettingsDialog(opts.get('alias') == 'repoconfig',
                           focus=opts.get('focus'))

File tortoisehg/util/patchctx.py

     def substate(self):
         return {}  # unapplied patch won't include .hgsubstate
 
+    # unlike changectx, `k in pctx` and `iter(pctx)` just iterates files
+    # included in the patch file, because it does not know the full manifest.
+
     def __contains__(self, key):
         return key in self._files
 
+    def __iter__(self):
+        return iter(sorted(self._files))
+
     def __str__(self):      return node.short(self.node())
     def node(self):         return self._node
     def files(self):        return self._files.keys()